@tonyclaw/llm-inspector 1.19.0 → 1.19.1
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/.output/cli.js +196 -69
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-DwayZPPO.js → CompareDrawer-DtERUdIt.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-iv3LVMEW.js → ProxyViewerContainer-DfxRK7Nt.js} +4 -4
- package/.output/public/assets/{ReplayDialog-CaV1elYO.js → ReplayDialog-VMsGnJSI.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CSfnjK7j.js → RequestAnatomy-Cx_vluvK.js} +1 -1
- package/.output/public/assets/{ResponseView-YkOL__xm.js → ResponseView-5F8Ms5z4.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-D_p6L-oB.js → StreamingChunkSequence-CKDCWfu9.js} +1 -1
- package/.output/public/assets/_sessionId-C-aKd1Ky.js +1 -0
- package/.output/public/assets/index-B8ttyigz.js +1 -0
- package/.output/public/assets/{json-viewer-BB-9bqnP.js → json-viewer-CztuZ9cT.js} +1 -1
- package/.output/public/assets/{main-COVN451W.js → main-CR9IJlz1.js} +2 -2
- package/.output/server/{_sessionId-BJT5qIib.mjs → _sessionId-DvWQaDEm.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-DNGYdUXs.mjs → CompareDrawer-C5FsxSDS.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-B-zDOLYE.mjs → ProxyViewerContainer-v0cvR8f5.mjs} +9 -9
- package/.output/server/_ssr/{ReplayDialog-DWeqMA4y.mjs → ReplayDialog-C3KOv9OW.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-TOsrMu9-.mjs → RequestAnatomy-BYRe33eG.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-BuqdPrzm.mjs → ResponseView-va7yQDeL.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DuzNZkqL.mjs → StreamingChunkSequence-BJlI-gWl.mjs} +3 -3
- package/.output/server/_ssr/{index-1nCQUt3y.mjs → index-CS0fA2GT.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BL8xhHbi.mjs → json-viewer-Dg8rqrxL.mjs} +2 -2
- package/.output/server/_ssr/{router-aCaUgVTW.mjs → router-D_Boe9Bu.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-cBRxvCjb.mjs → _tanstack-start-manifest_v-KFXyNRGC.mjs} +1 -1
- package/.output/server/index.mjs +63 -63
- package/package.json +1 -1
- package/src/cli/templates/skill-onboard.ts +202 -69
- package/src/cli.ts +12 -4
- package/.output/public/assets/_sessionId-BgCVUC6R.js +0 -1
- package/.output/public/assets/index-CWA4S0FO.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Renders `~/.claude/skills/llm-inspector-onboard/SKILL.md` for the
|
|
3
|
-
* `llm-inspector onboard` subcommand. The body is a
|
|
3
|
+
* `llm-inspector onboard` subcommand. The body is a multi-phase teaching
|
|
4
4
|
* workflow with `EXPLAIN / DO / PAUSE` markers — Claude reads it and walks
|
|
5
5
|
* the user through real actions (start the proxy, hit /api/health, post a
|
|
6
6
|
* test request, poll /api/logs) instead of just printing a static tutorial.
|
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
* The template embeds platform-aware shell snippets inline (bash + PowerShell
|
|
9
9
|
* side-by-side). Skills are pure Markdown — they can't `require()` from the
|
|
10
10
|
* package at runtime, so the duplication is the price of portability.
|
|
11
|
+
*
|
|
12
|
+
* All PowerShell snippets that use `$env:` variables are written so they
|
|
13
|
+
* work when invoked via the Bash tool. The pattern is:
|
|
14
|
+
* powershell -Command '<single-quoted block with $env:LITERAL>'
|
|
15
|
+
* NOT
|
|
16
|
+
* powershell -Command "<double-quoted block with escaped \$env:>"
|
|
17
|
+
* The latter collapses `$env:` to a literal in bash before PowerShell sees
|
|
18
|
+
* it, producing garbled paths. Single-quoted bash arguments pass through
|
|
19
|
+
* to PowerShell unchanged.
|
|
11
20
|
*/
|
|
12
21
|
|
|
13
22
|
export type SkillOnboardContext = {
|
|
@@ -23,8 +32,9 @@ export type SkillOnboardContext = {
|
|
|
23
32
|
readonly detectedSummary: string;
|
|
24
33
|
};
|
|
25
34
|
|
|
26
|
-
/** The
|
|
35
|
+
/** The 9 phase headings the spec requires. The validator checks for these. */
|
|
27
36
|
export const REQUIRED_PHASE_HEADINGS = [
|
|
37
|
+
"Phase 0: Idempotency check",
|
|
28
38
|
"Preflight",
|
|
29
39
|
"Phase 1: Welcome",
|
|
30
40
|
"Phase 2: Provider setup",
|
|
@@ -54,29 +64,117 @@ ${detectedSummary || " (no known AI tool detected — the user can still use th
|
|
|
54
64
|
|
|
55
65
|
Default proxy port: \`${port}\` (override with \`PORT=<n> llm-inspector\` or \`--port <n>\`).
|
|
56
66
|
|
|
67
|
+
> **PAUSE protocol.** Every \`**PAUSE**\` marker in this skill is a real stop.
|
|
68
|
+
> Use the \`AskUserQuestion\` tool to actually wait for the user before
|
|
69
|
+
> continuing. Do not stream past a PAUSE based on context — the user has
|
|
70
|
+
> not seen your output yet. Each PAUSE in the body below includes a sample
|
|
71
|
+
> question you can adapt.
|
|
72
|
+
|
|
57
73
|
---
|
|
58
74
|
|
|
59
|
-
##
|
|
75
|
+
## Phase 0: Idempotency check
|
|
76
|
+
|
|
77
|
+
**EXPLAIN:** "Before we do anything, let me see what's already set up. If some of the steps are already done, we can skip them."
|
|
78
|
+
|
|
79
|
+
**DO:** Probe the three pieces of state this skill touches. Use targeted checks — do **not** read large JSON files into the conversation.
|
|
80
|
+
|
|
81
|
+
\`\`\`bash
|
|
82
|
+
# 1. Is the proxy already up?
|
|
83
|
+
curl -fsS "http://localhost:${port}/api/health" 2>/dev/null && echo "PROXY: up" || echo "PROXY: down"
|
|
84
|
+
|
|
85
|
+
# 2. Does the config have a real provider key?
|
|
86
|
+
CFG="$HOME/.llm-inspector/config.json"
|
|
87
|
+
if [ -f "$CFG" ]; then
|
|
88
|
+
if grep -qE '"apiKey"[[:space:]]*:[[:space:]]*"(sk-[^"]+|REPLACE|REPLACE_)' "$CFG" 2>/dev/null; then
|
|
89
|
+
echo "CONFIG: has key (no REPLACE placeholder)"
|
|
90
|
+
else
|
|
91
|
+
echo "CONFIG: missing or has placeholder key"
|
|
92
|
+
fi
|
|
93
|
+
else
|
|
94
|
+
echo "CONFIG: file does not exist"
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# 3. Is the MCP server already wired? (project .mcp.json wins)
|
|
98
|
+
PROJ_MCP=".mcp.json"
|
|
99
|
+
HOME_MCP="$HOME/.claude.json"
|
|
100
|
+
if [ -f "$PROJ_MCP" ] && grep -q '"llm-inspector"' "$PROJ_MCP"; then
|
|
101
|
+
echo "MCP: wired in $PROJ_MCP"
|
|
102
|
+
elif [ -f "$HOME_MCP" ] && grep -q '"llm-inspector"' "$HOME_MCP"; then
|
|
103
|
+
echo "MCP: wired in $HOME_MCP"
|
|
104
|
+
else
|
|
105
|
+
echo "MCP: not wired"
|
|
106
|
+
fi
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
\`\`\`powershell
|
|
110
|
+
# Windows PowerShell — single-quoted so $env: expands correctly
|
|
111
|
+
$port = ${port}
|
|
112
|
+
$cfg = Join-Path $env:USERPROFILE '.llm-inspector/config.json'
|
|
113
|
+
|
|
114
|
+
# 1. Is the proxy already up?
|
|
115
|
+
try {
|
|
116
|
+
$null = Invoke-RestMethod -Uri "http://localhost:$port/api/health" -TimeoutSec 2 -ErrorAction Stop
|
|
117
|
+
Write-Host 'PROXY: up'
|
|
118
|
+
} catch {
|
|
119
|
+
Write-Host 'PROXY: down'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# 2. Does the config have a real provider key?
|
|
123
|
+
if (Test-Path $cfg) {
|
|
124
|
+
$content = Get-Content $cfg -Raw
|
|
125
|
+
if ($content -match '"apiKey"[[:space:]]*:[[:space:]]*"(sk-[^"]+)"') {
|
|
126
|
+
Write-Host 'CONFIG: has key'
|
|
127
|
+
} elseif ($content -match 'REPLACE') {
|
|
128
|
+
Write-Host 'CONFIG: has placeholder key'
|
|
129
|
+
} else {
|
|
130
|
+
Write-Host 'CONFIG: missing key'
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
Write-Host 'CONFIG: file does not exist'
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# 3. Is the MCP server already wired? (project .mcp.json wins)
|
|
137
|
+
$projMcp = Join-Path (Get-Location) '.mcp.json'
|
|
138
|
+
$homeMcp = Join-Path $env:USERPROFILE '.claude.json'
|
|
139
|
+
if ((Test-Path $projMcp) -and (Select-String -Path $projMcp -Pattern 'llm-inspector' -Quiet)) {
|
|
140
|
+
Write-Host "MCP: wired in $projMcp"
|
|
141
|
+
} elseif ((Test-Path $homeMcp) -and (Select-String -Path $homeMcp -Pattern 'llm-inspector' -Quiet)) {
|
|
142
|
+
Write-Host "MCP: wired in $homeMcp"
|
|
143
|
+
} else {
|
|
144
|
+
Write-Host 'MCP: not wired'
|
|
145
|
+
}
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
**DO:** Summarize the three checks in one line, then use \`AskUserQuestion\` to ask whether to skip the corresponding phases.
|
|
149
|
+
|
|
150
|
+
> **PAUSE** — call \`AskUserQuestion\` with:
|
|
151
|
+
> - header: \`Skip done\`
|
|
152
|
+
> - question: \`Proxy/CONFIG/MCP state: <summary>. Skip the phases that are already done?\`
|
|
153
|
+
> - options: \`["Yes, skip what's done", "No, walk me through everything again"]\`
|
|
154
|
+
> Wait for the answer before moving to Preflight.
|
|
155
|
+
|
|
156
|
+
---
|
|
60
157
|
|
|
61
|
-
|
|
158
|
+
## Preflight
|
|
62
159
|
|
|
63
|
-
**EXPLAIN:** "
|
|
160
|
+
**EXPLAIN:** "Quick env sanity check — make sure Node and Claude Code are present."
|
|
64
161
|
|
|
65
|
-
**DO:** Run the platform-appropriate commands below.
|
|
162
|
+
**DO:** Run the platform-appropriate commands below.
|
|
66
163
|
|
|
67
164
|
\`\`\`bash
|
|
68
165
|
# Unix / macOS / WSL
|
|
69
|
-
node --version
|
|
70
|
-
|
|
166
|
+
node --version # expect >= 18
|
|
167
|
+
command -v claude >/dev/null && echo "claude-code: present" || echo "claude-code: not detected"
|
|
71
168
|
\`\`\`
|
|
72
169
|
|
|
73
170
|
\`\`\`powershell
|
|
74
|
-
# Windows PowerShell
|
|
75
|
-
node --version
|
|
76
|
-
|
|
171
|
+
# Windows PowerShell — single-quoted so $env: expands correctly
|
|
172
|
+
node --version # expect >= 18
|
|
173
|
+
$null = Get-Command claude -ErrorAction SilentlyContinue
|
|
174
|
+
if ($null) { Write-Host 'claude-code: present' } else { Write-Host 'claude-code: not detected' }
|
|
77
175
|
\`\`\`
|
|
78
176
|
|
|
79
|
-
**PAUSE** — if Node is older than 18, ask the user to install a newer version (https://nodejs.org) before continuing.
|
|
177
|
+
> **PAUSE** — if Node is older than 18, ask the user to install a newer version (https://nodejs.org) before continuing. Use \`AskUserQuestion\` with header \`Node version\`.
|
|
80
178
|
|
|
81
179
|
---
|
|
82
180
|
|
|
@@ -99,7 +197,7 @@ llm-inspector is a transparent HTTP proxy + Web UI for AI coding tools. Point yo
|
|
|
99
197
|
Ready? Let's start with the provider.
|
|
100
198
|
\`\`\`
|
|
101
199
|
|
|
102
|
-
**PAUSE** —
|
|
200
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`Ready?\` and options \`["Yes, let's go", "Wait, I have a question"]\`. Wait for the user before continuing.
|
|
103
201
|
|
|
104
202
|
---
|
|
105
203
|
|
|
@@ -107,24 +205,53 @@ Ready? Let's start with the provider.
|
|
|
107
205
|
|
|
108
206
|
**EXPLAIN:** "A 'provider' is an upstream LLM endpoint — Anthropic, OpenAI, MiniMax, etc. llm-inspector routes each request to the right upstream based on the model name. You need at least one provider configured for the proxy to forward traffic."
|
|
109
207
|
|
|
110
|
-
**DO:**
|
|
208
|
+
**DO:** First, re-check whether the config already has a real key (Phase 0 may have raced with a manual edit). Use the same \`grep -qE '"apiKey":"sk-' "$HOME/.llm-inspector/config.json"\` (bash) or \`Select-String\` (PowerShell) check from Phase 0. If a real key is present, skip the rest of this phase.
|
|
111
209
|
|
|
112
|
-
|
|
210
|
+
**DO:** If no real key, ask the user for the provider type and API key via \`AskUserQuestion\`. The question should be a free-form text field (no fixed options) — the API key is a secret, so don't echo it back in the question UI.
|
|
211
|
+
|
|
212
|
+
\`\`\`bash
|
|
213
|
+
# After collecting the key, write the config
|
|
214
|
+
mkdir -p "$HOME/.llm-inspector"
|
|
215
|
+
cat > "$HOME/.llm-inspector/config.json" <<'JSON'
|
|
216
|
+
{
|
|
217
|
+
"providers": [
|
|
218
|
+
{
|
|
219
|
+
"id": "anthropic",
|
|
220
|
+
"type": "anthropic",
|
|
221
|
+
"apiKey": "REPLACE_ME_BEFORE_WRITING",
|
|
222
|
+
"baseUrl": "https://api.anthropic.com"
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
JSON
|
|
227
|
+
# Then patch the apiKey with the user-provided value (use jq if available)
|
|
228
|
+
\`\`\`
|
|
229
|
+
|
|
230
|
+
\`\`\`powershell
|
|
231
|
+
# Windows PowerShell — single-quoted so $env: expands correctly
|
|
232
|
+
$dir = Join-Path $env:USERPROFILE '.llm-inspector'
|
|
233
|
+
$file = Join-Path $dir 'config.json'
|
|
234
|
+
New-Item -ItemType Directory -Force -Path $dir | Out-Null
|
|
235
|
+
@'
|
|
113
236
|
{
|
|
114
237
|
"providers": [
|
|
115
238
|
{
|
|
116
239
|
"id": "anthropic",
|
|
117
240
|
"type": "anthropic",
|
|
118
|
-
"apiKey": "
|
|
241
|
+
"apiKey": "REPLACE_ME_BEFORE_WRITING",
|
|
119
242
|
"baseUrl": "https://api.anthropic.com"
|
|
120
243
|
}
|
|
121
244
|
]
|
|
122
245
|
}
|
|
246
|
+
'@ | Set-Content -Path $file -Encoding UTF8
|
|
247
|
+
# Then patch the apiKey with the user-provided value
|
|
123
248
|
\`\`\`
|
|
124
249
|
|
|
125
|
-
|
|
250
|
+
**DO:** Patch the placeholder with the actual key using \`jq\` (preferred) or a simple \`sed\`. Then read the file back to confirm the key is no longer \`REPLACE_ME_BEFORE_WRITING\`.
|
|
251
|
+
|
|
252
|
+
**DO:** If the user declines to provide a key in the AskUserQuestion (selects "Skip for now"), do **not** write a placeholder config. Tell the user that Phase 5 (First capture) will be skipped, and that they can re-run the skill after adding a key.
|
|
126
253
|
|
|
127
|
-
**PAUSE** —
|
|
254
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`Provider key\` and options \`["Key is in, continue", "Skip for now, I'll add it later"]\`. Wait for the answer.
|
|
128
255
|
|
|
129
256
|
---
|
|
130
257
|
|
|
@@ -132,7 +259,9 @@ Alternative (avoid touching the config file): point the user at the Web UI → t
|
|
|
132
259
|
|
|
133
260
|
**EXPLAIN:** "Time to start the proxy. It binds to port ${port} by default, kills any process already on that port, and prints the URL."
|
|
134
261
|
|
|
135
|
-
**DO:**
|
|
262
|
+
**DO:** Skip this phase entirely if the Phase 0 health check already reported \`PROXY: up\` and the user opted to skip done phases.
|
|
263
|
+
|
|
264
|
+
**DO:** Otherwise, start the proxy in the background. On Windows, the npm shim is a \`.cmd\`/PowerShell file — \`Start-Process -FilePath "llm-inspector"\` returns "Invalid Win32 application" because Windows can't decide which shim to run. Use \`cmd /c start /B\` to background it through the cmd interpreter.
|
|
136
265
|
|
|
137
266
|
\`\`\`bash
|
|
138
267
|
# Unix / macOS / WSL
|
|
@@ -140,14 +269,30 @@ nohup llm-inspector --no-open > /tmp/llm-inspector.log 2>&1 &
|
|
|
140
269
|
\`\`\`
|
|
141
270
|
|
|
142
271
|
\`\`\`powershell
|
|
143
|
-
# Windows PowerShell
|
|
144
|
-
|
|
272
|
+
# Windows PowerShell — single-quoted so $env: expands correctly.
|
|
273
|
+
# Locate the binary on PATH first (works for npm, pnpm, yarn, volta, fnm).
|
|
274
|
+
# If not on PATH, fall back to the common npm global shim at $env:APPDATA.
|
|
275
|
+
# As a last resort, let cmd /c resolve it through PATHEXT.
|
|
276
|
+
$log = Join-Path $env:TEMP 'llm-inspector.log'
|
|
277
|
+
$err = Join-Path $env:TEMP 'llm-inspector.err.log'
|
|
278
|
+
$found = Get-Command llm-inspector -ErrorAction SilentlyContinue
|
|
279
|
+
if ($found) {
|
|
280
|
+
$shim = $found.Source
|
|
281
|
+
$args = '--no-open'
|
|
282
|
+
} elseif (Test-Path (Join-Path $env:APPDATA 'npm/llm-inspector.cmd')) {
|
|
283
|
+
$shim = Join-Path $env:APPDATA 'npm/llm-inspector.cmd'
|
|
284
|
+
$args = '--no-open'
|
|
285
|
+
} else {
|
|
286
|
+
# bin not on PATH and not at the default npm prefix — let cmd resolve it
|
|
287
|
+
$shim = 'cmd.exe'
|
|
288
|
+
$args = '/c','start','/B','llm-inspector','--no-open'
|
|
289
|
+
}
|
|
290
|
+
Start-Process -FilePath $shim -ArgumentList $args -RedirectStandardOutput $log -RedirectStandardError $err -WindowStyle Hidden
|
|
145
291
|
\`\`\`
|
|
146
292
|
|
|
147
293
|
Then wait for the port to be ready:
|
|
148
294
|
|
|
149
295
|
\`\`\`bash
|
|
150
|
-
# Wait up to 10s for the port to come up
|
|
151
296
|
for i in $(seq 1 20); do
|
|
152
297
|
curl -fsS "http://localhost:${port}/api/health" >/dev/null 2>&1 && echo "ready" && break
|
|
153
298
|
sleep 0.5
|
|
@@ -160,7 +305,7 @@ done
|
|
|
160
305
|
curl -sS "http://localhost:${port}/api/health"
|
|
161
306
|
\`\`\`
|
|
162
307
|
|
|
163
|
-
**PAUSE** — if the health check fails, show the user the log file (\`/tmp/llm-inspector.log\` or \`%TEMP
|
|
308
|
+
> **PAUSE** — if the health check fails, show the user the log file (\`/tmp/llm-inspector.log\` or \`%TEMP%\\llm-inspector.log\`) and diagnose. Common issues: another process on the port, firewall, missing providers. Use \`AskUserQuestion\` with header \`Proxy up?\` and options \`["Yes, proxy is up", "No, I see an error in the log"]\`. Wait for the answer.
|
|
164
309
|
|
|
165
310
|
---
|
|
166
311
|
|
|
@@ -188,18 +333,22 @@ mimo
|
|
|
188
333
|
|
|
189
334
|
For a tool that wasn't auto-detected, fall through to the generic curl test in the next phase — the user can wire their tool later.
|
|
190
335
|
|
|
191
|
-
**PAUSE** —
|
|
336
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`Tool wired?\` and options \`["Yes, env var is set, claude is running", "No, I'm going to use the curl test instead"]\`. Wait for the answer.
|
|
192
337
|
|
|
193
338
|
---
|
|
194
339
|
|
|
195
340
|
## Phase 4.5: Wire MCP server
|
|
196
341
|
|
|
197
|
-
**EXPLAIN:** "The proxy also exposes an MCP server at \`http://localhost:${port}/api/mcp\`. Your AI agent can query logs, replay requests, and test providers through it — no need to leave the editor.
|
|
342
|
+
**EXPLAIN:** "The proxy also exposes an MCP server at \`http://localhost:${port}/api/mcp\`. Your AI agent can query logs, replay requests, and test providers through it — no need to leave the editor."
|
|
343
|
+
|
|
344
|
+
**DO:** Skip this phase if Phase 0 reported \`MCP: wired in <path>\` and the user opted to skip done phases.
|
|
198
345
|
|
|
199
|
-
**DO:**
|
|
346
|
+
**DO:** Otherwise, check the project-level \`.mcp.json\` first (preferred — modern Claude Code convention), then fall back to \`~/.claude.json\`. Use the \`Read\` tool to inspect; do **not** \`cat\` a 40 KB file into the conversation.
|
|
347
|
+
|
|
348
|
+
If neither has an \`llm-inspector\` entry, add one. The simplest path is to write to project \`.mcp.json\` (create it if missing):
|
|
200
349
|
|
|
201
350
|
\`\`\`json
|
|
202
|
-
//
|
|
351
|
+
// .mcp.json (project root)
|
|
203
352
|
{
|
|
204
353
|
"mcpServers": {
|
|
205
354
|
"llm-inspector": {
|
|
@@ -210,26 +359,7 @@ For a tool that wasn't auto-detected, fall through to the generic curl test in t
|
|
|
210
359
|
}
|
|
211
360
|
\`\`\`
|
|
212
361
|
|
|
213
|
-
If \`mcpServers\` already exists
|
|
214
|
-
|
|
215
|
-
PowerShell one-liner that creates the file if missing, or merges if present:
|
|
216
|
-
|
|
217
|
-
\`\`\`powershell
|
|
218
|
-
$cfg = Join-Path $env:USERPROFILE ".claude.json"
|
|
219
|
-
if (Test-Path $cfg) {
|
|
220
|
-
$doc = Get-Content $cfg -Raw | ConvertFrom-Json
|
|
221
|
-
} else {
|
|
222
|
-
$doc = [pscustomobject]@{ mcpServers = [pscustomobject]@{} }
|
|
223
|
-
}
|
|
224
|
-
if (-not ($doc.PSObject.Properties.Name -contains "mcpServers")) {
|
|
225
|
-
$doc | Add-Member -NotePropertyName mcpServers -NotePropertyValue ([pscustomobject]@{})
|
|
226
|
-
}
|
|
227
|
-
$doc.mcpServers | Add-Member -NotePropertyName "llm-inspector" -NotePropertyValue ([pscustomobject]@{
|
|
228
|
-
type = "http"
|
|
229
|
-
url = "http://localhost:${port}/api/mcp"
|
|
230
|
-
}) -Force
|
|
231
|
-
$doc | ConvertTo-Json -Depth 10 | Set-Content $cfg -Encoding UTF8
|
|
232
|
-
\`\`\`
|
|
362
|
+
If \`mcpServers\` already exists in \`.mcp.json\`, merge the \`llm-inspector\` key into it via the \`Edit\` tool — do not overwrite other entries. If you can't create a project \`.mcp.json\` (no project root, permission, etc.), fall back to merging into \`~/.claude.json\` using the same \`Read\`/\`Edit\` pattern.
|
|
233
363
|
|
|
234
364
|
**DO:** Verify the handshake. The MCP \`initialize\` request should return 200 with a \`serverInfo\` payload — that proves the server is mounted and reachable:
|
|
235
365
|
|
|
@@ -246,41 +376,33 @@ curl -sS -X POST "http://localhost:${port}/api/mcp" \\
|
|
|
246
376
|
"capabilities": {},
|
|
247
377
|
"clientInfo": { "name": "onboard-check", "version": "0" }
|
|
248
378
|
}
|
|
249
|
-
}'
|
|
250
|
-
# expect: HTTP 200, body contains "result" with serverInfo.name = "llm-inspector"
|
|
379
|
+
}' | grep -o '"name":"llm-inspector"' && echo "handshake OK"
|
|
251
380
|
\`\`\`
|
|
252
381
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
\`\`\`bash
|
|
256
|
-
SESSION=<session-id-from-initialize-response>
|
|
257
|
-
curl -sS -X POST "http://localhost:${port}/api/mcp" \\
|
|
258
|
-
-H "Content-Type: application/json" \\
|
|
259
|
-
-H "Accept: application/json, text/event-stream" \\
|
|
260
|
-
-H "mcp-session-id: $SESSION" \\
|
|
261
|
-
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
|
|
262
|
-
# expect: a result.tools array with at least 1 entry
|
|
263
|
-
\`\`\`
|
|
382
|
+
The \`grep -o '"name":"llm-inspector"'\` extracts only the serverInfo name — do not dump the full response. If the server returns session IDs, store the \`mcp-session-id\` header from the first response and use it for the follow-up \`tools/list\` call.
|
|
264
383
|
|
|
265
|
-
**PAUSE** —
|
|
384
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`MCP OK?\` and options \`["Yes, handshake returned 200", "No, the call failed"]\`. Wait for the answer.
|
|
266
385
|
|
|
267
386
|
---
|
|
268
387
|
|
|
269
388
|
## Phase 5: First capture
|
|
270
389
|
|
|
271
|
-
**EXPLAIN:** "Let's prove the proxy works end-to-end. We'll send one real request through it and confirm the log shows up in the API.
|
|
390
|
+
**EXPLAIN:** "Let's prove the proxy works end-to-end. We'll send one real request through it and confirm the log shows up in the API."
|
|
391
|
+
|
|
392
|
+
**DO:** First, re-check the config. If the \`apiKey\` is still a \`REPLACE_ME_BEFORE_WRITING\` placeholder (user opted out in Phase 2), **skip the capture test** and tell the user to fill in their key and re-run the skill. A 401 from the upstream is fine if they did provide a real key — the proxy will still log the request.
|
|
272
393
|
|
|
273
|
-
|
|
394
|
+
Fire a minimal Anthropic-format request through the proxy:
|
|
274
395
|
|
|
275
396
|
\`\`\`bash
|
|
276
397
|
curl -sS -X POST "http://localhost:${port}/proxy/v1/messages" \\
|
|
277
398
|
-H "Content-Type: application/json" \\
|
|
278
399
|
-H "anthropic-version: 2023-06-01" \\
|
|
279
400
|
-H "x-api-key: \${LLM_INSPECTOR_API_KEY:-sk-no-key-needed-for-routing}" \\
|
|
280
|
-
-d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}'
|
|
401
|
+
-d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}' \\
|
|
402
|
+
-o /tmp/llm-inspector-capture.json -w 'STATUS:%{http_code}\\n'
|
|
281
403
|
\`\`\`
|
|
282
404
|
|
|
283
|
-
**DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means
|
|
405
|
+
**DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means the request reached the proxy:
|
|
284
406
|
|
|
285
407
|
\`\`\`bash
|
|
286
408
|
for i in $(seq 1 10); do
|
|
@@ -288,13 +410,24 @@ for i in $(seq 1 10); do
|
|
|
288
410
|
count=$(echo "$resp" | grep -o '"total":[0-9]*' | head -1 | grep -o '[0-9]*$')
|
|
289
411
|
if [ "\${count:-0}" -ge 1 ]; then
|
|
290
412
|
echo "captured"
|
|
413
|
+
echo "$resp" | head -c 400
|
|
291
414
|
break
|
|
292
415
|
fi
|
|
293
416
|
sleep 0.5
|
|
294
417
|
done
|
|
295
418
|
\`\`\`
|
|
296
419
|
|
|
297
|
-
**
|
|
420
|
+
**DO:** Diagnose the response based on the actual status and body. **Do not** default to "auth failure" for every 4xx.
|
|
421
|
+
|
|
422
|
+
| Status | Body hint | Meaning |
|
|
423
|
+
|--------|-----------|---------|
|
|
424
|
+
| 200 | normal | Real success — the upstream returned data |
|
|
425
|
+
| 401 | \`"unauthorized"\` or similar | Upstream rejected the key (expected with a test key) |
|
|
426
|
+
| 403 | \`"Request not allowed"\` | **Proxy's allowlist** — not an auth failure, the proxy rejected the model/config. Show the user the proxy log. |
|
|
427
|
+
| 403 | other text | Could be upstream ACL — different problem |
|
|
428
|
+
| 5xx | anything | Upstream network error |
|
|
429
|
+
|
|
430
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`Captured?\` and options matching the diagnosis above. Wait for the answer.
|
|
298
431
|
|
|
299
432
|
---
|
|
300
433
|
|
|
@@ -311,14 +444,14 @@ done
|
|
|
311
444
|
# Unix / macOS
|
|
312
445
|
lsof -ti:${port} | xargs -r kill -9
|
|
313
446
|
|
|
314
|
-
# Windows PowerShell
|
|
315
|
-
Get-NetTCPConnection -LocalPort $
|
|
447
|
+
# Windows PowerShell — single-quoted so $env: expands correctly
|
|
448
|
+
Get-NetTCPConnection -LocalPort $port | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
|
|
316
449
|
\`\`\`
|
|
317
450
|
|
|
318
451
|
- **Re-run onboard**: \`llm-inspector onboard --force\` refreshes this skill.
|
|
319
452
|
- **Full docs**: see the project README (linked from the Web UI footer).
|
|
320
453
|
|
|
321
|
-
**PAUSE** —
|
|
454
|
+
> **PAUSE** — use \`AskUserQuestion\` with header \`All set?\` and options \`["All set, I'm done", "Wait, I want to revisit a phase"]\`. Wait for the answer.
|
|
322
455
|
|
|
323
456
|
You're done. Happy inspecting.
|
|
324
457
|
`;
|
package/src/cli.ts
CHANGED
|
@@ -191,11 +191,19 @@ function runStart(args: string[]): void {
|
|
|
191
191
|
// Start server with node
|
|
192
192
|
const serverEnv = { ...process.env };
|
|
193
193
|
if (configDir !== undefined) {
|
|
194
|
-
//
|
|
194
|
+
// Normalize MSYS / Git Bash paths to Windows native form.
|
|
195
|
+
// On Windows, `path.join('/c/Users/foo', 'config.json')` becomes
|
|
196
|
+
// `\c\Users\foo\config.json` (leading slash converted to backslash).
|
|
197
|
+
// Child processes spawned by `spawn()` won't follow that style, so
|
|
198
|
+
// rewrite `\c\...` (or any `\x\...` drive) to `C:\...` before
|
|
199
|
+
// handing the path to the proxy server. No-op on already-native
|
|
200
|
+
// Windows paths and on non-Windows platforms.
|
|
195
201
|
let resolvedPath = join(configDir, "config.json");
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
202
|
+
const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
|
|
203
|
+
if (msysMatch !== null) {
|
|
204
|
+
const drive = (msysMatch[1] ?? "").toUpperCase();
|
|
205
|
+
const rest = msysMatch[2] ?? "";
|
|
206
|
+
resolvedPath = `${drive}:\\${rest}`;
|
|
199
207
|
}
|
|
200
208
|
serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
|
|
201
209
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{R as s,j as e}from"./main-COVN451W.js";import{P as i}from"./ProxyViewerContainer-iv3LVMEW.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as o}from"./ProxyViewerContainer-iv3LVMEW.js";import"./main-COVN451W.js";const r=o;export{r as component};
|