@odla-ai/cli 0.21.0 → 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,
@@ -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") }
@@ -9632,6 +9665,7 @@ function exitCodeFor(err) {
9632
9665
  }
9633
9666
  async function runCli(argv = process.argv.slice(2), dependencies = {}) {
9634
9667
  const parsed = parseArgv(argv);
9668
+ recordInvocation(parsed);
9635
9669
  const command = parsed.positionals[0] ?? "help";
9636
9670
  if (command === "version" || command === "-v" || parsed.options.version === true) {
9637
9671
  assertArgs(parsed, ["version"], 1);
@@ -9774,6 +9808,7 @@ async function calendarCommand(parsed, dependencies) {
9774
9808
  inferGitHubRepository,
9775
9809
  initProject,
9776
9810
  installSkill,
9811
+ invocationPath,
9777
9812
  isTerminalHostedSecurityStatus,
9778
9813
  listGitHubSecuritySources,
9779
9814
  listHostedSecurityJobs,