diff --git a/src/ccs.ts b/src/ccs.ts index 3e1c22ef..9dd50056 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -338,6 +338,14 @@ async function main(): Promise { return; } + // Special case: copilot command (GitHub Copilot integration) + if (firstArg === 'copilot' && args.length > 1) { + // `ccs copilot ` - route to copilot command handler + const { handleCopilotCommand } = await import('./commands/copilot-command'); + const exitCode = await handleCopilotCommand(args.slice(1)); + process.exit(exitCode); + } + // Special case: headless delegation (-p flag) if (args.includes('-p') || args.includes('--prompt')) { const { DelegationHandler } = await import('./delegation/delegation-handler'); @@ -384,6 +392,16 @@ async function main(): Promise { const provider = profileInfo.provider || (profileInfo.name as CLIProxyProvider); const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath }); + } else if (profileInfo.type === 'copilot') { + // COPILOT FLOW: GitHub Copilot subscription via copilot-api proxy + const { executeCopilotProfile } = await import('./copilot'); + const copilotConfig = profileInfo.copilotConfig; + if (!copilotConfig) { + console.error('[X] Copilot configuration not found'); + process.exit(1); + } + const exitCode = await executeCopilotProfile(copilotConfig, remainingArgs); + process.exit(exitCode); } else if (profileInfo.type === 'settings') { // Settings-based profiles (glm, glmt, kimi) are third-party providers // WebSearch is server-side tool - third-party providers have no access diff --git a/src/commands/copilot-command.ts b/src/commands/copilot-command.ts new file mode 100644 index 00000000..5606cb82 --- /dev/null +++ b/src/commands/copilot-command.ts @@ -0,0 +1,261 @@ +/** + * Copilot CLI Command + * + * Handles `ccs copilot ` commands. + */ + +import { + startAuthFlow, + getCopilotStatus, + startDaemon, + stopDaemon, + getAvailableModels, + isCopilotApiInstalled, +} from '../copilot'; +import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../config/unified-config-loader'; +import { DEFAULT_COPILOT_CONFIG } from '../config/unified-config-types'; + +/** + * Handle copilot subcommand. + */ +export async function handleCopilotCommand(args: string[]): Promise { + const subcommand = args[0]; + + switch (subcommand) { + case 'auth': + return handleAuth(); + case 'status': + return handleStatus(); + case 'models': + return handleModels(); + 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(`[X] Unknown subcommand: ${subcommand}`); + console.error(''); + return handleHelp(); + } +} + +/** + * 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(' 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('After setup, use: ccs copilot (as profile name)'); + console.log(''); + return 0; +} + +/** + * Handle auth subcommand. + */ +async function handleAuth(): Promise { + if (!isCopilotApiInstalled()) { + console.error('[X] 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: npx copilot-api start'); + console.log(' 3. Use copilot: ccs copilot'); + return 0; + } else { + console.error(''); + console.error(`[X] ${result.error}`); + return 1; + } +} + +/** + * Handle status subcommand. + */ +async function handleStatus(): Promise { + const config = loadOrCreateUnifiedConfig(); + const copilotConfig = config.copilot ?? DEFAULT_COPILOT_CONFIG; + + const status = await getCopilotStatus(copilotConfig); + + console.log('GitHub Copilot Status'); + console.log('─────────────────────'); + console.log(''); + + // Enabled status + const enabledIcon = copilotConfig.enabled ? '[OK]' : '[X]'; + const enabledText = copilotConfig.enabled ? 'Enabled' : 'Disabled'; + console.log(`Integration: ${enabledIcon} ${enabledText}`); + + // Auth status + const authIcon = status.auth.authenticated ? '[OK]' : '[X]'; + const authText = status.auth.authenticated ? 'Authenticated' : 'Not authenticated'; + console.log(`Authentication: ${authIcon} ${authText}`); + + // Daemon status + const daemonIcon = status.daemon.running ? '[OK]' : '[X]'; + 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 = loadOrCreateUnifiedConfig(); + const copilotConfig = config.copilot ?? DEFAULT_COPILOT_CONFIG; + + 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}`); + } + + console.log(''); + console.log('To change model: ccs config (Copilot section)'); + + return 0; +} + +/** + * Handle start subcommand. + */ +async function handleStart(): Promise { + const config = loadOrCreateUnifiedConfig(); + const copilotConfig = config.copilot ?? DEFAULT_COPILOT_CONFIG; + + console.log(`[i] 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(`[X] ${result.error}`); + return 1; + } +} + +/** + * Handle stop subcommand. + */ +async function handleStop(): Promise { + console.log('[i] Stopping copilot-api daemon...'); + + const result = await stopDaemon(); + + if (result.success) { + console.log('[OK] Daemon stopped'); + return 0; + } else { + console.error(`[X] ${result.error}`); + return 1; + } +} + +/** + * Handle enable subcommand. + */ +async function handleEnable(): Promise { + const config = loadOrCreateUnifiedConfig(); + + if (!config.copilot) { + config.copilot = { ...DEFAULT_COPILOT_CONFIG }; + } + + config.copilot.enabled = true; + saveUnifiedConfig(config); + + 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 { + const config = loadOrCreateUnifiedConfig(); + + if (config.copilot) { + config.copilot.enabled = false; + saveUnifiedConfig(config); + } + + console.log('[OK] Copilot integration disabled'); + + return 0; +}