fix(cursor): route bare cursor through runtime profile

This commit is contained in:
Tam Nhu Tran committed 2026-04-01 22:50:58 -04:00
1 parent 27f6a675be
commit 2d67f40175
19 files changed
+549 -59

No files matched your search

+53
View File
@@ -195,5 +195,58 @@ describe('ProfileDetector', () => {
existsSyncSpy.mockRestore();
}
});
it('should detect cursor as a first-class runtime profile when enabled', () => {
const mockUnifiedConfig = {
version: 2,
cursor: {
enabled: true,
port: 20129,
auto_start: true,
ghost_mode: true,
model: 'gpt-5.3-codex',
},
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(
mockUnifiedConfig as any
);
try {
const result = detector.detectProfileType('cursor');
expect(result.type).toBe('cursor');
expect(result.name).toBe('cursor');
expect(result.cursorConfig?.auto_start).toBe(true);
} finally {
isUnifiedModeSpy.mockRestore();
loadUnifiedConfigSpy.mockRestore();
}
});
it('should throw a helpful error when cursor profile is disabled', () => {
const mockUnifiedConfig = {
version: 2,
cursor: {
enabled: false,
port: 20129,
auto_start: false,
ghost_mode: true,
model: 'gpt-5.3-codex',
},
};
const isUnifiedModeSpy = spyOn(unifiedConfigLoader, 'isUnifiedMode').mockReturnValue(true);
const loadUnifiedConfigSpy = spyOn(unifiedConfigLoader, 'loadUnifiedConfig').mockReturnValue(
mockUnifiedConfig as any
);
try {
expect(() => detector.detectProfileType('cursor')).toThrow(/Cursor profile is not enabled/);
} finally {
isUnifiedModeSpy.mockRestore();
loadUnifiedConfigSpy.mockRestore();
}
});
});
});
@@ -88,13 +88,13 @@ describe('help command parity', () => {
).toBe(true);
});
test('root help documents bare cursor as the status entrypoint', async () => {
test('root help documents bare cursor as the runtime entrypoint', async () => {
const lines: string[] = [];
await handleHelpCommand((line) => lines.push(line));
const rendered = stripAnsi(lines.join('\n'));
expect(rendered.includes('ccs cursor <cmd>')).toBe(false);
expect(rendered.includes('Cursor status + local daemon connection details')).toBe(true);
expect(rendered.includes('Run Claude via Cursor local proxy')).toBe(true);
});
test('root help explains Claude [1m] as an explicit CCS suffix with upstream limits', async () => {
@@ -74,6 +74,15 @@ describe('root-command-router', () => {
expect(calls).toEqual([]);
});
it('does not capture cursor so bare cursor can fall through to profile routing', async () => {
const tryHandleRootCommand = await loadTryHandleRootCommand();
await expect(tryHandleRootCommand(['cursor'])).resolves.toBe(false);
await expect(tryHandleRootCommand(['cursor', 'status'])).resolves.toBe(false);
expect(calls).toEqual([]);
});
it('prints update help without invoking the updater', async () => {
const tryHandleRootCommand = await loadTryHandleRootCommand();
+6 -10
View File
@@ -303,7 +303,7 @@ describe('stopDaemon', () => {
});
describe('handleCursorCommand', () => {
it('treats bare ccs cursor as a status entrypoint instead of help', async () => {
it('shows help when invoked without an admin subcommand', async () => {
const originalLog = console.log;
const originalError = console.error;
const logs: string[] = [];
@@ -321,10 +321,8 @@ describe('handleCursorCommand', () => {
expect(exitCode).toBe(0);
expect(errors).toHaveLength(0);
expect(logs.some((line) => line.includes('Cursor IDE Status'))).toBe(true);
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand> [options]'))).toBe(
false
);
expect(logs.some((line) => line.includes('Cursor IDE Integration'))).toBe(true);
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
} finally {
console.log = originalLog;
console.error = originalError;
@@ -473,7 +471,7 @@ describe('renderCursorStatus', () => {
});
describe('renderCursorHelp', () => {
it('shows bare ccs cursor as an optional entrypoint', () => {
it('shows bare ccs cursor as the runtime entrypoint', () => {
const originalLog = console.log;
const logs: string[] = [];
@@ -485,11 +483,9 @@ describe('renderCursorHelp', () => {
const exitCode = renderCursorHelp();
expect(exitCode).toBe(0);
expect(logs.some((line) => line.includes('Usage: ccs cursor [subcommand] [options]'))).toBe(
true
);
expect(logs.some((line) => line.includes('Usage: ccs cursor <subcommand>'))).toBe(true);
expect(
logs.some((line) => line.includes('4. ccs cursor # Show status and runtime connection details'))
logs.some((line) => line.includes('ccs cursor [claude args]'))
).toBe(true);
} finally {
console.log = originalLog;
@@ -0,0 +1,89 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import {
executeCursorProfile,
generateCursorEnv,
} from '../../../src/cursor/cursor-profile-executor';
import { saveCredentials } from '../../../src/cursor/cursor-auth';
import type { CursorConfig } from '../../../src/config/unified-config-types';
const BASE_CONFIG: CursorConfig = {
enabled: true,
port: 20129,
auto_start: false,
ghost_mode: true,
model: 'gpt-5.3-codex',
};
describe('cursor-profile-executor', () => {
let originalCcsHome: string | undefined;
let tempDir: string;
beforeEach(() => {
originalCcsHome = process.env.CCS_HOME;
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-cursor-profile-executor-'));
process.env.CCS_HOME = tempDir;
});
afterEach(() => {
if (originalCcsHome !== undefined) {
process.env.CCS_HOME = originalCcsHome;
} else {
delete process.env.CCS_HOME;
}
fs.rmSync(tempDir, { recursive: true, force: true });
});
it('builds Cursor env for Claude runtime', () => {
const env = generateCursorEnv(
{
...BASE_CONFIG,
opus_model: 'cursor-opus',
sonnet_model: 'cursor-sonnet',
haiku_model: 'cursor-haiku',
},
'/tmp/claude-config'
);
expect(env.ANTHROPIC_BASE_URL).toBe('http://127.0.0.1:20129');
expect(env.ANTHROPIC_AUTH_TOKEN).toBe('cursor-managed');
expect(env.ANTHROPIC_MODEL).toBe('gpt-5.3-codex');
expect(env.ANTHROPIC_DEFAULT_OPUS_MODEL).toBe('cursor-opus');
expect(env.ANTHROPIC_DEFAULT_SONNET_MODEL).toBe('cursor-sonnet');
expect(env.ANTHROPIC_DEFAULT_HAIKU_MODEL).toBe('cursor-haiku');
expect(env.CLAUDE_CONFIG_DIR).toBe('/tmp/claude-config');
});
it('fails fast when Cursor integration is disabled', async () => {
const exitCode = await executeCursorProfile({ ...BASE_CONFIG, enabled: false }, []);
expect(exitCode).toBe(1);
});
it('fails when credentials are missing', async () => {
const exitCode = await executeCursorProfile(BASE_CONFIG, []);
expect(exitCode).toBe(1);
});
it('fails with actionable guidance when daemon is down and auto_start is false', async () => {
saveCredentials({
accessToken: 'a'.repeat(60),
machineId: '1234567890abcdef1234567890abcdef',
authMethod: 'manual',
importedAt: new Date().toISOString(),
});
const exitCode = await executeCursorProfile(
{
...BASE_CONFIG,
port: 29991,
auto_start: false,
},
[]
);
expect(exitCode).toBe(1);
});
});
@@ -3,6 +3,27 @@ import { describe, expect, test } from 'bun:test';
import { evaluateTargetRuntimeCompatibility } from '../../../src/targets/target-runtime-compatibility';
describe('evaluateTargetRuntimeCompatibility', () => {
test('rejects account, copilot, and cursor profiles on Droid target', () => {
expect(
evaluateTargetRuntimeCompatibility({
target: 'droid',
profileType: 'account',
}).supported
).toBe(false);
expect(
evaluateTargetRuntimeCompatibility({
target: 'droid',
profileType: 'copilot',
}).supported
).toBe(false);
expect(
evaluateTargetRuntimeCompatibility({
target: 'droid',
profileType: 'cursor',
}).supported
).toBe(false);
});
test('supports native Codex default sessions', () => {
expect(
evaluateTargetRuntimeCompatibility({
@@ -69,7 +90,7 @@ describe('evaluateTargetRuntimeCompatibility', () => {
expect(genericSettingsCompatibility.reason).toMatch(/currently supports native default sessions/);
});
test('rejects account and copilot profiles on Codex target', () => {
test('rejects account, copilot, and cursor profiles on Codex target', () => {
expect(
evaluateTargetRuntimeCompatibility({
target: 'codex',
@@ -82,5 +103,11 @@ describe('evaluateTargetRuntimeCompatibility', () => {
profileType: 'copilot',
}).supported
).toBe(false);
expect(
evaluateTargetRuntimeCompatibility({
target: 'codex',
profileType: 'cursor',
}).supported
).toBe(false);
});
});