@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/src/publish/ship.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
// westmarch-ship app.json [--no-crawl] [--no-deploy] [--dry-run]
|
|
5
5
|
// westmarch-ship app.json --local <out> [--crawl] a view from staged records, no chain (see buildLocal)
|
|
6
|
-
// westmarch-ship app.json --register the app alone:
|
|
6
|
+
// westmarch-ship app.json --register the app alone: its repo, empty site, card bound
|
|
7
7
|
// westmarch-ship app.json --devnet [--only …] the whole app on this machine (see devnetShip)
|
|
8
8
|
// westmarch-ship app.json --index the deployed app's indexer, once, here (see indexHere)
|
|
9
9
|
//
|
|
@@ -12,17 +12,16 @@
|
|
|
12
12
|
// (/mcp) and its site. Nothing is embedded here. Every step checks the chain (or
|
|
13
13
|
// Cloudflare) first and only acts on a difference, so re-running is how you update.
|
|
14
14
|
//
|
|
15
|
-
// 1.
|
|
16
|
-
// The claimer is the app's first member, so its own updates are accepted.
|
|
15
|
+
// 1. the app's repo, `<your wallet> : app` (first run only; that block becomes the card's fromBlock)
|
|
17
16
|
// 2. run each source, which crawls and publishes straight into the app
|
|
18
17
|
// 3. the stock page, the card and _headers → .ship/site
|
|
19
18
|
// 4. deploy the Worker: its R2 bucket, Workers AI, the cron, CONFIG (app.json's schema rides in it)
|
|
20
|
-
// 5. register the ERC-8004 agent and
|
|
19
|
+
// 5. register the ERC-8004 agent and point the repo's @card at the card, if it points elsewhere
|
|
21
20
|
//
|
|
22
21
|
// The Worker picks up what 2 published on its next cron run (every few minutes),
|
|
23
22
|
// so the view follows the chain with nobody running anything.
|
|
24
23
|
//
|
|
25
|
-
// --register is the fast path, run before there is any data: 1, 3, 4 and 5
|
|
24
|
+
// --register is the fast path, run before there is any data: 1, 3, 4 and 5 pointing at the card
|
|
26
25
|
// without minting. The mint copies the card's skills into its IPFS registration file and is
|
|
27
26
|
// never redone, so it waits for the first full ship, when the card lists what the app does.
|
|
28
27
|
//
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
// to app.json: the site, the Worker's config and a small state.json.
|
|
32
31
|
|
|
33
32
|
import { spawn, spawnSync } from "node:child_process";
|
|
34
|
-
import { createHash
|
|
33
|
+
import { createHash } from "node:crypto";
|
|
35
34
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
36
35
|
import { createRequire } from "node:module";
|
|
37
36
|
import { dirname, join, relative, resolve } from "node:path";
|
|
@@ -39,35 +38,6 @@ import { homedir } from "node:os";
|
|
|
39
38
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
40
39
|
|
|
41
40
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
42
|
-
// USDC per x402 network (CAIP-2): its address and EIP-712 domain, as each contract reports them.
|
|
43
|
-
// They differ: Base Sepolia's is "USDC", Base's "USD Coin", and a payment signed under the wrong
|
|
44
|
-
// one fails at the facilitator.
|
|
45
|
-
const USDC = {
|
|
46
|
-
"eip155:84532": { asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", name: "USDC", version: "2" },
|
|
47
|
-
"eip155:8453": { asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", name: "USD Coin", version: "2" },
|
|
48
|
-
"eip155:421614": { asset: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", name: "USD Coin", version: "2" },
|
|
49
|
-
"eip155:42161": { asset: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", name: "USD Coin", version: "2" },
|
|
50
|
-
};
|
|
51
|
-
const X402_NETWORK = { "base-sepolia": "eip155:84532", base: "eip155:8453", "arbitrum-sepolia": "eip155:421614", arbitrum: "eip155:42161" };
|
|
52
|
-
|
|
53
|
-
/** How a sale settles, from `paid` or `private` in app.json: the network (`network`, else the
|
|
54
|
-
* app's own), the token and its EIP-712 domain, the payee (`payTo`, else the owner) and the facilitator. */
|
|
55
|
-
export function settlement(o, { network: own, owner }) {
|
|
56
|
-
const network = o.network ? X402_NETWORK[o.network] ?? o.network : own;
|
|
57
|
-
const usdc = o.asset ? {} : USDC[network];
|
|
58
|
-
if (!usdc) throw new Error(`network ${network}: no default USDC; set asset, tokenName and tokenVersion`);
|
|
59
|
-
const name = o.tokenName ?? usdc.name, version = o.tokenVersion ?? usdc.version;
|
|
60
|
-
if (!name || !version) throw new Error("asset needs tokenName and tokenVersion: its EIP-712 domain");
|
|
61
|
-
return { network, asset: o.asset ?? usdc.asset, name, version, decimals: o.decimals ?? 6, symbol: o.symbol ?? "USDC",
|
|
62
|
-
payTo: o.payTo ?? owner, facilitator: (o.facilitator ?? "https://facilitator.payai.network").replace(/\/$/, "") };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** "0.01" → "10000": a decimal price in the token's base units. */
|
|
66
|
-
export function units(price, decimals = 6) {
|
|
67
|
-
if (!/^\d+(\.\d+)?$/.test(String(price))) throw new Error(`price must be a decimal like "0.01", got ${price}`);
|
|
68
|
-
const [whole, frac = ""] = String(price).split(".");
|
|
69
|
-
return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt((frac + "0".repeat(decimals)).slice(0, decimals) || "0")).toString();
|
|
70
|
-
}
|
|
71
41
|
const STOCK = join(HERE, "../../site/dist");
|
|
72
42
|
const log = (s) => console.error(`[ship] ${s}`);
|
|
73
43
|
|
|
@@ -82,8 +52,8 @@ const log = (s) => console.error(`[ship] ${s}`);
|
|
|
82
52
|
// "pages": "pages", // optional: the app's own files, copied over the stock page
|
|
83
53
|
// // (pages/intro.html, a fragment, replaces the description on the front page)
|
|
84
54
|
// "nav": [{ "href": "coverage.html", "label": "Coverage" }], // optional: header links to them
|
|
85
|
-
// "examples": ["…"], // optional: questions to start from, on the front page
|
|
86
|
-
// "plugin": {"marketplace": "owner/repo", "name": "…"}, // optional: its Claude Code plugin (hooks),
|
|
55
|
+
// "examples": ["…"], // optional: questions to start from, on the front page
|
|
56
|
+
// "plugin": {"marketplace": "owner/repo", "name": "…"}, // optional: its Claude Code plugin (hooks), named in its card
|
|
87
57
|
// "skill": "skill/SKILL.md", // optional: the app's agent skill, served at skill/<app>/SKILL.md and named
|
|
88
58
|
// // in the card with its sha256; fangorn-index offers it to whoever opens the app
|
|
89
59
|
// "agent": "app/agent.js" }, // optional: the module whose registerAgent the card lists, when
|
|
@@ -94,33 +64,22 @@ const log = (s) => console.error(`[ship] ${s}`);
|
|
|
94
64
|
// "live": ["readings"], // optional: namespaces members push to from their own wallets (devices,
|
|
95
65
|
// // contributors), presented beside the sources'; true = every repo they push.
|
|
96
66
|
// // Without sources the app already presents every member's repo.
|
|
97
|
-
// "network": "baseSepolia", // required: the SDK's network name
|
|
98
|
-
//
|
|
67
|
+
// "network": "baseSepolia", // required: the SDK's network name
|
|
68
|
+
// "owner": "0x…", // the wallet that publishes the app's repo; only `westmarch build` (the
|
|
69
|
+
// // deploy button) needs it, westmarch-ship asks the CLI
|
|
99
70
|
// "fangorn": "fangorn", // the CLI that signs; e.g. "node ~/fangorn/fangorn/lib/cli/cli.js",
|
|
100
71
|
// // or "env HOME=<dir> node …/cli.js" to sign with <dir>/.fangorn/config.json
|
|
101
|
-
// "paid":
|
|
72
|
+
// "paid": true, // optional: the app quotes its records' prices over x402
|
|
73
|
+
// "facilitator": "https://…" // with paid: the x402 facilitator its quotes settle through (Barliman)
|
|
102
74
|
// }
|
|
103
75
|
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// (`wrangler secret put PAID_SECRET`), the same value the sources encrypt with.
|
|
109
|
-
// The facilitator (PayAI unless `paid.facilitator`) lists POST /paid in its Bazaar on the first
|
|
110
|
-
// sale; each ship says whether it has.
|
|
111
|
-
//
|
|
112
|
-
// "private": { "pass": { "price": "5", "days": 30 }, "call": "0.01", "description": "…" }
|
|
113
|
-
//
|
|
114
|
-
// `private` makes the data itself the product. Each source stages its records (it needs an
|
|
115
|
-
// --output-dir), ship seals every row whole (x402.js's sealRow) and commits them, so IPFS holds
|
|
116
|
-
// only ciphertext; the Worker opens them in its cron with PAID_SECRET (made once in
|
|
117
|
-
// .ship/secret, or $PAID_SECRET, and put to the Worker on every deploy) and serves the view and
|
|
118
|
-
// the MCP tools only with a pass (POST /pass, x402) or one x402 payment per tool call. The card,
|
|
119
|
-
// the catalog and `describe` stay free. Settles like `paid` (network, payTo, asset, facilitator).
|
|
76
|
+
// An app sells nothing itself: publishers seal and price their own records (`fangorn commit
|
|
77
|
+
// --paid body --price 0.01`), and the app presents them. With `paid`, the Worker answers
|
|
78
|
+
// GET /paid/<record id> with an x402 402 built from the record's signed terms, paid through
|
|
79
|
+
// Barliman (CONFIG.facilitator); the buyer then settles and gets the key from the key server.
|
|
120
80
|
//
|
|
121
81
|
// A source is any command that publishes into the repo `--namespace` names when given
|
|
122
|
-
// `--publish`.
|
|
123
|
-
// whatever the CLI's saved app is.
|
|
82
|
+
// `--publish`.
|
|
124
83
|
|
|
125
84
|
/** `{field}`, `{field|slug}` and `{field|lower}` from a row; an argv item that is exactly
|
|
126
85
|
* `{args...}` splices the row's own `args` array (for flags only some rows need). */
|
|
@@ -211,11 +170,10 @@ function run(cmd, args, { cwd, env, capture = false, dry = false, input } = {})
|
|
|
211
170
|
const expand = (p) => p.replace(/^~(?=\/|$)/, homedir());
|
|
212
171
|
const cliArgv = (cfg) => expandEnv(process.env.FANGORN_CLI ?? cfg.fangorn ?? "fangorn").split(/\s+/).filter(Boolean).map(expand);
|
|
213
172
|
let CLI = ["fangorn"];
|
|
214
|
-
const fangornCli = (
|
|
173
|
+
const fangornCli = (args, opts = {}) => run(CLI[0], [...CLI.slice(1), ...args], { capture: true, ...opts });
|
|
215
174
|
|
|
216
175
|
/** The CLI must be the SDK version westmarch reads with. A different one signs against
|
|
217
|
-
* another deployment's contracts: its
|
|
218
|
-
* and every read says the app is unclaimed. (It happened: a global 2026.08.18-dev CLI
|
|
176
|
+
* another deployment's contracts: its commits land where nothing here looks. (It happened: a global 2026.08.18-dev CLI
|
|
219
177
|
* claimed and published onto the previous registries.) */
|
|
220
178
|
function assertSameDeployment(cli) {
|
|
221
179
|
// "env HOME=… node cli.js": a CLI with its own config (another wallet); find the script past the env.
|
|
@@ -235,18 +193,6 @@ function assertSameDeployment(cli) {
|
|
|
235
193
|
"they talk to different contracts. Point app.json's \"fangorn\" at a matching CLI.");
|
|
236
194
|
}
|
|
237
195
|
|
|
238
|
-
/** The block `appId` was claimed in, or undefined: the AppRegistered log, window by window. */
|
|
239
|
-
export async function claimBlock(chain, FangornConfig, appId) {
|
|
240
|
-
const { parseAbiItem } = await import("viem");
|
|
241
|
-
const from = BigInt(FangornConfig.appRegistryFromBlock), to = await chain.getBlockNumber(), step = BigInt(FangornConfig.logWindow ?? 10000);
|
|
242
|
-
const event = parseAbiItem("event AppRegistered(bytes32 indexed app_id, address indexed owner)");
|
|
243
|
-
for (let a = from; a <= to; a += step) {
|
|
244
|
-
const [claimed] = await chain.getLogs({ address: FangornConfig.appRegistryContractAddress, event, args: { app_id: appId },
|
|
245
|
-
fromBlock: a, toBlock: a + step - 1n < to ? a + step - 1n : to });
|
|
246
|
-
if (claimed) return String(claimed.blockNumber);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
196
|
/** The template's app.json as `app`'s: its name, its type tag `<app>.item.v1` and the Worker. */
|
|
251
197
|
export const TEMPLATE_APP = "my-app";
|
|
252
198
|
export function nameApp(cfg, app) {
|
|
@@ -261,37 +207,30 @@ export function nameApp(cfg, app) {
|
|
|
261
207
|
* `westmarch build`: what Workers Builds runs under the deploy button. The site, with a card
|
|
262
208
|
* that names ORIGIN for its own URL (the Worker fills it in), and CONFIG, into the app's own
|
|
263
209
|
* wrangler.json, which `wrangler deploy` then ships with the bucket, AI and cron it declares.
|
|
264
|
-
* No chain writes and no Cloudflare calls: the owner
|
|
210
|
+
* No chain writes and no Cloudflare calls: the owner makes the repo and registers its agent
|
|
265
211
|
* from fangorn.network. While app.json still names the template's app, the app is the Worker's name.
|
|
266
|
-
*
|
|
267
|
-
* ponytail: free apps only; a paid or private one ships with westmarch-ship, which sets its secret.
|
|
268
212
|
*/
|
|
269
213
|
export async function buildForDeploy(configPath, wranglerPath = join(dirname(resolve(configPath)), "wrangler.json")) {
|
|
270
214
|
const w = JSON.parse(readFileSync(wranglerPath, "utf8"));
|
|
271
215
|
let cfg = loadConfig(configPath);
|
|
272
216
|
if (cfg.app === TEMPLATE_APP) cfg = nameApp(cfg, w.name);
|
|
273
|
-
if (cfg.paid || cfg.private) throw new Error(`${configPath}: a paid or private app ships with westmarch-ship`);
|
|
274
217
|
if (!cfg.network) throw new Error(`${configPath}: "network" is required`);
|
|
218
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(cfg.owner ?? "")) throw new Error(`${configPath}: "owner" is required: the wallet that publishes the app's repo`);
|
|
275
219
|
if (!w.r2_buckets?.[0]?.bucket_name?.includes(w.name))
|
|
276
220
|
log(`the R2 bucket ${w.r2_buckets?.[0]?.bucket_name} doesn't name the Worker; two apps sharing one overwrite each other's view`);
|
|
277
221
|
|
|
278
222
|
process.env.FANGORN_NETWORK = process.env.CHAIN_NAME = cfg.network;
|
|
279
|
-
const
|
|
223
|
+
const view = await import("./view.js");
|
|
280
224
|
const FangornConfig = await view.network(cfg.network);
|
|
281
|
-
const
|
|
282
|
-
fangorn.setAppId(cfg.app);
|
|
283
|
-
if (/^0x0+$/.test(await fangorn.getAppRegistry().getAppOwner()))
|
|
284
|
-
throw new Error(`${cfg.app} is unclaimed: claim it at fangorn.network first${/[._]/.test(cfg.app) ? "" : `, or set "app" in app.json to the name you claimed`}`);
|
|
285
|
-
const chain = createPublicClient({ transport: http(FangornConfig.rpcUrl) });
|
|
286
|
-
const fromBlock = cfg.fromBlock != null ? String(cfg.fromBlock) : await claimBlock(chain, FangornConfig, fangorn.getAppId());
|
|
225
|
+
const fromBlock = String(cfg.fromBlock ?? FangornConfig.fromBlock);
|
|
287
226
|
|
|
288
227
|
const base = dirname(resolve(configPath));
|
|
289
228
|
const { site, writeCard } = await buildSite(cfg, base);
|
|
290
|
-
writeCard(view.ORIGIN, { fromBlock, repos: [] });
|
|
229
|
+
writeCard(view.ORIGIN, { owner: cfg.owner, fromBlock, repos: [] });
|
|
291
230
|
const gateway = process.env.FANGORN_IPFS_GATEWAY;
|
|
292
231
|
w.assets = { ...w.assets, directory: relative(dirname(resolve(wranglerPath)), site) };
|
|
293
232
|
w.vars = { ...w.vars, CONFIG: { app: cfg.app, name: cfg.name, description: cfg.description, fromBlock, network: cfg.network,
|
|
294
|
-
schema: { types: cfg.types, relations: cfg.relations }, repos: [], paid:
|
|
233
|
+
schema: { types: cfg.types, relations: cfg.relations }, repos: [], paid: false },
|
|
295
234
|
...(gateway ? { IPFS_GATEWAY: gateway } : {}) };
|
|
296
235
|
writeFileSync(wranglerPath, JSON.stringify(w, null, 1) + "\n");
|
|
297
236
|
log(`built ${cfg.app} (fromBlock ${fromBlock}) into ${site}; wrangler deploy ships it`);
|
|
@@ -301,11 +240,11 @@ export async function buildForDeploy(configPath, wranglerPath = join(dirname(res
|
|
|
301
240
|
|
|
302
241
|
export async function ship(configPath, { crawl = true, deploy = true, dry = false, replace = false, only = null, register = false } = {}) {
|
|
303
242
|
const cfg = loadConfig(configPath);
|
|
304
|
-
// No sources is fine: the app then presents
|
|
305
|
-
if (!register && !Object.keys(cfg.types).length)
|
|
243
|
+
// No sources is fine: the app then presents every repo on chain (repos: [] = all of them), as the explorer does.
|
|
244
|
+
if (!register && cfg.sources.length && !Object.keys(cfg.types).length)
|
|
306
245
|
throw new Error(`${configPath}: no types yet, so nothing to build; --register sets up the app alone`);
|
|
307
|
-
|
|
308
|
-
if (!cfg.
|
|
246
|
+
if (!cfg.network) throw new Error(`${configPath}: "network" is required, e.g. "baseSepolia"`);
|
|
247
|
+
if (cfg.paid && !cfg.facilitator) throw new Error(`${configPath}: "paid" needs "facilitator", the URL of the x402 facilitator (Barliman) its quotes settle through`);
|
|
309
248
|
CLI = cliArgv(cfg);
|
|
310
249
|
assertSameDeployment(CLI);
|
|
311
250
|
const base = dirname(resolve(configPath));
|
|
@@ -314,45 +253,28 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
314
253
|
mkdirSync(dir, { recursive: true });
|
|
315
254
|
const state = existsSync(statePath) ? JSON.parse(readFileSync(statePath, "utf8")) : {};
|
|
316
255
|
const save = () => !dry && writeFileSync(statePath, JSON.stringify(state, null, 1));
|
|
317
|
-
// A private app's PAID_SECRET: $PAID_SECRET, else made once in .ship/secret. Losing it loses
|
|
318
|
-
// every sealed row, so it is never made again over one that exists.
|
|
319
|
-
const secretPath = join(dir, "secret");
|
|
320
|
-
const secret = () => {
|
|
321
|
-
if (process.env.PAID_SECRET) return process.env.PAID_SECRET;
|
|
322
|
-
if (!existsSync(secretPath)) writeFileSync(secretPath, randomBytes(32).toString("hex"), { mode: 0o600 });
|
|
323
|
-
return readFileSync(secretPath, "utf8").trim();
|
|
324
|
-
};
|
|
325
|
-
|
|
326
256
|
// The SDK's network name: view.js reads with it, and the CLI and the sources spawned below sign on it.
|
|
327
257
|
process.env.FANGORN_NETWORK = process.env.CHAIN_NAME = cfg.network;
|
|
328
258
|
const [{ createPublicClient, http }, view] = await Promise.all([import("viem"), import("./view.js")]);
|
|
329
259
|
const FangornConfig = await view.network(cfg.network);
|
|
330
260
|
const fangorn = await view.readOnlyFangorn();
|
|
331
|
-
fangorn.setAppId(cfg.app);
|
|
332
|
-
const appId = fangorn.getAppId();
|
|
333
|
-
const registry = fangorn.getAppRegistry();
|
|
334
261
|
const cf = cfg.site.account ? { CLOUDFLARE_ACCOUNT_ID: cfg.site.account } : {};
|
|
335
262
|
|
|
336
|
-
// 1.
|
|
337
|
-
|
|
338
|
-
const me = (fangornCli(cfg.app, ["wallet"]).match(/Address:\s+(0x[0-9a-fA-F]{40})/) ?? [])[1]?.toLowerCase();
|
|
263
|
+
// 1. the app's repo: this wallet's `cfg.app`. Repo names are per publisher, so there is nothing to claim.
|
|
264
|
+
const me = (fangornCli(["wallet"]).match(/Address:\s+(0x[0-9a-fA-F]{40})/) ?? [])[1];
|
|
339
265
|
if (!me) throw new Error("`fangorn wallet` shows no address; run `fangorn init`");
|
|
340
|
-
let owner = (await registry.getAppOwner()).toLowerCase();
|
|
341
266
|
const chain = createPublicClient({ transport: http(FangornConfig.rpcUrl) });
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
267
|
+
const repoDir = join(dir, "repo");
|
|
268
|
+
if (!(await fangorn.onChainTip(me, cfg.app))) {
|
|
269
|
+
// Its own records start now; an app presenting every repo (the explorer) reads from the deployment's start.
|
|
270
|
+
if (presentedRepos(cfg).length) state.fromBlock = String(await chain.getBlockNumber());
|
|
271
|
+
log(`creating the app's repo ${me}:${cfg.app}`);
|
|
272
|
+
mkdirSync(repoDir, { recursive: true });
|
|
273
|
+
fangornCli(["repo", "init", cfg.app], { cwd: repoDir, dry });
|
|
348
274
|
save();
|
|
349
|
-
} else if (owner !== me) {
|
|
350
|
-
throw new Error(`${cfg.app} belongs to ${owner}, not this wallet (${me}); pick another name`);
|
|
351
275
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
state.fromBlock ??= String(cfg.fromBlock ?? FangornConfig.appRegistryFromBlock);
|
|
355
|
-
log(`app ${cfg.app} = ${appId}, owner ${owner}, fromBlock ${state.fromBlock}`);
|
|
276
|
+
state.fromBlock ??= String(cfg.fromBlock ?? FangornConfig.fromBlock);
|
|
277
|
+
log(`app ${me}:${cfg.app}, fromBlock ${state.fromBlock}`);
|
|
356
278
|
|
|
357
279
|
// 2. sources: crawl + publish straight into the app. One source failing (a site that
|
|
358
280
|
// blocks us, a feed that is down) keeps its last published data and does not stop the
|
|
@@ -360,12 +282,9 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
360
282
|
const failed = [];
|
|
361
283
|
if (crawl && !register) for (const s of cfg.sources.filter((s) => !only || only.includes(s.namespace))) {
|
|
362
284
|
const cwd = s.cwd ? resolve(base, s.cwd.replace(/^~/, homedir())) : base;
|
|
363
|
-
const paidArgs = cfg.paid && s.paid_dir ? ["--paid-dir", resolve(cwd, s.paid_dir)] : [];
|
|
364
285
|
try {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
"--publish", "--namespace", s.namespace, "--fangorn-bin", CLI.join(" ")],
|
|
368
|
-
{ cwd, env: { FANGORN_APP_ID: cfg.app }, dry });
|
|
286
|
+
run(s.command[0], [...s.command.slice(1), ...(replace ? ["--replace"] : []),
|
|
287
|
+
"--publish", "--namespace", s.namespace, "--fangorn-bin", CLI.join(" ")], { cwd, dry });
|
|
369
288
|
} catch (e) {
|
|
370
289
|
log(`✗ ${s.namespace}: ${e.message}; its last published records stay live`);
|
|
371
290
|
failed.push(s.namespace);
|
|
@@ -378,26 +297,7 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
378
297
|
// which is only known once it is deployed: the first deploy learns it and the card follows.
|
|
379
298
|
let url = cfg.site.url?.replace(/\/$/, "") ?? state.url ?? null;
|
|
380
299
|
const repos = presentedRepos(cfg);
|
|
381
|
-
|
|
382
|
-
// HMAC-SHA256(PAID_SECRET, id), see x402.js). The Worker sells keys, never records, so the
|
|
383
|
-
// data a buyer pays for is on IPFS, committed, and in their hands before they pay.
|
|
384
|
-
const terms = (o) => settlement(o, { network: `eip155:${FangornConfig.caip2}`, owner });
|
|
385
|
-
const sale = (u) => {
|
|
386
|
-
if (!cfg.paid) return { paid: null, sale: null };
|
|
387
|
-
const T = terms(cfg.paid), price = units(cfg.paid.price, T.decimals), description = cfg.paid.description ?? "";
|
|
388
|
-
const paid = { url: `${u}/paid/{id}`, price, asset: T.asset, network: T.network, decimals: T.decimals, symbol: T.symbol, description };
|
|
389
|
-
return { paid, sale: { ...T, price, description } };
|
|
390
|
-
};
|
|
391
|
-
const access = (u) => {
|
|
392
|
-
const X = cfg.private;
|
|
393
|
-
if (!X) return { card: null, config: null };
|
|
394
|
-
if (!X.pass?.price || !X.call) throw new Error(`${configPath}: private needs pass.price and call, decimal prices like "5" and "0.01"`);
|
|
395
|
-
const T = terms(X), days = X.pass.days ?? 30, description = X.description ?? "";
|
|
396
|
-
const pass = { price: units(X.pass.price, T.decimals), seconds: Math.round(days * 86400) }, call = units(X.call, T.decimals);
|
|
397
|
-
return { card: { pass_url: `${u}/pass`, pass: pass.price, days, call, network: T.network, asset: T.asset, symbol: T.symbol, decimals: T.decimals, description },
|
|
398
|
-
config: { ...T, pass, call, description } };
|
|
399
|
-
};
|
|
400
|
-
const writeSite = (u) => writeCard(u, { fromBlock: state.fromBlock, repos, paid: sale(u).paid, access: access(u).card });
|
|
300
|
+
const writeSite = (u) => writeCard(u, { owner: me, fromBlock: state.fromBlock, repos, paid: !!cfg.paid });
|
|
401
301
|
writeSite(url ?? `https://${cfg.site.project}.workers.dev`);
|
|
402
302
|
|
|
403
303
|
// 4. the Worker
|
|
@@ -406,7 +306,8 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
406
306
|
writeFileSync(path, JSON.stringify(wranglerConfig({
|
|
407
307
|
name: cfg.site.project, site, account: cfg.site.account, cron: cfg.site.cron, cpuMs: cfg.site.cpu_ms, url: cfg.site.url, ipfsGateway: view.ipfsGateway(),
|
|
408
308
|
config: { app: cfg.app, name: cfg.name, description: cfg.description, fromBlock: state.fromBlock, network: cfg.network,
|
|
409
|
-
schema: { types: cfg.types, relations: cfg.relations }, repos: register ? [] : repos, paid:
|
|
309
|
+
schema: { types: cfg.types, relations: cfg.relations }, repos: register ? [] : repos, paid: !!cfg.paid,
|
|
310
|
+
...(cfg.facilitator ? { facilitator: cfg.facilitator } : {}) },
|
|
410
311
|
}), null, 1));
|
|
411
312
|
return path;
|
|
412
313
|
};
|
|
@@ -426,22 +327,20 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
426
327
|
}
|
|
427
328
|
state.url = url;
|
|
428
329
|
save();
|
|
429
|
-
if (cfg.private) run("npx", ["wrangler", "secret", "put", "PAID_SECRET", "--config", workerConfig(url)], { env: cf, capture: true, dry, input: secret() });
|
|
430
|
-
else if (cfg.paid) log("paid: set the key secret once with `npx wrangler secret put PAID_SECRET --config .ship/wrangler.json`");
|
|
431
330
|
} else workerConfig(url ?? `https://${cfg.site.project}.workers.dev`);
|
|
432
331
|
|
|
433
|
-
// 5. ERC-8004 + the
|
|
332
|
+
// 5. ERC-8004 + the repo's @card
|
|
434
333
|
const cardUrl = `${url}/.well-known/agent-card.json`;
|
|
435
|
-
const bound = await
|
|
334
|
+
const bound = (await fangorn.cardOf(me, cfg.app).catch(() => undefined)) ?? "";
|
|
436
335
|
const mint = () => {
|
|
437
|
-
const out = dry ? "" : fangornCli(
|
|
336
|
+
const out = dry ? "" : fangornCli(["card", cardUrl, "--repo", cfg.app]);
|
|
438
337
|
state.agentId = (out.match(/Agent ID:\s+(\S+)/) ?? [])[1] ?? state.agentId;
|
|
439
338
|
delete state.agentPending;
|
|
440
339
|
save();
|
|
441
340
|
};
|
|
442
341
|
if (deploy && bound !== cardUrl && register) {
|
|
443
342
|
log(`binding ${cardUrl} (on chain: ${bound || "nothing"}); the agent is minted on the first full ship`);
|
|
444
|
-
fangornCli(
|
|
343
|
+
fangornCli(["card", cardUrl, "--repo", cfg.app, "--skip-register"], { dry });
|
|
445
344
|
state.agentPending = true;
|
|
446
345
|
save();
|
|
447
346
|
} else if (deploy && bound !== cardUrl) {
|
|
@@ -458,93 +357,11 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
458
357
|
await indexHere(configPath).catch((e) => log(`✗ first index: ${e.message}; the cron will build it`));
|
|
459
358
|
}
|
|
460
359
|
|
|
461
|
-
// The Bazaar: the facilitator lists POST /paid once it settles a sale (and decides to);
|
|
462
|
-
// nothing here can list it sooner, so ship only says where it stands.
|
|
463
|
-
if (deploy && (cfg.paid || cfg.private) && !dry) for (const [route, T] of [["paid", sale(url).sale], ["pass", access(url).config]]) {
|
|
464
|
-
if (!T) continue;
|
|
465
|
-
const listed = await fetch(`${T.facilitator}/discovery/resources?payTo=${T.payTo}`).then((r) => r.json())
|
|
466
|
-
.then((d) => (d.items ?? []).some((i) => i.resource === `${url}/${route}`), () => null);
|
|
467
|
-
log(listed ? `the Bazaar at ${T.facilitator} lists ${url}/${route}`
|
|
468
|
-
: listed === false ? `the Bazaar at ${T.facilitator} does not list ${url}/${route} yet: it is listed on its first sale`
|
|
469
|
-
: `could not read the Bazaar at ${T.facilitator}`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
360
|
log(`done: ${url} · card ${cardUrl} · MCP ${url}/mcp · the view fills on the Worker's next cron run`);
|
|
473
361
|
if (failed.length) throw new Error(`deployed, but these sources failed and kept their old records: ${failed.join(", ")}`);
|
|
474
362
|
return { url, cardUrl, agentId: state.agentId };
|
|
475
363
|
}
|
|
476
364
|
|
|
477
|
-
/** A private app's source: staged (it needs an --output-dir), every row sealed whole, and committed
|
|
478
|
-
* as a full snapshot. Unchanged rows seal to the same bytes, so the Worker embeds only what changed. */
|
|
479
|
-
async function publishSealed(cfg, base, s, { secret, dry }) {
|
|
480
|
-
const { sealRow } = await import("../agent/x402.js");
|
|
481
|
-
const rows = readStage({ ...cfg, sources: [s] }, base, { crawl: true })[s.namespace];
|
|
482
|
-
const sdir = join(base, ".ship/sealed", s.namespace);
|
|
483
|
-
mkdirSync(sdir, { recursive: true });
|
|
484
|
-
const records = await Promise.all(rows.map(async (r) => ({ id: r.id, type: r.type, payload: await sealRow(secret, r.payload) })));
|
|
485
|
-
writeFileSync(join(sdir, "commit.json"), JSON.stringify({ records }));
|
|
486
|
-
log(`${s.namespace}: ${records.length} rows sealed`);
|
|
487
|
-
try { fangornCli(cfg.app, ["status"], { cwd: sdir, dry }); } catch { fangornCli(cfg.app, ["repo", "init", s.namespace], { cwd: sdir, dry }); }
|
|
488
|
-
fangornCli(cfg.app, ["commit", "commit.json", "-m", `${s.namespace}: ${records.length} rows, sealed`, "--replace"], { cwd: sdir, dry });
|
|
489
|
-
fangornCli(cfg.app, ["push"], { cwd: sdir, dry });
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/** What x402scan reads (docs/DISCOVERY.md in Merit-Systems/x402scan): /openapi.json with
|
|
493
|
-
* `x-payment-info` on each paid route, and /.well-known/x402 naming them. POST /paid (keys to
|
|
494
|
-
* sealed records) and POST /pass (a private app's pass); not GET /paid/{id}, a template no probe
|
|
495
|
-
* can call. The worker's live 402 is authoritative over both. A private app's tools, paid per
|
|
496
|
-
* call over MCP, are listed in the Bazaar instead: x402scan reads HTTP routes only.
|
|
497
|
-
* ponytail: currency "USD" assumes the asset is USDC, the only default ship knows. */
|
|
498
|
-
export function discovery(cfg, u, paid = null, access = null) {
|
|
499
|
-
const token = paid ?? access;
|
|
500
|
-
const usd = (n) => String(Number(n) / 10 ** (token.decimals ?? 6));
|
|
501
|
-
const ids = { type: "array", items: { type: "string" }, minItems: 1, maxItems: 1000 };
|
|
502
|
-
const sym = token.symbol ?? "USDC";
|
|
503
|
-
const paths = {};
|
|
504
|
-
if (paid) paths["/paid"] = { post: {
|
|
505
|
-
operationId: "buyKeys",
|
|
506
|
-
summary: `Keys to ${cfg.name}'s sealed records, ${usd(paid.price)} ${sym} each`,
|
|
507
|
-
description: `${paid.description ? `${paid.description}. ` : ""}Each record is in the app's rows already, sealed (paid_ct, `
|
|
508
|
-
+ `AES-256-GCM, with paid_sha256 of the plaintext): this sells its key. Find records and their ids with the MCP server at ${u}/mcp; `
|
|
509
|
-
+ `the agent card is ${u}/.well-known/agent-card.json.`,
|
|
510
|
-
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["ids"], properties: { ids } } } } },
|
|
511
|
-
responses: {
|
|
512
|
-
200: { description: "A hex key per id; ids it could not sell are in missing", content: { "application/json": { schema: { type: "object", properties: {
|
|
513
|
-
keys: { type: "object", additionalProperties: { type: "string" } }, missing: ids, receipts: { type: "array", items: { type: "object" } },
|
|
514
|
-
paid: { type: "array", items: { type: "string" } } } } } } },
|
|
515
|
-
402: { description: "Payment required: x402 v2, the PaymentRequired in the PAYMENT-REQUIRED header" },
|
|
516
|
-
},
|
|
517
|
-
"x-payment-info": { protocols: [{ x402: {} }], price: { mode: "dynamic", currency: "USD", min: usd(paid.price), max: usd(BigInt(paid.price) * 1000n) } },
|
|
518
|
-
} };
|
|
519
|
-
if (access) paths["/pass"] = { post: {
|
|
520
|
-
operationId: "buyPass",
|
|
521
|
-
summary: `A ${access.days}-day pass to ${cfg.name}, ${usd(access.pass)} ${sym}`,
|
|
522
|
-
description: `${access.description ? `${access.description}. ` : ""}Every tool of the MCP server at ${u}/mcp (search, get, similar, count, `
|
|
523
|
-
+ `browse) and the view at ${u}/view, for ${access.days} days: send Authorization: Bearer <token>. Without a pass each tool call costs `
|
|
524
|
-
+ `${usd(access.call)} ${sym} over x402's MCP transport.`,
|
|
525
|
-
responses: {
|
|
526
|
-
200: { description: "The pass", content: { "application/json": { schema: { type: "object", properties: {
|
|
527
|
-
token: { type: "string" }, expires: { type: "integer", description: "Unix seconds" }, use: { type: "string" } } } } } },
|
|
528
|
-
402: { description: "Payment required: x402 v2, the PaymentRequired in the PAYMENT-REQUIRED header" },
|
|
529
|
-
},
|
|
530
|
-
"x-payment-info": { protocols: [{ x402: {} }], price: { mode: "fixed", currency: "USD", amount: usd(access.pass) } },
|
|
531
|
-
} };
|
|
532
|
-
const guidance = access
|
|
533
|
-
? `${cfg.name}'s data is private. describe on the MCP server at ${u}/mcp is free. Buy a pass (POST /pass, x402 v2) and send `
|
|
534
|
-
+ `Authorization: Bearer <token> to ${u}/mcp, or pay each tool call over x402's MCP transport (_meta["x402/payment"]).`
|
|
535
|
-
: `Search ${cfg.name} with the MCP server at ${u}/mcp (free). A row with paid_ct is for sale: POST its id to /paid, `
|
|
536
|
-
+ `pay the 402 (x402 v2), and open paid_ct with the key that comes back (AES-256-GCM, nonce first; check paid_sha256).`;
|
|
537
|
-
return {
|
|
538
|
-
openapi: {
|
|
539
|
-
openapi: "3.1.0",
|
|
540
|
-
info: { title: cfg.name, version: new Date().toISOString().slice(0, 10), description: cfg.description, "x-guidance": guidance },
|
|
541
|
-
servers: [{ url: u }],
|
|
542
|
-
paths,
|
|
543
|
-
},
|
|
544
|
-
x402: { version: 1, resources: Object.keys(paths).map((p) => `${u}${p}`) },
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
365
|
/** The stock page with the app's own files over it, in .ship/site; `writeCard(url, …)` adds the card for where it is served. */
|
|
549
366
|
export async function buildSite(cfg, base) {
|
|
550
367
|
if (!existsSync(join(STOCK, "index.html"))) throw new Error(`no stock page at ${STOCK}; run \`npx vite build\` in westmarch/site`);
|
|
@@ -569,27 +386,18 @@ export async function buildSite(cfg, base) {
|
|
|
569
386
|
const { registerAgent } = await import(cfg.site.agent ? pathToFileURL(resolve(base, cfg.site.agent)).href : "../../site/agent.js");
|
|
570
387
|
const tools = await card.captureTools(() => registerAgent({}));
|
|
571
388
|
const config = await (await import("./view.js")).network(cfg.network);
|
|
572
|
-
const writeCard = (u, { fromBlock, repos, paid =
|
|
389
|
+
const writeCard = (u, { owner, fromBlock, repos, paid = false }) => {
|
|
573
390
|
const agentCard = card.agentCard({
|
|
574
391
|
name: cfg.name, description: cfg.description, url: `${u}/`, version: new Date().toISOString().slice(0, 10), tools, tags: cfg.tags,
|
|
575
392
|
// What is sold, as a skill tagged x402: ERC-8004 registration reads x402Support off the tags.
|
|
576
|
-
skills: [
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
...(paid ? [{ id: "keys", name: "Keys", tags: [...(cfg.tags ?? []), "x402"],
|
|
580
|
-
description: `The keys to sealed records (POST ${u}/paid), per record.` }] : []),
|
|
581
|
-
],
|
|
582
|
-
fangorn: { app: cfg.app, fromBlock, repos, views: [`${u}/view`], mcp: `${u}/mcp`, config, ...(paid ? { paid } : {}), ...(access ? { access } : {}),
|
|
393
|
+
skills: paid ? [{ id: "records", name: "Paid records", tags: [...(cfg.tags ?? []), "x402"],
|
|
394
|
+
description: `Records whose publishers sell fields (rows with a \`sale\`): GET ${u}/paid/{publisher}/{repo}/{id} quotes one over x402 (scheme "fangorn").` }] : [],
|
|
395
|
+
fangorn: { app: cfg.app, owner, fromBlock, repos, views: [`${u}/view`], mcp: `${u}/mcp`, config, ...(paid ? { paid: { url: `${u}/paid/{publisher}/{repo}/{id}` } } : {}),
|
|
583
396
|
...(skill ? { skill: { url: `${u}/${skill.path}`, sha256: skill.sha256 } } : {}),
|
|
584
397
|
...(cfg.site.plugin ? { plugin: cfg.site.plugin } : {}) },
|
|
585
398
|
});
|
|
586
399
|
mkdirSync(join(site, ".well-known"), { recursive: true });
|
|
587
400
|
writeFileSync(join(site, ".well-known/agent-card.json"), JSON.stringify(agentCard, null, 1));
|
|
588
|
-
if (paid || access) {
|
|
589
|
-
const d = discovery(cfg, u, paid, access);
|
|
590
|
-
writeFileSync(join(site, "openapi.json"), JSON.stringify(d.openapi, null, 1));
|
|
591
|
-
writeFileSync(join(site, ".well-known/x402"), JSON.stringify(d.x402, null, 1));
|
|
592
|
-
}
|
|
593
401
|
writeFileSync(join(site, "_headers"), "/.well-known/agent-card.json\n Access-Control-Allow-Origin: *\n"
|
|
594
402
|
+ "/openapi.json\n Access-Control-Allow-Origin: *\n"
|
|
595
403
|
+ "/.well-known/x402\n Access-Control-Allow-Origin: *\n Content-Type: application/json\n"
|
|
@@ -640,7 +448,7 @@ export function readStage(cfg, base, { crawl = false, only = null, log: say = lo
|
|
|
640
448
|
}
|
|
641
449
|
|
|
642
450
|
/**
|
|
643
|
-
* The whole app on this machine (devnet.js): a local chain and IPFS,
|
|
451
|
+
* The whole app on this machine (devnet.js): a local chain and IPFS, what the sources staged published there, and its Worker under
|
|
644
452
|
* `wrangler dev` with a local R2 and an encoder on localhost (or Workers AI with ai=true).
|
|
645
453
|
* Runs the indexer once, says where everything is, and stays up until interrupted.
|
|
646
454
|
*/
|
|
@@ -650,9 +458,11 @@ export async function devnetShip(configPath, { crawl = false, only = null, port
|
|
|
650
458
|
const { devnet } = await import("./devnet.js");
|
|
651
459
|
const staged = readStage(cfg, base, { crawl, only });
|
|
652
460
|
const net = await devnet({ log });
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
461
|
+
// A paid app's services, as a deployment runs them: the key server publishers deposit with,
|
|
462
|
+
// and Barliman, which its quotes settle through.
|
|
463
|
+
const services = cfg.paid ? await Promise.all([net.keyServer(), net.relay()]).then(([keys, r]) => ({ keys, relay: r.url })) : null;
|
|
464
|
+
const owner = await net.client();
|
|
465
|
+
// The owner publishes the sources, as `fangorn` does in a ship.
|
|
656
466
|
for (const [ns, records] of Object.entries(staged)) {
|
|
657
467
|
if (!records.length) continue;
|
|
658
468
|
await owner.initRepo(ns);
|
|
@@ -663,9 +473,10 @@ export async function devnetShip(configPath, { crawl = false, only = null, port
|
|
|
663
473
|
const url = `http://127.0.0.1:${port}`;
|
|
664
474
|
const repos = Object.keys(staged);
|
|
665
475
|
const { site, writeCard } = await buildSite(cfg, base);
|
|
666
|
-
writeCard(url, { fromBlock: "0", repos });
|
|
476
|
+
writeCard(url, { owner: owner.getAddress(), fromBlock: "0", repos, paid: !!services });
|
|
667
477
|
const wcfg = wranglerConfig({ name: cfg.site.project, site, config: { app: cfg.app, name: cfg.name, description: cfg.description,
|
|
668
|
-
fromBlock: "0", schema: { types: cfg.types, relations: cfg.relations }, repos, paid:
|
|
478
|
+
fromBlock: "0", schema: { types: cfg.types, relations: cfg.relations }, repos, paid: !!services, deployment: net.deployment,
|
|
479
|
+
...(services ? { facilitator: services.relay } : {}) } });
|
|
669
480
|
// Its own R2 and state, apart from a deployed app's.
|
|
670
481
|
wcfg.vars.RPC_URL = net.rpcUrl;
|
|
671
482
|
wcfg.vars.IPFS_GATEWAY = net.gatewayUrl;
|
|
@@ -693,13 +504,15 @@ export async function devnetShip(configPath, { crawl = false, only = null, port
|
|
|
693
504
|
log(`done: ${catalog?.domains?.length ?? 0} domains, ${(catalog?.domains ?? []).reduce((n, d) => n + d.count, 0)} rows`);
|
|
694
505
|
log(`site ${url} · MCP ${url}/mcp · view ${url}/view · reindex: curl '${url}/__scheduled' · Ctrl-C stops it all`);
|
|
695
506
|
log(`grade it: npx westmarch-eval ${url}/view --golden eval/golden.jsonl`);
|
|
507
|
+
// ponytail: the devnet's IPFS is in this process's memory, so records are sold from here (sealRecords),
|
|
508
|
+
// not from a separate `fangorn` CLI; scripts/e2e-paid.mjs does exactly that.
|
|
509
|
+
if (services) log(`paid: key server ${services.keys} · Barliman ${services.relay}`);
|
|
696
510
|
await new Promise((r) => wrangler.on("exit", r));
|
|
697
511
|
}
|
|
698
512
|
|
|
699
513
|
/**
|
|
700
514
|
* The deployed app's indexer, run once on this machine: its Worker under `wrangler dev` with
|
|
701
|
-
* the app's own R2 bucket and Workers AI (remote bindings)
|
|
702
|
-
* its PAID_SECRET (in .ship/.dev.vars for the run, then removed). The first view right after a
|
|
515
|
+
* the app's own R2 bucket and Workers AI (remote bindings) and its CONFIG. The first view right after a
|
|
703
516
|
* deploy, without waiting for the cron; and a catalog too big for one cron run's limits, since
|
|
704
517
|
* nothing here is capped. Writes the same view the cron would.
|
|
705
518
|
* ponytail: Workers AI still embeds; an encoder on this machine (EMBED_URL) would record another
|
|
@@ -713,11 +526,8 @@ export async function indexHere(configPath, { port = 8791 } = {}) {
|
|
|
713
526
|
w.r2_buckets = w.r2_buckets.map((b) => ({ ...b, remote: true }));
|
|
714
527
|
w.ai = { ...w.ai, remote: true };
|
|
715
528
|
delete w.routes;
|
|
716
|
-
const wpath = join(dir, "wrangler.index.json")
|
|
529
|
+
const wpath = join(dir, "wrangler.index.json");
|
|
717
530
|
writeFileSync(wpath, JSON.stringify(w, null, 1));
|
|
718
|
-
const secret = process.env.PAID_SECRET ?? (existsSync(join(dir, "secret")) ? readFileSync(join(dir, "secret"), "utf8").trim() : null);
|
|
719
|
-
if (cfg.private && !secret) throw new Error("a private app's rows open only with its PAID_SECRET: none in $PAID_SECRET or .ship/secret");
|
|
720
|
-
if (secret) writeFileSync(vars, `PAID_SECRET=${secret}\n`, { mode: 0o600 });
|
|
721
531
|
const url = `http://127.0.0.1:${port}`, start = Math.floor(Date.now() / 1000);
|
|
722
532
|
const wrangler = spawn("npx", ["wrangler", "dev", "--config", wpath, "--port", String(port), "--ip", "127.0.0.1", "--test-scheduled"],
|
|
723
533
|
{ cwd: join(HERE, "../.."), stdio: ["ignore", "pipe", "inherit"],
|
|
@@ -744,7 +554,6 @@ export async function indexHere(configPath, { port = 8791 } = {}) {
|
|
|
744
554
|
return catalog;
|
|
745
555
|
} finally {
|
|
746
556
|
wrangler.kill();
|
|
747
|
-
rmSync(vars, { force: true });
|
|
748
557
|
}
|
|
749
558
|
}
|
|
750
559
|
|
|
@@ -761,8 +570,8 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
|
|
|
761
570
|
const base = dirname(resolve(configPath));
|
|
762
571
|
const [{ env }, view] = await Promise.all([import("@huggingface/transformers"), import("./view.js")]);
|
|
763
572
|
env.cacheDir = `${homedir()}/.cache/fangorn-mcp/models`;
|
|
764
|
-
// Not the app's real
|
|
765
|
-
const
|
|
573
|
+
// Not the app's real owner: nothing here is compared with the chain.
|
|
574
|
+
const owner = `0x${"10ca1".padStart(40, "0")}`;
|
|
766
575
|
const chain = {};
|
|
767
576
|
// Storage addresses a record by its content, and so does this.
|
|
768
577
|
if (cfg.sources.length) for (const [ns, records] of Object.entries(readStage(cfg, base, { crawl, only, log: say })))
|
|
@@ -772,11 +581,10 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
|
|
|
772
581
|
if (cfg.live) {
|
|
773
582
|
if (cfg.network) process.env.FANGORN_NETWORK = process.env.CHAIN_NAME = cfg.network;
|
|
774
583
|
const fangorn = await view.readOnlyFangorn();
|
|
775
|
-
fangorn.setAppId(cfg.app);
|
|
776
584
|
const statePath = join(base, ".ship/state.json");
|
|
777
585
|
const fromBlock = (existsSync(statePath) ? JSON.parse(readFileSync(statePath, "utf8")).fromBlock : null) ?? cfg.fromBlock;
|
|
778
586
|
const staged = new Set(cfg.sources.map((s) => s.namespace));
|
|
779
|
-
for (const t of await fangorn.
|
|
587
|
+
for (const t of await fangorn.repos(fromBlock == null ? {} : { fromBlock: BigInt(fromBlock) })) {
|
|
780
588
|
if (t.repo === "fangorn.schema" || staged.has(t.repo) || (only && !only.includes(t.repo))
|
|
781
589
|
|| (cfg.live !== true && !cfg.live.includes(t.repo))) continue;
|
|
782
590
|
const { contents } = await fangorn.readRepo(t.owner, t.repo);
|
|
@@ -784,12 +592,12 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
|
|
|
784
592
|
say(`live ${t.owner}/${t.repo}: ${contents.records.length} records, from the chain`);
|
|
785
593
|
}
|
|
786
594
|
}
|
|
787
|
-
if (!Object.keys(chain).length) throw new Error("nothing to build: no sources staged anything and no
|
|
595
|
+
if (!Object.keys(chain).length) throw new Error("nothing to build: no sources staged anything and no publisher has pushed to a live namespace");
|
|
788
596
|
rmSync(out, { recursive: true, force: true });
|
|
789
597
|
mkdirSync(join(base, ".ship"), { recursive: true });
|
|
790
598
|
const embed = view.cachedEmbed(join(base, ".ship/vectors.ndjson"));
|
|
791
599
|
const repos = presentedRepos(cfg).filter((n) => !only || only.includes(n));
|
|
792
|
-
const report = await view.publishView({ fangorn: view.localFangorn(chain
|
|
600
|
+
const report = await view.publishView({ fangorn: view.localFangorn(chain), app: cfg.app,
|
|
793
601
|
schema: { types: cfg.types, relations: cfg.relations }, repos, out, embed, log: say });
|
|
794
602
|
const rows = Object.values(report).reduce((n, r) => n + r.added, 0);
|
|
795
603
|
say(`local view: ${out} · ${rows} records, ${embed.misses} embedded, ${rows - embed.misses} from the vector cache`);
|
|
@@ -807,26 +615,17 @@ if (process.argv[1]?.endsWith("ship.js") || process.argv[1]?.endsWith("westmarch
|
|
|
807
615
|
if (w.r2_buckets[0].bucket_name !== "quorum-view" || !w.ai || !w.triggers.crons.length || w.routes[0].pattern !== "quorum.example"
|
|
808
616
|
|| !w.compatibility_flags.includes("nodejs_compat") || !existsSync(w.main) || !existsSync(w.alias["@huggingface/transformers"]))
|
|
809
617
|
throw new Error(`wrangler config: ${JSON.stringify(w)}`);
|
|
810
|
-
const d = discovery({ name: "Q", description: "q" }, "https://q.example", { price: "10000", decimals: 6, symbol: "USDC" });
|
|
811
|
-
const op = d.openapi.paths["/paid"].post;
|
|
812
|
-
if (op["x-payment-info"].price.min !== "0.01" || op["x-payment-info"].price.max !== "10" || !op.responses[402] || d.x402.resources[0] !== "https://q.example/paid")
|
|
813
|
-
throw new Error(`discovery: ${JSON.stringify(d)}`);
|
|
814
|
-
const dp = discovery({ name: "Q", description: "q" }, "https://q.example", null, { pass: "5000000", days: 30, call: "10000", decimals: 6 });
|
|
815
|
-
if (dp.openapi.paths["/pass"].post["x-payment-info"].price.amount !== "5" || dp.x402.resources.join() !== "https://q.example/pass" || dp.openapi.paths["/paid"])
|
|
816
|
-
throw new Error(`private discovery: ${JSON.stringify(dp.x402)}`);
|
|
817
618
|
const src = [{ namespace: "towns", command: ["py"] }];
|
|
818
619
|
if (presentedRepos({ sources: src }).join() !== "towns" || presentedRepos({ sources: src, live: ["readings", "towns"] }).join() !== "towns,readings"
|
|
819
620
|
|| presentedRepos({ sources: src, live: true }).length || presentedRepos({ sources: [] }).length)
|
|
820
621
|
throw new Error("presentedRepos: sources plus live namespaces; [] (every repo) for live: true or no sources");
|
|
821
|
-
|
|
822
|
-
if (USDC["eip155:84532"].name !== "USDC" || USDC["eip155:8453"].name !== "USD Coin") throw new Error("USDC's EIP-712 name differs on Base Sepolia and Base");
|
|
823
|
-
console.log("ship.js self-check ok — a source template fills per row, the Worker config binds its bucket, AI and cron, and the paid route is priced per id in /openapi.json and named in /.well-known/x402");
|
|
622
|
+
console.log("ship.js self-check ok — a source template fills per row, the Worker config binds its bucket, AI and cron, and presentedRepos merges sources and live namespaces");
|
|
824
623
|
process.exit(0);
|
|
825
624
|
}
|
|
826
625
|
const [path, ...flags] = process.argv.slice(2);
|
|
827
626
|
if (!path || flags.includes("--help")) {
|
|
828
627
|
console.error("usage: westmarch-ship app.json [--no-crawl] [--no-deploy] [--dry-run] [--replace] [--only ns1,ns2]\n" +
|
|
829
|
-
" westmarch-ship app.json --register [--dry-run] (
|
|
628
|
+
" westmarch-ship app.json --register [--dry-run] (the app's repo, empty site, card bound: no data)\n" +
|
|
830
629
|
" westmarch-ship app.json --local <out> [--crawl] [--only ns1,ns2] (no chain, no deploy)\n" +
|
|
831
630
|
" westmarch-ship app.json --index (the deployed app's indexer, once, on this machine)\n" +
|
|
832
631
|
" westmarch-ship app.json --devnet [--crawl] [--only ns1,ns2] [--port 8787] [--ai] (the whole app on this machine)");
|