diff --git a/src/commands/commit.ts b/src/commands/commit.ts new file mode 100644 index 0000000..1ab33e0 --- /dev/null +++ b/src/commands/commit.ts @@ -0,0 +1,106 @@ +import { Command } from 'commander' +import { spawn } from 'node:child_process' +import { access } from 'node:fs/promises' +import { join } from 'node:path' +import { homedir } from 'node:os' +import { colors, logInfo, logError } from '../utils/output.js' + +const BITCH_DATA_DIR = join(homedir(), '.local', 'share', 'bitch') +const COMMITS_SCRIPT_PATH = join(BITCH_DATA_DIR, 'commits-daemon.sh') +const OLD_COMMITS_PATH = join(homedir(), '.local', 'bin', 'commits') + +/** + * Check if commits script exists at a path + */ +async function scriptExists(path: string): Promise { + try { + await access(path) + return true + } catch { + return false + } +} + +/** + * Find the commits script location + */ +async function findCommitsScript(): Promise { + // Check bitch's copy first + if (await scriptExists(COMMITS_SCRIPT_PATH)) { + return COMMITS_SCRIPT_PATH + } + // Fall back to standalone + if (await scriptExists(OLD_COMMITS_PATH)) { + return OLD_COMMITS_PATH + } + return null +} + +/** + * Run the commits script with the 'commit' subcommand + */ +function runCommit(scriptPath: string, args: string[]): void { + const child = spawn('bash', [scriptPath, 'commit', ...args], { + stdio: 'inherit', + env: process.env, + }) + + child.on('close', (code) => { + process.exit(code || 0) + }) + + child.on('error', (err) => { + logError(`Failed to run commit: ${err.message}`) + process.exit(1) + }) +} + +export function createCommitCommand(): Command { + const cmd = new Command('commit') + .description('One-shot AI-generated commit (uses daemon if running, otherwise standalone)') + .option('-A, --all', 'Commit all configured repos (default: current directory only)') + .option('-p, --push', 'Push after committing (default: true)') + .option('--no-push', 'Commit without pushing') + .option('-n, --dry-run', 'Preview what would be committed without committing') + .allowUnknownOption(true) + .action(async (options, command) => { + // Build args for the Python CLI + const args: string[] = [] + + if (options.all) { + args.push('--all') + } + if (options.push === false) { + args.push('--no-push') + } + if (options.dryRun) { + args.push('--dry-run') + } + + // Add any additional unknown args + const extraArgs = command.args.filter((arg: string) => !['commit'].includes(arg)) + args.push(...extraArgs) + + // Find the commits script + const scriptPath = await findCommitsScript() + + if (!scriptPath) { + logError('Commits script not found.') + console.log() + console.log('To install:') + console.log('1. Install auto-commit-service:') + console.log(colors.dim(' cd ~/Code/@applications/@ml/auto-commit-service')) + console.log(colors.dim(' pip install -e .')) + console.log() + console.log('2. Copy the commits CLI script to ~/.local/bin/commits') + console.log() + console.log('3. Run:') + console.log(colors.cyan(' bitch commits install')) + process.exit(1) + } + + runCommit(scriptPath, args) + }) + + return cmd +}