@odla-ai/cli 0.25.7 → 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 CHANGED
@@ -193,6 +193,8 @@ npx odla-ai code connect --env prod --once # bounded enrollment + heartbeat proo
193
193
  npx odla-ai provision --dry-run
194
194
  npx odla-ai provision --email owner@example.com --write-dev-vars --push-secrets
195
195
  npx odla-ai smoke --env dev
196
+ npx odla-ai agent jobs --env dev --state dead_letter --json
197
+ npx odla-ai agent retry <job-id> --env dev --json
196
198
  npx odla-ai secrets push --env dev
197
199
  npx odla-ai secrets set clerk_webhook_secret --env dev --stdin
198
200
  npx odla-ai secrets set-clerk-key --env dev --from-env CLERK_SECRET_KEY
@@ -214,6 +216,15 @@ npx odla-ai skill install
214
216
  npx odla-ai version
215
217
  ```
216
218
 
219
+ `agent jobs` is the remote-operator view of commit-trigger delivery. It reads
220
+ content-free lifecycle metadata—job id, trigger, entity reference, state,
221
+ attempts, timing, and bounded error/status codes—using an audience-bound
222
+ developer token for an owner of the selected environment. `agent retry` accepts one exact
223
+ dead-lettered id and resets its attempt lease; it does not replay successful or
224
+ currently running work. Successful receipts are retained for seven days (and
225
+ bounded to the newest 1,000 per tenant); unresolved dead letters remain until
226
+ an operator requeues them.
227
+
217
228
  `code connect` is the first-party personal Code terminal runtime. Run the exact
218
229
  command copied from **Studio → Code → Terminal** while your shell is in the
219
230
  local Git checkout. `--platform`, `--app-id`, and `--env` make the requested
package/dist/bin.cjs CHANGED
@@ -1020,11 +1020,6 @@ async function adminCommand(parsed) {
1020
1020
  });
1021
1021
  }
1022
1022
 
1023
- // src/app-export.ts
1024
- var import_node_fs5 = require("fs");
1025
- var import_node_stream = require("stream");
1026
- var import_promises = require("stream/promises");
1027
-
1028
1023
  // src/config.ts
1029
1024
  var import_node_fs4 = require("fs");
1030
1025
  var import_node_path4 = require("path");
@@ -1333,7 +1328,90 @@ function bothTenants(cfg) {
1333
1328
  return { sandbox: (0, import_apps.tenantIdFor)(cfg.app.id, "dev"), live: (0, import_apps.tenantIdFor)(cfg.app.id, "prod") };
1334
1329
  }
1335
1330
 
1331
+ // src/agent-command.ts
1332
+ async function agentCommand(parsed, deps = {}) {
1333
+ const action = parsed.positionals[1];
1334
+ if (action !== "jobs" && action !== "retry") {
1335
+ throw new Error(`unknown agent action "${action ?? ""}". Try "odla-ai agent jobs --json".`);
1336
+ }
1337
+ assertArgs(parsed, ["config", "env", "state", "limit", "json", "token", "email"], action === "jobs" ? 2 : 3);
1338
+ if (action === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
1339
+ throw new Error('--state and --limit are supported only by "agent jobs"');
1340
+ }
1341
+ const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
1342
+ const { env, tenant } = resolveTenant(cfg, stringOpt(parsed.options.env));
1343
+ const doFetch = deps.fetch ?? fetch;
1344
+ const out = deps.stdout ?? console;
1345
+ const credential2 = await getDeveloperToken(
1346
+ cfg,
1347
+ {
1348
+ configPath: cfg.configPath,
1349
+ token: stringOpt(parsed.options.token),
1350
+ email: stringOpt(parsed.options.email),
1351
+ open: false
1352
+ },
1353
+ doFetch,
1354
+ out
1355
+ );
1356
+ const base = `${cfg.dbEndpoint}/app/${encodeURIComponent(tenant)}/admin/agent-jobs`;
1357
+ const headers = { authorization: `Bearer ${credential2}` };
1358
+ if (action === "retry") {
1359
+ const id = parsed.positionals[2];
1360
+ const res2 = await doFetch(`${base}/${encodeURIComponent(id)}/retry`, { method: "POST", headers });
1361
+ const body2 = await readJson(res2);
1362
+ if (!res2.ok) throw new Error(`agent retry failed (${res2.status}): ${errorMessage(body2)}`);
1363
+ const result2 = { v: 1, appId: cfg.app.id, env, tenant, ...body2 };
1364
+ if (parsed.options.json === true) out.log(JSON.stringify(result2, null, 2));
1365
+ else out.log(`${tenant}: requeued ${id}`);
1366
+ return;
1367
+ }
1368
+ const state2 = stringOpt(parsed.options.state);
1369
+ const allowed = ["pending", "running", "succeeded", "dead_letter"];
1370
+ if (state2 && !allowed.includes(state2)) {
1371
+ throw new Error(`--state must be one of ${allowed.join(", ")}`);
1372
+ }
1373
+ const url = new URL(base);
1374
+ if (state2) url.searchParams.set("state", state2);
1375
+ const limit = numberOpt(parsed.options.limit, "--limit");
1376
+ if (limit) url.searchParams.set("limit", String(limit));
1377
+ const res = await doFetch(url, { headers });
1378
+ const body = await readJson(res);
1379
+ if (!res.ok) throw new Error(`agent jobs failed (${res.status}): ${errorMessage(body)}`);
1380
+ const result = { v: 1, appId: cfg.app.id, env, tenant, jobs: body.jobs ?? [], summary: body.summary };
1381
+ if (parsed.options.json === true) {
1382
+ out.log(JSON.stringify(result, null, 2));
1383
+ return;
1384
+ }
1385
+ const summary = body.summary;
1386
+ out.log(
1387
+ `${tenant}: ${summary?.pending ?? 0} pending, ${summary?.running ?? 0} running, ${summary?.deadLetter ?? 0} dead-letter, ${summary?.succeeded ?? 0} succeeded`
1388
+ );
1389
+ for (const job of body.jobs ?? []) {
1390
+ out.log(
1391
+ [job.state, job.id, job.triggerId, `${job.entityNs}/${job.entityId}`, `attempts=${job.attempts}`, job.lastErrorCode ?? ""].filter(Boolean).join(" ")
1392
+ );
1393
+ }
1394
+ }
1395
+ async function readJson(res) {
1396
+ try {
1397
+ return await res.json();
1398
+ } catch {
1399
+ return {};
1400
+ }
1401
+ }
1402
+ function errorMessage(body) {
1403
+ const error = body.error;
1404
+ if (typeof error === "string") return error;
1405
+ if (error && typeof error === "object" && typeof error.message === "string") {
1406
+ return error.message;
1407
+ }
1408
+ return "request failed";
1409
+ }
1410
+
1336
1411
  // src/app-export.ts
1412
+ var import_node_fs5 = require("fs");
1413
+ var import_node_stream = require("stream");
1414
+ var import_promises = require("stream/promises");
1337
1415
  async function appExport(options) {
1338
1416
  const cfg = await loadProjectConfig(options.configPath);
1339
1417
  const out = options.stdout ?? console;
@@ -2311,6 +2389,7 @@ var CAPABILITIES = {
2311
2389
  "apply Google Calendar booking config, then drive state-bound consent, status, discovery, and disconnect flows (bookings run live through the platform proxy)",
2312
2390
  "validate integration contracts offline and smoke-test a provisioned db environment plus anonymous capability routes",
2313
2391
  "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",
2392
+ "inspect durable agent wakeups as a versioned JSON envelope and explicitly requeue one dead-lettered job with a scoped environment credential",
2314
2393
  "run app-attributed hosted security discovery and independent validation without provider keys",
2315
2394
  "connect/revoke source-read-only GitHub sources and drive commit-pinned hosted security jobs without PATs or provider keys",
2316
2395
  "let admins manage system AI policy and credentials, inspect attributed usage, and read immutable admin changes through separate short-lived capability-only approvals"
@@ -6490,6 +6569,8 @@ Usage:
6490
6569
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
6491
6570
  odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
6492
6571
  odla-ai discuss watch [<topic>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json]
6572
+ odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--email <email>] [--json]
6573
+ odla-ai agent retry <job-id> [--env dev] [--email <email>] [--json]
6493
6574
  odla-ai o11y status [--app <id>] [--env prod] [--minutes 60] [--json]
6494
6575
  odla-ai whoami [--json]
6495
6576
  odla-ai runbook ask "<question>" [--app <id>] [--all] [--json]
@@ -6537,6 +6618,8 @@ Usage:
6537
6618
  odla-ai version
6538
6619
 
6539
6620
  Commands:
6621
+ agent Inspect durable agent wakeups and explicitly requeue a
6622
+ dead-lettered job; JSON output is stable for remote operators.
6540
6623
  runbook odla's operational procedures, stored in the database and read at
6541
6624
  the moment they are followed. "ask" gives a written, cited answer;
6542
6625
  "search" the passages behind it; "get" the whole document.
@@ -8519,6 +8602,7 @@ var PM_ENTITIES = Object.fromEntries(
8519
8602
  ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
8520
8603
  );
8521
8604
  var COMMAND_SURFACE = {
8605
+ agent: { jobs: {}, retry: {} },
8522
8606
  admin: {
8523
8607
  ai: {
8524
8608
  show: {},
@@ -10334,6 +10418,10 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
10334
10418
  await adminCommand(parsed);
10335
10419
  return;
10336
10420
  }
10421
+ if (command === "agent") {
10422
+ await agentCommand(parsed, runtime);
10423
+ return;
10424
+ }
10337
10425
  if (command === "app") {
10338
10426
  await appCommand(parsed, runtime);
10339
10427
  return;