@canaryai/cli 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/bin.js +1 -0
  2. package/dist/bin.js.map +1 -1
  3. package/dist/{chunk-DXJNFJ3A.js → chunk-6WWHXWCS.js} +3 -2
  4. package/dist/{chunk-DXJNFJ3A.js.map → chunk-6WWHXWCS.js.map} +1 -1
  5. package/dist/{chunk-HOYYXZPV.js → chunk-DXIAHB72.js} +3 -2
  6. package/dist/{chunk-HOYYXZPV.js.map → chunk-DXIAHB72.js.map} +1 -1
  7. package/dist/{chunk-UEOXNF5X.js → chunk-FIQBGAKW.js} +3 -1
  8. package/dist/{chunk-UEOXNF5X.js.map → chunk-FIQBGAKW.js.map} +1 -1
  9. package/dist/{chunk-TO66FC4R.js → chunk-ILEPYWZX.js} +3 -2
  10. package/dist/{chunk-TO66FC4R.js.map → chunk-ILEPYWZX.js.map} +1 -1
  11. package/dist/{chunk-7R4YFGP6.js → chunk-PWWQGYFG.js} +3 -1
  12. package/dist/{chunk-7R4YFGP6.js.map → chunk-PWWQGYFG.js.map} +1 -1
  13. package/dist/{chunk-DGUM43GV.js → chunk-VLFUCAPZ.js} +2 -1
  14. package/dist/{debug-workflow-EHKNO7BJ.js → debug-workflow-PT3OUR3V.js} +5 -4
  15. package/dist/{debug-workflow-EHKNO7BJ.js.map → debug-workflow-PT3OUR3V.js.map} +1 -1
  16. package/dist/docs-GCYDTEOY.js +270 -0
  17. package/dist/docs-GCYDTEOY.js.map +1 -0
  18. package/dist/{feature-flag-ZDLDYRSF.js → feature-flag-S5B5GLPP.js} +77 -33
  19. package/dist/feature-flag-S5B5GLPP.js.map +1 -0
  20. package/dist/index.js +26 -12
  21. package/dist/index.js.map +1 -1
  22. package/dist/{issues-FI3RIWGV.js → issues-4ZEDHPLW.js} +5 -4
  23. package/dist/{issues-FI3RIWGV.js.map → issues-4ZEDHPLW.js.map} +1 -1
  24. package/dist/{knobs-3MKMOXIV.js → knobs-RKUVK3HC.js} +5 -4
  25. package/dist/{knobs-3MKMOXIV.js.map → knobs-RKUVK3HC.js.map} +1 -1
  26. package/dist/{local-browser-GG5GUXDS.js → local-browser-J6WGFLVD.js} +5 -4
  27. package/dist/{local-browser-GG5GUXDS.js.map → local-browser-J6WGFLVD.js.map} +1 -1
  28. package/dist/mcp-HGYBMDYZ.js +687 -0
  29. package/dist/mcp-HGYBMDYZ.js.map +1 -0
  30. package/dist/{psql-IVAPNYZV.js → psql-WVIHMC6A.js} +4 -3
  31. package/dist/{psql-IVAPNYZV.js.map → psql-WVIHMC6A.js.map} +1 -1
  32. package/dist/record-X4SVNYP3.js +334 -0
  33. package/dist/record-X4SVNYP3.js.map +1 -0
  34. package/dist/{redis-LWY7L6AS.js → redis-RGHECKV5.js} +4 -3
  35. package/dist/{redis-LWY7L6AS.js.map → redis-RGHECKV5.js.map} +1 -1
  36. package/dist/{release-KQFCTAXA.js → release-ZOD4Y2BF.js} +4 -3
  37. package/dist/{release-KQFCTAXA.js.map → release-ZOD4Y2BF.js.map} +1 -1
  38. package/dist/runner/preload.js +3 -2
  39. package/dist/runner/preload.js.map +1 -1
  40. package/dist/test.js +3 -2
  41. package/dist/test.js.map +1 -1
  42. package/package.json +2 -2
  43. package/dist/feature-flag-ZDLDYRSF.js.map +0 -1
  44. package/dist/mcp-AD67OLQM.js +0 -384
  45. package/dist/mcp-AD67OLQM.js.map +0 -1
  46. /package/dist/{chunk-DGUM43GV.js.map → chunk-VLFUCAPZ.js.map} +0 -0
@@ -1 +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 if (run.status === \"completed\") {\n console.log(\"\\nRelease QA PASSED\");\n process.exit(0);\n } else {\n console.error(`\\nRelease QA FAILED (${run.status})`);\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":"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","names":[]}
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 if (run.status === \"completed\") {\n console.log(\"\\nRelease QA PASSED\");\n process.exit(0);\n } else {\n console.error(`\\nRelease QA FAILED (${run.status})`);\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":"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","names":[]}
@@ -1,3 +1,4 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
1
2
  import {
2
3
  alreadyPatched,
3
4
  getEventLog,
@@ -7,10 +8,10 @@ import {
7
8
  setEventLogPath,
8
9
  wrapExpect,
9
10
  wrapPage
10
- } from "../chunk-TO66FC4R.js";
11
+ } from "../chunk-ILEPYWZX.js";
11
12
  import {
12
13
  __require
13
- } from "../chunk-DGUM43GV.js";
14
+ } from "../chunk-VLFUCAPZ.js";
14
15
 
15
16
  // src/runner/instrumentation.ts
16
17
  import Module from "module";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/runner/instrumentation.ts","../../src/runner/preload.ts"],"sourcesContent":["/**\n * Module._load hook instrumentation for Playwright healing.\n *\n * Intercepts `@playwright/test` imports and patches them with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport Module from \"node:module\";\nimport { createRequire } from \"node:module\";\nimport { loadCanaryConfig } from \"./config\";\nimport { alreadyPatched, markPatched, recordHealingEvent, setEventLogPath } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n} from \"./healing-helpers\";\n\nlet DEBUG = false;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst requireFn: any = typeof require !== \"undefined\" ? require : createRequire(import.meta.url);\n\nexport function installInstrumentation(): void {\n if (alreadyPatched()) return;\n\n const config = loadCanaryConfig();\n DEBUG = !!config.debug;\n if (!config.enabled) {\n markPatched();\n return;\n }\n\n setEventLogPath(config.eventLogPath);\n if (!ensurePlaywrightVersion(config.allowedPlaywrightVersion)) {\n recordHealingEvent({\n kind: \"unknown\",\n action: \"playwright_version_mismatch\",\n errorMessage: \"Playwright version does not match CANARY_PLAYWRIGHT_VERSION\",\n healed: false,\n });\n markPatched();\n return;\n }\n hookPlaywrightModuleLoad();\n\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] instrumentation installed (eventLog=${config.eventLogPath})`);\n }\n\n recordHealingEvent({\n kind: \"unknown\",\n action: \"instrumentation_ready\",\n healed: false,\n });\n markPatched();\n}\n\nfunction hookPlaywrightModuleLoad(): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mod = Module as any;\n const originalLoad: typeof mod._load = mod._canaryOriginalLoad ?? mod._load;\n if (!mod._canaryOriginalLoad) {\n mod._canaryOriginalLoad = originalLoad;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mod._load = function patchedLoad(request: string, parent: any, isMain: boolean) {\n if (request === \"@playwright/test\" || request === \"playwright/test\") {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] patching playwright module (request=${request})`);\n }\n const real = originalLoad.call(this, request, parent, isMain);\n return createPatchedPlaywright(real);\n }\n return originalLoad.call(this, request, parent, isMain);\n };\n}\n\nfunction createPatchedPlaywright(real: PlaywrightExports): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const options = { debug: DEBUG };\n\n const extended = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: extended,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright patched`);\n }\n return patched;\n}\n\nfunction ensurePlaywrightVersion(expected?: string): boolean {\n if (!expected) return true;\n try {\n const pkg = requireFn(\"playwright/package.json\") as { version?: string };\n if (pkg.version && pkg.version.startsWith(expected)) {\n return true;\n }\n // eslint-disable-next-line no-console\n console.warn(`[canary] Playwright version mismatch. Expected ${expected}, found ${pkg.version ?? \"unknown\"}`);\n return false;\n } catch {\n return true;\n }\n}\n","import { installInstrumentation } from \"./instrumentation\";\nimport { getEventLog } from \"./state\";\n\n// Ensure globals are initialized and patch Playwright classes in each worker process.\ngetEventLog();\ninstallInstrumentation();\n"],"mappings":";;;;;;;;;;;;;;;AAOA,OAAO,YAAY;AACnB,SAAS,qBAAqB;AAS9B,IAAI,QAAQ;AAGZ,IAAM,YAAiB,OAAO,cAAY,cAAc,YAAU,cAAc,YAAY,GAAG;AAExF,SAAS,yBAA+B;AAC7C,MAAI,eAAe,EAAG;AAEtB,QAAM,SAAS,iBAAiB;AAChC,UAAQ,CAAC,CAAC,OAAO;AACjB,MAAI,CAAC,OAAO,SAAS;AACnB,gBAAY;AACZ;AAAA,EACF;AAEA,kBAAgB,OAAO,YAAY;AACnC,MAAI,CAAC,wBAAwB,OAAO,wBAAwB,GAAG;AAC7D,uBAAmB;AAAA,MACjB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AACD,gBAAY;AACZ;AAAA,EACF;AACA,2BAAyB;AAEzB,MAAI,OAAO;AAET,YAAQ,IAAI,uDAAuD,OAAO,YAAY,GAAG;AAAA,EAC3F;AAEA,qBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AACD,cAAY;AACd;AAEA,SAAS,2BAAiC;AAExC,QAAM,MAAM;AACZ,QAAM,eAAiC,IAAI,uBAAuB,IAAI;AACtE,MAAI,CAAC,IAAI,qBAAqB;AAC5B,QAAI,sBAAsB;AAAA,EAC5B;AAGA,MAAI,QAAQ,SAAS,YAAY,SAAiB,QAAa,QAAiB;AAC9E,QAAI,YAAY,sBAAsB,YAAY,mBAAmB;AACnE,UAAI,OAAO;AAET,gBAAQ,IAAI,uDAAuD,OAAO,GAAG;AAAA,MAC/E;AACA,YAAM,OAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAC5D,aAAO,wBAAwB,IAAI;AAAA,IACrC;AACA,WAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAAA,EACxD;AACF;AAEA,SAAS,wBAAwB,MAA4C;AAC3E,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,UAAU,EAAE,OAAO,MAAM;AAE/B,QAAM,WAAW,KAAK,KAAK,OAAO;AAAA,IAChC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,UAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAAC,QAA0C,kBAAkB;AAC7D,MAAI,OAAO;AAET,YAAQ,IAAI,oCAAoC;AAAA,EAClD;AACA,SAAO;AACT;AAEA,SAAS,wBAAwB,UAA4B;AAC3D,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,MAAM,UAAU,yBAAyB;AAC/C,QAAI,IAAI,WAAW,IAAI,QAAQ,WAAW,QAAQ,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,YAAQ,KAAK,kDAAkD,QAAQ,WAAW,IAAI,WAAW,SAAS,EAAE;AAC5G,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxHA,YAAY;AACZ,uBAAuB;","names":[]}
1
+ {"version":3,"sources":["../../src/runner/instrumentation.ts","../../src/runner/preload.ts"],"sourcesContent":["/**\n * Module._load hook instrumentation for Playwright healing.\n *\n * Intercepts `@playwright/test` imports and patches them with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport Module from \"node:module\";\nimport { createRequire } from \"node:module\";\nimport { loadCanaryConfig } from \"./config\";\nimport { alreadyPatched, markPatched, recordHealingEvent, setEventLogPath } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n} from \"./healing-helpers\";\n\nlet DEBUG = false;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst requireFn: any = typeof require !== \"undefined\" ? require : createRequire(import.meta.url);\n\nexport function installInstrumentation(): void {\n if (alreadyPatched()) return;\n\n const config = loadCanaryConfig();\n DEBUG = !!config.debug;\n if (!config.enabled) {\n markPatched();\n return;\n }\n\n setEventLogPath(config.eventLogPath);\n if (!ensurePlaywrightVersion(config.allowedPlaywrightVersion)) {\n recordHealingEvent({\n kind: \"unknown\",\n action: \"playwright_version_mismatch\",\n errorMessage: \"Playwright version does not match CANARY_PLAYWRIGHT_VERSION\",\n healed: false,\n });\n markPatched();\n return;\n }\n hookPlaywrightModuleLoad();\n\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] instrumentation installed (eventLog=${config.eventLogPath})`);\n }\n\n recordHealingEvent({\n kind: \"unknown\",\n action: \"instrumentation_ready\",\n healed: false,\n });\n markPatched();\n}\n\nfunction hookPlaywrightModuleLoad(): void {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const mod = Module as any;\n const originalLoad: typeof mod._load = mod._canaryOriginalLoad ?? mod._load;\n if (!mod._canaryOriginalLoad) {\n mod._canaryOriginalLoad = originalLoad;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n mod._load = function patchedLoad(request: string, parent: any, isMain: boolean) {\n if (request === \"@playwright/test\" || request === \"playwright/test\") {\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] patching playwright module (request=${request})`);\n }\n const real = originalLoad.call(this, request, parent, isMain);\n return createPatchedPlaywright(real);\n }\n return originalLoad.call(this, request, parent, isMain);\n };\n}\n\nfunction createPatchedPlaywright(real: PlaywrightExports): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const options = { debug: DEBUG };\n\n const extended = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: extended,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (DEBUG) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright patched`);\n }\n return patched;\n}\n\nfunction ensurePlaywrightVersion(expected?: string): boolean {\n if (!expected) return true;\n try {\n const pkg = requireFn(\"playwright/package.json\") as { version?: string };\n if (pkg.version && pkg.version.startsWith(expected)) {\n return true;\n }\n // eslint-disable-next-line no-console\n console.warn(`[canary] Playwright version mismatch. Expected ${expected}, found ${pkg.version ?? \"unknown\"}`);\n return false;\n } catch {\n return true;\n }\n}\n","import { installInstrumentation } from \"./instrumentation\";\nimport { getEventLog } from \"./state\";\n\n// Ensure globals are initialized and patch Playwright classes in each worker process.\ngetEventLog();\ninstallInstrumentation();\n"],"mappings":";;;;;;;;;;;;;;;;AAOA,OAAO,YAAY;AACnB,SAAS,qBAAqB;AAS9B,IAAI,QAAQ;AAGZ,IAAM,YAAiB,OAAO,cAAY,cAAc,YAAU,cAAc,YAAY,GAAG;AAExF,SAAS,yBAA+B;AAC7C,MAAI,eAAe,EAAG;AAEtB,QAAM,SAAS,iBAAiB;AAChC,UAAQ,CAAC,CAAC,OAAO;AACjB,MAAI,CAAC,OAAO,SAAS;AACnB,gBAAY;AACZ;AAAA,EACF;AAEA,kBAAgB,OAAO,YAAY;AACnC,MAAI,CAAC,wBAAwB,OAAO,wBAAwB,GAAG;AAC7D,uBAAmB;AAAA,MACjB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,QAAQ;AAAA,IACV,CAAC;AACD,gBAAY;AACZ;AAAA,EACF;AACA,2BAAyB;AAEzB,MAAI,OAAO;AAET,YAAQ,IAAI,uDAAuD,OAAO,YAAY,GAAG;AAAA,EAC3F;AAEA,qBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AACD,cAAY;AACd;AAEA,SAAS,2BAAiC;AAExC,QAAM,MAAM;AACZ,QAAM,eAAiC,IAAI,uBAAuB,IAAI;AACtE,MAAI,CAAC,IAAI,qBAAqB;AAC5B,QAAI,sBAAsB;AAAA,EAC5B;AAGA,MAAI,QAAQ,SAAS,YAAY,SAAiB,QAAa,QAAiB;AAC9E,QAAI,YAAY,sBAAsB,YAAY,mBAAmB;AACnE,UAAI,OAAO;AAET,gBAAQ,IAAI,uDAAuD,OAAO,GAAG;AAAA,MAC/E;AACA,YAAM,OAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAC5D,aAAO,wBAAwB,IAAI;AAAA,IACrC;AACA,WAAO,aAAa,KAAK,MAAM,SAAS,QAAQ,MAAM;AAAA,EACxD;AACF;AAEA,SAAS,wBAAwB,MAA4C;AAC3E,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,UAAU,EAAE,OAAO,MAAM;AAE/B,QAAM,WAAW,KAAK,KAAK,OAAO;AAAA,IAChC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,UAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAAC,QAA0C,kBAAkB;AAC7D,MAAI,OAAO;AAET,YAAQ,IAAI,oCAAoC;AAAA,EAClD;AACA,SAAO;AACT;AAEA,SAAS,wBAAwB,UAA4B;AAC3D,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,UAAM,MAAM,UAAU,yBAAyB;AAC/C,QAAI,IAAI,WAAW,IAAI,QAAQ,WAAW,QAAQ,GAAG;AACnD,aAAO;AAAA,IACT;AAEA,YAAQ,KAAK,kDAAkD,QAAQ,WAAW,IAAI,WAAW,SAAS,EAAE;AAC5G,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxHA,YAAY;AACZ,uBAAuB;","names":[]}
package/dist/test.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
1
2
  import {
2
3
  getEventLog,
3
4
  loadCanaryConfig,
@@ -6,8 +7,8 @@ import {
6
7
  setEventLogPath,
7
8
  wrapExpect,
8
9
  wrapPage
9
- } from "./chunk-TO66FC4R.js";
10
- import "./chunk-DGUM43GV.js";
10
+ } from "./chunk-ILEPYWZX.js";
11
+ import "./chunk-VLFUCAPZ.js";
11
12
 
12
13
  // src/test.ts
13
14
  import * as playwright from "@playwright/test";
package/dist/test.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/test.ts","../src/runner/wrapper.ts"],"sourcesContent":["import * as playwright from \"@playwright/test\";\nimport { loadCanaryConfig } from \"./runner/config\";\nimport { getEventLog, markPatched, recordHealingEvent, setEventLogPath } from \"./runner/state\";\nimport { wrapPlaywright, type PlaywrightExports } from \"./runner/wrapper\";\n\nconst config = loadCanaryConfig();\nsetEventLogPath(config.eventLogPath);\ngetEventLog();\n\nconst patched = wrapPlaywright(playwright as unknown as PlaywrightExports, { debug: !!config.debug });\nmarkPatched();\n\nrecordHealingEvent({\n kind: \"unknown\",\n action: \"module_wrap_ready\",\n healed: false,\n});\n\nconst {\n test,\n expect,\n chromium,\n firefox,\n webkit,\n devices,\n request,\n selectors,\n defineConfig,\n} = patched as typeof import(\"@playwright/test\");\n\nexport { test, expect, chromium, firefox, webkit, devices, request, selectors, defineConfig };\nexport type * from \"@playwright/test\";\n","/**\n * Direct Playwright wrapper for the `canary/test` import path.\n *\n * Wraps `test`, `expect`, and the `page` fixture with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport { recordHealingEvent } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n type WrapOptions,\n} from \"./healing-helpers\";\n\nexport type { PlaywrightExports, WrapOptions };\n\nexport function wrapPlaywright(real: PlaywrightExports, options: WrapOptions): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const patchedTest = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: patchedTest,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (options.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright surface wrapped`);\n }\n return patched;\n}\n"],"mappings":";;;;;;;;;;;;AAAA,YAAY,gBAAgB;;;ACiBrB,SAAS,eAAe,MAAyB,SAAyC;AAC/F,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,cAAc,KAAK,KAAK,OAAO;AAAA,IACnC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAMA,WAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAACA,SAA0C,kBAAkB;AAC7D,MAAI,QAAQ,OAAO;AAEjB,YAAQ,IAAI,4CAA4C;AAAA,EAC1D;AACA,SAAOA;AACT;;;ADvCA,IAAM,SAAS,iBAAiB;AAChC,gBAAgB,OAAO,YAAY;AACnC,YAAY;AAEZ,IAAM,UAAU,eAAe,YAA4C,EAAE,OAAO,CAAC,CAAC,OAAO,MAAM,CAAC;AACpG,YAAY;AAEZ,mBAAmB;AAAA,EACjB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AACV,CAAC;AAED,IAAM;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAAI;","names":["patched"]}
1
+ {"version":3,"sources":["../src/test.ts","../src/runner/wrapper.ts"],"sourcesContent":["import * as playwright from \"@playwright/test\";\nimport { loadCanaryConfig } from \"./runner/config\";\nimport { getEventLog, markPatched, recordHealingEvent, setEventLogPath } from \"./runner/state\";\nimport { wrapPlaywright, type PlaywrightExports } from \"./runner/wrapper\";\n\nconst config = loadCanaryConfig();\nsetEventLogPath(config.eventLogPath);\ngetEventLog();\n\nconst patched = wrapPlaywright(playwright as unknown as PlaywrightExports, { debug: !!config.debug });\nmarkPatched();\n\nrecordHealingEvent({\n kind: \"unknown\",\n action: \"module_wrap_ready\",\n healed: false,\n});\n\nconst {\n test,\n expect,\n chromium,\n firefox,\n webkit,\n devices,\n request,\n selectors,\n defineConfig,\n} = patched as typeof import(\"@playwright/test\");\n\nexport { test, expect, chromium, firefox, webkit, devices, request, selectors, defineConfig };\nexport type * from \"@playwright/test\";\n","/**\n * Direct Playwright wrapper for the `canary/test` import path.\n *\n * Wraps `test`, `expect`, and the `page` fixture with healing-aware proxies.\n * Delegates all proxy/healing logic to `healing-helpers.ts`.\n */\n\nimport { recordHealingEvent } from \"./state\";\nimport {\n wrapExpect,\n wrapPage,\n type PlaywrightExports,\n type WrapOptions,\n} from \"./healing-helpers\";\n\nexport type { PlaywrightExports, WrapOptions };\n\nexport function wrapPlaywright(real: PlaywrightExports, options: WrapOptions): PlaywrightExports {\n if ((real as { __canaryPatched?: boolean }).__canaryPatched) return real;\n\n const patchedTest = real.test.extend({\n page: async ({ page }, use) => {\n recordHealingEvent({\n kind: \"page\",\n action: \"fixture_initialized\",\n healed: false,\n });\n const wrappedPage = wrapPage(page, options);\n await use(wrappedPage);\n },\n });\n\n const patched: PlaywrightExports = {\n ...real,\n test: patchedTest,\n expect: wrapExpect(real.expect, options),\n };\n\n (patched as { __canaryPatched?: boolean }).__canaryPatched = true;\n if (options.debug) {\n // eslint-disable-next-line no-console\n console.log(`[canary][debug] playwright surface wrapped`);\n }\n return patched;\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,YAAY,gBAAgB;;;ACiBrB,SAAS,eAAe,MAAyB,SAAyC;AAC/F,MAAK,KAAuC,gBAAiB,QAAO;AAEpE,QAAM,cAAc,KAAK,KAAK,OAAO;AAAA,IACnC,MAAM,OAAO,EAAE,KAAK,GAAG,QAAQ;AAC7B,yBAAmB;AAAA,QACjB,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV,CAAC;AACD,YAAM,cAAc,SAAS,MAAM,OAAO;AAC1C,YAAM,IAAI,WAAW;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAMA,WAA6B;AAAA,IACjC,GAAG;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,WAAW,KAAK,QAAQ,OAAO;AAAA,EACzC;AAEA,EAACA,SAA0C,kBAAkB;AAC7D,MAAI,QAAQ,OAAO;AAEjB,YAAQ,IAAI,4CAA4C;AAAA,EAC1D;AACA,SAAOA;AACT;;;ADvCA,IAAM,SAAS,iBAAiB;AAChC,gBAAgB,OAAO,YAAY;AACnC,YAAY;AAEZ,IAAM,UAAU,eAAe,YAA4C,EAAE,OAAO,CAAC,CAAC,OAAO,MAAM,CAAC;AACpG,YAAY;AAEZ,mBAAmB;AAAA,EACjB,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,QAAQ;AACV,CAAC;AAED,IAAM;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,IAAI;","names":["patched"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canaryai/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -41,11 +41,11 @@
41
41
  "@ai-sdk/anthropic": "^2.0.23",
42
42
  "@ai-sdk/azure": "^2.0.68",
43
43
  "@ai-sdk/openai": "^2.0.42",
44
+ "@chatsdet/browser-core": "workspace:*",
44
45
  "@modelcontextprotocol/sdk": "^1.0.3",
45
46
  "ai": "^5.0.60",
46
47
  "dotenv": "^17.2.3",
47
48
  "eventsource-parser": "^3.0.0",
48
- "@chatsdet/browser-core": "workspace:*",
49
49
  "zod": "^4.1.12"
50
50
  },
51
51
  "devDependencies": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/feature-flag.ts"],"sourcesContent":["/**\n * CLI Feature Flag Management\n *\n * Allows superadmins to manage feature flags via the CLI.\n */\n\nimport process from \"node:process\";\nimport { resolveConfig, getArgValue, hasFlag } from \"./auth.js\";\nimport {\n type LifecycleStage,\n toLifecycleLabel,\n parseLifecycleStage,\n apiRequest,\n fetchList,\n} from \"./cli-helpers.js\";\n\ntype FeatureFlag = {\n id: string;\n name: string;\n description: string | null;\n lifecycleStage: LifecycleStage;\n finalValue: boolean | null;\n createdAt: string;\n organizations?: Array<{\n gateId: string;\n orgId: string;\n orgName: string;\n createdAt: string;\n }>;\n};\n\ntype FlagApiResponse = {\n ok: boolean;\n error?: string;\n flag?: FeatureFlag;\n};\n\nfunction fetchFlags(apiUrl: string, token: string): Promise<FeatureFlag[]> {\n return fetchList<FeatureFlag>(apiUrl, token, \"/superadmin/feature-flags\", \"flags\");\n}\n\nasync function handleList(argv: string[], apiUrl: string, token: string): Promise<void> {\n const jsonOutput = hasFlag(argv, \"--json\");\n const flags = await fetchFlags(apiUrl, token);\n\n if (jsonOutput) {\n console.log(JSON.stringify(flags, null, 2));\n return;\n }\n\n if (flags.length === 0) {\n console.log(\"No feature flags found.\");\n return;\n }\n\n for (const flag of flags) {\n const orgCount = flag.organizations?.length ?? 0;\n const lifecycle = `lifecycle=${toLifecycleLabel(flag.lifecycleStage)}`;\n const finalValue =\n flag.lifecycleStage === \"active\" ? \"\" : ` final=${flag.finalValue === true ? \"true\" : \"false\"}`;\n console.log(` ${flag.name} ${flag.description ?? \"\"} (${lifecycle}${finalValue}) [orgs=${orgCount}]`);\n }\n}\n\nasync function handleCreate(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag create <name> [--description <text>]\");\n process.exit(1);\n }\n\n const description = getArgValue(argv, \"--description\") ?? null;\n const result = await apiRequest<FlagApiResponse>(apiUrl, token, \"POST\", \"/superadmin/feature-flags\", {\n name,\n description,\n });\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Created feature flag: ${name}`);\n}\n\nasync function handleDelete(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag delete <name>\");\n process.exit(1);\n }\n\n const result = await apiRequest<FlagApiResponse>(\n apiUrl, token, \"DELETE\", `/superadmin/feature-flags/${encodeURIComponent(name)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Deleted feature flag: ${name}`);\n}\n\nasync function handleEnable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag enable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest<FlagApiResponse>(\n apiUrl, token, \"POST\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Enabled ${name} for org ${orgId}`);\n}\n\nasync function handleDisable(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n const orgId = getArgValue(argv, \"--org\");\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n if (!orgId) {\n console.error(\"Error: Missing --org <orgId>.\");\n console.error(\"Usage: canary feature-flag disable <name> --org <orgId>\");\n process.exit(1);\n }\n\n const result = await apiRequest<FlagApiResponse>(\n apiUrl, token, \"DELETE\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/organizations/${encodeURIComponent(orgId)}`\n );\n\n if (!result.ok) {\n console.error(`Error: ${result.error}`);\n process.exit(1);\n }\n\n console.log(`Disabled ${name} for org ${orgId}`);\n}\n\nasync function handleLifecycle(argv: string[], apiUrl: string, token: string): Promise<void> {\n const name = argv[0];\n\n if (!name || name.startsWith(\"--\")) {\n console.error(\"Error: Missing flag name.\");\n console.error(\"Usage: canary feature-flag lifecycle <name> --stage <active|deprecated|ready_for_cleanup> [--final-value true|false]\");\n process.exit(1);\n }\n\n const stage = parseLifecycleStage(argv);\n const rawFinalValue = getArgValue(argv, \"--final-value\");\n const clearFinalValue = hasFlag(argv, \"--clear-final-value\");\n\n if (rawFinalValue !== undefined && clearFinalValue) {\n console.error(\"Error: use either --final-value or --clear-final-value, not both.\");\n process.exit(1);\n }\n\n let finalValue: boolean | undefined = undefined;\n\n if (stage === \"active\") {\n if (rawFinalValue !== undefined) {\n console.error(\"Error: active stage does not accept --final-value.\");\n process.exit(1);\n }\n finalValue = undefined;\n } else {\n if (clearFinalValue) {\n console.error(\"Error: --clear-final-value can only be used with --stage active.\");\n process.exit(1);\n }\n if (!rawFinalValue) {\n console.error(\"Error: --final-value true|false is required for deprecated or ready_for_cleanup.\");\n process.exit(1);\n }\n if (rawFinalValue !== \"true\" && rawFinalValue !== \"false\") {\n console.error(\"Error: --final-value must be true or false.\");\n process.exit(1);\n }\n finalValue = rawFinalValue === \"true\";\n }\n\n const result = await apiRequest<FlagApiResponse>(\n apiUrl, token, \"POST\",\n `/superadmin/feature-flags/${encodeURIComponent(name)}/lifecycle`,\n { stage, finalValue }\n );\n\n if (!result.ok || !result.flag) {\n console.error(`Error: ${result.error ?? \"Failed to update lifecycle\"}`);\n process.exit(1);\n }\n\n const final =\n result.flag.lifecycleStage === \"active\"\n ? \"(none)\"\n : result.flag.finalValue === true\n ? \"true\"\n : \"false\";\n console.log(`Updated lifecycle for ${name}: stage=${toLifecycleLabel(result.flag.lifecycleStage)}, final=${final}`);\n}\n\nfunction printFeatureFlagHelp(): void {\n console.log(\n [\n \"Usage: canary feature-flag <sub-command> [options]\",\n \"\",\n \"Sub-commands:\",\n \" list List all feature flags\",\n \" create <name> [--description <text>] Create a new flag\",\n \" delete <name> Delete a flag and all its gates\",\n \" enable <name> --org <orgId> Enable a flag for an organization\",\n \" disable <name> --org <orgId> Disable a flag for an organization\",\n \" lifecycle <name> --stage <stage> [--final-value true|false]\",\n \" Mark lifecycle + final value\",\n \"\",\n \"Stages: active, deprecated, ready_for_cleanup\",\n \"\",\n \"Options:\",\n \" --final-value <true|false> Final value for deprecated/ready_for_cleanup\",\n \" --clear-final-value Clear final value (only valid with --stage active)\",\n \" --env <env> Target environment (prod, dev, local)\",\n \" --json Output as JSON (list only)\",\n \" --api-url <url> API URL override (takes precedence over --env)\",\n \" --token <key> API token override\",\n ].join(\"\\n\")\n );\n}\n\nexport async function runFeatureFlag(argv: string[]): Promise<void> {\n const [subCommand, ...rest] = argv;\n\n if (!subCommand || subCommand === \"help\" || hasFlag(argv, \"--help\", \"-h\")) {\n printFeatureFlagHelp();\n return;\n }\n\n const { apiUrl, token } = await resolveConfig(argv);\n\n switch (subCommand) {\n case \"list\":\n await handleList(rest, apiUrl, token);\n break;\n case \"create\":\n await handleCreate(rest, apiUrl, token);\n break;\n case \"delete\":\n await handleDelete(rest, apiUrl, token);\n break;\n case \"enable\":\n await handleEnable(rest, apiUrl, token);\n break;\n case \"disable\":\n await handleDisable(rest, apiUrl, token);\n break;\n case \"lifecycle\":\n await handleLifecycle(rest, apiUrl, token);\n break;\n default:\n console.error(`Unknown sub-command: ${subCommand}`);\n printFeatureFlagHelp();\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,384 +0,0 @@
1
- import {
2
- connectTunnel,
3
- createLocalRun,
4
- createTunnel
5
- } from "./chunk-HOYYXZPV.js";
6
- import {
7
- LocalBrowserHost
8
- } from "./chunk-UEOXNF5X.js";
9
- import {
10
- readStoredToken
11
- } from "./chunk-7R4YFGP6.js";
12
- import "./chunk-DGUM43GV.js";
13
-
14
- // src/mcp.ts
15
- import process from "process";
16
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
17
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
19
- import { createParser } from "eventsource-parser";
20
- var browserSessions = /* @__PURE__ */ new Map();
21
- var DEFAULT_API_URL = "https://api.trycanary.ai";
22
- function resolveApiUrl(input) {
23
- return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;
24
- }
25
- async function resolveToken() {
26
- const token = process.env.CANARY_API_TOKEN ?? await readStoredToken();
27
- if (!token) {
28
- throw new Error("Missing token. Run `canary login` first or set CANARY_API_TOKEN.");
29
- }
30
- return token;
31
- }
32
- function toolText(text) {
33
- return { content: [{ type: "text", text }] };
34
- }
35
- function toolJson(data) {
36
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
37
- }
38
- async function runMcp(argv) {
39
- const server = new Server(
40
- { name: "canary-cli", version: "0.1.0" },
41
- { capabilities: { tools: {} } }
42
- );
43
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
44
- tools: [
45
- {
46
- name: "local_run_tests",
47
- description: "Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.",
48
- inputSchema: {
49
- type: "object",
50
- properties: {
51
- port: { type: "number" },
52
- instructions: { type: "string" },
53
- title: { type: "string" }
54
- },
55
- required: ["port", "instructions"]
56
- }
57
- },
58
- {
59
- name: "local_wait_for_results",
60
- description: "Wait for a local test run to complete. Streams until completion and returns a compact report.",
61
- inputSchema: {
62
- type: "object",
63
- properties: {
64
- runId: { type: "string" }
65
- },
66
- required: ["runId"]
67
- }
68
- },
69
- {
70
- name: "local_browser_start",
71
- 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.",
72
- inputSchema: {
73
- type: "object",
74
- properties: {
75
- mode: {
76
- type: "string",
77
- enum: ["playwright", "cdp"],
78
- description: "Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome"
79
- },
80
- cdpUrl: {
81
- type: "string",
82
- description: "CDP endpoint URL when mode is 'cdp' (e.g. http://localhost:9222)"
83
- },
84
- headless: {
85
- type: "boolean",
86
- description: "Run browser headless (default: true for playwright mode)"
87
- },
88
- storageStatePath: {
89
- type: "string",
90
- description: "Path to Playwright storage state JSON for pre-authenticated sessions"
91
- },
92
- instructions: {
93
- type: "string",
94
- description: "Instructions for the cloud agent on what to test"
95
- }
96
- }
97
- }
98
- },
99
- {
100
- name: "local_browser_status",
101
- description: "Check the status of a local browser session.",
102
- inputSchema: {
103
- type: "object",
104
- properties: {
105
- sessionId: { type: "string" }
106
- },
107
- required: ["sessionId"]
108
- }
109
- },
110
- {
111
- name: "local_browser_stop",
112
- description: "Stop a local browser session and close the browser.",
113
- inputSchema: {
114
- type: "object",
115
- properties: {
116
- sessionId: { type: "string" }
117
- },
118
- required: ["sessionId"]
119
- }
120
- },
121
- {
122
- name: "local_browser_list",
123
- description: "List all active local browser sessions.",
124
- inputSchema: {
125
- type: "object",
126
- properties: {}
127
- }
128
- },
129
- {
130
- name: "local_browser_run",
131
- description: "Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.",
132
- inputSchema: {
133
- type: "object",
134
- properties: {
135
- sessionId: {
136
- type: "string",
137
- description: "The session ID from local_browser_start"
138
- },
139
- instructions: {
140
- type: "string",
141
- description: "Instructions for the cloud agent on what to test"
142
- },
143
- startUrl: {
144
- type: "string",
145
- description: "Optional URL to navigate to before starting"
146
- }
147
- },
148
- required: ["sessionId", "instructions"]
149
- }
150
- }
151
- ]
152
- }));
153
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
154
- const token = await resolveToken();
155
- const tool = req.params.name;
156
- if (tool === "local_run_tests") {
157
- const input = req.params.arguments;
158
- const apiUrl = resolveApiUrl();
159
- const tunnel = await createTunnel({ apiUrl, token, port: input.port });
160
- connectTunnel({
161
- apiUrl,
162
- tunnelId: tunnel.tunnelId,
163
- token: tunnel.token,
164
- port: input.port
165
- });
166
- const tunnelUrl = tunnel.publicUrl;
167
- const run = await createLocalRun({
168
- apiUrl,
169
- token,
170
- title: input.title ?? "Local MCP run",
171
- featureSpec: input.instructions,
172
- startUrl: void 0,
173
- tunnelUrl
174
- });
175
- return toolJson({
176
- runId: run.runId,
177
- watchUrl: run.watchUrl,
178
- tunnelUrl,
179
- note: "Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion."
180
- });
181
- }
182
- if (tool === "local_wait_for_results") {
183
- const input = req.params.arguments;
184
- const apiUrl = resolveApiUrl();
185
- const report = await waitForResult({ apiUrl, token, runId: input.runId });
186
- return toolJson(report);
187
- }
188
- if (tool === "local_browser_start") {
189
- const input = req.params.arguments;
190
- const apiUrl = resolveApiUrl();
191
- const mode = input.mode ?? "playwright";
192
- const sessionResponse = await fetch(`${apiUrl}/local-browser/sessions`, {
193
- method: "POST",
194
- headers: {
195
- "Content-Type": "application/json",
196
- Authorization: `Bearer ${token}`
197
- },
198
- body: JSON.stringify({
199
- browserMode: mode,
200
- instructions: input.instructions ?? null
201
- })
202
- });
203
- if (!sessionResponse.ok) {
204
- const text = await sessionResponse.text();
205
- return toolJson({ ok: false, error: `Failed to create session: ${text}` });
206
- }
207
- const session = await sessionResponse.json();
208
- const host = new LocalBrowserHost({
209
- apiUrl,
210
- wsToken: session.wsToken,
211
- sessionId: session.sessionId,
212
- browserMode: mode,
213
- cdpUrl: input.cdpUrl,
214
- headless: input.headless ?? true,
215
- storageStatePath: input.storageStatePath,
216
- onLog: (level, message) => {
217
- if (level === "error") {
218
- console.error(`[LocalBrowser] ${message}`);
219
- }
220
- }
221
- });
222
- host.start().catch((err) => {
223
- console.error("Failed to start local browser:", err);
224
- browserSessions.delete(session.sessionId);
225
- });
226
- browserSessions.set(session.sessionId, {
227
- sessionId: session.sessionId,
228
- host,
229
- startedAt: Date.now(),
230
- mode
231
- });
232
- return toolJson({
233
- ok: true,
234
- sessionId: session.sessionId,
235
- mode,
236
- expiresAt: session.expiresAt,
237
- note: "Browser session started. The cloud agent can now control this browser. Use local_browser_stop to end the session."
238
- });
239
- }
240
- if (tool === "local_browser_status") {
241
- const input = req.params.arguments;
242
- const session = browserSessions.get(input.sessionId);
243
- if (!session) {
244
- return toolJson({ ok: false, error: "Session not found", sessionId: input.sessionId });
245
- }
246
- return toolJson({
247
- ok: true,
248
- sessionId: session.sessionId,
249
- mode: session.mode,
250
- startedAt: new Date(session.startedAt).toISOString(),
251
- uptimeMs: Date.now() - session.startedAt
252
- });
253
- }
254
- if (tool === "local_browser_stop") {
255
- const input = req.params.arguments;
256
- const session = browserSessions.get(input.sessionId);
257
- if (!session) {
258
- return toolJson({ ok: false, error: "Session not found", sessionId: input.sessionId });
259
- }
260
- await session.host.stop();
261
- browserSessions.delete(input.sessionId);
262
- return toolJson({
263
- ok: true,
264
- sessionId: input.sessionId,
265
- note: "Browser session stopped and browser closed."
266
- });
267
- }
268
- if (tool === "local_browser_list") {
269
- const sessions = Array.from(browserSessions.values()).map((s) => ({
270
- sessionId: s.sessionId,
271
- mode: s.mode,
272
- startedAt: new Date(s.startedAt).toISOString(),
273
- uptimeMs: Date.now() - s.startedAt
274
- }));
275
- return toolJson({
276
- ok: true,
277
- count: sessions.length,
278
- sessions
279
- });
280
- }
281
- if (tool === "local_browser_run") {
282
- const input = req.params.arguments;
283
- const apiUrl = resolveApiUrl();
284
- const session = browserSessions.get(input.sessionId);
285
- if (!session) {
286
- return toolJson({
287
- ok: false,
288
- error: "Session not found locally. Make sure you started it with local_browser_start.",
289
- sessionId: input.sessionId
290
- });
291
- }
292
- const response = await fetch(`${apiUrl}/local-browser/sessions/${input.sessionId}/run`, {
293
- method: "POST",
294
- headers: {
295
- "Content-Type": "application/json",
296
- Authorization: `Bearer ${token}`
297
- },
298
- body: JSON.stringify({
299
- instructions: input.instructions,
300
- startUrl: input.startUrl ?? null
301
- })
302
- });
303
- if (!response.ok) {
304
- const text = await response.text();
305
- return toolJson({ ok: false, error: `Failed to start run: ${text}` });
306
- }
307
- const result = await response.json();
308
- return toolJson({
309
- ok: true,
310
- jobId: result.jobId,
311
- sessionId: result.sessionId,
312
- note: "Test run started. The cloud agent is now controlling your local browser. You can watch the browser to see the test in action."
313
- });
314
- }
315
- return toolText(`Unknown tool: ${tool}`);
316
- });
317
- const transport = new StdioServerTransport();
318
- await server.connect(transport);
319
- return new Promise(() => void 0);
320
- }
321
- async function waitForResult(input) {
322
- await streamUntilComplete(input);
323
- const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {
324
- credentials: "include",
325
- headers: { authorization: `Bearer ${input.token}` }
326
- });
327
- const data = await response.json();
328
- const run = data?.data?.run ?? data?.run ?? data?.data;
329
- const summary = run?.summaryJson;
330
- return formatReport({ run, summary });
331
- }
332
- async function streamUntilComplete(input) {
333
- const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {
334
- headers: { authorization: `Bearer ${input.token}` }
335
- });
336
- if (!response.body) return;
337
- const reader = response.body.getReader();
338
- const decoder = new TextDecoder();
339
- const parser = createParser({
340
- onEvent: (event) => {
341
- if (event.event === "status") {
342
- try {
343
- const payload = JSON.parse(event.data);
344
- if (payload?.status === "completed" || payload?.status === "failed") {
345
- reader.cancel().catch(() => void 0);
346
- }
347
- } catch {
348
- }
349
- }
350
- if (event.event === "complete" || event.event === "error") {
351
- reader.cancel().catch(() => void 0);
352
- }
353
- }
354
- });
355
- while (true) {
356
- const { done, value } = await reader.read();
357
- if (done) break;
358
- parser.feed(decoder.decode(value, { stream: true }));
359
- }
360
- }
361
- function formatReport(input) {
362
- if (!input.summary) {
363
- return {
364
- runId: input.run?.id,
365
- status: input.run?.status ?? "unknown",
366
- summary: "No final report available."
367
- };
368
- }
369
- const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];
370
- const status = input.summary.status ?? input.run?.status ?? "unknown";
371
- const issues = status === "issues_found" ? input.summary.notes ? [input.summary.notes] : ["Issues reported."] : [];
372
- return {
373
- runId: input.run?.id,
374
- status,
375
- summary: input.summary.summary ?? "Run completed.",
376
- testedItems: tested,
377
- issues,
378
- notes: input.summary.notes ?? null
379
- };
380
- }
381
- export {
382
- runMcp
383
- };
384
- //# sourceMappingURL=mcp-AD67OLQM.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["import process from \"node:process\";\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 { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { readStoredToken } from \"./auth\";\nimport { createLocalRun } from \"./local-run\";\nimport { connectTunnel, createTunnel } from \"./tunnel\";\nimport { LocalBrowserHost } from \"./local-browser/host\";\nimport type { LocalBrowserMode } from \"./local-browser/protocol\";\n\ntype RunLocalInput = {\n port: number;\n instructions: string;\n title?: string;\n};\n\ntype WaitForRunInput = {\n runId: string;\n};\n\ntype LocalBrowserStartInput = {\n mode?: LocalBrowserMode;\n cdpUrl?: string;\n headless?: boolean;\n storageStatePath?: string;\n instructions?: string;\n};\n\ntype LocalBrowserStopInput = {\n sessionId: string;\n};\n\ntype LocalBrowserStatusInput = {\n sessionId: string;\n};\n\ninterface BrowserSession {\n sessionId: string;\n host: LocalBrowserHost;\n startedAt: number;\n mode: LocalBrowserMode;\n}\n\n// Global browser sessions managed by MCP\nconst browserSessions = new Map<string, BrowserSession>();\n\nconst DEFAULT_API_URL = \"https://api.trycanary.ai\";\n\nfunction resolveApiUrl(input?: string): string {\n return input ?? process.env.CANARY_API_URL ?? DEFAULT_API_URL;\n}\n\nasync function resolveToken(): Promise<string> {\n const token = process.env.CANARY_API_TOKEN ?? (await readStoredToken());\n if (!token) {\n throw new Error(\"Missing token. Run `canary login` first or set CANARY_API_TOKEN.\");\n }\n return token;\n}\n\nfunction toolText(text: string) {\n return { content: [{ type: \"text\", text }] };\n}\n\nfunction toolJson(data: unknown) {\n return { content: [{ type: \"text\", text: JSON.stringify(data, null, 2) }] };\n}\n\nexport async function runMcp(argv: string[]) {\n const server = new Server(\n { name: \"canary-cli\", version: \"0.1.0\" },\n { capabilities: { tools: {} } }\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: [\n {\n name: \"local_run_tests\",\n description:\n \"Start an async local test run. A tunnel is opened automatically. Returns runId and watchUrl.\",\n inputSchema: {\n type: \"object\",\n properties: {\n port: { type: \"number\" },\n instructions: { type: \"string\" },\n title: { type: \"string\" },\n },\n required: [\"port\", \"instructions\"],\n },\n },\n {\n name: \"local_wait_for_results\",\n description:\n \"Wait for a local test run to complete. Streams until completion and returns a compact report.\",\n inputSchema: {\n type: \"object\",\n properties: {\n runId: { type: \"string\" },\n },\n required: [\"runId\"],\n },\n },\n {\n name: \"local_browser_start\",\n description:\n \"Start a local browser session that connects to the cloud agent. The cloud agent can then control this browser to test local applications. Returns sessionId for tracking.\",\n inputSchema: {\n type: \"object\",\n properties: {\n mode: {\n type: \"string\",\n enum: [\"playwright\", \"cdp\"],\n description: \"Browser mode: 'playwright' for fresh browser, 'cdp' to connect to existing Chrome\",\n },\n cdpUrl: {\n type: \"string\",\n description: \"CDP endpoint URL when mode is 'cdp' (e.g. http://localhost:9222)\",\n },\n headless: {\n type: \"boolean\",\n description: \"Run browser headless (default: true for playwright mode)\",\n },\n storageStatePath: {\n type: \"string\",\n description: \"Path to Playwright storage state JSON for pre-authenticated sessions\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n },\n },\n },\n {\n name: \"local_browser_status\",\n description: \"Check the status of a local browser session.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_stop\",\n description: \"Stop a local browser session and close the browser.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: { type: \"string\" },\n },\n required: [\"sessionId\"],\n },\n },\n {\n name: \"local_browser_list\",\n description: \"List all active local browser sessions.\",\n inputSchema: {\n type: \"object\",\n properties: {},\n },\n },\n {\n name: \"local_browser_run\",\n description:\n \"Start a test run on an active local browser session. The cloud agent will control the local browser according to the instructions.\",\n inputSchema: {\n type: \"object\",\n properties: {\n sessionId: {\n type: \"string\",\n description: \"The session ID from local_browser_start\",\n },\n instructions: {\n type: \"string\",\n description: \"Instructions for the cloud agent on what to test\",\n },\n startUrl: {\n type: \"string\",\n description: \"Optional URL to navigate to before starting\",\n },\n },\n required: [\"sessionId\", \"instructions\"],\n },\n },\n ],\n }));\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const token = await resolveToken();\n const tool = req.params.name;\n\n if (tool === \"local_run_tests\") {\n const input = req.params.arguments as RunLocalInput;\n const apiUrl = resolveApiUrl();\n const tunnel = await createTunnel({ apiUrl, token, port: input.port });\n connectTunnel({\n apiUrl,\n tunnelId: tunnel.tunnelId,\n token: tunnel.token,\n port: input.port,\n });\n const tunnelUrl = tunnel.publicUrl;\n\n const run = await createLocalRun({\n apiUrl,\n token,\n title: input.title ?? \"Local MCP run\",\n featureSpec: input.instructions,\n startUrl: undefined,\n tunnelUrl,\n });\n\n return toolJson({\n runId: run.runId,\n watchUrl: run.watchUrl,\n tunnelUrl,\n note:\n \"Testing is asynchronous. Use local_wait_for_results with the runId to wait for completion.\",\n });\n }\n\n if (tool === \"local_wait_for_results\") {\n const input = req.params.arguments as WaitForRunInput;\n const apiUrl = resolveApiUrl();\n const report = await waitForResult({ apiUrl, token, runId: input.runId });\n return toolJson(report);\n }\n\n if (tool === \"local_browser_start\") {\n const input = req.params.arguments as LocalBrowserStartInput;\n const apiUrl = resolveApiUrl();\n const mode = input.mode ?? \"playwright\";\n\n // Create session with cloud API\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 // Start the local browser host\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 // Silent logging for MCP context\n if (level === \"error\") {\n console.error(`[LocalBrowser] ${message}`);\n }\n },\n });\n\n // Start in background\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 = req.params.arguments 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 = req.params.arguments 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 = req.params.arguments as {\n sessionId: string;\n instructions: string;\n startUrl?: string;\n };\n const apiUrl = resolveApiUrl();\n\n // Verify session is active locally\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 // Call the API to start the run\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 toolText(`Unknown tool: ${tool}`);\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n return new Promise<void>(() => undefined);\n}\n\nasync function waitForResult(input: { apiUrl: string; token: string; runId: string }) {\n await streamUntilComplete(input);\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}`, {\n credentials: \"include\",\n headers: { authorization: `Bearer ${input.token}` },\n });\n const data = await response.json();\n const run = data?.data?.run ?? data?.run ?? data?.data;\n const summary = run?.summaryJson;\n return formatReport({ run, summary });\n}\n\nasync function streamUntilComplete(input: { apiUrl: string; token: string; runId: string }) {\n const response = await fetch(`${input.apiUrl}/local-tests/runs/${input.runId}/stream`, {\n headers: { authorization: `Bearer ${input.token}` },\n });\n\n if (!response.body) return;\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (event.event === \"status\") {\n try {\n const payload = JSON.parse(event.data);\n if (payload?.status === \"completed\" || payload?.status === \"failed\") {\n reader.cancel().catch(() => undefined);\n }\n } catch {}\n }\n if (event.event === \"complete\" || event.event === \"error\") {\n reader.cancel().catch(() => undefined);\n }\n },\n });\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n}\n\nfunction formatReport(input: { run: any; summary: any }) {\n if (!input.summary) {\n return {\n runId: input.run?.id,\n status: input.run?.status ?? \"unknown\",\n summary: \"No final report available.\",\n };\n }\n\n const tested = Array.isArray(input.summary.testedItems) ? input.summary.testedItems : [];\n const status = input.summary.status ?? input.run?.status ?? \"unknown\";\n const issues =\n status === \"issues_found\"\n ? (input.summary.notes ? [input.summary.notes] : [\"Issues reported.\"])\n : [];\n\n return {\n runId: input.run?.id,\n status,\n summary: input.summary.summary ?? \"Run completed.\",\n testedItems: tested,\n issues,\n notes: input.summary.notes ?? null,\n };\n}\n"],"mappings":"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","names":[]}