@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/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  inferGitHubRepository: () => inferGitHubRepository,
62
62
  initProject: () => initProject,
63
63
  installSkill: () => installSkill,
64
+ invocationPath: () => invocationPath,
64
65
  isTerminalHostedSecurityStatus: () => isTerminalHostedSecurityStatus,
65
66
  listGitHubSecuritySources: () => listGitHubSecuritySources,
66
67
  listHostedSecurityJobs: () => listHostedSecurityJobs,
@@ -116,7 +117,7 @@ function approvalLines(prompt) {
116
117
  return lines;
117
118
  }
118
119
  function printApproval(out, prompt) {
119
- for (const line of approvalLines(prompt)) out.log(line);
120
+ for (const line of approvalLines(prompt)) out.error(line);
120
121
  }
121
122
  function reminderLines(prompt) {
122
123
  return [
@@ -336,7 +337,7 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
336
337
  }
337
338
  const cached = readJsonFile(cfg.local.tokenFile);
338
339
  if (cached?.token && cached.platform === audience && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
339
- out.log(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
340
+ out.error(`auth: using cached developer token (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
340
341
  return cached.token;
341
342
  }
342
343
  const ctx = {
@@ -353,16 +354,16 @@ async function getDeveloperToken(cfg, options, doFetch, out) {
353
354
  const { token, expiresAt } = await resumePendingHandshake(ctx, waitMs) ?? await freshHandshake(ctx, waitMs);
354
355
  clearPendingHandshake(ctx.pendingFile);
355
356
  writePrivateJson(cfg.local.tokenFile, { platform: audience, email: ctx.email, token, expiresAt });
356
- out.log(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
357
+ out.error(`auth: developer token cached (${displayPath(cfg.local.tokenFile, cfg.rootDir)})`);
357
358
  return token;
358
359
  }
359
360
  async function resumePendingHandshake(ctx, waitMs) {
360
361
  const pending = readPendingHandshake(ctx.pendingFile, ctx.audience, ctx.email);
361
362
  if (!pending) return null;
362
- ctx.out.log("");
363
- ctx.out.log(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
363
+ ctx.out.error("");
364
+ ctx.out.error(`auth: resuming pending handshake \u2014 ${approvalHint(pending)}`);
364
365
  await launchApproval(ctx, pending.approvalUrl);
365
- ctx.out.log("");
366
+ ctx.out.error("");
366
367
  const stopReminder = approvalReminder(ctx.out, pending);
367
368
  try {
368
369
  return await (0, import_db.collectToken)({
@@ -378,7 +379,7 @@ async function resumePendingHandshake(ctx, waitMs) {
378
379
  if (code === "handshake_pending") throw stillPending(pending, ctx.email);
379
380
  if (code === "handshake_expired" || code === "handshake_timeout") {
380
381
  clearPendingHandshake(ctx.pendingFile);
381
- ctx.out.log("auth: pending handshake lapsed unapproved; starting a fresh one");
382
+ ctx.out.error("auth: pending handshake lapsed unapproved; starting a fresh one");
382
383
  return null;
383
384
  }
384
385
  if (code === "handshake_denied") clearPendingHandshake(ctx.pendingFile);
@@ -436,12 +437,12 @@ async function launchApproval(ctx, approvalUrl) {
436
437
  if (ctx.browser.open) {
437
438
  try {
438
439
  await (ctx.options.openApprovalUrl ?? openUrl)(approvalUrl);
439
- 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`);
440
+ 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`);
440
441
  } catch (err) {
441
- ctx.out.log(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
442
+ ctx.out.error(`auth: could not open browser (${err instanceof Error ? err.message : String(err)})`);
442
443
  }
443
444
  } else if (ctx.browser.reason) {
444
- ctx.out.log(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
445
+ ctx.out.error(`auth: browser launch skipped (${ctx.browser.reason}) \u2014 show the human the URL above`);
445
446
  }
446
447
  }
447
448
  function stillPending(pending, email) {
@@ -566,7 +567,7 @@ async function scopedToken(platform, scope, options, doFetch, out) {
566
567
  const cache = options.cache === false ? null : readJsonFile(tokenFile);
567
568
  const cached = cache?.platform === audience ? cache.tokens?.[scope] : void 0;
568
569
  if (cached?.token && (cached.expiresAt ?? 0) > Date.now() + 6e4) {
569
- out.log(`auth: using cached ${scope} grant (${tokenFile})`);
570
+ out.error(`auth: using cached ${scope} grant (${tokenFile})`);
570
571
  return cached.token;
571
572
  }
572
573
  const email = handshakeEmail(options.email, cache?.platform === audience ? cache.email : void 0);
@@ -597,9 +598,9 @@ async function scopedToken(platform, scope, options, doFetch, out) {
597
598
  tokens[scope] = { token, expiresAt };
598
599
  if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(rootDir, ".git"))) ensureGitignore(rootDir, [tokenFile]);
599
600
  writePrivateJson(tokenFile, { platform: audience, email, tokens });
600
- out.log(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
601
+ out.error(`auth: cached ${scope} grant (${tokenFile}; mode 0600)`);
601
602
  } else {
602
- out.log(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
603
+ out.error(`auth: ${scope} grant is in memory only; its credential record remains in odla-ai/db`);
603
604
  }
604
605
  return token;
605
606
  }
@@ -7858,13 +7859,163 @@ ${env}: credentials are already saved; retry "odla-ai secrets push --env ${env}$
7858
7859
  }
7859
7860
  }
7860
7861
 
7862
+ // src/record.ts
7863
+ var import_node_fs13 = require("fs");
7864
+ var import_node_process8 = __toESM(require("process"), 1);
7865
+
7866
+ // src/surface.ts
7867
+ var PM_ACTIONS = {
7868
+ list: {},
7869
+ add: {},
7870
+ create: {},
7871
+ get: {},
7872
+ set: {},
7873
+ update: {},
7874
+ status: {},
7875
+ move: {},
7876
+ done: {},
7877
+ comment: {},
7878
+ comments: {},
7879
+ rm: {},
7880
+ delete: {}
7881
+ };
7882
+ var PM_ENTITIES = Object.fromEntries(
7883
+ ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
7884
+ );
7885
+ var COMMAND_SURFACE = {
7886
+ admin: {
7887
+ ai: {
7888
+ show: {},
7889
+ set: {},
7890
+ credentials: {},
7891
+ models: {},
7892
+ usage: {},
7893
+ audit: {},
7894
+ credential: { set: {} }
7895
+ }
7896
+ },
7897
+ app: {
7898
+ archive: {},
7899
+ restore: {},
7900
+ export: {},
7901
+ import: {},
7902
+ rename: {},
7903
+ "refresh-sandbox": {},
7904
+ "go-live": {},
7905
+ promote: {},
7906
+ owners: { list: {}, add: {}, remove: {} }
7907
+ },
7908
+ calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
7909
+ capabilities: {},
7910
+ code: { connect: {} },
7911
+ doctor: {},
7912
+ help: {},
7913
+ init: {},
7914
+ pm: PM_ENTITIES,
7915
+ provision: {},
7916
+ runbook: {
7917
+ ask: {},
7918
+ search: {},
7919
+ impact: {},
7920
+ list: {},
7921
+ get: {},
7922
+ cat: {},
7923
+ new: {},
7924
+ edit: {},
7925
+ comment: {},
7926
+ import: {},
7927
+ visibility: {},
7928
+ publish: {},
7929
+ archive: {},
7930
+ history: {},
7931
+ revert: {},
7932
+ rm: {},
7933
+ lint: {}
7934
+ },
7935
+ secrets: { push: {}, set: {}, "set-clerk-key": {} },
7936
+ security: {
7937
+ plan: {},
7938
+ sources: {},
7939
+ run: {},
7940
+ status: {},
7941
+ report: {},
7942
+ github: { connect: {}, disconnect: {} }
7943
+ },
7944
+ setup: {},
7945
+ skill: { install: {} },
7946
+ smoke: {},
7947
+ version: {},
7948
+ whoami: {}
7949
+ };
7950
+ function acceptedAfter(path) {
7951
+ let node = COMMAND_SURFACE;
7952
+ for (const word of path) {
7953
+ node = node?.[word];
7954
+ if (!node) return [];
7955
+ }
7956
+ return Object.keys(node).sort();
7957
+ }
7958
+ function validateInvocation(words2) {
7959
+ let node = COMMAND_SURFACE;
7960
+ const walked = [];
7961
+ for (const word of words2) {
7962
+ if (Object.keys(node).length === 0) return null;
7963
+ const next = node[word];
7964
+ if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
7965
+ walked.push(word);
7966
+ node = next;
7967
+ }
7968
+ return null;
7969
+ }
7970
+ function describeProblem(problem) {
7971
+ const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
7972
+ return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
7973
+ }
7974
+ function invocationPath(words2) {
7975
+ let node = COMMAND_SURFACE;
7976
+ const path = [];
7977
+ for (const word of words2) {
7978
+ const next = node[word];
7979
+ if (!next) break;
7980
+ path.push(word);
7981
+ node = next;
7982
+ if (Object.keys(node).length === 0) break;
7983
+ }
7984
+ return path;
7985
+ }
7986
+ function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
7987
+ const paths = [];
7988
+ for (const [word, child] of Object.entries(node)) {
7989
+ const path = [...prefix, word];
7990
+ paths.push(path);
7991
+ paths.push(...surfacePaths(child, path));
7992
+ }
7993
+ return paths;
7994
+ }
7995
+
7996
+ // src/record.ts
7997
+ function recordInvocation(parsed) {
7998
+ const file = import_node_process8.default.env.ODLA_CLI_RECORD;
7999
+ if (!file) return;
8000
+ try {
8001
+ const entry = {
8002
+ path: invocationPath(parsed.positionals),
8003
+ options: Object.keys(parsed.options).sort()
8004
+ };
8005
+ if (!entry.path.length) return;
8006
+ (0, import_node_fs13.appendFileSync)(file, `${JSON.stringify(entry)}
8007
+ `);
8008
+ } catch {
8009
+ }
8010
+ }
8011
+
7861
8012
  // src/runbook-command.ts
7862
- var import_node_fs17 = require("fs");
8013
+ var import_node_fs18 = require("fs");
7863
8014
  var import_node_path14 = require("path");
7864
- var import_node_process9 = __toESM(require("process"), 1);
8015
+ var import_node_process10 = __toESM(require("process"), 1);
7865
8016
 
7866
8017
  // src/runbook-actions.ts
7867
- var import_node_fs13 = require("fs");
8018
+ var import_node_fs14 = require("fs");
7868
8019
 
7869
8020
  // src/runbook-requires.ts
7870
8021
  var SPEC = /^(@?[\w./-]+?)@(\d+\.\d+\.\d+(?:[\w.-]*)?)$/;
@@ -7949,7 +8100,7 @@ async function bySlug(ctx, slug) {
7949
8100
  function readBody(file, inline) {
7950
8101
  if (inline !== void 0) return inline;
7951
8102
  if (file === void 0) throw new Error("supply the new text with --file <path>, --file - (stdin), or --body");
7952
- return (0, import_node_fs13.readFileSync)(file === "-" ? 0 : file, "utf8");
8103
+ return (0, import_node_fs14.readFileSync)(file === "-" ? 0 : file, "utf8");
7953
8104
  }
7954
8105
  var stamp = (ms) => ms ? new Date(ms).toISOString().slice(0, 16).replace("T", " ") : "";
7955
8106
  async function runbookList(ctx, all, query) {
@@ -8036,7 +8187,7 @@ async function runbookRemove(ctx, slug) {
8036
8187
  }
8037
8188
 
8038
8189
  // src/runbook-import.ts
8039
- var import_node_fs14 = require("fs");
8190
+ var import_node_fs15 = require("fs");
8040
8191
  var import_node_path11 = require("path");
8041
8192
  function parseRunbook(text, slug) {
8042
8193
  let rest = text;
@@ -8062,12 +8213,12 @@ function parseRunbook(text, slug) {
8062
8213
  };
8063
8214
  }
8064
8215
  function readRunbookDir(dir) {
8065
- if (!(0, import_node_fs14.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
8066
- const files = (0, import_node_fs14.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
8216
+ if (!(0, import_node_fs15.statSync)(dir, { throwIfNoEntry: false })?.isDirectory()) throw new Error(`not a directory: ${dir}`);
8217
+ const files = (0, import_node_fs15.readdirSync)(dir).filter((f) => f.endsWith(".md")).sort();
8067
8218
  if (!files.length) throw new Error(`no .md files in ${dir}`);
8068
8219
  return files.map((file) => {
8069
8220
  const slug = (0, import_node_path11.basename)(file, ".md");
8070
- const parsed = parseRunbook((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(dir, file), "utf8"), slug);
8221
+ const parsed = parseRunbook((0, import_node_fs15.readFileSync)((0, import_node_path11.join)(dir, file), "utf8"), slug);
8071
8222
  return { file, slug, ...parsed, words: parsed.body.split(/\s+/).filter(Boolean).length };
8072
8223
  });
8073
8224
  }
@@ -8139,7 +8290,7 @@ async function upsert(ctx, r, visibility) {
8139
8290
 
8140
8291
  // src/runbook-impact.ts
8141
8292
  var import_node_child_process7 = require("child_process");
8142
- var import_node_fs15 = require("fs");
8293
+ var import_node_fs16 = require("fs");
8143
8294
  var import_node_path12 = require("path");
8144
8295
 
8145
8296
  // src/runbook-impact-scan.ts
@@ -8310,9 +8461,9 @@ ${body.split("\n").map((line) => `+${line}`).join("\n")}
8310
8461
  function manifestLabeller(root) {
8311
8462
  return (workspace) => {
8312
8463
  const manifest = (0, import_node_path12.join)(root, workspace, "package.json");
8313
- if (!(0, import_node_fs15.existsSync)(manifest)) return void 0;
8464
+ if (!(0, import_node_fs16.existsSync)(manifest)) return void 0;
8314
8465
  try {
8315
- const name = JSON.parse((0, import_node_fs15.readFileSync)(manifest, "utf8")).name;
8466
+ const name = JSON.parse((0, import_node_fs16.readFileSync)(manifest, "utf8")).name;
8316
8467
  return typeof name === "string" ? name : void 0;
8317
8468
  } catch {
8318
8469
  return void 0;
@@ -8379,7 +8530,7 @@ function report2(ctx, impacts) {
8379
8530
  async function runbookImpact(ctx, options, deps = {}) {
8380
8531
  const cwd = deps.cwd ?? process.cwd();
8381
8532
  const runGit = deps.runGit ?? gitRunner(cwd);
8382
- const read2 = deps.readRepoFile ?? ((path) => (0, import_node_fs15.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
8533
+ const read2 = deps.readRepoFile ?? ((path) => (0, import_node_fs16.readFileSync)((0, import_node_path12.join)(cwd, path), "utf8"));
8383
8534
  const surfaces = changedSurfaces(collectDiff(runGit, options.base, read2), manifestLabeller(cwd));
8384
8535
  if (!surfaces.length) {
8385
8536
  return ctx.out.log(
@@ -8391,124 +8542,6 @@ async function runbookImpact(ctx, options, deps = {}) {
8391
8542
  report2(ctx, impacts);
8392
8543
  }
8393
8544
 
8394
- // src/surface.ts
8395
- var PM_ACTIONS = {
8396
- list: {},
8397
- add: {},
8398
- create: {},
8399
- get: {},
8400
- set: {},
8401
- update: {},
8402
- status: {},
8403
- move: {},
8404
- done: {},
8405
- comment: {},
8406
- comments: {},
8407
- rm: {},
8408
- delete: {}
8409
- };
8410
- var PM_ENTITIES = Object.fromEntries(
8411
- ["goal", "conformance", "task", "kanban", "decision", "bug"].map((entity) => [entity, PM_ACTIONS])
8412
- );
8413
- var COMMAND_SURFACE = {
8414
- admin: {
8415
- ai: {
8416
- show: {},
8417
- set: {},
8418
- credentials: {},
8419
- models: {},
8420
- usage: {},
8421
- audit: {},
8422
- credential: { set: {} }
8423
- }
8424
- },
8425
- app: {
8426
- archive: {},
8427
- restore: {},
8428
- export: {},
8429
- import: {},
8430
- rename: {},
8431
- "refresh-sandbox": {},
8432
- "go-live": {},
8433
- promote: {},
8434
- owners: { list: {}, add: {}, remove: {} }
8435
- },
8436
- calendar: { status: {}, calendars: {}, connect: {}, disconnect: {} },
8437
- capabilities: {},
8438
- code: { connect: {} },
8439
- doctor: {},
8440
- help: {},
8441
- init: {},
8442
- pm: PM_ENTITIES,
8443
- provision: {},
8444
- runbook: {
8445
- ask: {},
8446
- search: {},
8447
- impact: {},
8448
- list: {},
8449
- get: {},
8450
- cat: {},
8451
- new: {},
8452
- edit: {},
8453
- comment: {},
8454
- import: {},
8455
- visibility: {},
8456
- publish: {},
8457
- archive: {},
8458
- history: {},
8459
- revert: {},
8460
- rm: {},
8461
- lint: {}
8462
- },
8463
- secrets: { push: {}, set: {}, "set-clerk-key": {} },
8464
- security: {
8465
- plan: {},
8466
- sources: {},
8467
- run: {},
8468
- status: {},
8469
- report: {},
8470
- github: { connect: {}, disconnect: {} }
8471
- },
8472
- setup: {},
8473
- skill: { install: {} },
8474
- smoke: {},
8475
- version: {},
8476
- whoami: {}
8477
- };
8478
- function acceptedAfter(path) {
8479
- let node = COMMAND_SURFACE;
8480
- for (const word of path) {
8481
- node = node?.[word];
8482
- if (!node) return [];
8483
- }
8484
- return Object.keys(node).sort();
8485
- }
8486
- function validateInvocation(words2) {
8487
- let node = COMMAND_SURFACE;
8488
- const walked = [];
8489
- for (const word of words2) {
8490
- if (Object.keys(node).length === 0) return null;
8491
- const next = node[word];
8492
- if (!next) return { validPrefix: walked.join(" "), word, accepted: Object.keys(node).sort() };
8493
- walked.push(word);
8494
- node = next;
8495
- }
8496
- return null;
8497
- }
8498
- function describeProblem(problem) {
8499
- const where = problem.validPrefix ? `after "${problem.validPrefix}"` : "as a command";
8500
- return `"${problem.word}" is not accepted ${where} \u2014 try: ${problem.accepted.join(", ")}`;
8501
- }
8502
- function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
8503
- const paths = [];
8504
- for (const [word, child] of Object.entries(node)) {
8505
- const path = [...prefix, word];
8506
- paths.push(path);
8507
- paths.push(...surfacePaths(child, path));
8508
- }
8509
- return paths;
8510
- }
8511
-
8512
8545
  // src/runbook-lint.ts
8513
8546
  function invocationsIn(body) {
8514
8547
  const re = /(`|npx[^\S\n]+)?(?:@odla-ai\/cli(?:@[\w.-]+)?|odla-ai)((?:[^\S\n]+[a-z][\w-]*)+)/g;
@@ -8630,12 +8663,12 @@ async function runbookComment(ctx, slug, body) {
8630
8663
 
8631
8664
  // src/runbook-editor.ts
8632
8665
  var import_node_child_process8 = require("child_process");
8633
- var import_node_fs16 = require("fs");
8666
+ var import_node_fs17 = require("fs");
8634
8667
  var import_node_os4 = require("os");
8635
8668
  var import_node_path13 = require("path");
8636
- var import_node_process8 = __toESM(require("process"), 1);
8669
+ var import_node_process9 = __toESM(require("process"), 1);
8637
8670
  var EDITOR_ENV = ["ODLA_EDITOR", "VISUAL", "EDITOR"];
8638
- function resolveEditor(env = import_node_process8.default.env) {
8671
+ function resolveEditor(env = import_node_process9.default.env) {
8639
8672
  for (const name of EDITOR_ENV) {
8640
8673
  const value = env[name];
8641
8674
  if (value && value.trim()) return value.trim();
@@ -8649,8 +8682,8 @@ function defaultRun(command, path) {
8649
8682
  return result.status ?? 0;
8650
8683
  }
8651
8684
  function editText(initial, slug, deps = {}) {
8652
- const env = deps.env ?? import_node_process8.default.env;
8653
- const interactive = deps.interactive ?? (() => Boolean(import_node_process8.default.stdin.isTTY));
8685
+ const env = deps.env ?? import_node_process9.default.env;
8686
+ const interactive = deps.interactive ?? (() => Boolean(import_node_process9.default.stdin.isTTY));
8654
8687
  const editor = resolveEditor(env);
8655
8688
  if (!editor)
8656
8689
  throw new Error(
@@ -8658,16 +8691,16 @@ function editText(initial, slug, deps = {}) {
8658
8691
  );
8659
8692
  if (!interactive())
8660
8693
  throw new Error(`cannot open an editor without a terminal \u2014 pass --file <path> or --body "\u2026" instead`);
8661
- const dir = (0, import_node_fs16.mkdtempSync)((0, import_node_path13.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
8694
+ const dir = (0, import_node_fs17.mkdtempSync)((0, import_node_path13.join)((0, import_node_os4.tmpdir)(), "odla-runbook-"));
8662
8695
  const file = (0, import_node_path13.join)(dir, `${slug}.md`);
8663
8696
  try {
8664
- (0, import_node_fs16.writeFileSync)(file, initial, { mode: 384 });
8697
+ (0, import_node_fs17.writeFileSync)(file, initial, { mode: 384 });
8665
8698
  const code = defaultRunOrInjected(deps)(editor, file);
8666
8699
  if (code !== 0) throw new Error(`editor "${editor}" exited with ${code}; nothing was written`);
8667
- const edited = (0, import_node_fs16.readFileSync)(file, "utf8");
8700
+ const edited = (0, import_node_fs17.readFileSync)(file, "utf8");
8668
8701
  return edited === initial ? null : edited;
8669
8702
  } finally {
8670
- (0, import_node_fs16.rmSync)(dir, { recursive: true, force: true });
8703
+ (0, import_node_fs17.rmSync)(dir, { recursive: true, force: true });
8671
8704
  }
8672
8705
  }
8673
8706
  var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
@@ -8767,11 +8800,11 @@ var WRITES = /* @__PURE__ */ new Set(["new", "edit", "publish", "archive", "visi
8767
8800
  var CONFIG_FREE = /* @__PURE__ */ new Set(["list", "search", "ask", "get", "cat", "history", "impact", "lint"]);
8768
8801
  async function loadOrDefaultConfig(configPath, action, appId) {
8769
8802
  const projectScoped = appId !== void 0;
8770
- if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs17.existsSync)((0, import_node_path14.resolve)(configPath))) {
8771
- const rootDir = import_node_process9.default.cwd();
8803
+ if (!projectScoped && CONFIG_FREE.has(action) && !(0, import_node_fs18.existsSync)((0, import_node_path14.resolve)(configPath))) {
8804
+ const rootDir = import_node_process10.default.cwd();
8772
8805
  return {
8773
8806
  configPath,
8774
- platformUrl: import_node_process9.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
8807
+ platformUrl: import_node_process10.default.env.ODLA_PLATFORM_URL ?? "https://odla.ai",
8775
8808
  rootDir,
8776
8809
  app: { id: "", name: "" },
8777
8810
  local: { tokenFile: (0, import_node_path14.join)(rootDir, ".odla/dev-token.json") }
@@ -8779,13 +8812,11 @@ async function loadOrDefaultConfig(configPath, action, appId) {
8779
8812
  }
8780
8813
  return loadProjectConfig(configPath);
8781
8814
  }
8782
- var PURE_STDOUT = /* @__PURE__ */ new Set(["get", "cat"]);
8783
8815
  async function buildContext2(parsed, deps, action) {
8784
8816
  const configPath = stringOpt(parsed.options.config) ?? "odla.config.mjs";
8785
8817
  const cfg = await loadOrDefaultConfig(configPath, action, stringOpt(parsed.options.app));
8786
8818
  const doFetch = deps.fetch ?? fetch;
8787
8819
  const out = deps.stdout ?? console;
8788
- const authOut = PURE_STDOUT.has(action) ? { log: (line) => out.error(line), error: (line) => out.error(line) } : out;
8789
8820
  const appId = stringOpt(parsed.options.app) ?? PLATFORM_SCOPE;
8790
8821
  const dryRun = parsed.options["dry-run"] === true;
8791
8822
  if (action === "import" && dryRun) {
@@ -8805,7 +8836,7 @@ async function buildContext2(parsed, deps, action) {
8805
8836
  email: stringOpt(parsed.options.email),
8806
8837
  label: `odla CLI (runbook ${action})`,
8807
8838
  fetch: doFetch,
8808
- stdout: authOut,
8839
+ stdout: out,
8809
8840
  openApprovalUrl: deps.openUrl,
8810
8841
  // Anchor the grant cache to this project, not the shell's cwd.
8811
8842
  rootDir: cfg.rootDir
@@ -8822,7 +8853,7 @@ async function buildContext2(parsed, deps, action) {
8822
8853
  open: void 0
8823
8854
  },
8824
8855
  doFetch,
8825
- authOut
8856
+ out
8826
8857
  );
8827
8858
  return {
8828
8859
  platformUrl: cfg.platformUrl,
@@ -9634,6 +9665,7 @@ function exitCodeFor(err) {
9634
9665
  }
9635
9666
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
9636
9667
  const parsed = parseArgv(argv);
9668
+ recordInvocation(parsed);
9637
9669
  const command = parsed.positionals[0] ?? "help";
9638
9670
  if (command === "version" || command === "-v" || parsed.options.version === true) {
9639
9671
  assertArgs(parsed, ["version"], 1);
@@ -9776,6 +9808,7 @@ async function calendarCommand(parsed, dependencies) {
9776
9808
  inferGitHubRepository,
9777
9809
  initProject,
9778
9810
  installSkill,
9811
+ invocationPath,
9779
9812
  isTerminalHostedSecurityStatus,
9780
9813
  listGitHubSecuritySources,
9781
9814
  listHostedSecurityJobs,