/** * Copilot CLI Command * * Handles `ccs copilot ` commands. */ import { startAuthFlow, getCopilotStatus, getCopilotUsage, startDaemon, stopDaemon, getAvailableModels, isCopilotApiInstalled, normalizeCopilotConfigWithWarnings, } from '../copilot'; import type { CopilotModel } from '../copilot'; import { loadOrCreateUnifiedConfig, mutateUnifiedConfig } from '../config/unified-config-loader'; import { DEFAULT_COPILOT_CONFIG } from '../config/unified-config-types'; import { ok, fail, info, color, warn } from '../utils/ui'; import { normalizeCopilotSubcommand } from '../copilot/constants'; /** * Handle copilot subcommand. */ export async function handleCopilotCommand(args: string[]): Promise { const subcommand = normalizeCopilotSubcommand(args[0]); switch (subcommand) { case 'auth': return handleAuth(); case 'status': return handleStatus(); case 'models': return handleModels(); case 'usage': return handleUsage(); case 'start': return handleStart(); case 'stop': return handleStop(); case 'enable': return handleEnable(); case 'disable': return handleDisable(); case undefined: case 'help': case '--help': case '-h': return handleHelp(); default: console.error(fail(`Unknown subcommand: ${subcommand}`)); console.error(''); handleHelp(); return 1; } } function loadCopilotConfigWithWarnings() { const config = loadOrCreateUnifiedConfig(); return normalizeCopilotConfigWithWarnings(config.copilot ?? DEFAULT_COPILOT_CONFIG); } function printCopilotWarnings(messages: string[]): void { if (messages.length === 0) return; messages.forEach((message) => console.log(warn(message))); console.log(warn('Run `ccs config` and save the Copilot section to persist these replacements.')); console.log(''); } /** * Show help for copilot commands. */ function handleHelp(): number { console.log('GitHub Copilot Integration'); console.log(''); console.log('Usage: ccs copilot '); console.log(''); console.log('Subcommands:'); console.log(' auth Start GitHub OAuth authentication'); console.log(' status Show authentication and daemon status'); console.log(' models List available models'); console.log(' usage Show Copilot quota usage'); console.log(' start Start copilot-api daemon'); console.log(' stop Stop copilot-api daemon'); console.log(' enable Enable copilot integration'); console.log(' disable Disable copilot integration'); console.log(' help Show this help message'); console.log(''); console.log('Quick start:'); console.log(' 1. ccs copilot auth # Authenticate with GitHub'); console.log(' 2. ccs copilot enable # Enable integration'); console.log(' 3. ccs copilot start # Start daemon'); console.log(' 4. ccs copilot usage # Check quota usage'); console.log(''); console.log('Flag aliases:'); console.log( ' ccs copilot --auth | --status | --models | --usage | --start | --stop | --enable | --disable' ); console.log(''); console.log('Or use the web UI: ccs config → Copilot tab'); console.log(''); return 0; } /** * Handle auth subcommand. */ async function handleAuth(): Promise { if (!isCopilotApiInstalled()) { console.error(fail('copilot-api is not installed.')); console.error(''); console.error('Install with: npm install -g copilot-api'); return 1; } const result = await startAuthFlow(); if (result.success) { console.log(''); console.log(ok('Authentication successful!')); console.log(''); console.log('Next steps:'); console.log(' 1. Enable copilot: ccs copilot enable'); console.log(' 2. Start daemon: ccs copilot start'); console.log(' (fallback: npx copilot-api start)'); console.log(' 3. Use copilot: ccs copilot'); return 0; } else { console.error(''); console.error(fail(result.error || 'Authentication failed')); return 1; } } /** * Handle status subcommand. */ async function handleStatus(): Promise { const { config: copilotConfig, warnings } = loadCopilotConfigWithWarnings(); printCopilotWarnings(warnings.map((warning) => warning.message)); const status = await getCopilotStatus(copilotConfig); console.log('GitHub Copilot Status'); console.log('─────────────────────'); console.log(''); // Enabled status const enabledIcon = copilotConfig.enabled ? color('[OK]', 'success') : color('[X]', 'error'); const enabledText = copilotConfig.enabled ? 'Enabled' : 'Disabled'; console.log(`Integration: ${enabledIcon} ${enabledText}`); // Auth status const authIcon = status.auth.authenticated ? color('[OK]', 'success') : color('[X]', 'error'); const authText = status.auth.authenticated ? 'Authenticated' : 'Not authenticated'; console.log(`Authentication: ${authIcon} ${authText}`); // Daemon status const daemonIcon = status.daemon.running ? color('[OK]', 'success') : color('[X]', 'error'); const daemonText = status.daemon.running ? 'Running' : 'Not running'; console.log(`Daemon: ${daemonIcon} ${daemonText}`); console.log(''); console.log('Configuration:'); console.log(` Port: ${copilotConfig.port}`); console.log(` Model: ${copilotConfig.model}`); console.log(` Account Type: ${copilotConfig.account_type}`); console.log(` Auto-start: ${copilotConfig.auto_start ? 'Yes' : 'No'}`); if (copilotConfig.rate_limit !== null) { console.log(` Rate Limit: ${copilotConfig.rate_limit}s`); } console.log(''); // Show next steps if not fully configured if (!copilotConfig.enabled || !status.auth.authenticated || !status.daemon.running) { console.log('Next steps:'); if (!copilotConfig.enabled) { console.log(' - Enable: ccs copilot enable'); } if (!status.auth.authenticated) { console.log(' - Auth: ccs copilot auth'); } if (!status.daemon.running) { console.log(' - Start: ccs copilot start'); } } return 0; } /** * Handle models subcommand. */ async function handleModels(): Promise { const { config: copilotConfig, warnings } = loadCopilotConfigWithWarnings(); printCopilotWarnings(warnings.map((warning) => warning.message)); console.log('Available Copilot Models'); console.log('────────────────────────'); console.log(''); const models = await getAvailableModels(copilotConfig.port); for (const model of models) { const current = model.id === copilotConfig.model ? ' [CURRENT]' : ''; const defaultMark = model.isDefault ? ' (default)' : ''; console.log(` ${model.id}${current}${defaultMark}`); console.log(` Provider: ${model.provider}`); const limits = formatModelLimits(model); if (limits) { console.log(` Limits: ${limits}`); } } console.log(''); if (models.some((model) => formatModelLimits(model))) { console.log('Live limits above come from GitHub Copilot model metadata.'); } else { console.log('Live Copilot limits were unavailable. Start the daemon and rerun this command.'); } console.log( 'CCS can switch Copilot models, but it cannot raise GitHub Copilot prompt/context caps.' ); console.log(''); console.log('To change model: ccs config (Copilot section)'); return 0; } function formatCompactTokens(value: number): string { if (value >= 1_000_000) { const millions = value / 1_000_000; return millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`; } if (value >= 1_000) { const thousands = value / 1_000; return thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`; } return `${value}`; } function formatModelLimits(model: CopilotModel): string | null { if (!model.limits) return null; const parts: string[] = []; if (model.limits.maxPromptTokens) { parts.push(`prompt ${formatCompactTokens(model.limits.maxPromptTokens)}`); } if (model.limits.maxContextWindowTokens) { parts.push(`context ${formatCompactTokens(model.limits.maxContextWindowTokens)}`); } if (model.limits.maxOutputTokens) { parts.push(`output ${formatCompactTokens(model.limits.maxOutputTokens)}`); } return parts.length > 0 ? parts.join(' | ') : null; } function formatQuotaLine( label: string, snapshot: { entitlement: number; used: number; percentUsed: number; percentRemaining: number; unlimited: boolean; } ): string { const quotaText = snapshot.unlimited ? 'Unlimited' : `${snapshot.used}/${snapshot.entitlement} used`; return `${label.padEnd(20)} ${quotaText} (${snapshot.percentUsed.toFixed(1)}% used, ${snapshot.percentRemaining.toFixed(1)}% remaining)`; } function formatResetDate(resetDate: string | null): string { if (!resetDate) return 'unknown'; const date = new Date(resetDate); if (Number.isNaN(date.getTime())) return resetDate; return date.toLocaleString(); } /** * Handle usage subcommand. */ async function handleUsage(): Promise { const { config: copilotConfig, warnings } = loadCopilotConfigWithWarnings(); printCopilotWarnings(warnings.map((warning) => warning.message)); const status = await getCopilotStatus(copilotConfig); if (!status.daemon.running) { console.error(fail('copilot-api daemon is not running.')); console.error(''); console.error('Start daemon first: ccs copilot start'); return 1; } const usage = await getCopilotUsage(copilotConfig.port); if (!usage) { console.error(fail('Failed to fetch Copilot usage.')); console.error(''); console.error('Try restarting daemon: ccs copilot stop && ccs copilot start'); return 1; } console.log('GitHub Copilot Usage'); console.log('────────────────────'); console.log(''); console.log(`Plan: ${usage.plan || 'unknown'}`); console.log(`Quota Reset: ${formatResetDate(usage.quotaResetDate)}`); console.log(''); console.log('Quotas:'); console.log(` ${formatQuotaLine('Premium Interactions', usage.quotas.premiumInteractions)}`); console.log(` ${formatQuotaLine('Chat', usage.quotas.chat)}`); console.log(` ${formatQuotaLine('Completions', usage.quotas.completions)}`); console.log(''); return 0; } /** * Handle start subcommand. */ async function handleStart(): Promise { const { config: copilotConfig, warnings } = loadCopilotConfigWithWarnings(); printCopilotWarnings(warnings.map((warning) => warning.message)); console.log(info(`Starting copilot-api daemon on port ${copilotConfig.port}...`)); const result = await startDaemon(copilotConfig); if (result.success) { console.log(ok(`Daemon started (PID: ${result.pid})`)); return 0; } else { console.error(fail(result.error || 'Failed to start daemon')); return 1; } } /** * Handle stop subcommand. */ async function handleStop(): Promise { console.log(info('Stopping copilot-api daemon...')); const result = await stopDaemon(); if (result.success) { console.log(ok('Daemon stopped')); return 0; } else { console.error(fail(result.error || 'Failed to stop daemon')); return 1; } } /** * Handle enable subcommand. */ async function handleEnable(): Promise { mutateUnifiedConfig((config) => { if (!config.copilot) { config.copilot = { ...DEFAULT_COPILOT_CONFIG }; } config.copilot.enabled = true; }); console.log(ok('Copilot integration enabled')); console.log(''); console.log('Next steps:'); console.log(' 1. Authenticate: ccs copilot auth'); console.log(' 2. Start daemon: ccs copilot start'); console.log(' 3. Use: ccs copilot'); return 0; } /** * Handle disable subcommand. */ async function handleDisable(): Promise { mutateUnifiedConfig((config) => { if (config.copilot) { config.copilot.enabled = false; } }); console.log(ok('Copilot integration disabled')); return 0; }