@tonyclaw/llm-inspector 1.7.3 → 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:45:59.559Z",
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-BWDeDWmr.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-BWDeDWmr.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-D0iGQ1Zd.mjs");
68
+ const $$splitComponentImporter = () => import("./index-BauwiGgk.mjs");
69
69
  const Route$d = createFileRoute("/")({
70
70
  component: lazyRouteComponent($$splitComponentImporter, "component")
71
71
  });
@@ -1396,68 +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
- const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
1421
- const currentOpenaiUrl = p.openaiBaseUrl ?? "";
1422
- if (p.format !== void 0 && (currentAnthropicUrl !== "" || currentOpenaiUrl !== "")) {
1423
- return p;
1424
- }
1425
- const newAnthropicUrl = oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : currentAnthropicUrl;
1426
- const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : currentOpenaiUrl;
1427
- let format;
1428
- let baseUrl;
1429
- if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
1430
- format = p.format ?? "anthropic";
1431
- baseUrl = p.baseUrl !== void 0 && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
1432
- } else if (newOpenaiUrl !== "") {
1433
- format = "openai";
1434
- baseUrl = newOpenaiUrl;
1435
- } else if (newAnthropicUrl !== "") {
1436
- format = "anthropic";
1437
- baseUrl = newAnthropicUrl;
1438
- } else if (p.format !== void 0 && p.baseUrl !== void 0 && p.baseUrl !== "") {
1439
- format = p.format;
1440
- baseUrl = p.baseUrl;
1441
- if (format === "openai") {
1442
- return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
1443
- } else {
1444
- return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
1445
- }
1446
- }
1447
- migrated = true;
1448
- return {
1449
- ...p,
1450
- format,
1451
- baseUrl,
1452
- anthropicBaseUrl: newAnthropicUrl,
1453
- openaiBaseUrl: newOpenaiUrl
1454
- };
1455
- });
1443
+ const updated = providers.map(migrateProvider);
1444
+ const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
1456
1445
  if (migrated) {
1457
1446
  store.set("providers", updated);
1458
1447
  }
1459
1448
  }
1460
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
+ }
1461
1461
  function getProviders() {
1462
1462
  return store.get("providers", []);
1463
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:45:53.063Z",
103
+ "mtime": "2026-06-03T10:13:16.267Z",
104
104
  "size": 5915,
105
105
  "path": "../public/assets/alibaba-TTwafVwX.svg"
106
106
  },
107
107
  "/assets/minimax-BPMzvuL-.jpeg": {
108
108
  "type": "image/jpeg",
109
109
  "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
110
- "mtime": "2026-06-03T09:45:53.065Z",
110
+ "mtime": "2026-06-03T10:13:16.267Z",
111
111
  "size": 6918,
112
112
  "path": "../public/assets/minimax-BPMzvuL-.jpeg"
113
113
  },
114
- "/assets/index-B3RwBPLW.css": {
115
- "type": "text/css; charset=utf-8",
116
- "etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
117
- "mtime": "2026-06-03T09:45:53.063Z",
118
- "size": 68724,
119
- "path": "../public/assets/index-B3RwBPLW.css"
120
- },
121
114
  "/assets/zhipuai-BPNAnxo-.svg": {
122
115
  "type": "image/svg+xml",
123
116
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
124
- "mtime": "2026-06-03T09:45:53.063Z",
117
+ "mtime": "2026-06-03T10:13:16.267Z",
125
118
  "size": 11256,
126
119
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
127
120
  },
121
+ "/assets/main-CpIX1ZHy.js": {
122
+ "type": "text/javascript; charset=utf-8",
123
+ "etag": '"4db57-PIyiLXQGvlFJuizUFXRhGOYXJwY"',
124
+ "mtime": "2026-06-03T10:13:16.269Z",
125
+ "size": 318295,
126
+ "path": "../public/assets/main-CpIX1ZHy.js"
127
+ },
128
128
  "/assets/qwen-CONDcHqt.png": {
129
129
  "type": "image/png",
130
130
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
131
- "mtime": "2026-06-03T09:45:53.065Z",
131
+ "mtime": "2026-06-03T10:13:16.269Z",
132
132
  "size": 357059,
133
133
  "path": "../public/assets/qwen-CONDcHqt.png"
134
134
  },
135
- "/assets/main-CpIX1ZHy.js": {
136
- "type": "text/javascript; charset=utf-8",
137
- "etag": '"4db57-PIyiLXQGvlFJuizUFXRhGOYXJwY"',
138
- "mtime": "2026-06-03T09:45:53.065Z",
139
- "size": 318295,
140
- "path": "../public/assets/main-CpIX1ZHy.js"
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
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:45:53.065Z",
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.3",
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();
@@ -31,82 +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
- const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
64
- const currentOpenaiUrl = p.openaiBaseUrl ?? "";
65
-
66
- // If already migrated (has format field and at least one URL), skip
67
- if (p.format !== undefined && (currentAnthropicUrl !== "" || currentOpenaiUrl !== "")) {
68
- return p;
69
- }
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
+ }
70
55
 
71
- // Preserve both URLs
72
- const newAnthropicUrl = oldAnthropicBaseUrl !== "" ? oldAnthropicBaseUrl : currentAnthropicUrl;
73
- const newOpenaiUrl = oldOpenaiBaseUrl !== "" ? oldOpenaiBaseUrl : currentOpenaiUrl;
74
-
75
- // Determine primary format based on which URL is set
76
- let format: "anthropic" | "openai" | undefined;
77
- let baseUrl: string | undefined;
78
-
79
- if (newAnthropicUrl !== "" && newOpenaiUrl !== "") {
80
- // Both URLs set - prefer anthropic as default, use baseUrl for backward compat
81
- format = p.format ?? "anthropic";
82
- baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : newAnthropicUrl;
83
- } else if (newOpenaiUrl !== "") {
84
- format = "openai";
85
- baseUrl = newOpenaiUrl;
86
- } else if (newAnthropicUrl !== "") {
87
- format = "anthropic";
88
- baseUrl = newAnthropicUrl;
89
- } else if (p.format !== undefined && p.baseUrl !== undefined && p.baseUrl !== "") {
90
- // Only baseUrl is set (legacy config) - migrate based on format
91
- format = p.format;
92
- baseUrl = p.baseUrl;
93
- if (format === "openai") {
94
- return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
95
- } else {
96
- return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
97
- }
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: "" };
98
78
  }
79
+ }
99
80
 
100
- migrated = true;
81
+ return {
82
+ ...p,
83
+ format,
84
+ baseUrl,
85
+ anthropicBaseUrl: currentAnthropicUrl,
86
+ openaiBaseUrl: currentOpenaiUrl,
87
+ };
88
+ }
101
89
 
102
- return {
103
- ...p,
104
- format,
105
- baseUrl,
106
- anthropicBaseUrl: newAnthropicUrl,
107
- openaiBaseUrl: newOpenaiUrl,
108
- };
109
- });
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]));
110
98
 
111
99
  if (migrated) {
112
100
  store.set("providers", updated);
@@ -116,6 +104,21 @@ function migrateProviders(): void {
116
104
  // Run migration on module load
117
105
  migrateProviders();
118
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
+
119
122
  export function getProviders(): ProviderConfig[] {
120
123
  return store.get("providers", []);
121
124
  }