@canaryai/cli 0.2.12 → 0.2.14

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 (73) hide show
  1. package/dist/{chunk-CEW4BDXD.js → chunk-4A4G5KTC.js} +14 -6
  2. package/dist/chunk-4A4G5KTC.js.map +1 -0
  3. package/dist/{chunk-MSMC6UXW.js → chunk-6IAPGYZQ.js} +1016 -245
  4. package/dist/chunk-6IAPGYZQ.js.map +1 -0
  5. package/dist/{chunk-PWWQGYFG.js → chunk-ACRIE2YR.js} +5 -2
  6. package/dist/chunk-ACRIE2YR.js.map +1 -0
  7. package/dist/{chunk-ERSNYLMZ.js → chunk-BOS2YLKH.js} +12 -8
  8. package/dist/chunk-BOS2YLKH.js.map +1 -0
  9. package/dist/{chunk-A44B2PEA.js → chunk-SYPQF57S.js} +40 -8
  10. package/dist/chunk-SYPQF57S.js.map +1 -0
  11. package/dist/{chunk-Q7WFBG5C.js → chunk-ZQF72UTG.js} +19 -5
  12. package/dist/chunk-ZQF72UTG.js.map +1 -0
  13. package/dist/{debug-workflow-53ULOFJC.js → debug-workflow-DIQZDFMN.js} +10 -12
  14. package/dist/debug-workflow-DIQZDFMN.js.map +1 -0
  15. package/dist/{docs-BEE3LOCO.js → docs-CSVSGIGW.js} +63 -15
  16. package/dist/docs-CSVSGIGW.js.map +1 -0
  17. package/dist/{feature-flag-CYTDV4ZB.js → feature-flag-BIPFVVNC.js} +3 -3
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +36 -30
  20. package/dist/index.js.map +1 -1
  21. package/dist/{init-M6I3MG3D.js → init-BTDX5N6P.js} +4 -4
  22. package/dist/{issues-NLM72HLU.js → issues-EWVB52CA.js} +37 -18
  23. package/dist/issues-EWVB52CA.js.map +1 -0
  24. package/dist/{knobs-O35GAU5M.js → knobs-VYABZESR.js} +3 -3
  25. package/dist/{list-4K4EIGAT.js → list-RCPYLS36.js} +3 -3
  26. package/dist/list-RCPYLS36.js.map +1 -0
  27. package/dist/{local-NHXXPHZ3.js → local-ZPVM4BXX.js} +6 -6
  28. package/dist/{local-browser-VAZORCO3.js → local-browser-WV4IH2DU.js} +4 -4
  29. package/dist/{login-ZLP64YQP.js → login-W4GXV3VA.js} +4 -4
  30. package/dist/{mcp-ZF5G5DCB.js → mcp-YER5GQG7.js} +7 -10
  31. package/dist/mcp-YER5GQG7.js.map +1 -0
  32. package/dist/{psql-2YPIRMDY.js → psql-XO5BB5L5.js} +2 -2
  33. package/dist/{record-V6QKFFH3.js → record-KRS2PHMW.js} +7 -7
  34. package/dist/record-KRS2PHMW.js.map +1 -0
  35. package/dist/{redis-A7GWM23E.js → redis-CQTBPZ6F.js} +2 -2
  36. package/dist/{release-7TI7EIGD.js → release-DW7RPQSQ.js} +2 -2
  37. package/dist/runner/preload.js +1 -1
  38. package/dist/{session-UGNJXRUW.js → session-CLWAVJ2K.js} +33 -12
  39. package/dist/{session-UGNJXRUW.js.map → session-CLWAVJ2K.js.map} +1 -1
  40. package/dist/{skill-ORWAPBDW.js → skill-2TXI3IKP.js} +1 -1
  41. package/dist/skill-2TXI3IKP.js.map +1 -0
  42. package/dist/{src-4VIDSK4A.js → src-WLOHOI6P.js} +8 -2
  43. package/dist/{start-E532F3BU.js → start-CNNQUP5I.js} +4 -4
  44. package/dist/test.js +1 -1
  45. package/dist/test.js.map +1 -1
  46. package/dist/{workflow-HXIUXRFI.js → workflow-XXL4H5R4.js} +23 -11
  47. package/dist/workflow-XXL4H5R4.js.map +1 -0
  48. package/package.json +1 -2
  49. package/dist/chunk-A44B2PEA.js.map +0 -1
  50. package/dist/chunk-CEW4BDXD.js.map +0 -1
  51. package/dist/chunk-ERSNYLMZ.js.map +0 -1
  52. package/dist/chunk-MSMC6UXW.js.map +0 -1
  53. package/dist/chunk-PWWQGYFG.js.map +0 -1
  54. package/dist/chunk-Q7WFBG5C.js.map +0 -1
  55. package/dist/debug-workflow-53ULOFJC.js.map +0 -1
  56. package/dist/docs-BEE3LOCO.js.map +0 -1
  57. package/dist/issues-NLM72HLU.js.map +0 -1
  58. package/dist/list-4K4EIGAT.js.map +0 -1
  59. package/dist/mcp-ZF5G5DCB.js.map +0 -1
  60. package/dist/record-V6QKFFH3.js.map +0 -1
  61. package/dist/skill-ORWAPBDW.js.map +0 -1
  62. package/dist/workflow-HXIUXRFI.js.map +0 -1
  63. /package/dist/{feature-flag-CYTDV4ZB.js.map → feature-flag-BIPFVVNC.js.map} +0 -0
  64. /package/dist/{init-M6I3MG3D.js.map → init-BTDX5N6P.js.map} +0 -0
  65. /package/dist/{knobs-O35GAU5M.js.map → knobs-VYABZESR.js.map} +0 -0
  66. /package/dist/{local-NHXXPHZ3.js.map → local-ZPVM4BXX.js.map} +0 -0
  67. /package/dist/{local-browser-VAZORCO3.js.map → local-browser-WV4IH2DU.js.map} +0 -0
  68. /package/dist/{login-ZLP64YQP.js.map → login-W4GXV3VA.js.map} +0 -0
  69. /package/dist/{psql-2YPIRMDY.js.map → psql-XO5BB5L5.js.map} +0 -0
  70. /package/dist/{redis-A7GWM23E.js.map → redis-CQTBPZ6F.js.map} +0 -0
  71. /package/dist/{release-7TI7EIGD.js.map → release-DW7RPQSQ.js.map} +0 -0
  72. /package/dist/{src-4VIDSK4A.js.map → src-WLOHOI6P.js.map} +0 -0
  73. /package/dist/{start-E532F3BU.js.map → start-CNNQUP5I.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\ntype StoredAuth = {\n token?: string;\n apiUrl?: string;\n orgId?: string;\n orgName?: string;\n};\n\n/** Multi-profile auth file format */\ntype MultiProfileAuth = {\n profiles: Record<string, StoredAuth>;\n default: string;\n};\n\n/** Legacy flat format (single profile) */\ntype LegacyAuth = StoredAuth;\n\nconst AUTH_DIR = path.join(os.homedir(), \".config\", \"canary-cli\");\nconst AUTH_FILE = path.join(AUTH_DIR, \"auth.json\");\n\n/* ── Shared argv helpers ─────────────────────────────────────────────── */\n\nexport function 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\nexport function hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/* ── Environment / profile mapping ───────────────────────────────────── */\n\n/** Map --env values to profile names for multi-profile auth storage */\nexport function envToProfile(env: string): string {\n if (env === \"prod\" || env === \"production\") return \"production\";\n if (env === \"dev\") return \"dev\";\n if (env === \"local\") return \"local\";\n return env;\n}\n\nconst VALID_ENVS = [\"prod\", \"dev\", \"local\"];\n\n/** Environment URL mappings shared across CLI commands */\nexport const ENV_URLS: Record<string, { api: string; app: string }> = {\n prod: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n production: {\n api: \"https://api.trycanary.ai\",\n app: \"https://app.trycanary.ai\",\n },\n dev: {\n api: \"https://api.dev.trycanary.ai\",\n app: \"https://app.dev.trycanary.ai\",\n },\n local: {\n api: \"http://localhost:3000\",\n app: \"http://localhost:5173\",\n },\n};\n\nfunction isMultiProfile(data: unknown): data is MultiProfileAuth {\n return typeof data === \"object\" && data !== null && \"profiles\" in data;\n}\n\n/** Read the raw auth file and normalize to multi-profile format */\nasync function readAuthFile(): Promise<MultiProfileAuth | null> {\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n return data;\n }\n\n // Legacy flat format — treat as the \"default\" profile\n const legacy = data as LegacyAuth;\n return {\n profiles: { default: legacy },\n default: \"default\",\n };\n } catch {\n return null;\n }\n}\n\nexport async function readStoredAuth(profile?: string): Promise<StoredAuth | null> {\n const auth = await readAuthFile();\n if (!auth) return null;\n\n const key = profile ?? auth.default;\n return auth.profiles[key] ?? null;\n}\n\nexport async function readStoredToken(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.token ?? null;\n}\n\nexport async function readStoredApiUrl(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.apiUrl ?? null;\n}\n\n/** Return all stored tokens across every profile */\nexport async function readAllStoredTokens(): Promise<string[]> {\n const auth = await readAuthFile();\n if (!auth) return [];\n return Object.values(auth.profiles)\n .map((p) => p.token)\n .filter((t): t is string => !!t);\n}\n\nexport async function saveAuth(auth: StoredAuth, profile?: string): Promise<string> {\n await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });\n\n // Read existing file (may be legacy or multi-profile)\n let existing: MultiProfileAuth;\n try {\n const content = await fs.readFile(AUTH_FILE, \"utf8\");\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n existing = data;\n } else {\n // Migrate legacy flat format into profiles.default\n existing = {\n profiles: { default: data as LegacyAuth },\n default: \"default\",\n };\n }\n } catch {\n existing = { profiles: {}, default: \"default\" };\n }\n\n const key = profile ?? \"default\";\n existing.profiles[key] = auth;\n\n // Set default to this profile if no default exists yet\n if (!existing.profiles[existing.default]) {\n existing.default = key;\n }\n\n await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), { encoding: \"utf8\", mode: 0o600 });\n return AUTH_FILE;\n}\n\n/* ── Shared config resolution ────────────────────────────────────────── */\n\nexport type ResolvedConfig = { apiUrl: string; token: string };\n\n/**\n * Resolve API URL and auth token from argv flags, env vars, and stored profiles.\n * Used by knobs, feature-flag, release, and debug-session commands.\n */\nexport async function resolveConfig(argv: string[]): Promise<ResolvedConfig> {\n const env = getArgValue(argv, \"--env\");\n const profile = env ? envToProfile(env) : undefined;\n\n if (env && !ENV_URLS[env]) {\n console.error(`Unknown environment: ${env}`);\n console.error(`Valid environments: ${VALID_ENVS.join(\", \")}`);\n process.exit(1);\n }\n\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n (env ? ENV_URLS[env].api : undefined) ??\n process.env.CANARY_API_URL ??\n storedApiUrl ??\n \"https://api.trycanary.ai\";\n\n // Explicit --token flag always wins\n const explicitToken = getArgValue(argv, \"--token\");\n\n let token: string | null;\n if (explicitToken) {\n token = explicitToken;\n } else if (profile) {\n // --env was specified: profile token takes priority, env var is fallback\n token = (await readStoredToken(profile)) ?? process.env.CANARY_API_TOKEN ?? null;\n if (!token) {\n console.error(`Error: No token found for \"${env}\" profile.`);\n console.error(`Run: canary login --env ${env}`);\n process.exit(1);\n }\n } else {\n // No --env: env var first, then default profile\n token = process.env.CANARY_API_TOKEN ?? (await readStoredToken()) ?? null;\n if (!token) {\n console.error(\"Error: No API token found.\");\n console.error(\"Run: canary login\");\n process.exit(1);\n }\n }\n\n return { apiUrl, token };\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/local-browser/host.ts"],"sourcesContent":["/**\n * Local Browser Host\n *\n * Manages a local browser instance and handles commands from the cloud API\n * via WebSocket. Delegates all browser automation to PlaywrightClient from\n * @chatsdet/browser-core, ensuring the agent experience is identical\n * regardless of whether the browser is local or cloud.\n *\n * @module local-browser-host\n */\n\nimport {\n PlaywrightClient,\n type IBrowserClient,\n type BrowserLogger,\n} from \"@chatsdet/browser-core\";\nimport type {\n BrowserCommand,\n BrowserResponse,\n HeartbeatMessage,\n SessionMessage,\n LocalBrowserMode,\n} from \"./protocol\";\n\nconst HEARTBEAT_INTERVAL_MS = 30_000;\nconst RECONNECT_DELAY_MS = 1000;\nconst MAX_RECONNECT_DELAY_MS = 30_000;\nconst MAX_RECONNECT_ATTEMPTS = 10;\n\nexport interface LocalBrowserHostOptions {\n apiUrl: string;\n wsToken: string;\n sessionId: string;\n browserMode: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n onLog?: (level: \"info\" | \"warn\" | \"error\" | \"debug\", message: string, data?: unknown) => void;\n}\n\n/**\n * LocalBrowserHost manages the WebSocket connection to the cloud API and\n * delegates all browser operations to a shared PlaywrightClient instance.\n */\nexport class LocalBrowserHost {\n private options: LocalBrowserHostOptions;\n private ws: WebSocket | null = null;\n private client: PlaywrightClient;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private reconnectAttempts = 0;\n private isShuttingDown = false;\n\n constructor(options: LocalBrowserHostOptions) {\n this.options = options;\n const logger: BrowserLogger = {\n debug: (msg: string, data?: Record<string, unknown>) => this.log(\"debug\", msg, data),\n info: (msg: string, data?: Record<string, unknown>) => this.log(\"info\", msg, data),\n warn: (msg: string, data?: Record<string, unknown>) => this.log(\"warn\", msg, data),\n error: (msg: string, data?: Record<string, unknown>) => this.log(\"error\", msg, data),\n };\n this.client = new PlaywrightClient({ logger });\n }\n\n private log(level: \"info\" | \"warn\" | \"error\" | \"debug\", message: string, data?: unknown) {\n if (this.options.onLog) {\n this.options.onLog(level, message, data);\n } else {\n const fn = level === \"error\" ? console.error : level === \"warn\" ? console.warn : console.log;\n fn(`[LocalBrowserHost] ${message}`, data ?? \"\");\n }\n }\n\n // =========================================================================\n // Lifecycle\n // =========================================================================\n\n async start(): Promise<void> {\n this.log(\"info\", \"Starting local browser host\", {\n browserMode: this.options.browserMode,\n sessionId: this.options.sessionId,\n });\n\n // Connect to WebSocket first\n await this.connectWebSocket();\n\n // Launch browser via PlaywrightClient\n const { browserMode, cdpUrl, headless = true, storageStatePath } = this.options;\n await this.client.connect({\n browserMode: headless ? \"headless\" : \"headed\",\n cdpUrl: browserMode === \"cdp\" ? cdpUrl : undefined,\n storageStatePath,\n });\n\n // Notify cloud that browser is ready\n this.sendSessionEvent(\"browser_ready\");\n }\n\n async stop(): Promise<void> {\n this.isShuttingDown = true;\n this.log(\"info\", \"Stopping local browser host\");\n\n this.stopHeartbeat();\n\n if (this.ws) {\n try {\n this.ws.close(1000, \"Shutdown\");\n } catch {}\n this.ws = null;\n }\n\n await this.client.disconnect();\n this.log(\"info\", \"Local browser host stopped\");\n }\n\n // =========================================================================\n // WebSocket Connection\n // =========================================================================\n\n private async connectWebSocket(): Promise<void> {\n return new Promise((resolve, reject) => {\n const wsUrl = `${this.options.apiUrl.replace(\"http\", \"ws\")}/local-browser/sessions/${this.options.sessionId}/connect?token=${this.options.wsToken}`;\n\n this.log(\"info\", \"Connecting to cloud API\", { url: wsUrl.replace(/token=.*/, \"token=***\") });\n\n const ws = new WebSocket(wsUrl);\n\n ws.onopen = () => {\n this.log(\"info\", \"Connected to cloud API\");\n this.ws = ws;\n this.reconnectAttempts = 0;\n this.startHeartbeat();\n resolve();\n };\n\n ws.onmessage = (event) => {\n this.handleMessage(event.data as string);\n };\n\n ws.onerror = (event) => {\n this.log(\"error\", \"WebSocket error\", event);\n };\n\n ws.onclose = () => {\n this.log(\"info\", \"WebSocket closed\");\n this.stopHeartbeat();\n this.ws = null;\n\n if (!this.isShuttingDown) {\n this.scheduleReconnect();\n }\n };\n\n // Timeout after 30 seconds\n setTimeout(() => {\n if (!this.ws) {\n reject(new Error(\"WebSocket connection timeout\"));\n }\n }, 30_000);\n });\n }\n\n private scheduleReconnect(): void {\n if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {\n this.log(\"error\", \"Max reconnection attempts reached, giving up\");\n this.stop();\n return;\n }\n\n const delay = Math.min(\n RECONNECT_DELAY_MS * Math.pow(2, this.reconnectAttempts),\n MAX_RECONNECT_DELAY_MS\n );\n\n this.reconnectAttempts++;\n this.log(\"info\", `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n\n setTimeout(async () => {\n try {\n await this.connectWebSocket();\n this.sendSessionEvent(\"connected\");\n this.sendSessionEvent(\"browser_ready\");\n } catch (error) {\n this.log(\"error\", \"Reconnection failed\", error);\n this.scheduleReconnect();\n }\n }, delay);\n }\n\n // =========================================================================\n // Heartbeat\n // =========================================================================\n\n private startHeartbeat(): void {\n this.stopHeartbeat();\n this.heartbeatTimer = setInterval(() => {\n if (this.ws?.readyState === WebSocket.OPEN) {\n const ping: HeartbeatMessage = {\n type: \"heartbeat\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: \"pong\",\n };\n this.ws.send(JSON.stringify(ping));\n }\n }, HEARTBEAT_INTERVAL_MS);\n }\n\n private stopHeartbeat(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n }\n\n // =========================================================================\n // Message Handling\n // =========================================================================\n\n private handleMessage(data: string): void {\n try {\n const message = JSON.parse(data);\n\n if (message.type === \"heartbeat\" && message.direction === \"ping\") {\n const pong: HeartbeatMessage = {\n type: \"heartbeat\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n direction: \"pong\",\n };\n this.ws?.send(JSON.stringify(pong));\n return;\n }\n\n if (message.type === \"command\") {\n this.handleCommand(message as BrowserCommand);\n return;\n }\n\n this.log(\"debug\", \"Received unknown message type\", message);\n } catch (error) {\n this.log(\"error\", \"Failed to parse message\", { error, data });\n }\n }\n\n private async handleCommand(command: BrowserCommand): Promise<void> {\n const startTime = Date.now();\n this.log(\"debug\", `Executing command: ${command.method}`, { id: command.id });\n\n try {\n const result = await this.executeMethod(command.method, command.args);\n const response: BrowserResponse = {\n type: \"response\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: true,\n result,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"debug\", `Command completed: ${command.method}`, {\n id: command.id,\n durationMs: Date.now() - startTime,\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const response: BrowserResponse = {\n type: \"response\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n requestId: command.id,\n success: false,\n error: errorMessage,\n stack: error instanceof Error ? error.stack : undefined,\n contextId: command.contextId,\n };\n this.ws?.send(JSON.stringify(response));\n\n this.log(\"error\", `Command failed: ${command.method}`, {\n id: command.id,\n error: errorMessage,\n });\n }\n }\n\n private sendSessionEvent(\n event: \"connected\" | \"disconnected\" | \"browser_ready\" | \"browser_closed\" | \"error\",\n error?: string\n ): void {\n if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;\n\n const message: SessionMessage = {\n type: \"session\",\n id: crypto.randomUUID(),\n timestamp: Date.now(),\n event,\n browserMode: this.options.browserMode,\n error,\n };\n this.ws.send(JSON.stringify(message));\n }\n\n // =========================================================================\n // Method Execution — Delegate to PlaywrightClient\n // =========================================================================\n\n /**\n * Maps incoming WebSocket command method names to PlaywrightClient methods.\n * The client implements IBrowserClient, so all standard browser operations\n * are available and behave identically to the cloud environment.\n */\n private async executeMethod(method: string, args: unknown[]): Promise<unknown> {\n const client = this.client as IBrowserClient;\n\n switch (method) {\n // Lifecycle\n case \"connect\":\n return client.connect(args[0] as any);\n case \"disconnect\":\n return client.disconnect();\n\n // Navigation\n case \"navigate\":\n return client.navigate(args[0] as string, args[1] as any);\n case \"navigateBack\":\n return client.navigateBack(args[0] as any);\n\n // Page Inspection\n case \"snapshot\":\n return client.snapshot(args[0] as any);\n case \"takeScreenshot\":\n return client.takeScreenshot(args[0] as any);\n case \"evaluate\":\n return client.evaluate(args[0] as string, args[1] as any);\n case \"runCode\":\n return client.runCode(args[0] as string, args[1] as any);\n case \"consoleMessages\":\n return client.consoleMessages(args[0] as any);\n case \"networkRequests\":\n return client.networkRequests(args[0] as any);\n\n // Interaction\n case \"click\":\n return client.click(args[0] as string, args[1] as string, args[2] as any);\n case \"clickAtCoordinates\":\n return client.clickAtCoordinates(\n args[0] as number, args[1] as number, args[2] as string, args[3] as any\n );\n case \"moveToCoordinates\":\n return client.moveToCoordinates(\n args[0] as number, args[1] as number, args[2] as string, args[3] as any\n );\n case \"dragCoordinates\":\n return client.dragCoordinates(\n args[0] as number, args[1] as number, args[2] as number,\n args[3] as number, args[4] as string, args[5] as any\n );\n case \"hover\":\n return client.hover(args[0] as string, args[1] as string, args[2] as any);\n case \"drag\":\n return client.drag(\n args[0] as string, args[1] as string,\n args[2] as string, args[3] as string, args[4] as any\n );\n case \"type\":\n return client.type(\n args[0] as string, args[1] as string, args[2] as string,\n args[3] as boolean, args[4] as any\n );\n case \"pressKey\":\n return client.pressKey(args[0] as string, args[1] as any);\n case \"fillForm\":\n return client.fillForm(args[0] as any[], args[1] as any);\n case \"selectOption\":\n return client.selectOption(\n args[0] as string, args[1] as string, args[2] as string, args[3] as any\n );\n case \"fileUpload\":\n return client.fileUpload(args[0] as string[], args[1] as any);\n\n // Scroll\n case \"scroll\":\n return client.scroll(args[0] as any, args[1] as any, args[2] as any, args[3] as any, args[4] as any);\n\n // Dialogs\n case \"handleDialog\":\n return client.handleDialog(args[0] as \"accept\" | \"dismiss\", args[1] as string, args[2] as any);\n\n // Waiting\n case \"waitFor\":\n return client.waitFor(args[0] as any);\n\n // Browser Management\n case \"close\":\n return client.close(args[0] as any);\n case \"resize\":\n return client.resize(args[0] as number, args[1] as number, args[2] as any);\n case \"tabs\":\n return client.tabs(args[0] as \"list\" | \"new\" | \"close\" | \"select\", args[1] as number, args[2] as any);\n\n // Context Management\n case \"swapContext\":\n return client.swapContext?.(args[0] as any);\n\n // Storage & Page Info\n case \"getStorageState\":\n return client.getStorageState(args[0] as any);\n case \"getCurrentUrl\":\n return client.getCurrentUrl(args[0] as any);\n case \"getTitle\":\n return client.getTitle(args[0] as any);\n case \"getLinks\":\n return client.getLinks(args[0] as any);\n case \"getElementBoundingBox\":\n return client.getElementBoundingBox(args[0] as string, args[1] as any);\n\n // Tracing\n case \"startTracing\":\n return client.startTracing?.(args[0] as any);\n case \"stopTracing\":\n return client.stopTracing?.(args[0] as any);\n\n // Video\n case \"isVideoRecordingEnabled\":\n return client.isVideoRecordingEnabled?.() ?? false;\n case \"saveVideo\":\n return client.saveVideo?.() ?? null;\n case \"getVideoPath\":\n return null;\n\n // Screencast\n case \"startScreencast\":\n return client.startScreencast?.(args[0] as any, args[1] as any);\n case \"stopScreencast\":\n return client.stopScreencast?.();\n case \"isScreencastActive\":\n return client.isScreencastActive?.() ?? false;\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/debug-workflow.ts"],"sourcesContent":["/**\n * Debug Workflow Command\n *\n * Runs a workflow in a local headed browser for debugging. Replays cached\n * actions where possible and falls back to the agent for uncached steps.\n * The browser stays open after execution so the user can inspect page state.\n *\n * Usage:\n * canary debug <workflowId> [--to-step N] [--api-url URL] [--env local|dev|prod]\n *\n * @module debug-workflow\n */\n\nimport fs from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth\";\nimport { downloadStorageState } from \"./cli-helpers.js\";\nimport { LocalBrowserHost } from \"./local-browser/host\";\n\ninterface CreateSessionResponse {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n wsUrl: string;\n expiresAt: string;\n error?: string;\n}\n\ninterface DebugSessionResponse {\n ok: boolean;\n sessionId?: string;\n workflowName?: string;\n nodes?: Array<{\n index: number;\n id: string;\n name: string;\n type: string;\n }>;\n isAuthenticated?: boolean;\n credential?: { credentialId: string; propertyId: string } | null;\n error?: string;\n}\n\ninterface StepStartedEvent {\n type: \"step-started\";\n stepIndex: number;\n totalSteps: number;\n stepName: string;\n nodeType: string;\n nodeId: string;\n}\n\ninterface NodeCompletedEvent {\n type: \"completed\";\n stepIndex?: number;\n result: { success: boolean; errorMessage?: string; source?: string };\n}\n\ninterface RunCompleteEvent {\n type: \"run-complete\";\n stepsExecuted: number;\n totalSteps: number;\n stoppedEarly: boolean;\n reason?: string;\n}\n\ntype DebugEvent =\n | StepStartedEvent\n | NodeCompletedEvent\n | RunCompleteEvent\n | { type: string; [key: string]: unknown };\n\nexport async function runDebugWorkflow(argv: string[]): Promise<void> {\n const workflowId = argv.find((arg) => !arg.startsWith(\"-\"));\n\n if (!workflowId) {\n console.error(\"Usage: canary debug <workflowId> [--to-step N] [--env local|dev|prod]\");\n process.exit(1);\n }\n\n const toStepStr = getArgValue(argv, \"--to-step\");\n const toStep = toStepStr ? parseInt(toStepStr, 10) : undefined;\n const verbose = hasFlag(argv, \"--verbose\", \"-v\");\n\n if (toStep !== undefined && (isNaN(toStep) || toStep < 1)) {\n console.error(\"Error: --to-step must be a positive integer\");\n process.exit(1);\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n // 1. Create local browser session\n console.log(\"Creating local browser session...\");\n\n let localSession: CreateSessionResponse;\n try {\n const res = 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({ browserMode: \"playwright\" }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n console.error(`Failed to create local browser session: ${res.status} ${text}`);\n process.exit(1);\n }\n\n localSession = (await res.json()) as CreateSessionResponse;\n if (!localSession.ok) {\n console.error(`Failed to create session: ${localSession.error}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to connect to API: ${err}`);\n process.exit(1);\n }\n\n // 2. Create debug session (before browser launch so we can download storage state)\n console.log(\"Loading workflow...\");\n\n let debugSession: DebugSessionResponse;\n try {\n const res = await fetch(`${apiUrl}/cli/debug/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n workflowId,\n localBrowserSessionId: localSession.sessionId,\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n console.error(`Failed to create debug session: ${res.status} ${text}`);\n process.exit(1);\n }\n\n debugSession = (await res.json()) as DebugSessionResponse;\n if (!debugSession.ok || !debugSession.sessionId) {\n console.error(`Failed to create debug session: ${debugSession.error}`);\n process.exit(1);\n }\n } catch (err) {\n console.error(`Failed to create debug session: ${err}`);\n process.exit(1);\n }\n\n let debugSessionId: string | null = debugSession.sessionId;\n\n // 3. Download storage state if the workflow has valid credentials\n let storageStatePath: string | undefined;\n if (debugSession.isAuthenticated && debugSession.credential) {\n console.log(\"Downloading storage state...\");\n storageStatePath = await downloadStorageState({\n apiUrl,\n token,\n propertyId: debugSession.credential.propertyId,\n credentialId: debugSession.credential.credentialId,\n prefix: \"canary-debug-ss\",\n });\n if (storageStatePath) {\n console.log(\"Storage state loaded.\");\n } else {\n console.warn(\"Could not download storage state, continuing without it.\");\n }\n }\n\n // 4. Launch headed browser with storage state\n console.log(\"Launching headed browser...\");\n\n const host = new LocalBrowserHost({\n apiUrl,\n wsToken: localSession.wsToken,\n sessionId: localSession.sessionId,\n browserMode: \"playwright\",\n headless: false,\n storageStatePath,\n onLog: (level, message, data) => {\n if (verbose) {\n const prefix = `[${level.toUpperCase()}]`;\n if (data) {\n console.log(prefix, message, data);\n } else {\n console.log(prefix, message);\n }\n }\n },\n });\n\n // Set up cleanup\n const cleanup = async () => {\n console.log(\"\\nShutting down...\");\n\n if (debugSessionId) {\n try {\n await fetch(`${apiUrl}/cli/debug/sessions/${debugSessionId}`, {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${token}` },\n });\n } catch {\n // Ignore cleanup errors\n }\n }\n\n if (storageStatePath) {\n await fs.unlink(storageStatePath).catch(() => {});\n }\n\n await host.stop();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => void cleanup());\n process.on(\"SIGTERM\", () => void cleanup());\n\n try {\n await host.start();\n } catch (err) {\n console.error(`Failed to start local browser: ${err}`);\n await host.stop();\n process.exit(1);\n }\n\n console.log(\"Browser connected.\");\n console.log();\n\n const nodes = debugSession.nodes ?? [];\n const totalSteps = nodes.length;\n const effectiveToStep = toStep ? Math.min(toStep, totalSteps) : totalSteps;\n\n console.log(`Debugging: \"${debugSession.workflowName}\" (${totalSteps} steps)`);\n if (toStep) {\n console.log(`Running steps 1-${effectiveToStep} of ${totalSteps}`);\n }\n console.log();\n\n // 5. Run steps via SSE\n let runRes: Response;\n try {\n runRes = await fetch(`${apiUrl}/cli/debug/sessions/${debugSessionId}/run`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify({ toStep: effectiveToStep }),\n });\n } catch (err) {\n console.error(`Failed to start execution: ${err}`);\n await cleanup();\n return;\n }\n\n if (!runRes.ok || !runRes.body) {\n console.error(`Failed to start execution: ${runRes.status}`);\n await cleanup();\n return;\n }\n\n // Track execution state\n let currentStep = 0;\n let hasError = false;\n const stepResults = new Map<number, { name: string; success: boolean; source?: string }>();\n\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (!event.data) return;\n\n try {\n const data = JSON.parse(event.data) as DebugEvent;\n\n if (verbose) {\n console.log(`[SSE] ${JSON.stringify(data)}`);\n }\n\n if (data.type === \"step-started\") {\n const e = data as StepStartedEvent;\n currentStep = e.stepIndex;\n process.stdout.write(`[${e.stepIndex}/${e.totalSteps}] ${e.stepName} `);\n }\n\n if (data.type === \"completed\") {\n const e = data as NodeCompletedEvent;\n const stepIndex = e.stepIndex ?? currentStep;\n const success = e.result.success;\n const source = e.result.source;\n\n if (success) {\n const sourceLabel = source === \"cache\" ? \"(cache)\" : source === \"agent\" ? \"(agent)\" : \"\";\n console.log(`\\u2713 ${sourceLabel}`);\n } else {\n console.log(\"\\u2717\");\n if (e.result.errorMessage) {\n console.log(` Error: ${e.result.errorMessage.slice(0, 200)}`);\n }\n hasError = true;\n }\n\n stepResults.set(stepIndex, {\n name: `Step ${stepIndex}`,\n success,\n source,\n });\n }\n\n if (data.type === \"run-complete\") {\n const e = data as RunCompleteEvent;\n console.log();\n if (e.stoppedEarly) {\n console.log(\n `Stopped at step ${e.stepsExecuted} of ${e.totalSteps}. ${e.reason === \"step-failed\" ? \"Step failed.\" : \"Error occurred.\"}`\n );\n } else {\n console.log(\n `All ${e.stepsExecuted} step(s) completed.`\n );\n }\n }\n\n if (data.type === \"run-error\") {\n const errorData = data as { error?: string };\n console.log(`\\u2717`);\n if (errorData.error) {\n console.log(` Error: ${String(errorData.error).slice(0, 200)}`);\n }\n hasError = true;\n }\n } catch {\n // Ignore parse errors\n }\n },\n });\n\n const reader = runRes.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n reader.releaseLock();\n }\n\n // 6. Keep browser open for inspection\n console.log();\n console.log(\"Browser is open for inspection.\");\n console.log(\"Press Ctrl+C to close.\");\n\n // Keep the process running until Ctrl+C\n await new Promise(() => {});\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/docs.ts"],"sourcesContent":["/**\n * CLI Documentation Management\n *\n * Allows agents and superadmins to list, pull, edit, and push documentation pages.\n */\n\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { getSubDir } from \"@chatsdet/tmp\";\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest, fetchList } from \"./cli-helpers.js\";\n\ntype DocsPageListItem = {\n slug: string;\n title: string;\n description: string | null;\n updatedAt: string;\n publishedBy: string | null;\n};\n\ntype DocsPageContent = {\n slug: string;\n path: string;\n title: string;\n description: string | null;\n content: string;\n checksum: string;\n updatedAt: string;\n publishedBy: string | null;\n};\n\ntype DocsVersionItem = {\n version: number;\n title: string;\n checksum: string;\n message: string | null;\n createdBy: string;\n createdAt: string;\n};\n\ntype DocsApiResponse = {\n ok: boolean;\n error?: string;\n data?: {\n slug?: string;\n title?: string;\n pages?: DocsPageListItem[];\n content?: string;\n versions?: DocsVersionItem[];\n };\n};\n\nconst DEFAULT_PULL_DIR = getSubDir('docs');\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const pages = await fetchList<DocsPageListItem>(apiUrl, token, \"/public/docs/pages\", \"pages\");\n\n if (jsonOutput) {\n console.log(JSON.stringify(pages, null, 2));\n return;\n }\n\n if (pages.length === 0) {\n console.log(\"No documentation pages found.\");\n return;\n }\n\n for (const page of pages) {\n const date = new Date(page.updatedAt).toISOString().slice(0, 10);\n const by = page.publishedBy ? ` (${page.publishedBy})` : \"\";\n console.log(` ${page.slug} ${page.title} [${date}${by}]`);\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs get <slug>\");\n process.exit(1);\n }\n\n const res = await fetch(`${apiUrl}/public/docs/pages/raw?slug=${encodeURIComponent(slug)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 404) {\n console.error(`Error: Page not found: ${slug}`);\n process.exit(1);\n }\n\n if (!res.ok) {\n console.error(`Error: ${res.statusText}`);\n process.exit(1);\n }\n\n const content = await res.text();\n console.log(content);\n}\n\nasync function handlePull(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs pull <slug> [--output <path>]\");\n process.exit(1);\n }\n\n const outputPath = getArgValue(argv, \"--output\") ?? path.join(DEFAULT_PULL_DIR, `${slug}.md`);\n\n const res = await fetch(`${apiUrl}/public/docs/pages/raw?slug=${encodeURIComponent(slug)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 404) {\n console.error(`Error: Page not found: ${slug}`);\n process.exit(1);\n }\n\n if (!res.ok) {\n console.error(`Error: ${res.statusText}`);\n process.exit(1);\n }\n\n const content = await res.text();\n await mkdir(path.dirname(outputPath), { recursive: true });\n await writeFile(outputPath, content, \"utf8\");\n console.log(`Pulled ${slug} → ${outputPath}`);\n}\n\nasync function handlePush(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs push <slug> --file <path> [--message <text>]\");\n process.exit(1);\n }\n\n const filePath = getArgValue(argv, \"--file\");\n if (!filePath) {\n console.error(\"Error: Missing --file <path>.\");\n process.exit(1);\n }\n\n const message = getArgValue(argv, \"--message\") ?? undefined;\n const createdBy = getArgValue(argv, \"--created-by\") ?? undefined;\n\n let content: string;\n try {\n content = await readFile(filePath, \"utf8\");\n } catch {\n console.error(`Error: Could not read file: ${filePath}`);\n process.exit(1);\n }\n\n const result = await apiRequest<DocsApiResponse>(\n apiUrl,\n token,\n \"PUT\",\n `/superadmin/docs/pages?slug=${encodeURIComponent(slug)}`,\n { content, message, createdBy }\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Published new version of ${slug}`);\n}\n\nasync function handleHistory(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs history <slug>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n\n const res = await fetch(\n `${apiUrl}/public/docs/pages/versions?slug=${encodeURIComponent(slug)}`,\n { headers: { Authorization: `Bearer ${token}` } }\n );\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: res.statusText }));\n console.error(`Error: ${(body as { error?: string }).error ?? res.statusText}`);\n process.exit(1);\n }\n\n const body = (await res.json()) as DocsApiResponse;\n const versions = body.data?.versions ?? [];\n\n if (jsonOutput) {\n console.log(JSON.stringify(versions, null, 2));\n return;\n }\n\n if (versions.length === 0) {\n console.log(`No versions found for ${slug}.`);\n return;\n }\n\n for (const v of versions) {\n const date = new Date(v.createdAt).toISOString().slice(0, 19).replace(\"T\", \" \");\n const msg = v.message ? ` — ${v.message}` : \"\";\n console.log(` v${v.version} ${date} ${v.createdBy}${msg}`);\n }\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs create <slug> --file <path> --title <text>\");\n process.exit(1);\n }\n\n const filePath = getArgValue(argv, \"--file\");\n const title = getArgValue(argv, \"--title\");\n\n if (!filePath) {\n console.error(\"Error: Missing --file <path>.\");\n process.exit(1);\n }\n if (!title) {\n console.error(\"Error: Missing --title <text>.\");\n process.exit(1);\n }\n\n const description = getArgValue(argv, \"--description\") ?? undefined;\n const message = getArgValue(argv, \"--message\") ?? undefined;\n\n let content: string;\n try {\n content = await readFile(filePath, \"utf8\");\n } catch {\n console.error(`Error: Could not read file: ${filePath}`);\n process.exit(1);\n }\n\n const result = await apiRequest<DocsApiResponse>(apiUrl, token, \"POST\", \"/superadmin/docs/pages\", {\n slug,\n title,\n description,\n content,\n message,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Created page: ${slug}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const slug = argv[0];\n if (!slug || slug.startsWith(\"--\")) {\n console.error(\"Error: Missing slug.\");\n console.error(\"Usage: canary docs delete <slug>\");\n process.exit(1);\n }\n\n const result = await apiRequest<DocsApiResponse>(\n apiUrl,\n token,\n \"DELETE\",\n `/superadmin/docs/pages?slug=${encodeURIComponent(slug)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted page: ${slug}`);\n}\n\nfunction printDocsHelp(): void {\n console.log(\n [\n \"Usage: canary docs <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all doc pages\",\n \" get <slug> Print content to stdout\",\n \" pull <slug> [--output <path>] Download to local file\",\n \" push <slug> --file <path> [--message] Publish new version\",\n \" history <slug> Show version history\",\n \" create <slug> --file <path> --title ... Create new page\",\n \" delete <slug> Soft-delete a page\",\n \"\",\n \"Options:\",\n \" --file <path> Path to markdown file\",\n \" --title <text> Page title (create only)\",\n \" --description <text> Page description (create only)\",\n \" --message <text> Version commit message\",\n \" --output <path> Output path for pull (default: /tmp/canary/docs/<slug>.md)\",\n \" --json Output as JSON (list, history)\",\n \" --env <env> Target environment (prod, dev)\",\n \" --api-url <url> API URL override\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runDocs(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printDocsHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n case \"pull\":\n await handlePull(rest, apiUrl, token);\n break;\n case \"push\":\n await handlePush(rest, apiUrl, token);\n break;\n case \"history\":\n await handleHistory(rest, apiUrl, token);\n break;\n case \"create\":\n await handleCreate(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printDocsHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":["body"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/issues.ts"],"sourcesContent":["/**\n * CLI Issues Management\n *\n * Search, list, and view issues with full diagnostics from the terminal.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest } from \"./cli-helpers.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype IssueCategorySummary = {\n id: string;\n key: string;\n label: string;\n description: string | null;\n instructions: string | null;\n};\n\ntype IssueAssignee = {\n id: string;\n displayName: string | null;\n primaryEmail: string | null;\n};\n\ntype FailureDiagnostic = {\n category: string;\n confidence: number;\n title: string;\n analysis: string;\n recommendation: string;\n smokingGun?: { description: string; detail?: string } | null;\n};\n\ntype IssueOccurrence = {\n id: string;\n primaryUrl: string;\n ticketUserFacingSummary: string | null;\n ticketReproSteps: string | null;\n diagnosticJson: FailureDiagnostic | null;\n consoleLogExcerpt: string | null;\n networkFailureUrl: string | null;\n networkFailureStatus: number | null;\n};\n\ntype IssueSummary = {\n id: string;\n title: string;\n severity: \"low\" | \"medium\" | \"high\" | \"unknown\";\n status: \"open\" | \"closed\" | \"not_a_bug\";\n category: IssueCategorySummary;\n firstSeenAt: string;\n lastSeenAt: string;\n occurrenceCount: number;\n assignedTo: IssueAssignee | null;\n latestOccurrence: IssueOccurrence | null;\n};\n\ntype ConsoleLogEntry = {\n type: string;\n text: string;\n timestamp: number | null;\n location: string | null;\n};\n\ntype NetworkRequestEntry = {\n url: string;\n method: string;\n status: number | null;\n durationMs: number | null;\n};\n\ntype Pagination = {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n};\n\ntype IssueListResponse = {\n ok: boolean;\n error?: string;\n data: IssueSummary[];\n pagination: Pagination;\n};\n\ntype IssueDetailResponse = {\n ok: boolean;\n error?: string;\n data: {\n issue: IssueSummary;\n occurrences: IssueOccurrence[];\n commentCount: number;\n };\n};\n\ntype DiagnosticsDataResponse = {\n ok: boolean;\n data: ConsoleLogEntry[] | NetworkRequestEntry[];\n};\n\n/* ── Formatting Helpers ───────────────────────────────────────────────── */\n\nconst SEVERITY_ICONS: Record<string, string> = {\n high: \"!!!\",\n medium: \"!!\",\n low: \"!\",\n unknown: \"?\",\n};\n\nconst DIAGNOSTIC_CATEGORY_LABELS: Record<string, string> = {\n software_bug: \"Software Bug\",\n dependency_issue: \"Dependency Issue\",\n agent_confusion: \"Agent Confusion\",\n};\n\nfunction formatStatusLabel(status: string): string {\n switch (status) {\n case \"open\": return \"Open\";\n case \"closed\": return \"Closed\";\n case \"not_a_bug\": return \"Not a Bug\";\n default: return status;\n }\n}\n\nfunction formatSeverity(severity: string): string {\n return severity.charAt(0).toUpperCase() + severity.slice(1);\n}\n\nfunction formatRelative(value: string | null): string {\n if (!value) return \"—\";\n try {\n const date = new Date(value);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n if (diffMins < 1) return \"just now\";\n if (diffMins < 60) return `${diffMins}m ago`;\n const diffHours = Math.floor(diffMins / 60);\n if (diffHours < 24) return `${diffHours}h ago`;\n const diffDays = Math.floor(diffHours / 24);\n if (diffDays < 30) return `${diffDays}d ago`;\n return date.toISOString().slice(0, 10);\n } catch {\n return value;\n }\n}\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return text.slice(0, max - 1) + \"…\";\n}\n\n/* ── Markdown Formatters ──────────────────────────────────────────────── */\n\nfunction formatIssueDetailMarkdown(\n issue: IssueSummary,\n consoleErrors: ConsoleLogEntry[],\n networkErrors: NetworkRequestEntry[]\n): string {\n const sections: string[] = [];\n const occ = issue.latestOccurrence;\n\n sections.push(`# ${issue.title}`);\n\n const meta: string[] = [];\n meta.push(`**Status:** ${formatStatusLabel(issue.status)}`);\n meta.push(`**Severity:** ${formatSeverity(issue.severity)}`);\n meta.push(`**Category:** ${issue.category.label}`);\n sections.push(meta.join(\" | \"));\n\n if (occ?.primaryUrl) {\n sections.push(`**URL:** ${occ.primaryUrl}`);\n }\n\n const timeParts: string[] = [];\n timeParts.push(`**First seen:** ${formatRelative(issue.firstSeenAt)}`);\n timeParts.push(`**Last seen:** ${formatRelative(issue.lastSeenAt)}`);\n timeParts.push(`**Occurrences:** ${issue.occurrenceCount}`);\n sections.push(timeParts.join(\" | \"));\n\n const summary = occ?.ticketUserFacingSummary?.trim();\n if (summary) {\n sections.push(`## Summary\\n${summary}`);\n }\n\n const reproSteps = occ?.ticketReproSteps?.trim();\n if (reproSteps) {\n sections.push(`## Reproduction Steps\\n${reproSteps}`);\n }\n\n const diagnostic = occ?.diagnosticJson;\n if (diagnostic) {\n const diagParts: string[] = [];\n const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;\n diagParts.push(\"## Diagnostic Analysis\");\n diagParts.push(`**Category:** ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}% confidence)`);\n diagParts.push(`**${diagnostic.title}**`);\n diagParts.push(diagnostic.analysis);\n\n if (diagnostic.smokingGun) {\n const gun = diagnostic.smokingGun;\n const gunLines = [`> **Key Evidence:** ${gun.description}`];\n if (gun.detail) {\n gunLines.push(`> \\`${gun.detail}\\``);\n }\n diagParts.push(gunLines.join(\"\\n\"));\n }\n\n diagParts.push(`**Recommendation:** ${diagnostic.recommendation}`);\n sections.push(diagParts.join(\"\\n\\n\"));\n }\n\n if (consoleErrors.length > 0) {\n const logLines = consoleErrors.map((entry) => {\n const loc = entry.location ? ` (${entry.location})` : \"\";\n return `[${entry.type}] ${entry.text}${loc}`;\n });\n sections.push(`## Console Errors\\n\\`\\`\\`\\n${logLines.join(\"\\n\")}\\n\\`\\`\\``);\n }\n\n if (networkErrors.length > 0) {\n const rows = networkErrors.map((entry) => {\n const status =\n entry.status === null || entry.status === 0\n ? \"0 (failed)\"\n : String(entry.status);\n const duration = entry.durationMs != null ? `${entry.durationMs}ms` : \"—\";\n return `| ${entry.method} | ${entry.url} | ${status} | ${duration} |`;\n });\n sections.push(\n `## Network Errors\\n| Method | URL | Status | Duration |\\n|--------|-----|--------|----------|\\n${rows.join(\"\\n\")}`\n );\n }\n\n return sections.join(\"\\n\\n\");\n}\n\nfunction formatIssueListMarkdown(data: IssueSummary[], pagination: Pagination): string {\n const sections: string[] = [];\n sections.push(`# Issues (page ${pagination.page} of ${pagination.totalPages}, ${pagination.totalItems} total)`);\n\n if (data.length === 0) {\n sections.push(\"No issues found.\");\n return sections.join(\"\\n\\n\");\n }\n\n const header = \"| Severity | Title | Status | Last Seen | Occurrences |\";\n const divider = \"|----------|-------|--------|-----------|-------------|\";\n const rows = data.map((issue) =>\n `| ${issue.severity} | ${truncate(issue.title, 60)} | ${issue.status} | ${formatRelative(issue.lastSeenAt)} | ${issue.occurrenceCount} |`\n );\n sections.push([header, divider, ...rows].join(\"\\n\"));\n\n return sections.join(\"\\n\\n\");\n}\n\n/* ── Diagnostics Fetching ─────────────────────────────────────────────── */\n\nasync function fetchDiagnostics(\n apiUrl: string,\n token: string,\n issue: IssueSummary\n): Promise<{ consoleErrors: ConsoleLogEntry[]; networkErrors: NetworkRequestEntry[] }> {\n const occ = issue.latestOccurrence;\n if (!occ) return { consoleErrors: [], networkErrors: [] };\n\n let consoleErrors: ConsoleLogEntry[] = [];\n let networkErrors: NetworkRequestEntry[] = [];\n\n try {\n const [consoleRes, networkRes] = await Promise.all([\n apiRequest<DiagnosticsDataResponse>(\n apiUrl, token, \"GET\",\n `/v2/issues/${issue.id}/occurrences/${occ.id}/console-logs`\n ),\n apiRequest<DiagnosticsDataResponse>(\n apiUrl, token, \"GET\",\n `/v2/issues/${issue.id}/occurrences/${occ.id}/network-requests`\n ),\n ]);\n\n if (consoleRes.ok && Array.isArray(consoleRes.data)) {\n consoleErrors = (consoleRes.data as ConsoleLogEntry[]).filter((e) => e.type === \"error\");\n }\n\n if (networkRes.ok && Array.isArray(networkRes.data)) {\n networkErrors = (networkRes.data as NetworkRequestEntry[]).filter(\n (e) => e.status === null || e.status === 0 || e.status >= 400\n );\n }\n } catch {\n // Diagnostics are best-effort; continue without them\n }\n\n // Fallback to legacy fields\n if (consoleErrors.length === 0 && occ.consoleLogExcerpt?.trim()) {\n consoleErrors = [{ type: \"error\", text: occ.consoleLogExcerpt.trim(), timestamp: null, location: null }];\n }\n\n if (networkErrors.length === 0 && occ.networkFailureUrl) {\n networkErrors = [{\n url: occ.networkFailureUrl,\n method: \"GET\",\n status: occ.networkFailureStatus,\n durationMs: null,\n }];\n }\n\n return { consoleErrors, networkErrors };\n}\n\n/* ── Sub-command Handlers ─────────────────────────────────────────────── */\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const markdownOutput = getArgValue(argv, \"--format\") === \"markdown\";\n\n const params = new URLSearchParams();\n const search = getArgValue(argv, \"--search\");\n const severity = getArgValue(argv, \"--severity\");\n const status = getArgValue(argv, \"--status\");\n const propertyId = getArgValue(argv, \"--property-id\");\n const page = getArgValue(argv, \"--page\");\n const pageSize = getArgValue(argv, \"--page-size\");\n\n if (search) params.set(\"search\", search);\n if (severity) params.set(\"severity\", severity);\n if (status) params.set(\"statuses\", status);\n if (propertyId) params.set(\"propertyId\", propertyId);\n if (page) params.set(\"page\", page);\n if (pageSize) params.set(\"pageSize\", pageSize);\n\n const qs = params.toString();\n const path = `/v2/issues${qs ? `?${qs}` : \"\"}`;\n\n const result = await apiRequest<IssueListResponse>(apiUrl, token, \"GET\", path);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify({ data: result.data, pagination: result.pagination }, null, 2));\n return;\n }\n\n if (markdownOutput) {\n console.log(formatIssueListMarkdown(result.data, result.pagination));\n return;\n }\n\n // Default compact output\n const { data, pagination } = result;\n console.log(`Issues: ${pagination.totalItems} total (page ${pagination.page}/${pagination.totalPages})\\n`);\n\n if (data.length === 0) {\n console.log(\"No issues found.\");\n return;\n }\n\n for (const issue of data) {\n const icon = SEVERITY_ICONS[issue.severity] ?? \"?\";\n const statusLabel = formatStatusLabel(issue.status);\n const lastSeen = formatRelative(issue.lastSeenAt);\n console.log(` [${icon}] ${truncate(issue.title, 60)} ${statusLabel} ${lastSeen} (${issue.occurrenceCount}x) ${issue.id}`);\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const issueId = argv[0];\n if (!issueId || issueId.startsWith(\"--\")) {\n console.error(\"Error: Missing issue ID.\");\n console.error(\"Usage: canary issues get <issueId>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const markdownOutput = getArgValue(argv, \"--format\") === \"markdown\";\n\n const result = await apiRequest<IssueDetailResponse>(apiUrl, token, \"GET\", `/v2/issues/${issueId}`);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n const { issue } = result.data;\n const { consoleErrors, networkErrors } = await fetchDiagnostics(apiUrl, token, issue);\n\n if (jsonOutput) {\n console.log(JSON.stringify({ ...result.data, consoleErrors, networkErrors }, null, 2));\n return;\n }\n\n if (markdownOutput) {\n console.log(formatIssueDetailMarkdown(issue, consoleErrors, networkErrors));\n return;\n }\n\n // Default human-readable output\n const occ = issue.latestOccurrence;\n console.log(` Title: ${issue.title}`);\n console.log(` ID: ${issue.id}`);\n console.log(` Status: ${formatStatusLabel(issue.status)}`);\n console.log(` Severity: ${formatSeverity(issue.severity)}`);\n console.log(` Category: ${issue.category.label}`);\n console.log(` Occurrences: ${issue.occurrenceCount}`);\n console.log(` First seen: ${formatRelative(issue.firstSeenAt)}`);\n console.log(` Last seen: ${formatRelative(issue.lastSeenAt)}`);\n\n if (occ?.primaryUrl) {\n console.log(` URL: ${occ.primaryUrl}`);\n }\n\n if (issue.assignedTo) {\n console.log(` Assigned to: ${issue.assignedTo.displayName ?? issue.assignedTo.primaryEmail ?? issue.assignedTo.id}`);\n }\n\n const summary = occ?.ticketUserFacingSummary?.trim();\n if (summary) {\n console.log(`\\n Summary:\\n ${summary.split(\"\\n\").join(\"\\n \")}`);\n }\n\n const diagnostic = occ?.diagnosticJson;\n if (diagnostic) {\n const categoryLabel = DIAGNOSTIC_CATEGORY_LABELS[diagnostic.category] ?? diagnostic.category;\n console.log(`\\n Diagnostic: ${categoryLabel} (${Math.round(diagnostic.confidence * 100)}%)`);\n console.log(` ${diagnostic.title}`);\n console.log(` Recommendation: ${diagnostic.recommendation}`);\n }\n\n if (consoleErrors.length > 0) {\n console.log(`\\n Console Errors (${consoleErrors.length}):`);\n for (const entry of consoleErrors.slice(0, 5)) {\n const loc = entry.location ? ` (${entry.location})` : \"\";\n console.log(` [${entry.type}] ${truncate(entry.text, 100)}${loc}`);\n }\n if (consoleErrors.length > 5) {\n console.log(` ... and ${consoleErrors.length - 5} more`);\n }\n }\n\n if (networkErrors.length > 0) {\n console.log(`\\n Network Errors (${networkErrors.length}):`);\n for (const entry of networkErrors.slice(0, 5)) {\n const status = entry.status === null || entry.status === 0 ? \"failed\" : String(entry.status);\n console.log(` ${entry.method} ${truncate(entry.url, 80)} -> ${status}`);\n }\n if (networkErrors.length > 5) {\n console.log(` ... and ${networkErrors.length - 5} more`);\n }\n }\n}\n\n/* ── Help & Entry Point ───────────────────────────────────────────────── */\n\nfunction printIssuesHelp(): void {\n console.log(\n [\n \"Usage: canary issues <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list [options] List and search issues\",\n \" get <issueId> [options] Get issue detail with diagnostics\",\n \"\",\n \"List options:\",\n \" --search <query> Full-text search\",\n \" --severity <level> Filter: low, medium, high, unknown\",\n \" --status <statuses> Filter: open, closed, not_a_bug (comma-separated)\",\n \" --property-id <uuid> Filter by property\",\n \" --page <n> Page number (default: 1)\",\n \" --page-size <n> Page size (default: 25)\",\n \"\",\n \"Output options:\",\n \" --json Output raw JSON\",\n \" --format markdown Output as markdown\",\n \"\",\n \"Common options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --api-url <url> API URL override (takes precedence over --env)\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runIssues(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printIssuesHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printIssuesHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/local/list.ts"],"sourcesContent":["/**\n * `canary local list` — List all local environments for the current user.\n *\n * @module\n */\n\nimport process from 'node:process';\nimport { resolveConfig } from '../auth.js';\nimport { fetchProperties, fetchList } from '../cli-helpers.js';\n\ninterface LocalEnvironment {\n id: string;\n name: string;\n url: string;\n propertyId: string;\n}\n\nexport async function runLocalList(argv: string[]) {\n const config = await resolveConfig(argv);\n\n const properties = await fetchProperties(config.apiUrl, config.token);\n\n if (properties.length === 0) {\n console.log('No properties found.');\n return;\n }\n\n const rows: Array<{ envId: string; property: string; name: string; url: string }> = [];\n\n for (const property of properties) {\n const envs = await fetchList<LocalEnvironment>(\n config.apiUrl,\n config.token,\n `/org/properties/${property.id}/environments/local`,\n 'items'\n );\n\n for (const env of envs) {\n rows.push({\n envId: env.id,\n property: property.name,\n name: env.name,\n url: env.url,\n });\n }\n }\n\n if (rows.length === 0) {\n console.log('No local environments found. Run `canary local init` to create one.');\n return;\n }\n\n // Print table\n const headers = ['ENV ID', 'PROPERTY', 'NAME', 'URL'];\n const widths = headers.map((h, i) => {\n const col = [h, ...rows.map((r) => Object.values(r)[i])];\n return Math.max(...col.map((v) => v.length));\n });\n\n const sep = widths.map((w) => '-'.repeat(w)).join(' ');\n const formatRow = (vals: string[]) =>\n vals.map((v, i) => v.padEnd(widths[i])).join(' ');\n\n console.log(formatRow(headers));\n console.log(sep);\n for (const row of rows) {\n console.log(formatRow([row.envId, row.property, row.name, row.url]));\n }\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,eAAsB,aAAa,MAAgB;AACjD,QAAM,SAAS,MAAM,cAAc,IAAI;AAEvC,QAAM,aAAa,MAAM,gBAAgB,OAAO,QAAQ,OAAO,KAAK;AAEpE,MAAI,WAAW,WAAW,GAAG;AAC3B,YAAQ,IAAI,sBAAsB;AAClC;AAAA,EACF;AAEA,QAAM,OAA8E,CAAC;AAErF,aAAW,YAAY,YAAY;AACjC,UAAM,OAAO,MAAM;AAAA,MACjB,OAAO;AAAA,MACP,OAAO;AAAA,MACP,mBAAmB,SAAS,EAAE;AAAA,MAC9B;AAAA,IACF;AAEA,eAAW,OAAO,MAAM;AACtB,WAAK,KAAK;AAAA,QACR,OAAO,IAAI;AAAA,QACX,UAAU,SAAS;AAAA,QACnB,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,KAAK,WAAW,GAAG;AACrB,YAAQ,IAAI,qEAAqE;AACjF;AAAA,EACF;AAGA,QAAM,UAAU,CAAC,UAAU,YAAY,QAAQ,KAAK;AACpD,QAAM,SAAS,QAAQ,IAAI,CAAC,GAAG,MAAM;AACnC,UAAM,MAAM,CAAC,GAAG,GAAG,KAAK,IAAI,CAAC,MAAM,OAAO,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AACvD,WAAO,KAAK,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AAAA,EAC7C,CAAC;AAED,QAAM,MAAM,OAAO,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,IAAI;AACtD,QAAM,YAAY,CAAC,SACjB,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI;AAEnD,UAAQ,IAAI,UAAU,OAAO,CAAC;AAC9B,UAAQ,IAAI,GAAG;AACf,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,UAAU,CAAC,IAAI,OAAO,IAAI,UAAU,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC;AAAA,EACrE;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/mcp/index.ts","../src/mcp/tool-helpers.ts","../src/mcp/session-tools.ts","../src/mcp/browser-tools.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 { readStoredToken } from '../auth';\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 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_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_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 * Browser MCP tool registration — daemon adapter.\n *\n * Thin layer that maps MCP tool calls to the session daemon,\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 * The daemon owns all browser sessions. MCP is just a protocol adapter.\n *\n * @module\n */\n\nimport { getBrowserToolDefinitionsWithLifecycle } from '@chatsdet/browser-core';\nimport {\n createSession,\n deleteSession,\n deleteAllSessions,\n callTool,\n resolveTargetSession,\n listSessions,\n} from '../session/daemon-client.js';\nimport { formatMCPContent, toolJson } from './tool-helpers';\nimport type { ToolResult } from '@chatsdet/browser-core';\n\n/** Track the active session ID within this MCP server instance */\nlet activeSessionId: string | null = null;\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 const result = await createSession({\n headless: (args.headless as boolean) ?? false,\n storageStatePath: args.storageStatePath as string | undefined,\n });\n\n if (!result.ok) {\n return toolJson({ ok: false, error: result.error });\n }\n\n activeSessionId = result.data!.id;\n return toolJson({\n ok: true,\n sessionId: activeSessionId,\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 if (activeSessionId) {\n await deleteSession(activeSessionId);\n activeSessionId = null;\n } else {\n // Stop all sessions if no specific one tracked\n await deleteAllSessions();\n }\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 try {\n // Resolve target session: prefer tracked MCP session, then auto-resolve\n let sessionId = activeSessionId;\n if (!sessionId) {\n try {\n const target = await resolveTargetSession();\n sessionId = target.id;\n activeSessionId = sessionId;\n } catch {\n return toolJson({\n ok: false,\n error: 'No active browser session. Start one with browser_start first.',\n });\n }\n }\n\n // Strip 'browser_' prefix for the daemon tool name\n const toolName = name.replace(/^browser_/, '');\n const result = await callTool(sessionId, toolName, args);\n\n if (!result.ok) {\n // If session not found, clear tracking and report error\n if (result.error?.includes('not found')) {\n activeSessionId = null;\n }\n return toolJson({ ok: false, error: result.error, tool: name });\n }\n\n // Convert daemon response back to ToolResult format for MCP formatting\n const toolResult: ToolResult = result.images?.length\n ? { text: result.text ?? '', images: result.images }\n : result.text ?? '';\n\n return formatMCPContent(toolResult);\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"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
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 { getCanaryTmpDir } from \"@chatsdet/tmp\";\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest, selectCredential, downloadStorageState } from \"./cli-helpers.js\";\nimport type { CredentialListItem } 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; inputType?: string; ariaLabel?: string; value: string; isFinal?: boolean }\n | { type: \"change\"; ts: number; tagName: string; id?: string; inputType?: string; ariaLabel?: string; value: string }\n | { type: \"keydown\"; ts: number; key: string }\n | { type: \"navigation\"; ts: number; url: string; snapshot?: string; navigationType?: string }\n | { type: \"initial-url\"; ts: number; url: string; snapshot?: string };\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\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 and select credential\n console.log(\"Fetching credentials...\");\n const credential = await selectCredential(config.apiUrl, config.token, credentialArg);\n if (!credential) {\n process.exit(1);\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 storageStatePath = await downloadStorageState({\n apiUrl: config.apiUrl,\n token: config.token,\n propertyId: credential.propertyId,\n credentialId: credential.id,\n });\n if (storageStatePath) {\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(getCanaryTmpDir(), `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\n // Emit initial-url event so we know the starting page\n const currentUrl = page.url();\n const initialUrlEvent: RecordingEvent = {\n type: \"initial-url\",\n ts: Date.now(),\n url: currentUrl,\n };\n try {\n const snapshot = await (page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> })._snapshotForAI({ mode: \"full\" });\n (initialUrlEvent as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail\n }\n events.push(initialUrlEvent);\n process.stdout.write(` [${events.length}] initial-url → ${currentUrl}\\n`);\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 // Snapshot every click for full page context\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 // 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 === \"change\"\n ? `change (${(evt as { tagName: string }).tagName})`\n : evt.type === \"navigation\"\n ? `navigation → ${(evt as { url: string }).url}`\n : evt.type === \"initial-url\"\n ? `initial-url → ${(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(getCanaryTmpDir(), `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 // --- Input debouncing ---\n // Accumulate input per element, flush after 500ms idle as a single event\n var inputTimers = new Map();\n var inputState = new Map();\n\n function getElementKey(el) {\n return (el.id || '') + '|' + (el.getAttribute('aria-label') || '') + '|' + (el.tagName || '') + '|' + (el.getAttribute('name') || '');\n }\n\n function flushInput(key) {\n var state = inputState.get(key);\n if (!state) return;\n inputTimers.delete(key);\n inputState.delete(key);\n push({\n type: 'input',\n ts: state.ts,\n tagName: state.tagName,\n id: state.id || undefined,\n inputType: state.inputType || undefined,\n ariaLabel: state.ariaLabel || undefined,\n value: state.value,\n isFinal: true,\n });\n }\n\n function flushAllInputs() {\n inputTimers.forEach(function(timer) { clearTimeout(timer); });\n inputState.forEach(function(_, key) { flushInput(key); });\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 var key = getElementKey(el);\n\n // Clear previous timer for this element\n var prevTimer = inputTimers.get(key);\n if (prevTimer) clearTimeout(prevTimer);\n\n // Update accumulated state\n inputState.set(key, {\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\n // Schedule flush after 500ms idle\n inputTimers.set(key, setTimeout(function() { flushInput(key); }, 500));\n }, true);\n\n // --- Change events for selects, checkboxes, radios ---\n document.addEventListener('change', function(e) {\n var el = e.target;\n var tagName = el.tagName || '';\n var inputType = (el.type || '').toLowerCase();\n\n // Only capture selects, checkboxes, and radios (text inputs are handled by input debouncing)\n var isSelect = tagName === 'SELECT';\n var isCheckOrRadio = inputType === 'checkbox' || inputType === 'radio';\n if (!isSelect && !isCheckOrRadio) return;\n\n var value;\n if (isCheckOrRadio) {\n value = el.checked ? 'checked' : 'unchecked';\n } else {\n value = el.value || '';\n }\n\n push({\n type: 'change',\n ts: Date.now(),\n tagName: tagName,\n id: el.id || undefined,\n inputType: inputType || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: 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 // --- SPA navigation: patch pushState/replaceState ---\n var origPushState = history.pushState;\n var origReplaceState = history.replaceState;\n\n history.pushState = function() {\n origPushState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'pushState',\n });\n };\n\n history.replaceState = function() {\n origReplaceState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'replaceState',\n });\n };\n\n window.addEventListener('popstate', function() {\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'popstate',\n });\n });\n\n window.addEventListener('beforeunload', function() {\n flushAllInputs();\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'beforeunload',\n });\n });\n})();\n`;\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/skill-templates/issue-log-xref.ts","../src/skill-templates/browser-testing.ts","../src/skill-templates/index.ts","../src/skill.ts"],"sourcesContent":["export type SkillTemplate = {\n name: string;\n description: string;\n render: () => string;\n};\n\nexport const issueLogXrefTemplate: SkillTemplate = {\n name: \"issue-log-xref\",\n description:\n \"Cross-reference Canary issues with server logs to find root causes\",\n render: () => `---\nname: issue-log-xref\ndescription: Cross-reference Canary QA issues with server logs to identify root causes. Use when investigating why a test failed, correlating browser errors with backend logs, or debugging issues found by Canary.\nallowed-tools: Bash(canary issues:*), Bash(canary login:*), Grep, Read\n---\n\n# Issue Log Cross-Reference\n\nCross-reference Canary-detected issues with server logs to identify root causes, correlate errors, and provide actionable debugging context.\n\n## Prerequisites\n\n- \\`canary\\` CLI installed and authenticated (\\`canary login\\`)\n- Access to server logs (see Log Source Configuration below)\n\n## Workflow\n\n### 1. Fetch issues from Canary\n\nList recent issues to find ones worth investigating:\n\n\\`\\`\\`bash\ncanary issues list --format markdown\n\\`\\`\\`\n\nUse filters to narrow down:\n\n\\`\\`\\`bash\ncanary issues list --severity high --status open --format markdown\n\\`\\`\\`\n\n### 2. Get issue details\n\nOnce you identify an issue, fetch its full diagnostics:\n\n\\`\\`\\`bash\ncanary issues get <issue-id> --format markdown\n\\`\\`\\`\n\nNote the following from the issue details:\n- **Timestamps** — when the issue was detected\n- **URLs / endpoints** — which pages or API routes were involved\n- **Error messages** — any visible error text captured in screenshots or DOM\n- **HTTP status codes** — failed network requests\n\n### 3. Access server logs\n\n<!-- ============================================================\n LOG SOURCE CONFIGURATION\n\n Replace {{LOG_SOURCE_INSTRUCTIONS}} below with instructions\n specific to your infrastructure. The AI agent reading this\n skill will use these instructions to fetch logs.\n\n Examples:\n\n **AWS CloudWatch:**\n Query CloudWatch Logs Insights for the relevant log group:\n \\`\\`\\`bash\n aws logs filter-log-events \\\\\n --log-group-name /ecs/my-api \\\\\n --start-time <epoch-ms> \\\\\n --end-time <epoch-ms> \\\\\n --filter-pattern \"<error-text>\"\n \\`\\`\\`\n\n **Local log files:**\n Search local log files for matching entries:\n \\`\\`\\`bash\n grep -i \"<error-text>\" /var/log/my-app/*.log\n \\`\\`\\`\n\n **Datadog:**\n Use the Datadog CLI or API to query logs:\n \\`\\`\\`bash\n dog logs list --query \"service:my-api status:error\" \\\\\n --from <ISO-timestamp> --to <ISO-timestamp>\n \\`\\`\\`\n\n **kubectl (Kubernetes):**\n Fetch pod logs for the relevant service:\n \\`\\`\\`bash\n kubectl logs -l app=my-api --since=1h | grep \"<error-text>\"\n \\`\\`\\`\n============================================================ -->\n\n{{LOG_SOURCE_INSTRUCTIONS}}\n\n### 4. Cross-reference and correlate\n\nMatch the Canary issue with log entries using these correlation points:\n\n- **Timestamps** — find log entries within a few seconds of the issue detection time\n- **Error messages** — search logs for the same error text shown in the issue\n- **HTTP status codes** — look for 4xx/5xx responses matching the failed requests\n- **URLs / routes** — filter logs by the endpoint or page path from the issue\n- **Stack traces** — identify the code path that produced the error\n- **Request IDs** — if your app uses correlation/request IDs, trace the full request lifecycle\n\n## Analysis Guidelines\n\nWhen presenting findings:\n\n1. **State the match confidence** — clear match, likely match, or circumstantial\n2. **Show the timeline** — lay out events in chronological order across both sources\n3. **Identify the root cause** — distinguish between the symptom (what Canary caught) and the cause (what the logs reveal)\n4. **Suggest next steps** — point to the specific code, service, or configuration that needs attention\n5. **Note gaps** — if logs are missing or incomplete, say so explicitly rather than speculating\n`,\n};\n","export type SkillTemplate = {\n name: string;\n description: string;\n render: () => string;\n};\n\nexport const browserTestingTemplate: SkillTemplate = {\n name: \"browser-testing\",\n description:\n \"Use the Canary CLI to control a browser for testing web applications\",\n render: () => `---\nname: browser-testing\ndescription: Control a local browser via the Canary CLI to test web applications interactively. Use when the user asks to test, browse, interact with, or verify a web application using canary browser sessions.\nallowed-tools: Bash(canary session:*), Bash(canary login:*), Read\n---\n\n# Browser Testing with Canary\n\nUse the \\`canary session\\` CLI to launch and control a local browser for testing web applications. You interact with pages through **snapshots** (accessibility tree + screenshot), then issue commands to click, type, navigate, and assert.\n\n## Prerequisites\n\n- \\`canary\\` CLI installed and authenticated (\\`canary login\\`)\n- Run \\`canary session --help\\` for the full command reference\n\n## Environments & Authentication\n\nCanary credentials are stored remotely and belong to a **property** (an app with a base URL). When the user asks you to test, determine the environment:\n\n### Remote / Deployed environments (default)\n\nCredentials are pre-configured with storage state. Just use \\`--credential\\` to load one:\n\n\\`\\`\\`bash\n# Interactive picker — shows credential name, property name, and base URL\ncanary session start --credential\n\n# By name\ncanary session start --credential \"Admin User\" --url https://staging.example.com\n\\`\\`\\`\n\n### Local development\n\nCredentials stored in Canary won't work against localhost because session cookies are bound to the remote domain. When testing a local dev server:\n\n1. **Ask the user to log in manually.** Start a session without credentials and tell them to authenticate in the browser:\n\n\\`\\`\\`bash\ncanary session start --url http://localhost:5173\n\\`\\`\\`\n\nThen tell the user: \"I've opened a browser to your local app. Please log in, and let me know when you're ready for me to continue.\"\n\n2. **Wait for confirmation** before proceeding with testing.\n\n3. Alternatively, if the user has a **local storage state file**, use it:\n\n\\`\\`\\`bash\ncanary session start --storage-state ./local-auth.json --url http://localhost:5173\n\\`\\`\\`\n\n**Rule:** If the URL is localhost or 127.0.0.1, do NOT use \\`--credential\\` — it will download remote session state that won't work locally. Instead, prompt the user to log in.\n\n## Core Workflow\n\nThe fundamental loop is: **snapshot → read → act → snapshot → verify**.\n\n### 1. Start a session\n\n\\`\\`\\`bash\n# Start a headed browser (you can watch it)\ncanary session start --url https://your-app.com\n\n# Start with a saved credential (interactive picker shows property + base URL)\ncanary session start --credential\n\n# Start with a specific credential by name\ncanary session start --credential \"Admin User\" --url https://your-app.com\n\n# Load a credential into an already-running session\ncanary session load-credential\n\\`\\`\\`\n\n### 2. Take a snapshot to see the page\n\nSnapshots are your primary way to understand what's on the page. They return an **accessibility tree** with element references (refs) and a **screenshot**.\n\n\\`\\`\\`bash\n# Full snapshot: accessibility tree + screenshot\ncanary session snapshot\n\n# Search for specific elements (fastest way to find what you need)\ncanary session snapshot --search \"Submit\"\ncanary session snapshot --search \"Email|Password\"\n\n# Screenshot only (useful for visual verification)\ncanary session snapshot --mode screenshot\n\n# Tree only (useful when you need ref details, no image)\ncanary session snapshot --mode tree\n\\`\\`\\`\n\n**Reading snapshot output:**\n\nThe accessibility tree shows elements with refs like \\`[ref=e15]\\`. Use these refs in subsequent commands. The tree is structured to show the page hierarchy — forms, buttons, links, headings, etc.\n\nWhen you use \\`--search\\`, matching elements are highlighted in the output and on the screenshot. Search is OR-matched and case-insensitive. Use search to **locate** elements, not to validate visual states — search overlays can obscure subtle UI changes.\n\n### 3. Interact with the page\n\nAlways reference elements by their **ref** from the snapshot.\n\n\\`\\`\\`bash\n# Click an element\ncanary session click --ref e15 --element \"Submit button\"\n\n# Type into an input field\ncanary session type --ref e7 --text \"user@example.com\" --element \"Email input\"\n\n# Type and submit (presses Enter after typing)\ncanary session type --ref e7 --text \"search query\" --element \"Search box\" --submit\n\n# Fill multiple form fields at once\ncanary session fill --fields '[{\"ref\":\"e3\",\"value\":\"John\"},{\"ref\":\"e4\",\"value\":\"john@example.com\"}]'\n\n# Select a dropdown option\ncanary session select --ref e12 --value \"admin\" --element \"Role dropdown\"\n\n# Press a keyboard key\ncanary session press --key Enter\ncanary session press --key Escape\ncanary session press --key ArrowDown\n\n# Hover to reveal hidden content (tooltips, dropdowns)\ncanary session hover --ref e8 --element \"User menu\"\n\\`\\`\\`\n\n### 4. Navigate\n\n\\`\\`\\`bash\ncanary session navigate --url https://your-app.com/settings\ncanary session back\n\\`\\`\\`\n\n### 5. Scroll\n\n\\`\\`\\`bash\n# Scroll down the page\ncanary session scroll --direction down\n\n# Scroll with more intensity (1-10, default 3)\ncanary session scroll --direction down --amount 8\n\n# Scroll up\ncanary session scroll --direction up\n\\`\\`\\`\n\n### 6. Verify results\n\nAfter every action, take a snapshot to verify the result:\n\n\\`\\`\\`bash\n# Check what happened after clicking submit\ncanary session snapshot\n\n# Search for a success message\ncanary session snapshot --search \"Success|saved\"\n\n# Check for error messages\ncanary session snapshot --search \"Error|failed|invalid\"\n\n# Check console for JavaScript errors\ncanary session console --only-errors\n\n# Check network requests for failed API calls\ncanary session network\n\\`\\`\\`\n\n## Session Management\n\n\\`\\`\\`bash\n# List all active sessions\ncanary session list\n\n# Check session details\ncanary session status\n\n# Stop a session\ncanary session stop\n\n# Stop all sessions\ncanary session stop --all\n\n# When multiple sessions are active, specify which one\ncanary session snapshot --session s1\ncanary session click --ref e5 --element \"Button\" --session s2\n\\`\\`\\`\n\n## Advanced Techniques\n\n### Waiting for page changes\n\n\\`\\`\\`bash\n# Wait for text to appear (after navigation or async action)\ncanary session wait --text \"Dashboard loaded\"\n\n# Wait for text to disappear (loading spinner)\ncanary session wait --text \"Loading...\"\n\n# Wait a fixed time (use sparingly)\ncanary session wait --time 2\n\\`\\`\\`\n\n### Coordinate-based clicking\n\nWhen ref-based clicking doesn't work (overlays, canvas elements), use coordinates. First take a snapshot, look at the screenshot, estimate coordinates, then click:\n\n\\`\\`\\`bash\n# Click at specific coordinates\ncanary session click --x 450 --y 300 --element \"Canvas element\"\n\\`\\`\\`\n\n### Tab management\n\n\\`\\`\\`bash\ncanary session tabs --action list\ncanary session tabs --action new\ncanary session tabs --action select --index 0\ncanary session tabs --action close --index 1\n\\`\\`\\`\n\n### Evaluating JavaScript\n\n\\`\\`\\`bash\ncanary session evaluate --js \"() => document.title\"\ncanary session evaluate --js \"() => window.location.href\"\ncanary session evaluate --js \"() => document.querySelectorAll('.error').length\"\n\\`\\`\\`\n\n### Taking labeled screenshots\n\n\\`\\`\\`bash\n# Take a labeled screenshot for later reference\ncanary session screenshot --label \"before-submit\"\n\n# Full page screenshot\ncanary session screenshot --full-page --label \"full-page-state\"\n\\`\\`\\`\n\n### File downloads\n\n\\`\\`\\`bash\n# List downloaded files\ncanary session evaluate --js \"() => 'trigger download'\"\n# ... after download completes:\ncanary session list-downloads\ncanary session read-download\n\\`\\`\\`\n\n## Testing Guidelines\n\n1. **Always snapshot after actions** — never assume an action succeeded. Take a snapshot to verify.\n2. **Use search to locate elements** — \\`--search \"Submit\"\\` is faster than scanning the full tree.\n3. **Use refs, not coordinates** — ref-based interaction is more reliable. Only fall back to coordinates when refs fail.\n4. **Check for errors** — after form submissions or navigation, check both the page content and console/network for errors.\n5. **Wait for async operations** — if an action triggers an API call, use \\`wait --text\\` for the expected result rather than fixed delays.\n6. **Load credentials for authenticated testing** — use \\`canary session start --credential\\` or \\`canary session load-credential\\` to test as specific users.\n7. **Report what you find** — describe what you see in snapshots, what worked, what failed, and include relevant refs or error messages.\n`,\n};\n","import type { SkillTemplate } from \"./issue-log-xref.js\";\nimport { issueLogXrefTemplate } from \"./issue-log-xref.js\";\nimport { browserTestingTemplate } from \"./browser-testing.js\";\n\nexport type { SkillTemplate };\n\nexport const SKILL_TEMPLATES: Record<string, SkillTemplate> = {\n \"issue-log-xref\": issueLogXrefTemplate,\n \"browser-testing\": browserTestingTemplate,\n};\n","import { SKILL_TEMPLATES } from \"./skill-templates/index.js\";\n\nfunction printSkillHelp() {\n const lines = [\n \"Usage: canary skill <name>\",\n \"\",\n \"Output an AI agent skill template to stdout.\",\n \"Install a skill:\",\n \" mkdir -p .claude/skills/<name> && canary skill <name> > .claude/skills/<name>/SKILL.md\",\n \"\",\n \"Available templates:\",\n ];\n\n for (const [key, template] of Object.entries(SKILL_TEMPLATES)) {\n lines.push(` ${key.padEnd(24)} ${template.description}`);\n }\n\n console.log(lines.join(\"\\n\"));\n}\n\nexport async function runSkill(argv: string[]) {\n const [name] = argv;\n\n if (!name || name === \"help\" || name === \"--help\" || name === \"-h\") {\n printSkillHelp();\n return;\n }\n\n const template = SKILL_TEMPLATES[name];\n if (!template) {\n console.error(`Unknown skill template \"${name}\".\\n`);\n printSkillHelp();\n process.exit(1);\n }\n\n process.stdout.write(template.render());\n}\n"],"mappings":";;;;AAMO,IAAM,uBAAsC;AAAA,EACjD,MAAM;AAAA,EACN,aACE;AAAA,EACF,QAAQ,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6GhB;;;ACjHO,IAAM,yBAAwC;AAAA,EACnD,MAAM;AAAA,EACN,aACE;AAAA,EACF,QAAQ,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmQhB;;;ACvQO,IAAM,kBAAiD;AAAA,EAC5D,kBAAkB;AAAA,EAClB,mBAAmB;AACrB;;;ACPA,SAAS,iBAAiB;AACxB,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,aAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,eAAe,GAAG;AAC7D,UAAM,KAAK,KAAK,IAAI,OAAO,EAAE,CAAC,IAAI,SAAS,WAAW,EAAE;AAAA,EAC1D;AAEA,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;AAEA,eAAsB,SAAS,MAAgB;AAC7C,QAAM,CAAC,IAAI,IAAI;AAEf,MAAI,CAAC,QAAQ,SAAS,UAAU,SAAS,YAAY,SAAS,MAAM;AAClE,mBAAe;AACf;AAAA,EACF;AAEA,QAAM,WAAW,gBAAgB,IAAI;AACrC,MAAI,CAAC,UAAU;AACb,YAAQ,MAAM,2BAA2B,IAAI;AAAA,CAAM;AACnD,mBAAe;AACf,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,OAAO,MAAM,SAAS,OAAO,CAAC;AACxC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workflow.ts","../src/workflow-create.ts"],"sourcesContent":["/**\n * CLI Workflow Management\n *\n * Inspect workflow definitions and steps from the terminal.\n */\n\nimport fs from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport { apiRequest } from \"./cli-helpers.js\";\nimport { handleWorkflowCreate } from \"./workflow-create.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype WorkflowNode = {\n id: string;\n nodeType: string;\n orderIndex: number;\n configJson: Record<string, unknown>;\n};\n\ntype WorkflowSummary = {\n id: string;\n name: string;\n description: string | null;\n status: string;\n flowType: string;\n nodes: WorkflowNode[];\n edges: unknown[];\n};\n\ntype Pagination = {\n page: number;\n pageSize: number;\n totalItems: number;\n totalPages: number;\n};\n\ntype WorkflowListItem = {\n id: string;\n name: string;\n status: string;\n flowType: string;\n nodeCount?: number;\n};\n\ntype WorkflowListResponse = {\n ok: boolean;\n error?: string;\n data: WorkflowListItem[];\n pagination: Pagination;\n};\n\ntype WorkflowDetailResponse = {\n ok?: boolean;\n error?: string;\n workflow?: WorkflowSummary;\n targetOrgId?: string;\n targetOrgName?: string;\n};\n\n/* ── Node Helpers ─────────────────────────────────────────────────────── */\n\nfunction truncate(text: string, max: number): string {\n if (text.length <= max) return text;\n return text.slice(0, max - 1) + \"…\";\n}\n\nfunction getNodeTitle(node: WorkflowNode): string {\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\":\n return (c.credentialName as string) || \"Login\";\n case \"navigate\":\n return (c.pageTitle as string) || truncate((c.pageUrl as string) || \"Navigate\", 40);\n case \"action\":\n return (c.customActionTitle as string) || truncate((c.actionDescription as string) || \"Action\", 50);\n case \"assertion\":\n return truncate((c.condition as string) || \"Assertion\", 50);\n case \"setup\":\n return (c.setupFlowName as string) || \"Setup\";\n case \"seed\":\n return (c.seedWorkflowName as string) || \"Seed\";\n case \"wait\":\n return \"Wait\";\n case \"condition\":\n return truncate((c.conditionDescription as string) || \"Condition\", 50);\n case \"end\":\n return \"End\";\n case \"api_sequence\":\n return \"API Sequence\";\n default:\n return node.nodeType;\n }\n}\n\nfunction getCleanConfig(node: WorkflowNode): Record<string, unknown> {\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\":\n return pick(c, [\"credentialName\", \"performLogin\"]);\n case \"navigate\":\n return {\n ...pick(c, [\"pageUrl\", \"pageTitle\"]),\n ...(c.openInNewTab ? { openInNewTab: true } : {}),\n };\n case \"action\":\n return pick(c, [\n \"actionDescription\",\n \"customActionInstructions\",\n \"playbookSteps\",\n \"inputValues\",\n \"declaredOutputs\",\n ]);\n case \"assertion\":\n return pick(c, [\"condition\", \"expectedOutcome\", \"strict\", \"alwaysUseAgent\"]);\n case \"setup\":\n return pick(c, [\"setupFlowId\", \"setupFlowName\"]);\n case \"seed\":\n return pick(c, [\"seedWorkflowId\", \"seedWorkflowName\", \"snapshotVersion\"]);\n case \"end\":\n return pick(c, [\"outcome\"]);\n case \"wait\":\n case \"condition\":\n case \"api_sequence\":\n return c;\n default:\n return c;\n }\n}\n\nfunction pick(\n obj: Record<string, unknown>,\n keys: string[]\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const key of keys) {\n if (obj[key] !== undefined && obj[key] !== null) {\n result[key] = obj[key];\n }\n }\n return result;\n}\n\n/* ── Markdown Formatter ───────────────────────────────────────────────── */\n\nfunction formatWorkflowMarkdown(workflow: WorkflowSummary): string {\n const sections: string[] = [];\n\n sections.push(`# ${workflow.name}`);\n const meta = [`**ID:** ${workflow.id}`, `**Status:** ${workflow.status}`, `**Type:** ${workflow.flowType}`];\n sections.push(meta.join(\" | \"));\n\n if (workflow.description) {\n sections.push(`**Description:** ${workflow.description}`);\n }\n\n sections.push(\"---\");\n\n const sorted = [...workflow.nodes].sort((a, b) => a.orderIndex - b.orderIndex);\n\n for (let i = 0; i < sorted.length; i++) {\n const node = sorted[i];\n const title = getNodeTitle(node);\n const stepLines: string[] = [];\n\n stepLines.push(`## Step ${i + 1}: ${title}`);\n stepLines.push(`**Type:** ${node.nodeType}`);\n\n const c = node.configJson;\n switch (node.nodeType) {\n case \"login\": {\n if (c.credentialName) stepLines.push(`**Credential:** ${c.credentialName}`);\n if (c.performLogin !== undefined) stepLines.push(`**Perform Login:** ${c.performLogin ? \"Yes\" : \"No\"}`);\n break;\n }\n case \"navigate\": {\n if (c.pageUrl) stepLines.push(`**URL:** ${c.pageUrl}`);\n if (c.openInNewTab) stepLines.push(`**Open in New Tab:** Yes`);\n break;\n }\n case \"action\": {\n const instructions = (c.customActionInstructions as string) || (c.actionDescription as string);\n if (instructions) stepLines.push(`**Instructions:** ${instructions}`);\n const playbook = c.playbookSteps as Array<{ order: number; action: string; target: string }> | undefined;\n if (playbook && playbook.length > 0) {\n stepLines.push(\"\");\n stepLines.push(\"**Playbook:**\");\n for (const step of playbook) {\n stepLines.push(`${step.order}. [${step.action}] ${step.target}`);\n }\n }\n break;\n }\n case \"assertion\": {\n if (c.condition) stepLines.push(`**Condition:** ${c.condition}`);\n if (c.strict) stepLines.push(`**Strict:** Yes`);\n break;\n }\n case \"setup\": {\n if (c.setupFlowName) stepLines.push(`**Setup Flow:** ${c.setupFlowName}`);\n break;\n }\n case \"seed\": {\n if (c.seedWorkflowName) stepLines.push(`**Seed Workflow:** ${c.seedWorkflowName}`);\n break;\n }\n case \"wait\": {\n if (c.waitDuration) stepLines.push(`**Duration:** ${c.waitDuration}`);\n break;\n }\n case \"condition\": {\n if (c.conditionDescription) stepLines.push(`**Condition:** ${c.conditionDescription}`);\n break;\n }\n case \"end\": {\n if (c.outcome) stepLines.push(`**Outcome:** ${c.outcome}`);\n break;\n }\n }\n\n sections.push(stepLines.join(\"\\n\"));\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/* ── JSON Formatter ───────────────────────────────────────────────────── */\n\nfunction formatWorkflowJson(workflow: WorkflowSummary): object {\n const sorted = [...workflow.nodes].sort((a, b) => a.orderIndex - b.orderIndex);\n\n return {\n workflow: {\n id: workflow.id,\n name: workflow.name,\n description: workflow.description,\n status: workflow.status,\n flowType: workflow.flowType,\n },\n steps: sorted.map((node, i) => ({\n index: i + 1,\n nodeId: node.id,\n nodeType: node.nodeType,\n title: getNodeTitle(node),\n config: getCleanConfig(node),\n })),\n };\n}\n\n/* ── Sub-command Handlers ─────────────────────────────────────────────── */\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n\n const params = new URLSearchParams();\n const search = getArgValue(argv, \"--search\");\n const flowType = getArgValue(argv, \"--flow-type\");\n const status = getArgValue(argv, \"--status\");\n const page = getArgValue(argv, \"--page\");\n const pageSize = getArgValue(argv, \"--page-size\");\n\n if (search) params.set(\"search\", search);\n if (flowType) params.set(\"flowType\", flowType);\n if (status) params.set(\"status\", status);\n if (page) params.set(\"page\", page);\n if (pageSize) params.set(\"pageSize\", pageSize);\n\n const qs = params.toString();\n const path = `/workflows${qs ? `?${qs}` : \"\"}`;\n\n const result = await apiRequest<WorkflowListResponse>(apiUrl, token, \"GET\", path);\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify({ data: result.data, pagination: result.pagination }, null, 2));\n return;\n }\n\n // Default table output\n const { data, pagination } = result;\n console.log(`Workflows: ${pagination.totalItems} total (page ${pagination.page}/${pagination.totalPages})\\n`);\n\n if (data.length === 0) {\n console.log(\"No workflows found.\");\n return;\n }\n\n // Column widths\n const idW = 36;\n const nameW = 30;\n const statusW = 12;\n const typeW = 10;\n\n const header = [\n \"ID\".padEnd(idW),\n \"Name\".padEnd(nameW),\n \"Status\".padEnd(statusW),\n \"Type\".padEnd(typeW),\n ].join(\" \");\n\n console.log(header);\n\n for (const w of data) {\n const row = [\n w.id.padEnd(idW),\n truncate(w.name, nameW).padEnd(nameW),\n w.status.padEnd(statusW),\n w.flowType.padEnd(typeW),\n ].join(\" \");\n console.log(row);\n }\n}\n\nasync function handleGet(argv: string[], apiUrl: string, token: string): Promise<void> {\n const workflowId = argv[0];\n if (!workflowId || workflowId.startsWith(\"--\")) {\n console.error(\"Error: Missing workflow ID.\");\n console.error(\"Usage: canary workflow get <workflowId>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\") || getArgValue(argv, \"--format\") === \"json\";\n const markdownOutput = hasFlag(argv, \"--markdown\") || getArgValue(argv, \"--format\") === \"markdown\";\n const outputFile = getArgValue(argv, \"--output\");\n\n const res = await fetch(`${apiUrl}/workflows/${workflowId}`, {\n headers: { Authorization: `Bearer ${token}`, \"Content-Type\": \"application/json\" },\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 const result = (await res.json()) as WorkflowDetailResponse;\n\n if (result.error === \"WORKFLOW_NOT_FOUND\") {\n console.error(\"Error: Workflow not found.\");\n process.exit(1);\n }\n if (result.error === \"WORKFLOW_WRONG_ORG\") {\n console.error(`Error: Workflow belongs to another org: ${result.targetOrgName ?? result.targetOrgId}`);\n process.exit(1);\n }\n if (!result.workflow) {\n console.error(`Error: ${result.error ?? \"Unexpected response\"}`);\n process.exit(1);\n }\n\n const workflow = result.workflow;\n let output: string;\n\n if (markdownOutput) {\n output = formatWorkflowMarkdown(workflow);\n } else {\n // Default to JSON for agent-friendly output\n output = JSON.stringify(formatWorkflowJson(workflow), null, 2);\n }\n\n if (outputFile) {\n try {\n await fs.writeFile(outputFile, output, \"utf-8\");\n console.log(`Written to ${outputFile}`);\n } catch (err) {\n console.error(`Error writing to ${outputFile}: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n return;\n }\n\n console.log(output);\n}\n\n/* ── Help & Entry Point ───────────────────────────────────────────────── */\n\nfunction printWorkflowHelp(): void {\n console.log(\n [\n \"Usage: canary workflow <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list [options] List workflows\",\n \" get <workflowId> [options] Get workflow definition with steps\",\n \" create [options] Create workflow from JSON (stdin or file)\",\n \"\",\n \"List options:\",\n \" --search <query> Search by name\",\n \" --flow-type <type> Filter: standard, setup, seed, teardown\",\n \" --status <status> Filter: draft, published, archived\",\n \" --page <n> Page number (default: 1)\",\n \" --page-size <n> Page size (default: 25)\",\n \" --json Output raw JSON\",\n \"\",\n \"Get options:\",\n \" --format json|markdown Output format (default: json)\",\n \" --json Shorthand for --format json\",\n \" --markdown Shorthand for --format markdown\",\n \" --output <file> Write output to file\",\n \"\",\n \"Create options:\",\n \" --from-stdin Read workflow JSON from stdin (default)\",\n \" --from-file <path> Read workflow JSON from file\",\n \" --quarantine Create with quarantined status (auto-promotes after 3 passes)\",\n \" --run-and-publish Create as draft, run test, publish on success\",\n \" --property <name|id> Property for run-and-publish\",\n \" --environment <name|id> Environment for run-and-publish\",\n \"\",\n \"Common options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --api-url <url> API URL override\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runWorkflow(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printWorkflowHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"get\":\n await handleGet(rest, apiUrl, token);\n break;\n case \"create\":\n await handleWorkflowCreate(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printWorkflowHelp();\n process.exit(1);\n }\n}\n","/**\n * CLI Workflow Creation\n *\n * Creates a workflow with nodes and edges atomically via the API.\n * Supports reading workflow JSON from stdin or file.\n *\n * @module workflow-create\n */\n\nimport fs from \"node:fs/promises\";\nimport process from \"node:process\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { getArgValue, hasFlag } from \"./auth.js\";\n\n/* ── Types ────────────────────────────────────────────────────────────── */\n\ntype WorkflowNodeInput = {\n nodeType: string;\n configJson?: Record<string, unknown>;\n};\n\ntype WorkflowCreateInput = {\n name: string;\n description?: string;\n status?: \"draft\" | \"quarantined\";\n flowType?: \"standard\" | \"setup\" | \"seed\" | \"teardown\";\n createdVia?: string;\n propertyId?: string;\n credentialId?: string;\n nodes: WorkflowNodeInput[];\n};\n\ntype CreateResponse = {\n ok: boolean;\n error?: string;\n workflow?: {\n id: string;\n name: string;\n status: string;\n flowType: string;\n createdVia: string | null;\n };\n nodes?: Array<{ id: string; nodeType: string; orderIndex: number }>;\n edges?: Array<{ id: string; sourceNodeId: string; targetNodeId: string }>;\n};\n\ntype SessionStatusResponse = {\n sessions?: Array<{\n id: string;\n propertyId?: string;\n credentialId?: string;\n propertyName?: string;\n credentialName?: string;\n }>;\n};\n\ntype TriggerRunResponse = {\n ok: boolean;\n error?: string;\n jobId?: string;\n suiteId?: string;\n appUrl?: string;\n};\n\ntype WorkflowTestEvent = {\n type: \"workflow-test\";\n workflowId: string;\n testRunId: string;\n status: string;\n errorMessage?: string | null;\n message?: string;\n};\n\ntype WorkflowTestSuiteEvent = {\n type: \"workflow-test-suite\";\n status: string;\n totalWorkflows: number;\n completedWorkflows: number;\n failedWorkflows: number;\n successfulWorkflows: number;\n};\n\n/* ── Input Reading ───────────────────────────────────────────────────── */\n\nasync function readFromStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n const stdin = process.stdin;\n\n if (stdin.isTTY) {\n throw new Error(\"No input on stdin. Pipe workflow JSON or use --from-file.\");\n }\n\n return new Promise((resolve, reject) => {\n stdin.on(\"data\", (chunk) => chunks.push(chunk));\n stdin.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n stdin.on(\"error\", reject);\n });\n}\n\nasync function readWorkflowInput(argv: string[]): Promise<WorkflowCreateInput> {\n let raw: string;\n\n const fromFile = getArgValue(argv, \"--from-file\");\n if (fromFile) {\n try {\n raw = await fs.readFile(fromFile, \"utf-8\");\n } catch (err) {\n throw new Error(`Failed to read file ${fromFile}: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n raw = await readFromStdin();\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(\"Invalid JSON input. Please provide valid workflow JSON.\");\n }\n\n const input = parsed as WorkflowCreateInput;\n\n if (!input.name || typeof input.name !== \"string\") {\n throw new Error('Workflow JSON must include a \"name\" string field.');\n }\n\n if (!Array.isArray(input.nodes) || input.nodes.length === 0) {\n throw new Error('Workflow JSON must include a non-empty \"nodes\" array.');\n }\n\n const validNodeTypes = [\"login\", \"navigate\", \"action\", \"assertion\", \"end\", \"setup\", \"seed\", \"wait\"];\n for (const node of input.nodes) {\n if (!node.nodeType || !validNodeTypes.includes(node.nodeType)) {\n throw new Error(\n `Invalid nodeType \"${node.nodeType}\". Valid types: ${validNodeTypes.join(\", \")}`\n );\n }\n }\n\n return input;\n}\n\n/* ── Session Auto-linking ────────────────────────────────────────────── */\n\nasync function detectSessionContext(\n apiUrl: string,\n token: string\n): Promise<{ propertyId?: string; credentialId?: string }> {\n try {\n const res = await fetch(`${apiUrl}/sessions/status`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return {};\n\n const data = (await res.json()) as SessionStatusResponse;\n const sessions = data.sessions;\n\n if (!sessions || sessions.length === 0) return {};\n\n // Use the first active session's property/credential\n const session = sessions[0];\n return {\n propertyId: session.propertyId,\n credentialId: session.credentialId,\n };\n } catch {\n // Session detection is best-effort\n return {};\n }\n}\n\n/* ── Run-and-Publish ─────────────────────────────────────────────────── */\n\nasync function runAndPublish(opts: {\n apiUrl: string;\n token: string;\n workflowId: string;\n workflowName: string;\n propertyId?: string;\n environmentId?: string;\n}): Promise<boolean> {\n const { apiUrl, token, workflowId, workflowName } = opts;\n\n // 1. Trigger a test run for this single workflow\n const body: Record<string, string> = {};\n if (opts.propertyId) body.propertyId = opts.propertyId;\n if (opts.environmentId) body.environmentId = opts.environmentId;\n\n const triggerUrl = `${apiUrl}/workflows/test-runs?namePattern=${encodeURIComponent(workflowName)}`;\n\n let triggerRes: Response;\n try {\n triggerRes = await fetch(triggerUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n } catch (err) {\n console.error(`Failed to trigger test run: ${err}`);\n return false;\n }\n\n if (!triggerRes.ok) {\n const errorText = await triggerRes.text();\n console.error(`Failed to trigger test run: ${triggerRes.status} ${errorText}`);\n return false;\n }\n\n const triggerData = (await triggerRes.json()) as TriggerRunResponse;\n if (!triggerData.ok || !triggerData.suiteId) {\n console.error(`Failed to trigger test run: ${triggerData.error ?? \"Unknown error\"}`);\n return false;\n }\n\n const { suiteId } = triggerData;\n console.log(`Test run started (suite: ${suiteId})`);\n console.log(\"Streaming results...\\n\");\n\n // 2. Stream SSE events\n const streamUrl = `${apiUrl}/workflows/test-runs/stream?suiteId=${suiteId}`;\n\n let streamRes: Response;\n try {\n streamRes = await fetch(streamUrl, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"text/event-stream\",\n },\n });\n } catch (err) {\n console.error(`Failed to connect to event stream: ${err}`);\n return false;\n }\n\n if (!streamRes.ok || !streamRes.body) {\n console.error(`Failed to connect to event stream: ${streamRes.status}`);\n return false;\n }\n\n let success = false;\n let hasCompleted = false;\n\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (!event.data) return;\n\n try {\n const data = JSON.parse(event.data) as WorkflowTestEvent | WorkflowTestSuiteEvent;\n\n if (event.event === \"workflow-test\") {\n const testEvent = data as WorkflowTestEvent;\n if (testEvent.status === \"success\") {\n console.log(` \\u2713 ${workflowName}`);\n success = true;\n } else if (testEvent.status === \"failed\") {\n console.log(` \\u2717 ${workflowName}`);\n if (testEvent.errorMessage) {\n console.log(` Error: ${testEvent.errorMessage.slice(0, 200)}`);\n }\n success = false;\n } else if (testEvent.status === \"running\") {\n console.log(` \\u25B6 Running ${workflowName}...`);\n }\n }\n\n if (event.event === \"workflow-test-suite\") {\n const suiteEvent = data as WorkflowTestSuiteEvent;\n if (suiteEvent.status === \"completed\") {\n hasCompleted = true;\n }\n }\n } catch {\n // Ignore parse errors for keepalive\n }\n },\n });\n\n const reader = streamRes.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (!hasCompleted) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n reader.releaseLock();\n }\n\n // 3. If success, publish the workflow\n if (success) {\n try {\n const publishRes = await fetch(`${apiUrl}/workflows/${workflowId}/publish`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (publishRes.ok) {\n console.log(`\\n\\u2713 Workflow published successfully`);\n return true;\n } else {\n console.error(`\\nFailed to publish workflow: ${publishRes.status}`);\n return false;\n }\n } catch (err) {\n console.error(`\\nFailed to publish workflow: ${err}`);\n return false;\n }\n }\n\n console.log(`\\n\\u2717 Test failed — workflow left in draft for manual review`);\n return false;\n}\n\n/* ── Main Handler ────────────────────────────────────────────────────── */\n\nexport async function handleWorkflowCreate(\n argv: string[],\n apiUrl: string,\n token: string\n): Promise<void> {\n const quarantine = hasFlag(argv, \"--quarantine\");\n const runAndPublishFlag = hasFlag(argv, \"--run-and-publish\");\n const propertyArg = getArgValue(argv, \"--property\");\n const environmentArg = getArgValue(argv, \"--environment\");\n\n if (quarantine && runAndPublishFlag) {\n console.error(\"Error: --quarantine and --run-and-publish are mutually exclusive.\");\n process.exit(1);\n }\n\n // 1. Read and validate input\n let input: WorkflowCreateInput;\n try {\n input = await readWorkflowInput(argv);\n } catch (err) {\n console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // 2. Auto-detect session context for property/credential linking\n if (!input.propertyId && !input.credentialId) {\n const sessionCtx = await detectSessionContext(apiUrl, token);\n if (sessionCtx.propertyId) {\n input.propertyId = sessionCtx.propertyId;\n console.log(`Auto-linked property from active session`);\n }\n if (sessionCtx.credentialId) {\n input.credentialId = sessionCtx.credentialId;\n console.log(`Auto-linked credential from active session`);\n }\n }\n\n // 3. Set status based on flags\n if (quarantine) {\n input.status = \"quarantined\";\n }\n\n input.createdVia = \"cli\";\n\n // 4. Create workflow via API\n const res = await fetch(`${apiUrl}/workflows/create-with-nodes`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(input),\n });\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 const result = (await res.json()) as CreateResponse;\n\n if (!result.ok || !result.workflow) {\n console.error(`Error: ${result.error ?? \"Failed to create workflow\"}`);\n process.exit(1);\n }\n\n const { workflow, nodes, edges } = result;\n\n console.log(`\\n\\u2713 Workflow created: ${workflow.name}`);\n console.log(` ID: ${workflow.id}`);\n console.log(` Status: ${workflow.status}`);\n console.log(` Nodes: ${nodes?.length ?? 0}`);\n console.log(` Edges: ${edges?.length ?? 0}`);\n\n // Derive web UI URL from API URL\n const webUrl = apiUrl.replace(/\\/api\\b|api\\./, \"app.\").replace(/:\\d+$/, \":5173\");\n console.log(` URL: ${webUrl}/workflows/${workflow.id}`);\n\n // 5. Run-and-publish if requested\n if (runAndPublishFlag) {\n console.log(\"\\nTriggering test run...\");\n\n const passed = await runAndPublish({\n apiUrl,\n token,\n workflowId: workflow.id,\n workflowName: workflow.name,\n propertyId: propertyArg ?? input.propertyId,\n environmentId: environmentArg,\n });\n\n if (!passed) {\n process.exit(1);\n }\n }\n}\n"],"mappings":"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","names":["fs","process","process","fs"]}