@tonyclaw/llm-inspector 1.18.2 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.output/cli.js +776 -139
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-C-4ypEWs.js → CompareDrawer-DwayZPPO.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-iv3LVMEW.js +101 -0
  5. package/.output/public/assets/{ReplayDialog-CyBKOgba.js → ReplayDialog-CaV1elYO.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-C0IrVQ3q.js → RequestAnatomy-CSfnjK7j.js} +1 -1
  7. package/.output/public/assets/{ResponseView-MogToC4i.js → ResponseView-YkOL__xm.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-ClhUhT-s.js → StreamingChunkSequence-D_p6L-oB.js} +1 -1
  9. package/.output/public/assets/_sessionId-BgCVUC6R.js +1 -0
  10. package/.output/public/assets/index-CWA4S0FO.js +1 -0
  11. package/.output/public/assets/index-DeJyypsp.css +1 -0
  12. package/.output/public/assets/{json-viewer-BicGakI5.js → json-viewer-BB-9bqnP.js} +2 -2
  13. package/.output/public/assets/{main-Be2qqUUW.js → main-COVN451W.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +93 -72
  15. package/.output/server/{_sessionId-DhKJIdQC.mjs → _sessionId-BJT5qIib.mjs} +2 -2
  16. package/.output/server/_ssr/{CompareDrawer-BGUgukJ8.mjs → CompareDrawer-DNGYdUXs.mjs} +4 -4
  17. package/.output/server/_ssr/{ProxyViewerContainer--3K3o3Sm.mjs → ProxyViewerContainer-B-zDOLYE.mjs} +354 -343
  18. package/.output/server/_ssr/{ReplayDialog-Bo86xZI4.mjs → ReplayDialog-DWeqMA4y.mjs} +4 -4
  19. package/.output/server/_ssr/{RequestAnatomy-jRU5qgwB.mjs → RequestAnatomy-TOsrMu9-.mjs} +3 -3
  20. package/.output/server/_ssr/{ResponseView-DdO_-79a.mjs → ResponseView-BuqdPrzm.mjs} +4 -4
  21. package/.output/server/_ssr/{StreamingChunkSequence-BigLwhh4.mjs → StreamingChunkSequence-DuzNZkqL.mjs} +3 -3
  22. package/.output/server/_ssr/{index-BHG6vOnr.mjs → index-1nCQUt3y.mjs} +2 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-B4c_WjXD.mjs → json-viewer-BL8xhHbi.mjs} +9 -5
  25. package/.output/server/_ssr/{router-DVixpJO-.mjs → router-aCaUgVTW.mjs} +3 -3
  26. package/.output/server/{_tanstack-start-manifest_v-BbvWUF4v.mjs → _tanstack-start-manifest_v-cBRxvCjb.mjs} +1 -1
  27. package/.output/server/index.mjs +61 -61
  28. package/package.json +2 -1
  29. package/src/cli/detect-tools.ts +146 -0
  30. package/src/cli/onboard.ts +229 -0
  31. package/src/cli/templates/command-onboard.ts +17 -0
  32. package/src/cli/templates/skill-onboard.ts +325 -0
  33. package/src/cli.ts +185 -163
  34. package/src/components/ProxyViewer.tsx +153 -142
  35. package/src/components/proxy-viewer/LogEntry.tsx +136 -157
  36. package/src/components/proxy-viewer/LogEntryHeader.tsx +147 -66
  37. package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
  38. package/src/components/ui/json-viewer.tsx +12 -0
  39. package/.output/public/assets/ProxyViewerContainer-WRenRpeh.js +0 -101
  40. package/.output/public/assets/_sessionId-BO47oA3Z.js +0 -1
  41. package/.output/public/assets/index-BRvz6-L6.css +0 -1
  42. package/.output/public/assets/index-Btw8ec7-.js +0 -1
@@ -0,0 +1,229 @@
1
+ /**
2
+ * `llm-inspector onboard` subcommand.
3
+ *
4
+ * Generates a Claude Code skill + slash command into the user's home dir so
5
+ * the user can run `/llm-inspector:onboard` to walk through a guided setup.
6
+ *
7
+ * Idempotent by default — re-runs without `--force` are no-ops. `--force`
8
+ * overwrites, `--dry-run` writes nothing. Designed to be safe to invoke
9
+ * from npm's `postinstall` script (any error is logged to stderr, not
10
+ * thrown — the postinstall must not fail the install).
11
+ */
12
+
13
+ import { mkdirSync, writeFileSync, existsSync, readFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { dirname, join } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ import { detectAll, detectFirst } from "./detect-tools.js";
19
+ import { renderCommandOnboard } from "./templates/command-onboard.js";
20
+ import { renderSkillOnboard, REQUIRED_PHASE_HEADINGS } from "./templates/skill-onboard.js";
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ const DEFAULT_PORT = 25947;
26
+ const SKILL_DIR_NAME = "llm-inspector-onboard";
27
+ const SKILL_FILE_NAME = "SKILL.md";
28
+ // Windows reserves `:` for NTFS alternate data streams, so a file named
29
+ // `llm-inspector:onboard.md` becomes an empty `llm-inspector` on disk.
30
+ // Use `-` on Windows and `:` on Unix to match the slash-command convention
31
+ // (`/llm-inspector:onboard` vs `/llm-inspector-onboard`) within the
32
+ // constraints of each platform.
33
+ const COMMAND_FILE_NAME =
34
+ process.platform === "win32" ? "llm-inspector-onboard.md" : "llm-inspector:onboard.md";
35
+
36
+ export type OnboardFlags = {
37
+ force: boolean;
38
+ dryRun: boolean;
39
+ skipProvider: boolean;
40
+ skipToolWire: boolean;
41
+ skillDir: string | null;
42
+ };
43
+
44
+ function parseFlags(argv: readonly string[]): OnboardFlags {
45
+ const flags: OnboardFlags = {
46
+ force: false,
47
+ dryRun: false,
48
+ skipProvider: false,
49
+ skipToolWire: false,
50
+ skillDir: null,
51
+ };
52
+ for (let i = 0; i < argv.length; i++) {
53
+ const arg = argv[i];
54
+ switch (arg) {
55
+ case undefined:
56
+ continue;
57
+ case "--force":
58
+ flags.force = true;
59
+ break;
60
+ case "--dry-run":
61
+ flags.dryRun = true;
62
+ break;
63
+ case "--skip-provider":
64
+ flags.skipProvider = true;
65
+ break;
66
+ case "--skip-tool-wire":
67
+ flags.skipToolWire = true;
68
+ break;
69
+ case "--skill-dir": {
70
+ const next = argv[i + 1];
71
+ if (next === undefined) {
72
+ process.stderr.write("llm-inspector onboard: --skill-dir requires a path argument\n");
73
+ process.exit(2);
74
+ }
75
+ flags.skillDir = next;
76
+ i++;
77
+ break;
78
+ }
79
+ case "--help":
80
+ case "-h":
81
+ printHelp();
82
+ process.exit(0);
83
+ break;
84
+ default:
85
+ process.stderr.write(`llm-inspector onboard: unknown flag: ${arg}\n`);
86
+ process.exit(2);
87
+ }
88
+ }
89
+ return flags;
90
+ }
91
+
92
+ function printHelp(): void {
93
+ process.stdout.write(`llm-inspector onboard — install the llm-inspector Claude Code skill
94
+
95
+ Usage:
96
+ llm-inspector onboard [options]
97
+
98
+ Options:
99
+ --force Overwrite the existing skill and slash command if they exist
100
+ --dry-run Print target paths and a template preview, write nothing
101
+ --skip-provider Skip the provider-setup phase in the skill body
102
+ --skip-tool-wire Skip the wire-tool phase in the skill body
103
+ --skill-dir <path> Override the target skill directory (default: ~/.claude/skills)
104
+ -h, --help Show this help
105
+
106
+ Exit codes:
107
+ 0 success (or already installed)
108
+ 2 invalid arguments
109
+ 1 write failed
110
+ `);
111
+ }
112
+
113
+ function resolveTargets(flags: OnboardFlags): {
114
+ skillFile: string;
115
+ commandFile: string;
116
+ } {
117
+ const claudeRoot = flags.skillDir ?? join(homedir(), ".claude");
118
+ const skillDir = join(claudeRoot, "skills", SKILL_DIR_NAME);
119
+ const commandsDir = join(claudeRoot, "commands");
120
+ return {
121
+ skillFile: join(skillDir, SKILL_FILE_NAME),
122
+ commandFile: join(commandsDir, COMMAND_FILE_NAME),
123
+ };
124
+ }
125
+
126
+ function isObject(value: unknown): value is Record<string, unknown> {
127
+ return typeof value === "object" && value !== null;
128
+ }
129
+
130
+ function buildDetectedSummary(): string {
131
+ const entries = detectAll();
132
+ const present = entries.filter((e) => e.result.found);
133
+ const absent = entries.filter((e) => !e.result.found);
134
+ const presentList = present.map((e) => e.displayName).join(", ") || "(none)";
135
+ const absentList = absent.map((e) => e.displayName).join(", ") || "(none)";
136
+ return ` - Detected: ${presentList}\n - Not detected: ${absentList}`;
137
+ }
138
+
139
+ export function runOnboard(argv: readonly string[]): Promise<number> {
140
+ try {
141
+ return Promise.resolve(runOnboardSync(argv));
142
+ } catch (err) {
143
+ // Last-resort safety net. The postinstall hook relies on this never
144
+ // surfacing — log to stderr and exit 0 so npm install keeps working.
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ process.stderr.write(`llm-inspector onboard: ${msg}\n`);
147
+ process.stderr.write(
148
+ "(postinstall skill install skipped — run `llm-inspector onboard` later)\n",
149
+ );
150
+ return Promise.resolve(0);
151
+ }
152
+ }
153
+
154
+ function runOnboardSync(argv: readonly string[]): number {
155
+ const flags = parseFlags(argv);
156
+ const { skillFile, commandFile } = resolveTargets(flags);
157
+
158
+ // Idempotency check — only relevant for the skill file. If the slash
159
+ // command file is missing but the skill is present, that's weird; treat
160
+ // it the same as "already installed" so a partial state doesn't get
161
+ // half-rewritten on the next run.
162
+ if (!flags.force && existsSync(skillFile)) {
163
+ process.stdout.write(`llm-inspector onboard: already installed at ${skillFile}\n`);
164
+ process.stdout.write("Re-run with --force to refresh.\n");
165
+ return 0;
166
+ }
167
+
168
+ // Version stamp — read from package.json so the skill and the published
169
+ // package can never disagree about which version wrote them.
170
+ let version = "0.0.0";
171
+ try {
172
+ const raw: unknown = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf8"));
173
+ if (isObject(raw) && typeof raw["version"] === "string") {
174
+ version = raw["version"];
175
+ }
176
+ } catch {
177
+ // best-effort: leave version as the fallback
178
+ }
179
+
180
+ const detectedSummary = buildDetectedSummary();
181
+ const skillBody = renderSkillOnboard({
182
+ version,
183
+ port: DEFAULT_PORT,
184
+ detectedSummary,
185
+ });
186
+ const commandBody = renderCommandOnboard();
187
+
188
+ if (flags.dryRun) {
189
+ process.stdout.write(`llm-inspector onboard --dry-run\n\n`);
190
+ process.stdout.write(`Skill target: ${skillFile}\n`);
191
+ process.stdout.write(`Command target: ${commandFile}\n\n`);
192
+ process.stdout.write(`Skill preview (first 5 lines + headings):\n`);
193
+ const previewLines = skillBody.split("\n").slice(0, 5);
194
+ process.stdout.write(`${previewLines.join("\n")}\n`);
195
+ process.stdout.write(`...\n`);
196
+ for (const heading of REQUIRED_PHASE_HEADINGS) {
197
+ process.stdout.write(` - ${heading}\n`);
198
+ }
199
+ process.stdout.write(`\nDetected tools:\n${detectedSummary}\n`);
200
+ process.stdout.write(`\nNo files were written.\n`);
201
+ return 0;
202
+ }
203
+
204
+ // Ensure target dirs exist. mkdirSync with recursive: true is a no-op if
205
+ // the dir already exists, and on failure it throws — caught by the
206
+ // outer try/catch and reported as a soft postinstall skip.
207
+ mkdirSync(join(skillFile, ".."), { recursive: true });
208
+ mkdirSync(join(commandFile, ".."), { recursive: true });
209
+
210
+ try {
211
+ writeFileSync(skillFile, skillBody, "utf8");
212
+ writeFileSync(commandFile, commandBody, "utf8");
213
+ } catch (err) {
214
+ const msg = err instanceof Error ? err.message : String(err);
215
+ process.stderr.write(`llm-inspector onboard: failed to write files: ${msg}\n`);
216
+ return 1;
217
+ }
218
+
219
+ const firstTool = detectFirst();
220
+ const toolHint = firstTool !== null ? firstTool.displayName : "no known tool detected";
221
+
222
+ process.stdout.write(`Installed skill to: ${skillFile}\n`);
223
+ process.stdout.write(`Installed command to: ${commandFile}\n`);
224
+ process.stdout.write(`\nNext steps:\n`);
225
+ process.stdout.write(` - Open Claude Code and run: /llm-inspector:onboard\n`);
226
+ process.stdout.write(` - Or refresh later: llm-inspector onboard --force\n`);
227
+ process.stdout.write(` - Detected primary tool: ${toolHint}\n`);
228
+ return 0;
229
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Renders `~/.claude/commands/llm-inspector:onboard.md` — a thin slash
3
+ * command that loads the `llm-inspector-onboard` skill. Claude Code
4
+ * auto-discovers both the skill and the command once they're written,
5
+ * so the command body just references the skill by name.
6
+ */
7
+
8
+ export function renderCommandOnboard(): string {
9
+ return `---
10
+ description: Walk through llm-inspector setup — start the proxy, wire your AI tool, capture your first request.
11
+ ---
12
+
13
+ Invoke the \`llm-inspector-onboard\` skill and follow its phases.
14
+
15
+ The user wants to set up llm-inspector. If they have specific context (e.g. "I'm on Windows", "I already added my API key"), honor it — but otherwise just run the skill end-to-end and let the \`EXPLAIN / DO / PAUSE\` markers drive the conversation.
16
+ `;
17
+ }
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Renders `~/.claude/skills/llm-inspector-onboard/SKILL.md` for the
3
+ * `llm-inspector onboard` subcommand. The body is a 7-phase teaching
4
+ * workflow with `EXPLAIN / DO / PAUSE` markers — Claude reads it and walks
5
+ * the user through real actions (start the proxy, hit /api/health, post a
6
+ * test request, poll /api/logs) instead of just printing a static tutorial.
7
+ *
8
+ * The template embeds platform-aware shell snippets inline (bash + PowerShell
9
+ * side-by-side). Skills are pure Markdown — they can't `require()` from the
10
+ * package at runtime, so the duplication is the price of portability.
11
+ */
12
+
13
+ export type SkillOnboardContext = {
14
+ /** Package version (from `package.json`), stamped into the frontmatter. */
15
+ readonly version: string;
16
+ /** The default proxy port (mirrors `DEFAULT_PORT` in `src/cli.ts`). */
17
+ readonly port: number;
18
+ /**
19
+ * One-line "detected tools" summary written into the Preflight phase so the
20
+ * skill's instructions match the user's actual environment. Empty string
21
+ * means "no known tool detected — fall through to the curl example".
22
+ */
23
+ readonly detectedSummary: string;
24
+ };
25
+
26
+ /** The 8 phase headings the spec requires. The validator checks for these. */
27
+ export const REQUIRED_PHASE_HEADINGS = [
28
+ "Preflight",
29
+ "Phase 1: Welcome",
30
+ "Phase 2: Provider setup",
31
+ "Phase 3: Start proxy",
32
+ "Phase 4: Wire tool",
33
+ "Phase 4.5: Wire MCP server",
34
+ "Phase 5: First capture",
35
+ "Phase 6: Tour & wrap",
36
+ ] as const;
37
+
38
+ export function renderSkillOnboard(ctx: SkillOnboardContext): string {
39
+ const { version, port, detectedSummary } = ctx;
40
+ return `---
41
+ name: llm-inspector-onboard
42
+ description: Guided setup for llm-inspector v${version} — start the proxy, wire your AI coding tool, capture your first request, and learn the Web UI.
43
+ metadata:
44
+ author: llm-inspector
45
+ version: ${version}
46
+ ---
47
+
48
+ # llm-inspector onboard
49
+
50
+ Guide the user from "I just installed llm-inspector" to "I can see my AI tool's traffic in the Web UI". This is a teaching experience — you'll do real work in their environment while explaining each step.
51
+
52
+ Environment detected by the installer:
53
+ ${detectedSummary || " (no known AI tool detected — the user can still use the generic curl example in Phase 4)"}
54
+
55
+ Default proxy port: \`${port}\` (override with \`PORT=<n> llm-inspector\` or \`--port <n>\`).
56
+
57
+ ---
58
+
59
+ ## Preflight
60
+
61
+ Before starting, verify the environment.
62
+
63
+ **EXPLAIN:** "Let's make sure everything we need is in place. Two quick checks."
64
+
65
+ **DO:** Run the platform-appropriate commands below. The user can copy-paste, or you can run them yourself if you have shell access.
66
+
67
+ \`\`\`bash
68
+ # Unix / macOS / WSL
69
+ node --version # expect >= 18
70
+ test -d "$HOME/.claude" && echo "claude-code: present" || echo "claude-code: not detected"
71
+ \`\`\`
72
+
73
+ \`\`\`powershell
74
+ # Windows PowerShell
75
+ node --version # expect >= 18
76
+ if (Test-Path "$env:USERPROFILE\\.claude") { Write-Host "claude-code: present" } else { Write-Host "claude-code: not detected" }
77
+ \`\`\`
78
+
79
+ **PAUSE** — if Node is older than 18, ask the user to install a newer version (https://nodejs.org) before continuing.
80
+
81
+ ---
82
+
83
+ ## Phase 1: Welcome
84
+
85
+ **EXPLAIN:**
86
+
87
+ \`\`\`
88
+ ## Welcome to llm-inspector!
89
+
90
+ llm-inspector is a transparent HTTP proxy + Web UI for AI coding tools. Point your tool at it, and you'll see every request and response — system prompts, tool definitions, message history, SSE streaming chunks, and token counts — captured live in a browser tab.
91
+
92
+ **What we'll do in the next ~10 minutes:**
93
+ 1. Add your first LLM provider (Anthropic or OpenAI key)
94
+ 2. Start the proxy
95
+ 3. Wire your AI tool to use it
96
+ 4. Capture a first request end-to-end
97
+ 5. Tour the key UI affordances
98
+
99
+ Ready? Let's start with the provider.
100
+ \`\`\`
101
+
102
+ **PAUSE** — wait for the user to confirm.
103
+
104
+ ---
105
+
106
+ ## Phase 2: Provider setup
107
+
108
+ **EXPLAIN:** "A 'provider' is an upstream LLM endpoint — Anthropic, OpenAI, MiniMax, etc. llm-inspector routes each request to the right upstream based on the model name. You need at least one provider configured for the proxy to forward traffic."
109
+
110
+ **DO:** Open (or create) \`~/.llm-inspector/config.json\` in the user's editor. If the file doesn't exist, create it with the structure below. Walk the user through filling in their API key for their provider of choice.
111
+
112
+ \`\`\`json
113
+ {
114
+ "providers": [
115
+ {
116
+ "id": "anthropic",
117
+ "type": "anthropic",
118
+ "apiKey": "sk-ant-...",
119
+ "baseUrl": "https://api.anthropic.com"
120
+ }
121
+ ]
122
+ }
123
+ \`\`\`
124
+
125
+ Alternative (avoid touching the config file): point the user at the Web UI → top-right Settings button → Providers tab, which has a form-driven flow.
126
+
127
+ **PAUSE** — wait for the user to confirm they have at least one provider with a key.
128
+
129
+ ---
130
+
131
+ ## Phase 3: Start proxy
132
+
133
+ **EXPLAIN:** "Time to start the proxy. It binds to port ${port} by default, kills any process already on that port, and prints the URL."
134
+
135
+ **DO:** Start the proxy in the background so you can keep working.
136
+
137
+ \`\`\`bash
138
+ # Unix / macOS / WSL
139
+ nohup llm-inspector --no-open > /tmp/llm-inspector.log 2>&1 &
140
+ \`\`\`
141
+
142
+ \`\`\`powershell
143
+ # Windows PowerShell
144
+ Start-Process -FilePath "llm-inspector" -ArgumentList "--no-open" -RedirectStandardOutput "$env:TEMP\\llm-inspector.log" -RedirectStandardError "$env:TEMP\\llm-inspector.err.log" -WindowStyle Hidden
145
+ \`\`\`
146
+
147
+ Then wait for the port to be ready:
148
+
149
+ \`\`\`bash
150
+ # Wait up to 10s for the port to come up
151
+ for i in $(seq 1 20); do
152
+ curl -fsS "http://localhost:${port}/api/health" >/dev/null 2>&1 && echo "ready" && break
153
+ sleep 0.5
154
+ done
155
+ \`\`\`
156
+
157
+ **DO:** Hit the health endpoint to confirm the proxy is alive:
158
+
159
+ \`\`\`bash
160
+ curl -sS "http://localhost:${port}/api/health"
161
+ \`\`\`
162
+
163
+ **PAUSE** — if the health check fails, show the user the log file (\`/tmp/llm-inspector.log\` or \`%TEMP%\\\\llm-inspector.log\`) and diagnose. Common issues: another process on the port, firewall, missing providers.
164
+
165
+ ---
166
+
167
+ ## Phase 4: Wire tool
168
+
169
+ **EXPLAIN:** "Now we tell your AI tool to send traffic through the proxy instead of directly to the upstream API. The exact env var depends on which tool you have."
170
+
171
+ **DO:** Based on the \`Environment detected\` block at the top of this skill, print the matching wiring command. Examples for each supported tool:
172
+
173
+ \`\`\`bash
174
+ # Claude Code
175
+ export ANTHROPIC_BASE_URL=http://localhost:${port}/proxy
176
+ claude
177
+
178
+ # OpenCode
179
+ export LLM_BASE_URL=http://localhost:${port}/proxy
180
+ opencode
181
+
182
+ # MiMo Code
183
+ export OPENAI_BASE_URL=http://localhost:${port}/proxy
184
+ mimo
185
+
186
+ # Cursor / Cody — set the OpenAI base URL in each tool's settings panel to http://localhost:${port}/proxy
187
+ \`\`\`
188
+
189
+ For a tool that wasn't auto-detected, fall through to the generic curl test in the next phase — the user can wire their tool later.
190
+
191
+ **PAUSE** — wait for the user to confirm they've set the env var (or that they're going to use the curl test instead).
192
+
193
+ ---
194
+
195
+ ## Phase 4.5: Wire MCP server
196
+
197
+ **EXPLAIN:** "The proxy also exposes an MCP server at \`http://localhost:${port}/api/mcp\`. Your AI agent can query logs, replay requests, and test providers through it — no need to leave the editor. We'll add an \`mcpServers\` entry so your agent picks it up automatically."
198
+
199
+ **DO:** Add (or merge) an \`mcpServers\` entry in the user's Claude Code config. The file is \`~/.claude.json\` on all platforms; the MCP spec entry uses HTTP Streamable transport:
200
+
201
+ \`\`\`json
202
+ // ~/.claude.json (merge into existing mcpServers)
203
+ {
204
+ "mcpServers": {
205
+ "llm-inspector": {
206
+ "type": "http",
207
+ "url": "http://localhost:${port}/api/mcp"
208
+ }
209
+ }
210
+ }
211
+ \`\`\`
212
+
213
+ If \`mcpServers\` already exists, add \`llm-inspector\` as a new key — don't clobber existing entries.
214
+
215
+ PowerShell one-liner that creates the file if missing, or merges if present:
216
+
217
+ \`\`\`powershell
218
+ $cfg = Join-Path $env:USERPROFILE ".claude.json"
219
+ if (Test-Path $cfg) {
220
+ $doc = Get-Content $cfg -Raw | ConvertFrom-Json
221
+ } else {
222
+ $doc = [pscustomobject]@{ mcpServers = [pscustomobject]@{} }
223
+ }
224
+ if (-not ($doc.PSObject.Properties.Name -contains "mcpServers")) {
225
+ $doc | Add-Member -NotePropertyName mcpServers -NotePropertyValue ([pscustomobject]@{})
226
+ }
227
+ $doc.mcpServers | Add-Member -NotePropertyName "llm-inspector" -NotePropertyValue ([pscustomobject]@{
228
+ type = "http"
229
+ url = "http://localhost:${port}/api/mcp"
230
+ }) -Force
231
+ $doc | ConvertTo-Json -Depth 10 | Set-Content $cfg -Encoding UTF8
232
+ \`\`\`
233
+
234
+ **DO:** Verify the handshake. The MCP \`initialize\` request should return 200 with a \`serverInfo\` payload — that proves the server is mounted and reachable:
235
+
236
+ \`\`\`bash
237
+ curl -sS -X POST "http://localhost:${port}/api/mcp" \\
238
+ -H "Content-Type: application/json" \\
239
+ -H "Accept: application/json, text/event-stream" \\
240
+ -d '{
241
+ "jsonrpc": "2.0",
242
+ "id": 1,
243
+ "method": "initialize",
244
+ "params": {
245
+ "protocolVersion": "2025-03-26",
246
+ "capabilities": {},
247
+ "clientInfo": { "name": "onboard-check", "version": "0" }
248
+ }
249
+ }'
250
+ # expect: HTTP 200, body contains "result" with serverInfo.name = "llm-inspector"
251
+ \`\`\`
252
+
253
+ After the handshake, issue a \`tools/list\` to confirm the tool catalog is reachable:
254
+
255
+ \`\`\`bash
256
+ SESSION=<session-id-from-initialize-response>
257
+ curl -sS -X POST "http://localhost:${port}/api/mcp" \\
258
+ -H "Content-Type: application/json" \\
259
+ -H "Accept: application/json, text/event-stream" \\
260
+ -H "mcp-session-id: $SESSION" \\
261
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
262
+ # expect: a result.tools array with at least 1 entry
263
+ \`\`\`
264
+
265
+ **PAUSE** — if \`initialize\` returns non-200, show the user the proxy log and re-check the JSON syntax. If it returns 200 but \`tools/list\` fails, the server is up but the session wasn't carried over — re-use the \`mcp-session-id\` header from the first response.
266
+
267
+ ---
268
+
269
+ ## Phase 5: First capture
270
+
271
+ **EXPLAIN:** "Let's prove the proxy works end-to-end. We'll send one real request through it and confirm the log shows up in the API. A 401/403 from the upstream is fine — the point is that the *request* reaches the proxy."
272
+
273
+ **DO:** Fire a minimal Anthropic-format request through the proxy. This works regardless of which tool the user wired up:
274
+
275
+ \`\`\`bash
276
+ curl -sS -X POST "http://localhost:${port}/proxy/v1/messages" \\
277
+ -H "Content-Type: application/json" \\
278
+ -H "anthropic-version: 2023-06-01" \\
279
+ -H "x-api-key: \${LLM_INSPECTOR_API_KEY:-sk-no-key-needed-for-routing}" \\
280
+ -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":1,"messages":[{"role":"user","content":"ping"}]}'
281
+ \`\`\`
282
+
283
+ **DO:** Poll the logs API for up to 5 seconds. A 200 with at least one entry means success:
284
+
285
+ \`\`\`bash
286
+ for i in $(seq 1 10); do
287
+ resp=$(curl -sS "http://localhost:${port}/api/logs?limit=1")
288
+ count=$(echo "$resp" | grep -o '"total":[0-9]*' | head -1 | grep -o '[0-9]*$')
289
+ if [ "\${count:-0}" -ge 1 ]; then
290
+ echo "captured"
291
+ break
292
+ fi
293
+ sleep 0.5
294
+ done
295
+ \`\`\`
296
+
297
+ **PAUSE** — show the user the captured log entry (id, status, model, elapsed). If the count never reached 1, the proxy didn't see the request — re-check the env var and the proxy log.
298
+
299
+ ---
300
+
301
+ ## Phase 6: Tour & wrap
302
+
303
+ **EXPLAIN:** "Everything's working. Here's the cheat sheet for the Web UI and the supporting surfaces:"
304
+
305
+ - **Web UI**: \`http://localhost:${port}/\` — collapsible log rows, per-tab Copy/Expand in the log header, Diff with Raw (request body), Diff with Previous (compare adjacent requests), Replay (re-send a request), Export (JSON ZIP).
306
+ - **MCP server**: \`http://localhost:${port}/api/mcp\` — connect from your coding agent to query logs, replay, and test providers without leaving the editor. Look for the "MCP Ready" badge in the Web UI header.
307
+ - **REST API**: \`/api/logs\`, \`/api/sessions\`, \`/api/providers\` — for scripting and shell-based inspection.
308
+ - **Stop the proxy**:
309
+
310
+ \`\`\`bash
311
+ # Unix / macOS
312
+ lsof -ti:${port} | xargs -r kill -9
313
+
314
+ # Windows PowerShell
315
+ Get-NetTCPConnection -LocalPort ${port} | ForEach-Object { Stop-Process -Id \\$_.OwningProcess -Force }
316
+ \`\`\`
317
+
318
+ - **Re-run onboard**: \`llm-inspector onboard --force\` refreshes this skill.
319
+ - **Full docs**: see the project README (linked from the Web UI footer).
320
+
321
+ **PAUSE** — let the user know they can come back to this skill at any time via \`/llm-inspector:onboard\` if they want a refresher, and call out that \`/llm-inspector:onboard --skip-tool-wire\` is the way to re-run later phases without re-detecting the tool.
322
+
323
+ You're done. Happy inspecting.
324
+ `;
325
+ }