@odla-ai/cli 0.39.0 → 0.40.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.
package/dist/bin.cjs CHANGED
@@ -403,6 +403,11 @@ var init_device_session = __esm({
403
403
 
404
404
  // src/odla-home.ts
405
405
  function odlaHome(env = import_node_process5.default.env) {
406
+ if (env.VITEST && !env.ODLA_HOME) {
407
+ throw new Error(
408
+ "ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
409
+ );
410
+ }
406
411
  return env.ODLA_HOME ?? (0, import_node_path4.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla");
407
412
  }
408
413
  function odlaHomePath(segments, env = import_node_process5.default.env) {
@@ -428,15 +433,15 @@ function pmContextFile(env) {
428
433
  }
429
434
  function adoptRepoLocalCache(legacyPath, machinePath, out) {
430
435
  if (!(0, import_node_fs6.existsSync)(legacyPath) || legacyPath === machinePath) return false;
431
- const superseded = (0, import_node_fs6.existsSync)(machinePath);
432
- if (!superseded) {
436
+ const superseded2 = (0, import_node_fs6.existsSync)(machinePath);
437
+ if (!superseded2) {
433
438
  (0, import_node_fs6.mkdirSync)((0, import_node_path4.dirname)(machinePath), { recursive: true });
434
439
  (0, import_node_fs6.copyFileSync)(legacyPath, machinePath);
435
440
  (0, import_node_fs6.chmodSync)(machinePath, 384);
436
441
  }
437
442
  (0, import_node_fs6.rmSync)(legacyPath, { force: true });
438
443
  out?.error(
439
- superseded ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
444
+ superseded2 ? `auth: removed superseded ${legacyPath}; this machine's credentials live in ${odlaHome()}` : `auth: moved ${legacyPath} into ${machinePath}; credentials are per machine now, not per worktree`
440
445
  );
441
446
  return true;
442
447
  }
@@ -615,6 +620,20 @@ function scopeInterruptionNotice(scope, state2) {
615
620
  platformScope ? " A platform scope needs an administrator's approval; an app owner's cannot carry it." : " One approval, every app you own, and it rolls forward while you keep working."
616
621
  ].join("\n");
617
622
  }
623
+ function narrowEnrollmentNotice(envelope) {
624
+ const everyApp = envelope.appIds.includes("*");
625
+ if (everyApp && envelope.capabilities.length > 0 && envelope.scopes.length > 0) return null;
626
+ const missing = [
627
+ everyApp ? null : `only ${envelope.appIds.length} app${envelope.appIds.length === 1 ? "" : "s"} \u2014 a new one will need a new approval`,
628
+ envelope.capabilities.length ? null : "no optional capabilities \u2014 app.manage, crm.read and code.session are not included",
629
+ envelope.scopes.length ? null : "no platform scopes \u2014 runbook edits, config plans and host connect will each ask again"
630
+ ].filter(Boolean);
631
+ return [
632
+ `odla: this is a NARROW enrollment: ${missing.join("; ")}.`,
633
+ " That is a fine choice if you meant it. If you did not, drop the flags:",
634
+ ` ${ENROL_EVERYTHING}`
635
+ ].join("\n");
636
+ }
618
637
  function lapseNotice(state2, now = Date.now()) {
619
638
  if (!state2.enrolled || !state2.lapsesAt) return null;
620
639
  const days = Math.floor((state2.lapsesAt - now) / (24 * 60 * 60 * 1e3));
@@ -630,7 +649,7 @@ var init_auth_guidance = __esm({
630
649
  init_device_session();
631
650
  init_odla_home();
632
651
  init_local();
633
- ENROL_EVERYTHING = "npx odla-ai device enroll --all-apps --capability all --no-open --wait 600";
652
+ ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
634
653
  ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
635
654
  }
636
655
  });
@@ -2705,6 +2724,11 @@ async function authCommand(parsed, deps = {}) {
2705
2724
  "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
2706
2725
  );
2707
2726
  }
2727
+ if (!machineAuthState(platformAudience(cfg.platformUrl)).enrolled) {
2728
+ out.error("odla: this machine is not enrolled, so this approval buys one project until it lapses.");
2729
+ out.error(` For one approval that covers every app you own, with no repeats:
2730
+ ${ENROL_EVERYTHING}`);
2731
+ }
2708
2732
  const token = await getDeveloperToken(
2709
2733
  cfg,
2710
2734
  {
@@ -2742,6 +2766,7 @@ var init_auth_command = __esm({
2742
2766
  init_argv();
2743
2767
  init_operator_context();
2744
2768
  init_token();
2769
+ init_auth_guidance();
2745
2770
  init_whoami_command();
2746
2771
  }
2747
2772
  });
@@ -7890,7 +7915,7 @@ var init_code2 = __esm({
7890
7915
  }
7891
7916
  });
7892
7917
 
7893
- // ../harness/dist/chunk-UVGZHNLW.js
7918
+ // ../harness/dist/chunk-L2T3LPEF.js
7894
7919
  async function digestStagedWorkspace(root, limits) {
7895
7920
  const files = [];
7896
7921
  const walk = async (directory) => {
@@ -8107,6 +8132,7 @@ function parseSnapshot(value2) {
8107
8132
  return { host, bindings, commands };
8108
8133
  }
8109
8134
  async function parseSource(value2) {
8135
+ const repositoryLimits = { maximumFiles: 1e5, maximumBytes: 80 * 1024 * 1024 };
8110
8136
  const snapshot = record5(record5(value2)?.snapshot);
8111
8137
  if (!snapshot || typeof snapshot.repository !== "string" || typeof snapshot.commitSha !== "string" || typeof snapshot.treeDigest !== "string" || !Array.isArray(snapshot.files)) throw invalid("source");
8112
8138
  const files = snapshot.files.map((value22) => {
@@ -8128,12 +8154,12 @@ async function parseSource(value2) {
8128
8154
  return { path: file.path, content: file.content };
8129
8155
  });
8130
8156
  const source2 = { repository: reference.repository, commitSha: reference.commitSha, files: referenceFiles };
8131
- const referenceDigest = await digestCodeRepositorySnapshot(source2, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
8157
+ const referenceDigest = await digestCodeRepositorySnapshot(source2, repositoryLimits);
8132
8158
  if (referenceDigest !== reference.treeDigest) throw invalid("reference source digest");
8133
8159
  references.push({ alias: reference.alias, ...source2, treeDigest: referenceDigest });
8134
8160
  }
8135
8161
  const source = { repository: snapshot.repository, commitSha: snapshot.commitSha, files };
8136
- const digest = await digestCodeRepositorySnapshot(source, { maximumFiles: 1e4, maximumBytes: 16 * 1024 * 1024 });
8162
+ const digest = await digestCodeRepositorySnapshot(source, repositoryLimits);
8137
8163
  if (digest !== snapshot.treeDigest) throw invalid("source digest");
8138
8164
  return { ...source, treeDigest: digest, ...references.length ? { references } : {} };
8139
8165
  }
@@ -8722,7 +8748,7 @@ async function prepareRuntimeLocalSource(input) {
8722
8748
  return { workspace, sourceDigest: descriptor2.snapshotDigest, trustedBaseDigest };
8723
8749
  }
8724
8750
  async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.tmpdir)()) {
8725
- if (!snapshot.files.length || snapshot.files.length > 1e4) throw new TypeError("Code source file count is invalid");
8751
+ if (!snapshot.files.length || snapshot.files.length > SOURCE_MAX_FILES) throw new TypeError("Code source file count is invalid");
8726
8752
  const root = await (0, import_promises8.mkdtemp)((0, import_path8.join)(tempRoot, "odla-code-source-"));
8727
8753
  const sourceDir = (0, import_path8.join)(root, "source");
8728
8754
  await (0, import_promises8.mkdir)(sourceDir);
@@ -8734,7 +8760,7 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
8734
8760
  if (seen.has(file.path)) throw new TypeError("Code source repeats a path");
8735
8761
  seen.add(file.path);
8736
8762
  bytes += Buffer.byteLength(file.path) + Buffer.byteLength(file.content);
8737
- if (bytes > 16 * 1024 * 1024) throw new TypeError("Code source exceeds its byte bound");
8763
+ if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
8738
8764
  const target = (0, import_path8.resolve)(sourceDir, file.path);
8739
8765
  if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code source path escapes its root");
8740
8766
  await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
@@ -8742,14 +8768,14 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = (0, import_os3.
8742
8768
  }
8743
8769
  for (const reference of snapshot.references ?? []) {
8744
8770
  validateAlias(reference.alias);
8745
- if (!reference.files.length || reference.files.length > 1e4) throw new TypeError("Code reference file count is invalid");
8771
+ if (!reference.files.length || reference.files.length > SOURCE_MAX_FILES) throw new TypeError("Code reference file count is invalid");
8746
8772
  for (const file of reference.files) {
8747
8773
  validatePath(file.path);
8748
8774
  const path = `.odla-references/${reference.alias}/${file.path}`;
8749
8775
  if (seen.has(path)) throw new TypeError("Code reference repeats a path");
8750
8776
  seen.add(path);
8751
8777
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
8752
- if (bytes > 80 * 1024 * 1024) throw new TypeError("Code source set exceeds its byte bound");
8778
+ if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
8753
8779
  const target = (0, import_path8.resolve)(sourceDir, path);
8754
8780
  if (!target.startsWith(`${(0, import_path8.resolve)(sourceDir)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
8755
8781
  await (0, import_promises8.mkdir)((0, import_path8.dirname)(target), { recursive: true });
@@ -8775,7 +8801,7 @@ async function attachCodeRuntimeReferences(workspace, references) {
8775
8801
  validatePath(file.path);
8776
8802
  const path = `.odla-references/${reference.alias}/${file.path}`;
8777
8803
  bytes += Buffer.byteLength(path) + Buffer.byteLength(file.content);
8778
- if (bytes > 64 * 1024 * 1024) throw new TypeError("Code reference set exceeds its byte bound");
8804
+ if (bytes > SOURCE_SET_MAX_BYTES - SOURCE_MAX_BYTES) throw new TypeError("Code reference set exceeds its byte bound");
8779
8805
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
8780
8806
  const target = (0, import_path8.resolve)(root, path);
8781
8807
  if (!target.startsWith(`${(0, import_path8.resolve)(root)}${import_path8.sep}`)) throw new TypeError("Code reference path escapes its root");
@@ -8825,7 +8851,10 @@ async function materializeCommandWorkspace(input) {
8825
8851
  trustedBaseDir: materialized.sourceDir,
8826
8852
  trustedBaseCommitSha: source.commitSha,
8827
8853
  checkpoint: codeCheckpointPayload(command.payload)
8828
- })).workspace : await stageWorkspace(materialized.sourceDir);
8854
+ })).workspace : await stageWorkspace(materialized.sourceDir, {
8855
+ maxFiles: SOURCE_MAX_FILES,
8856
+ maxBytes: SOURCE_SET_MAX_BYTES
8857
+ });
8829
8858
  return { workspace, sourceDigest: source.treeDigest, requestedLocal: null };
8830
8859
  } finally {
8831
8860
  await materialized.cleanup();
@@ -9885,9 +9914,9 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9885
9914
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9886
9915
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9887
9916
  }
9888
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9889
- var init_chunk_UVGZHNLW = __esm({
9890
- "../harness/dist/chunk-UVGZHNLW.js"() {
9917
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9918
+ var init_chunk_L2T3LPEF = __esm({
9919
+ "../harness/dist/chunk-L2T3LPEF.js"() {
9891
9920
  "use strict";
9892
9921
  init_cjs_shims();
9893
9922
  init_chunk_K76I2TCQ();
@@ -10027,6 +10056,9 @@ var init_chunk_UVGZHNLW = __esm({
10027
10056
  SOURCE_LIMITS = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
10028
10057
  RESERVED2 = /* @__PURE__ */ new Set([".git", ".odla", ".wrangler", "node_modules", "dist", "coverage"]);
10029
10058
  SECRET2 = /^(?:\.env(?:\..+)?|\.dev\.vars|credentials(?:\..+)?\.json|dev-token(?:\..+)?\.json)$/i;
10059
+ SOURCE_MAX_FILES = 1e5;
10060
+ SOURCE_MAX_BYTES = 80 * 1024 * 1024;
10061
+ SOURCE_SET_MAX_BYTES = 480 * 1024 * 1024;
10030
10062
  V1_SYSTEM_PROMPT = `You are Pi, the coding agent inside an odla Code harness.
10031
10063
  Use only the odla_read, odla_apply_git_diff, and odla_run_recipe tools.
10032
10064
  For mutations, call odla_apply_git_diff with raw git diff text. It must start
@@ -10406,7 +10438,7 @@ var init_node = __esm({
10406
10438
  "../harness/dist/node.js"() {
10407
10439
  "use strict";
10408
10440
  init_cjs_shims();
10409
- init_chunk_UVGZHNLW();
10441
+ init_chunk_L2T3LPEF();
10410
10442
  init_chunk_K76I2TCQ();
10411
10443
  MEASURED_PREMIUM = Object.freeze({
10412
10444
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
@@ -11397,13 +11429,16 @@ var init_help_usage = __esm({
11397
11429
  init_cjs_shims();
11398
11430
  AUTH_SECTION = `
11399
11431
  Enrol this machine once, then stop asking:
11400
- npx odla-ai device enroll --all-apps --capability all --no-open --wait 600
11401
- One browser approval. Afterwards every worktree on this machine mints its
11402
- own short-lived credentials with nobody's attention, for every app you
11403
- own \u2014 including apps you create later. The window rolls forward each time
11404
- you use it, so continuous work never interrupts anyone; only a real gap
11405
- does. Give the human the printed /studio?code= URL, keep the process
11406
- alive, and wait on it.
11432
+ npx odla-ai device enroll --no-open --wait 600
11433
+ One browser approval, and no flags to remember: this covers every app you
11434
+ own \u2014 including apps you create later \u2014 with every capability that
11435
+ approval can carry. Afterwards every worktree on this machine mints its
11436
+ own short-lived credentials with nobody's attention. The window rolls
11437
+ forward each time you use it, so continuous work never interrupts anyone;
11438
+ only a real gap does. Give the human the printed /studio?code= URL, keep
11439
+ the process alive, and wait on it.
11440
+ Narrow it deliberately with --app <id> or --capability <c>; the CLI then
11441
+ says what that gave up.
11407
11442
 
11408
11443
  npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600
11409
11444
  The same thing across all of odla, for weeks. Needs a platform
@@ -11546,7 +11581,7 @@ Usage:
11546
11581
  odla-ai security run [target] --ack-redacted-source [--env dev] [--profile odla] [--fail-on high]
11547
11582
  odla-ai security run [target] --self --ack-redacted-source
11548
11583
  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]
11549
- odla-ai device enroll [--all-apps|--app <id>[,<id>...]] [--capability all|<c>[,<c>...]] [--platform-wide]
11584
+ odla-ai device enroll [--app <id>[,<id>...]|--all-apps] [--capability all|<c>[,<c>...]] [--platform-wide]
11550
11585
  [--name <label>] [--device-ttl <30d|6w|2y|forever>] [--email <odla-account>] [--no-open] [--wait <seconds>] [--json]
11551
11586
  odla-ai device list [--email <odla-account>] [--json]
11552
11587
  odla-ai device revoke <device-id> [--email <odla-account>] [--json]
@@ -11674,11 +11709,13 @@ Commands:
11674
11709
  enrollment in the browser; from then on EVERY worktree on this
11675
11710
  machine mints its own short-lived credentials with nobody's
11676
11711
  attention, until the device is revoked or goes unused.
11677
- "--all-apps" covers every app you own, now and later, so creating
11678
- an app costs no new approval. "--capability all" takes everything
11679
- that approval is allowed to carry, so a capability you did not
11680
- think to name is not a 403 next week. "--platform-wide" is the
11681
- administrator's version, across all of odla.
11712
+ With no flags it covers every app you own, now and later, with
11713
+ every capability that approval can carry \u2014 so creating an app
11714
+ costs no new approval, and a capability nobody thought to name is
11715
+ not a 403 next week. "--app" or "--capability" narrow it
11716
+ deliberately, and the CLI says what that gave up.
11717
+ "--platform-wide" is the administrator's version, across all of
11718
+ odla.
11682
11719
  The expiry is a GAP, not a clock: each use rolls it forward, so
11683
11720
  only going quiet brings a human back into the loop \u2014 which is
11684
11721
  where anything that changed can be explained.
@@ -14988,22 +15025,29 @@ function advisoryCollectingFetch(inner, sink) {
14988
15025
  return response2;
14989
15026
  });
14990
15027
  }
15028
+ function supersedeAdvisory(code) {
15029
+ superseded.add(code);
15030
+ }
14991
15031
  function renderAdvisories(out, advisories, env = process.env) {
15032
+ const retracted = new Set(superseded);
15033
+ superseded.clear();
14992
15034
  if (env.ODLA_NO_ADVISORIES) return;
14993
15035
  const seen = /* @__PURE__ */ new Set();
14994
15036
  for (const advisory of advisories) {
15037
+ if (retracted.has(advisory.code)) continue;
14995
15038
  const key = `${advisory.code}:${advisory.message}`;
14996
15039
  if (seen.has(key)) continue;
14997
15040
  seen.add(key);
14998
15041
  out.error((0, import_apps14.formatAdvisory)(advisory));
14999
15042
  }
15000
15043
  }
15001
- var import_apps14;
15044
+ var import_apps14, superseded;
15002
15045
  var init_advisory_output = __esm({
15003
15046
  "src/advisory-output.ts"() {
15004
15047
  "use strict";
15005
15048
  init_cjs_shims();
15006
15049
  import_apps14 = require("@odla-ai/apps");
15050
+ superseded = /* @__PURE__ */ new Set();
15007
15051
  }
15008
15052
  });
15009
15053
 
@@ -15063,11 +15107,18 @@ async function deviceCommand(parsed, deps) {
15063
15107
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
15064
15108
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
15065
15109
  const platformWide = parsed.options["platform-wide"] === true;
15066
- const apps = platformWide || parsed.options["all-apps"] === true ? [import_db4.ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
15110
+ const narrowed = stringOpt(parsed.options.app) !== void 0 || stringOpt(parsed.options.capability) !== void 0;
15111
+ const apps = platformWide || parsed.options["all-apps"] === true || !narrowed ? [import_db4.ALL_OWNED_APPS] : (stringOpt(parsed.options.app) ?? cfg.app.id).split(",").map((id2) => id2.trim()).filter(Boolean);
15067
15112
  if (apps.length === 0) throw new Error("device enroll needs --app <id>[,<id>\u2026], or --all-apps");
15068
15113
  const deviceTtlMs = parseDeviceTtl(parsed.options["device-ttl"]);
15069
15114
  const extended = platformWide || deviceTtlMs !== void 0 && deviceTtlMs > OWNER_DEVICE_TTL_MS;
15070
- const { capabilities, scopes } = requestedEnvelope(parsed, platformWide);
15115
+ const { capabilities, scopes } = requestedEnvelope(parsed, platformWide, narrowed);
15116
+ const narrowNotice = narrowEnrollmentNotice({
15117
+ appIds: apps,
15118
+ capabilities: capabilities ?? [],
15119
+ scopes: scopes ?? []
15120
+ });
15121
+ if (narrowNotice) out.error(narrowNotice);
15071
15122
  const token = await scopedToken2(
15072
15123
  parsed,
15073
15124
  deps,
@@ -15103,8 +15154,10 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
15103
15154
  }, null, 2));
15104
15155
  (0, import_node_fs21.chmodSync)(path, 384);
15105
15156
  rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
15157
+ supersedeAdvisory("credential.expiring");
15106
15158
  const reach = body.device.appIds.includes(import_db4.ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
15107
15159
  out.error(`device: enrolled "${name}" for ${reach}; credential written to ${path}`);
15160
+ if (narrowNotice) out.error(narrowNotice);
15108
15161
  out.error(
15109
15162
  "device: every worktree on this machine mints its own credentials from now on \u2014 no further approvals,"
15110
15163
  );
@@ -15123,9 +15176,9 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
15123
15176
  }, null, 2));
15124
15177
  }
15125
15178
  }
15126
- function requestedEnvelope(parsed, platformWide) {
15179
+ function requestedEnvelope(parsed, platformWide, narrowed) {
15127
15180
  const raw = stringOpt(parsed.options.capability);
15128
- const everything = platformWide || raw?.trim().toLowerCase() === "all";
15181
+ const everything = platformWide || !narrowed || raw?.trim().toLowerCase() === "all";
15129
15182
  if (everything) {
15130
15183
  return {
15131
15184
  capabilities: [...import_db4.OPTIONAL_AGENT_PROJECT_CAPABILITIES],
@@ -15209,6 +15262,8 @@ var init_device_command = __esm({
15209
15262
  import_db4 = require("@odla-ai/db");
15210
15263
  init_device_ttl();
15211
15264
  init_machine_identity();
15265
+ init_auth_guidance();
15266
+ init_advisory_output();
15212
15267
  import_node_fs21 = require("fs");
15213
15268
  import_node_path19 = require("path");
15214
15269
  import_node_process19 = __toESM(require("process"), 1);