@odla-ai/cli 0.25.23 → 0.26.2
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 +13 -2
- package/dist/bin.cjs +270 -108
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-JJ42NV7X.js → chunk-MBZXWPZP.js} +272 -110
- package/dist/chunk-MBZXWPZP.js.map +1 -0
- package/dist/index.cjs +270 -108
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -11
- package/dist/index.d.ts +13 -11
- 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
|
}
|
|
@@ -551,6 +551,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
551
551
|
return token;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
+
// src/principal-presentation.ts
|
|
555
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
556
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
557
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
558
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
559
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
560
|
+
}
|
|
561
|
+
|
|
554
562
|
// src/admin-ai-audit.ts
|
|
555
563
|
function adminAiAuditQuery(filters) {
|
|
556
564
|
if (filters.limit === void 0) return "";
|
|
@@ -580,17 +588,17 @@ async function readAdminAiAudit(request2) {
|
|
|
580
588
|
String(event.changeKind ?? ""),
|
|
581
589
|
String(event.purpose ?? event.provider ?? ""),
|
|
582
590
|
route2,
|
|
583
|
-
|
|
591
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
584
592
|
].join(" "));
|
|
585
593
|
}
|
|
586
594
|
}
|
|
587
595
|
async function responseBody(response2) {
|
|
588
|
-
const
|
|
589
|
-
if (!
|
|
596
|
+
const text2 = await response2.text();
|
|
597
|
+
if (!text2) return {};
|
|
590
598
|
try {
|
|
591
|
-
return JSON.parse(
|
|
599
|
+
return JSON.parse(text2);
|
|
592
600
|
} catch {
|
|
593
|
-
return { message:
|
|
601
|
+
return { message: text2.slice(0, 300) };
|
|
594
602
|
}
|
|
595
603
|
}
|
|
596
604
|
function apiError(status, body) {
|
|
@@ -671,7 +679,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
671
679
|
timestamp2(event.created_at),
|
|
672
680
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
673
681
|
String(event.run_id ?? ""),
|
|
674
|
-
|
|
682
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
675
683
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
676
684
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
677
685
|
String(input + output),
|
|
@@ -692,12 +700,12 @@ function timestamp2(value2) {
|
|
|
692
700
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
693
701
|
}
|
|
694
702
|
async function responseBody2(res) {
|
|
695
|
-
const
|
|
696
|
-
if (!
|
|
703
|
+
const text2 = await res.text();
|
|
704
|
+
if (!text2) return {};
|
|
697
705
|
try {
|
|
698
|
-
return JSON.parse(
|
|
706
|
+
return JSON.parse(text2);
|
|
699
707
|
} catch {
|
|
700
|
-
return { message:
|
|
708
|
+
return { message: text2.slice(0, 300) };
|
|
701
709
|
}
|
|
702
710
|
}
|
|
703
711
|
function apiError2(action2, status, body) {
|
|
@@ -873,12 +881,12 @@ function catalogModels(body) {
|
|
|
873
881
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
874
882
|
}
|
|
875
883
|
async function responseBody3(res) {
|
|
876
|
-
const
|
|
877
|
-
if (!
|
|
884
|
+
const text2 = await res.text();
|
|
885
|
+
if (!text2) return {};
|
|
878
886
|
try {
|
|
879
|
-
return JSON.parse(
|
|
887
|
+
return JSON.parse(text2);
|
|
880
888
|
} catch {
|
|
881
|
-
return { message:
|
|
889
|
+
return { message: text2.slice(0, 300) };
|
|
882
890
|
}
|
|
883
891
|
}
|
|
884
892
|
function apiError3(action2, status, body) {
|
|
@@ -1722,8 +1730,8 @@ async function appImport(options) {
|
|
|
1722
1730
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1723
1731
|
const doFetch = options.fetch ?? fetch;
|
|
1724
1732
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1725
|
-
const
|
|
1726
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1733
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1734
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1727
1735
|
if (format === "namespace-map" && options.ns) {
|
|
1728
1736
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1729
1737
|
}
|
|
@@ -1814,7 +1822,10 @@ function report(options, owners, headline) {
|
|
|
1814
1822
|
if (headline) out.log(headline);
|
|
1815
1823
|
out.log(`owners (${owners.length}):`);
|
|
1816
1824
|
for (const o of owners) {
|
|
1817
|
-
|
|
1825
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1826
|
+
out.log(
|
|
1827
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1828
|
+
);
|
|
1818
1829
|
}
|
|
1819
1830
|
}
|
|
1820
1831
|
async function ownersList(options) {
|
|
@@ -3559,22 +3570,26 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3559
3570
|
}
|
|
3560
3571
|
}
|
|
3561
3572
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3562
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3563
3573
|
next.push({
|
|
3564
3574
|
code: "review_destructive",
|
|
3565
|
-
command:
|
|
3575
|
+
command: studioSettingsUrl(reconciliation),
|
|
3566
3576
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3567
3577
|
});
|
|
3568
3578
|
}
|
|
3569
3579
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3570
3580
|
next.push({
|
|
3571
3581
|
code: "declare_or_accept_runtime_state",
|
|
3572
|
-
command:
|
|
3582
|
+
command: studioSettingsUrl(reconciliation),
|
|
3573
3583
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3574
3584
|
});
|
|
3575
3585
|
}
|
|
3576
3586
|
return next;
|
|
3577
3587
|
}
|
|
3588
|
+
function studioSettingsUrl(reconciliation) {
|
|
3589
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3590
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3591
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3592
|
+
}
|
|
3578
3593
|
function quoteArg2(value2) {
|
|
3579
3594
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3580
3595
|
}
|
|
@@ -3614,15 +3629,15 @@ function readWranglerConfig(path) {
|
|
|
3614
3629
|
return null;
|
|
3615
3630
|
}
|
|
3616
3631
|
}
|
|
3617
|
-
function stripJsonComments(
|
|
3632
|
+
function stripJsonComments(text2) {
|
|
3618
3633
|
let result = "";
|
|
3619
3634
|
let inString = false;
|
|
3620
|
-
for (let i = 0; i <
|
|
3621
|
-
const ch =
|
|
3635
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3636
|
+
const ch = text2[i];
|
|
3622
3637
|
if (inString) {
|
|
3623
3638
|
result += ch;
|
|
3624
3639
|
if (ch === "\\") {
|
|
3625
|
-
result +=
|
|
3640
|
+
result += text2[i + 1] ?? "";
|
|
3626
3641
|
i++;
|
|
3627
3642
|
} else if (ch === '"') {
|
|
3628
3643
|
inString = false;
|
|
@@ -3634,14 +3649,14 @@ function stripJsonComments(text) {
|
|
|
3634
3649
|
result += ch;
|
|
3635
3650
|
continue;
|
|
3636
3651
|
}
|
|
3637
|
-
if (ch === "/" &&
|
|
3638
|
-
while (i <
|
|
3652
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3653
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3639
3654
|
result += "\n";
|
|
3640
3655
|
continue;
|
|
3641
3656
|
}
|
|
3642
|
-
if (ch === "/" &&
|
|
3657
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3643
3658
|
i += 2;
|
|
3644
|
-
while (i <
|
|
3659
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3645
3660
|
i++;
|
|
3646
3661
|
continue;
|
|
3647
3662
|
}
|
|
@@ -3658,7 +3673,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3658
3673
|
}
|
|
3659
3674
|
}
|
|
3660
3675
|
function wranglerPutSecret(run, opts) {
|
|
3661
|
-
const args = [
|
|
3676
|
+
const args = [
|
|
3677
|
+
"wrangler",
|
|
3678
|
+
"secret",
|
|
3679
|
+
"put",
|
|
3680
|
+
opts.name,
|
|
3681
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3682
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3683
|
+
];
|
|
3662
3684
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3663
3685
|
}
|
|
3664
3686
|
|
|
@@ -4036,9 +4058,9 @@ function initProject(options) {
|
|
|
4036
4058
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4037
4059
|
out.log("updated .gitignore for local odla credentials");
|
|
4038
4060
|
}
|
|
4039
|
-
function writeIfMissing(path,
|
|
4061
|
+
function writeIfMissing(path, text2) {
|
|
4040
4062
|
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4041
|
-
(0, import_node_fs13.writeFileSync)(path,
|
|
4063
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
4042
4064
|
}
|
|
4043
4065
|
function configTemplate(input) {
|
|
4044
4066
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4246,13 +4268,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4246
4268
|
body: JSON.stringify({ value: value2 })
|
|
4247
4269
|
});
|
|
4248
4270
|
if (!res.ok) {
|
|
4249
|
-
const
|
|
4250
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4271
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4272
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
4251
4273
|
}
|
|
4252
4274
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4253
4275
|
}
|
|
4254
|
-
function scrubValue(
|
|
4255
|
-
return redactSecrets(
|
|
4276
|
+
function scrubValue(text2, value2) {
|
|
4277
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
4256
4278
|
}
|
|
4257
4279
|
async function resolveVaultWrite(options) {
|
|
4258
4280
|
const out = options.stdout ?? console;
|
|
@@ -5080,8 +5102,8 @@ async function runContainerAttempt(options) {
|
|
|
5080
5102
|
let stopped = false;
|
|
5081
5103
|
let exited = false;
|
|
5082
5104
|
child.stderr.setEncoding("utf8");
|
|
5083
|
-
child.stderr.on("data", (
|
|
5084
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5105
|
+
child.stderr.on("data", (text2) => {
|
|
5106
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
5085
5107
|
});
|
|
5086
5108
|
const stop = (reason) => {
|
|
5087
5109
|
if (stopped || exited) return;
|
|
@@ -5913,8 +5935,8 @@ function boundedInteger(value2, spec) {
|
|
|
5913
5935
|
}
|
|
5914
5936
|
function boundedNumber(value2, spec) {
|
|
5915
5937
|
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(
|
|
5938
|
+
const text2 = String(value2);
|
|
5939
|
+
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
5940
|
return value2;
|
|
5919
5941
|
}
|
|
5920
5942
|
function enumMember(value2, spec) {
|
|
@@ -6070,8 +6092,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6070
6092
|
return void 0;
|
|
6071
6093
|
}
|
|
6072
6094
|
function looksLikeDestination(value2) {
|
|
6073
|
-
const
|
|
6074
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6095
|
+
const text2 = value2.trim();
|
|
6096
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
6075
6097
|
}
|
|
6076
6098
|
|
|
6077
6099
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -6440,8 +6462,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6440
6462
|
});
|
|
6441
6463
|
let stderr = "";
|
|
6442
6464
|
child.stderr.setEncoding("utf8");
|
|
6443
|
-
child.stderr.on("data", (
|
|
6444
|
-
if (stderr.length < 4e3) stderr +=
|
|
6465
|
+
child.stderr.on("data", (text2) => {
|
|
6466
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6445
6467
|
});
|
|
6446
6468
|
child.once("error", reject);
|
|
6447
6469
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7612,10 +7634,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7612
7634
|
task: lease.task,
|
|
7613
7635
|
limits: this.options.limits,
|
|
7614
7636
|
signal: active.abort.signal,
|
|
7615
|
-
onStderr: (
|
|
7637
|
+
onStderr: (text2) => this.#event(command, {
|
|
7616
7638
|
type: "message",
|
|
7617
7639
|
actor: "system",
|
|
7618
|
-
body:
|
|
7640
|
+
body: text2.slice(0, 4e3)
|
|
7619
7641
|
}, active.conversationRefs),
|
|
7620
7642
|
onMessage: async (output) => {
|
|
7621
7643
|
if (output.type === "inference.request") {
|
|
@@ -8497,7 +8519,8 @@ Usage:
|
|
|
8497
8519
|
odla-ai doctor [--config odla.config.mjs]
|
|
8498
8520
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8499
8521
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8500
|
-
odla-ai operations
|
|
8522
|
+
odla-ai operations get <operation-id> [--json]
|
|
8523
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8501
8524
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8502
8525
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8503
8526
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8611,9 +8634,9 @@ Commands:
|
|
|
8611
8634
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8612
8635
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8613
8636
|
a question usually needs both.
|
|
8614
|
-
whoami Report
|
|
8615
|
-
|
|
8616
|
-
|
|
8637
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8638
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8639
|
+
A manager relationship never supplies authority by itself.
|
|
8617
8640
|
context Explain selected config, platform, app, environment, and
|
|
8618
8641
|
developer-token provenance without printing credentials or
|
|
8619
8642
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8733,6 +8756,58 @@ Safety:
|
|
|
8733
8756
|
`);
|
|
8734
8757
|
}
|
|
8735
8758
|
|
|
8759
|
+
// src/discuss-principals.ts
|
|
8760
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8761
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8762
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8763
|
+
}
|
|
8764
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8765
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8766
|
+
const profile = projection.principals?.[principalId];
|
|
8767
|
+
if (profile) {
|
|
8768
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8769
|
+
if (profile.kind === "agent") {
|
|
8770
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8771
|
+
}
|
|
8772
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8773
|
+
}
|
|
8774
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8775
|
+
if (legacyName) {
|
|
8776
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8777
|
+
}
|
|
8778
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8779
|
+
}
|
|
8780
|
+
|
|
8781
|
+
// src/discuss-read-render.ts
|
|
8782
|
+
function discussBodyWithRefs(post) {
|
|
8783
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8784
|
+
let out = "";
|
|
8785
|
+
let cursor = 0;
|
|
8786
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8787
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8788
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8789
|
+
cursor = ref.end;
|
|
8790
|
+
}
|
|
8791
|
+
return out + post.body.slice(cursor);
|
|
8792
|
+
}
|
|
8793
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8794
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8795
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8796
|
+
for (const post of posts) {
|
|
8797
|
+
const who = discussPrincipalLabel(
|
|
8798
|
+
post.authorId,
|
|
8799
|
+
post.authorKind,
|
|
8800
|
+
projection
|
|
8801
|
+
);
|
|
8802
|
+
ctx.out.log(`
|
|
8803
|
+
\u2014 ${who}`);
|
|
8804
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8805
|
+
for (const file of post.attachments ?? []) {
|
|
8806
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8810
|
+
|
|
8736
8811
|
// src/discuss-actions.ts
|
|
8737
8812
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8738
8813
|
async function request(ctx, method, path, body) {
|
|
@@ -8751,17 +8826,6 @@ function emit(ctx, value2, human) {
|
|
|
8751
8826
|
else human();
|
|
8752
8827
|
}
|
|
8753
8828
|
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
8829
|
function content(parsed) {
|
|
8766
8830
|
const markup = stringOpt(parsed.options.markup);
|
|
8767
8831
|
if (markup) return { markup };
|
|
@@ -8816,11 +8880,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8816
8880
|
offset: requestedOffset ?? "0"
|
|
8817
8881
|
});
|
|
8818
8882
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8819
|
-
emit(
|
|
8883
|
+
emit(
|
|
8884
|
+
ctx,
|
|
8885
|
+
page,
|
|
8886
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8887
|
+
);
|
|
8820
8888
|
return;
|
|
8821
8889
|
}
|
|
8822
8890
|
for (let scan = 0; scan < 3; scan++) {
|
|
8823
8891
|
const posts = /* @__PURE__ */ new Map();
|
|
8892
|
+
const projection = {
|
|
8893
|
+
authors: {},
|
|
8894
|
+
principals: {}
|
|
8895
|
+
};
|
|
8824
8896
|
let topic = null;
|
|
8825
8897
|
let offset = 0;
|
|
8826
8898
|
for (; ; ) {
|
|
@@ -8831,6 +8903,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8831
8903
|
);
|
|
8832
8904
|
topic = page.topic;
|
|
8833
8905
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8906
|
+
mergeDiscussPrincipals(projection, page);
|
|
8834
8907
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8835
8908
|
if (!page.page?.hasMore) break;
|
|
8836
8909
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8843,24 +8916,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8843
8916
|
);
|
|
8844
8917
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8845
8918
|
if (ordered.length === expected) {
|
|
8846
|
-
const result = { topic, posts: ordered };
|
|
8847
|
-
emit(
|
|
8919
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8920
|
+
emit(
|
|
8921
|
+
ctx,
|
|
8922
|
+
result,
|
|
8923
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8924
|
+
);
|
|
8848
8925
|
return;
|
|
8849
8926
|
}
|
|
8850
8927
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8851
8928
|
}
|
|
8852
8929
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8853
8930
|
}
|
|
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
8931
|
async function discussPost(ctx, parsed) {
|
|
8865
8932
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8866
8933
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -9085,6 +9152,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9085
9152
|
found: true,
|
|
9086
9153
|
cursor,
|
|
9087
9154
|
events: matching,
|
|
9155
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9156
|
+
...page.principals ? { principals: page.principals } : {},
|
|
9088
9157
|
...posts && posts.length > 0 ? { posts } : {},
|
|
9089
9158
|
...topics && topics.length > 0 ? { topics } : {}
|
|
9090
9159
|
});
|
|
@@ -9117,7 +9186,11 @@ function report2(ctx, parsed, result) {
|
|
|
9117
9186
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9118
9187
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
9119
9188
|
for (const post of result.posts ?? []) {
|
|
9120
|
-
const who =
|
|
9189
|
+
const who = discussPrincipalLabel(
|
|
9190
|
+
post.authorId,
|
|
9191
|
+
post.authorKind,
|
|
9192
|
+
result
|
|
9193
|
+
);
|
|
9121
9194
|
ctx.out.log(`\u2014 ${who}
|
|
9122
9195
|
${post.body}`);
|
|
9123
9196
|
}
|
|
@@ -9262,8 +9335,8 @@ function collectFields(parsed, allowClear) {
|
|
|
9262
9335
|
if (allowClear) out[spec.key] = null;
|
|
9263
9336
|
continue;
|
|
9264
9337
|
}
|
|
9265
|
-
const
|
|
9266
|
-
out[spec.key] = spec.num ? Number(
|
|
9338
|
+
const text2 = stringOpt(value2);
|
|
9339
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
9267
9340
|
}
|
|
9268
9341
|
return out;
|
|
9269
9342
|
}
|
|
@@ -9593,17 +9666,23 @@ function age(input) {
|
|
|
9593
9666
|
|
|
9594
9667
|
// src/platform-command.ts
|
|
9595
9668
|
async function platformCommand(parsed, deps = {}) {
|
|
9669
|
+
const action2 = parsed.positionals[1];
|
|
9670
|
+
if (action2 === "status") {
|
|
9671
|
+
return platformStatus(parsed, deps);
|
|
9672
|
+
}
|
|
9673
|
+
throw new Error(
|
|
9674
|
+
`unknown platform action "${[
|
|
9675
|
+
action2,
|
|
9676
|
+
parsed.positionals[2]
|
|
9677
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9678
|
+
);
|
|
9679
|
+
}
|
|
9680
|
+
async function platformStatus(parsed, deps) {
|
|
9596
9681
|
assertArgs(
|
|
9597
9682
|
parsed,
|
|
9598
9683
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9599
9684
|
2
|
|
9600
9685
|
);
|
|
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
9686
|
const context = await resolveOperatorContext(parsed, {
|
|
9608
9687
|
allowMissingConfig: true
|
|
9609
9688
|
});
|
|
@@ -10048,14 +10127,14 @@ function statusMinutes(value2) {
|
|
|
10048
10127
|
}
|
|
10049
10128
|
async function read2(url, headers, doFetch) {
|
|
10050
10129
|
const response2 = await doFetch(url, { headers });
|
|
10051
|
-
const
|
|
10130
|
+
const text2 = await response2.text();
|
|
10052
10131
|
let body = {};
|
|
10053
|
-
if (
|
|
10132
|
+
if (text2) {
|
|
10054
10133
|
try {
|
|
10055
|
-
const value2 = JSON.parse(
|
|
10134
|
+
const value2 = JSON.parse(text2);
|
|
10056
10135
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
10057
10136
|
} catch {
|
|
10058
|
-
body = { message:
|
|
10137
|
+
body = { message: text2.slice(0, 300) };
|
|
10059
10138
|
}
|
|
10060
10139
|
}
|
|
10061
10140
|
return { httpStatus: response2.status, body };
|
|
@@ -10480,7 +10559,9 @@ var COMMAND_SURFACE = {
|
|
|
10480
10559
|
init: {},
|
|
10481
10560
|
o11y: { status: {} },
|
|
10482
10561
|
operations: { get: {}, wait: {} },
|
|
10483
|
-
platform: {
|
|
10562
|
+
platform: {
|
|
10563
|
+
status: {}
|
|
10564
|
+
},
|
|
10484
10565
|
pm: {
|
|
10485
10566
|
...PM_ENTITIES,
|
|
10486
10567
|
handoff: {}
|
|
@@ -10689,12 +10770,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10689
10770
|
});
|
|
10690
10771
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10691
10772
|
}
|
|
10692
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
10773
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10693
10774
|
const runbook = await bySlug(ctx, slug);
|
|
10694
10775
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10695
10776
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10696
10777
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10697
|
-
patch: {
|
|
10778
|
+
patch: {
|
|
10779
|
+
body,
|
|
10780
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
10781
|
+
...note ? { note } : {},
|
|
10782
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
10783
|
+
}
|
|
10698
10784
|
});
|
|
10699
10785
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10700
10786
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10748,12 +10834,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
10748
10834
|
// src/runbook-import.ts
|
|
10749
10835
|
var import_node_fs18 = require("fs");
|
|
10750
10836
|
var import_node_path15 = require("path");
|
|
10751
|
-
function parseRunbook(
|
|
10752
|
-
let rest =
|
|
10837
|
+
function parseRunbook(text2, slug) {
|
|
10838
|
+
let rest = text2;
|
|
10753
10839
|
const meta = {};
|
|
10754
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
10840
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10755
10841
|
if (fm) {
|
|
10756
|
-
rest =
|
|
10842
|
+
rest = text2.slice(fm[0].length);
|
|
10757
10843
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10758
10844
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10759
10845
|
if (!pair) continue;
|
|
@@ -10834,6 +10920,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10834
10920
|
patch: {
|
|
10835
10921
|
title: r.title,
|
|
10836
10922
|
body: r.body,
|
|
10923
|
+
expectedVersion: found.version,
|
|
10837
10924
|
...r.summary ? { summary: r.summary } : {},
|
|
10838
10925
|
...r.tags ? { tags: r.tags } : {},
|
|
10839
10926
|
note: `imported from ${r.file}`
|
|
@@ -11274,27 +11361,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11274
11361
|
const found = page.records[0];
|
|
11275
11362
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
11276
11363
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
11277
|
-
|
|
11364
|
+
const body = await editText(found.body, slug, deps);
|
|
11365
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
11278
11366
|
}
|
|
11279
11367
|
|
|
11280
11368
|
// src/whoami-command.ts
|
|
11369
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11370
|
+
function principalKind(value2, machine) {
|
|
11371
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11372
|
+
}
|
|
11373
|
+
function credentialKind(value2, machine, scopes) {
|
|
11374
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11375
|
+
if (machine) return "machine";
|
|
11376
|
+
if (scopes.length) return "device";
|
|
11377
|
+
return "unknown";
|
|
11378
|
+
}
|
|
11379
|
+
function managerOf(value2) {
|
|
11380
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11381
|
+
const row = value2;
|
|
11382
|
+
const principalId = text(row.principalId);
|
|
11383
|
+
if (!principalId) return null;
|
|
11384
|
+
return {
|
|
11385
|
+
principalId,
|
|
11386
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11387
|
+
handle: text(row.handle) ?? ""
|
|
11388
|
+
};
|
|
11389
|
+
}
|
|
11390
|
+
function unnamedPrincipal(kind) {
|
|
11391
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11392
|
+
if (kind === "service") return "Unnamed service";
|
|
11393
|
+
return "Unnamed member";
|
|
11394
|
+
}
|
|
11281
11395
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11282
11396
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11283
11397
|
headers: { authorization: `Bearer ${token}` }
|
|
11284
11398
|
});
|
|
11285
11399
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11286
11400
|
const body = await res.json();
|
|
11401
|
+
const developerId = text(body.developerId) ?? "";
|
|
11402
|
+
const machine = body.machine === true;
|
|
11403
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11404
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11405
|
+
const email = text(body.email);
|
|
11406
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11407
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11408
|
+
const handle = text(body.handle) ?? "";
|
|
11409
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11287
11410
|
return {
|
|
11288
|
-
developerId
|
|
11289
|
-
|
|
11411
|
+
developerId,
|
|
11412
|
+
principalId,
|
|
11413
|
+
principalKind: kind,
|
|
11414
|
+
displayName,
|
|
11415
|
+
handle,
|
|
11416
|
+
manager: managerOf(body.manager),
|
|
11417
|
+
credential: {
|
|
11418
|
+
id: text(credential2.id),
|
|
11419
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11420
|
+
},
|
|
11421
|
+
email,
|
|
11290
11422
|
admin: body.admin === true,
|
|
11291
|
-
machine
|
|
11292
|
-
scopes
|
|
11423
|
+
machine,
|
|
11424
|
+
scopes
|
|
11293
11425
|
};
|
|
11294
11426
|
}
|
|
11295
|
-
function
|
|
11296
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
11297
|
-
|
|
11427
|
+
function credentialLabel(identity) {
|
|
11428
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11429
|
+
if (identity.credential.kind === "device")
|
|
11430
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11431
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11432
|
+
return "unknown (legacy server)";
|
|
11433
|
+
}
|
|
11434
|
+
function namedPrincipal(identity) {
|
|
11435
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11436
|
+
}
|
|
11437
|
+
function namedManager(manager) {
|
|
11438
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11439
|
+
}
|
|
11440
|
+
function accountableOwner(identity) {
|
|
11441
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11442
|
+
return namedManager(identity.manager);
|
|
11443
|
+
}
|
|
11444
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11445
|
+
return namedPrincipal(identity);
|
|
11446
|
+
}
|
|
11447
|
+
return identity.email ?? "Unnamed member";
|
|
11298
11448
|
}
|
|
11299
11449
|
async function whoamiCommand(parsed, deps = {}) {
|
|
11300
11450
|
assertArgs(
|
|
@@ -11325,9 +11475,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
11325
11475
|
return;
|
|
11326
11476
|
}
|
|
11327
11477
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11328
|
-
out.log(`
|
|
11478
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11479
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11480
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11481
|
+
if (identity.principalKind === "agent")
|
|
11482
|
+
out.log(
|
|
11483
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11484
|
+
);
|
|
11485
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11486
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11329
11487
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11330
|
-
out.log(`credential: ${
|
|
11488
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11489
|
+
if (identity.credential.id)
|
|
11490
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11331
11491
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11332
11492
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11333
11493
|
if (!identity.admin) {
|
|
@@ -11476,14 +11636,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11476
11636
|
const name = requireSlug(slug, "edit");
|
|
11477
11637
|
const file = stringOpt(parsed.options.file);
|
|
11478
11638
|
const inline = stringOpt(parsed.options.body);
|
|
11479
|
-
const
|
|
11480
|
-
if (
|
|
11639
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11640
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11641
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11481
11642
|
return runbookEdit(
|
|
11482
11643
|
ctx,
|
|
11483
11644
|
name,
|
|
11484
11645
|
body,
|
|
11485
11646
|
stringOpt(parsed.options.note),
|
|
11486
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11647
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11648
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11487
11649
|
);
|
|
11488
11650
|
}
|
|
11489
11651
|
case "import": {
|