@ask-llm/plugin 0.13.0 → 0.14.0

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.
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "ask-llm",
3
- "version": "0.13.0",
4
- "description": "AI-to-AI collaboration — review code, brainstorm ideas, and debate plans across Codex, Antigravity, Ollama, and Gemini",
3
+ "version": "0.14.0",
4
+ "description": "AI-to-AI collaboration — review code and debate plans across Codex, Grok, Antigravity, Ollama, and Gemini",
5
5
  "author": {
6
6
  "name": "Anton Lykhoyda",
7
7
  "url": "https://github.com/Lykhoyda"
8
8
  },
9
9
  "repository": "https://github.com/Lykhoyda/ask-llm",
10
10
  "license": "MIT",
11
+ "mcpServers": "./.mcp.json",
11
12
  "keywords": [
12
13
  "gemini",
13
14
  "codex",
15
+ "grok",
16
+ "xai",
14
17
  "ollama",
15
18
  "antigravity",
16
19
  "code-review",
package/.mcp.json CHANGED
@@ -1,3 +1,8 @@
1
1
  {
2
- "mcpServers": {}
2
+ "mcpServers": {
3
+ "codex": {
4
+ "command": "npx",
5
+ "args": ["-y", "@ask-llm/codex-mcp"]
6
+ }
7
+ }
3
8
  }
package/README.md CHANGED
@@ -21,14 +21,20 @@ Part of the [Ask LLM](https://github.com/Lykhoyda/ask-llm) monorepo.
21
21
 
22
22
  > **After installing or upgrading, fully restart Claude Code** (quit and reopen) so the codex-pair `PostToolUse` hook registers. Claude Code binds hooks at session start; `/reload-plugins` refreshes the plugin cache but does **not** re-register hooks in a pre-existing session, so codex-pair won't auto-fire on edits until you restart (see [#74](https://github.com/Lykhoyda/ask-llm/issues/74)). Run `/codex-pair` afterwards to confirm the hook is wired up.
23
23
 
24
- ### MCP Servers (user scope for short tool names)
24
+ ### MCP Servers
25
+
26
+ The plugin bundles the supported Codex MCP registration under Claude Code's plugin namespace. After installation or upgrade, fully restart Claude Code and run `/mcp`; `plugin:ask-llm:codex` should be connected and `/sol-review` will select its `ask-codex` tool automatically.
27
+
28
+ Existing user-scoped Codex registrations remain compatible and keep the shorter `codex:ask-codex` name. The other provider servers are still registered explicitly at user scope:
25
29
 
26
30
  ```bash
27
31
  claude mcp add --scope user gemini -- npx -y @ask-llm/gemini-mcp
28
- claude mcp add --scope user codex -- npx -y @ask-llm/codex-mcp
29
32
  claude mcp add --scope user ollama -- npx -y @ask-llm/ollama-mcp
33
+ claude mcp add --scope user antigravity -- npx -y @ask-llm/antigravity-mcp
30
34
  ```
31
35
 
36
+ If Codex is missing entirely, register it explicitly with `claude mcp add --scope user codex -- npx -y @ask-llm/codex-mcp`. If `/mcp` shows the bundled registration but it is disconnected, run `npx -y @ask-llm/mcp doctor` and restart Claude Code. `/sol-review` preserves source-plugin and session-local MCP/settings context when reading the active `claude mcp list` inventory, reports missing and unavailable states separately, and discloses the explicit `codex exec` fallback after failed health or MCP transport failure.
37
+
32
38
  ### Pi
33
39
 
34
40
  ```bash
@@ -36,7 +42,7 @@ pi install npm:@ask-llm/plugin
36
42
  pi list
37
43
  ```
38
44
 
39
- Pi discovers the portable skills as `/skill:<name>` commands and registers native `ask-codex`, `ask-gemini`, `ask-ollama`, `ask-antigravity`, and bounded concurrent `ask-multi` tools. Pi intentionally has no built-in MCP client; do not configure these as MCP servers in Pi. `fable-review` remains Claude Code-only and is excluded from Pi discovery.
45
+ Pi discovers the portable skills as `/skill:<name>` commands and registers native `ask-codex`, `ask-gemini`, `ask-grok`, `ask-ollama`, `ask-antigravity`, model-neutral `ask-cursor-agent`, and bounded concurrent `ask-multi` tools. Pi intentionally has no built-in MCP client; do not configure these as MCP servers in Pi. `fable-review` remains Claude Code-only and is excluded from Pi discovery.
40
46
 
41
47
  For codex-pair, create `.codex-pair/context.md`, ensure Pi trusts the project, then run interactive `/codex-pair` to grant user-owned canonical-project consent. The marker alone never authorizes data transfer/cost. Revoke with `/codex-pair revoke`. Pi findings are non-blocking; blocking Stop-gate and one-shot print parity are not available.
42
48
 
@@ -55,10 +61,11 @@ See the [Pi host guide](https://lykhoyda.github.io/ask-llm/plugin/pi) for securi
55
61
  | `/gemini-review` | Gemini-only code review with confidence filtering |
56
62
  | `/codex-review` | Codex-only code review (precision-first, ≥80 confidence — default for routine PR review) |
57
63
  | `/fable-review` | Isolated, read-only review requesting native Fable, with runtime verification limits disclosed |
58
- | `/sol-review` | Model-pinned GPT-5.6 Sol review through Codex. Uses the `ask-codex` MCP tool when available; subagent contexts without it fall back to the `codex` CLI, so keep the CLI installed and authenticated |
64
+ | `/sol-review` | Model-pinned GPT-5.6 Sol review through the bundled `ask-codex` MCP tool; missing registration and service unavailability are diagnosed separately before the explicit CLI fallback |
59
65
  | `/ollama-review` | Local review — no data leaves your machine |
60
66
  | `/brainstorm` | Multi-LLM brainstorm with Claude Opus as a first-class research participant (default external: gemini,codex) |
61
- | `/brainstorm-all` | Brainstorm with all four external providers (Gemini, Codex, Ollama, Antigravity) + Claude Opus research |
67
+ | `/grok-review` | Grok review through explicit xAI API or Grok CLI harness; no fallback |
68
+ | `/brainstorm-all` | Brainstorm with all five external providers (Gemini, Codex, Grok, Ollama, Antigravity) + Claude Opus research |
62
69
  | `/compare` | Side-by-side raw responses from multiple providers (no synthesis, no consensus extraction) |
63
70
 
64
71
  ## Agents
@@ -12,6 +12,7 @@ tools:
12
12
  - WebSearch
13
13
  - mcp__gemini__ask-gemini
14
14
  - mcp__codex__ask-codex
15
+ - mcp__grok__ask-grok
15
16
  - mcp__ollama__ask-ollama
16
17
  - mcp__antigravity__ask-antigravity
17
18
  ---
@@ -32,7 +33,7 @@ The frontmatter and detailed implementation below define Claude Code subagent ex
32
33
  You are a brainstorming coordinator powered by Claude Opus. You have two jobs:
33
34
 
34
35
  1. **You are a first-class research participant.** Perform your own deep, independent analysis of the topic — read the actual files, trace the real code paths, factor in framework-specific semantics. Your findings go into the synthesis as peer input, not as commentary on what the external providers said.
35
- 2. **You orchestrate external consultations.** Dispatch the topic to the selected external providers (Antigravity, Codex, Ollama, Gemini — default: antigravity,codex) via a **single blocking foreground Bash call**, collect their responses, and combine them with your own research in a structured synthesis.
36
+ 2. **You orchestrate external consultations.** Dispatch the topic to the selected external providers (Antigravity, Codex, Grok, Ollama, Gemini — default: antigravity,codex) via a **single blocking foreground Bash call**, collect their responses, and combine them with your own research in a structured synthesis.
36
37
 
37
38
  You run on Opus and you have filesystem access. Skipping your own research phase wastes the one participant with the strongest grounding — don't do it.
38
39
 
@@ -117,6 +118,7 @@ The user specifies which external providers to use. Default is `antigravity,code
117
118
  - `antigravity` — Google Antigravity, subscription-backed via your Google AI Pro/Ultra plan, via the `agy` CLI (experimental; requires `agy` >=1.1.5 installed + logged in)
118
119
  - `gemini` — Google Gemini (large context, strong at analysis) via the `gemini` CLI
119
120
  - `codex` — OpenAI Codex (strong at code reasoning) via `codex exec --sandbox read-only`
121
+ - `grok` — Grok through the canonical runner and explicit `ASK_GROK_HARNESS` (`xai-api` default or `grok-cli`); may incur xAI API/plan usage and never falls back
120
122
  - `ollama` — Local Ollama (private, no data leaves machine) via the `ollama` CLI
121
123
 
122
124
  **Required Bash tool call parameters:**
@@ -185,6 +187,12 @@ esac
185
187
  > "$workdir/codex.out" 2> "$workdir/codex.err" &
186
188
  pid_codex=$!
187
189
 
190
+ # Only include this block if grok was requested. The canonical runner preserves
191
+ # explicit API-vs-CLI harness selection, key redaction, timeout/cancellation,
192
+ # actual model attribution, and the no-fallback contract.
193
+ node "${CLAUDE_PLUGIN_ROOT}/dist/grok-run.js" "$(cat "$workdir/prompt.md")" > "$workdir/grok.out" 2> "$workdir/grok.err" &
194
+ pid_grok=$!
195
+
188
196
  # Only include this line if ollama was requested:
189
197
  ollama run "${ASK_OLLAMA_MODEL:-qwen3.6:27b}" < "$workdir/prompt.md" > "$workdir/ollama.out" 2> "$workdir/ollama.err" &
190
198
  pid_ollama=$!
@@ -196,6 +204,7 @@ pid_ollama=$!
196
204
  wait "$pid_antigravity" 2>/dev/null; rc_antigravity=$?
197
205
  wait "$pid_gemini" 2>/dev/null; rc_gemini=$?
198
206
  wait "$pid_codex" 2>/dev/null; rc_codex=$?
207
+ wait "$pid_grok" 2>/dev/null; rc_grok=$?
199
208
  wait "$pid_ollama" 2>/dev/null; rc_ollama=$?
200
209
 
201
210
  # Dump everything so the tool result is self-contained for Phase 4.
@@ -211,6 +220,10 @@ echo "===== CODEX (rc=$rc_codex) ====="
211
220
  cat "$workdir/codex.out" 2>/dev/null
212
221
  echo "===== CODEX STDERR ====="
213
222
  cat "$workdir/codex.err" 2>/dev/null
223
+ echo "===== GROK (rc=$rc_grok) ====="
224
+ cat "$workdir/grok.out" 2>/dev/null
225
+ echo "===== GROK STDERR ====="
226
+ cat "$workdir/grok.err" 2>/dev/null
214
227
  echo "===== OLLAMA (rc=$rc_ollama) ====="
215
228
  cat "$workdir/ollama.out" 2>/dev/null
216
229
  echo "===== OLLAMA STDERR ====="
@@ -9,6 +9,7 @@ tools:
9
9
  - Grep
10
10
  - Read
11
11
  - mcp__codex__ask-codex
12
+ - mcp__plugin_ask-llm_codex__ask-codex
12
13
  ---
13
14
 
14
15
  <!-- PORTABLE-CONTRACT:START -->
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: grok-reviewer
3
+ description: Runs a focused Grok review through the metered xAI API, then verifies findings against source. Never changes billing or substitutes another model.
4
+ model: opus
5
+ color: blue
6
+ tools:
7
+ - Bash
8
+ - Glob
9
+ - Grep
10
+ - Read
11
+ - mcp__grok__ask-grok
12
+ ---
13
+
14
+ <!-- PORTABLE-CONTRACT:START -->
15
+ ## Portable contract
16
+
17
+ Review only the supplied changes and context with Grok through the xAI API. Require confidence scores, concrete file/line evidence, and source verification. Omit style-only, speculative, pre-existing, and linter-detectable findings. Preserve actual model attribution. Treat credential, model, quota, transport, malformed-output, and safety errors as terminal; never switch models or providers. Remind the operator that sent context leaves the machine and metered xAI API charges can apply.
18
+ <!-- PORTABLE-CONTRACT:END -->
19
+
20
+ <!-- HOST-ADAPTER:CLAUDE-CODE:START -->
21
+ ## Claude Code adapter
22
+
23
+ You coordinate an independent Grok code review. You may read source to understand and verify the review, but you never edit files.
24
+
25
+ ### Context gathering
26
+
27
+ 1. Read the supplied diff and the relevant current source.
28
+ 2. Discover project instructions that apply to each changed file.
29
+ 3. Keep the payload bounded and exclude secrets, generated artifacts, lockfiles, and unrelated documentation.
30
+
31
+ ### Grok request
32
+
33
+ Call `mcp__grok__ask-grok` with its model unset unless the user explicitly requested an exact xAI API model ID. Use `reasoningEffort: "high"` for review. Tell Grok to report only issues with confidence at least 80 and include:
34
+
35
+ - confidence score
36
+ - severity
37
+ - file and line
38
+ - concrete failure mode
39
+ - evidence from the changed code
40
+ - minimal remediation
41
+
42
+ Explicitly exclude style preferences, unchanged-code findings, linter/type-checker findings, and claims that depend on unstated runtime assumptions.
43
+
44
+ ### Verification
45
+
46
+ For every candidate, read the cited source and verify the claimed behavior. Drop findings with wrong lines, missing evidence, non-applicable project rules, or speculative impact. Group surviving findings as Critical (90–100) and Important (80–89), then state how many candidates were dropped.
47
+
48
+ If the xAI request fails, report the Grok-specific diagnostic and stop. Do not retry a different model, invoke another provider, enable billing, buy credits, or request priority capacity.
49
+ <!-- HOST-ADAPTER:CLAUDE-CODE:END -->
@@ -4,13 +4,10 @@ description: Coordinates an isolated, read-only code review explicitly pinned to
4
4
  model: opus
5
5
  effort: high
6
6
  color: blue
7
- tools:
8
- - Bash
9
- - Glob
10
- - Grep
11
- - Read
12
- - mcp__codex__ask-codex
13
- - mcp__plugin_ask-llm_codex__ask-codex
7
+ disallowedTools:
8
+ - Edit
9
+ - Write
10
+ - NotebookEdit
14
11
  ---
15
12
 
16
13
  <!-- PORTABLE-CONTRACT:START -->
@@ -38,13 +35,13 @@ You are a code review coordinator for a model-pinned OpenAI GPT-5.6 Sol review.
38
35
  - `preferred` unset
39
36
  - a prompt containing the scoped conventions, relevant ADR summaries, and the diff
40
37
 
41
- The tool name may be plugin-namespaced in some sessions (for example `mcp__plugin_ask-llm_codex__ask-codex`); any `ask-codex` MCP variant counts as the primary transport. If no `ask-codex` MCP tool is available in this subagent context, use the sanctioned CLI fallback, passing the same prompt on stdin:
38
+ The tool name may be plugin-namespaced in some sessions (for example `mcp__plugin_ask-llm_codex__ask-codex`). Treat a tool as authoritative only when its server identity maps to an active `@ask-llm/codex-mcp` registration. If no authoritative `ask-codex` tool is available in this subagent context, pass the same prompt on stdin to the shipped fallback runner:
42
39
 
43
40
  ```bash
44
- codex exec -m gpt-5.6-sol -c model_reasoning_effort="high" -s read-only --ignore-user-config --ignore-rules --skip-git-repo-check
41
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/sol-review-transport.mjs" --fallback --plugin-dir "${CLAUDE_PLUGIN_ROOT}" --cli-path "$(command -v codex || true)"
45
42
  ```
46
43
 
47
- The `-m` pin, the reasoning-effort override, and the `-s read-only` sandbox are load-bearing; never drop or substitute them. The `--ignore-user-config --ignore-rules --skip-git-repo-check` flags are equally load-bearing: they mirror what the project's MCP executor always passes, so a local `~/.codex/config.toml` cannot silently override the pinned model or reasoning effort. If the Sol invocation fails with a quota or rate-limit error, retry once with `-m "${ASK_CODEX_FALLBACK_MODEL:-gpt-5.6-terra}"` and all other flags identical, matching the MCP executor's configurable quota ladder (`ASK_CODEX_FALLBACK_MODEL` overrides the `gpt-5.6-terra` default there too), and disclose the fallback model exactly as you would on the MCP path. If the `codex` CLI is also unavailable, stop and report that the Sol review could not run. Do not review on another transport, on any model outside the Sol-to-fallback ladder, or in another sandbox mode.
44
+ Before executing Codex, the runner queries the active Claude MCP inventory again with the active plugin preserved; mirror any session-local `--mcp-config`, `--settings`, `--setting-sources`, and `--strict-mcp-config` flags on the runner command. This makes a parent `preferred` result followed by an absent or disconnected subagent tool a registered-but-unavailable state with the corresponding remediation. If the MCP call itself fails at the transport/service boundary, rerun the same fallback command with `--mcp-failed`; do not use that flag for a provider/model response. The runner then executes `codex exec -m gpt-5.6-sol -c model_reasoning_effort="high" -s read-only --ignore-user-config --ignore-rules --skip-git-repo-check`. The model pin, reasoning-effort override, read-only sandbox, and isolation flags are load-bearing; never drop or substitute them. On a quota or rate-limit failure only, the runner retries once with `${ASK_CODEX_FALLBACK_MODEL:-gpt-5.6-terra}` and identical flags, matching the MCP executor's configurable quota ladder. It writes the review result to stdout unchanged so the validated findings can be relayed without loss. If the `codex` CLI is also unavailable, stop and report that the Sol review could not run. Do not review on another transport, on any model outside the Sol-to-fallback ladder, or in another sandbox mode.
48
45
  4. Ask Sol for concrete correctness, security, data-loss, concurrency, resource-lifecycle, and compatibility failures with confidence scores and reproduction conditions.
49
46
  5. Read the reported source locations and trace each reproduction path. Drop style preferences, speculative improvements, pre-existing issues, linter/type-checker findings, and behavior documented as intentional.
50
47
  6. Report only validated findings with confidence of at least 80/100. Never invent findings to fill a report.
@@ -53,7 +50,7 @@ You are a code review coordinator for a model-pinned OpenAI GPT-5.6 Sol review.
53
50
 
54
51
  Lead with the highest-severity finding. For every surviving issue include severity (`BLOCKING`, `IMPORTANT`, or `ADVISORY`), confidence, file and line, failure mode, reproduction conditions, and the smallest concrete fix. State clearly when no high-confidence findings survive validation.
55
52
 
56
- The explicit `model` argument is load-bearing: do not omit it or replace it with an environment-selected default. Disclose every fallback you take, not only model fallbacks: if the response reports a Terra quota fallback, disclose that the Sol review could not complete as pinned, and if you used the CLI transport fallback because no `ask-codex` MCP tool was available, state that the review ran through `codex exec` rather than MCP.
53
+ The explicit `model` argument is load-bearing: do not omit it or replace it with an environment-selected default. Disclose every fallback you take, not only model fallbacks: if the response reports a Terra quota fallback, disclose that the Sol review could not complete as pinned. If you used the CLI transport fallback, state that the review ran through `codex exec` rather than MCP, report missing registration, registered-service unavailability, or an unreadable inventory from the preflight state without guessing, show its remediation, and relay the same validated findings.
57
54
 
58
55
  You have no edit tools. Remain read-only.
59
56
 
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=grok-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok-run.d.ts","sourceRoot":"","sources":["../src/grok-run.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { executeGrok } from "@ask-llm/grok-mcp/executor";
3
+ const prompt = process.argv.slice(2).join(" ");
4
+ async function readStdin() {
5
+ if (process.stdin.isTTY)
6
+ return "";
7
+ const chunks = [];
8
+ for await (const chunk of process.stdin)
9
+ chunks.push(chunk);
10
+ return Buffer.concat(chunks).toString().trim();
11
+ }
12
+ async function main() {
13
+ const stdin = await readStdin();
14
+ if (!prompt && !stdin) {
15
+ console.error("Usage: ask-grok-run <prompt>");
16
+ console.error(" echo 'diff' | ask-grok-run 'Review this diff'");
17
+ process.exit(1);
18
+ }
19
+ try {
20
+ const result = await executeGrok({ prompt: [prompt, stdin].filter(Boolean).join("\n\n") });
21
+ console.log(result.response);
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ console.error(`ask-grok-run failed: ${message}`);
26
+ process.exit(1);
27
+ }
28
+ }
29
+ main();
30
+ //# sourceMappingURL=grok-run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grok-run.js","sourceRoot":"","sources":["../src/grok-run.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,KAAK,UAAU,SAAS;IACtB,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAC;IAChC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC9C,OAAO,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACtE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3F,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7E;AAED,eAAO,MAAM,SAAS,EAAE,gBAAgB,EAqCvC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7E;AAED,eAAO,MAAM,SAAS,EAAE,gBAAgB,EA8CvC,CAAC"}
package/dist/index.js CHANGED
@@ -17,6 +17,15 @@ export const providers = [
17
17
  return result.response;
18
18
  },
19
19
  },
20
+ {
21
+ name: "grok",
22
+ command: "xai-api",
23
+ async execute(prompt) {
24
+ const { executeGrok } = await import("@ask-llm/grok-mcp/executor");
25
+ const result = await executeGrok({ prompt });
26
+ return result.response;
27
+ },
28
+ },
20
29
  {
21
30
  name: "ollama",
22
31
  command: "ollama",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,SAAS,GAAuB;IAC3C;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,QAAQ;QACjB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,OAAO;QAChB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACjD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,QAAQ;QACjB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,KAAK;QACd,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,mCAAmC,CAAC,CAAC;YACpF,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;CACF,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,MAAM,CAAC,MAAM,SAAS,GAAuB;IAC3C;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,QAAQ;QACjB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,OAAO;QAChB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,6BAA6B,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACjD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,SAAS;QAClB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,4BAA4B,CAAC,CAAC;YACnE,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,QAAQ;QACjB,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,8BAA8B,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YAClD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;IACD;QACE,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,KAAK;QACd,KAAK,CAAC,OAAO,CAAC,MAAc;YAC1B,MAAM,EAAE,qBAAqB,EAAE,GAAG,MAAM,MAAM,CAAC,mCAAmC,CAAC,CAAC;YACpF,MAAM,MAAM,GAAG,MAAM,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;KACF;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ask-llm/plugin",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Ask LLM review, comparison, brainstorming, image, verification, and pairing workflows for Claude Code and Pi",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,7 @@
8
8
  "ask-antigravity-run": "dist/antigravity-run.js",
9
9
  "ask-codex-run": "dist/codex-run.js",
10
10
  "ask-gemini-run": "dist/run.js",
11
+ "ask-grok-run": "dist/grok-run.js",
11
12
  "ask-ollama-run": "dist/ollama-run.js"
12
13
  },
13
14
  "scripts": {
@@ -21,6 +22,8 @@
21
22
  "claude-code-plugin",
22
23
  "codex",
23
24
  "gemini",
25
+ "grok",
26
+ "xai",
24
27
  "ollama",
25
28
  "antigravity",
26
29
  "code-review",
@@ -70,16 +73,19 @@
70
73
  "./skills/codex-verify/SKILL.md",
71
74
  "./skills/compare/SKILL.md",
72
75
  "./skills/gemini-review/SKILL.md",
76
+ "./skills/grok-review/SKILL.md",
73
77
  "./skills/multi-review/SKILL.md",
74
78
  "./skills/ollama-review/SKILL.md",
75
79
  "./skills/sol-review/SKILL.md"
76
80
  ]
77
81
  },
78
82
  "dependencies": {
79
- "@ask-llm/antigravity-mcp": "^0.7.1",
80
- "@ask-llm/codex-mcp": "^0.7.4",
81
- "@ask-llm/gemini-mcp": "^1.7.1",
82
- "@ask-llm/ollama-mcp": "^0.5.6"
83
+ "@ask-llm/antigravity-mcp": "^0.7.2",
84
+ "@ask-llm/codex-mcp": "^0.7.5",
85
+ "@ask-llm/gemini-mcp": "^1.7.2",
86
+ "@ask-llm/grok-mcp": "^0.1.1",
87
+ "@ask-llm/mcp": "^0.7.0",
88
+ "@ask-llm/ollama-mcp": "^0.5.7"
83
89
  },
84
90
  "peerDependencies": {
85
91
  "@earendil-works/pi-ai": "*",
@@ -52,6 +52,8 @@ const ALLOWLIST_LOCK_RETRY_MS = 25;
52
52
  const ALLOWLIST_LOCK_TIMEOUT_MS = 5_000;
53
53
  const ALLOWLIST_LOCK_STALE_MS = 30_000;
54
54
  const LOCK_CONTENTION_RETRY_MS = 100;
55
+ const CLAIM_SUFFIX = ".claim";
56
+ const CLAIM_STALE_MS = 30_000;
55
57
  const PI_PENDING_DIR = "pi-pending";
56
58
  const PI_REVIEWED_DIR = "pi-reviewed";
57
59
  const SKIP_PARTS = [
@@ -381,7 +383,7 @@ function sendFinding(pi: ExtensionAPI, finding: PairFinding): void {
381
383
 
382
384
  interface ClaimedFinding {
383
385
  claimPath: string;
384
- originalPath: string;
386
+ pendingPath: string;
385
387
  finding: PairFinding;
386
388
  }
387
389
 
@@ -398,39 +400,57 @@ function processIsAlive(pid: number): boolean {
398
400
  }
399
401
  }
400
402
 
403
+ function parseClaimOwner(content: string): number | undefined {
404
+ try {
405
+ const { pid } = JSON.parse(content) as { pid?: unknown };
406
+ return typeof pid === "number" && Number.isInteger(pid) ? pid : undefined;
407
+ } catch {
408
+ return undefined;
409
+ }
410
+ }
411
+
401
412
  async function recoverAbandonedClaims(markerDir: string): Promise<void> {
402
413
  const root = pendingRoot(markerDir);
403
414
  try {
404
- const names = (await readdir(root)).filter((name) => name.includes(".json.claim-")).sort();
415
+ const names = (await readdir(root)).filter((name) => name.endsWith(CLAIM_SUFFIX)).sort();
405
416
  for (const name of names) {
406
- const pid = Number(name.match(/\.claim-(\d+)-/)?.[1]);
407
- if (Number.isInteger(pid) && processIsAlive(pid)) continue;
408
417
  const claimPath = join(root, name);
409
- const originalPath = join(root, name.slice(0, name.indexOf(".claim-")));
418
+ const content = await readFile(claimPath, "utf8").catch(() => undefined);
419
+ const info = await stat(claimPath).catch(() => undefined);
420
+ // A live session releases and recreates claims at this same path, so only age plus an
421
+ // unchanged-identity recheck can tell an abandoned claim from one that was just taken.
422
+ if (content === undefined || !info || Date.now() - info.mtimeMs < CLAIM_STALE_MS) continue;
423
+ const pid = parseClaimOwner(content);
424
+ if (pid !== undefined && processIsAlive(pid)) continue;
425
+ const recheck = await readFile(claimPath, "utf8").catch(() => undefined);
426
+ const recheckInfo = await stat(claimPath).catch(() => undefined);
427
+ if (recheck !== content || recheckInfo?.mtimeMs !== info.mtimeMs) continue;
410
428
  try {
411
- await rename(claimPath, originalPath);
429
+ await unlink(claimPath);
412
430
  } catch {}
413
431
  }
414
432
  } catch {}
415
433
  }
416
434
 
417
435
  async function claimPending(path: string): Promise<ClaimedFinding | undefined> {
418
- const claimPath = `${path}.claim-${process.pid}-${randomUUID()}`;
436
+ const claimPath = `${path}${CLAIM_SUFFIX}`;
419
437
  try {
420
- await rename(path, claimPath);
438
+ await writeFile(claimPath, `${JSON.stringify({ pid: process.pid })}\n`, { flag: "wx", mode: 0o600 });
421
439
  } catch {
422
440
  return undefined;
423
441
  }
424
442
  try {
425
443
  return {
426
444
  claimPath,
427
- originalPath: path,
428
- finding: JSON.parse(await readFile(claimPath, "utf8")) as PairFinding,
445
+ pendingPath: path,
446
+ finding: JSON.parse(await readFile(path, "utf8")) as PairFinding,
429
447
  };
430
448
  } catch {
431
- try {
432
- await unlink(claimPath);
433
- } catch {}
449
+ for (const target of [path, claimPath]) {
450
+ try {
451
+ await unlink(target);
452
+ } catch {}
453
+ }
434
454
  return undefined;
435
455
  }
436
456
  }
@@ -498,30 +518,39 @@ export function registerCodexPair(pi: ExtensionAPI): void {
498
518
  return false;
499
519
  };
500
520
 
521
+ const releaseClaim = async (claimed: ClaimedFinding) => {
522
+ try {
523
+ await unlink(claimed.claimPath);
524
+ } catch {}
525
+ };
526
+
527
+ const consumeClaimed = async (claimed: ClaimedFinding) => {
528
+ try {
529
+ await unlink(claimed.pendingPath);
530
+ } catch {}
531
+ await releaseClaim(claimed);
532
+ };
533
+
501
534
  const deliverClaimed = async (claimed: ClaimedFinding) => {
502
535
  if (closed) {
503
- try {
504
- await rename(claimed.claimPath, claimed.originalPath);
505
- } catch {}
536
+ await releaseClaim(claimed);
506
537
  return;
507
538
  }
508
539
  try {
509
540
  const current = await readFile(claimed.finding.file);
510
541
  if (hash(current.toString("utf8")) !== claimed.finding.contentHash) {
511
- await unlink(claimed.claimPath);
542
+ await consumeClaimed(claimed);
512
543
  return;
513
544
  }
514
545
  if (closed) {
515
- await rename(claimed.claimPath, claimed.originalPath);
546
+ await releaseClaim(claimed);
516
547
  return;
517
548
  }
518
549
  sendFinding(pi, claimed.finding);
519
550
  await markReviewed(claimed.finding);
520
- await unlink(claimed.claimPath);
551
+ await consumeClaimed(claimed);
521
552
  } catch (error) {
522
- try {
523
- await rename(claimed.claimPath, claimed.originalPath);
524
- } catch {}
553
+ await releaseClaim(claimed);
525
554
  throw error;
526
555
  }
527
556
  };
@@ -531,8 +560,10 @@ export function registerCodexPair(pi: ExtensionAPI): void {
531
560
  while (!closed) {
532
561
  let names: string[];
533
562
  try {
534
- names = (await readdir(pendingRoot(markerDir)))
535
- .filter((name) => name.endsWith(".json"))
563
+ const entries = await readdir(pendingRoot(markerDir));
564
+ const claimedNames = new Set(entries.filter((name) => name.endsWith(CLAIM_SUFFIX)));
565
+ names = entries
566
+ .filter((name) => name.endsWith(".json") && !claimedNames.has(`${name}${CLAIM_SUFFIX}`))
536
567
  .sort()
537
568
  .slice(0, 8);
538
569
  } catch {
@@ -860,6 +891,7 @@ export const __testing = {
860
891
  activeConcerns,
861
892
  adaptiveContent,
862
893
  allowlistPath,
894
+ claimPending,
863
895
  deleteScheduledIfOwned,
864
896
  findMarkerUp,
865
897
  normalizeToolPath,
@@ -4,11 +4,14 @@ import { StringEnum } from "@earendil-works/pi-ai";
4
4
  import { executeTool as executeAntigravityTool } from "@ask-llm/antigravity-mcp/register";
5
5
  import { executeTool as executeCodexTool } from "@ask-llm/codex-mcp/register";
6
6
  import { executeTool as executeGeminiTool } from "@ask-llm/gemini-mcp/register";
7
+ import { executeTool as executeGrokTool } from "@ask-llm/grok-mcp/register";
7
8
  import { executeTool as executeOllamaTool } from "@ask-llm/ollama-mcp/register";
9
+ import { CURSOR_PROVIDERS, executeCursorAgent } from "@ask-llm/mcp/cursor";
8
10
  import { Type } from "typebox";
9
11
 
10
- const providerNames = ["codex", "gemini", "ollama", "antigravity"] as const;
12
+ const providerNames = ["codex", "gemini", "grok", "ollama", "antigravity"] as const;
11
13
  type ProviderName = (typeof providerNames)[number];
14
+ const cursorProviderNames = CURSOR_PROVIDERS;
12
15
 
13
16
  type CanonicalResult =
14
17
  | string
@@ -24,6 +27,7 @@ type CanonicalExecute = (
24
27
  const executors: Record<ProviderName, { tool: string; execute: CanonicalExecute }> = {
25
28
  codex: { tool: "ask-codex", execute: executeCodexTool as CanonicalExecute },
26
29
  gemini: { tool: "ask-gemini", execute: executeGeminiTool as CanonicalExecute },
30
+ grok: { tool: "ask-grok", execute: executeGrokTool as CanonicalExecute },
27
31
  ollama: { tool: "ask-ollama", execute: executeOllamaTool as CanonicalExecute },
28
32
  antigravity: { tool: "ask-antigravity", execute: executeAntigravityTool as CanonicalExecute },
29
33
  };
@@ -48,6 +52,12 @@ const geminiSchema = Type.Object({
48
52
  model: Type.Optional(Type.String({ minLength: 1 })),
49
53
  sessionId: Type.Optional(Type.String()),
50
54
  });
55
+ const grokSchema = Type.Object({
56
+ prompt,
57
+ model: Type.Optional(Type.String({ minLength: 1 })),
58
+ harness: Type.Optional(StringEnum(["xai-api", "grok-cli"] as const)),
59
+ reasoningEffort: Type.Optional(StringEnum(["low", "medium", "high", "xhigh"] as const)),
60
+ });
51
61
  const ollamaSchema = Type.Object({
52
62
  prompt,
53
63
  model: Type.Optional(Type.String({ minLength: 1 })),
@@ -61,21 +71,36 @@ const antigravitySchema = Type.Object({
61
71
  const providerOptionSchemas = {
62
72
  codex: Type.Omit(codexSchema, ["prompt"]),
63
73
  gemini: Type.Omit(geminiSchema, ["prompt"]),
74
+ grok: Type.Omit(grokSchema, ["prompt"]),
64
75
  ollama: Type.Omit(ollamaSchema, ["prompt"]),
65
76
  antigravity: Type.Omit(antigravitySchema, ["prompt"]),
66
77
  };
67
78
 
79
+ const cursorAgentSchema = Type.Object({
80
+ prompt,
81
+ provider: StringEnum(cursorProviderNames, {
82
+ description:
83
+ "Canonical provider family of the Cursor model (claude, codex, gemini, grok); verified against the requested and CLI-reported model ID.",
84
+ }),
85
+ model: Type.String({
86
+ minLength: 1,
87
+ description:
88
+ "Exact ID from agent --list-models; echoed back as `model`, with the CLI display label in `reportedModel`. Auto and other noncanonical IDs are refused.",
89
+ }),
90
+ });
91
+
68
92
  const askMultiSchema = Type.Object({
69
93
  prompt,
70
94
  providers: Type.Array(StringEnum(providerNames), {
71
95
  minItems: 2,
72
- maxItems: 4,
73
- description: "Two to four unique providers. Results preserve this input order.",
96
+ maxItems: 5,
97
+ description: "Two to five unique providers. Results preserve this input order.",
74
98
  }),
75
99
  options: Type.Optional(
76
100
  Type.Object({
77
101
  codex: Type.Optional(providerOptionSchemas.codex),
78
102
  gemini: Type.Optional(providerOptionSchemas.gemini),
103
+ grok: Type.Optional(providerOptionSchemas.grok),
79
104
  ollama: Type.Optional(providerOptionSchemas.ollama),
80
105
  antigravity: Type.Optional(providerOptionSchemas.antigravity),
81
106
  }),
@@ -128,7 +153,7 @@ type ProgressUpdate = {
128
153
  details: Record<string, unknown>;
129
154
  };
130
155
 
131
- function progressForwarder(onUpdate: ((result: ProgressUpdate) => void) | undefined, provider: ProviderName) {
156
+ function progressForwarder(onUpdate: ((result: ProgressUpdate) => void) | undefined, provider: string) {
132
157
  return onUpdate
133
158
  ? (text: string) => {
134
159
  const output = bounded(text);
@@ -178,6 +203,14 @@ export function registerProviderTools(pi: ExtensionAPI): void {
178
203
  parameters: geminiSchema,
179
204
  provider: "gemini",
180
205
  });
206
+ registerProviderTool(pi, {
207
+ name: "ask-grok",
208
+ label: "Ask Grok",
209
+ description:
210
+ "Consult Grok through Ask LLM's canonical xAI API executor. Requires XAI_API_KEY and may incur metered API charges; no billing changes or model fallback are performed. Output is bounded to Pi's 50KB/2000-line limits.",
211
+ parameters: grokSchema,
212
+ provider: "grok",
213
+ });
181
214
  registerProviderTool(pi, {
182
215
  name: "ask-ollama",
183
216
  label: "Ask Ollama",
@@ -195,11 +228,40 @@ export function registerProviderTools(pi: ExtensionAPI): void {
195
228
  provider: "antigravity",
196
229
  });
197
230
 
231
+ pi.registerTool({
232
+ name: "ask-cursor-agent",
233
+ label: "Ask via Cursor Agent",
234
+ description:
235
+ "Use Cursor Agent as a model-neutral read-only harness. Provider (claude, codex, gemini, grok) and exact model ID are separate and must agree; Auto or noncanonical catalog IDs are refused. Prompts above 16KB are piped over stdin. Requires an authenticated Cursor CLI and may consume included usage or on-demand spend; no spend settings or fallback are changed.",
236
+ parameters: cursorAgentSchema,
237
+ async execute(_toolCallId, params, signal, onUpdate) {
238
+ const result = await executeCursorAgent({
239
+ prompt: params.prompt,
240
+ provider: params.provider,
241
+ model: params.model,
242
+ signal,
243
+ onProgress: progressForwarder(onUpdate, params.provider),
244
+ });
245
+ const text = bounded(result.response);
246
+ return {
247
+ content: [{ type: "text", text: text.text }],
248
+ details: {
249
+ provider: result.provider,
250
+ harness: result.harness,
251
+ model: result.model,
252
+ reportedModel: result.reportedModel,
253
+ askLlmUsage: result.usage,
254
+ outputTruncated: text.truncated,
255
+ },
256
+ };
257
+ },
258
+ });
259
+
198
260
  pi.registerTool({
199
261
  name: "ask-multi",
200
262
  label: "Ask Multiple Providers",
201
263
  description:
202
- "Send exactly the same prompt to two to four Ask LLM providers concurrently. Dispatch is deterministic and bounded; results preserve provider input order and report every failure instead of silently dropping it.",
264
+ "Send exactly the same prompt to two to five Ask LLM providers concurrently. Dispatch is deterministic and bounded; results preserve provider input order and report every failure instead of silently dropping it.",
203
265
  parameters: askMultiSchema,
204
266
  async execute(_toolCallId, params, signal, onUpdate) {
205
267
  const unique = [...new Set(params.providers)];
@@ -14,6 +14,23 @@ import { platform } from "node:process";
14
14
 
15
15
  export const IS_WINDOWS = platform === "win32";
16
16
 
17
+ export function quoteArgsForWindows(args) {
18
+ return args.map((arg) => {
19
+ if (arg.includes(" ") || arg.includes('"') || arg.includes("&") || arg.includes("|") || arg.includes("^")) {
20
+ return `"${arg.replace(/"/g, '\\"')}"`;
21
+ }
22
+ return arg;
23
+ });
24
+ }
25
+
26
+ export function prepareCommandInvocation(args, options, runtimePlatform = platform) {
27
+ const isWindows = runtimePlatform === "win32";
28
+ return {
29
+ args: isWindows ? quoteArgsForWindows(args) : args,
30
+ options: { ...options, shell: isWindows },
31
+ };
32
+ }
33
+
17
34
  export function terminateProcessTree(child, signal) {
18
35
  if (!child || typeof child.pid !== "number" || child.killed || child.exitCode !== null) {
19
36
  return;
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn, spawnSync } from "node:child_process";
4
+ import { resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { prepareCommandInvocation } from "./lib/process.mjs";
7
+
8
+ export const ASK_CODEX_PACKAGE = "@ask-llm/codex-mcp";
9
+ export const ASK_CODEX_TOOL = "ask-codex";
10
+ export const SOL_MODEL = "gpt-5.6-sol";
11
+ export const TERRA_MODEL = "gpt-5.6-terra";
12
+
13
+ const scriptPath = fileURLToPath(import.meta.url);
14
+ const quotaSignals = [
15
+ "rate_limit_exceeded",
16
+ "quota_exceeded",
17
+ "429",
18
+ "insufficient_quota",
19
+ "out of credits",
20
+ "spend cap",
21
+ "usage limit",
22
+ ];
23
+
24
+ export function isAskCodexToolName(name) {
25
+ return name === ASK_CODEX_TOOL || /^mcp__.+__ask-codex$/.test(name);
26
+ }
27
+
28
+ export function isAskCodexRegistration(server) {
29
+ if (!server || typeof server !== "object") return false;
30
+ const command = typeof server.command === "string" ? server.command : "";
31
+ const args = Array.isArray(server.args) ? server.args.filter((arg) => typeof arg === "string") : [];
32
+ const commandLine = typeof server.commandLine === "string" ? server.commandLine : "";
33
+ return (
34
+ /(?:^|[/\\])ask-codex-mcp(?:\.cmd|\.exe)?$/.test(command) ||
35
+ args.includes(ASK_CODEX_PACKAGE) ||
36
+ /(?:^|\s)@ask-llm\/codex-mcp(?:@[^\s]+)?(?:\s|$)/.test(commandLine) ||
37
+ /(?:^|[/\\])ask-codex-mcp(?:\.cmd|\.exe)?(?:\s|$)/.test(commandLine)
38
+ );
39
+ }
40
+
41
+ export function parseClaudeMcpList(output) {
42
+ const servers = {};
43
+ for (const line of output.split(/\r?\n/)) {
44
+ const separator = line.indexOf(": ");
45
+ if (separator <= 0) continue;
46
+ const name = line.slice(0, separator).trim();
47
+ const details = line.slice(separator + 2).trim();
48
+ const statusSeparator = details.search(/ - (?=(?:✔|✘|!|⏸|cached\b|not configured\b|disabled\b))/i);
49
+ servers[name] = {
50
+ commandLine: statusSeparator === -1 ? details : details.slice(0, statusSeparator).trim(),
51
+ status: statusSeparator === -1 ? "" : details.slice(statusSeparator + 3).trim(),
52
+ };
53
+ }
54
+ return servers;
55
+ }
56
+
57
+ export function readActiveMcpServers({
58
+ command = process.env.CLAUDE_BIN || "claude",
59
+ execute = spawnSync,
60
+ platform = process.platform,
61
+ contextArgs = [],
62
+ } = {}) {
63
+ const invocation = prepareCommandInvocation(
64
+ [...contextArgs, "mcp", "list"],
65
+ {
66
+ cwd: process.cwd(),
67
+ encoding: "utf8",
68
+ windowsHide: true,
69
+ },
70
+ platform,
71
+ );
72
+ const result = execute(command, invocation.args, invocation.options);
73
+ if (result.error || result.status !== 0) {
74
+ const detail = result.error?.message || result.stderr?.trim() || `exited ${result.status}`;
75
+ throw new Error(`Unable to inspect active Claude MCP registrations: ${detail}.`);
76
+ }
77
+ return parseClaudeMcpList(result.stdout || "");
78
+ }
79
+
80
+ function expectedToolName(serverName) {
81
+ return `mcp__${serverName.replaceAll(":", "_")}__${ASK_CODEX_TOOL}`;
82
+ }
83
+
84
+ function isAvailableMcpServer(server) {
85
+ const status = typeof server?.status === "string" ? server.status.trim() : "";
86
+ return !status || /^✔\s*Connected\b/i.test(status) || /^cached\b.*\bconnects on first use\b/i.test(status);
87
+ }
88
+
89
+ export function classifySolReviewTransport({
90
+ availableTools = [],
91
+ mcpServers = {},
92
+ cliPath = "",
93
+ inventoryError = null,
94
+ mcpFailed = false,
95
+ }) {
96
+ if (inventoryError) {
97
+ const reason = `Ask LLM Codex MCP availability could not be determined because the active Claude MCP inventory could not be inspected: ${inventoryError}`;
98
+ const remediation = "Run `claude mcp list`, resolve the inventory failure, then fully restart Claude Code.";
99
+ if (!cliPath) {
100
+ return {
101
+ state: "inventory-unavailable",
102
+ transport: null,
103
+ toolName: null,
104
+ diagnostic: `${reason} The explicit CLI fallback is also unavailable.`,
105
+ remediation: `${remediation} Install the fallback with \`npm install -g @openai/codex\` if needed.`,
106
+ fallbackDisclosure: null,
107
+ };
108
+ }
109
+ return {
110
+ state: "inventory-unavailable",
111
+ transport: "cli",
112
+ toolName: null,
113
+ diagnostic: reason,
114
+ remediation,
115
+ fallbackDisclosure: `Transport disclosure: ${reason} Running the review through the explicit \`codex exec\` CLI fallback without claiming MCP registration or availability; validated findings will be relayed unchanged.`,
116
+ };
117
+ }
118
+
119
+ const registrations = Object.entries(mcpServers).filter(([, server]) => isAskCodexRegistration(server));
120
+ const availableRegistrations = registrations.filter(([, server]) => isAvailableMcpServer(server));
121
+ const registeredToolNames = new Set(availableRegistrations.map(([name]) => expectedToolName(name)));
122
+ const toolName = availableTools.find((name) => isAskCodexToolName(name) && registeredToolNames.has(name));
123
+ if (toolName && !mcpFailed) {
124
+ return {
125
+ state: "preferred",
126
+ transport: "mcp",
127
+ toolName,
128
+ diagnostic: `Ask LLM Codex transport available as ${toolName}.`,
129
+ remediation: null,
130
+ fallbackDisclosure: null,
131
+ };
132
+ }
133
+
134
+ const registered = registrations.length > 0;
135
+ const state = registered || mcpFailed ? "unavailable" : "missing-registration";
136
+ const remediation =
137
+ registered || mcpFailed
138
+ ? "Run `npx -y @ask-llm/mcp doctor`, inspect `/mcp`, then fully restart Claude Code."
139
+ : "Run `claude mcp add --scope user codex -- npx -y @ask-llm/codex-mcp`, fully restart Claude Code, then verify with `/mcp`.";
140
+ const unavailableRegistration = registrations.find(([, server]) => !isAvailableMcpServer(server));
141
+ const reason = mcpFailed
142
+ ? "Ask LLM Codex MCP invocation failed in this session, so the preferred transport is unavailable."
143
+ : unavailableRegistration
144
+ ? `Ask LLM Codex MCP is registered, but active inventory reports it unavailable: ${unavailableRegistration[1].status}.`
145
+ : registered
146
+ ? "Ask LLM Codex MCP is registered, but its `ask-codex` tool is unavailable in this session."
147
+ : "Ask LLM Codex MCP registration is missing from this Claude Code installation.";
148
+
149
+ if (!cliPath) {
150
+ return {
151
+ state,
152
+ transport: null,
153
+ toolName: null,
154
+ diagnostic: `${reason} The explicit CLI fallback is also unavailable.`,
155
+ remediation: `${remediation} Install the fallback with \`npm install -g @openai/codex\` if needed.`,
156
+ fallbackDisclosure: null,
157
+ };
158
+ }
159
+
160
+ return {
161
+ state,
162
+ transport: "cli",
163
+ toolName: null,
164
+ diagnostic: reason,
165
+ remediation,
166
+ fallbackDisclosure: `Transport disclosure: ${reason} Running the review through the explicit \`codex exec\` CLI fallback; validated findings will be relayed unchanged.`,
167
+ };
168
+ }
169
+
170
+ export function codexFallbackArgs(model) {
171
+ return [
172
+ "exec",
173
+ "-m",
174
+ model,
175
+ "-c",
176
+ 'model_reasoning_effort="high"',
177
+ "-s",
178
+ "read-only",
179
+ "--ignore-user-config",
180
+ "--ignore-rules",
181
+ "--skip-git-repo-check",
182
+ ];
183
+ }
184
+
185
+ export function executeCodex({ command, model, prompt, spawnProcess = spawn, platform = process.platform }) {
186
+ return new Promise((resolveRun) => {
187
+ const invocation = prepareCommandInvocation(
188
+ codexFallbackArgs(model),
189
+ {
190
+ cwd: process.cwd(),
191
+ env: process.env,
192
+ stdio: ["pipe", "pipe", "pipe"],
193
+ windowsHide: true,
194
+ },
195
+ platform,
196
+ );
197
+ const child = spawnProcess(command, invocation.args, invocation.options);
198
+ let stdout = "";
199
+ let stderr = "";
200
+ child.stdout.setEncoding("utf8");
201
+ child.stderr.setEncoding("utf8");
202
+ child.stdout.on("data", (chunk) => {
203
+ stdout += chunk;
204
+ });
205
+ child.stderr.on("data", (chunk) => {
206
+ stderr += chunk;
207
+ });
208
+ child.on("error", (error) => {
209
+ resolveRun({ code: 127, stdout, stderr: `${stderr}${error.message}` });
210
+ });
211
+ child.on("close", (code) => {
212
+ resolveRun({ code: code ?? 1, stdout, stderr });
213
+ });
214
+ child.stdin.on("error", () => {});
215
+ child.stdin.end(prompt);
216
+ });
217
+ }
218
+
219
+ export async function runCliFallback({
220
+ prompt,
221
+ command = process.env.ASK_CODEX_BIN || "codex",
222
+ fallbackModel = process.env.ASK_CODEX_FALLBACK_MODEL || TERRA_MODEL,
223
+ execute = executeCodex,
224
+ }) {
225
+ const primary = await execute({ command, model: SOL_MODEL, prompt });
226
+ if (primary.code === 0) {
227
+ return { response: primary.stdout, diagnostics: primary.stderr, model: SOL_MODEL, fellBack: false };
228
+ }
229
+
230
+ const primaryOutput = `${primary.stderr}\n${primary.stdout}`;
231
+ const normalizedPrimaryOutput = primaryOutput.toLowerCase();
232
+ const quotaFailure = quotaSignals.some((signal) => normalizedPrimaryOutput.includes(signal));
233
+ if (!quotaFailure || fallbackModel === SOL_MODEL) {
234
+ throw new Error(primaryOutput.trim() || `codex exec exited ${primary.code}`);
235
+ }
236
+
237
+ const fallback = await execute({ command, model: fallbackModel, prompt });
238
+ if (fallback.code !== 0) {
239
+ const fallbackOutput = `${fallback.stderr}\n${fallback.stdout}`.trim();
240
+ throw new Error(`Sol review failed and ${fallbackModel} fallback also failed: ${fallbackOutput}`);
241
+ }
242
+
243
+ return {
244
+ response: fallback.stdout,
245
+ diagnostics: fallback.stderr,
246
+ model: fallbackModel,
247
+ fellBack: true,
248
+ };
249
+ }
250
+
251
+ function parseArgs(args) {
252
+ const parsed = {
253
+ tools: [],
254
+ cliPath: "",
255
+ mcpList: null,
256
+ fallback: false,
257
+ mcpFailed: false,
258
+ claudeContextArgs: [],
259
+ };
260
+ const claudeContextValueFlags = new Set(["--plugin-dir", "--mcp-config", "--settings", "--setting-sources"]);
261
+ for (let index = 0; index < args.length; index += 1) {
262
+ const arg = args[index];
263
+ if (arg === "--fallback") parsed.fallback = true;
264
+ else if (arg === "--mcp-failed") parsed.mcpFailed = true;
265
+ else if (arg === "--tool") parsed.tools.push(args[++index] ?? "");
266
+ else if (arg === "--cli-path") parsed.cliPath = args[++index] ?? "";
267
+ else if (arg === "--mcp-list") parsed.mcpList = args[++index] ?? "";
268
+ else if (arg === "--strict-mcp-config") parsed.claudeContextArgs.push(arg);
269
+ else if (claudeContextValueFlags.has(arg)) {
270
+ const value = args[++index];
271
+ if (value === undefined) throw new Error(`Missing value for ${arg}`);
272
+ parsed.claudeContextArgs.push(arg, value);
273
+ } else throw new Error(`Unknown argument: ${arg}`);
274
+ }
275
+ return parsed;
276
+ }
277
+
278
+ function readMcpServers(parsed) {
279
+ if (parsed.mcpList !== null) {
280
+ return { mcpServers: parseClaudeMcpList(parsed.mcpList), inventoryError: null };
281
+ }
282
+ try {
283
+ return {
284
+ mcpServers: readActiveMcpServers({ contextArgs: parsed.claudeContextArgs }),
285
+ inventoryError: null,
286
+ };
287
+ } catch (error) {
288
+ return {
289
+ mcpServers: {},
290
+ inventoryError: error instanceof Error ? error.message : String(error),
291
+ };
292
+ }
293
+ }
294
+
295
+ async function main() {
296
+ const parsed = parseArgs(process.argv.slice(2));
297
+ const { mcpServers, inventoryError } = readMcpServers(parsed);
298
+ const decision = classifySolReviewTransport({
299
+ availableTools: parsed.tools,
300
+ mcpServers,
301
+ cliPath: parsed.cliPath,
302
+ inventoryError,
303
+ mcpFailed: parsed.mcpFailed,
304
+ });
305
+
306
+ if (parsed.fallback) {
307
+ if (decision.transport !== "cli") {
308
+ if (decision.transport === "mcp") {
309
+ throw new Error(`Ask LLM Codex MCP is available as ${decision.toolName}; CLI fallback was not started.`);
310
+ }
311
+ throw new Error(`${decision.diagnostic} ${decision.remediation}`);
312
+ }
313
+ process.stderr.write(`${decision.fallbackDisclosure}\nRemediation: ${decision.remediation}\n`);
314
+ let prompt = "";
315
+ process.stdin.setEncoding("utf8");
316
+ for await (const chunk of process.stdin) prompt += chunk;
317
+ if (!prompt.trim()) throw new Error("Sol review CLI fallback requires a prompt on stdin.");
318
+ const result = await runCliFallback({ prompt, command: parsed.cliPath });
319
+ process.stderr.write(
320
+ `Transport disclosure: review ran through codex exec (${result.model}, high effort, read-only).\n`,
321
+ );
322
+ if (result.fellBack) {
323
+ process.stderr.write(`Model fallback disclosure: Sol hit quota; review completed on ${result.model}.\n`);
324
+ }
325
+ if (result.diagnostics) process.stderr.write(result.diagnostics);
326
+ process.stdout.write(result.response);
327
+ return;
328
+ }
329
+
330
+ process.stdout.write(`${JSON.stringify(decision)}\n`);
331
+ if (!decision.transport) process.exitCode = 1;
332
+ }
333
+
334
+ if (resolve(process.argv[1] || "") === scriptPath) {
335
+ main().catch((error) => {
336
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
337
+ process.exitCode = 1;
338
+ });
339
+ }
@@ -30,9 +30,10 @@ Consult multiple external LLM providers simultaneously on a topic while Claude O
30
30
 
31
31
  ### Phase 1: Parse arguments
32
32
 
33
- - If the first argument looks like a comma-separated provider list (e.g., `antigravity,codex` or `gemini,codex,ollama`), use those as the external providers
33
+ - If the first argument looks like a comma-separated provider list (e.g., `antigravity,codex` or `gemini,codex,grok,ollama`), use those as the external providers
34
34
  - If no provider list is given, default to `antigravity,codex`
35
- - Valid external providers: `gemini`, `codex`, `ollama`, `antigravity`
35
+ - Valid external providers: `gemini`, `codex`, `grok`, `ollama`, `antigravity`
36
+ - `grok` requires `XAI_API_KEY` for the default API harness or official Grok Build auth with `ASK_GROK_HARNESS=grok-cli`; it may consume metered/plan usage and never falls back
36
37
  - `antigravity` requires `agy` installed + logged in; if it's unavailable the coordinator surfaces that and continues with the other providers
37
38
  - Everything after the provider list (or all args if no list) is the topic
38
39
  - Claude Opus is always a participant — it's not in the provider list because it runs inside the coordinator
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: brainstorm-all
3
- description: Send a topic to all external providers (Gemini, Codex, Ollama, Antigravity) concurrently after the current host model forms an independent view. Use when the user wants an all-provider brainstorm with synthesis and explicit unavailable-provider reporting.
3
+ description: Send a topic to all external providers (Gemini, Codex, Grok, Ollama, Antigravity) concurrently after the current host model forms an independent view. Use when the user wants an all-provider brainstorm with synthesis and explicit unavailable-provider reporting.
4
4
  ---
5
5
 
6
6
  <!-- PORTABLE-CONTRACT:START -->
7
7
  ## Portable contract
8
8
 
9
- Apply the brainstorm contract with all four external providers: Gemini, Codex, Ollama, and Antigravity. The current host model forms its independent view first; unavailable providers are reported, not silently omitted; synthesis distinguishes verified evidence from inference.
9
+ Apply the brainstorm contract with all five external providers: Gemini, Codex, Grok, Ollama, and Antigravity. The current host model forms its independent view first; unavailable providers are reported, not silently omitted; synthesis distinguishes verified evidence from inference.
10
10
  <!-- PORTABLE-CONTRACT:END -->
11
11
 
12
12
  ## Host adapters
13
13
 
14
14
  ### Pi adapter
15
15
 
16
- Follow `/skill:brainstorm` semantics with `ask-multi` providers `gemini,codex,ollama,antigravity`, after the current Pi host model has committed its independent view.
16
+ Follow `/skill:brainstorm` semantics with `ask-multi` providers `gemini,codex,grok,ollama,antigravity`, after the current Pi host model has committed its independent view.
17
17
 
18
18
  <!-- HOST-ADAPTER:CLAUDE-CODE:START -->
19
19
  ### Claude Code adapter
@@ -24,7 +24,7 @@ The existing detailed workflow below is the Claude Code adapter. Its Agent, MCP,
24
24
 
25
25
  # Multi-LLM Brainstorm (All Providers)
26
26
 
27
- Consult all available external LLM providers (Gemini, Codex, Ollama, Antigravity) simultaneously while Claude Opus performs its own independent research on the topic, then synthesize perspectives from all five participants.
27
+ Consult all available external LLM providers (Gemini, Codex, Grok, Ollama, Antigravity) simultaneously while Claude Opus performs its own independent research on the topic, then synthesize perspectives from all six participants.
28
28
 
29
29
  ## Instructions
30
30
 
@@ -35,9 +35,9 @@ Consult all available external LLM providers (Gemini, Codex, Ollama, Antigravity
35
35
 
36
36
  2. If no topic is clear, ask the user what they'd like to brainstorm about.
37
37
 
38
- 3. Launch the `brainstorm-coordinator` agent with the topic, external providers set to `gemini,codex,ollama,antigravity`, and any gathered context. The coordinator will:
38
+ 3. Launch the `brainstorm-coordinator` agent with the topic, external providers set to `gemini,codex,grok,ollama,antigravity`, and any gathered context. The coordinator will:
39
39
  - Run its own Claude Opus research phase in parallel with the external dispatches (Phase 3B — reads actual files, traces code, uses WebFetch/WebSearch on referenced external docs)
40
- - Dispatch the topic to the four external providers in parallel (Phase 3A)
40
+ - Dispatch the topic to the five external providers in parallel (Phase 3A)
41
41
  - Synthesize all findings with Claude's verified findings weighted higher than inferred ones
42
42
 
43
43
  <!-- HOST-ADAPTER:CLAUDE-CODE:END -->
@@ -1,19 +1,19 @@
1
1
  ---
2
2
  name: compare
3
- description: This skill should be used when the user asks to "compare LLMs", "see how each provider answers", "side-by-side response", "what do Gemini, Codex, Ollama, and Antigravity think", or wants raw responses from multiple providers without synthesis. Unlike /brainstorm (which synthesizes findings) or /multi-review (which validates code reviews), /compare just shows each provider's answer side-by-side.
3
+ description: This skill should be used when the user asks to "compare LLMs", "see how each provider answers", "side-by-side response", "what do Gemini, Codex, Grok, Ollama, and Antigravity think", or wants raw responses from multiple providers without synthesis. Unlike /brainstorm (which synthesizes findings) or /multi-review (which validates code reviews), /compare just shows each provider's answer side-by-side.
4
4
  ---
5
5
 
6
6
  <!-- PORTABLE-CONTRACT:START -->
7
7
  ## Portable contract
8
8
 
9
- Send the exact same bounded prompt to two to four selected providers concurrently. Preserve input order in the result, show each response verbatim without synthesis or adjudication, and show every provider failure instead of silently dropping it. Never invoke raw provider CLIs when a canonical Ask LLM bridge is available.
9
+ Send the exact same bounded prompt to two to five selected providers concurrently. Preserve input order in the result, show each response verbatim without synthesis or adjudication, and show every provider failure instead of silently dropping it. Never invoke raw provider CLIs when a canonical Ask LLM bridge is available.
10
10
  <!-- PORTABLE-CONTRACT:END -->
11
11
 
12
12
  ## Host adapters
13
13
 
14
14
  ### Pi adapter
15
15
 
16
- Use one native `ask-multi` call. Put the common prompt in `prompt` and two to four unique names in `providers`; its implementation, not model-emitted sibling calls, guarantees concurrent bounded dispatch and stable ordering.
16
+ Use one native `ask-multi` call. Put the common prompt in `prompt` and two to five unique names in `providers`; its implementation, not model-emitted sibling calls, guarantees concurrent bounded dispatch and stable ordering.
17
17
 
18
18
  <!-- HOST-ADAPTER:CLAUDE-CODE:START -->
19
19
  ### Claude Code adapter
@@ -41,8 +41,8 @@ If you're reviewing a code diff → use `/multi-review` instead.
41
41
 
42
42
  Extract from the user's message:
43
43
  1. **The question/prompt** to send to all providers (the meaningful payload)
44
- 2. **Optional provider filter** — if the user says "compare gemini and codex", only those two; otherwise default to all four (gemini, codex, ollama, antigravity)
45
- 3. **Optional context files** — if the user references files (`@path/to/file`), Read each referenced file and inline its contents into the shared provider prompt under a clearly labeled `<context_file path="...">` block. `@file` expansion is Gemini-only; passing the literal path to Codex, Ollama, or Antigravity silently drops the context. For a file too large to inline safely, include the relevant excerpts and state what was omitted.
44
+ 2. **Optional provider filter** — if the user says "compare gemini and codex", only those two; otherwise default to all five (gemini, codex, grok, ollama, antigravity)
45
+ 3. **Optional context files** — if the user references files (`@path/to/file`), Read each referenced file and inline its contents into the shared provider prompt under a clearly labeled `<context_file path="...">` block. `@file` expansion is Gemini-only; passing the literal path to Codex, Ollama, Grok, or Antigravity silently drops the context. For a file too large to inline safely, include the relevant excerpts and state what was omitted.
46
46
 
47
47
  If the question is missing or ambiguous, ask the user to clarify before dispatching.
48
48
 
@@ -61,6 +61,9 @@ gem_pid=$!
61
61
  GMCPT_TIMEOUT_MS=480000 node ${CLAUDE_PLUGIN_ROOT}/dist/codex-run.js "$PROMPT" > "$workdir/codex.out" 2> "$workdir/codex.err" &
62
62
  codex_pid=$!
63
63
 
64
+ GMCPT_TIMEOUT_MS=480000 node ${CLAUDE_PLUGIN_ROOT}/dist/grok-run.js "$PROMPT" > "$workdir/grok.out" 2> "$workdir/grok.err" &
65
+ grok_pid=$!
66
+
64
67
  GMCPT_TIMEOUT_MS=480000 node ${CLAUDE_PLUGIN_ROOT}/dist/ollama-run.js "$PROMPT" > "$workdir/ollama.out" 2> "$workdir/ollama.err" &
65
68
  ollama_pid=$!
66
69
 
@@ -69,6 +72,7 @@ antigravity_pid=$!
69
72
 
70
73
  gemini_rc=0; wait $gem_pid || gemini_rc=$?
71
74
  codex_rc=0; wait $codex_pid || codex_rc=$?
75
+ grok_rc=0; wait $grok_pid || grok_rc=$?
72
76
  ollama_rc=0; wait $ollama_pid || ollama_rc=$?
73
77
  antigravity_rc=0; wait $antigravity_pid || antigravity_rc=$?
74
78
 
@@ -85,6 +89,7 @@ dump_provider() {
85
89
 
86
90
  dump_provider gemini "$gemini_rc"
87
91
  dump_provider codex "$codex_rc"
92
+ dump_provider grok "$grok_rc"
88
93
  dump_provider ollama "$ollama_rc"
89
94
  dump_provider antigravity "$antigravity_rc"
90
95
  ```
@@ -110,6 +115,9 @@ Output structure:
110
115
  ### Codex
111
116
  > <verbatim provider response>
112
117
 
118
+ ### Grok
119
+ > <verbatim provider response>
120
+
113
121
  ### Ollama
114
122
  > <verbatim provider response>
115
123
 
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: grok-review
3
+ description: Get an independent Grok code review through the xAI API. Use when the user asks to review with Grok or wants a Grok second opinion. Requires XAI_API_KEY and explicit acceptance of metered xAI API pricing.
4
+ ---
5
+
6
+ <!-- PORTABLE-CONTRACT:START -->
7
+ ## Portable contract
8
+
9
+ Gather the relevant staged, unstaged, and untracked changes; build a bounded context brief; request an independent Grok review; verify every reported finding against source; and return only prioritized, source-supported findings. State that project context is sent to xAI and may incur API charges. Never enable billing, buy credits, use priority processing, substitute models, or retry on another model.
10
+ <!-- PORTABLE-CONTRACT:END -->
11
+
12
+ ## Host adapters
13
+
14
+ ### Pi adapter
15
+
16
+ Call the native `ask-grok` tool and apply only the `Portable contract` section of `../../agents/grok-reviewer.md`; ignore that file's frontmatter and Claude Code adapter.
17
+
18
+ <!-- HOST-ADAPTER:CLAUDE-CODE:START -->
19
+ ### Claude Code adapter
20
+
21
+ Review current code changes by delegating to the `grok-reviewer` agent.
22
+
23
+ 1. Confirm the user configured `XAI_API_KEY` and understands xAI API usage is metered separately from any Grok consumer subscription. Do not configure billing or credits.
24
+ 2. Gather `git diff`, `git diff --cached`, and relevant untracked files. If there are no changes, say so.
25
+ 3. Launch the `grok-reviewer` agent with a bounded diff and relevant project instructions.
26
+ 4. If the Grok MCP tool is unavailable, explain how to register it without attempting another provider. For API use:
27
+ `claude mcp add --scope user grok -e XAI_API_KEY="$XAI_API_KEY" -- npx -y @ask-llm/grok-mcp`
28
+ For official Grok Build, install/authenticate `grok`, set `ASK_GROK_HARNESS=grok-cli` in the MCP environment, and register the same package without silently switching from API.
29
+ 5. Preserve provider/model/harness attribution and any xAI error verbatim after secret redaction. Never silently route to Codex, Gemini, or another model.
30
+ <!-- HOST-ADAPTER:CLAUDE-CODE:END -->
@@ -32,10 +32,14 @@ Run a read-only, precision-first review explicitly pinned to GPT-5.6 Sol at high
32
32
  2. Include untracked files the user wants reviewed with `git add -N <path>` so their contents appear in the diff without staging them.
33
33
  3. If the combined diff is empty, report that there are no changes to review.
34
34
  4. Read the root and file-scoped `CLAUDE.md` files plus any ADRs cited by changed code.
35
- 5. Preflight the transport: check whether an `ask-codex` MCP tool is available in the current session (the name may be plugin-namespaced, e.g. `mcp__plugin_ask-llm_codex__ask-codex`). Subagents do not always inherit the session's MCP servers, so treat availability here as advisory only. If no MCP tool resolves, confirm the `codex` CLI is on `PATH` (`command -v codex`); if neither transport exists, stop and tell the user to install one instead of launching the agent.
36
- 6. Launch the `sol-reviewer` agent with the diff and a compact context brief containing the changed files, applicable conventions, referenced ADRs, the user's requested review focus, and the preflight result (which transport to expect).
35
+ 5. Preflight the transport through the shipped executable contract:
36
+ - Search the current tool surface for the exact `ask-codex` leaf tool. The executable correlates its client-assigned server prefix with an active `@ask-llm/codex-mcp` registration; similarly named tools and tools from unrelated servers are not authoritative.
37
+ - Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/sol-review-transport.mjs" --plugin-dir "${CLAUDE_PLUGIN_ROOT}" --cli-path "$(command -v codex || true)"`, adding `--tool "<resolved tool name>"` only when the exact `ask-codex` tool resolved. Mirror any session-local `--mcp-config`, `--settings`, `--setting-sources`, and `--strict-mcp-config` flags so the nested inventory sees the same configuration as the active session.
38
+ - Preserve the returned `state`, `diagnostic`, `remediation`, and `fallbackDisclosure`. The executable reads `claude mcp list` with that session context: `missing-registration` means that active inventory has no supported registration; `unavailable` means it contains the registration but the current tool surface does not expose a usable tool, reports failed health, or the MCP invocation failed; `inventory-unavailable` means the active inventory could not be inspected, so registration and service health are unknown. Do not collapse those states into a generic "no MCP" message. If `transport` is null, stop and show the executable remediation instead of launching the agent.
39
+ - Parent availability remains advisory because subagents do not always inherit the session's MCP servers. The reviewer's fallback runner re-reads the active inventory and reclassifies the absent subagent tool before executing the CLI fallback.
40
+ 6. Launch the `sol-reviewer` agent with the diff and a compact context brief containing the changed files, applicable conventions, referenced ADRs, the user's requested review focus, and the complete preflight result.
37
41
  7. Return the agent's validated findings without adding unverified issues.
38
42
 
39
- The reviewer must call `ask-codex` with `model: "gpt-5.6-sol"` and `reasoningEffort: "high"`, or, when no `ask-codex` MCP tool is available in the subagent context, use the sanctioned CLI fallback defined in the agent (`codex exec -m gpt-5.6-sol -c model_reasoning_effort="high" -s read-only --ignore-user-config --ignore-rules --skip-git-repo-check`). This explicit pin distinguishes `/sol-review` from `/codex-review`, which follows the configured Codex default. Both fallback kinds must be disclosed in the report: a Terra quota fallback means the requested Sol review did not complete on Sol, and a CLI transport fallback means the review ran through `codex exec` rather than MCP.
43
+ The reviewer must call `ask-codex` with `model: "gpt-5.6-sol"` and `reasoningEffort: "high"`, or use the shipped CLI fallback runner when no usable `ask-codex` MCP tool is available or its invocation fails at the transport/service boundary. That runner executes the sanctioned `codex exec -m gpt-5.6-sol -c model_reasoning_effort="high" -s read-only --ignore-user-config --ignore-rules --skip-git-repo-check` contract and relays its result unchanged. This explicit pin distinguishes `/sol-review` from `/codex-review`, which follows the configured Codex default. Both fallback kinds must be disclosed in the report: a Terra quota fallback means the requested Sol review did not complete on Sol, and a CLI transport fallback must report missing registration, registered-service unavailability, or an unreadable inventory without claiming a state that could not be determined.
40
44
 
41
45
  <!-- HOST-ADAPTER:CLAUDE-CODE:END -->