@tonyclaw/llm-inspector 1.19.0 → 1.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/cli.js +196 -69
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-DwayZPPO.js → CompareDrawer-DtERUdIt.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-iv3LVMEW.js → ProxyViewerContainer-DfxRK7Nt.js} +4 -4
- package/.output/public/assets/{ReplayDialog-CaV1elYO.js → ReplayDialog-VMsGnJSI.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-CSfnjK7j.js → RequestAnatomy-Cx_vluvK.js} +1 -1
- package/.output/public/assets/{ResponseView-YkOL__xm.js → ResponseView-5F8Ms5z4.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-D_p6L-oB.js → StreamingChunkSequence-CKDCWfu9.js} +1 -1
- package/.output/public/assets/_sessionId-C-aKd1Ky.js +1 -0
- package/.output/public/assets/index-B8ttyigz.js +1 -0
- package/.output/public/assets/{json-viewer-BB-9bqnP.js → json-viewer-CztuZ9cT.js} +1 -1
- package/.output/public/assets/{main-COVN451W.js → main-CR9IJlz1.js} +2 -2
- package/.output/server/{_sessionId-BJT5qIib.mjs → _sessionId-DvWQaDEm.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-DNGYdUXs.mjs → CompareDrawer-C5FsxSDS.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-B-zDOLYE.mjs → ProxyViewerContainer-v0cvR8f5.mjs} +9 -9
- package/.output/server/_ssr/{ReplayDialog-DWeqMA4y.mjs → ReplayDialog-C3KOv9OW.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-TOsrMu9-.mjs → RequestAnatomy-BYRe33eG.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-BuqdPrzm.mjs → ResponseView-va7yQDeL.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DuzNZkqL.mjs → StreamingChunkSequence-BJlI-gWl.mjs} +3 -3
- package/.output/server/_ssr/{index-1nCQUt3y.mjs → index-CS0fA2GT.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BL8xhHbi.mjs → json-viewer-Dg8rqrxL.mjs} +2 -2
- package/.output/server/_ssr/{router-aCaUgVTW.mjs → router-D_Boe9Bu.mjs} +2 -2
- package/.output/server/{_tanstack-start-manifest_v-cBRxvCjb.mjs → _tanstack-start-manifest_v-KFXyNRGC.mjs} +1 -1
- package/.output/server/index.mjs +63 -63
- package/package.json +1 -1
- package/src/cli/templates/skill-onboard.ts +202 -69
- package/src/cli.ts +12 -4
- package/.output/public/assets/_sessionId-BgCVUC6R.js +0 -1
- package/.output/public/assets/index-CWA4S0FO.js +0 -1
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
|
-
##
|
|
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
|
-
|
|
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:** "
|
|
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.
|
|
231
|
+
**DO:** Run the platform-appropriate commands below.
|
|
145
232
|
|
|
146
233
|
\`\`\`bash
|
|
147
234
|
# Unix / macOS / WSL
|
|
148
|
-
node --version
|
|
149
|
-
|
|
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
|
|
155
|
-
|
|
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
|
|
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:**
|
|
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
|
-
|
|
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": "
|
|
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
|
-
|
|
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
|
-
**
|
|
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:**
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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:**
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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:**
|
|
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
|
|
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
|
-
**
|
|
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 $
|
|
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
|
|
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
|
-
|
|
778
|
-
|
|
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
|
}
|
package/.output/nitro.json
CHANGED
|
@@ -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};
|