@odla-ai/cli 0.27.14 → 0.27.17
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 +15 -1
- package/dist/bin.cjs +2832 -1515
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +106 -12
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-3SQ2XUBB.js → chunk-3WVVHH3Y.js} +9148 -9172
- package/dist/chunk-3WVVHH3Y.js.map +1 -0
- package/dist/chunk-UKLSRQ5J.js +129 -0
- package/dist/chunk-UKLSRQ5J.js.map +1 -0
- package/dist/cli-DCSVQAZ6.js +12 -0
- package/dist/cli-DCSVQAZ6.js.map +1 -0
- package/dist/index.cjs +99 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -6
- package/dist/index.d.ts +18 -6
- package/dist/index.js +7 -3
- package/dist/runtime/pi-agent.js +15 -314577
- package/package.json +6 -6
- package/dist/chunk-3SQ2XUBB.js.map +0 -1
|
@@ -0,0 +1,129 @@
|
|
|
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/exit-code.ts
|
|
11
|
+
function exitCodeFor(err) {
|
|
12
|
+
const code = err?.code;
|
|
13
|
+
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
14
|
+
if (code === "checkpoint_required") return 3;
|
|
15
|
+
if (code === "remote_unavailable") return 6;
|
|
16
|
+
if (code === "auth_failed") return 5;
|
|
17
|
+
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/redact.ts
|
|
22
|
+
var REPLACEMENTS = [
|
|
23
|
+
[/odla_(sk|dev)_[A-Za-z0-9._-]+/g, "odla_$1_[redacted]"],
|
|
24
|
+
[/\bsk_(live|test)_[A-Za-z0-9]+/g, "sk_$1_[redacted]"],
|
|
25
|
+
[/\bsk-[A-Za-z0-9._-]+/g, "sk-[redacted]"],
|
|
26
|
+
[/\bwhsec_[A-Za-z0-9+/=]+/g, "whsec_[redacted]"],
|
|
27
|
+
[/\bo11y_[A-Za-z0-9]+/g, "o11y_[redacted]"],
|
|
28
|
+
[/\b(ghp|gho|github_pat)_[A-Za-z0-9_]+/g, "$1_[redacted]"],
|
|
29
|
+
[/\bAKIA[A-Z0-9]{12,}/g, "AKIA[redacted]"]
|
|
30
|
+
];
|
|
31
|
+
function redactSecrets(value) {
|
|
32
|
+
let result = value;
|
|
33
|
+
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
function redactingOutput(output) {
|
|
37
|
+
const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
|
|
38
|
+
return {
|
|
39
|
+
log: (...args) => output.log(...redactArgs(args)),
|
|
40
|
+
error: (...args) => output.error(...redactArgs(args))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function redactOutputValue(value, seen) {
|
|
44
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
45
|
+
if (value instanceof Error) {
|
|
46
|
+
const redacted = new Error(redactSecrets(value.message));
|
|
47
|
+
redacted.name = value.name;
|
|
48
|
+
if (value.stack) redacted.stack = redactSecrets(value.stack);
|
|
49
|
+
return redacted;
|
|
50
|
+
}
|
|
51
|
+
if (!value || typeof value !== "object") return value;
|
|
52
|
+
const prior = seen.get(value);
|
|
53
|
+
if (prior) return prior;
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
const next2 = [];
|
|
56
|
+
seen.set(value, next2);
|
|
57
|
+
for (const item of value) next2.push(redactOutputValue(item, seen));
|
|
58
|
+
return next2;
|
|
59
|
+
}
|
|
60
|
+
const prototype = Object.getPrototypeOf(value);
|
|
61
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
62
|
+
const next = {};
|
|
63
|
+
seen.set(value, next);
|
|
64
|
+
for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
|
|
65
|
+
return next;
|
|
66
|
+
}
|
|
67
|
+
function looksSecret(value) {
|
|
68
|
+
return redactSecrets(value) !== value || value.includes("-----BEGIN");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/runbook-requires.ts
|
|
72
|
+
var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
|
|
73
|
+
function parseRequires(value) {
|
|
74
|
+
if (!value) return [];
|
|
75
|
+
const out = [];
|
|
76
|
+
for (const token of value.split(/[\s,]+/).filter(Boolean)) {
|
|
77
|
+
const match = SPEC.exec(token);
|
|
78
|
+
if (match?.[1] && match[2]) out.push({ name: match[1], min: match[2] });
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function compareVersions(a, b) {
|
|
83
|
+
const parts = (v) => {
|
|
84
|
+
const [core = "", pre = ""] = v.split("-", 2);
|
|
85
|
+
return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
|
|
86
|
+
};
|
|
87
|
+
const left = parts(a);
|
|
88
|
+
const right = parts(b);
|
|
89
|
+
for (let i = 0; i < Math.max(left.nums.length, right.nums.length); i++) {
|
|
90
|
+
const diff = (left.nums[i] ?? 0) - (right.nums[i] ?? 0);
|
|
91
|
+
if (diff) return diff < 0 ? -1 : 1;
|
|
92
|
+
}
|
|
93
|
+
if (left.pre === right.pre) return 0;
|
|
94
|
+
if (!left.pre) return 1;
|
|
95
|
+
if (!right.pre) return -1;
|
|
96
|
+
return left.pre < right.pre ? -1 : 1;
|
|
97
|
+
}
|
|
98
|
+
function satisfies(installed, min) {
|
|
99
|
+
return compareVersions(installed, min) >= 0;
|
|
100
|
+
}
|
|
101
|
+
function unmetRequirements(requires, installedVersions) {
|
|
102
|
+
const unmet = [];
|
|
103
|
+
for (const requirement of parseRequires(requires)) {
|
|
104
|
+
const installed = installedVersions[requirement.name];
|
|
105
|
+
if (installed === void 0) {
|
|
106
|
+
unmet.push({ ...requirement, installed: null });
|
|
107
|
+
} else if (!satisfies(installed, requirement.min)) {
|
|
108
|
+
unmet.push({ ...requirement, installed });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return unmet;
|
|
112
|
+
}
|
|
113
|
+
function describeUnmet(slug, unmet) {
|
|
114
|
+
const detail = unmet.map((u) => `${u.name}@${u.min}${u.installed ? ` (you have ${u.installed})` : " (not installed here)"}`).join(", ");
|
|
115
|
+
return `note: runbook "${slug}" expects ${detail} \u2014 some steps may name commands your version does not have.`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
parseRequires,
|
|
120
|
+
compareVersions,
|
|
121
|
+
unmetRequirements,
|
|
122
|
+
describeUnmet,
|
|
123
|
+
cliVersion,
|
|
124
|
+
exitCodeFor,
|
|
125
|
+
redactSecrets,
|
|
126
|
+
redactingOutput,
|
|
127
|
+
looksSecret
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=chunk-UKLSRQ5J.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/exit-code.ts","../src/redact.ts","../src/runbook-requires.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","/** 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","// 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"],"mappings":";;;AAAA,SAAS,oBAAoB;AAEtB,SAAS,aAAqB;AACnC,QAAM,MAAM,KAAK,MAAM,aAAa,IAAI,IAAI,mBAAmB,YAAY,GAAG,GAAG,MAAM,CAAC;AACxF,SAAO,IAAI,WAAW;AACxB;;;ACFO,SAAS,YAAY,KAAsB;AAChD,QAAM,OAAQ,KAAmC;AACjD,MAAI,SAAS,uBAAuB,SAAS,mBAAmB,SAAS,oBAAqB,QAAO;AACrG,MAAI,SAAS,sBAAuB,QAAO;AAC3C,MAAI,SAAS,qBAAsB,QAAO;AAC1C,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,oBAAoB,SAAS,kBAAkB,SAAS,uBAAwB,QAAO;AACpG,SAAO;AACT;;;ACNA,IAAM,eAAwC;AAAA,EAC5C,CAAC,kCAAkC,oBAAoB;AAAA,EACvD,CAAC,kCAAkC,kBAAkB;AAAA,EACrD,CAAC,yBAAyB,eAAe;AAAA,EACzC,CAAC,4BAA4B,kBAAkB;AAAA,EAC/C,CAAC,wBAAwB,iBAAiB;AAAA,EAC1C,CAAC,yCAAyC,eAAe;AAAA,EACzD,CAAC,wBAAwB,gBAAgB;AAC3C;AAaO,SAAS,cAAc,OAAuB;AACnD,MAAI,SAAS;AACb,aAAW,CAAC,SAAS,WAAW,KAAK,aAAc,UAAS,OAAO,QAAQ,SAAS,WAAW;AAC/F,SAAO;AACT;AAQO,SAAS,gBACd,QACuC;AACvC,QAAM,aAAa,CAAC,SAAoB,KAAK,IAAI,CAAC,UAAU,kBAAkB,OAAO,oBAAI,QAAyB,CAAC,CAAC;AACpH,SAAO;AAAA,IACL,KAAK,IAAI,SAAoB,OAAO,IAAI,GAAG,WAAW,IAAI,CAAC;AAAA,IAC3D,OAAO,IAAI,SAAoB,OAAO,MAAM,GAAG,WAAW,IAAI,CAAC;AAAA,EACjE;AACF;AAEA,SAAS,kBAAkB,OAAgB,MAAyC;AAClF,MAAI,OAAO,UAAU,SAAU,QAAO,cAAc,KAAK;AACzD,MAAI,iBAAiB,OAAO;AAC1B,UAAM,WAAW,IAAI,MAAM,cAAc,MAAM,OAAO,CAAC;AACvD,aAAS,OAAO,MAAM;AACtB,QAAI,MAAM,MAAO,UAAS,QAAQ,cAAc,MAAM,KAAK;AAC3D,WAAO;AAAA,EACT;AACA,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,QAAQ,KAAK,IAAI,KAAK;AAC5B,MAAI,MAAO,QAAO;AAClB,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAMA,QAAkB,CAAC;AACzB,SAAK,IAAI,OAAOA,KAAI;AACpB,eAAW,QAAQ,MAAO,CAAAA,MAAK,KAAK,kBAAkB,MAAM,IAAI,CAAC;AACjE,WAAOA;AAAA,EACT;AACA,QAAM,YAAY,OAAO,eAAe,KAAK;AAC7C,MAAI,cAAc,OAAO,aAAa,cAAc,KAAM,QAAO;AACjE,QAAM,OAAgC,CAAC;AACvC,OAAK,IAAI,OAAO,IAAI;AACpB,aAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,EAAG,MAAK,GAAG,IAAI,kBAAkB,MAAM,IAAI;AACzF,SAAO;AACT;AAGO,SAAS,YAAY,OAAwB;AAClD,SAAO,cAAc,KAAK,MAAM,SAAS,MAAM,SAAS,YAAY;AACtE;;;ACpDA,IAAM,OAAO;AAIN,SAAS,cAAc,OAAiD;AAC7E,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,MAAqB,CAAC;AAC5B,aAAW,SAAS,MAAM,MAAM,QAAQ,EAAE,OAAO,OAAO,GAAG;AACzD,UAAM,QAAQ,KAAK,KAAK,KAAK;AAC7B,QAAI,QAAQ,CAAC,KAAK,MAAM,CAAC,EAAG,KAAI,KAAK,EAAE,MAAM,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,EAAE,CAAC;AAAA,EACxE;AACA,SAAO;AACT;AAIO,SAAS,gBAAgB,GAAW,GAAmB;AAC5D,QAAM,QAAQ,CAAC,MAAc;AAC3B,UAAM,CAAC,OAAO,IAAI,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,WAAO,EAAE,MAAM,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI;AAAA,EAC9E;AACA,QAAM,OAAO,MAAM,CAAC;AACpB,QAAM,QAAQ,MAAM,CAAC;AACrB,WAAS,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,KAAK,QAAQ,MAAM,KAAK,MAAM,GAAG,KAAK;AACtE,UAAM,QAAQ,KAAK,KAAK,CAAC,KAAK,MAAM,MAAM,KAAK,CAAC,KAAK;AACrD,QAAI,KAAM,QAAO,OAAO,IAAI,KAAK;AAAA,EACnC;AACA,MAAI,KAAK,QAAQ,MAAM,IAAK,QAAO;AACnC,MAAI,CAAC,KAAK,IAAK,QAAO;AACtB,MAAI,CAAC,MAAM,IAAK,QAAO;AACvB,SAAO,KAAK,MAAM,MAAM,MAAM,KAAK;AACrC;AAGO,SAAS,UAAU,WAAmB,KAAsB;AACjE,SAAO,gBAAgB,WAAW,GAAG,KAAK;AAC5C;AAUO,SAAS,kBACd,UACA,mBACoB;AACpB,QAAM,QAA4B,CAAC;AACnC,aAAW,eAAe,cAAc,QAAQ,GAAG;AACjD,UAAM,YAAY,kBAAkB,YAAY,IAAI;AACpD,QAAI,cAAc,QAAW;AAC3B,YAAM,KAAK,EAAE,GAAG,aAAa,WAAW,KAAK,CAAC;AAAA,IAChD,WAAW,CAAC,UAAU,WAAW,YAAY,GAAG,GAAG;AACjD,YAAM,KAAK,EAAE,GAAG,aAAa,UAAU,CAAC;AAAA,IAC1C;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,cAAc,MAAc,OAA4C;AACtF,QAAM,SAAS,MACZ,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,GAAG,GAAG,EAAE,YAAY,cAAc,EAAE,SAAS,MAAM,uBAAuB,EAAE,EACtG,KAAK,IAAI;AACZ,SAAO,kBAAkB,IAAI,aAAa,MAAM;AAClD;","names":["next"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ __export(index_exports, {
|
|
|
41
41
|
SYSTEM_AI_PURPOSES: () => SYSTEM_AI_PURPOSES,
|
|
42
42
|
acceptedAfter: () => acceptedAfter,
|
|
43
43
|
adminAi: () => adminAi,
|
|
44
|
+
aiModels: () => aiModels,
|
|
44
45
|
calendarBookingPageUrl: () => calendarBookingPageUrl,
|
|
45
46
|
calendarCalendars: () => calendarCalendars,
|
|
46
47
|
calendarConnect: () => calendarConnect,
|
|
@@ -2704,6 +2705,63 @@ function printGroup(out, heading, items) {
|
|
|
2704
2705
|
out.log("");
|
|
2705
2706
|
}
|
|
2706
2707
|
|
|
2708
|
+
// src/ai-models.ts
|
|
2709
|
+
var import_ai = require("@odla-ai/ai");
|
|
2710
|
+
async function aiModels(options = {}) {
|
|
2711
|
+
const cfg = await loadProjectConfig(options.configPath ?? "odla.config.mjs");
|
|
2712
|
+
const env = options.env ?? cfg.envs[0] ?? "dev";
|
|
2713
|
+
if (!cfg.envs.includes(env)) throw new Error(`ai models env "${env}" is not declared in config envs`);
|
|
2714
|
+
const url = new URL(`/registry/apps/${encodeURIComponent(cfg.app.id)}/public-config`, cfg.platformUrl);
|
|
2715
|
+
url.searchParams.set("env", env);
|
|
2716
|
+
const response2 = await (options.fetch ?? fetch)(url);
|
|
2717
|
+
if (!response2.ok) throw new Error(`read app AI models failed (${response2.status}): ${await safeText4(response2)}`);
|
|
2718
|
+
const body = await response2.json();
|
|
2719
|
+
if (!body.ai) throw new Error(`ai is not configured for ${cfg.app.id}/${env}`);
|
|
2720
|
+
const mode = body.ai.mode === "hosted" ? "hosted" : "byok";
|
|
2721
|
+
const defaultModel = typeof body.ai.model === "string" ? body.ai.model : void 0;
|
|
2722
|
+
let models;
|
|
2723
|
+
if (mode === "hosted") {
|
|
2724
|
+
if (body.ai.enabled !== true) throw new Error(`hosted ai is disabled for ${cfg.app.id}/${env}`);
|
|
2725
|
+
if (!Array.isArray(body.ai.models) || !body.ai.models.every(isModelSpec)) {
|
|
2726
|
+
throw new Error("platform returned an invalid hosted AI model catalog");
|
|
2727
|
+
}
|
|
2728
|
+
models = body.ai.models;
|
|
2729
|
+
} else {
|
|
2730
|
+
const provider = typeof body.ai.provider === "string" ? body.ai.provider : cfg.ai?.provider;
|
|
2731
|
+
if (!provider) throw new Error(`BYOK ai has no provider for ${cfg.app.id}/${env}`);
|
|
2732
|
+
models = Object.values(import_ai.DEFAULT_CATALOG).filter((model) => model.provider === provider);
|
|
2733
|
+
}
|
|
2734
|
+
if (options.provider) models = models.filter((model) => model.provider === options.provider);
|
|
2735
|
+
models.sort((a, b) => a.provider.localeCompare(b.provider) || a.id.localeCompare(b.id));
|
|
2736
|
+
const out = options.stdout ?? console;
|
|
2737
|
+
if (options.json) {
|
|
2738
|
+
out.log(JSON.stringify({ appId: cfg.app.id, env, mode, defaultModel: defaultModel ?? null, models }, null, 2));
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
out.log("provider model default capabilities");
|
|
2742
|
+
for (const model of models) {
|
|
2743
|
+
out.log([model.provider, model.id, model.id === defaultModel ? "yes" : "", capabilityList(model)].join(" "));
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
function capabilityList(model) {
|
|
2747
|
+
return [
|
|
2748
|
+
model.capabilities.imageIn ? "image" : "",
|
|
2749
|
+
model.capabilities.audioIn ? "audio" : "",
|
|
2750
|
+
model.capabilities.documentIn ? "document" : "",
|
|
2751
|
+
model.capabilities.toolUse ? "tools" : "",
|
|
2752
|
+
model.capabilities.thinking || model.capabilities.effort ? "reasoning" : "",
|
|
2753
|
+
model.capabilities.webSearch || model.superpowers?.webSearch ? "web-search" : ""
|
|
2754
|
+
].filter(Boolean).join(",");
|
|
2755
|
+
}
|
|
2756
|
+
function isModelSpec(value2) {
|
|
2757
|
+
if (!value2 || typeof value2 !== "object" || Array.isArray(value2)) return false;
|
|
2758
|
+
const model = value2;
|
|
2759
|
+
return typeof model.id === "string" && typeof model.nativeId === "string" && (model.provider === "anthropic" || model.provider === "openai" || model.provider === "google") && Boolean(model.capabilities) && typeof model.capabilities === "object";
|
|
2760
|
+
}
|
|
2761
|
+
async function safeText4(response2) {
|
|
2762
|
+
return (await response2.text().catch(() => "request failed")).slice(0, 300);
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2707
2765
|
// src/config-operation-command.ts
|
|
2708
2766
|
var import_apps6 = require("@odla-ai/apps");
|
|
2709
2767
|
var import_node_path8 = require("path");
|
|
@@ -2884,10 +2942,10 @@ function record2(value2) {
|
|
|
2884
2942
|
var import_apps5 = require("@odla-ai/apps");
|
|
2885
2943
|
|
|
2886
2944
|
// src/provision-helpers.ts
|
|
2887
|
-
var
|
|
2945
|
+
var import_ai2 = require("@odla-ai/ai");
|
|
2888
2946
|
var import_apps4 = require("@odla-ai/apps");
|
|
2889
2947
|
function defaultSecretName(provider) {
|
|
2890
|
-
const names =
|
|
2948
|
+
const names = import_ai2.DEFAULT_SECRET_NAMES;
|
|
2891
2949
|
return names[provider] ?? `${provider}_api_key`;
|
|
2892
2950
|
}
|
|
2893
2951
|
async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
@@ -2897,7 +2955,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2897
2955
|
});
|
|
2898
2956
|
if (res.ok || res.status === 404) return;
|
|
2899
2957
|
if (res.status === 403) {
|
|
2900
|
-
const detail = await
|
|
2958
|
+
const detail = await safeText5(res);
|
|
2901
2959
|
const code = errorCode(detail);
|
|
2902
2960
|
if (code === "human_session_required") {
|
|
2903
2961
|
throw new Error(
|
|
@@ -2913,7 +2971,7 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
2913
2971
|
`${env}: this credential lacks live app.manage authority for "${cfg.app.id}" (tenant ${tenantId}) \u2014 nothing was minted or written; run "odla-ai provision --request-grant --email <odla-account>" to open a fresh owner review. If the human account is not an owner, an existing owner must add it in signed-in Studio; an agent token cannot repair ownership`
|
|
2914
2972
|
);
|
|
2915
2973
|
}
|
|
2916
|
-
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await
|
|
2974
|
+
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
2917
2975
|
}
|
|
2918
2976
|
function errorCode(text2) {
|
|
2919
2977
|
try {
|
|
@@ -2929,7 +2987,7 @@ async function postJson(doFetch, url, bearer, body) {
|
|
|
2929
2987
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
2930
2988
|
body: JSON.stringify(body)
|
|
2931
2989
|
});
|
|
2932
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await
|
|
2990
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} failed: ${res.status} ${await safeText5(res)}`);
|
|
2933
2991
|
}
|
|
2934
2992
|
function normalizeClerkConfig(value2) {
|
|
2935
2993
|
if (!value2) return null;
|
|
@@ -2942,7 +3000,7 @@ function normalizeClerkConfig(value2) {
|
|
|
2942
3000
|
const publishableKey = envValue(cfg.publishableKey);
|
|
2943
3001
|
return publishableKey ? { publishableKey, ...cfg.audience ? { audience: cfg.audience } : {}, ...cfg.mode ? { mode: cfg.mode } : {} } : null;
|
|
2944
3002
|
}
|
|
2945
|
-
async function
|
|
3003
|
+
async function safeText5(res) {
|
|
2946
3004
|
try {
|
|
2947
3005
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
2948
3006
|
} catch {
|
|
@@ -4263,7 +4321,7 @@ function assertWranglerConfig(cfg) {
|
|
|
4263
4321
|
}
|
|
4264
4322
|
|
|
4265
4323
|
// src/secrets-set.ts
|
|
4266
|
-
var
|
|
4324
|
+
var import_ai3 = require("@odla-ai/ai");
|
|
4267
4325
|
var import_apps10 = require("@odla-ai/apps");
|
|
4268
4326
|
var PROD_ENV_NAMES2 = /* @__PURE__ */ new Set(["prod", "production"]);
|
|
4269
4327
|
async function secretsSet(options) {
|
|
@@ -4277,7 +4335,7 @@ async function secretsSet(options) {
|
|
|
4277
4335
|
optionalProjectCapabilities: ["app.manage"]
|
|
4278
4336
|
});
|
|
4279
4337
|
try {
|
|
4280
|
-
await (0,
|
|
4338
|
+
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, name, value2);
|
|
4281
4339
|
} catch (err) {
|
|
4282
4340
|
throw new Error(scrubValue(err instanceof Error ? err.message : String(err), value2));
|
|
4283
4341
|
}
|
|
@@ -4712,7 +4770,7 @@ async function getJson(doFetch, url, bearer) {
|
|
|
4712
4770
|
const res = await doFetch(url, {
|
|
4713
4771
|
headers: bearer ? { authorization: `Bearer ${bearer}` } : void 0
|
|
4714
4772
|
});
|
|
4715
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4773
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4716
4774
|
return res.json();
|
|
4717
4775
|
}
|
|
4718
4776
|
async function postJson2(doFetch, url, bearer, body) {
|
|
@@ -4721,7 +4779,7 @@ async function postJson2(doFetch, url, bearer, body) {
|
|
|
4721
4779
|
headers: { authorization: `Bearer ${bearer}`, "content-type": "application/json" },
|
|
4722
4780
|
body: JSON.stringify(body)
|
|
4723
4781
|
});
|
|
4724
|
-
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await
|
|
4782
|
+
if (!res.ok) throw new Error(`${new URL(url).pathname} returned ${res.status}: ${await safeText6(res)}`);
|
|
4725
4783
|
return res.json();
|
|
4726
4784
|
}
|
|
4727
4785
|
function publicConfigUrl(platformUrl, appId, env) {
|
|
@@ -4729,7 +4787,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
4729
4787
|
url.searchParams.set("env", env);
|
|
4730
4788
|
return url.toString();
|
|
4731
4789
|
}
|
|
4732
|
-
async function
|
|
4790
|
+
async function safeText6(res) {
|
|
4733
4791
|
try {
|
|
4734
4792
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
4735
4793
|
} catch {
|
|
@@ -4785,6 +4843,20 @@ async function secretsCommand(parsed, deps) {
|
|
|
4785
4843
|
});
|
|
4786
4844
|
}
|
|
4787
4845
|
async function projectCommand(command, parsed, deps) {
|
|
4846
|
+
if (command === "ai") {
|
|
4847
|
+
const sub = parsed.positionals[1];
|
|
4848
|
+
if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
|
|
4849
|
+
assertArgs(parsed, ["config", "env", "provider", "json"], 2);
|
|
4850
|
+
await aiModels({
|
|
4851
|
+
configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
|
|
4852
|
+
env: stringOpt(parsed.options.env),
|
|
4853
|
+
provider: stringOpt(parsed.options.provider),
|
|
4854
|
+
json: parsed.options.json === true,
|
|
4855
|
+
fetch: deps.fetch,
|
|
4856
|
+
stdout: deps.stdout
|
|
4857
|
+
});
|
|
4858
|
+
return true;
|
|
4859
|
+
}
|
|
4788
4860
|
if (command === "config") {
|
|
4789
4861
|
const sub = parsed.positionals[1];
|
|
4790
4862
|
if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
|
|
@@ -8561,6 +8633,7 @@ Usage:
|
|
|
8561
8633
|
odla-ai setup [--dir <project>] [--agent <name>] [--global] [--force]
|
|
8562
8634
|
odla-ai init --app-id <id> --name <name> [--services db,ai,o11y,calendar] [--env dev --env prod] [--ai-provider <byok-provider>]
|
|
8563
8635
|
odla-ai doctor [--config odla.config.mjs]
|
|
8636
|
+
odla-ai ai models [--config odla.config.mjs] [--env dev] [--provider <id>] [--json]
|
|
8564
8637
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8565
8638
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8566
8639
|
odla-ai operations get <operation-id> [--json]
|
|
@@ -8768,6 +8841,10 @@ Safety:
|
|
|
8768
8841
|
production odla.ai \u2014 there is no separate odla to point at. New projects get
|
|
8769
8842
|
the sandbox only. Add "prod" explicitly to odla.config.mjs and pass --yes to
|
|
8770
8843
|
provision the live database; use --dry-run first to inspect the resolved plan.
|
|
8844
|
+
A real provision first verifies both the released CLI version and the exact
|
|
8845
|
+
pinned versions of every external @odla-ai runtime module before importing
|
|
8846
|
+
the command graph. A stale workspace module blocks with its resolved path and
|
|
8847
|
+
tells the agent to update/rebase, npm ci, and rebuild.
|
|
8771
8848
|
Provision caches the approved developer token and service credentials under
|
|
8772
8849
|
.odla/ with mode 0600, and init adds those paths to .gitignore. Secret push
|
|
8773
8850
|
preflights Wrangler before any shown-once issuance or destructive rotation.
|
|
@@ -10546,7 +10623,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10546
10623
|
|
|
10547
10624
|
// src/provision.ts
|
|
10548
10625
|
var import_apps12 = require("@odla-ai/apps");
|
|
10549
|
-
var
|
|
10626
|
+
var import_ai4 = require("@odla-ai/ai");
|
|
10550
10627
|
var import_node_process11 = __toESM(require("process"), 1);
|
|
10551
10628
|
|
|
10552
10629
|
// src/integration-provision.ts
|
|
@@ -10660,14 +10737,14 @@ async function mintDbKey(opts, tenantId) {
|
|
|
10660
10737
|
appId: tenantId
|
|
10661
10738
|
})
|
|
10662
10739
|
});
|
|
10663
|
-
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await
|
|
10740
|
+
if (!created.ok) throw new Error(`db app create (${tenantId}) failed: ${created.status} ${await safeText7(created)}`);
|
|
10664
10741
|
res = await opts.fetch(`${opts.cfg.dbEndpoint}/admin/apps/${encodeURIComponent(tenantId)}/keys`, {
|
|
10665
10742
|
method: "POST",
|
|
10666
10743
|
headers,
|
|
10667
10744
|
body: "{}"
|
|
10668
10745
|
});
|
|
10669
10746
|
}
|
|
10670
|
-
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await
|
|
10747
|
+
if (!res.ok) throw new Error(`db key mint (${tenantId}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10671
10748
|
const body = await res.json();
|
|
10672
10749
|
if (!body.key) throw new Error(`db key mint (${tenantId}) returned no key`);
|
|
10673
10750
|
return body.key;
|
|
@@ -10683,12 +10760,12 @@ async function issueO11yToken(opts) {
|
|
|
10683
10760
|
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --rotate-o11y-token --push-secrets" to replace it explicitly`
|
|
10684
10761
|
);
|
|
10685
10762
|
}
|
|
10686
|
-
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await
|
|
10763
|
+
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
10687
10764
|
const body = await res.json();
|
|
10688
10765
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
10689
10766
|
return body.token;
|
|
10690
10767
|
}
|
|
10691
|
-
async function
|
|
10768
|
+
async function safeText7(res) {
|
|
10692
10769
|
try {
|
|
10693
10770
|
return redactSecrets((await res.text()).slice(0, 500));
|
|
10694
10771
|
} catch {
|
|
@@ -10870,7 +10947,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10870
10947
|
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10871
10948
|
if (key) {
|
|
10872
10949
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10873
|
-
await (0,
|
|
10950
|
+
await (0, import_ai4.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
10874
10951
|
out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
|
|
10875
10952
|
} else {
|
|
10876
10953
|
out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
|
|
@@ -10943,6 +11020,7 @@ var PM_ENTITIES = {
|
|
|
10943
11020
|
};
|
|
10944
11021
|
var COMMAND_SURFACE = {
|
|
10945
11022
|
agent: { jobs: {}, retry: {} },
|
|
11023
|
+
ai: { models: {} },
|
|
10946
11024
|
admin: {
|
|
10947
11025
|
ai: {
|
|
10948
11026
|
show: {},
|
|
@@ -12802,7 +12880,7 @@ async function securityStatus(parsed, dependencies) {
|
|
|
12802
12880
|
}
|
|
12803
12881
|
}
|
|
12804
12882
|
|
|
12805
|
-
// src/
|
|
12883
|
+
// src/exit-code.ts
|
|
12806
12884
|
function exitCodeFor(err) {
|
|
12807
12885
|
const code = err?.code;
|
|
12808
12886
|
if (code === "handshake_pending" || code === "watch_timeout" || code === "operation_pending") return 75;
|
|
@@ -12812,6 +12890,8 @@ function exitCodeFor(err) {
|
|
|
12812
12890
|
if (code === "invalid_cursor" || code === "invalid_plan" || code === "invalid_operation_id") return 2;
|
|
12813
12891
|
return 1;
|
|
12814
12892
|
}
|
|
12893
|
+
|
|
12894
|
+
// src/cli.ts
|
|
12815
12895
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
12816
12896
|
const runtime = {
|
|
12817
12897
|
...dependencies,
|
|
@@ -12967,6 +13047,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
12967
13047
|
SYSTEM_AI_PURPOSES,
|
|
12968
13048
|
acceptedAfter,
|
|
12969
13049
|
adminAi,
|
|
13050
|
+
aiModels,
|
|
12970
13051
|
calendarBookingPageUrl,
|
|
12971
13052
|
calendarCalendars,
|
|
12972
13053
|
calendarConnect,
|