@fangorn-network/westmarch 0.0.20-dev → 0.1.0

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 (386) hide show
  1. package/README.md +35 -5
  2. package/docs/app-to-agent.md +113 -620
  3. package/package.json +18 -18
  4. package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
  5. package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
  6. package/site/dist/assets/index-CkP1WhkV.js +8 -0
  7. package/site/dist/assets/roles-BDPDWrwo.js +1 -0
  8. package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  9. package/site/dist/assets/transformers.web-CUZUFjAK.js +49 -0
  10. package/site/dist/assets/transformers.web-CwR_IYQV.js +40 -0
  11. package/site/dist/index.html +4 -5
  12. package/site/feed.js +16 -1
  13. package/site/no-model.js +4 -0
  14. package/site/worker.js +382 -59
  15. package/src/agent/agent-card.js +51 -37
  16. package/src/agent/mcp.js +173 -500
  17. package/src/agent/tools.js +3 -2
  18. package/src/agent/ui.js +1 -1
  19. package/src/agent/view-tools.js +11 -30
  20. package/src/agent/x402.js +194 -85
  21. package/src/core/embed.js +56 -53
  22. package/src/core/embed.worker.js +1 -1
  23. package/src/core/rank.js +1 -1
  24. package/src/core/roles.js +7 -7
  25. package/src/core/shard.js +12 -8
  26. package/src/discover/apps.js +103 -67
  27. package/src/discover/corpora.js +1 -1
  28. package/src/discover/directory.js +3 -3
  29. package/src/market/cohort.js +1 -1
  30. package/src/market/demand.js +1 -1
  31. package/src/market/reactions.js +24 -18
  32. package/src/publish/devnet.js +146 -0
  33. package/src/publish/graph.js +1 -1
  34. package/src/publish/init.js +74 -0
  35. package/src/publish/lint.js +3 -2
  36. package/src/publish/ship.js +433 -287
  37. package/src/publish/view-cli.js +14 -10
  38. package/src/publish/view.js +485 -255
  39. package/src/taste/steam.js +1 -1
  40. package/src/taste/taste-doc.js +4 -3
  41. package/src/taste/taste.js +3 -3
  42. package/src/taste/vault.js +4 -2
  43. package/template/README.md +48 -0
  44. package/template/app.json +27 -0
  45. package/template/eval/goals.md +7 -0
  46. package/template/eval/golden.jsonl +0 -0
  47. package/template/gitignore +3 -0
  48. package/template/package.json +13 -0
  49. package/template/wrangler.json +12 -0
  50. package/plugin/scripts/fx.mjs +0 -268
  51. package/site/dist/assets/AccountNotFoundScreen-C4njXc69-BOfmiSH1.js +0 -1
  52. package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-D_EsIeJu.js +0 -31
  53. package/site/dist/assets/Address-H8kD1d9O-C85sJ79B.js +0 -17
  54. package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-DdEPFOww.js +0 -6
  55. package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-DhVy9iGN.js +0 -1
  56. package/site/dist/assets/ApiController-CYdiyu9B.js +0 -5
  57. package/site/dist/assets/ArrowsRightLeftIcon-cp60yobq.js +0 -1
  58. package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-8rbYiYYE.js +0 -1
  59. package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-2pkNfCno.js +0 -1
  60. package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-7AQt25_k.js +0 -1
  61. package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BZY30TGs.js +0 -103
  62. package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-BILOOLi_.js +0 -1
  63. package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-CvsbQcFv.js +0 -68
  64. package/site/dist/assets/CaptchaScreen-DbpvkIGx-D8eDcAkc.js +0 -1
  65. package/site/dist/assets/CheckCircleIcon-BM409mGI.js +0 -1
  66. package/site/dist/assets/CheckCircleIcon-NCOQc0Gs.js +0 -1
  67. package/site/dist/assets/Checkbox-D1EDeo41-rHlvsZzP.js +0 -52
  68. package/site/dist/assets/ChevronDownIcon-CW-4MSpE.js +0 -1
  69. package/site/dist/assets/Chip-CZKIKt9K-CutJG010.js +0 -26
  70. package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DLs9zX-Q.js +0 -16
  71. package/site/dist/assets/ConnectEmailForm-B-VRE0MN-N9J6T_YL.js +0 -3
  72. package/site/dist/assets/ConnectLedgerScreen-DWHABGot-h0uYIUi6.js +0 -10
  73. package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-LeuDMN1E.js +0 -1
  74. package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-DaRa59tZ.js +0 -1
  75. package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-crHzh-TL.js +0 -1
  76. package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-BEA4FmhI.js +0 -95
  77. package/site/dist/assets/ConnectWalletView-DqI-PTvX-CMQIad63.js +0 -74
  78. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-BeWLchcf.js +0 -7
  79. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-wu3a3kw4.js +0 -1
  80. package/site/dist/assets/ConnectorUtil-Cq4lWnfe.js +0 -21
  81. package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
  82. package/site/dist/assets/CopyToClipboard-i_OQSBJr-DujUHNzW.js +0 -39
  83. package/site/dist/assets/CopyableText-CQapvaMr-DMIdpDaU.js +0 -28
  84. package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-Csju14vF.js +0 -1
  85. package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-CBOrbNLo.js +0 -22
  86. package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-BL5hzNUI.js +0 -1
  87. package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-fcO51q5H.js +0 -1
  88. package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-B3vY888G.js +0 -187
  89. package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DmdQaFI2.js +0 -8
  90. package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-BoupRyfV.js +0 -1
  91. package/site/dist/assets/EmailInputForm-Cqo1mda8-B2NG3K5V.js +0 -124
  92. package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-D_AVgO4s.js +0 -1
  93. package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-CLsJbPYQ.js +0 -1
  94. package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-C9sAExzE.js +0 -23
  95. package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-DxaWAAKa.js +0 -1
  96. package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-CxrfLr5q.js +0 -1
  97. package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-DHZRGoTN.js +0 -1
  98. package/site/dist/assets/EnrollTotp-Dibew4dp-SA2pvNEc.js +0 -66
  99. package/site/dist/assets/EnvelopeIcon-Dh2vSVj4.js +0 -1
  100. package/site/dist/assets/ErrorBanner-BcpGRt0h-BKOYTyCi.js +0 -21
  101. package/site/dist/assets/ErrorMessage-D8VaAP5m-CrZOnUyg.js +0 -8
  102. package/site/dist/assets/ErrorScreen-DipdTWzE-DcxVjMO4.js +0 -1
  103. package/site/dist/assets/ErrorScreen-DipdTWzE-c8g8En21.js +0 -4
  104. package/site/dist/assets/ExclamationCircleIcon-DUA2wMfO.js +0 -1
  105. package/site/dist/assets/ExclamationTriangleIcon-eZfaqcFq.js +0 -1
  106. package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-s_s5M2CZ.js +0 -75
  107. package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-BHBR4k8H.js +0 -28
  108. package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-BH_sU6EV.js +0 -491
  109. package/site/dist/assets/FingerPrintIcon-Dm6kxmg7.js +0 -1
  110. package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-B8TRV36j.js +0 -1
  111. package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-BQdbJ5Bb.js +0 -1
  112. package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-9rPtyA3d.js +0 -58
  113. package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-GaXigGI9.js +0 -1
  114. package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-UsmtTqv4.js +0 -1
  115. package/site/dist/assets/GlobeAltIcon-vqrVV5ho.js +0 -1
  116. package/site/dist/assets/GooglePay-B53WnudL-BX5_WtCx.js +0 -1
  117. package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-D-I8j7NO.js +0 -4
  118. package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-BY3j50El.js +0 -1
  119. package/site/dist/assets/InfoBanner-Cb3p1z12-DLBl_Ywy.js +0 -22
  120. package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-DLSV_NjP.js +0 -1
  121. package/site/dist/assets/InstallWalletScreen-DTA8dwLv-CxreyXoD.js +0 -1
  122. package/site/dist/assets/JsonTree-BHzNC-ic-DcNcbSoM.js +0 -34
  123. package/site/dist/assets/LabelXs-oqZNqbm_-iafrj25N.js +0 -6
  124. package/site/dist/assets/LandingScreen-CGWUoSUG-DK1EY-6v.js +0 -1
  125. package/site/dist/assets/Layouts-BMRfo5hw-CeCOyCLK.js +0 -77
  126. package/site/dist/assets/Link-BdDilT2T-BEDEdHuS.js +0 -40
  127. package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-ClWoqvEL.js +0 -77
  128. package/site/dist/assets/LinkEmailScreen-dKo9frwc-C3v7glH0.js +0 -1
  129. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-D72Gb0LW.js +0 -117
  130. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-DYIl5Kr2.js +0 -1
  131. package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-C0DuD14j.js +0 -1
  132. package/site/dist/assets/Loader-UAZqxsnw-Cy7hEE2f.js +0 -62
  133. package/site/dist/assets/LoadingSkeleton-BMsgO5PV-CiEyS5PZ.js +0 -17
  134. package/site/dist/assets/LockClosedIcon-BgY_5v_Z.js +0 -1
  135. package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DhUMiJZJ.js +0 -1
  136. package/site/dist/assets/ManualTransferScreen-CHqcuqbs-tXiZDHmN.js +0 -1
  137. package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CaW7xeCC.js +0 -1
  138. package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-CeRqvx8k.js +0 -1
  139. package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-BkN1k4lh.js +0 -1
  140. package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-CXSqLVbE.js +0 -1
  141. package/site/dist/assets/ModalFooter-BCuEK_nz-c-khvG4Y.js +0 -403
  142. package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-BRFN2eXr.js +0 -16
  143. package/site/dist/assets/OAuthStatusScreen-ufbOa-da-CeiGcSia.js +0 -1
  144. package/site/dist/assets/OpenLink-CUpJ1mOr-DfAvJRyK.js +0 -39
  145. package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-CIf5I7wU.js +0 -1
  146. package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-CLoL7QC-.js +0 -6
  147. package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-B9F67IYV.js +0 -20
  148. package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
  149. package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
  150. package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
  151. package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
  152. package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
  153. package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
  154. package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
  155. package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
  156. package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
  157. package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
  158. package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
  159. package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
  160. package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
  161. package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
  162. package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
  163. package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
  164. package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
  165. package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
  166. package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
  167. package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
  168. package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
  169. package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
  170. package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
  171. package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
  172. package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
  173. package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
  174. package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
  175. package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
  176. package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
  177. package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
  178. package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
  179. package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
  180. package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
  181. package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
  182. package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
  183. package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
  184. package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
  185. package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
  186. package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
  187. package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
  188. package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
  189. package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
  190. package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
  191. package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
  192. package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
  193. package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
  194. package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
  195. package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
  196. package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
  197. package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
  198. package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
  199. package/site/dist/assets/PhoneIcon-CvIiPwYQ.js +0 -1
  200. package/site/dist/assets/PinInput-DbZ0b1i1-CDXZBdQx.js +0 -247
  201. package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CtQWBiV2.js +0 -1
  202. package/site/dist/assets/QrCode-DbnZwlkH-h6Bhoe5U.js +0 -22
  203. package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-D-A5P3PX.js +0 -1
  204. package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C8lTSeSr.js +0 -1
  205. package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-P5P3plkH.js +0 -7
  206. package/site/dist/assets/SIWXUtil-TUCxa7aA.js +0 -1
  207. package/site/dist/assets/Screen-D30meWsY-CCeVNuWs.js +0 -175
  208. package/site/dist/assets/ScreenHeader-CHmc4-Lu-DjyrH7IK.js +0 -31
  209. package/site/dist/assets/ScreenLayout-OwaQQcxH-CrQM6kvA.js +0 -1
  210. package/site/dist/assets/SelectSourceAsset-CWFog6gm-D7yTw87h.js +0 -140
  211. package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-f0iDIGmw.js +0 -14
  212. package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-kq1zvA7d.js +0 -11
  213. package/site/dist/assets/ShieldCheckIcon-o6RHYev1.js +0 -1
  214. package/site/dist/assets/SignRequestScreen-BHKv32KR-DDpMYyJ1.js +0 -26
  215. package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-DPwhuzcI.js +0 -1
  216. package/site/dist/assets/StackedContainer-B2vaEl56-eQCM2cmv.js +0 -24
  217. package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-BJ-onCjv.js +0 -20
  218. package/site/dist/assets/Subtitle-CV-2yKE4-zxWw2m1Q.js +0 -13
  219. package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-NWlMgNRX.js +0 -1
  220. package/site/dist/assets/Title-BnzYV3Is-B_JpTatT.js +0 -7
  221. package/site/dist/assets/TodoList-DnyULl18-C8Eyy28X.js +0 -36
  222. package/site/dist/assets/TransactionDetails-e9nesl30-C4MpIHR-.js +0 -109
  223. package/site/dist/assets/TransactionErrorView-BDBNqA_f-p1T9WVMx.js +0 -178
  224. package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-88IQcRnY.js +0 -31
  225. package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-1fDAIxAf.js +0 -6
  226. package/site/dist/assets/TurnstileWrapper-B7sNddys-Jwy1tJtm.js +0 -1
  227. package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-Jj2DQW8D.js +0 -1
  228. package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-DwYIUBqi.js +0 -1
  229. package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D7m70C4b.js +0 -1
  230. package/site/dist/assets/Value-DTgR824E-BRuIGiec.js +0 -27
  231. package/site/dist/assets/WalletCards-DH1rqayz-Bgp9Fsgs.js +0 -1
  232. package/site/dist/assets/WalletIcon-gKf0gTw6.js +0 -1
  233. package/site/dist/assets/WalletInfoCard-sq_2wgFg-DpNSAGte.js +0 -29
  234. package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DG-RuO-F.js +0 -1
  235. package/site/dist/assets/WalletLink-DK5LB5Ha-DHaL350n.js +0 -20
  236. package/site/dist/assets/WalletOverflowButton-bfCnp-9T-CUyIJFzt.js +0 -1
  237. package/site/dist/assets/WarningBanner-ZZqCEtZK-2eU4Qo74.js +0 -21
  238. package/site/dist/assets/__vite-browser-external-CitKSmYk.js +0 -1
  239. package/site/dist/assets/_esm-B26FZg5Y.js +0 -7
  240. package/site/dist/assets/_esm-BdeM6pxJ.js +0 -1
  241. package/site/dist/assets/accounts-D96UKWkC.js +0 -1
  242. package/site/dist/assets/accounts-DiZ-0VmN.js +0 -1
  243. package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
  244. package/site/dist/assets/baseSepolia-DX-qGsik.js +0 -1
  245. package/site/dist/assets/basic-oIwwSyu4.js +0 -1575
  246. package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
  247. package/site/dist/assets/ccip-65sIfSPR.js +0 -1
  248. package/site/dist/assets/ccip-BZvRTL6g.js +0 -7
  249. package/site/dist/assets/ccip-DA_zG341.js +0 -1
  250. package/site/dist/assets/chain-DM3oH6Px.js +0 -8
  251. package/site/dist/assets/check-BPgcwEPP.js +0 -1
  252. package/site/dist/assets/chevron-down-BSsbXkK-.js +0 -1
  253. package/site/dist/assets/circle-alert-DWaeLZNj.js +0 -1
  254. package/site/dist/assets/circle-check-big-Chb1ivw9.js +0 -1
  255. package/site/dist/assets/circle-x-AV9Cet0R.js +0 -1
  256. package/site/dist/assets/client-CZG0ndth.js +0 -2
  257. package/site/dist/assets/client-Dsa7_3V3.js +0 -9
  258. package/site/dist/assets/coinbase-DCPC2L_R-D3aIgpyd.js +0 -3
  259. package/site/dist/assets/copy-Ckr0AkL6.js +0 -1
  260. package/site/dist/assets/core-snws5xwv.js +0 -12
  261. package/site/dist/assets/createLucideIcon-IPW50ckY.js +0 -1
  262. package/site/dist/assets/defineChain-CiuCXUZW.js +0 -1
  263. package/site/dist/assets/dijkstra-Drfl93V4.js +0 -1
  264. package/site/dist/assets/dist-Cm7pNAtL.js +0 -1
  265. package/site/dist/assets/dist-CoS6E-3x.js +0 -1
  266. package/site/dist/assets/dist-Dl07uGq1.js +0 -1
  267. package/site/dist/assets/dist-DnGANz51.js +0 -16
  268. package/site/dist/assets/embed.worker-ugGhEqCV.js +0 -1
  269. package/site/dist/assets/errors-BQ3N-RVt-BLqjTPNR.js +0 -1
  270. package/site/dist/assets/esm-B5cbIQaz.js +0 -37
  271. package/site/dist/assets/esm-ByALO6mA.js +0 -1
  272. package/site/dist/assets/esm-C3DslVJx.js +0 -1
  273. package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
  274. package/site/dist/assets/esm-n2lWo7kz.js +0 -1
  275. package/site/dist/assets/ethers-zTjbodk5-wHhL_BQ1.js +0 -1
  276. package/site/dist/assets/eventemitter3-CL76jPsW.js +0 -1
  277. package/site/dist/assets/external-link-D-uehwM2.js +0 -1
  278. package/site/dist/assets/farcaster-DPlSjvF5-BqTvDXBR.js +0 -1
  279. package/site/dist/assets/features-DQ0UYy6K.js +0 -2
  280. package/site/dist/assets/fingerprint-pattern-D8ytA16Q.js +0 -1
  281. package/site/dist/assets/floating-ui.react-dom-DKWOL9UG.js +0 -1
  282. package/site/dist/assets/floating-ui.react-xRkl9O3u.js +0 -1
  283. package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
  284. package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
  285. package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-Dukf7_kJ.js +0 -1
  286. package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
  287. package/site/dist/assets/getErc20Balance-jXNpFf0N-Bb9WM31M.js +0 -1
  288. package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-KPrdn04K.js +0 -1
  289. package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
  290. package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
  291. package/site/dist/assets/guards-BIW-GiMJ-CHfXo8XI.js +0 -1
  292. package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
  293. package/site/dist/assets/hourglass-CPG1HZmf.js +0 -1
  294. package/site/dist/assets/id-UqEwL2BI-BkKm533U.js +0 -1
  295. package/site/dist/assets/index-1HYFYA-W.js +0 -8
  296. package/site/dist/assets/index-BdEw5wyl-ChSJvPZk.js +0 -1
  297. package/site/dist/assets/index-CWARkn2w-Cezl_uBr.js +0 -61
  298. package/site/dist/assets/index-DvMddJcd-BatvgC-S.js +0 -12
  299. package/site/dist/assets/index-YiCw0Y_x-CX5i3Z87.js +0 -437
  300. package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
  301. package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
  302. package/site/dist/assets/index.es-BmqyoBEM.js +0 -2
  303. package/site/dist/assets/index.es-C1v1or--.js +0 -3
  304. package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-By6r0dVB.js +0 -1
  305. package/site/dist/assets/jsx-runtime-BbDfbRii.js +0 -1
  306. package/site/dist/assets/landmark-CH6LV6F0.js +0 -1
  307. package/site/dist/assets/lib-BsS8z8Lj.js +0 -1
  308. package/site/dist/assets/localBatchGatewayRequest-3kLrX_c7.js +0 -32
  309. package/site/dist/assets/lock-DcskI4Bb.js +0 -1
  310. package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
  311. package/site/dist/assets/mail-Bs0r5yOi.js +0 -1
  312. package/site/dist/assets/modal-context-CwmSeFcv-DruX9J1E.js +0 -1
  313. package/site/dist/assets/native-BKj27wfc.js +0 -1
  314. package/site/dist/assets/ofetch.CWycOUEr-CHp-crOQ.js +0 -1
  315. package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
  316. package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
  317. package/site/dist/assets/parseEther-VjhVo3dd.js +0 -1
  318. package/site/dist/assets/pkce-DXF0yKjP-ChQhE0st.js +0 -1
  319. package/site/dist/assets/preload-helper-uBIymjUX.js +0 -1
  320. package/site/dist/assets/privy-context-81KmsBw2-CjWGzyg7.js +0 -2
  321. package/site/dist/assets/property-DuZ9DLP4.js +0 -3
  322. package/site/dist/assets/react-DCXkXSrv.js +0 -1
  323. package/site/dist/assets/react-DQnMl2Xv.js +0 -1
  324. package/site/dist/assets/react-DrJ0MmMO.js +0 -1
  325. package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
  326. package/site/dist/assets/react-dom-5dW7aKoP.js +0 -1
  327. package/site/dist/assets/reservoir-x-nGuZkT-BMX127i6.js +0 -1
  328. package/site/dist/assets/roles-32M1l9jI.js +0 -1
  329. package/site/dist/assets/rolldown-runtime-BcKkbAw3.js +0 -1
  330. package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
  331. package/site/dist/assets/secp256k1-C4nzcPo-.js +0 -1
  332. package/site/dist/assets/secp256k1-D92aGNnU.js +0 -1
  333. package/site/dist/assets/secp256k1-DmbwyFau.js +0 -1
  334. package/site/dist/assets/secp256k1-_MleGpqL.js +0 -1
  335. package/site/dist/assets/secp256k1-xl2og-wO.js +0 -1
  336. package/site/dist/assets/serializeAuthorizationList-CX8hGL56.js +0 -1
  337. package/site/dist/assets/serializeSignature-DfxkPnu-.js +0 -2
  338. package/site/dist/assets/sha2-BU_QyRTx.js +0 -1
  339. package/site/dist/assets/shared-DRZUmnL5-xqAUARWo.js +0 -193
  340. package/site/dist/assets/shared-FM0rljBt--Avg6q2R.js +0 -21
  341. package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BK_FBkaE.js +0 -1
  342. package/site/dist/assets/simulateTransaction-CfuOrae4-64lWYJAL.js +0 -1
  343. package/site/dist/assets/smartphone-CcEZ-XAy.js +0 -1
  344. package/site/dist/assets/src-BBjkfi-Q.js +0 -1
  345. package/site/dist/assets/storage-Ufl800Ne-D0bZBw-H.js +0 -9
  346. package/site/dist/assets/stringify-BlDm7tuc.js +0 -21
  347. package/site/dist/assets/stripe.esm-CZRDpc-N.js +0 -2
  348. package/site/dist/assets/styles-BsotlekN-CXEB4Ud9.js +0 -9
  349. package/site/dist/assets/styles-Cm9H63WO-Br4QtzUL.js +0 -92
  350. package/site/dist/assets/styles-DVyDvTdj-BTABn2K8.js +0 -74
  351. package/site/dist/assets/styles-hslXy6xG-D-49b1rk.js +0 -164
  352. package/site/dist/assets/telegram-B-JqnkqZ-RBoY3PLx.js +0 -1
  353. package/site/dist/assets/to-ui-error-DWe-qwS3-Cx2bk1kZ.js +0 -4
  354. package/site/dist/assets/toViemAccount-B5iDGq1b-Ba8L7zed.js +0 -2
  355. package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
  356. package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
  357. package/site/dist/assets/transformers.web-CVP3pJOS.js +0 -40
  358. package/site/dist/assets/transformers.web-D8W1u_I2.js +0 -49
  359. package/site/dist/assets/triangle-alert-CWjYH75H.js +0 -1
  360. package/site/dist/assets/twitch-5IOe4sIQ-lDtgk0os.js +0 -1
  361. package/site/dist/assets/types-4CuOD23i.js +0 -1
  362. package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-LUIVFBSd.js +0 -1
  363. package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-CC2pq27z.js +0 -1
  364. package/site/dist/assets/useActiveWallet-B-yZYGc6-D7Ik-LxJ.js +0 -123
  365. package/site/dist/assets/useGetSolPrice-x7gfUIHJ-D1hmQfpA.js +0 -1
  366. package/site/dist/assets/useGetTokenPrice-CaoUkb79-ZnbuJ-vP.js +0 -1
  367. package/site/dist/assets/useI18n-C-5n_noF-BcqC76yj.js +0 -1
  368. package/site/dist/assets/useWalletBalance-CZnP5f_f-Dw2GIJ4g.js +0 -1
  369. package/site/dist/assets/useWallets-X1ndvJ_U-DRjz7uYD.js +0 -1
  370. package/site/dist/assets/utils-BkR7EQjg.js +0 -1
  371. package/site/dist/assets/utils-DJhBNOZA.js +0 -1
  372. package/site/dist/assets/utils-DdckVfQr.js +0 -1
  373. package/site/dist/assets/w3m-modal-BeVrBOIf.js +0 -1174
  374. package/site/dist/assets/wallet-Bn85Sg2W.js +0 -1
  375. package/site/dist/assets/wallet-Bnzeiqed.js +0 -2
  376. package/site/dist/assets/wallet-connect-BvJtUsDt-Y03hhqFN.js +0 -1
  377. package/site/dist/assets/walletconnect-registry-xxG8XLCb-D1SSFvMp.js +0 -1
  378. package/site/dist/assets/walletconnect-v2-3frQFwbg-CrtLXofr.js +0 -12
  379. package/site/dist/assets/wui-ux-by-reown-D34ILNLS.js +0 -1147
  380. package/site/dist/assets/x-rlSp9U7W.js +0 -1
  381. package/src/discover/explore.js +0 -271
  382. package/src/market/settle.js +0 -165
  383. package/src/market/terms.js +0 -200
  384. package/src/publish/cli.js +0 -336
  385. package/src/publish/envelope.js +0 -185
  386. package/src/publish/manifest.js +0 -142
@@ -1,19 +1,18 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
- <script type="module" crossorigin src="./assets/index-1HYFYA-W.js"></script>
4
- <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-BcKkbAw3.js">
5
- <link rel="modulepreload" crossorigin href="./assets/preload-helper-uBIymjUX.js">
3
+ <script type="module" crossorigin src="./assets/index-CkP1WhkV.js"></script>
4
+ <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-COiEfhsA.js">
6
5
  <link rel="modulepreload" crossorigin href="./assets/roles-DKPW79Ca.js">
7
6
 
8
7
  <meta charset="utf-8">
9
8
  <meta name="viewport" content="width=device-width, initial-scale=1">
10
9
  <title>Fangorn app</title>
11
- <style>:root{--bg:#fbfbf8;--fg:#1a1c19;--muted:#6a6f68;--line:#dfe1db;--accent:#2f5d45;--mark:#e6eedb;--hot:#b4432f;--edge:#b7bab2;--font:system-ui, sans-serif;--font-display:var(--font);--radius:3px;--gutter:16px;--measure:760px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#121412;--fg:#e4e6e0;--muted:#959a92;--line:#2a2e29;--accent:#8fbf9f;--mark:#2d3b2a;--hot:#d9674f;--edge:#4b5049;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);font:16px/1.5 var(--font);margin:0}a{color:var(--accent)}button,input{font:inherit;color:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:var(--radius);background:0 0;padding:4px 10px}button:disabled{opacity:.45;cursor:default}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ul{margin:0;padding:0;list-style:none}h2{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:28px 0 8px;font-size:.75rem;font-weight:600}h2.page,.record h2{font:600 1.3rem/1.3 var(--font-display);text-transform:none;letter-spacing:0;color:var(--fg);margin:4px 0 12px}h3{margin:0;font-size:1rem}small,time,.hint,.empty,.about,#status,.resultsbar,.detail{color:var(--muted)}small,time,.hint{font-size:.8rem}.paid{border:1px solid var(--line);border-radius:var(--radius);gap:6px;margin:12px 0;padding:10px 14px;display:grid}.paid h3{margin:0;font-size:1rem}.paid ul{margin:0;padding-left:18px}.detail{margin:0;font-size:.9rem}.forsale{color:var(--accent);font-weight:600}mark{background:var(--mark);color:inherit}.bar{z-index:2;background:var(--bg);border-bottom:1px solid var(--line);position:sticky;top:0}.bar>div,main,footer{max-width:var(--measure);padding-inline:var(--gutter);margin:0 auto}.bar>div{flex-wrap:wrap;align-items:center;gap:12px;padding-block:10px;display:flex}#home{color:var(--fg);font:600 1.1rem var(--font-display);white-space:nowrap;text-decoration:none}#q{flex:240px}#q input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;padding:8px 10px}nav{gap:14px;font-size:.9rem;display:flex}nav>span{gap:inherit;display:flex}#aside:not(:empty){border-left:1px solid var(--line);padding-left:14px}nav a{color:var(--muted);text-decoration:none}nav a.on,nav a:hover{color:var(--fg)}main{padding-block:16px 48px}.list,article{border-top:1px solid var(--line)}.row{border-bottom:1px solid var(--line);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.main{overflow-wrap:anywhere;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.title{color:var(--fg);font-weight:500;text-decoration:none}.title:hover{color:var(--accent)}.resultsbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:.9rem;display:flex}.sort,.chips,.acts,.votes{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.chips{margin:4px 0 16px}.picker{color:var(--muted);flex-direction:column;gap:4px;margin:4px 0 16px;font-size:.85rem;display:inline-flex}.picker select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--edge);border-radius:var(--radius);min-width:min(320px,100%);padding:6px 10px;font-size:1rem}details.about summary{cursor:pointer;color:var(--fg);font-size:1.05rem}details.about p{color:var(--muted);margin:6px 0 0}.chip{padding:2px 10px;font-size:.85rem}.chip.on,.vote[aria-pressed=true]{border-color:var(--accent);background:var(--mark)}.vote{padding:2px 7px;line-height:1.3}.star{color:var(--muted);border:0;padding:4px 6px;font-size:1.25rem;line-height:1}.star[aria-pressed=true]{color:var(--accent);background:0 0}.more,.linkish{color:var(--accent);border:0;padding:8px 0 0;font-size:.9rem}.act{color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);padding:4px 10px;font-size:.88rem;text-decoration:none}article{padding:12px 0 4px}article header{flex-direction:column;margin-bottom:4px;display:flex}article .list{border-top:0}article .row:last-child{border-bottom:0}.record{border-top:0}.record dl{grid-template-columns:max-content 1fr;gap:2px 14px;margin:12px 0;font-size:.88rem;display:grid}.record dt{color:var(--muted);text-transform:capitalize}.record dd{margin:0}.acts{margin:12px 0 4px}.signal{padding:14px 0 6px}.signal h3{font:600 1.15rem/1.3 var(--font-display);margin:4px 0 6px}.signal .meta{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap;align-items:center;gap:4px 10px;font-size:.78rem;display:flex}.stage{color:var(--accent);font-weight:700}.steps{margin:8px 0 0;padding:0;list-style:none}.steps li{border-top:1px solid var(--line);grid-template-columns:7.5em 1fr;gap:10px;padding:5px 0;font-size:.9rem;display:grid}.steps time{color:var(--muted);font-variant-numeric:tabular-nums}.doc iframe{border:1px solid var(--line);background:#fff;width:100%;height:80vh;margin-top:12px}.taste{margin-bottom:20px}.pills{flex-wrap:wrap;gap:6px;margin:6px 0;display:flex}.pills li{border:1px solid var(--line);border-radius:var(--radius);align-items:center;gap:2px;padding:0 0 0 8px;font-size:.85rem;display:inline-flex}.pills li.no{color:var(--muted);text-decoration:line-through}.pills button{color:var(--muted);border:0;padding:0 8px}.knobs{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:12px 0;display:grid}.knob{gap:2px;font-size:.85rem;display:grid}.knob span{justify-content:space-between;font-weight:600;display:flex}.knob input{width:100%;accent-color:var(--accent)}body.wide{--measure:1200px}.mapbar input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;margin-bottom:8px;padding:8px 10px}.mapwrap{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start;gap:16px;display:grid}@media (width<=760px){.mapwrap{grid-template-columns:1fr}}svg.map{width:100%;height:auto;display:block;position:sticky;top:70px}svg.map path{stroke:var(--edge);stroke-width:.8px;cursor:pointer}svg.map path:hover,svg.map path:focus{stroke:var(--accent);stroke-width:2px;outline:none}svg.map path.near{stroke:var(--accent);stroke-width:2px;stroke-dasharray:4 3}svg.map path.on{stroke:var(--fg);stroke-width:2.6px}.h0{fill:var(--bg);background:var(--bg)}.h1{fill:color-mix(in srgb, var(--hot) 18%, var(--bg));background:color-mix(in srgb, var(--hot) 18%, var(--bg))}.h2{fill:color-mix(in srgb, var(--hot) 36%, var(--bg));background:color-mix(in srgb, var(--hot) 36%, var(--bg))}.h3{fill:color-mix(in srgb, var(--hot) 56%, var(--bg));background:color-mix(in srgb, var(--hot) 56%, var(--bg))}.h4{fill:color-mix(in srgb, var(--hot) 78%, var(--bg));background:color-mix(in srgb, var(--hot) 78%, var(--bg))}.h5{fill:var(--hot);background:var(--hot)}.sw{border:1px solid var(--edge);vertical-align:-1px;width:14px;height:12px;display:inline-block}.panel h3{margin:4px 0}.panel .chips{margin:6px 0}.panel .list{margin-top:8px}footer{color:var(--muted);padding-block:0 32px;font-size:.8rem}</style>
10
+ <style>:root{--bg:#fbfbf8;--fg:#1a1c19;--muted:#6a6f68;--line:#dfe1db;--accent:#2f5d45;--mark:#e6eedb;--hot:#b4432f;--edge:#b7bab2;--font:system-ui, sans-serif;--font-display:var(--font);--radius:3px;--gutter:16px;--measure:760px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#121412;--fg:#e4e6e0;--muted:#959a92;--line:#2a2e29;--accent:#8fbf9f;--mark:#2d3b2a;--hot:#d9674f;--edge:#4b5049;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}body{background:var(--bg);color:var(--fg);font:16px/1.5 var(--font);margin:0}a{color:var(--accent)}button,input{font:inherit;color:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:var(--radius);background:0 0;padding:4px 10px}button:disabled{opacity:.45;cursor:default}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}ul{margin:0;padding:0;list-style:none}h2{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:28px 0 8px;font-size:.75rem;font-weight:600}h2.page,.record h2{font:600 1.3rem/1.3 var(--font-display);text-transform:none;letter-spacing:0;color:var(--fg);margin:4px 0 12px}h3{margin:0;font-size:1rem}small,time,.hint,.empty,.about,#status,.resultsbar,.detail{color:var(--muted)}small,time,.hint{font-size:.8rem}.paid{border:1px solid var(--line);border-radius:var(--radius);gap:6px;margin:12px 0;padding:10px 14px;display:grid}.paid h3{margin:0;font-size:1rem}.paid ul{margin:0;padding-left:18px}.detail{margin:0;font-size:.9rem}.forsale{color:var(--accent);font-weight:600}mark{background:var(--mark);color:inherit}.bar{z-index:2;background:var(--bg);border-bottom:1px solid var(--line);position:sticky;top:0}.bar>div,main,footer{max-width:var(--measure);padding-inline:var(--gutter);margin:0 auto}.bar>div{flex-wrap:wrap;align-items:center;gap:12px;padding-block:10px;display:flex}#home{color:var(--fg);font:600 1.1rem var(--font-display);white-space:nowrap;text-decoration:none}#q{flex:240px}#q input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;padding:8px 10px}nav{gap:14px;font-size:.9rem;display:flex}nav>span{gap:inherit;display:flex}#aside:not(:empty){border-left:1px solid var(--line);padding-left:14px}nav a{color:var(--muted);text-decoration:none}nav a.on,nav a:hover{color:var(--fg)}main{padding-block:16px 48px}.list,article{border-top:1px solid var(--line)}.row{border-bottom:1px solid var(--line);align-items:flex-start;gap:10px;padding:10px 0;display:flex}.main{overflow-wrap:anywhere;flex-direction:column;flex:1;gap:2px;min-width:0;display:flex}.title{color:var(--fg);font-weight:500;text-decoration:none}.title:hover{color:var(--accent)}.resultsbar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;font-size:.9rem;display:flex}.sort,.chips,.acts,.votes{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.chips{margin:4px 0 16px}.picker{color:var(--muted);flex-direction:column;gap:4px;margin:4px 0 16px;font-size:.85rem;display:inline-flex}.picker select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--edge);border-radius:var(--radius);min-width:min(320px,100%);padding:6px 10px;font-size:1rem}details.about summary{cursor:pointer;color:var(--fg);font-size:1.05rem}details.about p{color:var(--muted);margin:6px 0 0}.chip{padding:2px 10px;font-size:.85rem}.chip.on,.vote[aria-pressed=true]{border-color:var(--accent);background:var(--mark)}.vote{padding:2px 7px;line-height:1.3}.star{color:var(--muted);border:0;padding:4px 6px;font-size:1.25rem;line-height:1}.star[aria-pressed=true]{color:var(--accent);background:0 0}.more,.linkish{color:var(--accent);border:0;padding:8px 0 0;font-size:.9rem}.act{color:var(--fg);border:1px solid var(--line);border-radius:var(--radius);padding:4px 10px;font-size:.88rem;text-decoration:none}article{padding:12px 0 4px}article header{flex-direction:column;margin-bottom:4px;display:flex}article .list{border-top:0}article .row:last-child{border-bottom:0}.record{border-top:0}.record dl{grid-template-columns:max-content 1fr;gap:2px 14px;margin:12px 0;font-size:.88rem;display:grid}.record dt{color:var(--muted);text-transform:capitalize}.record dd{margin:0}.acts{margin:12px 0 4px}.signal{padding:14px 0 6px}.signal h3{font:600 1.15rem/1.3 var(--font-display);margin:4px 0 6px}.signal .meta{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;flex-wrap:wrap;align-items:center;gap:4px 10px;font-size:.78rem;display:flex}.stage{color:var(--accent);font-weight:700}.steps{margin:8px 0 0;padding:0;list-style:none}.steps li{border-top:1px solid var(--line);grid-template-columns:7.5em 1fr;gap:10px;padding:5px 0;font-size:.9rem;display:grid}.steps time{color:var(--muted);font-variant-numeric:tabular-nums}.doc iframe{border:1px solid var(--line);background:#fff;width:100%;height:80vh;margin-top:12px}.taste{margin-bottom:20px}.pills{flex-wrap:wrap;gap:6px;margin:6px 0;display:flex}.pills li{border:1px solid var(--line);border-radius:var(--radius);align-items:center;gap:2px;padding:0 0 0 8px;font-size:.85rem;display:inline-flex}.pills li.no{color:var(--muted);text-decoration:line-through}.pills button{color:var(--muted);border:0;padding:0 8px}.knobs{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;margin:12px 0;display:grid}.knob{gap:2px;font-size:.85rem;display:grid}.knob span{justify-content:space-between;font-weight:600;display:flex}.knob input{width:100%;accent-color:var(--accent)}body.wide{--measure:1200px}.mapbar input{border:1px solid var(--line);border-radius:var(--radius);background:0 0;width:100%;margin-bottom:8px;padding:8px 10px}.mapwrap{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start;gap:16px;display:grid}@media (width<=760px){.mapwrap{grid-template-columns:1fr}}svg.map{width:100%;height:auto;display:block;position:sticky;top:70px}svg.map path{stroke:var(--edge);stroke-width:.8px;cursor:pointer}svg.map path:hover,svg.map path:focus{stroke:var(--accent);stroke-width:2px;outline:none}svg.map path.near{stroke:var(--accent);stroke-width:2px;stroke-dasharray:4 3}svg.map path.on{stroke:var(--fg);stroke-width:2.6px}.h0{fill:var(--bg);background:var(--bg)}.h1{fill:color-mix(in srgb, var(--hot) 18%, var(--bg));background:color-mix(in srgb, var(--hot) 18%, var(--bg))}.h2{fill:color-mix(in srgb, var(--hot) 36%, var(--bg));background:color-mix(in srgb, var(--hot) 36%, var(--bg))}.h3{fill:color-mix(in srgb, var(--hot) 56%, var(--bg));background:color-mix(in srgb, var(--hot) 56%, var(--bg))}.h4{fill:color-mix(in srgb, var(--hot) 78%, var(--bg));background:color-mix(in srgb, var(--hot) 78%, var(--bg))}.h5{fill:var(--hot);background:var(--hot)}.sw{border:1px solid var(--edge);vertical-align:-1px;width:14px;height:12px;display:inline-block}.panel h3{margin:4px 0}.panel .chips{margin:6px 0}.panel .list{margin-top:8px}footer{color:var(--muted);padding-block:0 32px;font-size:.8rem}.gate .stats{font-size:1.05rem}.gate .stats strong{font:600 1.4rem var(--font-display)}.buy{border-color:var(--accent);background:var(--accent);color:var(--bg);justify-self:start;padding:8px 16px;font-weight:600}.agents{margin-top:28px}.copy{gap:4px;margin:10px 0;display:grid}.copy pre{border:1px solid var(--line);border-radius:var(--radius);margin:0;padding:8px 10px;font-size:.8rem;overflow-x:auto}.copy .chip{justify-self:start}</style>
12
11
  <link rel="stylesheet" href="./theme.css">
13
12
  <header class="bar"><div>
14
13
  <a id="home" href="#/"><span id="name">Fangorn app</span></a>
15
14
  <form id="q" role="search"><input type="search" disabled placeholder="Loading…" aria-label="Search" list="recent" autocomplete="off"><datalist id="recent"></datalist></form>
16
- <nav><span id="primary"><a href="#/saved">Saved<span id="nsaved"></span></a></span><span id="aside"><a href="#/account" id="accountlink" hidden>Account</a></span></nav>
15
+ <nav><span id="primary"><a href="#/saved">Saved<span id="nsaved"></span></a></span><span id="aside"><a href="#/agents">Agents</a></span></nav>
17
16
  </div></header>
18
17
  <main id="view"><p id="status">Loading the data…</p></main>
19
18
  <footer>Searches run in your browser. Your searches and saved records stay on this device unless you export them.
package/site/feed.js CHANGED
@@ -36,6 +36,18 @@ export function occasions(rows, roles, { facet = null, only = null, today = new
36
36
  };
37
37
  }
38
38
 
39
+ /** A field's values to browse by: `[{ value, n, sub }]` in natural order (Part 2 before Part 10),
40
+ * `sub` the subtitle every row with that value shares (a part's title), else null. */
41
+ export function facetValues(rows, roles, field) {
42
+ const m = new Map();
43
+ for (const r of rows) for (const v of values(r[field] ?? "")) {
44
+ const sub = subtitleOf(r, roles) ?? null, g = m.get(v);
45
+ if (!g) m.set(v, { value: v, n: 1, sub });
46
+ else { g.n++; if (g.sub !== sub) g.sub = null; }
47
+ }
48
+ return [...m.values()].sort((a, b) => a.value.localeCompare(b.value, undefined, { numeric: true }));
49
+ }
50
+
39
51
  /** What a row says beyond its title: its text, less the title it opens with. */
40
52
  export function detail(r, roles) {
41
53
  const t = titleOf(r, roles), x = String(r.text ?? "");
@@ -58,5 +70,8 @@ if (typeof process !== "undefined" && process.argv[1]?.endsWith("/feed.js")) {
58
70
  eq(past.map((g) => `${g.place}${g.date}`), ["B2026-02-01", "A2026-01-01"], "past, latest first");
59
71
  eq(past[1].items.map((r) => detail(r, roles)), ["Approved 5-0.", ""], "one item per title, the one that says what happened");
60
72
  eq(occasions(rows, roles, { facet: "city", only: "B", today: "2026-02-15" }).past.length, 1, "narrowed to one place");
61
- console.log("feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place");
73
+ const parts = [{ part: "10", t: "Market Research" }, { part: "2", t: "Definitions" }, { part: "10", t: "Market Research" }, { part: "2", t: "Other" }];
74
+ eq(facetValues(parts, rolesFrom([{ role_map: { subtitle: "t" } }]), "part"), [{ value: "2", n: 2, sub: null }, { value: "10", n: 2, sub: "Market Research" }],
75
+ "natural order, counts, a subtitle only when every row shares it");
76
+ console.log("feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place, facet values");
62
77
  }
@@ -0,0 +1,4 @@
1
+ // What `@huggingface/transformers` resolves to in the app Worker (wrangler `alias`): the Worker
2
+ // embeds with its AI binding, never in-process, and the real package is ~1.5 MB of bundle.
3
+ const none = { from_pretrained() { throw new Error("no in-process model in the Worker; it embeds with its AI binding"); } };
4
+ export const AutoModel = none, AutoTokenizer = none, env = {};
package/site/worker.js CHANGED
@@ -1,38 +1,206 @@
1
- // The app's front door (Cloudflare Pages advanced mode): the static site, plus two things a
2
- // static site cannot do. `westmarch-ship` writes this to site/_worker.js with CONFIG filled in.
1
+ // A Fangorn app on the free tier: one Cloudflare Worker that is the app's indexer, its view,
2
+ // its MCP server and its site. `westmarch-ship` deploys it with the app's CONFIG.
3
3
  //
4
+ // cron the indexer. Follows the update log from where it left off, and runs
5
+ // the embedding lens (view.js, the Worker's AI binding as the encoder)
6
+ // over every member's repo whose head moved, into R2. A repo whose
7
+ // publisher leaves the app, or that the app excludes, loses its domain
8
+ // on the next run.
9
+ // GET /view/… the view, from R2: catalog, manifests, content-named shards
10
+ // POST /mcp the view as MCP tools (describe, search, get, similar, count, browse,
11
+ // threads) over streamable HTTP: no install, no browser
4
12
  // GET /doc?u=<url> a record's source document, served from this origin so the page can
5
13
  // show it inline. Source sites forbid framing and send no CORS, so the
6
14
  // page cannot fetch or frame them itself. Only hosts the app's own
7
- // records link to (CONFIG.docHosts), only PDFs, cached for a day.
8
- // GET /paid/<item id> with CONFIG.paid: 402 + the price; with a valid X-PAYMENT header, the
9
- // facilitator verifies and settles it, then the record is returned
10
- // GET /_paid/* 404 to everyone: the records sit here as static files, and the
11
- // only way to one is through a settled payment
12
- // anything else the static site, with the headers _headers would have set
15
+ // records link to (each domain's `link_hosts`), only PDFs, cached a day.
16
+ // GET /paid/<publisher>/<repo>/<id> with CONFIG.paid: an x402 v2 quote for one record its
17
+ // publisher sells (scheme "fangorn", from the record's signed terms). With a
18
+ // PAYMENT-SIGNATURE header, the facilitator (Barliman) verifies and settles it
19
+ // anything else the static site (the stock page, the card)
13
20
  //
14
- // A record that does not exist is a 404 before any price is quoted: nobody pays for nothing.
15
- // Each record's sha256 is in the app's public (committed) data, so a buyer can check it.
21
+ // The app sells nothing of its own. A publisher seals a record's fields and signs its terms
22
+ // (`fangorn commit --paid`); this worker only quotes them. After paying, the buyer proves it to
23
+ // the relay and asks the record's key server for the key: nothing secret passes through here.
24
+ //
25
+ // Bindings: VIEW (R2), AI (Workers AI), ASSETS (the site), CONFIG (a JSON var: app, name,
26
+ // description, fromBlock, schema, repos, paid, facilitator, deployment), and optionally RPC_URL
27
+ // and IPFS_GATEWAY (vars).
28
+
29
+ import { Fangorn, FangornConfig, SupportedNetworks } from "@fangorn-network/sdk";
30
+ import { createPublicClient, http, parseAbi } from "viem";
31
+ import { generatePrivateKey } from "viem/accounts";
32
+ import { EMBED_MODEL, DOCUMENT, QUERY, matryoshka } from "../src/core/embed.js";
33
+ import { configure, resetShard } from "../src/core/shard.js";
34
+ import { ORIGIN, publishView, r2Store } from "../src/publish/view.js";
35
+ import { viewTools } from "../src/agent/view-tools.js";
36
+
37
+ let CONFIG = { paid: null };
38
+
39
+ // ── the lens's encoder ────────────────────────────────────────────────────────
40
+
41
+ const AI_MODEL = "@cf/google/embeddinggemma-300m";
42
+ /** What a view built here records: the same model as embed.js, another runtime of it. */
43
+ export const WORKER_EMBEDDER = { runtime: "workers-ai", model: EMBED_MODEL, dtype: "workers-ai" };
44
+ // EMBED_URL: an encoder answering as the AI binding does ({text} → {data}), on a devnet, so
45
+ // nothing leaves the machine. Its views say so: {runtime, model, dtype} come back with it.
46
+ const ai = (env) => env.EMBED_URL
47
+ ? async (body) => (await fetch(env.EMBED_URL, { method: "POST", body: JSON.stringify(body) })).json()
48
+ : (body) => env.AI.run(AI_MODEL, body);
49
+ const embedderOf = async (env) => env.EMBED_URL ? (await (await fetch(env.EMBED_URL)).json()) : WORKER_EMBEDDER;
50
+ const encode = (env, prompt) => async (texts) =>
51
+ (await ai(env)({ text: texts.map((t) => prompt + t) })).data.map((v) => matryoshka(v));
52
+
53
+ // ── the indexer ───────────────────────────────────────────────────────────────
54
+
55
+ const STATE = "state.json", LOCK = "index.lock";
56
+ // A live run beats its lock every BEAT_MS; one silent past LOCK_MS is presumed dead (killed for CPU
57
+ // or wall time, which skips `finally`) and its lock taken over. Lock: "<last beat ms> <run id>".
58
+ // ponytail: get-then-put, so two runs starting in the same instant can both take it; R2's onlyIf etag when that shows.
59
+ const LOCK_MS = 2 * 60_000, BEAT_MS = 30_000;
60
+ export async function takeLock(bucket, now = Date.now()) {
61
+ const held = await (await bucket.get(LOCK))?.text();
62
+ if (held && now - Number(held.split(" ")[0]) < LOCK_MS) return null;
63
+ const id = crypto.randomUUID();
64
+ const beat = () => bucket.put(LOCK, `${Date.now()} ${id}`);
65
+ await beat();
66
+ return { id, beat, release: async () => { if ((await (await bucket.get(LOCK))?.text())?.endsWith(id)) await bucket.delete(LOCK); } };
67
+ }
68
+
69
+ // CONFIG.network: the SDK's network name ("baseSepolia"); absent, its default.
70
+ const network = () => SupportedNetworks[CONFIG.network] ?? FangornConfig;
71
+
72
+ function reader(env) {
73
+ // CONFIG.deployment: another deployment's addresses (and fromBlock as a string),
74
+ // for a testnet fork or a local chain. Absent, the network's.
75
+ const d = CONFIG.deployment ?? {};
76
+ const config = { ...network(), ...d, ...(d.fromBlock != null ? { fromBlock: BigInt(d.fromBlock) } : {}),
77
+ ...(env.RPC_URL ? { rpcUrl: env.RPC_URL } : {}) };
78
+ const fangorn = Fangorn.create({ privateKey: generatePrivateKey(), config,
79
+ storage: { signedUrl: env.IPFS_GATEWAY ? { gateway: env.IPFS_GATEWAY } : {} } });
80
+ return { fangorn, chain: createPublicClient({ chain: config.chain, transport: http(config.rpcUrl) }) };
81
+ }
82
+
83
+ /**
84
+ * Every repo at its latest head, kept across runs in R2: each run reads only the updates
85
+ * since the last. publishView narrows it to the repos the app lists.
86
+ */
87
+ export async function presented({ fangorn, chain }, state) {
88
+ const to = await chain.getBlockNumber();
89
+ const from = state.block == null ? BigInt(CONFIG.fromBlock ?? network().fromBlock) : BigInt(state.block) + 1n;
90
+ const registry = fangorn.getRegistry();
91
+ if (from <= to) for (const e of await registry.log({}, from, to))
92
+ state.repos[`${e.publisher.toLowerCase()}/${e.repo}`] = { owner: e.publisher, repo: e.repo, root: e.head };
93
+ state.block = String(to);
94
+ return Object.values(state.repos);
95
+ }
96
+
97
+ export async function index(env, { log = console.log } = {}) {
98
+ const lock = await takeLock(env.VIEW);
99
+ if (!lock) return log("index: a run is in progress; skipped");
100
+ const beating = setInterval(() => lock.beat().catch(() => {}), BEAT_MS);
101
+ try {
102
+ const state = (await (await env.VIEW.get(STATE))?.json()) ?? { block: null, repos: {} };
103
+ const r = reader(env);
104
+ const report = await publishView({
105
+ fangorn: r.fangorn, app: CONFIG.app, schema: CONFIG.schema, repos: CONFIG.repos, store: r2Store(env.VIEW),
106
+ presented: await presented(r, state), embedBatch: encode(env, DOCUMENT), embedder: await embedderOf(env), log,
107
+ });
108
+ await env.VIEW.put(STATE, JSON.stringify(state));
109
+ log(`index: through block ${state.block}, ${Object.keys(report).length} domain(s) changed`);
110
+ return report;
111
+ } finally {
112
+ clearInterval(beating);
113
+ await lock.release();
114
+ }
115
+ }
116
+
117
+ // ── the view, from R2 ─────────────────────────────────────────────────────────
118
+
119
+ const CONTENT = { ".gz": "application/gzip" };
120
+ async function viewFile(env, path) {
121
+ const o = await env.VIEW.get(`view/${path}`);
122
+ if (!o) return new Response("not found", { status: 404, headers: CORS });
123
+ const type = CONTENT[path.slice(path.lastIndexOf("."))] ?? "application/json";
124
+ // Shards are content-named, so a reader's browser keeps one for good: a change or a
125
+ // retraction writes new names. `private`: kept out of shared caches, so a retracted
126
+ // record's shard stops being served the moment the indexer deletes it.
127
+ const cache = path.includes("/shards/") ? "private, max-age=31536000, immutable" : "no-cache";
128
+ return new Response(o.body, { headers: { "content-type": type, "cache-control": cache, ...CORS } });
129
+ }
130
+
131
+ // ── MCP over streamable HTTP ──────────────────────────────────────────────────
132
+ //
133
+ // Stateless: every POST is one JSON-RPC message, answered with one JSON body. The tools are
134
+ // view-tools.js's, reading the view straight from R2, ranking by a query embedded here by the
135
+ // same runtime that embedded the rows. They are rebuilt when the indexer writes a new catalog.
136
+
137
+ let mcp = null;
138
+ async function tools(env, origin) {
139
+ const catalog = await env.VIEW.get("view/cdn/catalog");
140
+ const at = catalog?.uploaded?.getTime() ?? 0;
141
+ if (mcp?.at !== at) {
142
+ resetShard();
143
+ configure({ fetch: (url) => viewFile(env, new URL(url).pathname.replace(/^\/view\//, "")) });
144
+ const qv = encode(env, QUERY);
145
+ mcp = { at, tools: viewTools({ name: CONFIG.name ?? CONFIG.app, desc: CONFIG.description ?? "", views: [`${origin}/view`] },
146
+ { fetchCatalog: (url) => viewFile(env, new URL(url).pathname.replace(/^\/view\//, "")),
147
+ queryVector: async (q) => (await qv([q]).catch(() => [null]))[0] }) };
148
+ // viewTools starts its catalog survey unawaited. workerd cancels a request's I/O when
149
+ // it returns, so a survey begun under tools/list never settles and every later call
150
+ // waits on it forever. Finish it here, in the request that started it.
151
+ await mcp.tools.call("describe").catch(() => {});
152
+ }
153
+ return mcp.tools;
154
+ }
16
155
 
17
- const CONFIG = /*CONFIG*/ { docHosts: [], paid: null } /*END*/;
156
+ export async function mcpCall(env, origin, msg) {
157
+ const ok = (result) => ({ jsonrpc: "2.0", id: msg.id, result });
158
+ switch (msg.method) {
159
+ case "initialize": return ok({
160
+ protocolVersion: msg.params?.protocolVersion ?? "2025-06-18",
161
+ capabilities: { tools: { listChanged: false } },
162
+ serverInfo: { name: CONFIG.name ?? CONFIG.app ?? "fangorn-app", version: "0.1.0" },
163
+ instructions: `${CONFIG.description ?? ""} Call describe first, then search.`.trim(),
164
+ });
165
+ case "ping": return ok({});
166
+ case "tools/list": return ok({ tools: (await tools(env, origin))?.tools ?? [] });
167
+ case "tools/call": {
168
+ const t = await tools(env, origin);
169
+ try { return ok(await t.call(msg.params?.name, msg.params?.arguments ?? {})); }
170
+ catch (e) { return ok({ isError: true, content: [{ type: "text", text: e?.message ?? String(e) }] }); }
171
+ }
172
+ default: return { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `unknown method ${msg.method}` } };
173
+ }
174
+ }
175
+
176
+ async function serveMcp(request, env, url) {
177
+ if (request.method !== "POST") return new Response("POST JSON-RPC here (MCP streamable HTTP)", { status: 405, headers: { allow: "POST", ...CORS } });
178
+ let msg;
179
+ try { msg = await request.json(); } catch { return json({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "not JSON" } }, 400); }
180
+ if (Array.isArray(msg)) return json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "one message per request" } }, 400);
181
+ if (msg.id === undefined) return new Response(null, { status: 202, headers: CORS }); // a notification
182
+ return json(await mcpCall(env, url.origin, msg));
183
+ }
18
184
 
19
- const CORS = { "access-control-allow-origin": "*", "access-control-allow-headers": "X-PAYMENT, content-type", "access-control-expose-headers": "X-PAYMENT-RESPONSE" };
185
+ const CORS = { "access-control-allow-origin": "*", "access-control-allow-headers": "PAYMENT-SIGNATURE, authorization, content-type",
186
+ "access-control-expose-headers": "PAYMENT-REQUIRED, PAYMENT-RESPONSE" };
20
187
  const json = (o, status = 200, headers = {}) =>
21
188
  new Response(JSON.stringify(o), { status, headers: { "content-type": "application/json", ...CORS, ...headers } });
22
189
  const note = (msg, status) => new Response(`<!doctype html><meta charset="utf-8"><body style="font:15px system-ui;padding:24px;color:#555">${msg}</body>`,
23
190
  { status, headers: { "content-type": "text/html; charset=utf-8" } });
24
191
 
25
- async function doc(request, url) {
192
+ async function doc(request, env, url) {
26
193
  let src;
27
194
  try { src = new URL(url.searchParams.get("u") ?? ""); } catch { return note("No document named.", 400); }
28
- if (src.protocol !== "https:" || !CONFIG.docHosts.includes(src.hostname)) return note("Not one of this app's sources.", 403);
195
+ const hosts = new Set(((await (await env.VIEW.get("view/cdn/catalog"))?.json())?.domains ?? []).flatMap((d) => d.link_hosts ?? []));
196
+ if (src.protocol !== "https:" || !hosts.has(src.hostname)) return note("Not one of this app's sources.", 403);
29
197
  const cache = caches.default, key = new Request(url.href);
30
198
  const hit = await cache.match(key);
31
199
  if (hit) return hit;
32
200
  const up = await fetch(src.href, { headers: { "user-agent": "Mozilla/5.0 (compatible; fangorn-app/0.1; +https://fangorn.network)" }, redirect: "follow" });
33
201
  // A redirect may not leave the allowed hosts, and only a PDF is shown: an HTML page from
34
202
  // someone else's site, served from this origin, would run as this app.
35
- if (!CONFIG.docHosts.includes(new URL(up.url).hostname)) return note("The source redirected elsewhere.", 403);
203
+ if (!hosts.has(new URL(up.url).hostname)) return note("The source redirected elsewhere.", 403);
36
204
  if (!up.ok || !(up.headers.get("content-type") ?? "").includes("pdf"))
37
205
  return note(`This document is not a PDF. <a href="${src.href.replace(/"/g, "&quot;")}" target="_blank" rel="noopener">Open it on the source site</a>.`, 415);
38
206
  const res = new Response(up.body, { headers: { "content-type": "application/pdf", "content-disposition": "inline",
@@ -41,65 +209,220 @@ async function doc(request, url) {
41
209
  return res;
42
210
  }
43
211
 
44
- async function fileFor(id) {
45
- const d = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(id)));
46
- return [...d].map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 32);
212
+ // ── paid records ─────────────────────────────────────────────────────────────
213
+ // x402 v2 over HTTP: the 402's PaymentRequired in the PAYMENT-REQUIRED header (base64 JSON) and the
214
+ // body, the buyer's payment in PAYMENT-SIGNATURE, the settlement back in PAYMENT-RESPONSE. Only the
215
+ // payment's `payload` is taken from the buyer: the requirement sent to the facilitator is ours.
216
+
217
+ const b64json = (o) => btoa(JSON.stringify(o));
218
+
219
+ /**
220
+ * A record's quote: `{ requirement, body }`, or null when its publisher doesn't sell it.
221
+ * `read(publisher, repo, id)` → the record's payload, or null. `chain`: chainId, registry, usdc and
222
+ * its EIP-712 name and version. The facilitator's /supported names the referrer the buyer signs in.
223
+ */
224
+ export async function quoteOf({ publisher, repo, id, read, chain, facilitator }) {
225
+ const payload = await read(publisher, repo, id);
226
+ const t = payload?.["@terms"];
227
+ if (!t?.terms || !t.signature) return null;
228
+ const fields = Object.fromEntries(Object.entries(payload).filter(([, v]) => v?.["@type"] === "handle" && v.gadget === "field-v1"));
229
+ const generation = Object.values(fields)[0]?.generation;
230
+ if (generation == null) return null;
231
+ const deposit = await read(publisher, repo, `@deposit/${generation}`);
232
+ if (!deposit) return null;
233
+ const network = `eip155:${chain.chainId}`;
234
+ const kind = (await (await fetch(`${facilitator}/supported`)).json()).kinds?.find((k) => k.scheme === "fangorn" && k.network === network);
235
+ if (!kind?.extra?.referrer) throw new Error(`${facilitator} does not settle scheme "fangorn" on ${network}`);
236
+ return {
237
+ requirement: { scheme: "fangorn", network, amount: String(t.terms.price), asset: chain.usdc, payTo: chain.registry, maxTimeoutSeconds: 300,
238
+ extra: { terms: t.terms, signature: t.signature, referrer: kind.extra.referrer, name: chain.usdcName, version: chain.usdcVersion } },
239
+ body: { record: { publisher, repo, id }, fields, deposit, relay: facilitator },
240
+ };
47
241
  }
48
242
 
49
- async function facilitator(path, payment, requirements) {
50
- const r = await fetch(`${CONFIG.paid.facilitator}/${path}`, {
51
- method: "POST", headers: { "content-type": "application/json" },
52
- body: JSON.stringify({ x402Version: 1, paymentPayload: payment, paymentRequirements: requirements }),
53
- });
54
- const text = await r.text();
55
- try { return JSON.parse(text); } catch { return { error: `facilitator ${path}: HTTP ${r.status} ${text.slice(0, 200)}` }; }
243
+ let chainInfo = null;
244
+ /** The SettlementRegistry and its USDC (address, EIP-712 name and version), read once per isolate. */
245
+ function chainOf(fangorn) {
246
+ return (chainInfo ??= (async () => {
247
+ const c = fangorn.getConfig(), registry = fangorn.getSettlementRegistry().getAddress();
248
+ const pub = createPublicClient({ transport: http(c.rpcUrl) });
249
+ const usdc = await pub.readContract({ address: registry, abi: parseAbi(["function usdc() view returns (address)"]), functionName: "usdc" });
250
+ const str = (fn, fallback) => pub.readContract({ address: usdc, abi: parseAbi([`function ${fn}() view returns (string)`]), functionName: fn }).catch(() => fallback);
251
+ return { chainId: c.chain.id, registry, usdc, usdcName: await str("name", "USDC"), usdcVersion: await str("version", "2") };
252
+ })().catch((e) => { chainInfo = null; throw e; }));
56
253
  }
57
254
 
58
255
  async function sell(request, env, url) {
59
- const P = CONFIG.paid;
60
- const id = decodeURIComponent(url.pathname.slice("/paid/".length));
61
- const record = await env.ASSETS.fetch(new URL(`/_paid/${await fileFor(id)}.json`, url));
62
- // Pages answers an unknown path with index.html and a 200 (its single-page fallback), so
63
- // "ok" is not "exists": only a JSON body is a record. Nobody is quoted a price for nothing.
64
- if (!record.ok || !(record.headers.get("content-type") ?? "").includes("json")) return json({ error: `no paid record for ${id}` }, 404);
65
- const requirements = {
66
- scheme: "exact", network: P.network, maxAmountRequired: P.price, resource: url.href,
67
- description: P.description, mimeType: "application/json", payTo: P.payTo,
68
- maxTimeoutSeconds: 120, asset: P.asset, extra: { name: P.name, version: P.version },
256
+ if (!CONFIG.facilitator) return json({ error: "this app names no facilitator (CONFIG.facilitator)" }, 503);
257
+ const [publisher, repo, ...rest] = url.pathname.slice("/paid/".length).split("/").map(decodeURIComponent);
258
+ const id = rest.join("/");
259
+ if (!/^0x[0-9a-fA-F]{40}$/.test(publisher ?? "") || !repo || !id) return json({ error: "GET /paid/<publisher>/<repo>/<record id>" }, 400);
260
+ const { fangorn } = reader(env);
261
+ const read = async (p, r, i) => (await fangorn.engine.readRecord(p, r, i).catch(() => null))?.payload ?? null;
262
+ let q;
263
+ try { q = await quoteOf({ publisher, repo, id, read, chain: await chainOf(fangorn), facilitator: CONFIG.facilitator }); }
264
+ catch (e) { return json({ error: e.message }, 502); }
265
+ if (!q) return json({ error: "not for sale" }, 404);
266
+
267
+ const resource = { url: url.href, mimeType: "application/json", serviceName: CONFIG.name, description: `${CONFIG.name}: ${id}` };
268
+ const required = (error) => {
269
+ const r = { x402Version: 2, error, resource, accepts: [q.requirement] };
270
+ return json({ ...r, ...q.body }, 402, { "payment-required": b64json(r) });
69
271
  };
70
- const refuse = (error) => json({ x402Version: 1, error, accepts: [requirements] }, 402);
71
- const header = request.headers.get("X-PAYMENT");
72
- if (!header) return refuse("X-PAYMENT header is required");
272
+ const header = request.headers.get("PAYMENT-SIGNATURE");
273
+ if (!header) return required("payment required");
73
274
  let payment;
74
- try { payment = JSON.parse(atob(header)); } catch { return refuse("X-PAYMENT is not base64 JSON"); }
75
- const verified = await facilitator("verify", payment, requirements);
76
- if (!verified.isValid) return refuse(verified.invalidReason ?? verified.error ?? "payment did not verify");
77
- const settled = await facilitator("settle", payment, requirements);
78
- if (!settled.success) return refuse(settled.errorReason ?? settled.error ?? "settlement failed");
79
- return new Response(await record.text(), {
80
- headers: { "content-type": "application/json", "x-payment-response": btoa(JSON.stringify(settled)), ...CORS },
81
- });
275
+ try { payment = JSON.parse(atob(header)); } catch { return required("PAYMENT-SIGNATURE is not base64 JSON"); }
276
+ const paymentPayload = { x402Version: 2, resource, accepted: q.requirement, payload: payment?.payload };
277
+ const call = (path) => fetch(`${CONFIG.facilitator}/${path}`, { method: "POST", headers: { "content-type": "application/json" },
278
+ body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements: q.requirement }) })
279
+ .then((r) => r.json()).catch((e) => ({ error: `facilitator ${path}: ${e.message}` }));
280
+ const verified = await call("verify");
281
+ if (!verified.isValid) return required(verified.invalidReason ?? verified.error ?? "payment did not verify");
282
+ const settled = await call("settle");
283
+ if (!settled.success) return required(settled.errorReason ?? settled.error ?? "settlement failed");
284
+ return json({ ...q.body, settlement: settled }, 200, { "payment-response": b64json(settled) });
82
285
  }
83
286
 
84
- export default {
287
+ const BY_ORIGIN = new Set(["/.well-known/agent-card.json"]);
288
+
289
+ const worker = {
85
290
  async fetch(request, env) {
291
+ CONFIG = env.CONFIG ?? CONFIG;
86
292
  const url = new URL(request.url);
293
+ const p = url.pathname;
87
294
  if (request.method === "OPTIONS") return new Response(null, { status: 204, headers: CORS });
88
- if (url.pathname.startsWith("/_paid/") || url.pathname === "/_worker.js") return new Response("not found", { status: 404 });
89
- if (url.pathname === "/doc") return doc(request, url);
90
- if (url.pathname.startsWith("/paid/")) return CONFIG.paid ? sell(request, env, url) : json({ error: "nothing is sold here" }, 404);
295
+ if (p.startsWith("/view/")) return viewFile(env, p.slice("/view/".length));
296
+ if (p === "/mcp") return serveMcp(request, env, url);
297
+ if (p === "/doc") return doc(request, env, url);
298
+ if (p.startsWith("/paid/")) return CONFIG.paid ? sell(request, env, url) : json({ error: "nothing is sold here" }, 404);
91
299
 
92
- const res = await env.ASSETS.fetch(request);
300
+ let res = await env.ASSETS.fetch(request);
301
+ // A site built before its URL was known (`westmarch build`, under the deploy button) names
302
+ // ORIGIN wherever it means its own: it is wherever it is served from.
303
+ if (res.ok && BY_ORIGIN.has(p)) {
304
+ const body = (await res.text()).replaceAll(ORIGIN, url.origin);
305
+ res = new Response(body, { status: res.status, headers: res.headers });
306
+ res.headers.delete("content-length");
307
+ }
93
308
  const out = new Response(res.body, res);
94
- const p = url.pathname;
95
- if (p === "/.well-known/agent-card.json" || p.startsWith("/view/")) out.headers.set("access-control-allow-origin", "*");
309
+ if (p === "/.well-known/agent-card.json") out.headers.set("access-control-allow-origin", "*");
96
310
  if (p.startsWith("/assets/")) out.headers.set("cache-control", "public, max-age=31536000, immutable");
97
- // Shards are content-named, so a reader's browser keeps one for good: a change or a
98
- // retraction writes new names. `private`: pages.dev's edge cache cannot be purged, so it
99
- // must never hold one, and a retracted record's shard stops being served when the deploy
100
- // drops it. (This worker's header wins over _headers, which Pages skips for worker responses.)
101
- else if (/^\/view\/cdn\/domains\/[^/]+\/shards\//.test(p))
102
- out.headers.set("cache-control", (res.headers.get("content-type") ?? "").includes("html") ? "no-store" : "private, max-age=31536000, immutable");
103
311
  return out;
104
312
  },
313
+ async scheduled(_event, env, ctx) {
314
+ CONFIG = env.CONFIG ?? CONFIG;
315
+ ctx.waitUntil(index(env));
316
+ },
105
317
  };
318
+ export default worker;
319
+
320
+ // ── self-check: `node site/worker.js` — a fake bucket, a fake AI binding, a fake registry ──
321
+ if (typeof process !== "undefined" && import.meta.url === `file://${process.argv[1]}` && process.argv[1]?.endsWith("/worker.js")) {
322
+ const { localFangorn } = await import("../src/publish/view.js");
323
+ const eq = (a, b, m) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${m}: ${JSON.stringify(a)} != ${JSON.stringify(b)}`); };
324
+
325
+ const objs = new Map();
326
+ const object = (k) => { const b = objs.get(k).bytes; return { body: new Blob([b]).stream(), uploaded: objs.get(k).at,
327
+ arrayBuffer: async () => b, text: async () => new TextDecoder().decode(b), json: async () => JSON.parse(new TextDecoder().decode(b)) }; };
328
+ const VIEW = {
329
+ get: async (k) => (objs.has(k) ? object(k) : null),
330
+ head: async (k) => (objs.has(k) ? {} : null),
331
+ put: async (k, b) => { objs.set(k, { bytes: typeof b === "string" ? new TextEncoder().encode(b) : b, at: new Date(Date.now() + objs.size) }); },
332
+ delete: async (ks) => { for (const k of [ks].flat()) objs.delete(k); },
333
+ list: async ({ prefix, delimiter }) => {
334
+ const ks = [...objs.keys()].filter((k) => k.startsWith(prefix)).sort();
335
+ const rest = (k) => k.slice(prefix.length);
336
+ if (!delimiter) return { objects: ks.map((key) => ({ key })), truncated: false };
337
+ return { objects: ks.filter((k) => !rest(k).includes("/")).map((key) => ({ key })), truncated: false,
338
+ delimitedPrefixes: [...new Set(ks.filter((k) => rest(k).includes("/")).map((k) => `${prefix + rest(k).split("/")[0]}/`))] };
339
+ },
340
+ };
341
+ const a = await takeLock(VIEW);
342
+ eq([!!a, await takeLock(VIEW)], [true, null], "a held lock skips the next run");
343
+ objs.set(LOCK, { bytes: new TextEncoder().encode(`${Date.now() - LOCK_MS - 1} dead`), at: new Date() });
344
+ const b = await takeLock(VIEW);
345
+ await a.release();
346
+ eq([!!b, objs.has(LOCK)], [true, true], "a silent lock is taken over, and its dead owner can't release the new one");
347
+ await b.release();
348
+ eq(objs.has(LOCK), false, "the owner releases its own lock");
349
+
350
+ // Words → a 768-d vector, so a query shares dimensions with the rows that share its words.
351
+ const calls = [];
352
+ const AI = { run: async (model, { text }) => { calls.push({ model, text });
353
+ return { data: text.map((t) => { const v = new Array(768).fill(0); for (const w of t.toLowerCase().split(/\W+/)) if (w.length > 3) v[[...w].reduce((h, c) => (h * 31 + c.charCodeAt(0)) % 256, 7)] += 1; return v; }) }; } };
354
+ const env = { VIEW, AI, ASSETS: { fetch: async (r) => new Response(new URL(r.url).pathname.endsWith("agent-card.json") ? `{"url":"${ORIGIN}/"}` : "<!doctype html>site") },
355
+ CONFIG: { app: "shop", name: "Shop", description: "Things for a room.", repos: [] } };
356
+ CONFIG = env.CONFIG;
357
+
358
+ const APP = `0x${"ab".repeat(32)}`, OWNER = `0x${"11".repeat(20)}`, PUB = `0x${"22".repeat(20)}`, TAG = "shop.item.v1";
359
+ const rec = (cid, type, payload) => ({ id: cid, cid, type, payload });
360
+ CONFIG.schema = { types: { [TAG]: { role_map: { identity: "sku", title: "name", text: "about", media: "doc" } } } };
361
+ const chain = {
362
+ [`${PUB}/shop`]: { records: [
363
+ rec("c1", TAG, { sku: "a1", name: "Oak table", about: "a solid oak dining table", doc: "https://docs.example/oak.pdf" }),
364
+ rec("c2", TAG, { sku: "b2", name: "Brass lamp", about: "a brass reading lamp" })] },
365
+ };
366
+ const fangorn = localFangorn(chain);
367
+ await publishView({ fangorn, app: APP, schema: CONFIG.schema, store: r2Store(VIEW), embedBatch: encode(env, DOCUMENT), embedder: WORKER_EMBEDDER, log: () => {} });
368
+ eq([calls[0].model, calls[0].text[0].startsWith(DOCUMENT)], [AI_MODEL, true], "documents go to the AI binding with the document prompt");
369
+
370
+ const call = async (method, params) => (await (await worker.fetch(new Request("https://shop.test/mcp",
371
+ { method: "POST", body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }) }), env)).json());
372
+ eq((await call("initialize", {})).result.serverInfo.name, "Shop", "initialize names the app");
373
+ const names = (await call("tools/list")).result.tools.map((t) => t.name);
374
+ for (const t of ["describe", "search", "get", "similar", "count", "browse"]) if (!names.includes(t)) throw new Error(`missing tool ${t}: ${names}`);
375
+ const described = JSON.parse((await call("tools/call", { name: "describe", arguments: {} })).result.content[0].text);
376
+ eq(described.views.map((v) => v.rows), [2], "describe reads the catalog from R2");
377
+ eq(described.views[0].repos.map((r) => [r.publisher, r.repo, r.rows]), [[PUB, "shop", 2]], "describe names each repo's publisher");
378
+ const found = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "reading lamp", limit: 1 } })).result.content[0].text);
379
+ eq([found.ranked_by, found.hits[0].title, found.hits[0].publisher], ["meaning", "Brass lamp", PUB], "search ranks by meaning, the query embedded by the same runtime; a hit names its publisher");
380
+ const theirs = JSON.parse((await call("tools/call", { name: "search", arguments: { query: "lamp", where: { owner: OWNER } } })).result.content[0].text);
381
+ eq(theirs.hits.length, 0, "search narrows to one publisher with where.owner");
382
+ if (!calls.at(-1).text[0].startsWith(QUERY)) throw new Error("the query goes to the AI binding with the query prompt");
383
+ eq((await (await worker.fetch(new Request("https://shop.test/mcp", { method: "POST", body: '{"jsonrpc":"2.0","method":"notifications/initialized"}' }), env)).status), 202, "a notification is accepted, not answered");
384
+
385
+ const cat = await worker.fetch(new Request("https://shop.test/view/cdn/catalog"), env);
386
+ const card = await worker.fetch(new Request("https://shop.example.workers.dev/.well-known/agent-card.json"), env);
387
+ eq([await card.json(), card.headers.get("access-control-allow-origin")], [{ url: "https://shop.example.workers.dev/" }, "*"], "a card built before its URL was known names the origin it is served from");
388
+ eq([cat.status, cat.headers.get("access-control-allow-origin"), (await cat.json()).domains[0].link_hosts], [200, "*", ["docs.example"]], "the view is served from R2, with the hosts its records link to");
389
+ eq((await worker.fetch(new Request("https://shop.test/doc?u=https://evil.example/x.pdf"), env)).status, 403, "/doc refuses a host no record links to");
390
+ eq((await worker.fetch(new Request("https://shop.test/view/cdn/nope"), env)).status, 404, "a missing file is a 404");
391
+
392
+ // A paid record's quote: its signed terms as the requirement, its handles and deposit beside it.
393
+ const H = (b) => `0x${b.repeat(32)}`;
394
+ const terms = { terms: { publisher: PUB, repo: H("22"), key: H("33"), price: "10000", shareBps: 500, generation: "0", deadline: "2000000000" }, signature: "0x12" };
395
+ const handle = { "@type": "handle", gadget: "field-v1", generation: "0", cid: "bafy" };
396
+ const records = { [`${PUB}/shop/a1`]: { name: "Oak", body: handle, "@terms": terms }, [`${PUB}/shop/@deposit/0`]: { "@type": "deposit", sealed: "0x99" },
397
+ [`${PUB}/shop/free`]: { name: "Free" } };
398
+ const read = async (p, r, i) => records[`${p}/${r}/${i}`] ?? null;
399
+ const chainInfo = { chainId: 84532, registry: `0x${"e0".repeat(20)}`, usdc: `0x${"aa".repeat(20)}`, usdcName: "USDC", usdcVersion: "2" };
400
+ const realFetch = globalThis.fetch;
401
+ globalThis.fetch = async (u) => (u === "https://fac/supported"
402
+ ? new Response(JSON.stringify({ kinds: [{ x402Version: 2, scheme: "fangorn", network: "eip155:84532", extra: { referrer: `0x${"fe".repeat(20)}` } }] }))
403
+ : realFetch(u));
404
+ try {
405
+ const q = await quoteOf({ publisher: PUB, repo: "shop", id: "a1", read, chain: chainInfo, facilitator: "https://fac" });
406
+ eq([q.requirement.scheme, q.requirement.amount, q.requirement.payTo, q.requirement.extra.referrer, q.requirement.extra.terms.key],
407
+ ["fangorn", "10000", chainInfo.registry, `0x${"fe".repeat(20)}`, H("33")], "the requirement is the record's signed terms, paid to the registry, with the facilitator's referrer");
408
+ eq([Object.keys(q.body.fields), q.body.deposit.sealed, q.body.relay], [["body"], "0x99", "https://fac"], "the body carries the sealed fields' handles, the deposit and the relay");
409
+ eq(await quoteOf({ publisher: PUB, repo: "shop", id: "free", read, chain: chainInfo, facilitator: "https://fac" }), null, "a record with no terms is not for sale");
410
+ } finally { globalThis.fetch = realFetch; }
411
+ eq((await worker.fetch(new Request(`https://shop.test/paid/${PUB}/shop/a1`), env)).status, 404, "an app without `paid` sells nothing");
412
+
413
+ // The incremental scan: only updates since the last run.
414
+ const logs = [{ publisher: PUB, repo: "shop", head: "0x01", t: 10n }, { publisher: OWNER, repo: "notes", head: "0x02", t: 12n }];
415
+ const asked = [];
416
+ let block = 20n;
417
+ const r = { chain: { getBlockNumber: async () => block }, fangorn: { getRegistry: () => ({
418
+ log: async (_f, from, to) => { asked.push([from, to]); return logs.filter((l) => l.t >= from && l.t <= to); },
419
+ }) } };
420
+ CONFIG = { ...CONFIG, fromBlock: "5" };
421
+ const state = { block: null, repos: {} };
422
+ eq((await presented(r, state)).map((t) => t.repo).sort(), ["notes", "shop"], "the first run scans from the app's fromBlock");
423
+ logs.push({ publisher: PUB, repo: "shop", head: "0x03", t: 25n });
424
+ block = 30n;
425
+ eq((await presented(r, state)).map((t) => [t.repo, t.root]).sort(), [["notes", "0x02"], ["shop", "0x03"]], "the next reads only what is new");
426
+ eq(asked.map((a) => a.map(String)), [["5", "20"], ["21", "30"]], "each run starts where the last ended");
427
+ console.log("worker.js self-check ok — the lens embeds through the AI binding, /mcp serves the view's tools from R2 with queries embedded by the same runtime, the view and /doc are served from R2, a paid record is quoted from its signed terms, and the indexer scans only new updates");
428
+ }