@dev.sail.money/sailor 0.0.2 → 0.1.0-local

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/AGENTS.md +140 -111
  2. package/LICENSE +21 -21
  3. package/README.md +430 -337
  4. package/docs/PERMISSION_MODEL.md +93 -93
  5. package/examples/permissions/BoundedApproveAndCallBatch.sol +179 -0
  6. package/examples/permissions/BoundedBet_Limitless_Base.sol +97 -96
  7. package/examples/permissions/BoundedBorrow_AaveV3_Arbitrum.sol +94 -94
  8. package/examples/permissions/BoundedPerp_GMXv2_Arbitrum.sol +154 -143
  9. package/examples/permissions/BoundedStake_Venice_Base.sol +85 -0
  10. package/examples/permissions/BoundedSupply_AaveV3_Arbitrum.sol +82 -0
  11. package/examples/permissions/BoundedSwap_UniswapV3_Base.sol +116 -113
  12. package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +150 -144
  13. package/examples/permissions/BoundedTransfer_ERC20_Ethereum.sol +73 -73
  14. package/examples/permissions/BoundedVault_ERC4626_Base.sol +97 -0
  15. package/examples/permissions/README.md +79 -52
  16. package/examples/permissions/SailCalldata.sol +118 -0
  17. package/examples/permissions/foundry.toml +10 -10
  18. package/examples/permissions/interfaces/IBatchPermission.sol +38 -0
  19. package/examples/permissions/interfaces/IPermission.sol +18 -18
  20. package/package.json +45 -39
  21. package/packages/cli/README.md +34 -34
  22. package/packages/cli/dist/index.cjs +4571 -2944
  23. package/packages/cli/dist/server.cjs +1252 -2010
  24. package/packages/sdk/README.md +65 -65
  25. package/packages/sdk/dist/chains.d.ts +12 -0
  26. package/packages/sdk/dist/chains.d.ts.map +1 -0
  27. package/packages/sdk/dist/chains.js +94 -0
  28. package/packages/sdk/dist/chains.js.map +1 -0
  29. package/packages/sdk/dist/deployments.d.ts +14 -7
  30. package/packages/sdk/dist/deployments.d.ts.map +1 -1
  31. package/packages/sdk/dist/deployments.js +132 -141
  32. package/packages/sdk/dist/deployments.js.map +1 -1
  33. package/packages/sdk/dist/index.d.ts +3 -2
  34. package/packages/sdk/dist/index.d.ts.map +1 -1
  35. package/packages/sdk/dist/index.js +3 -2
  36. package/packages/sdk/dist/index.js.map +1 -1
  37. package/packages/sdk/dist/intelligence.d.ts +1 -1
  38. package/packages/sdk/dist/intelligence.js +1 -1
  39. package/packages/sdk/dist/lifi.d.ts +17 -0
  40. package/packages/sdk/dist/lifi.d.ts.map +1 -1
  41. package/packages/sdk/dist/lifi.js +24 -0
  42. package/packages/sdk/dist/lifi.js.map +1 -1
  43. package/packages/sdk/dist/safe.d.ts +83 -0
  44. package/packages/sdk/dist/safe.d.ts.map +1 -1
  45. package/packages/sdk/dist/safe.js +92 -1
  46. package/packages/sdk/dist/safe.js.map +1 -1
  47. package/packages/sdk/dist/templates/ammLiquidity.d.ts +24 -11
  48. package/packages/sdk/dist/templates/ammLiquidity.d.ts.map +1 -1
  49. package/packages/sdk/dist/templates/ammLiquidity.js +39 -31
  50. package/packages/sdk/dist/templates/ammLiquidity.js.map +1 -1
  51. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts +24 -10
  52. package/packages/sdk/dist/templates/approveAndCallBatch.d.ts.map +1 -1
  53. package/packages/sdk/dist/templates/approveAndCallBatch.js +36 -23
  54. package/packages/sdk/dist/templates/approveAndCallBatch.js.map +1 -1
  55. package/packages/sdk/dist/templates/boundedBorrow.d.ts +19 -9
  56. package/packages/sdk/dist/templates/boundedBorrow.d.ts.map +1 -1
  57. package/packages/sdk/dist/templates/boundedBorrow.js +28 -19
  58. package/packages/sdk/dist/templates/boundedBorrow.js.map +1 -1
  59. package/packages/sdk/dist/templates/boundedSwap.d.ts +19 -9
  60. package/packages/sdk/dist/templates/boundedSwap.d.ts.map +1 -1
  61. package/packages/sdk/dist/templates/boundedSwap.js +30 -20
  62. package/packages/sdk/dist/templates/boundedSwap.js.map +1 -1
  63. package/packages/sdk/dist/templates/defiBundle.d.ts +35 -9
  64. package/packages/sdk/dist/templates/defiBundle.d.ts.map +1 -1
  65. package/packages/sdk/dist/templates/defiBundle.js +84 -22
  66. package/packages/sdk/dist/templates/defiBundle.js.map +1 -1
  67. package/packages/sdk/dist/templates/pendle.d.ts +23 -8
  68. package/packages/sdk/dist/templates/pendle.d.ts.map +1 -1
  69. package/packages/sdk/dist/templates/pendle.js +34 -14
  70. package/packages/sdk/dist/templates/pendle.js.map +1 -1
  71. package/packages/sdk/dist/templates/transferTarget.d.ts +11 -3
  72. package/packages/sdk/dist/templates/transferTarget.d.ts.map +1 -1
  73. package/packages/sdk/dist/templates/transferTarget.js +14 -7
  74. package/packages/sdk/dist/templates/transferTarget.js.map +1 -1
  75. package/packages/sdk/dist/types.d.ts +19 -1
  76. package/packages/sdk/dist/types.d.ts.map +1 -1
  77. package/packages/sdk/package.json +80 -52
  78. package/packages/ui/dist/assets/{add-DaJhwIBV.js → add-BxpXfVWe.js} +1 -1
  79. package/packages/ui/dist/assets/{all-wallets-BUxsqWXi.js → all-wallets-BKTn_sWK.js} +1 -1
  80. package/packages/ui/dist/assets/{app-store-DkltwTqE.js → app-store-CfuKbwxR.js} +1 -1
  81. package/packages/ui/dist/assets/{apple-owVOeaIT.js → apple-BKSBbNYg.js} +1 -1
  82. package/packages/ui/dist/assets/{arrow-bottom-D2mmNJve.js → arrow-bottom-D4bG6gZi.js} +1 -1
  83. package/packages/ui/dist/assets/{arrow-bottom-circle-CbNYijx-.js → arrow-bottom-circle-BNTs1p0T.js} +1 -1
  84. package/packages/ui/dist/assets/{arrow-left-DJB61s4C.js → arrow-left-2uee3vYv.js} +1 -1
  85. package/packages/ui/dist/assets/{arrow-right-BBrsQ9R4.js → arrow-right-BktjMV6h.js} +1 -1
  86. package/packages/ui/dist/assets/{arrow-top-Cil6bOc8.js → arrow-top-Izu28fX4.js} +1 -1
  87. package/packages/ui/dist/assets/{bank-CbwEmRo3.js → bank-USBaAyFM.js} +1 -1
  88. package/packages/ui/dist/assets/{basic-CLNfjw3m.js → basic-C_9KjTEH.js} +1 -1
  89. package/packages/ui/dist/assets/{browser-B5TtF4Pb.js → browser-DAEMAKV7.js} +1 -1
  90. package/packages/ui/dist/assets/{card-CO7BVB-C.js → card-DT8yDkKN.js} +1 -1
  91. package/packages/ui/dist/assets/{ccip-2W7K3_J3.js → ccip-CkqfGSxX.js} +1 -1
  92. package/packages/ui/dist/assets/{checkmark-BEtSHq9m.js → checkmark-CsgdEXFj.js} +1 -1
  93. package/packages/ui/dist/assets/{checkmark-bold-D9xGHzPE.js → checkmark-bold-D2gjOQo2.js} +1 -1
  94. package/packages/ui/dist/assets/{chevron-bottom-BDztht6i.js → chevron-bottom-tprFynYV.js} +1 -1
  95. package/packages/ui/dist/assets/{chevron-left-EV4GFNbc.js → chevron-left-D2Zj1gNB.js} +1 -1
  96. package/packages/ui/dist/assets/{chevron-right-B4_bB9oR.js → chevron-right-D1rRuAVe.js} +1 -1
  97. package/packages/ui/dist/assets/{chevron-top-D54xPNzF.js → chevron-top-24dL1mbL.js} +1 -1
  98. package/packages/ui/dist/assets/{chrome-store-DYUpAJJq.js → chrome-store-Vy-5niYX.js} +1 -1
  99. package/packages/ui/dist/assets/{clock-Ca1T1Soz.js → clock-qBjLnVdJ.js} +1 -1
  100. package/packages/ui/dist/assets/{close-BZqWjurK.js → close-DARDwgcu.js} +1 -1
  101. package/packages/ui/dist/assets/{coinPlaceholder-e6fl2XDo.js → coinPlaceholder-BvpIbPlD.js} +1 -1
  102. package/packages/ui/dist/assets/{compass-DCLC7zIh.js → compass-BMTO0ayt.js} +1 -1
  103. package/packages/ui/dist/assets/{copy-Th2AaD-O.js → copy-PaXeRHza.js} +1 -1
  104. package/packages/ui/dist/assets/{core-Ckx_cyuH.js → core-BFnStQd-.js} +3 -3
  105. package/packages/ui/dist/assets/cursor-BDvw-B17.js +3 -0
  106. package/packages/ui/dist/assets/{cursor-transparent-BKHeABKB.js → cursor-transparent-BEMdi-8q.js} +1 -1
  107. package/packages/ui/dist/assets/{desktop-CBjY8t6F.js → desktop-CfuLLThw.js} +1 -1
  108. package/packages/ui/dist/assets/{disconnect-DbSs2cli.js → disconnect-DhwgJMiR.js} +1 -1
  109. package/packages/ui/dist/assets/{discord-ZlLOAUkM.js → discord-po8qoN1s.js} +1 -1
  110. package/packages/ui/dist/assets/{etherscan-CKUrqWYN.js → etherscan-BEsz0_yx.js} +1 -1
  111. package/packages/ui/dist/assets/{events-CiKP71cK.js → events-Bz33Unzu.js} +1 -1
  112. package/packages/ui/dist/assets/{exclamation-triangle-DA1QzFiO.js → exclamation-triangle-7CjTAGOQ.js} +1 -1
  113. package/packages/ui/dist/assets/{extension-BVJkmvpJ.js → extension-CmxjEWEt.js} +1 -1
  114. package/packages/ui/dist/assets/{external-link-D_bsR7B2.js → external-link-CmQ--bNS.js} +1 -1
  115. package/packages/ui/dist/assets/{facebook-CmFmhojx.js → facebook-CIBn9b65.js} +1 -1
  116. package/packages/ui/dist/assets/{fallback-Ofl6uSnB.js → fallback-DATyrQlb.js} +1 -1
  117. package/packages/ui/dist/assets/{farcaster-Co-M3Ss8.js → farcaster-OJ3Jasxg.js} +1 -1
  118. package/packages/ui/dist/assets/{filters-B1WwNaFU.js → filters-D4x09zeL.js} +1 -1
  119. package/packages/ui/dist/assets/{github-CP4fP6gn.js → github-ZlIuMArp.js} +1 -1
  120. package/packages/ui/dist/assets/{google-CsOIXJ6V.js → google-Gwg85sfv.js} +1 -1
  121. package/packages/ui/dist/assets/{help-circle-DiMkomdF.js → help-circle-D1uOWYcX.js} +1 -1
  122. package/packages/ui/dist/assets/{id-lmscL5LX.js → id-C0-5UdYk.js} +1 -1
  123. package/packages/ui/dist/assets/{image-B-ubJrY5.js → image-D_DUsv8-.js} +1 -1
  124. package/packages/ui/dist/assets/{index-CZR1Qjhs.js → index-BCzex_R6.js} +1 -1
  125. package/packages/ui/dist/assets/index-BUhrHLpY.js +1775 -0
  126. package/packages/ui/dist/assets/index-Cq02kQmy.css +1 -0
  127. package/packages/ui/dist/assets/{index-BaukYv-x.js → index-CrYzBWfD.js} +1 -1
  128. package/packages/ui/dist/assets/{index-CF0KMmke.js → index-DdbJhIdl.js} +3 -3
  129. package/packages/ui/dist/assets/{index-DVgfCzCo.js → index-DiojfeVM.js} +1 -1
  130. package/packages/ui/dist/assets/{index-Dbh5V1Z0.js → index-izd7vu_r.js} +1 -1
  131. package/packages/ui/dist/assets/{index.es-C78cE5SI.js → index.es-DdkHhQAj.js} +4 -4
  132. package/packages/ui/dist/assets/{info-Cqg57EVo.js → info-CiRd_kEG.js} +1 -1
  133. package/packages/ui/dist/assets/{info-circle-DkeSWNKV.js → info-circle-ypxjqarK.js} +1 -1
  134. package/packages/ui/dist/assets/{lightbulb-DNlO4qKh.js → lightbulb-B-pxLxd8.js} +1 -1
  135. package/packages/ui/dist/assets/{mail-kVQ8Jb9Y.js → mail-BYmicuVZ.js} +1 -1
  136. package/packages/ui/dist/assets/{metamask-sdk-CBalSvz7.js → metamask-sdk-Ccl6DG7Q.js} +1 -1
  137. package/packages/ui/dist/assets/{mobile-BEteuhF7.js → mobile-CtP5PqVT.js} +1 -1
  138. package/packages/ui/dist/assets/{more-DBWmXQli.js → more-6C2733we.js} +1 -1
  139. package/packages/ui/dist/assets/{network-placeholder-Dg1uUHiL.js → network-placeholder-CdhxMzqd.js} +1 -1
  140. package/packages/ui/dist/assets/{nftPlaceholder-i3AHSiD9.js → nftPlaceholder-DVmTWEAY.js} +1 -1
  141. package/packages/ui/dist/assets/{off-BtMm0fi2.js → off-DNYLughs.js} +1 -1
  142. package/packages/ui/dist/assets/{parseSignature-Cb5FlWWg.js → parseSignature-Dq2B5Bu3.js} +1 -1
  143. package/packages/ui/dist/assets/{play-store-iKKkXa6a.js → play-store-D7Qut5ta.js} +1 -1
  144. package/packages/ui/dist/assets/{plus-CA5NaRtb.js → plus-kqMyjt3q.js} +1 -1
  145. package/packages/ui/dist/assets/{qr-code-D2kiqR7h.js → qr-code-DiUCWRbz.js} +1 -1
  146. package/packages/ui/dist/assets/{recycle-horizontal-Dcme7R03.js → recycle-horizontal-Boe3XiS-.js} +1 -1
  147. package/packages/ui/dist/assets/{refresh-Dega3sDp.js → refresh-CrBgBQYO.js} +1 -1
  148. package/packages/ui/dist/assets/{reown-logo-xNkksyWJ.js → reown-logo-CFZCCHSx.js} +1 -1
  149. package/packages/ui/dist/assets/{search-HYl7NO8x.js → search-ChTDrghU.js} +1 -1
  150. package/packages/ui/dist/assets/{secp256k1-Cxd6_SiH.js → secp256k1-DAV5Q_FR.js} +1 -1
  151. package/packages/ui/dist/assets/{send-CJU8CUAo.js → send-DLFbBFe1.js} +1 -1
  152. package/packages/ui/dist/assets/{swapHorizontal-IMUKiUre.js → swapHorizontal-BEs3emfG.js} +1 -1
  153. package/packages/ui/dist/assets/{swapHorizontalBold-CNYnNJ9-.js → swapHorizontalBold-CC-Hfa7W.js} +1 -1
  154. package/packages/ui/dist/assets/{swapHorizontalMedium-B9VxEYsT.js → swapHorizontalMedium-BmR0H8DC.js} +1 -1
  155. package/packages/ui/dist/assets/{swapHorizontalRoundedBold-Dz33l_Jh.js → swapHorizontalRoundedBold-BdP5NGIH.js} +1 -1
  156. package/packages/ui/dist/assets/{swapVertical-CHUmjVJ0.js → swapVertical-CPrGEJPY.js} +1 -1
  157. package/packages/ui/dist/assets/{telegram-kl9S2mbU.js → telegram-CxNoZ80Q.js} +1 -1
  158. package/packages/ui/dist/assets/{three-dots-U5lhA1Am.js → three-dots-BRa6SBpL.js} +1 -1
  159. package/packages/ui/dist/assets/{twitch-KTEUWXEp.js → twitch-BC338bG5.js} +1 -1
  160. package/packages/ui/dist/assets/{twitterIcon-BHiq8mRg.js → twitterIcon-BGZmt2i9.js} +1 -1
  161. package/packages/ui/dist/assets/{verify-CfN-BXNd.js → verify-CEstW0zw.js} +1 -1
  162. package/packages/ui/dist/assets/{verify-filled-DwZccetj.js → verify-filled-OkZb0weU.js} +1 -1
  163. package/packages/ui/dist/assets/{w3m-modal-CS-PFqPE.js → w3m-modal-pS09ECwE.js} +1 -1
  164. package/packages/ui/dist/assets/{wallet-DVlGkhOY.js → wallet-BXVKCgC9.js} +1 -1
  165. package/packages/ui/dist/assets/{wallet-placeholder-CvR_iEWX.js → wallet-placeholder-C_kNhB1c.js} +1 -1
  166. package/packages/ui/dist/assets/{walletconnect-8pZBDvVI.js → walletconnect-CRKIuUHH.js} +1 -1
  167. package/packages/ui/dist/assets/{warning-circle-ylLEE0Yp.js → warning-circle-DB2NnwlJ.js} +1 -1
  168. package/packages/ui/dist/assets/{x-C_TBsTMj.js → x-DT4RmwL5.js} +1 -1
  169. package/packages/ui/dist/index.html +14 -14
  170. package/scripts/check-docs.mjs +262 -262
  171. package/scripts/check-init.mjs +108 -109
  172. package/scripts/postinstall.js +81 -366
  173. package/templates/custom-mandate/.sail/contracts/interfaces/IPermission.sol +18 -18
  174. package/templates/custom-mandate/README.md +116 -85
  175. package/templates/custom-mandate/foundry.toml +8 -8
  176. package/templates/custom-mandate/mandates/BoundedCallPermission.sol +41 -35
  177. package/templates/custom-mandate/mandates/README.md +16 -16
  178. package/templates/custom-mandate/mandates/SailCalldata.sol +118 -0
  179. package/templates/{dca-rebalancer → default}/.cursor/rules +25 -25
  180. package/templates/default/.env.example +20 -0
  181. package/templates/{dca-rebalancer → default}/.github/workflows/agent-tick.yml +33 -32
  182. package/templates/{dca-rebalancer → default}/.sail/README.md +13 -13
  183. package/templates/{dca-rebalancer → default}/.sail/config.json +10 -10
  184. package/templates/default/AGENTS.md +171 -0
  185. package/templates/{dca-rebalancer → default}/CLAUDE.md +2 -2
  186. package/templates/default/README.md +16 -0
  187. package/templates/{dca-rebalancer → default}/_gitignore +13 -13
  188. package/templates/{dca-rebalancer → default}/docs/PERMISSION_MODEL.md +93 -93
  189. package/templates/default/examples/dca/README.md +16 -0
  190. package/templates/default/examples/dca/agent.ts +174 -0
  191. package/templates/{dca-rebalancer/src → default/examples/dca}/mandate.ts +45 -67
  192. package/templates/{dca-rebalancer → default}/package.json +17 -17
  193. package/templates/default/src/agent.ts +37 -0
  194. package/templates/default/src/config.ts +24 -0
  195. package/templates/default/src/mandate.ts +22 -0
  196. package/templates/default/tsconfig.json +17 -0
  197. package/templates/{dca-rebalancer → default}/ui/README.md +3 -3
  198. package/templates/lifi-permissions/LifiBoundedApprovePermissionCloneable.sol +84 -84
  199. package/templates/lifi-permissions/LifiDiamondSwapPermissionCloneable.sol +97 -97
  200. package/templates/lifi-permissions/README.md +53 -53
  201. package/packages/ui/dist/assets/cursor-DV7rOqbJ.js +0 -3
  202. package/packages/ui/dist/assets/index-CKxgNxS9.css +0 -1
  203. package/packages/ui/dist/assets/index-Q2Yai4Fe.js +0 -2103
  204. package/templates/dca-rebalancer/.env.example +0 -6
  205. package/templates/dca-rebalancer/AGENTS.md +0 -246
  206. package/templates/dca-rebalancer/AGENT_PLAYBOOK.md +0 -110
  207. package/templates/dca-rebalancer/README.md +0 -16
  208. package/templates/dca-rebalancer/src/agent.ts +0 -253
  209. package/templates/dca-rebalancer/src/config.ts +0 -27
  210. package/templates/dca-rebalancer/tsconfig.json +0 -8
  211. /package/templates/{dca-rebalancer → default}/.sail/.gitkeep +0 -0
@@ -1,4 +1,4 @@
1
- import{F as l}from"./core-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const o=l`<svg fill="none" viewBox="0 0 96 67">
1
+ import{F as l}from"./core-BFnStQd-.js";import"./index-BUhrHLpY.js";import"./events-Bz33Unzu.js";import"./index.es-DdkHhQAj.js";import"./fallback-DATyrQlb.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-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const a=r`<svg fill="none" viewBox="0 0 20 20">
1
+ import{F as r}from"./core-BFnStQd-.js";import"./index-BUhrHLpY.js";import"./events-Bz33Unzu.js";import"./index.es-DdkHhQAj.js";import"./fallback-DATyrQlb.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-Ckx_cyuH.js";import"./index-Q2Yai4Fe.js";import"./events-CiKP71cK.js";import"./index.es-C78cE5SI.js";import"./fallback-Ofl6uSnB.js";const a=l`<svg fill="none" viewBox="0 0 41 40">
1
+ import{F as l}from"./core-BFnStQd-.js";import"./index-BUhrHLpY.js";import"./events-Bz33Unzu.js";import"./index.es-DdkHhQAj.js";import"./fallback-DATyrQlb.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
@@ -1,14 +1,14 @@
1
- <!DOCTYPE html>
2
- <html lang="en" style="color-scheme: dark">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <meta name="theme-color" content="#040b16" />
7
- <title>Sail - Unlocking Personalized Money</title>
8
- <script type="module" crossorigin src="/assets/index-Q2Yai4Fe.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-CKxgNxS9.css">
10
- </head>
11
- <body>
12
- <div id="root"></div>
13
- </body>
14
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en" style="color-scheme: dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#040b16" />
7
+ <title>Sail - Unlocking Personalized Money</title>
8
+ <script type="module" crossorigin src="/assets/index-BUhrHLpY.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Cq02kQmy.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -1,262 +1,262 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Doc-drift gate.
4
- *
5
- * The agent-facing docs (CLAUDE.md, AGENTS.md, AGENT_PLAYBOOK.md, the scaffolded
6
- * template, package READMEs) tell an LLM agent which `sailor` commands and which
7
- * `client.<ns>.<method>(...)` SDK calls to use. If a doc names a command or method
8
- * that no longer exists, the agent confidently does the wrong thing. This script
9
- * is the regression net: it derives the *real* surface from source and fails if a
10
- * doc references something that isn't there.
11
- *
12
- * - CLI commands ← parsed from packages/cli/src/index.ts (commander tree)
13
- * - SDK methods ← parsed from packages/sdk/src/client.ts (namespace classes)
14
- *
15
- * Only `sailor …` is validated, never the SailFramework `sail …` binary that some
16
- * docs reference. Only references inside `inline code` or ```fenced``` blocks are
17
- * checked, so prose ("the sailor CLI") never trips it.
18
- *
19
- * Run: `node scripts/check-docs.mjs` (or `pnpm docs:check`). Exit 1 on any miss.
20
- */
21
-
22
- import { readFileSync, readdirSync, statSync } from "node:fs";
23
- import { dirname, join, relative } from "node:path";
24
- import { fileURLToPath } from "node:url";
25
-
26
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
27
- const rel = (p) => relative(ROOT, p);
28
-
29
- // ── 1. Derive the real CLI command surface ──────────────────────────────────
30
-
31
- /**
32
- * Parse the commander tree from index.ts into:
33
- * leaves — Set of full top-level invocations ("init", "doctor", "dispatch preview")
34
- * groups — Map<groupName, Set<subcommand>> (e.g. "mandate" → {prepare, sign, …})
35
- */
36
- function parseCliSurface() {
37
- const src = readFileSync(join(ROOT, "packages/cli/src/index.ts"), "utf-8");
38
- const leaves = new Set();
39
- const groups = new Map();
40
- const varToGroup = new Map(); // commander variable name → group command name
41
-
42
- // `const ui = program.command("ui")` — a group declared on a variable.
43
- for (const m of src.matchAll(/const\s+(\w+)\s*=\s*program\s*\.command\(\s*"([^"]+)"/g)) {
44
- const groupName = m[2].split(/\s+/)[0];
45
- varToGroup.set(m[1], groupName);
46
- if (!groups.has(groupName)) groups.set(groupName, new Set());
47
- }
48
-
49
- // `program.command("init [dir]")` — a top-level leaf (not assigned to a var).
50
- for (const m of src.matchAll(/program\s*\.command\(\s*"([^"]+)"/g)) {
51
- const name = m[1].split(/\s+/)[0];
52
- // Multi-word leaf names (e.g. the "dispatch preview" stub) keep their full form too.
53
- const full = m[1]
54
- .replace(/\s*\[.*$/, "")
55
- .replace(/\s*<.*$/, "")
56
- .trim();
57
- if (!groups.has(name)) {
58
- leaves.add(name);
59
- if (full.includes(" ")) leaves.add(full);
60
- }
61
- }
62
-
63
- // `stub("setup", …)` / `stub("dispatch preview", …)` — registered top-level commands.
64
- for (const m of src.matchAll(/\bstub\(\s*"([^"]+)"/g)) {
65
- leaves.add(m[1]);
66
- leaves.add(m[1].split(/\s+/)[0]);
67
- }
68
-
69
- // `ui.command("start")` / `mandate.command("prepare")` — subcommands of a group.
70
- for (const m of src.matchAll(/(\w+)\s*\.command\(\s*"([^"]+)"/g)) {
71
- const groupName = varToGroup.get(m[1]);
72
- if (!groupName) continue; // not a known group variable (e.g. program.command handled above)
73
- const sub = m[2].split(/\s+/)[0];
74
- groups.get(groupName).add(sub);
75
- }
76
-
77
- return { leaves, groups };
78
- }
79
-
80
- // ── 2. Derive the real SDK surface ───────────────────────────────────────────
81
-
82
- /**
83
- * Parse client.ts into:
84
- * namespaces — Map<propertyName, Set<methodName>> (account → {create, get, …})
85
- * clientMethods — Set of direct SailorClient methods (capabilities, withSigner, …)
86
- */
87
- function parseSdkSurface() {
88
- const src = readFileSync(join(ROOT, "packages/sdk/src/client.ts"), "utf-8");
89
- const lines = src.split("\n");
90
-
91
- // Map namespace *class* → set of method names, by slicing each class body.
92
- const classMethods = new Map();
93
- let current = null;
94
- let depth = 0;
95
- const reserved = new Set([
96
- "if",
97
- "for",
98
- "while",
99
- "switch",
100
- "catch",
101
- "return",
102
- "super",
103
- "constructor",
104
- "function",
105
- "await",
106
- "typeof",
107
- "new",
108
- ]);
109
- for (const line of lines) {
110
- const classMatch = line.match(/^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
111
- if (classMatch) {
112
- current = classMatch[1];
113
- classMethods.set(current, new Set());
114
- depth = 0;
115
- }
116
- if (current) {
117
- // Method declarations live at class-body depth 1.
118
- const methodMatch = line.match(
119
- /^\s{2}(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(?:get\s+)?(\w+)\s*[(<]/,
120
- );
121
- if (methodMatch && !reserved.has(methodMatch[1])) {
122
- classMethods.get(current).add(methodMatch[1]);
123
- }
124
- depth += (line.match(/{/g)?.length ?? 0) - (line.match(/}/g)?.length ?? 0);
125
- if (depth <= 0 && line.includes("}")) current = null;
126
- }
127
- }
128
-
129
- // Map runtime property name → class: `this.account = new AccountNamespace(`
130
- const namespaces = new Map();
131
- for (const m of src.matchAll(/this\.(\w+)\s*=\s*new\s+(\w+)\s*\(/g)) {
132
- const [, prop, cls] = m;
133
- if (classMethods.has(cls)) namespaces.set(prop, classMethods.get(cls));
134
- }
135
- // `this.dispatch = dispatch;` — assigned from a local built earlier in the ctor.
136
- for (const m of src.matchAll(/this\.(\w+)\s*=\s*(\w+);/g)) {
137
- const [, prop, localVar] = m;
138
- if (namespaces.has(prop)) continue;
139
- // Resolve the local: `const dispatch = new DispatchNamespace(`
140
- const localDecl = new RegExp(`(?:const|let)\\s+${localVar}\\s*=\\s*new\\s+(\\w+)\\s*\\(`);
141
- const lm = src.match(localDecl);
142
- if (lm && classMethods.has(lm[1])) namespaces.set(prop, classMethods.get(lm[1]));
143
- }
144
-
145
- // Direct methods on the SailorClient class itself.
146
- const clientMethods = classMethods.get("SailorClient") ?? new Set();
147
-
148
- return { namespaces, clientMethods };
149
- }
150
-
151
- // ── 3. Collect doc files + extract code references ───────────────────────────
152
-
153
- const DOC_GLOBS = [
154
- "CLAUDE.md",
155
- "AGENTS.md",
156
- "AGENT_PLAYBOOK.md",
157
- "README.md",
158
- "docs",
159
- "templates",
160
- "packages/cli/README.md",
161
- "packages/sdk/README.md",
162
- "packages/chains/README.md",
163
- "packages/create-app/README.md",
164
- ];
165
-
166
- function collectDocs() {
167
- const files = [];
168
- const walk = (p) => {
169
- const st = statSync(p, { throwIfNoEntry: false });
170
- if (!st) return;
171
- if (st.isDirectory()) {
172
- for (const e of readdirSync(p)) {
173
- if (e === "node_modules" || e === "dist" || e.startsWith(".git")) continue;
174
- walk(join(p, e));
175
- }
176
- } else if (p.endsWith(".md")) {
177
- files.push(p);
178
- }
179
- };
180
- for (const g of DOC_GLOBS) walk(join(ROOT, g));
181
- return [...new Set(files)];
182
- }
183
-
184
- /** Pull the text of every `inline` and ```fenced``` code region from markdown. */
185
- function codeRegions(md) {
186
- const regions = [];
187
- // Fenced blocks first, then strip them so inline matching doesn't double-count.
188
- const rest = md.replace(/```[\s\S]*?```/g, (block) => {
189
- regions.push(block.replace(/```/g, ""));
190
- return "\n";
191
- });
192
- for (const m of rest.matchAll(/`([^`\n]+)`/g)) regions.push(m[1]);
193
- return regions;
194
- }
195
-
196
- // ── 4. Validate ──────────────────────────────────────────────────────────────
197
-
198
- function main() {
199
- const cli = parseCliSurface();
200
- const sdk = parseSdkSurface();
201
- const docs = collectDocs();
202
- const errors = [];
203
-
204
- for (const file of docs) {
205
- const md = readFileSync(file, "utf-8");
206
- for (const code of codeRegions(md)) {
207
- for (const lineRaw of code.split("\n")) {
208
- const line = lineRaw.trim();
209
-
210
- // ── CLI: `sailor <word1> [<word2>]` ──────────────────────────────────
211
- for (const m of line.matchAll(/\bsailor\s+([a-z][\w-]*)(?:\s+([a-z][\w-]*))?/g)) {
212
- const [, w1, w2] = m;
213
- const two = w2 ? `${w1} ${w2}` : null;
214
- if (cli.leaves.has(w1) || (two && cli.leaves.has(two))) continue; // top-level leaf
215
- if (cli.groups.has(w1)) {
216
- // Group: if a subcommand word follows (and isn't a flag), it must exist.
217
- if (!w2 || w2.startsWith("-")) continue; // bare group invocation, or a flag
218
- if (cli.groups.get(w1).has(w2)) continue;
219
- errors.push(
220
- `${rel(file)}: \`sailor ${w1} ${w2}\` — "${w2}" is not a subcommand of "${w1}" (have: ${[...cli.groups.get(w1)].join(", ")})`,
221
- );
222
- continue;
223
- }
224
- errors.push(`${rel(file)}: \`sailor ${w1}\` — unknown command`);
225
- }
226
-
227
- // ── SDK: `client.<ns>[.<method>]` ────────────────────────────────────
228
- for (const m of line.matchAll(/\bclient\.(\w+)(?:\.(\w+))?/g)) {
229
- const [, ns, method] = m;
230
- if (sdk.namespaces.has(ns)) {
231
- if (!method) continue;
232
- if (sdk.namespaces.get(ns).has(method)) continue;
233
- errors.push(
234
- `${rel(file)}: \`client.${ns}.${method}\` — "${method}" is not a method of the "${ns}" namespace (have: ${[...sdk.namespaces.get(ns)].join(", ")})`,
235
- );
236
- continue;
237
- }
238
- if (sdk.clientMethods.has(ns)) continue; // direct client method, e.g. capabilities/withSigner
239
- errors.push(`${rel(file)}: \`client.${ns}\` — unknown SDK namespace or method`);
240
- }
241
- }
242
- }
243
- }
244
-
245
- // ── Report ───────────────────────────────────────────────────────────────
246
- const cliCount = cli.leaves.size + [...cli.groups.values()].reduce((n, s) => n + s.size, 0);
247
- const sdkCount =
248
- [...sdk.namespaces.values()].reduce((n, s) => n + s.size, 0) + sdk.clientMethods.size;
249
- console.log(
250
- `Doc-drift check: ${docs.length} docs vs ${cliCount} CLI commands, ${sdkCount} SDK methods`,
251
- );
252
-
253
- if (errors.length > 0) {
254
- console.error(`\n✗ ${errors.length} stale reference(s):\n`);
255
- for (const e of [...new Set(errors)].sort()) console.error(` ${e}`);
256
- console.error("\nFix the doc, or update the CLI/SDK so the referenced surface exists.");
257
- process.exit(1);
258
- }
259
- console.log("✓ Every sailor command and client.* method referenced in docs exists.");
260
- }
261
-
262
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Doc-drift gate.
4
+ *
5
+ * The agent-facing docs (CLAUDE.md, AGENTS.md, AGENT_PLAYBOOK.md, the scaffolded
6
+ * template, package READMEs) tell an LLM agent which `sailor` commands and which
7
+ * `client.<ns>.<method>(...)` SDK calls to use. If a doc names a command or method
8
+ * that no longer exists, the agent confidently does the wrong thing. This script
9
+ * is the regression net: it derives the *real* surface from source and fails if a
10
+ * doc references something that isn't there.
11
+ *
12
+ * - CLI commands ← parsed from packages/cli/src/index.ts (commander tree)
13
+ * - SDK methods ← parsed from packages/sdk/src/client.ts (namespace classes)
14
+ *
15
+ * Only `sailor …` is validated, never the SailFramework `sail …` binary that some
16
+ * docs reference. Only references inside `inline code` or ```fenced``` blocks are
17
+ * checked, so prose ("the sailor CLI") never trips it.
18
+ *
19
+ * Run: `node scripts/check-docs.mjs` (or `pnpm docs:check`). Exit 1 on any miss.
20
+ */
21
+
22
+ import { readFileSync, readdirSync, statSync } from "node:fs";
23
+ import { dirname, join, relative } from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+
26
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
27
+ const rel = (p) => relative(ROOT, p);
28
+
29
+ // ── 1. Derive the real CLI command surface ──────────────────────────────────
30
+
31
+ /**
32
+ * Parse the commander tree from index.ts into:
33
+ * leaves — Set of full top-level invocations ("init", "doctor", "dispatch preview")
34
+ * groups — Map<groupName, Set<subcommand>> (e.g. "mandate" → {prepare, sign, …})
35
+ */
36
+ function parseCliSurface() {
37
+ const src = readFileSync(join(ROOT, "packages/cli/src/index.ts"), "utf-8");
38
+ const leaves = new Set();
39
+ const groups = new Map();
40
+ const varToGroup = new Map(); // commander variable name → group command name
41
+
42
+ // `const ui = program.command("ui")` — a group declared on a variable.
43
+ for (const m of src.matchAll(/const\s+(\w+)\s*=\s*program\s*\.command\(\s*"([^"]+)"/g)) {
44
+ const groupName = m[2].split(/\s+/)[0];
45
+ varToGroup.set(m[1], groupName);
46
+ if (!groups.has(groupName)) groups.set(groupName, new Set());
47
+ }
48
+
49
+ // `program.command("init [dir]")` — a top-level leaf (not assigned to a var).
50
+ for (const m of src.matchAll(/program\s*\.command\(\s*"([^"]+)"/g)) {
51
+ const name = m[1].split(/\s+/)[0];
52
+ // Multi-word leaf names (e.g. the "dispatch preview" stub) keep their full form too.
53
+ const full = m[1]
54
+ .replace(/\s*\[.*$/, "")
55
+ .replace(/\s*<.*$/, "")
56
+ .trim();
57
+ if (!groups.has(name)) {
58
+ leaves.add(name);
59
+ if (full.includes(" ")) leaves.add(full);
60
+ }
61
+ }
62
+
63
+ // `stub("setup", …)` / `stub("dispatch preview", …)` — registered top-level commands.
64
+ for (const m of src.matchAll(/\bstub\(\s*"([^"]+)"/g)) {
65
+ leaves.add(m[1]);
66
+ leaves.add(m[1].split(/\s+/)[0]);
67
+ }
68
+
69
+ // `ui.command("start")` / `mandate.command("prepare")` — subcommands of a group.
70
+ for (const m of src.matchAll(/(\w+)\s*\.command\(\s*"([^"]+)"/g)) {
71
+ const groupName = varToGroup.get(m[1]);
72
+ if (!groupName) continue; // not a known group variable (e.g. program.command handled above)
73
+ const sub = m[2].split(/\s+/)[0];
74
+ groups.get(groupName).add(sub);
75
+ }
76
+
77
+ return { leaves, groups };
78
+ }
79
+
80
+ // ── 2. Derive the real SDK surface ───────────────────────────────────────────
81
+
82
+ /**
83
+ * Parse client.ts into:
84
+ * namespaces — Map<propertyName, Set<methodName>> (account → {create, get, …})
85
+ * clientMethods — Set of direct SailorClient methods (capabilities, withSigner, …)
86
+ */
87
+ function parseSdkSurface() {
88
+ const src = readFileSync(join(ROOT, "packages/sdk/src/client.ts"), "utf-8");
89
+ const lines = src.split("\n");
90
+
91
+ // Map namespace *class* → set of method names, by slicing each class body.
92
+ const classMethods = new Map();
93
+ let current = null;
94
+ let depth = 0;
95
+ const reserved = new Set([
96
+ "if",
97
+ "for",
98
+ "while",
99
+ "switch",
100
+ "catch",
101
+ "return",
102
+ "super",
103
+ "constructor",
104
+ "function",
105
+ "await",
106
+ "typeof",
107
+ "new",
108
+ ]);
109
+ for (const line of lines) {
110
+ const classMatch = line.match(/^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
111
+ if (classMatch) {
112
+ current = classMatch[1];
113
+ classMethods.set(current, new Set());
114
+ depth = 0;
115
+ }
116
+ if (current) {
117
+ // Method declarations live at class-body depth 1.
118
+ const methodMatch = line.match(
119
+ /^\s{2}(?:public\s+|private\s+|protected\s+)?(?:async\s+)?(?:get\s+)?(\w+)\s*[(<]/,
120
+ );
121
+ if (methodMatch && !reserved.has(methodMatch[1])) {
122
+ classMethods.get(current).add(methodMatch[1]);
123
+ }
124
+ depth += (line.match(/{/g)?.length ?? 0) - (line.match(/}/g)?.length ?? 0);
125
+ if (depth <= 0 && line.includes("}")) current = null;
126
+ }
127
+ }
128
+
129
+ // Map runtime property name → class: `this.account = new AccountNamespace(`
130
+ const namespaces = new Map();
131
+ for (const m of src.matchAll(/this\.(\w+)\s*=\s*new\s+(\w+)\s*\(/g)) {
132
+ const [, prop, cls] = m;
133
+ if (classMethods.has(cls)) namespaces.set(prop, classMethods.get(cls));
134
+ }
135
+ // `this.dispatch = dispatch;` — assigned from a local built earlier in the ctor.
136
+ for (const m of src.matchAll(/this\.(\w+)\s*=\s*(\w+);/g)) {
137
+ const [, prop, localVar] = m;
138
+ if (namespaces.has(prop)) continue;
139
+ // Resolve the local: `const dispatch = new DispatchNamespace(`
140
+ const localDecl = new RegExp(`(?:const|let)\\s+${localVar}\\s*=\\s*new\\s+(\\w+)\\s*\\(`);
141
+ const lm = src.match(localDecl);
142
+ if (lm && classMethods.has(lm[1])) namespaces.set(prop, classMethods.get(lm[1]));
143
+ }
144
+
145
+ // Direct methods on the SailorClient class itself.
146
+ const clientMethods = classMethods.get("SailorClient") ?? new Set();
147
+
148
+ return { namespaces, clientMethods };
149
+ }
150
+
151
+ // ── 3. Collect doc files + extract code references ───────────────────────────
152
+
153
+ const DOC_GLOBS = [
154
+ "CLAUDE.md",
155
+ "AGENTS.md",
156
+ "AGENT_PLAYBOOK.md",
157
+ "README.md",
158
+ "docs",
159
+ "templates",
160
+ "packages/cli/README.md",
161
+ "packages/sdk/README.md",
162
+ "packages/chains/README.md",
163
+ "packages/create-app/README.md",
164
+ ];
165
+
166
+ function collectDocs() {
167
+ const files = [];
168
+ const walk = (p) => {
169
+ const st = statSync(p, { throwIfNoEntry: false });
170
+ if (!st) return;
171
+ if (st.isDirectory()) {
172
+ for (const e of readdirSync(p)) {
173
+ if (e === "node_modules" || e === "dist" || e.startsWith(".git")) continue;
174
+ walk(join(p, e));
175
+ }
176
+ } else if (p.endsWith(".md")) {
177
+ files.push(p);
178
+ }
179
+ };
180
+ for (const g of DOC_GLOBS) walk(join(ROOT, g));
181
+ return [...new Set(files)];
182
+ }
183
+
184
+ /** Pull the text of every `inline` and ```fenced``` code region from markdown. */
185
+ function codeRegions(md) {
186
+ const regions = [];
187
+ // Fenced blocks first, then strip them so inline matching doesn't double-count.
188
+ const rest = md.replace(/```[\s\S]*?```/g, (block) => {
189
+ regions.push(block.replace(/```/g, ""));
190
+ return "\n";
191
+ });
192
+ for (const m of rest.matchAll(/`([^`\n]+)`/g)) regions.push(m[1]);
193
+ return regions;
194
+ }
195
+
196
+ // ── 4. Validate ──────────────────────────────────────────────────────────────
197
+
198
+ function main() {
199
+ const cli = parseCliSurface();
200
+ const sdk = parseSdkSurface();
201
+ const docs = collectDocs();
202
+ const errors = [];
203
+
204
+ for (const file of docs) {
205
+ const md = readFileSync(file, "utf-8");
206
+ for (const code of codeRegions(md)) {
207
+ for (const lineRaw of code.split("\n")) {
208
+ const line = lineRaw.trim();
209
+
210
+ // ── CLI: `sailor <word1> [<word2>]` ──────────────────────────────────
211
+ for (const m of line.matchAll(/\bsailor\s+([a-z][\w-]*)(?:\s+([a-z][\w-]*))?/g)) {
212
+ const [, w1, w2] = m;
213
+ const two = w2 ? `${w1} ${w2}` : null;
214
+ if (cli.leaves.has(w1) || (two && cli.leaves.has(two))) continue; // top-level leaf
215
+ if (cli.groups.has(w1)) {
216
+ // Group: if a subcommand word follows (and isn't a flag), it must exist.
217
+ if (!w2 || w2.startsWith("-")) continue; // bare group invocation, or a flag
218
+ if (cli.groups.get(w1).has(w2)) continue;
219
+ errors.push(
220
+ `${rel(file)}: \`sailor ${w1} ${w2}\` — "${w2}" is not a subcommand of "${w1}" (have: ${[...cli.groups.get(w1)].join(", ")})`,
221
+ );
222
+ continue;
223
+ }
224
+ errors.push(`${rel(file)}: \`sailor ${w1}\` — unknown command`);
225
+ }
226
+
227
+ // ── SDK: `client.<ns>[.<method>]` ────────────────────────────────────
228
+ for (const m of line.matchAll(/\bclient\.(\w+)(?:\.(\w+))?/g)) {
229
+ const [, ns, method] = m;
230
+ if (sdk.namespaces.has(ns)) {
231
+ if (!method) continue;
232
+ if (sdk.namespaces.get(ns).has(method)) continue;
233
+ errors.push(
234
+ `${rel(file)}: \`client.${ns}.${method}\` — "${method}" is not a method of the "${ns}" namespace (have: ${[...sdk.namespaces.get(ns)].join(", ")})`,
235
+ );
236
+ continue;
237
+ }
238
+ if (sdk.clientMethods.has(ns)) continue; // direct client method, e.g. capabilities/withSigner
239
+ errors.push(`${rel(file)}: \`client.${ns}\` — unknown SDK namespace or method`);
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ // ── Report ───────────────────────────────────────────────────────────────
246
+ const cliCount = cli.leaves.size + [...cli.groups.values()].reduce((n, s) => n + s.size, 0);
247
+ const sdkCount =
248
+ [...sdk.namespaces.values()].reduce((n, s) => n + s.size, 0) + sdk.clientMethods.size;
249
+ console.log(
250
+ `Doc-drift check: ${docs.length} docs vs ${cliCount} CLI commands, ${sdkCount} SDK methods`,
251
+ );
252
+
253
+ if (errors.length > 0) {
254
+ console.error(`\n✗ ${errors.length} stale reference(s):\n`);
255
+ for (const e of [...new Set(errors)].sort()) console.error(` ${e}`);
256
+ console.error("\nFix the doc, or update the CLI/SDK so the referenced surface exists.");
257
+ process.exit(1);
258
+ }
259
+ console.log("✓ Every sailor command and client.* method referenced in docs exists.");
260
+ }
261
+
262
+ main();