@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/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
|
}
|
|
@@ -619,6 +619,14 @@ async function scopedToken(platform, scope, options, doFetch, out) {
|
|
|
619
619
|
return token;
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
+
// src/principal-presentation.ts
|
|
623
|
+
function unresolvedPrincipalLabel(credentialKind2, principalId) {
|
|
624
|
+
const kind = typeof credentialKind2 === "string" ? credentialKind2.trim() : "";
|
|
625
|
+
const id = typeof principalId === "string" ? principalId.trim() : "";
|
|
626
|
+
const audit = kind && id ? `${kind}:${id}` : kind || id;
|
|
627
|
+
return `Unknown principal${audit ? ` [${audit}]` : ""}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
622
630
|
// src/admin-ai-audit.ts
|
|
623
631
|
function adminAiAuditQuery(filters) {
|
|
624
632
|
if (filters.limit === void 0) return "";
|
|
@@ -648,17 +656,17 @@ async function readAdminAiAudit(request2) {
|
|
|
648
656
|
String(event.changeKind ?? ""),
|
|
649
657
|
String(event.purpose ?? event.provider ?? ""),
|
|
650
658
|
route2,
|
|
651
|
-
|
|
659
|
+
unresolvedPrincipalLabel(event.actorType, event.actorId)
|
|
652
660
|
].join(" "));
|
|
653
661
|
}
|
|
654
662
|
}
|
|
655
663
|
async function responseBody(response2) {
|
|
656
|
-
const
|
|
657
|
-
if (!
|
|
664
|
+
const text2 = await response2.text();
|
|
665
|
+
if (!text2) return {};
|
|
658
666
|
try {
|
|
659
|
-
return JSON.parse(
|
|
667
|
+
return JSON.parse(text2);
|
|
660
668
|
} catch {
|
|
661
|
-
return { message:
|
|
669
|
+
return { message: text2.slice(0, 300) };
|
|
662
670
|
}
|
|
663
671
|
}
|
|
664
672
|
function apiError(status, body) {
|
|
@@ -739,7 +747,7 @@ when app/env run actor purpose/role route / policy tokens cost status`);
|
|
|
739
747
|
timestamp2(event.created_at),
|
|
740
748
|
`${String(event.app_id ?? "")}/${String(event.env ?? "")}`,
|
|
741
749
|
String(event.run_id ?? ""),
|
|
742
|
-
|
|
750
|
+
unresolvedPrincipalLabel(event.actor_type, event.actor_id),
|
|
743
751
|
`${String(event.purpose ?? "")}/${String(event.role ?? "")}`,
|
|
744
752
|
`${String(event.provider ?? "")}/${String(event.model ?? "")}@v${String(event.policy_version ?? "unknown")}`,
|
|
745
753
|
String(input + output),
|
|
@@ -760,12 +768,12 @@ function timestamp2(value2) {
|
|
|
760
768
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
761
769
|
}
|
|
762
770
|
async function responseBody2(res) {
|
|
763
|
-
const
|
|
764
|
-
if (!
|
|
771
|
+
const text2 = await res.text();
|
|
772
|
+
if (!text2) return {};
|
|
765
773
|
try {
|
|
766
|
-
return JSON.parse(
|
|
774
|
+
return JSON.parse(text2);
|
|
767
775
|
} catch {
|
|
768
|
-
return { message:
|
|
776
|
+
return { message: text2.slice(0, 300) };
|
|
769
777
|
}
|
|
770
778
|
}
|
|
771
779
|
function apiError2(action2, status, body) {
|
|
@@ -941,12 +949,12 @@ function catalogModels(body) {
|
|
|
941
949
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
942
950
|
}
|
|
943
951
|
async function responseBody3(res) {
|
|
944
|
-
const
|
|
945
|
-
if (!
|
|
952
|
+
const text2 = await res.text();
|
|
953
|
+
if (!text2) return {};
|
|
946
954
|
try {
|
|
947
|
-
return JSON.parse(
|
|
955
|
+
return JSON.parse(text2);
|
|
948
956
|
} catch {
|
|
949
|
-
return { message:
|
|
957
|
+
return { message: text2.slice(0, 300) };
|
|
950
958
|
}
|
|
951
959
|
}
|
|
952
960
|
function apiError3(action2, status, body) {
|
|
@@ -1790,8 +1798,8 @@ async function appImport(options) {
|
|
|
1790
1798
|
const say = options.json ? (line) => out.error(line) : (line) => out.log(line);
|
|
1791
1799
|
const doFetch = options.fetch ?? fetch;
|
|
1792
1800
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
1793
|
-
const
|
|
1794
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
1801
|
+
const text2 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs8.readFileSync)(0, "utf8")))() : (0, import_node_fs8.readFileSync)(options.file, "utf8");
|
|
1802
|
+
const { format, sources } = (0, import_import.parseImport)(text2, options.ns);
|
|
1795
1803
|
if (format === "namespace-map" && options.ns) {
|
|
1796
1804
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
1797
1805
|
}
|
|
@@ -1882,7 +1890,10 @@ function report(options, owners, headline) {
|
|
|
1882
1890
|
if (headline) out.log(headline);
|
|
1883
1891
|
out.log(`owners (${owners.length}):`);
|
|
1884
1892
|
for (const o of owners) {
|
|
1885
|
-
|
|
1893
|
+
const name = o.email?.trim() || "Unnamed member";
|
|
1894
|
+
out.log(
|
|
1895
|
+
` ${o.primary ? "\u2605" : "\xB7"} ${name} [${o.ownerId}]${o.primary ? " (primary)" : ""}`
|
|
1896
|
+
);
|
|
1886
1897
|
}
|
|
1887
1898
|
}
|
|
1888
1899
|
async function ownersList(options) {
|
|
@@ -3627,22 +3638,26 @@ function planNextActions(reconciliation, configPath) {
|
|
|
3627
3638
|
}
|
|
3628
3639
|
}
|
|
3629
3640
|
if (reconciliation.actions.some((action2) => action2.applySupport === "studio")) {
|
|
3630
|
-
const { appId, platformUrl } = reconciliation.scope;
|
|
3631
3641
|
next.push({
|
|
3632
3642
|
code: "review_destructive",
|
|
3633
|
-
command:
|
|
3643
|
+
command: studioSettingsUrl(reconciliation),
|
|
3634
3644
|
description: "Review service disablement in the owning Studio scope; this plan will not apply it."
|
|
3635
3645
|
});
|
|
3636
3646
|
}
|
|
3637
3647
|
if (!reconciliation.actions.length && reconciliation.summary.unmanaged) {
|
|
3638
3648
|
next.push({
|
|
3639
3649
|
code: "declare_or_accept_runtime_state",
|
|
3640
|
-
command:
|
|
3650
|
+
command: studioSettingsUrl(reconciliation),
|
|
3641
3651
|
description: "Declare the live value in project config or keep it as an explicit runtime-managed setting."
|
|
3642
3652
|
});
|
|
3643
3653
|
}
|
|
3644
3654
|
return next;
|
|
3645
3655
|
}
|
|
3656
|
+
function studioSettingsUrl(reconciliation) {
|
|
3657
|
+
const environment2 = reconciliation.actions.map((action2) => action2.env).concat(reconciliation.scope.environments).find((candidate) => candidate === "dev" || candidate === "prod") ?? "dev";
|
|
3658
|
+
const origin = reconciliation.scope.platformUrl.replace(/\/$/, "");
|
|
3659
|
+
return `${origin}${(0, import_apps8.studioAppSettingsPath)(reconciliation.scope.appId, environment2, "environment")}`;
|
|
3660
|
+
}
|
|
3646
3661
|
function quoteArg2(value2) {
|
|
3647
3662
|
return `'${value2.replace(/'/g, `'\\''`)}'`;
|
|
3648
3663
|
}
|
|
@@ -3682,15 +3697,15 @@ function readWranglerConfig(path) {
|
|
|
3682
3697
|
return null;
|
|
3683
3698
|
}
|
|
3684
3699
|
}
|
|
3685
|
-
function stripJsonComments(
|
|
3700
|
+
function stripJsonComments(text2) {
|
|
3686
3701
|
let result = "";
|
|
3687
3702
|
let inString = false;
|
|
3688
|
-
for (let i = 0; i <
|
|
3689
|
-
const ch =
|
|
3703
|
+
for (let i = 0; i < text2.length; i++) {
|
|
3704
|
+
const ch = text2[i];
|
|
3690
3705
|
if (inString) {
|
|
3691
3706
|
result += ch;
|
|
3692
3707
|
if (ch === "\\") {
|
|
3693
|
-
result +=
|
|
3708
|
+
result += text2[i + 1] ?? "";
|
|
3694
3709
|
i++;
|
|
3695
3710
|
} else if (ch === '"') {
|
|
3696
3711
|
inString = false;
|
|
@@ -3702,14 +3717,14 @@ function stripJsonComments(text) {
|
|
|
3702
3717
|
result += ch;
|
|
3703
3718
|
continue;
|
|
3704
3719
|
}
|
|
3705
|
-
if (ch === "/" &&
|
|
3706
|
-
while (i <
|
|
3720
|
+
if (ch === "/" && text2[i + 1] === "/") {
|
|
3721
|
+
while (i < text2.length && text2[i] !== "\n") i++;
|
|
3707
3722
|
result += "\n";
|
|
3708
3723
|
continue;
|
|
3709
3724
|
}
|
|
3710
|
-
if (ch === "/" &&
|
|
3725
|
+
if (ch === "/" && text2[i + 1] === "*") {
|
|
3711
3726
|
i += 2;
|
|
3712
|
-
while (i <
|
|
3727
|
+
while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
|
|
3713
3728
|
i++;
|
|
3714
3729
|
continue;
|
|
3715
3730
|
}
|
|
@@ -3726,7 +3741,14 @@ async function wranglerLoggedIn(run, cwd) {
|
|
|
3726
3741
|
}
|
|
3727
3742
|
}
|
|
3728
3743
|
function wranglerPutSecret(run, opts) {
|
|
3729
|
-
const args = [
|
|
3744
|
+
const args = [
|
|
3745
|
+
"wrangler",
|
|
3746
|
+
"secret",
|
|
3747
|
+
"put",
|
|
3748
|
+
opts.name,
|
|
3749
|
+
...opts.env ? ["--env", opts.env] : [],
|
|
3750
|
+
...opts.configPath ? ["--config", opts.configPath] : []
|
|
3751
|
+
];
|
|
3730
3752
|
return run("npx", args, { input: opts.value, cwd: opts.cwd });
|
|
3731
3753
|
}
|
|
3732
3754
|
|
|
@@ -4104,9 +4126,9 @@ function initProject(options) {
|
|
|
4104
4126
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4105
4127
|
out.log("updated .gitignore for local odla credentials");
|
|
4106
4128
|
}
|
|
4107
|
-
function writeIfMissing(path,
|
|
4129
|
+
function writeIfMissing(path, text2) {
|
|
4108
4130
|
if ((0, import_node_fs13.existsSync)(path)) return;
|
|
4109
|
-
(0, import_node_fs13.writeFileSync)(path,
|
|
4131
|
+
(0, import_node_fs13.writeFileSync)(path, text2);
|
|
4110
4132
|
}
|
|
4111
4133
|
function configTemplate(input) {
|
|
4112
4134
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -4314,13 +4336,13 @@ async function secretsSetClerkKey(options) {
|
|
|
4314
4336
|
body: JSON.stringify({ value: value2 })
|
|
4315
4337
|
});
|
|
4316
4338
|
if (!res.ok) {
|
|
4317
|
-
const
|
|
4318
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
4339
|
+
const text2 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
4340
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text2 || "request failed"}`);
|
|
4319
4341
|
}
|
|
4320
4342
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
4321
4343
|
}
|
|
4322
|
-
function scrubValue(
|
|
4323
|
-
return redactSecrets(
|
|
4344
|
+
function scrubValue(text2, value2) {
|
|
4345
|
+
return redactSecrets(text2).split(value2).join("[value redacted]");
|
|
4324
4346
|
}
|
|
4325
4347
|
async function resolveVaultWrite(options) {
|
|
4326
4348
|
const out = options.stdout ?? console;
|
|
@@ -5148,8 +5170,8 @@ async function runContainerAttempt(options) {
|
|
|
5148
5170
|
let stopped = false;
|
|
5149
5171
|
let exited = false;
|
|
5150
5172
|
child.stderr.setEncoding("utf8");
|
|
5151
|
-
child.stderr.on("data", (
|
|
5152
|
-
if (stderr.length < 64 * 1024) stderr +=
|
|
5173
|
+
child.stderr.on("data", (text2) => {
|
|
5174
|
+
if (stderr.length < 64 * 1024) stderr += text2.slice(0, 64 * 1024 - stderr.length);
|
|
5153
5175
|
});
|
|
5154
5176
|
const stop = (reason) => {
|
|
5155
5177
|
if (stopped || exited) return;
|
|
@@ -5981,8 +6003,8 @@ function boundedInteger(value2, spec) {
|
|
|
5981
6003
|
}
|
|
5982
6004
|
function boundedNumber(value2, spec) {
|
|
5983
6005
|
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(
|
|
6006
|
+
const text2 = String(value2);
|
|
6007
|
+
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
6008
|
return value2;
|
|
5987
6009
|
}
|
|
5988
6010
|
function enumMember(value2, spec) {
|
|
@@ -6138,8 +6160,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6138
6160
|
return void 0;
|
|
6139
6161
|
}
|
|
6140
6162
|
function looksLikeDestination(value2) {
|
|
6141
|
-
const
|
|
6142
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6163
|
+
const text2 = value2.trim();
|
|
6164
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text2) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text2);
|
|
6143
6165
|
}
|
|
6144
6166
|
|
|
6145
6167
|
// ../harness/dist/chunk-GMVZ4LZH.js
|
|
@@ -6508,8 +6530,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
6508
6530
|
});
|
|
6509
6531
|
let stderr = "";
|
|
6510
6532
|
child.stderr.setEncoding("utf8");
|
|
6511
|
-
child.stderr.on("data", (
|
|
6512
|
-
if (stderr.length < 4e3) stderr +=
|
|
6533
|
+
child.stderr.on("data", (text2) => {
|
|
6534
|
+
if (stderr.length < 4e3) stderr += text2.slice(0, 4e3);
|
|
6513
6535
|
});
|
|
6514
6536
|
child.once("error", reject);
|
|
6515
6537
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(`patch did not apply: ${stderr.trim().slice(0, 500)}`)));
|
|
@@ -7680,10 +7702,10 @@ var CodePiRuntimeEngine = class {
|
|
|
7680
7702
|
task: lease.task,
|
|
7681
7703
|
limits: this.options.limits,
|
|
7682
7704
|
signal: active.abort.signal,
|
|
7683
|
-
onStderr: (
|
|
7705
|
+
onStderr: (text2) => this.#event(command, {
|
|
7684
7706
|
type: "message",
|
|
7685
7707
|
actor: "system",
|
|
7686
|
-
body:
|
|
7708
|
+
body: text2.slice(0, 4e3)
|
|
7687
7709
|
}, active.conversationRefs),
|
|
7688
7710
|
onMessage: async (output) => {
|
|
7689
7711
|
if (output.type === "inference.request") {
|
|
@@ -8565,7 +8587,8 @@ Usage:
|
|
|
8565
8587
|
odla-ai doctor [--config odla.config.mjs]
|
|
8566
8588
|
odla-ai config <diff|plan> [--config odla.config.mjs] [--email <odla-account>] [--json]
|
|
8567
8589
|
odla-ai config apply --plan <plan.json> [--idempotency-key <key>] [--email <odla-account>] [--json]
|
|
8568
|
-
odla-ai operations
|
|
8590
|
+
odla-ai operations get <operation-id> [--json]
|
|
8591
|
+
odla-ai operations wait <operation-id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
8569
8592
|
odla-ai calendar status [--env dev] [--email <odla-account>] [--json]
|
|
8570
8593
|
odla-ai calendar calendars [--env dev] [--email <odla-account>] [--json]
|
|
8571
8594
|
odla-ai calendar connect [--env dev] [--email <odla-account>] [--no-open] [--yes]
|
|
@@ -8679,9 +8702,9 @@ Commands:
|
|
|
8679
8702
|
does and what it guarantees is JSDoc, rendered per package at
|
|
8680
8703
|
https://odla.ai/docs and shipped in the installed .d.ts. Answering
|
|
8681
8704
|
a question usually needs both.
|
|
8682
|
-
whoami Report
|
|
8683
|
-
|
|
8684
|
-
|
|
8705
|
+
whoami Report the actual principal name, handle and kind, its manager,
|
|
8706
|
+
credential kind, accountable owner, and platform-admin status.
|
|
8707
|
+
A manager relationship never supplies authority by itself.
|
|
8685
8708
|
context Explain selected config, platform, app, environment, and
|
|
8686
8709
|
developer-token provenance without printing credentials or
|
|
8687
8710
|
starting a device handshake. Operator work can run outside a
|
|
@@ -8801,6 +8824,58 @@ Safety:
|
|
|
8801
8824
|
`);
|
|
8802
8825
|
}
|
|
8803
8826
|
|
|
8827
|
+
// src/discuss-principals.ts
|
|
8828
|
+
function mergeDiscussPrincipals(target, source) {
|
|
8829
|
+
Object.assign(target.authors, source.authors ?? {});
|
|
8830
|
+
Object.assign(target.principals, source.principals ?? {});
|
|
8831
|
+
}
|
|
8832
|
+
var withAt = (handle) => handle.startsWith("@") ? handle : `@${handle}`;
|
|
8833
|
+
function discussPrincipalLabel(principalId, authorKind, projection) {
|
|
8834
|
+
const profile = projection.principals?.[principalId];
|
|
8835
|
+
if (profile) {
|
|
8836
|
+
const identity = `${profile.displayName} (${withAt(profile.handle)})`;
|
|
8837
|
+
if (profile.kind === "agent") {
|
|
8838
|
+
return profile.managerDisplayName ? `${identity} \u2014 agent managed by ${profile.managerDisplayName}` : `${identity} \u2014 agent`;
|
|
8839
|
+
}
|
|
8840
|
+
return profile.kind === "service" ? `${identity} \u2014 service` : identity;
|
|
8841
|
+
}
|
|
8842
|
+
const legacyName = projection.authors?.[principalId]?.trim();
|
|
8843
|
+
if (legacyName) {
|
|
8844
|
+
return authorKind === "bot" ? `${legacyName} \u2014 agent` : legacyName;
|
|
8845
|
+
}
|
|
8846
|
+
return authorKind === "bot" ? "Unnamed agent" : "Unnamed member";
|
|
8847
|
+
}
|
|
8848
|
+
|
|
8849
|
+
// src/discuss-read-render.ts
|
|
8850
|
+
function discussBodyWithRefs(post) {
|
|
8851
|
+
if (!post.refs || post.refs.length === 0) return post.body;
|
|
8852
|
+
let out = "";
|
|
8853
|
+
let cursor = 0;
|
|
8854
|
+
for (const ref of [...post.refs].sort((a, b) => a.start - b.start)) {
|
|
8855
|
+
if (ref.start < cursor || ref.end > post.body.length) continue;
|
|
8856
|
+
out += post.body.slice(cursor, ref.start) + `@[${ref.label}](${ref.kind}/${ref.id})`;
|
|
8857
|
+
cursor = ref.end;
|
|
8858
|
+
}
|
|
8859
|
+
return out + post.body.slice(cursor);
|
|
8860
|
+
}
|
|
8861
|
+
function renderDiscussRead(ctx, topic, posts, projection) {
|
|
8862
|
+
const status = topic.resolved ? "resolved" : "open";
|
|
8863
|
+
ctx.out.log(`${topic.subject} [${status}] ${topic.appId ?? ""}`);
|
|
8864
|
+
for (const post of posts) {
|
|
8865
|
+
const who = discussPrincipalLabel(
|
|
8866
|
+
post.authorId,
|
|
8867
|
+
post.authorKind,
|
|
8868
|
+
projection
|
|
8869
|
+
);
|
|
8870
|
+
ctx.out.log(`
|
|
8871
|
+
\u2014 ${who}`);
|
|
8872
|
+
ctx.out.log(discussBodyWithRefs(post));
|
|
8873
|
+
for (const file of post.attachments ?? []) {
|
|
8874
|
+
ctx.out.log(` [attachment] ${file.name} (${file.size} bytes)`);
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
}
|
|
8878
|
+
|
|
8804
8879
|
// src/discuss-actions.ts
|
|
8805
8880
|
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8806
8881
|
async function request(ctx, method, path, body) {
|
|
@@ -8819,17 +8894,6 @@ function emit(ctx, value2, human) {
|
|
|
8819
8894
|
else human();
|
|
8820
8895
|
}
|
|
8821
8896
|
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
8897
|
function content(parsed) {
|
|
8834
8898
|
const markup = stringOpt(parsed.options.markup);
|
|
8835
8899
|
if (markup) return { markup };
|
|
@@ -8884,11 +8948,19 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8884
8948
|
offset: requestedOffset ?? "0"
|
|
8885
8949
|
});
|
|
8886
8950
|
const page = await request(ctx, "GET", `/topics/${encodeURIComponent(id)}?${query}`);
|
|
8887
|
-
emit(
|
|
8951
|
+
emit(
|
|
8952
|
+
ctx,
|
|
8953
|
+
page,
|
|
8954
|
+
() => renderDiscussRead(ctx, page.topic, page.posts, page)
|
|
8955
|
+
);
|
|
8888
8956
|
return;
|
|
8889
8957
|
}
|
|
8890
8958
|
for (let scan = 0; scan < 3; scan++) {
|
|
8891
8959
|
const posts = /* @__PURE__ */ new Map();
|
|
8960
|
+
const projection = {
|
|
8961
|
+
authors: {},
|
|
8962
|
+
principals: {}
|
|
8963
|
+
};
|
|
8892
8964
|
let topic = null;
|
|
8893
8965
|
let offset = 0;
|
|
8894
8966
|
for (; ; ) {
|
|
@@ -8899,6 +8971,7 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8899
8971
|
);
|
|
8900
8972
|
topic = page.topic;
|
|
8901
8973
|
for (const post of page.posts) posts.set(post.id, post);
|
|
8974
|
+
mergeDiscussPrincipals(projection, page);
|
|
8902
8975
|
if (posts.size > 1e4) throw new Error("discuss read failed: conversation exceeds 10000 posts");
|
|
8903
8976
|
if (!page.page?.hasMore) break;
|
|
8904
8977
|
if (page.page.nextOffset === null || page.page.nextOffset <= offset) {
|
|
@@ -8911,24 +8984,18 @@ async function discussRead(ctx, id, parsed) {
|
|
|
8911
8984
|
);
|
|
8912
8985
|
const expected = Number.isInteger(topic.replyCount) ? topic.replyCount + 1 : ordered.length;
|
|
8913
8986
|
if (ordered.length === expected) {
|
|
8914
|
-
const result = { topic, posts: ordered };
|
|
8915
|
-
emit(
|
|
8987
|
+
const result = { topic, posts: ordered, ...projection };
|
|
8988
|
+
emit(
|
|
8989
|
+
ctx,
|
|
8990
|
+
result,
|
|
8991
|
+
() => renderDiscussRead(ctx, result.topic, result.posts, result)
|
|
8992
|
+
);
|
|
8916
8993
|
return;
|
|
8917
8994
|
}
|
|
8918
8995
|
if (offset === 0) throw new Error("discuss read failed: registry did not provide forward post pages");
|
|
8919
8996
|
}
|
|
8920
8997
|
throw new Error("discuss read failed: conversation changed during every complete-read attempt");
|
|
8921
8998
|
}
|
|
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
8999
|
async function discussPost(ctx, parsed) {
|
|
8933
9000
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
8934
9001
|
if (!appId) throw new Error("discuss post needs --app <appId>");
|
|
@@ -9153,6 +9220,8 @@ async function discussWatch(ctx, topicId, parsed) {
|
|
|
9153
9220
|
found: true,
|
|
9154
9221
|
cursor,
|
|
9155
9222
|
events: matching,
|
|
9223
|
+
...page.authors ? { authors: page.authors } : {},
|
|
9224
|
+
...page.principals ? { principals: page.principals } : {},
|
|
9156
9225
|
...posts && posts.length > 0 ? { posts } : {},
|
|
9157
9226
|
...topics && topics.length > 0 ? { topics } : {}
|
|
9158
9227
|
});
|
|
@@ -9185,7 +9254,11 @@ function report2(ctx, parsed, result) {
|
|
|
9185
9254
|
ctx.out.log(JSON.stringify(result, null, 2));
|
|
9186
9255
|
} else if (parsed.options.jsonl !== true && result.found) {
|
|
9187
9256
|
for (const post of result.posts ?? []) {
|
|
9188
|
-
const who =
|
|
9257
|
+
const who = discussPrincipalLabel(
|
|
9258
|
+
post.authorId,
|
|
9259
|
+
post.authorKind,
|
|
9260
|
+
result
|
|
9261
|
+
);
|
|
9189
9262
|
ctx.out.log(`\u2014 ${who}
|
|
9190
9263
|
${post.body}`);
|
|
9191
9264
|
}
|
|
@@ -9330,8 +9403,8 @@ function collectFields(parsed, allowClear) {
|
|
|
9330
9403
|
if (allowClear) out[spec.key] = null;
|
|
9331
9404
|
continue;
|
|
9332
9405
|
}
|
|
9333
|
-
const
|
|
9334
|
-
out[spec.key] = spec.num ? Number(
|
|
9406
|
+
const text2 = stringOpt(value2);
|
|
9407
|
+
out[spec.key] = spec.num ? Number(text2) : text2;
|
|
9335
9408
|
}
|
|
9336
9409
|
return out;
|
|
9337
9410
|
}
|
|
@@ -9661,17 +9734,23 @@ function age(input) {
|
|
|
9661
9734
|
|
|
9662
9735
|
// src/platform-command.ts
|
|
9663
9736
|
async function platformCommand(parsed, deps = {}) {
|
|
9737
|
+
const action2 = parsed.positionals[1];
|
|
9738
|
+
if (action2 === "status") {
|
|
9739
|
+
return platformStatus(parsed, deps);
|
|
9740
|
+
}
|
|
9741
|
+
throw new Error(
|
|
9742
|
+
`unknown platform action "${[
|
|
9743
|
+
action2,
|
|
9744
|
+
parsed.positionals[2]
|
|
9745
|
+
].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
|
|
9746
|
+
);
|
|
9747
|
+
}
|
|
9748
|
+
async function platformStatus(parsed, deps) {
|
|
9664
9749
|
assertArgs(
|
|
9665
9750
|
parsed,
|
|
9666
9751
|
["config", "context", "platform", "token", "email", "open", "json"],
|
|
9667
9752
|
2
|
|
9668
9753
|
);
|
|
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
9754
|
const context = await resolveOperatorContext(parsed, {
|
|
9676
9755
|
allowMissingConfig: true
|
|
9677
9756
|
});
|
|
@@ -10116,14 +10195,14 @@ function statusMinutes(value2) {
|
|
|
10116
10195
|
}
|
|
10117
10196
|
async function read2(url, headers, doFetch) {
|
|
10118
10197
|
const response2 = await doFetch(url, { headers });
|
|
10119
|
-
const
|
|
10198
|
+
const text2 = await response2.text();
|
|
10120
10199
|
let body = {};
|
|
10121
|
-
if (
|
|
10200
|
+
if (text2) {
|
|
10122
10201
|
try {
|
|
10123
|
-
const value2 = JSON.parse(
|
|
10202
|
+
const value2 = JSON.parse(text2);
|
|
10124
10203
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
10125
10204
|
} catch {
|
|
10126
|
-
body = { message:
|
|
10205
|
+
body = { message: text2.slice(0, 300) };
|
|
10127
10206
|
}
|
|
10128
10207
|
}
|
|
10129
10208
|
return { httpStatus: response2.status, body };
|
|
@@ -10548,7 +10627,9 @@ var COMMAND_SURFACE = {
|
|
|
10548
10627
|
init: {},
|
|
10549
10628
|
o11y: { status: {} },
|
|
10550
10629
|
operations: { get: {}, wait: {} },
|
|
10551
|
-
platform: {
|
|
10630
|
+
platform: {
|
|
10631
|
+
status: {}
|
|
10632
|
+
},
|
|
10552
10633
|
pm: {
|
|
10553
10634
|
...PM_ENTITIES,
|
|
10554
10635
|
handoff: {}
|
|
@@ -10766,12 +10847,17 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
10766
10847
|
});
|
|
10767
10848
|
ctx.out.log(ctx.json ? JSON.stringify(created, null, 2) : `created ${slug} (${created.id})`);
|
|
10768
10849
|
}
|
|
10769
|
-
async function runbookEdit(ctx, slug, body, note, requires) {
|
|
10850
|
+
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
10770
10851
|
const runbook = await bySlug(ctx, slug);
|
|
10771
10852
|
const result = await call(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
10772
10853
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
10773
10854
|
// whatever is there, so an ordinary body edit never drops it.
|
|
10774
|
-
patch: {
|
|
10855
|
+
patch: {
|
|
10856
|
+
body,
|
|
10857
|
+
expectedVersion: expectedVersion ?? runbook.version,
|
|
10858
|
+
...note ? { note } : {},
|
|
10859
|
+
...requires === void 0 ? {} : { requires: requires || null }
|
|
10860
|
+
}
|
|
10775
10861
|
});
|
|
10776
10862
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
10777
10863
|
ctx.out.log(`${slug} \u2192 v${result.record?.version ?? runbook.version + 1}`);
|
|
@@ -10825,12 +10911,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
10825
10911
|
// src/runbook-import.ts
|
|
10826
10912
|
var import_node_fs18 = require("fs");
|
|
10827
10913
|
var import_node_path15 = require("path");
|
|
10828
|
-
function parseRunbook(
|
|
10829
|
-
let rest =
|
|
10914
|
+
function parseRunbook(text2, slug) {
|
|
10915
|
+
let rest = text2;
|
|
10830
10916
|
const meta = {};
|
|
10831
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
10917
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text2);
|
|
10832
10918
|
if (fm) {
|
|
10833
|
-
rest =
|
|
10919
|
+
rest = text2.slice(fm[0].length);
|
|
10834
10920
|
for (const line of fm[1].split(/\r?\n/)) {
|
|
10835
10921
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line.trim());
|
|
10836
10922
|
if (!pair) continue;
|
|
@@ -10911,6 +10997,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
10911
10997
|
patch: {
|
|
10912
10998
|
title: r.title,
|
|
10913
10999
|
body: r.body,
|
|
11000
|
+
expectedVersion: found.version,
|
|
10914
11001
|
...r.summary ? { summary: r.summary } : {},
|
|
10915
11002
|
...r.tags ? { tags: r.tags } : {},
|
|
10916
11003
|
note: `imported from ${r.file}`
|
|
@@ -11351,27 +11438,90 @@ async function editRunbook(ctx, slug, deps = {}) {
|
|
|
11351
11438
|
const found = page.records[0];
|
|
11352
11439
|
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
11353
11440
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
11354
|
-
|
|
11441
|
+
const body = await editText(found.body, slug, deps);
|
|
11442
|
+
return body === null ? null : { body, expectedVersion: found.version };
|
|
11355
11443
|
}
|
|
11356
11444
|
|
|
11357
11445
|
// src/whoami-command.ts
|
|
11446
|
+
var text = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
|
|
11447
|
+
function principalKind(value2, machine) {
|
|
11448
|
+
return value2 === "human" || value2 === "agent" || value2 === "service" ? value2 : machine ? "service" : "human";
|
|
11449
|
+
}
|
|
11450
|
+
function credentialKind(value2, machine, scopes) {
|
|
11451
|
+
if (value2 === "machine" || value2 === "device" || value2 === "clerk") return value2;
|
|
11452
|
+
if (machine) return "machine";
|
|
11453
|
+
if (scopes.length) return "device";
|
|
11454
|
+
return "unknown";
|
|
11455
|
+
}
|
|
11456
|
+
function managerOf(value2) {
|
|
11457
|
+
if (!value2 || typeof value2 !== "object") return null;
|
|
11458
|
+
const row = value2;
|
|
11459
|
+
const principalId = text(row.principalId);
|
|
11460
|
+
if (!principalId) return null;
|
|
11461
|
+
return {
|
|
11462
|
+
principalId,
|
|
11463
|
+
displayName: text(row.displayName) ?? "Unnamed member",
|
|
11464
|
+
handle: text(row.handle) ?? ""
|
|
11465
|
+
};
|
|
11466
|
+
}
|
|
11467
|
+
function unnamedPrincipal(kind) {
|
|
11468
|
+
if (kind === "agent") return "Unnamed agent";
|
|
11469
|
+
if (kind === "service") return "Unnamed service";
|
|
11470
|
+
return "Unnamed member";
|
|
11471
|
+
}
|
|
11358
11472
|
async function fetchIdentity(platformUrl, token, doFetch) {
|
|
11359
11473
|
const res = await doFetch(`${platformUrl.replace(/\/$/, "")}/registry/me`, {
|
|
11360
11474
|
headers: { authorization: `Bearer ${token}` }
|
|
11361
11475
|
});
|
|
11362
11476
|
if (!res.ok) throw new Error(`could not resolve identity (HTTP ${res.status})`);
|
|
11363
11477
|
const body = await res.json();
|
|
11478
|
+
const developerId = text(body.developerId) ?? "";
|
|
11479
|
+
const machine = body.machine === true;
|
|
11480
|
+
const scopes = Array.isArray(body.scopes) ? body.scopes.map(String) : [];
|
|
11481
|
+
const principalId = text(body.principalId) ?? developerId;
|
|
11482
|
+
const email = text(body.email);
|
|
11483
|
+
const kind = principalKind(body.principalKind, machine);
|
|
11484
|
+
const displayName = text(body.displayName) ?? email ?? unnamedPrincipal(kind);
|
|
11485
|
+
const handle = text(body.handle) ?? "";
|
|
11486
|
+
const credential2 = body.credential && typeof body.credential === "object" ? body.credential : {};
|
|
11364
11487
|
return {
|
|
11365
|
-
developerId
|
|
11366
|
-
|
|
11488
|
+
developerId,
|
|
11489
|
+
principalId,
|
|
11490
|
+
principalKind: kind,
|
|
11491
|
+
displayName,
|
|
11492
|
+
handle,
|
|
11493
|
+
manager: managerOf(body.manager),
|
|
11494
|
+
credential: {
|
|
11495
|
+
id: text(credential2.id),
|
|
11496
|
+
kind: credentialKind(credential2.kind, machine, scopes)
|
|
11497
|
+
},
|
|
11498
|
+
email,
|
|
11367
11499
|
admin: body.admin === true,
|
|
11368
|
-
machine
|
|
11369
|
-
scopes
|
|
11500
|
+
machine,
|
|
11501
|
+
scopes
|
|
11370
11502
|
};
|
|
11371
11503
|
}
|
|
11372
|
-
function
|
|
11373
|
-
if (identity.machine) return "machine (platform admin secret)";
|
|
11374
|
-
|
|
11504
|
+
function credentialLabel(identity) {
|
|
11505
|
+
if (identity.credential.kind === "machine") return "machine (platform admin secret)";
|
|
11506
|
+
if (identity.credential.kind === "device")
|
|
11507
|
+
return identity.scopes.length ? "device (scoped)" : "device";
|
|
11508
|
+
if (identity.credential.kind === "clerk") return "clerk";
|
|
11509
|
+
return "unknown (legacy server)";
|
|
11510
|
+
}
|
|
11511
|
+
function namedPrincipal(identity) {
|
|
11512
|
+
return identity.handle && identity.handle !== identity.displayName ? `${identity.displayName} (@${identity.handle})` : identity.displayName;
|
|
11513
|
+
}
|
|
11514
|
+
function namedManager(manager) {
|
|
11515
|
+
return manager.handle && manager.handle !== manager.displayName ? `${manager.displayName} (@${manager.handle})` : manager.displayName;
|
|
11516
|
+
}
|
|
11517
|
+
function accountableOwner(identity) {
|
|
11518
|
+
if (identity.principalKind === "agent" && identity.manager) {
|
|
11519
|
+
return namedManager(identity.manager);
|
|
11520
|
+
}
|
|
11521
|
+
if (identity.principalKind === "human" && identity.principalId === identity.developerId) {
|
|
11522
|
+
return namedPrincipal(identity);
|
|
11523
|
+
}
|
|
11524
|
+
return identity.email ?? "Unnamed member";
|
|
11375
11525
|
}
|
|
11376
11526
|
async function whoamiCommand(parsed, deps = {}) {
|
|
11377
11527
|
assertArgs(
|
|
@@ -11402,9 +11552,19 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
11402
11552
|
return;
|
|
11403
11553
|
}
|
|
11404
11554
|
out.log(`platform: ${cfg.platformUrl}`);
|
|
11405
|
-
out.log(`
|
|
11555
|
+
out.log(`principal: ${namedPrincipal(identity)}`);
|
|
11556
|
+
out.log(`principal id: ${identity.principalId}`);
|
|
11557
|
+
out.log(`kind: ${identity.principalKind}`);
|
|
11558
|
+
if (identity.principalKind === "agent")
|
|
11559
|
+
out.log(
|
|
11560
|
+
`manager: ${identity.manager ? namedManager(identity.manager) : "(unknown)"}`
|
|
11561
|
+
);
|
|
11562
|
+
out.log(`owner: ${accountableOwner(identity)}`);
|
|
11563
|
+
out.log(`owner id: ${identity.developerId}`);
|
|
11406
11564
|
out.log(`email: ${identity.email ?? "(none)"}`);
|
|
11407
|
-
out.log(`credential: ${
|
|
11565
|
+
out.log(`credential: ${credentialLabel(identity)}`);
|
|
11566
|
+
if (identity.credential.id)
|
|
11567
|
+
out.log(`credential id: ${identity.credential.id}`);
|
|
11408
11568
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
11409
11569
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
11410
11570
|
if (!identity.admin) {
|
|
@@ -11553,14 +11713,16 @@ async function runbookCommand(parsed, deps = {}) {
|
|
|
11553
11713
|
const name = requireSlug(slug, "edit");
|
|
11554
11714
|
const file = stringOpt(parsed.options.file);
|
|
11555
11715
|
const inline = stringOpt(parsed.options.body);
|
|
11556
|
-
const
|
|
11557
|
-
if (
|
|
11716
|
+
const edited = file === void 0 && inline === void 0 ? await editRunbook(ctx, name) : readBody(file, inline);
|
|
11717
|
+
if (edited === null) return ctx.out.log(`${name} unchanged; nothing written`);
|
|
11718
|
+
const body = typeof edited === "string" ? edited : edited.body;
|
|
11558
11719
|
return runbookEdit(
|
|
11559
11720
|
ctx,
|
|
11560
11721
|
name,
|
|
11561
11722
|
body,
|
|
11562
11723
|
stringOpt(parsed.options.note),
|
|
11563
|
-
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? ""
|
|
11724
|
+
parsed.options.requires === void 0 ? void 0 : stringOpt(parsed.options.requires) ?? "",
|
|
11725
|
+
typeof edited === "string" ? void 0 : edited.expectedVersion
|
|
11564
11726
|
);
|
|
11565
11727
|
}
|
|
11566
11728
|
case "import": {
|