@dev.sail.money/sailor 1.1.0-62 → 1.1.0-64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.sail.money/sailor",
3
- "version": "1.1.0-62",
3
+ "version": "1.1.0-64",
4
4
  "description": "Operator toolkit for Sail Protocol",
5
5
  "bin": {
6
6
  "sailor": "packages/cli/dist/index.cjs"
@@ -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 path8 = require("node:path");
970
- var fs9 = require("node:fs");
969
+ var path9 = require("node:path");
970
+ var fs10 = 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 = path8.resolve(baseDir, baseName);
1903
- if (fs9.existsSync(localBin)) return localBin;
1904
- if (sourceExt.includes(path8.extname(baseName))) return void 0;
1902
+ const localBin = path9.resolve(baseDir, baseName);
1903
+ if (fs10.existsSync(localBin)) return localBin;
1904
+ if (sourceExt.includes(path9.extname(baseName))) return void 0;
1905
1905
  const foundExt = sourceExt.find(
1906
- (ext) => fs9.existsSync(`${localBin}${ext}`)
1906
+ (ext) => fs10.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 = fs9.realpathSync(this._scriptPath);
1918
+ resolvedScriptPath = fs10.realpathSync(this._scriptPath);
1919
1919
  } catch (err) {
1920
1920
  resolvedScriptPath = this._scriptPath;
1921
1921
  }
1922
- executableDir = path8.resolve(
1923
- path8.dirname(resolvedScriptPath),
1922
+ executableDir = path9.resolve(
1923
+ path9.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 = path8.basename(
1930
+ const legacyName = path9.basename(
1931
1931
  this._scriptPath,
1932
- path8.extname(this._scriptPath)
1932
+ path9.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(path8.extname(executableFile));
1943
+ launchWithNode = sourceExt.includes(path9.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 = path8.basename(filename, path8.extname(filename));
2783
+ this._name = path9.basename(filename, path9.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(path9) {
2798
- if (path9 === void 0) return this._executableDir;
2799
- this._executableDir = path9;
2797
+ executableDir(path10) {
2798
+ if (path10 === void 0) return this._executableDir;
2799
+ this._executableDir = path10;
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, path8) {
30775
+ function setupKzg(parameters, path9) {
30776
30776
  try {
30777
- parameters.loadTrustedSetup(path8);
30777
+ parameters.loadTrustedSetup(path9);
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_fs18 = require("node:fs");
35194
- var import_node_path14 = require("node:path");
35193
+ var import_node_fs19 = require("node:fs");
35194
+ var import_node_path15 = 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(path8) {
37127
- if (!/^[mM]'?/.test(path8)) {
37126
+ derive(path9) {
37127
+ if (!/^[mM]'?/.test(path9)) {
37128
37128
  throw new Error('Path must start with "m" or "M"');
37129
37129
  }
37130
- if (/^[mM]'?$/.test(path8)) {
37130
+ if (/^[mM]'?$/.test(path9)) {
37131
37131
  return this;
37132
37132
  }
37133
- const parts = path8.replace(/^[mM]'?\//, "").split("/");
37133
+ const parts = path9.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: path8, ...options } = {}) {
37466
- const hdKey = hdKey_.derive(path8 || `m/44'/60'/${accountIndex}'/${changeIndex}/${addressIndex}`);
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}`);
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(path8, password) {
37568
- const keystore = JSON.parse((0, import_node_fs.readFileSync)(path8, "utf-8"));
37567
+ static async fromKeystoreFile(path9, password) {
37568
+ const keystore = JSON.parse((0, import_node_fs.readFileSync)(path9, "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. */
@@ -39141,9 +39141,9 @@ var SigningServer = class {
39141
39141
  );
39142
39142
  }
39143
39143
  removeRuntimeState() {
39144
- const path8 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39144
+ const path9 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
39145
39145
  try {
39146
- if ((0, import_node_fs5.existsSync)(path8)) (0, import_node_fs5.unlinkSync)(path8);
39146
+ if ((0, import_node_fs5.existsSync)(path9)) (0, import_node_fs5.unlinkSync)(path9);
39147
39147
  } catch {
39148
39148
  }
39149
39149
  }
@@ -40557,8 +40557,8 @@ function scaffoldFoundryWorkspace(root) {
40557
40557
  writeIfMissing((0, import_node_path6.join)(root, "mandates", "BoundedCallPermission.sol"), EXAMPLE_MANDATE_SOL);
40558
40558
  writeIfMissing((0, import_node_path6.join)(root, "mandates", "README.md"), MANDATES_README);
40559
40559
  }
40560
- function writeIfMissing(path8, content) {
40561
- if (!(0, import_node_fs7.existsSync)(path8)) (0, import_node_fs7.writeFileSync)(path8, content, "utf8");
40560
+ function writeIfMissing(path9, content) {
40561
+ if (!(0, import_node_fs7.existsSync)(path9)) (0, import_node_fs7.writeFileSync)(path9, content, "utf8");
40562
40562
  }
40563
40563
 
40564
40564
  // src/commands/init.ts
@@ -44044,8 +44044,423 @@ async function scan(options) {
44044
44044
  );
44045
44045
  }
44046
44046
 
44047
- // src/commands/trigger.ts
44047
+ // src/commands/service.ts
44048
44048
  var import_node_child_process2 = require("node:child_process");
44049
+ var import_node_fs16 = __toESM(require("node:fs"), 1);
44050
+ var import_node_os = __toESM(require("node:os"), 1);
44051
+ var import_node_path12 = __toESM(require("node:path"), 1);
44052
+ function sanitizeName(raw) {
44053
+ const s = raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
44054
+ return s || "agent";
44055
+ }
44056
+ function launchdLabel(projectName) {
44057
+ return `money.sail.${projectName}`;
44058
+ }
44059
+ function systemdUnitName(projectName) {
44060
+ return `sail-${projectName}.service`;
44061
+ }
44062
+ function windowsTaskName(projectName) {
44063
+ return `Sail-${projectName}`;
44064
+ }
44065
+ function runArgs(cfg) {
44066
+ const args = ["run"];
44067
+ if (cfg.chain != null) args.push("--chain", String(cfg.chain));
44068
+ return args;
44069
+ }
44070
+ function xmlEscape(s) {
44071
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
44072
+ }
44073
+ function buildLaunchdPlist(cfg) {
44074
+ const label = launchdLabel(cfg.projectName);
44075
+ const args = [cfg.nodePath, cfg.cliEntry, ...runArgs(cfg)];
44076
+ const argXml = args.map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
44077
+ const intervalEnv = cfg.interval != null ? ` <key>EnvironmentVariables</key>
44078
+ <dict>
44079
+ <key>SAILOR_INTERVAL</key>
44080
+ <string>${cfg.interval}</string>
44081
+ </dict>
44082
+ ` : "";
44083
+ return `<?xml version="1.0" encoding="UTF-8"?>
44084
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
44085
+ <plist version="1.0">
44086
+ <dict>
44087
+ <key>Label</key>
44088
+ <string>${label}</string>
44089
+ <key>ProgramArguments</key>
44090
+ <array>
44091
+ ${argXml}
44092
+ </array>
44093
+ <key>WorkingDirectory</key>
44094
+ <string>${xmlEscape(cfg.projectDir)}</string>
44095
+ ${intervalEnv} <key>RunAtLoad</key>
44096
+ <true/>
44097
+ <key>KeepAlive</key>
44098
+ <dict>
44099
+ <key>SuccessfulExit</key>
44100
+ <false/>
44101
+ </dict>
44102
+ <key>ThrottleInterval</key>
44103
+ <integer>${cfg.restartSec ?? 30}</integer>
44104
+ <key>StandardOutPath</key>
44105
+ <string>${xmlEscape(cfg.logPath)}</string>
44106
+ <key>StandardErrorPath</key>
44107
+ <string>${xmlEscape(cfg.logPath)}</string>
44108
+ </dict>
44109
+ </plist>
44110
+ `;
44111
+ }
44112
+ function buildSystemdUnit(cfg) {
44113
+ const exec = [cfg.nodePath, cfg.cliEntry, ...runArgs(cfg)].map((a) => /\s/.test(a) ? `"${a}"` : a).join(" ");
44114
+ const intervalEnv = cfg.interval != null ? `Environment=SAILOR_INTERVAL=${cfg.interval}
44115
+ ` : "";
44116
+ return `[Unit]
44117
+ Description=Sail agent \u2014 ${cfg.projectName}
44118
+ After=network-online.target
44119
+ Wants=network-online.target
44120
+
44121
+ [Service]
44122
+ Type=simple
44123
+ WorkingDirectory=${cfg.projectDir}
44124
+ ${intervalEnv}ExecStart=${exec}
44125
+ Restart=on-failure
44126
+ RestartSec=${cfg.restartSec ?? 30}
44127
+ StandardOutput=append:${cfg.logPath}
44128
+ StandardError=append:${cfg.logPath}
44129
+
44130
+ [Install]
44131
+ WantedBy=default.target
44132
+ `;
44133
+ }
44134
+ function buildWindowsTaskXml(cfg) {
44135
+ const runTail = runArgs(cfg).join(" ");
44136
+ const intervalSet = cfg.interval != null ? `set SAILOR_INTERVAL=${cfg.interval}&& ` : "";
44137
+ const inner = `${intervalSet}"${cfg.nodePath}" "${cfg.cliEntry}" ${runTail} >> "${cfg.logPath}" 2>&1`;
44138
+ const args = `/c "${inner}"`;
44139
+ return `<?xml version="1.0" encoding="UTF-16"?>
44140
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
44141
+ <RegistrationInfo>
44142
+ <Description>Sail agent \u2014 ${xmlEscape(cfg.projectName)}</Description>
44143
+ </RegistrationInfo>
44144
+ <Triggers>
44145
+ <LogonTrigger>
44146
+ <Enabled>true</Enabled>
44147
+ </LogonTrigger>
44148
+ </Triggers>
44149
+ <Settings>
44150
+ <RestartOnFailure>
44151
+ <Interval>PT${cfg.restartSec ?? 30}S</Interval>
44152
+ <Count>999</Count>
44153
+ </RestartOnFailure>
44154
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
44155
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
44156
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
44157
+ <ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
44158
+ </Settings>
44159
+ <Actions>
44160
+ <Exec>
44161
+ <Command>cmd.exe</Command>
44162
+ <Arguments>${xmlEscape(args)}</Arguments>
44163
+ <WorkingDirectory>${xmlEscape(cfg.projectDir)}</WorkingDirectory>
44164
+ </Exec>
44165
+ </Actions>
44166
+ </Task>
44167
+ `;
44168
+ }
44169
+ 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];
44173
+ return ["Desktop", "Documents", "Downloads"].includes(top ?? "");
44174
+ }
44175
+ function resolveCliEntry() {
44176
+ try {
44177
+ return import_node_fs16.default.realpathSync(process.argv[1]);
44178
+ } catch {
44179
+ return import_node_path12.default.resolve(process.argv[1]);
44180
+ }
44181
+ }
44182
+ 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"))) {
44185
+ throw new Error(
44186
+ `No .sail/ found in ${dir}.
44187
+ 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.)`
44188
+ );
44189
+ }
44190
+ return dir;
44191
+ }
44192
+ function passphraseReadiness(projectDir) {
44193
+ const account2 = (() => {
44194
+ try {
44195
+ return JSON.parse(
44196
+ import_node_fs16.default.readFileSync(import_node_path12.default.join(projectDir, ".sail", "account.json"), "utf-8")
44197
+ );
44198
+ } catch {
44199
+ return null;
44200
+ }
44201
+ })();
44202
+ const prevCwd = process.cwd();
44203
+ let keystorePresent = false;
44204
+ try {
44205
+ process.chdir(projectDir);
44206
+ keystorePresent = keyExists("manager", account2?.safe);
44207
+ } catch {
44208
+ keystorePresent = false;
44209
+ } finally {
44210
+ process.chdir(prevCwd);
44211
+ }
44212
+ const env = parseEnvFile(import_node_path12.default.join(projectDir, ".sail", ".env.local"));
44213
+ const passphraseInEnvFile = Boolean(env.SAIL_PASSPHRASE);
44214
+ return { keystorePresent, passphraseInEnvFile, ready: keystorePresent && passphraseInEnvFile };
44215
+ }
44216
+ function buildConfig(opts) {
44217
+ const projectDir = resolveProjectDir(opts.project);
44218
+ return {
44219
+ projectName: sanitizeName(import_node_path12.default.basename(projectDir)),
44220
+ projectDir,
44221
+ nodePath: process.execPath,
44222
+ cliEntry: resolveCliEntry(),
44223
+ logPath: import_node_path12.default.join(projectDir, ".sail", "agent.log"),
44224
+ interval: opts.interval != null ? Number(opts.interval) : void 0,
44225
+ chain: opts.chain != null ? Number(opts.chain) : void 0,
44226
+ restartSec: 30
44227
+ };
44228
+ }
44229
+ function plistPath(projectName) {
44230
+ return import_node_path12.default.join(import_node_os.default.homedir(), "Library", "LaunchAgents", `${launchdLabel(projectName)}.plist`);
44231
+ }
44232
+ function systemdPath(projectName) {
44233
+ return import_node_path12.default.join(import_node_os.default.homedir(), ".config", "systemd", "user", systemdUnitName(projectName));
44234
+ }
44235
+ async function serviceInstall(opts = {}) {
44236
+ const cfg = buildConfig(opts);
44237
+ const platform = process.platform;
44238
+ if (platform === "darwin" && isTccProtected(cfg.projectDir)) {
44239
+ const msg = `Project is under a macOS TCC-protected folder (Desktop/Documents/Downloads):
44240
+ ${cfg.projectDir}
44241
+ launchd services cannot read those folders, so the agent would silently fail to read .sail/.
44242
+ Move the project somewhere like ~/sail/<name>, or re-run with --force to install anyway.`;
44243
+ if (!opts.force) throw new Error(msg);
44244
+ console.warn(`\u26A0 ${msg}
44245
+ Proceeding because --force was given.`);
44246
+ }
44247
+ const pp = passphraseReadiness(cfg.projectDir);
44248
+ if (!pp.ready) {
44249
+ const why = !pp.keystorePresent ? "no agent keystore found" : "SAIL_PASSPHRASE is not in .sail/.env.local";
44250
+ const msg = `Passphrase not resolvable for an unattended service (${why}).
44251
+ A service does not inherit your shell env, so SAIL_PASSPHRASE must live in .sail/.env.local (0600).
44252
+ Run "sailor keys generate" (and save the passphrase) or set it in .sail/.env.local, then retry. Verify with "sailor doctor".`;
44253
+ if (!opts.force) throw new Error(msg);
44254
+ console.warn(`\u26A0 ${msg}
44255
+ Proceeding because --force was given.`);
44256
+ }
44257
+ import_node_fs16.default.mkdirSync(import_node_path12.default.dirname(cfg.logPath), { recursive: true });
44258
+ if (platform === "darwin") {
44259
+ const plist = buildLaunchdPlist(cfg);
44260
+ 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);
44263
+ const uid2 = process.getuid?.() ?? 0;
44264
+ try {
44265
+ try {
44266
+ (0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `gui/${uid2}/${launchdLabel(cfg.projectName)}`], {
44267
+ stdio: "ignore"
44268
+ });
44269
+ } catch {
44270
+ }
44271
+ (0, import_node_child_process2.execFileSync)("launchctl", ["bootstrap", `gui/${uid2}`, dest], { stdio: "inherit" });
44272
+ } catch (err) {
44273
+ throw new Error(`launchctl bootstrap failed: ${err.message}`);
44274
+ }
44275
+ emit(opts.json, () => okInstall(cfg, dest), { status: "ok", platform, unit: dest, ...cfg });
44276
+ return;
44277
+ }
44278
+ if (platform === "linux") {
44279
+ const unit = buildSystemdUnit(cfg);
44280
+ 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);
44283
+ try {
44284
+ (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
44285
+ (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "enable", "--now", systemdUnitName(cfg.projectName)], {
44286
+ stdio: "inherit"
44287
+ });
44288
+ } catch (err) {
44289
+ throw new Error(`systemctl --user enable failed: ${err.message}`);
44290
+ }
44291
+ emit(opts.json, () => okInstall(cfg, dest), { status: "ok", platform, unit: dest, ...cfg });
44292
+ return;
44293
+ }
44294
+ if (platform === "win32") {
44295
+ 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");
44298
+ try {
44299
+ (0, import_node_child_process2.execFileSync)(
44300
+ "schtasks",
44301
+ ["/Create", "/TN", windowsTaskName(cfg.projectName), "/XML", dest, "/F"],
44302
+ { stdio: "inherit" }
44303
+ );
44304
+ } catch (err) {
44305
+ throw new Error(`schtasks /Create failed: ${err.message}`);
44306
+ } finally {
44307
+ try {
44308
+ import_node_fs16.default.rmSync(dest, { force: true });
44309
+ } catch {
44310
+ }
44311
+ }
44312
+ emit(opts.json, () => okInstall(cfg, windowsTaskName(cfg.projectName)), {
44313
+ status: "ok",
44314
+ platform,
44315
+ unit: windowsTaskName(cfg.projectName),
44316
+ ...cfg
44317
+ });
44318
+ return;
44319
+ }
44320
+ throw new Error(`Unsupported platform for service install: ${platform}`);
44321
+ }
44322
+ function okInstall(cfg, unit) {
44323
+ console.log(`\u2713 Installed Sail agent service for "${cfg.projectName}"`);
44324
+ console.log(` unit: ${unit}`);
44325
+ console.log(` runs: ${cfg.nodePath} ${cfg.cliEntry} ${runArgs(cfg).join(" ")}`);
44326
+ console.log(` workdir: ${cfg.projectDir}`);
44327
+ console.log(` logs: ${cfg.logPath} (sailor service logs -f)`);
44328
+ console.log(" The loop runs unattended and restarts on crash.");
44329
+ console.log(
44330
+ " This is one execution host; you can also wake the agent on demand with `sailor trigger github`."
44331
+ );
44332
+ }
44333
+ async function serviceStatus(opts = {}) {
44334
+ const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44335
+ const platform = process.platform;
44336
+ let installed = false;
44337
+ let running = false;
44338
+ let detail = "";
44339
+ try {
44340
+ if (platform === "darwin") {
44341
+ installed = import_node_fs16.default.existsSync(plistPath(projectName));
44342
+ const uid2 = process.getuid?.() ?? 0;
44343
+ try {
44344
+ detail = (0, import_node_child_process2.execFileSync)("launchctl", ["print", `gui/${uid2}/${launchdLabel(projectName)}`], {
44345
+ encoding: "utf-8",
44346
+ stdio: ["ignore", "pipe", "ignore"]
44347
+ });
44348
+ installed = true;
44349
+ running = /state = running/.test(detail);
44350
+ } catch {
44351
+ }
44352
+ } else if (platform === "linux") {
44353
+ installed = import_node_fs16.default.existsSync(systemdPath(projectName));
44354
+ try {
44355
+ detail = (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "is-active", systemdUnitName(projectName)], {
44356
+ encoding: "utf-8",
44357
+ stdio: ["ignore", "pipe", "ignore"]
44358
+ }).trim();
44359
+ } catch (err) {
44360
+ detail = (err.stdout?.toString() ?? "").trim() || err.message;
44361
+ }
44362
+ running = detail === "active";
44363
+ } else if (platform === "win32") {
44364
+ try {
44365
+ detail = (0, import_node_child_process2.execFileSync)("schtasks", ["/Query", "/TN", windowsTaskName(projectName)], {
44366
+ encoding: "utf-8",
44367
+ stdio: ["ignore", "pipe", "ignore"]
44368
+ });
44369
+ installed = true;
44370
+ running = /Running/.test(detail);
44371
+ } catch (err) {
44372
+ detail = err.message;
44373
+ }
44374
+ }
44375
+ } catch (err) {
44376
+ detail = err.message;
44377
+ }
44378
+ const state = !installed ? "not installed" : running ? "running" : "installed \xB7 idle";
44379
+ emit(
44380
+ opts.json,
44381
+ () => {
44382
+ console.log(`Service "${projectName}": ${state}`);
44383
+ if (detail) console.log(detail.split("\n").slice(0, 8).join("\n"));
44384
+ },
44385
+ { status: "ok", project: projectName, installed, running }
44386
+ );
44387
+ }
44388
+ async function serviceStop(opts = {}) {
44389
+ const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44390
+ const platform = process.platform;
44391
+ if (platform === "darwin") {
44392
+ const uid2 = process.getuid?.() ?? 0;
44393
+ (0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `gui/${uid2}/${launchdLabel(projectName)}`], {
44394
+ stdio: "inherit"
44395
+ });
44396
+ } else if (platform === "linux") {
44397
+ (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "stop", systemdUnitName(projectName)], { stdio: "inherit" });
44398
+ } else if (platform === "win32") {
44399
+ (0, import_node_child_process2.execFileSync)("schtasks", ["/End", "/TN", windowsTaskName(projectName)], { stdio: "inherit" });
44400
+ }
44401
+ emit(opts.json, () => console.log(`\u2713 Stopped service "${projectName}" (not removed).`), {
44402
+ status: "ok",
44403
+ project: projectName,
44404
+ stopped: true
44405
+ });
44406
+ }
44407
+ async function serviceUninstall(opts = {}) {
44408
+ const projectName = sanitizeName(import_node_path12.default.basename(resolveProjectDir(opts.project)));
44409
+ const platform = process.platform;
44410
+ if (platform === "darwin") {
44411
+ const uid2 = process.getuid?.() ?? 0;
44412
+ try {
44413
+ (0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `gui/${uid2}/${launchdLabel(projectName)}`], {
44414
+ stdio: "ignore"
44415
+ });
44416
+ } catch {
44417
+ }
44418
+ import_node_fs16.default.rmSync(plistPath(projectName), { force: true });
44419
+ } else if (platform === "linux") {
44420
+ try {
44421
+ (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "disable", "--now", systemdUnitName(projectName)], {
44422
+ stdio: "ignore"
44423
+ });
44424
+ } catch {
44425
+ }
44426
+ import_node_fs16.default.rmSync(systemdPath(projectName), { force: true });
44427
+ try {
44428
+ (0, import_node_child_process2.execFileSync)("systemctl", ["--user", "daemon-reload"], { stdio: "ignore" });
44429
+ } catch {
44430
+ }
44431
+ } else if (platform === "win32") {
44432
+ (0, import_node_child_process2.execFileSync)("schtasks", ["/Delete", "/TN", windowsTaskName(projectName), "/F"], {
44433
+ stdio: "inherit"
44434
+ });
44435
+ }
44436
+ emit(opts.json, () => console.log(`\u2713 Uninstalled service "${projectName}".`), {
44437
+ status: "ok",
44438
+ project: projectName,
44439
+ uninstalled: true
44440
+ });
44441
+ }
44442
+ async function serviceLogs(opts = {}) {
44443
+ 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)) {
44446
+ console.log(`No log yet at ${logPath}. The service writes here once it runs.`);
44447
+ return;
44448
+ }
44449
+ if (opts.follow) {
44450
+ if (process.platform === "win32") {
44451
+ console.log(import_node_fs16.default.readFileSync(logPath, "utf-8"));
44452
+ console.log("(follow mode is not supported on Windows here \u2014 re-run to refresh)");
44453
+ return;
44454
+ }
44455
+ (0, import_node_child_process2.execFileSync)("tail", ["-f", logPath], { stdio: "inherit" });
44456
+ return;
44457
+ }
44458
+ const lines = import_node_fs16.default.readFileSync(logPath, "utf-8").split("\n");
44459
+ console.log(lines.slice(-200).join("\n"));
44460
+ }
44461
+
44462
+ // src/commands/trigger.ts
44463
+ var import_node_child_process3 = require("node:child_process");
44049
44464
  var GH_API = "https://api.github.com";
44050
44465
  function parseRepoFromRemoteUrl(url) {
44051
44466
  const trimmed = url.trim().replace(/\.git$/, "");
@@ -44061,7 +44476,7 @@ function resolveRepo(explicit) {
44061
44476
  }
44062
44477
  let url;
44063
44478
  try {
44064
- url = (0, import_node_child_process2.execFileSync)("git", ["remote", "get-url", "origin"], {
44479
+ url = (0, import_node_child_process3.execFileSync)("git", ["remote", "get-url", "origin"], {
44065
44480
  encoding: "utf-8",
44066
44481
  stdio: ["ignore", "pipe", "ignore"]
44067
44482
  }).trim();
@@ -44194,14 +44609,14 @@ async function sessionResume() {
44194
44609
  }
44195
44610
 
44196
44611
  // src/commands/station.ts
44197
- var import_node_fs16 = require("node:fs");
44198
- var import_node_path12 = require("node:path");
44199
- var RUNTIME_SERVER_FILE2 = (0, import_node_path12.join)(".sail", "runtime", "server.json");
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");
44200
44615
  function readState(projectRoot) {
44201
- const file = (0, import_node_path12.join)(projectRoot, RUNTIME_SERVER_FILE2);
44202
- if (!(0, import_node_fs16.existsSync)(file)) return null;
44616
+ const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44617
+ if (!(0, import_node_fs17.existsSync)(file)) return null;
44203
44618
  try {
44204
- return JSON.parse((0, import_node_fs16.readFileSync)(file, "utf8"));
44619
+ return JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
44205
44620
  } catch {
44206
44621
  return null;
44207
44622
  }
@@ -44273,9 +44688,9 @@ async function stationStop(options) {
44273
44688
  }
44274
44689
  const daemon = await discoverDaemon(projectRoot);
44275
44690
  if (!daemon) {
44276
- const file = (0, import_node_path12.join)(projectRoot, RUNTIME_SERVER_FILE2);
44691
+ const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44277
44692
  try {
44278
- if ((0, import_node_fs16.existsSync)(file)) (0, import_node_fs16.rmSync)(file);
44693
+ if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
44279
44694
  } catch {
44280
44695
  }
44281
44696
  emit(options.json, () => console.log("Station process not found; cleared stale state."), {
@@ -44291,9 +44706,9 @@ async function stationStop(options) {
44291
44706
  pid: state.pid
44292
44707
  });
44293
44708
  } catch {
44294
- const file = (0, import_node_path12.join)(projectRoot, RUNTIME_SERVER_FILE2);
44709
+ const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
44295
44710
  try {
44296
- if ((0, import_node_fs16.existsSync)(file)) (0, import_node_fs16.rmSync)(file);
44711
+ if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
44297
44712
  } catch {
44298
44713
  }
44299
44714
  emit(options.json, () => console.log("Station process not found; cleared stale state."), {
@@ -44347,16 +44762,16 @@ async function status() {
44347
44762
  }
44348
44763
 
44349
44764
  // src/commands/ui.ts
44350
- var import_node_child_process3 = require("node:child_process");
44351
- var import_node_fs17 = __toESM(require("node:fs"), 1);
44765
+ var import_node_child_process4 = require("node:child_process");
44766
+ var import_node_fs18 = __toESM(require("node:fs"), 1);
44352
44767
  var import_node_net2 = __toESM(require("node:net"), 1);
44353
- var import_node_path13 = __toESM(require("node:path"), 1);
44354
- var UI_STATE_FILE = import_node_path13.default.join(".sail", "runtime", "ui.json");
44768
+ var import_node_path14 = __toESM(require("node:path"), 1);
44769
+ var UI_STATE_FILE = import_node_path14.default.join(".sail", "runtime", "ui.json");
44355
44770
  function readState2(projectRoot) {
44356
- const file = import_node_path13.default.join(projectRoot, UI_STATE_FILE);
44357
- if (!import_node_fs17.default.existsSync(file)) return null;
44771
+ const file = import_node_path14.default.join(projectRoot, UI_STATE_FILE);
44772
+ if (!import_node_fs18.default.existsSync(file)) return null;
44358
44773
  try {
44359
- return JSON.parse(import_node_fs17.default.readFileSync(file, "utf-8"));
44774
+ return JSON.parse(import_node_fs18.default.readFileSync(file, "utf-8"));
44360
44775
  } catch {
44361
44776
  return null;
44362
44777
  }
@@ -44397,15 +44812,15 @@ function waitForPort(port, timeoutMs) {
44397
44812
  }
44398
44813
  async function uiCommand() {
44399
44814
  const distDir = cliDistDir();
44400
- const uiDistDir = import_node_path13.default.join(packageRoot(), "packages", "ui", "dist");
44401
- const serverBundle = import_node_path13.default.resolve(distDir, "server.cjs");
44815
+ const uiDistDir = import_node_path14.default.join(packageRoot(), "packages", "ui", "dist");
44816
+ const serverBundle = import_node_path14.default.resolve(distDir, "server.cjs");
44402
44817
  const projectRoot = process.cwd();
44403
- const sailDir2 = import_node_path13.default.join(projectRoot, ".sail");
44818
+ const sailDir2 = import_node_path14.default.join(projectRoot, ".sail");
44404
44819
  const port = await findFreePort(projectPort(projectRoot));
44405
- if (!import_node_fs17.default.existsSync(serverBundle)) {
44820
+ if (!import_node_fs18.default.existsSync(serverBundle)) {
44406
44821
  throw new Error(`Server bundle not found at ${serverBundle}. Re-run the sailor build.`);
44407
44822
  }
44408
- if (!import_node_fs17.default.existsSync(import_node_path13.default.join(uiDistDir, "index.html"))) {
44823
+ if (!import_node_fs18.default.existsSync(import_node_path14.default.join(uiDistDir, "index.html"))) {
44409
44824
  throw new Error(`UI dist not found at ${uiDistDir}. Re-run the sailor build.`);
44410
44825
  }
44411
44826
  const existing = readState2(projectRoot);
@@ -44413,17 +44828,17 @@ async function uiCommand() {
44413
44828
  console.log(`Sailor UI is already running (pid ${existing.pid}) at http://localhost:${existing.port}`);
44414
44829
  return;
44415
44830
  }
44416
- const runtimeDir = import_node_path13.default.join(projectRoot, ".sail", "runtime");
44417
- import_node_fs17.default.mkdirSync(runtimeDir, { recursive: true });
44418
- const logFile = import_node_path13.default.join(runtimeDir, "ui.log");
44419
- const logFd = import_node_fs17.default.openSync(logFile, "a");
44420
- const child = (0, import_node_child_process3.spawn)(process.execPath, [serverBundle], {
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");
44835
+ const child = (0, import_node_child_process4.spawn)(process.execPath, [serverBundle], {
44421
44836
  detached: true,
44422
44837
  stdio: ["ignore", logFd, logFd],
44423
44838
  env: { ...process.env, SAIL_DIR: sailDir2, SERVE_DIST: "1", PORT: String(port), SAILOR_UI_DIST: uiDistDir }
44424
44839
  });
44425
44840
  child.unref();
44426
- import_node_fs17.default.closeSync(logFd);
44841
+ import_node_fs18.default.closeSync(logFd);
44427
44842
  const READY_TIMEOUT_MS = 1e4;
44428
44843
  const deadline = Date.now() + READY_TIMEOUT_MS;
44429
44844
  let ready = false;
@@ -44437,18 +44852,18 @@ async function uiCommand() {
44437
44852
  if (!ready) {
44438
44853
  let tail = "";
44439
44854
  try {
44440
- tail = import_node_fs17.default.readFileSync(logFile, "utf-8").split("\n").filter(Boolean).slice(-15).join("\n");
44855
+ tail = import_node_fs18.default.readFileSync(logFile, "utf-8").split("\n").filter(Boolean).slice(-15).join("\n");
44441
44856
  } catch {
44442
44857
  }
44443
44858
  throw new Error(
44444
44859
  `Sailor UI failed to start within ${READY_TIMEOUT_MS / 1e3}s on port ${port}.` + (tail ? `
44445
44860
 
44446
44861
  Server output:
44447
- ${tail}` : ` See ${import_node_path13.default.relative(projectRoot, logFile)}.`)
44862
+ ${tail}` : ` See ${import_node_path14.default.relative(projectRoot, logFile)}.`)
44448
44863
  );
44449
44864
  }
44450
- import_node_fs17.default.writeFileSync(
44451
- import_node_path13.default.join(projectRoot, UI_STATE_FILE),
44865
+ import_node_fs18.default.writeFileSync(
44866
+ import_node_path14.default.join(projectRoot, UI_STATE_FILE),
44452
44867
  JSON.stringify({ pid: child.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
44453
44868
  );
44454
44869
  console.log(`Sailor UI started at http://localhost:${port} (pid ${child.pid})`);
@@ -44459,7 +44874,7 @@ function uiStatus() {
44459
44874
  if (state && isAlive(state.pid)) {
44460
44875
  console.log(`\u25CF running http://localhost:${state.port} (pid ${state.pid})`);
44461
44876
  } else {
44462
- if (state) import_node_fs17.default.rmSync(import_node_path13.default.join(process.cwd(), UI_STATE_FILE), { force: true });
44877
+ if (state) import_node_fs18.default.rmSync(import_node_path14.default.join(process.cwd(), UI_STATE_FILE), { force: true });
44463
44878
  console.log("\u25CB Sailor UI is not running");
44464
44879
  }
44465
44880
  }
@@ -44471,19 +44886,19 @@ function uiStop() {
44471
44886
  return;
44472
44887
  }
44473
44888
  if (!isAlive(state.pid)) {
44474
- import_node_fs17.default.rmSync(import_node_path13.default.join(projectRoot, UI_STATE_FILE), { force: true });
44889
+ import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
44475
44890
  console.log("Sailor UI is not running (stale state file removed).");
44476
44891
  return;
44477
44892
  }
44478
44893
  process.kill(state.pid, "SIGTERM");
44479
- import_node_fs17.default.rmSync(import_node_path13.default.join(projectRoot, UI_STATE_FILE), { force: true });
44894
+ import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
44480
44895
  console.log(`Stopped Sailor UI (pid ${state.pid}).`);
44481
44896
  }
44482
44897
 
44483
44898
  // src/index.ts
44484
44899
  function cliVersion() {
44485
44900
  try {
44486
- const pkg = JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path14.join)(packageRoot(), "package.json"), "utf-8"));
44901
+ const pkg = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path15.join)(packageRoot(), "package.json"), "utf-8"));
44487
44902
  return pkg.version ?? "0.0.0";
44488
44903
  } catch {
44489
44904
  return "0.0.0";
@@ -44597,6 +45012,14 @@ program2.command("capabilities").description(
44597
45012
  "Feasibility map (read-only): chains, kernel model, mandate templates, strategy primitives"
44598
45013
  ).option("--json", "Emit machine-readable JSON").action(actionWith(capabilities));
44599
45014
  program2.command("chains").description("List supported chains and their SailKernel deployment addresses").option("--verify", "Verify each kernel is deployed via eth_getCode (one RPC call per chain)").option("--json", "Emit machine-readable JSON").action(actionWith(chainsCommand));
45015
+ var service = program2.command("service").description(
45016
+ "Run the agent unattended as a local OS service (launchd/systemd/Task Scheduler).\nOne execution host among several \u2014 it runs the loop directly, and composes with\nthe external-trigger seam (`sailor trigger github`) and the cloud cron job."
45017
+ );
45018
+ service.command("install").description("Install + start the agent as a local service that restarts on crash").option("--interval <s>", "Loop interval in seconds (sets SAILOR_INTERVAL in the unit)").option("--project <path>", "Project root (must contain .sail/; default: current directory)").option("--chain <id>", "Chain ID to run on").option("--force", "Proceed despite a TCC-protected path or unresolved passphrase (with warning)").option("--json", "Emit machine-readable JSON").action(actionWith(serviceInstall));
45019
+ service.command("status").description("Show whether the agent service is installed and running").option("--project <path>", "Project root (default: current directory)").option("--json", "Emit machine-readable JSON").action(actionWith(serviceStatus));
45020
+ service.command("stop").description("Stop the service without removing it").option("--project <path>", "Project root (default: current directory)").option("--json", "Emit machine-readable JSON").action(actionWith(serviceStop));
45021
+ service.command("uninstall").description("Stop and remove the service unit entirely").option("--project <path>", "Project root (default: current directory)").option("--json", "Emit machine-readable JSON").action(actionWith(serviceUninstall));
45022
+ service.command("logs").description("Show the agent log (.sail/agent.log); -f to follow").option("--project <path>", "Project root (default: current directory)").option("-f, --follow", "Follow the log (tail -f)").action(actionWith(serviceLogs));
44600
45023
  program2.parse(process.argv);
44601
45024
  /*! Bundled license information:
44602
45025
 
@@ -5,7 +5,7 @@
5
5
  * Do not edit manually — run `pnpm build` to regenerate.
6
6
  *
7
7
  * Spec version : 1.2.0
8
- * Generated at : 2026-06-15T17:43:57.156Z
8
+ * Generated at : 2026-06-15T18:00:42.970Z
9
9
  */
10
10
  export declare const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
11
11
  export declare const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";
@@ -5,7 +5,7 @@
5
5
  * Do not edit manually — run `pnpm build` to regenerate.
6
6
  *
7
7
  * Spec version : 1.2.0
8
- * Generated at : 2026-06-15T17:43:57.156Z
8
+ * Generated at : 2026-06-15T18:00:42.970Z
9
9
  */
10
10
  export const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
11
11
  export const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";