@odla-ai/cli 0.25.6 → 0.25.8
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 -1
- package/dist/bin.cjs +144 -30
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-MWLZO2EZ.js → chunk-PM5FBL3S.js} +145 -31
- package/dist/chunk-PM5FBL3S.js.map +1 -0
- package/dist/index.cjs +144 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +4 -0
- package/dist/chunk-MWLZO2EZ.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1617,6 +1617,7 @@ var CAPABILITIES = {
|
|
|
1617
1617
|
"apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
|
|
1618
1618
|
"validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
|
|
1619
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",
|
|
1620
|
+
"inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
|
|
1620
1621
|
"run app-attributed hosted security discovery and independent validation without provider keys",
|
|
1621
1622
|
"connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
|
|
1622
1623
|
"let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
|
|
@@ -3451,8 +3452,8 @@ async function materializeGitTree(source, commitSha, options = {}) {
|
|
|
3451
3452
|
const maxFiles = options.maxFiles ?? 2e4;
|
|
3452
3453
|
const maxBytes = options.maxBytes ?? 512 * 1024 * 1024;
|
|
3453
3454
|
const inventory = (await gitOutput(sourceDir, ["ls-tree", "-rz", commitSha], 16 * 1024 * 1024)).toString("utf8").split("\0").filter(Boolean);
|
|
3454
|
-
const entries = inventory.flatMap((
|
|
3455
|
-
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(
|
|
3455
|
+
const entries = inventory.flatMap((record7) => {
|
|
3456
|
+
const match = /^(100644|100755) blob ([0-9a-f]{40,64})\t([\s\S]+)$/.exec(record7);
|
|
3456
3457
|
return match && allowedWorkspacePath(match[3]) ? [{ mode: match[1], hash: match[2], path: match[3] }] : [];
|
|
3457
3458
|
});
|
|
3458
3459
|
if (entries.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
@@ -3700,8 +3701,8 @@ function normalize(value) {
|
|
|
3700
3701
|
if (Array.isArray(value)) return value.map(normalize);
|
|
3701
3702
|
if (value instanceof Uint8Array) return { $bytes: [...value] };
|
|
3702
3703
|
if (typeof value === "object") {
|
|
3703
|
-
const
|
|
3704
|
-
return Object.fromEntries(Object.keys(
|
|
3704
|
+
const record7 = value;
|
|
3705
|
+
return Object.fromEntries(Object.keys(record7).filter((key) => record7[key] !== void 0).sort().map((key) => [key, normalize(record7[key])]));
|
|
3705
3706
|
}
|
|
3706
3707
|
throw new CamelError("state_conflict", "Canonical JSON rejects unsupported values.");
|
|
3707
3708
|
}
|
|
@@ -5995,6 +5996,8 @@ Usage:
|
|
|
5995
5996
|
odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
|
|
5996
5997
|
odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
|
|
5997
5998
|
odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
|
|
5999
|
+
odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
|
|
6000
|
+
odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
|
|
5998
6001
|
odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
|
|
5999
6002
|
odla-ai whoami [--json]
|
|
6000
6003
|
odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
|
|
@@ -6042,6 +6045,8 @@ Usage:
|
|
|
6042
6045
|
odla-ai version
|
|
6043
6046
|
|
|
6044
6047
|
Commands:
|
|
6048
|
+
agent Inspect durable agent wakeups and explicitly requeue a
|
|
6049
|
+
dead-lettered job; JSON output is stable for remote operators.
|
|
6045
6050
|
runbook odla's operational procedures, stored in the database and read at
|
|
6046
6051
|
the moment they are followed. "ask" gives a written, cited answer;
|
|
6047
6052
|
"search" the passages behind it; "get" the whole document.
|
|
@@ -6817,6 +6822,7 @@ var PM_ENTITIES = Object.fromEntries(
|
|
|
6817
6822
|
["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
|
|
6818
6823
|
);
|
|
6819
6824
|
var COMMAND_SURFACE = {
|
|
6825
|
+
agent: { jobs: {}, retry: {} },
|
|
6820
6826
|
admin: {
|
|
6821
6827
|
ai: {
|
|
6822
6828
|
show: {},
|
|
@@ -7353,11 +7359,6 @@ async function adminCommand(parsed) {
|
|
|
7353
7359
|
});
|
|
7354
7360
|
}
|
|
7355
7361
|
|
|
7356
|
-
// src/app-export.ts
|
|
7357
|
-
import { createWriteStream } from "fs";
|
|
7358
|
-
import { Readable } from "stream";
|
|
7359
|
-
import { pipeline } from "stream/promises";
|
|
7360
|
-
|
|
7361
7362
|
// src/tenant.ts
|
|
7362
7363
|
import { tenantIdFor as tenantIdFor5 } from "@odla-ai/apps";
|
|
7363
7364
|
function resolveEnv(cfg, requested) {
|
|
@@ -7382,7 +7383,90 @@ function bothTenants(cfg) {
|
|
|
7382
7383
|
return { sandbox: tenantIdFor5(cfg.app.id, "dev"), live: tenantIdFor5(cfg.app.id, "prod") };
|
|
7383
7384
|
}
|
|
7384
7385
|
|
|
7386
|
+
// src/agent-command.ts
|
|
7387
|
+
async function agentCommand(parsed, deps = {}) {
|
|
7388
|
+
const action = parsed.positionals[1];
|
|
7389
|
+
if (action !== "jobs" && action !== "retry") {
|
|
7390
|
+
throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
|
|
7391
|
+
}
|
|
7392
|
+
assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
|
|
7393
|
+
if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
|
|
7394
|
+
throw new Error('--state and --limit are supported only by "agent jobs"');
|
|
7395
|
+
}
|
|
7396
|
+
const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
|
|
7397
|
+
const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
|
|
7398
|
+
const doFetch = deps.fetch ?? fetch;
|
|
7399
|
+
const out = deps.stdout ?? console;
|
|
7400
|
+
const credential2 = await getDeveloperToken(
|
|
7401
|
+
cfg,
|
|
7402
|
+
{
|
|
7403
|
+
configPath: cfg.configPath,
|
|
7404
|
+
token: stringOpt(parsed.options.token),
|
|
7405
|
+
email: stringOpt(parsed.options.email),
|
|
7406
|
+
open: false
|
|
7407
|
+
},
|
|
7408
|
+
doFetch,
|
|
7409
|
+
out
|
|
7410
|
+
);
|
|
7411
|
+
const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
|
|
7412
|
+
const headers = { authorization: `Bearer ${credential2}` };
|
|
7413
|
+
if (action === "retry") {
|
|
7414
|
+
const id = parsed.positionals[2];
|
|
7415
|
+
const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
|
|
7416
|
+
const body2 = await readJson(res2);
|
|
7417
|
+
if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
|
|
7418
|
+
const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
|
|
7419
|
+
if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
|
|
7420
|
+
else out.log(`${tenant}: requeued ${id}`);
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7423
|
+
const state2 = stringOpt(parsed.options.state);
|
|
7424
|
+
const allowed = ["pending", "running", "succeeded", "dead_letter"];
|
|
7425
|
+
if (state2 && !allowed.includes(state2)) {
|
|
7426
|
+
throw new Error(`--state must be one of ${allowed.join(", ")}`);
|
|
7427
|
+
}
|
|
7428
|
+
const url = new URL(base);
|
|
7429
|
+
if (state2) url.searchParams.set("state", state2);
|
|
7430
|
+
const limit = numberOpt(parsed.options.limit, "--limit");
|
|
7431
|
+
if (limit) url.searchParams.set("limit", String(limit));
|
|
7432
|
+
const res = await doFetch(url, { headers });
|
|
7433
|
+
const body = await readJson(res);
|
|
7434
|
+
if (!res.ok) throw new Error(`agent jobs failed (${res.status}): ${errorMessage(body)}`);
|
|
7435
|
+
const result = { v: 1, appId: cfg.app.id, env, tenant, jobs: body.jobs ?? [], summary: body.summary };
|
|
7436
|
+
if (parsed.options.json === true) {
|
|
7437
|
+
out.log(JSON.stringify(result, null, 2));
|
|
7438
|
+
return;
|
|
7439
|
+
}
|
|
7440
|
+
const summary = body.summary;
|
|
7441
|
+
out.log(
|
|
7442
|
+
`${tenant}: ${summary?.pending ?? 0} pending, ${summary?.running ?? 0} running, ${summary?.deadLetter ?? 0} dead-letter, ${summary?.succeeded ?? 0} succeeded`
|
|
7443
|
+
);
|
|
7444
|
+
for (const job of body.jobs ?? []) {
|
|
7445
|
+
out.log(
|
|
7446
|
+
[job.state, job.id, job.triggerId, `${job.entityNs}/${job.entityId}`, `attempts=${job.attempts}`, job.lastErrorCode ?? ""].filter(Boolean).join(" ")
|
|
7447
|
+
);
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
async function readJson(res) {
|
|
7451
|
+
try {
|
|
7452
|
+
return await res.json();
|
|
7453
|
+
} catch {
|
|
7454
|
+
return {};
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
function errorMessage(body) {
|
|
7458
|
+
const error = body.error;
|
|
7459
|
+
if (typeof error === "string") return error;
|
|
7460
|
+
if (error && typeof error === "object" && typeof error.message === "string") {
|
|
7461
|
+
return error.message;
|
|
7462
|
+
}
|
|
7463
|
+
return "request failed";
|
|
7464
|
+
}
|
|
7465
|
+
|
|
7385
7466
|
// src/app-export.ts
|
|
7467
|
+
import { createWriteStream } from "fs";
|
|
7468
|
+
import { Readable } from "stream";
|
|
7469
|
+
import { pipeline } from "stream/promises";
|
|
7386
7470
|
async function appExport(options) {
|
|
7387
7471
|
const cfg = await loadProjectConfig(options.configPath);
|
|
7388
7472
|
const out = options.stdout ?? console;
|
|
@@ -8448,8 +8532,8 @@ async function pmAdd(ctx, entity, parsed) {
|
|
|
8448
8532
|
emit2(ctx, res, () => ctx.out.log(`created ${entity} ${res.id}`));
|
|
8449
8533
|
}
|
|
8450
8534
|
async function pmGet(ctx, entity, id) {
|
|
8451
|
-
const { record:
|
|
8452
|
-
emit2(ctx,
|
|
8535
|
+
const { record: record7 } = await pmRequest(ctx, "GET", `/${entity}/${encodeURIComponent(id)}`);
|
|
8536
|
+
emit2(ctx, record7, () => printRecord(ctx, entity, record7));
|
|
8453
8537
|
}
|
|
8454
8538
|
async function pmSet(ctx, entity, id, parsed) {
|
|
8455
8539
|
const patch2 = collectEntityFields(entity, parsed, true);
|
|
@@ -8494,9 +8578,9 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8494
8578
|
]);
|
|
8495
8579
|
const handoff = {
|
|
8496
8580
|
appId,
|
|
8497
|
-
unmetGoals: goals.filter((
|
|
8498
|
-
activeTasks: tasks.filter((
|
|
8499
|
-
openBugs: bugs.filter((
|
|
8581
|
+
unmetGoals: goals.filter((record7) => record7.status !== "met"),
|
|
8582
|
+
activeTasks: tasks.filter((record7) => record7.column !== "done"),
|
|
8583
|
+
openBugs: bugs.filter((record7) => record7.status !== "fixed" && record7.status !== "wontfix")
|
|
8500
8584
|
};
|
|
8501
8585
|
const result = {
|
|
8502
8586
|
...handoff,
|
|
@@ -8515,10 +8599,10 @@ async function pmHandoff(ctx, parsed) {
|
|
|
8515
8599
|
]) {
|
|
8516
8600
|
ctx.out.log(`${label}:`);
|
|
8517
8601
|
if (!records.length) ctx.out.log("- (none)");
|
|
8518
|
-
else for (const
|
|
8602
|
+
else for (const record7 of records) printRecord(
|
|
8519
8603
|
ctx,
|
|
8520
8604
|
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
8521
|
-
|
|
8605
|
+
record7
|
|
8522
8606
|
);
|
|
8523
8607
|
}
|
|
8524
8608
|
});
|
|
@@ -8659,6 +8743,21 @@ function statusVerdict(reads) {
|
|
|
8659
8743
|
severity: "degraded"
|
|
8660
8744
|
});
|
|
8661
8745
|
}
|
|
8746
|
+
const performance = record5(reads.liveSync.body.performance) ? reads.liveSync.body.performance : null;
|
|
8747
|
+
if (performance?.status === "unavailable") {
|
|
8748
|
+
reasons.push({
|
|
8749
|
+
source: "liveSync",
|
|
8750
|
+
code: "subscription_metrics_unavailable",
|
|
8751
|
+
severity: "degraded"
|
|
8752
|
+
});
|
|
8753
|
+
}
|
|
8754
|
+
if (numeric2(performance?.telemetryDropped) > 0) {
|
|
8755
|
+
reasons.push({
|
|
8756
|
+
source: "liveSync",
|
|
8757
|
+
code: "subscription_telemetry_dropped",
|
|
8758
|
+
severity: "degraded"
|
|
8759
|
+
});
|
|
8760
|
+
}
|
|
8662
8761
|
const canaryStatus = String(reads.canary.body.status ?? "");
|
|
8663
8762
|
if (canaryStatus === "failing") {
|
|
8664
8763
|
reasons.push({
|
|
@@ -8706,6 +8805,12 @@ function statusVerdict(reads) {
|
|
|
8706
8805
|
reasons
|
|
8707
8806
|
};
|
|
8708
8807
|
}
|
|
8808
|
+
function record5(value) {
|
|
8809
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8810
|
+
}
|
|
8811
|
+
function numeric2(value) {
|
|
8812
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8813
|
+
}
|
|
8709
8814
|
function collectorReason(read3, fallback) {
|
|
8710
8815
|
const reasons = read3.body.reasons;
|
|
8711
8816
|
if (!Array.isArray(reasons)) return fallback;
|
|
@@ -8763,7 +8868,7 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8763
8868
|
const [application, liveSync, canary, collector, provider] = await Promise.all([
|
|
8764
8869
|
read2(`${base}/metrics/red?${query}`, headers, doFetch),
|
|
8765
8870
|
read2(
|
|
8766
|
-
`${base}/live-sync/health?${
|
|
8871
|
+
`${base}/live-sync/health?${query}`,
|
|
8767
8872
|
headers,
|
|
8768
8873
|
doFetch
|
|
8769
8874
|
),
|
|
@@ -8783,7 +8888,7 @@ async function o11yCommand(parsed, deps = {}) {
|
|
|
8783
8888
|
provider
|
|
8784
8889
|
});
|
|
8785
8890
|
const status = {
|
|
8786
|
-
schemaVersion:
|
|
8891
|
+
schemaVersion: 4,
|
|
8787
8892
|
scope: { appId, env, minutes },
|
|
8788
8893
|
observedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8789
8894
|
verdict,
|
|
@@ -8823,42 +8928,47 @@ function printStatus2(status, out) {
|
|
|
8823
8928
|
out.log(
|
|
8824
8929
|
`o11y status ${status.scope.appId}/${status.scope.env} (${status.scope.minutes}m)`
|
|
8825
8930
|
);
|
|
8826
|
-
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(
|
|
8931
|
+
const routes = Array.isArray(status.application.body.routes) ? status.application.body.routes.filter(record6) : [];
|
|
8827
8932
|
const requests = routes.reduce(
|
|
8828
|
-
(total, row) => total +
|
|
8933
|
+
(total, row) => total + numeric3(row.requests),
|
|
8829
8934
|
0
|
|
8830
8935
|
);
|
|
8831
8936
|
const errors = routes.reduce(
|
|
8832
|
-
(total, row) => total +
|
|
8937
|
+
(total, row) => total + numeric3(row.errors),
|
|
8833
8938
|
0
|
|
8834
8939
|
);
|
|
8835
8940
|
out.log(
|
|
8836
8941
|
`application ${status.application.httpStatus} ${requests} requests ${errors} errors`
|
|
8837
8942
|
);
|
|
8838
8943
|
out.log(
|
|
8839
|
-
|
|
8944
|
+
liveSyncLine(status.liveSync)
|
|
8840
8945
|
);
|
|
8841
|
-
const canaryDurations =
|
|
8946
|
+
const canaryDurations = record6(status.canary.body.durationsMs) ? status.canary.body.durationsMs : {};
|
|
8842
8947
|
out.log(
|
|
8843
8948
|
`canary ${status.canary.httpStatus} ${String(status.canary.body.status ?? status.canary.body.error ?? "unavailable")} ${optionalNumeric(canaryDurations.publishToVisibleMs)} publish-to-visible`
|
|
8844
8949
|
);
|
|
8845
|
-
const collectorIngest =
|
|
8846
|
-
const collectorStorage =
|
|
8950
|
+
const collectorIngest = record6(status.collector.body.ingest) ? status.collector.body.ingest : {};
|
|
8951
|
+
const collectorStorage = record6(collectorIngest.storage) ? collectorIngest.storage : {};
|
|
8847
8952
|
out.log(
|
|
8848
|
-
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${
|
|
8953
|
+
`collector ${status.collector.httpStatus} ${String(status.collector.body.status ?? status.collector.body.error ?? "unavailable")} ${numeric3(collectorStorage.affectedPoints)} affected points`
|
|
8849
8954
|
);
|
|
8850
|
-
const providerMetrics =
|
|
8955
|
+
const providerMetrics = record6(status.provider.body.metrics) ? status.provider.body.metrics : {};
|
|
8851
8956
|
out.log(
|
|
8852
|
-
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${
|
|
8957
|
+
`cloudflare ${status.provider.httpStatus} ${String(status.provider.body.status ?? status.provider.body.error ?? "unavailable")} ${numeric3(providerMetrics.requests)} invocations ${numeric3(providerMetrics.errors)} runtime errors`
|
|
8853
8958
|
);
|
|
8854
8959
|
out.log(
|
|
8855
8960
|
`verdict ${status.verdict.status} ${status.verdict.reasons.map((reason) => `${reason.source}:${reason.code}`).join(", ") || "all required signals healthy"}`
|
|
8856
8961
|
);
|
|
8857
8962
|
}
|
|
8858
|
-
function
|
|
8963
|
+
function liveSyncLine(read3) {
|
|
8964
|
+
const performance = record6(read3.body.performance) ? read3.body.performance : {};
|
|
8965
|
+
const commitToSend = record6(performance.commitToSend) ? performance.commitToSend : {};
|
|
8966
|
+
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`;
|
|
8967
|
+
}
|
|
8968
|
+
function record6(value) {
|
|
8859
8969
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
8860
8970
|
}
|
|
8861
|
-
function
|
|
8971
|
+
function numeric3(value) {
|
|
8862
8972
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
8863
8973
|
}
|
|
8864
8974
|
function optionalNumeric(value) {
|
|
@@ -10307,6 +10417,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
10307
10417
|
await adminCommand(parsed);
|
|
10308
10418
|
return;
|
|
10309
10419
|
}
|
|
10420
|
+
if (command === "agent") {
|
|
10421
|
+
await agentCommand(parsed, runtime);
|
|
10422
|
+
return;
|
|
10423
|
+
}
|
|
10310
10424
|
if (command === "app") {
|
|
10311
10425
|
await appCommand(parsed, runtime);
|
|
10312
10426
|
return;
|
|
@@ -10446,4 +10560,4 @@ export {
|
|
|
10446
10560
|
exitCodeFor,
|
|
10447
10561
|
runCli
|
|
10448
10562
|
};
|
|
10449
|
-
//# sourceMappingURL=chunk-
|
|
10563
|
+
//# sourceMappingURL=chunk-PM5FBL3S.js.map
|