@dev-anywhere/proxy 0.1.0 → 0.1.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/README.md +21 -13
- package/dist/chunk-CDKXSDAV.js +36 -0
- package/dist/chunk-CDKXSDAV.js.map +1 -0
- package/dist/{chunk-QWPI6YON.js → chunk-DFLQ3TFT.js} +137 -40
- package/dist/chunk-DFLQ3TFT.js.map +1 -0
- package/dist/{chunk-JPJMOVQ5.js → chunk-QXOARRC2.js} +2 -2
- package/dist/{chunk-WXWH6L7J.js → chunk-TG7JPHE5.js} +2 -2
- package/dist/index.js +46 -18
- package/dist/index.js.map +1 -1
- package/dist/serve.js +111 -104
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +2 -2
- package/dist/{terminal-ZPPKNAL4.js → terminal-GIU6MXOR.js} +14 -6
- package/dist/terminal-GIU6MXOR.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-7PXDRNLY.js +0 -34
- package/dist/chunk-7PXDRNLY.js.map +0 -1
- package/dist/chunk-QWPI6YON.js.map +0 -1
- package/dist/terminal-ZPPKNAL4.js.map +0 -1
- /package/dist/{chunk-JPJMOVQ5.js.map → chunk-QXOARRC2.js.map} +0 -0
- /package/dist/{chunk-WXWH6L7J.js.map → chunk-TG7JPHE5.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-
|
|
3
|
+
daemonRelayArgs,
|
|
4
|
+
setDesiredDaemonRelay
|
|
5
|
+
} from "./chunk-CDKXSDAV.js";
|
|
6
6
|
import {
|
|
7
7
|
spawnScript
|
|
8
8
|
} from "./chunk-ZUWAB67J.js";
|
|
9
9
|
import {
|
|
10
10
|
CONFIG_PATH,
|
|
11
11
|
PID_PATH,
|
|
12
|
+
PROFILE_NAME,
|
|
12
13
|
SERVICE_LOG_PATH,
|
|
13
14
|
SOCK_PATH,
|
|
14
15
|
STOPPED_PATH,
|
|
15
16
|
createIpcReader,
|
|
17
|
+
ensureProfileWorkspace,
|
|
16
18
|
initWorkspace,
|
|
17
19
|
isInitialized,
|
|
18
20
|
serializeIpc
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-DFLQ3TFT.js";
|
|
20
22
|
|
|
21
23
|
// src/index.ts
|
|
22
24
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -34,6 +36,25 @@ function normalizeCliArgs(args) {
|
|
|
34
36
|
}
|
|
35
37
|
return normalized;
|
|
36
38
|
}
|
|
39
|
+
function stripProxyProfileArgs(args) {
|
|
40
|
+
const result = [];
|
|
41
|
+
for (let i = 0; i < args.length; i++) {
|
|
42
|
+
const arg = args[i];
|
|
43
|
+
if (arg === "claude" || arg === "codex") {
|
|
44
|
+
result.push(...args.slice(i));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
if (arg === "--profile") {
|
|
48
|
+
i++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg.startsWith("--profile=")) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
result.push(arg);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
37
58
|
function extractAgentInvocation(args) {
|
|
38
59
|
const [agent, ...providerArgs] = args;
|
|
39
60
|
if (agent !== "claude" && agent !== "codex") {
|
|
@@ -72,6 +93,7 @@ function showStatus() {
|
|
|
72
93
|
lines++;
|
|
73
94
|
};
|
|
74
95
|
if (!existsSync(PID_PATH)) {
|
|
96
|
+
log(`Profile: ${PROFILE_NAME}`);
|
|
75
97
|
log("Service: not running");
|
|
76
98
|
resolve(lines);
|
|
77
99
|
return;
|
|
@@ -88,19 +110,22 @@ function showStatus() {
|
|
|
88
110
|
resolve(lines);
|
|
89
111
|
return;
|
|
90
112
|
}
|
|
113
|
+
log(`Profile: ${PROFILE_NAME}`);
|
|
91
114
|
log(`Service: running (PID ${pid})`);
|
|
92
115
|
log(`Socket: ${SOCK_PATH}`);
|
|
93
116
|
log(`Log: ${SERVICE_LOG_PATH}`);
|
|
94
117
|
const sock = connect(SOCK_PATH);
|
|
95
118
|
sock.on("error", () => {
|
|
96
119
|
log("Sessions: unable to connect");
|
|
120
|
+
sock.destroy();
|
|
97
121
|
resolve(lines);
|
|
98
122
|
});
|
|
99
123
|
sock.on("connect", () => {
|
|
100
124
|
createIpcReader(sock, (msg) => {
|
|
101
125
|
if (msg.type === "service_status_response") {
|
|
102
126
|
const config = msg.config;
|
|
103
|
-
log(`
|
|
127
|
+
log(`Daemon: profile ${config.profile ?? PROFILE_NAME}`);
|
|
128
|
+
log(`Relay: ${config.relayName} (${config.relayNameSource})`);
|
|
104
129
|
log(`Config: relay ${config.relayUrl ?? "(unset)"} (${config.relayUrlSource})`);
|
|
105
130
|
const relay = msg.relay;
|
|
106
131
|
if (!relay) {
|
|
@@ -152,6 +177,7 @@ async function waitForServeReady(timeoutMs) {
|
|
|
152
177
|
return false;
|
|
153
178
|
}
|
|
154
179
|
async function startDaemon(options) {
|
|
180
|
+
ensureProfileWorkspace();
|
|
155
181
|
if (existsSync(PID_PATH)) {
|
|
156
182
|
const pid = parseInt(readFileSync(PID_PATH, "utf-8").trim(), 10);
|
|
157
183
|
try {
|
|
@@ -162,8 +188,9 @@ async function startDaemon(options) {
|
|
|
162
188
|
}
|
|
163
189
|
}
|
|
164
190
|
if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);
|
|
165
|
-
const serveArgs =
|
|
191
|
+
const serveArgs = ["--profile", PROFILE_NAME, ...daemonRelayArgs(options?.relayName)];
|
|
166
192
|
const child = spawnScript(new URL("./serve", import.meta.url), serveArgs, {
|
|
193
|
+
env: { ...process.env },
|
|
167
194
|
stdio: ["ignore", "ignore", "pipe"],
|
|
168
195
|
unref: false
|
|
169
196
|
});
|
|
@@ -205,15 +232,15 @@ async function startDaemon(options) {
|
|
|
205
232
|
}
|
|
206
233
|
process.exit(1);
|
|
207
234
|
}
|
|
208
|
-
var program = new Command("dev-anywhere").description("Dev Anywhere - transparent local AI CLI proxy with remote control").version(pkg.version).allowUnknownOption().allowExcessArguments().action(async () => {
|
|
235
|
+
var program = new Command("dev-anywhere").description("Dev Anywhere - transparent local AI CLI proxy with remote control").version(pkg.version).option("--profile <name>", "Use an isolated local proxy profile").allowUnknownOption().allowExcessArguments().action(async () => {
|
|
209
236
|
if (!isInitialized()) {
|
|
210
237
|
console.error(`Dev Anywhere is not initialized. Run "dev-anywhere init" first.`);
|
|
211
238
|
process.exit(1);
|
|
212
239
|
}
|
|
213
|
-
const { startTerminal } = await import("./terminal-
|
|
240
|
+
const { startTerminal } = await import("./terminal-GIU6MXOR.js");
|
|
214
241
|
let invocation;
|
|
215
242
|
try {
|
|
216
|
-
invocation = extractAgentInvocation(
|
|
243
|
+
invocation = extractAgentInvocation(cliArgsWithoutProfile);
|
|
217
244
|
} catch (err) {
|
|
218
245
|
console.error(err instanceof Error ? err.message : String(err));
|
|
219
246
|
process.exit(1);
|
|
@@ -221,26 +248,26 @@ var program = new Command("dev-anywhere").description("Dev Anywhere - transparen
|
|
|
221
248
|
const { provider, args } = invocation;
|
|
222
249
|
await startTerminal(args, provider);
|
|
223
250
|
});
|
|
224
|
-
var serve = new Command("serve").description("Manage the dev-anywhere background service").option("-d, --daemon", "Run in background").action(async (opts) => {
|
|
251
|
+
var serve = new Command("serve").description("Manage the dev-anywhere background service").option("--profile <name>", "Use an isolated local proxy profile").option("-d, --daemon", "Run in background").action(async (opts) => {
|
|
225
252
|
if (!isInitialized()) {
|
|
226
253
|
console.error(`Dev Anywhere is not initialized. Run "dev-anywhere init" first.`);
|
|
227
254
|
process.exit(1);
|
|
228
255
|
}
|
|
229
256
|
if (opts.daemon) {
|
|
230
|
-
|
|
257
|
+
setDesiredDaemonRelay(void 0);
|
|
231
258
|
await startDaemon();
|
|
232
259
|
} else {
|
|
233
260
|
const { startService } = await import("./serve.js");
|
|
234
261
|
await startService();
|
|
235
262
|
}
|
|
236
263
|
});
|
|
237
|
-
serve.command("start").description("Start the background service").option("--
|
|
264
|
+
serve.command("start").description("Start the background service").option("--relay <name>", "Use a named relay from config").action(async (opts) => {
|
|
238
265
|
if (!isInitialized()) {
|
|
239
266
|
console.error(`Dev Anywhere is not initialized. Run "dev-anywhere init" first.`);
|
|
240
267
|
process.exit(1);
|
|
241
268
|
}
|
|
242
|
-
|
|
243
|
-
await startDaemon({
|
|
269
|
+
setDesiredDaemonRelay(opts.relay);
|
|
270
|
+
await startDaemon({ relayName: opts.relay });
|
|
244
271
|
});
|
|
245
272
|
serve.command("status").description("Show service status and active sessions").option("-w, --watch", "Continuous monitoring mode").option("-n, --interval <seconds>", "Refresh interval in seconds", "2").action(async (opts) => {
|
|
246
273
|
if (opts.watch) {
|
|
@@ -259,10 +286,10 @@ serve.command("status").description("Show service status and active sessions").o
|
|
|
259
286
|
serve.command("stop").description("Stop the background service").action(() => {
|
|
260
287
|
stopService();
|
|
261
288
|
});
|
|
262
|
-
serve.command("restart").description("Restart the background service").option("--
|
|
263
|
-
|
|
289
|
+
serve.command("restart").description("Restart the background service").option("--relay <name>", "Use a named relay from config").action(async (opts) => {
|
|
290
|
+
setDesiredDaemonRelay(opts.relay);
|
|
264
291
|
stopService();
|
|
265
|
-
await startDaemon({
|
|
292
|
+
await startDaemon({ relayName: opts.relay });
|
|
266
293
|
});
|
|
267
294
|
program.addCommand(serve);
|
|
268
295
|
program.command("init").description("Initialize dev-anywhere workspace (~/.dev-anywhere)").action(() => {
|
|
@@ -275,5 +302,6 @@ program.command("init").description("Initialize dev-anywhere workspace (~/.dev-a
|
|
|
275
302
|
console.log(`Edit ${CONFIG_PATH} to configure relay server URL.`);
|
|
276
303
|
});
|
|
277
304
|
var cliArgs = normalizeCliArgs(process.argv.slice(2));
|
|
278
|
-
|
|
305
|
+
var cliArgsWithoutProfile = stripProxyProfileArgs(cliArgs);
|
|
306
|
+
program.parse(cliArgsWithoutProfile, { from: "user" });
|
|
279
307
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cli-args.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { Command } from \"commander\";\nimport {\n PID_PATH,\n SOCK_PATH,\n STOPPED_PATH,\n SERVICE_LOG_PATH,\n CONFIG_PATH,\n isInitialized,\n initWorkspace,\n} from \"./common/paths.js\";\nimport { spawnScript } from \"./common/env.js\";\nimport { daemonEnvArgs, setDesiredDaemonEnv } from \"./common/daemon-env.js\";\nimport { createIpcReader, serializeIpc } from \"./ipc/ipc-protocol.js\";\nimport { extractAgentInvocation, normalizeCliArgs } from \"./cli-args.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\")) as {\n version: string;\n};\n\nfunction stopService(): boolean {\n if (!existsSync(PID_PATH)) {\n console.error(\"Service is not running (no PID file)\");\n return false;\n }\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, \"SIGTERM\");\n console.log(`Service stopped (PID ${pid})`);\n } catch {\n console.error(`Process ${pid} not found, cleaning up stale files`);\n }\n if (existsSync(PID_PATH)) unlinkSync(PID_PATH);\n if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH);\n writeFileSync(STOPPED_PATH, String(Date.now()));\n return true;\n}\n\nfunction showStatus(): Promise<number> {\n return new Promise((resolve) => {\n let lines = 0;\n const log = (s: string) => {\n console.log(s);\n lines++;\n };\n\n if (!existsSync(PID_PATH)) {\n log(\"Service: not running\");\n resolve(lines);\n return;\n }\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n let alive = false;\n try {\n process.kill(pid, 0);\n alive = true;\n } catch {\n // process.kill(pid, 0) 抛错表示进程不存在\n }\n\n if (!alive) {\n log(\"Service: dead (stale PID file)\");\n resolve(lines);\n return;\n }\n\n log(`Service: running (PID ${pid})`);\n log(`Socket: ${SOCK_PATH}`);\n log(`Log: ${SERVICE_LOG_PATH}`);\n\n const sock = connect(SOCK_PATH);\n sock.on(\"error\", () => {\n log(\"Sessions: unable to connect\");\n resolve(lines);\n });\n sock.on(\"connect\", () => {\n createIpcReader(sock, (msg) => {\n if (msg.type === \"service_status_response\") {\n const config = msg.config;\n log(`Env: ${config.envName ?? \"single\"} (${config.envNameSource})`);\n log(`Config: relay ${config.relayUrl ?? \"(unset)\"} (${config.relayUrlSource})`);\n const relay = msg.relay;\n if (!relay) {\n log(\"Relay: not configured\");\n } else if (relay.connected) {\n log(`Relay: connected (proxy: ${relay.proxyId})`);\n log(\n ` queue depth: ${relay.queueDepth}, reconnect attempts: ${relay.reconnectAttempt}`,\n );\n } else {\n log(\n `Relay: disconnected (proxy: ${relay.proxyId}, reconnecting: attempt ${relay.reconnectAttempt}, queued: ${relay.queueDepth})`,\n );\n }\n log(\"\");\n\n // 显示会话列表\n const sessions = msg.sessions;\n if (sessions.length === 0) {\n log(\"Sessions: none\");\n } else {\n log(`Sessions: ${sessions.length}`);\n for (const s of sessions) {\n log(` ${s.id} ${s.mode} ${s.state} worker: ${s.hasWorker ? \"yes\" : \"no\"}`);\n }\n }\n sock.destroy();\n resolve(lines);\n }\n });\n sock.write(serializeIpc({ type: \"service_status_request\" }));\n });\n });\n}\n\nconst DAEMON_STARTUP_TIMEOUT_MS = 30_000;\nconst DAEMON_STARTUP_POLL_MS = 200;\n\n// 轮询 SOCK_PATH 直到可连接,作为 serve 的 readiness 信号。\n// serve.ts 里 server.listen(SOCK_PATH) 是启动序列的最后一步,连上即代表 ready。\nasync function waitForServeReady(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const connected = await new Promise<boolean>((resolve) => {\n const sock = connect(SOCK_PATH);\n sock.once(\"connect\", () => {\n sock.destroy();\n resolve(true);\n });\n sock.once(\"error\", () => resolve(false));\n });\n if (connected) return true;\n await sleep(DAEMON_STARTUP_POLL_MS);\n }\n return false;\n}\n\nasync function startDaemon(options?: { envName?: string }): Promise<void> {\n if (existsSync(PID_PATH)) {\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, 0);\n console.error(`Service is already running (PID ${pid})`);\n return;\n } catch {\n // process.kill(pid, 0) 抛错表示进程不存在,继续启动\n }\n }\n if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);\n\n // stderr 走 pipe 由父 CLI 订阅:子进程 ready 前(pino logger 未接管)的启动错误\n // 会被捕获;ready 后父 detach,pino 接管所有输出到 service.log。\n // start 命令必须等 daemon socket 可连接后再退出;否则用户会看到“启动成功”,实际服务还没就绪。\n const serveArgs = daemonEnvArgs(options?.envName);\n const child = spawnScript(new URL(\"./serve\", import.meta.url), serveArgs, {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n unref: false,\n });\n\n const stderrChunks: Buffer[] = [];\n child.stderr!.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n // race: readiness handshake vs. 子进程先挂。子进程 ready 前就 exit 说明启动硬失败,\n // 不必再等到 30s 超时才报错。\n type Outcome =\n | { kind: \"ready\" }\n | { kind: \"timeout\" }\n | { kind: \"exited\"; code: number | null; signal: NodeJS.Signals | null };\n\n const readyOutcome: Promise<Outcome> = waitForServeReady(DAEMON_STARTUP_TIMEOUT_MS).then((ok) =>\n ok ? { kind: \"ready\" as const } : { kind: \"timeout\" as const },\n );\n const exitOutcome: Promise<Outcome> = new Promise((resolve) => {\n // 设 listener 前已经 exit 的边界:Node 记在 exitCode 上\n if (child.exitCode !== null) {\n resolve({ kind: \"exited\", code: child.exitCode, signal: child.signalCode });\n return;\n }\n child.once(\"exit\", (code, signal) => resolve({ kind: \"exited\", code, signal }));\n });\n\n const result = await Promise.race([readyOutcome, exitOutcome]);\n\n if (result.kind === \"ready\") {\n console.log(`Service started in background (PID ${child.pid})`);\n // ready 后 detach:摘 stderr 订阅 + destroy pipe + unref 子进程。\n // 单独 child.unref() 不够,父侧的 stderr pipe fd 还在事件循环里会让父 CLI 永不退出;\n // 必须 destroy 掉 pipe 才能真正释放 refcount。pino 已接管子进程的输出到 service.log。\n child.stderr!.removeAllListeners(\"data\");\n child.stderr!.destroy();\n child.unref();\n return;\n }\n\n // 失败路径:timeout 或 exited\n const stderrOutput = Buffer.concat(stderrChunks).toString(\"utf-8\").trim();\n if (result.kind === \"exited\") {\n console.error(`Service exited during startup (code=${result.code}, signal=${result.signal}).`);\n } else {\n console.error(`Service failed to become ready within ${DAEMON_STARTUP_TIMEOUT_MS / 1000}s.`);\n try {\n process.kill(child.pid!, \"SIGTERM\");\n } catch {\n // 子进程可能已自己退出,kill 失败不影响后续退出码\n }\n }\n if (stderrOutput) {\n console.error(\"--- child stderr ---\");\n console.error(stderrOutput);\n }\n process.exit(1);\n}\n\nconst program = new Command(\"dev-anywhere\")\n .description(\"Dev Anywhere - transparent local AI CLI proxy with remote control\")\n .version(pkg.version)\n .allowUnknownOption()\n .allowExcessArguments()\n .action(async () => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n // 延迟导入 terminal: CLI 的其他子命令(init/stop/status)不需要 PTY + xterm 相关依赖,\n // tsup 基于 dynamic import 自动代码分裂,避免所有命令都为 terminal 付出 14KB 额外启动成本。\n const { startTerminal } = await import(\"./terminal.js\");\n let invocation: ReturnType<typeof extractAgentInvocation>;\n try {\n invocation = extractAgentInvocation(cliArgs);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n const { provider, args } = invocation;\n await startTerminal(args, provider);\n });\n\n// serve 子命令组\nconst serve = new Command(\"serve\")\n .description(\"Manage the dev-anywhere background service\")\n .option(\"-d, --daemon\", \"Run in background\")\n .action(async (opts) => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n if (opts.daemon) {\n setDesiredDaemonEnv(undefined);\n await startDaemon();\n } else {\n // 延迟导入 serve: daemon 模式只需要 startDaemon(纯 spawn),不需要加载 70KB 的 serve bundle\n const { startService } = await import(\"./serve.js\");\n await startService();\n }\n });\n\nserve\n .command(\"start\")\n .description(\"Start the background service\")\n .option(\"--env <name>\", \"Use a named config environment\")\n .action(async (opts) => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n setDesiredDaemonEnv(opts.env);\n await startDaemon({ envName: opts.env });\n });\n\nserve\n .command(\"status\")\n .description(\"Show service status and active sessions\")\n .option(\"-w, --watch\", \"Continuous monitoring mode\")\n .option(\"-n, --interval <seconds>\", \"Refresh interval in seconds\", \"2\")\n .action(async (opts) => {\n if (opts.watch) {\n const intervalMs = Number(opts.interval) * 1000;\n let lastLines = await showStatus();\n setInterval(async () => {\n if (lastLines > 0) {\n process.stdout.write(`\\x1B[${lastLines}A\\x1B[J`);\n }\n lastLines = await showStatus();\n }, intervalMs);\n } else {\n await showStatus();\n }\n });\n\nserve\n .command(\"stop\")\n .description(\"Stop the background service\")\n .action(() => {\n stopService();\n });\n\nserve\n .command(\"restart\")\n .description(\"Restart the background service\")\n .option(\"--env <name>\", \"Use a named config environment\")\n .action(async (opts) => {\n setDesiredDaemonEnv(opts.env);\n stopService();\n await startDaemon({ envName: opts.env });\n });\n\nprogram.addCommand(serve);\n\nprogram\n .command(\"init\")\n .description(\"Initialize dev-anywhere workspace (~/.dev-anywhere)\")\n .action(() => {\n if (isInitialized()) {\n console.log(`Already initialized. Config at ${CONFIG_PATH}`);\n return;\n }\n initWorkspace();\n console.log(\"Initialized ~/.dev-anywhere/\");\n console.log(`Edit ${CONFIG_PATH} to configure relay server URL.`);\n });\n\n// pnpm run dev -- args 会在参数前插入 \"--\"。根脚本和用户命令都可能再加一层\n// 分隔符,所以这里过滤所有前导分隔符,再交给 Commander 和 provider 参数解析。\nconst cliArgs = normalizeCliArgs(process.argv.slice(2));\n\nprogram.parse(cliArgs, { from: \"user\" });\n","import type { ProviderId } from \"./providers/index.js\";\n\nexport function normalizeCliArgs(args: string[]): string[] {\n const normalized = [...args];\n while (normalized[0] === \"--\") {\n normalized.shift();\n }\n return normalized;\n}\n\nexport function extractAgentInvocation(args: string[]): { provider: ProviderId; args: string[] } {\n const [agent, ...providerArgs] = args;\n if (agent !== \"claude\" && agent !== \"codex\") {\n throw new Error(\n 'Missing Agent CLI. Use \"dev-anywhere claude ...\" or \"dev-anywhere codex ...\".',\n );\n }\n return { provider: agent, args: providerArgs };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cli-args.ts"],"sourcesContent":["import { existsSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { connect } from \"node:net\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { Command } from \"commander\";\nimport {\n PID_PATH,\n SOCK_PATH,\n STOPPED_PATH,\n SERVICE_LOG_PATH,\n CONFIG_PATH,\n PROFILE_NAME,\n ensureProfileWorkspace,\n isInitialized,\n initWorkspace,\n} from \"./common/paths.js\";\nimport { spawnScript } from \"./common/env.js\";\nimport { daemonRelayArgs, setDesiredDaemonRelay } from \"./common/daemon-env.js\";\nimport { createIpcReader, serializeIpc } from \"./ipc/ipc-protocol.js\";\nimport { extractAgentInvocation, normalizeCliArgs, stripProxyProfileArgs } from \"./cli-args.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\")) as {\n version: string;\n};\n\nfunction stopService(): boolean {\n if (!existsSync(PID_PATH)) {\n console.error(\"Service is not running (no PID file)\");\n return false;\n }\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, \"SIGTERM\");\n console.log(`Service stopped (PID ${pid})`);\n } catch {\n console.error(`Process ${pid} not found, cleaning up stale files`);\n }\n if (existsSync(PID_PATH)) unlinkSync(PID_PATH);\n if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH);\n writeFileSync(STOPPED_PATH, String(Date.now()));\n return true;\n}\n\nfunction showStatus(): Promise<number> {\n return new Promise((resolve) => {\n let lines = 0;\n const log = (s: string) => {\n console.log(s);\n lines++;\n };\n\n if (!existsSync(PID_PATH)) {\n log(`Profile: ${PROFILE_NAME}`);\n log(\"Service: not running\");\n resolve(lines);\n return;\n }\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n let alive = false;\n try {\n process.kill(pid, 0);\n alive = true;\n } catch {\n // process.kill(pid, 0) 抛错表示进程不存在\n }\n\n if (!alive) {\n log(\"Service: dead (stale PID file)\");\n resolve(lines);\n return;\n }\n\n log(`Profile: ${PROFILE_NAME}`);\n log(`Service: running (PID ${pid})`);\n log(`Socket: ${SOCK_PATH}`);\n log(`Log: ${SERVICE_LOG_PATH}`);\n\n const sock = connect(SOCK_PATH);\n sock.on(\"error\", () => {\n log(\"Sessions: unable to connect\");\n sock.destroy();\n resolve(lines);\n });\n sock.on(\"connect\", () => {\n createIpcReader(sock, (msg) => {\n if (msg.type === \"service_status_response\") {\n const config = msg.config;\n log(`Daemon: profile ${config.profile ?? PROFILE_NAME}`);\n log(`Relay: ${config.relayName} (${config.relayNameSource})`);\n log(`Config: relay ${config.relayUrl ?? \"(unset)\"} (${config.relayUrlSource})`);\n const relay = msg.relay;\n if (!relay) {\n log(\"Relay: not configured\");\n } else if (relay.connected) {\n log(`Relay: connected (proxy: ${relay.proxyId})`);\n log(\n ` queue depth: ${relay.queueDepth}, reconnect attempts: ${relay.reconnectAttempt}`,\n );\n } else {\n log(\n `Relay: disconnected (proxy: ${relay.proxyId}, reconnecting: attempt ${relay.reconnectAttempt}, queued: ${relay.queueDepth})`,\n );\n }\n log(\"\");\n\n // 显示会话列表\n const sessions = msg.sessions;\n if (sessions.length === 0) {\n log(\"Sessions: none\");\n } else {\n log(`Sessions: ${sessions.length}`);\n for (const s of sessions) {\n log(` ${s.id} ${s.mode} ${s.state} worker: ${s.hasWorker ? \"yes\" : \"no\"}`);\n }\n }\n sock.destroy();\n resolve(lines);\n }\n });\n sock.write(serializeIpc({ type: \"service_status_request\" }));\n });\n });\n}\n\nconst DAEMON_STARTUP_TIMEOUT_MS = 30_000;\nconst DAEMON_STARTUP_POLL_MS = 200;\n\n// 轮询 SOCK_PATH 直到可连接,作为 serve 的 readiness 信号。\n// serve.ts 里 server.listen(SOCK_PATH) 是启动序列的最后一步,连上即代表 ready。\nasync function waitForServeReady(timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const connected = await new Promise<boolean>((resolve) => {\n const sock = connect(SOCK_PATH);\n sock.once(\"connect\", () => {\n sock.destroy();\n resolve(true);\n });\n sock.once(\"error\", () => resolve(false));\n });\n if (connected) return true;\n await sleep(DAEMON_STARTUP_POLL_MS);\n }\n return false;\n}\n\nasync function startDaemon(options?: { relayName?: string }): Promise<void> {\n ensureProfileWorkspace();\n if (existsSync(PID_PATH)) {\n const pid = parseInt(readFileSync(PID_PATH, \"utf-8\").trim(), 10);\n try {\n process.kill(pid, 0);\n console.error(`Service is already running (PID ${pid})`);\n return;\n } catch {\n // process.kill(pid, 0) 抛错表示进程不存在,继续启动\n }\n }\n if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);\n\n // stderr 走 pipe 由父 CLI 订阅:子进程 ready 前(pino logger 未接管)的启动错误\n // 会被捕获;ready 后父 detach,pino 接管所有输出到 service.log。\n // start 命令必须等 daemon socket 可连接后再退出;否则用户会看到“启动成功”,实际服务还没就绪。\n const serveArgs = [\"--profile\", PROFILE_NAME, ...daemonRelayArgs(options?.relayName)];\n const child = spawnScript(new URL(\"./serve\", import.meta.url), serveArgs, {\n env: { ...process.env },\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n unref: false,\n });\n\n const stderrChunks: Buffer[] = [];\n child.stderr!.on(\"data\", (chunk: Buffer) => {\n stderrChunks.push(chunk);\n });\n\n // race: readiness handshake vs. 子进程先挂。子进程 ready 前就 exit 说明启动硬失败,\n // 不必再等到 30s 超时才报错。\n type Outcome =\n | { kind: \"ready\" }\n | { kind: \"timeout\" }\n | { kind: \"exited\"; code: number | null; signal: NodeJS.Signals | null };\n\n const readyOutcome: Promise<Outcome> = waitForServeReady(DAEMON_STARTUP_TIMEOUT_MS).then((ok) =>\n ok ? { kind: \"ready\" as const } : { kind: \"timeout\" as const },\n );\n const exitOutcome: Promise<Outcome> = new Promise((resolve) => {\n // 设 listener 前已经 exit 的边界:Node 记在 exitCode 上\n if (child.exitCode !== null) {\n resolve({ kind: \"exited\", code: child.exitCode, signal: child.signalCode });\n return;\n }\n child.once(\"exit\", (code, signal) => resolve({ kind: \"exited\", code, signal }));\n });\n\n const result = await Promise.race([readyOutcome, exitOutcome]);\n\n if (result.kind === \"ready\") {\n console.log(`Service started in background (PID ${child.pid})`);\n // ready 后 detach:摘 stderr 订阅 + destroy pipe + unref 子进程。\n // 单独 child.unref() 不够,父侧的 stderr pipe fd 还在事件循环里会让父 CLI 永不退出;\n // 必须 destroy 掉 pipe 才能真正释放 refcount。pino 已接管子进程的输出到 service.log。\n child.stderr!.removeAllListeners(\"data\");\n child.stderr!.destroy();\n child.unref();\n return;\n }\n\n // 失败路径:timeout 或 exited\n const stderrOutput = Buffer.concat(stderrChunks).toString(\"utf-8\").trim();\n if (result.kind === \"exited\") {\n console.error(`Service exited during startup (code=${result.code}, signal=${result.signal}).`);\n } else {\n console.error(`Service failed to become ready within ${DAEMON_STARTUP_TIMEOUT_MS / 1000}s.`);\n try {\n process.kill(child.pid!, \"SIGTERM\");\n } catch {\n // 子进程可能已自己退出,kill 失败不影响后续退出码\n }\n }\n if (stderrOutput) {\n console.error(\"--- child stderr ---\");\n console.error(stderrOutput);\n }\n process.exit(1);\n}\n\nconst program = new Command(\"dev-anywhere\")\n .description(\"Dev Anywhere - transparent local AI CLI proxy with remote control\")\n .version(pkg.version)\n .option(\"--profile <name>\", \"Use an isolated local proxy profile\")\n .allowUnknownOption()\n .allowExcessArguments()\n .action(async () => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n // 延迟导入 terminal: CLI 的其他子命令(init/stop/status)不需要 PTY + xterm 相关依赖,\n // tsup 基于 dynamic import 自动代码分裂,避免所有命令都为 terminal 付出 14KB 额外启动成本。\n const { startTerminal } = await import(\"./terminal.js\");\n let invocation: ReturnType<typeof extractAgentInvocation>;\n try {\n invocation = extractAgentInvocation(cliArgsWithoutProfile);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n const { provider, args } = invocation;\n await startTerminal(args, provider);\n });\n\n// serve 子命令组\nconst serve = new Command(\"serve\")\n .description(\"Manage the dev-anywhere background service\")\n .option(\"--profile <name>\", \"Use an isolated local proxy profile\")\n .option(\"-d, --daemon\", \"Run in background\")\n .action(async (opts) => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n if (opts.daemon) {\n setDesiredDaemonRelay(undefined);\n await startDaemon();\n } else {\n // 延迟导入 serve: daemon 模式只需要 startDaemon(纯 spawn),不需要加载 70KB 的 serve bundle\n const { startService } = await import(\"./serve.js\");\n await startService();\n }\n });\n\nserve\n .command(\"start\")\n .description(\"Start the background service\")\n .option(\"--relay <name>\", \"Use a named relay from config\")\n .action(async (opts) => {\n if (!isInitialized()) {\n console.error(`Dev Anywhere is not initialized. Run \"dev-anywhere init\" first.`);\n process.exit(1);\n }\n setDesiredDaemonRelay(opts.relay);\n await startDaemon({ relayName: opts.relay });\n });\n\nserve\n .command(\"status\")\n .description(\"Show service status and active sessions\")\n .option(\"-w, --watch\", \"Continuous monitoring mode\")\n .option(\"-n, --interval <seconds>\", \"Refresh interval in seconds\", \"2\")\n .action(async (opts) => {\n if (opts.watch) {\n const intervalMs = Number(opts.interval) * 1000;\n let lastLines = await showStatus();\n setInterval(async () => {\n if (lastLines > 0) {\n process.stdout.write(`\\x1B[${lastLines}A\\x1B[J`);\n }\n lastLines = await showStatus();\n }, intervalMs);\n } else {\n await showStatus();\n }\n });\n\nserve\n .command(\"stop\")\n .description(\"Stop the background service\")\n .action(() => {\n stopService();\n });\n\nserve\n .command(\"restart\")\n .description(\"Restart the background service\")\n .option(\"--relay <name>\", \"Use a named relay from config\")\n .action(async (opts) => {\n setDesiredDaemonRelay(opts.relay);\n stopService();\n await startDaemon({ relayName: opts.relay });\n });\n\nprogram.addCommand(serve);\n\nprogram\n .command(\"init\")\n .description(\"Initialize dev-anywhere workspace (~/.dev-anywhere)\")\n .action(() => {\n if (isInitialized()) {\n console.log(`Already initialized. Config at ${CONFIG_PATH}`);\n return;\n }\n initWorkspace();\n console.log(\"Initialized ~/.dev-anywhere/\");\n console.log(`Edit ${CONFIG_PATH} to configure relay server URL.`);\n });\n\n// pnpm run dev -- args 会在参数前插入 \"--\"。根脚本和用户命令都可能再加一层\n// 分隔符,所以这里过滤所有前导分隔符,再交给 Commander 和 provider 参数解析。\nconst cliArgs = normalizeCliArgs(process.argv.slice(2));\nconst cliArgsWithoutProfile = stripProxyProfileArgs(cliArgs);\n\nprogram.parse(cliArgsWithoutProfile, { from: \"user\" });\n","import type { ProviderId } from \"./providers/index.js\";\n\nexport function normalizeCliArgs(args: string[]): string[] {\n const normalized = [...args];\n while (normalized[0] === \"--\") {\n normalized.shift();\n }\n return normalized;\n}\n\nexport function stripProxyProfileArgs(args: string[]): string[] {\n const result: string[] = [];\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"claude\" || arg === \"codex\") {\n result.push(...args.slice(i));\n break;\n }\n if (arg === \"--profile\") {\n i++;\n continue;\n }\n if (arg.startsWith(\"--profile=\")) {\n continue;\n }\n result.push(arg);\n }\n return result;\n}\n\nexport function extractAgentInvocation(args: string[]): { provider: ProviderId; args: string[] } {\n const [agent, ...providerArgs] = args;\n if (agent !== \"claude\" && agent !== \"codex\") {\n throw new Error(\n 'Missing Agent CLI. Use \"dev-anywhere claude ...\" or \"dev-anywhere codex ...\".',\n );\n }\n return { provider: agent, args: providerArgs };\n}\n"],"mappings":"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","names":[]}
|
package/dist/serve.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
KnownContentBlockSchema,
|
|
6
6
|
SeqCounter,
|
|
7
7
|
StreamJsonEventSchema
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-QXOARRC2.js";
|
|
9
9
|
import {
|
|
10
10
|
createFSM,
|
|
11
11
|
defineFSM,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
serviceLogger,
|
|
15
15
|
shouldReleaseApprovalWait,
|
|
16
16
|
stateAfterApprovalRelease
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-TG7JPHE5.js";
|
|
18
18
|
import {
|
|
19
19
|
spawnScript
|
|
20
20
|
} from "./chunk-ZUWAB67J.js";
|
|
@@ -27,9 +27,12 @@ import {
|
|
|
27
27
|
CONFIG_PATH,
|
|
28
28
|
ControlErrorCode,
|
|
29
29
|
DATA_DIR,
|
|
30
|
+
DEFAULT_PROXY_PROFILE,
|
|
30
31
|
HOOK_REGISTRY_PATH,
|
|
31
32
|
MessageEnvelopeSchema,
|
|
32
33
|
PID_PATH,
|
|
34
|
+
PROFILE_NAME,
|
|
35
|
+
PROXY_ID_PATH,
|
|
33
36
|
SESSIONS_PATH,
|
|
34
37
|
SOCK_PATH,
|
|
35
38
|
STOPPED_PATH,
|
|
@@ -37,11 +40,13 @@ import {
|
|
|
37
40
|
buildMessage,
|
|
38
41
|
createIpcReader,
|
|
39
42
|
createWorkerReader,
|
|
43
|
+
defaultHookPortForProfile,
|
|
44
|
+
ensureProfileWorkspace,
|
|
40
45
|
serializeIpc,
|
|
41
46
|
serializeWorkerMsg,
|
|
42
47
|
sessionPaths,
|
|
43
48
|
tildify
|
|
44
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-DFLQ3TFT.js";
|
|
45
50
|
|
|
46
51
|
// src/serve.ts
|
|
47
52
|
import { createServer as createServer2 } from "net";
|
|
@@ -620,31 +625,20 @@ function parsePort(value, source) {
|
|
|
620
625
|
}
|
|
621
626
|
return port;
|
|
622
627
|
}
|
|
623
|
-
function
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
const envName = requestedEnv ?? fromFile.defaultEnv ?? "local";
|
|
632
|
-
const config = fromFile.envs[envName];
|
|
633
|
-
if (!config) {
|
|
634
|
-
const available = Object.keys(fromFile.envs).sort();
|
|
635
|
-
throw new Error(
|
|
636
|
-
`Unknown config env "${envName}". Available envs: ${available.length > 0 ? available.join(", ") : "(none)"}`
|
|
637
|
-
);
|
|
628
|
+
function isRecord(value) {
|
|
629
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
630
|
+
}
|
|
631
|
+
function validateConfigShape(value) {
|
|
632
|
+
if (!isRecord(value) || !isRecord(value.profiles) || !isRecord(value.relays)) {
|
|
633
|
+
throw new Error(`Invalid config shape in ${CONFIG_PATH}: expected "profiles" and "relays".`);
|
|
638
634
|
}
|
|
639
|
-
return
|
|
640
|
-
envName,
|
|
641
|
-
envNameSource: requestedEnv ? "cli" : fromFile.defaultEnv ? "file" : "default",
|
|
642
|
-
config
|
|
643
|
-
};
|
|
635
|
+
return value;
|
|
644
636
|
}
|
|
645
637
|
function readConfigFile() {
|
|
646
|
-
if (!existsSync3(CONFIG_PATH))
|
|
647
|
-
|
|
638
|
+
if (!existsSync3(CONFIG_PATH)) {
|
|
639
|
+
throw new Error(`Dev Anywhere config not found at ${CONFIG_PATH}. Run "dev-anywhere init".`);
|
|
640
|
+
}
|
|
641
|
+
return validateConfigShape(JSON.parse(readFileSync3(CONFIG_PATH, "utf-8")));
|
|
648
642
|
}
|
|
649
643
|
function agentCliField(provider) {
|
|
650
644
|
return provider === "claude" ? "claudeBin" : "codexBin";
|
|
@@ -669,68 +663,71 @@ function uniqueAbsolutePaths(paths) {
|
|
|
669
663
|
}
|
|
670
664
|
return result;
|
|
671
665
|
}
|
|
672
|
-
function
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
666
|
+
function resolveRelayConfig(fromFile, requestedRelayName) {
|
|
667
|
+
const profile = fromFile.profiles[PROFILE_NAME];
|
|
668
|
+
if (!profile) {
|
|
669
|
+
const available = Object.keys(fromFile.profiles).sort();
|
|
670
|
+
throw new Error(
|
|
671
|
+
`Unknown profile "${PROFILE_NAME}". Available profiles: ${available.length > 0 ? available.join(", ") : "(none)"}`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
const relayName = requestedRelayName?.trim() || profile.relay?.trim();
|
|
675
|
+
if (!relayName) {
|
|
676
|
+
throw new Error(`Profile "${PROFILE_NAME}" must specify a relay.`);
|
|
677
|
+
}
|
|
678
|
+
const relay = fromFile.relays[relayName];
|
|
679
|
+
if (!relay) {
|
|
680
|
+
const available = Object.keys(fromFile.relays).sort();
|
|
681
|
+
throw new Error(
|
|
682
|
+
`Unknown relay "${relayName}". Available relays: ${available.length > 0 ? available.join(", ") : "(none)"}`
|
|
683
|
+
);
|
|
684
|
+
}
|
|
676
685
|
return {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
686
|
+
relayName,
|
|
687
|
+
relayNameSource: requestedRelayName?.trim() ? "cli" : "profile",
|
|
688
|
+
relay
|
|
680
689
|
};
|
|
681
690
|
}
|
|
682
691
|
function loadConfig(options) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
serviceLogger.warn(
|
|
689
|
-
{ path: CONFIG_PATH, err: err instanceof Error ? err.message : String(err) },
|
|
690
|
-
"Failed to parse config file, falling back to env-only"
|
|
691
|
-
);
|
|
692
|
-
}
|
|
693
|
-
} else {
|
|
694
|
-
serviceLogger.debug({ path: CONFIG_PATH }, "Config file not found, using env-only");
|
|
695
|
-
}
|
|
696
|
-
const resolved = resolveFileConfig(fromFile, options?.envName);
|
|
697
|
-
const hookPortFromFile = resolved.config.hookPort ?? fromFile.hookPort;
|
|
698
|
-
const claudeBinFromFile = resolved.config.claudeBin ?? fromFile.claudeBin;
|
|
699
|
-
const codexBinFromFile = resolved.config.codexBin ?? fromFile.codexBin;
|
|
700
|
-
const claudeBinHistory = [
|
|
701
|
-
...resolved.config.claudeBinHistory ?? [],
|
|
702
|
-
...fromFile.claudeBinHistory ?? []
|
|
703
|
-
];
|
|
704
|
-
const codexBinHistory = [
|
|
705
|
-
...resolved.config.codexBinHistory ?? [],
|
|
706
|
-
...fromFile.codexBinHistory ?? []
|
|
707
|
-
];
|
|
708
|
-
const claudeBin = process.env.CLAUDE_BIN ?? claudeBinFromFile;
|
|
709
|
-
const codexBin = process.env.CODEX_BIN ?? codexBinFromFile;
|
|
692
|
+
const fromFile = readConfigFile();
|
|
693
|
+
const agentCli = fromFile.agentCli ?? {};
|
|
694
|
+
const resolved = resolveRelayConfig(fromFile, options?.relayName);
|
|
695
|
+
const claudeBin = process.env.CLAUDE_BIN ?? agentCli.claudeBin;
|
|
696
|
+
const codexBin = process.env.CODEX_BIN ?? agentCli.codexBin;
|
|
710
697
|
const config = {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
698
|
+
profileName: PROFILE_NAME,
|
|
699
|
+
relayName: resolved.relayName,
|
|
700
|
+
relayUrl: process.env.RELAY_URL ?? resolved.relay.url,
|
|
701
|
+
relayToken: process.env.RELAY_PROXY_TOKEN ?? resolved.relay.proxyToken,
|
|
702
|
+
hookPort: parsePort(process.env.DEV_ANYWHERE_HOOK_PORT, "DEV_ANYWHERE_HOOK_PORT") ?? defaultHookPortForProfile(PROFILE_NAME),
|
|
715
703
|
claudeBin,
|
|
716
704
|
codexBin,
|
|
717
705
|
agentCliSuggestions: {
|
|
718
|
-
claude: uniqueAbsolutePaths([
|
|
719
|
-
|
|
706
|
+
claude: uniqueAbsolutePaths([
|
|
707
|
+
process.env.CLAUDE_BIN,
|
|
708
|
+
agentCli.claudeBin,
|
|
709
|
+
...agentCli.claudeBinHistory ?? []
|
|
710
|
+
]),
|
|
711
|
+
codex: uniqueAbsolutePaths([
|
|
712
|
+
process.env.CODEX_BIN,
|
|
713
|
+
agentCli.codexBin,
|
|
714
|
+
...agentCli.codexBinHistory ?? []
|
|
715
|
+
])
|
|
720
716
|
},
|
|
721
717
|
sources: {
|
|
722
|
-
|
|
723
|
-
relayUrl: process.env.RELAY_URL ? "env" : resolved.
|
|
724
|
-
relayToken: process.env.RELAY_PROXY_TOKEN ? "env" : resolved.
|
|
725
|
-
hookPort: process.env.DEV_ANYWHERE_HOOK_PORT ? "env" :
|
|
726
|
-
claudeBin: process.env.CLAUDE_BIN ? "env" :
|
|
727
|
-
codexBin: process.env.CODEX_BIN ? "env" :
|
|
718
|
+
relayName: resolved.relayNameSource,
|
|
719
|
+
relayUrl: process.env.RELAY_URL ? "env" : resolved.relay.url ? "file" : "none",
|
|
720
|
+
relayToken: process.env.RELAY_PROXY_TOKEN ? "env" : resolved.relay.proxyToken ? "file" : "none",
|
|
721
|
+
hookPort: process.env.DEV_ANYWHERE_HOOK_PORT ? "env" : "default",
|
|
722
|
+
claudeBin: process.env.CLAUDE_BIN ? "env" : agentCli.claudeBin ? "file" : "none",
|
|
723
|
+
codexBin: process.env.CODEX_BIN ? "env" : agentCli.codexBin ? "file" : "none"
|
|
728
724
|
}
|
|
729
725
|
};
|
|
730
726
|
serviceLogger.info(
|
|
731
727
|
{
|
|
732
|
-
|
|
733
|
-
|
|
728
|
+
profile: config.profileName,
|
|
729
|
+
relayName: config.relayName,
|
|
730
|
+
relayNameSource: config.sources.relayName,
|
|
734
731
|
relayUrl: config.relayUrl ?? "(unset)",
|
|
735
732
|
relayUrlSource: config.sources.relayUrl,
|
|
736
733
|
relayTokenSource: config.sources.relayToken,
|
|
@@ -750,20 +747,20 @@ function buildProviderEnv(config, baseEnv = process.env) {
|
|
|
750
747
|
...config.codexBin ? { CODEX_BIN: config.codexBin } : {}
|
|
751
748
|
};
|
|
752
749
|
}
|
|
753
|
-
function
|
|
750
|
+
function updateAgentCliConfig(config, provider, path) {
|
|
751
|
+
const field = agentCliField(provider);
|
|
752
|
+
const historyField = agentCliHistoryField(provider);
|
|
753
|
+
const history = uniqueAbsolutePaths([path, ...config[historyField] ?? []]).slice(0, 8);
|
|
754
|
+
return {
|
|
755
|
+
...config,
|
|
756
|
+
[field]: path,
|
|
757
|
+
[historyField]: history
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function saveAgentCliPath(provider, path) {
|
|
754
761
|
const normalized = validateAgentCliPath(path);
|
|
755
762
|
const fromFile = readConfigFile();
|
|
756
|
-
|
|
757
|
-
if (fromFile.envs) {
|
|
758
|
-
const envName = resolved.envName ?? options?.envName ?? fromFile.defaultEnv ?? "local";
|
|
759
|
-
fromFile.envs[envName] = updateAgentCliPathInEnvConfig(
|
|
760
|
-
fromFile.envs[envName] ?? {},
|
|
761
|
-
provider,
|
|
762
|
-
normalized
|
|
763
|
-
);
|
|
764
|
-
} else {
|
|
765
|
-
Object.assign(fromFile, updateAgentCliPathInEnvConfig(fromFile, provider, normalized));
|
|
766
|
-
}
|
|
763
|
+
fromFile.agentCli = updateAgentCliConfig(fromFile.agentCli ?? {}, provider, normalized);
|
|
767
764
|
mkdirSync3(dirname3(CONFIG_PATH), { recursive: true });
|
|
768
765
|
writeFileSync3(CONFIG_PATH, `${JSON.stringify(fromFile, null, 2)}
|
|
769
766
|
`, "utf-8");
|
|
@@ -2491,7 +2488,7 @@ var RelayResourceHandlers = class {
|
|
|
2491
2488
|
}
|
|
2492
2489
|
try {
|
|
2493
2490
|
const path = validateExecutablePath(rawPath ?? "");
|
|
2494
|
-
saveAgentCliPath(provider, path
|
|
2491
|
+
saveAgentCliPath(provider, path);
|
|
2495
2492
|
this.deps.setAgentCliPath(provider, path);
|
|
2496
2493
|
const agentCli = detectAgentCliStatus(this.deps.getProviderEnv(), {
|
|
2497
2494
|
suggestions: this.deps.getAgentCliSuggestions()
|
|
@@ -3110,7 +3107,6 @@ var RelayRouter = class {
|
|
|
3110
3107
|
relaySend: deps.relaySend,
|
|
3111
3108
|
controlHandlers: deps.controlHandlers,
|
|
3112
3109
|
sessionManager: deps.sessionManager,
|
|
3113
|
-
envName: deps.envName,
|
|
3114
3110
|
getProviderEnv: deps.getProviderEnv,
|
|
3115
3111
|
getAgentCliSuggestions: deps.getAgentCliSuggestions,
|
|
3116
3112
|
setAgentCliPath: deps.setAgentCliPath
|
|
@@ -3696,8 +3692,13 @@ async function cleanupStaleResources() {
|
|
|
3696
3692
|
serviceLogger.info("Removed stale PID file");
|
|
3697
3693
|
}
|
|
3698
3694
|
}
|
|
3695
|
+
function formatProxyNameForProfile(baseName, profileName = PROFILE_NAME) {
|
|
3696
|
+
return profileName === DEFAULT_PROXY_PROFILE ? baseName : `${baseName} (${profileName})`;
|
|
3697
|
+
}
|
|
3699
3698
|
function getProxyName() {
|
|
3700
|
-
|
|
3699
|
+
const explicitName = process.env.DEV_ANYWHERE_PROXY_NAME?.trim();
|
|
3700
|
+
if (explicitName) return explicitName;
|
|
3701
|
+
return formatProxyNameForProfile(getComputerName() || hostname());
|
|
3701
3702
|
}
|
|
3702
3703
|
function getComputerName() {
|
|
3703
3704
|
try {
|
|
@@ -4434,24 +4435,25 @@ function parseServiceOptions(argv) {
|
|
|
4434
4435
|
const options = {};
|
|
4435
4436
|
for (let i = 0; i < argv.length; i++) {
|
|
4436
4437
|
const arg = argv[i];
|
|
4437
|
-
if (arg === "--
|
|
4438
|
-
const
|
|
4439
|
-
if (!
|
|
4440
|
-
throw new Error("Missing value for --
|
|
4438
|
+
if (arg === "--relay") {
|
|
4439
|
+
const relayName = argv[i + 1];
|
|
4440
|
+
if (!relayName || relayName.startsWith("-")) {
|
|
4441
|
+
throw new Error("Missing value for --relay");
|
|
4441
4442
|
}
|
|
4442
|
-
options.
|
|
4443
|
+
options.relayName = relayName;
|
|
4443
4444
|
i++;
|
|
4444
4445
|
continue;
|
|
4445
4446
|
}
|
|
4446
|
-
if (arg.startsWith("--
|
|
4447
|
-
const
|
|
4448
|
-
if (!
|
|
4449
|
-
options.
|
|
4447
|
+
if (arg.startsWith("--relay=")) {
|
|
4448
|
+
const relayName = arg.slice("--relay=".length);
|
|
4449
|
+
if (!relayName) throw new Error("Missing value for --relay");
|
|
4450
|
+
options.relayName = relayName;
|
|
4450
4451
|
}
|
|
4451
4452
|
}
|
|
4452
4453
|
return options;
|
|
4453
4454
|
}
|
|
4454
4455
|
async function startService(options) {
|
|
4456
|
+
ensureProfileWorkspace();
|
|
4455
4457
|
await cleanupStaleResources();
|
|
4456
4458
|
try {
|
|
4457
4459
|
unlinkSync3(STOPPED_PATH);
|
|
@@ -4479,7 +4481,7 @@ async function startService(options) {
|
|
|
4479
4481
|
sessionManager.startReaper();
|
|
4480
4482
|
const terminalSockets = /* @__PURE__ */ new Map();
|
|
4481
4483
|
const proxyName = getProxyName();
|
|
4482
|
-
let proxyConfig = loadConfig({
|
|
4484
|
+
let proxyConfig = loadConfig({ relayName: options?.relayName });
|
|
4483
4485
|
const getProviderEnv = () => buildProviderEnv(proxyConfig, process.env);
|
|
4484
4486
|
const getAgentCliSuggestions = () => proxyConfig.agentCliSuggestions;
|
|
4485
4487
|
const setAgentCliPath = (provider, path) => {
|
|
@@ -4501,8 +4503,9 @@ async function startService(options) {
|
|
|
4501
4503
|
const relayUrl = options?.relayUrl ?? proxyConfig.relayUrl;
|
|
4502
4504
|
const relayToken = proxyConfig.relayToken;
|
|
4503
4505
|
const statusConfig = {
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
+
profile: PROFILE_NAME,
|
|
4507
|
+
relayName: proxyConfig.relayName,
|
|
4508
|
+
relayNameSource: proxyConfig.sources.relayName,
|
|
4506
4509
|
relayUrl,
|
|
4507
4510
|
relayUrlSource: proxyConfig.sources.relayUrl,
|
|
4508
4511
|
relayTokenSource: proxyConfig.sources.relayToken,
|
|
@@ -4510,12 +4513,16 @@ async function startService(options) {
|
|
|
4510
4513
|
hookPortSource: proxyConfig.sources.hookPort
|
|
4511
4514
|
};
|
|
4512
4515
|
if (!relayUrl) {
|
|
4513
|
-
const msg =
|
|
4516
|
+
const msg = `Relay URL is required. Set relays.${proxyConfig.relayName}.url in ~/.dev-anywhere/config.json or pass --relay <name>.`;
|
|
4514
4517
|
serviceLogger.error(msg);
|
|
4515
4518
|
console.error(msg);
|
|
4516
4519
|
process.exit(1);
|
|
4517
4520
|
}
|
|
4518
|
-
const relayConnection = new RelayConnection(relayUrl, {
|
|
4521
|
+
const relayConnection = new RelayConnection(relayUrl, {
|
|
4522
|
+
name: proxyName,
|
|
4523
|
+
token: relayToken,
|
|
4524
|
+
proxyIdPath: PROXY_ID_PATH
|
|
4525
|
+
});
|
|
4519
4526
|
const relaySend = (data) => relayConnection.sendRaw(data);
|
|
4520
4527
|
const controlHandlers = createControlMessageHandlers(relaySend, sessionManager);
|
|
4521
4528
|
const observerChangeState = (sessionId, next) => changeSessionState(sessionManager, relayConnection, sessionId, next);
|
|
@@ -4577,7 +4584,8 @@ async function startService(options) {
|
|
|
4577
4584
|
relayConnection.connect();
|
|
4578
4585
|
serviceLogger.info(
|
|
4579
4586
|
{
|
|
4580
|
-
|
|
4587
|
+
relayName: proxyConfig.relayName,
|
|
4588
|
+
profile: PROFILE_NAME,
|
|
4581
4589
|
relayUrl,
|
|
4582
4590
|
proxyName,
|
|
4583
4591
|
tokenSet: !!relayToken,
|
|
@@ -4601,7 +4609,6 @@ async function startService(options) {
|
|
|
4601
4609
|
permissionBroker,
|
|
4602
4610
|
hookEventRouter: hookRuntime.hookEventRouter,
|
|
4603
4611
|
agentStatusRegistry,
|
|
4604
|
-
envName: proxyConfig.envName,
|
|
4605
4612
|
getProviderEnv,
|
|
4606
4613
|
getAgentCliSuggestions,
|
|
4607
4614
|
setAgentCliPath
|