@canaryai/cli 0.2.7 → 0.2.9
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-C2PGZRYK.js +167 -0
- package/dist/chunk-C2PGZRYK.js.map +1 -0
- package/dist/{chunk-TEHABH2E.js → chunk-LC7ZVXPH.js} +2 -2
- package/dist/{chunk-6WWHXWCS.js → chunk-QLFSJG5O.js} +33 -5
- package/dist/chunk-QLFSJG5O.js.map +1 -0
- package/dist/{chunk-RL5Y6V3C.js → chunk-XGO62PO2.js} +2443 -1073
- package/dist/chunk-XGO62PO2.js.map +1 -0
- package/dist/{debug-workflow-ZFRF3JMY.js → debug-workflow-I3F36JBL.js} +57 -36
- package/dist/debug-workflow-I3F36JBL.js.map +1 -0
- package/dist/{docs-RPFT7ZJB.js → docs-REHST3YB.js} +2 -2
- package/dist/{feature-flag-2FDSKOVX.js → feature-flag-3HB5NTMY.js} +3 -2
- package/dist/{feature-flag-2FDSKOVX.js.map → feature-flag-3HB5NTMY.js.map} +1 -1
- package/dist/index.js +22 -9
- package/dist/index.js.map +1 -1
- package/dist/{issues-6ZDNDSD6.js → issues-YU57CHXS.js} +3 -2
- package/dist/{issues-6ZDNDSD6.js.map → issues-YU57CHXS.js.map} +1 -1
- package/dist/{knobs-MZRTYS3P.js → knobs-QJ4IBLCT.js} +3 -2
- package/dist/{knobs-MZRTYS3P.js.map → knobs-QJ4IBLCT.js.map} +1 -1
- package/dist/{local-browser-I2ANCFFH.js → local-browser-MKTJ36KY.js} +3 -3
- package/dist/{mcp-EOWUKFEB.js → mcp-ZOKM2AUE.js} +49 -238
- package/dist/mcp-ZOKM2AUE.js.map +1 -0
- package/dist/{record-TSF726OB.js → record-TNDBT3NY.js} +130 -28
- package/dist/record-TNDBT3NY.js.map +1 -0
- package/dist/session-RNLKFS2Z.js +751 -0
- package/dist/session-RNLKFS2Z.js.map +1 -0
- package/dist/skill-CZ7SHI3P.js +156 -0
- package/dist/skill-CZ7SHI3P.js.map +1 -0
- package/dist/{src-SCKO6YUB.js → src-2WSMYBMJ.js} +20 -2
- package/package.json +2 -2
- package/dist/chunk-6WWHXWCS.js.map +0 -1
- package/dist/chunk-RL5Y6V3C.js.map +0 -1
- package/dist/debug-workflow-ZFRF3JMY.js.map +0 -1
- package/dist/mcp-EOWUKFEB.js.map +0 -1
- package/dist/record-TSF726OB.js.map +0 -1
- /package/dist/{chunk-TEHABH2E.js.map → chunk-LC7ZVXPH.js.map} +0 -0
- /package/dist/{docs-RPFT7ZJB.js.map → docs-REHST3YB.js.map} +0 -0
- /package/dist/{local-browser-I2ANCFFH.js.map → local-browser-MKTJ36KY.js.map} +0 -0
- /package/dist/{src-SCKO6YUB.js.map → src-2WSMYBMJ.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 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 if (session.url) {\n console.log(` URL: ${session.url}`);\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 = 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 = 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/* ── 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 '',\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 '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 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 listing, selection, and storage state download for session start.\n *\n * Wraps existing cli-helpers and auth utilities to resolve credentials\n * before passing storage state paths to the daemon.\n *\n * @module\n */\n\nimport process from 'node:process';\nimport { resolveConfig } from '../auth.js';\nimport { fetchList, downloadStorageState } from '../cli-helpers.js';\n\ninterface CredentialListItem {\n id: string;\n name: string;\n propertyId: string;\n propertyName?: string;\n storageStateS3Key?: string | null;\n}\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 // Fetch all credentials for the org\n const credentials = await fetchList<CredentialListItem>(\n apiUrl,\n token,\n '/org/credentials',\n 'credentials'\n );\n\n if (credentials.length === 0) {\n console.error('No credentials found for this organization.');\n return null;\n }\n\n let credential: CredentialListItem | undefined;\n\n if (!credentialArg) {\n // Interactive selection\n console.log('Select a credential:');\n for (let i = 0; i < credentials.length; i++) {\n const c = credentials[i];\n const propLabel = c.propertyName ? ` (${c.propertyName})` : '';\n console.log(` ${i + 1}. ${c.name}${propLabel}`);\n }\n\n const readline = await import('node:readline');\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n const answer = await new Promise<string>((resolve) => {\n rl.question('> ', (ans) => {\n rl.close();\n resolve(ans.trim());\n });\n });\n\n const idx = parseInt(answer, 10) - 1;\n if (isNaN(idx) || idx < 0 || idx >= credentials.length) {\n console.error('Invalid selection.');\n return null;\n }\n credential = credentials[idx];\n } else {\n // Match by name (case-insensitive) or ID\n credential = credentials.find(\n (c) =>\n c.id === credentialArg ||\n c.name.toLowerCase() === credentialArg.toLowerCase()\n );\n if (!credential) {\n console.error(`Credential \"${credentialArg}\" not found.`);\n console.error('Available credentials:');\n for (const c of credentials) {\n console.error(` - ${c.name} (${c.id})`);\n }\n return null;\n }\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 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 // Navigate to initial URL if provided\n if (params.url) {\n await client.navigate(params.url);\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 } 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 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/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\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":"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","names":["process","process","process","sessions","process"]}
|
|
@@ -0,0 +1,156 @@
|
|
|
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 Cross-Reference
|
|
10
|
+
description: Cross-reference Canary QA issues with server logs to identify root causes
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Issue Log Cross-Reference
|
|
14
|
+
|
|
15
|
+
Cross-reference Canary-detected issues with server logs to identify root causes, correlate errors, and provide actionable debugging context.
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
- \`canary\` CLI installed and authenticated (\`canary login\`)
|
|
20
|
+
- Access to server logs (see Log Source Configuration below)
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
### 1. Fetch issues from Canary
|
|
25
|
+
|
|
26
|
+
List recent issues to find ones worth investigating:
|
|
27
|
+
|
|
28
|
+
\`\`\`bash
|
|
29
|
+
canary issues list --format markdown
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
Use filters to narrow down:
|
|
33
|
+
|
|
34
|
+
\`\`\`bash
|
|
35
|
+
canary issues list --severity high --status open --format markdown
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
### 2. Get issue details
|
|
39
|
+
|
|
40
|
+
Once you identify an issue, fetch its full diagnostics:
|
|
41
|
+
|
|
42
|
+
\`\`\`bash
|
|
43
|
+
canary issues get <issue-id> --format markdown
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
Note the following from the issue details:
|
|
47
|
+
- **Timestamps** \u2014 when the issue was detected
|
|
48
|
+
- **URLs / endpoints** \u2014 which pages or API routes were involved
|
|
49
|
+
- **Error messages** \u2014 any visible error text captured in screenshots or DOM
|
|
50
|
+
- **HTTP status codes** \u2014 failed network requests
|
|
51
|
+
|
|
52
|
+
### 3. Access server logs
|
|
53
|
+
|
|
54
|
+
<!-- ============================================================
|
|
55
|
+
LOG SOURCE CONFIGURATION
|
|
56
|
+
|
|
57
|
+
Replace {{LOG_SOURCE_INSTRUCTIONS}} below with instructions
|
|
58
|
+
specific to your infrastructure. The AI agent reading this
|
|
59
|
+
skill will use these instructions to fetch logs.
|
|
60
|
+
|
|
61
|
+
Examples:
|
|
62
|
+
|
|
63
|
+
**AWS CloudWatch:**
|
|
64
|
+
Query CloudWatch Logs Insights for the relevant log group:
|
|
65
|
+
\`\`\`bash
|
|
66
|
+
aws logs filter-log-events \\
|
|
67
|
+
--log-group-name /ecs/my-api \\
|
|
68
|
+
--start-time <epoch-ms> \\
|
|
69
|
+
--end-time <epoch-ms> \\
|
|
70
|
+
--filter-pattern "<error-text>"
|
|
71
|
+
\`\`\`
|
|
72
|
+
|
|
73
|
+
**Local log files:**
|
|
74
|
+
Search local log files for matching entries:
|
|
75
|
+
\`\`\`bash
|
|
76
|
+
grep -i "<error-text>" /var/log/my-app/*.log
|
|
77
|
+
\`\`\`
|
|
78
|
+
|
|
79
|
+
**Datadog:**
|
|
80
|
+
Use the Datadog CLI or API to query logs:
|
|
81
|
+
\`\`\`bash
|
|
82
|
+
dog logs list --query "service:my-api status:error" \\
|
|
83
|
+
--from <ISO-timestamp> --to <ISO-timestamp>
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
**kubectl (Kubernetes):**
|
|
87
|
+
Fetch pod logs for the relevant service:
|
|
88
|
+
\`\`\`bash
|
|
89
|
+
kubectl logs -l app=my-api --since=1h | grep "<error-text>"
|
|
90
|
+
\`\`\`
|
|
91
|
+
============================================================ -->
|
|
92
|
+
|
|
93
|
+
{{LOG_SOURCE_INSTRUCTIONS}}
|
|
94
|
+
|
|
95
|
+
### 4. Cross-reference and correlate
|
|
96
|
+
|
|
97
|
+
Match the Canary issue with log entries using these correlation points:
|
|
98
|
+
|
|
99
|
+
- **Timestamps** \u2014 find log entries within a few seconds of the issue detection time
|
|
100
|
+
- **Error messages** \u2014 search logs for the same error text shown in the issue
|
|
101
|
+
- **HTTP status codes** \u2014 look for 4xx/5xx responses matching the failed requests
|
|
102
|
+
- **URLs / routes** \u2014 filter logs by the endpoint or page path from the issue
|
|
103
|
+
- **Stack traces** \u2014 identify the code path that produced the error
|
|
104
|
+
- **Request IDs** \u2014 if your app uses correlation/request IDs, trace the full request lifecycle
|
|
105
|
+
|
|
106
|
+
## Analysis Guidelines
|
|
107
|
+
|
|
108
|
+
When presenting findings:
|
|
109
|
+
|
|
110
|
+
1. **State the match confidence** \u2014 clear match, likely match, or circumstantial
|
|
111
|
+
2. **Show the timeline** \u2014 lay out events in chronological order across both sources
|
|
112
|
+
3. **Identify the root cause** \u2014 distinguish between the symptom (what Canary caught) and the cause (what the logs reveal)
|
|
113
|
+
4. **Suggest next steps** \u2014 point to the specific code, service, or configuration that needs attention
|
|
114
|
+
5. **Note gaps** \u2014 if logs are missing or incomplete, say so explicitly rather than speculating
|
|
115
|
+
`
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/skill-templates/index.ts
|
|
119
|
+
var SKILL_TEMPLATES = {
|
|
120
|
+
"issue-log-xref": issueLogXrefTemplate
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/skill.ts
|
|
124
|
+
function printSkillHelp() {
|
|
125
|
+
const lines = [
|
|
126
|
+
"Usage: canary skill <name>",
|
|
127
|
+
"",
|
|
128
|
+
"Output an AI agent skill template to stdout.",
|
|
129
|
+
"Pipe to a file to install: canary skill <name> > .claude/skills/<name>.md",
|
|
130
|
+
"",
|
|
131
|
+
"Available templates:"
|
|
132
|
+
];
|
|
133
|
+
for (const [key, template] of Object.entries(SKILL_TEMPLATES)) {
|
|
134
|
+
lines.push(` ${key.padEnd(24)} ${template.description}`);
|
|
135
|
+
}
|
|
136
|
+
console.log(lines.join("\n"));
|
|
137
|
+
}
|
|
138
|
+
async function runSkill(argv) {
|
|
139
|
+
const [name] = argv;
|
|
140
|
+
if (!name || name === "help" || name === "--help" || name === "-h") {
|
|
141
|
+
printSkillHelp();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const template = SKILL_TEMPLATES[name];
|
|
145
|
+
if (!template) {
|
|
146
|
+
console.error(`Unknown skill template "${name}".
|
|
147
|
+
`);
|
|
148
|
+
printSkillHelp();
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write(template.render());
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
runSkill
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=skill-CZ7SHI3P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/skill-templates/issue-log-xref.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 Cross-Reference\ndescription: Cross-reference Canary QA issues with server logs to identify root causes\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","import type { SkillTemplate } from \"./issue-log-xref.js\";\nimport { issueLogXrefTemplate } from \"./issue-log-xref.js\";\n\nexport type { SkillTemplate };\n\nexport const SKILL_TEMPLATES: Record<string, SkillTemplate> = {\n \"issue-log-xref\": issueLogXrefTemplate,\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 \"Pipe to a file to install: canary skill <name> > .claude/skills/<name>.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;AA4GhB;;;ACjHO,IAAM,kBAAiD;AAAA,EAC5D,kBAAkB;AACpB;;;ACLA,SAAS,iBAAiB;AACxB,QAAM,QAAQ;AAAA,IACZ;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":[]}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
analyzeSnapshot,
|
|
15
15
|
annotateScreenshotIndexed,
|
|
16
16
|
appendHintsToResponse,
|
|
17
|
+
appendRowsToCSV,
|
|
17
18
|
buildResolutionNote,
|
|
18
19
|
buildSectionTree,
|
|
19
20
|
captureElementAtPoint,
|
|
@@ -26,6 +27,8 @@ import {
|
|
|
26
27
|
detectGrid,
|
|
27
28
|
detectModal,
|
|
28
29
|
detectModalFromYaml,
|
|
30
|
+
detectPaginationInfo,
|
|
31
|
+
dispatchBrowserTool,
|
|
29
32
|
downsampleImage,
|
|
30
33
|
ensureAllowedHost,
|
|
31
34
|
estimateTextTokens,
|
|
@@ -34,13 +37,16 @@ import {
|
|
|
34
37
|
extractCellText,
|
|
35
38
|
extractDataValue,
|
|
36
39
|
extractGridCell,
|
|
40
|
+
extractIndexedDB,
|
|
37
41
|
extractLinks,
|
|
38
42
|
extractLinksFromTree,
|
|
39
43
|
extractSemanticHint,
|
|
40
44
|
extractSnapshotYaml,
|
|
45
|
+
extractTablesFromSnapshot,
|
|
41
46
|
findActiveElement,
|
|
42
47
|
findCloseButton,
|
|
43
48
|
findRelevanceScope,
|
|
49
|
+
formatCSVWithFrontmatter,
|
|
44
50
|
formatClickedElement,
|
|
45
51
|
formatGridOutput,
|
|
46
52
|
formatGridRow,
|
|
@@ -48,6 +54,8 @@ import {
|
|
|
48
54
|
formatSemanticSnapshot,
|
|
49
55
|
getBrowserToolDefinitions,
|
|
50
56
|
getBrowserToolDefinitionsWithLifecycle,
|
|
57
|
+
gridInfoToRows,
|
|
58
|
+
guessMimeType,
|
|
51
59
|
hasDiffChanges,
|
|
52
60
|
highlightElement,
|
|
53
61
|
isMCPContentWithImages,
|
|
@@ -58,13 +66,14 @@ import {
|
|
|
58
66
|
parseSnapshotWithMetrics,
|
|
59
67
|
populateSearchContext,
|
|
60
68
|
reconcileCellColumn,
|
|
69
|
+
restoreIndexedDB,
|
|
61
70
|
searchElements,
|
|
62
71
|
setCdpScreencastLogger,
|
|
63
72
|
setSnapshotAnalyzerLogger,
|
|
64
73
|
sortMatchesByContext,
|
|
65
74
|
withRecoveryNotice,
|
|
66
75
|
withRecoveryNoticeMCP
|
|
67
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-XGO62PO2.js";
|
|
68
77
|
import "./chunk-XAA5VQ5N.js";
|
|
69
78
|
import {
|
|
70
79
|
consoleLogger,
|
|
@@ -88,6 +97,7 @@ export {
|
|
|
88
97
|
analyzeSnapshot,
|
|
89
98
|
annotateScreenshotIndexed,
|
|
90
99
|
appendHintsToResponse,
|
|
100
|
+
appendRowsToCSV,
|
|
91
101
|
buildResolutionNote,
|
|
92
102
|
buildSectionTree,
|
|
93
103
|
captureElementAtPoint,
|
|
@@ -101,6 +111,8 @@ export {
|
|
|
101
111
|
detectGrid,
|
|
102
112
|
detectModal,
|
|
103
113
|
detectModalFromYaml,
|
|
114
|
+
detectPaginationInfo,
|
|
115
|
+
dispatchBrowserTool,
|
|
104
116
|
downsampleImage,
|
|
105
117
|
ensureAllowedHost,
|
|
106
118
|
estimateTextTokens,
|
|
@@ -109,15 +121,18 @@ export {
|
|
|
109
121
|
extractCellText,
|
|
110
122
|
extractDataValue,
|
|
111
123
|
extractGridCell,
|
|
124
|
+
extractIndexedDB,
|
|
112
125
|
extractLinks,
|
|
113
126
|
extractLinksFromTree,
|
|
114
127
|
extractPdfText,
|
|
115
128
|
extractPdfTextFromBuffer,
|
|
116
129
|
extractSemanticHint,
|
|
117
130
|
extractSnapshotYaml,
|
|
131
|
+
extractTablesFromSnapshot,
|
|
118
132
|
findActiveElement,
|
|
119
133
|
findCloseButton,
|
|
120
134
|
findRelevanceScope,
|
|
135
|
+
formatCSVWithFrontmatter,
|
|
121
136
|
formatClickedElement,
|
|
122
137
|
formatGridOutput,
|
|
123
138
|
formatGridRow,
|
|
@@ -125,6 +140,8 @@ export {
|
|
|
125
140
|
formatSemanticSnapshot,
|
|
126
141
|
getBrowserToolDefinitions,
|
|
127
142
|
getBrowserToolDefinitionsWithLifecycle,
|
|
143
|
+
gridInfoToRows,
|
|
144
|
+
guessMimeType,
|
|
128
145
|
hasDiffChanges,
|
|
129
146
|
highlightElement,
|
|
130
147
|
isMCPContentWithImages,
|
|
@@ -135,6 +152,7 @@ export {
|
|
|
135
152
|
parseSnapshotWithMetrics,
|
|
136
153
|
populateSearchContext,
|
|
137
154
|
reconcileCellColumn,
|
|
155
|
+
restoreIndexedDB,
|
|
138
156
|
searchElements,
|
|
139
157
|
setCdpScreencastLogger,
|
|
140
158
|
setPdfExtractLogger,
|
|
@@ -143,4 +161,4 @@ export {
|
|
|
143
161
|
withRecoveryNotice,
|
|
144
162
|
withRecoveryNoticeMCP
|
|
145
163
|
};
|
|
146
|
-
//# sourceMappingURL=src-
|
|
164
|
+
//# sourceMappingURL=src-2WSMYBMJ.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canaryai/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"build": "rm -rf dist && tsup",
|
|
33
33
|
"dev": "tsup --watch",
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
|
-
"test": "playwright test -c ./playwright.config.ts",
|
|
35
|
+
"test": "bunx playwright test -c ./playwright.config.ts",
|
|
36
36
|
"unit": "bun test src/runner/healer.test.ts src/intent/intent-parser.test.ts src/run.test.ts src/jwt.test.ts",
|
|
37
37
|
"canary": "bun run build && node ./dist/index.js test",
|
|
38
38
|
"canary:dev": "bun --bun ./src/index.ts test"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli-helpers.ts"],"sourcesContent":["/**\n * Shared CLI helpers for superadmin management commands (knobs, feature-flags).\n */\n\nimport process from \"node:process\";\nimport { getArgValue } from \"./auth.js\";\n\nexport type LifecycleStage = \"active\" | \"deprecated\" | \"ready_for_cleanup\";\n\nexport function toLifecycleLabel(stage: LifecycleStage): string {\n switch (stage) {\n case \"deprecated\":\n return \"deprecated\";\n case \"ready_for_cleanup\":\n return \"ready_for_cleanup\";\n default:\n return \"active\";\n }\n}\n\nexport function parseLifecycleStage(argv: string[]): LifecycleStage {\n const stage = getArgValue(argv, \"--stage\");\n if (!stage || ![\"active\", \"deprecated\", \"ready_for_cleanup\"].includes(stage)) {\n console.error(\"Error: --stage is required and must be one of: active, deprecated, ready_for_cleanup\");\n process.exit(1);\n }\n return stage as LifecycleStage;\n}\n\nexport async function apiRequest<T extends { ok: boolean; error?: string }>(\n apiUrl: string,\n token: string,\n method: string,\n path: string,\n body?: Record<string, unknown>\n): Promise<T> {\n const res = await fetch(`${apiUrl}${path}`, {\n method,\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n ...(body ? { body: JSON.stringify(body) } : {}),\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 return (await res.json()) as T;\n}\n\nexport async function fetchList<T>(\n apiUrl: string,\n token: string,\n path: string,\n listKey: string\n): Promise<T[]> {\n const res = await fetch(`${apiUrl}${path}`, {\n headers: { Authorization: `Bearer ${token}` },\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 json = (await res.json()) as Record<string, unknown>;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n return (json[listKey] as T[]) ?? [];\n}\n"],"mappings":";;;;;;AAIA,OAAO,aAAa;AAKb,SAAS,iBAAiB,OAA+B;AAC9D,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,SAAS,oBAAoB,MAAgC;AAClE,QAAM,QAAQ,YAAY,MAAM,SAAS;AACzC,MAAI,CAAC,SAAS,CAAC,CAAC,UAAU,cAAc,mBAAmB,EAAE,SAAS,KAAK,GAAG;AAC5E,YAAQ,MAAM,sFAAsF;AACpG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAsB,WACpB,QACA,OACA,QACA,MACA,MACY;AACZ,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C;AAAA,IACA,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IAClB;AAAA,IACA,GAAI,OAAO,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC/C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAsB,UACpB,QACA,OACA,MACA,SACc;AACd,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,IAAI;AAAA,IAC1C,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,EAC9C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,qDAAqD;AACnE,YAAQ,MAAM,mBAAmB;AACjC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,MAAI,CAAC,KAAK,IAAI;AACZ,YAAQ,MAAM,UAAU,KAAK,KAAK,EAAE;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,KAAK,OAAO,KAAa,CAAC;AACpC;","names":[]}
|