@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.
Files changed (2) hide show
  1. package/dist/index.cjs +311 -52
  2. 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\u8BF7\u5728 ~/.cli-remote/config.json \u8BBE\u7F6E\uFF0C\u6216\u7528\u73AF\u5883\u53D8\u91CF REGISTRATION_KEY"
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
- const url = opts.url ?? process.env.RELAY_URL ?? config.relayUrl ?? "ws://127.0.0.1:8080/ws";
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
- const ws = new wrapper_default(url);
10610
+ let ws = null;
10525
10611
  let heartbeatTimer = null;
10526
- function cleanup(code = 0) {
10527
- pty.killAll();
10528
- for (const [, logger] of loggers) logger.logExit(-1);
10529
- loggers.clear();
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
- try {
10533
- ws.close();
10534
- } catch {
10535
- }
10536
- process.exit(code);
10617
+ heartbeatTimer = null;
10537
10618
  }
10538
- ws.on("open", () => {
10539
- console.log("[local] connected");
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
- heartbeatTimer = setInterval(() => {
10549
- if (ws.readyState === wrapper_default.OPEN) {
10550
- ws.send(encodeFrame({ t: "ping" }));
10551
- }
10552
- }, HEARTBEAT_INTERVAL_MS);
10553
- });
10554
- ws.on("message", (raw) => {
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
- ws.send(
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
- cleanup(1);
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\u4F1A\u8D70 OAuth Device Flow\uFF08RFC 8628\uFF09\uFF1A
10823
- 1. \u5728 ~/.cli-remote/config.json \u914D\u7F6E registrationKey\uFF08\u4ECE web\u300C\u6211\u7684\u8BBE\u5907\u300D\u9875\u590D\u5236\uFF09
10824
- 2. \u8FD0\u884C cli-local \u2192 \u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668 \u2192 \u767B\u5F55\u5E76\u6279\u51C6\u672C\u8BBE\u5907
10825
- 3. \u51ED\u8BC1\u5B58\u5165 ~/.cli-remote/credentials.json\uFF080600\uFF09\uFF0C\u4E4B\u540E\u81EA\u52A8\u7EED\u671F
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
- console.error("[local] fatal:", err);
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.1",
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": {