@dev.sail.money/sailor 1.1.0-61 → 1.1.0-63
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
|
@@ -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
|
|
970
|
-
var
|
|
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 =
|
|
1903
|
-
if (
|
|
1904
|
-
if (sourceExt.includes(
|
|
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) =>
|
|
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 =
|
|
1918
|
+
resolvedScriptPath = fs10.realpathSync(this._scriptPath);
|
|
1919
1919
|
} catch (err) {
|
|
1920
1920
|
resolvedScriptPath = this._scriptPath;
|
|
1921
1921
|
}
|
|
1922
|
-
executableDir =
|
|
1923
|
-
|
|
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 =
|
|
1930
|
+
const legacyName = path9.basename(
|
|
1931
1931
|
this._scriptPath,
|
|
1932
|
-
|
|
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(
|
|
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 =
|
|
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(
|
|
2798
|
-
if (
|
|
2799
|
-
this._executableDir =
|
|
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,
|
|
30775
|
+
function setupKzg(parameters, path9) {
|
|
30776
30776
|
try {
|
|
30777
|
-
parameters.loadTrustedSetup(
|
|
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
|
|
35194
|
-
var
|
|
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(
|
|
37127
|
-
if (!/^[mM]'?/.test(
|
|
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(
|
|
37130
|
+
if (/^[mM]'?$/.test(path9)) {
|
|
37131
37131
|
return this;
|
|
37132
37132
|
}
|
|
37133
|
-
const parts =
|
|
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:
|
|
37466
|
-
const hdKey = hdKey_.derive(
|
|
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(
|
|
37568
|
-
const keystore = JSON.parse((0, import_node_fs.readFileSync)(
|
|
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
|
|
39144
|
+
const path9 = (0, import_node_path4.join)(this.runtimeDir, SERVER_STATE_FILE);
|
|
39145
39145
|
try {
|
|
39146
|
-
if ((0, import_node_fs5.existsSync)(
|
|
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(
|
|
40561
|
-
if (!(0, import_node_fs7.existsSync)(
|
|
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
|
|
@@ -43604,6 +43604,7 @@ async function runCommand(opts) {
|
|
|
43604
43604
|
for (const [k, v] of Object.entries(env)) {
|
|
43605
43605
|
if (v && !process.env[k]) process.env[k] = v;
|
|
43606
43606
|
}
|
|
43607
|
+
const runReason = opts.reason ?? process.env.SAIL_RUN_REASON ?? "manual";
|
|
43607
43608
|
const configChainId = readJsonFile(sailPath("config.json"))?.chainId;
|
|
43608
43609
|
const chainIdRaw = opts.chain != null ? String(opts.chain) : process.env.CHAIN_ID ?? env.CHAIN_ID ?? (configChainId != null ? String(configChainId) : void 0);
|
|
43609
43610
|
if (!chainIdRaw) {
|
|
@@ -43733,7 +43734,7 @@ Configure the chain in the SDK chain registry or set KERNEL_ADDRESS in .sail/.en
|
|
|
43733
43734
|
return d;
|
|
43734
43735
|
};
|
|
43735
43736
|
async function runTick() {
|
|
43736
|
-
appendActivity({ ts: nowIso(), actor: "agent", type: "tick_start", chainId });
|
|
43737
|
+
appendActivity({ ts: nowIso(), actor: "agent", type: "tick_start", chainId, reason: runReason });
|
|
43737
43738
|
let blockInfo = { number: 0n, timestamp: 0n };
|
|
43738
43739
|
try {
|
|
43739
43740
|
const block = await publicClient.getBlock();
|
|
@@ -43896,6 +43897,7 @@ Configure the chain in the SDK chain registry or set KERNEL_ADDRESS in .sail/.en
|
|
|
43896
43897
|
console.log(`Account: ${accountAddr}`);
|
|
43897
43898
|
console.log(`Chain: ${chainName2} (${chainId})`);
|
|
43898
43899
|
console.log(once ? "Mode: single tick (--once)" : `Interval: ${intervalSec}s`);
|
|
43900
|
+
console.log(`Reason: ${runReason}`);
|
|
43899
43901
|
console.log("Press Ctrl+C to stop");
|
|
43900
43902
|
console.log("");
|
|
43901
43903
|
writeAgentPid(chainId);
|
|
@@ -44042,6 +44044,525 @@ async function scan(options) {
|
|
|
44042
44044
|
);
|
|
44043
44045
|
}
|
|
44044
44046
|
|
|
44047
|
+
// src/commands/service.ts
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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");
|
|
44464
|
+
var GH_API = "https://api.github.com";
|
|
44465
|
+
function parseRepoFromRemoteUrl(url) {
|
|
44466
|
+
const trimmed = url.trim().replace(/\.git$/, "");
|
|
44467
|
+
const m = trimmed.match(/github\.com[/:]([^/]+)\/([^/]+?)$/);
|
|
44468
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
44469
|
+
}
|
|
44470
|
+
function resolveRepo(explicit) {
|
|
44471
|
+
if (explicit) {
|
|
44472
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(explicit)) {
|
|
44473
|
+
throw new Error(`--repo must be in "owner/repo" form \u2014 got: "${explicit}"`);
|
|
44474
|
+
}
|
|
44475
|
+
return explicit;
|
|
44476
|
+
}
|
|
44477
|
+
let url;
|
|
44478
|
+
try {
|
|
44479
|
+
url = (0, import_node_child_process3.execFileSync)("git", ["remote", "get-url", "origin"], {
|
|
44480
|
+
encoding: "utf-8",
|
|
44481
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
44482
|
+
}).trim();
|
|
44483
|
+
} catch {
|
|
44484
|
+
throw new Error(
|
|
44485
|
+
'Could not read the git remote "origin".\nPass --repo <owner/repo> explicitly.'
|
|
44486
|
+
);
|
|
44487
|
+
}
|
|
44488
|
+
const repo = parseRepoFromRemoteUrl(url);
|
|
44489
|
+
if (!repo) {
|
|
44490
|
+
throw new Error(
|
|
44491
|
+
`Could not parse a GitHub owner/repo from the origin remote (${url}).
|
|
44492
|
+
Pass --repo <owner/repo>.`
|
|
44493
|
+
);
|
|
44494
|
+
}
|
|
44495
|
+
return repo;
|
|
44496
|
+
}
|
|
44497
|
+
function resolveToken() {
|
|
44498
|
+
const token = process.env.SAIL_GH_TOKEN ?? process.env.GITHUB_TOKEN;
|
|
44499
|
+
if (!token) {
|
|
44500
|
+
throw new Error(
|
|
44501
|
+
"No GitHub token found. Set SAIL_GH_TOKEN (or GITHUB_TOKEN) in your environment.\nIt needs the `actions: write` permission on the repo (a fine-grained PAT or a GitHub App token).\nThe token is read from the environment only \u2014 it is never passed as an argument or stored."
|
|
44502
|
+
);
|
|
44503
|
+
}
|
|
44504
|
+
return token;
|
|
44505
|
+
}
|
|
44506
|
+
function buildDispatchRequest(args) {
|
|
44507
|
+
return {
|
|
44508
|
+
url: `${GH_API}/repos/${args.repo}/actions/workflows/${encodeURIComponent(args.workflow)}/dispatches`,
|
|
44509
|
+
method: "POST",
|
|
44510
|
+
headers: {
|
|
44511
|
+
Authorization: `Bearer ${args.token}`,
|
|
44512
|
+
Accept: "application/vnd.github+json",
|
|
44513
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
44514
|
+
"User-Agent": "sailor-cli",
|
|
44515
|
+
"Content-Type": "application/json"
|
|
44516
|
+
},
|
|
44517
|
+
body: JSON.stringify({ ref: args.ref, inputs: { reason: args.reason } })
|
|
44518
|
+
};
|
|
44519
|
+
}
|
|
44520
|
+
async function triggerGithub(options = {}) {
|
|
44521
|
+
const workflow = options.workflow ?? "agent-tick.yml";
|
|
44522
|
+
const ref = options.ref ?? "main";
|
|
44523
|
+
const reason = options.reason ?? "manual";
|
|
44524
|
+
const repo = resolveRepo(options.repo);
|
|
44525
|
+
const token = resolveToken();
|
|
44526
|
+
const req = buildDispatchRequest({ repo, workflow, ref, reason, token });
|
|
44527
|
+
let res;
|
|
44528
|
+
try {
|
|
44529
|
+
res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body });
|
|
44530
|
+
} catch (err) {
|
|
44531
|
+
throw new Error(`Could not reach GitHub to fire workflow_dispatch: ${err.message}`);
|
|
44532
|
+
}
|
|
44533
|
+
if (res.status === 204) {
|
|
44534
|
+
emit(
|
|
44535
|
+
options.json,
|
|
44536
|
+
() => {
|
|
44537
|
+
console.log(`\u2713 Triggered ${workflow} on ${repo}@${ref}`);
|
|
44538
|
+
console.log(` reason: ${reason}`);
|
|
44539
|
+
console.log(` runs: https://github.com/${repo}/actions/workflows/${workflow}`);
|
|
44540
|
+
},
|
|
44541
|
+
{ status: "ok", repo, workflow, ref, reason }
|
|
44542
|
+
);
|
|
44543
|
+
return;
|
|
44544
|
+
}
|
|
44545
|
+
let detail = "";
|
|
44546
|
+
try {
|
|
44547
|
+
detail = await res.text();
|
|
44548
|
+
} catch {
|
|
44549
|
+
}
|
|
44550
|
+
let message = `GitHub returned ${res.status}`;
|
|
44551
|
+
if (res.status === 401) message += " \u2014 bad or expired token";
|
|
44552
|
+
else if (res.status === 403) message += " \u2014 token lacks `actions: write` on this repo";
|
|
44553
|
+
else if (res.status === 404)
|
|
44554
|
+
message += ` \u2014 workflow "${workflow}" or repo "${repo}" not found (or the token can't see it)`;
|
|
44555
|
+
if (detail) {
|
|
44556
|
+
try {
|
|
44557
|
+
const parsed = JSON.parse(detail);
|
|
44558
|
+
if (parsed.message) message += `: ${parsed.message}`;
|
|
44559
|
+
} catch {
|
|
44560
|
+
message += `: ${detail.slice(0, 200)}`;
|
|
44561
|
+
}
|
|
44562
|
+
}
|
|
44563
|
+
throw new Error(message);
|
|
44564
|
+
}
|
|
44565
|
+
|
|
44045
44566
|
// src/commands/session.ts
|
|
44046
44567
|
function requireAccount() {
|
|
44047
44568
|
const account2 = readJsonFile(sailPath("account.json"));
|
|
@@ -44088,14 +44609,14 @@ async function sessionResume() {
|
|
|
44088
44609
|
}
|
|
44089
44610
|
|
|
44090
44611
|
// src/commands/station.ts
|
|
44091
|
-
var
|
|
44092
|
-
var
|
|
44093
|
-
var RUNTIME_SERVER_FILE2 = (0,
|
|
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");
|
|
44094
44615
|
function readState(projectRoot) {
|
|
44095
|
-
const file = (0,
|
|
44096
|
-
if (!(0,
|
|
44616
|
+
const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
|
|
44617
|
+
if (!(0, import_node_fs17.existsSync)(file)) return null;
|
|
44097
44618
|
try {
|
|
44098
|
-
return JSON.parse((0,
|
|
44619
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(file, "utf8"));
|
|
44099
44620
|
} catch {
|
|
44100
44621
|
return null;
|
|
44101
44622
|
}
|
|
@@ -44167,9 +44688,9 @@ async function stationStop(options) {
|
|
|
44167
44688
|
}
|
|
44168
44689
|
const daemon = await discoverDaemon(projectRoot);
|
|
44169
44690
|
if (!daemon) {
|
|
44170
|
-
const file = (0,
|
|
44691
|
+
const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
|
|
44171
44692
|
try {
|
|
44172
|
-
if ((0,
|
|
44693
|
+
if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
|
|
44173
44694
|
} catch {
|
|
44174
44695
|
}
|
|
44175
44696
|
emit(options.json, () => console.log("Station process not found; cleared stale state."), {
|
|
@@ -44185,9 +44706,9 @@ async function stationStop(options) {
|
|
|
44185
44706
|
pid: state.pid
|
|
44186
44707
|
});
|
|
44187
44708
|
} catch {
|
|
44188
|
-
const file = (0,
|
|
44709
|
+
const file = (0, import_node_path13.join)(projectRoot, RUNTIME_SERVER_FILE2);
|
|
44189
44710
|
try {
|
|
44190
|
-
if ((0,
|
|
44711
|
+
if ((0, import_node_fs17.existsSync)(file)) (0, import_node_fs17.rmSync)(file);
|
|
44191
44712
|
} catch {
|
|
44192
44713
|
}
|
|
44193
44714
|
emit(options.json, () => console.log("Station process not found; cleared stale state."), {
|
|
@@ -44241,16 +44762,16 @@ async function status() {
|
|
|
44241
44762
|
}
|
|
44242
44763
|
|
|
44243
44764
|
// src/commands/ui.ts
|
|
44244
|
-
var
|
|
44245
|
-
var
|
|
44765
|
+
var import_node_child_process4 = require("node:child_process");
|
|
44766
|
+
var import_node_fs18 = __toESM(require("node:fs"), 1);
|
|
44246
44767
|
var import_node_net2 = __toESM(require("node:net"), 1);
|
|
44247
|
-
var
|
|
44248
|
-
var UI_STATE_FILE =
|
|
44768
|
+
var import_node_path14 = __toESM(require("node:path"), 1);
|
|
44769
|
+
var UI_STATE_FILE = import_node_path14.default.join(".sail", "runtime", "ui.json");
|
|
44249
44770
|
function readState2(projectRoot) {
|
|
44250
|
-
const file =
|
|
44251
|
-
if (!
|
|
44771
|
+
const file = import_node_path14.default.join(projectRoot, UI_STATE_FILE);
|
|
44772
|
+
if (!import_node_fs18.default.existsSync(file)) return null;
|
|
44252
44773
|
try {
|
|
44253
|
-
return JSON.parse(
|
|
44774
|
+
return JSON.parse(import_node_fs18.default.readFileSync(file, "utf-8"));
|
|
44254
44775
|
} catch {
|
|
44255
44776
|
return null;
|
|
44256
44777
|
}
|
|
@@ -44291,15 +44812,15 @@ function waitForPort(port, timeoutMs) {
|
|
|
44291
44812
|
}
|
|
44292
44813
|
async function uiCommand() {
|
|
44293
44814
|
const distDir = cliDistDir();
|
|
44294
|
-
const uiDistDir =
|
|
44295
|
-
const serverBundle =
|
|
44815
|
+
const uiDistDir = import_node_path14.default.join(packageRoot(), "packages", "ui", "dist");
|
|
44816
|
+
const serverBundle = import_node_path14.default.resolve(distDir, "server.cjs");
|
|
44296
44817
|
const projectRoot = process.cwd();
|
|
44297
|
-
const sailDir2 =
|
|
44818
|
+
const sailDir2 = import_node_path14.default.join(projectRoot, ".sail");
|
|
44298
44819
|
const port = await findFreePort(projectPort(projectRoot));
|
|
44299
|
-
if (!
|
|
44820
|
+
if (!import_node_fs18.default.existsSync(serverBundle)) {
|
|
44300
44821
|
throw new Error(`Server bundle not found at ${serverBundle}. Re-run the sailor build.`);
|
|
44301
44822
|
}
|
|
44302
|
-
if (!
|
|
44823
|
+
if (!import_node_fs18.default.existsSync(import_node_path14.default.join(uiDistDir, "index.html"))) {
|
|
44303
44824
|
throw new Error(`UI dist not found at ${uiDistDir}. Re-run the sailor build.`);
|
|
44304
44825
|
}
|
|
44305
44826
|
const existing = readState2(projectRoot);
|
|
@@ -44307,17 +44828,17 @@ async function uiCommand() {
|
|
|
44307
44828
|
console.log(`Sailor UI is already running (pid ${existing.pid}) at http://localhost:${existing.port}`);
|
|
44308
44829
|
return;
|
|
44309
44830
|
}
|
|
44310
|
-
const runtimeDir =
|
|
44311
|
-
|
|
44312
|
-
const logFile =
|
|
44313
|
-
const logFd =
|
|
44314
|
-
const child = (0,
|
|
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], {
|
|
44315
44836
|
detached: true,
|
|
44316
44837
|
stdio: ["ignore", logFd, logFd],
|
|
44317
44838
|
env: { ...process.env, SAIL_DIR: sailDir2, SERVE_DIST: "1", PORT: String(port), SAILOR_UI_DIST: uiDistDir }
|
|
44318
44839
|
});
|
|
44319
44840
|
child.unref();
|
|
44320
|
-
|
|
44841
|
+
import_node_fs18.default.closeSync(logFd);
|
|
44321
44842
|
const READY_TIMEOUT_MS = 1e4;
|
|
44322
44843
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
44323
44844
|
let ready = false;
|
|
@@ -44331,18 +44852,18 @@ async function uiCommand() {
|
|
|
44331
44852
|
if (!ready) {
|
|
44332
44853
|
let tail = "";
|
|
44333
44854
|
try {
|
|
44334
|
-
tail =
|
|
44855
|
+
tail = import_node_fs18.default.readFileSync(logFile, "utf-8").split("\n").filter(Boolean).slice(-15).join("\n");
|
|
44335
44856
|
} catch {
|
|
44336
44857
|
}
|
|
44337
44858
|
throw new Error(
|
|
44338
44859
|
`Sailor UI failed to start within ${READY_TIMEOUT_MS / 1e3}s on port ${port}.` + (tail ? `
|
|
44339
44860
|
|
|
44340
44861
|
Server output:
|
|
44341
|
-
${tail}` : ` See ${
|
|
44862
|
+
${tail}` : ` See ${import_node_path14.default.relative(projectRoot, logFile)}.`)
|
|
44342
44863
|
);
|
|
44343
44864
|
}
|
|
44344
|
-
|
|
44345
|
-
|
|
44865
|
+
import_node_fs18.default.writeFileSync(
|
|
44866
|
+
import_node_path14.default.join(projectRoot, UI_STATE_FILE),
|
|
44346
44867
|
JSON.stringify({ pid: child.pid, port, startedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
44347
44868
|
);
|
|
44348
44869
|
console.log(`Sailor UI started at http://localhost:${port} (pid ${child.pid})`);
|
|
@@ -44353,7 +44874,7 @@ function uiStatus() {
|
|
|
44353
44874
|
if (state && isAlive(state.pid)) {
|
|
44354
44875
|
console.log(`\u25CF running http://localhost:${state.port} (pid ${state.pid})`);
|
|
44355
44876
|
} else {
|
|
44356
|
-
if (state)
|
|
44877
|
+
if (state) import_node_fs18.default.rmSync(import_node_path14.default.join(process.cwd(), UI_STATE_FILE), { force: true });
|
|
44357
44878
|
console.log("\u25CB Sailor UI is not running");
|
|
44358
44879
|
}
|
|
44359
44880
|
}
|
|
@@ -44365,19 +44886,19 @@ function uiStop() {
|
|
|
44365
44886
|
return;
|
|
44366
44887
|
}
|
|
44367
44888
|
if (!isAlive(state.pid)) {
|
|
44368
|
-
|
|
44889
|
+
import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
|
|
44369
44890
|
console.log("Sailor UI is not running (stale state file removed).");
|
|
44370
44891
|
return;
|
|
44371
44892
|
}
|
|
44372
44893
|
process.kill(state.pid, "SIGTERM");
|
|
44373
|
-
|
|
44894
|
+
import_node_fs18.default.rmSync(import_node_path14.default.join(projectRoot, UI_STATE_FILE), { force: true });
|
|
44374
44895
|
console.log(`Stopped Sailor UI (pid ${state.pid}).`);
|
|
44375
44896
|
}
|
|
44376
44897
|
|
|
44377
44898
|
// src/index.ts
|
|
44378
44899
|
function cliVersion() {
|
|
44379
44900
|
try {
|
|
44380
|
-
const pkg = JSON.parse((0,
|
|
44901
|
+
const pkg = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path15.join)(packageRoot(), "package.json"), "utf-8"));
|
|
44381
44902
|
return pkg.version ?? "0.0.0";
|
|
44382
44903
|
} catch {
|
|
44383
44904
|
return "0.0.0";
|
|
@@ -44462,9 +44983,16 @@ owner.command("connect").description("Open the signing station, wait for your wa
|
|
|
44462
44983
|
owner.command("show").description("Show the saved project owner").option("--json", "Emit machine-readable JSON").action(actionWith(ownerShow));
|
|
44463
44984
|
program2.command("scan").description("Discover the owner's SMAs, their permissions, and local keys; save to context.json").option("--owner <address>", "Owner address to scan (defaults to the saved project owner)").option("--json", "Emit machine-readable JSON").action(actionWith(scan));
|
|
44464
44985
|
program2.command("status").description("Show current account, permission, and session status").action(action(status));
|
|
44465
|
-
program2.command("run").description("Run the agent execution loop (use --once for a single tick)").option("--once", "Run a single tick then exit").option("--chain <chainId>", "Chain ID to run on (overrides CHAIN_ID env and .env.local)").
|
|
44986
|
+
program2.command("run").description("Run the agent execution loop (use --once for a single tick)").option("--once", "Run a single tick then exit").option("--chain <chainId>", "Chain ID to run on (overrides CHAIN_ID env and .env.local)").option(
|
|
44987
|
+
"--reason <text>",
|
|
44988
|
+
"Label why this run fired (observability only; also read from SAIL_RUN_REASON)"
|
|
44989
|
+
).action(async (opts) => {
|
|
44466
44990
|
try {
|
|
44467
|
-
await runCommand({
|
|
44991
|
+
await runCommand({
|
|
44992
|
+
once: opts.once,
|
|
44993
|
+
chain: opts.chain ? Number(opts.chain) : void 0,
|
|
44994
|
+
reason: opts.reason
|
|
44995
|
+
});
|
|
44468
44996
|
} catch (err) {
|
|
44469
44997
|
console.error(`Error: ${err.message}`);
|
|
44470
44998
|
closePrompts();
|
|
@@ -44472,6 +45000,8 @@ program2.command("run").description("Run the agent execution loop (use --once fo
|
|
|
44472
45000
|
}
|
|
44473
45001
|
closePrompts();
|
|
44474
45002
|
});
|
|
45003
|
+
var trigger = program2.command("trigger").description("Wake the agent on demand from an external system");
|
|
45004
|
+
trigger.command("github").description("Fire the agent's GitHub Actions workflow_dispatch (the same job the cron runs)").option("--workflow <file>", "Workflow file to dispatch", "agent-tick.yml").option("--ref <branch>", "Git ref to run the workflow on", "main").option("--reason <text>", "Why this run fired \u2014 recorded as the workflow's reason input").option("--repo <owner/repo>", "Override the repository (default: from the git origin remote)").option("--json", "Emit machine-readable JSON").action(actionWith(triggerGithub));
|
|
44475
45005
|
var session = program2.command("session").description("Control the agent session");
|
|
44476
45006
|
session.command("pause").description("Pause the agent session (revoke dispatch rights)").action(action(sessionPause));
|
|
44477
45007
|
session.command("resume").description("Resume a paused session").action(action(sessionResume));
|
|
@@ -44482,6 +45012,14 @@ program2.command("capabilities").description(
|
|
|
44482
45012
|
"Feasibility map (read-only): chains, kernel model, mandate templates, strategy primitives"
|
|
44483
45013
|
).option("--json", "Emit machine-readable JSON").action(actionWith(capabilities));
|
|
44484
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));
|
|
44485
45023
|
program2.parse(process.argv);
|
|
44486
45024
|
/*! Bundled license information:
|
|
44487
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:
|
|
8
|
+
* Generated at : 2026-06-15T17:59:04.614Z
|
|
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:
|
|
8
|
+
* Generated at : 2026-06-15T17:59:04.614Z
|
|
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";
|
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
name: Agent Tick
|
|
2
2
|
|
|
3
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
# WHEN does your agent run? Two mechanisms — you almost certainly need to tune
|
|
5
|
+
# the first one for your strategy.
|
|
6
|
+
#
|
|
7
|
+
# 1. schedule (cron, below) — a heartbeat / backstop. GitHub Actions cron is
|
|
8
|
+
# NON-PUNCTUAL: it drifts and is SKIPPED under load, so it is the wrong
|
|
9
|
+
# PRIMARY driver for anything latency-sensitive. Tune the cadence to your
|
|
10
|
+
# strategy's volatility:
|
|
11
|
+
# • LP / perps / liquidations → minutes — but don't rely on cron for it;
|
|
12
|
+
# use an external trigger (#2). Here cron is only a safety net.
|
|
13
|
+
# • DCA / periodic rebalance → daily.
|
|
14
|
+
# • treasury / slow rebalances → hourly to daily.
|
|
15
|
+
# The default below (hourly) is a GENERIC PLACEHOLDER — change it.
|
|
16
|
+
#
|
|
17
|
+
# 2. workflow_dispatch — an on-demand trigger. Anything that can make an HTTP
|
|
18
|
+
# call can wake this job the instant a condition is met (a price band, an
|
|
19
|
+
# on-chain event, a webhook). Fire it from the CLI: sailor trigger github
|
|
20
|
+
# This is how you get low-latency reaction that cron cannot give you.
|
|
21
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
3
23
|
on:
|
|
4
24
|
schedule:
|
|
5
|
-
#
|
|
6
|
-
- cron: "0
|
|
25
|
+
# Generic placeholder — hourly. TUNE THIS to your strategy (see above).
|
|
26
|
+
- cron: "0 * * * *"
|
|
7
27
|
workflow_dispatch:
|
|
28
|
+
inputs:
|
|
29
|
+
reason:
|
|
30
|
+
description: "Why this run fired (recorded in the run log for auditability)"
|
|
31
|
+
required: false
|
|
32
|
+
default: "manual"
|
|
8
33
|
|
|
9
34
|
jobs:
|
|
10
35
|
tick:
|
|
@@ -30,4 +55,6 @@ jobs:
|
|
|
30
55
|
RPC_URL: ${{ secrets.RPC_URL }}
|
|
31
56
|
SAIL_PASSPHRASE: ${{ secrets.SAIL_PASSPHRASE }}
|
|
32
57
|
CHAIN_ID: ${{ vars.CHAIN_ID || '8453' }}
|
|
58
|
+
# Labels the run: the dispatch reason, or "scheduled" for cron runs.
|
|
59
|
+
SAIL_RUN_REASON: ${{ github.event.inputs.reason || 'scheduled' }}
|
|
33
60
|
run: npx sailor run --once
|