@runcofactor/cli 0.16.0 → 0.18.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 (129) hide show
  1. package/dist/{account-ZMV7NEFM.js → account-ZSM2Q3RB.js} +4 -4
  2. package/dist/{api-catalog-6HNOGVPA.js → api-catalog-MMPFWS52.js} +4 -4
  3. package/dist/{attachment-QAHR54HQ.js → attachment-NSYL5X3S.js} +4 -4
  4. package/dist/{augmentor-7T3L6ZR4.js → augmentor-LSJLMJYP.js} +4 -4
  5. package/dist/{auth-DURHUZDW.js → auth-OYF5A4BY.js} +4 -2
  6. package/dist/{chunk-RGDIVW3O.js → chunk-2R6544Z3.js} +2 -2
  7. package/dist/{chunk-AI3Y3AZH.js → chunk-7OO3RJYR.js} +1396 -185
  8. package/dist/chunk-7OO3RJYR.js.map +1 -0
  9. package/dist/{chunk-4NRDONSC.js → chunk-A6TH5VUI.js} +209 -79
  10. package/dist/chunk-A6TH5VUI.js.map +1 -0
  11. package/dist/{chunk-CEXV3MOC.js → chunk-E2FUQJVO.js} +4 -4
  12. package/dist/{chunk-D6AVOPOS.js → chunk-GFP4UEUT.js} +3 -3
  13. package/dist/{chunk-G5SXUXSU.js → chunk-H7ZML5QL.js} +2 -2
  14. package/dist/{chunk-LFFWD5JZ.js → chunk-IHCPMYKU.js} +3 -3
  15. package/dist/{chunk-UAJUZLDE.js → chunk-PVWG3V62.js} +3 -3
  16. package/dist/{chunk-3CZU7XCS.js → chunk-PWZ4ULCB.js} +20 -14
  17. package/dist/chunk-PWZ4ULCB.js.map +1 -0
  18. package/dist/{chunk-A4ISG3VX.js → chunk-U7OMQJ7V.js} +52 -4
  19. package/dist/chunk-U7OMQJ7V.js.map +1 -0
  20. package/dist/{chunk-DT2CEH2V.js → chunk-VHVOTGBQ.js} +3 -3
  21. package/dist/{chunk-IGHDIWZU.js → chunk-VYTIJ6HJ.js} +17 -2
  22. package/dist/{chunk-IGHDIWZU.js.map → chunk-VYTIJ6HJ.js.map} +1 -1
  23. package/dist/{credential-X6QKX473.js → credential-WOATCWOU.js} +4 -4
  24. package/dist/{credential-template-GD3D4MBI.js → credential-template-Q3DQE2AH.js} +4 -4
  25. package/dist/{debug-workflow-AAEOAME2.js → debug-workflow-ZNP5XZQF.js} +4 -4
  26. package/dist/{dist-QLTVBUMD.js → dist-4COOAKY3.js} +22 -2
  27. package/dist/{docs-BVUEXBY3.js → docs-XVU4LA7G.js} +4 -4
  28. package/dist/{doctor-RHMKD4W5.js → doctor-PGNHD3TI.js} +3 -3
  29. package/dist/{environment-WE7VVE6I.js → environment-MRDNWU47.js} +4 -4
  30. package/dist/{feature-flag-VJO2TJFT.js → feature-flag-D3DQZ6DX.js} +4 -4
  31. package/dist/index.js +51 -48
  32. package/dist/index.js.map +1 -1
  33. package/dist/{init-RQHHKJJD.js → init-DHWKUYIR.js} +4 -4
  34. package/dist/{install-CQLHNZ6U.js → install-DMEFGQHD.js} +4 -4
  35. package/dist/{issues-HK2JA5JS.js → issues-E4YQ4DVC.js} +4 -4
  36. package/dist/{knobs-ETJAV6YM.js → knobs-NA6SPLHX.js} +4 -4
  37. package/dist/{list-F6DGGW3Z.js → list-GJNGFQMC.js} +4 -4
  38. package/dist/{local-CMAY54I5.js → local-NSKOZHHA.js} +6 -6
  39. package/dist/{login-6CKG664C.js → login-AMSRVPXG.js} +4 -4
  40. package/dist/{mcp-GC2WOOZO.js → mcp-57VVZTXM.js} +5 -5
  41. package/dist/{metrics-IXICURAA.js → metrics-DM4YQQZN.js} +4 -4
  42. package/dist/{product-BUJWPYKB.js → product-WGOLTBRI.js} +4 -4
  43. package/dist/{property-4EXPWRNT.js → property-B7QMGLVW.js} +4 -4
  44. package/dist/{psql-XGLBI6T7.js → psql-4JHPJ2CO.js} +3 -3
  45. package/dist/{record-HL4QA7OX.js → record-RWVGZZSW.js} +5 -5
  46. package/dist/{redis-VJGCUJPV.js → redis-N44ZZ6VB.js} +3 -3
  47. package/dist/{release-2SMK5Y3V.js → release-R7GKLEAI.js} +4 -4
  48. package/dist/{sandbox-ORAPKOH3.js → sandbox-6D54ZQCG.js} +5 -5
  49. package/dist/{scenarios-DM5FDCAF.js → scenarios-WUUUKP2S.js} +4 -4
  50. package/dist/{session-X6IYGWYG.js → session-GDCMTNB3.js} +6 -6
  51. package/dist/{skill-ZU5V2EE5.js → skill-3SP7MAA4.js} +153 -2
  52. package/dist/skill-3SP7MAA4.js.map +1 -0
  53. package/dist/skills/optimize-cache/SKILL.md +25 -0
  54. package/dist/{spec-KO5QD6AY.js → spec-ESPA3H26.js} +3 -3
  55. package/dist/{src-DSD34JIX.js → src-5YXGM2KB.js} +18 -2
  56. package/dist/src-5YXGM2KB.js.map +1 -0
  57. package/dist/{src-PDYXACRP.js → src-B7CPYFLS.js} +4 -2
  58. package/dist/{src-HTGNKBUF.js → src-GRO2QR7U.js} +61 -23
  59. package/dist/{src-HTGNKBUF.js.map → src-GRO2QR7U.js.map} +1 -1
  60. package/dist/{start-2ECN4LX5.js → start-KDDRLUFN.js} +4 -4
  61. package/dist/{tag-R5I72HJ6.js → tag-DAGHQZAR.js} +5 -5
  62. package/dist/{task-V3JMSJIM.js → task-PRR75HI4.js} +4 -4
  63. package/dist/{test-suite-NOYVK6YX.js → test-suite-O3UY7EER.js} +6 -6
  64. package/dist/{trace-NOYEKNO4.js → trace-7LRHSPWN.js} +4 -4
  65. package/dist/{triage-4RFATHBE.js → triage-2BKUBMMX.js} +5 -5
  66. package/dist/{utilities-3WMXE743.js → utilities-O3OO64T5.js} +4 -4
  67. package/dist/{workflow-DC7WOTTW.js → workflow-5BFRS4CE.js} +10 -11
  68. package/dist/workflow-5BFRS4CE.js.map +1 -0
  69. package/dist/{workflow-cache-SDSI6SMF.js → workflow-cache-VVP3SPFM.js} +7 -7
  70. package/dist/{workflow-debug-6UQYP67Q.js → workflow-debug-XGOHNZ4E.js} +7 -7
  71. package/dist/{workflow-proposals-67AVGMHX.js → workflow-proposals-PGURJ6ZI.js} +4 -4
  72. package/package.json +5 -5
  73. package/dist/chunk-3CZU7XCS.js.map +0 -1
  74. package/dist/chunk-4NRDONSC.js.map +0 -1
  75. package/dist/chunk-A4ISG3VX.js.map +0 -1
  76. package/dist/chunk-AI3Y3AZH.js.map +0 -1
  77. package/dist/skill-ZU5V2EE5.js.map +0 -1
  78. package/dist/src-DSD34JIX.js.map +0 -1
  79. package/dist/workflow-DC7WOTTW.js.map +0 -1
  80. /package/dist/{account-ZMV7NEFM.js.map → account-ZSM2Q3RB.js.map} +0 -0
  81. /package/dist/{api-catalog-6HNOGVPA.js.map → api-catalog-MMPFWS52.js.map} +0 -0
  82. /package/dist/{attachment-QAHR54HQ.js.map → attachment-NSYL5X3S.js.map} +0 -0
  83. /package/dist/{augmentor-7T3L6ZR4.js.map → augmentor-LSJLMJYP.js.map} +0 -0
  84. /package/dist/{auth-DURHUZDW.js.map → auth-OYF5A4BY.js.map} +0 -0
  85. /package/dist/{chunk-RGDIVW3O.js.map → chunk-2R6544Z3.js.map} +0 -0
  86. /package/dist/{chunk-CEXV3MOC.js.map → chunk-E2FUQJVO.js.map} +0 -0
  87. /package/dist/{chunk-D6AVOPOS.js.map → chunk-GFP4UEUT.js.map} +0 -0
  88. /package/dist/{chunk-G5SXUXSU.js.map → chunk-H7ZML5QL.js.map} +0 -0
  89. /package/dist/{chunk-LFFWD5JZ.js.map → chunk-IHCPMYKU.js.map} +0 -0
  90. /package/dist/{chunk-UAJUZLDE.js.map → chunk-PVWG3V62.js.map} +0 -0
  91. /package/dist/{chunk-DT2CEH2V.js.map → chunk-VHVOTGBQ.js.map} +0 -0
  92. /package/dist/{credential-X6QKX473.js.map → credential-WOATCWOU.js.map} +0 -0
  93. /package/dist/{credential-template-GD3D4MBI.js.map → credential-template-Q3DQE2AH.js.map} +0 -0
  94. /package/dist/{debug-workflow-AAEOAME2.js.map → debug-workflow-ZNP5XZQF.js.map} +0 -0
  95. /package/dist/{dist-QLTVBUMD.js.map → dist-4COOAKY3.js.map} +0 -0
  96. /package/dist/{docs-BVUEXBY3.js.map → docs-XVU4LA7G.js.map} +0 -0
  97. /package/dist/{doctor-RHMKD4W5.js.map → doctor-PGNHD3TI.js.map} +0 -0
  98. /package/dist/{environment-WE7VVE6I.js.map → environment-MRDNWU47.js.map} +0 -0
  99. /package/dist/{feature-flag-VJO2TJFT.js.map → feature-flag-D3DQZ6DX.js.map} +0 -0
  100. /package/dist/{init-RQHHKJJD.js.map → init-DHWKUYIR.js.map} +0 -0
  101. /package/dist/{install-CQLHNZ6U.js.map → install-DMEFGQHD.js.map} +0 -0
  102. /package/dist/{issues-HK2JA5JS.js.map → issues-E4YQ4DVC.js.map} +0 -0
  103. /package/dist/{knobs-ETJAV6YM.js.map → knobs-NA6SPLHX.js.map} +0 -0
  104. /package/dist/{list-F6DGGW3Z.js.map → list-GJNGFQMC.js.map} +0 -0
  105. /package/dist/{local-CMAY54I5.js.map → local-NSKOZHHA.js.map} +0 -0
  106. /package/dist/{login-6CKG664C.js.map → login-AMSRVPXG.js.map} +0 -0
  107. /package/dist/{mcp-GC2WOOZO.js.map → mcp-57VVZTXM.js.map} +0 -0
  108. /package/dist/{metrics-IXICURAA.js.map → metrics-DM4YQQZN.js.map} +0 -0
  109. /package/dist/{product-BUJWPYKB.js.map → product-WGOLTBRI.js.map} +0 -0
  110. /package/dist/{property-4EXPWRNT.js.map → property-B7QMGLVW.js.map} +0 -0
  111. /package/dist/{psql-XGLBI6T7.js.map → psql-4JHPJ2CO.js.map} +0 -0
  112. /package/dist/{record-HL4QA7OX.js.map → record-RWVGZZSW.js.map} +0 -0
  113. /package/dist/{redis-VJGCUJPV.js.map → redis-N44ZZ6VB.js.map} +0 -0
  114. /package/dist/{release-2SMK5Y3V.js.map → release-R7GKLEAI.js.map} +0 -0
  115. /package/dist/{sandbox-ORAPKOH3.js.map → sandbox-6D54ZQCG.js.map} +0 -0
  116. /package/dist/{scenarios-DM5FDCAF.js.map → scenarios-WUUUKP2S.js.map} +0 -0
  117. /package/dist/{session-X6IYGWYG.js.map → session-GDCMTNB3.js.map} +0 -0
  118. /package/dist/{spec-KO5QD6AY.js.map → spec-ESPA3H26.js.map} +0 -0
  119. /package/dist/{src-PDYXACRP.js.map → src-B7CPYFLS.js.map} +0 -0
  120. /package/dist/{start-2ECN4LX5.js.map → start-KDDRLUFN.js.map} +0 -0
  121. /package/dist/{tag-R5I72HJ6.js.map → tag-DAGHQZAR.js.map} +0 -0
  122. /package/dist/{task-V3JMSJIM.js.map → task-PRR75HI4.js.map} +0 -0
  123. /package/dist/{test-suite-NOYVK6YX.js.map → test-suite-O3UY7EER.js.map} +0 -0
  124. /package/dist/{trace-NOYEKNO4.js.map → trace-7LRHSPWN.js.map} +0 -0
  125. /package/dist/{triage-4RFATHBE.js.map → triage-2BKUBMMX.js.map} +0 -0
  126. /package/dist/{utilities-3WMXE743.js.map → utilities-O3OO64T5.js.map} +0 -0
  127. /package/dist/{workflow-cache-SDSI6SMF.js.map → workflow-cache-VVP3SPFM.js.map} +0 -0
  128. /package/dist/{workflow-debug-6UQYP67Q.js.map → workflow-debug-XGOHNZ4E.js.map} +0 -0
  129. /package/dist/{workflow-proposals-67AVGMHX.js.map → workflow-proposals-PGURJ6ZI.js.map} +0 -0
@@ -3,10 +3,10 @@ import {
3
3
  apiFetch,
4
4
  exitOnFailure,
5
5
  isTerminalFailure
6
- } from "./chunk-3CZU7XCS.js";
6
+ } from "./chunk-PWZ4ULCB.js";
7
7
  import {
8
8
  getArgValue
9
- } from "./chunk-IGHDIWZU.js";
9
+ } from "./chunk-VYTIJ6HJ.js";
10
10
  import {
11
11
  getCanaryTmpDir
12
12
  } from "./chunk-JWHYXEA2.js";
@@ -364,4 +364,4 @@ export {
364
364
  uploadStorageState,
365
365
  fetchList
366
366
  };
367
- //# sourceMappingURL=chunk-UAJUZLDE.js.map
367
+ //# sourceMappingURL=chunk-PVWG3V62.js.map
@@ -23,7 +23,21 @@ function isSuperadminToken(token) {
23
23
  }
24
24
 
25
25
  // src/api-fetch.ts
26
+ import process2 from "process";
27
+
28
+ // src/debug.ts
26
29
  import process from "process";
30
+ function isDebug() {
31
+ return process.env.CANARY_DEBUG === "1" || process.env.CANARY_DEBUG === "true" || process.argv.includes("--debug");
32
+ }
33
+ function debug(line) {
34
+ if (isDebug()) {
35
+ process.stderr.write(`[canary-debug] ${line}
36
+ `);
37
+ }
38
+ }
39
+
40
+ // src/api-fetch.ts
27
41
  var CANARY_REQUEST_ID_HEADER = "x-canary-request-id";
28
42
  var DEFAULT_TIMEOUT_MS = 3e4;
29
43
  var JWT_EXP_SKEW_SEC = 60;
@@ -34,15 +48,6 @@ function redactUrl(url) {
34
48
  const q = url.indexOf("?");
35
49
  return q === -1 ? url : `${url.slice(0, q)}?[redacted]`;
36
50
  }
37
- function isDebug() {
38
- return process.env.CANARY_DEBUG === "1" || process.env.CANARY_DEBUG === "true" || process.argv.includes("--debug");
39
- }
40
- function debug(line) {
41
- if (isDebug()) {
42
- process.stderr.write(`[canary-debug] ${line}
43
- `);
44
- }
45
- }
46
51
  var TLS_CODES = /* @__PURE__ */ new Set([
47
52
  "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
48
53
  "UNABLE_TO_GET_ISSUER_CERT",
@@ -85,7 +90,7 @@ function maybeWarnPlaintextAuth(url, hasAuthHeader2) {
85
90
  return;
86
91
  }
87
92
  warnedAboutPlaintext = true;
88
- process.stderr.write(
93
+ process2.stderr.write(
89
94
  `[canary] WARNING: sending Authorization over plaintext http:// (${url}). Your API token may be exposed to the network. Use https:// in CANARY_API_URL.
90
95
  `
91
96
  );
@@ -243,13 +248,13 @@ function isTerminalFailure(failure) {
243
248
  return failure.kind !== "http-error";
244
249
  }
245
250
  function exitOnFailure(failure) {
246
- process.stderr.write(`Error: ${failure.message}
251
+ process2.stderr.write(`Error: ${failure.message}
247
252
  `);
248
253
  if (isDebug() && failure.body && failure.kind !== "html-body") {
249
- process.stderr.write(`[canary-debug] body: ${failure.body}
254
+ process2.stderr.write(`[canary-debug] body: ${failure.body}
250
255
  `);
251
256
  }
252
- process.exit(1);
257
+ process2.exit(1);
253
258
  }
254
259
  async function fetchOrExit(url, init = {}) {
255
260
  const result = await apiFetch(url, init);
@@ -262,6 +267,7 @@ async function fetchOrExit(url, init = {}) {
262
267
  }
263
268
 
264
269
  export {
270
+ debug,
265
271
  decodeJwtPayload,
266
272
  isSuperadminToken,
267
273
  apiFetch,
@@ -269,4 +275,4 @@ export {
269
275
  exitOnFailure,
270
276
  fetchOrExit
271
277
  };
272
- //# sourceMappingURL=chunk-3CZU7XCS.js.map
278
+ //# sourceMappingURL=chunk-PWZ4ULCB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/jwt.ts","../src/api-fetch.ts","../src/debug.ts"],"sourcesContent":["/**\n * JWT payload decoder for CLI superadmin detection.\n *\n * Decodes the payload section of a JWT locally (no signature verification,\n * no network calls). Used to conditionally show superadmin CLI commands.\n *\n * @module\n */\n\n/**\n * Decode the payload (second segment) of a JWT without verifying the signature.\n * Returns the parsed JSON object, or `null` if the token is malformed.\n */\nexport function decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split(\".\");\n if (parts.length !== 3) return null;\n\n const base64 = parts[1]!.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const json = Buffer.from(base64, \"base64\").toString(\"utf8\");\n const payload: unknown = JSON.parse(json);\n\n if (typeof payload !== \"object\" || payload === null || Array.isArray(payload)) {\n return null;\n }\n return payload as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Returns `true` when the JWT's `is_superadmin` claim is exactly `true`.\n * Returns `false` for any invalid, missing, or non-superadmin token.\n */\nexport function isSuperadminToken(token: string | null | undefined): boolean {\n if (!token) return false;\n const payload = decodeJwtPayload(token);\n return payload?.is_superadmin === true;\n}\n","/**\n * Central fetch wrapper for the Canary CLI.\n *\n * Exists to classify failures so corporate-proxy interference (Zscaler,\n * Netskope, Cloudflare Access, etc.) is distinguishable from real auth\n * expiry. Every non-2xx or transport error gets a specific, actionable\n * message instead of the historical blanket \"session may have expired.\"\n *\n * Set CANARY_DEBUG=1 (or pass --debug to the CLI) to log every request\n * and its classification — this is what affected users should share\n * with us or their IT/Zscaler admin.\n */\n\nimport process from 'node:process';\nimport { debug, isDebug } from './debug.js';\nimport { decodeJwtPayload } from './jwt.js';\n\n/** Header every canary API response carries. Absence => response did not come from our API. */\nconst CANARY_REQUEST_ID_HEADER = 'x-canary-request-id';\n\n/** Default per-request timeout. Zscaler-half-opened sockets have been observed\n * hanging indefinitely; the CLI should give up and classify rather than hang. */\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** Treat a JWT exp within this grace window as still valid — tolerates the\n * typical handful of seconds of client-clock skew so we don't mislabel a\n * 30-second-fast clock as `token-revoked`. */\nconst JWT_EXP_SKEW_SEC = 60;\n\n/**\n * Best-effort scrub of `Bearer <token>` and bare three-segment JWTs from\n * error text before we print it. Doctor output is explicitly coached to be\n * shared with IT — if a proxy block page reflects the Authorization header,\n * we do not want the token in that paste.\n */\nfunction redactSecrets(text: string): string {\n return text\n .replace(/Bearer\\s+[A-Za-z0-9._-]+/gi, 'Bearer [redacted]')\n .replace(/\\beyJ[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\b/g, '[redacted-jwt]');\n}\n\n/** Strip query string — query params sometimes carry one-time tokens. */\nfunction redactUrl(url: string): string {\n const q = url.indexOf('?');\n return q === -1 ? url : `${url.slice(0, q)}?[redacted]`;\n}\n\nexport type FailureKind =\n | 'tls'\n | 'network'\n | 'html-body'\n | 'proxy-injected-4xx'\n | 'token-revoked'\n | 'token-expired'\n | 'unauthorized'\n | 'http-error';\n\nexport interface ClassifiedFailure {\n kind: FailureKind;\n status: number | null;\n url: string;\n message: string;\n /**\n * Response body text. Full body for `http-error` (so callers can parse\n * structured error payloads); truncated snippet for proxy/TLS failures\n * where the body is likely an HTML block page.\n */\n body?: string;\n /** Best-effort content-type. */\n contentType?: string;\n /** Raw error object for transport failures. */\n cause?: unknown;\n /**\n * Value of `x-canary-request-id` on the response, if any. Present means\n * the response came from our API (even for `token-revoked` / `token-expired`\n * / `unauthorized`); absent means the response was injected or the request\n * never reached us.\n */\n requestId?: string | null;\n}\n\n/**\n * Node/undici TLS error codes that indicate a MITM proxy (Zscaler et al.)\n * is intercepting HTTPS without the user trusting the proxy's root CA.\n */\nconst TLS_CODES = new Set([\n 'UNABLE_TO_VERIFY_LEAF_SIGNATURE',\n 'UNABLE_TO_GET_ISSUER_CERT',\n 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY',\n 'SELF_SIGNED_CERT_IN_CHAIN',\n 'DEPTH_ZERO_SELF_SIGNED_CERT',\n 'CERT_HAS_EXPIRED',\n 'CERT_UNTRUSTED',\n 'ERR_TLS_CERT_ALTNAME_INVALID',\n 'CERT_SIGNATURE_FAILURE',\n]);\n\nfunction extractErrorCode(err: unknown): string | null {\n if (!err || typeof err !== 'object') return null;\n const withCode = err as { code?: unknown; cause?: unknown };\n if (typeof withCode.code === 'string') return withCode.code;\n if (withCode.cause) return extractErrorCode(withCode.cause);\n return null;\n}\n\nfunction looksLikeHtml(body: string, contentType: string | null): boolean {\n if (contentType && contentType.toLowerCase().includes('text/html')) return true;\n const trimmed = body.trimStart();\n return trimmed.startsWith('<!') || trimmed.startsWith('<html') || trimmed.startsWith('<HTML');\n}\n\n/** Decode JWT `exp` and compare to now (with a small skew grace); null if undecodable. */\nfunction jwtExpStatus(token: string | undefined): 'expired' | 'valid' | 'unknown' {\n if (!token) return 'unknown';\n const payload = decodeJwtPayload(token);\n const exp = payload && typeof payload.exp === 'number' ? (payload.exp as number) : null;\n if (exp === null) return 'unknown';\n const nowSec = Math.floor(Date.now() / 1000);\n return exp < nowSec - JWT_EXP_SKEW_SEC ? 'expired' : 'valid';\n}\n\n/** Warn once per process when sending Authorization to a non-local http:// URL. */\nlet warnedAboutPlaintext = false;\nfunction maybeWarnPlaintextAuth(url: string, hasAuthHeader: boolean): void {\n if (!hasAuthHeader || warnedAboutPlaintext) return;\n if (!url.startsWith('http://')) return;\n try {\n const host = new URL(url).hostname;\n if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return;\n } catch {\n return;\n }\n warnedAboutPlaintext = true;\n process.stderr.write(\n `[canary] WARNING: sending Authorization over plaintext http:// (${url}). ` +\n 'Your API token may be exposed to the network. Use https:// in CANARY_API_URL.\\n'\n );\n}\n\nfunction hasAuthHeader(init: RequestInit): boolean {\n const h = init.headers;\n if (!h) return false;\n if (h instanceof Headers) return h.has('authorization');\n if (Array.isArray(h)) return h.some(([k]) => k.toLowerCase() === 'authorization');\n return Object.keys(h).some((k) => k.toLowerCase() === 'authorization');\n}\n\n/**\n * Wrap global `fetch` with classification. Always resolves — never throws —\n * so callers can make a single uniform decision from the returned shape.\n */\nexport async function apiFetch(\n url: string,\n init: RequestInit & { token?: string; timeoutMs?: number } = {}\n): Promise<\n | { ok: true; response: Response; requestId: string | null }\n | { ok: false; failure: ClassifiedFailure }\n> {\n const { token, timeoutMs, ...fetchInit } = init;\n maybeWarnPlaintextAuth(url, hasAuthHeader(fetchInit));\n debug(`→ ${fetchInit.method ?? 'GET'} ${redactUrl(url)}`);\n\n // Merge caller-supplied signal with our timeout so both can abort.\n // timeoutMs <= 0 disables the timeout (use for long-lived SSE streams).\n const timeout = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const timeoutSignal = timeout > 0 ? AbortSignal.timeout(timeout) : null;\n const effectiveSignal =\n fetchInit.signal && timeoutSignal\n ? AbortSignal.any([fetchInit.signal, timeoutSignal])\n : (fetchInit.signal ?? timeoutSignal ?? undefined);\n\n let response: Response;\n try {\n response = await fetch(url, { ...fetchInit, signal: effectiveSignal });\n } catch (err) {\n const isTimeout =\n (err as { name?: string })?.name === 'TimeoutError' ||\n Boolean(timeoutSignal?.aborted);\n if (isTimeout) {\n const msg = `Request to ${redactUrl(url)} timed out after ${timeout}ms.`;\n debug(`✖ network timeout`);\n return {\n ok: false,\n failure: { kind: 'network', status: null, url, message: msg, cause: err },\n };\n }\n const code = extractErrorCode(err);\n if (code && TLS_CODES.has(code)) {\n const msg = [\n `TLS verification failed talking to ${redactUrl(url)} (${code}).`,\n 'A corporate proxy (Zscaler, Netskope, ZPA, Cloudflare Access) is likely intercepting HTTPS.',\n 'Fixes: point Node at the proxy\\'s root CA with',\n ' NODE_EXTRA_CA_CERTS=/path/to/corporate-root.pem canary …',\n 'or on Node 20+ enable the system trust store with',\n ' NODE_OPTIONS=--use-system-ca canary …',\n ].join('\\n');\n debug(`✖ tls ${code}`);\n return { ok: false, failure: { kind: 'tls', status: null, url, message: msg, cause: err } };\n }\n const msg = `Network error reaching ${redactUrl(url)}: ${String((err as Error).message ?? err)}`;\n debug(`✖ network ${msg}`);\n return { ok: false, failure: { kind: 'network', status: null, url, message: msg, cause: err } };\n }\n\n const requestId = response.headers.get(CANARY_REQUEST_ID_HEADER);\n const contentType = response.headers.get('content-type');\n debug(`← ${response.status} ${redactUrl(url)} (ct=${contentType ?? 'n/a'} rid=${requestId ?? 'none'})`);\n\n if (response.ok) {\n return { ok: true, response, requestId };\n }\n\n // Read body once so we can classify and still return something useful.\n // Scrub secrets before constructing user-facing `snippet`; `bodyText` is\n // kept intact for callers that parse structured JSON error bodies.\n const bodyText = await response.clone().text().catch(() => '');\n const snippet = redactSecrets(bodyText.slice(0, 200));\n\n // HTML on a JSON endpoint => proxy block page or captive portal.\n if (looksLikeHtml(bodyText, contentType)) {\n return {\n ok: false,\n failure: {\n kind: 'html-body',\n status: response.status,\n url,\n contentType: contentType ?? undefined,\n requestId,\n body: snippet,\n message: [\n `Got an HTML response from ${url} (status ${response.status}).`,\n 'A corporate proxy block page or captive portal is intercepting this request — the',\n 'response did not come from the Canary API.',\n 'Check that your proxy (e.g. Zscaler) allowlists the API hostname, and that any',\n 'required sign-in to the proxy itself is current.',\n snippet ? `First bytes: ${snippet.replace(/\\s+/g, ' ')}` : '',\n ]\n .filter(Boolean)\n .join('\\n'),\n },\n };\n }\n\n // 401/403 without our header => not our response.\n if ((response.status === 401 || response.status === 403) && !requestId) {\n return {\n ok: false,\n failure: {\n kind: 'proxy-injected-4xx',\n status: response.status,\n url,\n contentType: contentType ?? undefined,\n requestId,\n body: snippet,\n message: [\n `Got ${response.status} from ${url} but the response is missing the \\`${CANARY_REQUEST_ID_HEADER}\\` header.`,\n 'This response was almost certainly injected by a corporate proxy (Zscaler, Netskope,',\n 'Cloudflare Access) rather than returned by our API — retrying \"canary login\" will not fix it.',\n 'Ask your IT team to allowlist the API host, or re-auth to the proxy and try again.',\n ].join('\\n'),\n },\n };\n }\n\n if (response.status === 401) {\n const jwtStatus = jwtExpStatus(token);\n if (jwtStatus === 'valid') {\n return {\n ok: false,\n failure: {\n kind: 'token-revoked',\n status: 401,\n url,\n requestId,\n body: snippet,\n message: [\n 'Server rejected a token that is still valid locally (unexpired JWT).',\n 'Likely causes: the token was revoked, the server\\'s signing key rotated, or the',\n 'user/org was deactivated. `canary login` will issue a fresh token.',\n ].join('\\n'),\n },\n };\n }\n if (jwtStatus === 'expired') {\n return {\n ok: false,\n failure: {\n kind: 'token-expired',\n status: 401,\n url,\n requestId,\n body: snippet,\n message: 'Your stored token has expired. Run: canary login',\n },\n };\n }\n return {\n ok: false,\n failure: {\n kind: 'unauthorized',\n status: 401,\n url,\n requestId,\n body: snippet,\n message: 'Unauthorized. Run: canary login',\n },\n };\n }\n\n return {\n ok: false,\n failure: {\n kind: 'http-error',\n status: response.status,\n url,\n contentType: contentType ?? undefined,\n requestId,\n body: bodyText,\n message: `${response.status} ${response.statusText}${snippet ? `: ${snippet}` : ''}`,\n },\n };\n}\n\n/**\n * Kinds of failure the user can't recover from by retrying the same call —\n * no token on disk, proxy interception, TLS mismatch, rejected JWT. Callers\n * should `exitOnFailure` immediately for these. `http-error` is excluded\n * because the caller often needs to pretty-print a structured error body.\n */\nexport function isTerminalFailure(failure: ClassifiedFailure): boolean {\n return failure.kind !== 'http-error';\n}\n\n/**\n * Print a classified failure and exit(1). Use from command handlers that\n * have no way to recover — matches the historical behavior of the various\n * `if (res.status === 401) { console.error(...); process.exit(1); }` blocks.\n */\nexport function exitOnFailure(failure: ClassifiedFailure): never {\n process.stderr.write(`Error: ${failure.message}\\n`);\n if (isDebug() && failure.body && failure.kind !== 'html-body') {\n process.stderr.write(`[canary-debug] body: ${failure.body}\\n`);\n }\n process.exit(1);\n}\n\n/**\n * Drop-in for `fetch()` in CLI call sites that parse the body themselves:\n * exits the process for terminal failures (TLS/proxy/unauthorized) so the\n * user sees a diagnostic, and returns a Response otherwise — whether 2xx\n * or an HTTP error (so the caller can still inspect .status / .json()).\n */\nexport async function fetchOrExit(\n url: string,\n init: RequestInit & { token?: string; timeoutMs?: number } = {}\n): Promise<Response> {\n const result = await apiFetch(url, init);\n if (result.ok) return result.response;\n if (isTerminalFailure(result.failure)) exitOnFailure(result.failure);\n // http-error: reconstruct a Response the caller can json() / read status on.\n return new Response(result.failure.body ?? '', {\n status: result.failure.status ?? 500,\n headers: result.failure.contentType\n ? { 'content-type': result.failure.contentType }\n : undefined,\n });\n}\n","import process from 'node:process';\n\n/**\n * Whether verbose CLI diagnostics are enabled. Opt-in only, so normal runs stay\n * quiet — set CANARY_DEBUG=1 (or =true) or pass --debug. Diagnostics always go\n * to stderr so they never pollute stdout-piped (`--json`) output.\n */\nexport function isDebug(): boolean {\n return (\n process.env.CANARY_DEBUG === '1' ||\n process.env.CANARY_DEBUG === 'true' ||\n process.argv.includes('--debug')\n );\n}\n\n/** Write a diagnostic line to stderr, but only when debug mode is enabled. */\nexport function debug(line: string): void {\n if (isDebug()) {\n process.stderr.write(`[canary-debug] ${line}\\n`);\n }\n}\n"],"mappings":";;;AAaO,SAAS,iBAAiB,OAA+C;AAC9E,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,GAAG;AAC7B,QAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,UAAM,SAAS,MAAM,CAAC,EAAG,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,OAAO,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,MAAM;AAC1D,UAAM,UAAmB,KAAK,MAAM,IAAI;AAExC,QAAI,OAAO,YAAY,YAAY,YAAY,QAAQ,MAAM,QAAQ,OAAO,GAAG;AAC7E,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,kBAAkB,OAA2C;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,iBAAiB,KAAK;AACtC,SAAO,SAAS,kBAAkB;AACpC;;;AC1BA,OAAOA,cAAa;;;ACbpB,OAAO,aAAa;AAOb,SAAS,UAAmB;AACjC,SACE,QAAQ,IAAI,iBAAiB,OAC7B,QAAQ,IAAI,iBAAiB,UAC7B,QAAQ,KAAK,SAAS,SAAS;AAEnC;AAGO,SAAS,MAAM,MAAoB;AACxC,MAAI,QAAQ,GAAG;AACb,YAAQ,OAAO,MAAM,kBAAkB,IAAI;AAAA,CAAI;AAAA,EACjD;AACF;;;ADFA,IAAM,2BAA2B;AAIjC,IAAM,qBAAqB;AAK3B,IAAM,mBAAmB;AAQzB,SAAS,cAAc,MAAsB;AAC3C,SAAO,KACJ,QAAQ,8BAA8B,mBAAmB,EACzD,QAAQ,mEAAmE,gBAAgB;AAChG;AAGA,SAAS,UAAU,KAAqB;AACtC,QAAM,IAAI,IAAI,QAAQ,GAAG;AACzB,SAAO,MAAM,KAAK,MAAM,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC;AAC5C;AAwCA,IAAM,YAAY,oBAAI,IAAI;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,iBAAiB,KAA6B;AACrD,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,QAAM,WAAW;AACjB,MAAI,OAAO,SAAS,SAAS,SAAU,QAAO,SAAS;AACvD,MAAI,SAAS,MAAO,QAAO,iBAAiB,SAAS,KAAK;AAC1D,SAAO;AACT;AAEA,SAAS,cAAc,MAAc,aAAqC;AACxE,MAAI,eAAe,YAAY,YAAY,EAAE,SAAS,WAAW,EAAG,QAAO;AAC3E,QAAM,UAAU,KAAK,UAAU;AAC/B,SAAO,QAAQ,WAAW,IAAI,KAAK,QAAQ,WAAW,OAAO,KAAK,QAAQ,WAAW,OAAO;AAC9F;AAGA,SAAS,aAAa,OAA4D;AAChF,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAU,iBAAiB,KAAK;AACtC,QAAM,MAAM,WAAW,OAAO,QAAQ,QAAQ,WAAY,QAAQ,MAAiB;AACnF,MAAI,QAAQ,KAAM,QAAO;AACzB,QAAM,SAAS,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAC3C,SAAO,MAAM,SAAS,mBAAmB,YAAY;AACvD;AAGA,IAAI,uBAAuB;AAC3B,SAAS,uBAAuB,KAAaC,gBAA8B;AACzE,MAAI,CAACA,kBAAiB,qBAAsB;AAC5C,MAAI,CAAC,IAAI,WAAW,SAAS,EAAG;AAChC,MAAI;AACF,UAAM,OAAO,IAAI,IAAI,GAAG,EAAE;AAC1B,QAAI,SAAS,eAAe,SAAS,eAAe,SAAS,MAAO;AAAA,EACtE,QAAQ;AACN;AAAA,EACF;AACA,yBAAuB;AACvB,EAAAC,SAAQ,OAAO;AAAA,IACb,mEAAmE,GAAG;AAAA;AAAA,EAExE;AACF;AAEA,SAAS,cAAc,MAA4B;AACjD,QAAM,IAAI,KAAK;AACf,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,aAAa,QAAS,QAAO,EAAE,IAAI,eAAe;AACtD,MAAI,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,YAAY,MAAM,eAAe;AAChF,SAAO,OAAO,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,eAAe;AACvE;AAMA,eAAsB,SACpB,KACA,OAA6D,CAAC,GAI9D;AACA,QAAM,EAAE,OAAO,WAAW,GAAG,UAAU,IAAI;AAC3C,yBAAuB,KAAK,cAAc,SAAS,CAAC;AACpD,QAAM,UAAK,UAAU,UAAU,KAAK,IAAI,UAAU,GAAG,CAAC,EAAE;AAIxD,QAAM,UAAU,aAAa;AAC7B,QAAM,gBAAgB,UAAU,IAAI,YAAY,QAAQ,OAAO,IAAI;AACnE,QAAM,kBACJ,UAAU,UAAU,gBAChB,YAAY,IAAI,CAAC,UAAU,QAAQ,aAAa,CAAC,IAChD,UAAU,UAAU,iBAAiB;AAE5C,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,KAAK,EAAE,GAAG,WAAW,QAAQ,gBAAgB,CAAC;AAAA,EACvE,SAAS,KAAK;AACZ,UAAM,YACH,KAA2B,SAAS,kBACrC,QAAQ,eAAe,OAAO;AAChC,QAAI,WAAW;AACb,YAAMC,OAAM,cAAc,UAAU,GAAG,CAAC,oBAAoB,OAAO;AACnE,YAAM,wBAAmB;AACzB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS,EAAE,MAAM,WAAW,QAAQ,MAAM,KAAK,SAASA,MAAK,OAAO,IAAI;AAAA,MAC1E;AAAA,IACF;AACA,UAAM,OAAO,iBAAiB,GAAG;AACjC,QAAI,QAAQ,UAAU,IAAI,IAAI,GAAG;AAC/B,YAAMA,OAAM;AAAA,QACV,sCAAsC,UAAU,GAAG,CAAC,KAAK,IAAI;AAAA,QAC7D;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AACX,YAAM,cAAS,IAAI,EAAE;AACrB,aAAO,EAAE,IAAI,OAAO,SAAS,EAAE,MAAM,OAAO,QAAQ,MAAM,KAAK,SAASA,MAAK,OAAO,IAAI,EAAE;AAAA,IAC5F;AACA,UAAM,MAAM,0BAA0B,UAAU,GAAG,CAAC,KAAK,OAAQ,IAAc,WAAW,GAAG,CAAC;AAC9F,UAAM,kBAAa,GAAG,EAAE;AACxB,WAAO,EAAE,IAAI,OAAO,SAAS,EAAE,MAAM,WAAW,QAAQ,MAAM,KAAK,SAAS,KAAK,OAAO,IAAI,EAAE;AAAA,EAChG;AAEA,QAAM,YAAY,SAAS,QAAQ,IAAI,wBAAwB;AAC/D,QAAM,cAAc,SAAS,QAAQ,IAAI,cAAc;AACvD,QAAM,UAAK,SAAS,MAAM,IAAI,UAAU,GAAG,CAAC,QAAQ,eAAe,KAAK,QAAQ,aAAa,MAAM,GAAG;AAEtG,MAAI,SAAS,IAAI;AACf,WAAO,EAAE,IAAI,MAAM,UAAU,UAAU;AAAA,EACzC;AAKA,QAAM,WAAW,MAAM,SAAS,MAAM,EAAE,KAAK,EAAE,MAAM,MAAM,EAAE;AAC7D,QAAM,UAAU,cAAc,SAAS,MAAM,GAAG,GAAG,CAAC;AAGpD,MAAI,cAAc,UAAU,WAAW,GAAG;AACxC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,SAAS;AAAA,QACP,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA,aAAa,eAAe;AAAA,QAC5B;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,UACP,6BAA6B,GAAG,YAAY,SAAS,MAAM;AAAA,UAC3D;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU,gBAAgB,QAAQ,QAAQ,QAAQ,GAAG,CAAC,KAAK;AAAA,QAC7D,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAGA,OAAK,SAAS,WAAW,OAAO,SAAS,WAAW,QAAQ,CAAC,WAAW;AACtE,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,SAAS;AAAA,QACP,MAAM;AAAA,QACN,QAAQ,SAAS;AAAA,QACjB;AAAA,QACA,aAAa,eAAe;AAAA,QAC5B;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,UACP,OAAO,SAAS,MAAM,SAAS,GAAG,sCAAsC,wBAAwB;AAAA,UAChG;AAAA,UACA;AAAA,UACA;AAAA,QACF,EAAE,KAAK,IAAI;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,WAAW,KAAK;AAC3B,UAAM,YAAY,aAAa,KAAK;AACpC,QAAI,cAAc,SAAS;AACzB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,YACA;AAAA,YACA;AAAA,UACF,EAAE,KAAK,IAAI;AAAA,QACb;AAAA,MACF;AAAA,IACF;AACA,QAAI,cAAc,WAAW;AAC3B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,SAAS;AAAA,UACP,MAAM;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA,MAAM;AAAA,UACN,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,SAAS;AAAA,QACP,MAAM;AAAA,QACN,QAAQ;AAAA,QACR;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,SAAS;AAAA,MACP,MAAM;AAAA,MACN,QAAQ,SAAS;AAAA,MACjB;AAAA,MACA,aAAa,eAAe;AAAA,MAC5B;AAAA,MACA,MAAM;AAAA,MACN,SAAS,GAAG,SAAS,MAAM,IAAI,SAAS,UAAU,GAAG,UAAU,KAAK,OAAO,KAAK,EAAE;AAAA,IACpF;AAAA,EACF;AACF;AAQO,SAAS,kBAAkB,SAAqC;AACrE,SAAO,QAAQ,SAAS;AAC1B;AAOO,SAAS,cAAc,SAAmC;AAC/D,EAAAD,SAAQ,OAAO,MAAM,UAAU,QAAQ,OAAO;AAAA,CAAI;AAClD,MAAI,QAAQ,KAAK,QAAQ,QAAQ,QAAQ,SAAS,aAAa;AAC7D,IAAAA,SAAQ,OAAO,MAAM,wBAAwB,QAAQ,IAAI;AAAA,CAAI;AAAA,EAC/D;AACA,EAAAA,SAAQ,KAAK,CAAC;AAChB;AAQA,eAAsB,YACpB,KACA,OAA6D,CAAC,GAC3C;AACnB,QAAM,SAAS,MAAM,SAAS,KAAK,IAAI;AACvC,MAAI,OAAO,GAAI,QAAO,OAAO;AAC7B,MAAI,kBAAkB,OAAO,OAAO,EAAG,eAAc,OAAO,OAAO;AAEnE,SAAO,IAAI,SAAS,OAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7C,QAAQ,OAAO,QAAQ,UAAU;AAAA,IACjC,SAAS,OAAO,QAAQ,cACpB,EAAE,gBAAgB,OAAO,QAAQ,YAAY,IAC7C;AAAA,EACN,CAAC;AACH;","names":["process","hasAuthHeader","process","msg"]}
@@ -2733,6 +2733,7 @@ function parseTraceActions(traceZipPath) {
2733
2733
  title: deriveTitle(event),
2734
2734
  startTime: event.startTime ?? 0,
2735
2735
  endTime: 0,
2736
+ wallTime: event.wallTime,
2736
2737
  params: event.params,
2737
2738
  beforeSnapshot: event.beforeSnapshot,
2738
2739
  pageId: event.pageId ?? "",
@@ -2765,6 +2766,7 @@ function parseTraceActions(traceZipPath) {
2765
2766
  title: deriveTitle(event),
2766
2767
  startTime: event.startTime ?? 0,
2767
2768
  endTime: event.endTime ?? 0,
2769
+ wallTime: event.wallTime,
2768
2770
  params: event.params,
2769
2771
  beforeSnapshot: event.beforeSnapshot,
2770
2772
  afterSnapshot: event.afterSnapshot,
@@ -2783,7 +2785,11 @@ function parseTraceActions(traceZipPath) {
2783
2785
  if (!a.callId || !(a.beforeSnapshot || a.afterSnapshot)) return false;
2784
2786
  const method = a.title?.split(".").pop() ?? "";
2785
2787
  return MUTATION_METHODS.has(method) || OBSERVATION_METHODS.has(method);
2786
- }).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
2788
+ });
2789
+ const sortByWallTime = actions.every((a) => typeof a.wallTime === "number");
2790
+ actions.sort(
2791
+ (a, b) => sortByWallTime ? (a.wallTime ?? 0) - (b.wallTime ?? 0) : (a.startTime ?? 0) - (b.startTime ?? 0)
2792
+ );
2787
2793
  return actions;
2788
2794
  }
2789
2795
  function filterMutationActions(actions) {
@@ -2847,6 +2853,7 @@ function parseTraceGroups(traceZipPath) {
2847
2853
  callId: event.callId,
2848
2854
  name: event.title ?? "",
2849
2855
  startTime: event.startTime ?? 0,
2856
+ wallTime: event.wallTime,
2850
2857
  parentId: event.parentId
2851
2858
  });
2852
2859
  } else if (event.type === "after") {
@@ -2855,7 +2862,11 @@ function parseTraceGroups(traceZipPath) {
2855
2862
  }
2856
2863
  }
2857
2864
  }
2858
- return [...groups.values()].sort((a, b) => a.startTime - b.startTime);
2865
+ const groupList = [...groups.values()];
2866
+ const sortByWallTime = groupList.every((g) => typeof g.wallTime === "number");
2867
+ return groupList.sort(
2868
+ (a, b) => sortByWallTime ? (a.wallTime ?? 0) - (b.wallTime ?? 0) : a.startTime - b.startTime
2869
+ );
2859
2870
  }
2860
2871
 
2861
2872
  // ../trace-tools/src/snapshot-reconstructor.ts
@@ -3060,6 +3071,42 @@ async function withTraceViewerRetry(start, opts = {}) {
3060
3071
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
3061
3072
  }
3062
3073
 
3074
+ // ../trace-tools/src/trace-merger.ts
3075
+ var import_adm_zip2 = __toESM(require_adm_zip(), 1);
3076
+ var TRACE_ENTRY_SUFFIXES = [".trace", ".network", ".stacks"];
3077
+ function mergeTraceArchives(zipPaths, outPath) {
3078
+ const out = new import_adm_zip2.default();
3079
+ const seenResources = /* @__PURE__ */ new Set();
3080
+ let sourceCount = 0;
3081
+ zipPaths.forEach((zipPath, sourceIndex) => {
3082
+ let zip;
3083
+ try {
3084
+ zip = new import_adm_zip2.default(zipPath);
3085
+ } catch {
3086
+ return;
3087
+ }
3088
+ let contributed = false;
3089
+ for (const entry of zip.getEntries()) {
3090
+ if (entry.isDirectory) continue;
3091
+ const name = entry.entryName;
3092
+ if (name.startsWith("resources/")) {
3093
+ if (seenResources.has(name)) continue;
3094
+ seenResources.add(name);
3095
+ out.addFile(name, entry.getData());
3096
+ contributed = true;
3097
+ continue;
3098
+ }
3099
+ if (TRACE_ENTRY_SUFFIXES.some((suffix) => name.endsWith(suffix))) {
3100
+ out.addFile(`s${sourceIndex}-${name}`, entry.getData());
3101
+ contributed = true;
3102
+ }
3103
+ }
3104
+ if (contributed) sourceCount += 1;
3105
+ });
3106
+ out.writeZip(outPath);
3107
+ return { path: outPath, sourceCount, skippedCount: zipPaths.length - sourceCount };
3108
+ }
3109
+
3063
3110
  export {
3064
3111
  parseTraceActions,
3065
3112
  filterMutationActions,
@@ -3067,6 +3114,7 @@ export {
3067
3114
  parseTraceGroups,
3068
3115
  reconstructSnapshots,
3069
3116
  downloadTrace,
3070
- cleanupTrace
3117
+ cleanupTrace,
3118
+ mergeTraceArchives
3071
3119
  };
3072
- //# sourceMappingURL=chunk-A4ISG3VX.js.map
3120
+ //# sourceMappingURL=chunk-U7OMQJ7V.js.map