@runcofactor/cli 0.10.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.
- package/README.md +125 -0
- package/dist/api-catalog-GFGCLVS4.js +401 -0
- package/dist/api-catalog-GFGCLVS4.js.map +1 -0
- package/dist/attachment-SJC7D7FY.js +276 -0
- package/dist/attachment-SJC7D7FY.js.map +1 -0
- package/dist/auth-EN6NQCJY.js +31 -0
- package/dist/auth-EN6NQCJY.js.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-3CZU7XCS.js +272 -0
- package/dist/chunk-3CZU7XCS.js.map +1 -0
- package/dist/chunk-4NRDONSC.js +1048 -0
- package/dist/chunk-4NRDONSC.js.map +1 -0
- package/dist/chunk-4PNOV7OV.js +51980 -0
- package/dist/chunk-4PNOV7OV.js.map +1 -0
- package/dist/chunk-52IO7ACJ.js +2941 -0
- package/dist/chunk-52IO7ACJ.js.map +1 -0
- package/dist/chunk-6LEZCKKV.js +663 -0
- package/dist/chunk-6LEZCKKV.js.map +1 -0
- package/dist/chunk-A4OFTUVV.js +170 -0
- package/dist/chunk-A4OFTUVV.js.map +1 -0
- package/dist/chunk-B2VISN2V.js +2459 -0
- package/dist/chunk-B2VISN2V.js.map +1 -0
- package/dist/chunk-CLAWT7BJ.js +271 -0
- package/dist/chunk-CLAWT7BJ.js.map +1 -0
- package/dist/chunk-GKY2KXO5.js +281 -0
- package/dist/chunk-GKY2KXO5.js.map +1 -0
- package/dist/chunk-I4QLI3JO.js +413 -0
- package/dist/chunk-I4QLI3JO.js.map +1 -0
- package/dist/chunk-IMPZAX6L.js +180 -0
- package/dist/chunk-IMPZAX6L.js.map +1 -0
- package/dist/chunk-J6SH7GQT.js +241 -0
- package/dist/chunk-J6SH7GQT.js.map +1 -0
- package/dist/chunk-JDBCZ3Z2.js +298 -0
- package/dist/chunk-JDBCZ3Z2.js.map +1 -0
- package/dist/chunk-JICSYFRJ.js +46 -0
- package/dist/chunk-JICSYFRJ.js.map +1 -0
- package/dist/chunk-JWHYXEA2.js +32 -0
- package/dist/chunk-JWHYXEA2.js.map +1 -0
- package/dist/chunk-N7HFALX3.js +88 -0
- package/dist/chunk-N7HFALX3.js.map +1 -0
- package/dist/chunk-Q4BO4Z5V.js +178 -0
- package/dist/chunk-Q4BO4Z5V.js.map +1 -0
- package/dist/chunk-QFDOWABX.js +195 -0
- package/dist/chunk-QFDOWABX.js.map +1 -0
- package/dist/chunk-SJJW77WN.js +605 -0
- package/dist/chunk-SJJW77WN.js.map +1 -0
- package/dist/chunk-VKVL7WBN.js +50 -0
- package/dist/chunk-VKVL7WBN.js.map +1 -0
- package/dist/chunk-WOHL4FTB.js +353 -0
- package/dist/chunk-WOHL4FTB.js.map +1 -0
- package/dist/chunk-XQXVG7RA.js +426 -0
- package/dist/chunk-XQXVG7RA.js.map +1 -0
- package/dist/chunk-ZD3KHESI.js +94 -0
- package/dist/chunk-ZD3KHESI.js.map +1 -0
- package/dist/credential-COLTBV6D.js +450 -0
- package/dist/credential-COLTBV6D.js.map +1 -0
- package/dist/credential-template-EEPYFG7R.js +291 -0
- package/dist/credential-template-EEPYFG7R.js.map +1 -0
- package/dist/debug-workflow-KOVH5GOP.js +259 -0
- package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
- package/dist/dist-44PZR7BQ.js +423 -0
- package/dist/dist-44PZR7BQ.js.map +1 -0
- package/dist/docs-3SSORHB5.js +366 -0
- package/dist/docs-3SSORHB5.js.map +1 -0
- package/dist/doctor-OTSIWV6G.js +132 -0
- package/dist/doctor-OTSIWV6G.js.map +1 -0
- package/dist/feature-flag-T2LRZME3.js +283 -0
- package/dist/feature-flag-T2LRZME3.js.map +1 -0
- package/dist/file-type-S3UUBVCT.js +4205 -0
- package/dist/file-type-S3UUBVCT.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +982 -0
- package/dist/index.js.map +1 -0
- package/dist/init-TGWRECT2.js +208 -0
- package/dist/init-TGWRECT2.js.map +1 -0
- package/dist/install-DXSNWZKO.js +295 -0
- package/dist/install-DXSNWZKO.js.map +1 -0
- package/dist/issues-XIZOPX6T.js +564 -0
- package/dist/issues-XIZOPX6T.js.map +1 -0
- package/dist/knobs-NBINZIUB.js +295 -0
- package/dist/knobs-NBINZIUB.js.map +1 -0
- package/dist/list-3TDHK637.js +59 -0
- package/dist/list-3TDHK637.js.map +1 -0
- package/dist/local-HICVD6PU.js +100 -0
- package/dist/local-HICVD6PU.js.map +1 -0
- package/dist/login-UOIUXVUK.js +315 -0
- package/dist/login-UOIUXVUK.js.map +1 -0
- package/dist/mcp-54IJEOUA.js +1956 -0
- package/dist/mcp-54IJEOUA.js.map +1 -0
- package/dist/pdf-extract-ABWV3L5X.js +13 -0
- package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
- package/dist/pdfjs-DQ2YA5S3.js +41561 -0
- package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
- package/dist/property-UXLOLPTD.js +701 -0
- package/dist/property-UXLOLPTD.js.map +1 -0
- package/dist/property-skill-guide.md +189 -0
- package/dist/psql-BYUUNLGG.js +115 -0
- package/dist/psql-BYUUNLGG.js.map +1 -0
- package/dist/record-4TR2UIIS.js +233 -0
- package/dist/record-4TR2UIIS.js.map +1 -0
- package/dist/redis-35VBUYW7.js +116 -0
- package/dist/redis-35VBUYW7.js.map +1 -0
- package/dist/release-OOLTUBUT.js +354 -0
- package/dist/release-OOLTUBUT.js.map +1 -0
- package/dist/sandbox-XGSUU6G7.js +2046 -0
- package/dist/sandbox-XGSUU6G7.js.map +1 -0
- package/dist/scenarios-GWLJEGHA.js +280 -0
- package/dist/scenarios-GWLJEGHA.js.map +1 -0
- package/dist/session-MYFNZPRP.js +2414 -0
- package/dist/session-MYFNZPRP.js.map +1 -0
- package/dist/skill-KM34LDMO.js +1839 -0
- package/dist/skill-KM34LDMO.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +262 -0
- package/dist/skills/debug-run/history/1.0.0.md +244 -0
- package/dist/skills/debug-run/history/1.0.1.md +245 -0
- package/dist/skills/debug-run/history/1.0.2.md +254 -0
- package/dist/skills/debug-run/history/1.1.0.md +262 -0
- package/dist/skills/debug-run/version.json +3 -0
- package/dist/spec-RA3CFEKG.js +951 -0
- package/dist/spec-RA3CFEKG.js.map +1 -0
- package/dist/src-LB4HBTJQ.js +1516 -0
- package/dist/src-LB4HBTJQ.js.map +1 -0
- package/dist/src-N3TPKGTY.js +244 -0
- package/dist/src-N3TPKGTY.js.map +1 -0
- package/dist/src-UVDX5UP6.js +17 -0
- package/dist/src-UVDX5UP6.js.map +1 -0
- package/dist/start-PSEQSQA3.js +151 -0
- package/dist/start-PSEQSQA3.js.map +1 -0
- package/dist/tag-YY6YAABQ.js +22 -0
- package/dist/tag-YY6YAABQ.js.map +1 -0
- package/dist/task-IQC3YR5K.js +922 -0
- package/dist/task-IQC3YR5K.js.map +1 -0
- package/dist/test-suite-DZNJFXDW.js +394 -0
- package/dist/test-suite-DZNJFXDW.js.map +1 -0
- package/dist/trace-HXIW7U2D.js +14 -0
- package/dist/trace-HXIW7U2D.js.map +1 -0
- package/dist/triage-ORG45R5W.js +16 -0
- package/dist/triage-ORG45R5W.js.map +1 -0
- package/dist/upgrade-L2VYMP2V.js +61 -0
- package/dist/upgrade-L2VYMP2V.js.map +1 -0
- package/dist/workflow-XYFH5LTP.js +3708 -0
- package/dist/workflow-XYFH5LTP.js.map +1 -0
- package/dist/workflow-cache-NQJQFWMV.js +253 -0
- package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
- package/dist/workflow-debug-BGD4UIXU.js +927 -0
- package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
- package/dist/workflow-proposals-MTUDX46O.js +382 -0
- package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,46 @@
|
|
|
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 installConsoleBranding() {
|
|
30
|
+
if (command === "canary") return;
|
|
31
|
+
const methods = ["log", "error", "warn", "info"];
|
|
32
|
+
for (const method of methods) {
|
|
33
|
+
const original = console[method].bind(console);
|
|
34
|
+
console[method] = (...args) => {
|
|
35
|
+
original(...args.map((arg) => typeof arg === "string" ? brandText(arg) : arg));
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
command,
|
|
42
|
+
packageName,
|
|
43
|
+
version,
|
|
44
|
+
installConsoleBranding
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=chunk-JICSYFRJ.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 * 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;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"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
|
|
3
|
+
// ../tmp/src/index.ts
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import { mkdir, mkdtemp } from "fs/promises";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
var CANARY_TMP_DIR = path.join(os.tmpdir(), "canary");
|
|
10
|
+
var DIR_MODE = 448;
|
|
11
|
+
var dirEnsured = false;
|
|
12
|
+
var ensureSync = () => {
|
|
13
|
+
if (dirEnsured) return;
|
|
14
|
+
fs.mkdirSync(CANARY_TMP_DIR, { recursive: true, mode: DIR_MODE });
|
|
15
|
+
dirEnsured = true;
|
|
16
|
+
};
|
|
17
|
+
var getCanaryTmpDir = () => {
|
|
18
|
+
ensureSync();
|
|
19
|
+
return CANARY_TMP_DIR;
|
|
20
|
+
};
|
|
21
|
+
var getSubDir = (name) => {
|
|
22
|
+
ensureSync();
|
|
23
|
+
const dir = path.join(CANARY_TMP_DIR, name);
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true, mode: DIR_MODE });
|
|
25
|
+
return dir;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
getCanaryTmpDir,
|
|
30
|
+
getSubDir
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=chunk-JWHYXEA2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../tmp/src/index.ts"],"sourcesContent":["/**\n * Canary Temp Directory Helpers\n *\n * Centralizes all temporary file/directory creation under a single\n * `/tmp/canary/` base directory. This ensures the cleanup daemon only\n * removes files we own, not OS or third-party temp entries.\n *\n * All public functions lazily ensure the base directory exists on first\n * use, so callers never need to worry about directory creation.\n *\n * @module tmp\n * @see packages/tmp/README.md\n */\n\nimport os from 'node:os';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport { mkdir, mkdtemp } from 'node:fs/promises';\nimport { randomUUID } from 'node:crypto';\n\n/** Base directory for all canary temp files */\nconst CANARY_TMP_DIR = path.join(os.tmpdir(), 'canary');\n\n/** Restrictive permissions: owner-only access (rwx------) */\nconst DIR_MODE = 0o700;\n\n/**\n * Tracks whether the base directory has been created this process.\n * Once created, skip redundant mkdir calls.\n */\nlet dirEnsured = false;\n\nconst ensureSync = (): void => {\n if (dirEnsured) return;\n fs.mkdirSync(CANARY_TMP_DIR, { recursive: true, mode: DIR_MODE });\n dirEnsured = true;\n};\n\nconst ensureAsync = async (): Promise<void> => {\n if (dirEnsured) return;\n await mkdir(CANARY_TMP_DIR, { recursive: true, mode: DIR_MODE });\n dirEnsured = true;\n};\n\n/** Get the canary tmp base directory path (lazily ensures dir exists) */\nexport const getCanaryTmpDir = (): string => {\n ensureSync();\n return CANARY_TMP_DIR;\n};\n\n/** Ensure the canary base directory exists (async, idempotent) */\nexport const ensureCanaryTmpDir = async (): Promise<string> => {\n await ensureAsync();\n return CANARY_TMP_DIR;\n};\n\n/**\n * Get a named subdirectory under the canary tmp dir, creating it if needed.\n *\n * Accepts nested paths (e.g. `'workflow-runs/abc-123'`).\n *\n * @example\n * const dir = getSubDir('cache-agent');\n * // => '/tmp/canary/cache-agent' (created with 0o700 permissions)\n */\nexport const getSubDir = (name: string): string => {\n ensureSync();\n const dir = path.join(CANARY_TMP_DIR, name);\n fs.mkdirSync(dir, { recursive: true, mode: DIR_MODE });\n return dir;\n};\n\n/**\n * Async version of getSubDir. Prefer this in async contexts to avoid\n * blocking the event loop on first call.\n */\nexport const getSubDirAsync = async (name: string): Promise<string> => {\n await ensureAsync();\n const dir = path.join(CANARY_TMP_DIR, name);\n await mkdir(dir, { recursive: true, mode: DIR_MODE });\n return dir;\n};\n\n/** Create a temp directory under /tmp/canary/ with the given prefix */\nexport const makeTempDir = async (prefix: string): Promise<string> => {\n await ensureAsync();\n return mkdtemp(path.join(CANARY_TMP_DIR, prefix));\n};\n\n/** Synchronous version of makeTempDir */\nexport const makeTempDirSync = (prefix: string): string => {\n ensureSync();\n return fs.mkdtempSync(path.join(CANARY_TMP_DIR, prefix));\n};\n\n/** Generate a temp file path under /tmp/canary/ (ensures dir exists, does NOT create the file) */\nexport const getTempFilePath = (prefix: string, suffix: string = ''): string => {\n ensureSync();\n return path.join(CANARY_TMP_DIR, `${prefix}${randomUUID()}${suffix}`);\n};\n\n// ============================================================================\n// Org-scoped helpers — isolate temp files by organization ID\n// ============================================================================\n\n/** UUID v4 pattern — all orgIds in this codebase are UUIDs */\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\n/** Validate orgId is a UUID to prevent path traversal */\nconst validateOrgId = (orgId: string): void => {\n if (!UUID_RE.test(orgId)) {\n throw new Error(`Invalid orgId for tmp path: expected UUID, got \"${orgId}\"`);\n }\n};\n\n/** Get the org-scoped tmp base directory: `/tmp/canary/{orgId}/` */\nexport const getOrgTmpDir = (orgId: string): string => {\n validateOrgId(orgId);\n ensureSync();\n const dir = path.join(CANARY_TMP_DIR, orgId);\n fs.mkdirSync(dir, { recursive: true, mode: DIR_MODE });\n return dir;\n};\n\n/** Get a named subdirectory under the org-scoped tmp dir, creating it synchronously. */\nexport const getOrgSubDir = (opts: { orgId: string; name: string }): string => {\n validateOrgId(opts.orgId);\n ensureSync();\n const dir = path.join(CANARY_TMP_DIR, opts.orgId, opts.name);\n fs.mkdirSync(dir, { recursive: true, mode: DIR_MODE });\n return dir;\n};\n\n/** Async version of getOrgSubDir. */\nexport const getOrgSubDirAsync = async (opts: { orgId: string; name: string }): Promise<string> => {\n validateOrgId(opts.orgId);\n await ensureAsync();\n const dir = path.join(CANARY_TMP_DIR, opts.orgId, opts.name);\n await mkdir(dir, { recursive: true, mode: DIR_MODE });\n return dir;\n};\n\n/** Create a temp directory under `/tmp/canary/{orgId}/` with the given prefix. */\nexport const makeOrgTempDir = async (opts: { orgId: string; prefix: string }): Promise<string> => {\n const base = getOrgTmpDir(opts.orgId);\n return mkdtemp(path.join(base, opts.prefix));\n};\n\n/** Synchronous version of makeOrgTempDir. */\nexport const makeOrgTempDirSync = (opts: { orgId: string; prefix: string }): string => {\n const base = getOrgTmpDir(opts.orgId);\n return fs.mkdtempSync(path.join(base, opts.prefix));\n};\n\n/** Generate a temp file path under `/tmp/canary/{orgId}/` (ensures dir exists, does NOT create the file). */\nexport const getOrgTempFilePath = (opts: { orgId: string; prefix: string; suffix?: string }): string => {\n const base = getOrgTmpDir(opts.orgId);\n return path.join(base, `${opts.prefix}${randomUUID()}${opts.suffix ?? ''}`);\n};\n"],"mappings":";;;AAcA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,SAAS,OAAO,eAAe;AAC/B,SAAS,kBAAkB;AAG3B,IAAM,iBAAiB,KAAK,KAAK,GAAG,OAAO,GAAG,QAAQ;AAGtD,IAAM,WAAW;AAMjB,IAAI,aAAa;AAEjB,IAAM,aAAa,MAAY;AAC7B,MAAI,WAAY;AAChB,KAAG,UAAU,gBAAgB,EAAE,WAAW,MAAM,MAAM,SAAS,CAAC;AAChE,eAAa;AACf;AASO,IAAM,kBAAkB,MAAc;AAC3C,aAAW;AACX,SAAO;AACT;AAiBO,IAAM,YAAY,CAAC,SAAyB;AACjD,aAAW;AACX,QAAM,MAAM,KAAK,KAAK,gBAAgB,IAAI;AAC1C,KAAG,UAAU,KAAK,EAAE,WAAW,MAAM,MAAM,SAAS,CAAC;AACrD,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
packageName
|
|
4
|
+
} from "./chunk-JICSYFRJ.js";
|
|
5
|
+
|
|
6
|
+
// src/update-check.ts
|
|
7
|
+
import fs from "fs/promises";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path from "path";
|
|
10
|
+
var CACHE_DIR = path.join(os.homedir(), ".config", "canary-cli");
|
|
11
|
+
var CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
12
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13
|
+
var REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;
|
|
14
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
15
|
+
function isNewer(latest, current) {
|
|
16
|
+
const l = latest.split(".").map(Number);
|
|
17
|
+
const c = current.split(".").map(Number);
|
|
18
|
+
for (let i = 0; i < Math.max(l.length, c.length); i++) {
|
|
19
|
+
const lv = l[i] ?? 0;
|
|
20
|
+
const cv = c[i] ?? 0;
|
|
21
|
+
if (lv !== cv) return lv > cv;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
async function readCache() {
|
|
26
|
+
try {
|
|
27
|
+
const content = await fs.readFile(CACHE_FILE, "utf8");
|
|
28
|
+
return JSON.parse(content);
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function writeCache(cache) {
|
|
34
|
+
try {
|
|
35
|
+
await fs.mkdir(CACHE_DIR, { recursive: true });
|
|
36
|
+
await fs.writeFile(CACHE_FILE, JSON.stringify(cache), "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function fetchLatestVersion() {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(REGISTRY_URL, {
|
|
43
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) return null;
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
return data.version ?? null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function printWarning(current, latest) {
|
|
53
|
+
console.error(`
|
|
54
|
+
Update available: ${current} \u2192 ${latest}. Run "canary upgrade" to update.
|
|
55
|
+
`);
|
|
56
|
+
}
|
|
57
|
+
async function checkForUpdate(currentVersion) {
|
|
58
|
+
try {
|
|
59
|
+
const cache = await readCache();
|
|
60
|
+
const isFresh = cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS;
|
|
61
|
+
if (isFresh) {
|
|
62
|
+
if (isNewer(cache.latestVersion, currentVersion)) {
|
|
63
|
+
printWarning(currentVersion, cache.latestVersion);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const latest = await fetchLatestVersion();
|
|
68
|
+
if (latest) {
|
|
69
|
+
await writeCache({ latestVersion: latest, checkedAt: Date.now() });
|
|
70
|
+
if (isNewer(latest, currentVersion)) {
|
|
71
|
+
printWarning(currentVersion, latest);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function clearUpdateCache() {
|
|
78
|
+
try {
|
|
79
|
+
await fs.unlink(CACHE_FILE);
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
checkForUpdate,
|
|
86
|
+
clearUpdateCache
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=chunk-N7HFALX3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/update-check.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { packageName } from './brand.js';\n\nconst CACHE_DIR = path.join(os.homedir(), '.config', 'canary-cli');\nconst CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');\nconst CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 1 day\nconst REGISTRY_URL = `https://registry.npmjs.org/${packageName}/latest`;\nconst FETCH_TIMEOUT_MS = 3000;\n\ninterface UpdateCache {\n latestVersion: string;\n checkedAt: number;\n}\n\nfunction isNewer(latest: string, current: string): boolean {\n const l = latest.split('.').map(Number);\n const c = current.split('.').map(Number);\n for (let i = 0; i < Math.max(l.length, c.length); i++) {\n const lv = l[i] ?? 0;\n const cv = c[i] ?? 0;\n if (lv !== cv) return lv > cv;\n }\n return false;\n}\n\nasync function readCache(): Promise<UpdateCache | null> {\n try {\n const content = await fs.readFile(CACHE_FILE, 'utf8');\n return JSON.parse(content) as UpdateCache;\n } catch {\n return null;\n }\n}\n\nasync function writeCache(cache: UpdateCache): Promise<void> {\n try {\n await fs.mkdir(CACHE_DIR, { recursive: true });\n await fs.writeFile(CACHE_FILE, JSON.stringify(cache), 'utf8');\n } catch {\n // Non-critical — silently ignore\n }\n}\n\nasync function fetchLatestVersion(): Promise<string | null> {\n try {\n const res = await fetch(REGISTRY_URL, {\n signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),\n });\n if (!res.ok) return null;\n const data = (await res.json()) as { version?: string };\n return data.version ?? null;\n } catch {\n return null;\n }\n}\n\nfunction printWarning(current: string, latest: string): void {\n // stderr so the banner never pollutes stdout-piped JSON output.\n console.error(`\\nUpdate available: ${current} \\u2192 ${latest}. Run \"canary upgrade\" to update.\\n`);\n}\n\n/**\n * Check if a newer CLI version is available and print a warning.\n * Uses a local cache to avoid hitting the registry on every invocation.\n * Never throws or blocks meaningfully.\n */\nexport async function checkForUpdate(currentVersion: string): Promise<void> {\n try {\n const cache = await readCache();\n const isFresh = cache && Date.now() - cache.checkedAt < CHECK_INTERVAL_MS;\n\n if (isFresh) {\n if (isNewer(cache.latestVersion, currentVersion)) {\n printWarning(currentVersion, cache.latestVersion);\n }\n return;\n }\n\n // Cache is stale or missing — fetch from registry\n const latest = await fetchLatestVersion();\n if (latest) {\n await writeCache({ latestVersion: latest, checkedAt: Date.now() });\n if (isNewer(latest, currentVersion)) {\n printWarning(currentVersion, latest);\n }\n }\n } catch {\n // Never block CLI execution\n }\n}\n\n/** Clear the update cache (called after a successful upgrade). */\nexport async function clearUpdateCache(): Promise<void> {\n try {\n await fs.unlink(CACHE_FILE);\n } catch {\n // File may not exist\n }\n}\n"],"mappings":";;;;;;AAAA,OAAO,QAAQ;AACf,OAAO,QAAQ;AACf,OAAO,UAAU;AAGjB,IAAM,YAAY,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,YAAY;AACjE,IAAM,aAAa,KAAK,KAAK,WAAW,mBAAmB;AAC3D,IAAM,oBAAoB,KAAK,KAAK,KAAK;AACzC,IAAM,eAAe,8BAA8B,WAAW;AAC9D,IAAM,mBAAmB;AAOzB,SAAS,QAAQ,QAAgB,SAA0B;AACzD,QAAM,IAAI,OAAO,MAAM,GAAG,EAAE,IAAI,MAAM;AACtC,QAAM,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,KAAK;AACrD,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,UAAM,KAAK,EAAE,CAAC,KAAK;AACnB,QAAI,OAAO,GAAI,QAAO,KAAK;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,eAAe,YAAyC;AACtD,MAAI;AACF,UAAM,UAAU,MAAM,GAAG,SAAS,YAAY,MAAM;AACpD,WAAO,KAAK,MAAM,OAAO;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,WAAW,OAAmC;AAC3D,MAAI;AACF,UAAM,GAAG,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAC7C,UAAM,GAAG,UAAU,YAAY,KAAK,UAAU,KAAK,GAAG,MAAM;AAAA,EAC9D,QAAQ;AAAA,EAER;AACF;AAEA,eAAe,qBAA6C;AAC1D,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,cAAc;AAAA,MACpC,QAAQ,YAAY,QAAQ,gBAAgB;AAAA,IAC9C,CAAC;AACD,QAAI,CAAC,IAAI,GAAI,QAAO;AACpB,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,WAAO,KAAK,WAAW;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,aAAa,SAAiB,QAAsB;AAE3D,UAAQ,MAAM;AAAA,oBAAuB,OAAO,WAAW,MAAM;AAAA,CAAqC;AACpG;AAOA,eAAsB,eAAe,gBAAuC;AAC1E,MAAI;AACF,UAAM,QAAQ,MAAM,UAAU;AAC9B,UAAM,UAAU,SAAS,KAAK,IAAI,IAAI,MAAM,YAAY;AAExD,QAAI,SAAS;AACX,UAAI,QAAQ,MAAM,eAAe,cAAc,GAAG;AAChD,qBAAa,gBAAgB,MAAM,aAAa;AAAA,MAClD;AACA;AAAA,IACF;AAGA,UAAM,SAAS,MAAM,mBAAmB;AACxC,QAAI,QAAQ;AACV,YAAM,WAAW,EAAE,eAAe,QAAQ,WAAW,KAAK,IAAI,EAAE,CAAC;AACjE,UAAI,QAAQ,QAAQ,cAAc,GAAG;AACnC,qBAAa,gBAAgB,MAAM;AAAA,MACrC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AACF;AAGA,eAAsB,mBAAkC;AACtD,MAAI;AACF,UAAM,GAAG,OAAO,UAAU;AAAA,EAC5B,QAAQ;AAAA,EAER;AACF;","names":[]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
getArgValue
|
|
4
|
+
} from "./chunk-A4OFTUVV.js";
|
|
5
|
+
import {
|
|
6
|
+
formatSemanticSnapshot
|
|
7
|
+
} from "./chunk-4PNOV7OV.js";
|
|
8
|
+
|
|
9
|
+
// src/trace.ts
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
async function runTrace(argv) {
|
|
13
|
+
const [subcommand, ...rest] = argv;
|
|
14
|
+
if (subcommand === "snapshot") {
|
|
15
|
+
await runTraceSnapshot(rest);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
printTraceHelp();
|
|
19
|
+
}
|
|
20
|
+
function printTraceHelp() {
|
|
21
|
+
console.log(`
|
|
22
|
+
Usage: canary trace <subcommand>
|
|
23
|
+
|
|
24
|
+
Subcommands:
|
|
25
|
+
snapshot <trace-path-or-url> Reconstruct AI semantic snapshots from a Playwright trace
|
|
26
|
+
|
|
27
|
+
Options for 'snapshot':
|
|
28
|
+
--output-dir <dir> Write snapshot files to a directory (default: stdout)
|
|
29
|
+
--screenshots Include screenshots alongside YAML snapshots
|
|
30
|
+
--html Include reconstructed DOM HTML alongside YAML snapshots
|
|
31
|
+
--filter <mode> Which snapshot point per action: before, after, both (default: both)
|
|
32
|
+
--action <index> Only snapshot a specific action index (0-based)
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
canary trace snapshot ./trace.zip
|
|
36
|
+
canary trace snapshot https://example.com/trace.zip --output-dir ./replay
|
|
37
|
+
canary trace snapshot ./trace.zip --action 3 --screenshots
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
async function runTraceSnapshot(argv) {
|
|
41
|
+
const tracePath = argv.find((a) => !a.startsWith("--"));
|
|
42
|
+
if (!tracePath) {
|
|
43
|
+
console.error("Error: trace path or URL is required");
|
|
44
|
+
printTraceHelp();
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const outputDir = getArgValue(argv, "--output-dir");
|
|
48
|
+
const includeScreenshots = argv.includes("--screenshots");
|
|
49
|
+
const includeHtml = argv.includes("--html");
|
|
50
|
+
const filterArg = getArgValue(argv, "--filter") ?? "both";
|
|
51
|
+
const actionIndexArg = getArgValue(argv, "--action");
|
|
52
|
+
const actionIndex = actionIndexArg !== void 0 ? parseInt(actionIndexArg, 10) : void 0;
|
|
53
|
+
if (!["before", "after", "both"].includes(filterArg)) {
|
|
54
|
+
console.error(`Error: --filter must be 'before', 'after', or 'both'`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const filter = filterArg;
|
|
58
|
+
const { reconstructSnapshots, downloadTrace, cleanupTrace } = await import("./src-UVDX5UP6.js");
|
|
59
|
+
let localPath = tracePath;
|
|
60
|
+
let isTemp = false;
|
|
61
|
+
if (tracePath.startsWith("http://") || tracePath.startsWith("https://")) {
|
|
62
|
+
console.log("Downloading trace...");
|
|
63
|
+
localPath = await downloadTrace(tracePath);
|
|
64
|
+
isTemp = true;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const results = await reconstructSnapshots({
|
|
68
|
+
traceZipPath: localPath,
|
|
69
|
+
includeScreenshots,
|
|
70
|
+
includeHtml,
|
|
71
|
+
filter,
|
|
72
|
+
raw: false,
|
|
73
|
+
actionIndex,
|
|
74
|
+
onProgress: (msg) => process.stderr.write(`${msg}
|
|
75
|
+
`)
|
|
76
|
+
});
|
|
77
|
+
if (outputDir) {
|
|
78
|
+
writeOutputDir(outputDir, results);
|
|
79
|
+
console.log(`Replay written to ${outputDir}/`);
|
|
80
|
+
} else {
|
|
81
|
+
writeStdout(results, localPath);
|
|
82
|
+
}
|
|
83
|
+
} finally {
|
|
84
|
+
if (isTemp) cleanupTrace(localPath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function writeStdout(results, tracePath) {
|
|
88
|
+
const actionCount = new Set(results.map((r) => r.actionIndex)).size;
|
|
89
|
+
const firstTime = results.length > 0 ? results[0].action.startTime : 0;
|
|
90
|
+
console.log(`
|
|
91
|
+
${"=".repeat(60)}`);
|
|
92
|
+
console.log(`Agent Replay Log`);
|
|
93
|
+
console.log(`Trace: ${tracePath}`);
|
|
94
|
+
console.log(`Actions: ${actionCount}`);
|
|
95
|
+
console.log(`${"=".repeat(60)}
|
|
96
|
+
`);
|
|
97
|
+
let lastActionIndex = -1;
|
|
98
|
+
for (const result of results) {
|
|
99
|
+
if (result.actionIndex !== lastActionIndex) {
|
|
100
|
+
lastActionIndex = result.actionIndex;
|
|
101
|
+
const action = result.action;
|
|
102
|
+
const relativeMs = action.startTime - firstTime;
|
|
103
|
+
const timeStr = `[t=+${(relativeMs / 1e3).toFixed(1)}s]`;
|
|
104
|
+
const errorStr = action.error ? ` \u274C ${action.error.message}` : "";
|
|
105
|
+
console.log(`${"\u2501".repeat(60)}`);
|
|
106
|
+
console.log(
|
|
107
|
+
`Action ${result.actionIndex + 1}: ${formatActionTitle(action)} ${timeStr}${errorStr}`
|
|
108
|
+
);
|
|
109
|
+
console.log(`${"\u2501".repeat(60)}
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
const phaseLabel = result.phase === "before" ? "BEFORE (what the agent saw):" : "AFTER (result):";
|
|
113
|
+
console.log(phaseLabel);
|
|
114
|
+
console.log(result.yaml);
|
|
115
|
+
console.log("");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function snapshotFilename(result, ext) {
|
|
119
|
+
const prefix = String(result.actionIndex + 1).padStart(3, "0");
|
|
120
|
+
const slug = slugify(result.action.title);
|
|
121
|
+
return `${prefix}-${slug}-${result.phase}.${ext}`;
|
|
122
|
+
}
|
|
123
|
+
function writeOutputDir(dir, results) {
|
|
124
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
125
|
+
let renderedCount = 0;
|
|
126
|
+
for (const result of results) {
|
|
127
|
+
fs.writeFileSync(path.join(dir, snapshotFilename(result, "yaml")), result.yaml, "utf-8");
|
|
128
|
+
try {
|
|
129
|
+
const rendered = formatSemanticSnapshot(result.yaml);
|
|
130
|
+
fs.writeFileSync(
|
|
131
|
+
path.join(dir, snapshotFilename(result, "agent.txt")),
|
|
132
|
+
rendered,
|
|
133
|
+
"utf-8"
|
|
134
|
+
);
|
|
135
|
+
renderedCount += 1;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
138
|
+
console.warn(`Skipped agent-rendered snapshot for ${snapshotFilename(result, "yaml")}: ${msg}`);
|
|
139
|
+
}
|
|
140
|
+
if (result.html) {
|
|
141
|
+
fs.writeFileSync(path.join(dir, snapshotFilename(result, "html")), result.html, "utf-8");
|
|
142
|
+
}
|
|
143
|
+
if (result.screenshotBuffer) {
|
|
144
|
+
fs.writeFileSync(path.join(dir, snapshotFilename(result, "png")), result.screenshotBuffer);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log(
|
|
148
|
+
`Wrote ${results.length} raw snapshot${results.length === 1 ? "" : "s"} (.yaml) and ${renderedCount} agent-rendered (.agent.txt) to ${dir}`
|
|
149
|
+
);
|
|
150
|
+
const replay = {
|
|
151
|
+
actions: results.map((r) => ({
|
|
152
|
+
index: r.actionIndex,
|
|
153
|
+
type: r.action.title,
|
|
154
|
+
params: r.action.params,
|
|
155
|
+
phase: r.phase,
|
|
156
|
+
startTime: r.action.startTime,
|
|
157
|
+
endTime: r.action.endTime,
|
|
158
|
+
error: r.action.error ?? null,
|
|
159
|
+
snapshotFile: snapshotFilename(r, "yaml")
|
|
160
|
+
}))
|
|
161
|
+
};
|
|
162
|
+
fs.writeFileSync(path.join(dir, "replay.json"), JSON.stringify(replay, null, 2), "utf-8");
|
|
163
|
+
}
|
|
164
|
+
function formatActionTitle(action) {
|
|
165
|
+
const { title, params } = action;
|
|
166
|
+
if (!params) return title;
|
|
167
|
+
if (params.selector) return `${title}(${JSON.stringify(params.selector)})`;
|
|
168
|
+
if (params.url) return `${title}(${JSON.stringify(params.url)})`;
|
|
169
|
+
return title;
|
|
170
|
+
}
|
|
171
|
+
function slugify(text) {
|
|
172
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
runTrace
|
|
177
|
+
};
|
|
178
|
+
//# sourceMappingURL=chunk-Q4BO4Z5V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/trace.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { getArgValue } from './auth.js';\nimport type { SnapshotResult, TraceAction } from '@chatsdet/trace-tools';\nimport { formatSemanticSnapshot } from '@chatsdet/browser-core';\n\nexport async function runTrace(argv: string[]): Promise<void> {\n const [subcommand, ...rest] = argv;\n\n if (subcommand === 'snapshot') {\n await runTraceSnapshot(rest);\n return;\n }\n\n printTraceHelp();\n}\n\nfunction printTraceHelp(): void {\n console.log(`\nUsage: canary trace <subcommand>\n\nSubcommands:\n snapshot <trace-path-or-url> Reconstruct AI semantic snapshots from a Playwright trace\n\nOptions for 'snapshot':\n --output-dir <dir> Write snapshot files to a directory (default: stdout)\n --screenshots Include screenshots alongside YAML snapshots\n --html Include reconstructed DOM HTML alongside YAML snapshots\n --filter <mode> Which snapshot point per action: before, after, both (default: both)\n --action <index> Only snapshot a specific action index (0-based)\n\nExamples:\n canary trace snapshot ./trace.zip\n canary trace snapshot https://example.com/trace.zip --output-dir ./replay\n canary trace snapshot ./trace.zip --action 3 --screenshots\n`);\n}\n\nasync function runTraceSnapshot(argv: string[]): Promise<void> {\n const tracePath = argv.find((a) => !a.startsWith('--'));\n if (!tracePath) {\n console.error('Error: trace path or URL is required');\n printTraceHelp();\n process.exit(1);\n }\n\n const outputDir = getArgValue(argv, '--output-dir');\n const includeScreenshots = argv.includes('--screenshots');\n const includeHtml = argv.includes('--html');\n const filterArg = getArgValue(argv, '--filter') ?? 'both';\n const actionIndexArg = getArgValue(argv, '--action');\n const actionIndex = actionIndexArg !== undefined ? parseInt(actionIndexArg, 10) : undefined;\n\n if (!['before', 'after', 'both'].includes(filterArg)) {\n console.error(`Error: --filter must be 'before', 'after', or 'both'`);\n process.exit(1);\n }\n const filter = filterArg as 'before' | 'after' | 'both';\n\n const { reconstructSnapshots, downloadTrace, cleanupTrace } = await import(\n '@chatsdet/trace-tools'\n );\n\n let localPath = tracePath;\n let isTemp = false;\n\n if (tracePath.startsWith('http://') || tracePath.startsWith('https://')) {\n console.log('Downloading trace...');\n localPath = await downloadTrace(tracePath);\n isTemp = true;\n }\n\n try {\n const results = await reconstructSnapshots({\n traceZipPath: localPath,\n includeScreenshots,\n includeHtml,\n filter,\n raw: false,\n actionIndex,\n onProgress: (msg) => process.stderr.write(`${msg}\\n`),\n });\n\n if (outputDir) {\n writeOutputDir(outputDir, results);\n console.log(`Replay written to ${outputDir}/`);\n } else {\n writeStdout(results, localPath);\n }\n } finally {\n if (isTemp) cleanupTrace(localPath);\n }\n}\n\nfunction writeStdout(results: SnapshotResult[], tracePath: string): void {\n const actionCount = new Set(results.map((r) => r.actionIndex)).size;\n const firstTime = results.length > 0 ? results[0]!.action.startTime : 0;\n\n console.log(`\\n${'='.repeat(60)}`);\n console.log(`Agent Replay Log`);\n console.log(`Trace: ${tracePath}`);\n console.log(`Actions: ${actionCount}`);\n console.log(`${'='.repeat(60)}\\n`);\n\n let lastActionIndex = -1;\n\n for (const result of results) {\n if (result.actionIndex !== lastActionIndex) {\n lastActionIndex = result.actionIndex;\n const action = result.action;\n const relativeMs = action.startTime - firstTime;\n const timeStr = `[t=+${(relativeMs / 1000).toFixed(1)}s]`;\n const errorStr = action.error ? ` ❌ ${action.error.message}` : '';\n\n console.log(`${'━'.repeat(60)}`);\n console.log(\n `Action ${result.actionIndex + 1}: ${formatActionTitle(action)} ${timeStr}${errorStr}`\n );\n console.log(`${'━'.repeat(60)}\\n`);\n }\n\n const phaseLabel =\n result.phase === 'before'\n ? 'BEFORE (what the agent saw):'\n : 'AFTER (result):';\n console.log(phaseLabel);\n console.log(result.yaml);\n console.log('');\n }\n}\n\nfunction snapshotFilename(result: SnapshotResult, ext: string): string {\n const prefix = String(result.actionIndex + 1).padStart(3, '0');\n const slug = slugify(result.action.title);\n return `${prefix}-${slug}-${result.phase}.${ext}`;\n}\n\nfunction writeOutputDir(dir: string, results: SnapshotResult[]): void {\n fs.mkdirSync(dir, { recursive: true });\n\n let renderedCount = 0;\n\n for (const result of results) {\n fs.writeFileSync(path.join(dir, snapshotFilename(result, 'yaml')), result.yaml, 'utf-8');\n\n // Also emit the agent-rendered view (raw YAML piped through the same\n // formatter `browser_snapshot` uses in production). Without this, anyone\n // debugging a workflow run reads the raw .yaml and misses the\n // (1st)/(2nd)/[off-screen]/field: annotations the agent actually saw.\n try {\n const rendered = formatSemanticSnapshot(result.yaml);\n fs.writeFileSync(\n path.join(dir, snapshotFilename(result, 'agent.txt')),\n rendered,\n 'utf-8'\n );\n renderedCount += 1;\n } catch (err) {\n // Skip the rendered sibling on formatter failure; raw YAML is still on disk.\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`Skipped agent-rendered snapshot for ${snapshotFilename(result, 'yaml')}: ${msg}`);\n }\n\n if (result.html) {\n fs.writeFileSync(path.join(dir, snapshotFilename(result, 'html')), result.html, 'utf-8');\n }\n\n if (result.screenshotBuffer) {\n fs.writeFileSync(path.join(dir, snapshotFilename(result, 'png')), result.screenshotBuffer);\n }\n }\n\n console.log(\n `Wrote ${results.length} raw snapshot${results.length === 1 ? '' : 's'} (.yaml) ` +\n `and ${renderedCount} agent-rendered (.agent.txt) to ${dir}`\n );\n\n const replay = {\n actions: results.map((r) => ({\n index: r.actionIndex,\n type: r.action.title,\n params: r.action.params,\n phase: r.phase,\n startTime: r.action.startTime,\n endTime: r.action.endTime,\n error: r.action.error ?? null,\n snapshotFile: snapshotFilename(r, 'yaml'),\n })),\n };\n fs.writeFileSync(path.join(dir, 'replay.json'), JSON.stringify(replay, null, 2), 'utf-8');\n}\n\nfunction formatActionTitle(action: TraceAction): string {\n const { title, params } = action;\n if (!params) return title;\n if (params.selector) return `${title}(${JSON.stringify(params.selector)})`;\n if (params.url) return `${title}(${JSON.stringify(params.url)})`;\n return title;\n}\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 30);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ENV_URLS,
|
|
4
|
+
envToProfile,
|
|
5
|
+
getArgValue,
|
|
6
|
+
saveAuth
|
|
7
|
+
} from "./chunk-A4OFTUVV.js";
|
|
8
|
+
|
|
9
|
+
// src/login.ts
|
|
10
|
+
import process from "process";
|
|
11
|
+
import readline from "readline";
|
|
12
|
+
import { spawn } from "child_process";
|
|
13
|
+
function shouldOpenBrowser(argv) {
|
|
14
|
+
return !argv.includes("--no-open");
|
|
15
|
+
}
|
|
16
|
+
function openUrl(url) {
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
if (platform === "darwin") {
|
|
19
|
+
spawn("open", [url], { stdio: "ignore" });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (platform === "win32") {
|
|
23
|
+
spawn("cmd", ["/c", "start", "", url], { stdio: "ignore" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
spawn("xdg-open", [url], { stdio: "ignore" });
|
|
27
|
+
}
|
|
28
|
+
function promptChoice(question) {
|
|
29
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
rl.question(question, (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
resolve(answer.trim());
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async function fetchOrgs(apiUrl, token) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`${apiUrl}/cli-login/orgs`, {
|
|
40
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) return null;
|
|
43
|
+
return await res.json();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function switchOrg(apiUrl, token, orgId) {
|
|
49
|
+
const res = await fetch(`${apiUrl}/cli-login/switch-org`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
Authorization: `Bearer ${token}`,
|
|
53
|
+
"Content-Type": "application/json"
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify({ orgId })
|
|
56
|
+
});
|
|
57
|
+
return await res.json();
|
|
58
|
+
}
|
|
59
|
+
async function runLogin(argv) {
|
|
60
|
+
const env = getArgValue(argv, "--env");
|
|
61
|
+
const envUrls = env ? ENV_URLS[env] : void 0;
|
|
62
|
+
if (env && !envUrls) {
|
|
63
|
+
console.error(`Unknown environment: ${env}`);
|
|
64
|
+
console.error("Valid environments: prod, dev, local");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const apiUrl = getArgValue(argv, "--api-url") ?? envUrls?.api ?? process.env.CANARY_API_URL ?? "https://api.trycanary.ai";
|
|
68
|
+
const appUrl = getArgValue(argv, "--app-url") ?? envUrls?.app ?? process.env.CANARY_APP_URL ?? "https://app.trycanary.ai";
|
|
69
|
+
const orgFlag = getArgValue(argv, "--org");
|
|
70
|
+
const startRes = await fetch(`${apiUrl}/cli-login/start`, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "content-type": "application/json" },
|
|
73
|
+
body: JSON.stringify({ appUrl })
|
|
74
|
+
});
|
|
75
|
+
const startJson = await startRes.json();
|
|
76
|
+
if (!startRes.ok || !startJson.ok || !startJson.deviceCode || !startJson.userCode) {
|
|
77
|
+
console.error("Login start failed", startJson.error ?? startRes.statusText);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
console.log("Login required.");
|
|
81
|
+
console.log(`User code: ${startJson.userCode}`);
|
|
82
|
+
if (startJson.verificationUrl) {
|
|
83
|
+
console.log(`Open: ${startJson.verificationUrl}`);
|
|
84
|
+
if (shouldOpenBrowser(argv)) {
|
|
85
|
+
try {
|
|
86
|
+
openUrl(startJson.verificationUrl);
|
|
87
|
+
} catch {
|
|
88
|
+
console.log("Unable to open browser automatically. Please open the URL manually.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const intervalMs = (startJson.intervalSeconds ?? 3) * 1e3;
|
|
93
|
+
const expiresAt = startJson.expiresAt ? new Date(startJson.expiresAt).getTime() : null;
|
|
94
|
+
let token;
|
|
95
|
+
let initialOrgId;
|
|
96
|
+
while (true) {
|
|
97
|
+
if (expiresAt && Date.now() > expiresAt) {
|
|
98
|
+
console.error("Login code expired.");
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
102
|
+
const pollRes = await fetch(`${apiUrl}/cli-login/poll`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "content-type": "application/json" },
|
|
105
|
+
body: JSON.stringify({ deviceCode: startJson.deviceCode })
|
|
106
|
+
});
|
|
107
|
+
const pollJson = await pollRes.json();
|
|
108
|
+
if (!pollRes.ok || !pollJson.ok) {
|
|
109
|
+
console.error("Login poll failed", pollJson.error ?? pollRes.statusText);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
if (pollJson.status === "approved" && pollJson.accessToken) {
|
|
113
|
+
token = pollJson.accessToken;
|
|
114
|
+
initialOrgId = pollJson.orgId;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (pollJson.status === "rejected") {
|
|
118
|
+
console.error("Login rejected.");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
if (pollJson.status === "expired") {
|
|
122
|
+
console.error("Login expired.");
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const orgsData = await fetchOrgs(apiUrl, token);
|
|
127
|
+
const orgs = orgsData?.organizations ?? [];
|
|
128
|
+
let finalToken = token;
|
|
129
|
+
let finalOrgId = initialOrgId;
|
|
130
|
+
let finalOrgName;
|
|
131
|
+
if (orgs.length <= 1) {
|
|
132
|
+
finalOrgName = orgs[0]?.name;
|
|
133
|
+
} else {
|
|
134
|
+
let selectedOrg;
|
|
135
|
+
if (orgFlag) {
|
|
136
|
+
selectedOrg = orgs.find(
|
|
137
|
+
(o) => o.name.toLowerCase() === orgFlag.toLowerCase() || o.id === orgFlag
|
|
138
|
+
);
|
|
139
|
+
if (!selectedOrg) {
|
|
140
|
+
console.error(`Organization "${orgFlag}" not found. Available orgs:`);
|
|
141
|
+
for (const o of orgs) {
|
|
142
|
+
console.error(` - ${o.name} (${o.id})`);
|
|
143
|
+
}
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
} else if (process.stdin.isTTY) {
|
|
147
|
+
console.log("\nYou belong to multiple organizations. Select one:");
|
|
148
|
+
for (let i = 0; i < orgs.length; i++) {
|
|
149
|
+
const marker = orgs[i].id === initialOrgId ? " (current)" : "";
|
|
150
|
+
console.log(` ${i + 1}. ${orgs[i].name}${marker}`);
|
|
151
|
+
}
|
|
152
|
+
const answer = await promptChoice(`
|
|
153
|
+
Choice [1-${orgs.length}]: `);
|
|
154
|
+
const idx = parseInt(answer, 10) - 1;
|
|
155
|
+
if (isNaN(idx) || idx < 0 || idx >= orgs.length) {
|
|
156
|
+
console.error("Invalid selection.");
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
selectedOrg = orgs[idx];
|
|
160
|
+
} else {
|
|
161
|
+
const defaultOrg = orgs.find((o) => o.id === initialOrgId);
|
|
162
|
+
console.log(
|
|
163
|
+
`Warning: Multiple organizations available but running non-interactively. Using "${defaultOrg?.name ?? initialOrgId}".`
|
|
164
|
+
);
|
|
165
|
+
console.log("Tip: Use --org <name> to select a specific organization.");
|
|
166
|
+
selectedOrg = defaultOrg;
|
|
167
|
+
}
|
|
168
|
+
if (selectedOrg && selectedOrg.id !== initialOrgId) {
|
|
169
|
+
const switchRes = await switchOrg(apiUrl, token, selectedOrg.id);
|
|
170
|
+
if (!switchRes.ok || !switchRes.accessToken) {
|
|
171
|
+
console.error("Failed to switch organization:", switchRes.error ?? "Unknown error");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
finalToken = switchRes.accessToken;
|
|
175
|
+
finalOrgId = switchRes.orgId;
|
|
176
|
+
finalOrgName = switchRes.orgName;
|
|
177
|
+
} else if (selectedOrg) {
|
|
178
|
+
finalOrgName = selectedOrg.name;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const profileName = env ? envToProfile(env) : void 0;
|
|
182
|
+
const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName }, profileName);
|
|
183
|
+
const displayName = finalOrgName ? ` to ${finalOrgName}` : "";
|
|
184
|
+
const profileLabel = profileName ? ` (profile: ${profileName})` : "";
|
|
185
|
+
console.log(`Login successful${displayName}${profileLabel}. Token saved to ${filePath}`);
|
|
186
|
+
console.log("Set CANARY_API_TOKEN to use the CLI without re-login.");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
openUrl,
|
|
191
|
+
fetchOrgs,
|
|
192
|
+
switchOrg,
|
|
193
|
+
runLogin
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=chunk-QFDOWABX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/login.ts"],"sourcesContent":["import process from \"node:process\";\nimport readline from \"node:readline\";\nimport { spawn } from \"node:child_process\";\nimport { saveAuth, ENV_URLS, envToProfile, getArgValue } from \"./auth\";\n\nexport type StartResponse = {\n ok: boolean;\n deviceCode?: string;\n userCode?: string;\n verificationUrl?: string;\n expiresAt?: string;\n intervalSeconds?: number;\n error?: string;\n};\n\nexport type PollResponse = {\n ok: boolean;\n status?: \"pending\" | \"approved\" | \"rejected\" | \"expired\";\n accessToken?: string;\n orgId?: string;\n error?: string;\n};\n\nexport type OrgsResponse = {\n ok: boolean;\n currentOrgId?: string;\n organizations?: Array<{ id: string; name: string; role: string }>;\n error?: string;\n};\n\nexport type SwitchOrgResponse = {\n ok: boolean;\n accessToken?: string;\n orgId?: string;\n orgName?: string;\n role?: string;\n error?: string;\n};\n\nfunction shouldOpenBrowser(argv: string[]): boolean {\n return !argv.includes(\"--no-open\");\n}\n\nexport function openUrl(url: string) {\n const platform = process.platform;\n if (platform === \"darwin\") {\n spawn(\"open\", [url], { stdio: \"ignore\" });\n return;\n }\n if (platform === \"win32\") {\n spawn(\"cmd\", [\"/c\", \"start\", \"\", url], { stdio: \"ignore\" });\n return;\n }\n spawn(\"xdg-open\", [url], { stdio: \"ignore\" });\n}\n\nfunction promptChoice(question: string): Promise<string> {\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n rl.close();\n resolve(answer.trim());\n });\n });\n}\n\nexport async function fetchOrgs(apiUrl: string, token: string): Promise<OrgsResponse | null> {\n try {\n const res = await fetch(`${apiUrl}/cli-login/orgs`, {\n headers: { Authorization: `Bearer ${token}` },\n });\n if (!res.ok) return null;\n return (await res.json()) as OrgsResponse;\n } catch {\n return null;\n }\n}\n\nexport async function switchOrg(apiUrl: string, token: string, orgId: string): Promise<SwitchOrgResponse> {\n const res = await fetch(`${apiUrl}/cli-login/switch-org`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ orgId }),\n });\n return (await res.json()) as SwitchOrgResponse;\n}\n\nexport async function runLogin(argv: string[]) {\n const env = getArgValue(argv, \"--env\");\n const envUrls = env ? ENV_URLS[env] : undefined;\n\n if (env && !envUrls) {\n console.error(`Unknown environment: ${env}`);\n console.error(\"Valid environments: prod, dev, local\");\n process.exit(1);\n }\n\n const apiUrl =\n getArgValue(argv, \"--api-url\") ??\n envUrls?.api ??\n process.env.CANARY_API_URL ??\n \"https://api.trycanary.ai\";\n const appUrl =\n getArgValue(argv, \"--app-url\") ??\n envUrls?.app ??\n process.env.CANARY_APP_URL ??\n \"https://app.trycanary.ai\";\n\n const orgFlag = getArgValue(argv, \"--org\");\n\n const startRes = await fetch(`${apiUrl}/cli-login/start`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ appUrl }),\n });\n\n const startJson = (await startRes.json()) as StartResponse;\n if (!startRes.ok || !startJson.ok || !startJson.deviceCode || !startJson.userCode) {\n console.error(\"Login start failed\", startJson.error ?? startRes.statusText);\n process.exit(1);\n }\n\n console.log(\"Login required.\");\n console.log(`User code: ${startJson.userCode}`);\n if (startJson.verificationUrl) {\n console.log(`Open: ${startJson.verificationUrl}`);\n if (shouldOpenBrowser(argv)) {\n try {\n openUrl(startJson.verificationUrl);\n } catch {\n console.log(\"Unable to open browser automatically. Please open the URL manually.\");\n }\n }\n }\n\n const intervalMs = (startJson.intervalSeconds ?? 3) * 1000;\n const expiresAt = startJson.expiresAt ? new Date(startJson.expiresAt).getTime() : null;\n\n let token: string | undefined;\n let initialOrgId: string | undefined;\n\n while (true) {\n if (expiresAt && Date.now() > expiresAt) {\n console.error(\"Login code expired.\");\n process.exit(1);\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n\n const pollRes = await fetch(`${apiUrl}/cli-login/poll`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ deviceCode: startJson.deviceCode }),\n });\n\n const pollJson = (await pollRes.json()) as PollResponse;\n if (!pollRes.ok || !pollJson.ok) {\n console.error(\"Login poll failed\", pollJson.error ?? pollRes.statusText);\n process.exit(1);\n }\n\n if (pollJson.status === \"approved\" && pollJson.accessToken) {\n token = pollJson.accessToken;\n initialOrgId = pollJson.orgId;\n break;\n }\n\n if (pollJson.status === \"rejected\") {\n console.error(\"Login rejected.\");\n process.exit(1);\n }\n\n if (pollJson.status === \"expired\") {\n console.error(\"Login expired.\");\n process.exit(1);\n }\n }\n\n // Fetch user's organizations (gracefully skip if endpoint not available)\n const orgsData = await fetchOrgs(apiUrl, token);\n const orgs = orgsData?.organizations ?? [];\n\n let finalToken = token;\n let finalOrgId = initialOrgId;\n let finalOrgName: string | undefined;\n\n if (orgs.length <= 1) {\n // Single org (or none) — use as-is\n finalOrgName = orgs[0]?.name;\n } else {\n // Multiple orgs — determine which one to use\n let selectedOrg: (typeof orgs)[number] | undefined;\n\n if (orgFlag) {\n // Match --org flag by name (case-insensitive) or ID\n selectedOrg = orgs.find(\n (o) => o.name.toLowerCase() === orgFlag.toLowerCase() || o.id === orgFlag\n );\n if (!selectedOrg) {\n console.error(`Organization \"${orgFlag}\" not found. Available orgs:`);\n for (const o of orgs) {\n console.error(` - ${o.name} (${o.id})`);\n }\n process.exit(1);\n }\n } else if (process.stdin.isTTY) {\n // Interactive prompt\n console.log(\"\\nYou belong to multiple organizations. Select one:\");\n for (let i = 0; i < orgs.length; i++) {\n const marker = orgs[i].id === initialOrgId ? \" (current)\" : \"\";\n console.log(` ${i + 1}. ${orgs[i].name}${marker}`);\n }\n\n const answer = await promptChoice(`\\nChoice [1-${orgs.length}]: `);\n const idx = parseInt(answer, 10) - 1;\n if (isNaN(idx) || idx < 0 || idx >= orgs.length) {\n console.error(\"Invalid selection.\");\n process.exit(1);\n }\n selectedOrg = orgs[idx];\n } else {\n // Non-interactive — use default from JWT\n const defaultOrg = orgs.find((o) => o.id === initialOrgId);\n console.log(\n `Warning: Multiple organizations available but running non-interactively. Using \"${defaultOrg?.name ?? initialOrgId}\".`\n );\n console.log('Tip: Use --org <name> to select a specific organization.');\n selectedOrg = defaultOrg;\n }\n\n if (selectedOrg && selectedOrg.id !== initialOrgId) {\n // Switch to selected org\n const switchRes = await switchOrg(apiUrl, token, selectedOrg.id);\n if (!switchRes.ok || !switchRes.accessToken) {\n console.error(\"Failed to switch organization:\", switchRes.error ?? \"Unknown error\");\n process.exit(1);\n }\n finalToken = switchRes.accessToken;\n finalOrgId = switchRes.orgId;\n finalOrgName = switchRes.orgName;\n } else if (selectedOrg) {\n finalOrgName = selectedOrg.name;\n }\n }\n\n const profileName = env ? envToProfile(env) : undefined;\n const filePath = await saveAuth({ token: finalToken, apiUrl, orgId: finalOrgId, orgName: finalOrgName }, profileName);\n const displayName = finalOrgName ? ` to ${finalOrgName}` : \"\";\n const profileLabel = profileName ? ` (profile: ${profileName})` : \"\";\n console.log(`Login successful${displayName}${profileLabel}. Token saved to ${filePath}`);\n console.log(\"Set CANARY_API_TOKEN to use the CLI without re-login.\");\n}\n"],"mappings":"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","names":[]}
|