@odla-ai/cli 0.43.1 → 0.45.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/dist/bin.cjs +671 -552
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +12 -14
- package/dist/bin.js.map +1 -1
- package/dist/chunk-5MEO3BVF.js +282 -0
- package/dist/chunk-5MEO3BVF.js.map +1 -0
- package/dist/{chunk-T4OPSRME.js → chunk-JHB6352A.js} +429 -471
- package/dist/chunk-JHB6352A.js.map +1 -0
- package/dist/cli-2FTL245Y.js +12 -0
- package/dist/index.cjs +341 -256
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-T4OPSRME.js.map +0 -1
- package/dist/chunk-UKLSRQ5J.js +0 -129
- package/dist/chunk-UKLSRQ5J.js.map +0 -1
- package/dist/cli-3JOB4OUV.js +0 -12
- /package/dist/{cli-3JOB4OUV.js.map → cli-2FTL245Y.js.map} +0 -0
package/dist/bin.js
CHANGED
|
@@ -3,11 +3,14 @@ import {
|
|
|
3
3
|
cliVersion,
|
|
4
4
|
compareVersions,
|
|
5
5
|
exitCodeFor,
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
isWorkspaceCli,
|
|
7
|
+
redactSecrets,
|
|
8
|
+
resolvedEntryPath,
|
|
9
|
+
updateCacheFile,
|
|
10
|
+
writeUpdateCache
|
|
11
|
+
} from "./chunk-5MEO3BVF.js";
|
|
8
12
|
|
|
9
13
|
// src/cli-update.ts
|
|
10
|
-
import { realpathSync } from "fs";
|
|
11
14
|
var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@odla-ai%2fcli/latest";
|
|
12
15
|
var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
13
16
|
async function requireCurrentCliForProvision(argv2, options = {}) {
|
|
@@ -15,6 +18,7 @@ async function requireCurrentCliForProvision(argv2, options = {}) {
|
|
|
15
18
|
const current = options.currentVersion ?? cliVersion();
|
|
16
19
|
if (!VERSION.test(current)) return;
|
|
17
20
|
const latest = await fetchLatestCliVersion(options);
|
|
21
|
+
if (latest) rememberLatest(latest);
|
|
18
22
|
if (!latest || compareVersions(current, latest) >= 0) return;
|
|
19
23
|
const entryPath = resolvedEntryPath(options.entryPath ?? process.argv[1]);
|
|
20
24
|
const workspace = isWorkspaceCli(entryPath);
|
|
@@ -50,18 +54,12 @@ async function fetchLatestCliVersion(options) {
|
|
|
50
54
|
clearTimeout(timeout);
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
|
-
function
|
|
54
|
-
if (!entryPath) return "unknown executable";
|
|
57
|
+
function rememberLatest(latest) {
|
|
55
58
|
try {
|
|
56
|
-
|
|
59
|
+
writeUpdateCache(updateCacheFile(), { latest, checkedAt: Date.now() });
|
|
57
60
|
} catch {
|
|
58
|
-
return entryPath;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
61
|
-
function isWorkspaceCli(entryPath) {
|
|
62
|
-
const normalized = entryPath.replaceAll("\\", "/");
|
|
63
|
-
return normalized.includes("/packages/cli/dist/bin.") && !normalized.includes("/node_modules/");
|
|
64
|
-
}
|
|
65
63
|
function renderReleasedProvisionCommand(latest, argv2) {
|
|
66
64
|
const safeArgs = [];
|
|
67
65
|
for (let index = 0; index < argv2.length; index++) {
|
|
@@ -88,7 +86,7 @@ function shellQuote(value) {
|
|
|
88
86
|
|
|
89
87
|
// src/cli-runtime.ts
|
|
90
88
|
import { createRequire } from "module";
|
|
91
|
-
import { existsSync, readFileSync, realpathSync
|
|
89
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
92
90
|
import { dirname, isAbsolute, join, parse, resolve } from "path";
|
|
93
91
|
var EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
94
92
|
var RUNTIME_MODULES = [
|
|
@@ -166,7 +164,7 @@ function absoluteEntryPath(entryPath) {
|
|
|
166
164
|
const candidate = entryPath || join(process.cwd(), "odla-ai-cli.js");
|
|
167
165
|
const absolute = isAbsolute(candidate) ? candidate : resolve(candidate);
|
|
168
166
|
try {
|
|
169
|
-
return
|
|
167
|
+
return realpathSync(absolute);
|
|
170
168
|
} catch {
|
|
171
169
|
return absolute;
|
|
172
170
|
}
|
|
@@ -174,7 +172,7 @@ function absoluteEntryPath(entryPath) {
|
|
|
174
172
|
|
|
175
173
|
// src/bin.ts
|
|
176
174
|
var argv = process.argv.slice(2);
|
|
177
|
-
requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-
|
|
175
|
+
requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-2FTL245Y.js")).runCli()).catch((err) => {
|
|
178
176
|
console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
|
|
179
177
|
process.exitCode = exitCodeFor(err);
|
|
180
178
|
});
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli-update.ts","../src/cli-runtime.ts","../src/bin.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\";\nimport { compareVersions } from \"./runbook-requires\";\nimport { cliVersion } from \"./version\";\n\nconst DEFAULT_REGISTRY_URL = \"https://registry.npmjs.org/@odla-ai%2fcli/latest\";\nconst VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\n\nexport interface CliUpdateCheckOptions {\n currentVersion?: string;\n entryPath?: string;\n fetch?: typeof fetch;\n registryUrl?: string;\n timeoutMs?: number;\n}\n\n/**\n * Provisioning handshakes are security-sensitive protocol requests. Before a\n * real provision run, stop a CLI which is confirmed older than npm's current\n * release so an agent cannot unknowingly submit an obsolete grant shape.\n * Registry unavailability remains fail-open: npm must not become a production\n * availability dependency for an otherwise current CLI.\n */\nexport async function requireCurrentCliForProvision(\n argv: readonly string[],\n options: CliUpdateCheckOptions = {},\n): Promise<void> {\n if (argv[0] !== \"provision\" || argv.includes(\"--dry-run\")) return;\n\n const current = options.currentVersion ?? cliVersion();\n if (!VERSION.test(current)) return;\n const latest = await fetchLatestCliVersion(options);\n if (!latest || compareVersions(current, latest) >= 0) return;\n\n const entryPath = resolvedEntryPath(options.entryPath ?? process.argv[1]);\n const workspace = isWorkspaceCli(entryPath);\n const rerun = renderReleasedProvisionCommand(latest, argv);\n const source = workspace\n ? ` This executable resolves to the workspace build at ${entryPath}; update/rebase that worktree and rebuild it before using the linked CLI again.`\n : \" Update the installed dependency before using its CLI again.\";\n throw new Error(\n `provision blocked: @odla-ai/cli ${current} is older than the released ${latest}. ` +\n `Provisioning grant requests are security-sensitive, and a stale client can omit required authority.${source}\\n` +\n `Run the current release now:\\n ${rerun}`,\n );\n}\n\nasync function fetchLatestCliVersion(\n options: CliUpdateCheckOptions,\n): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 2_500);\n timeout.unref?.();\n try {\n const response = await (options.fetch ?? fetch)(\n options.registryUrl ?? process.env.ODLA_CLI_REGISTRY_URL ?? DEFAULT_REGISTRY_URL,\n {\n headers: {\n accept: \"application/json\",\n \"user-agent\": `odla-ai-cli/${options.currentVersion ?? cliVersion()}`,\n },\n signal: controller.signal,\n },\n );\n if (!response.ok) return null;\n const body = await response.json() as { version?: unknown };\n return typeof body.version === \"string\" && VERSION.test(body.version)\n ? body.version\n : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\nexport function resolvedEntryPath(entryPath: string | undefined): string {\n if (!entryPath) return \"unknown executable\";\n try {\n return realpathSync(entryPath);\n } catch {\n return entryPath;\n }\n}\n\nexport function isWorkspaceCli(entryPath: string): boolean {\n const normalized = entryPath.replaceAll(\"\\\\\", \"/\");\n return normalized.includes(\"/packages/cli/dist/bin.\") &&\n !normalized.includes(\"/node_modules/\");\n}\n\nexport function renderReleasedProvisionCommand(\n latest: string,\n argv: readonly string[],\n): string {\n const safeArgs: string[] = [];\n for (let index = 0; index < argv.length; index++) {\n const value = argv[index]!;\n safeArgs.push(value);\n if (value === \"--token\" && index + 1 < argv.length) {\n safeArgs.push(\"<redacted-token>\");\n index++;\n }\n }\n return [\n \"npm\", \"exec\", \"--yes\", `--package=@odla-ai/cli@${latest}`,\n \"--\", \"odla-ai\", ...safeArgs,\n ]\n .map(shellQuote)\n .join(\" \");\n}\n\nfunction shellQuote(value: string): string {\n return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)\n ? value\n : `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n","import { createRequire } from \"node:module\";\nimport { existsSync, readFileSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, parse, resolve } from \"node:path\";\nimport {\n isWorkspaceCli,\n renderReleasedProvisionCommand,\n resolvedEntryPath,\n} from \"./cli-update\";\nimport { cliVersion } from \"./version\";\n\nconst EXACT_VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\nconst RUNTIME_MODULES = [\n \"@odla-ai/ai\",\n \"@odla-ai/apps\",\n \"@odla-ai/brand\",\n \"@odla-ai/db\",\n \"@odla-ai/security\",\n] as const;\n\ninterface InstalledModule {\n version: string;\n path: string;\n}\n\nexport interface CliRuntimeCheckOptions {\n entryPath?: string;\n expectedVersions?: Readonly<Record<string, string>>;\n installedModules?: Readonly<Record<string, InstalledModule | undefined>>;\n}\n\n/**\n * A current CLI version is not sufficient when a workspace symlink or an npm\n * cache can resolve older external modules. Provisioning is allowed only when\n * every module loaded by the command graph has the exact version recorded in\n * the CLI manifest that was packed and tested.\n */\nexport function requireCoherentProvisionRuntime(\n argv: readonly string[],\n options: CliRuntimeCheckOptions = {},\n): void {\n if (argv[0] !== \"provision\" || argv.includes(\"--dry-run\")) return;\n\n const entryPath = absoluteEntryPath(options.entryPath ?? process.argv[1]);\n const expected = options.expectedVersions ?? expectedRuntimeVersions(entryPath);\n const installed = options.installedModules ?? installedRuntimeModules(entryPath);\n const problems: string[] = [];\n\n for (const name of RUNTIME_MODULES) {\n const wanted = expected[name];\n const loaded = installed[name];\n if (!wanted || !EXACT_VERSION.test(wanted)) {\n problems.push(`${name}: CLI manifest does not pin an exact tested version`);\n } else if (!loaded) {\n problems.push(`${name}: expected ${wanted}, module is not installed`);\n } else if (loaded.version !== wanted) {\n problems.push(`${name}: expected ${wanted}, resolved ${loaded.version} from ${loaded.path}`);\n }\n }\n if (problems.length === 0) return;\n\n const workspace = isWorkspaceCli(resolvedEntryPath(entryPath));\n const repair = workspace\n ? \"Update/rebase this worktree, run npm ci, and rebuild the CLI before provisioning again.\"\n : \"Reinstall the CLI so npm replaces its complete pinned runtime dependency graph.\";\n throw new Error(\n `provision blocked: the @odla-ai/cli runtime dependency graph is stale or incoherent:\\n` +\n `${problems.map((problem) => ` - ${problem}`).join(\"\\n\")}\\n` +\n `${repair}\\nRun the isolated released graph now:\\n ${renderReleasedProvisionCommand(cliVersion(), argv)}`,\n );\n}\n\nfunction expectedRuntimeVersions(entryPath: string): Record<string, string> {\n const manifest = findPackageManifest(entryPath, \"@odla-ai/cli\");\n const dependencies = manifest?.json.dependencies;\n return Object.fromEntries(RUNTIME_MODULES.map((name) => [\n name,\n typeof dependencies?.[name] === \"string\" ? dependencies[name] : \"\",\n ]));\n}\n\nfunction installedRuntimeModules(entryPath: string): Record<string, InstalledModule | undefined> {\n const require = createRequire(entryPath);\n return Object.fromEntries(RUNTIME_MODULES.map((name) => {\n try {\n const moduleEntry = require.resolve(name);\n const manifest = findPackageManifest(moduleEntry, name);\n return [name, manifest ? { version: manifest.json.version, path: manifest.path } : undefined];\n } catch {\n return [name, undefined];\n }\n }));\n}\n\nfunction findPackageManifest(\n fromPath: string,\n expectedName: string,\n): { path: string; json: { name?: string; version: string; dependencies?: Record<string, string> } } | undefined {\n let directory = dirname(resolvedEntryPath(fromPath));\n const root = parse(directory).root;\n while (true) {\n const path = join(directory, \"package.json\");\n if (existsSync(path)) {\n try {\n const json = JSON.parse(readFileSync(path, \"utf8\"));\n if (json?.name === expectedName && typeof json.version === \"string\") return { path, json };\n } catch {\n // Keep walking: a consumer package.json is not the module manifest.\n }\n }\n if (directory === root) return undefined;\n directory = dirname(directory);\n }\n}\n\nfunction absoluteEntryPath(entryPath: string | undefined): string {\n const candidate = entryPath || join(process.cwd(), \"odla-ai-cli.js\");\n const absolute = isAbsolute(candidate) ? candidate : resolve(candidate);\n try {\n return realpathSync(absolute);\n } catch {\n return absolute;\n }\n}\n","import { requireCurrentCliForProvision } from \"./cli-update\";\nimport { requireCoherentProvisionRuntime } from \"./cli-runtime\";\nimport { exitCodeFor } from \"./exit-code\";\nimport { redactSecrets } from \"./redact\";\n\nconst argv = process.argv.slice(2);\n\nrequireCurrentCliForProvision(argv)\n .then(() => requireCoherentProvisionRuntime(argv))\n // Keep the full command graph, including its external @odla-ai modules, out\n // of the process until both stale-client checks have had a chance to explain\n // exactly how to repair the executable and dependency graph.\n .then(async () => (await import(\"./cli\")).runCli())\n .catch((err) => {\n console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = exitCodeFor(err);\n });\n"],"mappings":";;;;;;;;;AAAA,SAAS,oBAAoB;AAI7B,IAAM,uBAAuB;AAC7B,IAAM,UAAU;AAiBhB,eAAsB,8BACpBA,OACA,UAAiC,CAAC,GACnB;AACf,MAAIA,MAAK,CAAC,MAAM,eAAeA,MAAK,SAAS,WAAW,EAAG;AAE3D,QAAM,UAAU,QAAQ,kBAAkB,WAAW;AACrD,MAAI,CAAC,QAAQ,KAAK,OAAO,EAAG;AAC5B,QAAM,SAAS,MAAM,sBAAsB,OAAO;AAClD,MAAI,CAAC,UAAU,gBAAgB,SAAS,MAAM,KAAK,EAAG;AAEtD,QAAM,YAAY,kBAAkB,QAAQ,aAAa,QAAQ,KAAK,CAAC,CAAC;AACxE,QAAM,YAAY,eAAe,SAAS;AAC1C,QAAM,QAAQ,+BAA+B,QAAQA,KAAI;AACzD,QAAM,SAAS,YACX,uDAAuD,SAAS,oFAChE;AACJ,QAAM,IAAI;AAAA,IACR,mCAAmC,OAAO,+BAA+B,MAAM,wGACuB,MAAM;AAAA;AAAA,IACzE,KAAK;AAAA,EAC1C;AACF;AAEA,eAAe,sBACb,SACwB;AACxB,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,QAAQ,aAAa,IAAK;AAC/E,UAAQ,QAAQ;AAChB,MAAI;AACF,UAAM,WAAW,OAAO,QAAQ,SAAS;AAAA,MACvC,QAAQ,eAAe,QAAQ,IAAI,yBAAyB;AAAA,MAC5D;AAAA,QACE,SAAS;AAAA,UACP,QAAQ;AAAA,UACR,cAAc,eAAe,QAAQ,kBAAkB,WAAW,CAAC;AAAA,QACrE;AAAA,QACA,QAAQ,WAAW;AAAA,MACrB;AAAA,IACF;AACA,QAAI,CAAC,SAAS,GAAI,QAAO;AACzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,OAAO,KAAK,YAAY,YAAY,QAAQ,KAAK,KAAK,OAAO,IAChE,KAAK,UACL;AAAA,EACN,QAAQ;AACN,WAAO;AAAA,EACT,UAAE;AACA,iBAAa,OAAO;AAAA,EACtB;AACF;AAEO,SAAS,kBAAkB,WAAuC;AACvE,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI;AACF,WAAO,aAAa,SAAS;AAAA,EAC/B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,WAA4B;AACzD,QAAM,aAAa,UAAU,WAAW,MAAM,GAAG;AACjD,SAAO,WAAW,SAAS,yBAAyB,KAClD,CAAC,WAAW,SAAS,gBAAgB;AACzC;AAEO,SAAS,+BACd,QACAA,OACQ;AACR,QAAM,WAAqB,CAAC;AAC5B,WAAS,QAAQ,GAAG,QAAQA,MAAK,QAAQ,SAAS;AAChD,UAAM,QAAQA,MAAK,KAAK;AACxB,aAAS,KAAK,KAAK;AACnB,QAAI,UAAU,aAAa,QAAQ,IAAIA,MAAK,QAAQ;AAClD,eAAS,KAAK,kBAAkB;AAChC;AAAA,IACF;AAAA,EACF;AACA,SAAO;AAAA,IACL;AAAA,IAAO;AAAA,IAAQ;AAAA,IAAS,0BAA0B,MAAM;AAAA,IACxD;AAAA,IAAM;AAAA,IAAW,GAAG;AAAA,EACtB,EACG,IAAI,UAAU,EACd,KAAK,GAAG;AACb;AAEA,SAAS,WAAW,OAAuB;AACzC,SAAO,2BAA2B,KAAK,KAAK,IACxC,QACA,IAAI,MAAM,WAAW,KAAK,OAAO,CAAC;AACxC;;;ACnHA,SAAS,qBAAqB;AAC9B,SAAS,YAAY,cAAc,gBAAAC,qBAAoB;AACvD,SAAS,SAAS,YAAY,MAAM,OAAO,eAAe;AAQ1D,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAmBO,SAAS,gCACdC,OACA,UAAkC,CAAC,GAC7B;AACN,MAAIA,MAAK,CAAC,MAAM,eAAeA,MAAK,SAAS,WAAW,EAAG;AAE3D,QAAM,YAAY,kBAAkB,QAAQ,aAAa,QAAQ,KAAK,CAAC,CAAC;AACxE,QAAM,WAAW,QAAQ,oBAAoB,wBAAwB,SAAS;AAC9E,QAAM,YAAY,QAAQ,oBAAoB,wBAAwB,SAAS;AAC/E,QAAM,WAAqB,CAAC;AAE5B,aAAW,QAAQ,iBAAiB;AAClC,UAAM,SAAS,SAAS,IAAI;AAC5B,UAAM,SAAS,UAAU,IAAI;AAC7B,QAAI,CAAC,UAAU,CAAC,cAAc,KAAK,MAAM,GAAG;AAC1C,eAAS,KAAK,GAAG,IAAI,qDAAqD;AAAA,IAC5E,WAAW,CAAC,QAAQ;AAClB,eAAS,KAAK,GAAG,IAAI,cAAc,MAAM,2BAA2B;AAAA,IACtE,WAAW,OAAO,YAAY,QAAQ;AACpC,eAAS,KAAK,GAAG,IAAI,cAAc,MAAM,cAAc,OAAO,OAAO,SAAS,OAAO,IAAI,EAAE;AAAA,IAC7F;AAAA,EACF;AACA,MAAI,SAAS,WAAW,EAAG;AAE3B,QAAM,YAAY,eAAe,kBAAkB,SAAS,CAAC;AAC7D,QAAM,SAAS,YACX,4FACA;AACJ,QAAM,IAAI;AAAA,IACR;AAAA,EACG,SAAS,IAAI,CAAC,YAAY,OAAO,OAAO,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,EACtD,MAAM;AAAA;AAAA,IAA6C,+BAA+B,WAAW,GAAGA,KAAI,CAAC;AAAA,EAC1G;AACF;AAEA,SAAS,wBAAwB,WAA2C;AAC1E,QAAM,WAAW,oBAAoB,WAAW,cAAc;AAC9D,QAAM,eAAe,UAAU,KAAK;AACpC,SAAO,OAAO,YAAY,gBAAgB,IAAI,CAAC,SAAS;AAAA,IACtD;AAAA,IACA,OAAO,eAAe,IAAI,MAAM,WAAW,aAAa,IAAI,IAAI;AAAA,EAClE,CAAC,CAAC;AACJ;AAEA,SAAS,wBAAwB,WAAgE;AAC/F,QAAMC,WAAU,cAAc,SAAS;AACvC,SAAO,OAAO,YAAY,gBAAgB,IAAI,CAAC,SAAS;AACtD,QAAI;AACF,YAAM,cAAcA,SAAQ,QAAQ,IAAI;AACxC,YAAM,WAAW,oBAAoB,aAAa,IAAI;AACtD,aAAO,CAAC,MAAM,WAAW,EAAE,SAAS,SAAS,KAAK,SAAS,MAAM,SAAS,KAAK,IAAI,MAAS;AAAA,IAC9F,QAAQ;AACN,aAAO,CAAC,MAAM,MAAS;AAAA,IACzB;AAAA,EACF,CAAC,CAAC;AACJ;AAEA,SAAS,oBACP,UACA,cAC+G;AAC/G,MAAI,YAAY,QAAQ,kBAAkB,QAAQ,CAAC;AACnD,QAAM,OAAO,MAAM,SAAS,EAAE;AAC9B,SAAO,MAAM;AACX,UAAM,OAAO,KAAK,WAAW,cAAc;AAC3C,QAAI,WAAW,IAAI,GAAG;AACpB,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAClD,YAAI,MAAM,SAAS,gBAAgB,OAAO,KAAK,YAAY,SAAU,QAAO,EAAE,MAAM,KAAK;AAAA,MAC3F,QAAQ;AAAA,MAER;AAAA,IACF;AACA,QAAI,cAAc,KAAM,QAAO;AAC/B,gBAAY,QAAQ,SAAS;AAAA,EAC/B;AACF;AAEA,SAAS,kBAAkB,WAAuC;AAChE,QAAM,YAAY,aAAa,KAAK,QAAQ,IAAI,GAAG,gBAAgB;AACnE,QAAM,WAAW,WAAW,SAAS,IAAI,YAAY,QAAQ,SAAS;AACtE,MAAI;AACF,WAAOC,cAAa,QAAQ;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACrHA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,8BAA8B,IAAI,EAC/B,KAAK,MAAM,gCAAgC,IAAI,CAAC,EAIhD,KAAK,aAAa,MAAM,OAAO,mBAAO,GAAG,OAAO,CAAC,EACjD,MAAM,CAAC,QAAQ;AACd,UAAQ,MAAM,cAAc,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE,CAAC;AAC3F,UAAQ,WAAW,YAAY,GAAG;AACpC,CAAC;","names":["argv","realpathSync","argv","require","realpathSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli-update.ts","../src/cli-runtime.ts","../src/bin.ts"],"sourcesContent":["import { compareVersions } from \"./runbook-requires\";\nimport { isWorkspaceCli, resolvedEntryPath, updateCacheFile, writeUpdateCache } from \"./update-notice\";\nimport { cliVersion } from \"./version\";\n\nconst DEFAULT_REGISTRY_URL = \"https://registry.npmjs.org/@odla-ai%2fcli/latest\";\nconst VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\n\nexport interface CliUpdateCheckOptions {\n currentVersion?: string;\n entryPath?: string;\n fetch?: typeof fetch;\n registryUrl?: string;\n timeoutMs?: number;\n}\n\n/**\n * Provisioning handshakes are security-sensitive protocol requests. Before a\n * real provision run, stop a CLI which is confirmed older than npm's current\n * release so an agent cannot unknowingly submit an obsolete grant shape.\n * Registry unavailability remains fail-open: npm must not become a production\n * availability dependency for an otherwise current CLI.\n */\nexport async function requireCurrentCliForProvision(\n argv: readonly string[],\n options: CliUpdateCheckOptions = {},\n): Promise<void> {\n if (argv[0] !== \"provision\" || argv.includes(\"--dry-run\")) return;\n\n const current = options.currentVersion ?? cliVersion();\n if (!VERSION.test(current)) return;\n const latest = await fetchLatestCliVersion(options);\n // Provision keeps its own LIVE fetch — a security gate must not trust a cache\n // that may be three hours old — but the answer it just paid for is the same\n // answer every other command reads, so leave it where they look.\n if (latest) rememberLatest(latest);\n if (!latest || compareVersions(current, latest) >= 0) return;\n\n const entryPath = resolvedEntryPath(options.entryPath ?? process.argv[1]);\n const workspace = isWorkspaceCli(entryPath);\n const rerun = renderReleasedProvisionCommand(latest, argv);\n const source = workspace\n ? ` This executable resolves to the workspace build at ${entryPath}; update/rebase that worktree and rebuild it before using the linked CLI again.`\n : \" Update the installed dependency before using its CLI again.\";\n throw new Error(\n `provision blocked: @odla-ai/cli ${current} is older than the released ${latest}. ` +\n `Provisioning grant requests are security-sensitive, and a stale client can omit required authority.${source}\\n` +\n `Run the current release now:\\n ${rerun}`,\n );\n}\n\nasync function fetchLatestCliVersion(\n options: CliUpdateCheckOptions,\n): Promise<string | null> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 2_500);\n timeout.unref?.();\n try {\n const response = await (options.fetch ?? fetch)(\n options.registryUrl ?? process.env.ODLA_CLI_REGISTRY_URL ?? DEFAULT_REGISTRY_URL,\n {\n headers: {\n accept: \"application/json\",\n \"user-agent\": `odla-ai-cli/${options.currentVersion ?? cliVersion()}`,\n },\n signal: controller.signal,\n },\n );\n if (!response.ok) return null;\n const body = await response.json() as { version?: unknown };\n return typeof body.version === \"string\" && VERSION.test(body.version)\n ? body.version\n : null;\n } catch {\n return null;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n/** Seed the shared cache. Best effort by definition: nothing about provisioning\n * may fail because a courtesy file could not be written. */\nfunction rememberLatest(latest: string): void {\n try {\n writeUpdateCache(updateCacheFile(), { latest, checkedAt: Date.now() });\n } catch {\n // No home directory, no permission, a read-only machine — all fine.\n }\n}\n\n\nexport function renderReleasedProvisionCommand(\n latest: string,\n argv: readonly string[],\n): string {\n const safeArgs: string[] = [];\n for (let index = 0; index < argv.length; index++) {\n const value = argv[index]!;\n safeArgs.push(value);\n if (value === \"--token\" && index + 1 < argv.length) {\n safeArgs.push(\"<redacted-token>\");\n index++;\n }\n }\n return [\n \"npm\", \"exec\", \"--yes\", `--package=@odla-ai/cli@${latest}`,\n \"--\", \"odla-ai\", ...safeArgs,\n ]\n .map(shellQuote)\n .join(\" \");\n}\n\nfunction shellQuote(value: string): string {\n return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)\n ? value\n : `'${value.replaceAll(\"'\", `'\"'\"'`)}'`;\n}\n","import { createRequire } from \"node:module\";\nimport { existsSync, readFileSync, realpathSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, parse, resolve } from \"node:path\";\nimport {\n renderReleasedProvisionCommand,\n} from \"./cli-update\";\nimport { isWorkspaceCli, resolvedEntryPath } from \"./update-notice\";\nimport { cliVersion } from \"./version\";\n\nconst EXACT_VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\nconst RUNTIME_MODULES = [\n \"@odla-ai/ai\",\n \"@odla-ai/apps\",\n \"@odla-ai/brand\",\n \"@odla-ai/db\",\n \"@odla-ai/security\",\n] as const;\n\ninterface InstalledModule {\n version: string;\n path: string;\n}\n\nexport interface CliRuntimeCheckOptions {\n entryPath?: string;\n expectedVersions?: Readonly<Record<string, string>>;\n installedModules?: Readonly<Record<string, InstalledModule | undefined>>;\n}\n\n/**\n * A current CLI version is not sufficient when a workspace symlink or an npm\n * cache can resolve older external modules. Provisioning is allowed only when\n * every module loaded by the command graph has the exact version recorded in\n * the CLI manifest that was packed and tested.\n */\nexport function requireCoherentProvisionRuntime(\n argv: readonly string[],\n options: CliRuntimeCheckOptions = {},\n): void {\n if (argv[0] !== \"provision\" || argv.includes(\"--dry-run\")) return;\n\n const entryPath = absoluteEntryPath(options.entryPath ?? process.argv[1]);\n const expected = options.expectedVersions ?? expectedRuntimeVersions(entryPath);\n const installed = options.installedModules ?? installedRuntimeModules(entryPath);\n const problems: string[] = [];\n\n for (const name of RUNTIME_MODULES) {\n const wanted = expected[name];\n const loaded = installed[name];\n if (!wanted || !EXACT_VERSION.test(wanted)) {\n problems.push(`${name}: CLI manifest does not pin an exact tested version`);\n } else if (!loaded) {\n problems.push(`${name}: expected ${wanted}, module is not installed`);\n } else if (loaded.version !== wanted) {\n problems.push(`${name}: expected ${wanted}, resolved ${loaded.version} from ${loaded.path}`);\n }\n }\n if (problems.length === 0) return;\n\n const workspace = isWorkspaceCli(resolvedEntryPath(entryPath));\n const repair = workspace\n ? \"Update/rebase this worktree, run npm ci, and rebuild the CLI before provisioning again.\"\n : \"Reinstall the CLI so npm replaces its complete pinned runtime dependency graph.\";\n throw new Error(\n `provision blocked: the @odla-ai/cli runtime dependency graph is stale or incoherent:\\n` +\n `${problems.map((problem) => ` - ${problem}`).join(\"\\n\")}\\n` +\n `${repair}\\nRun the isolated released graph now:\\n ${renderReleasedProvisionCommand(cliVersion(), argv)}`,\n );\n}\n\nfunction expectedRuntimeVersions(entryPath: string): Record<string, string> {\n const manifest = findPackageManifest(entryPath, \"@odla-ai/cli\");\n const dependencies = manifest?.json.dependencies;\n return Object.fromEntries(RUNTIME_MODULES.map((name) => [\n name,\n typeof dependencies?.[name] === \"string\" ? dependencies[name] : \"\",\n ]));\n}\n\nfunction installedRuntimeModules(entryPath: string): Record<string, InstalledModule | undefined> {\n const require = createRequire(entryPath);\n return Object.fromEntries(RUNTIME_MODULES.map((name) => {\n try {\n const moduleEntry = require.resolve(name);\n const manifest = findPackageManifest(moduleEntry, name);\n return [name, manifest ? { version: manifest.json.version, path: manifest.path } : undefined];\n } catch {\n return [name, undefined];\n }\n }));\n}\n\nfunction findPackageManifest(\n fromPath: string,\n expectedName: string,\n): { path: string; json: { name?: string; version: string; dependencies?: Record<string, string> } } | undefined {\n let directory = dirname(resolvedEntryPath(fromPath));\n const root = parse(directory).root;\n while (true) {\n const path = join(directory, \"package.json\");\n if (existsSync(path)) {\n try {\n const json = JSON.parse(readFileSync(path, \"utf8\"));\n if (json?.name === expectedName && typeof json.version === \"string\") return { path, json };\n } catch {\n // Keep walking: a consumer package.json is not the module manifest.\n }\n }\n if (directory === root) return undefined;\n directory = dirname(directory);\n }\n}\n\nfunction absoluteEntryPath(entryPath: string | undefined): string {\n const candidate = entryPath || join(process.cwd(), \"odla-ai-cli.js\");\n const absolute = isAbsolute(candidate) ? candidate : resolve(candidate);\n try {\n return realpathSync(absolute);\n } catch {\n return absolute;\n }\n}\n","import { requireCurrentCliForProvision } from \"./cli-update\";\nimport { requireCoherentProvisionRuntime } from \"./cli-runtime\";\nimport { exitCodeFor } from \"./exit-code\";\nimport { redactSecrets } from \"./redact\";\n\nconst argv = process.argv.slice(2);\n\nrequireCurrentCliForProvision(argv)\n .then(() => requireCoherentProvisionRuntime(argv))\n // Keep the full command graph, including its external @odla-ai modules, out\n // of the process until both stale-client checks have had a chance to explain\n // exactly how to repair the executable and dependency graph.\n .then(async () => (await import(\"./cli\")).runCli())\n .catch((err) => {\n console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = exitCodeFor(err);\n });\n"],"mappings":"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","names":["argv","argv","require"]}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/version.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
function cliVersion() {
|
|
6
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
|
+
return pkg.version ?? "unknown";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// src/update-notice.ts
|
|
11
|
+
import { spawn } from "child_process";
|
|
12
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, writeFileSync } from "fs";
|
|
13
|
+
import { dirname as dirname2 } from "path";
|
|
14
|
+
import process2 from "process";
|
|
15
|
+
|
|
16
|
+
// src/odla-home.ts
|
|
17
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync } from "fs";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import { dirname, join } from "path";
|
|
20
|
+
import process from "process";
|
|
21
|
+
function odlaHome(env = process.env) {
|
|
22
|
+
if (env.VITEST && !env.ODLA_HOME) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
return env.ODLA_HOME ?? join(env.HOME ?? homedir(), ".odla");
|
|
28
|
+
}
|
|
29
|
+
function odlaHomePath(segments, env = process.env) {
|
|
30
|
+
return join(odlaHome(env), ...segments);
|
|
31
|
+
}
|
|
32
|
+
function identityFile(env) {
|
|
33
|
+
return odlaHomePath(["identity.json"], env);
|
|
34
|
+
}
|
|
35
|
+
function deviceSessionFile(env) {
|
|
36
|
+
return odlaHomePath(["session.json"], env);
|
|
37
|
+
}
|
|
38
|
+
function appTokenFile(appId, env) {
|
|
39
|
+
return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
|
|
40
|
+
}
|
|
41
|
+
function appCredentialsFile(appId, env) {
|
|
42
|
+
return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
|
|
43
|
+
}
|
|
44
|
+
function scopedTokenFile(env) {
|
|
45
|
+
return odlaHomePath(["admin-token.local.json"], env);
|
|
46
|
+
}
|
|
47
|
+
function pmContextFile(env) {
|
|
48
|
+
return odlaHomePath(["pm-context.json"], env);
|
|
49
|
+
}
|
|
50
|
+
function adoptRepoLocalCache(legacyPath, machinePath, out) {
|
|
51
|
+
if (!existsSync(legacyPath) || legacyPath === machinePath) return false;
|
|
52
|
+
const superseded = existsSync(machinePath);
|
|
53
|
+
if (!superseded) {
|
|
54
|
+
mkdirSync(dirname(machinePath), { recursive: true });
|
|
55
|
+
copyFileSync(legacyPath, machinePath);
|
|
56
|
+
chmodSync(machinePath, 384);
|
|
57
|
+
}
|
|
58
|
+
rmSync(legacyPath, { force: true });
|
|
59
|
+
out?.error(
|
|
60
|
+
superseded ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
|
|
61
|
+
);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function safeSegment(value) {
|
|
65
|
+
const clean = value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
|
|
66
|
+
if (!clean) throw new Error(`"${value}" is not a usable app id`);
|
|
67
|
+
return clean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/runbook-requires.ts
|
|
71
|
+
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
72
|
+
function parseRequires(value) {
|
|
73
|
+
if (!value) return [];
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const token of value.split(/[\s,]+/).filter(Boolean)) {
|
|
76
|
+
const match = SPEC.exec(token);
|
|
77
|
+
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
function compareVersions(a, b) {
|
|
82
|
+
const parts = (v) => {
|
|
83
|
+
const [core = "", pre = ""] = v.split("-", 2);
|
|
84
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
85
|
+
};
|
|
86
|
+
const left = parts(a);
|
|
87
|
+
const right = parts(b);
|
|
88
|
+
for (let i = 0; i < Math.max(left.nums.length, right.nums.length); i++) {
|
|
89
|
+
const diff = (left.nums[i] ?? 0) - (right.nums[i] ?? 0);
|
|
90
|
+
if (diff) return diff < 0 ? -1 : 1;
|
|
91
|
+
}
|
|
92
|
+
if (left.pre === right.pre) return 0;
|
|
93
|
+
if (!left.pre) return 1;
|
|
94
|
+
if (!right.pre) return -1;
|
|
95
|
+
return left.pre < right.pre ? -1 : 1;
|
|
96
|
+
}
|
|
97
|
+
function satisfies(installed, min) {
|
|
98
|
+
return compareVersions(installed, min) >= 0;
|
|
99
|
+
}
|
|
100
|
+
function unmetRequirements(requires, installedVersions) {
|
|
101
|
+
const unmet = [];
|
|
102
|
+
for (const requirement of parseRequires(requires)) {
|
|
103
|
+
const installed = installedVersions[requirement.name];
|
|
104
|
+
if (installed === void 0) {
|
|
105
|
+
unmet.push({ ...requirement, installed: null });
|
|
106
|
+
} else if (!satisfies(installed, requirement.min)) {
|
|
107
|
+
unmet.push({ ...requirement, installed });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return unmet;
|
|
111
|
+
}
|
|
112
|
+
function describeUnmet(slug, unmet) {
|
|
113
|
+
const detail = unmet.map((u) => `${u.name}@${u.min}${u.installed ? ` (you have ${u.installed})` : " (not installed here)"}`).join(", ");
|
|
114
|
+
return `note: runbook "${slug}" expects ${detail} \u2014 some steps may name commands your version does not have.`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/update-notice.ts
|
|
118
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@odla-ai%2fcli/latest";
|
|
119
|
+
var INTERVAL_MS = 3 * 60 * 60 * 1e3;
|
|
120
|
+
var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
121
|
+
var refreshScheduled = false;
|
|
122
|
+
function updateCacheFile(env) {
|
|
123
|
+
return odlaHomePath(["cli-update.json"], env);
|
|
124
|
+
}
|
|
125
|
+
function readUpdateCache(path) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
128
|
+
if (typeof parsed.latest !== "string" || !VERSION.test(parsed.latest)) return null;
|
|
129
|
+
if (typeof parsed.checkedAt !== "number") return null;
|
|
130
|
+
return { latest: parsed.latest, checkedAt: parsed.checkedAt };
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function writeUpdateCache(path, cache) {
|
|
136
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
137
|
+
writeFileSync(path, JSON.stringify(cache));
|
|
138
|
+
}
|
|
139
|
+
function updateNotice(options = {}) {
|
|
140
|
+
const env = options.env ?? process2.env;
|
|
141
|
+
if (env.ODLA_CLI_UPDATE_CHECK === "0") return null;
|
|
142
|
+
const current = options.currentVersion ?? cliVersion();
|
|
143
|
+
if (!VERSION.test(current)) return null;
|
|
144
|
+
const path = updateCacheFile(env);
|
|
145
|
+
const cache = readUpdateCache(path);
|
|
146
|
+
const now = options.now ?? Date.now();
|
|
147
|
+
if ((!cache || now - cache.checkedAt > INTERVAL_MS) && !refreshScheduled) {
|
|
148
|
+
refreshScheduled = true;
|
|
149
|
+
(options.refresh ?? spawnRefresh)(path, env.ODLA_CLI_REGISTRY_URL ?? REGISTRY_URL);
|
|
150
|
+
}
|
|
151
|
+
if (!cache || compareVersions(current, cache.latest) >= 0) return null;
|
|
152
|
+
return renderNotice(current, cache.latest, options.entryPath ?? process2.argv[1]);
|
|
153
|
+
}
|
|
154
|
+
function renderNotice(current, latest, entryPath) {
|
|
155
|
+
const resolved = resolvedEntryPath(entryPath);
|
|
156
|
+
const behindMajor = Number(latest.split(".")[0]) > Number(current.split(".")[0]);
|
|
157
|
+
const repair = isWorkspaceCli(resolved) ? `this is the workspace build at ${resolved} \u2014 rebase that worktree and rebuild it` : resolved.includes("/_npx/") ? `run npx --yes @odla-ai/cli@${latest} <command>` : `run npm i @odla-ai/cli@${latest}`;
|
|
158
|
+
const severity = behindMajor ? "a MAJOR version behind, so commands and flags this version has may no longer exist" : "behind";
|
|
159
|
+
return `odla-ai: ${current} is ${severity}; npm serves ${latest}. To update, ${repair}.`;
|
|
160
|
+
}
|
|
161
|
+
function spawnRefresh(cachePath, registryUrl) {
|
|
162
|
+
const script = `
|
|
163
|
+
const {mkdirSync,writeFileSync}=require("node:fs");
|
|
164
|
+
const {dirname}=require("node:path");
|
|
165
|
+
const [path,url]=process.argv.slice(1);
|
|
166
|
+
const done=setTimeout(()=>process.exit(0),5000); done.unref();
|
|
167
|
+
fetch(url,{headers:{accept:"application/vnd.npm.install-v1+json"}})
|
|
168
|
+
.then(r=>r.ok?r.json():null)
|
|
169
|
+
.then(b=>{
|
|
170
|
+
const v=b&&b.version;
|
|
171
|
+
if (typeof v!=="string"||!/^\\d+\\.\\d+\\.\\d+/.test(v)) return;
|
|
172
|
+
mkdirSync(dirname(path),{recursive:true});
|
|
173
|
+
writeFileSync(path,JSON.stringify({latest:v,checkedAt:Date.now()}));
|
|
174
|
+
})
|
|
175
|
+
.catch(()=>{});
|
|
176
|
+
`;
|
|
177
|
+
try {
|
|
178
|
+
spawn(process2.execPath, ["-e", script, cachePath, registryUrl], {
|
|
179
|
+
detached: true,
|
|
180
|
+
stdio: "ignore"
|
|
181
|
+
}).unref();
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function resolvedEntryPath(entryPath) {
|
|
186
|
+
if (!entryPath) return "unknown executable";
|
|
187
|
+
try {
|
|
188
|
+
return realpathSync(entryPath);
|
|
189
|
+
} catch {
|
|
190
|
+
return entryPath;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function isWorkspaceCli(entryPath) {
|
|
194
|
+
const normalized = entryPath.replaceAll("\\", "/");
|
|
195
|
+
return /\/packages\/cli\/(dist|bin)\//.test(normalized) && !normalized.includes("/node_modules/");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/exit-code.ts
|
|
199
|
+
function exitCodeFor(err) {
|
|
200
|
+
const code = err?.code;
|
|
201
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
202
|
+
if (code === "checkpoint_required") return 3;
|
|
203
|
+
if (code === "remote_unavailable") return 6;
|
|
204
|
+
if (code === "auth_failed") return 5;
|
|
205
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/redact.ts
|
|
210
|
+
var REPLACEMENTS = [
|
|
211
|
+
[/odla_(sk|dev)_[A-Za-z0-9._-]+/g, "odla_$1_[redacted]"],
|
|
212
|
+
[/\bsk_(live|test)_[A-Za-z0-9]+/g, "sk_$1_[redacted]"],
|
|
213
|
+
[/\bsk-[A-Za-z0-9._-]+/g, "sk-[redacted]"],
|
|
214
|
+
[/\bwhsec_[A-Za-z0-9+/=]+/g, "whsec_[redacted]"],
|
|
215
|
+
[/\bo11y_[A-Za-z0-9]+/g, "o11y_[redacted]"],
|
|
216
|
+
[/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
|
|
217
|
+
[/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
|
|
218
|
+
];
|
|
219
|
+
function redactSecrets(value) {
|
|
220
|
+
let result = value;
|
|
221
|
+
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
function redactingOutput(output) {
|
|
225
|
+
const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
|
|
226
|
+
return {
|
|
227
|
+
log: (...args) => output.log(...redactArgs(args)),
|
|
228
|
+
error: (...args) => output.error(...redactArgs(args))
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function redactOutputValue(value, seen) {
|
|
232
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
233
|
+
if (value instanceof Error) {
|
|
234
|
+
const redacted = new Error(redactSecrets(value.message));
|
|
235
|
+
redacted.name = value.name;
|
|
236
|
+
if (value.stack) redacted.stack = redactSecrets(value.stack);
|
|
237
|
+
return redacted;
|
|
238
|
+
}
|
|
239
|
+
if (!value || typeof value !== "object") return value;
|
|
240
|
+
const prior = seen.get(value);
|
|
241
|
+
if (prior) return prior;
|
|
242
|
+
if (Array.isArray(value)) {
|
|
243
|
+
const next2 = [];
|
|
244
|
+
seen.set(value, next2);
|
|
245
|
+
for (const item of value) next2.push(redactOutputValue(item, seen));
|
|
246
|
+
return next2;
|
|
247
|
+
}
|
|
248
|
+
const prototype = Object.getPrototypeOf(value);
|
|
249
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
250
|
+
const next = {};
|
|
251
|
+
seen.set(value, next);
|
|
252
|
+
for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
|
|
253
|
+
return next;
|
|
254
|
+
}
|
|
255
|
+
function looksSecret(value) {
|
|
256
|
+
return redactSecrets(value) !== value || value.includes("-----BEGIN");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
parseRequires,
|
|
261
|
+
compareVersions,
|
|
262
|
+
unmetRequirements,
|
|
263
|
+
describeUnmet,
|
|
264
|
+
identityFile,
|
|
265
|
+
deviceSessionFile,
|
|
266
|
+
appTokenFile,
|
|
267
|
+
appCredentialsFile,
|
|
268
|
+
scopedTokenFile,
|
|
269
|
+
pmContextFile,
|
|
270
|
+
adoptRepoLocalCache,
|
|
271
|
+
cliVersion,
|
|
272
|
+
updateCacheFile,
|
|
273
|
+
writeUpdateCache,
|
|
274
|
+
updateNotice,
|
|
275
|
+
resolvedEntryPath,
|
|
276
|
+
isWorkspaceCli,
|
|
277
|
+
exitCodeFor,
|
|
278
|
+
redactSecrets,
|
|
279
|
+
redactingOutput,
|
|
280
|
+
looksSecret
|
|
281
|
+
};
|
|
282
|
+
//# sourceMappingURL=chunk-5MEO3BVF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/update-notice.ts","../src/odla-home.ts","../src/runbook-requires.ts","../src/exit-code.ts","../src/redact.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\n\nexport function cliVersion(): string {\n const pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\")) as { version?: string };\n return pkg.version ?? \"unknown\";\n}\n","// \"Is this CLI the one npm serves?\" — asked on every command, paid for on none.\n//\n// The machinery to ask already existed and only `provision` used it, as a hard\n// block: every other command ran happily on a build from six releases ago and\n// said nothing, which is how an agent ends up reasoning about a flag that has\n// since been renamed. Wrangler solves this the same way and there is nothing\n// clever about npx in it — the PACKAGE checks itself at runtime, so however it\n// was launched, it knows.\n//\n// Two rules make it affordable. The answer is READ from a cache file and never\n// waited for, so no invocation pays latency for it. The refresh that fills that\n// cache runs in a DETACHED child that outlives this process, so nothing is\n// pending at exit either. A machine that never goes online simply never gets a\n// notice, which is the correct failure.\nimport { spawn } from \"node:child_process\";\nimport { mkdirSync, readFileSync, realpathSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\nimport { odlaHomePath } from \"./odla-home\";\nimport { compareVersions } from \"./runbook-requires\";\nimport { cliVersion } from \"./version\";\n\nconst REGISTRY_URL = \"https://registry.npmjs.org/@odla-ai%2fcli/latest\";\nconst INTERVAL_MS = 3 * 60 * 60 * 1_000;\nconst VERSION = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?$/;\n\nexport interface UpdateCache {\n /** The version npm served when the cache was last filled. */\n latest: string;\n /** When that was, epoch ms — the whole basis for \"ask again yet?\". */\n checkedAt: number;\n}\n\n/**\n * One refresh per process, however many commands it runs.\n *\n * Wrangler memoizes the same way, and the reason showed up the moment this\n * shipped: the CLI test suite drives `runCli` dozens of times in one process,\n * and without this each one spawned its own child. That is not only a test\n * artifact — anything that embeds `runCli` in a loop had the same behaviour,\n * and a machine with no cached answer would pay for it on every call.\n */\nlet refreshScheduled = false;\n\n/** Reset between tests, which are the only caller that runs many \"processes\"\n * inside one. */\nexport function resetRefreshSchedule(): void {\n refreshScheduled = false;\n}\n\nexport interface UpdateNoticeOptions {\n currentVersion?: string;\n entryPath?: string;\n env?: Record<string, string | undefined>;\n now?: number;\n /** Injected so a test can prove the refresh is scheduled without spawning. */\n refresh?: (cachePath: string, registryUrl: string) => void;\n}\n\n/** Where this machine remembers npm's answer. Under `~/.odla` rather than a\n * temp dir because that is already the per-machine store every worktree shares,\n * and a reboot should not make twenty agents re-ask at once. */\nexport function updateCacheFile(env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"cli-update.json\"], env);\n}\n\n/** npm's current release as of the last refresh, or null if never filled. */\nexport function readUpdateCache(path: string): UpdateCache | null {\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as Partial<UpdateCache>;\n if (typeof parsed.latest !== \"string\" || !VERSION.test(parsed.latest)) return null;\n if (typeof parsed.checkedAt !== \"number\") return null;\n return { latest: parsed.latest, checkedAt: parsed.checkedAt };\n } catch {\n return null;\n }\n}\n\nexport function writeUpdateCache(path: string, cache: UpdateCache): void {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(cache));\n}\n\n/**\n * One stderr line when this CLI is behind the one npm serves, or null.\n *\n * Never stdout: every `--json` command must stay parseable by whatever is\n * piping it, and a helpful notice that corrupts a payload is not helpful.\n * Returns null — silently — when the cache has no answer yet, when the answer\n * is current, or when `ODLA_CLI_UPDATE_CHECK=0`. Filling the cache is a side\n * effect of asking, scheduled and abandoned.\n */\nexport function updateNotice(options: UpdateNoticeOptions = {}): string | null {\n const env = options.env ?? process.env;\n if (env.ODLA_CLI_UPDATE_CHECK === \"0\") return null;\n const current = options.currentVersion ?? cliVersion();\n if (!VERSION.test(current)) return null;\n\n const path = updateCacheFile(env);\n const cache = readUpdateCache(path);\n const now = options.now ?? Date.now();\n if ((!cache || now - cache.checkedAt > INTERVAL_MS) && !refreshScheduled) {\n refreshScheduled = true;\n (options.refresh ?? spawnRefresh)(path, env.ODLA_CLI_REGISTRY_URL ?? REGISTRY_URL);\n }\n if (!cache || compareVersions(current, cache.latest) >= 0) return null;\n return renderNotice(current, cache.latest, options.entryPath ?? process.argv[1]);\n}\n\n/** How to get the newer one, in the terms of however this executable was\n * launched — the three cases are genuinely different repairs, and telling a\n * monorepo developer to `npm i` the package they are editing is noise. */\nexport function renderNotice(current: string, latest: string, entryPath: string | undefined): string {\n const resolved = resolvedEntryPath(entryPath);\n const behindMajor = Number(latest.split(\".\")[0]) > Number(current.split(\".\")[0]);\n const repair = isWorkspaceCli(resolved)\n ? `this is the workspace build at ${resolved} — rebase that worktree and rebuild it`\n : resolved.includes(\"/_npx/\")\n ? `run npx --yes @odla-ai/cli@${latest} <command>`\n : `run npm i @odla-ai/cli@${latest}`;\n const severity = behindMajor\n ? \"a MAJOR version behind, so commands and flags this version has may no longer exist\"\n : \"behind\";\n return `odla-ai: ${current} is ${severity}; npm serves ${latest}. To update, ${repair}.`;\n}\n\n/**\n * Fill the cache in a process that outlives this one.\n *\n * Detached and unref'd on purpose: an in-process fetch would hold the event\n * loop open until it settled, which turns a background courtesy into latency on\n * every command — the exact cost that kept this check confined to `provision`.\n * Every failure is swallowed by the child: npm being unreachable must never be\n * something an otherwise working CLI reports.\n */\nfunction spawnRefresh(cachePath: string, registryUrl: string): void {\n const script = `\n const {mkdirSync,writeFileSync}=require(\"node:fs\");\n const {dirname}=require(\"node:path\");\n const [path,url]=process.argv.slice(1);\n const done=setTimeout(()=>process.exit(0),5000); done.unref();\n fetch(url,{headers:{accept:\"application/vnd.npm.install-v1+json\"}})\n .then(r=>r.ok?r.json():null)\n .then(b=>{\n const v=b&&b.version;\n if (typeof v!==\"string\"||!/^\\\\d+\\\\.\\\\d+\\\\.\\\\d+/.test(v)) return;\n mkdirSync(dirname(path),{recursive:true});\n writeFileSync(path,JSON.stringify({latest:v,checkedAt:Date.now()}));\n })\n .catch(()=>{});\n `;\n try {\n spawn(process.execPath, [\"-e\", script, cachePath, registryUrl], {\n detached: true,\n stdio: \"ignore\",\n }).unref();\n } catch {\n // A machine that will not let us spawn simply never gets a notice.\n }\n}\n\nexport function resolvedEntryPath(entryPath: string | undefined): string {\n if (!entryPath) return \"unknown executable\";\n try {\n return realpathSync(entryPath);\n } catch {\n return entryPath;\n }\n}\n\n/**\n * Is this executable the monorepo's own build rather than an installed one?\n *\n * Matched on `bin/` as well as `dist/`, because `bin/odla-ai.js` is the only\n * entry a launcher ever names: it imports `dist/bin.js` dynamically, so\n * `process.argv[1]` is the shim and never the bundle. Checking only for the\n * bundle made this answer `false` for every workspace CLI ever launched, which\n * left the \"rebase the worktree and rebuild\" guidance unreachable — it told a\n * monorepo developer to `npm i` the package they were editing.\n */\nexport function isWorkspaceCli(entryPath: string): boolean {\n const normalized = entryPath.replaceAll(\"\\\\\", \"/\");\n return /\\/packages\\/cli\\/(dist|bin)\\//.test(normalized) &&\n !normalized.includes(\"/node_modules/\");\n}\n","import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport process from \"node:process\";\n\n/**\n * Where this machine keeps everything odla knows about it.\n *\n * There used to be two answers. The device credential lived here, per user,\n * because the trust decision is about the machine — but every cached token,\n * every scoped grant, and the selected PM project lived under the checkout that\n * happened to ask for them. A worktree is not a security boundary and never\n * was; it is a directory. What that split actually bought was a fresh browser\n * approval per worktree, per capability, which is how a five-minute change\n * became an interruption for whoever was nearby.\n *\n * So there is one answer now, and it is this one. `ODLA_HOME` relocates it\n * whole — for a test, a sandbox, or a machine with an unusual home — and the\n * per-file `ODLA_*_FILE` overrides still win where they are read.\n */\nexport function odlaHome(env: Record<string, string | undefined> = process.env): string {\n // A test that resolves the real home writes credentials and selections into\n // the developer's actual machine, and it is not hypothetical: a PM fixture\n // wrote `projectId: \"project-1\"` into a real `~/.odla/pm-context.json` and\n // broke that developer's `pm` commands with an error about a product\n // mismatch, hours later, in a session that had nothing to do with the test.\n //\n // Refusing outright beats relying on a setup file: a setup file has to be\n // loaded, and the leak above happened in the window before one was.\n if (env.VITEST && !env.ODLA_HOME) {\n throw new Error(\n \"ODLA_HOME must be set under test — resolving the real ~/.odla would write to the developer's machine\",\n );\n }\n return env.ODLA_HOME ?? join(env.HOME ?? homedir(), \".odla\");\n}\n\nexport function odlaHomePath(\n segments: string[],\n env: Record<string, string | undefined> = process.env,\n): string {\n return join(odlaHome(env), ...segments);\n}\n\n/** The odla account this machine authenticates as — the analogue of\n * `~/.gitconfig`'s `user.email`, and for the same reason: asking every command\n * for an identity the machine already proved is how agents end up guessing one. */\nexport function identityFile(env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"identity.json\"], env);\n}\n\n/** The short-lived session this machine last minted from its device credential.\n * Cached so a run of twenty commands mints once rather than twenty times. */\nexport function deviceSessionFile(env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"session.json\"], env);\n}\n\n/** One app's cached developer credential. Keyed by app rather than by checkout\n * so every worktree of the same app shares it — which is the point. */\nexport function appTokenFile(appId: string, env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"apps\", safeSegment(appId), \"dev-token.json\"], env);\n}\n\n/** One app's local service credentials, formerly `.odla/credentials.local.json`. */\nexport function appCredentialsFile(appId: string, env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"apps\", safeSegment(appId), \"credentials.json\"], env);\n}\n\n/**\n * Scoped capability grants — one file for the machine.\n *\n * Not per app and not per checkout: a scope is a platform capability whose\n * consuming route re-checks ownership anyway, so a per-directory copy only ever\n * meant approving the same capability again in the next directory. The file\n * records which platform its contents belong to and is ignored wholesale\n * against any other, which is what audience-binding needs and all it needs.\n *\n * This is deliberately the same path a named operator context has always used\n * for its global case, so unifying the store did not invent a second one.\n */\nexport function scopedTokenFile(env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"admin-token.local.json\"], env);\n}\n\n/** The PM project selected for an app, formerly `.odla/pm-project.local.json`. */\nexport function pmContextFile(env?: Record<string, string | undefined>): string {\n return odlaHomePath([\"pm-context.json\"], env);\n}\n\n/**\n * Take a repository-local cache into the machine-wide store, once.\n *\n * Reported rather than silent, because a credential moving on disk is exactly\n * the kind of thing someone greps for at 2am. Deleting the old copy is the\n * point and not a side effect: two files holding the same credential means the\n * one you revoke is not always the one your next command reads.\n *\n * Returns true when it moved or removed something, so a caller can say so once\n * per process rather than once per lookup.\n */\nexport function adoptRepoLocalCache(\n legacyPath: string,\n machinePath: string,\n out?: Pick<typeof console, \"error\">,\n): boolean {\n if (!existsSync(legacyPath) || legacyPath === machinePath) return false;\n const superseded = existsSync(machinePath);\n if (!superseded) {\n mkdirSync(dirname(machinePath), { recursive: true });\n copyFileSync(legacyPath, machinePath);\n chmodSync(machinePath, 0o600);\n }\n rmSync(legacyPath, { force: true });\n out?.error(\n superseded\n ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}`\n : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`,\n );\n return true;\n}\n\n/** An app id is already `[a-z0-9-]`, but this path is built from config a\n * caller controls, and a path segment assembled from unvalidated input is how\n * `../` gets into one. */\nfunction safeSegment(value: string): string {\n const clean = value.toLowerCase().replace(/[^a-z0-9-]/g, \"-\").replace(/^-+|-+$/g, \"\");\n if (!clean) throw new Error(`\"${value}\" is not a usable app id`);\n return clean;\n}\n","// A runbook can be corrected the moment a step is wrong, which means its text\n// routinely runs ahead of the packages a reader has installed: the step is right,\n// and the command it names simply does not exist in their copy yet. `requires`\n// is how a runbook says which version it assumes, so the reader learns that from\n// the runbook instead of from a confusing \"unknown action\" error.\n//\n// Specs are `name@version`, space- or comma-separated, and read as MINIMUMS.\n// Deliberately not semver ranges: the only question worth answering here is\n// \"is what I have new enough\", and a range grammar invites `<` bounds that a\n// runbook has no way to be right about.\n\n/** One package version a runbook assumes. */\nexport interface Requirement {\n name: string;\n /** The lowest version whose steps are known to work. */\n min: string;\n}\n\n/** A requirement the caller does not meet. */\nexport interface UnmetRequirement extends Requirement {\n /** What is installed, or null when nothing local claims to be this package. */\n installed: string | null;\n}\n\nconst SPEC = /^(@?[\\w./-]+?)@(\\d+\\.\\d+\\.\\d+(?:[\\w.-]*)?)$/;\n\n/** Parse a `requires` string. Unparseable entries are dropped rather than\n * throwing: a malformed spec must not make a runbook unreadable. */\nexport function parseRequires(value: string | undefined | null): Requirement[] {\n if (!value) return [];\n const out: Requirement[] = [];\n for (const token of value.split(/[\\s,]+/).filter(Boolean)) {\n const match = SPEC.exec(token);\n if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });\n }\n return out;\n}\n\n/** Compare two dotted versions numerically. Pre-release suffixes sort BELOW the\n * release they precede, so `0.18.0-rc.1` does not satisfy `0.18.0`. */\nexport function compareVersions(a: string, b: string): number {\n const parts = (v: string) => {\n const [core = \"\", pre = \"\"] = v.split(\"-\", 2);\n return { nums: core.split(\".\").map((n) => Number.parseInt(n, 10) || 0), pre };\n };\n const left = parts(a);\n const right = parts(b);\n for (let i = 0; i < Math.max(left.nums.length, right.nums.length); i++) {\n const diff = (left.nums[i] ?? 0) - (right.nums[i] ?? 0);\n if (diff) return diff < 0 ? -1 : 1;\n }\n if (left.pre === right.pre) return 0;\n if (!left.pre) return 1;\n if (!right.pre) return -1;\n return left.pre < right.pre ? -1 : 1;\n}\n\n/** True when `installed` is at least `min`. */\nexport function satisfies(installed: string, min: string): boolean {\n return compareVersions(installed, min) >= 0;\n}\n\n/**\n * Which of a runbook's requirements the caller does not meet.\n *\n * `installedVersions` maps a package name to what the caller has. A package the\n * caller knows nothing about is reported with `installed: null` rather than\n * assumed satisfied — silence about an unknown dependency is the failure mode\n * this exists to prevent.\n */\nexport function unmetRequirements(\n requires: string | undefined | null,\n installedVersions: Readonly<Record<string, string | undefined>>,\n): UnmetRequirement[] {\n const unmet: UnmetRequirement[] = [];\n for (const requirement of parseRequires(requires)) {\n const installed = installedVersions[requirement.name];\n if (installed === undefined) {\n unmet.push({ ...requirement, installed: null });\n } else if (!satisfies(installed, requirement.min)) {\n unmet.push({ ...requirement, installed });\n }\n }\n return unmet;\n}\n\n/** The one-line warning shown when a runbook expects more than the caller has. */\nexport function describeUnmet(slug: string, unmet: readonly UnmetRequirement[]): string {\n const detail = unmet\n .map((u) => `${u.name}@${u.min}${u.installed ? ` (you have ${u.installed})` : \" (not installed here)\"}`)\n .join(\", \");\n return `note: runbook \"${slug}\" expects ${detail} — some steps may name commands your version does not have.`;\n}\n","/** Map a CLI failure to its documented process exit code without importing the\n * command graph. The binary needs this tiny module before it verifies that its\n * external runtime dependencies are coherent. */\nexport function exitCodeFor(err: unknown): number {\n const code = (err as { code?: unknown } | null)?.code;\n if (code === \"handshake_pending\" || code === \"watch_timeout\" || code === \"operation_pending\") return 75;\n if (code === \"checkpoint_required\") return 3;\n if (code === \"remote_unavailable\") return 6;\n if (code === \"auth_failed\") return 5;\n if (code === \"invalid_cursor\" || code === \"invalid_plan\" || code === \"invalid_operation_id\") return 2;\n return 1;\n}\n","/**\n * Secret redaction shared by every command's output path and by doctor's\n * \"does this value look like a secret\" checks. Clerk publishable keys\n * (pk_test_/pk_live_) are public by design and are deliberately not matched.\n */\nconst REPLACEMENTS: Array<[RegExp, string]> = [\n [/odla_(sk|dev)_[A-Za-z0-9._-]+/g, \"odla_$1_[redacted]\"],\n [/\\bsk_(live|test)_[A-Za-z0-9]+/g, \"sk_$1_[redacted]\"],\n [/\\bsk-[A-Za-z0-9._-]+/g, \"sk-[redacted]\"],\n [/\\bwhsec_[A-Za-z0-9+/=]+/g, \"whsec_[redacted]\"],\n [/\\bo11y_[A-Za-z0-9]+/g, \"o11y_[redacted]\"],\n [/\\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, \"$1_[redacted]\"],\n [/\\bAKIA[A-Z0-9]{12,}/g, \"AKIA[redacted]\"],\n];\n\n/**\n * Return `value` with every secret-shaped substring replaced by a `[redacted]`\n * placeholder that keeps the identifying prefix (e.g. `sk_live_[redacted]`).\n * Matches odla db keys (`odla_sk_`/`odla_dev_`), Clerk secret keys\n * (`sk_live_`/`sk_test_`), OpenAI keys (`sk-`), webhook signing secrets\n * (`whsec_`), o11y tokens (`o11y_`), GitHub tokens (`ghp_`/`gho_`/`github_pat_`),\n * and AWS access-key ids (`AKIA…`). Clerk publishable keys (`pk_test_`/\n * `pk_live_`) are public by design and intentionally left untouched. Used to\n * sanitize anything the CLI echoes (notably network error bodies) before it\n * reaches stdout/stderr. Non-matching input is returned unchanged.\n */\nexport function redactSecrets(value: string): string {\n let result = value;\n for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);\n return result;\n}\n\n/**\n * Wrap the CLI's line-oriented output boundary so command implementations\n * cannot accidentally bypass redaction when they print a backend message.\n * Objects and arrays retain their shape for console/test sinks while every\n * nested string is sanitized.\n */\nexport function redactingOutput(\n output: Pick<typeof console, \"log\" | \"error\">,\n): Pick<typeof console, \"log\" | \"error\"> {\n const redactArgs = (args: unknown[]) => args.map((value) => redactOutputValue(value, new WeakMap<object, unknown>()));\n return {\n log: (...args: unknown[]) => output.log(...redactArgs(args)),\n error: (...args: unknown[]) => output.error(...redactArgs(args)),\n };\n}\n\nfunction redactOutputValue(value: unknown, seen: WeakMap<object, unknown>): unknown {\n if (typeof value === \"string\") return redactSecrets(value);\n if (value instanceof Error) {\n const redacted = new Error(redactSecrets(value.message));\n redacted.name = value.name;\n if (value.stack) redacted.stack = redactSecrets(value.stack);\n return redacted;\n }\n if (!value || typeof value !== \"object\") return value;\n const prior = seen.get(value);\n if (prior) return prior;\n if (Array.isArray(value)) {\n const next: unknown[] = [];\n seen.set(value, next);\n for (const item of value) next.push(redactOutputValue(item, seen));\n return next;\n }\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null) return value;\n const next: Record<string, unknown> = {};\n seen.set(value, next);\n for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);\n return next;\n}\n\n/** True when a value matches any known secret shape (used for detection, never display). */\nexport function looksSecret(value: string): boolean {\n return redactSecrets(value) !== value || value.includes(\"-----BEGIN\");\n}\n"],"mappings":"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","names":["mkdirSync","readFileSync","dirname","process","readFileSync","mkdirSync","dirname","process","next"]}
|