devmentorai-server 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,6 +27,29 @@ npm install -g devmentorai-server
27
27
  devmentorai-server
28
28
  ```
29
29
 
30
+ ## Long-Running Usage (No Interruptions)
31
+
32
+ `npx devmentorai-server` is supported and starts the backend in background mode, but for long-running sessions (hours/days) you should use a process supervisor.
33
+
34
+ ### Option 1: Global install + PM2 (recommended)
35
+
36
+ ```bash
37
+ npm install -g devmentorai-server pm2
38
+ pm2 start "devmentorai-server start --foreground" --name devmentorai-server
39
+ pm2 save
40
+ pm2 status
41
+ ```
42
+
43
+ ### Option 2: systemd (Linux)
44
+
45
+ Create a user service that runs:
46
+
47
+ ```bash
48
+ devmentorai-server start --foreground
49
+ ```
50
+
51
+ Then enable restart policies (`Restart=always`) so the backend auto-recovers if it crashes.
52
+
30
53
  ## Commands
31
54
 
32
55
  | Command | Description |
@@ -100,6 +123,12 @@ The server runs as a background process and stores its data in `~/.devmentorai/`
100
123
  2. Check logs: `devmentorai-server logs`
101
124
  3. Try foreground mode: `devmentorai-server start -f`
102
125
 
126
+ ### Server stops unexpectedly
127
+
128
+ 1. Check the latest backend logs: `devmentorai-server logs --lines 200`
129
+ 2. Re-run in foreground to capture runtime errors directly: `devmentorai-server start -f`
130
+ 3. For continuous uptime, run under PM2 or systemd (see **Long-Running Usage**)
131
+
103
132
  ### Port already in use
104
133
 
105
134
  ```bash
@@ -1,10 +1,3 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // ../../packages/shared/src/contracts/api.contracts.js
9
2
  var DEFAULT_CONFIG = {
10
3
  DEFAULT_MODEL: "gpt-4.1",
@@ -65,6 +58,8 @@ var SESSION_TYPE_CONFIGS = {
65
58
  - Error diagnosis, log analysis, and troubleshooting
66
59
  - Architecture design and scalability patterns
67
60
 
61
+ IMPORTANT TOOL CAPABILITY: You have a tool called "fetch_url" available. Whenever a user provides a public URL in their request, you SHOULD PROACTIVELY use the fetch_url tool to read its contents and provide a more accurate, contextual response based on the actual content of that page. Do not assume you cannot read URLs - you have a tool specifically for this purpose.
62
+
68
63
  When analyzing configurations or errors:
69
64
  1. Identify the issue or configuration clearly
70
65
  2. Explain WHY something is problematic or recommended
@@ -72,7 +67,7 @@ When analyzing configurations or errors:
72
67
  4. Reference official documentation when helpful
73
68
  5. Warn about security implications when relevant
74
69
 
75
- Be concise but thorough. Use code blocks for configurations and commands.`
70
+ Provide a high-quality, detailed, and explanatory response that helps the user completely understand the concepts, steps, and 'why' behind the suggestions. Use code blocks for configurations and commands.`
76
71
  }
77
72
  },
78
73
  writing: {
@@ -99,7 +94,7 @@ Guidelines:
99
94
  3. Preserve formatting when rewriting
100
95
  4. For translations, keep cultural nuances in mind
101
96
  5. Provide alternatives when helpful
102
- 6. Be concise unless expansion is requested
97
+ 6. Provide rich, detailed explanations and fully expand on ideas when discussing or explaining writing choices.
103
98
 
104
99
  When the user provides text to modify, respond with ONLY the modified text unless they ask for explanation.`
105
100
  }
@@ -122,8 +117,10 @@ When the user provides text to modify, respond with ONLY the modified text unles
122
117
  - Documentation and code comments
123
118
  - Refactoring and code cleanup
124
119
 
120
+ IMPORTANT TOOL CAPABILITY: You have tools available to assist you. If you are in a devops session type, you have a tool called "fetch_url". Whenever a user provides a public URL in their request and you have the tool available, you SHOULD PROACTIVELY use the fetch_url tool to read its contents and provide a more accurate, contextual response. Do not assume you cannot read URLs.
121
+
125
122
  Guidelines:
126
- 1. Be concise and actionable
123
+ 1. Provide actionable and detailed explanatory responses that help the user completely understand the concepts, steps, and 'why' behind the suggestions.
127
124
  2. Explain the "why" behind suggestions
128
125
  3. Provide code examples when helpful
129
126
  4. Consider edge cases and error handling
@@ -296,10 +293,9 @@ ensureDir(IMAGES_DIR);
296
293
  ensureDir(LOG_DIR);
297
294
 
298
295
  // src/version.ts
299
- var BACKEND_VERSION = "1.2.2";
296
+ var BACKEND_VERSION = "1.3.0";
300
297
 
301
298
  export {
302
- __require,
303
299
  DATA_DIR,
304
300
  IMAGES_DIR,
305
301
  LOG_DIR,
@@ -324,4 +320,4 @@ export {
324
320
  checkForUpdate,
325
321
  BACKEND_VERSION
326
322
  };
327
- //# sourceMappingURL=chunk-APCRFDFH.js.map
323
+ //# sourceMappingURL=chunk-X4NL4LWR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../packages/shared/src/contracts/api.contracts.ts","../../../packages/shared/src/types/message.ts","../../../packages/shared/src/types/context.ts","../../../packages/shared/src/contracts/session-types.ts","../../../packages/shared/src/utils/helpers.ts","../../../packages/shared/src/utils/update-checker.ts","../src/lib/paths.ts","../src/version.ts"],"sourcesContent":["/**\n * API contract definitions for DevMentorAI\n * These define the endpoints and their request/response types\n */\n\nimport type {\n Session,\n CreateSessionRequest,\n UpdateSessionRequest,\n Message,\n SendMessageRequest,\n ApiResponse,\n PaginatedResponse,\n HealthResponse,\n ModelInfo,\n CopilotAuthStatus,\n CopilotQuotaStatus,\n} from '../types/index.js';\n\n/**\n * Backend API endpoints contract\n */\nexport const API_ENDPOINTS = {\n // Health\n HEALTH: '/api/health',\n \n // Sessions\n SESSIONS: '/api/sessions',\n SESSION: (id: string) => `/api/sessions/${id}`,\n SESSION_RESUME: (id: string) => `/api/sessions/${id}/resume`,\n SESSION_ABORT: (id: string) => `/api/sessions/${id}/abort`,\n SESSION_MESSAGES: (id: string) => `/api/sessions/${id}/messages`,\n \n // Chat\n CHAT: (sessionId: string) => `/api/sessions/${sessionId}/chat`,\n CHAT_STREAM: (sessionId: string) => `/api/sessions/${sessionId}/chat/stream`,\n \n // Image upload (pre-upload before sending chat)\n IMAGE_UPLOAD: (sessionId: string) => `/api/sessions/${sessionId}/images/upload`,\n\n // Models\n MODELS: '/api/models',\n\n // Account\n ACCOUNT_AUTH: '/api/account/auth',\n ACCOUNT_QUOTA: '/api/account/quota',\n} as const;\n\n/**\n * API endpoint type definitions\n */\nexport interface ApiEndpoints {\n // GET /api/health\n 'GET /api/health': {\n response: ApiResponse<HealthResponse>;\n };\n \n // GET /api/sessions\n 'GET /api/sessions': {\n response: ApiResponse<PaginatedResponse<Session>>;\n };\n \n // POST /api/sessions\n 'POST /api/sessions': {\n body: CreateSessionRequest;\n response: ApiResponse<Session>;\n };\n \n // GET /api/sessions/:id\n 'GET /api/sessions/:id': {\n params: { id: string };\n response: ApiResponse<Session>;\n };\n \n // PATCH /api/sessions/:id\n 'PATCH /api/sessions/:id': {\n params: { id: string };\n body: UpdateSessionRequest;\n response: ApiResponse<Session>;\n };\n \n // DELETE /api/sessions/:id\n 'DELETE /api/sessions/:id': {\n params: { id: string };\n response: ApiResponse<void>;\n };\n \n // POST /api/sessions/:id/resume\n 'POST /api/sessions/:id/resume': {\n params: { id: string };\n response: ApiResponse<Session>;\n };\n \n // POST /api/sessions/:id/abort\n 'POST /api/sessions/:id/abort': {\n params: { id: string };\n response: ApiResponse<void>;\n };\n \n // GET /api/sessions/:id/messages\n 'GET /api/sessions/:id/messages': {\n params: { id: string };\n response: ApiResponse<PaginatedResponse<Message>>;\n };\n \n // POST /api/sessions/:id/chat\n 'POST /api/sessions/:id/chat': {\n params: { id: string };\n body: SendMessageRequest;\n response: ApiResponse<Message>;\n };\n \n // POST /api/sessions/:id/chat/stream (SSE)\n 'POST /api/sessions/:id/chat/stream': {\n params: { id: string };\n body: SendMessageRequest;\n response: ReadableStream; // SSE stream\n };\n\n // POST /api/sessions/:id/images/upload\n 'POST /api/sessions/:id/images/upload': {\n params: { id: string };\n body: { images: Array<{ id: string; dataUrl: string; mimeType: string; source: string }> };\n response: ApiResponse<{ images: Array<{ id: string; thumbnailUrl: string; fullImageUrl: string; fullImagePath: string }> }>;\n };\n \n // GET /api/models\n 'GET /api/models': {\n response: ApiResponse<{ models: ModelInfo[]; default: string }>;\n };\n\n // GET /api/account/auth\n 'GET /api/account/auth': {\n response: ApiResponse<CopilotAuthStatus>;\n };\n\n // GET /api/account/quota\n 'GET /api/account/quota': {\n response: ApiResponse<CopilotQuotaStatus>;\n };\n}\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n DEFAULT_MODEL: 'gpt-4.1',\n DEFAULT_PORT: 3847,\n DEFAULT_HOST: 'localhost',\n REQUEST_TIMEOUT_MS: 60000,\n STREAM_TIMEOUT_MS: 300000,\n} as const;\n","/**\n * Message type definitions for DevMentorAI\n */\n\nexport type MessageRole = 'user' | 'assistant' | 'system';\n\nexport interface Message {\n id: string;\n sessionId: string;\n role: MessageRole;\n content: string;\n timestamp: string; // ISO date string\n metadata?: MessageMetadata;\n}\n\n// ============================================================================\n// Image Attachment Types\n// ============================================================================\n\nexport type ImageSource = 'screenshot' | 'paste' | 'drop';\nexport type ImageMimeType = 'image/png' | 'image/jpeg' | 'image/webp';\n\nexport interface ImageAttachment {\n id: string;\n source: ImageSource;\n mimeType: ImageMimeType;\n dimensions: { width: number; height: number };\n fileSize: number; // bytes\n timestamp: string; // ISO date string\n /** Original base64 data URL - only present in draft/request, not persisted */\n dataUrl?: string;\n /** Backend-provided thumbnail URL for display in chat history */\n thumbnailUrl?: string;\n /** Backend-provided full image URL for lightbox view */\n fullImageUrl?: string;\n}\n\n/** Image data sent in message request (before processing) */\nexport interface ImagePayload {\n id: string;\n dataUrl: string;\n mimeType: ImageMimeType;\n source: ImageSource;\n}\n\n/** Constants for image handling */\nexport const IMAGE_CONSTANTS = {\n MAX_IMAGES_PER_MESSAGE: 5,\n MAX_IMAGE_SIZE_BYTES: 5 * 1024 * 1024, // 5MB\n SUPPORTED_MIME_TYPES: ['image/png', 'image/jpeg', 'image/webp'] as const,\n THUMBNAIL_MAX_DIMENSION: 200,\n THUMBNAIL_QUALITY: 60,\n} as const;\n\n// ============================================================================\n// Message Metadata\n// ============================================================================\n\nexport interface MessageMetadata {\n pageUrl?: string;\n selectedText?: string;\n action?: QuickAction;\n toolCalls?: ToolCall[];\n streamComplete?: boolean;\n /** Attached images for this message */\n images?: ImageAttachment[];\n /** Whether context-aware mode was used */\n contextAware?: boolean;\n /** Stream or API error encountered during response */\n error?: string;\n}\n\nexport type QuickAction =\n | 'explain'\n | 'translate'\n | 'rewrite'\n | 'fix_grammar'\n | 'summarize'\n | 'expand'\n | 'analyze_config'\n | 'diagnose_error';\n\nexport interface ToolCall {\n toolName: string;\n toolCallId: string;\n status: 'pending' | 'running' | 'completed' | 'error';\n result?: unknown;\n}\n\nexport interface SendMessageRequest {\n prompt: string;\n context?: MessageContext;\n /** Full extracted context payload for context-aware mode */\n fullContext?: unknown; // ContextPayload - imported separately to avoid circular deps\n /** Whether to use context-aware mode */\n useContextAwareMode?: boolean;\n /** Images to attach to this message */\n images?: ImagePayload[];\n}\n\nexport interface MessageContext {\n pageUrl?: string;\n pageTitle?: string;\n selectedText?: string;\n action?: QuickAction;\n}\n\nexport interface StreamEvent {\n type: StreamEventType;\n data: StreamEventData;\n}\n\nexport type StreamEventType =\n | 'message_start'\n | 'message_delta'\n | 'message_complete'\n | 'tool_start'\n | 'tool_complete'\n | 'error'\n | 'done';\n\nexport interface StreamEventData {\n content?: string;\n deltaContent?: string;\n toolName?: string;\n toolCallId?: string;\n error?: string;\n messageId?: string;\n reason?: string; // Reason for completion (e.g., 'completed', 'timeout', 'idle_timeout')\n}\n","/**\n * Context-Aware Mentor Mode Types\n * \n * These types define the structured context payload that is extracted from\n * the browser and sent to the backend for context-aware AI responses.\n */\n\n// ============================================================================\n// Platform Detection Types\n// ============================================================================\n\nexport type PlatformType =\n | 'azure'\n | 'aws'\n | 'gcp'\n | 'github'\n | 'gitlab'\n | 'datadog'\n | 'newrelic'\n | 'grafana'\n | 'jenkins'\n | 'kubernetes'\n | 'docker'\n | 'generic';\n\nexport interface PlatformDetection {\n type: PlatformType;\n confidence: number; // 0-1\n indicators: string[];\n specificProduct?: string; // e.g., \"Azure DevOps\", \"AWS EC2\"\n specificContext?: Record<string, unknown>; // Phase 2: Platform-specific context\n}\n\n// ============================================================================\n// Error Detection Types\n// ============================================================================\n\nexport type ErrorType = 'error' | 'warning' | 'info';\nexport type ErrorSeverity = 'critical' | 'high' | 'medium' | 'low';\nexport type ErrorSource = 'console' | 'ui' | 'network' | 'dom';\n\nexport interface ExtractedError {\n type: ErrorType;\n message: string;\n source?: ErrorSource;\n severity: ErrorSeverity;\n context?: string; // Surrounding text\n element?: HTMLElementSnapshot;\n stackTrace?: string;\n}\n\nexport interface HTMLElementSnapshot {\n tagName: string;\n id?: string;\n className?: string;\n textContent?: string;\n attributes: Record<string, string>;\n}\n\n// ============================================================================\n// HTML Structure Types\n// ============================================================================\n\nexport type SectionPurpose =\n | 'error-container'\n | 'alert'\n | 'panel'\n | 'table'\n | 'form'\n | 'code-block'\n | 'modal'\n | 'generic';\n\nexport interface HTMLSection {\n purpose: SectionPurpose;\n outerHTML: string; // Truncated to ~500 chars\n textContent: string;\n attributes: Record<string, string>;\n xpath?: string;\n}\n\n// ============================================================================\n// Text Extraction Types\n// ============================================================================\n\nexport interface Heading {\n level: 1 | 2 | 3;\n text: string;\n xpath?: string;\n hierarchy?: string; // Parent > Child hierarchy\n}\n\nexport interface ConsoleLogs {\n errors: string[];\n warnings: string[];\n included: boolean;\n truncated: boolean;\n}\n\n// Phase 2: Console log capture type\nexport interface CapturedConsoleLog {\n level: 'log' | 'warn' | 'error' | 'info' | 'debug';\n message: string;\n timestamp: string;\n stackTrace?: string;\n}\n\n// Phase 2: Network error capture type\nexport interface CapturedNetworkError {\n url: string;\n method: string;\n status?: number;\n statusText?: string;\n errorMessage?: string;\n timestamp: string;\n}\n\nexport interface TextExtractionMetadata {\n totalLength: number;\n truncated: boolean;\n truncationReason?: string;\n}\n\n// ============================================================================\n// User Intent Types (DEPRECATED in Phase 3 - kept for backward compatibility)\n// ============================================================================\n\n/**\n * @deprecated Phase 3: The agent (LLM) now determines intent autonomously.\n * This type is kept for backward compatibility but fields are optional.\n */\nexport type IntentType = 'debug' | 'understand' | 'mentor' | 'help' | 'explain' | 'guide';\n\n/**\n * @deprecated Phase 3: Intent is no longer computed by the extension.\n * The agent interprets user intent directly from their message.\n */\nexport interface UserIntent {\n primary: IntentType;\n keywords: string[];\n explicitGoal?: string;\n implicitSignals: string[];\n}\n\n// ============================================================================\n// Session Context Types\n// ============================================================================\n\n/** Session type specifically for context-aware mode (extends base SessionType) */\nexport type ContextSessionType = 'devops' | 'debugging' | 'frontend' | 'general' | 'writing';\n\nexport interface ContextMessage {\n role: 'user' | 'assistant';\n content: string;\n timestamp: string;\n}\n\n// ============================================================================\n// Visual Context Types (Phase 3)\n// ============================================================================\n\nexport interface ScreenshotData {\n dataUrl: string; // base64 encoded image\n format: 'png' | 'jpeg';\n dimensions: { width: number; height: number };\n fileSize: number; // bytes\n quality?: number; // 0-100 for JPEG\n}\n\nexport interface VisualContext {\n screenshot?: ScreenshotData;\n supported: boolean; // Model supports vision\n included: boolean; // Screenshot actually captured\n reason?: string; // Why included/excluded\n}\n\n// ============================================================================\n// Privacy Types\n// ============================================================================\n\nexport interface PrivacyInfo {\n redactedFields: string[];\n sensitiveDataDetected: boolean;\n consentGiven: boolean;\n dataRetention: 'session' | 'none';\n privacyMaskingApplied?: boolean; // Whether privacy masking was applied\n sensitiveDataTypes?: string[]; // Types of data that were masked (e.g., 'email', 'token')\n}\n\n// ============================================================================\n// Main Context Payload\n// ============================================================================\n\nexport interface ContextPayload {\n // Core Metadata\n metadata: {\n captureTimestamp: string; // ISO 8601\n captureMode: 'auto' | 'manual';\n browserInfo: {\n userAgent: string;\n viewport: { width: number; height: number };\n language: string;\n };\n // Phase 2: Extended metadata\n phase2Features?: {\n consoleLogs: number;\n networkErrors: number;\n codeBlocks: number;\n tables: number;\n forms: number;\n hasModal: boolean;\n };\n // Extended features from context-extractor\n extendedFeatures?: {\n consoleLogs: number;\n networkErrors: number;\n runtimeErrors: number;\n codeBlocks: number;\n tables: number;\n forms: number;\n hasModal: boolean;\n uiState: string;\n privacyMaskingApplied: boolean;\n sensitiveDataTypesFound: string[];\n };\n };\n\n // Page Identity\n page: {\n url: string;\n urlParsed: {\n protocol: string;\n hostname: string;\n pathname: string;\n search: string;\n hash: string;\n };\n title: string;\n favicon?: string;\n platform: PlatformDetection;\n uiState?: Record<string, unknown>;\n };\n\n // Textual Context\n text: {\n selectedText?: string;\n visibleText: string;\n headings: Heading[];\n errors: ExtractedError[];\n logs?: ConsoleLogs;\n // Phase 2: Console and network error capture\n consoleLogs?: CapturedConsoleLog[];\n networkErrors?: CapturedNetworkError[];\n runtimeErrors?: Array<{\n message: string;\n source?: string;\n lineno?: number;\n colno?: number;\n stack?: string;\n timestamp: string;\n type?: string;\n }>;\n metadata: TextExtractionMetadata;\n };\n\n // Structural HTML Context\n structure: {\n relevantSections: HTMLSection[];\n errorContainers: HTMLSection[];\n // Phase 2: Extended structure extraction\n codeBlocks?: HTMLSection[];\n tables?: HTMLSection[];\n forms?: HTMLSection[];\n modal?: HTMLSection;\n activeElements: {\n focusedElement?: HTMLElementSnapshot;\n activeModals?: HTMLSection[];\n activePanels?: HTMLSection[];\n };\n metadata: {\n totalNodes: number;\n extractedNodes: number;\n relevanceScore: number; // 0-1 confidence\n };\n };\n\n // Visual Context (Optional - Phase 3)\n visual?: VisualContext;\n\n // Session Context\n session: {\n sessionId: string;\n sessionType: ContextSessionType;\n intent: UserIntent;\n previousMessages: {\n count: number;\n lastN: ContextMessage[];\n };\n userGoal?: string;\n };\n\n // Privacy & Safety\n privacy: PrivacyInfo;\n}\n\n// ============================================================================\n// Extraction Request/Response Types\n// ============================================================================\n\nexport interface ContextExtractionRequest {\n includeScreenshot?: boolean;\n maxTextLength?: number;\n maxHTMLSections?: number;\n selectedTextOnly?: boolean;\n}\n\nexport interface ContextExtractionResponse {\n success: boolean;\n context?: ContextPayload;\n error?: string;\n extractionTimeMs: number;\n}\n\n// ============================================================================\n// Size Limits Configuration\n// ============================================================================\n\nexport const CONTEXT_SIZE_LIMITS = {\n visibleText: 10000, // chars\n htmlSection: 500, // chars per section\n totalHTML: 5000, // chars total HTML\n headings: 50, // max headings\n errors: 20, // max errors\n consoleLogs: 100, // lines\n screenshot: 1024 * 1024, // 1MB\n selectedText: 5000, // chars\n} as const;\n","/**\n * Session type configurations with pre-defined agents\n */\n\nimport type { SessionType } from '../types/session.js';\n\nexport interface AgentConfig {\n name: string;\n displayName: string;\n description: string;\n prompt: string;\n}\n\nexport interface SessionTypeConfig {\n name: string;\n description: string;\n icon: string;\n agent: AgentConfig | null;\n defaultModel: string;\n}\n\nexport const SESSION_TYPE_CONFIGS: Record<SessionType, SessionTypeConfig> = {\n devops: {\n name: 'DevOps Mentor',\n description: 'Expert in DevOps, cloud infrastructure, and best practices',\n icon: '🛠️',\n defaultModel: 'gpt-4.1',\n agent: {\n name: 'devops-mentor',\n displayName: 'DevOps Mentor',\n description: 'Expert in DevOps, cloud infrastructure, and best practices',\n prompt: `You are a DevOps mentor and expert. You help users with:\n- AWS, Azure, GCP cloud services and best practices\n- Kubernetes, Docker, and container orchestration\n- CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins, CircleCI)\n- Infrastructure as Code (Terraform, Pulumi, CloudFormation, Ansible)\n- Security best practices and compliance\n- Cost optimization and resource management\n- Error diagnosis, log analysis, and troubleshooting\n- Architecture design and scalability patterns\n\nIMPORTANT TOOL CAPABILITY: You have a tool called \"fetch_url\" available. Whenever a user provides a public URL in their request, you SHOULD PROACTIVELY use the fetch_url tool to read its contents and provide a more accurate, contextual response based on the actual content of that page. Do not assume you cannot read URLs - you have a tool specifically for this purpose.\n\nWhen analyzing configurations or errors:\n1. Identify the issue or configuration clearly\n2. Explain WHY something is problematic or recommended\n3. Provide actionable steps to fix or improve\n4. Reference official documentation when helpful\n5. Warn about security implications when relevant\n\nProvide a high-quality, detailed, and explanatory response that helps the user completely understand the concepts, steps, and 'why' behind the suggestions. Use code blocks for configurations and commands.`,\n },\n },\n \n writing: {\n name: 'Writing Assistant',\n description: 'Helps with writing, rewriting, and translation',\n icon: '✍️',\n defaultModel: 'gpt-4.1',\n agent: {\n name: 'writing-assistant',\n displayName: 'Writing Assistant',\n description: 'Helps with writing, rewriting, and translation',\n prompt: `You are a professional writing assistant. You help users with:\n- Writing and composing emails (formal, casual, technical)\n- Rewriting text with different tones and styles\n- Grammar, spelling, and clarity improvements\n- Translation between languages (preserving tone and meaning)\n- Summarization and expansion of content\n- Technical documentation writing\n- Business communication and proposals\n\nGuidelines:\n1. Maintain the original meaning and intent\n2. Match the requested tone (formal, casual, friendly, technical)\n3. Preserve formatting when rewriting\n4. For translations, keep cultural nuances in mind\n5. Provide alternatives when helpful\n6. Provide rich, detailed explanations and fully expand on ideas when discussing or explaining writing choices.\n\nWhen the user provides text to modify, respond with ONLY the modified text unless they ask for explanation.`,\n },\n },\n \n development: {\n name: 'Development Helper',\n description: 'Assists with code review, debugging, and best practices',\n icon: '💻',\n defaultModel: 'gpt-4.1',\n agent: {\n name: 'dev-helper',\n displayName: 'Development Helper',\n description: 'Assists with code review, debugging, and best practices',\n prompt: `You are a senior software development assistant. You help users with:\n- Code review and improvement suggestions\n- Bug diagnosis and debugging strategies\n- Architecture decisions and design patterns\n- Performance optimization\n- Testing strategies and test writing\n- Documentation and code comments\n- Refactoring and code cleanup\n\nIMPORTANT TOOL CAPABILITY: You have tools available to assist you. If you are in a devops session type, you have a tool called \"fetch_url\". Whenever a user provides a public URL in their request and you have the tool available, you SHOULD PROACTIVELY use the fetch_url tool to read its contents and provide a more accurate, contextual response. Do not assume you cannot read URLs.\n\nGuidelines:\n1. Provide actionable and detailed explanatory responses that help the user completely understand the concepts, steps, and 'why' behind the suggestions.\n2. Explain the \"why\" behind suggestions\n3. Provide code examples when helpful\n4. Consider edge cases and error handling\n5. Suggest tests for critical changes\n6. Reference best practices and patterns\n\nWhen reviewing code, focus on:\n- Correctness and logic errors\n- Security vulnerabilities\n- Performance issues\n- Maintainability and readability\n- Missing error handling`,\n },\n },\n \n general: {\n name: 'General Assistant',\n description: 'General-purpose AI assistant',\n icon: '💬',\n defaultModel: 'gpt-4.1',\n agent: null, // Uses default Copilot behavior\n },\n};\n\n/**\n * Get the agent config for a session type\n */\nexport function getAgentConfig(type: SessionType): AgentConfig | null {\n return SESSION_TYPE_CONFIGS[type]?.agent ?? null;\n}\n\n/**\n * Get the default model for a session type\n */\nexport function getDefaultModel(type: SessionType): string {\n return SESSION_TYPE_CONFIGS[type]?.defaultModel ?? 'gpt-4.1';\n}\n","/**\n * Utility functions for DevMentorAI\n */\n\n/**\n * Generate a unique ID\n */\nexport function generateId(prefix?: string): string {\n const timestamp = Date.now().toString(36);\n const random = Math.random().toString(36).substring(2, 9);\n return prefix ? `${prefix}_${timestamp}${random}` : `${timestamp}${random}`;\n}\n\n/**\n * Generate a session ID\n */\nexport function generateSessionId(): string {\n return generateId('session');\n}\n\n/**\n * Generate a message ID\n */\nexport function generateMessageId(): string {\n return generateId('msg');\n}\n\n/**\n * Format a date as ISO string\n */\nexport function formatDate(date: Date = new Date()): string {\n return date.toISOString();\n}\n\n/**\n * Parse an ISO date string to Date\n */\nexport function parseDate(dateString: string): Date {\n return new Date(dateString);\n}\n\n/**\n * Truncate text to a maximum length\n */\nexport function truncate(text: string, maxLength: number, suffix = '...'): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength - suffix.length) + suffix;\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Retry a function with exponential backoff\n */\nexport async function retry<T>(\n fn: () => Promise<T>,\n options: {\n maxAttempts?: number;\n initialDelay?: number;\n maxDelay?: number;\n backoffFactor?: number;\n } = {}\n): Promise<T> {\n const {\n maxAttempts = 3,\n initialDelay = 1000,\n maxDelay = 30000,\n backoffFactor = 2,\n } = options;\n \n let lastError: Error | undefined;\n let delay = initialDelay;\n \n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n \n if (attempt === maxAttempts) break;\n \n await sleep(delay);\n delay = Math.min(delay * backoffFactor, maxDelay);\n }\n }\n \n throw lastError;\n}\n\n/**\n * Create an AbortController with timeout\n */\nexport function createTimeoutController(timeoutMs: number): AbortController {\n const controller = new AbortController();\n setTimeout(() => controller.abort(), timeoutMs);\n return controller;\n}\n","/**\n * Update checker for DevMentorAI components.\n * Queries GitHub Releases API to detect new versions.\n */\n\nexport type ComponentType = 'backend' | 'extension';\n\nexport interface UpdateInfo {\n hasUpdate: boolean;\n currentVersion: string;\n latestVersion: string;\n releaseUrl: string;\n downloadUrls: { chrome?: string; firefox?: string; npm?: string };\n publishedAt: string;\n}\n\ninterface GitHubRelease {\n tag_name: string;\n html_url: string;\n published_at: string;\n draft: boolean;\n prerelease: boolean;\n assets: Array<{\n name: string;\n browser_download_url: string;\n }>;\n}\n\nconst GITHUB_OWNER = 'BOTOOM';\nconst GITHUB_REPO = 'devmentorai';\nconst TAG_PREFIXES: Record<ComponentType, string> = {\n backend: 'backend-v',\n extension: 'ext-v',\n};\n\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\ninterface CacheEntry {\n data: UpdateInfo;\n timestamp: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\n\n/**\n * Compare two semver strings. Returns:\n * 1 if a > b, -1 if a < b, 0 if equal\n */\nexport function compareSemver(a: string, b: string): number {\n const pa = a.split('.').map(Number);\n const pb = b.split('.').map(Number);\n for (let i = 0; i < 3; i++) {\n const na = pa[i] ?? 0;\n const nb = pb[i] ?? 0;\n if (na > nb) return 1;\n if (na < nb) return -1;\n }\n return 0;\n}\n\n/**\n * Check if an update is available for a given component.\n * Uses GitHub Releases API with a 1-hour cache.\n *\n * @param component - 'backend' or 'extension'\n * @param currentVersion - Current version string (e.g. '1.0.0')\n * @param fetchFn - Optional fetch function (for environments where global fetch differs)\n */\nexport async function checkForUpdate(\n component: ComponentType,\n currentVersion: string,\n fetchFn: typeof fetch = globalThis.fetch,\n): Promise<UpdateInfo> {\n const cacheKey = `${component}:${currentVersion}`;\n const cached = cache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {\n return cached.data;\n }\n\n const noUpdate: UpdateInfo = {\n hasUpdate: false,\n currentVersion,\n latestVersion: currentVersion,\n releaseUrl: `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases`,\n downloadUrls: {},\n publishedAt: '',\n };\n\n try {\n const prefix = TAG_PREFIXES[component];\n const response = await fetchFn(\n `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`,\n {\n headers: { Accept: 'application/vnd.github.v3+json' },\n signal: AbortSignal.timeout(5000),\n },\n );\n\n if (!response.ok) {\n cache.set(cacheKey, { data: noUpdate, timestamp: Date.now() });\n return noUpdate;\n }\n\n const releases = (await response.json()) as GitHubRelease[];\n\n // Find the latest non-draft, non-prerelease release matching our tag prefix\n const matching = releases.find(\n (r) => r.tag_name.startsWith(prefix) && !r.draft && !r.prerelease,\n );\n\n if (!matching) {\n cache.set(cacheKey, { data: noUpdate, timestamp: Date.now() });\n return noUpdate;\n }\n\n const latestVersion = matching.tag_name.replace(prefix, '');\n const hasUpdate = compareSemver(latestVersion, currentVersion) > 0;\n\n const downloadUrls: UpdateInfo['downloadUrls'] = {};\n for (const asset of matching.assets) {\n if (asset.name.includes('chrome')) {\n downloadUrls.chrome = asset.browser_download_url;\n } else if (asset.name.includes('firefox')) {\n downloadUrls.firefox = asset.browser_download_url;\n }\n }\n\n if (component === 'backend') {\n downloadUrls.npm = `https://www.npmjs.com/package/devmentorai-server`;\n }\n\n const result: UpdateInfo = {\n hasUpdate,\n currentVersion,\n latestVersion,\n releaseUrl: matching.html_url,\n downloadUrls,\n publishedAt: matching.published_at,\n };\n\n cache.set(cacheKey, { data: result, timestamp: Date.now() });\n return result;\n } catch {\n cache.set(cacheKey, { data: noUpdate, timestamp: Date.now() });\n return noUpdate;\n }\n}\n\n/**\n * Clear the update check cache (useful for testing or forced refresh).\n */\nexport function clearUpdateCache(): void {\n cache.clear();\n}\n","/**\n * Cross-Platform Path Utilities\n * \n * Provides consistent path handling across Windows, macOS, and Linux.\n * Uses the same pattern as db/index.ts for data directory location.\n */\n\nimport path from 'node:path';\nimport os from 'node:os';\nimport fs from 'node:fs';\n\n/** Base data directory: ~/.devmentorai */\nexport const DATA_DIR = path.join(os.homedir(), '.devmentorai');\n\n/** Images directory: ~/.devmentorai/images */\nexport const IMAGES_DIR = path.join(DATA_DIR, 'images');\n\n/** Logs directory: ~/.devmentorai/logs */\nexport const LOG_DIR = path.join(DATA_DIR, 'logs');\n\n/** Server log file: ~/.devmentorai/logs/server.log */\nexport const LOG_FILE = path.join(LOG_DIR, 'server.log');\n\n/** PID file: ~/.devmentorai/server.pid */\nexport const PID_FILE = path.join(DATA_DIR, 'server.pid');\n\n/** Config file: ~/.devmentorai/config.json */\nexport const CONFIG_FILE = path.join(DATA_DIR, 'config.json');\n\n/**\n * Ensure a directory exists, creating it if necessary\n */\nexport function ensureDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true });\n }\n}\n\n/**\n * Get the directory path for a session's images\n * @param sessionId - The session ID\n * @returns Absolute path to the session's images directory\n */\nexport function getSessionImagesDir(sessionId: string): string {\n return path.join(IMAGES_DIR, sessionId);\n}\n\n/**\n * Get the directory path for a message's images\n * @param sessionId - The session ID\n * @param messageId - The message ID\n * @returns Absolute path to the message's images directory\n */\nexport function getMessageImagesDir(sessionId: string, messageId: string): string {\n return path.join(IMAGES_DIR, sessionId, messageId);\n}\n\n/**\n * Get the file path for a thumbnail image\n * @param sessionId - The session ID\n * @param messageId - The message ID\n * @param index - The image index (0-based)\n * @returns Absolute path to the thumbnail file\n */\nexport function getThumbnailPath(sessionId: string, messageId: string, index: number): string {\n return path.join(getMessageImagesDir(sessionId, messageId), `thumb_${index}.jpg`);\n}\n\n/**\n * Convert an absolute path to a relative path from DATA_DIR\n * This is used for storing portable paths in the database\n * @param absolutePath - The absolute file path\n * @returns Relative path from DATA_DIR\n */\nexport function toRelativePath(absolutePath: string): string {\n return path.relative(DATA_DIR, absolutePath);\n}\n\n/**\n * Convert an absolute image path to a relative path from IMAGES_DIR\n * This is used for constructing image URLs\n * @param absolutePath - The absolute file path\n * @returns Relative path from IMAGES_DIR (e.g., \"session_xxx/msg_xxx/thumb_0.jpg\")\n */\nexport function toImageRelativePath(absolutePath: string): string {\n return path.relative(IMAGES_DIR, absolutePath);\n}\n\n/**\n * Convert a relative path (from DB) to an absolute path\n * @param relativePath - The relative path stored in DB\n * @returns Absolute file path\n */\nexport function toAbsolutePath(relativePath: string): string {\n return path.join(DATA_DIR, relativePath);\n}\n\n/**\n * Convert a file system path to a URL-safe path (always forward slashes)\n * @param fsPath - The file system path\n * @returns URL-safe path with forward slashes\n */\nexport function toUrlPath(fsPath: string): string {\n return fsPath.split(path.sep).join('/');\n}\n\n/**\n * Delete a directory and all its contents\n * @param dirPath - The directory to delete\n */\nexport function deleteDir(dirPath: string): void {\n if (fs.existsSync(dirPath)) {\n fs.rmSync(dirPath, { recursive: true, force: true });\n }\n}\n\n/**\n * Check if a file exists\n * @param filePath - The file path to check\n * @returns True if the file exists\n */\nexport function fileExists(filePath: string): boolean {\n return fs.existsSync(filePath);\n}\n\n/**\n * Get file stats if the file exists\n * @param filePath - The file path\n * @returns File stats or null if file doesn't exist\n */\nexport function getFileStats(filePath: string): fs.Stats | null {\n try {\n return fs.statSync(filePath);\n } catch {\n return null;\n }\n}\n\n// Initialize directories on module load\nensureDir(IMAGES_DIR);\nensureDir(LOG_DIR);\n","/**\n * Backend version - Single source of truth\n * This file is auto-updated by semantic-release\n */\nexport const BACKEND_VERSION = '1.3.0';\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -7,10 +7,9 @@ import {
7
7
  LOG_DIR,
8
8
  LOG_FILE,
9
9
  PID_FILE,
10
- __require,
11
10
  checkForUpdate,
12
11
  ensureDir
13
- } from "./chunk-APCRFDFH.js";
12
+ } from "./chunk-X4NL4LWR.js";
14
13
 
15
14
  // src/lib/daemon.ts
16
15
  import { fork } from "child_process";
@@ -151,6 +150,24 @@ async function showUpdateNotice() {
151
150
  } catch {
152
151
  }
153
152
  }
153
+ async function showAuthNotice(port) {
154
+ try {
155
+ const response = await fetch(`http://127.0.0.1:${port}/api/account/auth`);
156
+ if (!response.ok) return;
157
+ const payload = await response.json();
158
+ if (!payload.success) return;
159
+ if (payload.data?.isAuthenticated) {
160
+ if (payload.data.login) {
161
+ console.log(` Copilot authenticated as @${payload.data.login}`);
162
+ }
163
+ return;
164
+ }
165
+ console.log(" \u26A0 Copilot login required for real responses.");
166
+ console.log(" Run: github-copilot auth login");
167
+ console.log(" Or: copilot auth login\n");
168
+ } catch {
169
+ }
170
+ }
154
171
  async function startCommand(options) {
155
172
  const port = options.port || DEFAULT_PORT2;
156
173
  const status = await isServerRunning(port);
@@ -160,6 +177,8 @@ async function startCommand(options) {
160
177
  console.log(` \u2192 http://127.0.0.1:${port}`);
161
178
  console.log(` Health: ${status.healthy ? "\u2713 healthy" : "\u2717 unhealthy"}
162
179
  `);
180
+ await showAuthNotice(port);
181
+ await showUpdateNotice();
163
182
  return;
164
183
  }
165
184
  if (options.foreground) {
@@ -181,6 +200,7 @@ async function startCommand(options) {
181
200
  console.log(` \u2192 http://127.0.0.1:${port}`);
182
201
  console.log(` Logs: ${LOG_FILE}
183
202
  `);
203
+ await showAuthNotice(port);
184
204
  await showUpdateNotice();
185
205
  } else {
186
206
  console.error(`\u2717 Server started but healthcheck failed`);
@@ -280,16 +300,15 @@ import net from "net";
280
300
  var DEFAULT_PORT5 = DEFAULT_CONFIG.DEFAULT_PORT;
281
301
  async function doctorCommand() {
282
302
  console.log("\n\u{1F50D} DevMentorAI Server Doctor\n");
283
- const checks = [];
284
- checks.push(checkNodeVersion());
285
- checks.push(checkDataDirectory());
286
- checks.push(await checkPort(DEFAULT_PORT5));
287
- checks.push(checkCopilotCli());
288
- checks.push(checkNativeDep("better-sqlite3"));
289
- checks.push(checkNativeDep("sharp"));
303
+ const checks = [
304
+ checkNodeVersion(),
305
+ checkDataDirectory(),
306
+ await checkPort(DEFAULT_PORT5),
307
+ checkCopilotCli()
308
+ ];
290
309
  let hasFailure = false;
291
310
  for (const check of checks) {
292
- const icon = check.status === "pass" ? "\u2713" : check.status === "warn" ? "\u26A0" : "\u2717";
311
+ const icon = getStatusIcon(check.status);
293
312
  console.log(` ${icon} ${check.name}: ${check.message}`);
294
313
  if (check.status === "fail") hasFailure = true;
295
314
  }
@@ -301,9 +320,14 @@ async function doctorCommand() {
301
320
  console.log(" All checks passed! The server is ready to run.\n");
302
321
  }
303
322
  }
323
+ function getStatusIcon(status) {
324
+ if (status === "pass") return "\u2713";
325
+ if (status === "warn") return "\u26A0";
326
+ return "\u2717";
327
+ }
304
328
  function checkNodeVersion() {
305
329
  const version = process.version;
306
- const major = parseInt(version.slice(1).split(".")[0], 10);
330
+ const major = Number.parseInt(version.slice(1).split(".")[0], 10);
307
331
  if (major >= 20) {
308
332
  return { name: "Node.js", status: "pass", message: `${version} (>= 20 required)` };
309
333
  }
@@ -358,22 +382,6 @@ function checkCopilotCli() {
358
382
  return { name: "Copilot CLI", status: "warn", message: "Not found \u2014 server will run in mock mode" };
359
383
  }
360
384
  }
361
- function checkNativeDep(name) {
362
- try {
363
- __require.resolve(name);
364
- return { name, status: "pass", message: "installed" };
365
- } catch {
366
- try {
367
- const modulePath = `${process.cwd()}/node_modules/${name}`;
368
- if (fs3.existsSync(modulePath)) {
369
- return { name, status: "pass", message: "installed" };
370
- }
371
- return { name, status: "fail", message: "not found \u2014 run npm install" };
372
- } catch {
373
- return { name, status: "fail", message: "not found \u2014 run npm install" };
374
- }
375
- }
376
- }
377
385
 
378
386
  // src/cli.ts
379
387
  var VERSION = "1.0.0";
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/daemon.ts","../src/cli/start.ts","../src/cli/stop.ts","../src/cli/status.ts","../src/cli/logs.ts","../src/cli/doctor.ts","../src/cli.ts"],"sourcesContent":["/**\n * Daemon management utilities\n *\n * Handles PID file, process spawning, and healthcheck for the background server.\n */\n\nimport { fork, type ChildProcess } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport http from 'node:http';\nimport { PID_FILE, LOG_FILE, LOG_DIR, ensureDir } from './paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\n/** Write PID to file */\nexport function writePid(pid: number): void {\n fs.writeFileSync(PID_FILE, String(pid), 'utf-8');\n}\n\n/** Read PID from file, returns null if not found */\nexport function readPid(): number | null {\n try {\n const content = fs.readFileSync(PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\n/** Remove PID file */\nexport function removePid(): void {\n try {\n fs.unlinkSync(PID_FILE);\n } catch {\n // Ignore if file doesn't exist\n }\n}\n\n/** Check if a process with the given PID is running */\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** HTTP healthcheck against the running server */\nexport function healthcheck(port: number = DEFAULT_PORT, timeoutMs: number = 3000): Promise<{\n ok: boolean;\n data?: Record<string, unknown>;\n}> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: timeoutMs }, (res) => {\n let body = '';\n res.on('data', (chunk: Buffer) => { body += chunk.toString(); });\n res.on('end', () => {\n try {\n const data = JSON.parse(body);\n resolve({ ok: res.statusCode === 200, data });\n } catch {\n resolve({ ok: false });\n }\n });\n });\n\n req.on('error', () => resolve({ ok: false }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false });\n });\n });\n}\n\n/** Check if the server is already running (PID + healthcheck) */\nexport async function isServerRunning(port: number = DEFAULT_PORT): Promise<{\n running: boolean;\n pid: number | null;\n healthy: boolean;\n}> {\n const pid = readPid();\n\n if (pid && isProcessRunning(pid)) {\n const { ok } = await healthcheck(port);\n return { running: true, pid, healthy: ok };\n }\n\n // PID file exists but process is dead — clean up stale PID\n if (pid) {\n removePid();\n }\n\n // Maybe the server is running without our PID file (unlikely but check)\n const { ok } = await healthcheck(port);\n return { running: ok, pid: null, healthy: ok };\n}\n\n/** Spawn the server as a detached background process */\nexport function spawnServer(port: number = DEFAULT_PORT): ChildProcess {\n ensureDir(LOG_DIR);\n\n const logFd = fs.openSync(LOG_FILE, 'a');\n const serverEntry = path.resolve(path.dirname(new URL(import.meta.url).pathname), 'server.js');\n\n const child = fork(serverEntry, [], {\n detached: true,\n stdio: ['ignore', logFd, logFd, 'ipc'],\n env: {\n ...process.env,\n DEVMENTORAI_PORT: String(port),\n NODE_ENV: process.env.NODE_ENV || 'production',\n },\n });\n\n // Write PID file\n if (child.pid) {\n writePid(child.pid);\n }\n\n // Disconnect IPC so parent can exit\n child.unref();\n child.disconnect();\n\n fs.closeSync(logFd);\n\n return child;\n}\n\n/** Wait for the server to become healthy */\nexport async function waitForHealthy(port: number = DEFAULT_PORT, maxWaitMs: number = 10000): Promise<boolean> {\n const start = Date.now();\n const interval = 500;\n\n while (Date.now() - start < maxWaitMs) {\n const { ok } = await healthcheck(port);\n if (ok) return true;\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n return false;\n}\n\n/** Gracefully stop the server process */\nexport async function stopServer(): Promise<boolean> {\n const pid = readPid();\n if (!pid) return false;\n\n if (!isProcessRunning(pid)) {\n removePid();\n return false;\n }\n\n // Send SIGTERM for graceful shutdown\n process.kill(pid, 'SIGTERM');\n\n // Wait for process to exit\n const maxWait = 5000;\n const start = Date.now();\n while (Date.now() - start < maxWait) {\n if (!isProcessRunning(pid)) {\n removePid();\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n\n // Force kill if still running\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // Process might have exited between check and kill\n }\n removePid();\n return true;\n}\n","/**\n * CLI: start command\n * Starts the DevMentorAI server in background or foreground mode.\n */\n\nimport type { CliOptions } from '../cli.js';\nimport { isServerRunning, spawnServer, waitForHealthy } from '../lib/daemon.js';\nimport { LOG_FILE } from '../lib/paths.js';\nimport { DEFAULT_CONFIG, checkForUpdate } from '@devmentorai/shared';\nimport { BACKEND_VERSION } from '../version.js';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nasync function showUpdateNotice(): Promise<void> {\n try {\n const info = await checkForUpdate('backend', BACKEND_VERSION);\n if (info.hasUpdate) {\n console.log(`\\n ⚠ Update available: ${BACKEND_VERSION} → ${info.latestVersion}`);\n console.log(` Run: npx devmentorai-server@latest`);\n console.log(` Or: npm install -g devmentorai-server@latest`);\n console.log(` ${info.releaseUrl}\\n`);\n }\n } catch {\n // Silent — don't block startup for update check\n }\n}\n\nexport async function startCommand(options: CliOptions): Promise<void> {\n const port = options.port || DEFAULT_PORT;\n\n // Check if already running\n const status = await isServerRunning(port);\n if (status.running) {\n console.log(`\\n✓ DevMentorAI server is already running (PID: ${status.pid || 'unknown'})`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Health: ${status.healthy ? '✓ healthy' : '✗ unhealthy'}\\n`);\n return;\n }\n\n // Foreground mode — import and run server directly\n if (options.foreground) {\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port} (foreground)...\\n`);\n process.env.DEVMENTORAI_PORT = String(port);\n const { createServer } = await import('../server.js');\n const fastify = await createServer();\n await fastify.listen({ port, host: '0.0.0.0' });\n return;\n }\n\n // Background mode\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port}...`);\n\n spawnServer(port);\n\n // Wait for the server to become healthy\n const healthy = await waitForHealthy(port);\n\n if (healthy) {\n console.log(`✓ Server started successfully`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Logs: ${LOG_FILE}\\n`);\n await showUpdateNotice();\n } else {\n console.error(`✗ Server started but healthcheck failed`);\n console.error(` Check logs: ${LOG_FILE}\\n`);\n process.exit(1);\n }\n}\n","/**\n * CLI: stop command\n * Stops the running DevMentorAI server.\n */\n\nimport { isServerRunning, stopServer, readPid } from '../lib/daemon.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function stopCommand(): Promise<void> {\n const pid = readPid();\n const status = await isServerRunning(DEFAULT_PORT);\n\n if (!status.running) {\n console.log('\\n⊘ DevMentorAI server is not running.\\n');\n return;\n }\n\n console.log(`\\n⏳ Stopping DevMentorAI server (PID: ${pid || 'unknown'})...`);\n\n const stopped = await stopServer();\n\n if (stopped) {\n console.log('✓ Server stopped successfully.\\n');\n } else {\n console.error('✗ Failed to stop server. You may need to kill the process manually.\\n');\n process.exit(1);\n }\n}\n","/**\n * CLI: status command\n * Shows the current status of the DevMentorAI server.\n */\n\nimport { isServerRunning, readPid, healthcheck } from '../lib/daemon.js';\nimport { PID_FILE, LOG_FILE, DATA_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function statusCommand(): Promise<void> {\n const status = await isServerRunning(DEFAULT_PORT);\n\n console.log('\\n╔══════════════════════════════════════╗');\n console.log('║ DevMentorAI Server Status ║');\n console.log('╚══════════════════════════════════════╝\\n');\n\n if (!status.running) {\n console.log(' Status: ⊘ stopped');\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n return;\n }\n\n const pid = readPid();\n const health = await healthcheck(DEFAULT_PORT);\n\n console.log(` Status: ✓ running`);\n console.log(` PID: ${pid || 'unknown'}`);\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` URL: http://127.0.0.1:${DEFAULT_PORT}`);\n console.log(` Health: ${health.ok ? '✓ healthy' : '✗ unhealthy'}`);\n\n if (health.ok && health.data?.data) {\n const data = health.data.data as Record<string, unknown>;\n if (data.uptime) {\n console.log(` Uptime: ${formatUptime(data.uptime as number)}`);\n }\n if (data.copilotConnected !== undefined) {\n console.log(` Copilot: ${data.copilotConnected ? '✓ connected' : '⊘ disconnected (mock mode)'}`);\n }\n }\n\n console.log(` PID file: ${PID_FILE}`);\n console.log(` Logs: ${LOG_FILE}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n}\n\nfunction formatUptime(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;\n const hours = Math.floor(seconds / 3600);\n const mins = Math.floor((seconds % 3600) / 60);\n return `${hours}h ${mins}m`;\n}\n","/**\n * CLI: logs command\n * Tail the DevMentorAI server log file.\n */\n\nimport fs from 'node:fs';\nimport { LOG_FILE } from '../lib/paths.js';\nimport type { CliOptions } from '../cli.js';\n\nexport async function logsCommand(options: CliOptions): Promise<void> {\n const lines = options.lines || 50;\n\n if (!fs.existsSync(LOG_FILE)) {\n console.log(`\\n⊘ No log file found at ${LOG_FILE}`);\n console.log(' The server may not have been started yet.\\n');\n return;\n }\n\n const content = fs.readFileSync(LOG_FILE, 'utf-8');\n const allLines = content.split('\\n');\n const tailLines = allLines.slice(-lines).join('\\n');\n\n console.log(`\\n📋 Last ${lines} lines of ${LOG_FILE}:\\n`);\n console.log(tailLines);\n console.log('');\n}\n","/**\n * CLI: doctor command\n * Verifies system requirements and dependencies for DevMentorAI server.\n */\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport { DATA_DIR, LOG_DIR, IMAGES_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\ninterface CheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n}\n\nexport async function doctorCommand(): Promise<void> {\n console.log('\\n🔍 DevMentorAI Server Doctor\\n');\n\n const checks: CheckResult[] = [];\n\n // 1. Node.js version\n checks.push(checkNodeVersion());\n\n // 2. Data directory\n checks.push(checkDataDirectory());\n\n // 3. Port availability\n checks.push(await checkPort(DEFAULT_PORT));\n\n // 4. Copilot CLI\n checks.push(checkCopilotCli());\n\n // 5. Native dependencies\n checks.push(checkNativeDep('better-sqlite3'));\n checks.push(checkNativeDep('sharp'));\n\n // Display results\n let hasFailure = false;\n for (const check of checks) {\n const icon = check.status === 'pass' ? '✓' : check.status === 'warn' ? '⚠' : '✗';\n console.log(` ${icon} ${check.name}: ${check.message}`);\n if (check.status === 'fail') hasFailure = true;\n }\n\n console.log('');\n if (hasFailure) {\n console.log(' Some checks failed. Please fix the issues above.\\n');\n process.exit(1);\n } else {\n console.log(' All checks passed! The server is ready to run.\\n');\n }\n}\n\nfunction checkNodeVersion(): CheckResult {\n const version = process.version;\n const major = parseInt(version.slice(1).split('.')[0], 10);\n\n if (major >= 20) {\n return { name: 'Node.js', status: 'pass', message: `${version} (>= 20 required)` };\n }\n return { name: 'Node.js', status: 'fail', message: `${version} — Node.js >= 20 is required` };\n}\n\nfunction checkDataDirectory(): CheckResult {\n const dirs = [DATA_DIR, LOG_DIR, IMAGES_DIR];\n const missing: string[] = [];\n\n for (const dir of dirs) {\n if (!fs.existsSync(dir)) {\n missing.push(dir);\n }\n }\n\n if (missing.length === 0) {\n return { name: 'Data directory', status: 'pass', message: DATA_DIR };\n }\n\n // Try to create missing dirs\n try {\n for (const dir of missing) {\n fs.mkdirSync(dir, { recursive: true });\n }\n return { name: 'Data directory', status: 'pass', message: `${DATA_DIR} (created)` };\n } catch (err) {\n return { name: 'Data directory', status: 'fail', message: `Cannot create ${DATA_DIR}: ${err}` };\n }\n}\n\nfunction checkPort(port: number): Promise<CheckResult> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve({ name: `Port ${port}`, status: 'warn', message: `Port ${port} is in use (server may already be running)` });\n } else {\n resolve({ name: `Port ${port}`, status: 'fail', message: `Cannot bind port ${port}: ${err.message}` });\n }\n });\n server.once('listening', () => {\n server.close(() => {\n resolve({ name: `Port ${port}`, status: 'pass', message: `Port ${port} is available` });\n });\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\nfunction checkCopilotCli(): CheckResult {\n try {\n const version = execSync('github-copilot --version 2>/dev/null || copilot --version 2>/dev/null', {\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n return { name: 'Copilot CLI', status: 'pass', message: version || 'installed' };\n } catch {\n return { name: 'Copilot CLI', status: 'warn', message: 'Not found — server will run in mock mode' };\n }\n}\n\nfunction checkNativeDep(name: string): CheckResult {\n try {\n require.resolve(name);\n return { name, status: 'pass', message: 'installed' };\n } catch {\n // In ESM context, require.resolve may not work; try dynamic import\n try {\n // Check if the module exists in node_modules\n const modulePath = `${process.cwd()}/node_modules/${name}`;\n if (fs.existsSync(modulePath)) {\n return { name, status: 'pass', message: 'installed' };\n }\n return { name, status: 'fail', message: 'not found — run npm install' };\n } catch {\n return { name, status: 'fail', message: 'not found — run npm install' };\n }\n }\n}\n","/**\n * DevMentorAI Server CLI\n *\n * Usage:\n * devmentorai-server [command]\n *\n * Commands:\n * start Start the server (default)\n * stop Stop the running server\n * status Show server status\n * logs Tail server logs\n * doctor Check system requirements\n */\n\nimport { startCommand } from './cli/start.js';\nimport { stopCommand } from './cli/stop.js';\nimport { statusCommand } from './cli/status.js';\nimport { logsCommand } from './cli/logs.js';\nimport { doctorCommand } from './cli/doctor.js';\n\nconst VERSION = '1.0.0';\n\nconst HELP = `\ndevmentorai-server v${VERSION}\n\nUsage:\n devmentorai-server [command] [options]\n\nCommands:\n start Start the server in background (default)\n stop Stop the running server\n status Show server status\n logs Tail server logs\n doctor Check system requirements and dependencies\n\nOptions:\n --port <port> Port to listen on (default: 3847)\n --foreground, -f Run in foreground (don't daemonize)\n --help, -h Show this help message\n --version, -v Show version\n\nExamples:\n npx devmentorai-server # Start server in background\n npx devmentorai-server status # Check if server is running\n npx devmentorai-server stop # Stop the server\n npx devmentorai-server doctor # Verify system setup\n`;\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0] || 'start';\n\n if (args.includes('--help') || args.includes('-h')) {\n console.log(HELP);\n process.exit(0);\n }\n\n if (args.includes('--version') || args.includes('-v')) {\n console.log(VERSION);\n process.exit(0);\n }\n\n const options = parseOptions(args.slice(command === 'start' || command === 'stop' || command === 'status' || command === 'logs' || command === 'doctor' ? 1 : 0));\n\n try {\n switch (command) {\n case 'start':\n await startCommand(options);\n break;\n case 'stop':\n await stopCommand();\n break;\n case 'status':\n await statusCommand();\n break;\n case 'logs':\n await logsCommand(options);\n break;\n case 'doctor':\n await doctorCommand();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n console.log(HELP);\n process.exit(1);\n }\n } catch (error) {\n console.error(`\\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n}\n\nexport interface CliOptions {\n port?: number;\n foreground?: boolean;\n lines?: number;\n}\n\nfunction parseOptions(args: string[]): CliOptions {\n const options: CliOptions = {};\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--port':\n options.port = parseInt(args[++i], 10);\n if (isNaN(options.port)) {\n console.error('Error: --port requires a valid number');\n process.exit(1);\n }\n break;\n case '--foreground':\n case '-f':\n options.foreground = true;\n break;\n case '--lines':\n case '-n':\n options.lines = parseInt(args[++i], 10);\n break;\n }\n }\n\n return options;\n}\n\nmain();\n"],"mappings":"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","names":["ok","DEFAULT_PORT","DEFAULT_PORT","DEFAULT_PORT","fs","fs","fs","DEFAULT_PORT","fs"]}
1
+ {"version":3,"sources":["../src/lib/daemon.ts","../src/cli/start.ts","../src/cli/stop.ts","../src/cli/status.ts","../src/cli/logs.ts","../src/cli/doctor.ts","../src/cli.ts"],"sourcesContent":["/**\n * Daemon management utilities\n *\n * Handles PID file, process spawning, and healthcheck for the background server.\n */\n\nimport { fork, type ChildProcess } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport http from 'node:http';\nimport { PID_FILE, LOG_FILE, LOG_DIR, ensureDir } from './paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\n/** Write PID to file */\nexport function writePid(pid: number): void {\n fs.writeFileSync(PID_FILE, String(pid), 'utf-8');\n}\n\n/** Read PID from file, returns null if not found */\nexport function readPid(): number | null {\n try {\n const content = fs.readFileSync(PID_FILE, 'utf-8').trim();\n const pid = parseInt(content, 10);\n return isNaN(pid) ? null : pid;\n } catch {\n return null;\n }\n}\n\n/** Remove PID file */\nexport function removePid(): void {\n try {\n fs.unlinkSync(PID_FILE);\n } catch {\n // Ignore if file doesn't exist\n }\n}\n\n/** Check if a process with the given PID is running */\nexport function isProcessRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** HTTP healthcheck against the running server */\nexport function healthcheck(port: number = DEFAULT_PORT, timeoutMs: number = 3000): Promise<{\n ok: boolean;\n data?: Record<string, unknown>;\n}> {\n return new Promise((resolve) => {\n const req = http.get(`http://127.0.0.1:${port}/api/health`, { timeout: timeoutMs }, (res) => {\n let body = '';\n res.on('data', (chunk: Buffer) => { body += chunk.toString(); });\n res.on('end', () => {\n try {\n const data = JSON.parse(body);\n resolve({ ok: res.statusCode === 200, data });\n } catch {\n resolve({ ok: false });\n }\n });\n });\n\n req.on('error', () => resolve({ ok: false }));\n req.on('timeout', () => {\n req.destroy();\n resolve({ ok: false });\n });\n });\n}\n\n/** Check if the server is already running (PID + healthcheck) */\nexport async function isServerRunning(port: number = DEFAULT_PORT): Promise<{\n running: boolean;\n pid: number | null;\n healthy: boolean;\n}> {\n const pid = readPid();\n\n if (pid && isProcessRunning(pid)) {\n const { ok } = await healthcheck(port);\n return { running: true, pid, healthy: ok };\n }\n\n // PID file exists but process is dead — clean up stale PID\n if (pid) {\n removePid();\n }\n\n // Maybe the server is running without our PID file (unlikely but check)\n const { ok } = await healthcheck(port);\n return { running: ok, pid: null, healthy: ok };\n}\n\n/** Spawn the server as a detached background process */\nexport function spawnServer(port: number = DEFAULT_PORT): ChildProcess {\n ensureDir(LOG_DIR);\n\n const logFd = fs.openSync(LOG_FILE, 'a');\n const serverEntry = path.resolve(path.dirname(new URL(import.meta.url).pathname), 'server.js');\n\n const child = fork(serverEntry, [], {\n detached: true,\n stdio: ['ignore', logFd, logFd, 'ipc'],\n env: {\n ...process.env,\n DEVMENTORAI_PORT: String(port),\n NODE_ENV: process.env.NODE_ENV || 'production',\n },\n });\n\n // Write PID file\n if (child.pid) {\n writePid(child.pid);\n }\n\n // Disconnect IPC so parent can exit\n child.unref();\n child.disconnect();\n\n fs.closeSync(logFd);\n\n return child;\n}\n\n/** Wait for the server to become healthy */\nexport async function waitForHealthy(port: number = DEFAULT_PORT, maxWaitMs: number = 10000): Promise<boolean> {\n const start = Date.now();\n const interval = 500;\n\n while (Date.now() - start < maxWaitMs) {\n const { ok } = await healthcheck(port);\n if (ok) return true;\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n return false;\n}\n\n/** Gracefully stop the server process */\nexport async function stopServer(): Promise<boolean> {\n const pid = readPid();\n if (!pid) return false;\n\n if (!isProcessRunning(pid)) {\n removePid();\n return false;\n }\n\n // Send SIGTERM for graceful shutdown\n process.kill(pid, 'SIGTERM');\n\n // Wait for process to exit\n const maxWait = 5000;\n const start = Date.now();\n while (Date.now() - start < maxWait) {\n if (!isProcessRunning(pid)) {\n removePid();\n return true;\n }\n await new Promise((resolve) => setTimeout(resolve, 200));\n }\n\n // Force kill if still running\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // Process might have exited between check and kill\n }\n removePid();\n return true;\n}\n","/**\n * CLI: start command\n * Starts the DevMentorAI server in background or foreground mode.\n */\n\nimport type { CliOptions } from '../cli.js';\nimport { isServerRunning, spawnServer, waitForHealthy } from '../lib/daemon.js';\nimport { LOG_FILE } from '../lib/paths.js';\nimport { DEFAULT_CONFIG, checkForUpdate } from '@devmentorai/shared';\nimport { BACKEND_VERSION } from '../version.js';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nasync function showUpdateNotice(): Promise<void> {\n try {\n const info = await checkForUpdate('backend', BACKEND_VERSION);\n if (info.hasUpdate) {\n console.log(`\\n ⚠ Update available: ${BACKEND_VERSION} → ${info.latestVersion}`);\n console.log(` Run: npx devmentorai-server@latest`);\n console.log(` Or: npm install -g devmentorai-server@latest`);\n console.log(` ${info.releaseUrl}\\n`);\n }\n } catch {\n // Silent — don't block startup for update check\n }\n}\n\nasync function showAuthNotice(port: number): Promise<void> {\n try {\n const response = await fetch(`http://127.0.0.1:${port}/api/account/auth`);\n if (!response.ok) return;\n\n const payload = await response.json() as {\n success?: boolean;\n data?: { isAuthenticated?: boolean; login?: string | null };\n };\n\n if (!payload.success) return;\n\n if (payload.data?.isAuthenticated) {\n if (payload.data.login) {\n console.log(` Copilot authenticated as @${payload.data.login}`);\n }\n return;\n }\n\n console.log(' ⚠ Copilot login required for real responses.');\n console.log(' Run: github-copilot auth login');\n console.log(' Or: copilot auth login\\n');\n } catch {\n // Do not block startup when auth check is unavailable.\n }\n}\n\nexport async function startCommand(options: CliOptions): Promise<void> {\n const port = options.port || DEFAULT_PORT;\n\n // Check if already running\n const status = await isServerRunning(port);\n if (status.running) {\n console.log(`\\n✓ DevMentorAI server is already running (PID: ${status.pid || 'unknown'})`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Health: ${status.healthy ? '✓ healthy' : '✗ unhealthy'}\\n`);\n await showAuthNotice(port);\n await showUpdateNotice();\n return;\n }\n\n // Foreground mode — import and run server directly\n if (options.foreground) {\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port} (foreground)...\\n`);\n process.env.DEVMENTORAI_PORT = String(port);\n const { createServer } = await import('../server.js');\n const fastify = await createServer();\n await fastify.listen({ port, host: '0.0.0.0' });\n return;\n }\n\n // Background mode\n console.log(`\\n🚀 Starting DevMentorAI server on port ${port}...`);\n\n spawnServer(port);\n\n // Wait for the server to become healthy\n const healthy = await waitForHealthy(port);\n\n if (healthy) {\n console.log(`✓ Server started successfully`);\n console.log(` → http://127.0.0.1:${port}`);\n console.log(` Logs: ${LOG_FILE}\\n`);\n await showAuthNotice(port);\n await showUpdateNotice();\n } else {\n console.error(`✗ Server started but healthcheck failed`);\n console.error(` Check logs: ${LOG_FILE}\\n`);\n process.exit(1);\n }\n}\n","/**\n * CLI: stop command\n * Stops the running DevMentorAI server.\n */\n\nimport { isServerRunning, stopServer, readPid } from '../lib/daemon.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function stopCommand(): Promise<void> {\n const pid = readPid();\n const status = await isServerRunning(DEFAULT_PORT);\n\n if (!status.running) {\n console.log('\\n⊘ DevMentorAI server is not running.\\n');\n return;\n }\n\n console.log(`\\n⏳ Stopping DevMentorAI server (PID: ${pid || 'unknown'})...`);\n\n const stopped = await stopServer();\n\n if (stopped) {\n console.log('✓ Server stopped successfully.\\n');\n } else {\n console.error('✗ Failed to stop server. You may need to kill the process manually.\\n');\n process.exit(1);\n }\n}\n","/**\n * CLI: status command\n * Shows the current status of the DevMentorAI server.\n */\n\nimport { isServerRunning, readPid, healthcheck } from '../lib/daemon.js';\nimport { PID_FILE, LOG_FILE, DATA_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\nexport async function statusCommand(): Promise<void> {\n const status = await isServerRunning(DEFAULT_PORT);\n\n console.log('\\n╔══════════════════════════════════════╗');\n console.log('║ DevMentorAI Server Status ║');\n console.log('╚══════════════════════════════════════╝\\n');\n\n if (!status.running) {\n console.log(' Status: ⊘ stopped');\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n return;\n }\n\n const pid = readPid();\n const health = await healthcheck(DEFAULT_PORT);\n\n console.log(` Status: ✓ running`);\n console.log(` PID: ${pid || 'unknown'}`);\n console.log(` Port: ${DEFAULT_PORT}`);\n console.log(` URL: http://127.0.0.1:${DEFAULT_PORT}`);\n console.log(` Health: ${health.ok ? '✓ healthy' : '✗ unhealthy'}`);\n\n if (health.ok && health.data?.data) {\n const data = health.data.data as Record<string, unknown>;\n if (data.uptime) {\n console.log(` Uptime: ${formatUptime(data.uptime as number)}`);\n }\n if (data.copilotConnected !== undefined) {\n console.log(` Copilot: ${data.copilotConnected ? '✓ connected' : '⊘ disconnected (mock mode)'}`);\n }\n }\n\n console.log(` PID file: ${PID_FILE}`);\n console.log(` Logs: ${LOG_FILE}`);\n console.log(` Data: ${DATA_DIR}\\n`);\n}\n\nfunction formatUptime(seconds: number): string {\n if (seconds < 60) return `${seconds}s`;\n if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;\n const hours = Math.floor(seconds / 3600);\n const mins = Math.floor((seconds % 3600) / 60);\n return `${hours}h ${mins}m`;\n}\n","/**\n * CLI: logs command\n * Tail the DevMentorAI server log file.\n */\n\nimport fs from 'node:fs';\nimport { LOG_FILE } from '../lib/paths.js';\nimport type { CliOptions } from '../cli.js';\n\nexport async function logsCommand(options: CliOptions): Promise<void> {\n const lines = options.lines || 50;\n\n if (!fs.existsSync(LOG_FILE)) {\n console.log(`\\n⊘ No log file found at ${LOG_FILE}`);\n console.log(' The server may not have been started yet.\\n');\n return;\n }\n\n const content = fs.readFileSync(LOG_FILE, 'utf-8');\n const allLines = content.split('\\n');\n const tailLines = allLines.slice(-lines).join('\\n');\n\n console.log(`\\n📋 Last ${lines} lines of ${LOG_FILE}:\\n`);\n console.log(tailLines);\n console.log('');\n}\n","/**\n * CLI: doctor command\n * Verifies system requirements and dependencies for DevMentorAI server.\n */\n\nimport { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport net from 'node:net';\nimport { DATA_DIR, LOG_DIR, IMAGES_DIR } from '../lib/paths.js';\nimport { DEFAULT_CONFIG } from '@devmentorai/shared';\n\nconst DEFAULT_PORT = DEFAULT_CONFIG.DEFAULT_PORT;\n\ninterface CheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n}\n\nexport async function doctorCommand(): Promise<void> {\n console.log('\\n🔍 DevMentorAI Server Doctor\\n');\n\n const checks: CheckResult[] = [\n checkNodeVersion(),\n checkDataDirectory(),\n await checkPort(DEFAULT_PORT),\n checkCopilotCli(),\n ];\n\n // Display results\n let hasFailure = false;\n for (const check of checks) {\n const icon = getStatusIcon(check.status);\n console.log(` ${icon} ${check.name}: ${check.message}`);\n if (check.status === 'fail') hasFailure = true;\n }\n\n console.log('');\n if (hasFailure) {\n console.log(' Some checks failed. Please fix the issues above.\\n');\n process.exit(1);\n } else {\n console.log(' All checks passed! The server is ready to run.\\n');\n }\n}\n\nfunction getStatusIcon(status: CheckResult['status']): string {\n if (status === 'pass') return '✓';\n if (status === 'warn') return '⚠';\n return '✗';\n}\n\nfunction checkNodeVersion(): CheckResult {\n const version = process.version;\n const major = Number.parseInt(version.slice(1).split('.')[0], 10);\n\n if (major >= 20) {\n return { name: 'Node.js', status: 'pass', message: `${version} (>= 20 required)` };\n }\n return { name: 'Node.js', status: 'fail', message: `${version} — Node.js >= 20 is required` };\n}\n\nfunction checkDataDirectory(): CheckResult {\n const dirs = [DATA_DIR, LOG_DIR, IMAGES_DIR];\n const missing: string[] = [];\n\n for (const dir of dirs) {\n if (!fs.existsSync(dir)) {\n missing.push(dir);\n }\n }\n\n if (missing.length === 0) {\n return { name: 'Data directory', status: 'pass', message: DATA_DIR };\n }\n\n // Try to create missing dirs\n try {\n for (const dir of missing) {\n fs.mkdirSync(dir, { recursive: true });\n }\n return { name: 'Data directory', status: 'pass', message: `${DATA_DIR} (created)` };\n } catch (err) {\n return { name: 'Data directory', status: 'fail', message: `Cannot create ${DATA_DIR}: ${err}` };\n }\n}\n\nfunction checkPort(port: number): Promise<CheckResult> {\n return new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve({ name: `Port ${port}`, status: 'warn', message: `Port ${port} is in use (server may already be running)` });\n } else {\n resolve({ name: `Port ${port}`, status: 'fail', message: `Cannot bind port ${port}: ${err.message}` });\n }\n });\n server.once('listening', () => {\n server.close(() => {\n resolve({ name: `Port ${port}`, status: 'pass', message: `Port ${port} is available` });\n });\n });\n server.listen(port, '127.0.0.1');\n });\n}\n\nfunction checkCopilotCli(): CheckResult {\n try {\n const version = execSync('github-copilot --version 2>/dev/null || copilot --version 2>/dev/null', {\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n return { name: 'Copilot CLI', status: 'pass', message: version || 'installed' };\n } catch {\n return { name: 'Copilot CLI', status: 'warn', message: 'Not found — server will run in mock mode' };\n }\n}\n","/**\n * DevMentorAI Server CLI\n *\n * Usage:\n * devmentorai-server [command]\n *\n * Commands:\n * start Start the server (default)\n * stop Stop the running server\n * status Show server status\n * logs Tail server logs\n * doctor Check system requirements\n */\n\nimport { startCommand } from './cli/start.js';\nimport { stopCommand } from './cli/stop.js';\nimport { statusCommand } from './cli/status.js';\nimport { logsCommand } from './cli/logs.js';\nimport { doctorCommand } from './cli/doctor.js';\n\nconst VERSION = '1.0.0';\n\nconst HELP = `\ndevmentorai-server v${VERSION}\n\nUsage:\n devmentorai-server [command] [options]\n\nCommands:\n start Start the server in background (default)\n stop Stop the running server\n status Show server status\n logs Tail server logs\n doctor Check system requirements and dependencies\n\nOptions:\n --port <port> Port to listen on (default: 3847)\n --foreground, -f Run in foreground (don't daemonize)\n --help, -h Show this help message\n --version, -v Show version\n\nExamples:\n npx devmentorai-server # Start server in background\n npx devmentorai-server status # Check if server is running\n npx devmentorai-server stop # Stop the server\n npx devmentorai-server doctor # Verify system setup\n`;\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const command = args[0] || 'start';\n\n if (args.includes('--help') || args.includes('-h')) {\n console.log(HELP);\n process.exit(0);\n }\n\n if (args.includes('--version') || args.includes('-v')) {\n console.log(VERSION);\n process.exit(0);\n }\n\n const options = parseOptions(args.slice(command === 'start' || command === 'stop' || command === 'status' || command === 'logs' || command === 'doctor' ? 1 : 0));\n\n try {\n switch (command) {\n case 'start':\n await startCommand(options);\n break;\n case 'stop':\n await stopCommand();\n break;\n case 'status':\n await statusCommand();\n break;\n case 'logs':\n await logsCommand(options);\n break;\n case 'doctor':\n await doctorCommand();\n break;\n default:\n console.error(`Unknown command: ${command}`);\n console.log(HELP);\n process.exit(1);\n }\n } catch (error) {\n console.error(`\\n✗ Error: ${error instanceof Error ? error.message : String(error)}`);\n process.exit(1);\n }\n}\n\nexport interface CliOptions {\n port?: number;\n foreground?: boolean;\n lines?: number;\n}\n\nfunction parseOptions(args: string[]): CliOptions {\n const options: CliOptions = {};\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--port':\n options.port = parseInt(args[++i], 10);\n if (isNaN(options.port)) {\n console.error('Error: --port requires a valid number');\n process.exit(1);\n }\n break;\n case '--foreground':\n case '-f':\n options.foreground = true;\n break;\n case '--lines':\n case '-n':\n options.lines = parseInt(args[++i], 10);\n break;\n }\n }\n\n return options;\n}\n\nmain();\n"],"mappings":"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","names":["ok","DEFAULT_PORT","DEFAULT_PORT","DEFAULT_PORT","fs","fs","fs","DEFAULT_PORT","fs"]}