@canaryai/cli 0.2.8 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -92
- package/dist/chunk-CEW4BDXD.js +186 -0
- package/dist/chunk-CEW4BDXD.js.map +1 -0
- package/dist/chunk-ERSNYLMZ.js +229 -0
- package/dist/chunk-ERSNYLMZ.js.map +1 -0
- package/dist/{chunk-FK3EZADZ.js → chunk-MSMC6UXW.js} +2021 -873
- package/dist/chunk-MSMC6UXW.js.map +1 -0
- package/dist/{chunk-K2OB72B6.js → chunk-Q7WFBG5C.js} +2 -2
- package/dist/{debug-workflow-55G4Y6YT.js → debug-workflow-53ULOFJC.js} +57 -36
- package/dist/debug-workflow-53ULOFJC.js.map +1 -0
- package/dist/{docs-RPFT7ZJB.js → docs-BEE3LOCO.js} +2 -2
- package/dist/{feature-flag-2FDSKOVX.js → feature-flag-CYTDV4ZB.js} +3 -2
- package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-CYTDV4ZB.js.map} +1 -1
- package/dist/index.js +72 -137
- package/dist/index.js.map +1 -1
- package/dist/init-M6I3MG3D.js +146 -0
- package/dist/init-M6I3MG3D.js.map +1 -0
- package/dist/{issues-6ZDNDSD6.js → issues-NLM72HLU.js} +3 -2
- package/dist/{issues-6ZDNDSD6.js.map → issues-NLM72HLU.js.map} +1 -1
- package/dist/{knobs-MZRTYS3P.js → knobs-O35GAU5M.js} +3 -2
- package/dist/{knobs-MZRTYS3P.js.map → knobs-O35GAU5M.js.map} +1 -1
- package/dist/list-4K4EIGAT.js +57 -0
- package/dist/list-4K4EIGAT.js.map +1 -0
- package/dist/local-NHXXPHZ3.js +63 -0
- package/dist/local-NHXXPHZ3.js.map +1 -0
- package/dist/{local-browser-X7J27IGS.js → local-browser-VAZORCO3.js} +3 -3
- package/dist/login-ZLP64YQP.js +130 -0
- package/dist/login-ZLP64YQP.js.map +1 -0
- package/dist/mcp-ZF5G5DCB.js +377 -0
- package/dist/mcp-ZF5G5DCB.js.map +1 -0
- package/dist/{record-4OX7HXWQ.js → record-V6QKFFH3.js} +133 -72
- package/dist/record-V6QKFFH3.js.map +1 -0
- package/dist/{release-L4IXOHDF.js → release-7TI7EIGD.js} +8 -4
- package/dist/release-7TI7EIGD.js.map +1 -0
- package/dist/session-UGNJXRUW.js +819 -0
- package/dist/session-UGNJXRUW.js.map +1 -0
- package/dist/skill-ORWAPBDW.js +424 -0
- package/dist/skill-ORWAPBDW.js.map +1 -0
- package/dist/{src-I4EXB5OD.js → src-4VIDSK4A.js} +18 -2
- package/dist/start-E532F3BU.js +112 -0
- package/dist/start-E532F3BU.js.map +1 -0
- package/dist/workflow-HXIUXRFI.js +613 -0
- package/dist/workflow-HXIUXRFI.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-6WWHXWCS.js +0 -65
- package/dist/chunk-6WWHXWCS.js.map +0 -1
- package/dist/chunk-DXIAHB72.js +0 -340
- package/dist/chunk-DXIAHB72.js.map +0 -1
- package/dist/chunk-FK3EZADZ.js.map +0 -1
- package/dist/debug-workflow-55G4Y6YT.js.map +0 -1
- package/dist/mcp-4JVLADZL.js +0 -688
- package/dist/mcp-4JVLADZL.js.map +0 -1
- package/dist/record-4OX7HXWQ.js.map +0 -1
- package/dist/release-L4IXOHDF.js.map +0 -1
- /package/dist/{chunk-K2OB72B6.js.map → chunk-Q7WFBG5C.js.map} +0 -0
- /package/dist/{docs-RPFT7ZJB.js.map → docs-BEE3LOCO.js.map} +0 -0
- /package/dist/{local-browser-X7J27IGS.js.map → local-browser-VAZORCO3.js.map} +0 -0
- /package/dist/{src-I4EXB5OD.js.map → src-4VIDSK4A.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session/index.ts","../src/session/credentials.ts","../src/session/daemon.ts"],"sourcesContent":["/**\n * CLI session command — `canary session <subcommand>`.\n *\n * Flat subcommands for session lifecycle and browser tool execution.\n * All commands delegate to the session daemon via HTTP.\n *\n * @module\n */\n\nimport process from 'node:process';\nimport { getArgValue, hasFlag } from '../auth.js';\nimport {\n createSession,\n listSessions,\n getSession,\n deleteSession,\n deleteAllSessions,\n swapSessionContext,\n callTool,\n resolveTargetSession,\n} from './daemon-client.js';\nimport { resolveCredential } from './credentials.js';\nimport { runDaemon } from './daemon.js';\nimport type { SessionInfo, ToolResponse } from './types.js';\n\n/* ── Output formatting ───────────────────────────────────────────────── */\n\nfunction formatUptime(startedAt: string): string {\n const seconds = Math.floor((Date.now() - new Date(startedAt).getTime()) / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const secs = seconds % 60;\n if (minutes < 60) return `${minutes}m ${secs}s`;\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return `${hours}h ${mins}m`;\n}\n\nfunction printSessionTable(sessions: SessionInfo[]) {\n if (sessions.length === 0) {\n console.log('No active sessions.');\n return;\n }\n\n const header = ['ID', 'NAME', 'MODE', 'CREDENTIAL', 'URL', 'UPTIME'];\n const rows = sessions.map((s) => [\n s.id,\n s.name,\n s.mode,\n s.credentialName ?? '(none)',\n s.url ?? '(none)',\n formatUptime(s.startedAt),\n ]);\n\n // Calculate column widths\n const widths = header.map((h, i) =>\n Math.max(h.length, ...rows.map((r) => r[i].length))\n );\n\n const pad = (str: string, width: number) => str.padEnd(width);\n console.log(header.map((h, i) => pad(h, widths[i])).join(' '));\n for (const row of rows) {\n console.log(row.map((cell, i) => pad(cell, widths[i])).join(' '));\n }\n}\n\nfunction printToolResult(result: ToolResponse, jsonMode: boolean) {\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n if (result.text) {\n console.log(result.text);\n }\n\n if (result.images?.length) {\n console.log(`[${result.images.length} image(s) captured]`);\n }\n}\n\n/* ── Subcommand: start ───────────────────────────────────────────────── */\n\nasync function handleStart(argv: string[]) {\n const headless = hasFlag(argv, '--headless');\n const name = getArgValue(argv, '--name');\n const url = getArgValue(argv, '--url');\n const storageStatePath = getArgValue(argv, '--storage-state');\n const jsonMode = hasFlag(argv, '--json');\n\n // Credential handling\n const credentialIdx = argv.indexOf('--credential');\n let resolvedCred: { storageStatePath: string | undefined; credentialName: string } | null = null;\n\n if (credentialIdx !== -1) {\n // Check if next arg is a value or another flag\n const nextArg = argv[credentialIdx + 1];\n const credentialArg = nextArg && !nextArg.startsWith('--') ? nextArg : undefined;\n resolvedCred = await resolveCredential(argv, credentialArg);\n if (!resolvedCred) {\n process.exitCode = 1;\n return;\n }\n }\n\n const result = await createSession({\n headless,\n name,\n url,\n storageStatePath: resolvedCred?.storageStatePath ?? storageStatePath,\n credentialName: resolvedCred?.credentialName,\n });\n\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n const session = result.data!;\n console.log(`Session \"${session.id}\" started (${session.mode})`);\n if (session.credentialName) {\n console.log(` Credential: ${session.credentialName}`);\n }\n // Show the requested URL if provided, since the daemon responds before navigation completes\n const displayUrl = url ?? session.url;\n if (displayUrl) {\n console.log(` URL: ${displayUrl}`);\n }\n}\n\n/* ── Subcommand: list ────────────────────────────────────────────────── */\n\nasync function handleList(argv: string[]) {\n const jsonMode = hasFlag(argv, '--json');\n const result = await listSessions();\n\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n printSessionTable(result.data ?? []);\n}\n\n/* ── Subcommand: status ──────────────────────────────────────────────── */\n\nasync function handleStatus(argv: string[]) {\n const sessionId =\n getArgValue(argv, '--session') ||\n (argv[0] && !argv[0].startsWith('--') ? argv[0] : undefined);\n const jsonMode = hasFlag(argv, '--json');\n\n try {\n const target = await resolveTargetSession(sessionId);\n const result = await getSession(target.id);\n\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n if (!result.ok || !result.data) {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n const s = result.data;\n console.log(`Session: ${s.id} (${s.name})`);\n console.log(` Mode: ${s.mode}`);\n console.log(` Credential: ${s.credentialName ?? '(none)'}`);\n console.log(` URL: ${s.url ?? '(none)'}`);\n console.log(` Uptime: ${formatUptime(s.startedAt)}`);\n console.log(` Started: ${s.startedAt}`);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n}\n\n/* ── Subcommand: stop ────────────────────────────────────────────────── */\n\nasync function handleStop(argv: string[]) {\n const jsonMode = hasFlag(argv, '--json');\n\n if (hasFlag(argv, '--all')) {\n const result = await deleteAllSessions();\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n } else if (result.ok) {\n console.log('All sessions stopped.');\n } else {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n }\n return;\n }\n\n const sessionId =\n getArgValue(argv, '--session') ||\n (argv[0] && !argv[0].startsWith('--') ? argv[0] : undefined);\n\n try {\n const target = await resolveTargetSession(sessionId);\n const result = await deleteSession(target.id);\n\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n } else if (result.ok) {\n console.log(`Session \"${target.id}\" stopped.`);\n } else {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n }\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n}\n\n/* ── Subcommand: load-credential ────────────────────────────────────── */\n\nasync function handleLoadCredential(argv: string[]) {\n const jsonMode = hasFlag(argv, '--json');\n const sessionId =\n getArgValue(argv, '--session') ||\n (argv[0] && !argv[0].startsWith('--') ? argv[0] : undefined);\n\n // Resolve which session to target\n let target;\n try {\n target = await resolveTargetSession(sessionId);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n return;\n }\n\n // Credential selection (interactive by default, or --credential <name|id>)\n const credentialIdx = argv.indexOf('--credential');\n const nextArg = credentialIdx !== -1 ? argv[credentialIdx + 1] : undefined;\n const credentialArg = nextArg && !nextArg.startsWith('--') ? nextArg : undefined;\n\n // Also support --storage-state for local files\n const storageStatePath = getArgValue(argv, '--storage-state');\n\n let resolvedStorageStatePath: string | undefined;\n let credentialName: string | undefined;\n\n if (storageStatePath) {\n resolvedStorageStatePath = storageStatePath;\n credentialName = '(local storage state)';\n } else {\n // Interactive or named credential resolution\n const resolvedCred = await resolveCredential(argv, credentialArg);\n if (!resolvedCred) {\n process.exitCode = 1;\n return;\n }\n resolvedStorageStatePath = resolvedCred.storageStatePath;\n credentialName = resolvedCred.credentialName;\n }\n\n const result = await swapSessionContext(target.id, {\n storageStatePath: resolvedStorageStatePath,\n credentialName,\n });\n\n if (jsonMode) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exitCode = 1;\n return;\n }\n\n const session = result.data!;\n console.log(`Credential loaded into session \"${session.id}\".`);\n if (session.credentialName) {\n console.log(` Credential: ${session.credentialName}`);\n }\n if (session.url) {\n console.log(` URL: ${session.url}`);\n }\n}\n\n/* ── Browser tool subcommands ────────────────────────────────────────── */\n\nasync function handleToolCommand(toolName: string, argv: string[]) {\n const sessionIdOrName = getArgValue(argv, '--session');\n const jsonMode = hasFlag(argv, '--json');\n\n try {\n const target = await resolveTargetSession(sessionIdOrName);\n const args = buildToolArgs(toolName, argv);\n const result = await callTool(target.id, toolName, args);\n printToolResult(result, jsonMode);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n}\n\nfunction buildToolArgs(toolName: string, argv: string[]): Record<string, unknown> {\n const args: Record<string, unknown> = {};\n\n switch (toolName) {\n case 'navigate':\n args.url = getArgValue(argv, '--url');\n break;\n\n case 'snapshot':\n args.search = getArgValue(argv, '--search');\n args.mode = getArgValue(argv, '--mode') ?? 'tree';\n break;\n\n case 'screenshot':\n args.fullPage = hasFlag(argv, '--full-page');\n args.label = getArgValue(argv, '--label');\n args.returnImage = true;\n break;\n\n case 'evaluate':\n args.function = getArgValue(argv, '--js');\n break;\n\n case 'click': {\n const ref = getArgValue(argv, '--ref');\n const x = getArgValue(argv, '--x');\n const y = getArgValue(argv, '--y');\n if (ref) args.ref = ref;\n if (x) args.x = parseInt(x, 10);\n if (y) args.y = parseInt(y, 10);\n args.element = getArgValue(argv, '--element') ?? '';\n break;\n }\n\n case 'type':\n args.ref = getArgValue(argv, '--ref');\n args.text = getArgValue(argv, '--text');\n args.element = getArgValue(argv, '--element') ?? '';\n args.submit = hasFlag(argv, '--submit');\n break;\n\n case 'fill_form': {\n const fieldsStr = getArgValue(argv, '--fields');\n if (fieldsStr) {\n try {\n args.fields = JSON.parse(fieldsStr);\n } catch {\n console.error('Error: --fields must be valid JSON');\n process.exitCode = 1;\n }\n }\n break;\n }\n\n case 'select_option':\n args.ref = getArgValue(argv, '--ref');\n args.value = getArgValue(argv, '--value');\n args.element = getArgValue(argv, '--element') ?? '';\n break;\n\n case 'press_key':\n args.key = getArgValue(argv, '--key');\n break;\n\n case 'scroll':\n args.direction = getArgValue(argv, '--direction') ?? 'down';\n {\n const amount = getArgValue(argv, '--amount');\n if (amount) args.amount = parseInt(amount, 10);\n }\n break;\n\n case 'hover':\n args.ref = getArgValue(argv, '--ref');\n args.element = getArgValue(argv, '--element') ?? '';\n break;\n\n case 'tabs': {\n args.action = getArgValue(argv, '--action') ?? 'list';\n const index = getArgValue(argv, '--index');\n if (index) args.index = parseInt(index, 10);\n break;\n }\n\n case 'wait_for': {\n args.text = getArgValue(argv, '--text');\n const timeout = getArgValue(argv, '--timeout');\n if (timeout) args.timeout = parseInt(timeout, 10);\n break;\n }\n\n case 'console_messages':\n args.onlyErrors = hasFlag(argv, '--only-errors');\n break;\n\n // navigate_back, network_requests, close, list_downloads, read_download\n // have no special args or use defaults\n }\n\n return args;\n}\n\n/* ── Command aliases ─────────────────────────────────────────────────── */\n\n/** Map flat CLI subcommands to browser tool names */\nconst TOOL_ALIASES: Record<string, string> = {\n navigate: 'navigate',\n back: 'navigate_back',\n snapshot: 'snapshot',\n screenshot: 'screenshot',\n evaluate: 'evaluate',\n click: 'click',\n type: 'type',\n fill: 'fill_form',\n select: 'select_option',\n press: 'press_key',\n scroll: 'scroll',\n hover: 'hover',\n drag: 'drag',\n tabs: 'tabs',\n wait: 'wait_for',\n console: 'console_messages',\n network: 'network_requests',\n};\n\n/* ── Help ────────────────────────────────────────────────────────────── */\n\nfunction printHelp() {\n console.log(\n [\n 'Usage: canary session <subcommand> [options]',\n '',\n 'Session lifecycle:',\n ' start [options] Start a new browser session',\n ' list List active sessions',\n ' status [<id>] Show session details',\n ' stop [<id>] Stop a session (or --all)',\n ' load-credential Load a credential into a running session',\n '',\n 'Browser actions:',\n ' navigate --url <url> Navigate to URL',\n ' back Navigate back',\n ' snapshot [--search <text>] [--mode tree|screenshot|both]',\n ' screenshot [--full-page] [--label <text>]',\n ' evaluate --js <expression> Evaluate JavaScript',\n ' click --ref <ref> [--element <desc>]',\n ' click --x <n> --y <n> [--element <desc>]',\n ' type --ref <ref> --text <text> [--submit]',\n ' fill --fields \\'[{\"ref\":\"e1\",\"value\":\"test\"}]\\'',\n ' select --ref <ref> --value <val> [--element <desc>]',\n ' press --key <key> Press keyboard key',\n ' scroll --direction <dir> [--amount <n>]',\n ' hover --ref <ref> [--element <desc>]',\n ' tabs [--action list|new|close|select] [--index <n>]',\n ' wait --text <text> [--timeout <ms>]',\n ' console [--only-errors] Show console messages',\n ' network Show network requests',\n '',\n 'Start options:',\n ' --credential [<name|id>] Use a credential (interactive if no value)',\n ' --storage-state <path> Use a local storage state file',\n ' --headless Run browser without visible UI',\n ' --url <url> Navigate to URL after launch',\n ' --name <label> Name the session',\n '',\n 'Load-credential options:',\n ' --credential [<name|id>] Use a credential (interactive if no value)',\n ' --storage-state <path> Use a local storage state file',\n '',\n 'Common options:',\n ' --session <id|name> Target a specific session (when multiple)',\n ' --json Output raw JSON',\n ' --env <env> Environment for credential resolution',\n ' -h, --help Show this help',\n ].join('\\n')\n );\n}\n\n/* ── Main entry ──────────────────────────────────────────────────────── */\n\nexport async function runSession(argv: string[]) {\n const [subcommand, ...rest] = argv;\n\n if (!subcommand || subcommand === 'help' || hasFlag(argv, '-h', '--help')) {\n printHelp();\n return;\n }\n\n // Internal: daemon process mode\n if (subcommand === 'daemon') {\n await runDaemon();\n return;\n }\n\n switch (subcommand) {\n case 'start':\n await handleStart(rest);\n break;\n case 'list':\n await handleList(rest);\n break;\n case 'status':\n await handleStatus(rest);\n break;\n case 'stop':\n await handleStop(rest);\n break;\n case 'load-credential':\n await handleLoadCredential(rest);\n break;\n default: {\n // Check for tool alias\n const toolName = TOOL_ALIASES[subcommand];\n if (toolName) {\n await handleToolCommand(toolName, rest);\n } else {\n console.error(`Unknown session subcommand: ${subcommand}`);\n printHelp();\n process.exitCode = 1;\n }\n }\n }\n}\n","/**\n * Credential resolution and storage state download for session commands.\n *\n * Uses shared selectCredential from cli-helpers for interactive selection,\n * then downloads the storage state for the selected credential.\n *\n * @module\n */\n\nimport { resolveConfig } from '../auth.js';\nimport { selectCredential, downloadStorageState } from '../cli-helpers.js';\n\n/**\n * Resolve a credential and download its storage state.\n *\n * @param argv - CLI arguments (for --env, --token, --api-url resolution)\n * @param credentialArg - Credential name, ID, or undefined for interactive selection\n * @returns Object with storageStatePath and credentialName, or null if no match\n */\nexport async function resolveCredential(\n argv: string[],\n credentialArg: string | undefined\n): Promise<{ storageStatePath: string | undefined; credentialName: string } | null> {\n const { apiUrl, token } = await resolveConfig(argv);\n\n const credential = await selectCredential(apiUrl, token, credentialArg);\n if (!credential) {\n return null;\n }\n\n console.log(`Fetching credential \"${credential.name}\"...`);\n\n let storageStatePath: string | undefined;\n if (credential.storageStateS3Key) {\n storageStatePath = await downloadStorageState({\n apiUrl,\n token,\n propertyId: credential.propertyId,\n credentialId: credential.id,\n prefix: 'session-ss',\n });\n\n if (storageStatePath) {\n console.log('Downloaded storage state.');\n } else {\n console.warn('Warning: Could not download storage state. Starting without auth.');\n }\n } else {\n console.log('Credential has no storage state. Starting without auth.');\n }\n\n return {\n storageStatePath,\n credentialName: credential.name,\n };\n}\n","/**\n * Session daemon — lightweight HTTP server that owns all browser sessions.\n *\n * Runs on localhost with an OS-assigned port. Both CLI and MCP delegate\n * browser lifecycle and tool execution to this daemon.\n *\n * @module\n */\n\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport os from 'node:os';\nimport {\n PlaywrightClient,\n BrowserToolExecutor,\n consoleLogger,\n dispatchBrowserTool,\n} from '@chatsdet/browser-core';\nimport type {\n DaemonState,\n SessionInfo,\n CreateSessionRequest,\n SwapContextRequest,\n DaemonResponse,\n ToolResponse,\n} from './types.js';\n\nconst PIDFILE_DIR = path.join(os.homedir(), '.config', 'canary-cli');\nconst PIDFILE_PATH = path.join(PIDFILE_DIR, 'daemon.json');\nconst IDLE_TIMEOUT_MS = 60_000;\n\ninterface ManagedSession {\n id: string;\n name: string;\n mode: 'headed' | 'headless';\n credentialName?: string;\n client: PlaywrightClient;\n executor: BrowserToolExecutor;\n startedAt: string;\n}\n\nconst sessions = new Map<string, ManagedSession>();\nlet nextId = 1;\nlet idleTimer: ReturnType<typeof setTimeout> | null = null;\n\nfunction resetIdleTimer() {\n if (idleTimer) clearTimeout(idleTimer);\n if (sessions.size === 0) {\n idleTimer = setTimeout(async () => {\n if (sessions.size === 0) {\n await removePidfile();\n process.exit(0);\n }\n }, IDLE_TIMEOUT_MS);\n }\n}\n\nfunction generateId(): string {\n return `s${nextId++}`;\n}\n\nasync function getSessionUrl(session: ManagedSession): Promise<string | undefined> {\n try {\n return await session.client.getCurrentUrl();\n } catch {\n return undefined;\n }\n}\n\nasync function toSessionInfo(s: ManagedSession): Promise<SessionInfo> {\n return {\n id: s.id,\n name: s.name,\n mode: s.mode,\n credentialName: s.credentialName,\n url: await getSessionUrl(s),\n startedAt: s.startedAt,\n };\n}\n\n/* ── Request parsing helpers ─────────────────────────────────────────── */\n\nasync function readBody(req: IncomingMessage): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks).toString('utf-8');\n}\n\nfunction parseJson(body: string): Record<string, unknown> {\n if (!body.trim()) return {};\n try {\n return JSON.parse(body);\n } catch {\n return {};\n }\n}\n\nfunction json(res: ServerResponse, status: number, data: unknown) {\n const body = JSON.stringify(data);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n });\n res.end(body);\n}\n\nfunction parseRoute(url: string): { path: string[]; query: Record<string, string> } {\n const [pathname] = url.split('?');\n const segments = pathname.split('/').filter(Boolean);\n return { path: segments, query: {} };\n}\n\n/* ── Route handlers ──────────────────────────────────────────────────── */\n\nasync function handleHealth(res: ServerResponse) {\n json(res, 200, { ok: true, sessions: sessions.size });\n}\n\nasync function handleCreateSession(body: string, res: ServerResponse) {\n const params = parseJson(body) as CreateSessionRequest;\n const id = generateId();\n const name = params.name ?? id;\n const mode = params.headless ? 'headless' : 'headed';\n\n try {\n const client = new PlaywrightClient({ logger: consoleLogger });\n await client.connect({\n browserMode: mode,\n storageStatePath: params.storageStatePath,\n });\n\n const executor = new BrowserToolExecutor(client, {\n autoSnapshotAfterAction: true,\n includeScreenshotWithSnapshot: true,\n clickValidation: true,\n logger: consoleLogger,\n });\n\n const session: ManagedSession = {\n id,\n name,\n mode,\n credentialName: params.credentialName,\n client,\n executor,\n startedAt: new Date().toISOString(),\n };\n\n sessions.set(id, session);\n if (idleTimer) {\n clearTimeout(idleTimer);\n idleTimer = null;\n }\n\n const result: DaemonResponse<SessionInfo> = {\n ok: true,\n data: await toSessionInfo(session),\n };\n json(res, 201, result);\n\n // Navigate after responding — navigation can take 30+ seconds and the\n // CLI intentionally does not wait so callers can issue commands in parallel.\n if (params.url) {\n client.navigate(params.url).catch((err) => {\n consoleLogger.warn(`Initial navigation to ${params.url} failed: ${err}`);\n });\n }\n } catch (err) {\n const result: DaemonResponse = {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n };\n json(res, 500, result);\n }\n}\n\nasync function handleListSessions(res: ServerResponse) {\n const list = await Promise.all(Array.from(sessions.values()).map(toSessionInfo));\n json(res, 200, { ok: true, data: list } satisfies DaemonResponse<SessionInfo[]>);\n}\n\nasync function handleGetSession(sessionId: string, res: ServerResponse) {\n const session = sessions.get(sessionId);\n if (!session) {\n json(res, 404, { ok: false, error: `Session \"${sessionId}\" not found` } satisfies DaemonResponse);\n return;\n }\n json(res, 200, { ok: true, data: await toSessionInfo(session) } satisfies DaemonResponse<SessionInfo>);\n}\n\nasync function handleDeleteSession(sessionId: string, res: ServerResponse) {\n const session = sessions.get(sessionId);\n if (!session) {\n json(res, 404, { ok: false, error: `Session \"${sessionId}\" not found` } satisfies DaemonResponse);\n return;\n }\n\n try {\n await session.client.disconnect();\n } catch {\n // Best effort\n }\n sessions.delete(sessionId);\n json(res, 200, { ok: true } satisfies DaemonResponse);\n resetIdleTimer();\n}\n\nasync function handleDeleteAllSessions(res: ServerResponse) {\n for (const session of sessions.values()) {\n try {\n await session.client.disconnect();\n } catch {\n // Best effort\n }\n }\n sessions.clear();\n json(res, 200, { ok: true } satisfies DaemonResponse);\n resetIdleTimer();\n}\n\nasync function handleSwapContext(sessionId: string, body: string, res: ServerResponse) {\n const session = sessions.get(sessionId);\n if (!session) {\n json(res, 404, { ok: false, error: `Session \"${sessionId}\" not found` } satisfies DaemonResponse);\n return;\n }\n\n const params = parseJson(body) as SwapContextRequest;\n\n try {\n await session.client.swapContext({\n storageStatePath: params.storageStatePath,\n });\n\n if (params.credentialName) {\n session.credentialName = params.credentialName;\n }\n\n json(res, 200, { ok: true, data: await toSessionInfo(session) } satisfies DaemonResponse<SessionInfo>);\n } catch (err) {\n json(res, 500, {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n } satisfies DaemonResponse);\n }\n}\n\nasync function handleGetStorageState(sessionId: string, res: ServerResponse) {\n const session = sessions.get(sessionId);\n if (!session) {\n json(res, 404, { ok: false, error: `Session \"${sessionId}\" not found` } satisfies DaemonResponse);\n return;\n }\n\n try {\n const storageState = await session.client.getStorageState();\n json(res, 200, { ok: true, data: storageState } satisfies DaemonResponse);\n } catch (err) {\n json(res, 500, {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n } satisfies DaemonResponse);\n }\n}\n\nasync function handleToolCall(sessionId: string, toolName: string, body: string, res: ServerResponse) {\n const session = sessions.get(sessionId);\n if (!session) {\n json(res, 404, { ok: false, error: `Session \"${sessionId}\" not found` } satisfies DaemonResponse);\n return;\n }\n\n const args = parseJson(body);\n const fullToolName = toolName.startsWith('browser_') ? toolName : `browser_${toolName}`;\n\n try {\n const result = await dispatchBrowserTool(session.executor, fullToolName, args);\n\n if (typeof result === 'string') {\n json(res, 200, { ok: true, text: result } satisfies ToolResponse);\n } else {\n json(res, 200, {\n ok: true,\n text: result.text,\n images: result.images,\n } satisfies ToolResponse);\n }\n } catch (err) {\n json(res, 500, {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n } satisfies ToolResponse);\n }\n}\n\n/* ── Server routing ──────────────────────────────────────────────────── */\n\nasync function handleRequest(req: IncomingMessage, res: ServerResponse) {\n const method = req.method ?? 'GET';\n const { path: segments } = parseRoute(req.url ?? '/');\n const body = method === 'POST' || method === 'DELETE' ? await readBody(req) : '';\n\n try {\n // GET /health\n if (method === 'GET' && segments[0] === 'health') {\n await handleHealth(res);\n return;\n }\n\n // POST /sessions\n if (method === 'POST' && segments[0] === 'sessions' && segments.length === 1) {\n await handleCreateSession(body, res);\n return;\n }\n\n // GET /sessions\n if (method === 'GET' && segments[0] === 'sessions' && segments.length === 1) {\n await handleListSessions(res);\n return;\n }\n\n // GET /sessions/:id\n if (method === 'GET' && segments[0] === 'sessions' && segments.length === 2) {\n await handleGetSession(segments[1], res);\n return;\n }\n\n // DELETE /sessions (all)\n if (method === 'DELETE' && segments[0] === 'sessions' && segments.length === 1) {\n await handleDeleteAllSessions(res);\n return;\n }\n\n // DELETE /sessions/:id\n if (method === 'DELETE' && segments[0] === 'sessions' && segments.length === 2) {\n await handleDeleteSession(segments[1], res);\n return;\n }\n\n // POST /sessions/:id/swap-context\n if (method === 'POST' && segments[0] === 'sessions' && segments[2] === 'swap-context' && segments.length === 3) {\n await handleSwapContext(segments[1], body, res);\n return;\n }\n\n // GET /sessions/:id/storage-state\n if (method === 'GET' && segments[0] === 'sessions' && segments[2] === 'storage-state' && segments.length === 3) {\n await handleGetStorageState(segments[1], res);\n return;\n }\n\n // POST /sessions/:id/tools/:toolName\n if (method === 'POST' && segments[0] === 'sessions' && segments[2] === 'tools' && segments.length === 4) {\n await handleToolCall(segments[1], segments[3], body, res);\n return;\n }\n\n json(res, 404, { ok: false, error: 'Not found' });\n } catch (err) {\n json(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n}\n\n/* ── Pidfile management ──────────────────────────────────────────────── */\n\nasync function writePidfile(port: number) {\n await fs.mkdir(PIDFILE_DIR, { recursive: true, mode: 0o700 });\n const state: DaemonState = {\n pid: process.pid,\n port,\n startedAt: new Date().toISOString(),\n };\n await fs.writeFile(PIDFILE_PATH, JSON.stringify(state, null, 2), { mode: 0o600 });\n}\n\nasync function removePidfile() {\n try {\n await fs.unlink(PIDFILE_PATH);\n } catch {\n // Already removed\n }\n}\n\n/* ── Cleanup ─────────────────────────────────────────────────────────── */\n\nasync function cleanup() {\n for (const session of sessions.values()) {\n try {\n await session.client.disconnect();\n } catch {\n // Best effort\n }\n }\n sessions.clear();\n await removePidfile();\n}\n\n/* ── Main entry ──────────────────────────────────────────────────────── */\n\nexport async function startDaemon(): Promise<{ port: number }> {\n const server = createServer(handleRequest);\n // Disable keep-alive to prevent \"other side closed\" socket errors when\n // the client reuses a connection the server is about to close.\n server.keepAliveTimeout = 0;\n\n return new Promise((resolve, reject) => {\n server.listen(0, '127.0.0.1', async () => {\n const addr = server.address();\n if (!addr || typeof addr === 'string') {\n reject(new Error('Failed to get server address'));\n return;\n }\n\n const port = addr.port;\n await writePidfile(port);\n resetIdleTimer();\n\n // Graceful shutdown\n process.on('SIGINT', async () => { await cleanup(); process.exit(0); });\n process.on('SIGTERM', async () => { await cleanup(); process.exit(0); });\n process.on('exit', () => { removePidfile().catch(() => {}); });\n\n resolve({ port });\n });\n\n server.on('error', reject);\n });\n}\n\n/** CLI entry point: `canary session daemon` (internal, spawned by daemon-client) */\nexport async function runDaemon() {\n const { port } = await startDaemon();\n // Write the port to stdout so the spawner can read it\n process.stdout.write(`DAEMON_READY:${port}\\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,OAAOA,cAAa;;;ACUpB,eAAsB,kBACpB,MACA,eACkF;AAClF,QAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,cAAc,IAAI;AAElD,QAAM,aAAa,MAAM,iBAAiB,QAAQ,OAAO,aAAa;AACtE,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAEA,UAAQ,IAAI,wBAAwB,WAAW,IAAI,MAAM;AAEzD,MAAI;AACJ,MAAI,WAAW,mBAAmB;AAChC,uBAAmB,MAAM,qBAAqB;AAAA,MAC5C;AAAA,MACA;AAAA,MACA,YAAY,WAAW;AAAA,MACvB,cAAc,WAAW;AAAA,MACzB,QAAQ;AAAA,IACV,CAAC;AAED,QAAI,kBAAkB;AACpB,cAAQ,IAAI,2BAA2B;AAAA,IACzC,OAAO;AACL,cAAQ,KAAK,mEAAmE;AAAA,IAClF;AAAA,EACF,OAAO;AACL,YAAQ,IAAI,yDAAyD;AAAA,EACvE;AAEA,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,WAAW;AAAA,EAC7B;AACF;;;AC9CA,SAAS,oBAA+D;AACxE,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AAgBf,IAAM,cAAc,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,YAAY;AACnE,IAAM,eAAe,KAAK,KAAK,aAAa,aAAa;AACzD,IAAM,kBAAkB;AAYxB,IAAM,WAAW,oBAAI,IAA4B;AACjD,IAAI,SAAS;AACb,IAAI,YAAkD;AAEtD,SAAS,iBAAiB;AACxB,MAAI,UAAW,cAAa,SAAS;AACrC,MAAI,SAAS,SAAS,GAAG;AACvB,gBAAY,WAAW,YAAY;AACjC,UAAI,SAAS,SAAS,GAAG;AACvB,cAAM,cAAc;AACpB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF,GAAG,eAAe;AAAA,EACpB;AACF;AAEA,SAAS,aAAqB;AAC5B,SAAO,IAAI,QAAQ;AACrB;AAEA,eAAe,cAAc,SAAsD;AACjF,MAAI;AACF,WAAO,MAAM,QAAQ,OAAO,cAAc;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,cAAc,GAAyC;AACpE,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,MAAM,EAAE;AAAA,IACR,MAAM,EAAE;AAAA,IACR,gBAAgB,EAAE;AAAA,IAClB,KAAK,MAAM,cAAc,CAAC;AAAA,IAC1B,WAAW,EAAE;AAAA,EACf;AACF;AAIA,eAAe,SAAS,KAAuC;AAC7D,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,KAAK;AAC7B,WAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,KAAK;AAAA,EACpE;AACA,SAAO,OAAO,OAAO,MAAM,EAAE,SAAS,OAAO;AAC/C;AAEA,SAAS,UAAU,MAAuC;AACxD,MAAI,CAAC,KAAK,KAAK,EAAG,QAAO,CAAC;AAC1B,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,SAAS,KAAK,KAAqB,QAAgB,MAAe;AAChE,QAAM,OAAO,KAAK,UAAU,IAAI;AAChC,MAAI,UAAU,QAAQ;AAAA,IACpB,gBAAgB;AAAA,IAChB,kBAAkB,OAAO,WAAW,IAAI;AAAA,EAC1C,CAAC;AACD,MAAI,IAAI,IAAI;AACd;AAEA,SAAS,WAAW,KAAgE;AAClF,QAAM,CAAC,QAAQ,IAAI,IAAI,MAAM,GAAG;AAChC,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,SAAO,EAAE,MAAM,UAAU,OAAO,CAAC,EAAE;AACrC;AAIA,eAAe,aAAa,KAAqB;AAC/C,OAAK,KAAK,KAAK,EAAE,IAAI,MAAM,UAAU,SAAS,KAAK,CAAC;AACtD;AAEA,eAAe,oBAAoB,MAAc,KAAqB;AACpE,QAAM,SAAS,UAAU,IAAI;AAC7B,QAAM,KAAK,WAAW;AACtB,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,OAAO,OAAO,WAAW,aAAa;AAE5C,MAAI;AACF,UAAM,SAAS,IAAI,iBAAiB,EAAE,QAAQ,cAAc,CAAC;AAC7D,UAAM,OAAO,QAAQ;AAAA,MACnB,aAAa;AAAA,MACb,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AAED,UAAM,WAAW,IAAI,oBAAoB,QAAQ;AAAA,MAC/C,yBAAyB;AAAA,MACzB,+BAA+B;AAAA,MAC/B,iBAAiB;AAAA,MACjB,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,UAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAEA,aAAS,IAAI,IAAI,OAAO;AACxB,QAAI,WAAW;AACb,mBAAa,SAAS;AACtB,kBAAY;AAAA,IACd;AAEA,UAAM,SAAsC;AAAA,MAC1C,IAAI;AAAA,MACJ,MAAM,MAAM,cAAc,OAAO;AAAA,IACnC;AACA,SAAK,KAAK,KAAK,MAAM;AAIrB,QAAI,OAAO,KAAK;AACd,aAAO,SAAS,OAAO,GAAG,EAAE,MAAM,CAAC,QAAQ;AACzC,sBAAc,KAAK,yBAAyB,OAAO,GAAG,YAAY,GAAG,EAAE;AAAA,MACzE,CAAC;AAAA,IACH;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,SAAyB;AAAA,MAC7B,IAAI;AAAA,MACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD;AACA,SAAK,KAAK,KAAK,MAAM;AAAA,EACvB;AACF;AAEA,eAAe,mBAAmB,KAAqB;AACrD,QAAM,OAAO,MAAM,QAAQ,IAAI,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE,IAAI,aAAa,CAAC;AAC/E,OAAK,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,KAAK,CAAyC;AACjF;AAEA,eAAe,iBAAiB,WAAmB,KAAqB;AACtE,QAAM,UAAU,SAAS,IAAI,SAAS;AACtC,MAAI,CAAC,SAAS;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,SAAS,cAAc,CAA0B;AAChG;AAAA,EACF;AACA,OAAK,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,MAAM,cAAc,OAAO,EAAE,CAAuC;AACvG;AAEA,eAAe,oBAAoB,WAAmB,KAAqB;AACzE,QAAM,UAAU,SAAS,IAAI,SAAS;AACtC,MAAI,CAAC,SAAS;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,SAAS,cAAc,CAA0B;AAChG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,QAAQ,OAAO,WAAW;AAAA,EAClC,QAAQ;AAAA,EAER;AACA,WAAS,OAAO,SAAS;AACzB,OAAK,KAAK,KAAK,EAAE,IAAI,KAAK,CAA0B;AACpD,iBAAe;AACjB;AAEA,eAAe,wBAAwB,KAAqB;AAC1D,aAAW,WAAW,SAAS,OAAO,GAAG;AACvC,QAAI;AACF,YAAM,QAAQ,OAAO,WAAW;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,WAAS,MAAM;AACf,OAAK,KAAK,KAAK,EAAE,IAAI,KAAK,CAA0B;AACpD,iBAAe;AACjB;AAEA,eAAe,kBAAkB,WAAmB,MAAc,KAAqB;AACrF,QAAM,UAAU,SAAS,IAAI,SAAS;AACtC,MAAI,CAAC,SAAS;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,SAAS,cAAc,CAA0B;AAChG;AAAA,EACF;AAEA,QAAM,SAAS,UAAU,IAAI;AAE7B,MAAI;AACF,UAAM,QAAQ,OAAO,YAAY;AAAA,MAC/B,kBAAkB,OAAO;AAAA,IAC3B,CAAC;AAED,QAAI,OAAO,gBAAgB;AACzB,cAAQ,iBAAiB,OAAO;AAAA,IAClC;AAEA,SAAK,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,MAAM,cAAc,OAAO,EAAE,CAAuC;AAAA,EACvG,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK;AAAA,MACb,IAAI;AAAA,MACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAA0B;AAAA,EAC5B;AACF;AAEA,eAAe,sBAAsB,WAAmB,KAAqB;AAC3E,QAAM,UAAU,SAAS,IAAI,SAAS;AACtC,MAAI,CAAC,SAAS;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,SAAS,cAAc,CAA0B;AAChG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,eAAe,MAAM,QAAQ,OAAO,gBAAgB;AAC1D,SAAK,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,aAAa,CAA0B;AAAA,EAC1E,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK;AAAA,MACb,IAAI;AAAA,MACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAA0B;AAAA,EAC5B;AACF;AAEA,eAAe,eAAe,WAAmB,UAAkB,MAAc,KAAqB;AACpG,QAAM,UAAU,SAAS,IAAI,SAAS;AACtC,MAAI,CAAC,SAAS;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,SAAS,cAAc,CAA0B;AAChG;AAAA,EACF;AAEA,QAAM,OAAO,UAAU,IAAI;AAC3B,QAAM,eAAe,SAAS,WAAW,UAAU,IAAI,WAAW,WAAW,QAAQ;AAErF,MAAI;AACF,UAAM,SAAS,MAAM,oBAAoB,QAAQ,UAAU,cAAc,IAAI;AAE7E,QAAI,OAAO,WAAW,UAAU;AAC9B,WAAK,KAAK,KAAK,EAAE,IAAI,MAAM,MAAM,OAAO,CAAwB;AAAA,IAClE,OAAO;AACL,WAAK,KAAK,KAAK;AAAA,QACb,IAAI;AAAA,QACJ,MAAM,OAAO;AAAA,QACb,QAAQ,OAAO;AAAA,MACjB,CAAwB;AAAA,IAC1B;AAAA,EACF,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK;AAAA,MACb,IAAI;AAAA,MACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACxD,CAAwB;AAAA,EAC1B;AACF;AAIA,eAAe,cAAc,KAAsB,KAAqB;AACtE,QAAM,SAAS,IAAI,UAAU;AAC7B,QAAM,EAAE,MAAM,SAAS,IAAI,WAAW,IAAI,OAAO,GAAG;AACpD,QAAM,OAAO,WAAW,UAAU,WAAW,WAAW,MAAM,SAAS,GAAG,IAAI;AAE9E,MAAI;AAEF,QAAI,WAAW,SAAS,SAAS,CAAC,MAAM,UAAU;AAChD,YAAM,aAAa,GAAG;AACtB;AAAA,IACF;AAGA,QAAI,WAAW,UAAU,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AAC5E,YAAM,oBAAoB,MAAM,GAAG;AACnC;AAAA,IACF;AAGA,QAAI,WAAW,SAAS,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AAC3E,YAAM,mBAAmB,GAAG;AAC5B;AAAA,IACF;AAGA,QAAI,WAAW,SAAS,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AAC3E,YAAM,iBAAiB,SAAS,CAAC,GAAG,GAAG;AACvC;AAAA,IACF;AAGA,QAAI,WAAW,YAAY,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AAC9E,YAAM,wBAAwB,GAAG;AACjC;AAAA,IACF;AAGA,QAAI,WAAW,YAAY,SAAS,CAAC,MAAM,cAAc,SAAS,WAAW,GAAG;AAC9E,YAAM,oBAAoB,SAAS,CAAC,GAAG,GAAG;AAC1C;AAAA,IACF;AAGA,QAAI,WAAW,UAAU,SAAS,CAAC,MAAM,cAAc,SAAS,CAAC,MAAM,kBAAkB,SAAS,WAAW,GAAG;AAC9G,YAAM,kBAAkB,SAAS,CAAC,GAAG,MAAM,GAAG;AAC9C;AAAA,IACF;AAGA,QAAI,WAAW,SAAS,SAAS,CAAC,MAAM,cAAc,SAAS,CAAC,MAAM,mBAAmB,SAAS,WAAW,GAAG;AAC9G,YAAM,sBAAsB,SAAS,CAAC,GAAG,GAAG;AAC5C;AAAA,IACF;AAGA,QAAI,WAAW,UAAU,SAAS,CAAC,MAAM,cAAc,SAAS,CAAC,MAAM,WAAW,SAAS,WAAW,GAAG;AACvG,YAAM,eAAe,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,MAAM,GAAG;AACxD;AAAA,IACF;AAEA,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,YAAY,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,SAAK,KAAK,KAAK,EAAE,IAAI,OAAO,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,CAAC;AAAA,EACvF;AACF;AAIA,eAAe,aAAa,MAAc;AACxC,QAAM,GAAG,MAAM,aAAa,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AAC5D,QAAM,QAAqB;AAAA,IACzB,KAAK,QAAQ;AAAA,IACb;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACA,QAAM,GAAG,UAAU,cAAc,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,IAAM,CAAC;AAClF;AAEA,eAAe,gBAAgB;AAC7B,MAAI;AACF,UAAM,GAAG,OAAO,YAAY;AAAA,EAC9B,QAAQ;AAAA,EAER;AACF;AAIA,eAAe,UAAU;AACvB,aAAW,WAAW,SAAS,OAAO,GAAG;AACvC,QAAI;AACF,YAAM,QAAQ,OAAO,WAAW;AAAA,IAClC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,WAAS,MAAM;AACf,QAAM,cAAc;AACtB;AAIA,eAAsB,cAAyC;AAC7D,QAAM,SAAS,aAAa,aAAa;AAGzC,SAAO,mBAAmB;AAE1B,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAO,OAAO,GAAG,aAAa,YAAY;AACxC,YAAM,OAAO,OAAO,QAAQ;AAC5B,UAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,eAAO,IAAI,MAAM,8BAA8B,CAAC;AAChD;AAAA,MACF;AAEA,YAAM,OAAO,KAAK;AAClB,YAAM,aAAa,IAAI;AACvB,qBAAe;AAGf,cAAQ,GAAG,UAAU,YAAY;AAAE,cAAM,QAAQ;AAAG,gBAAQ,KAAK,CAAC;AAAA,MAAG,CAAC;AACtE,cAAQ,GAAG,WAAW,YAAY;AAAE,cAAM,QAAQ;AAAG,gBAAQ,KAAK,CAAC;AAAA,MAAG,CAAC;AACvE,cAAQ,GAAG,QAAQ,MAAM;AAAE,sBAAc,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAAG,CAAC;AAE7D,cAAQ,EAAE,KAAK,CAAC;AAAA,IAClB,CAAC;AAED,WAAO,GAAG,SAAS,MAAM;AAAA,EAC3B,CAAC;AACH;AAGA,eAAsB,YAAY;AAChC,QAAM,EAAE,KAAK,IAAI,MAAM,YAAY;AAEnC,UAAQ,OAAO,MAAM,gBAAgB,IAAI;AAAA,CAAI;AAC/C;;;AF1ZA,SAAS,aAAa,WAA2B;AAC/C,QAAM,UAAU,KAAK,OAAO,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,GAAI;AAC9E,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AACnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,OAAO,UAAU;AACvB,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO,KAAK,IAAI;AAC5C,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,QAAM,OAAO,UAAU;AACvB,SAAO,GAAG,KAAK,KAAK,IAAI;AAC1B;AAEA,SAAS,kBAAkBC,WAAyB;AAClD,MAAIA,UAAS,WAAW,GAAG;AACzB,YAAQ,IAAI,qBAAqB;AACjC;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,MAAM,QAAQ,QAAQ,cAAc,OAAO,QAAQ;AACnE,QAAM,OAAOA,UAAS,IAAI,CAAC,MAAM;AAAA,IAC/B,EAAE;AAAA,IACF,EAAE;AAAA,IACF,EAAE;AAAA,IACF,EAAE,kBAAkB;AAAA,IACpB,EAAE,OAAO;AAAA,IACT,aAAa,EAAE,SAAS;AAAA,EAC1B,CAAC;AAGD,QAAM,SAAS,OAAO;AAAA,IAAI,CAAC,GAAG,MAC5B,KAAK,IAAI,EAAE,QAAQ,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC;AAAA,EACpD;AAEA,QAAM,MAAM,CAAC,KAAa,UAAkB,IAAI,OAAO,KAAK;AAC5D,UAAQ,IAAI,OAAO,IAAI,CAAC,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAC9D,aAAW,OAAO,MAAM;AACtB,YAAQ,IAAI,IAAI,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC;AAAA,EACnE;AACF;AAEA,SAAS,gBAAgB,QAAsB,UAAmB;AAChE,MAAI,UAAU;AACZ,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,IAAI;AACd,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,IAAAC,SAAQ,WAAW;AACnB;AAAA,EACF;AAEA,MAAI,OAAO,MAAM;AACf,YAAQ,IAAI,OAAO,IAAI;AAAA,EACzB;AAEA,MAAI,OAAO,QAAQ,QAAQ;AACzB,YAAQ,IAAI,IAAI,OAAO,OAAO,MAAM,qBAAqB;AAAA,EAC3D;AACF;AAIA,eAAe,YAAY,MAAgB;AACzC,QAAM,WAAW,QAAQ,MAAM,YAAY;AAC3C,QAAM,OAAO,YAAY,MAAM,QAAQ;AACvC,QAAM,MAAM,YAAY,MAAM,OAAO;AACrC,QAAM,mBAAmB,YAAY,MAAM,iBAAiB;AAC5D,QAAM,WAAW,QAAQ,MAAM,QAAQ;AAGvC,QAAM,gBAAgB,KAAK,QAAQ,cAAc;AACjD,MAAI,eAAwF;AAE5F,MAAI,kBAAkB,IAAI;AAExB,UAAM,UAAU,KAAK,gBAAgB,CAAC;AACtC,UAAM,gBAAgB,WAAW,CAAC,QAAQ,WAAW,IAAI,IAAI,UAAU;AACvE,mBAAe,MAAM,kBAAkB,MAAM,aAAa;AAC1D,QAAI,CAAC,cAAc;AACjB,MAAAA,SAAQ,WAAW;AACnB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,cAAc;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB,cAAc,oBAAoB;AAAA,IACpD,gBAAgB,cAAc;AAAA,EAChC,CAAC;AAED,MAAI,UAAU;AACZ,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,IAAI;AACd,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,IAAAA,SAAQ,WAAW;AACnB;AAAA,EACF;AAEA,QAAM,UAAU,OAAO;AACvB,UAAQ,IAAI,YAAY,QAAQ,EAAE,cAAc,QAAQ,IAAI,GAAG;AAC/D,MAAI,QAAQ,gBAAgB;AAC1B,YAAQ,IAAI,iBAAiB,QAAQ,cAAc,EAAE;AAAA,EACvD;AAEA,QAAM,aAAa,OAAO,QAAQ;AAClC,MAAI,YAAY;AACd,YAAQ,IAAI,UAAU,UAAU,EAAE;AAAA,EACpC;AACF;AAIA,eAAe,WAAW,MAAgB;AACxC,QAAM,WAAW,QAAQ,MAAM,QAAQ;AACvC,QAAM,SAAS,MAAM,aAAa;AAElC,MAAI,UAAU;AACZ,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,IAAI;AACd,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,IAAAA,SAAQ,WAAW;AACnB;AAAA,EACF;AAEA,oBAAkB,OAAO,QAAQ,CAAC,CAAC;AACrC;AAIA,eAAe,aAAa,MAAgB;AAC1C,QAAM,YACJ,YAAY,MAAM,WAAW,MAC5B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,IAAI,IAAI,KAAK,CAAC,IAAI;AACpD,QAAM,WAAW,QAAQ,MAAM,QAAQ;AAEvC,MAAI;AACF,UAAM,SAAS,MAAM,qBAAqB,SAAS;AACnD,UAAM,SAAS,MAAM,WAAW,OAAO,EAAE;AAEzC,QAAI,UAAU;AACZ,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,IACF;AAEA,QAAI,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;AAC9B,cAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,MAAAA,SAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,IAAI,OAAO;AACjB,YAAQ,IAAI,YAAY,EAAE,EAAE,KAAK,EAAE,IAAI,GAAG;AAC1C,YAAQ,IAAI,WAAW,EAAE,IAAI,EAAE;AAC/B,YAAQ,IAAI,iBAAiB,EAAE,kBAAkB,QAAQ,EAAE;AAC3D,YAAQ,IAAI,UAAU,EAAE,OAAO,QAAQ,EAAE;AACzC,YAAQ,IAAI,aAAa,aAAa,EAAE,SAAS,CAAC,EAAE;AACpD,YAAQ,IAAI,cAAc,EAAE,SAAS,EAAE;AAAA,EACzC,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,IAAAA,SAAQ,WAAW;AAAA,EACrB;AACF;AAIA,eAAe,WAAW,MAAgB;AACxC,QAAM,WAAW,QAAQ,MAAM,QAAQ;AAEvC,MAAI,QAAQ,MAAM,OAAO,GAAG;AAC1B,UAAM,SAAS,MAAM,kBAAkB;AACvC,QAAI,UAAU;AACZ,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,WAAW,OAAO,IAAI;AACpB,cAAQ,IAAI,uBAAuB;AAAA,IACrC,OAAO;AACL,cAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,MAAAA,SAAQ,WAAW;AAAA,IACrB;AACA;AAAA,EACF;AAEA,QAAM,YACJ,YAAY,MAAM,WAAW,MAC5B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,IAAI,IAAI,KAAK,CAAC,IAAI;AAEpD,MAAI;AACF,UAAM,SAAS,MAAM,qBAAqB,SAAS;AACnD,UAAM,SAAS,MAAM,cAAc,OAAO,EAAE;AAE5C,QAAI,UAAU;AACZ,cAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7C,WAAW,OAAO,IAAI;AACpB,cAAQ,IAAI,YAAY,OAAO,EAAE,YAAY;AAAA,IAC/C,OAAO;AACL,cAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,MAAAA,SAAQ,WAAW;AAAA,IACrB;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,IAAAA,SAAQ,WAAW;AAAA,EACrB;AACF;AAIA,eAAe,qBAAqB,MAAgB;AAClD,QAAM,WAAW,QAAQ,MAAM,QAAQ;AACvC,QAAM,YACJ,YAAY,MAAM,WAAW,MAC5B,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,WAAW,IAAI,IAAI,KAAK,CAAC,IAAI;AAGpD,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,qBAAqB,SAAS;AAAA,EAC/C,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,IAAAA,SAAQ,WAAW;AACnB;AAAA,EACF;AAGA,QAAM,gBAAgB,KAAK,QAAQ,cAAc;AACjD,QAAM,UAAU,kBAAkB,KAAK,KAAK,gBAAgB,CAAC,IAAI;AACjE,QAAM,gBAAgB,WAAW,CAAC,QAAQ,WAAW,IAAI,IAAI,UAAU;AAGvE,QAAM,mBAAmB,YAAY,MAAM,iBAAiB;AAE5D,MAAI;AACJ,MAAI;AAEJ,MAAI,kBAAkB;AACpB,+BAA2B;AAC3B,qBAAiB;AAAA,EACnB,OAAO;AAEL,UAAM,eAAe,MAAM,kBAAkB,MAAM,aAAa;AAChE,QAAI,CAAC,cAAc;AACjB,MAAAA,SAAQ,WAAW;AACnB;AAAA,IACF;AACA,+BAA2B,aAAa;AACxC,qBAAiB,aAAa;AAAA,EAChC;AAEA,QAAM,SAAS,MAAM,mBAAmB,OAAO,IAAI;AAAA,IACjD,kBAAkB;AAAA,IAClB;AAAA,EACF,CAAC;AAED,MAAI,UAAU;AACZ,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAC3C;AAAA,EACF;AAEA,MAAI,CAAC,OAAO,IAAI;AACd,YAAQ,MAAM,UAAU,OAAO,KAAK,EAAE;AACtC,IAAAA,SAAQ,WAAW;AACnB;AAAA,EACF;AAEA,QAAM,UAAU,OAAO;AACvB,UAAQ,IAAI,mCAAmC,QAAQ,EAAE,IAAI;AAC7D,MAAI,QAAQ,gBAAgB;AAC1B,YAAQ,IAAI,iBAAiB,QAAQ,cAAc,EAAE;AAAA,EACvD;AACA,MAAI,QAAQ,KAAK;AACf,YAAQ,IAAI,UAAU,QAAQ,GAAG,EAAE;AAAA,EACrC;AACF;AAIA,eAAe,kBAAkB,UAAkB,MAAgB;AACjE,QAAM,kBAAkB,YAAY,MAAM,WAAW;AACrD,QAAM,WAAW,QAAQ,MAAM,QAAQ;AAEvC,MAAI;AACF,UAAM,SAAS,MAAM,qBAAqB,eAAe;AACzD,UAAM,OAAO,cAAc,UAAU,IAAI;AACzC,UAAM,SAAS,MAAM,SAAS,OAAO,IAAI,UAAU,IAAI;AACvD,oBAAgB,QAAQ,QAAQ;AAAA,EAClC,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,IAAAA,SAAQ,WAAW;AAAA,EACrB;AACF;AAEA,SAAS,cAAc,UAAkB,MAAyC;AAChF,QAAM,OAAgC,CAAC;AAEvC,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,WAAK,MAAM,YAAY,MAAM,OAAO;AACpC;AAAA,IAEF,KAAK;AACH,WAAK,SAAS,YAAY,MAAM,UAAU;AAC1C,WAAK,OAAO,YAAY,MAAM,QAAQ,KAAK;AAC3C;AAAA,IAEF,KAAK;AACH,WAAK,WAAW,QAAQ,MAAM,aAAa;AAC3C,WAAK,QAAQ,YAAY,MAAM,SAAS;AACxC,WAAK,cAAc;AACnB;AAAA,IAEF,KAAK;AACH,WAAK,WAAW,YAAY,MAAM,MAAM;AACxC;AAAA,IAEF,KAAK,SAAS;AACZ,YAAM,MAAM,YAAY,MAAM,OAAO;AACrC,YAAM,IAAI,YAAY,MAAM,KAAK;AACjC,YAAM,IAAI,YAAY,MAAM,KAAK;AACjC,UAAI,IAAK,MAAK,MAAM;AACpB,UAAI,EAAG,MAAK,IAAI,SAAS,GAAG,EAAE;AAC9B,UAAI,EAAG,MAAK,IAAI,SAAS,GAAG,EAAE;AAC9B,WAAK,UAAU,YAAY,MAAM,WAAW,KAAK;AACjD;AAAA,IACF;AAAA,IAEA,KAAK;AACH,WAAK,MAAM,YAAY,MAAM,OAAO;AACpC,WAAK,OAAO,YAAY,MAAM,QAAQ;AACtC,WAAK,UAAU,YAAY,MAAM,WAAW,KAAK;AACjD,WAAK,SAAS,QAAQ,MAAM,UAAU;AACtC;AAAA,IAEF,KAAK,aAAa;AAChB,YAAM,YAAY,YAAY,MAAM,UAAU;AAC9C,UAAI,WAAW;AACb,YAAI;AACF,eAAK,SAAS,KAAK,MAAM,SAAS;AAAA,QACpC,QAAQ;AACN,kBAAQ,MAAM,oCAAoC;AAClD,UAAAA,SAAQ,WAAW;AAAA,QACrB;AAAA,MACF;AACA;AAAA,IACF;AAAA,IAEA,KAAK;AACH,WAAK,MAAM,YAAY,MAAM,OAAO;AACpC,WAAK,QAAQ,YAAY,MAAM,SAAS;AACxC,WAAK,UAAU,YAAY,MAAM,WAAW,KAAK;AACjD;AAAA,IAEF,KAAK;AACH,WAAK,MAAM,YAAY,MAAM,OAAO;AACpC;AAAA,IAEF,KAAK;AACH,WAAK,YAAY,YAAY,MAAM,aAAa,KAAK;AACrD;AACE,cAAM,SAAS,YAAY,MAAM,UAAU;AAC3C,YAAI,OAAQ,MAAK,SAAS,SAAS,QAAQ,EAAE;AAAA,MAC/C;AACA;AAAA,IAEF,KAAK;AACH,WAAK,MAAM,YAAY,MAAM,OAAO;AACpC,WAAK,UAAU,YAAY,MAAM,WAAW,KAAK;AACjD;AAAA,IAEF,KAAK,QAAQ;AACX,WAAK,SAAS,YAAY,MAAM,UAAU,KAAK;AAC/C,YAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,UAAI,MAAO,MAAK,QAAQ,SAAS,OAAO,EAAE;AAC1C;AAAA,IACF;AAAA,IAEA,KAAK,YAAY;AACf,WAAK,OAAO,YAAY,MAAM,QAAQ;AACtC,YAAM,UAAU,YAAY,MAAM,WAAW;AAC7C,UAAI,QAAS,MAAK,UAAU,SAAS,SAAS,EAAE;AAChD;AAAA,IACF;AAAA,IAEA,KAAK;AACH,WAAK,aAAa,QAAQ,MAAM,eAAe;AAC/C;AAAA,EAIJ;AAEA,SAAO;AACT;AAKA,IAAM,eAAuC;AAAA,EAC3C,UAAU;AAAA,EACV,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AAAA,EACT,SAAS;AACX;AAIA,SAAS,YAAY;AACnB,UAAQ;AAAA,IACN;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;AAIA,eAAsB,WAAW,MAAgB;AAC/C,QAAM,CAAC,YAAY,GAAG,IAAI,IAAI;AAE9B,MAAI,CAAC,cAAc,eAAe,UAAU,QAAQ,MAAM,MAAM,QAAQ,GAAG;AACzE,cAAU;AACV;AAAA,EACF;AAGA,MAAI,eAAe,UAAU;AAC3B,UAAM,UAAU;AAChB;AAAA,EACF;AAEA,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,YAAM,YAAY,IAAI;AACtB;AAAA,IACF,KAAK;AACH,YAAM,WAAW,IAAI;AACrB;AAAA,IACF,KAAK;AACH,YAAM,aAAa,IAAI;AACvB;AAAA,IACF,KAAK;AACH,YAAM,WAAW,IAAI;AACrB;AAAA,IACF,KAAK;AACH,YAAM,qBAAqB,IAAI;AAC/B;AAAA,IACF,SAAS;AAEP,YAAM,WAAW,aAAa,UAAU;AACxC,UAAI,UAAU;AACZ,cAAM,kBAAkB,UAAU,IAAI;AAAA,MACxC,OAAO;AACL,gBAAQ,MAAM,+BAA+B,UAAU,EAAE;AACzD,kBAAU;AACV,QAAAA,SAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;","names":["process","sessions","process"]}
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import "./chunk-VKVL7WBN.js";
|
|
3
|
+
|
|
4
|
+
// src/skill-templates/issue-log-xref.ts
|
|
5
|
+
var issueLogXrefTemplate = {
|
|
6
|
+
name: "issue-log-xref",
|
|
7
|
+
description: "Cross-reference Canary issues with server logs to find root causes",
|
|
8
|
+
render: () => `---
|
|
9
|
+
name: issue-log-xref
|
|
10
|
+
description: 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.
|
|
11
|
+
allowed-tools: Bash(canary issues:*), Bash(canary login:*), Grep, Read
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Issue Log Cross-Reference
|
|
15
|
+
|
|
16
|
+
Cross-reference Canary-detected issues with server logs to identify root causes, correlate errors, and provide actionable debugging context.
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
21
|
+
- Access to server logs (see Log Source Configuration below)
|
|
22
|
+
|
|
23
|
+
## Workflow
|
|
24
|
+
|
|
25
|
+
### 1. Fetch issues from Canary
|
|
26
|
+
|
|
27
|
+
List recent issues to find ones worth investigating:
|
|
28
|
+
|
|
29
|
+
\`\`\`bash
|
|
30
|
+
canary issues list --format markdown
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
Use filters to narrow down:
|
|
34
|
+
|
|
35
|
+
\`\`\`bash
|
|
36
|
+
canary issues list --severity high --status open --format markdown
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
### 2. Get issue details
|
|
40
|
+
|
|
41
|
+
Once you identify an issue, fetch its full diagnostics:
|
|
42
|
+
|
|
43
|
+
\`\`\`bash
|
|
44
|
+
canary issues get <issue-id> --format markdown
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
Note the following from the issue details:
|
|
48
|
+
- **Timestamps** \u2014 when the issue was detected
|
|
49
|
+
- **URLs / endpoints** \u2014 which pages or API routes were involved
|
|
50
|
+
- **Error messages** \u2014 any visible error text captured in screenshots or DOM
|
|
51
|
+
- **HTTP status codes** \u2014 failed network requests
|
|
52
|
+
|
|
53
|
+
### 3. Access server logs
|
|
54
|
+
|
|
55
|
+
<!-- ============================================================
|
|
56
|
+
LOG SOURCE CONFIGURATION
|
|
57
|
+
|
|
58
|
+
Replace {{LOG_SOURCE_INSTRUCTIONS}} below with instructions
|
|
59
|
+
specific to your infrastructure. The AI agent reading this
|
|
60
|
+
skill will use these instructions to fetch logs.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
|
|
64
|
+
**AWS CloudWatch:**
|
|
65
|
+
Query CloudWatch Logs Insights for the relevant log group:
|
|
66
|
+
\`\`\`bash
|
|
67
|
+
aws logs filter-log-events \\
|
|
68
|
+
--log-group-name /ecs/my-api \\
|
|
69
|
+
--start-time <epoch-ms> \\
|
|
70
|
+
--end-time <epoch-ms> \\
|
|
71
|
+
--filter-pattern "<error-text>"
|
|
72
|
+
\`\`\`
|
|
73
|
+
|
|
74
|
+
**Local log files:**
|
|
75
|
+
Search local log files for matching entries:
|
|
76
|
+
\`\`\`bash
|
|
77
|
+
grep -i "<error-text>" /var/log/my-app/*.log
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
**Datadog:**
|
|
81
|
+
Use the Datadog CLI or API to query logs:
|
|
82
|
+
\`\`\`bash
|
|
83
|
+
dog logs list --query "service:my-api status:error" \\
|
|
84
|
+
--from <ISO-timestamp> --to <ISO-timestamp>
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
**kubectl (Kubernetes):**
|
|
88
|
+
Fetch pod logs for the relevant service:
|
|
89
|
+
\`\`\`bash
|
|
90
|
+
kubectl logs -l app=my-api --since=1h | grep "<error-text>"
|
|
91
|
+
\`\`\`
|
|
92
|
+
============================================================ -->
|
|
93
|
+
|
|
94
|
+
{{LOG_SOURCE_INSTRUCTIONS}}
|
|
95
|
+
|
|
96
|
+
### 4. Cross-reference and correlate
|
|
97
|
+
|
|
98
|
+
Match the Canary issue with log entries using these correlation points:
|
|
99
|
+
|
|
100
|
+
- **Timestamps** \u2014 find log entries within a few seconds of the issue detection time
|
|
101
|
+
- **Error messages** \u2014 search logs for the same error text shown in the issue
|
|
102
|
+
- **HTTP status codes** \u2014 look for 4xx/5xx responses matching the failed requests
|
|
103
|
+
- **URLs / routes** \u2014 filter logs by the endpoint or page path from the issue
|
|
104
|
+
- **Stack traces** \u2014 identify the code path that produced the error
|
|
105
|
+
- **Request IDs** \u2014 if your app uses correlation/request IDs, trace the full request lifecycle
|
|
106
|
+
|
|
107
|
+
## Analysis Guidelines
|
|
108
|
+
|
|
109
|
+
When presenting findings:
|
|
110
|
+
|
|
111
|
+
1. **State the match confidence** \u2014 clear match, likely match, or circumstantial
|
|
112
|
+
2. **Show the timeline** \u2014 lay out events in chronological order across both sources
|
|
113
|
+
3. **Identify the root cause** \u2014 distinguish between the symptom (what Canary caught) and the cause (what the logs reveal)
|
|
114
|
+
4. **Suggest next steps** \u2014 point to the specific code, service, or configuration that needs attention
|
|
115
|
+
5. **Note gaps** \u2014 if logs are missing or incomplete, say so explicitly rather than speculating
|
|
116
|
+
`
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/skill-templates/browser-testing.ts
|
|
120
|
+
var browserTestingTemplate = {
|
|
121
|
+
name: "browser-testing",
|
|
122
|
+
description: "Use the Canary CLI to control a browser for testing web applications",
|
|
123
|
+
render: () => `---
|
|
124
|
+
name: browser-testing
|
|
125
|
+
description: 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.
|
|
126
|
+
allowed-tools: Bash(canary session:*), Bash(canary login:*), Read
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
# Browser Testing with Canary
|
|
130
|
+
|
|
131
|
+
Use 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.
|
|
132
|
+
|
|
133
|
+
## Prerequisites
|
|
134
|
+
|
|
135
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
136
|
+
- Run \`canary session --help\` for the full command reference
|
|
137
|
+
|
|
138
|
+
## Environments & Authentication
|
|
139
|
+
|
|
140
|
+
Canary 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:
|
|
141
|
+
|
|
142
|
+
### Remote / Deployed environments (default)
|
|
143
|
+
|
|
144
|
+
Credentials are pre-configured with storage state. Just use \`--credential\` to load one:
|
|
145
|
+
|
|
146
|
+
\`\`\`bash
|
|
147
|
+
# Interactive picker \u2014 shows credential name, property name, and base URL
|
|
148
|
+
canary session start --credential
|
|
149
|
+
|
|
150
|
+
# By name
|
|
151
|
+
canary session start --credential "Admin User" --url https://staging.example.com
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
### Local development
|
|
155
|
+
|
|
156
|
+
Credentials stored in Canary won't work against localhost because session cookies are bound to the remote domain. When testing a local dev server:
|
|
157
|
+
|
|
158
|
+
1. **Ask the user to log in manually.** Start a session without credentials and tell them to authenticate in the browser:
|
|
159
|
+
|
|
160
|
+
\`\`\`bash
|
|
161
|
+
canary session start --url http://localhost:5173
|
|
162
|
+
\`\`\`
|
|
163
|
+
|
|
164
|
+
Then 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."
|
|
165
|
+
|
|
166
|
+
2. **Wait for confirmation** before proceeding with testing.
|
|
167
|
+
|
|
168
|
+
3. Alternatively, if the user has a **local storage state file**, use it:
|
|
169
|
+
|
|
170
|
+
\`\`\`bash
|
|
171
|
+
canary session start --storage-state ./local-auth.json --url http://localhost:5173
|
|
172
|
+
\`\`\`
|
|
173
|
+
|
|
174
|
+
**Rule:** If the URL is localhost or 127.0.0.1, do NOT use \`--credential\` \u2014 it will download remote session state that won't work locally. Instead, prompt the user to log in.
|
|
175
|
+
|
|
176
|
+
## Core Workflow
|
|
177
|
+
|
|
178
|
+
The fundamental loop is: **snapshot \u2192 read \u2192 act \u2192 snapshot \u2192 verify**.
|
|
179
|
+
|
|
180
|
+
### 1. Start a session
|
|
181
|
+
|
|
182
|
+
\`\`\`bash
|
|
183
|
+
# Start a headed browser (you can watch it)
|
|
184
|
+
canary session start --url https://your-app.com
|
|
185
|
+
|
|
186
|
+
# Start with a saved credential (interactive picker shows property + base URL)
|
|
187
|
+
canary session start --credential
|
|
188
|
+
|
|
189
|
+
# Start with a specific credential by name
|
|
190
|
+
canary session start --credential "Admin User" --url https://your-app.com
|
|
191
|
+
|
|
192
|
+
# Load a credential into an already-running session
|
|
193
|
+
canary session load-credential
|
|
194
|
+
\`\`\`
|
|
195
|
+
|
|
196
|
+
### 2. Take a snapshot to see the page
|
|
197
|
+
|
|
198
|
+
Snapshots are your primary way to understand what's on the page. They return an **accessibility tree** with element references (refs) and a **screenshot**.
|
|
199
|
+
|
|
200
|
+
\`\`\`bash
|
|
201
|
+
# Full snapshot: accessibility tree + screenshot
|
|
202
|
+
canary session snapshot
|
|
203
|
+
|
|
204
|
+
# Search for specific elements (fastest way to find what you need)
|
|
205
|
+
canary session snapshot --search "Submit"
|
|
206
|
+
canary session snapshot --search "Email|Password"
|
|
207
|
+
|
|
208
|
+
# Screenshot only (useful for visual verification)
|
|
209
|
+
canary session snapshot --mode screenshot
|
|
210
|
+
|
|
211
|
+
# Tree only (useful when you need ref details, no image)
|
|
212
|
+
canary session snapshot --mode tree
|
|
213
|
+
\`\`\`
|
|
214
|
+
|
|
215
|
+
**Reading snapshot output:**
|
|
216
|
+
|
|
217
|
+
The accessibility tree shows elements with refs like \`[ref=e15]\`. Use these refs in subsequent commands. The tree is structured to show the page hierarchy \u2014 forms, buttons, links, headings, etc.
|
|
218
|
+
|
|
219
|
+
When 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 \u2014 search overlays can obscure subtle UI changes.
|
|
220
|
+
|
|
221
|
+
### 3. Interact with the page
|
|
222
|
+
|
|
223
|
+
Always reference elements by their **ref** from the snapshot.
|
|
224
|
+
|
|
225
|
+
\`\`\`bash
|
|
226
|
+
# Click an element
|
|
227
|
+
canary session click --ref e15 --element "Submit button"
|
|
228
|
+
|
|
229
|
+
# Type into an input field
|
|
230
|
+
canary session type --ref e7 --text "user@example.com" --element "Email input"
|
|
231
|
+
|
|
232
|
+
# Type and submit (presses Enter after typing)
|
|
233
|
+
canary session type --ref e7 --text "search query" --element "Search box" --submit
|
|
234
|
+
|
|
235
|
+
# Fill multiple form fields at once
|
|
236
|
+
canary session fill --fields '[{"ref":"e3","value":"John"},{"ref":"e4","value":"john@example.com"}]'
|
|
237
|
+
|
|
238
|
+
# Select a dropdown option
|
|
239
|
+
canary session select --ref e12 --value "admin" --element "Role dropdown"
|
|
240
|
+
|
|
241
|
+
# Press a keyboard key
|
|
242
|
+
canary session press --key Enter
|
|
243
|
+
canary session press --key Escape
|
|
244
|
+
canary session press --key ArrowDown
|
|
245
|
+
|
|
246
|
+
# Hover to reveal hidden content (tooltips, dropdowns)
|
|
247
|
+
canary session hover --ref e8 --element "User menu"
|
|
248
|
+
\`\`\`
|
|
249
|
+
|
|
250
|
+
### 4. Navigate
|
|
251
|
+
|
|
252
|
+
\`\`\`bash
|
|
253
|
+
canary session navigate --url https://your-app.com/settings
|
|
254
|
+
canary session back
|
|
255
|
+
\`\`\`
|
|
256
|
+
|
|
257
|
+
### 5. Scroll
|
|
258
|
+
|
|
259
|
+
\`\`\`bash
|
|
260
|
+
# Scroll down the page
|
|
261
|
+
canary session scroll --direction down
|
|
262
|
+
|
|
263
|
+
# Scroll with more intensity (1-10, default 3)
|
|
264
|
+
canary session scroll --direction down --amount 8
|
|
265
|
+
|
|
266
|
+
# Scroll up
|
|
267
|
+
canary session scroll --direction up
|
|
268
|
+
\`\`\`
|
|
269
|
+
|
|
270
|
+
### 6. Verify results
|
|
271
|
+
|
|
272
|
+
After every action, take a snapshot to verify the result:
|
|
273
|
+
|
|
274
|
+
\`\`\`bash
|
|
275
|
+
# Check what happened after clicking submit
|
|
276
|
+
canary session snapshot
|
|
277
|
+
|
|
278
|
+
# Search for a success message
|
|
279
|
+
canary session snapshot --search "Success|saved"
|
|
280
|
+
|
|
281
|
+
# Check for error messages
|
|
282
|
+
canary session snapshot --search "Error|failed|invalid"
|
|
283
|
+
|
|
284
|
+
# Check console for JavaScript errors
|
|
285
|
+
canary session console --only-errors
|
|
286
|
+
|
|
287
|
+
# Check network requests for failed API calls
|
|
288
|
+
canary session network
|
|
289
|
+
\`\`\`
|
|
290
|
+
|
|
291
|
+
## Session Management
|
|
292
|
+
|
|
293
|
+
\`\`\`bash
|
|
294
|
+
# List all active sessions
|
|
295
|
+
canary session list
|
|
296
|
+
|
|
297
|
+
# Check session details
|
|
298
|
+
canary session status
|
|
299
|
+
|
|
300
|
+
# Stop a session
|
|
301
|
+
canary session stop
|
|
302
|
+
|
|
303
|
+
# Stop all sessions
|
|
304
|
+
canary session stop --all
|
|
305
|
+
|
|
306
|
+
# When multiple sessions are active, specify which one
|
|
307
|
+
canary session snapshot --session s1
|
|
308
|
+
canary session click --ref e5 --element "Button" --session s2
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
## Advanced Techniques
|
|
312
|
+
|
|
313
|
+
### Waiting for page changes
|
|
314
|
+
|
|
315
|
+
\`\`\`bash
|
|
316
|
+
# Wait for text to appear (after navigation or async action)
|
|
317
|
+
canary session wait --text "Dashboard loaded"
|
|
318
|
+
|
|
319
|
+
# Wait for text to disappear (loading spinner)
|
|
320
|
+
canary session wait --text "Loading..."
|
|
321
|
+
|
|
322
|
+
# Wait a fixed time (use sparingly)
|
|
323
|
+
canary session wait --time 2
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
### Coordinate-based clicking
|
|
327
|
+
|
|
328
|
+
When ref-based clicking doesn't work (overlays, canvas elements), use coordinates. First take a snapshot, look at the screenshot, estimate coordinates, then click:
|
|
329
|
+
|
|
330
|
+
\`\`\`bash
|
|
331
|
+
# Click at specific coordinates
|
|
332
|
+
canary session click --x 450 --y 300 --element "Canvas element"
|
|
333
|
+
\`\`\`
|
|
334
|
+
|
|
335
|
+
### Tab management
|
|
336
|
+
|
|
337
|
+
\`\`\`bash
|
|
338
|
+
canary session tabs --action list
|
|
339
|
+
canary session tabs --action new
|
|
340
|
+
canary session tabs --action select --index 0
|
|
341
|
+
canary session tabs --action close --index 1
|
|
342
|
+
\`\`\`
|
|
343
|
+
|
|
344
|
+
### Evaluating JavaScript
|
|
345
|
+
|
|
346
|
+
\`\`\`bash
|
|
347
|
+
canary session evaluate --js "() => document.title"
|
|
348
|
+
canary session evaluate --js "() => window.location.href"
|
|
349
|
+
canary session evaluate --js "() => document.querySelectorAll('.error').length"
|
|
350
|
+
\`\`\`
|
|
351
|
+
|
|
352
|
+
### Taking labeled screenshots
|
|
353
|
+
|
|
354
|
+
\`\`\`bash
|
|
355
|
+
# Take a labeled screenshot for later reference
|
|
356
|
+
canary session screenshot --label "before-submit"
|
|
357
|
+
|
|
358
|
+
# Full page screenshot
|
|
359
|
+
canary session screenshot --full-page --label "full-page-state"
|
|
360
|
+
\`\`\`
|
|
361
|
+
|
|
362
|
+
### File downloads
|
|
363
|
+
|
|
364
|
+
\`\`\`bash
|
|
365
|
+
# List downloaded files
|
|
366
|
+
canary session evaluate --js "() => 'trigger download'"
|
|
367
|
+
# ... after download completes:
|
|
368
|
+
canary session list-downloads
|
|
369
|
+
canary session read-download
|
|
370
|
+
\`\`\`
|
|
371
|
+
|
|
372
|
+
## Testing Guidelines
|
|
373
|
+
|
|
374
|
+
1. **Always snapshot after actions** \u2014 never assume an action succeeded. Take a snapshot to verify.
|
|
375
|
+
2. **Use search to locate elements** \u2014 \`--search "Submit"\` is faster than scanning the full tree.
|
|
376
|
+
3. **Use refs, not coordinates** \u2014 ref-based interaction is more reliable. Only fall back to coordinates when refs fail.
|
|
377
|
+
4. **Check for errors** \u2014 after form submissions or navigation, check both the page content and console/network for errors.
|
|
378
|
+
5. **Wait for async operations** \u2014 if an action triggers an API call, use \`wait --text\` for the expected result rather than fixed delays.
|
|
379
|
+
6. **Load credentials for authenticated testing** \u2014 use \`canary session start --credential\` or \`canary session load-credential\` to test as specific users.
|
|
380
|
+
7. **Report what you find** \u2014 describe what you see in snapshots, what worked, what failed, and include relevant refs or error messages.
|
|
381
|
+
`
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/skill-templates/index.ts
|
|
385
|
+
var SKILL_TEMPLATES = {
|
|
386
|
+
"issue-log-xref": issueLogXrefTemplate,
|
|
387
|
+
"browser-testing": browserTestingTemplate
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/skill.ts
|
|
391
|
+
function printSkillHelp() {
|
|
392
|
+
const lines = [
|
|
393
|
+
"Usage: canary skill <name>",
|
|
394
|
+
"",
|
|
395
|
+
"Output an AI agent skill template to stdout.",
|
|
396
|
+
"Install a skill:",
|
|
397
|
+
" mkdir -p .claude/skills/<name> && canary skill <name> > .claude/skills/<name>/SKILL.md",
|
|
398
|
+
"",
|
|
399
|
+
"Available templates:"
|
|
400
|
+
];
|
|
401
|
+
for (const [key, template] of Object.entries(SKILL_TEMPLATES)) {
|
|
402
|
+
lines.push(` ${key.padEnd(24)} ${template.description}`);
|
|
403
|
+
}
|
|
404
|
+
console.log(lines.join("\n"));
|
|
405
|
+
}
|
|
406
|
+
async function runSkill(argv) {
|
|
407
|
+
const [name] = argv;
|
|
408
|
+
if (!name || name === "help" || name === "--help" || name === "-h") {
|
|
409
|
+
printSkillHelp();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const template = SKILL_TEMPLATES[name];
|
|
413
|
+
if (!template) {
|
|
414
|
+
console.error(`Unknown skill template "${name}".
|
|
415
|
+
`);
|
|
416
|
+
printSkillHelp();
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
process.stdout.write(template.render());
|
|
420
|
+
}
|
|
421
|
+
export {
|
|
422
|
+
runSkill
|
|
423
|
+
};
|
|
424
|
+
//# sourceMappingURL=skill-ORWAPBDW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":"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","names":[]}
|