Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
81f35ad
chore: Add Cursor CLI integration plan and phases
Shironex Dec 27, 2025
525c4c3
docs: Add Cursor CLI Integration Analysis document
Shironex Dec 27, 2025
2fae948
chore: remove pnpm lock file
Shironex Dec 27, 2025
8b1f597
feat: Add Cursor CLI types and models
Shironex Dec 27, 2025
d8dedf8
feat: Implement Cursor CLI Provider and Configuration Manager
Shironex Dec 27, 2025
6e9468a
feat: Integrate CursorProvider into ProviderFactory
Shironex Dec 27, 2025
5961223
feat: Add Cursor CLI configuration and status endpoints
Shironex Dec 27, 2025
6b03b3c
feat: Enhance log parser to support Cursor CLI events
Shironex Dec 27, 2025
22044bc
feat: Add Cursor setup step to UI setup wizard
Shironex Dec 28, 2025
c602314
feat: Implement Provider Tabs in Settings View
Shironex Dec 28, 2025
de11908
feat: Integrate Cursor provider support in AI profiles
Shironex Dec 28, 2025
c90f122
feat: Enhance AutoModeService and UI for Cursor model support
Shironex Dec 28, 2025
0bcc8fc
docs: Add guide for integrating new Cursor models in AutoMaker
Shironex Dec 28, 2025
b32eacc
feat: Enhance model resolution for Cursor models
Shironex Dec 28, 2025
52b1dc9
fix: ops mistake
Shironex Dec 28, 2025
e404262
feat: Add raw output logging and endpoint for debugging
Shironex Dec 28, 2025
f20053e
docs: Add comprehensive guides for integrating new Cursor models and …
Shironex Dec 28, 2025
de246bb
feat: Update Cursor model definitions and metadata
Shironex Dec 28, 2025
9900d54
refactor: Update default model configuration to use dynamic model IDs
Shironex Dec 28, 2025
3e8d2d7
feat: Enhance tool event handling in CursorProvider
Shironex Dec 28, 2025
1a37603
feat: Add WSL support for Cursor CLI on Windows
Shironex Dec 28, 2025
9c4f8f9
fix: Update label in BoardHeader component for clarity
Shironex Dec 28, 2025
f9882fe
fix: Update label in BoardHeader component for clarity
Shironex Dec 28, 2025
6c3d3aa
feat: Unify AI provider settings tabs with consistent design
Shironex Dec 29, 2025
fa23a7b
fix: Allow testing API keys without saving first
Shironex Dec 29, 2025
8e10f52
feat: Enhance Cursor model selection and profile handling
Shironex Dec 29, 2025
4115110
feat: Update ProfileForm to conditionally display enabled Cursor models
Shironex Dec 29, 2025
a842d1b
fix(tests): Update provider-factory tests for CursorProvider
Shironex Dec 29, 2025
35fa822
fix: Update section title in Claude setup step for clarity
Shironex Dec 29, 2025
b76f09d
refactor(types): Use ModelProvider type instead of hardcoded union
Shironex Dec 29, 2025
55bd9b0
refactor(cursor): Move stream dedup logic to CursorProvider
Shironex Dec 29, 2025
dc8c06e
feat(providers): Add provider registry pattern
Shironex Dec 29, 2025
677f441
feat(providers): Create CliProvider abstract base class
Shironex Dec 30, 2025
4157e11
refactor(cursor): Extend CliProvider base class
Shironex Dec 30, 2025
c1c2e70
chore: Remove temporary refactoring analysis document
Shironex Dec 30, 2025
a415ae6
feat(types): Add PhaseModelConfig for per-phase AI model selection
Shironex Dec 30, 2025
2ba1149
feat(ui): Add Phase Models settings tab
Shironex Dec 30, 2025
3d655c3
feat(ui): Add ModelOverrideTrigger component for quick model overrides
Shironex Dec 30, 2025
39f2c8c
feat(ui): Enhance AI model handling with Cursor support
Shironex Dec 30, 2025
45d93f2
fix(server): Improve Cursor CLI JSON response parsing
Shironex Dec 30, 2025
24599e0
feat(server): Add settings migration for phaseModels
Shironex Dec 30, 2025
2be0e7d
fix(ui): Use phaseModels.validationModel instead of legacy field
Shironex Dec 30, 2025
ac0d4a5
feat(server): Wire describe-file to use phaseModels.fileDescriptionModel
Shironex Dec 30, 2025
f43e90f
feat(server): Wire describe-image to use phaseModels.imageDescription…
Shironex Dec 30, 2025
4d69d04
feat(server): Wire generate-spec to use phaseModels.specGenerationModel
Shironex Dec 30, 2025
fcba327
feat(server): Wire generate-features-from-spec to use phaseModels.fea…
Shironex Dec 30, 2025
0798a64
feat(server): Wire generate-plan to use phaseModels.backlogPlanningModel
Shironex Dec 30, 2025
fd7c22a
feat(server): Wire analyzeProject to use phaseModels.projectAnalysisM…
Shironex Dec 30, 2025
d6a1c08
fix(ui): Sync phaseModels to server when changed
Shironex Dec 30, 2025
68cefe4
fix(ui): Add phaseModels to localStorage persistence
Shironex Dec 30, 2025
34e51dd
feat(server): Add Cursor provider support for describe-file and descr…
Shironex Dec 30, 2025
ed66fdd
fix(cursor): Pass prompt via stdin to avoid shell escaping issues
Shironex Dec 30, 2025
efd9a1b
feat(suggestions): Wire to phaseModels.enhancementModel with Cursor s…
Shironex Dec 30, 2025
26e4ac0
fix(suggestions): Improve JSON extraction for Cursor responses
Shironex Dec 30, 2025
19016f0
refactor(server): Extract JSON extraction utility to shared module
Shironex Dec 30, 2025
38d0e41
feat(server): Add Cursor provider routing to spec generation routes
Shironex Dec 30, 2025
b0f83b7
feat(server): Add readOnly mode to Cursor provider for read-only oper…
Shironex Dec 30, 2025
948fdb6
refactor(server): Use shared JSON extractor in feature and plan parsing
Shironex Dec 30, 2025
078ab94
fix(server): Add explicit JSON response instructions for Cursor prompts
Shironex Dec 30, 2025
dac9164
feat(server): Implement Cursor CLI permissions management
Shironex Dec 30, 2025
3c6736b
feat(cursor): Enhance Cursor tool handling with a registry and improv…
Shironex Dec 30, 2025
853292a
refactor(cursor): seperate components and add permissions skeleton
Shironex Dec 30, 2025
d539f7e
Merge origin/main into feat/cursor-cli
Shironex Dec 31, 2025
3bc4b7f
fix: update qs to 6.14.1 to fix high severity DoS vulnerability
Shironex Dec 31, 2025
5c400b7
fix(server): Fix unit tests and increase coverage
Shironex Dec 31, 2025
9653e2b
refactor(settings): remove AI Enhancement section and related components
Shironex Dec 31, 2025
f496bb8
feat(agent-view): refactor to folder pattern and add Cursor model sup…
Shironex Dec 31, 2025
f56b873
Merge main into feat/cursor-cli-integration
Shironex Jan 1, 2026
17dae15
fix(tests): update terminal-service tests to work cross-platform
Shironex Jan 1, 2026
7dec5d9
fix(sdk-options): normalize paths for cross-platform cloud storage de…
Shironex Jan 1, 2026
aa31809
feat(ui): add model override trigger to backlog plan dialog
Shironex Jan 1, 2026
207fd26
feat(backlog-plan): add model override trigger to footer
Shironex Jan 1, 2026
9b11744
fix(backlog-plan): extract result text from Cursor provider
Shironex Jan 1, 2026
cf9a1f9
fix(backlog-plan): use extractJsonWithArray and improve logging
Shironex Jan 1, 2026
0e22098
feat(backlog-plan): add detailed logging for Cursor result extraction
Shironex Jan 1, 2026
3b3e61d
fix(backlog-plan): add Cursor-specific prompt with no-file-write inst…
Shironex Jan 1, 2026
63b9f52
refactor(cursor-models): remove tier property from Cursor model confi…
Shironex Jan 1, 2026
cbe951d
fix(suggestions): extract result text from Cursor provider
Shironex Jan 1, 2026
83e59d6
feat(phase-model-selector): enhance model selection with favorites an…
Shironex Jan 1, 2026
ad94769
feat: enhance TaskProgressPanel and AgentOutputModal components
Shironex Jan 1, 2026
e1bdb4c
Merge remote-tracking branch 'origin/main' into feat/cursor-cli
Shironex Jan 2, 2026
914734c
feat(phase-model-selector): implement grouped model selection and enh…
Shironex Jan 2, 2026
81d3003
feat: enhance SDK options with thinking level support
Shironex Jan 2, 2026
8c04e00
feat: integrate thinking level support across agent and UI components
Shironex Jan 2, 2026
96a9998
feat: implement structured logging across server components
Shironex Jan 2, 2026
69f3ba9
feat: standardize logging across UI components
Shironex Jan 2, 2026
2b942a6
feat: integrate thinking level support across various components
Shironex Jan 2, 2026
3e95a11
refactor: replace logger with console statements in subprocess manage…
Shironex Jan 2, 2026
4a28b70
feat: enhance query options with maxThinkingTokens support
Shironex Jan 2, 2026
e72f7d1
fix: libs test
Shironex Jan 2, 2026
8e1a9ad
fix: update logger test expectations to include log level prefixes
Shironex Jan 2, 2026
3c8ee5b
chore: update .prettierignore and format vite.config.mts
Shironex Jan 2, 2026
9071f89
chore: remove obsolete Cursor CLI integration documentation
Shironex Jan 2, 2026
abed3b3
feat: enhance use-model-override to support default phase models
Shironex Jan 3, 2026
35441c1
feat: add AI Suggestions phase model to settings view
Shironex Jan 3, 2026
7596ff9
fix: enable logger colors by default in Node.js subprocess environments
Shironex Jan 3, 2026
6d4f285
fix: scrolling issues in phase model selector
Shironex Jan 3, 2026
d13a161
feat: enhance suggestion generation with model and thinking level ove…
Shironex Jan 3, 2026
a6d665c
fix: update logger tests to use console.log instead of console.warn
Shironex Jan 3, 2026
816bf8f
Merge branch 'main' into feat/cursor-cli
Shironex Jan 3, 2026
ec6d36b
chore: format
Shironex Jan 3, 2026
88aba36
fix: improve Cursor CLI implementation with type safety and security …
Shironex Jan 3, 2026
3ed3a90
refactor: rename phase models to model defaults and reorganize compon…
Shironex Jan 3, 2026
ef06c13
feat: implement timeout for plan approval and enhance error handling
Shironex Jan 4, 2026
c6d94d4
fix: improve abort handling in spawnJSONLProcess
Shironex Jan 4, 2026
078f107
Merge v0.8.0rc into feat/cursor-cli
Shironex Jan 4, 2026
f90cd61
fix: remove MCP permission settings references removed in v0.8.0rc
Shironex Jan 4, 2026
4a41dbb
style: fix formatting in auto-mode-service.ts
Shironex Jan 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pnpm-lock.yaml
# Generated files
*.min.js
*.min.css
routeTree.gen.ts
apps/ui/src/routeTree.gen.ts

# Test artifacts
test-results/
Expand Down
12 changes: 12 additions & 0 deletions apps/server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,15 @@ TERMINAL_ENABLED=true
TERMINAL_PASSWORD=

ENABLE_REQUEST_LOGGING=false

# ============================================
# OPTIONAL - Debugging
# ============================================

# Enable raw output logging for agent streams (default: false)
# When enabled, saves unprocessed stream events to raw-output.jsonl
# in each feature's directory (.automaker/features/{id}/raw-output.jsonl)
# Useful for debugging provider streaming issues, improving log parsing,
# or analyzing how different providers (Claude, Cursor) stream responses
# Note: This adds disk I/O overhead, only enable when debugging
AUTOMAKER_DEBUG_RAW_OUTPUT=false
62 changes: 31 additions & 31 deletions apps/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import dotenv from 'dotenv';

import { createEventEmitter, type EventEmitter } from './lib/events.js';
import { initAllowedPaths } from '@automaker/platform';
import { createLogger } from '@automaker/utils';

const logger = createLogger('Server');
import { authMiddleware, validateWsConnectionToken, checkRawAuthentication } from './lib/auth.js';
import { requireJsonContentType } from './middleware/require-json-content-type.js';
import { createAuthRoutes } from './routes/auth/index.js';
Expand Down Expand Up @@ -72,7 +75,7 @@ const ENABLE_REQUEST_LOGGING = process.env.ENABLE_REQUEST_LOGGING !== 'false'; /
const hasAnthropicKey = !!process.env.ANTHROPIC_API_KEY;

if (!hasAnthropicKey) {
console.warn(`
logger.warn(`
╔═══════════════════════════════════════════════════════════════════════╗
║ ⚠️ WARNING: No Claude authentication configured ║
║ ║
Expand All @@ -85,7 +88,7 @@ if (!hasAnthropicKey) {
╚═══════════════════════════════════════════════════════════════════════╝
`);
} else {
console.log('[Server] ✓ ANTHROPIC_API_KEY detected (API key auth)');
logger.info('✓ ANTHROPIC_API_KEY detected (API key auth)');
}

// Initialize security
Expand Down Expand Up @@ -169,15 +172,15 @@ const ideationService = new IdeationService(events, settingsService, featureLoad
// Initialize services
(async () => {
await agentService.initialize();
console.log('[Server] Agent service initialized');
logger.info('Agent service initialized');
})();

// Run stale validation cleanup every hour to prevent memory leaks from crashed validations
const VALIDATION_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
setInterval(() => {
const cleaned = cleanupStaleValidations();
if (cleaned > 0) {
console.log(`[Server] Cleaned up ${cleaned} stale validation entries`);
logger.info(`Cleaned up ${cleaned} stale validation entries`);
}
}, VALIDATION_CLEANUP_INTERVAL_MS);

Expand Down Expand Up @@ -271,7 +274,7 @@ server.on('upgrade', (request, socket, head) => {

// Authenticate all WebSocket connections
if (!authenticateWebSocket(request)) {
console.log('[WebSocket] Authentication failed, rejecting connection');
logger.info('Authentication failed, rejecting connection');
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
socket.destroy();
return;
Expand All @@ -292,11 +295,11 @@ server.on('upgrade', (request, socket, head) => {

// Events WebSocket connection handler
wss.on('connection', (ws: WebSocket) => {
console.log('[WebSocket] Client connected, ready state:', ws.readyState);
logger.info('Client connected, ready state:', ws.readyState);

// Subscribe to all events and forward to this client
const unsubscribe = events.subscribe((type, payload) => {
console.log('[WebSocket] Event received:', {
logger.info('Event received:', {
type,
hasPayload: !!payload,
payloadKeys: payload ? Object.keys(payload) : [],
Expand All @@ -306,27 +309,24 @@ wss.on('connection', (ws: WebSocket) => {

if (ws.readyState === WebSocket.OPEN) {
const message = JSON.stringify({ type, payload });
console.log('[WebSocket] Sending event to client:', {
logger.info('Sending event to client:', {
type,
messageLength: message.length,
sessionId: (payload as any)?.sessionId,
});
ws.send(message);
} else {
console.log(
'[WebSocket] WARNING: Cannot send event, WebSocket not open. ReadyState:',
ws.readyState
);
logger.info('WARNING: Cannot send event, WebSocket not open. ReadyState:', ws.readyState);
}
});

ws.on('close', () => {
console.log('[WebSocket] Client disconnected');
logger.info('Client disconnected');
unsubscribe();
});

ws.on('error', (error) => {
console.error('[WebSocket] ERROR:', error);
logger.error('ERROR:', error);
unsubscribe();
});
});
Expand All @@ -353,37 +353,37 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage
const sessionId = url.searchParams.get('sessionId');
const token = url.searchParams.get('token');

console.log(`[Terminal WS] Connection attempt for session: ${sessionId}`);
logger.info(`Connection attempt for session: ${sessionId}`);

// Check if terminal is enabled
if (!isTerminalEnabled()) {
console.log('[Terminal WS] Terminal is disabled');
logger.info('Terminal is disabled');
ws.close(4003, 'Terminal access is disabled');
return;
}

// Validate token if password is required
if (isTerminalPasswordRequired() && !validateTerminalToken(token || undefined)) {
console.log('[Terminal WS] Invalid or missing token');
logger.info('Invalid or missing token');
ws.close(4001, 'Authentication required');
return;
}

if (!sessionId) {
console.log('[Terminal WS] No session ID provided');
logger.info('No session ID provided');
ws.close(4002, 'Session ID required');
return;
}

// Check if session exists
const session = terminalService.getSession(sessionId);
if (!session) {
console.log(`[Terminal WS] Session ${sessionId} not found`);
logger.info(`Session ${sessionId} not found`);
ws.close(4004, 'Session not found');
return;
}

console.log(`[Terminal WS] Client connected to session ${sessionId}`);
logger.info(`Client connected to session ${sessionId}`);

// Track this connection
if (!terminalConnections.has(sessionId)) {
Expand Down Expand Up @@ -499,15 +499,15 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage
break;

default:
console.warn(`[Terminal WS] Unknown message type: ${msg.type}`);
logger.warn(`Unknown message type: ${msg.type}`);
}
} catch (error) {
console.error('[Terminal WS] Error processing message:', error);
logger.error('Error processing message:', error);
}
});

ws.on('close', () => {
console.log(`[Terminal WS] Client disconnected from session ${sessionId}`);
logger.info(`Client disconnected from session ${sessionId}`);
unsubscribeData();
unsubscribeExit();

Expand All @@ -526,7 +526,7 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage
});

ws.on('error', (error) => {
console.error(`[Terminal WS] Error on session ${sessionId}:`, error);
logger.error(`Error on session ${sessionId}:`, error);
unsubscribeData();
unsubscribeExit();
});
Expand All @@ -541,7 +541,7 @@ const startServer = (port: number) => {
: 'enabled'
: 'disabled';
const portStr = port.toString().padEnd(4);
console.log(`
logger.info(`
╔═══════════════════════════════════════════════════════╗
║ Automaker Backend Server ║
╠═══════════════════════════════════════════════════════╣
Expand All @@ -556,7 +556,7 @@ const startServer = (port: number) => {

server.on('error', (error: NodeJS.ErrnoException) => {
if (error.code === 'EADDRINUSE') {
console.error(`
logger.error(`
╔═══════════════════════════════════════════════════════╗
║ ❌ ERROR: Port ${port} is already in use ║
╠═══════════════════════════════════════════════════════╣
Expand All @@ -576,7 +576,7 @@ const startServer = (port: number) => {
`);
process.exit(1);
} else {
console.error('[Server] Error starting server:', error);
logger.error('Error starting server:', error);
process.exit(1);
}
});
Expand All @@ -586,19 +586,19 @@ startServer(PORT);

// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down...');
logger.info('SIGTERM received, shutting down...');
terminalService.cleanup();
server.close(() => {
console.log('Server closed');
logger.info('Server closed');
process.exit(0);
});
});

process.on('SIGINT', () => {
console.log('SIGINT received, shutting down...');
logger.info('SIGINT received, shutting down...');
terminalService.cleanup();
server.close(() => {
console.log('Server closed');
logger.info('Server closed');
process.exit(0);
});
});
25 changes: 14 additions & 11 deletions apps/server/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import type { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
import path from 'path';
import * as secureFs from './secure-fs.js';
import { createLogger } from '@automaker/utils';

const logger = createLogger('Auth');

const DATA_DIR = process.env.DATA_DIR || './data';
const API_KEY_FILE = path.join(DATA_DIR, '.api-key');
Expand Down Expand Up @@ -61,11 +64,11 @@ function loadSessions(): void {
}

if (loadedCount > 0 || expiredCount > 0) {
console.log(`[Auth] Loaded ${loadedCount} sessions (${expiredCount} expired)`);
logger.info(`Loaded ${loadedCount} sessions (${expiredCount} expired)`);
}
}
} catch (error) {
console.warn('[Auth] Error loading sessions:', error);
logger.warn('Error loading sessions:', error);
}
}

Expand All @@ -81,7 +84,7 @@ async function saveSessions(): Promise<void> {
mode: 0o600,
});
} catch (error) {
console.error('[Auth] Failed to save sessions:', error);
logger.error('Failed to save sessions:', error);
}
}

Expand All @@ -95,7 +98,7 @@ loadSessions();
function ensureApiKey(): string {
// First check environment variable (Electron passes it this way)
if (process.env.AUTOMAKER_API_KEY) {
console.log('[Auth] Using API key from environment variable');
logger.info('Using API key from environment variable');
return process.env.AUTOMAKER_API_KEY;
}

Expand All @@ -104,22 +107,22 @@ function ensureApiKey(): string {
if (secureFs.existsSync(API_KEY_FILE)) {
const key = (secureFs.readFileSync(API_KEY_FILE, 'utf-8') as string).trim();
if (key) {
console.log('[Auth] Loaded API key from file');
logger.info('Loaded API key from file');
return key;
}
}
} catch (error) {
console.warn('[Auth] Error reading API key file:', error);
logger.warn('Error reading API key file:', error);
}

// Generate new key
const newKey = crypto.randomUUID();
try {
secureFs.mkdirSync(path.dirname(API_KEY_FILE), { recursive: true });
secureFs.writeFileSync(API_KEY_FILE, newKey, { encoding: 'utf-8', mode: 0o600 });
console.log('[Auth] Generated new API key');
logger.info('Generated new API key');
} catch (error) {
console.error('[Auth] Failed to save API key:', error);
logger.error('Failed to save API key:', error);
}
return newKey;
}
Expand All @@ -129,7 +132,7 @@ const API_KEY = ensureApiKey();

// Print API key to console for web mode users (unless suppressed for production logging)
if (process.env.AUTOMAKER_HIDE_API_KEY !== 'true') {
console.log(`
logger.info(`
╔═══════════════════════════════════════════════════════════════════════╗
║ 🔐 API Key for Web Mode Authentication ║
╠═══════════════════════════════════════════════════════════════════════╣
Expand All @@ -142,7 +145,7 @@ if (process.env.AUTOMAKER_HIDE_API_KEY !== 'true') {
╚═══════════════════════════════════════════════════════════════════════╝
`);
} else {
console.log('[Auth] API key banner hidden (AUTOMAKER_HIDE_API_KEY=true)');
logger.info('API key banner hidden (AUTOMAKER_HIDE_API_KEY=true)');
}

/**
Expand Down Expand Up @@ -177,7 +180,7 @@ export function validateSession(token: string): boolean {
if (Date.now() > session.expiresAt) {
validSessions.delete(token);
// Fire-and-forget: persist removal asynchronously
saveSessions().catch((err) => console.error('[Auth] Error saving sessions:', err));
saveSessions().catch((err) => logger.error('Error saving sessions:', err));
return false;
}

Expand Down
5 changes: 4 additions & 1 deletion apps/server/src/lib/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*/

import type { EventType, EventCallback } from '@automaker/types';
import { createLogger } from '@automaker/utils';

const logger = createLogger('Events');

// Re-export event types from shared package
export type { EventType, EventCallback };
Expand All @@ -21,7 +24,7 @@ export function createEventEmitter(): EventEmitter {
try {
callback(type, payload);
} catch (error) {
console.error('Error in event subscriber:', error);
logger.error('Error in event subscriber:', error);
}
}
},
Expand Down
Loading