@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
package/dist/bin.cjs
CHANGED
|
@@ -1875,6 +1875,37 @@ function redactSecrets(value) {
|
|
|
1875
1875
|
for (const [pattern, replacement] of REPLACEMENTS) result = result.replace(pattern, replacement);
|
|
1876
1876
|
return result;
|
|
1877
1877
|
}
|
|
1878
|
+
function redactingOutput(output) {
|
|
1879
|
+
const redactArgs = (args) => args.map((value) => redactOutputValue(value, /* @__PURE__ */ new WeakMap()));
|
|
1880
|
+
return {
|
|
1881
|
+
log: (...args) => output.log(...redactArgs(args)),
|
|
1882
|
+
error: (...args) => output.error(...redactArgs(args))
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
function redactOutputValue(value, seen) {
|
|
1886
|
+
if (typeof value === "string") return redactSecrets(value);
|
|
1887
|
+
if (value instanceof Error) {
|
|
1888
|
+
const redacted = new Error(redactSecrets(value.message));
|
|
1889
|
+
redacted.name = value.name;
|
|
1890
|
+
if (value.stack) redacted.stack = redactSecrets(value.stack);
|
|
1891
|
+
return redacted;
|
|
1892
|
+
}
|
|
1893
|
+
if (!value || typeof value !== "object") return value;
|
|
1894
|
+
const prior = seen.get(value);
|
|
1895
|
+
if (prior) return prior;
|
|
1896
|
+
if (Array.isArray(value)) {
|
|
1897
|
+
const next2 = [];
|
|
1898
|
+
seen.set(value, next2);
|
|
1899
|
+
for (const item of value) next2.push(redactOutputValue(item, seen));
|
|
1900
|
+
return next2;
|
|
1901
|
+
}
|
|
1902
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1903
|
+
if (prototype !== Object.prototype && prototype !== null) return value;
|
|
1904
|
+
const next = {};
|
|
1905
|
+
seen.set(value, next);
|
|
1906
|
+
for (const [key, item] of Object.entries(value)) next[key] = redactOutputValue(item, seen);
|
|
1907
|
+
return next;
|
|
1908
|
+
}
|
|
1878
1909
|
function looksSecret(value) {
|
|
1879
1910
|
return redactSecrets(value) !== value || value.includes("-----BEGIN");
|
|
1880
1911
|
}
|
|
@@ -2279,6 +2310,7 @@ var CAPABILITIES = {
|
|
|
2279
2310
|
"compose declared app-capability schema/rules, create guarded seeds when absent, and configure platform AI, auth, and deployment links",
|
|
2280
2311
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
2281
2312
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
2313
|
+
"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",
|
|
2282
2314
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
2283
2315
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
2284
2316
|
"let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
|
|
@@ -2299,7 +2331,7 @@ var CAPABILITIES = {
|
|
|
2299
2331
|
"approve GitHub App repository access separately from redacted-snippet disclosure"
|
|
2300
2332
|
],
|
|
2301
2333
|
studio: [
|
|
2302
|
-
"view telemetry and environment state",
|
|
2334
|
+
"view application telemetry, reconciled live-sync load/freshness, commit-to-visible canary health, provider-owned Worker runtime metrics, and environment state",
|
|
2303
2335
|
"let signed-in users inventory/revoke their own agent grants and admins audit/global-revoke them",
|
|
2304
2336
|
"review calendar connection, granted read scope, selected calendars, and sync health without exposing provider tokens",
|
|
2305
2337
|
"perform manual credential recovery \u2014 for the primary owner or any co-owner \u2014 when the CLI's local shown-once copy is unavailable",
|
|
@@ -3291,9 +3323,17 @@ async function smoke(options) {
|
|
|
3291
3323
|
throw new Error(`local credentials are for app "${credentials.appId}", but config app is "${cfg.app.id}"`);
|
|
3292
3324
|
}
|
|
3293
3325
|
const entry = credentials.envs[env];
|
|
3294
|
-
if (!entry?.tenantId
|
|
3326
|
+
if (!entry?.tenantId) {
|
|
3327
|
+
throw new Error(`local credentials have no tenant for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
3328
|
+
}
|
|
3329
|
+
const hasDb = cfg.services.includes("db");
|
|
3330
|
+
const hasO11y = cfg.services.includes("o11y");
|
|
3331
|
+
if (hasDb && !entry.dbKey) {
|
|
3295
3332
|
throw new Error(`local credentials have no db key for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
3296
3333
|
}
|
|
3334
|
+
if (hasO11y && !entry.o11yToken) {
|
|
3335
|
+
throw new Error(`local credentials have no o11y token for env "${env}". Run "odla-ai provision --write-dev-vars".`);
|
|
3336
|
+
}
|
|
3297
3337
|
const doFetch = options.fetch ?? fetch;
|
|
3298
3338
|
out.log(`smoke: ${cfg.app.id}/${env}`);
|
|
3299
3339
|
out.log(` tenant: ${entry.tenantId}`);
|
|
@@ -3306,6 +3346,7 @@ async function smoke(options) {
|
|
|
3306
3346
|
}
|
|
3307
3347
|
out.log(` ai: ${provider}`);
|
|
3308
3348
|
}
|
|
3349
|
+
if (hasO11y) out.log(` o11y: credentials present`);
|
|
3309
3350
|
if (cfg.services.includes("calendar")) {
|
|
3310
3351
|
const token = await getDeveloperToken(
|
|
3311
3352
|
cfg,
|
|
@@ -3324,26 +3365,30 @@ async function smoke(options) {
|
|
|
3324
3365
|
out.log(` calendar: ${status.status}, bookable (booking \u2192 ${status.bookingCalendarId ?? "primary"})`);
|
|
3325
3366
|
}
|
|
3326
3367
|
const database = await resolveDatabaseConfig(cfg);
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3368
|
+
if (hasDb) {
|
|
3369
|
+
const expectedSchema = database.schema;
|
|
3370
|
+
const expectedEntities = serializedEntities(expectedSchema);
|
|
3371
|
+
const liveSchemaPayload = await getJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/schema`, entry.dbKey);
|
|
3372
|
+
const liveSchema = liveSchemaPayload.schema ?? liveSchemaPayload;
|
|
3373
|
+
const liveEntities = serializedEntities(liveSchema);
|
|
3374
|
+
if (expectedEntities.length) {
|
|
3375
|
+
const missing = expectedEntities.filter((entity) => !liveEntities.includes(entity));
|
|
3376
|
+
if (missing.length) throw new Error(`live schema is missing expected entities: ${missing.join(", ")}`);
|
|
3377
|
+
}
|
|
3378
|
+
out.log(` schema: ${liveEntities.length} entities`);
|
|
3379
|
+
const aggregateEntity = expectedEntities[0] ?? liveEntities[0];
|
|
3380
|
+
if (aggregateEntity) {
|
|
3381
|
+
const aggregate = await postJson(doFetch, `${cfg.dbEndpoint}/app/${encodeURIComponent(entry.tenantId)}/aggregate`, entry.dbKey, {
|
|
3382
|
+
ns: aggregateEntity,
|
|
3383
|
+
aggregate: { count: true }
|
|
3384
|
+
});
|
|
3385
|
+
const count = aggregate.aggregate?.count;
|
|
3386
|
+
out.log(` aggregate: ${aggregateEntity}.count=${String(count ?? "ok")}`);
|
|
3387
|
+
} else {
|
|
3388
|
+
out.log(` aggregate: skipped (schema has no entities)`);
|
|
3389
|
+
}
|
|
3345
3390
|
} else {
|
|
3346
|
-
out.log(`
|
|
3391
|
+
out.log(` db: skipped (not enabled)`);
|
|
3347
3392
|
}
|
|
3348
3393
|
const probes = database.integrations.flatMap(
|
|
3349
3394
|
(integration) => (integration.probes ?? []).map((probe) => ({ integration: integration.id, probe }))
|
|
@@ -3396,7 +3441,7 @@ function publicConfigUrl(platformUrl, appId, env) {
|
|
|
3396
3441
|
}
|
|
3397
3442
|
async function safeText3(res) {
|
|
3398
3443
|
try {
|
|
3399
|
-
return (await res.text()).slice(0, 500);
|
|
3444
|
+
return redactSecrets((await res.text()).slice(0, 500));
|
|
3400
3445
|
} catch {
|
|
3401
3446
|
return "";
|
|
3402
3447
|
}
|
|
@@ -3901,8 +3946,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
3901
3946
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
3902
3947
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
3903
3948
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
3904
|
-
const entries = inventory.flatMap((
|
|
3905
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
3949
|
+
const entries = inventory.flatMap((record7) => {
|
|
3950
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record7);
|
|
3906
3951
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
3907
3952
|
});
|
|
3908
3953
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -4150,8 +4195,8 @@ function normalize(value) {
|
|
|
4150
4195
|
if (Array.isArray(value)) return value.map(normalize);
|
|
4151
4196
|
if (value instanceof Uint8Array) return { $bytes: [...value] };
|
|
4152
4197
|
if (typeof value === "object") {
|
|
4153
|
-
const
|
|
4154
|
-
return Object.fromEntries(Object.keys(
|
|
4198
|
+
const record7 = value;
|
|
4199
|
+
return Object.fromEntries(Object.keys(record7).filter((key) => record7[key] !== void 0).sort().map((key) => [key, normalize(record7[key])]));
|
|
4155
4200
|
}
|
|
4156
4201
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
4157
4202
|
}
|
|
@@ -6396,8 +6441,8 @@ function cliVersion() {
|
|
|
6396
6441
|
const pkg = JSON.parse((0, import_node_fs11.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
|
|
6397
6442
|
return pkg.version ?? "unknown";
|
|
6398
6443
|
}
|
|
6399
|
-
function printHelp() {
|
|
6400
|
-
|
|
6444
|
+
function printHelp(output = console) {
|
|
6445
|
+
output.log(`odla-ai
|
|
6401
6446
|
|
|
6402
6447
|
Start here:
|
|
6403
6448
|
odla-ai runbook ask "<question>" The current procedure, from odla's own
|
|
@@ -6429,22 +6474,23 @@ Usage:
|
|
|
6429
6474
|
odla-ai app owners add <email> [--email <odla-account>] [--json]
|
|
6430
6475
|
odla-ai app owners remove <email> [--email <odla-account>] [--json]
|
|
6431
6476
|
odla-ai pm <goal|task|decision|bug> list [--app <id>] [--status <s>] [--severity <s>] [--column <c>] [--q <text>] [--json]
|
|
6432
|
-
odla-ai pm <goal|task|decision|bug> add --app <id> --title <t> [--severity high|--column doing|--goal <id>|--description <text>|--body <text>|--proof <text>]
|
|
6477
|
+
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>]
|
|
6433
6478
|
odla-ai pm <goal|task|decision|bug> get <id> [--json]
|
|
6434
|
-
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>]
|
|
6435
|
-
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>]
|
|
6436
|
-
odla-ai pm <goal|task|decision|bug> comment <id> --body "..."
|
|
6479
|
+
odla-ai pm <goal|task|decision|bug> set <id> [--status <s>|--column <c>|--assignee <id>|--no-assignee|--goal <id>|--decision <id>] [--mutation-id <id>]
|
|
6480
|
+
odla-ai pm <goal|task|decision|bug> done <id> [--decision <accepted-decision-id>] [--mutation-id <id>]
|
|
6481
|
+
odla-ai pm <goal|task|decision|bug> comment <id> --body "..." [--mutation-id <id>]
|
|
6437
6482
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
6438
6483
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
6439
6484
|
odla-ai pm handoff --app <id> [--json]
|
|
6440
6485
|
odla-ai discuss groups [--json]
|
|
6441
6486
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
|
|
6442
6487
|
odla-ai discuss read <topic> [--json]
|
|
6443
|
-
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"]
|
|
6444
|
-
odla-ai discuss reply <topic> --body "..." [--markup "..."]
|
|
6445
|
-
odla-ai discuss resolve <topic> [--reopen]
|
|
6488
|
+
odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
|
|
6489
|
+
odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
|
|
6490
|
+
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
6446
6491
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
6447
6492
|
odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
|
|
6493
|
+
odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
|
|
6448
6494
|
odla-ai whoami [--json]
|
|
6449
6495
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
6450
6496
|
odla-ai runbook search "<question>" [--app <id>] [--all] [--limit <n>] [--json]
|
|
@@ -6556,6 +6602,11 @@ Commands:
|
|
|
6556
6602
|
(not 1) when it times out with nothing new, so a script can tell
|
|
6557
6603
|
"no answer yet" from a failure and just rerun. Use "who" to look
|
|
6558
6604
|
up the exact @[Label](kind/id) markup for a mention.
|
|
6605
|
+
o11y Read one stable status envelope for application RED, current
|
|
6606
|
+
live-sync load/freshness, the protected commit-to-visible
|
|
6607
|
+
canary, collector ingest/scheduler trust, Cloudflare-owned
|
|
6608
|
+
runtime metrics, and a machine verdict.
|
|
6609
|
+
--json keeps auth progress on stderr for unattended agents.
|
|
6559
6610
|
provision Register services, compose integrations, persist credentials, optionally push secrets.
|
|
6560
6611
|
smoke Verify credentials, public-config, composed schema, db aggregate, and integration probes.
|
|
6561
6612
|
skill Same installer; --agent accepts all, claude, codex, cursor,
|
|
@@ -7203,6 +7254,7 @@ async function codeCommand(parsed, dependencies) {
|
|
|
7203
7254
|
}
|
|
7204
7255
|
|
|
7205
7256
|
// src/discuss-actions.ts
|
|
7257
|
+
var writeMutationId = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7206
7258
|
async function request(ctx, method, path, body) {
|
|
7207
7259
|
const res = await ctx.doFetch(`${ctx.platformUrl}/registry/discuss${path}`, {
|
|
7208
7260
|
method,
|
|
@@ -7299,7 +7351,8 @@ async function discussPost(ctx, parsed) {
|
|
|
7299
7351
|
const created = await request(ctx, "POST", "/topics", {
|
|
7300
7352
|
appId,
|
|
7301
7353
|
subject,
|
|
7302
|
-
...content(parsed)
|
|
7354
|
+
...content(parsed),
|
|
7355
|
+
mutationId: writeMutationId(parsed)
|
|
7303
7356
|
});
|
|
7304
7357
|
emit(ctx, created, () => ctx.out.log(`opened topic ${created.id}`));
|
|
7305
7358
|
}
|
|
@@ -7308,16 +7361,16 @@ async function discussReply(ctx, id, parsed) {
|
|
|
7308
7361
|
ctx,
|
|
7309
7362
|
"POST",
|
|
7310
7363
|
`/topics/${encodeURIComponent(id)}/replies`,
|
|
7311
|
-
content(parsed)
|
|
7364
|
+
{ ...content(parsed), mutationId: writeMutationId(parsed) }
|
|
7312
7365
|
);
|
|
7313
7366
|
emit(ctx, created, () => ctx.out.log(`replied ${created.id}`));
|
|
7314
7367
|
}
|
|
7315
|
-
async function discussResolve(ctx, id, resolved) {
|
|
7368
|
+
async function discussResolve(ctx, id, resolved, parsed) {
|
|
7316
7369
|
const result = await request(
|
|
7317
7370
|
ctx,
|
|
7318
7371
|
"PATCH",
|
|
7319
7372
|
`/topics/${encodeURIComponent(id)}`,
|
|
7320
|
-
{ resolved }
|
|
7373
|
+
{ resolved, mutationId: writeMutationId(parsed) }
|
|
7321
7374
|
);
|
|
7322
7375
|
emit(ctx, result, () => ctx.out.log(`${resolved ? "resolved" : "reopened"} ${result.id}`));
|
|
7323
7376
|
}
|
|
@@ -7430,7 +7483,8 @@ var ALLOWED = [
|
|
|
7430
7483
|
"by",
|
|
7431
7484
|
"self",
|
|
7432
7485
|
"interval",
|
|
7433
|
-
"timeout"
|
|
7486
|
+
"timeout",
|
|
7487
|
+
"mutation-id"
|
|
7434
7488
|
];
|
|
7435
7489
|
function requireId(id, action) {
|
|
7436
7490
|
if (!id) throw new Error(`"discuss ${action}" needs a topic id`);
|
|
@@ -7472,7 +7526,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
7472
7526
|
case "reply":
|
|
7473
7527
|
return discussReply(ctx, requireId(id, "reply"), parsed);
|
|
7474
7528
|
case "resolve":
|
|
7475
|
-
return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true);
|
|
7529
|
+
return discussResolve(ctx, requireId(id, "resolve"), parsed.options.reopen !== true, parsed);
|
|
7476
7530
|
case "who":
|
|
7477
7531
|
return discussWho(ctx, parsed);
|
|
7478
7532
|
case "watch": {
|
|
@@ -7486,6 +7540,7 @@ async function discussCommand(parsed, deps = {}) {
|
|
|
7486
7540
|
}
|
|
7487
7541
|
|
|
7488
7542
|
// src/pm-actions.ts
|
|
7543
|
+
var writeMutationId2 = (parsed) => stringOpt(parsed.options["mutation-id"]) ?? crypto.randomUUID();
|
|
7489
7544
|
var FIELD_MAP = {
|
|
7490
7545
|
title: { key: "title" },
|
|
7491
7546
|
status: { key: "status" },
|
|
@@ -7587,25 +7642,35 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
7587
7642
|
if (!input.title) throw new Error("pm add needs --title <title>");
|
|
7588
7643
|
if (entity === "bug" && !input.description)
|
|
7589
7644
|
throw new Error("pm bug add needs --description <context> (or --body <context>)");
|
|
7590
|
-
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
7645
|
+
const res = await pmRequest(ctx, "POST", `/${entity}`, {
|
|
7646
|
+
appId,
|
|
7647
|
+
input,
|
|
7648
|
+
mutationId: writeMutationId2(parsed)
|
|
7649
|
+
});
|
|
7591
7650
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
7592
7651
|
}
|
|
7593
7652
|
async function pmGet(ctx, entity, id) {
|
|
7594
|
-
const { record:
|
|
7595
|
-
emit2(ctx,
|
|
7653
|
+
const { record: record7 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
7654
|
+
emit2(ctx, record7, () => printRecord(ctx, entity, record7));
|
|
7596
7655
|
}
|
|
7597
7656
|
async function pmSet(ctx, entity, id, parsed) {
|
|
7598
7657
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
7599
7658
|
if (Object.keys(patch2).length === 0)
|
|
7600
7659
|
throw new Error("pm set needs at least one field flag (e.g. --status doing, --assignee me, --no-assignee)");
|
|
7601
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
7660
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
7661
|
+
patch: patch2,
|
|
7662
|
+
mutationId: writeMutationId2(parsed)
|
|
7663
|
+
});
|
|
7602
7664
|
emit2(ctx, res, () => ctx.out.log(res.record ? `${res.record.id} [${statusCol(entity, res.record)}] ${res.record.appId} ${res.record.title ?? ""}` : `updated ${entity} ${id}`));
|
|
7603
7665
|
}
|
|
7604
7666
|
async function pmDone(ctx, entity, id, parsed) {
|
|
7605
7667
|
const decisionId = stringOpt(parsed.options.decision);
|
|
7606
7668
|
if (decisionId && entity !== "bug") throw new Error("--decision is only valid when completing a bug");
|
|
7607
7669
|
const patch2 = { ...DONE[entity], ...decisionId ? { decisionId } : {} };
|
|
7608
|
-
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
7670
|
+
const res = await pmRequest(ctx, "PATCH", `/${entity}/${encodeURIComponent(id)}`, {
|
|
7671
|
+
patch: patch2,
|
|
7672
|
+
mutationId: writeMutationId2(parsed)
|
|
7673
|
+
});
|
|
7609
7674
|
emit2(ctx, res, () => ctx.out.log(`${entity} ${id} \u2192 done`));
|
|
7610
7675
|
}
|
|
7611
7676
|
async function allRecords(ctx, entity, appId) {
|
|
@@ -7631,9 +7696,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
7631
7696
|
]);
|
|
7632
7697
|
const handoff = {
|
|
7633
7698
|
appId,
|
|
7634
|
-
unmetGoals: goals.filter((
|
|
7635
|
-
activeTasks: tasks.filter((
|
|
7636
|
-
openBugs: bugs.filter((
|
|
7699
|
+
unmetGoals: goals.filter((record7) => record7.status !== "met"),
|
|
7700
|
+
activeTasks: tasks.filter((record7) => record7.column !== "done"),
|
|
7701
|
+
openBugs: bugs.filter((record7) => record7.status !== "fixed" && record7.status !== "wontfix")
|
|
7637
7702
|
};
|
|
7638
7703
|
const result = {
|
|
7639
7704
|
...handoff,
|
|
@@ -7652,10 +7717,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
7652
7717
|
]) {
|
|
7653
7718
|
ctx.out.log(`${label}:`);
|
|
7654
7719
|
if (!records.length) ctx.out.log("- (none)");
|
|
7655
|
-
else for (const
|
|
7720
|
+
else for (const record7 of records) printRecord(
|
|
7656
7721
|
ctx,
|
|
7657
7722
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
7658
|
-
|
|
7723
|
+
record7
|
|
7659
7724
|
);
|
|
7660
7725
|
}
|
|
7661
7726
|
});
|
|
@@ -7663,7 +7728,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
7663
7728
|
async function pmComment(ctx, entity, id, parsed) {
|
|
7664
7729
|
const body = stringOpt(parsed.options.body);
|
|
7665
7730
|
if (!body) throw new Error('pm comment needs --body "..."');
|
|
7666
|
-
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
|
|
7731
|
+
await pmRequest(ctx, "POST", `/${entity}/${encodeURIComponent(id)}/comments`, {
|
|
7732
|
+
body,
|
|
7733
|
+
mutationId: writeMutationId2(parsed)
|
|
7734
|
+
});
|
|
7667
7735
|
ctx.out.log(`commented on ${entity} ${id}`);
|
|
7668
7736
|
}
|
|
7669
7737
|
async function pmComments(ctx, entity, id) {
|
|
@@ -7713,7 +7781,8 @@ var ALLOWED2 = [
|
|
|
7713
7781
|
"decision",
|
|
7714
7782
|
"limit",
|
|
7715
7783
|
"offset",
|
|
7716
|
-
"q"
|
|
7784
|
+
"q",
|
|
7785
|
+
"mutation-id"
|
|
7717
7786
|
];
|
|
7718
7787
|
function requireId2(id, action) {
|
|
7719
7788
|
if (!id) throw new Error(`"pm ... ${action}" needs an item id`);
|
|
@@ -7770,6 +7839,260 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
7770
7839
|
}
|
|
7771
7840
|
}
|
|
7772
7841
|
|
|
7842
|
+
// src/o11y-verdict.ts
|
|
7843
|
+
function statusVerdict(reads) {
|
|
7844
|
+
const reasons = [];
|
|
7845
|
+
sourceHttp(reasons, "application", reads.application);
|
|
7846
|
+
sourceHttp(reasons, "liveSync", reads.liveSync);
|
|
7847
|
+
sourceHttp(reasons, "canary", reads.canary);
|
|
7848
|
+
sourceHttp(reasons, "collector", reads.collector);
|
|
7849
|
+
sourceHttp(reasons, "provider", reads.provider);
|
|
7850
|
+
const liveStatus = String(reads.liveSync.body.status ?? "");
|
|
7851
|
+
if (liveStatus === "partial") {
|
|
7852
|
+
reasons.push({
|
|
7853
|
+
source: "liveSync",
|
|
7854
|
+
code: "partial_reconciliation",
|
|
7855
|
+
severity: "degraded"
|
|
7856
|
+
});
|
|
7857
|
+
} else if (liveStatus === "stale" || liveStatus === "unavailable") {
|
|
7858
|
+
reasons.push({
|
|
7859
|
+
source: "liveSync",
|
|
7860
|
+
code: `reconciliation_${liveStatus}`,
|
|
7861
|
+
severity: "degraded"
|
|
7862
|
+
});
|
|
7863
|
+
}
|
|
7864
|
+
const performance = record5(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
7865
|
+
if (performance?.status === "unavailable") {
|
|
7866
|
+
reasons.push({
|
|
7867
|
+
source: "liveSync",
|
|
7868
|
+
code: "subscription_metrics_unavailable",
|
|
7869
|
+
severity: "degraded"
|
|
7870
|
+
});
|
|
7871
|
+
}
|
|
7872
|
+
if (numeric2(performance?.telemetryDropped) > 0) {
|
|
7873
|
+
reasons.push({
|
|
7874
|
+
source: "liveSync",
|
|
7875
|
+
code: "subscription_telemetry_dropped",
|
|
7876
|
+
severity: "degraded"
|
|
7877
|
+
});
|
|
7878
|
+
}
|
|
7879
|
+
const canaryStatus = String(reads.canary.body.status ?? "");
|
|
7880
|
+
if (canaryStatus === "failing") {
|
|
7881
|
+
reasons.push({
|
|
7882
|
+
source: "canary",
|
|
7883
|
+
code: String(reads.canary.body.errorCode ?? "journey_failed"),
|
|
7884
|
+
severity: "unhealthy"
|
|
7885
|
+
});
|
|
7886
|
+
} else if (canaryStatus === "stale") {
|
|
7887
|
+
reasons.push({
|
|
7888
|
+
source: "canary",
|
|
7889
|
+
code: "journey_stale",
|
|
7890
|
+
severity: "unhealthy"
|
|
7891
|
+
});
|
|
7892
|
+
} else if (canaryStatus === "pending" || canaryStatus === "not_configured") {
|
|
7893
|
+
reasons.push({
|
|
7894
|
+
source: "canary",
|
|
7895
|
+
code: `journey_${canaryStatus}`,
|
|
7896
|
+
severity: "degraded"
|
|
7897
|
+
});
|
|
7898
|
+
}
|
|
7899
|
+
const collectorStatus = String(reads.collector.body.status ?? "");
|
|
7900
|
+
if (collectorStatus === "unhealthy") {
|
|
7901
|
+
reasons.push({
|
|
7902
|
+
source: "collector",
|
|
7903
|
+
code: collectorReason(reads.collector, "collector_unhealthy"),
|
|
7904
|
+
severity: "unhealthy"
|
|
7905
|
+
});
|
|
7906
|
+
} else if (collectorStatus === "degraded") {
|
|
7907
|
+
reasons.push({
|
|
7908
|
+
source: "collector",
|
|
7909
|
+
code: collectorReason(reads.collector, "collector_degraded"),
|
|
7910
|
+
severity: "degraded"
|
|
7911
|
+
});
|
|
7912
|
+
}
|
|
7913
|
+
const providerStatus = String(reads.provider.body.status ?? "");
|
|
7914
|
+
if (providerStatus && providerStatus !== "observed") {
|
|
7915
|
+
reasons.push({
|
|
7916
|
+
source: "provider",
|
|
7917
|
+
code: `provider_${providerStatus}`,
|
|
7918
|
+
severity: "degraded"
|
|
7919
|
+
});
|
|
7920
|
+
}
|
|
7921
|
+
return {
|
|
7922
|
+
status: reasons.some((reason) => reason.severity === "unhealthy") ? "unhealthy" : reasons.length > 0 ? "degraded" : "healthy",
|
|
7923
|
+
reasons
|
|
7924
|
+
};
|
|
7925
|
+
}
|
|
7926
|
+
function record5(value) {
|
|
7927
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7928
|
+
}
|
|
7929
|
+
function numeric2(value) {
|
|
7930
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
7931
|
+
}
|
|
7932
|
+
function collectorReason(read3, fallback) {
|
|
7933
|
+
const reasons = read3.body.reasons;
|
|
7934
|
+
if (!Array.isArray(reasons)) return fallback;
|
|
7935
|
+
const first = reasons.find(
|
|
7936
|
+
(reason) => Boolean(reason) && typeof reason === "object" && !Array.isArray(reason)
|
|
7937
|
+
);
|
|
7938
|
+
return typeof first?.code === "string" && first.code ? first.code : fallback;
|
|
7939
|
+
}
|
|
7940
|
+
function sourceHttp(reasons, source, read3) {
|
|
7941
|
+
if (read3.httpStatus >= 200 && read3.httpStatus < 300) return;
|
|
7942
|
+
reasons.push({
|
|
7943
|
+
source,
|
|
7944
|
+
code: `http_${read3.httpStatus}`,
|
|
7945
|
+
severity: read3.httpStatus >= 500 && read3.httpStatus !== 501 ? "unhealthy" : "degraded"
|
|
7946
|
+
});
|
|
7947
|
+
}
|
|
7948
|
+
|
|
7949
|
+
// src/o11y-command.ts
|
|
7950
|
+
async function o11yCommand(parsed, deps = {}) {
|
|
7951
|
+
assertArgs(
|
|
7952
|
+
parsed,
|
|
7953
|
+
["config", "token", "email", "json", "app", "env", "minutes"],
|
|
7954
|
+
2
|
|
7955
|
+
);
|
|
7956
|
+
const action = parsed.positionals[1];
|
|
7957
|
+
if (action !== "status") {
|
|
7958
|
+
throw new Error(
|
|
7959
|
+
`unknown o11y action "${action ?? ""}". Try "odla-ai o11y status --json".`
|
|
7960
|
+
);
|
|
7961
|
+
}
|
|
7962
|
+
const minutes = statusMinutes(
|
|
7963
|
+
numberOpt(parsed.options.minutes, "--minutes") ?? 60
|
|
7964
|
+
);
|
|
7965
|
+
const cfg = await loadProjectConfig(
|
|
7966
|
+
stringOpt(parsed.options.config) ?? "odla.config.mjs"
|
|
7967
|
+
);
|
|
7968
|
+
const doFetch = deps.fetch ?? fetch;
|
|
7969
|
+
const out = deps.stdout ?? console;
|
|
7970
|
+
const token = await getDeveloperToken(
|
|
7971
|
+
cfg,
|
|
7972
|
+
{
|
|
7973
|
+
configPath: cfg.configPath,
|
|
7974
|
+
token: stringOpt(parsed.options.token),
|
|
7975
|
+
email: stringOpt(parsed.options.email),
|
|
7976
|
+
open: false
|
|
7977
|
+
},
|
|
7978
|
+
doFetch,
|
|
7979
|
+
out
|
|
7980
|
+
);
|
|
7981
|
+
const appId = stringOpt(parsed.options.app)?.trim() || cfg.app.id;
|
|
7982
|
+
const env = stringOpt(parsed.options.env)?.trim() || "prod";
|
|
7983
|
+
const base = `${cfg.platformUrl}/o11y/${encodeURIComponent(appId)}`;
|
|
7984
|
+
const query = new URLSearchParams({ env, minutes: String(minutes) });
|
|
7985
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
7986
|
+
const [application, liveSync, canary, collector, provider] = await Promise.all([
|
|
7987
|
+
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
7988
|
+
read2(
|
|
7989
|
+
`${base}/live-sync/health?${query}`,
|
|
7990
|
+
headers,
|
|
7991
|
+
doFetch
|
|
7992
|
+
),
|
|
7993
|
+
read2(
|
|
7994
|
+
`${base}/live-sync/canary?${new URLSearchParams({ env })}`,
|
|
7995
|
+
headers,
|
|
7996
|
+
doFetch
|
|
7997
|
+
),
|
|
7998
|
+
read2(`${base}/self-health?${query}`, headers, doFetch),
|
|
7999
|
+
read2(`${base}/provider/cloudflare?${query}`, headers, doFetch)
|
|
8000
|
+
]);
|
|
8001
|
+
const verdict = statusVerdict({
|
|
8002
|
+
application,
|
|
8003
|
+
liveSync,
|
|
8004
|
+
canary,
|
|
8005
|
+
collector,
|
|
8006
|
+
provider
|
|
8007
|
+
});
|
|
8008
|
+
const status = {
|
|
8009
|
+
schemaVersion: 4,
|
|
8010
|
+
scope: { appId, env, minutes },
|
|
8011
|
+
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8012
|
+
verdict,
|
|
8013
|
+
application,
|
|
8014
|
+
liveSync,
|
|
8015
|
+
canary,
|
|
8016
|
+
collector,
|
|
8017
|
+
provider
|
|
8018
|
+
};
|
|
8019
|
+
if (parsed.options.json === true) {
|
|
8020
|
+
out.log(JSON.stringify(status, null, 2));
|
|
8021
|
+
return;
|
|
8022
|
+
}
|
|
8023
|
+
printStatus2(status, out);
|
|
8024
|
+
}
|
|
8025
|
+
function statusMinutes(value) {
|
|
8026
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 7 * 24 * 60) {
|
|
8027
|
+
throw new Error("--minutes must be an integer from 1 to 10080");
|
|
8028
|
+
}
|
|
8029
|
+
return value;
|
|
8030
|
+
}
|
|
8031
|
+
async function read2(url, headers, doFetch) {
|
|
8032
|
+
const response2 = await doFetch(url, { headers });
|
|
8033
|
+
const text = await response2.text();
|
|
8034
|
+
let body = {};
|
|
8035
|
+
if (text) {
|
|
8036
|
+
try {
|
|
8037
|
+
const value = JSON.parse(text);
|
|
8038
|
+
body = value && typeof value === "object" && !Array.isArray(value) ? value : { value };
|
|
8039
|
+
} catch {
|
|
8040
|
+
body = { message: text.slice(0, 300) };
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
return { httpStatus: response2.status, body };
|
|
8044
|
+
}
|
|
8045
|
+
function printStatus2(status, out) {
|
|
8046
|
+
out.log(
|
|
8047
|
+
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
8048
|
+
);
|
|
8049
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
|
|
8050
|
+
const requests = routes.reduce(
|
|
8051
|
+
(total, row) => total + numeric3(row.requests),
|
|
8052
|
+
0
|
|
8053
|
+
);
|
|
8054
|
+
const errors = routes.reduce(
|
|
8055
|
+
(total, row) => total + numeric3(row.errors),
|
|
8056
|
+
0
|
|
8057
|
+
);
|
|
8058
|
+
out.log(
|
|
8059
|
+
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
8060
|
+
);
|
|
8061
|
+
out.log(
|
|
8062
|
+
liveSyncLine(status.liveSync)
|
|
8063
|
+
);
|
|
8064
|
+
const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
8065
|
+
out.log(
|
|
8066
|
+
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
8067
|
+
);
|
|
8068
|
+
const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
8069
|
+
const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
8070
|
+
out.log(
|
|
8071
|
+
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8072
|
+
);
|
|
8073
|
+
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8074
|
+
out.log(
|
|
8075
|
+
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
8076
|
+
);
|
|
8077
|
+
out.log(
|
|
8078
|
+
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8079
|
+
);
|
|
8080
|
+
}
|
|
8081
|
+
function liveSyncLine(read3) {
|
|
8082
|
+
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8083
|
+
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
8084
|
+
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`;
|
|
8085
|
+
}
|
|
8086
|
+
function record6(value) {
|
|
8087
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8088
|
+
}
|
|
8089
|
+
function numeric3(value) {
|
|
8090
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8091
|
+
}
|
|
8092
|
+
function optionalNumeric(value) {
|
|
8093
|
+
return typeof value === "number" && Number.isFinite(value) ? `${value} ms` : "\u2014";
|
|
8094
|
+
}
|
|
8095
|
+
|
|
7773
8096
|
// src/provision.ts
|
|
7774
8097
|
var import_apps5 = require("@odla-ai/apps");
|
|
7775
8098
|
var import_ai3 = require("@odla-ai/ai");
|
|
@@ -7947,15 +8270,6 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
7947
8270
|
}
|
|
7948
8271
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
7949
8272
|
}
|
|
7950
|
-
async function readRegistryApp(cfg, token, doFetch) {
|
|
7951
|
-
const res = await doFetch(`${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}`, {
|
|
7952
|
-
headers: { authorization: `Bearer ${token}` }
|
|
7953
|
-
});
|
|
7954
|
-
if (res.status === 404) return null;
|
|
7955
|
-
if (!res.ok) return null;
|
|
7956
|
-
const json = await res.json();
|
|
7957
|
-
return json.app ?? null;
|
|
7958
|
-
}
|
|
7959
8273
|
async function postJson3(doFetch, url, bearer, body) {
|
|
7960
8274
|
const res = await doFetch(url, {
|
|
7961
8275
|
method: "POST",
|
|
@@ -8054,7 +8368,7 @@ async function provision(options) {
|
|
|
8054
8368
|
const doFetch = options.fetch ?? fetch;
|
|
8055
8369
|
const token = await getDeveloperToken(cfg, options, doFetch, out);
|
|
8056
8370
|
const apps = (0, import_apps5.createAppsClient)({ endpoint: cfg.platformUrl, token, fetcher: { fetch: doFetch } });
|
|
8057
|
-
const existing = await
|
|
8371
|
+
const existing = await apps.resolveApp(cfg.app.id);
|
|
8058
8372
|
if (existing) {
|
|
8059
8373
|
out.log(`app: ${cfg.app.id} already exists`);
|
|
8060
8374
|
} else {
|
|
@@ -8245,6 +8559,7 @@ var COMMAND_SURFACE = {
|
|
|
8245
8559
|
doctor: {},
|
|
8246
8560
|
help: {},
|
|
8247
8561
|
init: {},
|
|
8562
|
+
o11y: { status: {} },
|
|
8248
8563
|
pm: {
|
|
8249
8564
|
...PM_ENTITIES,
|
|
8250
8565
|
handoff: {}
|
|
@@ -8745,7 +9060,7 @@ var SOURCE2 = /\.(ts|tsx|js|jsx|mts|cts)$/;
|
|
|
8745
9060
|
function gitRunner(cwd) {
|
|
8746
9061
|
return (args) => (0, import_node_child_process7.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
|
|
8747
9062
|
}
|
|
8748
|
-
function collectDiff(runGit, base,
|
|
9063
|
+
function collectDiff(runGit, base, read3) {
|
|
8749
9064
|
let merged = "";
|
|
8750
9065
|
try {
|
|
8751
9066
|
merged += runGit(["diff", "--unified=3", `${base}...HEAD`]);
|
|
@@ -8758,9 +9073,9 @@ function collectDiff(runGit, base, read2) {
|
|
|
8758
9073
|
merged += runGit(["diff", "--unified=3", "HEAD"]);
|
|
8759
9074
|
} catch {
|
|
8760
9075
|
}
|
|
8761
|
-
return merged + untrackedDiff(runGit,
|
|
9076
|
+
return merged + untrackedDiff(runGit, read3);
|
|
8762
9077
|
}
|
|
8763
|
-
function untrackedDiff(runGit,
|
|
9078
|
+
function untrackedDiff(runGit, read3) {
|
|
8764
9079
|
let listed = "";
|
|
8765
9080
|
try {
|
|
8766
9081
|
listed = runGit(["ls-files", "--others", "--exclude-standard"]);
|
|
@@ -8776,7 +9091,7 @@ function untrackedDiff(runGit, read2) {
|
|
|
8776
9091
|
if (!SOURCE2.test(path)) continue;
|
|
8777
9092
|
let body;
|
|
8778
9093
|
try {
|
|
8779
|
-
body =
|
|
9094
|
+
body = read3(path);
|
|
8780
9095
|
} catch {
|
|
8781
9096
|
continue;
|
|
8782
9097
|
}
|
|
@@ -8858,8 +9173,8 @@ function report3(ctx, impacts) {
|
|
|
8858
9173
|
async function runbookImpact(ctx, options, deps = {}) {
|
|
8859
9174
|
const cwd = deps.cwd ?? process.cwd();
|
|
8860
9175
|
const runGit = deps.runGit ?? gitRunner(cwd);
|
|
8861
|
-
const
|
|
8862
|
-
const surfaces = changedSurfaces(collectDiff(runGit, options.base,
|
|
9176
|
+
const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs16.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
|
|
9177
|
+
const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
|
|
8863
9178
|
if (!surfaces.length) {
|
|
8864
9179
|
return ctx.out.log(
|
|
8865
9180
|
ctx.json ? JSON.stringify({ base: options.base, impacts: [] }, null, 2) : `no changes against ${options.base}`
|
|
@@ -9982,33 +10297,37 @@ function exitCodeFor(err) {
|
|
|
9982
10297
|
return code === "handshake_pending" || code === "watch_timeout" ? 75 : 1;
|
|
9983
10298
|
}
|
|
9984
10299
|
async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
10300
|
+
const runtime = {
|
|
10301
|
+
...dependencies,
|
|
10302
|
+
stdout: redactingOutput(dependencies.stdout ?? console)
|
|
10303
|
+
};
|
|
9985
10304
|
const parsed = parseArgv(argv);
|
|
9986
10305
|
recordInvocation(parsed);
|
|
9987
10306
|
const command = parsed.positionals[0] ?? "help";
|
|
9988
10307
|
if (command === "version" || command === "-v" || parsed.options.version === true) {
|
|
9989
10308
|
assertArgs(parsed, ["version"], 1);
|
|
9990
|
-
|
|
10309
|
+
runtime.stdout.log(cliVersion());
|
|
9991
10310
|
return;
|
|
9992
10311
|
}
|
|
9993
10312
|
if (command === "help" || command === "--help" || command === "-h") {
|
|
9994
10313
|
assertArgs(parsed, ["help"], 1);
|
|
9995
|
-
printHelp();
|
|
10314
|
+
printHelp(runtime.stdout);
|
|
9996
10315
|
return;
|
|
9997
10316
|
}
|
|
9998
10317
|
if (command === "whoami") {
|
|
9999
|
-
await whoamiCommand(parsed,
|
|
10318
|
+
await whoamiCommand(parsed, runtime);
|
|
10000
10319
|
return;
|
|
10001
10320
|
}
|
|
10002
10321
|
if (command === "runbook") {
|
|
10003
|
-
await runbookCommand(parsed,
|
|
10322
|
+
await runbookCommand(parsed, runtime);
|
|
10004
10323
|
return;
|
|
10005
10324
|
}
|
|
10006
10325
|
if (command === "calendar") {
|
|
10007
|
-
await calendarCommand(parsed,
|
|
10326
|
+
await calendarCommand(parsed, runtime);
|
|
10008
10327
|
return;
|
|
10009
10328
|
}
|
|
10010
10329
|
if (command === "code") {
|
|
10011
|
-
await codeCommand(parsed,
|
|
10330
|
+
await codeCommand(parsed, runtime);
|
|
10012
10331
|
return;
|
|
10013
10332
|
}
|
|
10014
10333
|
if (command === "admin") {
|
|
@@ -10016,26 +10335,30 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10016
10335
|
return;
|
|
10017
10336
|
}
|
|
10018
10337
|
if (command === "app") {
|
|
10019
|
-
await appCommand(parsed,
|
|
10338
|
+
await appCommand(parsed, runtime);
|
|
10020
10339
|
return;
|
|
10021
10340
|
}
|
|
10022
10341
|
if (command === "security") {
|
|
10023
|
-
await securityCommand(parsed,
|
|
10342
|
+
await securityCommand(parsed, runtime);
|
|
10024
10343
|
return;
|
|
10025
10344
|
}
|
|
10026
10345
|
if (command === "pm") {
|
|
10027
|
-
await pmCommand(parsed,
|
|
10346
|
+
await pmCommand(parsed, runtime);
|
|
10028
10347
|
return;
|
|
10029
10348
|
}
|
|
10030
10349
|
if (command === "discuss") {
|
|
10031
|
-
await discussCommand(parsed,
|
|
10350
|
+
await discussCommand(parsed, runtime);
|
|
10351
|
+
return;
|
|
10352
|
+
}
|
|
10353
|
+
if (command === "o11y") {
|
|
10354
|
+
await o11yCommand(parsed, runtime);
|
|
10032
10355
|
return;
|
|
10033
10356
|
}
|
|
10034
10357
|
if (command === "provision") {
|
|
10035
|
-
await provisionCommand(parsed,
|
|
10358
|
+
await provisionCommand(parsed, runtime);
|
|
10036
10359
|
return;
|
|
10037
10360
|
}
|
|
10038
|
-
if (await projectCommand(command, parsed,
|
|
10361
|
+
if (await projectCommand(command, parsed, runtime)) return;
|
|
10039
10362
|
throw new Error(`unknown command "${command}". Run "odla-ai help".`);
|
|
10040
10363
|
}
|
|
10041
10364
|
async function provisionCommand(parsed, dependencies) {
|
|
@@ -10101,7 +10424,7 @@ async function calendarCommand(parsed, dependencies) {
|
|
|
10101
10424
|
|
|
10102
10425
|
// src/bin.ts
|
|
10103
10426
|
runCli().catch((err) => {
|
|
10104
|
-
console.error(`odla-ai: ${err instanceof Error ? err.message : String(err)}`);
|
|
10427
|
+
console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
|
|
10105
10428
|
process.exitCode = exitCodeFor(err);
|
|
10106
10429
|
});
|
|
10107
10430
|
//# sourceMappingURL=bin.cjs.map
|