@odla-ai/cli 0.25.23 → 0.26.1
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 +32 -2
- package/dist/bin.cjs +485 -165
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-JJ42NV7X.js → chunk-ZAZBUURZ.js} +485 -165
- package/dist/chunk-ZAZBUURZ.js.map +1 -0
- package/dist/index.cjs +485 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -12
- package/dist/index.d.ts +14 -12
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/references/sdks.md +5 -3
- package/skills/odla-migrate/references/phase-2b-calendar.md +13 -8
- package/dist/chunk-JJ42NV7X.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -182,10 +182,10 @@ function isManagedDevVar(line) {
|
|
|
182
182
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
183
183
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
184
184
|
}
|
|
185
|
-
function writePrivateText(path,
|
|
185
|
+
function writePrivateText(path, text2) {
|
|
186
186
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
187
187
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
188
|
-
(0, import_node_fs.writeFileSync)(temporary,
|
|
188
|
+
(0, import_node_fs.writeFileSync)(temporary, text2, { mode: 384 });
|
|
189
189
|
(0, import_node_fs.chmodSync)(temporary, 384);
|
|
190
190
|
(0, import_node_fs.renameSync)(temporary, path);
|
|
191
191
|
}
|
|
@@ -492,6 +492,7 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
492
492
|
}
|
|
493
493
|
var SCOPE_PURPOSE = {
|
|
494
494
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
495
|
+
"platform:chat:credential:write": "rotate the built-in Discussion responder credential",
|
|
495
496
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
496
497
|
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
497
498
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
@@ -551,6 +552,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
551
552
|
return token;
|
|
552
553
|
}
|
|
553
554
|
|
|
555
|
+
// src/principal-presentation.ts
|
|
556
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
557
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
558
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
559
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
560
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
554
563
|
// src/admin-ai-audit.ts
|
|
555
564
|
function adminAiAuditQuery(filters) {
|
|
556
565
|
if (filters.limit === void 0) return "";
|
|
@@ -580,17 +589,17 @@ async function readAdminAiAudit(request2) {
|
|
|
580
589
|
String(event.changeKind ?? ""),
|
|
581
590
|
String(event.purpose ?? event.provider ?? ""),
|
|
582
591
|
route2,
|
|
583
|
-
|
|
592
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
584
593
|
].join(" "));
|
|
585
594
|
}
|
|
586
595
|
}
|
|
587
596
|
async function responseBody(response2) {
|
|
588
|
-
const
|
|
589
|
-
if (!
|
|
597
|
+
const text2 = await response2.text();
|
|
598
|
+
if (!text2) return {};
|
|
590
599
|
try {
|
|
591
|
-
return JSON.parse(
|
|
600
|
+
return JSON.parse(text2);
|
|
592
601
|
} catch {
|
|
593
|
-
return { message:
|
|
602
|
+
return { message: text2.slice(0, 300) };
|
|
594
603
|
}
|
|
595
604
|
}
|
|
596
605
|
function apiError(status, body) {
|
|
@@ -671,7 +680,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
671
680
|
timestamp2(event.created_at),
|
|
672
681
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
673
682
|
String(event.run_id ?? ""),
|
|
674
|
-
|
|
683
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
675
684
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
676
685
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
677
686
|
String(input + output),
|
|
@@ -692,12 +701,12 @@ function timestamp2(value2) {
|
|
|
692
701
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
693
702
|
}
|
|
694
703
|
async function responseBody2(res) {
|
|
695
|
-
const
|
|
696
|
-
if (!
|
|
704
|
+
const text2 = await res.text();
|
|
705
|
+
if (!text2) return {};
|
|
697
706
|
try {
|
|
698
|
-
return JSON.parse(
|
|
707
|
+
return JSON.parse(text2);
|
|
699
708
|
} catch {
|
|
700
|
-
return { message:
|
|
709
|
+
return { message: text2.slice(0, 300) };
|
|
701
710
|
}
|
|
702
711
|
}
|
|
703
712
|
function apiError2(action2, status, body) {
|
|
@@ -873,12 +882,12 @@ function catalogModels(body) {
|
|
|
873
882
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
874
883
|
}
|
|
875
884
|
async function responseBody3(res) {
|
|
876
|
-
const
|
|
877
|
-
if (!
|
|
885
|
+
const text2 = await res.text();
|
|
886
|
+
if (!text2) return {};
|
|
878
887
|
try {
|
|
879
|
-
return JSON.parse(
|
|
888
|
+
return JSON.parse(text2);
|
|
880
889
|
} catch {
|
|
881
|
-
return { message:
|
|
890
|
+
return { message: text2.slice(0, 300) };
|
|
882
891
|
}
|
|
883
892
|
}
|
|
884
893
|
function apiError3(action2, status, body) {
|
|
@@ -1722,8 +1731,8 @@ async function appImport(options) {
|
|
|
1722
1731
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1723
1732
|
const doFetch = options.fetch ?? fetch;
|
|
1724
1733
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1725
|
-
const
|
|
1726
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1734
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1735
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1727
1736
|
if (format === "namespace-map" && options.ns) {
|
|
1728
1737
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1729
1738
|
}
|
|
@@ -1814,7 +1823,10 @@ function report(options, owners, headline) {
|
|
|
1814
1823
|
if (headline) out.log(headline);
|
|
1815
1824
|
out.log(`owners (${owners.length}):`);
|
|
1816
1825
|
for (const o of owners) {
|
|
1817
|
-
|
|
1826
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1827
|
+
out.log(
|
|
1828
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1829
|
+
);
|
|
1818
1830
|
}
|
|
1819
1831
|
}
|
|
1820
1832
|
async function ownersList(options) {
|
|
@@ -2731,9 +2743,9 @@ function canonicalValue(value2) {
|
|
|
2731
2743
|
}
|
|
2732
2744
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2733
2745
|
if (value2 && typeof value2 === "object") {
|
|
2734
|
-
const
|
|
2746
|
+
const record11 = value2;
|
|
2735
2747
|
return Object.fromEntries(
|
|
2736
|
-
Object.keys(
|
|
2748
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2737
2749
|
);
|
|
2738
2750
|
}
|
|
2739
2751
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -3559,22 +3571,26 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3559
3571
|
}
|
|
3560
3572
|
}
|
|
3561
3573
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3562
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3563
3574
|
next.push({
|
|
3564
3575
|
code: "review_destructive",
|
|
3565
|
-
command:
|
|
3576
|
+
command: studioSettingsUrl(reconciliation),
|
|
3566
3577
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3567
3578
|
});
|
|
3568
3579
|
}
|
|
3569
3580
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3570
3581
|
next.push({
|
|
3571
3582
|
code: "declare_or_accept_runtime_state",
|
|
3572
|
-
command:
|
|
3583
|
+
command: studioSettingsUrl(reconciliation),
|
|
3573
3584
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3574
3585
|
});
|
|
3575
3586
|
}
|
|
3576
3587
|
return next;
|
|
3577
3588
|
}
|
|
3589
|
+
function studioSettingsUrl(reconciliation) {
|
|
3590
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3591
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3592
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3593
|
+
}
|
|
3578
3594
|
function quoteArg2(value2) {
|
|
3579
3595
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3580
3596
|
}
|
|
@@ -3614,15 +3630,15 @@ function readWranglerConfig(path) {
|
|
|
3614
3630
|
return null;
|
|
3615
3631
|
}
|
|
3616
3632
|
}
|
|
3617
|
-
function stripJsonComments(
|
|
3633
|
+
function stripJsonComments(text2) {
|
|
3618
3634
|
let result = "";
|
|
3619
3635
|
let inString = false;
|
|
3620
|
-
for (let i = 0; i <
|
|
3621
|
-
const ch =
|
|
3636
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3637
|
+
const ch = text2[i];
|
|
3622
3638
|
if (inString) {
|
|
3623
3639
|
result += ch;
|
|
3624
3640
|
if (ch === "\\") {
|
|
3625
|
-
result +=
|
|
3641
|
+
result += text2[i + 1] ?? "";
|
|
3626
3642
|
i++;
|
|
3627
3643
|
} else if (ch === '"') {
|
|
3628
3644
|
inString = false;
|
|
@@ -3634,14 +3650,14 @@ function stripJsonComments(text) {
|
|
|
3634
3650
|
result += ch;
|
|
3635
3651
|
continue;
|
|
3636
3652
|
}
|
|
3637
|
-
if (ch === "/" &&
|
|
3638
|
-
while (i <
|
|
3653
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3654
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3639
3655
|
result += "\n";
|
|
3640
3656
|
continue;
|
|
3641
3657
|
}
|
|
3642
|
-
if (ch === "/" &&
|
|
3658
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3643
3659
|
i += 2;
|
|
3644
|
-
while (i <
|
|
3660
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3645
3661
|
i++;
|
|
3646
3662
|
continue;
|
|
3647
3663
|
}
|
|
@@ -3658,7 +3674,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3658
3674
|
}
|
|
3659
3675
|
}
|
|
3660
3676
|
function wranglerPutSecret(run, opts) {
|
|
3661
|
-
const args = [
|
|
3677
|
+
const args = [
|
|
3678
|
+
"wrangler",
|
|
3679
|
+
"secret",
|
|
3680
|
+
"put",
|
|
3681
|
+
opts.name,
|
|
3682
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3683
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3684
|
+
];
|
|
3662
3685
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3663
3686
|
}
|
|
3664
3687
|
|
|
@@ -4036,9 +4059,9 @@ function initProject(options) {
|
|
|
4036
4059
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4037
4060
|
out.log("updated .gitignore for local odla credentials");
|
|
4038
4061
|
}
|
|
4039
|
-
function writeIfMissing(path,
|
|
4062
|
+
function writeIfMissing(path, text2) {
|
|
4040
4063
|
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4041
|
-
(0, import_node_fs13.writeFileSync)(path,
|
|
4064
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
4042
4065
|
}
|
|
4043
4066
|
function configTemplate(input) {
|
|
4044
4067
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4246,13 +4269,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4246
4269
|
body: JSON.stringify({ value: value2 })
|
|
4247
4270
|
});
|
|
4248
4271
|
if (!res.ok) {
|
|
4249
|
-
const
|
|
4250
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4272
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4273
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
4251
4274
|
}
|
|
4252
4275
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4253
4276
|
}
|
|
4254
|
-
function scrubValue(
|
|
4255
|
-
return redactSecrets(
|
|
4277
|
+
function scrubValue(text2, value2) {
|
|
4278
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
4256
4279
|
}
|
|
4257
4280
|
async function resolveVaultWrite(options) {
|
|
4258
4281
|
const out = options.stdout ?? console;
|
|
@@ -5080,8 +5103,8 @@ async function runContainerAttempt(options) {
|
|
|
5080
5103
|
let stopped = false;
|
|
5081
5104
|
let exited = false;
|
|
5082
5105
|
child.stderr.setEncoding("utf8");
|
|
5083
|
-
child.stderr.on("data", (
|
|
5084
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5106
|
+
child.stderr.on("data", (text2) => {
|
|
5107
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
5085
5108
|
});
|
|
5086
5109
|
const stop = (reason) => {
|
|
5087
5110
|
if (stopped || exited) return;
|
|
@@ -5239,8 +5262,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5239
5262
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5240
5263
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5241
5264
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
5242
|
-
const entries = inventory.flatMap((
|
|
5243
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5265
|
+
const entries = inventory.flatMap((record11) => {
|
|
5266
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
5244
5267
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5245
5268
|
});
|
|
5246
5269
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5488,8 +5511,8 @@ function normalize(value2) {
|
|
|
5488
5511
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5489
5512
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5490
5513
|
if (typeof value2 === "object") {
|
|
5491
|
-
const
|
|
5492
|
-
return Object.fromEntries(Object.keys(
|
|
5514
|
+
const record11 = value2;
|
|
5515
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5493
5516
|
}
|
|
5494
5517
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5495
5518
|
}
|
|
@@ -5913,8 +5936,8 @@ function boundedInteger(value2, spec) {
|
|
|
5913
5936
|
}
|
|
5914
5937
|
function boundedNumber(value2, spec) {
|
|
5915
5938
|
if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
|
|
5916
|
-
const
|
|
5917
|
-
if (/e/i.test(
|
|
5939
|
+
const text2 = String(value2);
|
|
5940
|
+
if (/e/i.test(text2) || (text2.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
5918
5941
|
return value2;
|
|
5919
5942
|
}
|
|
5920
5943
|
function enumMember(value2, spec) {
|
|
@@ -6070,8 +6093,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6070
6093
|
return void 0;
|
|
6071
6094
|
}
|
|
6072
6095
|
function looksLikeDestination(value2) {
|
|
6073
|
-
const
|
|
6074
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6096
|
+
const text2 = value2.trim();
|
|
6097
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
6075
6098
|
}
|
|
6076
6099
|
|
|
6077
6100
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -6440,8 +6463,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6440
6463
|
});
|
|
6441
6464
|
let stderr = "";
|
|
6442
6465
|
child.stderr.setEncoding("utf8");
|
|
6443
|
-
child.stderr.on("data", (
|
|
6444
|
-
if (stderr.length < 4e3) stderr +=
|
|
6466
|
+
child.stderr.on("data", (text2) => {
|
|
6467
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6445
6468
|
});
|
|
6446
6469
|
child.once("error", reject);
|
|
6447
6470
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7612,10 +7635,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7612
7635
|
task: lease.task,
|
|
7613
7636
|
limits: this.options.limits,
|
|
7614
7637
|
signal: active.abort.signal,
|
|
7615
|
-
onStderr: (
|
|
7638
|
+
onStderr: (text2) => this.#event(command, {
|
|
7616
7639
|
type: "message",
|
|
7617
7640
|
actor: "system",
|
|
7618
|
-
body:
|
|
7641
|
+
body: text2.slice(0, 4e3)
|
|
7619
7642
|
}, active.conversationRefs),
|
|
7620
7643
|
onMessage: async (output) => {
|
|
7621
7644
|
if (output.type === "inference.request") {
|
|
@@ -8497,7 +8520,8 @@ Usage:
|
|
|
8497
8520
|
odla-ai doctor [--config odla.config.mjs]
|
|
8498
8521
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8499
8522
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8500
|
-
odla-ai operations
|
|
8523
|
+
odla-ai operations get <operation-id> [--json]
|
|
8524
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8501
8525
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8502
8526
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8503
8527
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8548,6 +8572,7 @@ Usage:
|
|
|
8548
8572
|
odla-ai context remove <name> --yes [--json]
|
|
8549
8573
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8550
8574
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8575
|
+
odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
|
|
8551
8576
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8552
8577
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8553
8578
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -8611,9 +8636,9 @@ Commands:
|
|
|
8611
8636
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8612
8637
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8613
8638
|
a question usually needs both.
|
|
8614
|
-
whoami Report
|
|
8615
|
-
|
|
8616
|
-
|
|
8639
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8640
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8641
|
+
A manager relationship never supplies authority by itself.
|
|
8617
8642
|
context Explain selected config, platform, app, environment, and
|
|
8618
8643
|
developer-token provenance without printing credentials or
|
|
8619
8644
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8733,6 +8758,58 @@ Safety:
|
|
|
8733
8758
|
`);
|
|
8734
8759
|
}
|
|
8735
8760
|
|
|
8761
|
+
// src/discuss-principals.ts
|
|
8762
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8763
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8764
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8765
|
+
}
|
|
8766
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8767
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8768
|
+
const profile = projection.principals?.[principalId];
|
|
8769
|
+
if (profile) {
|
|
8770
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8771
|
+
if (profile.kind === "agent") {
|
|
8772
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8773
|
+
}
|
|
8774
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8775
|
+
}
|
|
8776
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8777
|
+
if (legacyName) {
|
|
8778
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8779
|
+
}
|
|
8780
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8781
|
+
}
|
|
8782
|
+
|
|
8783
|
+
// src/discuss-read-render.ts
|
|
8784
|
+
function discussBodyWithRefs(post) {
|
|
8785
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8786
|
+
let out = "";
|
|
8787
|
+
let cursor = 0;
|
|
8788
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8789
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8790
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8791
|
+
cursor = ref.end;
|
|
8792
|
+
}
|
|
8793
|
+
return out + post.body.slice(cursor);
|
|
8794
|
+
}
|
|
8795
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8796
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8797
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8798
|
+
for (const post of posts) {
|
|
8799
|
+
const who = discussPrincipalLabel(
|
|
8800
|
+
post.authorId,
|
|
8801
|
+
post.authorKind,
|
|
8802
|
+
projection
|
|
8803
|
+
);
|
|
8804
|
+
ctx.out.log(`
|
|
8805
|
+
\u2014 ${who}`);
|
|
8806
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8807
|
+
for (const file of post.attachments ?? []) {
|
|
8808
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8809
|
+
}
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
|
|
8736
8813
|
// src/discuss-actions.ts
|
|
8737
8814
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8738
8815
|
async function request(ctx, method, path, body) {
|
|
@@ -8751,17 +8828,6 @@ function emit(ctx, value2, human) {
|
|
|
8751
8828
|
else human();
|
|
8752
8829
|
}
|
|
8753
8830
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
8754
|
-
function bodyWithRefs(post) {
|
|
8755
|
-
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8756
|
-
let out = "";
|
|
8757
|
-
let cursor = 0;
|
|
8758
|
-
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8759
|
-
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8760
|
-
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8761
|
-
cursor = ref.end;
|
|
8762
|
-
}
|
|
8763
|
-
return out + post.body.slice(cursor);
|
|
8764
|
-
}
|
|
8765
8831
|
function content(parsed) {
|
|
8766
8832
|
const markup = stringOpt(parsed.options.markup);
|
|
8767
8833
|
if (markup) return { markup };
|
|
@@ -8816,11 +8882,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8816
8882
|
offset: requestedOffset ?? "0"
|
|
8817
8883
|
});
|
|
8818
8884
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8819
|
-
emit(
|
|
8885
|
+
emit(
|
|
8886
|
+
ctx,
|
|
8887
|
+
page,
|
|
8888
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8889
|
+
);
|
|
8820
8890
|
return;
|
|
8821
8891
|
}
|
|
8822
8892
|
for (let scan = 0; scan < 3; scan++) {
|
|
8823
8893
|
const posts = /* @__PURE__ */ new Map();
|
|
8894
|
+
const projection = {
|
|
8895
|
+
authors: {},
|
|
8896
|
+
principals: {}
|
|
8897
|
+
};
|
|
8824
8898
|
let topic = null;
|
|
8825
8899
|
let offset = 0;
|
|
8826
8900
|
for (; ; ) {
|
|
@@ -8831,6 +8905,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8831
8905
|
);
|
|
8832
8906
|
topic = page.topic;
|
|
8833
8907
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8908
|
+
mergeDiscussPrincipals(projection, page);
|
|
8834
8909
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8835
8910
|
if (!page.page?.hasMore) break;
|
|
8836
8911
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8843,24 +8918,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8843
8918
|
);
|
|
8844
8919
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8845
8920
|
if (ordered.length === expected) {
|
|
8846
|
-
const result = { topic, posts: ordered };
|
|
8847
|
-
emit(
|
|
8921
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8922
|
+
emit(
|
|
8923
|
+
ctx,
|
|
8924
|
+
result,
|
|
8925
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8926
|
+
);
|
|
8848
8927
|
return;
|
|
8849
8928
|
}
|
|
8850
8929
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8851
8930
|
}
|
|
8852
8931
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8853
8932
|
}
|
|
8854
|
-
function renderRead(ctx, topic, posts) {
|
|
8855
|
-
ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
|
|
8856
|
-
for (const post of posts) {
|
|
8857
|
-
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
8858
|
-
ctx.out.log(`
|
|
8859
|
-
\u2014 ${who}`);
|
|
8860
|
-
ctx.out.log(bodyWithRefs(post));
|
|
8861
|
-
for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8862
|
-
}
|
|
8863
|
-
}
|
|
8864
8933
|
async function discussPost(ctx, parsed) {
|
|
8865
8934
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8866
8935
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -9085,6 +9154,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9085
9154
|
found: true,
|
|
9086
9155
|
cursor,
|
|
9087
9156
|
events: matching,
|
|
9157
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9158
|
+
...page.principals ? { principals: page.principals } : {},
|
|
9088
9159
|
...posts && posts.length > 0 ? { posts } : {},
|
|
9089
9160
|
...topics && topics.length > 0 ? { topics } : {}
|
|
9090
9161
|
});
|
|
@@ -9117,7 +9188,11 @@ function report2(ctx, parsed, result) {
|
|
|
9117
9188
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9118
9189
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
9119
9190
|
for (const post of result.posts ?? []) {
|
|
9120
|
-
const who =
|
|
9191
|
+
const who = discussPrincipalLabel(
|
|
9192
|
+
post.authorId,
|
|
9193
|
+
post.authorKind,
|
|
9194
|
+
result
|
|
9195
|
+
);
|
|
9121
9196
|
ctx.out.log(`\u2014 ${who}
|
|
9122
9197
|
${post.body}`);
|
|
9123
9198
|
}
|
|
@@ -9262,8 +9337,8 @@ function collectFields(parsed, allowClear) {
|
|
|
9262
9337
|
if (allowClear) out[spec.key] = null;
|
|
9263
9338
|
continue;
|
|
9264
9339
|
}
|
|
9265
|
-
const
|
|
9266
|
-
out[spec.key] = spec.num ? Number(
|
|
9340
|
+
const text2 = stringOpt(value2);
|
|
9341
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
9267
9342
|
}
|
|
9268
9343
|
return out;
|
|
9269
9344
|
}
|
|
@@ -9330,8 +9405,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9330
9405
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
9331
9406
|
}
|
|
9332
9407
|
async function pmGet(ctx, entity, id) {
|
|
9333
|
-
const { record:
|
|
9334
|
-
emit2(ctx,
|
|
9408
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9409
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
9335
9410
|
}
|
|
9336
9411
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9337
9412
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9376,9 +9451,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9376
9451
|
]);
|
|
9377
9452
|
const handoff = {
|
|
9378
9453
|
appId,
|
|
9379
|
-
unmetGoals: goals.filter((
|
|
9380
|
-
activeTasks: tasks.filter((
|
|
9381
|
-
openBugs: bugs.filter((
|
|
9454
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
9455
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
9456
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
9382
9457
|
};
|
|
9383
9458
|
const result = {
|
|
9384
9459
|
...handoff,
|
|
@@ -9397,10 +9472,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9397
9472
|
]) {
|
|
9398
9473
|
ctx.out.log(`${label}:`);
|
|
9399
9474
|
if (!records.length) ctx.out.log("- (none)");
|
|
9400
|
-
else for (const
|
|
9475
|
+
else for (const record11 of records) printRecord(
|
|
9401
9476
|
ctx,
|
|
9402
9477
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9403
|
-
|
|
9478
|
+
record11
|
|
9404
9479
|
);
|
|
9405
9480
|
}
|
|
9406
9481
|
});
|
|
@@ -9591,19 +9666,180 @@ function age(input) {
|
|
|
9591
9666
|
return `${Math.round(input / 6e4)}m`;
|
|
9592
9667
|
}
|
|
9593
9668
|
|
|
9669
|
+
// src/platform-chat-credential-command.ts
|
|
9670
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
9671
|
+
async function rotatePlatformChatCredential(parsed, deps) {
|
|
9672
|
+
assertArgs(
|
|
9673
|
+
parsed,
|
|
9674
|
+
[
|
|
9675
|
+
"config",
|
|
9676
|
+
"context",
|
|
9677
|
+
"platform",
|
|
9678
|
+
"token",
|
|
9679
|
+
"email",
|
|
9680
|
+
"open",
|
|
9681
|
+
"json",
|
|
9682
|
+
"yes",
|
|
9683
|
+
"wrangler-config",
|
|
9684
|
+
"expected-version"
|
|
9685
|
+
],
|
|
9686
|
+
3
|
|
9687
|
+
);
|
|
9688
|
+
if (parsed.options.yes !== true) {
|
|
9689
|
+
throw new Error(
|
|
9690
|
+
"platform chat-credentials rotate changes the production chat secret; pass --yes"
|
|
9691
|
+
);
|
|
9692
|
+
}
|
|
9693
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9694
|
+
allowMissingConfig: true
|
|
9695
|
+
});
|
|
9696
|
+
const platform = context.platform.value;
|
|
9697
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9698
|
+
const out = deps.stdout ?? console;
|
|
9699
|
+
const run = deps.runner ?? defaultRunner;
|
|
9700
|
+
const cwd = import_node_process10.default.cwd();
|
|
9701
|
+
const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
|
|
9702
|
+
if (!await wranglerLoggedIn(run, cwd)) {
|
|
9703
|
+
throw new Error(
|
|
9704
|
+
'Wrangler is not authenticated; run "npx wrangler login" and retry'
|
|
9705
|
+
);
|
|
9706
|
+
}
|
|
9707
|
+
const priorHealth = await doFetch(`${platform}/health/services/chat`);
|
|
9708
|
+
const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
|
|
9709
|
+
await priorHealth.body?.cancel().catch(() => {
|
|
9710
|
+
});
|
|
9711
|
+
if (!priorVersion) {
|
|
9712
|
+
throw new Error(
|
|
9713
|
+
"chat health did not identify its current Worker version; refusing to rotate"
|
|
9714
|
+
);
|
|
9715
|
+
}
|
|
9716
|
+
const expectedVersion = stringOpt(parsed.options["expected-version"]);
|
|
9717
|
+
if (expectedVersion && priorVersion !== expectedVersion) {
|
|
9718
|
+
throw new Error(
|
|
9719
|
+
`chat health answered from version ${priorVersion}; expected ${expectedVersion}`
|
|
9720
|
+
);
|
|
9721
|
+
}
|
|
9722
|
+
const token = await resolveAdminPlatformToken({
|
|
9723
|
+
platform,
|
|
9724
|
+
scope: "platform:chat:credential:write",
|
|
9725
|
+
token: stringOpt(parsed.options.token),
|
|
9726
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9727
|
+
email: stringOpt(parsed.options.email),
|
|
9728
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9729
|
+
fetch: doFetch,
|
|
9730
|
+
stdout: out,
|
|
9731
|
+
openApprovalUrl: deps.openUrl,
|
|
9732
|
+
label: "odla CLI (rotate built-in Discussion responder)"
|
|
9733
|
+
});
|
|
9734
|
+
const mintedResponse = await doFetch(
|
|
9735
|
+
`${platform}/registry/platform/chat-credentials/rotate`,
|
|
9736
|
+
{
|
|
9737
|
+
method: "POST",
|
|
9738
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9739
|
+
}
|
|
9740
|
+
);
|
|
9741
|
+
const minted = await mintedResponse.json().catch(() => null);
|
|
9742
|
+
if (!mintedResponse.ok) {
|
|
9743
|
+
throw new Error(
|
|
9744
|
+
`mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
|
|
9745
|
+
);
|
|
9746
|
+
}
|
|
9747
|
+
if (!isPlatformChatCredential(minted)) {
|
|
9748
|
+
throw new Error(
|
|
9749
|
+
"platform returned an invalid odla.platform-chat-credential/v1 envelope"
|
|
9750
|
+
);
|
|
9751
|
+
}
|
|
9752
|
+
const put = await wranglerPutSecret(run, {
|
|
9753
|
+
name: "ODLA_BOT_TOKENS",
|
|
9754
|
+
value: JSON.stringify({
|
|
9755
|
+
[minted.appId]: { [minted.principalId]: minted.key }
|
|
9756
|
+
}),
|
|
9757
|
+
configPath: wranglerConfig,
|
|
9758
|
+
cwd
|
|
9759
|
+
});
|
|
9760
|
+
if (put.code !== 0) {
|
|
9761
|
+
throw new Error(
|
|
9762
|
+
"Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
|
|
9763
|
+
);
|
|
9764
|
+
}
|
|
9765
|
+
const version = await waitForRotatedChatHealth({
|
|
9766
|
+
fetch: doFetch,
|
|
9767
|
+
platform,
|
|
9768
|
+
priorVersion,
|
|
9769
|
+
wait: deps.pollWait
|
|
9770
|
+
});
|
|
9771
|
+
const result = {
|
|
9772
|
+
schemaVersion: "odla.platform-chat-credential-rotation/v1",
|
|
9773
|
+
appId: minted.appId,
|
|
9774
|
+
appIncarnation: minted.appIncarnation,
|
|
9775
|
+
principalId: minted.principalId,
|
|
9776
|
+
health: { ok: true, service: "odla-chat-agent", version }
|
|
9777
|
+
};
|
|
9778
|
+
if (parsed.options.json === true) {
|
|
9779
|
+
out.log(JSON.stringify(result, null, 2));
|
|
9780
|
+
} else {
|
|
9781
|
+
out.log(
|
|
9782
|
+
`rotated ${result.appId} ${result.principalId} ${result.health.version}`
|
|
9783
|
+
);
|
|
9784
|
+
}
|
|
9785
|
+
}
|
|
9786
|
+
async function waitForRotatedChatHealth(opts) {
|
|
9787
|
+
const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
|
|
9788
|
+
let lastStatus = 0;
|
|
9789
|
+
let lastVersion = null;
|
|
9790
|
+
let lastError = "private_service_unready";
|
|
9791
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
9792
|
+
const response2 = await opts.fetch(
|
|
9793
|
+
`${opts.platform}/health/services/chat`
|
|
9794
|
+
);
|
|
9795
|
+
const body = await response2.json().catch(() => null);
|
|
9796
|
+
const version = response2.headers.get("x-odla-worker-version-id");
|
|
9797
|
+
if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
|
|
9798
|
+
return version;
|
|
9799
|
+
}
|
|
9800
|
+
lastStatus = response2.status;
|
|
9801
|
+
lastVersion = version;
|
|
9802
|
+
lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
|
|
9803
|
+
if (attempt < 59) await wait2(5e3);
|
|
9804
|
+
}
|
|
9805
|
+
throw new Error(
|
|
9806
|
+
`chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
|
|
9807
|
+
);
|
|
9808
|
+
}
|
|
9809
|
+
function isPlatformChatCredential(value2) {
|
|
9810
|
+
return record7(value2) && value2.schemaVersion === "odla.platform-chat-credential/v1" && value2.appId === "odla-pm" && typeof value2.appIncarnation === "string" && value2.appIncarnation.length > 0 && value2.principalId === "agent_odla" && typeof value2.key === "string" && value2.key.startsWith("odla_sk_");
|
|
9811
|
+
}
|
|
9812
|
+
function apiMessage(value2) {
|
|
9813
|
+
if (!record7(value2)) return "request failed";
|
|
9814
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9815
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9816
|
+
}
|
|
9817
|
+
function record7(value2) {
|
|
9818
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9594
9821
|
// src/platform-command.ts
|
|
9595
9822
|
async function platformCommand(parsed, deps = {}) {
|
|
9823
|
+
const action2 = parsed.positionals[1];
|
|
9824
|
+
if (action2 === "status") {
|
|
9825
|
+
return platformStatus(parsed, deps);
|
|
9826
|
+
}
|
|
9827
|
+
if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
|
|
9828
|
+
return rotatePlatformChatCredential(parsed, deps);
|
|
9829
|
+
}
|
|
9830
|
+
throw new Error(
|
|
9831
|
+
`unknown platform action "${[
|
|
9832
|
+
action2,
|
|
9833
|
+
parsed.positionals[2]
|
|
9834
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9835
|
+
);
|
|
9836
|
+
}
|
|
9837
|
+
async function platformStatus(parsed, deps) {
|
|
9596
9838
|
assertArgs(
|
|
9597
9839
|
parsed,
|
|
9598
9840
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9599
9841
|
2
|
|
9600
9842
|
);
|
|
9601
|
-
const action2 = parsed.positionals[1];
|
|
9602
|
-
if (action2 !== "status") {
|
|
9603
|
-
throw new Error(
|
|
9604
|
-
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9605
|
-
);
|
|
9606
|
-
}
|
|
9607
9843
|
const context = await resolveOperatorContext(parsed, {
|
|
9608
9844
|
allowMissingConfig: true
|
|
9609
9845
|
});
|
|
@@ -9628,7 +9864,7 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9628
9864
|
const body = await response2.json().catch(() => null);
|
|
9629
9865
|
if (!response2.ok) {
|
|
9630
9866
|
throw new Error(
|
|
9631
|
-
`read platform status failed (HTTP ${response2.status}): ${
|
|
9867
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
|
|
9632
9868
|
);
|
|
9633
9869
|
}
|
|
9634
9870
|
if (!isPlatformStatus(body)) {
|
|
@@ -9641,17 +9877,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9641
9877
|
}
|
|
9642
9878
|
}
|
|
9643
9879
|
function isPlatformStatus(value2) {
|
|
9644
|
-
if (!
|
|
9645
|
-
if (!
|
|
9646
|
-
if (!
|
|
9880
|
+
if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9881
|
+
if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9882
|
+
if (!record8(value2.catalog) || !record8(value2.summary)) return false;
|
|
9647
9883
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9648
9884
|
}
|
|
9649
|
-
function
|
|
9650
|
-
if (!
|
|
9651
|
-
const error =
|
|
9885
|
+
function apiMessage2(value2) {
|
|
9886
|
+
if (!record8(value2)) return "request failed";
|
|
9887
|
+
const error = record8(value2.error) ? value2.error : value2;
|
|
9652
9888
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9653
9889
|
}
|
|
9654
|
-
function
|
|
9890
|
+
function record8(value2) {
|
|
9655
9891
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9656
9892
|
}
|
|
9657
9893
|
|
|
@@ -9692,7 +9928,7 @@ function statusVerdict(reads) {
|
|
|
9692
9928
|
severity: "degraded"
|
|
9693
9929
|
});
|
|
9694
9930
|
}
|
|
9695
|
-
const performance =
|
|
9931
|
+
const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9696
9932
|
if (performance?.status === "unavailable") {
|
|
9697
9933
|
reasons.push({
|
|
9698
9934
|
source: "liveSync",
|
|
@@ -9773,7 +10009,7 @@ function statusVerdict(reads) {
|
|
|
9773
10009
|
reasons
|
|
9774
10010
|
};
|
|
9775
10011
|
}
|
|
9776
|
-
function
|
|
10012
|
+
function record9(value2) {
|
|
9777
10013
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9778
10014
|
}
|
|
9779
10015
|
function numeric2(value2) {
|
|
@@ -9801,7 +10037,7 @@ function printO11yStatus(status, out) {
|
|
|
9801
10037
|
out.log(
|
|
9802
10038
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9803
10039
|
);
|
|
9804
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10040
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
|
|
9805
10041
|
const requests = routes.reduce(
|
|
9806
10042
|
(total, row) => total + numeric3(row.requests),
|
|
9807
10043
|
0
|
|
@@ -9813,39 +10049,39 @@ function printO11yStatus(status, out) {
|
|
|
9813
10049
|
out.log(
|
|
9814
10050
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9815
10051
|
);
|
|
9816
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10052
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
|
|
9817
10053
|
out.log(
|
|
9818
10054
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9819
10055
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9820
10056
|
).join(", ") : "none observed"}`
|
|
9821
10057
|
);
|
|
9822
10058
|
out.log(liveSyncLine(status.liveSync));
|
|
9823
|
-
const canaryDurations =
|
|
10059
|
+
const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9824
10060
|
out.log(
|
|
9825
10061
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9826
10062
|
);
|
|
9827
|
-
const collectorIngest =
|
|
9828
|
-
const collectorStorage =
|
|
10063
|
+
const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10064
|
+
const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9829
10065
|
out.log(
|
|
9830
10066
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9831
10067
|
);
|
|
9832
|
-
const providerMetrics =
|
|
9833
|
-
const providerCapacity =
|
|
9834
|
-
const workerMemory =
|
|
10068
|
+
const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10069
|
+
const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10070
|
+
const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9835
10071
|
out.log(
|
|
9836
10072
|
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors ${optionalBytes(workerMemory.headroomBytes)} isolate memory headroom`
|
|
9837
10073
|
);
|
|
9838
10074
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9839
10075
|
out.log(line);
|
|
9840
10076
|
}
|
|
9841
|
-
const coverage =
|
|
9842
|
-
const coverageCounts =
|
|
9843
|
-
const coverageBudget =
|
|
10077
|
+
const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10078
|
+
const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10079
|
+
const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9844
10080
|
out.log(
|
|
9845
10081
|
`request-coverage ${status.providerReconciliation.httpStatus} ${String(status.providerReconciliation.body.status ?? status.providerReconciliation.body.error ?? "unavailable")} ${optionalPercent(coverage.applicationCoverage)} application/provider ${numeric3(coverageCounts.applicationRequests)}/${numeric3(coverageCounts.providerRequests)} requests \xB1${optionalPercent(coverageBudget.maxRelativeError)} budget`
|
|
9846
10082
|
);
|
|
9847
10083
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9848
|
-
const providerFreshness =
|
|
10084
|
+
const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9849
10085
|
out.log(
|
|
9850
10086
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9851
10087
|
);
|
|
@@ -9854,17 +10090,17 @@ function printO11yStatus(status, out) {
|
|
|
9854
10090
|
);
|
|
9855
10091
|
}
|
|
9856
10092
|
function providerCapacityLines(read3) {
|
|
9857
|
-
const resources =
|
|
9858
|
-
const durableObjects =
|
|
9859
|
-
const periodic =
|
|
9860
|
-
const storage =
|
|
9861
|
-
const d1 =
|
|
9862
|
-
const d1Activity =
|
|
9863
|
-
const d1Storage =
|
|
9864
|
-
const d1Latency =
|
|
9865
|
-
const r2 =
|
|
9866
|
-
const r2Operations =
|
|
9867
|
-
const r2Storage =
|
|
10093
|
+
const resources = record10(read3.body.resources) ? read3.body.resources : {};
|
|
10094
|
+
const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
|
|
10095
|
+
const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10096
|
+
const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10097
|
+
const d1 = record10(resources.d1) ? resources.d1 : {};
|
|
10098
|
+
const d1Activity = record10(d1.activity) ? d1.activity : {};
|
|
10099
|
+
const d1Storage = record10(d1.storage) ? d1.storage : {};
|
|
10100
|
+
const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
|
|
10101
|
+
const r2 = record10(resources.r2) ? resources.r2 : {};
|
|
10102
|
+
const r2Operations = record10(r2.operations) ? r2.operations : {};
|
|
10103
|
+
const r2Storage = record10(r2.storage) ? r2.storage : {};
|
|
9868
10104
|
const status = String(
|
|
9869
10105
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9870
10106
|
);
|
|
@@ -9875,11 +10111,11 @@ function providerCapacityLines(read3) {
|
|
|
9875
10111
|
];
|
|
9876
10112
|
}
|
|
9877
10113
|
function liveSyncLine(read3) {
|
|
9878
|
-
const performance =
|
|
9879
|
-
const commitToSend =
|
|
10114
|
+
const performance = record10(read3.body.performance) ? read3.body.performance : {};
|
|
10115
|
+
const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
|
|
9880
10116
|
return `live-sync ${read3.httpStatus} ${String(read3.body.status ?? read3.body.error ?? "unavailable")} ${numeric3(read3.body.activeConnections)} active ${optionalNumeric(commitToSend.p95)} commit-to-send p95 ${numeric3(performance.sendFailures)} send failures`;
|
|
9881
10117
|
}
|
|
9882
|
-
function
|
|
10118
|
+
function record10(value2) {
|
|
9883
10119
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9884
10120
|
}
|
|
9885
10121
|
function numeric3(value2) {
|
|
@@ -10048,14 +10284,14 @@ function statusMinutes(value2) {
|
|
|
10048
10284
|
}
|
|
10049
10285
|
async function read2(url, headers, doFetch) {
|
|
10050
10286
|
const response2 = await doFetch(url, { headers });
|
|
10051
|
-
const
|
|
10287
|
+
const text2 = await response2.text();
|
|
10052
10288
|
let body = {};
|
|
10053
|
-
if (
|
|
10289
|
+
if (text2) {
|
|
10054
10290
|
try {
|
|
10055
|
-
const value2 = JSON.parse(
|
|
10291
|
+
const value2 = JSON.parse(text2);
|
|
10056
10292
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
10057
10293
|
} catch {
|
|
10058
|
-
body = { message:
|
|
10294
|
+
body = { message: text2.slice(0, 300) };
|
|
10059
10295
|
}
|
|
10060
10296
|
}
|
|
10061
10297
|
return { httpStatus: response2.status, body };
|
|
@@ -10064,7 +10300,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10064
10300
|
// src/provision.ts
|
|
10065
10301
|
var import_apps12 = require("@odla-ai/apps");
|
|
10066
10302
|
var import_ai3 = require("@odla-ai/ai");
|
|
10067
|
-
var
|
|
10303
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
10068
10304
|
|
|
10069
10305
|
// src/integration-provision.ts
|
|
10070
10306
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -10371,7 +10607,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10371
10607
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10372
10608
|
}
|
|
10373
10609
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10374
|
-
const key =
|
|
10610
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10375
10611
|
if (key) {
|
|
10376
10612
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10377
10613
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10413,7 +10649,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10413
10649
|
|
|
10414
10650
|
// src/record.ts
|
|
10415
10651
|
var import_node_fs16 = require("fs");
|
|
10416
|
-
var
|
|
10652
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10417
10653
|
|
|
10418
10654
|
// src/surface.ts
|
|
10419
10655
|
var PM_ACTIONS = {
|
|
@@ -10480,7 +10716,10 @@ var COMMAND_SURFACE = {
|
|
|
10480
10716
|
init: {},
|
|
10481
10717
|
o11y: { status: {} },
|
|
10482
10718
|
operations: { get: {}, wait: {} },
|
|
10483
|
-
platform: {
|
|
10719
|
+
platform: {
|
|
10720
|
+
status: {},
|
|
10721
|
+
"chat-credentials": { rotate: {} }
|
|
10722
|
+
},
|
|
10484
10723
|
pm: {
|
|
10485
10724
|
...PM_ENTITIES,
|
|
10486
10725
|
handoff: {}
|
|
@@ -10559,7 +10798,7 @@ function invocationPath(words2) {
|
|
|
10559
10798
|
|
|
10560
10799
|
// src/record.ts
|
|
10561
10800
|
function recordInvocation(parsed) {
|
|
10562
|
-
const file =
|
|
10801
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10563
10802
|
if (!file) return;
|
|
10564
10803
|
try {
|
|
10565
10804
|
const entry = {
|
|
@@ -10689,12 +10928,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10689
10928
|
});
|
|
10690
10929
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10691
10930
|
}
|
|
10692
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
10931
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10693
10932
|
const runbook = await bySlug(ctx, slug);
|
|
10694
10933
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10695
10934
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10696
10935
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10697
|
-
patch: {
|
|
10936
|
+
patch: {
|
|
10937
|
+
body,
|
|
10938
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
10939
|
+
...note ? { note } : {},
|
|
10940
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
10941
|
+
}
|
|
10698
10942
|
});
|
|
10699
10943
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10700
10944
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10748,12 +10992,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
10748
10992
|
// src/runbook-import.ts
|
|
10749
10993
|
var import_node_fs18 = require("fs");
|
|
10750
10994
|
var import_node_path15 = require("path");
|
|
10751
|
-
function parseRunbook(
|
|
10752
|
-
let rest =
|
|
10995
|
+
function parseRunbook(text2, slug) {
|
|
10996
|
+
let rest = text2;
|
|
10753
10997
|
const meta = {};
|
|
10754
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
10998
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10755
10999
|
if (fm) {
|
|
10756
|
-
rest =
|
|
11000
|
+
rest = text2.slice(fm[0].length);
|
|
10757
11001
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10758
11002
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10759
11003
|
if (!pair) continue;
|
|
@@ -10834,6 +11078,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10834
11078
|
patch: {
|
|
10835
11079
|
title: r.title,
|
|
10836
11080
|
body: r.body,
|
|
11081
|
+
expectedVersion: found.version,
|
|
10837
11082
|
...r.summary ? { summary: r.summary } : {},
|
|
10838
11083
|
...r.tags ? { tags: r.tags } : {},
|
|
10839
11084
|
note: `imported from ${r.file}`
|
|
@@ -11225,9 +11470,9 @@ var import_node_child_process8 = require("child_process");
|
|
|
11225
11470
|
var import_node_fs20 = require("fs");
|
|
11226
11471
|
var import_node_os5 = require("os");
|
|
11227
11472
|
var import_node_path17 = require("path");
|
|
11228
|
-
var
|
|
11473
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
11229
11474
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11230
|
-
function resolveEditor(env =
|
|
11475
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
11231
11476
|
for (const name of EDITOR_ENV) {
|
|
11232
11477
|
const value2 = env[name];
|
|
11233
11478
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11241,8 +11486,8 @@ function defaultRun(command, path) {
|
|
|
11241
11486
|
return result.status ?? 0;
|
|
11242
11487
|
}
|
|
11243
11488
|
function editText(initial, slug, deps = {}) {
|
|
11244
|
-
const env = deps.env ??
|
|
11245
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11489
|
+
const env = deps.env ?? import_node_process13.default.env;
|
|
11490
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
|
|
11246
11491
|
const editor = resolveEditor(env);
|
|
11247
11492
|
if (!editor)
|
|
11248
11493
|
throw new Error(
|
|
@@ -11274,27 +11519,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11274
11519
|
const found = page.records[0];
|
|
11275
11520
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
11276
11521
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
11277
|
-
|
|
11522
|
+
const body = await editText(found.body, slug, deps);
|
|
11523
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
11278
11524
|
}
|
|
11279
11525
|
|
|
11280
11526
|
// src/whoami-command.ts
|
|
11527
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11528
|
+
function principalKind(value2, machine) {
|
|
11529
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11530
|
+
}
|
|
11531
|
+
function credentialKind(value2, machine, scopes) {
|
|
11532
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11533
|
+
if (machine) return "machine";
|
|
11534
|
+
if (scopes.length) return "device";
|
|
11535
|
+
return "unknown";
|
|
11536
|
+
}
|
|
11537
|
+
function managerOf(value2) {
|
|
11538
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11539
|
+
const row = value2;
|
|
11540
|
+
const principalId = text(row.principalId);
|
|
11541
|
+
if (!principalId) return null;
|
|
11542
|
+
return {
|
|
11543
|
+
principalId,
|
|
11544
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11545
|
+
handle: text(row.handle) ?? ""
|
|
11546
|
+
};
|
|
11547
|
+
}
|
|
11548
|
+
function unnamedPrincipal(kind) {
|
|
11549
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11550
|
+
if (kind === "service") return "Unnamed service";
|
|
11551
|
+
return "Unnamed member";
|
|
11552
|
+
}
|
|
11281
11553
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11282
11554
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11283
11555
|
headers: { authorization: `Bearer ${token}` }
|
|
11284
11556
|
});
|
|
11285
11557
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11286
11558
|
const body = await res.json();
|
|
11559
|
+
const developerId = text(body.developerId) ?? "";
|
|
11560
|
+
const machine = body.machine === true;
|
|
11561
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11562
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11563
|
+
const email = text(body.email);
|
|
11564
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11565
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11566
|
+
const handle = text(body.handle) ?? "";
|
|
11567
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11287
11568
|
return {
|
|
11288
|
-
developerId
|
|
11289
|
-
|
|
11569
|
+
developerId,
|
|
11570
|
+
principalId,
|
|
11571
|
+
principalKind: kind,
|
|
11572
|
+
displayName,
|
|
11573
|
+
handle,
|
|
11574
|
+
manager: managerOf(body.manager),
|
|
11575
|
+
credential: {
|
|
11576
|
+
id: text(credential2.id),
|
|
11577
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11578
|
+
},
|
|
11579
|
+
email,
|
|
11290
11580
|
admin: body.admin === true,
|
|
11291
|
-
machine
|
|
11292
|
-
scopes
|
|
11581
|
+
machine,
|
|
11582
|
+
scopes
|
|
11293
11583
|
};
|
|
11294
11584
|
}
|
|
11295
|
-
function
|
|
11296
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
11297
|
-
|
|
11585
|
+
function credentialLabel(identity) {
|
|
11586
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11587
|
+
if (identity.credential.kind === "device")
|
|
11588
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11589
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11590
|
+
return "unknown (legacy server)";
|
|
11591
|
+
}
|
|
11592
|
+
function namedPrincipal(identity) {
|
|
11593
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11594
|
+
}
|
|
11595
|
+
function namedManager(manager) {
|
|
11596
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11597
|
+
}
|
|
11598
|
+
function accountableOwner(identity) {
|
|
11599
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11600
|
+
return namedManager(identity.manager);
|
|
11601
|
+
}
|
|
11602
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11603
|
+
return namedPrincipal(identity);
|
|
11604
|
+
}
|
|
11605
|
+
return identity.email ?? "Unnamed member";
|
|
11298
11606
|
}
|
|
11299
11607
|
async function whoamiCommand(parsed, deps = {}) {
|
|
11300
11608
|
assertArgs(
|
|
@@ -11325,9 +11633,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
11325
11633
|
return;
|
|
11326
11634
|
}
|
|
11327
11635
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11328
|
-
out.log(`
|
|
11636
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11637
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11638
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11639
|
+
if (identity.principalKind === "agent")
|
|
11640
|
+
out.log(
|
|
11641
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11642
|
+
);
|
|
11643
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11644
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11329
11645
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11330
|
-
out.log(`credential: ${
|
|
11646
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11647
|
+
if (identity.credential.id)
|
|
11648
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11331
11649
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11332
11650
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11333
11651
|
if (!identity.admin) {
|
|
@@ -11476,14 +11794,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11476
11794
|
const name = requireSlug(slug, "edit");
|
|
11477
11795
|
const file = stringOpt(parsed.options.file);
|
|
11478
11796
|
const inline = stringOpt(parsed.options.body);
|
|
11479
|
-
const
|
|
11480
|
-
if (
|
|
11797
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11798
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11799
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11481
11800
|
return runbookEdit(
|
|
11482
11801
|
ctx,
|
|
11483
11802
|
name,
|
|
11484
11803
|
body,
|
|
11485
11804
|
stringOpt(parsed.options.note),
|
|
11486
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11805
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11806
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11487
11807
|
);
|
|
11488
11808
|
}
|
|
11489
11809
|
case "import": {
|