UNPKG

termcode

Version:

Superior terminal AI coding agent with enterprise-grade security, intelligent error recovery, performance monitoring, and plugin system - Advanced Claude Code alternative

1,015 lines (1,014 loc) • 57.4 kB
#!/usr/bin/env node import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import path from "node:path"; import readline from "node:readline"; import { ensureMemory } from "./memory.js"; import { runTask } from "./agent/planner.js"; import { buildIndex } from "./retriever/indexer.js"; import { ensureCleanGit, createBranch, checkoutBranch, deleteBranch, mergeBranch } from "./tools/git.js"; import { createPullRequest } from "./tools/github.js"; import { detectProjectType } from "./tools/test.js"; import { runShell } from "./tools/shell.js"; import { getSessionLogs, clearSessionLogs } from "./util/sessionLog.js"; import { log } from "./util/logging.js"; import { runOnboarding, getProviderKey, setProviderKey, listProviderKeys } from "./onboarding.js"; import { loadConfig, validateConfig, resetConfig, configPath } from "./state/config.js"; import { loadSession, createSession, updateSession, listRecentSessions } from "./state/session.js"; import { formatBudgetStatus } from "./util/costs.js"; import { getIndexStats } from "./retriever/indexer.js"; import { getProvider, registry } from "./providers/index.js"; import { terminal } from "./ui/terminal.js"; import { workspaceManager } from "./workspace/manager.js"; import { PipelineProcessor } from "./tools/pipe.js"; import { enhancedSandbox } from "./security/enhanced-sandbox.js"; import { hookManager } from "./hooks/manager.js"; import { intelligentErrorRecovery } from "./intelligence/error-recovery.js"; import { enhancedDiffManager } from "./agent/enhanced-diff.js"; import { performanceMonitor } from "./monitoring/performance.js"; import { smartSuggestionEngine } from "./cli/smart-suggestions.js"; import { pluginSystem } from "./plugins/system.js"; import { backgroundExecutor } from "./tools/background.js"; import { permissionManager } from "./security/permissions.js"; import { autocompleteEngine, setupAutocomplete } from "./ui/autocomplete.js"; import { realtimeInputManager } from "./ui/realtime-input.js"; const argv = yargs(hideBin(process.argv)) .scriptName("termcode") .usage("$0 [task] --repo <path> [options]") .command("$0 [task]", "Execute a coding task or start interactive session", (yargs) => { yargs.positional("task", { describe: "High-level coding request (feature/refactor/fix/etc.)", type: "string", }); }) .option("repo", { alias: "r", type: "string", demandOption: true, describe: "Path to repository (required)", }) .option("dry", { alias: "d", type: "boolean", default: false, describe: "Preview changes without applying them", }) .option("model", { alias: "m", type: "string", describe: "Override model (gpt-4o, claude-3-5-sonnet, etc.)", }) .option("provider", { alias: "p", type: "string", describe: "Override provider (openai, anthropic, xai, google, etc.)", }) .option("ui", { alias: "u", type: "boolean", default: false, describe: "Launch full-screen TUI interface", }) .option("skip-git-check", { type: "boolean", default: false, describe: "Skip git clean state check (use with caution)", }) .example("$0 --repo .", "Start interactive session in current directory") .example('$0 "Add user auth" --repo .', "Execute one-shot task") .example("$0 --repo . --provider anthropic", "Use specific AI provider") .example("$0 --repo . --model gpt-4o --dry", "Preview changes with GPT-4o") .example("$0 --repo . --ui", "Launch full-screen TUI interface") .version() .help() .parseSync(); const repo = path.resolve(String(argv.repo)); const initialTask = String(argv._?.[0] || ""); const dry = Boolean(argv.dry); const cliModel = String(argv.model || ""); const cliProvider = String(argv.provider || ""); const launchUI = Boolean(argv.ui); const skipGitCheck = Boolean(argv["skip-git-check"]); (async () => { try { // Validate repository path exists try { const fs = await import("node:fs/promises"); const stat = await fs.stat(repo); if (!stat.isDirectory()) { log.error(`Repository path is not a directory: ${repo}`); process.exit(1); } } catch (error) { log.error(`Repository path does not exist: ${repo}`); process.exit(1); } // Check if first run (no config) let config = await loadConfig(); if (!config) { console.log("šŸš€ Welcome to TermCode — first-run setup:"); config = await runOnboarding(); } // Validate provider exists if (cliProvider && !Object.keys(registry).includes(cliProvider)) { log.error(`Unknown provider: ${cliProvider}. Available: ${Object.keys(registry).join(", ")}`); process.exit(1); } // Override with CLI options const currentProvider = cliProvider || config.defaultProvider; const currentModel = cliModel || config.models[currentProvider]?.chat; if (!currentModel) { log.error(`No model configured for provider ${currentProvider}`); if (!cliModel) { log.info(`Try: termcode --repo ${repo} --provider ${currentProvider} --model <model-name>`); } process.exit(1); } // Validate provider has required API key (except ollama) if (currentProvider !== "ollama" && !(await getProviderKey(currentProvider))) { log.error(`No API key found for ${currentProvider}. Run 'termcode --repo ${repo}' to set up keys.`); process.exit(1); } log.step("Initializing", `${currentProvider} (${currentModel})`); log.step("Loading repository", repo); // Check for clean Git state (unless skipped) if (!skipGitCheck) { const clean = await ensureCleanGit(repo); if (!clean.ok) { log.error(clean.error); log.info("Tip: Use --skip-git-check to bypass this check (use with caution)"); process.exit(1); } } else { log.warn("Skipping git clean state check"); } // Create working branch const branchName = `termcode-${Date.now()}`; const branch = createBranch(repo, branchName); if (!branch.ok) { log.error("Failed to create branch:", branch.error); process.exit(1); } log.step("Created branch", branchName); log.step("Building index", "scanning codebase..."); await ensureMemory(repo); await buildIndex(repo); // Load or create session let session = await loadSession(repo); if (!session) { session = await createSession(repo, currentProvider, currentModel, branchName); log.step("Created session", "new project session"); } else { // Update session with current info await updateSession(repo, { provider: currentProvider, model: currentModel, branchName: branchName }); log.step("Loaded session", `${session.recentTasks.length} recent tasks`); } // Initialize enhanced systems await workspaceManager.initialize(); await hookManager.initialize(); await enhancedSandbox.initialize(); await performanceMonitor.initialize(); await pluginSystem.initialize(); await permissionManager.initialize(repo); // Initialize autocomplete with project context autocompleteEngine.initialize({ repoPath: repo, currentDir: repo, provider: currentProvider, model: currentModel, projectType: (await detectProjectType(repo)).type }); // Show project info const projectInfo = await detectProjectType(repo); const indexStats = await getIndexStats(repo); // Load workspace const workspace = await workspaceManager.loadWorkspace(repo, projectInfo); // Show enhanced welcome screen terminal.setTheme(workspace.preferences.theme); terminal.showWelcome(projectInfo, { provider: currentProvider, model: currentModel, branchName }); if (indexStats) { log.raw(` ${log.colors.dim("Index:")} ${log.colors.green(indexStats.chunkCount.toString())} chunks from ${log.colors.green(indexStats.fileCount.toString())} files`); } // Session state for REPL let sessionState = { provider: currentProvider, model: currentModel, config: config, session: session, projectInfo: projectInfo }; // If user gave a one-shot task, run & exit if (initialTask) { await runTask(repo, initialTask, dry, sessionState.model, branchName, sessionState.provider); process.exit(0); } // Launch TUI if requested if (launchUI) { log.info("TUI is temporarily disabled - compiling React components..."); log.info("Use the regular CLI mode for now"); // const { startTUI } = await import("./ui/App.js"); // // log.step("Launching TUI", "full-screen interface"); // // startTUI({ // repo, // provider: currentProvider, // model: currentModel, // config, // session, // projectInfo, // }); // // return; // TUI handles everything from here } // Otherwise start REPL session with enhanced features const rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: terminal.getPrompt(), historySize: 100, }); // Setup enhanced autocomplete and real-time input setupAutocomplete(rl, autocompleteEngine); realtimeInputManager.initialize(rl); log.info("Enhanced CLI features enabled:"); log.raw(` ${log.colors.cyan("Tab")} - Autocomplete files, commands, @-mentions`); log.raw(` ${log.colors.cyan("@filename")} - Reference files and resources`); log.raw(` ${log.colors.cyan("/bg command")} - Run commands in background`); log.raw(` ${log.colors.cyan("/permissions")} - Manage tool permissions`); log.raw(` ${log.colors.cyan("Enter (while AI working)")} - Send real-time guidance`); log.raw(` ${log.colors.cyan("Ctrl+G (while AI working)")} - Send urgent message`); log.raw(""); rl.prompt(); rl.on("line", async (line) => { const input = line.trim(); const cmd = input.toLowerCase(); if (!input) { rl.prompt(); return; } // Exit commands if (cmd === "exit" || cmd === "quit") { rl.close(); return; } // Provider/Model switching commands if (input.startsWith("/provider ")) { const providerId = input.split(" ")[1]; const availableProviders = Object.keys(registry); if (!availableProviders.includes(providerId)) { log.error(`Unknown provider: ${providerId}. Available: ${availableProviders.join(", ")}`); rl.prompt(); return; } // Check if provider has required key (except for ollama) if (providerId !== "ollama" && !(await getProviderKey(providerId))) { log.warn(`No API key found for ${providerId}. Please add one:`); const inquirer = await import("inquirer"); const { key } = await inquirer.default.prompt([{ type: "password", name: "key", message: `API key for ${providerId}:`, mask: "•" }]); if (key) { await setProviderKey(providerId, key); log.info("āœ“ API key saved"); } else { log.error("No API key provided"); rl.prompt(); return; } } sessionState.provider = providerId; sessionState.model = sessionState.config.models[providerId]?.chat || ""; if (!sessionState.model) { log.error(`No chat model configured for ${providerId}`); rl.prompt(); return; } log.success(`Switched to ${log.colors.bright(providerId)} (${sessionState.model})`); rl.prompt(); return; } if (input.startsWith("/model ")) { const modelId = input.slice(7).trim(); sessionState.model = modelId; log.success(`Model updated to ${log.colors.bright(modelId)}`); rl.prompt(); return; } if (cmd === "/keys") { const providerKeys = await listProviderKeys(); const availableProviders = Object.keys(registry); const providers = availableProviders.map(id => ({ id, hasKey: id === "ollama" || providerKeys.includes(id) })); terminal.showProviderStatus(providers); rl.prompt(); return; } if (cmd === "/health") { log.raw(""); log.raw(log.colors.bright("šŸ„ Provider Health Check:")); const availableProviders = Object.keys(registry); for (const providerId of availableProviders) { const providerName = log.colors.cyan(providerId.padEnd(12)); process.stdout.write(` ${providerName} ${log.colors.dim("checking...")}`); try { const provider = getProvider(providerId); if (provider.healthCheck) { const health = await provider.healthCheck(); const statusIcon = health.status === "healthy" ? log.colors.green("āœ…") : health.status === "degraded" ? log.colors.yellow("āš ļø") : log.colors.red("āŒ"); const statusText = health.status === "healthy" ? log.colors.green("healthy") : health.status === "degraded" ? log.colors.yellow("degraded") : log.colors.red("error"); const latencyText = health.latency ? log.colors.dim(` (${health.latency}ms)`) : ""; process.stdout.write(`\\r ${providerName} ${statusIcon} ${statusText}${latencyText}\\n`); if (health.error) { log.raw(` ${log.colors.dim("Error:")} ${log.colors.red(health.error)}`); } if (health.models && health.models.length > 0) { const modelList = health.models.slice(0, 3).join(", "); const moreText = health.models.length > 3 ? `... (${health.models.length} total)` : ""; log.raw(` ${log.colors.dim("Models:")} ${log.colors.dim(modelList + moreText)}`); } } else { process.stdout.write(`\\r ${providerName} ${log.colors.dim("āž– health check not implemented")}\\n`); } } catch (error) { const errorText = error instanceof Error ? error.message : "unknown"; process.stdout.write(`\\r ${providerName} ${log.colors.red("āŒ error")} ${log.colors.dim("- " + errorText)}\\n`); } } log.raw(""); rl.prompt(); return; } if (cmd === "/whoami") { log.raw(""); log.raw(log.colors.bright("šŸ¤– Current Session:")); log.raw(` ${log.colors.dim("Provider:")} ${log.colors.cyan(sessionState.provider)}`); log.raw(` ${log.colors.dim("Model:")} ${log.colors.bright(sessionState.model)}`); log.raw(` ${log.colors.dim("Branch:")} ${log.colors.magenta(branchName)}`); log.raw(` ${log.colors.dim("Project:")} ${log.colors.cyan(sessionState.projectInfo.type)}`); const enabledTools = Object.entries(sessionState.config.tools) .filter(([_, enabled]) => enabled) .map(([name]) => name); log.raw(` ${log.colors.dim("Tools:")} ${log.colors.green(enabledTools.join(", "))}`); // Show session stats if (sessionState.session.totalTokensUsed > 0) { log.raw(` ${log.colors.dim("Usage:")} ${log.colors.yellow(sessionState.session.totalTokensUsed.toString())} tokens, ${log.colors.yellow("$" + sessionState.session.totalCostUSD.toFixed(4))}`); } log.raw(` ${log.colors.dim("Config:")} ${log.colors.dim(configPath())}`); log.raw(""); rl.prompt(); return; } if (cmd === "/budget") { log.raw(""); const budgetStatus = await formatBudgetStatus(); log.raw(budgetStatus); log.raw(""); rl.prompt(); return; } if (cmd === "/sessions") { log.raw(""); log.raw(log.colors.bright("šŸ“‹ Recent Sessions:")); const recentSessions = await listRecentSessions(5); if (recentSessions.length === 0) { log.raw(log.colors.dim(" No recent sessions found")); } else { for (const sess of recentSessions) { const timeAgo = new Date(Date.now() - new Date(sess.lastUsed).getTime()).toISOString().substr(11, 8); const current = sess.repoPath === repo ? log.colors.green(" (current)") : ""; log.raw(` ${log.colors.cyan(sess.repoPath.split('/').pop() || sess.repoPath)} - ${log.colors.dim(sess.provider)}/${log.colors.dim(sess.model)} - ${log.colors.dim(timeAgo)} ago${current}`); if (sess.recentTasks.length > 0) { log.raw(` ${log.colors.dim("Last:")} ${log.colors.dim(sess.recentTasks[0])}`); } } } log.raw(""); rl.prompt(); return; } if (cmd === "/config") { log.raw(""); log.raw(log.colors.bright("āš™ļø Configuration Commands:")); log.raw(` ${log.colors.cyan("/config validate")} ${log.colors.dim("- Validate current configuration")}`); log.raw(` ${log.colors.cyan("/config reset")} ${log.colors.dim("- Reset configuration (requires re-setup)")}`); log.raw(` ${log.colors.cyan("/config path")} ${log.colors.dim("- Show config file path")}`); log.raw(` ${log.colors.cyan("/config edit")} ${log.colors.dim("- Open config in editor")}`); log.raw(""); rl.prompt(); return; } if (input.startsWith("/config ")) { const subCmd = input.split(" ")[1]; switch (subCmd) { case "validate": const validation = await validateConfig(); if (validation.valid) { log.info("āœ… Configuration is valid"); } else { log.error("āŒ Configuration errors:"); validation.errors.forEach(err => console.log(` • ${err}`)); } break; case "reset": const inquirer = await import("inquirer"); const { confirm } = await inquirer.default.prompt([{ type: "confirm", name: "confirm", message: "Reset configuration? This will require re-setup.", default: false }]); if (confirm) { await resetConfig(); log.info("āœ… Configuration reset. Run termcode again to re-configure."); rl.close(); return; } else { log.info("Reset cancelled"); } break; case "path": console.log(`\\nšŸ“ Configuration file: ${configPath()}`); break; case "edit": const { spawn } = await import("node:child_process"); const editor = process.env.EDITOR || "nano"; spawn(editor, [configPath()], { stdio: "inherit" }); break; default: log.error(`Unknown config command: ${subCmd}`); log.info("Use '/config' to see available commands"); } rl.prompt(); return; } // Git workflow commands if (cmd === "rollback") { log.step("Rolling back", "discarding all changes..."); checkoutBranch(repo, "main"); deleteBranch(repo, branchName); log.success("Rollback complete - switched back to main"); rl.close(); return; } if (cmd === "merge") { log.step("Merging", `${branchName} into main...`); checkoutBranch(repo, "main"); const m = mergeBranch(repo, branchName); if (!m.ok) { log.error("Merge failed:", m.error); } else { log.success("Merge complete"); deleteBranch(repo, branchName); } rl.close(); return; } // PR command: pr "Title" if (input.startsWith("pr ")) { const title = input.slice(3).replace(/^["']|["']$/g, ""); if (!title) { log.error("Usage: pr \"Pull request title\""); rl.prompt(); return; } try { const logs = await getSessionLogs(repo); const branchLogs = logs.filter(l => l.branchName === branchName); const body = branchLogs.map(l => `• ${l.task}`).join("\\n") + "\\n\\nšŸ¤– Generated with TermCode"; const prUrl = await createPullRequest(repo, branchName, title, body); log.success(`Pull request created: ${log.colors.blue(prUrl)}`); } catch (error) { log.error("Failed to create PR:", error.message); } rl.prompt(); return; } // Test commands if (cmd === "test") { const { runTests } = await import("./tools/test.js"); const result = await runTests(repo); if (result.success) { let message = `Tests passed with ${result.runner}`; if (result.testsRun) { message += ` (${result.testsRun} tests`; if (result.testsPassed) message += `, ${result.testsPassed} passed`; if (result.testsFailed) message += `, ${result.testsFailed} failed`; message += ")"; } if (result.duration) { message += ` in ${(result.duration / 1000).toFixed(1)}s`; } log.success(message); } else { log.error(`Tests failed: ${result.output.split('\n')[0]}`); } rl.prompt(); return; } if (cmd === "lint") { const { runLinter } = await import("./tools/test.js"); const result = await runLinter(repo); if (result.success) { let message = `Linting passed with ${result.runner}`; if (result.duration) { message += ` in ${(result.duration / 1000).toFixed(1)}s`; } log.success(message); } else { log.error(`Linting failed: ${result.output.split('\n')[0]}`); } rl.prompt(); return; } if (cmd === "build") { const { runBuild } = await import("./tools/test.js"); const result = await runBuild(repo); if (result.success) { let message = `Build passed with ${result.runner}`; if (result.duration) { message += ` in ${(result.duration / 1000).toFixed(1)}s`; } log.success(message); } else { log.error(`Build failed: ${result.output.split('\n')[0]}`); } rl.prompt(); return; } // Log commands if (cmd === "log") { const logs = await getSessionLogs(repo); const branchLogs = logs.filter(l => l.branchName === branchName); if (branchLogs.length === 0) { log.raw(""); log.raw(log.colors.dim("šŸ“‹ No session logs found for this branch")); log.raw(""); } else { log.raw(""); log.raw(log.colors.bright("šŸ“‹ Session Log:")); branchLogs.forEach((entry, i) => { const num = log.colors.dim(`${i + 1}.`); const task = log.colors.bright(entry.task); log.raw(`${num} ${task}`); if (entry.applied && entry.applied.length > 0) { const files = entry.applied.map(f => log.colors.cyan(f)).join(", "); log.raw(` ${log.colors.dim("Applied:")} ${files}`); } log.raw(` ${log.colors.dim("Model:")} ${log.colors.magenta(entry.model)}`); if (i < branchLogs.length - 1) log.raw(""); }); log.raw(""); } rl.prompt(); return; } if (cmd === "clear-log") { await clearSessionLogs(repo); log.success("Session logs cleared"); rl.prompt(); return; } // Shell command: !<command> if (input.startsWith("!")) { const shellCmd = input.slice(1).trim(); if (!shellCmd) { log.error("Usage: !<command>"); rl.prompt(); return; } log.step("Executing", shellCmd); const result = await runShell(shellCmd.split(" "), repo); if (result.ok) { if (result.data.stdout) { log.raw(result.data.stdout); } if (result.data.stderr) { log.raw(log.colors.yellow(result.data.stderr)); } const code = result.data.code; if (code === 0) { log.success(`Command completed successfully`); } else { log.warn(`Command exited with code ${code}`); } } else { log.error("Shell command failed:", result.error); } rl.prompt(); return; } // Enhanced system commands if (cmd === "/hooks") { const hooks = hookManager.listHooks(); log.raw(""); log.raw(log.colors.bright("šŸ”— Active Hooks:")); hooks.forEach(hook => { const status = hook.enabled ? log.colors.green("āœ“") : log.colors.red("āŒ"); log.raw(` ${status} ${log.colors.cyan(hook.name)} - ${log.colors.dim(hook.description)}`); }); log.raw(""); rl.prompt(); return; } if (cmd === "/security") { const stats = enhancedSandbox.getSecurityStats(); log.raw(""); log.raw(log.colors.bright("šŸ›”ļø Security Statistics:")); log.raw(` ${log.colors.dim("Total Violations:")} ${log.colors.yellow(stats.totalViolations.toString())}`); log.raw(` ${log.colors.dim("By Severity:")}`); Object.entries(stats.violationsBySeverity).forEach(([severity, count]) => { const color = severity === 'critical' ? 'red' : severity === 'high' ? 'yellow' : 'green'; log.raw(` ${log.colors[color](severity)}: ${count}`); }); log.raw(""); rl.prompt(); return; } if (cmd === "/diffs") { const activeDiffs = enhancedDiffManager.getActiveDiffs(); const stats = enhancedDiffManager.getDiffStats(); log.raw(""); log.raw(log.colors.bright("šŸ“ Diff Management:")); log.raw(` ${log.colors.dim("Active Diffs:")} ${log.colors.cyan(stats.activeDiffs.toString())}`); log.raw(` ${log.colors.dim("Success Rate:")} ${log.colors.green((stats.successRate * 100).toFixed(1) + '%')}`); if (activeDiffs.length > 0) { log.raw(` ${log.colors.dim("Recent:")}`); activeDiffs.slice(-5).forEach(diff => { const risk = diff.impact.riskLevel; const color = risk === 'critical' ? 'red' : risk === 'high' ? 'yellow' : 'green'; log.raw(` ${log.colors[color](risk)} ${diff.id} - ${diff.changes.length} files`); }); } log.raw(""); rl.prompt(); return; } if (cmd === "/intelligence") { const stats = intelligentErrorRecovery.getRecoveryStats(); log.raw(""); log.raw(log.colors.bright("🧠 Intelligence Statistics:")); log.raw(` ${log.colors.dim("Total Errors:")} ${log.colors.yellow(stats.totalErrors.toString())}`); log.raw(` ${log.colors.dim("Successful Recoveries:")} ${log.colors.green(stats.successfulRecoveries.toString())}`); log.raw(` ${log.colors.dim("Average Recovery Time:")} ${log.colors.cyan((stats.averageRecoveryTime / 1000).toFixed(1) + 's')}`); if (stats.topErrorCategories.length > 0) { log.raw(` ${log.colors.dim("Top Error Categories:")}`); stats.topErrorCategories.forEach(({ category, count }) => { log.raw(` ${log.colors.magenta(category)}: ${count}`); }); } log.raw(""); rl.prompt(); return; } if (cmd === "/performance") { const stats = performanceMonitor.getPerformanceStats(); const recommendations = performanceMonitor.getOptimizationRecommendations(); log.raw(""); log.raw(log.colors.bright("⚔ Performance Statistics:")); log.raw(` ${log.colors.dim("Total Metrics:")} ${log.colors.cyan(stats.overview.totalMetrics.toString())}`); log.raw(` ${log.colors.dim("Active Alerts:")} ${log.colors.yellow(stats.overview.activeAlerts.toString())}`); log.raw(` ${log.colors.dim("Avg Response Time:")} ${log.colors.magenta(Math.round(stats.overview.avgResponseTime).toString() + 'ms')}`); log.raw(` ${log.colors.dim("Success Rate:")} ${log.colors.green((stats.overview.successRate * 100).toFixed(1) + '%')}`); if (Object.keys(stats.breakdown.byProvider).length > 0) { log.raw(` ${log.colors.dim("By Provider:")}`); Object.entries(stats.breakdown.byProvider).forEach(([provider, data]) => { log.raw(` ${log.colors.cyan(provider)}: ${Math.round(data.avgTime)}ms (${data.calls} calls)`); }); } if (recommendations.length > 0) { log.raw(` ${log.colors.dim("Recommendations:")}`); recommendations.slice(0, 3).forEach(rec => { const priority = rec.priority === 'high' ? log.colors.red('HIGH') : rec.priority === 'medium' ? log.colors.yellow('MED') : log.colors.green('LOW'); log.raw(` ${priority} ${rec.title}`); log.raw(` ${log.colors.dim(rec.description)}`); }); } log.raw(""); rl.prompt(); return; } if (cmd === "/plugins") { const plugins = pluginSystem.listPlugins(); const stats = pluginSystem.getPluginStats(); log.raw(""); log.raw(log.colors.bright("šŸ”Œ Plugin System:")); log.raw(` ${log.colors.dim("Total Plugins:")} ${log.colors.cyan(stats.totalPlugins.toString())}`); log.raw(` ${log.colors.dim("Enabled:")} ${log.colors.green(stats.enabledPlugins.toString())}`); if (Object.keys(stats.pluginsByType).length > 0) { log.raw(` ${log.colors.dim("By Type:")}`); Object.entries(stats.pluginsByType).forEach(([type, count]) => { log.raw(` ${log.colors.magenta(type)}: ${count}`); }); } if (plugins.length > 0) { log.raw(` ${log.colors.dim("Installed Plugins:")}`); plugins.slice(0, 5).forEach(plugin => { const status = plugin.enabled ? log.colors.green("āœ“") : log.colors.red("āŒ"); log.raw(` ${status} ${log.colors.cyan(plugin.manifest.name)} v${plugin.version}`); log.raw(` ${log.colors.dim(plugin.manifest.description)}`); }); } log.raw(""); rl.prompt(); return; } if (cmd === "/suggestions") { const suggestions = await smartSuggestionEngine.getSmartSuggestions("", { repoPath: repo, provider: sessionState.provider, model: sessionState.model, projectInfo: sessionState.projectInfo, currentBranch: branchName }); const suggestionStats = smartSuggestionEngine.getSuggestionStats(); log.raw(""); log.raw(log.colors.bright("šŸ’” Smart Suggestions:")); log.raw(` ${log.colors.dim("Total Patterns:")} ${log.colors.cyan(suggestionStats.learnedPatterns.toString())}`); log.raw(` ${log.colors.dim("Accuracy:")} ${log.colors.green((suggestionStats.accuracy * 100).toFixed(1) + '%')}`); if (suggestions.length > 0) { log.raw(` ${log.colors.dim("Current Suggestions:")}`); suggestions.slice(0, 5).forEach((suggestion, i) => { const priority = suggestion.priority === 'high' ? log.colors.red('HIGH') : suggestion.priority === 'medium' ? log.colors.yellow('MED') : log.colors.green('LOW'); log.raw(` ${i + 1}. ${priority} ${suggestion.title} (${Math.round(suggestion.confidence * 100)}%)`); log.raw(` ${log.colors.dim(suggestion.description)}`); }); } log.raw(""); rl.prompt(); return; } // Help command if (cmd === "help") { terminal.showHelp(); rl.prompt(); return; } // Check for pipe commands if (PipelineProcessor.hasPipes(input)) { log.step("Executing", "pipe command"); const processor = new PipelineProcessor(repo); const result = await processor.execute(input); if (result.ok) { if (result.data) { log.raw(result.data); } log.success("Pipe command completed"); } else { terminal.showError(result.error || "Pipe execution failed"); } rl.prompt(); return; } // Check for theme command if (input.startsWith("/theme ")) { const themeName = input.slice(7).trim(); terminal.setTheme(themeName); rl.setPrompt(terminal.getPrompt()); log.success(`Theme changed to: ${themeName}`); // Update workspace preferences await workspaceManager.updateWorkspacePreferences(repo, { theme: themeName }); rl.prompt(); return; } // Check for workspace commands if (cmd === "/workspace") { const workspace = workspaceManager.getCurrentWorkspace(); if (workspace) { log.raw(""); log.raw(log.colors.bright("šŸ“ Current Workspace")); log.raw(` ${log.colors.dim("Name:")} ${log.colors.cyan(workspace.name)}`); log.raw(` ${log.colors.dim("Type:")} ${log.colors.magenta(workspace.type)}`); if (workspace.framework) { log.raw(` ${log.colors.dim("Framework:")} ${log.colors.yellow(workspace.framework)}`); } log.raw(` ${log.colors.dim("Path:")} ${log.colors.dim(workspace.path)}`); log.raw(` ${log.colors.dim("Last Used:")} ${log.colors.dim(new Date(workspace.lastUsed).toLocaleString())}`); if (workspace.bookmarks.length > 0) { log.raw(` ${log.colors.dim("Bookmarks:")} ${workspace.bookmarks.map(b => log.colors.blue(b)).join(", ")}`); } log.raw(""); } rl.prompt(); return; } if (input.startsWith("/bookmark ")) { const bookmark = input.slice(10).trim(); await workspaceManager.addBookmark(repo, bookmark); log.success(`Bookmark added: ${bookmark}`); rl.prompt(); return; } // Background process commands if (input.startsWith("/bg ") || input.startsWith("/background ")) { const bgCmd = input.startsWith("/bg ") ? input.slice(4).trim() : input.slice(12).trim(); if (!bgCmd) { log.error("Usage: /bg <command> or /background <command>"); rl.prompt(); return; } try { const [command, ...args] = bgCmd.split(" "); const result = await backgroundExecutor.startBackground(command, args, repo); log.info(result.message); } catch (error) { log.error("Failed to start background process:", error.message); } rl.prompt(); return; } if (cmd === "/bg-list" || cmd === "/background-list") { const processes = backgroundExecutor.listProcesses(); log.raw(""); log.raw(log.colors.bright("šŸ”„ Background Processes:")); if (processes.length === 0) { log.raw(log.colors.dim(" No background processes")); } else { processes.forEach(proc => { const statusColor = proc.status === 'running' ? 'green' : proc.status === 'completed' ? 'cyan' : 'red'; const duration = Date.now() - proc.startTime; const durationStr = `${(duration / 1000).toFixed(1)}s`; log.raw(` ${log.colors[statusColor](proc.status.padEnd(10))} ${log.colors.cyan(proc.id)} - ${proc.command} ${proc.args.join(' ')} (${durationStr})`); if (proc.output.length > 0) { const lastLine = proc.output[proc.output.length - 1]; log.raw(` ${log.colors.dim("Last:")} ${log.colors.dim(lastLine.slice(0, 80))}`); } }); const stats = backgroundExecutor.getStats(); log.raw(""); log.raw(` ${log.colors.dim("Total:")} ${stats.total}, ${log.colors.dim("Running:")} ${stats.running}, ${log.colors.dim("Completed:")} ${stats.completed}, ${log.colors.dim("Failed:")} ${stats.failed}`); } log.raw(""); rl.prompt(); return; } if (input.startsWith("/bg-kill ") || input.startsWith("/background-kill ")) { const processId = input.includes("/bg-kill ") ? input.slice(9).trim() : input.slice(17).trim(); if (!processId) { log.error("Usage: /bg-kill <process-id>"); rl.prompt(); return; } const killed = await backgroundExecutor.killProcess(processId); if (killed) { log.success(`Killed background process: ${processId}`); } else { log.error(`Failed to kill process: ${processId}`); } rl.prompt(); return; } if (cmd === "/bg-kill-all" || cmd === "/background-kill-all") { const killedCount = await backgroundExecutor.killAllProcesses(); log.info(`Killed ${killedCount} background processes`); rl.prompt(); return; } if (input.startsWith("/bg-output ") || input.startsWith("/background-output ")) { const processId = input.includes("/bg-output ") ? input.slice(11).trim() : input.slice(19).trim(); if (!processId) { log.error("Usage: /bg-output <process-id>"); rl.prompt(); return; } const output = backgroundExecutor.getOutput(processId); if (!output) { log.error(`Process not found: ${processId}`); } else { log.raw(""); log.raw(log.colors.bright(`šŸ“„ Output for ${processId}:`)); if (output.stdout.length > 0) { log.raw(log.colors.dim(" STDOUT:")); output.stdout.slice(-20).forEach(line => { log.raw(` ${line}`); }); } if (output.stderr.length > 0) { log.raw(log.colors.dim(" STDERR:")); output.stderr.slice(-20).forEach(line => { log.raw(` ${log.colors.red(line)}`); }); } if (output.stdout.length === 0 && output.stderr.length === 0) { log.raw(log.colors.dim(" No output yet")); } } log.raw(""); rl.prompt(); return; } // Permissions management if (cmd === "/permissions") { const permissions = permissionManager.getPermissions(); const stats = permissionManager.getStats(); log.raw(""); log.raw(log.colors.bright("šŸ›”ļø Tool Permissions:")); log.raw(` ${log.colors.dim("Total:")} ${stats.totalPermissions}, ${log.colors.dim("Allowed:")} ${stats.allowedTools}, ${log.colors.dim("Denied:")} ${stats.deniedTools}`); log.raw(` ${log.colors.dim("Rules:")} ${stats.rulesCount}`); if (permissions.length > 0) { log.raw(""); log.raw(log.colors.dim(" Recent Permissions:")); permissions.slice(-10).forEach(perm => { const statusIcon = perm.allowed ? log.colors.green("āœ…") : log.colors.red("āŒ"); const scopeColor = perm.scope === 'global' ? 'yellow' : perm.scope === 'project' ? 'cyan' : 'dim'; log.raw(` ${statusIcon} ${log.colors.bright(perm.name)} (${log.colors[scopeColor](perm.scope)})`); if (perm.restrictions?.requireConfirmation) { log.raw(` ${log.colors.dim("āš ļø Requires confirmation")}`); } }); } log.raw(""); log.raw(log.colors.dim("Commands:")); log.raw(` ${log.colors.cyan("/permissions allow <tool>")} - Allow a tool`); log.raw(` ${log.colors.cyan("/permissions deny <tool>")} - Deny a tool`); log.raw(` ${log.colors.cyan("/permissions rules")} - Show permission rules`); log.raw(` ${log.colors.cyan("/permissions export")} - Export permissions`); log.raw(""); rl.prompt(); return; } if (input.startsWith("/permissions ")) { const parts = input.slice(13).trim().split(" "); const subCmd = parts[0]; const toolName = parts[1]; switch (subCmd) { case "allow": if (!toolName) { log.error("Usage: /permissions allow <tool-name>"); } else { await permissionManager.setPermission(toolName, true); log.success(`Tool ${toolName} is now allowed`); } break; case "deny": if (!toolName) { log.error("Usage: /permissions deny <tool-name>"); } else { await permissionManager.setPermission(toolName, false); log.success(`Tool ${toolName} is now denied`); } break; case "rules": const rules = permissionManager.getRules(); log.raw(""); log.raw(log.colors.bright("šŸ“‹ Permission Rules:")); rules.slice(0, 10).forEach((rule, i) => { const actionColor = rule.action === 'allow' ? 'green' : rule.action === 'deny' ? 'red' : 'yellow'; log.raw(` ${i + 1}. ${log.colors[actionColor](rule.action.toUpperCase())} ${rule.pattern} (priority: ${rule.priority})`); log.raw(` ${log.colors.dim(rule.description)}`); }); log.raw(""); break; case "export": try { const exported = permissionManager.exportPermissions(); log.raw(""); log.raw(log.colors.bright("šŸ“¤ Exported Permissions:")); log.raw(exported); log.raw("");