@fangorn-network/westmarch 0.0.19-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.
- package/README.md +35 -5
- package/docs/app-to-agent.md +113 -620
- package/package.json +18 -18
- package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
- package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
- package/site/dist/assets/index-CkP1WhkV.js +8 -0
- package/site/dist/assets/roles-BDPDWrwo.js +1 -0
- package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/site/dist/assets/transformers.web-CUZUFjAK.js +49 -0
- package/site/dist/assets/transformers.web-CwR_IYQV.js +40 -0
- package/site/dist/index.html +6 -7
- package/site/feed.js +16 -1
- package/site/no-model.js +4 -0
- package/site/worker.js +382 -59
- package/src/agent/agent-card.js +51 -37
- package/src/agent/mcp.js +173 -500
- package/src/agent/tools.js +3 -2
- package/src/agent/ui.js +1 -1
- package/src/agent/view-tools.js +11 -30
- package/src/agent/x402.js +194 -85
- package/src/core/embed.js +56 -53
- package/src/core/embed.worker.js +1 -1
- package/src/core/rank.js +1 -1
- package/src/core/roles.js +7 -7
- package/src/core/shard.js +12 -8
- package/src/discover/apps.js +103 -67
- package/src/discover/corpora.js +1 -1
- package/src/discover/directory.js +3 -3
- package/src/market/cohort.js +1 -1
- package/src/market/demand.js +1 -1
- package/src/market/reactions.js +24 -18
- package/src/publish/devnet.js +146 -0
- package/src/publish/graph.js +1 -1
- package/src/publish/init.js +74 -0
- package/src/publish/lint.js +3 -2
- package/src/publish/ship.js +433 -287
- package/src/publish/view-cli.js +14 -10
- package/src/publish/view.js +485 -255
- package/src/taste/steam.js +1 -1
- package/src/taste/taste-doc.js +4 -3
- package/src/taste/taste.js +3 -3
- package/src/taste/vault.js +4 -2
- package/template/README.md +48 -0
- package/template/app.json +27 -0
- package/template/eval/goals.md +7 -0
- package/template/eval/golden.jsonl +0 -0
- package/template/gitignore +3 -0
- package/template/package.json +13 -0
- package/template/wrangler.json +12 -0
- package/plugin/scripts/fx.mjs +0 -268
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-BOfmiSH1.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-B6uvYyqa.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-DcHv7A8C.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-v2wz-N-s.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-DhVy9iGN.js +0 -1
- package/site/dist/assets/ApiController-BbhXeqTV.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-cp60yobq.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-B0eT1zE6.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-DL5PH-v_.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-n1BsRPqc.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BVl2BJDY.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-BD-OZUQb.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-CaYrzRje.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-BQJ7inx1.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BM409mGI.js +0 -1
- package/site/dist/assets/CheckCircleIcon-NCOQc0Gs.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-rHlvsZzP.js +0 -52
- package/site/dist/assets/ChevronDownIcon-CW-4MSpE.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-CutJG010.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DDaMC7cl.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-DVSf4tFJ.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-h0uYIUi6.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-DP19hJq5.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-DHvijlTx.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-ewmXYPTQ.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-DUYdmVtr.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-RobGErVW.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Bbiq3hY6.js +0 -1
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-DPkOGNWl.js +0 -7
- package/site/dist/assets/ConnectorUtil-BO1L6VvV.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-DujUHNzW.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-DMIdpDaU.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-DbGy4QyA.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-B3WqJZBh.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-CmUDoS0C.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-fcO51q5H.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-ClAb_j1B.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-CN1P_KY9.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-BtDZmuEN.js +0 -1
- package/site/dist/assets/EmailInputForm-Cqo1mda8-B2NG3K5V.js +0 -124
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-D3pExuaW.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-CLsJbPYQ.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-DI6bu-oi.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-BQ9Ygr0P.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-CxrfLr5q.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-DHZRGoTN.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-7gVtJ1Jt.js +0 -66
- package/site/dist/assets/EnvelopeIcon-Dh2vSVj4.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BKOYTyCi.js +0 -21
- package/site/dist/assets/ErrorMessage-D8VaAP5m-CrZOnUyg.js +0 -8
- package/site/dist/assets/ErrorScreen-DipdTWzE-BT8TtRTx.js +0 -4
- package/site/dist/assets/ErrorScreen-DipdTWzE-DAam41Xl.js +0 -1
- package/site/dist/assets/ExclamationCircleIcon-DUA2wMfO.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-eZfaqcFq.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-BPCBi37Q.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-XCmWpnUd.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-DJRcUh2N.js +0 -491
- package/site/dist/assets/FingerPrintIcon-Dm6kxmg7.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-BfNVSTb1.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-BQdbJ5Bb.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-BfvMcHb6.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-VBfbahnA.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-CMuBYf64.js +0 -1
- package/site/dist/assets/GlobeAltIcon-vqrVV5ho.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BX5_WtCx.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-D-I8j7NO.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-BY3j50El.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-DLBl_Ywy.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-DLSV_NjP.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-CxreyXoD.js +0 -1
- package/site/dist/assets/JsonTree-BHzNC-ic-DcNcbSoM.js +0 -34
- package/site/dist/assets/LabelXs-oqZNqbm_-iafrj25N.js +0 -6
- package/site/dist/assets/LandingScreen-CGWUoSUG-Q7hgXKvM.js +0 -1
- package/site/dist/assets/Layouts-BMRfo5hw-CeCOyCLK.js +0 -77
- package/site/dist/assets/Link-BdDilT2T-BEDEdHuS.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-DK1z_v7y.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-BYYpJ5-6.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uhub9C26.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uid22VTN.js +0 -117
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-CVQibNi9.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-Cy7hEE2f.js +0 -62
- package/site/dist/assets/LoadingSkeleton-BMsgO5PV-CiEyS5PZ.js +0 -17
- package/site/dist/assets/LockClosedIcon-BgY_5v_Z.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DhUMiJZJ.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-DMygAXMU.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-_1GFUSIg.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-DLnBJ5b8.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-BUJY1lzs.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-25l5fVun.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-c-khvG4Y.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-BRFN2eXr.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-DL30vu07.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-DfAvJRyK.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-DtxhRQyw.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-CNfu8ujT.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-BR6z6xE8.js +0 -20
- package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
- package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
- package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
- package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
- package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
- package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
- package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
- package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
- package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
- package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
- package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
- package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
- package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
- package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
- package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
- package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
- package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
- package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
- package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
- package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
- package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
- package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
- package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
- package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
- package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
- package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
- package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
- package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
- package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
- package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
- package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
- package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
- package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
- package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
- package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
- package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
- package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
- package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
- package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
- package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
- package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
- package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
- package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
- package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
- package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
- package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
- package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
- package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
- package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
- package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
- package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
- package/site/dist/assets/PhoneIcon-CvIiPwYQ.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-CDXZBdQx.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CtQWBiV2.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-BymHrKY7.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-Dro1btv5.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C1q1hiaN.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-C21D20gD.js +0 -7
- package/site/dist/assets/SIWXUtil-CHZVugix.js +0 -1
- package/site/dist/assets/Screen-D30meWsY-CCeVNuWs.js +0 -175
- package/site/dist/assets/ScreenHeader-CHmc4-Lu-DjyrH7IK.js +0 -31
- package/site/dist/assets/ScreenLayout-OwaQQcxH-CrQM6kvA.js +0 -1
- package/site/dist/assets/SelectSourceAsset-CWFog6gm-RKjBsB6m.js +0 -140
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-BcWHJoEG.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-kq1zvA7d.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-o6RHYev1.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-iQCaBkcz.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-BUdJJ0ep.js +0 -1
- package/site/dist/assets/StackedContainer-B2vaEl56-eQCM2cmv.js +0 -24
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-nART-xUU.js +0 -20
- package/site/dist/assets/Subtitle-CV-2yKE4-zxWw2m1Q.js +0 -13
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Dw9geyK3.js +0 -1
- package/site/dist/assets/Title-BnzYV3Is-B_JpTatT.js +0 -7
- package/site/dist/assets/TodoList-DnyULl18-C8Eyy28X.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-DeEL1JQS.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-Dffuo7I4.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-CWLWUiHy.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-Bo-tm6wr.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-Jwy1tJtm.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-gFbSIUhh.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-Coy4E1NQ.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D7m70C4b.js +0 -1
- package/site/dist/assets/Value-DTgR824E-BRuIGiec.js +0 -27
- package/site/dist/assets/WalletCards-DH1rqayz-Bgp9Fsgs.js +0 -1
- package/site/dist/assets/WalletIcon-gKf0gTw6.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-ISBsQt8p.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DG-RuO-F.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-CXBqCHf3.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-DP0-PoZL.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-2eU4Qo74.js +0 -21
- package/site/dist/assets/__vite-browser-external-CitKSmYk.js +0 -1
- package/site/dist/assets/_esm-CJe2F7LL.js +0 -7
- package/site/dist/assets/_esm-zhBBffjV.js +0 -1
- package/site/dist/assets/accounts-Bq8t1wAD.js +0 -1
- package/site/dist/assets/accounts-D96UKWkC.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-DX-qGsik.js +0 -1
- package/site/dist/assets/basic-MlZ7f4s9.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-BI9LWBBg.js +0 -1
- package/site/dist/assets/ccip-C-59SNEF.js +0 -7
- package/site/dist/assets/ccip-y08wK9du.js +0 -1
- package/site/dist/assets/chain-DM3oH6Px.js +0 -8
- package/site/dist/assets/check-BPgcwEPP.js +0 -1
- package/site/dist/assets/chevron-down-BSsbXkK-.js +0 -1
- package/site/dist/assets/circle-alert-DWaeLZNj.js +0 -1
- package/site/dist/assets/circle-check-big-Chb1ivw9.js +0 -1
- package/site/dist/assets/circle-x-AV9Cet0R.js +0 -1
- package/site/dist/assets/client-D0IV9lV9.js +0 -2
- package/site/dist/assets/client-Dsa7_3V3.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-Rs9nVCfm.js +0 -3
- package/site/dist/assets/copy-Ckr0AkL6.js +0 -1
- package/site/dist/assets/core-zQ1tZtVZ.js +0 -12
- package/site/dist/assets/createLucideIcon-IPW50ckY.js +0 -1
- package/site/dist/assets/defineChain-CiuCXUZW.js +0 -1
- package/site/dist/assets/dijkstra-Drfl93V4.js +0 -1
- package/site/dist/assets/dist-BxQqwnue.js +0 -16
- package/site/dist/assets/dist-Cm7pNAtL.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Dl07uGq1.js +0 -1
- package/site/dist/assets/embed.worker-ugGhEqCV.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-BLqjTPNR.js +0 -1
- package/site/dist/assets/esm-C3DslVJx.js +0 -1
- package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
- package/site/dist/assets/esm-DXsETz3H.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/esm-vs5jNx_Y.js +0 -37
- package/site/dist/assets/ethers-zTjbodk5-B7BBFFKf.js +0 -1
- package/site/dist/assets/eventemitter3-CL76jPsW.js +0 -1
- package/site/dist/assets/external-link-D-uehwM2.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-BqTvDXBR.js +0 -1
- package/site/dist/assets/features-CYXRa3_m.js +0 -2
- package/site/dist/assets/fingerprint-pattern-D8ytA16Q.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-DKWOL9UG.js +0 -1
- package/site/dist/assets/floating-ui.react-xRkl9O3u.js +0 -1
- package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
- package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
- package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-Dukf7_kJ.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-V2btxzrJ.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-CtCwQuGA.js +0 -1
- package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
- package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
- package/site/dist/assets/guards-BIW-GiMJ-CHfXo8XI.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-CPG1HZmf.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-CdKXapd-.js +0 -1
- package/site/dist/assets/index-BdEw5wyl-ChSJvPZk.js +0 -1
- package/site/dist/assets/index-CKdAF_dK.js +0 -8
- package/site/dist/assets/index-CWARkn2w-Cezl_uBr.js +0 -61
- package/site/dist/assets/index-DvMddJcd-D-4mRLKK.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-BX1zsiqG.js +0 -437
- package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
- package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
- package/site/dist/assets/index.es-BmqyoBEM.js +0 -2
- package/site/dist/assets/index.es-C1v1or--.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-By6r0dVB.js +0 -1
- package/site/dist/assets/jsx-runtime-BbDfbRii.js +0 -1
- package/site/dist/assets/landmark-CH6LV6F0.js +0 -1
- package/site/dist/assets/lib-BsS8z8Lj.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-5YxrERBj.js +0 -32
- package/site/dist/assets/lock-DcskI4Bb.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-Bs0r5yOi.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DruX9J1E.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-CHp-crOQ.js +0 -1
- package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
- package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
- package/site/dist/assets/parseEther-CGP3gnf2.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-Bqz4wAsq.js +0 -1
- package/site/dist/assets/preload-helper-BaNbYf_w.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-CjWGzyg7.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-DCXkXSrv.js +0 -1
- package/site/dist/assets/react-DQnMl2Xv.js +0 -1
- package/site/dist/assets/react-DrJ0MmMO.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-5dW7aKoP.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BMX127i6.js +0 -1
- package/site/dist/assets/roles-32M1l9jI.js +0 -1
- package/site/dist/assets/rolldown-runtime-BcKkbAw3.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-C4nzcPo-.js +0 -1
- package/site/dist/assets/secp256k1-D92aGNnU.js +0 -1
- package/site/dist/assets/secp256k1-DmbwyFau.js +0 -1
- package/site/dist/assets/secp256k1-_MleGpqL.js +0 -1
- package/site/dist/assets/secp256k1-xl2og-wO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-CX8hGL56.js +0 -1
- package/site/dist/assets/serializeSignature-DfxkPnu-.js +0 -2
- package/site/dist/assets/sha2-BU_QyRTx.js +0 -1
- package/site/dist/assets/shared-DRZUmnL5-xqAUARWo.js +0 -193
- package/site/dist/assets/shared-FM0rljBt--Avg6q2R.js +0 -21
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-ByFOTrQK.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-CMFGDgT_.js +0 -1
- package/site/dist/assets/smartphone-CcEZ-XAy.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-EalZTH9q.js +0 -9
- package/site/dist/assets/stringify-BlDm7tuc.js +0 -21
- package/site/dist/assets/stripe.esm-CZRDpc-N.js +0 -2
- package/site/dist/assets/styles-BsotlekN-CXEB4Ud9.js +0 -9
- package/site/dist/assets/styles-Cm9H63WO-D90SsXOs.js +0 -92
- package/site/dist/assets/styles-DVyDvTdj-BTABn2K8.js +0 -74
- package/site/dist/assets/styles-hslXy6xG-D-49b1rk.js +0 -164
- package/site/dist/assets/telegram-B-JqnkqZ-RBoY3PLx.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-DgCO-Y70.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-DZ1MBqUW.js +0 -2
- package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
- package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
- package/site/dist/assets/transformers.web-D4RvOP4-.js +0 -40
- package/site/dist/assets/transformers.web-D8W1u_I2.js +0 -49
- package/site/dist/assets/triangle-alert-CWjYH75H.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-lDtgk0os.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-Dn6_8w-Y.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-DgsQ75lh.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-B6JWREbk.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-D1hmQfpA.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-ZnbuJ-vP.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-BcqC76yj.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-L3V0jDjL.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-CLwUcevw.js +0 -1
- package/site/dist/assets/utils-BkR7EQjg.js +0 -1
- package/site/dist/assets/utils-DJhBNOZA.js +0 -1
- package/site/dist/assets/utils-DdckVfQr.js +0 -1
- package/site/dist/assets/w3m-modal-BMubXAoG.js +0 -1174
- package/site/dist/assets/wallet-Bn85Sg2W.js +0 -1
- package/site/dist/assets/wallet-connect-BvJtUsDt-Y03hhqFN.js +0 -1
- package/site/dist/assets/wallet-uSKqQsgP.js +0 -2
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-D1SSFvMp.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-Dv6JY_ko.js +0 -12
- package/site/dist/assets/wui-ux-by-reown-fVu0QkI4.js +0 -1147
- package/site/dist/assets/x-rlSp9U7W.js +0 -1
- package/src/discover/explore.js +0 -271
- package/src/market/settle.js +0 -165
- package/src/market/terms.js +0 -200
- package/src/publish/cli.js +0 -336
- package/src/publish/envelope.js +0 -185
- package/src/publish/manifest.js +0 -142
package/site/dist/index.html
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
<script type="module" crossorigin src="./assets/index-
|
|
4
|
-
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-
|
|
5
|
-
<link rel="modulepreload" crossorigin href="./assets/preload-helper-BaNbYf_w.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="#/
|
|
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
|
-
<footer>
|
|
20
|
-
|
|
18
|
+
<footer>Searches run in your browser. Your searches and saved records stay on this device unless you export them.
|
|
19
|
+
Official documents are shown as their publishers posted them. For AI agents: <a href="./.well-known/agent-card.json">agent card</a>.</footer>
|
|
21
20
|
</html>
|
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
|
-
|
|
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
|
}
|
package/site/no-model.js
ADDED
|
@@ -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
|
-
//
|
|
2
|
-
//
|
|
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 (
|
|
8
|
-
// GET /paid/<
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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
|
-
//
|
|
15
|
-
//
|
|
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
|
-
|
|
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": "
|
|
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
|
-
|
|
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 (!
|
|
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, """)}" 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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
71
|
-
|
|
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
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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 (
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|