vibe-gate-mcp 0.1.5 → 0.1.7
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/.env.example +7 -3
- package/CHANGELOG.md +24 -0
- package/README.md +4 -2
- package/dist/index.mjs +227 -55
- package/docs/CLI_PROVIDERS.md +13 -9
- package/docs/TROUBLESHOOTING.md +3 -3
- package/docs/USAGE.md +4 -3
- package/docs/project/VARIABLES.md +14 -14
- package/package.json +1 -1
package/.env.example
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
# NEVER commit real keys. Pick one API provider with a key OR a signed-in local CLI.
|
|
4
4
|
# =============================================================================
|
|
5
5
|
|
|
6
|
-
# ---
|
|
6
|
+
# --- Critic provider (optional) ---
|
|
7
|
+
# When unset, Vibe-Gate selects the first installed CLI in this order:
|
|
8
|
+
# codex-cli -> claude-code -> cursor-agent -> opencode-cli; provider/model CRITIC_MODEL prefers opencode-cli.
|
|
9
|
+
# If no local CLI is found, OpenAI remains the default and requires OPENAI_API_KEY.
|
|
10
|
+
# To choose an API provider or override auto-detection, set CRITIC_PROVIDER explicitly.
|
|
7
11
|
# One of: openai | anthropic | google | minimax | opencode | codex-cli | claude-code | cursor-agent | opencode-cli
|
|
8
|
-
CRITIC_PROVIDER=openai
|
|
12
|
+
# CRITIC_PROVIDER=openai
|
|
9
13
|
|
|
10
14
|
# --- API key for direct API providers (uncomment ONE block when needed) ---
|
|
11
15
|
|
|
@@ -35,7 +39,7 @@ OPENAI_API_KEY=
|
|
|
35
39
|
# Optional executable paths when the MCP host PATH cannot find the CLI
|
|
36
40
|
# CODEX_CLI_PATH=/absolute/path/to/codex
|
|
37
41
|
# CLAUDE_CODE_CLI_PATH=/absolute/path/to/claude
|
|
38
|
-
# CURSOR_AGENT_CLI_PATH=/absolute/path/to/
|
|
42
|
+
# CURSOR_AGENT_CLI_PATH=/absolute/path/to/agent
|
|
39
43
|
# OPENCODE_CLI_PATH=/absolute/path/to/opencode
|
|
40
44
|
# CRITIC_CLI_TIMEOUT_MS=120000
|
|
41
45
|
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.7] - 2026-09-24
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Detect and launch Windows `.cmd` and `.bat` CLI shims through `cmd.exe` while keeping native executables on the direct spawn path.
|
|
15
|
+
|
|
16
|
+
### Tests
|
|
17
|
+
|
|
18
|
+
- Cover Windows `PATHEXT` shim resolution and safely quoted batch-shim invocation.
|
|
19
|
+
|
|
20
|
+
## [0.1.6] - 2026-09-24
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- Auto-detect an installed local CLI when `CRITIC_PROVIDER` is unset and report the selected CLI in the first review response.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Use Cursor's current `agent` command by default and fall back to the legacy `cursor-agent` command when it is unavailable.
|
|
29
|
+
|
|
30
|
+
### Tests
|
|
31
|
+
|
|
32
|
+
- Cover local CLI auto-detection and Cursor Agent's legacy executable fallback.
|
|
33
|
+
|
|
10
34
|
## [0.1.5] - 2026-09-23
|
|
11
35
|
|
|
12
36
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ An **Adversarial Quality Gate** for AI-assisted IDEs: the IDE agent and a Critic
|
|
|
6
6
|
|
|
7
7
|
### 1. Choose a Critic provider
|
|
8
8
|
|
|
9
|
-
Use a direct API provider with its key, or use a local CLI that is already installed and signed in. Local CLI providers do not need a separate provider API key.
|
|
9
|
+
Use a direct API provider with its key, or use a local CLI that is already installed and signed in. Local CLI providers do not need a separate provider API key. If `CRITIC_PROVIDER` is omitted, Vibe-Gate selects the first installed local CLI in this order: Codex, Claude Code, Cursor Agent, then OpenCode CLI. OpenCode CLI moves to the front when `CRITIC_MODEL` is in `provider/model` form. The first review response includes a notice naming the selected CLI. If none is found, OpenAI remains the default and requires `OPENAI_API_KEY`. Set `CRITIC_PROVIDER` to choose explicitly.
|
|
10
10
|
|
|
11
11
|
Copy from the package’s [`.env.example`](.env.example):
|
|
12
12
|
|
|
@@ -40,9 +40,11 @@ OPENAI_API_KEY=YOUR_OPENAI_API_KEY
|
|
|
40
40
|
| OpenCode | `opencode` | `OPENCODE_API_KEY` (+ optional `OPENCODE_PLAN`) |
|
|
41
41
|
| Codex CLI | `codex-cli` | Existing `codex login` session |
|
|
42
42
|
| Claude Code | `claude-code` | Existing Claude Code account session |
|
|
43
|
-
| Cursor Agent | `cursor-agent` | Existing `
|
|
43
|
+
| Cursor Agent | `cursor-agent` | Existing `agent login` session |
|
|
44
44
|
| OpenCode CLI | `opencode-cli` | Saved `opencode auth login` credentials + model |
|
|
45
45
|
|
|
46
|
+
For Cursor Agent, Vibe-Gate runs `agent` first and falls back to the legacy `cursor-agent` executable if the primary command is unavailable.
|
|
47
|
+
|
|
46
48
|
`opencode` is still the separate Zen/Go HTTP provider and needs `OPENCODE_API_KEY`. `opencode-cli` runs the local CLI and requires a `provider/model` value in `CRITIC_MODEL`; see the CLI guide for details.
|
|
47
49
|
|
|
48
50
|
`CRITIC_MODEL` is passed to the selected provider or CLI without a Vibe-Gate model allowlist; the provider must support that model ID. The OpenAI API provider uses the Responses API. See [provider configuration](docs/USAGE.md#configuration--providers).
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
2
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { config } from "dotenv";
|
|
5
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { accessSync, constants, existsSync, readFileSync } from "node:fs";
|
|
6
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -111,9 +111,11 @@ const PROVIDERS = {
|
|
|
111
111
|
const CLI_DEFAULT_COMMANDS = {
|
|
112
112
|
[PROVIDERS.CODEX_CLI]: "codex",
|
|
113
113
|
[PROVIDERS.CLAUDE_CODE]: "claude",
|
|
114
|
-
[PROVIDERS.CURSOR_AGENT]: "
|
|
114
|
+
[PROVIDERS.CURSOR_AGENT]: "agent",
|
|
115
115
|
[PROVIDERS.OPENCODE_CLI]: "opencode"
|
|
116
116
|
};
|
|
117
|
+
/** Previous Cursor CLI executable name retained for older installations. */
|
|
118
|
+
const CURSOR_AGENT_LEGACY_COMMAND = "cursor-agent";
|
|
117
119
|
/** Environment keys removed from local CLI processes unless a provider explicitly needs one. */
|
|
118
120
|
const CLI_STRIPPED_ENV_KEYS = [
|
|
119
121
|
ENV_KEYS.OPENAI_API_KEY,
|
|
@@ -329,7 +331,7 @@ const CONFLICT_LOOP = {
|
|
|
329
331
|
};
|
|
330
332
|
/** Error messages (SSoT) */
|
|
331
333
|
const ERROR_MESSAGES = {
|
|
332
|
-
NO_LLM_PROVIDER: "No LLM provider available.
|
|
334
|
+
NO_LLM_PROVIDER: "No LLM provider available. Check CRITIC_PROVIDER and its required API key, or install and sign in to a supported local CLI.",
|
|
333
335
|
STARTUP_FAILED: "Vibe-Gate failed to start:"
|
|
334
336
|
};
|
|
335
337
|
/** Debug log prefix (stderr) */
|
|
@@ -603,7 +605,7 @@ const personaSchema = z.enum([
|
|
|
603
605
|
PERSONAS.CLEAN_CODE_MONK
|
|
604
606
|
]);
|
|
605
607
|
const configSchema = z.object({
|
|
606
|
-
criticProvider: providerSchema.
|
|
608
|
+
criticProvider: providerSchema.optional(),
|
|
607
609
|
criticModel: z.string().min(1).optional(),
|
|
608
610
|
criticPersona: personaSchema.default(PERSONAS.CLEAN_CODE_MONK),
|
|
609
611
|
openaiApiKey: z.string().optional(),
|
|
@@ -631,7 +633,7 @@ function getEnv(key) {
|
|
|
631
633
|
}
|
|
632
634
|
function loadConfig() {
|
|
633
635
|
const raw = {
|
|
634
|
-
criticProvider: getEnv(ENV_KEYS.CRITIC_PROVIDER)
|
|
636
|
+
criticProvider: getEnv(ENV_KEYS.CRITIC_PROVIDER)?.trim() || void 0,
|
|
635
637
|
criticModel: getEnv(ENV_KEYS.CRITIC_MODEL),
|
|
636
638
|
criticPersona: getEnv(ENV_KEYS.CRITIC_PERSONA) ?? PERSONAS.CLEAN_CODE_MONK,
|
|
637
639
|
openaiApiKey: getEnv(ENV_KEYS.OPENAI_API_KEY),
|
|
@@ -648,9 +650,9 @@ function loadConfig() {
|
|
|
648
650
|
};
|
|
649
651
|
return configSchema.parse(raw);
|
|
650
652
|
}
|
|
651
|
-
function getEffectiveModel(config) {
|
|
653
|
+
function getEffectiveModel(config, providerId) {
|
|
652
654
|
if (config.criticModel) return config.criticModel;
|
|
653
|
-
return DEFAULT_MODELS[config.criticProvider];
|
|
655
|
+
return DEFAULT_MODELS[providerId ?? config.criticProvider ?? PROVIDERS.OPENAI];
|
|
654
656
|
}
|
|
655
657
|
//#endregion
|
|
656
658
|
//#region src/utils/debug.ts
|
|
@@ -2475,6 +2477,42 @@ function createOpenCodeProvider(apiKey, model, plan = OPENCODE_PLANS.ZEN) {
|
|
|
2475
2477
|
} };
|
|
2476
2478
|
}
|
|
2477
2479
|
//#endregion
|
|
2480
|
+
//#region src/llm/cli-command.ts
|
|
2481
|
+
/** Platform-specific executable lookup and process invocation for local CLIs. */
|
|
2482
|
+
const WINDOWS_EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
2483
|
+
".COM",
|
|
2484
|
+
".EXE",
|
|
2485
|
+
".BAT",
|
|
2486
|
+
".CMD"
|
|
2487
|
+
]);
|
|
2488
|
+
const WINDOWS_BATCH_EXTENSIONS = /* @__PURE__ */ new Set([".BAT", ".CMD"]);
|
|
2489
|
+
function executableExtensions(command, isWindows, pathExt = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
|
|
2490
|
+
if (!isWindows || extname(command)) return [""];
|
|
2491
|
+
return pathExt.split(";").filter((extension) => WINDOWS_EXECUTABLE_EXTENSIONS.has(extension.toUpperCase()));
|
|
2492
|
+
}
|
|
2493
|
+
function quoteCmdToken(value) {
|
|
2494
|
+
if (/[%"\0\r\n]/.test(value)) throw new Error("Windows CLI arguments cannot contain percent signs, quotes, or line breaks.");
|
|
2495
|
+
return `"${value}"`;
|
|
2496
|
+
}
|
|
2497
|
+
function buildCliSpawnTarget(command, args, platform = process.platform, comspec = process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe") {
|
|
2498
|
+
const extension = extname(command).toUpperCase();
|
|
2499
|
+
if (platform !== "win32" || !WINDOWS_BATCH_EXTENSIONS.has(extension)) return {
|
|
2500
|
+
command,
|
|
2501
|
+
args
|
|
2502
|
+
};
|
|
2503
|
+
return {
|
|
2504
|
+
command: comspec,
|
|
2505
|
+
args: [
|
|
2506
|
+
"/d",
|
|
2507
|
+
"/s",
|
|
2508
|
+
"/v:off",
|
|
2509
|
+
"/c",
|
|
2510
|
+
`"${[command, ...args].map(quoteCmdToken).join(" ")}"`
|
|
2511
|
+
],
|
|
2512
|
+
windowsVerbatimArguments: true
|
|
2513
|
+
};
|
|
2514
|
+
}
|
|
2515
|
+
//#endregion
|
|
2478
2516
|
//#region src/llm/cli.ts
|
|
2479
2517
|
/**
|
|
2480
2518
|
* Local authenticated CLI providers. These CLIs receive the complete review
|
|
@@ -2483,9 +2521,11 @@ function createOpenCodeProvider(apiKey, model, plan = OPENCODE_PLANS.ZEN) {
|
|
|
2483
2521
|
*/
|
|
2484
2522
|
var CliExecutionError = class extends Error {
|
|
2485
2523
|
stdout;
|
|
2486
|
-
|
|
2524
|
+
code;
|
|
2525
|
+
constructor(message, stdout, code) {
|
|
2487
2526
|
super(message);
|
|
2488
2527
|
this.stdout = stdout;
|
|
2528
|
+
this.code = code;
|
|
2489
2529
|
this.name = "CliExecutionError";
|
|
2490
2530
|
}
|
|
2491
2531
|
};
|
|
@@ -2516,11 +2556,13 @@ function sanitizeStderr(stderr) {
|
|
|
2516
2556
|
}
|
|
2517
2557
|
function runCli({ command, args, cwd, input, timeoutMs, label, env, keepCredentialEnvKeys }) {
|
|
2518
2558
|
return new Promise((resolve, reject) => {
|
|
2519
|
-
const
|
|
2559
|
+
const target = buildCliSpawnTarget(command, args);
|
|
2560
|
+
const child = spawn(target.command, target.args, {
|
|
2520
2561
|
cwd,
|
|
2521
2562
|
env: createChildEnv(env, keepCredentialEnvKeys),
|
|
2522
2563
|
shell: false,
|
|
2523
2564
|
windowsHide: true,
|
|
2565
|
+
...target.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
|
|
2524
2566
|
stdio: [
|
|
2525
2567
|
"pipe",
|
|
2526
2568
|
"pipe",
|
|
@@ -2558,8 +2600,9 @@ function runCli({ command, args, cwd, input, timeoutMs, label, env, keepCredenti
|
|
|
2558
2600
|
stderr = keepTail(stderr, chunk, CLI_PROVIDER_MAX_STDERR_BYTES);
|
|
2559
2601
|
});
|
|
2560
2602
|
child.on("error", (error) => {
|
|
2561
|
-
const
|
|
2562
|
-
|
|
2603
|
+
const errorCode = error.code;
|
|
2604
|
+
const detail = errorCode === "ENOENT" ? `Install ${label} or set its executable path in the MCP environment.` : error.message;
|
|
2605
|
+
finish(new CliExecutionError(`${label} CLI could not be started. ${detail}`, stdout.toString("utf8"), errorCode));
|
|
2563
2606
|
});
|
|
2564
2607
|
child.on("close", (code, signal) => {
|
|
2565
2608
|
if (failure) {
|
|
@@ -2584,6 +2627,18 @@ function runCli({ command, args, cwd, input, timeoutMs, label, env, keepCredenti
|
|
|
2584
2627
|
child.stdin.end(input);
|
|
2585
2628
|
});
|
|
2586
2629
|
}
|
|
2630
|
+
async function runCliWithFallback(options, fallbackCommands = []) {
|
|
2631
|
+
const commands = [options.command, ...fallbackCommands];
|
|
2632
|
+
for (const [index, command] of commands.entries()) try {
|
|
2633
|
+
return await runCli({
|
|
2634
|
+
...options,
|
|
2635
|
+
command
|
|
2636
|
+
});
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
if (!(error instanceof CliExecutionError && error.code === "ENOENT" && index < commands.length - 1)) throw error;
|
|
2639
|
+
}
|
|
2640
|
+
throw new Error(`${options.label} CLI could not be started.`);
|
|
2641
|
+
}
|
|
2587
2642
|
function parseCodexOutput(stdout) {
|
|
2588
2643
|
const messages = [];
|
|
2589
2644
|
for (const line of stdout.split(/\r?\n/)) {
|
|
@@ -2844,14 +2899,14 @@ async function completeCursorAgent(options, cwd, input) {
|
|
|
2844
2899
|
"json",
|
|
2845
2900
|
...modelArgs(options.model, "--model")
|
|
2846
2901
|
];
|
|
2847
|
-
return { content: parseJsonResult(await
|
|
2902
|
+
return { content: parseJsonResult(await runCliWithFallback({
|
|
2848
2903
|
command: options.command,
|
|
2849
2904
|
args,
|
|
2850
2905
|
cwd,
|
|
2851
2906
|
input,
|
|
2852
2907
|
timeoutMs: options.timeoutMs,
|
|
2853
2908
|
label: options.label
|
|
2854
|
-
}), options.label) };
|
|
2909
|
+
}, options.fallbackCommands), options.label) };
|
|
2855
2910
|
}
|
|
2856
2911
|
async function deleteOpenCodeSession(options, cwd, env, sessionId) {
|
|
2857
2912
|
await runCli({
|
|
@@ -2965,13 +3020,14 @@ function createClaudeCodeProvider(command, model, timeoutMs) {
|
|
|
2965
3020
|
timeoutMs
|
|
2966
3021
|
});
|
|
2967
3022
|
}
|
|
2968
|
-
function createCursorAgentProvider(command, model, timeoutMs) {
|
|
3023
|
+
function createCursorAgentProvider(command, model, timeoutMs, allowLegacyFallback = command === CLI_DEFAULT_COMMANDS[PROVIDERS.CURSOR_AGENT]) {
|
|
2969
3024
|
return createCliProvider({
|
|
2970
3025
|
id: PROVIDERS.CURSOR_AGENT,
|
|
2971
3026
|
label: "Cursor Agent",
|
|
2972
3027
|
command,
|
|
2973
3028
|
model,
|
|
2974
|
-
timeoutMs
|
|
3029
|
+
timeoutMs,
|
|
3030
|
+
fallbackCommands: allowLegacyFallback ? [CURSOR_AGENT_LEGACY_COMMAND] : []
|
|
2975
3031
|
});
|
|
2976
3032
|
}
|
|
2977
3033
|
function createOpenCodeCliProvider(command, model, timeoutMs) {
|
|
@@ -2985,40 +3041,119 @@ function createOpenCodeCliProvider(command, model, timeoutMs) {
|
|
|
2985
3041
|
}
|
|
2986
3042
|
//#endregion
|
|
2987
3043
|
//#region src/llm/index.ts
|
|
3044
|
+
/**
|
|
3045
|
+
* LLM provider factory and local CLI auto-detection.
|
|
3046
|
+
*/
|
|
3047
|
+
const AUTO_DETECT_CLI_ORDER = [
|
|
3048
|
+
PROVIDERS.CODEX_CLI,
|
|
3049
|
+
PROVIDERS.CLAUDE_CODE,
|
|
3050
|
+
PROVIDERS.CURSOR_AGENT,
|
|
3051
|
+
PROVIDERS.OPENCODE_CLI
|
|
3052
|
+
];
|
|
3053
|
+
function configuredCliPath(id, config) {
|
|
3054
|
+
switch (id) {
|
|
3055
|
+
case PROVIDERS.CODEX_CLI: return config.codexCliPath;
|
|
3056
|
+
case PROVIDERS.CLAUDE_CODE: return config.claudeCodeCliPath;
|
|
3057
|
+
case PROVIDERS.CURSOR_AGENT: return config.cursorAgentCliPath;
|
|
3058
|
+
case PROVIDERS.OPENCODE_CLI: return config.opencodeCliPath;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
function commandCandidates(id, config) {
|
|
3062
|
+
const configuredPath = configuredCliPath(id, config);
|
|
3063
|
+
if (configuredPath) return [configuredPath];
|
|
3064
|
+
if (id === PROVIDERS.CURSOR_AGENT) return [CLI_DEFAULT_COMMANDS[id], CURSOR_AGENT_LEGACY_COMMAND];
|
|
3065
|
+
return [CLI_DEFAULT_COMMANDS[id]];
|
|
3066
|
+
}
|
|
3067
|
+
function commandSearchBases(command) {
|
|
3068
|
+
if (isAbsolute(command) || command.includes("/") || command.includes("\\")) return [resolve(command)];
|
|
3069
|
+
return (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean).map((dir) => join(dir, command));
|
|
3070
|
+
}
|
|
3071
|
+
function canExecute(path, isWindows) {
|
|
3072
|
+
try {
|
|
3073
|
+
accessSync(path, isWindows ? constants.F_OK : constants.X_OK);
|
|
3074
|
+
return true;
|
|
3075
|
+
} catch {
|
|
3076
|
+
return false;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
function findExecutable(command) {
|
|
3080
|
+
const isWindows = process.platform === "win32";
|
|
3081
|
+
const bases = commandSearchBases(command);
|
|
3082
|
+
const extensions = executableExtensions(command, isWindows);
|
|
3083
|
+
for (const base of bases) for (const extension of extensions) {
|
|
3084
|
+
const candidate = extension ? `${base}${extension}` : base;
|
|
3085
|
+
if (canExecute(candidate, isWindows)) return candidate;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
function hasOpenCodeModel(config) {
|
|
3089
|
+
const model = config.criticModel;
|
|
3090
|
+
const separator = model?.indexOf("/") ?? -1;
|
|
3091
|
+
return Boolean(model && separator > 0 && separator < model.length - 1);
|
|
3092
|
+
}
|
|
3093
|
+
function detectLocalCli(config) {
|
|
3094
|
+
const configuredOrder = AUTO_DETECT_CLI_ORDER.filter((id) => configuredCliPath(id, config));
|
|
3095
|
+
const defaultOrder = AUTO_DETECT_CLI_ORDER.filter((id) => !configuredCliPath(id, config));
|
|
3096
|
+
const openCodeHasExplicitModel = hasOpenCodeModel(config) && defaultOrder.includes(PROVIDERS.OPENCODE_CLI);
|
|
3097
|
+
const preferredOrder = [
|
|
3098
|
+
...configuredOrder,
|
|
3099
|
+
...openCodeHasExplicitModel ? [PROVIDERS.OPENCODE_CLI] : [],
|
|
3100
|
+
...defaultOrder.filter((id) => !openCodeHasExplicitModel || id !== PROVIDERS.OPENCODE_CLI)
|
|
3101
|
+
];
|
|
3102
|
+
for (const id of preferredOrder) {
|
|
3103
|
+
if (id === PROVIDERS.OPENCODE_CLI && !hasOpenCodeModel(config)) continue;
|
|
3104
|
+
for (const candidate of commandCandidates(id, config)) {
|
|
3105
|
+
const command = findExecutable(candidate);
|
|
3106
|
+
if (command) return {
|
|
3107
|
+
id,
|
|
3108
|
+
command
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
const providerLabels = {
|
|
3114
|
+
[PROVIDERS.OPENAI]: "OpenAI",
|
|
3115
|
+
[PROVIDERS.ANTHROPIC]: "Anthropic",
|
|
3116
|
+
[PROVIDERS.GOOGLE]: "Google Gemini",
|
|
3117
|
+
[PROVIDERS.MINIMAX]: "MiniMax",
|
|
3118
|
+
[PROVIDERS.OPENCODE]: "OpenCode",
|
|
3119
|
+
[PROVIDERS.CODEX_CLI]: "Codex CLI",
|
|
3120
|
+
[PROVIDERS.CLAUDE_CODE]: "Claude Code",
|
|
3121
|
+
[PROVIDERS.CURSOR_AGENT]: "Cursor Agent",
|
|
3122
|
+
[PROVIDERS.OPENCODE_CLI]: "OpenCode CLI"
|
|
3123
|
+
};
|
|
3124
|
+
function isCliProvider(id) {
|
|
3125
|
+
return AUTO_DETECT_CLI_ORDER.includes(id);
|
|
3126
|
+
}
|
|
3127
|
+
function getProviderLabel(id) {
|
|
3128
|
+
return providerLabels[id];
|
|
3129
|
+
}
|
|
2988
3130
|
function createLLMProvider(config) {
|
|
2989
|
-
const
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
[PROVIDERS.
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
[PROVIDERS.
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
},
|
|
3016
|
-
[PROVIDERS.CODEX_CLI]: () => createCodexCliProvider(config.codexCliPath ?? CLI_DEFAULT_COMMANDS[PROVIDERS.CODEX_CLI], model, config.criticCliTimeoutMs),
|
|
3017
|
-
[PROVIDERS.CLAUDE_CODE]: () => createClaudeCodeProvider(config.claudeCodeCliPath ?? CLI_DEFAULT_COMMANDS[PROVIDERS.CLAUDE_CODE], model, config.criticCliTimeoutMs),
|
|
3018
|
-
[PROVIDERS.CURSOR_AGENT]: () => createCursorAgentProvider(config.cursorAgentCliPath ?? CLI_DEFAULT_COMMANDS[PROVIDERS.CURSOR_AGENT], model, config.criticCliTimeoutMs),
|
|
3019
|
-
[PROVIDERS.OPENCODE_CLI]: () => createOpenCodeCliProvider(config.opencodeCliPath ?? CLI_DEFAULT_COMMANDS[PROVIDERS.OPENCODE_CLI], model, config.criticCliTimeoutMs)
|
|
3020
|
-
}[config.criticProvider];
|
|
3021
|
-
return factory ? factory() : null;
|
|
3131
|
+
const detected = config.criticProvider ? void 0 : detectLocalCli(config);
|
|
3132
|
+
const providerId = config.criticProvider ?? detected?.id ?? PROVIDERS.OPENAI;
|
|
3133
|
+
const autoDetected = !config.criticProvider && detected !== void 0;
|
|
3134
|
+
const model = getEffectiveModel(config, providerId);
|
|
3135
|
+
const command = (id, configuredPath) => {
|
|
3136
|
+
if (detected && id === detected.id) return detected.command;
|
|
3137
|
+
return configuredPath ?? CLI_DEFAULT_COMMANDS[id];
|
|
3138
|
+
};
|
|
3139
|
+
const provider = {
|
|
3140
|
+
[PROVIDERS.OPENAI]: () => config.openaiApiKey ? createOpenAIProvider(config.openaiApiKey, model) : null,
|
|
3141
|
+
[PROVIDERS.ANTHROPIC]: () => config.anthropicApiKey ? createAnthropicProvider(config.anthropicApiKey, model) : null,
|
|
3142
|
+
[PROVIDERS.GOOGLE]: () => config.googleApiKey ? createGoogleProvider(config.googleApiKey, model) : null,
|
|
3143
|
+
[PROVIDERS.MINIMAX]: () => config.minimaxApiKey ? createMiniMaxProvider(config.minimaxApiKey, model) : null,
|
|
3144
|
+
[PROVIDERS.OPENCODE]: () => config.opencodeApiKey ? createOpenCodeProvider(config.opencodeApiKey, model, config.opencodePlan) : null,
|
|
3145
|
+
[PROVIDERS.CODEX_CLI]: () => createCodexCliProvider(command(PROVIDERS.CODEX_CLI, config.codexCliPath), model, config.criticCliTimeoutMs),
|
|
3146
|
+
[PROVIDERS.CLAUDE_CODE]: () => createClaudeCodeProvider(command(PROVIDERS.CLAUDE_CODE, config.claudeCodeCliPath), model, config.criticCliTimeoutMs),
|
|
3147
|
+
[PROVIDERS.CURSOR_AGENT]: () => createCursorAgentProvider(command(PROVIDERS.CURSOR_AGENT, config.cursorAgentCliPath), model, config.criticCliTimeoutMs, !config.cursorAgentCliPath),
|
|
3148
|
+
[PROVIDERS.OPENCODE_CLI]: () => createOpenCodeCliProvider(command(PROVIDERS.OPENCODE_CLI, config.opencodeCliPath), model, config.criticCliTimeoutMs)
|
|
3149
|
+
}[providerId]();
|
|
3150
|
+
if (!provider) return null;
|
|
3151
|
+
return {
|
|
3152
|
+
...provider,
|
|
3153
|
+
providerId,
|
|
3154
|
+
autoDetected: autoDetected && isCliProvider(providerId),
|
|
3155
|
+
...autoDetected && detected ? { providerCommand: detected.command } : {}
|
|
3156
|
+
};
|
|
3022
3157
|
}
|
|
3023
3158
|
//#endregion
|
|
3024
3159
|
//#region src/summarizer/parse-semantic-diff.ts
|
|
@@ -3743,6 +3878,35 @@ async function readRequestedFiles(workspaceRoot, response) {
|
|
|
3743
3878
|
* MCP tool: submit_phase_review
|
|
3744
3879
|
* Implementer reports phase completion; triggers Critic review.
|
|
3745
3880
|
*/
|
|
3881
|
+
let autoProviderNoticeSent = false;
|
|
3882
|
+
function addAutoProviderNotice(result, provider, providerWasInvoked) {
|
|
3883
|
+
if (autoProviderNoticeSent || !providerWasInvoked || !provider.autoDetected || !provider.providerId || !provider.providerCommand) return result;
|
|
3884
|
+
const command = basename(provider.providerCommand);
|
|
3885
|
+
const notice = `FYI: CRITIC_PROVIDER was not set; Vibe-Gate found ${getProviderLabel(provider.providerId)} (${command}) and is using it for this review.`;
|
|
3886
|
+
const [first, ...rest] = result.content;
|
|
3887
|
+
if (!first) return result;
|
|
3888
|
+
let payload;
|
|
3889
|
+
try {
|
|
3890
|
+
payload = JSON.parse(first.text);
|
|
3891
|
+
} catch {
|
|
3892
|
+
payload = { result: first.text };
|
|
3893
|
+
}
|
|
3894
|
+
const notifiedPayload = typeof payload === "object" && payload !== null && !Array.isArray(payload) ? {
|
|
3895
|
+
...payload,
|
|
3896
|
+
providerNotice: notice
|
|
3897
|
+
} : {
|
|
3898
|
+
result: payload,
|
|
3899
|
+
providerNotice: notice
|
|
3900
|
+
};
|
|
3901
|
+
autoProviderNoticeSent = true;
|
|
3902
|
+
return {
|
|
3903
|
+
...result,
|
|
3904
|
+
content: [{
|
|
3905
|
+
...first,
|
|
3906
|
+
text: JSON.stringify(notifiedPayload)
|
|
3907
|
+
}, ...rest]
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3746
3910
|
/**
|
|
3747
3911
|
* Build a structured history summary from prior rounds.
|
|
3748
3912
|
*
|
|
@@ -3916,7 +4080,7 @@ async function runCriticReview(workspaceRoot, args, session, config, provider) {
|
|
|
3916
4080
|
}
|
|
3917
4081
|
const round = args.round ?? 1;
|
|
3918
4082
|
const personaPrompt = getPersonaPrompt(config.criticPersona);
|
|
3919
|
-
const model = getEffectiveModel(config);
|
|
4083
|
+
const model = getEffectiveModel(config, provider.providerId);
|
|
3920
4084
|
const [status, contextBlockResult, rules, preferencesLog] = await Promise.all([
|
|
3921
4085
|
getStatus(workspaceRoot),
|
|
3922
4086
|
buildContextBlock(workspaceRoot, args.dependencies ?? [], args.semanticDiff),
|
|
@@ -4068,7 +4232,7 @@ const submitPhaseReviewInputSchema = submitPhaseReviewFieldsSchema.superRefine((
|
|
|
4068
4232
|
});
|
|
4069
4233
|
const SUBMIT_PHASE_REVIEW_SCHEMA = {
|
|
4070
4234
|
title: "Submit Phase Review",
|
|
4071
|
-
description: "IDE AI (Implementer) submits a phase completion report for Critic review. Prefer files[] (workspace-relative paths; MCP reads disk and builds FILE:...CONTENT:). Alternatives: semanticDiffPath or inline semanticDiff — exactly one. Set updateStatus:false for connectivity probes.",
|
|
4235
|
+
description: "IDE AI (Implementer) submits a phase completion report for Critic review. Prefer files[] (workspace-relative paths; MCP reads disk and builds FILE:...CONTENT:). Alternatives: semanticDiffPath or inline semanticDiff — exactly one. If CRITIC_PROVIDER is unset, Vibe-Gate selects the first installed local CLI and includes providerNotice in the first result. Set updateStatus:false for connectivity probes.",
|
|
4072
4236
|
inputSchema: submitPhaseReviewFieldsSchema
|
|
4073
4237
|
};
|
|
4074
4238
|
const INSUFFICIENT_REVIEW_GUIDANCE = `
|
|
@@ -4245,16 +4409,24 @@ async function handleSubmitPhaseReview(rawArgs) {
|
|
|
4245
4409
|
const session = await readSession(workspaceRoot);
|
|
4246
4410
|
const deadlockResult = await checkDeadlockEarly(workspaceRoot, args, session, semanticDiffHints);
|
|
4247
4411
|
if (deadlockResult) return deadlockResult;
|
|
4412
|
+
let providerWasInvoked = false;
|
|
4413
|
+
const trackedProvider = {
|
|
4414
|
+
...provider,
|
|
4415
|
+
async complete(messages) {
|
|
4416
|
+
providerWasInvoked = true;
|
|
4417
|
+
return provider.complete(messages);
|
|
4418
|
+
}
|
|
4419
|
+
};
|
|
4248
4420
|
try {
|
|
4249
|
-
return processVerdict(await runCriticReview(workspaceRoot, args, session, config,
|
|
4421
|
+
return addAutoProviderNotice(await processVerdict(await runCriticReview(workspaceRoot, args, session, config, trackedProvider), session, workspaceRoot, args, round, semanticDiffHints), provider, providerWasInvoked);
|
|
4250
4422
|
} catch (err) {
|
|
4251
4423
|
const errorMessage = getErrorMessage(err);
|
|
4252
4424
|
const errorObj = err;
|
|
4253
|
-
if (errorMessage.includes("401") || errorObj?.status === 401 || errorObj?.statusCode === 401) return { content: [toTextContent({
|
|
4425
|
+
if (!(provider.providerId !== void 0 && isCliProvider(provider.providerId)) && (errorMessage.includes("401") || errorObj?.status === 401 || errorObj?.statusCode === 401)) return addAutoProviderNotice({ content: [toTextContent({
|
|
4254
4426
|
error: "Authentication failed (401 Unauthorized). Invalid or missing API keys.",
|
|
4255
4427
|
details: "Vibe-Gate reads keys in this order:\n1. Process Environment (MCP config, shell env)\n2. Workspace .env (if VIBE_WORKSPACE_ROOT is set)\n3. Package .env (where vibe-gate-mcp is installed)\n\nPlease ensure your key is correct."
|
|
4256
|
-
})] };
|
|
4257
|
-
return { content: [toTextContent({ error: errorMessage })] };
|
|
4428
|
+
})] }, provider, providerWasInvoked);
|
|
4429
|
+
return addAutoProviderNotice({ content: [toTextContent({ error: errorMessage })] }, provider, providerWasInvoked);
|
|
4258
4430
|
}
|
|
4259
4431
|
}
|
|
4260
4432
|
//#endregion
|
package/docs/CLI_PROVIDERS.md
CHANGED
|
@@ -6,12 +6,16 @@ The CLI is installed locally, but its model request still goes to the provider.
|
|
|
6
6
|
|
|
7
7
|
## Supported CLIs
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
| `
|
|
14
|
-
|
|
|
9
|
+
When `CRITIC_PROVIDER` is empty or unset, Vibe-Gate checks local CLI executables in this order: Codex (`codex`), Claude Code (`claude`), Cursor Agent (`agent`, then legacy `cursor-agent`), and OpenCode CLI (`opencode`). OpenCode CLI is considered only with a `provider/model` `CRITIC_MODEL`, and that model format moves it to the front of the default order. Configured `*_CLI_PATH` values are checked before default PATH names, using the same provider order. The first tool response reports the selected CLI in `providerNotice`. Detection checks whether the executable is available; it does not verify that the CLI is signed in. If the selected CLI needs login, Vibe-Gate returns its error and does not switch to another account or service. If no local CLI is found, Vibe-Gate retains its OpenAI default and requires `OPENAI_API_KEY`. Set `CRITIC_PROVIDER` to select a provider explicitly.
|
|
10
|
+
|
|
11
|
+
On Windows, discovery follows `PATHEXT` for `.COM`, `.EXE`, `.BAT`, and `.CMD` files. Batch shims (`.bat` and `.cmd`) run through `cmd.exe`; native executables run directly.
|
|
12
|
+
|
|
13
|
+
| `CRITIC_PROVIDER` | Command | Sign in | Model selection |
|
|
14
|
+
| ----------------- | ----------------------------------------- | ---------------------------------------------- | -------------------------------------------------- |
|
|
15
|
+
| `codex-cli` | `codex` | `codex login` | Optional `CRITIC_MODEL` |
|
|
16
|
+
| `claude-code` | `claude` | `claude auth login` | Optional `CRITIC_MODEL` |
|
|
17
|
+
| `cursor-agent` | `agent` (legacy fallback: `cursor-agent`) | `agent login` (legacy: `cursor-agent login`) | Optional `CRITIC_MODEL` |
|
|
18
|
+
| `opencode-cli` | `opencode` | `opencode auth login` or `/connect` in its TUI | Required `CRITIC_MODEL` in `provider/model` format |
|
|
15
19
|
|
|
16
20
|
If `CRITIC_MODEL` is omitted, Vibe-Gate leaves model selection to Codex, Claude Code, or Cursor Agent. Use model IDs accepted by the selected CLI when setting an override. Codex is deliberately started with user configuration ignored, so it uses its built-in default model rather than a custom model from `config.toml`; the saved login remains available. OpenCode CLI requires `CRITIC_MODEL` because Vibe-Gate isolates its user configuration.
|
|
17
21
|
|
|
@@ -48,8 +52,8 @@ Vibe-Gate uses print mode and safe mode, disables built-in tools and MCP tools,
|
|
|
48
52
|
Install Cursor CLI and sign in to your Cursor account:
|
|
49
53
|
|
|
50
54
|
```sh
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
agent login
|
|
56
|
+
agent status
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
Then configure Vibe-Gate:
|
|
@@ -57,7 +61,7 @@ Then configure Vibe-Gate:
|
|
|
57
61
|
```env
|
|
58
62
|
CRITIC_PROVIDER=cursor-agent
|
|
59
63
|
# Optional when PATH in the IDE differs from your terminal:
|
|
60
|
-
# CURSOR_AGENT_CLI_PATH=/absolute/path/to/
|
|
64
|
+
# CURSOR_AGENT_CLI_PATH=/absolute/path/to/agent
|
|
61
65
|
# Optional model override:
|
|
62
66
|
# CRITIC_MODEL=gpt-5
|
|
63
67
|
```
|
package/docs/TROUBLESHOOTING.md
CHANGED
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
1. For direct API providers, set its key: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `MINIMAX_API_KEY`, or `OPENCODE_API_KEY`.
|
|
12
12
|
2. For a local CLI provider, install the selected CLI, sign in with its account, and check the executable path.
|
|
13
|
-
3.
|
|
13
|
+
3. If `CRITIC_PROVIDER` is set, ensure it matches one of: `openai`, `anthropic`, `google`, `minimax`, `opencode`, `codex-cli`, `claude-code`, `cursor-agent`, or `opencode-cli`. If it is unset, Vibe-Gate checks for local CLI executables in the documented order.
|
|
14
14
|
4. For `opencode-cli`, set `CRITIC_MODEL` to an available `provider/model` value from `opencode models`.
|
|
15
15
|
5. Verify `.env` is loaded (MCP config must pass `env` or the process must inherit it).
|
|
16
16
|
|
|
17
17
|
### Local CLI not found or not authenticated
|
|
18
18
|
|
|
19
|
-
**Cause:** IDE-launched MCP servers may have a shorter `PATH` than an interactive terminal, or the CLI may not have an account session for this OS user.
|
|
19
|
+
**Cause:** IDE-launched MCP servers may have a shorter `PATH` than an interactive terminal, or the CLI may not have an account session for this OS user. On Windows, the CLI shim may be a `.cmd` or `.bat` file listed in `PATHEXT`.
|
|
20
20
|
|
|
21
|
-
**Fix:** Run the CLI's login flow in a terminal as the same user running the IDE, then set its matching `*_CLI_PATH` to the absolute executable path if needed. For Claude Code, `claude auth login` defaults to Claude subscription sign-in; `--console` selects API billing. See [CLI_PROVIDERS.md](CLI_PROVIDERS.md) for provider-specific setup.
|
|
21
|
+
**Fix:** Run the CLI's login flow in a terminal as the same user running the IDE, then set its matching `*_CLI_PATH` to the absolute executable path if needed. For Cursor, `cursor-agent` remains the `CRITIC_PROVIDER` value, while the current executable is `agent`; `cursor-agent` is only a legacy fallback. For Claude Code, `claude auth login` defaults to Claude subscription sign-in; `--console` selects API billing. See [CLI_PROVIDERS.md](CLI_PROVIDERS.md) for provider-specific setup.
|
|
22
22
|
|
|
23
23
|
### OpenCode CLI reports insufficient balance
|
|
24
24
|
|
package/docs/USAGE.md
CHANGED
|
@@ -48,10 +48,11 @@ Vibe-Gate can call a direct API provider with an API key, or a local CLI using i
|
|
|
48
48
|
|
|
49
49
|
### Local CLI providers (no separate API key)
|
|
50
50
|
|
|
51
|
-
Install and sign in to one supported CLI, then
|
|
51
|
+
Install and sign in to one supported CLI. If `CRITIC_PROVIDER` is unset, Vibe-Gate automatically uses the first available local CLI in this order: Codex, Claude Code, Cursor Agent, then OpenCode CLI. A `provider/model` `CRITIC_MODEL` moves OpenCode CLI to the front. The first review response identifies the selected CLI. Set `CRITIC_PROVIDER` to choose explicitly.
|
|
52
52
|
|
|
53
53
|
```env
|
|
54
|
-
|
|
54
|
+
# Optional override; omit this to auto-detect a local CLI
|
|
55
|
+
# CRITIC_PROVIDER=codex-cli
|
|
55
56
|
# Or: claude-code | cursor-agent | opencode-cli
|
|
56
57
|
# OpenCode CLI reuses its saved login and requires a model:
|
|
57
58
|
# CRITIC_PROVIDER=opencode-cli
|
|
@@ -62,7 +63,7 @@ When `CRITIC_MODEL` is omitted, Codex, Claude Code, and Cursor Agent choose thei
|
|
|
62
63
|
|
|
63
64
|
Full setup, security behavior, and alternatives we evaluated are in [CLI_PROVIDERS.md](CLI_PROVIDERS.md).
|
|
64
65
|
|
|
65
|
-
### OpenAI
|
|
66
|
+
### OpenAI
|
|
66
67
|
|
|
67
68
|
```env
|
|
68
69
|
CRITIC_PROVIDER=openai
|
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Copy [`.env.example`](../../.env.example) → `.env` in the package directory **or** set the same keys in MCP `env`. Choose a direct API provider with its key, or a signed-in local CLI provider without a separate API key.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
| Variable | When required
|
|
8
|
-
| ------------------------------ |
|
|
9
|
-
| `CRITIC_PROVIDER` |
|
|
10
|
-
| `OPENAI_API_KEY` | `CRITIC_PROVIDER=openai`
|
|
11
|
-
| `ANTHROPIC_API_KEY` | `CRITIC_PROVIDER=anthropic`
|
|
12
|
-
| `GOOGLE_GENERATIVE_AI_API_KEY` | `CRITIC_PROVIDER=google`
|
|
13
|
-
| `MINIMAX_API_KEY` | `CRITIC_PROVIDER=minimax`
|
|
14
|
-
| `OPENCODE_API_KEY` | `CRITIC_PROVIDER=opencode`
|
|
15
|
-
| `CODEX_CLI_PATH` | `CRITIC_PROVIDER=codex-cli`
|
|
16
|
-
| `CLAUDE_CODE_CLI_PATH` | `CRITIC_PROVIDER=claude-code`
|
|
17
|
-
| `CURSOR_AGENT_CLI_PATH` | `CRITIC_PROVIDER=cursor-agent`
|
|
18
|
-
| `OPENCODE_CLI_PATH` | `CRITIC_PROVIDER=opencode-cli`
|
|
5
|
+
## Provider selection (optional)
|
|
6
|
+
|
|
7
|
+
| Variable | When required | Description |
|
|
8
|
+
| ------------------------------ | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| `CRITIC_PROVIDER` | Optional (auto-detect local CLI) | `openai` \| `anthropic` \| `google` \| `minimax` \| `opencode` \| `codex-cli` \| `claude-code` \| `cursor-agent` \| `opencode-cli` |
|
|
10
|
+
| `OPENAI_API_KEY` | `CRITIC_PROVIDER=openai` | OpenAI API key |
|
|
11
|
+
| `ANTHROPIC_API_KEY` | `CRITIC_PROVIDER=anthropic` | Anthropic API key |
|
|
12
|
+
| `GOOGLE_GENERATIVE_AI_API_KEY` | `CRITIC_PROVIDER=google` | Google Gemini API key |
|
|
13
|
+
| `MINIMAX_API_KEY` | `CRITIC_PROVIDER=minimax` | MiniMax API key |
|
|
14
|
+
| `OPENCODE_API_KEY` | `CRITIC_PROVIDER=opencode` | From https://opencode.ai/auth |
|
|
15
|
+
| `CODEX_CLI_PATH` | `CRITIC_PROVIDER=codex-cli` | Optional path to the `codex` executable |
|
|
16
|
+
| `CLAUDE_CODE_CLI_PATH` | `CRITIC_PROVIDER=claude-code` | Optional path to the `claude` executable |
|
|
17
|
+
| `CURSOR_AGENT_CLI_PATH` | `CRITIC_PROVIDER=cursor-agent` | Optional path to the `agent` executable (`cursor-agent` is accepted for legacy installs) |
|
|
18
|
+
| `OPENCODE_CLI_PATH` | `CRITIC_PROVIDER=opencode-cli` | Optional path to the `opencode` executable |
|
|
19
19
|
|
|
20
20
|
## Optional
|
|
21
21
|
|