@fangorn-network/westmarch 2026.10.29 → 2026.11.4-dev
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 +5 -5
- package/docs/app-to-agent.md +61 -625
- package/package.json +12 -17
- 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-Djs8f08m.js +8 -0
- package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/site/dist/assets/{transformers.web-BTyOEJ0C.js → transformers.web-CwR_IYQV.js} +1 -1
- package/site/dist/index.html +3 -4
- package/site/worker.js +115 -338
- package/src/agent/agent-card.js +33 -45
- package/src/agent/mcp.js +168 -371
- package/src/agent/tools.js +2 -1
- package/src/agent/view-tools.js +4 -56
- package/src/agent/x402.js +182 -369
- package/src/core/roles.js +6 -6
- package/src/core/shard.js +4 -4
- package/src/discover/apps.js +67 -81
- package/src/discover/directory.js +2 -2
- package/src/market/reactions.js +21 -15
- package/src/publish/devnet.js +69 -18
- package/src/publish/init.js +1 -1
- package/src/publish/ship.js +74 -275
- package/src/publish/view.js +52 -53
- package/template/README.md +9 -11
- package/template/package.json +1 -1
- package/plugin/scripts/fx.mjs +0 -266
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-DyVH4CEz.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-Dz1--SS9.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-BdFZmRwI.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-CxaCAisL.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-JGk7vndN.js +0 -1
- package/site/dist/assets/ApiController-CTLbMCcH.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-B8g9adpz.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-CXD9_OP8.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-BtJCglvU.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-BgC2TsqH.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-B9OzLeEO.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-C7r5snUL.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-BTV0NRio.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-7rSZIuM-.js +0 -1
- package/site/dist/assets/CheckCircleIcon-B1l17ioc.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BGSI8nKf.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-BUfq0w99.js +0 -52
- package/site/dist/assets/ChevronDownIcon-B6uJvPHu.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-r9Nk__0-.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DWWNHxmR.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-CRQwS0Xu.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-CZcCt31O.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-C5IOf-SQ.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-cwg74F3A.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-D28oeK6b.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-EEdkKeTp.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-C2mEzukj.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-CH_7r8iV.js +0 -7
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Z33kis0B.js +0 -1
- package/site/dist/assets/ConnectorUtil-DH4NePk5.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-_G88JCVR.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-CGGfc_zM.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-C9Okoc0V.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-D2l6hdSl.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-Dda3KKqS.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-DG8kJPt1.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-CcOUfn6f.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DeituDNh.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-FDg6K14-.js +0 -1
- package/site/dist/assets/EmailInputForm-Cqo1mda8-BmbxvPHC.js +0 -124
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-B05CQ9rx.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-Cxd1b9XH.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-CrJy0ViX.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-Bakaa3zu.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-j78vKrcj.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-D3dr7W9_.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-BXbYV8nI.js +0 -66
- package/site/dist/assets/EnvelopeIcon-DjPofnRI.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BhH6_V-9.js +0 -21
- package/site/dist/assets/ErrorMessage-D8VaAP5m-Bs7uX2a9.js +0 -8
- package/site/dist/assets/ErrorScreen-DipdTWzE-BgPRgnOb.js +0 -4
- package/site/dist/assets/ErrorScreen-DipdTWzE-CvxXC6pz.js +0 -1
- package/site/dist/assets/ExclamationCircleIcon-BE-WGrut.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-Ca1Xlc_f.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-De2JvZdV.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-bLp3hXoO.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-Jr6D6JiZ.js +0 -491
- package/site/dist/assets/FingerPrintIcon-DOCbS93y.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-A_G5kfa1.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-LPv35wkD.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-0hfGrfvf.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-Dz0s_NWR.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-ZGm-3-Io.js +0 -1
- package/site/dist/assets/GlobeAltIcon-BBRl2MMn.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BGiVpB8y.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-CHdfIeD_.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-Cgoiow3m.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-_YqQmRiu.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-tuIrKQHH.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-BMU_wa61.js +0 -1
- package/site/dist/assets/JsonTree-BHzNC-ic-DcVr0YF0.js +0 -34
- package/site/dist/assets/LabelXs-oqZNqbm_-CNprGVQM.js +0 -6
- package/site/dist/assets/LandingScreen-CGWUoSUG-fH6rsQen.js +0 -1
- package/site/dist/assets/Layouts-BMRfo5hw-e-sepi6F.js +0 -77
- package/site/dist/assets/Link-BdDilT2T-BdwlRJch.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-BXKN2Qm5.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-Cyn7Q8p1.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-CWiqnwLM.js +0 -117
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-YmDevJzf.js +0 -1
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-h7shW-Ay.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-DJEVblm1.js +0 -62
- package/site/dist/assets/LoadingSkeleton-BMsgO5PV-D2atjsP5.js +0 -17
- package/site/dist/assets/LockClosedIcon-9EFzWK1C.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-By3Si77a.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-BAHPY8rs.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CLjffFG1.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-BmQg1Y61.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-DmBtTtlw.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-4a683Bmf.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-C_71qsaz.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-DEHHcEf8.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-BjAb_WsY.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-BLWFsPOW.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-2PJTJxX-.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-DCEg761l.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-8nVjNc8k.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-CZ47md80.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-BrTXu3Km.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CY60yubk.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-CcMrWV5D.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-DgeDnHOK.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-CvJJwLhK.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-CADtGtSV.js +0 -7
- package/site/dist/assets/SIWXUtil-Cpiv91-M.js +0 -1
- package/site/dist/assets/Screen-D30meWsY-vEusJ7KX.js +0 -175
- package/site/dist/assets/ScreenHeader-CHmc4-Lu-BvGTLg53.js +0 -31
- package/site/dist/assets/ScreenLayout-OwaQQcxH-cfOTmyl0.js +0 -1
- package/site/dist/assets/SelectSourceAsset-CWFog6gm-DxYklZYs.js +0 -140
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-oeqeo05x.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-BxFee9UA.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-eFjsosCA.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-z8ixfIma.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-Bc_lvdiF.js +0 -1
- package/site/dist/assets/StackedContainer-B2vaEl56-Do5dXB8-.js +0 -24
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-DudetZAc.js +0 -20
- package/site/dist/assets/Subtitle-CV-2yKE4-DV0o1av3.js +0 -13
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Bo3sONvf.js +0 -1
- package/site/dist/assets/Title-BnzYV3Is-C3qOPnO_.js +0 -7
- package/site/dist/assets/TodoList-DnyULl18-BvxvubbF.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-WWflkOIi.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-CotYKcgf.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-sVFlWqa8.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-CV2-mY19.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-oPbuMTQt.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-D_mjSpAy.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-6RLG0ahv.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-CaVN0bti.js +0 -1
- package/site/dist/assets/Value-DTgR824E-Bp6WGZZm.js +0 -27
- package/site/dist/assets/WalletCards-DH1rqayz-D3cOTb9x.js +0 -1
- package/site/dist/assets/WalletIcon-DOIZhb2l.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-bOqfl5MA.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DWaVkMCA.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-DjUZvqs-.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-B6P7DQXA.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-Bc2YKZb7.js +0 -21
- package/site/dist/assets/__vite-browser-external-NciY4Vu1.js +0 -1
- package/site/dist/assets/_esm-BZP3cXTv.js +0 -1
- package/site/dist/assets/_esm-ZmzBJpEs.js +0 -6
- package/site/dist/assets/accounts-D4AXF17G.js +0 -1
- package/site/dist/assets/accounts-IkdFx9nq.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-Bn1j1Ysw.js +0 -1
- package/site/dist/assets/basic-Cz1YiCSk.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-B1J3oTZx.js +0 -7
- package/site/dist/assets/ccip-Bd7261zy.js +0 -1
- package/site/dist/assets/ccip-D6EHKpjI.js +0 -1
- package/site/dist/assets/chain-scefkYIH.js +0 -5
- package/site/dist/assets/check-DHnllI8N.js +0 -1
- package/site/dist/assets/chevron-down-1y-r4bGO.js +0 -1
- package/site/dist/assets/circle-alert-OwxamXUX.js +0 -1
- package/site/dist/assets/circle-check-big-3sZGVC10.js +0 -1
- package/site/dist/assets/circle-x-DQjNUV9J.js +0 -1
- package/site/dist/assets/client-B-BpRtwY.js +0 -1
- package/site/dist/assets/client-BfIR1FvH.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-YEuIn3jh.js +0 -3
- package/site/dist/assets/copy-DMOX_yn7.js +0 -1
- package/site/dist/assets/core-GIEmxmAa.js +0 -12
- package/site/dist/assets/createLucideIcon-BsERoFTA.js +0 -1
- package/site/dist/assets/defineChain-B-SQDLTG.js +0 -1
- package/site/dist/assets/dijkstra-CY95afk4.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Cp6IXeJd.js +0 -1
- package/site/dist/assets/dist-CrNxmdZj.js +0 -1
- package/site/dist/assets/dist-DYGtf4F0.js +0 -16
- package/site/dist/assets/embed.worker-Cnl4VGr3.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-CZPAWCgC.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-Co94ENUV.js +0 -37
- package/site/dist/assets/esm-bb8SANZh.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/ethers-zTjbodk5-Cs648CnD.js +0 -1
- package/site/dist/assets/eventemitter3-Dzc_0Uq2.js +0 -1
- package/site/dist/assets/external-link-BBPG3rG7.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-CzBCmgyL.js +0 -1
- package/site/dist/assets/features-CuiHbgEJ.js +0 -2
- package/site/dist/assets/fingerprint-pattern-CjLtyIxB.js +0 -1
- package/site/dist/assets/floating-ui.react-DmaCfayr.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-CaoMF8mP.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-BNiOFXbK.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-q4bV8kQq.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-DcnTFrXN.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-D3tWTQAR.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-D_GfQdJr.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-CTlvRMA5.js +0 -1
- package/site/dist/assets/index-BdEw5wyl-kLbhqLbv.js +0 -1
- package/site/dist/assets/index-C2h3EneX.js +0 -8
- package/site/dist/assets/index-CWARkn2w-DxuckIFn.js +0 -61
- package/site/dist/assets/index-DvMddJcd-CNBb4VUn.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-Czl0tWfF.js +0 -436
- 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-Crd0TyUS.js +0 -2
- package/site/dist/assets/index.es-DQbup1WG.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-Cjf3l05I.js +0 -1
- package/site/dist/assets/jsx-runtime-CndCZlyt.js +0 -1
- package/site/dist/assets/landmark-BZPqMFEf.js +0 -1
- package/site/dist/assets/lib-DGtzi2DK.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-B71J8sQ9.js +0 -29
- package/site/dist/assets/lock-DyN_c-ud.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-V_mcPb64.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DPWNJrfK.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-Duby3iHT.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-CHS0GNkM.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-7_VY-d83.js +0 -1
- package/site/dist/assets/preload-helper-CRzLMJPS.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-DiR6-Yq8.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-BfYA1q0G.js +0 -1
- package/site/dist/assets/react-CK2HRI1t.js +0 -1
- package/site/dist/assets/react-DxGeY8B8.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-BI2Koe4o.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BguVFH3D.js +0 -1
- package/site/dist/assets/rolldown-runtime-Bjp52BOI.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-BeYL8aZl.js +0 -1
- package/site/dist/assets/secp256k1-Bh4MSkhz.js +0 -1
- package/site/dist/assets/secp256k1-Ci4cqHl0.js +0 -1
- package/site/dist/assets/secp256k1-DsxXqbXv.js +0 -1
- package/site/dist/assets/secp256k1-N9RUmafO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-DGMik9Wx.js +0 -1
- package/site/dist/assets/serializeSignature-CWKpxoab.js +0 -2
- package/site/dist/assets/sha2-C6s7zZmb.js +0 -1
- package/site/dist/assets/shared-DRZUmnL5-BewE5wK8.js +0 -193
- package/site/dist/assets/shared-FM0rljBt-CxLACri_.js +0 -21
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BkAKsRNi.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-PjA_tGjX.js +0 -1
- package/site/dist/assets/smartphone-3p8nFSTE.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-1qCR2JMO.js +0 -8
- package/site/dist/assets/stringify-DqUj9VzO.js +0 -20
- package/site/dist/assets/stripe.esm-DEi2D291.js +0 -2
- package/site/dist/assets/styles-BsotlekN-Aq27nZGc.js +0 -9
- package/site/dist/assets/styles-Cm9H63WO-p_ic_dxE.js +0 -92
- package/site/dist/assets/styles-DVyDvTdj-CjsWqAld.js +0 -74
- package/site/dist/assets/styles-hslXy6xG-D5QHIrJU.js +0 -164
- package/site/dist/assets/telegram-B-JqnkqZ-Dqkm4rDn.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-Bi1HUQI9.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-CdFZwv07.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/triangle-alert-BG3Qw-Hq.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-CaXHFlAu.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-CQqeyhGY.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-C0xQN_Lp.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-BkK6rFBm.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-CLVXqv6I.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-DAXXs3rs.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-CGpkgIVy.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-Cfd64m4n.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-CAYqg7Za.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-B4H_uiPH.js +0 -1174
- package/site/dist/assets/wallet-BNI12IYk.js +0 -1
- package/site/dist/assets/wallet-CITSaPlB.js +0 -2
- package/site/dist/assets/wallet-connect-BvJtUsDt-CVlSNIXP.js +0 -1
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-BpzpBq5s.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-CzZ6oybX.js +0 -12
- package/site/dist/assets/wui-ux-by-reown-XyHdtV1O.js +0 -1147
- package/site/dist/assets/x-gjDrY2rk.js +0 -1
- package/src/discover/explore.js +0 -291
- 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/docs/app-to-agent.md
CHANGED
|
@@ -3,47 +3,35 @@
|
|
|
3
3
|
This walks an app owner from an empty directory to a live app that people can use in a
|
|
4
4
|
browser and any agent can find, verify and query. By the end you have:
|
|
5
5
|
|
|
6
|
-
- **an app
|
|
7
|
-
- **a schema**
|
|
6
|
+
- **an app**: a repo under your wallet, `<your address>:<app>`; repo names are per publisher, so there is nothing to claim;
|
|
7
|
+
- **a schema** in `app.json`: what your data's types are, which field is what, and
|
|
8
8
|
which relations are allowed between them;
|
|
9
|
-
- **your data** committed on chain
|
|
9
|
+
- **your data** committed on chain into that repo;
|
|
10
10
|
- **a view** of it: your committed data embedded and sharded as static files, so people
|
|
11
11
|
and agents can search it by meaning, updated with one command after each commit;
|
|
12
12
|
- **a site**: the view, a page for people, and an MCP endpoint for agents (one Cloudflare
|
|
13
|
-
Worker
|
|
14
|
-
- **an agent card** bound to the app on chain, and registered as an ERC-8004 agent;
|
|
15
|
-
- **agents
|
|
13
|
+
Worker, deployed by `westmarch-ship`);
|
|
14
|
+
- **an agent card** bound to the app on chain (the repo's `@card` record), and registered as an ERC-8004 agent;
|
|
15
|
+
- **agents finding it:** the Fangorn explorer indexes every publisher, so `fangorn-mcp`'s
|
|
16
|
+
`search` finds your records and names you as their publisher.
|
|
16
17
|
|
|
17
18
|
Where the data comes from is up to you. The guide starts from a JSON array of objects,
|
|
18
19
|
which anything can produce: a scraper, a database export, a converted spreadsheet.
|
|
19
20
|
|
|
20
21
|
```
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
fangorn-mcp: open-app my-app ─► reads the binding, verifies the card, searches the view
|
|
22
|
+
sources ─► fangorn commit/push ─► chain (<you>:<app>)
|
|
23
|
+
│
|
|
24
|
+
app.json (types) ─► the Worker's CONFIG
|
|
25
|
+
▼
|
|
26
|
+
the Worker's cron ─► embeds what changed ─► view in R2 ─► page, /mcp, data tools
|
|
27
|
+
│
|
|
28
|
+
westmarch-ship ─► card at <site>/.well-known/agent-card.json
|
|
29
|
+
└─► fangorn card <url> --repo <app> ─► the repo's @card = card URL, + ERC-8004 agent
|
|
30
|
+
│
|
|
31
|
+
the explorer (indexes every publisher) ◄── fangorn-mcp: search, publishers, get, buy
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
- **Free, local:** `westmarch-view` (step 7). It turns your commits into a view on your
|
|
36
|
-
own machine or CI. It embeds on CPU, ~14 records/s, only what changed since the last
|
|
37
|
-
run, and you host the files with your site.
|
|
38
|
-
- **Hosted:** quickbeam watches apps on chain, embeds on a GPU, and serves the view
|
|
39
|
-
itself. It will also do things one app's files can't, such as state across apps. It
|
|
40
|
-
writes the same files, so moving an app to it means changing the `views` URL in its
|
|
41
|
-
card.
|
|
42
|
-
|
|
43
|
-
It runs on Base Sepolia (new apps) or Arbitrum Sepolia (apps claimed there before the move).
|
|
44
|
-
`"network"` in `app.json` is required: `"baseSepolia"`, or the network the app was claimed on.
|
|
45
|
-
`westmarch-view` and `fangorn-mcp` read Base Sepolia unless `FANGORN_NETWORK=arbitrumSepolia`;
|
|
46
|
-
the CLI signs on `CHAIN_NAME`.
|
|
34
|
+
Everything runs on Base Sepolia: `"network": "baseSepolia"` in `app.json`.
|
|
47
35
|
|
|
48
36
|
## The short way: one config, one command
|
|
49
37
|
|
|
@@ -62,14 +50,14 @@ update: each step checks the chain (or Cloudflare) first and acts only on a diff
|
|
|
62
50
|
"presentation": { "facets": ["category"] } } },
|
|
63
51
|
"relations": [],
|
|
64
52
|
"sources": [{ "namespace": "things", "command": ["python", "-m", "my_source"] }],
|
|
65
|
-
"network": "baseSepolia", // required
|
|
53
|
+
"network": "baseSepolia", // required
|
|
66
54
|
"fangorn": "fangorn", // the CLI that signs
|
|
67
55
|
"paid": { "price": "0.01", "description": "the full record" } // optional: USDC on the app's network, x402 v2
|
|
68
56
|
}
|
|
69
57
|
```
|
|
70
58
|
|
|
71
59
|
```sh
|
|
72
|
-
npx westmarch-ship app.json #
|
|
60
|
+
npx westmarch-ship app.json # repo, crawl + publish, site, card, deploy, agent
|
|
73
61
|
npx westmarch-ship app.json --dry-run # every step, printed, none done
|
|
74
62
|
npx westmarch-ship app.json --no-crawl # rebuild view, site and card from what is on chain
|
|
75
63
|
npx westmarch-ship app.json --replace # each source's crawl becomes its namespace's whole state
|
|
@@ -79,10 +67,9 @@ npx westmarch-ship app.json --index # build the deployed app's view once, on
|
|
|
79
67
|
The app is one Cloudflare Worker: a cron that indexes the chain into R2 (embedding with
|
|
80
68
|
Workers AI), the view, `/mcp`, and the page. What `ship` does, in order:
|
|
81
69
|
|
|
82
|
-
1. **
|
|
83
|
-
the claimer is the app's first member;
|
|
70
|
+
1. **create the app's repo** (`fangorn repo init <app>`) on the first run, and record that block (the card's `fromBlock`);
|
|
84
71
|
2. **run each source**: a command that publishes into `--namespace` when given `--publish`.
|
|
85
|
-
|
|
72
|
+
A source with `"each": "rows.json"` is a template,
|
|
86
73
|
one source per row (`{field}`, `{field|slug}`, `{args...}`), so adding a town, a feed or
|
|
87
74
|
an account is adding a row;
|
|
88
75
|
3. **the site** in `.ship/site`: the stock page (search, plus a feed of what is coming up and
|
|
@@ -94,20 +81,12 @@ Workers AI), the view, `/mcp`, and the page. What `ship` does, in order:
|
|
|
94
81
|
the cron (`site.cron`, default every 5 minutes) and its CONFIG, which carries the schema. With `paid`, it sells each
|
|
95
82
|
record over x402; only records whose sha256 a live row carries are for sale. If the app
|
|
96
83
|
has no view yet, ship builds the first one here (`--index`) rather than wait for the cron;
|
|
97
|
-
5. **
|
|
98
|
-
agent is minted
|
|
84
|
+
5. **bind the card** (`fangorn card <url> --repo <app>`): the repo's `@card` record names the
|
|
85
|
+
card URL, and an ERC-8004 agent is minted from the card. Only when `@card` points
|
|
86
|
+
elsewhere; an agent is minted once, later runs only rebind.
|
|
99
87
|
|
|
100
88
|
State lives in `.ship/` next to `app.json`. In CI, cache `.ship/cache` and `.ship/stage`
|
|
101
|
-
and commit `.ship/state.json
|
|
102
|
-
(daily cron, and on every push to its config).
|
|
103
|
-
|
|
104
|
-
**What was run:** all of it, for Quorum (`https://quorum-bua.pages.dev`, agent `421614:245`):
|
|
105
|
-
five towns' meetings crawled, published, embedded and deployed by `westmarch-ship`, then
|
|
106
|
-
searched and bought (x402, settled on Arbitrum Sepolia) from `fangorn-mcp` in a fresh
|
|
107
|
-
session. FAR Desk (`https://far-desk.quickbeam.workers.dev`, agent `84532:10323`) is the
|
|
108
|
-
private one: 3,113 sealed FAR sections, registered on Base Sepolia and selling in real USDC on
|
|
109
|
-
Base, searched per call and with a pass; its `search` tool is in PayAI's Bazaar.
|
|
110
|
-
Kingsfoil, the reference at the end, predates `ship` and was built by hand.
|
|
89
|
+
and commit `.ship/state.json`.
|
|
111
90
|
|
|
112
91
|
## A private app: the data is the product
|
|
113
92
|
|
|
@@ -132,8 +111,7 @@ Kingsfoil, the reference at the end, predates `ship` and was built by hand.
|
|
|
132
111
|
- **Serving.** `/view/*` (but `cdn/catalog`) and every MCP tool but `describe` need access: a pass
|
|
133
112
|
(`POST /pass`, x402 v2, a bearer token good for `days`) or one x402 payment per tool call over
|
|
134
113
|
x402's MCP transport (`_meta["x402/payment"]`). A call that fails or misses is not charged.
|
|
135
|
-
|
|
136
|
-
`FANGORN_MCP_MAX_PASS`, else per call at or under `FANGORN_MCP_MAX_PRICE`.
|
|
114
|
+
An agent pays with any x402 client pointed at the app's `/mcp`.
|
|
137
115
|
- **Discovery.** The card's Fangorn extension carries `access` (pass URL and prices), and its
|
|
138
116
|
skills are tagged `x402`, so the ERC-8004 registration says `x402Support: true`.
|
|
139
117
|
`/openapi.json` and `/.well-known/x402` list `POST /pass` for x402scan; the facilitator's Bazaar
|
|
@@ -192,578 +170,63 @@ Without labels, it also searches 200 sampled rows by their own titles (`known@1`
|
|
|
192
170
|
|
|
193
171
|
Worse is: mean precision or `known@10` down more than 0.05, any question the base
|
|
194
172
|
answered now answered by nothing, a `hard` record check rising, or any floor or ceiling
|
|
195
|
-
missed.
|
|
196
|
-
|
|
197
|
-
required check on `main`, and put `eval/`, `.github/` and `app.json` under `CODEOWNERS`,
|
|
173
|
+
missed. Run it in CI on every pull request, from the records and vectors a scheduled
|
|
174
|
+
`westmarch-ship` leaves in its cache, with no secrets; make it a required check on `main`, and put `eval/`, `.github/` and `app.json` under `CODEOWNERS`,
|
|
198
175
|
or a PR can change the gate along with what it gates.
|
|
199
176
|
|
|
200
177
|
What is not graded yet: a change to how a source *parses*. `--local` reads what the
|
|
201
178
|
sources last staged; it does not replay their parsing over the raw documents.
|
|
202
179
|
|
|
203
|
-
##
|
|
204
|
-
|
|
205
|
-
What `ship` does, one step at a time: for understanding it, or for doing one step differently.
|
|
206
|
-
|
|
207
|
-
### What you need
|
|
208
|
-
|
|
209
|
-
- Node 22.
|
|
210
|
-
- The Fangorn CLI: `npm i -g @fangorn-network/sdk@2026.10.20-dev` (gives `fangorn`).
|
|
211
|
-
- A wallet on the app's network (Base Sepolia, or Arbitrum Sepolia for older apps) with a little ETH. **It owns the app forever**, so use the
|
|
212
|
-
wallet that will publish the data.
|
|
213
|
-
- A Pinata JWT. Registering the ERC-8004 agent (step 11) pins a file to IPFS.
|
|
214
|
-
- A static host that serves `/.well-known/` and custom headers. Cloudflare Pages does
|
|
215
|
-
both, and is what the commands below use.
|
|
216
|
-
|
|
217
|
-
Start the project:
|
|
218
|
-
|
|
219
|
-
```sh
|
|
220
|
-
mkdir my-app && cd my-app && npm init -y && npm pkg set type=module
|
|
221
|
-
npm i @fangorn-network/westmarch @huggingface/transformers vite
|
|
222
|
-
mkdir -p data schema pipeline app
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The Fangorn SDK and viem come with westmarch as peer dependencies. transformers embeds:
|
|
226
|
-
your records in `westmarch-view`, and the reader's query in their browser.
|
|
227
|
-
|
|
228
|
-
## 1. Set up the CLI
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
fangorn init # writes ~/.fangorn/config.json: key, Pinata JWT, gateway
|
|
232
|
-
fangorn wallet # confirm which address will sign
|
|
233
|
-
fangorn set-app my-app # the app every later command uses; --app <name> overrides it once
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
> The config file wins over `ETH_PRIVATE_KEY` in the environment. If `fangorn wallet`
|
|
237
|
-
> shows the wrong address, that's why.
|
|
238
|
-
|
|
239
|
-
## 2. Claim the app
|
|
240
|
-
|
|
241
|
-
First, note the current block. It becomes the card's `fromBlock` (step 9), which is where
|
|
242
|
-
readers start scanning the chain for your app:
|
|
243
|
-
|
|
244
|
-
```sh
|
|
245
|
-
node -e 'const {createPublicClient,http}=require("viem");const {baseSepolia}=require("viem/chains");
|
|
246
|
-
createPublicClient({chain:baseSepolia,transport:http()}).getBlockNumber().then(String).then(console.log)' # arbitrumSepolia for older apps
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
Then claim the name:
|
|
250
|
-
|
|
251
|
-
```sh
|
|
252
|
-
fangorn app info # "unclaimed", and the wallet you expect
|
|
253
|
-
fangorn app claim # first come, first served; permanent
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
With no flags, `claim` uses placeholder terms. Publish real ones later with
|
|
257
|
-
`fangorn app terms <hash> <uri>`; anyone who joined under the old terms has to accept again.
|
|
258
|
-
`--fee <wei>` sets what joining costs other publishers.
|
|
259
|
-
|
|
260
|
-
## 3. Become a publisher in it
|
|
261
|
-
|
|
262
|
-
Owning the app makes you its first publisher. You still need global standing:
|
|
263
|
-
|
|
264
|
-
```sh
|
|
265
|
-
fangorn register # DataRegistry.register(), then joins this app
|
|
266
|
-
fangorn app info # DataRegistry: registered · This app: joined
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
Without the join, every push reverts with `NotRegisteredForApp`.
|
|
270
|
-
|
|
271
|
-
## 4. Your data, as a graph
|
|
272
|
-
|
|
273
|
-
A commit is a graph: vertices, each with an `id`, a `tag` (its type) and a `payload` (the
|
|
274
|
-
record), and optional edges between them. Start from rows, `data/rows.json`:
|
|
275
|
-
|
|
276
|
-
```json
|
|
277
|
-
[
|
|
278
|
-
{ "id": "oak", "name": "Oak", "species": "Quercus robur", "family": "Fagaceae", "region": "Europe",
|
|
279
|
-
"description": "Long-lived deciduous tree with lobed leaves and acorns; hard durable timber …" },
|
|
280
|
-
{ "id": "baobab", "name": "Baobab", "species": "Adansonia digitata", "family": "Malvaceae", "region": "Africa",
|
|
281
|
-
"description": "Massive trunk stores water through the dry season; fruit pulp is rich in vitamin C." }
|
|
282
|
-
]
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
```js
|
|
286
|
-
// pipeline/graph.mjs — data/rows.json → data/graph.json
|
|
287
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
288
|
-
const rows = JSON.parse(readFileSync("data/rows.json", "utf8"));
|
|
289
|
-
writeFileSync("data/graph.json", JSON.stringify({
|
|
290
|
-
vertices: rows.map((r) => ({ id: r.id, tag: "my-app.tree.v1", payload: r })),
|
|
291
|
-
// edges: [{ rel: "grows_in", from: "oak", to: "europe" }] // between vertex ids, if you have them
|
|
292
|
-
}));
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
What makes records work well:
|
|
296
|
-
|
|
297
|
-
- **A stable id.** The same thing keeps the same id across commits.
|
|
298
|
-
- **Text that says what the record is, in words.** This is what gets embedded, so it's
|
|
299
|
-
what "search by meaning" matches. A title alone is weak; a sentence or a paragraph is good.
|
|
300
|
-
- **Fields worth filtering or counting on**, such as a category, a region, or a date. Agents
|
|
301
|
-
use these with `where` and `count`.
|
|
302
|
-
- **One tag per kind of thing.** Version it (`.v1`) so a later shape can be a new type.
|
|
303
|
-
|
|
304
|
-
## 5. Declare the schema
|
|
305
|
-
|
|
306
|
-
The schema says what your data is. It lives in `app.json` and nowhere else; nothing is
|
|
307
|
-
committed on chain:
|
|
308
|
-
- **`types`:** one entry per type tag: `description`, `role_map`, `presentation`.
|
|
309
|
-
- **`relations`:** the references allowed between types, `{ "rel", "from", "to" }`.
|
|
310
|
-
|
|
311
|
-
`westmarch-ship` hands it to the Worker in its CONFIG, and `westmarch-view` reads it with
|
|
312
|
-
`--config app.json`. It is applied to every record they embed, so it decides:
|
|
313
|
-
- **what gets embedded:** the `text` role, plus title, subtitle and tags;
|
|
314
|
-
- **what readers see:** titles, links, labels;
|
|
315
|
-
- **what agents can do:** filters, counts, and choosing among views by their description.
|
|
316
|
-
|
|
317
|
-
In `app.json`:
|
|
318
|
-
|
|
319
|
-
```json
|
|
320
|
-
"types": {
|
|
321
|
-
"my-app.tree.v1": {
|
|
322
|
-
"description": "Tree species: what they are, where they grow, and what they are good for.",
|
|
323
|
-
"role_map": { "identity": "id", "title": "name", "subtitle": "species",
|
|
324
|
-
"tags": ["family", "region"], "text": ["description"] },
|
|
325
|
-
"presentation": { "externalUrl": { "my-app.tree.v1": "https://en.wikipedia.org/wiki/{species}" } } }
|
|
326
|
-
},
|
|
327
|
-
"relations": []
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
**`role_map`** says which field is what:
|
|
331
|
-
|
|
332
|
-
| role | means | used for |
|
|
333
|
-
|---|---|---|
|
|
334
|
-
| `identity` | the id field | `get`, links, updates |
|
|
335
|
-
| `text` | the field(s) whose words get embedded | search by meaning |
|
|
336
|
-
| `title`, `subtitle` | what a record is called | result lists, and embedded too |
|
|
337
|
-
| `tags` | categories | filters, `count`, and embedded too |
|
|
338
|
-
| `temporal`, `spatial`, `measures`, `relations`, `media` | dates, places, numbers, links to other records, playable media | sorting, facets, previews |
|
|
339
|
-
|
|
340
|
-
**Rules:**
|
|
341
|
-
- **Declared means declared.** A role you leave out stays empty; it isn't guessed.
|
|
342
|
-
- **Undeclared types are still guessed:** a type the schema doesn't mention gets a role
|
|
343
|
-
map inferred from its fields. That works for display and often fails for search: a
|
|
344
|
-
field called `desc` or `nct` isn't recognized, and a `price` can end up as the date.
|
|
345
|
-
Declare every type you publish.
|
|
346
|
-
- **You can change it at any time**: edit `app.json` and ship again. The view takes the new
|
|
347
|
-
role map and description on the Worker's next cron run (or the next `westmarch-view`). **Records already embedded keep the text they were embedded
|
|
348
|
-
with** until they're re-embedded, so change the `text` role early.
|
|
349
|
-
- **Relations** (e.g. `{ "rel": "grows_in", "from": "my-app.tree.v1", "to": "my-app.region.v1" }`) declare which edges the app allows. Nothing enforces them yet; they
|
|
350
|
-
are what publisher graphs will be validated against.
|
|
351
|
-
|
|
352
|
-
## 6. Commit and push the data
|
|
353
|
-
|
|
354
|
-
```sh
|
|
355
|
-
node pipeline/graph.mjs
|
|
356
|
-
fangorn repo init my-app # the data namespace, tracked in ./.fangorn
|
|
357
|
-
fangorn commit data/graph.json -m "first data" --replace # local; builds and uploads the commit
|
|
358
|
-
fangorn push # the on-chain transaction
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
`--replace` makes the file the namespace's whole state, so records you removed are
|
|
362
|
-
removed. Without it, the commit adds to what's already there. `fangorn status` compares
|
|
363
|
-
your local tip with the one on chain.
|
|
364
|
-
|
|
365
|
-
## 7. Publish the view
|
|
366
|
-
|
|
367
|
-
```sh
|
|
368
|
-
npx westmarch-view --app my-app --namespace my-app --out site/view --from-block 311700000
|
|
369
|
-
# my-app: 1 publisher(s) since block 311700000
|
|
370
|
-
# …-my-app: +20 embedded, -0 removed, 20 live
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
It reads the chain and writes files; it needs no key. For every publisher of each
|
|
374
|
-
`--namespace` in the app, it:
|
|
375
|
-
- reads their records;
|
|
376
|
-
- embeds the ones the view doesn't hold yet, with your schema's roles;
|
|
377
|
-
- marks the ones that were removed as deleted;
|
|
378
|
-
- writes a new shard;
|
|
379
|
-
- refits the coverage sketch once the view has doubled.
|
|
380
|
-
|
|
381
|
-
The view's own files are its state, so a second run with nothing new on chain changes
|
|
382
|
-
nothing, and it runs the same on a laptop, in CI, or from an agent. The first run
|
|
383
|
-
downloads the embedding model (131 MB, kept in `~/.cache/fangorn-mcp/models`, shared with
|
|
384
|
-
`fangorn-mcp`). Use `--from-block` with the block you noted in step 2.
|
|
385
|
-
|
|
386
|
-
```
|
|
387
|
-
site/view/cdn/catalog every publisher's domain, with descriptions and coverage
|
|
388
|
-
site/view/cdn/domains/<app>-<publisher>-<ns>/manifest role map, embedder, shards with their sha256, tombstones
|
|
389
|
-
site/view/cdn/domains/<app>-<publisher>-<ns>/shards/shard-NNNN-<sha>.ndjson.gz
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
Check it the way a reader will:
|
|
393
|
-
|
|
394
|
-
```sh
|
|
395
|
-
(cd site && python3 -m http.server 8765 &)
|
|
396
|
-
npx westmarch-lint http://127.0.0.1:8765/view
|
|
397
|
-
# …-my-app — 20 rows — nothing to fix
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
`lint.js` reports three levels:
|
|
401
|
-
- **blocking:** readers can't find you (no coverage, wrong model);
|
|
402
|
-
- **findable, but not readable** (no text role);
|
|
403
|
-
- **readable, but a dead end** (nowhere for a hit to link to).
|
|
404
|
-
|
|
405
|
-
The fix for most findings is in the schema (step 5).
|
|
406
|
-
|
|
407
|
-
A view remembers which embedder made it, and `westmarch-view` refuses to add to a view
|
|
408
|
-
another one built. Vectors from two encoders don't compare. Pass `--rebake` to start
|
|
409
|
-
a view over.
|
|
410
|
-
|
|
411
|
-
## 8. The page
|
|
412
|
-
|
|
413
|
-
A page for people, with the same search exposed to agents in the browser as WebMCP tools.
|
|
414
|
-
It reads the view: shards are checked against their manifests' sha256, the query is
|
|
415
|
-
embedded in the reader's tab, and nothing about the query leaves it.
|
|
416
|
-
|
|
417
|
-
```js
|
|
418
|
-
// app/agent.js — the page's tools for agents in a browser. `ctx` holds the loaded rows.
|
|
419
|
-
import { search, getRow } from "@fangorn-network/westmarch/tools";
|
|
420
|
-
|
|
421
|
-
const ok = (o) => ({ content: [{ type: "text", text: JSON.stringify(o) }] });
|
|
422
|
-
|
|
423
|
-
export function registerAgent(ctx) {
|
|
424
|
-
const mc = document.modelContext;
|
|
425
|
-
if (!mc?.registerTool) return; // most browsers have no WebMCP yet
|
|
426
|
-
mc.registerTool({
|
|
427
|
-
name: "search-trees",
|
|
428
|
-
description: "Search tree species by meaning: a question in plain words. Call this first.",
|
|
429
|
-
inputSchema: { type: "object", properties: { query: { type: "string" }, limit: { type: "number" } }, required: ["query"] },
|
|
430
|
-
execute: async ({ query, limit = 10 }) =>
|
|
431
|
-
ok(search(ctx.rows, query, ctx.roles, { qv: await ctx.queryVector(query), limit })),
|
|
432
|
-
});
|
|
433
|
-
mc.registerTool({
|
|
434
|
-
name: "get-tree",
|
|
435
|
-
description: "One tree species in full, by the id search returns.",
|
|
436
|
-
inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
437
|
-
execute: async ({ id }) => ok(getRow(ctx.rows, id, ctx.roles) ?? { error: `no tree ${id}` }),
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
```js
|
|
443
|
-
// app/main.js
|
|
444
|
-
import { configure, loadShard } from "@fangorn-network/westmarch/shard";
|
|
445
|
-
import { rolesFrom, textOf } from "@fangorn-network/westmarch/roles";
|
|
446
|
-
import { search } from "@fangorn-network/westmarch/tools";
|
|
447
|
-
import { embedQuery } from "@fangorn-network/westmarch/embed";
|
|
448
|
-
import { registerAgent } from "./agent.js";
|
|
449
|
-
|
|
450
|
-
export const VIEW = new URL("view", location.href).href; // step 7
|
|
451
|
-
|
|
452
|
-
const ctx = { rows: [], roles: rolesFrom([]), queryVector: (q) => embedQuery(q).catch(() => null) };
|
|
453
|
-
configure({ onManifests: (ms) => { ctx.roles = rolesFrom(ms); }, rowText: (f) => textOf(f, ctx.roles) });
|
|
454
|
-
ctx.rows = await loadShard(VIEW); // checked against the manifests' sha256
|
|
455
|
-
registerAgent(ctx); // only now: a tool called before its data loads answers wrong, silently
|
|
456
|
-
|
|
457
|
-
// The human side: the same search, as a list.
|
|
458
|
-
const input = document.querySelector("input"), list = document.querySelector("ul");
|
|
459
|
-
input.placeholder = `Search ${ctx.rows.length} trees…`;
|
|
460
|
-
input.oninput = async () => {
|
|
461
|
-
const q = input.value;
|
|
462
|
-
const hits = search(ctx.rows, q, ctx.roles, { qv: await ctx.queryVector(q), limit: 10 });
|
|
463
|
-
if (q !== input.value) return; // a newer keystroke won
|
|
464
|
-
list.replaceChildren(...hits.map((h) => {
|
|
465
|
-
const li = document.createElement("li"), a = document.createElement("a");
|
|
466
|
-
a.textContent = h.title; if (h.url) a.href = h.url;
|
|
467
|
-
li.append(a, ` — ${h.subtitle ?? ""}`);
|
|
468
|
-
return li;
|
|
469
|
-
}));
|
|
470
|
-
};
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
```html
|
|
474
|
-
<!-- app/index.html -->
|
|
475
|
-
<!doctype html>
|
|
476
|
-
<meta charset="utf-8">
|
|
477
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
478
|
-
<title>My App</title>
|
|
479
|
-
<h1>My App</h1>
|
|
480
|
-
<input type="search" autofocus placeholder="Loading…">
|
|
481
|
-
<ul></ul>
|
|
482
|
-
<script type="module" src="./main.js"></script>
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
```js
|
|
486
|
-
// app/vite.config.js
|
|
487
|
-
export default {
|
|
488
|
-
base: "./",
|
|
489
|
-
// westmarch's embedder finds its worker by `new URL(…, import.meta.url)`;
|
|
490
|
-
// pre-bundling loses that and search silently drops to word matching.
|
|
491
|
-
optimizeDeps: { exclude: ["@fangorn-network/westmarch"] },
|
|
492
|
-
build: { outDir: "../site", emptyOutDir: false }, // keep site/view
|
|
493
|
-
};
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
```sh
|
|
497
|
-
(cd app && npx vite build)
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
`@fangorn-network/westmarch/tools` also has `describe`, `browse`, `facet` and `neighbors`,
|
|
501
|
-
if the page needs more than search. `watchShard(fn)` tells the page when the view gains
|
|
502
|
-
shards, if it should update without a reload.
|
|
503
|
-
|
|
504
|
-
## 9. The agent card
|
|
505
|
-
|
|
506
|
-
A WebMCP tool only exists inside a loaded tab, so the card is the only machine-readable
|
|
507
|
-
record that the app, its tools and its view exist. The tool list isn't written by hand:
|
|
508
|
-
`captureTools` runs your register function against a recording stub, so the card lists
|
|
509
|
-
exactly what the page registers. No `execute` runs.
|
|
510
|
-
|
|
511
|
-
```js
|
|
512
|
-
// pipeline/agent-card.mjs — site/ → site/.well-known/agent-card.json + site/_headers
|
|
513
|
-
import { mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
514
|
-
import { agentCard, captureTools } from "@fangorn-network/westmarch/agent-card";
|
|
515
|
-
import { registerAgent } from "../app/agent.js";
|
|
516
|
-
|
|
517
|
-
const SITE = "https://my-app.pages.dev";
|
|
518
|
-
const VIEW = `${SITE}/view`; // step 7; a hosted quickbeam view's URL goes here instead
|
|
519
|
-
|
|
520
|
-
const card = agentCard({
|
|
521
|
-
name: "My App",
|
|
522
|
-
description: "Tree species: what they are, where they grow, and what they are good for. Search by meaning.",
|
|
523
|
-
url: `${SITE}/`, // the page that registers the tools
|
|
524
|
-
version: "1",
|
|
525
|
-
tools: await captureTools(() => registerAgent({})),
|
|
526
|
-
tags: ["trees"],
|
|
527
|
-
fangorn: {
|
|
528
|
-
app: "my-app", // the name you claimed
|
|
529
|
-
fromBlock: 311700000, // the block you noted before claiming
|
|
530
|
-
namespaces: ["my-app"], // your data namespace(s)
|
|
531
|
-
views: [VIEW],
|
|
532
|
-
},
|
|
533
|
-
});
|
|
534
|
-
mkdirSync("site/.well-known", { recursive: true });
|
|
535
|
-
writeFileSync("site/.well-known/agent-card.json", JSON.stringify(card, null, 1));
|
|
536
|
-
|
|
537
|
-
// The card is read from other origins, so it needs CORS, and so does the view, for
|
|
538
|
-
// agents and pages elsewhere. Bundles and shards are named by their digest, so they
|
|
539
|
-
// never change: tell caches so. Shards only in the reader's own browser (`private`): a
|
|
540
|
-
// retracted record's bytes must stop being served by shared caches once the deploy drops
|
|
541
|
-
// its shard. (Pages allows one * per rule, hence a rule per domain.)
|
|
542
|
-
const immutable = " Cache-Control: public, max-age=31536000, immutable\n";
|
|
543
|
-
const mine = " Cache-Control: private, max-age=31536000, immutable\n";
|
|
544
|
-
writeFileSync("site/_headers",
|
|
545
|
-
"/.well-known/agent-card.json\n Access-Control-Allow-Origin: *\n"
|
|
546
|
-
+ "/view/*\n Access-Control-Allow-Origin: *\n"
|
|
547
|
-
+ `/assets/*\n${immutable}`
|
|
548
|
-
+ readdirSync("site/view/cdn/domains").map((d) => `/view/cdn/domains/${d}/shards/*\n${mine}`).join(""));
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
```sh
|
|
552
|
-
node pipeline/agent-card.mjs
|
|
553
|
-
```
|
|
180
|
+
## How agents find your data
|
|
554
181
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
- **`url` is the page that registers the tools,** not just any page on the site.
|
|
561
|
-
- **`fromBlock`** only affects speed: readers query logs 1000 blocks per call, so an early
|
|
562
|
-
block is slow, and 0 means hundreds of thousands of calls.
|
|
563
|
-
- **Keep these values fixed.** A rebuild that drops the extension or changes `url`
|
|
564
|
-
publishes a card every reader rejects.
|
|
182
|
+
Through the **Fangorn explorer** (`https://explorer.fangorn.network`), the app that indexes
|
|
183
|
+
every publisher. Once your records are pushed, its next index run (every few minutes) picks
|
|
184
|
+
them up, and anyone running `fangorn-mcp` finds them with `search`, sees your repos under
|
|
185
|
+
`publishers`, and buys what you sell with `buy`. You register nothing for this: being a
|
|
186
|
+
publisher is enough. [docs/mcp.md](mcp.md) has the setup.
|
|
565
187
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
```sh
|
|
569
|
-
npx wrangler pages deploy site --project-name my-app
|
|
570
|
-
curl -sD - https://my-app.pages.dev/.well-known/agent-card.json -o /dev/null | grep -iE "^HTTP|access-control"
|
|
571
|
-
# HTTP/2 200 · access-control-allow-origin: *
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
Open `https://my-app.pages.dev/` and search: that's the app for people. The card must be
|
|
575
|
-
live before step 11, because the CLI fetches it.
|
|
576
|
-
|
|
577
|
-
## 11. Register the agent and bind the card
|
|
578
|
-
|
|
579
|
-
```sh
|
|
580
|
-
fangorn app agent https://my-app.pages.dev/.well-known/agent-card.json
|
|
581
|
-
# Agent ID: 84532:…
|
|
582
|
-
# Card: https://my-app.pages.dev/.well-known/agent-card.json
|
|
583
|
-
# Tx: 0x…
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
This does two things:
|
|
587
|
-
|
|
588
|
-
1. **ERC-8004.** It builds a registration file from the card (name, description, A2A
|
|
589
|
-
endpoint = the card URL, trust = reputation, x402 only if a skill is tagged `x402`),
|
|
590
|
-
pins it to Pinata, and mints an agent in the identity registry at
|
|
591
|
-
`0x8004A818BFB912233c491871b3d84c89A494BD9e`. That's the same address as on Ethereum
|
|
592
|
-
and Base Sepolia. [8004scan](https://8004scan.io) indexes it within minutes.
|
|
593
|
-
2. **Fangorn.** It calls `setAppAgentUri(cardUrl)`, which emits `AppAgentChanged`. That log
|
|
594
|
-
is the app directory: `list-apps` and `listApps` read it.
|
|
595
|
-
|
|
596
|
-
**Mint once.** Every run without flags mints a new agent. After the first:
|
|
597
|
-
|
|
598
|
-
- `--update <agentId>` refreshes the existing agent's registration from the card as it is now
|
|
599
|
-
(name, description, skills, `x402Support`), re-pins it and sets its URI. It rebinds only if
|
|
600
|
-
the chain points elsewhere.
|
|
601
|
-
- `--skip-register` does only the second step, e.g. to move a card to a new URL.
|
|
602
|
-
|
|
603
|
-
The bind retries on a nonce race (`replacement transaction underpriced`), which load-balanced
|
|
604
|
-
RPCs cause right after the mint. If a run failed after `Agent ID:` printed, the agent exists:
|
|
605
|
-
finish with `--skip-register`, don't run it again.
|
|
606
|
-
|
|
607
|
-
## 12. Use it as an agent would
|
|
608
|
-
|
|
609
|
-
Register `fangorn-mcp` once (see [Using Fangorn apps from an agent](#using-fangorn-apps-from-an-agent)),
|
|
610
|
-
then in any agent:
|
|
611
|
-
|
|
612
|
-
```
|
|
613
|
-
open-app my-app → verified: true, data_tools: true
|
|
614
|
-
my-app__describe → the view's domains, with the schema's description
|
|
615
|
-
my-app__search "trees that survive fire" → cork oak, oak, redwood (ranked by meaning)
|
|
616
|
-
my-app__get "baobab" → the whole record
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
`open-app` reads your app's binding from the registry, fetches the card, and checks that
|
|
620
|
-
the binding points back at exactly that URL. Only then does it add the tools.
|
|
621
|
-
|
|
622
|
-
## 13. Update it
|
|
623
|
-
|
|
624
|
-
New or changed data is a commit, then the view, then a deploy:
|
|
625
|
-
|
|
626
|
-
```sh
|
|
627
|
-
node pipeline/graph.mjs && fangorn commit data/graph.json -m "…" --replace && fangorn push
|
|
628
|
-
npx westmarch-view --app my-app --namespace my-app --out site/view --from-block 311700000
|
|
629
|
-
node pipeline/agent-card.mjs && npx wrangler pages deploy site --project-name my-app
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
Only what changed is embedded and uploaded. The page and agents see it on their next
|
|
633
|
-
load, since the view URL, the card and the on-chain binding all stay the same. Run
|
|
634
|
-
`fangorn app agent` again only if the card moves. Anything that can run those three lines
|
|
635
|
-
can keep an app current: a cron job, a CI workflow, or an agent that just published.
|
|
636
|
-
|
|
637
|
-
---
|
|
638
|
-
|
|
639
|
-
## Using Fangorn apps from an agent
|
|
640
|
-
|
|
641
|
-
`fangorn-mcp` is one MCP server for every Fangorn app: a new app needs no new
|
|
642
|
-
registration. In Claude Code, the `fangorn-index` plugin installs it with the skill that uses it:
|
|
643
|
-
|
|
644
|
-
```sh
|
|
645
|
-
claude plugin marketplace add fangorn-network/westmarch && claude plugin install fangorn-index@fangorn
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
App owners add `fangorn-builder@fangorn` (the `fangorn-app` and `fangorn-improve` skills).
|
|
649
|
-
Without the plugin, install it through npm (node ≥ 20):
|
|
650
|
-
|
|
651
|
-
```sh
|
|
652
|
-
claude mcp add fangorn -- \
|
|
653
|
-
npx -y -p @fangorn-network/westmarch -p @huggingface/transformers fangorn-mcp
|
|
654
|
-
```
|
|
655
|
-
|
|
656
|
-
Without `-p @huggingface/transformers`, the install is ~500 MB lighter and `search` ranks by
|
|
657
|
-
words, not meaning. It lists the apps on Base Sepolia; add `-e FANGORN_NETWORK=arbitrumSepolia`
|
|
658
|
-
before `--` for those still on Arbitrum Sepolia. For private apps, `FANGORN_MCP_WALLET_KEY` lets it pay
|
|
659
|
-
(see [A private app](#a-private-app-the-data-is-the-product)); the plugin reads it from your
|
|
660
|
-
environment, while `-e` stores it in `~/.claude.json` in plain text.
|
|
661
|
-
|
|
662
|
-
[docs/mcp.md](mcp.md) has the full setup: other MCP clients, every setting, running from
|
|
663
|
-
source, and troubleshooting. A single-file binary (`bun build-bin.js`) exists but is not
|
|
664
|
-
published yet.
|
|
665
|
-
|
|
666
|
-
It starts with three tools:
|
|
667
|
-
- **`list-apps`** reads the apps off the chain and keeps the ones whose cards verify.
|
|
668
|
-
- **`open-app <name>`** reads that app's binding from the registry (one call), verifies its
|
|
669
|
-
card, and adds the app's tools as `<name>__<tool>`.
|
|
670
|
-
- **`call-app-tool`** reaches the same tools, for clients that don't refresh their tool list.
|
|
671
|
-
|
|
672
|
-
**Two kinds of tools.**
|
|
673
|
-
- **Data tools:** an app whose card names `views` gets these at once, with no browser:
|
|
674
|
-
`describe`, `search`, `get`, `similar`, `count`, `browse`. westmarch runs them over the
|
|
675
|
-
app's shards, checked against their manifests' sha256. `search` reads the 3 views whose
|
|
676
|
-
coverage sketch best matches the query and names the next candidates.
|
|
677
|
-
- **Page tools:** `open-app` with `page: true` also opens `card.url` in a browser for the
|
|
678
|
-
page's own WebMCP tools. An app with no views gets only these.
|
|
679
|
-
|
|
680
|
-
**How fast** (Kingsfoil: 23 views, 60,760 trials):
|
|
681
|
-
|
|
682
|
-
| | time |
|
|
683
|
-
|---|---|
|
|
684
|
-
| `open-app` | 0.4–2 s |
|
|
685
|
-
| `describe` | under 5 ms |
|
|
686
|
-
| first `search`, caches warm | 0.4–1.8 s |
|
|
687
|
-
| later searches | ~0.2–0.5 s |
|
|
688
|
-
| first `search` from an empty machine (single file) | ~3.5 s, by words while the model downloads |
|
|
689
|
-
| page tools (browser) | 5–15 s to open, ~2 GB of memory |
|
|
690
|
-
|
|
691
|
-
On a new machine, the embedding model (131 MB, kept in `~/.cache/fangorn-mcp/models`)
|
|
692
|
-
downloads in the background. Until it lands, `search` ranks by words and says so in
|
|
693
|
-
`ranked_by`; then it switches to meaning. Shards are downloaded once and kept by content
|
|
694
|
-
hash.
|
|
695
|
-
|
|
696
|
-
**Where the browser comes from (page tools only).** Anything that speaks the Chrome DevTools
|
|
697
|
-
Protocol and has WebMCP will do: a headless Chromium service, a hosted browser, or the local
|
|
698
|
-
Chrome.
|
|
699
|
-
|
|
700
|
-
| setting | browser |
|
|
701
|
-
|---|---|
|
|
702
|
-
| `FANGORN_MCP_CDP=http://host:9222` (or `--cdp`) | a running browser's DevTools address |
|
|
703
|
-
| `FANGORN_MCP_CDP=ws://host:9222/devtools/browser/…` | a browser WebSocket endpoint, which is what hosted services hand out |
|
|
704
|
-
| neither | the local Chrome 150+ (`CHROME=/path` if it isn't `google-chrome`), headless (`--headed` to watch) |
|
|
705
|
-
|
|
706
|
-
A remote browser is never closed; only the tabs the server opened are. A local Chrome is
|
|
707
|
-
shared by every session on the machine and closes when the last one exits. Its profile is
|
|
708
|
-
`~/.cache/westmarch-mcp/`. `--from-block <n>` points the server at another deployment.
|
|
188
|
+
Your app's own Worker still serves its page and its own MCP endpoint at `/mcp`, with your
|
|
189
|
+
schema and your tools, for agents that come to your app directly.
|
|
709
190
|
|
|
710
191
|
## Discovering apps in code
|
|
711
192
|
|
|
712
|
-
|
|
193
|
+
An app is a publisher's repo whose `@card` record points at an agent card that points back.
|
|
194
|
+
`discoverCard(cardUrl)` accepts a card only if:
|
|
713
195
|
|
|
714
|
-
- the card carries the Fangorn extension for **this** chain and **this**
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
The registry and the index are where you look. The on-chain binding is what you trust.
|
|
718
|
-
`examples/demo/discover.mjs` runs all three routes:
|
|
719
|
-
|
|
720
|
-
```sh
|
|
721
|
-
node examples/demo/discover.mjs card https://kingsfoil.pages.dev/.well-known/agent-card.json
|
|
722
|
-
node examples/demo/discover.mjs chain 311637349 # every app bound since this block
|
|
723
|
-
node examples/demo/discover.mjs search Kingsfoil # the public ERC-8004 index
|
|
724
|
-
```
|
|
196
|
+
- the card carries the Fangorn repo extension for **this** chain and **this** DataRegistry,
|
|
197
|
+
naming a publisher and a repo, and
|
|
198
|
+
- that repo's `@card` on chain is **exactly** the card URL.
|
|
725
199
|
|
|
726
200
|
**From a card URL:**
|
|
727
201
|
|
|
728
202
|
```js
|
|
729
|
-
import { Fangorn
|
|
203
|
+
import { Fangorn } from "@fangorn-network/sdk";
|
|
730
204
|
import { generatePrivateKey } from "viem/accounts";
|
|
731
205
|
// Fangorn.create needs a signer; a throwaway key is fine for reading.
|
|
732
|
-
const fangorn = Fangorn.create({ privateKey: generatePrivateKey()
|
|
733
|
-
const { card,
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
**From the chain** (every Fangorn app; no index, no server):
|
|
737
|
-
|
|
738
|
-
```js
|
|
739
|
-
import { listApps } from "@fangorn-network/westmarch/apps";
|
|
740
|
-
const { apps, rejected } = await listApps(fangorn, { fromBlock: 311637349n });
|
|
741
|
-
// apps: [{ name, appId, card, url, views, namespaces, fromBlock }]
|
|
742
|
-
// rejected: [{ card, why }]: unreachable or not bound, reported rather than dropped
|
|
206
|
+
const fangorn = Fangorn.create({ privateKey: generatePrivateKey() });
|
|
207
|
+
const { card, publisher, repo } = await fangorn.discoverCard(cardUrl);
|
|
743
208
|
```
|
|
744
209
|
|
|
745
210
|
**From ERC-8004** (for general-purpose agents that know nothing about Fangorn):
|
|
746
211
|
|
|
747
212
|
```js
|
|
748
213
|
const api = "https://8004scan.io/api/v1/agents";
|
|
749
|
-
const { items } = await (await fetch(`${api}?chain_id=
|
|
750
|
-
const detail = await (await fetch(`${api}/
|
|
751
|
-
const found = await fangorn.
|
|
214
|
+
const { items } = await (await fetch(`${api}?chain_id=84532&search=My%20App`)).json();
|
|
215
|
+
const detail = await (await fetch(`${api}/84532/${items[0].token_id}`)).json();
|
|
216
|
+
const found = await fangorn.discoverCard(detail.services.a2a.endpoint);
|
|
752
217
|
```
|
|
753
218
|
|
|
754
219
|
**The committed data itself:**
|
|
755
220
|
|
|
756
221
|
```js
|
|
757
|
-
fangorn.
|
|
758
|
-
const timelines = await fangorn.appNamespaces({ namespace: namespaces[0], fromBlock });
|
|
759
|
-
const { contents } = await fangorn.readNamespace(timelines[0].owner, namespaces[0]);
|
|
222
|
+
const { contents } = await fangorn.readRepo(publisher, repo);
|
|
760
223
|
```
|
|
761
224
|
|
|
762
225
|
## What the binding proves, and what it doesn't
|
|
763
226
|
|
|
764
|
-
- **Proves:**
|
|
765
|
-
|
|
766
|
-
URL. This is tested (`src/discover-
|
|
227
|
+
- **Proves:** the repo's publisher chose this card URL. Anyone can serve a card naming any
|
|
228
|
+
repo, and `discoverCard` rejects it unless the repo's `@card` points back at that exact
|
|
229
|
+
URL. This is tested in the SDK (`src/discover-card.test.ts`, `src/e2e/cards.e2e.test.ts`).
|
|
767
230
|
- **Doesn't prove:** that the page behind `card.url` behaves as described, or that the card
|
|
768
231
|
wasn't changed after binding. The card is live HTTP, not content-addressed, and the
|
|
769
232
|
binding is to the URL. That's deliberate: the card is mostly endpoints, which change, and
|
|
@@ -774,50 +237,23 @@ const { contents } = await fangorn.readNamespace(timelines[0].owner, namespaces[
|
|
|
774
237
|
|
|
775
238
|
| symptom | cause |
|
|
776
239
|
|---|---|
|
|
777
|
-
|
|
|
778
|
-
| `not bound to app … on-chain agent_uri is …` | URLs must match exactly. A trailing slash, `http` vs `https`, or a different path counts as a different card. |
|
|
240
|
+
| card rejected as not bound | URLs must match exactly. A trailing slash, `http` vs `https`, or a different path counts as a different card. |
|
|
779
241
|
| card rejected after a rebuild | the build dropped the `fangorn` block or changed `fromBlock`/`url`; keep them fixed |
|
|
780
242
|
| discovery works in Node, fails in a browser | no `Access-Control-Allow-Origin` on the card |
|
|
781
|
-
| `
|
|
782
|
-
| `
|
|
783
|
-
| `
|
|
784
|
-
|
|
|
785
|
-
| the 8004 registration says `x402Support: false` | no card skill is tagged `x402`; rebuild the card, then `fangorn app agent <card> --update <agentId>` |
|
|
243
|
+
| `fangorn card`: "ERC-8004 registration pins the registration file to IPFS" | no Pinata JWT in `fangorn init` |
|
|
244
|
+
| `fangorn card` signs as the wrong wallet | `~/.fangorn/config.json` beats `ETH_PRIVATE_KEY` |
|
|
245
|
+
| `fangorn card`: "replacement transaction underpriced" | a nonce race after the mint; the agent exists, so rerun with `--skip-register` |
|
|
246
|
+
| the 8004 registration says `x402Support: false` | no card skill is tagged `x402`; rebuild the card, then `fangorn card <card> --repo <app> --update <agentId>` |
|
|
786
247
|
| a private app's page says the data is still being indexed | the cron hasn't run yet; `westmarch-ship app.json --index` |
|
|
787
248
|
| search on the page matches words, never meaning | the bundler pre-bundled westmarch; `optimizeDeps.exclude` it |
|
|
788
|
-
| an agent's first call returns empty, later calls work | the tools registered before their data loaded
|
|
249
|
+
| an agent's first call returns empty, later calls work | the tools registered before their data loaded |
|
|
789
250
|
| an agent opens the page and finds no tools | `card.url` is not the page that registers them |
|
|
790
|
-
| `search` ignores what records say, or titles are ids | the type isn't declared in the schema, so its roles were guessed; declare it
|
|
251
|
+
| `search` ignores what records say, or titles are ids | the type isn't declared in the schema, so its roles were guessed; declare it in `app.json` |
|
|
791
252
|
| a schema change didn't change search results | records keep the text they were embedded with; only newly embedded records use the new `text` role |
|
|
792
|
-
| the view is empty after a push |
|
|
793
|
-
| `westmarch-view`: "vectors from two encoders are not comparable" | the view was built by another embedder
|
|
794
|
-
| `westmarch-view` runs for minutes before doing anything | `--from-block` is far too early; the log scan is 1000 blocks per call |
|
|
253
|
+
| the view is empty after a push | the Worker's cron hasn't run yet (`westmarch-ship app.json --index`), or the push hasn't landed (`fangorn status`) |
|
|
254
|
+
| `westmarch-view`: "vectors from two encoders are not comparable" | the view was built by another embedder; `--rebake`, or keep publishing it with that tool |
|
|
795
255
|
| lint: "nowhere to go" | no `presentation.externalUrl` in the schema, so a hit links nowhere |
|
|
796
256
|
| `fangorn-mcp`: "this browser has no WebMCP" | page tools only: the browser lacks `document.modelContext`; it needs Chrome 150+ with `--enable-features=WebMCP` |
|
|
797
257
|
| `fangorn-mcp`: "no browser: cannot launch google-chrome" | page tools only: no local Chrome; set `FANGORN_MCP_CDP` |
|
|
798
|
-
| a log scan misses a transaction you just sent | fixed in the SDK (uncached head block); upgrade to ≥ 2026.9.22-dev |
|
|
799
258
|
| IPFS reads return "switching to a service worker gateway" | ipfs.io stopped serving raw content; use a Pinata gateway |
|
|
800
259
|
| the page isn't listed as a "Web" service on 8004scan | agent0-sdk has no web endpoint type; agents find the page through the card's `url` |
|
|
801
|
-
|
|
802
|
-
## Reference: Kingsfoil
|
|
803
|
-
|
|
804
|
-
Kingsfoil has 60,760 clinical trials in 23 views. It predates the schema and quickbeam
|
|
805
|
-
path: it embeds and bakes its own views (`kingsfoil/pipeline/bake.mjs`) and serves them
|
|
806
|
-
from its own site. That works, but every update is a manual rebake and redeploy.
|
|
807
|
-
|
|
808
|
-
| | |
|
|
809
|
-
|---|---|
|
|
810
|
-
| site | https://kingsfoil.pages.dev |
|
|
811
|
-
| tools page (`card.url`) | https://kingsfoil.pages.dev/provider.html (the root is the patient view, with no tools) |
|
|
812
|
-
| card | https://kingsfoil.pages.dev/.well-known/agent-card.json |
|
|
813
|
-
| app | `kingsfoil` = `0x3069aaab9c73b01fb147a97106c7e595534850af5cab0e0ff7f2a55554d5c8ca` |
|
|
814
|
-
| owner | `0x7a7849231cF7Ab1EA003BcF0063CB89704D7Cce9` |
|
|
815
|
-
| fromBlock | 311671082 |
|
|
816
|
-
| claim tx | `0x881a88e4775c6360f799503b098f00ef470806278f7b282045d6cc881df3cf72` |
|
|
817
|
-
| ERC-8004 | agent `421614:226`, registration `ipfs://bafkreigmy2ubgzrsp55fnf77fykabapuv4y42nk6peq4ko3oi3w2x7u7ay` |
|
|
818
|
-
| bind tx | `0xc7d8726d31bb9698ba2bfae3bc107a6111062d4b7e26526301a984638ab94c9f` |
|
|
819
|
-
| card script | `kingsfoil/pipeline/agent-card.mjs` |
|
|
820
|
-
|
|
821
|
-
Deployment (Arbitrum Sepolia, 421614): AppRegistry `0x11d228c4774af3d9cae3b4b6874a12576a1a83ec`,
|
|
822
|
-
DataRegistry `0x775026e905d7b58b34d16bcbd385fa630ee36c26`, earliest AppRegistry event at block
|
|
823
|
-
311637349.
|