@cli-remote/local 0.3.3 → 0.4.0
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/index.cjs +686 -75
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -2263,9 +2263,9 @@ var require_websocket = __commonJS({
|
|
|
2263
2263
|
var EventEmitter = require("events");
|
|
2264
2264
|
var https = require("https");
|
|
2265
2265
|
var http = require("http");
|
|
2266
|
-
var
|
|
2266
|
+
var net2 = require("net");
|
|
2267
2267
|
var tls = require("tls");
|
|
2268
|
-
var { randomBytes: randomBytes3, createHash } = require("crypto");
|
|
2268
|
+
var { randomBytes: randomBytes3, createHash: createHash2 } = require("crypto");
|
|
2269
2269
|
var { Duplex, Readable } = require("stream");
|
|
2270
2270
|
var { URL: URL2 } = require("url");
|
|
2271
2271
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2933,7 +2933,7 @@ var require_websocket = __commonJS({
|
|
|
2933
2933
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
2934
2934
|
return;
|
|
2935
2935
|
}
|
|
2936
|
-
const digest =
|
|
2936
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
2937
2937
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
2938
2938
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
2939
2939
|
return;
|
|
@@ -3007,12 +3007,12 @@ var require_websocket = __commonJS({
|
|
|
3007
3007
|
}
|
|
3008
3008
|
function netConnect(options) {
|
|
3009
3009
|
options.path = options.socketPath;
|
|
3010
|
-
return
|
|
3010
|
+
return net2.connect(options);
|
|
3011
3011
|
}
|
|
3012
3012
|
function tlsConnect(options) {
|
|
3013
3013
|
options.path = void 0;
|
|
3014
3014
|
if (!options.servername && options.servername !== "") {
|
|
3015
|
-
options.servername =
|
|
3015
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
3016
3016
|
}
|
|
3017
3017
|
return tls.connect(options);
|
|
3018
3018
|
}
|
|
@@ -3302,7 +3302,7 @@ var require_websocket_server = __commonJS({
|
|
|
3302
3302
|
var EventEmitter = require("events");
|
|
3303
3303
|
var http = require("http");
|
|
3304
3304
|
var { Duplex } = require("stream");
|
|
3305
|
-
var { createHash } = require("crypto");
|
|
3305
|
+
var { createHash: createHash2 } = require("crypto");
|
|
3306
3306
|
var extension2 = require_extension();
|
|
3307
3307
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
3308
3308
|
var subprotocol2 = require_subprotocol();
|
|
@@ -3609,7 +3609,7 @@ var require_websocket_server = __commonJS({
|
|
|
3609
3609
|
);
|
|
3610
3610
|
}
|
|
3611
3611
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
3612
|
-
const digest =
|
|
3612
|
+
const digest = createHash2("sha1").update(key + GUID).digest("base64");
|
|
3613
3613
|
const headers = [
|
|
3614
3614
|
"HTTP/1.1 101 Switching Protocols",
|
|
3615
3615
|
"Upgrade: websocket",
|
|
@@ -9940,9 +9940,9 @@ var require_lib = __commonJS({
|
|
|
9940
9940
|
|
|
9941
9941
|
// src/index.ts
|
|
9942
9942
|
var import_node_fs5 = require("node:fs");
|
|
9943
|
-
var
|
|
9944
|
-
var
|
|
9945
|
-
var
|
|
9943
|
+
var import_node_child_process5 = require("node:child_process");
|
|
9944
|
+
var import_node_os8 = require("node:os");
|
|
9945
|
+
var import_node_path8 = require("node:path");
|
|
9946
9946
|
var import_node_url = require("node:url");
|
|
9947
9947
|
|
|
9948
9948
|
// ../../node_modules/.pnpm/ws@8.21.3/node_modules/ws/wrapper.mjs
|
|
@@ -9957,11 +9957,11 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
9957
9957
|
var wrapper_default = import_websocket.default;
|
|
9958
9958
|
|
|
9959
9959
|
// src/client.ts
|
|
9960
|
-
var
|
|
9960
|
+
var import_node_child_process4 = require("node:child_process");
|
|
9961
9961
|
var import_node_readline = require("node:readline");
|
|
9962
9962
|
var import_node_fs4 = require("node:fs");
|
|
9963
|
-
var
|
|
9964
|
-
var
|
|
9963
|
+
var import_node_os7 = require("node:os");
|
|
9964
|
+
var import_node_path7 = require("node:path");
|
|
9965
9965
|
|
|
9966
9966
|
// ../shared/src/codec.ts
|
|
9967
9967
|
var import_msgpack = __toESM(require_dist(), 1);
|
|
@@ -10031,6 +10031,8 @@ function resolveCommand(cmd) {
|
|
|
10031
10031
|
return { file: "kimi", args: [] };
|
|
10032
10032
|
case "pi":
|
|
10033
10033
|
return { file: "pi", args: [] };
|
|
10034
|
+
case "desktop":
|
|
10035
|
+
throw new Error("desktop sessions are not PTY sessions");
|
|
10034
10036
|
default:
|
|
10035
10037
|
return { file: cmd, args: [] };
|
|
10036
10038
|
}
|
|
@@ -10214,6 +10216,12 @@ var SessionLogger = class {
|
|
|
10214
10216
|
if (this.closed || this.mode === "off") return;
|
|
10215
10217
|
this.write({ ts: Date.now(), event: "forced", cmd });
|
|
10216
10218
|
}
|
|
10219
|
+
/** Desktop display/workspace switch metadata (v0.8). Content is never
|
|
10220
|
+
* logged — this records only WHICH screen/desktop was switched to. */
|
|
10221
|
+
logSwitch(kind, value) {
|
|
10222
|
+
if (this.closed || this.mode === "off") return;
|
|
10223
|
+
this.write({ ts: Date.now(), event: "switch", kind, value });
|
|
10224
|
+
}
|
|
10217
10225
|
logExit(code) {
|
|
10218
10226
|
if (this.closed || this.mode === "off") return;
|
|
10219
10227
|
this.write({ ts: Date.now(), event: "exit", code });
|
|
@@ -10421,6 +10429,347 @@ async function exists(p) {
|
|
|
10421
10429
|
}
|
|
10422
10430
|
}
|
|
10423
10431
|
|
|
10432
|
+
// src/desktop-manager.ts
|
|
10433
|
+
var import_node_child_process = require("node:child_process");
|
|
10434
|
+
var import_node_util = require("node:util");
|
|
10435
|
+
var import_node_net = __toESM(require("node:net"), 1);
|
|
10436
|
+
var execFileP = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
10437
|
+
var X11VNC_START_TIMEOUT_MS = 8e3;
|
|
10438
|
+
var PAUSE_POLL_MS = 25;
|
|
10439
|
+
var EXEC_TIMEOUT_MS = 5e3;
|
|
10440
|
+
var DesktopManager = class {
|
|
10441
|
+
constructor(hooks, config, platform2 = process.platform, execImpl, spawnImpl) {
|
|
10442
|
+
this.hooks = hooks;
|
|
10443
|
+
this.config = config;
|
|
10444
|
+
this.platform = platform2;
|
|
10445
|
+
this.exec = execImpl ?? ((file, args, timeoutMs = EXEC_TIMEOUT_MS) => execFileP(file, args, { timeout: timeoutMs }).then((r) => String(r.stdout ?? "")));
|
|
10446
|
+
this.spawnChildFn = spawnImpl ?? ((file, args) => {
|
|
10447
|
+
const child = (0, import_node_child_process.spawn)(file, args, { stdio: "ignore" });
|
|
10448
|
+
child.unref();
|
|
10449
|
+
return child;
|
|
10450
|
+
});
|
|
10451
|
+
}
|
|
10452
|
+
sessions = /* @__PURE__ */ new Map();
|
|
10453
|
+
exec;
|
|
10454
|
+
spawnChildFn;
|
|
10455
|
+
/** Human-readable reason when the machine cannot serve desktop sessions. */
|
|
10456
|
+
unsupportedReason() {
|
|
10457
|
+
if (!this.config.enabled) {
|
|
10458
|
+
return 'desktop disabled on this device (config.json: {"desktop": {"enabled": true}})';
|
|
10459
|
+
}
|
|
10460
|
+
if (this.platform === "win32") {
|
|
10461
|
+
return "desktop sessions are not supported on Windows yet";
|
|
10462
|
+
}
|
|
10463
|
+
return null;
|
|
10464
|
+
}
|
|
10465
|
+
// -------------------------------------------------------------------------
|
|
10466
|
+
// Enumeration (desktop-query)
|
|
10467
|
+
// -------------------------------------------------------------------------
|
|
10468
|
+
/** Probes displays/workspaces. Degrades gracefully — missing tools yield
|
|
10469
|
+
* empty/unknown values, never throws (web always gets an answer). */
|
|
10470
|
+
async queryInfo() {
|
|
10471
|
+
if (this.platform !== "linux") {
|
|
10472
|
+
return { displays: [], workspaces: null, currentWorkspace: null, canPickDisplay: false };
|
|
10473
|
+
}
|
|
10474
|
+
const [heads, ws] = await Promise.all([this.listHeads().catch(() => []), this.workspaceState().catch(() => null)]);
|
|
10475
|
+
return {
|
|
10476
|
+
displays: heads.map((h) => ({ name: h.name, width: h.w, height: h.h })),
|
|
10477
|
+
workspaces: ws?.count ?? null,
|
|
10478
|
+
currentWorkspace: ws?.current ?? null,
|
|
10479
|
+
canPickDisplay: heads.length > 0
|
|
10480
|
+
};
|
|
10481
|
+
}
|
|
10482
|
+
/** `xrandr --query` connected heads (name + geometry). */
|
|
10483
|
+
async listHeads() {
|
|
10484
|
+
const out = await this.exec("xrandr", ["--query"]);
|
|
10485
|
+
const heads = [];
|
|
10486
|
+
for (const line of out.split("\n")) {
|
|
10487
|
+
const m = /^(\S+) connected (?:primary )?(\d+)x(\d+)\+(\d+)\+(\d+)\s/.exec(line);
|
|
10488
|
+
if (m) heads.push({ name: m[1], w: Number(m[2]), h: Number(m[3]), x: Number(m[4]), y: Number(m[5]) });
|
|
10489
|
+
}
|
|
10490
|
+
return heads;
|
|
10491
|
+
}
|
|
10492
|
+
/** `wmctrl -d` → { count, current }. The current line carries ` * `. */
|
|
10493
|
+
async workspaceState() {
|
|
10494
|
+
const out = await this.exec("wmctrl", ["-d"]);
|
|
10495
|
+
const lines = out.split("\n").filter((l) => l.trim());
|
|
10496
|
+
if (lines.length === 0) return null;
|
|
10497
|
+
const current = lines.findIndex((l) => /(^|\s)\*(\s|$)/.test(l));
|
|
10498
|
+
return { count: lines.length, current: current >= 0 ? current : 0 };
|
|
10499
|
+
}
|
|
10500
|
+
// -------------------------------------------------------------------------
|
|
10501
|
+
// Session lifecycle
|
|
10502
|
+
// -------------------------------------------------------------------------
|
|
10503
|
+
async spawn(sessionId, opts = {}) {
|
|
10504
|
+
if (this.sessions.has(sessionId)) {
|
|
10505
|
+
throw new Error(`session ${sessionId} already exists`);
|
|
10506
|
+
}
|
|
10507
|
+
const { vncHost: host, vncPort: port } = this.config;
|
|
10508
|
+
if (opts.workspace !== void 0 && this.platform !== "linux") {
|
|
10509
|
+
throw new Error("workspace selection is not supported on this platform (session-internal prev/next still works on macOS)");
|
|
10510
|
+
}
|
|
10511
|
+
if (opts.display !== void 0 && this.platform !== "linux") {
|
|
10512
|
+
throw new Error("display selection is not supported on this platform");
|
|
10513
|
+
}
|
|
10514
|
+
if (opts.workspace !== void 0) {
|
|
10515
|
+
await this.switchWorkspaceAbsolute(opts.workspace);
|
|
10516
|
+
this.hooks.log(`desktop ${sessionId}: workspace \u2192 ${opts.workspace}`);
|
|
10517
|
+
}
|
|
10518
|
+
let tcp = await probeConnect(host, port, 750);
|
|
10519
|
+
let ownChild = null;
|
|
10520
|
+
let clip;
|
|
10521
|
+
if (opts.display !== void 0) {
|
|
10522
|
+
const heads = await this.listHeads().catch(() => []);
|
|
10523
|
+
clip = heads[opts.display];
|
|
10524
|
+
if (!clip) {
|
|
10525
|
+
throw new Error(`display ${opts.display} not found (${heads.length} connected)`);
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
if (!tcp && this.platform === "linux") {
|
|
10529
|
+
const x11vnc = await this.startX11vnc(port, clip);
|
|
10530
|
+
ownChild = x11vnc;
|
|
10531
|
+
tcp = await probeConnect(host, port, X11VNC_START_TIMEOUT_MS);
|
|
10532
|
+
if (!tcp) {
|
|
10533
|
+
try {
|
|
10534
|
+
x11vnc?.kill("SIGTERM");
|
|
10535
|
+
} catch {
|
|
10536
|
+
}
|
|
10537
|
+
throw new Error(
|
|
10538
|
+
`no VNC server on ${host}:${port} and x11vnc did not come up (is a desktop/X11 session running?)`
|
|
10539
|
+
);
|
|
10540
|
+
}
|
|
10541
|
+
if (x11vnc) {
|
|
10542
|
+
x11vnc.once("exit", () => {
|
|
10543
|
+
const s = this.sessions.get(sessionId);
|
|
10544
|
+
if (s && s.x11vnc === x11vnc) this.end(sessionId, 1, "x11vnc exited");
|
|
10545
|
+
});
|
|
10546
|
+
}
|
|
10547
|
+
this.hooks.log(
|
|
10548
|
+
`desktop ${sessionId}: started x11vnc on ${host}:${port}` + (clip ? ` clipped to ${clip.w}x${clip.h}+${clip.x}+${clip.y}` : "") + ` (display ${this.config.display}, loopback only)`
|
|
10549
|
+
);
|
|
10550
|
+
}
|
|
10551
|
+
if (!tcp) {
|
|
10552
|
+
throw new Error(
|
|
10553
|
+
this.platform === "darwin" ? `no VNC server on ${host}:${port} \u2014 enable \u300C\u5C4F\u5E55\u5171\u4EAB\u300Din macOS System Settings, or set desktop.vncHost/vncPort` : `no VNC server on ${host}:${port} (auto-start is only supported on Linux X11)`
|
|
10554
|
+
);
|
|
10555
|
+
}
|
|
10556
|
+
if (clip && !ownChild) {
|
|
10557
|
+
throw new Error("external VNC server on the port cannot serve a chosen display (stop it, or spawn with the default display)");
|
|
10558
|
+
}
|
|
10559
|
+
const session = {
|
|
10560
|
+
id: sessionId,
|
|
10561
|
+
tcp,
|
|
10562
|
+
startedAt: Date.now(),
|
|
10563
|
+
paused: false,
|
|
10564
|
+
externalServer: !ownChild,
|
|
10565
|
+
...ownChild ? { x11vnc: ownChild } : {},
|
|
10566
|
+
...clip ? { clip: { w: clip.w, h: clip.h, x: clip.x, y: clip.y } } : {}
|
|
10567
|
+
};
|
|
10568
|
+
this.sessions.set(sessionId, session);
|
|
10569
|
+
this.wireSocket(session, tcp);
|
|
10570
|
+
}
|
|
10571
|
+
async startX11vnc(port, clip) {
|
|
10572
|
+
if (this.platform !== "linux") return null;
|
|
10573
|
+
try {
|
|
10574
|
+
const args = ["-display", this.config.display, "-localhost", "-nopw", "-quiet", "-rfbport", String(port)];
|
|
10575
|
+
if (clip) args.push("-clip", `${clip.w}x${clip.h}+${clip.x}+${clip.y}`);
|
|
10576
|
+
return this.spawnChildFn("x11vnc", args);
|
|
10577
|
+
} catch {
|
|
10578
|
+
return null;
|
|
10579
|
+
}
|
|
10580
|
+
}
|
|
10581
|
+
/** Attach data/backpressure/close handlers to a session's TCP socket. */
|
|
10582
|
+
wireSocket(session, tcp) {
|
|
10583
|
+
session.tcp = tcp;
|
|
10584
|
+
tcp.setNoDelay(true);
|
|
10585
|
+
tcp.on("data", (buf) => {
|
|
10586
|
+
this.hooks.send(session.id, new Uint8Array(buf));
|
|
10587
|
+
if (!session.paused && this.hooks.congested()) {
|
|
10588
|
+
session.paused = true;
|
|
10589
|
+
tcp.pause();
|
|
10590
|
+
session.pauseTimer = setInterval(() => {
|
|
10591
|
+
if (!this.hooks.congested()) {
|
|
10592
|
+
session.paused = false;
|
|
10593
|
+
if (session.pauseTimer) clearInterval(session.pauseTimer);
|
|
10594
|
+
session.pauseTimer = void 0;
|
|
10595
|
+
try {
|
|
10596
|
+
tcp.resume();
|
|
10597
|
+
} catch {
|
|
10598
|
+
}
|
|
10599
|
+
}
|
|
10600
|
+
}, PAUSE_POLL_MS);
|
|
10601
|
+
}
|
|
10602
|
+
});
|
|
10603
|
+
tcp.on("error", (err) => {
|
|
10604
|
+
this.hooks.log(`desktop ${session.id}: tcp error ${err.message}`);
|
|
10605
|
+
this.end(session.id, 1, "tcp error");
|
|
10606
|
+
});
|
|
10607
|
+
tcp.on("close", () => {
|
|
10608
|
+
const s = this.sessions.get(session.id);
|
|
10609
|
+
if (s) this.end(session.id, 0, "vnc closed");
|
|
10610
|
+
});
|
|
10611
|
+
}
|
|
10612
|
+
/** VNC client bytes (RFB) from the web side → the local VNC server. */
|
|
10613
|
+
write(sessionId, data) {
|
|
10614
|
+
const s = this.sessions.get(sessionId);
|
|
10615
|
+
if (!s) throw new Error(`desktop session ${sessionId} not found`);
|
|
10616
|
+
s.tcp.write(data);
|
|
10617
|
+
}
|
|
10618
|
+
has(sessionId) {
|
|
10619
|
+
return this.sessions.has(sessionId);
|
|
10620
|
+
}
|
|
10621
|
+
list() {
|
|
10622
|
+
return [...this.sessions.values()].map((s) => ({ sessionId: s.id, startedAt: s.startedAt }));
|
|
10623
|
+
}
|
|
10624
|
+
kill(sessionId) {
|
|
10625
|
+
const s = this.sessions.get(sessionId);
|
|
10626
|
+
if (s) this.end(sessionId, 0, "killed");
|
|
10627
|
+
}
|
|
10628
|
+
/** Called whenever the upstream transport dies or the process exits —
|
|
10629
|
+
* a desktop session must NEVER outlive the tunnel (design §2 #8). */
|
|
10630
|
+
killAll() {
|
|
10631
|
+
for (const id of [...this.sessions.keys()]) {
|
|
10632
|
+
this.kill(id);
|
|
10633
|
+
}
|
|
10634
|
+
}
|
|
10635
|
+
end(sessionId, code, why) {
|
|
10636
|
+
const s = this.sessions.get(sessionId);
|
|
10637
|
+
if (!s) return;
|
|
10638
|
+
this.sessions.delete(sessionId);
|
|
10639
|
+
if (s.pauseTimer) clearInterval(s.pauseTimer);
|
|
10640
|
+
try {
|
|
10641
|
+
s.tcp.destroy();
|
|
10642
|
+
} catch {
|
|
10643
|
+
}
|
|
10644
|
+
if (s.x11vnc) {
|
|
10645
|
+
try {
|
|
10646
|
+
s.x11vnc.kill("SIGTERM");
|
|
10647
|
+
} catch {
|
|
10648
|
+
}
|
|
10649
|
+
}
|
|
10650
|
+
this.hooks.log(`desktop ${sessionId} ended (${why})`);
|
|
10651
|
+
this.hooks.onExit(sessionId, code);
|
|
10652
|
+
}
|
|
10653
|
+
// -------------------------------------------------------------------------
|
|
10654
|
+
// Live switching (desktop-switch)
|
|
10655
|
+
// -------------------------------------------------------------------------
|
|
10656
|
+
/** Returns an error reason on failure, undefined on success. */
|
|
10657
|
+
async switch(sessionId, opts) {
|
|
10658
|
+
const s = this.sessions.get(sessionId);
|
|
10659
|
+
if (!s) return `desktop session ${sessionId} not found`;
|
|
10660
|
+
if (opts.display !== void 0) {
|
|
10661
|
+
const err = await this.switchDisplay(s, opts.display);
|
|
10662
|
+
if (err) return err;
|
|
10663
|
+
}
|
|
10664
|
+
if (opts.workspaceDelta !== void 0) {
|
|
10665
|
+
const err = await this.switchWorkspaceDelta(opts.workspaceDelta);
|
|
10666
|
+
if (err) return err;
|
|
10667
|
+
} else if (opts.workspace !== void 0) {
|
|
10668
|
+
const err = await this.switchWorkspaceAbsolute(opts.workspace);
|
|
10669
|
+
if (err) return err;
|
|
10670
|
+
}
|
|
10671
|
+
return void 0;
|
|
10672
|
+
}
|
|
10673
|
+
/** Linux: respawn OUR x11vnc with a new -clip and swap the TCP socket.
|
|
10674
|
+
* The RFB handshake restarts on the web side (viewer re-attaches). */
|
|
10675
|
+
async switchDisplay(s, display) {
|
|
10676
|
+
if (this.platform !== "linux") return "display switching is not supported on this platform";
|
|
10677
|
+
if (s.externalServer || !s.x11vnc) {
|
|
10678
|
+
return "cannot switch displays on an external VNC server (only locally started x11vnc)";
|
|
10679
|
+
}
|
|
10680
|
+
const heads = await this.listHeads().catch(() => []);
|
|
10681
|
+
const head = heads[display];
|
|
10682
|
+
if (!head) return `display ${display} not found (${heads.length} connected)`;
|
|
10683
|
+
const { vncHost: host, vncPort: port } = this.config;
|
|
10684
|
+
try {
|
|
10685
|
+
s.tcp.removeAllListeners();
|
|
10686
|
+
s.tcp.destroy();
|
|
10687
|
+
} catch {
|
|
10688
|
+
}
|
|
10689
|
+
try {
|
|
10690
|
+
s.x11vnc.kill("SIGTERM");
|
|
10691
|
+
} catch {
|
|
10692
|
+
}
|
|
10693
|
+
const child = await this.startX11vnc(port, head);
|
|
10694
|
+
const tcp = await probeConnect(host, port, X11VNC_START_TIMEOUT_MS);
|
|
10695
|
+
if (!tcp) {
|
|
10696
|
+
this.end(s.id, 1, "x11vnc restart failed during display switch");
|
|
10697
|
+
return "x11vnc did not come back up";
|
|
10698
|
+
}
|
|
10699
|
+
if (child) {
|
|
10700
|
+
child.once("exit", () => {
|
|
10701
|
+
const cur = this.sessions.get(s.id);
|
|
10702
|
+
if (cur && cur.x11vnc === child) this.end(s.id, 1, "x11vnc exited");
|
|
10703
|
+
});
|
|
10704
|
+
}
|
|
10705
|
+
s.x11vnc = child ?? void 0;
|
|
10706
|
+
s.clip = { w: head.w, h: head.h, x: head.x, y: head.y };
|
|
10707
|
+
this.wireSocket(s, tcp);
|
|
10708
|
+
this.hooks.log(`desktop ${s.id}: display \u2192 ${display} (${head.name} ${head.w}x${head.h}+${head.x}+${head.y})`);
|
|
10709
|
+
return void 0;
|
|
10710
|
+
}
|
|
10711
|
+
async switchWorkspaceAbsolute(n) {
|
|
10712
|
+
if (this.platform !== "linux") return "absolute workspace switching is not supported on this platform";
|
|
10713
|
+
try {
|
|
10714
|
+
await this.exec("wmctrl", ["-s", String(n)]);
|
|
10715
|
+
return void 0;
|
|
10716
|
+
} catch {
|
|
10717
|
+
return `wmctrl -s ${n} failed (is wmctrl installed? does workspace ${n} exist?)`;
|
|
10718
|
+
}
|
|
10719
|
+
}
|
|
10720
|
+
async switchWorkspaceDelta(delta) {
|
|
10721
|
+
if (this.platform === "linux") {
|
|
10722
|
+
const ws = await this.workspaceState().catch(() => null);
|
|
10723
|
+
if (!ws) return "cannot read current workspace (wmctrl missing?)";
|
|
10724
|
+
const target = ws.current + delta;
|
|
10725
|
+
if (target < 0 || target >= ws.count) return `no workspace ${target < 0 ? "before" : "after"} the current one`;
|
|
10726
|
+
return this.switchWorkspaceAbsolute(target);
|
|
10727
|
+
}
|
|
10728
|
+
if (this.platform === "darwin") {
|
|
10729
|
+
const keyCode = delta === 1 ? 124 : 123;
|
|
10730
|
+
try {
|
|
10731
|
+
await this.exec(
|
|
10732
|
+
"osascript",
|
|
10733
|
+
["-e", `tell application "System Events" to key code ${keyCode} using control down`],
|
|
10734
|
+
8e3
|
|
10735
|
+
);
|
|
10736
|
+
return void 0;
|
|
10737
|
+
} catch {
|
|
10738
|
+
return "System Events keystroke failed (grant Accessibility permission to the app running cli-local, or switch Spaces manually)";
|
|
10739
|
+
}
|
|
10740
|
+
}
|
|
10741
|
+
return "workspace switching is not supported on this platform";
|
|
10742
|
+
}
|
|
10743
|
+
};
|
|
10744
|
+
function probeConnect(host, port, timeoutMs) {
|
|
10745
|
+
return new Promise((resolve2) => {
|
|
10746
|
+
const sock = import_node_net.default.connect({ host, port });
|
|
10747
|
+
let settled = false;
|
|
10748
|
+
const done = (result) => {
|
|
10749
|
+
if (settled) return;
|
|
10750
|
+
settled = true;
|
|
10751
|
+
clearTimeout(timer);
|
|
10752
|
+
if (result === null) {
|
|
10753
|
+
sock.destroy();
|
|
10754
|
+
}
|
|
10755
|
+
resolve2(result);
|
|
10756
|
+
};
|
|
10757
|
+
const timer = setTimeout(() => done(null), timeoutMs);
|
|
10758
|
+
sock.once("connect", () => done(sock));
|
|
10759
|
+
sock.once("error", () => done(null));
|
|
10760
|
+
});
|
|
10761
|
+
}
|
|
10762
|
+
|
|
10763
|
+
// src/config.ts
|
|
10764
|
+
function resolveDesktopConfig(cfg) {
|
|
10765
|
+
return {
|
|
10766
|
+
enabled: cfg?.enabled === true,
|
|
10767
|
+
display: cfg?.display ?? ":0",
|
|
10768
|
+
vncHost: cfg?.vncHost ?? "127.0.0.1",
|
|
10769
|
+
vncPort: cfg?.vncPort ?? 5900
|
|
10770
|
+
};
|
|
10771
|
+
}
|
|
10772
|
+
|
|
10424
10773
|
// src/gateway-api.ts
|
|
10425
10774
|
var import_node_http = require("node:http");
|
|
10426
10775
|
var import_node_fs3 = require("node:fs");
|
|
@@ -10606,18 +10955,148 @@ function startSetupCallback(timeoutMs = 3 * 6e4) {
|
|
|
10606
10955
|
}
|
|
10607
10956
|
|
|
10608
10957
|
// src/auth.ts
|
|
10609
|
-
var
|
|
10958
|
+
var import_node_child_process3 = require("node:child_process");
|
|
10959
|
+
var import_node_crypto3 = require("node:crypto");
|
|
10960
|
+
var import_promises3 = require("node:fs/promises");
|
|
10961
|
+
var import_node_os6 = require("node:os");
|
|
10962
|
+
var import_node_path6 = require("node:path");
|
|
10963
|
+
var import_qrcode = __toESM(require_lib(), 1);
|
|
10964
|
+
|
|
10965
|
+
// src/machine-id.ts
|
|
10966
|
+
var import_node_child_process2 = require("node:child_process");
|
|
10610
10967
|
var import_node_crypto2 = require("node:crypto");
|
|
10611
10968
|
var import_promises2 = require("node:fs/promises");
|
|
10612
10969
|
var import_node_os5 = require("node:os");
|
|
10613
10970
|
var import_node_path5 = require("node:path");
|
|
10614
|
-
var
|
|
10971
|
+
var import_node_util2 = require("node:util");
|
|
10972
|
+
var execFileAsync = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
|
|
10973
|
+
var MACHINE_ID_DOMAIN = "cli-remote.machineid.v1";
|
|
10974
|
+
function normalizeHardwareId(raw) {
|
|
10975
|
+
return raw.trim().toLowerCase().replace(/\s+/g, " ");
|
|
10976
|
+
}
|
|
10977
|
+
var GARBAGE_VALUES = /* @__PURE__ */ new Set([
|
|
10978
|
+
"",
|
|
10979
|
+
"none",
|
|
10980
|
+
"null",
|
|
10981
|
+
"default string",
|
|
10982
|
+
"system serial number",
|
|
10983
|
+
"to be filled by o.e.m.",
|
|
10984
|
+
"to be filled by oem",
|
|
10985
|
+
"not specified",
|
|
10986
|
+
"not available",
|
|
10987
|
+
"unknown",
|
|
10988
|
+
"chassis manufacturer",
|
|
10989
|
+
"no enclosure",
|
|
10990
|
+
"empty"
|
|
10991
|
+
]);
|
|
10992
|
+
function isValidHardwareId(raw) {
|
|
10993
|
+
const v = normalizeHardwareId(raw);
|
|
10994
|
+
if (v.length < 4) return false;
|
|
10995
|
+
if (GARBAGE_VALUES.has(v)) return false;
|
|
10996
|
+
const digits = v.replace(/[^0-9a-f]/g, "");
|
|
10997
|
+
if (digits.length >= 8 && /^([0-9a-f])\1*$/.test(digits)) return false;
|
|
10998
|
+
return true;
|
|
10999
|
+
}
|
|
11000
|
+
function deriveMachineId(tag, raw) {
|
|
11001
|
+
const h = (0, import_node_crypto2.createHash)("sha256").update(`${MACHINE_ID_DOMAIN}|${tag}|${normalizeHardwareId(raw)}`).digest("hex");
|
|
11002
|
+
return "m_" + h.slice(0, 32);
|
|
11003
|
+
}
|
|
11004
|
+
async function ioregValue(exec, key) {
|
|
11005
|
+
try {
|
|
11006
|
+
const { stdout } = await exec("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
|
|
11007
|
+
const m = stdout.match(new RegExp(`"${key}"\\s*=\\s*"([^"]*)"`));
|
|
11008
|
+
return m?.[1] ? normalizeHardwareId(m[1]) : null;
|
|
11009
|
+
} catch {
|
|
11010
|
+
return null;
|
|
11011
|
+
}
|
|
11012
|
+
}
|
|
11013
|
+
async function firstPhysicalMacMac(deps) {
|
|
11014
|
+
try {
|
|
11015
|
+
const names = (await deps.readDir("/sys/class/net")).sort();
|
|
11016
|
+
for (const name of names) {
|
|
11017
|
+
const base = (0, import_node_path5.join)("/sys/class/net", name);
|
|
11018
|
+
if (!await deps.statOrNull((0, import_node_path5.join)(base, "device"))) continue;
|
|
11019
|
+
const mac = await deps.readFile((0, import_node_path5.join)(base, "address"));
|
|
11020
|
+
return normalizeHardwareId(mac);
|
|
11021
|
+
}
|
|
11022
|
+
} catch {
|
|
11023
|
+
}
|
|
11024
|
+
return null;
|
|
11025
|
+
}
|
|
11026
|
+
async function winMachineGuid(exec) {
|
|
11027
|
+
try {
|
|
11028
|
+
const { stdout } = await exec("reg", [
|
|
11029
|
+
"query",
|
|
11030
|
+
"HKLM\\SOFTWARE\\Microsoft\\Cryptography",
|
|
11031
|
+
"/v",
|
|
11032
|
+
"MachineGuid"
|
|
11033
|
+
]);
|
|
11034
|
+
const m = stdout.match(/MachineGuid\s+REG_SZ\s+(\S+)/i);
|
|
11035
|
+
return m?.[1] ? normalizeHardwareId(m[1]) : null;
|
|
11036
|
+
} catch {
|
|
11037
|
+
return null;
|
|
11038
|
+
}
|
|
11039
|
+
}
|
|
11040
|
+
async function resolveHardwareMachineId(deps = {}) {
|
|
11041
|
+
const plat = deps.platform ?? (0, import_node_os5.platform)();
|
|
11042
|
+
const readFileDeps = deps.readFile ?? ((p) => (0, import_promises2.readFile)(p, "utf-8"));
|
|
11043
|
+
const execDeps = deps.exec ?? (async (cmd, args) => {
|
|
11044
|
+
const { stdout } = await execFileAsync(cmd, args, { timeout: 3e3 });
|
|
11045
|
+
return { stdout };
|
|
11046
|
+
});
|
|
11047
|
+
const candidates = [];
|
|
11048
|
+
if (plat === "darwin") {
|
|
11049
|
+
candidates.push(
|
|
11050
|
+
{ tag: "mac:serial", raw: await ioregValue(execDeps, "IOPlatformSerialNumber") },
|
|
11051
|
+
{ tag: "mac:platform-uuid", raw: await ioregValue(execDeps, "IOPlatformUUID") }
|
|
11052
|
+
);
|
|
11053
|
+
} else if (plat === "linux") {
|
|
11054
|
+
const readFileOrNull = async (p) => {
|
|
11055
|
+
try {
|
|
11056
|
+
return await readFileDeps(p);
|
|
11057
|
+
} catch {
|
|
11058
|
+
return null;
|
|
11059
|
+
}
|
|
11060
|
+
};
|
|
11061
|
+
candidates.push(
|
|
11062
|
+
{ tag: "linux:product-uuid", raw: await readFileOrNull("/sys/class/dmi/id/product_uuid") },
|
|
11063
|
+
{ tag: "linux:board-serial", raw: await readFileOrNull("/sys/class/dmi/id/board_serial") },
|
|
11064
|
+
{ tag: "linux:machine-id", raw: await readFileOrNull("/etc/machine-id") },
|
|
11065
|
+
{ tag: "linux:machine-id", raw: await readFileOrNull("/var/lib/dbus/machine-id") },
|
|
11066
|
+
{
|
|
11067
|
+
tag: "linux:nic-mac",
|
|
11068
|
+
raw: await firstPhysicalMacMac({
|
|
11069
|
+
readFile: readFileDeps,
|
|
11070
|
+
readDir: deps.readDir ?? ((p) => (0, import_promises2.readdir)(p)),
|
|
11071
|
+
statOrNull: deps.statOrNull ?? (async (p) => {
|
|
11072
|
+
try {
|
|
11073
|
+
await (0, import_promises2.stat)(p);
|
|
11074
|
+
return true;
|
|
11075
|
+
} catch {
|
|
11076
|
+
return false;
|
|
11077
|
+
}
|
|
11078
|
+
})
|
|
11079
|
+
})
|
|
11080
|
+
}
|
|
11081
|
+
);
|
|
11082
|
+
} else if (plat === "win32") {
|
|
11083
|
+
candidates.push({ tag: "win:machine-guid", raw: await winMachineGuid(execDeps) });
|
|
11084
|
+
}
|
|
11085
|
+
for (const c of candidates) {
|
|
11086
|
+
if (c.raw !== null && isValidHardwareId(c.raw)) {
|
|
11087
|
+
return { id: deriveMachineId(c.tag, c.raw), source: c.tag };
|
|
11088
|
+
}
|
|
11089
|
+
}
|
|
11090
|
+
return null;
|
|
11091
|
+
}
|
|
11092
|
+
|
|
11093
|
+
// src/auth.ts
|
|
10615
11094
|
function credentialsPath() {
|
|
10616
|
-
return (0,
|
|
11095
|
+
return (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
|
|
10617
11096
|
}
|
|
10618
11097
|
async function loadCredentials() {
|
|
10619
11098
|
try {
|
|
10620
|
-
const raw = JSON.parse(await (0,
|
|
11099
|
+
const raw = JSON.parse(await (0, import_promises3.readFile)(credentialsPath(), "utf-8"));
|
|
10621
11100
|
if (!raw.deviceId || !raw.accessToken || !raw.refreshToken) return null;
|
|
10622
11101
|
return raw;
|
|
10623
11102
|
} catch {
|
|
@@ -10626,10 +11105,10 @@ async function loadCredentials() {
|
|
|
10626
11105
|
}
|
|
10627
11106
|
async function saveCredentials(creds) {
|
|
10628
11107
|
const path = credentialsPath();
|
|
10629
|
-
await (0,
|
|
10630
|
-
await (0,
|
|
11108
|
+
await (0, import_promises3.mkdir)((0, import_node_path6.join)(path, ".."), { recursive: true });
|
|
11109
|
+
await (0, import_promises3.writeFile)(path, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
|
|
10631
11110
|
try {
|
|
10632
|
-
await (0,
|
|
11111
|
+
await (0, import_promises3.chmod)(path, 384);
|
|
10633
11112
|
} catch {
|
|
10634
11113
|
}
|
|
10635
11114
|
}
|
|
@@ -10655,7 +11134,7 @@ function openBrowser(url) {
|
|
|
10655
11134
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
10656
11135
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
10657
11136
|
try {
|
|
10658
|
-
const child = (0,
|
|
11137
|
+
const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: "ignore", detached: true });
|
|
10659
11138
|
child.unref();
|
|
10660
11139
|
} catch {
|
|
10661
11140
|
}
|
|
@@ -10667,7 +11146,14 @@ async function loginAndFetchRegistrationKey(baseUrl, email, password) {
|
|
|
10667
11146
|
throw new AuthError(`\u767B\u5F55\u5931\u8D25: ${reason ?? login.status}`);
|
|
10668
11147
|
}
|
|
10669
11148
|
const token = login.data.token;
|
|
10670
|
-
if (!token)
|
|
11149
|
+
if (!token) {
|
|
11150
|
+
if (login.data?.totpRequired) {
|
|
11151
|
+
throw new AuthError(
|
|
11152
|
+
"\u8BE5\u8D26\u53F7\u5DF2\u5F00\u542F\u4E24\u6B65\u9A8C\u8BC1\uFF0C\u8D26\u53F7\u5BC6\u7801\u65B9\u5F0F\u4E0D\u53EF\u7528\u3002\u8BF7\u6539\u7528\uFF1A\u2460\u201C\u901A\u8FC7\u7F51\u9875\u767B\u5F55\u83B7\u53D6\u201D\uFF08\u63A8\u8350\uFF09\u6216 \u2461\u624B\u52A8\u7C98\u8D34 registrationKey"
|
|
11153
|
+
);
|
|
11154
|
+
}
|
|
11155
|
+
throw new AuthError("\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u4F1A\u8BDD token");
|
|
11156
|
+
}
|
|
10671
11157
|
const me = await fetch(`${baseUrl}/api/me`, {
|
|
10672
11158
|
headers: { authorization: `Bearer ${token}` }
|
|
10673
11159
|
});
|
|
@@ -10677,15 +11163,15 @@ async function loginAndFetchRegistrationKey(baseUrl, email, password) {
|
|
|
10677
11163
|
return { registrationKey: data.registrationKey, user: data.user?.email ?? email };
|
|
10678
11164
|
}
|
|
10679
11165
|
async function mergeLocalConfig(patch) {
|
|
10680
|
-
const dir = (0,
|
|
10681
|
-
const path = (0,
|
|
11166
|
+
const dir = (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR);
|
|
11167
|
+
const path = (0, import_node_path6.join)(dir, CONFIG_FILE);
|
|
10682
11168
|
let existing = {};
|
|
10683
11169
|
try {
|
|
10684
|
-
existing = JSON.parse(await (0,
|
|
11170
|
+
existing = JSON.parse(await (0, import_promises3.readFile)(path, "utf-8"));
|
|
10685
11171
|
} catch {
|
|
10686
11172
|
}
|
|
10687
|
-
await (0,
|
|
10688
|
-
await (0,
|
|
11173
|
+
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
11174
|
+
await (0, import_promises3.writeFile)(path, JSON.stringify({ ...existing, ...patch }, null, 2) + "\n");
|
|
10689
11175
|
}
|
|
10690
11176
|
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
10691
11177
|
var AuthError = class extends Error {
|
|
@@ -10699,7 +11185,7 @@ async function deviceFlowAuthorize(baseUrl, registrationKey, deviceId) {
|
|
|
10699
11185
|
const res = await postJson(`${baseUrl}/oauth/device_authorization`, {
|
|
10700
11186
|
registrationKey,
|
|
10701
11187
|
device_id: deviceId,
|
|
10702
|
-
device_name: (0,
|
|
11188
|
+
device_name: (0, import_node_os6.hostname)(),
|
|
10703
11189
|
platform: process.platform
|
|
10704
11190
|
});
|
|
10705
11191
|
if (res.status !== 200) {
|
|
@@ -10789,14 +11275,19 @@ async function ensureFreshAccessToken(baseUrl, creds) {
|
|
|
10789
11275
|
return refreshInFlight;
|
|
10790
11276
|
}
|
|
10791
11277
|
async function ensureMachineId() {
|
|
10792
|
-
const path = (0,
|
|
11278
|
+
const path = (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
|
|
10793
11279
|
try {
|
|
10794
|
-
const cfg = JSON.parse(await (0,
|
|
11280
|
+
const cfg = JSON.parse(await (0, import_promises3.readFile)(path, "utf-8"));
|
|
10795
11281
|
if (typeof cfg.machineId === "string" && cfg.machineId) return cfg.machineId;
|
|
10796
11282
|
} catch {
|
|
10797
11283
|
}
|
|
10798
|
-
const
|
|
10799
|
-
|
|
11284
|
+
const hw = await resolveHardwareMachineId();
|
|
11285
|
+
if (hw) {
|
|
11286
|
+
await mergeLocalConfig({ machineId: hw.id, machineIdSource: hw.source });
|
|
11287
|
+
return hw.id;
|
|
11288
|
+
}
|
|
11289
|
+
const machineId = "m_" + (0, import_node_crypto3.randomBytes)(16).toString("hex");
|
|
11290
|
+
await mergeLocalConfig({ machineId, machineIdSource: "random" });
|
|
10800
11291
|
return machineId;
|
|
10801
11292
|
}
|
|
10802
11293
|
async function stableDeviceId() {
|
|
@@ -10819,7 +11310,7 @@ function isPermanentAuthFailure(e) {
|
|
|
10819
11310
|
return e instanceof AuthError && e.message.includes("invalid_grant");
|
|
10820
11311
|
}
|
|
10821
11312
|
async function loadConfig() {
|
|
10822
|
-
const path = (0,
|
|
11313
|
+
const path = (0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
|
|
10823
11314
|
try {
|
|
10824
11315
|
return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf-8"));
|
|
10825
11316
|
} catch {
|
|
@@ -10840,7 +11331,7 @@ function applyConfigDeprecations(cfg) {
|
|
|
10840
11331
|
function isTTY() {
|
|
10841
11332
|
return process.stdin.isTTY === true;
|
|
10842
11333
|
}
|
|
10843
|
-
function detectCommands() {
|
|
11334
|
+
function detectCommands(desktopEnabled) {
|
|
10844
11335
|
const bins = [
|
|
10845
11336
|
["claude", "claude"],
|
|
10846
11337
|
["codex", "codex"],
|
|
@@ -10851,15 +11342,16 @@ function detectCommands() {
|
|
|
10851
11342
|
for (const [cmd, bin] of bins) {
|
|
10852
11343
|
let ok = false;
|
|
10853
11344
|
if (process.platform === "win32") {
|
|
10854
|
-
ok = (0,
|
|
11345
|
+
ok = (0, import_node_child_process4.spawnSync)("where", [bin], { timeout: 5e3 }).status === 0;
|
|
10855
11346
|
} else {
|
|
10856
11347
|
const shell = process.env.SHELL ?? "/bin/bash";
|
|
10857
|
-
ok = (0,
|
|
11348
|
+
ok = (0, import_node_child_process4.spawnSync)(shell, ["-l", "-c", `command -v ${bin}`], {
|
|
10858
11349
|
timeout: 5e3
|
|
10859
11350
|
}).status === 0;
|
|
10860
11351
|
}
|
|
10861
11352
|
if (ok) found.push(cmd);
|
|
10862
11353
|
}
|
|
11354
|
+
if (desktopEnabled) found.push("desktop");
|
|
10863
11355
|
return found;
|
|
10864
11356
|
}
|
|
10865
11357
|
async function readInput(prompt) {
|
|
@@ -10985,6 +11477,20 @@ async function startLocal(opts = {}) {
|
|
|
10985
11477
|
(data) => safeSend(data),
|
|
10986
11478
|
(msg) => console.log(`[local] ${msg}`)
|
|
10987
11479
|
);
|
|
11480
|
+
const desktops = new DesktopManager(
|
|
11481
|
+
{
|
|
11482
|
+
send: (sessionId, data) => safeSend(encodeFrame({ t: "desktop-data", sessionId, data })),
|
|
11483
|
+
congested: () => ws ? ws.bufferedAmount > 4 * 1024 * 1024 : false,
|
|
11484
|
+
onExit: (sessionId, code) => {
|
|
11485
|
+
const logger = loggers.get(sessionId);
|
|
11486
|
+
logger?.logExit(code);
|
|
11487
|
+
loggers.delete(sessionId);
|
|
11488
|
+
safeSend(encodeFrame({ t: "exit", sessionId, code }));
|
|
11489
|
+
},
|
|
11490
|
+
log: (msg) => console.log(`[local] ${msg}`)
|
|
11491
|
+
},
|
|
11492
|
+
resolveDesktopConfig(config.desktop)
|
|
11493
|
+
);
|
|
10988
11494
|
const other = acquireSingleInstanceLock();
|
|
10989
11495
|
if (other !== null) {
|
|
10990
11496
|
console.error(`[local] \u53E6\u4E00\u4E2A cli-local \u6B63\u5728\u8FD0\u884C (pid ${other})\uFF0C\u62D2\u7EDD\u53CC\u5F00\u3002`);
|
|
@@ -11018,7 +11524,7 @@ async function startLocal(opts = {}) {
|
|
|
11018
11524
|
} else {
|
|
11019
11525
|
credentials = await ensureCredentials(baseUrl, registrationKey);
|
|
11020
11526
|
}
|
|
11021
|
-
const commands = detectCommands();
|
|
11527
|
+
const commands = detectCommands(desktops.unsupportedReason() === null);
|
|
11022
11528
|
console.log(`[local] connecting to ${url}`);
|
|
11023
11529
|
console.log(`[local] deviceId=${credentials.deviceId || "(\u672A\u6388\u6743)"}`);
|
|
11024
11530
|
console.log(`[local] bash: passthrough (audit: ${auditMode}, retention: ${retentionDays === 0 ? "forever" : retentionDays + "d"}${dangerConfirm ? ", dangerConfirm: on" : ""})`);
|
|
@@ -11041,11 +11547,18 @@ async function startLocal(opts = {}) {
|
|
|
11041
11547
|
uptimeMs: Date.now() - startedAt,
|
|
11042
11548
|
reconnectAttempts,
|
|
11043
11549
|
accessTokenExpiresAt: credentials.accessTokenExpiresAt || null,
|
|
11044
|
-
sessions:
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11550
|
+
sessions: [
|
|
11551
|
+
...pty.list().map((s) => ({
|
|
11552
|
+
sessionId: s.id,
|
|
11553
|
+
cmd: s.cmd,
|
|
11554
|
+
startedAt: s.startedAt
|
|
11555
|
+
})),
|
|
11556
|
+
...desktops.list().map((d) => ({
|
|
11557
|
+
sessionId: d.sessionId,
|
|
11558
|
+
cmd: "desktop",
|
|
11559
|
+
startedAt: d.startedAt
|
|
11560
|
+
}))
|
|
11561
|
+
]
|
|
11049
11562
|
});
|
|
11050
11563
|
function setPhase(p, reason = "") {
|
|
11051
11564
|
phase = p;
|
|
@@ -11096,6 +11609,10 @@ async function startLocal(opts = {}) {
|
|
|
11096
11609
|
exitProcess(0);
|
|
11097
11610
|
},
|
|
11098
11611
|
killSession: (id) => {
|
|
11612
|
+
if (desktops.has(id)) {
|
|
11613
|
+
desktops.kill(id);
|
|
11614
|
+
return true;
|
|
11615
|
+
}
|
|
11099
11616
|
try {
|
|
11100
11617
|
pty.kill(id);
|
|
11101
11618
|
return true;
|
|
@@ -11148,6 +11665,7 @@ async function startLocal(opts = {}) {
|
|
|
11148
11665
|
process.exit(userExitCode);
|
|
11149
11666
|
}
|
|
11150
11667
|
console.log("[local] disconnected; PTY sessions kept alive, reconnecting\u2026");
|
|
11668
|
+
desktops.killAll();
|
|
11151
11669
|
if (phase !== "needs-pairing") setPhase("reconnecting");
|
|
11152
11670
|
scheduleReconnect();
|
|
11153
11671
|
});
|
|
@@ -11186,6 +11704,7 @@ async function startLocal(opts = {}) {
|
|
|
11186
11704
|
controlApi?.close();
|
|
11187
11705
|
if (opts.daemon) removeGatewayPid();
|
|
11188
11706
|
transfers.abortAll();
|
|
11707
|
+
desktops.killAll();
|
|
11189
11708
|
pty.killAll();
|
|
11190
11709
|
for (const [, logger] of loggers) logger.logExit(-1);
|
|
11191
11710
|
loggers.clear();
|
|
@@ -11224,9 +11743,22 @@ async function startLocal(opts = {}) {
|
|
|
11224
11743
|
case "spawn":
|
|
11225
11744
|
handleSpawn(frame);
|
|
11226
11745
|
break;
|
|
11746
|
+
case "desktop-query":
|
|
11747
|
+
handleDesktopQuery(frame);
|
|
11748
|
+
break;
|
|
11749
|
+
case "desktop-switch":
|
|
11750
|
+
void handleDesktopSwitch(frame);
|
|
11751
|
+
break;
|
|
11227
11752
|
case "input":
|
|
11228
11753
|
handleInput(frame);
|
|
11229
11754
|
break;
|
|
11755
|
+
case "desktop-data":
|
|
11756
|
+
try {
|
|
11757
|
+
desktops.write(frame.sessionId, frame.data);
|
|
11758
|
+
} catch (e) {
|
|
11759
|
+
console.error("[local] desktop write error:", e.message);
|
|
11760
|
+
}
|
|
11761
|
+
break;
|
|
11230
11762
|
case "resize":
|
|
11231
11763
|
pty.resize(frame.sessionId, frame.cols, frame.rows);
|
|
11232
11764
|
break;
|
|
@@ -11279,6 +11811,13 @@ async function startLocal(opts = {}) {
|
|
|
11279
11811
|
}
|
|
11280
11812
|
function handleSpawn(frame) {
|
|
11281
11813
|
const sessionId = frame.sessionId;
|
|
11814
|
+
if (frame.cmd === "desktop") {
|
|
11815
|
+
void handleDesktopSpawn(sessionId, {
|
|
11816
|
+
...frame.desktopDisplay !== void 0 ? { display: frame.desktopDisplay } : {},
|
|
11817
|
+
...frame.desktopWorkspace !== void 0 ? { workspace: frame.desktopWorkspace } : {}
|
|
11818
|
+
});
|
|
11819
|
+
return;
|
|
11820
|
+
}
|
|
11282
11821
|
try {
|
|
11283
11822
|
const logger = new SessionLogger(sessionId, frame.cmd, auditMode);
|
|
11284
11823
|
pty.spawn(
|
|
@@ -11326,6 +11865,63 @@ async function startLocal(opts = {}) {
|
|
|
11326
11865
|
console.error(`[local] spawn failed for ${sessionId}:`, reason);
|
|
11327
11866
|
}
|
|
11328
11867
|
}
|
|
11868
|
+
async function handleDesktopSpawn(sessionId, opts2 = {}) {
|
|
11869
|
+
const unsupported = desktops.unsupportedReason();
|
|
11870
|
+
if (unsupported) {
|
|
11871
|
+
safeSend(encodeFrame({ t: "spawn-err", sessionId, reason: unsupported }));
|
|
11872
|
+
return;
|
|
11873
|
+
}
|
|
11874
|
+
const logger = new SessionLogger(sessionId, "desktop", auditMode);
|
|
11875
|
+
try {
|
|
11876
|
+
await desktops.spawn(sessionId, opts2);
|
|
11877
|
+
loggers.set(sessionId, logger);
|
|
11878
|
+
safeSend(encodeFrame({ t: "spawn-ok", sessionId }));
|
|
11879
|
+
console.log(`[local] desktop session ${sessionId} bridged to VNC`);
|
|
11880
|
+
} catch (e) {
|
|
11881
|
+
logger.logExit(1);
|
|
11882
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
11883
|
+
safeSend(encodeFrame({ t: "spawn-err", sessionId, reason }));
|
|
11884
|
+
console.error(`[local] desktop spawn failed for ${sessionId}:`, reason);
|
|
11885
|
+
}
|
|
11886
|
+
}
|
|
11887
|
+
function handleDesktopQuery(frame) {
|
|
11888
|
+
desktops.queryInfo().then((info) => {
|
|
11889
|
+
safeSend(encodeFrame({ t: "desktop-info", sessionId: frame.sessionId, ...info }));
|
|
11890
|
+
}).catch(() => {
|
|
11891
|
+
safeSend(
|
|
11892
|
+
encodeFrame({
|
|
11893
|
+
t: "desktop-info",
|
|
11894
|
+
sessionId: frame.sessionId,
|
|
11895
|
+
displays: [],
|
|
11896
|
+
workspaces: null,
|
|
11897
|
+
currentWorkspace: null,
|
|
11898
|
+
canPickDisplay: false
|
|
11899
|
+
})
|
|
11900
|
+
);
|
|
11901
|
+
});
|
|
11902
|
+
}
|
|
11903
|
+
async function handleDesktopSwitch(frame) {
|
|
11904
|
+
const logger = loggers.get(frame.sessionId);
|
|
11905
|
+
const reason = await desktops.switch(frame.sessionId, {
|
|
11906
|
+
...frame.display !== void 0 ? { display: frame.display } : {},
|
|
11907
|
+
...frame.workspace !== void 0 ? { workspace: frame.workspace } : {},
|
|
11908
|
+
...frame.workspaceDelta !== void 0 ? { workspaceDelta: frame.workspaceDelta } : {}
|
|
11909
|
+
}).catch((e) => e instanceof Error ? e.message : String(e));
|
|
11910
|
+
if (reason === void 0) {
|
|
11911
|
+
if (frame.display !== void 0) logger?.logSwitch("display", String(frame.display));
|
|
11912
|
+
if (frame.workspace !== void 0) logger?.logSwitch("workspace", String(frame.workspace));
|
|
11913
|
+
if (frame.workspaceDelta !== void 0)
|
|
11914
|
+
logger?.logSwitch("workspace", frame.workspaceDelta === 1 ? "next" : "prev");
|
|
11915
|
+
}
|
|
11916
|
+
safeSend(
|
|
11917
|
+
encodeFrame({
|
|
11918
|
+
t: "desktop-switch-res",
|
|
11919
|
+
sessionId: frame.sessionId,
|
|
11920
|
+
ok: reason === void 0,
|
|
11921
|
+
...reason !== void 0 ? { reason } : {}
|
|
11922
|
+
})
|
|
11923
|
+
);
|
|
11924
|
+
}
|
|
11329
11925
|
function handleInput(frame) {
|
|
11330
11926
|
const sessionId = frame.sessionId;
|
|
11331
11927
|
const logger = loggers.get(sessionId);
|
|
@@ -11400,10 +11996,25 @@ async function startLocal(opts = {}) {
|
|
|
11400
11996
|
}
|
|
11401
11997
|
}
|
|
11402
11998
|
function handleKill(sessionId) {
|
|
11999
|
+
if (desktops.has(sessionId)) {
|
|
12000
|
+
desktops.kill(sessionId);
|
|
12001
|
+
return;
|
|
12002
|
+
}
|
|
11403
12003
|
pty.kill(sessionId);
|
|
11404
12004
|
lineBuffers.delete(sessionId);
|
|
11405
12005
|
}
|
|
11406
12006
|
function handleReplay(frame) {
|
|
12007
|
+
if (desktops.has(frame.sessionId)) {
|
|
12008
|
+
safeSend(
|
|
12009
|
+
encodeFrame({
|
|
12010
|
+
t: "replay-res",
|
|
12011
|
+
sessionId: frame.sessionId,
|
|
12012
|
+
frames: [],
|
|
12013
|
+
exists: true
|
|
12014
|
+
})
|
|
12015
|
+
);
|
|
12016
|
+
return;
|
|
12017
|
+
}
|
|
11407
12018
|
const frames = pty.replay(frame.sessionId, frame.fromSeq);
|
|
11408
12019
|
safeSend(
|
|
11409
12020
|
encodeFrame({
|
|
@@ -11429,10 +12040,10 @@ async function startLocal(opts = {}) {
|
|
|
11429
12040
|
}
|
|
11430
12041
|
}
|
|
11431
12042
|
function credentialsPathOf() {
|
|
11432
|
-
return (0,
|
|
12043
|
+
return (0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "credentials.json");
|
|
11433
12044
|
}
|
|
11434
12045
|
function instanceLockPath() {
|
|
11435
|
-
return (0,
|
|
12046
|
+
return (0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "instance.lock");
|
|
11436
12047
|
}
|
|
11437
12048
|
function processAlive(pid) {
|
|
11438
12049
|
try {
|
|
@@ -11452,7 +12063,7 @@ function acquireSingleInstanceLock() {
|
|
|
11452
12063
|
} catch {
|
|
11453
12064
|
}
|
|
11454
12065
|
try {
|
|
11455
|
-
(0, import_node_fs4.mkdirSync)((0,
|
|
12066
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path7.join)(path, ".."), { recursive: true });
|
|
11456
12067
|
(0, import_node_fs4.writeFileSync)(path, String(process.pid));
|
|
11457
12068
|
} catch {
|
|
11458
12069
|
}
|
|
@@ -11482,10 +12093,10 @@ function toUint8(raw) {
|
|
|
11482
12093
|
throw new Error("unexpected raw type");
|
|
11483
12094
|
}
|
|
11484
12095
|
function gatewayPidFile() {
|
|
11485
|
-
return (0,
|
|
12096
|
+
return (0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid");
|
|
11486
12097
|
}
|
|
11487
12098
|
function writeGatewayPid() {
|
|
11488
|
-
(0, import_node_fs4.mkdirSync)((0,
|
|
12099
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR), { recursive: true });
|
|
11489
12100
|
(0, import_node_fs4.writeFileSync)(gatewayPidFile(), String(process.pid));
|
|
11490
12101
|
}
|
|
11491
12102
|
function removeGatewayPid() {
|
|
@@ -11530,11 +12141,11 @@ function resolveHere() {
|
|
|
11530
12141
|
if (DEBUG) console.error("[debug] cannot resolve script path");
|
|
11531
12142
|
return;
|
|
11532
12143
|
}
|
|
11533
|
-
const hereDir = (0,
|
|
12144
|
+
const hereDir = (0, import_node_path8.dirname)(here);
|
|
11534
12145
|
const candidates = [
|
|
11535
|
-
(0,
|
|
11536
|
-
(0,
|
|
11537
|
-
(0,
|
|
12146
|
+
(0, import_node_path8.join)(hereDir, "..", "node_modules", "node-pty", "prebuilds"),
|
|
12147
|
+
(0, import_node_path8.join)(hereDir, "..", "..", "node-pty", "prebuilds"),
|
|
12148
|
+
(0, import_node_path8.join)(hereDir, "..", "..", "..", "node_modules", "node-pty", "prebuilds")
|
|
11538
12149
|
];
|
|
11539
12150
|
let prebuildsDir = null;
|
|
11540
12151
|
for (const c of candidates) {
|
|
@@ -11549,7 +12160,7 @@ function resolveHere() {
|
|
|
11549
12160
|
}
|
|
11550
12161
|
let n = 0;
|
|
11551
12162
|
for (const sub of (0, import_node_fs5.readdirSync)(prebuildsDir)) {
|
|
11552
|
-
const helper = (0,
|
|
12163
|
+
const helper = (0, import_node_path8.join)(prebuildsDir, sub, "spawn-helper");
|
|
11553
12164
|
if ((0, import_node_fs5.existsSync)(helper)) {
|
|
11554
12165
|
try {
|
|
11555
12166
|
(0, import_node_fs5.chmodSync)(helper, 493);
|
|
@@ -11563,7 +12174,7 @@ function resolveHere() {
|
|
|
11563
12174
|
var argUrl = process.argv[2];
|
|
11564
12175
|
var argMatch = argUrl && (argUrl.startsWith("ws://") || argUrl.startsWith("wss://"));
|
|
11565
12176
|
function readVersion() {
|
|
11566
|
-
if (true) return "0.
|
|
12177
|
+
if (true) return "0.4.0";
|
|
11567
12178
|
try {
|
|
11568
12179
|
const pkg = JSON.parse(
|
|
11569
12180
|
(0, import_node_fs5.readFileSync)(new URL("../package.json", import_meta.url), "utf-8")
|
|
@@ -11623,7 +12234,7 @@ example:
|
|
|
11623
12234
|
}
|
|
11624
12235
|
var LAUNCHD_LABEL = "net.cli-remote.local";
|
|
11625
12236
|
function launchdPlistPath() {
|
|
11626
|
-
return (0,
|
|
12237
|
+
return (0, import_node_path8.join)((0, import_node_os8.homedir)(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
11627
12238
|
}
|
|
11628
12239
|
function entryScript() {
|
|
11629
12240
|
const a1 = process.argv[1];
|
|
@@ -11636,7 +12247,7 @@ function entryScript() {
|
|
|
11636
12247
|
}
|
|
11637
12248
|
function run(cmd, args, okToFail = false) {
|
|
11638
12249
|
try {
|
|
11639
|
-
return (0,
|
|
12250
|
+
return (0, import_node_child_process5.execFileSync)(cmd, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11640
12251
|
} catch (e) {
|
|
11641
12252
|
if (okToFail) return null;
|
|
11642
12253
|
throw e;
|
|
@@ -11647,7 +12258,7 @@ function launchdInstall(urlArg) {
|
|
|
11647
12258
|
console.error(`[local] launchd \u5E38\u9A7B\u4EC5\u652F\u6301 macOS\uFF08\u5F53\u524D ${process.platform}\uFF09\uFF1BLinux \u8BF7\u7528 systemd --user \u81EA\u884C\u6258\u7BA1`);
|
|
11648
12259
|
process.exit(1);
|
|
11649
12260
|
}
|
|
11650
|
-
const home = (0,
|
|
12261
|
+
const home = (0, import_node_os8.homedir)();
|
|
11651
12262
|
const entry = entryScript();
|
|
11652
12263
|
if (!entry) {
|
|
11653
12264
|
console.error("[local] \u5F53\u524D\u4EE5\u6E90\u7801\uFF08tsx\uFF09\u6A21\u5F0F\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1\u3002");
|
|
@@ -11655,9 +12266,9 @@ function launchdInstall(urlArg) {
|
|
|
11655
12266
|
process.exit(1);
|
|
11656
12267
|
}
|
|
11657
12268
|
if (urlArg) {
|
|
11658
|
-
const cfgDir = (0,
|
|
12269
|
+
const cfgDir = (0, import_node_path8.join)(home, DEFAULT_CONFIG_DIR);
|
|
11659
12270
|
(0, import_node_fs5.mkdirSync)(cfgDir, { recursive: true });
|
|
11660
|
-
const cfgPath = (0,
|
|
12271
|
+
const cfgPath = (0, import_node_path8.join)(cfgDir, "config.json");
|
|
11661
12272
|
let cfg = {};
|
|
11662
12273
|
try {
|
|
11663
12274
|
cfg = JSON.parse((0, import_node_fs5.readFileSync)(cfgPath, "utf-8"));
|
|
@@ -11667,15 +12278,15 @@ function launchdInstall(urlArg) {
|
|
|
11667
12278
|
(0, import_node_fs5.writeFileSync)(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
11668
12279
|
console.log(`[local] relayUrl \u5DF2\u5199\u5165 ${cfgPath}`);
|
|
11669
12280
|
}
|
|
11670
|
-
const credPath = (0,
|
|
12281
|
+
const credPath = (0, import_node_path8.join)(home, DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
|
|
11671
12282
|
if (!(0, import_node_fs5.existsSync)(credPath)) {
|
|
11672
12283
|
console.error("[local] \u5C1A\u672A\u6388\u6743\uFF08\u627E\u4E0D\u5230 credentials.json\uFF09\u3002");
|
|
11673
12284
|
console.error("[local] \u8BF7\u5148\u4EA4\u4E92\u5F0F\u8FD0\u884C\u4E00\u6B21 cli-local \u5B8C\u6210\u914D\u7F6E\u4E0E\u6388\u6743\uFF0C\u518D\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1\u3002");
|
|
11674
12285
|
process.exit(1);
|
|
11675
12286
|
}
|
|
11676
12287
|
const plist = launchdPlistPath();
|
|
11677
|
-
const logDir = (0,
|
|
11678
|
-
(0, import_node_fs5.mkdirSync)((0,
|
|
12288
|
+
const logDir = (0, import_node_path8.join)(home, DEFAULT_CONFIG_DIR, "logs");
|
|
12289
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path8.dirname)(plist), { recursive: true });
|
|
11679
12290
|
(0, import_node_fs5.mkdirSync)(logDir, { recursive: true });
|
|
11680
12291
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11681
12292
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -11690,8 +12301,8 @@ function launchdInstall(urlArg) {
|
|
|
11690
12301
|
<key>RunAtLoad</key><true/>
|
|
11691
12302
|
<key>KeepAlive</key><true/>
|
|
11692
12303
|
<key>ThrottleInterval</key><integer>30</integer>
|
|
11693
|
-
<key>StandardOutPath</key><string>${(0,
|
|
11694
|
-
<key>StandardErrorPath</key><string>${(0,
|
|
12304
|
+
<key>StandardOutPath</key><string>${(0, import_node_path8.join)(logDir, "daemon.out.log")}</string>
|
|
12305
|
+
<key>StandardErrorPath</key><string>${(0, import_node_path8.join)(logDir, "daemon.err.log")}</string>
|
|
11695
12306
|
</dict>
|
|
11696
12307
|
</plist>
|
|
11697
12308
|
`;
|
|
@@ -11734,8 +12345,8 @@ if (argUrl && !argMatch && argUrl === "uninstall") {
|
|
|
11734
12345
|
}
|
|
11735
12346
|
if (argUrl === "pair") {
|
|
11736
12347
|
void (async () => {
|
|
11737
|
-
const home = (0,
|
|
11738
|
-
const cfgPath = (0,
|
|
12348
|
+
const home = (0, import_node_os8.homedir)();
|
|
12349
|
+
const cfgPath = (0, import_node_path8.join)(home, DEFAULT_CONFIG_DIR, "config.json");
|
|
11739
12350
|
let cfg = {};
|
|
11740
12351
|
try {
|
|
11741
12352
|
cfg = JSON.parse((0, import_node_fs5.readFileSync)(cfgPath, "utf-8"));
|
|
@@ -11773,7 +12384,7 @@ if (argUrl === "pair") {
|
|
|
11773
12384
|
}
|
|
11774
12385
|
if (argUrl === "gateway") {
|
|
11775
12386
|
let gatewayLogFile = function() {
|
|
11776
|
-
return (0,
|
|
12387
|
+
return (0, import_node_path8.join)((0, import_node_os8.homedir)(), DEFAULT_CONFIG_DIR, "logs", "gateway.log");
|
|
11777
12388
|
}, gatewayLogs = function() {
|
|
11778
12389
|
const follow = rest.includes("-f") || rest.includes("--follow");
|
|
11779
12390
|
const li = rest.findIndex((a) => a === "-n" || a === "--lines");
|
|
@@ -11817,10 +12428,10 @@ if (argUrl === "gateway") {
|
|
|
11817
12428
|
console.log(`[gateway] already running (pid ${gatewayPid()})`);
|
|
11818
12429
|
process.exit(0);
|
|
11819
12430
|
}
|
|
11820
|
-
const home = (0,
|
|
12431
|
+
const home = (0, import_node_os8.homedir)();
|
|
11821
12432
|
const entry = entryScript();
|
|
11822
|
-
const cfgDir = (0,
|
|
11823
|
-
const cfgPath = (0,
|
|
12433
|
+
const cfgDir = (0, import_node_path8.join)(home, DEFAULT_CONFIG_DIR);
|
|
12434
|
+
const cfgPath = (0, import_node_path8.join)(cfgDir, "config.json");
|
|
11824
12435
|
const hasCfg = (0, import_node_fs5.existsSync)(cfgPath);
|
|
11825
12436
|
if (!entry) {
|
|
11826
12437
|
console.error("[gateway] \u6E90\u7801\uFF08tsx\uFF09\u6A21\u5F0F\u4E0D\u652F\u6301\u540E\u53F0\u8FD0\u884C\uFF1B\u8BF7\u7528 npm \u5B89\u88C5\u7248\uFF1Anpm i -g @cli-remote/local");
|
|
@@ -11831,13 +12442,13 @@ if (argUrl === "gateway") {
|
|
|
11831
12442
|
console.error("[gateway] \uFF08\u65E0\u51ED\u8BC1\u4E5F\u53EF\u4EE5\u542F\u52A8\uFF1Agateway \u4F1A\u8FDB\u5165 needs-pairing\uFF0C\u7B49 cli-local pair\uFF09");
|
|
11832
12443
|
process.exit(1);
|
|
11833
12444
|
}
|
|
11834
|
-
(0, import_node_fs5.mkdirSync)((0,
|
|
12445
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path8.dirname)(gatewayLogFile()), { recursive: true });
|
|
11835
12446
|
const out = (0, import_node_fs5.openSync)(gatewayLogFile(), "a");
|
|
11836
12447
|
const env = {};
|
|
11837
12448
|
for (const [k, v] of Object.entries(process.env)) if (v !== void 0) env[k] = v;
|
|
11838
12449
|
const args = [entry, "gateway", "run"];
|
|
11839
12450
|
if (hubArg) args.push("--hub", hubArg);
|
|
11840
|
-
const child = (0,
|
|
12451
|
+
const child = (0, import_node_child_process5.spawn)(process.execPath, args, {
|
|
11841
12452
|
detached: true,
|
|
11842
12453
|
stdio: ["ignore", out, out],
|
|
11843
12454
|
env
|
|
@@ -11863,12 +12474,12 @@ if (argUrl === "gateway") {
|
|
|
11863
12474
|
while (Date.now() - t02 < 5e3 && gatewayAlive()) {
|
|
11864
12475
|
await new Promise((res) => setTimeout(res, 150));
|
|
11865
12476
|
}
|
|
11866
|
-
(0, import_node_fs5.rmSync)((0,
|
|
12477
|
+
(0, import_node_fs5.rmSync)((0, import_node_path8.join)((0, import_node_os8.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11867
12478
|
return;
|
|
11868
12479
|
}
|
|
11869
12480
|
const pid = gatewayPid();
|
|
11870
12481
|
if (pid === null || !gatewayAlive()) {
|
|
11871
|
-
(0, import_node_fs5.rmSync)((0,
|
|
12482
|
+
(0, import_node_fs5.rmSync)((0, import_node_path8.join)((0, import_node_os8.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11872
12483
|
return;
|
|
11873
12484
|
}
|
|
11874
12485
|
try {
|
|
@@ -11879,7 +12490,7 @@ if (argUrl === "gateway") {
|
|
|
11879
12490
|
while (Date.now() - t0 < 5e3 && gatewayAlive()) {
|
|
11880
12491
|
await new Promise((res) => setTimeout(res, 150));
|
|
11881
12492
|
}
|
|
11882
|
-
(0, import_node_fs5.rmSync)((0,
|
|
12493
|
+
(0, import_node_fs5.rmSync)((0, import_node_path8.join)((0, import_node_os8.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11883
12494
|
}
|
|
11884
12495
|
async function gatewayStop() {
|
|
11885
12496
|
const pid = gatewayPid();
|
|
@@ -11920,7 +12531,7 @@ if (argUrl === "gateway") {
|
|
|
11920
12531
|
const pid = gatewayPid();
|
|
11921
12532
|
let hub = "(\u672A\u914D\u7F6E)";
|
|
11922
12533
|
try {
|
|
11923
|
-
const cfg = JSON.parse((0, import_node_fs5.readFileSync)((0,
|
|
12534
|
+
const cfg = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path8.join)((0, import_node_os8.homedir)(), DEFAULT_CONFIG_DIR, "config.json"), "utf-8"));
|
|
11924
12535
|
hub = cfg.relayUrl ?? hub;
|
|
11925
12536
|
} catch {
|
|
11926
12537
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cli-remote/local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Local PTY agent for cli-remote — exposes your local CLI tools (claude/codex/kimi-cli/shell) to the mobile web client via a self-hosted hub.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"build": "node scripts/build.mjs",
|
|
31
31
|
"postinstall": "node scripts/fix-spawn-helper.mjs",
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
33
34
|
"prepublishOnly": "pnpm build"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|