@cli-remote/local 0.0.3 → 0.0.6

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 +543 -74
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3214,7 +3214,7 @@ var require_stream = __commonJS({
3214
3214
  };
3215
3215
  duplex._final = function(callback) {
3216
3216
  if (ws.readyState === ws.CONNECTING) {
3217
- ws.once("open", function open() {
3217
+ ws.once("open", function open2() {
3218
3218
  duplex._final(callback);
3219
3219
  });
3220
3220
  return;
@@ -3235,7 +3235,7 @@ var require_stream = __commonJS({
3235
3235
  };
3236
3236
  duplex._write = function(chunk, encoding, callback) {
3237
3237
  if (ws.readyState === ws.CONNECTING) {
3238
- ws.once("open", function open() {
3238
+ ws.once("open", function open2() {
3239
3239
  duplex._write(chunk, encoding, callback);
3240
3240
  });
3241
3241
  return;
@@ -6683,11 +6683,11 @@ var require_dijkstra = __commonJS({
6683
6683
  var predecessors = {};
6684
6684
  var costs = {};
6685
6685
  costs[s] = 0;
6686
- var open = dijkstra.PriorityQueue.make();
6687
- open.push(s, 0);
6686
+ var open2 = dijkstra.PriorityQueue.make();
6687
+ open2.push(s, 0);
6688
6688
  var closest, u, v, cost_of_s_to_u, adjacent_nodes, cost_of_e, cost_of_s_to_u_plus_cost_of_e, cost_of_s_to_v, first_visit;
6689
- while (!open.empty()) {
6690
- closest = open.pop();
6689
+ while (!open2.empty()) {
6690
+ closest = open2.pop();
6691
6691
  u = closest.value;
6692
6692
  cost_of_s_to_u = closest.cost;
6693
6693
  adjacent_nodes = graph[u] || {};
@@ -6699,7 +6699,7 @@ var require_dijkstra = __commonJS({
6699
6699
  first_visit = typeof costs[v] === "undefined";
6700
6700
  if (first_visit || cost_of_s_to_v > cost_of_s_to_u_plus_cost_of_e) {
6701
6701
  costs[v] = cost_of_s_to_u_plus_cost_of_e;
6702
- open.push(v, cost_of_s_to_u_plus_cost_of_e);
6702
+ open2.push(v, cost_of_s_to_u_plus_cost_of_e);
6703
6703
  predecessors[v] = u;
6704
6704
  }
6705
6705
  }
@@ -9786,10 +9786,10 @@ var require_browser = __commonJS({
9786
9786
  text = canvas;
9787
9787
  canvas = void 0;
9788
9788
  }
9789
- return new Promise(function(resolve, reject) {
9789
+ return new Promise(function(resolve2, reject) {
9790
9790
  try {
9791
9791
  const data = QRCode2.create(text, opts);
9792
- resolve(renderFunc(data, canvas, opts));
9792
+ resolve2(renderFunc(data, canvas, opts));
9793
9793
  } catch (e) {
9794
9794
  reject(e);
9795
9795
  }
@@ -9870,11 +9870,11 @@ var require_server = __commonJS({
9870
9870
  }
9871
9871
  function render(renderFunc, text, params) {
9872
9872
  if (!params.cb) {
9873
- return new Promise(function(resolve, reject) {
9873
+ return new Promise(function(resolve2, reject) {
9874
9874
  try {
9875
9875
  const data = QRCode2.create(text, params.opts);
9876
9876
  return renderFunc(data, params.opts, function(err, data2) {
9877
- return err ? reject(err) : resolve(data2);
9877
+ return err ? reject(err) : resolve2(data2);
9878
9878
  });
9879
9879
  } catch (e) {
9880
9880
  reject(e);
@@ -9939,10 +9939,10 @@ var require_lib = __commonJS({
9939
9939
  });
9940
9940
 
9941
9941
  // src/index.ts
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");
9945
- var import_node_path5 = require("node:path");
9942
+ var import_node_fs4 = require("node:fs");
9943
+ var import_node_child_process3 = require("node:child_process");
9944
+ var import_node_os6 = require("node:os");
9945
+ var import_node_path6 = 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 import_promises2 = require("node:fs/promises");
9962
- var import_node_os4 = require("node:os");
9963
- var import_node_path4 = require("node:path");
9962
+ var import_node_fs3 = require("node:fs");
9963
+ var import_node_os5 = require("node:os");
9964
+ var import_node_path5 = require("node:path");
9964
9965
 
9965
9966
  // ../shared/src/codec.ts
9966
9967
  var import_msgpack = __toESM(require_dist(), 1);
@@ -9980,6 +9981,9 @@ var ACCESS_TOKEN_TTL_MS = 60 * 6e4;
9980
9981
  var REFRESH_TOKEN_TTL_MS = 30 * 24 * 60 * 6e4;
9981
9982
  var REFRESH_MARGIN_MS = 5 * 6e4;
9982
9983
  var OUTPUT_BUFFER_SIZE = 500;
9984
+ var FILE_CHUNK_SIZE = 64 * 1024;
9985
+ var MAX_TRANSFER_BYTES = 500 * 1024 * 1024;
9986
+ var UPLOAD_DIR = "uploads";
9983
9987
  var RECONNECT_DELAYS_MS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
9984
9988
  var DEFAULT_CONFIG_DIR = ".cli-remote";
9985
9989
  var CREDENTIALS_FILE = "credentials.json";
@@ -10074,6 +10078,8 @@ function resolveCommand(cmd) {
10074
10078
  return { file: "codex", args: [] };
10075
10079
  case "kimi-cli":
10076
10080
  return { file: "kimi", args: [] };
10081
+ case "pi":
10082
+ return { file: "pi", args: [] };
10077
10083
  default:
10078
10084
  return { file: cmd, args: [] };
10079
10085
  }
@@ -10327,19 +10333,196 @@ function checkCommand(line, whitelist, dangerousPatterns = DANGEROUS_PATTERNS) {
10327
10333
  return { allowed: true };
10328
10334
  }
10329
10335
 
10330
- // src/auth.ts
10331
- var import_node_child_process = require("node:child_process");
10332
- var import_node_crypto = require("node:crypto");
10336
+ // src/file-transfer.ts
10337
+ var import_node_fs2 = require("node:fs");
10333
10338
  var import_promises = require("node:fs/promises");
10334
10339
  var import_node_os3 = require("node:os");
10335
10340
  var import_node_path3 = require("node:path");
10341
+ var FileTransferManager = class _FileTransferManager {
10342
+ constructor(send, log) {
10343
+ this.send = send;
10344
+ this.log = log;
10345
+ }
10346
+ uploads = /* @__PURE__ */ new Map();
10347
+ uploadDir() {
10348
+ return (0, import_node_path3.join)((0, import_node_os3.homedir)(), DEFAULT_CONFIG_DIR, UPLOAD_DIR);
10349
+ }
10350
+ expandPath(p) {
10351
+ const home = process.env.PTY_HOME ?? (0, import_node_os3.homedir)();
10352
+ if (p === "~") return home;
10353
+ if (p.startsWith("~/")) return (0, import_node_path3.join)(home, p.slice(2));
10354
+ return (0, import_node_path3.resolve)(p);
10355
+ }
10356
+ /** Strip any directory components and control chars from a client filename. */
10357
+ static sanitizeFilename(name) {
10358
+ const base = (0, import_node_path3.basename)(name).replace(/[\u0000-\u001f\u007f]/g, "");
10359
+ return base && base !== "." && base !== ".." ? base : "upload.bin";
10360
+ }
10361
+ async handleUploadStart(frame) {
10362
+ const { sessionId, uploadId, filename, size } = frame;
10363
+ if (this.uploads.has(uploadId)) return;
10364
+ if (size > MAX_TRANSFER_BYTES) {
10365
+ this.emit({
10366
+ t: "upload-err",
10367
+ sessionId,
10368
+ uploadId,
10369
+ reason: `\u6587\u4EF6\u8FC7\u5927\uFF08>${Math.floor(MAX_TRANSFER_BYTES / 1024 / 1024)}MB\uFF09`
10370
+ });
10371
+ return;
10372
+ }
10373
+ try {
10374
+ const dir = this.uploadDir();
10375
+ await (0, import_promises.mkdir)(dir, { recursive: true });
10376
+ const target = await this.uniqPath(dir, _FileTransferManager.sanitizeFilename(filename));
10377
+ const stream = (0, import_node_fs2.createWriteStream)(target, { mode: 384 });
10378
+ this.uploads.set(uploadId, {
10379
+ uploadId,
10380
+ sessionId,
10381
+ path: target,
10382
+ size,
10383
+ received: 0,
10384
+ lastSeq: 0,
10385
+ stream
10386
+ });
10387
+ this.emit({ t: "upload-ok", sessionId, uploadId, path: target });
10388
+ this.log(`upload start ${uploadId} \u2192 ${target} (${size}B)`);
10389
+ } catch (e) {
10390
+ this.emit({ t: "upload-err", sessionId, uploadId, reason: e.message });
10391
+ }
10392
+ }
10393
+ async uniqPath(dir, name) {
10394
+ let candidate = (0, import_node_path3.join)(dir, name);
10395
+ let i = 1;
10396
+ while (await exists(candidate)) {
10397
+ const dot = name.lastIndexOf(".");
10398
+ const stem = dot > 0 ? name.slice(0, dot) : name;
10399
+ const ext = dot > 0 ? name.slice(dot) : "";
10400
+ candidate = (0, import_node_path3.join)(dir, `${stem} (${i++})${ext}`);
10401
+ }
10402
+ return candidate;
10403
+ }
10404
+ handleUploadChunk(frame) {
10405
+ const up = this.uploads.get(frame.uploadId);
10406
+ if (!up) return;
10407
+ if (frame.seq <= up.lastSeq) return;
10408
+ up.lastSeq = frame.seq;
10409
+ up.received += frame.data.byteLength;
10410
+ up.stream.write(Buffer.from(frame.data.buffer, frame.data.byteOffset, frame.data.byteLength));
10411
+ }
10412
+ async handleUploadEnd(frame) {
10413
+ const up = this.uploads.get(frame.uploadId);
10414
+ if (!up) return;
10415
+ this.uploads.delete(frame.uploadId);
10416
+ await new Promise((res) => up.stream.end(res));
10417
+ const sizeOk = up.received === up.size;
10418
+ this.log(`upload done ${frame.uploadId}: ${up.received}B \u2192 ${up.path}`);
10419
+ if (!sizeOk) {
10420
+ this.emit({
10421
+ t: "upload-err",
10422
+ sessionId: up.sessionId,
10423
+ uploadId: frame.uploadId,
10424
+ reason: `\u5927\u5C0F\u4E0D\u5339\u914D\uFF08\u58F0\u660E ${up.size}B\uFF0C\u5B9E\u6536 ${up.received}B\uFF09`
10425
+ });
10426
+ }
10427
+ }
10428
+ abortUpload(uploadId) {
10429
+ const up = this.uploads.get(uploadId);
10430
+ if (!up) return;
10431
+ this.uploads.delete(uploadId);
10432
+ up.stream.destroy();
10433
+ this.log(`upload aborted ${uploadId}`);
10434
+ }
10435
+ /** Stream a file back to the web client with mild backpressure via `send`. */
10436
+ async handleDownloadReq(frame, isBusy) {
10437
+ const sessionId = frame.sessionId;
10438
+ const downloadId = "dl_" + Math.random().toString(36).slice(2, 10);
10439
+ let abs;
10440
+ try {
10441
+ abs = this.expandPath(frame.path);
10442
+ const st = await (0, import_promises.stat)(abs);
10443
+ if (!st.isFile()) throw new Error("\u4E0D\u662F\u666E\u901A\u6587\u4EF6");
10444
+ if (st.size > MAX_TRANSFER_BYTES) {
10445
+ throw new Error(`\u6587\u4EF6\u8FC7\u5927\uFF08>${Math.floor(MAX_TRANSFER_BYTES / 1024 / 1024)}MB\uFF09`);
10446
+ }
10447
+ this.emit({
10448
+ t: "download-start",
10449
+ sessionId,
10450
+ downloadId,
10451
+ filename: (0, import_node_path3.basename)(abs),
10452
+ size: st.size
10453
+ });
10454
+ this.log(`download start ${downloadId} \u2190 ${abs} (${st.size}B)`);
10455
+ await this.streamFile(sessionId, downloadId, abs, isBusy);
10456
+ this.emit({ t: "download-end", sessionId, downloadId });
10457
+ this.log(`download done ${downloadId}: ${abs}`);
10458
+ } catch (e) {
10459
+ this.emit({
10460
+ t: "download-err",
10461
+ sessionId,
10462
+ downloadId,
10463
+ reason: e.message
10464
+ });
10465
+ }
10466
+ }
10467
+ async streamFile(sessionId, downloadId, abs, isBusy) {
10468
+ const handle = await (0, import_promises.open)(abs, "r");
10469
+ try {
10470
+ let seq = 0;
10471
+ let pos = 0;
10472
+ for (; ; ) {
10473
+ const buf = Buffer.alloc(FILE_CHUNK_SIZE);
10474
+ const { bytesRead } = await handle.read(buf, 0, FILE_CHUNK_SIZE, pos);
10475
+ if (bytesRead === 0) break;
10476
+ pos += bytesRead;
10477
+ seq++;
10478
+ this.emit({
10479
+ t: "download-chunk",
10480
+ sessionId,
10481
+ downloadId,
10482
+ data: new Uint8Array(buf.buffer, buf.byteOffset, bytesRead),
10483
+ seq
10484
+ });
10485
+ if (isBusy()) {
10486
+ await new Promise((res) => setImmediate(res));
10487
+ let spins = 0;
10488
+ while (isBusy() && spins++ < 2e3) {
10489
+ await new Promise((res) => setTimeout(res, 20));
10490
+ }
10491
+ }
10492
+ }
10493
+ } finally {
10494
+ await handle.close();
10495
+ }
10496
+ }
10497
+ abortAll() {
10498
+ for (const id of [...this.uploads.keys()]) this.abortUpload(id);
10499
+ }
10500
+ emit(frame) {
10501
+ this.send(encodeFrame(frame));
10502
+ }
10503
+ };
10504
+ async function exists(p) {
10505
+ try {
10506
+ await (0, import_promises.stat)(p);
10507
+ return true;
10508
+ } catch {
10509
+ return false;
10510
+ }
10511
+ }
10512
+
10513
+ // src/auth.ts
10514
+ var import_node_child_process = require("node:child_process");
10515
+ var import_node_crypto = require("node:crypto");
10516
+ var import_promises2 = require("node:fs/promises");
10517
+ var import_node_os4 = require("node:os");
10518
+ var import_node_path4 = require("node:path");
10336
10519
  var import_qrcode = __toESM(require_lib(), 1);
10337
10520
  function credentialsPath() {
10338
- return (0, import_node_path3.join)((0, import_node_os3.homedir)(), DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
10521
+ return (0, import_node_path4.join)((0, import_node_os4.homedir)(), DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
10339
10522
  }
10340
10523
  async function loadCredentials() {
10341
10524
  try {
10342
- const raw = JSON.parse(await (0, import_promises.readFile)(credentialsPath(), "utf-8"));
10525
+ const raw = JSON.parse(await (0, import_promises2.readFile)(credentialsPath(), "utf-8"));
10343
10526
  if (!raw.deviceId || !raw.accessToken || !raw.refreshToken) return null;
10344
10527
  return raw;
10345
10528
  } catch {
@@ -10348,16 +10531,16 @@ async function loadCredentials() {
10348
10531
  }
10349
10532
  async function saveCredentials(creds) {
10350
10533
  const path = credentialsPath();
10351
- await (0, import_promises.mkdir)((0, import_node_path3.join)(path, ".."), { recursive: true });
10352
- await (0, import_promises.writeFile)(path, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
10534
+ await (0, import_promises2.mkdir)((0, import_node_path4.join)(path, ".."), { recursive: true });
10535
+ await (0, import_promises2.writeFile)(path, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
10353
10536
  try {
10354
- await (0, import_promises.chmod)(path, 384);
10537
+ await (0, import_promises2.chmod)(path, 384);
10355
10538
  } catch {
10356
10539
  }
10357
10540
  }
10358
10541
  async function clearCredentials() {
10359
10542
  try {
10360
- await (0, import_promises.writeFile)(credentialsPath(), "");
10543
+ await (0, import_promises2.writeFile)(credentialsPath(), "");
10361
10544
  } catch {
10362
10545
  }
10363
10546
  }
@@ -10388,18 +10571,34 @@ function openBrowser(url) {
10388
10571
  } catch {
10389
10572
  }
10390
10573
  }
10574
+ async function loginAndFetchRegistrationKey(baseUrl, email, password) {
10575
+ const login = await postJson(`${baseUrl}/api/login`, { email, password });
10576
+ if (login.status !== 200) {
10577
+ const reason = login.data?.reason;
10578
+ throw new AuthError(`\u767B\u5F55\u5931\u8D25: ${reason ?? login.status}`);
10579
+ }
10580
+ const token = login.data.token;
10581
+ if (!token) throw new AuthError("\u670D\u52A1\u7AEF\u672A\u8FD4\u56DE\u4F1A\u8BDD token");
10582
+ const me = await fetch(`${baseUrl}/api/me`, {
10583
+ headers: { authorization: `Bearer ${token}` }
10584
+ });
10585
+ if (!me.ok) throw new AuthError(`\u83B7\u53D6\u8D26\u53F7\u4FE1\u606F\u5931\u8D25 (${me.status})`);
10586
+ const data = await me.json();
10587
+ if (!data.registrationKey) throw new AuthError("\u8D26\u53F7\u4FE1\u606F\u91CC\u6CA1\u6709 registrationKey");
10588
+ return { registrationKey: data.registrationKey, user: data.user?.email ?? email };
10589
+ }
10391
10590
  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);
10591
+ const dir = (0, import_node_path4.join)((0, import_node_os4.homedir)(), DEFAULT_CONFIG_DIR);
10592
+ const path = (0, import_node_path4.join)(dir, CONFIG_FILE);
10394
10593
  let existing = {};
10395
10594
  try {
10396
- existing = JSON.parse(await (0, import_promises.readFile)(path, "utf-8"));
10595
+ existing = JSON.parse(await (0, import_promises2.readFile)(path, "utf-8"));
10397
10596
  } catch {
10398
10597
  }
10399
- await (0, import_promises.mkdir)(dir, { recursive: true });
10400
- await (0, import_promises.writeFile)(path, JSON.stringify({ ...existing, ...patch }, null, 2) + "\n");
10598
+ await (0, import_promises2.mkdir)(dir, { recursive: true });
10599
+ await (0, import_promises2.writeFile)(path, JSON.stringify({ ...existing, ...patch }, null, 2) + "\n");
10401
10600
  }
10402
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
10601
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
10403
10602
  var AuthError = class extends Error {
10404
10603
  };
10405
10604
  async function deviceFlowAuthorize(baseUrl, registrationKey, deviceId) {
@@ -10411,7 +10610,7 @@ async function deviceFlowAuthorize(baseUrl, registrationKey, deviceId) {
10411
10610
  const res = await postJson(`${baseUrl}/oauth/device_authorization`, {
10412
10611
  registrationKey,
10413
10612
  device_id: deviceId,
10414
- device_name: (0, import_node_os3.hostname)(),
10613
+ device_name: (0, import_node_os4.hostname)(),
10415
10614
  platform: process.platform
10416
10615
  });
10417
10616
  if (res.status !== 200) {
@@ -10514,9 +10713,9 @@ async function ensureCredentials(baseUrl, registrationKey) {
10514
10713
 
10515
10714
  // src/client.ts
10516
10715
  async function loadConfig() {
10517
- const path = (0, import_node_path4.join)((0, import_node_os4.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
10716
+ const path = (0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR, CONFIG_FILE);
10518
10717
  try {
10519
- return JSON.parse(await (0, import_promises2.readFile)(path, "utf-8"));
10718
+ return JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
10520
10719
  } catch {
10521
10720
  return {};
10522
10721
  }
@@ -10530,10 +10729,32 @@ function buildWhitelist(cfg) {
10530
10729
  function isTTY() {
10531
10730
  return process.stdin.isTTY === true;
10532
10731
  }
10732
+ function detectCommands() {
10733
+ const bins = [
10734
+ ["claude", "claude"],
10735
+ ["codex", "codex"],
10736
+ ["kimi-cli", "kimi"],
10737
+ ["pi", "pi"]
10738
+ ];
10739
+ const found = ["shell"];
10740
+ for (const [cmd, bin] of bins) {
10741
+ let ok = false;
10742
+ if (process.platform === "win32") {
10743
+ ok = (0, import_node_child_process2.spawnSync)("where", [bin], { timeout: 5e3 }).status === 0;
10744
+ } else {
10745
+ const shell = process.env.SHELL ?? "/bin/bash";
10746
+ ok = (0, import_node_child_process2.spawnSync)(shell, ["-l", "-c", `command -v ${bin}`], {
10747
+ timeout: 5e3
10748
+ }).status === 0;
10749
+ }
10750
+ if (ok) found.push(cmd);
10751
+ }
10752
+ return found;
10753
+ }
10533
10754
  async function readInput(prompt) {
10534
10755
  const rl = (0, import_node_readline.createInterface)({ input: process.stdin, output: process.stdout });
10535
10756
  try {
10536
- return await new Promise((resolve) => rl.question(prompt, (answer) => resolve(answer)));
10757
+ return await new Promise((resolve2) => rl.question(prompt, (answer) => resolve2(answer)));
10537
10758
  } finally {
10538
10759
  rl.close();
10539
10760
  }
@@ -10568,18 +10789,42 @@ async function setupWizard(config, opts) {
10568
10789
  const base = baseUrlFromWsUrl(url);
10569
10790
  console.log("");
10570
10791
  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
10792
  if (!isTTY()) {
10793
+ openBrowser(base);
10574
10794
  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
10795
  return null;
10576
10796
  }
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;
10797
+ console.log(" 1) \u767B\u5F55\u8D26\u53F7\u81EA\u52A8\u83B7\u53D6\uFF08\u63A8\u8350\uFF09");
10798
+ console.log(" 2) \u4ECE\u7F51\u9875\u300C\u63A7\u5236\u9762\u677F\u300D\u590D\u5236\u7C98\u8D34");
10799
+ const mode = (await readInput("\u9009\u62E9 [1]: ")).trim();
10800
+ if (mode === "2") {
10801
+ openBrowser(base);
10802
+ 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`);
10803
+ const ans = (await readInput("\u7C98\u8D34 registrationKey: ")).trim();
10804
+ if (!ans) {
10805
+ console.log("[local] \u672A\u63D0\u4F9B registrationKey\uFF0C\u9000\u51FA\u3002");
10806
+ return null;
10807
+ }
10808
+ registrationKey = ans;
10809
+ } else {
10810
+ for (; ; ) {
10811
+ const email = (await readInput("\u90AE\u7BB1: ")).trim();
10812
+ const password = (await readInput("\u5BC6\u7801: ")).trim();
10813
+ if (!email || !password) {
10814
+ console.log("[local] \u90AE\u7BB1\u6216\u5BC6\u7801\u4E3A\u7A7A\uFF0C\u91CD\u8BD5\uFF08\u6216 Ctrl+C \u9000\u51FA\uFF09\u3002");
10815
+ continue;
10816
+ }
10817
+ try {
10818
+ const res = await loginAndFetchRegistrationKey(base, email, password);
10819
+ registrationKey = res.registrationKey;
10820
+ console.log(`[local] \u5DF2\u767B\u5F55 ${res.user}\uFF0C\u83B7\u53D6 registrationKey \u6210\u529F`);
10821
+ break;
10822
+ } catch (e) {
10823
+ console.error(`[local] ${e.message}`);
10824
+ console.log("[local] \u91CD\u8BD5\uFF08\u6216 Ctrl+C \u9000\u51FA\uFF09\u3002");
10825
+ }
10826
+ }
10581
10827
  }
10582
- registrationKey = ans;
10583
10828
  changed = true;
10584
10829
  }
10585
10830
  if (changed) {
@@ -10603,10 +10848,17 @@ async function startLocal(opts = {}) {
10603
10848
  const pty = new PtyManager();
10604
10849
  const loggers = /* @__PURE__ */ new Map();
10605
10850
  const lineBuffers = /* @__PURE__ */ new Map();
10851
+ const transfers = new FileTransferManager(
10852
+ (data) => safeSend(data),
10853
+ (msg) => console.log(`[local] ${msg}`)
10854
+ );
10606
10855
  let credentials = await ensureCredentials(baseUrl, registrationKey);
10856
+ const commands = detectCommands();
10857
+ if (opts.daemon) writeGatewayPid();
10607
10858
  console.log(`[local] connecting to ${url}`);
10608
10859
  console.log(`[local] deviceId=${credentials.deviceId}`);
10609
10860
  console.log(`[local] whitelist: ${whitelist.size} commands`);
10861
+ console.log(`[local] available CLIs: ${commands.join(", ")}`);
10610
10862
  let ws = null;
10611
10863
  let heartbeatTimer = null;
10612
10864
  let reconnectTimer = null;
@@ -10622,7 +10874,8 @@ async function startLocal(opts = {}) {
10622
10874
  t: "auth",
10623
10875
  role: "bridge",
10624
10876
  deviceId: credentials.deviceId,
10625
- token: credentials.accessToken
10877
+ token: credentials.accessToken,
10878
+ commands
10626
10879
  })
10627
10880
  );
10628
10881
  }
@@ -10677,6 +10930,8 @@ async function startLocal(opts = {}) {
10677
10930
  userExitCode = code;
10678
10931
  if (reconnectTimer) clearTimeout(reconnectTimer);
10679
10932
  reconnectTimer = null;
10933
+ if (opts.daemon) removeGatewayPid();
10934
+ transfers.abortAll();
10680
10935
  pty.killAll();
10681
10936
  for (const [, logger] of loggers) logger.logExit(-1);
10682
10937
  loggers.clear();
@@ -10726,6 +10981,21 @@ async function startLocal(opts = {}) {
10726
10981
  case "replay-req":
10727
10982
  handleReplay(frame);
10728
10983
  break;
10984
+ case "upload-start":
10985
+ void transfers.handleUploadStart(frame);
10986
+ break;
10987
+ case "upload-chunk":
10988
+ transfers.handleUploadChunk(frame);
10989
+ break;
10990
+ case "upload-end":
10991
+ void transfers.handleUploadEnd(frame);
10992
+ break;
10993
+ case "download-req":
10994
+ void transfers.handleDownloadReq(
10995
+ frame,
10996
+ () => ws ? ws.bufferedAmount > 4 * 1024 * 1024 : false
10997
+ );
10998
+ break;
10729
10999
  default:
10730
11000
  console.log("[local] unhandled frame:", frame.t);
10731
11001
  }
@@ -10878,7 +11148,8 @@ async function startLocal(opts = {}) {
10878
11148
  encodeFrame({
10879
11149
  t: "replay-res",
10880
11150
  sessionId: frame.sessionId,
10881
- frames
11151
+ frames,
11152
+ exists: pty.has(frame.sessionId)
10882
11153
  })
10883
11154
  );
10884
11155
  }
@@ -10899,6 +11170,37 @@ function toUint8(raw) {
10899
11170
  }
10900
11171
  throw new Error("unexpected raw type");
10901
11172
  }
11173
+ function gatewayPidFile() {
11174
+ return (0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid");
11175
+ }
11176
+ function writeGatewayPid() {
11177
+ (0, import_node_fs3.mkdirSync)((0, import_node_path5.join)((0, import_node_os5.homedir)(), DEFAULT_CONFIG_DIR), { recursive: true });
11178
+ (0, import_node_fs3.writeFileSync)(gatewayPidFile(), String(process.pid));
11179
+ }
11180
+ function removeGatewayPid() {
11181
+ try {
11182
+ (0, import_node_fs3.rmSync)(gatewayPidFile());
11183
+ } catch {
11184
+ }
11185
+ }
11186
+ function gatewayPid() {
11187
+ try {
11188
+ const pid = Number((0, import_node_fs3.readFileSync)(gatewayPidFile(), "utf-8").trim());
11189
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
11190
+ } catch {
11191
+ return null;
11192
+ }
11193
+ }
11194
+ function gatewayAlive() {
11195
+ const pid = gatewayPid();
11196
+ if (pid === null) return false;
11197
+ try {
11198
+ process.kill(pid, 0);
11199
+ return true;
11200
+ } catch {
11201
+ return false;
11202
+ }
11203
+ }
10902
11204
 
10903
11205
  // src/index.ts
10904
11206
  var import_meta = {};
@@ -10917,15 +11219,15 @@ function resolveHere() {
10917
11219
  if (DEBUG) console.error("[debug] cannot resolve script path");
10918
11220
  return;
10919
11221
  }
10920
- const hereDir = (0, import_node_path5.dirname)(here);
11222
+ const hereDir = (0, import_node_path6.dirname)(here);
10921
11223
  const candidates = [
10922
- (0, import_node_path5.join)(hereDir, "..", "node_modules", "node-pty", "prebuilds"),
10923
- (0, import_node_path5.join)(hereDir, "..", "..", "node-pty", "prebuilds"),
10924
- (0, import_node_path5.join)(hereDir, "..", "..", "..", "node_modules", "node-pty", "prebuilds")
11224
+ (0, import_node_path6.join)(hereDir, "..", "node_modules", "node-pty", "prebuilds"),
11225
+ (0, import_node_path6.join)(hereDir, "..", "..", "node-pty", "prebuilds"),
11226
+ (0, import_node_path6.join)(hereDir, "..", "..", "..", "node_modules", "node-pty", "prebuilds")
10925
11227
  ];
10926
11228
  let prebuildsDir = null;
10927
11229
  for (const c of candidates) {
10928
- if ((0, import_node_fs2.existsSync)(c)) {
11230
+ if ((0, import_node_fs4.existsSync)(c)) {
10929
11231
  prebuildsDir = c;
10930
11232
  break;
10931
11233
  }
@@ -10935,11 +11237,11 @@ function resolveHere() {
10935
11237
  return;
10936
11238
  }
10937
11239
  let n = 0;
10938
- for (const sub of (0, import_node_fs2.readdirSync)(prebuildsDir)) {
10939
- const helper = (0, import_node_path5.join)(prebuildsDir, sub, "spawn-helper");
10940
- if ((0, import_node_fs2.existsSync)(helper)) {
11240
+ for (const sub of (0, import_node_fs4.readdirSync)(prebuildsDir)) {
11241
+ const helper = (0, import_node_path6.join)(prebuildsDir, sub, "spawn-helper");
11242
+ if ((0, import_node_fs4.existsSync)(helper)) {
10941
11243
  try {
10942
- (0, import_node_fs2.chmodSync)(helper, 493);
11244
+ (0, import_node_fs4.chmodSync)(helper, 493);
10943
11245
  n++;
10944
11246
  } catch {
10945
11247
  }
@@ -10986,20 +11288,20 @@ example:
10986
11288
  }
10987
11289
  var LAUNCHD_LABEL = "net.cli-remote.local";
10988
11290
  function launchdPlistPath() {
10989
- return (0, import_node_path5.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
11291
+ return (0, import_node_path6.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
10990
11292
  }
10991
11293
  function entryScript() {
10992
11294
  const a1 = process.argv[1];
10993
11295
  if (!a1 || a1.endsWith(".ts")) return null;
10994
11296
  try {
10995
- return (0, import_node_fs2.realpathSync)(a1);
11297
+ return (0, import_node_fs4.realpathSync)(a1);
10996
11298
  } catch {
10997
11299
  return a1;
10998
11300
  }
10999
11301
  }
11000
11302
  function run(cmd, args, okToFail = false) {
11001
11303
  try {
11002
- return (0, import_node_child_process2.execFileSync)(cmd, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
11304
+ return (0, import_node_child_process3.execFileSync)(cmd, args, { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
11003
11305
  } catch (e) {
11004
11306
  if (okToFail) return null;
11005
11307
  throw e;
@@ -11010,7 +11312,7 @@ function launchdInstall(urlArg) {
11010
11312
  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
11313
  process.exit(1);
11012
11314
  }
11013
- const home = (0, import_node_os5.homedir)();
11315
+ const home = (0, import_node_os6.homedir)();
11014
11316
  const entry = entryScript();
11015
11317
  if (!entry) {
11016
11318
  console.error("[local] \u5F53\u524D\u4EE5\u6E90\u7801\uFF08tsx\uFF09\u6A21\u5F0F\u8FD0\u884C\uFF0C\u65E0\u6CD5\u5B89\u88C5\u5E38\u9A7B\u670D\u52A1\u3002");
@@ -11018,28 +11320,28 @@ function launchdInstall(urlArg) {
11018
11320
  process.exit(1);
11019
11321
  }
11020
11322
  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");
11323
+ const cfgDir = (0, import_node_path6.join)(home, DEFAULT_CONFIG_DIR);
11324
+ (0, import_node_fs4.mkdirSync)(cfgDir, { recursive: true });
11325
+ const cfgPath = (0, import_node_path6.join)(cfgDir, "config.json");
11024
11326
  let cfg = {};
11025
11327
  try {
11026
- cfg = JSON.parse((0, import_node_fs2.readFileSync)(cfgPath, "utf-8"));
11328
+ cfg = JSON.parse((0, import_node_fs4.readFileSync)(cfgPath, "utf-8"));
11027
11329
  } catch {
11028
11330
  }
11029
11331
  cfg.relayUrl = urlArg;
11030
- (0, import_node_fs2.writeFileSync)(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
11332
+ (0, import_node_fs4.writeFileSync)(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
11031
11333
  console.log(`[local] relayUrl \u5DF2\u5199\u5165 ${cfgPath}`);
11032
11334
  }
11033
- const credPath = (0, import_node_path5.join)(home, DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
11034
- if (!(0, import_node_fs2.existsSync)(credPath)) {
11335
+ const credPath = (0, import_node_path6.join)(home, DEFAULT_CONFIG_DIR, CREDENTIALS_FILE);
11336
+ if (!(0, import_node_fs4.existsSync)(credPath)) {
11035
11337
  console.error("[local] \u5C1A\u672A\u6388\u6743\uFF08\u627E\u4E0D\u5230 credentials.json\uFF09\u3002");
11036
11338
  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
11339
  process.exit(1);
11038
11340
  }
11039
11341
  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 });
11342
+ const logDir = (0, import_node_path6.join)(home, DEFAULT_CONFIG_DIR, "logs");
11343
+ (0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(plist), { recursive: true });
11344
+ (0, import_node_fs4.mkdirSync)(logDir, { recursive: true });
11043
11345
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
11044
11346
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
11045
11347
  <plist version="1.0">
@@ -11053,12 +11355,12 @@ function launchdInstall(urlArg) {
11053
11355
  <key>RunAtLoad</key><true/>
11054
11356
  <key>KeepAlive</key><true/>
11055
11357
  <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>
11358
+ <key>StandardOutPath</key><string>${(0, import_node_path6.join)(logDir, "daemon.out.log")}</string>
11359
+ <key>StandardErrorPath</key><string>${(0, import_node_path6.join)(logDir, "daemon.err.log")}</string>
11058
11360
  </dict>
11059
11361
  </plist>
11060
11362
  `;
11061
- (0, import_node_fs2.writeFileSync)(plist, xml);
11363
+ (0, import_node_fs4.writeFileSync)(plist, xml);
11062
11364
  const uid = process.getuid?.();
11063
11365
  if (uid === void 0) {
11064
11366
  console.error("[local] \u65E0\u6CD5\u786E\u5B9A uid\uFF0Claunchd \u5B89\u88C5\u5931\u8D25");
@@ -11076,7 +11378,7 @@ function launchdInstall(urlArg) {
11076
11378
  }
11077
11379
  function launchdUninstall() {
11078
11380
  const plist = launchdPlistPath();
11079
- if (!(0, import_node_fs2.existsSync)(plist)) {
11381
+ if (!(0, import_node_fs4.existsSync)(plist)) {
11080
11382
  console.log("[local] \u672A\u627E\u5230\u5DF2\u5B89\u88C5\u7684\u5E38\u9A7B\u670D\u52A1\uFF08nothing to uninstall\uFF09");
11081
11383
  process.exit(0);
11082
11384
  }
@@ -11084,7 +11386,7 @@ function launchdUninstall() {
11084
11386
  const guiDomain = `gui/${uid ?? 0}`;
11085
11387
  run("launchctl", ["bootout", guiDomain, plist], true);
11086
11388
  run("launchctl", ["unload", "-w", plist], true);
11087
- (0, import_node_fs2.rmSync)(plist);
11389
+ (0, import_node_fs4.rmSync)(plist);
11088
11390
  console.log(`[local] launchd \u670D\u52A1\u5DF2\u505C\u6B62\u5E76\u5378\u8F7D\uFF1A${LAUNCHD_LABEL}`);
11089
11391
  }
11090
11392
  if (argUrl && !argMatch && argUrl === "install") {
@@ -11095,6 +11397,173 @@ if (argUrl && !argMatch && argUrl === "uninstall") {
11095
11397
  launchdUninstall();
11096
11398
  process.exit(0);
11097
11399
  }
11400
+ if (argUrl === "gateway") {
11401
+ let gatewayLogFile = function() {
11402
+ return (0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "logs", "gateway.log");
11403
+ }, stopWorker = function() {
11404
+ const pid = gatewayPid();
11405
+ if (pid === null || !gatewayAlive()) {
11406
+ (0, import_node_fs4.rmSync)((0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
11407
+ return;
11408
+ }
11409
+ try {
11410
+ process.kill(pid, "SIGTERM");
11411
+ } catch {
11412
+ }
11413
+ const t0 = Date.now();
11414
+ while (Date.now() - t0 < 5e3 && gatewayAlive()) {
11415
+ const end = Date.now() + 150;
11416
+ while (Date.now() < end) {
11417
+ }
11418
+ }
11419
+ (0, import_node_fs4.rmSync)((0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "gateway.pid"), { force: true });
11420
+ }, gatewayStop = function() {
11421
+ const pid = gatewayPid();
11422
+ const wasRunning = gatewayAlive();
11423
+ stopWorker();
11424
+ console.log(wasRunning ? `[gateway] stopped (pid ${pid ?? "?"})` : "[gateway] not running");
11425
+ process.exit(0);
11426
+ }, gatewayStatus = function() {
11427
+ if (!gatewayAlive()) {
11428
+ console.log("[gateway] stopped");
11429
+ process.exit(1);
11430
+ }
11431
+ const pid = gatewayPid();
11432
+ let hub = "(\u672A\u914D\u7F6E)";
11433
+ try {
11434
+ const cfg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path6.join)((0, import_node_os6.homedir)(), DEFAULT_CONFIG_DIR, "config.json"), "utf-8"));
11435
+ hub = cfg.relayUrl ?? hub;
11436
+ } catch {
11437
+ }
11438
+ console.log(`[gateway] running (pid ${pid})`);
11439
+ console.log(`[gateway] hub: ${hub}`);
11440
+ console.log(`[gateway] \u65E5\u5FD7: ${gatewayLogFile()}`);
11441
+ process.exit(0);
11442
+ }, gatewayLogs = function() {
11443
+ const follow = rest.includes("-f") || rest.includes("--follow");
11444
+ const li = rest.findIndex((a) => a === "-n" || a === "--lines");
11445
+ const lines = li >= 0 ? Number(rest[li + 1]) || 50 : 50;
11446
+ const file = gatewayLogFile();
11447
+ if (!(0, import_node_fs4.existsSync)(file)) {
11448
+ console.log(`[gateway] \u6682\u65E0\u65E5\u5FD7\uFF08${file} \u4E0D\u5B58\u5728\uFF09`);
11449
+ process.exit(0);
11450
+ }
11451
+ const tail = (0, import_node_fs4.readFileSync)(file, "utf-8").trimEnd().split("\n").slice(-lines);
11452
+ console.log(tail.join("\n"));
11453
+ if (!follow) process.exit(0);
11454
+ let size = (0, import_node_fs4.statSync)(file).size;
11455
+ console.log("--- following (Ctrl+C \u9000\u51FA) ---");
11456
+ setInterval(() => {
11457
+ try {
11458
+ const st = (0, import_node_fs4.statSync)(file);
11459
+ if (st.size > size) {
11460
+ const fd = (0, import_node_fs4.openSync)(file, "r");
11461
+ const buf = Buffer.alloc(st.size - size);
11462
+ (0, import_node_fs4.readSync)(fd, buf, 0, buf.length, size);
11463
+ (0, import_node_fs4.closeSync)(fd);
11464
+ size = st.size;
11465
+ process.stdout.write(buf.toString("utf-8"));
11466
+ } else if (st.size < size) {
11467
+ size = 0;
11468
+ }
11469
+ } catch {
11470
+ }
11471
+ }, 1e3);
11472
+ };
11473
+ gatewayLogFile2 = gatewayLogFile, stopWorker2 = stopWorker, gatewayStop2 = gatewayStop, gatewayStatus2 = gatewayStatus, gatewayLogs2 = gatewayLogs;
11474
+ const action = process.argv[3] ?? "";
11475
+ const rest = process.argv.slice(4);
11476
+ const hubArg = (() => {
11477
+ const i = rest.indexOf("--hub");
11478
+ return i >= 0 ? rest[i + 1] : void 0;
11479
+ })();
11480
+ async function gatewayStart() {
11481
+ if (gatewayAlive()) {
11482
+ console.log(`[gateway] already running (pid ${gatewayPid()})`);
11483
+ process.exit(0);
11484
+ }
11485
+ const home = (0, import_node_os6.homedir)();
11486
+ const entry = entryScript();
11487
+ const cfgDir = (0, import_node_path6.join)(home, DEFAULT_CONFIG_DIR);
11488
+ const credPath = (0, import_node_path6.join)(cfgDir, CREDENTIALS_FILE);
11489
+ const cfgPath = (0, import_node_path6.join)(cfgDir, "config.json");
11490
+ const hasCfg = (0, import_node_fs4.existsSync)(cfgPath);
11491
+ if (!entry) {
11492
+ 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");
11493
+ process.exit(1);
11494
+ }
11495
+ if (!hasCfg || !(0, import_node_fs4.existsSync)(credPath)) {
11496
+ console.error("[gateway] \u5C1A\u672A\u914D\u7F6E/\u6388\u6743\u3002\u8BF7\u5148\u4EA4\u4E92\u5F0F\u8FD0\u884C\u4E00\u6B21 cli-local \u5B8C\u6210\u767B\u5F55\u4E0E\u8BBE\u5907\u6388\u6743\u3002");
11497
+ process.exit(1);
11498
+ }
11499
+ (0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(gatewayLogFile()), { recursive: true });
11500
+ const out = (0, import_node_fs4.openSync)(gatewayLogFile(), "a");
11501
+ const env = {};
11502
+ for (const [k, v] of Object.entries(process.env)) if (v !== void 0) env[k] = v;
11503
+ const args = [entry, "gateway", "run"];
11504
+ if (hubArg) args.push("--hub", hubArg);
11505
+ const child = (0, import_node_child_process3.spawn)(process.execPath, args, {
11506
+ detached: true,
11507
+ stdio: ["ignore", out, out],
11508
+ env
11509
+ });
11510
+ child.unref();
11511
+ console.log(`[gateway] started (pid ${child.pid})`);
11512
+ console.log(`[gateway] \u65E5\u5FD7: ${gatewayLogFile()}`);
11513
+ console.log("[gateway] \u72B6\u6001: cli-local gateway status");
11514
+ }
11515
+ switch (action) {
11516
+ case "start":
11517
+ gatewayStart().then(() => process.exit(0)).catch((e) => {
11518
+ console.error(`[gateway] ${e.message}`);
11519
+ process.exit(1);
11520
+ });
11521
+ break;
11522
+ case "stop":
11523
+ gatewayStop();
11524
+ break;
11525
+ case "restart":
11526
+ stopWorker();
11527
+ gatewayStart().then(() => process.exit(0)).catch((e) => {
11528
+ console.error(`[gateway] ${e.message}`);
11529
+ process.exit(1);
11530
+ });
11531
+ break;
11532
+ case "status":
11533
+ gatewayStatus();
11534
+ break;
11535
+ case "logs":
11536
+ gatewayLogs();
11537
+ break;
11538
+ case "run":
11539
+ startLocal({ url: hubArg, daemon: true }).catch((err) => {
11540
+ if (err instanceof AuthError) console.error(`[local] ${err.message}`);
11541
+ else console.error("[local] fatal:", err);
11542
+ process.exit(1);
11543
+ });
11544
+ break;
11545
+ default:
11546
+ console.log(`cli-local gateway \u2014 \u540E\u53F0\u8FD0\u884C\u7BA1\u7406
11547
+
11548
+ usage:
11549
+ cli-local gateway start [--hub URL] \u540E\u53F0\u542F\u52A8\uFF08\u81EA\u52A8\u91CD\u8FDE\uFF09
11550
+ cli-local gateway stop \u505C\u6B62
11551
+ cli-local gateway restart \u91CD\u542F
11552
+ cli-local gateway status \u8FD0\u884C\u72B6\u6001
11553
+ cli-local gateway logs [-f] [-n N] \u67E5\u770B\u65E5\u5FD7\uFF08-f \u8DDF\u968F\uFF09
11554
+ cli-local gateway run [--hub URL] \u524D\u53F0\u8FD0\u884C\uFF08\u4F9B start \u62C9\u8D77\uFF09
11555
+
11556
+ \u8BF4\u660E\uFF1A\u540E\u53F0\u8FDB\u7A0B\u7531 pid \u6587\u4EF6\u7BA1\u7406\uFF08~/.cli-remote/gateway.pid\uFF09\uFF0C\u65E5\u5FD7\u5728
11557
+ ~/.cli-remote/logs/gateway.log\u3002\u9996\u6B21\u4F7F\u7528\u8BF7\u5148\u4EA4\u4E92\u5F0F\u8FD0\u884C cli-local
11558
+ \u5B8C\u6210\u767B\u5F55\u4E0E\u8BBE\u5907\u6388\u6743\u3002macOS \u60F3\u5F00\u673A\u81EA\u542F\u7528 launchd\uFF1Acli-local install\u3002`);
11559
+ process.exit(action ? 1 : 0);
11560
+ }
11561
+ }
11562
+ var gatewayLogFile2;
11563
+ var stopWorker2;
11564
+ var gatewayStop2;
11565
+ var gatewayStatus2;
11566
+ var gatewayLogs2;
11098
11567
  startLocal(argMatch ? { url: argUrl } : {}).catch((err) => {
11099
11568
  if (err instanceof AuthError) {
11100
11569
  console.error(`[local] ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cli-remote/local",
3
- "version": "0.0.3",
3
+ "version": "0.0.6",
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": {