@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.
Files changed (30) hide show
  1. package/.output/cli.js +196 -69
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DwayZPPO.js → CompareDrawer-DtERUdIt.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-iv3LVMEW.js → ProxyViewerContainer-DfxRK7Nt.js} +4 -4
  5. package/.output/public/assets/{ReplayDialog-CaV1elYO.js → ReplayDialog-VMsGnJSI.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CSfnjK7j.js → RequestAnatomy-Cx_vluvK.js} +1 -1
  7. package/.output/public/assets/{ResponseView-YkOL__xm.js → ResponseView-5F8Ms5z4.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-D_p6L-oB.js → StreamingChunkSequence-CKDCWfu9.js} +1 -1
  9. package/.output/public/assets/_sessionId-C-aKd1Ky.js +1 -0
  10. package/.output/public/assets/index-B8ttyigz.js +1 -0
  11. package/.output/public/assets/{json-viewer-BB-9bqnP.js → json-viewer-CztuZ9cT.js} +1 -1
  12. package/.output/public/assets/{main-COVN451W.js → main-CR9IJlz1.js} +2 -2
  13. package/.output/server/{_sessionId-BJT5qIib.mjs → _sessionId-DvWQaDEm.mjs} +2 -2
  14. package/.output/server/_ssr/{CompareDrawer-DNGYdUXs.mjs → CompareDrawer-C5FsxSDS.mjs} +3 -3
  15. package/.output/server/_ssr/{ProxyViewerContainer-B-zDOLYE.mjs → ProxyViewerContainer-v0cvR8f5.mjs} +9 -9
  16. package/.output/server/_ssr/{ReplayDialog-DWeqMA4y.mjs → ReplayDialog-C3KOv9OW.mjs} +4 -4
  17. package/.output/server/_ssr/{RequestAnatomy-TOsrMu9-.mjs → RequestAnatomy-BYRe33eG.mjs} +2 -2
  18. package/.output/server/_ssr/{ResponseView-BuqdPrzm.mjs → ResponseView-va7yQDeL.mjs} +3 -3
  19. package/.output/server/_ssr/{StreamingChunkSequence-DuzNZkqL.mjs → StreamingChunkSequence-BJlI-gWl.mjs} +3 -3
  20. package/.output/server/_ssr/{index-1nCQUt3y.mjs → index-CS0fA2GT.mjs} +2 -2
  21. package/.output/server/_ssr/index.mjs +2 -2
  22. package/.output/server/_ssr/{json-viewer-BL8xhHbi.mjs → json-viewer-Dg8rqrxL.mjs} +2 -2
  23. package/.output/server/_ssr/{router-aCaUgVTW.mjs → router-D_Boe9Bu.mjs} +2 -2
  24. package/.output/server/{_tanstack-start-manifest_v-cBRxvCjb.mjs → _tanstack-start-manifest_v-KFXyNRGC.mjs} +1 -1
  25. package/.output/server/index.mjs +63 -63
  26. package/package.json +1 -1
  27. package/src/cli/templates/skill-onboard.ts +202 -69
  28. package/src/cli.ts +12 -4
  29. package/.output/public/assets/_sessionId-BgCVUC6R.js +0 -1
  30. 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 7-phase teaching
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 8 phase headings the spec requires. The validator checks for these. */
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
- ## Preflight
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
- Before starting, verify the environment.
158
+ ## Preflight
62
159
 
63
- **EXPLAIN:** "Let's make sure everything we need is in place. Two quick checks."
160
+ **EXPLAIN:** "Quick env sanity check make sure Node and Claude Code are present."
64
161
 
65
- **DO:** Run the platform-appropriate commands below. The user can copy-paste, or you can run them yourself if you have shell access.
162
+ **DO:** Run the platform-appropriate commands below.
66
163
 
67
164
  \`\`\`bash
68
165
  # Unix / macOS / WSL
69
- node --version # expect >= 18
70
- test -d "$HOME/.claude" && echo "claude-code: present" || echo "claude-code: not detected"
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 # expect >= 18
76
- if (Test-Path "$env:USERPROFILE\\.claude") { Write-Host "claude-code: present" } else { Write-Host "claude-code: not detected" }
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** — wait for the user to confirm.
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:** Open (or create) \`~/.llm-inspector/config.json\` in the user's editor. If the file doesn't exist, create it with the structure below. Walk the user through filling in their API key for their provider of choice.
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
- \`\`\`json
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": "sk-ant-...",
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
- Alternative (avoid touching the config file): point the user at the Web UI top-right Settings button Providers tab, which has a form-driven flow.
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** — wait for the user to confirm they have at least one provider with a key.
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:** Start the proxy in the background so you can keep working.
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
- Start-Process -FilePath "llm-inspector" -ArgumentList "--no-open" -RedirectStandardOutput "$env:TEMP\\llm-inspector.log" -RedirectStandardError "$env:TEMP\\llm-inspector.err.log" -WindowStyle Hidden
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%\\\\llm-inspector.log\`) and diagnose. Common issues: another process on the port, firewall, missing providers.
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** — wait for the user to confirm they've set the env var (or that they're going to use the curl test instead).
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. We'll add an \`mcpServers\` entry so your agent picks it up automatically."
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:** Add (or merge) an \`mcpServers\` entry in the user's Claude Code config. The file is \`~/.claude.json\` on all platforms; the MCP spec entry uses HTTP Streamable transport:
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
- // ~/.claude.json (merge into existing mcpServers)
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, add \`llm-inspector\` as a new keydon't clobber existing entries.
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
- After the handshake, issue a \`tools/list\` to confirm the tool catalog is reachable:
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** — if \`initialize\` returns non-200, show the user the proxy log and re-check the JSON syntax. If it returns 200 but \`tools/list\` fails, the server is up but the session wasn't carried over — re-use the \`mcp-session-id\` header from the first response.
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. A 401/403 from the upstream is fine — the point is that the *request* reaches the proxy."
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
- **DO:** Fire a minimal Anthropic-format request through the proxy. This works regardless of which tool the user wired up:
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 success:
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
- **PAUSE** show the user the captured log entry (id, status, model, elapsed). If the count never reached 1, the proxy didn't see the request — re-check the env var and the proxy log.
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 ${port} | ForEach-Object { Stop-Process -Id \\$_.OwningProcess -Force }
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** — let the user know they can come back to this skill at any time via \`/llm-inspector:onboard\` if they want a refresher, and call out that \`/llm-inspector:onboard --skip-tool-wire\` is the way to re-run later phases without re-detecting the tool.
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
- // Convert MSYS/Git Bash path like /c/Users/... to Windows absolute path
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
- // Convert /c/... to C:\... format
197
- if (resolvedPath.startsWith("\\c\\")) {
198
- resolvedPath = "C:" + resolvedPath;
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};