@cli-remote/local 0.0.1 → 0.0.3
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 +311 -52
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9940,6 +9940,8 @@ var require_lib = __commonJS({
|
|
|
9940
9940
|
|
|
9941
9941
|
// src/index.ts
|
|
9942
9942
|
var import_node_fs2 = require("node:fs");
|
|
9943
|
+
var import_node_child_process2 = require("node:child_process");
|
|
9944
|
+
var import_node_os5 = require("node:os");
|
|
9943
9945
|
var import_node_path5 = require("node:path");
|
|
9944
9946
|
var import_node_url = require("node:url");
|
|
9945
9947
|
|
|
@@ -9955,6 +9957,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
9955
9957
|
var wrapper_default = import_websocket.default;
|
|
9956
9958
|
|
|
9957
9959
|
// src/client.ts
|
|
9960
|
+
var import_node_readline = require("node:readline");
|
|
9958
9961
|
var import_promises2 = require("node:fs/promises");
|
|
9959
9962
|
var import_node_os4 = require("node:os");
|
|
9960
9963
|
var import_node_path4 = require("node:path");
|
|
@@ -9977,6 +9980,7 @@ var ACCESS_TOKEN_TTL_MS = 60 * 6e4;
|
|
|
9977
9980
|
var REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 6e4;
|
|
9978
9981
|
var REFRESH_MARGIN_MS = 5 * 6e4;
|
|
9979
9982
|
var OUTPUT_BUFFER_SIZE = 500;
|
|
9983
|
+
var RECONNECT_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
|
|
9980
9984
|
var DEFAULT_CONFIG_DIR = ".cli-remote";
|
|
9981
9985
|
var CREDENTIALS_FILE = "credentials.json";
|
|
9982
9986
|
var CONFIG_FILE = "config.json";
|
|
@@ -10210,6 +10214,10 @@ var SessionLogger = class {
|
|
|
10210
10214
|
if (this.closed) return;
|
|
10211
10215
|
this.write({ ts: Date.now(), event: "rejected", cmd, reason, dangerous });
|
|
10212
10216
|
}
|
|
10217
|
+
logForced(cmd) {
|
|
10218
|
+
if (this.closed) return;
|
|
10219
|
+
this.write({ ts: Date.now(), event: "forced", cmd });
|
|
10220
|
+
}
|
|
10213
10221
|
logExit(code) {
|
|
10214
10222
|
if (this.closed) return;
|
|
10215
10223
|
this.write({ ts: Date.now(), event: "exit", code });
|
|
@@ -10380,13 +10388,24 @@ function openBrowser(url) {
|
|
|
10380
10388
|
} catch {
|
|
10381
10389
|
}
|
|
10382
10390
|
}
|
|
10391
|
+
async function mergeLocalConfig(patch) {
|
|
10392
|
+
const dir = (0, import_node_path3.join)((0, import_node_os3.homedir)(), DEFAULT_CONFIG_DIR);
|
|
10393
|
+
const path = (0, import_node_path3.join)(dir, CONFIG_FILE);
|
|
10394
|
+
let existing = {};
|
|
10395
|
+
try {
|
|
10396
|
+
existing = JSON.parse(await (0, import_promises.readFile)(path, "utf-8"));
|
|
10397
|
+
} catch {
|
|
10398
|
+
}
|
|
10399
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
10400
|
+
await (0, import_promises.writeFile)(path, JSON.stringify({ ...existing, ...patch }, null, 2) + "\n");
|
|
10401
|
+
}
|
|
10383
10402
|
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
10384
10403
|
var AuthError = class extends Error {
|
|
10385
10404
|
};
|
|
10386
10405
|
async function deviceFlowAuthorize(baseUrl, registrationKey, deviceId) {
|
|
10387
10406
|
if (!registrationKey) {
|
|
10388
10407
|
throw new AuthError(
|
|
10389
|
-
"\u7F3A\u5C11 registrationKey\uFF1A\
|
|
10408
|
+
"\u7F3A\u5C11 registrationKey\uFF1A\u5199\u5165 ~/.cli-remote/config.json \u6216\u8BBE\u7F6E REGISTRATION_KEY \u540E\u91CD\u8BD5"
|
|
10390
10409
|
);
|
|
10391
10410
|
}
|
|
10392
10411
|
const res = await postJson(`${baseUrl}/oauth/device_authorization`, {
|
|
@@ -10508,12 +10527,79 @@ function buildWhitelist(cfg) {
|
|
|
10508
10527
|
cfg.whitelist?.remove?.forEach((c) => set.delete(c));
|
|
10509
10528
|
return set;
|
|
10510
10529
|
}
|
|
10530
|
+
function isTTY() {
|
|
10531
|
+
return process.stdin.isTTY === true;
|
|
10532
|
+
}
|
|
10533
|
+
async function readInput(prompt) {
|
|
10534
|
+
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
10535
|
+
try {
|
|
10536
|
+
return await new Promise((resolve) => rl.question(prompt, (answer) => resolve(answer)));
|
|
10537
|
+
} finally {
|
|
10538
|
+
rl.close();
|
|
10539
|
+
}
|
|
10540
|
+
}
|
|
10541
|
+
function toWsUrl(input) {
|
|
10542
|
+
let s = input.trim();
|
|
10543
|
+
if (/^wss?:\/\//i.test(s)) {
|
|
10544
|
+
return s.replace(/\/+$/, "").replace(/\/ws$/, "") + "/ws";
|
|
10545
|
+
}
|
|
10546
|
+
if (/^https:\/\//i.test(s)) s = "wss://" + s.slice("https://".length);
|
|
10547
|
+
else if (/^http:\/\//i.test(s)) s = "ws://" + s.slice("http://".length);
|
|
10548
|
+
else s = "wss://" + s;
|
|
10549
|
+
return s.replace(/\/+$/, "") + "/ws";
|
|
10550
|
+
}
|
|
10551
|
+
var DEFAULT_HUB = "wss://cli-remote.mkjs.net/ws";
|
|
10552
|
+
async function setupWizard(config, opts) {
|
|
10553
|
+
let url = opts.url ?? process.env.RELAY_URL ?? config.relayUrl ?? void 0;
|
|
10554
|
+
let registrationKey = config.registrationKey ?? process.env.REGISTRATION_KEY ?? process.env.BRIDGE_REGISTRATION_KEY ?? void 0;
|
|
10555
|
+
let changed = false;
|
|
10556
|
+
if (!url) {
|
|
10557
|
+
if (!isTTY()) {
|
|
10558
|
+
console.log("[local] \u7F3A\u5C11 hub \u5730\u5740\uFF0C\u4E14\u5F53\u524D\u975E\u4EA4\u4E92\u7EC8\u7AEF\u3002");
|
|
10559
|
+
console.log("[local] \u7528\u6CD5: cli-local wss://your-hub/ws");
|
|
10560
|
+
console.log('[local] \u6216\u5199\u5165 ~/.cli-remote/config.json: { "relayUrl": "wss://your-hub/ws" }');
|
|
10561
|
+
return null;
|
|
10562
|
+
}
|
|
10563
|
+
const ans = (await readInput(`hub \u5730\u5740\uFF08\u56DE\u8F66\u4F7F\u7528\u9ED8\u8BA4 ${DEFAULT_HUB}\uFF09: `)).trim();
|
|
10564
|
+
url = ans ? toWsUrl(ans) : DEFAULT_HUB;
|
|
10565
|
+
changed = true;
|
|
10566
|
+
}
|
|
10567
|
+
if (!registrationKey) {
|
|
10568
|
+
const base = baseUrlFromWsUrl(url);
|
|
10569
|
+
console.log("");
|
|
10570
|
+
console.log("[local] \u7F3A\u5C11 registrationKey\u3002");
|
|
10571
|
+
console.log(`[local] \u5DF2\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u767B\u5F55 ${base} \u5E76\u5728\u300C\u6211\u7684\u8BBE\u5907\u300D\u9875\u590D\u5236 registrationKey`);
|
|
10572
|
+
openBrowser(base);
|
|
10573
|
+
if (!isTTY()) {
|
|
10574
|
+
console.log("[local] \u5F53\u524D\u975E\u4EA4\u4E92\u7EC8\u7AEF\uFF1A\u62FF\u5230 key \u540E\u5199\u5165 ~/.cli-remote/config.json \u518D\u8FD0\u884C\u3002");
|
|
10575
|
+
return null;
|
|
10576
|
+
}
|
|
10577
|
+
const ans = (await readInput("\u7C98\u8D34 registrationKey: ")).trim();
|
|
10578
|
+
if (!ans) {
|
|
10579
|
+
console.log("[local] \u672A\u63D0\u4F9B registrationKey\uFF0C\u9000\u51FA\u3002");
|
|
10580
|
+
return null;
|
|
10581
|
+
}
|
|
10582
|
+
registrationKey = ans;
|
|
10583
|
+
changed = true;
|
|
10584
|
+
}
|
|
10585
|
+
if (changed) {
|
|
10586
|
+
await mergeLocalConfig({ relayUrl: url, registrationKey });
|
|
10587
|
+
console.log("[local] \u914D\u7F6E\u5DF2\u4FDD\u5B58\u5230 ~/.cli-remote/config.json");
|
|
10588
|
+
}
|
|
10589
|
+
return { url, registrationKey };
|
|
10590
|
+
}
|
|
10511
10591
|
async function startLocal(opts = {}) {
|
|
10512
10592
|
const config = await loadConfig();
|
|
10513
|
-
|
|
10593
|
+
let url = opts.url ?? process.env.RELAY_URL ?? config.relayUrl ?? "ws://127.0.0.1:8080/ws";
|
|
10594
|
+
let registrationKey = config.registrationKey ?? process.env.REGISTRATION_KEY ?? process.env.BRIDGE_REGISTRATION_KEY;
|
|
10595
|
+
if (!config.relayUrl && !process.env.RELAY_URL && !opts.url || !registrationKey) {
|
|
10596
|
+
const setup = await setupWizard(config, opts);
|
|
10597
|
+
if (!setup) return;
|
|
10598
|
+
url = setup.url;
|
|
10599
|
+
registrationKey = setup.registrationKey;
|
|
10600
|
+
}
|
|
10514
10601
|
const baseUrl = baseUrlFromWsUrl(url);
|
|
10515
10602
|
const whitelist = buildWhitelist(config);
|
|
10516
|
-
const registrationKey = config.registrationKey ?? process.env.REGISTRATION_KEY ?? process.env.BRIDGE_REGISTRATION_KEY;
|
|
10517
10603
|
const pty = new PtyManager();
|
|
10518
10604
|
const loggers = /* @__PURE__ */ new Map();
|
|
10519
10605
|
const lineBuffers = /* @__PURE__ */ new Map();
|
|
@@ -10521,23 +10607,17 @@ async function startLocal(opts = {}) {
|
|
|
10521
10607
|
console.log(`[local] connecting to ${url}`);
|
|
10522
10608
|
console.log(`[local] deviceId=${credentials.deviceId}`);
|
|
10523
10609
|
console.log(`[local] whitelist: ${whitelist.size} commands`);
|
|
10524
|
-
|
|
10610
|
+
let ws = null;
|
|
10525
10611
|
let heartbeatTimer = null;
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
lineBuffers.clear();
|
|
10612
|
+
let reconnectTimer = null;
|
|
10613
|
+
let reconnectAttempts = 0;
|
|
10614
|
+
let userExitCode = null;
|
|
10615
|
+
function stopHeartbeat() {
|
|
10531
10616
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10532
|
-
|
|
10533
|
-
ws.close();
|
|
10534
|
-
} catch {
|
|
10535
|
-
}
|
|
10536
|
-
process.exit(code);
|
|
10617
|
+
heartbeatTimer = null;
|
|
10537
10618
|
}
|
|
10538
|
-
|
|
10539
|
-
|
|
10540
|
-
ws.send(
|
|
10619
|
+
function sendAuth() {
|
|
10620
|
+
ws?.send(
|
|
10541
10621
|
encodeFrame({
|
|
10542
10622
|
t: "auth",
|
|
10543
10623
|
role: "bridge",
|
|
@@ -10545,13 +10625,73 @@ async function startLocal(opts = {}) {
|
|
|
10545
10625
|
token: credentials.accessToken
|
|
10546
10626
|
})
|
|
10547
10627
|
);
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10628
|
+
}
|
|
10629
|
+
function connect() {
|
|
10630
|
+
if (userExitCode !== null) return;
|
|
10631
|
+
const sock = new wrapper_default(url);
|
|
10632
|
+
ws = sock;
|
|
10633
|
+
sock.on("open", () => {
|
|
10634
|
+
console.log("[local] connected");
|
|
10635
|
+
sendAuth();
|
|
10636
|
+
stopHeartbeat();
|
|
10637
|
+
heartbeatTimer = setInterval(() => {
|
|
10638
|
+
if (sock.readyState === wrapper_default.OPEN) {
|
|
10639
|
+
sock.send(encodeFrame({ t: "ping" }));
|
|
10640
|
+
}
|
|
10641
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
10642
|
+
});
|
|
10643
|
+
sock.on("message", (raw) => handleFrame(sock, raw));
|
|
10644
|
+
sock.on("close", () => {
|
|
10645
|
+
stopHeartbeat();
|
|
10646
|
+
if (ws === sock) ws = null;
|
|
10647
|
+
if (userExitCode !== null) {
|
|
10648
|
+
process.exit(userExitCode);
|
|
10649
|
+
}
|
|
10650
|
+
console.log("[local] disconnected; PTY sessions kept alive, reconnecting\u2026");
|
|
10651
|
+
scheduleReconnect();
|
|
10652
|
+
});
|
|
10653
|
+
sock.on("error", (err) => {
|
|
10654
|
+
console.error("[local] ws error:", err.message);
|
|
10655
|
+
});
|
|
10656
|
+
}
|
|
10657
|
+
async function scheduleReconnect() {
|
|
10658
|
+
if (reconnectTimer || userExitCode !== null) return;
|
|
10659
|
+
const idx = Math.min(reconnectAttempts, RECONNECT_DELAYS_MS.length - 1);
|
|
10660
|
+
const delay = RECONNECT_DELAYS_MS[idx];
|
|
10661
|
+
reconnectAttempts++;
|
|
10662
|
+
console.log(`[local] reconnecting in ${Math.round(delay / 1e3)}s (attempt ${reconnectAttempts})`);
|
|
10663
|
+
reconnectTimer = setTimeout(() => {
|
|
10664
|
+
reconnectTimer = null;
|
|
10665
|
+
ensureFreshAccessToken(baseUrl, credentials).then((creds) => {
|
|
10666
|
+
credentials = creds;
|
|
10667
|
+
connect();
|
|
10668
|
+
}).catch(async (e) => {
|
|
10669
|
+
await clearCredentials();
|
|
10670
|
+
console.error(`[local] ${e.message}`);
|
|
10671
|
+
console.error("[local] \u51ED\u8BC1\u5DF2\u5931\u6548\u5E76\u6E05\u9664\uFF1B\u8BF7\u91CD\u65B0\u8FD0\u884C cli-local \u5B8C\u6210\u6388\u6743");
|
|
10672
|
+
exitProcess(1);
|
|
10673
|
+
});
|
|
10674
|
+
}, delay);
|
|
10675
|
+
}
|
|
10676
|
+
function exitProcess(code) {
|
|
10677
|
+
userExitCode = code;
|
|
10678
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
10679
|
+
reconnectTimer = null;
|
|
10680
|
+
pty.killAll();
|
|
10681
|
+
for (const [, logger] of loggers) logger.logExit(-1);
|
|
10682
|
+
loggers.clear();
|
|
10683
|
+
lineBuffers.clear();
|
|
10684
|
+
stopHeartbeat();
|
|
10685
|
+
try {
|
|
10686
|
+
ws?.close();
|
|
10687
|
+
} catch {
|
|
10688
|
+
}
|
|
10689
|
+
process.exit(code);
|
|
10690
|
+
}
|
|
10691
|
+
function cleanup(code = 0) {
|
|
10692
|
+
exitProcess(code);
|
|
10693
|
+
}
|
|
10694
|
+
function handleFrame(sock, raw) {
|
|
10555
10695
|
let frame;
|
|
10556
10696
|
try {
|
|
10557
10697
|
frame = decodeFrame(toUint8(raw));
|
|
@@ -10560,6 +10700,10 @@ async function startLocal(opts = {}) {
|
|
|
10560
10700
|
}
|
|
10561
10701
|
switch (frame.t) {
|
|
10562
10702
|
case "auth-ok":
|
|
10703
|
+
if (reconnectAttempts > 0) {
|
|
10704
|
+
console.log(`[local] reconnected after ${reconnectAttempts} attempt(s); ${pty.list().length} PTY session(s) alive`);
|
|
10705
|
+
}
|
|
10706
|
+
reconnectAttempts = 0;
|
|
10563
10707
|
console.log(`[local] auth ok, deviceId=${frame.channelId}`);
|
|
10564
10708
|
break;
|
|
10565
10709
|
case "auth-err":
|
|
@@ -10585,20 +10729,13 @@ async function startLocal(opts = {}) {
|
|
|
10585
10729
|
default:
|
|
10586
10730
|
console.log("[local] unhandled frame:", frame.t);
|
|
10587
10731
|
}
|
|
10588
|
-
}
|
|
10732
|
+
}
|
|
10589
10733
|
async function handleAuthErr(reason) {
|
|
10590
10734
|
if (reason === "access token expired") {
|
|
10591
10735
|
try {
|
|
10592
10736
|
credentials = await ensureFreshAccessToken(baseUrl, credentials);
|
|
10593
10737
|
console.log("[local] token refreshed, retrying auth");
|
|
10594
|
-
|
|
10595
|
-
encodeFrame({
|
|
10596
|
-
t: "auth",
|
|
10597
|
-
role: "bridge",
|
|
10598
|
-
deviceId: credentials.deviceId,
|
|
10599
|
-
token: credentials.accessToken
|
|
10600
|
-
})
|
|
10601
|
-
);
|
|
10738
|
+
sendAuth();
|
|
10602
10739
|
return;
|
|
10603
10740
|
} catch (e) {
|
|
10604
10741
|
console.error(`[local] ${e.message}`);
|
|
@@ -10607,7 +10744,7 @@ async function startLocal(opts = {}) {
|
|
|
10607
10744
|
await clearCredentials();
|
|
10608
10745
|
console.error("[local] auth failed:", reason);
|
|
10609
10746
|
console.error("[local] \u51ED\u8BC1\u5DF2\u6E05\u9664\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C cli-local \u5B8C\u6210\u6388\u6743");
|
|
10610
|
-
|
|
10747
|
+
exitProcess(1);
|
|
10611
10748
|
}
|
|
10612
10749
|
function handleSpawn(frame) {
|
|
10613
10750
|
const sessionId = frame.sessionId;
|
|
@@ -10663,6 +10800,15 @@ async function startLocal(opts = {}) {
|
|
|
10663
10800
|
const logger = loggers.get(sessionId);
|
|
10664
10801
|
const session = pty.get(sessionId);
|
|
10665
10802
|
if (!session || !logger) return;
|
|
10803
|
+
if (frame.force) {
|
|
10804
|
+
const line = new TextDecoder().decode(frame.data);
|
|
10805
|
+
ptyWriteChar(sessionId, line + "\r");
|
|
10806
|
+
logger.logInput(new TextEncoder().encode(line + "\r"));
|
|
10807
|
+
logger.logForced(line);
|
|
10808
|
+
lineBuffers.set(sessionId, "");
|
|
10809
|
+
console.log(`[local] forced execution in ${sessionId}: ${line}`);
|
|
10810
|
+
return;
|
|
10811
|
+
}
|
|
10666
10812
|
if (session.cmd === "shell" && !session.inAltScreen) {
|
|
10667
10813
|
handleShellInput(sessionId, frame.data, logger);
|
|
10668
10814
|
} else {
|
|
@@ -10737,23 +10883,13 @@ async function startLocal(opts = {}) {
|
|
|
10737
10883
|
);
|
|
10738
10884
|
}
|
|
10739
10885
|
function safeSend(data) {
|
|
10740
|
-
if (ws.readyState === wrapper_default.OPEN) {
|
|
10886
|
+
if (ws && ws.readyState === wrapper_default.OPEN) {
|
|
10741
10887
|
ws.send(data);
|
|
10742
10888
|
}
|
|
10743
10889
|
}
|
|
10744
|
-
ws.on("close", () => {
|
|
10745
|
-
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
10746
|
-
pty.killAll();
|
|
10747
|
-
for (const [, logger] of loggers) logger.logExit(-1);
|
|
10748
|
-
loggers.clear();
|
|
10749
|
-
lineBuffers.clear();
|
|
10750
|
-
console.log("[local] disconnected");
|
|
10751
|
-
});
|
|
10752
|
-
ws.on("error", (err) => {
|
|
10753
|
-
console.error("[local] error:", err.message);
|
|
10754
|
-
});
|
|
10755
10890
|
process.on("SIGINT", () => cleanup(0));
|
|
10756
10891
|
process.on("SIGTERM", () => cleanup(0));
|
|
10892
|
+
connect();
|
|
10757
10893
|
}
|
|
10758
10894
|
function toUint8(raw) {
|
|
10759
10895
|
if (raw instanceof Buffer) return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
@@ -10818,15 +10954,22 @@ if (argUrl && !argMatch && (argUrl === "-h" || argUrl === "--help" || argUrl ===
|
|
|
10818
10954
|
|
|
10819
10955
|
usage:
|
|
10820
10956
|
cli-local [HUB_URL]
|
|
10957
|
+
cli-local install [HUB_URL] \u5B89\u88C5 macOS launchd \u5E38\u9A7B\u670D\u52A1\uFF08\u5F00\u673A\u81EA\u542F + \u65AD\u7EBF\u81EA\u52A8\u91CD\u8FDE\uFF09
|
|
10958
|
+
cli-local uninstall \u5378\u8F7D launchd \u5E38\u9A7B\u670D\u52A1
|
|
10821
10959
|
|
|
10822
|
-
\u9996\u6B21\u8FD0\u884C\
|
|
10823
|
-
1. \
|
|
10824
|
-
2. \
|
|
10825
|
-
3. \
|
|
10960
|
+
\u9996\u6B21\u8FD0\u884C\uFF08\u65E0\u914D\u7F6E\uFF09\u4F1A\u8FDB\u5165\u5F15\u5BFC\uFF1A
|
|
10961
|
+
1. \u63D0\u793A hub \u5730\u5740\uFF08\u56DE\u8F66\u7528\u9ED8\u8BA4\uFF09
|
|
10962
|
+
2. \u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668 \u2192 \u767B\u5F55 hub\uFF0C\u4ECE\u300C\u6211\u7684\u8BBE\u5907\u300D\u9875\u590D\u5236 registrationKey
|
|
10963
|
+
3. \u56DE\u5230\u7EC8\u7AEF\u7C98\u8D34 key \u2192 \u81EA\u52A8\u5199\u5165 ~/.cli-remote/config.json
|
|
10964
|
+
4. \u968F\u540E\u8D70 OAuth Device Flow\uFF08RFC 8628\uFF09\uFF1A\u81EA\u52A8\u6253\u5F00\u6388\u6743\u9875 \u2192 \u6279\u51C6\u672C\u8BBE\u5907
|
|
10965
|
+
5. \u51ED\u8BC1\u5B58\u5165 ~/.cli-remote/credentials.json\uFF080600\uFF09\uFF0C\u4E4B\u540E\u81EA\u52A8\u7EED\u671F
|
|
10966
|
+
|
|
10967
|
+
\u65AD\u7EBF\u81EA\u52A8\u91CD\u8FDE\uFF1Ahub \u91CD\u542F/\u7F51\u7EDC\u95EA\u65AD\u540E\u8FDB\u7A0B\u4E0D\u9000\u51FA\uFF0CPTY \u4F1A\u8BDD\u4FDD\u6D3B\u5E76\u81EA\u52A8\u91CD\u8FDE\u3002
|
|
10826
10968
|
|
|
10827
10969
|
environment:
|
|
10828
10970
|
RELAY_URL hub ws url (overrides argv and config)
|
|
10829
10971
|
REGISTRATION_KEY registration key (fallback of config.json)
|
|
10972
|
+
CLI_LOCAL_NO_BROWSER=1 \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u53EA\u6253\u5370\u94FE\u63A5\uFF09
|
|
10830
10973
|
HOME where ~/.cli-remote/ lives
|
|
10831
10974
|
|
|
10832
10975
|
config (~/.cli-remote/config.json):
|
|
@@ -10837,11 +10980,127 @@ config (~/.cli-remote/config.json):
|
|
|
10837
10980
|
}
|
|
10838
10981
|
|
|
10839
10982
|
example:
|
|
10840
|
-
cli-local wss://cli-remote.mkjs.net/ws
|
|
10983
|
+
cli-local wss://cli-remote.mkjs.net/ws
|
|
10984
|
+
cli-local install # \u914D\u7F6E\u5E76\u6388\u6743\u540E\uFF0C\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1`);
|
|
10985
|
+
process.exit(0);
|
|
10986
|
+
}
|
|
10987
|
+
var LAUNCHD_LABEL = "net.cli-remote.local";
|
|
10988
|
+
function launchdPlistPath() {
|
|
10989
|
+
return (0, import_node_path5.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
10990
|
+
}
|
|
10991
|
+
function entryScript() {
|
|
10992
|
+
const a1 = process.argv[1];
|
|
10993
|
+
if (!a1 || a1.endsWith(".ts")) return null;
|
|
10994
|
+
try {
|
|
10995
|
+
return (0, import_node_fs2.realpathSync)(a1);
|
|
10996
|
+
} catch {
|
|
10997
|
+
return a1;
|
|
10998
|
+
}
|
|
10999
|
+
}
|
|
11000
|
+
function run(cmd, args, okToFail = false) {
|
|
11001
|
+
try {
|
|
11002
|
+
return (0, import_node_child_process2.execFileSync)(cmd, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11003
|
+
} catch (e) {
|
|
11004
|
+
if (okToFail) return null;
|
|
11005
|
+
throw e;
|
|
11006
|
+
}
|
|
11007
|
+
}
|
|
11008
|
+
function launchdInstall(urlArg) {
|
|
11009
|
+
if (process.platform !== "darwin") {
|
|
11010
|
+
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`);
|
|
11011
|
+
process.exit(1);
|
|
11012
|
+
}
|
|
11013
|
+
const home = (0, import_node_os5.homedir)();
|
|
11014
|
+
const entry = entryScript();
|
|
11015
|
+
if (!entry) {
|
|
11016
|
+
console.error("[local] \u5F53\u524D\u4EE5\u6E90\u7801\uFF08tsx\uFF09\u6A21\u5F0F\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1\u3002");
|
|
11017
|
+
console.error("[local] \u8BF7\u5148\u901A\u8FC7 npm \u5B89\u88C5\uFF1Anpm i -g @cli-remote/local\uFF0C\u518D\u7528\u5B89\u88C5\u7248\u6267\u884C install\u3002");
|
|
11018
|
+
process.exit(1);
|
|
11019
|
+
}
|
|
11020
|
+
if (urlArg) {
|
|
11021
|
+
const cfgDir = (0, import_node_path5.join)(home, DEFAULT_CONFIG_DIR);
|
|
11022
|
+
(0, import_node_fs2.mkdirSync)(cfgDir, { recursive: true });
|
|
11023
|
+
const cfgPath = (0, import_node_path5.join)(cfgDir, "config.json");
|
|
11024
|
+
let cfg = {};
|
|
11025
|
+
try {
|
|
11026
|
+
cfg = JSON.parse((0, import_node_fs2.readFileSync)(cfgPath, "utf-8"));
|
|
11027
|
+
} catch {
|
|
11028
|
+
}
|
|
11029
|
+
cfg.relayUrl = urlArg;
|
|
11030
|
+
(0, import_node_fs2.writeFileSync)(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
11031
|
+
console.log(`[local] relayUrl \u5DF2\u5199\u5165 ${cfgPath}`);
|
|
11032
|
+
}
|
|
11033
|
+
const credPath = (0, import_node_path5.join)(home, DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
|
|
11034
|
+
if (!(0, import_node_fs2.existsSync)(credPath)) {
|
|
11035
|
+
console.error("[local] \u5C1A\u672A\u6388\u6743\uFF08\u627E\u4E0D\u5230 credentials.json\uFF09\u3002");
|
|
11036
|
+
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");
|
|
11037
|
+
process.exit(1);
|
|
11038
|
+
}
|
|
11039
|
+
const plist = launchdPlistPath();
|
|
11040
|
+
const logDir = (0, import_node_path5.join)(home, DEFAULT_CONFIG_DIR, "logs");
|
|
11041
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path5.dirname)(plist), { recursive: true });
|
|
11042
|
+
(0, import_node_fs2.mkdirSync)(logDir, { recursive: true });
|
|
11043
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11044
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
11045
|
+
<plist version="1.0">
|
|
11046
|
+
<dict>
|
|
11047
|
+
<key>Label</key><string>${LAUNCHD_LABEL}</string>
|
|
11048
|
+
<key>ProgramArguments</key>
|
|
11049
|
+
<array>
|
|
11050
|
+
<string>${process.execPath}</string>
|
|
11051
|
+
<string>${entry}</string>
|
|
11052
|
+
</array>
|
|
11053
|
+
<key>RunAtLoad</key><true/>
|
|
11054
|
+
<key>KeepAlive</key><true/>
|
|
11055
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
11056
|
+
<key>StandardOutPath</key><string>${(0, import_node_path5.join)(logDir, "daemon.out.log")}</string>
|
|
11057
|
+
<key>StandardErrorPath</key><string>${(0, import_node_path5.join)(logDir, "daemon.err.log")}</string>
|
|
11058
|
+
</dict>
|
|
11059
|
+
</plist>
|
|
11060
|
+
`;
|
|
11061
|
+
(0, import_node_fs2.writeFileSync)(plist, xml);
|
|
11062
|
+
const uid = process.getuid?.();
|
|
11063
|
+
if (uid === void 0) {
|
|
11064
|
+
console.error("[local] \u65E0\u6CD5\u786E\u5B9A uid\uFF0Claunchd \u5B89\u88C5\u5931\u8D25");
|
|
11065
|
+
process.exit(1);
|
|
11066
|
+
}
|
|
11067
|
+
const guiDomain = `gui/${uid}`;
|
|
11068
|
+
run("launchctl", ["bootout", guiDomain, plist], true);
|
|
11069
|
+
run("launchctl", ["unload", "-w", plist], true);
|
|
11070
|
+
run("launchctl", ["bootstrap", guiDomain, plist]);
|
|
11071
|
+
run("launchctl", ["enable", `${guiDomain}/${LAUNCHD_LABEL}`], true);
|
|
11072
|
+
console.log(`[local] launchd \u670D\u52A1\u5DF2\u5B89\u88C5\u5E76\u542F\u52A8\uFF1A${LAUNCHD_LABEL}`);
|
|
11073
|
+
console.log(`[local] plist: ${plist}`);
|
|
11074
|
+
console.log("[local] \u65E5\u5FD7: ~/.cli-remote/logs/daemon.out.log / daemon.err.log");
|
|
11075
|
+
console.log("[local] \u7BA1\u7406\u670D\u52A1: launchctl kickstart -k, bootout, \u6216 cli-local uninstall");
|
|
11076
|
+
}
|
|
11077
|
+
function launchdUninstall() {
|
|
11078
|
+
const plist = launchdPlistPath();
|
|
11079
|
+
if (!(0, import_node_fs2.existsSync)(plist)) {
|
|
11080
|
+
console.log("[local] \u672A\u627E\u5230\u5DF2\u5B89\u88C5\u7684\u5E38\u9A7B\u670D\u52A1\uFF08nothing to uninstall\uFF09");
|
|
11081
|
+
process.exit(0);
|
|
11082
|
+
}
|
|
11083
|
+
const uid = process.getuid?.();
|
|
11084
|
+
const guiDomain = `gui/${uid ?? 0}`;
|
|
11085
|
+
run("launchctl", ["bootout", guiDomain, plist], true);
|
|
11086
|
+
run("launchctl", ["unload", "-w", plist], true);
|
|
11087
|
+
(0, import_node_fs2.rmSync)(plist);
|
|
11088
|
+
console.log(`[local] launchd \u670D\u52A1\u5DF2\u505C\u6B62\u5E76\u5378\u8F7D\uFF1A${LAUNCHD_LABEL}`);
|
|
11089
|
+
}
|
|
11090
|
+
if (argUrl && !argMatch && argUrl === "install") {
|
|
11091
|
+
launchdInstall(process.argv[3]);
|
|
11092
|
+
process.exit(0);
|
|
11093
|
+
}
|
|
11094
|
+
if (argUrl && !argMatch && argUrl === "uninstall") {
|
|
11095
|
+
launchdUninstall();
|
|
10841
11096
|
process.exit(0);
|
|
10842
11097
|
}
|
|
10843
11098
|
startLocal(argMatch ? { url: argUrl } : {}).catch((err) => {
|
|
10844
|
-
|
|
11099
|
+
if (err instanceof AuthError) {
|
|
11100
|
+
console.error(`[local] ${err.message}`);
|
|
11101
|
+
} else {
|
|
11102
|
+
console.error("[local] fatal:", err);
|
|
11103
|
+
}
|
|
10845
11104
|
process.exit(1);
|
|
10846
11105
|
});
|
|
10847
11106
|
process.on("SIGINT", () => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cli-remote/local",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
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": {
|