@tonyclaw/llm-inspector 1.7.2 → 1.7.4

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 CHANGED
@@ -12,6 +12,8 @@ var portDefault = envPort !== void 0 ? Number(envPort) : DEFAULT_PORT;
12
12
  var args = process.argv.slice(2);
13
13
  var port = portDefault;
14
14
  var open = true;
15
+ var configDir;
16
+ var providersJson;
15
17
  for (let i = 0; i < args.length; i++) {
16
18
  const arg = args[i] ?? "";
17
19
  switch (arg) {
@@ -26,6 +28,14 @@ for (let i = 0; i < args.length; i++) {
26
28
  case "--open":
27
29
  open = true;
28
30
  break;
31
+ case "--config-dir":
32
+ configDir = args[i + 1];
33
+ i++;
34
+ break;
35
+ case "--providers":
36
+ providersJson = args[i + 1];
37
+ i++;
38
+ break;
29
39
  default:
30
40
  break;
31
41
  }
@@ -124,9 +134,20 @@ if (open) {
124
134
  }
125
135
  var outputDir = __dirname;
126
136
  var serverPath = join(outputDir, "../.output/server/index.mjs");
137
+ var serverEnv = { ...process.env };
138
+ if (configDir !== void 0) {
139
+ let resolvedPath = join(configDir, "config.json");
140
+ if (resolvedPath.startsWith("\\c\\")) {
141
+ resolvedPath = "C:" + resolvedPath;
142
+ }
143
+ serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
144
+ }
145
+ if (providersJson !== void 0) {
146
+ serverEnv["LLM_INSPECTOR_PROVIDERS_JSON"] = providersJson;
147
+ }
127
148
  var serverProcess = spawn(process.execPath, [serverPath], {
128
149
  stdio: ["ignore", "inherit", "inherit"],
129
150
  detached: true,
130
- env: { ...process.env }
151
+ env: serverEnv
131
152
  });
132
153
  serverProcess.unref();
@@ -1,5 +1,5 @@
1
1
  {
2
- "date": "2026-06-03T09:30:25.048Z",
2
+ "date": "2026-06-03T10:13:21.551Z",
3
3
  "preset": "node-server",
4
4
  "framework": {
5
5
  "name": "nitro",
@@ -1,5 +1,5 @@
1
1
  import { r as reactExports, j as jsxRuntimeExports, a as React } from "../_libs/react.mjs";
2
- import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-D7g2K6y6.mjs";
2
+ import { C as CapturedLogSchema, a as parseRequest, p as parseOpenAIResponse, I as InspectorResponseSchema } from "./router-DPnnefGg.mjs";
3
3
  import { u as useVirtualizer } from "../_libs/tanstack__react-virtual.mjs";
4
4
  import { J as JSZip } from "../_libs/jszip.mjs";
5
5
  import { c as clsx } from "../_libs/clsx.mjs";
@@ -766,7 +766,7 @@ let entriesPromise;
766
766
  let baseManifestPromise;
767
767
  let cachedFinalManifestPromise;
768
768
  async function loadEntries() {
769
- const routerEntry = await import("./router-D7g2K6y6.mjs").then((n) => n.r);
769
+ const routerEntry = await import("./router-DPnnefGg.mjs").then((n) => n.r);
770
770
  const startEntry = await import("./start-HYkvq4Ni.mjs");
771
771
  return { startEntry, routerEntry };
772
772
  }
@@ -65,7 +65,7 @@ function RootDocument({ children }) {
65
65
  ] })
66
66
  ] });
67
67
  }
68
- const $$splitComponentImporter = () => import("./index-BZkxgx8f.mjs");
68
+ const $$splitComponentImporter = () => import("./index-BauwiGgk.mjs");
69
69
  const Route$d = createFileRoute("/")({
70
70
  component: lazyRouteComponent($$splitComponentImporter, "component")
71
71
  });
@@ -1396,58 +1396,68 @@ const ProviderConfigSchema = object({
1396
1396
  object({
1397
1397
  providers: array(ProviderConfigSchema)
1398
1398
  });
1399
+ const configPath = process.env["LLM_INSPECTOR_CONFIG_PATH"];
1399
1400
  const store = new Conf({
1400
1401
  projectName: "llm-inspector",
1401
1402
  defaults: {
1402
1403
  providers: []
1404
+ },
1405
+ ...configPath !== void 0 ? { path: configPath } : {}
1406
+ });
1407
+ function migrateProvider(p) {
1408
+ const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
1409
+ const currentOpenaiUrl = p.openaiBaseUrl ?? "";
1410
+ if (currentAnthropicUrl !== "" || currentOpenaiUrl !== "") {
1411
+ return p;
1412
+ }
1413
+ let format;
1414
+ let baseUrl;
1415
+ if (currentAnthropicUrl !== "" && currentOpenaiUrl !== "") {
1416
+ format = p.format ?? "anthropic";
1417
+ baseUrl = p.baseUrl !== void 0 && p.baseUrl !== "" ? p.baseUrl : currentAnthropicUrl;
1418
+ } else if (currentOpenaiUrl !== "") {
1419
+ format = "openai";
1420
+ baseUrl = currentOpenaiUrl;
1421
+ } else if (currentAnthropicUrl !== "") {
1422
+ format = "anthropic";
1423
+ baseUrl = currentAnthropicUrl;
1424
+ } else if (p.format !== void 0 && p.baseUrl !== void 0 && p.baseUrl !== "") {
1425
+ format = p.format;
1426
+ baseUrl = p.baseUrl;
1427
+ if (format === "openai") {
1428
+ return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
1429
+ } else {
1430
+ return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
1431
+ }
1403
1432
  }
1404
- });
1433
+ return {
1434
+ ...p,
1435
+ format,
1436
+ baseUrl,
1437
+ anthropicBaseUrl: currentAnthropicUrl,
1438
+ openaiBaseUrl: currentOpenaiUrl
1439
+ };
1440
+ }
1405
1441
  function migrateProviders() {
1406
1442
  const providers = store.get("providers", []);
1407
- let migrated = false;
1408
- const updated = providers.map((p) => {
1409
- function getOldUrl(obj, key) {
1410
- if (obj !== null && typeof obj === "object") {
1411
- const desc = Object.getOwnPropertyDescriptor(obj, key);
1412
- if (desc !== void 0 && typeof desc.value === "string") {
1413
- return desc.value;
1414
- }
1415
- }
1416
- return "";
1417
- }
1418
- const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
1419
- const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
1420
- if (p.format !== void 0 && oldAnthropicBaseUrl === "" && oldOpenaiBaseUrl === "") {
1421
- return p;
1422
- }
1423
- const newAnthropicUrl = oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : p.anthropicBaseUrl ?? "";
1424
- const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : p.openaiBaseUrl ?? "";
1425
- let format;
1426
- let baseUrl;
1427
- if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
1428
- format = p.format ?? "anthropic";
1429
- baseUrl = p.baseUrl !== void 0 && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
1430
- } else if (newOpenaiUrl !== "") {
1431
- format = "openai";
1432
- baseUrl = newOpenaiUrl;
1433
- } else if (newAnthropicUrl !== "") {
1434
- format = "anthropic";
1435
- baseUrl = newAnthropicUrl;
1436
- }
1437
- migrated = true;
1438
- return {
1439
- ...p,
1440
- format,
1441
- baseUrl,
1442
- anthropicBaseUrl: newAnthropicUrl,
1443
- openaiBaseUrl: newOpenaiUrl
1444
- };
1445
- });
1443
+ const updated = providers.map(migrateProvider);
1444
+ const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
1446
1445
  if (migrated) {
1447
1446
  store.set("providers", updated);
1448
1447
  }
1449
1448
  }
1450
1449
  migrateProviders();
1450
+ const providersJson = process.env["LLM_INSPECTOR_PROVIDERS_JSON"];
1451
+ if (providersJson !== void 0) {
1452
+ try {
1453
+ const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
1454
+ if (parsed.success) {
1455
+ const migrated = parsed.data.map(migrateProvider);
1456
+ store.set("providers", migrated);
1457
+ }
1458
+ } catch {
1459
+ }
1460
+ }
1451
1461
  function getProviders() {
1452
1462
  return store.get("providers", []);
1453
1463
  }
@@ -100,49 +100,49 @@ const assets = {
100
100
  "/assets/alibaba-TTwafVwX.svg": {
101
101
  "type": "image/svg+xml",
102
102
  "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
103
- "mtime": "2026-06-03T09:30:18.865Z",
103
+ "mtime": "2026-06-03T10:13:16.267Z",
104
104
  "size": 5915,
105
105
  "path": "../public/assets/alibaba-TTwafVwX.svg"
106
106
  },
107
- "/assets/index-B3RwBPLW.css": {
108
- "type": "text/css; charset=utf-8",
109
- "etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
110
- "mtime": "2026-06-03T09:30:18.865Z",
111
- "size": 68724,
112
- "path": "../public/assets/index-B3RwBPLW.css"
113
- },
114
107
  "/assets/minimax-BPMzvuL-.jpeg": {
115
108
  "type": "image/jpeg",
116
109
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
117
- "mtime": "2026-06-03T09:30:18.865Z",
110
+ "mtime": "2026-06-03T10:13:16.267Z",
118
111
  "size": 6918,
119
112
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
120
113
  },
121
114
  "/assets/zhipuai-BPNAnxo-.svg": {
122
115
  "type": "image/svg+xml",
123
116
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
124
- "mtime": "2026-06-03T09:30:18.862Z",
117
+ "mtime": "2026-06-03T10:13:16.267Z",
125
118
  "size": 11256,
126
119
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
127
120
  },
128
121
  "/assets/main-CpIX1ZHy.js": {
129
122
  "type": "text/javascript; charset=utf-8",
130
123
  "etag": '"4db57-PIyiLXQGvlFJuizUFXRhGOYXJwY"',
131
- "mtime": "2026-06-03T09:30:18.865Z",
124
+ "mtime": "2026-06-03T10:13:16.269Z",
132
125
  "size": 318295,
133
126
  "path": "../public/assets/main-CpIX1ZHy.js"
134
127
  },
135
128
  "/assets/qwen-CONDcHqt.png": {
136
129
  "type": "image/png",
137
130
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
138
- "mtime": "2026-06-03T09:30:18.865Z",
131
+ "mtime": "2026-06-03T10:13:16.269Z",
139
132
  "size": 357059,
140
133
  "path": "../public/assets/qwen-CONDcHqt.png"
141
134
  },
135
+ "/assets/index-B3RwBPLW.css": {
136
+ "type": "text/css; charset=utf-8",
137
+ "etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
138
+ "mtime": "2026-06-03T10:13:16.269Z",
139
+ "size": 68724,
140
+ "path": "../public/assets/index-B3RwBPLW.css"
141
+ },
142
142
  "/assets/index-Bf_WGooQ.js": {
143
143
  "type": "text/javascript; charset=utf-8",
144
144
  "etag": '"831df-gmdpd1CCnM4IdaxHIs9uyMgWFaY"',
145
- "mtime": "2026-06-03T09:30:18.865Z",
145
+ "mtime": "2026-06-03T10:13:16.269Z",
146
146
  "size": 537055,
147
147
  "path": "../public/assets/index-Bf_WGooQ.js"
148
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "type": "module",
5
5
  "description": "LLM API proxy inspector — captures and displays requests/responses from AI coding tools in a web UI",
6
6
  "license": "MIT",
package/src/cli.ts CHANGED
@@ -16,6 +16,8 @@ const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
16
16
  const args = process.argv.slice(2);
17
17
  let port = portDefault;
18
18
  let open = true;
19
+ let configDir: string | undefined;
20
+ let providersJson: string | undefined;
19
21
 
20
22
  for (let i = 0; i < args.length; i++) {
21
23
  const arg = args[i] ?? "";
@@ -31,6 +33,14 @@ for (let i = 0; i < args.length; i++) {
31
33
  case "--open":
32
34
  open = true;
33
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;
34
44
  default:
35
45
  break;
36
46
  }
@@ -157,10 +167,23 @@ const outputDir = __dirname;
157
167
  const serverPath = join(outputDir, "../.output/server/index.mjs");
158
168
 
159
169
  // Start server with node
170
+ const serverEnv = { ...process.env };
171
+ if (configDir !== undefined) {
172
+ // Convert MSYS/Git Bash path like /c/Users/... to Windows absolute path
173
+ let resolvedPath = join(configDir, "config.json");
174
+ // Convert /c/... to C:\... format
175
+ if (resolvedPath.startsWith("\\c\\")) {
176
+ resolvedPath = "C:" + resolvedPath;
177
+ }
178
+ serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
179
+ }
180
+ if (providersJson !== undefined) {
181
+ serverEnv["LLM_INSPECTOR_PROVIDERS_JSON"] = providersJson;
182
+ }
160
183
  const serverProcess = spawn(process.execPath, [serverPath], {
161
184
  stdio: ["ignore", "inherit", "inherit"],
162
185
  detached: true,
163
- env: { ...process.env },
186
+ env: serverEnv,
164
187
  });
165
188
 
166
189
  serverProcess.unref();
@@ -7,7 +7,6 @@ import {
7
7
  Trash2,
8
8
  RotateCw,
9
9
  CheckCircle,
10
- XCircle,
11
10
  Minus,
12
11
  ExternalLink,
13
12
  AlertCircle,
@@ -31,72 +31,70 @@ type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
31
31
 
32
32
  // Using conf for storage - works in any Node.js environment without Electron
33
33
  // Note: conf stores data in plain JSON. For production, consider additional encryption.
34
+ const configPath = process.env["LLM_INSPECTOR_CONFIG_PATH"];
34
35
  export const store = new Conf<ProvidersStore>({
35
36
  projectName: "llm-inspector",
36
37
  defaults: {
37
38
  providers: [],
38
39
  },
40
+ ...(configPath !== undefined ? { path: configPath } : {}),
39
41
  });
40
42
 
41
43
  /**
42
- * Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
43
- * Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
44
+ * Migrates a single provider config to preserve both Anthropic and OpenAI URLs.
44
45
  */
45
- function migrateProviders(): void {
46
- const providers = store.get("providers", []);
47
- let migrated = false;
48
-
49
- const updated = providers.map((p) => {
50
- // Helper to safely get old URL properties using Object.getOwnPropertyDescriptor
51
- function getOldUrl(obj: unknown, key: string): string {
52
- if (obj !== null && typeof obj === "object") {
53
- const desc = Object.getOwnPropertyDescriptor(obj, key);
54
- if (desc !== undefined && typeof desc.value === "string") {
55
- return desc.value;
56
- }
57
- }
58
- return "";
59
- }
60
-
61
- const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
62
- const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
63
-
64
- // If already migrated (has format field and both URLs), skip
65
- if (p.format !== undefined && oldAnthropicBaseUrl === "" && oldOpenaiBaseUrl === "") {
66
- return p;
67
- }
46
+ export function migrateProvider(p: ProviderConfig): ProviderConfig {
47
+ const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
48
+ const currentOpenaiUrl = p.openaiBaseUrl ?? "";
49
+
50
+ // If already migrated (has at least one URL set), skip
51
+ // format may be undefined but if URL is set, it's already migrated
52
+ if (currentAnthropicUrl !== "" || currentOpenaiUrl !== "") {
53
+ return p;
54
+ }
68
55
 
69
- // Preserve both URLs
70
- const newAnthropicUrl =
71
- oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : (p.anthropicBaseUrl ?? "");
72
- const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : (p.openaiBaseUrl ?? "");
73
-
74
- // Determine primary format based on which URL is set
75
- let format: "anthropic" | "openai" | undefined;
76
- let baseUrl: string | undefined;
77
-
78
- if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
79
- // Both URLs set - prefer anthropic as default, use baseUrl for backward compat
80
- format = p.format ?? "anthropic";
81
- baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
82
- } else if (newOpenaiUrl !== "") {
83
- format = "openai";
84
- baseUrl = newOpenaiUrl;
85
- } else if (newAnthropicUrl !== "") {
86
- format = "anthropic";
87
- baseUrl = newAnthropicUrl;
56
+ // Determine primary format based on which URL is set
57
+ let format: "anthropic" | "openai" | undefined;
58
+ let baseUrl: string | undefined;
59
+
60
+ if (currentAnthropicUrl !== "" && currentOpenaiUrl !== "") {
61
+ // Both URLs set - prefer anthropic as default, use baseUrl for backward compat
62
+ format = p.format ?? "anthropic";
63
+ baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : currentAnthropicUrl;
64
+ } else if (currentOpenaiUrl !== "") {
65
+ format = "openai";
66
+ baseUrl = currentOpenaiUrl;
67
+ } else if (currentAnthropicUrl !== "") {
68
+ format = "anthropic";
69
+ baseUrl = currentAnthropicUrl;
70
+ } else if (p.format !== undefined && p.baseUrl !== undefined && p.baseUrl !== "") {
71
+ // Only baseUrl is set (legacy config) - migrate based on format
72
+ format = p.format;
73
+ baseUrl = p.baseUrl;
74
+ if (format === "openai") {
75
+ return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
76
+ } else {
77
+ return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
88
78
  }
79
+ }
89
80
 
90
- migrated = true;
81
+ return {
82
+ ...p,
83
+ format,
84
+ baseUrl,
85
+ anthropicBaseUrl: currentAnthropicUrl,
86
+ openaiBaseUrl: currentOpenaiUrl,
87
+ };
88
+ }
91
89
 
92
- return {
93
- ...p,
94
- format,
95
- baseUrl,
96
- anthropicBaseUrl: newAnthropicUrl,
97
- openaiBaseUrl: newOpenaiUrl,
98
- };
99
- });
90
+ /**
91
+ * Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
92
+ * Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
93
+ */
94
+ function migrateProviders(): void {
95
+ const providers = store.get("providers", []);
96
+ const updated = providers.map(migrateProvider);
97
+ const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
100
98
 
101
99
  if (migrated) {
102
100
  store.set("providers", updated);
@@ -106,6 +104,21 @@ function migrateProviders(): void {
106
104
  // Run migration on module load
107
105
  migrateProviders();
108
106
 
107
+ // Override with JSON env var if provided (for testing)
108
+ const providersJson = process.env["LLM_INSPECTOR_PROVIDERS_JSON"];
109
+ if (providersJson !== undefined) {
110
+ try {
111
+ const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
112
+ if (parsed.success) {
113
+ // Apply migration to JSON providers (e.g., convert baseUrl to openaiBaseUrl/anthropicBaseUrl)
114
+ const migrated = parsed.data.map(migrateProvider);
115
+ store.set("providers", migrated);
116
+ }
117
+ } catch {
118
+ // Ignore invalid JSON
119
+ }
120
+ }
121
+
109
122
  export function getProviders(): ProviderConfig[] {
110
123
  return store.get("providers", []);
111
124
  }