@dev.sail.money/sailor 1.2.1-94 → 1.3.0-208

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 (106) hide show
  1. package/README.md +63 -0
  2. package/package.json +2 -1
  3. package/packages/cli/dist/index.cjs +467 -321
  4. package/packages/cli/dist/server.cjs +7 -8
  5. package/packages/sdk/dist/intelligence.d.ts +1 -1
  6. package/packages/sdk/dist/intelligence.js +1 -1
  7. package/packages/sdk/dist/keyring.d.ts +0 -6
  8. package/packages/sdk/dist/keyring.d.ts.map +1 -1
  9. package/packages/sdk/dist/keyring.js +1 -8
  10. package/packages/sdk/dist/keyring.js.map +1 -1
  11. package/packages/ui/dist/assets/{add-Dj4_n773.js → add-CfTRVkh-.js} +1 -1
  12. package/packages/ui/dist/assets/{all-wallets-BaFysN1G.js → all-wallets-VZChJwJf.js} +1 -1
  13. package/packages/ui/dist/assets/{app-store-DOc31zzY.js → app-store-CuFK1xBJ.js} +1 -1
  14. package/packages/ui/dist/assets/{apple-BvrvOkPH.js → apple-DETqVk_I.js} +1 -1
  15. package/packages/ui/dist/assets/{arrow-bottom-E1AfjXOT.js → arrow-bottom-CnNYsZfo.js} +1 -1
  16. package/packages/ui/dist/assets/{arrow-bottom-circle-sou7Vo1V.js → arrow-bottom-circle-DZwJF3UO.js} +1 -1
  17. package/packages/ui/dist/assets/{arrow-left-wk3G9Ywf.js → arrow-left-CBnisTAa.js} +1 -1
  18. package/packages/ui/dist/assets/{arrow-right-_a703_Pw.js → arrow-right-DA3Y1Yzf.js} +1 -1
  19. package/packages/ui/dist/assets/{arrow-top-txyg3R3e.js → arrow-top-e8-FE_fo.js} +1 -1
  20. package/packages/ui/dist/assets/{bank-BB1kd0NJ.js → bank-DdwromQn.js} +1 -1
  21. package/packages/ui/dist/assets/{basic-D0dy7nIZ.js → basic-DLYER2un.js} +1 -1
  22. package/packages/ui/dist/assets/{browser-BwHLhsTn.js → browser-BJNrv4Zj.js} +1 -1
  23. package/packages/ui/dist/assets/{card-DRC6HmPF.js → card-DB6j4DSk.js} +1 -1
  24. package/packages/ui/dist/assets/{ccip-Dq6KeJQl.js → ccip-Dy_s323Y.js} +1 -1
  25. package/packages/ui/dist/assets/{checkmark-TbAAQGjd.js → checkmark-Db9oicko.js} +1 -1
  26. package/packages/ui/dist/assets/{checkmark-bold-4723vJ0-.js → checkmark-bold-D9MgGHxv.js} +1 -1
  27. package/packages/ui/dist/assets/{chevron-bottom-DtLoBRe9.js → chevron-bottom-IGUDJRKN.js} +1 -1
  28. package/packages/ui/dist/assets/{chevron-left-BtydsF9j.js → chevron-left-CWpfP-rw.js} +1 -1
  29. package/packages/ui/dist/assets/{chevron-right-BbkU_Kf8.js → chevron-right-BejY0vtE.js} +1 -1
  30. package/packages/ui/dist/assets/{chevron-top-BWf-sN-F.js → chevron-top-BaUN0LGF.js} +1 -1
  31. package/packages/ui/dist/assets/{chrome-store-CVFxc5H2.js → chrome-store-fox1tIqs.js} +1 -1
  32. package/packages/ui/dist/assets/{clock-B4hJww1X.js → clock-DlqB4Ehi.js} +1 -1
  33. package/packages/ui/dist/assets/{close-Ci93jqGe.js → close-03e52feP.js} +1 -1
  34. package/packages/ui/dist/assets/{coinPlaceholder-cRv_xDgC.js → coinPlaceholder-BO3nv4hx.js} +1 -1
  35. package/packages/ui/dist/assets/{compass-_sZZPtm6.js → compass-CIqPRE0k.js} +1 -1
  36. package/packages/ui/dist/assets/{copy-DLaiLK0n.js → copy-D5cJBwZq.js} +1 -1
  37. package/packages/ui/dist/assets/{core-YssZmpJa.js → core-COelOg3-.js} +3 -3
  38. package/packages/ui/dist/assets/cursor-CRb7R3u1.js +3 -0
  39. package/packages/ui/dist/assets/{cursor-transparent-CzXQbB2p.js → cursor-transparent-Cvbiq9Br.js} +1 -1
  40. package/packages/ui/dist/assets/{desktop-BiqQI1Ss.js → desktop-Dp2wtF3n.js} +1 -1
  41. package/packages/ui/dist/assets/{disconnect-DYu7YCzu.js → disconnect-Dx_IGM73.js} +1 -1
  42. package/packages/ui/dist/assets/{discord-C-ife-UZ.js → discord-Cxryn14a.js} +1 -1
  43. package/packages/ui/dist/assets/{etherscan-Dq7ac-p9.js → etherscan-D8aNXWCx.js} +1 -1
  44. package/packages/ui/dist/assets/{events-BhTGLk8n.js → events-DR6OOHz2.js} +1 -1
  45. package/packages/ui/dist/assets/{exclamation-triangle-BcOdRdbp.js → exclamation-triangle-BBCaPLj1.js} +1 -1
  46. package/packages/ui/dist/assets/{extension-DmJLIidM.js → extension-bACRNBl5.js} +1 -1
  47. package/packages/ui/dist/assets/{external-link-HNjp7-Po.js → external-link-BZRqHo3q.js} +1 -1
  48. package/packages/ui/dist/assets/{facebook-BrSvHNyw.js → facebook-TAdUitg4.js} +1 -1
  49. package/packages/ui/dist/assets/{fallback-Ubz80uXP.js → fallback-DGiwrboN.js} +1 -1
  50. package/packages/ui/dist/assets/{farcaster-BsjltM6k.js → farcaster-YzdobBfR.js} +1 -1
  51. package/packages/ui/dist/assets/{filters-B4yQbYZP.js → filters-CT-xLEoI.js} +1 -1
  52. package/packages/ui/dist/assets/{github-CFq82Dds.js → github-DxPBbmEL.js} +1 -1
  53. package/packages/ui/dist/assets/{google-DwZutf4a.js → google-B0TMgFhK.js} +1 -1
  54. package/packages/ui/dist/assets/{help-circle-CyA_8KQg.js → help-circle-Be6sRPTw.js} +1 -1
  55. package/packages/ui/dist/assets/{id-jyZYn4LJ.js → id-Dem_akbE.js} +1 -1
  56. package/packages/ui/dist/assets/{image-Zv0yoBcH.js → image-CszBzFVG.js} +1 -1
  57. package/packages/ui/dist/assets/{index-BxRaYDiX.js → index-B0FDHAiK.js} +1 -1
  58. package/packages/ui/dist/assets/{index-67NncC_u.js → index-C2n9XvGG.js} +3 -3
  59. package/packages/ui/dist/assets/{index-_DkPx8RN.js → index-Cb5eBfrN.js} +77 -77
  60. package/packages/ui/dist/assets/{index-BY8A6EK8.js → index-DAawBKd2.js} +1 -1
  61. package/packages/ui/dist/assets/{index-CeGppB9s.js → index-DXqj6oYp.js} +1 -1
  62. package/packages/ui/dist/assets/{index-BdG43zCl.js → index-homh6mWC.js} +1 -1
  63. package/packages/ui/dist/assets/{index.es-C8EklrJY.js → index.es-DJgl78dj.js} +4 -4
  64. package/packages/ui/dist/assets/{info-BwGEOdmB.js → info-BimGN4NK.js} +1 -1
  65. package/packages/ui/dist/assets/{info-circle-BPCcX-Iq.js → info-circle-lnKEJu2R.js} +1 -1
  66. package/packages/ui/dist/assets/{lightbulb-BRwr4IaC.js → lightbulb-DS1vEMce.js} +1 -1
  67. package/packages/ui/dist/assets/{mail-D-k_Vhe4.js → mail-DoS3Wibk.js} +1 -1
  68. package/packages/ui/dist/assets/{metamask-sdk-AcOgwGrj.js → metamask-sdk-CMIU3BwW.js} +1 -1
  69. package/packages/ui/dist/assets/{mobile-B1ok9PfY.js → mobile-B09Cs95_.js} +1 -1
  70. package/packages/ui/dist/assets/{more-DeMTMLWs.js → more-CLBZT8WI.js} +1 -1
  71. package/packages/ui/dist/assets/{network-placeholder-J0Rc1I89.js → network-placeholder-C45Cp8rY.js} +1 -1
  72. package/packages/ui/dist/assets/{nftPlaceholder-dXGrzzoa.js → nftPlaceholder-CAVclL3u.js} +1 -1
  73. package/packages/ui/dist/assets/{off-CS0f77lw.js → off-CwE8HoZ0.js} +1 -1
  74. package/packages/ui/dist/assets/{parseSignature-DJ16fHC4.js → parseSignature-kmMNHYjl.js} +1 -1
  75. package/packages/ui/dist/assets/{play-store-DmK8UaNC.js → play-store-DiFF_6n4.js} +1 -1
  76. package/packages/ui/dist/assets/{plus-CSGSnoCh.js → plus-aygpl_pZ.js} +1 -1
  77. package/packages/ui/dist/assets/{qr-code-BJMH0xmM.js → qr-code-DpIS4fFK.js} +1 -1
  78. package/packages/ui/dist/assets/{recycle-horizontal-D5XP5VgJ.js → recycle-horizontal-Qk2N74JE.js} +1 -1
  79. package/packages/ui/dist/assets/{refresh-MLX3Rdp6.js → refresh-sWRq9Vnt.js} +1 -1
  80. package/packages/ui/dist/assets/{reown-logo-CVoDFQbM.js → reown-logo-YQxBa9i9.js} +1 -1
  81. package/packages/ui/dist/assets/{search-BDJDyvjC.js → search-Bt6BSPcC.js} +1 -1
  82. package/packages/ui/dist/assets/{secp256k1-BWh3B02k.js → secp256k1-BUh3OoFd.js} +1 -1
  83. package/packages/ui/dist/assets/{send-BOq2svx_.js → send-ym-Fn17O.js} +1 -1
  84. package/packages/ui/dist/assets/{swapHorizontal-ByjLi_-_.js → swapHorizontal-fBE2gnqc.js} +1 -1
  85. package/packages/ui/dist/assets/{swapHorizontalBold-fmCp_4AE.js → swapHorizontalBold-CekQ22FJ.js} +1 -1
  86. package/packages/ui/dist/assets/{swapHorizontalMedium-D6dFseYw.js → swapHorizontalMedium-B9BI9ovU.js} +1 -1
  87. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-B2V3m4ha.js → swapHorizontalRoundedBold-DOC75mBH.js} +1 -1
  88. package/packages/ui/dist/assets/{swapVertical-DVmCsmeT.js → swapVertical-CSQAPH0z.js} +1 -1
  89. package/packages/ui/dist/assets/{telegram-Bv8JRbMM.js → telegram-BPjzFxKU.js} +1 -1
  90. package/packages/ui/dist/assets/{three-dots-YYDJ18z6.js → three-dots-DP6hdXH8.js} +1 -1
  91. package/packages/ui/dist/assets/{twitch-B2_Jy-Ft.js → twitch-CgHMd-f3.js} +1 -1
  92. package/packages/ui/dist/assets/{twitterIcon-pKQzPQRt.js → twitterIcon-BrR8ynXm.js} +1 -1
  93. package/packages/ui/dist/assets/{verify-Bty2jIav.js → verify-D35D7Wtv.js} +1 -1
  94. package/packages/ui/dist/assets/{verify-filled-CKz2fuKl.js → verify-filled-DCP5CT59.js} +1 -1
  95. package/packages/ui/dist/assets/{w3m-modal-Wg1mtmVM.js → w3m-modal-B-ZptWk0.js} +1 -1
  96. package/packages/ui/dist/assets/{wallet-DslzC8rD.js → wallet-Cnb4dkr3.js} +1 -1
  97. package/packages/ui/dist/assets/{wallet-placeholder-Dyw8jJ56.js → wallet-placeholder-CCzk0PqU.js} +1 -1
  98. package/packages/ui/dist/assets/{walletconnect-DZBfbgp4.js → walletconnect-f6tQZYRi.js} +1 -1
  99. package/packages/ui/dist/assets/{warning-circle-D7NPDx4k.js → warning-circle-zxMagvfx.js} +1 -1
  100. package/packages/ui/dist/assets/{x-DRR_9FNs.js → x-D0nEGO1G.js} +1 -1
  101. package/packages/ui/dist/index.html +1 -1
  102. package/scripts/clean.mjs +17 -0
  103. package/templates/default/.agents/skills/sail-onboarding/SKILL.md +36 -0
  104. package/templates/default/.agents/skills/sail-servers/SKILL.md +26 -0
  105. package/templates/default/AGENTS.md +1 -1
  106. package/packages/ui/dist/assets/cursor-B_xyJnjj.js +0 -3
@@ -22626,7 +22626,7 @@ var require_websocket = __commonJS({
22626
22626
  var EventEmitter = require("events");
22627
22627
  var https = require("https");
22628
22628
  var http2 = require("http");
22629
- var net2 = require("net");
22629
+ var net3 = require("net");
22630
22630
  var tls = require("tls");
22631
22631
  var { randomBytes: randomBytes4, createHash } = require("crypto");
22632
22632
  var { Duplex, Readable } = require("stream");
@@ -23360,12 +23360,12 @@ var require_websocket = __commonJS({
23360
23360
  }
23361
23361
  function netConnect(options) {
23362
23362
  options.path = options.socketPath;
23363
- return net2.connect(options);
23363
+ return net3.connect(options);
23364
23364
  }
23365
23365
  function tlsConnect(options) {
23366
23366
  options.path = void 0;
23367
23367
  if (!options.servername && options.servername !== "") {
23368
- options.servername = net2.isIP(options.host) ? "" : options.host;
23368
+ options.servername = net3.isIP(options.host) ? "" : options.host;
23369
23369
  }
23370
23370
  return tls.connect(options);
23371
23371
  }
@@ -33756,7 +33756,7 @@ var require_websocket2 = __commonJS({
33756
33756
  var EventEmitter = require("events");
33757
33757
  var https = require("https");
33758
33758
  var http2 = require("http");
33759
- var net2 = require("net");
33759
+ var net3 = require("net");
33760
33760
  var tls = require("tls");
33761
33761
  var { randomBytes: randomBytes4, createHash } = require("crypto");
33762
33762
  var { Duplex, Readable } = require("stream");
@@ -34500,12 +34500,12 @@ var require_websocket2 = __commonJS({
34500
34500
  }
34501
34501
  function netConnect(options) {
34502
34502
  options.path = options.socketPath;
34503
- return net2.connect(options);
34503
+ return net3.connect(options);
34504
34504
  }
34505
34505
  function tlsConnect(options) {
34506
34506
  options.path = void 0;
34507
34507
  if (!options.servername && options.servername !== "") {
34508
- options.servername = net2.isIP(options.host) ? "" : options.host;
34508
+ options.servername = net3.isIP(options.host) ? "" : options.host;
34509
34509
  }
34510
34510
  return tls.connect(options);
34511
34511
  }
@@ -37445,18 +37445,12 @@ var LocalKeyring = class _LocalKeyring {
37445
37445
  this.account = account2;
37446
37446
  this.address = account2.address;
37447
37447
  this.privateKey = options.privateKey;
37448
- } else if (options.type === "mnemonic") {
37448
+ } else {
37449
37449
  const account2 = mnemonicToAccount(options.mnemonic, options.derivationPath ? { path: options.derivationPath } : void 0);
37450
37450
  this.account = account2;
37451
37451
  this.address = account2.address;
37452
- } else {
37453
- throw new Error("keystore decryption not implemented \u2014 use type: 'privateKey' or type: 'mnemonic' instead");
37454
37452
  }
37455
37453
  }
37456
- /** Returns a lightweight signer stub for read-only contexts where the key is not available. */
37457
- static fromAddress(address) {
37458
- return { address };
37459
- }
37460
37454
  /** Generates a brand-new keyring backed by a random private key. */
37461
37455
  static generate() {
37462
37456
  return new _LocalKeyring({ type: "privateKey", privateKey: generatePrivateKey() });
@@ -38497,15 +38491,58 @@ function upsertAccountInList(account2, name, baseSailDir = sailDir()) {
38497
38491
  }
38498
38492
 
38499
38493
  // src/signing/client.ts
38500
- var import_node_fs6 = require("node:fs");
38494
+ var import_node_fs7 = require("node:fs");
38501
38495
  var import_node_path5 = require("node:path");
38502
38496
 
38503
38497
  // src/signing/server.ts
38504
38498
  var import_node_crypto2 = require("node:crypto");
38505
- var import_node_fs5 = require("node:fs");
38499
+ var import_node_fs6 = require("node:fs");
38506
38500
  var import_node_http = require("node:http");
38507
- var import_node_net = require("node:net");
38508
38501
  var import_node_path4 = require("node:path");
38502
+
38503
+ // src/lib/process.ts
38504
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
38505
+ var import_node_net = __toESM(require("node:net"), 1);
38506
+ function agentPidPath(chainId) {
38507
+ return chainId != null ? sailPath(`agent-${chainId}.pid`) : sailPath("agent.pid");
38508
+ }
38509
+ function writeAgentPid(chainId) {
38510
+ import_node_fs5.default.mkdirSync(sailPath(), { recursive: true });
38511
+ import_node_fs5.default.writeFileSync(agentPidPath(chainId), `${process.pid}
38512
+ `);
38513
+ }
38514
+ function clearAgentPid(chainId) {
38515
+ try {
38516
+ import_node_fs5.default.rmSync(agentPidPath(chainId));
38517
+ } catch {
38518
+ }
38519
+ }
38520
+ function readAgentPid(chainId) {
38521
+ try {
38522
+ const pid = Number.parseInt(import_node_fs5.default.readFileSync(agentPidPath(chainId), "utf-8").trim(), 10);
38523
+ return Number.isNaN(pid) ? null : pid;
38524
+ } catch {
38525
+ return null;
38526
+ }
38527
+ }
38528
+ function isProcessAlive(pid) {
38529
+ try {
38530
+ process.kill(pid, 0);
38531
+ return true;
38532
+ } catch (err) {
38533
+ return err.code === "EPERM";
38534
+ }
38535
+ }
38536
+ function findFreePort(from14) {
38537
+ return new Promise((resolve3, reject) => {
38538
+ const server = import_node_net.default.createServer();
38539
+ server.unref();
38540
+ server.on("error", () => findFreePort(from14 + 1).then(resolve3, reject));
38541
+ server.listen(from14, "127.0.0.1", () => server.close(() => resolve3(from14)));
38542
+ });
38543
+ }
38544
+
38545
+ // src/signing/server.ts
38509
38546
  init_esm2();
38510
38547
 
38511
38548
  // ../../node_modules/.pnpm/ws@8.21.0_bufferutil@4.1.0_utf-8-validate@5.0.10/node_modules/ws/wrapper.mjs
@@ -38519,7 +38556,8 @@ var import_websocket2 = __toESM(require_websocket2(), 1);
38519
38556
  var import_websocket_server2 = __toESM(require_websocket_server2(), 1);
38520
38557
 
38521
38558
  // src/signing/server.ts
38522
- var DEFAULT_SIGNING_PORT = 3141;
38559
+ var _signingPort = parseInt(process.env.SAILOR_STATION_PORT ?? "", 10);
38560
+ var DEFAULT_SIGNING_PORT = Number.isFinite(_signingPort) && _signingPort >= 1 && _signingPort <= 65535 ? _signingPort : 3141;
38523
38561
  var RUNTIME_SUBDIR = (0, import_node_path4.join)(".sail", "runtime");
38524
38562
  var SERVER_STATE_FILE = "server.json";
38525
38563
  var REQUEST_SECRET_HEADER = "x-sailor-secret";
@@ -38544,7 +38582,7 @@ function findUiDist() {
38544
38582
  (0, import_node_path4.join)(process.cwd(), "..", "ui", "dist")
38545
38583
  ];
38546
38584
  for (const c of candidates) {
38547
- if ((0, import_node_fs5.existsSync)((0, import_node_path4.join)(c, "index.html"))) return c;
38585
+ if ((0, import_node_fs6.existsSync)((0, import_node_path4.join)(c, "index.html"))) return c;
38548
38586
  }
38549
38587
  return null;
38550
38588
  }
@@ -38592,7 +38630,7 @@ var SigningServer = class {
38592
38630
  return this.httpServer?.listening ?? false;
38593
38631
  }
38594
38632
  async start() {
38595
- this.port = await findAvailablePort(this.port);
38633
+ this.port = await findFreePort(this.port);
38596
38634
  this._url = `http://localhost:${this.port}`;
38597
38635
  this.requestSecret = (0, import_node_crypto2.randomBytes)(16).toString("hex");
38598
38636
  const http2 = (0, import_node_http.createServer)((req, res) => this.handleHttp(req, res));
@@ -38772,13 +38810,13 @@ var SigningServer = class {
38772
38810
  const path11 = this.sailFile("config.json");
38773
38811
  let config = {};
38774
38812
  try {
38775
- config = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf-8"));
38813
+ config = JSON.parse((0, import_node_fs6.readFileSync)(path11, "utf-8"));
38776
38814
  } catch {
38777
38815
  }
38778
38816
  if (Number(config.chainId) === Number(chainId)) return;
38779
38817
  config.chainId = Number(chainId);
38780
- (0, import_node_fs5.mkdirSync)(this.sailFile(), { recursive: true });
38781
- (0, import_node_fs5.writeFileSync)(path11, `${JSON.stringify(config, null, 2)}
38818
+ (0, import_node_fs6.mkdirSync)(this.sailFile(), { recursive: true });
38819
+ (0, import_node_fs6.writeFileSync)(path11, `${JSON.stringify(config, null, 2)}
38782
38820
  `);
38783
38821
  } catch {
38784
38822
  }
@@ -38786,7 +38824,7 @@ var SigningServer = class {
38786
38824
  /** Stream a JSON file back, or a fallback body when it is missing/invalid. */
38787
38825
  sendJsonFile(res, filePath, fallback2) {
38788
38826
  try {
38789
- const raw = (0, import_node_fs5.readFileSync)(filePath, "utf-8");
38827
+ const raw = (0, import_node_fs6.readFileSync)(filePath, "utf-8");
38790
38828
  JSON.parse(raw);
38791
38829
  res.writeHead(200, { "Content-Type": "application/json" });
38792
38830
  res.end(raw);
@@ -38853,8 +38891,8 @@ var SigningServer = class {
38853
38891
  };
38854
38892
  const baseSailDir = this.sailFile();
38855
38893
  upsertAccountInList(record, void 0, baseSailDir);
38856
- (0, import_node_fs5.mkdirSync)(baseSailDir, { recursive: true });
38857
- (0, import_node_fs5.writeFileSync)(this.sailFile("account.json"), `${JSON.stringify(record, null, 2)}
38894
+ (0, import_node_fs6.mkdirSync)(baseSailDir, { recursive: true });
38895
+ (0, import_node_fs6.writeFileSync)(this.sailFile("account.json"), `${JSON.stringify(record, null, 2)}
38858
38896
  `);
38859
38897
  this.syncConfigChainId(chainId);
38860
38898
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -38869,12 +38907,12 @@ var SigningServer = class {
38869
38907
  res.writeHead(200, { "Content-Type": "application/json" });
38870
38908
  let active = null;
38871
38909
  try {
38872
- active = JSON.parse((0, import_node_fs5.readFileSync)(this.sailFile("account.json"), "utf-8")).safe;
38910
+ active = JSON.parse((0, import_node_fs6.readFileSync)(this.sailFile("account.json"), "utf-8")).safe;
38873
38911
  } catch {
38874
38912
  }
38875
38913
  try {
38876
38914
  const accounts = JSON.parse(
38877
- (0, import_node_fs5.readFileSync)(this.sailFile("state", "accounts.json"), "utf-8")
38915
+ (0, import_node_fs6.readFileSync)(this.sailFile("state", "accounts.json"), "utf-8")
38878
38916
  );
38879
38917
  res.end(
38880
38918
  JSON.stringify(
@@ -38887,7 +38925,7 @@ var SigningServer = class {
38887
38925
  } catch {
38888
38926
  try {
38889
38927
  const a = JSON.parse(
38890
- (0, import_node_fs5.readFileSync)(this.sailFile("account.json"), "utf-8")
38928
+ (0, import_node_fs6.readFileSync)(this.sailFile("account.json"), "utf-8")
38891
38929
  );
38892
38930
  res.end(JSON.stringify([{ ...a, name: "My SMA", active: true, addedAt: null }]));
38893
38931
  } catch {
@@ -39010,16 +39048,16 @@ var SigningServer = class {
39010
39048
  res.end();
39011
39049
  return;
39012
39050
  }
39013
- if ((0, import_node_fs5.existsSync)(filePath)) {
39051
+ if ((0, import_node_fs6.existsSync)(filePath)) {
39014
39052
  const mime = MIME[(0, import_node_path4.extname)(filePath)] ?? "application/octet-stream";
39015
39053
  res.writeHead(200, { "Content-Type": mime });
39016
- res.end((0, import_node_fs5.readFileSync)(filePath));
39054
+ res.end((0, import_node_fs6.readFileSync)(filePath));
39017
39055
  return;
39018
39056
  }
39019
39057
  const indexHtml = (0, import_node_path4.join)(this.uiDist, "index.html");
39020
- if ((0, import_node_fs5.existsSync)(indexHtml)) {
39058
+ if ((0, import_node_fs6.existsSync)(indexHtml)) {
39021
39059
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
39022
- res.end((0, import_node_fs5.readFileSync)(indexHtml));
39060
+ res.end((0, import_node_fs6.readFileSync)(indexHtml));
39023
39061
  return;
39024
39062
  }
39025
39063
  }
@@ -39106,15 +39144,15 @@ var SigningServer = class {
39106
39144
  }
39107
39145
  writeRuntimeState() {
39108
39146
  const path11 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39109
- if ((0, import_node_fs5.existsSync)(path11)) {
39147
+ if ((0, import_node_fs6.existsSync)(path11)) {
39110
39148
  try {
39111
- const existing = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39112
- if (existing.pid != null && existing.pid !== process.pid && pidAlive(existing.pid)) return;
39149
+ const existing = JSON.parse((0, import_node_fs6.readFileSync)(path11, "utf8"));
39150
+ if (existing.pid != null && existing.pid !== process.pid && isProcessAlive(existing.pid)) return;
39113
39151
  } catch {
39114
39152
  }
39115
39153
  }
39116
- if (!(0, import_node_fs5.existsSync)(this.runtimeDir)) (0, import_node_fs5.mkdirSync)(this.runtimeDir, { recursive: true });
39117
- (0, import_node_fs5.writeFileSync)(
39154
+ if (!(0, import_node_fs6.existsSync)(this.runtimeDir)) (0, import_node_fs6.mkdirSync)(this.runtimeDir, { recursive: true });
39155
+ (0, import_node_fs6.writeFileSync)(
39118
39156
  (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE),
39119
39157
  JSON.stringify(
39120
39158
  {
@@ -39133,43 +39171,25 @@ var SigningServer = class {
39133
39171
  removeRuntimeState() {
39134
39172
  const path11 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39135
39173
  try {
39136
- if (!(0, import_node_fs5.existsSync)(path11)) return;
39137
- const state = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39174
+ if (!(0, import_node_fs6.existsSync)(path11)) return;
39175
+ const state = JSON.parse((0, import_node_fs6.readFileSync)(path11, "utf8"));
39138
39176
  if (state.pid != null && state.pid !== process.pid) return;
39139
- (0, import_node_fs5.unlinkSync)(path11);
39177
+ (0, import_node_fs6.unlinkSync)(path11);
39140
39178
  } catch {
39141
39179
  }
39142
39180
  }
39143
39181
  };
39144
- function pidAlive(pid) {
39145
- try {
39146
- process.kill(pid, 0);
39147
- return true;
39148
- } catch (err) {
39149
- return err.code === "EPERM";
39150
- }
39151
- }
39152
39182
  function reapStaleRuntimeState(projectRoot = process.cwd()) {
39153
39183
  const path11 = (0, import_node_path4.join)(projectRoot, RUNTIME_SUBDIR, SERVER_STATE_FILE);
39154
39184
  try {
39155
- if (!(0, import_node_fs5.existsSync)(path11)) return;
39156
- const state = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39157
- if (state.pid != null && state.pid !== process.pid && !pidAlive(state.pid)) {
39158
- (0, import_node_fs5.unlinkSync)(path11);
39185
+ if (!(0, import_node_fs6.existsSync)(path11)) return;
39186
+ const state = JSON.parse((0, import_node_fs6.readFileSync)(path11, "utf8"));
39187
+ if (state.pid != null && state.pid !== process.pid && !isProcessAlive(state.pid)) {
39188
+ (0, import_node_fs6.unlinkSync)(path11);
39159
39189
  }
39160
39190
  } catch {
39161
39191
  }
39162
39192
  }
39163
- async function findAvailablePort(startPort) {
39164
- return new Promise((res) => {
39165
- const probe = (0, import_node_net.createServer)();
39166
- probe.listen(startPort, "127.0.0.1", () => {
39167
- const addr = probe.address();
39168
- probe.close(() => res(addr.port));
39169
- });
39170
- probe.on("error", () => res(findAvailablePort(startPort + 1)));
39171
- });
39172
- }
39173
39193
 
39174
39194
  // src/signing/client.ts
39175
39195
  var RUNTIME_SERVER_FILE = (0, import_node_path5.join)(".sail", "runtime", "server.json");
@@ -39251,14 +39271,14 @@ function toLoopbackIPv4(url) {
39251
39271
  }
39252
39272
  function readRuntimeServerState(projectRoot) {
39253
39273
  const file = (0, import_node_path5.join)(projectRoot, RUNTIME_SERVER_FILE);
39254
- if (!(0, import_node_fs6.existsSync)(file)) return null;
39274
+ if (!(0, import_node_fs7.existsSync)(file)) return null;
39255
39275
  try {
39256
- return JSON.parse((0, import_node_fs6.readFileSync)(file, "utf8"));
39276
+ return JSON.parse((0, import_node_fs7.readFileSync)(file, "utf8"));
39257
39277
  } catch {
39258
39278
  return null;
39259
39279
  }
39260
39280
  }
39261
- function signingPageUrl(_channel, dashboardPort) {
39281
+ function signingPageUrl(dashboardPort) {
39262
39282
  return `http://localhost:${dashboardPort}/#/station`;
39263
39283
  }
39264
39284
  async function discoverDaemon(projectRoot = process.cwd()) {
@@ -39529,7 +39549,7 @@ Deploying SMA on ${getChainById(targetChainId).name} (chain ${targetChainId})\u2
39529
39549
  const channel = await createSigningChannel(process.cwd());
39530
39550
  try {
39531
39551
  await channel.start();
39532
- const stationUrl = signingPageUrl(channel, projectPort(process.cwd()));
39552
+ const stationUrl = signingPageUrl(projectPort(process.cwd()));
39533
39553
  if (json) {
39534
39554
  console.log(
39535
39555
  JSON.stringify(
@@ -39666,100 +39686,6 @@ function emit(json, human, payload) {
39666
39686
 
39667
39687
  // src/lib/project.ts
39668
39688
  init_esm2();
39669
-
39670
- // src/lib/keys.ts
39671
- init_esm2();
39672
- var ROLES = ["manager", "permissionSigner"];
39673
- function normalizeRole(input) {
39674
- const n = input.trim().toLowerCase().replace(/[-_\s]/g, "");
39675
- if (n === "manager" || n === "mgr" || n === "m" || n === "agent" || n === "agentwallet") {
39676
- return "manager";
39677
- }
39678
- if (n === "permissionsigner" || n === "signer" || n === "ps" || n === "permission" || n === "mandatesigner" || n === "mandate") {
39679
- return "permissionSigner";
39680
- }
39681
- return null;
39682
- }
39683
- function roleLabel(role) {
39684
- return role === "manager" ? "agent wallet" : "mandate signer";
39685
- }
39686
- function safeHex(safe) {
39687
- return safe.toLowerCase().replace(/^0x/, "").replace(/[^0-9a-f]/g, "");
39688
- }
39689
- function keyPath(role, safe) {
39690
- if (safe) {
39691
- return sailPath("keys", `${role}-0x${safeHex(safe)}.json`);
39692
- }
39693
- return sailPath("keys", `${role}.json`);
39694
- }
39695
- function legacyKeyPath(role, safe) {
39696
- return sailPath("keys", `${role}-${safeHex(safe)}.json`);
39697
- }
39698
- function resolveKeyPath(role, safe) {
39699
- if (safe) {
39700
- const perSma = keyPath(role, safe);
39701
- if (fileExists(perSma)) return perSma;
39702
- const legacy = legacyKeyPath(role, safe);
39703
- if (fileExists(legacy)) return legacy;
39704
- }
39705
- return keyPath(role);
39706
- }
39707
- function keyExists(role, safe) {
39708
- return fileExists(resolveKeyPath(role, safe));
39709
- }
39710
- async function loadKeyring(role, safe) {
39711
- const keystore = readJsonFile(resolveKeyPath(role, safe));
39712
- if (!keystore) {
39713
- throw new Error(
39714
- `No ${roleLabel(role)} found.
39715
- Run "sailor keys generate" and choose "${roleLabel(role)}" first.`
39716
- );
39717
- }
39718
- const password = await promptHidden(`Password for ${roleLabel(role)} key`);
39719
- try {
39720
- return await LocalKeyring.fromKeystore(keystore, password);
39721
- } catch {
39722
- throw new Error("Invalid password.");
39723
- }
39724
- }
39725
- async function loadManagerSigner(safe) {
39726
- const passphrase = process.env.SAIL_PASSPHRASE;
39727
- if (passphrase) {
39728
- const keystore = readJsonFile(resolveKeyPath("manager", safe));
39729
- if (!keystore) {
39730
- throw new Error(
39731
- 'No agent wallet found.\nRun "sailor keys generate" and choose "agent wallet".'
39732
- );
39733
- }
39734
- try {
39735
- return await LocalKeyring.fromKeystore(keystore, passphrase);
39736
- } catch {
39737
- throw new Error(
39738
- "SAIL_PASSPHRASE does not match this keystore.\nCheck the value in .sail/.env.local (or the SAIL_PASSPHRASE CI secret) \u2014 it must be the passphrase the agent wallet was encrypted with."
39739
- );
39740
- }
39741
- }
39742
- if (process.stdin.isTTY !== true && keyExists("manager", safe)) {
39743
- throw new Error(
39744
- 'Agent keystore found but SAIL_PASSPHRASE is not set.\nIf you created the key in the dashboard, add SAIL_PASSPHRASE to .sail/.env.local, or run "sailor keys generate".\nFor CI, set the SAIL_PASSPHRASE GitHub Actions secret.'
39745
- );
39746
- }
39747
- return loadKeyring("manager", safe);
39748
- }
39749
- function managerKeystorePath(managerAddr) {
39750
- if (!isAddress(managerAddr, { strict: false })) {
39751
- throw new Error(`managerKeystorePath: invalid address "${managerAddr}"`);
39752
- }
39753
- const hex = managerAddr.toLowerCase().replace(/^0x/, "");
39754
- return sailPath("keys", "managers", `${hex}.json`);
39755
- }
39756
- async function loadAnySigner() {
39757
- if (keyExists("permissionSigner")) return loadKeyring("permissionSigner");
39758
- if (keyExists("manager")) return loadKeyring("manager");
39759
- throw new Error('No signing key found.\nRun "sailor keys generate" first.');
39760
- }
39761
-
39762
- // src/lib/project.ts
39763
39689
  function nonEmpty(value) {
39764
39690
  return typeof value === "string" && value.trim().length > 0;
39765
39691
  }
@@ -39818,24 +39744,6 @@ var ProjectContext = class {
39818
39744
  });
39819
39745
  }
39820
39746
  };
39821
- async function loadManagerSigner2() {
39822
- if (!process.env.SAIL_PASSPHRASE) {
39823
- try {
39824
- const env = parseEnvFile(sailPath(".env.local"));
39825
- if (env.SAIL_PASSPHRASE) process.env.SAIL_PASSPHRASE = env.SAIL_PASSPHRASE;
39826
- } catch {
39827
- }
39828
- }
39829
- const passphrase = process.env.SAIL_PASSPHRASE;
39830
- if (passphrase) {
39831
- const keystore = readJsonFile(keyPath("manager"));
39832
- if (!keystore) {
39833
- throw new Error('No manager key found.\nRun "sailor keys generate" and choose "manager".');
39834
- }
39835
- return LocalKeyring.fromKeystore(keystore, passphrase);
39836
- }
39837
- return loadKeyring("manager");
39838
- }
39839
39747
 
39840
39748
  // src/commands/capabilities.ts
39841
39749
  function chainName(chainId) {
@@ -40043,6 +39951,101 @@ function checkSelectorRoutes(calldata, bytecode) {
40043
39951
  return { selector, routes: body.includes(selector) };
40044
39952
  }
40045
39953
 
39954
+ // src/lib/keys.ts
39955
+ init_esm2();
39956
+ var ROLES = ["manager", "permissionSigner"];
39957
+ function normalizeRole(input) {
39958
+ const n = input.trim().toLowerCase().replace(/[-_\s]/g, "");
39959
+ if (n === "manager" || n === "mgr" || n === "m" || n === "agent" || n === "agentwallet") {
39960
+ return "manager";
39961
+ }
39962
+ if (n === "permissionsigner" || n === "signer" || n === "ps" || n === "permission" || n === "mandatesigner" || n === "mandate") {
39963
+ return "permissionSigner";
39964
+ }
39965
+ return null;
39966
+ }
39967
+ function roleLabel(role) {
39968
+ return role === "manager" ? "agent wallet" : "mandate signer";
39969
+ }
39970
+ function safeHex(safe) {
39971
+ return safe.toLowerCase().replace(/^0x/, "").replace(/[^0-9a-f]/g, "");
39972
+ }
39973
+ function keyPath(role, safe) {
39974
+ if (safe) {
39975
+ return sailPath("keys", `${role}-0x${safeHex(safe)}.json`);
39976
+ }
39977
+ return sailPath("keys", `${role}.json`);
39978
+ }
39979
+ function legacyKeyPath(role, safe) {
39980
+ return sailPath("keys", `${role}-${safeHex(safe)}.json`);
39981
+ }
39982
+ function resolveKeyPath(role, safe) {
39983
+ if (safe) {
39984
+ const perSma = keyPath(role, safe);
39985
+ if (fileExists(perSma)) return perSma;
39986
+ const legacy = legacyKeyPath(role, safe);
39987
+ if (fileExists(legacy)) return legacy;
39988
+ }
39989
+ return keyPath(role);
39990
+ }
39991
+ function keyExists(role, safe) {
39992
+ return fileExists(resolveKeyPath(role, safe));
39993
+ }
39994
+ async function loadKeyring(role, safe) {
39995
+ const keystore = readJsonFile(resolveKeyPath(role, safe));
39996
+ if (!keystore) {
39997
+ throw new Error(
39998
+ `No ${roleLabel(role)} found.
39999
+ Run "sailor keys generate" and choose "${roleLabel(role)}" first.`
40000
+ );
40001
+ }
40002
+ const passphrase = process.env.SAIL_PASSPHRASE;
40003
+ if (passphrase) {
40004
+ try {
40005
+ return await LocalKeyring.fromKeystore(keystore, passphrase);
40006
+ } catch {
40007
+ throw new Error(
40008
+ `SAIL_PASSPHRASE does not match the ${roleLabel(role)} keystore.
40009
+ Check the value in .sail/.env.local (or the SAIL_PASSPHRASE CI secret) \u2014 it must be the passphrase this key was encrypted with.`
40010
+ );
40011
+ }
40012
+ }
40013
+ if (process.stdin.isTTY !== true) {
40014
+ throw new Error(
40015
+ `${roleLabel(role)} keystore found but SAIL_PASSPHRASE is not set.
40016
+ Set SAIL_PASSPHRASE in .sail/.env.local (or as a CI secret) to run non-interactively.`
40017
+ );
40018
+ }
40019
+ const password = await promptHidden(`Password for ${roleLabel(role)} key`);
40020
+ try {
40021
+ return await LocalKeyring.fromKeystore(keystore, password);
40022
+ } catch {
40023
+ throw new Error("Invalid password.");
40024
+ }
40025
+ }
40026
+ async function loadManagerSigner(safe) {
40027
+ if (!process.env.SAIL_PASSPHRASE) {
40028
+ try {
40029
+ const env = parseEnvFile(sailPath(".env.local"));
40030
+ if (env.SAIL_PASSPHRASE) process.env.SAIL_PASSPHRASE = env.SAIL_PASSPHRASE;
40031
+ } catch {
40032
+ }
40033
+ }
40034
+ return loadKeyring("manager", safe);
40035
+ }
40036
+ function managerKeystorePath(managerAddr) {
40037
+ if (!isAddress(managerAddr, { strict: false })) {
40038
+ throw new Error(`managerKeystorePath: invalid address "${managerAddr}"`);
40039
+ }
40040
+ const hex = managerAddr.toLowerCase().replace(/^0x/, "");
40041
+ return sailPath("keys", "managers", `${hex}.json`);
40042
+ }
40043
+ async function loadAnySigner() {
40044
+ if (keyExists("permissionSigner")) return loadKeyring("permissionSigner");
40045
+ if (keyExists("manager")) return loadKeyring("manager");
40046
+ throw new Error('No signing key found.\nRun "sailor keys generate" first.');
40047
+ }
40048
+
40046
40049
  // src/lib/permission-resolver.ts
40047
40050
  var IPERMISSION_ABI = [
40048
40051
  {
@@ -40438,11 +40441,11 @@ Probe is heuristic: an unknown selector (${PROBE_SELECTOR}) to a neutral target
40438
40441
  }
40439
40442
 
40440
40443
  // src/commands/init.ts
40441
- var import_node_fs9 = __toESM(require("node:fs"), 1);
40444
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
40442
40445
  var import_node_path8 = __toESM(require("node:path"), 1);
40443
40446
 
40444
40447
  // src/lib/foundry.ts
40445
- var import_node_fs7 = require("node:fs");
40448
+ var import_node_fs8 = require("node:fs");
40446
40449
  var import_node_path6 = require("node:path");
40447
40450
  var FOUNDRY_TOML = `[profile.default]
40448
40451
  src = "mandates"
@@ -40591,7 +40594,7 @@ Compiled artifacts are written to \`out/\` and the deployed address is tracked i
40591
40594
  function scaffoldFoundryWorkspace(root) {
40592
40595
  const dirs = [(0, import_node_path6.join)(root, "mandates"), (0, import_node_path6.join)(root, ".sail", "contracts", "interfaces")];
40593
40596
  for (const d of dirs) {
40594
- if (!(0, import_node_fs7.existsSync)(d)) (0, import_node_fs7.mkdirSync)(d, { recursive: true });
40597
+ if (!(0, import_node_fs8.existsSync)(d)) (0, import_node_fs8.mkdirSync)(d, { recursive: true });
40595
40598
  }
40596
40599
  writeIfMissing((0, import_node_path6.join)(root, "foundry.toml"), FOUNDRY_TOML);
40597
40600
  writeIfMissing(
@@ -40606,11 +40609,11 @@ function scaffoldFoundryWorkspace(root) {
40606
40609
  writeIfMissing((0, import_node_path6.join)(root, "mandates", "README.md"), MANDATES_README);
40607
40610
  }
40608
40611
  function writeIfMissing(path11, content) {
40609
- if (!(0, import_node_fs7.existsSync)(path11)) (0, import_node_fs7.writeFileSync)(path11, content, "utf8");
40612
+ if (!(0, import_node_fs8.existsSync)(path11)) (0, import_node_fs8.writeFileSync)(path11, content, "utf8");
40610
40613
  }
40611
40614
 
40612
40615
  // src/lib/template.ts
40613
- var import_node_fs8 = __toESM(require("node:fs"), 1);
40616
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
40614
40617
  var import_node_path7 = __toESM(require("node:path"), 1);
40615
40618
  var TEMPLATE_COPY_EXCLUDES = /* @__PURE__ */ new Set([
40616
40619
  "node_modules",
@@ -40621,8 +40624,8 @@ var TEMPLATE_COPY_EXCLUDES = /* @__PURE__ */ new Set([
40621
40624
  ".git"
40622
40625
  ]);
40623
40626
  function copyDirSync(src, dest) {
40624
- import_node_fs8.default.mkdirSync(dest, { recursive: true });
40625
- for (const entry of import_node_fs8.default.readdirSync(src, { withFileTypes: true })) {
40627
+ import_node_fs9.default.mkdirSync(dest, { recursive: true });
40628
+ for (const entry of import_node_fs9.default.readdirSync(src, { withFileTypes: true })) {
40626
40629
  if (TEMPLATE_COPY_EXCLUDES.has(entry.name)) continue;
40627
40630
  const srcPath = import_node_path7.default.join(src, entry.name);
40628
40631
  const destName = entry.name.startsWith("_") ? `.${entry.name.slice(1)}` : entry.name;
@@ -40630,24 +40633,24 @@ function copyDirSync(src, dest) {
40630
40633
  if (entry.isDirectory()) {
40631
40634
  copyDirSync(srcPath, destPath);
40632
40635
  } else {
40633
- import_node_fs8.default.copyFileSync(srcPath, destPath);
40636
+ import_node_fs9.default.copyFileSync(srcPath, destPath);
40634
40637
  }
40635
40638
  }
40636
40639
  }
40637
40640
  function writeIfMissing2(file, content) {
40638
- if (!import_node_fs8.default.existsSync(file)) import_node_fs8.default.writeFileSync(file, content, "utf-8");
40641
+ if (!import_node_fs9.default.existsSync(file)) import_node_fs9.default.writeFileSync(file, content, "utf-8");
40639
40642
  }
40640
40643
  function copyDirSyncIfMissing(src, dest, added = [], base2 = dest) {
40641
- import_node_fs8.default.mkdirSync(dest, { recursive: true });
40642
- for (const entry of import_node_fs8.default.readdirSync(src, { withFileTypes: true })) {
40644
+ import_node_fs9.default.mkdirSync(dest, { recursive: true });
40645
+ for (const entry of import_node_fs9.default.readdirSync(src, { withFileTypes: true })) {
40643
40646
  if (TEMPLATE_COPY_EXCLUDES.has(entry.name)) continue;
40644
40647
  const srcPath = import_node_path7.default.join(src, entry.name);
40645
40648
  const destName = entry.name.startsWith("_") ? `.${entry.name.slice(1)}` : entry.name;
40646
40649
  const destPath = import_node_path7.default.join(dest, destName);
40647
40650
  if (entry.isDirectory()) {
40648
40651
  copyDirSyncIfMissing(srcPath, destPath, added, base2);
40649
- } else if (!import_node_fs8.default.existsSync(destPath)) {
40650
- import_node_fs8.default.copyFileSync(srcPath, destPath);
40652
+ } else if (!import_node_fs9.default.existsSync(destPath)) {
40653
+ import_node_fs9.default.copyFileSync(srcPath, destPath);
40651
40654
  added.push(import_node_path7.default.relative(base2, destPath));
40652
40655
  }
40653
40656
  }
@@ -40673,7 +40676,7 @@ var DEV_PKG = "@dev.sail.money/sailor";
40673
40676
  function cliPackageInfo() {
40674
40677
  try {
40675
40678
  const pkg = JSON.parse(
40676
- import_node_fs9.default.readFileSync(import_node_path8.default.join(packageRoot(), "package.json"), "utf-8")
40679
+ import_node_fs10.default.readFileSync(import_node_path8.default.join(packageRoot(), "package.json"), "utf-8")
40677
40680
  );
40678
40681
  return {
40679
40682
  name: pkg.name ?? CANONICAL_PKG,
@@ -40690,10 +40693,13 @@ function scaffoldProjectWorkspace(dest, name, options) {
40690
40693
  return n;
40691
40694
  })() : null;
40692
40695
  const sailDir2 = import_node_path8.default.join(dest, ".sail");
40693
- import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "keys"), { recursive: true });
40694
- import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "runtime"), { recursive: true });
40695
- import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "state"), { recursive: true });
40696
- import_node_fs9.default.writeFileSync(
40696
+ import_node_fs10.default.mkdirSync(import_node_path8.default.join(sailDir2, "keys"), { recursive: true });
40697
+ import_node_fs10.default.mkdirSync(import_node_path8.default.join(sailDir2, "runtime"), { recursive: true });
40698
+ import_node_fs10.default.mkdirSync(import_node_path8.default.join(sailDir2, "state"), { recursive: true });
40699
+ const installMode = process.env.SAILOR_INSTALL_MODE === "docker" ? "docker" : "local";
40700
+ const _rawContainerName = process.env.SAILOR_CONTAINER_NAME ?? "agent";
40701
+ const containerName = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(_rawContainerName) ? _rawContainerName : "agent";
40702
+ import_node_fs10.default.writeFileSync(
40697
40703
  import_node_path8.default.join(sailDir2, "config.json"),
40698
40704
  `${JSON.stringify(
40699
40705
  {
@@ -40703,6 +40709,8 @@ function scaffoldProjectWorkspace(dest, name, options) {
40703
40709
  // null = chain not yet chosen; Stage 1 will set this
40704
40710
  stateDir: ".sail/state",
40705
40711
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
40712
+ installMode,
40713
+ ...installMode === "docker" ? { containerName } : {},
40706
40714
  contracts: {
40707
40715
  kernel: "",
40708
40716
  mandateFactory: ""
@@ -40718,7 +40726,7 @@ function scaffoldProjectWorkspace(dest, name, options) {
40718
40726
  const chainEntries = Object.values(chains);
40719
40727
  const perChainVarLines = chainEntries.map((c) => `# ${c.rpcEnvVar}=https://your-${c.name.toLowerCase().replace(/\s+/g, "-")}-endpoint`).join("\n");
40720
40728
  const chainIdExample = chainId != null ? `CHAIN_ID=${chainId}` : `# CHAIN_ID=8453 # set after choosing your chain in Stage 1`;
40721
- import_node_fs9.default.writeFileSync(
40729
+ import_node_fs10.default.writeFileSync(
40722
40730
  import_node_path8.default.join(dest, ".env.example"),
40723
40731
  `# Sailor agent environment
40724
40732
  #
@@ -40744,7 +40752,7 @@ ${perChainVarLines}
40744
40752
  const val = isActive && options.rpcUrl ? options.rpcUrl : `https://your-${c.name.toLowerCase().replace(/\s+/g, "-")}-endpoint`;
40745
40753
  return isActive && options.rpcUrl ? `${c.rpcEnvVar}=${val}` : `# ${c.rpcEnvVar}=${val}`;
40746
40754
  }).join("\n");
40747
- import_node_fs9.default.writeFileSync(
40755
+ import_node_fs10.default.writeFileSync(
40748
40756
  import_node_path8.default.join(sailDir2, ".env.local"),
40749
40757
  `# Real values \u2014 never commit this file.
40750
40758
  #
@@ -40771,8 +40779,8 @@ async function initCommand(dir, options = {}) {
40771
40779
  throw new Error(`Invalid template name: "${templateName}"`);
40772
40780
  }
40773
40781
  const templateSrc = import_node_path8.default.join(templatesDir, templateName);
40774
- const availableTemplates = () => import_node_fs9.default.existsSync(templatesDir) ? import_node_fs9.default.readdirSync(templatesDir).filter((e) => import_node_fs9.default.existsSync(import_node_path8.default.join(templatesDir, e, "package.json"))).join(", ") || "none" : "none";
40775
- if (!import_node_fs9.default.existsSync(templateSrc) || !import_node_fs9.default.existsSync(import_node_path8.default.join(templateSrc, "package.json"))) {
40782
+ const availableTemplates = () => import_node_fs10.default.existsSync(templatesDir) ? import_node_fs10.default.readdirSync(templatesDir).filter((e) => import_node_fs10.default.existsSync(import_node_path8.default.join(templatesDir, e, "package.json"))).join(", ") || "none" : "none";
40783
+ if (!import_node_fs10.default.existsSync(templateSrc) || !import_node_fs10.default.existsSync(import_node_path8.default.join(templateSrc, "package.json"))) {
40776
40784
  const available = availableTemplates();
40777
40785
  const hint = available === "none" ? `
40778
40786
  No templates found under ${templatesDir}.
@@ -40785,54 +40793,74 @@ run from the repo root.` : ` Available: ${available}`;
40785
40793
  if (!inPlace && !dest.startsWith(cwd + import_node_path8.default.sep) && dest !== cwd) {
40786
40794
  throw new Error(`Directory must be inside the current working directory`);
40787
40795
  }
40788
- if (!inPlace && import_node_fs9.default.existsSync(dest) && !options.force) {
40796
+ if (!inPlace && import_node_fs10.default.existsSync(dest) && !options.force) {
40789
40797
  throw new Error(`Directory already exists: ${dest}
40790
40798
  Pass --force to scaffold into it anyway (existing files with the same name are overwritten).`);
40791
40799
  }
40792
- if (inPlace && import_node_fs9.default.existsSync(import_node_path8.default.join(dest, ".sail", "config.json")) && !options.force) {
40800
+ if (inPlace && import_node_fs10.default.existsSync(import_node_path8.default.join(dest, ".sail", "config.json")) && !options.force) {
40793
40801
  throw new Error(
40794
40802
  "This project is already initialized.\nRun `sailor update` to re-sync template files, or `sailor init --force` to re-initialize (overwrites scaffold files; your .sail/keys/ and .sail/state/ are left in place)."
40795
40803
  );
40796
40804
  }
40805
+ const existingConfigPath = import_node_path8.default.join(dest, ".sail", "config.json");
40806
+ const previousConfig = import_node_fs10.default.existsSync(existingConfigPath) ? (() => {
40807
+ try {
40808
+ return JSON.parse(import_node_fs10.default.readFileSync(existingConfigPath, "utf-8"));
40809
+ } catch {
40810
+ return null;
40811
+ }
40812
+ })() : null;
40797
40813
  copyDirSync(templateSrc, dest);
40798
40814
  const pkgRoot = packageRoot();
40799
40815
  const examplesPermSrc = import_node_path8.default.join(pkgRoot, "examples", "permissions");
40800
- if (import_node_fs9.default.existsSync(examplesPermSrc)) {
40816
+ if (import_node_fs10.default.existsSync(examplesPermSrc)) {
40801
40817
  copyDirSync(examplesPermSrc, import_node_path8.default.join(dest, "examples", "permissions"));
40802
40818
  }
40803
40819
  const customMandateSrc = import_node_path8.default.join(pkgRoot, "examples", "custom-mandate");
40804
- if (import_node_fs9.default.existsSync(customMandateSrc)) {
40820
+ if (import_node_fs10.default.existsSync(customMandateSrc)) {
40805
40821
  copyDirSync(customMandateSrc, import_node_path8.default.join(dest, "examples", "custom-mandate"));
40806
40822
  }
40807
40823
  const permModelSrc = import_node_path8.default.join(pkgRoot, "docs", "PERMISSION_MODEL.md");
40808
- if (import_node_fs9.default.existsSync(permModelSrc)) {
40809
- import_node_fs9.default.mkdirSync(import_node_path8.default.join(dest, "docs"), { recursive: true });
40810
- writeIfMissing2(import_node_path8.default.join(dest, "docs", "PERMISSION_MODEL.md"), import_node_fs9.default.readFileSync(permModelSrc, "utf-8"));
40824
+ if (import_node_fs10.default.existsSync(permModelSrc)) {
40825
+ import_node_fs10.default.mkdirSync(import_node_path8.default.join(dest, "docs"), { recursive: true });
40826
+ writeIfMissing2(import_node_path8.default.join(dest, "docs", "PERMISSION_MODEL.md"), import_node_fs10.default.readFileSync(permModelSrc, "utf-8"));
40811
40827
  }
40812
40828
  const pkgPath = import_node_path8.default.join(dest, "package.json");
40813
- if (import_node_fs9.default.existsSync(pkgPath)) {
40814
- const pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgPath, "utf-8"));
40829
+ if (import_node_fs10.default.existsSync(pkgPath)) {
40830
+ const pkg = JSON.parse(import_node_fs10.default.readFileSync(pkgPath, "utf-8"));
40815
40831
  pkg.name = name;
40816
40832
  const devDeps = pkg.devDependencies ?? {};
40817
40833
  const { name: cliName, version: cliVer } = cliPackageInfo();
40818
40834
  devDeps[CANONICAL_PKG] = cliName === DEV_PKG ? `npm:${DEV_PKG}@^${cliVer}` : `^${cliVer}`;
40819
40835
  pkg.devDependencies = devDeps;
40820
- import_node_fs9.default.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
40836
+ import_node_fs10.default.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
40821
40837
  `);
40822
40838
  }
40823
40839
  const tsconfigPath = import_node_path8.default.join(dest, "tsconfig.json");
40824
- if (import_node_fs9.default.existsSync(tsconfigPath)) {
40825
- const tsconfig = JSON.parse(import_node_fs9.default.readFileSync(tsconfigPath, "utf-8"));
40840
+ if (import_node_fs10.default.existsSync(tsconfigPath)) {
40841
+ const tsconfig = JSON.parse(import_node_fs10.default.readFileSync(tsconfigPath, "utf-8"));
40826
40842
  const co = tsconfig.compilerOptions;
40827
40843
  if (co && "paths" in co) {
40828
40844
  delete co.paths;
40829
40845
  delete co.baseUrl;
40830
- import_node_fs9.default.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
40846
+ import_node_fs10.default.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
40831
40847
  `);
40832
40848
  }
40833
40849
  }
40834
40850
  scaffoldProjectWorkspace(dest, name, options);
40835
40851
  scaffoldFoundryWorkspace(dest);
40852
+ const newMode = process.env.SAILOR_INSTALL_MODE === "docker" ? "docker" : "local";
40853
+ if (previousConfig?.installMode === "docker" && newMode === "local") {
40854
+ const prev = previousConfig.containerName ?? "agent";
40855
+ console.log(`
40856
+ Switched to local install. If the Docker container is still running:`);
40857
+ console.log(` docker stop ${prev}`);
40858
+ console.log(`You can restart it anytime with the standard docker run command.`);
40859
+ } else if (previousConfig?.installMode === "local" && newMode === "docker") {
40860
+ const containerName = process.env.SAILOR_CONTAINER_NAME ?? "agent";
40861
+ console.log(`
40862
+ Switched to Docker install (container: ${containerName}).`);
40863
+ }
40836
40864
  printWelcome(
40837
40865
  dest,
40838
40866
  name,
@@ -40853,20 +40881,20 @@ function chainLabel(chainId) {
40853
40881
  }
40854
40882
  function detectState(dest) {
40855
40883
  try {
40856
- const configRaw = import_node_fs9.default.readFileSync(import_node_path8.default.join(dest, ".sail", "config.json"), "utf-8");
40884
+ const configRaw = import_node_fs10.default.readFileSync(import_node_path8.default.join(dest, ".sail", "config.json"), "utf-8");
40857
40885
  const config = JSON.parse(configRaw);
40858
40886
  const projectName = config.name ?? import_node_path8.default.basename(dest);
40859
40887
  const accountPath = import_node_path8.default.join(dest, ".sail", "account.json");
40860
- if (!import_node_fs9.default.existsSync(accountPath)) {
40888
+ if (!import_node_fs10.default.existsSync(accountPath)) {
40861
40889
  return { kind: "B", projectName, chain: chainLabel(config.chainId ?? 0) };
40862
40890
  }
40863
- const accountRaw = import_node_fs9.default.readFileSync(accountPath, "utf-8");
40891
+ const accountRaw = import_node_fs10.default.readFileSync(accountPath, "utf-8");
40864
40892
  const account2 = JSON.parse(accountRaw);
40865
40893
  const sma = account2.safe ?? "";
40866
40894
  const chain2 = chainLabel(account2.chainId ?? config.chainId ?? 0);
40867
40895
  let permissionCount = 0;
40868
40896
  try {
40869
- const mandatesRaw = import_node_fs9.default.readFileSync(
40897
+ const mandatesRaw = import_node_fs10.default.readFileSync(
40870
40898
  import_node_path8.default.join(dest, ".sail", "state", "mandates.json"),
40871
40899
  "utf-8"
40872
40900
  );
@@ -40943,13 +40971,19 @@ Created ${name}/`);
40943
40971
  "\u2551 If you skip this step, setup WILL break and you will have to \u2551",
40944
40972
  "\u2551 restart. There are no shortcuts. \u2551",
40945
40973
  "\u2551 \u2551",
40974
+ "\u2551 IF SAILOR IS RUNNING IN DOCKER: \u2551",
40975
+ "\u2551 \u2022 Read project files from your local filesystem \u2014 they are \u2551",
40976
+ "\u2551 shared via volume mount, do NOT use docker exec to read them. \u2551",
40977
+ "\u2551 \u2022 Prefix every sailor command with: \u2551",
40978
+ "\u2551 docker exec <containerName> sailor <command> \u2551",
40979
+ "\u2551 \u2551",
40946
40980
  "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
40947
40981
  ""
40948
40982
  ].join("\n"));
40949
40983
  }
40950
40984
 
40951
40985
  // src/commands/update.ts
40952
- var import_node_fs10 = __toESM(require("node:fs"), 1);
40986
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
40953
40987
  var import_node_path9 = __toESM(require("node:path"), 1);
40954
40988
  var UPDATE_PATHS = [
40955
40989
  ".agents",
@@ -40965,18 +40999,18 @@ var STALE_PATHS = [
40965
40999
  ];
40966
41000
  async function updateCommand() {
40967
41001
  const dest = process.cwd();
40968
- if (!import_node_fs10.default.existsSync(import_node_path9.default.join(dest, ".sail", "config.json"))) {
41002
+ if (!import_node_fs11.default.existsSync(import_node_path9.default.join(dest, ".sail", "config.json"))) {
40969
41003
  throw new Error("Not a sailor project \u2014 .sail/config.json not found. Run `sailor init` first.");
40970
41004
  }
40971
41005
  const templateSrc = import_node_path9.default.join(packageRoot(), "templates", "default");
40972
- if (!import_node_fs10.default.existsSync(templateSrc)) {
41006
+ if (!import_node_fs11.default.existsSync(templateSrc)) {
40973
41007
  throw new Error(`Template directory not found at ${templateSrc}`);
40974
41008
  }
40975
41009
  const removed = [];
40976
41010
  for (const p of STALE_PATHS) {
40977
41011
  const target = import_node_path9.default.join(dest, p);
40978
- if (import_node_fs10.default.existsSync(target)) {
40979
- import_node_fs10.default.rmSync(target, { recursive: true, force: true });
41012
+ if (import_node_fs11.default.existsSync(target)) {
41013
+ import_node_fs11.default.rmSync(target, { recursive: true, force: true });
40980
41014
  removed.push(p);
40981
41015
  }
40982
41016
  }
@@ -40984,18 +41018,53 @@ async function updateCommand() {
40984
41018
  for (const p of UPDATE_PATHS) {
40985
41019
  const src = import_node_path9.default.join(templateSrc, p);
40986
41020
  const dst = import_node_path9.default.join(dest, p);
40987
- if (!import_node_fs10.default.existsSync(src)) continue;
40988
- const stat = import_node_fs10.default.statSync(src);
41021
+ if (!import_node_fs11.default.existsSync(src)) continue;
41022
+ const stat = import_node_fs11.default.statSync(src);
40989
41023
  if (stat.isDirectory()) {
40990
41024
  copyDirSync(src, dst);
40991
41025
  } else {
40992
- import_node_fs10.default.mkdirSync(import_node_path9.default.dirname(dst), { recursive: true });
40993
- import_node_fs10.default.copyFileSync(src, dst);
41026
+ import_node_fs11.default.mkdirSync(import_node_path9.default.dirname(dst), { recursive: true });
41027
+ import_node_fs11.default.copyFileSync(src, dst);
40994
41028
  }
40995
41029
  updated.push(p);
40996
41030
  }
40997
41031
  const added = [];
40998
41032
  copyDirSyncIfMissing(templateSrc, dest, added);
41033
+ const configPath = import_node_path9.default.join(dest, ".sail", "config.json");
41034
+ try {
41035
+ const config = JSON.parse(import_node_fs11.default.readFileSync(configPath, "utf-8"));
41036
+ const newMode = process.env.SAILOR_INSTALL_MODE === "docker" ? "docker" : "local";
41037
+ const _rawContainerName = process.env.SAILOR_CONTAINER_NAME ?? "agent";
41038
+ const containerName = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(_rawContainerName) ? _rawContainerName : "agent";
41039
+ if (config.installMode !== newMode) {
41040
+ const previousMode = config.installMode;
41041
+ const previousContainer = config.containerName;
41042
+ config.installMode = newMode;
41043
+ if (newMode === "docker") {
41044
+ config.containerName = containerName;
41045
+ } else {
41046
+ delete config.containerName;
41047
+ }
41048
+ import_node_fs11.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
41049
+ `, "utf-8");
41050
+ if (previousMode === "docker" && newMode === "local") {
41051
+ const prev = previousContainer ?? "agent";
41052
+ console.log(`
41053
+ Switched to local install. If the Docker container is still running:`);
41054
+ console.log(` docker stop ${prev}`);
41055
+ console.log(`You can restart it anytime with the standard docker run command.`);
41056
+ } else if (newMode === "docker") {
41057
+ console.log(`
41058
+ Switched to Docker install (container: ${containerName}).`);
41059
+ }
41060
+ } else if (newMode === "docker" && config.containerName !== containerName) {
41061
+ config.containerName = containerName;
41062
+ import_node_fs11.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
41063
+ `, "utf-8");
41064
+ }
41065
+ } catch {
41066
+ console.warn("Warning: could not update install mode in .sail/config.json");
41067
+ }
40999
41068
  if (removed.length === 0 && updated.length === 0 && added.length === 0) {
41000
41069
  console.log("Nothing to update.");
41001
41070
  return;
@@ -41019,7 +41088,7 @@ Added (new in template):`);
41019
41088
  }
41020
41089
 
41021
41090
  // src/commands/keys.ts
41022
- var import_node_fs11 = __toESM(require("node:fs"), 1);
41091
+ var import_node_fs12 = __toESM(require("node:fs"), 1);
41023
41092
  var import_node_path10 = __toESM(require("node:path"), 1);
41024
41093
  function readStdin() {
41025
41094
  return new Promise((resolve3, reject) => {
@@ -41126,14 +41195,14 @@ async function keysExportCi() {
41126
41195
  );
41127
41196
  }
41128
41197
  const dest = import_node_path10.default.resolve(process.cwd(), "ci-keystore.json");
41129
- import_node_fs11.default.copyFileSync(src, dest);
41198
+ import_node_fs12.default.copyFileSync(src, dest);
41130
41199
  console.log(`\u2713 Keystore copied to ci-keystore.json`);
41131
41200
  console.log(` Source: ${src}`);
41132
41201
  const gitignorePath = import_node_path10.default.resolve(process.cwd(), ".gitignore");
41133
- if (import_node_fs11.default.existsSync(gitignorePath)) {
41134
- const content = import_node_fs11.default.readFileSync(gitignorePath, "utf-8");
41202
+ if (import_node_fs12.default.existsSync(gitignorePath)) {
41203
+ const content = import_node_fs12.default.readFileSync(gitignorePath, "utf-8");
41135
41204
  if (!content.includes("ci-keystore.json")) {
41136
- import_node_fs11.default.appendFileSync(
41205
+ import_node_fs12.default.appendFileSync(
41137
41206
  gitignorePath,
41138
41207
  "\n# CI keystore \u2014 encrypted agent wallet, safe to commit\n!ci-keystore.json\n"
41139
41208
  );
@@ -41171,7 +41240,7 @@ async function keysShow() {
41171
41240
 
41172
41241
  // src/commands/mandate-contracts.ts
41173
41242
  var import_node_child_process = require("node:child_process");
41174
- var import_node_fs12 = require("node:fs");
41243
+ var import_node_fs13 = require("node:fs");
41175
41244
  var import_node_path11 = require("node:path");
41176
41245
  init_esm2();
41177
41246
 
@@ -41303,7 +41372,7 @@ Project: ${project.name}`));
41303
41372
  if (!keyExists("manager")) {
41304
41373
  throw new Error('No agent wallet found. Run "sailor keys generate" and choose "agent wallet" first.');
41305
41374
  }
41306
- const agentSigner = await loadManagerSigner2();
41375
+ const agentSigner = await loadManagerSigner();
41307
41376
  const agentAddress = agentSigner.address;
41308
41377
  say(() => console.log("\u2713", `Agent wallet: ${agentAddress}`));
41309
41378
  const chain2 = getChainById(project.chainId);
@@ -41316,7 +41385,7 @@ Project: ${project.name}`));
41316
41385
  () => console.log(
41317
41386
  `
41318
41387
  \u2192 Open the Sailor dashboard to approve signing requests:
41319
- ${signingPageUrl(channel, projectPort(process.cwd()))}
41388
+ ${signingPageUrl(projectPort(process.cwd()))}
41320
41389
  `
41321
41390
  )
41322
41391
  );
@@ -41817,7 +41886,7 @@ Mandate command failed: ${msg}`), {
41817
41886
  process.exit(1);
41818
41887
  }
41819
41888
  function announceSigningUrl(json) {
41820
- const url = signingPageUrl(void 0, projectPort(process.cwd()));
41889
+ const url = signingPageUrl(projectPort(process.cwd()));
41821
41890
  if (json) {
41822
41891
  process.stdout.write(`${JSON.stringify({ status: "waiting_for_signature", url })}
41823
41892
  `);
@@ -41870,7 +41939,7 @@ async function runDeploy(project, channel, options) {
41870
41939
  if (options.argsFile) {
41871
41940
  const argsFilePath = (0, import_node_path11.resolve)(options.argsFile);
41872
41941
  try {
41873
- argsJson = (0, import_node_fs12.readFileSync)(argsFilePath, "utf8").trim();
41942
+ argsJson = (0, import_node_fs13.readFileSync)(argsFilePath, "utf8").trim();
41874
41943
  } catch {
41875
41944
  throw new Error(`Cannot read --args-file: ${argsFilePath}`);
41876
41945
  }
@@ -42078,7 +42147,7 @@ async function runDeployClone(project, channel, options) {
42078
42147
  }
42079
42148
  const chain2 = getChainById(project.chainId);
42080
42149
  const publicClient = publicClientFor(project);
42081
- const agentSigner = await loadManagerSigner2();
42150
+ const agentSigner = await loadManagerSigner();
42082
42151
  const registered = await publicClient.readContract({
42083
42152
  address: project.contracts.kernel,
42084
42153
  abi: SailKernelAbi,
@@ -42422,7 +42491,7 @@ Connect the owner wallet (mandate signer) in the browser \u2014 the agent wallet
42422
42491
  } catch (err) {
42423
42492
  if (err.message.startsWith("Security:")) throw err;
42424
42493
  }
42425
- const agentSigner = await loadManagerSigner2();
42494
+ const agentSigner = await loadManagerSigner();
42426
42495
  const walletClient = createWalletClient({
42427
42496
  account: agentSigner.viemAccount,
42428
42497
  chain: getChainById(project.chainId),
@@ -42464,7 +42533,7 @@ async function attachToSma(project, channel, publicClient, sma, mandate2, label,
42464
42533
  const say = (fn) => {
42465
42534
  if (!json) fn();
42466
42535
  };
42467
- const agentSigner = await loadManagerSigner2();
42536
+ const agentSigner = await loadManagerSigner();
42468
42537
  const registered = await publicClient.readContract({
42469
42538
  address: project.contracts.kernel,
42470
42539
  abi: SailKernelAbi,
@@ -42505,7 +42574,7 @@ async function attachBatchToSma(project, channel, publicClient, sma, permissions
42505
42574
  const say = (fn) => {
42506
42575
  if (!json) fn();
42507
42576
  };
42508
- const agentSigner = await loadManagerSigner2();
42577
+ const agentSigner = await loadManagerSigner();
42509
42578
  const registered = await publicClient.readContract({
42510
42579
  address: project.contracts.kernel,
42511
42580
  abi: SailKernelAbi,
@@ -42721,14 +42790,14 @@ Resolved: ${resolved}`
42721
42790
  }
42722
42791
  artifactPath = resolved;
42723
42792
  if (options.build) runForgeBuild();
42724
- if (!(0, import_node_fs12.existsSync)(artifactPath)) {
42793
+ if (!(0, import_node_fs13.existsSync)(artifactPath)) {
42725
42794
  ensureForgeHint();
42726
42795
  throw new Error(
42727
42796
  `Artifact not found: ${artifactPath}
42728
42797
  Compile your mandate first (e.g. \`forge build\`), or pass --build.`
42729
42798
  );
42730
42799
  }
42731
- const artifact = JSON.parse((0, import_node_fs12.readFileSync)(artifactPath, "utf8"));
42800
+ const artifact = JSON.parse((0, import_node_fs13.readFileSync)(artifactPath, "utf8"));
42732
42801
  const abi2 = artifact.abi;
42733
42802
  const bytecodeRaw = artifact.bytecode?.object ?? artifact.bytecode;
42734
42803
  if (!bytecodeRaw || typeof bytecodeRaw !== "string") {
@@ -42811,13 +42880,13 @@ function runForgeBuild() {
42811
42880
  init_esm2();
42812
42881
 
42813
42882
  // src/lib/permission-explainer.ts
42814
- var import_node_fs13 = require("node:fs");
42883
+ var import_node_fs14 = require("node:fs");
42815
42884
  var import_node_path12 = require("node:path");
42816
42885
  function explainPermission(name, sourcePath) {
42817
42886
  const resolved = sourcePath ?? (0, import_node_path12.join)(process.cwd(), "mandates", `${name}.sol`);
42818
42887
  let src;
42819
42888
  try {
42820
- src = (0, import_node_fs13.readFileSync)(resolved, "utf8");
42889
+ src = (0, import_node_fs14.readFileSync)(resolved, "utf8");
42821
42890
  } catch {
42822
42891
  return null;
42823
42892
  }
@@ -43039,7 +43108,7 @@ ${unregistered.length} permission(s) are not yet registered on this SMA. Initiat
43039
43108
  }
43040
43109
 
43041
43110
  // src/commands/mandate-simulate.ts
43042
- var import_node_fs14 = require("node:fs");
43111
+ var import_node_fs15 = require("node:fs");
43043
43112
  init_esm2();
43044
43113
  function parseExpect(raw, where) {
43045
43114
  if (raw === void 0) return void 0;
@@ -43055,7 +43124,7 @@ function resolveSampleCalls(options) {
43055
43124
  if (options.calls) {
43056
43125
  let raw;
43057
43126
  try {
43058
- raw = JSON.parse((0, import_node_fs14.readFileSync)(options.calls, "utf8"));
43127
+ raw = JSON.parse((0, import_node_fs15.readFileSync)(options.calls, "utf8"));
43059
43128
  } catch (err) {
43060
43129
  throw new Error(`Could not read --calls file "${options.calls}": ${err.message}`);
43061
43130
  }
@@ -43271,7 +43340,7 @@ async function mandateSimulate(options) {
43271
43340
  }
43272
43341
 
43273
43342
  // src/commands/rotate-signer.ts
43274
- var import_node_fs15 = require("node:fs");
43343
+ var import_node_fs16 = require("node:fs");
43275
43344
  init_esm2();
43276
43345
  var PENDING_REATTACH_FILE = ["state", "pending-reattach.json"];
43277
43346
  async function rotateSigner(options) {
@@ -43401,7 +43470,7 @@ SMA: ${smaAddress}`);
43401
43470
  console.log(
43402
43471
  `
43403
43472
  \u2192 Open the Sailor dashboard to approve signing requests:
43404
- ${signingPageUrl(channel, projectPort(process.cwd()))}
43473
+ ${signingPageUrl(projectPort(process.cwd()))}
43405
43474
  `
43406
43475
  );
43407
43476
  });
@@ -43659,7 +43728,7 @@ ensure the agent that signs dispatches holds this key.`
43659
43728
  const keystore = await keyring.exportKeystore(password);
43660
43729
  writeJsonFile(target, keystore, 384);
43661
43730
  const perManagerPath = managerKeystorePath(keyring.address);
43662
- (0, import_node_fs15.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43731
+ (0, import_node_fs16.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43663
43732
  writeJsonFile(perManagerPath, keystore, 384);
43664
43733
  say(
43665
43734
  () => console.log(
@@ -43671,7 +43740,7 @@ ensure the agent that signs dispatches holds this key.`
43671
43740
  function promoteManagerKeystore(newManager, say) {
43672
43741
  const stored = readJsonFile(managerKeystorePath(newManager));
43673
43742
  if (!stored) return;
43674
- (0, import_node_fs15.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43743
+ (0, import_node_fs16.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43675
43744
  const activeTarget = keyPath("manager");
43676
43745
  const displaced = readJsonFile(activeTarget);
43677
43746
  if (displaced?.address) {
@@ -43723,7 +43792,7 @@ function writePending(pending) {
43723
43792
  }
43724
43793
  function clearPending() {
43725
43794
  try {
43726
- (0, import_node_fs15.rmSync)(sailPath(...PENDING_REATTACH_FILE), { force: true });
43795
+ (0, import_node_fs16.rmSync)(sailPath(...PENDING_REATTACH_FILE), { force: true });
43727
43796
  } catch {
43728
43797
  }
43729
43798
  }
@@ -43777,7 +43846,7 @@ async function ownerConnect(options) {
43777
43846
  await channel.start();
43778
43847
  if (!options.json) {
43779
43848
  console.log("\u2192 Open the Sailor dashboard and connect your wallet:");
43780
- console.log(` ${signingPageUrl(channel, projectPort(projectRoot))}`);
43849
+ console.log(` ${signingPageUrl(projectPort(projectRoot))}`);
43781
43850
  if (channel.remote) console.log(" (using the running signing station)");
43782
43851
  console.log("\nWaiting for a wallet connection\u2026");
43783
43852
  }
@@ -43826,41 +43895,6 @@ var import_node_fs17 = __toESM(require("node:fs"), 1);
43826
43895
  var import_node_path13 = __toESM(require("node:path"), 1);
43827
43896
  var import_node_url = require("node:url");
43828
43897
  init_esm2();
43829
-
43830
- // src/lib/process.ts
43831
- var import_node_fs16 = __toESM(require("node:fs"), 1);
43832
- function agentPidPath(chainId) {
43833
- return chainId != null ? sailPath(`agent-${chainId}.pid`) : sailPath("agent.pid");
43834
- }
43835
- function writeAgentPid(chainId) {
43836
- import_node_fs16.default.mkdirSync(sailPath(), { recursive: true });
43837
- import_node_fs16.default.writeFileSync(agentPidPath(chainId), `${process.pid}
43838
- `);
43839
- }
43840
- function clearAgentPid(chainId) {
43841
- try {
43842
- import_node_fs16.default.rmSync(agentPidPath(chainId));
43843
- } catch {
43844
- }
43845
- }
43846
- function readAgentPid(chainId) {
43847
- try {
43848
- const pid = Number.parseInt(import_node_fs16.default.readFileSync(agentPidPath(chainId), "utf-8").trim(), 10);
43849
- return Number.isNaN(pid) ? null : pid;
43850
- } catch {
43851
- return null;
43852
- }
43853
- }
43854
- function isProcessAlive(pid) {
43855
- try {
43856
- process.kill(pid, 0);
43857
- return true;
43858
- } catch {
43859
- return false;
43860
- }
43861
- }
43862
-
43863
- // src/commands/run.ts
43864
43898
  var DEFAULT_INTERVAL_SEC = 60;
43865
43899
  var sleep2 = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
43866
43900
  var ERC20_READ_ABI = [
@@ -44905,7 +44939,7 @@ function requireAccount() {
44905
44939
  }
44906
44940
  return account2;
44907
44941
  }
44908
- async function sessionPause() {
44942
+ async function sessionPause(opts = {}) {
44909
44943
  const account2 = requireAccount();
44910
44944
  const signer = await loadAnySigner();
44911
44945
  const client = makeClient(account2.chainId);
@@ -44920,9 +44954,10 @@ async function sessionPause() {
44920
44954
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
44921
44955
  };
44922
44956
  writeJsonFile(sailPath("session.json"), session2);
44923
- console.log("Session paused \u2014 agent dispatch rights revoked.");
44957
+ if (opts.json) console.log(JSON.stringify(session2));
44958
+ else console.log("Session paused \u2014 agent dispatch rights revoked.");
44924
44959
  }
44925
- async function sessionResume() {
44960
+ async function sessionResume(opts = {}) {
44926
44961
  const account2 = requireAccount();
44927
44962
  const signer = await loadAnySigner();
44928
44963
  const client = makeClient(account2.chainId);
@@ -44937,7 +44972,8 @@ async function sessionResume() {
44937
44972
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
44938
44973
  };
44939
44974
  writeJsonFile(sailPath("session.json"), session2);
44940
- console.log("Session resumed \u2014 agent dispatch rights restored.");
44975
+ if (opts.json) console.log(JSON.stringify(session2));
44976
+ else console.log("Session resumed \u2014 agent dispatch rights restored.");
44941
44977
  }
44942
44978
 
44943
44979
  // src/commands/station.ts
@@ -45055,7 +45091,11 @@ async function status() {
45055
45091
  const hasManager = keyExists("manager");
45056
45092
  const hasPermissionSigner = keyExists("permissionSigner");
45057
45093
  const account2 = readJsonFile(sailPath("account.json"));
45058
- const mandate2 = readJsonFile(sailPath("mandate.json"));
45094
+ const mandateRaw = readJsonFile(sailPath("mandate.json"));
45095
+ const mandates = (Array.isArray(mandateRaw) ? mandateRaw : mandateRaw ? [mandateRaw] : []).filter(
45096
+ Boolean
45097
+ );
45098
+ const hasMandate = mandates.length > 0;
45059
45099
  const session2 = readJsonFile(sailPath("session.json"));
45060
45100
  console.log("Sailor status");
45061
45101
  console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
@@ -45071,9 +45111,12 @@ async function status() {
45071
45111
  console.log(' \u2717 not deployed run "sailor onboard --new-sma"');
45072
45112
  }
45073
45113
  console.log("Mandate:");
45074
- if (mandate2) {
45075
- const n = mandate2.permissions.length;
45076
- console.log(` \u2713 signed ${n} permission${n === 1 ? "" : "s"}`);
45114
+ if (hasMandate) {
45115
+ const n = mandates.reduce((sum, m) => sum + (m?.permissions?.length ?? 0), 0);
45116
+ const permLabel = `${n} permission${n === 1 ? "" : "s"}`;
45117
+ console.log(
45118
+ mandates.length > 1 ? ` \u2713 signed ${mandates.length} mandates, ${permLabel}` : ` \u2713 signed ${permLabel}`
45119
+ );
45077
45120
  } else {
45078
45121
  console.log(' \u2717 not signed run "sailor mandate sign"');
45079
45122
  }
@@ -45083,7 +45126,7 @@ async function status() {
45083
45126
  let agentState;
45084
45127
  if (running) {
45085
45128
  agentState = `running (PID ${pid})`;
45086
- } else if (!mandate2) {
45129
+ } else if (!hasMandate) {
45087
45130
  agentState = "not configured";
45088
45131
  } else if (session2 && session2.active === false) {
45089
45132
  agentState = "stopped (session paused)";
@@ -45094,10 +45137,52 @@ async function status() {
45094
45137
  }
45095
45138
 
45096
45139
  // src/commands/ui.ts
45097
- var import_node_child_process4 = require("node:child_process");
45140
+ var import_node_child_process5 = require("node:child_process");
45098
45141
  var import_node_fs20 = __toESM(require("node:fs"), 1);
45099
45142
  var import_node_net2 = __toESM(require("node:net"), 1);
45100
45143
  var import_node_path16 = __toESM(require("node:path"), 1);
45144
+
45145
+ // src/lib/tailscale.ts
45146
+ var import_node_child_process4 = require("node:child_process");
45147
+ function tailscaleAvailable() {
45148
+ try {
45149
+ return (0, import_node_child_process4.spawnSync)("tailscale", ["version"], { stdio: "ignore" }).status === 0;
45150
+ } catch {
45151
+ return false;
45152
+ }
45153
+ }
45154
+ function tailnetDnsName() {
45155
+ const r = (0, import_node_child_process4.spawnSync)("tailscale", ["status", "--json"], { encoding: "utf-8" });
45156
+ if (r.status !== 0 || !r.stdout) return null;
45157
+ try {
45158
+ const j = JSON.parse(r.stdout);
45159
+ if (j.BackendState && j.BackendState !== "Running") return null;
45160
+ const dns = j.Self?.DNSName;
45161
+ if (typeof dns !== "string" || dns.length === 0) return null;
45162
+ return dns.replace(/\.$/, "");
45163
+ } catch {
45164
+ return null;
45165
+ }
45166
+ }
45167
+ function tailscaleServeUp(port) {
45168
+ const r = (0, import_node_child_process4.spawnSync)(
45169
+ "tailscale",
45170
+ ["serve", "--bg", "--https=443", `http://127.0.0.1:${port}`],
45171
+ { encoding: "utf-8" }
45172
+ );
45173
+ if (r.status !== 0) {
45174
+ const detail = (r.stderr || r.stdout || "").trim() || `exit ${r.status ?? "?"}`;
45175
+ throw new Error(detail);
45176
+ }
45177
+ }
45178
+ function tailscaleServeDown() {
45179
+ try {
45180
+ (0, import_node_child_process4.spawnSync)("tailscale", ["serve", "--https=443", "off"], { stdio: "ignore" });
45181
+ } catch {
45182
+ }
45183
+ }
45184
+
45185
+ // src/commands/ui.ts
45101
45186
  var UI_STATE_FILE = import_node_path16.default.join(".sail", "runtime", "ui.json");
45102
45187
  function readState2(projectRoot) {
45103
45188
  const file = import_node_path16.default.join(projectRoot, UI_STATE_FILE);
@@ -45108,21 +45193,33 @@ function readState2(projectRoot) {
45108
45193
  return null;
45109
45194
  }
45110
45195
  }
45111
- function isAlive(pid) {
45196
+ function installedCliVersion() {
45112
45197
  try {
45113
- process.kill(pid, 0);
45114
- return true;
45198
+ const pkg = JSON.parse(
45199
+ import_node_fs20.default.readFileSync(import_node_path16.default.join(packageRoot(), "package.json"), "utf-8")
45200
+ );
45201
+ return pkg.version ?? null;
45115
45202
  } catch {
45116
- return false;
45203
+ return null;
45117
45204
  }
45118
45205
  }
45119
- function findFreePort(from14) {
45120
- return new Promise((resolve3, reject) => {
45121
- const server = import_node_net2.default.createServer();
45122
- server.unref();
45123
- server.on("error", () => findFreePort(from14 + 1).then(resolve3, reject));
45124
- server.listen(from14, "127.0.0.1", () => server.close(() => resolve3(from14)));
45125
- });
45206
+ async function warnIfVersionSkew(port) {
45207
+ const cli = installedCliVersion();
45208
+ if (!cli) return;
45209
+ try {
45210
+ const res = await fetch(`http://127.0.0.1:${port}/api/version`, {
45211
+ signal: AbortSignal.timeout(500)
45212
+ });
45213
+ if (!res.ok) return;
45214
+ const { running } = await res.json();
45215
+ if (running && running !== cli) {
45216
+ console.warn(
45217
+ `\u26A0 Version skew: the running dashboard is v${running}, but the installed CLI is v${cli}.
45218
+ Restart it to load the new version: sailor ui stop && sailor ui start`
45219
+ );
45220
+ }
45221
+ } catch {
45222
+ }
45126
45223
  }
45127
45224
  function waitForPort(port, timeoutMs) {
45128
45225
  const deadline = Date.now() + timeoutMs;
@@ -45142,32 +45239,63 @@ function waitForPort(port, timeoutMs) {
45142
45239
  attempt();
45143
45240
  });
45144
45241
  }
45145
- async function uiCommand() {
45242
+ async function uiCommand(opts = {}) {
45146
45243
  const distDir = cliDistDir();
45147
45244
  const uiDistDir = import_node_path16.default.join(packageRoot(), "packages", "ui", "dist");
45148
45245
  const serverBundle = import_node_path16.default.resolve(distDir, "server.cjs");
45149
45246
  const projectRoot = process.cwd();
45150
45247
  const sailDir2 = import_node_path16.default.join(projectRoot, ".sail");
45151
- const port = await findFreePort(projectPort(projectRoot));
45248
+ const envPort = Number(process.env.PORT);
45249
+ const port = await findFreePort(Number.isInteger(envPort) && envPort > 0 && envPort <= 65535 ? envPort : projectPort(projectRoot));
45152
45250
  if (!import_node_fs20.default.existsSync(serverBundle)) {
45153
45251
  throw new Error(`Server bundle not found at ${serverBundle}. Re-run the sailor build.`);
45154
45252
  }
45155
45253
  if (!import_node_fs20.default.existsSync(import_node_path16.default.join(uiDistDir, "index.html"))) {
45156
45254
  throw new Error(`UI dist not found at ${uiDistDir}. Re-run the sailor build.`);
45157
45255
  }
45256
+ let tailnetUrl = null;
45257
+ if (opts.expose) {
45258
+ if (opts.expose !== "tailscale") {
45259
+ throw new Error(`Unknown --expose mode "${opts.expose}". Supported: tailscale.`);
45260
+ }
45261
+ if (!tailscaleAvailable()) {
45262
+ throw new Error(
45263
+ "tailscale CLI not found. Install Tailscale and run `tailscale up`, or start the dashboard without --expose to keep it local-only."
45264
+ );
45265
+ }
45266
+ const dns = tailnetDnsName();
45267
+ if (!dns) {
45268
+ throw new Error(
45269
+ "Could not resolve this node's tailnet name. Is tailscale running and logged in? Run `tailscale status` to check."
45270
+ );
45271
+ }
45272
+ tailnetUrl = `https://${dns}`;
45273
+ }
45158
45274
  const existing = readState2(projectRoot);
45159
- if (existing && isAlive(existing.pid)) {
45275
+ if (existing && isProcessAlive(existing.pid)) {
45160
45276
  console.log(`Sailor UI is already running (pid ${existing.pid}) at http://localhost:${existing.port}`);
45277
+ if (opts.expose) {
45278
+ console.log("To expose it on the tailnet, stop it first: sailor ui stop && sailor ui start --expose tailscale");
45279
+ }
45280
+ await warnIfVersionSkew(existing.port);
45161
45281
  return;
45162
45282
  }
45163
45283
  const runtimeDir = import_node_path16.default.join(projectRoot, ".sail", "runtime");
45164
45284
  import_node_fs20.default.mkdirSync(runtimeDir, { recursive: true });
45165
45285
  const logFile = import_node_path16.default.join(runtimeDir, "ui.log");
45166
45286
  const logFd = import_node_fs20.default.openSync(logFile, "a");
45167
- const child = (0, import_node_child_process4.spawn)(process.execPath, [serverBundle], {
45287
+ const corsOrigins = [process.env.SAILOR_CORS_ORIGINS, tailnetUrl].filter(Boolean).join(",");
45288
+ const child = (0, import_node_child_process5.spawn)(process.execPath, [serverBundle], {
45168
45289
  detached: true,
45169
45290
  stdio: ["ignore", logFd, logFd],
45170
- env: { ...process.env, SAIL_DIR: sailDir2, SERVE_DIST: "1", PORT: String(port), SAILOR_UI_DIST: uiDistDir }
45291
+ env: {
45292
+ ...process.env,
45293
+ SAIL_DIR: sailDir2,
45294
+ SERVE_DIST: "1",
45295
+ PORT: String(port),
45296
+ SAILOR_UI_DIST: uiDistDir,
45297
+ ...corsOrigins ? { SAILOR_CORS_ORIGINS: corsOrigins } : {}
45298
+ }
45171
45299
  });
45172
45300
  child.unref();
45173
45301
  import_node_fs20.default.closeSync(logFd);
@@ -45175,7 +45303,7 @@ async function uiCommand() {
45175
45303
  const deadline = Date.now() + READY_TIMEOUT_MS;
45176
45304
  let ready = false;
45177
45305
  while (Date.now() < deadline) {
45178
- if (!isAlive(child.pid)) break;
45306
+ if (!isProcessAlive(child.pid)) break;
45179
45307
  if (await waitForPort(port, 500)) {
45180
45308
  ready = true;
45181
45309
  break;
@@ -45194,17 +45322,31 @@ Server output:
45194
45322
  ${tail}` : ` See ${import_node_path16.default.relative(projectRoot, logFile)}.`)
45195
45323
  );
45196
45324
  }
45325
+ let exposed = false;
45326
+ if (tailnetUrl) {
45327
+ try {
45328
+ tailscaleServeUp(port);
45329
+ exposed = true;
45330
+ } catch (err) {
45331
+ console.warn(
45332
+ `\u26A0 Could not expose on the tailnet: ${err.message}
45333
+ The dashboard is still running locally.`
45334
+ );
45335
+ }
45336
+ }
45197
45337
  import_node_fs20.default.writeFileSync(
45198
45338
  import_node_path16.default.join(projectRoot, UI_STATE_FILE),
45199
- JSON.stringify({ pid: child.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
45339
+ JSON.stringify({ pid: child.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString(), exposed }, null, 2)
45200
45340
  );
45201
45341
  console.log(`Sailor UI started at http://localhost:${port} (pid ${child.pid})`);
45342
+ if (exposed) console.log(`Exposed on your tailnet at ${tailnetUrl}/`);
45202
45343
  console.log(`Stop it with: sailor ui stop`);
45203
45344
  }
45204
- function uiStatus() {
45345
+ async function uiStatus() {
45205
45346
  const state = readState2(process.cwd());
45206
- if (state && isAlive(state.pid)) {
45347
+ if (state && isProcessAlive(state.pid)) {
45207
45348
  console.log(`\u25CF running http://localhost:${state.port} (pid ${state.pid})`);
45349
+ await warnIfVersionSkew(state.port);
45208
45350
  } else {
45209
45351
  if (state) import_node_fs20.default.rmSync(import_node_path16.default.join(process.cwd(), UI_STATE_FILE), { force: true });
45210
45352
  console.log("\u25CB Sailor UI is not running");
@@ -45217,12 +45359,16 @@ function uiStop() {
45217
45359
  console.log("Sailor UI is not running.");
45218
45360
  return;
45219
45361
  }
45220
- if (!isAlive(state.pid)) {
45362
+ if (!isProcessAlive(state.pid)) {
45221
45363
  import_node_fs20.default.rmSync(import_node_path16.default.join(projectRoot, UI_STATE_FILE), { force: true });
45222
45364
  console.log("Sailor UI is not running (stale state file removed).");
45223
45365
  return;
45224
45366
  }
45225
45367
  process.kill(state.pid, "SIGTERM");
45368
+ if (state.exposed) {
45369
+ tailscaleServeDown();
45370
+ console.log("Tailnet exposure removed.");
45371
+ }
45226
45372
  import_node_fs20.default.rmSync(import_node_path16.default.join(projectRoot, UI_STATE_FILE), { force: true });
45227
45373
  console.log(`Stopped Sailor UI (pid ${state.pid}).`);
45228
45374
  }
@@ -45274,7 +45420,7 @@ program2.command("init [dir]").description("Scaffold a new Sail agent into the c
45274
45420
  );
45275
45421
  program2.command("update").description("Re-sync agent tooling files (skills, AGENTS.md, Dockerfile) from the latest template").action(action(updateCommand));
45276
45422
  var ui = program2.command("ui").description("Manage the local Sailor dashboard");
45277
- ui.command("start").description("Start the dashboard at localhost:3333 (default)").action(action(uiCommand));
45423
+ ui.command("start").description("Start the dashboard at localhost:3333 (default)").option("--expose <mode>", "Expose the dashboard over HTTPS on your tailnet (mode: tailscale)").action(actionWith(uiCommand));
45278
45424
  ui.command("stop").description("Stop the running dashboard").action(() => uiStop());
45279
45425
  ui.command("status").description("Show whether the dashboard is running").action(() => uiStatus());
45280
45426
  ui.action(action(uiCommand));
@@ -45339,8 +45485,8 @@ program2.command("run").description("Run the agent execution loop (use --once fo
45339
45485
  var trigger = program2.command("trigger").description("Wake the agent on demand from an external system");
45340
45486
  trigger.command("github").description("Fire the agent's GitHub Actions workflow_dispatch (the same job the cron runs)").option("--workflow <file>", "Workflow file to dispatch", "agent-tick.yml").option("--ref <branch>", "Git ref to run the workflow on", "main").option("--reason <text>", "Why this run fired \u2014 recorded as the workflow's reason input").option("--repo <owner/repo>", "Override the repository (default: from the git origin remote)").option("--json", "Emit machine-readable JSON").action(actionWith(triggerGithub));
45341
45487
  var session = program2.command("session").description("Control the agent session");
45342
- session.command("pause").description("Pause the agent session (revoke dispatch rights)").action(action(sessionPause));
45343
- session.command("resume").description("Resume a paused session").action(action(sessionResume));
45488
+ session.command("pause").description("Pause the agent session (revoke dispatch rights)").option("--json", "Emit machine-readable JSON").action(actionWith(sessionPause));
45489
+ session.command("resume").description("Resume a paused session").option("--json", "Emit machine-readable JSON").action(actionWith(sessionResume));
45344
45490
  program2.command("doctor").description(
45345
45491
  "Preflight (read-only): kernel model, permission health, RPC + gas balances, before spending gas"
45346
45492
  ).option("--json", "Output machine-readable JSON").option("--account <address>", "SMA to check (defaults to .sail/account.json)").action(actionWith(doctor));