@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/index.cjs
CHANGED
|
@@ -250,10 +250,10 @@ function isManagedDevVar(line) {
|
|
|
250
250
|
const match = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
251
251
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
252
252
|
}
|
|
253
|
-
function writePrivateText(path,
|
|
253
|
+
function writePrivateText(path, text2) {
|
|
254
254
|
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
255
255
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
256
|
-
(0, import_node_fs.writeFileSync)(temporary,
|
|
256
|
+
(0, import_node_fs.writeFileSync)(temporary, text2, { mode: 384 });
|
|
257
257
|
(0, import_node_fs.chmodSync)(temporary, 384);
|
|
258
258
|
(0, import_node_fs.renameSync)(temporary, path);
|
|
259
259
|
}
|
|
@@ -560,6 +560,7 @@ function audienceBoundEnvToken(token, platform) {
|
|
|
560
560
|
}
|
|
561
561
|
var SCOPE_PURPOSE = {
|
|
562
562
|
"platform:status:read": "read the platform fleet health and deployment snapshot",
|
|
563
|
+
"platform:chat:credential:write": "rotate the built-in Discussion responder credential",
|
|
563
564
|
"app:config:read": "compare checked-in intent with an exact-id app Registry configuration",
|
|
564
565
|
"app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
|
|
565
566
|
"platform:runbook:write": "add or edit odla's operational runbooks",
|
|
@@ -619,6 +620,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
619
620
|
return token;
|
|
620
621
|
}
|
|
621
622
|
|
|
623
|
+
// src/principal-presentation.ts
|
|
624
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
625
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
626
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
627
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
628
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
629
|
+
}
|
|
630
|
+
|
|
622
631
|
// src/admin-ai-audit.ts
|
|
623
632
|
function adminAiAuditQuery(filters) {
|
|
624
633
|
if (filters.limit === void 0) return "";
|
|
@@ -648,17 +657,17 @@ async function readAdminAiAudit(request2) {
|
|
|
648
657
|
String(event.changeKind ?? ""),
|
|
649
658
|
String(event.purpose ?? event.provider ?? ""),
|
|
650
659
|
route2,
|
|
651
|
-
|
|
660
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
652
661
|
].join(" "));
|
|
653
662
|
}
|
|
654
663
|
}
|
|
655
664
|
async function responseBody(response2) {
|
|
656
|
-
const
|
|
657
|
-
if (!
|
|
665
|
+
const text2 = await response2.text();
|
|
666
|
+
if (!text2) return {};
|
|
658
667
|
try {
|
|
659
|
-
return JSON.parse(
|
|
668
|
+
return JSON.parse(text2);
|
|
660
669
|
} catch {
|
|
661
|
-
return { message:
|
|
670
|
+
return { message: text2.slice(0, 300) };
|
|
662
671
|
}
|
|
663
672
|
}
|
|
664
673
|
function apiError(status, body) {
|
|
@@ -739,7 +748,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
739
748
|
timestamp2(event.created_at),
|
|
740
749
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
741
750
|
String(event.run_id ?? ""),
|
|
742
|
-
|
|
751
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
743
752
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
744
753
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
745
754
|
String(input + output),
|
|
@@ -760,12 +769,12 @@ function timestamp2(value2) {
|
|
|
760
769
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
761
770
|
}
|
|
762
771
|
async function responseBody2(res) {
|
|
763
|
-
const
|
|
764
|
-
if (!
|
|
772
|
+
const text2 = await res.text();
|
|
773
|
+
if (!text2) return {};
|
|
765
774
|
try {
|
|
766
|
-
return JSON.parse(
|
|
775
|
+
return JSON.parse(text2);
|
|
767
776
|
} catch {
|
|
768
|
-
return { message:
|
|
777
|
+
return { message: text2.slice(0, 300) };
|
|
769
778
|
}
|
|
770
779
|
}
|
|
771
780
|
function apiError2(action2, status, body) {
|
|
@@ -941,12 +950,12 @@ function catalogModels(body) {
|
|
|
941
950
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
942
951
|
}
|
|
943
952
|
async function responseBody3(res) {
|
|
944
|
-
const
|
|
945
|
-
if (!
|
|
953
|
+
const text2 = await res.text();
|
|
954
|
+
if (!text2) return {};
|
|
946
955
|
try {
|
|
947
|
-
return JSON.parse(
|
|
956
|
+
return JSON.parse(text2);
|
|
948
957
|
} catch {
|
|
949
|
-
return { message:
|
|
958
|
+
return { message: text2.slice(0, 300) };
|
|
950
959
|
}
|
|
951
960
|
}
|
|
952
961
|
function apiError3(action2, status, body) {
|
|
@@ -1790,8 +1799,8 @@ async function appImport(options) {
|
|
|
1790
1799
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1791
1800
|
const doFetch = options.fetch ?? fetch;
|
|
1792
1801
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1793
|
-
const
|
|
1794
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1802
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1803
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1795
1804
|
if (format === "namespace-map" && options.ns) {
|
|
1796
1805
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1797
1806
|
}
|
|
@@ -1882,7 +1891,10 @@ function report(options, owners, headline) {
|
|
|
1882
1891
|
if (headline) out.log(headline);
|
|
1883
1892
|
out.log(`owners (${owners.length}):`);
|
|
1884
1893
|
for (const o of owners) {
|
|
1885
|
-
|
|
1894
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1895
|
+
out.log(
|
|
1896
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1897
|
+
);
|
|
1886
1898
|
}
|
|
1887
1899
|
}
|
|
1888
1900
|
async function ownersList(options) {
|
|
@@ -2799,9 +2811,9 @@ function canonicalValue(value2) {
|
|
|
2799
2811
|
}
|
|
2800
2812
|
if (Array.isArray(value2)) return value2.map(canonicalValue);
|
|
2801
2813
|
if (value2 && typeof value2 === "object") {
|
|
2802
|
-
const
|
|
2814
|
+
const record11 = value2;
|
|
2803
2815
|
return Object.fromEntries(
|
|
2804
|
-
Object.keys(
|
|
2816
|
+
Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, canonicalValue(record11[key])])
|
|
2805
2817
|
);
|
|
2806
2818
|
}
|
|
2807
2819
|
throw new TypeError("canonical JSON rejects unsupported values");
|
|
@@ -3627,22 +3639,26 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3627
3639
|
}
|
|
3628
3640
|
}
|
|
3629
3641
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3630
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3631
3642
|
next.push({
|
|
3632
3643
|
code: "review_destructive",
|
|
3633
|
-
command:
|
|
3644
|
+
command: studioSettingsUrl(reconciliation),
|
|
3634
3645
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3635
3646
|
});
|
|
3636
3647
|
}
|
|
3637
3648
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3638
3649
|
next.push({
|
|
3639
3650
|
code: "declare_or_accept_runtime_state",
|
|
3640
|
-
command:
|
|
3651
|
+
command: studioSettingsUrl(reconciliation),
|
|
3641
3652
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3642
3653
|
});
|
|
3643
3654
|
}
|
|
3644
3655
|
return next;
|
|
3645
3656
|
}
|
|
3657
|
+
function studioSettingsUrl(reconciliation) {
|
|
3658
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3659
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3660
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3661
|
+
}
|
|
3646
3662
|
function quoteArg2(value2) {
|
|
3647
3663
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3648
3664
|
}
|
|
@@ -3682,15 +3698,15 @@ function readWranglerConfig(path) {
|
|
|
3682
3698
|
return null;
|
|
3683
3699
|
}
|
|
3684
3700
|
}
|
|
3685
|
-
function stripJsonComments(
|
|
3701
|
+
function stripJsonComments(text2) {
|
|
3686
3702
|
let result = "";
|
|
3687
3703
|
let inString = false;
|
|
3688
|
-
for (let i = 0; i <
|
|
3689
|
-
const ch =
|
|
3704
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3705
|
+
const ch = text2[i];
|
|
3690
3706
|
if (inString) {
|
|
3691
3707
|
result += ch;
|
|
3692
3708
|
if (ch === "\\") {
|
|
3693
|
-
result +=
|
|
3709
|
+
result += text2[i + 1] ?? "";
|
|
3694
3710
|
i++;
|
|
3695
3711
|
} else if (ch === '"') {
|
|
3696
3712
|
inString = false;
|
|
@@ -3702,14 +3718,14 @@ function stripJsonComments(text) {
|
|
|
3702
3718
|
result += ch;
|
|
3703
3719
|
continue;
|
|
3704
3720
|
}
|
|
3705
|
-
if (ch === "/" &&
|
|
3706
|
-
while (i <
|
|
3721
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3722
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3707
3723
|
result += "\n";
|
|
3708
3724
|
continue;
|
|
3709
3725
|
}
|
|
3710
|
-
if (ch === "/" &&
|
|
3726
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3711
3727
|
i += 2;
|
|
3712
|
-
while (i <
|
|
3728
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3713
3729
|
i++;
|
|
3714
3730
|
continue;
|
|
3715
3731
|
}
|
|
@@ -3726,7 +3742,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3726
3742
|
}
|
|
3727
3743
|
}
|
|
3728
3744
|
function wranglerPutSecret(run, opts) {
|
|
3729
|
-
const args = [
|
|
3745
|
+
const args = [
|
|
3746
|
+
"wrangler",
|
|
3747
|
+
"secret",
|
|
3748
|
+
"put",
|
|
3749
|
+
opts.name,
|
|
3750
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3751
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3752
|
+
];
|
|
3730
3753
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3731
3754
|
}
|
|
3732
3755
|
|
|
@@ -4104,9 +4127,9 @@ function initProject(options) {
|
|
|
4104
4127
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4105
4128
|
out.log("updated .gitignore for local odla credentials");
|
|
4106
4129
|
}
|
|
4107
|
-
function writeIfMissing(path,
|
|
4130
|
+
function writeIfMissing(path, text2) {
|
|
4108
4131
|
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4109
|
-
(0, import_node_fs13.writeFileSync)(path,
|
|
4132
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
4110
4133
|
}
|
|
4111
4134
|
function configTemplate(input) {
|
|
4112
4135
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4314,13 +4337,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4314
4337
|
body: JSON.stringify({ value: value2 })
|
|
4315
4338
|
});
|
|
4316
4339
|
if (!res.ok) {
|
|
4317
|
-
const
|
|
4318
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4340
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4341
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
4319
4342
|
}
|
|
4320
4343
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4321
4344
|
}
|
|
4322
|
-
function scrubValue(
|
|
4323
|
-
return redactSecrets(
|
|
4345
|
+
function scrubValue(text2, value2) {
|
|
4346
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
4324
4347
|
}
|
|
4325
4348
|
async function resolveVaultWrite(options) {
|
|
4326
4349
|
const out = options.stdout ?? console;
|
|
@@ -5148,8 +5171,8 @@ async function runContainerAttempt(options) {
|
|
|
5148
5171
|
let stopped = false;
|
|
5149
5172
|
let exited = false;
|
|
5150
5173
|
child.stderr.setEncoding("utf8");
|
|
5151
|
-
child.stderr.on("data", (
|
|
5152
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5174
|
+
child.stderr.on("data", (text2) => {
|
|
5175
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
5153
5176
|
});
|
|
5154
5177
|
const stop = (reason) => {
|
|
5155
5178
|
if (stopped || exited) return;
|
|
@@ -5307,8 +5330,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
5307
5330
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
5308
5331
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
5309
5332
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
5310
|
-
const entries = inventory.flatMap((
|
|
5311
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
5333
|
+
const entries = inventory.flatMap((record11) => {
|
|
5334
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record11);
|
|
5312
5335
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
5313
5336
|
});
|
|
5314
5337
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -5556,8 +5579,8 @@ function normalize(value2) {
|
|
|
5556
5579
|
if (Array.isArray(value2)) return value2.map(normalize);
|
|
5557
5580
|
if (value2 instanceof Uint8Array) return { $bytes: [...value2] };
|
|
5558
5581
|
if (typeof value2 === "object") {
|
|
5559
|
-
const
|
|
5560
|
-
return Object.fromEntries(Object.keys(
|
|
5582
|
+
const record11 = value2;
|
|
5583
|
+
return Object.fromEntries(Object.keys(record11).filter((key) => record11[key] !== void 0).sort().map((key) => [key, normalize(record11[key])]));
|
|
5561
5584
|
}
|
|
5562
5585
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
5563
5586
|
}
|
|
@@ -5981,8 +6004,8 @@ function boundedInteger(value2, spec) {
|
|
|
5981
6004
|
}
|
|
5982
6005
|
function boundedNumber(value2, spec) {
|
|
5983
6006
|
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.");
|
|
5984
|
-
const
|
|
5985
|
-
if (/e/i.test(
|
|
6007
|
+
const text2 = String(value2);
|
|
6008
|
+
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.");
|
|
5986
6009
|
return value2;
|
|
5987
6010
|
}
|
|
5988
6011
|
function enumMember(value2, spec) {
|
|
@@ -6138,8 +6161,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6138
6161
|
return void 0;
|
|
6139
6162
|
}
|
|
6140
6163
|
function looksLikeDestination(value2) {
|
|
6141
|
-
const
|
|
6142
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6164
|
+
const text2 = value2.trim();
|
|
6165
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
6143
6166
|
}
|
|
6144
6167
|
|
|
6145
6168
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -6508,8 +6531,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6508
6531
|
});
|
|
6509
6532
|
let stderr = "";
|
|
6510
6533
|
child.stderr.setEncoding("utf8");
|
|
6511
|
-
child.stderr.on("data", (
|
|
6512
|
-
if (stderr.length < 4e3) stderr +=
|
|
6534
|
+
child.stderr.on("data", (text2) => {
|
|
6535
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6513
6536
|
});
|
|
6514
6537
|
child.once("error", reject);
|
|
6515
6538
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7680,10 +7703,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7680
7703
|
task: lease.task,
|
|
7681
7704
|
limits: this.options.limits,
|
|
7682
7705
|
signal: active.abort.signal,
|
|
7683
|
-
onStderr: (
|
|
7706
|
+
onStderr: (text2) => this.#event(command, {
|
|
7684
7707
|
type: "message",
|
|
7685
7708
|
actor: "system",
|
|
7686
|
-
body:
|
|
7709
|
+
body: text2.slice(0, 4e3)
|
|
7687
7710
|
}, active.conversationRefs),
|
|
7688
7711
|
onMessage: async (output) => {
|
|
7689
7712
|
if (output.type === "inference.request") {
|
|
@@ -8565,7 +8588,8 @@ Usage:
|
|
|
8565
8588
|
odla-ai doctor [--config odla.config.mjs]
|
|
8566
8589
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8567
8590
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8568
|
-
odla-ai operations
|
|
8591
|
+
odla-ai operations get <operation-id> [--json]
|
|
8592
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8569
8593
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8570
8594
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8571
8595
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8616,6 +8640,7 @@ Usage:
|
|
|
8616
8640
|
odla-ai context remove <name> --yes [--json]
|
|
8617
8641
|
odla-ai o11y status [--app <id>] [--context <name>] [--platform https://odla.ai] [--env prod] [--minutes 60] [--json]
|
|
8618
8642
|
odla-ai platform status [--context <name>] [--platform https://odla.ai] [--email <odla-account>] [--json]
|
|
8643
|
+
odla-ai platform chat-credentials rotate [--context <name>] [--email <odla-account>] [--wrangler-config <path>] [--expected-version <id>] [--json] --yes
|
|
8619
8644
|
odla-ai whoami [--context <name>] [--platform https://odla.ai] [--json]
|
|
8620
8645
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
8621
8646
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -8679,9 +8704,9 @@ Commands:
|
|
|
8679
8704
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8680
8705
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8681
8706
|
a question usually needs both.
|
|
8682
|
-
whoami Report
|
|
8683
|
-
|
|
8684
|
-
|
|
8707
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8708
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8709
|
+
A manager relationship never supplies authority by itself.
|
|
8685
8710
|
context Explain selected config, platform, app, environment, and
|
|
8686
8711
|
developer-token provenance without printing credentials or
|
|
8687
8712
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8801,6 +8826,58 @@ Safety:
|
|
|
8801
8826
|
`);
|
|
8802
8827
|
}
|
|
8803
8828
|
|
|
8829
|
+
// src/discuss-principals.ts
|
|
8830
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8831
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8832
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8833
|
+
}
|
|
8834
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8835
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8836
|
+
const profile = projection.principals?.[principalId];
|
|
8837
|
+
if (profile) {
|
|
8838
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8839
|
+
if (profile.kind === "agent") {
|
|
8840
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8841
|
+
}
|
|
8842
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8843
|
+
}
|
|
8844
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8845
|
+
if (legacyName) {
|
|
8846
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8847
|
+
}
|
|
8848
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8849
|
+
}
|
|
8850
|
+
|
|
8851
|
+
// src/discuss-read-render.ts
|
|
8852
|
+
function discussBodyWithRefs(post) {
|
|
8853
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8854
|
+
let out = "";
|
|
8855
|
+
let cursor = 0;
|
|
8856
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8857
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8858
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8859
|
+
cursor = ref.end;
|
|
8860
|
+
}
|
|
8861
|
+
return out + post.body.slice(cursor);
|
|
8862
|
+
}
|
|
8863
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8864
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8865
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8866
|
+
for (const post of posts) {
|
|
8867
|
+
const who = discussPrincipalLabel(
|
|
8868
|
+
post.authorId,
|
|
8869
|
+
post.authorKind,
|
|
8870
|
+
projection
|
|
8871
|
+
);
|
|
8872
|
+
ctx.out.log(`
|
|
8873
|
+
\u2014 ${who}`);
|
|
8874
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8875
|
+
for (const file of post.attachments ?? []) {
|
|
8876
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8877
|
+
}
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8804
8881
|
// src/discuss-actions.ts
|
|
8805
8882
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8806
8883
|
async function request(ctx, method, path, body) {
|
|
@@ -8819,17 +8896,6 @@ function emit(ctx, value2, human) {
|
|
|
8819
8896
|
else human();
|
|
8820
8897
|
}
|
|
8821
8898
|
var state = (topic) => topic.resolved ? "resolved" : "open";
|
|
8822
|
-
function bodyWithRefs(post) {
|
|
8823
|
-
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8824
|
-
let out = "";
|
|
8825
|
-
let cursor = 0;
|
|
8826
|
-
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8827
|
-
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8828
|
-
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8829
|
-
cursor = ref.end;
|
|
8830
|
-
}
|
|
8831
|
-
return out + post.body.slice(cursor);
|
|
8832
|
-
}
|
|
8833
8899
|
function content(parsed) {
|
|
8834
8900
|
const markup = stringOpt(parsed.options.markup);
|
|
8835
8901
|
if (markup) return { markup };
|
|
@@ -8884,11 +8950,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8884
8950
|
offset: requestedOffset ?? "0"
|
|
8885
8951
|
});
|
|
8886
8952
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8887
|
-
emit(
|
|
8953
|
+
emit(
|
|
8954
|
+
ctx,
|
|
8955
|
+
page,
|
|
8956
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8957
|
+
);
|
|
8888
8958
|
return;
|
|
8889
8959
|
}
|
|
8890
8960
|
for (let scan = 0; scan < 3; scan++) {
|
|
8891
8961
|
const posts = /* @__PURE__ */ new Map();
|
|
8962
|
+
const projection = {
|
|
8963
|
+
authors: {},
|
|
8964
|
+
principals: {}
|
|
8965
|
+
};
|
|
8892
8966
|
let topic = null;
|
|
8893
8967
|
let offset = 0;
|
|
8894
8968
|
for (; ; ) {
|
|
@@ -8899,6 +8973,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8899
8973
|
);
|
|
8900
8974
|
topic = page.topic;
|
|
8901
8975
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8976
|
+
mergeDiscussPrincipals(projection, page);
|
|
8902
8977
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8903
8978
|
if (!page.page?.hasMore) break;
|
|
8904
8979
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8911,24 +8986,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8911
8986
|
);
|
|
8912
8987
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8913
8988
|
if (ordered.length === expected) {
|
|
8914
|
-
const result = { topic, posts: ordered };
|
|
8915
|
-
emit(
|
|
8989
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8990
|
+
emit(
|
|
8991
|
+
ctx,
|
|
8992
|
+
result,
|
|
8993
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8994
|
+
);
|
|
8916
8995
|
return;
|
|
8917
8996
|
}
|
|
8918
8997
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8919
8998
|
}
|
|
8920
8999
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8921
9000
|
}
|
|
8922
|
-
function renderRead(ctx, topic, posts) {
|
|
8923
|
-
ctx.out.log(`${topic.subject} [${state(topic)}] ${topic.appId ?? ""}`);
|
|
8924
|
-
for (const post of posts) {
|
|
8925
|
-
const who = post.authorKind === "bot" ? `${post.authorId} (agent)` : post.authorId;
|
|
8926
|
-
ctx.out.log(`
|
|
8927
|
-
\u2014 ${who}`);
|
|
8928
|
-
ctx.out.log(bodyWithRefs(post));
|
|
8929
|
-
for (const file of post.attachments ?? []) ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8930
|
-
}
|
|
8931
|
-
}
|
|
8932
9001
|
async function discussPost(ctx, parsed) {
|
|
8933
9002
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8934
9003
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -9153,6 +9222,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9153
9222
|
found: true,
|
|
9154
9223
|
cursor,
|
|
9155
9224
|
events: matching,
|
|
9225
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9226
|
+
...page.principals ? { principals: page.principals } : {},
|
|
9156
9227
|
...posts && posts.length > 0 ? { posts } : {},
|
|
9157
9228
|
...topics && topics.length > 0 ? { topics } : {}
|
|
9158
9229
|
});
|
|
@@ -9185,7 +9256,11 @@ function report2(ctx, parsed, result) {
|
|
|
9185
9256
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9186
9257
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
9187
9258
|
for (const post of result.posts ?? []) {
|
|
9188
|
-
const who =
|
|
9259
|
+
const who = discussPrincipalLabel(
|
|
9260
|
+
post.authorId,
|
|
9261
|
+
post.authorKind,
|
|
9262
|
+
result
|
|
9263
|
+
);
|
|
9189
9264
|
ctx.out.log(`\u2014 ${who}
|
|
9190
9265
|
${post.body}`);
|
|
9191
9266
|
}
|
|
@@ -9330,8 +9405,8 @@ function collectFields(parsed, allowClear) {
|
|
|
9330
9405
|
if (allowClear) out[spec.key] = null;
|
|
9331
9406
|
continue;
|
|
9332
9407
|
}
|
|
9333
|
-
const
|
|
9334
|
-
out[spec.key] = spec.num ? Number(
|
|
9408
|
+
const text2 = stringOpt(value2);
|
|
9409
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
9335
9410
|
}
|
|
9336
9411
|
return out;
|
|
9337
9412
|
}
|
|
@@ -9398,8 +9473,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
9398
9473
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
9399
9474
|
}
|
|
9400
9475
|
async function pmGet(ctx, entity, id) {
|
|
9401
|
-
const { record:
|
|
9402
|
-
emit2(ctx,
|
|
9476
|
+
const { record: record11 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
9477
|
+
emit2(ctx, record11, () => printRecord(ctx, entity, record11));
|
|
9403
9478
|
}
|
|
9404
9479
|
async function pmSet(ctx, entity, id, parsed) {
|
|
9405
9480
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -9444,9 +9519,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9444
9519
|
]);
|
|
9445
9520
|
const handoff = {
|
|
9446
9521
|
appId,
|
|
9447
|
-
unmetGoals: goals.filter((
|
|
9448
|
-
activeTasks: tasks.filter((
|
|
9449
|
-
openBugs: bugs.filter((
|
|
9522
|
+
unmetGoals: goals.filter((record11) => record11.status !== "met"),
|
|
9523
|
+
activeTasks: tasks.filter((record11) => record11.column !== "done"),
|
|
9524
|
+
openBugs: bugs.filter((record11) => record11.status !== "fixed" && record11.status !== "wontfix")
|
|
9450
9525
|
};
|
|
9451
9526
|
const result = {
|
|
9452
9527
|
...handoff,
|
|
@@ -9465,10 +9540,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
9465
9540
|
]) {
|
|
9466
9541
|
ctx.out.log(`${label}:`);
|
|
9467
9542
|
if (!records.length) ctx.out.log("- (none)");
|
|
9468
|
-
else for (const
|
|
9543
|
+
else for (const record11 of records) printRecord(
|
|
9469
9544
|
ctx,
|
|
9470
9545
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
9471
|
-
|
|
9546
|
+
record11
|
|
9472
9547
|
);
|
|
9473
9548
|
}
|
|
9474
9549
|
});
|
|
@@ -9659,19 +9734,180 @@ function age(input) {
|
|
|
9659
9734
|
return `${Math.round(input / 6e4)}m`;
|
|
9660
9735
|
}
|
|
9661
9736
|
|
|
9737
|
+
// src/platform-chat-credential-command.ts
|
|
9738
|
+
var import_node_process10 = __toESM(require("process"), 1);
|
|
9739
|
+
async function rotatePlatformChatCredential(parsed, deps) {
|
|
9740
|
+
assertArgs(
|
|
9741
|
+
parsed,
|
|
9742
|
+
[
|
|
9743
|
+
"config",
|
|
9744
|
+
"context",
|
|
9745
|
+
"platform",
|
|
9746
|
+
"token",
|
|
9747
|
+
"email",
|
|
9748
|
+
"open",
|
|
9749
|
+
"json",
|
|
9750
|
+
"yes",
|
|
9751
|
+
"wrangler-config",
|
|
9752
|
+
"expected-version"
|
|
9753
|
+
],
|
|
9754
|
+
3
|
|
9755
|
+
);
|
|
9756
|
+
if (parsed.options.yes !== true) {
|
|
9757
|
+
throw new Error(
|
|
9758
|
+
"platform chat-credentials rotate changes the production chat secret; pass --yes"
|
|
9759
|
+
);
|
|
9760
|
+
}
|
|
9761
|
+
const context = await resolveOperatorContext(parsed, {
|
|
9762
|
+
allowMissingConfig: true
|
|
9763
|
+
});
|
|
9764
|
+
const platform = context.platform.value;
|
|
9765
|
+
const doFetch = deps.fetch ?? fetch;
|
|
9766
|
+
const out = deps.stdout ?? console;
|
|
9767
|
+
const run = deps.runner ?? defaultRunner;
|
|
9768
|
+
const cwd = import_node_process10.default.cwd();
|
|
9769
|
+
const wranglerConfig = stringOpt(parsed.options["wrangler-config"]) ?? "packages/chat-agent/wrangler.jsonc";
|
|
9770
|
+
if (!await wranglerLoggedIn(run, cwd)) {
|
|
9771
|
+
throw new Error(
|
|
9772
|
+
'Wrangler is not authenticated; run "npx wrangler login" and retry'
|
|
9773
|
+
);
|
|
9774
|
+
}
|
|
9775
|
+
const priorHealth = await doFetch(`${platform}/health/services/chat`);
|
|
9776
|
+
const priorVersion = priorHealth.headers.get("x-odla-worker-version-id");
|
|
9777
|
+
await priorHealth.body?.cancel().catch(() => {
|
|
9778
|
+
});
|
|
9779
|
+
if (!priorVersion) {
|
|
9780
|
+
throw new Error(
|
|
9781
|
+
"chat health did not identify its current Worker version; refusing to rotate"
|
|
9782
|
+
);
|
|
9783
|
+
}
|
|
9784
|
+
const expectedVersion = stringOpt(parsed.options["expected-version"]);
|
|
9785
|
+
if (expectedVersion && priorVersion !== expectedVersion) {
|
|
9786
|
+
throw new Error(
|
|
9787
|
+
`chat health answered from version ${priorVersion}; expected ${expectedVersion}`
|
|
9788
|
+
);
|
|
9789
|
+
}
|
|
9790
|
+
const token = await resolveAdminPlatformToken({
|
|
9791
|
+
platform,
|
|
9792
|
+
scope: "platform:chat:credential:write",
|
|
9793
|
+
token: stringOpt(parsed.options.token),
|
|
9794
|
+
tokenFile: context.credentials.scopedTokenFile,
|
|
9795
|
+
email: stringOpt(parsed.options.email),
|
|
9796
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
9797
|
+
fetch: doFetch,
|
|
9798
|
+
stdout: out,
|
|
9799
|
+
openApprovalUrl: deps.openUrl,
|
|
9800
|
+
label: "odla CLI (rotate built-in Discussion responder)"
|
|
9801
|
+
});
|
|
9802
|
+
const mintedResponse = await doFetch(
|
|
9803
|
+
`${platform}/registry/platform/chat-credentials/rotate`,
|
|
9804
|
+
{
|
|
9805
|
+
method: "POST",
|
|
9806
|
+
headers: { authorization: `Bearer ${token}` }
|
|
9807
|
+
}
|
|
9808
|
+
);
|
|
9809
|
+
const minted = await mintedResponse.json().catch(() => null);
|
|
9810
|
+
if (!mintedResponse.ok) {
|
|
9811
|
+
throw new Error(
|
|
9812
|
+
`mint Discussion credential failed (HTTP ${mintedResponse.status}): ${apiMessage(minted)}`
|
|
9813
|
+
);
|
|
9814
|
+
}
|
|
9815
|
+
if (!isPlatformChatCredential(minted)) {
|
|
9816
|
+
throw new Error(
|
|
9817
|
+
"platform returned an invalid odla.platform-chat-credential/v1 envelope"
|
|
9818
|
+
);
|
|
9819
|
+
}
|
|
9820
|
+
const put = await wranglerPutSecret(run, {
|
|
9821
|
+
name: "ODLA_BOT_TOKENS",
|
|
9822
|
+
value: JSON.stringify({
|
|
9823
|
+
[minted.appId]: { [minted.principalId]: minted.key }
|
|
9824
|
+
}),
|
|
9825
|
+
configPath: wranglerConfig,
|
|
9826
|
+
cwd
|
|
9827
|
+
});
|
|
9828
|
+
if (put.code !== 0) {
|
|
9829
|
+
throw new Error(
|
|
9830
|
+
"Wrangler could not replace ODLA_BOT_TOKENS; the new scoped key was not activated"
|
|
9831
|
+
);
|
|
9832
|
+
}
|
|
9833
|
+
const version = await waitForRotatedChatHealth({
|
|
9834
|
+
fetch: doFetch,
|
|
9835
|
+
platform,
|
|
9836
|
+
priorVersion,
|
|
9837
|
+
wait: deps.pollWait
|
|
9838
|
+
});
|
|
9839
|
+
const result = {
|
|
9840
|
+
schemaVersion: "odla.platform-chat-credential-rotation/v1",
|
|
9841
|
+
appId: minted.appId,
|
|
9842
|
+
appIncarnation: minted.appIncarnation,
|
|
9843
|
+
principalId: minted.principalId,
|
|
9844
|
+
health: { ok: true, service: "odla-chat-agent", version }
|
|
9845
|
+
};
|
|
9846
|
+
if (parsed.options.json === true) {
|
|
9847
|
+
out.log(JSON.stringify(result, null, 2));
|
|
9848
|
+
} else {
|
|
9849
|
+
out.log(
|
|
9850
|
+
`rotated ${result.appId} ${result.principalId} ${result.health.version}`
|
|
9851
|
+
);
|
|
9852
|
+
}
|
|
9853
|
+
}
|
|
9854
|
+
async function waitForRotatedChatHealth(opts) {
|
|
9855
|
+
const wait2 = opts.wait ?? ((milliseconds) => new Promise((resolve12) => setTimeout(resolve12, milliseconds)));
|
|
9856
|
+
let lastStatus = 0;
|
|
9857
|
+
let lastVersion = null;
|
|
9858
|
+
let lastError = "private_service_unready";
|
|
9859
|
+
for (let attempt = 0; attempt < 60; attempt++) {
|
|
9860
|
+
const response2 = await opts.fetch(
|
|
9861
|
+
`${opts.platform}/health/services/chat`
|
|
9862
|
+
);
|
|
9863
|
+
const body = await response2.json().catch(() => null);
|
|
9864
|
+
const version = response2.headers.get("x-odla-worker-version-id");
|
|
9865
|
+
if (response2.ok && record7(body) && body.ok === true && body.service === "odla-chat-agent" && version && version !== opts.priorVersion) {
|
|
9866
|
+
return version;
|
|
9867
|
+
}
|
|
9868
|
+
lastStatus = response2.status;
|
|
9869
|
+
lastVersion = version;
|
|
9870
|
+
lastError = record7(body) && typeof body.error === "string" ? body.error : "private_service_unready";
|
|
9871
|
+
if (attempt < 59) await wait2(5e3);
|
|
9872
|
+
}
|
|
9873
|
+
throw new Error(
|
|
9874
|
+
`chat health did not converge on the rotated Worker deployment (HTTP ${lastStatus}, version ${lastVersion ?? "unknown"}, ${lastError})`
|
|
9875
|
+
);
|
|
9876
|
+
}
|
|
9877
|
+
function isPlatformChatCredential(value2) {
|
|
9878
|
+
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_");
|
|
9879
|
+
}
|
|
9880
|
+
function apiMessage(value2) {
|
|
9881
|
+
if (!record7(value2)) return "request failed";
|
|
9882
|
+
const error = record7(value2.error) ? value2.error : value2;
|
|
9883
|
+
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9884
|
+
}
|
|
9885
|
+
function record7(value2) {
|
|
9886
|
+
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9662
9889
|
// src/platform-command.ts
|
|
9663
9890
|
async function platformCommand(parsed, deps = {}) {
|
|
9891
|
+
const action2 = parsed.positionals[1];
|
|
9892
|
+
if (action2 === "status") {
|
|
9893
|
+
return platformStatus(parsed, deps);
|
|
9894
|
+
}
|
|
9895
|
+
if (action2 === "chat-credentials" && parsed.positionals[2] === "rotate") {
|
|
9896
|
+
return rotatePlatformChatCredential(parsed, deps);
|
|
9897
|
+
}
|
|
9898
|
+
throw new Error(
|
|
9899
|
+
`unknown platform action "${[
|
|
9900
|
+
action2,
|
|
9901
|
+
parsed.positionals[2]
|
|
9902
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9903
|
+
);
|
|
9904
|
+
}
|
|
9905
|
+
async function platformStatus(parsed, deps) {
|
|
9664
9906
|
assertArgs(
|
|
9665
9907
|
parsed,
|
|
9666
9908
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9667
9909
|
2
|
|
9668
9910
|
);
|
|
9669
|
-
const action2 = parsed.positionals[1];
|
|
9670
|
-
if (action2 !== "status") {
|
|
9671
|
-
throw new Error(
|
|
9672
|
-
`unknown platform action "${action2 ?? ""}". Try "odla-ai platform status --json".`
|
|
9673
|
-
);
|
|
9674
|
-
}
|
|
9675
9911
|
const context = await resolveOperatorContext(parsed, {
|
|
9676
9912
|
allowMissingConfig: true
|
|
9677
9913
|
});
|
|
@@ -9696,7 +9932,7 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9696
9932
|
const body = await response2.json().catch(() => null);
|
|
9697
9933
|
if (!response2.ok) {
|
|
9698
9934
|
throw new Error(
|
|
9699
|
-
`read platform status failed (HTTP ${response2.status}): ${
|
|
9935
|
+
`read platform status failed (HTTP ${response2.status}): ${apiMessage2(body)}`
|
|
9700
9936
|
);
|
|
9701
9937
|
}
|
|
9702
9938
|
if (!isPlatformStatus(body)) {
|
|
@@ -9709,17 +9945,17 @@ async function platformCommand(parsed, deps = {}) {
|
|
|
9709
9945
|
}
|
|
9710
9946
|
}
|
|
9711
9947
|
function isPlatformStatus(value2) {
|
|
9712
|
-
if (!
|
|
9713
|
-
if (!
|
|
9714
|
-
if (!
|
|
9948
|
+
if (!record8(value2) || value2.schemaVersion !== "odla.platform-status/v1") return false;
|
|
9949
|
+
if (!record8(value2.verdict) || !Array.isArray(value2.verdict.reasons)) return false;
|
|
9950
|
+
if (!record8(value2.catalog) || !record8(value2.summary)) return false;
|
|
9715
9951
|
return Array.isArray(value2.services) && Array.isArray(value2.nextActions);
|
|
9716
9952
|
}
|
|
9717
|
-
function
|
|
9718
|
-
if (!
|
|
9719
|
-
const error =
|
|
9953
|
+
function apiMessage2(value2) {
|
|
9954
|
+
if (!record8(value2)) return "request failed";
|
|
9955
|
+
const error = record8(value2.error) ? value2.error : value2;
|
|
9720
9956
|
return typeof error.message === "string" ? error.message : typeof error.code === "string" ? error.code : "request failed";
|
|
9721
9957
|
}
|
|
9722
|
-
function
|
|
9958
|
+
function record8(value2) {
|
|
9723
9959
|
return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
|
|
9724
9960
|
}
|
|
9725
9961
|
|
|
@@ -9760,7 +9996,7 @@ function statusVerdict(reads) {
|
|
|
9760
9996
|
severity: "degraded"
|
|
9761
9997
|
});
|
|
9762
9998
|
}
|
|
9763
|
-
const performance =
|
|
9999
|
+
const performance = record9(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
9764
10000
|
if (performance?.status === "unavailable") {
|
|
9765
10001
|
reasons.push({
|
|
9766
10002
|
source: "liveSync",
|
|
@@ -9841,7 +10077,7 @@ function statusVerdict(reads) {
|
|
|
9841
10077
|
reasons
|
|
9842
10078
|
};
|
|
9843
10079
|
}
|
|
9844
|
-
function
|
|
10080
|
+
function record9(value2) {
|
|
9845
10081
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9846
10082
|
}
|
|
9847
10083
|
function numeric2(value2) {
|
|
@@ -9869,7 +10105,7 @@ function printO11yStatus(status, out) {
|
|
|
9869
10105
|
out.log(
|
|
9870
10106
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
9871
10107
|
);
|
|
9872
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
10108
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record10) : [];
|
|
9873
10109
|
const requests = routes.reduce(
|
|
9874
10110
|
(total, row) => total + numeric3(row.requests),
|
|
9875
10111
|
0
|
|
@@ -9881,39 +10117,39 @@ function printO11yStatus(status, out) {
|
|
|
9881
10117
|
out.log(
|
|
9882
10118
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
9883
10119
|
);
|
|
9884
|
-
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(
|
|
10120
|
+
const versions = Array.isArray(status.applicationVersions.body.rows) ? status.applicationVersions.body.rows.filter(record10) : [];
|
|
9885
10121
|
out.log(
|
|
9886
10122
|
`application-versions ${status.applicationVersions.httpStatus} ${versions.length ? versions.slice(0, 5).map(
|
|
9887
10123
|
(row) => `${String(row.value || "(unattributed)")}:${numeric3(row.requests)}`
|
|
9888
10124
|
).join(", ") : "none observed"}`
|
|
9889
10125
|
);
|
|
9890
10126
|
out.log(liveSyncLine(status.liveSync));
|
|
9891
|
-
const canaryDurations =
|
|
10127
|
+
const canaryDurations = record10(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
9892
10128
|
out.log(
|
|
9893
10129
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
9894
10130
|
);
|
|
9895
|
-
const collectorIngest =
|
|
9896
|
-
const collectorStorage =
|
|
10131
|
+
const collectorIngest = record10(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
10132
|
+
const collectorStorage = record10(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
9897
10133
|
out.log(
|
|
9898
10134
|
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
9899
10135
|
);
|
|
9900
|
-
const providerMetrics =
|
|
9901
|
-
const providerCapacity =
|
|
9902
|
-
const workerMemory =
|
|
10136
|
+
const providerMetrics = record10(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
10137
|
+
const providerCapacity = record10(status.provider.body.capacity) ? status.provider.body.capacity : {};
|
|
10138
|
+
const workerMemory = record10(providerCapacity.memory) ? providerCapacity.memory : {};
|
|
9903
10139
|
out.log(
|
|
9904
10140
|
`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`
|
|
9905
10141
|
);
|
|
9906
10142
|
for (const line of providerCapacityLines(status.providerCapacity)) {
|
|
9907
10143
|
out.log(line);
|
|
9908
10144
|
}
|
|
9909
|
-
const coverage =
|
|
9910
|
-
const coverageCounts =
|
|
9911
|
-
const coverageBudget =
|
|
10145
|
+
const coverage = record10(status.providerReconciliation.body.comparison) ? status.providerReconciliation.body.comparison : {};
|
|
10146
|
+
const coverageCounts = record10(status.providerReconciliation.body.counts) ? status.providerReconciliation.body.counts : {};
|
|
10147
|
+
const coverageBudget = record10(status.providerReconciliation.body.budget) ? status.providerReconciliation.body.budget : {};
|
|
9912
10148
|
out.log(
|
|
9913
10149
|
`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`
|
|
9914
10150
|
);
|
|
9915
10151
|
const providerPoints = Array.isArray(status.providerHistory.body.points) ? status.providerHistory.body.points.length : 0;
|
|
9916
|
-
const providerFreshness =
|
|
10152
|
+
const providerFreshness = record10(status.providerHistory.body.freshness) ? status.providerHistory.body.freshness : {};
|
|
9917
10153
|
out.log(
|
|
9918
10154
|
`cloudflare-history ${status.providerHistory.httpStatus} ${String(status.providerHistory.body.status ?? status.providerHistory.body.error ?? "unavailable")} ${providerPoints} snapshots ${optionalAge(providerFreshness.ageMs)} old`
|
|
9919
10155
|
);
|
|
@@ -9922,17 +10158,17 @@ function printO11yStatus(status, out) {
|
|
|
9922
10158
|
);
|
|
9923
10159
|
}
|
|
9924
10160
|
function providerCapacityLines(read3) {
|
|
9925
|
-
const resources =
|
|
9926
|
-
const durableObjects =
|
|
9927
|
-
const periodic =
|
|
9928
|
-
const storage =
|
|
9929
|
-
const d1 =
|
|
9930
|
-
const d1Activity =
|
|
9931
|
-
const d1Storage =
|
|
9932
|
-
const d1Latency =
|
|
9933
|
-
const r2 =
|
|
9934
|
-
const r2Operations =
|
|
9935
|
-
const r2Storage =
|
|
10161
|
+
const resources = record10(read3.body.resources) ? read3.body.resources : {};
|
|
10162
|
+
const durableObjects = record10(resources.durableObjects) ? resources.durableObjects : {};
|
|
10163
|
+
const periodic = record10(durableObjects.periodic) ? durableObjects.periodic : {};
|
|
10164
|
+
const storage = record10(durableObjects.sqliteStorage) ? durableObjects.sqliteStorage : {};
|
|
10165
|
+
const d1 = record10(resources.d1) ? resources.d1 : {};
|
|
10166
|
+
const d1Activity = record10(d1.activity) ? d1.activity : {};
|
|
10167
|
+
const d1Storage = record10(d1.storage) ? d1.storage : {};
|
|
10168
|
+
const d1Latency = record10(d1Activity.latency) ? d1Activity.latency : {};
|
|
10169
|
+
const r2 = record10(resources.r2) ? resources.r2 : {};
|
|
10170
|
+
const r2Operations = record10(r2.operations) ? r2.operations : {};
|
|
10171
|
+
const r2Storage = record10(r2.storage) ? r2.storage : {};
|
|
9936
10172
|
const status = String(
|
|
9937
10173
|
read3.body.status ?? read3.body.error ?? "unavailable"
|
|
9938
10174
|
);
|
|
@@ -9943,11 +10179,11 @@ function providerCapacityLines(read3) {
|
|
|
9943
10179
|
];
|
|
9944
10180
|
}
|
|
9945
10181
|
function liveSyncLine(read3) {
|
|
9946
|
-
const performance =
|
|
9947
|
-
const commitToSend =
|
|
10182
|
+
const performance = record10(read3.body.performance) ? read3.body.performance : {};
|
|
10183
|
+
const commitToSend = record10(performance.commitToSend) ? performance.commitToSend : {};
|
|
9948
10184
|
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`;
|
|
9949
10185
|
}
|
|
9950
|
-
function
|
|
10186
|
+
function record10(value2) {
|
|
9951
10187
|
return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
|
|
9952
10188
|
}
|
|
9953
10189
|
function numeric3(value2) {
|
|
@@ -10116,14 +10352,14 @@ function statusMinutes(value2) {
|
|
|
10116
10352
|
}
|
|
10117
10353
|
async function read2(url, headers, doFetch) {
|
|
10118
10354
|
const response2 = await doFetch(url, { headers });
|
|
10119
|
-
const
|
|
10355
|
+
const text2 = await response2.text();
|
|
10120
10356
|
let body = {};
|
|
10121
|
-
if (
|
|
10357
|
+
if (text2) {
|
|
10122
10358
|
try {
|
|
10123
|
-
const value2 = JSON.parse(
|
|
10359
|
+
const value2 = JSON.parse(text2);
|
|
10124
10360
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
10125
10361
|
} catch {
|
|
10126
|
-
body = { message:
|
|
10362
|
+
body = { message: text2.slice(0, 300) };
|
|
10127
10363
|
}
|
|
10128
10364
|
}
|
|
10129
10365
|
return { httpStatus: response2.status, body };
|
|
@@ -10132,7 +10368,7 @@ async function read2(url, headers, doFetch) {
|
|
|
10132
10368
|
// src/provision.ts
|
|
10133
10369
|
var import_apps12 = require("@odla-ai/apps");
|
|
10134
10370
|
var import_ai3 = require("@odla-ai/ai");
|
|
10135
|
-
var
|
|
10371
|
+
var import_node_process11 = __toESM(require("process"), 1);
|
|
10136
10372
|
|
|
10137
10373
|
// src/integration-provision.ts
|
|
10138
10374
|
var import_db3 = require("@odla-ai/db");
|
|
@@ -10439,7 +10675,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10439
10675
|
await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
|
|
10440
10676
|
}
|
|
10441
10677
|
if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
|
|
10442
|
-
const key =
|
|
10678
|
+
const key = import_node_process11.default.env[cfg.ai.keyEnv];
|
|
10443
10679
|
if (key) {
|
|
10444
10680
|
const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
|
|
10445
10681
|
await (0, import_ai3.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
|
|
@@ -10481,7 +10717,7 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
|
|
|
10481
10717
|
|
|
10482
10718
|
// src/record.ts
|
|
10483
10719
|
var import_node_fs16 = require("fs");
|
|
10484
|
-
var
|
|
10720
|
+
var import_node_process12 = __toESM(require("process"), 1);
|
|
10485
10721
|
|
|
10486
10722
|
// src/surface.ts
|
|
10487
10723
|
var PM_ACTIONS = {
|
|
@@ -10548,7 +10784,10 @@ var COMMAND_SURFACE = {
|
|
|
10548
10784
|
init: {},
|
|
10549
10785
|
o11y: { status: {} },
|
|
10550
10786
|
operations: { get: {}, wait: {} },
|
|
10551
|
-
platform: {
|
|
10787
|
+
platform: {
|
|
10788
|
+
status: {},
|
|
10789
|
+
"chat-credentials": { rotate: {} }
|
|
10790
|
+
},
|
|
10552
10791
|
pm: {
|
|
10553
10792
|
...PM_ENTITIES,
|
|
10554
10793
|
handoff: {}
|
|
@@ -10636,7 +10875,7 @@ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
|
10636
10875
|
|
|
10637
10876
|
// src/record.ts
|
|
10638
10877
|
function recordInvocation(parsed) {
|
|
10639
|
-
const file =
|
|
10878
|
+
const file = import_node_process12.default.env.ODLA_CLI_RECORD;
|
|
10640
10879
|
if (!file) return;
|
|
10641
10880
|
try {
|
|
10642
10881
|
const entry = {
|
|
@@ -10766,12 +11005,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10766
11005
|
});
|
|
10767
11006
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10768
11007
|
}
|
|
10769
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
11008
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10770
11009
|
const runbook = await bySlug(ctx, slug);
|
|
10771
11010
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10772
11011
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10773
11012
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10774
|
-
patch: {
|
|
11013
|
+
patch: {
|
|
11014
|
+
body,
|
|
11015
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
11016
|
+
...note ? { note } : {},
|
|
11017
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
11018
|
+
}
|
|
10775
11019
|
});
|
|
10776
11020
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10777
11021
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10825,12 +11069,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
10825
11069
|
// src/runbook-import.ts
|
|
10826
11070
|
var import_node_fs18 = require("fs");
|
|
10827
11071
|
var import_node_path15 = require("path");
|
|
10828
|
-
function parseRunbook(
|
|
10829
|
-
let rest =
|
|
11072
|
+
function parseRunbook(text2, slug) {
|
|
11073
|
+
let rest = text2;
|
|
10830
11074
|
const meta = {};
|
|
10831
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
11075
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10832
11076
|
if (fm) {
|
|
10833
|
-
rest =
|
|
11077
|
+
rest = text2.slice(fm[0].length);
|
|
10834
11078
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10835
11079
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10836
11080
|
if (!pair) continue;
|
|
@@ -10911,6 +11155,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10911
11155
|
patch: {
|
|
10912
11156
|
title: r.title,
|
|
10913
11157
|
body: r.body,
|
|
11158
|
+
expectedVersion: found.version,
|
|
10914
11159
|
...r.summary ? { summary: r.summary } : {},
|
|
10915
11160
|
...r.tags ? { tags: r.tags } : {},
|
|
10916
11161
|
note: `imported from ${r.file}`
|
|
@@ -11302,9 +11547,9 @@ var import_node_child_process8 = require("child_process");
|
|
|
11302
11547
|
var import_node_fs20 = require("fs");
|
|
11303
11548
|
var import_node_os5 = require("os");
|
|
11304
11549
|
var import_node_path17 = require("path");
|
|
11305
|
-
var
|
|
11550
|
+
var import_node_process13 = __toESM(require("process"), 1);
|
|
11306
11551
|
var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
|
|
11307
|
-
function resolveEditor(env =
|
|
11552
|
+
function resolveEditor(env = import_node_process13.default.env) {
|
|
11308
11553
|
for (const name of EDITOR_ENV) {
|
|
11309
11554
|
const value2 = env[name];
|
|
11310
11555
|
if (value2 && value2.trim()) return value2.trim();
|
|
@@ -11318,8 +11563,8 @@ function defaultRun(command, path) {
|
|
|
11318
11563
|
return result.status ?? 0;
|
|
11319
11564
|
}
|
|
11320
11565
|
function editText(initial, slug, deps = {}) {
|
|
11321
|
-
const env = deps.env ??
|
|
11322
|
-
const interactive = deps.interactive ?? (() => Boolean(
|
|
11566
|
+
const env = deps.env ?? import_node_process13.default.env;
|
|
11567
|
+
const interactive = deps.interactive ?? (() => Boolean(import_node_process13.default.stdin.isTTY));
|
|
11323
11568
|
const editor = resolveEditor(env);
|
|
11324
11569
|
if (!editor)
|
|
11325
11570
|
throw new Error(
|
|
@@ -11351,27 +11596,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11351
11596
|
const found = page.records[0];
|
|
11352
11597
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
11353
11598
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
11354
|
-
|
|
11599
|
+
const body = await editText(found.body, slug, deps);
|
|
11600
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
11355
11601
|
}
|
|
11356
11602
|
|
|
11357
11603
|
// src/whoami-command.ts
|
|
11604
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11605
|
+
function principalKind(value2, machine) {
|
|
11606
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11607
|
+
}
|
|
11608
|
+
function credentialKind(value2, machine, scopes) {
|
|
11609
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11610
|
+
if (machine) return "machine";
|
|
11611
|
+
if (scopes.length) return "device";
|
|
11612
|
+
return "unknown";
|
|
11613
|
+
}
|
|
11614
|
+
function managerOf(value2) {
|
|
11615
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11616
|
+
const row = value2;
|
|
11617
|
+
const principalId = text(row.principalId);
|
|
11618
|
+
if (!principalId) return null;
|
|
11619
|
+
return {
|
|
11620
|
+
principalId,
|
|
11621
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11622
|
+
handle: text(row.handle) ?? ""
|
|
11623
|
+
};
|
|
11624
|
+
}
|
|
11625
|
+
function unnamedPrincipal(kind) {
|
|
11626
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11627
|
+
if (kind === "service") return "Unnamed service";
|
|
11628
|
+
return "Unnamed member";
|
|
11629
|
+
}
|
|
11358
11630
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11359
11631
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11360
11632
|
headers: { authorization: `Bearer ${token}` }
|
|
11361
11633
|
});
|
|
11362
11634
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11363
11635
|
const body = await res.json();
|
|
11636
|
+
const developerId = text(body.developerId) ?? "";
|
|
11637
|
+
const machine = body.machine === true;
|
|
11638
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11639
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11640
|
+
const email = text(body.email);
|
|
11641
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11642
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11643
|
+
const handle = text(body.handle) ?? "";
|
|
11644
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11364
11645
|
return {
|
|
11365
|
-
developerId
|
|
11366
|
-
|
|
11646
|
+
developerId,
|
|
11647
|
+
principalId,
|
|
11648
|
+
principalKind: kind,
|
|
11649
|
+
displayName,
|
|
11650
|
+
handle,
|
|
11651
|
+
manager: managerOf(body.manager),
|
|
11652
|
+
credential: {
|
|
11653
|
+
id: text(credential2.id),
|
|
11654
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11655
|
+
},
|
|
11656
|
+
email,
|
|
11367
11657
|
admin: body.admin === true,
|
|
11368
|
-
machine
|
|
11369
|
-
scopes
|
|
11658
|
+
machine,
|
|
11659
|
+
scopes
|
|
11370
11660
|
};
|
|
11371
11661
|
}
|
|
11372
|
-
function
|
|
11373
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
11374
|
-
|
|
11662
|
+
function credentialLabel(identity) {
|
|
11663
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11664
|
+
if (identity.credential.kind === "device")
|
|
11665
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11666
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11667
|
+
return "unknown (legacy server)";
|
|
11668
|
+
}
|
|
11669
|
+
function namedPrincipal(identity) {
|
|
11670
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11671
|
+
}
|
|
11672
|
+
function namedManager(manager) {
|
|
11673
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11674
|
+
}
|
|
11675
|
+
function accountableOwner(identity) {
|
|
11676
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11677
|
+
return namedManager(identity.manager);
|
|
11678
|
+
}
|
|
11679
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11680
|
+
return namedPrincipal(identity);
|
|
11681
|
+
}
|
|
11682
|
+
return identity.email ?? "Unnamed member";
|
|
11375
11683
|
}
|
|
11376
11684
|
async function whoamiCommand(parsed, deps = {}) {
|
|
11377
11685
|
assertArgs(
|
|
@@ -11402,9 +11710,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
11402
11710
|
return;
|
|
11403
11711
|
}
|
|
11404
11712
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11405
|
-
out.log(`
|
|
11713
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11714
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11715
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11716
|
+
if (identity.principalKind === "agent")
|
|
11717
|
+
out.log(
|
|
11718
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11719
|
+
);
|
|
11720
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11721
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11406
11722
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11407
|
-
out.log(`credential: ${
|
|
11723
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11724
|
+
if (identity.credential.id)
|
|
11725
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11408
11726
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11409
11727
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11410
11728
|
if (!identity.admin) {
|
|
@@ -11553,14 +11871,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11553
11871
|
const name = requireSlug(slug, "edit");
|
|
11554
11872
|
const file = stringOpt(parsed.options.file);
|
|
11555
11873
|
const inline = stringOpt(parsed.options.body);
|
|
11556
|
-
const
|
|
11557
|
-
if (
|
|
11874
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11875
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11876
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11558
11877
|
return runbookEdit(
|
|
11559
11878
|
ctx,
|
|
11560
11879
|
name,
|
|
11561
11880
|
body,
|
|
11562
11881
|
stringOpt(parsed.options.note),
|
|
11563
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11882
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11883
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11564
11884
|
);
|
|
11565
11885
|
}
|
|
11566
11886
|
case "import": {
|