@tonyclaw/llm-inspector 1.19.1 → 1.19.3

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 +146 -37
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DtERUdIt.js → CompareDrawer-AMCFfwDw.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-DfxRK7Nt.js → ProxyViewerContainer-Bi1QXDYo.js} +9 -9
  5. package/.output/public/assets/{ReplayDialog-VMsGnJSI.js → ReplayDialog-C1x2-73A.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-Cx_vluvK.js → RequestAnatomy-CBNqMbxo.js} +1 -1
  7. package/.output/public/assets/{ResponseView-5F8Ms5z4.js → ResponseView-BsBxYW8b.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-CKDCWfu9.js → StreamingChunkSequence-BjX20-9R.js} +1 -1
  9. package/.output/public/assets/_sessionId-DE0J5YHU.js +1 -0
  10. package/.output/public/assets/{index-DeJyypsp.css → index-Bqi9RAGS.css} +1 -1
  11. package/.output/public/assets/index-DqgTchf4.js +1 -0
  12. package/.output/public/assets/{json-viewer-CztuZ9cT.js → json-viewer-CyJ5nYhX.js} +1 -1
  13. package/.output/public/assets/{main-CR9IJlz1.js → main-DpODUL6C.js} +4 -4
  14. package/.output/server/{_sessionId-DvWQaDEm.mjs → _sessionId-DJ9Eab2q.mjs} +4 -3
  15. package/.output/server/_ssr/{CompareDrawer-C5FsxSDS.mjs → CompareDrawer-CZjtgISX.mjs} +4 -4
  16. package/.output/server/_ssr/{ProxyViewerContainer-v0cvR8f5.mjs → ProxyViewerContainer-PjfpTAwC.mjs} +57 -9
  17. package/.output/server/_ssr/{ReplayDialog-C3KOv9OW.mjs → ReplayDialog-OiEK8Sbt.mjs} +5 -5
  18. package/.output/server/_ssr/{RequestAnatomy-BYRe33eG.mjs → RequestAnatomy-DN7d-srT.mjs} +4 -3
  19. package/.output/server/_ssr/{ResponseView-va7yQDeL.mjs → ResponseView-7iJG8mU_.mjs} +5 -5
  20. package/.output/server/_ssr/{StreamingChunkSequence-BJlI-gWl.mjs → StreamingChunkSequence-DgtV9nLT.mjs} +4 -4
  21. package/.output/server/_ssr/{index-CS0fA2GT.mjs → index-YZwFhEEr.mjs} +4 -3
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-Dg8rqrxL.mjs → json-viewer-0GHTWO7g.mjs} +4 -4
  24. package/.output/server/_ssr/{router-D_Boe9Bu.mjs → router-DCW8d1sw.mjs} +131 -106
  25. package/.output/server/{_tanstack-start-manifest_v-KFXyNRGC.mjs → _tanstack-start-manifest_v-CxTbhyJ_.mjs} +1 -1
  26. package/.output/server/index.mjs +75 -75
  27. package/README.md +22 -0
  28. package/package.json +4 -2
  29. package/src/assets/favicon.svg +18 -0
  30. package/src/cli/detect-tools.ts +1 -0
  31. package/src/cli/templates/skill-onboard.ts +6 -6
  32. package/src/cli.ts +152 -35
  33. package/src/components/ProxyViewerContainer.tsx +52 -0
  34. package/src/components/proxy-viewer/LogEntryHeader.tsx +1 -0
  35. package/src/proxy/logFinalizer.ts +7 -3
  36. package/src/proxy/sessionProcess.ts +14 -7
  37. package/src/proxy/sessionSupervisor.ts +3 -2
  38. package/src/proxy/socketTracker.ts +19 -7
  39. package/src/routes/__root.tsx +5 -1
  40. package/styles/globals.css +14 -7
  41. package/.output/public/assets/_sessionId-C-aKd1Ky.js +0 -1
  42. package/.output/public/assets/index-B8ttyigz.js +0 -1
@@ -5,9 +5,10 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFil
5
5
  import { mkdir, appendFile, readFile, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
6
6
  import { Buffer } from "node:buffer";
7
7
  import path, { join, isAbsolute, dirname } from "node:path";
8
+ import { execFile, exec, spawn } from "node:child_process";
9
+ import { fileURLToPath } from "node:url";
8
10
  import { C as Conf } from "../_libs/conf.mjs";
9
11
  import { randomUUID } from "crypto";
10
- import { exec, fork } from "node:child_process";
11
12
  import { promisify } from "node:util";
12
13
  import { Worker } from "node:worker_threads";
13
14
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport } from "../_libs/modelcontextprotocol__server.mjs";
@@ -47,7 +48,8 @@ import "../_libs/debounce-fn.mjs";
47
48
  import "../_libs/mimic-function.mjs";
48
49
  import "../_libs/semver.mjs";
49
50
  import "../_libs/uint8array-extras.mjs";
50
- const appCss = "/assets/index-DeJyypsp.css";
51
+ const faviconSvg = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2064%2064'%20role='img'%20aria-label='LLM%20Inspector'%3e%3crect%20width='64'%20height='64'%20rx='14'%20fill='%23111827'%20/%3e%3cg%20fill='none'%20stroke='%23f59e0b'%20stroke-width='4.2'%20stroke-linecap='round'%20stroke-linejoin='round'%3e%3cpath%20fill='%23f59e0b'%20d='M15%2036c0-11%207-18%2017-18s17%207%2017%2018c0%208-7%2013-17%2013s-17-5-17-13z'%20/%3e%3cpath%20d='M16%2031c-6-5-12-3-12%204%200%205%206%206%2011%202'%20/%3e%3cpath%20d='M48%2031c6-5%2012-3%2012%204%200%205-6%206-11%202'%20/%3e%3cpath%20d='M27%2019l-3-7'%20/%3e%3cpath%20d='M37%2019l3-7'%20/%3e%3cpath%20d='M19%2045l-6%209'%20/%3e%3cpath%20d='M27%2048l-3%209'%20/%3e%3cpath%20d='M37%2048l3%209'%20/%3e%3cpath%20d='M45%2045l6%209'%20/%3e%3c/g%3e%3ccircle%20cx='24'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='40'%20cy='11'%20r='3.2'%20fill='%23f59e0b'%20/%3e%3ccircle%20cx='25'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3ccircle%20cx='39'%20cy='34'%20r='2.1'%20fill='%23111827'%20/%3e%3c/svg%3e";
52
+ const appCss = "/assets/index-Bqi9RAGS.css";
51
53
  const Route$l = createRootRoute({
52
54
  head: () => ({
53
55
  meta: [
@@ -55,7 +57,10 @@ const Route$l = createRootRoute({
55
57
  { name: "viewport", content: "width=device-width, initial-scale=1" },
56
58
  { title: "llm-inspector" }
57
59
  ],
58
- links: [{ rel: "stylesheet", href: appCss }]
60
+ links: [
61
+ { rel: "stylesheet", href: appCss },
62
+ { rel: "icon", type: "image/svg+xml", href: faviconSvg }
63
+ ]
59
64
  }),
60
65
  component: RootComponent
61
66
  });
@@ -71,7 +76,7 @@ function RootDocument({ children }) {
71
76
  ] })
72
77
  ] });
73
78
  }
74
- const $$splitComponentImporter$1 = () => import("./index-CS0fA2GT.mjs");
79
+ const $$splitComponentImporter$1 = () => import("./index-YZwFhEEr.mjs");
75
80
  const Route$k = createFileRoute("/")({
76
81
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
77
82
  });
@@ -114,7 +119,7 @@ function decodeSessionIdFromPath(encoded) {
114
119
  function getSessionPath(sessionId) {
115
120
  return `/session/${encodeSessionIdForPath(sessionId)}`;
116
121
  }
117
- const $$splitComponentImporter = () => import("../_sessionId-DvWQaDEm.mjs");
122
+ const $$splitComponentImporter = () => import("../_sessionId-DJ9Eab2q.mjs");
118
123
  const Route$j = createFileRoute("/session/$sessionId")({
119
124
  component: lazyRouteComponent($$splitComponentImporter, "component"),
120
125
  parseParams: (params) => ({
@@ -887,13 +892,113 @@ function readChunks(path2) {
887
892
  return null;
888
893
  }
889
894
  }
895
+ const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
896
+ const MAX_RESTARTS = 3;
897
+ const _processes = /* @__PURE__ */ new Map();
898
+ function resolveSessionWorkerPath() {
899
+ return fileURLToPath(new URL("./sessionWorkerEntry.ts", import.meta.url));
900
+ }
901
+ function isSessionProcessAvailable() {
902
+ return existsSync(resolveSessionWorkerPath());
903
+ }
904
+ class SessionProcess {
905
+ constructor(sessionId) {
906
+ this.sessionId = sessionId;
907
+ }
908
+ child = null;
909
+ pending = /* @__PURE__ */ new Map();
910
+ nextId = 0;
911
+ idleTimer = null;
912
+ restartCount = 0;
913
+ destroyed = false;
914
+ /** Number of outstanding jobs sent to this process. */
915
+ get pendingCount() {
916
+ return this.pending.size;
917
+ }
918
+ ensureRunning() {
919
+ if (this.child !== null && this.child.connected) return this.child;
920
+ const resolvedPath = resolveSessionWorkerPath();
921
+ this.child = spawn(process.execPath, [...process.execArgv, resolvedPath], {
922
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
923
+ windowsHide: true
924
+ });
925
+ this.restartCount += 1;
926
+ this.child.on("message", (msg) => {
927
+ const pending = this.pending.get(msg.id);
928
+ if (pending !== void 0) {
929
+ this.pending.delete(msg.id);
930
+ pending.resolve(msg.result);
931
+ }
932
+ this.resetIdleTimer();
933
+ });
934
+ this.child.on("error", (err) => {
935
+ logger.error(`[sessionProcess] Session ${this.sessionId} process error:`, err.message);
936
+ });
937
+ this.child.on("exit", (code, signal) => {
938
+ const wasConnected = this.child !== null;
939
+ this.child = null;
940
+ if (this.destroyed) return;
941
+ for (const [, pending] of this.pending) {
942
+ pending.reject(
943
+ new Error(
944
+ `Session process exited with code ${code ?? signal}, session=${this.sessionId}`
945
+ )
946
+ );
947
+ }
948
+ this.pending.clear();
949
+ if (wasConnected && this.restartCount <= MAX_RESTARTS) {
950
+ logger.warn(
951
+ `[sessionProcess] Session ${this.sessionId} worker exited (code=${code ?? signal}), restart ${this.restartCount}/${MAX_RESTARTS}`
952
+ );
953
+ }
954
+ });
955
+ this.resetIdleTimer();
956
+ return this.child;
957
+ }
958
+ enqueue(job) {
959
+ return new Promise((resolve, reject) => {
960
+ const child = this.ensureRunning();
961
+ const id = String(++this.nextId);
962
+ this.pending.set(id, { resolve, reject });
963
+ child.send({ id, job });
964
+ });
965
+ }
966
+ resetIdleTimer() {
967
+ if (this.idleTimer !== null) clearTimeout(this.idleTimer);
968
+ this.idleTimer = setTimeout(() => {
969
+ if (this.pending.size === 0) {
970
+ this.destroy();
971
+ }
972
+ }, IDLE_TIMEOUT_MS);
973
+ }
974
+ destroy() {
975
+ this.destroyed = true;
976
+ if (this.idleTimer !== null) {
977
+ clearTimeout(this.idleTimer);
978
+ this.idleTimer = null;
979
+ }
980
+ if (this.child !== null) {
981
+ this.child.kill();
982
+ this.child = null;
983
+ }
984
+ _processes.delete(this.sessionId);
985
+ }
986
+ }
987
+ function getSessionProcess(sessionId) {
988
+ const existing = _processes.get(sessionId);
989
+ if (existing !== void 0) return existing;
990
+ const sp = new SessionProcess(sessionId);
991
+ _processes.set(sessionId, sp);
992
+ return sp;
993
+ }
890
994
  const PROVIDER_TEST_SESSION_ID = "provider-test";
891
995
  function getRuntimeMode() {
892
996
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "in-process";
893
997
  const mode = process.env["FINALIZER_RUNTIME"];
998
+ if (mode === "process") return "child-process";
894
999
  if (mode === "worker") return "worker-thread";
895
1000
  if (mode === "inline") return "in-process";
896
- return "child-process";
1001
+ return isSessionProcessAvailable() ? "child-process" : "in-process";
897
1002
  }
898
1003
  const sessions = /* @__PURE__ */ new Map();
899
1004
  const knownLogSessions = /* @__PURE__ */ new Map();
@@ -2263,6 +2368,7 @@ function findProviderByModel(model) {
2263
2368
  return null;
2264
2369
  }
2265
2370
  const execAsync = promisify(exec);
2371
+ const execFileAsync = promisify(execFile);
2266
2372
  const cache = /* @__PURE__ */ new Map();
2267
2373
  const CACHE_TTL_MS = 5 * 60 * 1e3;
2268
2374
  const MAX_CACHE_SIZE = 200;
@@ -2305,9 +2411,15 @@ async function lookupClientInfo(port) {
2305
2411
  ` Write-Output "$pid|$cmd"`,
2306
2412
  `}`
2307
2413
  ].join("; ");
2308
- const { stdout } = await execAsync(`powershell -NoProfile -Command "${psScript}"`, {
2309
- windowsHide: true
2310
- });
2414
+ const { stdout } = await execFileAsync(
2415
+ "powershell.exe",
2416
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", psScript],
2417
+ {
2418
+ windowsHide: true,
2419
+ timeout: 3e3,
2420
+ maxBuffer: 64 * 1024
2421
+ }
2422
+ );
2311
2423
  const trimmed = stdout.trim();
2312
2424
  if (trimmed === "") {
2313
2425
  return { port, pid: null, cwd: null, projectFolder: null };
@@ -2374,9 +2486,14 @@ async function lookupProcessInfo(pid) {
2374
2486
  const platform = process.platform;
2375
2487
  try {
2376
2488
  if (platform === "win32") {
2377
- const { stdout } = await execAsync(
2378
- `wmic process where processid=${pid} get commandline /value`,
2379
- { windowsHide: true }
2489
+ const { stdout } = await execFileAsync(
2490
+ "wmic.exe",
2491
+ ["process", "where", `processid=${pid}`, "get", "commandline", "/value"],
2492
+ {
2493
+ windowsHide: true,
2494
+ timeout: 3e3,
2495
+ maxBuffer: 64 * 1024
2496
+ }
2380
2497
  );
2381
2498
  const lines = stdout.trim().split("\n").filter(Boolean);
2382
2499
  for (const line of lines) {
@@ -2646,99 +2763,6 @@ function stripOpenAIOrphanToolMessages(rawBody) {
2646
2763
  parsed.messages = kept;
2647
2764
  return { body: JSON.stringify(parsed), removed: indices.length, orphanIds };
2648
2765
  }
2649
- const IDLE_TIMEOUT_MS = Number(process.env["SESSION_PROCESS_IDLE_MS"]) || 5 * 60 * 1e3;
2650
- const MAX_RESTARTS = 3;
2651
- const _processes = /* @__PURE__ */ new Map();
2652
- class SessionProcess {
2653
- constructor(sessionId) {
2654
- this.sessionId = sessionId;
2655
- }
2656
- child = null;
2657
- pending = /* @__PURE__ */ new Map();
2658
- nextId = 0;
2659
- idleTimer = null;
2660
- restartCount = 0;
2661
- destroyed = false;
2662
- /** Number of outstanding jobs sent to this process. */
2663
- get pendingCount() {
2664
- return this.pending.size;
2665
- }
2666
- ensureRunning() {
2667
- if (this.child !== null && this.child.connected) return this.child;
2668
- const entryPath = new URL("./sessionWorkerEntry.ts", import.meta.url).pathname;
2669
- const resolvedPath = process.platform === "win32" && entryPath.startsWith("/") ? entryPath.slice(1) : entryPath;
2670
- this.child = fork(resolvedPath, [], {
2671
- stdio: ["pipe", "pipe", "pipe", "ipc"]
2672
- });
2673
- this.restartCount += 1;
2674
- this.child.on("message", (msg) => {
2675
- const pending = this.pending.get(msg.id);
2676
- if (pending !== void 0) {
2677
- this.pending.delete(msg.id);
2678
- pending.resolve(msg.result);
2679
- }
2680
- this.resetIdleTimer();
2681
- });
2682
- this.child.on("error", (err) => {
2683
- logger.error(`[sessionProcess] Session ${this.sessionId} process error:`, err.message);
2684
- });
2685
- this.child.on("exit", (code, signal) => {
2686
- const wasConnected = this.child !== null;
2687
- this.child = null;
2688
- if (this.destroyed) return;
2689
- for (const [, pending] of this.pending) {
2690
- pending.reject(
2691
- new Error(
2692
- `Session process exited with code ${code ?? signal}, session=${this.sessionId}`
2693
- )
2694
- );
2695
- }
2696
- this.pending.clear();
2697
- if (wasConnected && this.restartCount <= MAX_RESTARTS) {
2698
- logger.warn(
2699
- `[sessionProcess] Session ${this.sessionId} worker exited (code=${code ?? signal}), restart ${this.restartCount}/${MAX_RESTARTS}`
2700
- );
2701
- }
2702
- });
2703
- this.resetIdleTimer();
2704
- return this.child;
2705
- }
2706
- enqueue(job) {
2707
- return new Promise((resolve, reject) => {
2708
- const child = this.ensureRunning();
2709
- const id = String(++this.nextId);
2710
- this.pending.set(id, { resolve, reject });
2711
- child.send({ id, job });
2712
- });
2713
- }
2714
- resetIdleTimer() {
2715
- if (this.idleTimer !== null) clearTimeout(this.idleTimer);
2716
- this.idleTimer = setTimeout(() => {
2717
- if (this.pending.size === 0) {
2718
- this.destroy();
2719
- }
2720
- }, IDLE_TIMEOUT_MS);
2721
- }
2722
- destroy() {
2723
- this.destroyed = true;
2724
- if (this.idleTimer !== null) {
2725
- clearTimeout(this.idleTimer);
2726
- this.idleTimer = null;
2727
- }
2728
- if (this.child !== null) {
2729
- this.child.kill();
2730
- this.child = null;
2731
- }
2732
- _processes.delete(this.sessionId);
2733
- }
2734
- }
2735
- function getSessionProcess(sessionId) {
2736
- const existing = _processes.get(sessionId);
2737
- if (existing !== void 0) return existing;
2738
- const sp = new SessionProcess(sessionId);
2739
- _processes.set(sessionId, sp);
2740
- return sp;
2741
- }
2742
2766
  function buildFileLogEntry(log, upstreamUrl) {
2743
2767
  return {
2744
2768
  timestamp: log.timestamp,
@@ -2873,9 +2897,10 @@ function commitFinalizeLogResult(result) {
2873
2897
  const RUNTIME = (() => {
2874
2898
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
2875
2899
  const mode = process.env["FINALIZER_RUNTIME"];
2900
+ if (mode === "process") return "process";
2876
2901
  if (mode === "worker") return "worker";
2877
2902
  if (mode === "inline") return "inline";
2878
- return "process";
2903
+ return isSessionProcessAvailable() ? "process" : "inline";
2879
2904
  })();
2880
2905
  function executeBuildInSessionProcess(job) {
2881
2906
  const sessionId = job.log.sessionId ?? "__unassigned__";
@@ -1,4 +1,4 @@
1
- const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/session/$sessionId"], "preloads": ["/assets/main-CR9IJlz1.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-B8ttyigz.js", "/assets/ProxyViewerContainer-DfxRK7Nt.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-C-aKd1Ky.js", "/assets/ProxyViewerContainer-DfxRK7Nt.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-CR9IJlz1.js" });
1
+ const tsrStartManifest = () => ({ "routes": { "__root__": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/__root.tsx", "children": ["/", "/api/config", "/api/health", "/api/logs", "/api/mcp", "/api/models", "/api/providers", "/api/sessions", "/proxy/$", "/session/$sessionId"], "preloads": ["/assets/main-DpODUL6C.js"], "assets": [] }, "/": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/index.tsx", "assets": [], "preloads": ["/assets/index-DqgTchf4.js", "/assets/ProxyViewerContainer-Bi1QXDYo.js"] }, "/api/config": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.ts", "children": ["/api/config/paths"] }, "/api/health": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/health.ts" }, "/api/logs": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.ts", "children": ["/api/logs/$id", "/api/logs/stream"] }, "/api/mcp": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/mcp.ts" }, "/api/models": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/models.ts" }, "/api/providers": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.ts", "children": ["/api/providers/$providerId", "/api/providers/export", "/api/providers/import", "/api/providers/scan"] }, "/api/sessions": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/sessions.ts" }, "/proxy/$": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/proxy/$.ts" }, "/session/$sessionId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/session/$sessionId.tsx", "assets": [], "preloads": ["/assets/_sessionId-DE0J5YHU.js", "/assets/ProxyViewerContainer-Bi1QXDYo.js"] }, "/api/config/paths": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/config.paths.ts" }, "/api/logs/$id": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.ts", "children": ["/api/logs/$id/chunks", "/api/logs/$id/replay"] }, "/api/logs/stream": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.stream.ts" }, "/api/providers/$providerId": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.ts", "children": ["/api/providers/$providerId/test"] }, "/api/providers/export": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.export.ts" }, "/api/providers/import": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.import.ts" }, "/api/providers/scan": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.scan.ts" }, "/api/logs/$id/chunks": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.chunks.ts" }, "/api/logs/$id/replay": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/logs.$id.replay.ts" }, "/api/providers/$providerId/test": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.ts", "children": ["/api/providers/$providerId/test/log"] }, "/api/providers/$providerId/test/log": { "filePath": "C:/Users/claw/workspace/llm-inspector/src/routes/api/providers.$providerId.test.log.ts" } }, "clientEntry": "/assets/main-DpODUL6C.js" });
2
2
  export {
3
3
  tsrStartManifest
4
4
  };
@@ -35,110 +35,110 @@ const headers = ((m) => function headersRouteRule(event) {
35
35
  }
36
36
  });
37
37
  const assets = {
38
- "/assets/CompareDrawer-DtERUdIt.js": {
39
- "type": "text/javascript; charset=utf-8",
40
- "etag": '"4a1f-iUikoccqSI5dxDV/DitC+V4SyF4"',
41
- "mtime": "2026-06-18T01:14:51.128Z",
42
- "size": 18975,
43
- "path": "../public/assets/CompareDrawer-DtERUdIt.js"
38
+ "/assets/alibaba-TTwafVwX.svg": {
39
+ "type": "image/svg+xml",
40
+ "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
41
+ "mtime": "2026-06-18T09:12:22.689Z",
42
+ "size": 5915,
43
+ "path": "../public/assets/alibaba-TTwafVwX.svg"
44
44
  },
45
- "/assets/minimax-BPMzvuL-.jpeg": {
46
- "type": "image/jpeg",
47
- "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
48
- "mtime": "2026-06-18T01:14:51.128Z",
49
- "size": 6918,
50
- "path": "../public/assets/minimax-BPMzvuL-.jpeg"
45
+ "/assets/index-Bqi9RAGS.css": {
46
+ "type": "text/css; charset=utf-8",
47
+ "etag": '"177a9-QMoW/GsYu1jERI0JyFHA9CiB/o0"',
48
+ "mtime": "2026-06-18T09:12:22.692Z",
49
+ "size": 96169,
50
+ "path": "../public/assets/index-Bqi9RAGS.css"
51
+ },
52
+ "/assets/json-viewer-CyJ5nYhX.js": {
53
+ "type": "text/javascript; charset=utf-8",
54
+ "etag": '"1e651-ydiPWeNOTBxab1yp6aZhKmFLc9w"',
55
+ "mtime": "2026-06-18T09:12:22.693Z",
56
+ "size": 124497,
57
+ "path": "../public/assets/json-viewer-CyJ5nYhX.js"
51
58
  },
52
- "/assets/index-B8ttyigz.js": {
59
+ "/assets/index-DqgTchf4.js": {
53
60
  "type": "text/javascript; charset=utf-8",
54
- "etag": '"74-b0SfzNSVRwMMxI9rB/BBMzOdSls"',
55
- "mtime": "2026-06-18T01:14:51.128Z",
61
+ "etag": '"74-dCXgAHXAyb0J55GEgjrhbbUgzbc"',
62
+ "mtime": "2026-06-18T09:12:22.692Z",
56
63
  "size": 116,
57
- "path": "../public/assets/index-B8ttyigz.js"
64
+ "path": "../public/assets/index-DqgTchf4.js"
58
65
  },
59
- "/assets/json-viewer-CztuZ9cT.js": {
66
+ "/assets/CompareDrawer-AMCFfwDw.js": {
60
67
  "type": "text/javascript; charset=utf-8",
61
- "etag": '"1e651-ZXyBYCz6WZX+FFKagLcAEHfl5xM"',
62
- "mtime": "2026-06-18T01:14:51.128Z",
63
- "size": 124497,
64
- "path": "../public/assets/json-viewer-CztuZ9cT.js"
68
+ "etag": '"4a1f-JS26/3WLLROR+e/DYSYJ+f7WiQc"',
69
+ "mtime": "2026-06-18T09:12:22.693Z",
70
+ "size": 18975,
71
+ "path": "../public/assets/CompareDrawer-AMCFfwDw.js"
65
72
  },
66
- "/assets/index-DeJyypsp.css": {
67
- "type": "text/css; charset=utf-8",
68
- "etag": '"17504-zMXz49VysEK/ru01MvYLc8/SiPI"',
69
- "mtime": "2026-06-18T01:14:51.128Z",
70
- "size": 95492,
71
- "path": "../public/assets/index-DeJyypsp.css"
73
+ "/assets/ReplayDialog-C1x2-73A.js": {
74
+ "type": "text/javascript; charset=utf-8",
75
+ "etag": '"11c0-0BdsMUTzMbm8+qZrAg7WR+cZJxI"',
76
+ "mtime": "2026-06-18T09:12:22.692Z",
77
+ "size": 4544,
78
+ "path": "../public/assets/ReplayDialog-C1x2-73A.js"
72
79
  },
73
- "/assets/RequestAnatomy-Cx_vluvK.js": {
80
+ "/assets/StreamingChunkSequence-BjX20-9R.js": {
74
81
  "type": "text/javascript; charset=utf-8",
75
- "etag": '"142a-Yhymkf38kgBJoPDw4VNi/Dr6f+k"',
76
- "mtime": "2026-06-18T01:14:51.128Z",
77
- "size": 5162,
78
- "path": "../public/assets/RequestAnatomy-Cx_vluvK.js"
82
+ "etag": '"d81-wlp9qhgGUggvmv1Zxwa60zK7kv0"',
83
+ "mtime": "2026-06-18T09:12:22.693Z",
84
+ "size": 3457,
85
+ "path": "../public/assets/StreamingChunkSequence-BjX20-9R.js"
79
86
  },
80
- "/assets/ResponseView-5F8Ms5z4.js": {
87
+ "/assets/ResponseView-BsBxYW8b.js": {
81
88
  "type": "text/javascript; charset=utf-8",
82
- "etag": '"6e91-th7Dsd8aj+055p7AR8JrAwuhP30"',
83
- "mtime": "2026-06-18T01:14:51.128Z",
89
+ "etag": '"6e91-dcQF6OvaeJlJzeXqydW9EwIelj8"',
90
+ "mtime": "2026-06-18T09:12:22.693Z",
84
91
  "size": 28305,
85
- "path": "../public/assets/ResponseView-5F8Ms5z4.js"
92
+ "path": "../public/assets/ResponseView-BsBxYW8b.js"
86
93
  },
87
- "/assets/StreamingChunkSequence-CKDCWfu9.js": {
88
- "type": "text/javascript; charset=utf-8",
89
- "etag": '"d81-j+RBgIkb3QoxK8obtxQiGw8pId4"',
90
- "mtime": "2026-06-18T01:14:51.128Z",
91
- "size": 3457,
92
- "path": "../public/assets/StreamingChunkSequence-CKDCWfu9.js"
94
+ "/assets/minimax-BPMzvuL-.jpeg": {
95
+ "type": "image/jpeg",
96
+ "etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
97
+ "mtime": "2026-06-18T09:12:22.692Z",
98
+ "size": 6918,
99
+ "path": "../public/assets/minimax-BPMzvuL-.jpeg"
93
100
  },
94
101
  "/assets/zhipuai-BPNAnxo-.svg": {
95
102
  "type": "image/svg+xml",
96
103
  "etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
97
- "mtime": "2026-06-18T01:14:51.128Z",
104
+ "mtime": "2026-06-18T09:12:22.692Z",
98
105
  "size": 11256,
99
106
  "path": "../public/assets/zhipuai-BPNAnxo-.svg"
100
107
  },
101
- "/assets/_sessionId-C-aKd1Ky.js": {
102
- "type": "text/javascript; charset=utf-8",
103
- "etag": '"d2-gL7mjPIXj+6BHz4XUVVdKjuvNDk"',
104
- "mtime": "2026-06-18T01:14:51.128Z",
105
- "size": 210,
106
- "path": "../public/assets/_sessionId-C-aKd1Ky.js"
107
- },
108
- "/assets/main-CR9IJlz1.js": {
109
- "type": "text/javascript; charset=utf-8",
110
- "etag": '"50a37-XeRUA7+G6lgGxJXRg+juq8ZZbMM"',
111
- "mtime": "2026-06-18T01:14:51.128Z",
112
- "size": 330295,
113
- "path": "../public/assets/main-CR9IJlz1.js"
114
- },
115
- "/assets/alibaba-TTwafVwX.svg": {
116
- "type": "image/svg+xml",
117
- "etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
118
- "mtime": "2026-06-18T01:14:51.128Z",
119
- "size": 5915,
120
- "path": "../public/assets/alibaba-TTwafVwX.svg"
121
- },
122
- "/assets/ProxyViewerContainer-DfxRK7Nt.js": {
108
+ "/assets/main-DpODUL6C.js": {
123
109
  "type": "text/javascript; charset=utf-8",
124
- "etag": '"76d0d-d2XlfO92n/Dltmn6NOHQY/Ui+fw"',
125
- "mtime": "2026-06-18T01:14:51.128Z",
126
- "size": 486669,
127
- "path": "../public/assets/ProxyViewerContainer-DfxRK7Nt.js"
110
+ "etag": '"50e93-I+c4WJ0eqNgllRdD+TE3/FNhdZw"',
111
+ "mtime": "2026-06-18T09:12:22.692Z",
112
+ "size": 331411,
113
+ "path": "../public/assets/main-DpODUL6C.js"
128
114
  },
129
115
  "/assets/qwen-CONDcHqt.png": {
130
116
  "type": "image/png",
131
117
  "etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
132
- "mtime": "2026-06-18T01:14:51.128Z",
118
+ "mtime": "2026-06-18T09:12:22.692Z",
133
119
  "size": 357059,
134
120
  "path": "../public/assets/qwen-CONDcHqt.png"
135
121
  },
136
- "/assets/ReplayDialog-VMsGnJSI.js": {
122
+ "/assets/RequestAnatomy-CBNqMbxo.js": {
137
123
  "type": "text/javascript; charset=utf-8",
138
- "etag": '"11c0-t7Uuk+IRrGGdj7fX41f/Jt6J41I"',
139
- "mtime": "2026-06-18T01:14:51.128Z",
140
- "size": 4544,
141
- "path": "../public/assets/ReplayDialog-VMsGnJSI.js"
124
+ "etag": '"142a-OyCo/ZrZQjHOESLPR2QHdrDlSfY"',
125
+ "mtime": "2026-06-18T09:12:22.693Z",
126
+ "size": 5162,
127
+ "path": "../public/assets/RequestAnatomy-CBNqMbxo.js"
128
+ },
129
+ "/assets/ProxyViewerContainer-Bi1QXDYo.js": {
130
+ "type": "text/javascript; charset=utf-8",
131
+ "etag": '"76fed-g89dZRBRI198ItonW8vjcw/8gls"',
132
+ "mtime": "2026-06-18T09:12:22.692Z",
133
+ "size": 487405,
134
+ "path": "../public/assets/ProxyViewerContainer-Bi1QXDYo.js"
135
+ },
136
+ "/assets/_sessionId-DE0J5YHU.js": {
137
+ "type": "text/javascript; charset=utf-8",
138
+ "etag": '"d2-yTX8F3UFbyfcaXlKoGMrmxJtX0U"',
139
+ "mtime": "2026-06-18T09:12:22.692Z",
140
+ "size": 210,
141
+ "path": "../public/assets/_sessionId-DE0J5YHU.js"
142
142
  }
143
143
  };
144
144
  function readAsset(id) {
package/README.md CHANGED
@@ -18,6 +18,7 @@
18
18
  - **Web UI**:`http://localhost:25947`
19
19
  - **Proxy**:`http://localhost:25947/proxy`
20
20
  - **MCP Server**:`http://localhost:25947/api/mcp`
21
+ - **Chrome Companion**:`extensions/chrome`(通过 `chrome://extensions` 以 Load unpacked 方式加载)
21
22
 
22
23
  ## 核心能力
23
24
 
@@ -53,6 +54,8 @@ llm-inspector
53
54
  ```bash
54
55
  llm-inspector --no-open
55
56
  llm-inspector --port 3000
57
+ llm-inspector --background
58
+ llm-inspector --force-restart
56
59
  llm-inspector --config-dir ./local-config
57
60
  llm-inspector --providers '[{"name":"OpenAI","apiKey":"sk-...","models":["gpt-4o-mini"],"openaiBaseUrl":"https://api.openai.com/v1","authHeader":"bearer","createdAt":"2026-01-01T00:00:00.000Z","updatedAt":"2026-01-01T00:00:00.000Z","id":"demo"}]'
58
61
  ```
@@ -73,6 +76,23 @@ bun install
73
76
  bun run dev
74
77
  ```
75
78
 
79
+ ### Chrome Companion 扩展
80
+
81
+ 仓库内提供了一个轻量 Chrome 侧边栏扩展,用来连接本机 llm-inspector、查看运行状态和最近请求,并快速打开 Dashboard 或复制代理命令。
82
+
83
+ ```text
84
+ extensions/chrome
85
+ ```
86
+
87
+ 启动方式:
88
+
89
+ 1. 先运行 `llm-inspector` 或 `bun run dev`。
90
+ 2. 打开 `chrome://extensions`。
91
+ 3. 开启 **Developer mode**。
92
+ 4. 点击 **Load unpacked**。
93
+ 5. 选择 `extensions/chrome` 目录。
94
+ 6. 点击扩展图标打开侧边栏。
95
+
76
96
  ## 配置 Provider
77
97
 
78
98
  打开 Web UI,进入 **Settings** 添加 Provider:
@@ -315,6 +335,8 @@ ANTHROPIC_BASE_URL=http://localhost:25947/proxy claude
315
335
  - [架构说明](docs/Architecture.md)
316
336
  - [开发指南](docs/Development.md)
317
337
  - [MCP Server](docs/MCP-Server.md)
338
+ - [CHANGELOG](CHANGELOG.md) — release notes
339
+ - [CONTRIBUTING](CONTRIBUTING.md) — how to land a change
318
340
 
319
341
  ## License
320
342
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@tonyclaw/llm-inspector",
3
- "version": "1.19.1",
3
+ "version": "1.19.3",
4
4
  "type": "module",
5
- "description": "LLM API proxy inspector captures and displays requests/responses from AI coding tools in a web UI",
5
+ "description": "Local LLM API inspector for AI coding tools, with real-time request and response capture.",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git"
@@ -40,6 +40,8 @@
40
40
  "lint": "eslint .",
41
41
  "format": "biome format --write .",
42
42
  "format:check": "biome format .",
43
+ "extension:zip": "node scripts/pack-chrome-extension.mjs",
44
+ "extension:release": "node scripts/release-chrome-extension.mjs",
43
45
  "knip": "knip",
44
46
  "check": "bun format && bun typecheck && bun lint",
45
47
  "prepare": "husky",
@@ -0,0 +1,18 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="LLM Inspector">
2
+ <rect width="64" height="64" rx="14" fill="#111827" />
3
+ <g fill="none" stroke="#f59e0b" stroke-width="4.2" stroke-linecap="round" stroke-linejoin="round">
4
+ <path fill="#f59e0b" d="M15 36c0-11 7-18 17-18s17 7 17 18c0 8-7 13-17 13s-17-5-17-13z" />
5
+ <path d="M16 31c-6-5-12-3-12 4 0 5 6 6 11 2" />
6
+ <path d="M48 31c6-5 12-3 12 4 0 5-6 6-11 2" />
7
+ <path d="M27 19l-3-7" />
8
+ <path d="M37 19l3-7" />
9
+ <path d="M19 45l-6 9" />
10
+ <path d="M27 48l-3 9" />
11
+ <path d="M37 48l3 9" />
12
+ <path d="M45 45l6 9" />
13
+ </g>
14
+ <circle cx="24" cy="11" r="3.2" fill="#f59e0b" />
15
+ <circle cx="40" cy="11" r="3.2" fill="#f59e0b" />
16
+ <circle cx="25" cy="34" r="2.1" fill="#111827" />
17
+ <circle cx="39" cy="34" r="2.1" fill="#111827" />
18
+ </svg>
@@ -47,6 +47,7 @@ function which(bin: string): string | null {
47
47
  const out = execFileSync("where", [bin], {
48
48
  encoding: "utf8",
49
49
  timeout: 3000,
50
+ windowsHide: true,
50
51
  stdio: ["ignore", "pipe", "ignore"],
51
52
  });
52
53
  const first = out.split(/\r?\n/).find((line) => line.trim().length > 0);