opencara 0.23.9 → 0.23.11

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 +85 -154
  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
  }
@@ -4686,6 +4613,17 @@ function agentConfigToDescriptor(config, agentId, index, agentOwner, userOrgs) {
4686
4613
  var DEFAULT_RECHECK_INTERVAL = 50;
4687
4614
 
4688
4615
  // src/commands/agent.ts
4616
+ function resolveCommandTemplate(agentConfig, globalCommand) {
4617
+ if (agentConfig?.command) return agentConfig.command;
4618
+ if (globalCommand) return globalCommand;
4619
+ if (agentConfig?.tool) {
4620
+ const toolDef = getToolDef(agentConfig.tool);
4621
+ if (toolDef) {
4622
+ return toolDef.command.replaceAll("${MODEL}", agentConfig.model ?? "");
4623
+ }
4624
+ }
4625
+ return void 0;
4626
+ }
4689
4627
  var DEFAULT_POLL_INTERVAL_MS = 1e4;
4690
4628
  var MAX_CONSECUTIVE_AUTH_ERRORS = 3;
4691
4629
  var MAX_POLL_BACKOFF_MS = 3e5;
@@ -5739,7 +5677,7 @@ function sleep2(ms, signal) {
5739
5677
  async function startAgent(agentId, platformUrl, agentInfo, reviewDeps, consumptionDeps, options) {
5740
5678
  const client = new ApiClient(platformUrl, {
5741
5679
  authToken: options?.authToken,
5742
- cliVersion: "0.23.9",
5680
+ cliVersion: "0.23.11",
5743
5681
  versionOverride: options?.versionOverride,
5744
5682
  onTokenRefresh: options?.onTokenRefresh
5745
5683
  });
@@ -6035,7 +5973,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6035
5973
  const { versionOverride, verbose, instancesOverride, agentOwner, userOrgs } = options;
6036
5974
  const client = new ApiClient(config.platformUrl, {
6037
5975
  authToken: oauthToken,
6038
- cliVersion: "0.23.9",
5976
+ cliVersion: "0.23.11",
6039
5977
  versionOverride,
6040
5978
  onTokenRefresh: () => getValidToken(config.platformUrl, { configPath: config.authFile })
6041
5979
  });
@@ -6063,7 +6001,7 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6063
6001
  let skipped = 0;
6064
6002
  for (let i = 0; i < agents.length; i++) {
6065
6003
  const agentConfig = agents[i];
6066
- const commandTemplate = agentConfig.command ?? config.agentCommand ?? void 0;
6004
+ const commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6067
6005
  const label = agentConfig.name ?? `agent[${i}]`;
6068
6006
  if (!commandTemplate) {
6069
6007
  logError(`[${label}] No command configured. Skipping.`);
@@ -6206,14 +6144,11 @@ async function startBatchAgents(config, agents, pollIntervalMs, oauthToken, opti
6206
6144
  async function startAgentRouter() {
6207
6145
  const config = loadConfig();
6208
6146
  const agentId = crypto2.randomUUID();
6209
- let commandTemplate;
6210
6147
  let agentConfig;
6211
6148
  if (config.agents && config.agents.length > 0) {
6212
6149
  agentConfig = config.agents.find((a) => a.router) ?? config.agents[0];
6213
- commandTemplate = agentConfig.command ?? config.agentCommand ?? void 0;
6214
- } else {
6215
- commandTemplate = config.agentCommand ?? void 0;
6216
6150
  }
6151
+ const commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6217
6152
  const router = new RouterRelay();
6218
6153
  router.start();
6219
6154
  const logger = createLogger(agentConfig?.name ?? "agent[0]");
@@ -6285,14 +6220,11 @@ async function startAgentRouter() {
6285
6220
  router.stop();
6286
6221
  }
6287
6222
  function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versionOverride, verbose, instancesOverride, agentOwner, userOrgs) {
6288
- let commandTemplate;
6289
6223
  let agentConfig;
6290
6224
  if (config.agents && config.agents.length > agentIndex) {
6291
6225
  agentConfig = config.agents[agentIndex];
6292
- commandTemplate = agentConfig.command ?? config.agentCommand ?? void 0;
6293
- } else {
6294
- commandTemplate = config.agentCommand ?? void 0;
6295
6226
  }
6227
+ const commandTemplate = resolveCommandTemplate(agentConfig, config.agentCommand);
6296
6228
  const label = agentConfig?.name ?? `agent[${agentIndex}]`;
6297
6229
  if (!commandTemplate) {
6298
6230
  console.error(`[${label}] No command configured. Skipping.`);
@@ -6368,7 +6300,7 @@ function startAgentByIndex(config, agentIndex, pollIntervalMs, oauthToken, versi
6368
6300
  return promises;
6369
6301
  }
6370
6302
  var agentCommand = new Command("agent").description("Manage review agents");
6371
- 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(
6303
+ 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(
6372
6304
  "--version-override <value>",
6373
6305
  "Cloudflare Workers version override (e.g. opencara-server=abc123)"
6374
6306
  ).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(
@@ -6386,7 +6318,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6386
6318
  }
6387
6319
  config = loadConfig();
6388
6320
  }
6389
- console.log(formatVersionBanner("0.23.9", "84eda5a"));
6321
+ console.log(formatVersionBanner("0.23.11", "1ab6f70"));
6390
6322
  if (config.agents && config.agents.length > 0) {
6391
6323
  const toolEntries = config.agents.map((a) => ({
6392
6324
  tool: a.tool,
@@ -6455,21 +6387,7 @@ agentCommand.command("start").description("Start agents in polling mode").option
6455
6387
  } else if (needsOrgs && userOrgs.size > 0) {
6456
6388
  console.log(`Org memberships: ${[...userOrgs].join(", ")}`);
6457
6389
  }
6458
- if (opts.all) {
6459
- if (!config.agents || config.agents.length === 0) {
6460
- console.error("No agents configured in ~/.opencara/config.toml");
6461
- process.exit(1);
6462
- return;
6463
- }
6464
- console.log(`Starting ${config.agents.length} agent config(s) in batch mode...`);
6465
- await startBatchAgents(config, config.agents, pollIntervalMs, oauthToken, {
6466
- versionOverride,
6467
- verbose: opts.verbose,
6468
- instancesOverride,
6469
- agentOwner,
6470
- userOrgs
6471
- });
6472
- } else {
6390
+ if (opts.agent != null) {
6473
6391
  const maxIndex = (config.agents?.length ?? 0) - 1;
6474
6392
  const agentIndex = Number(opts.agent);
6475
6393
  if (!Number.isInteger(agentIndex) || agentIndex < 0 || agentIndex > maxIndex) {
@@ -6502,6 +6420,20 @@ agentCommand.command("start").description("Start agents in polling mode").option
6502
6420
  }
6503
6421
  process.exit(1);
6504
6422
  }
6423
+ } else {
6424
+ if (!config.agents || config.agents.length === 0) {
6425
+ console.error("No agents configured in ~/.opencara/config.toml");
6426
+ process.exit(1);
6427
+ return;
6428
+ }
6429
+ console.log(`Starting ${config.agents.length} agent config(s) in batch mode...`);
6430
+ await startBatchAgents(config, config.agents, pollIntervalMs, oauthToken, {
6431
+ versionOverride,
6432
+ verbose: opts.verbose,
6433
+ instancesOverride,
6434
+ agentOwner,
6435
+ userOrgs
6436
+ });
6505
6437
  }
6506
6438
  }
6507
6439
  );
@@ -6792,11 +6724,10 @@ function resolveAgentCommand(toolName) {
6792
6724
  if (cmd) return cmd;
6793
6725
  }
6794
6726
  }
6795
- const registryTool = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
6796
- if (registryTool) {
6797
- const defaultModel = DEFAULT_REGISTRY.models.find((m) => m.tools.includes(toolName));
6798
- const modelName = defaultModel?.name ?? "";
6799
- return registryTool.commandTemplate.replaceAll("${MODEL}", modelName);
6727
+ const toolDef = getToolDef(toolName);
6728
+ if (toolDef) {
6729
+ const modelName = toolDef.models[0] ?? "";
6730
+ return toolDef.command.replaceAll("${MODEL}", modelName);
6800
6731
  }
6801
6732
  return null;
6802
6733
  }
@@ -7043,7 +6974,7 @@ async function runDedupInit(options, deps = {}) {
7043
6974
  const cmd = resolveCmd(options.agent);
7044
6975
  if (!cmd) {
7045
6976
  logError(
7046
- `${icons.error} Unknown agent tool "${options.agent}". Available: ${DEFAULT_REGISTRY.tools.map((t) => t.name).join(", ")}`
6977
+ `${icons.error} Unknown agent tool "${options.agent}". Available: ${loadToolDefs().map((t) => t.name).join(", ")}`
7047
6978
  );
7048
6979
  process.exitCode = 1;
7049
6980
  return;
@@ -7098,13 +7029,13 @@ function agentRoleLabel(agent) {
7098
7029
  return "reviewer+synthesizer";
7099
7030
  }
7100
7031
  function resolveToolBinary(toolName) {
7101
- const entry = DEFAULT_REGISTRY.tools.find((t) => t.name === toolName);
7102
- return entry?.binary ?? toolName;
7032
+ const def = getToolDef(toolName);
7033
+ return def?.binary ?? toolName;
7103
7034
  }
7104
7035
  function resolveCommand(agent) {
7105
7036
  if (agent.command) return agent.command;
7106
- const entry = DEFAULT_REGISTRY.tools.find((t) => t.name === agent.tool);
7107
- return entry?.commandTemplate ?? null;
7037
+ const def = getToolDef(agent.tool);
7038
+ return def?.command ?? null;
7108
7039
  }
7109
7040
  async function checkConnectivity(platformUrl, fetchFn = fetch) {
7110
7041
  const start = Date.now();
@@ -7209,7 +7140,7 @@ var statusCommand = new Command4("status").description("Show agent config, conne
7209
7140
  });
7210
7141
 
7211
7142
  // src/index.ts
7212
- var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.9"} (${"84eda5a"})`);
7143
+ var program = new Command5().name("opencara").description("OpenCara \u2014 distributed AI code review agent").version(`${"0.23.11"} (${"1ab6f70"})`);
7213
7144
  program.addCommand(agentCommand);
7214
7145
  program.addCommand(authCommand());
7215
7146
  program.addCommand(dedupCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencara",
3
- "version": "0.23.9",
3
+ "version": "0.23.11",
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",