@cli-remote/local 0.0.4 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +753 -96
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2265,7 +2265,7 @@ var require_websocket = __commonJS({
|
|
|
2265
2265
|
var http = require("http");
|
|
2266
2266
|
var net = require("net");
|
|
2267
2267
|
var tls = require("tls");
|
|
2268
|
-
var { randomBytes, createHash } = require("crypto");
|
|
2268
|
+
var { randomBytes: randomBytes2, createHash } = require("crypto");
|
|
2269
2269
|
var { Duplex, Readable } = require("stream");
|
|
2270
2270
|
var { URL: URL2 } = require("url");
|
|
2271
2271
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -2803,7 +2803,7 @@ var require_websocket = __commonJS({
|
|
|
2803
2803
|
}
|
|
2804
2804
|
}
|
|
2805
2805
|
const defaultPort = isSecure ? 443 : 80;
|
|
2806
|
-
const key =
|
|
2806
|
+
const key = randomBytes2(16).toString("base64");
|
|
2807
2807
|
const request = isSecure ? https.request : http.request;
|
|
2808
2808
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2809
2809
|
let perMessageDeflate;
|
|
@@ -9939,10 +9939,10 @@ var require_lib = __commonJS({
|
|
|
9939
9939
|
});
|
|
9940
9940
|
|
|
9941
9941
|
// src/index.ts
|
|
9942
|
-
var
|
|
9943
|
-
var
|
|
9944
|
-
var
|
|
9945
|
-
var
|
|
9942
|
+
var import_node_fs5 = require("node:fs");
|
|
9943
|
+
var import_node_child_process3 = require("node:child_process");
|
|
9944
|
+
var import_node_os7 = require("node:os");
|
|
9945
|
+
var import_node_path7 = 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,10 +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 import_node_child_process2 = require("node:child_process");
|
|
9960
9961
|
var import_node_readline = require("node:readline");
|
|
9961
|
-
var
|
|
9962
|
-
var
|
|
9963
|
-
var
|
|
9962
|
+
var import_node_fs4 = require("node:fs");
|
|
9963
|
+
var import_node_os6 = require("node:os");
|
|
9964
|
+
var import_node_path6 = require("node:path");
|
|
9964
9965
|
|
|
9965
9966
|
// ../shared/src/codec.ts
|
|
9966
9967
|
var import_msgpack = __toESM(require_dist(), 1);
|
|
@@ -10077,6 +10078,8 @@ function resolveCommand(cmd) {
|
|
|
10077
10078
|
return { file: "codex", args: [] };
|
|
10078
10079
|
case "kimi-cli":
|
|
10079
10080
|
return { file: "kimi", args: [] };
|
|
10081
|
+
case "pi":
|
|
10082
|
+
return { file: "pi", args: [] };
|
|
10080
10083
|
default:
|
|
10081
10084
|
return { file: cmd, args: [] };
|
|
10082
10085
|
}
|
|
@@ -10507,15 +10510,100 @@ async function exists(p) {
|
|
|
10507
10510
|
}
|
|
10508
10511
|
}
|
|
10509
10512
|
|
|
10513
|
+
// src/gateway-api.ts
|
|
10514
|
+
var import_node_http = require("node:http");
|
|
10515
|
+
var import_node_fs3 = require("node:fs");
|
|
10516
|
+
var import_node_os4 = require("node:os");
|
|
10517
|
+
var import_node_path4 = require("node:path");
|
|
10518
|
+
var DEFAULT_API_PORT = 18790;
|
|
10519
|
+
function apiPortFromEnv() {
|
|
10520
|
+
return Number(process.env.CLI_LOCAL_API_PORT) || DEFAULT_API_PORT;
|
|
10521
|
+
}
|
|
10522
|
+
function readConfiguredPort() {
|
|
10523
|
+
if (process.env.CLI_LOCAL_API_PORT) return apiPortFromEnv();
|
|
10524
|
+
try {
|
|
10525
|
+
const cfg = JSON.parse(
|
|
10526
|
+
(0, import_node_fs3.readFileSync)((0, import_node_path4.join)((0, import_node_os4.homedir)(), DEFAULT_CONFIG_DIR, "config.json"), "utf-8")
|
|
10527
|
+
);
|
|
10528
|
+
return Number(cfg.gateway?.apiPort) || DEFAULT_API_PORT;
|
|
10529
|
+
} catch {
|
|
10530
|
+
return DEFAULT_API_PORT;
|
|
10531
|
+
}
|
|
10532
|
+
}
|
|
10533
|
+
function startControlApi(opts) {
|
|
10534
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
10535
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10536
|
+
const route = `${req.method} ${url.pathname}`;
|
|
10537
|
+
const json = (code, body) => {
|
|
10538
|
+
res.writeHead(code, { "content-type": "application/json" });
|
|
10539
|
+
res.end(JSON.stringify(body));
|
|
10540
|
+
};
|
|
10541
|
+
if (route === "GET /healthz") return json(200, { ok: true });
|
|
10542
|
+
if (route === "GET /status") return json(200, opts.getStatus());
|
|
10543
|
+
if (route === "GET /sessions") {
|
|
10544
|
+
return json(200, { sessions: opts.getStatus().sessions });
|
|
10545
|
+
}
|
|
10546
|
+
if (route === "GET /logs") {
|
|
10547
|
+
const n = Math.min(500, Math.max(1, Number(url.searchParams.get("n")) || 200));
|
|
10548
|
+
const lines = opts.getLogs().slice(-n);
|
|
10549
|
+
return json(200, { lines });
|
|
10550
|
+
}
|
|
10551
|
+
if (route === "POST /stop") {
|
|
10552
|
+
json(200, { ok: true });
|
|
10553
|
+
opts.onStop();
|
|
10554
|
+
return;
|
|
10555
|
+
}
|
|
10556
|
+
const killMatch = /^POST \/sessions\/([\w-]+)\/kill$/.exec(`${req.method} ${url.pathname}`);
|
|
10557
|
+
if (killMatch) {
|
|
10558
|
+
const ok = opts.killSession(killMatch[1]);
|
|
10559
|
+
return json(ok ? 200 : 404, ok ? { ok: true } : { ok: false, error: "no such session" });
|
|
10560
|
+
}
|
|
10561
|
+
json(404, { error: "not found" });
|
|
10562
|
+
});
|
|
10563
|
+
server.listen(opts.port, "127.0.0.1");
|
|
10564
|
+
return {
|
|
10565
|
+
port: opts.port,
|
|
10566
|
+
close: () => server.close()
|
|
10567
|
+
};
|
|
10568
|
+
}
|
|
10569
|
+
async function apiGet(port, path, timeoutMs = 800) {
|
|
10570
|
+
try {
|
|
10571
|
+
const ctrl = new AbortController();
|
|
10572
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
10573
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
10574
|
+
signal: ctrl.signal
|
|
10575
|
+
});
|
|
10576
|
+
clearTimeout(timer);
|
|
10577
|
+
if (!res.ok) return null;
|
|
10578
|
+
return await res.json();
|
|
10579
|
+
} catch {
|
|
10580
|
+
return null;
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
async function apiPost(port, path, timeoutMs = 800) {
|
|
10584
|
+
try {
|
|
10585
|
+
const ctrl = new AbortController();
|
|
10586
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
10587
|
+
const res = await fetch(`http://127.0.0.1:${port}${path}`, {
|
|
10588
|
+
method: "POST",
|
|
10589
|
+
signal: ctrl.signal
|
|
10590
|
+
});
|
|
10591
|
+
clearTimeout(timer);
|
|
10592
|
+
return res.ok;
|
|
10593
|
+
} catch {
|
|
10594
|
+
return false;
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
|
|
10510
10598
|
// src/auth.ts
|
|
10511
10599
|
var import_node_child_process = require("node:child_process");
|
|
10512
10600
|
var import_node_crypto = require("node:crypto");
|
|
10513
10601
|
var import_promises2 = require("node:fs/promises");
|
|
10514
|
-
var
|
|
10515
|
-
var
|
|
10602
|
+
var import_node_os5 = require("node:os");
|
|
10603
|
+
var import_node_path5 = require("node:path");
|
|
10516
10604
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
10517
10605
|
function credentialsPath() {
|
|
10518
|
-
return (0,
|
|
10606
|
+
return (0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
|
|
10519
10607
|
}
|
|
10520
10608
|
async function loadCredentials() {
|
|
10521
10609
|
try {
|
|
@@ -10528,19 +10616,13 @@ async function loadCredentials() {
|
|
|
10528
10616
|
}
|
|
10529
10617
|
async function saveCredentials(creds) {
|
|
10530
10618
|
const path = credentialsPath();
|
|
10531
|
-
await (0, import_promises2.mkdir)((0,
|
|
10619
|
+
await (0, import_promises2.mkdir)((0, import_node_path5.join)(path, ".."), { recursive: true });
|
|
10532
10620
|
await (0, import_promises2.writeFile)(path, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
|
|
10533
10621
|
try {
|
|
10534
10622
|
await (0, import_promises2.chmod)(path, 384);
|
|
10535
10623
|
} catch {
|
|
10536
10624
|
}
|
|
10537
10625
|
}
|
|
10538
|
-
async function clearCredentials() {
|
|
10539
|
-
try {
|
|
10540
|
-
await (0, import_promises2.writeFile)(credentialsPath(), "");
|
|
10541
|
-
} catch {
|
|
10542
|
-
}
|
|
10543
|
-
}
|
|
10544
10626
|
function baseUrlFromWsUrl(wsUrl) {
|
|
10545
10627
|
const http = wsUrl.replace(/^ws/, "http");
|
|
10546
10628
|
try {
|
|
@@ -10568,9 +10650,25 @@ function openBrowser(url) {
|
|
|
10568
10650
|
} catch {
|
|
10569
10651
|
}
|
|
10570
10652
|
}
|
|
10653
|
+
async function loginAndFetchRegistrationKey(baseUrl, email, password) {
|
|
10654
|
+
const login = await postJson(`${baseUrl}/api/login`, { email, password });
|
|
10655
|
+
if (login.status !== 200) {
|
|
10656
|
+
const reason = login.data?.reason;
|
|
10657
|
+
throw new AuthError(`\u767B\u5F55\u5931\u8D25: ${reason ?? login.status}`);
|
|
10658
|
+
}
|
|
10659
|
+
const token = login.data.token;
|
|
10660
|
+
if (!token) throw new AuthError("\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u4F1A\u8BDD token");
|
|
10661
|
+
const me = await fetch(`${baseUrl}/api/me`, {
|
|
10662
|
+
headers: { authorization: `Bearer ${token}` }
|
|
10663
|
+
});
|
|
10664
|
+
if (!me.ok) throw new AuthError(`\u83B7\u53D6\u8D26\u53F7\u4FE1\u606F\u5931\u8D25 (${me.status})`);
|
|
10665
|
+
const data = await me.json();
|
|
10666
|
+
if (!data.registrationKey) throw new AuthError("\u8D26\u53F7\u4FE1\u606F\u91CC\u6CA1\u6709 registrationKey");
|
|
10667
|
+
return { registrationKey: data.registrationKey, user: data.user?.email ?? email };
|
|
10668
|
+
}
|
|
10571
10669
|
async function mergeLocalConfig(patch) {
|
|
10572
|
-
const dir = (0,
|
|
10573
|
-
const path = (0,
|
|
10670
|
+
const dir = (0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR);
|
|
10671
|
+
const path = (0, import_node_path5.join)(dir, CONFIG_FILE);
|
|
10574
10672
|
let existing = {};
|
|
10575
10673
|
try {
|
|
10576
10674
|
existing = JSON.parse(await (0, import_promises2.readFile)(path, "utf-8"));
|
|
@@ -10591,7 +10689,7 @@ async function deviceFlowAuthorize(baseUrl, registrationKey, deviceId) {
|
|
|
10591
10689
|
const res = await postJson(`${baseUrl}/oauth/device_authorization`, {
|
|
10592
10690
|
registrationKey,
|
|
10593
10691
|
device_id: deviceId,
|
|
10594
|
-
device_name: (0,
|
|
10692
|
+
device_name: (0, import_node_os5.hostname)(),
|
|
10595
10693
|
platform: process.platform
|
|
10596
10694
|
});
|
|
10597
10695
|
if (res.status !== 200) {
|
|
@@ -10680,6 +10778,20 @@ async function ensureFreshAccessToken(baseUrl, creds) {
|
|
|
10680
10778
|
}
|
|
10681
10779
|
return refreshInFlight;
|
|
10682
10780
|
}
|
|
10781
|
+
async function ensureMachineId() {
|
|
10782
|
+
const path = (0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
|
|
10783
|
+
try {
|
|
10784
|
+
const cfg = JSON.parse(await (0, import_promises2.readFile)(path, "utf-8"));
|
|
10785
|
+
if (typeof cfg.machineId === "string" && cfg.machineId) return cfg.machineId;
|
|
10786
|
+
} catch {
|
|
10787
|
+
}
|
|
10788
|
+
const machineId = "m_" + (0, import_node_crypto.randomBytes)(16).toString("hex");
|
|
10789
|
+
await mergeLocalConfig({ machineId });
|
|
10790
|
+
return machineId;
|
|
10791
|
+
}
|
|
10792
|
+
async function stableDeviceId() {
|
|
10793
|
+
return "dev_" + await ensureMachineId();
|
|
10794
|
+
}
|
|
10683
10795
|
async function ensureCredentials(baseUrl, registrationKey) {
|
|
10684
10796
|
const existing = await loadCredentials();
|
|
10685
10797
|
if (existing) {
|
|
@@ -10689,14 +10801,17 @@ async function ensureCredentials(baseUrl, registrationKey) {
|
|
|
10689
10801
|
console.error(`[local] ${e.message}\uFF0C\u91CD\u65B0\u8D70\u6388\u6743\u6D41\u7A0B`);
|
|
10690
10802
|
}
|
|
10691
10803
|
}
|
|
10692
|
-
return deviceFlowAuthorize(baseUrl, registrationKey,
|
|
10804
|
+
return deviceFlowAuthorize(baseUrl, registrationKey, await stableDeviceId());
|
|
10693
10805
|
}
|
|
10694
10806
|
|
|
10695
10807
|
// src/client.ts
|
|
10808
|
+
function isPermanentAuthFailure(e) {
|
|
10809
|
+
return e instanceof AuthError && e.message.includes("invalid_grant");
|
|
10810
|
+
}
|
|
10696
10811
|
async function loadConfig() {
|
|
10697
|
-
const path = (0,
|
|
10812
|
+
const path = (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
|
|
10698
10813
|
try {
|
|
10699
|
-
return JSON.parse(
|
|
10814
|
+
return JSON.parse((0, import_node_fs4.readFileSync)(path, "utf-8"));
|
|
10700
10815
|
} catch {
|
|
10701
10816
|
return {};
|
|
10702
10817
|
}
|
|
@@ -10710,6 +10825,28 @@ function buildWhitelist(cfg) {
|
|
|
10710
10825
|
function isTTY() {
|
|
10711
10826
|
return process.stdin.isTTY === true;
|
|
10712
10827
|
}
|
|
10828
|
+
function detectCommands() {
|
|
10829
|
+
const bins = [
|
|
10830
|
+
["claude", "claude"],
|
|
10831
|
+
["codex", "codex"],
|
|
10832
|
+
["kimi-cli", "kimi"],
|
|
10833
|
+
["pi", "pi"]
|
|
10834
|
+
];
|
|
10835
|
+
const found = ["shell"];
|
|
10836
|
+
for (const [cmd, bin] of bins) {
|
|
10837
|
+
let ok = false;
|
|
10838
|
+
if (process.platform === "win32") {
|
|
10839
|
+
ok = (0, import_node_child_process2.spawnSync)("where", [bin], { timeout: 5e3 }).status === 0;
|
|
10840
|
+
} else {
|
|
10841
|
+
const shell = process.env.SHELL ?? "/bin/bash";
|
|
10842
|
+
ok = (0, import_node_child_process2.spawnSync)(shell, ["-l", "-c", `command -v ${bin}`], {
|
|
10843
|
+
timeout: 5e3
|
|
10844
|
+
}).status === 0;
|
|
10845
|
+
}
|
|
10846
|
+
if (ok) found.push(cmd);
|
|
10847
|
+
}
|
|
10848
|
+
return found;
|
|
10849
|
+
}
|
|
10713
10850
|
async function readInput(prompt) {
|
|
10714
10851
|
const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
10715
10852
|
try {
|
|
@@ -10748,18 +10885,42 @@ async function setupWizard(config, opts) {
|
|
|
10748
10885
|
const base = baseUrlFromWsUrl(url);
|
|
10749
10886
|
console.log("");
|
|
10750
10887
|
console.log("[local] \u7F3A\u5C11 registrationKey\u3002");
|
|
10751
|
-
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`);
|
|
10752
|
-
openBrowser(base);
|
|
10753
10888
|
if (!isTTY()) {
|
|
10889
|
+
openBrowser(base);
|
|
10754
10890
|
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");
|
|
10755
10891
|
return null;
|
|
10756
10892
|
}
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10893
|
+
console.log(" 1) \u767B\u5F55\u8D26\u53F7\u81EA\u52A8\u83B7\u53D6\uFF08\u63A8\u8350\uFF09");
|
|
10894
|
+
console.log(" 2) \u4ECE\u7F51\u9875\u300C\u63A7\u5236\u9762\u677F\u300D\u590D\u5236\u7C98\u8D34");
|
|
10895
|
+
const mode = (await readInput("\u9009\u62E9 [1]: ")).trim();
|
|
10896
|
+
if (mode === "2") {
|
|
10897
|
+
openBrowser(base);
|
|
10898
|
+
console.log(`[local] \u5DF2\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u767B\u5F55 ${base} \u5E76\u5728\u300C\u63A7\u5236\u9762\u677F\u300D\u9875\u590D\u5236 registrationKey`);
|
|
10899
|
+
const ans = (await readInput("\u7C98\u8D34 registrationKey: ")).trim();
|
|
10900
|
+
if (!ans) {
|
|
10901
|
+
console.log("[local] \u672A\u63D0\u4F9B registrationKey\uFF0C\u9000\u51FA\u3002");
|
|
10902
|
+
return null;
|
|
10903
|
+
}
|
|
10904
|
+
registrationKey = ans;
|
|
10905
|
+
} else {
|
|
10906
|
+
for (; ; ) {
|
|
10907
|
+
const email = (await readInput("\u90AE\u7BB1: ")).trim();
|
|
10908
|
+
const password = (await readInput("\u5BC6\u7801: ")).trim();
|
|
10909
|
+
if (!email || !password) {
|
|
10910
|
+
console.log("[local] \u90AE\u7BB1\u6216\u5BC6\u7801\u4E3A\u7A7A\uFF0C\u91CD\u8BD5\uFF08\u6216 Ctrl+C \u9000\u51FA\uFF09\u3002");
|
|
10911
|
+
continue;
|
|
10912
|
+
}
|
|
10913
|
+
try {
|
|
10914
|
+
const res = await loginAndFetchRegistrationKey(base, email, password);
|
|
10915
|
+
registrationKey = res.registrationKey;
|
|
10916
|
+
console.log(`[local] \u5DF2\u767B\u5F55 ${res.user}\uFF0C\u83B7\u53D6 registrationKey \u6210\u529F`);
|
|
10917
|
+
break;
|
|
10918
|
+
} catch (e) {
|
|
10919
|
+
console.error(`[local] ${e.message}`);
|
|
10920
|
+
console.log("[local] \u91CD\u8BD5\uFF08\u6216 Ctrl+C \u9000\u51FA\uFF09\u3002");
|
|
10921
|
+
}
|
|
10922
|
+
}
|
|
10761
10923
|
}
|
|
10762
|
-
registrationKey = ans;
|
|
10763
10924
|
changed = true;
|
|
10764
10925
|
}
|
|
10765
10926
|
if (changed) {
|
|
@@ -10787,14 +10948,123 @@ async function startLocal(opts = {}) {
|
|
|
10787
10948
|
(data) => safeSend(data),
|
|
10788
10949
|
(msg) => console.log(`[local] ${msg}`)
|
|
10789
10950
|
);
|
|
10790
|
-
|
|
10951
|
+
const other = acquireSingleInstanceLock();
|
|
10952
|
+
if (other !== null) {
|
|
10953
|
+
console.error(`[local] \u53E6\u4E00\u4E2A cli-local \u6B63\u5728\u8FD0\u884C (pid ${other})\uFF0C\u62D2\u7EDD\u53CC\u5F00\u3002`);
|
|
10954
|
+
console.error("[local] \u53CC\u5F00\u4F1A\u4E92\u76F8\u5237\u65B0 token \u5BFC\u81F4\u8BBE\u5907\u88AB\u670D\u52A1\u7AEF\u540A\u9500\u3002");
|
|
10955
|
+
console.error("[local] \u540E\u53F0\u5E38\u9A7B\u8BF7\u7528: cli-local gateway start / restart / status");
|
|
10956
|
+
process.exit(1);
|
|
10957
|
+
}
|
|
10958
|
+
const startedAt = Date.now();
|
|
10959
|
+
const logRing = [];
|
|
10960
|
+
if (opts.daemon) {
|
|
10961
|
+
writeGatewayPid();
|
|
10962
|
+
patchConsoleIntoRing(logRing);
|
|
10963
|
+
}
|
|
10964
|
+
let credentials;
|
|
10965
|
+
if (opts.daemon) {
|
|
10966
|
+
const existing = await loadCredentials();
|
|
10967
|
+
if (!existing) {
|
|
10968
|
+
credentials = {
|
|
10969
|
+
deviceId: "",
|
|
10970
|
+
accessToken: "",
|
|
10971
|
+
refreshToken: "",
|
|
10972
|
+
accessTokenExpiresAt: 0
|
|
10973
|
+
};
|
|
10974
|
+
} else {
|
|
10975
|
+
credentials = existing;
|
|
10976
|
+
try {
|
|
10977
|
+
credentials = await ensureFreshAccessToken(baseUrl, existing);
|
|
10978
|
+
} catch {
|
|
10979
|
+
}
|
|
10980
|
+
}
|
|
10981
|
+
} else {
|
|
10982
|
+
credentials = await ensureCredentials(baseUrl, registrationKey);
|
|
10983
|
+
}
|
|
10984
|
+
const commands = detectCommands();
|
|
10791
10985
|
console.log(`[local] connecting to ${url}`);
|
|
10792
|
-
console.log(`[local] deviceId=${credentials.deviceId}`);
|
|
10986
|
+
console.log(`[local] deviceId=${credentials.deviceId || "(\u672A\u6388\u6743)"}`);
|
|
10793
10987
|
console.log(`[local] whitelist: ${whitelist.size} commands`);
|
|
10988
|
+
console.log(`[local] available CLIs: ${commands.join(", ")}`);
|
|
10989
|
+
let phase = "starting";
|
|
10990
|
+
let phaseReason = "";
|
|
10991
|
+
let reconnectAttempts = 0;
|
|
10992
|
+
const getStatus = () => ({
|
|
10993
|
+
phase,
|
|
10994
|
+
...phaseReason ? { reason: phaseReason } : {},
|
|
10995
|
+
deviceId: credentials.deviceId || null,
|
|
10996
|
+
hubUrl: url,
|
|
10997
|
+
uptimeMs: Date.now() - startedAt,
|
|
10998
|
+
reconnectAttempts,
|
|
10999
|
+
accessTokenExpiresAt: credentials.accessTokenExpiresAt || null,
|
|
11000
|
+
sessions: pty.list().map((s) => ({
|
|
11001
|
+
sessionId: s.id,
|
|
11002
|
+
cmd: s.cmd,
|
|
11003
|
+
startedAt: s.startedAt
|
|
11004
|
+
}))
|
|
11005
|
+
});
|
|
11006
|
+
function setPhase(p, reason = "") {
|
|
11007
|
+
phase = p;
|
|
11008
|
+
phaseReason = reason;
|
|
11009
|
+
}
|
|
11010
|
+
let pairingTimer = null;
|
|
11011
|
+
let pairingCredMtime = 0;
|
|
11012
|
+
function enterNeedsPairing(reason) {
|
|
11013
|
+
setPhase("needs-pairing", reason);
|
|
11014
|
+
console.error(`[local] \u9700\u8981\u91CD\u65B0\u6388\u6743: ${reason}`);
|
|
11015
|
+
console.error("[local] \u5728\u4EFB\u610F\u7EC8\u7AEF\u8FD0\u884C: cli-local pair");
|
|
11016
|
+
console.error("[local] \uFF08\u672C\u8FDB\u7A0B\u4FDD\u6301\u8FD0\u884C\uFF0CPTY \u4F1A\u8BDD\u4FDD\u6D3B\uFF1B\u914D\u5BF9\u5B8C\u6210\u540E\u81EA\u52A8\u91CD\u8FDE\uFF09");
|
|
11017
|
+
if (pairingTimer) return;
|
|
11018
|
+
try {
|
|
11019
|
+
pairingCredMtime = (0, import_node_fs4.statSync)(credentialsPathOf()).mtimeMs;
|
|
11020
|
+
} catch {
|
|
11021
|
+
pairingCredMtime = 0;
|
|
11022
|
+
}
|
|
11023
|
+
pairingTimer = setInterval(async () => {
|
|
11024
|
+
let mtime;
|
|
11025
|
+
try {
|
|
11026
|
+
mtime = (0, import_node_fs4.statSync)(credentialsPathOf()).mtimeMs;
|
|
11027
|
+
} catch {
|
|
11028
|
+
return;
|
|
11029
|
+
}
|
|
11030
|
+
if (mtime <= pairingCredMtime) return;
|
|
11031
|
+
const fresh = await loadCredentials();
|
|
11032
|
+
if (!fresh?.deviceId || !fresh?.refreshToken) return;
|
|
11033
|
+
pairingCredMtime = mtime;
|
|
11034
|
+
credentials = fresh;
|
|
11035
|
+
console.log("[local] \u68C0\u6D4B\u5230\u65B0\u51ED\u8BC1\uFF0C\u91CD\u8FDE\u4E2D\u2026");
|
|
11036
|
+
if (pairingTimer) clearInterval(pairingTimer);
|
|
11037
|
+
pairingTimer = null;
|
|
11038
|
+
reconnectAttempts = 0;
|
|
11039
|
+
setPhase("connecting");
|
|
11040
|
+
connect();
|
|
11041
|
+
}, 2e3);
|
|
11042
|
+
}
|
|
11043
|
+
let controlApi = null;
|
|
11044
|
+
if (opts.daemon) {
|
|
11045
|
+
const port = readConfiguredPort();
|
|
11046
|
+
controlApi = startControlApi({
|
|
11047
|
+
port,
|
|
11048
|
+
getStatus,
|
|
11049
|
+
getLogs: () => [...logRing],
|
|
11050
|
+
onStop: () => {
|
|
11051
|
+
console.log("[local] \u6536\u5230\u505C\u6B62\u6307\u4EE4\uFF08\u63A7\u5236 API\uFF09");
|
|
11052
|
+
exitProcess(0);
|
|
11053
|
+
},
|
|
11054
|
+
killSession: (id) => {
|
|
11055
|
+
try {
|
|
11056
|
+
pty.kill(id);
|
|
11057
|
+
return true;
|
|
11058
|
+
} catch {
|
|
11059
|
+
return false;
|
|
11060
|
+
}
|
|
11061
|
+
}
|
|
11062
|
+
});
|
|
11063
|
+
console.log(`[local] control api: http://127.0.0.1:${port} (\u4EC5\u672C\u673A)`);
|
|
11064
|
+
}
|
|
10794
11065
|
let ws = null;
|
|
10795
11066
|
let heartbeatTimer = null;
|
|
10796
11067
|
let reconnectTimer = null;
|
|
10797
|
-
let reconnectAttempts = 0;
|
|
10798
11068
|
let userExitCode = null;
|
|
10799
11069
|
function stopHeartbeat() {
|
|
10800
11070
|
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
@@ -10806,7 +11076,8 @@ async function startLocal(opts = {}) {
|
|
|
10806
11076
|
t: "auth",
|
|
10807
11077
|
role: "bridge",
|
|
10808
11078
|
deviceId: credentials.deviceId,
|
|
10809
|
-
token: credentials.accessToken
|
|
11079
|
+
token: credentials.accessToken,
|
|
11080
|
+
commands
|
|
10810
11081
|
})
|
|
10811
11082
|
);
|
|
10812
11083
|
}
|
|
@@ -10816,6 +11087,7 @@ async function startLocal(opts = {}) {
|
|
|
10816
11087
|
ws = sock;
|
|
10817
11088
|
sock.on("open", () => {
|
|
10818
11089
|
console.log("[local] connected");
|
|
11090
|
+
if (phase !== "needs-pairing") setPhase("connecting");
|
|
10819
11091
|
sendAuth();
|
|
10820
11092
|
stopHeartbeat();
|
|
10821
11093
|
heartbeatTimer = setInterval(() => {
|
|
@@ -10832,6 +11104,7 @@ async function startLocal(opts = {}) {
|
|
|
10832
11104
|
process.exit(userExitCode);
|
|
10833
11105
|
}
|
|
10834
11106
|
console.log("[local] disconnected; PTY sessions kept alive, reconnecting\u2026");
|
|
11107
|
+
if (phase !== "needs-pairing") setPhase("reconnecting");
|
|
10835
11108
|
scheduleReconnect();
|
|
10836
11109
|
});
|
|
10837
11110
|
sock.on("error", (err) => {
|
|
@@ -10840,6 +11113,7 @@ async function startLocal(opts = {}) {
|
|
|
10840
11113
|
}
|
|
10841
11114
|
async function scheduleReconnect() {
|
|
10842
11115
|
if (reconnectTimer || userExitCode !== null) return;
|
|
11116
|
+
if (phase === "needs-pairing") return;
|
|
10843
11117
|
const idx = Math.min(reconnectAttempts, RECONNECT_DELAYS_MS.length - 1);
|
|
10844
11118
|
const delay = RECONNECT_DELAYS_MS[idx];
|
|
10845
11119
|
reconnectAttempts++;
|
|
@@ -10849,11 +11123,13 @@ async function startLocal(opts = {}) {
|
|
|
10849
11123
|
ensureFreshAccessToken(baseUrl, credentials).then((creds) => {
|
|
10850
11124
|
credentials = creds;
|
|
10851
11125
|
connect();
|
|
10852
|
-
}).catch(
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
11126
|
+
}).catch((e) => {
|
|
11127
|
+
if (isPermanentAuthFailure(e)) {
|
|
11128
|
+
enterNeedsPairing(e.message);
|
|
11129
|
+
return;
|
|
11130
|
+
}
|
|
11131
|
+
console.error(`[local] token \u5237\u65B0\u6682\u65F6\u5931\u8D25: ${e.message}`);
|
|
11132
|
+
scheduleReconnect();
|
|
10857
11133
|
});
|
|
10858
11134
|
}, delay);
|
|
10859
11135
|
}
|
|
@@ -10861,6 +11137,10 @@ async function startLocal(opts = {}) {
|
|
|
10861
11137
|
userExitCode = code;
|
|
10862
11138
|
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
10863
11139
|
reconnectTimer = null;
|
|
11140
|
+
if (pairingTimer) clearInterval(pairingTimer);
|
|
11141
|
+
pairingTimer = null;
|
|
11142
|
+
controlApi?.close();
|
|
11143
|
+
if (opts.daemon) removeGatewayPid();
|
|
10864
11144
|
transfers.abortAll();
|
|
10865
11145
|
pty.killAll();
|
|
10866
11146
|
for (const [, logger] of loggers) logger.logExit(-1);
|
|
@@ -10889,6 +11169,7 @@ async function startLocal(opts = {}) {
|
|
|
10889
11169
|
console.log(`[local] reconnected after ${reconnectAttempts} attempt(s); ${pty.list().length} PTY session(s) alive`);
|
|
10890
11170
|
}
|
|
10891
11171
|
reconnectAttempts = 0;
|
|
11172
|
+
setPhase("connected");
|
|
10892
11173
|
console.log(`[local] auth ok, deviceId=${frame.channelId}`);
|
|
10893
11174
|
break;
|
|
10894
11175
|
case "auth-err":
|
|
@@ -10931,20 +11212,26 @@ async function startLocal(opts = {}) {
|
|
|
10931
11212
|
}
|
|
10932
11213
|
}
|
|
10933
11214
|
async function handleAuthErr(reason) {
|
|
10934
|
-
if (reason === "access token expired") {
|
|
11215
|
+
if (reason === "access token expired" || reason === "invalid access token") {
|
|
10935
11216
|
try {
|
|
10936
11217
|
credentials = await ensureFreshAccessToken(baseUrl, credentials);
|
|
10937
11218
|
console.log("[local] token refreshed, retrying auth");
|
|
10938
11219
|
sendAuth();
|
|
10939
11220
|
return;
|
|
10940
11221
|
} catch (e) {
|
|
10941
|
-
|
|
11222
|
+
if (isPermanentAuthFailure(e)) {
|
|
11223
|
+
enterNeedsPairing(e.message);
|
|
11224
|
+
return;
|
|
11225
|
+
}
|
|
11226
|
+
console.error(`[local] token \u5237\u65B0\u6682\u65F6\u5931\u8D25\uFF08${e.message}\uFF09\uFF0C\u8F6C\u5165\u91CD\u8FDE`);
|
|
11227
|
+
try {
|
|
11228
|
+
ws?.close();
|
|
11229
|
+
} catch {
|
|
11230
|
+
}
|
|
11231
|
+
return;
|
|
10942
11232
|
}
|
|
10943
11233
|
}
|
|
10944
|
-
|
|
10945
|
-
console.error("[local] auth failed:", reason);
|
|
10946
|
-
console.error("[local] \u51ED\u8BC1\u5DF2\u6E05\u9664\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C cli-local \u5B8C\u6210\u6388\u6743");
|
|
10947
|
-
exitProcess(1);
|
|
11234
|
+
enterNeedsPairing(reason);
|
|
10948
11235
|
}
|
|
10949
11236
|
function handleSpawn(frame) {
|
|
10950
11237
|
const sessionId = frame.sessionId;
|
|
@@ -11078,7 +11365,8 @@ async function startLocal(opts = {}) {
|
|
|
11078
11365
|
encodeFrame({
|
|
11079
11366
|
t: "replay-res",
|
|
11080
11367
|
sessionId: frame.sessionId,
|
|
11081
|
-
frames
|
|
11368
|
+
frames,
|
|
11369
|
+
exists: pty.has(frame.sessionId)
|
|
11082
11370
|
})
|
|
11083
11371
|
);
|
|
11084
11372
|
}
|
|
@@ -11089,7 +11377,57 @@ async function startLocal(opts = {}) {
|
|
|
11089
11377
|
}
|
|
11090
11378
|
process.on("SIGINT", () => cleanup(0));
|
|
11091
11379
|
process.on("SIGTERM", () => cleanup(0));
|
|
11092
|
-
|
|
11380
|
+
if (credentials.deviceId) {
|
|
11381
|
+
setPhase("connecting");
|
|
11382
|
+
connect();
|
|
11383
|
+
} else {
|
|
11384
|
+
enterNeedsPairing("\u65E0\u51ED\u8BC1\uFF08credentials.json \u7F3A\u5931\u6216\u4E3A\u7A7A\uFF09");
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11387
|
+
function credentialsPathOf() {
|
|
11388
|
+
return (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "credentials.json");
|
|
11389
|
+
}
|
|
11390
|
+
function instanceLockPath() {
|
|
11391
|
+
return (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "instance.lock");
|
|
11392
|
+
}
|
|
11393
|
+
function processAlive(pid) {
|
|
11394
|
+
try {
|
|
11395
|
+
process.kill(pid, 0);
|
|
11396
|
+
return true;
|
|
11397
|
+
} catch {
|
|
11398
|
+
return false;
|
|
11399
|
+
}
|
|
11400
|
+
}
|
|
11401
|
+
function acquireSingleInstanceLock() {
|
|
11402
|
+
const path = instanceLockPath();
|
|
11403
|
+
try {
|
|
11404
|
+
const pid = Number((0, import_node_fs4.readFileSync)(path, "utf-8").trim());
|
|
11405
|
+
if (Number.isFinite(pid) && pid !== process.pid && processAlive(pid)) {
|
|
11406
|
+
return pid;
|
|
11407
|
+
}
|
|
11408
|
+
} catch {
|
|
11409
|
+
}
|
|
11410
|
+
try {
|
|
11411
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path6.join)(path, ".."), { recursive: true });
|
|
11412
|
+
(0, import_node_fs4.writeFileSync)(path, String(process.pid));
|
|
11413
|
+
} catch {
|
|
11414
|
+
}
|
|
11415
|
+
return null;
|
|
11416
|
+
}
|
|
11417
|
+
function patchConsoleIntoRing(ring) {
|
|
11418
|
+
const CAP = 500;
|
|
11419
|
+
const push = (line) => {
|
|
11420
|
+
ring.push(line);
|
|
11421
|
+
if (ring.length > CAP) ring.splice(0, ring.length - CAP);
|
|
11422
|
+
};
|
|
11423
|
+
const wrap = (orig) => {
|
|
11424
|
+
return (...args) => {
|
|
11425
|
+
push(args.map((a) => typeof a === "string" ? a : String(a)).join(" "));
|
|
11426
|
+
orig(...args);
|
|
11427
|
+
};
|
|
11428
|
+
};
|
|
11429
|
+
console.log = wrap(console.log.bind(console));
|
|
11430
|
+
console.error = wrap(console.error.bind(console));
|
|
11093
11431
|
}
|
|
11094
11432
|
function toUint8(raw) {
|
|
11095
11433
|
if (raw instanceof Buffer) return new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength);
|
|
@@ -11099,6 +11437,37 @@ function toUint8(raw) {
|
|
|
11099
11437
|
}
|
|
11100
11438
|
throw new Error("unexpected raw type");
|
|
11101
11439
|
}
|
|
11440
|
+
function gatewayPidFile() {
|
|
11441
|
+
return (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid");
|
|
11442
|
+
}
|
|
11443
|
+
function writeGatewayPid() {
|
|
11444
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR), { recursive: true });
|
|
11445
|
+
(0, import_node_fs4.writeFileSync)(gatewayPidFile(), String(process.pid));
|
|
11446
|
+
}
|
|
11447
|
+
function removeGatewayPid() {
|
|
11448
|
+
try {
|
|
11449
|
+
(0, import_node_fs4.rmSync)(gatewayPidFile());
|
|
11450
|
+
} catch {
|
|
11451
|
+
}
|
|
11452
|
+
}
|
|
11453
|
+
function gatewayPid() {
|
|
11454
|
+
try {
|
|
11455
|
+
const pid = Number((0, import_node_fs4.readFileSync)(gatewayPidFile(), "utf-8").trim());
|
|
11456
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
11457
|
+
} catch {
|
|
11458
|
+
return null;
|
|
11459
|
+
}
|
|
11460
|
+
}
|
|
11461
|
+
function gatewayAlive() {
|
|
11462
|
+
const pid = gatewayPid();
|
|
11463
|
+
if (pid === null) return false;
|
|
11464
|
+
try {
|
|
11465
|
+
process.kill(pid, 0);
|
|
11466
|
+
return true;
|
|
11467
|
+
} catch {
|
|
11468
|
+
return false;
|
|
11469
|
+
}
|
|
11470
|
+
}
|
|
11102
11471
|
|
|
11103
11472
|
// src/index.ts
|
|
11104
11473
|
var import_meta = {};
|
|
@@ -11117,15 +11486,15 @@ function resolveHere() {
|
|
|
11117
11486
|
if (DEBUG) console.error("[debug] cannot resolve script path");
|
|
11118
11487
|
return;
|
|
11119
11488
|
}
|
|
11120
|
-
const hereDir = (0,
|
|
11489
|
+
const hereDir = (0, import_node_path7.dirname)(here);
|
|
11121
11490
|
const candidates = [
|
|
11122
|
-
(0,
|
|
11123
|
-
(0,
|
|
11124
|
-
(0,
|
|
11491
|
+
(0, import_node_path7.join)(hereDir, "..", "node_modules", "node-pty", "prebuilds"),
|
|
11492
|
+
(0, import_node_path7.join)(hereDir, "..", "..", "node-pty", "prebuilds"),
|
|
11493
|
+
(0, import_node_path7.join)(hereDir, "..", "..", "..", "node_modules", "node-pty", "prebuilds")
|
|
11125
11494
|
];
|
|
11126
11495
|
let prebuildsDir = null;
|
|
11127
11496
|
for (const c of candidates) {
|
|
11128
|
-
if ((0,
|
|
11497
|
+
if ((0, import_node_fs5.existsSync)(c)) {
|
|
11129
11498
|
prebuildsDir = c;
|
|
11130
11499
|
break;
|
|
11131
11500
|
}
|
|
@@ -11135,11 +11504,11 @@ function resolveHere() {
|
|
|
11135
11504
|
return;
|
|
11136
11505
|
}
|
|
11137
11506
|
let n = 0;
|
|
11138
|
-
for (const sub of (0,
|
|
11139
|
-
const helper = (0,
|
|
11140
|
-
if ((0,
|
|
11507
|
+
for (const sub of (0, import_node_fs5.readdirSync)(prebuildsDir)) {
|
|
11508
|
+
const helper = (0, import_node_path7.join)(prebuildsDir, sub, "spawn-helper");
|
|
11509
|
+
if ((0, import_node_fs5.existsSync)(helper)) {
|
|
11141
11510
|
try {
|
|
11142
|
-
(0,
|
|
11511
|
+
(0, import_node_fs5.chmodSync)(helper, 493);
|
|
11143
11512
|
n++;
|
|
11144
11513
|
} catch {
|
|
11145
11514
|
}
|
|
@@ -11150,56 +11519,63 @@ function resolveHere() {
|
|
|
11150
11519
|
var argUrl = process.argv[2];
|
|
11151
11520
|
var argMatch = argUrl && (argUrl.startsWith("ws://") || argUrl.startsWith("wss://"));
|
|
11152
11521
|
if (argUrl && !argMatch && (argUrl === "-h" || argUrl === "--help" || argUrl === "help")) {
|
|
11153
|
-
console.log(`cli-local \u2014 local PTY agent for cli-remote
|
|
11522
|
+
console.log(`cli-local \u2014 local PTY agent for cli-remote (gateway \u5E38\u9A7B\u670D\u52A1)
|
|
11154
11523
|
|
|
11155
11524
|
usage:
|
|
11156
|
-
cli-local
|
|
11157
|
-
cli-local
|
|
11158
|
-
cli-local
|
|
11525
|
+
cli-local \u65E0 gateway \u4E14\u5DF2\u914D\u7F6E\u65F6\u524D\u53F0\u8FD0\u884C\uFF1B\u5426\u5219\u663E\u793A\u72B6\u6001
|
|
11526
|
+
cli-local pair \u91CD\u65B0\u6388\u6743\uFF08\u540A\u9500/\u8FC7\u671F\u540E\u91CD\u914D\u5BF9\uFF1Bgateway \u70ED\u52A0\u8F7D\uFF09
|
|
11527
|
+
cli-local gateway start \u540E\u53F0\u542F\u52A8\u5E38\u9A7B gateway\uFF08\u63A8\u8350\uFF09
|
|
11528
|
+
cli-local gateway stop|restart|status|logs [-f] [-n N]
|
|
11529
|
+
cli-local install [HUB_URL] \u5B89\u88C5 macOS launchd \u5E38\u9A7B\u670D\u52A1
|
|
11530
|
+
cli-local uninstall \u5378\u8F7D launchd \u5E38\u9A7B\u670D\u52A1
|
|
11531
|
+
|
|
11532
|
+
\u9996\u6B21\u8FD0\u884C\uFF08\u65E0\u914D\u7F6E\uFF09\u4F1A\u8FDB\u5165\u5F15\u5BFC\uFF1Ahub \u5730\u5740 \u2192 registrationKey \u2192 Device Flow \u6388\u6743\u3002
|
|
11533
|
+
\u6388\u6743\u540E\u5EFA\u8BAE\u7528 cli-local gateway start \u5E38\u9A7B\uFF08\u65AD\u7EBF\u81EA\u52A8\u91CD\u8FDE\u3001PTY \u4FDD\u6D3B\uFF09\u3002
|
|
11159
11534
|
|
|
11160
|
-
\
|
|
11161
|
-
|
|
11162
|
-
2. \u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668 \u2192 \u767B\u5F55 hub\uFF0C\u4ECE\u300C\u6211\u7684\u8BBE\u5907\u300D\u9875\u590D\u5236 registrationKey
|
|
11163
|
-
3. \u56DE\u5230\u7EC8\u7AEF\u7C98\u8D34 key \u2192 \u81EA\u52A8\u5199\u5165 ~/.cli-remote/config.json
|
|
11164
|
-
4. \u968F\u540E\u8D70 OAuth Device Flow\uFF08RFC 8628\uFF09\uFF1A\u81EA\u52A8\u6253\u5F00\u6388\u6743\u9875 \u2192 \u6279\u51C6\u672C\u8BBE\u5907
|
|
11165
|
-
5. \u51ED\u8BC1\u5B58\u5165 ~/.cli-remote/credentials.json\uFF080600\uFF09\uFF0C\u4E4B\u540E\u81EA\u52A8\u7EED\u671F
|
|
11535
|
+
\u88AB\u540A\u9500/\u51ED\u8BC1\u8FC7\u671F\u65F6 gateway \u4E0D\u4F1A\u9000\u51FA\uFF0C\u800C\u662F\u8FDB\u5165 needs-pairing \u7B49\u5F85\uFF1B
|
|
11536
|
+
\u53E6\u5F00\u7EC8\u7AEF\u8DD1 cli-local pair \u5199\u5165\u65B0\u51ED\u8BC1\u540E\u81EA\u52A8\u91CD\u8FDE\uFF08\u65E0\u9700\u91CD\u542F gateway\uFF09\u3002
|
|
11166
11537
|
|
|
11167
|
-
\
|
|
11538
|
+
\u672C\u5730\u63A7\u5236 API\uFF08\u4EC5 127.0.0.1\uFF0Cgateway \u8FD0\u884C\u65F6\uFF09\uFF1A
|
|
11539
|
+
GET /status /healthz /logs /sessions POST /stop
|
|
11540
|
+
\u7AEF\u53E3\u9ED8\u8BA4 18790\uFF0Cconfig.json gateway.apiPort \u6216 CLI_LOCAL_API_PORT \u53EF\u6539
|
|
11168
11541
|
|
|
11169
11542
|
environment:
|
|
11170
11543
|
RELAY_URL hub ws url (overrides argv and config)
|
|
11171
11544
|
REGISTRATION_KEY registration key (fallback of config.json)
|
|
11172
11545
|
CLI_LOCAL_NO_BROWSER=1 \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u53EA\u6253\u5370\u94FE\u63A5\uFF09
|
|
11546
|
+
CLI_LOCAL_API_PORT gateway \u63A7\u5236API\u7AEF\u53E3
|
|
11173
11547
|
HOME where ~/.cli-remote/ lives
|
|
11174
11548
|
|
|
11175
11549
|
config (~/.cli-remote/config.json):
|
|
11176
11550
|
{
|
|
11177
11551
|
"relayUrl": "wss://your-hub/ws",
|
|
11178
11552
|
"registrationKey": "from-the-web-devices-page",
|
|
11553
|
+
"gateway": { "apiPort": 18790 },
|
|
11179
11554
|
"whitelist": { "add": [], "remove": [] }
|
|
11180
11555
|
}
|
|
11181
11556
|
|
|
11182
11557
|
example:
|
|
11183
|
-
cli-local
|
|
11184
|
-
cli-local
|
|
11558
|
+
cli-local pair # \u9996\u6B21/\u91CD\u65B0\u6388\u6743
|
|
11559
|
+
cli-local gateway start # \u540E\u53F0\u5E38\u9A7B
|
|
11560
|
+
cli-local gateway status # \u67E5\u770B\u8FDE\u63A5\u72B6\u6001`);
|
|
11185
11561
|
process.exit(0);
|
|
11186
11562
|
}
|
|
11187
11563
|
var LAUNCHD_LABEL = "net.cli-remote.local";
|
|
11188
11564
|
function launchdPlistPath() {
|
|
11189
|
-
return (0,
|
|
11565
|
+
return (0, import_node_path7.join)((0, import_node_os7.homedir)(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
11190
11566
|
}
|
|
11191
11567
|
function entryScript() {
|
|
11192
11568
|
const a1 = process.argv[1];
|
|
11193
11569
|
if (!a1 || a1.endsWith(".ts")) return null;
|
|
11194
11570
|
try {
|
|
11195
|
-
return (0,
|
|
11571
|
+
return (0, import_node_fs5.realpathSync)(a1);
|
|
11196
11572
|
} catch {
|
|
11197
11573
|
return a1;
|
|
11198
11574
|
}
|
|
11199
11575
|
}
|
|
11200
11576
|
function run(cmd, args, okToFail = false) {
|
|
11201
11577
|
try {
|
|
11202
|
-
return (0,
|
|
11578
|
+
return (0, import_node_child_process3.execFileSync)(cmd, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
11203
11579
|
} catch (e) {
|
|
11204
11580
|
if (okToFail) return null;
|
|
11205
11581
|
throw e;
|
|
@@ -11210,7 +11586,7 @@ function launchdInstall(urlArg) {
|
|
|
11210
11586
|
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`);
|
|
11211
11587
|
process.exit(1);
|
|
11212
11588
|
}
|
|
11213
|
-
const home = (0,
|
|
11589
|
+
const home = (0, import_node_os7.homedir)();
|
|
11214
11590
|
const entry = entryScript();
|
|
11215
11591
|
if (!entry) {
|
|
11216
11592
|
console.error("[local] \u5F53\u524D\u4EE5\u6E90\u7801\uFF08tsx\uFF09\u6A21\u5F0F\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1\u3002");
|
|
@@ -11218,28 +11594,28 @@ function launchdInstall(urlArg) {
|
|
|
11218
11594
|
process.exit(1);
|
|
11219
11595
|
}
|
|
11220
11596
|
if (urlArg) {
|
|
11221
|
-
const cfgDir = (0,
|
|
11222
|
-
(0,
|
|
11223
|
-
const cfgPath = (0,
|
|
11597
|
+
const cfgDir = (0, import_node_path7.join)(home, DEFAULT_CONFIG_DIR);
|
|
11598
|
+
(0, import_node_fs5.mkdirSync)(cfgDir, { recursive: true });
|
|
11599
|
+
const cfgPath = (0, import_node_path7.join)(cfgDir, "config.json");
|
|
11224
11600
|
let cfg = {};
|
|
11225
11601
|
try {
|
|
11226
|
-
cfg = JSON.parse((0,
|
|
11602
|
+
cfg = JSON.parse((0, import_node_fs5.readFileSync)(cfgPath, "utf-8"));
|
|
11227
11603
|
} catch {
|
|
11228
11604
|
}
|
|
11229
11605
|
cfg.relayUrl = urlArg;
|
|
11230
|
-
(0,
|
|
11606
|
+
(0, import_node_fs5.writeFileSync)(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
|
|
11231
11607
|
console.log(`[local] relayUrl \u5DF2\u5199\u5165 ${cfgPath}`);
|
|
11232
11608
|
}
|
|
11233
|
-
const credPath = (0,
|
|
11234
|
-
if (!(0,
|
|
11609
|
+
const credPath = (0, import_node_path7.join)(home, DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
|
|
11610
|
+
if (!(0, import_node_fs5.existsSync)(credPath)) {
|
|
11235
11611
|
console.error("[local] \u5C1A\u672A\u6388\u6743\uFF08\u627E\u4E0D\u5230 credentials.json\uFF09\u3002");
|
|
11236
11612
|
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");
|
|
11237
11613
|
process.exit(1);
|
|
11238
11614
|
}
|
|
11239
11615
|
const plist = launchdPlistPath();
|
|
11240
|
-
const logDir = (0,
|
|
11241
|
-
(0,
|
|
11242
|
-
(0,
|
|
11616
|
+
const logDir = (0, import_node_path7.join)(home, DEFAULT_CONFIG_DIR, "logs");
|
|
11617
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path7.dirname)(plist), { recursive: true });
|
|
11618
|
+
(0, import_node_fs5.mkdirSync)(logDir, { recursive: true });
|
|
11243
11619
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11244
11620
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
11245
11621
|
<plist version="1.0">
|
|
@@ -11253,12 +11629,12 @@ function launchdInstall(urlArg) {
|
|
|
11253
11629
|
<key>RunAtLoad</key><true/>
|
|
11254
11630
|
<key>KeepAlive</key><true/>
|
|
11255
11631
|
<key>ThrottleInterval</key><integer>30</integer>
|
|
11256
|
-
<key>StandardOutPath</key><string>${(0,
|
|
11257
|
-
<key>StandardErrorPath</key><string>${(0,
|
|
11632
|
+
<key>StandardOutPath</key><string>${(0, import_node_path7.join)(logDir, "daemon.out.log")}</string>
|
|
11633
|
+
<key>StandardErrorPath</key><string>${(0, import_node_path7.join)(logDir, "daemon.err.log")}</string>
|
|
11258
11634
|
</dict>
|
|
11259
11635
|
</plist>
|
|
11260
11636
|
`;
|
|
11261
|
-
(0,
|
|
11637
|
+
(0, import_node_fs5.writeFileSync)(plist, xml);
|
|
11262
11638
|
const uid = process.getuid?.();
|
|
11263
11639
|
if (uid === void 0) {
|
|
11264
11640
|
console.error("[local] \u65E0\u6CD5\u786E\u5B9A uid\uFF0Claunchd \u5B89\u88C5\u5931\u8D25");
|
|
@@ -11276,7 +11652,7 @@ function launchdInstall(urlArg) {
|
|
|
11276
11652
|
}
|
|
11277
11653
|
function launchdUninstall() {
|
|
11278
11654
|
const plist = launchdPlistPath();
|
|
11279
|
-
if (!(0,
|
|
11655
|
+
if (!(0, import_node_fs5.existsSync)(plist)) {
|
|
11280
11656
|
console.log("[local] \u672A\u627E\u5230\u5DF2\u5B89\u88C5\u7684\u5E38\u9A7B\u670D\u52A1\uFF08nothing to uninstall\uFF09");
|
|
11281
11657
|
process.exit(0);
|
|
11282
11658
|
}
|
|
@@ -11284,7 +11660,7 @@ function launchdUninstall() {
|
|
|
11284
11660
|
const guiDomain = `gui/${uid ?? 0}`;
|
|
11285
11661
|
run("launchctl", ["bootout", guiDomain, plist], true);
|
|
11286
11662
|
run("launchctl", ["unload", "-w", plist], true);
|
|
11287
|
-
(0,
|
|
11663
|
+
(0, import_node_fs5.rmSync)(plist);
|
|
11288
11664
|
console.log(`[local] launchd \u670D\u52A1\u5DF2\u505C\u6B62\u5E76\u5378\u8F7D\uFF1A${LAUNCHD_LABEL}`);
|
|
11289
11665
|
}
|
|
11290
11666
|
if (argUrl && !argMatch && argUrl === "install") {
|
|
@@ -11295,13 +11671,294 @@ if (argUrl && !argMatch && argUrl === "uninstall") {
|
|
|
11295
11671
|
launchdUninstall();
|
|
11296
11672
|
process.exit(0);
|
|
11297
11673
|
}
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11674
|
+
if (argUrl === "pair") {
|
|
11675
|
+
void (async () => {
|
|
11676
|
+
const home = (0, import_node_os7.homedir)();
|
|
11677
|
+
const cfgPath = (0, import_node_path7.join)(home, DEFAULT_CONFIG_DIR, "config.json");
|
|
11678
|
+
let cfg = {};
|
|
11679
|
+
try {
|
|
11680
|
+
cfg = JSON.parse((0, import_node_fs5.readFileSync)(cfgPath, "utf-8"));
|
|
11681
|
+
} catch {
|
|
11682
|
+
}
|
|
11683
|
+
const url = process.argv[4] ?? process.env.RELAY_URL ?? cfg.relayUrl;
|
|
11684
|
+
if (!url) {
|
|
11685
|
+
console.error("[pair] \u7F3A\u5C11 hub \u5730\u5740\uFF1A\u5148\u8FD0\u884C\u4E00\u6B21 cli-local \u5B8C\u6210\u914D\u7F6E\uFF0C\u6216 cli-local pair wss://your-hub/ws");
|
|
11686
|
+
process.exit(1);
|
|
11687
|
+
}
|
|
11688
|
+
if (!cfg.registrationKey && !process.env.REGISTRATION_KEY) {
|
|
11689
|
+
console.error("[pair] \u7F3A\u5C11 registrationKey\uFF1A\u5199\u5165 ~/.cli-remote/config.json \u6216\u8BBE\u7F6E REGISTRATION_KEY");
|
|
11690
|
+
process.exit(1);
|
|
11691
|
+
}
|
|
11692
|
+
const prev = await loadCredentials();
|
|
11693
|
+
const deviceId = prev?.deviceId || await stableDeviceId();
|
|
11694
|
+
console.log(`[pair] hub: ${url}\uFF08\u8BBE\u5907 ${deviceId}${prev ? "\uFF0C\u590D\u7528" : "\uFF0C\u7531\u673A\u5668\u7801\u6D3E\u751F"}\uFF09`);
|
|
11695
|
+
const creds = await deviceFlowAuthorize(
|
|
11696
|
+
baseUrlFromWsUrl(url),
|
|
11697
|
+
cfg.registrationKey ?? process.env.REGISTRATION_KEY,
|
|
11698
|
+
deviceId
|
|
11699
|
+
);
|
|
11700
|
+
console.log(`[pair] \u6388\u6743\u5B8C\u6210 deviceId=${creds.deviceId}`);
|
|
11701
|
+
const port = readConfiguredPort();
|
|
11702
|
+
const health = await apiGet(port, "/healthz");
|
|
11703
|
+
if (health?.ok) {
|
|
11704
|
+
console.log("[pair] gateway \u5728\u8FD0\u884C\uFF0C\u65B0\u51ED\u8BC1\u5DF2\u5199\u5165 \u2014 \u6570\u79D2\u5185\u81EA\u52A8\u91CD\u8FDE\uFF08\u65E0\u9700\u91CD\u542F\uFF09");
|
|
11705
|
+
} else {
|
|
11706
|
+
console.log("[pair] gateway \u672A\u8FD0\u884C\uFF1Acli-local gateway start \u542F\u52A8\u5E38\u9A7B\u670D\u52A1");
|
|
11707
|
+
}
|
|
11708
|
+
})().catch((e) => {
|
|
11709
|
+
console.error(`[pair] ${e.message}`);
|
|
11710
|
+
process.exit(1);
|
|
11711
|
+
});
|
|
11712
|
+
}
|
|
11713
|
+
if (argUrl === "gateway") {
|
|
11714
|
+
let gatewayLogFile = function() {
|
|
11715
|
+
return (0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "logs", "gateway.log");
|
|
11716
|
+
}, gatewayLogs = function() {
|
|
11717
|
+
const follow = rest.includes("-f") || rest.includes("--follow");
|
|
11718
|
+
const li = rest.findIndex((a) => a === "-n" || a === "--lines");
|
|
11719
|
+
const lines = li >= 0 ? Number(rest[li + 1]) || 50 : 50;
|
|
11720
|
+
const file = gatewayLogFile();
|
|
11721
|
+
if (!(0, import_node_fs5.existsSync)(file)) {
|
|
11722
|
+
console.log(`[gateway] \u6682\u65E0\u65E5\u5FD7\uFF08${file} \u4E0D\u5B58\u5728\uFF09`);
|
|
11723
|
+
process.exit(0);
|
|
11724
|
+
}
|
|
11725
|
+
const tail = (0, import_node_fs5.readFileSync)(file, "utf-8").trimEnd().split("\n").slice(-lines);
|
|
11726
|
+
console.log(tail.join("\n"));
|
|
11727
|
+
if (!follow) process.exit(0);
|
|
11728
|
+
let size = (0, import_node_fs5.statSync)(file).size;
|
|
11729
|
+
console.log("--- following (Ctrl+C \u9000\u51FA) ---");
|
|
11730
|
+
setInterval(() => {
|
|
11731
|
+
try {
|
|
11732
|
+
const st = (0, import_node_fs5.statSync)(file);
|
|
11733
|
+
if (st.size > size) {
|
|
11734
|
+
const fd = (0, import_node_fs5.openSync)(file, "r");
|
|
11735
|
+
const buf = Buffer.alloc(st.size - size);
|
|
11736
|
+
(0, import_node_fs5.readSync)(fd, buf, 0, buf.length, size);
|
|
11737
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
11738
|
+
size = st.size;
|
|
11739
|
+
process.stdout.write(buf.toString("utf-8"));
|
|
11740
|
+
} else if (st.size < size) {
|
|
11741
|
+
size = 0;
|
|
11742
|
+
}
|
|
11743
|
+
} catch {
|
|
11744
|
+
}
|
|
11745
|
+
}, 1e3);
|
|
11746
|
+
};
|
|
11747
|
+
gatewayLogFile2 = gatewayLogFile, gatewayLogs2 = gatewayLogs;
|
|
11748
|
+
const action = process.argv[3] ?? "";
|
|
11749
|
+
const rest = process.argv.slice(4);
|
|
11750
|
+
const hubArg = (() => {
|
|
11751
|
+
const i = rest.indexOf("--hub");
|
|
11752
|
+
return i >= 0 ? rest[i + 1] : void 0;
|
|
11753
|
+
})();
|
|
11754
|
+
async function gatewayStart() {
|
|
11755
|
+
if (gatewayAlive()) {
|
|
11756
|
+
console.log(`[gateway] already running (pid ${gatewayPid()})`);
|
|
11757
|
+
process.exit(0);
|
|
11758
|
+
}
|
|
11759
|
+
const home = (0, import_node_os7.homedir)();
|
|
11760
|
+
const entry = entryScript();
|
|
11761
|
+
const cfgDir = (0, import_node_path7.join)(home, DEFAULT_CONFIG_DIR);
|
|
11762
|
+
const cfgPath = (0, import_node_path7.join)(cfgDir, "config.json");
|
|
11763
|
+
const hasCfg = (0, import_node_fs5.existsSync)(cfgPath);
|
|
11764
|
+
if (!entry) {
|
|
11765
|
+
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");
|
|
11766
|
+
process.exit(1);
|
|
11767
|
+
}
|
|
11768
|
+
if (!hasCfg) {
|
|
11769
|
+
console.error("[gateway] \u5C1A\u672A\u914D\u7F6E hub \u5730\u5740\u3002\u8BF7\u5148\u4EA4\u4E92\u5F0F\u8FD0\u884C\u4E00\u6B21 cli-local \u5B8C\u6210\u521D\u59CB\u914D\u7F6E\u3002");
|
|
11770
|
+
console.error("[gateway] \uFF08\u65E0\u51ED\u8BC1\u4E5F\u53EF\u4EE5\u542F\u52A8\uFF1Agateway \u4F1A\u8FDB\u5165 needs-pairing\uFF0C\u7B49 cli-local pair\uFF09");
|
|
11771
|
+
process.exit(1);
|
|
11772
|
+
}
|
|
11773
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path7.dirname)(gatewayLogFile()), { recursive: true });
|
|
11774
|
+
const out = (0, import_node_fs5.openSync)(gatewayLogFile(), "a");
|
|
11775
|
+
const env = {};
|
|
11776
|
+
for (const [k, v] of Object.entries(process.env)) if (v !== void 0) env[k] = v;
|
|
11777
|
+
const args = [entry, "gateway", "run"];
|
|
11778
|
+
if (hubArg) args.push("--hub", hubArg);
|
|
11779
|
+
const child = (0, import_node_child_process3.spawn)(process.execPath, args, {
|
|
11780
|
+
detached: true,
|
|
11781
|
+
stdio: ["ignore", out, out],
|
|
11782
|
+
env
|
|
11783
|
+
});
|
|
11784
|
+
child.unref();
|
|
11785
|
+
console.log(`[gateway] started (pid ${child.pid})`);
|
|
11786
|
+
const port = readConfiguredPort();
|
|
11787
|
+
for (let i = 0; i < 20; i++) {
|
|
11788
|
+
await new Promise((res) => setTimeout(res, 250));
|
|
11789
|
+
const h = await apiGet(port, "/healthz", 400);
|
|
11790
|
+
if (h?.ok) {
|
|
11791
|
+
console.log(`[gateway] \u63A7\u5236API\u5C31\u7EEA: http://127.0.0.1:${port}\uFF08\u4EC5\u672C\u673A\uFF09`);
|
|
11792
|
+
break;
|
|
11793
|
+
}
|
|
11794
|
+
}
|
|
11795
|
+
console.log(`[gateway] \u65E5\u5FD7: ${gatewayLogFile()}`);
|
|
11796
|
+
console.log("[gateway] \u72B6\u6001: cli-local gateway status");
|
|
11797
|
+
}
|
|
11798
|
+
async function stopWorker() {
|
|
11799
|
+
const port = readConfiguredPort();
|
|
11800
|
+
if (await apiPost(port, "/stop", 1500)) {
|
|
11801
|
+
const t02 = Date.now();
|
|
11802
|
+
while (Date.now() - t02 < 5e3 && gatewayAlive()) {
|
|
11803
|
+
await new Promise((res) => setTimeout(res, 150));
|
|
11804
|
+
}
|
|
11805
|
+
(0, import_node_fs5.rmSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11806
|
+
return;
|
|
11807
|
+
}
|
|
11808
|
+
const pid = gatewayPid();
|
|
11809
|
+
if (pid === null || !gatewayAlive()) {
|
|
11810
|
+
(0, import_node_fs5.rmSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11811
|
+
return;
|
|
11812
|
+
}
|
|
11813
|
+
try {
|
|
11814
|
+
process.kill(pid, "SIGTERM");
|
|
11815
|
+
} catch {
|
|
11816
|
+
}
|
|
11817
|
+
const t0 = Date.now();
|
|
11818
|
+
while (Date.now() - t0 < 5e3 && gatewayAlive()) {
|
|
11819
|
+
await new Promise((res) => setTimeout(res, 150));
|
|
11820
|
+
}
|
|
11821
|
+
(0, import_node_fs5.rmSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
|
|
11822
|
+
}
|
|
11823
|
+
async function gatewayStop() {
|
|
11824
|
+
const pid = gatewayPid();
|
|
11825
|
+
const wasRunning = gatewayAlive();
|
|
11826
|
+
await stopWorker();
|
|
11827
|
+
console.log(wasRunning ? `[gateway] stopped (pid ${pid ?? "?"})` : "[gateway] not running");
|
|
11828
|
+
process.exit(0);
|
|
11829
|
+
}
|
|
11830
|
+
async function gatewayStatus() {
|
|
11831
|
+
const port = readConfiguredPort();
|
|
11832
|
+
const st = await apiGet(port, "/status", 600);
|
|
11833
|
+
if (st) {
|
|
11834
|
+
const phaseLabel = {
|
|
11835
|
+
starting: "\u542F\u52A8\u4E2D",
|
|
11836
|
+
connecting: "\u8FDE\u63A5\u4E2D",
|
|
11837
|
+
connected: "\u5DF2\u8FDE\u63A5",
|
|
11838
|
+
reconnecting: "\u91CD\u8FDE\u4E2D",
|
|
11839
|
+
"needs-pairing": "\u9700\u8981\u91CD\u65B0\u6388\u6743"
|
|
11840
|
+
};
|
|
11841
|
+
const up = Math.floor(st.uptimeMs / 1e3);
|
|
11842
|
+
const upStr = up > 3600 ? `${Math.floor(up / 3600)}h${Math.floor(up % 3600 / 60)}m` : up > 60 ? `${Math.floor(up / 60)}m${up % 60}s` : `${up}s`;
|
|
11843
|
+
const exp = st.accessTokenExpiresAt ? new Date(st.accessTokenExpiresAt).toLocaleTimeString() : "\u2014";
|
|
11844
|
+
console.log(`[gateway] running (pid ${gatewayPid() ?? "?"}, api :${port})`);
|
|
11845
|
+
console.log(`[gateway] \u72B6\u6001: ${phaseLabel[st.phase] ?? st.phase}${st.reason ? ` (${st.reason})` : ""}`);
|
|
11846
|
+
console.log(`[gateway] hub: ${st.hubUrl}`);
|
|
11847
|
+
console.log(`[gateway] deviceId: ${st.deviceId ?? "\u2014"}`);
|
|
11848
|
+
console.log(`[gateway] uptime: ${upStr}, token \u5230\u671F: ${exp}`);
|
|
11849
|
+
console.log(`[gateway] PTY \u4F1A\u8BDD: ${st.sessions.length} \u4E2A${st.sessions.length ? ` (${st.sessions.map((s) => s.cmd).join(", ")})` : ""}`);
|
|
11850
|
+
if (st.phase === "needs-pairing") {
|
|
11851
|
+
console.log("[gateway] \u26A0 \u8FD0\u884C cli-local pair \u91CD\u65B0\u6388\u6743\uFF08\u5B8C\u6210\u540E\u81EA\u52A8\u91CD\u8FDE\uFF09");
|
|
11852
|
+
}
|
|
11853
|
+
process.exit(0);
|
|
11854
|
+
}
|
|
11855
|
+
if (!gatewayAlive()) {
|
|
11856
|
+
console.log("[gateway] stopped");
|
|
11857
|
+
process.exit(1);
|
|
11858
|
+
}
|
|
11859
|
+
const pid = gatewayPid();
|
|
11860
|
+
let hub = "(\u672A\u914D\u7F6E)";
|
|
11861
|
+
try {
|
|
11862
|
+
const cfg = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path7.join)((0, import_node_os7.homedir)(), DEFAULT_CONFIG_DIR, "config.json"), "utf-8"));
|
|
11863
|
+
hub = cfg.relayUrl ?? hub;
|
|
11864
|
+
} catch {
|
|
11865
|
+
}
|
|
11866
|
+
console.log(`[gateway] running (pid ${pid}) \u2014 \u63A7\u5236API\u672A\u54CD\u5E94\uFF08\u65E7\u7248\u672C\uFF1F\uFF09`);
|
|
11867
|
+
console.log(`[gateway] hub: ${hub}`);
|
|
11868
|
+
console.log(`[gateway] \u65E5\u5FD7: ${gatewayLogFile()}`);
|
|
11869
|
+
process.exit(0);
|
|
11870
|
+
}
|
|
11871
|
+
void (async () => {
|
|
11872
|
+
switch (action) {
|
|
11873
|
+
case "start":
|
|
11874
|
+
gatewayStart().then(() => process.exit(0)).catch((e) => {
|
|
11875
|
+
console.error(`[gateway] ${e.message}`);
|
|
11876
|
+
process.exit(1);
|
|
11877
|
+
});
|
|
11878
|
+
break;
|
|
11879
|
+
case "stop":
|
|
11880
|
+
gatewayStop();
|
|
11881
|
+
break;
|
|
11882
|
+
case "restart":
|
|
11883
|
+
await stopWorker();
|
|
11884
|
+
gatewayStart().then(() => process.exit(0)).catch((e) => {
|
|
11885
|
+
console.error(`[gateway] ${e.message}`);
|
|
11886
|
+
process.exit(1);
|
|
11887
|
+
});
|
|
11888
|
+
break;
|
|
11889
|
+
case "status":
|
|
11890
|
+
void gatewayStatus();
|
|
11891
|
+
break;
|
|
11892
|
+
case "logs":
|
|
11893
|
+
gatewayLogs();
|
|
11894
|
+
break;
|
|
11895
|
+
case "run":
|
|
11896
|
+
startLocal({ url: hubArg, daemon: true }).catch((err) => {
|
|
11897
|
+
if (err instanceof AuthError) console.error(`[local] ${err.message}`);
|
|
11898
|
+
else console.error("[local] fatal:", err);
|
|
11899
|
+
process.exit(1);
|
|
11900
|
+
});
|
|
11901
|
+
break;
|
|
11902
|
+
default:
|
|
11903
|
+
console.log(`cli-local gateway \u2014 \u540E\u53F0\u8FD0\u884C\u7BA1\u7406
|
|
11904
|
+
|
|
11905
|
+
usage:
|
|
11906
|
+
cli-local gateway start [--hub URL] \u540E\u53F0\u542F\u52A8\uFF08\u81EA\u52A8\u91CD\u8FDE\uFF09
|
|
11907
|
+
cli-local gateway stop \u505C\u6B62
|
|
11908
|
+
cli-local gateway restart \u91CD\u542F
|
|
11909
|
+
cli-local gateway status \u8FD0\u884C\u72B6\u6001
|
|
11910
|
+
cli-local gateway logs [-f] [-n N] \u67E5\u770B\u65E5\u5FD7\uFF08-f \u8DDF\u968F\uFF09
|
|
11911
|
+
cli-local gateway run [--hub URL] \u524D\u53F0\u8FD0\u884C\uFF08\u4F9B start \u62C9\u8D77\uFF09
|
|
11912
|
+
|
|
11913
|
+
\u8BF4\u660E\uFF1A\u540E\u53F0\u8FDB\u7A0B\u7531 pid \u6587\u4EF6\u7BA1\u7406\uFF08~/.cli-remote/gateway.pid\uFF09\uFF0C\u672C\u5730\u63A7\u5236API
|
|
11914
|
+
\u4EC5 127.0.0.1\uFF08\u9ED8\u8BA4 18790\uFF0Cconfig.json gateway.apiPort \u53EF\u6539\uFF09\uFF0C\u65E5\u5FD7\u5728
|
|
11915
|
+
~/.cli-remote/logs/gateway.log\u3002\u65E0\u51ED\u8BC1\u4E5F\u80FD\u542F\u52A8\uFF08\u8FDB\u5165 needs-pairing \u7B49
|
|
11916
|
+
cli-local pair\uFF09\u3002macOS \u60F3\u5F00\u673A\u81EA\u542F\u7528 launchd\uFF1Acli-local install\u3002`);
|
|
11917
|
+
process.exit(action ? 1 : 0);
|
|
11918
|
+
}
|
|
11919
|
+
})().catch((e) => {
|
|
11920
|
+
console.error(`[gateway] ${e.message}`);
|
|
11921
|
+
process.exit(1);
|
|
11922
|
+
});
|
|
11923
|
+
}
|
|
11924
|
+
var gatewayLogFile2;
|
|
11925
|
+
var gatewayLogs2;
|
|
11926
|
+
if (!argUrl || argMatch) {
|
|
11927
|
+
if (!argUrl) {
|
|
11928
|
+
void (async () => {
|
|
11929
|
+
const port = readConfiguredPort();
|
|
11930
|
+
const health = await apiGet(port, "/healthz", 400);
|
|
11931
|
+
if (health?.ok) {
|
|
11932
|
+
console.log("[local] gateway \u5E38\u9A7B\u670D\u52A1\u8FD0\u884C\u4E2D\u3002\u5B9E\u65F6\u72B6\u6001:");
|
|
11933
|
+
const entry = process.argv[1];
|
|
11934
|
+
if (entry && !entry.endsWith(".ts")) {
|
|
11935
|
+
const { execFileSync: ex } = await import("node:child_process");
|
|
11936
|
+
try {
|
|
11937
|
+
ex(process.execPath, [entry, "gateway", "status"], { stdio: "inherit" });
|
|
11938
|
+
} catch {
|
|
11939
|
+
console.log(`[local] \u63A7\u5236API: http://127.0.0.1:${port}/status`);
|
|
11940
|
+
}
|
|
11941
|
+
} else {
|
|
11942
|
+
console.log(`[local] \u63A7\u5236API: http://127.0.0.1:${port}/status`);
|
|
11943
|
+
}
|
|
11944
|
+
process.exit(0);
|
|
11945
|
+
}
|
|
11946
|
+
await startLocal({});
|
|
11947
|
+
})().catch((err) => {
|
|
11948
|
+
if (err instanceof AuthError) console.error(`[local] ${err.message}`);
|
|
11949
|
+
else console.error("[local] fatal:", err);
|
|
11950
|
+
process.exit(1);
|
|
11951
|
+
});
|
|
11301
11952
|
} else {
|
|
11302
|
-
|
|
11953
|
+
startLocal({ url: argUrl }).catch((err) => {
|
|
11954
|
+
if (err instanceof AuthError) {
|
|
11955
|
+
console.error(`[local] ${err.message}`);
|
|
11956
|
+
} else {
|
|
11957
|
+
console.error("[local] fatal:", err);
|
|
11958
|
+
}
|
|
11959
|
+
process.exit(1);
|
|
11960
|
+
});
|
|
11303
11961
|
}
|
|
11304
|
-
|
|
11305
|
-
});
|
|
11962
|
+
}
|
|
11306
11963
|
process.on("SIGINT", () => process.exit(0));
|
|
11307
11964
|
process.on("SIGTERM", () => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cli-remote/local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
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": {
|