@odla-ai/cli 0.43.1 → 0.45.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.
package/dist/bin.cjs CHANGED
@@ -99,17 +99,173 @@ var init_runbook_requires = __esm({
99
99
  }
100
100
  });
101
101
 
102
+ // src/odla-home.ts
103
+ function odlaHome(env = import_node_process.default.env) {
104
+ if (env.VITEST && !env.ODLA_HOME) {
105
+ throw new Error(
106
+ "ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
107
+ );
108
+ }
109
+ return env.ODLA_HOME ?? (0, import_node_path.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla");
110
+ }
111
+ function odlaHomePath(segments, env = import_node_process.default.env) {
112
+ return (0, import_node_path.join)(odlaHome(env), ...segments);
113
+ }
114
+ function identityFile(env) {
115
+ return odlaHomePath(["identity.json"], env);
116
+ }
117
+ function deviceSessionFile(env) {
118
+ return odlaHomePath(["session.json"], env);
119
+ }
120
+ function appTokenFile(appId, env) {
121
+ return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
122
+ }
123
+ function appCredentialsFile(appId, env) {
124
+ return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
125
+ }
126
+ function scopedTokenFile(env) {
127
+ return odlaHomePath(["admin-token.local.json"], env);
128
+ }
129
+ function pmContextFile(env) {
130
+ return odlaHomePath(["pm-context.json"], env);
131
+ }
132
+ function adoptRepoLocalCache(legacyPath, machinePath, out) {
133
+ if (!(0, import_node_fs.existsSync)(legacyPath) || legacyPath === machinePath) return false;
134
+ const superseded2 = (0, import_node_fs.existsSync)(machinePath);
135
+ if (!superseded2) {
136
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(machinePath), { recursive: true });
137
+ (0, import_node_fs.copyFileSync)(legacyPath, machinePath);
138
+ (0, import_node_fs.chmodSync)(machinePath, 384);
139
+ }
140
+ (0, import_node_fs.rmSync)(legacyPath, { force: true });
141
+ out?.error(
142
+ 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`
143
+ );
144
+ return true;
145
+ }
146
+ function safeSegment(value2) {
147
+ const clean4 = value2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
148
+ if (!clean4) throw new Error(`"${value2}" is not a usable app id`);
149
+ return clean4;
150
+ }
151
+ var import_node_fs, import_node_os, import_node_path, import_node_process;
152
+ var init_odla_home = __esm({
153
+ "src/odla-home.ts"() {
154
+ "use strict";
155
+ init_cjs_shims();
156
+ import_node_fs = require("fs");
157
+ import_node_os = require("os");
158
+ import_node_path = require("path");
159
+ import_node_process = __toESM(require("process"), 1);
160
+ }
161
+ });
162
+
102
163
  // src/version.ts
103
164
  function cliVersion() {
104
- const pkg = JSON.parse((0, import_node_fs.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
165
+ const pkg = JSON.parse((0, import_node_fs2.readFileSync)(new URL("../package.json", importMetaUrl), "utf8"));
105
166
  return pkg.version ?? "unknown";
106
167
  }
107
- var import_node_fs;
168
+ var import_node_fs2;
108
169
  var init_version = __esm({
109
170
  "src/version.ts"() {
110
171
  "use strict";
111
172
  init_cjs_shims();
112
- import_node_fs = require("fs");
173
+ import_node_fs2 = require("fs");
174
+ }
175
+ });
176
+
177
+ // src/update-notice.ts
178
+ function updateCacheFile(env) {
179
+ return odlaHomePath(["cli-update.json"], env);
180
+ }
181
+ function readUpdateCache(path) {
182
+ try {
183
+ const parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
184
+ if (typeof parsed.latest !== "string" || !VERSION.test(parsed.latest)) return null;
185
+ if (typeof parsed.checkedAt !== "number") return null;
186
+ return { latest: parsed.latest, checkedAt: parsed.checkedAt };
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ function writeUpdateCache(path, cache2) {
192
+ (0, import_node_fs3.mkdirSync)((0, import_node_path2.dirname)(path), { recursive: true });
193
+ (0, import_node_fs3.writeFileSync)(path, JSON.stringify(cache2));
194
+ }
195
+ function updateNotice(options = {}) {
196
+ const env = options.env ?? import_node_process2.default.env;
197
+ if (env.ODLA_CLI_UPDATE_CHECK === "0") return null;
198
+ const current = options.currentVersion ?? cliVersion();
199
+ if (!VERSION.test(current)) return null;
200
+ const path = updateCacheFile(env);
201
+ const cache2 = readUpdateCache(path);
202
+ const now = options.now ?? Date.now();
203
+ if ((!cache2 || now - cache2.checkedAt > INTERVAL_MS) && !refreshScheduled) {
204
+ refreshScheduled = true;
205
+ (options.refresh ?? spawnRefresh)(path, env.ODLA_CLI_REGISTRY_URL ?? REGISTRY_URL);
206
+ }
207
+ if (!cache2 || compareVersions(current, cache2.latest) >= 0) return null;
208
+ return renderNotice(current, cache2.latest, options.entryPath ?? import_node_process2.default.argv[1]);
209
+ }
210
+ function renderNotice(current, latest, entryPath) {
211
+ const resolved = resolvedEntryPath(entryPath);
212
+ const behindMajor = Number(latest.split(".")[0]) > Number(current.split(".")[0]);
213
+ const repair = isWorkspaceCli(resolved) ? `this is the workspace build at ${resolved} \u2014 rebase that worktree and rebuild it` : resolved.includes("/_npx/") ? `run npx --yes @odla-ai/cli@${latest} <command>` : `run npm i @odla-ai/cli@${latest}`;
214
+ const severity = behindMajor ? "a MAJOR version behind, so commands and flags this version has may no longer exist" : "behind";
215
+ return `odla-ai: ${current} is ${severity}; npm serves ${latest}. To update, ${repair}.`;
216
+ }
217
+ function spawnRefresh(cachePath, registryUrl) {
218
+ const script = `
219
+ const {mkdirSync,writeFileSync}=require("node:fs");
220
+ const {dirname}=require("node:path");
221
+ const [path,url]=process.argv.slice(1);
222
+ const done=setTimeout(()=>process.exit(0),5000); done.unref();
223
+ fetch(url,{headers:{accept:"application/vnd.npm.install-v1+json"}})
224
+ .then(r=>r.ok?r.json():null)
225
+ .then(b=>{
226
+ const v=b&&b.version;
227
+ if (typeof v!=="string"||!/^\\d+\\.\\d+\\.\\d+/.test(v)) return;
228
+ mkdirSync(dirname(path),{recursive:true});
229
+ writeFileSync(path,JSON.stringify({latest:v,checkedAt:Date.now()}));
230
+ })
231
+ .catch(()=>{});
232
+ `;
233
+ try {
234
+ (0, import_node_child_process.spawn)(import_node_process2.default.execPath, ["-e", script, cachePath, registryUrl], {
235
+ detached: true,
236
+ stdio: "ignore"
237
+ }).unref();
238
+ } catch {
239
+ }
240
+ }
241
+ function resolvedEntryPath(entryPath) {
242
+ if (!entryPath) return "unknown executable";
243
+ try {
244
+ return (0, import_node_fs3.realpathSync)(entryPath);
245
+ } catch {
246
+ return entryPath;
247
+ }
248
+ }
249
+ function isWorkspaceCli(entryPath) {
250
+ const normalized = entryPath.replaceAll("\\", "/");
251
+ return /\/packages\/cli\/(dist|bin)\//.test(normalized) && !normalized.includes("/node_modules/");
252
+ }
253
+ var import_node_child_process, import_node_fs3, import_node_path2, import_node_process2, REGISTRY_URL, INTERVAL_MS, VERSION, refreshScheduled;
254
+ var init_update_notice = __esm({
255
+ "src/update-notice.ts"() {
256
+ "use strict";
257
+ init_cjs_shims();
258
+ import_node_child_process = require("child_process");
259
+ import_node_fs3 = require("fs");
260
+ import_node_path2 = require("path");
261
+ import_node_process2 = __toESM(require("process"), 1);
262
+ init_odla_home();
263
+ init_runbook_requires();
264
+ init_version();
265
+ REGISTRY_URL = "https://registry.npmjs.org/@odla-ai%2fcli/latest";
266
+ INTERVAL_MS = 3 * 60 * 60 * 1e3;
267
+ VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
268
+ refreshScheduled = false;
113
269
  }
114
270
  });
115
271
 
@@ -232,8 +388,8 @@ var init_approval_prompt = __esm({
232
388
 
233
389
  // src/open.ts
234
390
  async function openUrl(url, options = {}) {
235
- const command = openerFor(options.platform ?? import_node_process.default.platform);
236
- const doSpawn = options.spawnImpl ?? import_node_child_process.spawn;
391
+ const command = openerFor(options.platform ?? import_node_process3.default.platform);
392
+ const doSpawn = options.spawnImpl ?? import_node_child_process2.spawn;
237
393
  await new Promise((resolve15, reject) => {
238
394
  const child = doSpawn(command.cmd, [...command.args, url], {
239
395
  stdio: "ignore",
@@ -251,13 +407,13 @@ function openerFor(platform) {
251
407
  if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", ""] };
252
408
  return { cmd: "xdg-open", args: [] };
253
409
  }
254
- var import_node_child_process, import_node_process;
410
+ var import_node_child_process2, import_node_process3;
255
411
  var init_open = __esm({
256
412
  "src/open.ts"() {
257
413
  "use strict";
258
414
  init_cjs_shims();
259
- import_node_child_process = require("child_process");
260
- import_node_process = __toESM(require("process"), 1);
415
+ import_node_child_process2 = require("child_process");
416
+ import_node_process3 = __toESM(require("process"), 1);
261
417
  }
262
418
  });
263
419
 
@@ -265,7 +421,7 @@ var init_open = __esm({
265
421
  function approvalBrowser(options, host = {}) {
266
422
  if (options.open === false) return { open: false, reason: "disabled by --no-open" };
267
423
  if (options.open === true) return { open: true, mode: "forced" };
268
- const env = host.env ?? import_node_process2.default.env;
424
+ const env = host.env ?? import_node_process4.default.env;
269
425
  if (env.VITEST || env.NODE_ENV === "test") {
270
426
  return { open: false, reason: "test environment" };
271
427
  }
@@ -295,12 +451,12 @@ function handshakeUrl(platformUrl, userCode) {
295
451
  url.searchParams.set("code", userCode);
296
452
  return url.toString();
297
453
  }
298
- var import_node_process2;
454
+ var import_node_process4;
299
455
  var init_handshake_approval = __esm({
300
456
  "src/handshake-approval.ts"() {
301
457
  "use strict";
302
458
  init_cjs_shims();
303
- import_node_process2 = __toESM(require("process"), 1);
459
+ import_node_process4 = __toESM(require("process"), 1);
304
460
  init_approval_prompt();
305
461
  init_open();
306
462
  }
@@ -308,10 +464,10 @@ var init_handshake_approval = __esm({
308
464
 
309
465
  // src/handshake-state.ts
310
466
  function handshakeFile(cfg) {
311
- return (0, import_node_path2.join)((0, import_node_path2.dirname)(cfg.local.tokenFile), "handshake.local.json");
467
+ return (0, import_node_path4.join)((0, import_node_path4.dirname)(cfg.local.tokenFile), "handshake.local.json");
312
468
  }
313
469
  function clearPendingHandshake(path) {
314
- (0, import_node_fs4.rmSync)(path, { force: true });
470
+ (0, import_node_fs5.rmSync)(path, { force: true });
315
471
  }
316
472
  function minutesLeft(expiresAt) {
317
473
  return Math.max(1, Math.round((expiresAt - Date.now()) / 6e4));
@@ -331,31 +487,31 @@ function approvalReminder(out, pending, periodMs = 3e4) {
331
487
  timer.unref?.();
332
488
  return () => clearInterval(timer);
333
489
  }
334
- function handshakeWaitMs(waitSeconds, interactive = import_node_process3.default.stdout.isTTY === true) {
490
+ function handshakeWaitMs(waitSeconds, interactive = import_node_process5.default.stdout.isTTY === true) {
335
491
  if (waitSeconds !== void 0) return waitSeconds * 1e3;
336
492
  return interactive ? void 0 : 9e4;
337
493
  }
338
- var import_node_fs4, import_node_path2, import_node_process3;
494
+ var import_node_fs5, import_node_path4, import_node_process5;
339
495
  var init_handshake_state = __esm({
340
496
  "src/handshake-state.ts"() {
341
497
  "use strict";
342
498
  init_cjs_shims();
343
- import_node_fs4 = require("fs");
344
- import_node_path2 = require("path");
345
- import_node_process3 = __toESM(require("process"), 1);
499
+ import_node_fs5 = require("fs");
500
+ import_node_path4 = require("path");
501
+ import_node_process5 = __toESM(require("process"), 1);
346
502
  init_approval_prompt();
347
503
  }
348
504
  });
349
505
 
350
506
  // src/device-session.ts
351
- function deviceCredentialPath(env = import_node_process4.default.env) {
352
- return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path3.join)(env.HOME ?? (0, import_node_os.homedir)(), ".odla", "device.json");
507
+ function deviceCredentialPath(env = import_node_process6.default.env) {
508
+ return env.ODLA_DEVICE_CREDENTIAL ?? (0, import_node_path5.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla", "device.json");
353
509
  }
354
- function readDeviceCredential(platform, env = import_node_process4.default.env) {
510
+ function readDeviceCredential(platform, env = import_node_process6.default.env) {
355
511
  const path = deviceCredentialPath(env);
356
- if (!(0, import_node_fs5.existsSync)(path)) return null;
512
+ if (!(0, import_node_fs6.existsSync)(path)) return null;
357
513
  try {
358
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
514
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
359
515
  if (typeof parsed.token !== "string" || !parsed.token.startsWith("odla_device_")) return null;
360
516
  if (parsed.platform !== platform) return null;
361
517
  return { ...parsed, token: parsed.token, platform: parsed.platform };
@@ -389,76 +545,15 @@ async function mintDeviceSession(platformUrl, credential2, doFetch) {
389
545
  ...Array.isArray(body.scopes) ? { scopes: body.scopes } : {}
390
546
  };
391
547
  }
392
- var import_node_fs5, import_node_os, import_node_path3, import_node_process4;
548
+ var import_node_fs6, import_node_os2, import_node_path5, import_node_process6;
393
549
  var init_device_session = __esm({
394
550
  "src/device-session.ts"() {
395
- "use strict";
396
- init_cjs_shims();
397
- import_node_fs5 = require("fs");
398
- import_node_os = require("os");
399
- import_node_path3 = require("path");
400
- import_node_process4 = __toESM(require("process"), 1);
401
- }
402
- });
403
-
404
- // src/odla-home.ts
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
- }
411
- return env.ODLA_HOME ?? (0, import_node_path4.join)(env.HOME ?? (0, import_node_os2.homedir)(), ".odla");
412
- }
413
- function odlaHomePath(segments, env = import_node_process5.default.env) {
414
- return (0, import_node_path4.join)(odlaHome(env), ...segments);
415
- }
416
- function identityFile(env) {
417
- return odlaHomePath(["identity.json"], env);
418
- }
419
- function deviceSessionFile(env) {
420
- return odlaHomePath(["session.json"], env);
421
- }
422
- function appTokenFile(appId, env) {
423
- return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
424
- }
425
- function appCredentialsFile(appId, env) {
426
- return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
427
- }
428
- function scopedTokenFile(env) {
429
- return odlaHomePath(["admin-token.local.json"], env);
430
- }
431
- function pmContextFile(env) {
432
- return odlaHomePath(["pm-context.json"], env);
433
- }
434
- function adoptRepoLocalCache(legacyPath, machinePath, out) {
435
- if (!(0, import_node_fs6.existsSync)(legacyPath) || legacyPath === machinePath) return false;
436
- const superseded2 = (0, import_node_fs6.existsSync)(machinePath);
437
- if (!superseded2) {
438
- (0, import_node_fs6.mkdirSync)((0, import_node_path4.dirname)(machinePath), { recursive: true });
439
- (0, import_node_fs6.copyFileSync)(legacyPath, machinePath);
440
- (0, import_node_fs6.chmodSync)(machinePath, 384);
441
- }
442
- (0, import_node_fs6.rmSync)(legacyPath, { force: true });
443
- out?.error(
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`
445
- );
446
- return true;
447
- }
448
- function safeSegment(value2) {
449
- const clean4 = value2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
450
- if (!clean4) throw new Error(`"${value2}" is not a usable app id`);
451
- return clean4;
452
- }
453
- var import_node_fs6, import_node_os2, import_node_path4, import_node_process5;
454
- var init_odla_home = __esm({
455
- "src/odla-home.ts"() {
456
551
  "use strict";
457
552
  init_cjs_shims();
458
553
  import_node_fs6 = require("fs");
459
554
  import_node_os2 = require("os");
460
- import_node_path4 = require("path");
461
- import_node_process5 = __toESM(require("process"), 1);
555
+ import_node_path5 = require("path");
556
+ import_node_process6 = __toESM(require("process"), 1);
462
557
  }
463
558
  });
464
559
 
@@ -508,7 +603,7 @@ function mergeCredential(current, update) {
508
603
  return next;
509
604
  }
510
605
  function ensureGitignore(rootDir, localPaths = []) {
511
- const path = (0, import_node_path5.resolve)(rootDir, ".gitignore");
606
+ const path = (0, import_node_path6.resolve)(rootDir, ".gitignore");
512
607
  const existing = (0, import_node_fs7.existsSync)(path) ? (0, import_node_fs7.readFileSync)(path, "utf8") : "";
513
608
  const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
514
609
  const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
@@ -529,7 +624,7 @@ function o11yDevVars(cfg) {
529
624
  function resolveWriteDevVarsTarget(cfg, requested) {
530
625
  if (!requested) return null;
531
626
  if (requested === true) return cfg.local.devVarsFile;
532
- return (0, import_node_path5.resolve)((0, import_node_path5.dirname)(cfg.configPath), requested);
627
+ return (0, import_node_path6.resolve)((0, import_node_path6.dirname)(cfg.configPath), requested);
533
628
  }
534
629
  function writeDevVars(path, credentials, env, o11y) {
535
630
  const entry = credentials.envs[env];
@@ -557,28 +652,28 @@ function isManagedDevVar(line2) {
557
652
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
558
653
  }
559
654
  function writePrivateText(path, text4) {
560
- (0, import_node_fs7.mkdirSync)((0, import_node_path5.dirname)(path), { recursive: true });
655
+ (0, import_node_fs7.mkdirSync)((0, import_node_path6.dirname)(path), { recursive: true });
561
656
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
562
657
  (0, import_node_fs7.writeFileSync)(temporary, text4, { mode: 384 });
563
658
  (0, import_node_fs7.chmodSync)(temporary, 384);
564
659
  (0, import_node_fs7.renameSync)(temporary, path);
565
660
  }
566
661
  function gitignoreEntry(rootDir, path) {
567
- const rel = (0, import_node_path5.relative)((0, import_node_path5.resolve)(rootDir), (0, import_node_path5.resolve)(path));
568
- if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path5.isAbsolute)(rel)) return null;
662
+ const rel = (0, import_node_path6.relative)((0, import_node_path6.resolve)(rootDir), (0, import_node_path6.resolve)(path));
663
+ if (!rel || rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || (0, import_node_path6.isAbsolute)(rel)) return null;
569
664
  return rel.replaceAll("\\", "/");
570
665
  }
571
666
  function displayPath(path, rootDir = process.cwd()) {
572
- const rel = (0, import_node_path5.relative)(rootDir, path);
667
+ const rel = (0, import_node_path6.relative)(rootDir, path);
573
668
  return rel && !rel.startsWith("..") ? rel : path;
574
669
  }
575
- var import_node_fs7, import_node_path5, GITIGNORE_LINES, MANAGED_DEV_VARS;
670
+ var import_node_fs7, import_node_path6, GITIGNORE_LINES, MANAGED_DEV_VARS;
576
671
  var init_local = __esm({
577
672
  "src/local.ts"() {
578
673
  "use strict";
579
674
  init_cjs_shims();
580
675
  import_node_fs7 = require("fs");
581
- import_node_path5 = require("path");
676
+ import_node_path6 = require("path");
582
677
  GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
583
678
  MANAGED_DEV_VARS = /* @__PURE__ */ new Set([
584
679
  "ODLA_PLATFORM",
@@ -596,7 +691,7 @@ var init_local = __esm({
596
691
  });
597
692
 
598
693
  // src/auth-guidance.ts
599
- function machineAuthState(audience, env = import_node_process6.default.env) {
694
+ function machineAuthState(audience, env = import_node_process7.default.env) {
600
695
  const device = readDeviceCredential(audience, env);
601
696
  if (!device) return { enrolled: false };
602
697
  const session = readJsonFile(deviceSessionFile(env));
@@ -640,12 +735,12 @@ function lapseNotice(state2, now = Date.now()) {
640
735
  if (days < 0) return "this machine's enrollment has lapsed; the next command will ask for approval";
641
736
  return `idle for ${days} more day${days === 1 ? "" : "s"} before this machine needs approving again (using it resets the clock)`;
642
737
  }
643
- var import_node_process6, ENROL_EVERYTHING, ENROL_PLATFORM_WIDE;
738
+ var import_node_process7, ENROL_EVERYTHING, ENROL_PLATFORM_WIDE;
644
739
  var init_auth_guidance = __esm({
645
740
  "src/auth-guidance.ts"() {
646
741
  "use strict";
647
742
  init_cjs_shims();
648
- import_node_process6 = __toESM(require("process"), 1);
743
+ import_node_process7 = __toESM(require("process"), 1);
649
744
  init_device_session();
650
745
  init_odla_home();
651
746
  init_local();
@@ -687,7 +782,7 @@ var init_cached_credential = __esm({
687
782
  });
688
783
 
689
784
  // src/device-session-cache.ts
690
- async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = import_node_process7.default.env) {
785
+ async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = import_node_process8.default.env) {
691
786
  const path = deviceSessionFile(env);
692
787
  const cached = readJsonFile(path);
693
788
  if (cached?.token && cached.platform === audience && cached.deviceId === credential2.deviceId && (cached.expiresAt ?? 0) > Date.now() + SKEW_MS) return cached;
@@ -700,12 +795,12 @@ async function deviceSessionToken(platformUrl, audience, credential2, doFetch, e
700
795
  writePrivateJson(path, session);
701
796
  return session;
702
797
  }
703
- var import_node_process7, SKEW_MS;
798
+ var import_node_process8, SKEW_MS;
704
799
  var init_device_session_cache = __esm({
705
800
  "src/device-session-cache.ts"() {
706
801
  "use strict";
707
802
  init_cjs_shims();
708
- import_node_process7 = __toESM(require("process"), 1);
803
+ import_node_process8 = __toESM(require("process"), 1);
709
804
  init_odla_home();
710
805
  init_local();
711
806
  init_device_session();
@@ -714,23 +809,23 @@ var init_device_session_cache = __esm({
714
809
  });
715
810
 
716
811
  // src/machine-identity.ts
717
- function readMachineIdentity(audience, env = import_node_process8.default.env) {
812
+ function readMachineIdentity(audience, env = import_node_process9.default.env) {
718
813
  const stored = readJsonFile(identityFile(env));
719
814
  if (!stored || typeof stored.email !== "string" || !stored.email) return null;
720
815
  return stored.platform === audience ? { platform: audience, email: stored.email } : null;
721
816
  }
722
- function rememberMachineIdentity(audience, email, env = import_node_process8.default.env) {
817
+ function rememberMachineIdentity(audience, email, env = import_node_process9.default.env) {
723
818
  if (!email) return;
724
819
  const existing = readMachineIdentity(audience, env);
725
820
  if (existing?.email === email) return;
726
821
  writePrivateJson(identityFile(env), { platform: audience, email });
727
822
  }
728
- var import_node_process8;
823
+ var import_node_process9;
729
824
  var init_machine_identity = __esm({
730
825
  "src/machine-identity.ts"() {
731
826
  "use strict";
732
827
  init_cjs_shims();
733
- import_node_process8 = __toESM(require("process"), 1);
828
+ import_node_process9 = __toESM(require("process"), 1);
734
829
  init_odla_home();
735
830
  init_local();
736
831
  }
@@ -744,14 +839,14 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
744
839
  const cached = readJsonFile(cfg.local.tokenFile);
745
840
  if (!grantRequest.forceReview && !grantRequest.freshLogin) {
746
841
  if (options.token) return options.token;
747
- if (import_node_process9.default.env.ODLA_DEV_TOKEN) {
748
- const declared = import_node_process9.default.env.ODLA_DEV_TOKEN_AUDIENCE;
842
+ if (import_node_process10.default.env.ODLA_DEV_TOKEN) {
843
+ const declared = import_node_process10.default.env.ODLA_DEV_TOKEN_AUDIENCE;
749
844
  if (declared) {
750
845
  if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
751
846
  } else if (audience !== "https://odla.ai") {
752
847
  throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
753
848
  }
754
- return import_node_process9.default.env.ODLA_DEV_TOKEN;
849
+ return import_node_process10.default.env.ODLA_DEV_TOKEN;
755
850
  }
756
851
  const device = readDeviceCredential(audience);
757
852
  if (device) {
@@ -866,7 +961,7 @@ function stillPending(pending, email) {
866
961
  );
867
962
  }
868
963
  function handshakeEmail(value2, cached) {
869
- const email = (value2 ?? import_node_process9.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
964
+ const email = (value2 ?? import_node_process10.default.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
870
965
  if (/@users\.noreply\.github\.com$/i.test(email)) {
871
966
  throw new Error(
872
967
  `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
@@ -895,14 +990,14 @@ function platformAudience(value2) {
895
990
  }
896
991
  return url.origin;
897
992
  }
898
- var import_db, import_node_crypto, import_node_process9;
993
+ var import_db, import_node_crypto, import_node_process10;
899
994
  var init_token = __esm({
900
995
  "src/token.ts"() {
901
996
  "use strict";
902
997
  init_cjs_shims();
903
998
  import_db = require("@odla-ai/db");
904
999
  import_node_crypto = require("crypto");
905
- import_node_process9 = __toESM(require("process"), 1);
1000
+ import_node_process10 = __toESM(require("process"), 1);
906
1001
  init_handshake_approval();
907
1002
  init_handshake_state();
908
1003
  init_cached_credential();
@@ -917,7 +1012,7 @@ var init_token = __esm({
917
1012
  async function secretInputValue(options, kind = "credential") {
918
1013
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
919
1014
  let value2;
920
- if (options.fromEnv) value2 = import_node_process10.default.env[options.fromEnv];
1015
+ if (options.fromEnv) value2 = import_node_process11.default.env[options.fromEnv];
921
1016
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
922
1017
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
923
1018
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -925,7 +1020,7 @@ async function secretInputValue(options, kind = "credential") {
925
1020
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
926
1021
  return value2;
927
1022
  }
928
- async function readSecretStream(kind, stream = import_node_process10.default.stdin) {
1023
+ async function readSecretStream(kind, stream = import_node_process11.default.stdin) {
929
1024
  let value2 = "";
930
1025
  for await (const chunk of stream) {
931
1026
  value2 += String(chunk);
@@ -933,12 +1028,12 @@ async function readSecretStream(kind, stream = import_node_process10.default.std
933
1028
  }
934
1029
  return value2;
935
1030
  }
936
- var import_node_process10, MAX_BYTES;
1031
+ var import_node_process11, MAX_BYTES;
937
1032
  var init_secret_input = __esm({
938
1033
  "src/secret-input.ts"() {
939
1034
  "use strict";
940
1035
  init_cjs_shims();
941
- import_node_process10 = __toESM(require("process"), 1);
1036
+ import_node_process11 = __toESM(require("process"), 1);
942
1037
  MAX_BYTES = 64 * 1024;
943
1038
  }
944
1039
  });
@@ -950,7 +1045,7 @@ async function getScopedPlatformToken(options) {
950
1045
  async function resolveAdminPlatformToken(options) {
951
1046
  const audience = platformAudience(options.platform);
952
1047
  if (options.token) return options.token;
953
- const fromEnv = import_node_process11.default.env.ODLA_ADMIN_TOKEN;
1048
+ const fromEnv = import_node_process12.default.env.ODLA_ADMIN_TOKEN;
954
1049
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
955
1050
  return scopedToken(
956
1051
  audience,
@@ -962,7 +1057,7 @@ async function resolveAdminPlatformToken(options) {
962
1057
  }
963
1058
  function audienceBoundEnvToken(token, platform) {
964
1059
  const audience = platformAudience(platform);
965
- const declared = import_node_process11.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
1060
+ const declared = import_node_process12.default.env.ODLA_ADMIN_TOKEN_AUDIENCE;
966
1061
  if (declared) {
967
1062
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
968
1063
  } else if (audience !== "https://odla.ai") {
@@ -972,9 +1067,9 @@ function audienceBoundEnvToken(token, platform) {
972
1067
  }
973
1068
  async function scopedToken(platform, scope, options, doFetch, out) {
974
1069
  const audience = platformAudience(platform);
975
- const rootDir = options.rootDir ?? import_node_process11.default.cwd();
1070
+ const rootDir = options.rootDir ?? import_node_process12.default.cwd();
976
1071
  const tokenFile = options.tokenFile ?? scopedTokenFile();
977
- adoptRepoLocalCache((0, import_node_path6.join)(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
1072
+ adoptRepoLocalCache((0, import_node_path7.join)(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
978
1073
  const device = readDeviceCredential(audience);
979
1074
  if (device && options.cache !== false) {
980
1075
  const session = await deviceSessionToken(platform, audience, device, doFetch);
@@ -1021,13 +1116,13 @@ async function scopedToken(platform, scope, options, doFetch, out) {
1021
1116
  }
1022
1117
  return token;
1023
1118
  }
1024
- var import_node_path6, import_node_process11, import_db2, SCOPE_PURPOSE;
1119
+ var import_node_path7, import_node_process12, import_db2, SCOPE_PURPOSE;
1025
1120
  var init_admin_ai_auth = __esm({
1026
1121
  "src/admin-ai-auth.ts"() {
1027
1122
  "use strict";
1028
1123
  init_cjs_shims();
1029
- import_node_path6 = require("path");
1030
- import_node_process11 = __toESM(require("process"), 1);
1124
+ import_node_path7 = require("path");
1125
+ import_node_process12 = __toESM(require("process"), 1);
1031
1126
  import_db2 = require("@odla-ai/db");
1032
1127
  init_local();
1033
1128
  init_handshake_approval();
@@ -1252,7 +1347,7 @@ var init_admin_ai_usage = __esm({
1252
1347
 
1253
1348
  // src/admin-ai.ts
1254
1349
  async function adminAi(options) {
1255
- const platform = platformAudience(options.platform ?? import_node_process12.default.env.ODLA_PLATFORM ?? "https://odla.ai");
1350
+ const platform = platformAudience(options.platform ?? import_node_process13.default.env.ODLA_PLATFORM ?? "https://odla.ai");
1256
1351
  const doFetch = options.fetch ?? fetch;
1257
1352
  const out = options.stdout ?? console;
1258
1353
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -1430,12 +1525,12 @@ function apiError3(action2, status, body) {
1430
1525
  function isRecord3(value2) {
1431
1526
  return Boolean(value2) && typeof value2 === "object" && !Array.isArray(value2);
1432
1527
  }
1433
- var import_node_process12;
1528
+ var import_node_process13;
1434
1529
  var init_admin_ai = __esm({
1435
1530
  "src/admin-ai.ts"() {
1436
1531
  "use strict";
1437
1532
  init_cjs_shims();
1438
- import_node_process12 = __toESM(require("process"), 1);
1533
+ import_node_process13 = __toESM(require("process"), 1);
1439
1534
  init_token();
1440
1535
  init_secret_input();
1441
1536
  init_admin_ai_auth();
@@ -1480,10 +1575,17 @@ function parseArgv(argv2) {
1480
1575
  function assertArgs(parsed, allowedOptions2, maxPositionals) {
1481
1576
  const allowed = new Set(allowedOptions2);
1482
1577
  for (const name of Object.keys(parsed.options)) {
1483
- if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
1578
+ if (allowed.has(name)) continue;
1579
+ const accepts = [...allowed].sort().map((option) => `--${option}`).join(" ");
1580
+ throw new Error(
1581
+ `unknown option "--${name}"` + (accepts ? ` \u2014 this command accepts: ${accepts}` : " \u2014 this command takes no options")
1582
+ );
1484
1583
  }
1485
1584
  if (parsed.positionals.length > maxPositionals) {
1486
- throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
1585
+ const taken = parsed.positionals.slice(0, maxPositionals).join(" ");
1586
+ throw new Error(
1587
+ `unexpected argument "${parsed.positionals[maxPositionals]}" \u2014 ` + (maxPositionals ? `"odla-ai ${taken}" takes no further arguments` : "this command takes no arguments")
1588
+ );
1487
1589
  }
1488
1590
  }
1489
1591
  function requiredString(value2, name) {
@@ -1527,6 +1629,196 @@ var init_argv = __esm({
1527
1629
  }
1528
1630
  });
1529
1631
 
1632
+ // src/surface.ts
1633
+ function acceptedAfter(path) {
1634
+ let node = COMMAND_SURFACE;
1635
+ for (const word of path) {
1636
+ node = node?.[word];
1637
+ if (!node) return [];
1638
+ }
1639
+ return Object.keys(node).sort();
1640
+ }
1641
+ function validateInvocation(words2) {
1642
+ let node = COMMAND_SURFACE;
1643
+ const walked = [];
1644
+ for (const word of words2) {
1645
+ if (Object.keys(node).length === 0) return null;
1646
+ const next = node[word];
1647
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
1648
+ walked.push(word);
1649
+ node = next;
1650
+ }
1651
+ return null;
1652
+ }
1653
+ function describeProblem(problem) {
1654
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
1655
+ if (!problem.accepted.length) return `"${problem.word}" is not accepted ${where}. Run "odla-ai help"`;
1656
+ if (!problem.word) return `"odla-ai ${problem.validPrefix}" needs one of: ${problem.accepted.join(", ")}`;
1657
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
1658
+ }
1659
+ function rejectWord(path, word, note) {
1660
+ const sentence = describeProblem({
1661
+ validPrefix: path.join(" "),
1662
+ word: word ?? "",
1663
+ accepted: acceptedAfter(path)
1664
+ });
1665
+ throw new Error(note ? `${sentence}. ${note}` : sentence);
1666
+ }
1667
+ function invocationPath(words2) {
1668
+ let node = COMMAND_SURFACE;
1669
+ const path = [];
1670
+ for (const word of words2) {
1671
+ const next = node[word];
1672
+ if (!next) break;
1673
+ path.push(word);
1674
+ node = next;
1675
+ if (Object.keys(node).length === 0) break;
1676
+ }
1677
+ return path;
1678
+ }
1679
+ var PM_ACTIONS, PM_TASK_ACTIONS, PM_ENTITIES, COMMAND_SURFACE;
1680
+ var init_surface = __esm({
1681
+ "src/surface.ts"() {
1682
+ "use strict";
1683
+ init_cjs_shims();
1684
+ PM_ACTIONS = {
1685
+ list: {},
1686
+ add: {},
1687
+ create: {},
1688
+ get: {},
1689
+ set: {},
1690
+ update: {},
1691
+ status: {},
1692
+ move: {},
1693
+ done: {},
1694
+ comment: {},
1695
+ comments: {},
1696
+ history: {},
1697
+ link: {},
1698
+ ref: {},
1699
+ rm: {},
1700
+ delete: {}
1701
+ };
1702
+ PM_TASK_ACTIONS = {
1703
+ ...PM_ACTIONS,
1704
+ ready: {},
1705
+ claim: {},
1706
+ release: {}
1707
+ };
1708
+ PM_ENTITIES = {
1709
+ ...Object.fromEntries(
1710
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
1711
+ ),
1712
+ task: PM_TASK_ACTIONS,
1713
+ kanban: PM_TASK_ACTIONS
1714
+ };
1715
+ COMMAND_SURFACE = {
1716
+ agent: { jobs: {}, retry: {} },
1717
+ ai: { models: {} },
1718
+ admin: {
1719
+ ai: {
1720
+ show: {},
1721
+ set: {},
1722
+ credentials: {},
1723
+ models: {},
1724
+ usage: {},
1725
+ audit: {},
1726
+ credential: { set: {} }
1727
+ },
1728
+ spend: { show: {}, reset: {} }
1729
+ },
1730
+ app: {
1731
+ archive: {},
1732
+ restore: {},
1733
+ export: {},
1734
+ import: {},
1735
+ rename: {},
1736
+ "refresh-sandbox": {},
1737
+ "go-live": {},
1738
+ promote: {},
1739
+ owners: { list: {}, add: {}, remove: {} }
1740
+ },
1741
+ auth: { login: {} },
1742
+ brand: { design: { unpack: {} } },
1743
+ bug: { create: {}, list: {}, report: {} },
1744
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
1745
+ capabilities: {},
1746
+ code: {
1747
+ connect: {},
1748
+ grant: { request: {}, list: {}, approve: {}, revoke: {} },
1749
+ repository: { show: {}, list: {}, bind: {} }
1750
+ },
1751
+ config: { diff: {}, plan: {}, apply: {} },
1752
+ context: { show: {}, list: {}, save: {}, remove: {} },
1753
+ credentials: { list: {}, revoke: {} },
1754
+ device: { enroll: {}, list: {}, revoke: {} },
1755
+ // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
1756
+ discuss: {
1757
+ groups: {},
1758
+ list: {},
1759
+ topics: {},
1760
+ read: {},
1761
+ post: {},
1762
+ reply: {},
1763
+ resolve: {},
1764
+ who: {},
1765
+ watch: {}
1766
+ },
1767
+ doctor: {},
1768
+ help: {},
1769
+ init: {},
1770
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
1771
+ o11y: { status: {} },
1772
+ operations: { get: {}, wait: {} },
1773
+ platform: {
1774
+ status: {}
1775
+ },
1776
+ pm: {
1777
+ ...PM_ENTITIES,
1778
+ project: { list: {}, add: {}, create: {}, use: {} },
1779
+ handoff: {},
1780
+ next: {},
1781
+ start: {},
1782
+ watch: {}
1783
+ },
1784
+ provision: {},
1785
+ runbook: {
1786
+ ask: {},
1787
+ search: {},
1788
+ impact: {},
1789
+ list: {},
1790
+ get: {},
1791
+ cat: {},
1792
+ new: {},
1793
+ edit: {},
1794
+ comment: {},
1795
+ import: {},
1796
+ visibility: {},
1797
+ publish: {},
1798
+ archive: {},
1799
+ history: {},
1800
+ revert: {},
1801
+ rm: {},
1802
+ lint: {}
1803
+ },
1804
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
1805
+ security: {
1806
+ plan: {},
1807
+ sources: {},
1808
+ run: {},
1809
+ status: {},
1810
+ report: {},
1811
+ github: { connect: {}, disconnect: {} }
1812
+ },
1813
+ setup: {},
1814
+ skill: { install: {} },
1815
+ smoke: {},
1816
+ version: {},
1817
+ whoami: {}
1818
+ };
1819
+ }
1820
+ });
1821
+
1530
1822
  // src/admin-spend.ts
1531
1823
  async function call(ctx, method, scope) {
1532
1824
  const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
@@ -1575,9 +1867,7 @@ async function spendReset(ctx, scope) {
1575
1867
  async function adminSpend(parsed, ctx) {
1576
1868
  const action2 = parsed.positionals[2];
1577
1869
  const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1578
- if (action2 !== "show" && action2 !== "reset") {
1579
- throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1580
- }
1870
+ if (action2 !== "show" && action2 !== "reset") rejectWord(["admin", "spend"], action2);
1581
1871
  if (!scope) {
1582
1872
  throw new Error(
1583
1873
  `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
@@ -1591,6 +1881,7 @@ var init_admin_spend = __esm({
1591
1881
  "use strict";
1592
1882
  init_cjs_shims();
1593
1883
  init_argv();
1884
+ init_surface();
1594
1885
  money = (value2) => `$${value2.toFixed(2)}`;
1595
1886
  }
1596
1887
  });
@@ -2017,12 +2308,12 @@ var init_monitoring_validation = __esm({
2017
2308
 
2018
2309
  // src/config.ts
2019
2310
  async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
2020
- const resolved = (0, import_node_path7.resolve)(configPath);
2311
+ const resolved = (0, import_node_path8.resolve)(configPath);
2021
2312
  if (!(0, import_node_fs9.existsSync)(resolved)) {
2022
2313
  throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
2023
2314
  }
2024
2315
  const raw = await loadConfigModule(resolved);
2025
- const rootDir = (0, import_node_path7.dirname)(resolved);
2316
+ const rootDir = (0, import_node_path8.dirname)(resolved);
2026
2317
  validateRawConfig(raw, resolved);
2027
2318
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
2028
2319
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
@@ -2032,14 +2323,14 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
2032
2323
  validateCalendarConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
2033
2324
  validateMonitoringConfig(raw, unique3([...envs, ...options.additionalEnvs ?? []]), services, resolved);
2034
2325
  const local = {
2035
- tokenFile: raw.local?.tokenFile ? (0, import_node_path7.resolve)(rootDir, raw.local.tokenFile) : appTokenFile(raw.app.id),
2036
- credentialsFile: raw.local?.credentialsFile ? (0, import_node_path7.resolve)(rootDir, raw.local.credentialsFile) : appCredentialsFile(raw.app.id),
2037
- devVarsFile: (0, import_node_path7.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
2326
+ tokenFile: raw.local?.tokenFile ? (0, import_node_path8.resolve)(rootDir, raw.local.tokenFile) : appTokenFile(raw.app.id),
2327
+ credentialsFile: raw.local?.credentialsFile ? (0, import_node_path8.resolve)(rootDir, raw.local.credentialsFile) : appCredentialsFile(raw.app.id),
2328
+ devVarsFile: (0, import_node_path8.resolve)(rootDir, raw.local?.devVarsFile ?? ".dev.vars"),
2038
2329
  gitignore: raw.local?.gitignore ?? true
2039
2330
  };
2040
- adoptRepoLocalCache((0, import_node_path7.resolve)(rootDir, ".odla/dev-token.json"), local.tokenFile, stderr);
2041
- adoptRepoLocalCache((0, import_node_path7.resolve)(rootDir, ".odla/credentials.local.json"), local.credentialsFile, stderr);
2042
- (0, import_node_fs9.rmSync)((0, import_node_path7.resolve)(rootDir, ".odla/handshake.local.json"), { force: true });
2331
+ adoptRepoLocalCache((0, import_node_path8.resolve)(rootDir, ".odla/dev-token.json"), local.tokenFile, stderr);
2332
+ adoptRepoLocalCache((0, import_node_path8.resolve)(rootDir, ".odla/credentials.local.json"), local.credentialsFile, stderr);
2333
+ (0, import_node_fs9.rmSync)((0, import_node_path8.resolve)(rootDir, ".odla/handshake.local.json"), { force: true });
2043
2334
  return {
2044
2335
  ...raw,
2045
2336
  configPath: resolved,
@@ -2054,7 +2345,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
2054
2345
  async function resolveDataExport(cfg, value2, names) {
2055
2346
  if (value2 === void 0 || value2 === null || value2 === false) return void 0;
2056
2347
  if (typeof value2 !== "string") return value2;
2057
- const target = (0, import_node_path7.isAbsolute)(value2) ? value2 : (0, import_node_path7.resolve)(cfg.rootDir, value2);
2348
+ const target = (0, import_node_path8.isAbsolute)(value2) ? value2 : (0, import_node_path8.resolve)(cfg.rootDir, value2);
2058
2349
  if (target.endsWith(".json")) {
2059
2350
  return JSON.parse((0, import_node_fs9.readFileSync)(target, "utf8"));
2060
2351
  }
@@ -2145,13 +2436,13 @@ function trimSlash(value2) {
2145
2436
  function unique3(values) {
2146
2437
  return [...new Set(values.filter(Boolean))];
2147
2438
  }
2148
- var import_node_fs9, import_node_path7, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE, stderr;
2439
+ var import_node_fs9, import_node_path8, import_node_url, import_apps, DEFAULT_PLATFORM, DEFAULT_ENVS, DEFAULT_SERVICES, configImportSerial, GOOGLE_CALENDAR_EVENTS_SCOPE, stderr;
2149
2440
  var init_config = __esm({
2150
2441
  "src/config.ts"() {
2151
2442
  "use strict";
2152
2443
  init_cjs_shims();
2153
2444
  import_node_fs9 = require("fs");
2154
- import_node_path7 = require("path");
2445
+ import_node_path8 = require("path");
2155
2446
  import_node_url = require("url");
2156
2447
  import_apps = require("@odla-ai/apps");
2157
2448
  init_ai_config_validation();
@@ -2174,13 +2465,13 @@ var init_config = __esm({
2174
2465
 
2175
2466
  // src/operator-profiles.ts
2176
2467
  function operatorProfileFile() {
2177
- return (0, import_node_path8.resolve)(
2178
- clean(import_node_process13.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".odla", "contexts.json")
2468
+ return (0, import_node_path9.resolve)(
2469
+ clean(import_node_process14.default.env.ODLA_CONTEXT_FILE) ?? (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".odla", "contexts.json")
2179
2470
  );
2180
2471
  }
2181
2472
  function resolveOperatorProfile(parsed) {
2182
2473
  const fromFlag = clean(stringOpt(parsed.options.context));
2183
- const fromEnvironment = clean(import_node_process13.default.env.ODLA_CONTEXT);
2474
+ const fromEnvironment = clean(import_node_process14.default.env.ODLA_CONTEXT);
2184
2475
  const name = fromFlag ?? fromEnvironment ?? null;
2185
2476
  const file = operatorProfileFile();
2186
2477
  if (!name) {
@@ -2220,10 +2511,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
2220
2511
  return true;
2221
2512
  }
2222
2513
  function operatorCredentialFiles(selection) {
2223
- const base = selection.name ? (0, import_node_path8.join)((0, import_node_path8.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".odla");
2514
+ const base = selection.name ? (0, import_node_path9.join)((0, import_node_path9.dirname)(selection.file), "profiles", selection.name) : (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".odla");
2224
2515
  return {
2225
- developer: (0, import_node_path8.join)(base, "dev-token.json"),
2226
- scoped: (0, import_node_path8.join)(base, "admin-token.local.json")
2516
+ developer: (0, import_node_path9.join)(base, "dev-token.json"),
2517
+ scoped: (0, import_node_path9.join)(base, "admin-token.local.json")
2227
2518
  };
2228
2519
  }
2229
2520
  function assertOperatorName(value2, label) {
@@ -2295,15 +2586,15 @@ function clean(value2) {
2295
2586
  const normalized = value2?.trim();
2296
2587
  return normalized || void 0;
2297
2588
  }
2298
- var import_node_fs10, import_node_os3, import_node_path8, import_node_process13;
2589
+ var import_node_fs10, import_node_os3, import_node_path9, import_node_process14;
2299
2590
  var init_operator_profiles = __esm({
2300
2591
  "src/operator-profiles.ts"() {
2301
2592
  "use strict";
2302
2593
  init_cjs_shims();
2303
2594
  import_node_fs10 = require("fs");
2304
2595
  import_node_os3 = require("os");
2305
- import_node_path8 = require("path");
2306
- import_node_process13 = __toESM(require("process"), 1);
2596
+ import_node_path9 = require("path");
2597
+ import_node_process14 = __toESM(require("process"), 1);
2307
2598
  init_argv();
2308
2599
  init_local();
2309
2600
  init_token();
@@ -2314,7 +2605,7 @@ var init_operator_profiles = __esm({
2314
2605
  async function resolveOperatorContext(parsed, options = {}) {
2315
2606
  const profile = resolveOperatorProfile(parsed);
2316
2607
  const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
2317
- const configPath = (0, import_node_path9.resolve)(configArgument);
2608
+ const configPath = (0, import_node_path10.resolve)(configArgument);
2318
2609
  const explicitConfig = parsed.options.config !== void 0;
2319
2610
  const hasConfig = (0, import_node_fs11.existsSync)(configPath);
2320
2611
  if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
@@ -2322,13 +2613,13 @@ async function resolveOperatorContext(parsed, options = {}) {
2322
2613
  }
2323
2614
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
2324
2615
  const platformFlag = clean2(stringOpt(parsed.options.platform));
2325
- const platformEnvironment = clean2(import_node_process14.default.env.ODLA_PLATFORM_URL);
2616
+ const platformEnvironment = clean2(import_node_process15.default.env.ODLA_PLATFORM_URL);
2326
2617
  const platformValue = platformAudience(
2327
2618
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
2328
2619
  );
2329
2620
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
2330
2621
  const appFlag = clean2(stringOpt(parsed.options.app));
2331
- const appEnvironment = clean2(import_node_process14.default.env.ODLA_APP_ID);
2622
+ const appEnvironment = clean2(import_node_process15.default.env.ODLA_APP_ID);
2332
2623
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
2333
2624
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
2334
2625
  if (appValue) {
@@ -2342,16 +2633,16 @@ async function resolveOperatorContext(parsed, options = {}) {
2342
2633
  );
2343
2634
  }
2344
2635
  const envFlag = clean2(stringOpt(parsed.options.env));
2345
- const envEnvironment = clean2(import_node_process14.default.env.ODLA_ENV);
2636
+ const envEnvironment = clean2(import_node_process15.default.env.ODLA_ENV);
2346
2637
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
2347
2638
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
2348
2639
  if (environmentValue) {
2349
2640
  assertOperatorName(environmentValue, "environment");
2350
2641
  }
2351
- const rootDir = loaded?.rootDir ?? import_node_process14.default.cwd();
2642
+ const rootDir = loaded?.rootDir ?? import_node_process15.default.cwd();
2352
2643
  const profileCredentials = operatorCredentialFiles(profile);
2353
- const tokenFile = clean2(import_node_process14.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path9.resolve)(import_node_process14.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2354
- const scopedTokenFile2 = clean2(import_node_process14.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path9.resolve)(import_node_process14.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path9.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2644
+ const tokenFile = clean2(import_node_process15.default.env.ODLA_DEV_TOKEN_FILE) ? (0, import_node_path10.resolve)(import_node_process15.default.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2645
+ const scopedTokenFile2 = clean2(import_node_process15.default.env.ODLA_ADMIN_TOKEN_FILE) ? (0, import_node_path10.resolve)(import_node_process15.default.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? (0, import_node_path10.join)(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2355
2646
  const cfg = loaded ? {
2356
2647
  ...loaded,
2357
2648
  platformUrl: platformValue,
@@ -2373,8 +2664,8 @@ async function resolveOperatorContext(parsed, options = {}) {
2373
2664
  services: [],
2374
2665
  local: {
2375
2666
  tokenFile,
2376
- credentialsFile: (0, import_node_path9.join)(rootDir, ".odla", "credentials.local.json"),
2377
- devVarsFile: (0, import_node_path9.join)(rootDir, ".dev.vars"),
2667
+ credentialsFile: (0, import_node_path10.join)(rootDir, ".odla", "credentials.local.json"),
2668
+ devVarsFile: (0, import_node_path10.join)(rootDir, ".dev.vars"),
2378
2669
  gitignore: true
2379
2670
  }
2380
2671
  };
@@ -2406,14 +2697,14 @@ function clean2(value2) {
2406
2697
  const normalized = value2?.trim();
2407
2698
  return normalized || void 0;
2408
2699
  }
2409
- var import_node_fs11, import_node_path9, import_node_process14, DEFAULT_PLATFORM2;
2700
+ var import_node_fs11, import_node_path10, import_node_process15, DEFAULT_PLATFORM2;
2410
2701
  var init_operator_context = __esm({
2411
2702
  "src/operator-context.ts"() {
2412
2703
  "use strict";
2413
2704
  init_cjs_shims();
2414
2705
  import_node_fs11 = require("fs");
2415
- import_node_path9 = require("path");
2416
- import_node_process14 = __toESM(require("process"), 1);
2706
+ import_node_path10 = require("path");
2707
+ import_node_process15 = __toESM(require("process"), 1);
2417
2708
  init_argv();
2418
2709
  init_config();
2419
2710
  init_operator_profiles();
@@ -2426,6 +2717,11 @@ var init_operator_context = __esm({
2426
2717
  async function adminCommand(parsed, deps = {}) {
2427
2718
  const area = parsed.positionals[1];
2428
2719
  const action2 = parsed.positionals[2];
2720
+ if (area !== "ai" && area !== "spend") rejectWord(["admin"], area);
2721
+ if (!acceptedAfter(["admin", area]).includes(action2 ?? "")) rejectWord(["admin", area], action2);
2722
+ if (action2 === "credential" && !acceptedAfter(["admin", "ai", "credential"]).includes(parsed.positionals[3] ?? "")) {
2723
+ rejectWord(["admin", "ai", "credential"], parsed.positionals[3]);
2724
+ }
2429
2725
  if (area === "spend") {
2430
2726
  assertArgs(parsed, JSON_OPTIONS, 4);
2431
2727
  const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2451,14 +2747,11 @@ async function adminCommand(parsed, deps = {}) {
2451
2747
  out
2452
2748
  });
2453
2749
  }
2454
- const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
2750
+ const credentialSet = action2 === "credential";
2455
2751
  const credentials = action2 === "credentials";
2456
2752
  const models = action2 === "models";
2457
2753
  const usage = action2 === "usage";
2458
2754
  const audit = action2 === "audit";
2459
- if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
2460
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
2461
- }
2462
2755
  const allowed = credentialSet ? [...CONTEXT_OPTIONS, "from-env", "stdin"] : action2 === "set" ? SET_OPTIONS : models ? [...JSON_OPTIONS, "provider"] : usage ? [...JSON_OPTIONS, "app-id", "env", "run-id", "limit"] : audit ? [...JSON_OPTIONS, "limit"] : JSON_OPTIONS;
2463
2756
  assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
2464
2757
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2504,6 +2797,7 @@ var init_admin_command = __esm({
2504
2797
  init_token();
2505
2798
  init_argv();
2506
2799
  init_operator_context();
2800
+ init_surface();
2507
2801
  CONTEXT_OPTIONS = ["platform", "config", "context", "token", "open", "email"];
2508
2802
  JSON_OPTIONS = [...CONTEXT_OPTIONS, "json"];
2509
2803
  SET_OPTIONS = [
@@ -2708,9 +3002,7 @@ async function authCommand(parsed, deps = {}) {
2708
3002
  "json"
2709
3003
  ], 2);
2710
3004
  const action2 = parsed.positionals[1] ?? "login";
2711
- if (action2 !== "login") {
2712
- throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2713
- }
3005
+ if (action2 !== "login") rejectWord(["auth"], action2);
2714
3006
  const context = await resolveOperatorContext(parsed, {
2715
3007
  allowMissingConfig: true,
2716
3008
  requireApp: true
@@ -2718,7 +3010,7 @@ async function authCommand(parsed, deps = {}) {
2718
3010
  const { cfg } = context;
2719
3011
  const out = deps.stdout ?? console;
2720
3012
  const doFetch = deps.fetch ?? fetch;
2721
- const email = stringOpt(parsed.options.email) ?? import_node_process15.default.env.ODLA_USER_EMAIL?.trim();
3013
+ const email = stringOpt(parsed.options.email) ?? import_node_process16.default.env.ODLA_USER_EMAIL?.trim();
2722
3014
  if (!email) {
2723
3015
  throw new Error(
2724
3016
  "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
@@ -2757,17 +3049,18 @@ async function authCommand(parsed, deps = {}) {
2757
3049
  out.log(`Authorized ${identity.displayName}${handle} for ${cfg.app.id}.`);
2758
3050
  out.log(`odla account: ${identity.email ?? "not returned"}`);
2759
3051
  }
2760
- var import_node_process15;
3052
+ var import_node_process16;
2761
3053
  var init_auth_command = __esm({
2762
3054
  "src/auth-command.ts"() {
2763
3055
  "use strict";
2764
3056
  init_cjs_shims();
2765
- import_node_process15 = __toESM(require("process"), 1);
3057
+ import_node_process16 = __toESM(require("process"), 1);
2766
3058
  init_argv();
2767
3059
  init_operator_context();
2768
3060
  init_token();
2769
3061
  init_auth_guidance();
2770
3062
  init_whoami_command();
3063
+ init_surface();
2771
3064
  }
2772
3065
  });
2773
3066
 
@@ -2803,9 +3096,7 @@ var init_tenant = __esm({
2803
3096
  // src/agent-command.ts
2804
3097
  async function agentCommand(parsed, deps = {}) {
2805
3098
  const action2 = parsed.positionals[1];
2806
- if (action2 !== "jobs" && action2 !== "retry") {
2807
- throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
2808
- }
3099
+ if (action2 !== "jobs" && action2 !== "retry") rejectWord(["agent"], action2);
2809
3100
  assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
2810
3101
  if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
2811
3102
  throw new Error('--state and --limit are supported only by "agent jobs"');
@@ -2887,6 +3178,7 @@ var init_agent_command = __esm({
2887
3178
  init_config();
2888
3179
  init_tenant();
2889
3180
  init_local();
3181
+ init_surface();
2890
3182
  }
2891
3183
  });
2892
3184
 
@@ -3010,9 +3302,7 @@ async function appOwnersCommand(parsed, dependencies = {}) {
3010
3302
  await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
3011
3303
  return;
3012
3304
  }
3013
- throw new Error(
3014
- `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
3015
- );
3305
+ rejectWord(["app", "owners"], sub);
3016
3306
  }
3017
3307
  var init_app_owners = __esm({
3018
3308
  "src/app-owners.ts"() {
@@ -3020,6 +3310,7 @@ var init_app_owners = __esm({
3020
3310
  init_cjs_shims();
3021
3311
  init_argv();
3022
3312
  init_human_session();
3313
+ init_surface();
3023
3314
  }
3024
3315
  });
3025
3316
 
@@ -3143,8 +3434,10 @@ async function appCommand(parsed, dependencies = {}) {
3143
3434
  return;
3144
3435
  }
3145
3436
  if (sub !== "archive" && sub !== "restore") {
3146
- throw new Error(
3147
- `unknown app subcommand "${sub ?? ""}". Try "odla-ai app archive --yes", "odla-ai app restore", "odla-ai app export", "odla-ai app import <file>", "odla-ai app refresh-sandbox", "odla-ai app go-live", "odla-ai app promote", "odla-ai app rename <name>", or "odla-ai app owners <list|add|remove>". (Permanent deletion has no CLI: it requires a signed-in owner in Studio.)`
3437
+ rejectWord(
3438
+ ["app"],
3439
+ sub,
3440
+ "Permanent deletion has no CLI: it requires a signed-in owner in Studio."
3148
3441
  );
3149
3442
  }
3150
3443
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -3171,6 +3464,7 @@ var init_app_lifecycle = __esm({
3171
3464
  init_app_transfer();
3172
3465
  init_argv();
3173
3466
  init_human_session();
3467
+ init_surface();
3174
3468
  }
3175
3469
  });
3176
3470
 
@@ -3280,15 +3574,15 @@ var init_brand_design_unpack = __esm({
3280
3574
 
3281
3575
  // src/brand-command.ts
3282
3576
  async function readBundle(source, deps) {
3283
- if (source !== "-") return (0, import_promises.readFile)((0, import_node_path10.resolve)(source), "utf8");
3577
+ if (source !== "-") return (0, import_promises.readFile)((0, import_node_path11.resolve)(source), "utf8");
3284
3578
  const readStdin = deps.readStdin;
3285
3579
  if (!readStdin) throw new Error("reading a bundle from stdin is not supported here");
3286
3580
  return readStdin();
3287
3581
  }
3288
3582
  async function writeAll(result, outDir) {
3289
3583
  for (const file of result.files) {
3290
- const target = (0, import_node_path10.resolve)(outDir, file.path);
3291
- await (0, import_promises.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
3584
+ const target = (0, import_node_path11.resolve)(outDir, file.path);
3585
+ await (0, import_promises.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
3292
3586
  await (0, import_promises.writeFile)(target, file.bytes);
3293
3587
  }
3294
3588
  }
@@ -3296,7 +3590,7 @@ async function designUnpack(parsed, deps) {
3296
3590
  assertArgs(parsed, ["out", "json"], 4);
3297
3591
  const source = parsed.positionals[3];
3298
3592
  if (!source) throw new Error(USAGE);
3299
- const outDir = (0, import_node_path10.resolve)(stringOpt(parsed.options.out) ?? "design");
3593
+ const outDir = (0, import_node_path11.resolve)(stringOpt(parsed.options.out) ?? "design");
3300
3594
  const result = unpackDesign(await readBundle(source, deps));
3301
3595
  await writeAll(result, outDir);
3302
3596
  const out = deps.stdout ?? console;
@@ -3320,17 +3614,19 @@ async function brandCommand(parsed, deps) {
3320
3614
  await designUnpack(parsed, deps);
3321
3615
  return;
3322
3616
  }
3323
- throw new Error(USAGE);
3617
+ if (subject !== "design") rejectWord(["brand"], subject);
3618
+ rejectWord(["brand", "design"], action2, USAGE);
3324
3619
  }
3325
- var import_promises, import_node_path10, USAGE;
3620
+ var import_promises, import_node_path11, USAGE;
3326
3621
  var init_brand_command = __esm({
3327
3622
  "src/brand-command.ts"() {
3328
3623
  "use strict";
3329
3624
  init_cjs_shims();
3330
3625
  import_promises = require("fs/promises");
3331
- import_node_path10 = require("path");
3626
+ import_node_path11 = require("path");
3332
3627
  init_argv();
3333
3628
  init_brand_design_unpack();
3629
+ init_surface();
3334
3630
  USAGE = "usage: odla-ai brand design unpack <bundle.html|-> [--out <dir>] [--json]";
3335
3631
  }
3336
3632
  });
@@ -4376,7 +4672,7 @@ async function operationClient(cfg, options, purpose) {
4376
4672
  platform: cfg.platformUrl,
4377
4673
  scope: "app:config:write",
4378
4674
  token: options.token,
4379
- tokenFile: (0, import_node_path11.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4675
+ tokenFile: (0, import_node_path12.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4380
4676
  rootDir: cfg.rootDir,
4381
4677
  email: options.email,
4382
4678
  open: options.open,
@@ -4428,13 +4724,13 @@ function normalizeRequestError(error) {
4428
4724
  function record4(value2) {
4429
4725
  return !!value2 && typeof value2 === "object" && !Array.isArray(value2);
4430
4726
  }
4431
- var import_apps6, import_node_path11, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4727
+ var import_apps6, import_node_path12, IDEMPOTENCY_KEY, DEFAULT_WAIT_SECONDS, DEFAULT_INTERVAL_SECONDS;
4432
4728
  var init_config_operation_command = __esm({
4433
4729
  "src/config-operation-command.ts"() {
4434
4730
  "use strict";
4435
4731
  init_cjs_shims();
4436
4732
  import_apps6 = require("@odla-ai/apps");
4437
- import_node_path11 = require("path");
4733
+ import_node_path12 = require("path");
4438
4734
  init_admin_ai_auth();
4439
4735
  init_version();
4440
4736
  init_config();
@@ -4757,7 +5053,7 @@ async function inspectConfig(options) {
4757
5053
  platform: cfg.platformUrl,
4758
5054
  scope: "app:config:read",
4759
5055
  token: options.token,
4760
- tokenFile: (0, import_node_path12.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
5056
+ tokenFile: (0, import_node_path13.join)(cfg.rootDir, ".odla", "admin-token.local.json"),
4761
5057
  rootDir: cfg.rootDir,
4762
5058
  email: options.email,
4763
5059
  open: options.open,
@@ -4886,13 +5182,13 @@ function studioSettingsUrl(reconciliation) {
4886
5182
  function quoteArg2(value2) {
4887
5183
  return `'${value2.replace(/'/g, `'\\''`)}'`;
4888
5184
  }
4889
- var import_apps8, import_node_path12;
5185
+ var import_apps8, import_node_path13;
4890
5186
  var init_config_reconcile_command = __esm({
4891
5187
  "src/config-reconcile-command.ts"() {
4892
5188
  "use strict";
4893
5189
  init_cjs_shims();
4894
5190
  import_apps8 = require("@odla-ai/apps");
4895
- import_node_path12 = require("path");
5191
+ import_node_path13 = require("path");
4896
5192
  init_admin_ai_auth();
4897
5193
  init_config();
4898
5194
  init_config_reconcile_digest();
@@ -4905,7 +5201,7 @@ var init_config_reconcile_command = __esm({
4905
5201
  // src/wrangler.ts
4906
5202
  function findWranglerConfig(rootDir) {
4907
5203
  for (const name of WRANGLER_CONFIG_FILES) {
4908
- const path = (0, import_node_path13.join)(rootDir, name);
5204
+ const path = (0, import_node_path14.join)(rootDir, name);
4909
5205
  if ((0, import_node_fs14.existsSync)(path)) return path;
4910
5206
  }
4911
5207
  return null;
@@ -5017,16 +5313,16 @@ function wranglerBulkSecrets(run, opts) {
5017
5313
  ];
5018
5314
  return run("npx", args, { input: JSON.stringify(opts.secrets), cwd: opts.cwd });
5019
5315
  }
5020
- var import_node_child_process2, import_node_fs14, import_node_path13, defaultRunner, WRANGLER_CONFIG_FILES;
5316
+ var import_node_child_process3, import_node_fs14, import_node_path14, defaultRunner, WRANGLER_CONFIG_FILES;
5021
5317
  var init_wrangler = __esm({
5022
5318
  "src/wrangler.ts"() {
5023
5319
  "use strict";
5024
5320
  init_cjs_shims();
5025
- import_node_child_process2 = require("child_process");
5321
+ import_node_child_process3 = require("child_process");
5026
5322
  import_node_fs14 = require("fs");
5027
- import_node_path13 = require("path");
5323
+ import_node_path14 = require("path");
5028
5324
  defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
5029
- const child = (0, import_node_child_process2.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
5325
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
5030
5326
  let stdout = "";
5031
5327
  let stderr2 = "";
5032
5328
  child.stdout.on("data", (chunk) => stdout += chunk.toString());
@@ -5083,10 +5379,10 @@ function wranglerWarnings(rootDir) {
5083
5379
  for (const { label, block: block2 } of blocks) {
5084
5380
  const assets = block2.assets;
5085
5381
  if (assets?.directory) {
5086
- const dir = (0, import_node_path14.resolve)(rootDir, assets.directory);
5087
- if (dir === (0, import_node_path14.resolve)(rootDir)) {
5382
+ const dir = (0, import_node_path15.resolve)(rootDir, assets.directory);
5383
+ if (dir === (0, import_node_path15.resolve)(rootDir)) {
5088
5384
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
5089
- } else if ((0, import_node_fs15.existsSync)((0, import_node_path14.join)(dir, "node_modules"))) {
5385
+ } else if ((0, import_node_fs15.existsSync)((0, import_node_path15.join)(dir, "node_modules"))) {
5090
5386
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
5091
5387
  }
5092
5388
  }
@@ -5121,7 +5417,7 @@ function o11yProjectWarnings(rootDir) {
5121
5417
  warnings.push("cannot verify o11y Worker instrumentation \u2014 add a parseable wrangler.jsonc/json config");
5122
5418
  return warnings;
5123
5419
  }
5124
- const main = typeof config.main === "string" ? (0, import_node_path14.resolve)(rootDir, config.main) : null;
5420
+ const main = typeof config.main === "string" ? (0, import_node_path15.resolve)(rootDir, config.main) : null;
5125
5421
  if (!main || !(0, import_node_fs15.existsSync)(main)) {
5126
5422
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
5127
5423
  } else {
@@ -5151,23 +5447,23 @@ function calendarProjectWarnings(rootDir) {
5151
5447
  }
5152
5448
  function readPackageJson(rootDir) {
5153
5449
  try {
5154
- return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(rootDir, "package.json"), "utf8"));
5450
+ return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path15.join)(rootDir, "package.json"), "utf8"));
5155
5451
  } catch {
5156
5452
  return null;
5157
5453
  }
5158
5454
  }
5159
- var import_node_child_process3, import_node_fs15, import_node_path14, defaultExec;
5455
+ var import_node_child_process4, import_node_fs15, import_node_path15, defaultExec;
5160
5456
  var init_doctor_checks = __esm({
5161
5457
  "src/doctor-checks.ts"() {
5162
5458
  "use strict";
5163
5459
  init_cjs_shims();
5164
- import_node_child_process3 = require("child_process");
5460
+ import_node_child_process4 = require("child_process");
5165
5461
  import_node_fs15 = require("fs");
5166
- import_node_path14 = require("path");
5462
+ import_node_path15 = require("path");
5167
5463
  init_redact();
5168
5464
  init_local();
5169
5465
  init_wrangler();
5170
- defaultExec = (cmd, args, cwd) => (0, import_node_child_process3.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5466
+ defaultExec = (cmd, args, cwd) => (0, import_node_child_process4.execFileSync)(cmd, args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
5171
5467
  }
5172
5468
  });
5173
5469
 
@@ -5500,8 +5796,8 @@ var init_harness_options = __esm({
5500
5796
  // src/init.ts
5501
5797
  function initProject(options) {
5502
5798
  const out = options.stdout ?? console;
5503
- const rootDir = (0, import_node_path15.resolve)(options.rootDir ?? process.cwd());
5504
- const configPath = (0, import_node_path15.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5799
+ const rootDir = (0, import_node_path16.resolve)(options.rootDir ?? process.cwd());
5800
+ const configPath = (0, import_node_path16.resolve)(rootDir, options.configPath ?? "odla.config.mjs");
5505
5801
  if ((0, import_node_fs16.existsSync)(configPath) && !options.force) {
5506
5802
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
5507
5803
  }
@@ -5518,12 +5814,12 @@ function initProject(options) {
5518
5814
  }
5519
5815
  }
5520
5816
  const aiProvider = options.aiProvider;
5521
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(configPath), { recursive: true });
5522
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.resolve)(rootDir, "src/odla"), { recursive: true });
5523
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.resolve)(rootDir, ".odla"), { recursive: true });
5817
+ (0, import_node_fs16.mkdirSync)((0, import_node_path16.dirname)(configPath), { recursive: true });
5818
+ (0, import_node_fs16.mkdirSync)((0, import_node_path16.resolve)(rootDir, "src/odla"), { recursive: true });
5819
+ (0, import_node_fs16.mkdirSync)((0, import_node_path16.resolve)(rootDir, ".odla"), { recursive: true });
5524
5820
  (0, import_node_fs16.writeFileSync)(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
5525
- writeIfMissing((0, import_node_path15.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5526
- writeIfMissing((0, import_node_path15.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5821
+ writeIfMissing((0, import_node_path16.resolve)(rootDir, "src/odla/schema.mjs"), schemaTemplate());
5822
+ writeIfMissing((0, import_node_path16.resolve)(rootDir, "src/odla/rules.mjs"), rulesTemplate());
5527
5823
  ensureGitignore(rootDir);
5528
5824
  out.log(`created ${relativeDisplay(configPath, rootDir)}`);
5529
5825
  out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
@@ -5636,13 +5932,13 @@ function defaultKeyEnv(provider) {
5636
5932
  function relativeDisplay(path, rootDir) {
5637
5933
  return path.startsWith(rootDir) ? path.slice(rootDir.length + 1) : path;
5638
5934
  }
5639
- var import_node_fs16, import_node_path15, import_apps9;
5935
+ var import_node_fs16, import_node_path16, import_apps9;
5640
5936
  var init_init = __esm({
5641
5937
  "src/init.ts"() {
5642
5938
  "use strict";
5643
5939
  init_cjs_shims();
5644
5940
  import_node_fs16 = require("fs");
5645
- import_node_path15 = require("path");
5941
+ import_node_path16 = require("path");
5646
5942
  import_apps9 = require("@odla-ai/apps");
5647
5943
  init_local();
5648
5944
  }
@@ -5975,8 +6271,8 @@ function installSkill(options = {}) {
5975
6271
  const files = listFiles(sourceDir);
5976
6272
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
5977
6273
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
5978
- const root = (0, import_node_path16.resolve)(options.dir ?? process.cwd());
5979
- const home = (0, import_node_path16.resolve)(options.homeDir ?? (0, import_node_os4.homedir)());
6274
+ const root = (0, import_node_path17.resolve)(options.dir ?? process.cwd());
6275
+ const home = (0, import_node_path17.resolve)(options.homeDir ?? (0, import_node_os4.homedir)());
5980
6276
  const plans = /* @__PURE__ */ new Map();
5981
6277
  const targets = /* @__PURE__ */ new Map();
5982
6278
  const rememberTarget = (harness, target) => {
@@ -5990,48 +6286,48 @@ function installSkill(options = {}) {
5990
6286
  plans.set(target, { target, content: content2, boundary, managedMerge });
5991
6287
  };
5992
6288
  const planSkillTree = (targetDir2, boundary = root) => {
5993
- for (const rel of files) plan((0, import_node_path16.join)(targetDir2, rel), (0, import_node_fs17.readFileSync)((0, import_node_path16.join)(sourceDir, rel), "utf8"), false, boundary);
6289
+ for (const rel of files) plan((0, import_node_path17.join)(targetDir2, rel), (0, import_node_fs17.readFileSync)((0, import_node_path17.join)(sourceDir, rel), "utf8"), false, boundary);
5994
6290
  };
5995
6291
  let targetDir;
5996
6292
  if (options.global) {
5997
- const claudeRoot = (0, import_node_path16.join)(home, ".claude", "skills");
5998
- const codexRoot = (0, import_node_path16.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path16.join)(home, ".codex"), "skills");
6293
+ const claudeRoot = (0, import_node_path17.join)(home, ".claude", "skills");
6294
+ const codexRoot = (0, import_node_path17.resolve)(options.codexHomeDir ?? process.env.CODEX_HOME ?? (0, import_node_path17.join)(home, ".codex"), "skills");
5999
6295
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
6000
6296
  for (const harness of harnesses) {
6001
6297
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
6002
- planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path16.dirname)((0, import_node_path16.dirname)(codexRoot)));
6298
+ planSkillTree(skillRoot, harness === "claude" ? home : (0, import_node_path17.dirname)((0, import_node_path17.dirname)(codexRoot)));
6003
6299
  rememberTarget(harness, skillRoot);
6004
6300
  }
6005
6301
  } else {
6006
- const sharedRoot = (0, import_node_path16.join)(root, ".agents", "skills");
6302
+ const sharedRoot = (0, import_node_path17.join)(root, ".agents", "skills");
6007
6303
  planSkillTree(sharedRoot);
6008
- const claudeRoot = (0, import_node_path16.join)(root, ".claude", "skills");
6304
+ const claudeRoot = (0, import_node_path17.join)(root, ".claude", "skills");
6009
6305
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
6010
6306
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
6011
6307
  if (harnesses.includes("claude")) {
6012
6308
  for (const skill of skillNames(files)) {
6013
- const canonical2 = (0, import_node_fs17.readFileSync)((0, import_node_path16.join)(sourceDir, skill, "SKILL.md"), "utf8");
6014
- plan((0, import_node_path16.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
6309
+ const canonical2 = (0, import_node_fs17.readFileSync)((0, import_node_path17.join)(sourceDir, skill, "SKILL.md"), "utf8");
6310
+ plan((0, import_node_path17.join)(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
6015
6311
  }
6016
6312
  rememberTarget("claude", claudeRoot);
6017
6313
  }
6018
6314
  if (harnesses.includes("cursor")) {
6019
- const cursorRule = (0, import_node_path16.join)(root, ".cursor", "rules", "odla.mdc");
6315
+ const cursorRule = (0, import_node_path17.join)(root, ".cursor", "rules", "odla.mdc");
6020
6316
  plan(cursorRule, CURSOR_RULE);
6021
6317
  rememberTarget("cursor", cursorRule);
6022
6318
  }
6023
6319
  if (harnesses.includes("agents")) {
6024
- const agentsFile = (0, import_node_path16.join)(root, "AGENTS.md");
6320
+ const agentsFile = (0, import_node_path17.join)(root, "AGENTS.md");
6025
6321
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
6026
6322
  rememberTarget("agents", agentsFile);
6027
6323
  }
6028
6324
  if (harnesses.includes("copilot")) {
6029
- const copilotFile = (0, import_node_path16.join)(root, ".github", "copilot-instructions.md");
6325
+ const copilotFile = (0, import_node_path17.join)(root, ".github", "copilot-instructions.md");
6030
6326
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
6031
6327
  rememberTarget("copilot", copilotFile);
6032
6328
  }
6033
6329
  if (harnesses.includes("gemini")) {
6034
- const geminiFile = (0, import_node_path16.join)(root, "GEMINI.md");
6330
+ const geminiFile = (0, import_node_path17.join)(root, "GEMINI.md");
6035
6331
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
6036
6332
  rememberTarget("gemini", geminiFile);
6037
6333
  }
@@ -6067,7 +6363,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
6067
6363
  }
6068
6364
  for (const file of plans.values()) {
6069
6365
  if (!(0, import_node_fs17.existsSync)(file.target) || (0, import_node_fs17.readFileSync)(file.target, "utf8") !== file.content) {
6070
- (0, import_node_fs17.mkdirSync)((0, import_node_path16.dirname)(file.target), { recursive: true });
6366
+ (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(file.target), { recursive: true });
6071
6367
  (0, import_node_fs17.writeFileSync)(file.target, file.content);
6072
6368
  }
6073
6369
  }
@@ -6087,7 +6383,7 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
6087
6383
  };
6088
6384
  }
6089
6385
  function pathsUnder(root, paths2) {
6090
- return [...paths2].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
6386
+ return [...paths2].map((path) => (0, import_node_path17.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path17.sep}`) && !(0, import_node_path17.isAbsolute)(path)).sort();
6091
6387
  }
6092
6388
  function normalizeHarnesses(values, global) {
6093
6389
  const requested = values?.length ? values : ["claude"];
@@ -6132,13 +6428,13 @@ function managedFileContent(path, block2, force, boundary) {
6132
6428
  return `${current.slice(0, startAt)}${block2}${current.slice(afterEnd)}`;
6133
6429
  }
6134
6430
  function symlinkedComponent(boundary, target) {
6135
- const rel = (0, import_node_path16.relative)(boundary, target);
6136
- if (rel === ".." || rel.startsWith(`..${import_node_path16.sep}`) || (0, import_node_path16.isAbsolute)(rel)) {
6431
+ const rel = (0, import_node_path17.relative)(boundary, target);
6432
+ if (rel === ".." || rel.startsWith(`..${import_node_path17.sep}`) || (0, import_node_path17.isAbsolute)(rel)) {
6137
6433
  throw new Error(`agent setup target escapes its install root: ${target}`);
6138
6434
  }
6139
6435
  let current = boundary;
6140
- for (const part of rel.split(import_node_path16.sep).filter(Boolean)) {
6141
- current = (0, import_node_path16.join)(current, part);
6436
+ for (const part of rel.split(import_node_path17.sep).filter(Boolean)) {
6437
+ current = (0, import_node_path17.join)(current, part);
6142
6438
  try {
6143
6439
  if ((0, import_node_fs17.lstatSync)(current).isSymbolicLink()) return current;
6144
6440
  } catch (error) {
@@ -6155,22 +6451,22 @@ function listFiles(dir) {
6155
6451
  const results = [];
6156
6452
  const walk = (current) => {
6157
6453
  for (const entry of (0, import_node_fs17.readdirSync)(current, { withFileTypes: true })) {
6158
- const path = (0, import_node_path16.join)(current, entry.name);
6454
+ const path = (0, import_node_path17.join)(current, entry.name);
6159
6455
  if (entry.isDirectory()) walk(path);
6160
- else results.push((0, import_node_path16.relative)(dir, path));
6456
+ else results.push((0, import_node_path17.relative)(dir, path));
6161
6457
  }
6162
6458
  };
6163
6459
  walk(dir);
6164
6460
  return results.sort();
6165
6461
  }
6166
- var import_node_fs17, import_node_os4, import_node_path16, import_node_url2, AGENT_HARNESSES;
6462
+ var import_node_fs17, import_node_os4, import_node_path17, import_node_url2, AGENT_HARNESSES;
6167
6463
  var init_skill = __esm({
6168
6464
  "src/skill.ts"() {
6169
6465
  "use strict";
6170
6466
  init_cjs_shims();
6171
6467
  import_node_fs17 = require("fs");
6172
6468
  import_node_os4 = require("os");
6173
- import_node_path16 = require("path");
6469
+ import_node_path17 = require("path");
6174
6470
  import_node_url2 = require("url");
6175
6471
  init_skill_adapters();
6176
6472
  AGENT_HARNESSES = ["claude", "codex", "cursor", "copilot", "gemini", "agents"];
@@ -6392,9 +6688,7 @@ async function secretsCommand(parsed, deps) {
6392
6688
  return;
6393
6689
  }
6394
6690
  if (sub !== "push") {
6395
- throw new Error(
6396
- `unknown secrets subcommand "${sub ?? ""}". Try "odla-ai secrets push --env dev", "odla-ai secrets status --env dev", "odla-ai secrets set <name> --env dev --stdin", or "odla-ai secrets set-clerk-key --env dev --stdin".`
6397
- );
6691
+ rejectWord(["secrets"], sub);
6398
6692
  }
6399
6693
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
6400
6694
  await secretsPush({
@@ -6407,7 +6701,7 @@ async function secretsCommand(parsed, deps) {
6407
6701
  async function projectCommand(command, parsed, deps) {
6408
6702
  if (command === "ai") {
6409
6703
  const sub = parsed.positionals[1];
6410
- if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
6704
+ if (sub !== "models") rejectWord(["ai"], sub);
6411
6705
  assertArgs(parsed, ["config", "env", "provider", "json"], 2);
6412
6706
  await aiModels({
6413
6707
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -6421,9 +6715,7 @@ async function projectCommand(command, parsed, deps) {
6421
6715
  }
6422
6716
  if (command === "config") {
6423
6717
  const sub = parsed.positionals[1];
6424
- if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
6425
- throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
6426
- }
6718
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") rejectWord(["config"], sub);
6427
6719
  assertArgs(
6428
6720
  parsed,
6429
6721
  sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
@@ -6451,7 +6743,7 @@ async function projectCommand(command, parsed, deps) {
6451
6743
  if (command === "operations") {
6452
6744
  const sub = parsed.positionals[1];
6453
6745
  if (sub !== "get" && sub !== "wait") {
6454
- throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
6746
+ rejectWord(["operations"], sub);
6455
6747
  }
6456
6748
  assertArgs(
6457
6749
  parsed,
@@ -6525,7 +6817,7 @@ async function projectCommand(command, parsed, deps) {
6525
6817
  }
6526
6818
  if (command === "skill") {
6527
6819
  const sub = parsed.positionals[1];
6528
- if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
6820
+ if (sub !== "install") rejectWord(["skill"], sub);
6529
6821
  install(parsed, 2, deps);
6530
6822
  return true;
6531
6823
  }
@@ -6553,6 +6845,7 @@ var init_cli_project = __esm({
6553
6845
  init_secrets_status();
6554
6846
  init_skill();
6555
6847
  init_smoke();
6848
+ init_surface();
6556
6849
  SKILL_OPTS = ["dir", "global", "force", "agent", "harness"];
6557
6850
  }
6558
6851
  });
@@ -7743,7 +8036,7 @@ var init_dist2 = __esm({
7743
8036
  });
7744
8037
 
7745
8038
  // ../graph/dist/code/index.js
7746
- function dirname10(path) {
8039
+ function dirname11(path) {
7747
8040
  const at = path.lastIndexOf("/");
7748
8041
  return at <= 0 ? "." : path.slice(0, at);
7749
8042
  }
@@ -7759,7 +8052,7 @@ function join14(base, specifier) {
7759
8052
  }
7760
8053
  function resolveImport(fromPath, specifier, known) {
7761
8054
  if (!specifier.startsWith(".")) return null;
7762
- const base = join14(dirname10(fromPath), specifier);
8055
+ const base = join14(dirname11(fromPath), specifier);
7763
8056
  const candidates = [
7764
8057
  base,
7765
8058
  base.replace(/\.js$/, ".ts"),
@@ -10870,19 +11163,19 @@ async function inferGitHubRepository(cwd = process.cwd(), readOrigin = defaultRe
10870
11163
  return repositoryFromGitRemote(remote);
10871
11164
  }
10872
11165
  async function defaultReadOrigin(cwd) {
10873
- const result = await (0, import_node_util2.promisify)(import_node_child_process4.execFile)(
11166
+ const result = await (0, import_node_util2.promisify)(import_node_child_process5.execFile)(
10874
11167
  "git",
10875
11168
  ["remote", "get-url", "origin"],
10876
11169
  { cwd, encoding: "utf8" }
10877
11170
  );
10878
11171
  return result.stdout;
10879
11172
  }
10880
- var import_node_child_process4, import_node_util2;
11173
+ var import_node_child_process5, import_node_util2;
10881
11174
  var init_security_hosted_github = __esm({
10882
11175
  "src/security-hosted-github.ts"() {
10883
11176
  "use strict";
10884
11177
  init_cjs_shims();
10885
- import_node_child_process4 = require("child_process");
11178
+ import_node_child_process5 = require("child_process");
10886
11179
  import_node_util2 = require("util");
10887
11180
  init_security_hosted_request();
10888
11181
  }
@@ -10929,7 +11222,7 @@ async function prepareCodeLocalSource(cwd, repository, readHead = readGitHead) {
10929
11222
  }
10930
11223
  async function readGitHead(cwd) {
10931
11224
  const value2 = await new Promise((accept, reject) => {
10932
- (0, import_node_child_process5.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
11225
+ (0, import_node_child_process6.execFile)("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8", maxBuffer: 16384 }, (error, stdout) => {
10933
11226
  if (error) reject(new Error("code connect requires a Git checkout with an initial commit"));
10934
11227
  else accept(stdout.trim());
10935
11228
  });
@@ -10940,12 +11233,12 @@ async function readGitHead(cwd) {
10940
11233
  function digestText(value2) {
10941
11234
  return `sha256:${(0, import_node_crypto3.createHash)("sha256").update(value2).digest("hex")}`;
10942
11235
  }
10943
- var import_node_child_process5, import_node_crypto3, SOURCE_LIMITS2;
11236
+ var import_node_child_process6, import_node_crypto3, SOURCE_LIMITS2;
10944
11237
  var init_code_local_source = __esm({
10945
11238
  "src/code-local-source.ts"() {
10946
11239
  "use strict";
10947
11240
  init_cjs_shims();
10948
- import_node_child_process5 = require("child_process");
11241
+ import_node_child_process6 = require("child_process");
10949
11242
  import_node_crypto3 = require("crypto");
10950
11243
  init_node();
10951
11244
  SOURCE_LIMITS2 = { maxFiles: 2e4, maxBytes: 512 * 1024 * 1024 };
@@ -10986,7 +11279,7 @@ var init_code_runtime_config = __esm({
10986
11279
  // src/code-connect.ts
10987
11280
  async function codeConnect(options) {
10988
11281
  const cwd = options.cwd ?? process.cwd();
10989
- const configPath = (0, import_node_path17.resolve)(cwd, options.configPath);
11282
+ const configPath = (0, import_node_path18.resolve)(cwd, options.configPath);
10990
11283
  const cfg = (0, import_node_fs18.existsSync)(configPath) ? await loadProjectConfig(configPath) : null;
10991
11284
  const requestedAppId = options.appId?.trim();
10992
11285
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
@@ -11152,14 +11445,14 @@ function apiFailure(action2, status, value2) {
11152
11445
  function record6(value2) {
11153
11446
  return value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
11154
11447
  }
11155
- var import_node_fs18, import_node_os5, import_node_path17;
11448
+ var import_node_fs18, import_node_os5, import_node_path18;
11156
11449
  var init_code_connect = __esm({
11157
11450
  "src/code-connect.ts"() {
11158
11451
  "use strict";
11159
11452
  init_cjs_shims();
11160
11453
  import_node_fs18 = require("fs");
11161
11454
  import_node_os5 = require("os");
11162
- import_node_path17 = require("path");
11455
+ import_node_path18 = require("path");
11163
11456
  init_node();
11164
11457
  init_admin_ai_auth();
11165
11458
  init_config();
@@ -11198,9 +11491,7 @@ function grantsUrl(cfg, suffix = "") {
11198
11491
  async function codeGrantCommand(parsed, deps = {}) {
11199
11492
  const action2 = parsed.positionals[2];
11200
11493
  if (!isAction(action2)) {
11201
- throw new Error(
11202
- `unknown code grant action "${action2 ?? ""}". Try "odla-ai code grant list --env dev".`
11203
- );
11494
+ rejectWord(["code", "grant"], action2);
11204
11495
  }
11205
11496
  const decides = action2 === "approve" || action2 === "revoke";
11206
11497
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open"], decides ? 4 : 3);
@@ -11267,6 +11558,7 @@ var init_code_grant_command = __esm({
11267
11558
  init_config();
11268
11559
  init_redact();
11269
11560
  init_token();
11561
+ init_surface();
11270
11562
  ACTIONS = ["request", "list", "approve", "revoke"];
11271
11563
  isAction = (value2) => ACTIONS.includes(value2 ?? "");
11272
11564
  }
@@ -11331,9 +11623,7 @@ function repositoryUrl(cfg, suffix = "") {
11331
11623
  async function codeRepositoryCommand(parsed, deps = {}) {
11332
11624
  const action2 = parsed.positionals[2];
11333
11625
  if (!isAction2(action2)) {
11334
- throw new Error(
11335
- `unknown code repository action "${action2 ?? ""}". Try "odla-ai code repository show --env dev".`
11336
- );
11626
+ rejectWord(["code", "repository"], action2);
11337
11627
  }
11338
11628
  assertArgs(parsed, ["config", "env", "repo", "json", "token", "email", "open"], 3);
11339
11629
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -11398,6 +11688,7 @@ var init_code_repository_command = __esm({
11398
11688
  init_config();
11399
11689
  init_redact();
11400
11690
  init_token();
11691
+ init_surface();
11401
11692
  ACTIONS2 = ["show", "list", "bind"];
11402
11693
  isAction2 = (value2) => ACTIONS2.includes(value2 ?? "");
11403
11694
  }
@@ -11421,9 +11712,7 @@ async function codeCommand(parsed, dependencies) {
11421
11712
  });
11422
11713
  }
11423
11714
  if (sub !== "connect") {
11424
- throw new Error(
11425
- `unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev" or "odla-ai code grant list --env dev".`
11426
- );
11715
+ rejectWord(["code"], sub);
11427
11716
  }
11428
11717
  assertArgs(parsed, [
11429
11718
  "config",
@@ -11468,6 +11757,7 @@ var init_code_command = __esm({
11468
11757
  init_code_connect();
11469
11758
  init_code_grant_command();
11470
11759
  init_code_repository_command();
11760
+ init_surface();
11471
11761
  }
11472
11762
  });
11473
11763
 
@@ -11477,7 +11767,7 @@ function developerTokenStatus(context, parsed, now = Date.now()) {
11477
11767
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
11478
11768
  const source = clean3(
11479
11769
  stringOpt(parsed.options.token)
11480
- ) ? "flag" : clean3(import_node_process16.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
11770
+ ) ? "flag" : clean3(import_node_process17.default.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
11481
11771
  return {
11482
11772
  source,
11483
11773
  cacheFile: context.cfg.local.tokenFile,
@@ -11488,12 +11778,12 @@ function clean3(value2) {
11488
11778
  const normalized = value2?.trim();
11489
11779
  return normalized || void 0;
11490
11780
  }
11491
- var import_node_process16;
11781
+ var import_node_process17;
11492
11782
  var init_operator_credentials = __esm({
11493
11783
  "src/operator-credentials.ts"() {
11494
11784
  "use strict";
11495
11785
  init_cjs_shims();
11496
- import_node_process16 = __toESM(require("process"), 1);
11786
+ import_node_process17 = __toESM(require("process"), 1);
11497
11787
  init_argv();
11498
11788
  init_local();
11499
11789
  }
@@ -11579,9 +11869,7 @@ async function contextCommand(parsed, deps = {}) {
11579
11869
  return;
11580
11870
  }
11581
11871
  if (action2 !== "show") {
11582
- throw new Error(
11583
- `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
11584
- );
11872
+ rejectWord(["context"], action2);
11585
11873
  }
11586
11874
  const context = await resolveOperatorContext(parsed, {
11587
11875
  allowMissingConfig: true,
@@ -11638,6 +11926,7 @@ var init_context_command = __esm({
11638
11926
  init_operator_credentials();
11639
11927
  init_operator_context();
11640
11928
  init_operator_profiles();
11929
+ init_surface();
11641
11930
  }
11642
11931
  });
11643
11932
 
@@ -11647,9 +11936,7 @@ async function responseError(response2) {
11647
11936
  }
11648
11937
  async function credentialCommand(parsed, deps = {}) {
11649
11938
  const action2 = parsed.positionals[1] ?? "list";
11650
- if (action2 !== "list" && action2 !== "revoke") {
11651
- throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
11652
- }
11939
+ if (action2 !== "list" && action2 !== "revoke") rejectWord(["credentials"], action2);
11653
11940
  assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
11654
11941
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
11655
11942
  const doFetch = deps.fetch ?? fetch;
@@ -11697,6 +11984,7 @@ var init_credential_command = __esm({
11697
11984
  init_config();
11698
11985
  init_redact();
11699
11986
  init_token();
11987
+ init_surface();
11700
11988
  }
11701
11989
  });
11702
11990
 
@@ -12075,9 +12363,17 @@ Safety:
12075
12363
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
12076
12364
  the local cache, prints and opens a fresh exact-project owner-review URL, then
12077
12365
  continues provisioning with the approved replacement credential.
12078
- Before a non-dry-run provision, the executable checks npm's current CLI
12079
- release. A confirmed stale client stops with a safe npx rerun command; a
12080
- workspace-linked client also identifies the worktree that must be updated.
12366
+ Every command says so on STDERR when this CLI is older than the one npm
12367
+ serves, naming the repair for how this executable was launched \u2014 rebuild the
12368
+ worktree, npm i the dependency, or npx the scoped package. The answer is read
12369
+ from a cache under ~/.odla refreshed in the background at most every three
12370
+ hours, so no invocation waits on the registry and stdout is never touched.
12371
+ Set ODLA_CLI_UPDATE_CHECK=0 to switch it off.
12372
+ Before a non-dry-run provision, the executable additionally checks npm LIVE
12373
+ rather than trusting that cache: a security-sensitive grant shape must not
12374
+ ride on a three-hour-old answer. A confirmed stale client stops with a safe
12375
+ npx rerun command; a workspace-linked client also identifies the worktree
12376
+ that must be updated.
12081
12377
  Run Code from a GitHub checkout already connected to an app in Studio; an
12082
12378
  odla.config.mjs may select the app explicitly but is not required. With an
12083
12379
  enrolled code.session device, the Studio repository selection authorizes the
@@ -12697,7 +12993,7 @@ async function discussCommand(parsed, deps = {}) {
12697
12993
  assertArgs(parsed, ALLOWED, 3);
12698
12994
  const action2 = parsed.positionals[1];
12699
12995
  const id2 = parsed.positionals[2];
12700
- if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
12996
+ if (!acceptedAfter(["discuss"]).includes(action2 ?? "")) rejectWord(["discuss"], action2);
12701
12997
  const ctx = await buildContext(parsed, deps);
12702
12998
  switch (action2) {
12703
12999
  case "groups":
@@ -12721,7 +13017,7 @@ async function discussCommand(parsed, deps = {}) {
12721
13017
  return;
12722
13018
  }
12723
13019
  default:
12724
- throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
13020
+ rejectWord(["discuss"], action2);
12725
13021
  }
12726
13022
  }
12727
13023
  var ALLOWED;
@@ -12734,6 +13030,7 @@ var init_discuss_command = __esm({
12734
13030
  init_discuss_actions();
12735
13031
  init_discuss_watch();
12736
13032
  init_token();
13033
+ init_surface();
12737
13034
  ALLOWED = [
12738
13035
  "config",
12739
13036
  "token",
@@ -13578,7 +13875,7 @@ function writePmProjectContext(rootDir, value2) {
13578
13875
  });
13579
13876
  }
13580
13877
  function adoptLegacySelection(rootDir) {
13581
- const legacy = (0, import_node_path18.resolve)(rootDir, ".odla", "pm-project.local.json");
13878
+ const legacy = (0, import_node_path19.resolve)(rootDir, ".odla", "pm-project.local.json");
13582
13879
  if (!(0, import_node_fs19.existsSync)(legacy)) return;
13583
13880
  const previous = readJsonFile(legacy);
13584
13881
  (0, import_node_fs19.rmSync)(legacy, { force: true });
@@ -13593,13 +13890,13 @@ function readSelections() {
13593
13890
  function isSelection(value2) {
13594
13891
  return !!value2 && typeof value2.appId === "string" && typeof value2.projectId === "string" && typeof value2.selectedAt === "string";
13595
13892
  }
13596
- var import_node_fs19, import_node_path18, pmProjectContextFile;
13893
+ var import_node_fs19, import_node_path19, pmProjectContextFile;
13597
13894
  var init_pm_project_context = __esm({
13598
13895
  "src/pm-project-context.ts"() {
13599
13896
  "use strict";
13600
13897
  init_cjs_shims();
13601
13898
  import_node_fs19 = require("fs");
13602
- import_node_path18 = require("path");
13899
+ import_node_path19 = require("path");
13603
13900
  init_local();
13604
13901
  init_odla_home();
13605
13902
  pmProjectContextFile = () => pmContextFile();
@@ -13722,7 +14019,7 @@ async function pmCommand(parsed, deps = {}) {
13722
14019
  assertArgs(parsed, COMMON_OPTIONS, 4);
13723
14020
  return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
13724
14021
  }
13725
- throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
14022
+ rejectWord(["pm", "project"], action3);
13726
14023
  }
13727
14024
  if (word === "next") {
13728
14025
  assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
@@ -13753,10 +14050,10 @@ async function pmCommand(parsed, deps = {}) {
13753
14050
  return pmHandoff(await buildContext2(parsed, deps), parsed);
13754
14051
  }
13755
14052
  const entity = ALIASES[word];
13756
- if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm start" to claim Ready work, or "odla-ai pm bug list" (goal|task|decision|bug).`);
14053
+ if (!entity) rejectWord(["pm"], word);
13757
14054
  const requestedAction = parsed.positionals[2] ?? "list";
13758
14055
  const action2 = canonicalAction(requestedAction);
13759
- if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
14056
+ if (!action2) rejectWord(["pm", word], requestedAction);
13760
14057
  assertArgs(parsed, allowedOptions(entity, action2), 4);
13761
14058
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
13762
14059
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -13809,6 +14106,7 @@ var init_pm_command = __esm({
13809
14106
  init_pm_watch();
13810
14107
  init_pm_project_actions();
13811
14108
  init_pm_project_context();
14109
+ init_surface();
13812
14110
  ALIASES = {
13813
14111
  goal: "goal",
13814
14112
  conformance: "goal",
@@ -13918,12 +14216,7 @@ async function platformCommand(parsed, deps = {}) {
13918
14216
  if (action2 === "status") {
13919
14217
  return platformStatus(parsed, deps);
13920
14218
  }
13921
- throw new Error(
13922
- `unknown platform action "${[
13923
- action2,
13924
- parsed.positionals[2]
13925
- ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
13926
- );
14219
+ rejectWord(["platform"], action2);
13927
14220
  }
13928
14221
  async function platformStatus(parsed, deps) {
13929
14222
  assertArgs(
@@ -13989,6 +14282,7 @@ var init_platform_command = __esm({
13989
14282
  init_argv();
13990
14283
  init_operator_context();
13991
14284
  init_platform_output();
14285
+ init_surface();
13992
14286
  }
13993
14287
  });
13994
14288
 
@@ -14282,9 +14576,7 @@ async function o11yCommand(parsed, deps = {}) {
14282
14576
  );
14283
14577
  const action2 = parsed.positionals[1];
14284
14578
  if (action2 !== "status") {
14285
- throw new Error(
14286
- `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
14287
- );
14579
+ rejectWord(["o11y"], action2);
14288
14580
  }
14289
14581
  const minutes = statusMinutes(
14290
14582
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
@@ -14420,6 +14712,7 @@ var init_o11y_command = __esm({
14420
14712
  init_o11y_verdict();
14421
14713
  init_o11y_output();
14422
14714
  init_token();
14715
+ init_surface();
14423
14716
  }
14424
14717
  });
14425
14718
 
@@ -14540,9 +14833,7 @@ var init_monitoring_config = __esm({
14540
14833
  async function monitorCommand(parsed, deps = {}) {
14541
14834
  assertArgs(parsed, OPTIONS, 3);
14542
14835
  const action2 = parsed.positionals[1] ?? "status";
14543
- if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
14544
- throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
14545
- }
14836
+ if (!acceptedAfter(["monitor"]).includes(action2)) rejectWord(["monitor"], action2);
14546
14837
  const context = await resolveOperatorContext(parsed, {
14547
14838
  allowMissingConfig: action2 !== "plan" && action2 !== "apply",
14548
14839
  requireApp: true
@@ -14696,6 +14987,7 @@ var init_monitor_command = __esm({
14696
14987
  init_monitoring_config();
14697
14988
  init_operator_context();
14698
14989
  init_token();
14990
+ init_surface();
14699
14991
  OPTIONS = [
14700
14992
  "config",
14701
14993
  "context",
@@ -15182,7 +15474,7 @@ async function provision(options) {
15182
15474
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
15183
15475
  }
15184
15476
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
15185
- const key = import_node_process17.default.env[cfg.ai.keyEnv];
15477
+ const key = import_node_process18.default.env[cfg.ai.keyEnv];
15186
15478
  if (key) {
15187
15479
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
15188
15480
  await (0, import_ai6.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -15221,14 +15513,14 @@ async function provision(options) {
15221
15513
  }
15222
15514
  }
15223
15515
  }
15224
- var import_apps13, import_ai6, import_node_process17;
15516
+ var import_apps13, import_ai6, import_node_process18;
15225
15517
  var init_provision = __esm({
15226
15518
  "src/provision.ts"() {
15227
15519
  "use strict";
15228
15520
  init_cjs_shims();
15229
15521
  import_apps13 = require("@odla-ai/apps");
15230
15522
  import_ai6 = require("@odla-ai/ai");
15231
- import_node_process17 = __toESM(require("process"), 1);
15523
+ import_node_process18 = __toESM(require("process"), 1);
15232
15524
  init_config();
15233
15525
  init_calendar();
15234
15526
  init_calendar_errors();
@@ -15245,186 +15537,9 @@ var init_provision = __esm({
15245
15537
  }
15246
15538
  });
15247
15539
 
15248
- // src/surface.ts
15249
- function acceptedAfter(path) {
15250
- let node = COMMAND_SURFACE;
15251
- for (const word of path) {
15252
- node = node?.[word];
15253
- if (!node) return [];
15254
- }
15255
- return Object.keys(node).sort();
15256
- }
15257
- function validateInvocation(words2) {
15258
- let node = COMMAND_SURFACE;
15259
- const walked = [];
15260
- for (const word of words2) {
15261
- if (Object.keys(node).length === 0) return null;
15262
- const next = node[word];
15263
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
15264
- walked.push(word);
15265
- node = next;
15266
- }
15267
- return null;
15268
- }
15269
- function describeProblem(problem) {
15270
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
15271
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
15272
- }
15273
- function invocationPath(words2) {
15274
- let node = COMMAND_SURFACE;
15275
- const path = [];
15276
- for (const word of words2) {
15277
- const next = node[word];
15278
- if (!next) break;
15279
- path.push(word);
15280
- node = next;
15281
- if (Object.keys(node).length === 0) break;
15282
- }
15283
- return path;
15284
- }
15285
- var PM_ACTIONS, PM_TASK_ACTIONS, PM_ENTITIES, COMMAND_SURFACE;
15286
- var init_surface = __esm({
15287
- "src/surface.ts"() {
15288
- "use strict";
15289
- init_cjs_shims();
15290
- PM_ACTIONS = {
15291
- list: {},
15292
- add: {},
15293
- create: {},
15294
- get: {},
15295
- set: {},
15296
- update: {},
15297
- status: {},
15298
- move: {},
15299
- done: {},
15300
- comment: {},
15301
- comments: {},
15302
- ref: {},
15303
- rm: {},
15304
- delete: {}
15305
- };
15306
- PM_TASK_ACTIONS = {
15307
- ...PM_ACTIONS,
15308
- ready: {},
15309
- claim: {},
15310
- release: {}
15311
- };
15312
- PM_ENTITIES = {
15313
- ...Object.fromEntries(
15314
- ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
15315
- ),
15316
- task: PM_TASK_ACTIONS,
15317
- kanban: PM_TASK_ACTIONS
15318
- };
15319
- COMMAND_SURFACE = {
15320
- agent: { jobs: {}, retry: {} },
15321
- ai: { models: {} },
15322
- admin: {
15323
- ai: {
15324
- show: {},
15325
- set: {},
15326
- credentials: {},
15327
- models: {},
15328
- usage: {},
15329
- audit: {},
15330
- credential: { set: {} }
15331
- }
15332
- },
15333
- app: {
15334
- archive: {},
15335
- restore: {},
15336
- export: {},
15337
- import: {},
15338
- rename: {},
15339
- "refresh-sandbox": {},
15340
- "go-live": {},
15341
- promote: {},
15342
- owners: { list: {}, add: {}, remove: {} }
15343
- },
15344
- auth: { login: {} },
15345
- brand: { design: { unpack: {} } },
15346
- bug: { create: {}, list: {}, report: {} },
15347
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
15348
- capabilities: {},
15349
- code: {
15350
- connect: {},
15351
- grant: { request: {}, list: {}, approve: {}, revoke: {} },
15352
- repository: { show: {}, list: {}, bind: {} }
15353
- },
15354
- config: { diff: {}, plan: {}, apply: {} },
15355
- context: { show: {}, list: {}, save: {}, remove: {} },
15356
- credentials: { list: {}, revoke: {} },
15357
- device: { enroll: {}, list: {}, revoke: {} },
15358
- // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
15359
- discuss: {
15360
- groups: {},
15361
- list: {},
15362
- topics: {},
15363
- read: {},
15364
- post: {},
15365
- reply: {},
15366
- resolve: {},
15367
- who: {},
15368
- watch: {}
15369
- },
15370
- doctor: {},
15371
- help: {},
15372
- init: {},
15373
- monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
15374
- o11y: { status: {} },
15375
- operations: { get: {}, wait: {} },
15376
- platform: {
15377
- status: {}
15378
- },
15379
- pm: {
15380
- ...PM_ENTITIES,
15381
- project: { list: {}, add: {}, create: {}, use: {} },
15382
- handoff: {},
15383
- next: {},
15384
- start: {},
15385
- watch: {}
15386
- },
15387
- provision: {},
15388
- runbook: {
15389
- ask: {},
15390
- search: {},
15391
- impact: {},
15392
- list: {},
15393
- get: {},
15394
- cat: {},
15395
- new: {},
15396
- edit: {},
15397
- comment: {},
15398
- import: {},
15399
- visibility: {},
15400
- publish: {},
15401
- archive: {},
15402
- history: {},
15403
- revert: {},
15404
- rm: {},
15405
- lint: {}
15406
- },
15407
- secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
15408
- security: {
15409
- plan: {},
15410
- sources: {},
15411
- run: {},
15412
- status: {},
15413
- report: {},
15414
- github: { connect: {}, disconnect: {} }
15415
- },
15416
- setup: {},
15417
- skill: { install: {} },
15418
- smoke: {},
15419
- version: {},
15420
- whoami: {}
15421
- };
15422
- }
15423
- });
15424
-
15425
15540
  // src/record.ts
15426
15541
  function recordInvocation(parsed) {
15427
- const file = import_node_process18.default.env.ODLA_CLI_RECORD;
15542
+ const file = import_node_process19.default.env.ODLA_CLI_RECORD;
15428
15543
  if (!file) return;
15429
15544
  try {
15430
15545
  const entry = {
@@ -15437,13 +15552,13 @@ function recordInvocation(parsed) {
15437
15552
  } catch {
15438
15553
  }
15439
15554
  }
15440
- var import_node_fs20, import_node_process18;
15555
+ var import_node_fs20, import_node_process19;
15441
15556
  var init_record = __esm({
15442
15557
  "src/record.ts"() {
15443
15558
  "use strict";
15444
15559
  init_cjs_shims();
15445
15560
  import_node_fs20 = require("fs");
15446
- import_node_process18 = __toESM(require("process"), 1);
15561
+ import_node_process19 = __toESM(require("process"), 1);
15447
15562
  init_surface();
15448
15563
  }
15449
15564
  });
@@ -15529,6 +15644,7 @@ async function deviceCommand(parsed, deps) {
15529
15644
  "wait"
15530
15645
  ], 3);
15531
15646
  const action2 = parsed.positionals[1] ?? "";
15647
+ if (!acceptedAfter(["device"]).includes(action2)) rejectWord(["device"], action2);
15532
15648
  const out = deps.stdout ?? console;
15533
15649
  const doFetch = deps.fetch ?? fetch;
15534
15650
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
@@ -15536,7 +15652,7 @@ async function deviceCommand(parsed, deps) {
15536
15652
  if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
15537
15653
  if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
15538
15654
  if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
15539
- throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
15655
+ rejectWord(["device"], action2);
15540
15656
  }
15541
15657
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
15542
15658
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
@@ -15567,7 +15683,7 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
15567
15683
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
15568
15684
  body: JSON.stringify({
15569
15685
  name,
15570
- platform: import_node_process19.default.platform,
15686
+ platform: import_node_process20.default.platform,
15571
15687
  appIds: apps,
15572
15688
  ...capabilities ? { capabilities } : {},
15573
15689
  ...scopes ? { scopes } : {},
@@ -15579,7 +15695,7 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
15579
15695
  throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
15580
15696
  }
15581
15697
  const path = deviceCredentialPath();
15582
- (0, import_node_fs21.mkdirSync)((0, import_node_path19.dirname)(path), { recursive: true });
15698
+ (0, import_node_fs21.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
15583
15699
  (0, import_node_fs21.writeFileSync)(path, JSON.stringify({
15584
15700
  token: body.token,
15585
15701
  platform: cfg.platformUrl.replace(/\/$/, ""),
@@ -15686,9 +15802,9 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
15686
15802
  });
15687
15803
  }
15688
15804
  function defaultDeviceName() {
15689
- return `${import_node_process19.default.env.HOSTNAME ?? import_node_process19.default.env.HOST ?? "machine"}-${import_node_process19.default.platform}`;
15805
+ return `${import_node_process20.default.env.HOSTNAME ?? import_node_process20.default.env.HOST ?? "machine"}-${import_node_process20.default.platform}`;
15690
15806
  }
15691
- var import_db4, import_node_fs21, import_node_path19, import_node_process19;
15807
+ var import_db4, import_node_fs21, import_node_path20, import_node_process20;
15692
15808
  var init_device_command = __esm({
15693
15809
  "src/device-command.ts"() {
15694
15810
  "use strict";
@@ -15699,13 +15815,14 @@ var init_device_command = __esm({
15699
15815
  init_auth_guidance();
15700
15816
  init_advisory_output();
15701
15817
  import_node_fs21 = require("fs");
15702
- import_node_path19 = require("path");
15703
- import_node_process19 = __toESM(require("process"), 1);
15818
+ import_node_path20 = require("path");
15819
+ import_node_process20 = __toESM(require("process"), 1);
15704
15820
  init_argv();
15705
15821
  init_admin_ai_auth();
15706
15822
  init_device_session();
15707
15823
  init_config();
15708
15824
  init_operator_context();
15825
+ init_surface();
15709
15826
  }
15710
15827
  });
15711
15828
 
@@ -15886,8 +16003,8 @@ function readRunbookDir(dir) {
15886
16003
  const files = (0, import_node_fs23.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
15887
16004
  if (!files.length) throw new Error(`no .md files in ${dir}`);
15888
16005
  return files.map((file) => {
15889
- const slug = (0, import_node_path20.basename)(file, ".md");
15890
- const parsed = parseRunbook((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(dir, file), "utf8"), slug);
16006
+ const slug = (0, import_node_path21.basename)(file, ".md");
16007
+ const parsed = parseRunbook((0, import_node_fs23.readFileSync)((0, import_node_path21.join)(dir, file), "utf8"), slug);
15891
16008
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
15892
16009
  });
15893
16010
  }
@@ -15952,13 +16069,13 @@ async function upsert(ctx, r, visibility) {
15952
16069
  );
15953
16070
  return "updated";
15954
16071
  }
15955
- var import_node_fs23, import_node_path20;
16072
+ var import_node_fs23, import_node_path21;
15956
16073
  var init_runbook_import = __esm({
15957
16074
  "src/runbook-import.ts"() {
15958
16075
  "use strict";
15959
16076
  init_cjs_shims();
15960
16077
  import_node_fs23 = require("fs");
15961
- import_node_path20 = require("path");
16078
+ import_node_path21 = require("path");
15962
16079
  init_runbook_actions();
15963
16080
  }
15964
16081
  });
@@ -16091,7 +16208,7 @@ var init_runbook_impact_scan = __esm({
16091
16208
 
16092
16209
  // src/runbook-impact.ts
16093
16210
  function gitRunner(cwd) {
16094
- return (args) => (0, import_node_child_process6.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
16211
+ return (args) => (0, import_node_child_process7.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 64 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
16095
16212
  }
16096
16213
  function collectDiff(runGit, base, read3) {
16097
16214
  let merged = "";
@@ -16136,7 +16253,7 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
16136
16253
  }
16137
16254
  function manifestLabeller(root) {
16138
16255
  return (workspace) => {
16139
- const manifest = (0, import_node_path21.join)(root, workspace, "package.json");
16256
+ const manifest = (0, import_node_path22.join)(root, workspace, "package.json");
16140
16257
  if (!(0, import_node_fs24.existsSync)(manifest)) return void 0;
16141
16258
  try {
16142
16259
  const name = JSON.parse((0, import_node_fs24.readFileSync)(manifest, "utf8")).name;
@@ -16205,7 +16322,7 @@ function report4(ctx, impacts) {
16205
16322
  async function runbookImpact(ctx, options, deps = {}) {
16206
16323
  const cwd = deps.cwd ?? process.cwd();
16207
16324
  const runGit = deps.runGit ?? gitRunner(cwd);
16208
- const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs24.readFileSync)((0, import_node_path21.join)(cwd, path), "utf8"));
16325
+ const read3 = deps.readRepoFile ?? ((path) => (0, import_node_fs24.readFileSync)((0, import_node_path22.join)(cwd, path), "utf8"));
16209
16326
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
16210
16327
  if (!surfaces.length) {
16211
16328
  return ctx.out.log(
@@ -16216,14 +16333,14 @@ async function runbookImpact(ctx, options, deps = {}) {
16216
16333
  if (ctx.json) return ctx.out.log(JSON.stringify({ base: options.base, impacts }, null, 2));
16217
16334
  report4(ctx, impacts);
16218
16335
  }
16219
- var import_node_child_process6, import_node_fs24, import_node_path21, SOURCE3, editHint;
16336
+ var import_node_child_process7, import_node_fs24, import_node_path22, SOURCE3, editHint;
16220
16337
  var init_runbook_impact = __esm({
16221
16338
  "src/runbook-impact.ts"() {
16222
16339
  "use strict";
16223
16340
  init_cjs_shims();
16224
- import_node_child_process6 = require("child_process");
16341
+ import_node_child_process7 = require("child_process");
16225
16342
  import_node_fs24 = require("fs");
16226
- import_node_path21 = require("path");
16343
+ import_node_path22 = require("path");
16227
16344
  init_runbook_impact_scan();
16228
16345
  init_runbook_actions();
16229
16346
  SOURCE3 = /\.(ts|tsx|js|jsx|mts|cts)$/;
@@ -16363,7 +16480,7 @@ var init_runbook_search_command = __esm({
16363
16480
  });
16364
16481
 
16365
16482
  // src/runbook-editor.ts
16366
- function resolveEditor(env = import_node_process20.default.env) {
16483
+ function resolveEditor(env = import_node_process21.default.env) {
16367
16484
  for (const name of EDITOR_ENV) {
16368
16485
  const value2 = env[name];
16369
16486
  if (value2 && value2.trim()) return value2.trim();
@@ -16372,13 +16489,13 @@ function resolveEditor(env = import_node_process20.default.env) {
16372
16489
  }
16373
16490
  function defaultRun(command, path) {
16374
16491
  const [bin, ...args] = command.split(/\s+/);
16375
- const result = (0, import_node_child_process7.spawnSync)(bin, [...args, path], { stdio: "inherit" });
16492
+ const result = (0, import_node_child_process8.spawnSync)(bin, [...args, path], { stdio: "inherit" });
16376
16493
  if (result.error) throw new Error(`could not start editor "${command}": ${result.error.message}`);
16377
16494
  return result.status ?? 0;
16378
16495
  }
16379
16496
  function editText(initial, slug, deps = {}) {
16380
- const env = deps.env ?? import_node_process20.default.env;
16381
- const interactive = deps.interactive ?? (() => Boolean(import_node_process20.default.stdin.isTTY));
16497
+ const env = deps.env ?? import_node_process21.default.env;
16498
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process21.default.stdin.isTTY));
16382
16499
  const editor = resolveEditor(env);
16383
16500
  if (!editor)
16384
16501
  throw new Error(
@@ -16386,8 +16503,8 @@ function editText(initial, slug, deps = {}) {
16386
16503
  );
16387
16504
  if (!interactive())
16388
16505
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
16389
- const dir = (0, import_node_fs25.mkdtempSync)((0, import_node_path22.join)((0, import_node_os6.tmpdir)(), "odla-runbook-"));
16390
- const file = (0, import_node_path22.join)(dir, `${slug}.md`);
16506
+ const dir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os6.tmpdir)(), "odla-runbook-"));
16507
+ const file = (0, import_node_path23.join)(dir, `${slug}.md`);
16391
16508
  try {
16392
16509
  (0, import_node_fs25.writeFileSync)(file, initial, { mode: 384 });
16393
16510
  const code = defaultRunOrInjected(deps)(editor, file);
@@ -16398,16 +16515,16 @@ function editText(initial, slug, deps = {}) {
16398
16515
  (0, import_node_fs25.rmSync)(dir, { recursive: true, force: true });
16399
16516
  }
16400
16517
  }
16401
- var import_node_child_process7, import_node_fs25, import_node_os6, import_node_path22, import_node_process20, EDITOR_ENV, defaultRunOrInjected;
16518
+ var import_node_child_process8, import_node_fs25, import_node_os6, import_node_path23, import_node_process21, EDITOR_ENV, defaultRunOrInjected;
16402
16519
  var init_runbook_editor = __esm({
16403
16520
  "src/runbook-editor.ts"() {
16404
16521
  "use strict";
16405
16522
  init_cjs_shims();
16406
- import_node_child_process7 = require("child_process");
16523
+ import_node_child_process8 = require("child_process");
16407
16524
  import_node_fs25 = require("fs");
16408
16525
  import_node_os6 = require("os");
16409
- import_node_path22 = require("path");
16410
- import_node_process20 = __toESM(require("process"), 1);
16526
+ import_node_path23 = require("path");
16527
+ import_node_process21 = __toESM(require("process"), 1);
16411
16528
  EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
16412
16529
  defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
16413
16530
  }
@@ -16494,6 +16611,7 @@ async function buildContext3(parsed, deps, action2) {
16494
16611
  async function runbookCommand(parsed, deps = {}) {
16495
16612
  const action2 = parsed.positionals[1] ?? "list";
16496
16613
  assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
16614
+ if (!acceptedAfter(["runbook"]).includes(action2)) rejectWord(["runbook"], action2);
16497
16615
  const ctx = await buildContext3(parsed, deps, action2);
16498
16616
  const slug = parsed.positionals[2];
16499
16617
  switch (action2) {
@@ -16583,7 +16701,7 @@ async function runbookCommand(parsed, deps = {}) {
16583
16701
  case "rm":
16584
16702
  return runbookRemove(ctx, requireSlug(slug, "rm"));
16585
16703
  default:
16586
- throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
16704
+ rejectWord(["runbook"], action2);
16587
16705
  }
16588
16706
  }
16589
16707
  var ALLOWED2, WRITES2;
@@ -16796,9 +16914,9 @@ async function runHostedSecurity(options) {
16796
16914
  const appId = selfAudit ? "odla-ai" : cfg.app.id;
16797
16915
  const env = selfAudit ? "prod" : selectEnv(options.env, cfg.envs, cfg.configPath, cfg.rootDir);
16798
16916
  const platform = options.platform ?? cfg?.platformUrl ?? "https://odla.ai";
16799
- const target = (0, import_node_path23.resolve)(options.target ?? cfg?.rootDir ?? ".");
16800
- const output = (0, import_node_path23.resolve)(options.out ?? (0, import_node_path23.resolve)(target, ".odla/security/hosted"));
16801
- const outputRelative = (0, import_node_path23.relative)(target, output).split(import_node_path23.sep).join("/");
16917
+ const target = (0, import_node_path24.resolve)(options.target ?? cfg?.rootDir ?? ".");
16918
+ const output = (0, import_node_path24.resolve)(options.out ?? (0, import_node_path24.resolve)(target, ".odla/security/hosted"));
16919
+ const outputRelative = (0, import_node_path24.relative)(target, output).split(import_node_path24.sep).join("/");
16802
16920
  if (!outputRelative) throw new Error("Hosted security output cannot be the repository root");
16803
16921
  const profile = profileFor(options.profile ?? "odla", options.maxHuntTasks ?? 12);
16804
16922
  const tokenRequest = {
@@ -16810,7 +16928,7 @@ async function runHostedSecurity(options) {
16810
16928
  };
16811
16929
  const token = await injectedToken(options, tokenRequest);
16812
16930
  const snapshot = await (0, import_node3.snapshotDirectory)(target, {
16813
- exclude: !outputRelative.startsWith("../") && !(0, import_node_path23.isAbsolute)(outputRelative) ? [outputRelative] : []
16931
+ exclude: !outputRelative.startsWith("../") && !(0, import_node_path24.isAbsolute)(outputRelative) ? [outputRelative] : []
16814
16932
  });
16815
16933
  const hosted = await (0, import_security.createPlatformSecurityReasoners)({
16816
16934
  platform,
@@ -16828,7 +16946,7 @@ async function runHostedSecurity(options) {
16828
16946
  });
16829
16947
  const harness = (0, import_security.createSecurityHarness)({
16830
16948
  profile,
16831
- store: new import_node3.FileRunStore((0, import_node_path23.resolve)(output, "state")),
16949
+ store: new import_node3.FileRunStore((0, import_node_path24.resolve)(output, "state")),
16832
16950
  discoveryReasoner: hosted.discoveryReasoner,
16833
16951
  validationReasoner: hosted.validationReasoner,
16834
16952
  policy: {
@@ -16852,7 +16970,7 @@ async function runHostedSecurity(options) {
16852
16970
  function selectEnv(requested, declared, configPath, rootDir) {
16853
16971
  const env = requested ?? (declared.includes("dev") ? "dev" : declared[0]);
16854
16972
  if (!env || !declared.includes(env)) {
16855
- const shown = (0, import_node_path23.relative)(rootDir, configPath) || configPath;
16973
+ const shown = (0, import_node_path24.relative)(rootDir, configPath) || configPath;
16856
16974
  throw new Error(`env "${env ?? ""}" is not declared in ${shown}`);
16857
16975
  }
16858
16976
  return env;
@@ -16881,17 +16999,17 @@ function printSummary(out, appId, env, run, report5, output) {
16881
16999
  out.log(` coverage: ${report5.coverageStatus} ${complete}/${report5.coverage.length} blocked=${report5.metrics.blockedCells} shallow=${report5.metrics.shallowCells} unscheduled=${report5.metrics.unscheduledCells} budget_exhausted=${report5.metrics.budgetExhaustedCells}`);
16882
17000
  if (report5.callBudget) out.log(` calls: discovery=${formatBudget(report5.callBudget.discovery)} validation=${formatBudget(report5.callBudget.validation)}`);
16883
17001
  out.log(` findings: confirmed=${report5.metrics.confirmed} needs_reproduction=${report5.metrics.needsReproduction} candidates=${report5.metrics.candidates}`);
16884
- out.log(` report: ${(0, import_node_path23.resolve)(output, "REPORT.md")}`);
17002
+ out.log(` report: ${(0, import_node_path24.resolve)(output, "REPORT.md")}`);
16885
17003
  }
16886
17004
  function formatBudget(usage) {
16887
17005
  return usage ? `${usage.usedCalls}/${usage.maxCalls} skipped=${usage.skippedCalls}` : "caller-managed";
16888
17006
  }
16889
- var import_node_path23, import_security, import_node3;
17007
+ var import_node_path24, import_security, import_node3;
16890
17008
  var init_security = __esm({
16891
17009
  "src/security.ts"() {
16892
17010
  "use strict";
16893
17011
  init_cjs_shims();
16894
- import_node_path23 = require("path");
17012
+ import_node_path24 = require("path");
16895
17013
  import_security = require("@odla-ai/security");
16896
17014
  import_node3 = require("@odla-ai/security/node");
16897
17015
  init_config();
@@ -17292,9 +17410,7 @@ async function securityCommand(parsed, dependencies) {
17292
17410
  else printHostedReport(context.stdout, report5);
17293
17411
  return;
17294
17412
  }
17295
- if (sub !== "run") {
17296
- throw new Error('unknown security command. Try "odla-ai security plan", "security sources", or "security run".');
17297
- }
17413
+ if (sub !== "run") rejectWord(["security"], sub);
17298
17414
  const sourceId = stringOpt(parsed.options.source);
17299
17415
  if (sourceId) await runSourceSecurityCommand(parsed, dependencies, sourceId);
17300
17416
  else await runLocalSecurityCommand(parsed, dependencies);
@@ -17311,9 +17427,7 @@ async function githubSecurityCommand(parsed, dependencies) {
17311
17427
  stringOpt(parsed.options.env)
17312
17428
  );
17313
17429
  }
17314
- if (action2 !== "connect") {
17315
- throw new Error('unknown security github command. Try "odla-ai security github connect".');
17316
- }
17430
+ if (action2 !== "connect") rejectWord(["security", "github"], action2);
17317
17431
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
17318
17432
  await requireStudioHuman(
17319
17433
  stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -17369,6 +17483,7 @@ var init_security_command = __esm({
17369
17483
  init_security_run_command();
17370
17484
  init_security_hosted();
17371
17485
  init_human_session();
17486
+ init_surface();
17372
17487
  }
17373
17488
  });
17374
17489
 
@@ -17393,6 +17508,11 @@ async function runCli(argv2 = process.argv.slice(2), dependencies = {}) {
17393
17508
  throw error;
17394
17509
  } finally {
17395
17510
  renderAdvisories(out, advisories);
17511
+ try {
17512
+ const behind = updateNotice();
17513
+ if (behind) out.error(behind);
17514
+ } catch {
17515
+ }
17396
17516
  }
17397
17517
  }
17398
17518
  async function dispatchCli(argv2, dependencies) {
@@ -17473,6 +17593,7 @@ async function dispatchCli(argv2, dependencies) {
17473
17593
  }
17474
17594
  if (command === "bug") {
17475
17595
  const action2 = parsed.positionals[1] ?? "list";
17596
+ if (!acceptedAfter(["bug"]).includes(action2)) rejectWord(["bug"], action2);
17476
17597
  const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
17477
17598
  await pmCommand({
17478
17599
  ...parsed,
@@ -17501,7 +17622,7 @@ async function dispatchCli(argv2, dependencies) {
17501
17622
  return;
17502
17623
  }
17503
17624
  if (await projectCommand(command, parsed, runtime)) return;
17504
- throw new Error(`unknown command "${command}". Run "odla-ai help".`);
17625
+ rejectWord([], command);
17505
17626
  }
17506
17627
  async function provisionCommand(parsed, dependencies) {
17507
17628
  assertArgs(parsed, [
@@ -17545,7 +17666,7 @@ async function provisionCommand(parsed, dependencies) {
17545
17666
  async function calendarCommand(parsed, dependencies) {
17546
17667
  const sub = parsed.positionals[1];
17547
17668
  if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "disconnect") {
17548
- throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
17669
+ rejectWord(["calendar"], sub);
17549
17670
  }
17550
17671
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
17551
17672
  if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
@@ -17599,6 +17720,8 @@ var init_cli = __esm({
17599
17720
  init_runbook_command();
17600
17721
  init_security_command();
17601
17722
  init_whoami_command();
17723
+ init_surface();
17724
+ init_update_notice();
17602
17725
  init_exit_code();
17603
17726
  }
17604
17727
  });
@@ -17608,16 +17731,17 @@ init_cjs_shims();
17608
17731
 
17609
17732
  // src/cli-update.ts
17610
17733
  init_cjs_shims();
17611
- var import_node_fs2 = require("fs");
17612
17734
  init_runbook_requires();
17735
+ init_update_notice();
17613
17736
  init_version();
17614
17737
  var DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/@odla-ai%2fcli/latest";
17615
- var VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
17738
+ var VERSION2 = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
17616
17739
  async function requireCurrentCliForProvision(argv2, options = {}) {
17617
17740
  if (argv2[0] !== "provision" || argv2.includes("--dry-run")) return;
17618
17741
  const current = options.currentVersion ?? cliVersion();
17619
- if (!VERSION.test(current)) return;
17742
+ if (!VERSION2.test(current)) return;
17620
17743
  const latest = await fetchLatestCliVersion(options);
17744
+ if (latest) rememberLatest(latest);
17621
17745
  if (!latest || compareVersions(current, latest) >= 0) return;
17622
17746
  const entryPath = resolvedEntryPath(options.entryPath ?? process.argv[1]);
17623
17747
  const workspace = isWorkspaceCli(entryPath);
@@ -17646,25 +17770,19 @@ async function fetchLatestCliVersion(options) {
17646
17770
  );
17647
17771
  if (!response2.ok) return null;
17648
17772
  const body = await response2.json();
17649
- return typeof body.version === "string" && VERSION.test(body.version) ? body.version : null;
17773
+ return typeof body.version === "string" && VERSION2.test(body.version) ? body.version : null;
17650
17774
  } catch {
17651
17775
  return null;
17652
17776
  } finally {
17653
17777
  clearTimeout(timeout);
17654
17778
  }
17655
17779
  }
17656
- function resolvedEntryPath(entryPath) {
17657
- if (!entryPath) return "unknown executable";
17780
+ function rememberLatest(latest) {
17658
17781
  try {
17659
- return (0, import_node_fs2.realpathSync)(entryPath);
17782
+ writeUpdateCache(updateCacheFile(), { latest, checkedAt: Date.now() });
17660
17783
  } catch {
17661
- return entryPath;
17662
17784
  }
17663
17785
  }
17664
- function isWorkspaceCli(entryPath) {
17665
- const normalized = entryPath.replaceAll("\\", "/");
17666
- return normalized.includes("/packages/cli/dist/bin.") && !normalized.includes("/node_modules/");
17667
- }
17668
17786
  function renderReleasedProvisionCommand(latest, argv2) {
17669
17787
  const safeArgs = [];
17670
17788
  for (let index = 0; index < argv2.length; index++) {
@@ -17692,8 +17810,9 @@ function shellQuote(value2) {
17692
17810
  // src/cli-runtime.ts
17693
17811
  init_cjs_shims();
17694
17812
  var import_node_module = require("module");
17695
- var import_node_fs3 = require("fs");
17696
- var import_node_path = require("path");
17813
+ var import_node_fs4 = require("fs");
17814
+ var import_node_path3 = require("path");
17815
+ init_update_notice();
17697
17816
  init_version();
17698
17817
  var EXACT_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
17699
17818
  var RUNTIME_MODULES = [
@@ -17752,26 +17871,26 @@ function installedRuntimeModules(entryPath) {
17752
17871
  }));
17753
17872
  }
17754
17873
  function findPackageManifest(fromPath, expectedName) {
17755
- let directory = (0, import_node_path.dirname)(resolvedEntryPath(fromPath));
17756
- const root = (0, import_node_path.parse)(directory).root;
17874
+ let directory = (0, import_node_path3.dirname)(resolvedEntryPath(fromPath));
17875
+ const root = (0, import_node_path3.parse)(directory).root;
17757
17876
  while (true) {
17758
- const path = (0, import_node_path.join)(directory, "package.json");
17759
- if ((0, import_node_fs3.existsSync)(path)) {
17877
+ const path = (0, import_node_path3.join)(directory, "package.json");
17878
+ if ((0, import_node_fs4.existsSync)(path)) {
17760
17879
  try {
17761
- const json = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8"));
17880
+ const json = JSON.parse((0, import_node_fs4.readFileSync)(path, "utf8"));
17762
17881
  if (json?.name === expectedName && typeof json.version === "string") return { path, json };
17763
17882
  } catch {
17764
17883
  }
17765
17884
  }
17766
17885
  if (directory === root) return void 0;
17767
- directory = (0, import_node_path.dirname)(directory);
17886
+ directory = (0, import_node_path3.dirname)(directory);
17768
17887
  }
17769
17888
  }
17770
17889
  function absoluteEntryPath(entryPath) {
17771
- const candidate = entryPath || (0, import_node_path.join)(process.cwd(), "odla-ai-cli.js");
17772
- const absolute = (0, import_node_path.isAbsolute)(candidate) ? candidate : (0, import_node_path.resolve)(candidate);
17890
+ const candidate = entryPath || (0, import_node_path3.join)(process.cwd(), "odla-ai-cli.js");
17891
+ const absolute = (0, import_node_path3.isAbsolute)(candidate) ? candidate : (0, import_node_path3.resolve)(candidate);
17773
17892
  try {
17774
- return (0, import_node_fs3.realpathSync)(absolute);
17893
+ return (0, import_node_fs4.realpathSync)(absolute);
17775
17894
  } catch {
17776
17895
  return absolute;
17777
17896
  }