@odla-ai/cli 0.25.3 → 0.25.7
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 -15
- package/REQUIREMENTS.md +5 -5
- package/dist/bin.cjs +405 -82
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +3 -2
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-MX6FSJUN.js → chunk-5U3EXWCR.js} +405 -82
- package/dist/chunk-5U3EXWCR.js.map +1 -0
- package/dist/index.cjs +404 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MX6FSJUN.js.map +0 -1
|
@@ -1156,6 +1156,37 @@ function redactSecrets(value) {
|
|
|
1156
1156
|
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
1157
1157
|
return result;
|
|
1158
1158
|
}
|
|
1159
|
+
function redactingOutput(output) {
|
|
1160
|
+
const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
|
|
1161
|
+
return {
|
|
1162
|
+
log: (...args) => output.log(...redactArgs(args)),
|
|
1163
|
+
error: (...args) => output.error(...redactArgs(args))
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
function redactOutputValue(value, seen) {
|
|
1167
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
1168
|
+
if (value instanceof Error) {
|
|
1169
|
+
const redacted = new Error(redactSecrets(value.message));
|
|
1170
|
+
redacted.name = value.name;
|
|
1171
|
+
if (value.stack) redacted.stack = redactSecrets(value.stack);
|
|
1172
|
+
return redacted;
|
|
1173
|
+
}
|
|
1174
|
+
if (!value || typeof value !== "object") return value;
|
|
1175
|
+
const prior = seen.get(value);
|
|
1176
|
+
if (prior) return prior;
|
|
1177
|
+
if (Array.isArray(value)) {
|
|
1178
|
+
const next2 = [];
|
|
1179
|
+
seen.set(value, next2);
|
|
1180
|
+
for (const item of value) next2.push(redactOutputValue(item, seen));
|
|
1181
|
+
return next2;
|
|
1182
|
+
}
|
|
1183
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1184
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
1185
|
+
const next = {};
|
|
1186
|
+
seen.set(value, next);
|
|
1187
|
+
for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
|
|
1188
|
+
return next;
|
|
1189
|
+
}
|
|
1159
1190
|
function looksSecret(value) {
|
|
1160
1191
|
return redactSecrets(value) !== value || value.includes("-----BEGIN");
|
|
1161
1192
|
}
|
|
@@ -1585,6 +1616,7 @@ var CAPABILITIES = {
|
|
|
1585
1616
|
"compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
|
|
1586
1617
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
1587
1618
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
1619
|
+
"read one versioned o11y status envelope spanning application RED, current live-sync freshness/load, the protected commit-to-visible canary, collector ingest/scheduler trust, provider-owned runtime metrics, and a bounded machine verdict",
|
|
1588
1620
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
1589
1621
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
1590
1622
|
"let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
|
|
@@ -1605,7 +1637,7 @@ var CAPABILITIES = {
|
|
|
1605
1637
|
"approve GitHub App repository access separately from redacted-snippet disclosure"
|
|
1606
1638
|
],
|
|
1607
1639
|
studio: [
|
|
1608
|
-
"view telemetry and environment state",
|
|
1640
|
+
"view application telemetry, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
1609
1641
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
1610
1642
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
1611
1643
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -2578,9 +2610,17 @@ async function smoke(options) {
|
|
|
2578
2610
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
2579
2611
|
}
|
|
2580
2612
|
const entry = credentials.envs[env];
|
|
2581
|
-
if (!entry?.tenantId
|
|
2613
|
+
if (!entry?.tenantId) {
|
|
2614
|
+
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
2615
|
+
}
|
|
2616
|
+
const hasDb = cfg.services.includes("db");
|
|
2617
|
+
const hasO11y = cfg.services.includes("o11y");
|
|
2618
|
+
if (hasDb && !entry.dbKey) {
|
|
2582
2619
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
2583
2620
|
}
|
|
2621
|
+
if (hasO11y && !entry.o11yToken) {
|
|
2622
|
+
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
2623
|
+
}
|
|
2584
2624
|
const doFetch = options.fetch ?? fetch;
|
|
2585
2625
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
2586
2626
|
out.log(` tenant: ${entry.tenantId}`);
|
|
@@ -2593,6 +2633,7 @@ async function smoke(options) {
|
|
|
2593
2633
|
}
|
|
2594
2634
|
out.log(` ai: ${provider}`);
|
|
2595
2635
|
}
|
|
2636
|
+
if (hasO11y) out.log(` o11y: credentials present`);
|
|
2596
2637
|
if (cfg.services.includes("calendar")) {
|
|
2597
2638
|
const token = await getDeveloperToken(
|
|
2598
2639
|
cfg,
|
|
@@ -2611,26 +2652,30 @@ async function smoke(options) {
|
|
|
2611
2652
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
2612
2653
|
}
|
|
2613
2654
|
const database = await resolveDatabaseConfig(cfg);
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2655
|
+
if (hasDb) {
|
|
2656
|
+
const expectedSchema = database.schema;
|
|
2657
|
+
const expectedEntities = serializedEntities(expectedSchema);
|
|
2658
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/schema`, entry.dbKey);
|
|
2659
|
+
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
2660
|
+
const liveEntities = serializedEntities(liveSchema);
|
|
2661
|
+
if (expectedEntities.length) {
|
|
2662
|
+
const missing = expectedEntities.filter((entity) => !liveEntities.includes(entity));
|
|
2663
|
+
if (missing.length) throw new Error(`live schema is missing expected entities: ${missing.join(", ")}`);
|
|
2664
|
+
}
|
|
2665
|
+
out.log(` schema: ${liveEntities.length} entities`);
|
|
2666
|
+
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
2667
|
+
if (aggregateEntity) {
|
|
2668
|
+
const aggregate = await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
|
|
2669
|
+
ns: aggregateEntity,
|
|
2670
|
+
aggregate: { count: true }
|
|
2671
|
+
});
|
|
2672
|
+
const count = aggregate.aggregate?.count;
|
|
2673
|
+
out.log(` aggregate: ${aggregateEntity}.count=${String(count ?? "ok")}`);
|
|
2674
|
+
} else {
|
|
2675
|
+
out.log(` aggregate: skipped (schema has no entities)`);
|
|
2676
|
+
}
|
|
2632
2677
|
} else {
|
|
2633
|
-
out.log(`
|
|
2678
|
+
out.log(` db: skipped (not enabled)`);
|
|
2634
2679
|
}
|
|
2635
2680
|
const probes = database.integrations.flatMap(
|
|
2636
2681
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
@@ -2683,7 +2728,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
2683
2728
|
}
|
|
2684
2729
|
async function safeText3(res) {
|
|
2685
2730
|
try {
|
|
2686
|
-
return (await res.text()).slice(0, 500);
|
|
2731
|
+
return redactSecrets((await res.text()).slice(0, 500));
|
|
2687
2732
|
} catch {
|
|
2688
2733
|
return "";
|
|
2689
2734
|
}
|
|
@@ -3406,8 +3451,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
3406
3451
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
3407
3452
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
3408
3453
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
3409
|
-
const entries = inventory.flatMap((
|
|
3410
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
3454
|
+
const entries = inventory.flatMap((record7) => {
|
|
3455
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record7);
|
|
3411
3456
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
3412
3457
|
});
|
|
3413
3458
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -3655,8 +3700,8 @@ function normalize(value) {
|
|
|
3655
3700
|
if (Array.isArray(value)) return value.map(normalize);
|
|
3656
3701
|
if (value instanceof Uint8Array) return { $bytes: [...value] };
|
|
3657
3702
|
if (typeof value === "object") {
|
|
3658
|
-
const
|
|
3659
|
-
return Object.fromEntries(Object.keys(
|
|
3703
|
+
const record7 = value;
|
|
3704
|
+
return Object.fromEntries(Object.keys(record7).filter((key) => record7[key] !== void 0).sort().map((key) => [key, normalize(record7[key])]));
|
|
3660
3705
|
}
|
|
3661
3706
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
3662
3707
|
}
|
|
@@ -5901,8 +5946,8 @@ function cliVersion() {
|
|
|
5901
5946
|
const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
|
|
5902
5947
|
return pkg.version ?? "unknown";
|
|
5903
5948
|
}
|
|
5904
|
-
function printHelp() {
|
|
5905
|
-
|
|
5949
|
+
function printHelp(output = console) {
|
|
5950
|
+
output.log(`odla-ai
|
|
5906
5951
|
|
|
5907
5952
|
Start here:
|
|
5908
5953
|
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
@@ -5934,22 +5979,23 @@ Usage:
|
|
|
5934
5979
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
5935
5980
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
5936
5981
|
odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
|
|
5937
|
-
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
|
|
5982
|
+
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>] [--mutation-id <id>]
|
|
5938
5983
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
5939
|
-
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
|
|
5940
|
-
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
|
|
5941
|
-
odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
|
|
5984
|
+
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>] [--mutation-id <id>]
|
|
5985
|
+
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
5986
|
+
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
5942
5987
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
5943
5988
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
5944
5989
|
odla-ai pm handoff --app <id> [--json]
|
|
5945
5990
|
odla-ai discuss groups [--json]
|
|
5946
5991
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
5947
5992
|
odla-ai discuss read <topic> [--json]
|
|
5948
|
-
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"]
|
|
5949
|
-
odla-ai discuss reply <topic> --body "..." [--markup "..."]
|
|
5950
|
-
odla-ai discuss resolve <topic> [--reopen]
|
|
5993
|
+
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
5994
|
+
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
5995
|
+
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
5951
5996
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
5952
5997
|
odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
|
|
5998
|
+
odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
|
|
5953
5999
|
odla-ai whoami [--json]
|
|
5954
6000
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
5955
6001
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6061,6 +6107,11 @@ Commands:
|
|
|
6061
6107
|
(not 1) when it times out with nothing new, so a script can tell
|
|
6062
6108
|
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
6063
6109
|
up the exact @[Label](kind/id) markup for a mention.
|
|
6110
|
+
o11y Read one stable status envelope for application RED, current
|
|
6111
|
+
live-sync load/freshness, the protected commit-to-visible
|
|
6112
|
+
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
6113
|
+
runtime metrics, and a machine verdict.
|
|
6114
|
+
--json keeps auth progress on stderr for unattended agents.
|
|
6064
6115
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
6065
6116
|
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
6066
6117
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
@@ -6521,15 +6572,6 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
6521
6572
|
}
|
|
6522
6573
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
6523
6574
|
}
|
|
6524
|
-
async function readRegistryApp(cfg, token, doFetch) {
|
|
6525
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
|
|
6526
|
-
headers: { authorization: `Bearer ${token}` }
|
|
6527
|
-
});
|
|
6528
|
-
if (res.status === 404) return null;
|
|
6529
|
-
if (!res.ok) return null;
|
|
6530
|
-
const json = await res.json();
|
|
6531
|
-
return json.app ?? null;
|
|
6532
|
-
}
|
|
6533
6575
|
async function postJson3(doFetch, url, bearer, body) {
|
|
6534
6576
|
const res = await doFetch(url, {
|
|
6535
6577
|
method: "POST",
|
|
@@ -6628,7 +6670,7 @@ async function provision(options) {
|
|
|
6628
6670
|
const doFetch = options.fetch ?? fetch;
|
|
6629
6671
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
6630
6672
|
const apps = createAppsClient({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
6631
|
-
const existing = await
|
|
6673
|
+
const existing = await apps.resolveApp(cfg.app.id);
|
|
6632
6674
|
if (existing) {
|
|
6633
6675
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
6634
6676
|
} else {
|
|
@@ -6815,6 +6857,7 @@ var COMMAND_SURFACE = {
|
|
|
6815
6857
|
doctor: {},
|
|
6816
6858
|
help: {},
|
|
6817
6859
|
init: {},
|
|
6860
|
+
o11y: { status: {} },
|
|
6818
6861
|
pm: {
|
|
6819
6862
|
...PM_ENTITIES,
|
|
6820
6863
|
handoff: {}
|
|
@@ -8009,6 +8052,7 @@ async function codeCommand(parsed, dependencies) {
|
|
|
8009
8052
|
}
|
|
8010
8053
|
|
|
8011
8054
|
// src/discuss-actions.ts
|
|
8055
|
+
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8012
8056
|
async function request(ctx, method, path, body) {
|
|
8013
8057
|
const res = await ctx.doFetch(`${ctx.platformUrl}/registry/discuss${path}`, {
|
|
8014
8058
|
method,
|
|
@@ -8105,7 +8149,8 @@ async function discussPost(ctx, parsed) {
|
|
|
8105
8149
|
const created = await request(ctx, "POST", "/topics", {
|
|
8106
8150
|
appId,
|
|
8107
8151
|
subject,
|
|
8108
|
-
...content(parsed)
|
|
8152
|
+
...content(parsed),
|
|
8153
|
+
mutationId: writeMutationId(parsed)
|
|
8109
8154
|
});
|
|
8110
8155
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
8111
8156
|
}
|
|
@@ -8114,16 +8159,16 @@ async function discussReply(ctx, id, parsed) {
|
|
|
8114
8159
|
ctx,
|
|
8115
8160
|
"POST",
|
|
8116
8161
|
`/topics/${encodeURIComponent(id)}/replies`,
|
|
8117
|
-
content(parsed)
|
|
8162
|
+
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
8118
8163
|
);
|
|
8119
8164
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
8120
8165
|
}
|
|
8121
|
-
async function discussResolve(ctx, id, resolved) {
|
|
8166
|
+
async function discussResolve(ctx, id, resolved, parsed) {
|
|
8122
8167
|
const result = await request(
|
|
8123
8168
|
ctx,
|
|
8124
8169
|
"PATCH",
|
|
8125
8170
|
`/topics/${encodeURIComponent(id)}`,
|
|
8126
|
-
{ resolved }
|
|
8171
|
+
{ resolved, mutationId: writeMutationId(parsed) }
|
|
8127
8172
|
);
|
|
8128
8173
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
8129
8174
|
}
|
|
@@ -8236,7 +8281,8 @@ var ALLOWED = [
|
|
|
8236
8281
|
"by",
|
|
8237
8282
|
"self",
|
|
8238
8283
|
"interval",
|
|
8239
|
-
"timeout"
|
|
8284
|
+
"timeout",
|
|
8285
|
+
"mutation-id"
|
|
8240
8286
|
];
|
|
8241
8287
|
function requireId(id, action) {
|
|
8242
8288
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
@@ -8278,7 +8324,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
8278
8324
|
case "reply":
|
|
8279
8325
|
return discussReply(ctx, requireId(id, "reply"), parsed);
|
|
8280
8326
|
case "resolve":
|
|
8281
|
-
return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true);
|
|
8327
|
+
return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true, parsed);
|
|
8282
8328
|
case "who":
|
|
8283
8329
|
return discussWho(ctx, parsed);
|
|
8284
8330
|
case "watch": {
|
|
@@ -8292,6 +8338,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
8292
8338
|
}
|
|
8293
8339
|
|
|
8294
8340
|
// src/pm-actions.ts
|
|
8341
|
+
var writeMutationId2 = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
8295
8342
|
var FIELD_MAP = {
|
|
8296
8343
|
title: { key: "title" },
|
|
8297
8344
|
status: { key: "status" },
|
|
@@ -8393,25 +8440,35 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8393
8440
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
8394
8441
|
if (entity === "bug" && !input.description)
|
|
8395
8442
|
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
8396
|
-
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
8443
|
+
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
8444
|
+
appId,
|
|
8445
|
+
input,
|
|
8446
|
+
mutationId: writeMutationId2(parsed)
|
|
8447
|
+
});
|
|
8397
8448
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8398
8449
|
}
|
|
8399
8450
|
async function pmGet(ctx, entity, id) {
|
|
8400
|
-
const { record:
|
|
8401
|
-
emit2(ctx,
|
|
8451
|
+
const { record: record7 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
8452
|
+
emit2(ctx, record7, () => printRecord(ctx, entity, record7));
|
|
8402
8453
|
}
|
|
8403
8454
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8404
8455
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
8405
8456
|
if (Object.keys(patch2).length === 0)
|
|
8406
8457
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
8407
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
8458
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
8459
|
+
patch: patch2,
|
|
8460
|
+
mutationId: writeMutationId2(parsed)
|
|
8461
|
+
});
|
|
8408
8462
|
emit2(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
|
|
8409
8463
|
}
|
|
8410
8464
|
async function pmDone(ctx, entity, id, parsed) {
|
|
8411
8465
|
const decisionId = stringOpt(parsed.options.decision);
|
|
8412
8466
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
8413
8467
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
8414
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
8468
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
8469
|
+
patch: patch2,
|
|
8470
|
+
mutationId: writeMutationId2(parsed)
|
|
8471
|
+
});
|
|
8415
8472
|
emit2(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
|
|
8416
8473
|
}
|
|
8417
8474
|
async function allRecords(ctx, entity, appId) {
|
|
@@ -8437,9 +8494,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8437
8494
|
]);
|
|
8438
8495
|
const handoff = {
|
|
8439
8496
|
appId,
|
|
8440
|
-
unmetGoals: goals.filter((
|
|
8441
|
-
activeTasks: tasks.filter((
|
|
8442
|
-
openBugs: bugs.filter((
|
|
8497
|
+
unmetGoals: goals.filter((record7) => record7.status !== "met"),
|
|
8498
|
+
activeTasks: tasks.filter((record7) => record7.column !== "done"),
|
|
8499
|
+
openBugs: bugs.filter((record7) => record7.status !== "fixed" && record7.status !== "wontfix")
|
|
8443
8500
|
};
|
|
8444
8501
|
const result = {
|
|
8445
8502
|
...handoff,
|
|
@@ -8458,10 +8515,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8458
8515
|
]) {
|
|
8459
8516
|
ctx.out.log(`${label}:`);
|
|
8460
8517
|
if (!records.length) ctx.out.log("- (none)");
|
|
8461
|
-
else for (const
|
|
8518
|
+
else for (const record7 of records) printRecord(
|
|
8462
8519
|
ctx,
|
|
8463
8520
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
8464
|
-
|
|
8521
|
+
record7
|
|
8465
8522
|
);
|
|
8466
8523
|
}
|
|
8467
8524
|
});
|
|
@@ -8469,7 +8526,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8469
8526
|
async function pmComment(ctx, entity, id, parsed) {
|
|
8470
8527
|
const body = stringOpt(parsed.options.body);
|
|
8471
8528
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
8472
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
|
|
8529
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
|
|
8530
|
+
body,
|
|
8531
|
+
mutationId: writeMutationId2(parsed)
|
|
8532
|
+
});
|
|
8473
8533
|
ctx.out.log(`commented on ${entity} ${id}`);
|
|
8474
8534
|
}
|
|
8475
8535
|
async function pmComments(ctx, entity, id) {
|
|
@@ -8519,7 +8579,8 @@ var ALLOWED2 = [
|
|
|
8519
8579
|
"decision",
|
|
8520
8580
|
"limit",
|
|
8521
8581
|
"offset",
|
|
8522
|
-
"q"
|
|
8582
|
+
"q",
|
|
8583
|
+
"mutation-id"
|
|
8523
8584
|
];
|
|
8524
8585
|
function requireId2(id, action) {
|
|
8525
8586
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
@@ -8576,6 +8637,260 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
8576
8637
|
}
|
|
8577
8638
|
}
|
|
8578
8639
|
|
|
8640
|
+
// src/o11y-verdict.ts
|
|
8641
|
+
function statusVerdict(reads) {
|
|
8642
|
+
const reasons = [];
|
|
8643
|
+
sourceHttp(reasons, "application", reads.application);
|
|
8644
|
+
sourceHttp(reasons, "liveSync", reads.liveSync);
|
|
8645
|
+
sourceHttp(reasons, "canary", reads.canary);
|
|
8646
|
+
sourceHttp(reasons, "collector", reads.collector);
|
|
8647
|
+
sourceHttp(reasons, "provider", reads.provider);
|
|
8648
|
+
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
8649
|
+
if (liveStatus === "partial") {
|
|
8650
|
+
reasons.push({
|
|
8651
|
+
source: "liveSync",
|
|
8652
|
+
code: "partial_reconciliation",
|
|
8653
|
+
severity: "degraded"
|
|
8654
|
+
});
|
|
8655
|
+
} else if (liveStatus === "stale" || liveStatus === "unavailable") {
|
|
8656
|
+
reasons.push({
|
|
8657
|
+
source: "liveSync",
|
|
8658
|
+
code: `reconciliation_${liveStatus}`,
|
|
8659
|
+
severity: "degraded"
|
|
8660
|
+
});
|
|
8661
|
+
}
|
|
8662
|
+
const performance = record5(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
8663
|
+
if (performance?.status === "unavailable") {
|
|
8664
|
+
reasons.push({
|
|
8665
|
+
source: "liveSync",
|
|
8666
|
+
code: "subscription_metrics_unavailable",
|
|
8667
|
+
severity: "degraded"
|
|
8668
|
+
});
|
|
8669
|
+
}
|
|
8670
|
+
if (numeric2(performance?.telemetryDropped) > 0) {
|
|
8671
|
+
reasons.push({
|
|
8672
|
+
source: "liveSync",
|
|
8673
|
+
code: "subscription_telemetry_dropped",
|
|
8674
|
+
severity: "degraded"
|
|
8675
|
+
});
|
|
8676
|
+
}
|
|
8677
|
+
const canaryStatus = String(reads.canary.body.status ?? "");
|
|
8678
|
+
if (canaryStatus === "failing") {
|
|
8679
|
+
reasons.push({
|
|
8680
|
+
source: "canary",
|
|
8681
|
+
code: String(reads.canary.body.errorCode ?? "journey_failed"),
|
|
8682
|
+
severity: "unhealthy"
|
|
8683
|
+
});
|
|
8684
|
+
} else if (canaryStatus === "stale") {
|
|
8685
|
+
reasons.push({
|
|
8686
|
+
source: "canary",
|
|
8687
|
+
code: "journey_stale",
|
|
8688
|
+
severity: "unhealthy"
|
|
8689
|
+
});
|
|
8690
|
+
} else if (canaryStatus === "pending" || canaryStatus === "not_configured") {
|
|
8691
|
+
reasons.push({
|
|
8692
|
+
source: "canary",
|
|
8693
|
+
code: `journey_${canaryStatus}`,
|
|
8694
|
+
severity: "degraded"
|
|
8695
|
+
});
|
|
8696
|
+
}
|
|
8697
|
+
const collectorStatus = String(reads.collector.body.status ?? "");
|
|
8698
|
+
if (collectorStatus === "unhealthy") {
|
|
8699
|
+
reasons.push({
|
|
8700
|
+
source: "collector",
|
|
8701
|
+
code: collectorReason(reads.collector, "collector_unhealthy"),
|
|
8702
|
+
severity: "unhealthy"
|
|
8703
|
+
});
|
|
8704
|
+
} else if (collectorStatus === "degraded") {
|
|
8705
|
+
reasons.push({
|
|
8706
|
+
source: "collector",
|
|
8707
|
+
code: collectorReason(reads.collector, "collector_degraded"),
|
|
8708
|
+
severity: "degraded"
|
|
8709
|
+
});
|
|
8710
|
+
}
|
|
8711
|
+
const providerStatus = String(reads.provider.body.status ?? "");
|
|
8712
|
+
if (providerStatus && providerStatus !== "observed") {
|
|
8713
|
+
reasons.push({
|
|
8714
|
+
source: "provider",
|
|
8715
|
+
code: `provider_${providerStatus}`,
|
|
8716
|
+
severity: "degraded"
|
|
8717
|
+
});
|
|
8718
|
+
}
|
|
8719
|
+
return {
|
|
8720
|
+
status: reasons.some((reason) => reason.severity === "unhealthy") ? "unhealthy" : reasons.length > 0 ? "degraded" : "healthy",
|
|
8721
|
+
reasons
|
|
8722
|
+
};
|
|
8723
|
+
}
|
|
8724
|
+
function record5(value) {
|
|
8725
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8726
|
+
}
|
|
8727
|
+
function numeric2(value) {
|
|
8728
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8729
|
+
}
|
|
8730
|
+
function collectorReason(read3, fallback) {
|
|
8731
|
+
const reasons = read3.body.reasons;
|
|
8732
|
+
if (!Array.isArray(reasons)) return fallback;
|
|
8733
|
+
const first = reasons.find(
|
|
8734
|
+
(reason) => Boolean(reason) && typeof reason === "object" && !Array.isArray(reason)
|
|
8735
|
+
);
|
|
8736
|
+
return typeof first?.code === "string" && first.code ? first.code : fallback;
|
|
8737
|
+
}
|
|
8738
|
+
function sourceHttp(reasons, source, read3) {
|
|
8739
|
+
if (read3.httpStatus >= 200 && read3.httpStatus < 300) return;
|
|
8740
|
+
reasons.push({
|
|
8741
|
+
source,
|
|
8742
|
+
code: `http_${read3.httpStatus}`,
|
|
8743
|
+
severity: read3.httpStatus >= 500 && read3.httpStatus !== 501 ? "unhealthy" : "degraded"
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
8746
|
+
|
|
8747
|
+
// src/o11y-command.ts
|
|
8748
|
+
async function o11yCommand(parsed, deps = {}) {
|
|
8749
|
+
assertArgs(
|
|
8750
|
+
parsed,
|
|
8751
|
+
["config", "token", "email", "json", "app", "env", "minutes"],
|
|
8752
|
+
2
|
|
8753
|
+
);
|
|
8754
|
+
const action = parsed.positionals[1];
|
|
8755
|
+
if (action !== "status") {
|
|
8756
|
+
throw new Error(
|
|
8757
|
+
`unknown o11y action "${action ?? ""}". Try "odla-ai o11y status --json".`
|
|
8758
|
+
);
|
|
8759
|
+
}
|
|
8760
|
+
const minutes = statusMinutes(
|
|
8761
|
+
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
8762
|
+
);
|
|
8763
|
+
const cfg = await loadProjectConfig(
|
|
8764
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs"
|
|
8765
|
+
);
|
|
8766
|
+
const doFetch = deps.fetch ?? fetch;
|
|
8767
|
+
const out = deps.stdout ?? console;
|
|
8768
|
+
const token = await getDeveloperToken(
|
|
8769
|
+
cfg,
|
|
8770
|
+
{
|
|
8771
|
+
configPath: cfg.configPath,
|
|
8772
|
+
token: stringOpt(parsed.options.token),
|
|
8773
|
+
email: stringOpt(parsed.options.email),
|
|
8774
|
+
open: false
|
|
8775
|
+
},
|
|
8776
|
+
doFetch,
|
|
8777
|
+
out
|
|
8778
|
+
);
|
|
8779
|
+
const appId = stringOpt(parsed.options.app)?.trim() || cfg.app.id;
|
|
8780
|
+
const env = stringOpt(parsed.options.env)?.trim() || "prod";
|
|
8781
|
+
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
8782
|
+
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
8783
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
8784
|
+
const [application, liveSync, canary, collector, provider] = await Promise.all([
|
|
8785
|
+
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
8786
|
+
read2(
|
|
8787
|
+
`${base}/live-sync/health?${query}`,
|
|
8788
|
+
headers,
|
|
8789
|
+
doFetch
|
|
8790
|
+
),
|
|
8791
|
+
read2(
|
|
8792
|
+
`${base}/live-sync/canary?${new URLSearchParams({ env })}`,
|
|
8793
|
+
headers,
|
|
8794
|
+
doFetch
|
|
8795
|
+
),
|
|
8796
|
+
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
8797
|
+
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch)
|
|
8798
|
+
]);
|
|
8799
|
+
const verdict = statusVerdict({
|
|
8800
|
+
application,
|
|
8801
|
+
liveSync,
|
|
8802
|
+
canary,
|
|
8803
|
+
collector,
|
|
8804
|
+
provider
|
|
8805
|
+
});
|
|
8806
|
+
const status = {
|
|
8807
|
+
schemaVersion: 4,
|
|
8808
|
+
scope: { appId, env, minutes },
|
|
8809
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8810
|
+
verdict,
|
|
8811
|
+
application,
|
|
8812
|
+
liveSync,
|
|
8813
|
+
canary,
|
|
8814
|
+
collector,
|
|
8815
|
+
provider
|
|
8816
|
+
};
|
|
8817
|
+
if (parsed.options.json === true) {
|
|
8818
|
+
out.log(JSON.stringify(status, null, 2));
|
|
8819
|
+
return;
|
|
8820
|
+
}
|
|
8821
|
+
printStatus2(status, out);
|
|
8822
|
+
}
|
|
8823
|
+
function statusMinutes(value) {
|
|
8824
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
|
|
8825
|
+
throw new Error("--minutes must be an integer from 1 to 10080");
|
|
8826
|
+
}
|
|
8827
|
+
return value;
|
|
8828
|
+
}
|
|
8829
|
+
async function read2(url, headers, doFetch) {
|
|
8830
|
+
const response2 = await doFetch(url, { headers });
|
|
8831
|
+
const text = await response2.text();
|
|
8832
|
+
let body = {};
|
|
8833
|
+
if (text) {
|
|
8834
|
+
try {
|
|
8835
|
+
const value = JSON.parse(text);
|
|
8836
|
+
body = value && typeof value === "object" && !Array.isArray(value) ? value : { value };
|
|
8837
|
+
} catch {
|
|
8838
|
+
body = { message: text.slice(0, 300) };
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
return { httpStatus: response2.status, body };
|
|
8842
|
+
}
|
|
8843
|
+
function printStatus2(status, out) {
|
|
8844
|
+
out.log(
|
|
8845
|
+
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
8846
|
+
);
|
|
8847
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
|
|
8848
|
+
const requests = routes.reduce(
|
|
8849
|
+
(total, row) => total + numeric3(row.requests),
|
|
8850
|
+
0
|
|
8851
|
+
);
|
|
8852
|
+
const errors = routes.reduce(
|
|
8853
|
+
(total, row) => total + numeric3(row.errors),
|
|
8854
|
+
0
|
|
8855
|
+
);
|
|
8856
|
+
out.log(
|
|
8857
|
+
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
8858
|
+
);
|
|
8859
|
+
out.log(
|
|
8860
|
+
liveSyncLine(status.liveSync)
|
|
8861
|
+
);
|
|
8862
|
+
const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
8863
|
+
out.log(
|
|
8864
|
+
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
8865
|
+
);
|
|
8866
|
+
const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
8867
|
+
const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
8868
|
+
out.log(
|
|
8869
|
+
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8870
|
+
);
|
|
8871
|
+
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8872
|
+
out.log(
|
|
8873
|
+
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
8874
|
+
);
|
|
8875
|
+
out.log(
|
|
8876
|
+
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8877
|
+
);
|
|
8878
|
+
}
|
|
8879
|
+
function liveSyncLine(read3) {
|
|
8880
|
+
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8881
|
+
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
8882
|
+
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`;
|
|
8883
|
+
}
|
|
8884
|
+
function record6(value) {
|
|
8885
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8886
|
+
}
|
|
8887
|
+
function numeric3(value) {
|
|
8888
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8889
|
+
}
|
|
8890
|
+
function optionalNumeric(value) {
|
|
8891
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8579
8894
|
// src/record.ts
|
|
8580
8895
|
import { appendFileSync } from "fs";
|
|
8581
8896
|
import process9 from "process";
|
|
@@ -9002,7 +9317,7 @@ var SOURCE2 = /\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
|
9002
9317
|
function gitRunner(cwd) {
|
|
9003
9318
|
return (args) => execFileSync2("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
9004
9319
|
}
|
|
9005
|
-
function collectDiff(runGit, base,
|
|
9320
|
+
function collectDiff(runGit, base, read3) {
|
|
9006
9321
|
let merged = "";
|
|
9007
9322
|
try {
|
|
9008
9323
|
merged += runGit(["diff", "--unified=3", `${base}...HEAD`]);
|
|
@@ -9015,9 +9330,9 @@ function collectDiff(runGit, base, read2) {
|
|
|
9015
9330
|
merged += runGit(["diff", "--unified=3", "HEAD"]);
|
|
9016
9331
|
} catch {
|
|
9017
9332
|
}
|
|
9018
|
-
return merged + untrackedDiff(runGit,
|
|
9333
|
+
return merged + untrackedDiff(runGit, read3);
|
|
9019
9334
|
}
|
|
9020
|
-
function untrackedDiff(runGit,
|
|
9335
|
+
function untrackedDiff(runGit, read3) {
|
|
9021
9336
|
let listed = "";
|
|
9022
9337
|
try {
|
|
9023
9338
|
listed = runGit(["ls-files", "--others", "--exclude-standard"]);
|
|
@@ -9033,7 +9348,7 @@ function untrackedDiff(runGit, read2) {
|
|
|
9033
9348
|
if (!SOURCE2.test(path)) continue;
|
|
9034
9349
|
let body;
|
|
9035
9350
|
try {
|
|
9036
|
-
body =
|
|
9351
|
+
body = read3(path);
|
|
9037
9352
|
} catch {
|
|
9038
9353
|
continue;
|
|
9039
9354
|
}
|
|
@@ -9115,8 +9430,8 @@ function report3(ctx, impacts) {
|
|
|
9115
9430
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
9116
9431
|
const cwd = deps.cwd ?? process.cwd();
|
|
9117
9432
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
9118
|
-
const
|
|
9119
|
-
const surfaces = changedSurfaces(collectDiff(runGit, options.base,
|
|
9433
|
+
const read3 = deps.readRepoFile ?? ((path) => readFileSync10(join10(cwd, path), "utf8"));
|
|
9434
|
+
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
9120
9435
|
if (!surfaces.length) {
|
|
9121
9436
|
return ctx.out.log(
|
|
9122
9437
|
ctx.json ? JSON.stringify({ base: options.base, impacts: [] }, null, 2) : `no changes against ${options.base}`
|
|
@@ -9981,33 +10296,37 @@ function exitCodeFor(err) {
|
|
|
9981
10296
|
return code === "handshake_pending" || code === "watch_timeout" ? 75 : 1;
|
|
9982
10297
|
}
|
|
9983
10298
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
10299
|
+
const runtime = {
|
|
10300
|
+
...dependencies,
|
|
10301
|
+
stdout: redactingOutput(dependencies.stdout ?? console)
|
|
10302
|
+
};
|
|
9984
10303
|
const parsed = parseArgv(argv);
|
|
9985
10304
|
recordInvocation(parsed);
|
|
9986
10305
|
const command = parsed.positionals[0] ?? "help";
|
|
9987
10306
|
if (command === "version" || command === "-v" || parsed.options.version === true) {
|
|
9988
10307
|
assertArgs(parsed, ["version"], 1);
|
|
9989
|
-
|
|
10308
|
+
runtime.stdout.log(cliVersion());
|
|
9990
10309
|
return;
|
|
9991
10310
|
}
|
|
9992
10311
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
9993
10312
|
assertArgs(parsed, ["help"], 1);
|
|
9994
|
-
printHelp();
|
|
10313
|
+
printHelp(runtime.stdout);
|
|
9995
10314
|
return;
|
|
9996
10315
|
}
|
|
9997
10316
|
if (command === "whoami") {
|
|
9998
|
-
await whoamiCommand(parsed,
|
|
10317
|
+
await whoamiCommand(parsed, runtime);
|
|
9999
10318
|
return;
|
|
10000
10319
|
}
|
|
10001
10320
|
if (command === "runbook") {
|
|
10002
|
-
await runbookCommand(parsed,
|
|
10321
|
+
await runbookCommand(parsed, runtime);
|
|
10003
10322
|
return;
|
|
10004
10323
|
}
|
|
10005
10324
|
if (command === "calendar") {
|
|
10006
|
-
await calendarCommand(parsed,
|
|
10325
|
+
await calendarCommand(parsed, runtime);
|
|
10007
10326
|
return;
|
|
10008
10327
|
}
|
|
10009
10328
|
if (command === "code") {
|
|
10010
|
-
await codeCommand(parsed,
|
|
10329
|
+
await codeCommand(parsed, runtime);
|
|
10011
10330
|
return;
|
|
10012
10331
|
}
|
|
10013
10332
|
if (command === "admin") {
|
|
@@ -10015,26 +10334,30 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10015
10334
|
return;
|
|
10016
10335
|
}
|
|
10017
10336
|
if (command === "app") {
|
|
10018
|
-
await appCommand(parsed,
|
|
10337
|
+
await appCommand(parsed, runtime);
|
|
10019
10338
|
return;
|
|
10020
10339
|
}
|
|
10021
10340
|
if (command === "security") {
|
|
10022
|
-
await securityCommand(parsed,
|
|
10341
|
+
await securityCommand(parsed, runtime);
|
|
10023
10342
|
return;
|
|
10024
10343
|
}
|
|
10025
10344
|
if (command === "pm") {
|
|
10026
|
-
await pmCommand(parsed,
|
|
10345
|
+
await pmCommand(parsed, runtime);
|
|
10027
10346
|
return;
|
|
10028
10347
|
}
|
|
10029
10348
|
if (command === "discuss") {
|
|
10030
|
-
await discussCommand(parsed,
|
|
10349
|
+
await discussCommand(parsed, runtime);
|
|
10350
|
+
return;
|
|
10351
|
+
}
|
|
10352
|
+
if (command === "o11y") {
|
|
10353
|
+
await o11yCommand(parsed, runtime);
|
|
10031
10354
|
return;
|
|
10032
10355
|
}
|
|
10033
10356
|
if (command === "provision") {
|
|
10034
|
-
await provisionCommand(parsed,
|
|
10357
|
+
await provisionCommand(parsed, runtime);
|
|
10035
10358
|
return;
|
|
10036
10359
|
}
|
|
10037
|
-
if (await projectCommand(command, parsed,
|
|
10360
|
+
if (await projectCommand(command, parsed, runtime)) return;
|
|
10038
10361
|
throw new Error(`unknown command "${command}". Run "odla-ai help".`);
|
|
10039
10362
|
}
|
|
10040
10363
|
async function provisionCommand(parsed, dependencies) {
|
|
@@ -10149,4 +10472,4 @@ export {
|
|
|
10149
10472
|
exitCodeFor,
|
|
10150
10473
|
runCli
|
|
10151
10474
|
};
|
|
10152
|
-
//# sourceMappingURL=chunk-
|
|
10475
|
+
//# sourceMappingURL=chunk-5U3EXWCR.js.map
|