responses-proxy 0.1.4 → 0.2.1
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/cli.js +366 -92
- package/dist/client/assets/{index-DAy1ivku.js → index-D4Ktr4qL.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/mitm/cert.js +111 -0
- package/dist/mitm/dns.js +150 -0
- package/dist/mitm/server.js +229 -0
- package/dist/mitm/start-server.js +13 -0
- package/dist/package.json +1 -1
- package/dist/server.js +111 -22
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -1,76 +1,307 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* responses-proxy CLI —
|
|
3
|
+
* responses-proxy CLI — full command interface for managing the proxy.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
* responses-proxy --
|
|
8
|
-
* responses-proxy
|
|
6
|
+
* responses-proxy # Start foreground, open browser
|
|
7
|
+
* responses-proxy --background # Start as background daemon
|
|
8
|
+
* responses-proxy stop # Stop daemon
|
|
9
|
+
* responses-proxy status # Check running state
|
|
10
|
+
* responses-proxy restart # Restart daemon
|
|
11
|
+
* responses-proxy logs # Tail server logs
|
|
12
|
+
* responses-proxy config # Show current config
|
|
13
|
+
* responses-proxy config set KEY VAL # Set env config
|
|
14
|
+
* responses-proxy setup # Interactive first-time setup
|
|
15
|
+
* responses-proxy password <pass> # Set dashboard password
|
|
16
|
+
* responses-proxy info # Show connection details (endpoint + key)
|
|
9
17
|
*/
|
|
10
18
|
|
|
11
|
-
const { spawn, exec } = require("child_process");
|
|
19
|
+
const { spawn, exec, execSync } = require("child_process");
|
|
12
20
|
const path = require("path");
|
|
13
21
|
const fs = require("fs");
|
|
14
22
|
const os = require("os");
|
|
23
|
+
const readline = require("readline");
|
|
15
24
|
|
|
16
25
|
const pkg = require("./package.json");
|
|
17
26
|
const args = process.argv.slice(2);
|
|
18
27
|
|
|
19
|
-
//
|
|
28
|
+
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const dataDir = path.join(os.homedir(), ".responses-proxy");
|
|
31
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
32
|
+
const PID_FILE = path.join(dataDir, "server.pid");
|
|
33
|
+
const LOG_FILE = path.join(dataDir, "server.log");
|
|
34
|
+
const ENV_FILE = path.join(dataDir, "config.env");
|
|
35
|
+
const serverPath = path.join(__dirname, "dist", "server.js");
|
|
36
|
+
const nodeModulesPath = path.join(__dirname, "dist", "node_modules");
|
|
37
|
+
|
|
38
|
+
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function loadEnvConfig() {
|
|
41
|
+
if (!fs.existsSync(ENV_FILE)) return {};
|
|
42
|
+
const lines = fs.readFileSync(ENV_FILE, "utf8").split("\n");
|
|
43
|
+
const env = {};
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
46
|
+
if (match) env[match[1]] = match[2];
|
|
47
|
+
}
|
|
48
|
+
return env;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function saveEnvConfig(env) {
|
|
52
|
+
const lines = Object.entries(env).map(([k, v]) => `${k}=${v}`);
|
|
53
|
+
fs.writeFileSync(ENV_FILE, lines.join("\n") + "\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getConfig(key) {
|
|
57
|
+
const env = loadEnvConfig();
|
|
58
|
+
return env[key] || process.env[key] || "";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setConfig(key, value) {
|
|
62
|
+
const env = loadEnvConfig();
|
|
63
|
+
env[key] = value;
|
|
64
|
+
saveEnvConfig(env);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ─── Parse args ──────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
20
69
|
const DEFAULT_PORT = 8318;
|
|
21
|
-
let port = DEFAULT_PORT;
|
|
22
|
-
let host = "0.0.0.0";
|
|
70
|
+
let port = parseInt(getConfig("PORT")) || DEFAULT_PORT;
|
|
71
|
+
let host = getConfig("HOST") || "0.0.0.0";
|
|
23
72
|
let noBrowser = false;
|
|
73
|
+
let background = false;
|
|
74
|
+
let subcommand = null;
|
|
75
|
+
let subArgs = [];
|
|
24
76
|
|
|
25
|
-
// Parse args
|
|
26
77
|
for (let i = 0; i < args.length; i++) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
const arg = args[i];
|
|
79
|
+
if (arg === "--port" || arg === "-p") { port = parseInt(args[++i], 10) || DEFAULT_PORT; }
|
|
80
|
+
else if (arg === "--host" || arg === "-H") { host = args[++i] || "0.0.0.0"; }
|
|
81
|
+
else if (arg === "--no-browser" || arg === "-n") { noBrowser = true; }
|
|
82
|
+
else if (arg === "--background" || arg === "--bg" || arg === "-b" || arg === "--daemon") { background = true; }
|
|
83
|
+
else if (arg === "--help" || arg === "-h") { printHelp(); process.exit(0); }
|
|
84
|
+
else if (arg === "--version" || arg === "-v") { console.log(pkg.version); process.exit(0); }
|
|
85
|
+
else if (!arg.startsWith("-")) {
|
|
86
|
+
subcommand = arg;
|
|
87
|
+
subArgs = args.slice(i + 1);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printHelp() {
|
|
93
|
+
console.log(`
|
|
94
|
+
responses-proxy v${pkg.version} — AI Routing Proxy
|
|
95
|
+
|
|
96
|
+
Usage: responses-proxy [command] [options]
|
|
97
|
+
|
|
98
|
+
Commands:
|
|
99
|
+
(none) Start the server (foreground)
|
|
100
|
+
stop Stop the background daemon
|
|
101
|
+
status Check if running
|
|
102
|
+
restart Restart background daemon
|
|
103
|
+
logs Show recent server logs
|
|
104
|
+
info Show endpoint URL + API key
|
|
105
|
+
config Show all config
|
|
106
|
+
config set KEY VALUE Set a config value
|
|
107
|
+
config get KEY Get a config value
|
|
108
|
+
setup Interactive first-time setup wizard
|
|
109
|
+
password <pass> Set dashboard admin password
|
|
110
|
+
open Open dashboard in browser
|
|
40
111
|
|
|
41
112
|
Options:
|
|
42
|
-
-p, --port <port>
|
|
43
|
-
-H, --host <host>
|
|
44
|
-
-n, --no-browser
|
|
45
|
-
-
|
|
46
|
-
-
|
|
113
|
+
-p, --port <port> Port (default: ${DEFAULT_PORT})
|
|
114
|
+
-H, --host <host> Host (default: 0.0.0.0)
|
|
115
|
+
-n, --no-browser Don't auto-open browser
|
|
116
|
+
-b, --background Run as background daemon
|
|
117
|
+
-h, --help Show help
|
|
118
|
+
-v, --version Show version
|
|
119
|
+
|
|
120
|
+
Data directory: ${dataDir}
|
|
121
|
+
Config file: ${ENV_FILE}
|
|
47
122
|
`);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ─── Subcommand dispatch ─────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
if (subcommand === "stop") { stopDaemon(); process.exit(0); }
|
|
128
|
+
if (subcommand === "status") { checkStatus(); process.exit(0); }
|
|
129
|
+
if (subcommand === "restart") { stopDaemon(); background = true; }
|
|
130
|
+
if (subcommand === "logs") { showLogs(); process.exit(0); }
|
|
131
|
+
if (subcommand === "info") { showInfo(); process.exit(0); }
|
|
132
|
+
if (subcommand === "open") { openDashboard(); process.exit(0); }
|
|
133
|
+
if (subcommand === "config") { handleConfig(); process.exit(0); }
|
|
134
|
+
if (subcommand === "password") { handlePassword(); process.exit(0); }
|
|
135
|
+
if (subcommand === "setup") { runSetup().then(() => process.exit(0)); }
|
|
136
|
+
else if (subcommand && !["restart"].includes(subcommand)) {
|
|
137
|
+
console.error(`Unknown command: ${subcommand}\nRun 'responses-proxy --help' for usage.`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Subcommand implementations ──────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
function getDaemonPid() {
|
|
144
|
+
try {
|
|
145
|
+
if (!fs.existsSync(PID_FILE)) return null;
|
|
146
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
147
|
+
if (!pid) return null;
|
|
148
|
+
process.kill(pid, 0);
|
|
149
|
+
return pid;
|
|
150
|
+
} catch {
|
|
151
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
152
|
+
return null;
|
|
52
153
|
}
|
|
53
154
|
}
|
|
54
155
|
|
|
55
|
-
|
|
56
|
-
const
|
|
156
|
+
function stopDaemon() {
|
|
157
|
+
const pid = getDaemonPid();
|
|
158
|
+
if (!pid) { console.log("⏹ Not running."); return; }
|
|
159
|
+
try {
|
|
160
|
+
process.kill(pid, "SIGTERM");
|
|
161
|
+
console.log(`⏹ Stopped (PID: ${pid})`);
|
|
162
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
163
|
+
} catch (e) { console.error(`Failed: ${e.message}`); }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function checkStatus() {
|
|
167
|
+
const pid = getDaemonPid();
|
|
168
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
169
|
+
if (pid) {
|
|
170
|
+
console.log(`✅ Running (PID: ${pid})`);
|
|
171
|
+
console.log(` Endpoint: http://${displayHost}:${port}/v1`);
|
|
172
|
+
console.log(` Dashboard: http://${displayHost}:${port}/`);
|
|
173
|
+
console.log(` Data: ${dataDir}`);
|
|
174
|
+
} else {
|
|
175
|
+
console.log("⏹ Not running.");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function showLogs() {
|
|
180
|
+
if (!fs.existsSync(LOG_FILE)) { console.log("No logs yet."); return; }
|
|
181
|
+
const lines = fs.readFileSync(LOG_FILE, "utf8").split("\n");
|
|
182
|
+
console.log(lines.slice(-80).join("\n"));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function showInfo() {
|
|
186
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
187
|
+
const password = getConfig("DASHBOARD_PASSWORD") || "admin";
|
|
188
|
+
console.log(`
|
|
189
|
+
┌─────────────────────────────────────────┐
|
|
190
|
+
│ responses-proxy v${pkg.version.padEnd(25)}│
|
|
191
|
+
├─────────────────────────────────────────┤
|
|
192
|
+
│ Endpoint: http://${displayHost}:${port}/v1${" ".repeat(Math.max(0, 18 - String(port).length))}│
|
|
193
|
+
│ Dashboard: http://${displayHost}:${port}/${" ".repeat(Math.max(0, 20 - String(port).length))}│
|
|
194
|
+
│ Password: ${password.padEnd(27)}│
|
|
195
|
+
├─────────────────────────────────────────┤
|
|
196
|
+
│ For Claude Code: │
|
|
197
|
+
│ export ANTHROPIC_BASE_URL=http://${displayHost}:${port}/v1
|
|
198
|
+
│ export ANTHROPIC_API_KEY=sk_anything │
|
|
199
|
+
├─────────────────────────────────────────┤
|
|
200
|
+
│ For Codex / OpenAI SDK: │
|
|
201
|
+
│ base_url = http://${displayHost}:${port}/v1${" ".repeat(Math.max(0, 18 - String(port).length))}│
|
|
202
|
+
│ api_key = sk_anything │
|
|
203
|
+
└─────────────────────────────────────────┘
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function openDashboard() {
|
|
208
|
+
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
209
|
+
const url = `http://${displayHost}:${port}`;
|
|
210
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
211
|
+
exec(cmd, { windowsHide: true }, () => {});
|
|
212
|
+
console.log(`Opening ${url}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function handleConfig() {
|
|
216
|
+
if (subArgs[0] === "set" && subArgs[1]) {
|
|
217
|
+
setConfig(subArgs[1], subArgs.slice(2).join(" "));
|
|
218
|
+
console.log(`✅ ${subArgs[1]} = ${subArgs.slice(2).join(" ")}`);
|
|
219
|
+
} else if (subArgs[0] === "get" && subArgs[1]) {
|
|
220
|
+
console.log(getConfig(subArgs[1]) || "(not set)");
|
|
221
|
+
} else {
|
|
222
|
+
const env = loadEnvConfig();
|
|
223
|
+
console.log(`Config file: ${ENV_FILE}\n`);
|
|
224
|
+
if (Object.keys(env).length === 0) {
|
|
225
|
+
console.log(" (empty — using defaults)");
|
|
226
|
+
console.log(`\n Default port: ${DEFAULT_PORT}`);
|
|
227
|
+
console.log(` Default password: admin`);
|
|
228
|
+
console.log(`\n Set values with: responses-proxy config set KEY VALUE`);
|
|
229
|
+
} else {
|
|
230
|
+
for (const [k, v] of Object.entries(env)) {
|
|
231
|
+
console.log(` ${k} = ${v}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
console.log(`\nAvailable keys:`);
|
|
235
|
+
console.log(` PORT Server port (default: 8318)`);
|
|
236
|
+
console.log(` HOST Bind host (default: 0.0.0.0)`);
|
|
237
|
+
console.log(` DASHBOARD_PASSWORD Admin password (default: admin)`);
|
|
238
|
+
console.log(` UPSTREAM_BASE_URL Default upstream provider URL`);
|
|
239
|
+
console.log(` UPSTREAM_API_KEY Default upstream API key`);
|
|
240
|
+
console.log(` KIRO_ENABLED Enable Kiro provider (true/false)`);
|
|
241
|
+
console.log(` KIRO_DB_PATH Path to 9router kiro.sqlite`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function handlePassword() {
|
|
246
|
+
const pass = subArgs[0];
|
|
247
|
+
if (!pass) {
|
|
248
|
+
console.log(`Current password: ${getConfig("DASHBOARD_PASSWORD") || "admin"}`);
|
|
249
|
+
console.log(`\nUsage: responses-proxy password <new-password>`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
setConfig("DASHBOARD_PASSWORD", pass);
|
|
253
|
+
console.log(`✅ Dashboard password set to: ${pass}`);
|
|
254
|
+
console.log(` Restart the server for changes to take effect.`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function runSetup() {
|
|
258
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
259
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
260
|
+
|
|
261
|
+
console.log(`\n🚀 responses-proxy Setup Wizard\n`);
|
|
262
|
+
|
|
263
|
+
const portAnswer = await ask(`Port [${port}]: `);
|
|
264
|
+
if (portAnswer.trim()) setConfig("PORT", portAnswer.trim());
|
|
265
|
+
|
|
266
|
+
const passAnswer = await ask(`Dashboard password [admin]: `);
|
|
267
|
+
if (passAnswer.trim()) setConfig("DASHBOARD_PASSWORD", passAnswer.trim());
|
|
268
|
+
|
|
269
|
+
const kiroAnswer = await ask(`Enable Kiro provider? (y/n) [y]: `);
|
|
270
|
+
if (kiroAnswer.trim().toLowerCase() === "n") setConfig("KIRO_ENABLED", "false");
|
|
271
|
+
else setConfig("KIRO_ENABLED", "true");
|
|
272
|
+
|
|
273
|
+
const upstreamAnswer = await ask(`Default upstream URL (leave empty to skip): `);
|
|
274
|
+
if (upstreamAnswer.trim()) setConfig("UPSTREAM_BASE_URL", upstreamAnswer.trim());
|
|
275
|
+
|
|
276
|
+
const keyAnswer = await ask(`Default upstream API key (leave empty to skip): `);
|
|
277
|
+
if (keyAnswer.trim()) setConfig("UPSTREAM_API_KEY", keyAnswer.trim());
|
|
278
|
+
|
|
279
|
+
rl.close();
|
|
280
|
+
|
|
281
|
+
console.log(`\n✅ Config saved to ${ENV_FILE}`);
|
|
282
|
+
console.log(`\nStart the server with:`);
|
|
283
|
+
console.log(` responses-proxy`);
|
|
284
|
+
console.log(` responses-proxy --background`);
|
|
285
|
+
console.log(`\nView connection info:`);
|
|
286
|
+
console.log(` responses-proxy info\n`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ─── If subcommand was handled above (setup is async), bail ──────────────────
|
|
290
|
+
if (subcommand === "setup") { /* runSetup handles exit */ return; }
|
|
291
|
+
if (subcommand && subcommand !== "restart") process.exit(0);
|
|
292
|
+
|
|
293
|
+
// ─── Ensure deps ─────────────────────────────────────────────────────────────
|
|
57
294
|
|
|
58
295
|
if (!fs.existsSync(serverPath)) {
|
|
59
|
-
console.error("Error: Built server not found
|
|
60
|
-
console.error("Run 'npm run build' first, or reinstall the package.");
|
|
296
|
+
console.error("Error: Built server not found. Reinstall the package.");
|
|
61
297
|
process.exit(1);
|
|
62
298
|
}
|
|
63
299
|
|
|
64
|
-
// Check if deps are installed
|
|
65
|
-
const nodeModulesPath = path.join(__dirname, "dist", "node_modules");
|
|
66
300
|
if (!fs.existsSync(path.join(nodeModulesPath, "fastify"))) {
|
|
67
|
-
console.log("📦
|
|
68
|
-
const { execSync } = require("child_process");
|
|
301
|
+
console.log("📦 Installing runtime dependencies...");
|
|
69
302
|
try {
|
|
70
303
|
execSync("npm install --omit=dev --no-audit --no-fund --loglevel=error", {
|
|
71
|
-
cwd: path.join(__dirname, "dist"),
|
|
72
|
-
stdio: "inherit",
|
|
73
|
-
timeout: 120000,
|
|
304
|
+
cwd: path.join(__dirname, "dist"), stdio: "inherit", timeout: 120000,
|
|
74
305
|
});
|
|
75
306
|
} catch {
|
|
76
307
|
console.error("Failed to install deps. Run: cd " + path.join(__dirname, "dist") + " && npm install --omit=dev");
|
|
@@ -78,69 +309,112 @@ if (!fs.existsSync(path.join(nodeModulesPath, "fastify"))) {
|
|
|
78
309
|
}
|
|
79
310
|
}
|
|
80
311
|
|
|
81
|
-
//
|
|
82
|
-
|
|
312
|
+
// ─── Kill existing ───────────────────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
const existingPid = getDaemonPid();
|
|
315
|
+
if (existingPid) {
|
|
316
|
+
try { process.kill(existingPid, "SIGTERM"); } catch {}
|
|
317
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
318
|
+
try { execSync("sleep 1", { stdio: "ignore" }); } catch {}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Build env ───────────────────────────────────────────────────────────────
|
|
322
|
+
|
|
83
323
|
fs.mkdirSync(path.join(dataDir, "sessions"), { recursive: true });
|
|
324
|
+
const userConfig = loadEnvConfig();
|
|
325
|
+
|
|
326
|
+
const serverEnv = {
|
|
327
|
+
...process.env,
|
|
328
|
+
...userConfig,
|
|
329
|
+
NODE_PATH: nodeModulesPath,
|
|
330
|
+
PORT: String(port),
|
|
331
|
+
HOST: host,
|
|
332
|
+
UPSTREAM_BASE_URL: userConfig.UPSTREAM_BASE_URL || process.env.UPSTREAM_BASE_URL || "https://placeholder.invalid",
|
|
333
|
+
UPSTREAM_API_KEY: userConfig.UPSTREAM_API_KEY || process.env.UPSTREAM_API_KEY || "",
|
|
334
|
+
APP_DB_PATH: path.join(dataDir, "app.sqlite"),
|
|
335
|
+
SESSION_LOG_DIR: path.join(dataDir, "sessions"),
|
|
336
|
+
CUSTOMER_KEY_DB_PATH: path.join(dataDir, "telegram-bot.sqlite"),
|
|
337
|
+
KIRO_DB_PATH: userConfig.KIRO_DB_PATH || path.join(dataDir, "kiro.sqlite"),
|
|
338
|
+
KIRO_ENABLED: userConfig.KIRO_ENABLED || process.env.KIRO_ENABLED || "true",
|
|
339
|
+
QUICK_APPLY_HERMES_CONFIG_PATH: path.join(os.homedir(), ".hermes", "config.yaml"),
|
|
340
|
+
QUICK_APPLY_CODEX_CONFIG_PATH: path.join(os.homedir(), ".codex", "config.toml"),
|
|
341
|
+
QUICK_APPLY_CODEX_AUTH_PATH: path.join(os.homedir(), ".codex", "auth.json"),
|
|
342
|
+
TELEGRAM_BOT_TOKEN: userConfig.TELEGRAM_BOT_TOKEN || "",
|
|
343
|
+
TELEGRAM_OWNER_USER_IDS: userConfig.TELEGRAM_OWNER_USER_IDS || "",
|
|
344
|
+
TELEGRAM_ADMIN_USER_IDS: userConfig.TELEGRAM_ADMIN_USER_IDS || "",
|
|
345
|
+
DASHBOARD_PASSWORD: userConfig.DASHBOARD_PASSWORD || process.env.DASHBOARD_PASSWORD || "admin",
|
|
346
|
+
};
|
|
84
347
|
|
|
85
348
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
86
349
|
const url = `http://${displayHost}:${port}`;
|
|
87
350
|
|
|
88
|
-
|
|
351
|
+
// ─── Background mode ─────────────────────────────────────────────────────────
|
|
352
|
+
|
|
353
|
+
if (background) {
|
|
354
|
+
const logFd = fs.openSync(LOG_FILE, "a");
|
|
355
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
356
|
+
cwd: __dirname, env: serverEnv,
|
|
357
|
+
stdio: ["ignore", logFd, logFd],
|
|
358
|
+
detached: true,
|
|
359
|
+
});
|
|
360
|
+
child.unref();
|
|
361
|
+
fs.writeFileSync(PID_FILE, String(child.pid));
|
|
362
|
+
fs.closeSync(logFd);
|
|
363
|
+
|
|
364
|
+
console.log(`
|
|
89
365
|
┌─────────────────────────────────────────┐
|
|
90
366
|
│ responses-proxy v${pkg.version.padEnd(25)}│
|
|
367
|
+
│ Running in background (PID: ${String(child.pid).padEnd(10)}│
|
|
91
368
|
│ ${url.padEnd(39)}│
|
|
92
369
|
│ Dashboard: ${(url + "/").padEnd(27)}│
|
|
93
370
|
│ API: ${(url + "/v1").padEnd(33)}│
|
|
371
|
+
├─────────────────────────────────────────┤
|
|
372
|
+
│ Stop: responses-proxy stop │
|
|
373
|
+
│ Status: responses-proxy status │
|
|
374
|
+
│ Logs: responses-proxy logs │
|
|
375
|
+
│ Config: responses-proxy config │
|
|
376
|
+
│ Info: responses-proxy info │
|
|
94
377
|
└─────────────────────────────────────────┘
|
|
95
378
|
`);
|
|
96
379
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...process.env,
|
|
103
|
-
NODE_PATH: nodeModulesPath,
|
|
104
|
-
PORT: String(port),
|
|
105
|
-
HOST: host,
|
|
106
|
-
UPSTREAM_BASE_URL: process.env.UPSTREAM_BASE_URL || "https://placeholder.invalid",
|
|
107
|
-
UPSTREAM_API_KEY: process.env.UPSTREAM_API_KEY || "",
|
|
108
|
-
APP_DB_PATH: path.join(dataDir, "app.sqlite"),
|
|
109
|
-
SESSION_LOG_DIR: path.join(dataDir, "sessions"),
|
|
110
|
-
CUSTOMER_KEY_DB_PATH: path.join(dataDir, "telegram-bot.sqlite"),
|
|
111
|
-
KIRO_DB_PATH: path.join(dataDir, "kiro.sqlite"),
|
|
112
|
-
KIRO_ENABLED: process.env.KIRO_ENABLED || "true",
|
|
113
|
-
QUICK_APPLY_HERMES_CONFIG_PATH: path.join(os.homedir(), ".hermes", "config.yaml"),
|
|
114
|
-
QUICK_APPLY_CODEX_CONFIG_PATH: path.join(os.homedir(), ".codex", "config.toml"),
|
|
115
|
-
QUICK_APPLY_CODEX_AUTH_PATH: path.join(os.homedir(), ".codex", "auth.json"),
|
|
116
|
-
TELEGRAM_BOT_TOKEN: "",
|
|
117
|
-
TELEGRAM_OWNER_USER_IDS: "",
|
|
118
|
-
TELEGRAM_ADMIN_USER_IDS: "",
|
|
119
|
-
DASHBOARD_PASSWORD: process.env.DASHBOARD_PASSWORD || "admin",
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Open browser after short delay
|
|
124
|
-
if (!noBrowser) {
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
const openCmd =
|
|
127
|
-
process.platform === "darwin" ? `open "${url}"` :
|
|
128
|
-
process.platform === "win32" ? `start "" "${url}"` :
|
|
129
|
-
`xdg-open "${url}"`;
|
|
130
|
-
exec(openCmd, { windowsHide: true }, () => {});
|
|
131
|
-
}, 2000);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Handle exit
|
|
135
|
-
function cleanup() {
|
|
136
|
-
if (server.pid) {
|
|
137
|
-
try { process.kill(server.pid, "SIGTERM"); } catch {}
|
|
380
|
+
if (!noBrowser) {
|
|
381
|
+
setTimeout(() => {
|
|
382
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
383
|
+
exec(cmd, { windowsHide: true }, () => {});
|
|
384
|
+
}, 2000);
|
|
138
385
|
}
|
|
139
|
-
|
|
386
|
+
setTimeout(() => process.exit(0), 2500);
|
|
387
|
+
|
|
388
|
+
} else {
|
|
389
|
+
// ─── Foreground mode ───────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
console.log(`
|
|
392
|
+
┌─────────────────────────────────────────┐
|
|
393
|
+
│ responses-proxy v${pkg.version.padEnd(25)}│
|
|
394
|
+
│ ${url.padEnd(39)}│
|
|
395
|
+
│ Dashboard: ${(url + "/").padEnd(27)}│
|
|
396
|
+
│ API: ${(url + "/v1").padEnd(33)}│
|
|
397
|
+
│ Press Ctrl+C to stop │
|
|
398
|
+
└─────────────────────────────────────────┘
|
|
399
|
+
`);
|
|
400
|
+
|
|
401
|
+
const server = spawn(process.execPath, [serverPath], {
|
|
402
|
+
cwd: __dirname, stdio: "inherit", env: serverEnv,
|
|
403
|
+
});
|
|
404
|
+
fs.writeFileSync(PID_FILE, String(server.pid));
|
|
140
405
|
|
|
141
|
-
|
|
142
|
-
|
|
406
|
+
if (!noBrowser) {
|
|
407
|
+
setTimeout(() => {
|
|
408
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
409
|
+
exec(cmd, { windowsHide: true }, () => {});
|
|
410
|
+
}, 2000);
|
|
411
|
+
}
|
|
143
412
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
413
|
+
function cleanup() {
|
|
414
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
415
|
+
if (server.pid) { try { process.kill(server.pid, "SIGTERM"); } catch {} }
|
|
416
|
+
}
|
|
417
|
+
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
418
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
419
|
+
server.on("close", (code) => { try { fs.unlinkSync(PID_FILE); } catch {} process.exit(code || 0); });
|
|
420
|
+
}
|