@runcofactor/cli 0.10.0 → 0.13.0

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 (148) hide show
  1. package/dist/{api-catalog-GFGCLVS4.js → api-catalog-7HLEN7C2.js} +5 -4
  2. package/dist/{api-catalog-GFGCLVS4.js.map → api-catalog-7HLEN7C2.js.map} +1 -1
  3. package/dist/{attachment-SJC7D7FY.js → attachment-OVNQD6Z2.js} +5 -4
  4. package/dist/{attachment-SJC7D7FY.js.map → attachment-OVNQD6Z2.js.map} +1 -1
  5. package/dist/{auth-EN6NQCJY.js → auth-VUHL74FH.js} +3 -2
  6. package/dist/{chunk-IMPZAX6L.js → chunk-5GD47G2D.js} +37 -1
  7. package/dist/{chunk-IMPZAX6L.js.map → chunk-5GD47G2D.js.map} +1 -1
  8. package/dist/{chunk-WOHL4FTB.js → chunk-CQE46HSM.js} +5 -5
  9. package/dist/chunk-CQE46HSM.js.map +1 -0
  10. package/dist/{chunk-52IO7ACJ.js → chunk-CRHPDPRS.js} +39 -3
  11. package/dist/{chunk-52IO7ACJ.js.map → chunk-CRHPDPRS.js.map} +1 -1
  12. package/dist/{chunk-XQXVG7RA.js → chunk-EEN2TZUQ.js} +3 -3
  13. package/dist/{chunk-I4QLI3JO.js → chunk-EJ4U242G.js} +2 -2
  14. package/dist/{chunk-CLAWT7BJ.js → chunk-ENDHMYEJ.js} +3 -3
  15. package/dist/chunk-G4UYGRZR.js +2 -0
  16. package/dist/chunk-JZMJUPZP.js +92 -0
  17. package/dist/chunk-JZMJUPZP.js.map +1 -0
  18. package/dist/{chunk-GKY2KXO5.js → chunk-NL7CVG56.js} +105 -35
  19. package/dist/chunk-NL7CVG56.js.map +1 -0
  20. package/dist/{chunk-A4OFTUVV.js → chunk-PZ4LCN6K.js} +7 -4
  21. package/dist/chunk-PZ4LCN6K.js.map +1 -0
  22. package/dist/{chunk-SJJW77WN.js → chunk-QGI4UCJX.js} +69 -5
  23. package/dist/chunk-QGI4UCJX.js.map +1 -0
  24. package/dist/{chunk-4PNOV7OV.js → chunk-QQVWGWPI.js} +1023 -150
  25. package/dist/chunk-QQVWGWPI.js.map +1 -0
  26. package/dist/{chunk-N7HFALX3.js → chunk-VGTKUOYS.js} +2 -2
  27. package/dist/{chunk-B2VISN2V.js → chunk-YN763NWQ.js} +230 -66
  28. package/dist/chunk-YN763NWQ.js.map +1 -0
  29. package/dist/{chunk-QFDOWABX.js → chunk-Z5LUTUDN.js} +6 -3
  30. package/dist/chunk-Z5LUTUDN.js.map +1 -0
  31. package/dist/{chunk-Q4BO4Z5V.js → chunk-ZTOE3ND3.js} +4 -4
  32. package/dist/{credential-COLTBV6D.js → credential-LO2SEEHU.js} +24 -20
  33. package/dist/credential-LO2SEEHU.js.map +1 -0
  34. package/dist/{credential-template-EEPYFG7R.js → credential-template-ZN7W7734.js} +30 -28
  35. package/dist/credential-template-ZN7W7734.js.map +1 -0
  36. package/dist/{debug-workflow-KOVH5GOP.js → debug-workflow-R3RYYAXK.js} +5 -4
  37. package/dist/{debug-workflow-KOVH5GOP.js.map → debug-workflow-R3RYYAXK.js.map} +1 -1
  38. package/dist/{docs-3SSORHB5.js → docs-DIAFBDXQ.js} +5 -4
  39. package/dist/{docs-3SSORHB5.js.map → docs-DIAFBDXQ.js.map} +1 -1
  40. package/dist/{doctor-OTSIWV6G.js → doctor-NZPUSCRE.js} +9 -5
  41. package/dist/doctor-NZPUSCRE.js.map +1 -0
  42. package/dist/environment-RQVU4P5M.js +221 -0
  43. package/dist/environment-RQVU4P5M.js.map +1 -0
  44. package/dist/{feature-flag-T2LRZME3.js → feature-flag-XG26RURR.js} +72 -10
  45. package/dist/feature-flag-XG26RURR.js.map +1 -0
  46. package/dist/index.js +57 -48
  47. package/dist/index.js.map +1 -1
  48. package/dist/{init-TGWRECT2.js → init-EDNLKGPT.js} +5 -4
  49. package/dist/{init-TGWRECT2.js.map → init-EDNLKGPT.js.map} +1 -1
  50. package/dist/{install-DXSNWZKO.js → install-FYZ3HNZK.js} +7 -7
  51. package/dist/{issues-XIZOPX6T.js → issues-NNRVTJ4L.js} +5 -4
  52. package/dist/{issues-XIZOPX6T.js.map → issues-NNRVTJ4L.js.map} +1 -1
  53. package/dist/{knobs-NBINZIUB.js → knobs-A2BOBH22.js} +5 -4
  54. package/dist/{knobs-NBINZIUB.js.map → knobs-A2BOBH22.js.map} +1 -1
  55. package/dist/{list-3TDHK637.js → list-F33VZ4BJ.js} +5 -4
  56. package/dist/{list-3TDHK637.js.map → list-F33VZ4BJ.js.map} +1 -1
  57. package/dist/{local-HICVD6PU.js → local-DQDTM3XA.js} +7 -6
  58. package/dist/{local-HICVD6PU.js.map → local-DQDTM3XA.js.map} +1 -1
  59. package/dist/{login-UOIUXVUK.js → login-BUMVIB36.js} +5 -4
  60. package/dist/{login-UOIUXVUK.js.map → login-BUMVIB36.js.map} +1 -1
  61. package/dist/{mcp-54IJEOUA.js → mcp-B5Y5CDMQ.js} +23 -24
  62. package/dist/mcp-B5Y5CDMQ.js.map +1 -0
  63. package/dist/{property-UXLOLPTD.js → property-NJ4SRXQG.js} +76 -4
  64. package/dist/property-NJ4SRXQG.js.map +1 -0
  65. package/dist/{psql-BYUUNLGG.js → psql-VBZFCMNR.js} +3 -2
  66. package/dist/{psql-BYUUNLGG.js.map → psql-VBZFCMNR.js.map} +1 -1
  67. package/dist/{record-4TR2UIIS.js → record-WAO36O7G.js} +16 -17
  68. package/dist/record-WAO36O7G.js.map +1 -0
  69. package/dist/{redis-35VBUYW7.js → redis-DHZXHL4S.js} +3 -2
  70. package/dist/{redis-35VBUYW7.js.map → redis-DHZXHL4S.js.map} +1 -1
  71. package/dist/{release-OOLTUBUT.js → release-STJKUKTE.js} +5 -4
  72. package/dist/{release-OOLTUBUT.js.map → release-STJKUKTE.js.map} +1 -1
  73. package/dist/{sandbox-XGSUU6G7.js → sandbox-DWSGLY7B.js} +9 -8
  74. package/dist/{sandbox-XGSUU6G7.js.map → sandbox-DWSGLY7B.js.map} +1 -1
  75. package/dist/{scenarios-GWLJEGHA.js → scenarios-HCIDY725.js} +5 -4
  76. package/dist/{scenarios-GWLJEGHA.js.map → scenarios-HCIDY725.js.map} +1 -1
  77. package/dist/{session-MYFNZPRP.js → session-Y5RHU54D.js} +76 -8
  78. package/dist/session-Y5RHU54D.js.map +1 -0
  79. package/dist/{skill-KM34LDMO.js → skill-23WAHUOB.js} +217 -67
  80. package/dist/skill-23WAHUOB.js.map +1 -0
  81. package/dist/skills/debug-run/SKILL.md +33 -22
  82. package/dist/skills/debug-run/history/1.2.0.md +262 -0
  83. package/dist/skills/debug-run/history/1.3.0.md +273 -0
  84. package/dist/skills/debug-run/version.json +1 -1
  85. package/dist/skills/optimize-cache/SKILL.md +206 -0
  86. package/dist/{spec-RA3CFEKG.js → spec-LM5XHOQ5.js} +54 -25
  87. package/dist/spec-LM5XHOQ5.js.map +1 -0
  88. package/dist/{src-LB4HBTJQ.js → src-22RI5VP2.js} +19 -11
  89. package/dist/src-22RI5VP2.js.map +1 -0
  90. package/dist/src-BGDRMOKC.js +5748 -0
  91. package/dist/src-BGDRMOKC.js.map +1 -0
  92. package/dist/{src-N3TPKGTY.js → src-NCJPUAE6.js} +3 -2
  93. package/dist/{src-UVDX5UP6.js → src-Y3K7UJEF.js} +4 -2
  94. package/dist/{start-PSEQSQA3.js → start-SWY55CZX.js} +5 -4
  95. package/dist/{start-PSEQSQA3.js.map → start-SWY55CZX.js.map} +1 -1
  96. package/dist/{tag-YY6YAABQ.js → tag-YSU2XXPI.js} +6 -5
  97. package/dist/{task-IQC3YR5K.js → task-NLBKLD7D.js} +5 -4
  98. package/dist/{task-IQC3YR5K.js.map → task-NLBKLD7D.js.map} +1 -1
  99. package/dist/{test-suite-DZNJFXDW.js → test-suite-QCPWZ2LS.js} +9 -6
  100. package/dist/{test-suite-DZNJFXDW.js.map → test-suite-QCPWZ2LS.js.map} +1 -1
  101. package/dist/{trace-HXIW7U2D.js → trace-A3ZY6K26.js} +6 -4
  102. package/dist/{triage-ORG45R5W.js → triage-TT4BMDBU.js} +6 -5
  103. package/dist/triage-TT4BMDBU.js.map +1 -0
  104. package/dist/{upgrade-L2VYMP2V.js → upgrade-6ZUFETKX.js} +3 -3
  105. package/dist/{workflow-XYFH5LTP.js → workflow-2CX3ZNOQ.js} +241 -237
  106. package/dist/workflow-2CX3ZNOQ.js.map +1 -0
  107. package/dist/{workflow-cache-NQJQFWMV.js → workflow-cache-GXNE6SU2.js} +140 -7
  108. package/dist/workflow-cache-GXNE6SU2.js.map +1 -0
  109. package/dist/{workflow-debug-BGD4UIXU.js → workflow-debug-PRX3DWGD.js} +8 -7
  110. package/dist/{workflow-debug-BGD4UIXU.js.map → workflow-debug-PRX3DWGD.js.map} +1 -1
  111. package/dist/{workflow-proposals-MTUDX46O.js → workflow-proposals-BW327WGL.js} +5 -4
  112. package/dist/{workflow-proposals-MTUDX46O.js.map → workflow-proposals-BW327WGL.js.map} +1 -1
  113. package/package.json +2 -1
  114. package/dist/chunk-4PNOV7OV.js.map +0 -1
  115. package/dist/chunk-A4OFTUVV.js.map +0 -1
  116. package/dist/chunk-B2VISN2V.js.map +0 -1
  117. package/dist/chunk-GKY2KXO5.js.map +0 -1
  118. package/dist/chunk-JICSYFRJ.js +0 -46
  119. package/dist/chunk-JICSYFRJ.js.map +0 -1
  120. package/dist/chunk-QFDOWABX.js.map +0 -1
  121. package/dist/chunk-SJJW77WN.js.map +0 -1
  122. package/dist/chunk-WOHL4FTB.js.map +0 -1
  123. package/dist/credential-COLTBV6D.js.map +0 -1
  124. package/dist/credential-template-EEPYFG7R.js.map +0 -1
  125. package/dist/doctor-OTSIWV6G.js.map +0 -1
  126. package/dist/feature-flag-T2LRZME3.js.map +0 -1
  127. package/dist/mcp-54IJEOUA.js.map +0 -1
  128. package/dist/property-UXLOLPTD.js.map +0 -1
  129. package/dist/record-4TR2UIIS.js.map +0 -1
  130. package/dist/session-MYFNZPRP.js.map +0 -1
  131. package/dist/skill-KM34LDMO.js.map +0 -1
  132. package/dist/spec-RA3CFEKG.js.map +0 -1
  133. package/dist/src-LB4HBTJQ.js.map +0 -1
  134. package/dist/workflow-XYFH5LTP.js.map +0 -1
  135. package/dist/workflow-cache-NQJQFWMV.js.map +0 -1
  136. /package/dist/{auth-EN6NQCJY.js.map → auth-VUHL74FH.js.map} +0 -0
  137. /package/dist/{chunk-XQXVG7RA.js.map → chunk-EEN2TZUQ.js.map} +0 -0
  138. /package/dist/{chunk-I4QLI3JO.js.map → chunk-EJ4U242G.js.map} +0 -0
  139. /package/dist/{chunk-CLAWT7BJ.js.map → chunk-ENDHMYEJ.js.map} +0 -0
  140. /package/dist/{src-N3TPKGTY.js.map → chunk-G4UYGRZR.js.map} +0 -0
  141. /package/dist/{chunk-N7HFALX3.js.map → chunk-VGTKUOYS.js.map} +0 -0
  142. /package/dist/{chunk-Q4BO4Z5V.js.map → chunk-ZTOE3ND3.js.map} +0 -0
  143. /package/dist/{install-DXSNWZKO.js.map → install-FYZ3HNZK.js.map} +0 -0
  144. /package/dist/{src-UVDX5UP6.js.map → src-NCJPUAE6.js.map} +0 -0
  145. /package/dist/{tag-YY6YAABQ.js.map → src-Y3K7UJEF.js.map} +0 -0
  146. /package/dist/{trace-HXIW7U2D.js.map → tag-YSU2XXPI.js.map} +0 -0
  147. /package/dist/{triage-ORG45R5W.js.map → trace-A3ZY6K26.js.map} +0 -0
  148. /package/dist/{upgrade-L2VYMP2V.js.map → upgrade-6ZUFETKX.js.map} +0 -0
@@ -1,12 +1,12 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
2
  import {
3
3
  apiRequest
4
- } from "./chunk-WOHL4FTB.js";
4
+ } from "./chunk-CQE46HSM.js";
5
5
  import {
6
6
  getArgValue,
7
7
  hasFlag,
8
8
  resolveConfig
9
- } from "./chunk-A4OFTUVV.js";
9
+ } from "./chunk-PZ4LCN6K.js";
10
10
 
11
11
  // src/tag.ts
12
12
  import process from "process";
@@ -423,4 +423,4 @@ export {
423
423
  applyWorkflowTagFlags,
424
424
  runTag
425
425
  };
426
- //# sourceMappingURL=chunk-XQXVG7RA.js.map
426
+ //# sourceMappingURL=chunk-EEN2TZUQ.js.map
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
2
  import {
3
3
  PlaywrightClient
4
- } from "./chunk-4PNOV7OV.js";
4
+ } from "./chunk-QQVWGWPI.js";
5
5
 
6
6
  // src/local-browser/host.ts
7
7
  var HEARTBEAT_INTERVAL_MS = 3e4;
@@ -410,4 +410,4 @@ export {
410
410
  LocalBrowserHost,
411
411
  USER_PAUSED_ERROR
412
412
  };
413
- //# sourceMappingURL=chunk-I4QLI3JO.js.map
413
+ //# sourceMappingURL=chunk-EJ4U242G.js.map
@@ -1,12 +1,12 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
2
  import {
3
3
  apiRequest
4
- } from "./chunk-WOHL4FTB.js";
4
+ } from "./chunk-CQE46HSM.js";
5
5
  import {
6
6
  getArgValue,
7
7
  hasFlag,
8
8
  resolveConfig
9
- } from "./chunk-A4OFTUVV.js";
9
+ } from "./chunk-PZ4LCN6K.js";
10
10
 
11
11
  // src/triage.ts
12
12
  import process from "process";
@@ -268,4 +268,4 @@ export {
268
268
  isTriageEnabledForExecution,
269
269
  runTriage
270
270
  };
271
- //# sourceMappingURL=chunk-CLAWT7BJ.js.map
271
+ //# sourceMappingURL=chunk-ENDHMYEJ.js.map
@@ -0,0 +1,2 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ //# sourceMappingURL=chunk-G4UYGRZR.js.map
@@ -0,0 +1,92 @@
1
+ import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+
3
+ // src/brand.ts
4
+ import { createRequire } from "module";
5
+ import path from "path";
6
+ import process from "process";
7
+ var require2 = createRequire(import.meta.url);
8
+ var pkg = require2("../package.json");
9
+ function resolveBrandFromArgv(argv1) {
10
+ if (argv1) {
11
+ const base = path.basename(argv1).replace(/\.[^./\\]+$/, "").toLowerCase();
12
+ if (base.includes("cofactor")) return "cofactor";
13
+ }
14
+ return "canary";
15
+ }
16
+ var command = resolveBrandFromArgv(process.argv[1]);
17
+ var packageName = pkg.name;
18
+ var version = pkg.version;
19
+ var INVOCATION_RE = /(^|[^\w-])canary(?= [a-z])/g;
20
+ var PROSE_RE = /\bCanary\b/g;
21
+ function brandTextFor(text, brand) {
22
+ if (brand === "canary") return text;
23
+ const display = brand === "cofactor" ? "Cofactor" : "Canary";
24
+ return text.replace(INVOCATION_RE, (_m, prefix) => `${prefix}${brand}`).replace(PROSE_RE, display);
25
+ }
26
+ function brandText(text) {
27
+ return brandTextFor(text, command);
28
+ }
29
+ function brandSkillContentFor(content, brand) {
30
+ const base = brandTextFor(content, brand);
31
+ if (brand !== "cofactor") return base;
32
+ return base.replace(/`canary`/g, "`cofactor`").replace(/@canaryai\/cli/g, "@runcofactor/cli").replace(/\bCANARY_/g, "COFACTOR_").replace(/trycanary\.ai/g, "runcofactor.com");
33
+ }
34
+ function readBrandEnvFor(canaryName, brand, env = process.env) {
35
+ const cofactorName = canaryName.replace(/^CANARY_/, "COFACTOR_");
36
+ const order = brand === "cofactor" ? [cofactorName, canaryName] : [canaryName, cofactorName];
37
+ for (const name of order) {
38
+ const value = env[name];
39
+ if (value != null && value !== "") return value;
40
+ }
41
+ return void 0;
42
+ }
43
+ function readBrandEnv(canaryName, env = process.env) {
44
+ return readBrandEnvFor(canaryName, command, env);
45
+ }
46
+ var SPEC_EXTENSIONS = [".cofactor.md", ".canary.md"];
47
+ var SPEC_DIRS = ["cofactor", "canary"];
48
+ function specExtensionFor(brand) {
49
+ return brand === "cofactor" ? ".cofactor.md" : ".canary.md";
50
+ }
51
+ function specDirFor(brand) {
52
+ return path.join(brand === "cofactor" ? "cofactor" : "canary", "specs");
53
+ }
54
+ var specExtension = specExtensionFor(command);
55
+ var specDir = specDirFor(command);
56
+ function readableSpecDirs() {
57
+ return SPEC_DIRS.map((root) => path.join(root, "specs"));
58
+ }
59
+ function isSpecFile(name) {
60
+ return SPEC_EXTENSIONS.some((ext) => name.endsWith(ext));
61
+ }
62
+ function splitSpecExtension(name) {
63
+ for (const ext of SPEC_EXTENSIONS) {
64
+ if (name.endsWith(ext)) return { base: name.slice(0, -ext.length), ext };
65
+ }
66
+ return null;
67
+ }
68
+ function installConsoleBranding() {
69
+ if (command === "canary") return;
70
+ const methods = ["log", "error", "warn", "info"];
71
+ for (const method of methods) {
72
+ const original = console[method].bind(console);
73
+ console[method] = (...args) => {
74
+ original(...args.map((arg) => typeof arg === "string" ? brandText(arg) : arg));
75
+ };
76
+ }
77
+ }
78
+
79
+ export {
80
+ command,
81
+ packageName,
82
+ version,
83
+ brandSkillContentFor,
84
+ readBrandEnv,
85
+ specExtension,
86
+ specDir,
87
+ readableSpecDirs,
88
+ isSpecFile,
89
+ splitSpecExtension,
90
+ installConsoleBranding
91
+ };
92
+ //# sourceMappingURL=chunk-JZMJUPZP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/brand.ts"],"sourcesContent":["/**\n * Brand resolution for the dual-published CLI.\n *\n * We ship a single build to two npm packages:\n * - `@canaryai/cli` → binary `canary` (legacy brand)\n * - `@runcofactor/cli` → binary `cofactor` (new brand)\n *\n * Both packages contain the identical artifact; they differ only in their\n * `name` and `bin` map (rewritten at publish time — see\n * `.github/workflows/publish-canary-cli.yml`). Everything brand-specific is\n * therefore derived at runtime:\n *\n * - `command` comes from the invoked binary name (`basename(process.argv[1])`),\n * so output reflects how the user called it (`brandText` rewrites legacy\n * `canary` display strings to the active brand).\n * - `packageName` comes from the installed package's own `package.json`, so\n * self-upgrade and the update check target whatever package was installed.\n *\n * The legacy `canary` brand is the default for every non-`cofactor` invocation\n * (`node dist/index.js`, dev scripts, tests), which keeps existing behaviour\n * byte-for-byte unchanged.\n *\n * @module\n */\n\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\nimport process from 'node:process';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../package.json') as { name: string; version: string };\n\nexport type Brand = 'canary' | 'cofactor';\n\n/**\n * Resolve the brand from the invoked binary path (`process.argv[1]`). The\n * global-install shim is named `canary` or `cofactor` (npm/bun/pnpm bin link),\n * so the basename is the brand. Everything else (`node dist/index.js`, dev\n * scripts, tests) defaults to the legacy `canary` brand. Pure for testing.\n */\nexport function resolveBrandFromArgv(argv1: string | undefined): Brand {\n if (argv1) {\n const base = path.basename(argv1).replace(/\\.[^./\\\\]+$/, '').toLowerCase();\n if (base.includes('cofactor')) return 'cofactor';\n }\n return 'canary';\n}\n\n/** The invoked command / binary name, e.g. `canary` or `cofactor`. */\nexport const command: Brand = resolveBrandFromArgv(process.argv[1]);\n\n/** npm package name of the installed CLI, read from its own package.json. */\nexport const packageName: string = pkg.name;\n\n/** CLI version, read from package.json. */\nexport const version: string = pkg.version;\n\n/**\n * Rewrite display strings authored against the legacy `canary` brand to the\n * active brand. Identity for the `canary` brand (so legacy output never\n * changes). Two precise, word-anchored rules:\n *\n * 1. The invocation token `canary ` followed by a lowercase letter (i.e. a\n * sub-command or `canary v1.2.3`). Anchored on a non-word, non-hyphen\n * boundary so it never touches `trycanary.ai`, `x-canary-request-id`,\n * `canary-cli`, or `CANARY_API_TOKEN`.\n * 2. The prose noun `Canary` (capitalized, standalone word) → display name.\n */\nconst INVOCATION_RE = /(^|[^\\w-])canary(?= [a-z])/g;\nconst PROSE_RE = /\\bCanary\\b/g;\n\n/** Pure brand rewrite for a given target brand. Identity for `canary`. */\nexport function brandTextFor(text: string, brand: Brand): string {\n if (brand === 'canary') return text;\n const display = brand === 'cofactor' ? 'Cofactor' : 'Canary';\n return text\n .replace(INVOCATION_RE, (_m, prefix: string) => `${prefix}${brand}`)\n .replace(PROSE_RE, display);\n}\n\nfunction brandText(text: string): string {\n return brandTextFor(text, command);\n}\n\n/**\n * Rewrite a bundled skill template's content to the target brand for the\n * `canary skill install` / `canary skill <name>` output. Builds on\n * {@link brandTextFor} (invocation tokens + the prose noun) and additionally\n * maps the brand-specific identifiers that {@link brandTextFor} deliberately\n * leaves intact for console output — but which a distributed skill file must\n * carry per brand:\n *\n * - inline binary: `` `canary` `` → `` `cofactor` `` (a backtick-wrapped bare\n * binary reference, e.g. \"the `canary` CLI\" — `brandTextFor` only rewrites\n * `canary <subcommand>` invocations, not the standalone binary name)\n * - npm package: `@canaryai/cli` → `@runcofactor/cli`\n * - env prefix: `CANARY_*` → `COFACTOR_*`\n * - domains: `*.trycanary.ai` → `*.runcofactor.com`\n *\n * Identity for the `canary` brand, so legacy skill text installs byte-for-byte\n * unchanged. The skill's frontmatter `name:` is never an invocation/prose/token\n * match (it's `canary-basic`, hyphenated), so the install directory key is\n * preserved. Trigger keywords in prose (e.g. \"mentions canary, canary CLI\")\n * intentionally keep the legacy term so the skill still activates for users who\n * say \"canary\". Pure (brand injectable).\n */\nexport function brandSkillContentFor(content: string, brand: Brand): string {\n const base = brandTextFor(content, brand);\n if (brand !== 'cofactor') return base;\n return base\n .replace(/`canary`/g, '`cofactor`')\n .replace(/@canaryai\\/cli/g, '@runcofactor/cli')\n .replace(/\\bCANARY_/g, 'COFACTOR_')\n .replace(/trycanary\\.ai/g, 'runcofactor.com');\n}\n\n/**\n * Read a brand-prefixed environment variable, accepting EITHER brand's prefix.\n *\n * The two published brands historically diverged: the binary only ever read the\n * legacy `CANARY_*` names, but the cofactor docs tell users to set `COFACTOR_*`\n * (e.g. `COFACTOR_API_TOKEN`). This bridges that gap — a value set under either\n * prefix is honoured regardless of which binary (`canary` / `cofactor`) was\n * invoked. When both are set, the active brand's prefix wins.\n *\n * `canaryName` is the legacy `CANARY_`-prefixed name; the `COFACTOR_` variant is\n * derived from it. Empty strings are treated as unset so a blank value can't\n * shadow a real one set under the other prefix. Pure (brand + env injectable).\n */\nexport function readBrandEnvFor(\n canaryName: string,\n brand: Brand,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n const cofactorName = canaryName.replace(/^CANARY_/, 'COFACTOR_');\n const order = brand === 'cofactor' ? [cofactorName, canaryName] : [canaryName, cofactorName];\n for (const name of order) {\n const value = env[name];\n if (value != null && value !== '') return value;\n }\n return undefined;\n}\n\n/** Read a brand-prefixed env var for the active brand. See {@link readBrandEnvFor}. */\nexport function readBrandEnv(\n canaryName: string,\n env: NodeJS.ProcessEnv = process.env,\n): string | undefined {\n return readBrandEnvFor(canaryName, command, env);\n}\n\n/**\n * Workflow spec file convention — brand-aware, backwards compatible.\n *\n * The spec extension and default directory follow the active brand: the\n * `cofactor` binary writes `.cofactor.md` into `cofactor/specs`, the legacy\n * `canary` binary writes `.canary.md` into `canary/specs` (byte-for-byte\n * unchanged). Reading/discovery always accepts BOTH — {@link SPEC_EXTENSIONS}\n * and {@link SPEC_DIRS} list every recognized variant (newest brand first), so\n * an existing `.canary.md` repo keeps working under either binary.\n */\nexport const SPEC_EXTENSIONS = ['.cofactor.md', '.canary.md'] as const;\n\n/**\n * Default spec-directory roots, newest brand first. Each is used as\n * `<root>/specs`. The ordering is a precedence contract: consumers that merge\n * across roots (e.g. `pull --all`) must treat earlier entries as higher\n * priority, so the active/new brand's copy wins over a legacy one.\n */\nexport const SPEC_DIRS = ['cofactor', 'canary'] as const;\n\n/** Spec-file extension to WRITE for a given brand. See {@link SPEC_EXTENSIONS}. */\nexport function specExtensionFor(brand: Brand): string {\n return brand === 'cofactor' ? '.cofactor.md' : '.canary.md';\n}\n\n/** Default spec directory (`<root>/specs`) to WRITE into for a given brand. */\nexport function specDirFor(brand: Brand): string {\n return path.join(brand === 'cofactor' ? 'cofactor' : 'canary', 'specs');\n}\n\n/** Spec-file extension to WRITE for the active brand. See {@link specExtensionFor}. */\nexport const specExtension: string = specExtensionFor(command);\n\n/** Default spec directory to WRITE into for the active brand. See {@link specDirFor}. */\nexport const specDir: string = specDirFor(command);\n\n/**\n * Every spec directory to READ from, highest precedence first — `<root>/specs`\n * for each {@link SPEC_DIRS} root. This is the single source of the spec-dir\n * convention for discovery; consumers merge across these in order (first-seen\n * wins) so the active brand's copy beats a legacy one. Brand-independent: a\n * `cofactor` run still discovers an existing `canary/specs` repo, and vice\n * versa.\n */\nexport function readableSpecDirs(): string[] {\n return SPEC_DIRS.map((root) => path.join(root, 'specs'));\n}\n\n/** True if `name` ends with any recognized spec extension (either brand). */\nexport function isSpecFile(name: string): boolean {\n return SPEC_EXTENSIONS.some((ext) => name.endsWith(ext));\n}\n\n/**\n * Split a recognized spec extension off `name`, returning `{ base, ext }` for\n * the first matching {@link SPEC_EXTENSIONS} entry, or `null` if none match.\n * Lets callers derive sibling names (e.g. `.stamped`) without assuming a brand.\n */\nexport function splitSpecExtension(name: string): { base: string; ext: string } | null {\n for (const ext of SPEC_EXTENSIONS) {\n if (name.endsWith(ext)) return { base: name.slice(0, -ext.length), ext };\n }\n return null;\n}\n\n/**\n * Install brand rewriting over the `console` methods used for human-readable\n * output. No-op for the `canary` brand. Skipped by the caller for machine\n * output (`--json`/`--format`) and the `mcp` JSON-RPC transport, where rewriting\n * could corrupt structured payloads — display branding only.\n */\nexport function installConsoleBranding(): void {\n if (command === 'canary') return;\n const methods = ['log', 'error', 'warn', 'info'] as const;\n for (const method of methods) {\n const original = console[method].bind(console);\n console[method] = (...args: unknown[]) => {\n original(...args.map((arg) => (typeof arg === 'string' ? brandText(arg) : arg)));\n };\n }\n}\n"],"mappings":";;;AAyBA,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,OAAO,aAAa;AAEpB,IAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,MAAMA,SAAQ,iBAAiB;AAU9B,SAAS,qBAAqB,OAAkC;AACrE,MAAI,OAAO;AACT,UAAM,OAAO,KAAK,SAAS,KAAK,EAAE,QAAQ,eAAe,EAAE,EAAE,YAAY;AACzE,QAAI,KAAK,SAAS,UAAU,EAAG,QAAO;AAAA,EACxC;AACA,SAAO;AACT;AAGO,IAAM,UAAiB,qBAAqB,QAAQ,KAAK,CAAC,CAAC;AAG3D,IAAM,cAAsB,IAAI;AAGhC,IAAM,UAAkB,IAAI;AAanC,IAAM,gBAAgB;AACtB,IAAM,WAAW;AAGV,SAAS,aAAa,MAAc,OAAsB;AAC/D,MAAI,UAAU,SAAU,QAAO;AAC/B,QAAM,UAAU,UAAU,aAAa,aAAa;AACpD,SAAO,KACJ,QAAQ,eAAe,CAAC,IAAI,WAAmB,GAAG,MAAM,GAAG,KAAK,EAAE,EAClE,QAAQ,UAAU,OAAO;AAC9B;AAEA,SAAS,UAAU,MAAsB;AACvC,SAAO,aAAa,MAAM,OAAO;AACnC;AAwBO,SAAS,qBAAqB,SAAiB,OAAsB;AAC1E,QAAM,OAAO,aAAa,SAAS,KAAK;AACxC,MAAI,UAAU,WAAY,QAAO;AACjC,SAAO,KACJ,QAAQ,aAAa,YAAY,EACjC,QAAQ,mBAAmB,kBAAkB,EAC7C,QAAQ,cAAc,WAAW,EACjC,QAAQ,kBAAkB,iBAAiB;AAChD;AAeO,SAAS,gBACd,YACA,OACA,MAAyB,QAAQ,KACb;AACpB,QAAM,eAAe,WAAW,QAAQ,YAAY,WAAW;AAC/D,QAAM,QAAQ,UAAU,aAAa,CAAC,cAAc,UAAU,IAAI,CAAC,YAAY,YAAY;AAC3F,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,IAAI,IAAI;AACtB,QAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAAA,EAC5C;AACA,SAAO;AACT;AAGO,SAAS,aACd,YACA,MAAyB,QAAQ,KACb;AACpB,SAAO,gBAAgB,YAAY,SAAS,GAAG;AACjD;AAYO,IAAM,kBAAkB,CAAC,gBAAgB,YAAY;AAQrD,IAAM,YAAY,CAAC,YAAY,QAAQ;AAGvC,SAAS,iBAAiB,OAAsB;AACrD,SAAO,UAAU,aAAa,iBAAiB;AACjD;AAGO,SAAS,WAAW,OAAsB;AAC/C,SAAO,KAAK,KAAK,UAAU,aAAa,aAAa,UAAU,OAAO;AACxE;AAGO,IAAM,gBAAwB,iBAAiB,OAAO;AAGtD,IAAM,UAAkB,WAAW,OAAO;AAU1C,SAAS,mBAA6B;AAC3C,SAAO,UAAU,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,OAAO,CAAC;AACzD;AAGO,SAAS,WAAW,MAAuB;AAChD,SAAO,gBAAgB,KAAK,CAAC,QAAQ,KAAK,SAAS,GAAG,CAAC;AACzD;AAOO,SAAS,mBAAmB,MAAoD;AACrF,aAAW,OAAO,iBAAiB;AACjC,QAAI,KAAK,SAAS,GAAG,EAAG,QAAO,EAAE,MAAM,KAAK,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,IAAI;AAAA,EACzE;AACA,SAAO;AACT;AAQO,SAAS,yBAA+B;AAC7C,MAAI,YAAY,SAAU;AAC1B,QAAM,UAAU,CAAC,OAAO,SAAS,QAAQ,MAAM;AAC/C,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,QAAQ,MAAM,EAAE,KAAK,OAAO;AAC7C,YAAQ,MAAM,IAAI,IAAI,SAAoB;AACxC,eAAS,GAAG,KAAK,IAAI,CAAC,QAAS,OAAO,QAAQ,WAAW,UAAU,GAAG,IAAI,GAAI,CAAC;AAAA,IACjF;AAAA,EACF;AACF;","names":["require"]}
@@ -1,10 +1,10 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
2
  import {
3
3
  isTriageEnabledForExecution
4
- } from "./chunk-CLAWT7BJ.js";
4
+ } from "./chunk-ENDHMYEJ.js";
5
5
  import {
6
6
  resolveConfig
7
- } from "./chunk-A4OFTUVV.js";
7
+ } from "./chunk-PZ4LCN6K.js";
8
8
 
9
9
  // src/remote-test.ts
10
10
  import process from "process";
@@ -97,12 +97,59 @@ ${available}`
97
97
  }
98
98
  return result;
99
99
  }
100
+ async function triggerNamedSuiteRun(apiUrl, token, suiteArg, environmentId, verbose) {
101
+ const authHeaders = {
102
+ Authorization: `Bearer ${token}`,
103
+ "Content-Type": "application/json"
104
+ };
105
+ const listRes = await fetch(`${apiUrl}/test-suites`, { headers: authHeaders });
106
+ if (!listRes.ok) {
107
+ console.error(`Failed to list test suites: ${listRes.status}`);
108
+ process.exit(1);
109
+ }
110
+ const listJson = await listRes.json();
111
+ const suites = listJson.data ?? [];
112
+ const match = suites.find((s) => s.id === suiteArg) ?? suites.find((s) => s.name.toLowerCase() === suiteArg.toLowerCase());
113
+ if (!match) {
114
+ console.error(`Suite not found: "${suiteArg}"`);
115
+ if (suites.length > 0) {
116
+ console.error("Available suites:");
117
+ for (const s of suites) console.error(` - ${s.name} (${s.id})`);
118
+ }
119
+ process.exit(1);
120
+ }
121
+ console.log(`Starting test suite: ${match.name}`);
122
+ if (environmentId) console.log(` Environment: ${environmentId}`);
123
+ console.log("");
124
+ const runRes = await fetch(`${apiUrl}/test-suites/${match.id}/run`, {
125
+ method: "POST",
126
+ headers: authHeaders,
127
+ body: JSON.stringify(environmentId ? { environmentId } : {})
128
+ });
129
+ if (!runRes.ok) {
130
+ console.error(`Failed to start suite: ${runRes.status}`);
131
+ console.error(await runRes.text());
132
+ process.exit(1);
133
+ }
134
+ const runJson = await runRes.json();
135
+ if (!runJson.ok || !runJson.data) {
136
+ console.error(`Failed to start suite: ${runJson.error ?? "Unknown error"}`);
137
+ process.exit(1);
138
+ }
139
+ if (verbose) {
140
+ console.log(`Execution ID: ${runJson.data.executionId}`);
141
+ console.log(`Job ID: ${runJson.data.jobId}`);
142
+ console.log("");
143
+ }
144
+ return runJson.data;
145
+ }
100
146
  async function runRemoteTest(argv) {
101
147
  const { apiUrl, token } = await resolveConfig(argv);
102
148
  const tag = getArgValue(argv, "--tag");
103
149
  const namePattern = getArgValue(argv, "--name-pattern");
104
150
  const propertyArg = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
105
151
  const environmentArg = getArgValue(argv, "--environment");
152
+ const suiteArg = getArgValue(argv, "--suite");
106
153
  const verbose = hasFlag(argv, "--verbose", "-v");
107
154
  let resolvedConfig = {};
108
155
  if (propertyArg || environmentArg) {
@@ -119,39 +166,62 @@ async function runRemoteTest(argv) {
119
166
  if (tag) console.log(` Filtering by tag: ${tag}`);
120
167
  if (namePattern) console.log(` Filtering by name pattern: ${namePattern}`);
121
168
  console.log("");
122
- const queryParams = new URLSearchParams();
123
- if (tag) queryParams.set("tag", tag);
124
- if (namePattern) queryParams.set("namePattern", namePattern);
125
- const triggerUrl = `${apiUrl}/workflows/test-runs${queryParams.toString() ? `?${queryParams}` : ""}`;
126
- const body = {};
127
- if (resolvedConfig.propertyId) body.propertyId = resolvedConfig.propertyId;
128
- if (resolvedConfig.environmentId) body.environmentId = resolvedConfig.environmentId;
129
- let triggerRes;
130
- try {
131
- triggerRes = await fetch(triggerUrl, {
132
- method: "POST",
133
- headers: {
134
- Authorization: `Bearer ${token}`,
135
- "Content-Type": "application/json"
136
- },
137
- body: JSON.stringify(body)
138
- });
139
- } catch (err) {
140
- console.error(`Failed to connect to API: ${err}`);
141
- process.exit(1);
142
- }
143
- if (!triggerRes.ok) {
144
- const errorText = await triggerRes.text();
145
- console.error(`Failed to start tests: ${triggerRes.status}`);
146
- console.error(errorText);
147
- process.exit(1);
148
- }
149
- const triggerData = await triggerRes.json();
150
- if (!triggerData.ok || !triggerData.suiteId) {
151
- console.error(`Failed to start tests: ${triggerData.error ?? "Unknown error"}`);
152
- process.exit(1);
169
+ let suiteId;
170
+ let jobId;
171
+ let appUrl;
172
+ if (suiteArg) {
173
+ const triggered = await triggerNamedSuiteRun(
174
+ apiUrl,
175
+ token,
176
+ suiteArg,
177
+ resolvedConfig.environmentId,
178
+ verbose
179
+ );
180
+ suiteId = triggered.executionId;
181
+ jobId = triggered.jobId;
182
+ } else {
183
+ console.log("Starting remote workflow tests...");
184
+ if (resolvedConfig.propertyId) console.log(` Property: ${propertyArg}`);
185
+ if (resolvedConfig.environmentId) console.log(` Environment: ${environmentArg}`);
186
+ if (tag) console.log(` Filtering by tag: ${tag}`);
187
+ if (namePattern) console.log(` Filtering by name pattern: ${namePattern}`);
188
+ console.log("");
189
+ const queryParams = new URLSearchParams();
190
+ if (tag) queryParams.set("tag", tag);
191
+ if (namePattern) queryParams.set("namePattern", namePattern);
192
+ const triggerUrl = `${apiUrl}/workflows/test-runs${queryParams.toString() ? `?${queryParams}` : ""}`;
193
+ const body = {};
194
+ if (resolvedConfig.propertyId) body.propertyId = resolvedConfig.propertyId;
195
+ if (resolvedConfig.environmentId) body.environmentId = resolvedConfig.environmentId;
196
+ let triggerRes;
197
+ try {
198
+ triggerRes = await fetch(triggerUrl, {
199
+ method: "POST",
200
+ headers: {
201
+ Authorization: `Bearer ${token}`,
202
+ "Content-Type": "application/json"
203
+ },
204
+ body: JSON.stringify(body)
205
+ });
206
+ } catch (err) {
207
+ console.error(`Failed to connect to API: ${err}`);
208
+ process.exit(1);
209
+ }
210
+ if (!triggerRes.ok) {
211
+ const errorText = await triggerRes.text();
212
+ console.error(`Failed to start tests: ${triggerRes.status}`);
213
+ console.error(errorText);
214
+ process.exit(1);
215
+ }
216
+ const triggerData = await triggerRes.json();
217
+ if (!triggerData.ok || !triggerData.suiteId) {
218
+ console.error(`Failed to start tests: ${triggerData.error ?? "Unknown error"}`);
219
+ process.exit(1);
220
+ }
221
+ suiteId = triggerData.suiteId;
222
+ jobId = triggerData.jobId;
223
+ appUrl = triggerData.appUrl;
153
224
  }
154
- const { suiteId, jobId, appUrl } = triggerData;
155
225
  if (verbose) {
156
226
  console.log(`Suite ID: ${suiteId}`);
157
227
  console.log(`Job ID: ${jobId}`);
@@ -278,4 +348,4 @@ async function runRemoteTest(argv) {
278
348
  export {
279
349
  runRemoteTest
280
350
  };
281
- //# sourceMappingURL=chunk-GKY2KXO5.js.map
351
+ //# sourceMappingURL=chunk-NL7CVG56.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/remote-test.ts"],"sourcesContent":["/**\n * Remote Test Command\n *\n * Triggers workflow tests on the Canary platform and streams results via SSE.\n * Designed for CI/CD pipelines - exits with code 0 on success, 1 on failure.\n *\n * Usage:\n * canary test-suite run [options]\n *\n * Options:\n * --token <key> API key (or set CANARY_API_TOKEN env var)\n * --api-url <url> API URL (default: https://api.trycanary.ai)\n * --tag <tag> Filter workflows by tag\n * --name-pattern <pat> Filter workflows by name pattern\n * --verbose, -v Show all events (not just results)\n */\n\nimport process from \"node:process\";\nimport { createParser, type EventSourceMessage } from \"eventsource-parser\";\nimport { resolveConfig } from \"./auth\";\nimport { isTriageEnabledForExecution } from \"./triage.js\";\n\ntype WorkflowTestStatus = \"queued\" | \"running\" | \"waiting\" | \"success\" | \"failed\";\n\ntype WorkflowTestEvent = {\n type: \"workflow-test\";\n suiteId: string;\n workflowId: string;\n testRunId: string;\n workflowRunId: string | null;\n status: WorkflowTestStatus;\n startedAt: string | null;\n finishedAt: string | null;\n durationMs: number | null;\n errorMessage?: string | null;\n linkedIssueId?: string | null;\n message?: string;\n resumeAt?: string | null;\n segmentIndex?: number | null;\n totalSegments?: number | null;\n};\n\ntype WorkflowTestSuiteEvent = {\n type: \"workflow-test-suite\";\n suiteId: string;\n status: \"running\" | \"completed\";\n startedAt: string;\n finishedAt: string | null;\n durationMs: number | null;\n totalWorkflows: number;\n completedWorkflows: number;\n failedWorkflows: number;\n successfulWorkflows: number;\n errorMessage?: string | null;\n message?: string;\n};\n\ntype TriggerResponse = {\n ok: boolean;\n jobId?: string;\n suiteId?: string;\n startedAt?: string;\n appUrl?: string;\n error?: string;\n};\n\nfunction getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nfunction hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\nfunction buildQueryParams(tag?: string, namePattern?: string): string {\n const params = new URLSearchParams();\n if (tag) params.set(\"tag\", tag);\n if (namePattern) params.set(\"namePattern\", namePattern);\n return params.toString();\n}\n\nfunction extractWorkflowName(message: string | undefined, workflowId: string): string {\n if (!message) return workflowId;\n const match = message.match(/^Flow \"(.+?)\" /);\n return match ? match[1] : workflowId;\n}\n\ntype FailedTest = { name: string; testRunId: string };\n\nfunction formatFailedTests(\n failedTests: FailedTest[],\n appUrl: string | undefined,\n): string | null {\n if (failedTests.length === 0 || !appUrl) return null;\n\n const lines = [\"\", \"Failed tests:\"];\n for (const t of failedTests) {\n lines.push(` \\u2717 ${t.name}`);\n lines.push(` ${appUrl}/runs/tests/${t.testRunId}`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction formatSummary(stats: {\n totalWorkflows: number;\n successfulWorkflows: number;\n failedWorkflows: number;\n completedWorkflows: number;\n}): { message: string; exitCode: number } {\n const { totalWorkflows, successfulWorkflows, failedWorkflows, completedWorkflows } = stats;\n\n if (totalWorkflows === 0) {\n return { message: \"No workflows found matching the filter criteria.\", exitCode: 0 };\n }\n\n const passRate = Math.round((successfulWorkflows / totalWorkflows) * 100);\n const waitingWorkflows = totalWorkflows - completedWorkflows;\n\n let message: string;\n let exitCode: number;\n\n if (failedWorkflows > 0) {\n message = `FAILED: ${failedWorkflows} of ${totalWorkflows} workflows failed (${passRate}% pass rate)`;\n exitCode = 1;\n } else {\n message = `PASSED: ${successfulWorkflows} of ${totalWorkflows} workflows passed`;\n exitCode = 0;\n }\n\n if (waitingWorkflows > 0) {\n message += `\\nNote: ${waitingWorkflows} workflow(s) are still waiting (scheduled for later)`;\n }\n\n return { message, exitCode };\n}\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ntype TestConfigProperty = {\n id: string;\n name: string;\n baseUrl: string;\n environments: Array<{ id: string; name: string; url: string; isDefault: boolean }>;\n};\n\ntype TestConfigResponse = {\n ok: boolean;\n properties: TestConfigProperty[];\n error?: string;\n};\n\ntype ResolvedConfig = {\n propertyId?: string;\n environmentId?: string;\n};\n\nasync function resolveTestConfig(\n apiUrl: string,\n token: string,\n propertyArg?: string,\n environmentArg?: string,\n): Promise<ResolvedConfig> {\n if (!propertyArg && !environmentArg) return {};\n\n const res = await fetch(`${apiUrl}/workflows/test-config`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Failed to fetch test config: ${res.status} ${text}`);\n }\n\n const data = (await res.json()) as TestConfigResponse;\n if (!data.ok || !data.properties) {\n throw new Error(`Failed to fetch test config: ${data.error ?? \"Unknown error\"}`);\n }\n\n const { properties } = data;\n let property: TestConfigProperty | undefined;\n\n if (propertyArg) {\n if (UUID_RE.test(propertyArg)) {\n property = properties.find((p) => p.id === propertyArg);\n } else {\n property = properties.find(\n (p) => p.name.toLowerCase() === propertyArg.toLowerCase()\n );\n }\n if (!property) {\n const available = properties.map((p) => ` - ${p.name} (${p.id})`).join(\"\\n\");\n throw new Error(\n `Application \"${propertyArg}\" not found.\\nAvailable applications:\\n${available}`\n );\n }\n } else if (environmentArg) {\n // --environment without --application: auto-resolve if single application\n if (properties.length === 0) {\n throw new Error(\"No applications found in your organization.\");\n }\n if (properties.length > 1) {\n const available = properties.map((p) => ` - ${p.name} (${p.id})`).join(\"\\n\");\n throw new Error(\n `--environment requires --application when multiple applications exist.\\nAvailable applications:\\n${available}`\n );\n }\n property = properties[0];\n }\n\n const result: ResolvedConfig = {};\n if (property) {\n result.propertyId = property.id;\n\n if (environmentArg) {\n let env: TestConfigProperty[\"environments\"][number] | undefined;\n if (UUID_RE.test(environmentArg)) {\n env = property.environments.find((e) => e.id === environmentArg);\n } else {\n env = property.environments.find(\n (e) => e.name.toLowerCase() === environmentArg.toLowerCase()\n );\n }\n if (!env) {\n const available = property.environments\n .map((e) => ` - ${e.name}${e.isDefault ? \" (default)\" : \"\"} (${e.id})`)\n .join(\"\\n\");\n throw new Error(\n `Environment \"${environmentArg}\" not found for application \"${property.name}\".\\nAvailable environments:\\n${available}`\n );\n }\n result.environmentId = env.id;\n }\n }\n\n return result;\n}\n\n// Export internals for testing\nexport const __internals = {\n getArgValue,\n hasFlag,\n buildQueryParams,\n extractWorkflowName,\n formatSummary,\n formatFailedTests,\n resolveTestConfig,\n UUID_RE,\n};\n\ntype SuiteListItem = { id: string; name: string };\n\n/**\n * Resolve a `--suite <name|id>` argument to a suite id and trigger a layered\n * run via POST /test-suites/:id/run. Returns the execution id (used as the SSE\n * stream key) and the orchestrator job id.\n */\nasync function triggerNamedSuiteRun(\n apiUrl: string,\n token: string,\n suiteArg: string,\n environmentId: string | undefined,\n verbose: boolean\n): Promise<{ executionId: string; jobId: string }> {\n const authHeaders = {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n };\n\n // Resolve name → id (exact id is accepted as-is; otherwise match by name).\n const listRes = await fetch(`${apiUrl}/test-suites`, { headers: authHeaders });\n if (!listRes.ok) {\n console.error(`Failed to list test suites: ${listRes.status}`);\n process.exit(1);\n }\n const listJson = (await listRes.json()) as { ok: boolean; data?: SuiteListItem[] };\n const suites = listJson.data ?? [];\n const match =\n suites.find((s) => s.id === suiteArg) ??\n suites.find((s) => s.name.toLowerCase() === suiteArg.toLowerCase());\n if (!match) {\n console.error(`Suite not found: \"${suiteArg}\"`);\n if (suites.length > 0) {\n console.error(\"Available suites:\");\n for (const s of suites) console.error(` - ${s.name} (${s.id})`);\n }\n process.exit(1);\n }\n\n console.log(`Starting test suite: ${match.name}`);\n if (environmentId) console.log(` Environment: ${environmentId}`);\n console.log(\"\");\n\n const runRes = await fetch(`${apiUrl}/test-suites/${match.id}/run`, {\n method: \"POST\",\n headers: authHeaders,\n body: JSON.stringify(environmentId ? { environmentId } : {}),\n });\n if (!runRes.ok) {\n console.error(`Failed to start suite: ${runRes.status}`);\n console.error(await runRes.text());\n process.exit(1);\n }\n const runJson = (await runRes.json()) as {\n ok: boolean;\n data?: { executionId: string; jobId: string };\n error?: string;\n };\n if (!runJson.ok || !runJson.data) {\n console.error(`Failed to start suite: ${runJson.error ?? \"Unknown error\"}`);\n process.exit(1);\n }\n if (verbose) {\n console.log(`Execution ID: ${runJson.data.executionId}`);\n console.log(`Job ID: ${runJson.data.jobId}`);\n console.log(\"\");\n }\n return runJson.data;\n}\n\nexport async function runRemoteTest(argv: string[]): Promise<void> {\n const { apiUrl, token } = await resolveConfig(argv);\n\n const tag = getArgValue(argv, \"--tag\");\n const namePattern = getArgValue(argv, \"--name-pattern\");\n // \"--application\" is the user-facing flag; \"--property\" stays as a backwards-compat alias.\n const propertyArg = getArgValue(argv, \"--application\") ?? getArgValue(argv, \"--property\");\n const environmentArg = getArgValue(argv, \"--environment\");\n const suiteArg = getArgValue(argv, \"--suite\");\n const verbose = hasFlag(argv, \"--verbose\", \"-v\");\n\n // 0. Resolve property/environment if specified\n let resolvedConfig: ResolvedConfig = {};\n if (propertyArg || environmentArg) {\n try {\n resolvedConfig = await resolveTestConfig(apiUrl, token, propertyArg, environmentArg);\n } catch (err) {\n console.error(`${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n }\n\n console.log(\"Starting remote workflow tests...\");\n if (resolvedConfig.propertyId) console.log(` Application: ${propertyArg}`);\n if (resolvedConfig.environmentId) console.log(` Environment: ${environmentArg}`);\n if (tag) console.log(` Filtering by tag: ${tag}`);\n if (namePattern) console.log(` Filtering by name pattern: ${namePattern}`);\n console.log(\"\");\n\n let suiteId: string;\n let jobId: string | undefined;\n let appUrl: string | undefined;\n\n if (suiteArg) {\n // Named, layered suite run. Resolve name→id, then POST /test-suites/:id/run.\n const triggered = await triggerNamedSuiteRun(\n apiUrl,\n token,\n suiteArg,\n resolvedConfig.environmentId,\n verbose\n );\n suiteId = triggered.executionId;\n jobId = triggered.jobId;\n } else {\n console.log(\"Starting remote workflow tests...\");\n if (resolvedConfig.propertyId) console.log(` Property: ${propertyArg}`);\n if (resolvedConfig.environmentId) console.log(` Environment: ${environmentArg}`);\n if (tag) console.log(` Filtering by tag: ${tag}`);\n if (namePattern) console.log(` Filtering by name pattern: ${namePattern}`);\n console.log(\"\");\n\n // 1. Trigger test run\n const queryParams = new URLSearchParams();\n if (tag) queryParams.set(\"tag\", tag);\n if (namePattern) queryParams.set(\"namePattern\", namePattern);\n\n const triggerUrl = `${apiUrl}/workflows/test-runs${queryParams.toString() ? `?${queryParams}` : \"\"}`;\n\n const body: Record<string, string> = {};\n if (resolvedConfig.propertyId) body.propertyId = resolvedConfig.propertyId;\n if (resolvedConfig.environmentId) body.environmentId = resolvedConfig.environmentId;\n\n let triggerRes: Response;\n try {\n triggerRes = await fetch(triggerUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n } catch (err) {\n console.error(`Failed to connect to API: ${err}`);\n process.exit(1);\n }\n\n if (!triggerRes.ok) {\n const errorText = await triggerRes.text();\n console.error(`Failed to start tests: ${triggerRes.status}`);\n console.error(errorText);\n process.exit(1);\n }\n\n const triggerData = (await triggerRes.json()) as TriggerResponse;\n if (!triggerData.ok || !triggerData.suiteId) {\n console.error(`Failed to start tests: ${triggerData.error ?? \"Unknown error\"}`);\n process.exit(1);\n }\n suiteId = triggerData.suiteId;\n jobId = triggerData.jobId;\n appUrl = triggerData.appUrl;\n }\n\n if (verbose) {\n console.log(`Suite ID: ${suiteId}`);\n console.log(`Job ID: ${jobId}`);\n console.log(\"\");\n }\n\n // 2. Subscribe to SSE stream\n const streamUrl = `${apiUrl}/workflows/test-runs/stream?suiteId=${suiteId}`;\n\n let streamRes: Response;\n try {\n streamRes = await fetch(streamUrl, {\n headers: {\n Authorization: `Bearer ${token}`,\n Accept: \"text/event-stream\",\n },\n });\n } catch (err) {\n console.error(`Failed to connect to event stream: ${err}`);\n process.exit(1);\n }\n\n if (!streamRes.ok || !streamRes.body) {\n console.error(`Failed to connect to event stream: ${streamRes.status}`);\n process.exit(1);\n }\n\n // Track state\n let exitCode = 0;\n let hasCompleted = false;\n const workflowNames = new Map<string, string>();\n const failedTests: FailedTest[] = [];\n let totalWorkflows = 0;\n let completedWorkflows = 0;\n let failedWorkflows = 0;\n let successfulWorkflows = 0;\n\n // 3. Parse SSE events\n const parser = createParser({\n onEvent: (event: EventSourceMessage) => {\n if (!event.data) return;\n\n try {\n const data = JSON.parse(event.data) as\n | WorkflowTestEvent\n | WorkflowTestSuiteEvent\n | { error?: string };\n\n if (verbose) {\n console.log(`[${event.event}] ${JSON.stringify(data)}`);\n }\n\n if (event.event === \"workflow-test\") {\n const testEvent = data as WorkflowTestEvent;\n const { status, workflowId, message, errorMessage } = testEvent;\n const name = workflowNames.get(workflowId) || message?.replace(/^Flow \"(.+)\" .*$/, \"$1\") || workflowId;\n\n if (message?.startsWith('Flow \"')) {\n const match = message.match(/^Flow \"(.+?)\" /);\n if (match) workflowNames.set(workflowId, match[1]);\n }\n\n if (!verbose) {\n if (status === \"success\") {\n console.log(` \\u2713 ${name}`);\n } else if (status === \"failed\") {\n console.log(` \\u2717 ${name}`);\n if (errorMessage) {\n console.log(` Error: ${errorMessage.slice(0, 200)}`);\n }\n failedTests.push({ name, testRunId: testEvent.testRunId });\n exitCode = 1;\n } else if (status === \"running\") {\n // Don't log running status in non-verbose mode\n } else if (status === \"waiting\") {\n console.log(` \\u23F3 ${name} (waiting for scheduled time)`);\n }\n }\n }\n\n if (event.event === \"workflow-test-suite\") {\n const suiteEvent = data as WorkflowTestSuiteEvent;\n totalWorkflows = suiteEvent.totalWorkflows;\n completedWorkflows = suiteEvent.completedWorkflows;\n failedWorkflows = suiteEvent.failedWorkflows;\n successfulWorkflows = suiteEvent.successfulWorkflows;\n\n if (suiteEvent.status === \"completed\") {\n hasCompleted = true;\n }\n }\n\n if (event.event === \"error\") {\n const errorData = data as { error?: string };\n console.error(`Stream error: ${errorData.error ?? \"Unknown error\"}`);\n exitCode = 1;\n }\n } catch {\n // Ignore parse errors for keepalive, etc.\n }\n },\n });\n\n const reader = streamRes.body.getReader();\n const decoder = new TextDecoder();\n\n try {\n while (!hasCompleted) {\n const { done, value } = await reader.read();\n if (done) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n } finally {\n reader.releaseLock();\n }\n\n // 4. Print summary\n console.log(\"\");\n console.log(\"─\".repeat(50));\n\n if (totalWorkflows === 0) {\n console.log(\"No workflows found matching the filter criteria.\");\n process.exit(0);\n }\n\n const passRate = totalWorkflows > 0 ? Math.round((successfulWorkflows / totalWorkflows) * 100) : 0;\n\n if (failedWorkflows > 0) {\n console.log(`FAILED: ${failedWorkflows} of ${totalWorkflows} workflows failed (${passRate}% pass rate)`);\n exitCode = 1;\n } else {\n console.log(`PASSED: ${successfulWorkflows} of ${totalWorkflows} workflows passed`);\n }\n\n const waitingWorkflows = totalWorkflows - completedWorkflows;\n if (waitingWorkflows > 0) {\n console.log(`Note: ${waitingWorkflows} workflow(s) are still waiting (scheduled for later)`);\n }\n\n const failedSection = formatFailedTests(failedTests, appUrl);\n if (failedSection) {\n console.log(failedSection);\n }\n\n if (failedWorkflows > 0) {\n const triageEnabled = await isTriageEnabledForExecution(apiUrl, token, suiteId);\n if (triageEnabled) {\n console.log(\"\");\n console.log(`Triage: canary triage wait ${suiteId}`);\n }\n }\n\n process.exit(exitCode);\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,7 @@
1
1
  import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
2
+ import {
3
+ readBrandEnv
4
+ } from "./chunk-JZMJUPZP.js";
2
5
 
3
6
  // src/auth.ts
4
7
  import fs from "fs/promises";
@@ -124,20 +127,20 @@ async function resolveConfigInternal(argv, opts) {
124
127
  return null;
125
128
  }
126
129
  const storedApiUrl = await readStoredApiUrl(profile);
127
- const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env].api : void 0) ?? process.env.CANARY_API_URL ?? storedApiUrl ?? "https://api.trycanary.ai";
130
+ const apiUrl = getArgValue(argv, "--api-url") ?? (env ? ENV_URLS[env].api : void 0) ?? readBrandEnv("CANARY_API_URL") ?? storedApiUrl ?? "https://api.trycanary.ai";
128
131
  const explicitToken = getArgValue(argv, "--token");
129
132
  let token;
130
133
  if (explicitToken) {
131
134
  token = explicitToken;
132
135
  } else if (profile) {
133
- token = await readStoredToken(profile) ?? process.env.CANARY_API_TOKEN ?? null;
136
+ token = await readStoredToken(profile) ?? readBrandEnv("CANARY_API_TOKEN") ?? null;
134
137
  if (!token && opts.required) {
135
138
  console.error(`Error: No token found for "${env}" profile.`);
136
139
  console.error(`Run: canary login --env ${env}`);
137
140
  process.exit(1);
138
141
  }
139
142
  } else {
140
- token = process.env.CANARY_API_TOKEN ?? await readStoredToken() ?? null;
143
+ token = readBrandEnv("CANARY_API_TOKEN") ?? await readStoredToken() ?? null;
141
144
  if (!token && opts.required) {
142
145
  console.error("Error: No API token found.");
143
146
  console.error("Run: canary login");
@@ -167,4 +170,4 @@ export {
167
170
  resolveConfig,
168
171
  resolveOptionalConfig
169
172
  };
170
- //# sourceMappingURL=chunk-A4OFTUVV.js.map
173
+ //# sourceMappingURL=chunk-PZ4LCN6K.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport process from 'node:process';\nimport { readBrandEnv } from './brand.js';\n\ntype StoredAuth = {\n token?: string;\n apiUrl?: string;\n orgId?: string;\n orgName?: string;\n};\n\n/** Multi-profile auth file format */\ntype MultiProfileAuth = {\n profiles: Record<string, StoredAuth>;\n default: string;\n};\n\n/** Legacy flat format (single profile) */\ntype LegacyAuth = StoredAuth;\n\nconst AUTH_DIR = path.join(os.homedir(), '.config', 'canary-cli');\nconst AUTH_FILE = path.join(AUTH_DIR, 'auth.json');\n\n/* ── Shared argv helpers ─────────────────────────────────────────────── */\n\nexport function getArgValue(argv: string[], key: string): string | undefined {\n const index = argv.indexOf(key);\n if (index === -1 || index >= argv.length - 1) return undefined;\n return argv[index + 1];\n}\n\nexport function hasFlag(argv: string[], ...flags: string[]): boolean {\n return flags.some((flag) => argv.includes(flag));\n}\n\n/* ── Environment / profile mapping ───────────────────────────────────── */\n\n/** Map --env values to profile names for multi-profile auth storage */\nexport function envToProfile(env: string): string {\n if (env === 'prod' || env === 'production') return 'production';\n if (env === 'dev') return 'dev';\n if (env === 'local') return 'local';\n return env;\n}\n\nconst VALID_ENVS = ['prod', 'dev', 'local'];\n\n/** Environment URL mappings shared across CLI commands */\nexport const ENV_URLS: Record<string, { api: string; app: string }> = {\n prod: {\n api: 'https://api.trycanary.ai',\n app: 'https://app.trycanary.ai',\n },\n production: {\n api: 'https://api.trycanary.ai',\n app: 'https://app.trycanary.ai',\n },\n dev: {\n api: 'https://api.dev.trycanary.ai',\n app: 'https://app.dev.trycanary.ai',\n },\n local: {\n api: 'http://localhost:3000',\n app: 'http://localhost:5173',\n },\n};\n\n/** Derive the web-app URL that corresponds to a given API URL. */\nexport function resolveAppUrl(apiUrl: string): string {\n for (const env of Object.values(ENV_URLS)) {\n if (env.api === apiUrl) return env.app;\n }\n // Fallback for custom API URLs: replace \"api.\" at protocol boundary or \"/api\" path\n return apiUrl.replace(/(:\\/\\/)api\\./, '$1app.').replace(/:3000\\b/, ':5173');\n}\n\nfunction isMultiProfile(data: unknown): data is MultiProfileAuth {\n return typeof data === 'object' && data !== null && 'profiles' in data;\n}\n\n/** Read the raw auth file and normalize to multi-profile format */\nasync function readAuthFile(): Promise<MultiProfileAuth | null> {\n try {\n const content = await fs.readFile(AUTH_FILE, 'utf8');\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n return data;\n }\n\n // Legacy flat format — treat as the \"default\" profile\n const legacy = data as LegacyAuth;\n return {\n profiles: { default: legacy },\n default: 'default',\n };\n } catch {\n return null;\n }\n}\n\nexport async function readStoredAuth(profile?: string): Promise<StoredAuth | null> {\n const auth = await readAuthFile();\n if (!auth) return null;\n\n const key = profile ?? auth.default;\n return auth.profiles[key] ?? null;\n}\n\nexport async function readStoredToken(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.token ?? null;\n}\n\nexport async function readStoredApiUrl(profile?: string): Promise<string | null> {\n const auth = await readStoredAuth(profile);\n return auth?.apiUrl ?? null;\n}\n\n/** Return all stored tokens across every profile */\nexport async function readAllStoredTokens(): Promise<string[]> {\n const auth = await readAuthFile();\n if (!auth) return [];\n return Object.values(auth.profiles)\n .map((p) => p.token)\n .filter((t): t is string => !!t);\n}\n\nexport async function saveAuth(auth: StoredAuth, profile?: string): Promise<string> {\n await fs.mkdir(AUTH_DIR, { recursive: true, mode: 0o700 });\n\n // Read existing file (may be legacy or multi-profile)\n let existing: MultiProfileAuth;\n try {\n const content = await fs.readFile(AUTH_FILE, 'utf8');\n const data = JSON.parse(content) as unknown;\n\n if (isMultiProfile(data)) {\n existing = data;\n } else {\n // Migrate legacy flat format into profiles.default\n existing = {\n profiles: { default: data as LegacyAuth },\n default: 'default',\n };\n }\n } catch {\n existing = { profiles: {}, default: 'default' };\n }\n\n const key = profile ?? 'default';\n existing.profiles[key] = auth;\n\n // Set default to this profile if no default exists yet\n if (!existing.profiles[existing.default]) {\n existing.default = key;\n }\n\n await fs.writeFile(AUTH_FILE, JSON.stringify(existing, null, 2), {\n encoding: 'utf8',\n mode: 0o600,\n });\n return AUTH_FILE;\n}\n\n/* ── Shared config resolution ────────────────────────────────────────── */\n\ntype ResolvedConfig = { apiUrl: string; token: string };\n\nasync function resolveConfigInternal(\n argv: string[],\n opts: { required: boolean }\n): Promise<ResolvedConfig | null> {\n const env = getArgValue(argv, '--env');\n const profile = env ? envToProfile(env) : undefined;\n\n if (env && !ENV_URLS[env]) {\n if (opts.required) {\n console.error(`Unknown environment: ${env}`);\n console.error(`Valid environments: ${VALID_ENVS.join(', ')}`);\n process.exit(1);\n }\n return null;\n }\n\n const storedApiUrl = await readStoredApiUrl(profile);\n const apiUrl =\n getArgValue(argv, '--api-url') ??\n (env ? ENV_URLS[env].api : undefined) ??\n readBrandEnv('CANARY_API_URL') ??\n storedApiUrl ??\n 'https://api.trycanary.ai';\n\n const explicitToken = getArgValue(argv, '--token');\n\n let token: string | null;\n if (explicitToken) {\n token = explicitToken;\n } else if (profile) {\n token = (await readStoredToken(profile)) ?? readBrandEnv('CANARY_API_TOKEN') ?? null;\n if (!token && opts.required) {\n console.error(`Error: No token found for \"${env}\" profile.`);\n console.error(`Run: canary login --env ${env}`);\n process.exit(1);\n }\n } else {\n token = readBrandEnv('CANARY_API_TOKEN') ?? (await readStoredToken()) ?? null;\n if (!token && opts.required) {\n console.error('Error: No API token found.');\n console.error('Run: canary login');\n process.exit(1);\n }\n }\n\n return token ? { apiUrl, token } : null;\n}\n\n/**\n * Resolve API URL and auth token from argv flags, env vars, and stored profiles.\n * Used by knobs, feature-flag, release, and debug-session commands.\n */\nexport async function resolveConfig(argv: string[]): Promise<ResolvedConfig> {\n return (await resolveConfigInternal(argv, { required: true }))!;\n}\n\nexport async function resolveOptionalConfig(argv: string[]): Promise<ResolvedConfig | null> {\n return resolveConfigInternal(argv, { required: false });\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import { createRequire as __cr } from "module"; const require = __cr(import.meta
3
3
  // ../types/src/workflows.ts
4
4
  var USER_SETTABLE_WORKFLOW_STATUSES = [
5
5
  "draft",
6
- "published"
6
+ "approved"
7
7
  ];
8
8
 
9
9
  // ../types/src/sandbox-instance-size.ts
@@ -86,6 +86,7 @@ var NODE_TYPE_LABEL = {
86
86
  notification: "Notification",
87
87
  audio: "Audio input",
88
88
  "audio-assertion": "Audio assertion",
89
+ sandbox_action: "Sandbox action",
89
90
  end: "End"
90
91
  };
91
92
  function normalizeNodeType(input) {
@@ -439,6 +440,7 @@ var MACRO_CATALOG = [
439
440
  ];
440
441
  var VALID_MACRO_NAMES = new Set(MACRO_CATALOG.map((m) => m.name));
441
442
  var MACRO_ARG_COUNTS = new Map(MACRO_CATALOG.map((m) => [m.name, m.args.length]));
443
+ var MACRO_PATTERN = /(?<!\\)\$([A-Z_]+)\(([^)]*)\)/g;
442
444
 
443
445
  // ../types/src/workflow-node-schemas.ts
444
446
  import { z as z4 } from "zod";
@@ -460,9 +462,12 @@ var FormFieldSchema = z4.object({
460
462
  value: z4.string().describe("The value to use for this field"),
461
463
  options: z4.array(z4.object({ value: z4.string(), label: z4.string() })).optional().describe("If type is select/multiselect, provide allowed options here")
462
464
  });
465
+ var AssertionSeverityEnum = z4.enum(["warn", "fail", "fatal"]);
463
466
  var InlineAssertionSchema = z4.object({
464
467
  condition: z4.string().describe("What to verify after the action"),
465
- severity: z4.enum(["warn", "fail"]).describe("warn = log and continue on failure, fail = stop the test"),
468
+ severity: AssertionSeverityEnum.describe(
469
+ "fatal = stop the test immediately on failure, fail = mark the run failed but keep going, warn = log a warning and continue (run still passes)"
470
+ ),
466
471
  agentic: z4.boolean().optional().describe("true if requires visual/complex evaluation that cannot be cached")
467
472
  });
468
473
  var NodeTypeEnum = z4.enum([
@@ -478,7 +483,8 @@ var NodeTypeEnum = z4.enum([
478
483
  "end",
479
484
  "notification",
480
485
  "audio",
481
- "audio-assertion"
486
+ "audio-assertion",
487
+ "sandbox_action"
482
488
  ]);
483
489
 
484
490
  // ../types/src/browser-viewport.ts
@@ -493,6 +499,60 @@ var BROWSER_VIEWPORT_PRESET_KEYS = Object.keys(
493
499
  BROWSER_VIEWPORT_PRESETS
494
500
  );
495
501
 
502
+ // ../types/src/identifier-stability.ts
503
+ var FRAMEWORK_PREFIX_RE = /^(?::r[0-9a-z]+:?$|radix-|headlessui-|mantine-|react-aria-|react-select-|rc-|menu-v-\d)/i;
504
+ var BARE_HASH_RE = /^[a-f0-9]{8,}$/i;
505
+ var PREFIX_HASH_RE = /^[a-z]+-[0-9a-f]{6,}$/i;
506
+ var UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
507
+ var ULID_RE = /\b[0-9A-HJKMNP-TV-Z]{26}\b/i;
508
+ var DIGIT_RUN_RE = /(?:^|[^0-9A-Za-z])(\d{6,})(?![0-9A-Za-z])/;
509
+ var TRAILING_COUNTER_RE = /[-_](\d{3,})$/;
510
+ var MIN_PREFIX_LEN = 3;
511
+ function usablePrefix(prefix) {
512
+ return prefix.length >= MIN_PREFIX_LEN && /[a-z]/i.test(prefix);
513
+ }
514
+ function earliestVolatileTokenIndex(value) {
515
+ let best = null;
516
+ const consider = (idx) => {
517
+ if (idx !== void 0 && idx >= 0 && (best === null || idx < best)) best = idx;
518
+ };
519
+ consider(UUID_RE.exec(value)?.index);
520
+ consider(ULID_RE.exec(value)?.index);
521
+ const digit = DIGIT_RUN_RE.exec(value);
522
+ if (digit) consider(digit.index + (digit[0].length - digit[1].length));
523
+ const counter = TRAILING_COUNTER_RE.exec(value);
524
+ if (counter) consider(counter.index + 1);
525
+ return best;
526
+ }
527
+ function classifyIdentifier(value) {
528
+ if (!value) return { kind: "volatile" };
529
+ if (FRAMEWORK_PREFIX_RE.test(value) || BARE_HASH_RE.test(value) || PREFIX_HASH_RE.test(value)) {
530
+ return { kind: "volatile" };
531
+ }
532
+ const tokenIdx = earliestVolatileTokenIndex(value);
533
+ if (tokenIdx !== null) {
534
+ const prefix = value.slice(0, tokenIdx);
535
+ return usablePrefix(prefix) ? { kind: "stable-prefix", prefix } : { kind: "volatile" };
536
+ }
537
+ return { kind: "stable" };
538
+ }
539
+ function isVolatileIdentifier(value) {
540
+ return classifyIdentifier(value).kind !== "stable";
541
+ }
542
+ function stableIdentifierPrefix(value) {
543
+ const c = classifyIdentifier(value);
544
+ return c.kind === "stable-prefix" ? c.prefix : null;
545
+ }
546
+ function escapeCssAttrValue(value) {
547
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
548
+ }
549
+ function stableAttrSelector(attr, value) {
550
+ const c = classifyIdentifier(value);
551
+ if (c.kind === "stable") return `[${attr}="${escapeCssAttrValue(value)}"]`;
552
+ if (c.kind === "stable-prefix") return `[${attr}^="${escapeCssAttrValue(c.prefix)}"]`;
553
+ return null;
554
+ }
555
+
496
556
  // ../types/src/index.ts
497
557
  var PLAYBOOK_STEP_ACTIONS = [
498
558
  "click",
@@ -598,8 +658,12 @@ export {
598
658
  INSTANCE_SIZE_SPEC,
599
659
  parseInstanceSize,
600
660
  VALID_MACRO_NAMES,
661
+ MACRO_PATTERN,
601
662
  InlineAssertionSchema,
602
663
  NODE_TYPE_LABEL,
603
- normalizeNodeType
664
+ normalizeNodeType,
665
+ isVolatileIdentifier,
666
+ stableIdentifierPrefix,
667
+ stableAttrSelector
604
668
  };
605
- //# sourceMappingURL=chunk-SJJW77WN.js.map
669
+ //# sourceMappingURL=chunk-QGI4UCJX.js.map