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
This commit is contained in:
kaitranntt
2025-12-18 02:26:46 -05:00
parent ecdad1d6d0
commit 9a8eea82c5
4 changed files with 54 additions and 28 deletions
+15 -14
View File
@@ -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<CopilotDebugInfo | null> {
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<CopilotAuthStatus> {
* @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',
});
+6 -5
View File
@@ -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',
+17 -4
View File
@@ -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<number> {
// 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;
}
+16 -5
View File
@@ -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';