mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
feat(cursor): add daemon lifecycle, models catalog, and CLI commands
Implements #520 - Cursor IDE daemon, models catalog, module index, and CLI. Files created: - src/cursor/cursor-daemon.ts (~230 LOC) - Lifecycle management (start/stop/status) - src/cursor/cursor-models.ts (~145 LOC) - Model catalog with Anthropic/OpenAI/Google/Cursor models - src/cursor/index.ts (~30 LOC) - Module barrel exports - src/commands/cursor-command.ts (~230 LOC) - CLI commands (auth/status/models/start/stop) Key features: - Daemon health checks via HTTP /health endpoint - PID file management in ~/.ccs/cursor/daemon.pid - Model catalog with fetchModelsFromDaemon() and fallback defaults - CLI subcommand routing following copilot-command.ts pattern - ASCII-only output ([OK], [X], [i] markers) Temporary defaults for config (port: 4242, model: gpt-4.1) until #521 adds cursor to unified config schema. Note: Actual daemon server implementation (CursorExecutor integration) deferred to #522 - this creates the lifecycle structure only.
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Cursor CLI Command
|
||||
*
|
||||
* Handles `ccs cursor <subcommand>` commands.
|
||||
*/
|
||||
|
||||
import {
|
||||
autoDetectTokens,
|
||||
checkAuthStatus,
|
||||
startDaemon,
|
||||
stopDaemon,
|
||||
getDaemonStatus,
|
||||
getAvailableModels,
|
||||
} from '../cursor';
|
||||
import { ok, fail, info, color } from '../utils/ui';
|
||||
|
||||
// Temporary default config until #521 adds cursor to unified config
|
||||
const DEFAULT_CURSOR_CONFIG = {
|
||||
port: 4242,
|
||||
model: 'gpt-4.1',
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle cursor subcommand.
|
||||
*/
|
||||
export async function handleCursorCommand(args: string[]): Promise<number> {
|
||||
const subcommand = args[0];
|
||||
|
||||
switch (subcommand) {
|
||||
case 'auth':
|
||||
return handleAuth();
|
||||
case 'status':
|
||||
return handleStatus();
|
||||
case 'models':
|
||||
return handleModels();
|
||||
case 'start':
|
||||
return handleStart();
|
||||
case 'stop':
|
||||
return handleStop();
|
||||
case undefined:
|
||||
case 'help':
|
||||
case '--help':
|
||||
case '-h':
|
||||
return handleHelp();
|
||||
default:
|
||||
console.error(fail(`Unknown subcommand: ${subcommand}`));
|
||||
console.error('');
|
||||
return handleHelp();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show help for cursor commands.
|
||||
*/
|
||||
function handleHelp(): number {
|
||||
console.log('Cursor IDE Integration');
|
||||
console.log('');
|
||||
console.log('Usage: ccs cursor <subcommand>');
|
||||
console.log('');
|
||||
console.log('Subcommands:');
|
||||
console.log(' auth Import Cursor IDE authentication token');
|
||||
console.log(' status Show authentication and daemon status');
|
||||
console.log(' models List available models');
|
||||
console.log(' start Start cursor daemon');
|
||||
console.log(' stop Stop cursor daemon');
|
||||
console.log(' help Show this help message');
|
||||
console.log('');
|
||||
console.log('Quick start:');
|
||||
console.log(' 1. ccs cursor auth # Import Cursor IDE token');
|
||||
console.log(' 2. ccs cursor start # Start daemon');
|
||||
console.log(' 3. Use cursor models # Via daemon on configured port');
|
||||
console.log('');
|
||||
console.log('Or use the web UI: ccs config → Cursor tab');
|
||||
console.log('');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle auth subcommand.
|
||||
*/
|
||||
async function handleAuth(): Promise<number> {
|
||||
console.log(info('Importing Cursor IDE authentication...'));
|
||||
console.log('');
|
||||
|
||||
// Try auto-detection first
|
||||
console.log(info('Attempting auto-detection...'));
|
||||
const autoResult = await autoDetectTokens();
|
||||
|
||||
if (autoResult.found && autoResult.accessToken && autoResult.machineId) {
|
||||
console.log(ok('Auto-detected Cursor credentials'));
|
||||
console.log('');
|
||||
console.log('Next steps:');
|
||||
console.log(' 1. Start daemon: ccs cursor start');
|
||||
console.log(' 2. Check status: ccs cursor status');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fall back to manual import
|
||||
console.log('');
|
||||
console.log('Auto-detection failed. Please provide credentials manually.');
|
||||
console.log('');
|
||||
console.log('To find your Cursor credentials:');
|
||||
console.log(' 1. Open Cursor IDE');
|
||||
console.log(' 2. Check application data directory');
|
||||
console.log(' 3. Look for access token and machine ID');
|
||||
console.log('');
|
||||
|
||||
// For now, just show instructions
|
||||
// Manual import flow will be implemented when needed
|
||||
console.error(fail('Manual import not yet implemented'));
|
||||
console.error('');
|
||||
console.error('Use auto-detection for now or wait for manual import feature.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle status subcommand.
|
||||
*/
|
||||
async function handleStatus(): Promise<number> {
|
||||
// TODO: Load from unified config when #521 is complete
|
||||
const cursorConfig = DEFAULT_CURSOR_CONFIG;
|
||||
|
||||
const authStatus = await checkAuthStatus();
|
||||
const daemonStatus = await getDaemonStatus(cursorConfig.port);
|
||||
|
||||
console.log('Cursor IDE Status');
|
||||
console.log('─────────────────');
|
||||
console.log('');
|
||||
|
||||
// Auth status
|
||||
const authIcon = authStatus.authenticated ? color('[OK]', 'success') : color('[X]', 'error');
|
||||
const authText = authStatus.authenticated ? 'Authenticated' : 'Not authenticated';
|
||||
console.log(`Authentication: ${authIcon} ${authText}`);
|
||||
|
||||
if (authStatus.authenticated && authStatus.tokenAge !== undefined) {
|
||||
console.log(` Token age: ${authStatus.tokenAge.toFixed(1)} hours`);
|
||||
}
|
||||
|
||||
// Daemon status
|
||||
const daemonIcon = daemonStatus.running ? color('[OK]', 'success') : color('[X]', 'error');
|
||||
const daemonText = daemonStatus.running ? 'Running' : 'Not running';
|
||||
console.log(`Daemon: ${daemonIcon} ${daemonText}`);
|
||||
|
||||
if (daemonStatus.pid) {
|
||||
console.log(` PID: ${daemonStatus.pid}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('Configuration:');
|
||||
console.log(` Port: ${cursorConfig.port}`);
|
||||
console.log(` Model: ${cursorConfig.model}`);
|
||||
|
||||
console.log('');
|
||||
|
||||
// Show next steps if not fully configured
|
||||
if (!authStatus.authenticated || !daemonStatus.running) {
|
||||
console.log('Next steps:');
|
||||
if (!authStatus.authenticated) {
|
||||
console.log(' - Auth: ccs cursor auth');
|
||||
}
|
||||
if (!daemonStatus.running) {
|
||||
console.log(' - Start: ccs cursor start');
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle models subcommand.
|
||||
*/
|
||||
async function handleModels(): Promise<number> {
|
||||
// TODO: Load from unified config when #521 is complete
|
||||
const cursorConfig = DEFAULT_CURSOR_CONFIG;
|
||||
|
||||
console.log('Available Cursor Models');
|
||||
console.log('───────────────────────');
|
||||
console.log('');
|
||||
|
||||
const models = await getAvailableModels(cursorConfig.port);
|
||||
|
||||
for (const model of models) {
|
||||
const current = model.id === cursorConfig.model ? ' [CURRENT]' : '';
|
||||
const defaultMark = model.isDefault ? ' (default)' : '';
|
||||
console.log(` ${model.id}${current}${defaultMark}`);
|
||||
console.log(` Provider: ${model.provider}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('To change model: ccs config (Cursor section)');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle start subcommand.
|
||||
*/
|
||||
async function handleStart(): Promise<number> {
|
||||
// TODO: Load from unified config when #521 is complete
|
||||
const cursorConfig = DEFAULT_CURSOR_CONFIG;
|
||||
|
||||
// Check auth first
|
||||
const authStatus = await checkAuthStatus();
|
||||
if (!authStatus.authenticated) {
|
||||
console.error(fail('Not authenticated. Run: ccs cursor auth'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
console.log(info(`Starting cursor daemon on port ${cursorConfig.port}...`));
|
||||
|
||||
const result = await startDaemon(cursorConfig);
|
||||
|
||||
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> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Cursor Daemon Manager
|
||||
*
|
||||
* Manages the cursor daemon lifecycle (start/stop/status).
|
||||
* Uses CursorExecutor for OpenAI-compatible API proxy to Cursor backend.
|
||||
*/
|
||||
|
||||
import { spawn, ChildProcess } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import type { CursorDaemonStatus } from './types';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
|
||||
// Temporary interface until #521 adds cursor to unified config
|
||||
interface CursorConfig {
|
||||
port: number;
|
||||
model: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Cursor directory path.
|
||||
*/
|
||||
function getCursorDir(): string {
|
||||
return path.join(getCcsDir(), 'cursor');
|
||||
}
|
||||
|
||||
const PID_FILE = path.join(getCursorDir(), 'daemon.pid');
|
||||
|
||||
/**
|
||||
* Check if cursor daemon is running on the specified port.
|
||||
* Uses 127.0.0.1 instead of localhost for more reliable local connections.
|
||||
*/
|
||||
export async function isDaemonRunning(port: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.request(
|
||||
{
|
||||
hostname: '127.0.0.1',
|
||||
port,
|
||||
path: '/health',
|
||||
method: 'GET',
|
||||
timeout: 3000,
|
||||
},
|
||||
(res) => {
|
||||
resolve(res.statusCode === 200);
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get daemon status.
|
||||
*/
|
||||
export async function getDaemonStatus(port: number): Promise<CursorDaemonStatus> {
|
||||
const running = await isDaemonRunning(port);
|
||||
const pid = getPidFromFile();
|
||||
|
||||
return {
|
||||
running,
|
||||
port,
|
||||
pid: running ? (pid ?? undefined) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Read PID from file.
|
||||
*/
|
||||
function getPidFromFile(): number | null {
|
||||
try {
|
||||
if (fs.existsSync(PID_FILE)) {
|
||||
const content = fs.readFileSync(PID_FILE, 'utf8').trim();
|
||||
const pid = parseInt(content, 10);
|
||||
return isNaN(pid) ? null : pid;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write PID to file.
|
||||
*/
|
||||
function writePidToFile(pid: number): void {
|
||||
try {
|
||||
const dir = path.dirname(PID_FILE);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
}
|
||||
fs.writeFileSync(PID_FILE, pid.toString(), { mode: 0o600 });
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove PID file.
|
||||
*/
|
||||
function removePidFile(): void {
|
||||
try {
|
||||
if (fs.existsSync(PID_FILE)) {
|
||||
fs.unlinkSync(PID_FILE);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the cursor daemon.
|
||||
*
|
||||
* @param config Cursor configuration
|
||||
* @returns Promise that resolves when daemon is ready
|
||||
*/
|
||||
export async function startDaemon(
|
||||
config: CursorConfig
|
||||
): Promise<{ success: boolean; pid?: number; error?: string }> {
|
||||
// Check if already running
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
return { success: true, pid: getPidFromFile() ?? undefined };
|
||||
}
|
||||
|
||||
// For now, create a simple structure that will be filled in later
|
||||
// The actual server implementation will be added in a separate task
|
||||
return new Promise((resolve) => {
|
||||
let proc: ChildProcess;
|
||||
|
||||
try {
|
||||
// Spawn a placeholder Node.js process
|
||||
// TODO: Replace with actual CursorExecutor-based server
|
||||
const args = [
|
||||
'-e',
|
||||
`
|
||||
const http = require('http');
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.url === '/health') {
|
||||
res.writeHead(200);
|
||||
res.end('OK');
|
||||
} else if (req.url === '/v1/models') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ data: [] }));
|
||||
} else {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
}
|
||||
});
|
||||
server.listen(${config.port}, '127.0.0.1');
|
||||
`,
|
||||
];
|
||||
|
||||
proc = spawn('node', args, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: true,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
// Unref so parent can exit
|
||||
proc.unref();
|
||||
|
||||
if (proc.pid) {
|
||||
writePidToFile(proc.pid);
|
||||
}
|
||||
|
||||
// Wait for daemon to be ready (poll for up to 30 seconds)
|
||||
let attempts = 0;
|
||||
const maxAttempts = 30;
|
||||
const checkInterval = setInterval(async () => {
|
||||
attempts++;
|
||||
|
||||
if (await isDaemonRunning(config.port)) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({ success: true, pid: proc.pid });
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Daemon did not start within 30 seconds',
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
proc.on('error', (err) => {
|
||||
clearInterval(checkInterval);
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to start daemon: ${err.message}`,
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to spawn daemon: ${(err as Error).message}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the cursor daemon.
|
||||
*/
|
||||
export async function stopDaemon(): Promise<{ success: boolean; error?: string }> {
|
||||
const pid = getPidFromFile();
|
||||
|
||||
if (!pid) {
|
||||
// No PID file, try to find by port
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
try {
|
||||
// Send SIGTERM to the process
|
||||
process.kill(pid, 'SIGTERM');
|
||||
|
||||
// Wait for process to exit (up to 5 seconds)
|
||||
let attempts = 0;
|
||||
while (attempts < 10) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
try {
|
||||
// Check if process still exists (kill(pid, 0) throws if not)
|
||||
process.kill(pid, 0);
|
||||
attempts++;
|
||||
} catch {
|
||||
// Process no longer exists
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
const error = err as NodeJS.ErrnoException;
|
||||
if (error.code === 'ESRCH') {
|
||||
// Process doesn't exist
|
||||
removePidFile();
|
||||
return { success: true };
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to stop daemon: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Cursor Model Catalog
|
||||
*
|
||||
* Manages available models from Cursor IDE.
|
||||
* Based on Cursor's supported models as of Feb 2025.
|
||||
*/
|
||||
|
||||
import * as http from 'http';
|
||||
import type { CursorModel } from './types';
|
||||
|
||||
/**
|
||||
* Default models available through Cursor IDE.
|
||||
* Used as fallback when daemon is not reachable.
|
||||
* Source: Cursor IDE supported models (Feb 2025)
|
||||
*/
|
||||
export const DEFAULT_CURSOR_MODELS: CursorModel[] = [
|
||||
// Anthropic Models
|
||||
{
|
||||
id: 'claude-sonnet-4',
|
||||
name: 'Claude Sonnet 4',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-sonnet-4.5',
|
||||
name: 'Claude Sonnet 4.5',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
{
|
||||
id: 'claude-opus-4',
|
||||
name: 'Claude Opus 4',
|
||||
provider: 'anthropic',
|
||||
},
|
||||
|
||||
// OpenAI Models
|
||||
{
|
||||
id: 'gpt-4.1',
|
||||
name: 'GPT-4.1',
|
||||
provider: 'openai',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'gpt-5-mini',
|
||||
name: 'GPT-5 Mini',
|
||||
provider: 'openai',
|
||||
},
|
||||
{
|
||||
id: 'o3-mini',
|
||||
name: 'O3 Mini',
|
||||
provider: 'openai',
|
||||
},
|
||||
|
||||
// Google Models
|
||||
{
|
||||
id: 'gemini-2.5-pro',
|
||||
name: 'Gemini 2.5 Pro',
|
||||
provider: 'google',
|
||||
},
|
||||
|
||||
// Cursor Custom Models
|
||||
{
|
||||
id: 'cursor-small',
|
||||
name: 'Cursor Small',
|
||||
provider: 'cursor',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Fetch available models from running cursor daemon.
|
||||
*
|
||||
* @param port The port cursor daemon is running on
|
||||
* @returns List of available models
|
||||
*/
|
||||
export async function fetchModelsFromDaemon(port: number): Promise<CursorModel[]> {
|
||||
return new Promise((resolve) => {
|
||||
const req = http.request(
|
||||
{
|
||||
// Use 127.0.0.1 instead of localhost for more reliable local connections
|
||||
hostname: '127.0.0.1',
|
||||
port,
|
||||
path: '/v1/models',
|
||||
method: 'GET',
|
||||
timeout: 5000,
|
||||
},
|
||||
(res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const response = JSON.parse(data) as { data?: Array<{ id: string }> };
|
||||
if (response.data && Array.isArray(response.data)) {
|
||||
const models: CursorModel[] = response.data.map((m) => ({
|
||||
id: m.id,
|
||||
name: formatModelName(m.id),
|
||||
provider: detectProvider(m.id),
|
||||
isDefault: m.id === 'gpt-4.1',
|
||||
}));
|
||||
resolve(models.length > 0 ? models : DEFAULT_CURSOR_MODELS);
|
||||
} else {
|
||||
resolve(DEFAULT_CURSOR_MODELS);
|
||||
}
|
||||
} catch {
|
||||
resolve(DEFAULT_CURSOR_MODELS);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('error', () => {
|
||||
resolve(DEFAULT_CURSOR_MODELS);
|
||||
});
|
||||
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(DEFAULT_CURSOR_MODELS);
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available models (from daemon or defaults).
|
||||
*/
|
||||
export async function getAvailableModels(port: number): Promise<CursorModel[]> {
|
||||
return fetchModelsFromDaemon(port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default model.
|
||||
* Uses gpt-4.1 as it's commonly available.
|
||||
*/
|
||||
export function getDefaultModel(): string {
|
||||
return 'gpt-4.1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect provider from model ID.
|
||||
*/
|
||||
function detectProvider(modelId: string): string {
|
||||
if (modelId.includes('claude')) return 'anthropic';
|
||||
if (modelId.includes('gpt') || modelId.includes('o3')) return 'openai';
|
||||
if (modelId.includes('gemini')) return 'google';
|
||||
if (modelId.includes('cursor')) return 'cursor';
|
||||
return 'openai';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format model ID to human-readable name.
|
||||
*/
|
||||
function formatModelName(modelId: string): string {
|
||||
// Find model in catalog for metadata
|
||||
const model = DEFAULT_CURSOR_MODELS.find((m) => m.id === modelId);
|
||||
if (model) {
|
||||
return model.name;
|
||||
}
|
||||
|
||||
// Fallback: convert kebab-case to title case
|
||||
return modelId
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Cursor Module Index
|
||||
*
|
||||
* Central exports for Cursor IDE integration.
|
||||
*/
|
||||
|
||||
// Types
|
||||
export * from './types';
|
||||
|
||||
// Auth
|
||||
export {
|
||||
autoDetectTokens,
|
||||
saveCredentials,
|
||||
loadCredentials,
|
||||
checkAuthStatus,
|
||||
} from './cursor-auth';
|
||||
|
||||
// Daemon
|
||||
export { isDaemonRunning, getDaemonStatus, startDaemon, stopDaemon } from './cursor-daemon';
|
||||
|
||||
// Models
|
||||
export {
|
||||
DEFAULT_CURSOR_MODELS,
|
||||
fetchModelsFromDaemon,
|
||||
getAvailableModels,
|
||||
getDefaultModel,
|
||||
} from './cursor-models';
|
||||
|
||||
// Executor
|
||||
export { CursorExecutor } from './cursor-executor';
|
||||
Reference in New Issue
Block a user