@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.
@@ -1,21 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ adoptRepoLocalCache,
4
+ appCredentialsFile,
5
+ appTokenFile,
3
6
  cliVersion,
4
7
  describeUnmet,
8
+ deviceSessionFile,
9
+ identityFile,
5
10
  looksSecret,
6
11
  parseRequires,
12
+ pmContextFile,
7
13
  redactSecrets,
8
14
  redactingOutput,
9
- unmetRequirements
10
- } from "./chunk-UKLSRQ5J.js";
15
+ scopedTokenFile,
16
+ unmetRequirements,
17
+ updateNotice
18
+ } from "./chunk-5MEO3BVF.js";
11
19
 
12
20
  // src/admin-ai.ts
13
- import process13 from "process";
21
+ import process12 from "process";
14
22
 
15
23
  // src/token.ts
16
24
  import { OdlaError, requestToken } from "@odla-ai/db";
17
25
  import { createHash } from "crypto";
18
- import process10 from "process";
26
+ import process9 from "process";
19
27
 
20
28
  // src/handshake-approval.ts
21
29
  import process3 from "process";
@@ -150,10 +158,10 @@ function handshakeWaitMs(waitSeconds, interactive = process4.stdout.isTTY === tr
150
158
  }
151
159
 
152
160
  // src/cached-credential.ts
153
- import { rmSync as rmSync3 } from "fs";
161
+ import { rmSync as rmSync2 } from "fs";
154
162
 
155
163
  // src/auth-guidance.ts
156
- import process7 from "process";
164
+ import process6 from "process";
157
165
 
158
166
  // src/device-session.ts
159
167
  import { existsSync, readFileSync } from "fs";
@@ -202,63 +210,9 @@ async function mintDeviceSession(platformUrl, credential2, doFetch) {
202
210
  };
203
211
  }
204
212
 
205
- // src/odla-home.ts
206
- import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, rmSync as rmSync2 } from "fs";
207
- import { homedir as homedir2 } from "os";
208
- import { dirname as dirname2, join as join3 } from "path";
209
- import process6 from "process";
210
- function odlaHome(env = process6.env) {
211
- if (env.VITEST && !env.ODLA_HOME) {
212
- throw new Error(
213
- "ODLA_HOME must be set under test \u2014 resolving the real ~/.odla would write to the developer's machine"
214
- );
215
- }
216
- return env.ODLA_HOME ?? join3(env.HOME ?? homedir2(), ".odla");
217
- }
218
- function odlaHomePath(segments, env = process6.env) {
219
- return join3(odlaHome(env), ...segments);
220
- }
221
- function identityFile(env) {
222
- return odlaHomePath(["identity.json"], env);
223
- }
224
- function deviceSessionFile(env) {
225
- return odlaHomePath(["session.json"], env);
226
- }
227
- function appTokenFile(appId, env) {
228
- return odlaHomePath(["apps", safeSegment(appId), "dev-token.json"], env);
229
- }
230
- function appCredentialsFile(appId, env) {
231
- return odlaHomePath(["apps", safeSegment(appId), "credentials.json"], env);
232
- }
233
- function scopedTokenFile(env) {
234
- return odlaHomePath(["admin-token.local.json"], env);
235
- }
236
- function pmContextFile(env) {
237
- return odlaHomePath(["pm-context.json"], env);
238
- }
239
- function adoptRepoLocalCache(legacyPath, machinePath, out) {
240
- if (!existsSync2(legacyPath) || legacyPath === machinePath) return false;
241
- const superseded2 = existsSync2(machinePath);
242
- if (!superseded2) {
243
- mkdirSync(dirname2(machinePath), { recursive: true });
244
- copyFileSync(legacyPath, machinePath);
245
- chmodSync(machinePath, 384);
246
- }
247
- rmSync2(legacyPath, { force: true });
248
- out?.error(
249
- 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`
250
- );
251
- return true;
252
- }
253
- function safeSegment(value2) {
254
- const clean4 = value2.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "");
255
- if (!clean4) throw new Error(`"${value2}" is not a usable app id`);
256
- return clean4;
257
- }
258
-
259
213
  // src/local.ts
260
- import { chmodSync as chmodSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
261
- import { dirname as dirname3, isAbsolute, relative, resolve } from "path";
214
+ import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
215
+ import { dirname as dirname2, isAbsolute, relative, resolve } from "path";
262
216
  var GITIGNORE_LINES = [".odla/*.local.json", ".odla/dev-token.json", ".dev.vars"];
263
217
  function readJsonFile(path) {
264
218
  try {
@@ -272,7 +226,7 @@ function writePrivateJson(path, value2) {
272
226
  `);
273
227
  }
274
228
  function readCredentials(path) {
275
- if (!existsSync3(path)) return null;
229
+ if (!existsSync2(path)) return null;
276
230
  let value2;
277
231
  try {
278
232
  value2 = JSON.parse(readFileSync2(path, "utf8"));
@@ -306,7 +260,7 @@ function mergeCredential(current, update) {
306
260
  }
307
261
  function ensureGitignore(rootDir, localPaths = []) {
308
262
  const path = resolve(rootDir, ".gitignore");
309
- const existing = existsSync3(path) ? readFileSync2(path, "utf8") : "";
263
+ const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
310
264
  const configured = localPaths.map((localPath) => gitignoreEntry(rootDir, localPath)).filter((line2) => !!line2);
311
265
  const wanted = [.../* @__PURE__ */ new Set([...GITIGNORE_LINES, ...configured])];
312
266
  const missing = wanted.filter((line2) => !existing.split(/\r?\n/).includes(line2));
@@ -326,7 +280,7 @@ function o11yDevVars(cfg) {
326
280
  function resolveWriteDevVarsTarget(cfg, requested) {
327
281
  if (!requested) return null;
328
282
  if (requested === true) return cfg.local.devVarsFile;
329
- return resolve(dirname3(cfg.configPath), requested);
283
+ return resolve(dirname2(cfg.configPath), requested);
330
284
  }
331
285
  function writeDevVars(path, credentials, env, o11y) {
332
286
  const entry = credentials.envs[env];
@@ -340,7 +294,7 @@ function writeDevVars(path, credentials, env, o11y) {
340
294
  if (o11y.version) lines.push(`ODLA_O11Y_VERSION="${o11y.version}"`);
341
295
  if (entry.o11yToken) lines.push(`ODLA_O11Y_TOKEN="${entry.o11yToken}"`);
342
296
  }
343
- const existing = existsSync3(path) ? readFileSync2(path, "utf8") : "";
297
+ const existing = existsSync2(path) ? readFileSync2(path, "utf8") : "";
344
298
  const retained = existing.split(/\r?\n/).filter((line2) => !isManagedDevVar(line2));
345
299
  while (retained.at(-1) === "") retained.pop();
346
300
  const prefix = retained.length ? `${retained.join("\n")}
@@ -366,10 +320,10 @@ function isManagedDevVar(line2) {
366
320
  return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
367
321
  }
368
322
  function writePrivateText(path, text4) {
369
- mkdirSync2(dirname3(path), { recursive: true });
323
+ mkdirSync(dirname2(path), { recursive: true });
370
324
  const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
371
325
  writeFileSync(temporary, text4, { mode: 384 });
372
- chmodSync2(temporary, 384);
326
+ chmodSync(temporary, 384);
373
327
  renameSync(temporary, path);
374
328
  }
375
329
  function gitignoreEntry(rootDir, path) {
@@ -385,7 +339,7 @@ function displayPath(path, rootDir = process.cwd()) {
385
339
  // src/auth-guidance.ts
386
340
  var ENROL_EVERYTHING = "npx odla-ai device enroll --no-open --wait 600";
387
341
  var ENROL_PLATFORM_WIDE = "npx odla-ai device enroll --platform-wide --device-ttl 6w --no-open --wait 600";
388
- function machineAuthState(audience, env = process7.env) {
342
+ function machineAuthState(audience, env = process6.env) {
389
343
  const device = readDeviceCredential(audience, env);
390
344
  if (!device) return { enrolled: false };
391
345
  const session = readJsonFile(deviceSessionFile(env));
@@ -443,7 +397,7 @@ function explainRejectedCredential(error) {
443
397
  const tokenFile = noted;
444
398
  if (!tokenFile || !isCredentialRejection(error)) return null;
445
399
  noted = null;
446
- rmSync3(tokenFile, { force: true });
400
+ rmSync2(tokenFile, { force: true });
447
401
  return [
448
402
  "auth: the cached credential was rejected by odla, so it was revoked before its cached expiry.",
449
403
  " The usual cause is a newer sign-in for this account: collecting a handshake retires the",
@@ -454,9 +408,9 @@ function explainRejectedCredential(error) {
454
408
  }
455
409
 
456
410
  // src/device-session-cache.ts
457
- import process8 from "process";
411
+ import process7 from "process";
458
412
  var SKEW_MS = 6e4;
459
- async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = process8.env) {
413
+ async function deviceSessionToken(platformUrl, audience, credential2, doFetch, env = process7.env) {
460
414
  const path = deviceSessionFile(env);
461
415
  const cached = readJsonFile(path);
462
416
  if (cached?.token && cached.platform === audience && cached.deviceId === credential2.deviceId && (cached.expiresAt ?? 0) > Date.now() + SKEW_MS) return cached;
@@ -471,13 +425,13 @@ async function deviceSessionToken(platformUrl, audience, credential2, doFetch, e
471
425
  }
472
426
 
473
427
  // src/machine-identity.ts
474
- import process9 from "process";
475
- function readMachineIdentity(audience, env = process9.env) {
428
+ import process8 from "process";
429
+ function readMachineIdentity(audience, env = process8.env) {
476
430
  const stored = readJsonFile(identityFile(env));
477
431
  if (!stored || typeof stored.email !== "string" || !stored.email) return null;
478
432
  return stored.platform === audience ? { platform: audience, email: stored.email } : null;
479
433
  }
480
- function rememberMachineIdentity(audience, email, env = process9.env) {
434
+ function rememberMachineIdentity(audience, email, env = process8.env) {
481
435
  if (!email) return;
482
436
  const existing = readMachineIdentity(audience, env);
483
437
  if (existing?.email === email) return;
@@ -492,14 +446,14 @@ async function getDeveloperToken(cfg, options, doFetch, out, grantRequest = {})
492
446
  const cached = readJsonFile(cfg.local.tokenFile);
493
447
  if (!grantRequest.forceReview && !grantRequest.freshLogin) {
494
448
  if (options.token) return options.token;
495
- if (process10.env.ODLA_DEV_TOKEN) {
496
- const declared = process10.env.ODLA_DEV_TOKEN_AUDIENCE;
449
+ if (process9.env.ODLA_DEV_TOKEN) {
450
+ const declared = process9.env.ODLA_DEV_TOKEN_AUDIENCE;
497
451
  if (declared) {
498
452
  if (platformAudience(declared) !== audience) throw new Error("ODLA_DEV_TOKEN_AUDIENCE does not match the configured platform");
499
453
  } else if (audience !== "https://odla.ai") {
500
454
  throw new Error("ODLA_DEV_TOKEN_AUDIENCE is required for a non-default platform");
501
455
  }
502
- return process10.env.ODLA_DEV_TOKEN;
456
+ return process9.env.ODLA_DEV_TOKEN;
503
457
  }
504
458
  const device = readDeviceCredential(audience);
505
459
  if (device) {
@@ -614,7 +568,7 @@ function stillPending(pending, email) {
614
568
  );
615
569
  }
616
570
  function handshakeEmail(value2, cached) {
617
- const email = (value2 ?? process10.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
571
+ const email = (value2 ?? process9.env.ODLA_USER_EMAIL ?? cached ?? "").trim().toLowerCase();
618
572
  if (/@users\.noreply\.github\.com$/i.test(email)) {
619
573
  throw new Error(
620
574
  `"${email}" is a GitHub commit identity, not an odla account email; use --email <signed-in-odla-account> or ODLA_USER_EMAIL`
@@ -645,12 +599,12 @@ function platformAudience(value2) {
645
599
  }
646
600
 
647
601
  // src/secret-input.ts
648
- import process11 from "process";
602
+ import process10 from "process";
649
603
  var MAX_BYTES = 64 * 1024;
650
604
  async function secretInputValue(options, kind = "credential") {
651
605
  if (options.fromEnv && options.stdin) throw new Error("choose exactly one of --from-env or --stdin");
652
606
  let value2;
653
- if (options.fromEnv) value2 = process11.env[options.fromEnv];
607
+ if (options.fromEnv) value2 = process10.env[options.fromEnv];
654
608
  else if (options.stdin) value2 = await (options.readStdin ?? (() => readSecretStream(kind)))();
655
609
  else throw new Error(`${kind} input required: use --from-env <NAME> or --stdin; values are never accepted as arguments`);
656
610
  value2 = value2?.replace(/[\r\n]+$/, "");
@@ -658,7 +612,7 @@ async function secretInputValue(options, kind = "credential") {
658
612
  if (new TextEncoder().encode(value2).byteLength > MAX_BYTES) throw new Error(`${kind} exceeds 64 KiB`);
659
613
  return value2;
660
614
  }
661
- async function readSecretStream(kind, stream = process11.stdin) {
615
+ async function readSecretStream(kind, stream = process10.stdin) {
662
616
  let value2 = "";
663
617
  for await (const chunk of stream) {
664
618
  value2 += String(chunk);
@@ -668,8 +622,8 @@ async function readSecretStream(kind, stream = process11.stdin) {
668
622
  }
669
623
 
670
624
  // src/admin-ai-auth.ts
671
- import { join as join4 } from "path";
672
- import process12 from "process";
625
+ import { join as join3 } from "path";
626
+ import process11 from "process";
673
627
  import { requestToken as requestToken2 } from "@odla-ai/db";
674
628
  async function getScopedPlatformToken(options) {
675
629
  return resolveAdminPlatformToken(options);
@@ -677,7 +631,7 @@ async function getScopedPlatformToken(options) {
677
631
  async function resolveAdminPlatformToken(options) {
678
632
  const audience = platformAudience(options.platform);
679
633
  if (options.token) return options.token;
680
- const fromEnv = process12.env.ODLA_ADMIN_TOKEN;
634
+ const fromEnv = process11.env.ODLA_ADMIN_TOKEN;
681
635
  if (fromEnv) return audienceBoundEnvToken(fromEnv, audience);
682
636
  return scopedToken(
683
637
  audience,
@@ -689,7 +643,7 @@ async function resolveAdminPlatformToken(options) {
689
643
  }
690
644
  function audienceBoundEnvToken(token, platform) {
691
645
  const audience = platformAudience(platform);
692
- const declared = process12.env.ODLA_ADMIN_TOKEN_AUDIENCE;
646
+ const declared = process11.env.ODLA_ADMIN_TOKEN_AUDIENCE;
693
647
  if (declared) {
694
648
  if (platformAudience(declared) !== audience) throw new Error("ODLA_ADMIN_TOKEN_AUDIENCE does not match the configured platform");
695
649
  } else if (audience !== "https://odla.ai") {
@@ -716,9 +670,9 @@ var SCOPE_PURPOSE = {
716
670
  };
717
671
  async function scopedToken(platform, scope, options, doFetch, out) {
718
672
  const audience = platformAudience(platform);
719
- const rootDir = options.rootDir ?? process12.cwd();
673
+ const rootDir = options.rootDir ?? process11.cwd();
720
674
  const tokenFile = options.tokenFile ?? scopedTokenFile();
721
- adoptRepoLocalCache(join4(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
675
+ adoptRepoLocalCache(join3(rootDir, ".odla/admin-token.local.json"), tokenFile, out);
722
676
  const device = readDeviceCredential(audience);
723
677
  if (device && options.cache !== false) {
724
678
  const session = await deviceSessionToken(platform, audience, device, doFetch);
@@ -934,7 +888,7 @@ function isRecord2(value2) {
934
888
 
935
889
  // src/admin-ai.ts
936
890
  async function adminAi(options) {
937
- const platform = platformAudience(options.platform ?? process13.env.ODLA_PLATFORM ?? "https://odla.ai");
891
+ const platform = platformAudience(options.platform ?? process12.env.ODLA_PLATFORM ?? "https://odla.ai");
938
892
  const doFetch = options.fetch ?? fetch;
939
893
  const out = options.stdout ?? console;
940
894
  const usageQuery = options.action === "usage" ? adminAiUsageQuery(options) : void 0;
@@ -1147,10 +1101,17 @@ function parseArgv(argv) {
1147
1101
  function assertArgs(parsed, allowedOptions2, maxPositionals) {
1148
1102
  const allowed = new Set(allowedOptions2);
1149
1103
  for (const name of Object.keys(parsed.options)) {
1150
- if (!allowed.has(name)) throw new Error(`unknown option "--${name}"; run "odla-ai help" for supported options`);
1104
+ if (allowed.has(name)) continue;
1105
+ const accepts = [...allowed].sort().map((option) => `--${option}`).join(" ");
1106
+ throw new Error(
1107
+ `unknown option "--${name}"` + (accepts ? ` \u2014 this command accepts: ${accepts}` : " \u2014 this command takes no options")
1108
+ );
1151
1109
  }
1152
1110
  if (parsed.positionals.length > maxPositionals) {
1153
- throw new Error(`unexpected argument "${parsed.positionals[maxPositionals]}"; run "odla-ai help"`);
1111
+ const taken = parsed.positionals.slice(0, maxPositionals).join(" ");
1112
+ throw new Error(
1113
+ `unexpected argument "${parsed.positionals[maxPositionals]}" \u2014 ` + (maxPositionals ? `"odla-ai ${taken}" takes no further arguments` : "this command takes no arguments")
1114
+ );
1154
1115
  }
1155
1116
  }
1156
1117
  function requiredString(value2, name) {
@@ -1188,6 +1149,198 @@ function addOption(options, name, value2) {
1188
1149
  else options[name] = [String(current), String(value2)];
1189
1150
  }
1190
1151
 
1152
+ // src/surface.ts
1153
+ var PM_ACTIONS = {
1154
+ list: {},
1155
+ add: {},
1156
+ create: {},
1157
+ get: {},
1158
+ set: {},
1159
+ update: {},
1160
+ status: {},
1161
+ move: {},
1162
+ done: {},
1163
+ comment: {},
1164
+ comments: {},
1165
+ history: {},
1166
+ link: {},
1167
+ ref: {},
1168
+ rm: {},
1169
+ delete: {}
1170
+ };
1171
+ var PM_TASK_ACTIONS = {
1172
+ ...PM_ACTIONS,
1173
+ ready: {},
1174
+ claim: {},
1175
+ release: {}
1176
+ };
1177
+ var PM_ENTITIES = {
1178
+ ...Object.fromEntries(
1179
+ ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
1180
+ ),
1181
+ task: PM_TASK_ACTIONS,
1182
+ kanban: PM_TASK_ACTIONS
1183
+ };
1184
+ var COMMAND_SURFACE = {
1185
+ agent: { jobs: {}, retry: {} },
1186
+ ai: { models: {} },
1187
+ admin: {
1188
+ ai: {
1189
+ show: {},
1190
+ set: {},
1191
+ credentials: {},
1192
+ models: {},
1193
+ usage: {},
1194
+ audit: {},
1195
+ credential: { set: {} }
1196
+ },
1197
+ spend: { show: {}, reset: {} }
1198
+ },
1199
+ app: {
1200
+ archive: {},
1201
+ restore: {},
1202
+ export: {},
1203
+ import: {},
1204
+ rename: {},
1205
+ "refresh-sandbox": {},
1206
+ "go-live": {},
1207
+ promote: {},
1208
+ owners: { list: {}, add: {}, remove: {} }
1209
+ },
1210
+ auth: { login: {} },
1211
+ brand: { design: { unpack: {} } },
1212
+ bug: { create: {}, list: {}, report: {} },
1213
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
1214
+ capabilities: {},
1215
+ code: {
1216
+ connect: {},
1217
+ grant: { request: {}, list: {}, approve: {}, revoke: {} },
1218
+ repository: { show: {}, list: {}, bind: {} }
1219
+ },
1220
+ config: { diff: {}, plan: {}, apply: {} },
1221
+ context: { show: {}, list: {}, save: {}, remove: {} },
1222
+ credentials: { list: {}, revoke: {} },
1223
+ device: { enroll: {}, list: {}, revoke: {} },
1224
+ // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
1225
+ discuss: {
1226
+ groups: {},
1227
+ list: {},
1228
+ topics: {},
1229
+ read: {},
1230
+ post: {},
1231
+ reply: {},
1232
+ resolve: {},
1233
+ who: {},
1234
+ watch: {}
1235
+ },
1236
+ doctor: {},
1237
+ help: {},
1238
+ init: {},
1239
+ monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
1240
+ o11y: { status: {} },
1241
+ operations: { get: {}, wait: {} },
1242
+ platform: {
1243
+ status: {}
1244
+ },
1245
+ pm: {
1246
+ ...PM_ENTITIES,
1247
+ project: { list: {}, add: {}, create: {}, use: {} },
1248
+ handoff: {},
1249
+ next: {},
1250
+ start: {},
1251
+ watch: {}
1252
+ },
1253
+ provision: {},
1254
+ runbook: {
1255
+ ask: {},
1256
+ search: {},
1257
+ impact: {},
1258
+ list: {},
1259
+ get: {},
1260
+ cat: {},
1261
+ new: {},
1262
+ edit: {},
1263
+ comment: {},
1264
+ import: {},
1265
+ visibility: {},
1266
+ publish: {},
1267
+ archive: {},
1268
+ history: {},
1269
+ revert: {},
1270
+ rm: {},
1271
+ lint: {}
1272
+ },
1273
+ secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
1274
+ security: {
1275
+ plan: {},
1276
+ sources: {},
1277
+ run: {},
1278
+ status: {},
1279
+ report: {},
1280
+ github: { connect: {}, disconnect: {} }
1281
+ },
1282
+ setup: {},
1283
+ skill: { install: {} },
1284
+ smoke: {},
1285
+ version: {},
1286
+ whoami: {}
1287
+ };
1288
+ function acceptedAfter(path) {
1289
+ let node = COMMAND_SURFACE;
1290
+ for (const word of path) {
1291
+ node = node?.[word];
1292
+ if (!node) return [];
1293
+ }
1294
+ return Object.keys(node).sort();
1295
+ }
1296
+ function validateInvocation(words2) {
1297
+ let node = COMMAND_SURFACE;
1298
+ const walked = [];
1299
+ for (const word of words2) {
1300
+ if (Object.keys(node).length === 0) return null;
1301
+ const next = node[word];
1302
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
1303
+ walked.push(word);
1304
+ node = next;
1305
+ }
1306
+ return null;
1307
+ }
1308
+ function describeProblem(problem) {
1309
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
1310
+ if (!problem.accepted.length) return `"${problem.word}" is not accepted ${where}. Run "odla-ai help"`;
1311
+ if (!problem.word) return `"odla-ai ${problem.validPrefix}" needs one of: ${problem.accepted.join(", ")}`;
1312
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
1313
+ }
1314
+ function rejectWord(path, word, note) {
1315
+ const sentence = describeProblem({
1316
+ validPrefix: path.join(" "),
1317
+ word: word ?? "",
1318
+ accepted: acceptedAfter(path)
1319
+ });
1320
+ throw new Error(note ? `${sentence}. ${note}` : sentence);
1321
+ }
1322
+ function invocationPath(words2) {
1323
+ let node = COMMAND_SURFACE;
1324
+ const path = [];
1325
+ for (const word of words2) {
1326
+ const next = node[word];
1327
+ if (!next) break;
1328
+ path.push(word);
1329
+ node = next;
1330
+ if (Object.keys(node).length === 0) break;
1331
+ }
1332
+ return path;
1333
+ }
1334
+ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
1335
+ const paths2 = [];
1336
+ for (const [word, child] of Object.entries(node)) {
1337
+ const path = [...prefix, word];
1338
+ paths2.push(path);
1339
+ paths2.push(...surfacePaths(child, path));
1340
+ }
1341
+ return paths2;
1342
+ }
1343
+
1191
1344
  // src/admin-spend.ts
1192
1345
  async function call(ctx, method, scope) {
1193
1346
  const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
@@ -1237,9 +1390,7 @@ async function spendReset(ctx, scope) {
1237
1390
  async function adminSpend(parsed, ctx) {
1238
1391
  const action2 = parsed.positionals[2];
1239
1392
  const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
1240
- if (action2 !== "show" && action2 !== "reset") {
1241
- throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
1242
- }
1393
+ if (action2 !== "show" && action2 !== "reset") rejectWord(["admin", "spend"], action2);
1243
1394
  if (!scope) {
1244
1395
  throw new Error(
1245
1396
  `"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
@@ -1249,13 +1400,13 @@ async function adminSpend(parsed, ctx) {
1249
1400
  }
1250
1401
 
1251
1402
  // src/operator-context.ts
1252
- import { existsSync as existsSync6 } from "fs";
1253
- import { join as join6, resolve as resolve4 } from "path";
1254
- import process15 from "process";
1403
+ import { existsSync as existsSync5 } from "fs";
1404
+ import { join as join5, resolve as resolve4 } from "path";
1405
+ import process14 from "process";
1255
1406
 
1256
1407
  // src/config.ts
1257
- import { existsSync as existsSync4, rmSync as rmSync4, readFileSync as readFileSync3 } from "fs";
1258
- import { dirname as dirname4, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
1408
+ import { existsSync as existsSync3, rmSync as rmSync3, readFileSync as readFileSync3 } from "fs";
1409
+ import { dirname as dirname3, isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
1259
1410
  import { pathToFileURL } from "url";
1260
1411
  import { appServiceDefinition, appServiceIds } from "@odla-ai/apps";
1261
1412
 
@@ -1665,11 +1816,11 @@ var stderr = { error: (message2) => {
1665
1816
  } };
1666
1817
  async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1667
1818
  const resolved = resolve2(configPath);
1668
- if (!existsSync4(resolved)) {
1819
+ if (!existsSync3(resolved)) {
1669
1820
  throw new Error(`config not found: ${configPath}. Run "odla-ai init" first or pass --config.`);
1670
1821
  }
1671
1822
  const raw = await loadConfigModule(resolved);
1672
- const rootDir = dirname4(resolved);
1823
+ const rootDir = dirname3(resolved);
1673
1824
  validateRawConfig(raw, resolved);
1674
1825
  const platformUrl = trimSlash(process.env.ODLA_PLATFORM_URL || raw.platformUrl || DEFAULT_PLATFORM);
1675
1826
  const dbEndpoint = trimSlash(process.env.ODLA_DB_ENDPOINT || raw.dbEndpoint || platformUrl);
@@ -1686,7 +1837,7 @@ async function loadProjectConfig(configPath = "odla.config.mjs", options = {}) {
1686
1837
  };
1687
1838
  adoptRepoLocalCache(resolve2(rootDir, ".odla/dev-token.json"), local.tokenFile, stderr);
1688
1839
  adoptRepoLocalCache(resolve2(rootDir, ".odla/credentials.local.json"), local.credentialsFile, stderr);
1689
- rmSync4(resolve2(rootDir, ".odla/handshake.local.json"), { force: true });
1840
+ rmSync3(resolve2(rootDir, ".odla/handshake.local.json"), { force: true });
1690
1841
  return {
1691
1842
  ...raw,
1692
1843
  configPath: resolved,
@@ -1794,18 +1945,18 @@ function unique3(values) {
1794
1945
  }
1795
1946
 
1796
1947
  // src/operator-profiles.ts
1797
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1798
- import { homedir as homedir3 } from "os";
1799
- import { dirname as dirname5, join as join5, resolve as resolve3 } from "path";
1800
- import process14 from "process";
1948
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1949
+ import { homedir as homedir2 } from "os";
1950
+ import { dirname as dirname4, join as join4, resolve as resolve3 } from "path";
1951
+ import process13 from "process";
1801
1952
  function operatorProfileFile() {
1802
1953
  return resolve3(
1803
- clean(process14.env.ODLA_CONTEXT_FILE) ?? join5(homedir3(), ".odla", "contexts.json")
1954
+ clean(process13.env.ODLA_CONTEXT_FILE) ?? join4(homedir2(), ".odla", "contexts.json")
1804
1955
  );
1805
1956
  }
1806
1957
  function resolveOperatorProfile(parsed) {
1807
1958
  const fromFlag = clean(stringOpt(parsed.options.context));
1808
- const fromEnvironment = clean(process14.env.ODLA_CONTEXT);
1959
+ const fromEnvironment = clean(process13.env.ODLA_CONTEXT);
1809
1960
  const name = fromFlag ?? fromEnvironment ?? null;
1810
1961
  const file = operatorProfileFile();
1811
1962
  if (!name) {
@@ -1845,10 +1996,10 @@ function removeOperatorProfile(name, file = operatorProfileFile()) {
1845
1996
  return true;
1846
1997
  }
1847
1998
  function operatorCredentialFiles(selection) {
1848
- const base = selection.name ? join5(dirname5(selection.file), "profiles", selection.name) : join5(homedir3(), ".odla");
1999
+ const base = selection.name ? join4(dirname4(selection.file), "profiles", selection.name) : join4(homedir2(), ".odla");
1849
2000
  return {
1850
- developer: join5(base, "dev-token.json"),
1851
- scoped: join5(base, "admin-token.local.json")
2001
+ developer: join4(base, "dev-token.json"),
2002
+ scoped: join4(base, "admin-token.local.json")
1852
2003
  };
1853
2004
  }
1854
2005
  function assertOperatorName(value2, label) {
@@ -1859,7 +2010,7 @@ function assertOperatorName(value2, label) {
1859
2010
  }
1860
2011
  }
1861
2012
  function readOperatorProfiles(file) {
1862
- if (!existsSync5(file)) return emptyProfiles();
2013
+ if (!existsSync4(file)) return emptyProfiles();
1863
2014
  let raw;
1864
2015
  try {
1865
2016
  raw = JSON.parse(readFileSync4(file, "utf8"));
@@ -1928,19 +2079,19 @@ async function resolveOperatorContext(parsed, options = {}) {
1928
2079
  const configArgument = stringOpt(parsed.options.config) ?? "odla.config.mjs";
1929
2080
  const configPath = resolve4(configArgument);
1930
2081
  const explicitConfig = parsed.options.config !== void 0;
1931
- const hasConfig = existsSync6(configPath);
2082
+ const hasConfig = existsSync5(configPath);
1932
2083
  if (!hasConfig && (!options.allowMissingConfig || explicitConfig)) {
1933
2084
  await loadProjectConfig(configArgument);
1934
2085
  }
1935
2086
  const loaded = hasConfig ? await loadProjectConfig(configArgument) : void 0;
1936
2087
  const platformFlag = clean2(stringOpt(parsed.options.platform));
1937
- const platformEnvironment = clean2(process15.env.ODLA_PLATFORM_URL);
2088
+ const platformEnvironment = clean2(process14.env.ODLA_PLATFORM_URL);
1938
2089
  const platformValue = platformAudience(
1939
2090
  platformFlag ?? platformEnvironment ?? profile.value?.platform ?? loaded?.platformUrl ?? DEFAULT_PLATFORM2
1940
2091
  );
1941
2092
  const platformSource = platformFlag ? "flag" : platformEnvironment ? "environment" : profile.value ? "profile" : loaded ? "config" : "default";
1942
2093
  const appFlag = clean2(stringOpt(parsed.options.app));
1943
- const appEnvironment = clean2(process15.env.ODLA_APP_ID);
2094
+ const appEnvironment = clean2(process14.env.ODLA_APP_ID);
1944
2095
  const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
1945
2096
  const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
1946
2097
  if (appValue) {
@@ -1954,16 +2105,16 @@ async function resolveOperatorContext(parsed, options = {}) {
1954
2105
  );
1955
2106
  }
1956
2107
  const envFlag = clean2(stringOpt(parsed.options.env));
1957
- const envEnvironment = clean2(process15.env.ODLA_ENV);
2108
+ const envEnvironment = clean2(process14.env.ODLA_ENV);
1958
2109
  const environmentValue = envFlag ?? envEnvironment ?? profile.value?.environment ?? options.defaultEnvironment ?? null;
1959
2110
  const environmentSource = envFlag ? "flag" : envEnvironment ? "environment" : profile.value?.environment ? "profile" : options.defaultEnvironment ? "default" : "unresolved";
1960
2111
  if (environmentValue) {
1961
2112
  assertOperatorName(environmentValue, "environment");
1962
2113
  }
1963
- const rootDir = loaded?.rootDir ?? process15.cwd();
2114
+ const rootDir = loaded?.rootDir ?? process14.cwd();
1964
2115
  const profileCredentials = operatorCredentialFiles(profile);
1965
- const tokenFile = clean2(process15.env.ODLA_DEV_TOKEN_FILE) ? resolve4(process15.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
1966
- const scopedTokenFile2 = clean2(process15.env.ODLA_ADMIN_TOKEN_FILE) ? resolve4(process15.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join6(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
2116
+ const tokenFile = clean2(process14.env.ODLA_DEV_TOKEN_FILE) ? resolve4(process14.env.ODLA_DEV_TOKEN_FILE) : profile.name ? profileCredentials.developer : loaded?.local.tokenFile ?? profileCredentials.developer;
2117
+ const scopedTokenFile2 = clean2(process14.env.ODLA_ADMIN_TOKEN_FILE) ? resolve4(process14.env.ODLA_ADMIN_TOKEN_FILE) : profile.name ? profileCredentials.scoped : loaded ? join5(loaded.rootDir, ".odla", "admin-token.local.json") : profileCredentials.scoped;
1967
2118
  const cfg = loaded ? {
1968
2119
  ...loaded,
1969
2120
  platformUrl: platformValue,
@@ -1985,8 +2136,8 @@ async function resolveOperatorContext(parsed, options = {}) {
1985
2136
  services: [],
1986
2137
  local: {
1987
2138
  tokenFile,
1988
- credentialsFile: join6(rootDir, ".odla", "credentials.local.json"),
1989
- devVarsFile: join6(rootDir, ".dev.vars"),
2139
+ credentialsFile: join5(rootDir, ".odla", "credentials.local.json"),
2140
+ devVarsFile: join5(rootDir, ".dev.vars"),
1990
2141
  gitignore: true
1991
2142
  }
1992
2143
  };
@@ -2038,6 +2189,11 @@ var SET_OPTIONS = [
2038
2189
  async function adminCommand(parsed, deps = {}) {
2039
2190
  const area = parsed.positionals[1];
2040
2191
  const action2 = parsed.positionals[2];
2192
+ if (area !== "ai" && area !== "spend") rejectWord(["admin"], area);
2193
+ if (!acceptedAfter(["admin", area]).includes(action2 ?? "")) rejectWord(["admin", area], action2);
2194
+ if (action2 === "credential" && !acceptedAfter(["admin", "ai", "credential"]).includes(parsed.positionals[3] ?? "")) {
2195
+ rejectWord(["admin", "ai", "credential"], parsed.positionals[3]);
2196
+ }
2041
2197
  if (area === "spend") {
2042
2198
  assertArgs(parsed, JSON_OPTIONS, 4);
2043
2199
  const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2063,14 +2219,11 @@ async function adminCommand(parsed, deps = {}) {
2063
2219
  out
2064
2220
  });
2065
2221
  }
2066
- const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
2222
+ const credentialSet = action2 === "credential";
2067
2223
  const credentials = action2 === "credentials";
2068
2224
  const models = action2 === "models";
2069
2225
  const usage = action2 === "usage";
2070
2226
  const audit = action2 === "audit";
2071
- if (area !== "ai" || action2 !== "show" && action2 !== "set" && !credentialSet && !credentials && !models && !usage && !audit) {
2072
- throw new Error('unknown admin command. Try "odla-ai admin ai show".');
2073
- }
2074
2227
  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;
2075
2228
  assertArgs(parsed, allowed, credentialSet ? 5 : action2 === "set" ? 4 : 3);
2076
2229
  const context = await resolveOperatorContext(parsed, { allowMissingConfig: true });
@@ -2108,7 +2261,7 @@ async function adminCommand(parsed, deps = {}) {
2108
2261
  }
2109
2262
 
2110
2263
  // src/auth-command.ts
2111
- import process16 from "process";
2264
+ import process15 from "process";
2112
2265
 
2113
2266
  // src/whoami-command.ts
2114
2267
  var text2 = (value2) => typeof value2 === "string" && value2.trim() ? value2.trim() : null;
@@ -2284,9 +2437,7 @@ async function authCommand(parsed, deps = {}) {
2284
2437
  "json"
2285
2438
  ], 2);
2286
2439
  const action2 = parsed.positionals[1] ?? "login";
2287
- if (action2 !== "login") {
2288
- throw new Error(`unknown auth action "${action2}". Try "odla-ai auth login --app <id> --email <odla-account>".`);
2289
- }
2440
+ if (action2 !== "login") rejectWord(["auth"], action2);
2290
2441
  const context = await resolveOperatorContext(parsed, {
2291
2442
  allowMissingConfig: true,
2292
2443
  requireApp: true
@@ -2294,7 +2445,7 @@ async function authCommand(parsed, deps = {}) {
2294
2445
  const { cfg } = context;
2295
2446
  const out = deps.stdout ?? console;
2296
2447
  const doFetch = deps.fetch ?? fetch;
2297
- const email = stringOpt(parsed.options.email) ?? process16.env.ODLA_USER_EMAIL?.trim();
2448
+ const email = stringOpt(parsed.options.email) ?? process15.env.ODLA_USER_EMAIL?.trim();
2298
2449
  if (!email) {
2299
2450
  throw new Error(
2300
2451
  "auth login requires --email <odla-account> or ODLA_USER_EMAIL; confirm the signed-in odla email instead of using git or GitHub identity"
@@ -2359,9 +2510,7 @@ function bothTenants(cfg) {
2359
2510
  // src/agent-command.ts
2360
2511
  async function agentCommand(parsed, deps = {}) {
2361
2512
  const action2 = parsed.positionals[1];
2362
- if (action2 !== "jobs" && action2 !== "retry") {
2363
- throw new Error(`unknown agent action "${action2 ?? ""}". Try "odla-ai agent jobs --json".`);
2364
- }
2513
+ if (action2 !== "jobs" && action2 !== "retry") rejectWord(["agent"], action2);
2365
2514
  assertArgs(parsed, ["config", "env", "state", "limit", "json", "token"], action2 === "jobs" ? 2 : 3);
2366
2515
  if (action2 === "retry" && (parsed.options.state !== void 0 || parsed.options.limit !== void 0)) {
2367
2516
  throw new Error('--state and --limit are supported only by "agent jobs"');
@@ -2536,9 +2685,7 @@ async function appOwnersCommand(parsed, dependencies = {}) {
2536
2685
  await (sub === "add" ? ownersAdd(email, options) : ownersRemove(email, options));
2537
2686
  return;
2538
2687
  }
2539
- throw new Error(
2540
- `unknown app owners subcommand "${sub}". Try "odla-ai app owners list", "odla-ai app owners add <email>", or "odla-ai app owners remove <email>".`
2541
- );
2688
+ rejectWord(["app", "owners"], sub);
2542
2689
  }
2543
2690
 
2544
2691
  // src/app-rename.ts
@@ -2644,8 +2791,10 @@ async function appCommand(parsed, dependencies = {}) {
2644
2791
  return;
2645
2792
  }
2646
2793
  if (sub !== "archive" && sub !== "restore") {
2647
- throw new Error(
2648
- `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.)`
2794
+ rejectWord(
2795
+ ["app"],
2796
+ sub,
2797
+ "Permanent deletion has no CLI: it requires a signed-in owner in Studio."
2649
2798
  );
2650
2799
  }
2651
2800
  assertArgs(parsed, ["config", "token", "email", "yes", "json"], 2);
@@ -2664,7 +2813,7 @@ async function appCommand(parsed, dependencies = {}) {
2664
2813
 
2665
2814
  // src/brand-command.ts
2666
2815
  import { mkdir, readFile, writeFile } from "fs/promises";
2667
- import { dirname as dirname6, resolve as resolve5 } from "path";
2816
+ import { dirname as dirname5, resolve as resolve5 } from "path";
2668
2817
 
2669
2818
  // src/brand-design-unpack.ts
2670
2819
  import { gunzipSync } from "zlib";
@@ -2779,7 +2928,7 @@ async function readBundle(source, deps) {
2779
2928
  async function writeAll(result, outDir) {
2780
2929
  for (const file of result.files) {
2781
2930
  const target = resolve5(outDir, file.path);
2782
- await mkdir(dirname6(target), { recursive: true });
2931
+ await mkdir(dirname5(target), { recursive: true });
2783
2932
  await writeFile(target, file.bytes);
2784
2933
  }
2785
2934
  }
@@ -2811,7 +2960,8 @@ async function brandCommand(parsed, deps) {
2811
2960
  await designUnpack(parsed, deps);
2812
2961
  return;
2813
2962
  }
2814
- throw new Error(USAGE);
2963
+ if (subject !== "design") rejectWord(["brand"], subject);
2964
+ rejectWord(["brand", "design"], action2, USAGE);
2815
2965
  }
2816
2966
 
2817
2967
  // src/calendar-errors.ts
@@ -3345,7 +3495,7 @@ import {
3345
3495
  AppsError,
3346
3496
  createAppsClient
3347
3497
  } from "@odla-ai/apps";
3348
- import { join as join7 } from "path";
3498
+ import { join as join6 } from "path";
3349
3499
 
3350
3500
  // src/config-operation-error.ts
3351
3501
  var ConfigOperationCommandError = class extends Error {
@@ -3770,7 +3920,7 @@ async function operationClient(cfg, options, purpose) {
3770
3920
  platform: cfg.platformUrl,
3771
3921
  scope: "app:config:write",
3772
3922
  token: options.token,
3773
- tokenFile: join7(cfg.rootDir, ".odla", "admin-token.local.json"),
3923
+ tokenFile: join6(cfg.rootDir, ".odla", "admin-token.local.json"),
3774
3924
  rootDir: cfg.rootDir,
3775
3925
  email: options.email,
3776
3926
  open: options.open,
@@ -3825,7 +3975,7 @@ function record4(value2) {
3825
3975
 
3826
3976
  // src/config-reconcile-command.ts
3827
3977
  import { createAppsClient as createAppsClient2, studioAppSettingsPath } from "@odla-ai/apps";
3828
- import { join as join8 } from "path";
3978
+ import { join as join7 } from "path";
3829
3979
 
3830
3980
  // src/config-reconcile.ts
3831
3981
  import { appServiceIds as appServiceIds2, orderAppServices as orderAppServices2 } from "@odla-ai/apps";
@@ -4121,7 +4271,7 @@ async function inspectConfig(options) {
4121
4271
  platform: cfg.platformUrl,
4122
4272
  scope: "app:config:read",
4123
4273
  token: options.token,
4124
- tokenFile: join8(cfg.rootDir, ".odla", "admin-token.local.json"),
4274
+ tokenFile: join7(cfg.rootDir, ".odla", "admin-token.local.json"),
4125
4275
  rootDir: cfg.rootDir,
4126
4276
  email: options.email,
4127
4277
  open: options.open,
@@ -4253,13 +4403,13 @@ function quoteArg2(value2) {
4253
4403
 
4254
4404
  // src/doctor-checks.ts
4255
4405
  import { execFileSync } from "child_process";
4256
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
4257
- import { join as join10, resolve as resolve6 } from "path";
4406
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
4407
+ import { join as join9, resolve as resolve6 } from "path";
4258
4408
 
4259
4409
  // src/wrangler.ts
4260
4410
  import { spawn as spawn2 } from "child_process";
4261
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
4262
- import { join as join9 } from "path";
4411
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
4412
+ import { join as join8 } from "path";
4263
4413
  var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) => {
4264
4414
  const child = spawn2(cmd, args, { cwd: opts?.cwd, stdio: ["pipe", "pipe", "pipe"] });
4265
4415
  let stdout = "";
@@ -4273,8 +4423,8 @@ var defaultRunner = (cmd, args, opts) => new Promise((resolvePromise, reject) =>
4273
4423
  var WRANGLER_CONFIG_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
4274
4424
  function findWranglerConfig(rootDir) {
4275
4425
  for (const name of WRANGLER_CONFIG_FILES) {
4276
- const path = join9(rootDir, name);
4277
- if (existsSync7(path)) return path;
4426
+ const path = join8(rootDir, name);
4427
+ if (existsSync6(path)) return path;
4278
4428
  }
4279
4429
  return null;
4280
4430
  }
@@ -4434,7 +4584,7 @@ function wranglerWarnings(rootDir) {
4434
4584
  const dir = resolve6(rootDir, assets.directory);
4435
4585
  if (dir === resolve6(rootDir)) {
4436
4586
  warnings.push(`${label}assets.directory is the project root \u2014 point it at a dedicated build dir (wrangler dev fails with "spawn EBADF")`);
4437
- } else if (existsSync8(join10(dir, "node_modules"))) {
4587
+ } else if (existsSync7(join9(dir, "node_modules"))) {
4438
4588
  warnings.push(`${label}assets.directory contains node_modules \u2014 wrangler dev's watcher will exhaust file descriptors`);
4439
4589
  }
4440
4590
  }
@@ -4470,7 +4620,7 @@ function o11yProjectWarnings(rootDir) {
4470
4620
  return warnings;
4471
4621
  }
4472
4622
  const main = typeof config.main === "string" ? resolve6(rootDir, config.main) : null;
4473
- if (!main || !existsSync8(main)) {
4623
+ if (!main || !existsSync7(main)) {
4474
4624
  warnings.push("cannot verify o11y Worker instrumentation \u2014 wrangler main is missing or unreadable");
4475
4625
  } else {
4476
4626
  let source = "";
@@ -4499,7 +4649,7 @@ function calendarProjectWarnings(rootDir) {
4499
4649
  }
4500
4650
  function readPackageJson(rootDir) {
4501
4651
  try {
4502
- return JSON.parse(readFileSync8(join10(rootDir, "package.json"), "utf8"));
4652
+ return JSON.parse(readFileSync8(join9(rootDir, "package.json"), "utf8"));
4503
4653
  } catch {
4504
4654
  return null;
4505
4655
  }
@@ -4793,14 +4943,14 @@ function harnessOption(value2, flag) {
4793
4943
  }
4794
4944
 
4795
4945
  // src/init.ts
4796
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
4797
- import { dirname as dirname7, resolve as resolve7 } from "path";
4946
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
4947
+ import { dirname as dirname6, resolve as resolve7 } from "path";
4798
4948
  import { appServiceDefinition as appServiceDefinition3, appServiceIds as appServiceIds3 } from "@odla-ai/apps";
4799
4949
  function initProject(options) {
4800
4950
  const out = options.stdout ?? console;
4801
4951
  const rootDir = resolve7(options.rootDir ?? process.cwd());
4802
4952
  const configPath = resolve7(rootDir, options.configPath ?? "odla.config.mjs");
4803
- if (existsSync9(configPath) && !options.force) {
4953
+ if (existsSync8(configPath) && !options.force) {
4804
4954
  throw new Error(`${configPath} already exists. Pass --force to overwrite.`);
4805
4955
  }
4806
4956
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.appId)) {
@@ -4816,9 +4966,9 @@ function initProject(options) {
4816
4966
  }
4817
4967
  }
4818
4968
  const aiProvider = options.aiProvider;
4819
- mkdirSync3(dirname7(configPath), { recursive: true });
4820
- mkdirSync3(resolve7(rootDir, "src/odla"), { recursive: true });
4821
- mkdirSync3(resolve7(rootDir, ".odla"), { recursive: true });
4969
+ mkdirSync2(dirname6(configPath), { recursive: true });
4970
+ mkdirSync2(resolve7(rootDir, "src/odla"), { recursive: true });
4971
+ mkdirSync2(resolve7(rootDir, ".odla"), { recursive: true });
4822
4972
  writeFileSync2(configPath, configTemplate({ appId: options.appId, name: options.name, envs, services, aiProvider }));
4823
4973
  writeIfMissing(resolve7(rootDir, "src/odla/schema.mjs"), schemaTemplate());
4824
4974
  writeIfMissing(resolve7(rootDir, "src/odla/rules.mjs"), rulesTemplate());
@@ -4828,7 +4978,7 @@ function initProject(options) {
4828
4978
  out.log("updated .gitignore for local odla credentials");
4829
4979
  }
4830
4980
  function writeIfMissing(path, text4) {
4831
- if (existsSync9(path)) return;
4981
+ if (existsSync8(path)) return;
4832
4982
  writeFileSync2(path, text4);
4833
4983
  }
4834
4984
  function configTemplate(input) {
@@ -5127,9 +5277,9 @@ function printReport(report5, out) {
5127
5277
  }
5128
5278
 
5129
5279
  // src/skill.ts
5130
- import { existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync9, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5131
- import { homedir as homedir4 } from "os";
5132
- import { dirname as dirname8, isAbsolute as isAbsolute3, join as join11, relative as relative2, resolve as resolve8, sep } from "path";
5280
+ import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync, writeFileSync as writeFileSync3 } from "fs";
5281
+ import { homedir as homedir3 } from "os";
5282
+ import { dirname as dirname7, isAbsolute as isAbsolute3, join as join10, relative as relative2, resolve as resolve8, sep } from "path";
5133
5283
  import { fileURLToPath } from "url";
5134
5284
 
5135
5285
  // src/skill-adapters.ts
@@ -5229,7 +5379,7 @@ function installSkill(options = {}) {
5229
5379
  if (files.length === 0) throw new Error(`no bundled skills found at ${sourceDir}`);
5230
5380
  const harnesses = normalizeHarnesses(options.harnesses, options.global === true);
5231
5381
  const root = resolve8(options.dir ?? process.cwd());
5232
- const home = resolve8(options.homeDir ?? homedir4());
5382
+ const home = resolve8(options.homeDir ?? homedir3());
5233
5383
  const plans = /* @__PURE__ */ new Map();
5234
5384
  const targets = /* @__PURE__ */ new Map();
5235
5385
  const rememberTarget = (harness, target) => {
@@ -5243,48 +5393,48 @@ function installSkill(options = {}) {
5243
5393
  plans.set(target, { target, content: content2, boundary, managedMerge });
5244
5394
  };
5245
5395
  const planSkillTree = (targetDir2, boundary = root) => {
5246
- for (const rel of files) plan(join11(targetDir2, rel), readFileSync9(join11(sourceDir, rel), "utf8"), false, boundary);
5396
+ for (const rel of files) plan(join10(targetDir2, rel), readFileSync9(join10(sourceDir, rel), "utf8"), false, boundary);
5247
5397
  };
5248
5398
  let targetDir;
5249
5399
  if (options.global) {
5250
- const claudeRoot = join11(home, ".claude", "skills");
5251
- const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ?? join11(home, ".codex"), "skills");
5400
+ const claudeRoot = join10(home, ".claude", "skills");
5401
+ const codexRoot = resolve8(options.codexHomeDir ?? process.env.CODEX_HOME ?? join10(home, ".codex"), "skills");
5252
5402
  targetDir = harnesses[0] === "codex" ? codexRoot : claudeRoot;
5253
5403
  for (const harness of harnesses) {
5254
5404
  const skillRoot = harness === "claude" ? claudeRoot : codexRoot;
5255
- planSkillTree(skillRoot, harness === "claude" ? home : dirname8(dirname8(codexRoot)));
5405
+ planSkillTree(skillRoot, harness === "claude" ? home : dirname7(dirname7(codexRoot)));
5256
5406
  rememberTarget(harness, skillRoot);
5257
5407
  }
5258
5408
  } else {
5259
- const sharedRoot = join11(root, ".agents", "skills");
5409
+ const sharedRoot = join10(root, ".agents", "skills");
5260
5410
  planSkillTree(sharedRoot);
5261
- const claudeRoot = join11(root, ".claude", "skills");
5411
+ const claudeRoot = join10(root, ".claude", "skills");
5262
5412
  targetDir = harnesses.includes("claude") ? claudeRoot : sharedRoot;
5263
5413
  for (const harness of harnesses) rememberTarget(harness, sharedRoot);
5264
5414
  if (harnesses.includes("claude")) {
5265
5415
  for (const skill of skillNames(files)) {
5266
- const canonical2 = readFileSync9(join11(sourceDir, skill, "SKILL.md"), "utf8");
5267
- plan(join11(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5416
+ const canonical2 = readFileSync9(join10(sourceDir, skill, "SKILL.md"), "utf8");
5417
+ plan(join10(claudeRoot, skill, "SKILL.md"), claudeAdapter(skill, canonical2));
5268
5418
  }
5269
5419
  rememberTarget("claude", claudeRoot);
5270
5420
  }
5271
5421
  if (harnesses.includes("cursor")) {
5272
- const cursorRule = join11(root, ".cursor", "rules", "odla.mdc");
5422
+ const cursorRule = join10(root, ".cursor", "rules", "odla.mdc");
5273
5423
  plan(cursorRule, CURSOR_RULE);
5274
5424
  rememberTarget("cursor", cursorRule);
5275
5425
  }
5276
5426
  if (harnesses.includes("agents")) {
5277
- const agentsFile = join11(root, "AGENTS.md");
5427
+ const agentsFile = join10(root, "AGENTS.md");
5278
5428
  plan(agentsFile, managedFileContent(agentsFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5279
5429
  rememberTarget("agents", agentsFile);
5280
5430
  }
5281
5431
  if (harnesses.includes("copilot")) {
5282
- const copilotFile = join11(root, ".github", "copilot-instructions.md");
5432
+ const copilotFile = join10(root, ".github", "copilot-instructions.md");
5283
5433
  plan(copilotFile, managedFileContent(copilotFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5284
5434
  rememberTarget("copilot", copilotFile);
5285
5435
  }
5286
5436
  if (harnesses.includes("gemini")) {
5287
- const geminiFile = join11(root, "GEMINI.md");
5437
+ const geminiFile = join10(root, "GEMINI.md");
5288
5438
  plan(geminiFile, managedFileContent(geminiFile, PROJECT_INSTRUCTIONS, options.force === true, root), true);
5289
5439
  rememberTarget("gemini", geminiFile);
5290
5440
  }
@@ -5298,7 +5448,7 @@ function installSkill(options = {}) {
5298
5448
  conflicts.push(`${file.target} (redirected by symbolic link ${symlink})`);
5299
5449
  continue;
5300
5450
  }
5301
- if (!existsSync10(file.target)) {
5451
+ if (!existsSync9(file.target)) {
5302
5452
  writtenPaths.add(file.target);
5303
5453
  continue;
5304
5454
  }
@@ -5319,8 +5469,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
5319
5469
  );
5320
5470
  }
5321
5471
  for (const file of plans.values()) {
5322
- if (!existsSync10(file.target) || readFileSync9(file.target, "utf8") !== file.content) {
5323
- mkdirSync4(dirname8(file.target), { recursive: true });
5472
+ if (!existsSync9(file.target) || readFileSync9(file.target, "utf8") !== file.content) {
5473
+ mkdirSync3(dirname7(file.target), { recursive: true });
5324
5474
  writeFileSync3(file.target, file.content);
5325
5475
  }
5326
5476
  }
@@ -5362,7 +5512,7 @@ function normalizeHarnesses(values, global) {
5362
5512
  function managedFileContent(path, block2, force, boundary) {
5363
5513
  const symlink = symlinkedComponent(boundary, path);
5364
5514
  if (symlink) throw new Error(`refusing to manage ${path}: symbolic link component ${symlink}`);
5365
- if (!existsSync10(path)) return `${block2}
5515
+ if (!existsSync9(path)) return `${block2}
5366
5516
  `;
5367
5517
  const current = readFileSync9(path, "utf8");
5368
5518
  const start = "<!-- odla-ai agent setup:start -->";
@@ -5391,7 +5541,7 @@ function symlinkedComponent(boundary, target) {
5391
5541
  }
5392
5542
  let current = boundary;
5393
5543
  for (const part of rel.split(sep).filter(Boolean)) {
5394
- current = join11(current, part);
5544
+ current = join10(current, part);
5395
5545
  try {
5396
5546
  if (lstatSync(current).isSymbolicLink()) return current;
5397
5547
  } catch (error) {
@@ -5404,11 +5554,11 @@ function skillNames(files) {
5404
5554
  return [...new Set(files.filter((file) => /(^|[\\/])SKILL\.md$/.test(file)).map((file) => file.split(/[\\/]/)[0]))].sort();
5405
5555
  }
5406
5556
  function listFiles(dir) {
5407
- if (!existsSync10(dir)) return [];
5557
+ if (!existsSync9(dir)) return [];
5408
5558
  const results = [];
5409
5559
  const walk = (current) => {
5410
5560
  for (const entry of readdirSync(current, { withFileTypes: true })) {
5411
- const path = join11(current, entry.name);
5561
+ const path = join10(current, entry.name);
5412
5562
  if (entry.isDirectory()) walk(path);
5413
5563
  else results.push(relative2(dir, path));
5414
5564
  }
@@ -5620,9 +5770,7 @@ async function secretsCommand(parsed, deps) {
5620
5770
  return;
5621
5771
  }
5622
5772
  if (sub !== "push") {
5623
- throw new Error(
5624
- `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".`
5625
- );
5773
+ rejectWord(["secrets"], sub);
5626
5774
  }
5627
5775
  assertArgs(parsed, ["config", "env", "dry-run", "yes"], 2);
5628
5776
  await secretsPush({
@@ -5635,7 +5783,7 @@ async function secretsCommand(parsed, deps) {
5635
5783
  async function projectCommand(command, parsed, deps) {
5636
5784
  if (command === "ai") {
5637
5785
  const sub = parsed.positionals[1];
5638
- if (sub !== "models") throw new Error(`unknown ai subcommand "${sub ?? ""}". Try "odla-ai ai models --env dev".`);
5786
+ if (sub !== "models") rejectWord(["ai"], sub);
5639
5787
  assertArgs(parsed, ["config", "env", "provider", "json"], 2);
5640
5788
  await aiModels({
5641
5789
  configPath: stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -5649,9 +5797,7 @@ async function projectCommand(command, parsed, deps) {
5649
5797
  }
5650
5798
  if (command === "config") {
5651
5799
  const sub = parsed.positionals[1];
5652
- if (sub !== "diff" && sub !== "plan" && sub !== "apply") {
5653
- throw new Error(`unknown config subcommand "${sub ?? ""}". Try "odla-ai config diff --json".`);
5654
- }
5800
+ if (sub !== "diff" && sub !== "plan" && sub !== "apply") rejectWord(["config"], sub);
5655
5801
  assertArgs(
5656
5802
  parsed,
5657
5803
  sub === "apply" ? ["config", "plan", "idempotency-key", "token", "email", "open", "json"] : ["config", "token", "email", "open", "json"],
@@ -5679,7 +5825,7 @@ async function projectCommand(command, parsed, deps) {
5679
5825
  if (command === "operations") {
5680
5826
  const sub = parsed.positionals[1];
5681
5827
  if (sub !== "get" && sub !== "wait") {
5682
- throw new Error(`unknown operations subcommand "${sub ?? ""}". Try "odla-ai operations get <operation-id> --json".`);
5828
+ rejectWord(["operations"], sub);
5683
5829
  }
5684
5830
  assertArgs(
5685
5831
  parsed,
@@ -5753,7 +5899,7 @@ async function projectCommand(command, parsed, deps) {
5753
5899
  }
5754
5900
  if (command === "skill") {
5755
5901
  const sub = parsed.positionals[1];
5756
- if (sub !== "install") throw new Error(`unknown skill subcommand "${sub ?? ""}". Try "odla-ai skill install".`);
5902
+ if (sub !== "install") rejectWord(["skill"], sub);
5757
5903
  install(parsed, 2, deps);
5758
5904
  return true;
5759
5905
  }
@@ -5765,7 +5911,7 @@ async function projectCommand(command, parsed, deps) {
5765
5911
  }
5766
5912
 
5767
5913
  // src/code-connect.ts
5768
- import { existsSync as existsSync11 } from "fs";
5914
+ import { existsSync as existsSync10 } from "fs";
5769
5915
  import { cpus, hostname, totalmem } from "os";
5770
5916
  import { resolve as resolve11 } from "path";
5771
5917
 
@@ -5776,7 +5922,7 @@ var HARNESS_PROTOCOL_VERSION = 1;
5776
5922
  import { execFile, spawn as spawn3 } from "child_process";
5777
5923
  import { constants } from "fs";
5778
5924
  import { access } from "fs/promises";
5779
- import { delimiter, join as join12 } from "path";
5925
+ import { delimiter, join as join11 } from "path";
5780
5926
  import { getgid, getuid } from "process";
5781
5927
  import { mkdir as mkdir2, mkdtemp, realpath, rm, writeFile as writeFile2 } from "fs/promises";
5782
5928
  import { tmpdir } from "os";
@@ -5794,7 +5940,7 @@ function assertPinnedImage(image) {
5794
5940
  async function commandAvailable(engine) {
5795
5941
  for (const directory of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
5796
5942
  try {
5797
- await access(join12(directory, engine), constants.X_OK);
5943
+ await access(join11(directory, engine), constants.X_OK);
5798
5944
  return true;
5799
5945
  } catch {
5800
5946
  }
@@ -6492,10 +6638,10 @@ import { randomUUID } from "crypto";
6492
6638
  import { createHash as createHash22, randomUUID as randomUUID2 } from "crypto";
6493
6639
  import { createReadStream } from "fs";
6494
6640
  import { lstat as lstat22 } from "fs/promises";
6495
- import { join as join14 } from "path";
6641
+ import { join as join13 } from "path";
6496
6642
  import { mkdir as mkdir3, mkdtemp as mkdtemp3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
6497
6643
  import { tmpdir as tmpdir3 } from "os";
6498
- import { dirname as dirname10, join as join23, resolve as resolve32, sep as sep23 } from "path";
6644
+ import { dirname as dirname9, join as join23, resolve as resolve32, sep as sep23 } from "path";
6499
6645
  import {
6500
6646
  keepRecentExchanges,
6501
6647
  runAgent
@@ -6893,11 +7039,11 @@ function rollup(graph, kind, options = {}) {
6893
7039
  }
6894
7040
 
6895
7041
  // ../graph/dist/code/index.js
6896
- function dirname9(path) {
7042
+ function dirname8(path) {
6897
7043
  const at = path.lastIndexOf("/");
6898
7044
  return at <= 0 ? "." : path.slice(0, at);
6899
7045
  }
6900
- function join13(base, specifier) {
7046
+ function join12(base, specifier) {
6901
7047
  const parts = [];
6902
7048
  const segments = `${base === "." ? "" : `${base}/`}${specifier}`.split("/");
6903
7049
  for (const segment of segments) {
@@ -6921,7 +7067,7 @@ var BARE_IMPORT = /^\s*import\s*["']([^"']+)["']/gm;
6921
7067
  var isSourcePath = (path) => SOURCE.test(path);
6922
7068
  function resolveImport(fromPath, specifier, known) {
6923
7069
  if (!specifier.startsWith(".")) return null;
6924
- const base = join13(dirname9(fromPath), specifier);
7070
+ const base = join12(dirname8(fromPath), specifier);
6925
7071
  const candidates = [
6926
7072
  base,
6927
7073
  base.replace(/\.js$/, ".ts"),
@@ -7725,7 +7871,7 @@ async function inspectArtifacts(workspaceDir, recipe2) {
7725
7871
  const receipts = [];
7726
7872
  for (const artifact of recipe2.expectedArtifacts ?? []) {
7727
7873
  try {
7728
- const path = join14(workspaceDir, artifact.path);
7874
+ const path = join13(workspaceDir, artifact.path);
7729
7875
  const info = await lstat22(path);
7730
7876
  if (!info.isFile() || info.isSymbolicLink()) {
7731
7877
  receipts.push({ artifactId: artifact.id, status: "invalid", bytes: null, digest: null });
@@ -8014,7 +8160,7 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
8014
8160
  if (bytes > SOURCE_MAX_BYTES) throw new TypeError("Code source exceeds its byte bound");
8015
8161
  const target = resolve32(sourceDir, file.path);
8016
8162
  if (!target.startsWith(`${resolve32(sourceDir)}${sep23}`)) throw new TypeError("Code source path escapes its root");
8017
- await mkdir3(dirname10(target), { recursive: true });
8163
+ await mkdir3(dirname9(target), { recursive: true });
8018
8164
  await writeFile3(target, file.content, { flag: "wx", mode: 420 });
8019
8165
  }
8020
8166
  for (const reference of snapshot.references ?? []) {
@@ -8029,7 +8175,7 @@ async function materializeCodeRuntimeSource(snapshot, tempRoot = tmpdir3()) {
8029
8175
  if (bytes > SOURCE_SET_MAX_BYTES) throw new TypeError("Code source set exceeds its byte bound");
8030
8176
  const target = resolve32(sourceDir, path);
8031
8177
  if (!target.startsWith(`${resolve32(sourceDir)}${sep23}`)) throw new TypeError("Code reference path escapes its root");
8032
- await mkdir3(dirname10(target), { recursive: true });
8178
+ await mkdir3(dirname9(target), { recursive: true });
8033
8179
  await writeFile3(target, file.content, { flag: "wx", mode: 292 });
8034
8180
  }
8035
8181
  }
@@ -8056,7 +8202,7 @@ async function attachCodeRuntimeReferences(workspace, references) {
8056
8202
  for (const root of [workspace.baselineDir, workspace.workspaceDir]) {
8057
8203
  const target = resolve32(root, path);
8058
8204
  if (!target.startsWith(`${resolve32(root)}${sep23}`)) throw new TypeError("Code reference path escapes its root");
8059
- await mkdir3(dirname10(target), { recursive: true });
8205
+ await mkdir3(dirname9(target), { recursive: true });
8060
8206
  await writeFile3(target, file.content, { flag: "wx", mode: 292 });
8061
8207
  }
8062
8208
  }
@@ -10119,7 +10265,7 @@ var CODE_BUILD_RECIPES = Object.freeze([{
10119
10265
  async function codeConnect(options) {
10120
10266
  const cwd = options.cwd ?? process.cwd();
10121
10267
  const configPath = resolve11(cwd, options.configPath);
10122
- const cfg = existsSync11(configPath) ? await loadProjectConfig(configPath) : null;
10268
+ const cfg = existsSync10(configPath) ? await loadProjectConfig(configPath) : null;
10123
10269
  const requestedAppId = options.appId?.trim();
10124
10270
  if (requestedAppId && !/^[a-z0-9][a-z0-9-]{1,62}$/.test(requestedAppId)) {
10125
10271
  throw new Error("--app-id must be a valid odla app id");
@@ -10314,9 +10460,7 @@ function grantsUrl(cfg, suffix = "") {
10314
10460
  async function codeGrantCommand(parsed, deps = {}) {
10315
10461
  const action2 = parsed.positionals[2];
10316
10462
  if (!isAction(action2)) {
10317
- throw new Error(
10318
- `unknown code grant action "${action2 ?? ""}". Try "odla-ai code grant list --env dev".`
10319
- );
10463
+ rejectWord(["code", "grant"], action2);
10320
10464
  }
10321
10465
  const decides = action2 === "approve" || action2 === "revoke";
10322
10466
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open"], decides ? 4 : 3);
@@ -10436,9 +10580,7 @@ function repositoryUrl(cfg, suffix = "") {
10436
10580
  async function codeRepositoryCommand(parsed, deps = {}) {
10437
10581
  const action2 = parsed.positionals[2];
10438
10582
  if (!isAction2(action2)) {
10439
- throw new Error(
10440
- `unknown code repository action "${action2 ?? ""}". Try "odla-ai code repository show --env dev".`
10441
- );
10583
+ rejectWord(["code", "repository"], action2);
10442
10584
  }
10443
10585
  assertArgs(parsed, ["config", "env", "repo", "json", "token", "email", "open"], 3);
10444
10586
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
@@ -10513,9 +10655,7 @@ async function codeCommand(parsed, dependencies) {
10513
10655
  });
10514
10656
  }
10515
10657
  if (sub !== "connect") {
10516
- throw new Error(
10517
- `unknown code subcommand "${sub ?? ""}". Try "odla-ai code connect --env dev" or "odla-ai code grant list --env dev".`
10518
- );
10658
+ rejectWord(["code"], sub);
10519
10659
  }
10520
10660
  assertArgs(parsed, [
10521
10661
  "config",
@@ -10554,13 +10694,13 @@ async function codeCommand(parsed, dependencies) {
10554
10694
  }
10555
10695
 
10556
10696
  // src/operator-credentials.ts
10557
- import process17 from "process";
10697
+ import process16 from "process";
10558
10698
  function developerTokenStatus(context, parsed, now = Date.now()) {
10559
10699
  const cached = readJsonFile(context.cfg.local.tokenFile);
10560
10700
  const cacheStatus = !cached?.token ? "missing" : cached.platform !== context.platform.value ? "other-platform" : (cached.expiresAt ?? 0) <= now + 6e4 ? "expired" : "valid";
10561
10701
  const source = clean3(
10562
10702
  stringOpt(parsed.options.token)
10563
- ) ? "flag" : clean3(process17.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10703
+ ) ? "flag" : clean3(process16.env.ODLA_DEV_TOKEN) ? "environment" : cacheStatus === "valid" ? "cache" : "missing";
10564
10704
  return {
10565
10705
  source,
10566
10706
  cacheFile: context.cfg.local.tokenFile,
@@ -10652,9 +10792,7 @@ async function contextCommand(parsed, deps = {}) {
10652
10792
  return;
10653
10793
  }
10654
10794
  if (action2 !== "show") {
10655
- throw new Error(
10656
- `unknown context action "${action2 ?? ""}". Try show|list|save|remove.`
10657
- );
10795
+ rejectWord(["context"], action2);
10658
10796
  }
10659
10797
  const context = await resolveOperatorContext(parsed, {
10660
10798
  allowMissingConfig: true,
@@ -10710,9 +10848,7 @@ async function responseError(response2) {
10710
10848
  }
10711
10849
  async function credentialCommand(parsed, deps = {}) {
10712
10850
  const action2 = parsed.positionals[1] ?? "list";
10713
- if (action2 !== "list" && action2 !== "revoke") {
10714
- throw new Error(`unknown credentials action "${action2}". Try "odla-ai credentials list".`);
10715
- }
10851
+ if (action2 !== "list" && action2 !== "revoke") rejectWord(["credentials"], action2);
10716
10852
  assertArgs(parsed, ["config", "env", "all", "json", "token", "email", "open"], action2 === "revoke" ? 3 : 2);
10717
10853
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config) ?? "odla.config.mjs");
10718
10854
  const doFetch = deps.fetch ?? fetch;
@@ -11121,9 +11257,17 @@ Safety:
11121
11257
  grant, run it once with --request-grant. That flag ignores ODLA_DEV_TOKEN and
11122
11258
  the local cache, prints and opens a fresh exact-project owner-review URL, then
11123
11259
  continues provisioning with the approved replacement credential.
11124
- Before a non-dry-run provision, the executable checks npm's current CLI
11125
- release. A confirmed stale client stops with a safe npx rerun command; a
11126
- workspace-linked client also identifies the worktree that must be updated.
11260
+ Every command says so on STDERR when this CLI is older than the one npm
11261
+ serves, naming the repair for how this executable was launched \u2014 rebuild the
11262
+ worktree, npm i the dependency, or npx the scoped package. The answer is read
11263
+ from a cache under ~/.odla refreshed in the background at most every three
11264
+ hours, so no invocation waits on the registry and stdout is never touched.
11265
+ Set ODLA_CLI_UPDATE_CHECK=0 to switch it off.
11266
+ Before a non-dry-run provision, the executable additionally checks npm LIVE
11267
+ rather than trusting that cache: a security-sensitive grant shape must not
11268
+ ride on a three-hour-old answer. A confirmed stale client stops with a safe
11269
+ npx rerun command; a workspace-linked client also identifies the worktree
11270
+ that must be updated.
11127
11271
  Run Code from a GitHub checkout already connected to an app in Studio; an
11128
11272
  odla.config.mjs may select the app explicitly but is not required. With an
11129
11273
  enrolled code.session device, the Studio repository selection authorizes the
@@ -11714,7 +11858,7 @@ async function discussCommand(parsed, deps = {}) {
11714
11858
  assertArgs(parsed, ALLOWED, 3);
11715
11859
  const action2 = parsed.positionals[1];
11716
11860
  const id2 = parsed.positionals[2];
11717
- if (!action2) throw new Error('"discuss" needs an action. Run "odla-ai help".');
11861
+ if (!acceptedAfter(["discuss"]).includes(action2 ?? "")) rejectWord(["discuss"], action2);
11718
11862
  const ctx = await buildContext(parsed, deps);
11719
11863
  switch (action2) {
11720
11864
  case "groups":
@@ -11738,7 +11882,7 @@ async function discussCommand(parsed, deps = {}) {
11738
11882
  return;
11739
11883
  }
11740
11884
  default:
11741
- throw new Error(`unknown discuss action "${action2}". Run "odla-ai help".`);
11885
+ rejectWord(["discuss"], action2);
11742
11886
  }
11743
11887
  }
11744
11888
 
@@ -12458,7 +12602,7 @@ async function pmWatch(ctx, parsed) {
12458
12602
  }
12459
12603
 
12460
12604
  // src/pm-project-context.ts
12461
- import { existsSync as existsSync12, rmSync as rmSync5 } from "fs";
12605
+ import { existsSync as existsSync11, rmSync as rmSync4 } from "fs";
12462
12606
  import { resolve as resolve12 } from "path";
12463
12607
  var pmProjectContextFile = () => pmContextFile();
12464
12608
  function readPmProjectContext(rootDir) {
@@ -12475,9 +12619,9 @@ function writePmProjectContext(rootDir, value2) {
12475
12619
  }
12476
12620
  function adoptLegacySelection(rootDir) {
12477
12621
  const legacy = resolve12(rootDir, ".odla", "pm-project.local.json");
12478
- if (!existsSync12(legacy)) return;
12622
+ if (!existsSync11(legacy)) return;
12479
12623
  const previous = readJsonFile(legacy);
12480
- rmSync5(legacy, { force: true });
12624
+ rmSync4(legacy, { force: true });
12481
12625
  if (!isSelection(previous)) return;
12482
12626
  const selections = readSelections();
12483
12627
  if (selections[previous.appId]) return;
@@ -12648,7 +12792,7 @@ async function pmCommand(parsed, deps = {}) {
12648
12792
  assertArgs(parsed, COMMON_OPTIONS, 4);
12649
12793
  return pmProjectUse(await buildContext2(parsed, deps), requireId2(parsed.positionals[3], action3));
12650
12794
  }
12651
- throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
12795
+ rejectWord(["pm", "project"], action3);
12652
12796
  }
12653
12797
  if (word === "next") {
12654
12798
  assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
@@ -12679,10 +12823,10 @@ async function pmCommand(parsed, deps = {}) {
12679
12823
  return pmHandoff(await buildContext2(parsed, deps), parsed);
12680
12824
  }
12681
12825
  const entity = ALIASES[word];
12682
- 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).`);
12826
+ if (!entity) rejectWord(["pm"], word);
12683
12827
  const requestedAction = parsed.positionals[2] ?? "list";
12684
12828
  const action2 = canonicalAction(requestedAction);
12685
- if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
12829
+ if (!action2) rejectWord(["pm", word], requestedAction);
12686
12830
  assertArgs(parsed, allowedOptions(entity, action2), 4);
12687
12831
  if ((action2 === "ready" || action2 === "claim" || action2 === "release") && entity !== "task") {
12688
12832
  throw new Error(`pm ${action2} is only valid for tasks`);
@@ -12768,12 +12912,7 @@ async function platformCommand(parsed, deps = {}) {
12768
12912
  if (action2 === "status") {
12769
12913
  return platformStatus(parsed, deps);
12770
12914
  }
12771
- throw new Error(
12772
- `unknown platform action "${[
12773
- action2,
12774
- parsed.positionals[2]
12775
- ].filter(Boolean).join(" ")}". Try "odla-ai platform status --json".`
12776
- );
12915
+ rejectWord(["platform"], action2);
12777
12916
  }
12778
12917
  async function platformStatus(parsed, deps) {
12779
12918
  assertArgs(
@@ -13110,9 +13249,7 @@ async function o11yCommand(parsed, deps = {}) {
13110
13249
  );
13111
13250
  const action2 = parsed.positionals[1];
13112
13251
  if (action2 !== "status") {
13113
- throw new Error(
13114
- `unknown o11y action "${action2 ?? ""}". Try "odla-ai o11y status --json".`
13115
- );
13252
+ rejectWord(["o11y"], action2);
13116
13253
  }
13117
13254
  const minutes = statusMinutes(
13118
13255
  numberOpt(parsed.options.minutes, "--minutes") ?? 60
@@ -13365,9 +13502,7 @@ var OPTIONS = [
13365
13502
  async function monitorCommand(parsed, deps = {}) {
13366
13503
  assertArgs(parsed, OPTIONS, 3);
13367
13504
  const action2 = parsed.positionals[1] ?? "status";
13368
- if (!["plan", "apply", "run", "status", "incidents", "report"].includes(action2)) {
13369
- throw new Error(`unknown monitor action "${action2}". Try "odla-ai monitor status --json".`);
13370
- }
13505
+ if (!acceptedAfter(["monitor"]).includes(action2)) rejectWord(["monitor"], action2);
13371
13506
  const context = await resolveOperatorContext(parsed, {
13372
13507
  allowMissingConfig: action2 !== "plan" && action2 !== "apply",
13373
13508
  requireApp: true
@@ -13516,7 +13651,7 @@ function percent(value2) {
13516
13651
  // src/provision.ts
13517
13652
  import { AppsError as AppsError2, createAppsClient as createAppsClient3, orderAppServices as orderAppServices3, tenantIdFor as tenantIdFor6 } from "@odla-ai/apps";
13518
13653
  import { putSecret as putSecret2 } from "@odla-ai/ai";
13519
- import process18 from "process";
13654
+ import process17 from "process";
13520
13655
 
13521
13656
  // src/integration-provision.ts
13522
13657
  import { uuidv7 } from "@odla-ai/db";
@@ -13954,7 +14089,7 @@ async function provision(options) {
13954
14089
  await provisionIntegrationSeeds(doFetch, cfg.dbEndpoint, tenantId, dbKey, database.integrations, env, out);
13955
14090
  }
13956
14091
  if (cfg.services.includes("ai") && cfg.ai?.provider && cfg.ai.keyEnv) {
13957
- const key = process18.env[cfg.ai.keyEnv];
14092
+ const key = process17.env[cfg.ai.keyEnv];
13958
14093
  if (key) {
13959
14094
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
13960
14095
  await putSecret2({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
@@ -13996,190 +14131,9 @@ async function provision(options) {
13996
14131
 
13997
14132
  // src/record.ts
13998
14133
  import { appendFileSync } from "fs";
13999
- import process19 from "process";
14000
-
14001
- // src/surface.ts
14002
- var PM_ACTIONS = {
14003
- list: {},
14004
- add: {},
14005
- create: {},
14006
- get: {},
14007
- set: {},
14008
- update: {},
14009
- status: {},
14010
- move: {},
14011
- done: {},
14012
- comment: {},
14013
- comments: {},
14014
- ref: {},
14015
- rm: {},
14016
- delete: {}
14017
- };
14018
- var PM_TASK_ACTIONS = {
14019
- ...PM_ACTIONS,
14020
- ready: {},
14021
- claim: {},
14022
- release: {}
14023
- };
14024
- var PM_ENTITIES = {
14025
- ...Object.fromEntries(
14026
- ["goal", "conformance", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
14027
- ),
14028
- task: PM_TASK_ACTIONS,
14029
- kanban: PM_TASK_ACTIONS
14030
- };
14031
- var COMMAND_SURFACE = {
14032
- agent: { jobs: {}, retry: {} },
14033
- ai: { models: {} },
14034
- admin: {
14035
- ai: {
14036
- show: {},
14037
- set: {},
14038
- credentials: {},
14039
- models: {},
14040
- usage: {},
14041
- audit: {},
14042
- credential: { set: {} }
14043
- }
14044
- },
14045
- app: {
14046
- archive: {},
14047
- restore: {},
14048
- export: {},
14049
- import: {},
14050
- rename: {},
14051
- "refresh-sandbox": {},
14052
- "go-live": {},
14053
- promote: {},
14054
- owners: { list: {}, add: {}, remove: {} }
14055
- },
14056
- auth: { login: {} },
14057
- brand: { design: { unpack: {} } },
14058
- bug: { create: {}, list: {}, report: {} },
14059
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
14060
- capabilities: {},
14061
- code: {
14062
- connect: {},
14063
- grant: { request: {}, list: {}, approve: {}, revoke: {} },
14064
- repository: { show: {}, list: {}, bind: {} }
14065
- },
14066
- config: { diff: {}, plan: {}, apply: {} },
14067
- context: { show: {}, list: {}, save: {}, remove: {} },
14068
- credentials: { list: {}, revoke: {} },
14069
- device: { enroll: {}, list: {}, revoke: {} },
14070
- // `watch`, `read`, `reply`, and `resolve` take a topic id from there on.
14071
- discuss: {
14072
- groups: {},
14073
- list: {},
14074
- topics: {},
14075
- read: {},
14076
- post: {},
14077
- reply: {},
14078
- resolve: {},
14079
- who: {},
14080
- watch: {}
14081
- },
14082
- doctor: {},
14083
- help: {},
14084
- init: {},
14085
- monitor: { plan: {}, apply: {}, run: {}, status: {}, incidents: {}, report: {} },
14086
- o11y: { status: {} },
14087
- operations: { get: {}, wait: {} },
14088
- platform: {
14089
- status: {}
14090
- },
14091
- pm: {
14092
- ...PM_ENTITIES,
14093
- project: { list: {}, add: {}, create: {}, use: {} },
14094
- handoff: {},
14095
- next: {},
14096
- start: {},
14097
- watch: {}
14098
- },
14099
- provision: {},
14100
- runbook: {
14101
- ask: {},
14102
- search: {},
14103
- impact: {},
14104
- list: {},
14105
- get: {},
14106
- cat: {},
14107
- new: {},
14108
- edit: {},
14109
- comment: {},
14110
- import: {},
14111
- visibility: {},
14112
- publish: {},
14113
- archive: {},
14114
- history: {},
14115
- revert: {},
14116
- rm: {},
14117
- lint: {}
14118
- },
14119
- secrets: { push: {}, status: {}, set: {}, "set-clerk-key": {} },
14120
- security: {
14121
- plan: {},
14122
- sources: {},
14123
- run: {},
14124
- status: {},
14125
- report: {},
14126
- github: { connect: {}, disconnect: {} }
14127
- },
14128
- setup: {},
14129
- skill: { install: {} },
14130
- smoke: {},
14131
- version: {},
14132
- whoami: {}
14133
- };
14134
- function acceptedAfter(path) {
14135
- let node = COMMAND_SURFACE;
14136
- for (const word of path) {
14137
- node = node?.[word];
14138
- if (!node) return [];
14139
- }
14140
- return Object.keys(node).sort();
14141
- }
14142
- function validateInvocation(words2) {
14143
- let node = COMMAND_SURFACE;
14144
- const walked = [];
14145
- for (const word of words2) {
14146
- if (Object.keys(node).length === 0) return null;
14147
- const next = node[word];
14148
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
14149
- walked.push(word);
14150
- node = next;
14151
- }
14152
- return null;
14153
- }
14154
- function describeProblem(problem) {
14155
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
14156
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
14157
- }
14158
- function invocationPath(words2) {
14159
- let node = COMMAND_SURFACE;
14160
- const path = [];
14161
- for (const word of words2) {
14162
- const next = node[word];
14163
- if (!next) break;
14164
- path.push(word);
14165
- node = next;
14166
- if (Object.keys(node).length === 0) break;
14167
- }
14168
- return path;
14169
- }
14170
- function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
14171
- const paths2 = [];
14172
- for (const [word, child] of Object.entries(node)) {
14173
- const path = [...prefix, word];
14174
- paths2.push(path);
14175
- paths2.push(...surfacePaths(child, path));
14176
- }
14177
- return paths2;
14178
- }
14179
-
14180
- // src/record.ts
14134
+ import process18 from "process";
14181
14135
  function recordInvocation(parsed) {
14182
- const file = process19.env.ODLA_CLI_RECORD;
14136
+ const file = process18.env.ODLA_CLI_RECORD;
14183
14137
  if (!file) return;
14184
14138
  try {
14185
14139
  const entry = {
@@ -14250,9 +14204,9 @@ function parseDeviceTtl(raw) {
14250
14204
  }
14251
14205
 
14252
14206
  // src/device-command.ts
14253
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
14254
- import { dirname as dirname11 } from "path";
14255
- import process20 from "process";
14207
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
14208
+ import { dirname as dirname10 } from "path";
14209
+ import process19 from "process";
14256
14210
  async function deviceCommand(parsed, deps) {
14257
14211
  assertArgs(parsed, [
14258
14212
  "app",
@@ -14271,6 +14225,7 @@ async function deviceCommand(parsed, deps) {
14271
14225
  "wait"
14272
14226
  ], 3);
14273
14227
  const action2 = parsed.positionals[1] ?? "";
14228
+ if (!acceptedAfter(["device"]).includes(action2)) rejectWord(["device"], action2);
14274
14229
  const out = deps.stdout ?? console;
14275
14230
  const doFetch = deps.fetch ?? fetch;
14276
14231
  const cfg = await loadProjectConfig(stringOpt(parsed.options.config));
@@ -14278,7 +14233,7 @@ async function deviceCommand(parsed, deps) {
14278
14233
  if (action2 === "enroll") return enroll(parsed, deps, cfg, doFetch, out, json);
14279
14234
  if (action2 === "list") return list2(parsed, deps, cfg, doFetch, out, json);
14280
14235
  if (action2 === "revoke") return revoke(parsed, deps, cfg, doFetch, out, json);
14281
- throw new Error('odla-ai device expects "enroll", "list", or "revoke"');
14236
+ rejectWord(["device"], action2);
14282
14237
  }
14283
14238
  async function enroll(parsed, deps, cfg, doFetch, out, json) {
14284
14239
  const name = stringOpt(parsed.options.name) ?? defaultDeviceName();
@@ -14309,7 +14264,7 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
14309
14264
  headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
14310
14265
  body: JSON.stringify({
14311
14266
  name,
14312
- platform: process20.platform,
14267
+ platform: process19.platform,
14313
14268
  appIds: apps,
14314
14269
  ...capabilities ? { capabilities } : {},
14315
14270
  ...scopes ? { scopes } : {},
@@ -14321,14 +14276,14 @@ async function enroll(parsed, deps, cfg, doFetch, out, json) {
14321
14276
  throw new Error(`device enroll failed: ${body.error?.message ?? `registry returned ${response2.status}`} (${response2.status})`);
14322
14277
  }
14323
14278
  const path = deviceCredentialPath();
14324
- mkdirSync5(dirname11(path), { recursive: true });
14279
+ mkdirSync4(dirname10(path), { recursive: true });
14325
14280
  writeFileSync4(path, JSON.stringify({
14326
14281
  token: body.token,
14327
14282
  platform: cfg.platformUrl.replace(/\/$/, ""),
14328
14283
  deviceId: body.device.deviceId,
14329
14284
  name
14330
14285
  }, null, 2));
14331
- chmodSync3(path, 384);
14286
+ chmodSync2(path, 384);
14332
14287
  rememberMachineIdentity(cfg.platformUrl.replace(/\/$/, ""), stringOpt(parsed.options.email));
14333
14288
  supersedeAdvisory("credential.expiring");
14334
14289
  const reach = body.device.appIds.includes(ALL_OWNED_APPS) ? "every app you own, now and later" : body.device.appIds.join(", ");
@@ -14428,7 +14383,7 @@ async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app
14428
14383
  });
14429
14384
  }
14430
14385
  function defaultDeviceName() {
14431
- return `${process20.env.HOSTNAME ?? process20.env.HOST ?? "machine"}-${process20.platform}`;
14386
+ return `${process19.env.HOSTNAME ?? process19.env.HOST ?? "machine"}-${process19.platform}`;
14432
14387
  }
14433
14388
 
14434
14389
  // src/runbook-actions.ts
@@ -14572,7 +14527,7 @@ async function runbookRemove(ctx, slug) {
14572
14527
 
14573
14528
  // src/runbook-import.ts
14574
14529
  import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
14575
- import { basename as basename2, join as join15 } from "path";
14530
+ import { basename as basename2, join as join14 } from "path";
14576
14531
  function parseRunbook(text4, slug) {
14577
14532
  let rest = text4;
14578
14533
  const meta = {};
@@ -14602,7 +14557,7 @@ function readRunbookDir(dir) {
14602
14557
  if (!files.length) throw new Error(`no .md files in ${dir}`);
14603
14558
  return files.map((file) => {
14604
14559
  const slug = basename2(file, ".md");
14605
- const parsed = parseRunbook(readFileSync11(join15(dir, file), "utf8"), slug);
14560
+ const parsed = parseRunbook(readFileSync11(join14(dir, file), "utf8"), slug);
14606
14561
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
14607
14562
  });
14608
14563
  }
@@ -14670,8 +14625,8 @@ async function upsert(ctx, r, visibility) {
14670
14625
 
14671
14626
  // src/runbook-impact.ts
14672
14627
  import { execFileSync as execFileSync2 } from "child_process";
14673
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "fs";
14674
- import { join as join16 } from "path";
14628
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
14629
+ import { join as join15 } from "path";
14675
14630
 
14676
14631
  // src/runbook-impact-scan.ts
14677
14632
  var DECL = /^[+-]\s*export\s+(?:declare\s+)?(?:default\s+)?(?:abstract\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+([A-Za-z_$][\w$]*)/;
@@ -14840,8 +14795,8 @@ ${body.split("\n").map((line2) => `+${line2}`).join("\n")}
14840
14795
  }
14841
14796
  function manifestLabeller(root) {
14842
14797
  return (workspace) => {
14843
- const manifest = join16(root, workspace, "package.json");
14844
- if (!existsSync13(manifest)) return void 0;
14798
+ const manifest = join15(root, workspace, "package.json");
14799
+ if (!existsSync12(manifest)) return void 0;
14845
14800
  try {
14846
14801
  const name = JSON.parse(readFileSync12(manifest, "utf8")).name;
14847
14802
  return typeof name === "string" ? name : void 0;
@@ -14910,7 +14865,7 @@ function report4(ctx, impacts) {
14910
14865
  async function runbookImpact(ctx, options, deps = {}) {
14911
14866
  const cwd = deps.cwd ?? process.cwd();
14912
14867
  const runGit = deps.runGit ?? gitRunner(cwd);
14913
- const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join16(cwd, path), "utf8"));
14868
+ const read3 = deps.readRepoFile ?? ((path) => readFileSync12(join15(cwd, path), "utf8"));
14914
14869
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read3), manifestLabeller(cwd));
14915
14870
  if (!surfaces.length) {
14916
14871
  return ctx.out.log(
@@ -15037,12 +14992,12 @@ async function runbookComment(ctx, slug, body) {
15037
14992
 
15038
14993
  // src/runbook-editor.ts
15039
14994
  import { spawnSync } from "child_process";
15040
- import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
14995
+ import { mkdtempSync, readFileSync as readFileSync13, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
15041
14996
  import { tmpdir as tmpdir4 } from "os";
15042
- import { join as join17 } from "path";
15043
- import process21 from "process";
14997
+ import { join as join16 } from "path";
14998
+ import process20 from "process";
15044
14999
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
15045
- function resolveEditor(env = process21.env) {
15000
+ function resolveEditor(env = process20.env) {
15046
15001
  for (const name of EDITOR_ENV) {
15047
15002
  const value2 = env[name];
15048
15003
  if (value2 && value2.trim()) return value2.trim();
@@ -15056,8 +15011,8 @@ function defaultRun(command, path) {
15056
15011
  return result.status ?? 0;
15057
15012
  }
15058
15013
  function editText(initial, slug, deps = {}) {
15059
- const env = deps.env ?? process21.env;
15060
- const interactive = deps.interactive ?? (() => Boolean(process21.stdin.isTTY));
15014
+ const env = deps.env ?? process20.env;
15015
+ const interactive = deps.interactive ?? (() => Boolean(process20.stdin.isTTY));
15061
15016
  const editor = resolveEditor(env);
15062
15017
  if (!editor)
15063
15018
  throw new Error(
@@ -15065,8 +15020,8 @@ function editText(initial, slug, deps = {}) {
15065
15020
  );
15066
15021
  if (!interactive())
15067
15022
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
15068
- const dir = mkdtempSync(join17(tmpdir4(), "odla-runbook-"));
15069
- const file = join17(dir, `${slug}.md`);
15023
+ const dir = mkdtempSync(join16(tmpdir4(), "odla-runbook-"));
15024
+ const file = join16(dir, `${slug}.md`);
15070
15025
  try {
15071
15026
  writeFileSync5(file, initial, { mode: 384 });
15072
15027
  const code = defaultRunOrInjected(deps)(editor, file);
@@ -15074,7 +15029,7 @@ function editText(initial, slug, deps = {}) {
15074
15029
  const edited = readFileSync13(file, "utf8");
15075
15030
  return edited === initial ? null : edited;
15076
15031
  } finally {
15077
- rmSync6(dir, { recursive: true, force: true });
15032
+ rmSync5(dir, { recursive: true, force: true });
15078
15033
  }
15079
15034
  }
15080
15035
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -15176,6 +15131,7 @@ async function buildContext3(parsed, deps, action2) {
15176
15131
  async function runbookCommand(parsed, deps = {}) {
15177
15132
  const action2 = parsed.positionals[1] ?? "list";
15178
15133
  assertArgs(parsed, ALLOWED2, action2 === "ask" || action2 === "search" ? 64 : 4);
15134
+ if (!acceptedAfter(["runbook"]).includes(action2)) rejectWord(["runbook"], action2);
15179
15135
  const ctx = await buildContext3(parsed, deps, action2);
15180
15136
  const slug = parsed.positionals[2];
15181
15137
  switch (action2) {
@@ -15265,7 +15221,7 @@ async function runbookCommand(parsed, deps = {}) {
15265
15221
  case "rm":
15266
15222
  return runbookRemove(ctx, requireSlug(slug, "rm"));
15267
15223
  default:
15268
- throw new Error(`unknown runbook action "${action2}". Try ${acceptedAfter(["runbook"]).join(", ")}.`);
15224
+ rejectWord(["runbook"], action2);
15269
15225
  }
15270
15226
  }
15271
15227
 
@@ -15886,9 +15842,7 @@ async function securityCommand(parsed, dependencies) {
15886
15842
  else printHostedReport(context.stdout, report5);
15887
15843
  return;
15888
15844
  }
15889
- if (sub !== "run") {
15890
- throw new Error('unknown security command. Try "odla-ai security plan", "security sources", or "security run".');
15891
- }
15845
+ if (sub !== "run") rejectWord(["security"], sub);
15892
15846
  const sourceId = stringOpt(parsed.options.source);
15893
15847
  if (sourceId) await runSourceSecurityCommand(parsed, dependencies, sourceId);
15894
15848
  else await runLocalSecurityCommand(parsed, dependencies);
@@ -15905,9 +15859,7 @@ async function githubSecurityCommand(parsed, dependencies) {
15905
15859
  stringOpt(parsed.options.env)
15906
15860
  );
15907
15861
  }
15908
- if (action2 !== "connect") {
15909
- throw new Error('unknown security github command. Try "odla-ai security github connect".');
15910
- }
15862
+ if (action2 !== "connect") rejectWord(["security", "github"], action2);
15911
15863
  assertArgs(parsed, ["config", "env", "platform", "repo", "email", "open"], 3);
15912
15864
  await requireStudioHuman(
15913
15865
  stringOpt(parsed.options.config) ?? "odla.config.mjs",
@@ -15970,6 +15922,11 @@ async function runCli(argv = process.argv.slice(2), dependencies = {}) {
15970
15922
  throw error;
15971
15923
  } finally {
15972
15924
  renderAdvisories(out, advisories);
15925
+ try {
15926
+ const behind = updateNotice();
15927
+ if (behind) out.error(behind);
15928
+ } catch {
15929
+ }
15973
15930
  }
15974
15931
  }
15975
15932
  async function dispatchCli(argv, dependencies) {
@@ -16050,6 +16007,7 @@ async function dispatchCli(argv, dependencies) {
16050
16007
  }
16051
16008
  if (command === "bug") {
16052
16009
  const action2 = parsed.positionals[1] ?? "list";
16010
+ if (!acceptedAfter(["bug"]).includes(action2)) rejectWord(["bug"], action2);
16053
16011
  const canonical2 = action2 === "report" || action2 === "create" ? "add" : action2;
16054
16012
  await pmCommand({
16055
16013
  ...parsed,
@@ -16078,7 +16036,7 @@ async function dispatchCli(argv, dependencies) {
16078
16036
  return;
16079
16037
  }
16080
16038
  if (await projectCommand(command, parsed, runtime)) return;
16081
- throw new Error(`unknown command "${command}". Run "odla-ai help".`);
16039
+ rejectWord([], command);
16082
16040
  }
16083
16041
  async function provisionCommand(parsed, dependencies) {
16084
16042
  assertArgs(parsed, [
@@ -16122,7 +16080,7 @@ async function provisionCommand(parsed, dependencies) {
16122
16080
  async function calendarCommand(parsed, dependencies) {
16123
16081
  const sub = parsed.positionals[1];
16124
16082
  if (sub !== "status" && sub !== "calendars" && sub !== "connect" && sub !== "disconnect") {
16125
- throw new Error(`unknown calendar subcommand "${sub ?? ""}". Try "odla-ai calendar status --env dev".`);
16083
+ rejectWord(["calendar"], sub);
16126
16084
  }
16127
16085
  assertArgs(parsed, ["config", "env", "json", "token", "email", "open", "yes"], 2);
16128
16086
  if (sub !== "status" && sub !== "calendars" && parsed.options.json !== void 0) throw new Error(`--json is supported only by calendar status/calendars`);
@@ -16149,6 +16107,12 @@ export {
16149
16107
  getScopedPlatformToken,
16150
16108
  SYSTEM_AI_PURPOSES,
16151
16109
  adminAi,
16110
+ COMMAND_SURFACE,
16111
+ acceptedAfter,
16112
+ validateInvocation,
16113
+ describeProblem,
16114
+ invocationPath,
16115
+ surfacePaths,
16152
16116
  calendarServiceConfig,
16153
16117
  calendarBookingPageUrl,
16154
16118
  GOOGLE_CALENDAR_EVENTS_SCOPE,
@@ -16186,12 +16150,6 @@ export {
16186
16150
  monitoringWireConfig,
16187
16151
  monitorCommand,
16188
16152
  provision,
16189
- COMMAND_SURFACE,
16190
- acceptedAfter,
16191
- validateInvocation,
16192
- describeProblem,
16193
- invocationPath,
16194
- surfacePaths,
16195
16153
  runHostedSecurity,
16196
16154
  getHostedSecurityIntent,
16197
16155
  getHostedSecurityPlan,
@@ -16203,4 +16161,4 @@ export {
16203
16161
  isTerminalHostedSecurityStatus,
16204
16162
  runCli
16205
16163
  };
16206
- //# sourceMappingURL=chunk-T4OPSRME.js.map
16164
+ //# sourceMappingURL=chunk-JHB6352A.js.map