@odla-ai/cli 0.36.1 → 0.37.1

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-4QJ5NS64.js";
4
+ } from "./chunk-XFIFFYNH.js";
5
5
  import {
6
6
  exitCodeFor
7
7
  } from "./chunk-UKLSRQ5J.js";
@@ -9,4 +9,4 @@ export {
9
9
  exitCodeFor,
10
10
  runCli
11
11
  };
12
- //# sourceMappingURL=cli-BFGY7F6X.js.map
12
+ //# sourceMappingURL=cli-OH6PCYZ3.js.map
package/dist/index.cjs CHANGED
@@ -638,6 +638,7 @@ var SCOPE_PURPOSE = {
638
638
  "app:config:write": "apply or inspect one revision-bound configuration operation for an app you own",
639
639
  "platform:runbook:write": "read and edit all of odla's operational runbooks, including admin-visible content",
640
640
  "app:device:enroll": "enrol this machine so it can mint its own short-lived credentials without asking you again",
641
+ "platform:device:enroll:extended": "enrol this machine with a credential that outlives the seven-day owner ceiling \u2014 a durable secret on a machine you are vouching for",
641
642
  "platform:ai:policy:write": "change System AI model routing",
642
643
  "platform:ai:policy:read": "read System AI model routing",
643
644
  "platform:ai:credential:write": "replace a stored AI provider key",
@@ -7009,7 +7010,7 @@ function createCodeRuntimeControlClient(options) {
7009
7010
  throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
7010
7011
  }
7011
7012
  const request3 = options.fetch ?? fetch;
7012
- const call2 = async (path, body, timeoutMs = requestTimeoutMs) => {
7013
+ const call3 = async (path, body, timeoutMs = requestTimeoutMs) => {
7013
7014
  const timeout = AbortSignal.timeout(timeoutMs);
7014
7015
  const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
7015
7016
  let response2;
@@ -7039,17 +7040,17 @@ function createCodeRuntimeControlClient(options) {
7039
7040
  return {
7040
7041
  heartbeat: async (version, capabilities) => {
7041
7042
  validateHeartbeat(version, capabilities);
7042
- return parseSnapshot(await call2("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
7043
+ return parseSnapshot(await call3("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
7043
7044
  },
7044
7045
  acknowledge: async (commandId, result) => {
7045
7046
  if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
7046
- await call2(`/registry/code/runtime/commands/${commandId}/ack`, result);
7047
+ await call3(`/registry/code/runtime/commands/${commandId}/ack`, result);
7047
7048
  },
7048
7049
  source: async (sessionId) => parseSource(
7049
- await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7050
+ await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
7050
7051
  ),
7051
7052
  infer: async (sessionId, inference) => {
7052
- const value2 = record5(await call2(
7053
+ const value2 = record5(await call3(
7053
7054
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
7054
7055
  inference,
7055
7056
  modelRequestTimeoutMs
@@ -7060,11 +7061,11 @@ function createCodeRuntimeControlClient(options) {
7060
7061
  return value2;
7061
7062
  },
7062
7063
  review: async (sessionId, review) => parseReview(
7063
- await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
7064
+ await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
7064
7065
  ),
7065
7066
  submitCandidate: async (sessionId, checkpointId, verification) => {
7066
7067
  if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
7067
- return parseCandidate(await call2(
7068
+ return parseCandidate(await call3(
7068
7069
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
7069
7070
  { checkpointId, verification }
7070
7071
  ));
@@ -7074,21 +7075,21 @@ function createCodeRuntimeControlClient(options) {
7074
7075
  if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
7075
7076
  throw new TypeError("invalid Code session event");
7076
7077
  }
7077
- await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7078
+ await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
7078
7079
  },
7079
7080
  recallMemories: async (sessionId, subjects, limit) => {
7080
- const response2 = await call2(
7081
+ const response2 = await call3(
7081
7082
  `/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
7082
7083
  { subjects: [...subjects], limit }
7083
7084
  );
7084
7085
  return Array.isArray(response2.memories) ? response2.memories : [];
7085
7086
  },
7086
7087
  rememberMemory: async (sessionId, memory) => {
7087
- await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7088
+ await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
7088
7089
  },
7089
7090
  reportSessionFailure: async (sessionId, message2) => {
7090
7091
  if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
7091
- await call2(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
7092
+ await call3(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
7092
7093
  }
7093
7094
  };
7094
7095
  }
@@ -7977,7 +7978,7 @@ var SYSTEM_PROMPT_FOR = {
7977
7978
  };
7978
7979
  function codeSkill(opts) {
7979
7980
  let seq = 0;
7980
- const call2 = async (tool, input, signal) => {
7981
+ const call3 = async (tool, input, signal) => {
7981
7982
  const startedAt = Date.now();
7982
7983
  const response2 = await opts.broker.execute(
7983
7984
  { lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
@@ -7999,7 +8000,7 @@ function codeSkill(opts) {
7999
8000
  },
8000
8001
  additionalProperties: false
8001
8002
  },
8002
- handler: (input, ctx) => call2("sandbox.read", input, ctx.signal)
8003
+ handler: (input, ctx) => call3("sandbox.read", input, ctx.signal)
8003
8004
  };
8004
8005
  const applyPatch = {
8005
8006
  name: "odla_apply_git_diff",
@@ -8010,7 +8011,7 @@ function codeSkill(opts) {
8010
8011
  properties: { patch: { type: "string", minLength: 1, maxLength: 262144 } },
8011
8012
  additionalProperties: false
8012
8013
  },
8013
- handler: (input, ctx) => call2("sandbox.apply_patch", input, ctx.signal)
8014
+ handler: (input, ctx) => call3("sandbox.apply_patch", input, ctx.signal)
8014
8015
  };
8015
8016
  const runRecipe = {
8016
8017
  name: "odla_run_recipe",
@@ -8021,7 +8022,7 @@ function codeSkill(opts) {
8021
8022
  properties: { recipeId: { type: "string", minLength: 1, maxLength: 120, pattern: "^[a-zA-Z0-9._:-]+$" } },
8022
8023
  additionalProperties: false
8023
8024
  },
8024
- handler: (input, ctx) => call2("sandbox.run_recipe", input, ctx.signal)
8025
+ handler: (input, ctx) => call3("sandbox.run_recipe", input, ctx.signal)
8025
8026
  };
8026
8027
  const listFiles2 = {
8027
8028
  name: "odla_list",
@@ -8034,7 +8035,7 @@ function codeSkill(opts) {
8034
8035
  },
8035
8036
  additionalProperties: false
8036
8037
  },
8037
- handler: (input, ctx) => call2("sandbox.list", input, ctx.signal)
8038
+ handler: (input, ctx) => call3("sandbox.list", input, ctx.signal)
8038
8039
  };
8039
8040
  const searchFiles = {
8040
8041
  name: "odla_search",
@@ -8050,7 +8051,7 @@ function codeSkill(opts) {
8050
8051
  },
8051
8052
  additionalProperties: false
8052
8053
  },
8053
- handler: (input, ctx) => call2("sandbox.search", input, ctx.signal)
8054
+ handler: (input, ctx) => call3("sandbox.search", input, ctx.signal)
8054
8055
  };
8055
8056
  const graphTool = (name, tool, description, required) => ({
8056
8057
  name,
@@ -8061,7 +8062,7 @@ function codeSkill(opts) {
8061
8062
  properties: { query: { type: "string", maxLength: 512 } },
8062
8063
  additionalProperties: false
8063
8064
  },
8064
- handler: (input, ctx) => call2(tool, input, ctx.signal)
8065
+ handler: (input, ctx) => call3(tool, input, ctx.signal)
8065
8066
  });
8066
8067
  const orientation = [
8067
8068
  graphTool(
@@ -8100,9 +8101,9 @@ async function runCodeAgent(options) {
8100
8101
  lease: options.lease,
8101
8102
  workspaceDir: options.workspaceDir,
8102
8103
  surface,
8103
- onToolCall: (call2) => {
8104
- toolCalls.push(call2);
8105
- options.onToolCall?.(call2);
8104
+ onToolCall: (call3) => {
8105
+ toolCalls.push(call3);
8106
+ options.onToolCall?.(call3);
8106
8107
  }
8107
8108
  });
8108
8109
  const compaction = options.compaction === void 0 ? (0, import_ai4.keepRecentExchanges)({ whenInputTokensExceed: 12e4, keep: 3 }) : options.compaction;
@@ -10414,7 +10415,7 @@ Usage:
10414
10415
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
10415
10416
  odla-ai security run [target] --self --ack-redacted-source
10416
10417
  odla-ai provision [--live] [--config odla.config.mjs] [--email <odla-account>] [--request-grant] [--no-open] [--wait <seconds>] [--dry-run] [--push-secrets] [--rotate-o11y-token] [--write-dev-vars[=path]] [--yes]
10417
- odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--email <odla-account>] [--no-open] [--json]
10418
+ odla-ai device enroll [--app <id>[,<id>...]] [--name <label>] [--capability <c>[,<c>...]] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--json]
10418
10419
  odla-ai device list [--email <odla-account>] [--json]
10419
10420
  odla-ai device revoke <device-id> [--email <odla-account>] [--json]
10420
10421
  odla-ai credentials list [--config odla.config.mjs] [--env dev] [--all] [--json]
@@ -12883,9 +12884,15 @@ async function provisionEnvCredentials(opts) {
12883
12884
  if (o11yToken) {
12884
12885
  opts.stdout.log(`${opts.env}: reusing local o11y ingest token`);
12885
12886
  } else {
12886
- o11yToken = await issueO11yToken(opts);
12887
- credentials = save(opts, credentials, tenantId, { ...dbKey ? { dbKey } : {}, o11yToken });
12888
- opts.stdout.log(`${opts.env}: ${opts.rotateO11y ? "rotated" : "issued"} o11y ingest token`);
12887
+ o11yToken = await issueO11yToken(opts) ?? void 0;
12888
+ if (o11yToken) {
12889
+ credentials = save(opts, credentials, tenantId, { ...dbKey ? { dbKey } : {}, o11yToken });
12890
+ opts.stdout.log(`${opts.env}: ${opts.rotateO11y ? "rotated" : "issued"} o11y ingest token`);
12891
+ } else {
12892
+ opts.stdout.log(
12893
+ `${opts.env}: o11y token already exists and its shown-once value is not on this machine \u2014 leaving it alone (a co-owner provisioned this env). Nothing was rotated, and this env's running Worker keeps its token. To replace it deliberately \u2014 which invalidates the token on whatever is deployed \u2014 run "odla-ai provision --rotate-o11y-token --push-secrets".`
12894
+ );
12895
+ }
12889
12896
  }
12890
12897
  }
12891
12898
  return save(opts, credentials, tenantId, {
@@ -12939,11 +12946,7 @@ async function issueO11yToken(opts) {
12939
12946
  `${opts.cfg.platformUrl}/o11y/${encodeURIComponent(opts.cfg.app.id)}/token${suffix}?env=${encodeURIComponent(opts.env)}`,
12940
12947
  { method: "POST", headers: { authorization: `Bearer ${opts.developerToken}` } }
12941
12948
  );
12942
- if (res.status === 409 && !opts.rotateO11y) {
12943
- throw new Error(
12944
- `o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --push-secrets" to install a separate runtime credential without rotating siblings`
12945
- );
12946
- }
12949
+ if (res.status === 409 && !opts.rotateO11y) return null;
12947
12950
  if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
12948
12951
  const body = await res.json();
12949
12952
  if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
@@ -13505,6 +13508,24 @@ function renderAdvisories(out, advisories, env = process.env) {
13505
13508
  }
13506
13509
  }
13507
13510
 
13511
+ // src/device-ttl.ts
13512
+ var OWNER_DEVICE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13513
+ var DAY_MS = 24 * 60 * 60 * 1e3;
13514
+ var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
13515
+ function parseDeviceTtl(raw) {
13516
+ if (raw === void 0 || raw === true) return void 0;
13517
+ const text3 = String(raw).trim().toLowerCase();
13518
+ if (!text3) return void 0;
13519
+ if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
13520
+ const match = /^(\d+)\s*([dwy])$/.exec(text3);
13521
+ if (!match) {
13522
+ throw new Error(
13523
+ `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
13524
+ );
13525
+ }
13526
+ return Number(match[1]) * UNITS[match[2]];
13527
+ }
13528
+
13508
13529
  // src/device-command.ts
13509
13530
  var import_node_fs18 = require("fs");
13510
13531
  var import_node_path17 = require("path");
@@ -13524,7 +13545,17 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
13524
13545
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
13525
13546
  const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
13526
13547
  if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026]");
13527
- const token = await scopedToken2(parsed, deps, cfg, doFetch, out, `odla CLI (enroll ${name})`);
13548
+ const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
13549
+ const extended = deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
13550
+ const token = await scopedToken2(
13551
+ parsed,
13552
+ deps,
13553
+ cfg,
13554
+ doFetch,
13555
+ out,
13556
+ `odla CLI (enroll ${name})`,
13557
+ extended ? "platform:device:enroll:extended" : "app:device:enroll"
13558
+ );
13528
13559
  const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
13529
13560
  method: "POST",
13530
13561
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
@@ -13532,7 +13563,8 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
13532
13563
  name,
13533
13564
  platform: import_node_process15.default.platform,
13534
13565
  appIds: apps,
13535
- ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
13566
+ ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {},
13567
+ ...deviceTtlMs === void 0 ? {} : { deviceTtlMs }
13536
13568
  })
13537
13569
  });
13538
13570
  const body = await response2.json().catch(() => ({}));
@@ -13585,12 +13617,12 @@ async function revoke(parsed, deps, cfg, doFetch, out, json) {
13585
13617
  out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
13586
13618
  if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
13587
13619
  }
13588
- async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
13620
+ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app:device:enroll") {
13589
13621
  const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
13590
13622
  const scopedTokenFile = credentials.scopedTokenFile;
13591
13623
  return getScopedPlatformToken({
13592
13624
  platform: cfg.platformUrl,
13593
- scope: "app:device:enroll",
13625
+ scope,
13594
13626
  email: stringOpt(parsed.options.email),
13595
13627
  label,
13596
13628
  fetch: doFetch,
@@ -13849,12 +13881,7 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
13849
13881
  );
13850
13882
  }
13851
13883
  async function upsert(ctx, r, visibility) {
13852
- const page2 = await call(
13853
- ctx,
13854
- "GET",
13855
- `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(r.slug)}&limit=1`
13856
- );
13857
- const found = page2.records[0];
13884
+ const found = await bySlug(ctx, r.slug).catch(() => null);
13858
13885
  if (!found) {
13859
13886
  await call(ctx, "POST", "/runbook", {
13860
13887
  appId: ctx.appId,
@@ -14259,13 +14286,7 @@ async function runbookAsk(ctx, question, all) {
14259
14286
  ctx.out.log(JSDOC_POINTER);
14260
14287
  }
14261
14288
  async function runbookComment(ctx, slug, body) {
14262
- const page2 = await call(
14263
- ctx,
14264
- "GET",
14265
- `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
14266
- );
14267
- const found = page2.records[0];
14268
- if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
14289
+ const found = await bySlug(ctx, slug);
14269
14290
  await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
14270
14291
  ctx.out.log(`commented on ${slug} (v${found.version})`);
14271
14292
  }
@@ -14316,13 +14337,7 @@ var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
14316
14337
 
14317
14338
  // src/runbook-edit-flow.ts
14318
14339
  async function editRunbook(ctx, slug, deps = {}) {
14319
- const page2 = await call(
14320
- ctx,
14321
- "GET",
14322
- `/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
14323
- );
14324
- const found = page2.records[0];
14325
- if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
14340
+ const found = await bySlug(ctx, slug);
14326
14341
  ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
14327
14342
  const body = await editText(found.body, slug, deps);
14328
14343
  return body === null ? null : { body, expectedVersion: found.version };