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
This commit is contained in:
Tam Nhu Tran
2026-02-12 07:43:27 +07:00
parent 2ba826bb72
commit 6af718626f
5 changed files with 28 additions and 16 deletions
+10 -5
View File
@@ -554,14 +554,19 @@ async function main(): Promise<void> {
}
// 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 <subcommand>` - route to cursor command handler
if (firstArg === 'cursor') {
if (args.length > 1 && CURSOR_SUBCOMMANDS.includes(args[1])) {
// `ccs cursor <subcommand>` - 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);
}
+2 -6
View File
@@ -280,6 +280,7 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): 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 };
}
+3
View File
@@ -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,
+7 -4
View File
@@ -72,6 +72,7 @@ router.get('/status', async (_req: Request, res: Response): Promise<void> => {
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<void> => {
});
/**
* 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<void> => {
router.post('/daemon/start', async (_req: Request, res: Response): Promise<void> => {
try {
const config = loadOrCreateUnifiedConfig();
const cursorConfig = config.cursor ?? DEFAULT_CURSOR_CONFIG;
@@ -167,9 +169,10 @@ router.post('/start', async (_req: Request, res: Response): Promise<void> => {
});
/**
* 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<void> => {
router.post('/daemon/stop', async (_req: Request, res: Response): Promise<void> => {
try {
const result = await stopDaemon();
res.json(result);
@@ -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,