@documentation-robotics/cli 0.1.5 → 0.1.8

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.
@@ -58,13 +58,15 @@ class StdioClient {
58
58
  * Initialize the analyzer with client information
59
59
  *
60
60
  * Sends an initialize request and waits for the response.
61
- * Times out after 2 seconds if no response is received.
61
+ * Times out after 5 seconds if no response is received. Was 2 seconds,
62
+ * but that became too tight for slower analyzer binary cold starts
63
+ * (observed failing in CI against codebase-memory-mcp 0.10.x - see #802).
62
64
  *
63
65
  * @param clientInfo Client information object
64
66
  * @throws Error if initialize times out or fails
65
67
  */
66
68
  async initialize(clientInfo) {
67
- return this.callTool("initialize", clientInfo, { timeout: 2e3 });
69
+ return this.callTool("initialize", clientInfo, { timeout: 5e3 });
68
70
  }
69
71
  /**
70
72
  * Call a tool on the analyzer
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/analyzers/stdio-client.ts"],
4
- "sourcesContent": ["/**\n * MCP JSON-RPC 2.0 stdio client\n *\n * Minimal implementation for communicating with analyzers via JSON-RPC over stdio.\n * Handles process spawning, request/response correlation, timeouts, and clean shutdown.\n *\n * Protocol: JSON-RPC 2.0 with line-delimited JSON framing.\n * - Each request/response is a complete JSON object on a single line\n * - Request IDs are integers for correlation\n * - Errors are extracted from JSON-RPC error response objects\n */\n\nimport { spawn, ChildProcess } from \"child_process\";\n\n/**\n * JSON-RPC 2.0 request structure\n */\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n id: number;\n}\n\n/**\n * JSON-RPC notification (no id field)\n */\ninterface JsonRpcNotification {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n}\n\n/**\n * MCP client for communicating with analyzer backends via JSON-RPC 2.0 stdio\n */\nexport class StdioClient {\n private proc: ChildProcess | null = null;\n private requestId: number = 0;\n private pendingRequests: Map<number, { resolve: (value: unknown) => void; reject: (reason: Error) => void; timeout: NodeJS.Timeout }> = new Map();\n\n /**\n * Spawn an analyzer process\n *\n * @param binaryPath Path to the analyzer binary\n * @param args Optional command-line arguments (or env object for backwards compatibility)\n * @param env Optional environment variables (merged with process.env)\n * @throws Error if spawn fails\n */\n spawn(binaryPath: string, args?: string[] | Record<string, string>, env?: Record<string, string>): void {\n if (this.proc) {\n throw new Error(\"Process already spawned\");\n }\n\n // Handle backwards compatibility: if args is an object, treat it as env\n let actualArgs: string[] = [];\n let actualEnv = process.env as Record<string, string>;\n\n if (Array.isArray(args)) {\n actualArgs = args;\n actualEnv = { ...actualEnv, ...env };\n } else if (args && typeof args === \"object\") {\n actualEnv = { ...actualEnv, ...args };\n }\n\n this.proc = spawn(binaryPath, actualArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: actualEnv,\n });\n\n // Set up stdout listener for responses\n if (this.proc.stdout) {\n this.proc.stdout.setEncoding(\"utf-8\");\n let buffer = \"\";\n\n this.proc.stdout.on(\"data\", (chunk: string) => {\n buffer += chunk;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const message = JSON.parse(line);\n this.handleMessage(message);\n } catch (error) {\n // If we have pending requests, reject the oldest one since it likely\n // sent the request that produced this invalid response\n const firstKey = this.pendingRequests.keys().next().value;\n if (firstKey !== undefined) {\n const pending = this.pendingRequests.get(firstKey);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(firstKey);\n pending.reject(new Error(`Failed to parse JSON-RPC response from analyzer: ${line}`));\n }\n } else {\n // No pending requests, just log the error\n console.error(`Failed to parse JSON-RPC message from analyzer: ${line}`);\n }\n }\n }\n });\n }\n\n // Set up stderr listener\n if (this.proc.stderr) {\n this.proc.stderr.setEncoding(\"utf-8\");\n this.proc.stderr.on(\"data\", (chunk: string) => {\n console.error(`[Analyzer stderr] ${chunk}`);\n });\n }\n\n // Handle process exit\n this.proc.on(\"exit\", (code: number | null, signal: string | null) => {\n this.proc = null;\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(`Process exited with code ${code} (signal: ${signal})`));\n }\n this.pendingRequests.clear();\n });\n\n this.proc.on(\"error\", (error: Error) => {\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(error);\n }\n this.pendingRequests.clear();\n });\n }\n\n /**\n * Initialize the analyzer with client information\n *\n * Sends an initialize request and waits for the response.\n * Times out after 2 seconds if no response is received.\n *\n * @param clientInfo Client information object\n * @throws Error if initialize times out or fails\n */\n async initialize(clientInfo: Record<string, unknown>): Promise<unknown> {\n return this.callTool(\"initialize\", clientInfo, { timeout: 2000 });\n }\n\n /**\n * Call a tool on the analyzer\n *\n * Sends a JSON-RPC request and waits for the response.\n * Defaults to 30-second timeout for graph queries.\n *\n * @param name Tool name (method)\n * @param params Tool parameters\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Tool result\n * @throws Error if tool call fails or times out\n */\n async callTool(\n name: string,\n params?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n if (!this.proc) {\n throw new Error(\"Process not spawned\");\n }\n\n const timeout = options?.timeout ?? 30000;\n const id = ++this.requestId;\n\n const request: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n method: name,\n params,\n id,\n };\n\n return new Promise((resolve, reject) => {\n const timeoutHandle = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Tool call '${name}' timed out after ${timeout}ms`));\n }, timeout);\n\n this.pendingRequests.set(id, { resolve, reject, timeout: timeoutHandle });\n\n try {\n const line = JSON.stringify(request) + \"\\n\";\n // Explicit null checks to prevent race condition with exit handler\n if (!this.proc || !this.proc.stdin) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(new Error(\"Process or stdin closed before write could complete\"));\n return;\n }\n\n if (!this.proc.stdin.write(line)) {\n // Write returned false (backpressure), but data is already in the buffer.\n // No action needed \u2014 the data will be flushed automatically.\n this.proc.stdin.once(\"drain\", () => {\n // Drain event listener registered but no action needed\n });\n }\n } catch (error) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(error);\n }\n });\n }\n\n /**\n * Close the connection and clean up\n *\n * Sends a shutdown notification and terminates the process.\n * Sends SIGTERM first, then escalates to SIGKILL after 1 second if needed.\n * Cleans up all pending requests and event listeners.\n */\n\n /**\n * Invoke a tool on the analyzer via the MCP tools/call protocol\n *\n * Sends a tools/call request with {name, arguments} and unwraps the\n * MCP content envelope from the response (content[0].text parsed as JSON).\n *\n * Use this for all tool invocations. Use callTool() only for MCP protocol\n * methods (initialize, tools/list).\n *\n * @param name Tool name (e.g., \"search_graph\", \"index_repository\")\n * @param args Tool arguments to pass as the arguments field\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Parsed tool result unwrapped from MCP content envelope\n */\n async invokeTool(\n name: string,\n args?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n const response = await this.callTool(\n \"tools/call\",\n { name, arguments: args ?? {} },\n options\n ) as { content?: Array<{ type: string; text?: string }>; isError?: boolean };\n\n // Unwrap MCP tools/call envelope: result is JSON-encoded in content[0].text\n if (response?.content && Array.isArray(response.content) && response.content.length > 0) {\n const first = response.content[0];\n if (first.type === \"text\" && typeof first.text === \"string\") {\n try {\n return JSON.parse(first.text);\n } catch {\n return first.text;\n }\n }\n }\n return response;\n }\n\n close(): void {\n if (!this.proc) {\n return;\n }\n\n const procToClose = this.proc;\n this.proc = null;\n\n // Send shutdown notification\n try {\n const notification: JsonRpcNotification = {\n jsonrpc: \"2.0\",\n method: \"shutdown\",\n };\n const line = JSON.stringify(notification) + \"\\n\";\n procToClose.stdin?.write(line);\n } catch (error) {\n // Ignore write errors during shutdown\n }\n\n // Clean up pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(\"Client closed\"));\n }\n this.pendingRequests.clear();\n\n // Remove listeners\n if (procToClose.stdout) {\n procToClose.stdout.removeAllListeners();\n }\n if (procToClose.stderr) {\n procToClose.stderr.removeAllListeners();\n }\n procToClose.removeAllListeners();\n\n // Terminate process with SIGKILL fallback\n try {\n procToClose.stdin?.end();\n if (!procToClose.killed) {\n procToClose.kill(\"SIGTERM\");\n\n // Schedule SIGKILL fallback after 1 second if process doesn't exit\n const killTimer = setTimeout(() => {\n // Check if process has actually exited (exitCode is non-null once exit event fires)\n if (procToClose.exitCode === null) {\n try {\n procToClose.kill(\"SIGKILL\");\n } catch {\n // Process may have already exited\n }\n }\n }, 1000);\n\n // Unreference the timer so it doesn't keep the event loop alive\n killTimer.unref();\n }\n } catch (error) {\n // Ignore errors during shutdown\n }\n }\n\n /**\n * Handle a JSON-RPC message from the analyzer\n *\n * Matches responses to pending requests by ID and resolves/rejects accordingly.\n * Extracts error information from JSON-RPC error objects.\n *\n * @param message Parsed JSON-RPC message\n */\n private handleMessage(message: unknown): void {\n if (!message || typeof message !== \"object\") {\n return;\n }\n\n const msg = message as Record<string, unknown>;\n\n // Ignore notifications (no id field)\n if (!(\"id\" in msg) || typeof msg.id !== \"number\") {\n return;\n }\n\n const msgId = msg.id as number;\n const pending = this.pendingRequests.get(msgId);\n\n if (!pending) {\n // Log diagnostic information for unmatched response IDs\n console.warn(\n `Received response with unmatched ID ${msgId} (type: ${typeof msgId}). ` +\n `Pending request IDs: [${Array.from(this.pendingRequests.keys()).join(\", \")}]. ` +\n `This may indicate a type mismatch or stale response.`\n );\n return;\n }\n\n const { resolve, reject, timeout } = pending;\n clearTimeout(timeout);\n this.pendingRequests.delete(msgId);\n\n // Check for error response\n if (\"error\" in msg && msg.error !== null && typeof msg.error === \"object\") {\n const error = msg.error as Record<string, unknown>;\n const errMessage = (error.message as string) || \"Unknown error\";\n const code = (error.code as number) ?? -1;\n const data = error.data;\n\n const errorMessage = data ? `${errMessage}: ${JSON.stringify(data)}` : errMessage;\n reject(new Error(`[Error ${code}] ${errorMessage}`));\n return;\n }\n\n // Success response\n if (\"result\" in msg) {\n resolve(msg.result);\n return;\n }\n\n // Invalid response\n reject(new Error(\"Invalid JSON-RPC response: no result or error field\"));\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * MCP JSON-RPC 2.0 stdio client\n *\n * Minimal implementation for communicating with analyzers via JSON-RPC over stdio.\n * Handles process spawning, request/response correlation, timeouts, and clean shutdown.\n *\n * Protocol: JSON-RPC 2.0 with line-delimited JSON framing.\n * - Each request/response is a complete JSON object on a single line\n * - Request IDs are integers for correlation\n * - Errors are extracted from JSON-RPC error response objects\n */\n\nimport { spawn, ChildProcess } from \"child_process\";\n\n/**\n * JSON-RPC 2.0 request structure\n */\ninterface JsonRpcRequest {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n id: number;\n}\n\n/**\n * JSON-RPC notification (no id field)\n */\ninterface JsonRpcNotification {\n jsonrpc: \"2.0\";\n method: string;\n params?: unknown;\n}\n\n/**\n * MCP client for communicating with analyzer backends via JSON-RPC 2.0 stdio\n */\nexport class StdioClient {\n private proc: ChildProcess | null = null;\n private requestId: number = 0;\n private pendingRequests: Map<number, { resolve: (value: unknown) => void; reject: (reason: Error) => void; timeout: NodeJS.Timeout }> = new Map();\n\n /**\n * Spawn an analyzer process\n *\n * @param binaryPath Path to the analyzer binary\n * @param args Optional command-line arguments (or env object for backwards compatibility)\n * @param env Optional environment variables (merged with process.env)\n * @throws Error if spawn fails\n */\n spawn(binaryPath: string, args?: string[] | Record<string, string>, env?: Record<string, string>): void {\n if (this.proc) {\n throw new Error(\"Process already spawned\");\n }\n\n // Handle backwards compatibility: if args is an object, treat it as env\n let actualArgs: string[] = [];\n let actualEnv = process.env as Record<string, string>;\n\n if (Array.isArray(args)) {\n actualArgs = args;\n actualEnv = { ...actualEnv, ...env };\n } else if (args && typeof args === \"object\") {\n actualEnv = { ...actualEnv, ...args };\n }\n\n this.proc = spawn(binaryPath, actualArgs, {\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: actualEnv,\n });\n\n // Set up stdout listener for responses\n if (this.proc.stdout) {\n this.proc.stdout.setEncoding(\"utf-8\");\n let buffer = \"\";\n\n this.proc.stdout.on(\"data\", (chunk: string) => {\n buffer += chunk;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.trim()) continue;\n\n try {\n const message = JSON.parse(line);\n this.handleMessage(message);\n } catch (error) {\n // If we have pending requests, reject the oldest one since it likely\n // sent the request that produced this invalid response\n const firstKey = this.pendingRequests.keys().next().value;\n if (firstKey !== undefined) {\n const pending = this.pendingRequests.get(firstKey);\n if (pending) {\n clearTimeout(pending.timeout);\n this.pendingRequests.delete(firstKey);\n pending.reject(new Error(`Failed to parse JSON-RPC response from analyzer: ${line}`));\n }\n } else {\n // No pending requests, just log the error\n console.error(`Failed to parse JSON-RPC message from analyzer: ${line}`);\n }\n }\n }\n });\n }\n\n // Set up stderr listener\n if (this.proc.stderr) {\n this.proc.stderr.setEncoding(\"utf-8\");\n this.proc.stderr.on(\"data\", (chunk: string) => {\n console.error(`[Analyzer stderr] ${chunk}`);\n });\n }\n\n // Handle process exit\n this.proc.on(\"exit\", (code: number | null, signal: string | null) => {\n this.proc = null;\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(`Process exited with code ${code} (signal: ${signal})`));\n }\n this.pendingRequests.clear();\n });\n\n this.proc.on(\"error\", (error: Error) => {\n // Reject all pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(error);\n }\n this.pendingRequests.clear();\n });\n }\n\n /**\n * Initialize the analyzer with client information\n *\n * Sends an initialize request and waits for the response.\n * Times out after 5 seconds if no response is received. Was 2 seconds,\n * but that became too tight for slower analyzer binary cold starts\n * (observed failing in CI against codebase-memory-mcp 0.10.x - see #802).\n *\n * @param clientInfo Client information object\n * @throws Error if initialize times out or fails\n */\n async initialize(clientInfo: Record<string, unknown>): Promise<unknown> {\n return this.callTool(\"initialize\", clientInfo, { timeout: 5000 });\n }\n\n /**\n * Call a tool on the analyzer\n *\n * Sends a JSON-RPC request and waits for the response.\n * Defaults to 30-second timeout for graph queries.\n *\n * @param name Tool name (method)\n * @param params Tool parameters\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Tool result\n * @throws Error if tool call fails or times out\n */\n async callTool(\n name: string,\n params?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n if (!this.proc) {\n throw new Error(\"Process not spawned\");\n }\n\n const timeout = options?.timeout ?? 30000;\n const id = ++this.requestId;\n\n const request: JsonRpcRequest = {\n jsonrpc: \"2.0\",\n method: name,\n params,\n id,\n };\n\n return new Promise((resolve, reject) => {\n const timeoutHandle = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Tool call '${name}' timed out after ${timeout}ms`));\n }, timeout);\n\n this.pendingRequests.set(id, { resolve, reject, timeout: timeoutHandle });\n\n try {\n const line = JSON.stringify(request) + \"\\n\";\n // Explicit null checks to prevent race condition with exit handler\n if (!this.proc || !this.proc.stdin) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(new Error(\"Process or stdin closed before write could complete\"));\n return;\n }\n\n if (!this.proc.stdin.write(line)) {\n // Write returned false (backpressure), but data is already in the buffer.\n // No action needed \u2014 the data will be flushed automatically.\n this.proc.stdin.once(\"drain\", () => {\n // Drain event listener registered but no action needed\n });\n }\n } catch (error) {\n clearTimeout(timeoutHandle);\n this.pendingRequests.delete(id);\n reject(error);\n }\n });\n }\n\n /**\n * Close the connection and clean up\n *\n * Sends a shutdown notification and terminates the process.\n * Sends SIGTERM first, then escalates to SIGKILL after 1 second if needed.\n * Cleans up all pending requests and event listeners.\n */\n\n /**\n * Invoke a tool on the analyzer via the MCP tools/call protocol\n *\n * Sends a tools/call request with {name, arguments} and unwraps the\n * MCP content envelope from the response (content[0].text parsed as JSON).\n *\n * Use this for all tool invocations. Use callTool() only for MCP protocol\n * methods (initialize, tools/list).\n *\n * @param name Tool name (e.g., \"search_graph\", \"index_repository\")\n * @param args Tool arguments to pass as the arguments field\n * @param options Optional configuration (timeout in milliseconds)\n * @returns Parsed tool result unwrapped from MCP content envelope\n */\n async invokeTool(\n name: string,\n args?: unknown,\n options?: { timeout?: number }\n ): Promise<unknown> {\n const response = await this.callTool(\n \"tools/call\",\n { name, arguments: args ?? {} },\n options\n ) as { content?: Array<{ type: string; text?: string }>; isError?: boolean };\n\n // Unwrap MCP tools/call envelope: result is JSON-encoded in content[0].text\n if (response?.content && Array.isArray(response.content) && response.content.length > 0) {\n const first = response.content[0];\n if (first.type === \"text\" && typeof first.text === \"string\") {\n try {\n return JSON.parse(first.text);\n } catch {\n return first.text;\n }\n }\n }\n return response;\n }\n\n close(): void {\n if (!this.proc) {\n return;\n }\n\n const procToClose = this.proc;\n this.proc = null;\n\n // Send shutdown notification\n try {\n const notification: JsonRpcNotification = {\n jsonrpc: \"2.0\",\n method: \"shutdown\",\n };\n const line = JSON.stringify(notification) + \"\\n\";\n procToClose.stdin?.write(line);\n } catch (error) {\n // Ignore write errors during shutdown\n }\n\n // Clean up pending requests\n for (const [, { reject, timeout }] of this.pendingRequests) {\n clearTimeout(timeout);\n reject(new Error(\"Client closed\"));\n }\n this.pendingRequests.clear();\n\n // Remove listeners\n if (procToClose.stdout) {\n procToClose.stdout.removeAllListeners();\n }\n if (procToClose.stderr) {\n procToClose.stderr.removeAllListeners();\n }\n procToClose.removeAllListeners();\n\n // Terminate process with SIGKILL fallback\n try {\n procToClose.stdin?.end();\n if (!procToClose.killed) {\n procToClose.kill(\"SIGTERM\");\n\n // Schedule SIGKILL fallback after 1 second if process doesn't exit\n const killTimer = setTimeout(() => {\n // Check if process has actually exited (exitCode is non-null once exit event fires)\n if (procToClose.exitCode === null) {\n try {\n procToClose.kill(\"SIGKILL\");\n } catch {\n // Process may have already exited\n }\n }\n }, 1000);\n\n // Unreference the timer so it doesn't keep the event loop alive\n killTimer.unref();\n }\n } catch (error) {\n // Ignore errors during shutdown\n }\n }\n\n /**\n * Handle a JSON-RPC message from the analyzer\n *\n * Matches responses to pending requests by ID and resolves/rejects accordingly.\n * Extracts error information from JSON-RPC error objects.\n *\n * @param message Parsed JSON-RPC message\n */\n private handleMessage(message: unknown): void {\n if (!message || typeof message !== \"object\") {\n return;\n }\n\n const msg = message as Record<string, unknown>;\n\n // Ignore notifications (no id field)\n if (!(\"id\" in msg) || typeof msg.id !== \"number\") {\n return;\n }\n\n const msgId = msg.id as number;\n const pending = this.pendingRequests.get(msgId);\n\n if (!pending) {\n // Log diagnostic information for unmatched response IDs\n console.warn(\n `Received response with unmatched ID ${msgId} (type: ${typeof msgId}). ` +\n `Pending request IDs: [${Array.from(this.pendingRequests.keys()).join(\", \")}]. ` +\n `This may indicate a type mismatch or stale response.`\n );\n return;\n }\n\n const { resolve, reject, timeout } = pending;\n clearTimeout(timeout);\n this.pendingRequests.delete(msgId);\n\n // Check for error response\n if (\"error\" in msg && msg.error !== null && typeof msg.error === \"object\") {\n const error = msg.error as Record<string, unknown>;\n const errMessage = (error.message as string) || \"Unknown error\";\n const code = (error.code as number) ?? -1;\n const data = error.data;\n\n const errorMessage = data ? `${errMessage}: ${JSON.stringify(data)}` : errMessage;\n reject(new Error(`[Error ${code}] ${errorMessage}`));\n return;\n }\n\n // Success response\n if (\"result\" in msg) {\n resolve(msg.result);\n return;\n }\n\n // Invalid response\n reject(new Error(\"Invalid JSON-RPC response: no result or error field\"));\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/dist/cli.js CHANGED
@@ -47,7 +47,7 @@ async function extractExitCode(error) {
47
47
  }
48
48
  return 1;
49
49
  }
50
- const cliVersion = "0.1.5", program = new Command();
50
+ const cliVersion = "0.1.8", program = new Command();
51
51
  (process.argv.includes("--version") || process.argv.includes("-V")) && (process.argv = ["node", "dr", "version"]), program.name("dr").description("Documentation Robotics CLI - Architecture Model Management").option("-v, --verbose", "Enable verbose output").option("--debug", "Enable debug mode").exitOverride().showSuggestionAfterError().hook("preAction", async (thisCommand) => {
52
52
  const options = thisCommand.opts();
53
53
  setGlobalOptions({
@@ -2,7 +2,7 @@ import ansis from "ansis";
2
2
  import { getCliBundledSpecVersion } from "../utils/spec-version.js";
3
3
  import { isTelemetryBuiltIn, isTelemetryConfigured } from "../telemetry/detect.js";
4
4
  import { getSpecReferencePath, getModelPath } from "../utils/project-paths.js";
5
- const gitHash = "d3cc2b3", cliVersion = "0.1.5";
5
+ const gitHash = "f413188", cliVersion = "0.1.8";
6
6
  async function versionCommand() {
7
7
  const specVersion = getCliBundledSpecVersion(), telemetryBuiltIn = isTelemetryBuiltIn(), telemetryConfigured = await isTelemetryConfigured();
8
8
  let telemetryStatus;
@@ -56,7 +56,7 @@ async function visualizeCommand(options) {
56
56
  DR_WITH_DANGER: String(withDanger)
57
57
  }, { isDebug: getDebugState, isVerbose } = await import("../utils/globals.js");
58
58
  getDebugState() && (env.DEBUG = "1", env.DR_TELEMETRY_DEBUG = "1", logDebug("Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess")), isVerbose() && (env.VERBOSE = "1", logDebug("Passing VERBOSE=1 to subprocess")), authToken && (env.DR_AUTH_TOKEN = authToken), options.viewerPath && (env.DR_VIEWER_PATH = options.viewerPath, logDebug(`Using custom viewer from: ${options.viewerPath}`));
59
- const serverProcess = spawn("bun", ["run", serverEntryPath], {
59
+ const serverProcess = spawn(process.execPath, [serverEntryPath], {
60
60
  cwd: process.cwd(),
61
61
  stdio: ["pipe", "pipe", "pipe"],
62
62
  env
@@ -129,12 +129,13 @@ ${serverOutput}` : `Server exited with code ${code}`;
129
129
  rejectKeepAlive && rejectKeepAlive(error);
130
130
  }
131
131
  }), serverProcess.on("error", (error) => {
132
- const message = `Failed to start visualization server: ${error.message}`;
133
- isTelemetryEnabled && serverStartupSpan && (async () => {
132
+ const errorCode = error.code;
133
+ let message, suggestions;
134
+ errorCode === "EACCES" ? (message = `dr visualize could not run the visualization server: permission denied executing ${process.execPath}.`, suggestions = [`Check that ${process.execPath} is executable (chmod +x).`]) : message = `Failed to start visualization server: ${error.message}`, isTelemetryEnabled && serverStartupSpan && (async () => {
134
135
  const { endSpan } = await import("../telemetry/index.js");
135
136
  "recordException" in serverStartupSpan && (serverStartupSpan.recordException(error), serverStartupSpan.setStatus({ code: 2, message })), endSpan(serverStartupSpan), serverStartupSpan = null;
136
- })(), console.error(message);
137
- const cliError = new CLIError(message, 1);
137
+ })();
138
+ const cliError = new CLIError(message, 1, suggestions);
138
139
  rejectKeepAlive && rejectKeepAlive(cliError);
139
140
  });
140
141
  let flushInterval = null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/visualize.ts"],
4
- "sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from \"ansis\"\nimport { spawn } from \"child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname } from \"node:path\"\nimport { Model } from \"../core/model.js\"\nimport { logVerbose, logDebug } from \"../utils/globals.js\"\nimport { CLIError } from \"../utils/errors.js\"\nimport { getSpecReferencePath, getModelPath } from \"../utils/project-paths.js\"\nimport { getErrorMessage } from \"../utils/errors.js\"\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false\n\nexport interface VisualizeOptions {\n port?: number\n noBrowser?: boolean\n noAuth?: boolean\n token?: string\n verbose?: boolean\n debug?: boolean\n withDanger?: boolean\n viewerPath?: string\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n const { createServer } = await import(\"net\")\n for (let port = startPort; port <= 65535; port++) {\n const available = await new Promise<boolean>((resolve) => {\n const server = createServer()\n server.once(\"error\", () => resolve(false))\n server.once(\"listening\", () => { server.close(); resolve(true) })\n server.listen(port, \"127.0.0.1\")\n })\n if (available) return port\n }\n throw new Error(\"No available ports found\")\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug(\"Loading model for validation...\")\n\n // Load model to validate it exists (without full content to be quick)\n const model = await Model.load(process.cwd(), { lazyLoad: true })\n\n logVerbose(`Model validated: ${model.manifest.name}`)\n\n // Parse and validate port\n let port = 8080\n if (options.port) {\n const portNum = parseInt(String(options.port), 10)\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`)\n }\n port = portNum\n }\n\n // Find an available port starting from the requested port\n port = await findAvailablePort(port)\n if (port !== (options.port ? parseInt(String(options.port), 10) : 8080)) {\n console.log(ansis.yellow(` Port ${options.port ?? 8080} is in use, using port ${port} instead`))\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth // Auth enabled by default unless --no-auth flag\n const authToken = options.token // Optional token for testing\n const withDanger = options.withDanger || false // Danger mode disabled by default\n\n logDebug(`Starting visualization server on port ${port}`)\n logDebug(`Authentication: ${authEnabled ? \"enabled\" : \"disabled\"}`)\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`)\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`)\n }\n if (withDanger) {\n logDebug(`Danger mode enabled - chat permissions will be skipped`)\n }\n\n // Get the directory where this command file is located (dist/commands/)\n const commandDir = dirname(fileURLToPath(import.meta.url))\n const distDir = dirname(commandDir) // Go up to dist/\n const serverEntryPath = `${distDir}/server-entry.js`\n\n logDebug(`Server entry path: ${serverEntryPath}`)\n\n // Create telemetry span for server startup\n let serverStartupSpan: any = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Creating visualize.server.startup span\")\n const { startSpan } = await import(\"../telemetry/index.js\")\n serverStartupSpan = startSpan(\"visualize.server.startup\", {\n \"server.port\": port,\n \"server.auth_enabled\": authEnabled,\n \"server.with_danger\": withDanger,\n })\n logDebug(`[Telemetry] Span created: ${serverStartupSpan ? \"success\" : \"failed\"}`)\n } else {\n logDebug(\"[Telemetry] TELEMETRY_ENABLED is false - no spans will be created\")\n }\n\n // Spawn server in Bun subprocess with environment variables\n const env: Record<string, string> = {\n ...process.env,\n DR_VISUALIZE_PORT: String(port),\n DR_PROJECT_PATH: process.cwd(),\n DR_AUTH_ENABLED: String(authEnabled),\n DR_WITH_DANGER: String(withDanger),\n }\n\n // Pass debug/verbose flags to subprocess\n // The CLI --debug flag sets globalOptions.debug, but subprocess checks process.env.DEBUG\n const { isDebug: getDebugState, isVerbose } = await import(\"../utils/globals.js\")\n if (getDebugState()) {\n env.DEBUG = \"1\"\n env.DR_TELEMETRY_DEBUG = \"1\" // Enable telemetry exporter debug logging\n logDebug(\"Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess\")\n }\n if (isVerbose()) {\n env.VERBOSE = \"1\"\n logDebug(\"Passing VERBOSE=1 to subprocess\")\n }\n\n // Only set auth token if provided\n if (authToken) {\n env.DR_AUTH_TOKEN = authToken\n }\n\n // Set custom viewer path if provided\n if (options.viewerPath) {\n env.DR_VIEWER_PATH = options.viewerPath\n logDebug(`Using custom viewer from: ${options.viewerPath}`)\n }\n\n const serverProcess = spawn(\"bun\", [\"run\", serverEntryPath], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env,\n })\n\n let authTokenFromServer = \"\"\n let serverOutput = \"\"\n let serverStartupShown = false\n\n // Capture output from server process\n if (serverProcess.stdout) {\n serverProcess.stdout.on(\"data\", async (data) => {\n const chunk = data.toString()\n serverOutput += chunk\n\n // Process each line individually: Bun's pipe buffering often batches multiple\n // console.log calls (e.g. \"running at\" + \"TOKEN:\") into a single data event.\n // Using startsWith/substring on the whole chunk would either miss the TOKEN line\n // (when \"running at\" is first) or extract a malformed token (when trailing lines\n // are appended), causing the browser to open with the wrong token and get a 403.\n const lines = chunk.split(\"\\n\")\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) continue\n\n // Extract token from output\n if (line.startsWith(\"TOKEN:\")) {\n authTokenFromServer = line.substring(6)\n logDebug(`Received auth token from server: ${authTokenFromServer.substring(0, 8)}...`)\n\n // If we haven't shown startup info yet and now have the token, show it\n if (serverStartupShown && authEnabled && authTokenFromServer) {\n // Update the URL display with actual token\n console.log(\n ansis.cyan(` Access URL: http://localhost:${port}?token=${authTokenFromServer}`)\n )\n console.log(ansis.dim(` Auth token: ${authTokenFromServer}`))\n\n // Now that we have the token, open browser if needed\n if (!options.noBrowser) {\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}?token=${authTokenFromServer}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n }\n }\n }\n\n // Server started message - defer detailed output if auth enabled\n if (line.includes(\"running at http://localhost\")) {\n serverStartupShown = true\n\n // End telemetry span for server startup\n if (isTelemetryEnabled && serverStartupSpan) {\n logDebug(\n \"[Telemetry] Ending visualize.server.startup span (server started successfully)\"\n )\n const { endSpan } = await import(\"../telemetry/index.js\")\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n logDebug(\"[Telemetry] Span ended\")\n }\n\n console.log(ansis.green(`\u2713 Visualization server started`))\n\n // Display access information (but may be incomplete if auth enabled and token not yet received)\n if (authEnabled) {\n const token = authTokenFromServer || authToken || \"(pending)\"\n if (authTokenFromServer) {\n // Token already received (same chunk), show full info immediately\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`))\n console.log(ansis.dim(` Auth token: ${token}`))\n } else {\n // Token not yet received, just show port for now\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n logDebug(` (waiting for auth token...)`)\n }\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n console.log(ansis.yellow(` \u26A0 Authentication disabled`))\n }\n\n if (withDanger) {\n console.log(\n ansis.yellow(` \u26A0 Danger mode enabled - chat permissions will be skipped`)\n )\n }\n\n console.log(ansis.blueBright(`Model name: ${model.manifest.name}`))\n const drFolder = await getSpecReferencePath()\n console.log(\n `DR folder: ${ansis.cyan(drFolder || \"Not found\")}`\n )\n console.log(`Model path: ${ansis.cyan((await getModelPath()) || \"Not found\")}`)\n\n // Optionally open browser\n // If auth is enabled, delay opening until we have the token\n const shouldOpenBrowser = !options.noBrowser\n logDebug(\n `Browser auto-open: ${shouldOpenBrowser ? \"enabled\" : \"disabled\"} (noBrowser=${options.noBrowser})`\n )\n\n if (shouldOpenBrowser && !authEnabled) {\n // Auth disabled: open immediately\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n } else if (shouldOpenBrowser && authEnabled && !authTokenFromServer) {\n // Auth enabled but token not yet in this chunk: will open when TOKEN line is processed\n logDebug(\"Browser auto-open deferred until token is received\")\n } else {\n logDebug(\"Browser auto-open disabled\")\n }\n }\n\n // Log other output\n if (!line.startsWith(\"TOKEN:\")) {\n logDebug(`[server] ${line}`)\n }\n }\n })\n }\n\n // Capture stderr\n if (serverProcess.stderr) {\n serverProcess.stderr.on(\"data\", (data) => {\n const output = data.toString().trim()\n if (output) {\n serverOutput += output + \"\\n\"\n // Log stderr at debug level so --debug flag shows telemetry exporter logs\n logDebug(`[server stderr] ${output}`)\n }\n })\n }\n\n // Handle process close (fires after all stdio streams have closed)\n let rejectKeepAlive: ((reason: Error) => void) | null = null\n\n serverProcess.on(\"close\", (code) => {\n if (code !== 0) {\n // Include server output in error message if available (avoid separate logDebug to prevent minification issues)\n const message = serverOutput\n ? `Server exited with code ${code}\\nServer output:\\n${serverOutput}`\n : `Server exited with code ${code}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(new Error(message));\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before rejecting (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const error = new CLIError(message, code || 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(error)\n }\n }\n })\n\n // Handle process errors\n serverProcess.on(\"error\", (error) => {\n const message = `Failed to start visualization server: ${error.message}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(error);\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before throwing (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const cliError = new CLIError(message, 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(cliError)\n }\n })\n\n // Setup periodic telemetry flushing for long-running command\n // Since the visualize command never completes (keep-alive pattern),\n // the cli.execute span would never end and export. We flush periodically\n // to ensure active spans are exported to the telemetry backend.\n let flushInterval: NodeJS.Timeout | null = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Setting up periodic flush (every 15 seconds)\")\n const { flushTelemetry } = await import(\"../telemetry/index.js\")\n\n flushInterval = setInterval(async () => {\n logDebug(\"[Telemetry] Periodic flush: flushing pending spans...\")\n try {\n await flushTelemetry()\n logDebug(\"[Telemetry] Periodic flush complete\")\n } catch (error) {\n // Silently ignore flush errors - telemetry should never break the app\n logDebug(`[Telemetry] Periodic flush failed: ${error}`)\n }\n }, 15000) // Flush every 15 seconds\n }\n\n // Handle graceful shutdown on SIGINT (Ctrl-C) and SIGTERM\n const handleShutdown = async (signal: string) => {\n logDebug(`\\n[${signal}] Shutting down visualization server gracefully...`)\n\n // Clear periodic flush interval\n if (flushInterval) {\n logDebug(\"[Telemetry] Stopping periodic flush\")\n clearInterval(flushInterval)\n flushInterval = null\n }\n\n // Forward signal to server process for graceful shutdown\n if (serverProcess && !serverProcess.killed) {\n logDebug(\"Sending termination signal to server process...\")\n serverProcess.kill(signal as NodeJS.Signals)\n\n // Wait for server to exit (with timeout)\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n logDebug(\"Server shutdown timeout, forcing exit...\")\n if (!serverProcess.killed) {\n serverProcess.kill(\"SIGKILL\")\n }\n resolve()\n }, 3000) // 3 second timeout\n\n serverProcess.on(\"exit\", () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n logDebug(\"Visualization server stopped\")\n process.exit(0)\n }\n\n // Register signal handlers\n process.on(\"SIGINT\", () => handleShutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"))\n\n // Keep parent process alive - reject promise if server fails\n await new Promise((_, reject) => {\n rejectKeepAlive = reject\n })\n } catch (error) {\n // Throw CLIError instead of process.exit to allow telemetry shutdown\n // Note: Avoid side effects (like logDebug) before throw to prevent minification issues\n if (error instanceof CLIError) {\n // CLIError should have already printed its message\n throw error\n }\n // Non-CLIError needs to be printed and wrapped\n const message = getErrorMessage(error)\n console.error(message)\n throw new CLIError(message, 1)\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform\n\n if (platform === \"darwin\") {\n return \"open\"\n }\n if (platform === \"win32\") {\n return \"start\"\n }\n // Linux and others\n return \"xdg-open\"\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Visualize command - Launch visualization server\n */\n\nimport ansis from \"ansis\"\nimport { spawn } from \"child_process\"\nimport { fileURLToPath } from \"node:url\"\nimport { dirname } from \"node:path\"\nimport { Model } from \"../core/model.js\"\nimport { logVerbose, logDebug } from \"../utils/globals.js\"\nimport { CLIError } from \"../utils/errors.js\"\nimport { getSpecReferencePath, getModelPath } from \"../utils/project-paths.js\"\nimport { getErrorMessage } from \"../utils/errors.js\"\n\n// Conditional telemetry import based on compile-time flag\ndeclare const TELEMETRY_ENABLED: boolean | undefined\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false\n\nexport interface VisualizeOptions {\n port?: number\n noBrowser?: boolean\n noAuth?: boolean\n token?: string\n verbose?: boolean\n debug?: boolean\n withDanger?: boolean\n viewerPath?: string\n}\n\nasync function findAvailablePort(startPort: number): Promise<number> {\n const { createServer } = await import(\"net\")\n for (let port = startPort; port <= 65535; port++) {\n const available = await new Promise<boolean>((resolve) => {\n const server = createServer()\n server.once(\"error\", () => resolve(false))\n server.once(\"listening\", () => { server.close(); resolve(true) })\n server.listen(port, \"127.0.0.1\")\n })\n if (available) return port\n }\n throw new Error(\"No available ports found\")\n}\n\nexport async function visualizeCommand(\n options: VisualizeOptions & { port?: string }\n): Promise<void> {\n try {\n logDebug(\"Loading model for validation...\")\n\n // Load model to validate it exists (without full content to be quick)\n const model = await Model.load(process.cwd(), { lazyLoad: true })\n\n logVerbose(`Model validated: ${model.manifest.name}`)\n\n // Parse and validate port\n let port = 8080\n if (options.port) {\n const portNum = parseInt(String(options.port), 10)\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${options.port}. Port must be between 1 and 65535.`)\n }\n port = portNum\n }\n\n // Find an available port starting from the requested port\n port = await findAvailablePort(port)\n if (port !== (options.port ? parseInt(String(options.port), 10) : 8080)) {\n console.log(ansis.yellow(` Port ${options.port ?? 8080} is in use, using port ${port} instead`))\n }\n\n // Determine auth settings\n const authEnabled = !options.noAuth // Auth enabled by default unless --no-auth flag\n const authToken = options.token // Optional token for testing\n const withDanger = options.withDanger || false // Danger mode disabled by default\n\n logDebug(`Starting visualization server on port ${port}`)\n logDebug(`Authentication: ${authEnabled ? \"enabled\" : \"disabled\"}`)\n if (authEnabled && authToken) {\n logDebug(`Using provided token for authentication`)\n } else if (authEnabled) {\n logDebug(`Token will be auto-generated`)\n }\n if (withDanger) {\n logDebug(`Danger mode enabled - chat permissions will be skipped`)\n }\n\n // Get the directory where this command file is located (dist/commands/)\n const commandDir = dirname(fileURLToPath(import.meta.url))\n const distDir = dirname(commandDir) // Go up to dist/\n const serverEntryPath = `${distDir}/server-entry.js`\n\n logDebug(`Server entry path: ${serverEntryPath}`)\n\n // Create telemetry span for server startup\n let serverStartupSpan: any = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Creating visualize.server.startup span\")\n const { startSpan } = await import(\"../telemetry/index.js\")\n serverStartupSpan = startSpan(\"visualize.server.startup\", {\n \"server.port\": port,\n \"server.auth_enabled\": authEnabled,\n \"server.with_danger\": withDanger,\n })\n logDebug(`[Telemetry] Span created: ${serverStartupSpan ? \"success\" : \"failed\"}`)\n } else {\n logDebug(\"[Telemetry] TELEMETRY_ENABLED is false - no spans will be created\")\n }\n\n // Build environment variables for the server subprocess\n const env: Record<string, string> = {\n ...process.env,\n DR_VISUALIZE_PORT: String(port),\n DR_PROJECT_PATH: process.cwd(),\n DR_AUTH_ENABLED: String(authEnabled),\n DR_WITH_DANGER: String(withDanger),\n }\n\n // Pass debug/verbose flags to subprocess\n // The CLI --debug flag sets globalOptions.debug, but subprocess checks process.env.DEBUG\n const { isDebug: getDebugState, isVerbose } = await import(\"../utils/globals.js\")\n if (getDebugState()) {\n env.DEBUG = \"1\"\n env.DR_TELEMETRY_DEBUG = \"1\" // Enable telemetry exporter debug logging\n logDebug(\"Passing DEBUG=1 and DR_TELEMETRY_DEBUG=1 to subprocess\")\n }\n if (isVerbose()) {\n env.VERBOSE = \"1\"\n logDebug(\"Passing VERBOSE=1 to subprocess\")\n }\n\n // Only set auth token if provided\n if (authToken) {\n env.DR_AUTH_TOKEN = authToken\n }\n\n // Set custom viewer path if provided\n if (options.viewerPath) {\n env.DR_VIEWER_PATH = options.viewerPath\n logDebug(`Using custom viewer from: ${options.viewerPath}`)\n }\n\n // Reuse whichever runtime is currently executing the CLI (node or bun) to launch\n // the server subprocess. server-entry.ts has no Bun-specific dependencies (see #800),\n // so this works identically under Node.js or Bun \u2014 and unlike spawning a literal\n // \"bun\" binary, process.execPath is guaranteed to exist since it's already running.\n const serverProcess = spawn(process.execPath, [serverEntryPath], {\n cwd: process.cwd(),\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env,\n })\n\n let authTokenFromServer = \"\"\n let serverOutput = \"\"\n let serverStartupShown = false\n\n // Capture output from server process\n if (serverProcess.stdout) {\n serverProcess.stdout.on(\"data\", async (data) => {\n const chunk = data.toString()\n serverOutput += chunk\n\n // Process each line individually: stdio pipe buffering can batch multiple\n // console.log calls (e.g. \"running at\" + \"TOKEN:\") into a single data event,\n // regardless of runtime.\n // Using startsWith/substring on the whole chunk would either miss the TOKEN line\n // (when \"running at\" is first) or extract a malformed token (when trailing lines\n // are appended), causing the browser to open with the wrong token and get a 403.\n const lines = chunk.split(\"\\n\")\n for (const rawLine of lines) {\n const line = rawLine.trim()\n if (!line) continue\n\n // Extract token from output\n if (line.startsWith(\"TOKEN:\")) {\n authTokenFromServer = line.substring(6)\n logDebug(`Received auth token from server: ${authTokenFromServer.substring(0, 8)}...`)\n\n // If we haven't shown startup info yet and now have the token, show it\n if (serverStartupShown && authEnabled && authTokenFromServer) {\n // Update the URL display with actual token\n console.log(\n ansis.cyan(` Access URL: http://localhost:${port}?token=${authTokenFromServer}`)\n )\n console.log(ansis.dim(` Auth token: ${authTokenFromServer}`))\n\n // Now that we have the token, open browser if needed\n if (!options.noBrowser) {\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}?token=${authTokenFromServer}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n }\n }\n }\n\n // Server started message - defer detailed output if auth enabled\n if (line.includes(\"running at http://localhost\")) {\n serverStartupShown = true\n\n // End telemetry span for server startup\n if (isTelemetryEnabled && serverStartupSpan) {\n logDebug(\n \"[Telemetry] Ending visualize.server.startup span (server started successfully)\"\n )\n const { endSpan } = await import(\"../telemetry/index.js\")\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n logDebug(\"[Telemetry] Span ended\")\n }\n\n console.log(ansis.green(`\u2713 Visualization server started`))\n\n // Display access information (but may be incomplete if auth enabled and token not yet received)\n if (authEnabled) {\n const token = authTokenFromServer || authToken || \"(pending)\"\n if (authTokenFromServer) {\n // Token already received (same chunk), show full info immediately\n console.log(ansis.cyan(` Access URL: http://localhost:${port}?token=${token}`))\n console.log(ansis.dim(` Auth token: ${token}`))\n } else {\n // Token not yet received, just show port for now\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n logDebug(` (waiting for auth token...)`)\n }\n } else {\n console.log(ansis.dim(` Open http://localhost:${port} in your browser`))\n console.log(ansis.yellow(` \u26A0 Authentication disabled`))\n }\n\n if (withDanger) {\n console.log(\n ansis.yellow(` \u26A0 Danger mode enabled - chat permissions will be skipped`)\n )\n }\n\n console.log(ansis.blueBright(`Model name: ${model.manifest.name}`))\n const drFolder = await getSpecReferencePath()\n console.log(\n `DR folder: ${ansis.cyan(drFolder || \"Not found\")}`\n )\n console.log(`Model path: ${ansis.cyan((await getModelPath()) || \"Not found\")}`)\n\n // Optionally open browser\n // If auth is enabled, delay opening until we have the token\n const shouldOpenBrowser = !options.noBrowser\n logDebug(\n `Browser auto-open: ${shouldOpenBrowser ? \"enabled\" : \"disabled\"} (noBrowser=${options.noBrowser})`\n )\n\n if (shouldOpenBrowser && !authEnabled) {\n // Auth disabled: open immediately\n try {\n const command = getOpenCommand()\n const url = `http://localhost:${port}`\n logDebug(`Opening browser with command: ${command} ${url}`)\n spawn(command, [url], {\n detached: true,\n stdio: \"ignore\",\n })\n } catch (error) {\n logVerbose(\"Could not auto-open browser (not critical)\")\n }\n } else if (shouldOpenBrowser && authEnabled && !authTokenFromServer) {\n // Auth enabled but token not yet in this chunk: will open when TOKEN line is processed\n logDebug(\"Browser auto-open deferred until token is received\")\n } else {\n logDebug(\"Browser auto-open disabled\")\n }\n }\n\n // Log other output\n if (!line.startsWith(\"TOKEN:\")) {\n logDebug(`[server] ${line}`)\n }\n }\n })\n }\n\n // Capture stderr\n if (serverProcess.stderr) {\n serverProcess.stderr.on(\"data\", (data) => {\n const output = data.toString().trim()\n if (output) {\n serverOutput += output + \"\\n\"\n // Log stderr at debug level so --debug flag shows telemetry exporter logs\n logDebug(`[server stderr] ${output}`)\n }\n })\n }\n\n // Handle process close (fires after all stdio streams have closed)\n let rejectKeepAlive: ((reason: Error) => void) | null = null\n\n serverProcess.on(\"close\", (code) => {\n if (code !== 0) {\n // Include server output in error message if available (avoid separate logDebug to prevent minification issues)\n const message = serverOutput\n ? `Server exited with code ${code}\\nServer output:\\n${serverOutput}`\n : `Server exited with code ${code}`\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(new Error(message));\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Print error before rejecting (CLI wrapper expects CLIError messages to be pre-printed)\n console.error(message)\n const error = new CLIError(message, code || 1)\n if (rejectKeepAlive) {\n rejectKeepAlive(error)\n }\n }\n })\n\n // Handle process errors\n serverProcess.on(\"error\", (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n let message: string\n let suggestions: string[] | undefined\n\n // The server subprocess is launched via process.execPath (the same node/bun\n // binary already running this CLI), so it's always present - ENOENT here would\n // indicate something unusual about the runtime environment rather than a missing\n // dependency (see #800: this used to hard-depend on a separately installed `bun`).\n if (errorCode === \"EACCES\") {\n message = `dr visualize could not run the visualization server: permission denied executing ${process.execPath}.`\n suggestions = [`Check that ${process.execPath} is executable (chmod +x).`]\n } else {\n message = `Failed to start visualization server: ${error.message}`\n }\n\n // Record error in startup span if still active\n if (isTelemetryEnabled && serverStartupSpan) {\n (async () => {\n const { endSpan } = await import(\"../telemetry/index.js\")\n if (\"recordException\" in serverStartupSpan) {\n (serverStartupSpan as any).recordException(error);\n (serverStartupSpan as any).setStatus({ code: 2, message }) // ERROR\n }\n endSpan(serverStartupSpan)\n serverStartupSpan = null\n })()\n }\n\n // Don't print here: rejecting propagates this CLIError up through the catch block\n // below to the top-level CLI handler, which prints `.format()` (including\n // `suggestions`) exactly once. Printing here too would duplicate the whole block.\n const cliError = new CLIError(message, 1, suggestions)\n if (rejectKeepAlive) {\n rejectKeepAlive(cliError)\n }\n })\n\n // Setup periodic telemetry flushing for long-running command\n // Since the visualize command never completes (keep-alive pattern),\n // the cli.execute span would never end and export. We flush periodically\n // to ensure active spans are exported to the telemetry backend.\n let flushInterval: NodeJS.Timeout | null = null\n if (isTelemetryEnabled) {\n logDebug(\"[Telemetry] Setting up periodic flush (every 15 seconds)\")\n const { flushTelemetry } = await import(\"../telemetry/index.js\")\n\n flushInterval = setInterval(async () => {\n logDebug(\"[Telemetry] Periodic flush: flushing pending spans...\")\n try {\n await flushTelemetry()\n logDebug(\"[Telemetry] Periodic flush complete\")\n } catch (error) {\n // Silently ignore flush errors - telemetry should never break the app\n logDebug(`[Telemetry] Periodic flush failed: ${error}`)\n }\n }, 15000) // Flush every 15 seconds\n }\n\n // Handle graceful shutdown on SIGINT (Ctrl-C) and SIGTERM\n const handleShutdown = async (signal: string) => {\n logDebug(`\\n[${signal}] Shutting down visualization server gracefully...`)\n\n // Clear periodic flush interval\n if (flushInterval) {\n logDebug(\"[Telemetry] Stopping periodic flush\")\n clearInterval(flushInterval)\n flushInterval = null\n }\n\n // Forward signal to server process for graceful shutdown\n if (serverProcess && !serverProcess.killed) {\n logDebug(\"Sending termination signal to server process...\")\n serverProcess.kill(signal as NodeJS.Signals)\n\n // Wait for server to exit (with timeout)\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n logDebug(\"Server shutdown timeout, forcing exit...\")\n if (!serverProcess.killed) {\n serverProcess.kill(\"SIGKILL\")\n }\n resolve()\n }, 3000) // 3 second timeout\n\n serverProcess.on(\"exit\", () => {\n clearTimeout(timeout)\n resolve()\n })\n })\n }\n\n logDebug(\"Visualization server stopped\")\n process.exit(0)\n }\n\n // Register signal handlers\n process.on(\"SIGINT\", () => handleShutdown(\"SIGINT\"))\n process.on(\"SIGTERM\", () => handleShutdown(\"SIGTERM\"))\n\n // Keep parent process alive - reject promise if server fails\n await new Promise((_, reject) => {\n rejectKeepAlive = reject\n })\n } catch (error) {\n // Throw CLIError instead of process.exit to allow telemetry shutdown\n // Note: Avoid side effects (like logDebug) before throw to prevent minification issues\n if (error instanceof CLIError) {\n // CLIError should have already printed its message\n throw error\n }\n // Non-CLIError needs to be printed and wrapped\n const message = getErrorMessage(error)\n console.error(message)\n throw new CLIError(message, 1)\n }\n}\n\n/**\n * Get the appropriate command to open a URL\n */\nfunction getOpenCommand(): string {\n const platform = process.platform\n\n if (platform === \"darwin\") {\n return \"open\"\n }\n if (platform === \"win32\") {\n return \"start\"\n }\n // Linux and others\n return \"xdg-open\"\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }