opencara 0.23.10 → 0.23.12

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 (2) hide show
  1. package/dist/index.js +99 -156
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,15 +34,12 @@ function isRepoAllowed(repoConfig, targetOwner, targetRepo, agentOwner, userOrgs
34
34
  case "public":
35
35
  return true;
36
36
  case "private": {
37
+ if (repoConfig.list && repoConfig.list.length > 0 && repoConfig.list.includes(fullRepo)) {
38
+ return true;
39
+ }
37
40
  const normalizedTarget = targetOwner.toLowerCase();
38
41
  const normalizedOwner = agentOwner?.toLowerCase();
39
- const hasAccess = normalizedOwner === normalizedTarget || userOrgs != null && userOrgs.has(normalizedTarget);
40
- if (!hasAccess)
41
- return false;
42
- if (repoConfig.list && repoConfig.list.length > 0) {
43
- return repoConfig.list.includes(fullRepo);
44
- }
45
- return true;
42
+ return normalizedOwner === normalizedTarget || userOrgs != null && userOrgs.has(normalizedTarget);
46
43
  }
47
44
  case "whitelist":
48
45
  return (repoConfig.list ?? []).includes(fullRepo);
@@ -53,84 +50,6 @@ function isRepoAllowed(repoConfig, targetOwner, targetRepo, agentOwner, userOrgs
53
50
  }
54
51
  }
55
52
 
56
- // ../shared/dist/api.js
57
- var DEFAULT_REGISTRY = {
58
- tools: [
59
- {
60
- name: "claude",
61
- displayName: "Claude",
62
- binary: "claude",
63
- commandTemplate: "claude --model ${MODEL} --allowedTools '*' --print",
64
- tokenParser: "claude"
65
- },
66
- {
67
- name: "codex",
68
- displayName: "Codex",
69
- binary: "codex",
70
- commandTemplate: "codex --model ${MODEL} exec",
71
- tokenParser: "codex"
72
- },
73
- {
74
- name: "gemini",
75
- displayName: "Gemini",
76
- binary: "gemini",
77
- commandTemplate: "gemini -m ${MODEL}",
78
- tokenParser: "gemini"
79
- },
80
- {
81
- name: "qwen",
82
- displayName: "Qwen",
83
- binary: "qwen",
84
- commandTemplate: "qwen --model ${MODEL} -y",
85
- tokenParser: "qwen"
86
- }
87
- ],
88
- models: [
89
- {
90
- name: "claude-opus-4-6",
91
- displayName: "Claude Opus 4.6",
92
- tools: ["claude"]
93
- },
94
- {
95
- name: "claude-opus-4-6[1m]",
96
- displayName: "Claude Opus 4.6 (1M context)",
97
- tools: ["claude"]
98
- },
99
- {
100
- name: "claude-sonnet-4-6",
101
- displayName: "Claude Sonnet 4.6",
102
- tools: ["claude"]
103
- },
104
- {
105
- name: "claude-sonnet-4-6[1m]",
106
- displayName: "Claude Sonnet 4.6 (1M context)",
107
- tools: ["claude"]
108
- },
109
- {
110
- name: "gpt-5-codex",
111
- displayName: "GPT-5 Codex",
112
- tools: ["codex"]
113
- },
114
- {
115
- name: "gemini-2.5-pro",
116
- displayName: "Gemini 2.5 Pro",
117
- tools: ["gemini"]
118
- },
119
- {
120
- name: "qwen3.5-plus",
121
- displayName: "Qwen 3.5 Plus",
122
- tools: ["qwen"]
123
- },
124
- { name: "glm-5", displayName: "GLM-5", tools: ["qwen"] },
125
- { name: "kimi-k2.5", displayName: "Kimi K2.5", tools: ["qwen"] },
126
- {
127
- name: "minimax-m2.5",
128
- displayName: "Minimax M2.5",
129
- tools: ["qwen"]
130
- }
131
- ]
132
- };
133
-
134
53
  // ../shared/dist/review-config.js
135
54
  import { parse as parseToml } from "smol-toml";
136
55
  function isObject(value) {
@@ -552,6 +471,26 @@ import * as fs from "fs";
552
471
  import * as path from "path";
553
472
  import * as os from "os";
554
473
  import { parse as parseToml2, stringify as stringifyToml } from "smol-toml";
474
+
475
+ // src/tool-defs.ts
476
+ var _cache = null;
477
+ function loadToolDefs() {
478
+ if (!_cache) {
479
+ _cache = JSON.parse('[{"name":"claude","binary":"claude","models":["claude-sonnet-4-6","claude-opus-4-6"],"command":"claude --model ${MODEL} --allowedTools \'*\' --print","scannable":true,"installLink":"https://docs.anthropic.com/en/docs/claude-code"},{"name":"codex","binary":"codex","models":["gpt-5-codex"],"command":"codex --model ${MODEL} exec","scannable":true,"installLink":"https://github.com/openai/codex"},{"name":"gemini","binary":"gemini","models":["gemini-2.5-pro"],"command":"gemini -m ${MODEL}","scannable":true,"installLink":"https://github.com/google-gemini/gemini-cli"},{"name":"qwen","binary":"qwen","models":["qwen3.5-plus","glm-5","kimi-k2.5","minimax-m2.5"],"command":"qwen --model ${MODEL} -y","scannable":false}]');
480
+ }
481
+ return _cache;
482
+ }
483
+ function getToolDef(name) {
484
+ return loadToolDefs().find((t) => t.name === name);
485
+ }
486
+ function getScannableTools() {
487
+ return loadToolDefs().filter((t) => t.scannable);
488
+ }
489
+ function getKnownToolNames() {
490
+ return new Set(loadToolDefs().map((t) => t.name));
491
+ }
492
+
493
+ // src/config.ts
555
494
  var DEFAULT_PLATFORM_URL = "https://api.opencara.com";
556
495
  var CONFIG_DIR = path.join(os.homedir(), ".opencara");
557
496
  var CONFIG_FILE = process.env.OPENCARA_CONFIG && process.env.OPENCARA_CONFIG.trim() ? path.resolve(process.env.OPENCARA_CONFIG) : path.join(CONFIG_DIR, "config.toml");
@@ -589,7 +528,7 @@ var ConfigValidationError = class extends Error {
589
528
  this.name = "ConfigValidationError";
590
529
  }
591
530
  };
592
- var KNOWN_TOOL_NAMES = new Set(DEFAULT_REGISTRY.tools.map((t) => t.name));
531
+ var KNOWN_TOOL_NAMES = getKnownToolNames();
593
532
  var TOOL_ALIASES = {
594
533
  "claude-code": "claude"
595
534
  };
@@ -4416,17 +4355,6 @@ function countReviewComments(commentsText) {
4416
4355
  import { execFileSync as execFileSync8 } from "child_process";
4417
4356
  import * as fs9 from "fs";
4418
4357
  import * as readline2 from "readline";
4419
- var SCANNABLE_TOOLS = ["claude", "codex", "gemini"];
4420
- var DEFAULT_MODELS = {
4421
- claude: "claude-sonnet-4-6",
4422
- codex: "gpt-5-codex",
4423
- gemini: "gemini-2.5-pro"
4424
- };
4425
- var INSTALL_LINKS = {
4426
- claude: "https://docs.anthropic.com/en/docs/claude-code",
4427
- codex: "https://github.com/openai/codex",
4428
- gemini: "https://github.com/google-gemini/gemini-cli"
4429
- };
4430
4358
  function checkPrerequisites() {
4431
4359
  const gitInstalled = validateCommandBinary("git");
4432
4360
  const ghInstalled = validateCommandBinary("gh");
@@ -4450,21 +4378,13 @@ function checkPrerequisites() {
4450
4378
  }
4451
4379
  function discoverTools() {
4452
4380
  const results = [];
4453
- for (const toolName of SCANNABLE_TOOLS) {
4454
- if (validateCommandBinary(toolName)) {
4455
- const defaultModel = resolveDefaultModel(toolName);
4456
- results.push({ toolName, defaultModel });
4381
+ for (const tool of getScannableTools()) {
4382
+ if (validateCommandBinary(tool.binary)) {
4383
+ results.push({ toolName: tool.name, defaultModel: tool.models[0] });
4457
4384
  }
4458
4385
  }
4459
4386
  return results;
4460
4387
  }
4461
- function resolveDefaultModel(toolName) {
4462
- if (DEFAULT_MODELS[toolName]) {
4463
- return DEFAULT_MODELS[toolName];
4464
- }
4465
- const registryModel = DEFAULT_REGISTRY.models.find((m) => m.tools.includes(toolName));
4466
- return registryModel?.name ?? toolName;
4467
- }
4468
4388
  function generateConfig(tools) {
4469
4389
  const lines = [
4470
4390
  "# Auto-generated by opencara \u2014 edit to customize",
@@ -4541,22 +4461,29 @@ No config found at ${CONFIG_FILE}
4541
4461
  process.stdout.write("Continuing without gh \u2014 some features may be limited.\n");
4542
4462
  }
4543
4463
  process.stdout.write("\nScanning for AI tools...\n");
4464
+ const scannableTools = getScannableTools();
4544
4465
  const found = discoverTools();
4545
- for (const tool of SCANNABLE_TOOLS) {
4546
- const disc = found.find((t) => t.toolName === tool);
4466
+ for (const tool of scannableTools) {
4467
+ const disc = found.find((t) => t.toolName === tool.name);
4547
4468
  if (disc) {
4548
- process.stdout.write(` \u2713 ${tool} (${disc.defaultModel})
4469
+ process.stdout.write(` \u2713 ${tool.name} (${disc.defaultModel})
4549
4470
  `);
4550
4471
  } else {
4551
- process.stdout.write(` \u2717 ${tool} (not found)
4472
+ process.stdout.write(` \u2717 ${tool.name} (not found)
4552
4473
  `);
4553
4474
  }
4554
4475
  }
4555
4476
  if (found.length === 0) {
4556
- process.stdout.write("\nNo AI tools found. Install one of: claude, codex, gemini\n");
4557
- for (const tool of SCANNABLE_TOOLS) {
4558
- process.stdout.write(` ${tool}: ${INSTALL_LINKS[tool]}
4477
+ process.stdout.write(
4478
+ `
4479
+ No AI tools found. Install one of: ${scannableTools.map((t) => t.name).join(", ")}
4480
+ `
4481
+ );
4482
+ for (const tool of scannableTools) {
4483
+ if (tool.installLink) {
4484
+ process.stdout.write(` ${tool.name}: ${tool.installLink}
4559
4485
  `);
4486
+ }
4560
4487
  }
4561
4488
  return false;
4562
4489
  }
@@ -4649,21 +4576,39 @@ function buildBatchPollRequest(agents) {
4649
4576
  });
4650
4577
  return { agents: batchAgents };
4651
4578
  }
4652
- function filterTasksForAgent(tasks, agent, maxDiffSizeKb, diffFailCounts, maxDiffFetchAttempts = 3, accessibleRepos) {
4579
+ function filterTasksForAgent(tasks, agent, maxDiffSizeKb, diffFailCounts, maxDiffFetchAttempts = 3, accessibleRepos, log) {
4653
4580
  return tasks.filter((t) => {
4654
- if (accessibleRepos && !accessibleRepos.has(`${t.owner}/${t.repo}`)) {
4581
+ const repo = `${t.owner}/${t.repo}`;
4582
+ if (accessibleRepos && !accessibleRepos.has(repo)) {
4583
+ log?.(
4584
+ `Skipping task ${t.task_id.slice(0, 8)}\u2026 (${repo} PR#${t.pr_number}) \u2014 repo not in accessible set`
4585
+ );
4655
4586
  return false;
4656
4587
  }
4657
4588
  if (agent.repoConfig && !isRepoAllowed(agent.repoConfig, t.owner, t.repo, agent.agentOwner, agent.userOrgs)) {
4589
+ log?.(
4590
+ `Skipping task ${t.task_id.slice(0, 8)}\u2026 (${repo} PR#${t.pr_number}) \u2014 repo not allowed by config (mode=${agent.repoConfig.mode})`
4591
+ );
4658
4592
  return false;
4659
4593
  }
4660
4594
  if (agent.synthesizeRepos && !isRepoAllowed(agent.synthesizeRepos, t.owner, t.repo, agent.agentOwner, agent.userOrgs)) {
4595
+ log?.(
4596
+ `Skipping task ${t.task_id.slice(0, 8)}\u2026 (${repo} PR#${t.pr_number}) \u2014 repo not allowed by synthesize_repos config`
4597
+ );
4661
4598
  return false;
4662
4599
  }
4663
- if (maxDiffSizeKb && t.diff_size != null && t.diff_size * ESTIMATED_BYTES_PER_DIFF_LINE / 1024 > maxDiffSizeKb) {
4600
+ const isExplicitlyListed = agent.repoConfig?.list?.includes(repo) ?? false;
4601
+ if (!isExplicitlyListed && maxDiffSizeKb && t.diff_size != null && t.diff_size * ESTIMATED_BYTES_PER_DIFF_LINE / 1024 > maxDiffSizeKb) {
4602
+ const estimatedKb = Math.round(t.diff_size * ESTIMATED_BYTES_PER_DIFF_LINE / 1024);
4603
+ log?.(
4604
+ `Skipping task ${t.task_id.slice(0, 8)}\u2026 (${repo} PR#${t.pr_number}) \u2014 estimated diff ${estimatedKb}KB exceeds max_diff_size_kb (${maxDiffSizeKb}KB)`
4605
+ );
4664
4606
  return false;
4665
4607
  }
4666
4608
  if (diffFailCounts && (diffFailCounts.get(t.task_id) ?? 0) >= maxDiffFetchAttempts) {
4609
+ log?.(
4610
+ `Skipping task ${t.task_id.slice(0, 8)}\u2026 (${repo} PR#${t.pr_number}) \u2014 diff fetch failed ${maxDiffFetchAttempts} times`
4611
+ );
4667
4612
  return false;
4668
4613
  }
4669
4614
  return true;
@@ -4690,9 +4635,9 @@ function resolveCommandTemplate(agentConfig, globalCommand) {
4690
4635
  if (agentConfig?.command) return agentConfig.command;
4691
4636
  if (globalCommand) return globalCommand;
4692
4637
  if (agentConfig?.tool) {
4693
- const registryTool = DEFAULT_REGISTRY.tools.find((t) => t.name === agentConfig.tool);
4694
- if (registryTool) {
4695
- return registryTool.commandTemplate.replaceAll("${MODEL}", agentConfig.model ?? "");
4638
+ const toolDef = getToolDef(agentConfig.tool);
4639
+ if (toolDef) {
4640
+ return toolDef.command.replaceAll("${MODEL}", agentConfig.model ?? "");
4696
4641
  }
4697
4642
  }
4698
4643
  return void 0;
@@ -5750,7 +5695,7 @@ function sleep2(ms, signal) {
5750
5695
  async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
5751
5696
  const client = new ApiClient(platformUrl, {
5752
5697
  authToken: options?.authToken,
5753
- cliVersion: "0.23.10",
5698
+ cliVersion: "0.23.12",
5754
5699
  versionOverride: options?.versionOverride,
5755
5700
  onTokenRefresh: options?.onTokenRefresh
5756
5701
  });
@@ -5935,7 +5880,8 @@ async function batchPollLoop(client, agentStates, options) {
5935
5880
  state.reviewDeps.maxDiffSizeKb,
5936
5881
  state.diffFailCounts,
5937
5882
  MAX_DIFF_FETCH_ATTEMPTS,
5938
- accessibleRepos
5883
+ accessibleRepos,
5884
+ (msg) => state.logger.logWarn(`${icons.warn} ${msg}`)
5939
5885
  );
5940
5886
  const task = eligible[0];
5941
5887
  if (!task) continue;
@@ -6046,7 +5992,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6046
5992
  const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
6047
5993
  const client = new ApiClient(config.platformUrl, {
6048
5994
  authToken: oauthToken,
6049
- cliVersion: "0.23.10",
5995
+ cliVersion: "0.23.12",
6050
5996
  versionOverride,
6051
5997
  onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
6052
5998
  });
@@ -6217,12 +6163,11 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6217
6163
  async function startAgentRouter() {
6218
6164
  const config = loadConfig();
6219
6165
  const agentId = crypto2.randomUUID();
6220
- let commandTemplate;
6221
6166
  let agentConfig;
6222
6167
  if (config.agents && config.agents.length > 0) {
6223
6168
  agentConfig = config.agents.find((a) => a.router) ?? config.agents[0];
6224
6169
  }
6225
- commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6170
+ const commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6226
6171
  const router = new RouterRelay();
6227
6172
  router.start();
6228
6173
  const logger = createLogger(agentConfig?.name ?? "agent[0]");
@@ -6294,12 +6239,11 @@ async function startAgentRouter() {
6294
6239
  router.stop();
6295
6240
  }
6296
6241
  function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versionOverride, verbose, instancesOverride, agentOwner, userOrgs) {
6297
- let commandTemplate;
6298
6242
  let agentConfig;
6299
6243
  if (config.agents && config.agents.length > agentIndex) {
6300
6244
  agentConfig = config.agents[agentIndex];
6301
6245
  }
6302
- commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6246
+ const commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6303
6247
  const label = agentConfig?.name ?? `agent[${agentIndex}]`;
6304
6248
  if (!commandTemplate) {
6305
6249
  console.error(`[${label}] No command configured. Skipping.`);
@@ -6375,7 +6319,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versi
6375
6319
  return promises;
6376
6320
  }
6377
6321
  var agentCommand = new Command("agent").description("Manage review agents");
6378
- agentCommand.command("start").description("Start agents in polling mode").option("--poll-interval <seconds>", "Poll interval in seconds", "10").option("--agent <index>", "Agent index from config.toml (0-based)", "0").option("--all", "Start all configured agents concurrently").option(
6322
+ agentCommand.command("start").description("Start agents in polling mode").option("--poll-interval <seconds>", "Poll interval in seconds", "10").option("--agent <index>", "Start a single agent by index from config.toml (0-based)").option("--all", "Start all configured agents concurrently (default when --agent is not set)").option(
6379
6323
  "--version-override <value>",
6380
6324
  "Cloudflare Workers version override (e.g. opencara-server=abc123)"
6381
6325
  ).option("-v, --verbose", "Log tool stdout/stderr after each review/summary for debugging").option("--instances <count>", "Number of concurrent instances per agent (overrides config)").action(
@@ -6393,7 +6337,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6393
6337
  }
6394
6338
  config = loadConfig();
6395
6339
  }
6396
- console.log(formatVersionBanner("0.23.10", "3fd8827"));
6340
+ console.log(formatVersionBanner("0.23.12", "d2879c9"));
6397
6341
  if (config.agents && config.agents.length > 0) {
6398
6342
  const toolEntries = config.agents.map((a) => ({
6399
6343
  tool: a.tool,
@@ -6462,21 +6406,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6462
6406
  } else if (needsOrgs && userOrgs.size > 0) {
6463
6407
  console.log(`Org memberships: ${[...userOrgs].join(", ")}`);
6464
6408
  }
6465
- if (opts.all) {
6466
- if (!config.agents || config.agents.length === 0) {
6467
- console.error("No agents configured in ~/.opencara/config.toml");
6468
- process.exit(1);
6469
- return;
6470
- }
6471
- console.log(`Starting ${config.agents.length} agent config(s) in batch mode...`);
6472
- await startBatchAgents(config, config.agents, pollIntervalMs, oauthToken, {
6473
- versionOverride,
6474
- verbose: opts.verbose,
6475
- instancesOverride,
6476
- agentOwner,
6477
- userOrgs
6478
- });
6479
- } else {
6409
+ if (opts.agent != null) {
6480
6410
  const maxIndex = (config.agents?.length ?? 0) - 1;
6481
6411
  const agentIndex = Number(opts.agent);
6482
6412
  if (!Number.isInteger(agentIndex) || agentIndex < 0 || agentIndex > maxIndex) {
@@ -6509,6 +6439,20 @@ agentCommand.command("start").description("Start agents in polling mode").option
6509
6439
  }
6510
6440
  process.exit(1);
6511
6441
  }
6442
+ } else {
6443
+ if (!config.agents || config.agents.length === 0) {
6444
+ console.error("No agents configured in ~/.opencara/config.toml");
6445
+ process.exit(1);
6446
+ return;
6447
+ }
6448
+ console.log(`Starting ${config.agents.length} agent config(s) in batch mode...`);
6449
+ await startBatchAgents(config, config.agents, pollIntervalMs, oauthToken, {
6450
+ versionOverride,
6451
+ verbose: opts.verbose,
6452
+ instancesOverride,
6453
+ agentOwner,
6454
+ userOrgs
6455
+ });
6512
6456
  }
6513
6457
  }
6514
6458
  );
@@ -6799,11 +6743,10 @@ function resolveAgentCommand(toolName) {
6799
6743
  if (cmd) return cmd;
6800
6744
  }
6801
6745
  }
6802
- const registryTool = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
6803
- if (registryTool) {
6804
- const defaultModel = DEFAULT_REGISTRY.models.find((m) => m.tools.includes(toolName));
6805
- const modelName = defaultModel?.name ?? "";
6806
- return registryTool.commandTemplate.replaceAll("${MODEL}", modelName);
6746
+ const toolDef = getToolDef(toolName);
6747
+ if (toolDef) {
6748
+ const modelName = toolDef.models[0] ?? "";
6749
+ return toolDef.command.replaceAll("${MODEL}", modelName);
6807
6750
  }
6808
6751
  return null;
6809
6752
  }
@@ -7050,7 +6993,7 @@ async function runDedupInit(options, deps = {}) {
7050
6993
  const cmd = resolveCmd(options.agent);
7051
6994
  if (!cmd) {
7052
6995
  logError(
7053
- `${icons.error} Unknown agent tool "${options.agent}". Available: ${DEFAULT_REGISTRY.tools.map((t) => t.name).join(", ")}`
6996
+ `${icons.error} Unknown agent tool "${options.agent}". Available: ${loadToolDefs().map((t) => t.name).join(", ")}`
7054
6997
  );
7055
6998
  process.exitCode = 1;
7056
6999
  return;
@@ -7105,13 +7048,13 @@ function agentRoleLabel(agent) {
7105
7048
  return "reviewer+synthesizer";
7106
7049
  }
7107
7050
  function resolveToolBinary(toolName) {
7108
- const entry = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
7109
- return entry?.binary ?? toolName;
7051
+ const def = getToolDef(toolName);
7052
+ return def?.binary ?? toolName;
7110
7053
  }
7111
7054
  function resolveCommand(agent) {
7112
7055
  if (agent.command) return agent.command;
7113
- const entry = DEFAULT_REGISTRY.tools.find((t) => t.name === agent.tool);
7114
- return entry?.commandTemplate ?? null;
7056
+ const def = getToolDef(agent.tool);
7057
+ return def?.command ?? null;
7115
7058
  }
7116
7059
  async function checkConnectivity(platformUrl, fetchFn = fetch) {
7117
7060
  const start = Date.now();
@@ -7216,7 +7159,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
7216
7159
  });
7217
7160
 
7218
7161
  // src/index.ts
7219
- var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.10"} (${"3fd8827"})`);
7162
+ var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.12"} (${"d2879c9"})`);
7220
7163
  program.addCommand(agentCommand);
7221
7164
  program.addCommand(authCommand());
7222
7165
  program.addCommand(dedupCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.23.10",
3
+ "version": "0.23.12",
4
4
  "description": "Distributed AI code review agent — poll, review, and submit PR reviews using your own AI tools",
5
5
  "type": "module",
6
6
  "license": "MIT",