From 9a8eea82c52e4ab6de261ccf566058e1d5d4a77d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 18 Dec 2025 02:26:46 -0500 Subject: [PATCH] refactor(copilot): use local installation from package manager - copilot-auth: use getCopilotApiBinPath() instead of npx - copilot-daemon: move PID file to ~/.ccs/copilot/daemon.pid - copilot-executor: auto-install copilot-api on first use - index: export package manager functions --- src/copilot/copilot-auth.ts | 29 +++++++++++++++-------------- src/copilot/copilot-daemon.ts | 11 ++++++----- src/copilot/copilot-executor.ts | 21 +++++++++++++++++---- src/copilot/index.ts | 21 ++++++++++++++++----- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/copilot/copilot-auth.ts b/src/copilot/copilot-auth.ts index 62b87528..e2f2d5e3 100644 --- a/src/copilot/copilot-auth.ts +++ b/src/copilot/copilot-auth.ts @@ -2,25 +2,22 @@ * Copilot Auth Handler * * Handles GitHub OAuth authentication for copilot-api. + * Uses local installation from ~/.ccs/copilot/ (managed by copilot-package-manager). */ -import { spawn, spawnSync } from 'child_process'; +import { spawn } from 'child_process'; import { CopilotAuthStatus, CopilotDebugInfo } from './types'; +import { + isCopilotApiInstalled as checkInstalled, + getCopilotApiBinPath, +} from './copilot-package-manager'; /** - * Check if copilot-api is installed (available via npx). + * Check if copilot-api is installed locally. + * Uses copilot-package-manager to check ~/.ccs/copilot/node_modules/.bin/copilot-api */ export function isCopilotApiInstalled(): boolean { - try { - const result = spawnSync('npx', ['copilot-api', '--version'], { - stdio: 'pipe', - timeout: 10000, - shell: process.platform === 'win32', - }); - return result.status === 0; - } catch { - return false; - } + return checkInstalled(); } /** @@ -28,9 +25,11 @@ export function isCopilotApiInstalled(): boolean { * Returns authentication status and version info. */ export async function getCopilotDebugInfo(): Promise { + const binPath = getCopilotApiBinPath(); + return new Promise((resolve) => { try { - const proc = spawn('npx', ['copilot-api', 'debug', '--json'], { + const proc = spawn(binPath, ['debug', '--json'], { stdio: ['ignore', 'pipe', 'pipe'], shell: process.platform === 'win32', timeout: 15000, @@ -100,12 +99,14 @@ export async function checkAuthStatus(): Promise { * @returns Promise that resolves when auth flow completes */ export function startAuthFlow(): Promise<{ success: boolean; error?: string }> { + const binPath = getCopilotApiBinPath(); + return new Promise((resolve) => { console.log('[i] Starting GitHub authentication for Copilot...'); console.log('[i] A browser window will open for GitHub OAuth.'); console.log(''); - const proc = spawn('npx', ['copilot-api', 'auth'], { + const proc = spawn(binPath, ['auth'], { stdio: 'inherit', shell: process.platform === 'win32', }); diff --git a/src/copilot/copilot-daemon.ts b/src/copilot/copilot-daemon.ts index f004bf4b..73fe69e6 100644 --- a/src/copilot/copilot-daemon.ts +++ b/src/copilot/copilot-daemon.ts @@ -2,18 +2,18 @@ * Copilot Daemon Manager * * Manages the copilot-api daemon lifecycle (start/stop/status). - * Only used when auto_start is enabled in config. + * Uses local installation from ~/.ccs/copilot/ (managed by copilot-package-manager). */ import { spawn, ChildProcess } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; -import * as os from 'os'; import * as http from 'http'; import { CopilotDaemonStatus } from './types'; import { CopilotConfig } from '../config/unified-config-types'; +import { getCopilotDir, getCopilotApiBinPath } from './copilot-package-manager'; -const PID_FILE = path.join(os.homedir(), '.ccs', 'copilot.pid'); +const PID_FILE = path.join(getCopilotDir(), 'daemon.pid'); /** * Check if copilot-api daemon is running on the specified port. @@ -118,7 +118,8 @@ export async function startDaemon( return { success: true, pid: getPidFromFile() ?? undefined }; } - const args = ['copilot-api', 'start', '--port', config.port.toString()]; + const binPath = getCopilotApiBinPath(); + const args = ['start', '--port', config.port.toString()]; // Add account type if (config.account_type !== 'individual') { @@ -137,7 +138,7 @@ export async function startDaemon( let proc: ChildProcess; try { - proc = spawn('npx', args, { + proc = spawn(binPath, args, { stdio: ['ignore', 'pipe', 'pipe'], detached: true, shell: process.platform === 'win32', diff --git a/src/copilot/copilot-executor.ts b/src/copilot/copilot-executor.ts index 77fd7623..c6e67633 100644 --- a/src/copilot/copilot-executor.ts +++ b/src/copilot/copilot-executor.ts @@ -2,13 +2,14 @@ * Copilot Executor * * Main execution flow for running Claude Code with copilot-api proxy. - * Similar to CLIProxy executor but for GitHub Copilot. + * Uses local installation from ~/.ccs/copilot/ (managed by copilot-package-manager). */ import { spawn } from 'child_process'; import { CopilotConfig } from '../config/unified-config-types'; import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth'; import { isDaemonRunning, startDaemon } from './copilot-daemon'; +import { ensureCopilotApi } from './copilot-package-manager'; import { CopilotStatus } from './types'; /** @@ -65,12 +66,24 @@ export async function executeCopilotProfile( config: CopilotConfig, claudeArgs: string[] ): Promise { - // Check if copilot-api is installed + // Ensure copilot-api is installed (auto-install if missing, auto-update if outdated) + try { + await ensureCopilotApi(); + } catch (error) { + console.error('[X] Failed to install copilot-api.'); + console.error(''); + console.error(`Error: ${(error as Error).message}`); + console.error(''); + console.error('Try installing manually:'); + console.error(' npm install -g copilot-api'); + return 1; + } + + // Check if copilot-api is installed (should be after ensureCopilotApi) if (!isCopilotApiInstalled()) { console.error('[X] copilot-api is not installed.'); console.error(''); - console.error('Install with: npm install -g copilot-api'); - console.error('Or run via npx: npx copilot-api auth'); + console.error('Install with: ccs copilot --install'); return 1; } diff --git a/src/copilot/index.ts b/src/copilot/index.ts index 6a36d4ea..d7d45f41 100644 --- a/src/copilot/index.ts +++ b/src/copilot/index.ts @@ -7,13 +7,24 @@ // Types export * from './types'; -// Auth +// Package Manager (self-managed installation) export { + getCopilotDir, + getCopilotApiBinPath, isCopilotApiInstalled, - checkAuthStatus, - startAuthFlow, - getCopilotDebugInfo, -} from './copilot-auth'; + getInstalledVersion, + getPinnedVersion, + savePinnedVersion, + clearPinnedVersion, + checkForUpdates, + ensureCopilotApi, + installCopilotApiVersion, + uninstallCopilotApi, + getCopilotApiInfo, +} from './copilot-package-manager'; + +// Auth +export { checkAuthStatus, startAuthFlow, getCopilotDebugInfo } from './copilot-auth'; // Daemon export { isDaemonRunning, getDaemonStatus, startDaemon, stopDaemon } from './copilot-daemon';