@canaryai/cli 0.2.9 → 0.2.13
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/dist/{chunk-PWWQGYFG.js → chunk-ACRIE2YR.js} +5 -2
- package/dist/chunk-ACRIE2YR.js.map +1 -0
- package/dist/chunk-BOS2YLKH.js +233 -0
- package/dist/chunk-BOS2YLKH.js.map +1 -0
- package/dist/{chunk-XGO62PO2.js → chunk-IFOJT3A5.js} +1198 -262
- package/dist/chunk-IFOJT3A5.js.map +1 -0
- package/dist/{chunk-LC7ZVXPH.js → chunk-SVU2XTYZ.js} +19 -5
- package/dist/chunk-SVU2XTYZ.js.map +1 -0
- package/dist/{chunk-A44B2PEA.js → chunk-SYPQF57S.js} +40 -8
- package/dist/chunk-SYPQF57S.js.map +1 -0
- package/dist/{chunk-C2PGZRYK.js → chunk-Z3F373YR.js} +37 -11
- package/dist/chunk-Z3F373YR.js.map +1 -0
- package/dist/{debug-workflow-I3F36JBL.js → debug-workflow-K2LL6CO4.js} +10 -12
- package/dist/debug-workflow-K2LL6CO4.js.map +1 -0
- package/dist/{docs-REHST3YB.js → docs-SR7CW24Y.js} +19 -14
- package/dist/docs-SR7CW24Y.js.map +1 -0
- package/dist/{feature-flag-3HB5NTMY.js → feature-flag-BIPFVVNC.js} +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +83 -155
- package/dist/index.js.map +1 -1
- package/dist/init-KXAVWHYE.js +146 -0
- package/dist/init-KXAVWHYE.js.map +1 -0
- package/dist/{issues-YU57CHXS.js → issues-EWVB52CA.js} +37 -18
- package/dist/issues-EWVB52CA.js.map +1 -0
- package/dist/{knobs-QJ4IBLCT.js → knobs-VYABZESR.js} +3 -3
- package/dist/list-RCPYLS36.js +57 -0
- package/dist/list-RCPYLS36.js.map +1 -0
- package/dist/local-34FX3M5K.js +63 -0
- package/dist/local-34FX3M5K.js.map +1 -0
- package/dist/{local-browser-MKTJ36KY.js → local-browser-VPOSJS52.js} +4 -4
- package/dist/login-MSIM2VIH.js +130 -0
- package/dist/login-MSIM2VIH.js.map +1 -0
- package/dist/{mcp-ZOKM2AUE.js → mcp-YBR7G254.js} +7 -132
- package/dist/mcp-YBR7G254.js.map +1 -0
- package/dist/{psql-2YPIRMDY.js → psql-XO5BB5L5.js} +2 -2
- package/dist/{record-TNDBT3NY.js → record-DXXQHPGT.js} +10 -51
- package/dist/record-DXXQHPGT.js.map +1 -0
- package/dist/{redis-A7GWM23E.js → redis-CQTBPZ6F.js} +2 -2
- package/dist/{release-L4IXOHDF.js → release-DW7RPQSQ.js} +9 -5
- package/dist/release-DW7RPQSQ.js.map +1 -0
- package/dist/runner/preload.js +1 -1
- package/dist/{session-RNLKFS2Z.js → session-XQGCLWNC.js} +164 -75
- package/dist/session-XQGCLWNC.js.map +1 -0
- package/dist/skill-2TXI3IKP.js +424 -0
- package/dist/skill-2TXI3IKP.js.map +1 -0
- package/dist/{src-2WSMYBMJ.js → src-F7LQ5PY2.js} +8 -2
- package/dist/start-ZOMUD6LW.js +112 -0
- package/dist/start-ZOMUD6LW.js.map +1 -0
- package/dist/test.js +1 -1
- package/dist/test.js.map +1 -1
- package/dist/workflow-5UZTKX7X.js +624 -0
- package/dist/workflow-5UZTKX7X.js.map +1 -0
- package/package.json +1 -2
- package/dist/chunk-A44B2PEA.js.map +0 -1
- package/dist/chunk-C2PGZRYK.js.map +0 -1
- package/dist/chunk-DXIAHB72.js +0 -340
- package/dist/chunk-DXIAHB72.js.map +0 -1
- package/dist/chunk-LC7ZVXPH.js.map +0 -1
- package/dist/chunk-PWWQGYFG.js.map +0 -1
- package/dist/chunk-QLFSJG5O.js +0 -93
- package/dist/chunk-QLFSJG5O.js.map +0 -1
- package/dist/chunk-XGO62PO2.js.map +0 -1
- package/dist/debug-workflow-I3F36JBL.js.map +0 -1
- package/dist/docs-REHST3YB.js.map +0 -1
- package/dist/issues-YU57CHXS.js.map +0 -1
- package/dist/mcp-ZOKM2AUE.js.map +0 -1
- package/dist/record-TNDBT3NY.js.map +0 -1
- package/dist/release-L4IXOHDF.js.map +0 -1
- package/dist/session-RNLKFS2Z.js.map +0 -1
- package/dist/skill-CZ7SHI3P.js +0 -156
- package/dist/skill-CZ7SHI3P.js.map +0 -1
- /package/dist/{feature-flag-3HB5NTMY.js.map → feature-flag-BIPFVVNC.js.map} +0 -0
- /package/dist/{knobs-QJ4IBLCT.js.map → knobs-VYABZESR.js.map} +0 -0
- /package/dist/{local-browser-MKTJ36KY.js.map → local-browser-VPOSJS52.js.map} +0 -0
- /package/dist/{psql-2YPIRMDY.js.map → psql-XO5BB5L5.js.map} +0 -0
- /package/dist/{redis-A7GWM23E.js.map → redis-CQTBPZ6F.js.map} +0 -0
- /package/dist/{src-2WSMYBMJ.js.map → src-F7LQ5PY2.js.map} +0 -0
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
LocalBrowserHost
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SVU2XTYZ.js";
|
|
5
5
|
import {
|
|
6
6
|
callTool,
|
|
7
7
|
createSession,
|
|
8
8
|
deleteAllSessions,
|
|
9
9
|
deleteSession,
|
|
10
10
|
resolveTargetSession
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
connectTunnel,
|
|
14
|
-
createLocalRun,
|
|
15
|
-
createTunnel
|
|
16
|
-
} from "./chunk-DXIAHB72.js";
|
|
11
|
+
} from "./chunk-Z3F373YR.js";
|
|
17
12
|
import {
|
|
18
13
|
readStoredToken
|
|
19
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ACRIE2YR.js";
|
|
20
15
|
import {
|
|
21
16
|
getBrowserToolDefinitionsWithLifecycle
|
|
22
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-IFOJT3A5.js";
|
|
23
18
|
import "./chunk-XAA5VQ5N.js";
|
|
24
19
|
import "./chunk-P5Z2Y5VV.js";
|
|
25
20
|
import "./chunk-VKVL7WBN.js";
|
|
@@ -51,7 +46,6 @@ function formatMCPContent(result) {
|
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
// src/mcp/session-tools.ts
|
|
54
|
-
import { createParser } from "eventsource-parser";
|
|
55
49
|
import process from "process";
|
|
56
50
|
var browserSessions = /* @__PURE__ */ new Map();
|
|
57
51
|
var DEFAULT_API_URL = "https://api.trycanary.ai";
|
|
@@ -67,30 +61,6 @@ async function resolveToken() {
|
|
|
67
61
|
}
|
|
68
62
|
function getSessionToolDefinitions() {
|
|
69
63
|
return [
|
|
70
|
-
{
|
|
71
|
-
name: "local_run_tests",
|
|
72
|
-
description: "Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.",
|
|
73
|
-
inputSchema: {
|
|
74
|
-
type: "object",
|
|
75
|
-
properties: {
|
|
76
|
-
port: { type: "number" },
|
|
77
|
-
instructions: { type: "string" },
|
|
78
|
-
title: { type: "string" }
|
|
79
|
-
},
|
|
80
|
-
required: ["port", "instructions"]
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "local_wait_for_results",
|
|
85
|
-
description: "Wait for a local test run to complete. Streams until completion and returns a compact report.",
|
|
86
|
-
inputSchema: {
|
|
87
|
-
type: "object",
|
|
88
|
-
properties: {
|
|
89
|
-
runId: { type: "string" }
|
|
90
|
-
},
|
|
91
|
-
required: ["runId"]
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
64
|
{
|
|
95
65
|
name: "local_browser_start",
|
|
96
66
|
description: "Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.",
|
|
@@ -177,38 +147,6 @@ function getSessionToolDefinitions() {
|
|
|
177
147
|
}
|
|
178
148
|
async function handleSessionTool(tool, args) {
|
|
179
149
|
const token = await resolveToken();
|
|
180
|
-
if (tool === "local_run_tests") {
|
|
181
|
-
const input = args;
|
|
182
|
-
const apiUrl = resolveApiUrl();
|
|
183
|
-
const tunnel = await createTunnel({ apiUrl, token, port: input.port });
|
|
184
|
-
connectTunnel({
|
|
185
|
-
apiUrl,
|
|
186
|
-
tunnelId: tunnel.tunnelId,
|
|
187
|
-
token: tunnel.token,
|
|
188
|
-
port: input.port
|
|
189
|
-
});
|
|
190
|
-
const tunnelUrl = tunnel.publicUrl;
|
|
191
|
-
const run = await createLocalRun({
|
|
192
|
-
apiUrl,
|
|
193
|
-
token,
|
|
194
|
-
title: input.title ?? "Local MCP run",
|
|
195
|
-
featureSpec: input.instructions,
|
|
196
|
-
startUrl: void 0,
|
|
197
|
-
tunnelUrl
|
|
198
|
-
});
|
|
199
|
-
return toolJson({
|
|
200
|
-
runId: run.runId,
|
|
201
|
-
watchUrl: run.watchUrl,
|
|
202
|
-
tunnelUrl,
|
|
203
|
-
note: "Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion."
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (tool === "local_wait_for_results") {
|
|
207
|
-
const input = args;
|
|
208
|
-
const apiUrl = resolveApiUrl();
|
|
209
|
-
const report = await waitForResult({ apiUrl, token, runId: input.runId });
|
|
210
|
-
return toolJson(report);
|
|
211
|
-
}
|
|
212
150
|
if (tool === "local_browser_start") {
|
|
213
151
|
const input = args;
|
|
214
152
|
const apiUrl = resolveApiUrl();
|
|
@@ -338,66 +276,6 @@ async function handleSessionTool(tool, args) {
|
|
|
338
276
|
}
|
|
339
277
|
return null;
|
|
340
278
|
}
|
|
341
|
-
async function waitForResult(input) {
|
|
342
|
-
await streamUntilComplete(input);
|
|
343
|
-
const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {
|
|
344
|
-
credentials: "include",
|
|
345
|
-
headers: { authorization: `Bearer ${input.token}` }
|
|
346
|
-
});
|
|
347
|
-
const data = await response.json();
|
|
348
|
-
const run = data?.data?.run ?? data?.run ?? data?.data;
|
|
349
|
-
const summary = run?.summaryJson;
|
|
350
|
-
return formatReport({ run, summary });
|
|
351
|
-
}
|
|
352
|
-
async function streamUntilComplete(input) {
|
|
353
|
-
const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {
|
|
354
|
-
headers: { authorization: `Bearer ${input.token}` }
|
|
355
|
-
});
|
|
356
|
-
if (!response.body) return;
|
|
357
|
-
const reader = response.body.getReader();
|
|
358
|
-
const decoder = new TextDecoder();
|
|
359
|
-
const parser = createParser({
|
|
360
|
-
onEvent: (event) => {
|
|
361
|
-
if (event.event === "status") {
|
|
362
|
-
try {
|
|
363
|
-
const payload = JSON.parse(event.data);
|
|
364
|
-
if (payload?.status === "completed" || payload?.status === "failed") {
|
|
365
|
-
reader.cancel().catch(() => void 0);
|
|
366
|
-
}
|
|
367
|
-
} catch {
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (event.event === "complete" || event.event === "error") {
|
|
371
|
-
reader.cancel().catch(() => void 0);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
while (true) {
|
|
376
|
-
const { done, value } = await reader.read();
|
|
377
|
-
if (done) break;
|
|
378
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
function formatReport(input) {
|
|
382
|
-
if (!input.summary) {
|
|
383
|
-
return {
|
|
384
|
-
runId: input.run?.id,
|
|
385
|
-
status: input.run?.status ?? "unknown",
|
|
386
|
-
summary: "No final report available."
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];
|
|
390
|
-
const status = input.summary.status ?? input.run?.status ?? "unknown";
|
|
391
|
-
const issues = status === "issues_found" ? input.summary.notes ? [input.summary.notes] : ["Issues reported."] : [];
|
|
392
|
-
return {
|
|
393
|
-
runId: input.run?.id,
|
|
394
|
-
status,
|
|
395
|
-
summary: input.summary.summary ?? "Run completed.",
|
|
396
|
-
testedItems: tested,
|
|
397
|
-
issues,
|
|
398
|
-
notes: input.summary.notes ?? null
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
279
|
|
|
402
280
|
// src/mcp/browser-tools.ts
|
|
403
281
|
var activeSessionId = null;
|
|
@@ -469,16 +347,13 @@ async function handleBrowserTool(name, args) {
|
|
|
469
347
|
}
|
|
470
348
|
|
|
471
349
|
// src/mcp/index.ts
|
|
472
|
-
async function runMcp(
|
|
350
|
+
async function runMcp(_argv) {
|
|
473
351
|
const server = new Server(
|
|
474
352
|
{ name: "canary-cli", version: "0.1.0" },
|
|
475
353
|
{ capabilities: { tools: {} } }
|
|
476
354
|
);
|
|
477
355
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
478
|
-
tools: [
|
|
479
|
-
...getSessionToolDefinitions(),
|
|
480
|
-
...getBrowserMCPToolDefinitions()
|
|
481
|
-
]
|
|
356
|
+
tools: [...getSessionToolDefinitions(), ...getBrowserMCPToolDefinitions()]
|
|
482
357
|
}));
|
|
483
358
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
484
359
|
const tool = req.params.name;
|
|
@@ -496,4 +371,4 @@ async function runMcp(argv) {
|
|
|
496
371
|
export {
|
|
497
372
|
runMcp
|
|
498
373
|
};
|
|
499
|
-
//# sourceMappingURL=mcp-
|
|
374
|
+
//# sourceMappingURL=mcp-YBR7G254.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp/index.ts","../src/mcp/tool-helpers.ts","../src/mcp/session-tools.ts","../src/mcp/browser-tools.ts"],"sourcesContent":["/**\n * MCP Server for Canary CLI.\n *\n * Combines cloud-relay session tools and direct browser tools\n * into a single MCP server.\n *\n * @module\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';\nimport { toolText } from './tool-helpers';\nimport { getSessionToolDefinitions, handleSessionTool } from './session-tools';\nimport { getBrowserMCPToolDefinitions, handleBrowserTool } from './browser-tools';\n\nexport async function runMcp(_argv: string[]) {\n const server = new Server(\n { name: 'canary-cli', version: '0.1.0' },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [...getSessionToolDefinitions(), ...getBrowserMCPToolDefinitions()],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const tool = req.params.name;\n const args = (req.params.arguments ?? {}) as Record<string, unknown>;\n\n // Try browser tools first (browser_* prefix)\n const browserResult = await handleBrowserTool(tool, args);\n if (browserResult) return browserResult;\n\n // Try session tools (local_* prefix)\n const sessionResult = await handleSessionTool(tool, args);\n if (sessionResult) return sessionResult;\n\n return toolText(`Unknown tool: ${tool}`);\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n return new Promise<void>(() => undefined);\n}\n","/**\n * MCP tool response helpers.\n *\n * @module\n */\n\nimport type { ToolResult } from '@chatsdet/browser-core';\n\nexport function toolText(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nexport function toolJson(data: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };\n}\n\n/**\n * Convert a BrowserToolExecutor result to MCP SDK content blocks.\n */\nexport function formatMCPContent(result: ToolResult) {\n if (typeof result === 'string') {\n return { content: [{ type: 'text' as const, text: result }] };\n }\n\n // MCPContentResult with text and optional images\n const content: Array<{ type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string }> = [];\n\n if (result.text) {\n content.push({ type: 'text', text: result.text });\n }\n\n for (const img of result.images ?? []) {\n content.push({ type: 'image', data: img.data, mimeType: img.mimeType });\n }\n\n return { content };\n}\n","/**\n * Cloud-relay session tools for the MCP server.\n *\n * These tools proxy browser control through the cloud API via WebSocket.\n *\n * @module\n */\n\nimport { readStoredToken } from '../auth';\nimport { LocalBrowserHost } from '../local-browser/host';\nimport type { LocalBrowserMode } from '../local-browser/protocol';\nimport { toolJson } from './tool-helpers';\n\nimport process from 'node:process';\n\ntype LocalBrowserStartInput = {\n mode?: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n instructions?: string;\n};\n\ntype LocalBrowserStopInput = {\n sessionId: string;\n};\n\ntype LocalBrowserStatusInput = {\n sessionId: string;\n};\n\ninterface BrowserSession {\n sessionId: string;\n host: LocalBrowserHost;\n startedAt: number;\n mode: LocalBrowserMode;\n}\n\n// Global browser sessions managed by MCP\nconst browserSessions = new Map<string, BrowserSession>();\n\nconst DEFAULT_API_URL = 'https://api.trycanary.ai';\n\nfunction resolveApiUrl(input?: string): string {\n return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error('Missing token. Run `canary login` first or set CANARY_API_TOKEN.');\n }\n return token;\n}\n\nexport function getSessionToolDefinitions() {\n return [\n {\n name: 'local_browser_start',\n description:\n 'Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.',\n inputSchema: {\n type: 'object',\n properties: {\n mode: {\n type: 'string',\n enum: ['playwright', 'cdp'],\n description:\n \"Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome\",\n },\n cdpUrl: {\n type: 'string',\n description: \"CDP endpoint URL when mode is 'cdp' (e.g. http://localhost:9222)\",\n },\n headless: {\n type: 'boolean',\n description: 'Run browser headless (default: true for playwright mode)',\n },\n storageStatePath: {\n type: 'string',\n description: 'Path to Playwright storage state JSON for pre-authenticated sessions',\n },\n instructions: {\n type: 'string',\n description: 'Instructions for the cloud agent on what to test',\n },\n },\n },\n },\n {\n name: 'local_browser_status',\n description: 'Check the status of a local browser session.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string' },\n },\n required: ['sessionId'],\n },\n },\n {\n name: 'local_browser_stop',\n description: 'Stop a local browser session and close the browser.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: { type: 'string' },\n },\n required: ['sessionId'],\n },\n },\n {\n name: 'local_browser_list',\n description: 'List all active local browser sessions.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n {\n name: 'local_browser_run',\n description:\n 'Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.',\n inputSchema: {\n type: 'object',\n properties: {\n sessionId: {\n type: 'string',\n description: 'The session ID from local_browser_start',\n },\n instructions: {\n type: 'string',\n description: 'Instructions for the cloud agent on what to test',\n },\n startUrl: {\n type: 'string',\n description: 'Optional URL to navigate to before starting',\n },\n },\n required: ['sessionId', 'instructions'],\n },\n },\n ];\n}\n\nexport async function handleSessionTool(tool: string, args: Record<string, unknown>) {\n const token = await resolveToken();\n\n if (tool === 'local_browser_start') {\n const input = args as unknown as LocalBrowserStartInput;\n const apiUrl = resolveApiUrl();\n const mode = input.mode ?? 'playwright';\n\n const sessionResponse = await fetch(`${apiUrl}/local-browser/sessions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n browserMode: mode,\n instructions: input.instructions ?? null,\n }),\n });\n\n if (!sessionResponse.ok) {\n const text = await sessionResponse.text();\n return toolJson({ ok: false, error: `Failed to create session: ${text}` });\n }\n\n const session = (await sessionResponse.json()) as {\n ok: boolean;\n sessionId: string;\n wsToken: string;\n expiresAt: string;\n };\n\n const host = new LocalBrowserHost({\n apiUrl,\n wsToken: session.wsToken,\n sessionId: session.sessionId,\n browserMode: mode,\n cdpUrl: input.cdpUrl,\n headless: input.headless ?? true,\n storageStatePath: input.storageStatePath,\n onLog: (level, message) => {\n if (level === 'error') {\n console.error(`[LocalBrowser] ${message}`);\n }\n },\n });\n\n host.start().catch((err) => {\n console.error('Failed to start local browser:', err);\n browserSessions.delete(session.sessionId);\n });\n\n browserSessions.set(session.sessionId, {\n sessionId: session.sessionId,\n host,\n startedAt: Date.now(),\n mode,\n });\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode,\n expiresAt: session.expiresAt,\n note: 'Browser session started. The cloud agent can now control this browser. Use local_browser_stop to end the session.',\n });\n }\n\n if (tool === 'local_browser_status') {\n const input = args as unknown as LocalBrowserStatusInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: 'Session not found', sessionId: input.sessionId });\n }\n\n return toolJson({\n ok: true,\n sessionId: session.sessionId,\n mode: session.mode,\n startedAt: new Date(session.startedAt).toISOString(),\n uptimeMs: Date.now() - session.startedAt,\n });\n }\n\n if (tool === 'local_browser_stop') {\n const input = args as unknown as LocalBrowserStopInput;\n const session = browserSessions.get(input.sessionId);\n\n if (!session) {\n return toolJson({ ok: false, error: 'Session not found', sessionId: input.sessionId });\n }\n\n await session.host.stop();\n browserSessions.delete(input.sessionId);\n\n return toolJson({\n ok: true,\n sessionId: input.sessionId,\n note: 'Browser session stopped and browser closed.',\n });\n }\n\n if (tool === 'local_browser_list') {\n const sessions = Array.from(browserSessions.values()).map((s) => ({\n sessionId: s.sessionId,\n mode: s.mode,\n startedAt: new Date(s.startedAt).toISOString(),\n uptimeMs: Date.now() - s.startedAt,\n }));\n\n return toolJson({\n ok: true,\n count: sessions.length,\n sessions,\n });\n }\n\n if (tool === 'local_browser_run') {\n const input = args as unknown as {\n sessionId: string;\n instructions: string;\n startUrl?: string;\n };\n const apiUrl = resolveApiUrl();\n\n const session = browserSessions.get(input.sessionId);\n if (!session) {\n return toolJson({\n ok: false,\n error: 'Session not found locally. Make sure you started it with local_browser_start.',\n sessionId: input.sessionId,\n });\n }\n\n const response = await fetch(`${apiUrl}/local-browser/sessions/${input.sessionId}/run`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify({\n instructions: input.instructions,\n startUrl: input.startUrl ?? null,\n }),\n });\n\n if (!response.ok) {\n const text = await response.text();\n return toolJson({ ok: false, error: `Failed to start run: ${text}` });\n }\n\n const result = (await response.json()) as { ok: boolean; jobId: string; sessionId: string };\n\n return toolJson({\n ok: true,\n jobId: result.jobId,\n sessionId: result.sessionId,\n note: 'Test run started. The cloud agent is now controlling your local browser. You can watch the browser to see the test in action.',\n });\n }\n\n return null; // Not handled\n}\n","/**\n * Browser MCP tool registration — daemon adapter.\n *\n * Thin layer that maps MCP tool calls to the session daemon,\n * ensuring the CLI gets the SAME semantic diff, snapshot formatting,\n * click validation, auto-snapshot, and recovery notices as the cloud agent.\n *\n * The daemon owns all browser sessions. MCP is just a protocol adapter.\n *\n * @module\n */\n\nimport { getBrowserToolDefinitionsWithLifecycle } from '@chatsdet/browser-core';\nimport {\n createSession,\n deleteSession,\n deleteAllSessions,\n callTool,\n resolveTargetSession,\n} from '../session/daemon-client.js';\nimport { formatMCPContent, toolJson } from './tool-helpers';\nimport type { ToolResult } from '@chatsdet/browser-core';\n\n/** Track the active session ID within this MCP server instance */\nlet activeSessionId: string | null = null;\n\n/**\n * Get tool definitions for browser MCP tools (including lifecycle).\n */\nexport function getBrowserMCPToolDefinitions() {\n return getBrowserToolDefinitionsWithLifecycle();\n}\n\n/**\n * Handle a browser tool call.\n * Returns null if the tool name is not a browser tool.\n */\nexport async function handleBrowserTool(name: string, args: Record<string, unknown>) {\n // Lifecycle tools\n if (name === 'browser_start') {\n try {\n const result = await createSession({\n headless: (args.headless as boolean) ?? false,\n storageStatePath: args.storageStatePath as string | undefined,\n });\n\n if (!result.ok) {\n return toolJson({ ok: false, error: result.error });\n }\n\n activeSessionId = result.data!.id;\n return toolJson({\n ok: true,\n sessionId: activeSessionId,\n note: 'Browser started. You can now use browser_navigate, browser_click, and other browser tools.',\n });\n } catch (err) {\n return toolJson({ ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n if (name === 'browser_stop') {\n try {\n if (activeSessionId) {\n await deleteSession(activeSessionId);\n activeSessionId = null;\n } else {\n // Stop all sessions if no specific one tracked\n await deleteAllSessions();\n }\n return toolJson({ ok: true, note: 'Browser stopped and cleaned up.' });\n } catch (err) {\n return toolJson({ ok: false, error: err instanceof Error ? err.message : String(err) });\n }\n }\n\n // All other browser_* tools require an active session\n if (!name.startsWith('browser_')) return null;\n\n try {\n // Resolve target session: prefer tracked MCP session, then auto-resolve\n let sessionId = activeSessionId;\n if (!sessionId) {\n try {\n const target = await resolveTargetSession();\n sessionId = target.id;\n activeSessionId = sessionId;\n } catch {\n return toolJson({\n ok: false,\n error: 'No active browser session. Start one with browser_start first.',\n });\n }\n }\n\n // Strip 'browser_' prefix for the daemon tool name\n const toolName = name.replace(/^browser_/, '');\n const result = await callTool(sessionId, toolName, args);\n\n if (!result.ok) {\n // If session not found, clear tracking and report error\n if (result.error?.includes('not found')) {\n activeSessionId = null;\n }\n return toolJson({ ok: false, error: result.error, tool: name });\n }\n\n // Convert daemon response back to ToolResult format for MCP formatting\n const toolResult: ToolResult = result.images?.length\n ? { text: result.text ?? '', images: result.images }\n : (result.text ?? '');\n\n return formatMCPContent(toolResult);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return toolJson({ ok: false, error: message, tool: name });\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
|
|
|
2
2
|
import {
|
|
3
3
|
readStoredApiUrl,
|
|
4
4
|
readStoredToken
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ACRIE2YR.js";
|
|
6
6
|
import "./chunk-VKVL7WBN.js";
|
|
7
7
|
|
|
8
8
|
// src/psql.ts
|
|
@@ -122,4 +122,4 @@ Time: ${json.durationMs}ms`);
|
|
|
122
122
|
export {
|
|
123
123
|
runPsql
|
|
124
124
|
};
|
|
125
|
-
//# sourceMappingURL=psql-
|
|
125
|
+
//# sourceMappingURL=psql-XO5BB5L5.js.map
|
|
@@ -2,13 +2,13 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
|
|
|
2
2
|
import {
|
|
3
3
|
apiRequest,
|
|
4
4
|
downloadStorageState,
|
|
5
|
-
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
selectCredential
|
|
6
|
+
} from "./chunk-BOS2YLKH.js";
|
|
7
7
|
import {
|
|
8
8
|
getArgValue,
|
|
9
9
|
hasFlag,
|
|
10
10
|
resolveConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ACRIE2YR.js";
|
|
12
12
|
import {
|
|
13
13
|
getCanaryTmpDir
|
|
14
14
|
} from "./chunk-XAA5VQ5N.js";
|
|
@@ -17,7 +17,6 @@ import "./chunk-VKVL7WBN.js";
|
|
|
17
17
|
// src/record.ts
|
|
18
18
|
import fs from "fs/promises";
|
|
19
19
|
import path from "path";
|
|
20
|
-
import readline from "readline";
|
|
21
20
|
import process from "process";
|
|
22
21
|
|
|
23
22
|
// src/record-interaction-script.ts
|
|
@@ -186,24 +185,6 @@ var INTERACTION_CAPTURE_SCRIPT = `
|
|
|
186
185
|
`;
|
|
187
186
|
|
|
188
187
|
// src/record.ts
|
|
189
|
-
async function promptSelection(items, prompt) {
|
|
190
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
191
|
-
return new Promise((resolve) => {
|
|
192
|
-
for (let i = 0; i < items.length; i++) {
|
|
193
|
-
console.log(` ${i + 1}. ${items[i]}`);
|
|
194
|
-
}
|
|
195
|
-
rl.question(`
|
|
196
|
-
${prompt} `, (answer) => {
|
|
197
|
-
rl.close();
|
|
198
|
-
const idx = parseInt(answer, 10) - 1;
|
|
199
|
-
if (isNaN(idx) || idx < 0 || idx >= items.length) {
|
|
200
|
-
console.error("Invalid selection.");
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
resolve(idx);
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
188
|
async function runRecord(argv) {
|
|
208
189
|
if (hasFlag(argv, "--help", "-h")) {
|
|
209
190
|
console.log(
|
|
@@ -230,32 +211,10 @@ async function runRecord(argv) {
|
|
|
230
211
|
const outputDir = getArgValue(argv, "--output");
|
|
231
212
|
const skipUpload = hasFlag(argv, "--no-upload");
|
|
232
213
|
console.log("Fetching credentials...");
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
config.token,
|
|
236
|
-
"/org/credentials",
|
|
237
|
-
"items"
|
|
238
|
-
);
|
|
239
|
-
if (credentials.length === 0) {
|
|
240
|
-
console.error("No credentials found. Create one first in the web app.");
|
|
214
|
+
const credential = await selectCredential(config.apiUrl, config.token, credentialArg);
|
|
215
|
+
if (!credential) {
|
|
241
216
|
process.exit(1);
|
|
242
217
|
}
|
|
243
|
-
let credential;
|
|
244
|
-
if (credentialArg) {
|
|
245
|
-
const found = credentials.find((c) => c.id === credentialArg);
|
|
246
|
-
if (!found) {
|
|
247
|
-
console.error(`Credential not found: ${credentialArg}`);
|
|
248
|
-
process.exit(1);
|
|
249
|
-
}
|
|
250
|
-
credential = found;
|
|
251
|
-
} else {
|
|
252
|
-
console.log("\nSelect a credential:\n");
|
|
253
|
-
const labels = credentials.map(
|
|
254
|
-
(c) => `${c.name} (${c.propertyName ?? c.propertyId})`
|
|
255
|
-
);
|
|
256
|
-
const idx = await promptSelection(labels, "Enter number:");
|
|
257
|
-
credential = credentials[idx];
|
|
258
|
-
}
|
|
259
218
|
console.log(`Using credential: ${credential.name}`);
|
|
260
219
|
const propertyId = credential.propertyId;
|
|
261
220
|
let storageStatePath;
|
|
@@ -285,10 +244,12 @@ async function runRecord(argv) {
|
|
|
285
244
|
}
|
|
286
245
|
const navigateUrl = startUrl ?? loginUrl;
|
|
287
246
|
if (!navigateUrl) {
|
|
288
|
-
console.warn(
|
|
247
|
+
console.warn(
|
|
248
|
+
"No start URL provided and no login URL on credential. Browser will open to about:blank."
|
|
249
|
+
);
|
|
289
250
|
}
|
|
290
251
|
console.log("Launching browser...");
|
|
291
|
-
const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import("./src-
|
|
252
|
+
const { PlaywrightClient, consoleLogger, captureElementAtPoint } = await import("./src-F7LQ5PY2.js");
|
|
292
253
|
const videoDir = path.join(getCanaryTmpDir(), `canary-record-video-${Date.now()}`);
|
|
293
254
|
await fs.mkdir(videoDir, { recursive: true });
|
|
294
255
|
const client = new PlaywrightClient({ logger: consoleLogger });
|
|
@@ -311,7 +272,6 @@ async function runRecord(argv) {
|
|
|
311
272
|
console.warn("Navigation timed out or failed, continuing anyway.");
|
|
312
273
|
});
|
|
313
274
|
}
|
|
314
|
-
const startedAt = /* @__PURE__ */ new Date();
|
|
315
275
|
const events = [];
|
|
316
276
|
let running = true;
|
|
317
277
|
const currentUrl = page.url();
|
|
@@ -374,7 +334,6 @@ async function runRecord(argv) {
|
|
|
374
334
|
if (!running) return;
|
|
375
335
|
running = false;
|
|
376
336
|
clearInterval(pollInterval);
|
|
377
|
-
const endedAt = /* @__PURE__ */ new Date();
|
|
378
337
|
console.log(`
|
|
379
338
|
Recording stopped. ${events.length} events captured.`);
|
|
380
339
|
console.log("Saving video...");
|
|
@@ -435,4 +394,4 @@ Output directory: ${outDir}`);
|
|
|
435
394
|
export {
|
|
436
395
|
runRecord
|
|
437
396
|
};
|
|
438
|
-
//# sourceMappingURL=record-
|
|
397
|
+
//# sourceMappingURL=record-DXXQHPGT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/record.ts","../src/record-interaction-script.ts"],"sourcesContent":["/**\n * `canary record` — Record browser interactions for flow creation.\n *\n * Launches a headed browser, injects an in-page capture script,\n * and records user interactions (clicks, inputs, navigation) enriched\n * with accessibility snapshots and element info. On stop, bundles\n * everything as JSONL + video and uploads to the API.\n *\n * @module record\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { getCanaryTmpDir } from '@chatsdet/tmp';\nimport process from 'node:process';\nimport { resolveConfig, getArgValue, hasFlag } from './auth.js';\nimport { apiRequest, selectCredential, downloadStorageState } from './cli-helpers.js';\n\nimport { INTERACTION_CAPTURE_SCRIPT } from './record-interaction-script.js';\n\n/** Minimal recording event types (mirrors @chatsdet/types/recording) */\ninterface RecordingClickEvent {\n type: 'click';\n ts: number;\n x: number;\n y: number;\n tagName: string;\n id?: string;\n testId?: string;\n ariaLabel?: string;\n role?: string;\n textContent?: string;\n className?: string;\n elementInfo?: Record<string, unknown>;\n snapshot?: string;\n}\n\ntype RecordingEvent =\n | RecordingClickEvent\n | {\n type: 'input';\n ts: number;\n tagName: string;\n id?: string;\n inputType?: string;\n ariaLabel?: string;\n value: string;\n isFinal?: boolean;\n }\n | {\n type: 'change';\n ts: number;\n tagName: string;\n id?: string;\n inputType?: string;\n ariaLabel?: string;\n value: string;\n }\n | { type: 'keydown'; ts: number; key: string }\n | { type: 'navigation'; ts: number; url: string; snapshot?: string; navigationType?: string }\n | { type: 'initial-url'; ts: number; url: string; snapshot?: string };\n\ninterface CredentialDetail {\n id: string;\n name: string;\n loginUrl?: string;\n storageStateS3Key?: string | null;\n}\n\nexport async function runRecord(argv: string[]): Promise<void> {\n if (hasFlag(argv, '--help', '-h')) {\n console.log(\n [\n 'Usage: canary record [options]',\n '',\n 'Record browser interactions for flow creation.',\n '',\n 'Options:',\n ' --credential <id> Credential ID (skip interactive selection)',\n ' --url <startUrl> URL to navigate to after launch',\n ' --output <dir> Local output directory (default: temp dir)',\n ' --env <env> Environment (local, dev, prod)',\n ' --no-upload Save locally only, skip API upload',\n '',\n 'Press Ctrl+C to stop recording.',\n ].join('\\n')\n );\n return;\n }\n\n const config = await resolveConfig(argv);\n const credentialArg = getArgValue(argv, '--credential');\n const startUrl = getArgValue(argv, '--url');\n const outputDir = getArgValue(argv, '--output');\n const skipUpload = hasFlag(argv, '--no-upload');\n\n // 1. Fetch and select credential\n console.log('Fetching credentials...');\n const credential = await selectCredential(config.apiUrl, config.token, credentialArg);\n if (!credential) {\n process.exit(1);\n }\n\n console.log(`Using credential: ${credential.name}`);\n\n const propertyId = credential.propertyId;\n\n // 3. Download storage state if available\n let storageStatePath: string | undefined;\n if (credential.storageStateS3Key) {\n console.log('Downloading storage state...');\n storageStatePath = await downloadStorageState({\n apiUrl: config.apiUrl,\n token: config.token,\n propertyId: credential.propertyId,\n credentialId: credential.id,\n });\n if (storageStatePath) {\n console.log('Storage state loaded.');\n } else {\n console.warn('Could not download storage state, continuing without it.');\n }\n }\n\n // 4. Get credential detail for loginUrl\n let loginUrl = credential.loginUrl;\n if (!loginUrl) {\n const detail = await apiRequest<{ ok: boolean; credential?: CredentialDetail }>(\n config.apiUrl,\n config.token,\n 'GET',\n `/org/properties/${propertyId}/credentials/${credential.id}`\n );\n loginUrl = detail.credential?.loginUrl ?? undefined;\n }\n\n // 5. Determine start URL\n const navigateUrl = startUrl ?? loginUrl;\n if (!navigateUrl) {\n console.warn(\n 'No start URL provided and no login URL on credential. Browser will open to about:blank.'\n );\n }\n\n // 6. Launch browser\n console.log('Launching browser...');\n\n // Lazy-load playwright-dependent modules\n const { PlaywrightClient, consoleLogger, captureElementAtPoint } =\n await import('@chatsdet/browser-core');\n\n const videoDir = path.join(getCanaryTmpDir(), `canary-record-video-${Date.now()}`);\n await fs.mkdir(videoDir, { recursive: true });\n\n const client = new PlaywrightClient({ logger: consoleLogger });\n await client.connect({\n browserMode: 'headed',\n storageStatePath,\n recordVideo: { dir: videoDir },\n });\n\n const page = await client.getPageForReplay();\n if (!page) {\n console.error('Failed to get browser page.');\n await client.disconnect();\n process.exit(1);\n }\n\n // 7. Inject interaction capture script\n await page.addInitScript(INTERACTION_CAPTURE_SCRIPT);\n await page.evaluate(INTERACTION_CAPTURE_SCRIPT);\n\n // 8. Navigate\n if (navigateUrl) {\n console.log(`Navigating to ${navigateUrl}`);\n await page.goto(navigateUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 }).catch(() => {\n console.warn('Navigation timed out or failed, continuing anyway.');\n });\n }\n\n const events: RecordingEvent[] = [];\n let running = true;\n\n // Emit initial-url event so we know the starting page\n const currentUrl = page.url();\n const initialUrlEvent: RecordingEvent = {\n type: 'initial-url',\n ts: Date.now(),\n url: currentUrl,\n };\n try {\n const snapshot = await (\n page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> }\n )._snapshotForAI({ mode: 'full' });\n (initialUrlEvent as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail\n }\n events.push(initialUrlEvent);\n process.stdout.write(` [${events.length}] initial-url → ${currentUrl}\\n`);\n\n console.log('\\nRecording started. Interact with the browser.');\n console.log('Press Ctrl+C to stop recording.\\n');\n\n // 9. Poll loop — collect events from the page\n const pollInterval = setInterval(async () => {\n if (!running) return;\n try {\n const raw = await page.evaluate(() => {\n const evts =\n (window as unknown as { __canaryRecordedEvents?: unknown[] }).__canaryRecordedEvents ??\n [];\n (window as unknown as { __canaryRecordedEvents: unknown[] }).__canaryRecordedEvents = [];\n return evts;\n });\n\n if (!Array.isArray(raw) || raw.length === 0) return;\n\n for (const evt of raw as RecordingEvent[]) {\n // Enrich click events with element info and periodic snapshots\n if (evt.type === 'click') {\n const clickEvt = evt as RecordingClickEvent;\n try {\n const info = await captureElementAtPoint(page, clickEvt.x, clickEvt.y);\n if (info) {\n clickEvt.elementInfo = info as unknown as Record<string, unknown>;\n }\n } catch {\n // Element may have disappeared\n }\n\n // Snapshot every click for full page context\n try {\n const snapshot = await (\n page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> }\n )._snapshotForAI({ mode: 'full' });\n clickEvt.snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n // Enrich navigation events with snapshot\n if (evt.type === 'navigation') {\n try {\n const snapshot = await (\n page as unknown as { _snapshotForAI(opts: { mode: string }): Promise<string> }\n )._snapshotForAI({ mode: 'full' });\n (evt as { snapshot?: string }).snapshot = snapshot;\n } catch {\n // Snapshot may fail during navigation\n }\n }\n\n events.push(evt);\n const label =\n evt.type === 'click'\n ? `click (${(evt as RecordingClickEvent).tagName})`\n : evt.type === 'input'\n ? `input`\n : evt.type === 'change'\n ? `change (${(evt as { tagName: string }).tagName})`\n : evt.type === 'navigation'\n ? `navigation → ${(evt as { url: string }).url}`\n : evt.type === 'initial-url'\n ? `initial-url → ${(evt as { url: string }).url}`\n : `keydown: ${(evt as { key: string }).key}`;\n process.stdout.write(` [${events.length}] ${label}\\n`);\n }\n } catch {\n // Page may have been closed\n }\n }, 500);\n\n // 10. Handle Ctrl+C\n const cleanup = async () => {\n if (!running) return;\n running = false;\n clearInterval(pollInterval);\n\n console.log(`\\nRecording stopped. ${events.length} events captured.`);\n\n // Save video\n console.log('Saving video...');\n const videoResult = await client.saveVideo();\n await client.disconnect();\n\n // Clean up temp storage state\n if (storageStatePath) {\n await fs.unlink(storageStatePath).catch(() => {});\n }\n\n // Prepare output directory\n const outDir = outputDir ?? path.join(getCanaryTmpDir(), `canary-recording-${Date.now()}`);\n await fs.mkdir(outDir, { recursive: true });\n\n // Write events JSONL\n const eventsPath = path.join(outDir, 'events.jsonl');\n const lines = events.map((e) => JSON.stringify(e)).join('\\n');\n await fs.writeFile(eventsPath, lines, 'utf-8');\n console.log(`Events saved: ${eventsPath}`);\n\n // Copy video if available\n let videoPath: string | undefined;\n if (videoResult?.videoPath) {\n videoPath = path.join(outDir, 'video.webm');\n await fs.copyFile(videoResult.videoPath, videoPath);\n console.log(`Video saved: ${videoPath}`);\n }\n\n // Upload to API\n if (!skipUpload && events.length > 0) {\n console.log('Uploading recording...');\n try {\n const formData = new FormData();\n formData.append('propertyId', propertyId);\n formData.append('credentialId', credential.id);\n\n const eventsBlob = new Blob([lines], { type: 'application/x-ndjson' });\n formData.append('events', eventsBlob, 'events.jsonl');\n\n if (videoPath) {\n const videoBuffer = await fs.readFile(videoPath);\n const videoBlob = new Blob([videoBuffer], { type: 'video/webm' });\n formData.append('video', videoBlob, 'video.webm');\n }\n\n const res = await fetch(`${config.apiUrl}/org/recordings/upload`, {\n method: 'POST',\n headers: { Authorization: `Bearer ${config.token}` },\n body: formData,\n });\n\n const json = (await res.json()) as {\n ok: boolean;\n recordingId?: string;\n error?: string;\n };\n\n if (json.ok) {\n console.log(`Recording uploaded. ID: ${json.recordingId}`);\n } else {\n console.error(`Upload failed: ${json.error ?? 'Unknown error'}`);\n }\n } catch (err) {\n console.error('Upload failed:', err instanceof Error ? err.message : err);\n }\n }\n\n console.log(`\\nOutput directory: ${outDir}`);\n process.exit(0);\n };\n\n process.on('SIGINT', () => void cleanup());\n process.on('SIGTERM', () => void cleanup());\n\n // Also stop if the page is closed\n page.on('close', () => void cleanup());\n}\n","/**\n * In-page interaction capture script injected via addInitScript.\n * Captures user interactions to window.__canaryRecordedEvents.\n *\n * @module record-interaction-script\n */\n\nexport const INTERACTION_CAPTURE_SCRIPT = `\n(function() {\n if (window.__canaryRecordedEvents) return;\n window.__canaryRecordedEvents = [];\n\n function push(event) {\n window.__canaryRecordedEvents.push(event);\n }\n\n // --- Input debouncing ---\n // Accumulate input per element, flush after 500ms idle as a single event\n var inputTimers = new Map();\n var inputState = new Map();\n\n function getElementKey(el) {\n return (el.id || '') + '|' + (el.getAttribute('aria-label') || '') + '|' + (el.tagName || '') + '|' + (el.getAttribute('name') || '');\n }\n\n function flushInput(key) {\n var state = inputState.get(key);\n if (!state) return;\n inputTimers.delete(key);\n inputState.delete(key);\n push({\n type: 'input',\n ts: state.ts,\n tagName: state.tagName,\n id: state.id || undefined,\n inputType: state.inputType || undefined,\n ariaLabel: state.ariaLabel || undefined,\n value: state.value,\n isFinal: true,\n });\n }\n\n function flushAllInputs() {\n inputTimers.forEach(function(timer) { clearTimeout(timer); });\n inputState.forEach(function(_, key) { flushInput(key); });\n }\n\n document.addEventListener('click', function(e) {\n var el = e.target;\n push({\n type: 'click',\n ts: Date.now(),\n x: e.clientX,\n y: e.clientY,\n tagName: el.tagName || '',\n id: el.id || undefined,\n testId: el.getAttribute('data-testid') || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n role: el.getAttribute('role') || undefined,\n textContent: (el.textContent || '').slice(0, 200).trim() || undefined,\n className: el.className && typeof el.className === 'string' ? el.className.slice(0, 200) : undefined,\n });\n }, true);\n\n document.addEventListener('input', function(e) {\n var el = e.target;\n var isPassword = el.type === 'password';\n var key = getElementKey(el);\n\n // Clear previous timer for this element\n var prevTimer = inputTimers.get(key);\n if (prevTimer) clearTimeout(prevTimer);\n\n // Update accumulated state\n inputState.set(key, {\n ts: Date.now(),\n tagName: el.tagName || '',\n id: el.id || undefined,\n inputType: el.type || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: isPassword ? '***' : (el.value || ''),\n });\n\n // Schedule flush after 500ms idle\n inputTimers.set(key, setTimeout(function() { flushInput(key); }, 500));\n }, true);\n\n // --- Change events for selects, checkboxes, radios ---\n document.addEventListener('change', function(e) {\n var el = e.target;\n var tagName = el.tagName || '';\n var inputType = (el.type || '').toLowerCase();\n\n // Only capture selects, checkboxes, and radios (text inputs are handled by input debouncing)\n var isSelect = tagName === 'SELECT';\n var isCheckOrRadio = inputType === 'checkbox' || inputType === 'radio';\n if (!isSelect && !isCheckOrRadio) return;\n\n var value;\n if (isCheckOrRadio) {\n value = el.checked ? 'checked' : 'unchecked';\n } else {\n value = el.value || '';\n }\n\n push({\n type: 'change',\n ts: Date.now(),\n tagName: tagName,\n id: el.id || undefined,\n inputType: inputType || undefined,\n ariaLabel: el.getAttribute('aria-label') || undefined,\n value: value,\n });\n }, true);\n\n document.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === 'Tab' || e.key === 'Escape') {\n push({\n type: 'keydown',\n ts: Date.now(),\n key: e.key,\n });\n }\n }, true);\n\n // --- SPA navigation: patch pushState/replaceState ---\n var origPushState = history.pushState;\n var origReplaceState = history.replaceState;\n\n history.pushState = function() {\n origPushState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'pushState',\n });\n };\n\n history.replaceState = function() {\n origReplaceState.apply(this, arguments);\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'replaceState',\n });\n };\n\n window.addEventListener('popstate', function() {\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'popstate',\n });\n });\n\n window.addEventListener('beforeunload', function() {\n flushAllInputs();\n push({\n type: 'navigation',\n ts: Date.now(),\n url: window.location.href,\n navigationType: 'beforeunload',\n });\n });\n})();\n`;\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,7 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
|
|
|
2
2
|
import {
|
|
3
3
|
readStoredApiUrl,
|
|
4
4
|
readStoredToken
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ACRIE2YR.js";
|
|
6
6
|
import "./chunk-VKVL7WBN.js";
|
|
7
7
|
|
|
8
8
|
// src/redis.ts
|
|
@@ -128,4 +128,4 @@ Time: ${json_response.durationMs}ms`);
|
|
|
128
128
|
export {
|
|
129
129
|
runRedis
|
|
130
130
|
};
|
|
131
|
-
//# sourceMappingURL=redis-
|
|
131
|
+
//# sourceMappingURL=redis-CQTBPZ6F.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getArgValue,
|
|
4
4
|
hasFlag,
|
|
5
5
|
resolveConfig
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ACRIE2YR.js";
|
|
7
7
|
import "./chunk-VKVL7WBN.js";
|
|
8
8
|
|
|
9
9
|
// src/release.ts
|
|
@@ -154,12 +154,16 @@ async function handleRun(argv, apiUrl, token) {
|
|
|
154
154
|
console.log(` Testers: ${run.testersCompleted}/${run.testersSpawned} completed, ${run.testersFailed} failed`);
|
|
155
155
|
console.log(` Regressions: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed, ${run.regressionTestsFailed} failed`);
|
|
156
156
|
console.log(` Issues found: ${run.issuesFound}`);
|
|
157
|
-
if (run.status === "
|
|
158
|
-
console.
|
|
157
|
+
if (run.status === "failed" || run.status === "canceled" || run.status === "timeout") {
|
|
158
|
+
console.error(`
|
|
159
|
+
Release QA FAILED (${run.status})`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
} else if (run.regressionTestsFailed === 0) {
|
|
162
|
+
console.log("\nRelease QA PASSED (0 regression failures)");
|
|
159
163
|
process.exit(0);
|
|
160
164
|
} else {
|
|
161
165
|
console.error(`
|
|
162
|
-
Release QA FAILED (${run.
|
|
166
|
+
Release QA FAILED (${run.regressionTestsFailed} regression test(s) failed)`);
|
|
163
167
|
process.exit(1);
|
|
164
168
|
}
|
|
165
169
|
}
|
|
@@ -217,4 +221,4 @@ async function runRelease(argv) {
|
|
|
217
221
|
export {
|
|
218
222
|
runRelease
|
|
219
223
|
};
|
|
220
|
-
//# sourceMappingURL=release-
|
|
224
|
+
//# sourceMappingURL=release-DW7RPQSQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/release.ts"],"sourcesContent":["/**\n * CLI Release QA Management\n *\n * Trigger, poll, and check status of Release QA runs from CI/CD pipelines.\n * Used by the scheduled-release GitHub Actions workflow to gate deployments.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\n\ntype TriggerResponse = {\n ok: boolean;\n releaseRunId?: string;\n jobId?: string;\n error?: string;\n};\n\ntype RunStatusResponse = {\n ok: boolean;\n run?: {\n id: string;\n status: string;\n triggerSource: string;\n cutoffReason: string | null;\n fromSha: string | null;\n toSha: string | null;\n commitsAnalyzed: number;\n testersSpawned: number;\n testersCompleted: number;\n testersFailed: number;\n issuesFound: number;\n regressionTestsTotal: number;\n regressionTestsPassed: number;\n regressionTestsFailed: number;\n startedAt: string | null;\n finishedAt: string | null;\n createdAt: string;\n };\n error?: string;\n};\n\n/** Terminal statuses that stop polling */\nconst TERMINAL_STATUSES = new Set([\n \"completed\",\n \"completed_with_errors\",\n \"failed\",\n \"canceled\",\n \"timeout\",\n]);\n\nasync function handleTrigger(argv: string[], apiUrl: string, token: string): Promise<void> {\n const propertyId = getArgValue(argv, \"--property-id\");\n if (!propertyId) {\n console.error(\"Error: Missing --property-id <uuid>.\");\n console.error(\"Usage: canary release trigger --property-id <uuid> [--credential-ids <uuid,...>]\");\n process.exit(1);\n }\n\n const credentialIdsRaw = getArgValue(argv, \"--credential-ids\");\n const body: Record<string, unknown> = { propertyId };\n if (credentialIdsRaw) {\n body.credentialIds = credentialIdsRaw.split(\",\").map((id) => id.trim());\n }\n\n const res = await fetch(`${apiUrl}/api/v1/release-qa/trigger`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n const json = (await res.json()) as TriggerResponse;\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n console.log(`Release QA run triggered`);\n console.log(` Run ID: ${json.releaseRunId}`);\n console.log(` Job ID: ${json.jobId}`);\n}\n\nasync function fetchRunStatus(\n apiUrl: string,\n token: string,\n runId: string\n): Promise<RunStatusResponse> {\n const res = await fetch(`${apiUrl}/api/v1/release-qa/runs/${encodeURIComponent(runId)}`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (res.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n return (await res.json()) as RunStatusResponse;\n}\n\nasync function handleStatus(argv: string[], apiUrl: string, token: string): Promise<void> {\n const runId = argv[0];\n if (!runId || runId.startsWith(\"--\")) {\n console.error(\"Error: Missing run ID.\");\n console.error(\"Usage: canary release status <run-id>\");\n process.exit(1);\n }\n\n const jsonOutput = hasFlag(argv, \"--json\");\n const json = await fetchRunStatus(apiUrl, token, runId);\n\n if (!json.ok) {\n console.error(`Error: ${json.error}`);\n process.exit(1);\n }\n\n const run = json.run!;\n\n if (jsonOutput) {\n console.log(JSON.stringify(run, null, 2));\n return;\n }\n\n console.log(`Release QA Run: ${run.id}`);\n console.log(` Status: ${run.status}`);\n console.log(` Trigger: ${run.triggerSource}`);\n console.log(` Commits analyzed: ${run.commitsAnalyzed}`);\n console.log(` Testers: ${run.testersCompleted}/${run.testersSpawned} completed, ${run.testersFailed} failed`);\n console.log(` Issues found: ${run.issuesFound}`);\n console.log(` Regression tests: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed, ${run.regressionTestsFailed} failed`);\n if (run.startedAt) console.log(` Started: ${run.startedAt}`);\n if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function handleRun(argv: string[], apiUrl: string, token: string): Promise<void> {\n const propertyId = getArgValue(argv, \"--property-id\");\n if (!propertyId) {\n console.error(\"Error: Missing --property-id <uuid>.\");\n console.error(\n \"Usage: canary release run --property-id <uuid> [--timeout 3600] [--poll-interval 30]\"\n );\n process.exit(1);\n }\n\n const timeoutSec = parseInt(getArgValue(argv, \"--timeout\") ?? \"3600\", 10);\n const pollIntervalSec = parseInt(getArgValue(argv, \"--poll-interval\") ?? \"30\", 10);\n\n const credentialIdsRaw = getArgValue(argv, \"--credential-ids\");\n const body: Record<string, unknown> = { propertyId };\n if (credentialIdsRaw) {\n body.credentialIds = credentialIdsRaw.split(\",\").map((id) => id.trim());\n }\n\n // Trigger\n console.log(\"Triggering Release QA run...\");\n const triggerRes = await fetch(`${apiUrl}/api/v1/release-qa/trigger`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n if (triggerRes.status === 401) {\n console.error(\"Error: Unauthorized. Check your API token.\");\n process.exit(1);\n }\n\n const triggerJson = (await triggerRes.json()) as TriggerResponse;\n\n if (!triggerJson.ok) {\n console.error(`Error triggering run: ${triggerJson.error}`);\n process.exit(1);\n }\n\n const runId = triggerJson.releaseRunId!;\n console.log(`Run started: ${runId}`);\n\n // Poll\n const deadline = Date.now() + timeoutSec * 1000;\n let lastStatus = \"\";\n\n while (Date.now() < deadline) {\n await sleep(pollIntervalSec * 1000);\n\n let statusJson: RunStatusResponse;\n try {\n statusJson = await fetchRunStatus(apiUrl, token, runId);\n } catch (err) {\n console.error(`Warning: Failed to fetch status, retrying... (${err})`);\n continue;\n }\n\n if (!statusJson.ok) {\n console.error(`Error: ${statusJson.error}`);\n process.exit(1);\n }\n\n const run = statusJson.run!;\n const statusLine = `[${run.status}] testers: ${run.testersCompleted}/${run.testersSpawned}, regressions: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed`;\n\n if (run.status !== lastStatus) {\n console.log(statusLine);\n lastStatus = run.status;\n } else {\n // Print progress on same status change in metrics\n console.log(statusLine);\n }\n\n if (TERMINAL_STATUSES.has(run.status)) {\n console.log(\"\");\n console.log(`Run finished: ${run.status}`);\n console.log(` Testers: ${run.testersCompleted}/${run.testersSpawned} completed, ${run.testersFailed} failed`);\n console.log(` Regressions: ${run.regressionTestsPassed}/${run.regressionTestsTotal} passed, ${run.regressionTestsFailed} failed`);\n console.log(` Issues found: ${run.issuesFound}`);\n\n // Gate deployment solely on regression test failures.\n // Other issues (failed testers, build errors, cutoffs) are visible in the\n // UI but should not block deploys when all regressions pass.\n if (run.status === \"failed\" || run.status === \"canceled\" || run.status === \"timeout\") {\n // Run didn't complete normally — no reliable regression data\n console.error(`\\nRelease QA FAILED (${run.status})`);\n process.exit(1);\n } else if (run.regressionTestsFailed === 0) {\n console.log(\"\\nRelease QA PASSED (0 regression failures)\");\n process.exit(0);\n } else {\n console.error(`\\nRelease QA FAILED (${run.regressionTestsFailed} regression test(s) failed)`);\n process.exit(1);\n }\n }\n }\n\n console.error(`\\nTimeout: Release QA did not complete within ${timeoutSec}s`);\n process.exit(1);\n}\n\nfunction printReleaseHelp(): void {\n console.log(\n [\n \"Usage: canary release <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" trigger --property-id <uuid> [--credential-ids <uuid,...>]\",\n \" Trigger a Release QA run\",\n \" status <run-id> [--json] Check run status\",\n \" run --property-id <uuid> [options] Trigger and poll until complete\",\n \"\",\n \"Run options:\",\n \" --timeout <seconds> Max wait time (default: 3600)\",\n \" --poll-interval <secs> Poll frequency (default: 30)\",\n \" --credential-ids <ids> Comma-separated credential UUIDs\",\n \"\",\n \"Options:\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --api-url <url> API URL override\",\n \" --token <key> API token override (or set CANARY_API_TOKEN)\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runRelease(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printReleaseHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"trigger\":\n await handleTrigger(rest, apiUrl, token);\n break;\n case \"status\":\n await handleStatus(rest, apiUrl, token);\n break;\n case \"run\":\n await handleRun(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printReleaseHelp();\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;AAOA,OAAO,aAAa;AAmCpB,IAAM,oBAAoB,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,eAAe,cAAc,MAAgB,QAAgB,OAA8B;AACzF,QAAM,aAAa,YAAY,MAAM,eAAe;AACpD,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,sCAAsC;AACpD,YAAQ,MAAM,kFAAkF;AAChG,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,mBAAmB,YAAY,MAAM,kBAAkB;AAC7D,QAAM,OAAgC,EAAE,WAAW;AACnD,MAAI,kBAAkB;AACpB,SAAK,gBAAgB,iBAAiB,MAAM,GAAG,EAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;AAAA,EACxE;AAEA,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,8BAA8B;AAAA,IAC7D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,4CAA4C;AAC1D,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,UAAQ,IAAI,0BAA0B;AACtC,UAAQ,IAAI,aAAa,KAAK,YAAY,EAAE;AAC5C,UAAQ,IAAI,aAAa,KAAK,KAAK,EAAE;AACvC;AAEA,eAAe,eACb,QACA,OACA,OAC4B;AAC5B,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,2BAA2B,mBAAmB,KAAK,CAAC,IAAI;AAAA,IACvF,SAAS,EAAE,eAAe,UAAU,KAAK,GAAG;AAAA,EAC9C,CAAC;AAED,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ,MAAM,4CAA4C;AAC1D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,SAAQ,MAAM,IAAI,KAAK;AACzB;AAEA,eAAe,aAAa,MAAgB,QAAgB,OAA8B;AACxF,QAAM,QAAQ,KAAK,CAAC;AACpB,MAAI,CAAC,SAAS,MAAM,WAAW,IAAI,GAAG;AACpC,YAAQ,MAAM,wBAAwB;AACtC,YAAQ,MAAM,uCAAuC;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,aAAa,QAAQ,MAAM,QAAQ;AACzC,QAAM,OAAO,MAAM,eAAe,QAAQ,OAAO,KAAK;AAEtD,MAAI,CAAC,KAAK,IAAI;AACZ,YAAQ,MAAM,UAAU,KAAK,KAAK,EAAE;AACpC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,MAAM,KAAK;AAEjB,MAAI,YAAY;AACd,YAAQ,IAAI,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AACxC;AAAA,EACF;AAEA,UAAQ,IAAI,mBAAmB,IAAI,EAAE,EAAE;AACvC,UAAQ,IAAI,0BAA0B,IAAI,MAAM,EAAE;AAClD,UAAQ,IAAI,0BAA0B,IAAI,aAAa,EAAE;AACzD,UAAQ,IAAI,0BAA0B,IAAI,eAAe,EAAE;AAC3D,UAAQ,IAAI,0BAA0B,IAAI,gBAAgB,IAAI,IAAI,cAAc,eAAe,IAAI,aAAa,SAAS;AACzH,UAAQ,IAAI,0BAA0B,IAAI,WAAW,EAAE;AACvD,UAAQ,IAAI,0BAA0B,IAAI,qBAAqB,IAAI,IAAI,oBAAoB,YAAY,IAAI,qBAAqB,SAAS;AACzI,MAAI,IAAI,UAAW,SAAQ,IAAI,0BAA0B,IAAI,SAAS,EAAE;AACxE,MAAI,IAAI,WAAY,SAAQ,IAAI,0BAA0B,IAAI,UAAU,EAAE;AAC5E;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,eAAe,UAAU,MAAgB,QAAgB,OAA8B;AACrF,QAAM,aAAa,YAAY,MAAM,eAAe;AACpD,MAAI,CAAC,YAAY;AACf,YAAQ,MAAM,sCAAsC;AACpD,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,aAAa,SAAS,YAAY,MAAM,WAAW,KAAK,QAAQ,EAAE;AACxE,QAAM,kBAAkB,SAAS,YAAY,MAAM,iBAAiB,KAAK,MAAM,EAAE;AAEjF,QAAM,mBAAmB,YAAY,MAAM,kBAAkB;AAC7D,QAAM,OAAgC,EAAE,WAAW;AACnD,MAAI,kBAAkB;AACpB,SAAK,gBAAgB,iBAAiB,MAAM,GAAG,EAAE,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;AAAA,EACxE;AAGA,UAAQ,IAAI,8BAA8B;AAC1C,QAAM,aAAa,MAAM,MAAM,GAAG,MAAM,8BAA8B;AAAA,IACpE,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AAED,MAAI,WAAW,WAAW,KAAK;AAC7B,YAAQ,MAAM,4CAA4C;AAC1D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAe,MAAM,WAAW,KAAK;AAE3C,MAAI,CAAC,YAAY,IAAI;AACnB,YAAQ,MAAM,yBAAyB,YAAY,KAAK,EAAE;AAC1D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,YAAY;AAC1B,UAAQ,IAAI,gBAAgB,KAAK,EAAE;AAGnC,QAAM,WAAW,KAAK,IAAI,IAAI,aAAa;AAC3C,MAAI,aAAa;AAEjB,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,MAAM,kBAAkB,GAAI;AAElC,QAAI;AACJ,QAAI;AACF,mBAAa,MAAM,eAAe,QAAQ,OAAO,KAAK;AAAA,IACxD,SAAS,KAAK;AACZ,cAAQ,MAAM,iDAAiD,GAAG,GAAG;AACrE;AAAA,IACF;AAEA,QAAI,CAAC,WAAW,IAAI;AAClB,cAAQ,MAAM,UAAU,WAAW,KAAK,EAAE;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,UAAM,MAAM,WAAW;AACvB,UAAM,aAAa,IAAI,IAAI,MAAM,cAAc,IAAI,gBAAgB,IAAI,IAAI,cAAc,kBAAkB,IAAI,qBAAqB,IAAI,IAAI,oBAAoB;AAEhK,QAAI,IAAI,WAAW,YAAY;AAC7B,cAAQ,IAAI,UAAU;AACtB,mBAAa,IAAI;AAAA,IACnB,OAAO;AAEL,cAAQ,IAAI,UAAU;AAAA,IACxB;AAEA,QAAI,kBAAkB,IAAI,IAAI,MAAM,GAAG;AACrC,cAAQ,IAAI,EAAE;AACd,cAAQ,IAAI,iBAAiB,IAAI,MAAM,EAAE;AACzC,cAAQ,IAAI,cAAc,IAAI,gBAAgB,IAAI,IAAI,cAAc,eAAe,IAAI,aAAa,SAAS;AAC7G,cAAQ,IAAI,kBAAkB,IAAI,qBAAqB,IAAI,IAAI,oBAAoB,YAAY,IAAI,qBAAqB,SAAS;AACjI,cAAQ,IAAI,mBAAmB,IAAI,WAAW,EAAE;AAKhD,UAAI,IAAI,WAAW,YAAY,IAAI,WAAW,cAAc,IAAI,WAAW,WAAW;AAEpF,gBAAQ,MAAM;AAAA,qBAAwB,IAAI,MAAM,GAAG;AACnD,gBAAQ,KAAK,CAAC;AAAA,MAChB,WAAW,IAAI,0BAA0B,GAAG;AAC1C,gBAAQ,IAAI,6CAA6C;AACzD,gBAAQ,KAAK,CAAC;AAAA,MAChB,OAAO;AACL,gBAAQ,MAAM;AAAA,qBAAwB,IAAI,qBAAqB,6BAA6B;AAC5F,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,MAAM;AAAA,8CAAiD,UAAU,GAAG;AAC5E,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,mBAAyB;AAChC,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,IACF,EAAE,KAAK,IAAI;AAAA,EACb;AACF;AAEA,eAAsB,WAAW,MAA+B;AAC9D,QAAM,CAAC,YAAY,GAAG,IAAI,IAAI;AAE9B,MAAI,CAAC,cAAc,eAAe,UAAU,QAAQ,MAAM,UAAU,IAAI,GAAG;AACzE,qBAAiB;AACjB;AAAA,EACF;AAEA,QAAM,EAAE,QAAQ,MAAM,IAAI,MAAM,cAAc,IAAI;AAElD,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,YAAM,cAAc,MAAM,QAAQ,KAAK;AACvC;AAAA,IACF,KAAK;AACH,YAAM,aAAa,MAAM,QAAQ,KAAK;AACtC;AAAA,IACF,KAAK;AACH,YAAM,UAAU,MAAM,QAAQ,KAAK;AACnC;AAAA,IACF;AACE,cAAQ,MAAM,wBAAwB,UAAU,EAAE;AAClD,uBAAiB;AACjB,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;","names":[]}
|