@clawos-dev/clawd 0.1.2 → 0.2.2-beta.1.e86e2eb

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/cli.cjs CHANGED
@@ -293,8 +293,8 @@ var require_req = __commonJS({
293
293
  if (req.originalUrl) {
294
294
  _req.url = req.originalUrl;
295
295
  } else {
296
- const path12 = req.path;
297
- _req.url = typeof path12 === "string" ? path12 : req.url ? req.url.path || req.url : void 0;
296
+ const path16 = req.path;
297
+ _req.url = typeof path16 === "string" ? path16 : req.url ? req.url.path || req.url : void 0;
298
298
  }
299
299
  if (req.query) {
300
300
  _req.query = req.query;
@@ -459,14 +459,14 @@ var require_redact = __commonJS({
459
459
  }
460
460
  return obj;
461
461
  }
462
- function parsePath(path12) {
462
+ function parsePath(path16) {
463
463
  const parts = [];
464
464
  let current = "";
465
465
  let inBrackets = false;
466
466
  let inQuotes = false;
467
467
  let quoteChar = "";
468
- for (let i = 0; i < path12.length; i++) {
469
- const char = path12[i];
468
+ for (let i = 0; i < path16.length; i++) {
469
+ const char = path16[i];
470
470
  if (!inBrackets && char === ".") {
471
471
  if (current) {
472
472
  parts.push(current);
@@ -597,10 +597,10 @@ var require_redact = __commonJS({
597
597
  return current;
598
598
  }
599
599
  function redactPaths(obj, paths, censor, remove = false) {
600
- for (const path12 of paths) {
601
- const parts = parsePath(path12);
600
+ for (const path16 of paths) {
601
+ const parts = parsePath(path16);
602
602
  if (parts.includes("*")) {
603
- redactWildcardPath(obj, parts, censor, path12, remove);
603
+ redactWildcardPath(obj, parts, censor, path16, remove);
604
604
  } else {
605
605
  if (remove) {
606
606
  removeKey(obj, parts);
@@ -685,8 +685,8 @@ var require_redact = __commonJS({
685
685
  }
686
686
  } else {
687
687
  if (afterWildcard.includes("*")) {
688
- const wrappedCensor = typeof censor === "function" ? (value, path12) => {
689
- const fullPath = [...pathArray.slice(0, pathLength), ...path12];
688
+ const wrappedCensor = typeof censor === "function" ? (value, path16) => {
689
+ const fullPath = [...pathArray.slice(0, pathLength), ...path16];
690
690
  return censor(value, fullPath);
691
691
  } : censor;
692
692
  redactWildcardPath(current, afterWildcard, wrappedCensor, originalPath, remove);
@@ -721,8 +721,8 @@ var require_redact = __commonJS({
721
721
  return null;
722
722
  }
723
723
  const pathStructure = /* @__PURE__ */ new Map();
724
- for (const path12 of pathsToClone) {
725
- const parts = parsePath(path12);
724
+ for (const path16 of pathsToClone) {
725
+ const parts = parsePath(path16);
726
726
  let current = pathStructure;
727
727
  for (let i = 0; i < parts.length; i++) {
728
728
  const part = parts[i];
@@ -774,24 +774,24 @@ var require_redact = __commonJS({
774
774
  }
775
775
  return cloneSelectively(obj, pathStructure);
776
776
  }
777
- function validatePath(path12) {
778
- if (typeof path12 !== "string") {
777
+ function validatePath(path16) {
778
+ if (typeof path16 !== "string") {
779
779
  throw new Error("Paths must be (non-empty) strings");
780
780
  }
781
- if (path12 === "") {
781
+ if (path16 === "") {
782
782
  throw new Error("Invalid redaction path ()");
783
783
  }
784
- if (path12.includes("..")) {
785
- throw new Error(`Invalid redaction path (${path12})`);
784
+ if (path16.includes("..")) {
785
+ throw new Error(`Invalid redaction path (${path16})`);
786
786
  }
787
- if (path12.includes(",")) {
788
- throw new Error(`Invalid redaction path (${path12})`);
787
+ if (path16.includes(",")) {
788
+ throw new Error(`Invalid redaction path (${path16})`);
789
789
  }
790
790
  let bracketCount = 0;
791
791
  let inQuotes = false;
792
792
  let quoteChar = "";
793
- for (let i = 0; i < path12.length; i++) {
794
- const char = path12[i];
793
+ for (let i = 0; i < path16.length; i++) {
794
+ const char = path16[i];
795
795
  if ((char === '"' || char === "'") && bracketCount > 0) {
796
796
  if (!inQuotes) {
797
797
  inQuotes = true;
@@ -805,20 +805,20 @@ var require_redact = __commonJS({
805
805
  } else if (char === "]" && !inQuotes) {
806
806
  bracketCount--;
807
807
  if (bracketCount < 0) {
808
- throw new Error(`Invalid redaction path (${path12})`);
808
+ throw new Error(`Invalid redaction path (${path16})`);
809
809
  }
810
810
  }
811
811
  }
812
812
  if (bracketCount !== 0) {
813
- throw new Error(`Invalid redaction path (${path12})`);
813
+ throw new Error(`Invalid redaction path (${path16})`);
814
814
  }
815
815
  }
816
816
  function validatePaths(paths) {
817
817
  if (!Array.isArray(paths)) {
818
818
  throw new TypeError("paths must be an array");
819
819
  }
820
- for (const path12 of paths) {
821
- validatePath(path12);
820
+ for (const path16 of paths) {
821
+ validatePath(path16);
822
822
  }
823
823
  }
824
824
  function slowRedact(options = {}) {
@@ -986,8 +986,8 @@ var require_redaction = __commonJS({
986
986
  if (shape[k] === null) {
987
987
  o[k] = (value) => topCensor(value, [k]);
988
988
  } else {
989
- const wrappedCensor = typeof censor === "function" ? (value, path12) => {
990
- return censor(value, [k, ...path12]);
989
+ const wrappedCensor = typeof censor === "function" ? (value, path16) => {
990
+ return censor(value, [k, ...path16]);
991
991
  } : censor;
992
992
  o[k] = Redact({
993
993
  paths: shape[k],
@@ -1205,10 +1205,10 @@ var require_atomic_sleep = __commonJS({
1205
1205
  var require_sonic_boom = __commonJS({
1206
1206
  "../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
1207
1207
  "use strict";
1208
- var fs12 = require("fs");
1208
+ var fs16 = require("fs");
1209
1209
  var EventEmitter = require("events");
1210
1210
  var inherits = require("util").inherits;
1211
- var path12 = require("path");
1211
+ var path16 = require("path");
1212
1212
  var sleep = require_atomic_sleep();
1213
1213
  var assert = require("assert");
1214
1214
  var BUSY_WRITE_TIMEOUT = 100;
@@ -1262,20 +1262,20 @@ var require_sonic_boom = __commonJS({
1262
1262
  const mode = sonic.mode;
1263
1263
  if (sonic.sync) {
1264
1264
  try {
1265
- if (sonic.mkdir) fs12.mkdirSync(path12.dirname(file), { recursive: true });
1266
- const fd = fs12.openSync(file, flags, mode);
1265
+ if (sonic.mkdir) fs16.mkdirSync(path16.dirname(file), { recursive: true });
1266
+ const fd = fs16.openSync(file, flags, mode);
1267
1267
  fileOpened(null, fd);
1268
1268
  } catch (err) {
1269
1269
  fileOpened(err);
1270
1270
  throw err;
1271
1271
  }
1272
1272
  } else if (sonic.mkdir) {
1273
- fs12.mkdir(path12.dirname(file), { recursive: true }, (err) => {
1273
+ fs16.mkdir(path16.dirname(file), { recursive: true }, (err) => {
1274
1274
  if (err) return fileOpened(err);
1275
- fs12.open(file, flags, mode, fileOpened);
1275
+ fs16.open(file, flags, mode, fileOpened);
1276
1276
  });
1277
1277
  } else {
1278
- fs12.open(file, flags, mode, fileOpened);
1278
+ fs16.open(file, flags, mode, fileOpened);
1279
1279
  }
1280
1280
  }
1281
1281
  function SonicBoom(opts) {
@@ -1316,8 +1316,8 @@ var require_sonic_boom = __commonJS({
1316
1316
  this.flush = flushBuffer;
1317
1317
  this.flushSync = flushBufferSync;
1318
1318
  this._actualWrite = actualWriteBuffer;
1319
- fsWriteSync = () => fs12.writeSync(this.fd, this._writingBuf);
1320
- fsWrite = () => fs12.write(this.fd, this._writingBuf, this.release);
1319
+ fsWriteSync = () => fs16.writeSync(this.fd, this._writingBuf);
1320
+ fsWrite = () => fs16.write(this.fd, this._writingBuf, this.release);
1321
1321
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
1322
1322
  this._writingBuf = "";
1323
1323
  this.write = write;
@@ -1326,15 +1326,15 @@ var require_sonic_boom = __commonJS({
1326
1326
  this._actualWrite = actualWrite;
1327
1327
  fsWriteSync = () => {
1328
1328
  if (Buffer.isBuffer(this._writingBuf)) {
1329
- return fs12.writeSync(this.fd, this._writingBuf);
1329
+ return fs16.writeSync(this.fd, this._writingBuf);
1330
1330
  }
1331
- return fs12.writeSync(this.fd, this._writingBuf, "utf8");
1331
+ return fs16.writeSync(this.fd, this._writingBuf, "utf8");
1332
1332
  };
1333
1333
  fsWrite = () => {
1334
1334
  if (Buffer.isBuffer(this._writingBuf)) {
1335
- return fs12.write(this.fd, this._writingBuf, this.release);
1335
+ return fs16.write(this.fd, this._writingBuf, this.release);
1336
1336
  }
1337
- return fs12.write(this.fd, this._writingBuf, "utf8", this.release);
1337
+ return fs16.write(this.fd, this._writingBuf, "utf8", this.release);
1338
1338
  };
1339
1339
  } else {
1340
1340
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -1391,7 +1391,7 @@ var require_sonic_boom = __commonJS({
1391
1391
  }
1392
1392
  }
1393
1393
  if (this._fsync) {
1394
- fs12.fsyncSync(this.fd);
1394
+ fs16.fsyncSync(this.fd);
1395
1395
  }
1396
1396
  const len = this._len;
1397
1397
  if (this._reopening) {
@@ -1505,7 +1505,7 @@ var require_sonic_boom = __commonJS({
1505
1505
  const onDrain = () => {
1506
1506
  if (!this._fsync) {
1507
1507
  try {
1508
- fs12.fsync(this.fd, (err) => {
1508
+ fs16.fsync(this.fd, (err) => {
1509
1509
  this._flushPending = false;
1510
1510
  cb(err);
1511
1511
  });
@@ -1607,7 +1607,7 @@ var require_sonic_boom = __commonJS({
1607
1607
  const fd = this.fd;
1608
1608
  this.once("ready", () => {
1609
1609
  if (fd !== this.fd) {
1610
- fs12.close(fd, (err) => {
1610
+ fs16.close(fd, (err) => {
1611
1611
  if (err) {
1612
1612
  return this.emit("error", err);
1613
1613
  }
@@ -1656,7 +1656,7 @@ var require_sonic_boom = __commonJS({
1656
1656
  buf = this._bufs[0];
1657
1657
  }
1658
1658
  try {
1659
- const n = Buffer.isBuffer(buf) ? fs12.writeSync(this.fd, buf) : fs12.writeSync(this.fd, buf, "utf8");
1659
+ const n = Buffer.isBuffer(buf) ? fs16.writeSync(this.fd, buf) : fs16.writeSync(this.fd, buf, "utf8");
1660
1660
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
1661
1661
  buf = releasedBufObj.writingBuf;
1662
1662
  this._len = releasedBufObj.len;
@@ -1672,7 +1672,7 @@ var require_sonic_boom = __commonJS({
1672
1672
  }
1673
1673
  }
1674
1674
  try {
1675
- fs12.fsyncSync(this.fd);
1675
+ fs16.fsyncSync(this.fd);
1676
1676
  } catch {
1677
1677
  }
1678
1678
  }
@@ -1693,7 +1693,7 @@ var require_sonic_boom = __commonJS({
1693
1693
  buf = mergeBuf(this._bufs[0], this._lens[0]);
1694
1694
  }
1695
1695
  try {
1696
- const n = fs12.writeSync(this.fd, buf);
1696
+ const n = fs16.writeSync(this.fd, buf);
1697
1697
  buf = buf.subarray(n);
1698
1698
  this._len = Math.max(this._len - n, 0);
1699
1699
  if (buf.length <= 0) {
@@ -1721,13 +1721,13 @@ var require_sonic_boom = __commonJS({
1721
1721
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
1722
1722
  if (this.sync) {
1723
1723
  try {
1724
- const written = Buffer.isBuffer(this._writingBuf) ? fs12.writeSync(this.fd, this._writingBuf) : fs12.writeSync(this.fd, this._writingBuf, "utf8");
1724
+ const written = Buffer.isBuffer(this._writingBuf) ? fs16.writeSync(this.fd, this._writingBuf) : fs16.writeSync(this.fd, this._writingBuf, "utf8");
1725
1725
  release(null, written);
1726
1726
  } catch (err) {
1727
1727
  release(err);
1728
1728
  }
1729
1729
  } else {
1730
- fs12.write(this.fd, this._writingBuf, release);
1730
+ fs16.write(this.fd, this._writingBuf, release);
1731
1731
  }
1732
1732
  }
1733
1733
  function actualWriteBuffer() {
@@ -1736,7 +1736,7 @@ var require_sonic_boom = __commonJS({
1736
1736
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
1737
1737
  if (this.sync) {
1738
1738
  try {
1739
- const written = fs12.writeSync(this.fd, this._writingBuf);
1739
+ const written = fs16.writeSync(this.fd, this._writingBuf);
1740
1740
  release(null, written);
1741
1741
  } catch (err) {
1742
1742
  release(err);
@@ -1745,7 +1745,7 @@ var require_sonic_boom = __commonJS({
1745
1745
  if (kCopyBuffer) {
1746
1746
  this._writingBuf = Buffer.from(this._writingBuf);
1747
1747
  }
1748
- fs12.write(this.fd, this._writingBuf, release);
1748
+ fs16.write(this.fd, this._writingBuf, release);
1749
1749
  }
1750
1750
  }
1751
1751
  function actualClose(sonic) {
@@ -1761,12 +1761,12 @@ var require_sonic_boom = __commonJS({
1761
1761
  sonic._lens = [];
1762
1762
  assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
1763
1763
  try {
1764
- fs12.fsync(sonic.fd, closeWrapped);
1764
+ fs16.fsync(sonic.fd, closeWrapped);
1765
1765
  } catch {
1766
1766
  }
1767
1767
  function closeWrapped() {
1768
1768
  if (sonic.fd !== 1 && sonic.fd !== 2) {
1769
- fs12.close(sonic.fd, done);
1769
+ fs16.close(sonic.fd, done);
1770
1770
  } else {
1771
1771
  done();
1772
1772
  }
@@ -2497,7 +2497,7 @@ var require_transport = __commonJS({
2497
2497
  stream.flushSync();
2498
2498
  }
2499
2499
  function transport(fullOptions) {
2500
- const { pipeline, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
2500
+ const { pipeline: pipeline2, targets, levels, dedupe, worker = {}, caller = getCallers(), sync = false } = fullOptions;
2501
2501
  const options = {
2502
2502
  ...fullOptions.options
2503
2503
  };
@@ -2525,9 +2525,9 @@ var require_transport = __commonJS({
2525
2525
  };
2526
2526
  });
2527
2527
  });
2528
- } else if (pipeline) {
2528
+ } else if (pipeline2) {
2529
2529
  target = bundlerOverrides["pino-worker"] || join2(__dirname, "worker.js");
2530
- options.pipelines = [pipeline.map((dest) => {
2530
+ options.pipelines = [pipeline2.map((dest) => {
2531
2531
  return {
2532
2532
  ...dest,
2533
2533
  target: fixTarget(dest.target)
@@ -4130,7 +4130,7 @@ var require_multistream = __commonJS({
4130
4130
  var require_pino = __commonJS({
4131
4131
  "../node_modules/.pnpm/pino@9.14.0/node_modules/pino/pino.js"(exports2, module2) {
4132
4132
  "use strict";
4133
- var os9 = require("os");
4133
+ var os11 = require("os");
4134
4134
  var stdSerializers = require_pino_std_serializers();
4135
4135
  var caller = require_caller();
4136
4136
  var redaction = require_redaction();
@@ -4177,7 +4177,7 @@ var require_pino = __commonJS({
4177
4177
  } = symbols;
4178
4178
  var { epochTime, nullTime } = time;
4179
4179
  var { pid } = process;
4180
- var hostname = os9.hostname();
4180
+ var hostname = os11.hostname();
4181
4181
  var defaultErrorSerializer = stdSerializers.err;
4182
4182
  var defaultOptions = {
4183
4183
  level: "info",
@@ -6540,7 +6540,7 @@ var require_websocket = __commonJS({
6540
6540
  var net = require("net");
6541
6541
  var tls = require("tls");
6542
6542
  var { randomBytes, createHash } = require("crypto");
6543
- var { Duplex, Readable } = require("stream");
6543
+ var { Duplex, Readable: Readable2 } = require("stream");
6544
6544
  var { URL: URL2 } = require("url");
6545
6545
  var PerMessageDeflate2 = require_permessage_deflate();
6546
6546
  var Receiver2 = require_receiver();
@@ -7958,6 +7958,7 @@ var import_node_os = __toESM(require("os"), 1);
7958
7958
  var import_node_path = __toESM(require("path"), 1);
7959
7959
  var DEFAULT_PORT = 18790;
7960
7960
  var DEFAULT_HOST = "127.0.0.1";
7961
+ var DEFAULT_CLAWOS_API = "https://api.clawos.chat";
7961
7962
  function parseArgs(argv) {
7962
7963
  const out = {};
7963
7964
  for (let i = 0; i < argv.length; i++) {
@@ -7986,8 +7987,14 @@ function parseArgs(argv) {
7986
7987
  out.logLevel = v;
7987
7988
  break;
7988
7989
  }
7989
- case "--server-url":
7990
- out.serverUrl = next();
7990
+ case "--tunnel":
7991
+ out.tunnel = true;
7992
+ break;
7993
+ case "--clawos-api":
7994
+ out.clawosApi = next();
7995
+ break;
7996
+ case "--auth-token":
7997
+ out.authToken = next();
7991
7998
  break;
7992
7999
  case "--help":
7993
8000
  case "-h":
@@ -8029,14 +8036,26 @@ function resolveConfig(opts) {
8029
8036
  const port = args.port ?? (typeof fileCfg.port === "number" ? fileCfg.port : DEFAULT_PORT);
8030
8037
  const host = typeof fileCfg.host === "string" && fileCfg.host || DEFAULT_HOST;
8031
8038
  const logLevel = args.logLevel ?? fileCfg.logLevel ?? "info";
8032
- const serverUrl = args.serverUrl ?? (typeof fileCfg.serverUrl === "string" ? fileCfg.serverUrl : null);
8039
+ const env = opts.env;
8040
+ const fileTunnel = fileCfg.tunnel === true;
8041
+ const fileClawosApi = typeof fileCfg.clawosApi === "string" ? fileCfg.clawosApi : null;
8042
+ const fileAuthToken = typeof fileCfg.authToken === "string" ? fileCfg.authToken : null;
8043
+ const tunnel = args.tunnel === true || fileTunnel || env.CLAWD_TUNNEL === "1";
8044
+ const clawosApi = args.clawosApi ?? env.CLAWOS_API ?? fileClawosApi ?? (tunnel ? DEFAULT_CLAWOS_API : null);
8045
+ const authToken = args.authToken ?? env.CLAWD_AUTH_TOKEN ?? fileAuthToken;
8046
+ const noTunnelPersist = env.CLAWD_NO_TUNNEL_PERSIST === "1" || env.NODE_ENV === "development";
8047
+ const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
8033
8048
  return {
8034
8049
  port,
8035
8050
  host,
8036
8051
  dataDir,
8037
8052
  configPath: args.config ? configPath : import_node_fs.default.existsSync(configPath) ? configPath : null,
8038
8053
  logLevel,
8039
- serverUrl
8054
+ tunnel,
8055
+ clawosApi,
8056
+ authToken,
8057
+ noTunnelPersist,
8058
+ frpcBinary
8040
8059
  };
8041
8060
  }
8042
8061
  var HELP_TEXT = `clawd [options]
@@ -8044,14 +8063,20 @@ var HELP_TEXT = `clawd [options]
8044
8063
  --data-dir <path> \u6570\u636E\u76EE\u5F55\uFF0C\u9ED8\u8BA4 ~/.clawd
8045
8064
  --config <path> \u914D\u7F6E\u6587\u4EF6\uFF0C\u9ED8\u8BA4 <data-dir>/config.json
8046
8065
  --log-level <level> debug / info / warn / error\uFF08\u9ED8\u8BA4 info\uFF09
8047
- --server-url <url> \u53CD\u8FDE\u8FDC\u7AEF WS server\uFF08v2 \u9884\u7559\uFF09
8066
+ --tunnel \u542F\u7528 frp tunnel \u628A\u672C\u5730\u7AEF\u53E3\u66B4\u9732\u5230\u516C\u7F51
8067
+ --clawos-api <url> tunnel register \u63A5\u53E3\u7684 base url\uFF08\u9ED8\u8BA4 https://api.clawos.chat\uFF09
8068
+ --auth-token <s> \u6307\u5B9A daemon auth token\uFF1B\u7F3A\u7701\u65F6 tunnel \u6A21\u5F0F\u4ECE ~/.clawd/auth.json \u590D\u7528\uFF0C\u6CA1\u6709\u5C31\u751F\u6210
8048
8069
  --help / -h \u663E\u793A\u5E2E\u52A9
8049
8070
  --version / -v \u663E\u793A\u7248\u672C
8071
+
8072
+ Env (advanced):
8073
+ CLAWD_FRPC_BIN \u81EA\u5E26 frpc \u4E8C\u8FDB\u5236\u8DEF\u5F84\uFF08\u9ED8\u8BA4\u6309\u9700\u4E0B\u8F7D\u5230 ~/.clawd/bin/frpc\uFF09
8074
+ CLAWD_NO_TUNNEL_PERSIST \u542F\u52A8\u65F6\u4E0D\u590D\u7528 ~/.clawd/tunnel.json\uFF08dev only\uFF1BNODE_ENV=development \u81EA\u52A8\u5F00\u542F\uFF09
8050
8075
  `;
8051
8076
 
8052
8077
  // src/index.ts
8053
- var import_node_os8 = __toESM(require("os"), 1);
8054
- var import_node_path11 = __toESM(require("path"), 1);
8078
+ var import_node_os10 = __toESM(require("os"), 1);
8079
+ var import_node_path15 = __toESM(require("path"), 1);
8055
8080
 
8056
8081
  // src/logger.ts
8057
8082
  var import_node_fs2 = __toESM(require("fs"), 1);
@@ -8642,8 +8667,8 @@ function getErrorMap() {
8642
8667
 
8643
8668
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
8644
8669
  var makeIssue = (params) => {
8645
- const { data, path: path12, errorMaps, issueData } = params;
8646
- const fullPath = [...path12, ...issueData.path || []];
8670
+ const { data, path: path16, errorMaps, issueData } = params;
8671
+ const fullPath = [...path16, ...issueData.path || []];
8647
8672
  const fullIssue = {
8648
8673
  ...issueData,
8649
8674
  path: fullPath
@@ -8759,11 +8784,11 @@ var errorUtil;
8759
8784
 
8760
8785
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
8761
8786
  var ParseInputLazyPath = class {
8762
- constructor(parent, value, path12, key) {
8787
+ constructor(parent, value, path16, key) {
8763
8788
  this._cachedPath = [];
8764
8789
  this.parent = parent;
8765
8790
  this.data = value;
8766
- this._path = path12;
8791
+ this._path = path16;
8767
8792
  this._key = key;
8768
8793
  }
8769
8794
  get path() {
@@ -12591,6 +12616,22 @@ var RecentDirEntrySchema = external_exports.object({
12591
12616
  var HistoryRecentDirsResponseSchema = external_exports.object({
12592
12617
  dirs: external_exports.array(RecentDirEntrySchema)
12593
12618
  });
12619
+ var AuthRequestFrameSchema = external_exports.object({
12620
+ type: external_exports.literal("auth"),
12621
+ token: external_exports.string().min(1),
12622
+ scheme: external_exports.literal("bearer").optional()
12623
+ });
12624
+ var AuthOkFrameSchema = external_exports.object({
12625
+ type: external_exports.literal("auth:ok"),
12626
+ version: external_exports.string().min(1).optional(),
12627
+ protocolVersion: external_exports.number().int().nonnegative().optional()
12628
+ });
12629
+ var TunnelExitedEventSchema = external_exports.object({
12630
+ type: external_exports.literal("tunnel:exited"),
12631
+ code: external_exports.number().int().nullable(),
12632
+ subdomain: external_exports.string().nullable(),
12633
+ url: external_exports.string().nullable()
12634
+ });
12594
12635
 
12595
12636
  // ../protocol/src/frames.ts
12596
12637
  var PROTOCOL_VERSION = 1;
@@ -14005,11 +14046,11 @@ Diff.prototype = {
14005
14046
  }
14006
14047
  }
14007
14048
  },
14008
- addToPath: function addToPath(path12, added, removed, oldPosInc, options) {
14009
- var last = path12.lastComponent;
14049
+ addToPath: function addToPath(path16, added, removed, oldPosInc, options) {
14050
+ var last = path16.lastComponent;
14010
14051
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
14011
14052
  return {
14012
- oldPos: path12.oldPos + oldPosInc,
14053
+ oldPos: path16.oldPos + oldPosInc,
14013
14054
  lastComponent: {
14014
14055
  count: last.count + 1,
14015
14056
  added,
@@ -14019,7 +14060,7 @@ Diff.prototype = {
14019
14060
  };
14020
14061
  } else {
14021
14062
  return {
14022
- oldPos: path12.oldPos + oldPosInc,
14063
+ oldPos: path16.oldPos + oldPosInc,
14023
14064
  lastComponent: {
14024
14065
  count: 1,
14025
14066
  added,
@@ -14862,10 +14903,10 @@ function attachmentToHistoryMessage(o, ts) {
14862
14903
  const memories = raw.map((m) => {
14863
14904
  if (!m || typeof m !== "object") return null;
14864
14905
  const rec = m;
14865
- const path12 = typeof rec.path === "string" ? rec.path : null;
14906
+ const path16 = typeof rec.path === "string" ? rec.path : null;
14866
14907
  const content = typeof rec.content === "string" ? rec.content : null;
14867
- if (!path12 || content == null) return null;
14868
- const entry = { path: path12, content };
14908
+ if (!path16 || content == null) return null;
14909
+ const entry = { path: path16, content };
14869
14910
  if (typeof rec.mtimeMs === "number") entry.mtimeMs = rec.mtimeMs;
14870
14911
  return entry;
14871
14912
  }).filter((m) => m !== null);
@@ -15601,10 +15642,10 @@ function parseAttachment(obj) {
15601
15642
  const memories = raw.map((m) => {
15602
15643
  if (!m || typeof m !== "object") return null;
15603
15644
  const rec = m;
15604
- const path12 = typeof rec.path === "string" ? rec.path : null;
15645
+ const path16 = typeof rec.path === "string" ? rec.path : null;
15605
15646
  const content = typeof rec.content === "string" ? rec.content : null;
15606
- if (!path12 || content == null) return null;
15607
- const out = { path: path12, content };
15647
+ if (!path16 || content == null) return null;
15648
+ const out = { path: path16, content };
15608
15649
  if (typeof rec.mtimeMs === "number") out.mtimeMs = rec.mtimeMs;
15609
15650
  return out;
15610
15651
  }).filter((m) => m !== null);
@@ -16217,7 +16258,7 @@ var LocalWsServer = class {
16217
16258
  this.logger?.error("ws server error", { err: err.message });
16218
16259
  reject(err);
16219
16260
  });
16220
- wss.on("connection", (socket) => this.onConnection(socket));
16261
+ wss.on("connection", (socket, req) => this.onConnection(socket, req?.socket?.remoteAddress));
16221
16262
  this.wss = wss;
16222
16263
  });
16223
16264
  }
@@ -16246,13 +16287,37 @@ var LocalWsServer = class {
16246
16287
  }
16247
16288
  }
16248
16289
  }
16290
+ // 广播给所有已连接客户端(无视 session 订阅)。
16291
+ // 用于 daemon 级事件,例如 tunnel:exited。未通过 auth 的连接被自动跳过。
16292
+ broadcastAll(frame) {
16293
+ const gate = this.opts.authGate;
16294
+ for (const c of this.clients.values()) {
16295
+ if (gate && !gate.isAuthed(c.handle.id)) continue;
16296
+ this.safeSend(c.ws, frame);
16297
+ }
16298
+ }
16249
16299
  firstSubscriber(sessionId) {
16250
16300
  for (const c of this.clients.values()) {
16251
16301
  if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
16252
16302
  }
16253
16303
  return null;
16254
16304
  }
16255
- onConnection(socket) {
16305
+ // AuthGate 用:按 client id 关闭连接(替代之前的反射访问)
16306
+ closeClient(clientId, code, reason) {
16307
+ const c = this.clients.get(clientId);
16308
+ if (!c) return;
16309
+ try {
16310
+ c.ws.close(code, reason);
16311
+ } catch {
16312
+ }
16313
+ }
16314
+ // AuthGate 用:按 client id 发送一帧(auth:ok 等)
16315
+ sendToClient(clientId, frame) {
16316
+ const c = this.clients.get(clientId);
16317
+ if (!c) return;
16318
+ this.safeSend(c.ws, frame);
16319
+ }
16320
+ onConnection(socket, remoteAddress) {
16256
16321
  const id = v4_default();
16257
16322
  const subscribedSessions = /* @__PURE__ */ new Set();
16258
16323
  const handle = {
@@ -16268,27 +16333,38 @@ var LocalWsServer = class {
16268
16333
  }
16269
16334
  }, this.pingIntervalMs);
16270
16335
  this.clients.set(id, { handle, ws: socket, pingTimer });
16271
- this.logger?.info("client connected", { clientId: id, total: this.clients.size });
16336
+ this.logger?.info("client connected", { clientId: id, total: this.clients.size, remoteAddress });
16337
+ const authGate = this.opts.authGate;
16338
+ let authed = true;
16339
+ if (authGate) {
16340
+ const authHandle = { id, remoteAddress };
16341
+ authed = authGate.registerConnection(authHandle);
16342
+ }
16272
16343
  try {
16273
- const readyFrame = this.opts.readyFrameBuilder();
16274
- this.safeSend(socket, { type: "ready", ...readyFrame });
16344
+ if (authed) {
16345
+ const readyFrame = this.opts.readyFrameBuilder();
16346
+ this.safeSend(socket, { type: "ready", ...readyFrame });
16347
+ } else {
16348
+ this.safeSend(socket, { type: "ready", protocolVersion: this.opts.protocolVersion });
16349
+ }
16275
16350
  } catch (err) {
16276
16351
  this.logger?.warn("ready frame build failed", { err: err.message });
16277
16352
  }
16278
16353
  socket.on("message", (data) => {
16279
- this.onMessage(handle, data);
16354
+ this.onMessage(handle, data, remoteAddress);
16280
16355
  });
16281
16356
  socket.on("close", () => {
16282
16357
  const c = this.clients.get(id);
16283
16358
  if (c?.pingTimer) clearInterval(c.pingTimer);
16284
16359
  this.clients.delete(id);
16360
+ authGate?.unregister(id);
16285
16361
  this.logger?.info("client disconnected", { clientId: id, remaining: this.clients.size });
16286
16362
  });
16287
16363
  socket.on("error", (err) => {
16288
16364
  this.logger?.warn("client socket error", { clientId: id, err: err.message });
16289
16365
  });
16290
16366
  }
16291
- onMessage(client, data) {
16367
+ onMessage(client, data, remoteAddress) {
16292
16368
  let frame;
16293
16369
  try {
16294
16370
  frame = JSON.parse(data.toString());
@@ -16296,6 +16372,22 @@ var LocalWsServer = class {
16296
16372
  this.safeSend(this.clients.get(client.id).ws, { type: "error", code: "VALIDATION_ERROR", message: "invalid json" });
16297
16373
  return;
16298
16374
  }
16375
+ const authGate = this.opts.authGate;
16376
+ if (authGate) {
16377
+ const wasAuthed = authGate.isAuthed(client.id);
16378
+ const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
16379
+ if (verdict !== "pass") {
16380
+ if (!wasAuthed && authGate.isAuthed(client.id)) {
16381
+ try {
16382
+ const full = this.opts.readyFrameBuilder();
16383
+ this.safeSend(this.clients.get(client.id).ws, { type: "ready", ...full });
16384
+ } catch (err) {
16385
+ this.logger?.warn("post-auth ready frame build failed", { err: err.message });
16386
+ }
16387
+ }
16388
+ return;
16389
+ }
16390
+ }
16299
16391
  if (frame.type === "ping") {
16300
16392
  this.safeSend(this.clients.get(client.id).ws, { type: "pong", at: Date.now() });
16301
16393
  return;
@@ -16340,6 +16432,99 @@ var LocalWsServer = class {
16340
16432
  }
16341
16433
  };
16342
16434
 
16435
+ // src/transport/auth.ts
16436
+ var AuthGate = class {
16437
+ states = /* @__PURE__ */ new Map();
16438
+ opts;
16439
+ constructor(opts) {
16440
+ this.opts = { ...opts, timeoutMs: opts.timeoutMs ?? 5e3 };
16441
+ }
16442
+ // 新连接接入;返回 isAuthed(豁免时直接 true)
16443
+ registerConnection(handle) {
16444
+ const enforce = this.opts.shouldEnforce(handle.remoteAddress);
16445
+ if (!enforce) {
16446
+ this.states.set(handle.id, { authed: true, enforce: false, timer: null });
16447
+ return true;
16448
+ }
16449
+ const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
16450
+ const timer = setT(() => {
16451
+ const st = this.states.get(handle.id);
16452
+ if (!st || st.authed) return;
16453
+ this.opts.closeConnection(handle, 1008, "auth timeout");
16454
+ }, this.opts.timeoutMs);
16455
+ this.states.set(handle.id, { authed: false, enforce: true, timer });
16456
+ return false;
16457
+ }
16458
+ unregister(handleId) {
16459
+ const st = this.states.get(handleId);
16460
+ if (st?.timer != null) {
16461
+ const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
16462
+ clear(st.timer);
16463
+ }
16464
+ this.states.delete(handleId);
16465
+ }
16466
+ isAuthed(handleId) {
16467
+ return this.states.get(handleId)?.authed ?? false;
16468
+ }
16469
+ // 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
16470
+ // - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
16471
+ // - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接)
16472
+ // - pass:已 authed,调用方继续走业务路由
16473
+ handleFrame(handle, frame) {
16474
+ const st = this.states.get(handle.id);
16475
+ if (!st) return "reject";
16476
+ if (st.authed) return "pass";
16477
+ const parsed = AuthRequestFrameSchema.safeParse(frame);
16478
+ if (!parsed.success) {
16479
+ const reason = frame.type === "auth" ? "auth failed" : "auth required";
16480
+ this.opts.closeConnection(handle, 1008, reason);
16481
+ this.markFailed(handle.id);
16482
+ return frame.type === "auth" ? "consumed" : "reject";
16483
+ }
16484
+ if (!this.opts.expectedToken) {
16485
+ this.opts.closeConnection(handle, 1008, "auth not configured");
16486
+ this.markFailed(handle.id);
16487
+ return "consumed";
16488
+ }
16489
+ if (!constantTimeEqual(parsed.data.token, this.opts.expectedToken)) {
16490
+ this.opts.closeConnection(handle, 1008, "auth failed");
16491
+ this.markFailed(handle.id);
16492
+ return "consumed";
16493
+ }
16494
+ if (st.timer != null) {
16495
+ const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
16496
+ clear(st.timer);
16497
+ }
16498
+ st.authed = true;
16499
+ st.timer = null;
16500
+ this.opts.sendOk(handle, { type: "auth:ok" });
16501
+ return "consumed";
16502
+ }
16503
+ markFailed(handleId) {
16504
+ const st = this.states.get(handleId);
16505
+ if (!st) return;
16506
+ if (st.timer != null) {
16507
+ const clear = this.opts.clearTimer ?? ((h) => clearTimeout(h));
16508
+ clear(st.timer);
16509
+ }
16510
+ st.timer = null;
16511
+ }
16512
+ };
16513
+ function constantTimeEqual(a, b) {
16514
+ if (a.length !== b.length) return false;
16515
+ let diff2 = 0;
16516
+ for (let i = 0; i < a.length; i++) diff2 |= a.charCodeAt(i) ^ b.charCodeAt(i);
16517
+ return diff2 === 0;
16518
+ }
16519
+ function buildShouldEnforce(opts) {
16520
+ if (opts.tunnel) return () => true;
16521
+ return (addr) => !isLocalhost(addr);
16522
+ }
16523
+ function isLocalhost(addr) {
16524
+ if (!addr) return false;
16525
+ return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
16526
+ }
16527
+
16343
16528
  // src/discovery/state-file.ts
16344
16529
  var import_node_fs10 = __toESM(require("fs"), 1);
16345
16530
  var import_node_path9 = __toESM(require("path"), 1);
@@ -16399,17 +16584,532 @@ var StateFileManager = class {
16399
16584
  }
16400
16585
  };
16401
16586
 
16402
- // src/workspace/git.ts
16403
- var import_node_child_process3 = require("child_process");
16587
+ // src/tunnel/tunnel-manager.ts
16588
+ var import_node_fs13 = __toESM(require("fs"), 1);
16589
+ var import_node_path12 = __toESM(require("path"), 1);
16590
+ var import_node_child_process4 = require("child_process");
16591
+
16592
+ // src/tunnel/tunnel-store.ts
16404
16593
  var import_node_fs11 = __toESM(require("fs"), 1);
16405
- var import_node_os7 = __toESM(require("os"), 1);
16406
16594
  var import_node_path10 = __toESM(require("path"), 1);
16595
+ var TunnelStore = class {
16596
+ constructor(filePath) {
16597
+ this.filePath = filePath;
16598
+ }
16599
+ filePath;
16600
+ async get() {
16601
+ try {
16602
+ const raw = await import_node_fs11.default.promises.readFile(this.filePath, "utf8");
16603
+ const obj = JSON.parse(raw);
16604
+ if (!isPersistedTunnel(obj)) return null;
16605
+ return obj;
16606
+ } catch (err) {
16607
+ const code = err?.code;
16608
+ if (code === "ENOENT") return null;
16609
+ return null;
16610
+ }
16611
+ }
16612
+ async set(v) {
16613
+ const dir = import_node_path10.default.dirname(this.filePath);
16614
+ await import_node_fs11.default.promises.mkdir(dir, { recursive: true });
16615
+ const data = JSON.stringify(v, null, 2);
16616
+ const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
16617
+ await import_node_fs11.default.promises.writeFile(tmp, data, { mode: 384 });
16618
+ if (process.platform !== "win32") {
16619
+ try {
16620
+ await import_node_fs11.default.promises.chmod(tmp, 384);
16621
+ } catch {
16622
+ }
16623
+ }
16624
+ await import_node_fs11.default.promises.rename(tmp, this.filePath);
16625
+ }
16626
+ async clear() {
16627
+ try {
16628
+ await import_node_fs11.default.promises.unlink(this.filePath);
16629
+ } catch (err) {
16630
+ const code = err?.code;
16631
+ if (code !== "ENOENT") throw err;
16632
+ }
16633
+ }
16634
+ };
16635
+ function isPersistedTunnel(o) {
16636
+ if (!o || typeof o !== "object") return false;
16637
+ const r = o;
16638
+ return typeof r.subdomain === "string" && typeof r.frpsHost === "string" && typeof r.frpsPort === "number" && typeof r.frpsToken === "string" && typeof r.url === "string" && typeof r.registeredAt === "string";
16639
+ }
16640
+
16641
+ // src/tunnel/register-client.ts
16642
+ var TunnelRegisterError = class extends Error {
16643
+ constructor(message, cause) {
16644
+ super(message);
16645
+ this.cause = cause;
16646
+ this.name = "TunnelRegisterError";
16647
+ }
16648
+ cause;
16649
+ };
16650
+ async function registerTunnel(opts) {
16651
+ const f = opts.fetchImpl ?? globalThis.fetch;
16652
+ if (!f) throw new TunnelRegisterError("fetch is not available (Node >= 18 required)");
16653
+ const url = `${opts.api.replace(/\/+$/, "")}/api/tunnel/register`;
16654
+ const ctrl = new AbortController();
16655
+ const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
16656
+ let res;
16657
+ try {
16658
+ res = await f(url, {
16659
+ method: "POST",
16660
+ headers: { "content-type": "application/json" },
16661
+ body: JSON.stringify({ gatewayCount: 1, deviceKeys: [opts.deviceKey] }),
16662
+ signal: ctrl.signal
16663
+ });
16664
+ } catch (err) {
16665
+ clearTimeout(timer);
16666
+ throw new TunnelRegisterError(`tunnel register network error: ${err.message}`, err);
16667
+ }
16668
+ clearTimeout(timer);
16669
+ if (!res.ok) {
16670
+ let bodyText = "";
16671
+ try {
16672
+ bodyText = (await res.text()).slice(0, 200);
16673
+ } catch {
16674
+ }
16675
+ throw new TunnelRegisterError(`tunnel register failed: HTTP ${res.status} ${bodyText}`);
16676
+ }
16677
+ let payload;
16678
+ try {
16679
+ payload = await res.json();
16680
+ } catch (err) {
16681
+ throw new TunnelRegisterError(`tunnel register: invalid json response`, err);
16682
+ }
16683
+ const tunnels = payload.tunnels;
16684
+ if (!Array.isArray(tunnels) || tunnels.length === 0) {
16685
+ throw new TunnelRegisterError(`tunnel register: missing tunnels in response`);
16686
+ }
16687
+ const t = tunnels[0];
16688
+ if (typeof t.subdomain !== "string" || typeof t.url !== "string" || typeof t.frpsHost !== "string" || typeof t.frpsPort !== "number" || typeof t.token !== "string") {
16689
+ throw new TunnelRegisterError(`tunnel register: malformed tunnel entry`);
16690
+ }
16691
+ return {
16692
+ subdomain: t.subdomain,
16693
+ url: t.url,
16694
+ frpsHost: t.frpsHost,
16695
+ frpsPort: t.frpsPort,
16696
+ frpsToken: t.token
16697
+ };
16698
+ }
16699
+
16700
+ // src/tunnel/frpc-config.ts
16701
+ function buildFrpcToml(input) {
16702
+ const localIP = input.localIP ?? "127.0.0.1";
16703
+ const lines = [];
16704
+ lines.push(`serverAddr = "${escape(input.serverAddr)}"`);
16705
+ lines.push(`serverPort = ${input.serverPort}`);
16706
+ lines.push(`auth.method = "token"`);
16707
+ lines.push(`auth.token = "${escape(input.authToken)}"`);
16708
+ lines.push(`log.level = "${input.logLevel ?? "info"}"`);
16709
+ lines.push("");
16710
+ lines.push("[[proxies]]");
16711
+ lines.push(`name = "${escape(input.proxyName)}"`);
16712
+ lines.push('type = "http"');
16713
+ lines.push(`localIP = "${escape(localIP)}"`);
16714
+ lines.push(`localPort = ${input.localPort}`);
16715
+ lines.push(`subdomain = "${escape(input.subdomain)}"`);
16716
+ return lines.join("\n") + "\n";
16717
+ }
16718
+ function escape(v) {
16719
+ return v.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
16720
+ }
16721
+
16722
+ // src/tunnel/frpc-binary.ts
16723
+ var import_node_fs12 = __toESM(require("fs"), 1);
16724
+ var import_node_os7 = __toESM(require("os"), 1);
16725
+ var import_node_path11 = __toESM(require("path"), 1);
16726
+ var import_node_child_process3 = require("child_process");
16727
+ var import_node_stream = require("stream");
16728
+ var import_promises = require("stream/promises");
16729
+ var FRPC_VERSION = "0.68.0";
16730
+ var UnsupportedPlatformError = class extends Error {
16731
+ constructor(platform, arch) {
16732
+ super(`unsupported frpc platform: ${platform}/${arch}`);
16733
+ this.platform = platform;
16734
+ this.arch = arch;
16735
+ }
16736
+ platform;
16737
+ arch;
16738
+ };
16739
+ function detectPlatform(opts = {}) {
16740
+ const platform = opts.platform ?? process.platform;
16741
+ const arch = opts.arch ?? process.arch;
16742
+ let oss;
16743
+ if (platform === "darwin") oss = "darwin";
16744
+ else if (platform === "linux") oss = "linux";
16745
+ else throw new UnsupportedPlatformError(platform, arch);
16746
+ let a;
16747
+ if (arch === "arm64" || arch === "aarch64") a = "arm64";
16748
+ else if (arch === "x64" || arch === "amd64") a = "amd64";
16749
+ else throw new UnsupportedPlatformError(platform, arch);
16750
+ return { os: oss, arch: a };
16751
+ }
16752
+ function frpcDownloadUrl(version2, p) {
16753
+ return `https://github.com/fatedier/frp/releases/download/v${version2}/frp_${version2}_${p.os}_${p.arch}.tar.gz`;
16754
+ }
16755
+ async function ensureFrpcBinary(opts) {
16756
+ if (opts.override) {
16757
+ if (!import_node_fs12.default.existsSync(opts.override)) {
16758
+ throw new Error(`frpc binary not found at override path: ${opts.override}`);
16759
+ }
16760
+ return opts.override;
16761
+ }
16762
+ const version2 = opts.version ?? FRPC_VERSION;
16763
+ const platform = opts.platform ?? detectPlatform();
16764
+ const binDir = import_node_path11.default.join(opts.dataDir, "bin");
16765
+ import_node_fs12.default.mkdirSync(binDir, { recursive: true });
16766
+ cleanupStaleArtifacts(binDir);
16767
+ const stableBin = import_node_path11.default.join(binDir, "frpc");
16768
+ if (import_node_fs12.default.existsSync(stableBin)) return stableBin;
16769
+ const partialBin = `${stableBin}.partial`;
16770
+ const tarballPath = import_node_path11.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
16771
+ try {
16772
+ const url = frpcDownloadUrl(version2, platform);
16773
+ await downloadToFile(url, tarballPath, opts.fetchImpl);
16774
+ if (opts.installFromTarball) {
16775
+ await opts.installFromTarball(tarballPath, partialBin);
16776
+ } else {
16777
+ await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
16778
+ }
16779
+ import_node_fs12.default.chmodSync(partialBin, 493);
16780
+ import_node_fs12.default.renameSync(partialBin, stableBin);
16781
+ } finally {
16782
+ safeUnlink(tarballPath);
16783
+ safeUnlink(partialBin);
16784
+ }
16785
+ return stableBin;
16786
+ }
16787
+ function cleanupStaleArtifacts(binDir) {
16788
+ let entries;
16789
+ try {
16790
+ entries = import_node_fs12.default.readdirSync(binDir);
16791
+ } catch {
16792
+ return;
16793
+ }
16794
+ for (const name of entries) {
16795
+ if (name.endsWith(".partial") || name.startsWith("extract-")) {
16796
+ const full = import_node_path11.default.join(binDir, name);
16797
+ try {
16798
+ import_node_fs12.default.rmSync(full, { recursive: true, force: true });
16799
+ } catch {
16800
+ }
16801
+ }
16802
+ }
16803
+ }
16804
+ function safeUnlink(p) {
16805
+ try {
16806
+ import_node_fs12.default.unlinkSync(p);
16807
+ } catch {
16808
+ }
16809
+ }
16810
+ async function downloadToFile(url, dest, fetchImpl) {
16811
+ const f = fetchImpl ?? globalThis.fetch;
16812
+ if (!f) throw new Error("fetch is not available (Node >= 18 required)");
16813
+ const res = await f(url, { redirect: "follow" });
16814
+ if (!res.ok || !res.body) {
16815
+ throw new Error(`download failed: ${res.status} ${res.statusText}`);
16816
+ }
16817
+ const out = import_node_fs12.default.createWriteStream(dest);
16818
+ const nodeStream = import_node_stream.Readable.fromWeb(res.body);
16819
+ await (0, import_promises.pipeline)(nodeStream, out);
16820
+ }
16821
+ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
16822
+ const work = import_node_path11.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
16823
+ import_node_fs12.default.mkdirSync(work, { recursive: true });
16824
+ try {
16825
+ await new Promise((resolve, reject) => {
16826
+ const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
16827
+ proc.on("error", reject);
16828
+ proc.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`tar exited ${code}`)));
16829
+ });
16830
+ const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
16831
+ const src = import_node_path11.default.join(work, dirName, "frpc");
16832
+ if (!import_node_fs12.default.existsSync(src)) {
16833
+ throw new Error(`frpc not found inside tarball at ${src}`);
16834
+ }
16835
+ import_node_fs12.default.copyFileSync(src, destBin);
16836
+ } finally {
16837
+ import_node_fs12.default.rmSync(work, { recursive: true, force: true });
16838
+ }
16839
+ }
16840
+
16841
+ // src/tunnel/tunnel-manager.ts
16842
+ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
16843
+ var TunnelManager = class {
16844
+ constructor(deps) {
16845
+ this.deps = deps;
16846
+ this.store = deps.store ?? new TunnelStore(import_node_path12.default.join(deps.dataDir, "tunnel.json"));
16847
+ this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
16848
+ this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
16849
+ }
16850
+ deps;
16851
+ proc = null;
16852
+ current = null;
16853
+ store;
16854
+ ttlMs;
16855
+ startupTimeoutMs;
16856
+ exitHookInstalled = false;
16857
+ // 标记是否在主动 stop 中:true 时不要把 frpc 退出 emit 成 onFrpcExit
16858
+ stopping = false;
16859
+ get url() {
16860
+ return this.current?.url ?? null;
16861
+ }
16862
+ async start(opts) {
16863
+ const { logger } = this.deps;
16864
+ if (!this.deps.noPersist) {
16865
+ const cached = await this.store.get();
16866
+ if (cached && this.isFresh(cached)) {
16867
+ logger?.info("tunnel: trying cached credentials", { subdomain: cached.subdomain });
16868
+ const r2 = await this.tryStart(cached, opts.localPort);
16869
+ if (r2.ok) {
16870
+ this.proc = r2.proc;
16871
+ this.current = cached;
16872
+ this.installProcessExitHandlers();
16873
+ this.attachExitListener(r2.proc);
16874
+ return { url: cached.url, subdomain: cached.subdomain };
16875
+ }
16876
+ logger?.warn("tunnel: cached credentials failed, re-registering", { err: r2.error.message });
16877
+ }
16878
+ }
16879
+ const reg = await (this.deps.registerImpl ?? registerTunnel)({
16880
+ api: this.deps.clawosApi,
16881
+ deviceKey: this.deps.deviceKey
16882
+ });
16883
+ const persisted = {
16884
+ subdomain: reg.subdomain,
16885
+ frpsHost: reg.frpsHost,
16886
+ frpsPort: reg.frpsPort,
16887
+ frpsToken: reg.frpsToken,
16888
+ url: reg.url,
16889
+ registeredAt: (/* @__PURE__ */ new Date()).toISOString()
16890
+ };
16891
+ await this.store.set(persisted);
16892
+ const r = await this.tryStart(persisted, opts.localPort);
16893
+ if (!r.ok) throw r.error;
16894
+ this.proc = r.proc;
16895
+ this.current = persisted;
16896
+ this.installProcessExitHandlers();
16897
+ this.attachExitListener(r.proc);
16898
+ return { url: persisted.url, subdomain: persisted.subdomain };
16899
+ }
16900
+ async stop() {
16901
+ this.stopping = true;
16902
+ const proc = this.proc;
16903
+ this.proc = null;
16904
+ if (!proc) return;
16905
+ proc.kill("SIGTERM");
16906
+ await new Promise((resolve) => {
16907
+ const t = setTimeout(() => {
16908
+ try {
16909
+ proc.kill("SIGKILL");
16910
+ } catch {
16911
+ }
16912
+ resolve();
16913
+ }, 5e3);
16914
+ proc.once("exit", () => {
16915
+ clearTimeout(t);
16916
+ resolve();
16917
+ });
16918
+ });
16919
+ }
16920
+ // 同步杀子进程:父进程崩溃 / SIGHUP / process.exit 路径下使用,
16921
+ // async stop() 跑不完时的兜底,避免留下孤儿 frpc。
16922
+ killSync() {
16923
+ const proc = this.proc;
16924
+ this.proc = null;
16925
+ if (!proc) return;
16926
+ try {
16927
+ proc.kill("SIGTERM");
16928
+ } catch {
16929
+ }
16930
+ }
16931
+ isFresh(t) {
16932
+ const age = Date.now() - new Date(t.registeredAt).getTime();
16933
+ return Number.isFinite(age) && age < this.ttlMs;
16934
+ }
16935
+ attachExitListener(proc) {
16936
+ proc.on("exit", (code) => {
16937
+ this.deps.logger?.warn("frpc exited", { code });
16938
+ if (this.stopping) return;
16939
+ const cur = this.current;
16940
+ this.proc = null;
16941
+ this.deps.onFrpcExit?.({
16942
+ code,
16943
+ subdomain: cur?.subdomain ?? null,
16944
+ url: cur?.url ?? null
16945
+ });
16946
+ });
16947
+ }
16948
+ installProcessExitHandlers() {
16949
+ if (this.exitHookInstalled) return;
16950
+ if (this.deps.installProcessExitHandlers !== true) return;
16951
+ this.exitHookInstalled = true;
16952
+ const sync = () => this.killSync();
16953
+ process.once("exit", sync);
16954
+ process.once("SIGHUP", sync);
16955
+ process.once("uncaughtException", sync);
16956
+ }
16957
+ async tryStart(t, localPort) {
16958
+ try {
16959
+ const frpcBin = await (this.deps.ensureFrpcImpl ?? ensureFrpcBinary)({
16960
+ dataDir: this.deps.dataDir,
16961
+ override: this.deps.frpcBinaryOverride ?? void 0
16962
+ });
16963
+ const tomlPath = import_node_path12.default.join(this.deps.dataDir, "frpc.toml");
16964
+ const toml = buildFrpcToml({
16965
+ serverAddr: t.frpsHost,
16966
+ serverPort: t.frpsPort,
16967
+ authToken: t.frpsToken,
16968
+ proxyName: "clawd",
16969
+ subdomain: t.subdomain,
16970
+ localPort,
16971
+ logLevel: "info"
16972
+ });
16973
+ await import_node_fs13.default.promises.writeFile(tomlPath, toml, { mode: 384 });
16974
+ const proc = (this.deps.spawnImpl ?? import_node_child_process4.spawn)(frpcBin, ["-c", tomlPath], {
16975
+ stdio: ["ignore", "pipe", "pipe"]
16976
+ });
16977
+ const logFilePath = import_node_path12.default.join(this.deps.dataDir, "frpc.log");
16978
+ const logStream = import_node_fs13.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
16979
+ logStream.on("error", () => {
16980
+ });
16981
+ const tee = (chunk) => {
16982
+ logStream.write(String(chunk));
16983
+ };
16984
+ proc.stdout?.on("data", tee);
16985
+ proc.stderr?.on("data", tee);
16986
+ proc.once("exit", () => {
16987
+ logStream.end();
16988
+ });
16989
+ const ready = await waitForFrpcReady(proc, this.startupTimeoutMs);
16990
+ if (!ready.ok) {
16991
+ try {
16992
+ proc.kill("SIGTERM");
16993
+ } catch {
16994
+ }
16995
+ return { ok: false, error: new Error(ready.error) };
16996
+ }
16997
+ this.deps.logger?.info("tunnel: frpc up", { subdomain: t.subdomain, url: t.url });
16998
+ return { ok: true, proc };
16999
+ } catch (err) {
17000
+ if (err instanceof TunnelRegisterError) throw err;
17001
+ return { ok: false, error: err };
17002
+ }
17003
+ }
17004
+ };
17005
+ async function waitForFrpcReady(proc, timeoutMs) {
17006
+ return new Promise((resolve) => {
17007
+ let settled = false;
17008
+ let buf = "";
17009
+ const finish = (r) => {
17010
+ if (settled) return;
17011
+ settled = true;
17012
+ cleanup();
17013
+ resolve(r);
17014
+ };
17015
+ const onData = (chunk) => {
17016
+ buf += String(chunk);
17017
+ if (/start proxy success/.test(buf) || /\[clawd\] success/.test(buf)) {
17018
+ finish({ ok: true });
17019
+ }
17020
+ if (/login to server failed|authentication failed/i.test(buf)) {
17021
+ finish({ ok: false, error: `frpc handshake failed: ${buf.trim().slice(-200)}` });
17022
+ }
17023
+ };
17024
+ const onExit = (code) => {
17025
+ finish({ ok: false, error: `frpc exited before ready (code=${code})` });
17026
+ };
17027
+ const onErr = (err) => finish({ ok: false, error: `frpc spawn error: ${err.message}` });
17028
+ const cleanup = () => {
17029
+ proc.stdout?.off("data", onData);
17030
+ proc.stderr?.off("data", onData);
17031
+ proc.off("exit", onExit);
17032
+ proc.off("error", onErr);
17033
+ clearTimeout(timer);
17034
+ };
17035
+ proc.stdout?.on("data", onData);
17036
+ proc.stderr?.on("data", onData);
17037
+ proc.on("exit", onExit);
17038
+ proc.on("error", onErr);
17039
+ const timer = setTimeout(() => {
17040
+ finish({ ok: false, error: `frpc startup timeout after ${timeoutMs}ms` });
17041
+ }, timeoutMs);
17042
+ });
17043
+ }
17044
+
17045
+ // src/tunnel/device-key.ts
17046
+ var import_node_os8 = __toESM(require("os"), 1);
17047
+ var import_node_crypto3 = __toESM(require("crypto"), 1);
17048
+ var DERIVE_SALT = "clawd-tunnel-device-v1";
17049
+ function deriveStableDeviceKey(opts = {}) {
17050
+ const hostname = opts.hostname ?? import_node_os8.default.hostname();
17051
+ const uid = opts.uid ?? (typeof import_node_os8.default.userInfo === "function" ? import_node_os8.default.userInfo().uid : 0);
17052
+ const input = `${hostname}::${uid}`;
17053
+ return import_node_crypto3.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
17054
+ }
17055
+
17056
+ // src/auth-store.ts
17057
+ var import_node_fs14 = __toESM(require("fs"), 1);
17058
+ var import_node_path13 = __toESM(require("path"), 1);
17059
+ var import_node_crypto4 = __toESM(require("crypto"), 1);
17060
+ var AUTH_FILE_NAME = "auth.json";
17061
+ function authFilePath(dataDir) {
17062
+ return import_node_path13.default.join(dataDir, AUTH_FILE_NAME);
17063
+ }
17064
+ function loadOrCreateAuthToken(opts) {
17065
+ const file = authFilePath(opts.dataDir);
17066
+ const existing = readAuthFile(file);
17067
+ if (existing && existing.token) return existing.token;
17068
+ const token = (opts.generate ?? defaultGenerate)();
17069
+ const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
17070
+ writeAuthFile(file, { token, createdAt: now.toISOString() });
17071
+ return token;
17072
+ }
17073
+ function defaultGenerate() {
17074
+ return import_node_crypto4.default.randomBytes(32).toString("base64url");
17075
+ }
17076
+ function readAuthFile(file) {
17077
+ try {
17078
+ const raw = import_node_fs14.default.readFileSync(file, "utf8");
17079
+ const parsed = JSON.parse(raw);
17080
+ if (typeof parsed?.token === "string" && parsed.token.length > 0) {
17081
+ return {
17082
+ token: parsed.token,
17083
+ createdAt: typeof parsed.createdAt === "string" ? parsed.createdAt : (/* @__PURE__ */ new Date(0)).toISOString()
17084
+ };
17085
+ }
17086
+ return null;
17087
+ } catch (err) {
17088
+ const code = err?.code;
17089
+ if (code === "ENOENT") return null;
17090
+ return null;
17091
+ }
17092
+ }
17093
+ function writeAuthFile(file, content) {
17094
+ import_node_fs14.default.mkdirSync(import_node_path13.default.dirname(file), { recursive: true });
17095
+ import_node_fs14.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
17096
+ try {
17097
+ import_node_fs14.default.chmodSync(file, 384);
17098
+ } catch {
17099
+ }
17100
+ }
17101
+
17102
+ // src/workspace/git.ts
17103
+ var import_node_child_process5 = require("child_process");
17104
+ var import_node_fs15 = __toESM(require("fs"), 1);
17105
+ var import_node_os9 = __toESM(require("os"), 1);
17106
+ var import_node_path14 = __toESM(require("path"), 1);
16407
17107
  var import_node_util = require("util");
16408
- var pexec = (0, import_node_util.promisify)(import_node_child_process3.execFile);
17108
+ var pexec = (0, import_node_util.promisify)(import_node_child_process5.execFile);
16409
17109
  function normalizePath(p) {
16410
- const resolved = import_node_path10.default.resolve(p);
17110
+ const resolved = import_node_path14.default.resolve(p);
16411
17111
  try {
16412
- return import_node_fs11.default.realpathSync(resolved);
17112
+ return import_node_fs15.default.realpathSync(resolved);
16413
17113
  } catch {
16414
17114
  return resolved;
16415
17115
  }
@@ -16496,7 +17196,7 @@ function sanitizeLabel(raw) {
16496
17196
  function computePrefix() {
16497
17197
  let username;
16498
17198
  try {
16499
- username = import_node_os7.default.userInfo().username;
17199
+ username = import_node_os9.default.userInfo().username;
16500
17200
  } catch {
16501
17201
  username = void 0;
16502
17202
  }
@@ -16510,13 +17210,13 @@ function flattenToDirName(branch) {
16510
17210
  }
16511
17211
  function encodeClaudeProjectDir(absPath) {
16512
17212
  if (!absPath || typeof absPath !== "string") return "";
16513
- let canonical = import_node_path10.default.resolve(absPath);
17213
+ let canonical = import_node_path14.default.resolve(absPath);
16514
17214
  try {
16515
- canonical = import_node_fs11.default.realpathSync(canonical);
17215
+ canonical = import_node_fs15.default.realpathSync(canonical);
16516
17216
  } catch {
16517
17217
  try {
16518
- const parent = import_node_fs11.default.realpathSync(import_node_path10.default.dirname(canonical));
16519
- canonical = import_node_path10.default.join(parent, import_node_path10.default.basename(canonical));
17218
+ const parent = import_node_fs15.default.realpathSync(import_node_path14.default.dirname(canonical));
17219
+ canonical = import_node_path14.default.join(parent, import_node_path14.default.basename(canonical));
16520
17220
  } catch {
16521
17221
  }
16522
17222
  }
@@ -16540,11 +17240,11 @@ async function createWorktree(input) {
16540
17240
  if (!isGitRoot) {
16541
17241
  throw new Error(`\u76EE\u5F55 ${cwd} \u4E0D\u662F git repo \u6839`);
16542
17242
  }
16543
- const parent = import_node_path10.default.dirname(import_node_path10.default.resolve(cwd));
16544
- if (parent === "/" || parent === import_node_path10.default.resolve(cwd)) {
17243
+ const parent = import_node_path14.default.dirname(import_node_path14.default.resolve(cwd));
17244
+ if (parent === "/" || parent === import_node_path14.default.resolve(cwd)) {
16545
17245
  throw new Error("repo \u5728\u78C1\u76D8\u6839\u76EE\u5F55\uFF0C\u65E0\u6CD5\u5728\u540C\u7EA7\u521B\u5EFA worktree");
16546
17246
  }
16547
- const worktreeRoot = import_node_path10.default.join(parent, dirName);
17247
+ const worktreeRoot = import_node_path14.default.join(parent, dirName);
16548
17248
  try {
16549
17249
  await pexec("git", ["-C", cwd, "rev-parse", "--verify", `refs/heads/${baseBranch}`], {
16550
17250
  timeout: 3e3
@@ -16561,7 +17261,7 @@ async function createWorktree(input) {
16561
17261
  const msg = err.message;
16562
17262
  if (msg.startsWith("\u5206\u652F ")) throw err;
16563
17263
  }
16564
- if (import_node_fs11.default.existsSync(worktreeRoot)) {
17264
+ if (import_node_fs15.default.existsSync(worktreeRoot)) {
16565
17265
  throw new Error(`\u76EE\u5F55 ${worktreeRoot} \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E00\u4E2A label \u6216\u6E05\u7406\u540E\u91CD\u8BD5`);
16566
17266
  }
16567
17267
  try {
@@ -16591,8 +17291,8 @@ async function removeWorktree(input) {
16591
17291
  );
16592
17292
  const gitCommonDir = stdout.trim();
16593
17293
  if (!gitCommonDir) throw new Error("empty git-common-dir");
16594
- const absGitCommon = import_node_path10.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path10.default.resolve(worktreeRoot, gitCommonDir);
16595
- repoRoot = import_node_path10.default.dirname(absGitCommon);
17294
+ const absGitCommon = import_node_path14.default.isAbsolute(gitCommonDir) ? gitCommonDir : import_node_path14.default.resolve(worktreeRoot, gitCommonDir);
17295
+ repoRoot = import_node_path14.default.dirname(absGitCommon);
16596
17296
  } catch {
16597
17297
  repoRoot = null;
16598
17298
  }
@@ -16604,7 +17304,7 @@ async function removeWorktree(input) {
16604
17304
  } catch (err) {
16605
17305
  const stderr = err.stderr || err.message;
16606
17306
  const lower = stderr.toLowerCase();
16607
- const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs11.default.existsSync(worktreeRoot);
17307
+ const vanished = lower.includes("not a working tree") || lower.includes("is not a working tree") || !import_node_fs15.default.existsSync(worktreeRoot);
16608
17308
  if (!vanished) {
16609
17309
  const firstLine = stderr.split("\n")[0].trim();
16610
17310
  throw new Error(`\u6E05\u7406 worktree \u5931\u8D25\uFF1A${firstLine}`);
@@ -16625,10 +17325,10 @@ async function removeWorktree(input) {
16625
17325
  try {
16626
17326
  const encoded = encodeClaudeProjectDir(worktreeRoot);
16627
17327
  if (encoded) {
16628
- const projectsRoot = import_node_path10.default.join(import_node_os7.default.homedir(), ".claude", "projects");
16629
- const target = import_node_path10.default.resolve(projectsRoot, encoded);
16630
- if (target.startsWith(projectsRoot + import_node_path10.default.sep) && target !== projectsRoot) {
16631
- import_node_fs11.default.rmSync(target, { recursive: true, force: true });
17328
+ const projectsRoot = import_node_path14.default.join(import_node_os9.default.homedir(), ".claude", "projects");
17329
+ const target = import_node_path14.default.resolve(projectsRoot, encoded);
17330
+ if (target.startsWith(projectsRoot + import_node_path14.default.sep) && target !== projectsRoot) {
17331
+ import_node_fs15.default.rmSync(target, { recursive: true, force: true });
16632
17332
  }
16633
17333
  }
16634
17334
  } catch {
@@ -16650,13 +17350,13 @@ function listRecentDirs(store, limit = 50) {
16650
17350
  }
16651
17351
 
16652
17352
  // src/version.ts
16653
- var version = "0.1.2";
17353
+ var version = "0.2.1";
16654
17354
 
16655
17355
  // src/index.ts
16656
17356
  async function startDaemon(config) {
16657
17357
  const logger = createLogger({
16658
17358
  level: config.logLevel,
16659
- file: import_node_path11.default.join(config.dataDir, "clawd.log")
17359
+ file: import_node_path15.default.join(config.dataDir, "clawd.log")
16660
17360
  });
16661
17361
  logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
16662
17362
  const stateMgr = new StateFileManager({ dataDir: config.dataDir });
@@ -16667,9 +17367,20 @@ async function startDaemon(config) {
16667
17367
  if (pre.status === "stale") {
16668
17368
  logger.warn("stale state file detected, overwriting", { pid: pre.existing.pid });
16669
17369
  }
16670
- if (config.serverUrl) {
16671
- throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, "remote server transport not implemented yet");
16672
- }
17370
+ let resolvedAuthToken = null;
17371
+ if (config.authToken && config.authToken.trim()) {
17372
+ resolvedAuthToken = config.authToken.trim();
17373
+ } else if (config.tunnel) {
17374
+ resolvedAuthToken = loadOrCreateAuthToken({ dataDir: config.dataDir });
17375
+ }
17376
+ const authMode = resolvedAuthToken == null ? "none" : "first-message";
17377
+ let wsServer = null;
17378
+ const authGate = authMode === "first-message" ? new AuthGate({
17379
+ shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
17380
+ expectedToken: resolvedAuthToken,
17381
+ closeConnection: (h, code, reason) => wsServer?.closeClient(h.id, code, reason),
17382
+ sendOk: (h, payload) => wsServer?.sendToClient(h.id, payload)
17383
+ }) : null;
16673
17384
  resetRegistry();
16674
17385
  registerAdapter(
16675
17386
  "claude",
@@ -16703,14 +17414,17 @@ async function startDaemon(config) {
16703
17414
  onStatus: (sessionId, status) => transport?.broadcastToSession(sessionId, { type: "session:status", sessionId, status })
16704
17415
  });
16705
17416
  const handlers = buildMethodHandlers({ manager, workspace, skills, history, observer, getAdapter, store });
16706
- const wsServer = new LocalWsServer({
17417
+ wsServer = new LocalWsServer({
16707
17418
  host: config.host,
16708
17419
  port: config.port,
16709
17420
  logger,
16710
- readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter })
17421
+ readyFrameBuilder: () => buildReadyFrame({ manager, getAdapter }),
17422
+ protocolVersion: PROTOCOL_VERSION,
17423
+ authGate: authGate ?? void 0
16711
17424
  });
16712
17425
  transport = wsServer;
16713
- wsServer.onFrame(async (client, frame) => {
17426
+ const wss = wsServer;
17427
+ wss.onFrame(async (client, frame) => {
16714
17428
  const type = frame.type;
16715
17429
  if (!METHOD_NAMES.includes(type)) {
16716
17430
  throw new ClawdError(ERROR_CODES.METHOD_NOT_ALLOWED, `unknown method: ${type}`);
@@ -16734,27 +17448,79 @@ async function startDaemon(config) {
16734
17448
  }
16735
17449
  }
16736
17450
  });
16737
- await wsServer.start();
17451
+ await wss.start();
16738
17452
  const url = `ws://${config.host}:${config.port}`;
16739
- const state = {
17453
+ let stateSnapshot = {
16740
17454
  pid: process.pid,
16741
17455
  transport: "local-ws",
16742
17456
  url,
16743
17457
  version,
16744
17458
  protocolVersion: PROTOCOL_VERSION,
16745
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
17459
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
17460
+ authMode,
17461
+ authToken: resolvedAuthToken ?? void 0
16746
17462
  };
16747
- stateMgr.write(state);
17463
+ stateMgr.write(stateSnapshot);
16748
17464
  process.stdout.write(`Ready: ${url}
16749
17465
  `);
17466
+ if (authMode === "first-message" && resolvedAuthToken) {
17467
+ process.stdout.write(`Auth Token: ${resolvedAuthToken}
17468
+ `);
17469
+ }
17470
+ let tunnelMgr = null;
17471
+ if (config.tunnel) {
17472
+ if (!config.clawosApi) {
17473
+ throw new ClawdError(ERROR_CODES.INTERNAL, "--tunnel requires --clawos-api");
17474
+ }
17475
+ tunnelMgr = new TunnelManager({
17476
+ dataDir: config.dataDir,
17477
+ clawosApi: config.clawosApi,
17478
+ deviceKey: deriveStableDeviceKey(),
17479
+ frpcBinaryOverride: config.frpcBinary,
17480
+ noPersist: config.noTunnelPersist,
17481
+ logger,
17482
+ installProcessExitHandlers: true,
17483
+ onFrpcExit: (info) => {
17484
+ wss.broadcastAll({ type: "tunnel:exited", code: info.code, subdomain: info.subdomain, url: info.url });
17485
+ }
17486
+ });
17487
+ try {
17488
+ const r = await tunnelMgr.start({ localPort: config.port });
17489
+ stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url };
17490
+ stateMgr.write(stateSnapshot);
17491
+ process.stdout.write(`Tunnel: ${r.url}
17492
+ `);
17493
+ if (resolvedAuthToken) {
17494
+ process.stdout.write(`Connect: ${r.url}#token=${resolvedAuthToken}
17495
+ `);
17496
+ }
17497
+ } catch (err) {
17498
+ try {
17499
+ await tunnelMgr.stop();
17500
+ } catch {
17501
+ }
17502
+ tunnelMgr = null;
17503
+ stateMgr.delete();
17504
+ await wss.stop();
17505
+ throw err;
17506
+ }
17507
+ }
16750
17508
  const shutdown = async () => {
16751
17509
  logger.info("stopping clawd");
16752
17510
  observer.stopAll();
16753
17511
  manager.stopAll();
16754
- await wsServer.stop();
17512
+ if (tunnelMgr) {
17513
+ await tunnelMgr.stop();
17514
+ }
17515
+ await wss.stop();
16755
17516
  stateMgr.delete();
16756
17517
  };
16757
- return { stop: shutdown, url };
17518
+ return {
17519
+ stop: shutdown,
17520
+ url,
17521
+ tunnelUrl: stateSnapshot.tunnelUrl ?? null,
17522
+ authToken: resolvedAuthToken
17523
+ };
16758
17524
  }
16759
17525
  function buildMethodHandlers(deps) {
16760
17526
  const { manager, workspace, skills, history, observer, getAdapter: getAdapter2, store } = deps;
@@ -16975,7 +17741,7 @@ function buildReadyFrame(deps) {
16975
17741
  return {
16976
17742
  version,
16977
17743
  protocolVersion: PROTOCOL_VERSION,
16978
- hostname: import_node_os8.default.hostname(),
17744
+ hostname: import_node_os10.default.hostname(),
16979
17745
  os: process.platform,
16980
17746
  tools,
16981
17747
  runningSessions: info.runningSessions
@@ -16997,7 +17763,10 @@ async function main() {
16997
17763
  }
16998
17764
  const config = resolveConfig({ argv, env: process.env });
16999
17765
  const runtime = await startDaemon(config);
17766
+ let shuttingDown = false;
17000
17767
  const shutdown = async (signal) => {
17768
+ if (shuttingDown) return;
17769
+ shuttingDown = true;
17001
17770
  try {
17002
17771
  console.error(`[clawd] received ${signal}, shutting down`);
17003
17772
  await runtime.stop();
@@ -17014,4 +17783,3 @@ main().catch((err) => {
17014
17783
  console.error("[clawd] fatal:", err);
17015
17784
  process.exit(1);
17016
17785
  });
17017
- //# sourceMappingURL=cli.cjs.map