fix(cursor): address remaining PR #527 review feedback

- Add SIGKILL escalation in stopDaemon after SIGTERM timeout
- Document router difference between cursor/copilot help behavior
- Fix detectProvider to handle o1/o4 models via regex pattern
- Add fetchModelsFromDaemon fallback test for unreachable daemon
- Update CLAUDE.md help table with cursor command entry
This commit is contained in:
Tam Nhu Tran
2026-02-12 04:14:28 +07:00
parent 9f9db7dcea
commit afb5e746b3
5 changed files with 29 additions and 1 deletions
+1
View File
@@ -129,6 +129,7 @@ bun run validate # Step 3: Final check (must pass)
| `ccs cliproxy --help` | `src/commands/cliproxy-command.ts``showHelp()` |
| `ccs config --help` | `src/commands/config-command.ts``showHelp()` |
| `ccs copilot --help` | `src/commands/copilot-command.ts``handleHelp()` |
| `ccs cursor --help` | `src/commands/cursor-command.ts``handleHelp()` |
| `ccs doctor --help` | `src/commands/doctor-command.ts``showHelp()` |
| `ccs migrate --help` | `src/commands/migrate-command.ts``printMigrateHelp()` |
| `ccs env --help` | `src/commands/env-command.ts``showHelp()` |
+2
View File
@@ -534,6 +534,8 @@ async function main(): Promise<void> {
// 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
const CURSOR_SUBCOMMANDS = ['auth', 'status', 'models', 'start', 'stop', 'help', '--help', '-h'];
if (firstArg === 'cursor' && (args.length === 1 || CURSOR_SUBCOMMANDS.includes(args[1]))) {
// `ccs cursor <subcommand>` - route to cursor command handler
+8
View File
@@ -265,6 +265,14 @@ export async function stopDaemon(): Promise<{ success: boolean; error?: string }
}
}
// Escalate to SIGKILL if process still alive after SIGTERM attempts
try {
process.kill(pid, 0); // Check if still alive
process.kill(pid, 'SIGKILL'); // Escalate to force kill
} catch {
// Already dead — good
}
removePidFile();
return { success: true };
} catch (err) {
+1 -1
View File
@@ -148,7 +148,7 @@ export function getDefaultModel(): string {
*/
export function detectProvider(modelId: string): string {
if (modelId.includes('claude')) return 'anthropic';
if (modelId.includes('gpt') || modelId.includes('o3')) return 'openai';
if (modelId.includes('gpt') || /^o\d/.test(modelId)) return 'openai';
if (modelId.includes('gemini')) return 'google';
if (modelId.includes('cursor')) return 'cursor';
return 'openai';
+17
View File
@@ -10,6 +10,7 @@ import {
getDefaultModel,
detectProvider,
formatModelName,
fetchModelsFromDaemon,
} from '../../../src/cursor/cursor-models';
describe('DEFAULT_CURSOR_MODELS', () => {
@@ -57,6 +58,12 @@ describe('detectProvider', () => {
expect(detectProvider('o3-mini')).toBe('openai');
});
it('detects o1 and o4 models as openai', () => {
expect(detectProvider('o1')).toBe('openai');
expect(detectProvider('o1-preview')).toBe('openai');
expect(detectProvider('o4-mini')).toBe('openai');
});
it('detects google models', () => {
expect(detectProvider('gemini-2.5-pro')).toBe('google');
});
@@ -80,3 +87,13 @@ describe('formatModelName', () => {
expect(formatModelName('my-custom-model')).toBe('My Custom Model');
});
});
describe('fetchModelsFromDaemon', () => {
it('falls back to DEFAULT_CURSOR_MODELS when daemon is unreachable', async () => {
// Use a port that nothing is listening on
const unreachablePort = 9999;
const models = await fetchModelsFromDaemon(unreachablePort);
expect(models).toEqual(DEFAULT_CURSOR_MODELS);
});
});