@odla-ai/cli 0.36.1 → 0.37.0

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-OWTIDSL5.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-QR44X5IB.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",
@@ -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]
@@ -13505,6 +13506,24 @@ function renderAdvisories(out, advisories, env = process.env) {
13505
13506
  }
13506
13507
  }
13507
13508
 
13509
+ // src/device-ttl.ts
13510
+ var OWNER_DEVICE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
13511
+ var DAY_MS = 24 * 60 * 60 * 1e3;
13512
+ var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
13513
+ function parseDeviceTtl(raw) {
13514
+ if (raw === void 0 || raw === true) return void 0;
13515
+ const text3 = String(raw).trim().toLowerCase();
13516
+ if (!text3) return void 0;
13517
+ if (text3 === "forever" || text3 === "never") return 100 * 365 * DAY_MS;
13518
+ const match = /^(\d+)\s*([dwy])$/.exec(text3);
13519
+ if (!match) {
13520
+ throw new Error(
13521
+ `--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
13522
+ );
13523
+ }
13524
+ return Number(match[1]) * UNITS[match[2]];
13525
+ }
13526
+
13508
13527
  // src/device-command.ts
13509
13528
  var import_node_fs18 = require("fs");
13510
13529
  var import_node_path17 = require("path");
@@ -13524,7 +13543,17 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
13524
13543
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
13525
13544
  const apps = (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
13526
13545
  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})`);
13546
+ const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
13547
+ const extended = deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
13548
+ const token = await scopedToken2(
13549
+ parsed,
13550
+ deps,
13551
+ cfg,
13552
+ doFetch,
13553
+ out,
13554
+ `odla CLI (enroll ${name})`,
13555
+ extended ? "platform:device:enroll:extended" : "app:device:enroll"
13556
+ );
13528
13557
  const response2 = await doFetch(`${cfg.platformUrl}/registry/devices`, {
13529
13558
  method: "POST",
13530
13559
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
@@ -13532,7 +13561,8 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
13532
13561
  name,
13533
13562
  platform: import_node_process15.default.platform,
13534
13563
  appIds: apps,
13535
- ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {}
13564
+ ...parsed.options.capability ? { capabilities: String(parsed.options.capability).split(",").map((c) => c.trim()).filter(Boolean) } : {},
13565
+ ...deviceTtlMs === void 0 ? {} : { deviceTtlMs }
13536
13566
  })
13537
13567
  });
13538
13568
  const body = await response2.json().catch(() => ({}));
@@ -13585,12 +13615,12 @@ async function revoke(parsed, deps, cfg, doFetch, out, json) {
13585
13615
  out.error(`device: revoked ${deviceId}; every credential it minted is revoked with it`);
13586
13616
  if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
13587
13617
  }
13588
- async function scopedToken2(parsed, deps, cfg, doFetch, out, label) {
13618
+ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app:device:enroll") {
13589
13619
  const { credentials } = await resolveOperatorContext(parsed, { allowMissingConfig: true });
13590
13620
  const scopedTokenFile = credentials.scopedTokenFile;
13591
13621
  return getScopedPlatformToken({
13592
13622
  platform: cfg.platformUrl,
13593
- scope: "app:device:enroll",
13623
+ scope,
13594
13624
  email: stringOpt(parsed.options.email),
13595
13625
  label,
13596
13626
  fetch: doFetch,