UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

541 lines 20.9 kB
import { randomUUID } from 'crypto'; import { promises as fs } from 'fs'; import path from 'path'; import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js'; /** * Check if a directory is a git repository */ async function isGitRepository(dirPath) { try { await fs.access(path.join(dirPath, '.git')); return true; } catch { return false; } } /** * Auto-detect repositories in a directory */ async function detectRepositories(rootPath) { const repositories = []; try { const entries = await fs.readdir(rootPath, { withFileTypes: true }); for (const entry of entries) { if (entry.isDirectory() && !entry.name.startsWith('.')) { const fullPath = path.join(rootPath, entry.name); // Check for version control systems let repoType = 'local'; if (await isGitRepository(fullPath)) { repoType = 'git'; } else { // Check for other VCS try { await fs.access(path.join(fullPath, '.svn')); repoType = 'svn'; } catch { try { await fs.access(path.join(fullPath, '.hg')); repoType = 'mercurial'; } catch { // Check if it's a Node.js package try { await fs.access(path.join(fullPath, 'package.json')); repoType = 'local'; } catch { // Skip non-repository directories continue; } } } } repositories.push({ name: entry.name, path: fullPath, type: repoType }); } } } catch (error) { console.error('Error detecting repositories:', error); } return repositories; } /** * Create a new workspace */ const createWorkspaceTool = createTool({ name: 'create_workspace', description: 'Create a new multi-repository workspace', category: 'workspace', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Workspace name', minLength: 1, maxLength: 200 }, description: { type: 'string', description: 'Workspace description', maxLength: 1000 }, rootPath: { type: 'string', description: 'Root directory path (defaults to current directory)' }, autoDetect: { type: 'boolean', description: 'Automatically detect repositories', default: true }, primaryRepoPath: { type: 'string', description: 'Path to the primary repository' } }, required: ['name'], additionalProperties: false }, async execute(input, context) { try { const rootPath = input.rootPath || process.cwd(); // Verify root path exists try { await fs.access(rootPath); } catch { return createErrorResult({ code: 'INVALID_PATH', message: 'Root path does not exist', details: { rootPath }, category: 'validation' }); } // Resolve absolute path const absoluteRootPath = path.resolve(rootPath); // Check for duplicate workspace names const existingWorkspace = await context.db.get('SELECT id FROM workspaces WHERE name = ?', [input.name]); if (existingWorkspace.success && existingWorkspace.data) { return createErrorResult({ code: 'DUPLICATE_RESOURCE', message: 'A workspace with this name already exists', category: 'validation' }); } const workspaceId = `ws-${randomUUID()}`; const now = Date.now(); // Create workspace const result = await context.db.run(`INSERT INTO workspaces (id, name, description, root_path, active, settings, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ workspaceId, input.name, input.description || '', absoluteRootPath, false, // Not active by default JSON.stringify({}), now, now ]); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to create workspace', details: { error: result.error }, category: 'system' }); } // Auto-detect repositories if requested const detectedRepos = []; if (input.autoDetect !== false) { const repos = await detectRepositories(absoluteRootPath); for (const repo of repos) { const repoId = `repo-${randomUUID()}`; const isPrimary = repo.path === input.primaryRepoPath || (repos.length === 1 && !input.primaryRepoPath); await context.db.run(`INSERT INTO workspace_repositories (id, workspace_id, name, path, type, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ repoId, workspaceId, repo.name, repo.path, repo.type, isPrimary, now, now ]); detectedRepos.push({ id: repoId, name: repo.name, path: repo.path, type: repo.type, primary: isPrimary }); } } // If no active workspace exists, make this one active const activeWorkspaceCheck = await context.db.get('SELECT id FROM workspaces WHERE active = TRUE'); if (!activeWorkspaceCheck.success || !activeWorkspaceCheck.data) { await context.db.run('UPDATE workspaces SET active = TRUE WHERE id = ?', [workspaceId]); } return createSuccessResult({ workspace: { id: workspaceId, name: input.name, description: input.description || '', rootPath: absoluteRootPath, active: !activeWorkspaceCheck.data, repositories: detectedRepos, createdAt: new Date(now).toISOString() }, message: `Workspace "${input.name}" created successfully`, detectedRepositories: detectedRepos.length, nextSteps: [ detectedRepos.length === 0 ? 'Add repositories to your workspace' : null, !detectedRepos.some(r => r.primary) ? 'Designate a primary repository' : null, 'Switch to this workspace to make it active' ].filter(Boolean) }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to create workspace: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Add a repository to a workspace */ const addRepositoryTool = createTool({ name: 'add_repository', description: 'Add a repository to an existing workspace', category: 'workspace', inputSchema: { type: 'object', properties: { workspaceId: { type: 'string', description: 'Workspace ID', pattern: '^ws-[a-f0-9-]+$' }, name: { type: 'string', description: 'Repository name (defaults to directory name)', maxLength: 200 }, path: { type: 'string', description: 'Repository path' }, type: { type: 'string', enum: ['git', 'svn', 'mercurial', 'local'], default: 'local', description: 'Repository type' }, setPrimary: { type: 'boolean', description: 'Set as primary repository', default: false }, remote: { type: 'string', description: 'Remote repository URL' }, branch: { type: 'string', description: 'Current branch name' } }, required: ['workspaceId', 'path'], additionalProperties: false }, async execute(input, context) { try { // Verify workspace exists const workspaceResult = await context.db.get('SELECT id, name FROM workspaces WHERE id = ?', [input.workspaceId]); if (!workspaceResult.success || !workspaceResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Workspace not found', category: 'validation' }); } const workspace = workspaceResult.data; // Verify repository path exists try { await fs.access(input.path); } catch { return createErrorResult({ code: 'INVALID_PATH', message: 'Repository path does not exist', details: { path: input.path }, category: 'validation' }); } const absolutePath = path.resolve(input.path); const repoName = input.name || path.basename(absolutePath); // Check if repository already exists in workspace const existingRepo = await context.db.get('SELECT id FROM workspace_repositories WHERE workspace_id = ? AND path = ?', [input.workspaceId, absolutePath]); if (existingRepo.success && existingRepo.data) { return createErrorResult({ code: 'DUPLICATE_RESOURCE', message: 'Repository already exists in this workspace', category: 'validation' }); } // Auto-detect repository type if not specified let repoType = input.type || 'local'; if (repoType === 'local') { if (await isGitRepository(absolutePath)) { repoType = 'git'; } } const repoId = `repo-${randomUUID()}`; const now = Date.now(); // If setting as primary, unset other primary repos if (input.setPrimary) { await context.db.run('UPDATE workspace_repositories SET is_primary = FALSE WHERE workspace_id = ?', [input.workspaceId]); } // Add repository const result = await context.db.run(`INSERT INTO workspace_repositories (id, workspace_id, name, path, type, is_primary, remote_url, current_branch, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ repoId, input.workspaceId, repoName, absolutePath, repoType, input.setPrimary || false, input.remote || null, input.branch || null, now, now ]); if (!result.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to add repository', details: { error: result.error }, category: 'system' }); } // Update workspace modification time await context.db.run('UPDATE workspaces SET updated_at = ? WHERE id = ?', [now, input.workspaceId]); return createSuccessResult({ repository: { id: repoId, workspaceId: input.workspaceId, name: repoName, path: absolutePath, type: repoType, primary: input.setPrimary || false, remote: input.remote || null, branch: input.branch || null, createdAt: new Date(now).toISOString() }, workspace: { id: workspace.id, name: workspace.name }, message: `Repository "${repoName}" added to workspace "${workspace.name}"`, detectedType: repoType !== (input.type || 'local'), nextSteps: input.setPrimary ? [] : ['Consider setting this as the primary repository'] }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to add repository: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * List repositories in a workspace */ const listRepositoriesTool = createTool({ name: 'list_repositories', description: 'List all repositories in a workspace', category: 'workspace', readOnly: true, inputSchema: { type: 'object', properties: { workspaceId: { type: 'string', description: 'Workspace ID', pattern: '^ws-[a-f0-9-]+$' } }, required: ['workspaceId'], additionalProperties: false }, async execute(input, context) { try { // Get workspace details const workspaceResult = await context.db.get('SELECT * FROM workspaces WHERE id = ?', [input.workspaceId]); if (!workspaceResult.success || !workspaceResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Workspace not found', category: 'validation' }); } const workspace = workspaceResult.data; // Get all repositories const reposResult = await context.db.query('SELECT * FROM workspace_repositories WHERE workspace_id = ? ORDER BY is_primary DESC, name', [input.workspaceId]); if (!reposResult.success) { return createErrorResult({ code: 'DATABASE_ERROR', message: 'Failed to list repositories', details: { error: reposResult.error }, category: 'system' }); } const repositories = (reposResult.data || []).map((repo) => ({ id: repo.id, name: repo.name, path: repo.path, type: repo.type, primary: repo.is_primary, remote: repo.remote_url, branch: repo.current_branch, lastSync: repo.last_sync ? new Date(repo.last_sync).toISOString() : null, dependencies: JSON.parse(repo.dependencies || '[]') })); // Get repository type breakdown const typeBreakdown = repositories.reduce((acc, repo) => { acc[repo.type] = (acc[repo.type] || 0) + 1; return acc; }, {}); return createSuccessResult({ workspace: { id: workspace.id, name: workspace.name, description: workspace.description, rootPath: workspace.root_path, active: workspace.active, createdAt: new Date(workspace.created_at).toISOString(), updatedAt: new Date(workspace.updated_at).toISOString() }, repositories, statistics: { total: repositories.length, byType: typeBreakdown, primaryRepository: repositories.find((r) => r.primary)?.name || null } }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to list repositories: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Switch active workspace */ const switchWorkspaceTool = createTool({ name: 'switch_workspace', description: 'Switch the active workspace', category: 'workspace', inputSchema: { type: 'object', properties: { workspaceId: { type: 'string', description: 'Workspace ID to switch to', pattern: '^ws-[a-f0-9-]+$' } }, required: ['workspaceId'], additionalProperties: false }, async execute(input, context) { try { // Verify workspace exists const workspaceResult = await context.db.get('SELECT * FROM workspaces WHERE id = ?', [input.workspaceId]); if (!workspaceResult.success || !workspaceResult.data) { return createErrorResult({ code: 'RESOURCE_NOT_FOUND', message: 'Workspace not found', category: 'validation' }); } const workspace = workspaceResult.data; // If already active, no-op if (workspace.active) { return createSuccessResult({ workspace: { id: workspace.id, name: workspace.name, active: true }, message: `Workspace "${workspace.name}" is already active`, changed: false }); } // Deactivate all workspaces await context.db.run('UPDATE workspaces SET active = FALSE'); // Activate target workspace const now = Date.now(); await context.db.run('UPDATE workspaces SET active = TRUE, updated_at = ? WHERE id = ?', [now, input.workspaceId]); // Get repository count const repoCountResult = await context.db.get('SELECT COUNT(*) as count FROM workspace_repositories WHERE workspace_id = ?', [input.workspaceId]); return createSuccessResult({ workspace: { id: workspace.id, name: workspace.name, description: workspace.description, rootPath: workspace.root_path, active: true, repositoryCount: repoCountResult.data?.count || 0 }, message: `Switched to workspace "${workspace.name}"`, changed: true, previousActiveWorkspace: null // Could track this if needed }); } catch (error) { return createErrorResult({ code: 'EXECUTION_ERROR', message: `Failed to switch workspace: ${error instanceof Error ? error.message : 'Unknown error'}`, category: 'execution' }); } } }); /** * Setup workspace tools */ export async function setupWorkspaceTools() { return { module: 'workspace', tools: [ createWorkspaceTool, addRepositoryTool, listRepositoriesTool, switchWorkspaceTool ] }; } //# sourceMappingURL=tools.js.map