vibe-gate-mcp 0.1.6 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.8] - 2026-09-24
11
+
12
+ ### Fixed
13
+
14
+ - Resolve explicitly selected CLI names through Windows `PATH` and `PATHEXT` before spawning, including Cursor's `agent.cmd` and legacy `cursor-agent.cmd` shims.
15
+ - Launch Windows `.cmd` and `.bat` shims through `cmd.exe` while keeping native executables on the direct spawn path.
16
+
17
+ ### Tests
18
+
19
+ - Cover Windows `PATHEXT` resolution for explicit `agent` and `cursor-agent` commands and safely quoted batch-shim invocation.
20
+
21
+ ## [0.1.7] - 2026-09-24
22
+
23
+ ### Fixed
24
+
25
+ - Detect and launch Windows `.cmd` and `.bat` CLI shims through `cmd.exe` while keeping native executables on the direct spawn path.
26
+
27
+ ### Tests
28
+
29
+ - Cover Windows `PATHEXT` shim resolution and safely quoted batch-shim invocation.
30
+
10
31
  ## [0.1.6] - 2026-09-24
11
32
 
12
33
  ### Added
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
2
+ import * as path from "node:path";
3
+ import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { config } from "dotenv";
5
6
  import { accessSync, constants, existsSync, readFileSync } from "node:fs";
@@ -2477,6 +2478,65 @@ function createOpenCodeProvider(apiKey, model, plan = OPENCODE_PLANS.ZEN) {
2477
2478
  } };
2478
2479
  }
2479
2480
  //#endregion
2481
+ //#region src/llm/cli-command.ts
2482
+ /** Platform-specific executable lookup and process invocation for local CLIs. */
2483
+ const WINDOWS_EXECUTABLE_EXTENSIONS = /* @__PURE__ */ new Set([
2484
+ ".COM",
2485
+ ".EXE",
2486
+ ".BAT",
2487
+ ".CMD"
2488
+ ]);
2489
+ const WINDOWS_BATCH_EXTENSIONS = /* @__PURE__ */ new Set([".BAT", ".CMD"]);
2490
+ function executableExtensions(command, isWindows, pathExt = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
2491
+ const commandExtension = isWindows ? path.win32.extname(command) : extname(command);
2492
+ if (!isWindows || commandExtension) return [""];
2493
+ return pathExt.split(";").filter((extension) => WINDOWS_EXECUTABLE_EXTENSIONS.has(extension.toUpperCase()));
2494
+ }
2495
+ function executableCandidates(command, options = {}) {
2496
+ const isWindows = (options.platform ?? process.platform) === "win32";
2497
+ const pathApi = isWindows ? path.win32 : path;
2498
+ const hasDirectory = pathApi.isAbsolute(command) || command.includes("/") || command.includes("\\");
2499
+ const pathValue = options.pathValue ?? process.env.PATH ?? process.env.Path ?? "";
2500
+ const pathDelimiter = isWindows ? ";" : path.delimiter;
2501
+ const bases = hasDirectory ? [pathApi.resolve(command)] : pathValue.split(pathDelimiter).filter(Boolean).map((directory) => pathApi.join(directory, command));
2502
+ const extensions = executableExtensions(command, isWindows, options.pathExt);
2503
+ return bases.flatMap((base) => extensions.map((extension) => extension ? `${base}${extension}` : base));
2504
+ }
2505
+ function findExecutable(command, options = {}) {
2506
+ const isWindows = (options.platform ?? process.platform) === "win32";
2507
+ const canAccess = options.canAccess ?? ((candidate, windows) => {
2508
+ try {
2509
+ accessSync(candidate, windows ? constants.F_OK : constants.X_OK);
2510
+ return true;
2511
+ } catch {
2512
+ return false;
2513
+ }
2514
+ });
2515
+ for (const candidate of executableCandidates(command, options)) if (canAccess(candidate, isWindows)) return candidate;
2516
+ }
2517
+ function quoteCmdToken(value) {
2518
+ if (/[%"\0\r\n]/.test(value)) throw new Error("Windows CLI arguments cannot contain percent signs, quotes, or line breaks.");
2519
+ return `"${value}"`;
2520
+ }
2521
+ function buildCliSpawnTarget(command, args, platform = process.platform, comspec = process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe") {
2522
+ const extension = extname(command).toUpperCase();
2523
+ if (platform !== "win32" || !WINDOWS_BATCH_EXTENSIONS.has(extension)) return {
2524
+ command,
2525
+ args
2526
+ };
2527
+ return {
2528
+ command: comspec,
2529
+ args: [
2530
+ "/d",
2531
+ "/s",
2532
+ "/v:off",
2533
+ "/c",
2534
+ `"${[command, ...args].map(quoteCmdToken).join(" ")}"`
2535
+ ],
2536
+ windowsVerbatimArguments: true
2537
+ };
2538
+ }
2539
+ //#endregion
2480
2540
  //#region src/llm/cli.ts
2481
2541
  /**
2482
2542
  * Local authenticated CLI providers. These CLIs receive the complete review
@@ -2520,11 +2580,14 @@ function sanitizeStderr(stderr) {
2520
2580
  }
2521
2581
  function runCli({ command, args, cwd, input, timeoutMs, label, env, keepCredentialEnvKeys }) {
2522
2582
  return new Promise((resolve, reject) => {
2523
- const child = spawn(command, args, {
2583
+ const childEnv = createChildEnv(env, keepCredentialEnvKeys);
2584
+ const target = buildCliSpawnTarget(findExecutable(command, { pathValue: childEnv.PATH ?? childEnv.Path }) ?? command, args);
2585
+ const child = spawn(target.command, target.args, {
2524
2586
  cwd,
2525
- env: createChildEnv(env, keepCredentialEnvKeys),
2587
+ env: childEnv,
2526
2588
  shell: false,
2527
2589
  windowsHide: true,
2590
+ ...target.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {},
2528
2591
  stdio: [
2529
2592
  "pipe",
2530
2593
  "pipe",
@@ -3003,9 +3066,6 @@ function createOpenCodeCliProvider(command, model, timeoutMs) {
3003
3066
  }
3004
3067
  //#endregion
3005
3068
  //#region src/llm/index.ts
3006
- /**
3007
- * LLM provider factory and local CLI auto-detection.
3008
- */
3009
3069
  const AUTO_DETECT_CLI_ORDER = [
3010
3070
  PROVIDERS.CODEX_CLI,
3011
3071
  PROVIDERS.CLAUDE_CODE,
@@ -3026,31 +3086,6 @@ function commandCandidates(id, config) {
3026
3086
  if (id === PROVIDERS.CURSOR_AGENT) return [CLI_DEFAULT_COMMANDS[id], CURSOR_AGENT_LEGACY_COMMAND];
3027
3087
  return [CLI_DEFAULT_COMMANDS[id]];
3028
3088
  }
3029
- function commandSearchBases(command) {
3030
- if (isAbsolute(command) || command.includes("/") || command.includes("\\")) return [resolve(command)];
3031
- return (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean).map((dir) => join(dir, command));
3032
- }
3033
- function executableExtensions(command, isWindows) {
3034
- if (!isWindows || extname(command)) return [""];
3035
- return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter((extension) => [".COM", ".EXE"].includes(extension.toUpperCase()));
3036
- }
3037
- function canExecute(path, isWindows) {
3038
- try {
3039
- accessSync(path, isWindows ? constants.F_OK : constants.X_OK);
3040
- return true;
3041
- } catch {
3042
- return false;
3043
- }
3044
- }
3045
- function findExecutable(command) {
3046
- const isWindows = process.platform === "win32";
3047
- const bases = commandSearchBases(command);
3048
- const extensions = executableExtensions(command, isWindows);
3049
- for (const base of bases) for (const extension of extensions) {
3050
- const candidate = extension ? `${base}${extension}` : base;
3051
- if (canExecute(candidate, isWindows)) return candidate;
3052
- }
3053
- }
3054
3089
  function hasOpenCodeModel(config) {
3055
3090
  const model = config.criticModel;
3056
3091
  const separator = model?.indexOf("/") ?? -1;
@@ -8,6 +8,8 @@ The CLI is installed locally, but its model request still goes to the provider.
8
8
 
9
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
10
 
11
+ On Windows, command names are resolved through `PATH` and `PATHEXT` for `.COM`, `.EXE`, `.BAT`, and `.CMD` files whether `CRITIC_PROVIDER` is automatic or explicitly set. Batch shims (`.bat` and `.cmd`) run through `cmd.exe`; native executables run directly.
12
+
11
13
  | `CRITIC_PROVIDER` | Command | Sign in | Model selection |
12
14
  | ----------------- | ----------------------------------------- | ---------------------------------------------- | -------------------------------------------------- |
13
15
  | `codex-cli` | `codex` | `codex login` | Optional `CRITIC_MODEL` |
@@ -16,7 +16,7 @@
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
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-gate-mcp",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "Adversarial Quality Gate MCP for vibe-coding: IDE AI vs Critic AI, human decides on deadlock",
6
6
  "license": "MIT",