@odla-ai/cli 0.20.2 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -57,7 +57,7 @@ function approvalLines(prompt) {
57
57
  return lines;
58
58
  }
59
59
  function printApproval(out, prompt) {
60
- for (const line of approvalLines(prompt)) out.log(line);
60
+ for (const line of approvalLines(prompt)) out.error(line);
61
61
  }
62
62
  function reminderLines(prompt) {
63
63
  return [
@@ -277,7 +277,7 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
277
277
  }
278
278
  const cached = readJsonFile(cfg.local.tokenFile);
279
279
  if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
280
- out.log(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
280
+ out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
281
281
  return cached.token;
282
282
  }
283
283
  const ctx = {
@@ -294,16 +294,16 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
294
294
  const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
295
295
  clearPendingHandshake(ctx.pendingFile);
296
296
  writePrivateJson(cfg.local.tokenFile, { platform: audience, email: ctx.email, token, expiresAt });
297
- out.log(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
297
+ out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
298
298
  return token;
299
299
  }
300
300
  async function resumePendingHandshake(ctx, waitMs) {
301
301
  const pending = readPendingHandshake(ctx.pendingFile, ctx.audience, ctx.email);
302
302
  if (!pending) return null;
303
- ctx.out.log("");
304
- ctx.out.log(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
303
+ ctx.out.error("");
304
+ ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
305
305
  await launchApproval(ctx, pending.approvalUrl);
306
- ctx.out.log("");
306
+ ctx.out.error("");
307
307
  const stopReminder = approvalReminder(ctx.out, pending);
308
308
  try {
309
309
  return await (0, import_db.collectToken)({
@@ -319,7 +319,7 @@ async function resumePendingHandshake(ctx, waitMs) {
319
319
  if (code === "handshake_pending") throw stillPending(pending, ctx.email);
320
320
  if (code === "handshake_expired" || code === "handshake_timeout") {
321
321
  clearPendingHandshake(ctx.pendingFile);
322
- ctx.out.log("auth: pending handshake lapsed unapproved; starting a fresh one");
322
+ ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
323
323
  return null;
324
324
  }
325
325
  if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
@@ -377,12 +377,12 @@ async function launchApproval(ctx, approvalUrl) {
377
377
  if (ctx.browser.open) {
378
378
  try {
379
379
  await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
380
- ctx.out.log(`auth: asked the OS to open a browser${ctx.browser.mode === "auto" ? " (auto)" : ""} \u2014 if no tab appeared, use the URL above`);
380
+ ctx.out.error(`auth: asked the OS to open a browser${ctx.browser.mode === "auto" ? " (auto)" : ""} \u2014 if no tab appeared, use the URL above`);
381
381
  } catch (err) {
382
- ctx.out.log(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
382
+ ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
383
383
  }
384
384
  } else if (ctx.browser.reason) {
385
- ctx.out.log(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
385
+ ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
386
386
  }
387
387
  }
388
388
  function stillPending(pending, email) {
@@ -507,7 +507,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
507
507
  const cache = options.cache === false ? null : readJsonFile(tokenFile);
508
508
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
509
509
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
510
- out.log(`auth: using cached ${scope} grant (${tokenFile})`);
510
+ out.error(`auth: using cached ${scope} grant (${tokenFile})`);
511
511
  return cached.token;
512
512
  }
513
513
  const email = handshakeEmail(options.email, cache?.platform === audience ? cache.email : void 0);
@@ -538,9 +538,9 @@ async function scopedToken(platform, scope, options, doFetch, out) {
538
538
  tokens[scope] = { token, expiresAt };
539
539
  if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
540
540
  writePrivateJson(tokenFile, { platform: audience, email, tokens });
541
- out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
541
+ out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
542
542
  } else {
543
- out.log(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
543
+ out.error(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
544
544
  }
545
545
  return token;
546
546
  }
@@ -7799,13 +7799,154 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
7799
7799
  }
7800
7800
  }
7801
7801
 
7802
+ // src/record.ts
7803
+ var import_node_fs13 = require("fs");
7804
+ var import_node_process8 = __toESM(require("process"), 1);
7805
+
7806
+ // src/surface.ts
7807
+ var PM_ACTIONS = {
7808
+ list: {},
7809
+ add: {},
7810
+ create: {},
7811
+ get: {},
7812
+ set: {},
7813
+ update: {},
7814
+ status: {},
7815
+ move: {},
7816
+ done: {},
7817
+ comment: {},
7818
+ comments: {},
7819
+ rm: {},
7820
+ delete: {}
7821
+ };
7822
+ var PM_ENTITIES = Object.fromEntries(
7823
+ ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
7824
+ );
7825
+ var COMMAND_SURFACE = {
7826
+ admin: {
7827
+ ai: {
7828
+ show: {},
7829
+ set: {},
7830
+ credentials: {},
7831
+ models: {},
7832
+ usage: {},
7833
+ audit: {},
7834
+ credential: { set: {} }
7835
+ }
7836
+ },
7837
+ app: {
7838
+ archive: {},
7839
+ restore: {},
7840
+ export: {},
7841
+ import: {},
7842
+ rename: {},
7843
+ "refresh-sandbox": {},
7844
+ "go-live": {},
7845
+ promote: {},
7846
+ owners: { list: {}, add: {}, remove: {} }
7847
+ },
7848
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
7849
+ capabilities: {},
7850
+ code: { connect: {} },
7851
+ doctor: {},
7852
+ help: {},
7853
+ init: {},
7854
+ pm: PM_ENTITIES,
7855
+ provision: {},
7856
+ runbook: {
7857
+ ask: {},
7858
+ search: {},
7859
+ impact: {},
7860
+ list: {},
7861
+ get: {},
7862
+ cat: {},
7863
+ new: {},
7864
+ edit: {},
7865
+ comment: {},
7866
+ import: {},
7867
+ visibility: {},
7868
+ publish: {},
7869
+ archive: {},
7870
+ history: {},
7871
+ revert: {},
7872
+ rm: {},
7873
+ lint: {}
7874
+ },
7875
+ secrets: { push: {}, set: {}, "set-clerk-key": {} },
7876
+ security: {
7877
+ plan: {},
7878
+ sources: {},
7879
+ run: {},
7880
+ status: {},
7881
+ report: {},
7882
+ github: { connect: {}, disconnect: {} }
7883
+ },
7884
+ setup: {},
7885
+ skill: { install: {} },
7886
+ smoke: {},
7887
+ version: {},
7888
+ whoami: {}
7889
+ };
7890
+ function acceptedAfter(path) {
7891
+ let node = COMMAND_SURFACE;
7892
+ for (const word of path) {
7893
+ node = node?.[word];
7894
+ if (!node) return [];
7895
+ }
7896
+ return Object.keys(node).sort();
7897
+ }
7898
+ function validateInvocation(words2) {
7899
+ let node = COMMAND_SURFACE;
7900
+ const walked = [];
7901
+ for (const word of words2) {
7902
+ if (Object.keys(node).length === 0) return null;
7903
+ const next = node[word];
7904
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
7905
+ walked.push(word);
7906
+ node = next;
7907
+ }
7908
+ return null;
7909
+ }
7910
+ function describeProblem(problem) {
7911
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
7912
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
7913
+ }
7914
+ function invocationPath(words2) {
7915
+ let node = COMMAND_SURFACE;
7916
+ const path = [];
7917
+ for (const word of words2) {
7918
+ const next = node[word];
7919
+ if (!next) break;
7920
+ path.push(word);
7921
+ node = next;
7922
+ if (Object.keys(node).length === 0) break;
7923
+ }
7924
+ return path;
7925
+ }
7926
+
7927
+ // src/record.ts
7928
+ function recordInvocation(parsed) {
7929
+ const file = import_node_process8.default.env.ODLA_CLI_RECORD;
7930
+ if (!file) return;
7931
+ try {
7932
+ const entry = {
7933
+ path: invocationPath(parsed.positionals),
7934
+ options: Object.keys(parsed.options).sort()
7935
+ };
7936
+ if (!entry.path.length) return;
7937
+ (0, import_node_fs13.appendFileSync)(file, `${JSON.stringify(entry)}
7938
+ `);
7939
+ } catch {
7940
+ }
7941
+ }
7942
+
7802
7943
  // src/runbook-command.ts
7803
- var import_node_fs17 = require("fs");
7944
+ var import_node_fs18 = require("fs");
7804
7945
  var import_node_path14 = require("path");
7805
- var import_node_process9 = __toESM(require("process"), 1);
7946
+ var import_node_process10 = __toESM(require("process"), 1);
7806
7947
 
7807
7948
  // src/runbook-actions.ts
7808
- var import_node_fs13 = require("fs");
7949
+ var import_node_fs14 = require("fs");
7809
7950
 
7810
7951
  // src/runbook-requires.ts
7811
7952
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -7890,7 +8031,7 @@ async function bySlug(ctx, slug) {
7890
8031
  function readBody(file, inline) {
7891
8032
  if (inline !== void 0) return inline;
7892
8033
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
7893
- return (0, import_node_fs13.readFileSync)(file === "-" ? 0 : file, "utf8");
8034
+ return (0, import_node_fs14.readFileSync)(file === "-" ? 0 : file, "utf8");
7894
8035
  }
7895
8036
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
7896
8037
  async function runbookList(ctx, all, query) {
@@ -7977,7 +8118,7 @@ async function runbookRemove(ctx, slug) {
7977
8118
  }
7978
8119
 
7979
8120
  // src/runbook-import.ts
7980
- var import_node_fs14 = require("fs");
8121
+ var import_node_fs15 = require("fs");
7981
8122
  var import_node_path11 = require("path");
7982
8123
  function parseRunbook(text, slug) {
7983
8124
  let rest = text;
@@ -8003,12 +8144,12 @@ function parseRunbook(text, slug) {
8003
8144
  };
8004
8145
  }
8005
8146
  function readRunbookDir(dir) {
8006
- if (!(0, import_node_fs14.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
8007
- const files = (0, import_node_fs14.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
8147
+ if (!(0, import_node_fs15.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
8148
+ const files = (0, import_node_fs15.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
8008
8149
  if (!files.length) throw new Error(`no .md files in ${dir}`);
8009
8150
  return files.map((file) => {
8010
8151
  const slug = (0, import_node_path11.basename)(file, ".md");
8011
- const parsed = parseRunbook((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(dir, file), "utf8"), slug);
8152
+ const parsed = parseRunbook((0, import_node_fs15.readFileSync)((0, import_node_path11.join)(dir, file), "utf8"), slug);
8012
8153
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
8013
8154
  });
8014
8155
  }
@@ -8080,7 +8221,7 @@ async function upsert(ctx, r, visibility) {
8080
8221
 
8081
8222
  // src/runbook-impact.ts
8082
8223
  var import_node_child_process7 = require("child_process");
8083
- var import_node_fs15 = require("fs");
8224
+ var import_node_fs16 = require("fs");
8084
8225
  var import_node_path12 = require("path");
8085
8226
 
8086
8227
  // src/runbook-impact-scan.ts
@@ -8251,9 +8392,9 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
8251
8392
  function manifestLabeller(root) {
8252
8393
  return (workspace) => {
8253
8394
  const manifest = (0, import_node_path12.join)(root, workspace, "package.json");
8254
- if (!(0, import_node_fs15.existsSync)(manifest)) return void 0;
8395
+ if (!(0, import_node_fs16.existsSync)(manifest)) return void 0;
8255
8396
  try {
8256
- const name = JSON.parse((0, import_node_fs15.readFileSync)(manifest, "utf8")).name;
8397
+ const name = JSON.parse((0, import_node_fs16.readFileSync)(manifest, "utf8")).name;
8257
8398
  return typeof name === "string" ? name : void 0;
8258
8399
  } catch {
8259
8400
  return void 0;
@@ -8320,7 +8461,7 @@ function report2(ctx, impacts) {
8320
8461
  async function runbookImpact(ctx, options, deps = {}) {
8321
8462
  const cwd = deps.cwd ?? process.cwd();
8322
8463
  const runGit = deps.runGit ?? gitRunner(cwd);
8323
- const read2 = deps.readRepoFile ?? ((path) => (0, import_node_fs15.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
8464
+ const read2 = deps.readRepoFile ?? ((path) => (0, import_node_fs16.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
8324
8465
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read2), manifestLabeller(cwd));
8325
8466
  if (!surfaces.length) {
8326
8467
  return ctx.out.log(
@@ -8332,115 +8473,6 @@ async function runbookImpact(ctx, options, deps = {}) {
8332
8473
  report2(ctx, impacts);
8333
8474
  }
8334
8475
 
8335
- // src/surface.ts
8336
- var PM_ACTIONS = {
8337
- list: {},
8338
- add: {},
8339
- create: {},
8340
- get: {},
8341
- set: {},
8342
- update: {},
8343
- status: {},
8344
- move: {},
8345
- done: {},
8346
- comment: {},
8347
- comments: {},
8348
- rm: {},
8349
- delete: {}
8350
- };
8351
- var PM_ENTITIES = Object.fromEntries(
8352
- ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
8353
- );
8354
- var COMMAND_SURFACE = {
8355
- admin: {
8356
- ai: {
8357
- show: {},
8358
- set: {},
8359
- credentials: {},
8360
- models: {},
8361
- usage: {},
8362
- audit: {},
8363
- credential: { set: {} }
8364
- }
8365
- },
8366
- app: {
8367
- archive: {},
8368
- restore: {},
8369
- export: {},
8370
- import: {},
8371
- rename: {},
8372
- "refresh-sandbox": {},
8373
- "go-live": {},
8374
- promote: {},
8375
- owners: { list: {}, add: {}, remove: {} }
8376
- },
8377
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
8378
- capabilities: {},
8379
- code: { connect: {} },
8380
- doctor: {},
8381
- help: {},
8382
- init: {},
8383
- pm: PM_ENTITIES,
8384
- provision: {},
8385
- runbook: {
8386
- ask: {},
8387
- search: {},
8388
- impact: {},
8389
- list: {},
8390
- get: {},
8391
- cat: {},
8392
- new: {},
8393
- edit: {},
8394
- comment: {},
8395
- import: {},
8396
- visibility: {},
8397
- publish: {},
8398
- archive: {},
8399
- history: {},
8400
- revert: {},
8401
- rm: {},
8402
- lint: {}
8403
- },
8404
- secrets: { push: {}, set: {}, "set-clerk-key": {} },
8405
- security: {
8406
- plan: {},
8407
- sources: {},
8408
- run: {},
8409
- status: {},
8410
- report: {},
8411
- github: { connect: {}, disconnect: {} }
8412
- },
8413
- setup: {},
8414
- skill: { install: {} },
8415
- smoke: {},
8416
- version: {},
8417
- whoami: {}
8418
- };
8419
- function acceptedAfter(path) {
8420
- let node = COMMAND_SURFACE;
8421
- for (const word of path) {
8422
- node = node?.[word];
8423
- if (!node) return [];
8424
- }
8425
- return Object.keys(node).sort();
8426
- }
8427
- function validateInvocation(words2) {
8428
- let node = COMMAND_SURFACE;
8429
- const walked = [];
8430
- for (const word of words2) {
8431
- if (Object.keys(node).length === 0) return null;
8432
- const next = node[word];
8433
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
8434
- walked.push(word);
8435
- node = next;
8436
- }
8437
- return null;
8438
- }
8439
- function describeProblem(problem) {
8440
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
8441
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
8442
- }
8443
-
8444
8476
  // src/runbook-lint.ts
8445
8477
  function invocationsIn(body) {
8446
8478
  const re = /(`|npx[^\S\n]+)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
@@ -8562,12 +8594,12 @@ async function runbookComment(ctx, slug, body) {
8562
8594
 
8563
8595
  // src/runbook-editor.ts
8564
8596
  var import_node_child_process8 = require("child_process");
8565
- var import_node_fs16 = require("fs");
8597
+ var import_node_fs17 = require("fs");
8566
8598
  var import_node_os4 = require("os");
8567
8599
  var import_node_path13 = require("path");
8568
- var import_node_process8 = __toESM(require("process"), 1);
8600
+ var import_node_process9 = __toESM(require("process"), 1);
8569
8601
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
8570
- function resolveEditor(env = import_node_process8.default.env) {
8602
+ function resolveEditor(env = import_node_process9.default.env) {
8571
8603
  for (const name of EDITOR_ENV) {
8572
8604
  const value = env[name];
8573
8605
  if (value && value.trim()) return value.trim();
@@ -8581,8 +8613,8 @@ function defaultRun(command, path) {
8581
8613
  return result.status ?? 0;
8582
8614
  }
8583
8615
  function editText(initial, slug, deps = {}) {
8584
- const env = deps.env ?? import_node_process8.default.env;
8585
- const interactive = deps.interactive ?? (() => Boolean(import_node_process8.default.stdin.isTTY));
8616
+ const env = deps.env ?? import_node_process9.default.env;
8617
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process9.default.stdin.isTTY));
8586
8618
  const editor = resolveEditor(env);
8587
8619
  if (!editor)
8588
8620
  throw new Error(
@@ -8590,16 +8622,16 @@ function editText(initial, slug, deps = {}) {
8590
8622
  );
8591
8623
  if (!interactive())
8592
8624
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
8593
- const dir = (0, import_node_fs16.mkdtempSync)((0, import_node_path13.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
8625
+ const dir = (0, import_node_fs17.mkdtempSync)((0, import_node_path13.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
8594
8626
  const file = (0, import_node_path13.join)(dir, `${slug}.md`);
8595
8627
  try {
8596
- (0, import_node_fs16.writeFileSync)(file, initial, { mode: 384 });
8628
+ (0, import_node_fs17.writeFileSync)(file, initial, { mode: 384 });
8597
8629
  const code = defaultRunOrInjected(deps)(editor, file);
8598
8630
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
8599
- const edited = (0, import_node_fs16.readFileSync)(file, "utf8");
8631
+ const edited = (0, import_node_fs17.readFileSync)(file, "utf8");
8600
8632
  return edited === initial ? null : edited;
8601
8633
  } finally {
8602
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
8634
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
8603
8635
  }
8604
8636
  }
8605
8637
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -8699,11 +8731,11 @@ var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visi
8699
8731
  var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
8700
8732
  async function loadOrDefaultConfig(configPath, action, appId) {
8701
8733
  const projectScoped = appId !== void 0;
8702
- if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs17.existsSync)((0, import_node_path14.resolve)(configPath))) {
8703
- const rootDir = import_node_process9.default.cwd();
8734
+ if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs18.existsSync)((0, import_node_path14.resolve)(configPath))) {
8735
+ const rootDir = import_node_process10.default.cwd();
8704
8736
  return {
8705
8737
  configPath,
8706
- platformUrl: import_node_process9.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
8738
+ platformUrl: import_node_process10.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
8707
8739
  rootDir,
8708
8740
  app: { id: "", name: "" },
8709
8741
  local: { tokenFile: (0, import_node_path14.join)(rootDir, ".odla/dev-token.json") }
@@ -8711,13 +8743,11 @@ async function loadOrDefaultConfig(configPath, action, appId) {
8711
8743
  }
8712
8744
  return loadProjectConfig(configPath);
8713
8745
  }
8714
- var PURE_STDOUT = /* @__PURE__ */ new Set(["get", "cat"]);
8715
8746
  async function buildContext2(parsed, deps, action) {
8716
8747
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
8717
8748
  const cfg = await loadOrDefaultConfig(configPath, action, stringOpt(parsed.options.app));
8718
8749
  const doFetch = deps.fetch ?? fetch;
8719
8750
  const out = deps.stdout ?? console;
8720
- const authOut = PURE_STDOUT.has(action) ? { log: (line) => out.error(line), error: (line) => out.error(line) } : out;
8721
8751
  const appId = stringOpt(parsed.options.app) ?? PLATFORM_SCOPE;
8722
8752
  const dryRun = parsed.options["dry-run"] === true;
8723
8753
  if (action === "import" && dryRun) {
@@ -8737,7 +8767,7 @@ async function buildContext2(parsed, deps, action) {
8737
8767
  email: stringOpt(parsed.options.email),
8738
8768
  label: `odla CLI (runbook ${action})`,
8739
8769
  fetch: doFetch,
8740
- stdout: authOut,
8770
+ stdout: out,
8741
8771
  openApprovalUrl: deps.openUrl,
8742
8772
  // Anchor the grant cache to this project, not the shell's cwd.
8743
8773
  rootDir: cfg.rootDir
@@ -8754,7 +8784,7 @@ async function buildContext2(parsed, deps, action) {
8754
8784
  open: void 0
8755
8785
  },
8756
8786
  doFetch,
8757
- authOut
8787
+ out
8758
8788
  );
8759
8789
  return {
8760
8790
  platformUrl: cfg.platformUrl,
@@ -9555,6 +9585,7 @@ function exitCodeFor(err) {
9555
9585
  }
9556
9586
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
9557
9587
  const parsed = parseArgv(argv);
9588
+ recordInvocation(parsed);
9558
9589
  const command = parsed.positionals[0] ?? "help";
9559
9590
  if (command === "version" || command === "-v" || parsed.options.version === true) {
9560
9591
  assertArgs(parsed, ["version"], 1);