@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
package/.output/cli.js CHANGED
@@ -133,29 +133,117 @@ ${detectedSummary || " (no known AI tool detected \u2014 the user can still use
133
133
 
134
134
  Default proxy port: \`${port}\` (override with \`PORT=<n> llm-inspector\` or \`--port <n>\`).
135
135
 
136
+ > **PAUSE protocol.** Every \`**PAUSE**\` marker in this skill is a real stop.
137
+ > Use the \`AskUserQuestion\` tool to actually wait for the user before
138
+ > continuing. Do not stream past a PAUSE based on context \u2014 the user has
139
+ > not seen your output yet. Each PAUSE in the body below includes a sample
140
+ > question you can adapt.
141
+
136
142
  ---
137
143
 
138
- ## Preflight
144
+ ## Phase 0: Idempotency check
145
+
146
+ **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."
147
+
148
+ **DO:** Probe the three pieces of state this skill touches. Use targeted checks \u2014 do **not** read large JSON files into the conversation.
149
+
150
+ \`\`\`bash
151
+ # 1. Is the proxy already up?
152
+ curl -fsS "http://localhost:${port}/api/health" 2>/dev/null && echo "PROXY: up" || echo "PROXY: down"
153
+
154
+ # 2. Does the config have a real provider key?
155
+ CFG="$HOME/.llm-inspector/config.json"
156
+ if [ -f "$CFG" ]; then
157
+ if grep -qE '"apiKey"[[:space:]]*:[[:space:]]*"(sk-[^"]+|REPLACE|REPLACE_)' "$CFG" 2>/dev/null; then
158
+ echo "CONFIG: has key (no REPLACE placeholder)"
159
+ else
160
+ echo "CONFIG: missing or has placeholder key"
161
+ fi
162
+ else
163
+ echo "CONFIG: file does not exist"
164
+ fi
165
+
166
+ # 3. Is the MCP server already wired? (project .mcp.json wins)
167
+ PROJ_MCP=".mcp.json"
168
+ HOME_MCP="$HOME/.claude.json"
169
+ if [ -f "$PROJ_MCP" ] && grep -q '"llm-inspector"' "$PROJ_MCP"; then
170
+ echo "MCP: wired in $PROJ_MCP"
171
+ elif [ -f "$HOME_MCP" ] && grep -q '"llm-inspector"' "$HOME_MCP"; then
172
+ echo "MCP: wired in $HOME_MCP"
173
+ else
174
+ echo "MCP: not wired"
175
+ fi
176
+ \`\`\`
177
+
178
+ \`\`\`powershell
179
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
180
+ $port = ${port}
181
+ $cfg = Join-Path $env:USERPROFILE '.llm-inspector/config.json'
182
+
183
+ # 1. Is the proxy already up?
184
+ try {
185
+ $null = Invoke-RestMethod -Uri "http://localhost:$port/api/health" -TimeoutSec 2 -ErrorAction Stop
186
+ Write-Host 'PROXY: up'
187
+ } catch {
188
+ Write-Host 'PROXY: down'
189
+ }
190
+
191
+ # 2. Does the config have a real provider key?
192
+ if (Test-Path $cfg) {
193
+ $content = Get-Content $cfg -Raw
194
+ if ($content -match '"apiKey"[[:space:]]*:[[:space:]]*"(sk-[^"]+)"') {
195
+ Write-Host 'CONFIG: has key'
196
+ } elseif ($content -match 'REPLACE') {
197
+ Write-Host 'CONFIG: has placeholder key'
198
+ } else {
199
+ Write-Host 'CONFIG: missing key'
200
+ }
201
+ } else {
202
+ Write-Host 'CONFIG: file does not exist'
203
+ }
139
204
 
140
- Before starting, verify the environment.
205
+ # 3. Is the MCP server already wired? (project .mcp.json wins)
206
+ $projMcp = Join-Path (Get-Location) '.mcp.json'
207
+ $homeMcp = Join-Path $env:USERPROFILE '.claude.json'
208
+ if ((Test-Path $projMcp) -and (Select-String -Path $projMcp -Pattern 'llm-inspector' -Quiet)) {
209
+ Write-Host "MCP: wired in $projMcp"
210
+ } elseif ((Test-Path $homeMcp) -and (Select-String -Path $homeMcp -Pattern 'llm-inspector' -Quiet)) {
211
+ Write-Host "MCP: wired in $homeMcp"
212
+ } else {
213
+ Write-Host 'MCP: not wired'
214
+ }
215
+ \`\`\`
216
+
217
+ **DO:** Summarize the three checks in one line, then use \`AskUserQuestion\` to ask whether to skip the corresponding phases.
218
+
219
+ > **PAUSE** \u2014 call \`AskUserQuestion\` with:
220
+ > - header: \`Skip done\`
221
+ > - question: \`Proxy/CONFIG/MCP state: <summary>. Skip the phases that are already done?\`
222
+ > - options: \`["Yes, skip what's done", "No, walk me through everything again"]\`
223
+ > Wait for the answer before moving to Preflight.
224
+
225
+ ---
226
+
227
+ ## Preflight
141
228
 
142
- **EXPLAIN:** "Let's make sure everything we need is in place. Two quick checks."
229
+ **EXPLAIN:** "Quick env sanity check \u2014 make sure Node and Claude Code are present."
143
230
 
144
- **DO:** Run the platform-appropriate commands below. The user can copy-paste, or you can run them yourself if you have shell access.
231
+ **DO:** Run the platform-appropriate commands below.
145
232
 
146
233
  \`\`\`bash
147
234
  # Unix / macOS / WSL
148
- node --version # expect >= 18
149
- test -d "$HOME/.claude" && echo "claude-code: present" || echo "claude-code: not detected"
235
+ node --version # expect >= 18
236
+ command -v claude >/dev/null && echo "claude-code: present" || echo "claude-code: not detected"
150
237
  \`\`\`
151
238
 
152
239
  \`\`\`powershell
153
- # Windows PowerShell
154
- node --version # expect >= 18
155
- if (Test-Path "$env:USERPROFILE\\.claude") { Write-Host "claude-code: present" } else { Write-Host "claude-code: not detected" }
240
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
241
+ node --version # expect >= 18
242
+ $null = Get-Command claude -ErrorAction SilentlyContinue
243
+ if ($null) { Write-Host 'claude-code: present' } else { Write-Host 'claude-code: not detected' }
156
244
  \`\`\`
157
245
 
158
- **PAUSE** \u2014 if Node is older than 18, ask the user to install a newer version (https://nodejs.org) before continuing.
246
+ > **PAUSE** \u2014 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\`.
159
247
 
160
248
  ---
161
249
 
@@ -178,7 +266,7 @@ llm-inspector is a transparent HTTP proxy + Web UI for AI coding tools. Point yo
178
266
  Ready? Let's start with the provider.
179
267
  \`\`\`
180
268
 
181
- **PAUSE** \u2014 wait for the user to confirm.
269
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Ready?\` and options \`["Yes, let's go", "Wait, I have a question"]\`. Wait for the user before continuing.
182
270
 
183
271
  ---
184
272
 
@@ -186,24 +274,53 @@ Ready? Let's start with the provider.
186
274
 
187
275
  **EXPLAIN:** "A 'provider' is an upstream LLM endpoint \u2014 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."
188
276
 
189
- **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.
277
+ **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.
190
278
 
191
- \`\`\`json
279
+ **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) \u2014 the API key is a secret, so don't echo it back in the question UI.
280
+
281
+ \`\`\`bash
282
+ # After collecting the key, write the config
283
+ mkdir -p "$HOME/.llm-inspector"
284
+ cat > "$HOME/.llm-inspector/config.json" <<'JSON'
192
285
  {
193
286
  "providers": [
194
287
  {
195
288
  "id": "anthropic",
196
289
  "type": "anthropic",
197
- "apiKey": "sk-ant-...",
290
+ "apiKey": "REPLACE_ME_BEFORE_WRITING",
198
291
  "baseUrl": "https://api.anthropic.com"
199
292
  }
200
293
  ]
201
294
  }
295
+ JSON
296
+ # Then patch the apiKey with the user-provided value (use jq if available)
202
297
  \`\`\`
203
298
 
204
- Alternative (avoid touching the config file): point the user at the Web UI \u2192 top-right Settings button \u2192 Providers tab, which has a form-driven flow.
299
+ \`\`\`powershell
300
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
301
+ $dir = Join-Path $env:USERPROFILE '.llm-inspector'
302
+ $file = Join-Path $dir 'config.json'
303
+ New-Item -ItemType Directory -Force -Path $dir | Out-Null
304
+ @'
305
+ {
306
+ "providers": [
307
+ {
308
+ "id": "anthropic",
309
+ "type": "anthropic",
310
+ "apiKey": "REPLACE_ME_BEFORE_WRITING",
311
+ "baseUrl": "https://api.anthropic.com"
312
+ }
313
+ ]
314
+ }
315
+ '@ | Set-Content -Path $file -Encoding UTF8
316
+ # Then patch the apiKey with the user-provided value
317
+ \`\`\`
318
+
319
+ **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\`.
205
320
 
206
- **PAUSE** \u2014 wait for the user to confirm they have at least one provider with a key.
321
+ **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.
322
+
323
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Provider key\` and options \`["Key is in, continue", "Skip for now, I'll add it later"]\`. Wait for the answer.
207
324
 
208
325
  ---
209
326
 
@@ -211,7 +328,9 @@ Alternative (avoid touching the config file): point the user at the Web UI \u219
211
328
 
212
329
  **EXPLAIN:** "Time to start the proxy. It binds to port ${port} by default, kills any process already on that port, and prints the URL."
213
330
 
214
- **DO:** Start the proxy in the background so you can keep working.
331
+ **DO:** Skip this phase entirely if the Phase 0 health check already reported \`PROXY: up\` and the user opted to skip done phases.
332
+
333
+ **DO:** Otherwise, start the proxy in the background. On Windows, the npm shim is a \`.cmd\`/PowerShell file \u2014 \`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.
215
334
 
216
335
  \`\`\`bash
217
336
  # Unix / macOS / WSL
@@ -219,14 +338,30 @@ nohup llm-inspector --no-open > /tmp/llm-inspector.log 2>&1 &
219
338
  \`\`\`
220
339
 
221
340
  \`\`\`powershell
222
- # Windows PowerShell
223
- Start-Process -FilePath "llm-inspector" -ArgumentList "--no-open" -RedirectStandardOutput "$env:TEMP\\llm-inspector.log" -RedirectStandardError "$env:TEMP\\llm-inspector.err.log" -WindowStyle Hidden
341
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly.
342
+ # Locate the binary on PATH first (works for npm, pnpm, yarn, volta, fnm).
343
+ # If not on PATH, fall back to the common npm global shim at $env:APPDATA.
344
+ # As a last resort, let cmd /c resolve it through PATHEXT.
345
+ $log = Join-Path $env:TEMP 'llm-inspector.log'
346
+ $err = Join-Path $env:TEMP 'llm-inspector.err.log'
347
+ $found = Get-Command llm-inspector -ErrorAction SilentlyContinue
348
+ if ($found) {
349
+ $shim = $found.Source
350
+ $args = '--no-open'
351
+ } elseif (Test-Path (Join-Path $env:APPDATA 'npm/llm-inspector.cmd')) {
352
+ $shim = Join-Path $env:APPDATA 'npm/llm-inspector.cmd'
353
+ $args = '--no-open'
354
+ } else {
355
+ # bin not on PATH and not at the default npm prefix \u2014 let cmd resolve it
356
+ $shim = 'cmd.exe'
357
+ $args = '/c','start','/B','llm-inspector','--no-open'
358
+ }
359
+ Start-Process -FilePath $shim -ArgumentList $args -RedirectStandardOutput $log -RedirectStandardError $err -WindowStyle Hidden
224
360
  \`\`\`
225
361
 
226
362
  Then wait for the port to be ready:
227
363
 
228
364
  \`\`\`bash
229
- # Wait up to 10s for the port to come up
230
365
  for i in $(seq 1 20); do
231
366
  curl -fsS "http://localhost:${port}/api/health" >/dev/null 2>&1 && echo "ready" && break
232
367
  sleep 0.5
@@ -239,7 +374,7 @@ done
239
374
  curl -sS "http://localhost:${port}/api/health"
240
375
  \`\`\`
241
376
 
242
- **PAUSE** \u2014 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.
377
+ > **PAUSE** \u2014 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.
243
378
 
244
379
  ---
245
380
 
@@ -267,18 +402,22 @@ mimo
267
402
 
268
403
  For a tool that wasn't auto-detected, fall through to the generic curl test in the next phase \u2014 the user can wire their tool later.
269
404
 
270
- **PAUSE** \u2014 wait for the user to confirm they've set the env var (or that they're going to use the curl test instead).
405
+ > **PAUSE** \u2014 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.
271
406
 
272
407
  ---
273
408
 
274
409
  ## Phase 4.5: Wire MCP server
275
410
 
276
- **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 \u2014 no need to leave the editor. We'll add an \`mcpServers\` entry so your agent picks it up automatically."
411
+ **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 \u2014 no need to leave the editor."
277
412
 
278
- **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:
413
+ **DO:** Skip this phase if Phase 0 reported \`MCP: wired in <path>\` and the user opted to skip done phases.
414
+
415
+ **DO:** Otherwise, check the project-level \`.mcp.json\` first (preferred \u2014 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.
416
+
417
+ If neither has an \`llm-inspector\` entry, add one. The simplest path is to write to project \`.mcp.json\` (create it if missing):
279
418
 
280
419
  \`\`\`json
281
- // ~/.claude.json (merge into existing mcpServers)
420
+ // .mcp.json (project root)
282
421
  {
283
422
  "mcpServers": {
284
423
  "llm-inspector": {
@@ -289,26 +428,7 @@ For a tool that wasn't auto-detected, fall through to the generic curl test in t
289
428
  }
290
429
  \`\`\`
291
430
 
292
- If \`mcpServers\` already exists, add \`llm-inspector\` as a new key \u2014 don't clobber existing entries.
293
-
294
- PowerShell one-liner that creates the file if missing, or merges if present:
295
-
296
- \`\`\`powershell
297
- $cfg = Join-Path $env:USERPROFILE ".claude.json"
298
- if (Test-Path $cfg) {
299
- $doc = Get-Content $cfg -Raw | ConvertFrom-Json
300
- } else {
301
- $doc = [pscustomobject]@{ mcpServers = [pscustomobject]@{} }
302
- }
303
- if (-not ($doc.PSObject.Properties.Name -contains "mcpServers")) {
304
- $doc | Add-Member -NotePropertyName mcpServers -NotePropertyValue ([pscustomobject]@{})
305
- }
306
- $doc.mcpServers | Add-Member -NotePropertyName "llm-inspector" -NotePropertyValue ([pscustomobject]@{
307
- type = "http"
308
- url = "http://localhost:${port}/api/mcp"
309
- }) -Force
310
- $doc | ConvertTo-Json -Depth 10 | Set-Content $cfg -Encoding UTF8
311
- \`\`\`
431
+ If \`mcpServers\` already exists in \`.mcp.json\`, merge the \`llm-inspector\` key into it via the \`Edit\` tool \u2014 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.
312
432
 
313
433
  **DO:** Verify the handshake. The MCP \`initialize\` request should return 200 with a \`serverInfo\` payload \u2014 that proves the server is mounted and reachable:
314
434
 
@@ -325,41 +445,33 @@ curl -sS -X POST "http://localhost:${port}/api/mcp" \\
325
445
  "capabilities": {},
326
446
  "clientInfo": { "name": "onboard-check", "version": "0" }
327
447
  }
328
- }'
329
- # expect: HTTP 200, body contains "result" with serverInfo.name = "llm-inspector"
448
+ }' | grep -o '"name":"llm-inspector"' && echo "handshake OK"
330
449
  \`\`\`
331
450
 
332
- After the handshake, issue a \`tools/list\` to confirm the tool catalog is reachable:
451
+ The \`grep -o '"name":"llm-inspector"'\` extracts only the serverInfo name \u2014 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.
333
452
 
334
- \`\`\`bash
335
- SESSION=<session-id-from-initialize-response>
336
- curl -sS -X POST "http://localhost:${port}/api/mcp" \\
337
- -H "Content-Type: application/json" \\
338
- -H "Accept: application/json, text/event-stream" \\
339
- -H "mcp-session-id: $SESSION" \\
340
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
341
- # expect: a result.tools array with at least 1 entry
342
- \`\`\`
343
-
344
- **PAUSE** \u2014 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 \u2014 re-use the \`mcp-session-id\` header from the first response.
453
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`MCP OK?\` and options \`["Yes, handshake returned 200", "No, the call failed"]\`. Wait for the answer.
345
454
 
346
455
  ---
347
456
 
348
457
  ## Phase 5: First capture
349
458
 
350
- **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 \u2014 the point is that the *request* reaches the proxy."
459
+ **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."
351
460
 
352
- **DO:** Fire a minimal Anthropic-format request through the proxy. This works regardless of which tool the user wired up:
461
+ **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 \u2014 the proxy will still log the request.
462
+
463
+ Fire a minimal Anthropic-format request through the proxy:
353
464
 
354
465
  \`\`\`bash
355
466
  curl -sS -X POST "http://localhost:${port}/proxy/v1/messages" \\
356
467
  -H "Content-Type: application/json" \\
357
468
  -H "anthropic-version: 2023-06-01" \\
358
469
  -H "x-api-key: \${LLM_INSPECTOR_API_KEY:-sk-no-key-needed-for-routing}" \\
359
- -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}'
470
+ -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}' \\
471
+ -o /tmp/llm-inspector-capture.json -w 'STATUS:%{http_code}\\n'
360
472
  \`\`\`
361
473
 
362
- **DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means success:
474
+ **DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means the request reached the proxy:
363
475
 
364
476
  \`\`\`bash
365
477
  for i in $(seq 1 10); do
@@ -367,13 +479,24 @@ for i in $(seq 1 10); do
367
479
  count=$(echo "$resp" | grep -o '"total":[0-9]*' | head -1 | grep -o '[0-9]*$')
368
480
  if [ "\${count:-0}" -ge 1 ]; then
369
481
  echo "captured"
482
+ echo "$resp" | head -c 400
370
483
  break
371
484
  fi
372
485
  sleep 0.5
373
486
  done
374
487
  \`\`\`
375
488
 
376
- **PAUSE** \u2014 show the user the captured log entry (id, status, model, elapsed). If the count never reached 1, the proxy didn't see the request \u2014 re-check the env var and the proxy log.
489
+ **DO:** Diagnose the response based on the actual status and body. **Do not** default to "auth failure" for every 4xx.
490
+
491
+ | Status | Body hint | Meaning |
492
+ |--------|-----------|---------|
493
+ | 200 | normal | Real success \u2014 the upstream returned data |
494
+ | 401 | \`"unauthorized"\` or similar | Upstream rejected the key (expected with a test key) |
495
+ | 403 | \`"Request not allowed"\` | **Proxy's allowlist** \u2014 not an auth failure, the proxy rejected the model/config. Show the user the proxy log. |
496
+ | 403 | other text | Could be upstream ACL \u2014 different problem |
497
+ | 5xx | anything | Upstream network error |
498
+
499
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`Captured?\` and options matching the diagnosis above. Wait for the answer.
377
500
 
378
501
  ---
379
502
 
@@ -390,14 +513,14 @@ done
390
513
  # Unix / macOS
391
514
  lsof -ti:${port} | xargs -r kill -9
392
515
 
393
- # Windows PowerShell
394
- Get-NetTCPConnection -LocalPort ${port} | ForEach-Object { Stop-Process -Id \\$_.OwningProcess -Force }
516
+ # Windows PowerShell \u2014 single-quoted so $env: expands correctly
517
+ Get-NetTCPConnection -LocalPort $port | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }
395
518
  \`\`\`
396
519
 
397
520
  - **Re-run onboard**: \`llm-inspector onboard --force\` refreshes this skill.
398
521
  - **Full docs**: see the project README (linked from the Web UI footer).
399
522
 
400
- **PAUSE** \u2014 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.
523
+ > **PAUSE** \u2014 use \`AskUserQuestion\` with header \`All set?\` and options \`["All set, I'm done", "Wait, I want to revisit a phase"]\`. Wait for the answer.
401
524
 
402
525
  You're done. Happy inspecting.
403
526
  `;
@@ -407,6 +530,7 @@ var init_skill_onboard = __esm({
407
530
  "src/cli/templates/skill-onboard.ts"() {
408
531
  "use strict";
409
532
  REQUIRED_PHASE_HEADINGS = [
533
+ "Phase 0: Idempotency check",
410
534
  "Preflight",
411
535
  "Phase 1: Welcome",
412
536
  "Phase 2: Provider setup",
@@ -774,8 +898,11 @@ function runStart(args) {
774
898
  const serverEnv = { ...process.env };
775
899
  if (configDir !== void 0) {
776
900
  let resolvedPath = join3(configDir, "config.json");
777
- if (resolvedPath.startsWith("\\c\\")) {
778
- resolvedPath = "C:" + resolvedPath;
901
+ const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
902
+ if (msysMatch !== null) {
903
+ const drive = (msysMatch[1] ?? "").toUpperCase();
904
+ const rest = msysMatch[2] ?? "";
905
+ resolvedPath = `${drive}:\\${rest}`;
779
906
  }
780
907
  serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
781
908
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "date": "2026-06-17T12:54:18.572Z",
2
+ "date": "2026-06-18T01:14:58.083Z",
3
3
  "preset": "node-server",
4
4
  "framework": {
5
5
  "name": "nitro",
@@ -1 +1 @@
1
- import{r as h,j as t}from"./main-COVN451W.js";import{c as X,g as O,r as P,a as q,X as Y,b as x,B as Z,f as $,R as ee,C as te,M as _,d as J,e as M,h as B,i as re,j as ne}from"./ProxyViewerContainer-iv3LVMEW.js";import{J as N}from"./json-viewer-BB-9bqnP.js";const se=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],ae=X("equal",se),oe="";function j(e){if(e.length===0)return oe;let r="";for(let n=0;n<e.length;n++){const s=e[n];s!==void 0&&(typeof s=="number"?r+=`[${s}]`:n===0?r+=s:r+=`.${s}`)}return r}function de(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e){if(typeof e=="string")try{return C(JSON.parse(e))}catch{return{kind:"primitive",value:e}}return C(e)}function C(e){if(e===null)return{kind:"primitive",value:null};if(typeof e=="string")return{kind:"primitive",value:e};if(typeof e=="number")return{kind:"primitive",value:e};if(typeof e=="boolean")return{kind:"primitive",value:e};if(Array.isArray(e))return{kind:"array",value:e.map(r=>C(r))};if(de(e)){const r={};for(const n of Object.keys(e).sort())r[n]=C(e[n]);return{kind:"object",value:r}}return{kind:"primitive",value:null}}function ie(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(E(r,n)){s.push({kind:"equal",path:d,value:r});return}if(r.kind!==n.kind){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="primitive"&&n.kind==="primitive"){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="object"&&n.kind==="object"){const o=Object.keys(r.value),a=Object.keys(n.value),m=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!m.has(i))s.push({kind:"removed",path:j([...e,i]),value:f});else{const p=n.value[i];if(p===void 0)continue;R([...e,i],f,p,s)}}for(const i of a){if(o.includes(i))continue;const f=n.value[i];f!==void 0&&s.push({kind:"added",path:j([...e,i]),value:f})}return}if(r.kind==="array"&&n.kind==="array"){const o=Math.min(r.value.length,n.value.length);for(let a=0;a<o;a++){const m=r.value[a],i=n.value[a];m===void 0||i===void 0||R([...e,a],m,i,s)}for(let a=o;a<n.value.length;a++){const m=n.value[a];m!==void 0&&s.push({kind:"added",path:j([...e,a]),value:m})}for(let a=o;a<r.value.length;a++){const m=r.value[a];m!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:m})}}}function E(e,r){if(e.kind!==r.kind)return!1;if(e.kind==="primitive"&&r.kind==="primitive")return e.value===r.value;if(e.kind==="array"&&r.kind==="array"){if(e.value.length!==r.value.length)return!1;for(let n=0;n<e.value.length;n++){const s=e.value[n],d=r.value[n];if(s===void 0||d===void 0||!E(s,d))return!1}return!0}if(e.kind==="object"&&r.kind==="object"){const n=Object.keys(e.value),s=Object.keys(r.value);if(n.length!==s.length)return!1;for(const d of n){const o=e.value[d],a=r.value[d];if(o===void 0||a===void 0||!E(o,a))return!1}return!0}return!1}function v(e,r=80){let n;switch(e.kind){case"primitive":n=e.value===null?"null":JSON.stringify(e.value);break;case"array":n=`[… ${e.value.length} items]`;break;case"object":n=`{… ${Object.keys(e.value).length} keys}`;break}return n.length>r&&(n=`${n.slice(0,r-1)}…`),n}function w(e,r=2){return JSON.stringify(S(e),null,r)}function S(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(S);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=S(s);return r}}}function L(e){if(e==="")return"";for(let r=e.length-1;r>=0;r--){const n=e[r];if(n==="."||n==="[")return e.substring(0,r)}return""}function A(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function le(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&A(s)){const d=L(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!A(a)||L(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let m=n;m<o;m++){const i=e[m];i!==void 0&&i.kind==="equal"&&a.push(i)}r.push({kind:"equal-run",ops:a}),n=o;continue}}s!==void 0&&r.push({kind:"single",op:s}),n++}return r}const V={added:{icon:J,accent:"text-emerald-600 dark:text-emerald-400",bg:"bg-emerald-500/5 hover:bg-emerald-500/10",border:"border-l-emerald-500",label:"ADDED"},removed:{icon:_,accent:"text-rose-600 dark:text-rose-400",bg:"bg-rose-500/5 hover:bg-rose-500/10",border:"border-l-rose-500",label:"REMOVED"},changed:{icon:M,accent:"text-amber-600 dark:text-amber-400",bg:"bg-amber-500/5 hover:bg-amber-500/10",border:"border-l-amber-500",label:"CHANGED"},equal:{icon:ae,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ce({ops:e,expanded:r,onToggle:n}){const s=e[0],d=e[e.length-1];if(s===void 0||d===void 0)return t.jsx("div",{className:"text-muted-foreground/40 text-xs",children:"—"});const o=s.path,a=d.path,m=e.length===1?o:`${o} … ${a}`,i=s.value.kind==="array"?`${e.length} equal arrays`:s.value.kind==="object"?`${e.length} equal objects`:"equal",f=V.equal;return t.jsxs("div",{className:x("border-l-4 rounded-sm",f.border,f.bg),children:[t.jsxs("button",{type:"button",onClick:n,className:"w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",children:[t.jsx(B,{className:x("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:x("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:m}),t.jsx("span",{className:x("text-[10px] uppercase tracking-wider shrink-0",f.accent),children:f.label}),t.jsxs("span",{className:"text-muted-foreground/60 shrink-0",children:["(",i,")"]})]}),r&&t.jsx("div",{className:"ml-5 mt-1 mb-2 space-y-2 pr-2",children:e.map(p=>t.jsxs("div",{className:"border border-border/50 rounded p-2 bg-muted/20",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:p.path}),t.jsx(N,{text:w(p.value),defaultExpandDepth:0})]},p.path))})]})}function ue({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=V[e.kind],m=a.icon,i=e.kind==="added"||e.kind==="removed"?e.value.kind==="object"||e.value.kind==="array":e.kind==="changed"?e.left.kind==="object"||e.left.kind==="array"||e.right.kind==="object"||e.right.kind==="array":!1,f=e.kind==="changed"?[{text:v(e.left,400),tone:"text-rose-700 dark:text-rose-300 line-through"},{text:v(e.right,400),tone:"text-emerald-700 dark:text-emerald-300"}]:e.kind==="removed"?[{text:v(e.value,400),tone:"text-rose-700 dark:text-rose-300 line-through"}]:e.kind==="added"?[{text:v(e.value,400),tone:"text-emerald-700 dark:text-emerald-300"}]:[{text:v(e.value,400),tone:"text-muted-foreground"}],p=n===e.path&&e.path!=="";return t.jsxs("div",{"data-diff-idx":r,"data-diff-kind":e.kind,className:x("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors",a.border,a.bg),children:[t.jsxs("button",{type:"button",onClick:o,disabled:!i,className:x("w-full flex items-center gap-2 text-xs text-left rounded-sm",i?"cursor-pointer":"cursor-default"),"aria-expanded":i?d:void 0,"aria-label":i?d?`Collapse ${e.path||"root"}`:`Expand ${e.path||"root"}`:void 0,children:[i?t.jsx(B,{className:x("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(m,{className:x("size-3.5 shrink-0",a.accent),strokeWidth:2.5}),t.jsx("span",{className:"font-mono truncate flex-1 min-w-0",title:e.path||"(root)",children:e.path===""?"(root)":e.path}),t.jsx("span",{className:x("text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",a.accent,e.kind==="equal"?"bg-muted/40":"bg-background/60"),children:a.label}),e.path!==""&&t.jsx("span",{role:"button",tabIndex:0,onClick:b=>{b.stopPropagation(),s(e.path)},onKeyDown:b=>{(b.key==="Enter"||b.key===" ")&&(b.stopPropagation(),b.preventDefault(),s(e.path))},className:x("shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",p?"text-emerald-500":"text-muted-foreground/50 hover:text-foreground hover:bg-muted"),"aria-label":p?"Copied":"Copy",title:p?"Copied!":"Copy",children:p?t.jsx(re,{className:"size-3"}):t.jsx(ne,{className:"size-3"})})]}),f.map((b,y)=>t.jsx("div",{className:x("font-mono text-xs mt-1 break-all pl-5",b.tone),children:b.text},y)),t.jsx("div",{className:"overflow-hidden transition-all duration-200",style:{maxHeight:d&&i?"2000px":"0"},"aria-hidden":!d,children:d&&i&&e.kind!=="equal"?t.jsx(me,{op:e}):null})]})}function me({op:e}){if(e.kind==="added"||e.kind==="removed")return t.jsx("div",{className:"pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20",children:t.jsx(N,{text:w(e.value),defaultExpandDepth:0})});const r=e.left.kind==="object"||e.left.kind==="array",n=e.right.kind==="object"||e.right.kind==="array";return!r&&!n?t.jsx("div",{className:"pl-5 mt-2 text-xs text-muted-foreground/70 italic",children:"Primitive values are shown inline above."}):t.jsxs("div",{className:"pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2",children:[t.jsxs("div",{className:"border border-rose-500/30 rounded p-2 bg-rose-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-rose-500 mb-1",children:"Old"}),t.jsx(N,{text:w(e.left),defaultExpandDepth:0})]}),t.jsxs("div",{className:"border border-emerald-500/30 rounded p-2 bg-emerald-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-emerald-500 mb-1",children:"New"}),t.jsx(N,{text:w(e.right),defaultExpandDepth:0})]})]})}function xe({counts:e,onJumpTo:r}){const n=e.added+e.removed+e.changed;return t.jsxs("div",{className:"px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap",children:[t.jsxs("span",{className:"text-muted-foreground font-medium",children:[n," ",n===1?"change":"changes"]}),t.jsxs("button",{type:"button",onClick:()=>r("removed"),disabled:e.removed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.removed>0?"border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.removed>0?"Jump to first removed":"No removals",children:[t.jsx(_,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.added>0?"border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.added>0?"Jump to first added":"No additions",children:[t.jsx(J,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.changed>0?"border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.changed>0?"Jump to first changed":"No changes",children:[t.jsx(M,{className:"size-3"}),e.changed," changed"]})]})}function fe({mode:e,onChange:r}){return t.jsxs("div",{className:"inline-flex rounded-md border border-border overflow-hidden",children:[t.jsxs("button",{type:"button",onClick:()=>r("unified"),"aria-pressed":e==="unified",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",e==="unified"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Unified view (single column, emphasized diffs)",children:[t.jsx(ee,{className:"size-3"}),"Unified"]}),t.jsxs("button",{type:"button",onClick:()=>r("split"),"aria-pressed":e==="split",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",e==="split"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Split view (path | left | right)",children:[t.jsx(te,{className:"size-3"}),"Split"]})]})}function K({log:e,side:r}){const n=q(e);return t.jsxs("div",{className:"flex-1 min-w-0 space-y-1 text-xs",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Z,{variant:"outline",className:x("text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",r==="left"?"border-rose-500/40 text-rose-400":"border-emerald-500/40 text-emerald-400"),children:r==="left"?"← Left":"Right →"}),t.jsxs("span",{className:"font-mono text-blue-400/80",children:["#",e.id]}),e.model!==null&&t.jsx("span",{className:"font-mono text-muted-foreground truncate",children:e.model})]}),t.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground font-mono",children:[e.cacheCreationInputTokens!==null&&e.cacheCreationInputTokens>0&&t.jsxs("span",{className:"text-emerald-400",children:["Cache +",$(e.cacheCreationInputTokens)]}),e.cacheReadInputTokens!==null&&e.cacheReadInputTokens>0&&t.jsxs("span",{className:"text-purple-400",children:["Cache ~",$(e.cacheReadInputTokens)]}),t.jsx("span",{className:"truncate",title:e.timestamp,children:e.timestamp})]}),t.jsxs("div",{className:"text-muted-foreground/70 font-mono truncate",title:n,children:["session: ",n]})]})}function ge({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=O(P(e)).analyzeRequest(e.rawRequestBody),c=O(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return ie(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>le(s),[s]),o=h.useMemo(()=>{let l=0,c=0,u=0;for(const g of d)if(g.kind==="single")switch(g.op.kind){case"added":l++;break;case"removed":c++;break;case"changed":u++;break}return{added:l,removed:c,changed:u}},[d]),[a,m]=h.useState(new Set),i=l=>{m(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})},[f,p]=h.useState(new Set),b=l=>{p(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})};h.useEffect(()=>{p(new Set)},[e.id,r.id]);const[y,F]=h.useState("unified"),T=h.useRef(null),[U,z]=h.useState(null),k=h.useRef(null),H=l=>{window.navigator.clipboard.writeText(l).then(()=>{z(l),k.current!==null&&clearTimeout(k.current),k.current=setTimeout(()=>z(null),1500)})};h.useEffect(()=>()=>{k.current!==null&&clearTimeout(k.current)},[]);const G=l=>{const c=d.findIndex(I=>I.kind==="single"&&I.op.kind===l);if(c===-1)return;const u=T.current;if(u===null)return;const g=u.querySelector(`[data-diff-idx="${c}"]`);g!==null&&g.scrollIntoView({behavior:"smooth",block:"center"})};h.useEffect(()=>{const l=u=>{u.key==="Escape"&&n()};document.addEventListener("keydown",l);const c=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.removeEventListener("keydown",l),document.body.style.overflow=c}},[n]);const Q=q(e)===q(r),W=s.length===1&&s[0]?.kind==="equal";return t.jsxs("div",{className:"fixed inset-0 z-50 flex justify-end",role:"dialog","aria-modal":"true","aria-label":"Compare two log requests",children:[t.jsx("button",{type:"button",onClick:n,"aria-label":"Close compare drawer",className:"absolute inset-0 bg-black/40 cursor-default",tabIndex:-1}),t.jsxs("div",{className:x("relative bg-background border-l border-border shadow-xl","w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"),onClick:l=>l.stopPropagation(),onKeyDown:l=>l.stopPropagation(),children:[t.jsxs("div",{className:"flex items-start gap-4 px-4 py-3 border-b border-border",children:[t.jsxs("div",{className:"flex-1 flex gap-4 min-w-0",children:[t.jsx(K,{log:e,side:"left"}),t.jsx(K,{log:r,side:"right"})]}),t.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[t.jsx(fe,{mode:y,onChange:F}),t.jsx("button",{type:"button",onClick:n,"aria-label":"Close",className:"p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",children:t.jsx(Y,{className:"size-4"})})]})]}),!Q&&t.jsx("div",{className:"px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border",children:"Heads up: the two selected logs are from different sessions."}),W?t.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm",children:"The two Request payloads are identical."}):t.jsxs(t.Fragment,{children:[t.jsx(xe,{counts:o,onJumpTo:G}),t.jsx("div",{ref:T,className:"flex-1 min-h-0 overflow-y-auto",children:y==="unified"?t.jsx("div",{className:"px-3 py-2 space-y-0.5",children:d.map((l,c)=>{if(l.kind==="equal-run")return t.jsx(ce,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(ue,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(pe,{grouped:d,left:e,right:r})})]})]})]})}function pe({grouped:e,left:r,right:n}){return t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs",children:[t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground",children:[t.jsx("span",{children:"Path"}),t.jsxs("span",{children:["Left (Log #",r.id,")"]}),t.jsxs("span",{children:["Right (Log #",n.id,")"]})]}),e.map((s,d)=>{if(s.kind==="equal-run")return t.jsxs("div",{className:"col-span-3 px-2 py-1 text-xs text-muted-foreground/60",children:[s.ops.length," equal siblings collapsed — switch to Unified to expand"]},d);const o=s.op;return o.kind==="equal"?t.jsxs("div",{className:"col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",children:[t.jsx("span",{className:"font-mono text-xs truncate",title:o.path,children:o.path}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)})]},d):o.kind==="added"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-emerald-300/90",children:["+ ",v(o.value,400)]})]},d):o.kind==="removed"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-rose-300/90 line-through",children:["− ",v(o.value,400)]})]},d):t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:o.path}),t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[t.jsx("div",{className:"font-mono text-rose-300/90 break-all line-through",children:v(o.left,400)}),t.jsx("div",{className:"font-mono text-emerald-300/90 break-all",children:v(o.right,400)})]})]},d)})]})}export{ge as CompareDrawer};
1
+ import{r as h,j as t}from"./main-CR9IJlz1.js";import{c as X,g as O,r as P,a as q,X as Y,b as x,B as Z,f as $,R as ee,C as te,M as _,d as J,e as M,h as B,i as re,j as ne}from"./ProxyViewerContainer-DfxRK7Nt.js";import{J as N}from"./json-viewer-CztuZ9cT.js";const se=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],ae=X("equal",se),oe="";function j(e){if(e.length===0)return oe;let r="";for(let n=0;n<e.length;n++){const s=e[n];s!==void 0&&(typeof s=="number"?r+=`[${s}]`:n===0?r+=s:r+=`.${s}`)}return r}function de(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e){if(typeof e=="string")try{return C(JSON.parse(e))}catch{return{kind:"primitive",value:e}}return C(e)}function C(e){if(e===null)return{kind:"primitive",value:null};if(typeof e=="string")return{kind:"primitive",value:e};if(typeof e=="number")return{kind:"primitive",value:e};if(typeof e=="boolean")return{kind:"primitive",value:e};if(Array.isArray(e))return{kind:"array",value:e.map(r=>C(r))};if(de(e)){const r={};for(const n of Object.keys(e).sort())r[n]=C(e[n]);return{kind:"object",value:r}}return{kind:"primitive",value:null}}function ie(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(E(r,n)){s.push({kind:"equal",path:d,value:r});return}if(r.kind!==n.kind){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="primitive"&&n.kind==="primitive"){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="object"&&n.kind==="object"){const o=Object.keys(r.value),a=Object.keys(n.value),m=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!m.has(i))s.push({kind:"removed",path:j([...e,i]),value:f});else{const p=n.value[i];if(p===void 0)continue;R([...e,i],f,p,s)}}for(const i of a){if(o.includes(i))continue;const f=n.value[i];f!==void 0&&s.push({kind:"added",path:j([...e,i]),value:f})}return}if(r.kind==="array"&&n.kind==="array"){const o=Math.min(r.value.length,n.value.length);for(let a=0;a<o;a++){const m=r.value[a],i=n.value[a];m===void 0||i===void 0||R([...e,a],m,i,s)}for(let a=o;a<n.value.length;a++){const m=n.value[a];m!==void 0&&s.push({kind:"added",path:j([...e,a]),value:m})}for(let a=o;a<r.value.length;a++){const m=r.value[a];m!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:m})}}}function E(e,r){if(e.kind!==r.kind)return!1;if(e.kind==="primitive"&&r.kind==="primitive")return e.value===r.value;if(e.kind==="array"&&r.kind==="array"){if(e.value.length!==r.value.length)return!1;for(let n=0;n<e.value.length;n++){const s=e.value[n],d=r.value[n];if(s===void 0||d===void 0||!E(s,d))return!1}return!0}if(e.kind==="object"&&r.kind==="object"){const n=Object.keys(e.value),s=Object.keys(r.value);if(n.length!==s.length)return!1;for(const d of n){const o=e.value[d],a=r.value[d];if(o===void 0||a===void 0||!E(o,a))return!1}return!0}return!1}function v(e,r=80){let n;switch(e.kind){case"primitive":n=e.value===null?"null":JSON.stringify(e.value);break;case"array":n=`[… ${e.value.length} items]`;break;case"object":n=`{… ${Object.keys(e.value).length} keys}`;break}return n.length>r&&(n=`${n.slice(0,r-1)}…`),n}function w(e,r=2){return JSON.stringify(S(e),null,r)}function S(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(S);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=S(s);return r}}}function L(e){if(e==="")return"";for(let r=e.length-1;r>=0;r--){const n=e[r];if(n==="."||n==="[")return e.substring(0,r)}return""}function A(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function le(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&A(s)){const d=L(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!A(a)||L(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let m=n;m<o;m++){const i=e[m];i!==void 0&&i.kind==="equal"&&a.push(i)}r.push({kind:"equal-run",ops:a}),n=o;continue}}s!==void 0&&r.push({kind:"single",op:s}),n++}return r}const V={added:{icon:J,accent:"text-emerald-600 dark:text-emerald-400",bg:"bg-emerald-500/5 hover:bg-emerald-500/10",border:"border-l-emerald-500",label:"ADDED"},removed:{icon:_,accent:"text-rose-600 dark:text-rose-400",bg:"bg-rose-500/5 hover:bg-rose-500/10",border:"border-l-rose-500",label:"REMOVED"},changed:{icon:M,accent:"text-amber-600 dark:text-amber-400",bg:"bg-amber-500/5 hover:bg-amber-500/10",border:"border-l-amber-500",label:"CHANGED"},equal:{icon:ae,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ce({ops:e,expanded:r,onToggle:n}){const s=e[0],d=e[e.length-1];if(s===void 0||d===void 0)return t.jsx("div",{className:"text-muted-foreground/40 text-xs",children:"—"});const o=s.path,a=d.path,m=e.length===1?o:`${o} … ${a}`,i=s.value.kind==="array"?`${e.length} equal arrays`:s.value.kind==="object"?`${e.length} equal objects`:"equal",f=V.equal;return t.jsxs("div",{className:x("border-l-4 rounded-sm",f.border,f.bg),children:[t.jsxs("button",{type:"button",onClick:n,className:"w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",children:[t.jsx(B,{className:x("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:x("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:m}),t.jsx("span",{className:x("text-[10px] uppercase tracking-wider shrink-0",f.accent),children:f.label}),t.jsxs("span",{className:"text-muted-foreground/60 shrink-0",children:["(",i,")"]})]}),r&&t.jsx("div",{className:"ml-5 mt-1 mb-2 space-y-2 pr-2",children:e.map(p=>t.jsxs("div",{className:"border border-border/50 rounded p-2 bg-muted/20",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:p.path}),t.jsx(N,{text:w(p.value),defaultExpandDepth:0})]},p.path))})]})}function ue({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=V[e.kind],m=a.icon,i=e.kind==="added"||e.kind==="removed"?e.value.kind==="object"||e.value.kind==="array":e.kind==="changed"?e.left.kind==="object"||e.left.kind==="array"||e.right.kind==="object"||e.right.kind==="array":!1,f=e.kind==="changed"?[{text:v(e.left,400),tone:"text-rose-700 dark:text-rose-300 line-through"},{text:v(e.right,400),tone:"text-emerald-700 dark:text-emerald-300"}]:e.kind==="removed"?[{text:v(e.value,400),tone:"text-rose-700 dark:text-rose-300 line-through"}]:e.kind==="added"?[{text:v(e.value,400),tone:"text-emerald-700 dark:text-emerald-300"}]:[{text:v(e.value,400),tone:"text-muted-foreground"}],p=n===e.path&&e.path!=="";return t.jsxs("div",{"data-diff-idx":r,"data-diff-kind":e.kind,className:x("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors",a.border,a.bg),children:[t.jsxs("button",{type:"button",onClick:o,disabled:!i,className:x("w-full flex items-center gap-2 text-xs text-left rounded-sm",i?"cursor-pointer":"cursor-default"),"aria-expanded":i?d:void 0,"aria-label":i?d?`Collapse ${e.path||"root"}`:`Expand ${e.path||"root"}`:void 0,children:[i?t.jsx(B,{className:x("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(m,{className:x("size-3.5 shrink-0",a.accent),strokeWidth:2.5}),t.jsx("span",{className:"font-mono truncate flex-1 min-w-0",title:e.path||"(root)",children:e.path===""?"(root)":e.path}),t.jsx("span",{className:x("text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",a.accent,e.kind==="equal"?"bg-muted/40":"bg-background/60"),children:a.label}),e.path!==""&&t.jsx("span",{role:"button",tabIndex:0,onClick:b=>{b.stopPropagation(),s(e.path)},onKeyDown:b=>{(b.key==="Enter"||b.key===" ")&&(b.stopPropagation(),b.preventDefault(),s(e.path))},className:x("shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",p?"text-emerald-500":"text-muted-foreground/50 hover:text-foreground hover:bg-muted"),"aria-label":p?"Copied":"Copy",title:p?"Copied!":"Copy",children:p?t.jsx(re,{className:"size-3"}):t.jsx(ne,{className:"size-3"})})]}),f.map((b,y)=>t.jsx("div",{className:x("font-mono text-xs mt-1 break-all pl-5",b.tone),children:b.text},y)),t.jsx("div",{className:"overflow-hidden transition-all duration-200",style:{maxHeight:d&&i?"2000px":"0"},"aria-hidden":!d,children:d&&i&&e.kind!=="equal"?t.jsx(me,{op:e}):null})]})}function me({op:e}){if(e.kind==="added"||e.kind==="removed")return t.jsx("div",{className:"pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20",children:t.jsx(N,{text:w(e.value),defaultExpandDepth:0})});const r=e.left.kind==="object"||e.left.kind==="array",n=e.right.kind==="object"||e.right.kind==="array";return!r&&!n?t.jsx("div",{className:"pl-5 mt-2 text-xs text-muted-foreground/70 italic",children:"Primitive values are shown inline above."}):t.jsxs("div",{className:"pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2",children:[t.jsxs("div",{className:"border border-rose-500/30 rounded p-2 bg-rose-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-rose-500 mb-1",children:"Old"}),t.jsx(N,{text:w(e.left),defaultExpandDepth:0})]}),t.jsxs("div",{className:"border border-emerald-500/30 rounded p-2 bg-emerald-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-emerald-500 mb-1",children:"New"}),t.jsx(N,{text:w(e.right),defaultExpandDepth:0})]})]})}function xe({counts:e,onJumpTo:r}){const n=e.added+e.removed+e.changed;return t.jsxs("div",{className:"px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap",children:[t.jsxs("span",{className:"text-muted-foreground font-medium",children:[n," ",n===1?"change":"changes"]}),t.jsxs("button",{type:"button",onClick:()=>r("removed"),disabled:e.removed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.removed>0?"border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.removed>0?"Jump to first removed":"No removals",children:[t.jsx(_,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.added>0?"border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.added>0?"Jump to first added":"No additions",children:[t.jsx(J,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.changed>0?"border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.changed>0?"Jump to first changed":"No changes",children:[t.jsx(M,{className:"size-3"}),e.changed," changed"]})]})}function fe({mode:e,onChange:r}){return t.jsxs("div",{className:"inline-flex rounded-md border border-border overflow-hidden",children:[t.jsxs("button",{type:"button",onClick:()=>r("unified"),"aria-pressed":e==="unified",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",e==="unified"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Unified view (single column, emphasized diffs)",children:[t.jsx(ee,{className:"size-3"}),"Unified"]}),t.jsxs("button",{type:"button",onClick:()=>r("split"),"aria-pressed":e==="split",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",e==="split"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Split view (path | left | right)",children:[t.jsx(te,{className:"size-3"}),"Split"]})]})}function K({log:e,side:r}){const n=q(e);return t.jsxs("div",{className:"flex-1 min-w-0 space-y-1 text-xs",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Z,{variant:"outline",className:x("text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",r==="left"?"border-rose-500/40 text-rose-400":"border-emerald-500/40 text-emerald-400"),children:r==="left"?"← Left":"Right →"}),t.jsxs("span",{className:"font-mono text-blue-400/80",children:["#",e.id]}),e.model!==null&&t.jsx("span",{className:"font-mono text-muted-foreground truncate",children:e.model})]}),t.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground font-mono",children:[e.cacheCreationInputTokens!==null&&e.cacheCreationInputTokens>0&&t.jsxs("span",{className:"text-emerald-400",children:["Cache +",$(e.cacheCreationInputTokens)]}),e.cacheReadInputTokens!==null&&e.cacheReadInputTokens>0&&t.jsxs("span",{className:"text-purple-400",children:["Cache ~",$(e.cacheReadInputTokens)]}),t.jsx("span",{className:"truncate",title:e.timestamp,children:e.timestamp})]}),t.jsxs("div",{className:"text-muted-foreground/70 font-mono truncate",title:n,children:["session: ",n]})]})}function ge({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=O(P(e)).analyzeRequest(e.rawRequestBody),c=O(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return ie(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>le(s),[s]),o=h.useMemo(()=>{let l=0,c=0,u=0;for(const g of d)if(g.kind==="single")switch(g.op.kind){case"added":l++;break;case"removed":c++;break;case"changed":u++;break}return{added:l,removed:c,changed:u}},[d]),[a,m]=h.useState(new Set),i=l=>{m(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})},[f,p]=h.useState(new Set),b=l=>{p(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})};h.useEffect(()=>{p(new Set)},[e.id,r.id]);const[y,F]=h.useState("unified"),T=h.useRef(null),[U,z]=h.useState(null),k=h.useRef(null),H=l=>{window.navigator.clipboard.writeText(l).then(()=>{z(l),k.current!==null&&clearTimeout(k.current),k.current=setTimeout(()=>z(null),1500)})};h.useEffect(()=>()=>{k.current!==null&&clearTimeout(k.current)},[]);const G=l=>{const c=d.findIndex(I=>I.kind==="single"&&I.op.kind===l);if(c===-1)return;const u=T.current;if(u===null)return;const g=u.querySelector(`[data-diff-idx="${c}"]`);g!==null&&g.scrollIntoView({behavior:"smooth",block:"center"})};h.useEffect(()=>{const l=u=>{u.key==="Escape"&&n()};document.addEventListener("keydown",l);const c=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.removeEventListener("keydown",l),document.body.style.overflow=c}},[n]);const Q=q(e)===q(r),W=s.length===1&&s[0]?.kind==="equal";return t.jsxs("div",{className:"fixed inset-0 z-50 flex justify-end",role:"dialog","aria-modal":"true","aria-label":"Compare two log requests",children:[t.jsx("button",{type:"button",onClick:n,"aria-label":"Close compare drawer",className:"absolute inset-0 bg-black/40 cursor-default",tabIndex:-1}),t.jsxs("div",{className:x("relative bg-background border-l border-border shadow-xl","w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"),onClick:l=>l.stopPropagation(),onKeyDown:l=>l.stopPropagation(),children:[t.jsxs("div",{className:"flex items-start gap-4 px-4 py-3 border-b border-border",children:[t.jsxs("div",{className:"flex-1 flex gap-4 min-w-0",children:[t.jsx(K,{log:e,side:"left"}),t.jsx(K,{log:r,side:"right"})]}),t.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[t.jsx(fe,{mode:y,onChange:F}),t.jsx("button",{type:"button",onClick:n,"aria-label":"Close",className:"p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",children:t.jsx(Y,{className:"size-4"})})]})]}),!Q&&t.jsx("div",{className:"px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border",children:"Heads up: the two selected logs are from different sessions."}),W?t.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm",children:"The two Request payloads are identical."}):t.jsxs(t.Fragment,{children:[t.jsx(xe,{counts:o,onJumpTo:G}),t.jsx("div",{ref:T,className:"flex-1 min-h-0 overflow-y-auto",children:y==="unified"?t.jsx("div",{className:"px-3 py-2 space-y-0.5",children:d.map((l,c)=>{if(l.kind==="equal-run")return t.jsx(ce,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(ue,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(pe,{grouped:d,left:e,right:r})})]})]})]})}function pe({grouped:e,left:r,right:n}){return t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs",children:[t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground",children:[t.jsx("span",{children:"Path"}),t.jsxs("span",{children:["Left (Log #",r.id,")"]}),t.jsxs("span",{children:["Right (Log #",n.id,")"]})]}),e.map((s,d)=>{if(s.kind==="equal-run")return t.jsxs("div",{className:"col-span-3 px-2 py-1 text-xs text-muted-foreground/60",children:[s.ops.length," equal siblings collapsed — switch to Unified to expand"]},d);const o=s.op;return o.kind==="equal"?t.jsxs("div",{className:"col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",children:[t.jsx("span",{className:"font-mono text-xs truncate",title:o.path,children:o.path}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)})]},d):o.kind==="added"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-emerald-300/90",children:["+ ",v(o.value,400)]})]},d):o.kind==="removed"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-rose-300/90 line-through",children:["− ",v(o.value,400)]})]},d):t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:o.path}),t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[t.jsx("div",{className:"font-mono text-rose-300/90 break-all line-through",children:v(o.left,400)}),t.jsx("div",{className:"font-mono text-emerald-300/90 break-all",children:v(o.right,400)})]})]},d)})]})}export{ge as CompareDrawer};