Files
ccs/src/commands/cursor-command.ts
T
Tam Nhu Tran 4f6e61739c refactor(config): adopt config-loader-facade across the codebase
Issue #1161. Sweeps 127 files to import from
src/config/config-loader-facade.ts instead of unified-config-loader or
utils/config-manager directly.

WRITE callers (32 files): replaced raw saveUnifiedConfig /
mutateUnifiedConfig / updateUnifiedConfig calls with the facade's
cache-coherent wrappers saveConfig / mutateConfig / updateConfig. This
fixes a latent stale-cache window where direct writes through the
underlying loader bypassed the facade's memoization.

READ callers (95 files): mechanical import-path migration only —
function names unchanged because the facade re-exports them. No
behavior change.

Also updated:
- tests/unit/utils/browser/browser-setup.test.ts (DI interface rename)
- src/management/checks/image-analysis-check.ts (dynamic import rename)
- src/web-server/health-service.ts (dynamic require rename)
- src/ccs.ts (path prefix fix from sweep script)

After sweep: zero raw write callers remain outside src/config/. Direct
imports of config-manager remain only for symbols not in the facade
(getConfigPath, getCcsDirSource, etc). Behavior unchanged; full suite
passes 1824/1824.

Out of scope: switching loadOrCreateUnifiedConfig() callers to
getCachedConfig() — needs per-callsite cache-safety analysis. Tracked
as follow-up.

Refs #1161
2026-05-03 01:42:53 -04:00

323 lines
8.8 KiB
TypeScript

/**
* Cursor CLI Command
*
* Handles `ccs cursor [subcommand]` commands.
*/
import {
autoDetectTokens,
validateToken,
saveCredentials,
checkAuthStatus,
startDaemon,
stopDaemon,
getDaemonStatus,
getAvailableModels,
getDefaultModel,
probeCursorRuntime,
} from '../cursor';
import { DEFAULT_CURSOR_CONFIG } from '../config/unified-config-types';
import {
renderCursorHelp,
renderCursorModels,
renderCursorProbe,
renderCursorStatus,
} from './cursor-command-display';
import { ok, fail, info } from '../utils/ui';
import { getCursorConfig, mutateConfig } from '../config/config-loader-facade';
const LEGACY_CURSOR_COMMAND = 'ccs legacy cursor';
const CLIPROXY_CURSOR_COMMAND = 'ccs cursor';
function printLegacyCursorDeprecationNotice(): void {
console.log(
info(
`Deprecated compatibility path. \`${CLIPROXY_CURSOR_COMMAND}\` now belongs to the CLIProxy Cursor provider; use \`${LEGACY_CURSOR_COMMAND}\` for the old bridge.`
)
);
console.log('');
}
/**
* Handle cursor subcommand.
*/
export async function handleCursorCommand(args: string[]): Promise<number> {
const subcommand = args[0];
switch (subcommand) {
case 'auth':
return handleAuth(args.slice(1));
case 'status':
return handleStatus();
case 'probe':
return handleProbe();
case 'models':
return handleModels();
case 'start':
return handleStart();
case 'stop':
return handleStop();
case 'enable':
return handleEnable();
case 'disable':
return handleDisable();
case undefined:
return handleHelp();
case 'help':
case '--help':
case '-h':
return handleHelp();
default:
console.error(fail(`Unknown subcommand: ${subcommand}`));
console.error('');
void handleHelp(); // Print help but keep exit code 1
return 1;
}
}
function handleHelp(): number {
return renderCursorHelp();
}
function parseOptionValue(args: string[], key: string): string | undefined {
const exactIndex = args.findIndex((arg) => arg === key);
if (exactIndex !== -1 && args[exactIndex + 1]) {
return args[exactIndex + 1];
}
const prefix = `${key}=`;
const withEquals = args.find((arg) => arg.startsWith(prefix));
if (withEquals) {
return withEquals.slice(prefix.length);
}
return undefined;
}
function printAutoDetectFailure(result: {
error?: string;
checkedPaths?: string[];
reason?: string;
}): void {
console.error(fail(`Auto-detection failed: ${result.error ?? 'Unknown error'}`));
if (result.checkedPaths?.length && result.reason === 'db_not_found') {
console.log('');
console.log('Checked paths:');
for (const candidate of result.checkedPaths) {
console.log(` - ${candidate}`);
}
}
if (result.reason === 'sqlite_unavailable') {
console.log('');
console.log('Recommended next steps:');
console.log(' 1. Install sqlite3 so CCS can read Cursor state automatically');
console.log(' 2. Or use manual import immediately');
} else if (result.reason === 'db_query_failed') {
console.log('');
console.log('Recommended next steps:');
console.log(' 1. Close Cursor IDE and retry auto-detect');
console.log(' 2. If the database remains unreadable, use manual import');
}
}
/**
* Handle auth subcommand.
*/
async function handleAuth(args: string[]): Promise<number> {
printLegacyCursorDeprecationNotice();
const manual = args.includes('--manual');
if (manual) {
const accessToken =
parseOptionValue(args, '--token') ?? parseOptionValue(args, '--access-token') ?? '';
const machineId =
parseOptionValue(args, '--machine-id') ?? parseOptionValue(args, '--machineId') ?? '';
if (!accessToken || !machineId) {
console.error(
fail(
`Manual auth requires both token and machine ID.\n\nExample:\n ${LEGACY_CURSOR_COMMAND} auth --manual --token <token> --machine-id <machine-id>`
)
);
return 1;
}
if (!validateToken(accessToken, machineId)) {
console.error(fail('Invalid token or machine ID format'));
return 1;
}
saveCredentials({
accessToken,
machineId,
authMethod: 'manual',
importedAt: new Date().toISOString(),
});
console.log(ok('Cursor credentials imported (manual mode)'));
console.log('');
console.log('Next steps:');
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
return 0;
}
console.log(info('Importing Cursor IDE authentication...'));
console.log('');
console.log(info('Attempting auto-detection...'));
const autoResult = autoDetectTokens();
if (autoResult.found && autoResult.accessToken && autoResult.machineId) {
saveCredentials({
accessToken: autoResult.accessToken,
machineId: autoResult.machineId,
authMethod: 'auto-detect',
importedAt: new Date().toISOString(),
});
console.log(ok('Auto-detected Cursor credentials'));
console.log('');
console.log('Next steps:');
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
console.log(` 1. Enable integration: ${LEGACY_CURSOR_COMMAND} enable`);
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
return 0;
}
console.log('');
printAutoDetectFailure(autoResult);
console.log('');
console.log('Manual fallback:');
console.log(` ${LEGACY_CURSOR_COMMAND} auth --manual --token <token> --machine-id <machine-id>`);
console.log('');
return 1;
}
async function handleStatus(): Promise<number> {
printLegacyCursorDeprecationNotice();
const cursorConfig = getCursorConfig();
const authStatus = checkAuthStatus();
const daemonStatus = await getDaemonStatus(cursorConfig.port);
renderCursorStatus(cursorConfig, authStatus, daemonStatus);
return 0;
}
async function handleProbe(): Promise<number> {
printLegacyCursorDeprecationNotice();
const cursorConfig = getCursorConfig();
const result = await probeCursorRuntime(cursorConfig);
renderCursorProbe(result);
return result.ok ? 0 : 1;
}
async function handleModels(): Promise<number> {
printLegacyCursorDeprecationNotice();
const cursorConfig = getCursorConfig();
const models = await getAvailableModels(cursorConfig.port);
const defaultModel = getDefaultModel();
renderCursorModels(models, defaultModel);
return 0;
}
/**
* Handle start subcommand.
*/
async function handleStart(): Promise<number> {
printLegacyCursorDeprecationNotice();
const cursorConfig = getCursorConfig();
if (!cursorConfig.enabled) {
console.error(fail(`Cursor integration is disabled. Run: ${LEGACY_CURSOR_COMMAND} enable`));
return 1;
}
const authStatus = checkAuthStatus();
if (!authStatus.authenticated) {
console.error(fail(`Not authenticated. Run: ${LEGACY_CURSOR_COMMAND} auth`));
return 1;
}
if (authStatus.expired) {
console.error(fail(`Credentials expired. Run: ${LEGACY_CURSOR_COMMAND} auth`));
return 1;
}
console.log(info(`Starting cursor daemon on port ${cursorConfig.port}...`));
const result = await startDaemon({
port: cursorConfig.port,
ghost_mode: cursorConfig.ghost_mode,
});
if (result.success) {
console.log(ok(`Daemon started (PID: ${result.pid})`));
return 0;
} else {
console.error(fail(result.error || 'Failed to start daemon'));
return 1;
}
}
/**
* Handle stop subcommand.
*/
async function handleStop(): Promise<number> {
printLegacyCursorDeprecationNotice();
console.log(info('Stopping cursor daemon...'));
const result = await stopDaemon();
if (result.success) {
console.log(ok('Daemon stopped'));
return 0;
} else {
console.error(fail(result.error || 'Failed to stop daemon'));
return 1;
}
}
/**
* Handle enable subcommand.
*/
async function handleEnable(): Promise<number> {
printLegacyCursorDeprecationNotice();
mutateConfig((config) => {
if (!config.cursor) {
config.cursor = { ...DEFAULT_CURSOR_CONFIG };
}
config.cursor.enabled = true;
});
console.log(ok('Cursor integration enabled'));
console.log('');
console.log('Next steps:');
console.log(` 0. Preferred auth: ${CLIPROXY_CURSOR_COMMAND} --auth`);
console.log(` 1. Authenticate: ${LEGACY_CURSOR_COMMAND} auth`);
console.log(` 2. Start daemon: ${LEGACY_CURSOR_COMMAND} start`);
console.log(` 3. Check status: ${LEGACY_CURSOR_COMMAND} status`);
return 0;
}
/**
* Handle disable subcommand.
*/
async function handleDisable(): Promise<number> {
printLegacyCursorDeprecationNotice();
mutateConfig((config) => {
if (config.cursor) {
config.cursor.enabled = false;
}
});
console.log(ok('Cursor integration disabled'));
return 0;
}