From 6af718626ff9db1e58a132972d08abbc3aece5bd Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 12 Feb 2026 07:43:27 +0700 Subject: [PATCH] fix(cursor): align daemon routes, add enabled field, handle bare command - Change /start and /stop to /daemon/start and /daemon/stop matching copilot convention - Add enabled field to CursorConfig for dashboard toggle parity - Simplify getCursorConfig() to trust mergeWithDefaults() - Handle bare 'ccs cursor' to show help instead of reserved-name error --- src/ccs.ts | 15 ++++++++++----- src/config/unified-config-loader.ts | 8 ++------ src/config/unified-config-types.ts | 3 +++ src/web-server/routes/cursor-routes.ts | 11 +++++++---- src/web-server/routes/cursor-settings-routes.ts | 7 ++++++- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/ccs.ts b/src/ccs.ts index 6431c855..d50d57a8 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -554,14 +554,19 @@ async function main(): Promise { } // Special case: cursor command (Cursor IDE integration) - // Only route to command handler for known subcommands, otherwise treat as profile - // Bare 'ccs cursor' falls through to profile detection (reserved name). Subcommands required. + // Route to cursor handler for known subcommands or bare 'ccs cursor' (shows help) // Note: cursor does not have enable/disable — it uses daemon start/stop instead const CURSOR_SUBCOMMANDS = ['auth', 'status', 'models', 'start', 'stop', 'help', '--help', '-h']; - if (firstArg === 'cursor' && args.length > 1 && CURSOR_SUBCOMMANDS.includes(args[1])) { - // `ccs cursor ` - route to cursor command handler + if (firstArg === 'cursor') { + if (args.length > 1 && CURSOR_SUBCOMMANDS.includes(args[1])) { + // `ccs cursor ` - route to cursor command handler + const { handleCursorCommand } = await import('./commands/cursor-command'); + const exitCode = await handleCursorCommand(args.slice(1)); + process.exit(exitCode); + } + // Bare `ccs cursor` - show help instead of reserved-name error const { handleCursorCommand } = await import('./commands/cursor-command'); - const exitCode = await handleCursorCommand(args.slice(1)); + const exitCode = await handleCursorCommand([]); process.exit(exitCode); } diff --git a/src/config/unified-config-loader.ts b/src/config/unified-config-loader.ts index fd61b330..1ae5db8c 100644 --- a/src/config/unified-config-loader.ts +++ b/src/config/unified-config-loader.ts @@ -280,6 +280,7 @@ function mergeWithDefaults(partial: Partial): UnifiedConfig { }, // Cursor config - disabled by default, merge with defaults cursor: { + enabled: partial.cursor?.enabled ?? DEFAULT_CURSOR_CONFIG.enabled, port: partial.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port, auto_start: partial.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start, ghost_mode: partial.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode, @@ -915,10 +916,5 @@ export function getImageAnalysisConfig(): ImageAnalysisConfig { */ export function getCursorConfig(): CursorConfig { const config = loadOrCreateUnifiedConfig(); - - return { - port: config.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port, - auto_start: config.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start, - ghost_mode: config.cursor?.ghost_mode ?? DEFAULT_CURSOR_CONFIG.ghost_mode, - }; + return config.cursor ?? { ...DEFAULT_CURSOR_CONFIG }; } diff --git a/src/config/unified-config-types.ts b/src/config/unified-config-types.ts index 399dc56a..0a00de5f 100644 --- a/src/config/unified-config-types.ts +++ b/src/config/unified-config-types.ts @@ -236,6 +236,8 @@ export interface CopilotConfig { * Enables Cursor IDE usage via cursor proxy daemon. */ export interface CursorConfig { + /** Enable Cursor integration (default: false) */ + enabled: boolean; /** Port for cursor proxy daemon (default: 20129) */ port: number; /** Auto-start daemon when CCS starts (default: false) */ @@ -657,6 +659,7 @@ export const DEFAULT_COPILOT_CONFIG: CopilotConfig = { * Disabled by default, ghost mode enabled for privacy. */ export const DEFAULT_CURSOR_CONFIG: CursorConfig = { + enabled: false, port: 20129, auto_start: false, ghost_mode: true, diff --git a/src/web-server/routes/cursor-routes.ts b/src/web-server/routes/cursor-routes.ts index 529443fa..8c01ea39 100644 --- a/src/web-server/routes/cursor-routes.ts +++ b/src/web-server/routes/cursor-routes.ts @@ -72,6 +72,7 @@ router.get('/status', async (_req: Request, res: Response): Promise => { const daemonStatus = await getDaemonStatus(cursorConfig.port); res.json({ + enabled: cursorConfig.enabled, authenticated: authStatus.authenticated, daemon_running: daemonStatus.running, port: cursorConfig.port, @@ -153,9 +154,10 @@ router.get('/models', async (_req: Request, res: Response): Promise => { }); /** - * POST /api/cursor/start - Start cursor proxy daemon + * POST /api/cursor/daemon/start - Start cursor proxy daemon + * Path matches copilot convention: /api/{provider}/daemon/{action} */ -router.post('/start', async (_req: Request, res: Response): Promise => { +router.post('/daemon/start', async (_req: Request, res: Response): Promise => { try { const config = loadOrCreateUnifiedConfig(); const cursorConfig = config.cursor ?? DEFAULT_CURSOR_CONFIG; @@ -167,9 +169,10 @@ router.post('/start', async (_req: Request, res: Response): Promise => { }); /** - * POST /api/cursor/stop - Stop cursor proxy daemon + * POST /api/cursor/daemon/stop - Stop cursor proxy daemon + * Path matches copilot convention: /api/{provider}/daemon/{action} */ -router.post('/stop', async (_req: Request, res: Response): Promise => { +router.post('/daemon/stop', async (_req: Request, res: Response): Promise => { try { const result = await stopDaemon(); res.json(result); diff --git a/src/web-server/routes/cursor-settings-routes.ts b/src/web-server/routes/cursor-settings-routes.ts index 45ef3090..685b643c 100644 --- a/src/web-server/routes/cursor-settings-routes.ts +++ b/src/web-server/routes/cursor-settings-routes.ts @@ -52,6 +52,10 @@ router.put('/', (req: Request, res: Response): void => { return; } } + if ('enabled' in updates && typeof updates.enabled !== 'boolean') { + res.status(400).json({ error: 'enabled must be a boolean' }); + return; + } if ('auto_start' in updates && typeof updates.auto_start !== 'boolean') { res.status(400).json({ error: 'auto_start must be a boolean' }); return; @@ -64,8 +68,9 @@ router.put('/', (req: Request, res: Response): void => { const config = loadOrCreateUnifiedConfig(); // Merge updates with existing config - // Only known fields (port, auto_start, ghost_mode) are merged — unknown properties are ignored + // Only known fields are merged — unknown properties are ignored config.cursor = { + enabled: updates.enabled ?? config.cursor?.enabled ?? DEFAULT_CURSOR_CONFIG.enabled, port: updates.port ?? config.cursor?.port ?? DEFAULT_CURSOR_CONFIG.port, auto_start: updates.auto_start ?? config.cursor?.auto_start ?? DEFAULT_CURSOR_CONFIG.auto_start,