@dev.sail.money/sailor 0.0.2-12 → 0.0.2-15
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/AGENTS.md +1 -1
- package/README.md +27 -26
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +20 -40
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/packages/ui/dist/assets/{add-DaJhwIBV.js → add-BHtIsGoW.js} +1 -1
- package/packages/ui/dist/assets/{all-wallets-BUxsqWXi.js → all-wallets-BH8IiQp_.js} +1 -1
- package/packages/ui/dist/assets/{app-store-DkltwTqE.js → app-store-C7je0Hvt.js} +1 -1
- package/packages/ui/dist/assets/{apple-owVOeaIT.js → apple-C24YGi7n.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-D2mmNJve.js → arrow-bottom-NmB75e6T.js} +1 -1
- package/packages/ui/dist/assets/{arrow-bottom-circle-CbNYijx-.js → arrow-bottom-circle-D9vkbcGm.js} +1 -1
- package/packages/ui/dist/assets/{arrow-left-DJB61s4C.js → arrow-left-BXX7egmu.js} +1 -1
- package/packages/ui/dist/assets/{arrow-right-BBrsQ9R4.js → arrow-right-CL5l1ER5.js} +1 -1
- package/packages/ui/dist/assets/{arrow-top-Cil6bOc8.js → arrow-top-B3NcsHvl.js} +1 -1
- package/packages/ui/dist/assets/{bank-CbwEmRo3.js → bank-DyxCAL_C.js} +1 -1
- package/packages/ui/dist/assets/{basic-CLNfjw3m.js → basic-CnjXr1WW.js} +1 -1
- package/packages/ui/dist/assets/{browser-B5TtF4Pb.js → browser-C1sQ7PjQ.js} +1 -1
- package/packages/ui/dist/assets/{card-CO7BVB-C.js → card-DUcWbZgm.js} +1 -1
- package/packages/ui/dist/assets/{ccip-2W7K3_J3.js → ccip-ZC8164Ut.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-BEtSHq9m.js → checkmark-BFIb0gZI.js} +1 -1
- package/packages/ui/dist/assets/{checkmark-bold-D9xGHzPE.js → checkmark-bold-GxOovLvw.js} +1 -1
- package/packages/ui/dist/assets/{chevron-bottom-BDztht6i.js → chevron-bottom-BWFm5iOO.js} +1 -1
- package/packages/ui/dist/assets/{chevron-left-EV4GFNbc.js → chevron-left-Dw1vrfPS.js} +1 -1
- package/packages/ui/dist/assets/{chevron-right-B4_bB9oR.js → chevron-right-Lj-4a_f7.js} +1 -1
- package/packages/ui/dist/assets/{chevron-top-D54xPNzF.js → chevron-top-CNtyUs8e.js} +1 -1
- package/packages/ui/dist/assets/{chrome-store-DYUpAJJq.js → chrome-store-BFjfxT2g.js} +1 -1
- package/packages/ui/dist/assets/{clock-Ca1T1Soz.js → clock-upNWT1cC.js} +1 -1
- package/packages/ui/dist/assets/{close-BZqWjurK.js → close-BXOvqFtp.js} +1 -1
- package/packages/ui/dist/assets/{coinPlaceholder-e6fl2XDo.js → coinPlaceholder-IDofua41.js} +1 -1
- package/packages/ui/dist/assets/{compass-DCLC7zIh.js → compass-Du2xGTuZ.js} +1 -1
- package/packages/ui/dist/assets/{copy-Th2AaD-O.js → copy-thfTJs3L.js} +1 -1
- package/packages/ui/dist/assets/{core-Ckx_cyuH.js → core-C8jSlwHY.js} +3 -3
- package/packages/ui/dist/assets/cursor-Dc-Hxk5X.js +3 -0
- package/packages/ui/dist/assets/{cursor-transparent-BKHeABKB.js → cursor-transparent-DGzNzXxd.js} +1 -1
- package/packages/ui/dist/assets/{desktop-CBjY8t6F.js → desktop-BozY9d6T.js} +1 -1
- package/packages/ui/dist/assets/{disconnect-DbSs2cli.js → disconnect-B-oC8Fzu.js} +1 -1
- package/packages/ui/dist/assets/{discord-ZlLOAUkM.js → discord-CpgvcuGY.js} +1 -1
- package/packages/ui/dist/assets/{etherscan-CKUrqWYN.js → etherscan-DB-rL796.js} +1 -1
- package/packages/ui/dist/assets/{events-CiKP71cK.js → events-i9ztcj9W.js} +1 -1
- package/packages/ui/dist/assets/{exclamation-triangle-DA1QzFiO.js → exclamation-triangle-Ct0mzv3Y.js} +1 -1
- package/packages/ui/dist/assets/{extension-BVJkmvpJ.js → extension-CubWAVae.js} +1 -1
- package/packages/ui/dist/assets/{external-link-D_bsR7B2.js → external-link-CgYSHh7f.js} +1 -1
- package/packages/ui/dist/assets/{facebook-CmFmhojx.js → facebook-DI_kGikU.js} +1 -1
- package/packages/ui/dist/assets/{fallback-Ofl6uSnB.js → fallback-CtKplO-p.js} +1 -1
- package/packages/ui/dist/assets/{farcaster-Co-M3Ss8.js → farcaster-PEZAXYqi.js} +1 -1
- package/packages/ui/dist/assets/{filters-B1WwNaFU.js → filters-D6NPvINV.js} +1 -1
- package/packages/ui/dist/assets/{github-CP4fP6gn.js → github-BRk_ww3A.js} +1 -1
- package/packages/ui/dist/assets/{google-CsOIXJ6V.js → google-3tO0AiTd.js} +1 -1
- package/packages/ui/dist/assets/{help-circle-DiMkomdF.js → help-circle-DtbseC_9.js} +1 -1
- package/packages/ui/dist/assets/{id-lmscL5LX.js → id-DNQF-HQS.js} +1 -1
- package/packages/ui/dist/assets/{image-B-ubJrY5.js → image-D4l2W9gw.js} +1 -1
- package/packages/ui/dist/assets/{index-DVgfCzCo.js → index-A8Mpr5Rm.js} +1 -1
- package/packages/ui/dist/assets/{index-BaukYv-x.js → index-BnYYo2S0.js} +1 -1
- package/packages/ui/dist/assets/{index-CZR1Qjhs.js → index-D-PqJKnq.js} +1 -1
- package/packages/ui/dist/assets/{index-Q2Yai4Fe.js → index-L6zhWbjO.js} +70 -70
- package/packages/ui/dist/assets/{index-Dbh5V1Z0.js → index-cl7Zazmz.js} +1 -1
- package/packages/ui/dist/assets/{index-CF0KMmke.js → index-vIXQ5sb2.js} +3 -3
- package/packages/ui/dist/assets/{index.es-C78cE5SI.js → index.es-BxPfiRyX.js} +4 -4
- package/packages/ui/dist/assets/{info-Cqg57EVo.js → info-DAwJNl3r.js} +1 -1
- package/packages/ui/dist/assets/{info-circle-DkeSWNKV.js → info-circle-lvyXhzfD.js} +1 -1
- package/packages/ui/dist/assets/{lightbulb-DNlO4qKh.js → lightbulb-BkrBfCQo.js} +1 -1
- package/packages/ui/dist/assets/{mail-kVQ8Jb9Y.js → mail-CrRh9uAx.js} +1 -1
- package/packages/ui/dist/assets/{metamask-sdk-CBalSvz7.js → metamask-sdk-SH1hL_jU.js} +1 -1
- package/packages/ui/dist/assets/{mobile-BEteuhF7.js → mobile-Cq71LsIe.js} +1 -1
- package/packages/ui/dist/assets/{more-DBWmXQli.js → more-CzCaTRQT.js} +1 -1
- package/packages/ui/dist/assets/{network-placeholder-Dg1uUHiL.js → network-placeholder-B7ilsHLj.js} +1 -1
- package/packages/ui/dist/assets/{nftPlaceholder-i3AHSiD9.js → nftPlaceholder-DYo4ThqR.js} +1 -1
- package/packages/ui/dist/assets/{off-BtMm0fi2.js → off-CVXtiamx.js} +1 -1
- package/packages/ui/dist/assets/{parseSignature-Cb5FlWWg.js → parseSignature-DDqjAA1x.js} +1 -1
- package/packages/ui/dist/assets/{play-store-iKKkXa6a.js → play-store-Bxo5wDfY.js} +1 -1
- package/packages/ui/dist/assets/{plus-CA5NaRtb.js → plus-ByOmN8u0.js} +1 -1
- package/packages/ui/dist/assets/{qr-code-D2kiqR7h.js → qr-code-BLcaF-bG.js} +1 -1
- package/packages/ui/dist/assets/{recycle-horizontal-Dcme7R03.js → recycle-horizontal-R32E5-sJ.js} +1 -1
- package/packages/ui/dist/assets/{refresh-Dega3sDp.js → refresh-DV3LEAlt.js} +1 -1
- package/packages/ui/dist/assets/{reown-logo-xNkksyWJ.js → reown-logo-CzXV-ULV.js} +1 -1
- package/packages/ui/dist/assets/{search-HYl7NO8x.js → search-C4ArXMR1.js} +1 -1
- package/packages/ui/dist/assets/{secp256k1-Cxd6_SiH.js → secp256k1-CigFWhM4.js} +1 -1
- package/packages/ui/dist/assets/{send-CJU8CUAo.js → send-nlnStDog.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontal-IMUKiUre.js → swapHorizontal-I-zOG8Yz.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalBold-CNYnNJ9-.js → swapHorizontalBold-5VnEg1-A.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalMedium-B9VxEYsT.js → swapHorizontalMedium-DK837_PS.js} +1 -1
- package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Dz33l_Jh.js → swapHorizontalRoundedBold-EcrRbYtU.js} +1 -1
- package/packages/ui/dist/assets/{swapVertical-CHUmjVJ0.js → swapVertical--ZRk6QJu.js} +1 -1
- package/packages/ui/dist/assets/{telegram-kl9S2mbU.js → telegram-DQOf_6vw.js} +1 -1
- package/packages/ui/dist/assets/{three-dots-U5lhA1Am.js → three-dots-RaCioLRu.js} +1 -1
- package/packages/ui/dist/assets/{twitch-KTEUWXEp.js → twitch-CXMYNleq.js} +1 -1
- package/packages/ui/dist/assets/{twitterIcon-BHiq8mRg.js → twitterIcon-CafuOSTc.js} +1 -1
- package/packages/ui/dist/assets/{verify-CfN-BXNd.js → verify-B5SZMJAG.js} +1 -1
- package/packages/ui/dist/assets/{verify-filled-DwZccetj.js → verify-filled-BrlLx5ry.js} +1 -1
- package/packages/ui/dist/assets/{w3m-modal-CS-PFqPE.js → w3m-modal-57iUR7pY.js} +1 -1
- package/packages/ui/dist/assets/{wallet-DVlGkhOY.js → wallet-Fu8Tn5wK.js} +1 -1
- package/packages/ui/dist/assets/{wallet-placeholder-CvR_iEWX.js → wallet-placeholder-BYpbF3M_.js} +1 -1
- package/packages/ui/dist/assets/{walletconnect-8pZBDvVI.js → walletconnect-BT9pozGB.js} +1 -1
- package/packages/ui/dist/assets/{warning-circle-ylLEE0Yp.js → warning-circle-BQ8awbZW.js} +1 -1
- package/packages/ui/dist/assets/{x-C_TBsTMj.js → x-D21DQ5BR.js} +1 -1
- package/packages/ui/dist/index.html +1 -1
- package/scripts/check-init.mjs +2 -3
- package/scripts/postinstall.js +14 -324
- package/templates/{dca-rebalancer → default}/.sail/config.json +2 -2
- package/templates/default/AGENTS.md +92 -0
- package/templates/default/README.md +16 -0
- package/templates/default/examples/dca/README.md +16 -0
- package/templates/default/examples/dca/agent.ts +174 -0
- package/templates/{dca-rebalancer/src → default/examples/dca}/mandate.ts +8 -30
- package/templates/{dca-rebalancer → default}/package.json +2 -2
- package/templates/default/src/agent.ts +37 -0
- package/templates/{dca-rebalancer → default}/src/config.ts +8 -1
- package/templates/default/src/mandate.ts +22 -0
- package/packages/ui/dist/assets/cursor-DV7rOqbJ.js +0 -3
- package/templates/dca-rebalancer/AGENTS.md +0 -246
- package/templates/dca-rebalancer/AGENT_PLAYBOOK.md +0 -110
- package/templates/dca-rebalancer/README.md +0 -16
- package/templates/dca-rebalancer/src/agent.ts +0 -253
- /package/templates/{dca-rebalancer → default}/.cursor/rules +0 -0
- /package/templates/{dca-rebalancer → default}/.env.example +0 -0
- /package/templates/{dca-rebalancer → default}/.github/workflows/agent-tick.yml +0 -0
- /package/templates/{dca-rebalancer → default}/.sail/.gitkeep +0 -0
- /package/templates/{dca-rebalancer → default}/.sail/README.md +0 -0
- /package/templates/{dca-rebalancer → default}/CLAUDE.md +0 -0
- /package/templates/{dca-rebalancer → default}/_gitignore +0 -0
- /package/templates/{dca-rebalancer → default}/docs/PERMISSION_MODEL.md +0 -0
- /package/templates/{dca-rebalancer → default}/tsconfig.json +0 -0
- /package/templates/{dca-rebalancer → default}/ui/README.md +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as l}from"./core-
|
|
1
|
+
import{F as l}from"./core-C8jSlwHY.js";import"./index-L6zhWbjO.js";import"./events-i9ztcj9W.js";import"./index.es-BxPfiRyX.js";import"./fallback-CtKplO-p.js";const o=l`<svg fill="none" viewBox="0 0 96 67">
|
|
2
2
|
<path
|
|
3
3
|
fill="currentColor"
|
|
4
4
|
d="M25.32 18.8a32.56 32.56 0 0 1 45.36 0l1.5 1.47c.63.62.63 1.61 0 2.22l-5.15 5.05c-.31.3-.82.3-1.14 0l-2.07-2.03a22.71 22.71 0 0 0-31.64 0l-2.22 2.18c-.31.3-.82.3-1.14 0l-5.15-5.05a1.55 1.55 0 0 1 0-2.22l1.65-1.62Zm56.02 10.44 4.59 4.5c.63.6.63 1.6 0 2.21l-20.7 20.26c-.62.61-1.63.61-2.26 0L48.28 41.83a.4.4 0 0 0-.56 0L33.03 56.21c-.63.61-1.64.61-2.27 0L10.07 35.95a1.55 1.55 0 0 1 0-2.22l4.59-4.5a1.63 1.63 0 0 1 2.27 0L31.6 43.63a.4.4 0 0 0 .57 0l14.69-14.38a1.63 1.63 0 0 1 2.26 0l14.69 14.38a.4.4 0 0 0 .57 0l14.68-14.38a1.63 1.63 0 0 1 2.27 0Z"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as r}from"./core-
|
|
1
|
+
import{F as r}from"./core-C8jSlwHY.js";import"./index-L6zhWbjO.js";import"./events-i9ztcj9W.js";import"./index.es-BxPfiRyX.js";import"./fallback-CtKplO-p.js";const a=r`<svg fill="none" viewBox="0 0 20 20">
|
|
2
2
|
<path
|
|
3
3
|
fill="currentColor"
|
|
4
4
|
d="M11 6.67a1 1 0 1 0-2 0v2.66a1 1 0 0 0 2 0V6.67ZM10 14.5a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{F as l}from"./core-
|
|
1
|
+
import{F as l}from"./core-C8jSlwHY.js";import"./index-L6zhWbjO.js";import"./events-i9ztcj9W.js";import"./index.es-BxPfiRyX.js";import"./fallback-CtKplO-p.js";const a=l`<svg fill="none" viewBox="0 0 41 40">
|
|
2
2
|
<g clip-path="url(#a)">
|
|
3
3
|
<path fill="#000" d="M.8 0h40v40H.8z" />
|
|
4
4
|
<path
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#040b16" />
|
|
7
7
|
<title>Sail - Unlocking Personalized Money</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-L6zhWbjO.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-CKxgNxS9.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/scripts/check-init.mjs
CHANGED
|
@@ -53,9 +53,8 @@ try {
|
|
|
53
53
|
fail(`\`sailor init ${PROJECT}\` exited non-zero.\n ${out || err.message}`);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// A successful fresh init prints the
|
|
57
|
-
|
|
58
|
-
if (!/Say: "start"/.test(stdout)) {
|
|
56
|
+
// A successful fresh init prints the single-line handoff to the AI assistant.
|
|
57
|
+
if (!/say start/i.test(stdout)) {
|
|
59
58
|
fail(`init did not report success.\n stdout: ${stdout.trim()}`);
|
|
60
59
|
}
|
|
61
60
|
|
package/scripts/postinstall.js
CHANGED
|
@@ -2,24 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Post-install script for the Sailor package.
|
|
4
4
|
*
|
|
5
|
-
* Runs automatically after `npm install` / `pnpm add`.
|
|
6
|
-
* 1. sailor init
|
|
7
|
-
* 2.
|
|
8
|
-
* 3. sailor ui — start the dashboard server in background
|
|
9
|
-
* 4. browser launch — open the dashboard so the user hits the onboarding flow
|
|
5
|
+
* Runs automatically after `npm install` / `pnpm add`. Does two things:
|
|
6
|
+
* 1. sailor init — scaffold the .sail/ workspace (skipped if already done)
|
|
7
|
+
* 2. Print the one-line handoff banner directing the user to their AI assistant
|
|
10
8
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* the user next.
|
|
9
|
+
* The UI and browser launch that previously ran here have been removed.
|
|
10
|
+
* The chat (AGENTS.md) owns the welcome — the terminal just directs traffic.
|
|
14
11
|
*/
|
|
15
12
|
|
|
16
13
|
"use strict";
|
|
17
14
|
|
|
18
15
|
const fs = require("node:fs");
|
|
19
16
|
const path = require("node:path");
|
|
20
|
-
const
|
|
21
|
-
const os = require("node:os");
|
|
22
|
-
const { execFileSync, spawn } = require("node:child_process");
|
|
17
|
+
const { execFileSync } = require("node:child_process");
|
|
23
18
|
|
|
24
19
|
// Directory where the user ran `npm install` / `pnpm add`
|
|
25
20
|
const initCwd = process.env.INIT_CWD || process.cwd();
|
|
@@ -40,327 +35,22 @@ if (!fs.existsSync(cliBundle)) {
|
|
|
40
35
|
process.exit(0);
|
|
41
36
|
}
|
|
42
37
|
|
|
43
|
-
// ──
|
|
38
|
+
// ── Step 1: scaffold — run sailor init if not already done ───────────────────
|
|
44
39
|
|
|
45
|
-
|
|
46
|
-
function ok(msg) { console.log(`${TAG} ✓ ${msg}`); }
|
|
47
|
-
function fail(msg){ console.error(`${TAG} ✗ ${msg}`); }
|
|
48
|
-
function sep(label) {
|
|
49
|
-
console.log(`${TAG} ${"─".repeat(52)}`);
|
|
50
|
-
if (label) console.log(`${TAG} ${label}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ── Utilities ────────────────────────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
function readJson(file) {
|
|
56
|
-
try { return JSON.parse(fs.readFileSync(file, "utf-8")); } catch { return null; }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isAlive(pid) {
|
|
60
|
-
try { process.kill(pid, 0); return true; } catch { return false; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Repeatedly calls fn() until it returns a non-null value or the timeout expires. */
|
|
64
|
-
function poll(fn, { timeout = 12_000, interval = 600 } = {}) {
|
|
65
|
-
return new Promise((resolve) => {
|
|
66
|
-
const deadline = Date.now() + timeout;
|
|
67
|
-
function attempt() {
|
|
68
|
-
Promise.resolve(fn()).then((result) => {
|
|
69
|
-
if (result != null) { resolve(result); return; }
|
|
70
|
-
if (Date.now() >= deadline) { resolve(null); return; }
|
|
71
|
-
setTimeout(attempt, interval);
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
attempt();
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Returns true if any HTTP response arrives from url (any status = server is up). */
|
|
79
|
-
function httpPing(url) {
|
|
80
|
-
return new Promise((resolve) => {
|
|
81
|
-
try {
|
|
82
|
-
const req = http.get(url, { timeout: 2500 }, (res) => {
|
|
83
|
-
res.resume();
|
|
84
|
-
resolve(true);
|
|
85
|
-
});
|
|
86
|
-
req.on("error", () => resolve(false));
|
|
87
|
-
req.on("timeout", () => { req.destroy(); resolve(false); });
|
|
88
|
-
} catch { resolve(false); }
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/** Open the user's default browser (best-effort, non-fatal). */
|
|
93
|
-
function openBrowser(url) {
|
|
94
|
-
try {
|
|
95
|
-
const platform = os.platform();
|
|
96
|
-
if (platform === "darwin") {
|
|
97
|
-
spawn("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
98
|
-
} else if (platform === "win32") {
|
|
99
|
-
// `start ""` avoids treating the URL as a window title
|
|
100
|
-
spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
|
|
101
|
-
} else {
|
|
102
|
-
spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
|
|
103
|
-
}
|
|
104
|
-
} catch { /* best-effort */ }
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ── Shared status object (machine-readable summary at the end) ───────────────
|
|
108
|
-
|
|
109
|
-
const status = {
|
|
110
|
-
init: { state: "skipped", detail: "" },
|
|
111
|
-
station: { state: "not-started", detail: "" },
|
|
112
|
-
ui: { state: "not-started", detail: "" },
|
|
113
|
-
uiUrl: /** @type {string|null} */ (null),
|
|
114
|
-
errors: /** @type {string[]} */ ([]),
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// ── Step 1: init ─────────────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
|
-
async function step1Init() {
|
|
120
|
-
sep("Step 1 — init");
|
|
121
|
-
|
|
122
|
-
const configExists = fs.existsSync(path.join(initCwd, ".sail", "config.json"));
|
|
40
|
+
const configExists = fs.existsSync(path.join(initCwd, ".sail", "config.json"));
|
|
123
41
|
|
|
124
|
-
|
|
125
|
-
ok("Project already initialized — skipping init.");
|
|
126
|
-
status.init = { state: "already-initialized", detail: path.join(initCwd, ".sail", "config.json") };
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
log("Running sailor init…");
|
|
42
|
+
if (!configExists) {
|
|
131
43
|
try {
|
|
132
44
|
execFileSync(process.execPath, [cliBundle, "init"], {
|
|
133
45
|
cwd: initCwd,
|
|
134
46
|
stdio: "inherit",
|
|
135
47
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
} catch (err) {
|
|
140
|
-
fail("sailor init exited with an error. Run `sailor init` manually to complete setup.");
|
|
141
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
142
|
-
status.init = { state: "error", detail: msg };
|
|
143
|
-
status.errors.push(`init: ${msg}`);
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ── Step 2: signing station ───────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
async function step2Station() {
|
|
151
|
-
sep("Step 2 — signing station");
|
|
152
|
-
|
|
153
|
-
const stateFile = path.join(initCwd, ".sail", "runtime", "server.json");
|
|
154
|
-
const existing = readJson(stateFile);
|
|
155
|
-
|
|
156
|
-
// Already running?
|
|
157
|
-
if (existing && existing.pid && isAlive(existing.pid)) {
|
|
158
|
-
ok(`Station already running at ${existing.url} (pid ${existing.pid})`);
|
|
159
|
-
status.station = { state: "already-running", detail: existing.url };
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Remove stale state file if present
|
|
164
|
-
if (existing) {
|
|
165
|
-
try { fs.rmSync(stateFile, { force: true }); } catch { /* ignore */ }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
log("Starting signing station in background…");
|
|
169
|
-
|
|
170
|
-
// station start blocks (keeps the process alive), so we must spawn it detached.
|
|
171
|
-
const child = spawn(process.execPath, [cliBundle, "station", "start"], {
|
|
172
|
-
cwd: initCwd,
|
|
173
|
-
detached: true,
|
|
174
|
-
stdio: "ignore",
|
|
175
|
-
});
|
|
176
|
-
child.unref();
|
|
177
|
-
|
|
178
|
-
// Poll until the state file appears and /config responds
|
|
179
|
-
const state = await poll(async () => {
|
|
180
|
-
const s = readJson(stateFile);
|
|
181
|
-
if (!s || !s.url || !s.pid) return null;
|
|
182
|
-
if (!isAlive(s.pid)) return null;
|
|
183
|
-
const up = await httpPing(`${s.url}/config`);
|
|
184
|
-
return up ? s : null;
|
|
185
|
-
}, { timeout: 15_000 });
|
|
186
|
-
|
|
187
|
-
if (state) {
|
|
188
|
-
ok(`Station running at ${state.url} (pid ${state.pid})`);
|
|
189
|
-
status.station = { state: "running", detail: state.url };
|
|
190
|
-
} else {
|
|
191
|
-
fail("Station did not come up within 15 s.");
|
|
192
|
-
fail("Start it manually with: sailor station start");
|
|
193
|
-
status.station = { state: "error", detail: "timed out" };
|
|
194
|
-
status.errors.push("station: timed out waiting for /config to respond");
|
|
48
|
+
} catch {
|
|
49
|
+
// sailor init failed or was interrupted — user can run it manually
|
|
50
|
+
console.warn(`${TAG} sailor init exited with an error. Run \`sailor init\` manually to complete setup.`);
|
|
195
51
|
}
|
|
196
52
|
}
|
|
197
53
|
|
|
198
|
-
// ──
|
|
199
|
-
|
|
200
|
-
async function step3Ui() {
|
|
201
|
-
sep("Step 3 — dashboard UI");
|
|
202
|
-
|
|
203
|
-
const stateFile = path.join(initCwd, ".sail", "runtime", "ui.json");
|
|
204
|
-
const existing = readJson(stateFile);
|
|
205
|
-
|
|
206
|
-
// Already running?
|
|
207
|
-
if (existing && existing.pid && isAlive(existing.pid)) {
|
|
208
|
-
const url = `http://localhost:${existing.port}`;
|
|
209
|
-
ok(`UI already running at ${url} (pid ${existing.pid})`);
|
|
210
|
-
status.ui = { state: "already-running", detail: url };
|
|
211
|
-
status.uiUrl = url;
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Remove stale state file if present
|
|
216
|
-
if (existing) {
|
|
217
|
-
try { fs.rmSync(stateFile, { force: true }); } catch { /* ignore */ }
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
log("Starting Sailor UI in background…");
|
|
221
|
-
|
|
222
|
-
// uiCommand() spawns the server process itself and returns quickly (~300 ms).
|
|
223
|
-
try {
|
|
224
|
-
execFileSync(process.execPath, [cliBundle, "ui"], {
|
|
225
|
-
cwd: initCwd,
|
|
226
|
-
stdio: "pipe", // suppress duplicate "Sailor UI started" output; we log our own
|
|
227
|
-
});
|
|
228
|
-
} catch (err) {
|
|
229
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
230
|
-
fail(`UI start failed: ${msg}`);
|
|
231
|
-
status.ui = { state: "error", detail: msg };
|
|
232
|
-
status.errors.push(`ui: ${msg}`);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// uiCommand writes the state file before returning; read it immediately.
|
|
237
|
-
const state = readJson(stateFile);
|
|
238
|
-
if (!state || !state.port) {
|
|
239
|
-
fail("UI state file missing after start. Run `sailor ui` manually.");
|
|
240
|
-
status.ui = { state: "error", detail: "state file missing" };
|
|
241
|
-
status.errors.push("ui: .sail/runtime/ui.json not written after start");
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const url = `http://localhost:${state.port}`;
|
|
246
|
-
|
|
247
|
-
// Wait until the server is actually serving (it may need a moment to bind).
|
|
248
|
-
const up = await poll(() => httpPing(url), { timeout: 10_000, interval: 500 });
|
|
249
|
-
|
|
250
|
-
if (up) {
|
|
251
|
-
ok(`UI running at ${url} (pid ${state.pid})`);
|
|
252
|
-
status.ui = { state: "running", detail: url };
|
|
253
|
-
status.uiUrl = url;
|
|
254
|
-
} else {
|
|
255
|
-
fail(`UI process started (pid ${state.pid}) but not responding at ${url}.`);
|
|
256
|
-
fail("Run `sailor ui status` to check.");
|
|
257
|
-
status.ui = { state: "degraded", detail: url };
|
|
258
|
-
status.uiUrl = url; // still try to open the browser — maybe it comes up in time
|
|
259
|
-
status.errors.push(`ui: process alive but HTTP not responding at ${url}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ── Step 4: open browser ──────────────────────────────────────────────────────
|
|
264
|
-
|
|
265
|
-
async function step4Browser() {
|
|
266
|
-
if (!status.uiUrl) return;
|
|
267
|
-
sep("Step 4 — opening browser");
|
|
268
|
-
log(`Opening ${status.uiUrl} …`);
|
|
269
|
-
openBrowser(status.uiUrl);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ── Final summary ─────────────────────────────────────────────────────────────
|
|
273
|
-
|
|
274
|
-
function printSummary() {
|
|
275
|
-
const icon = (s) =>
|
|
276
|
-
({ ok: "✓", "already-initialized": "✓", "already-running": "✓", running: "✓",
|
|
277
|
-
skipped: "–", error: "✗", degraded: "⚠", "not-started": "–" })[s] ?? "?";
|
|
278
|
-
|
|
279
|
-
sep("SAILOR SETUP — STATUS SUMMARY");
|
|
280
|
-
log(` init: ${icon(status.init.state)} ${status.init.state}${status.init.detail ? " (" + status.init.detail + ")" : ""}`);
|
|
281
|
-
log(` station: ${icon(status.station.state)} ${status.station.detail || status.station.state}`);
|
|
282
|
-
log(` ui: ${icon(status.ui.state)} ${status.ui.detail || status.ui.state}`);
|
|
283
|
-
|
|
284
|
-
if (status.errors.length) {
|
|
285
|
-
log("");
|
|
286
|
-
log(" Errors encountered:");
|
|
287
|
-
status.errors.forEach((e) => log(` • ${e}`));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
sep();
|
|
291
|
-
|
|
292
|
-
if (status.uiUrl) {
|
|
293
|
-
log(` → Onboarding: ${status.uiUrl}`);
|
|
294
|
-
log(` Open the URL above to connect your wallet and complete setup.`);
|
|
295
|
-
} else {
|
|
296
|
-
log(` → Run \`sailor ui\` to start the dashboard.`);
|
|
297
|
-
log(` → Run \`sailor station start\` to start the signing daemon.`);
|
|
298
|
-
log(` → Then open the dashboard URL to complete onboarding.`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
sep();
|
|
302
|
-
printAgentInstructions();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function printAgentInstructions() {
|
|
306
|
-
const uiUrl = status.uiUrl ?? "<sailor ui URL — run `sailor ui` to get it>";
|
|
307
|
-
const L = (msg = "") => console.log(`${TAG} ${msg}`);
|
|
308
|
-
|
|
309
|
-
L("╔══════════════════════════════════════════════════════╗");
|
|
310
|
-
L("║ IF YOU ARE AN AI AGENT ║");
|
|
311
|
-
L("╚══════════════════════════════════════════════════════╝");
|
|
312
|
-
L();
|
|
313
|
-
L(" Sailor has been installed and its services are running.");
|
|
314
|
-
L(" Your job now is to guide the USER through onboarding.");
|
|
315
|
-
L();
|
|
316
|
-
L(" ── Step 1: ask the user these questions ────────────────");
|
|
317
|
-
L();
|
|
318
|
-
L(" 1. Which network do you want to deploy your SMA on?");
|
|
319
|
-
L(" (e.g. Base, Arbitrum One, Ethereum mainnet, …)");
|
|
320
|
-
L();
|
|
321
|
-
L(" 2. Do you have an RPC URL for that network?");
|
|
322
|
-
L(" If not, suggest a free provider: Alchemy, Infura, Ankr.");
|
|
323
|
-
L(" Once they have it: write it to .sail/.env.local as RPC_URL=<url>");
|
|
324
|
-
L();
|
|
325
|
-
L(" 3. Do you have a funded wallet (MetaMask or similar)?");
|
|
326
|
-
L(" They will need gas on the chosen network to deploy the SMA.");
|
|
327
|
-
L();
|
|
328
|
-
L(" ── Step 2: send the user to the browser ────────────────");
|
|
329
|
-
L();
|
|
330
|
-
L(` Dashboard: ${uiUrl}`);
|
|
331
|
-
L();
|
|
332
|
-
L(" Tell them to open the URL and complete these 3 browser steps:");
|
|
333
|
-
L(" a. Connect their wallet");
|
|
334
|
-
L(" b. Deploy their SMA (requires gas on the chosen network)");
|
|
335
|
-
L(" c. Create the agent wallet");
|
|
336
|
-
L();
|
|
337
|
-
L(" The signing station is running — browser requests will be");
|
|
338
|
-
L(" routed through it automatically.");
|
|
339
|
-
L();
|
|
340
|
-
L(" ── Step 3: once browser steps are done ─────────────────");
|
|
341
|
-
L();
|
|
342
|
-
L(" Ask the user to describe their strategy (what the agent");
|
|
343
|
-
L(" should do, with what limits). Then:");
|
|
344
|
-
L(" • Build the mandate contract: sailor mandate deploy …");
|
|
345
|
-
L(" • Verify everything is healthy: sailor doctor");
|
|
346
|
-
L(" • Set up automation (local or GitHub Actions)");
|
|
347
|
-
L();
|
|
348
|
-
L(" Run `sailor capabilities` for a full list of what this");
|
|
349
|
-
L(" kernel version supports (no wallet, no gas needed).");
|
|
350
|
-
L();
|
|
351
|
-
L("══════════════════════════════════════════════════════════");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
355
|
-
|
|
356
|
-
(async () => {
|
|
357
|
-
await step1Init();
|
|
358
|
-
|
|
359
|
-
// Start station and UI concurrently — they are independent of each other.
|
|
360
|
-
// Station is discovered by the UI at runtime via .sail/runtime/server.json.
|
|
361
|
-
// We attempt both even if init failed, in case the project was partially set up.
|
|
362
|
-
await Promise.all([step2Station(), step3Ui()]);
|
|
54
|
+
// ── Done — direct the user to their AI assistant ─────────────────────────────
|
|
363
55
|
|
|
364
|
-
|
|
365
|
-
printSummary();
|
|
366
|
-
})();
|
|
56
|
+
console.log("\nYour Sail agent project is ready. Open your AI coding assistant in this folder and say start.");
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Sail Protocol is infrastructure for onchain Separately Managed Accounts run by AI agents. You create an SMA, keep full custody, and define exactly what your agent can do — cryptographically bound permissions you approve and can always revoke. The agent executes within those bounds on every transaction. It cannot exceed them.
|
|
2
|
+
|
|
3
|
+
I'm Sailor, the operator that sets this up. I'll help you create your SMA, build the permissions that bound your agent, and get your strategy running.
|
|
4
|
+
|
|
5
|
+
Here's where we're headed:
|
|
6
|
+
|
|
7
|
+
1. Deploy your SMA and create your agent wallet
|
|
8
|
+
2. Define your strategy
|
|
9
|
+
3. Build, test, and sign your mandate
|
|
10
|
+
4. Run your agent — locally or on a schedule
|
|
11
|
+
5. Extend with notifications and a custom dashboard
|
|
12
|
+
|
|
13
|
+
Ready? Say **start** and I'll open the setup interface in your browser.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Instructions for the assistant
|
|
18
|
+
|
|
19
|
+
Everything below is for you, the assistant. The user sees the welcome above; you follow the flow below.
|
|
20
|
+
|
|
21
|
+
## Voice
|
|
22
|
+
|
|
23
|
+
You are Sailor. Serious, precise, confident. No hype, no emojis, no exclamation marks. Explain *why*, not just *what* — the user is moving real funds. Use user-facing terms (SMA, mandate, permissions, agent wallet, owner). Assume crypto-native; teach the Sail-specific model.
|
|
24
|
+
|
|
25
|
+
Never overstate safety: custody is protected, but a mandate is only as correct as its permission contracts.
|
|
26
|
+
|
|
27
|
+
## Authorization rule
|
|
28
|
+
|
|
29
|
+
During **setup**, always ask before anything that costs gas. Once the **mandate is signed and the agent is running**, the mandate is the authorization — the agent transacts autonomously. Do not ask per-dispatch.
|
|
30
|
+
|
|
31
|
+
## First contact
|
|
32
|
+
|
|
33
|
+
When the user says start (or any first message), present the welcome above in full — definition, stage list, handoff line — before doing anything else. Do not launch the UI yet. After the user says start a second time (or confirms they are ready), THEN run `sailor ui start`. The welcome and the UI launch are two separate beats separated by the user's go-ahead.
|
|
34
|
+
|
|
35
|
+
Determine the user's progress by reading `.sail/` — do not ask; read it.
|
|
36
|
+
|
|
37
|
+
If the user's first message is an npm install command, run it, then present the welcome immediately after it completes — do not wait for another message.
|
|
38
|
+
|
|
39
|
+
## Stage 1 — Deploy your SMA and create your agent wallet
|
|
40
|
+
|
|
41
|
+
In the browser. Run `sailor ui start`, open the printed URL, connect your owner wallet, choose your network, and deploy your SMA. Then create your agent wallet — a separate signing key I use to submit transactions on your behalf. You need gas in both: the owner wallet to deploy and sign the mandate; the agent wallet to submit transactions once the agent is running. The owner key never leaves the browser.
|
|
42
|
+
|
|
43
|
+
## Stage 2 — Define your strategy
|
|
44
|
+
|
|
45
|
+
Tell me what you want your agent to do. I'll ask the right questions, establish the on-chain bounds with you (tokens, amounts, slippage, venues), and set up your RPC endpoint once you've chosen your chain. Blank slate — you define the strategy.
|
|
46
|
+
|
|
47
|
+
For a worked end-to-end example (DCA / Uniswap V3 / Base), consult `examples/dca/` — reference only; not your strategy.
|
|
48
|
+
|
|
49
|
+
## Stage 3 — Build, test, and sign your mandate
|
|
50
|
+
|
|
51
|
+
I'll write the permission contracts that bound your agent, prove in plain English what each one permits and blocks against sample calls, deploy them, and walk you through signing to authorize. Author, verify, sign — one step.
|
|
52
|
+
|
|
53
|
+
Permission contracts live in `mandates/`. The user authors, reviews, and owns them. For examples by protocol and chain, see `examples/permissions/`.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
forge build
|
|
57
|
+
sailor mandate deploy --contract <Name> --attach --sma <SMA>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Registration requires the owner to sign in the browser. If the wrong wallet is connected, the CLI rejects it.
|
|
61
|
+
|
|
62
|
+
## Stage 4 — Run
|
|
63
|
+
|
|
64
|
+
Your agent starts executing within its mandate — locally on a schedule or via GitHub Actions. No per-transaction confirmation. The mandate is the authorization.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
sailor run # local, continuous
|
|
68
|
+
sailor run --once # single tick — confirm it works before automating
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
For GitHub Actions: push repo, add `RPC_URL` and `SAIL_PASSPHRASE` as secrets. The scaffolded workflow at `.github/workflows/agent-tick.yml` runs on a schedule.
|
|
72
|
+
|
|
73
|
+
## Stage 5 — Extend
|
|
74
|
+
|
|
75
|
+
I can set up notifications (Telegram, email, or other) for runs and transactions, and build you a custom dashboard tailored to your strategy — a price chart and portfolio view for a trading agent, health-factor and yield for a lending agent.
|
|
76
|
+
|
|
77
|
+
These are things the coding assistant builds on request — not Sailor features. Raise them once the agent is live; build on request.
|
|
78
|
+
|
|
79
|
+
## Signing (for custom runners)
|
|
80
|
+
|
|
81
|
+
Use `buildDispatchSignature` from `@sail.money/sdk` — it reads the on-chain `DISPATCH_TYPEHASH` and builds the correct typed data. Never hand-roll the EIP-712 struct or hardcode the dispatch model.
|
|
82
|
+
|
|
83
|
+
## What NOT to do
|
|
84
|
+
|
|
85
|
+
- Do not present the welcome and immediately launch the UI — wait for the second "start"
|
|
86
|
+
- Do not describe, mention, or present any code in `src/` or `examples/` as the user's strategy — treat Stage 2 as a blank slate; ask what they want
|
|
87
|
+
- Do not ask a running agent to confirm individual dispatches within its mandate
|
|
88
|
+
- Do not put an owner key in the terminal — owner signing is browser-only
|
|
89
|
+
- Do not hand-roll dispatch EIP-712 signatures — use `buildDispatchSignature`
|
|
90
|
+
- Do not hardcode the dispatch model — detect it on-chain
|
|
91
|
+
- Do not present example permissions as audited or as a supported menu
|
|
92
|
+
- Do not commit `SAIL_PASSPHRASE` or private keys
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Sail Protocol Agent
|
|
2
|
+
|
|
3
|
+
A blank Sail Protocol agent project. Open this folder in your AI coding assistant and say:
|
|
4
|
+
|
|
5
|
+
> start
|
|
6
|
+
|
|
7
|
+
Your assistant will walk you through every step — chain selection, SMA deployment, strategy design, mandate authoring, and automation.
|
|
8
|
+
|
|
9
|
+
## Project layout
|
|
10
|
+
|
|
11
|
+
- `src/agent.ts` — your agent's tick loop (implement your strategy here)
|
|
12
|
+
- `src/mandate.ts` — your strategy parameters and contract addresses
|
|
13
|
+
- `mandates/` — Foundry workspace for your IPermission contracts
|
|
14
|
+
- `examples/dca/` — worked reference: DCA (USDC→WETH) on Base via Uniswap V3
|
|
15
|
+
- `examples/permissions/` — protocol-specific permission examples
|
|
16
|
+
- `.sail/` — local project state (keys, account, activity log)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# DCA reference example
|
|
2
|
+
|
|
3
|
+
**Reference only — not your strategy.** This shows a complete worked implementation of a dollar-cost-averaging agent (USDC → WETH via Uniswap V3 on Base). Consult it for patterns when authoring your own strategy in `src/`.
|
|
4
|
+
|
|
5
|
+
## What it shows
|
|
6
|
+
|
|
7
|
+
| File | Purpose |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `mandate.ts` | Token addresses, swap parameters, and contract addresses for Base mainnet |
|
|
10
|
+
| `agent.ts` | Full tick loop: check balance → approve if needed → quote → swap with slippage protection |
|
|
11
|
+
|
|
12
|
+
## How to use it
|
|
13
|
+
|
|
14
|
+
In Stage 2 (strategy), when you describe what you want, your assistant will adapt these patterns for your protocol, chain, and parameters — not copy them as-is.
|
|
15
|
+
|
|
16
|
+
The on-chain permission contract that authorizes these dispatches must be authored separately (Stage 3). The agent code here is only the intent-building side; the mandate enforces the on-chain bounds.
|