@canaryai/cli 0.2.2 → 0.2.4

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.
Files changed (46) hide show
  1. package/dist/bin.js +1 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-DXJNFJ3A.js → chunk-6WWHXWCS.js} +3 -2
  4. package/dist/{chunk-DXJNFJ3A.js.map → chunk-6WWHXWCS.js.map} +1 -1
  5. package/dist/{chunk-HOYYXZPV.js → chunk-DXIAHB72.js} +3 -2
  6. package/dist/{chunk-HOYYXZPV.js.map → chunk-DXIAHB72.js.map} +1 -1
  7. package/dist/{chunk-UEOXNF5X.js → chunk-FIQBGAKW.js} +3 -1
  8. package/dist/{chunk-UEOXNF5X.js.map → chunk-FIQBGAKW.js.map} +1 -1
  9. package/dist/{chunk-TO66FC4R.js → chunk-ILEPYWZX.js} +3 -2
  10. package/dist/{chunk-TO66FC4R.js.map → chunk-ILEPYWZX.js.map} +1 -1
  11. package/dist/{chunk-7R4YFGP6.js → chunk-PWWQGYFG.js} +3 -1
  12. package/dist/{chunk-7R4YFGP6.js.map → chunk-PWWQGYFG.js.map} +1 -1
  13. package/dist/{chunk-DGUM43GV.js → chunk-VLFUCAPZ.js} +2 -1
  14. package/dist/{debug-workflow-EHKNO7BJ.js → debug-workflow-PT3OUR3V.js} +5 -4
  15. package/dist/{debug-workflow-EHKNO7BJ.js.map → debug-workflow-PT3OUR3V.js.map} +1 -1
  16. package/dist/docs-GCYDTEOY.js +270 -0
  17. package/dist/docs-GCYDTEOY.js.map +1 -0
  18. package/dist/{feature-flag-ZDLDYRSF.js → feature-flag-S5B5GLPP.js} +77 -33
  19. package/dist/feature-flag-S5B5GLPP.js.map +1 -0
  20. package/dist/index.js +26 -12
  21. package/dist/index.js.map +1 -1
  22. package/dist/{issues-FI3RIWGV.js → issues-4ZEDHPLW.js} +5 -4
  23. package/dist/{issues-FI3RIWGV.js.map → issues-4ZEDHPLW.js.map} +1 -1
  24. package/dist/{knobs-3MKMOXIV.js → knobs-RKUVK3HC.js} +5 -4
  25. package/dist/{knobs-3MKMOXIV.js.map → knobs-RKUVK3HC.js.map} +1 -1
  26. package/dist/{local-browser-GG5GUXDS.js → local-browser-J6WGFLVD.js} +5 -4
  27. package/dist/{local-browser-GG5GUXDS.js.map → local-browser-J6WGFLVD.js.map} +1 -1
  28. package/dist/mcp-HGYBMDYZ.js +687 -0
  29. package/dist/mcp-HGYBMDYZ.js.map +1 -0
  30. package/dist/{psql-IVAPNYZV.js → psql-WVIHMC6A.js} +4 -3
  31. package/dist/{psql-IVAPNYZV.js.map → psql-WVIHMC6A.js.map} +1 -1
  32. package/dist/record-X4SVNYP3.js +334 -0
  33. package/dist/record-X4SVNYP3.js.map +1 -0
  34. package/dist/{redis-LWY7L6AS.js → redis-RGHECKV5.js} +4 -3
  35. package/dist/{redis-LWY7L6AS.js.map → redis-RGHECKV5.js.map} +1 -1
  36. package/dist/{release-KQFCTAXA.js → release-ZOD4Y2BF.js} +4 -3
  37. package/dist/{release-KQFCTAXA.js.map → release-ZOD4Y2BF.js.map} +1 -1
  38. package/dist/runner/preload.js +3 -2
  39. package/dist/runner/preload.js.map +1 -1
  40. package/dist/test.js +3 -2
  41. package/dist/test.js.map +1 -1
  42. package/package.json +2 -2
  43. package/dist/feature-flag-ZDLDYRSF.js.map +0 -1
  44. package/dist/mcp-AD67OLQM.js +0 -384
  45. package/dist/mcp-AD67OLQM.js.map +0 -1
  46. /package/dist/{chunk-DGUM43GV.js.map → chunk-VLFUCAPZ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/index.ts","../src/mcp/tool-helpers.ts","../src/mcp/session-tools.ts","../src/mcp/browser-tools.ts","../src/mcp/browser-session.ts"],"sourcesContent":["/**\n * MCP Server for Canary CLI.\n *\n * Combines cloud-relay session tools and direct browser tools\n * into a single MCP server.\n *\n * @module\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { toolText } from './tool-helpers';\nimport { getSessionToolDefinitions, handleSessionTool } from './session-tools';\nimport { getBrowserMCPToolDefinitions, handleBrowserTool } from './browser-tools';\n\nexport async function runMcp(argv: string[]) {\n const server = new Server(\n { name: 'canary-cli', version: '0.1.0' },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n ...getSessionToolDefinitions(),\n ...getBrowserMCPToolDefinitions(),\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const tool = req.params.name;\n const args = (req.params.arguments ?? {}) as Record<string, unknown>;\n\n // Try browser tools first (browser_* prefix)\n const browserResult = await handleBrowserTool(tool, args);\n if (browserResult) return browserResult;\n\n // Try session tools (local_* prefix)\n const sessionResult = await handleSessionTool(tool, args);\n if (sessionResult) return sessionResult;\n\n return toolText(`Unknown tool: ${tool}`);\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n return new Promise<void>(() => undefined);\n}\n","/**\n * MCP tool response helpers.\n *\n * @module\n */\n\nimport type { ToolResult } from '@chatsdet/browser-core';\n\nexport function toolText(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function toolJson(data: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };\n}\n\n/**\n * Convert a BrowserToolExecutor result to MCP SDK content blocks.\n */\nexport function formatMCPContent(result: ToolResult) {\n if (typeof result === 'string') {\n return { content: [{ type: 'text' as const, text: result }] };\n }\n\n // MCPContentResult with text and optional images\n const content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> = [];\n\n if (result.text) {\n content.push({ type: 'text', text: result.text });\n }\n\n for (const img of result.images ?? []) {\n content.push({ type: 'image', data: img.data, mimeType: img.mimeType });\n }\n\n return { content };\n}\n","/**\n * Cloud-relay session tools for the MCP server.\n *\n * These tools proxy browser control through the cloud API via WebSocket.\n *\n * @module\n */\n\nimport { createParser, type EventSourceMessage } from 'eventsource-parser';\nimport { readStoredToken } from '../auth';\nimport { createLocalRun } from '../local-run';\nimport { connectTunnel, createTunnel } from '../tunnel';\nimport { LocalBrowserHost } from '../local-browser/host';\nimport type { LocalBrowserMode } from '../local-browser/protocol';\nimport { toolText, toolJson } from './tool-helpers';\n\nimport process from 'node:process';\n\ntype RunLocalInput = {\n port: number;\n instructions: string;\n title?: string;\n};\n\ntype WaitForRunInput = {\n runId: string;\n};\n\ntype LocalBrowserStartInput = {\n mode?: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n instructions?: string;\n};\n\ntype LocalBrowserStopInput = {\n sessionId: string;\n};\n\ntype LocalBrowserStatusInput = {\n sessionId: string;\n};\n\ninterface BrowserSession {\n sessionId: string;\n host: LocalBrowserHost;\n startedAt: number;\n mode: LocalBrowserMode;\n}\n\n// Global browser sessions managed by MCP\nconst browserSessions = new Map<string, BrowserSession>();\n\nconst DEFAULT_API_URL = 'https://api.trycanary.ai';\n\nfunction resolveApiUrl(input?: string): string {\n return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error('Missing token. Run `canary login` first or set CANARY_API_TOKEN.');\n }\n return token;\n}\n\nexport function getSessionToolDefinitions() {\n return [\n {\n name: 'local_run_tests',\n description:\n 'Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.',\n inputSchema: {\n type: 'object',\n properties: {\n port: { type: 'number' },\n instructions: { type: 'string' },\n title: { type: 'string' },\n },\n required: ['port', 'instructions'],\n },\n },\n {\n name: 'local_wait_for_results',\n description:\n 'Wait for a local test run to complete. Streams until completion and returns a compact report.',\n inputSchema: {\n type: 'object',\n properties: {\n runId: { type: 'string' },\n },\n required: ['runId'],\n },\n },\n {\n name: 'local_browser_start',\n description:\n 'Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.',\n inputSchema: {\n type: 'object',\n properties: {\n mode: {\n type: 'string',\n enum: ['playwright', 'cdp'],\n description: \"Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome\",\n },\n cdpUrl: {\n type: 'string',\n description: 'CDP endpoint URL when mode is \\'cdp\\' (e.g. http://localhost:9222)',\n },\n headless: {\n type: 'boolean',\n description: 'Run browser headless (default: true for playwright mode)',\n },\n storageStatePath: {\n type: 'string',\n description: 'Path to Playwright storage state JSON for pre-authenticated sessions',\n },\n instructions: {\n type: 'string',\n description: 'Instructions for the cloud agent on what to test',\n },\n },\n },\n },\n {\n name: 'local_browser_status',\n description: 'Check the status of a local browser session.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string' },\n },\n required: ['sessionId'],\n },\n },\n {\n name: 'local_browser_stop',\n description: 'Stop a local browser session and close the browser.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string' },\n },\n required: ['sessionId'],\n },\n },\n {\n name: 'local_browser_list',\n description: 'List all active local browser sessions.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'local_browser_run',\n description:\n 'Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: {\n type: 'string',\n description: 'The session ID from local_browser_start',\n },\n instructions: {\n type: 'string',\n description: 'Instructions for the cloud agent on what to test',\n },\n startUrl: {\n type: 'string',\n description: 'Optional URL to navigate to before starting',\n },\n },\n required: ['sessionId', 'instructions'],\n },\n },\n ];\n}\n\nexport async function handleSessionTool(tool: string, args: Record<string, unknown>) {\n const token = await resolveToken();\n\n if (tool === 'local_run_tests') {\n const input = args as unknown as RunLocalInput;\n const apiUrl = resolveApiUrl();\n const tunnel = await createTunnel({ apiUrl, token, port: input.port });\n connectTunnel({\n apiUrl,\n tunnelId: tunnel.tunnelId,\n token: tunnel.token,\n port: input.port,\n });\n const tunnelUrl = tunnel.publicUrl;\n\n const run = await createLocalRun({\n apiUrl,\n token,\n title: input.title ?? 'Local MCP run',\n featureSpec: input.instructions,\n startUrl: undefined,\n tunnelUrl,\n });\n\n return toolJson({\n runId: run.runId,\n watchUrl: run.watchUrl,\n tunnelUrl,\n note: 'Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion.',\n });\n }\n\n if (tool === 'local_wait_for_results') {\n const input = args as unknown as WaitForRunInput;\n const apiUrl = resolveApiUrl();\n const report = await waitForResult({ apiUrl, token, runId: input.runId });\n return toolJson(report);\n }\n\n if (tool === 'local_browser_start') {\n const input = args as unknown as LocalBrowserStartInput;\n const apiUrl = resolveApiUrl();\n const mode = input.mode ?? 'playwright';\n\n const sessionResponse = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: mode,\n instructions: input.instructions ?? null,\n }),\n });\n\n if (!sessionResponse.ok) {\n const text = await sessionResponse.text();\n return toolJson({ ok: false, error: `Failed to create session: ${text}` });\n }\n\n const session = (await sessionResponse.json()) as {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n expiresAt: string;\n };\n\n const host = new LocalBrowserHost({\n apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: mode,\n cdpUrl: input.cdpUrl,\n headless: input.headless ?? true,\n storageStatePath: input.storageStatePath,\n onLog: (level, message) => {\n if (level === 'error') {\n console.error(`[LocalBrowser] ${message}`);\n }\n },\n });\n\n host.start().catch((err) => {\n console.error('Failed to start local browser:', err);\n browserSessions.delete(session.sessionId);\n });\n\n browserSessions.set(session.sessionId, {\n sessionId: session.sessionId,\n host,\n startedAt: Date.now(),\n mode,\n });\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode,\n expiresAt: session.expiresAt,\n note: 'Browser session started. The cloud agent can now control this browser. Use local_browser_stop to end the session.',\n });\n }\n\n if (tool === 'local_browser_status') {\n const input = args as unknown as LocalBrowserStatusInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: 'Session not found', sessionId: input.sessionId });\n }\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode: session.mode,\n startedAt: new Date(session.startedAt).toISOString(),\n uptimeMs: Date.now() - session.startedAt,\n });\n }\n\n if (tool === 'local_browser_stop') {\n const input = args as unknown as LocalBrowserStopInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: 'Session not found', sessionId: input.sessionId });\n }\n\n await session.host.stop();\n browserSessions.delete(input.sessionId);\n\n return toolJson({\n ok: true,\n sessionId: input.sessionId,\n note: 'Browser session stopped and browser closed.',\n });\n }\n\n if (tool === 'local_browser_list') {\n const sessions = Array.from(browserSessions.values()).map((s) => ({\n sessionId: s.sessionId,\n mode: s.mode,\n startedAt: new Date(s.startedAt).toISOString(),\n uptimeMs: Date.now() - s.startedAt,\n }));\n\n return toolJson({\n ok: true,\n count: sessions.length,\n sessions,\n });\n }\n\n if (tool === 'local_browser_run') {\n const input = args as unknown as {\n sessionId: string;\n instructions: string;\n startUrl?: string;\n };\n const apiUrl = resolveApiUrl();\n\n const session = browserSessions.get(input.sessionId);\n if (!session) {\n return toolJson({\n ok: false,\n error: 'Session not found locally. Make sure you started it with local_browser_start.',\n sessionId: input.sessionId,\n });\n }\n\n const response = await fetch(`${apiUrl}/local-browser/sessions/${input.sessionId}/run`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n instructions: input.instructions,\n startUrl: input.startUrl ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return toolJson({ ok: false, error: `Failed to start run: ${text}` });\n }\n\n const result = (await response.json()) as { ok: boolean; jobId: string; sessionId: string };\n\n return toolJson({\n ok: true,\n jobId: result.jobId,\n sessionId: result.sessionId,\n note: 'Test run started. The cloud agent is now controlling your local browser. You can watch the browser to see the test in action.',\n });\n }\n\n return null; // Not handled\n}\n\n// ==================== Internal helpers ====================\n\nasync function waitForResult(input: { apiUrl: string; token: string; runId: string }) {\n await streamUntilComplete(input);\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {\n credentials: 'include',\n headers: { authorization: `Bearer ${input.token}` },\n });\n const data = await response.json();\n const run = data?.data?.run ?? data?.run ?? data?.data;\n const summary = run?.summaryJson;\n return formatReport({ run, summary });\n}\n\nasync function streamUntilComplete(input: { apiUrl: string; token: string; runId: string }) {\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {\n headers: { authorization: `Bearer ${input.token}` },\n });\n\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (event.event === 'status') {\n try {\n const payload = JSON.parse(event.data);\n if (payload?.status === 'completed' || payload?.status === 'failed') {\n reader.cancel().catch(() => undefined);\n }\n } catch {}\n }\n if (event.event === 'complete' || event.event === 'error') {\n reader.cancel().catch(() => undefined);\n }\n },\n });\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n}\n\nfunction formatReport(input: { run: any; summary: any }) {\n if (!input.summary) {\n return {\n runId: input.run?.id,\n status: input.run?.status ?? 'unknown',\n summary: 'No final report available.',\n };\n }\n\n const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];\n const status = input.summary.status ?? input.run?.status ?? 'unknown';\n const issues =\n status === 'issues_found'\n ? (input.summary.notes ? [input.summary.notes] : ['Issues reported.'])\n : [];\n\n return {\n runId: input.run?.id,\n status,\n summary: input.summary.summary ?? 'Run completed.',\n testedItems: tested,\n issues,\n notes: input.summary.notes ?? null,\n };\n}\n","/**\n * Browser MCP tool registration wrapping BrowserToolExecutor.\n *\n * Thin layer that maps MCP tool calls to the shared executor,\n * ensuring the CLI gets the SAME semantic diff, snapshot formatting,\n * click validation, auto-snapshot, and recovery notices as the cloud agent.\n *\n * @module\n */\n\nimport { getBrowserToolDefinitionsWithLifecycle } from '@chatsdet/browser-core';\nimport {\n startDirectBrowser,\n stopDirectBrowser,\n requireDirectBrowser,\n hasDirectBrowser,\n} from './browser-session';\nimport { formatMCPContent, toolJson, toolText } from './tool-helpers';\n\n/**\n * Get tool definitions for browser MCP tools (including lifecycle).\n */\nexport function getBrowserMCPToolDefinitions() {\n return getBrowserToolDefinitionsWithLifecycle();\n}\n\n/**\n * Handle a browser tool call.\n * Returns null if the tool name is not a browser tool.\n */\nexport async function handleBrowserTool(name: string, args: Record<string, unknown>) {\n // Lifecycle tools\n if (name === 'browser_start') {\n try {\n await startDirectBrowser({\n headless: (args.headless as boolean) ?? false,\n storageStatePath: args.storageStatePath as string | undefined,\n });\n return toolJson({\n ok: true,\n note: 'Browser started. You can now use browser_navigate, browser_click, and other browser tools.',\n });\n } catch (err) {\n return toolJson({ ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n if (name === 'browser_stop') {\n try {\n await stopDirectBrowser();\n return toolJson({ ok: true, note: 'Browser stopped and cleaned up.' });\n } catch (err) {\n return toolJson({ ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n // All other browser_* tools require an active session\n if (!name.startsWith('browser_')) return null;\n\n let session;\n try {\n session = requireDirectBrowser();\n } catch (err) {\n return toolJson({ ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n\n const executor = session.executor;\n\n try {\n switch (name) {\n case 'browser_navigate':\n return formatMCPContent(await executor.navigate(args.url as string));\n\n case 'browser_navigate_back':\n return formatMCPContent(await executor.navigateBack());\n\n case 'browser_snapshot':\n return formatMCPContent(\n await executor.snapshot({\n mode: args.mode as 'both' | 'screenshot' | 'tree' | undefined,\n expand: args.expand as string | undefined,\n search: args.search as string | undefined,\n showCoordinateGrid: args.showCoordinateGrid as boolean | undefined,\n probeAt: args.probeAt as { x: number; y: number } | undefined,\n })\n );\n\n case 'browser_screenshot':\n return formatMCPContent(\n await executor.screenshot({\n fullPage: args.fullPage as boolean | undefined,\n element: args.element as string | undefined,\n ref: args.ref as string | undefined,\n label: args.label as string | undefined,\n returnImage: args.returnImage as boolean | undefined,\n })\n );\n\n case 'browser_evaluate':\n return formatMCPContent(\n await executor.evaluate({\n expression: args.function as string,\n element: args.element as string | undefined,\n ref: args.ref as string | undefined,\n })\n );\n\n case 'browser_console_messages':\n return formatMCPContent(\n await executor.consoleMessages({ onlyErrors: args.onlyErrors as boolean | undefined })\n );\n\n case 'browser_network_requests':\n return formatMCPContent(await executor.networkRequests());\n\n case 'browser_click':\n return formatMCPContent(\n await executor.click({\n ref: args.ref as string | undefined,\n x: args.x as number | undefined,\n y: args.y as number | undefined,\n element: args.element as string,\n doubleClick: args.doubleClick as boolean | undefined,\n button: args.button as string | undefined,\n modifiers: args.modifiers as string[] | undefined,\n })\n );\n\n case 'browser_hover':\n return formatMCPContent(\n await executor.hover({\n ref: args.ref as string,\n element: args.element as string,\n })\n );\n\n case 'browser_drag':\n return formatMCPContent(\n await executor.drag({\n startRef: args.startRef as string | undefined,\n endRef: args.endRef as string | undefined,\n startElement: args.startElement as string,\n endElement: args.endElement as string,\n startX: args.startX as number | undefined,\n startY: args.startY as number | undefined,\n endX: args.endX as number | undefined,\n endY: args.endY as number | undefined,\n })\n );\n\n case 'browser_type':\n return formatMCPContent(\n await executor.type({\n ref: args.ref as string,\n text: args.text as string,\n element: args.element as string,\n submit: args.submit as boolean | undefined,\n delay: args.delay as number | undefined,\n })\n );\n\n case 'browser_press_key':\n return formatMCPContent(await executor.pressKey(args.key as string));\n\n case 'browser_fill_form':\n return formatMCPContent(\n await executor.fillForm(\n args.fields as Array<{ ref: string; element?: string; value: string }>\n )\n );\n\n case 'browser_select_option':\n return formatMCPContent(\n await executor.selectOption({\n ref: args.ref as string,\n value: args.value as string,\n element: args.element as string,\n })\n );\n\n case 'browser_file_upload':\n return formatMCPContent(await executor.fileUpload(args.paths as string[]));\n\n case 'browser_scroll':\n return formatMCPContent(\n await executor.scroll({\n direction: args.direction as 'up' | 'down' | 'left' | 'right',\n amount: args.amount as number | undefined,\n withinRef: args.withinRef as string | undefined,\n toRef: args.toRef as string | undefined,\n x: args.x as number | undefined,\n y: args.y as number | undefined,\n })\n );\n\n case 'browser_handle_dialog':\n return formatMCPContent(\n await executor.handleDialog({\n action: args.action as 'accept' | 'dismiss',\n promptText: args.promptText as string | undefined,\n })\n );\n\n case 'browser_dismiss_overlay':\n return formatMCPContent(\n await executor.dismissOverlay({\n preferredStrategy: args.preferredStrategy as\n | 'escape'\n | 'close_button'\n | 'backdrop'\n | 'auto'\n | undefined,\n })\n );\n\n case 'browser_wait_for':\n return formatMCPContent(\n await executor.waitFor({\n timeSec: args.time as number | undefined,\n text: args.text as string | undefined,\n textGone: args.textGone as string | undefined,\n selector: args.selector as string | undefined,\n state: args.state as string | undefined,\n timeout: args.timeout as number | undefined,\n })\n );\n\n case 'browser_close':\n return formatMCPContent(await executor.close());\n\n case 'browser_resize':\n return formatMCPContent(\n await executor.resize(args.width as number, args.height as number)\n );\n\n case 'browser_tabs':\n return formatMCPContent(\n await executor.tabs({\n action: args.action as 'list' | 'new' | 'close' | 'select',\n index: args.index as number | undefined,\n })\n );\n\n case 'browser_list_downloads':\n return formatMCPContent(await executor.listDownloads());\n\n case 'browser_read_download':\n return formatMCPContent(\n await executor.readDownload(args.downloadId as number | undefined)\n );\n\n case 'browser_pdf_read':\n return formatMCPContent(await executor.fetchPdfText(args.url as string));\n\n default:\n return toolText(`Unknown browser tool: ${name}`);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return toolJson({ ok: false, error: message, tool: name });\n }\n}\n","/**\n * Direct browser session lifecycle for CLI MCP tools.\n *\n * Manages a singleton PlaywrightClient that the BrowserToolExecutor\n * controls directly (no cloud proxy).\n *\n * @module\n */\n\nimport {\n PlaywrightClient,\n BrowserToolExecutor,\n consoleLogger,\n type BrowserToolOptions,\n} from '@chatsdet/browser-core';\n\ninterface DirectBrowserSession {\n client: PlaywrightClient;\n executor: BrowserToolExecutor;\n startedAt: number;\n}\n\nlet activeSession: DirectBrowserSession | null = null;\n\n/**\n * Start a direct local browser session.\n * Default: headed mode so user can watch.\n */\nexport async function startDirectBrowser(opts?: {\n headless?: boolean;\n storageStatePath?: string;\n executorOptions?: Partial<BrowserToolOptions>;\n}): Promise<DirectBrowserSession> {\n if (activeSession) {\n throw new Error('A browser session is already active. Stop it first with browser_stop.');\n }\n\n const client = new PlaywrightClient({\n logger: consoleLogger,\n });\n\n await client.connect({\n browserMode: opts?.headless ? 'headless' : 'headed',\n storageStatePath: opts?.storageStatePath,\n });\n\n const executor = new BrowserToolExecutor(client, {\n autoSnapshotAfterAction: true,\n includeScreenshotWithSnapshot: true,\n clickValidation: true,\n logger: consoleLogger,\n ...opts?.executorOptions,\n });\n\n activeSession = {\n client,\n executor,\n startedAt: Date.now(),\n };\n\n // Cleanup on process exit\n const cleanup = async () => {\n if (activeSession) {\n try {\n await activeSession.client.disconnect();\n } catch {\n // Best effort\n }\n activeSession = null;\n }\n };\n\n process.on('exit', () => { cleanup(); });\n process.on('SIGINT', async () => { await cleanup(); process.exit(0); });\n process.on('SIGTERM', async () => { await cleanup(); process.exit(0); });\n\n return activeSession;\n}\n\n/**\n * Stop the active browser session.\n */\nexport async function stopDirectBrowser(): Promise<void> {\n if (!activeSession) {\n throw new Error('No active browser session to stop.');\n }\n\n await activeSession.client.disconnect();\n activeSession = null;\n}\n\n/**\n * Get the active browser session, or throw if none is running.\n */\nexport function requireDirectBrowser(): DirectBrowserSession {\n if (!activeSession) {\n throw new Error('No active browser session. Start one with browser_start first.');\n }\n return activeSession;\n}\n\n/**\n * Check if a direct browser session is currently active.\n */\nexport function hasDirectBrowser(): boolean {\n return activeSession !== null;\n}\n"],"mappings":"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","names":["process"]}
@@ -1,8 +1,9 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
1
2
  import {
2
3
  readStoredApiUrl,
3
4
  readStoredToken
4
- } from "./chunk-7R4YFGP6.js";
5
- import "./chunk-DGUM43GV.js";
5
+ } from "./chunk-PWWQGYFG.js";
6
+ import "./chunk-VLFUCAPZ.js";
6
7
 
7
8
  // src/psql.ts
8
9
  import process from "process";
@@ -121,4 +122,4 @@ Time: ${json.durationMs}ms`);
121
122
  export {
122
123
  runPsql
123
124
  };
124
- //# sourceMappingURL=psql-IVAPNYZV.js.map
125
+ //# sourceMappingURL=psql-WVIHMC6A.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/psql.ts"],"sourcesContent":["/**\n * CLI PSQL Passthrough\n *\n * Allows superadmins to execute read-only SQL queries against the production database.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst MAX_QUERY_SIZE = 10_000; // 10KB - reasonable for interactive use\n\ntype PsqlResponse = {\n ok: boolean;\n data?: Record<string, unknown>[];\n rowCount?: number;\n truncated?: boolean;\n durationMs?: number;\n error?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/**\n * Formats query results as a psql-style table.\n */\nfunction formatTable(data: Record<string, unknown>[]): string {\n if (data.length === 0) return \"(0 rows)\";\n\n const columns = Object.keys(data[0]);\n\n // Calculate column widths\n const widths = columns.map((col) =>\n Math.max(col.length, ...data.map((row) => String(row[col] ?? \"\").length))\n );\n\n // Build header\n const header = columns.map((col, i) => col.padEnd(widths[i])).join(\" | \");\n const separator = widths.map((w) => \"-\".repeat(w)).join(\"-+-\");\n\n // Build rows\n const rows = data.map((row) =>\n columns.map((col, i) => String(row[col] ?? \"\").padEnd(widths[i])).join(\" | \")\n );\n\n return [header, separator, ...rows, `(${data.length} rows)`].join(\"\\n\");\n}\n\nexport async function runPsql(argv: string[]): Promise<void> {\n const profile = getArgValue(argv, \"--profile\");\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken(profile));\n\n const jsonOutput = hasFlag(argv, \"--json\");\n\n // Get query: either --query value or remaining args joined\n let query = getArgValue(argv, \"--query\");\n if (!query) {\n // Filter out flags and their values\n const flagsWithValues = [\"--api-url\", \"--token\", \"--query\", \"--profile\"];\n const queryParts: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n if (flagsWithValues.includes(argv[i])) {\n i++; // Skip flag value\n continue;\n }\n if (argv[i].startsWith(\"--\")) continue;\n queryParts.push(argv[i]);\n }\n query = queryParts.join(\" \");\n }\n\n if (!query) {\n console.error(\"Error: No query provided.\");\n console.error(\"\");\n console.error(\"Usage: canary psql <query> [--json]\");\n console.error(' canary psql --query \"SELECT * FROM users LIMIT 10\"');\n console.error(\"\");\n console.error(\"Examples:\");\n console.error(\" canary psql SELECT id, status FROM jobs LIMIT 5\");\n console.error(' canary psql \"SELECT * FROM jobs WHERE status = \\'running\\'\" --json');\n process.exit(1);\n }\n\n if (query.length > MAX_QUERY_SIZE) {\n console.error(`Error: Query too large (${query.length} chars, max ${MAX_QUERY_SIZE})`);\n console.error(\"For large queries, consider using psql directly with appropriate credentials.\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n try {\n const res = await fetch(`${apiUrl}/superadmin/psql`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ query }),\n });\n\n // Handle HTTP errors before parsing JSON\n if (!res.ok) {\n const text = await res.text();\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n if (res.status === 404) {\n console.error(\"Error: Endpoint not found. The psql feature may not be deployed to this environment.\");\n process.exit(1);\n }\n\n // Try to parse error as JSON, fallback to raw text\n try {\n const errorJson = JSON.parse(text) as { error?: string };\n console.error(`Error: ${errorJson.error ?? text}`);\n } catch {\n console.error(`Error (${res.status}): ${text || res.statusText}`);\n }\n process.exit(1);\n }\n\n const json = (await res.json()) as PsqlResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(json.data, null, 2));\n } else {\n console.log(formatTable(json.data ?? []));\n if (json.truncated) {\n console.log(`\\nNote: Results truncated to ${json.rowCount} rows`);\n }\n console.log(`\\nTime: ${json.durationMs}ms`);\n }\n } catch (err) {\n console.error(`Failed to execute query: ${err}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/psql.ts"],"sourcesContent":["/**\n * CLI PSQL Passthrough\n *\n * Allows superadmins to execute read-only SQL queries against the production database.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst MAX_QUERY_SIZE = 10_000; // 10KB - reasonable for interactive use\n\ntype PsqlResponse = {\n ok: boolean;\n data?: Record<string, unknown>[];\n rowCount?: number;\n truncated?: boolean;\n durationMs?: number;\n error?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/**\n * Formats query results as a psql-style table.\n */\nfunction formatTable(data: Record<string, unknown>[]): string {\n if (data.length === 0) return \"(0 rows)\";\n\n const columns = Object.keys(data[0]);\n\n // Calculate column widths\n const widths = columns.map((col) =>\n Math.max(col.length, ...data.map((row) => String(row[col] ?? \"\").length))\n );\n\n // Build header\n const header = columns.map((col, i) => col.padEnd(widths[i])).join(\" | \");\n const separator = widths.map((w) => \"-\".repeat(w)).join(\"-+-\");\n\n // Build rows\n const rows = data.map((row) =>\n columns.map((col, i) => String(row[col] ?? \"\").padEnd(widths[i])).join(\" | \")\n );\n\n return [header, separator, ...rows, `(${data.length} rows)`].join(\"\\n\");\n}\n\nexport async function runPsql(argv: string[]): Promise<void> {\n const profile = getArgValue(argv, \"--profile\");\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken(profile));\n\n const jsonOutput = hasFlag(argv, \"--json\");\n\n // Get query: either --query value or remaining args joined\n let query = getArgValue(argv, \"--query\");\n if (!query) {\n // Filter out flags and their values\n const flagsWithValues = [\"--api-url\", \"--token\", \"--query\", \"--profile\"];\n const queryParts: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n if (flagsWithValues.includes(argv[i])) {\n i++; // Skip flag value\n continue;\n }\n if (argv[i].startsWith(\"--\")) continue;\n queryParts.push(argv[i]);\n }\n query = queryParts.join(\" \");\n }\n\n if (!query) {\n console.error(\"Error: No query provided.\");\n console.error(\"\");\n console.error(\"Usage: canary psql <query> [--json]\");\n console.error(' canary psql --query \"SELECT * FROM users LIMIT 10\"');\n console.error(\"\");\n console.error(\"Examples:\");\n console.error(\" canary psql SELECT id, status FROM jobs LIMIT 5\");\n console.error(' canary psql \"SELECT * FROM jobs WHERE status = \\'running\\'\" --json');\n process.exit(1);\n }\n\n if (query.length > MAX_QUERY_SIZE) {\n console.error(`Error: Query too large (${query.length} chars, max ${MAX_QUERY_SIZE})`);\n console.error(\"For large queries, consider using psql directly with appropriate credentials.\");\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n try {\n const res = await fetch(`${apiUrl}/superadmin/psql`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ query }),\n });\n\n // Handle HTTP errors before parsing JSON\n if (!res.ok) {\n const text = await res.text();\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n if (res.status === 404) {\n console.error(\"Error: Endpoint not found. The psql feature may not be deployed to this environment.\");\n process.exit(1);\n }\n\n // Try to parse error as JSON, fallback to raw text\n try {\n const errorJson = JSON.parse(text) as { error?: string };\n console.error(`Error: ${errorJson.error ?? text}`);\n } catch {\n console.error(`Error (${res.status}): ${text || res.statusText}`);\n }\n process.exit(1);\n }\n\n const json = (await res.json()) as PsqlResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(json.data, null, 2));\n } else {\n console.log(formatTable(json.data ?? []));\n if (json.truncated) {\n console.log(`\\nNote: Results truncated to ${json.rowCount} rows`);\n }\n console.log(`\\nTime: ${json.durationMs}ms`);\n }\n } catch (err) {\n console.error(`Failed to execute query: ${err}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,334 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ import {
3
+ apiRequest,
4
+ fetchList
5
+ } from "./chunk-6WWHXWCS.js";
6
+ import {
7
+ getArgValue,
8
+ hasFlag,
9
+ resolveConfig
10
+ } from "./chunk-PWWQGYFG.js";
11
+ import "./chunk-VLFUCAPZ.js";
12
+
13
+ // src/record.ts
14
+ import fs from "fs/promises";
15
+ import path from "path";
16
+ import os from "os";
17
+ import readline from "readline";
18
+ import process from "process";
19
+
20
+ // src/record-interaction-script.ts
21
+ var INTERACTION_CAPTURE_SCRIPT = `
22
+ (function() {
23
+ if (window.__canaryRecordedEvents) return;
24
+ window.__canaryRecordedEvents = [];
25
+
26
+ function push(event) {
27
+ window.__canaryRecordedEvents.push(event);
28
+ }
29
+
30
+ document.addEventListener('click', function(e) {
31
+ var el = e.target;
32
+ push({
33
+ type: 'click',
34
+ ts: Date.now(),
35
+ x: e.clientX,
36
+ y: e.clientY,
37
+ tagName: el.tagName || '',
38
+ id: el.id || undefined,
39
+ testId: el.getAttribute('data-testid') || undefined,
40
+ ariaLabel: el.getAttribute('aria-label') || undefined,
41
+ role: el.getAttribute('role') || undefined,
42
+ textContent: (el.textContent || '').slice(0, 200).trim() || undefined,
43
+ className: el.className && typeof el.className === 'string' ? el.className.slice(0, 200) : undefined,
44
+ });
45
+ }, true);
46
+
47
+ document.addEventListener('input', function(e) {
48
+ var el = e.target;
49
+ var isPassword = el.type === 'password';
50
+ push({
51
+ type: 'input',
52
+ ts: Date.now(),
53
+ tagName: el.tagName || '',
54
+ id: el.id || undefined,
55
+ inputType: el.type || undefined,
56
+ ariaLabel: el.getAttribute('aria-label') || undefined,
57
+ value: isPassword ? '***' : (el.value || ''),
58
+ });
59
+ }, true);
60
+
61
+ document.addEventListener('keydown', function(e) {
62
+ if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {
63
+ push({
64
+ type: 'keydown',
65
+ ts: Date.now(),
66
+ key: e.key,
67
+ });
68
+ }
69
+ }, true);
70
+
71
+ window.addEventListener('beforeunload', function() {
72
+ push({
73
+ type: 'navigation',
74
+ ts: Date.now(),
75
+ url: window.location.href,
76
+ });
77
+ });
78
+
79
+ window.addEventListener('popstate', function() {
80
+ push({
81
+ type: 'navigation',
82
+ ts: Date.now(),
83
+ url: window.location.href,
84
+ });
85
+ });
86
+ })();
87
+ `;
88
+
89
+ // src/record.ts
90
+ async function promptSelection(items, prompt) {
91
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
92
+ return new Promise((resolve) => {
93
+ for (let i = 0; i < items.length; i++) {
94
+ console.log(` ${i + 1}. ${items[i]}`);
95
+ }
96
+ rl.question(`
97
+ ${prompt} `, (answer) => {
98
+ rl.close();
99
+ const idx = parseInt(answer, 10) - 1;
100
+ if (isNaN(idx) || idx < 0 || idx >= items.length) {
101
+ console.error("Invalid selection.");
102
+ process.exit(1);
103
+ }
104
+ resolve(idx);
105
+ });
106
+ });
107
+ }
108
+ async function runRecord(argv) {
109
+ if (hasFlag(argv, "--help", "-h")) {
110
+ console.log(
111
+ [
112
+ "Usage: canary record [options]",
113
+ "",
114
+ "Record browser interactions for flow creation.",
115
+ "",
116
+ "Options:",
117
+ " --credential <id> Credential ID (skip interactive selection)",
118
+ " --url <startUrl> URL to navigate to after launch",
119
+ " --output <dir> Local output directory (default: temp dir)",
120
+ " --env <env> Environment (local, dev, prod)",
121
+ " --no-upload Save locally only, skip API upload",
122
+ "",
123
+ "Press Ctrl+C to stop recording."
124
+ ].join("\n")
125
+ );
126
+ return;
127
+ }
128
+ const config = await resolveConfig(argv);
129
+ const credentialArg = getArgValue(argv, "--credential");
130
+ const startUrl = getArgValue(argv, "--url");
131
+ const outputDir = getArgValue(argv, "--output");
132
+ const skipUpload = hasFlag(argv, "--no-upload");
133
+ console.log("Fetching credentials...");
134
+ const credentials = await fetchList(
135
+ config.apiUrl,
136
+ config.token,
137
+ "/org/credentials",
138
+ "credentials"
139
+ );
140
+ if (credentials.length === 0) {
141
+ console.error("No credentials found. Create one first in the web app.");
142
+ process.exit(1);
143
+ }
144
+ let credential;
145
+ if (credentialArg) {
146
+ const found = credentials.find((c) => c.id === credentialArg);
147
+ if (!found) {
148
+ console.error(`Credential not found: ${credentialArg}`);
149
+ process.exit(1);
150
+ }
151
+ credential = found;
152
+ } else {
153
+ console.log("\nSelect a credential:\n");
154
+ const labels = credentials.map(
155
+ (c) => `${c.name} (${c.propertyName ?? c.propertyId})`
156
+ );
157
+ const idx = await promptSelection(labels, "Enter number:");
158
+ credential = credentials[idx];
159
+ }
160
+ console.log(`Using credential: ${credential.name}`);
161
+ const propertyId = credential.propertyId;
162
+ let storageStatePath;
163
+ if (credential.storageStateS3Key) {
164
+ console.log("Downloading storage state...");
165
+ const tmpFile = path.join(os.tmpdir(), `canary-ss-${Date.now()}.json`);
166
+ const res = await fetch(
167
+ `${config.apiUrl}/org/properties/${propertyId}/credentials/${credential.id}/storage-state/download`,
168
+ {
169
+ headers: { Authorization: `Bearer ${config.token}` },
170
+ redirect: "follow"
171
+ }
172
+ );
173
+ if (res.ok) {
174
+ const body = await res.text();
175
+ await fs.writeFile(tmpFile, body, "utf-8");
176
+ storageStatePath = tmpFile;
177
+ console.log("Storage state loaded.");
178
+ } else {
179
+ console.warn("Could not download storage state, continuing without it.");
180
+ }
181
+ }
182
+ let loginUrl = credential.loginUrl;
183
+ if (!loginUrl) {
184
+ const detail = await apiRequest(
185
+ config.apiUrl,
186
+ config.token,
187
+ "GET",
188
+ `/org/properties/${propertyId}/credentials/${credential.id}`
189
+ );
190
+ loginUrl = detail.credential?.loginUrl ?? void 0;
191
+ }
192
+ const navigateUrl = startUrl ?? loginUrl;
193
+ if (!navigateUrl) {
194
+ console.warn("No start URL provided and no login URL on credential. Browser will open to about:blank.");
195
+ }
196
+ console.log("Launching browser...");
197
+ const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import("@chatsdet/browser-core");
198
+ const videoDir = path.join(os.tmpdir(), `canary-record-video-${Date.now()}`);
199
+ await fs.mkdir(videoDir, { recursive: true });
200
+ const client = new PlaywrightClient({ logger: consoleLogger });
201
+ await client.connect({
202
+ browserMode: "headed",
203
+ storageStatePath,
204
+ recordVideo: { dir: videoDir }
205
+ });
206
+ const page = await client.getPageForReplay();
207
+ if (!page) {
208
+ console.error("Failed to get browser page.");
209
+ await client.disconnect();
210
+ process.exit(1);
211
+ }
212
+ await page.addInitScript(INTERACTION_CAPTURE_SCRIPT);
213
+ await page.evaluate(INTERACTION_CAPTURE_SCRIPT);
214
+ if (navigateUrl) {
215
+ console.log(`Navigating to ${navigateUrl}`);
216
+ await page.goto(navigateUrl, { waitUntil: "domcontentloaded", timeout: 3e4 }).catch(() => {
217
+ console.warn("Navigation timed out or failed, continuing anyway.");
218
+ });
219
+ }
220
+ const startedAt = /* @__PURE__ */ new Date();
221
+ const events = [];
222
+ let running = true;
223
+ let snapshotCounter = 0;
224
+ console.log("\nRecording started. Interact with the browser.");
225
+ console.log("Press Ctrl+C to stop recording.\n");
226
+ const pollInterval = setInterval(async () => {
227
+ if (!running) return;
228
+ try {
229
+ const raw = await page.evaluate(() => {
230
+ const evts = window.__canaryRecordedEvents ?? [];
231
+ window.__canaryRecordedEvents = [];
232
+ return evts;
233
+ });
234
+ if (!Array.isArray(raw) || raw.length === 0) return;
235
+ for (const evt of raw) {
236
+ if (evt.type === "click") {
237
+ const clickEvt = evt;
238
+ try {
239
+ const info = await captureElementAtPoint(page, clickEvt.x, clickEvt.y);
240
+ if (info) {
241
+ clickEvt.elementInfo = info;
242
+ }
243
+ } catch {
244
+ }
245
+ snapshotCounter++;
246
+ if (snapshotCounter % 3 === 1) {
247
+ try {
248
+ const snapshot = await page._snapshotForAI({ mode: "full" });
249
+ clickEvt.snapshot = snapshot;
250
+ } catch {
251
+ }
252
+ }
253
+ }
254
+ if (evt.type === "navigation") {
255
+ try {
256
+ const snapshot = await page._snapshotForAI({ mode: "full" });
257
+ evt.snapshot = snapshot;
258
+ } catch {
259
+ }
260
+ }
261
+ events.push(evt);
262
+ const label = evt.type === "click" ? `click (${evt.tagName})` : evt.type === "input" ? `input` : evt.type === "navigation" ? `navigation \u2192 ${evt.url}` : `keydown: ${evt.key}`;
263
+ process.stdout.write(` [${events.length}] ${label}
264
+ `);
265
+ }
266
+ } catch {
267
+ }
268
+ }, 500);
269
+ const cleanup = async () => {
270
+ if (!running) return;
271
+ running = false;
272
+ clearInterval(pollInterval);
273
+ const endedAt = /* @__PURE__ */ new Date();
274
+ console.log(`
275
+ Recording stopped. ${events.length} events captured.`);
276
+ console.log("Saving video...");
277
+ const videoResult = await client.saveVideo();
278
+ await client.disconnect();
279
+ if (storageStatePath) {
280
+ await fs.unlink(storageStatePath).catch(() => {
281
+ });
282
+ }
283
+ const outDir = outputDir ?? path.join(os.tmpdir(), `canary-recording-${Date.now()}`);
284
+ await fs.mkdir(outDir, { recursive: true });
285
+ const eventsPath = path.join(outDir, "events.jsonl");
286
+ const lines = events.map((e) => JSON.stringify(e)).join("\n");
287
+ await fs.writeFile(eventsPath, lines, "utf-8");
288
+ console.log(`Events saved: ${eventsPath}`);
289
+ let videoPath;
290
+ if (videoResult?.videoPath) {
291
+ videoPath = path.join(outDir, "video.webm");
292
+ await fs.copyFile(videoResult.videoPath, videoPath);
293
+ console.log(`Video saved: ${videoPath}`);
294
+ }
295
+ if (!skipUpload && events.length > 0) {
296
+ console.log("Uploading recording...");
297
+ try {
298
+ const formData = new FormData();
299
+ formData.append("propertyId", propertyId);
300
+ formData.append("credentialId", credential.id);
301
+ const eventsBlob = new Blob([lines], { type: "application/x-ndjson" });
302
+ formData.append("events", eventsBlob, "events.jsonl");
303
+ if (videoPath) {
304
+ const videoBuffer = await fs.readFile(videoPath);
305
+ const videoBlob = new Blob([videoBuffer], { type: "video/webm" });
306
+ formData.append("video", videoBlob, "video.webm");
307
+ }
308
+ const res = await fetch(`${config.apiUrl}/org/recordings/upload`, {
309
+ method: "POST",
310
+ headers: { Authorization: `Bearer ${config.token}` },
311
+ body: formData
312
+ });
313
+ const json = await res.json();
314
+ if (json.ok) {
315
+ console.log(`Recording uploaded. ID: ${json.recordingId}`);
316
+ } else {
317
+ console.error(`Upload failed: ${json.error ?? "Unknown error"}`);
318
+ }
319
+ } catch (err) {
320
+ console.error("Upload failed:", err instanceof Error ? err.message : err);
321
+ }
322
+ }
323
+ console.log(`
324
+ Output directory: ${outDir}`);
325
+ process.exit(0);
326
+ };
327
+ process.on("SIGINT", () => void cleanup());
328
+ process.on("SIGTERM", () => void cleanup());
329
+ page.on("close", () => void cleanup());
330
+ }
331
+ export {
332
+ runRecord
333
+ };
334
+ //# sourceMappingURL=record-X4SVNYP3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/record.ts","../src/record-interaction-script.ts"],"sourcesContent":["/**\n * `canary record` — Record browser interactions for flow creation.\n *\n * Launches a headed browser, injects an in-page capture script,\n * and records user interactions (clicks, inputs, navigation) enriched\n * with accessibility snapshots and element info. On stop, bundles\n * everything as JSONL + video and uploads to the API.\n *\n * @module record\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport readline from \"node:readline\";\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest, fetchList } from \"./cli-helpers.js\";\nimport { INTERACTION_CAPTURE_SCRIPT } from \"./record-interaction-script.js\";\n\n/** Minimal recording event types (mirrors @chatsdet/types/recording) */\ninterface RecordingClickEvent {\n type: \"click\";\n ts: number;\n x: number;\n y: number;\n tagName: string;\n id?: string;\n testId?: string;\n ariaLabel?: string;\n role?: string;\n textContent?: string;\n className?: string;\n elementInfo?: Record<string, unknown>;\n snapshot?: string;\n}\n\ntype RecordingEvent =\n | RecordingClickEvent\n | { type: \"input\"; ts: number; tagName: string; id?: string; value: string }\n | { type: \"keydown\"; ts: number; key: string }\n | { type: \"navigation\"; ts: number; url: string; snapshot?: string };\n\ninterface Credential {\n id: string;\n name: string;\n propertyId: string;\n propertyName?: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\ninterface CredentialDetail {\n id: string;\n name: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\ninterface Property {\n id: string;\n name: string;\n baseUrl?: string;\n}\n\nasync function promptSelection(items: string[], prompt: string): Promise<number> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise<number>((resolve) => {\n for (let i = 0; i < items.length; i++) {\n console.log(` ${i + 1}. ${items[i]}`);\n }\n rl.question(`\\n${prompt} `, (answer) => {\n rl.close();\n const idx = parseInt(answer, 10) - 1;\n if (isNaN(idx) || idx < 0 || idx >= items.length) {\n console.error(\"Invalid selection.\");\n process.exit(1);\n }\n resolve(idx);\n });\n });\n}\n\nexport async function runRecord(argv: string[]): Promise<void> {\n if (hasFlag(argv, \"--help\", \"-h\")) {\n console.log(\n [\n \"Usage: canary record [options]\",\n \"\",\n \"Record browser interactions for flow creation.\",\n \"\",\n \"Options:\",\n \" --credential <id> Credential ID (skip interactive selection)\",\n \" --url <startUrl> URL to navigate to after launch\",\n \" --output <dir> Local output directory (default: temp dir)\",\n \" --env <env> Environment (local, dev, prod)\",\n \" --no-upload Save locally only, skip API upload\",\n \"\",\n \"Press Ctrl+C to stop recording.\",\n ].join(\"\\n\")\n );\n return;\n }\n\n const config = await resolveConfig(argv);\n const credentialArg = getArgValue(argv, \"--credential\");\n const startUrl = getArgValue(argv, \"--url\");\n const outputDir = getArgValue(argv, \"--output\");\n const skipUpload = hasFlag(argv, \"--no-upload\");\n\n // 1. Fetch credentials\n console.log(\"Fetching credentials...\");\n const credentials = await fetchList<Credential>(\n config.apiUrl,\n config.token,\n \"/org/credentials\",\n \"credentials\"\n );\n\n if (credentials.length === 0) {\n console.error(\"No credentials found. Create one first in the web app.\");\n process.exit(1);\n }\n\n // 2. Select credential\n let credential: Credential;\n if (credentialArg) {\n const found = credentials.find((c) => c.id === credentialArg);\n if (!found) {\n console.error(`Credential not found: ${credentialArg}`);\n process.exit(1);\n }\n credential = found;\n } else {\n console.log(\"\\nSelect a credential:\\n\");\n const labels = credentials.map(\n (c) => `${c.name} (${c.propertyName ?? c.propertyId})`\n );\n const idx = await promptSelection(labels, \"Enter number:\");\n credential = credentials[idx];\n }\n\n console.log(`Using credential: ${credential.name}`);\n\n const propertyId = credential.propertyId;\n\n // 3. Download storage state if available\n let storageStatePath: string | undefined;\n if (credential.storageStateS3Key) {\n console.log(\"Downloading storage state...\");\n const tmpFile = path.join(os.tmpdir(), `canary-ss-${Date.now()}.json`);\n const res = await fetch(\n `${config.apiUrl}/org/properties/${propertyId}/credentials/${credential.id}/storage-state/download`,\n {\n headers: { Authorization: `Bearer ${config.token}` },\n redirect: \"follow\",\n }\n );\n if (res.ok) {\n const body = await res.text();\n await fs.writeFile(tmpFile, body, \"utf-8\");\n storageStatePath = tmpFile;\n console.log(\"Storage state loaded.\");\n } else {\n console.warn(\"Could not download storage state, continuing without it.\");\n }\n }\n\n // 4. Get credential detail for loginUrl\n let loginUrl = credential.loginUrl;\n if (!loginUrl) {\n const detail = await apiRequest<{ ok: boolean; credential?: CredentialDetail }>(\n config.apiUrl,\n config.token,\n \"GET\",\n `/org/properties/${propertyId}/credentials/${credential.id}`\n );\n loginUrl = detail.credential?.loginUrl ?? undefined;\n }\n\n // 5. Determine start URL\n const navigateUrl = startUrl ?? loginUrl;\n if (!navigateUrl) {\n console.warn(\"No start URL provided and no login URL on credential. Browser will open to about:blank.\");\n }\n\n // 6. Launch browser\n console.log(\"Launching browser...\");\n\n // Lazy-load playwright-dependent modules\n const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import(\"@chatsdet/browser-core\");\n\n const videoDir = path.join(os.tmpdir(), `canary-record-video-${Date.now()}`);\n await fs.mkdir(videoDir, { recursive: true });\n\n const client = new PlaywrightClient({ logger: consoleLogger });\n await client.connect({\n browserMode: \"headed\",\n storageStatePath,\n recordVideo: { dir: videoDir },\n });\n\n const page = await client.getPageForReplay();\n if (!page) {\n console.error(\"Failed to get browser page.\");\n await client.disconnect();\n process.exit(1);\n }\n\n // 7. Inject interaction capture script\n await page.addInitScript(INTERACTION_CAPTURE_SCRIPT);\n await page.evaluate(INTERACTION_CAPTURE_SCRIPT);\n\n // 8. Navigate\n if (navigateUrl) {\n console.log(`Navigating to ${navigateUrl}`);\n await page.goto(navigateUrl, { waitUntil: \"domcontentloaded\", timeout: 30_000 }).catch(() => {\n console.warn(\"Navigation timed out or failed, continuing anyway.\");\n });\n }\n\n const startedAt = new Date();\n const events: RecordingEvent[] = [];\n let running = true;\n let snapshotCounter = 0;\n\n console.log(\"\\nRecording started. Interact with the browser.\");\n console.log(\"Press Ctrl+C to stop recording.\\n\");\n\n // 9. Poll loop — collect events from the page\n const pollInterval = setInterval(async () => {\n if (!running) return;\n try {\n const raw = await page.evaluate(() => {\n const evts = (window as unknown as { __canaryRecordedEvents?: unknown[] }).__canaryRecordedEvents ?? [];\n (window as unknown as { __canaryRecordedEvents: unknown[] }).__canaryRecordedEvents = [];\n return evts;\n });\n\n if (!Array.isArray(raw) || raw.length === 0) return;\n\n for (const evt of raw as RecordingEvent[]) {\n // Enrich click events with element info and periodic snapshots\n if (evt.type === \"click\") {\n const clickEvt = evt as RecordingClickEvent;\n try {\n const info = await captureElementAtPoint(page, clickEvt.x, clickEvt.y);\n if (info) {\n clickEvt.elementInfo = info as unknown as Record<string, unknown>;\n }\n } catch {\n // Element may have disappeared\n }\n\n // Take a snapshot every 3rd click to avoid overhead\n snapshotCounter++;\n if (snapshotCounter % 3 === 1) {\n try {\n const snapshot = await (page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> })._snapshotForAI({ mode: \"full\" });\n clickEvt.snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n }\n\n // Enrich navigation events with snapshot\n if (evt.type === \"navigation\") {\n try {\n const snapshot = await (page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> })._snapshotForAI({ mode: \"full\" });\n (evt as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n events.push(evt);\n const label =\n evt.type === \"click\"\n ? `click (${(evt as RecordingClickEvent).tagName})`\n : evt.type === \"input\"\n ? `input`\n : evt.type === \"navigation\"\n ? `navigation → ${(evt as { url: string }).url}`\n : `keydown: ${(evt as { key: string }).key}`;\n process.stdout.write(` [${events.length}] ${label}\\n`);\n }\n } catch {\n // Page may have been closed\n }\n }, 500);\n\n // 10. Handle Ctrl+C\n const cleanup = async () => {\n if (!running) return;\n running = false;\n clearInterval(pollInterval);\n\n const endedAt = new Date();\n console.log(`\\nRecording stopped. ${events.length} events captured.`);\n\n // Save video\n console.log(\"Saving video...\");\n const videoResult = await client.saveVideo();\n await client.disconnect();\n\n // Clean up temp storage state\n if (storageStatePath) {\n await fs.unlink(storageStatePath).catch(() => {});\n }\n\n // Prepare output directory\n const outDir = outputDir ?? path.join(os.tmpdir(), `canary-recording-${Date.now()}`);\n await fs.mkdir(outDir, { recursive: true });\n\n // Write events JSONL\n const eventsPath = path.join(outDir, \"events.jsonl\");\n const lines = events.map((e) => JSON.stringify(e)).join(\"\\n\");\n await fs.writeFile(eventsPath, lines, \"utf-8\");\n console.log(`Events saved: ${eventsPath}`);\n\n // Copy video if available\n let videoPath: string | undefined;\n if (videoResult?.videoPath) {\n videoPath = path.join(outDir, \"video.webm\");\n await fs.copyFile(videoResult.videoPath, videoPath);\n console.log(`Video saved: ${videoPath}`);\n }\n\n // Upload to API\n if (!skipUpload && events.length > 0) {\n console.log(\"Uploading recording...\");\n try {\n const formData = new FormData();\n formData.append(\"propertyId\", propertyId);\n formData.append(\"credentialId\", credential.id);\n\n const eventsBlob = new Blob([lines], { type: \"application/x-ndjson\" });\n formData.append(\"events\", eventsBlob, \"events.jsonl\");\n\n if (videoPath) {\n const videoBuffer = await fs.readFile(videoPath);\n const videoBlob = new Blob([videoBuffer], { type: \"video/webm\" });\n formData.append(\"video\", videoBlob, \"video.webm\");\n }\n\n const res = await fetch(`${config.apiUrl}/org/recordings/upload`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${config.token}` },\n body: formData,\n });\n\n const json = (await res.json()) as {\n ok: boolean;\n recordingId?: string;\n error?: string;\n };\n\n if (json.ok) {\n console.log(`Recording uploaded. ID: ${json.recordingId}`);\n } else {\n console.error(`Upload failed: ${json.error ?? \"Unknown error\"}`);\n }\n } catch (err) {\n console.error(\"Upload failed:\", err instanceof Error ? err.message : err);\n }\n }\n\n console.log(`\\nOutput directory: ${outDir}`);\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => void cleanup());\n process.on(\"SIGTERM\", () => void cleanup());\n\n // Also stop if the page is closed\n page.on(\"close\", () => void cleanup());\n}\n","/**\n * In-page interaction capture script injected via addInitScript.\n * Captures user interactions to window.__canaryRecordedEvents.\n *\n * @module record-interaction-script\n */\n\nexport const INTERACTION_CAPTURE_SCRIPT = `\n(function() {\n if (window.__canaryRecordedEvents) return;\n window.__canaryRecordedEvents = [];\n\n function push(event) {\n window.__canaryRecordedEvents.push(event);\n }\n\n document.addEventListener('click', function(e) {\n var el = e.target;\n push({\n type: 'click',\n ts: Date.now(),\n x: e.clientX,\n y: e.clientY,\n tagName: el.tagName || '',\n id: el.id || undefined,\n testId: el.getAttribute('data-testid') || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n role: el.getAttribute('role') || undefined,\n textContent: (el.textContent || '').slice(0, 200).trim() || undefined,\n className: el.className && typeof el.className === 'string' ? el.className.slice(0, 200) : undefined,\n });\n }, true);\n\n document.addEventListener('input', function(e) {\n var el = e.target;\n var isPassword = el.type === 'password';\n push({\n type: 'input',\n ts: Date.now(),\n tagName: el.tagName || '',\n id: el.id || undefined,\n inputType: el.type || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: isPassword ? '***' : (el.value || ''),\n });\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {\n push({\n type: 'keydown',\n ts: Date.now(),\n key: e.key,\n });\n }\n }, true);\n\n window.addEventListener('beforeunload', function() {\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n });\n });\n\n window.addEventListener('popstate', function() {\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n });\n });\n})();\n`;\n"],"mappings":"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","names":[]}
@@ -1,8 +1,9 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
1
2
  import {
2
3
  readStoredApiUrl,
3
4
  readStoredToken
4
- } from "./chunk-7R4YFGP6.js";
5
- import "./chunk-DGUM43GV.js";
5
+ } from "./chunk-PWWQGYFG.js";
6
+ import "./chunk-VLFUCAPZ.js";
6
7
 
7
8
  // src/redis.ts
8
9
  import process from "process";
@@ -127,4 +128,4 @@ Time: ${json_response.durationMs}ms`);
127
128
  export {
128
129
  runRedis
129
130
  };
130
- //# sourceMappingURL=redis-LWY7L6AS.js.map
131
+ //# sourceMappingURL=redis-RGHECKV5.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/redis.ts"],"sourcesContent":["/**\n * CLI Redis Passthrough\n *\n * Allows superadmins to execute read-only Redis commands against the production cache.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst MAX_COMMAND_SIZE = 10_000; // 10KB - reasonable for interactive use\n\ntype RedisResponse = {\n ok: boolean;\n data?: unknown;\n durationMs?: number;\n error?: string;\n code?: \"DISABLED\" | \"BLOCKED\";\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/**\n * Formats Redis response for display.\n */\nfunction formatOutput(data: unknown): string {\n if (data === null) {\n return \"(nil)\";\n }\n\n if (typeof data === \"string\") {\n return data;\n }\n\n if (typeof data === \"number\") {\n return `(integer) ${data}`;\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) {\n return \"(empty array)\";\n }\n return data\n .map((item, index) => `${index + 1}) ${formatOutput(item)}`)\n .join(\"\\n\");\n }\n\n if (typeof data === \"object\") {\n return JSON.stringify(data, null, 2);\n }\n\n return String(data);\n}\n\nexport async function runRedis(argv: string[]): Promise<void> {\n const profile = getArgValue(argv, \"--profile\");\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken(profile));\n\n const jsonOutput = hasFlag(argv, \"--json\");\n\n // Get command: remaining args after filtering flags\n const flagsWithValues = [\"--api-url\", \"--token\", \"--profile\"];\n const commandParts: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n if (flagsWithValues.includes(argv[i])) {\n i++; // Skip flag value\n continue;\n }\n if (argv[i].startsWith(\"--\")) continue;\n commandParts.push(argv[i]);\n }\n const command = commandParts.join(\" \");\n\n if (!command) {\n console.error(\"Error: No command provided.\");\n console.error(\"\");\n console.error(\"Usage: canary redis <command> [--json]\");\n console.error(' canary redis KEYS \"job:*\"');\n console.error(\"\");\n console.error(\"Examples:\");\n console.error(\" canary redis PING\");\n console.error(\" canary redis INFO\");\n console.error(' canary redis KEYS \"job:*\"');\n console.error(' canary redis HGETALL \"job:123:status\"');\n console.error(' canary redis GET \"some:key\" --json');\n process.exit(1);\n }\n\n if (command.length > MAX_COMMAND_SIZE) {\n console.error(`Error: Command too large (${command.length} chars, max ${MAX_COMMAND_SIZE})`);\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n try {\n const res = await fetch(`${apiUrl}/superadmin/redis`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ command }),\n });\n\n // Handle HTTP errors before parsing JSON\n if (!res.ok) {\n const text = await res.text();\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n if (res.status === 404) {\n console.error(\"Error: Endpoint not found. The redis feature may not be deployed to this environment.\");\n process.exit(1);\n }\n\n // Try to parse error as JSON, fallback to raw text\n try {\n const errorJson = JSON.parse(text) as { error?: string };\n console.error(`Error: ${errorJson.error ?? text}`);\n } catch {\n console.error(`Error (${res.status}): ${text || res.statusText}`);\n }\n process.exit(1);\n }\n\n const json_response = (await res.json()) as RedisResponse;\n\n if (!json_response.ok) {\n console.error(`Error: ${json_response.error}`);\n if (json_response.code === \"BLOCKED\") {\n console.error(\"The Redis debugging feature has been disabled due to this blocked command.\");\n }\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(json_response.data, null, 2));\n } else {\n console.log(formatOutput(json_response.data));\n console.log(`\\nTime: ${json_response.durationMs}ms`);\n }\n } catch (err) {\n console.error(`Failed to execute command: ${err}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/redis.ts"],"sourcesContent":["/**\n * CLI Redis Passthrough\n *\n * Allows superadmins to execute read-only Redis commands against the production cache.\n */\n\nimport process from \"node:process\";\nimport { readStoredToken, readStoredApiUrl } from \"./auth.js\";\n\nconst MAX_COMMAND_SIZE = 10_000; // 10KB - reasonable for interactive use\n\ntype RedisResponse = {\n ok: boolean;\n data?: unknown;\n durationMs?: number;\n error?: string;\n code?: \"DISABLED\" | \"BLOCKED\";\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/**\n * Formats Redis response for display.\n */\nfunction formatOutput(data: unknown): string {\n if (data === null) {\n return \"(nil)\";\n }\n\n if (typeof data === \"string\") {\n return data;\n }\n\n if (typeof data === \"number\") {\n return `(integer) ${data}`;\n }\n\n if (Array.isArray(data)) {\n if (data.length === 0) {\n return \"(empty array)\";\n }\n return data\n .map((item, index) => `${index + 1}) ${formatOutput(item)}`)\n .join(\"\\n\");\n }\n\n if (typeof data === \"object\") {\n return JSON.stringify(data, null, 2);\n }\n\n return String(data);\n}\n\nexport async function runRedis(argv: string[]): Promise<void> {\n const profile = getArgValue(argv, \"--profile\");\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n const token =\n getArgValue(argv, \"--token\") ?? process.env.CANARY_API_TOKEN ?? (await readStoredToken(profile));\n\n const jsonOutput = hasFlag(argv, \"--json\");\n\n // Get command: remaining args after filtering flags\n const flagsWithValues = [\"--api-url\", \"--token\", \"--profile\"];\n const commandParts: string[] = [];\n for (let i = 0; i < argv.length; i++) {\n if (flagsWithValues.includes(argv[i])) {\n i++; // Skip flag value\n continue;\n }\n if (argv[i].startsWith(\"--\")) continue;\n commandParts.push(argv[i]);\n }\n const command = commandParts.join(\" \");\n\n if (!command) {\n console.error(\"Error: No command provided.\");\n console.error(\"\");\n console.error(\"Usage: canary redis <command> [--json]\");\n console.error(' canary redis KEYS \"job:*\"');\n console.error(\"\");\n console.error(\"Examples:\");\n console.error(\" canary redis PING\");\n console.error(\" canary redis INFO\");\n console.error(' canary redis KEYS \"job:*\"');\n console.error(' canary redis HGETALL \"job:123:status\"');\n console.error(' canary redis GET \"some:key\" --json');\n process.exit(1);\n }\n\n if (command.length > MAX_COMMAND_SIZE) {\n console.error(`Error: Command too large (${command.length} chars, max ${MAX_COMMAND_SIZE})`);\n process.exit(1);\n }\n\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n try {\n const res = await fetch(`${apiUrl}/superadmin/redis`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ command }),\n });\n\n // Handle HTTP errors before parsing JSON\n if (!res.ok) {\n const text = await res.text();\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Your session may have expired.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n\n if (res.status === 404) {\n console.error(\"Error: Endpoint not found. The redis feature may not be deployed to this environment.\");\n process.exit(1);\n }\n\n // Try to parse error as JSON, fallback to raw text\n try {\n const errorJson = JSON.parse(text) as { error?: string };\n console.error(`Error: ${errorJson.error ?? text}`);\n } catch {\n console.error(`Error (${res.status}): ${text || res.statusText}`);\n }\n process.exit(1);\n }\n\n const json_response = (await res.json()) as RedisResponse;\n\n if (!json_response.ok) {\n console.error(`Error: ${json_response.error}`);\n if (json_response.code === \"BLOCKED\") {\n console.error(\"The Redis debugging feature has been disabled due to this blocked command.\");\n }\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify(json_response.data, null, 2));\n } else {\n console.log(formatOutput(json_response.data));\n console.log(`\\nTime: ${json_response.durationMs}ms`);\n }\n } catch (err) {\n console.error(`Failed to execute command: ${err}`);\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,9 +1,10 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
1
2
  import {
2
3
  getArgValue,
3
4
  hasFlag,
4
5
  resolveConfig
5
- } from "./chunk-7R4YFGP6.js";
6
- import "./chunk-DGUM43GV.js";
6
+ } from "./chunk-PWWQGYFG.js";
7
+ import "./chunk-VLFUCAPZ.js";
7
8
 
8
9
  // src/release.ts
9
10
  import process from "process";
@@ -216,4 +217,4 @@ async function runRelease(argv) {
216
217
  export {
217
218
  runRelease
218
219
  };
219
- //# sourceMappingURL=release-KQFCTAXA.js.map
220
+ //# sourceMappingURL=release-ZOD4Y2BF.js.map