@dev.sail.money/sailor 1.2.0-74 → 1.2.0-76

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.
@@ -966,8 +966,8 @@ var require_command = __commonJS({
966
966
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports2) {
967
967
  var EventEmitter = require("node:events").EventEmitter;
968
968
  var childProcess = require("node:child_process");
969
- var path9 = require("node:path");
970
- var fs10 = require("node:fs");
969
+ var path11 = require("node:path");
970
+ var fs12 = require("node:fs");
971
971
  var process2 = require("node:process");
972
972
  var { Argument: Argument2, humanReadableArgName } = require_argument();
973
973
  var { CommanderError: CommanderError2 } = require_error();
@@ -1899,11 +1899,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1899
1899
  let launchWithNode = false;
1900
1900
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1901
1901
  function findFile(baseDir, baseName) {
1902
- const localBin = path9.resolve(baseDir, baseName);
1903
- if (fs10.existsSync(localBin)) return localBin;
1904
- if (sourceExt.includes(path9.extname(baseName))) return void 0;
1902
+ const localBin = path11.resolve(baseDir, baseName);
1903
+ if (fs12.existsSync(localBin)) return localBin;
1904
+ if (sourceExt.includes(path11.extname(baseName))) return void 0;
1905
1905
  const foundExt = sourceExt.find(
1906
- (ext) => fs10.existsSync(`${localBin}${ext}`)
1906
+ (ext) => fs12.existsSync(`${localBin}${ext}`)
1907
1907
  );
1908
1908
  if (foundExt) return `${localBin}${foundExt}`;
1909
1909
  return void 0;
@@ -1915,21 +1915,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1915
1915
  if (this._scriptPath) {
1916
1916
  let resolvedScriptPath;
1917
1917
  try {
1918
- resolvedScriptPath = fs10.realpathSync(this._scriptPath);
1918
+ resolvedScriptPath = fs12.realpathSync(this._scriptPath);
1919
1919
  } catch (err) {
1920
1920
  resolvedScriptPath = this._scriptPath;
1921
1921
  }
1922
- executableDir = path9.resolve(
1923
- path9.dirname(resolvedScriptPath),
1922
+ executableDir = path11.resolve(
1923
+ path11.dirname(resolvedScriptPath),
1924
1924
  executableDir
1925
1925
  );
1926
1926
  }
1927
1927
  if (executableDir) {
1928
1928
  let localFile = findFile(executableDir, executableFile);
1929
1929
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1930
- const legacyName = path9.basename(
1930
+ const legacyName = path11.basename(
1931
1931
  this._scriptPath,
1932
- path9.extname(this._scriptPath)
1932
+ path11.extname(this._scriptPath)
1933
1933
  );
1934
1934
  if (legacyName !== this._name) {
1935
1935
  localFile = findFile(
@@ -1940,7 +1940,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1940
1940
  }
1941
1941
  executableFile = localFile || executableFile;
1942
1942
  }
1943
- launchWithNode = sourceExt.includes(path9.extname(executableFile));
1943
+ launchWithNode = sourceExt.includes(path11.extname(executableFile));
1944
1944
  let proc;
1945
1945
  if (process2.platform !== "win32") {
1946
1946
  if (launchWithNode) {
@@ -2780,7 +2780,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2780
2780
  * @return {Command}
2781
2781
  */
2782
2782
  nameFromFilename(filename) {
2783
- this._name = path9.basename(filename, path9.extname(filename));
2783
+ this._name = path11.basename(filename, path11.extname(filename));
2784
2784
  return this;
2785
2785
  }
2786
2786
  /**
@@ -2794,9 +2794,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2794
2794
  * @param {string} [path]
2795
2795
  * @return {(string|null|Command)}
2796
2796
  */
2797
- executableDir(path10) {
2798
- if (path10 === void 0) return this._executableDir;
2799
- this._executableDir = path10;
2797
+ executableDir(path12) {
2798
+ if (path12 === void 0) return this._executableDir;
2799
+ this._executableDir = path12;
2800
2800
  return this;
2801
2801
  }
2802
2802
  /**
@@ -30772,9 +30772,9 @@ var init_defineKzg = __esm({
30772
30772
  });
30773
30773
 
30774
30774
  // ../../node_modules/.pnpm/viem@2.51.3_bufferutil@4.1.0_typescript@5.9.3_utf-8-validate@5.0.10_zod@4.4.3/node_modules/viem/_esm/utils/kzg/setupKzg.js
30775
- function setupKzg(parameters, path9) {
30775
+ function setupKzg(parameters, path11) {
30776
30776
  try {
30777
- parameters.loadTrustedSetup(path9);
30777
+ parameters.loadTrustedSetup(path11);
30778
30778
  } catch (e) {
30779
30779
  const error = e;
30780
30780
  if (!error.message.includes("trusted setup is already loaded"))
@@ -35190,8 +35190,8 @@ var require_websocket_server2 = __commonJS({
35190
35190
  });
35191
35191
 
35192
35192
  // src/index.ts
35193
- var import_node_fs19 = require("node:fs");
35194
- var import_node_path15 = require("node:path");
35193
+ var import_node_fs21 = require("node:fs");
35194
+ var import_node_path17 = require("node:path");
35195
35195
 
35196
35196
  // ../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
35197
35197
  var import_index = __toESM(require_commander(), 1);
@@ -37123,14 +37123,14 @@ var HDKey = class _HDKey {
37123
37123
  }
37124
37124
  this.pubHash = hash160(this.pubKey);
37125
37125
  }
37126
- derive(path9) {
37127
- if (!/^[mM]'?/.test(path9)) {
37126
+ derive(path11) {
37127
+ if (!/^[mM]'?/.test(path11)) {
37128
37128
  throw new Error('Path must start with "m" or "M"');
37129
37129
  }
37130
- if (/^[mM]'?$/.test(path9)) {
37130
+ if (/^[mM]'?$/.test(path11)) {
37131
37131
  return this;
37132
37132
  }
37133
- const parts = path9.replace(/^[mM]'?\//, "").split("/");
37133
+ const parts = path11.replace(/^[mM]'?\//, "").split("/");
37134
37134
  let child = this;
37135
37135
  for (const c of parts) {
37136
37136
  const m = /^(\d+)('?)$/.exec(c);
@@ -37462,8 +37462,8 @@ function privateKeyToAccount(privateKey, options = {}) {
37462
37462
  }
37463
37463
 
37464
37464
  // ../../node_modules/.pnpm/viem@2.51.3_bufferutil@4.1.0_typescript@5.9.3_utf-8-validate@5.0.10_zod@4.4.3/node_modules/viem/_esm/accounts/hdKeyToAccount.js
37465
- function hdKeyToAccount(hdKey_, { accountIndex = 0, addressIndex = 0, changeIndex = 0, path: path9, ...options } = {}) {
37466
- const hdKey = hdKey_.derive(path9 || `m/44'/60'/${accountIndex}'/${changeIndex}/${addressIndex}`);
37465
+ function hdKeyToAccount(hdKey_, { accountIndex = 0, addressIndex = 0, changeIndex = 0, path: path11, ...options } = {}) {
37466
+ const hdKey = hdKey_.derive(path11 || `m/44'/60'/${accountIndex}'/${changeIndex}/${addressIndex}`);
37467
37467
  const account2 = privateKeyToAccount(toHex(hdKey.privateKey), options);
37468
37468
  return {
37469
37469
  ...account2,
@@ -37564,8 +37564,8 @@ var LocalKeyring = class _LocalKeyring {
37564
37564
  return _LocalKeyring.fromPrivateKey(`0x${pkBytes.toString("hex")}`);
37565
37565
  }
37566
37566
  /** Loads a keyring from an encrypted JSON keystore file on disk. */
37567
- static async fromKeystoreFile(path9, password) {
37568
- const keystore = JSON.parse((0, import_node_fs.readFileSync)(path9, "utf-8"));
37567
+ static async fromKeystoreFile(path11, password) {
37568
+ const keystore = JSON.parse((0, import_node_fs.readFileSync)(path11, "utf-8"));
37569
37569
  return _LocalKeyring.fromKeystore(keystore, password);
37570
37570
  }
37571
37571
  /** Signs a raw 32-byte hash. Returns a 65-byte ECDSA signature. */
@@ -38686,9 +38686,13 @@ var SigningServer = class {
38686
38686
  http2.once("error", rej);
38687
38687
  });
38688
38688
  this.httpServer = http2;
38689
- if (this.advertise) this.writeRuntimeState();
38689
+ if (this.advertise) {
38690
+ reapStaleRuntimeState(this.projectRoot);
38691
+ this.writeRuntimeState();
38692
+ }
38690
38693
  process.once("SIGINT", () => this.stop());
38691
38694
  process.once("SIGTERM", () => this.stop());
38695
+ process.once("exit", () => this.stop());
38692
38696
  }
38693
38697
  stop() {
38694
38698
  for (const [id, entry] of this.pending) {
@@ -38833,6 +38837,28 @@ var SigningServer = class {
38833
38837
  sailFile(...segments) {
38834
38838
  return (0, import_node_path4.join)(this.projectRoot, ".sail", ...segments);
38835
38839
  }
38840
+ /**
38841
+ * Persist the active chain into config.json. The onboarding stage machine keys
38842
+ * off config.json.chainId; leaving it null after SMA creation misclassifies the
38843
+ * stage on resume. Best-effort — never blocks the account save on a config write.
38844
+ */
38845
+ syncConfigChainId(chainId) {
38846
+ if (chainId == null) return;
38847
+ try {
38848
+ const path11 = this.sailFile("config.json");
38849
+ let config = {};
38850
+ try {
38851
+ config = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf-8"));
38852
+ } catch {
38853
+ }
38854
+ if (Number(config.chainId) === Number(chainId)) return;
38855
+ config.chainId = Number(chainId);
38856
+ (0, import_node_fs5.mkdirSync)(this.sailFile(), { recursive: true });
38857
+ (0, import_node_fs5.writeFileSync)(path11, `${JSON.stringify(config, null, 2)}
38858
+ `);
38859
+ } catch {
38860
+ }
38861
+ }
38836
38862
  /** Stream a JSON file back, or a fallback body when it is missing/invalid. */
38837
38863
  sendJsonFile(res, filePath, fallback2) {
38838
38864
  try {
@@ -38875,6 +38901,7 @@ var SigningServer = class {
38875
38901
  (0, import_node_fs5.mkdirSync)(baseSailDir, { recursive: true });
38876
38902
  (0, import_node_fs5.writeFileSync)(this.sailFile("account.json"), `${JSON.stringify(record, null, 2)}
38877
38903
  `);
38904
+ this.syncConfigChainId(chainId);
38878
38905
  res.writeHead(200, { "Content-Type": "application/json" });
38879
38906
  res.end(JSON.stringify({ ok: true }));
38880
38907
  }).catch((err) => {
@@ -39123,6 +39150,14 @@ var SigningServer = class {
39123
39150
  }
39124
39151
  }
39125
39152
  writeRuntimeState() {
39153
+ const path11 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39154
+ if ((0, import_node_fs5.existsSync)(path11)) {
39155
+ try {
39156
+ const existing = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39157
+ if (existing.pid != null && existing.pid !== process.pid && pidAlive(existing.pid)) return;
39158
+ } catch {
39159
+ }
39160
+ }
39126
39161
  if (!(0, import_node_fs5.existsSync)(this.runtimeDir)) (0, import_node_fs5.mkdirSync)(this.runtimeDir, { recursive: true });
39127
39162
  (0, import_node_fs5.writeFileSync)(
39128
39163
  (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE),
@@ -39141,13 +39176,35 @@ var SigningServer = class {
39141
39176
  );
39142
39177
  }
39143
39178
  removeRuntimeState() {
39144
- const path9 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39179
+ const path11 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39145
39180
  try {
39146
- if ((0, import_node_fs5.existsSync)(path9)) (0, import_node_fs5.unlinkSync)(path9);
39181
+ if (!(0, import_node_fs5.existsSync)(path11)) return;
39182
+ const state = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39183
+ if (state.pid != null && state.pid !== process.pid) return;
39184
+ (0, import_node_fs5.unlinkSync)(path11);
39147
39185
  } catch {
39148
39186
  }
39149
39187
  }
39150
39188
  };
39189
+ function pidAlive(pid) {
39190
+ try {
39191
+ process.kill(pid, 0);
39192
+ return true;
39193
+ } catch (err) {
39194
+ return err.code === "EPERM";
39195
+ }
39196
+ }
39197
+ function reapStaleRuntimeState(projectRoot = process.cwd()) {
39198
+ const path11 = (0, import_node_path4.join)(projectRoot, RUNTIME_SUBDIR, SERVER_STATE_FILE);
39199
+ try {
39200
+ if (!(0, import_node_fs5.existsSync)(path11)) return;
39201
+ const state = JSON.parse((0, import_node_fs5.readFileSync)(path11, "utf8"));
39202
+ if (state.pid != null && state.pid !== process.pid && !pidAlive(state.pid)) {
39203
+ (0, import_node_fs5.unlinkSync)(path11);
39204
+ }
39205
+ } catch {
39206
+ }
39207
+ }
39151
39208
  async function findAvailablePort(startPort) {
39152
39209
  return new Promise((res) => {
39153
39210
  const probe = (0, import_node_net.createServer)();
@@ -39256,9 +39313,10 @@ async function discoverDaemon(projectRoot = process.cwd()) {
39256
39313
  return await client.ping() ? client : null;
39257
39314
  }
39258
39315
  async function createSigningChannel(projectRoot = process.cwd()) {
39316
+ reapStaleRuntimeState(projectRoot);
39259
39317
  const daemon = await discoverDaemon(projectRoot);
39260
39318
  if (daemon) return daemon;
39261
- return new SigningServer({ projectRoot, advertise: false });
39319
+ return new SigningServer({ projectRoot, advertise: true });
39262
39320
  }
39263
39321
 
39264
39322
  // src/commands/account.ts
@@ -40121,15 +40179,20 @@ async function resolvePermissionForBatch(params) {
40121
40179
  }
40122
40180
 
40123
40181
  // src/commands/doctor.ts
40124
- var LOW_GAS_THRESHOLD_WEI = 500000000000000n;
40125
- async function nativeBalance(pc, address) {
40182
+ var LOW_GAS_THRESHOLD_L1_WEI = 5000000000000000n;
40183
+ var LOW_GAS_THRESHOLD_L2_WEI = 200000000000000n;
40184
+ var L1_GAS_CHAINS = /* @__PURE__ */ new Set([1, 11155111]);
40185
+ function lowGasThresholdWei(chainId) {
40186
+ return L1_GAS_CHAINS.has(chainId) ? LOW_GAS_THRESHOLD_L1_WEI : LOW_GAS_THRESHOLD_L2_WEI;
40187
+ }
40188
+ async function nativeBalance(pc, address, chainId) {
40126
40189
  const wei = await pc.getBalance({ address });
40127
40190
  return {
40128
40191
  address,
40129
40192
  wei: wei.toString(),
40130
40193
  eth: formatEther(wei),
40131
40194
  funded: wei > 0n,
40132
- low: wei > 0n && wei < LOW_GAS_THRESHOLD_WEI
40195
+ low: wei > 0n && wei < lowGasThresholdWei(chainId)
40133
40196
  };
40134
40197
  }
40135
40198
  function keystoreAddress(role, safe) {
@@ -40208,8 +40271,8 @@ async function doctor(options = {}) {
40208
40271
  let ownerBal = null;
40209
40272
  let managerBal = null;
40210
40273
  try {
40211
- if (ownerAddr) ownerBal = await nativeBalance(pc, ownerAddr);
40212
- if (managerAddr) managerBal = await nativeBalance(pc, managerAddr);
40274
+ if (ownerAddr) ownerBal = await nativeBalance(pc, ownerAddr, chainId);
40275
+ if (managerAddr) managerBal = await nativeBalance(pc, managerAddr, chainId);
40213
40276
  } catch {
40214
40277
  }
40215
40278
  if (options.json) {
@@ -40390,8 +40453,8 @@ Probe is heuristic: an unknown selector (${PROBE_SELECTOR}) to a neutral target
40390
40453
  }
40391
40454
 
40392
40455
  // src/commands/init.ts
40393
- var import_node_fs8 = __toESM(require("node:fs"), 1);
40394
- var import_node_path7 = __toESM(require("node:path"), 1);
40456
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
40457
+ var import_node_path8 = __toESM(require("node:path"), 1);
40395
40458
 
40396
40459
  // src/lib/foundry.ts
40397
40460
  var import_node_fs7 = require("node:fs");
@@ -40557,11 +40620,13 @@ function scaffoldFoundryWorkspace(root) {
40557
40620
  writeIfMissing((0, import_node_path6.join)(root, "mandates", "BoundedCallPermission.sol"), EXAMPLE_MANDATE_SOL);
40558
40621
  writeIfMissing((0, import_node_path6.join)(root, "mandates", "README.md"), MANDATES_README);
40559
40622
  }
40560
- function writeIfMissing(path9, content) {
40561
- if (!(0, import_node_fs7.existsSync)(path9)) (0, import_node_fs7.writeFileSync)(path9, content, "utf8");
40623
+ function writeIfMissing(path11, content) {
40624
+ if (!(0, import_node_fs7.existsSync)(path11)) (0, import_node_fs7.writeFileSync)(path11, content, "utf8");
40562
40625
  }
40563
40626
 
40564
- // src/commands/init.ts
40627
+ // src/lib/template.ts
40628
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
40629
+ var import_node_path7 = __toESM(require("node:path"), 1);
40565
40630
  var TEMPLATE_COPY_EXCLUDES = /* @__PURE__ */ new Set([
40566
40631
  "node_modules",
40567
40632
  "dist",
@@ -40584,6 +40649,26 @@ function copyDirSync(src, dest) {
40584
40649
  }
40585
40650
  }
40586
40651
  }
40652
+ function writeIfMissing2(file, content) {
40653
+ if (!import_node_fs8.default.existsSync(file)) import_node_fs8.default.writeFileSync(file, content, "utf-8");
40654
+ }
40655
+ function copyDirSyncIfMissing(src, dest, added = [], base2 = dest) {
40656
+ import_node_fs8.default.mkdirSync(dest, { recursive: true });
40657
+ for (const entry of import_node_fs8.default.readdirSync(src, { withFileTypes: true })) {
40658
+ if (TEMPLATE_COPY_EXCLUDES.has(entry.name)) continue;
40659
+ const srcPath = import_node_path7.default.join(src, entry.name);
40660
+ const destName = entry.name.startsWith("_") ? `.${entry.name.slice(1)}` : entry.name;
40661
+ const destPath = import_node_path7.default.join(dest, destName);
40662
+ if (entry.isDirectory()) {
40663
+ copyDirSyncIfMissing(srcPath, destPath, added, base2);
40664
+ } else if (!import_node_fs8.default.existsSync(destPath)) {
40665
+ import_node_fs8.default.copyFileSync(srcPath, destPath);
40666
+ added.push(import_node_path7.default.relative(base2, destPath));
40667
+ }
40668
+ }
40669
+ }
40670
+
40671
+ // src/commands/init.ts
40587
40672
  var SAIL_WORKSPACE_README = `# Sailor Project Workspace
40588
40673
 
40589
40674
  This folder is the local workspace for one Sailor agent deployment.
@@ -40598,15 +40683,12 @@ This folder is the local workspace for one Sailor agent deployment.
40598
40683
  AI coding agents should read the project's \`AGENTS.md\` and this folder's \`config.json\`
40599
40684
  before changing strategy code or running commands that touch funds.
40600
40685
  `;
40601
- function writeIfMissing2(file, content) {
40602
- if (!import_node_fs8.default.existsSync(file)) import_node_fs8.default.writeFileSync(file, content, "utf-8");
40603
- }
40604
40686
  var CANONICAL_PKG = "@sail.money/sailor";
40605
40687
  var DEV_PKG = "@dev.sail.money/sailor";
40606
40688
  function cliPackageInfo() {
40607
40689
  try {
40608
40690
  const pkg = JSON.parse(
40609
- import_node_fs8.default.readFileSync(import_node_path7.default.join(packageRoot(), "package.json"), "utf-8")
40691
+ import_node_fs9.default.readFileSync(import_node_path8.default.join(packageRoot(), "package.json"), "utf-8")
40610
40692
  );
40611
40693
  return {
40612
40694
  name: pkg.name ?? CANONICAL_PKG,
@@ -40622,12 +40704,12 @@ function scaffoldProjectWorkspace(dest, name, options) {
40622
40704
  if (!Number.isInteger(n) || n <= 0) throw new Error(`Invalid chain id: "${options.chain}"`);
40623
40705
  return n;
40624
40706
  })() : null;
40625
- const sailDir2 = import_node_path7.default.join(dest, ".sail");
40626
- import_node_fs8.default.mkdirSync(import_node_path7.default.join(sailDir2, "keys"), { recursive: true });
40627
- import_node_fs8.default.mkdirSync(import_node_path7.default.join(sailDir2, "runtime"), { recursive: true });
40628
- import_node_fs8.default.mkdirSync(import_node_path7.default.join(sailDir2, "state"), { recursive: true });
40629
- import_node_fs8.default.writeFileSync(
40630
- import_node_path7.default.join(sailDir2, "config.json"),
40707
+ const sailDir2 = import_node_path8.default.join(dest, ".sail");
40708
+ import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "keys"), { recursive: true });
40709
+ import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "runtime"), { recursive: true });
40710
+ import_node_fs9.default.mkdirSync(import_node_path8.default.join(sailDir2, "state"), { recursive: true });
40711
+ import_node_fs9.default.writeFileSync(
40712
+ import_node_path8.default.join(sailDir2, "config.json"),
40631
40713
  `${JSON.stringify(
40632
40714
  {
40633
40715
  version: 1,
@@ -40647,12 +40729,12 @@ function scaffoldProjectWorkspace(dest, name, options) {
40647
40729
  `,
40648
40730
  "utf-8"
40649
40731
  );
40650
- writeIfMissing2(import_node_path7.default.join(sailDir2, "README.md"), SAIL_WORKSPACE_README);
40732
+ writeIfMissing2(import_node_path8.default.join(sailDir2, "README.md"), SAIL_WORKSPACE_README);
40651
40733
  const chainEntries = Object.values(chains);
40652
40734
  const perChainVarLines = chainEntries.map((c) => `# ${c.rpcEnvVar}=https://your-${c.name.toLowerCase().replace(/\s+/g, "-")}-endpoint`).join("\n");
40653
40735
  const chainIdExample = chainId != null ? `CHAIN_ID=${chainId}` : `# CHAIN_ID=8453 # set after choosing your chain in Stage 1`;
40654
- import_node_fs8.default.writeFileSync(
40655
- import_node_path7.default.join(dest, ".env.example"),
40736
+ import_node_fs9.default.writeFileSync(
40737
+ import_node_path8.default.join(dest, ".env.example"),
40656
40738
  `# Sailor agent environment
40657
40739
  #
40658
40740
  # RPC configuration \u2014 two patterns, pick one:
@@ -40677,8 +40759,8 @@ ${perChainVarLines}
40677
40759
  const val = isActive && options.rpcUrl ? options.rpcUrl : `https://your-${c.name.toLowerCase().replace(/\s+/g, "-")}-endpoint`;
40678
40760
  return isActive && options.rpcUrl ? `${c.rpcEnvVar}=${val}` : `# ${c.rpcEnvVar}=${val}`;
40679
40761
  }).join("\n");
40680
- import_node_fs8.default.writeFileSync(
40681
- import_node_path7.default.join(sailDir2, ".env.local"),
40762
+ import_node_fs9.default.writeFileSync(
40763
+ import_node_path8.default.join(sailDir2, ".env.local"),
40682
40764
  `# Real values \u2014 never commit this file.
40683
40765
  #
40684
40766
  # Option A: single active chain (simplest)
@@ -40696,16 +40778,16 @@ ${allChainVarLines}
40696
40778
  }
40697
40779
  async function initCommand(dir, options = {}) {
40698
40780
  const inPlace = !dir || dir === ".";
40699
- const dest = inPlace ? process.cwd() : import_node_path7.default.resolve(process.cwd(), dir);
40700
- const name = import_node_path7.default.basename(dest);
40701
- const templatesDir = import_node_path7.default.join(packageRoot(), "templates");
40781
+ const dest = inPlace ? process.cwd() : import_node_path8.default.resolve(process.cwd(), dir);
40782
+ const name = import_node_path8.default.basename(dest);
40783
+ const templatesDir = import_node_path8.default.join(packageRoot(), "templates");
40702
40784
  const templateName = options.template ?? "default";
40703
40785
  if (/[/\\.]/.test(templateName) || templateName.includes("..")) {
40704
40786
  throw new Error(`Invalid template name: "${templateName}"`);
40705
40787
  }
40706
- const templateSrc = import_node_path7.default.join(templatesDir, templateName);
40707
- const availableTemplates = () => import_node_fs8.default.existsSync(templatesDir) ? import_node_fs8.default.readdirSync(templatesDir).filter((e) => import_node_fs8.default.existsSync(import_node_path7.default.join(templatesDir, e, "package.json"))).join(", ") || "none" : "none";
40708
- if (!import_node_fs8.default.existsSync(templateSrc) || !import_node_fs8.default.existsSync(import_node_path7.default.join(templateSrc, "package.json"))) {
40788
+ const templateSrc = import_node_path8.default.join(templatesDir, templateName);
40789
+ 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";
40790
+ if (!import_node_fs9.default.existsSync(templateSrc) || !import_node_fs9.default.existsSync(import_node_path8.default.join(templateSrc, "package.json"))) {
40709
40791
  const available = availableTemplates();
40710
40792
  const hint = available === "none" ? `
40711
40793
  No templates found under ${templatesDir}.
@@ -40715,49 +40797,49 @@ run from the repo root.` : ` Available: ${available}`;
40715
40797
  throw new Error(`Template "${templateName}" not found.${hint}`);
40716
40798
  }
40717
40799
  const cwd = process.cwd();
40718
- if (!inPlace && !dest.startsWith(cwd + import_node_path7.default.sep) && dest !== cwd) {
40800
+ if (!inPlace && !dest.startsWith(cwd + import_node_path8.default.sep) && dest !== cwd) {
40719
40801
  throw new Error(`Directory must be inside the current working directory`);
40720
40802
  }
40721
- if (!inPlace && import_node_fs8.default.existsSync(dest)) {
40803
+ if (!inPlace && import_node_fs9.default.existsSync(dest)) {
40722
40804
  throw new Error(`Directory already exists: ${dest}`);
40723
40805
  }
40724
- if (inPlace && import_node_fs8.default.existsSync(import_node_path7.default.join(dest, ".sail", "config.json"))) {
40725
- throw new Error(`Already initialized \u2014 .sail/config.json exists`);
40806
+ if (inPlace && import_node_fs9.default.existsSync(import_node_path8.default.join(dest, ".sail", "config.json"))) {
40807
+ throw new Error("This project is already initialized. Run `sailor update` to re-sync template files.");
40726
40808
  }
40727
40809
  copyDirSync(templateSrc, dest);
40728
40810
  const pkgRoot = packageRoot();
40729
- const examplesPermSrc = import_node_path7.default.join(pkgRoot, "examples", "permissions");
40730
- if (import_node_fs8.default.existsSync(examplesPermSrc)) {
40731
- copyDirSync(examplesPermSrc, import_node_path7.default.join(dest, "examples", "permissions"));
40732
- }
40733
- const customMandateSrc = import_node_path7.default.join(pkgRoot, "examples", "custom-mandate");
40734
- if (import_node_fs8.default.existsSync(customMandateSrc)) {
40735
- copyDirSync(customMandateSrc, import_node_path7.default.join(dest, "examples", "custom-mandate"));
40736
- }
40737
- const permModelSrc = import_node_path7.default.join(pkgRoot, "docs", "PERMISSION_MODEL.md");
40738
- if (import_node_fs8.default.existsSync(permModelSrc)) {
40739
- import_node_fs8.default.mkdirSync(import_node_path7.default.join(dest, "docs"), { recursive: true });
40740
- writeIfMissing2(import_node_path7.default.join(dest, "docs", "PERMISSION_MODEL.md"), import_node_fs8.default.readFileSync(permModelSrc, "utf-8"));
40741
- }
40742
- const pkgPath = import_node_path7.default.join(dest, "package.json");
40743
- if (import_node_fs8.default.existsSync(pkgPath)) {
40744
- const pkg = JSON.parse(import_node_fs8.default.readFileSync(pkgPath, "utf-8"));
40811
+ const examplesPermSrc = import_node_path8.default.join(pkgRoot, "examples", "permissions");
40812
+ if (import_node_fs9.default.existsSync(examplesPermSrc)) {
40813
+ copyDirSync(examplesPermSrc, import_node_path8.default.join(dest, "examples", "permissions"));
40814
+ }
40815
+ const customMandateSrc = import_node_path8.default.join(pkgRoot, "examples", "custom-mandate");
40816
+ if (import_node_fs9.default.existsSync(customMandateSrc)) {
40817
+ copyDirSync(customMandateSrc, import_node_path8.default.join(dest, "examples", "custom-mandate"));
40818
+ }
40819
+ const permModelSrc = import_node_path8.default.join(pkgRoot, "docs", "PERMISSION_MODEL.md");
40820
+ if (import_node_fs9.default.existsSync(permModelSrc)) {
40821
+ import_node_fs9.default.mkdirSync(import_node_path8.default.join(dest, "docs"), { recursive: true });
40822
+ writeIfMissing2(import_node_path8.default.join(dest, "docs", "PERMISSION_MODEL.md"), import_node_fs9.default.readFileSync(permModelSrc, "utf-8"));
40823
+ }
40824
+ const pkgPath = import_node_path8.default.join(dest, "package.json");
40825
+ if (import_node_fs9.default.existsSync(pkgPath)) {
40826
+ const pkg = JSON.parse(import_node_fs9.default.readFileSync(pkgPath, "utf-8"));
40745
40827
  pkg.name = name;
40746
40828
  const devDeps = pkg.devDependencies ?? {};
40747
40829
  const { name: cliName, version: cliVer } = cliPackageInfo();
40748
40830
  devDeps[CANONICAL_PKG] = cliName === DEV_PKG ? `npm:${DEV_PKG}@^${cliVer}` : `^${cliVer}`;
40749
40831
  pkg.devDependencies = devDeps;
40750
- import_node_fs8.default.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
40832
+ import_node_fs9.default.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
40751
40833
  `);
40752
40834
  }
40753
- const tsconfigPath = import_node_path7.default.join(dest, "tsconfig.json");
40754
- if (import_node_fs8.default.existsSync(tsconfigPath)) {
40755
- const tsconfig = JSON.parse(import_node_fs8.default.readFileSync(tsconfigPath, "utf-8"));
40835
+ const tsconfigPath = import_node_path8.default.join(dest, "tsconfig.json");
40836
+ if (import_node_fs9.default.existsSync(tsconfigPath)) {
40837
+ const tsconfig = JSON.parse(import_node_fs9.default.readFileSync(tsconfigPath, "utf-8"));
40756
40838
  const co = tsconfig.compilerOptions;
40757
40839
  if (co && "paths" in co) {
40758
40840
  delete co.paths;
40759
40841
  delete co.baseUrl;
40760
- import_node_fs8.default.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
40842
+ import_node_fs9.default.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
40761
40843
  `);
40762
40844
  }
40763
40845
  }
@@ -40783,21 +40865,21 @@ function chainLabel(chainId) {
40783
40865
  }
40784
40866
  function detectState(dest) {
40785
40867
  try {
40786
- const configRaw = import_node_fs8.default.readFileSync(import_node_path7.default.join(dest, ".sail", "config.json"), "utf-8");
40868
+ const configRaw = import_node_fs9.default.readFileSync(import_node_path8.default.join(dest, ".sail", "config.json"), "utf-8");
40787
40869
  const config = JSON.parse(configRaw);
40788
- const projectName = config.name ?? import_node_path7.default.basename(dest);
40789
- const accountPath = import_node_path7.default.join(dest, ".sail", "account.json");
40790
- if (!import_node_fs8.default.existsSync(accountPath)) {
40870
+ const projectName = config.name ?? import_node_path8.default.basename(dest);
40871
+ const accountPath = import_node_path8.default.join(dest, ".sail", "account.json");
40872
+ if (!import_node_fs9.default.existsSync(accountPath)) {
40791
40873
  return { kind: "B", projectName, chain: chainLabel(config.chainId ?? 0) };
40792
40874
  }
40793
- const accountRaw = import_node_fs8.default.readFileSync(accountPath, "utf-8");
40875
+ const accountRaw = import_node_fs9.default.readFileSync(accountPath, "utf-8");
40794
40876
  const account2 = JSON.parse(accountRaw);
40795
40877
  const sma = account2.safe ?? "";
40796
40878
  const chain2 = chainLabel(account2.chainId ?? config.chainId ?? 0);
40797
40879
  let permissionCount = 0;
40798
40880
  try {
40799
- const mandatesRaw = import_node_fs8.default.readFileSync(
40800
- import_node_path7.default.join(dest, ".sail", "state", "mandates.json"),
40881
+ const mandatesRaw = import_node_fs9.default.readFileSync(
40882
+ import_node_path8.default.join(dest, ".sail", "state", "mandates.json"),
40801
40883
  "utf-8"
40802
40884
  );
40803
40885
  const mandates = JSON.parse(mandatesRaw);
@@ -40878,9 +40960,79 @@ Created ${name}/`);
40878
40960
  ].join("\n"));
40879
40961
  }
40880
40962
 
40963
+ // src/commands/update.ts
40964
+ var import_node_fs10 = __toESM(require("node:fs"), 1);
40965
+ var import_node_path9 = __toESM(require("node:path"), 1);
40966
+ var UPDATE_PATHS = [
40967
+ ".agents",
40968
+ // all sail-* skills
40969
+ ".cursor",
40970
+ // cursor IDE rules
40971
+ ".env.example"
40972
+ // documents env vars; not meant to be edited directly
40973
+ ];
40974
+ var STALE_PATHS = [
40975
+ ".agents/skills/sail-ci"
40976
+ // renamed to sail-automation
40977
+ ];
40978
+ async function updateCommand() {
40979
+ const dest = process.cwd();
40980
+ if (!import_node_fs10.default.existsSync(import_node_path9.default.join(dest, ".sail", "config.json"))) {
40981
+ throw new Error("Not a sailor project \u2014 .sail/config.json not found. Run `sailor init` first.");
40982
+ }
40983
+ const templateSrc = import_node_path9.default.join(packageRoot(), "templates", "default");
40984
+ if (!import_node_fs10.default.existsSync(templateSrc)) {
40985
+ throw new Error(`Template directory not found at ${templateSrc}`);
40986
+ }
40987
+ const removed = [];
40988
+ for (const p of STALE_PATHS) {
40989
+ const target = import_node_path9.default.join(dest, p);
40990
+ if (import_node_fs10.default.existsSync(target)) {
40991
+ import_node_fs10.default.rmSync(target, { recursive: true, force: true });
40992
+ removed.push(p);
40993
+ }
40994
+ }
40995
+ const updated = [];
40996
+ for (const p of UPDATE_PATHS) {
40997
+ const src = import_node_path9.default.join(templateSrc, p);
40998
+ const dst = import_node_path9.default.join(dest, p);
40999
+ if (!import_node_fs10.default.existsSync(src)) continue;
41000
+ const stat = import_node_fs10.default.statSync(src);
41001
+ if (stat.isDirectory()) {
41002
+ copyDirSync(src, dst);
41003
+ } else {
41004
+ import_node_fs10.default.mkdirSync(import_node_path9.default.dirname(dst), { recursive: true });
41005
+ import_node_fs10.default.copyFileSync(src, dst);
41006
+ }
41007
+ updated.push(p);
41008
+ }
41009
+ const added = [];
41010
+ copyDirSyncIfMissing(templateSrc, dest, added);
41011
+ if (removed.length === 0 && updated.length === 0 && added.length === 0) {
41012
+ console.log("Nothing to update.");
41013
+ return;
41014
+ }
41015
+ if (removed.length > 0) {
41016
+ console.log(`
41017
+ Removed stale files:`);
41018
+ for (const p of removed) console.log(` ${p}`);
41019
+ }
41020
+ if (updated.length > 0) {
41021
+ console.log(`
41022
+ Updated from template:`);
41023
+ for (const p of updated) console.log(` ${p}`);
41024
+ }
41025
+ if (added.length > 0) {
41026
+ console.log(`
41027
+ Added (new in template):`);
41028
+ for (const p of added) console.log(` ${p}`);
41029
+ }
41030
+ console.log();
41031
+ }
41032
+
40881
41033
  // src/commands/keys.ts
40882
- var import_node_fs9 = __toESM(require("node:fs"), 1);
40883
- var import_node_path8 = __toESM(require("node:path"), 1);
41034
+ var import_node_fs11 = __toESM(require("node:fs"), 1);
41035
+ var import_node_path10 = __toESM(require("node:path"), 1);
40884
41036
  async function keysGenerate() {
40885
41037
  const roleInput = await prompt("Which key? (agent wallet / mandate signer)", "agent wallet");
40886
41038
  const role = normalizeRole(roleInput);
@@ -40933,15 +41085,15 @@ async function keysExportCi() {
40933
41085
  'No agent wallet keystore found.\nComplete Stage 1 (browser UI) to generate your agent wallet, or run\n"sailor keys generate" and choose "agent wallet" to create one manually.'
40934
41086
  );
40935
41087
  }
40936
- const dest = import_node_path8.default.resolve(process.cwd(), "ci-keystore.json");
40937
- import_node_fs9.default.copyFileSync(src, dest);
41088
+ const dest = import_node_path10.default.resolve(process.cwd(), "ci-keystore.json");
41089
+ import_node_fs11.default.copyFileSync(src, dest);
40938
41090
  console.log(`\u2713 Keystore copied to ci-keystore.json`);
40939
41091
  console.log(` Source: ${src}`);
40940
- const gitignorePath = import_node_path8.default.resolve(process.cwd(), ".gitignore");
40941
- if (import_node_fs9.default.existsSync(gitignorePath)) {
40942
- const content = import_node_fs9.default.readFileSync(gitignorePath, "utf-8");
41092
+ const gitignorePath = import_node_path10.default.resolve(process.cwd(), ".gitignore");
41093
+ if (import_node_fs11.default.existsSync(gitignorePath)) {
41094
+ const content = import_node_fs11.default.readFileSync(gitignorePath, "utf-8");
40943
41095
  if (!content.includes("ci-keystore.json")) {
40944
- import_node_fs9.default.appendFileSync(
41096
+ import_node_fs11.default.appendFileSync(
40945
41097
  gitignorePath,
40946
41098
  "\n# CI keystore \u2014 encrypted agent wallet, safe to commit\n!ci-keystore.json\n"
40947
41099
  );
@@ -40979,8 +41131,8 @@ async function keysShow() {
40979
41131
 
40980
41132
  // src/commands/mandate-contracts.ts
40981
41133
  var import_node_child_process = require("node:child_process");
40982
- var import_node_fs10 = require("node:fs");
40983
- var import_node_path9 = require("node:path");
41134
+ var import_node_fs12 = require("node:fs");
41135
+ var import_node_path11 = require("node:path");
40984
41136
  init_esm2();
40985
41137
 
40986
41138
  // src/lib/mandates.ts
@@ -41624,6 +41776,18 @@ Mandate command failed: ${msg}`), {
41624
41776
  });
41625
41777
  process.exit(1);
41626
41778
  }
41779
+ function announceSigningUrl(json) {
41780
+ const url = signingPageUrl(void 0, projectPort(process.cwd()));
41781
+ if (json) {
41782
+ process.stdout.write(`${JSON.stringify({ status: "waiting_for_signature", url })}
41783
+ `);
41784
+ } else {
41785
+ console.log(`
41786
+ \u2192 Open the Sailor dashboard to approve signing requests:
41787
+ ${url}
41788
+ `);
41789
+ }
41790
+ }
41627
41791
  function publicClientFor(project) {
41628
41792
  return createPublicClient({
41629
41793
  chain: getChainById(project.chainId),
@@ -41664,9 +41828,9 @@ async function runDeploy(project, channel, options) {
41664
41828
  const { abi: abi2, bytecode, contractName, artifactPath } = resolveArtifact(options);
41665
41829
  let argsJson;
41666
41830
  if (options.argsFile) {
41667
- const argsFilePath = (0, import_node_path9.resolve)(options.argsFile);
41831
+ const argsFilePath = (0, import_node_path11.resolve)(options.argsFile);
41668
41832
  try {
41669
- argsJson = (0, import_node_fs10.readFileSync)(argsFilePath, "utf8").trim();
41833
+ argsJson = (0, import_node_fs12.readFileSync)(argsFilePath, "utf8").trim();
41670
41834
  } catch {
41671
41835
  throw new Error(`Cannot read --args-file: ${argsFilePath}`);
41672
41836
  }
@@ -41677,15 +41841,8 @@ async function runDeploy(project, channel, options) {
41677
41841
  const deployData = encodeDeployData({ abi: abi2, bytecode, args });
41678
41842
  const chainId = project.chainId;
41679
41843
  const publicClient = publicClientFor(project);
41680
- say(() => {
41681
- console.log(
41682
- `
41683
- \u2192 Open the Sailor dashboard to approve signing requests:
41684
- ${signingPageUrl(channel, projectPort(process.cwd()))}
41685
- `
41686
- );
41687
- console.log(`Pushing deploy request for "${contractName}"\u2026`);
41688
- });
41844
+ announceSigningUrl(json);
41845
+ say(() => console.log(`Pushing deploy request for "${contractName}"\u2026`));
41689
41846
  const response = await channel.requestSignature({
41690
41847
  type: "transaction",
41691
41848
  kind: "deploy-mandate",
@@ -41835,6 +41992,15 @@ function parseAddressList(csv, flag) {
41835
41992
  }
41836
41993
  async function mandateDeployClone(options) {
41837
41994
  const project = requireProject();
41995
+ const templateMap = project.deployment.standaloneTemplates ?? {};
41996
+ if (Object.keys(templateMap).length === 0) {
41997
+ fail(
41998
+ new Error(
41999
+ `deploy-clone is unavailable on chain ${project.chainId}: no clone templates are deployed against this kernel (${project.deployment.kernel}) yet. Deploy your permission directly with \`sailor mandate deploy\` instead.`
42000
+ ),
42001
+ options.json
42002
+ );
42003
+ }
41838
42004
  const channel = await createSigningChannel(process.cwd());
41839
42005
  try {
41840
42006
  await channel.start();
@@ -41911,11 +42077,8 @@ ${spec.label} clone (${options.template})`);
41911
42077
  console.log(` predicted clone: ${clone}`);
41912
42078
  console.log(` SMA: ${sma}`);
41913
42079
  for (const d of spec.describe(initParams)) console.log(` ${d.label}: ${d.value}`);
41914
- console.log(`
41915
- \u2192 Open the Sailor dashboard to approve signing requests:
41916
- ${signingPageUrl(channel, projectPort(process.cwd()))}
41917
- `);
41918
42080
  });
42081
+ announceSigningUrl(json);
41919
42082
  const nonce = await publicClient.readContract({
41920
42083
  address: project.contracts.kernel,
41921
42084
  abi: SailKernelAbi,
@@ -42073,14 +42236,7 @@ async function runAttach(project, channel, options) {
42073
42236
  const mandateAddress = getAddress(rawAddress);
42074
42237
  const label = options.label ?? tracked?.name ?? "mandate";
42075
42238
  const publicClient = publicClientFor(project);
42076
- if (!json) {
42077
- console.log(
42078
- `
42079
- \u2192 Open the Sailor dashboard to approve signing requests:
42080
- ${signingPageUrl(channel, projectPort(process.cwd()))}
42081
- `
42082
- );
42083
- }
42239
+ announceSigningUrl(json);
42084
42240
  const txHash = await attachToSma(
42085
42241
  project,
42086
42242
  channel,
@@ -42157,13 +42313,8 @@ async function runRevoke(project, channel, options) {
42157
42313
  console.log(`
42158
42314
  Revoking ${targets.length} permission(s) from ${sma}:`);
42159
42315
  for (const p of targets) console.log(` ${nameFor(p) ?? p} ${p}`);
42160
- console.log(
42161
- `
42162
- \u2192 Open the Sailor dashboard to approve signing requests:
42163
- ${signingPageUrl(channel, projectPort(process.cwd()))}
42164
- `
42165
- );
42166
42316
  });
42317
+ announceSigningUrl(json);
42167
42318
  const response = await channel.requestSignature({
42168
42319
  type: "typed-data",
42169
42320
  kind: "revoke-permissions",
@@ -42340,21 +42491,27 @@ function mandateTemplates(options) {
42340
42491
  { chainId, community }
42341
42492
  );
42342
42493
  }
42343
- function mandateContractsList() {
42494
+ function mandateContractsList(options = {}) {
42344
42495
  const store = new MandateStore();
42345
42496
  const mandates = store.list();
42346
- if (mandates.length === 0) {
42347
- console.log('No permission contracts deployed yet. Use "sailor mandate deploy".');
42348
- return;
42349
- }
42350
- for (const m of mandates) {
42351
- console.log(m.name, `(chain ${m.chainId})`);
42352
- console.log(" Address: ", m.address);
42353
- console.log(" Deployed:", m.deployedAt);
42354
- if (m.attachments?.length) {
42355
- console.log(" Registered on:", m.attachments.map((a) => a.sma).join(", "));
42356
- }
42357
- }
42497
+ emit(
42498
+ !!options.json,
42499
+ () => {
42500
+ if (mandates.length === 0) {
42501
+ console.log('No permission contracts deployed yet. Use "sailor mandate deploy".');
42502
+ return;
42503
+ }
42504
+ for (const m of mandates) {
42505
+ console.log(m.name, `(chain ${m.chainId})`);
42506
+ console.log(" Address: ", m.address);
42507
+ console.log(" Deployed:", m.deployedAt);
42508
+ if (m.attachments?.length) {
42509
+ console.log(" Registered on:", m.attachments.map((a) => a.sma).join(", "));
42510
+ }
42511
+ }
42512
+ },
42513
+ { status: "ok", mandates }
42514
+ );
42358
42515
  }
42359
42516
  function mandateUpdate(options) {
42360
42517
  const { address, name, sourcePath, artifactPath, json } = options;
@@ -42376,11 +42533,11 @@ function resolveArtifact(options) {
42376
42533
  let contractName = options.contract ?? options.name ?? "";
42377
42534
  if (!artifactPath) {
42378
42535
  if (!options.contract) throw new Error("Provide --artifact <path> or --contract <name>");
42379
- artifactPath = (0, import_node_path9.join)(options.out, `${options.contract}.sol`, `${options.contract}.json`);
42536
+ artifactPath = (0, import_node_path11.join)(options.out, `${options.contract}.sol`, `${options.contract}.json`);
42380
42537
  }
42381
- const resolved = (0, import_node_path9.resolve)(artifactPath);
42382
- const projectRoot = (0, import_node_path9.resolve)(process.cwd());
42383
- if (!resolved.startsWith(projectRoot + import_node_path9.sep) && resolved !== projectRoot) {
42538
+ const resolved = (0, import_node_path11.resolve)(artifactPath);
42539
+ const projectRoot = (0, import_node_path11.resolve)(process.cwd());
42540
+ if (!resolved.startsWith(projectRoot + import_node_path11.sep) && resolved !== projectRoot) {
42384
42541
  throw new Error(
42385
42542
  `Artifact path must be inside the project directory.
42386
42543
  Resolved: ${resolved}`
@@ -42388,14 +42545,14 @@ Resolved: ${resolved}`
42388
42545
  }
42389
42546
  artifactPath = resolved;
42390
42547
  if (options.build) runForgeBuild();
42391
- if (!(0, import_node_fs10.existsSync)(artifactPath)) {
42548
+ if (!(0, import_node_fs12.existsSync)(artifactPath)) {
42392
42549
  ensureForgeHint();
42393
42550
  throw new Error(
42394
42551
  `Artifact not found: ${artifactPath}
42395
42552
  Compile your mandate first (e.g. \`forge build\`), or pass --build.`
42396
42553
  );
42397
42554
  }
42398
- const artifact = JSON.parse((0, import_node_fs10.readFileSync)(artifactPath, "utf8"));
42555
+ const artifact = JSON.parse((0, import_node_fs12.readFileSync)(artifactPath, "utf8"));
42399
42556
  const abi2 = artifact.abi;
42400
42557
  const bytecodeRaw = artifact.bytecode?.object ?? artifact.bytecode;
42401
42558
  if (!bytecodeRaw || typeof bytecodeRaw !== "string") {
@@ -42478,13 +42635,13 @@ function runForgeBuild() {
42478
42635
  init_esm2();
42479
42636
 
42480
42637
  // src/lib/permission-explainer.ts
42481
- var import_node_fs11 = require("node:fs");
42482
- var import_node_path10 = require("node:path");
42638
+ var import_node_fs13 = require("node:fs");
42639
+ var import_node_path12 = require("node:path");
42483
42640
  function explainPermission(name, sourcePath) {
42484
- const resolved = sourcePath ?? (0, import_node_path10.join)(process.cwd(), "mandates", `${name}.sol`);
42641
+ const resolved = sourcePath ?? (0, import_node_path12.join)(process.cwd(), "mandates", `${name}.sol`);
42485
42642
  let src;
42486
42643
  try {
42487
- src = (0, import_node_fs11.readFileSync)(resolved, "utf8");
42644
+ src = (0, import_node_fs13.readFileSync)(resolved, "utf8");
42488
42645
  } catch {
42489
42646
  return null;
42490
42647
  }
@@ -42691,7 +42848,6 @@ ${unregistered.length} permission(s) are not yet registered on this SMA. Initiat
42691
42848
  safe: account2.safe,
42692
42849
  chainId,
42693
42850
  signedAt: (/* @__PURE__ */ new Date()).toISOString(),
42694
- signature: "",
42695
42851
  registeredOnChain: true,
42696
42852
  // Only include permissions that are currently active on-chain.
42697
42853
  permissions: activePermissions.map((p) => ({ template: p.label, params: {} }))
@@ -42707,7 +42863,7 @@ ${unregistered.length} permission(s) are not yet registered on this SMA. Initiat
42707
42863
  }
42708
42864
 
42709
42865
  // src/commands/mandate-simulate.ts
42710
- var import_node_fs12 = require("node:fs");
42866
+ var import_node_fs14 = require("node:fs");
42711
42867
  init_esm2();
42712
42868
  function parseExpect(raw, where) {
42713
42869
  if (raw === void 0) return void 0;
@@ -42723,7 +42879,7 @@ function resolveSampleCalls(options) {
42723
42879
  if (options.calls) {
42724
42880
  let raw;
42725
42881
  try {
42726
- raw = JSON.parse((0, import_node_fs12.readFileSync)(options.calls, "utf8"));
42882
+ raw = JSON.parse((0, import_node_fs14.readFileSync)(options.calls, "utf8"));
42727
42883
  } catch (err) {
42728
42884
  throw new Error(`Could not read --calls file "${options.calls}": ${err.message}`);
42729
42885
  }
@@ -42939,7 +43095,7 @@ async function mandateSimulate(options) {
42939
43095
  }
42940
43096
 
42941
43097
  // src/commands/rotate-signer.ts
42942
- var import_node_fs13 = require("node:fs");
43098
+ var import_node_fs15 = require("node:fs");
42943
43099
  init_esm2();
42944
43100
  var PENDING_REATTACH_FILE = ["state", "pending-reattach.json"];
42945
43101
  async function rotateSigner(options) {
@@ -43327,7 +43483,7 @@ ensure the agent that signs dispatches holds this key.`
43327
43483
  const keystore = await keyring.exportKeystore(password);
43328
43484
  writeJsonFile(target, keystore, 384);
43329
43485
  const perManagerPath = managerKeystorePath(keyring.address);
43330
- (0, import_node_fs13.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43486
+ (0, import_node_fs15.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43331
43487
  writeJsonFile(perManagerPath, keystore, 384);
43332
43488
  say(
43333
43489
  () => console.log(
@@ -43339,7 +43495,7 @@ ensure the agent that signs dispatches holds this key.`
43339
43495
  function promoteManagerKeystore(newManager, say) {
43340
43496
  const stored = readJsonFile(managerKeystorePath(newManager));
43341
43497
  if (!stored) return;
43342
- (0, import_node_fs13.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43498
+ (0, import_node_fs15.mkdirSync)(sailPath("keys", "managers"), { recursive: true });
43343
43499
  const activeTarget = keyPath("manager");
43344
43500
  const displaced = readJsonFile(activeTarget);
43345
43501
  if (displaced?.address) {
@@ -43391,7 +43547,7 @@ function writePending(pending) {
43391
43547
  }
43392
43548
  function clearPending() {
43393
43549
  try {
43394
- (0, import_node_fs13.rmSync)(sailPath(...PENDING_REATTACH_FILE), { force: true });
43550
+ (0, import_node_fs15.rmSync)(sailPath(...PENDING_REATTACH_FILE), { force: true });
43395
43551
  } catch {
43396
43552
  }
43397
43553
  }
@@ -43490,30 +43646,30 @@ function ownerShow(options) {
43490
43646
  }
43491
43647
 
43492
43648
  // src/commands/run.ts
43493
- var import_node_fs15 = __toESM(require("node:fs"), 1);
43494
- var import_node_path11 = __toESM(require("node:path"), 1);
43649
+ var import_node_fs17 = __toESM(require("node:fs"), 1);
43650
+ var import_node_path13 = __toESM(require("node:path"), 1);
43495
43651
  var import_node_url = require("node:url");
43496
43652
  init_esm2();
43497
43653
 
43498
43654
  // src/lib/process.ts
43499
- var import_node_fs14 = __toESM(require("node:fs"), 1);
43655
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
43500
43656
  function agentPidPath(chainId) {
43501
43657
  return chainId != null ? sailPath(`agent-${chainId}.pid`) : sailPath("agent.pid");
43502
43658
  }
43503
43659
  function writeAgentPid(chainId) {
43504
- import_node_fs14.default.mkdirSync(sailPath(), { recursive: true });
43505
- import_node_fs14.default.writeFileSync(agentPidPath(chainId), `${process.pid}
43660
+ import_node_fs16.default.mkdirSync(sailPath(), { recursive: true });
43661
+ import_node_fs16.default.writeFileSync(agentPidPath(chainId), `${process.pid}
43506
43662
  `);
43507
43663
  }
43508
43664
  function clearAgentPid(chainId) {
43509
43665
  try {
43510
- import_node_fs14.default.rmSync(agentPidPath(chainId));
43666
+ import_node_fs16.default.rmSync(agentPidPath(chainId));
43511
43667
  } catch {
43512
43668
  }
43513
43669
  }
43514
43670
  function readAgentPid(chainId) {
43515
43671
  try {
43516
- const pid = Number.parseInt(import_node_fs14.default.readFileSync(agentPidPath(chainId), "utf-8").trim(), 10);
43672
+ const pid = Number.parseInt(import_node_fs16.default.readFileSync(agentPidPath(chainId), "utf-8").trim(), 10);
43517
43673
  return Number.isNaN(pid) ? null : pid;
43518
43674
  } catch {
43519
43675
  return null;
@@ -43560,7 +43716,7 @@ var ERC20_READ_ABI = [
43560
43716
  function loadAgentData(filePath) {
43561
43717
  if (!filePath) return {};
43562
43718
  try {
43563
- const parsed = JSON.parse(import_node_fs15.default.readFileSync(filePath, "utf-8"));
43719
+ const parsed = JSON.parse(import_node_fs17.default.readFileSync(filePath, "utf-8"));
43564
43720
  return parsed && typeof parsed === "object" ? parsed : {};
43565
43721
  } catch {
43566
43722
  return {};
@@ -43569,8 +43725,8 @@ function loadAgentData(filePath) {
43569
43725
  async function loadAgent() {
43570
43726
  const candidates = ["src/agent.ts", "src/agent.js", "dist/agent.js", "dist/src/agent.js"];
43571
43727
  for (const rel of candidates) {
43572
- const abs = import_node_path11.default.join(process.cwd(), rel);
43573
- if (!import_node_fs15.default.existsSync(abs)) continue;
43728
+ const abs = import_node_path13.default.join(process.cwd(), rel);
43729
+ if (!import_node_fs17.default.existsSync(abs)) continue;
43574
43730
  let mod2;
43575
43731
  if (abs.endsWith(".ts")) {
43576
43732
  const { tsImport } = await import("tsx/esm/api");
@@ -44046,9 +44202,9 @@ async function scan(options) {
44046
44202
 
44047
44203
  // src/commands/service.ts
44048
44204
  var import_node_child_process2 = require("node:child_process");
44049
- var import_node_fs16 = __toESM(require("node:fs"), 1);
44205
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
44050
44206
  var import_node_os = __toESM(require("node:os"), 1);
44051
- var import_node_path12 = __toESM(require("node:path"), 1);
44207
+ var import_node_path14 = __toESM(require("node:path"), 1);
44052
44208
  function sanitizeName(raw) {
44053
44209
  const s = raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
44054
44210
  return s || "agent";
@@ -44167,21 +44323,21 @@ function buildWindowsTaskXml(cfg) {
44167
44323
  `;
44168
44324
  }
44169
44325
  function isTccProtected(projectDir, home = import_node_os.default.homedir()) {
44170
- const rel = import_node_path12.default.relative(home, import_node_path12.default.resolve(projectDir));
44171
- if (rel.startsWith("..") || import_node_path12.default.isAbsolute(rel)) return false;
44172
- const top = rel.split(import_node_path12.default.sep)[0];
44326
+ const rel = import_node_path14.default.relative(home, import_node_path14.default.resolve(projectDir));
44327
+ if (rel.startsWith("..") || import_node_path14.default.isAbsolute(rel)) return false;
44328
+ const top = rel.split(import_node_path14.default.sep)[0];
44173
44329
  return ["Desktop", "Documents", "Downloads"].includes(top ?? "");
44174
44330
  }
44175
44331
  function resolveCliEntry() {
44176
44332
  try {
44177
- return import_node_fs16.default.realpathSync(process.argv[1]);
44333
+ return import_node_fs18.default.realpathSync(process.argv[1]);
44178
44334
  } catch {
44179
- return import_node_path12.default.resolve(process.argv[1]);
44335
+ return import_node_path14.default.resolve(process.argv[1]);
44180
44336
  }
44181
44337
  }
44182
44338
  function resolveProjectDir(explicit) {
44183
- const dir = import_node_path12.default.resolve(explicit ?? process.cwd());
44184
- if (!import_node_fs16.default.existsSync(import_node_path12.default.join(dir, ".sail"))) {
44339
+ const dir = import_node_path14.default.resolve(explicit ?? process.cwd());
44340
+ if (!import_node_fs18.default.existsSync(import_node_path14.default.join(dir, ".sail"))) {
44185
44341
  throw new Error(
44186
44342
  `No .sail/ found in ${dir}.
44187
44343
  Run this from your Sailor project root, or pass --project <path>. (The installer does not search parent directories \u2014 that would risk targeting the wrong project.)`
@@ -44193,7 +44349,7 @@ function passphraseReadiness(projectDir) {
44193
44349
  const account2 = (() => {
44194
44350
  try {
44195
44351
  return JSON.parse(
44196
- import_node_fs16.default.readFileSync(import_node_path12.default.join(projectDir, ".sail", "account.json"), "utf-8")
44352
+ import_node_fs18.default.readFileSync(import_node_path14.default.join(projectDir, ".sail", "account.json"), "utf-8")
44197
44353
  );
44198
44354
  } catch {
44199
44355
  return null;
@@ -44209,28 +44365,28 @@ function passphraseReadiness(projectDir) {
44209
44365
  } finally {
44210
44366
  process.chdir(prevCwd);
44211
44367
  }
44212
- const env = parseEnvFile(import_node_path12.default.join(projectDir, ".sail", ".env.local"));
44368
+ const env = parseEnvFile(import_node_path14.default.join(projectDir, ".sail", ".env.local"));
44213
44369
  const passphraseInEnvFile = Boolean(env.SAIL_PASSPHRASE);
44214
44370
  return { keystorePresent, passphraseInEnvFile, ready: keystorePresent && passphraseInEnvFile };
44215
44371
  }
44216
44372
  function buildConfig(opts) {
44217
44373
  const projectDir = resolveProjectDir(opts.project);
44218
44374
  return {
44219
- projectName: sanitizeName(import_node_path12.default.basename(projectDir)),
44375
+ projectName: sanitizeName(import_node_path14.default.basename(projectDir)),
44220
44376
  projectDir,
44221
44377
  nodePath: process.execPath,
44222
44378
  cliEntry: resolveCliEntry(),
44223
- logPath: import_node_path12.default.join(projectDir, ".sail", "agent.log"),
44379
+ logPath: import_node_path14.default.join(projectDir, ".sail", "agent.log"),
44224
44380
  interval: opts.interval != null ? Number(opts.interval) : void 0,
44225
44381
  chain: opts.chain != null ? Number(opts.chain) : void 0,
44226
44382
  restartSec: 30
44227
44383
  };
44228
44384
  }
44229
44385
  function plistPath(projectName) {
44230
- return import_node_path12.default.join(import_node_os.default.homedir(), "Library", "LaunchAgents", `${launchdLabel(projectName)}.plist`);
44386
+ return import_node_path14.default.join(import_node_os.default.homedir(), "Library", "LaunchAgents", `${launchdLabel(projectName)}.plist`);
44231
44387
  }
44232
44388
  function systemdPath(projectName) {
44233
- return import_node_path12.default.join(import_node_os.default.homedir(), ".config", "systemd", "user", systemdUnitName(projectName));
44389
+ return import_node_path14.default.join(import_node_os.default.homedir(), ".config", "systemd", "user", systemdUnitName(projectName));
44234
44390
  }
44235
44391
  async function serviceInstall(opts = {}) {
44236
44392
  const cfg = buildConfig(opts);
@@ -44254,12 +44410,12 @@ Run "sailor keys generate" (and save the passphrase) or set it in .sail/.env.loc
44254
44410
  console.warn(`\u26A0 ${msg}
44255
44411
  Proceeding because --force was given.`);
44256
44412
  }
44257
- import_node_fs16.default.mkdirSync(import_node_path12.default.dirname(cfg.logPath), { recursive: true });
44413
+ import_node_fs18.default.mkdirSync(import_node_path14.default.dirname(cfg.logPath), { recursive: true });
44258
44414
  if (platform === "darwin") {
44259
44415
  const plist = buildLaunchdPlist(cfg);
44260
44416
  const dest = plistPath(cfg.projectName);
44261
- import_node_fs16.default.mkdirSync(import_node_path12.default.dirname(dest), { recursive: true });
44262
- import_node_fs16.default.writeFileSync(dest, plist);
44417
+ import_node_fs18.default.mkdirSync(import_node_path14.default.dirname(dest), { recursive: true });
44418
+ import_node_fs18.default.writeFileSync(dest, plist);
44263
44419
  const uid2 = process.getuid?.() ?? 0;
44264
44420
  try {
44265
44421
  try {
@@ -44278,8 +44434,8 @@ Run "sailor keys generate" (and save the passphrase) or set it in .sail/.env.loc
44278
44434
  if (platform === "linux") {
44279
44435
  const unit = buildSystemdUnit(cfg);
44280
44436
  const dest = systemdPath(cfg.projectName);
44281
- import_node_fs16.default.mkdirSync(import_node_path12.default.dirname(dest), { recursive: true });
44282
- import_node_fs16.default.writeFileSync(dest, unit);
44437
+ import_node_fs18.default.mkdirSync(import_node_path14.default.dirname(dest), { recursive: true });
44438
+ import_node_fs18.default.writeFileSync(dest, unit);
44283
44439
  try {
44284
44440
  (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
44285
44441
  (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "enable", "--now", systemdUnitName(cfg.projectName)], {
@@ -44293,8 +44449,8 @@ Run "sailor keys generate" (and save the passphrase) or set it in .sail/.env.loc
44293
44449
  }
44294
44450
  if (platform === "win32") {
44295
44451
  const xml = buildWindowsTaskXml(cfg);
44296
- const dest = import_node_path12.default.join(import_node_os.default.tmpdir(), `${windowsTaskName(cfg.projectName)}.xml`);
44297
- import_node_fs16.default.writeFileSync(dest, xml, "utf-8");
44452
+ const dest = import_node_path14.default.join(import_node_os.default.tmpdir(), `${windowsTaskName(cfg.projectName)}.xml`);
44453
+ import_node_fs18.default.writeFileSync(dest, xml, "utf-8");
44298
44454
  try {
44299
44455
  (0, import_node_child_process2.execFileSync)(
44300
44456
  "schtasks",
@@ -44305,7 +44461,7 @@ Run "sailor keys generate" (and save the passphrase) or set it in .sail/.env.loc
44305
44461
  throw new Error(`schtasks /Create failed: ${err.message}`);
44306
44462
  } finally {
44307
44463
  try {
44308
- import_node_fs16.default.rmSync(dest, { force: true });
44464
+ import_node_fs18.default.rmSync(dest, { force: true });
44309
44465
  } catch {
44310
44466
  }
44311
44467
  }
@@ -44331,14 +44487,14 @@ function okInstall(cfg, unit) {
44331
44487
  );
44332
44488
  }
44333
44489
  async function serviceStatus(opts = {}) {
44334
- const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44490
+ const projectName = sanitizeName(import_node_path14.default.basename(resolveProjectDir(opts.project)));
44335
44491
  const platform = process.platform;
44336
44492
  let installed = false;
44337
44493
  let running = false;
44338
44494
  let detail = "";
44339
44495
  try {
44340
44496
  if (platform === "darwin") {
44341
- installed = import_node_fs16.default.existsSync(plistPath(projectName));
44497
+ installed = import_node_fs18.default.existsSync(plistPath(projectName));
44342
44498
  const uid2 = process.getuid?.() ?? 0;
44343
44499
  try {
44344
44500
  detail = (0, import_node_child_process2.execFileSync)("launchctl", ["print", `gui/${uid2}/${launchdLabel(projectName)}`], {
@@ -44350,7 +44506,7 @@ async function serviceStatus(opts = {}) {
44350
44506
  } catch {
44351
44507
  }
44352
44508
  } else if (platform === "linux") {
44353
- installed = import_node_fs16.default.existsSync(systemdPath(projectName));
44509
+ installed = import_node_fs18.default.existsSync(systemdPath(projectName));
44354
44510
  try {
44355
44511
  detail = (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "is-active", systemdUnitName(projectName)], {
44356
44512
  encoding: "utf-8",
@@ -44386,7 +44542,7 @@ async function serviceStatus(opts = {}) {
44386
44542
  );
44387
44543
  }
44388
44544
  async function serviceStop(opts = {}) {
44389
- const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44545
+ const projectName = sanitizeName(import_node_path14.default.basename(resolveProjectDir(opts.project)));
44390
44546
  const platform = process.platform;
44391
44547
  if (platform === "darwin") {
44392
44548
  const uid2 = process.getuid?.() ?? 0;
@@ -44405,7 +44561,7 @@ async function serviceStop(opts = {}) {
44405
44561
  });
44406
44562
  }
44407
44563
  async function serviceUninstall(opts = {}) {
44408
- const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44564
+ const projectName = sanitizeName(import_node_path14.default.basename(resolveProjectDir(opts.project)));
44409
44565
  const platform = process.platform;
44410
44566
  if (platform === "darwin") {
44411
44567
  const uid2 = process.getuid?.() ?? 0;
@@ -44415,7 +44571,7 @@ async function serviceUninstall(opts = {}) {
44415
44571
  });
44416
44572
  } catch {
44417
44573
  }
44418
- import_node_fs16.default.rmSync(plistPath(projectName), { force: true });
44574
+ import_node_fs18.default.rmSync(plistPath(projectName), { force: true });
44419
44575
  } else if (platform === "linux") {
44420
44576
  try {
44421
44577
  (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "disable", "--now", systemdUnitName(projectName)], {
@@ -44423,7 +44579,7 @@ async function serviceUninstall(opts = {}) {
44423
44579
  });
44424
44580
  } catch {
44425
44581
  }
44426
- import_node_fs16.default.rmSync(systemdPath(projectName), { force: true });
44582
+ import_node_fs18.default.rmSync(systemdPath(projectName), { force: true });
44427
44583
  try {
44428
44584
  (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
44429
44585
  } catch {
@@ -44441,21 +44597,21 @@ async function serviceUninstall(opts = {}) {
44441
44597
  }
44442
44598
  async function serviceLogs(opts = {}) {
44443
44599
  const projectDir = resolveProjectDir(opts.project);
44444
- const logPath = import_node_path12.default.join(projectDir, ".sail", "agent.log");
44445
- if (!import_node_fs16.default.existsSync(logPath)) {
44600
+ const logPath = import_node_path14.default.join(projectDir, ".sail", "agent.log");
44601
+ if (!import_node_fs18.default.existsSync(logPath)) {
44446
44602
  console.log(`No log yet at ${logPath}. The service writes here once it runs.`);
44447
44603
  return;
44448
44604
  }
44449
44605
  if (opts.follow) {
44450
44606
  if (process.platform === "win32") {
44451
- console.log(import_node_fs16.default.readFileSync(logPath, "utf-8"));
44607
+ console.log(import_node_fs18.default.readFileSync(logPath, "utf-8"));
44452
44608
  console.log("(follow mode is not supported on Windows here \u2014 re-run to refresh)");
44453
44609
  return;
44454
44610
  }
44455
44611
  (0, import_node_child_process2.execFileSync)("tail", ["-f", logPath], { stdio: "inherit" });
44456
44612
  return;
44457
44613
  }
44458
- const lines = import_node_fs16.default.readFileSync(logPath, "utf-8").split("\n");
44614
+ const lines = import_node_fs18.default.readFileSync(logPath, "utf-8").split("\n");
44459
44615
  console.log(lines.slice(-200).join("\n"));
44460
44616
  }
44461
44617
 
@@ -44609,14 +44765,14 @@ async function sessionResume() {
44609
44765
  }
44610
44766
 
44611
44767
  // src/commands/station.ts
44612
- var import_node_fs17 = require("node:fs");
44613
- var import_node_path13 = require("node:path");
44614
- var RUNTIME_SERVER_FILE2 = (0, import_node_path13.join)(".sail", "runtime", "server.json");
44768
+ var import_node_fs19 = require("node:fs");
44769
+ var import_node_path15 = require("node:path");
44770
+ var RUNTIME_SERVER_FILE2 = (0, import_node_path15.join)(".sail", "runtime", "server.json");
44615
44771
  function readState(projectRoot) {
44616
- const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44617
- if (!(0, import_node_fs17.existsSync)(file)) return null;
44772
+ const file = (0, import_node_path15.join)(projectRoot, RUNTIME_SERVER_FILE2);
44773
+ if (!(0, import_node_fs19.existsSync)(file)) return null;
44618
44774
  try {
44619
- return JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
44775
+ return JSON.parse((0, import_node_fs19.readFileSync)(file, "utf8"));
44620
44776
  } catch {
44621
44777
  return null;
44622
44778
  }
@@ -44688,9 +44844,9 @@ async function stationStop(options) {
44688
44844
  }
44689
44845
  const daemon = await discoverDaemon(projectRoot);
44690
44846
  if (!daemon) {
44691
- const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44847
+ const file = (0, import_node_path15.join)(projectRoot, RUNTIME_SERVER_FILE2);
44692
44848
  try {
44693
- if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
44849
+ if ((0, import_node_fs19.existsSync)(file)) (0, import_node_fs19.rmSync)(file);
44694
44850
  } catch {
44695
44851
  }
44696
44852
  emit(options.json, () => console.log("Station process not found; cleared stale state."), {
@@ -44706,9 +44862,9 @@ async function stationStop(options) {
44706
44862
  pid: state.pid
44707
44863
  });
44708
44864
  } catch {
44709
- const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44865
+ const file = (0, import_node_path15.join)(projectRoot, RUNTIME_SERVER_FILE2);
44710
44866
  try {
44711
- if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
44867
+ if ((0, import_node_fs19.existsSync)(file)) (0, import_node_fs19.rmSync)(file);
44712
44868
  } catch {
44713
44869
  }
44714
44870
  emit(options.json, () => console.log("Station process not found; cleared stale state."), {
@@ -44763,15 +44919,15 @@ async function status() {
44763
44919
 
44764
44920
  // src/commands/ui.ts
44765
44921
  var import_node_child_process4 = require("node:child_process");
44766
- var import_node_fs18 = __toESM(require("node:fs"), 1);
44922
+ var import_node_fs20 = __toESM(require("node:fs"), 1);
44767
44923
  var import_node_net2 = __toESM(require("node:net"), 1);
44768
- var import_node_path14 = __toESM(require("node:path"), 1);
44769
- var UI_STATE_FILE = import_node_path14.default.join(".sail", "runtime", "ui.json");
44924
+ var import_node_path16 = __toESM(require("node:path"), 1);
44925
+ var UI_STATE_FILE = import_node_path16.default.join(".sail", "runtime", "ui.json");
44770
44926
  function readState2(projectRoot) {
44771
- const file = import_node_path14.default.join(projectRoot, UI_STATE_FILE);
44772
- if (!import_node_fs18.default.existsSync(file)) return null;
44927
+ const file = import_node_path16.default.join(projectRoot, UI_STATE_FILE);
44928
+ if (!import_node_fs20.default.existsSync(file)) return null;
44773
44929
  try {
44774
- return JSON.parse(import_node_fs18.default.readFileSync(file, "utf-8"));
44930
+ return JSON.parse(import_node_fs20.default.readFileSync(file, "utf-8"));
44775
44931
  } catch {
44776
44932
  return null;
44777
44933
  }
@@ -44812,15 +44968,15 @@ function waitForPort(port, timeoutMs) {
44812
44968
  }
44813
44969
  async function uiCommand() {
44814
44970
  const distDir = cliDistDir();
44815
- const uiDistDir = import_node_path14.default.join(packageRoot(), "packages", "ui", "dist");
44816
- const serverBundle = import_node_path14.default.resolve(distDir, "server.cjs");
44971
+ const uiDistDir = import_node_path16.default.join(packageRoot(), "packages", "ui", "dist");
44972
+ const serverBundle = import_node_path16.default.resolve(distDir, "server.cjs");
44817
44973
  const projectRoot = process.cwd();
44818
- const sailDir2 = import_node_path14.default.join(projectRoot, ".sail");
44974
+ const sailDir2 = import_node_path16.default.join(projectRoot, ".sail");
44819
44975
  const port = await findFreePort(projectPort(projectRoot));
44820
- if (!import_node_fs18.default.existsSync(serverBundle)) {
44976
+ if (!import_node_fs20.default.existsSync(serverBundle)) {
44821
44977
  throw new Error(`Server bundle not found at ${serverBundle}. Re-run the sailor build.`);
44822
44978
  }
44823
- if (!import_node_fs18.default.existsSync(import_node_path14.default.join(uiDistDir, "index.html"))) {
44979
+ if (!import_node_fs20.default.existsSync(import_node_path16.default.join(uiDistDir, "index.html"))) {
44824
44980
  throw new Error(`UI dist not found at ${uiDistDir}. Re-run the sailor build.`);
44825
44981
  }
44826
44982
  const existing = readState2(projectRoot);
@@ -44828,17 +44984,17 @@ async function uiCommand() {
44828
44984
  console.log(`Sailor UI is already running (pid ${existing.pid}) at http://localhost:${existing.port}`);
44829
44985
  return;
44830
44986
  }
44831
- const runtimeDir = import_node_path14.default.join(projectRoot, ".sail", "runtime");
44832
- import_node_fs18.default.mkdirSync(runtimeDir, { recursive: true });
44833
- const logFile = import_node_path14.default.join(runtimeDir, "ui.log");
44834
- const logFd = import_node_fs18.default.openSync(logFile, "a");
44987
+ const runtimeDir = import_node_path16.default.join(projectRoot, ".sail", "runtime");
44988
+ import_node_fs20.default.mkdirSync(runtimeDir, { recursive: true });
44989
+ const logFile = import_node_path16.default.join(runtimeDir, "ui.log");
44990
+ const logFd = import_node_fs20.default.openSync(logFile, "a");
44835
44991
  const child = (0, import_node_child_process4.spawn)(process.execPath, [serverBundle], {
44836
44992
  detached: true,
44837
44993
  stdio: ["ignore", logFd, logFd],
44838
44994
  env: { ...process.env, SAIL_DIR: sailDir2, SERVE_DIST: "1", PORT: String(port), SAILOR_UI_DIST: uiDistDir }
44839
44995
  });
44840
44996
  child.unref();
44841
- import_node_fs18.default.closeSync(logFd);
44997
+ import_node_fs20.default.closeSync(logFd);
44842
44998
  const READY_TIMEOUT_MS = 1e4;
44843
44999
  const deadline = Date.now() + READY_TIMEOUT_MS;
44844
45000
  let ready = false;
@@ -44852,18 +45008,18 @@ async function uiCommand() {
44852
45008
  if (!ready) {
44853
45009
  let tail = "";
44854
45010
  try {
44855
- tail = import_node_fs18.default.readFileSync(logFile, "utf-8").split("\n").filter(Boolean).slice(-15).join("\n");
45011
+ tail = import_node_fs20.default.readFileSync(logFile, "utf-8").split("\n").filter(Boolean).slice(-15).join("\n");
44856
45012
  } catch {
44857
45013
  }
44858
45014
  throw new Error(
44859
45015
  `Sailor UI failed to start within ${READY_TIMEOUT_MS / 1e3}s on port ${port}.` + (tail ? `
44860
45016
 
44861
45017
  Server output:
44862
- ${tail}` : ` See ${import_node_path14.default.relative(projectRoot, logFile)}.`)
45018
+ ${tail}` : ` See ${import_node_path16.default.relative(projectRoot, logFile)}.`)
44863
45019
  );
44864
45020
  }
44865
- import_node_fs18.default.writeFileSync(
44866
- import_node_path14.default.join(projectRoot, UI_STATE_FILE),
45021
+ import_node_fs20.default.writeFileSync(
45022
+ import_node_path16.default.join(projectRoot, UI_STATE_FILE),
44867
45023
  JSON.stringify({ pid: child.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
44868
45024
  );
44869
45025
  console.log(`Sailor UI started at http://localhost:${port} (pid ${child.pid})`);
@@ -44874,7 +45030,7 @@ function uiStatus() {
44874
45030
  if (state && isAlive(state.pid)) {
44875
45031
  console.log(`\u25CF running http://localhost:${state.port} (pid ${state.pid})`);
44876
45032
  } else {
44877
- if (state) import_node_fs18.default.rmSync(import_node_path14.default.join(process.cwd(), UI_STATE_FILE), { force: true });
45033
+ if (state) import_node_fs20.default.rmSync(import_node_path16.default.join(process.cwd(), UI_STATE_FILE), { force: true });
44878
45034
  console.log("\u25CB Sailor UI is not running");
44879
45035
  }
44880
45036
  }
@@ -44886,19 +45042,19 @@ function uiStop() {
44886
45042
  return;
44887
45043
  }
44888
45044
  if (!isAlive(state.pid)) {
44889
- import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
45045
+ import_node_fs20.default.rmSync(import_node_path16.default.join(projectRoot, UI_STATE_FILE), { force: true });
44890
45046
  console.log("Sailor UI is not running (stale state file removed).");
44891
45047
  return;
44892
45048
  }
44893
45049
  process.kill(state.pid, "SIGTERM");
44894
- import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
45050
+ import_node_fs20.default.rmSync(import_node_path16.default.join(projectRoot, UI_STATE_FILE), { force: true });
44895
45051
  console.log(`Stopped Sailor UI (pid ${state.pid}).`);
44896
45052
  }
44897
45053
 
44898
45054
  // src/index.ts
44899
45055
  function cliVersion() {
44900
45056
  try {
44901
- const pkg = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path15.join)(packageRoot(), "package.json"), "utf-8"));
45057
+ const pkg = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path17.join)(packageRoot(), "package.json"), "utf-8"));
44902
45058
  return pkg.version ?? "0.0.0";
44903
45059
  } catch {
44904
45060
  return "0.0.0";
@@ -44940,6 +45096,7 @@ program2.command("init [dir]").description("Scaffold a new Sail agent into the c
44940
45096
  }
44941
45097
  }
44942
45098
  );
45099
+ program2.command("update").description("Re-sync agent tooling files (skills, AGENTS.md, Dockerfile) from the latest template").action(action(updateCommand));
44943
45100
  var ui = program2.command("ui").description("Manage the local Sailor dashboard");
44944
45101
  ui.command("start").description("Start the dashboard at localhost:3333 (default)").action(action(uiCommand));
44945
45102
  ui.command("stop").description("Stop the running dashboard").action(() => uiStop());
@@ -44965,14 +45122,14 @@ mandate.command("prepare").description("Prepare a mandate draft for review and s
44965
45122
  mandate.command("sign").description("Review and confirm the permissions authorized for your SMA").option("--yes", "Skip the confirmation prompt (for non-interactive / CI use)").action(actionWith(mandateSign));
44966
45123
  mandate.command("deploy").description("Deploy a Foundry-compiled permission contract via the browser signing UI").option("--artifact <path>", "Path to the Foundry artifact JSON (out/<Name>.sol/<Name>.json)").option("--contract <name>", "Contract name; resolves to <out>/<name>.sol/<name>.json").option("--out <dir>", "Foundry output directory", "out").option("--name <label>", "Label to track this permission under (defaults to contract name)").option("--args <json>", `Constructor args as JSON array. Bash: '["0x..","1"]'. PowerShell: '[\\"0x..\\",\\"1\\"]'. Use --args-file to avoid quoting.`).option("--args-file <path>", "Path to a JSON file containing constructor args array (recommended on PowerShell)").option("--build", "Run `forge build` before deploying").option("--attach", "After deploy, register the permission on --sma").option("--sma <address>", "SMA to register on (required with --attach)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeploy));
44967
45124
  mandate.command("attach").description("Register an already-deployed permission on an SMA (EIP-712 RegisterPermission)").requiredOption("--address <mandateOrName>", "Permission address, or a name tracked locally").requiredOption("--sma <address>", "SMA to register the permission on").option("--label <label>", "Human-readable label shown in the signing UI").option("--json", "Emit machine-readable JSON").action(actionWith(mandateAttach));
44968
- mandate.command("deploy-clone").description("Deploy + register a standalone clone permission (e.g. boundedApprove) via the signing UI").requiredOption("--template <key>", "Standalone clone template key (e.g. boundedApprove)").requiredOption("--sma <address>", "SMA to deploy the clone for and register it on").option("--tokens <csv>", "Comma-separated allowed token addresses").option("--spenders <csv>", "Comma-separated allowed spender addresses").option("--max <amount>", "Max amount per tx in base units (default: uint256 max)").option("--label <label>", "Human-readable label to track this permission under").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeployClone));
45125
+ mandate.command("deploy-clone").description("[currently unavailable \u2014 no clone templates deployed on any chain; use `mandate deploy`] Deploy + register a standalone clone permission via the signing UI").requiredOption("--template <key>", "Standalone clone template key (e.g. boundedApprove)").requiredOption("--sma <address>", "SMA to deploy the clone for and register it on").option("--tokens <csv>", "Comma-separated allowed token addresses").option("--spenders <csv>", "Comma-separated allowed spender addresses").option("--max <amount>", "Max amount per tx in base units (default: uint256 max)").option("--label <label>", "Human-readable label to track this permission under").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeployClone));
44969
45126
  mandate.command("revoke").description("Revoke permission(s) from an SMA (EIP-712 RevokePermissions, owner-authorized)").option("--address <permissionOrName>", "Permission address, or a name tracked locally").requiredOption("--sma <address>", "Safe (SMA) to revoke the permission(s) from").option("--all", "Revoke every permission currently registered on the SMA").option("--json", "Output JSON").action(actionWith(mandateRevoke));
44970
45127
  mandate.command("templates").description("Show how to author your own permission contract (and any community-deployed addresses)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateTemplates));
44971
45128
  mandate.command("simulate").description(
44972
45129
  "Probe a permission against sample calls off-chain (eth_call, NO gas) \u2014 prove it accepts the calls you want and rejects the ones you don't, before authorizing on-chain"
44973
45130
  ).requiredOption("--address <permissionOrName>", "Permission to probe (address or tracked name)").option("--sma <address>", "SMA to probe as (ctx.account; defaults to .sail/account.json)").option("--target <address>", "Inline single call: target contract address").option("--calldata <hex>", "Inline single call: 0x-prefixed calldata").option("--value <wei>", "Inline single call: ETH value in wei (default 0)").option("--expect <pass|fail>", "Inline single call: expected outcome (sets non-zero exit on mismatch)").option("--label <text>", "Inline single call: human-readable label").option("--calls <file>", "Batch: JSON array of { target, calldata, value?, expect?, label? }").option("--json", "Emit machine-readable JSON").action(actionWith(mandateSimulate));
44974
45131
  mandate.command("update").description("Update metadata for a tracked permission contract (rename, source path, artifact path)").requiredOption("--address <mandateOrName>", "Permission address or tracked name to update").option("--name <label>", "New tracking label (must be unique within the same chain)").option("--source-path <path>", "Update the relative path to the Solidity source file").option("--artifact-path <path>", "Update the relative path to the Foundry artifact JSON").option("--json", "Emit machine-readable JSON").action(actionWith(mandateUpdate));
44975
- mandate.command("list").description("List permission contracts deployed from this project").action(action(async () => mandateContractsList()));
45132
+ mandate.command("list").description("List permission contracts deployed from this project").option("--json", "Emit machine-readable JSON").action(actionWith(mandateContractsList));
44976
45133
  program2.command("onboard").description("Set up an SMA, register a permission, confirm the agent is operational").option("--sma <address>", "Use a specific SMA address instead of prompting").option("--new-sma", "Create a new SMA via SailKernel").option("--salt <n>", "CREATE2 salt for deterministic Safe address (default: 0; use 0 for first SMA, increment for subsequent)").option("--template <kindOrAddress>", "Register this permission contract (kind, label, or address)").option("--skip-mandate", "Skip the permission registration step").option("--json", "Emit machine-readable JSON (implies non-interactive)").action(actionWith(onboard));
44977
45134
  var station = program2.command("station").description("Manage the persistent signing station (browser signing daemon)");
44978
45135
  station.command("start").description("Start the signing station and keep it running (blocks \u2014 run in the background)").option("--json", "Emit machine-readable JSON").action(actionWith(stationStart));