diff --git a/Dockerfile b/Dockerfile index c32b17644..70754825f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -142,6 +142,7 @@ RUN chmod +x /usr/local/bin/docker-entrypoint.sh # Environment variables ENV PORT=3008 +ENV HOST=0.0.0.0 ENV DATA_DIR=/data ENV HOME=/home/automaker # Add user's local bin to PATH for cursor-agent diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 755569de8..0196ead78 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -538,21 +538,25 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage // Start server with error handling for port conflicts const startServer = (port: number) => { - server.listen(port, () => { + // Default to localhost for security, use HOST=0.0.0.0 for Docker (fixes #428) + const host = process.env.HOST || 'localhost'; + server.listen(port, host, () => { const terminalStatus = isTerminalEnabled() ? isTerminalPasswordRequired() ? 'enabled (password protected)' : 'enabled' : 'disabled'; const portStr = port.toString().padEnd(4); + // Display localhost for 0.0.0.0 binding (accessible via localhost) + const displayHost = host === '0.0.0.0' ? 'localhost' : host; logger.info(` ╔═══════════════════════════════════════════════════════╗ ║ Automaker Backend Server ║ ╠═══════════════════════════════════════════════════════╣ -║ HTTP API: http://localhost:${portStr} ║ -║ WebSocket: ws://localhost:${portStr}/api/events ║ -║ Terminal: ws://localhost:${portStr}/api/terminal/ws ║ -║ Health: http://localhost:${portStr}/api/health ║ +║ HTTP API: http://${displayHost}:${portStr} ║ +║ WebSocket: ws://${displayHost}:${portStr}/api/events ║ +║ Terminal: ws://${displayHost}:${portStr}/api/terminal/ws ║ +║ Health: http://${displayHost}:${portStr}/api/health ║ ║ Terminal: ${terminalStatus.padEnd(37)}║ ╚═══════════════════════════════════════════════════════╝ `); diff --git a/apps/server/src/routes/setup/routes/deauth-claude.ts b/apps/server/src/routes/setup/routes/deauth-claude.ts index 8f3c19308..00c107571 100644 --- a/apps/server/src/routes/setup/routes/deauth-claude.ts +++ b/apps/server/src/routes/setup/routes/deauth-claude.ts @@ -3,24 +3,29 @@ */ import type { Request, Response } from 'express'; -import { getErrorMessage, logError } from '../common.js'; -import * as fs from 'fs'; +import { logError } from '../common.js'; +import * as fs from 'fs/promises'; import * as path from 'path'; +// Marker file location must be consistent with: +// - get-claude-status.ts (reads from .automaker/) +// - auth-claude.ts (deletes from .automaker/) +// - provider-factory.ts (checks in .automaker/) +const AUTOMAKER_DIR = '.automaker'; +const DISCONNECTED_MARKER = '.claude-disconnected'; + export function createDeauthClaudeHandler() { return async (_req: Request, res: Response): Promise => { try { - // Create a marker file to indicate the CLI is disconnected from the app - const automakerDir = path.join(process.cwd(), '.automaker'); - const markerPath = path.join(automakerDir, '.claude-disconnected'); + const projectRoot = process.cwd(); + const automakerDir = path.join(projectRoot, AUTOMAKER_DIR); + const markerPath = path.join(automakerDir, DISCONNECTED_MARKER); // Ensure .automaker directory exists - if (!fs.existsSync(automakerDir)) { - fs.mkdirSync(automakerDir, { recursive: true }); - } + await fs.mkdir(automakerDir, { recursive: true }); // Create the marker file with timestamp - fs.writeFileSync( + await fs.writeFile( markerPath, JSON.stringify({ disconnectedAt: new Date().toISOString(), @@ -34,10 +39,22 @@ export function createDeauthClaudeHandler() { }); } catch (error) { logError(error, 'Deauth Claude failed'); + + // Return generic error to client (security: don't expose paths) + // Detailed diagnostics are in server logs + const nodeError = error as NodeJS.ErrnoException; + let userMessage = 'Failed to disconnect Claude CLI'; + if (nodeError.code === 'EACCES') { + userMessage = 'Permission denied. Check directory permissions.'; + } else if (nodeError.code === 'ENOSPC') { + userMessage = 'No space left on device.'; + } else if (nodeError.code === 'EROFS') { + userMessage = 'Read-only filesystem. Check volume mounts.'; + } + res.status(500).json({ success: false, - error: getErrorMessage(error), - message: 'Failed to disconnect Claude CLI from the app', + error: userMessage, }); } }; diff --git a/apps/server/src/services/ideation-service.ts b/apps/server/src/services/ideation-service.ts index 81fc3de6b..21fced6bc 100644 --- a/apps/server/src/services/ideation-service.ts +++ b/apps/server/src/services/ideation-service.ts @@ -1682,18 +1682,19 @@ Focus on practical, implementable suggestions that would genuinely improve the p * Used by both idea-to-feature conversion and suggestion-to-feature conversion. */ mapSuggestionCategoryToFeatureCategory(category: IdeaCategory): string { + // Use capitalized categories to match existing conventions (fixes #396) const mapping: Record = { - feature: 'ui', - 'ux-ui': 'enhancement', - dx: 'chore', - growth: 'feature', - technical: 'refactor', - security: 'bug', - performance: 'enhancement', - accessibility: 'enhancement', - analytics: 'feature', + feature: 'Feature', + 'ux-ui': 'UI', + dx: 'DX', + growth: 'Growth', + technical: 'Technical', + security: 'Security', + performance: 'Performance', + accessibility: 'Accessibility', + analytics: 'Analytics', }; - return mapping[category] || 'feature'; + return mapping[category] || 'Feature'; } private async saveSessionToDisk( diff --git a/apps/server/tests/unit/services/ideation-service.test.ts b/apps/server/tests/unit/services/ideation-service.test.ts index 346fe4426..984a63027 100644 --- a/apps/server/tests/unit/services/ideation-service.test.ts +++ b/apps/server/tests/unit/services/ideation-service.test.ts @@ -531,7 +531,7 @@ describe('IdeationService', () => { expect(feature.id).toMatch(/^feature-/); expect(feature.title).toBe('Add Dark Mode'); expect(feature.description).toBe('Implement dark mode theme'); - expect(feature.category).toBe('ui'); // features -> ui mapping + expect(feature.category).toBe('Feature'); // features -> Feature mapping (capitalized, fixes #396) expect(feature.status).toBe('backlog'); }); diff --git a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx index 5124f7afa..b9d183c70 100644 --- a/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx @@ -401,7 +401,7 @@ export function AgentOutputModal({ )} ) : effectiveViewMode === 'summary' && summary ? ( -
+
{summary}
) : ( @@ -409,7 +409,7 @@ export function AgentOutputModal({
{isLoading && !output ? (
@@ -423,7 +423,7 @@ export function AgentOutputModal({ ) : effectiveViewMode === 'parsed' ? ( ) : ( -
{output}
+
{output}
)}