From bfc9361701376b0a2d5541f2c8e48e95e2637539 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Thu, 12 Feb 2026 08:37:01 +0700 Subject: [PATCH] fix(cursor): fix router fall-through, add daemon marker, use random test port - Route all `ccs cursor *` to handleCursorCommand (no profile-switching) - Add --ccs-daemon marker to spawned process for stable PID validation - Use random port in lifecycle integration test to prevent CI conflicts - Remove unnecessary .toFixed(1) on integer tokenAge --- src/ccs.ts | 15 ++++----------- src/commands/cursor-command.ts | 2 +- src/cursor/cursor-daemon.ts | 5 +++-- tests/unit/cursor/cursor-daemon.test.ts | 2 +- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/ccs.ts b/src/ccs.ts index 2880dab9..c12e35b3 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -533,18 +533,11 @@ async function main(): Promise { } // Special case: cursor command (Cursor IDE integration) - // Only route to command handler for known subcommands, otherwise treat as profile - // Note: Bare `ccs cursor` shows help (unlike copilot which falls through to profile) - // This is intentional — cursor has no profile-switching mode + // All `ccs cursor *` routes to cursor command handler — cursor has no profile-switching mode if (firstArg === 'cursor') { - const { handleCursorCommand, CURSOR_SUBCOMMANDS } = await import('./commands/cursor-command'); - if ( - args.length === 1 || - CURSOR_SUBCOMMANDS.includes(args[1] as (typeof CURSOR_SUBCOMMANDS)[number]) - ) { - const exitCode = await handleCursorCommand(args.slice(1)); - process.exit(exitCode); - } + const { handleCursorCommand } = await import('./commands/cursor-command'); + const exitCode = await handleCursorCommand(args.slice(1)); + process.exit(exitCode); } // Special case: copilot command (GitHub Copilot integration) diff --git a/src/commands/cursor-command.ts b/src/commands/cursor-command.ts index c5547380..ad6ce59a 100644 --- a/src/commands/cursor-command.ts +++ b/src/commands/cursor-command.ts @@ -160,7 +160,7 @@ async function handleStatus(): Promise { console.log(`Authentication: ${authIcon} ${authText}`); if (authStatus.authenticated && authStatus.tokenAge !== undefined) { - console.log(` Token age: ${authStatus.tokenAge.toFixed(1)} hours`); + console.log(` Token age: ${authStatus.tokenAge} hours`); } // Daemon status diff --git a/src/cursor/cursor-daemon.ts b/src/cursor/cursor-daemon.ts index 19ad0034..99fef7b2 100644 --- a/src/cursor/cursor-daemon.ts +++ b/src/cursor/cursor-daemon.ts @@ -179,7 +179,8 @@ export async function startDaemon( `, ]; - proc = spawn(process.execPath, args, { + // Append --ccs-daemon marker for PID validation in stopDaemon + proc = spawn(process.execPath, [...args, '--ccs-daemon'], { stdio: 'ignore', detached: true, }); @@ -267,7 +268,7 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string } // Verify the PID belongs to our daemon before signaling try { const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8'); - if (!cmdline.includes('cursor') && !cmdline.includes('/health')) { + if (!cmdline.includes('--ccs-daemon')) { // PID was reused by an unrelated process removePidFile(); return { success: true }; diff --git a/tests/unit/cursor/cursor-daemon.test.ts b/tests/unit/cursor/cursor-daemon.test.ts index 4865dbed..e6480b9d 100644 --- a/tests/unit/cursor/cursor-daemon.test.ts +++ b/tests/unit/cursor/cursor-daemon.test.ts @@ -141,7 +141,7 @@ describe('startDaemon', () => { it( 'starts and stops daemon successfully', async () => { - const port = 18765; + const port = 10000 + Math.floor(Math.random() * 50000); const result = await startDaemon({ port, model: 'test' }); expect(result.success).toBe(true); expect(result.pid).toBeDefined();