UNPKG

scai

Version:

> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.

255 lines (254 loc) 11 kB
// commands/factory.ts import { Command } from 'commander'; import { bootstrap } from '../modelSetup.js'; import { runAskCommand } from './AskCmd.js'; import { runIndexCommand } from './IndexCmd.js'; import { resetDatabase } from './ResetDbCmd.js'; import { suggestCommitMessage } from './CommitSuggesterCmd.js'; import { reviewPullRequestCmd } from './ReviewCmd.js'; import { checkGit } from './GitCmd.js'; import { promptForToken } from '../github/token.js'; import { validateGitHubTokenAgainstRepo } from '../github/githubAuthCheck.js'; import { runWorkflowCommand } from './WorkflowCmd.js'; import { handleStandaloneChangelogUpdate } from './ChangeLogUpdateCmd.js'; import { runInteractiveSwitch } from './SwitchCmd.js'; import { runInteractiveDelete } from './DeleteIndex.js'; import { startDaemon, stopDaemon, restartDaemon, statusDaemon, unlockConfig, showLogs } from './DaemonCmd.js'; import { Config } from '../config.js'; import { fileURLToPath } from 'url'; import { dirname, resolve } from 'path'; import { execSync } from 'child_process'; import { runInspectCommand } from './InspectCmd.js'; import { runFindCommand } from './FindCmd.js'; import { runBackupCommand } from './BackupCmd.js'; import { updateContext } from '../context.js'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); const { version } = require('../../package.json'); export async function withContext(action) { const ok = await updateContext(); if (!ok) return; // or process.exit(1) await action(); } export function createProgram() { const cmd = new Command('scai'); cmd.version(version, '-v, --version', 'output the current version'); // ---------------- Init commands ---------------- cmd .command('init') .description('Initialize the model and download required models') .action(async () => { await bootstrap(); console.log('✅ Model initialization completed!'); }); // ---------------- Git commands ---------------- const git = cmd.command('git').description('Git utilities'); git .command('review') .description('Review an open pull request using AI') .option('-a, --all', 'Show all PRs requiring a review', false) .action(async (opts) => { await withContext(async () => { await reviewPullRequestCmd('main', opts.all); }); }); git .command('commit') .description('Suggest a commit message from staged changes') .option('-l, --changelog', 'Generate and optionally stage a changelog entry') .action(async (opts) => { await withContext(async () => { await suggestCommitMessage(opts); }); }); git .command('check') .description('Check Git working directory and branch status') .action(async () => { await withContext(async () => { checkGit(); }); }); // ---------------- Auth commands ---------------- const auth = cmd.command('auth').description('GitHub authentication commands'); auth .command('check') .description('Check if GitHub authentication is valid') .action(async () => { await withContext(async () => { try { const token = Config.getGitHubToken(); if (!token) return console.log('❌ GitHub token not found.'); const result = await validateGitHubTokenAgainstRepo(); console.log(result); } catch (err) { console.error(err instanceof Error ? err.message : err); } }); }); auth .command('reset') .description('Reset GitHub authentication credentials') .action(async () => { await withContext(async () => { Config.setGitHubToken(''); console.log('✅ GitHub token reset.'); }); }); auth .command('set') .description('Set GitHub Personal Access Token') .action(async () => { await withContext(async () => { const token = (await promptForToken()).trim(); Config.setGitHubToken(token); console.log('🔑 GitHub token set.'); }); }); // ---------------- Workflow ---------------- const workflow = cmd.command('workflow').description('Run or manage module pipelines'); workflow .command('run <goals...>') .description('Run one or more modules as a pipeline') .option('-f, --file <filepath>', 'File to process (omit to read from stdin)') .action(async (goals, options) => { await withContext(async () => { await runWorkflowCommand(goals, options); }); }) .on('--help', () => { console.log('\nExamples:'); console.log(' $ scai workflow run comments tests -f path/to/myfile.ts'); console.log(' $ cat file.ts | scai workflow run comments tests > file.test.ts\n'); console.log('Available modules: summary, comments, tests'); }); // ---------------- Gen / Changelog ---------------- const gen = cmd.command('gen').description('Generate code-related output'); gen .command('changelog') .description('Update or create CHANGELOG.md') .action(async () => { await withContext(async () => { await handleStandaloneChangelogUpdate(); }); }); // ---------------- Config ---------------- const config = cmd.command('config').description('Manage SCAI configuration'); config .command('set-model <model>') .description('Set the model to use') .option('-g, --global', 'Set globally instead of repo') .action(async (model, opts) => { await withContext(async () => { Config.setModel(model, opts.global ? 'global' : 'repo'); Config.show(); }); }); config .command('set-lang <lang>') .description('Set programming language') .action(async (lang) => { await withContext(async () => { Config.setLanguage(lang); Config.show(); }); }); config .command('show') .option('--raw', 'Show raw config') .description('Display current configuration') .action(async (opts) => { await withContext(async () => { if (opts.raw) console.log(JSON.stringify(Config.getRaw(), null, 2)); else Config.show(); }); }); // 💤 Set daemon sleep intervals config .command('set-daemon') .description('Set daemon sleep intervals (ms between runs)') .option('--sleep <ms>', 'Sleep time in milliseconds between batches') .option('--idle <ms>', 'Sleep time in milliseconds when idle') .action(async (options) => { await withContext(async () => { const updates = {}; if (options.sleep) updates.sleepMs = parseInt(options.sleep, 10); if (options.idle) updates.idleSleepMs = parseInt(options.idle, 10); if (!Object.keys(updates).length) { console.log('⚠️ No options provided. Use --sleep or --idle.'); return; } Config.setDaemonConfig(updates); const current = Config.getDaemonConfig(); console.log(`🕒 Updated daemon settings:\n sleepMs=${current.sleepMs}ms\n idleSleepMs=${current.idleSleepMs}ms`); }); }); // ---------------- Index ---------------- const index = cmd.command('index').description('Index operations'); index .command('start') .description('Index supported files') .action(async () => await withContext(runIndexCommand)); index .command('set [dir]') .description('Set index directory') .action(async (dir = process.cwd()) => { await Config.setIndexDir(dir); Config.show(); }); index .command('list') .description('List all indexed repositories') .action(async () => await withContext(async () => { await Config.printAllRepos(); })); index .command('switch') .description('Switch active repository interactively') .action(async () => await withContext(runInteractiveSwitch)); index .command('delete') .description('Delete repository interactively') .action(async () => await withContext(runInteractiveDelete)); // ---------------- DB ---------------- const db = cmd.command('db').description('Database operations'); db .command('check') .description('Run dbcheck script') .action(async () => { await withContext(async () => { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const scriptPath = resolve(__dirname, '../..', 'dist/scripts', 'dbcheck.js'); try { execSync(`node "${scriptPath}"`, { stdio: 'inherit' }); } catch (err) { console.error('❌ DB check failed:', err instanceof Error ? err.message : err); } }); }); db .command('reset') .description('Reset SQLite database') .action(async () => await withContext(resetDatabase)); db .command('inspect <filepath>') .description('Inspect a file in the index') .action(async (filepath) => await withContext(() => runInspectCommand(filepath))); // ---------------- Daemon ---------------- const daemon = cmd.command('daemon').description('Daemon management'); daemon.command('start').description('Start daemon').action(async () => withContext(startDaemon)); daemon.command('stop').description('Stop daemon').action(async () => withContext(stopDaemon)); daemon.command('restart').description('Restart daemon').action(async () => withContext(restartDaemon)); daemon.command('status').description('Daemon status').action(async () => withContext(statusDaemon)); daemon.command('unlock').description('Force unlock').action(async () => withContext(unlockConfig)); daemon.command('logs').option('-n, --lines <n>', 'Last N log lines', '20') .description('Show daemon logs') .action(async (opts) => withContext(() => showLogs(parseInt(opts.lines, 10)))); // ---------------- Backup / Find / Ask ---------------- cmd.command('backup').description('Backup .scai folder').action(async () => await withContext(runBackupCommand)); cmd.command('find <query>').description('Search indexed files').action(async (q) => await withContext(() => runFindCommand(q))); cmd.command('ask [question...]').description('Ask a question').action(async (parts) => { await withContext(async () => { await runAskCommand(parts?.join(' ')); }); }); return cmd; }