@stackmemoryai/stackmemory 0.3.21 → 0.3.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/linear-unified.js +2 -3
- package/dist/cli/commands/linear-unified.js.map +2 -2
- package/dist/cli/commands/tasks.js +1 -1
- package/dist/cli/commands/tasks.js.map +2 -2
- package/dist/integrations/mcp/handlers/code-execution-handlers.js +262 -0
- package/dist/integrations/mcp/handlers/code-execution-handlers.js.map +7 -0
- package/dist/integrations/mcp/tool-definitions-code.js +121 -0
- package/dist/integrations/mcp/tool-definitions-code.js.map +7 -0
- package/dist/servers/railway/index.js +98 -92
- package/dist/servers/railway/index.js.map +3 -3
- package/package.json +1 -2
- package/scripts/claude-sm-autostart.js +1 -1
- package/scripts/clean-linear-backlog.js +2 -2
- package/scripts/debug-linear-update.js +1 -1
- package/scripts/debug-railway-build.js +87 -0
- package/scripts/delete-linear-tasks.js +2 -2
- package/scripts/install-code-execution-hooks.sh +96 -0
- package/scripts/linear-task-review.js +1 -1
- package/scripts/sync-and-clean-tasks.js +1 -1
- package/scripts/sync-linear-graphql.js +3 -3
- package/scripts/sync-linear-tasks.js +1 -1
- package/scripts/test-code-execution.js +143 -0
- package/scripts/update-linear-tasks-fixed.js +1 -1
- package/scripts/validate-railway-deployment.js +137 -0
- package/templates/claude-hooks/hook-config.json +59 -0
- package/templates/claude-hooks/pre-tool-use +189 -0
- package/dist/servers/railway/minimal.js +0 -91
- package/dist/servers/railway/minimal.js.map +0 -7
|
@@ -122,7 +122,7 @@ function registerUnifiedLinearCommands(parent) {
|
|
|
122
122
|
try {
|
|
123
123
|
const authManager = new LinearAuthManager(process.cwd());
|
|
124
124
|
const hasAuth = authManager.isConfigured();
|
|
125
|
-
if (!hasAuth && !process.env.LINEAR_API_KEY) {
|
|
125
|
+
if (!hasAuth && !process.env.STACKMEMORY_LINEAR_API_KEY && !process.env.LINEAR_API_KEY) {
|
|
126
126
|
console.log(chalk.yellow("\u26A0 Not connected to Linear"));
|
|
127
127
|
console.log('Run "stackmemory linear auth" to connect');
|
|
128
128
|
return;
|
|
@@ -248,9 +248,8 @@ function registerUnifiedLinearCommands(parent) {
|
|
|
248
248
|
});
|
|
249
249
|
linear.command("auth").description("Authenticate with Linear").option("--api-key <key>", "Use API key").option("--oauth", "Use OAuth flow").action(async (options) => {
|
|
250
250
|
try {
|
|
251
|
-
const authManager = new LinearAuthManager(process.cwd());
|
|
252
251
|
if (options.apiKey) {
|
|
253
|
-
process.env.
|
|
252
|
+
process.env.STACKMEMORY_LINEAR_API_KEY = options.apiKey;
|
|
254
253
|
console.log(chalk.green("\u2713 API key configured"));
|
|
255
254
|
const client = new LinearClient({ apiKey: options.apiKey });
|
|
256
255
|
const user = await client.getViewer();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/cli/commands/linear-unified.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Unified Linear CLI Commands\n * Consolidates all Linear operations into a single, coherent interface\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport {\n UnifiedLinearSync,\n UnifiedSyncConfig,\n SyncStats,\n DEFAULT_UNIFIED_CONFIG,\n} from '../../integrations/linear/unified-sync.js';\nimport { LinearAuthManager } from '../../integrations/linear/auth.js';\nimport { LinearClient } from '../../integrations/linear/client.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport Database from 'better-sqlite3';\nimport { join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\nimport { logger } from '../../core/monitoring/logger.js';\nimport Table from 'cli-table3';\nimport ora from 'ora';\n\nexport function registerUnifiedLinearCommands(parent: Command) {\n const linear = parent\n .command('linear')\n .description(\n 'Unified Linear integration with duplicate detection and task planning'\n );\n\n // Main sync command with all features\n linear\n .command('sync')\n .description(\n 'Intelligent sync with Linear (duplicate detection, bidirectional, task planning)'\n )\n .option(\n '-d, --direction <dir>',\n 'Sync direction: bidirectional, to_linear, from_linear',\n 'bidirectional'\n )\n .option('-t, --team <id>', 'Linear team ID')\n .option('--no-duplicates', 'Disable duplicate detection')\n .option(\n '--merge-strategy <strategy>',\n 'Duplicate handling: merge_content, skip, create_anyway',\n 'merge_content'\n )\n .option(\n '--conflict <resolution>',\n 'Conflict resolution: newest_wins, linear_wins, local_wins',\n 'newest_wins'\n )\n .option('--task-plan', 'Enable task planning integration')\n .option('--dry-run', 'Preview changes without syncing')\n .option('--daemon', 'Run as background daemon')\n .option('-i, --interval <minutes>', 'Auto-sync interval', '15')\n .option('--verbose', 'Show detailed sync progress')\n .action(async (options) => {\n const spinner = ora('Initializing Linear sync...').start();\n\n try {\n const projectRoot = process.cwd();\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n\n if (!existsSync(dbPath)) {\n spinner.fail('StackMemory not initialized');\n console.log(chalk.yellow('Run \"stackmemory init\" first'));\n return;\n }\n\n // Initialize components\n const db = new Database(dbPath);\n const taskStore = new LinearTaskManager(projectRoot, db);\n const authManager = new LinearAuthManager(projectRoot);\n\n // Build config from options\n const config: Partial<UnifiedSyncConfig> = {\n direction: options.direction,\n defaultTeamId: options.team,\n duplicateDetection: options.duplicates !== false,\n mergeStrategy: options.mergeStrategy,\n conflictResolution: options.conflict,\n taskPlanningEnabled: options.taskPlan || false,\n };\n\n // Create unified sync instance\n const unifiedSync = new UnifiedLinearSync(\n taskStore,\n authManager,\n projectRoot,\n config\n );\n\n // Listen to events for progress\n if (options.verbose) {\n unifiedSync.on('sync:started', ({ config }) => {\n spinner.text = `Syncing (${config.direction})...`;\n });\n }\n\n // Initialize\n spinner.text = 'Authenticating with Linear...';\n await unifiedSync.initialize();\n\n if (options.dryRun) {\n spinner.info('Dry run mode - no changes will be made');\n // TODO: Implement dry run preview\n return;\n }\n\n if (options.daemon) {\n // Daemon mode\n spinner.succeed('Starting sync daemon');\n console.log(chalk.cyan(`Sync interval: ${options.interval} minutes`));\n console.log(chalk.gray('Press Ctrl+C to stop\\n'));\n\n // Initial sync\n const stats = await unifiedSync.sync();\n displaySyncStats(stats);\n\n // Schedule periodic syncs\n const interval = setInterval(\n async () => {\n console.log(\n chalk.yellow(\n `\\n[${new Date().toLocaleTimeString()}] Running scheduled sync...`\n )\n );\n try {\n const stats = await unifiedSync.sync();\n displaySyncStats(stats);\n } catch (error: unknown) {\n console.error(\n chalk.red('Sync failed:'),\n (error as Error).message\n );\n }\n },\n parseInt(options.interval) * 60 * 1000\n );\n\n // Handle graceful shutdown\n process.on('SIGINT', () => {\n console.log(chalk.yellow('\\nStopping sync daemon...'));\n clearInterval(interval);\n db.close();\n process.exit(0);\n });\n\n // Keep process alive\n process.stdin.resume();\n } else {\n // Single sync\n spinner.text = 'Syncing tasks...';\n const stats = await unifiedSync.sync();\n\n spinner.succeed('Sync completed');\n displaySyncStats(stats);\n\n // Show task plan if enabled\n if (options.taskPlan) {\n displayTaskPlan(projectRoot);\n }\n\n db.close();\n }\n } catch (error: unknown) {\n spinner.fail('Sync failed');\n console.error(chalk.red('Error:'), (error as Error).message);\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n });\n\n // Quick status command\n linear\n .command('status')\n .description('Show Linear connection status and sync statistics')\n .action(async () => {\n try {\n const authManager = new LinearAuthManager(process.cwd());\n const hasAuth = authManager.isConfigured();\n\n if (!hasAuth && !process.env.LINEAR_API_KEY) {\n console.log(chalk.yellow('\u26A0 Not connected to Linear'));\n console.log('Run \"stackmemory linear auth\" to connect');\n return;\n }\n\n console.log(chalk.green('\u2713 Linear connection configured'));\n\n // Show last sync stats if available\n const statsFile = join(\n process.cwd(),\n '.stackmemory',\n 'sync-stats.json'\n );\n if (existsSync(statsFile)) {\n const stats = JSON.parse(readFileSync(statsFile, 'utf8'));\n console.log(chalk.cyan('\\nLast sync:'));\n console.log(` Time: ${new Date(stats.timestamp).toLocaleString()}`);\n console.log(` Duration: ${stats.duration}ms`);\n console.log(\n ` To Linear: ${stats.toLinear.created} created, ${stats.toLinear.updated} updated`\n );\n console.log(\n ` From Linear: ${stats.fromLinear.created} created, ${stats.fromLinear.updated} updated`\n );\n\n if (stats.toLinear.duplicatesMerged > 0) {\n console.log(\n chalk.green(\n ` Duplicates prevented: ${stats.toLinear.duplicatesMerged}`\n )\n );\n }\n }\n\n // Show task plan status\n const planFile = join(process.cwd(), '.stackmemory', 'task-plan.md');\n if (existsSync(planFile)) {\n console.log(chalk.cyan('\\n\u2713 Task planning enabled'));\n const reportFile = join(\n process.cwd(),\n '.stackmemory',\n 'task-report.md'\n );\n if (existsSync(reportFile)) {\n console.log(` Report: ${reportFile}`);\n }\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Status check failed:'),\n (error as Error).message\n );\n }\n });\n\n // Task planning command\n linear\n .command('plan')\n .description('View and manage task planning')\n .option('--generate', 'Generate task plan from current tasks')\n .option('--report', 'Show task report')\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n\n if (options.generate) {\n console.log(chalk.yellow('Generating task plan...'));\n // Run sync with task planning enabled\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n const db = new Database(dbPath);\n const taskStore = new LinearTaskManager(projectRoot, db);\n const authManager = new LinearAuthManager(projectRoot);\n\n const unifiedSync = new UnifiedLinearSync(\n taskStore,\n authManager,\n projectRoot,\n {\n taskPlanningEnabled: true,\n autoCreateTaskPlan: true,\n }\n );\n\n await unifiedSync.initialize();\n await unifiedSync.sync();\n\n console.log(chalk.green('\u2713 Task plan generated'));\n db.close();\n }\n\n displayTaskPlan(projectRoot, options.report);\n } catch (error: unknown) {\n console.error(\n chalk.red('Plan generation failed:'),\n (error as Error).message\n );\n }\n });\n\n // Duplicate detection command\n linear\n .command('duplicates')\n .description('Check for and manage duplicate Linear issues')\n .option('--check <title>', 'Check if a title would create a duplicate')\n .option('--merge', 'Merge detected duplicates')\n .option('--list', 'List potential duplicates')\n .action(async (options) => {\n try {\n const authManager = new LinearAuthManager(process.cwd());\n const token = await authManager.getValidToken();\n\n if (!token) {\n console.log(chalk.red('Not authenticated with Linear'));\n return;\n }\n\n const client = new LinearClient({\n apiKey: token,\n useBearer: authManager.isOAuth(),\n });\n\n if (options.check) {\n // Import dynamically to avoid circular dependency\n const { LinearDuplicateDetector } =\n await import('../../integrations/linear/sync-enhanced.js');\n const detector = new LinearDuplicateDetector(client);\n\n console.log(\n chalk.yellow(`Checking for duplicates of: \"${options.check}\"`)\n );\n const result = await detector.checkForDuplicate(options.check);\n\n if (result.isDuplicate && result.existingIssue) {\n console.log(chalk.red('\u26A0 Duplicate detected!'));\n console.log(\n ` Issue: ${result.existingIssue.identifier} - ${result.existingIssue.title}`\n );\n console.log(\n ` Similarity: ${Math.round((result.similarity || 0) * 100)}%`\n );\n console.log(` URL: ${result.existingIssue.url}`);\n } else {\n console.log(chalk.green('\u2713 No duplicates found'));\n }\n } else if (options.list) {\n console.log(chalk.yellow('Scanning for potential duplicates...'));\n // TODO: Implement duplicate scanning\n console.log('Feature coming soon');\n } else {\n console.log('Specify --check, --merge, or --list');\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Duplicate check failed:'),\n (error as Error).message\n );\n }\n });\n\n // Auth command (simplified)\n linear\n .command('auth')\n .description('Authenticate with Linear')\n .option('--api-key <key>', 'Use API key')\n .option('--oauth', 'Use OAuth flow')\n .action(async (options) => {\n try {\n const authManager = new LinearAuthManager(process.cwd());\n\n if (options.apiKey) {\n // Simple API key setup\n process.env.LINEAR_API_KEY = options.apiKey;\n console.log(chalk.green('\u2713 API key configured'));\n\n // Test connection\n const client = new LinearClient({ apiKey: options.apiKey });\n const user = await client.getViewer();\n console.log(chalk.cyan(`Connected as: ${user.name} (${user.email})`));\n } else {\n console.log(chalk.cyan('Linear Authentication'));\n console.log('\\nOption 1: API Key (Recommended for automation)');\n console.log(' 1. Go to: https://linear.app/settings/api');\n console.log(' 2. Create a personal API key');\n console.log(' 3. Run: stackmemory linear auth --api-key YOUR_KEY');\n console.log('\\nOption 2: OAuth (For user-facing apps)');\n console.log(' Configure OAuth app and use linear-oauth-server');\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Authentication failed:'),\n (error as Error).message\n );\n }\n });\n}\n\n/**\n * Display sync statistics\n */\nfunction displaySyncStats(stats: SyncStats): void {\n const table = new Table({\n head: ['Direction', 'Created', 'Updated', 'Merged', 'Skipped'],\n style: { head: ['cyan'] },\n });\n\n table.push(\n [\n '\u2192 Linear',\n stats.toLinear.created,\n stats.toLinear.updated,\n stats.toLinear.duplicatesMerged,\n stats.toLinear.skipped,\n ],\n [\n '\u2190 Linear',\n stats.fromLinear.created,\n stats.fromLinear.updated,\n '-',\n stats.fromLinear.skipped,\n ]\n );\n\n console.log('\\n' + table.toString());\n\n if (stats.conflicts.length > 0) {\n console.log(chalk.yellow(`\\n\u26A0 Conflicts: ${stats.conflicts.length}`));\n stats.conflicts.slice(0, 5).forEach((c) => {\n console.log(` - ${c.reason}`);\n });\n }\n\n if (stats.errors.length > 0) {\n console.log(chalk.red(`\\n\u274C Errors: ${stats.errors.length}`));\n stats.errors.slice(0, 5).forEach((e: string) => {\n console.log(` - ${e.substring(0, 100)}`);\n });\n }\n\n console.log(chalk.gray(`\\nCompleted in ${stats.duration}ms`));\n}\n\n/**\n * Display task plan\n */\nfunction displayTaskPlan(projectRoot: string, showReport = false): void {\n const reportFile = join(projectRoot, '.stackmemory', 'task-report.md');\n const planFile = join(projectRoot, '.stackmemory', 'task-plan.json');\n\n if (showReport && existsSync(reportFile)) {\n const report = readFileSync(reportFile, 'utf8');\n console.log('\\n' + report);\n } else if (existsSync(planFile)) {\n const plan = JSON.parse(readFileSync(planFile, 'utf8'));\n\n console.log(chalk.cyan('\\n\uD83D\uDCCB Task Plan Overview'));\n console.log(\n `Last updated: ${new Date(plan.lastUpdated).toLocaleString()}\\n`\n );\n\n plan.phases.forEach((phase) => {\n console.log(chalk.yellow(`${phase.name} (${phase.tasks.length})`));\n console.log(chalk.gray(` ${phase.description}`));\n\n if (phase.tasks.length > 0) {\n phase.tasks.slice(0, 5).forEach((task) => {\n const status = task.linearId ? '\uD83D\uDD17' : ' ';\n console.log(` ${status} ${task.title}`);\n });\n\n if (phase.tasks.length > 5) {\n console.log(chalk.gray(` ...and ${phase.tasks.length - 5} more`));\n }\n }\n console.log();\n });\n } else {\n console.log(\n chalk.yellow('No task plan found. Run sync with --task-plan to generate.')\n );\n }\n}\n"],
|
|
5
|
-
"mappings": "AAMA,OAAO,WAAW;AAClB;AAAA,EACE;AAAA,
|
|
4
|
+
"sourcesContent": ["/**\n * Unified Linear CLI Commands\n * Consolidates all Linear operations into a single, coherent interface\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport {\n UnifiedLinearSync,\n UnifiedSyncConfig,\n SyncStats,\n} from '../../integrations/linear/unified-sync.js';\nimport { LinearAuthManager } from '../../integrations/linear/auth.js';\nimport { LinearClient } from '../../integrations/linear/client.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport Database from 'better-sqlite3';\nimport { join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n// import { logger } from '../../core/monitoring/logger.js';\nimport Table from 'cli-table3';\nimport ora from 'ora';\n\nexport function registerUnifiedLinearCommands(parent: Command) {\n const linear = parent\n .command('linear')\n .description(\n 'Unified Linear integration with duplicate detection and task planning'\n );\n\n // Main sync command with all features\n linear\n .command('sync')\n .description(\n 'Intelligent sync with Linear (duplicate detection, bidirectional, task planning)'\n )\n .option(\n '-d, --direction <dir>',\n 'Sync direction: bidirectional, to_linear, from_linear',\n 'bidirectional'\n )\n .option('-t, --team <id>', 'Linear team ID')\n .option('--no-duplicates', 'Disable duplicate detection')\n .option(\n '--merge-strategy <strategy>',\n 'Duplicate handling: merge_content, skip, create_anyway',\n 'merge_content'\n )\n .option(\n '--conflict <resolution>',\n 'Conflict resolution: newest_wins, linear_wins, local_wins',\n 'newest_wins'\n )\n .option('--task-plan', 'Enable task planning integration')\n .option('--dry-run', 'Preview changes without syncing')\n .option('--daemon', 'Run as background daemon')\n .option('-i, --interval <minutes>', 'Auto-sync interval', '15')\n .option('--verbose', 'Show detailed sync progress')\n .action(async (options) => {\n const spinner = ora('Initializing Linear sync...').start();\n\n try {\n const projectRoot = process.cwd();\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n\n if (!existsSync(dbPath)) {\n spinner.fail('StackMemory not initialized');\n console.log(chalk.yellow('Run \"stackmemory init\" first'));\n return;\n }\n\n // Initialize components\n const db = new Database(dbPath);\n const taskStore = new LinearTaskManager(projectRoot, db);\n const authManager = new LinearAuthManager(projectRoot);\n\n // Build config from options\n const config: Partial<UnifiedSyncConfig> = {\n direction: options.direction,\n defaultTeamId: options.team,\n duplicateDetection: options.duplicates !== false,\n mergeStrategy: options.mergeStrategy,\n conflictResolution: options.conflict,\n taskPlanningEnabled: options.taskPlan || false,\n };\n\n // Create unified sync instance\n const unifiedSync = new UnifiedLinearSync(\n taskStore,\n authManager,\n projectRoot,\n config\n );\n\n // Listen to events for progress\n if (options.verbose) {\n unifiedSync.on('sync:started', ({ config }) => {\n spinner.text = `Syncing (${config.direction})...`;\n });\n }\n\n // Initialize\n spinner.text = 'Authenticating with Linear...';\n await unifiedSync.initialize();\n\n if (options.dryRun) {\n spinner.info('Dry run mode - no changes will be made');\n // TODO: Implement dry run preview\n return;\n }\n\n if (options.daemon) {\n // Daemon mode\n spinner.succeed('Starting sync daemon');\n console.log(chalk.cyan(`Sync interval: ${options.interval} minutes`));\n console.log(chalk.gray('Press Ctrl+C to stop\\n'));\n\n // Initial sync\n const stats = await unifiedSync.sync();\n displaySyncStats(stats);\n\n // Schedule periodic syncs\n const interval = setInterval(\n async () => {\n console.log(\n chalk.yellow(\n `\\n[${new Date().toLocaleTimeString()}] Running scheduled sync...`\n )\n );\n try {\n const stats = await unifiedSync.sync();\n displaySyncStats(stats);\n } catch (error: unknown) {\n console.error(\n chalk.red('Sync failed:'),\n (error as Error).message\n );\n }\n },\n parseInt(options.interval) * 60 * 1000\n );\n\n // Handle graceful shutdown\n process.on('SIGINT', () => {\n console.log(chalk.yellow('\\nStopping sync daemon...'));\n clearInterval(interval);\n db.close();\n process.exit(0);\n });\n\n // Keep process alive\n process.stdin.resume();\n } else {\n // Single sync\n spinner.text = 'Syncing tasks...';\n const stats = await unifiedSync.sync();\n\n spinner.succeed('Sync completed');\n displaySyncStats(stats);\n\n // Show task plan if enabled\n if (options.taskPlan) {\n displayTaskPlan(projectRoot);\n }\n\n db.close();\n }\n } catch (error: unknown) {\n spinner.fail('Sync failed');\n console.error(chalk.red('Error:'), (error as Error).message);\n if (options.verbose) {\n console.error(error);\n }\n process.exit(1);\n }\n });\n\n // Quick status command\n linear\n .command('status')\n .description('Show Linear connection status and sync statistics')\n .action(async () => {\n try {\n const authManager = new LinearAuthManager(process.cwd());\n const hasAuth = authManager.isConfigured();\n\n if (!hasAuth && !process.env.STACKMEMORY_LINEAR_API_KEY && !process.env.LINEAR_API_KEY) {\n console.log(chalk.yellow('\u26A0 Not connected to Linear'));\n console.log('Run \"stackmemory linear auth\" to connect');\n return;\n }\n\n console.log(chalk.green('\u2713 Linear connection configured'));\n\n // Show last sync stats if available\n const statsFile = join(\n process.cwd(),\n '.stackmemory',\n 'sync-stats.json'\n );\n if (existsSync(statsFile)) {\n const stats = JSON.parse(readFileSync(statsFile, 'utf8'));\n console.log(chalk.cyan('\\nLast sync:'));\n console.log(` Time: ${new Date(stats.timestamp).toLocaleString()}`);\n console.log(` Duration: ${stats.duration}ms`);\n console.log(\n ` To Linear: ${stats.toLinear.created} created, ${stats.toLinear.updated} updated`\n );\n console.log(\n ` From Linear: ${stats.fromLinear.created} created, ${stats.fromLinear.updated} updated`\n );\n\n if (stats.toLinear.duplicatesMerged > 0) {\n console.log(\n chalk.green(\n ` Duplicates prevented: ${stats.toLinear.duplicatesMerged}`\n )\n );\n }\n }\n\n // Show task plan status\n const planFile = join(process.cwd(), '.stackmemory', 'task-plan.md');\n if (existsSync(planFile)) {\n console.log(chalk.cyan('\\n\u2713 Task planning enabled'));\n const reportFile = join(\n process.cwd(),\n '.stackmemory',\n 'task-report.md'\n );\n if (existsSync(reportFile)) {\n console.log(` Report: ${reportFile}`);\n }\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Status check failed:'),\n (error as Error).message\n );\n }\n });\n\n // Task planning command\n linear\n .command('plan')\n .description('View and manage task planning')\n .option('--generate', 'Generate task plan from current tasks')\n .option('--report', 'Show task report')\n .action(async (options) => {\n try {\n const projectRoot = process.cwd();\n\n if (options.generate) {\n console.log(chalk.yellow('Generating task plan...'));\n // Run sync with task planning enabled\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n const db = new Database(dbPath);\n const taskStore = new LinearTaskManager(projectRoot, db);\n const authManager = new LinearAuthManager(projectRoot);\n\n const unifiedSync = new UnifiedLinearSync(\n taskStore,\n authManager,\n projectRoot,\n {\n taskPlanningEnabled: true,\n autoCreateTaskPlan: true,\n }\n );\n\n await unifiedSync.initialize();\n await unifiedSync.sync();\n\n console.log(chalk.green('\u2713 Task plan generated'));\n db.close();\n }\n\n displayTaskPlan(projectRoot, options.report);\n } catch (error: unknown) {\n console.error(\n chalk.red('Plan generation failed:'),\n (error as Error).message\n );\n }\n });\n\n // Duplicate detection command\n linear\n .command('duplicates')\n .description('Check for and manage duplicate Linear issues')\n .option('--check <title>', 'Check if a title would create a duplicate')\n .option('--merge', 'Merge detected duplicates')\n .option('--list', 'List potential duplicates')\n .action(async (options) => {\n try {\n const authManager = new LinearAuthManager(process.cwd());\n const token = await authManager.getValidToken();\n\n if (!token) {\n console.log(chalk.red('Not authenticated with Linear'));\n return;\n }\n\n const client = new LinearClient({\n apiKey: token,\n useBearer: authManager.isOAuth(),\n });\n\n if (options.check) {\n // Import dynamically to avoid circular dependency\n const { LinearDuplicateDetector } =\n await import('../../integrations/linear/sync-enhanced.js');\n const detector = new LinearDuplicateDetector(client);\n\n console.log(\n chalk.yellow(`Checking for duplicates of: \"${options.check}\"`)\n );\n const result = await detector.checkForDuplicate(options.check);\n\n if (result.isDuplicate && result.existingIssue) {\n console.log(chalk.red('\u26A0 Duplicate detected!'));\n console.log(\n ` Issue: ${result.existingIssue.identifier} - ${result.existingIssue.title}`\n );\n console.log(\n ` Similarity: ${Math.round((result.similarity || 0) * 100)}%`\n );\n console.log(` URL: ${result.existingIssue.url}`);\n } else {\n console.log(chalk.green('\u2713 No duplicates found'));\n }\n } else if (options.list) {\n console.log(chalk.yellow('Scanning for potential duplicates...'));\n // TODO: Implement duplicate scanning\n console.log('Feature coming soon');\n } else {\n console.log('Specify --check, --merge, or --list');\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Duplicate check failed:'),\n (error as Error).message\n );\n }\n });\n\n // Auth command (simplified)\n linear\n .command('auth')\n .description('Authenticate with Linear')\n .option('--api-key <key>', 'Use API key')\n .option('--oauth', 'Use OAuth flow')\n .action(async (options) => {\n try {\n // const authManager = new LinearAuthManager(process.cwd());\n\n if (options.apiKey) {\n // Simple API key setup\n process.env.STACKMEMORY_LINEAR_API_KEY = options.apiKey;\n console.log(chalk.green('\u2713 API key configured'));\n\n // Test connection\n const client = new LinearClient({ apiKey: options.apiKey });\n const user = await client.getViewer();\n console.log(chalk.cyan(`Connected as: ${user.name} (${user.email})`));\n } else {\n console.log(chalk.cyan('Linear Authentication'));\n console.log('\\nOption 1: API Key (Recommended for automation)');\n console.log(' 1. Go to: https://linear.app/settings/api');\n console.log(' 2. Create a personal API key');\n console.log(' 3. Run: stackmemory linear auth --api-key YOUR_KEY');\n console.log('\\nOption 2: OAuth (For user-facing apps)');\n console.log(' Configure OAuth app and use linear-oauth-server');\n }\n } catch (error: unknown) {\n console.error(\n chalk.red('Authentication failed:'),\n (error as Error).message\n );\n }\n });\n}\n\n/**\n * Display sync statistics\n */\nfunction displaySyncStats(stats: SyncStats): void {\n const table = new Table({\n head: ['Direction', 'Created', 'Updated', 'Merged', 'Skipped'],\n style: { head: ['cyan'] },\n });\n\n table.push(\n [\n '\u2192 Linear',\n stats.toLinear.created,\n stats.toLinear.updated,\n stats.toLinear.duplicatesMerged,\n stats.toLinear.skipped,\n ],\n [\n '\u2190 Linear',\n stats.fromLinear.created,\n stats.fromLinear.updated,\n '-',\n stats.fromLinear.skipped,\n ]\n );\n\n console.log('\\n' + table.toString());\n\n if (stats.conflicts.length > 0) {\n console.log(chalk.yellow(`\\n\u26A0 Conflicts: ${stats.conflicts.length}`));\n stats.conflicts.slice(0, 5).forEach((c) => {\n console.log(` - ${c.reason}`);\n });\n }\n\n if (stats.errors.length > 0) {\n console.log(chalk.red(`\\n\u274C Errors: ${stats.errors.length}`));\n stats.errors.slice(0, 5).forEach((e: string) => {\n console.log(` - ${e.substring(0, 100)}`);\n });\n }\n\n console.log(chalk.gray(`\\nCompleted in ${stats.duration}ms`));\n}\n\n/**\n * Display task plan\n */\nfunction displayTaskPlan(projectRoot: string, showReport = false): void {\n const reportFile = join(projectRoot, '.stackmemory', 'task-report.md');\n const planFile = join(projectRoot, '.stackmemory', 'task-plan.json');\n\n if (showReport && existsSync(reportFile)) {\n const report = readFileSync(reportFile, 'utf8');\n console.log('\\n' + report);\n } else if (existsSync(planFile)) {\n const plan = JSON.parse(readFileSync(planFile, 'utf8'));\n\n console.log(chalk.cyan('\\n\uD83D\uDCCB Task Plan Overview'));\n console.log(\n `Last updated: ${new Date(plan.lastUpdated).toLocaleString()}\\n`\n );\n\n plan.phases.forEach((phase) => {\n console.log(chalk.yellow(`${phase.name} (${phase.tasks.length})`));\n console.log(chalk.gray(` ${phase.description}`));\n\n if (phase.tasks.length > 0) {\n phase.tasks.slice(0, 5).forEach((task) => {\n const status = task.linearId ? '\uD83D\uDD17' : ' ';\n console.log(` ${status} ${task.title}`);\n });\n\n if (phase.tasks.length > 5) {\n console.log(chalk.gray(` ...and ${phase.tasks.length - 5} more`));\n }\n }\n console.log();\n });\n } else {\n console.log(\n chalk.yellow('No task plan found. Run sync with --task-plan to generate.')\n );\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["config", "stats"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/cli/commands/tasks.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Enhanced Task Commands for StackMemory CLI\n * Provides task management directly from command line\n */\n\nimport { Command } from 'commander';\nimport Database from 'better-sqlite3';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport {\n LinearTaskManager,\n TaskPriority,\n} from '../../features/tasks/linear-task-manager.js';\n\nfunction getTaskStore(projectRoot: string): LinearTaskManager | null {\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n if (!existsSync(dbPath)) {\n console.log(\n '\u274C StackMemory not initialized. Run \"stackmemory init\" first.'\n );\n return null;\n }\n \n // Use project isolation for proper task management\n const config = {\n linearApiKey: process.env.LINEAR_API_KEY,\n autoSync: true,\n syncInterval: 15,\n };\n return new LinearTaskManager(config, undefined, projectRoot);\n}\n\nexport function createTaskCommands(): Command {\n const tasks = new Command('tasks')\n .alias('task')\n .description('Manage tasks from command line');\n\n // List tasks\n tasks\n .command('list')\n .alias('ls')\n .description('List tasks')\n .option(\n '-s, --status <status>',\n 'Filter by status (pending, in_progress, completed, blocked)'\n )\n .option(\n '-p, --priority <priority>',\n 'Filter by priority (urgent, high, medium, low)'\n )\n .option('-q, --query <text>', 'Search in title/description')\n .option('-l, --limit <n>', 'Limit results', '20')\n .option('-a, --all', 'Include completed tasks')\n .action(async (options) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n // Get all tasks from DB\n const db = new Database(\n join(projectRoot, '.stackmemory', 'context.db')\n );\n let query = 'SELECT * FROM task_cache WHERE 1=1';\n const params: any[] = [];\n\n if (!options.all && !options.status) {\n query += \" AND status NOT IN ('completed', 'cancelled')\";\n }\n\n if (options.status) {\n query += ' AND status = ?';\n params.push(options.status);\n }\n\n if (options.priority) {\n query += ' AND priority = ?';\n params.push(options.priority);\n }\n\n if (options.query) {\n query += ' AND (title LIKE ? OR description LIKE ?)';\n params.push(`%${options.query}%`, `%${options.query}%`);\n }\n\n query += ' ORDER BY priority ASC, created_at DESC LIMIT ?';\n params.push(parseInt(options.limit));\n\n const rows = db.prepare(query).all(...params) as any[];\n db.close();\n\n if (rows.length === 0) {\n console.log('\uD83D\uDCDD No tasks found');\n return;\n }\n\n console.log(`\\n\uD83D\uDCCB Tasks (${rows.length})\\n`);\n\n const priorityIcon: Record<string, string> = {\n urgent: '\uD83D\uDD34',\n high: '\uD83D\uDFE0',\n medium: '\uD83D\uDFE1',\n low: '\uD83D\uDFE2',\n };\n const statusIcon: Record<string, string> = {\n pending: '\u23F3',\n in_progress: '\uD83D\uDD04',\n completed: '\u2705',\n blocked: '\uD83D\uDEAB',\n cancelled: '\u274C',\n };\n\n rows.forEach((row) => {\n const pIcon = priorityIcon[row.priority] || '\u26AA';\n const sIcon = statusIcon[row.status] || '\u26AA';\n const id = row.id.slice(0, 10);\n console.log(`${sIcon} ${pIcon} [${id}] ${row.title}`);\n if (row.description) {\n const desc = row.description.split('\\n')[0].slice(0, 60);\n console.log(\n ` ${desc}${row.description.length > 60 ? '...' : ''}`\n );\n }\n });\n console.log('');\n } catch (error: unknown) {\n console.error('\u274C Failed to list tasks:', (error as Error).message);\n }\n });\n\n // Add task\n tasks\n .command('add <title>')\n .description('Add a new task')\n .option('-d, --description <text>', 'Task description')\n .option(\n '-p, --priority <priority>',\n 'Priority (urgent, high, medium, low)',\n 'medium'\n )\n .option('-t, --tags <tags>', 'Comma-separated tags')\n .action(async (title, options) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n const taskId = taskStore.createTask({\n title,\n description: options.description,\n priority: options.priority as TaskPriority,\n frameId: 'cli',\n tags: options.tags\n ? options.tags.split(',').map((t: string) => t.trim())\n : [],\n });\n\n console.log(`\u2705 Created task: ${taskId.slice(0, 10)}`);\n console.log(` Title: ${title}`);\n console.log(` Priority: ${options.priority}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to add task:', (error as Error).message);\n }\n });\n\n // Start task (set to in_progress)\n tasks\n .command('start <taskId>')\n .description('Start working on a task')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n // Find task by partial ID\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n taskStore.updateTaskStatus(task.id, 'in_progress', 'Started from CLI');\n console.log(`\uD83D\uDD04 Started: ${task.title}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to start task:', (error as Error).message);\n }\n });\n\n // Complete task\n tasks\n .command('done <taskId>')\n .alias('complete')\n .description('Mark task as completed')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n taskStore.updateTaskStatus(task.id, 'completed', 'Completed from CLI');\n console.log(`\u2705 Completed: ${task.title}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to complete task:', (error as Error).message);\n }\n });\n\n // Show task details\n tasks\n .command('show <taskId>')\n .description('Show task details')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n\n try {\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n console.log(`\\n\uD83D\uDCCB Task Details\\n`);\n console.log(`ID: ${task.id}`);\n console.log(`Title: ${task.title}`);\n console.log(`Status: ${task.status}`);\n console.log(`Priority: ${task.priority}`);\n console.log(\n `Created: ${new Date(task.created_at * 1000).toLocaleString()}`\n );\n if (task.completed_at) {\n console.log(\n `Completed: ${new Date(task.completed_at * 1000).toLocaleString()}`\n );\n }\n if (task.description) {\n console.log(`\\nDescription:\\n${task.description}`);\n }\n const tags = JSON.parse(task.tags || '[]');\n if (tags.length > 0) {\n console.log(`\\nTags: ${tags.join(', ')}`);\n }\n console.log('');\n } catch (error: unknown) {\n console.error('\u274C Failed to show task:', (error as Error).message);\n }\n });\n\n return tasks;\n}\n\nfunction findTaskByPartialId(\n projectRoot: string,\n partialId: string\n): any | null {\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n if (!existsSync(dbPath)) return null;\n\n const db = new Database(dbPath);\n\n // Try exact match first, then partial\n let row = db.prepare('SELECT * FROM task_cache WHERE id = ?').get(partialId);\n\n if (!row) {\n row = db\n .prepare('SELECT * FROM task_cache WHERE id LIKE ?')\n .get(`${partialId}%`);\n }\n\n // Also try matching Linear identifier in title\n if (!row && partialId.match(/^ENG-\\d+$/i)) {\n row = db\n .prepare('SELECT * FROM task_cache WHERE title LIKE ?')\n .get(`%[${partialId.toUpperCase()}]%`);\n }\n\n db.close();\n return row || null;\n}\n"],
|
|
5
|
-
"mappings": "AAKA,SAAS,eAAe;AACxB,OAAO,cAAc;AACrB,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAC3B;AAAA,EACE;AAAA,OAEK;AAEP,SAAS,aAAa,aAA+C;AACnE,QAAM,SAAS,KAAK,aAAa,gBAAgB,YAAY;AAC7D,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAGA,QAAM,SAAS;AAAA,IACb,cAAc,QAAQ,IAAI;AAAA,
|
|
4
|
+
"sourcesContent": ["/**\n * Enhanced Task Commands for StackMemory CLI\n * Provides task management directly from command line\n */\n\nimport { Command } from 'commander';\nimport Database from 'better-sqlite3';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport {\n LinearTaskManager,\n TaskPriority,\n} from '../../features/tasks/linear-task-manager.js';\n\nfunction getTaskStore(projectRoot: string): LinearTaskManager | null {\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n if (!existsSync(dbPath)) {\n console.log(\n '\u274C StackMemory not initialized. Run \"stackmemory init\" first.'\n );\n return null;\n }\n \n // Use project isolation for proper task management\n const config = {\n linearApiKey: process.env.STACKMEMORY_LINEAR_API_KEY || process.env.LINEAR_API_KEY,\n autoSync: true,\n syncInterval: 15,\n };\n return new LinearTaskManager(config, undefined, projectRoot);\n}\n\nexport function createTaskCommands(): Command {\n const tasks = new Command('tasks')\n .alias('task')\n .description('Manage tasks from command line');\n\n // List tasks\n tasks\n .command('list')\n .alias('ls')\n .description('List tasks')\n .option(\n '-s, --status <status>',\n 'Filter by status (pending, in_progress, completed, blocked)'\n )\n .option(\n '-p, --priority <priority>',\n 'Filter by priority (urgent, high, medium, low)'\n )\n .option('-q, --query <text>', 'Search in title/description')\n .option('-l, --limit <n>', 'Limit results', '20')\n .option('-a, --all', 'Include completed tasks')\n .action(async (options) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n // Get all tasks from DB\n const db = new Database(\n join(projectRoot, '.stackmemory', 'context.db')\n );\n let query = 'SELECT * FROM task_cache WHERE 1=1';\n const params: any[] = [];\n\n if (!options.all && !options.status) {\n query += \" AND status NOT IN ('completed', 'cancelled')\";\n }\n\n if (options.status) {\n query += ' AND status = ?';\n params.push(options.status);\n }\n\n if (options.priority) {\n query += ' AND priority = ?';\n params.push(options.priority);\n }\n\n if (options.query) {\n query += ' AND (title LIKE ? OR description LIKE ?)';\n params.push(`%${options.query}%`, `%${options.query}%`);\n }\n\n query += ' ORDER BY priority ASC, created_at DESC LIMIT ?';\n params.push(parseInt(options.limit));\n\n const rows = db.prepare(query).all(...params) as any[];\n db.close();\n\n if (rows.length === 0) {\n console.log('\uD83D\uDCDD No tasks found');\n return;\n }\n\n console.log(`\\n\uD83D\uDCCB Tasks (${rows.length})\\n`);\n\n const priorityIcon: Record<string, string> = {\n urgent: '\uD83D\uDD34',\n high: '\uD83D\uDFE0',\n medium: '\uD83D\uDFE1',\n low: '\uD83D\uDFE2',\n };\n const statusIcon: Record<string, string> = {\n pending: '\u23F3',\n in_progress: '\uD83D\uDD04',\n completed: '\u2705',\n blocked: '\uD83D\uDEAB',\n cancelled: '\u274C',\n };\n\n rows.forEach((row) => {\n const pIcon = priorityIcon[row.priority] || '\u26AA';\n const sIcon = statusIcon[row.status] || '\u26AA';\n const id = row.id.slice(0, 10);\n console.log(`${sIcon} ${pIcon} [${id}] ${row.title}`);\n if (row.description) {\n const desc = row.description.split('\\n')[0].slice(0, 60);\n console.log(\n ` ${desc}${row.description.length > 60 ? '...' : ''}`\n );\n }\n });\n console.log('');\n } catch (error: unknown) {\n console.error('\u274C Failed to list tasks:', (error as Error).message);\n }\n });\n\n // Add task\n tasks\n .command('add <title>')\n .description('Add a new task')\n .option('-d, --description <text>', 'Task description')\n .option(\n '-p, --priority <priority>',\n 'Priority (urgent, high, medium, low)',\n 'medium'\n )\n .option('-t, --tags <tags>', 'Comma-separated tags')\n .action(async (title, options) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n const taskId = taskStore.createTask({\n title,\n description: options.description,\n priority: options.priority as TaskPriority,\n frameId: 'cli',\n tags: options.tags\n ? options.tags.split(',').map((t: string) => t.trim())\n : [],\n });\n\n console.log(`\u2705 Created task: ${taskId.slice(0, 10)}`);\n console.log(` Title: ${title}`);\n console.log(` Priority: ${options.priority}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to add task:', (error as Error).message);\n }\n });\n\n // Start task (set to in_progress)\n tasks\n .command('start <taskId>')\n .description('Start working on a task')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n // Find task by partial ID\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n taskStore.updateTaskStatus(task.id, 'in_progress', 'Started from CLI');\n console.log(`\uD83D\uDD04 Started: ${task.title}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to start task:', (error as Error).message);\n }\n });\n\n // Complete task\n tasks\n .command('done <taskId>')\n .alias('complete')\n .description('Mark task as completed')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n const taskStore = getTaskStore(projectRoot);\n if (!taskStore) return;\n\n try {\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n taskStore.updateTaskStatus(task.id, 'completed', 'Completed from CLI');\n console.log(`\u2705 Completed: ${task.title}`);\n } catch (error: unknown) {\n console.error('\u274C Failed to complete task:', (error as Error).message);\n }\n });\n\n // Show task details\n tasks\n .command('show <taskId>')\n .description('Show task details')\n .action(async (taskId) => {\n const projectRoot = process.cwd();\n\n try {\n const task = findTaskByPartialId(projectRoot, taskId);\n if (!task) {\n console.log(`\u274C Task not found: ${taskId}`);\n return;\n }\n\n console.log(`\\n\uD83D\uDCCB Task Details\\n`);\n console.log(`ID: ${task.id}`);\n console.log(`Title: ${task.title}`);\n console.log(`Status: ${task.status}`);\n console.log(`Priority: ${task.priority}`);\n console.log(\n `Created: ${new Date(task.created_at * 1000).toLocaleString()}`\n );\n if (task.completed_at) {\n console.log(\n `Completed: ${new Date(task.completed_at * 1000).toLocaleString()}`\n );\n }\n if (task.description) {\n console.log(`\\nDescription:\\n${task.description}`);\n }\n const tags = JSON.parse(task.tags || '[]');\n if (tags.length > 0) {\n console.log(`\\nTags: ${tags.join(', ')}`);\n }\n console.log('');\n } catch (error: unknown) {\n console.error('\u274C Failed to show task:', (error as Error).message);\n }\n });\n\n return tasks;\n}\n\nfunction findTaskByPartialId(\n projectRoot: string,\n partialId: string\n): any | null {\n const dbPath = join(projectRoot, '.stackmemory', 'context.db');\n if (!existsSync(dbPath)) return null;\n\n const db = new Database(dbPath);\n\n // Try exact match first, then partial\n let row = db.prepare('SELECT * FROM task_cache WHERE id = ?').get(partialId);\n\n if (!row) {\n row = db\n .prepare('SELECT * FROM task_cache WHERE id LIKE ?')\n .get(`${partialId}%`);\n }\n\n // Also try matching Linear identifier in title\n if (!row && partialId.match(/^ENG-\\d+$/i)) {\n row = db\n .prepare('SELECT * FROM task_cache WHERE title LIKE ?')\n .get(`%[${partialId.toUpperCase()}]%`);\n }\n\n db.close();\n return row || null;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { tmpdir } from "os";
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
const MAX_OUTPUT_SIZE = 5e4;
|
|
7
|
+
const EXECUTION_TIMEOUT = 3e4;
|
|
8
|
+
class CodeExecutionHandler {
|
|
9
|
+
allowedLanguages = ["python", "javascript", "typescript"];
|
|
10
|
+
sandboxDir;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.sandboxDir = join(tmpdir(), "stackmemory-sandbox");
|
|
13
|
+
this.ensureSandboxDir();
|
|
14
|
+
}
|
|
15
|
+
async ensureSandboxDir() {
|
|
16
|
+
try {
|
|
17
|
+
await fs.mkdir(this.sandboxDir, { recursive: true });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.error("Failed to create sandbox directory:", error);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Execute code in a controlled environment
|
|
24
|
+
*/
|
|
25
|
+
async executeCode(params) {
|
|
26
|
+
const { language, code, workingDirectory, timeout = EXECUTION_TIMEOUT } = params;
|
|
27
|
+
if (!this.allowedLanguages.includes(language.toLowerCase())) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
stdout: "",
|
|
31
|
+
stderr: `Language '${language}' is not allowed. Use: ${this.allowedLanguages.join(", ")}`,
|
|
32
|
+
exitCode: 1,
|
|
33
|
+
truncated: false
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const tempFile = join(
|
|
37
|
+
this.sandboxDir,
|
|
38
|
+
`code_${randomBytes(8).toString("hex")}.${this.getFileExtension(language)}`
|
|
39
|
+
);
|
|
40
|
+
try {
|
|
41
|
+
await fs.writeFile(tempFile, code, "utf-8");
|
|
42
|
+
const result = await this.runCode(language, tempFile, workingDirectory || this.sandboxDir, timeout);
|
|
43
|
+
await fs.unlink(tempFile).catch(() => {
|
|
44
|
+
});
|
|
45
|
+
return result;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
stdout: "",
|
|
50
|
+
stderr: `Execution error: ${error instanceof Error ? error.message : String(error)}`,
|
|
51
|
+
exitCode: 1,
|
|
52
|
+
truncated: false
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Run code with appropriate interpreter
|
|
58
|
+
*/
|
|
59
|
+
async runCode(language, filePath, workingDirectory, timeout) {
|
|
60
|
+
const command = this.getCommand(language);
|
|
61
|
+
const args = this.getArgs(language, filePath);
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
let stdout = "";
|
|
64
|
+
let stderr = "";
|
|
65
|
+
let truncated = false;
|
|
66
|
+
const child = spawn(command, args, {
|
|
67
|
+
cwd: workingDirectory,
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
PYTHONDONTWRITEBYTECODE: "1",
|
|
71
|
+
// Don't create .pyc files
|
|
72
|
+
NODE_ENV: "sandbox"
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
const timeoutId = setTimeout(() => {
|
|
76
|
+
child.kill("SIGTERM");
|
|
77
|
+
stderr += "\n[Process killed due to timeout]";
|
|
78
|
+
}, timeout);
|
|
79
|
+
child.stdout.on("data", (data) => {
|
|
80
|
+
stdout += data.toString();
|
|
81
|
+
if (stdout.length > MAX_OUTPUT_SIZE) {
|
|
82
|
+
truncated = true;
|
|
83
|
+
child.kill("SIGTERM");
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
child.stderr.on("data", (data) => {
|
|
87
|
+
stderr += data.toString();
|
|
88
|
+
if (stderr.length > MAX_OUTPUT_SIZE) {
|
|
89
|
+
truncated = true;
|
|
90
|
+
child.kill("SIGTERM");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
child.on("close", async (code) => {
|
|
94
|
+
clearTimeout(timeoutId);
|
|
95
|
+
let outputFile;
|
|
96
|
+
if (truncated) {
|
|
97
|
+
outputFile = join(this.sandboxDir, `output_${randomBytes(8).toString("hex")}.txt`);
|
|
98
|
+
await fs.writeFile(outputFile, stdout + "\n---STDERR---\n" + stderr, "utf-8").catch(() => {
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
resolve({
|
|
102
|
+
success: code === 0,
|
|
103
|
+
stdout: truncated ? stdout.slice(0, MAX_OUTPUT_SIZE) + "\n[Output truncated]" : stdout,
|
|
104
|
+
stderr: truncated ? stderr.slice(0, MAX_OUTPUT_SIZE) + "\n[Output truncated]" : stderr,
|
|
105
|
+
exitCode: code,
|
|
106
|
+
truncated,
|
|
107
|
+
outputFile
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
child.on("error", (error) => {
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
resolve({
|
|
113
|
+
success: false,
|
|
114
|
+
stdout: "",
|
|
115
|
+
stderr: `Failed to start process: ${error.message}`,
|
|
116
|
+
exitCode: null,
|
|
117
|
+
truncated: false
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get file extension for language
|
|
124
|
+
*/
|
|
125
|
+
getFileExtension(language) {
|
|
126
|
+
switch (language.toLowerCase()) {
|
|
127
|
+
case "python":
|
|
128
|
+
return "py";
|
|
129
|
+
case "javascript":
|
|
130
|
+
return "js";
|
|
131
|
+
case "typescript":
|
|
132
|
+
return "ts";
|
|
133
|
+
default:
|
|
134
|
+
return "txt";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get command for language
|
|
139
|
+
*/
|
|
140
|
+
getCommand(language) {
|
|
141
|
+
switch (language.toLowerCase()) {
|
|
142
|
+
case "python":
|
|
143
|
+
return "python3";
|
|
144
|
+
case "javascript":
|
|
145
|
+
return "node";
|
|
146
|
+
case "typescript":
|
|
147
|
+
return "npx";
|
|
148
|
+
default:
|
|
149
|
+
return "echo";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get arguments for language
|
|
154
|
+
*/
|
|
155
|
+
getArgs(language, filePath) {
|
|
156
|
+
switch (language.toLowerCase()) {
|
|
157
|
+
case "python":
|
|
158
|
+
return [filePath];
|
|
159
|
+
case "javascript":
|
|
160
|
+
return [filePath];
|
|
161
|
+
case "typescript":
|
|
162
|
+
return ["tsx", filePath];
|
|
163
|
+
default:
|
|
164
|
+
return ["Unsupported language"];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validate code for dangerous patterns
|
|
169
|
+
*/
|
|
170
|
+
validateCode(code) {
|
|
171
|
+
const warnings = [];
|
|
172
|
+
const dangerousPatterns = [
|
|
173
|
+
{ pattern: /import\s+os/i, message: "Importing os module detected" },
|
|
174
|
+
{ pattern: /import\s+subprocess/i, message: "Subprocess module detected" },
|
|
175
|
+
{ pattern: /exec\s*\(/i, message: "exec() function detected" },
|
|
176
|
+
{ pattern: /eval\s*\(/i, message: "eval() function detected" },
|
|
177
|
+
{ pattern: /__import__/i, message: "__import__ detected" },
|
|
178
|
+
{ pattern: /open\s*\([^)]*['"]\//i, message: "Absolute path file access detected" },
|
|
179
|
+
{ pattern: /require\s*\([^)]*child_process/i, message: "child_process module detected" },
|
|
180
|
+
{ pattern: /require\s*\([^)]*fs/i, message: "fs module access detected" }
|
|
181
|
+
];
|
|
182
|
+
for (const { pattern, message } of dangerousPatterns) {
|
|
183
|
+
if (pattern.test(code)) {
|
|
184
|
+
warnings.push(message);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
safe: warnings.length === 0,
|
|
189
|
+
warnings
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get sandbox status
|
|
194
|
+
*/
|
|
195
|
+
async getSandboxStatus() {
|
|
196
|
+
try {
|
|
197
|
+
const files = await fs.readdir(this.sandboxDir);
|
|
198
|
+
let totalSize = 0;
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
const stat = await fs.stat(join(this.sandboxDir, file));
|
|
201
|
+
totalSize += stat.size;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
sandboxDir: this.sandboxDir,
|
|
205
|
+
tempFiles: files.length,
|
|
206
|
+
totalSize
|
|
207
|
+
};
|
|
208
|
+
} catch {
|
|
209
|
+
return {
|
|
210
|
+
sandboxDir: this.sandboxDir,
|
|
211
|
+
tempFiles: 0,
|
|
212
|
+
totalSize: 0
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clean sandbox directory
|
|
218
|
+
*/
|
|
219
|
+
async cleanSandbox() {
|
|
220
|
+
try {
|
|
221
|
+
const files = await fs.readdir(this.sandboxDir);
|
|
222
|
+
for (const file of files) {
|
|
223
|
+
await fs.unlink(join(this.sandboxDir, file)).catch(() => {
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error("Failed to clean sandbox:", error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const codeExecutionHandlers = {
|
|
232
|
+
"code.execute": async (params) => {
|
|
233
|
+
const handler = new CodeExecutionHandler();
|
|
234
|
+
const validation = handler.validateCode(params.code);
|
|
235
|
+
if (!validation.safe && !params.force) {
|
|
236
|
+
return {
|
|
237
|
+
error: "Code validation failed",
|
|
238
|
+
warnings: validation.warnings,
|
|
239
|
+
hint: "Add force: true to execute anyway"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return await handler.executeCode(params);
|
|
243
|
+
},
|
|
244
|
+
"code.validate": async (params) => {
|
|
245
|
+
const handler = new CodeExecutionHandler();
|
|
246
|
+
return handler.validateCode(params.code);
|
|
247
|
+
},
|
|
248
|
+
"code.sandbox_status": async () => {
|
|
249
|
+
const handler = new CodeExecutionHandler();
|
|
250
|
+
return await handler.getSandboxStatus();
|
|
251
|
+
},
|
|
252
|
+
"code.clean_sandbox": async () => {
|
|
253
|
+
const handler = new CodeExecutionHandler();
|
|
254
|
+
await handler.cleanSandbox();
|
|
255
|
+
return { success: true, message: "Sandbox cleaned" };
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
export {
|
|
259
|
+
CodeExecutionHandler,
|
|
260
|
+
codeExecutionHandlers
|
|
261
|
+
};
|
|
262
|
+
//# sourceMappingURL=code-execution-handlers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/integrations/mcp/handlers/code-execution-handlers.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Code Execution MCP Handlers\n * Provides controlled Python/JavaScript code execution with safety measures\n */\n\nimport { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { tmpdir } from 'os';\nimport { randomBytes } from 'crypto';\n\ninterface ExecutionResult {\n success: boolean;\n stdout: string;\n stderr: string;\n exitCode: number | null;\n truncated: boolean;\n outputFile?: string;\n}\n\nconst MAX_OUTPUT_SIZE = 50000; // Maximum output size before truncation\nconst EXECUTION_TIMEOUT = 30000; // 30 seconds timeout\n\nexport class CodeExecutionHandler {\n private readonly allowedLanguages = ['python', 'javascript', 'typescript'];\n private readonly sandboxDir: string;\n\n constructor() {\n // Create a sandbox directory for code execution\n this.sandboxDir = join(tmpdir(), 'stackmemory-sandbox');\n this.ensureSandboxDir();\n }\n\n private async ensureSandboxDir(): Promise<void> {\n try {\n await fs.mkdir(this.sandboxDir, { recursive: true });\n } catch (error) {\n console.error('Failed to create sandbox directory:', error);\n }\n }\n\n /**\n * Execute code in a controlled environment\n */\n async executeCode(params: {\n language: string;\n code: string;\n workingDirectory?: string;\n timeout?: number;\n }): Promise<ExecutionResult> {\n const { language, code, workingDirectory, timeout = EXECUTION_TIMEOUT } = params;\n\n // Validate language\n if (!this.allowedLanguages.includes(language.toLowerCase())) {\n return {\n success: false,\n stdout: '',\n stderr: `Language '${language}' is not allowed. Use: ${this.allowedLanguages.join(', ')}`,\n exitCode: 1,\n truncated: false,\n };\n }\n\n // Create temporary file for code\n const tempFile = join(\n this.sandboxDir,\n `code_${randomBytes(8).toString('hex')}.${this.getFileExtension(language)}`\n );\n\n try {\n // Write code to temporary file\n await fs.writeFile(tempFile, code, 'utf-8');\n\n // Execute code\n const result = await this.runCode(language, tempFile, workingDirectory || this.sandboxDir, timeout);\n\n // Clean up temp file\n await fs.unlink(tempFile).catch(() => {});\n\n return result;\n } catch (error) {\n return {\n success: false,\n stdout: '',\n stderr: `Execution error: ${error instanceof Error ? error.message : String(error)}`,\n exitCode: 1,\n truncated: false,\n };\n }\n }\n\n /**\n * Run code with appropriate interpreter\n */\n private async runCode(\n language: string,\n filePath: string,\n workingDirectory: string,\n timeout: number\n ): Promise<ExecutionResult> {\n const command = this.getCommand(language);\n const args = this.getArgs(language, filePath);\n\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let truncated = false;\n\n const child = spawn(command, args, {\n cwd: workingDirectory,\n env: {\n ...process.env,\n PYTHONDONTWRITEBYTECODE: '1', // Don't create .pyc files\n NODE_ENV: 'sandbox',\n },\n });\n\n // Set timeout\n const timeoutId = setTimeout(() => {\n child.kill('SIGTERM');\n stderr += '\\n[Process killed due to timeout]';\n }, timeout);\n\n child.stdout.on('data', (data) => {\n stdout += data.toString();\n if (stdout.length > MAX_OUTPUT_SIZE) {\n truncated = true;\n child.kill('SIGTERM');\n }\n });\n\n child.stderr.on('data', (data) => {\n stderr += data.toString();\n if (stderr.length > MAX_OUTPUT_SIZE) {\n truncated = true;\n child.kill('SIGTERM');\n }\n });\n\n child.on('close', async (code) => {\n clearTimeout(timeoutId);\n\n // If output is truncated, save to file\n let outputFile: string | undefined;\n if (truncated) {\n outputFile = join(this.sandboxDir, `output_${randomBytes(8).toString('hex')}.txt`);\n await fs.writeFile(outputFile, stdout + '\\n---STDERR---\\n' + stderr, 'utf-8').catch(() => {});\n }\n\n resolve({\n success: code === 0,\n stdout: truncated ? stdout.slice(0, MAX_OUTPUT_SIZE) + '\\n[Output truncated]' : stdout,\n stderr: truncated ? stderr.slice(0, MAX_OUTPUT_SIZE) + '\\n[Output truncated]' : stderr,\n exitCode: code,\n truncated,\n outputFile,\n });\n });\n\n child.on('error', (error) => {\n clearTimeout(timeoutId);\n resolve({\n success: false,\n stdout: '',\n stderr: `Failed to start process: ${error.message}`,\n exitCode: null,\n truncated: false,\n });\n });\n });\n }\n\n /**\n * Get file extension for language\n */\n private getFileExtension(language: string): string {\n switch (language.toLowerCase()) {\n case 'python':\n return 'py';\n case 'javascript':\n return 'js';\n case 'typescript':\n return 'ts';\n default:\n return 'txt';\n }\n }\n\n /**\n * Get command for language\n */\n private getCommand(language: string): string {\n switch (language.toLowerCase()) {\n case 'python':\n return 'python3';\n case 'javascript':\n return 'node';\n case 'typescript':\n return 'npx';\n default:\n return 'echo';\n }\n }\n\n /**\n * Get arguments for language\n */\n private getArgs(language: string, filePath: string): string[] {\n switch (language.toLowerCase()) {\n case 'python':\n return [filePath];\n case 'javascript':\n return [filePath];\n case 'typescript':\n return ['tsx', filePath];\n default:\n return ['Unsupported language'];\n }\n }\n\n /**\n * Validate code for dangerous patterns\n */\n validateCode(code: string): { safe: boolean; warnings: string[] } {\n const warnings: string[] = [];\n const dangerousPatterns = [\n { pattern: /import\\s+os/i, message: 'Importing os module detected' },\n { pattern: /import\\s+subprocess/i, message: 'Subprocess module detected' },\n { pattern: /exec\\s*\\(/i, message: 'exec() function detected' },\n { pattern: /eval\\s*\\(/i, message: 'eval() function detected' },\n { pattern: /__import__/i, message: '__import__ detected' },\n { pattern: /open\\s*\\([^)]*['\"]\\//i, message: 'Absolute path file access detected' },\n { pattern: /require\\s*\\([^)]*child_process/i, message: 'child_process module detected' },\n { pattern: /require\\s*\\([^)]*fs/i, message: 'fs module access detected' },\n ];\n\n for (const { pattern, message } of dangerousPatterns) {\n if (pattern.test(code)) {\n warnings.push(message);\n }\n }\n\n return {\n safe: warnings.length === 0,\n warnings,\n };\n }\n\n /**\n * Get sandbox status\n */\n async getSandboxStatus(): Promise<{\n sandboxDir: string;\n tempFiles: number;\n totalSize: number;\n }> {\n try {\n const files = await fs.readdir(this.sandboxDir);\n let totalSize = 0;\n\n for (const file of files) {\n const stat = await fs.stat(join(this.sandboxDir, file));\n totalSize += stat.size;\n }\n\n return {\n sandboxDir: this.sandboxDir,\n tempFiles: files.length,\n totalSize,\n };\n } catch {\n return {\n sandboxDir: this.sandboxDir,\n tempFiles: 0,\n totalSize: 0,\n };\n }\n }\n\n /**\n * Clean sandbox directory\n */\n async cleanSandbox(): Promise<void> {\n try {\n const files = await fs.readdir(this.sandboxDir);\n for (const file of files) {\n await fs.unlink(join(this.sandboxDir, file)).catch(() => {});\n }\n } catch (error) {\n console.error('Failed to clean sandbox:', error);\n }\n }\n}\n\n// Export MCP tool handlers\nexport const codeExecutionHandlers = {\n 'code.execute': async (params: any) => {\n const handler = new CodeExecutionHandler();\n \n // Validate code before execution\n const validation = handler.validateCode(params.code);\n if (!validation.safe && !params.force) {\n return {\n error: 'Code validation failed',\n warnings: validation.warnings,\n hint: 'Add force: true to execute anyway',\n };\n }\n\n return await handler.executeCode(params);\n },\n\n 'code.validate': async (params: any) => {\n const handler = new CodeExecutionHandler();\n return handler.validateCode(params.code);\n },\n\n 'code.sandbox_status': async () => {\n const handler = new CodeExecutionHandler();\n return await handler.getSandboxStatus();\n },\n\n 'code.clean_sandbox': async () => {\n const handler = new CodeExecutionHandler();\n await handler.cleanSandbox();\n return { success: true, message: 'Sandbox cleaned' };\n },\n};"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const codeExecutionTools = [
|
|
2
|
+
{
|
|
3
|
+
name: "code.execute",
|
|
4
|
+
description: "Execute Python, JavaScript, or TypeScript code in a sandboxed environment",
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
language: {
|
|
9
|
+
type: "string",
|
|
10
|
+
enum: ["python", "javascript", "typescript"],
|
|
11
|
+
description: "Programming language to execute"
|
|
12
|
+
},
|
|
13
|
+
code: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Code to execute"
|
|
16
|
+
},
|
|
17
|
+
workingDirectory: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Optional working directory for execution"
|
|
20
|
+
},
|
|
21
|
+
timeout: {
|
|
22
|
+
type: "number",
|
|
23
|
+
description: "Execution timeout in milliseconds (default: 30000)"
|
|
24
|
+
},
|
|
25
|
+
force: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
description: "Force execution even if code validation fails"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ["language", "code"]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "code.validate",
|
|
35
|
+
description: "Validate code for potential security issues",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
code: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Code to validate"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ["code"]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "code.sandbox_status",
|
|
49
|
+
description: "Get status of the code execution sandbox",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: {}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "code.clean_sandbox",
|
|
57
|
+
description: "Clean temporary files from the sandbox",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
];
|
|
64
|
+
const codeOnlyModeExample = `
|
|
65
|
+
# When in code_only mode, Claude can ONLY execute code
|
|
66
|
+
|
|
67
|
+
## Example Python execution:
|
|
68
|
+
\`\`\`python
|
|
69
|
+
import numpy as np
|
|
70
|
+
import matplotlib.pyplot as plt
|
|
71
|
+
|
|
72
|
+
# Generate data
|
|
73
|
+
x = np.linspace(0, 10, 100)
|
|
74
|
+
y = np.sin(x)
|
|
75
|
+
|
|
76
|
+
# Create visualization (won't display but will execute)
|
|
77
|
+
plt.plot(x, y)
|
|
78
|
+
plt.title('Sine Wave')
|
|
79
|
+
plt.xlabel('X')
|
|
80
|
+
plt.ylabel('Y')
|
|
81
|
+
|
|
82
|
+
# Calculate statistics
|
|
83
|
+
mean_y = np.mean(y)
|
|
84
|
+
std_y = np.std(y)
|
|
85
|
+
print(f"Mean: {mean_y:.4f}")
|
|
86
|
+
print(f"Std: {std_y:.4f}")
|
|
87
|
+
\`\`\`
|
|
88
|
+
|
|
89
|
+
## Example JavaScript execution:
|
|
90
|
+
\`\`\`javascript
|
|
91
|
+
// Process data
|
|
92
|
+
const data = Array.from({length: 10}, (_, i) => i ** 2);
|
|
93
|
+
|
|
94
|
+
// Calculate sum
|
|
95
|
+
const sum = data.reduce((a, b) => a + b, 0);
|
|
96
|
+
console.log('Sum of squares:', sum);
|
|
97
|
+
|
|
98
|
+
// Async operation
|
|
99
|
+
async function fetchData() {
|
|
100
|
+
// Simulate API call
|
|
101
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
102
|
+
return { status: 'success', data: [1, 2, 3] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fetchData().then(result => {
|
|
106
|
+
console.log('Async result:', result);
|
|
107
|
+
});
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
## Benefits of code_only mode:
|
|
111
|
+
1. Safe computational environment
|
|
112
|
+
2. No file system modifications
|
|
113
|
+
3. No network access
|
|
114
|
+
4. Pure problem-solving focus
|
|
115
|
+
5. Ideal for algorithms, data analysis, and mathematical computations
|
|
116
|
+
`;
|
|
117
|
+
export {
|
|
118
|
+
codeExecutionTools,
|
|
119
|
+
codeOnlyModeExample
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=tool-definitions-code.js.map
|