@open-code-review/cli 2.2.0 → 2.2.1

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.
Files changed (57) hide show
  1. package/dist/dashboard/client/assets/{_basePickBy-BBPb8BJA.js → _basePickBy-BAlGnwHG.js} +1 -1
  2. package/dist/dashboard/client/assets/{_baseUniq-CFHdos6T.js → _baseUniq-CoauyOeL.js} +1 -1
  3. package/dist/dashboard/client/assets/{arc-BKGGWA2F.js → arc-DtS0aHfP.js} +1 -1
  4. package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-B_ovNjX1.js → architectureDiagram-VXUJARFQ-CnWmtRTh.js} +1 -1
  5. package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-C2M-avVp.js → blockDiagram-VD42YOAC-DgPp4oGV.js} +1 -1
  6. package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-BtOBpAzH.js → c4Diagram-YG6GDRKO--LV4qQaE.js} +1 -1
  7. package/dist/dashboard/client/assets/channel-BU2129fl.js +1 -0
  8. package/dist/dashboard/client/assets/{chunk-4BX2VUAB-Cz2EbHPl.js → chunk-4BX2VUAB-BRglpc7Z.js} +1 -1
  9. package/dist/dashboard/client/assets/{chunk-55IACEB6-C8xpXw9G.js → chunk-55IACEB6-Bgx06_CV.js} +1 -1
  10. package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BSRfOovX.js → chunk-B4BG7PRW-D6HN3Yiy.js} +1 -1
  11. package/dist/dashboard/client/assets/{chunk-DI55MBZ5-CEUbYQWn.js → chunk-DI55MBZ5-NH9EgN9T.js} +1 -1
  12. package/dist/dashboard/client/assets/{chunk-FMBD7UC4-5xWP6GRj.js → chunk-FMBD7UC4-xriO6WNP.js} +1 -1
  13. package/dist/dashboard/client/assets/{chunk-QN33PNHL-DfNCVcy8.js → chunk-QN33PNHL-CV1h6_Zl.js} +1 -1
  14. package/dist/dashboard/client/assets/{chunk-QZHKN3VN--OdToKKu.js → chunk-QZHKN3VN-CV4VzxNq.js} +1 -1
  15. package/dist/dashboard/client/assets/{chunk-TZMSLE5B-B_0K0Qso.js → chunk-TZMSLE5B-isdklocW.js} +1 -1
  16. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-CVftFGiR.js +1 -0
  17. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-CVftFGiR.js +1 -0
  18. package/dist/dashboard/client/assets/clone-DC6LEEC5.js +1 -0
  19. package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-Cc_Dmnxz.js → cose-bilkent-S5V4N54A-CCzlFSJf.js} +1 -1
  20. package/dist/dashboard/client/assets/{dagre-6UL2VRFP-DaAfvUXU.js → dagre-6UL2VRFP-DVN3PkjZ.js} +1 -1
  21. package/dist/dashboard/client/assets/{diagram-PSM6KHXK-7idwN0rC.js → diagram-PSM6KHXK-SzJVoSsb.js} +1 -1
  22. package/dist/dashboard/client/assets/{diagram-QEK2KX5R-D9j9H13n.js → diagram-QEK2KX5R-CgGn7ts-.js} +1 -1
  23. package/dist/dashboard/client/assets/{diagram-S2PKOQOG-SMF5SB0K.js → diagram-S2PKOQOG-Bz1ukSx8.js} +1 -1
  24. package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-EVJ4Qa2F.js → erDiagram-Q2GNP2WA-CpstUTMZ.js} +1 -1
  25. package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-tZ7SFE77.js → flowDiagram-NV44I4VS-aYVydGhp.js} +1 -1
  26. package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-DFSqguY7.js → ganttDiagram-JELNMOA3-Cb2DUSRk.js} +1 -1
  27. package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-CqHdP3HE.js → gitGraphDiagram-V2S2FVAM-BUOnwA2w.js} +1 -1
  28. package/dist/dashboard/client/assets/{graph-C0XnkNkk.js → graph-4X5ddhLp.js} +1 -1
  29. package/dist/dashboard/client/assets/index-CKWqYAfu.js +581 -0
  30. package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-DlXZo9U2.js → infoDiagram-HS3SLOUP-BlMqcrwm.js} +1 -1
  31. package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CgC8_7eN.js → journeyDiagram-XKPGCS4Q-DF2ew7ju.js} +1 -1
  32. package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-BMAw_jNp.js → kanban-definition-3W4ZIXB7-BKQMx0-n.js} +1 -1
  33. package/dist/dashboard/client/assets/{layout-XjM3Q-ka.js → layout-DNcn2g9w.js} +1 -1
  34. package/dist/dashboard/client/assets/{linear-CMUrrr1X.js → linear-Bqy9gvqb.js} +1 -1
  35. package/dist/dashboard/client/assets/{mermaid-renderer-D2jYNs7K.js → mermaid-renderer-dJ71wgld.js} +4 -4
  36. package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-CL4hv-vg.js → mindmap-definition-VGOIOE7T-BARc8sqJ.js} +1 -1
  37. package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-DTqv-1h1.js → pieDiagram-ADFJNKIX-CULlNZTd.js} +1 -1
  38. package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-BpFlSW9N.js → quadrantDiagram-AYHSOK5B-BJEZPVe9.js} +1 -1
  39. package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BqYqqXL4.js → requirementDiagram-UZGBJVZJ-BhMsmUIs.js} +1 -1
  40. package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-kEI9kntR.js → sankeyDiagram-TZEHDZUN-BYbNgogG.js} +1 -1
  41. package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-Cnu_1j-N.js → sequenceDiagram-WL72ISMW-MoM_NwWk.js} +1 -1
  42. package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BoC-rqoG.js → stateDiagram-FKZM4ZOC-ditrlbM3.js} +1 -1
  43. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-SqoG2LCn.js +1 -0
  44. package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-CXMWuzDL.js → timeline-definition-IT6M3QCI-DOAJyjuz.js} +1 -1
  45. package/dist/dashboard/client/assets/{treemap-GDKQZRPO-o9ZFgpbJ.js → treemap-GDKQZRPO-BBJkjnJl.js} +1 -1
  46. package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CfIuUpeA.js → xychartDiagram-PRI3JC2R-CPW4s5vm.js} +1 -1
  47. package/dist/dashboard/client/index.html +1 -1
  48. package/dist/dashboard/server.js +157 -153
  49. package/dist/index.js +144 -68
  50. package/dist/lib/models.js +125 -50
  51. package/package.json +2 -2
  52. package/dist/dashboard/client/assets/channel-rgw7C1e7.js +0 -1
  53. package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +0 -1
  54. package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +0 -1
  55. package/dist/dashboard/client/assets/clone-Cz7hswqi.js +0 -1
  56. package/dist/dashboard/client/assets/index-C3NEq704.js +0 -571
  57. package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +0 -1
package/dist/index.js CHANGED
@@ -16034,6 +16034,12 @@ function execBinary(binary, args, opts) {
16034
16034
  shell: isWindows
16035
16035
  });
16036
16036
  }
16037
+ async function execBinaryAsync(binary, args, opts) {
16038
+ return execFilePromise(binary, args, {
16039
+ ...opts,
16040
+ shell: isWindows
16041
+ });
16042
+ }
16037
16043
  function isProcessAlive(pid) {
16038
16044
  try {
16039
16045
  process.kill(pid, 0);
@@ -30086,7 +30092,7 @@ ${hint}
30086
30092
  }
30087
30093
 
30088
30094
  // src/lib/version.ts
30089
- var CLI_VERSION = true ? "2.2.0" : createRequire(import.meta.url)("../../package.json").version;
30095
+ var CLI_VERSION = true ? "2.2.1" : createRequire(import.meta.url)("../../package.json").version;
30090
30096
 
30091
30097
  // src/lib/deps.ts
30092
30098
  init_src();
@@ -35520,23 +35526,62 @@ var sessionCommand = new Command("session").description("Manage agent-CLI sessio
35520
35526
 
35521
35527
  // src/lib/models.ts
35522
35528
  init_src();
35523
- var BUNDLED_CLAUDE_MODELS = [
35524
- { id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
35525
- { id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
35526
- { id: "claude-haiku-4-5-20251001", displayName: "Claude Haiku 4.5" }
35527
- ];
35528
- var BUNDLED_OPENCODE_MODELS = [
35529
- { id: "anthropic/claude-opus-4-7", provider: "anthropic" },
35530
- { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
35531
- { id: "anthropic/claude-haiku-4-5-20251001", provider: "anthropic" }
35532
- ];
35533
- function detectActiveVendor() {
35534
- for (const vendor of ["claude", "opencode"]) {
35529
+ function parseOpenCodeModelList(stdout) {
35530
+ const models = [];
35531
+ for (const rawLine of stdout.split(/\r?\n/)) {
35532
+ const line = rawLine.trim();
35533
+ if (!/^[^\s:]+\/\S+$/.test(line)) continue;
35534
+ const provider = line.slice(0, line.indexOf("/"));
35535
+ models.push({ id: line, provider });
35536
+ }
35537
+ return models.length > 0 ? models : null;
35538
+ }
35539
+ var VENDOR_MODEL_STRATEGIES = {
35540
+ claude: {
35541
+ displayName: "Claude Code",
35542
+ native: {
35543
+ // Verified against Claude Code 2.1.x: the CLI has no model-listing
35544
+ // subcommand (`claude models --json` → "unknown option"). Revisit if
35545
+ // a future release adds one.
35546
+ unavailableReason: "Claude Code does not provide a model-listing command; showing its documented model aliases instead"
35547
+ },
35548
+ // Vendor-documented aliases that always track the latest generation —
35549
+ // dated ids here would go stale by construction (the exact bug class of
35550
+ // issue #39). Pinned dated ids remain available via free-text entry.
35551
+ bundled: [
35552
+ { id: "opus", displayName: "Claude Opus (latest)" },
35553
+ { id: "sonnet", displayName: "Claude Sonnet (latest)" },
35554
+ { id: "haiku", displayName: "Claude Haiku (latest)" }
35555
+ ]
35556
+ },
35557
+ opencode: {
35558
+ displayName: "OpenCode",
35559
+ native: {
35560
+ // Plain `opencode models` — newline-delimited ids. (`--json` is not a
35561
+ // real flag, and `--verbose` interleaves JSON metadata blocks that
35562
+ // defeat line parsing.)
35563
+ args: ["models"],
35564
+ parse: parseOpenCodeModelList
35565
+ },
35566
+ bundled: [
35567
+ { id: "anthropic/claude-opus-4-8", provider: "anthropic" },
35568
+ { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
35569
+ { id: "anthropic/claude-haiku-4-5", provider: "anthropic" }
35570
+ ]
35571
+ }
35572
+ };
35573
+ var SUPPORTED_VENDORS = Object.keys(
35574
+ VENDOR_MODEL_STRATEGIES
35575
+ );
35576
+ function isModelVendor(value) {
35577
+ return Object.hasOwn(VENDOR_MODEL_STRATEGIES, value);
35578
+ }
35579
+ async function detectActiveVendor() {
35580
+ for (const vendor of SUPPORTED_VENDORS) {
35535
35581
  try {
35536
- execBinary(vendor, ["--version"], {
35582
+ await execBinaryAsync(vendor, ["--version"], {
35537
35583
  encoding: "utf-8",
35538
- timeout: 3e3,
35539
- stdio: ["ignore", "pipe", "ignore"]
35584
+ timeout: 3e3
35540
35585
  });
35541
35586
  return vendor;
35542
35587
  } catch {
@@ -35544,68 +35589,97 @@ function detectActiveVendor() {
35544
35589
  }
35545
35590
  return null;
35546
35591
  }
35547
- function tryNativeEnumeration(vendor) {
35592
+ function describeProbeFailure(vendor, args, err) {
35593
+ const command = `${vendor} ${args.join(" ")}`;
35594
+ const e = err;
35595
+ if (e.code === "ENOENT") {
35596
+ return `\`${vendor}\` is not installed or not on PATH`;
35597
+ }
35598
+ if (e.killed) {
35599
+ return `\`${command}\` timed out or exceeded output limits`;
35600
+ }
35601
+ const stderr = typeof e.stderr === "string" ? e.stderr.trim() : "";
35602
+ const firstLine = (stderr.split(/\r?\n/)[0] ?? "").replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
35603
+ const detail = firstLine ? `: ${firstLine}` : "";
35604
+ const exit = typeof e.code === "number" ? ` with exit code ${e.code}` : "";
35605
+ return `\`${command}\` failed${exit}${detail}`;
35606
+ }
35607
+ async function tryNativeEnumeration(vendor, probe) {
35608
+ let stdout;
35548
35609
  try {
35549
- const output = execBinary(vendor, ["models", "--json"], {
35610
+ const result = await execBinaryAsync(vendor, probe.args, {
35550
35611
  encoding: "utf-8",
35551
- timeout: 5e3,
35552
- stdio: ["ignore", "pipe", "ignore"]
35612
+ timeout: 5e3
35553
35613
  });
35554
- const parsed = JSON.parse(output);
35555
- if (!Array.isArray(parsed)) return null;
35556
- const models = [];
35557
- for (const item of parsed) {
35558
- if (typeof item === "string") {
35559
- models.push({ id: item });
35560
- } else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
35561
- const obj = item;
35562
- const desc = { id: obj.id };
35563
- if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
35564
- if (typeof obj.provider === "string") desc.provider = obj.provider;
35565
- if (Array.isArray(obj.tags)) {
35566
- desc.tags = obj.tags.filter((t) => typeof t === "string");
35567
- }
35568
- models.push(desc);
35569
- }
35570
- }
35571
- return models.length > 0 ? models : null;
35572
- } catch {
35573
- return null;
35614
+ stdout = result.stdout;
35615
+ } catch (err) {
35616
+ return { models: null, reason: describeProbeFailure(vendor, probe.args, err) };
35574
35617
  }
35618
+ const models = probe.parse(stdout);
35619
+ if (!models) {
35620
+ return {
35621
+ models: null,
35622
+ reason: `\`${vendor} ${probe.args.join(" ")}\` output did not contain any model identifiers`
35623
+ };
35624
+ }
35625
+ return { models };
35575
35626
  }
35576
- function bundledForVendor(vendor) {
35577
- if (vendor === "claude") return BUNDLED_CLAUDE_MODELS;
35578
- return BUNDLED_OPENCODE_MODELS;
35579
- }
35580
- function listModelsForVendor(vendor) {
35581
- const native = tryNativeEnumeration(vendor);
35582
- if (native) {
35583
- return { vendor, source: "native", models: native };
35627
+ var SUCCESS_TTL_MS = 6e4;
35628
+ var FAILURE_TTL_MS = 1e4;
35629
+ var cache = /* @__PURE__ */ new Map();
35630
+ async function listModelsForVendor(vendor) {
35631
+ const cached = cache.get(vendor);
35632
+ if (cached && cached.expiresAt > Date.now()) {
35633
+ return cached.result;
35634
+ }
35635
+ const strategy = VENDOR_MODEL_STRATEGIES[vendor];
35636
+ if (!strategy) {
35637
+ throw new Error(`Unknown vendor: ${vendor}`);
35584
35638
  }
35585
- return { vendor, source: "bundled", models: bundledForVendor(vendor) };
35639
+ let result;
35640
+ if ("unavailableReason" in strategy.native) {
35641
+ result = {
35642
+ vendor,
35643
+ source: "bundled",
35644
+ models: strategy.bundled,
35645
+ nativeUnavailableReason: strategy.native.unavailableReason
35646
+ };
35647
+ } else {
35648
+ const native = await tryNativeEnumeration(vendor, strategy.native);
35649
+ result = native.models ? { vendor, source: "native", models: native.models } : {
35650
+ vendor,
35651
+ source: "bundled",
35652
+ models: strategy.bundled,
35653
+ nativeUnavailableReason: native.reason
35654
+ };
35655
+ }
35656
+ const ttl = result.source === "native" ? SUCCESS_TTL_MS : FAILURE_TTL_MS;
35657
+ cache.set(vendor, { result, expiresAt: Date.now() + ttl });
35658
+ return result;
35586
35659
  }
35587
35660
 
35588
35661
  // src/commands/models.ts
35589
- var listSubcommand2 = new Command("list").description("List models the active AI CLI is willing to accept").option(
35590
- "--vendor <vendor>",
35591
- "Override autodetection (claude | opencode)"
35592
- ).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
35662
+ var vendorList = SUPPORTED_VENDORS.join(" | ");
35663
+ var listSubcommand2 = new Command("list").description("List models the active AI CLI is willing to accept").option("--vendor <vendor>", `Override autodetection (${vendorList})`).option("--json", "Emit JSON for programmatic consumption").action(async (options) => {
35593
35664
  let vendor;
35594
35665
  if (options.vendor) {
35595
- if (options.vendor !== "claude" && options.vendor !== "opencode") {
35666
+ const requested = options.vendor.toLowerCase();
35667
+ if (!isModelVendor(requested)) {
35596
35668
  console.error(
35597
35669
  source_default.red(
35598
- `Invalid --vendor: "${options.vendor}". Must be "claude" or "opencode".`
35670
+ `Invalid --vendor: "${options.vendor}". Must be one of: ${vendorList}.`
35599
35671
  )
35600
35672
  );
35601
35673
  process.exit(1);
35602
35674
  }
35603
- vendor = options.vendor;
35675
+ vendor = requested;
35604
35676
  } else {
35605
- vendor = detectActiveVendor();
35677
+ vendor = await detectActiveVendor();
35606
35678
  if (!vendor) {
35607
35679
  if (options.json) {
35608
- console.log("[]");
35680
+ console.log(
35681
+ JSON.stringify({ vendor: null, source: null, models: [] }, null, 2)
35682
+ );
35609
35683
  return;
35610
35684
  }
35611
35685
  console.error(
@@ -35616,16 +35690,18 @@ var listSubcommand2 = new Command("list").description("List models the active AI
35616
35690
  process.exit(1);
35617
35691
  }
35618
35692
  }
35619
- const { source, models } = listModelsForVendor(vendor);
35693
+ const result = await listModelsForVendor(vendor);
35620
35694
  if (options.json) {
35621
- console.log(JSON.stringify(models, null, 2));
35695
+ console.log(JSON.stringify(result, null, 2));
35622
35696
  return;
35623
35697
  }
35698
+ const { source, models, nativeUnavailableReason } = result;
35624
35699
  console.log(source_default.bold(`Models for ${vendor} (${source})`));
35625
35700
  if (source === "bundled") {
35701
+ const reason = nativeUnavailableReason ? ` \u2014 ${nativeUnavailableReason}` : "";
35626
35702
  console.log(
35627
35703
  source_default.dim(
35628
- " Note: bundled fallback list \u2014 may be stale. Free-text input is always accepted."
35704
+ ` Note: bundled fallback list${reason}. Free-text input is always accepted.`
35629
35705
  )
35630
35706
  );
35631
35707
  }
@@ -36844,10 +36920,10 @@ function readCache(cacheFile) {
36844
36920
  return null;
36845
36921
  }
36846
36922
  }
36847
- function writeCache(cacheFile, cache) {
36923
+ function writeCache(cacheFile, cache2) {
36848
36924
  try {
36849
36925
  mkdirSync8(join28(cacheFile, ".."), { recursive: true });
36850
- writeFileSync10(cacheFile, JSON.stringify(cache));
36926
+ writeFileSync10(cacheFile, JSON.stringify(cache2));
36851
36927
  } catch {
36852
36928
  }
36853
36929
  }
@@ -36869,14 +36945,14 @@ async function checkForUpdate(currentVersion, options) {
36869
36945
  }
36870
36946
  const cacheFile = join28(options?.cacheDir ?? CACHE_DIR2, "update-check.json");
36871
36947
  try {
36872
- const cache = readCache(cacheFile);
36873
- if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
36874
- if (!cache.latestVersion) return null;
36875
- if (!isNewer(cache.latestVersion, currentVersion)) return null;
36948
+ const cache2 = readCache(cacheFile);
36949
+ if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
36950
+ if (!cache2.latestVersion) return null;
36951
+ if (!isNewer(cache2.latestVersion, currentVersion)) return null;
36876
36952
  return {
36877
36953
  updateAvailable: true,
36878
36954
  currentVersion,
36879
- latestVersion: cache.latestVersion,
36955
+ latestVersion: cache2.latestVersion,
36880
36956
  updateCommand: detectUpdateCommand()
36881
36957
  };
36882
36958
  }
@@ -7,31 +7,70 @@ import {
7
7
  import { promisify } from "node:util";
8
8
  var execFilePromise = promisify(execFile);
9
9
  var isWindows = process.platform === "win32";
10
- function execBinary(binary, args, opts) {
11
- return execFileSync(binary, args, {
10
+ async function execBinaryAsync(binary, args, opts) {
11
+ return execFilePromise(binary, args, {
12
12
  ...opts,
13
13
  shell: isWindows
14
14
  });
15
15
  }
16
16
 
17
17
  // src/lib/models.ts
18
- var BUNDLED_CLAUDE_MODELS = [
19
- { id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
20
- { id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
21
- { id: "claude-haiku-4-5-20251001", displayName: "Claude Haiku 4.5" }
22
- ];
23
- var BUNDLED_OPENCODE_MODELS = [
24
- { id: "anthropic/claude-opus-4-7", provider: "anthropic" },
25
- { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
26
- { id: "anthropic/claude-haiku-4-5-20251001", provider: "anthropic" }
27
- ];
28
- function detectActiveVendor() {
29
- for (const vendor of ["claude", "opencode"]) {
18
+ function parseOpenCodeModelList(stdout) {
19
+ const models = [];
20
+ for (const rawLine of stdout.split(/\r?\n/)) {
21
+ const line = rawLine.trim();
22
+ if (!/^[^\s:]+\/\S+$/.test(line)) continue;
23
+ const provider = line.slice(0, line.indexOf("/"));
24
+ models.push({ id: line, provider });
25
+ }
26
+ return models.length > 0 ? models : null;
27
+ }
28
+ var VENDOR_MODEL_STRATEGIES = {
29
+ claude: {
30
+ displayName: "Claude Code",
31
+ native: {
32
+ // Verified against Claude Code 2.1.x: the CLI has no model-listing
33
+ // subcommand (`claude models --json` → "unknown option"). Revisit if
34
+ // a future release adds one.
35
+ unavailableReason: "Claude Code does not provide a model-listing command; showing its documented model aliases instead"
36
+ },
37
+ // Vendor-documented aliases that always track the latest generation —
38
+ // dated ids here would go stale by construction (the exact bug class of
39
+ // issue #39). Pinned dated ids remain available via free-text entry.
40
+ bundled: [
41
+ { id: "opus", displayName: "Claude Opus (latest)" },
42
+ { id: "sonnet", displayName: "Claude Sonnet (latest)" },
43
+ { id: "haiku", displayName: "Claude Haiku (latest)" }
44
+ ]
45
+ },
46
+ opencode: {
47
+ displayName: "OpenCode",
48
+ native: {
49
+ // Plain `opencode models` — newline-delimited ids. (`--json` is not a
50
+ // real flag, and `--verbose` interleaves JSON metadata blocks that
51
+ // defeat line parsing.)
52
+ args: ["models"],
53
+ parse: parseOpenCodeModelList
54
+ },
55
+ bundled: [
56
+ { id: "anthropic/claude-opus-4-8", provider: "anthropic" },
57
+ { id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
58
+ { id: "anthropic/claude-haiku-4-5", provider: "anthropic" }
59
+ ]
60
+ }
61
+ };
62
+ var SUPPORTED_VENDORS = Object.keys(
63
+ VENDOR_MODEL_STRATEGIES
64
+ );
65
+ function isModelVendor(value) {
66
+ return Object.hasOwn(VENDOR_MODEL_STRATEGIES, value);
67
+ }
68
+ async function detectActiveVendor() {
69
+ for (const vendor of SUPPORTED_VENDORS) {
30
70
  try {
31
- execBinary(vendor, ["--version"], {
71
+ await execBinaryAsync(vendor, ["--version"], {
32
72
  encoding: "utf-8",
33
- timeout: 3e3,
34
- stdio: ["ignore", "pipe", "ignore"]
73
+ timeout: 3e3
35
74
  });
36
75
  return vendor;
37
76
  } catch {
@@ -39,47 +78,83 @@ function detectActiveVendor() {
39
78
  }
40
79
  return null;
41
80
  }
42
- function tryNativeEnumeration(vendor) {
81
+ function describeProbeFailure(vendor, args, err) {
82
+ const command = `${vendor} ${args.join(" ")}`;
83
+ const e = err;
84
+ if (e.code === "ENOENT") {
85
+ return `\`${vendor}\` is not installed or not on PATH`;
86
+ }
87
+ if (e.killed) {
88
+ return `\`${command}\` timed out or exceeded output limits`;
89
+ }
90
+ const stderr = typeof e.stderr === "string" ? e.stderr.trim() : "";
91
+ const firstLine = (stderr.split(/\r?\n/)[0] ?? "").replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
92
+ const detail = firstLine ? `: ${firstLine}` : "";
93
+ const exit = typeof e.code === "number" ? ` with exit code ${e.code}` : "";
94
+ return `\`${command}\` failed${exit}${detail}`;
95
+ }
96
+ async function tryNativeEnumeration(vendor, probe) {
97
+ let stdout;
43
98
  try {
44
- const output = execBinary(vendor, ["models", "--json"], {
99
+ const result = await execBinaryAsync(vendor, probe.args, {
45
100
  encoding: "utf-8",
46
- timeout: 5e3,
47
- stdio: ["ignore", "pipe", "ignore"]
101
+ timeout: 5e3
48
102
  });
49
- const parsed = JSON.parse(output);
50
- if (!Array.isArray(parsed)) return null;
51
- const models = [];
52
- for (const item of parsed) {
53
- if (typeof item === "string") {
54
- models.push({ id: item });
55
- } else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
56
- const obj = item;
57
- const desc = { id: obj.id };
58
- if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
59
- if (typeof obj.provider === "string") desc.provider = obj.provider;
60
- if (Array.isArray(obj.tags)) {
61
- desc.tags = obj.tags.filter((t) => typeof t === "string");
62
- }
63
- models.push(desc);
64
- }
65
- }
66
- return models.length > 0 ? models : null;
67
- } catch {
68
- return null;
103
+ stdout = result.stdout;
104
+ } catch (err) {
105
+ return { models: null, reason: describeProbeFailure(vendor, probe.args, err) };
69
106
  }
107
+ const models = probe.parse(stdout);
108
+ if (!models) {
109
+ return {
110
+ models: null,
111
+ reason: `\`${vendor} ${probe.args.join(" ")}\` output did not contain any model identifiers`
112
+ };
113
+ }
114
+ return { models };
70
115
  }
71
- function bundledForVendor(vendor) {
72
- if (vendor === "claude") return BUNDLED_CLAUDE_MODELS;
73
- return BUNDLED_OPENCODE_MODELS;
116
+ var SUCCESS_TTL_MS = 6e4;
117
+ var FAILURE_TTL_MS = 1e4;
118
+ var cache = /* @__PURE__ */ new Map();
119
+ function clearModelListCache() {
120
+ cache.clear();
74
121
  }
75
- function listModelsForVendor(vendor) {
76
- const native = tryNativeEnumeration(vendor);
77
- if (native) {
78
- return { vendor, source: "native", models: native };
122
+ async function listModelsForVendor(vendor) {
123
+ const cached = cache.get(vendor);
124
+ if (cached && cached.expiresAt > Date.now()) {
125
+ return cached.result;
126
+ }
127
+ const strategy = VENDOR_MODEL_STRATEGIES[vendor];
128
+ if (!strategy) {
129
+ throw new Error(`Unknown vendor: ${vendor}`);
130
+ }
131
+ let result;
132
+ if ("unavailableReason" in strategy.native) {
133
+ result = {
134
+ vendor,
135
+ source: "bundled",
136
+ models: strategy.bundled,
137
+ nativeUnavailableReason: strategy.native.unavailableReason
138
+ };
139
+ } else {
140
+ const native = await tryNativeEnumeration(vendor, strategy.native);
141
+ result = native.models ? { vendor, source: "native", models: native.models } : {
142
+ vendor,
143
+ source: "bundled",
144
+ models: strategy.bundled,
145
+ nativeUnavailableReason: native.reason
146
+ };
79
147
  }
80
- return { vendor, source: "bundled", models: bundledForVendor(vendor) };
148
+ const ttl = result.source === "native" ? SUCCESS_TTL_MS : FAILURE_TTL_MS;
149
+ cache.set(vendor, { result, expiresAt: Date.now() + ttl });
150
+ return result;
81
151
  }
82
152
  export {
153
+ SUPPORTED_VENDORS,
154
+ VENDOR_MODEL_STRATEGIES,
155
+ clearModelListCache,
83
156
  detectActiveVendor,
84
- listModelsForVendor
157
+ isModelVendor,
158
+ listModelsForVendor,
159
+ parseOpenCodeModelList
85
160
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-code-review/cli",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -80,7 +80,7 @@
80
80
  "ora": "^8.1.1",
81
81
  "socket.io": "^4.8",
82
82
  "yaml": "^2.8.3",
83
- "@open-code-review/agents": "2.2.0"
83
+ "@open-code-review/agents": "2.2.1"
84
84
  },
85
85
  "publishConfig": {
86
86
  "access": "public"
@@ -1 +0,0 @@
1
- import{ap as o,aq as n}from"./mermaid-renderer-D2jYNs7K.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-BSRfOovX.js";import{_ as i}from"./mermaid-renderer-D2jYNs7K.js";import"./chunk-FMBD7UC4-5xWP6GRj.js";import"./chunk-55IACEB6-C8xpXw9G.js";import"./chunk-QN33PNHL-DfNCVcy8.js";import"./index-C3NEq704.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{s as a,c as s,a as e,C as t}from"./chunk-B4BG7PRW-BSRfOovX.js";import{_ as i}from"./mermaid-renderer-D2jYNs7K.js";import"./chunk-FMBD7UC4-5xWP6GRj.js";import"./chunk-55IACEB6-C8xpXw9G.js";import"./chunk-QN33PNHL-DfNCVcy8.js";import"./index-C3NEq704.js";var u={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{u as diagram};
@@ -1 +0,0 @@
1
- import{b as r}from"./_baseUniq-CFHdos6T.js";var e=4;function a(o){return r(o,e)}export{a as c};