scai
Version:
> AI-powered CLI tool for commit messages **and** pull request reviews ā using local models.
256 lines (254 loc) ⢠9.16 kB
JavaScript
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const { version } = require('../package.json');
import { Command } from "commander";
import { Config } from './config.js';
import { suggestCommitMessage } from "./commands/CommitSuggesterCmd.js";
import { handleRefactor } from "./commands/RefactorCmd.js";
import { generateTests } from "./commands/TestGenCmd.js";
import { bootstrap } from './modelSetup.js';
import { summarizeFile } from "./commands/SummaryCmd.js";
import { handleStandaloneChangelogUpdate } from './commands/ChangeLogUpdateCmd.js';
import { runModulePipelineFromCLI } from './commands/ModulePipelineCmd.js';
import { runIndexCommand } from './commands/IndexCmd.js';
import { resetDatabase } from './commands/ResetDbCmd.js';
import { runFindCommand } from './commands/FindCmd.js';
import { startDaemon } from './commands/DaemonCmd.js';
import { runStopDaemonCommand } from "./commands/StopDaemonCmd.js";
import { runAskCommand } from './commands/AskCmd.js';
import { runBackupCommand } from './commands/BackupCmd.js';
import { runMigrateCommand } from "./commands/MigrateCmd.js";
import { runInspectCommand } from "./commands/InspectCmd.js";
import { reviewPullRequestCmd } from "./commands/ReviewCmd.js";
import { promptForToken } from "./github/token.js";
import { validateGitHubTokenAgainstRepo } from "./github/githubAuthCheck.js";
import { checkGit } from "./commands/GitCmd.js";
import { runInteractiveSwitch } from "./commands/SwitchCmd.js";
import { execSync } from "child_process";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
// šļø CLI Setup
const cmd = new Command('scai')
.version(version)
.option('--model <model>', 'Set the model to use (e.g., codellama:34b)')
.option('--lang <lang>', 'Set the target language (ts, java, rust)');
// š§ Main command group
cmd
.command('init')
.description('Initialize the model and download required models')
.action(async () => {
await bootstrap();
console.log('ā
Model initialization completed!');
});
// š§ Group: Git-related 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 (not just for the current user)', false)
.action(async (cmd) => {
const showAll = cmd.all;
await reviewPullRequestCmd('main', showAll);
});
git
.command('commit')
.description('Suggest a commit message from staged changes and optionally commit')
.option('-l, --changelog', 'Generate and optionally stage a changelog entry')
.action((options) => suggestCommitMessage(options));
git
.command('check')
.description('Check Git working directory and branch status')
.action(() => {
checkGit();
});
// Add auth-related commands
const auth = cmd.command('auth').description('GitHub authentication commands');
auth
.command('check')
.description('Check if GitHub authentication is set up and valid')
.action(async () => {
try {
const token = Config.getGitHubToken();
if (!token) {
console.log('ā GitHub authentication not found. Please set your token.');
return;
}
const result = await validateGitHubTokenAgainstRepo();
console.log(result);
}
catch (err) {
console.error(typeof err === 'string' ? err : err.message);
}
});
auth
.command('reset')
.description('Reset GitHub authentication credentials')
.action(() => {
Config.setGitHubToken('');
console.log('š GitHub authentication has been reset.');
const token = Config.getGitHubToken();
console.log(token ? 'ā Token still exists in the configuration.' : 'ā
Token successfully removed.');
});
auth
.command('set')
.description('Set your GitHub Personal Access Token')
.action(async () => {
const token = await promptForToken();
Config.setGitHubToken(token.trim());
console.log('š GitHub token set successfully.');
});
// š ļø Group: `gen` commands for content generation
const gen = cmd.command('gen').description('Generate code-related output');
gen
.command('comm <file>')
.description('Write comments for the given file')
.option('-a, --apply', 'Apply the refactored version to the original file')
.action((file, options) => handleRefactor(file, options));
gen
.command('changelog')
.description('Update or create the CHANGELOG.md based on current Git diff')
.action(async () => {
await handleStandaloneChangelogUpdate();
});
gen
.command('summ [file]')
.description('Print a summary of the given file to the terminal')
.action((file) => summarizeFile(file));
gen
.command('tests <file>')
.description('Generate a Jest test file for the specified JS/TS module')
.action((file) => generateTests(file));
// āļø Group: Configuration settings
const config = cmd.command('config').description('Manage SCAI configuration');
config
.command('set-model <model>')
.description('Set the model to use')
.action((model) => {
Config.setModel(model);
Config.show();
});
config
.command('set-lang <lang>')
.description('Set the programming language')
.action((lang) => {
Config.setLanguage(lang);
Config.show();
});
config
.command('show')
.option('--raw', 'Show full raw config')
.description('Display current configuration')
.action((options) => {
if (options.raw) {
console.log(JSON.stringify(Config.getRaw(), null, 2));
}
else {
Config.show();
}
});
const index = cmd.command('index').description('index operations');
index
.command('start')
.description('Index supported files in the configured index directory')
.action(runIndexCommand);
index
.command('set <dir>')
.description('Set and activate index directory')
.action((dir) => {
Config.setIndexDir(dir);
Config.show();
});
index
.command('list')
.description('List all indexed repositories')
.action(() => {
Config.printAllRepos(); // š simple and clean
});
index
.command('switch')
.description('Switch active repository (by interactive list only)')
.action(() => {
runInteractiveSwitch();
});
// This will help resolve the current directory in an ES Module
cmd
.command('check-db')
.description('Run the dbcheck script to check the database status')
.action(() => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Go up two levels from dist/commands ā dist ā then into dist/scripts/dbcheck.js
const scriptPath = resolve(__dirname, '..', 'dist/scripts', 'dbcheck.js');
console.log(`š Running database check script: ${scriptPath}`);
try {
execSync(`node "${scriptPath}"`, { stdio: 'inherit' });
}
catch (err) {
console.error('ā Error running dbcheck script:', err instanceof Error ? err.message : err);
}
});
cmd
.command('backup')
.description('Backup the current .scai folder')
.action(runBackupCommand);
cmd
.command('find <query>')
.description('Search indexed files by keyword')
.action(runFindCommand);
cmd
.command('ask [question...]')
.description('Ask a question based on indexed files')
.action((questionParts) => {
const fullQuery = questionParts?.join(' ');
runAskCommand(fullQuery);
});
cmd
.command('daemon')
.description('Run background summarization of indexed files')
.action(async () => {
await startDaemon();
});
cmd
.command('stop-daemon')
.description('Stop the background summarizer daemon')
.action(runStopDaemonCommand);
cmd
.command('migrate')
.description('Run DB migration scripts')
.action(runMigrateCommand);
cmd
.command('inspect')
.argument('<filepath>', 'Path to the file to inspect')
.description('Inspect a specific file and print its indexed summary and functions')
.action(async (filepath) => {
await runInspectCommand(filepath);
});
cmd
.command('reset-db')
.description('Delete and reset the SQLite database')
.action(() => resetDatabase());
cmd
.command('pipe')
.description('Run a module pipeline on a given file')
.argument('<file>', 'Target file')
.option('-m, --modules <modules>', 'Comma-separated list of modules to run (e.g., comments,cleanup,summary)')
.action((file, options) => {
if (!options.modules) {
console.error('ā You must specify modules with -m or --modules');
process.exit(1);
}
runModulePipelineFromCLI(file, options);
});
cmd.addHelpText('after', `
šØ Alpha Features:
- The "index", "daemon", "stop-daemon", "reset-db" commands are considered alpha features.
- These commands are in active development and may change in the future.
š” Use with caution and expect possible changes or instability.
`);
cmd.parse(process.argv);
const opts = cmd.opts();
if (opts.model)
Config.setModel(opts.model);
if (opts.lang)
Config.setLanguage(opts.lang);