@tonyclaw/llm-inspector 1.6.0 → 1.6.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.
- package/.output/cli.js +119 -0
- package/.output/nitro.json +1 -1
- package/.output/server/index.mjs +19 -19
- package/README.md +54 -2
- package/package.json +7 -6
- package/src/cli.ts +115 -36
package/.output/cli.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
var __dirname = dirname(__filename);
|
|
10
|
+
var DEFAULT_PORT = 25947;
|
|
11
|
+
var findBun = () => {
|
|
12
|
+
const pathEnv = process.env.PATH ?? "";
|
|
13
|
+
const pathDirs = pathEnv.split(process.platform === "win32" ? ";" : ":");
|
|
14
|
+
for (const dir of pathDirs) {
|
|
15
|
+
const bunPath2 = join(dir, process.platform === "win32" ? "bun.exe" : "bun");
|
|
16
|
+
if (existsSync(bunPath2)) {
|
|
17
|
+
if (process.platform === "win32" && !bunPath2.endsWith(".exe")) {
|
|
18
|
+
const actualPath = join(dir, "node_modules", "bun", "bin", "bun.exe");
|
|
19
|
+
if (existsSync(actualPath)) {
|
|
20
|
+
return actualPath;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return bunPath2;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (process.platform === "win32") {
|
|
27
|
+
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
28
|
+
const appData = process.env.APPDATA ?? "";
|
|
29
|
+
const userProfile = process.env.USERPROFILE ?? "";
|
|
30
|
+
const commonPaths = [
|
|
31
|
+
join(localAppData, "bun", "bin", "bun.exe"),
|
|
32
|
+
join(appData, "bun", "bin", "bun.exe"),
|
|
33
|
+
join(userProfile, "AppData", "Local", "bun", "bin", "bun.exe"),
|
|
34
|
+
join(userProfile, "AppData", "Roaming", "npm", "node_modules", "bun", "bin", "bun.exe"),
|
|
35
|
+
join(userProfile, "AppData", "Roaming", "npm", "bun")
|
|
36
|
+
];
|
|
37
|
+
for (const bunPath2 of commonPaths) {
|
|
38
|
+
if (existsSync(bunPath2)) {
|
|
39
|
+
return bunPath2;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
var envPort = process.env["PORT"];
|
|
46
|
+
var portDefault = envPort !== void 0 ? Number(envPort) : DEFAULT_PORT;
|
|
47
|
+
var args = process.argv.slice(2);
|
|
48
|
+
var port = portDefault;
|
|
49
|
+
var open = true;
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
const arg = args[i] ?? "";
|
|
52
|
+
switch (arg) {
|
|
53
|
+
case "--port":
|
|
54
|
+
case "-p":
|
|
55
|
+
port = Number(args[i + 1]);
|
|
56
|
+
i++;
|
|
57
|
+
break;
|
|
58
|
+
case "--no-open":
|
|
59
|
+
open = false;
|
|
60
|
+
break;
|
|
61
|
+
case "--open":
|
|
62
|
+
open = true;
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
process.env["PORT"] = String(port);
|
|
69
|
+
var url = `http://localhost:${port}`;
|
|
70
|
+
console.log(`Server running at ${url}`);
|
|
71
|
+
console.log(` Proxy: ${url}/proxy`);
|
|
72
|
+
console.log(``);
|
|
73
|
+
console.log(`Route AI coding tools through the proxy:`);
|
|
74
|
+
console.log(` Claude Code: ANTHROPIC_BASE_URL=${url}/proxy claude`);
|
|
75
|
+
console.log(` OpenCode: LLM_BASE_URL=${url}/proxy opencode`);
|
|
76
|
+
console.log(` Direct HTTP: curl ${url}/proxy/v1/messages -d '{"model":"...","messages":[...]}'`);
|
|
77
|
+
console.log(``);
|
|
78
|
+
console.log(`Routing environment variables:`);
|
|
79
|
+
console.log(` ROUTES JSON map of model prefix -> upstream URL`);
|
|
80
|
+
console.log(` DEFAULT_UPSTREAM Fallback upstream for unmatched models`);
|
|
81
|
+
console.log(
|
|
82
|
+
` Example: ROUTES='{"claude-":"https://api.anthropic.com","MiniMax":"https://api.minimaxi.com/anthropic"}'`
|
|
83
|
+
);
|
|
84
|
+
var openBrowser = (targetUrl) => {
|
|
85
|
+
let command;
|
|
86
|
+
switch (process.platform) {
|
|
87
|
+
case "darwin":
|
|
88
|
+
command = ["open", targetUrl];
|
|
89
|
+
break;
|
|
90
|
+
case "linux":
|
|
91
|
+
command = ["xdg-open", targetUrl];
|
|
92
|
+
break;
|
|
93
|
+
case "win32":
|
|
94
|
+
command = ["cmd", "/c", "start", targetUrl];
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
if (command === void 0) return;
|
|
100
|
+
const [bin, ...cmdArgs] = command;
|
|
101
|
+
if (bin === void 0) return;
|
|
102
|
+
spawn(bin, cmdArgs, { stdio: "ignore", detached: true });
|
|
103
|
+
};
|
|
104
|
+
if (open) {
|
|
105
|
+
openBrowser(url);
|
|
106
|
+
}
|
|
107
|
+
var bunPath = findBun();
|
|
108
|
+
if (bunPath === null) {
|
|
109
|
+
console.error("\nError: bun is not installed or not in PATH.");
|
|
110
|
+
console.error("Please install bun from https://bun.sh");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
var outputDir = __dirname;
|
|
114
|
+
var serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
115
|
+
var serverProcess = spawn(bunPath, [serverPath], {
|
|
116
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
117
|
+
detached: true
|
|
118
|
+
});
|
|
119
|
+
serverProcess.unref();
|
package/.output/nitro.json
CHANGED
package/.output/server/index.mjs
CHANGED
|
@@ -96,49 +96,49 @@ const assets = {
|
|
|
96
96
|
"/assets/alibaba-TTwafVwX.svg": {
|
|
97
97
|
"type": "image/svg+xml",
|
|
98
98
|
"etag": '"171b-6dyV5K8QjiaY35sN9qNprh9zDIs"',
|
|
99
|
-
"mtime": "2026-06-
|
|
99
|
+
"mtime": "2026-06-03T07:55:59.581Z",
|
|
100
100
|
"size": 5915,
|
|
101
101
|
"path": "../public/assets/alibaba-TTwafVwX.svg"
|
|
102
102
|
},
|
|
103
103
|
"/assets/index-B3RwBPLW.css": {
|
|
104
104
|
"type": "text/css; charset=utf-8",
|
|
105
105
|
"etag": '"10c74-aXacU4DRFVsUwcC5jHnjoPRSlTA"',
|
|
106
|
-
"mtime": "2026-06-
|
|
106
|
+
"mtime": "2026-06-03T07:55:59.583Z",
|
|
107
107
|
"size": 68724,
|
|
108
108
|
"path": "../public/assets/index-B3RwBPLW.css"
|
|
109
109
|
},
|
|
110
|
-
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
111
|
-
"type": "image/jpeg",
|
|
112
|
-
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
113
|
-
"mtime": "2026-06-03T06:11:11.192Z",
|
|
114
|
-
"size": 6918,
|
|
115
|
-
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
116
|
-
},
|
|
117
|
-
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
118
|
-
"type": "image/svg+xml",
|
|
119
|
-
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
120
|
-
"mtime": "2026-06-03T06:11:11.193Z",
|
|
121
|
-
"size": 11256,
|
|
122
|
-
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
123
|
-
},
|
|
124
110
|
"/assets/main-Cp8AM0Pa.js": {
|
|
125
111
|
"type": "text/javascript; charset=utf-8",
|
|
126
112
|
"etag": '"4db57-FpqlPRLq9OHoaAFCL2NIXtZbW5c"',
|
|
127
|
-
"mtime": "2026-06-
|
|
113
|
+
"mtime": "2026-06-03T07:55:59.583Z",
|
|
128
114
|
"size": 318295,
|
|
129
115
|
"path": "../public/assets/main-Cp8AM0Pa.js"
|
|
130
116
|
},
|
|
131
117
|
"/assets/qwen-CONDcHqt.png": {
|
|
132
118
|
"type": "image/png",
|
|
133
119
|
"etag": '"572c3-cdJAPaHdOvFCGzuaQjagdgOu6XE"',
|
|
134
|
-
"mtime": "2026-06-
|
|
120
|
+
"mtime": "2026-06-03T07:55:59.581Z",
|
|
135
121
|
"size": 357059,
|
|
136
122
|
"path": "../public/assets/qwen-CONDcHqt.png"
|
|
137
123
|
},
|
|
124
|
+
"/assets/minimax-BPMzvuL-.jpeg": {
|
|
125
|
+
"type": "image/jpeg",
|
|
126
|
+
"etag": '"1b06-IwivU89ko5UTMUM1/t7hn4sQK9A"',
|
|
127
|
+
"mtime": "2026-06-03T07:55:59.580Z",
|
|
128
|
+
"size": 6918,
|
|
129
|
+
"path": "../public/assets/minimax-BPMzvuL-.jpeg"
|
|
130
|
+
},
|
|
131
|
+
"/assets/zhipuai-BPNAnxo-.svg": {
|
|
132
|
+
"type": "image/svg+xml",
|
|
133
|
+
"etag": '"2bf8-hNaLCTi89nOFCsIIfWpP/jrfo0s"',
|
|
134
|
+
"mtime": "2026-06-03T07:55:59.581Z",
|
|
135
|
+
"size": 11256,
|
|
136
|
+
"path": "../public/assets/zhipuai-BPNAnxo-.svg"
|
|
137
|
+
},
|
|
138
138
|
"/assets/index-s4lwsWvq.js": {
|
|
139
139
|
"type": "text/javascript; charset=utf-8",
|
|
140
140
|
"etag": '"828c8-LEW/XL92J2/5lU4VKALlH7aVpaA"',
|
|
141
|
-
"mtime": "2026-06-
|
|
141
|
+
"mtime": "2026-06-03T07:55:59.583Z",
|
|
142
142
|
"size": 534728,
|
|
143
143
|
"path": "../public/assets/index-s4lwsWvq.js"
|
|
144
144
|
}
|
package/README.md
CHANGED
|
@@ -12,6 +12,20 @@ llm-inspector 是一个 LLM API 透明代理,能够捕获 AI 编程工具(Cl
|
|
|
12
12
|
|
|
13
13
|
## 核心特性 / Features
|
|
14
14
|
|
|
15
|
+
### 请求重放调试 / Request Replay
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
回放任意历史请求,快速复现问题。支持流式/非流式两种模式。
|
|
20
|
+
|
|
21
|
+
### 实时 SSE 流更新 / Real-Time SSE Streaming
|
|
22
|
+
|
|
23
|
+
SSE 流式响应实时推送,无需轮询,即时查看每个数据块。
|
|
24
|
+
|
|
25
|
+
### 健康检查 / Health Check
|
|
26
|
+
|
|
27
|
+
`GET /api/health` 端点,支持 Docker Compose 健康检查和容器编排。
|
|
28
|
+
|
|
15
29
|
### 支持多种 AI 编程工具 / Multiple AI Coding Tools
|
|
16
30
|
|
|
17
31
|
```bash
|
|
@@ -47,6 +61,23 @@ ANTHROPIC_BASE_URL=http://localhost:25947/proxy <your-tool>
|
|
|
47
61
|
|
|
48
62
|
## 快速开始 / Quick Start
|
|
49
63
|
|
|
64
|
+
### npm 安装(推荐)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install -g @tonyclaw/llm-inspector
|
|
68
|
+
|
|
69
|
+
# 启动
|
|
70
|
+
llm-inspector
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Docker
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
docker-compose up -d
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 源码运行
|
|
80
|
+
|
|
50
81
|
```bash
|
|
51
82
|
# 安装依赖
|
|
52
83
|
bun install
|
|
@@ -71,6 +102,23 @@ LLM_BASE_URL=http://localhost:25947/proxy opencode
|
|
|
71
102
|
|
|
72
103
|
---
|
|
73
104
|
|
|
105
|
+
## Docker 部署 / Docker Deployment
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# 构建并启动
|
|
109
|
+
docker-compose up -d
|
|
110
|
+
|
|
111
|
+
# 查看日志
|
|
112
|
+
docker-compose logs -f
|
|
113
|
+
|
|
114
|
+
# 停止
|
|
115
|
+
docker-compose down
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
容器默认暴露 `25947` 端口,包含健康检查,可直接接入 Docker Compose 或 Kubernetes。
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
74
122
|
## 实时仪表盘 / Real-Time Dashboard
|
|
75
123
|
|
|
76
124
|
### 日志列表 / Log List
|
|
@@ -132,7 +180,9 @@ AI Coding Tool → llm-inspector (:25947/proxy/*) → LLM Provider
|
|
|
132
180
|
## 命令行 / CLI
|
|
133
181
|
|
|
134
182
|
```bash
|
|
135
|
-
llm-inspector
|
|
183
|
+
llm-inspector # 启动服务
|
|
184
|
+
llm-inspector --port 3000 # 指定端口
|
|
185
|
+
llm-inspector --no-open # 禁止自动打开浏览器
|
|
136
186
|
```
|
|
137
187
|
|
|
138
188
|
| 参数 | 默认值 | 说明 |
|
|
@@ -192,5 +242,7 @@ git tag v<version>
|
|
|
192
242
|
git push origin main --tags
|
|
193
243
|
|
|
194
244
|
# 3. 发布到 npm
|
|
195
|
-
npm publish --access public
|
|
245
|
+
npm publish --access public
|
|
196
246
|
```
|
|
247
|
+
|
|
248
|
+
包名:`@tonyclaw/llm-inspector`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonyclaw/llm-inspector",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"ai-coding-tools"
|
|
19
19
|
],
|
|
20
20
|
"bin": {
|
|
21
|
-
"llm-inspector": "
|
|
21
|
+
"llm-inspector": ".output/cli.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
33
|
"dev": "vite dev",
|
|
34
|
-
"start": "
|
|
35
|
-
"build": "vite build",
|
|
36
|
-
"
|
|
34
|
+
"start": "node .output/cli.js",
|
|
35
|
+
"build": "vite build && bun build:cli",
|
|
36
|
+
"build:cli": "npx esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=.output/cli.js",
|
|
37
|
+
"prepublishOnly": "npm run build",
|
|
37
38
|
"typecheck": "tsc --noEmit",
|
|
38
39
|
"lint": "eslint .",
|
|
39
40
|
"format": "biome format --write .",
|
|
@@ -52,7 +53,6 @@
|
|
|
52
53
|
"@tanstack/react-start": "^1.161.0",
|
|
53
54
|
"@tanstack/react-virtual": "^3.13.26",
|
|
54
55
|
"class-variance-authority": "^0.7.1",
|
|
55
|
-
"cleye": "^2.2.1",
|
|
56
56
|
"clsx": "^2.1.1",
|
|
57
57
|
"conf": "^15.1.0",
|
|
58
58
|
"jszip": "^3.10.1",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
77
77
|
"@typescript-eslint/parser": "^8.55.0",
|
|
78
78
|
"@vitejs/plugin-react": "^5.1.4",
|
|
79
|
+
"esbuild": "^0.25.0",
|
|
79
80
|
"eslint": "^9.32.0",
|
|
80
81
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
81
82
|
"eslint-plugin-functional": "^9.0.2",
|
package/src/cli.ts
CHANGED
|
@@ -1,31 +1,88 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
2
|
-
import {
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
3
9
|
|
|
4
10
|
const DEFAULT_PORT = 25947;
|
|
5
11
|
|
|
12
|
+
// Find bun executable
|
|
13
|
+
const findBun = (): string | null => {
|
|
14
|
+
// Check if bun is in PATH
|
|
15
|
+
const pathEnv = process.env.PATH ?? "";
|
|
16
|
+
const pathDirs = pathEnv.split(process.platform === "win32" ? ";" : ":");
|
|
17
|
+
|
|
18
|
+
for (const dir of pathDirs) {
|
|
19
|
+
const bunPath = join(dir, process.platform === "win32" ? "bun.exe" : "bun");
|
|
20
|
+
if (existsSync(bunPath)) {
|
|
21
|
+
// On Windows, npm shim is a script, not the actual exe
|
|
22
|
+
if (process.platform === "win32" && !bunPath.endsWith(".exe")) {
|
|
23
|
+
// Check if it's the npm shim and find the actual exe
|
|
24
|
+
const actualPath = join(dir, "node_modules", "bun", "bin", "bun.exe");
|
|
25
|
+
if (existsSync(actualPath)) {
|
|
26
|
+
return actualPath;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return bunPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Common Windows paths
|
|
34
|
+
if (process.platform === "win32") {
|
|
35
|
+
const localAppData = process.env.LOCALAPPDATA ?? "";
|
|
36
|
+
const appData = process.env.APPDATA ?? "";
|
|
37
|
+
const userProfile = process.env.USERPROFILE ?? "";
|
|
38
|
+
|
|
39
|
+
const commonPaths = [
|
|
40
|
+
join(localAppData, "bun", "bin", "bun.exe"),
|
|
41
|
+
join(appData, "bun", "bin", "bun.exe"),
|
|
42
|
+
join(userProfile, "AppData", "Local", "bun", "bin", "bun.exe"),
|
|
43
|
+
join(userProfile, "AppData", "Roaming", "npm", "node_modules", "bun", "bin", "bun.exe"),
|
|
44
|
+
join(userProfile, "AppData", "Roaming", "npm", "bun"),
|
|
45
|
+
];
|
|
46
|
+
for (const bunPath of commonPaths) {
|
|
47
|
+
if (existsSync(bunPath)) {
|
|
48
|
+
return bunPath;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
6
56
|
const envPort = process.env["PORT"];
|
|
7
57
|
const portDefault = envPort !== undefined ? Number(envPort) : DEFAULT_PORT;
|
|
8
58
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type: Number,
|
|
14
|
-
alias: "p",
|
|
15
|
-
default: portDefault,
|
|
16
|
-
description: "Port to listen on (env: PORT)",
|
|
17
|
-
},
|
|
18
|
-
open: {
|
|
19
|
-
type: Boolean,
|
|
20
|
-
default: true,
|
|
21
|
-
description: "Open the browser on start (use --no-open to disable)",
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
});
|
|
59
|
+
// Simple argument parsing
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
let port = portDefault;
|
|
62
|
+
let open = true;
|
|
25
63
|
|
|
26
|
-
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
const arg = args[i] ?? "";
|
|
66
|
+
switch (arg) {
|
|
67
|
+
case "--port":
|
|
68
|
+
case "-p":
|
|
69
|
+
port = Number(args[i + 1]);
|
|
70
|
+
i++;
|
|
71
|
+
break;
|
|
72
|
+
case "--no-open":
|
|
73
|
+
open = false;
|
|
74
|
+
break;
|
|
75
|
+
case "--open":
|
|
76
|
+
open = true;
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
process.env["PORT"] = String(port);
|
|
27
84
|
|
|
28
|
-
const url = `http://localhost:${
|
|
85
|
+
const url = `http://localhost:${port}`;
|
|
29
86
|
|
|
30
87
|
console.log(`Server running at ${url}`);
|
|
31
88
|
console.log(` Proxy: ${url}/proxy`);
|
|
@@ -43,26 +100,48 @@ console.log(
|
|
|
43
100
|
);
|
|
44
101
|
|
|
45
102
|
const openBrowser = (targetUrl: string): void => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
103
|
+
let command: string[] | undefined;
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
|
|
105
|
+
switch (process.platform) {
|
|
106
|
+
case "darwin":
|
|
107
|
+
command = ["open", targetUrl];
|
|
108
|
+
break;
|
|
109
|
+
case "linux":
|
|
110
|
+
command = ["xdg-open", targetUrl];
|
|
111
|
+
break;
|
|
112
|
+
case "win32":
|
|
113
|
+
command = ["cmd", "/c", "start", targetUrl];
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
// Unsupported platform - do nothing
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
52
119
|
if (command === undefined) return;
|
|
53
|
-
const [bin, ...
|
|
120
|
+
const [bin, ...cmdArgs] = command;
|
|
54
121
|
if (bin === undefined) return;
|
|
55
|
-
|
|
122
|
+
spawn(bin, cmdArgs, { stdio: "ignore", detached: true });
|
|
56
123
|
};
|
|
57
124
|
|
|
58
|
-
if (
|
|
125
|
+
if (open) {
|
|
59
126
|
openBrowser(url);
|
|
60
127
|
}
|
|
61
128
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
129
|
+
// Find bun and start server
|
|
130
|
+
const bunPath = findBun();
|
|
131
|
+
if (bunPath === null) {
|
|
132
|
+
console.error("\nError: bun is not installed or not in PATH.");
|
|
133
|
+
console.error("Please install bun from https://bun.sh");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Compute server path
|
|
138
|
+
const outputDir = __dirname;
|
|
139
|
+
const serverPath = join(outputDir, "../.output/server/index.mjs");
|
|
140
|
+
|
|
141
|
+
// Start server with bun
|
|
142
|
+
const serverProcess = spawn(bunPath, [serverPath], {
|
|
143
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
144
|
+
detached: true,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
serverProcess.unref();
|