@tonyclaw/llm-inspector 1.19.0 → 1.19.2

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 (40) hide show
  1. package/.output/cli.js +338 -102
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-DwayZPPO.js → CompareDrawer-BzTsEelr.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-iv3LVMEW.js → ProxyViewerContainer-BHm-n-_W.js} +9 -9
  5. package/.output/public/assets/{ReplayDialog-CaV1elYO.js → ReplayDialog-Dxxo80xO.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CSfnjK7j.js → RequestAnatomy-D-swiaii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-YkOL__xm.js → ResponseView-DvdH2bGk.js} +1 -1
  8. package/.output/public/assets/{StreamingChunkSequence-D_p6L-oB.js → StreamingChunkSequence-D_RzgyKq.js} +1 -1
  9. package/.output/public/assets/_sessionId-DdODJCYY.js +1 -0
  10. package/.output/public/assets/{index-DeJyypsp.css → index-Bqi9RAGS.css} +1 -1
  11. package/.output/public/assets/index-EvnsNPOK.js +1 -0
  12. package/.output/public/assets/{json-viewer-BB-9bqnP.js → json-viewer-DIHZbEId.js} +1 -1
  13. package/.output/public/assets/{main-COVN451W.js → main-Br2EjrqZ.js} +2 -2
  14. package/.output/server/{_sessionId-BJT5qIib.mjs → _sessionId-CPkCxTP8.mjs} +4 -3
  15. package/.output/server/_ssr/{CompareDrawer-DNGYdUXs.mjs → CompareDrawer-DKHgXC5-.mjs} +4 -4
  16. package/.output/server/_ssr/{ProxyViewerContainer-B-zDOLYE.mjs → ProxyViewerContainer-B41D-2Eo.mjs} +57 -9
  17. package/.output/server/_ssr/{ReplayDialog-DWeqMA4y.mjs → ReplayDialog-D2piRWb0.mjs} +5 -5
  18. package/.output/server/_ssr/{RequestAnatomy-TOsrMu9-.mjs → RequestAnatomy-Ce7QdQNP.mjs} +4 -3
  19. package/.output/server/_ssr/{ResponseView-BuqdPrzm.mjs → ResponseView-D50UPv-r.mjs} +5 -5
  20. package/.output/server/_ssr/{StreamingChunkSequence-DuzNZkqL.mjs → StreamingChunkSequence-CDlNFS3Z.mjs} +4 -4
  21. package/.output/server/_ssr/{index-1nCQUt3y.mjs → index-DhAQxjnZ.mjs} +4 -3
  22. package/.output/server/_ssr/index.mjs +2 -2
  23. package/.output/server/_ssr/{json-viewer-BL8xhHbi.mjs → json-viewer-BZRjG_f7.mjs} +4 -4
  24. package/.output/server/_ssr/{router-aCaUgVTW.mjs → router-yP98-Gq-.mjs} +126 -105
  25. package/.output/server/{_tanstack-start-manifest_v-cBRxvCjb.mjs → _tanstack-start-manifest_v-d4a4xlOi.mjs} +1 -1
  26. package/.output/server/index.mjs +64 -64
  27. package/README.md +22 -0
  28. package/package.json +3 -1
  29. package/src/cli/detect-tools.ts +1 -0
  30. package/src/cli/templates/skill-onboard.ts +204 -71
  31. package/src/cli.ts +164 -39
  32. package/src/components/ProxyViewerContainer.tsx +52 -0
  33. package/src/components/proxy-viewer/LogEntryHeader.tsx +1 -0
  34. package/src/proxy/logFinalizer.ts +7 -3
  35. package/src/proxy/sessionProcess.ts +14 -7
  36. package/src/proxy/sessionSupervisor.ts +3 -2
  37. package/src/proxy/socketTracker.ts +19 -7
  38. package/styles/globals.css +14 -7
  39. package/.output/public/assets/_sessionId-BgCVUC6R.js +0 -1
  40. package/.output/public/assets/index-CWA4S0FO.js +0 -1
package/src/cli.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { spawn, execSync } from "node:child_process";
2
+ import { spawn, execSync, type ChildProcess } from "node:child_process";
3
+ import { createConnection } from "node:net";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { dirname, join } from "node:path";
5
6
  import { existsSync } from "node:fs";
@@ -8,6 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = dirname(__filename);
9
10
 
10
11
  const DEFAULT_PORT = 25947;
12
+ const LOCAL_PROBE_TIMEOUT_MS = 2000;
11
13
 
12
14
  /**
13
15
  * Subcommand router. The legacy one-liner UX (`llm-inspector` with no args,
@@ -23,18 +25,107 @@ if (subcommand === "onboard") {
23
25
  process.exit(code);
24
26
  }
25
27
 
26
- runStart(process.argv.slice(2));
28
+ await runStart(process.argv.slice(2));
27
29
 
28
30
  // -----------------------------------------------------------------------------
29
31
  // Legacy `start` behavior — start the proxy on the configured port. Extracted
30
32
  // into a function so the router above can keep the top-level flow readable.
31
33
  // -----------------------------------------------------------------------------
32
- function runStart(args: string[]): void {
34
+ async function isInspectorHealthy(port: number): Promise<boolean> {
35
+ const controller = new AbortController();
36
+ const timeout = setTimeout(() => controller.abort(), LOCAL_PROBE_TIMEOUT_MS);
37
+ try {
38
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
39
+ cache: "no-store",
40
+ signal: controller.signal,
41
+ });
42
+ return response.ok;
43
+ } catch {
44
+ return false;
45
+ } finally {
46
+ clearTimeout(timeout);
47
+ }
48
+ }
49
+
50
+ function isPortAcceptingConnections(port: number): Promise<boolean> {
51
+ return new Promise((resolve) => {
52
+ const socket = createConnection({ host: "127.0.0.1", port });
53
+ const finish = (value: boolean): void => {
54
+ socket.removeAllListeners();
55
+ socket.destroy();
56
+ resolve(value);
57
+ };
58
+
59
+ socket.setTimeout(LOCAL_PROBE_TIMEOUT_MS);
60
+ socket.once("connect", () => finish(true));
61
+ socket.once("timeout", () => finish(false));
62
+ socket.once("error", () => finish(false));
63
+ });
64
+ }
65
+
66
+ function sleep(ms: number): Promise<void> {
67
+ return new Promise((resolve) => {
68
+ setTimeout(resolve, ms);
69
+ });
70
+ }
71
+
72
+ async function waitForInspectorHealthy(port: number, timeoutMs: number): Promise<boolean> {
73
+ const start = Date.now();
74
+ while (Date.now() - start < timeoutMs) {
75
+ if (await isInspectorHealthy(port)) return true;
76
+ await sleep(250);
77
+ }
78
+ return false;
79
+ }
80
+
81
+ function openBrowser(targetUrl: string): void {
82
+ let command: string[] | undefined;
83
+ // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
84
+ switch (process.platform) {
85
+ case "darwin":
86
+ command = ["open", targetUrl];
87
+ break;
88
+ case "linux":
89
+ command = ["xdg-open", targetUrl];
90
+ break;
91
+ case "win32":
92
+ command = ["cmd", "/c", "start", "", targetUrl];
93
+ break;
94
+ default:
95
+ // Unsupported platform - do nothing
96
+ break;
97
+ }
98
+ if (command === undefined) return;
99
+ const [bin, ...cmdArgs] = command;
100
+ if (bin === undefined) return;
101
+ const browserProcess = spawn(bin, cmdArgs, {
102
+ stdio: "ignore",
103
+ detached: true,
104
+ windowsHide: true,
105
+ });
106
+ browserProcess.unref();
107
+ }
108
+
109
+ function waitForProcessExit(child: ChildProcess): Promise<number> {
110
+ return new Promise((resolve) => {
111
+ child.once("exit", (code) => {
112
+ resolve(code ?? 1);
113
+ });
114
+ child.once("error", () => {
115
+ resolve(1);
116
+ });
117
+ });
118
+ }
119
+
120
+ async function runStart(args: string[]): Promise<void> {
33
121
  const envPort = process.env["PORT"];
34
122
  const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
35
123
 
36
124
  let port = portDefault;
37
125
  let open = true;
126
+ let openWasSpecified = false;
127
+ let background = false;
128
+ let forceRestart = false;
38
129
  let configDir: string | undefined;
39
130
  let providersJson: string | undefined;
40
131
 
@@ -48,9 +139,18 @@ function runStart(args: string[]): void {
48
139
  break;
49
140
  case "--no-open":
50
141
  open = false;
142
+ openWasSpecified = true;
51
143
  break;
52
144
  case "--open":
53
145
  open = true;
146
+ openWasSpecified = true;
147
+ break;
148
+ case "--force-restart":
149
+ case "--restart":
150
+ forceRestart = true;
151
+ break;
152
+ case "--background":
153
+ background = true;
54
154
  break;
55
155
  case "--config-dir":
56
156
  configDir = args[i + 1];
@@ -65,6 +165,12 @@ function runStart(args: string[]): void {
65
165
  }
66
166
  }
67
167
 
168
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
169
+ console.error(`Invalid port: ${String(port)}. Use --port <1-65535>.`);
170
+ process.exitCode = 1;
171
+ return;
172
+ }
173
+
68
174
  /**
69
175
  * Check if a port is in use and kill the process using it
70
176
  */
@@ -79,6 +185,7 @@ function runStart(args: string[]): void {
79
185
  const output = execSync(`netstat -ano | findstr :${targetPort}`, {
80
186
  encoding: "utf8",
81
187
  timeout: 5000,
188
+ windowsHide: true,
82
189
  });
83
190
  const lines = output.trim().split("\n");
84
191
  for (const line of lines) {
@@ -99,8 +206,12 @@ function runStart(args: string[]): void {
99
206
 
100
207
  for (const pid of pids) {
101
208
  try {
102
- console.log(`Killing process ${pid} on port ${port}...`);
103
- execSync(`taskkill /PID ${pid} /F`, { encoding: "utf8", timeout: 5000 });
209
+ console.log(`Killing process ${pid} on port ${targetPort}...`);
210
+ execSync(`taskkill /PID ${pid} /F`, {
211
+ encoding: "utf8",
212
+ timeout: 5000,
213
+ windowsHide: true,
214
+ });
104
215
  } catch {
105
216
  // Process may have already exited
106
217
  }
@@ -120,7 +231,7 @@ function runStart(args: string[]): void {
120
231
 
121
232
  for (const pid of pids) {
122
233
  try {
123
- console.log(`Killing process ${pid} on port ${port}...`);
234
+ console.log(`Killing process ${pid} on port ${targetPort}...`);
124
235
  execSync(`kill -9 ${pid}`, { encoding: "utf8", timeout: 5000 });
125
236
  } catch {
126
237
  // Process may have already exited
@@ -134,11 +245,28 @@ function runStart(args: string[]): void {
134
245
 
135
246
  process.env["PORT"] = String(port);
136
247
 
137
- // Kill any existing process on the port
138
- killProcessOnPort(port);
139
-
140
248
  const url = `http://localhost:${port}`;
141
249
 
250
+ if (!forceRestart && (await isInspectorHealthy(port))) {
251
+ console.log(`llm-inspector is already running at ${url}`);
252
+ console.log(`Use --force-restart to restart the existing instance.`);
253
+ if (open && openWasSpecified) {
254
+ openBrowser(url);
255
+ }
256
+ return;
257
+ }
258
+
259
+ if (!forceRestart && (await isPortAcceptingConnections(port))) {
260
+ console.error(`Port ${port} is already in use, but it is not a healthy llm-inspector.`);
261
+ console.error(`Stop that process, choose --port <n>, or re-run with --force-restart.`);
262
+ process.exitCode = 1;
263
+ return;
264
+ }
265
+
266
+ if (forceRestart) {
267
+ killProcessOnPort(port);
268
+ }
269
+
142
270
  console.log(`Server running at ${url}`);
143
271
  console.log(` Proxy: ${url}/proxy`);
144
272
  console.log(``);
@@ -157,29 +285,6 @@ function runStart(args: string[]): void {
157
285
  ` Example: ROUTES='{"claude-":"https://api.anthropic.com","MiniMax":"https://api.minimaxi.com/anthropic"}'`,
158
286
  );
159
287
 
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
288
  if (open) {
184
289
  openBrowser(url);
185
290
  }
@@ -191,11 +296,19 @@ function runStart(args: string[]): void {
191
296
  // Start server with node
192
297
  const serverEnv = { ...process.env };
193
298
  if (configDir !== undefined) {
194
- // Convert MSYS/Git Bash path like /c/Users/... to Windows absolute path
299
+ // Normalize MSYS / Git Bash paths to Windows native form.
300
+ // On Windows, `path.join('/c/Users/foo', 'config.json')` becomes
301
+ // `\c\Users\foo\config.json` (leading slash converted to backslash).
302
+ // Child processes spawned by `spawn()` won't follow that style, so
303
+ // rewrite `\c\...` (or any `\x\...` drive) to `C:\...` before
304
+ // handing the path to the proxy server. No-op on already-native
305
+ // Windows paths and on non-Windows platforms.
195
306
  let resolvedPath = join(configDir, "config.json");
196
- // Convert /c/... to C:\... format
197
- if (resolvedPath.startsWith("\\c\\")) {
198
- resolvedPath = "C:" + resolvedPath;
307
+ const msysMatch = /^\\([a-z])\\(.*)$/i.exec(resolvedPath);
308
+ if (msysMatch !== null) {
309
+ const drive = (msysMatch[1] ?? "").toUpperCase();
310
+ const rest = msysMatch[2] ?? "";
311
+ resolvedPath = `${drive}:\\${rest}`;
199
312
  }
200
313
  serverEnv["LLM_INSPECTOR_CONFIG_PATH"] = resolvedPath;
201
314
  }
@@ -203,10 +316,22 @@ function runStart(args: string[]): void {
203
316
  serverEnv["LLM_INSPECTOR_PROVIDERS_JSON"] = providersJson;
204
317
  }
205
318
  const serverProcess = spawn(process.execPath, [serverPath], {
206
- stdio: ["ignore", "inherit", "inherit"],
207
- detached: true,
319
+ stdio: background ? ["ignore", "ignore", "ignore"] : "inherit",
320
+ detached: background,
208
321
  env: serverEnv,
322
+ windowsHide: background,
209
323
  });
210
324
 
211
- serverProcess.unref();
325
+ if (background) {
326
+ serverProcess.unref();
327
+ if (await waitForInspectorHealthy(port, 5000)) {
328
+ console.log(`llm-inspector background server is ready at ${url}`);
329
+ return;
330
+ }
331
+ console.error(`llm-inspector background server did not become ready at ${url}.`);
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+
336
+ process.exitCode = await waitForProcessExit(serverProcess);
212
337
  }
@@ -61,6 +61,8 @@ function filterLogs(
61
61
  }
62
62
 
63
63
  const DEBOUNCE_MS = 50;
64
+ const HASH_SCROLL_ATTEMPTS = 12;
65
+ const HASH_HIGHLIGHT_MS = 1800;
64
66
 
65
67
  function buildLogsStreamUrl(sessionId: string | undefined): string {
66
68
  if (sessionId === undefined) return "/api/logs/stream";
@@ -93,6 +95,7 @@ export function ProxyViewerContainer({
93
95
  const [error, setError] = useState<string | null>(null);
94
96
  const eventSourceRef = useRef<EventSource | null>(null);
95
97
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
98
+ const handledHashRef = useRef<string | null>(null);
96
99
 
97
100
  // O(1) log lookup by id
98
101
  const logIndexRef = useRef<Map<number, number>>(new Map());
@@ -214,6 +217,55 @@ export function ProxyViewerContainer({
214
217
  };
215
218
  }, [connectSSE]);
216
219
 
220
+ useEffect(() => {
221
+ const hash = window.location.hash;
222
+ if (!hash.startsWith("#log-")) return;
223
+ if (handledHashRef.current === hash) return;
224
+ const targetId = hash.slice(1);
225
+ let cancelled = false;
226
+ let attempts = 0;
227
+ let highlightedTarget: HTMLElement | null = null;
228
+ let highlightTimer: number | null = null;
229
+
230
+ const tryScrollToLog = (): void => {
231
+ if (cancelled) return;
232
+ const target = document.getElementById(targetId);
233
+ if (target !== null) {
234
+ handledHashRef.current = hash;
235
+ target.scrollIntoView({ block: "center", behavior: "smooth" });
236
+ if (target instanceof HTMLElement) {
237
+ highlightedTarget = target;
238
+ target.setAttribute("data-deep-link-highlight", "true");
239
+ highlightTimer = window.setTimeout(() => {
240
+ target.removeAttribute("data-deep-link-highlight");
241
+ }, HASH_HIGHLIGHT_MS);
242
+ target.focus({ preventScroll: true });
243
+ if (target.getAttribute("data-nav-action") === "expand") {
244
+ target.click();
245
+ }
246
+ }
247
+ return;
248
+ }
249
+
250
+ attempts += 1;
251
+ if (attempts < HASH_SCROLL_ATTEMPTS) {
252
+ window.setTimeout(tryScrollToLog, 100);
253
+ }
254
+ };
255
+
256
+ tryScrollToLog();
257
+
258
+ return () => {
259
+ cancelled = true;
260
+ if (highlightTimer !== null) {
261
+ window.clearTimeout(highlightTimer);
262
+ }
263
+ if (highlightedTarget !== null) {
264
+ highlightedTarget.removeAttribute("data-deep-link-highlight");
265
+ }
266
+ };
267
+ }, [logs.length]);
268
+
217
269
  const handleClearAll = useCallback(() => {
218
270
  if (initialSessionId !== undefined && allLogs.length === 0) return;
219
271
  void (async () => {
@@ -151,6 +151,7 @@ export const LogEntryHeader = memo(function ({
151
151
  return (
152
152
  <TooltipProvider>
153
153
  <div
154
+ id={`log-${log.id}`}
154
155
  role="button"
155
156
  tabIndex={0}
156
157
  data-nav-id={`log-${log.id}`}
@@ -3,7 +3,7 @@ import { writeChunks } from "./chunkStorage";
3
3
  import { formatForPath } from "./formats";
4
4
  import { appendLogEntry, logger } from "./logger";
5
5
  import type { CapturedLog } from "./schemas";
6
- import { getSessionProcess } from "./sessionProcess";
6
+ import { getSessionProcess, isSessionProcessAvailable } from "./sessionProcess";
7
7
  import { finalizeLogUpdate } from "./store";
8
8
 
9
9
  type BaseFinalizeLogJob = {
@@ -209,17 +209,21 @@ export function commitFinalizeLogResult(result: FinalizeLogResult): void {
209
209
 
210
210
  // ── Routing ─────────────────────────────────────────────────────
211
211
  // FINALIZER_RUNTIME selects the execution backend:
212
- // "process" → per-session child process (default, max isolation)
212
+ // "process" → per-session child process (max isolation)
213
213
  // "worker" → shared Worker Thread pool
214
214
  // "inline" → synchronous in-process (debug / fallback)
215
+ // The default is "process" only when its worker entry exists. Production
216
+ // bundles that do not emit the standalone worker entry fall back to "inline"
217
+ // instead of repeatedly spawning a failing child process.
215
218
  // For backward compatibility, FINALIZER_USE_WORKER=0 forces "inline".
216
219
 
217
220
  const RUNTIME: "process" | "worker" | "inline" = (() => {
218
221
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "inline";
219
222
  const mode = process.env["FINALIZER_RUNTIME"];
223
+ if (mode === "process") return "process";
220
224
  if (mode === "worker") return "worker";
221
225
  if (mode === "inline") return "inline";
222
- return "process";
226
+ return isSessionProcessAvailable() ? "process" : "inline";
223
227
  })();
224
228
 
225
229
  function executeBuildInSessionProcess(job: FinalizeLogJob): Promise<FinalizeLogResult> {
@@ -1,4 +1,6 @@
1
- import { fork, type ChildProcess } from "node:child_process";
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
2
4
  import { logger } from "./logger";
3
5
  import type { FinalizeLogJob, FinalizeLogResult } from "./logFinalizer";
4
6
 
@@ -12,6 +14,14 @@ type PendingJob = {
12
14
  reject: (err: Error) => void;
13
15
  };
14
16
 
17
+ function resolveSessionWorkerPath(): string {
18
+ return fileURLToPath(new URL("./sessionWorkerEntry.ts", import.meta.url));
19
+ }
20
+
21
+ export function isSessionProcessAvailable(): boolean {
22
+ return existsSync(resolveSessionWorkerPath());
23
+ }
24
+
15
25
  export class SessionProcess {
16
26
  private child: ChildProcess | null = null;
17
27
  private pending = new Map<string, PendingJob>();
@@ -30,14 +40,11 @@ export class SessionProcess {
30
40
  private ensureRunning(): ChildProcess {
31
41
  if (this.child !== null && this.child.connected) return this.child;
32
42
 
33
- const entryPath = new URL("./sessionWorkerEntry.ts", import.meta.url).pathname;
34
- // On Windows, the path from URL.pathname starts with "/" which needs to
35
- // be stripped when it's a drive letter path (e.g. "/C:/..." → "C:/...")
36
- const resolvedPath =
37
- process.platform === "win32" && entryPath.startsWith("/") ? entryPath.slice(1) : entryPath;
43
+ const resolvedPath = resolveSessionWorkerPath();
38
44
 
39
- this.child = fork(resolvedPath, [], {
45
+ this.child = spawn(process.execPath, [...process.execArgv, resolvedPath], {
40
46
  stdio: ["pipe", "pipe", "pipe", "ipc"],
47
+ windowsHide: true,
41
48
  });
42
49
 
43
50
  this.restartCount += 1;
@@ -1,4 +1,5 @@
1
1
  import type { CapturedLog } from "./schemas";
2
+ import { isSessionProcessAvailable } from "./sessionProcess";
2
3
 
3
4
  export const PROVIDER_TEST_SESSION_ID = "provider-test";
4
5
 
@@ -21,10 +22,10 @@ export type SessionRuntimeMode = "in-process" | "worker-thread" | "child-process
21
22
  function getRuntimeMode(): SessionRuntimeMode {
22
23
  if (process.env["FINALIZER_USE_WORKER"] === "0") return "in-process";
23
24
  const mode = process.env["FINALIZER_RUNTIME"];
25
+ if (mode === "process") return "child-process";
24
26
  if (mode === "worker") return "worker-thread";
25
27
  if (mode === "inline") return "in-process";
26
- // Default: per-session child process
27
- return "child-process";
28
+ return isSessionProcessAvailable() ? "child-process" : "in-process";
28
29
  }
29
30
 
30
31
  export type SessionSnapshot = {
@@ -1,8 +1,9 @@
1
- import { exec } from "node:child_process";
1
+ import { exec, execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  import { logger } from "./logger";
4
4
 
5
5
  const execAsync = promisify(exec);
6
+ const execFileAsync = promisify(execFile);
6
7
 
7
8
  type ClientInfo = {
8
9
  port: number | null;
@@ -78,9 +79,15 @@ async function lookupClientInfo(port: number): Promise<ClientInfo> {
78
79
  ` Write-Output "$pid|$cmd"`,
79
80
  `}`,
80
81
  ].join("; ");
81
- const { stdout } = await execAsync(`powershell -NoProfile -Command "${psScript}"`, {
82
- windowsHide: true,
83
- });
82
+ const { stdout } = await execFileAsync(
83
+ "powershell.exe",
84
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", psScript],
85
+ {
86
+ windowsHide: true,
87
+ timeout: 3000,
88
+ maxBuffer: 64 * 1024,
89
+ },
90
+ );
84
91
  const trimmed = stdout.trim();
85
92
  if (trimmed === "") {
86
93
  return { port, pid: null, cwd: null, projectFolder: null };
@@ -164,9 +171,14 @@ async function lookupProcessInfo(
164
171
 
165
172
  try {
166
173
  if (platform === "win32") {
167
- const { stdout } = await execAsync(
168
- `wmic process where processid=${pid} get commandline /value`,
169
- { windowsHide: true },
174
+ const { stdout } = await execFileAsync(
175
+ "wmic.exe",
176
+ ["process", "where", `processid=${pid}`, "get", "commandline", "/value"],
177
+ {
178
+ windowsHide: true,
179
+ timeout: 3000,
180
+ maxBuffer: 64 * 1024,
181
+ },
170
182
  );
171
183
  const lines = stdout.trim().split("\n").filter(Boolean);
172
184
  for (const line of lines) {
@@ -165,13 +165,20 @@
165
165
  }
166
166
  }
167
167
 
168
- @media (prefers-reduced-motion: reduce) {
169
- .animate-crab-piano-pop {
170
- animation: none !important;
171
- }
172
- }
173
-
174
- @layer base {
168
+ @media (prefers-reduced-motion: reduce) {
169
+ .animate-crab-piano-pop {
170
+ animation: none !important;
171
+ }
172
+ }
173
+
174
+ [data-deep-link-highlight="true"] {
175
+ background: color-mix(in oklch, var(--chart-2) 18%, transparent);
176
+ box-shadow:
177
+ inset 0 0 0 1px color-mix(in oklch, var(--chart-2) 65%, transparent),
178
+ 0 0 0 3px color-mix(in oklch, var(--chart-2) 20%, transparent);
179
+ }
180
+
181
+ @layer base {
175
182
  * {
176
183
  @apply border-border outline-ring/50;
177
184
  }
@@ -1 +0,0 @@
1
- import{R as s,j as e}from"./main-COVN451W.js";import{P as i}from"./ProxyViewerContainer-iv3LVMEW.js";function t(){const{sessionId:o}=s.useParams();return e.jsx(i,{initialSessionId:o},o)}export{t as component};
@@ -1 +0,0 @@
1
- import{P as o}from"./ProxyViewerContainer-iv3LVMEW.js";import"./main-COVN451W.js";const r=o;export{r as component};