@tonyclaw/llm-inspector 1.18.2 → 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 +903 -139
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-C-4ypEWs.js → CompareDrawer-DtERUdIt.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DfxRK7Nt.js +101 -0
- package/.output/public/assets/{ReplayDialog-CyBKOgba.js → ReplayDialog-VMsGnJSI.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-C0IrVQ3q.js → RequestAnatomy-Cx_vluvK.js} +1 -1
- package/.output/public/assets/{ResponseView-MogToC4i.js → ResponseView-5F8Ms5z4.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-ClhUhT-s.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/index-DeJyypsp.css +1 -0
- package/.output/public/assets/{json-viewer-BicGakI5.js → json-viewer-CztuZ9cT.js} +2 -2
- package/.output/public/assets/{main-Be2qqUUW.js → main-CR9IJlz1.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +93 -72
- package/.output/server/{_sessionId-DhKJIdQC.mjs → _sessionId-DvWQaDEm.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BGUgukJ8.mjs → CompareDrawer-C5FsxSDS.mjs} +4 -4
- package/.output/server/_ssr/{ProxyViewerContainer--3K3o3Sm.mjs → ProxyViewerContainer-v0cvR8f5.mjs} +354 -343
- package/.output/server/_ssr/{ReplayDialog-Bo86xZI4.mjs → ReplayDialog-C3KOv9OW.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-jRU5qgwB.mjs → RequestAnatomy-BYRe33eG.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-DdO_-79a.mjs → ResponseView-va7yQDeL.mjs} +4 -4
- package/.output/server/_ssr/{StreamingChunkSequence-BigLwhh4.mjs → StreamingChunkSequence-BJlI-gWl.mjs} +3 -3
- package/.output/server/_ssr/{index-BHG6vOnr.mjs → index-CS0fA2GT.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-B4c_WjXD.mjs → json-viewer-Dg8rqrxL.mjs} +9 -5
- package/.output/server/_ssr/{router-DVixpJO-.mjs → router-D_Boe9Bu.mjs} +3 -3
- package/.output/server/{_tanstack-start-manifest_v-BbvWUF4v.mjs → _tanstack-start-manifest_v-KFXyNRGC.mjs} +1 -1
- package/.output/server/index.mjs +65 -65
- package/package.json +2 -1
- package/src/cli/detect-tools.ts +146 -0
- package/src/cli/onboard.ts +229 -0
- package/src/cli/templates/command-onboard.ts +17 -0
- package/src/cli/templates/skill-onboard.ts +458 -0
- package/src/cli.ts +193 -163
- package/src/components/ProxyViewer.tsx +153 -142
- package/src/components/proxy-viewer/LogEntry.tsx +136 -157
- package/src/components/proxy-viewer/LogEntryHeader.tsx +147 -66
- package/src/components/proxy-viewer/useCopyFeedback.ts +36 -0
- package/src/components/ui/json-viewer.tsx +12 -0
- package/.output/public/assets/ProxyViewerContainer-WRenRpeh.js +0 -101
- package/.output/public/assets/_sessionId-BO47oA3Z.js +0 -1
- package/.output/public/assets/index-BRvz6-L6.css +0 -1
- package/.output/public/assets/index-Btw8ec7-.js +0 -1
package/src/cli.ts
CHANGED
|
@@ -9,182 +9,212 @@ const __dirname = dirname(__filename);
|
|
|
9
9
|
|
|
10
10
|
const DEFAULT_PORT = 25947;
|
|
11
11
|
|
|
12
|
-
const envPort = process.env["PORT"];
|
|
13
|
-
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
14
|
-
|
|
15
|
-
// Simple argument parsing
|
|
16
|
-
const args = process.argv.slice(2);
|
|
17
|
-
let port = portDefault;
|
|
18
|
-
let open = true;
|
|
19
|
-
let configDir: string | undefined;
|
|
20
|
-
let providersJson: string | undefined;
|
|
21
|
-
|
|
22
|
-
for (let i = 0; i < args.length; i++) {
|
|
23
|
-
const arg = args[i] ?? "";
|
|
24
|
-
switch (arg) {
|
|
25
|
-
case "--port":
|
|
26
|
-
case "-p":
|
|
27
|
-
port = Number(args[i + 1]);
|
|
28
|
-
i++;
|
|
29
|
-
break;
|
|
30
|
-
case "--no-open":
|
|
31
|
-
open = false;
|
|
32
|
-
break;
|
|
33
|
-
case "--open":
|
|
34
|
-
open = true;
|
|
35
|
-
break;
|
|
36
|
-
case "--config-dir":
|
|
37
|
-
configDir = args[i + 1];
|
|
38
|
-
i++;
|
|
39
|
-
break;
|
|
40
|
-
case "--providers":
|
|
41
|
-
providersJson = args[i + 1];
|
|
42
|
-
i++;
|
|
43
|
-
break;
|
|
44
|
-
default:
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
12
|
/**
|
|
50
|
-
*
|
|
13
|
+
* Subcommand router. The legacy one-liner UX (`llm-inspector` with no args,
|
|
14
|
+
* or `llm-inspector start`) starts the proxy. `llm-inspector onboard` runs
|
|
15
|
+
* the guided skill install. Unknown subcommands fall back to the legacy
|
|
16
|
+
* behavior so a stray positional arg doesn't break an old invocation.
|
|
51
17
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
18
|
+
const subcommand = process.argv[2];
|
|
19
|
+
|
|
20
|
+
if (subcommand === "onboard") {
|
|
21
|
+
const { runOnboard } = await import("./cli/onboard.js");
|
|
22
|
+
const code = await runOnboard(process.argv.slice(3));
|
|
23
|
+
process.exit(code);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
runStart(process.argv.slice(2));
|
|
27
|
+
|
|
28
|
+
// -----------------------------------------------------------------------------
|
|
29
|
+
// Legacy `start` behavior — start the proxy on the configured port. Extracted
|
|
30
|
+
// into a function so the router above can keep the top-level flow readable.
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
function runStart(args: string[]): void {
|
|
33
|
+
const envPort = process.env["PORT"];
|
|
34
|
+
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
35
|
+
|
|
36
|
+
let port = portDefault;
|
|
37
|
+
let open = true;
|
|
38
|
+
let configDir: string | undefined;
|
|
39
|
+
let providersJson: string | undefined;
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < args.length; i++) {
|
|
42
|
+
const arg = args[i] ?? "";
|
|
43
|
+
switch (arg) {
|
|
44
|
+
case "--port":
|
|
45
|
+
case "-p":
|
|
46
|
+
port = Number(args[i + 1]);
|
|
47
|
+
i++;
|
|
48
|
+
break;
|
|
49
|
+
case "--no-open":
|
|
50
|
+
open = false;
|
|
51
|
+
break;
|
|
52
|
+
case "--open":
|
|
53
|
+
open = true;
|
|
54
|
+
break;
|
|
55
|
+
case "--config-dir":
|
|
56
|
+
configDir = args[i + 1];
|
|
57
|
+
i++;
|
|
58
|
+
break;
|
|
59
|
+
case "--providers":
|
|
60
|
+
providersJson = args[i + 1];
|
|
61
|
+
i++;
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if a port is in use and kill the process using it
|
|
70
|
+
*/
|
|
71
|
+
function killProcessOnPort(targetPort: number): void {
|
|
72
|
+
const platform = process.platform;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
let pids: number[] = [];
|
|
76
|
+
|
|
77
|
+
if (platform === "win32") {
|
|
78
|
+
// Windows: use netstat to find PID, then taskkill
|
|
79
|
+
const output = execSync(`netstat -ano | findstr :${targetPort}`, {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
timeout: 5000,
|
|
82
|
+
});
|
|
83
|
+
const lines = output.trim().split("\n");
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
const parts = line.trim().split(/\s+/);
|
|
86
|
+
if (parts.length >= 5) {
|
|
87
|
+
const localAddress = parts[1] ?? "";
|
|
88
|
+
const pidStr = parts[4] ?? "";
|
|
89
|
+
if (localAddress !== "" && pidStr !== "" && localAddress.includes(`:${targetPort}`)) {
|
|
90
|
+
const pid = parseInt(pidStr, 10);
|
|
91
|
+
if (!isNaN(pid) && pid > 0) {
|
|
92
|
+
pids.push(pid);
|
|
93
|
+
}
|
|
74
94
|
}
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
97
|
+
// Remove duplicates
|
|
98
|
+
pids = [...new Set(pids)];
|
|
99
|
+
|
|
100
|
+
for (const pid of pids) {
|
|
101
|
+
try {
|
|
102
|
+
console.log(`Killing process ${pid} on port ${port}...`);
|
|
103
|
+
execSync(`taskkill /PID ${pid} /F`, { encoding: "utf8", timeout: 5000 });
|
|
104
|
+
} catch {
|
|
105
|
+
// Process may have already exited
|
|
106
|
+
}
|
|
87
107
|
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
} else {
|
|
109
|
+
// Unix-like: use lsof
|
|
110
|
+
const output = execSync(`lsof -ti:${targetPort}`, { encoding: "utf8", timeout: 5000 });
|
|
111
|
+
const lines = output.trim().split("\n");
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
const pid = parseInt(line.trim(), 10);
|
|
114
|
+
if (!isNaN(pid) && pid > 0) {
|
|
115
|
+
pids.push(pid);
|
|
116
|
+
}
|
|
97
117
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
// Remove duplicates
|
|
119
|
+
pids = [...new Set(pids)];
|
|
120
|
+
|
|
121
|
+
for (const pid of pids) {
|
|
122
|
+
try {
|
|
123
|
+
console.log(`Killing process ${pid} on port ${port}...`);
|
|
124
|
+
execSync(`kill -9 ${pid}`, { encoding: "utf8", timeout: 5000 });
|
|
125
|
+
} catch {
|
|
126
|
+
// Process may have already exited
|
|
127
|
+
}
|
|
108
128
|
}
|
|
109
129
|
}
|
|
130
|
+
} catch {
|
|
131
|
+
// No process found on port, which is fine
|
|
110
132
|
}
|
|
111
|
-
} catch {
|
|
112
|
-
// No process found on port, which is fine
|
|
113
133
|
}
|
|
114
|
-
}
|
|
115
134
|
|
|
116
|
-
process.env["PORT"] = String(port);
|
|
117
|
-
|
|
118
|
-
// Kill any existing process on the port
|
|
119
|
-
killProcessOnPort(port);
|
|
120
|
-
|
|
121
|
-
const url = `http://localhost:${port}`;
|
|
122
|
-
|
|
123
|
-
console.log(`Server running at ${url}`);
|
|
124
|
-
console.log(` Proxy: ${url}/proxy`);
|
|
125
|
-
console.log(``);
|
|
126
|
-
console.log(`Route AI coding tools through the proxy:`);
|
|
127
|
-
console.log(` Claude Code: ANTHROPIC_BASE_URL=${url}/proxy claude`);
|
|
128
|
-
console.log(` OpenCode: LLM_BASE_URL=${url}/proxy opencode`);
|
|
129
|
-
console.log(` MiMo Code: OPENAI_BASE_URL=${url}/proxy mimo`);
|
|
130
|
-
console.log(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(
|
|
134
|
-
console.log(`
|
|
135
|
-
console.log(
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
135
|
+
process.env["PORT"] = String(port);
|
|
136
|
+
|
|
137
|
+
// Kill any existing process on the port
|
|
138
|
+
killProcessOnPort(port);
|
|
139
|
+
|
|
140
|
+
const url = `http://localhost:${port}`;
|
|
141
|
+
|
|
142
|
+
console.log(`Server running at ${url}`);
|
|
143
|
+
console.log(` Proxy: ${url}/proxy`);
|
|
144
|
+
console.log(``);
|
|
145
|
+
console.log(`Route AI coding tools through the proxy:`);
|
|
146
|
+
console.log(` Claude Code: ANTHROPIC_BASE_URL=${url}/proxy claude`);
|
|
147
|
+
console.log(` OpenCode: LLM_BASE_URL=${url}/proxy opencode`);
|
|
148
|
+
console.log(` MiMo Code: OPENAI_BASE_URL=${url}/proxy mimo`);
|
|
149
|
+
console.log(
|
|
150
|
+
` Direct HTTP: curl ${url}/proxy/v1/messages -d '{"model":"...","messages":[...]}'`,
|
|
151
|
+
);
|
|
152
|
+
console.log(``);
|
|
153
|
+
console.log(`Routing environment variables:`);
|
|
154
|
+
console.log(` ROUTES JSON map of model prefix -> upstream URL`);
|
|
155
|
+
console.log(` DEFAULT_UPSTREAM Fallback upstream for unmatched models`);
|
|
156
|
+
console.log(
|
|
157
|
+
` Example: ROUTES='{"claude-":"https://api.anthropic.com","MiniMax":"https://api.minimaxi.com/anthropic"}'`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const openBrowser = (targetUrl: string): void => {
|
|
161
|
+
let command: string[] | undefined;
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
163
|
+
switch (process.platform) {
|
|
164
|
+
case "darwin":
|
|
165
|
+
command = ["open", targetUrl];
|
|
166
|
+
break;
|
|
167
|
+
case "linux":
|
|
168
|
+
command = ["xdg-open", targetUrl];
|
|
169
|
+
break;
|
|
170
|
+
case "win32":
|
|
171
|
+
command = ["cmd", "/c", "start", targetUrl];
|
|
172
|
+
break;
|
|
173
|
+
default:
|
|
174
|
+
// Unsupported platform - do nothing
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
if (command === undefined) return;
|
|
178
|
+
const [bin, ...cmdArgs] = command;
|
|
179
|
+
if (bin === undefined) return;
|
|
180
|
+
spawn(bin, cmdArgs, { stdio: "ignore", detached: true });
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
if (open) {
|
|
184
|
+
openBrowser(url);
|
|
155
185
|
}
|
|
156
|
-
if (command === undefined) return;
|
|
157
|
-
const [bin, ...cmdArgs] = command;
|
|
158
|
-
if (bin === undefined) return;
|
|
159
|
-
spawn(bin, cmdArgs, { stdio: "ignore", detached: true });
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
if (open) {
|
|
163
|
-
openBrowser(url);
|
|
164
|
-
}
|
|
165
186
|
|
|
166
|
-
// Compute server path
|
|
167
|
-
const outputDir = __dirname;
|
|
168
|
-
const serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
169
|
-
|
|
170
|
-
// Start server with node
|
|
171
|
-
const serverEnv = { ...process.env };
|
|
172
|
-
if (configDir !== undefined) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
187
|
+
// Compute server path
|
|
188
|
+
const outputDir = __dirname;
|
|
189
|
+
const serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
190
|
+
|
|
191
|
+
// Start server with node
|
|
192
|
+
const serverEnv = { ...process.env };
|
|
193
|
+
if (configDir !== undefined) {
|
|
194
|
+
// Normalize MSYS / Git Bash paths to Windows native form.
|
|
195
|
+
// On Windows, `path.join('/c/Users/foo', 'config.json')` becomes
|
|
196
|
+
// `\c\Users\foo\config.json` (leading slash converted to backslash).
|
|
197
|
+
// Child processes spawned by `spawn()` won't follow that style, so
|
|
198
|
+
// rewrite `\c\...` (or any `\x\...` drive) to `C:\...` before
|
|
199
|
+
// handing the path to the proxy server. No-op on already-native
|
|
200
|
+
// Windows paths and on non-Windows platforms.
|
|
201
|
+
let resolvedPath = join(configDir, "config.json");
|
|
202
|
+
const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
|
|
203
|
+
if (msysMatch !== null) {
|
|
204
|
+
const drive = (msysMatch[1] ?? "").toUpperCase();
|
|
205
|
+
const rest = msysMatch[2] ?? "";
|
|
206
|
+
resolvedPath = `${drive}:\\${rest}`;
|
|
207
|
+
}
|
|
208
|
+
serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
|
|
178
209
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
env: serverEnv,
|
|
188
|
-
});
|
|
210
|
+
if (providersJson !== undefined) {
|
|
211
|
+
serverEnv["LLM_INSPECTOR_PROVIDERS_JSON"] = providersJson;
|
|
212
|
+
}
|
|
213
|
+
const serverProcess = spawn(process.execPath, [serverPath], {
|
|
214
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
215
|
+
detached: true,
|
|
216
|
+
env: serverEnv,
|
|
217
|
+
});
|
|
189
218
|
|
|
190
|
-
serverProcess.unref();
|
|
219
|
+
serverProcess.unref();
|
|
220
|
+
}
|