Skip to main content
Glama
workspace.ts5.39 kB
import type { Tool } from '../types'; import { logError } from '@utils/logging'; import * as fs from 'node:fs'; import * as path from 'node:path'; // Import vscode conditionally since it's only available in VS Code extension host let vscode: typeof import('vscode') | undefined; try { vscode = require('vscode'); } catch { // Module not available, will handle in getWorkspacePath } export const enum TestEnvironment { NONE = 'none', VITEST = 'vitest', VSCODE = 'vscode' } export function getTestEnvironment(): TestEnvironment { if (process.env.VITEST) return TestEnvironment.VITEST; if (process.env.VSCODE_TEST === '1') return TestEnvironment.VSCODE; return TestEnvironment.NONE; } /** * Gets the workspace path. * In VS Code environment (including tests), uses workspace API. * In Vitest tests, uses test-monorepo fixture. * In production CLI, uses current working directory. */ export function getWorkspacePath(): string { const env = getTestEnvironment(); switch (env) { case TestEnvironment.VITEST: return path.resolve(process.cwd(), 'src/__tests__/fixtures/test-monorepo'); case TestEnvironment.VSCODE: // Use VS Code workspace API if available if (vscode) { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders?.length) { throw new Error('No workspace folder found'); } return workspaceFolders[0].uri.fsPath; } // Fall back to test monorepo path if running VS Code tests return path.resolve(process.cwd(), 'src/__tests__/fixtures/test-monorepo'); default: // Production environment return process.cwd(); } } export function validateWorkspacePath(workspacePath: string): string { if (!workspacePath || !fs.existsSync(workspacePath)) { throw new Error('Invalid workspace path'); } return path.resolve(workspacePath); } /** * Scans workspace for available tools */ export async function scanWorkspaceTools(workspacePath: string): Promise<Map<string, Tool>> { const validPath = validateWorkspacePath(workspacePath); const tools = new Map<string, Tool>(); try { // First scan package.json for scripts and workspaces await scanPackageJson(validPath, tools); // Then scan node_modules/.bin at workspace root await scanNodeModulesBin(validPath, tools); // Then scan workspace-specific tools await scanWorkspaceBin(validPath, tools); // Finally scan for global tools await scanGlobalBinaries(validPath, tools); return tools; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logError(`Error scanning workspace for tools: ${errorMessage}`); return new Map(); } } export async function scanPackageJson(validPath: string, tools: Map<string, Tool>): Promise<void> { const packageJsonPath = path.join(validPath, 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); if (packageJson.scripts) { for (const [name, script] of Object.entries(packageJson.scripts)) { tools.set(`npm:${name}`, { name: `npm:${name}`, location: packageJsonPath, workingDirectory: validPath, type: 'npm-script', context: { script } }); } } } } export async function scanNodeModulesBin(validPath: string, tools: Map<string, Tool>): Promise<void> { const binPath = path.join(validPath, 'node_modules', '.bin'); if (fs.existsSync(binPath)) { const files = fs.readdirSync(binPath); for (const file of files) { const filePath = path.join(binPath, file); if (fs.statSync(filePath).isFile()) { // Only add if not already found if (!tools.has(file)) { tools.set(file, { name: file, location: path.relative(validPath, filePath), workingDirectory: validPath, type: 'package-bin', context: {} }); } } } } } export async function scanWorkspaceBin(validPath: string, tools: Map<string, Tool>): Promise<void> { const workspaceBinPath = path.join(validPath, 'bin'); if (fs.existsSync(workspaceBinPath)) { const files = fs.readdirSync(workspaceBinPath); for (const file of files) { const filePath = path.join(workspaceBinPath, file); if (fs.statSync(filePath).isFile()) { // Only add if not already found in node_modules/.bin if (!tools.has(file)) { tools.set(file, { name: file, location: filePath, workingDirectory: validPath, type: 'workspace-bin', context: {} }); } } } } } export async function scanGlobalBinaries(_validPath: string, tools: Map<string, Tool>): Promise<void> { const globalTools = ['git', 'node', 'npm', 'yarn', 'pnpm']; for (const tool of globalTools) { // Only add as global if not already found as a package or workspace tool if (!tools.has(tool)) { tools.set(tool, { name: tool, type: 'global-bin', workingDirectory: process.cwd(), context: {} }); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/patelnav/my-tools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server