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
This commit is contained in:
Tam Nhu Tran
2026-02-12 08:37:01 +07:00
parent 760a5c3ca4
commit bfc9361701
4 changed files with 9 additions and 15 deletions
+4 -11
View File
@@ -533,18 +533,11 @@ async function main(): Promise<void> {
} }
// Special case: cursor command (Cursor IDE integration) // Special case: cursor command (Cursor IDE integration)
// Only route to command handler for known subcommands, otherwise treat as profile // All `ccs cursor *` routes to cursor command handler — cursor has no profile-switching mode
// Note: Bare `ccs cursor` shows help (unlike copilot which falls through to profile)
// This is intentional — cursor has no profile-switching mode
if (firstArg === 'cursor') { if (firstArg === 'cursor') {
const { handleCursorCommand, CURSOR_SUBCOMMANDS } = await import('./commands/cursor-command'); const { handleCursorCommand } = await import('./commands/cursor-command');
if ( const exitCode = await handleCursorCommand(args.slice(1));
args.length === 1 || process.exit(exitCode);
CURSOR_SUBCOMMANDS.includes(args[1] as (typeof CURSOR_SUBCOMMANDS)[number])
) {
const exitCode = await handleCursorCommand(args.slice(1));
process.exit(exitCode);
}
} }
// Special case: copilot command (GitHub Copilot integration) // Special case: copilot command (GitHub Copilot integration)
+1 -1
View File
@@ -160,7 +160,7 @@ async function handleStatus(): Promise<number> {
console.log(`Authentication: ${authIcon} ${authText}`); console.log(`Authentication: ${authIcon} ${authText}`);
if (authStatus.authenticated && authStatus.tokenAge !== undefined) { if (authStatus.authenticated && authStatus.tokenAge !== undefined) {
console.log(` Token age: ${authStatus.tokenAge.toFixed(1)} hours`); console.log(` Token age: ${authStatus.tokenAge} hours`);
} }
// Daemon status // Daemon status
+3 -2
View File
@@ -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', stdio: 'ignore',
detached: true, detached: true,
}); });
@@ -267,7 +268,7 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
// Verify the PID belongs to our daemon before signaling // Verify the PID belongs to our daemon before signaling
try { try {
const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8'); 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 // PID was reused by an unrelated process
removePidFile(); removePidFile();
return { success: true }; return { success: true };
+1 -1
View File
@@ -141,7 +141,7 @@ describe('startDaemon', () => {
it( it(
'starts and stops daemon successfully', 'starts and stops daemon successfully',
async () => { async () => {
const port = 18765; const port = 10000 + Math.floor(Math.random() * 50000);
const result = await startDaemon({ port, model: 'test' }); const result = await startDaemon({ port, model: 'test' });
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.pid).toBeDefined(); expect(result.pid).toBeDefined();