@dev-anywhere/proxy 0.1.7 → 0.1.9
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/dist/chunk-2JUB4LDU.js +84 -0
- package/dist/chunk-2JUB4LDU.js.map +1 -0
- package/dist/{chunk-ODK6N2NP.js → chunk-7XMJMVIL.js} +2 -2
- package/dist/{chunk-TX6HNHDB.js → chunk-BMVYMCKF.js} +2 -2
- package/dist/chunk-DCDXAM76.js +195 -0
- package/dist/chunk-DCDXAM76.js.map +1 -0
- package/dist/{chunk-JGGDVMY5.js → chunk-ORZTFYXR.js} +2 -21
- package/dist/chunk-ORZTFYXR.js.map +1 -0
- package/dist/{chunk-RFBTVZ2X.js → chunk-QFYI6AMN.js} +31 -340
- package/dist/chunk-QFYI6AMN.js.map +1 -0
- package/dist/chunk-U5T7ZYXT.js +346 -0
- package/dist/chunk-U5T7ZYXT.js.map +1 -0
- package/dist/index.js +25 -13
- package/dist/index.js.map +1 -1
- package/dist/relay-token-Z4JZFPQ5.js +81 -0
- package/dist/relay-token-Z4JZFPQ5.js.map +1 -0
- package/dist/serve.js +54 -200
- package/dist/serve.js.map +1 -1
- package/dist/session-worker.js +3 -2
- package/dist/session-worker.js.map +1 -1
- package/dist/{terminal-ES6I5W32.js → terminal-FJAIRC73.js} +13 -9
- package/dist/{terminal-ES6I5W32.js.map → terminal-FJAIRC73.js.map} +1 -1
- package/package.json +3 -3
- package/dist/chunk-JGGDVMY5.js.map +0 -1
- package/dist/chunk-RFBTVZ2X.js.map +0 -1
- /package/dist/{chunk-ODK6N2NP.js.map → chunk-7XMJMVIL.js.map} +0 -0
- /package/dist/{chunk-TX6HNHDB.js.map → chunk-BMVYMCKF.js.map} +0 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SessionState
|
|
4
|
+
} from "./chunk-QFYI6AMN.js";
|
|
5
|
+
|
|
6
|
+
// src/ipc/ipc-protocol.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
// src/ipc/line-buffer.ts
|
|
10
|
+
import { Transform } from "stream";
|
|
11
|
+
var LineBuffer = class extends Transform {
|
|
12
|
+
buffer = "";
|
|
13
|
+
_transform(chunk, _encoding, callback) {
|
|
14
|
+
this.buffer += typeof chunk === "string" ? chunk : chunk.toString();
|
|
15
|
+
const segments = this.buffer.split("\n");
|
|
16
|
+
this.buffer = segments.pop();
|
|
17
|
+
for (const segment of segments) {
|
|
18
|
+
if (segment.length > 0) {
|
|
19
|
+
this.push(segment);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
callback();
|
|
23
|
+
}
|
|
24
|
+
_flush(callback) {
|
|
25
|
+
if (this.buffer.length > 0) {
|
|
26
|
+
this.push(this.buffer);
|
|
27
|
+
this.buffer = "";
|
|
28
|
+
}
|
|
29
|
+
callback();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// src/ipc/ipc-protocol.ts
|
|
34
|
+
var IPC_BINARY_MARKER = 0;
|
|
35
|
+
function encodeBinaryIpcFrame(sessionId, data, outputSeq) {
|
|
36
|
+
const sessionIdBuf = Buffer.from(sessionId, "utf-8");
|
|
37
|
+
const payloadLen = 1 + sessionIdBuf.length + 4 + data.length;
|
|
38
|
+
const frame = Buffer.alloc(1 + 4 + payloadLen);
|
|
39
|
+
let offset = 0;
|
|
40
|
+
frame[offset] = IPC_BINARY_MARKER;
|
|
41
|
+
offset += 1;
|
|
42
|
+
frame.writeUInt32LE(payloadLen, offset);
|
|
43
|
+
offset += 4;
|
|
44
|
+
frame[offset] = sessionIdBuf.length;
|
|
45
|
+
offset += 1;
|
|
46
|
+
sessionIdBuf.copy(frame, offset);
|
|
47
|
+
offset += sessionIdBuf.length;
|
|
48
|
+
frame.writeUInt32LE(outputSeq, offset);
|
|
49
|
+
offset += 4;
|
|
50
|
+
data.copy(frame, offset);
|
|
51
|
+
return frame;
|
|
52
|
+
}
|
|
53
|
+
var sessionStateValues = Object.values(SessionState);
|
|
54
|
+
var ProviderHookContextSchema = z.object({
|
|
55
|
+
provider: z.enum(["claude", "codex"]),
|
|
56
|
+
sessionId: z.string(),
|
|
57
|
+
hookUrl: z.string(),
|
|
58
|
+
marker: z.string(),
|
|
59
|
+
token: z.string()
|
|
60
|
+
});
|
|
61
|
+
var IpcMessageSchema = z.discriminatedUnion("type", [
|
|
62
|
+
// 客户端请求创建新会话,sessionId 可选用于重连时复用
|
|
63
|
+
z.object({
|
|
64
|
+
type: z.literal("session_create_request"),
|
|
65
|
+
name: z.string().optional(),
|
|
66
|
+
mode: z.enum(["pty", "json"]),
|
|
67
|
+
provider: z.enum(["claude", "codex"]),
|
|
68
|
+
cwd: z.string(),
|
|
69
|
+
pid: z.number(),
|
|
70
|
+
sessionId: z.string().optional()
|
|
71
|
+
}),
|
|
72
|
+
// 服务端响应创建会话
|
|
73
|
+
z.object({
|
|
74
|
+
type: z.literal("session_create_response"),
|
|
75
|
+
sessionId: z.string(),
|
|
76
|
+
error: z.string().optional(),
|
|
77
|
+
hook: ProviderHookContextSchema.optional()
|
|
78
|
+
}),
|
|
79
|
+
// 客户端请求终止会话
|
|
80
|
+
z.object({
|
|
81
|
+
type: z.literal("session_terminate_request"),
|
|
82
|
+
sessionId: z.string()
|
|
83
|
+
}),
|
|
84
|
+
// 服务端响应终止会话
|
|
85
|
+
z.object({
|
|
86
|
+
type: z.literal("session_terminate_response"),
|
|
87
|
+
sessionId: z.string(),
|
|
88
|
+
success: z.boolean()
|
|
89
|
+
}),
|
|
90
|
+
// 客户端向服务端注册 PTY 会话
|
|
91
|
+
z.object({
|
|
92
|
+
type: z.literal("pty_register"),
|
|
93
|
+
sessionId: z.string(),
|
|
94
|
+
pid: z.number()
|
|
95
|
+
}),
|
|
96
|
+
// 客户端取消注册 PTY 会话
|
|
97
|
+
z.object({
|
|
98
|
+
type: z.literal("pty_deregister"),
|
|
99
|
+
sessionId: z.string()
|
|
100
|
+
}),
|
|
101
|
+
// 输入,从服务端转发到客户端的 PTY stdin(手机远程输入注入)
|
|
102
|
+
z.object({
|
|
103
|
+
type: z.literal("pty_input"),
|
|
104
|
+
sessionId: z.string(),
|
|
105
|
+
data: z.string()
|
|
106
|
+
}),
|
|
107
|
+
// serve → terminal:Web 端移除本地终端会话时,只断开远程视图,不杀本地 CLI。
|
|
108
|
+
z.object({
|
|
109
|
+
type: z.literal("pty_detach"),
|
|
110
|
+
sessionId: z.string()
|
|
111
|
+
}),
|
|
112
|
+
// 服务端广播会话状态变更
|
|
113
|
+
z.object({
|
|
114
|
+
type: z.literal("session_status_update"),
|
|
115
|
+
sessionId: z.string(),
|
|
116
|
+
state: z.enum(sessionStateValues)
|
|
117
|
+
}),
|
|
118
|
+
// 错误响应
|
|
119
|
+
z.object({
|
|
120
|
+
type: z.literal("error"),
|
|
121
|
+
message: z.string(),
|
|
122
|
+
code: z.string().optional()
|
|
123
|
+
}),
|
|
124
|
+
// 客户端请求服务状态(含 relay 连接信息和 worker 状态)
|
|
125
|
+
z.object({
|
|
126
|
+
type: z.literal("service_status_request")
|
|
127
|
+
}),
|
|
128
|
+
// 服务端响应增强版服务状态
|
|
129
|
+
z.object({
|
|
130
|
+
type: z.literal("service_status_response"),
|
|
131
|
+
config: z.object({
|
|
132
|
+
profile: z.string().optional(),
|
|
133
|
+
relayName: z.string(),
|
|
134
|
+
relayNameSource: z.enum(["cli", "profile"]),
|
|
135
|
+
relayUrl: z.string().optional(),
|
|
136
|
+
relayUrlSource: z.enum(["env", "file", "none"]),
|
|
137
|
+
relayTokenSource: z.enum(["env", "file", "none"]),
|
|
138
|
+
hookPort: z.number(),
|
|
139
|
+
hookPortSource: z.enum(["env", "file", "default"])
|
|
140
|
+
}),
|
|
141
|
+
relay: z.object({
|
|
142
|
+
connected: z.boolean(),
|
|
143
|
+
proxyId: z.string(),
|
|
144
|
+
reconnectAttempt: z.number(),
|
|
145
|
+
queueDepth: z.number()
|
|
146
|
+
}).nullable(),
|
|
147
|
+
sessions: z.array(
|
|
148
|
+
z.object({
|
|
149
|
+
id: z.string(),
|
|
150
|
+
mode: z.enum(["pty", "json"]),
|
|
151
|
+
state: z.enum(sessionStateValues),
|
|
152
|
+
createdAt: z.string(),
|
|
153
|
+
name: z.string().optional(),
|
|
154
|
+
hasWorker: z.boolean()
|
|
155
|
+
})
|
|
156
|
+
)
|
|
157
|
+
}),
|
|
158
|
+
// terminal → serve:终端标题变化,由 xterm onTitleChange 触发
|
|
159
|
+
z.object({
|
|
160
|
+
type: z.literal("pty_title_change"),
|
|
161
|
+
sessionId: z.string(),
|
|
162
|
+
title: z.string()
|
|
163
|
+
}),
|
|
164
|
+
// terminal → serve:local runtime 观察到的 PTY 语义事件。
|
|
165
|
+
// 下面 state 枚举必须与 src/common/osc-extractor.ts 的 PtySemanticState 保持一致。
|
|
166
|
+
z.object({
|
|
167
|
+
type: z.literal("pty_semantic_event"),
|
|
168
|
+
sessionId: z.string(),
|
|
169
|
+
state: z.enum(["working", "turn_complete", "approval_wait", "mid_pause"]),
|
|
170
|
+
title: z.string().optional(),
|
|
171
|
+
tool: z.string().optional()
|
|
172
|
+
}),
|
|
173
|
+
// terminal → serve:终端尺寸变化
|
|
174
|
+
z.object({
|
|
175
|
+
type: z.literal("pty_resize"),
|
|
176
|
+
sessionId: z.string(),
|
|
177
|
+
cols: z.number(),
|
|
178
|
+
rows: z.number()
|
|
179
|
+
}),
|
|
180
|
+
// serve → terminal:请求 HeadlessTerminal serialize() 快照
|
|
181
|
+
z.object({
|
|
182
|
+
type: z.literal("pty_subscribe"),
|
|
183
|
+
sessionId: z.string(),
|
|
184
|
+
requestId: z.string().optional()
|
|
185
|
+
}),
|
|
186
|
+
// terminal → serve:serialize() 结果
|
|
187
|
+
z.object({
|
|
188
|
+
type: z.literal("pty_snapshot"),
|
|
189
|
+
sessionId: z.string(),
|
|
190
|
+
cols: z.number(),
|
|
191
|
+
rows: z.number(),
|
|
192
|
+
data: z.string(),
|
|
193
|
+
outputSeq: z.number().int().nonnegative(),
|
|
194
|
+
requestId: z.string().optional()
|
|
195
|
+
}),
|
|
196
|
+
// serve → terminal:relay 连接状态变更,供终端给用户显示 remote viewing 是否通畅
|
|
197
|
+
z.object({
|
|
198
|
+
type: z.literal("bridge_status"),
|
|
199
|
+
connected: z.boolean()
|
|
200
|
+
})
|
|
201
|
+
]);
|
|
202
|
+
var WorkerMessageSchema = z.discriminatedUnion("type", [
|
|
203
|
+
// serve → worker: 发送用户输入给 claude
|
|
204
|
+
z.object({
|
|
205
|
+
type: z.literal("worker_input"),
|
|
206
|
+
content: z.string()
|
|
207
|
+
}),
|
|
208
|
+
// serve → worker: 停止 claude 进程
|
|
209
|
+
z.object({
|
|
210
|
+
type: z.literal("worker_stop")
|
|
211
|
+
}),
|
|
212
|
+
// serve → worker: 工具审批响应
|
|
213
|
+
z.object({
|
|
214
|
+
type: z.literal("worker_approval_response"),
|
|
215
|
+
requestId: z.string(),
|
|
216
|
+
behavior: z.enum(["allow", "deny"]),
|
|
217
|
+
message: z.string().optional()
|
|
218
|
+
}),
|
|
219
|
+
// worker → serve: claude 输出事件(带序列号)
|
|
220
|
+
z.object({
|
|
221
|
+
type: z.literal("worker_event"),
|
|
222
|
+
seq: z.number(),
|
|
223
|
+
event: z.record(z.string(), z.unknown())
|
|
224
|
+
}),
|
|
225
|
+
// worker → serve: claude 进程退出
|
|
226
|
+
z.object({
|
|
227
|
+
type: z.literal("worker_exit"),
|
|
228
|
+
code: z.number()
|
|
229
|
+
}),
|
|
230
|
+
// worker → serve: 工具审批请求
|
|
231
|
+
z.object({
|
|
232
|
+
type: z.literal("worker_approval_request"),
|
|
233
|
+
requestId: z.string(),
|
|
234
|
+
toolName: z.string(),
|
|
235
|
+
input: z.record(z.string(), z.unknown())
|
|
236
|
+
}),
|
|
237
|
+
// worker → serve: worker 就绪,claude 已启动
|
|
238
|
+
z.object({
|
|
239
|
+
type: z.literal("worker_ready"),
|
|
240
|
+
pid: z.number()
|
|
241
|
+
}),
|
|
242
|
+
// worker → serve: 从 stream-json 的 system.init 事件捕获 Claude CLI 侧的 session ID
|
|
243
|
+
// proxy 拿它来读 ~/.claude/projects/.../<id>.jsonl 历史或后续 --resume
|
|
244
|
+
z.object({
|
|
245
|
+
type: z.literal("worker_claude_session_id"),
|
|
246
|
+
sessionId: z.string()
|
|
247
|
+
}),
|
|
248
|
+
// serve → worker: 将指定工具加入会话白名单,后续同名工具自动审批
|
|
249
|
+
z.object({
|
|
250
|
+
type: z.literal("worker_whitelist_add"),
|
|
251
|
+
toolName: z.string()
|
|
252
|
+
})
|
|
253
|
+
]);
|
|
254
|
+
function serializeWorkerMsg(msg) {
|
|
255
|
+
return JSON.stringify(msg) + "\n";
|
|
256
|
+
}
|
|
257
|
+
function createWorkerReader(stream, onMessage) {
|
|
258
|
+
const lineBuffer = new LineBuffer();
|
|
259
|
+
lineBuffer.on("data", (line) => {
|
|
260
|
+
const str = typeof line === "string" ? line : line.toString();
|
|
261
|
+
if (str.length === 0) return;
|
|
262
|
+
try {
|
|
263
|
+
const raw = JSON.parse(str);
|
|
264
|
+
const result = WorkerMessageSchema.safeParse(raw);
|
|
265
|
+
if (result.success) {
|
|
266
|
+
onMessage(result.data);
|
|
267
|
+
} else {
|
|
268
|
+
stream.emit(
|
|
269
|
+
"error",
|
|
270
|
+
new Error(`Worker message validation failed: ${result.error.message}`)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
stream.emit("error", new Error("Worker message parse error", { cause: err }));
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
stream.pipe(lineBuffer);
|
|
278
|
+
}
|
|
279
|
+
function serializeIpc(msg) {
|
|
280
|
+
return JSON.stringify(msg) + "\n";
|
|
281
|
+
}
|
|
282
|
+
function createIpcReader(stream, onMessage, onBinaryFrame) {
|
|
283
|
+
let buf = Buffer.alloc(0);
|
|
284
|
+
let disposed = false;
|
|
285
|
+
function drain() {
|
|
286
|
+
while (buf.length > 0) {
|
|
287
|
+
if (buf[0] === IPC_BINARY_MARKER) {
|
|
288
|
+
if (buf.length < 5) return;
|
|
289
|
+
const payloadLen = buf.readUInt32LE(1);
|
|
290
|
+
const totalFrameLen = 1 + 4 + payloadLen;
|
|
291
|
+
if (buf.length < totalFrameLen) return;
|
|
292
|
+
const payloadStart = 5;
|
|
293
|
+
const sessionIdLen = buf[payloadStart];
|
|
294
|
+
const sessionId = buf.subarray(payloadStart + 1, payloadStart + 1 + sessionIdLen).toString("utf-8");
|
|
295
|
+
const seqOffset = payloadStart + 1 + sessionIdLen;
|
|
296
|
+
const outputSeq = buf.readUInt32LE(seqOffset);
|
|
297
|
+
const ptyData = Buffer.from(buf.subarray(seqOffset + 4, totalFrameLen));
|
|
298
|
+
if (onBinaryFrame) {
|
|
299
|
+
onBinaryFrame(sessionId, ptyData, outputSeq);
|
|
300
|
+
}
|
|
301
|
+
buf = buf.subarray(totalFrameLen);
|
|
302
|
+
} else {
|
|
303
|
+
const newlineIdx = buf.indexOf(10);
|
|
304
|
+
if (newlineIdx === -1) return;
|
|
305
|
+
const line = buf.subarray(0, newlineIdx).toString("utf-8");
|
|
306
|
+
buf = buf.subarray(newlineIdx + 1);
|
|
307
|
+
if (line.length === 0) continue;
|
|
308
|
+
try {
|
|
309
|
+
const raw = JSON.parse(line);
|
|
310
|
+
const result = IpcMessageSchema.safeParse(raw);
|
|
311
|
+
if (result.success) {
|
|
312
|
+
onMessage(result.data);
|
|
313
|
+
} else {
|
|
314
|
+
stream.emit(
|
|
315
|
+
"error",
|
|
316
|
+
new Error(`IPC message validation failed: ${result.error.message}`)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
} catch (err) {
|
|
320
|
+
stream.emit("error", new Error("IPC message parse error", { cause: err }));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function onData(chunk) {
|
|
326
|
+
if (disposed) return;
|
|
327
|
+
const incoming = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
328
|
+
buf = Buffer.concat([buf, incoming]);
|
|
329
|
+
drain();
|
|
330
|
+
}
|
|
331
|
+
stream.on("data", onData);
|
|
332
|
+
return () => {
|
|
333
|
+
disposed = true;
|
|
334
|
+
stream.off("data", onData);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export {
|
|
339
|
+
LineBuffer,
|
|
340
|
+
encodeBinaryIpcFrame,
|
|
341
|
+
serializeWorkerMsg,
|
|
342
|
+
createWorkerReader,
|
|
343
|
+
serializeIpc,
|
|
344
|
+
createIpcReader
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=chunk-U5T7ZYXT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ipc/ipc-protocol.ts","../src/ipc/line-buffer.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { SessionState } from \"@dev-anywhere/shared\";\nimport { LineBuffer } from \"./line-buffer.js\";\n\n// IPC binary 帧标记字节,0x00 不可能是 JSON 行的首字节(JSON 以 '{' 开头)\nexport const IPC_BINARY_MARKER = 0x00;\n\n// 编码 binary PTY 数据帧用于 IPC 传输\n// 格式: [1B 0x00 marker][4B payload_len uint32LE][1B sessionId_len][sessionId UTF-8][4B outputSeq uint32LE][PTY data]\nexport function encodeBinaryIpcFrame(sessionId: string, data: Buffer, outputSeq: number): Buffer {\n const sessionIdBuf = Buffer.from(sessionId, \"utf-8\");\n const payloadLen = 1 + sessionIdBuf.length + 4 + data.length;\n const frame = Buffer.alloc(1 + 4 + payloadLen);\n let offset = 0;\n frame[offset] = IPC_BINARY_MARKER;\n offset += 1;\n frame.writeUInt32LE(payloadLen, offset);\n offset += 4;\n frame[offset] = sessionIdBuf.length;\n offset += 1;\n sessionIdBuf.copy(frame, offset);\n offset += sessionIdBuf.length;\n frame.writeUInt32LE(outputSeq, offset);\n offset += 4;\n data.copy(frame, offset);\n return frame;\n}\n\nconst sessionStateValues = Object.values(SessionState) as [SessionState, ...SessionState[]];\n\nconst ProviderHookContextSchema = z.object({\n provider: z.enum([\"claude\", \"codex\"]),\n sessionId: z.string(),\n hookUrl: z.string(),\n marker: z.string(),\n token: z.string(),\n});\n\n// IPC 消息 schema,客户端与服务端通过 Unix domain socket 使用 NDJSON 通信\nexport const IpcMessageSchema = z.discriminatedUnion(\"type\", [\n // 客户端请求创建新会话,sessionId 可选用于重连时复用\n z.object({\n type: z.literal(\"session_create_request\"),\n name: z.string().optional(),\n mode: z.enum([\"pty\", \"json\"]),\n provider: z.enum([\"claude\", \"codex\"]),\n cwd: z.string(),\n pid: z.number(),\n sessionId: z.string().optional(),\n }),\n\n // 服务端响应创建会话\n z.object({\n type: z.literal(\"session_create_response\"),\n sessionId: z.string(),\n error: z.string().optional(),\n hook: ProviderHookContextSchema.optional(),\n }),\n\n // 客户端请求终止会话\n z.object({\n type: z.literal(\"session_terminate_request\"),\n sessionId: z.string(),\n }),\n\n // 服务端响应终止会话\n z.object({\n type: z.literal(\"session_terminate_response\"),\n sessionId: z.string(),\n success: z.boolean(),\n }),\n\n // 客户端向服务端注册 PTY 会话\n z.object({\n type: z.literal(\"pty_register\"),\n sessionId: z.string(),\n pid: z.number(),\n }),\n\n // 客户端取消注册 PTY 会话\n z.object({\n type: z.literal(\"pty_deregister\"),\n sessionId: z.string(),\n }),\n\n // 输入,从服务端转发到客户端的 PTY stdin(手机远程输入注入)\n z.object({\n type: z.literal(\"pty_input\"),\n sessionId: z.string(),\n data: z.string(),\n }),\n\n // serve → terminal:Web 端移除本地终端会话时,只断开远程视图,不杀本地 CLI。\n z.object({\n type: z.literal(\"pty_detach\"),\n sessionId: z.string(),\n }),\n\n // 服务端广播会话状态变更\n z.object({\n type: z.literal(\"session_status_update\"),\n sessionId: z.string(),\n state: z.enum(sessionStateValues),\n }),\n\n // 错误响应\n z.object({\n type: z.literal(\"error\"),\n message: z.string(),\n code: z.string().optional(),\n }),\n\n // 客户端请求服务状态(含 relay 连接信息和 worker 状态)\n z.object({\n type: z.literal(\"service_status_request\"),\n }),\n\n // 服务端响应增强版服务状态\n z.object({\n type: z.literal(\"service_status_response\"),\n config: z.object({\n profile: z.string().optional(),\n relayName: z.string(),\n relayNameSource: z.enum([\"cli\", \"profile\"]),\n relayUrl: z.string().optional(),\n relayUrlSource: z.enum([\"env\", \"file\", \"none\"]),\n relayTokenSource: z.enum([\"env\", \"file\", \"none\"]),\n hookPort: z.number(),\n hookPortSource: z.enum([\"env\", \"file\", \"default\"]),\n }),\n relay: z\n .object({\n connected: z.boolean(),\n proxyId: z.string(),\n reconnectAttempt: z.number(),\n queueDepth: z.number(),\n })\n .nullable(),\n sessions: z.array(\n z.object({\n id: z.string(),\n mode: z.enum([\"pty\", \"json\"]),\n state: z.enum(sessionStateValues),\n createdAt: z.string(),\n name: z.string().optional(),\n hasWorker: z.boolean(),\n }),\n ),\n }),\n\n // terminal → serve:终端标题变化,由 xterm onTitleChange 触发\n z.object({\n type: z.literal(\"pty_title_change\"),\n sessionId: z.string(),\n title: z.string(),\n }),\n\n // terminal → serve:local runtime 观察到的 PTY 语义事件。\n // 下面 state 枚举必须与 src/common/osc-extractor.ts 的 PtySemanticState 保持一致。\n z.object({\n type: z.literal(\"pty_semantic_event\"),\n sessionId: z.string(),\n state: z.enum([\"working\", \"turn_complete\", \"approval_wait\", \"mid_pause\"]),\n title: z.string().optional(),\n tool: z.string().optional(),\n }),\n\n // terminal → serve:终端尺寸变化\n z.object({\n type: z.literal(\"pty_resize\"),\n sessionId: z.string(),\n cols: z.number(),\n rows: z.number(),\n }),\n\n // serve → terminal:请求 HeadlessTerminal serialize() 快照\n z.object({\n type: z.literal(\"pty_subscribe\"),\n sessionId: z.string(),\n requestId: z.string().optional(),\n }),\n\n // terminal → serve:serialize() 结果\n z.object({\n type: z.literal(\"pty_snapshot\"),\n sessionId: z.string(),\n cols: z.number(),\n rows: z.number(),\n data: z.string(),\n outputSeq: z.number().int().nonnegative(),\n requestId: z.string().optional(),\n }),\n\n // serve → terminal:relay 连接状态变更,供终端给用户显示 remote viewing 是否通畅\n z.object({\n type: z.literal(\"bridge_status\"),\n connected: z.boolean(),\n }),\n]);\n\n// serve 与 session-worker 之间的通信协议\nexport const WorkerMessageSchema = z.discriminatedUnion(\"type\", [\n // serve → worker: 发送用户输入给 claude\n z.object({\n type: z.literal(\"worker_input\"),\n content: z.string(),\n }),\n\n // serve → worker: 停止 claude 进程\n z.object({\n type: z.literal(\"worker_stop\"),\n }),\n\n // serve → worker: 工具审批响应\n z.object({\n type: z.literal(\"worker_approval_response\"),\n requestId: z.string(),\n behavior: z.enum([\"allow\", \"deny\"]),\n message: z.string().optional(),\n }),\n\n // worker → serve: claude 输出事件(带序列号)\n z.object({\n type: z.literal(\"worker_event\"),\n seq: z.number(),\n event: z.record(z.string(), z.unknown()),\n }),\n\n // worker → serve: claude 进程退出\n z.object({\n type: z.literal(\"worker_exit\"),\n code: z.number(),\n }),\n\n // worker → serve: 工具审批请求\n z.object({\n type: z.literal(\"worker_approval_request\"),\n requestId: z.string(),\n toolName: z.string(),\n input: z.record(z.string(), z.unknown()),\n }),\n\n // worker → serve: worker 就绪,claude 已启动\n z.object({\n type: z.literal(\"worker_ready\"),\n pid: z.number(),\n }),\n\n // worker → serve: 从 stream-json 的 system.init 事件捕获 Claude CLI 侧的 session ID\n // proxy 拿它来读 ~/.claude/projects/.../<id>.jsonl 历史或后续 --resume\n z.object({\n type: z.literal(\"worker_claude_session_id\"),\n sessionId: z.string(),\n }),\n\n // serve → worker: 将指定工具加入会话白名单,后续同名工具自动审批\n z.object({\n type: z.literal(\"worker_whitelist_add\"),\n toolName: z.string(),\n }),\n]);\n\nexport type WorkerMessage = z.infer<typeof WorkerMessageSchema>;\n\nexport function serializeWorkerMsg(msg: WorkerMessage): string {\n return JSON.stringify(msg) + \"\\n\";\n}\n\nexport function createWorkerReader(\n stream: NodeJS.ReadableStream,\n onMessage: (msg: WorkerMessage) => void,\n): void {\n const lineBuffer = new LineBuffer();\n lineBuffer.on(\"data\", (line: Buffer | string) => {\n const str = typeof line === \"string\" ? line : line.toString();\n if (str.length === 0) return;\n try {\n const raw = JSON.parse(str);\n const result = WorkerMessageSchema.safeParse(raw);\n if (result.success) {\n onMessage(result.data);\n } else {\n stream.emit(\n \"error\",\n new Error(`Worker message validation failed: ${result.error.message}`),\n );\n }\n } catch (err) {\n stream.emit(\"error\", new Error(\"Worker message parse error\", { cause: err }));\n }\n });\n (stream as NodeJS.ReadableStream).pipe(lineBuffer);\n}\n\nexport type IpcMessage = z.infer<typeof IpcMessageSchema>;\n\n// 将 IPC 消息序列化为 NDJSON 格式的字符串\nexport function serializeIpc(msg: IpcMessage): string {\n return JSON.stringify(msg) + \"\\n\";\n}\n\n// 混合协议 IPC 读取器,支持 NDJSON 控制消息和 binary PTY 帧。\n// binary 帧以 0x00 开头,NDJSON 行以 '{' 开头,通过首字节区分。\n// 返回 dispose 函数用于摘掉 'data' 监听,长连接可以忽略,一次性等待(如 waitForMessage)必须调用避免累积 listener 重复解析每条消息。\nexport function createIpcReader(\n stream: NodeJS.ReadableStream,\n onMessage: (msg: IpcMessage) => void,\n onBinaryFrame?: (sessionId: string, data: Buffer, outputSeq: number) => void,\n): () => void {\n let buf = Buffer.alloc(0);\n let disposed = false;\n\n // 解析状态机:不断消费 buf 中的完整消息\n function drain(): void {\n while (buf.length > 0) {\n if (buf[0] === IPC_BINARY_MARKER) {\n // binary 帧: [1B marker][4B payload_len LE][payload]\n // 需要至少 5 字节才能读取 header\n if (buf.length < 5) return;\n const payloadLen = buf.readUInt32LE(1);\n const totalFrameLen = 1 + 4 + payloadLen;\n if (buf.length < totalFrameLen) return;\n\n // 解析 payload: [1B sessionId_len][sessionId][4B outputSeq][pty data]\n const payloadStart = 5;\n const sessionIdLen = buf[payloadStart];\n const sessionId = buf\n .subarray(payloadStart + 1, payloadStart + 1 + sessionIdLen)\n .toString(\"utf-8\");\n const seqOffset = payloadStart + 1 + sessionIdLen;\n const outputSeq = buf.readUInt32LE(seqOffset);\n const ptyData = Buffer.from(buf.subarray(seqOffset + 4, totalFrameLen));\n\n if (onBinaryFrame) {\n onBinaryFrame(sessionId, ptyData, outputSeq);\n }\n\n buf = buf.subarray(totalFrameLen);\n } else {\n // NDJSON 行: 找 \\n 分隔符\n const newlineIdx = buf.indexOf(0x0a); // '\\n'\n if (newlineIdx === -1) return;\n\n const line = buf.subarray(0, newlineIdx).toString(\"utf-8\");\n buf = buf.subarray(newlineIdx + 1);\n\n if (line.length === 0) continue;\n\n try {\n const raw = JSON.parse(line);\n const result = IpcMessageSchema.safeParse(raw);\n if (result.success) {\n onMessage(result.data);\n } else {\n stream.emit(\n \"error\",\n new Error(`IPC message validation failed: ${result.error.message}`),\n );\n }\n } catch (err) {\n stream.emit(\"error\", new Error(\"IPC message parse error\", { cause: err }));\n }\n }\n }\n }\n\n function onData(chunk: Buffer | string): void {\n if (disposed) return;\n const incoming = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n buf = Buffer.concat([buf, incoming]);\n drain();\n }\n\n stream.on(\"data\", onData);\n\n return () => {\n disposed = true;\n stream.off(\"data\", onData);\n };\n}\n","import { Transform, type TransformCallback } from \"node:stream\";\n\n// 将任意 data 事件分割为完整的 \\n 分隔行\n// Node.js data 事件不保证按行边界分割,此 Transform 保证每次 push 一个完整行\nexport class LineBuffer extends Transform {\n private buffer = \"\";\n\n _transform(chunk: Buffer | string, _encoding: BufferEncoding, callback: TransformCallback): void {\n this.buffer += typeof chunk === \"string\" ? chunk : chunk.toString();\n const segments = this.buffer.split(\"\\n\");\n // 最后一段可能是不完整行,保留到 buffer\n this.buffer = segments.pop()!;\n\n for (const segment of segments) {\n if (segment.length > 0) {\n this.push(segment);\n }\n }\n callback();\n }\n\n _flush(callback: TransformCallback): void {\n if (this.buffer.length > 0) {\n this.push(this.buffer);\n this.buffer = \"\";\n }\n callback();\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
daemonRelayArgs,
|
|
4
4
|
setDesiredDaemonRelay
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-BMVYMCKF.js";
|
|
6
6
|
import {
|
|
7
7
|
spawnScript
|
|
8
8
|
} from "./chunk-ZUWAB67J.js";
|
|
9
|
+
import {
|
|
10
|
+
createIpcReader,
|
|
11
|
+
serializeIpc
|
|
12
|
+
} from "./chunk-U5T7ZYXT.js";
|
|
9
13
|
import {
|
|
10
14
|
CONFIG_PATH,
|
|
11
15
|
PID_PATH,
|
|
@@ -13,12 +17,10 @@ import {
|
|
|
13
17
|
SERVICE_LOG_PATH,
|
|
14
18
|
SOCK_PATH,
|
|
15
19
|
STOPPED_PATH,
|
|
16
|
-
createIpcReader,
|
|
17
20
|
ensureProfileWorkspace,
|
|
18
21
|
initWorkspace,
|
|
19
|
-
isInitialized
|
|
20
|
-
|
|
21
|
-
} from "./chunk-RFBTVZ2X.js";
|
|
22
|
+
isInitialized
|
|
23
|
+
} from "./chunk-QFYI6AMN.js";
|
|
22
24
|
|
|
23
25
|
// src/index.ts
|
|
24
26
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -127,17 +129,17 @@ function showStatus() {
|
|
|
127
129
|
log(`Daemon: profile ${config.profile ?? PROFILE_NAME}`);
|
|
128
130
|
log(`Relay: ${config.relayName} (${config.relayNameSource})`);
|
|
129
131
|
log(`Config: relay ${config.relayUrl ?? "(unset)"} (${config.relayUrlSource})`);
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
132
|
+
const relay2 = msg.relay;
|
|
133
|
+
if (!relay2) {
|
|
132
134
|
log("Relay: not configured");
|
|
133
|
-
} else if (
|
|
134
|
-
log(`Relay: connected (proxy: ${
|
|
135
|
+
} else if (relay2.connected) {
|
|
136
|
+
log(`Relay: connected (proxy: ${relay2.proxyId})`);
|
|
135
137
|
log(
|
|
136
|
-
` queue depth: ${
|
|
138
|
+
` queue depth: ${relay2.queueDepth}, reconnect attempts: ${relay2.reconnectAttempt}`
|
|
137
139
|
);
|
|
138
140
|
} else {
|
|
139
141
|
log(
|
|
140
|
-
`Relay: disconnected (proxy: ${
|
|
142
|
+
`Relay: disconnected (proxy: ${relay2.proxyId}, reconnecting: attempt ${relay2.reconnectAttempt}, queued: ${relay2.queueDepth})`
|
|
141
143
|
);
|
|
142
144
|
}
|
|
143
145
|
log("");
|
|
@@ -232,12 +234,11 @@ async function startDaemon(options) {
|
|
|
232
234
|
}
|
|
233
235
|
process.exit(1);
|
|
234
236
|
}
|
|
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 () => {
|
|
237
|
+
var program = new Command("dev-anywhere").description("Dev Anywhere - transparent local AI CLI proxy with remote control").version(pkg.version, "-v, --version").option("--profile <name>", "Use an isolated local proxy profile").allowUnknownOption().allowExcessArguments().action(async () => {
|
|
236
238
|
if (!isInitialized()) {
|
|
237
239
|
console.error(`Dev Anywhere is not initialized. Run "dev-anywhere init" first.`);
|
|
238
240
|
process.exit(1);
|
|
239
241
|
}
|
|
240
|
-
const { startTerminal } = await import("./terminal-ES6I5W32.js");
|
|
241
242
|
let invocation;
|
|
242
243
|
try {
|
|
243
244
|
invocation = extractAgentInvocation(cliArgsWithoutProfile);
|
|
@@ -245,6 +246,7 @@ var program = new Command("dev-anywhere").description("Dev Anywhere - transparen
|
|
|
245
246
|
console.error(err instanceof Error ? err.message : String(err));
|
|
246
247
|
process.exit(1);
|
|
247
248
|
}
|
|
249
|
+
const { startTerminal } = await import("./terminal-FJAIRC73.js");
|
|
248
250
|
const { provider, args } = invocation;
|
|
249
251
|
await startTerminal(args, provider);
|
|
250
252
|
});
|
|
@@ -292,6 +294,16 @@ serve.command("restart").description("Restart the background service").option("-
|
|
|
292
294
|
await startDaemon({ relayName: opts.relay });
|
|
293
295
|
});
|
|
294
296
|
program.addCommand(serve);
|
|
297
|
+
var relay = new Command("relay").description("Inspect and manage relay configuration");
|
|
298
|
+
relay.command("token").description("Print the relay's current client token (auth: proxy token)").option("--relay <name>", "Use a named relay from config").action(async (opts) => {
|
|
299
|
+
if (!isInitialized()) {
|
|
300
|
+
console.error(`Dev Anywhere is not initialized. Run "dev-anywhere init" first.`);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
const { runRelayTokenCommand } = await import("./relay-token-Z4JZFPQ5.js");
|
|
304
|
+
await runRelayTokenCommand({ relayName: opts.relay });
|
|
305
|
+
});
|
|
306
|
+
program.addCommand(relay);
|
|
295
307
|
program.command("init").description("Initialize dev-anywhere workspace (~/.dev-anywhere)").action(() => {
|
|
296
308
|
if (isInitialized()) {
|
|
297
309
|
console.log(`Already initialized. Config at ${CONFIG_PATH}`);
|
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 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":[]}
|
|
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, \"-v, --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 // 参数校验放在 dynamic import 之前:错误参数路径不应触发 terminal 模块加载,\n // 避免无谓地拉起 PTY/xterm/logger 这些重资源(也避免 logger 文件 IO 副作用)。\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 // 延迟导入 terminal: CLI 的其他子命令(init/stop/status)不需要 PTY + xterm 相关依赖,\n // tsup 基于 dynamic import 自动代码分裂,避免所有命令都为 terminal 付出 14KB 额外启动成本。\n const { startTerminal } = await import(\"./terminal.js\");\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\nconst relay = new Command(\"relay\").description(\"Inspect and manage relay configuration\");\n\nrelay\n .command(\"token\")\n .description(\"Print the relay's current client token (auth: proxy token)\")\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 const { runRelayTokenCommand } = await import(\"./relay-token.js\");\n await runRelayTokenCommand({ relayName: opts.relay });\n });\n\nprogram.addCommand(relay);\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":["relay"]}
|