@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.
- package/.claude-plugin/plugin.json +5 -2
- package/.mcp.json +6 -1
- package/README.md +12 -5
- package/agents/brainstorm-coordinator.md +14 -1
- package/agents/codex-reviewer.md +1 -0
- package/agents/grok-reviewer.md +49 -0
- package/agents/sol-reviewer.md +8 -11
- package/dist/grok-run.d.ts +3 -0
- package/dist/grok-run.d.ts.map +1 -0
- package/dist/grok-run.js +30 -0
- package/dist/grok-run.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -5
- package/pi/extensions/codex-pair.ts +56 -24
- package/pi/extensions/provider-tools.ts +67 -5
- package/scripts/lib/process.mjs +17 -0
- package/scripts/sol-review-transport.mjs +339 -0
- package/skills/brainstorm/SKILL.md +3 -2
- package/skills/brainstorm-all/SKILL.md +6 -6
- package/skills/compare/SKILL.md +13 -5
- package/skills/grok-review/SKILL.md +30 -0
- package/skills/sol-review/SKILL.md +7 -3
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ask-llm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI-to-AI collaboration — review code
|
|
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
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
|
|
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
|
|
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
|
-
| `/
|
|
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 ====="
|
package/agents/codex-reviewer.md
CHANGED
|
@@ -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 -->
|
package/agents/sol-reviewer.md
CHANGED
|
@@ -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
|
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
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`)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"grok-run.d.ts","sourceRoot":"","sources":["../src/grok-run.ts"],"names":[],"mappings":""}
|
package/dist/grok-run.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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.
|
|
80
|
-
"@ask-llm/codex-mcp": "^0.7.
|
|
81
|
-
"@ask-llm/gemini-mcp": "^1.7.
|
|
82
|
-
"@ask-llm/
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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}
|
|
436
|
+
const claimPath = `${path}${CLAIM_SUFFIX}`;
|
|
419
437
|
try {
|
|
420
|
-
await
|
|
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
|
-
|
|
428
|
-
finding: JSON.parse(await readFile(
|
|
445
|
+
pendingPath: path,
|
|
446
|
+
finding: JSON.parse(await readFile(path, "utf8")) as PairFinding,
|
|
429
447
|
};
|
|
430
448
|
} catch {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
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
|
|
542
|
+
await consumeClaimed(claimed);
|
|
512
543
|
return;
|
|
513
544
|
}
|
|
514
545
|
if (closed) {
|
|
515
|
-
await
|
|
546
|
+
await releaseClaim(claimed);
|
|
516
547
|
return;
|
|
517
548
|
}
|
|
518
549
|
sendFinding(pi, claimed.finding);
|
|
519
550
|
await markReviewed(claimed.finding);
|
|
520
|
-
await
|
|
551
|
+
await consumeClaimed(claimed);
|
|
521
552
|
} catch (error) {
|
|
522
|
-
|
|
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
|
-
|
|
535
|
-
|
|
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:
|
|
73
|
-
description: "Two to
|
|
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:
|
|
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
|
|
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)];
|
package/scripts/lib/process.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 -->
|
package/skills/compare/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
36
|
-
|
|
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
|
|
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 -->
|