@fangorn-network/westmarch 0.0.19-dev → 2026.10.2-0.0-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 +33 -3
- package/docs/app-to-agent.md +100 -27
- package/package.json +9 -5
- package/plugin/scripts/fx.mjs +7 -9
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-RUyAWRzV.js +1 -0
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-ZV-LlYBe.js +31 -0
- package/site/dist/assets/Address-H8kD1d9O-CGQnt7Pu.js +17 -0
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-CKn2JLwq.js +6 -0
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-9oIP9fma.js +1 -0
- package/site/dist/assets/ArrowsRightLeftIcon-BEhBD_6N.js +1 -0
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-D1wLlSJs.js +1 -0
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-CSBbHTyz.js +1 -0
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-BsRq-xBt.js +1 -0
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BPdhsKvN.js +103 -0
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-CEGNdrkr.js +1 -0
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-Da9P7eSx.js +68 -0
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-BecgMnD_.js +1 -0
- package/site/dist/assets/CheckCircleIcon-DBiWFjO6.js +1 -0
- package/site/dist/assets/CheckCircleIcon-DzG2ROJZ.js +1 -0
- package/site/dist/assets/Checkbox-D1EDeo41-DMHcj9o_.js +52 -0
- package/site/dist/assets/ChevronDownIcon-2Os56gUt.js +1 -0
- package/site/dist/assets/Chip-CZKIKt9K-BWzs9fj5.js +26 -0
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-CFvcSiDs.js +16 -0
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-BozkCg8q.js +3 -0
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-DaCnbBzh.js +10 -0
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-DUkCLkxD.js +1 -0
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-Dv_5nMkZ.js +1 -0
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-DSGEeB3Y.js +1 -0
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-C-s4s_Lq.js +95 -0
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-BKX7fdYr.js +74 -0
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-DSoAuMFZ.js +7 -0
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-CZTXyfV0.js +39 -0
- package/site/dist/assets/CopyableText-CQapvaMr-DHO818I0.js +28 -0
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-DW4nRGfz.js +1 -0
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-DH8Nnb4Y.js +22 -0
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-gfUS_uV5.js +1 -0
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-DZj9HTHc.js +1 -0
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-eHYsDK9W.js +187 -0
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DK_XVgVk.js +8 -0
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-6gRZzhLe.js +1 -0
- package/site/dist/assets/{EmailInputForm-Cqo1mda8-B2NG3K5V.js → EmailInputForm-Cqo1mda8-CZD4r5LM.js} +11 -11
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-BZu2ae0k.js +1 -0
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-86pviemJ.js +1 -0
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-vDcvT53Y.js +23 -0
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-rNJAtSJc.js +1 -0
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-I8Q95280.js +1 -0
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-Coi7OxoI.js +1 -0
- package/site/dist/assets/EnrollTotp-Dibew4dp-DC7FrDgo.js +66 -0
- package/site/dist/assets/EnvelopeIcon-DY6ERnW7.js +1 -0
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BvESwN99.js +21 -0
- package/site/dist/assets/{ErrorMessage-D8VaAP5m-CrZOnUyg.js → ErrorMessage-D8VaAP5m-oSruKN8L.js} +2 -2
- package/site/dist/assets/ErrorScreen-DipdTWzE-DnKGxH1Z.js +4 -0
- package/site/dist/assets/ExclamationCircleIcon-BZhlqH0r.js +1 -0
- package/site/dist/assets/ExclamationTriangleIcon-CkAOJIYV.js +1 -0
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-4pi7jooW.js +75 -0
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-z2XCMaLL.js +28 -0
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-CBVA9v1-.js +491 -0
- package/site/dist/assets/FingerPrintIcon-ra2dqmJO.js +1 -0
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-DwAwc6Ny.js +1 -0
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-CZszQsZf.js +1 -0
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-DfX6nvFQ.js +58 -0
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-CDS_sk_t.js +1 -0
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-CPfNQemT.js +1 -0
- package/site/dist/assets/GlobeAltIcon-SEUEIe_E.js +1 -0
- package/site/dist/assets/GooglePay-B53WnudL-aap7zwag.js +1 -0
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-DgSjCjQI.js +4 -0
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-DO4wICml.js +1 -0
- package/site/dist/assets/InfoBanner-Cb3p1z12-CDWt2ADo.js +22 -0
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-Ba19XoBr.js +1 -0
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-DGpewEch.js +1 -0
- package/site/dist/assets/{JsonTree-BHzNC-ic-DcNcbSoM.js → JsonTree-BHzNC-ic-pAEq1bl9.js} +3 -3
- package/site/dist/assets/{LabelXs-oqZNqbm_-iafrj25N.js → LabelXs-oqZNqbm_-D09YjVR4.js} +2 -2
- package/site/dist/assets/LandingScreen-CGWUoSUG-CUOCWUXd.js +1 -0
- package/site/dist/assets/{Layouts-BMRfo5hw-CeCOyCLK.js → Layouts-BMRfo5hw-D2u1-w8g.js} +16 -16
- package/site/dist/assets/Link-BdDilT2T-BYiP1zvW.js +40 -0
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-Tb6cAepT.js +77 -0
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-pq0NF2JO.js +1 -0
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-D9MOmDqg.js +117 -0
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-B80UJqAZ.js +1 -0
- package/site/dist/assets/{LoadingSkeleton-BMsgO5PV-CiEyS5PZ.js → LoadingSkeleton-BMsgO5PV-BRMh4Fv4.js} +6 -6
- package/site/dist/assets/LockClosedIcon-BqkoEL9J.js +1 -0
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DlnOM-Kh.js +1 -0
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-DSShupq9.js +1 -0
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-DUPV_SLA.js +1 -0
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-D96GpzXX.js +1 -0
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-CYQDa4ND.js +1 -0
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-CJtSAXGN.js +1 -0
- package/site/dist/assets/ModalFooter-BCuEK_nz-D2I8KWtk.js +403 -0
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-Bx5aHy-z.js +16 -0
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-CS6988X2.js +1 -0
- package/site/dist/assets/OpenLink-CUpJ1mOr-EZWVSX2r.js +39 -0
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-BY4eK5Jh.js +1 -0
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-LXys76u9.js +6 -0
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-RczvLvmo.js +20 -0
- package/site/dist/assets/{PhArrowCircleDown-D97G-BOA.js → PhArrowCircleDown-BtlNV0TZ.js} +5 -5
- package/site/dist/assets/PhArrowClockwise-mOiWYLMV.js +14 -0
- package/site/dist/assets/PhArrowDown-CkoOD89h.js +14 -0
- package/site/dist/assets/PhArrowLeft-C0jlxVhM.js +14 -0
- package/site/dist/assets/PhArrowRight-BI-V03Xf.js +14 -0
- package/site/dist/assets/{PhArrowSquareOut-Bk2CMG3I.js → PhArrowSquareOut-DnQ7n_B4.js} +5 -5
- package/site/dist/assets/PhArrowUp-izKvURUW.js +14 -0
- package/site/dist/assets/PhArrowUpRight-Bn-JhHsY.js +14 -0
- package/site/dist/assets/{PhArrowsClockwise-BXRn1GEl.js → PhArrowsClockwise-Brtx2Re-.js} +5 -5
- package/site/dist/assets/{PhArrowsDownUp-DM09Jo_3.js → PhArrowsDownUp-PZosTqZG.js} +5 -5
- package/site/dist/assets/PhArrowsLeftRight-CYsRQ2qh.js +14 -0
- package/site/dist/assets/{PhBank-fHbgCqqZ.js → PhBank-hhPAIPVb.js} +5 -5
- package/site/dist/assets/PhBrowser-BjzAmrHO.js +14 -0
- package/site/dist/assets/PhCaretDown-ddSjwcwm.js +14 -0
- package/site/dist/assets/PhCaretLeft-B7YLoNEF.js +14 -0
- package/site/dist/assets/PhCaretRight-BY8D5MIg.js +14 -0
- package/site/dist/assets/PhCaretUp-BG10nSut.js +14 -0
- package/site/dist/assets/PhCheck-DrKd7pBC.js +14 -0
- package/site/dist/assets/{PhCircleHalf-MJZeRBGF.js → PhCircleHalf-Bk9C4DvT.js} +5 -5
- package/site/dist/assets/PhClock-Sj17weCB.js +14 -0
- package/site/dist/assets/{PhCompass-hYn3HhGN.js → PhCompass-Df1ugFO-.js} +5 -5
- package/site/dist/assets/PhCopy-rgttky8E.js +14 -0
- package/site/dist/assets/{PhCreditCard-Fy4pzwtt.js → PhCreditCard-CZpdid2n.js} +5 -5
- package/site/dist/assets/{PhCurrencyDollar-BgiaenCi.js → PhCurrencyDollar-B6IKKjue.js} +5 -5
- package/site/dist/assets/{PhDesktop-CxrzCSv5.js → PhDesktop-BuCDXYJj.js} +5 -5
- package/site/dist/assets/PhDeviceMobile-CnybhXY-.js +14 -0
- package/site/dist/assets/PhDotsThree-CSAEcba1.js +14 -0
- package/site/dist/assets/PhEnvelope-SmaNeXoQ.js +14 -0
- package/site/dist/assets/PhFunnelSimple-DKx35ITq.js +14 -0
- package/site/dist/assets/{PhGlobe-DvmSc9_A.js → PhGlobe-q1mqj65V.js} +5 -5
- package/site/dist/assets/{PhIdentificationCard-CfkZm-5K.js → PhIdentificationCard-BeLXJpfZ.js} +5 -5
- package/site/dist/assets/{PhImage-Cf0J2ZfJ.js → PhImage-BYrKfN9k.js} +5 -5
- package/site/dist/assets/{PhInfo-Bwi51-2X.js → PhInfo-X33f06Ej.js} +5 -5
- package/site/dist/assets/{PhLightbulb-C4FZV7Es.js → PhLightbulb-DMZJsowR.js} +5 -5
- package/site/dist/assets/PhMagnifyingGlass-B6D0sWFL.js +14 -0
- package/site/dist/assets/{PhPaperPlaneRight-zxQVvPLX.js → PhPaperPlaneRight-Dvo-nXr9.js} +5 -5
- package/site/dist/assets/PhPlus-CBvZvLax.js +14 -0
- package/site/dist/assets/{PhPower-CFyWDmgD.js → PhPower-D8-AcNM6.js} +5 -5
- package/site/dist/assets/{PhPuzzlePiece-BjoNVfST.js → PhPuzzlePiece-C-7mYqGO.js} +5 -5
- package/site/dist/assets/{PhQrCode-CP3tFauY.js → PhQrCode-uIXlI5ER.js} +5 -5
- package/site/dist/assets/{PhQuestion-wOWJBd3G.js → PhQuestion-rUoqC2sC.js} +5 -5
- package/site/dist/assets/{PhQuestionMark-CV0k9C0Y.js → PhQuestionMark-W55Txvvb.js} +5 -5
- package/site/dist/assets/{PhSealCheck-vIKzREuN.js → PhSealCheck-BeVLq9PH.js} +5 -5
- package/site/dist/assets/{PhSignOut-CxELV-i0.js → PhSignOut-Cq2PivQ6.js} +5 -5
- package/site/dist/assets/{PhSpinner-CGDmbzUd.js → PhSpinner-CZMtAFdX.js} +5 -5
- package/site/dist/assets/{PhTrash-C9PD0SAt.js → PhTrash-BamrmMsN.js} +5 -5
- package/site/dist/assets/{PhUser-0sWNXQeZ.js → PhUser-A2fxqIV0.js} +5 -5
- package/site/dist/assets/{PhVault-6JTskUcC.js → PhVault-B8m1usHw.js} +5 -5
- package/site/dist/assets/{PhWarning-CoKs3gz6.js → PhWarning-CpkID9oi.js} +5 -5
- package/site/dist/assets/PhWarningCircle-Cm58gXb9.js +14 -0
- package/site/dist/assets/PhX-C3b4cCEy.js +14 -0
- package/site/dist/assets/PhoneIcon-CzbssTb8.js +1 -0
- package/site/dist/assets/PinInput-DbZ0b1i1-1DtLWL81.js +247 -0
- package/site/dist/assets/QrCode-DbnZwlkH-ChwcUk_h.js +22 -0
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-C79jd345.js +1 -0
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C9c0x-yq.js +1 -0
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-PNqHb6CI.js +7 -0
- package/site/dist/assets/{Screen-D30meWsY-CCeVNuWs.js → Screen-D30meWsY-DDum2qlY.js} +22 -22
- package/site/dist/assets/{ScreenHeader-CHmc4-Lu-DjyrH7IK.js → ScreenHeader-CHmc4-Lu-BqrAEXbV.js} +4 -4
- package/site/dist/assets/ScreenLayout-OwaQQcxH-B4Z8FjFp.js +1 -0
- package/site/dist/assets/{SelectSourceAsset-CWFog6gm-RKjBsB6m.js → SelectSourceAsset-CWFog6gm-8iXuqbAs.js} +18 -18
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-CUpQZ9wq.js +14 -0
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-ZzvPLj2s.js +15 -0
- package/site/dist/assets/ShieldCheckIcon-Bjwe_-PV.js +1 -0
- package/site/dist/assets/SignRequestScreen-BHKv32KR-0qGeNipB.js +26 -0
- package/site/dist/assets/{StackedContainer-B2vaEl56-eQCM2cmv.js → StackedContainer-B2vaEl56-C-ojzCoQ.js} +2 -2
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-C3lgJb68.js +20 -0
- package/site/dist/assets/{Subtitle-CV-2yKE4-zxWw2m1Q.js → Subtitle-CV-2yKE4-o-1FQLlX.js} +2 -2
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-l35_C2DG.js +1 -0
- package/site/dist/assets/{Title-BnzYV3Is-B_JpTatT.js → Title-BnzYV3Is-RX1f0RV-.js} +2 -2
- package/site/dist/assets/TodoList-DnyULl18-JxWWU9KA.js +36 -0
- package/site/dist/assets/TransactionDetails-e9nesl30-C91nJPM9.js +109 -0
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-Cpm-4xzm.js +178 -0
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-BWCH7-Si.js +31 -0
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-BWMeJabk.js +6 -0
- package/site/dist/assets/TurnstileWrapper-B7sNddys-Dgr80Gih.js +1 -0
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-Clk4zk-8.js +1 -0
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-BgRsoR3B.js +1 -0
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D-SU-vAz.js +1 -0
- package/site/dist/assets/{Value-DTgR824E-BRuIGiec.js → Value-DTgR824E-DZpvBCWi.js} +7 -7
- package/site/dist/assets/WalletCards-DH1rqayz-UuZ4i_Ze.js +1 -0
- package/site/dist/assets/WalletIcon-B1i03Vqc.js +1 -0
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-TXTxmb8L.js +29 -0
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-CbrFb0ON.js +1 -0
- package/site/dist/assets/WalletLink-DK5LB5Ha-BYQZV7UV.js +20 -0
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-CNgSPnTS.js +1 -0
- package/site/dist/assets/WarningBanner-ZZqCEtZK-DNZUUOph.js +21 -0
- package/site/dist/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/site/dist/assets/analytics-mkkvFRju-DOXuftJB.js +1 -0
- package/site/dist/assets/basic-C9wn6QoQ.js +1575 -0
- package/site/dist/assets/bytes-CjWw8TJh.js +1 -0
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-tV1Idewc.js +1 -0
- package/site/dist/assets/ccip-BhHEqVIW.js +1 -0
- package/site/dist/assets/check-C_DTyjLS.js +1 -0
- package/site/dist/assets/chevron-down-iklXXH-z.js +1 -0
- package/site/dist/assets/circle-alert-KEOUF48T.js +1 -0
- package/site/dist/assets/circle-check-big-BJ_ADwB4.js +1 -0
- package/site/dist/assets/circle-x-BSfWmhYk.js +1 -0
- package/site/dist/assets/client-BnDcP42J.js +9 -0
- package/site/dist/assets/client-DH86IrJA.js +125 -0
- package/site/dist/assets/coinbase-DCPC2L_R-2ivYfoMv.js +3 -0
- package/site/dist/assets/copy-sK00DOfH.js +1 -0
- package/site/dist/assets/core-CRE213RM.js +211 -0
- package/site/dist/assets/createLucideIcon-D9OK_uxG.js +1 -0
- package/site/dist/assets/custom-V1wex-Di.js +1 -0
- package/site/dist/assets/dijkstra-COg3n3zL.js +1 -0
- package/site/dist/assets/embed.worker-BRIYO7m8.js +1 -0
- package/site/dist/assets/encodePacked-DyXYq3Cc.js +1 -0
- package/site/dist/assets/ethers-zTjbodk5-DSYOi6tU.js +1 -0
- package/site/dist/assets/external-link-Cdp8NBio.js +1 -0
- package/site/dist/assets/fallback-DiFue5Zm.js +1 -0
- package/site/dist/assets/farcaster-DPlSjvF5-DP2DLoHZ.js +1 -0
- package/site/dist/assets/features-B6BTj_BL.js +5 -0
- package/site/dist/assets/fingerprint-pattern-gFJTz5e4.js +1 -0
- package/site/dist/assets/floating-ui.react-D9ndYqYK.js +1 -0
- package/site/dist/assets/floating-ui.react-dom-CNbOgOFt.js +1 -0
- package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-CkAr4Oc0.js +1 -0
- package/site/dist/assets/getChainName-DjpPdUSc-c2urPd0g.js +1 -0
- package/site/dist/assets/getErc20Balance-jXNpFf0N-mFQ9Iqk-.js +1 -0
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-Dx5he_uG.js +1 -0
- package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-C446pzMl.js +1 -0
- package/site/dist/assets/getTransactionCount-80UHlwAV.js +1 -0
- package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-FDalV59C.js +1 -0
- package/site/dist/assets/hashTypedData-Beq57Ogb.js +22 -0
- package/site/dist/assets/hooks.module-ClpaQ1Tg.js +1 -0
- package/site/dist/assets/hourglass-CB7z_I9H.js +1 -0
- package/site/dist/assets/http-BocEfKYb.js +19 -0
- package/site/dist/assets/index-0knDOgU9.js +1 -0
- package/site/dist/assets/index-9jNeS_nC.js +1 -0
- package/site/dist/assets/index-B--T-Fmt.js +16 -0
- package/site/dist/assets/index-BDkarAkX.js +2 -0
- package/site/dist/assets/index-BMfMy017.js +679 -0
- package/site/dist/assets/index-BOd8jv8-.js +1 -0
- package/site/dist/assets/index-BdEw5wyl-2VGMzHIs.js +1 -0
- package/site/dist/assets/index-C8ThRelw.js +9 -0
- package/site/dist/assets/{index-CWARkn2w-Cezl_uBr.js → index-CWARkn2w-D6ikWJWj.js} +7 -7
- package/site/dist/assets/{wui-ux-by-reown-fVu0QkI4.js → index-CztIxJB6.js} +296 -296
- package/site/dist/assets/index-D4LdLFDC.js +1 -0
- package/site/dist/assets/index-D6T-s6sR.js +1 -0
- package/site/dist/assets/index-D7396TWe.js +1 -0
- package/site/dist/assets/index-DdxtHe81.js +2 -0
- package/site/dist/assets/index-DvMddJcd-D7AEQrzj.js +12 -0
- package/site/dist/assets/index-nibyPLVP.js +1 -0
- package/site/dist/assets/index.browser-B6Bt5URJ.js +1 -0
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-D7xtzRqA.js +1 -0
- package/site/dist/assets/landmark-CMLKifhk.js +1 -0
- package/site/dist/assets/lock-BkyjNE8h.js +1 -0
- package/site/dist/assets/mail-B6-Tq9Ld.js +1 -0
- package/site/dist/assets/native-CJ5et6AR.js +1 -0
- package/site/dist/assets/nonceManager-zkW7cM66.js +1 -0
- package/site/dist/assets/parseEther-C7aOvuze.js +1 -0
- package/site/dist/assets/parseSignature-BIjcuWdB.js +1 -0
- package/site/dist/assets/preload-helper-PPVm8Dsz.js +1 -0
- package/site/dist/assets/privateKeyToAccount-CRvvpHWf.js +1 -0
- package/site/dist/assets/property-CnFDNXw5.js +3 -0
- package/site/dist/assets/react-auth_false-DedQs79w.js +1 -0
- package/site/dist/assets/reservoir-x-nGuZkT-DFxAFt2v.js +1 -0
- package/site/dist/assets/roles-DV_6FRBZ.js +1 -0
- package/site/dist/assets/safe-url-D7SRPu33-A8zMgOTd.js +1 -0
- package/site/dist/assets/serializeErc6492Signature-CeJeL3h6.js +1 -0
- package/site/dist/assets/serializeTransaction-L-_Ej-jW.js +8 -0
- package/site/dist/assets/{shared-DRZUmnL5-xqAUARWo.js → shared-DRZUmnL5-MWD-C-pp.js} +33 -33
- package/site/dist/assets/{shared-FM0rljBt--Avg6q2R.js → shared-FM0rljBt-c4eSjzGj.js} +6 -6
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-CF2QteTJ.js +1 -0
- package/site/dist/assets/simulateTransaction-CfuOrae4-BHUMPf6Q.js +1 -0
- package/site/dist/assets/smartphone-DAN5rY0T.js +1 -0
- package/site/dist/assets/stripe.esm-Dv8C3q95.js +2 -0
- package/site/dist/assets/styles-BsotlekN-Cjuu2SDV.js +9 -0
- package/site/dist/assets/{styles-Cm9H63WO-D90SsXOs.js → styles-Cm9H63WO-Cv1TbqYi.js} +17 -17
- package/site/dist/assets/{styles-DVyDvTdj-BTABn2K8.js → styles-DVyDvTdj-CI4xZNHp.js} +8 -8
- package/site/dist/assets/{styles-hslXy6xG-D-49b1rk.js → styles-hslXy6xG-DeVcFoDD.js} +23 -23
- package/site/dist/assets/telegram-B-JqnkqZ-DTjJLkIF.js +1 -0
- package/site/dist/assets/to-ui-error-DWe-qwS3-Dx3yzcV0.js +4 -0
- package/site/dist/assets/toAccount-Bq6rbpbI.js +1 -0
- package/site/dist/assets/transaction-BNTP-bFm-CrebgH-D.js +1 -0
- package/site/dist/assets/transactionReceipt-BwJl-gjg.js +1 -0
- package/site/dist/assets/transfer-6YztDh-t-DU6hH7CZ.js +1 -0
- package/site/dist/assets/transformers.web-BEKVh7pD.js +40 -0
- package/site/dist/assets/transformers.web-aWa49tv1.js +40 -0
- package/site/dist/assets/triangle-alert-ndoiBs4y.js +1 -0
- package/site/dist/assets/twitch-5IOe4sIQ-BXviIywg.js +1 -0
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-C7axYJ--.js +1 -0
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-CR4CrguU.js +1 -0
- package/site/dist/assets/useI18n-C-5n_noF-C_t8QqBJ.js +1 -0
- package/site/dist/assets/useWalletBalance-CZnP5f_f-BnSHLVI_.js +1 -0
- package/site/dist/assets/w3m-modal-Bz5KWTQ-.js +1174 -0
- package/site/dist/assets/wallet-DTM31trz.js +2 -0
- package/site/dist/assets/wallet-UESEqPei.js +1 -0
- package/site/dist/assets/wallet-connect-BvJtUsDt-CnvUnFbz.js +1 -0
- package/site/dist/assets/walletconnect-v2-3frQFwbg-CzQk99gS.js +15 -0
- package/site/dist/assets/x-B2_XdOVQ.js +1 -0
- package/site/dist/index.html +120 -8
- package/site/feed.js +16 -1
- package/site/no-model.js +4 -0
- package/site/worker.js +597 -58
- package/src/agent/agent-card.js +20 -11
- package/src/agent/mcp.js +150 -316
- package/src/agent/view-tools.js +53 -12
- package/src/agent/x402.js +379 -44
- package/src/core/embed.js +51 -52
- package/src/core/embed.worker.js +1 -1
- package/src/core/shard.js +7 -3
- package/src/discover/apps.js +4 -3
- package/src/discover/explore.js +36 -22
- package/src/market/reactions.js +2 -2
- package/src/publish/devnet.js +95 -0
- package/src/publish/lint.js +2 -1
- package/src/publish/ship.js +517 -236
- package/src/publish/view-cli.js +11 -10
- package/src/publish/view.js +359 -206
- package/src/taste/taste-doc.js +3 -2
- package/src/taste/taste.js +2 -2
- package/src/taste/vault.js +3 -1
- package/site/dist/assets/AccountNotFoundScreen-C4njXc69-BOfmiSH1.js +0 -1
- package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-B6uvYyqa.js +0 -31
- package/site/dist/assets/Address-H8kD1d9O-DcHv7A8C.js +0 -17
- package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-v2wz-N-s.js +0 -6
- package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-DhVy9iGN.js +0 -1
- package/site/dist/assets/ApiController-BbhXeqTV.js +0 -5
- package/site/dist/assets/ArrowsRightLeftIcon-cp60yobq.js +0 -1
- package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-B0eT1zE6.js +0 -1
- package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-DL5PH-v_.js +0 -1
- package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-n1BsRPqc.js +0 -1
- package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-BVl2BJDY.js +0 -103
- package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-BD-OZUQb.js +0 -1
- package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-CaYrzRje.js +0 -68
- package/site/dist/assets/CaptchaScreen-DbpvkIGx-BQJ7inx1.js +0 -1
- package/site/dist/assets/CheckCircleIcon-BM409mGI.js +0 -1
- package/site/dist/assets/CheckCircleIcon-NCOQc0Gs.js +0 -1
- package/site/dist/assets/Checkbox-D1EDeo41-rHlvsZzP.js +0 -52
- package/site/dist/assets/ChevronDownIcon-CW-4MSpE.js +0 -1
- package/site/dist/assets/Chip-CZKIKt9K-CutJG010.js +0 -26
- package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DDaMC7cl.js +0 -16
- package/site/dist/assets/ConnectEmailForm-B-VRE0MN-DVSf4tFJ.js +0 -3
- package/site/dist/assets/ConnectLedgerScreen-DWHABGot-h0uYIUi6.js +0 -10
- package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-DP19hJq5.js +0 -1
- package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-DHvijlTx.js +0 -1
- package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-ewmXYPTQ.js +0 -1
- package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-DUYdmVtr.js +0 -95
- package/site/dist/assets/ConnectWalletView-DqI-PTvX-RobGErVW.js +0 -74
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Bbiq3hY6.js +0 -1
- package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-DPkOGNWl.js +0 -7
- package/site/dist/assets/ConnectorUtil-BO1L6VvV.js +0 -21
- package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
- package/site/dist/assets/CopyToClipboard-i_OQSBJr-DujUHNzW.js +0 -39
- package/site/dist/assets/CopyableText-CQapvaMr-DMIdpDaU.js +0 -28
- package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-DbGy4QyA.js +0 -1
- package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-B3WqJZBh.js +0 -22
- package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-CmUDoS0C.js +0 -1
- package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-fcO51q5H.js +0 -1
- package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-ClAb_j1B.js +0 -187
- package/site/dist/assets/DepositAddressScreen-DBLRs7aI-CN1P_KY9.js +0 -8
- package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-BtDZmuEN.js +0 -1
- package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-D3pExuaW.js +0 -1
- package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-CLsJbPYQ.js +0 -1
- package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-DI6bu-oi.js +0 -23
- package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-BQ9Ygr0P.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-CxrfLr5q.js +0 -1
- package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-DHZRGoTN.js +0 -1
- package/site/dist/assets/EnrollTotp-Dibew4dp-7gVtJ1Jt.js +0 -66
- package/site/dist/assets/EnvelopeIcon-Dh2vSVj4.js +0 -1
- package/site/dist/assets/ErrorBanner-BcpGRt0h-BKOYTyCi.js +0 -21
- package/site/dist/assets/ErrorScreen-DipdTWzE-BT8TtRTx.js +0 -4
- package/site/dist/assets/ErrorScreen-DipdTWzE-DAam41Xl.js +0 -1
- package/site/dist/assets/ExclamationCircleIcon-DUA2wMfO.js +0 -1
- package/site/dist/assets/ExclamationTriangleIcon-eZfaqcFq.js +0 -1
- package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-BPCBi37Q.js +0 -75
- package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-XCmWpnUd.js +0 -28
- package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-DJRcUh2N.js +0 -491
- package/site/dist/assets/FingerPrintIcon-Dm6kxmg7.js +0 -1
- package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-BfNVSTb1.js +0 -1
- package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-BQdbJ5Bb.js +0 -1
- package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-BfvMcHb6.js +0 -58
- package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-VBfbahnA.js +0 -1
- package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-CMuBYf64.js +0 -1
- package/site/dist/assets/GlobeAltIcon-vqrVV5ho.js +0 -1
- package/site/dist/assets/GooglePay-B53WnudL-BX5_WtCx.js +0 -1
- package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-D-I8j7NO.js +0 -4
- package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-BY3j50El.js +0 -1
- package/site/dist/assets/InfoBanner-Cb3p1z12-DLBl_Ywy.js +0 -22
- package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-DLSV_NjP.js +0 -1
- package/site/dist/assets/InstallWalletScreen-DTA8dwLv-CxreyXoD.js +0 -1
- package/site/dist/assets/LandingScreen-CGWUoSUG-Q7hgXKvM.js +0 -1
- package/site/dist/assets/Link-BdDilT2T-BEDEdHuS.js +0 -40
- package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-DK1z_v7y.js +0 -77
- package/site/dist/assets/LinkEmailScreen-dKo9frwc-BYYpJ5-6.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uhub9C26.js +0 -1
- package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-uid22VTN.js +0 -117
- package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-CVQibNi9.js +0 -1
- package/site/dist/assets/Loader-UAZqxsnw-Cy7hEE2f.js +0 -62
- package/site/dist/assets/LockClosedIcon-BgY_5v_Z.js +0 -1
- package/site/dist/assets/LoginFailedScreen-CN0dJV6U-DhUMiJZJ.js +0 -1
- package/site/dist/assets/ManualTransferScreen-CHqcuqbs-DMygAXMU.js +0 -1
- package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-_1GFUSIg.js +0 -1
- package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-DLnBJ5b8.js +0 -1
- package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-BUJY1lzs.js +0 -1
- package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-25l5fVun.js +0 -1
- package/site/dist/assets/ModalFooter-BCuEK_nz-c-khvG4Y.js +0 -403
- package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-BRFN2eXr.js +0 -16
- package/site/dist/assets/OAuthStatusScreen-ufbOa-da-DL30vu07.js +0 -1
- package/site/dist/assets/OpenLink-CUpJ1mOr-DfAvJRyK.js +0 -39
- package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-DtxhRQyw.js +0 -1
- package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-CNfu8ujT.js +0 -6
- package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-BR6z6xE8.js +0 -20
- package/site/dist/assets/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/PhArrowUp-4sgyKPGx.js +0 -14
- package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
- package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.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/PhClock-x3MS3Mwi.js +0 -14
- package/site/dist/assets/PhCopy-CWNYcABI.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/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
- package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
- package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
- package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
- package/site/dist/assets/PhoneIcon-CvIiPwYQ.js +0 -1
- package/site/dist/assets/PinInput-DbZ0b1i1-CDXZBdQx.js +0 -247
- package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CtQWBiV2.js +0 -1
- package/site/dist/assets/QrCode-DbnZwlkH-BymHrKY7.js +0 -22
- package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-Dro1btv5.js +0 -1
- package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-C1q1hiaN.js +0 -1
- package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-C21D20gD.js +0 -7
- package/site/dist/assets/SIWXUtil-CHZVugix.js +0 -1
- package/site/dist/assets/ScreenLayout-OwaQQcxH-CrQM6kvA.js +0 -1
- package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-BcWHJoEG.js +0 -14
- package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-kq1zvA7d.js +0 -11
- package/site/dist/assets/ShieldCheckIcon-o6RHYev1.js +0 -1
- package/site/dist/assets/SignRequestScreen-BHKv32KR-iQCaBkcz.js +0 -26
- package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-BUdJJ0ep.js +0 -1
- package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-nART-xUU.js +0 -20
- package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Dw9geyK3.js +0 -1
- package/site/dist/assets/TodoList-DnyULl18-C8Eyy28X.js +0 -36
- package/site/dist/assets/TransactionDetails-e9nesl30-DeEL1JQS.js +0 -109
- package/site/dist/assets/TransactionErrorView-BDBNqA_f-Dffuo7I4.js +0 -178
- package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-CWLWUiHy.js +0 -31
- package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-Bo-tm6wr.js +0 -6
- package/site/dist/assets/TurnstileWrapper-B7sNddys-Jwy1tJtm.js +0 -1
- package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-gFbSIUhh.js +0 -1
- package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-Coy4E1NQ.js +0 -1
- package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-D7m70C4b.js +0 -1
- package/site/dist/assets/WalletCards-DH1rqayz-Bgp9Fsgs.js +0 -1
- package/site/dist/assets/WalletIcon-gKf0gTw6.js +0 -1
- package/site/dist/assets/WalletInfoCard-sq_2wgFg-ISBsQt8p.js +0 -29
- package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DG-RuO-F.js +0 -1
- package/site/dist/assets/WalletLink-DK5LB5Ha-CXBqCHf3.js +0 -20
- package/site/dist/assets/WalletOverflowButton-bfCnp-9T-DP0-PoZL.js +0 -1
- package/site/dist/assets/WarningBanner-ZZqCEtZK-2eU4Qo74.js +0 -21
- package/site/dist/assets/__vite-browser-external-CitKSmYk.js +0 -1
- package/site/dist/assets/_esm-CJe2F7LL.js +0 -7
- package/site/dist/assets/_esm-zhBBffjV.js +0 -1
- package/site/dist/assets/accounts-Bq8t1wAD.js +0 -1
- package/site/dist/assets/accounts-D96UKWkC.js +0 -1
- package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
- package/site/dist/assets/baseSepolia-DX-qGsik.js +0 -1
- package/site/dist/assets/basic-MlZ7f4s9.js +0 -1575
- package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
- package/site/dist/assets/ccip-BI9LWBBg.js +0 -1
- package/site/dist/assets/ccip-C-59SNEF.js +0 -7
- package/site/dist/assets/ccip-y08wK9du.js +0 -1
- package/site/dist/assets/chain-DM3oH6Px.js +0 -8
- package/site/dist/assets/check-BPgcwEPP.js +0 -1
- package/site/dist/assets/chevron-down-BSsbXkK-.js +0 -1
- package/site/dist/assets/circle-alert-DWaeLZNj.js +0 -1
- package/site/dist/assets/circle-check-big-Chb1ivw9.js +0 -1
- package/site/dist/assets/circle-x-AV9Cet0R.js +0 -1
- package/site/dist/assets/client-D0IV9lV9.js +0 -2
- package/site/dist/assets/client-Dsa7_3V3.js +0 -9
- package/site/dist/assets/coinbase-DCPC2L_R-Rs9nVCfm.js +0 -3
- package/site/dist/assets/copy-Ckr0AkL6.js +0 -1
- package/site/dist/assets/core-zQ1tZtVZ.js +0 -12
- package/site/dist/assets/createLucideIcon-IPW50ckY.js +0 -1
- package/site/dist/assets/defineChain-CiuCXUZW.js +0 -1
- package/site/dist/assets/dijkstra-Drfl93V4.js +0 -1
- package/site/dist/assets/dist-BxQqwnue.js +0 -16
- package/site/dist/assets/dist-Cm7pNAtL.js +0 -1
- package/site/dist/assets/dist-CoS6E-3x.js +0 -1
- package/site/dist/assets/dist-Dl07uGq1.js +0 -1
- package/site/dist/assets/embed.worker-ugGhEqCV.js +0 -1
- package/site/dist/assets/errors-BQ3N-RVt-BLqjTPNR.js +0 -1
- package/site/dist/assets/esm-C3DslVJx.js +0 -1
- package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
- package/site/dist/assets/esm-DXsETz3H.js +0 -1
- package/site/dist/assets/esm-n2lWo7kz.js +0 -1
- package/site/dist/assets/esm-vs5jNx_Y.js +0 -37
- package/site/dist/assets/ethers-zTjbodk5-B7BBFFKf.js +0 -1
- package/site/dist/assets/eventemitter3-CL76jPsW.js +0 -1
- package/site/dist/assets/external-link-D-uehwM2.js +0 -1
- package/site/dist/assets/farcaster-DPlSjvF5-BqTvDXBR.js +0 -1
- package/site/dist/assets/features-CYXRa3_m.js +0 -2
- package/site/dist/assets/fingerprint-pattern-D8ytA16Q.js +0 -1
- package/site/dist/assets/floating-ui.react-dom-DKWOL9UG.js +0 -1
- package/site/dist/assets/floating-ui.react-xRkl9O3u.js +0 -1
- package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
- package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
- package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-Dukf7_kJ.js +0 -1
- package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
- package/site/dist/assets/getErc20Balance-jXNpFf0N-V2btxzrJ.js +0 -1
- package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-CtCwQuGA.js +0 -1
- package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
- package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
- package/site/dist/assets/guards-BIW-GiMJ-CHfXo8XI.js +0 -1
- package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
- package/site/dist/assets/hourglass-CPG1HZmf.js +0 -1
- package/site/dist/assets/id-UqEwL2BI-CdKXapd-.js +0 -1
- package/site/dist/assets/index-BdEw5wyl-ChSJvPZk.js +0 -1
- package/site/dist/assets/index-CKdAF_dK.js +0 -8
- package/site/dist/assets/index-DvMddJcd-D-4mRLKK.js +0 -12
- package/site/dist/assets/index-YiCw0Y_x-BX1zsiqG.js +0 -437
- package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
- package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
- package/site/dist/assets/index.es-BmqyoBEM.js +0 -2
- package/site/dist/assets/index.es-C1v1or--.js +0 -3
- package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-By6r0dVB.js +0 -1
- package/site/dist/assets/jsx-runtime-BbDfbRii.js +0 -1
- package/site/dist/assets/landmark-CH6LV6F0.js +0 -1
- package/site/dist/assets/lib-BsS8z8Lj.js +0 -1
- package/site/dist/assets/localBatchGatewayRequest-5YxrERBj.js +0 -32
- package/site/dist/assets/lock-DcskI4Bb.js +0 -1
- package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
- package/site/dist/assets/mail-Bs0r5yOi.js +0 -1
- package/site/dist/assets/modal-context-CwmSeFcv-DruX9J1E.js +0 -1
- package/site/dist/assets/native-BKj27wfc.js +0 -1
- package/site/dist/assets/ofetch.CWycOUEr-CHp-crOQ.js +0 -1
- package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
- package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
- package/site/dist/assets/parseEther-CGP3gnf2.js +0 -1
- package/site/dist/assets/pkce-DXF0yKjP-Bqz4wAsq.js +0 -1
- package/site/dist/assets/preload-helper-BaNbYf_w.js +0 -1
- package/site/dist/assets/privy-context-81KmsBw2-CjWGzyg7.js +0 -2
- package/site/dist/assets/property-DuZ9DLP4.js +0 -3
- package/site/dist/assets/react-DCXkXSrv.js +0 -1
- package/site/dist/assets/react-DQnMl2Xv.js +0 -1
- package/site/dist/assets/react-DrJ0MmMO.js +0 -1
- package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
- package/site/dist/assets/react-dom-5dW7aKoP.js +0 -1
- package/site/dist/assets/reservoir-x-nGuZkT-BMX127i6.js +0 -1
- package/site/dist/assets/roles-32M1l9jI.js +0 -1
- package/site/dist/assets/roles-DKPW79Ca.js +0 -1
- package/site/dist/assets/rolldown-runtime-BcKkbAw3.js +0 -1
- package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
- package/site/dist/assets/secp256k1-C4nzcPo-.js +0 -1
- package/site/dist/assets/secp256k1-D92aGNnU.js +0 -1
- package/site/dist/assets/secp256k1-DmbwyFau.js +0 -1
- package/site/dist/assets/secp256k1-_MleGpqL.js +0 -1
- package/site/dist/assets/secp256k1-xl2og-wO.js +0 -1
- package/site/dist/assets/serializeAuthorizationList-CX8hGL56.js +0 -1
- package/site/dist/assets/serializeSignature-DfxkPnu-.js +0 -2
- package/site/dist/assets/sha2-BU_QyRTx.js +0 -1
- package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-ByFOTrQK.js +0 -1
- package/site/dist/assets/simulateTransaction-CfuOrae4-CMFGDgT_.js +0 -1
- package/site/dist/assets/smartphone-CcEZ-XAy.js +0 -1
- package/site/dist/assets/src-BBjkfi-Q.js +0 -1
- package/site/dist/assets/storage-Ufl800Ne-EalZTH9q.js +0 -9
- package/site/dist/assets/stringify-BlDm7tuc.js +0 -21
- package/site/dist/assets/stripe.esm-CZRDpc-N.js +0 -2
- package/site/dist/assets/styles-BsotlekN-CXEB4Ud9.js +0 -9
- package/site/dist/assets/telegram-B-JqnkqZ-RBoY3PLx.js +0 -1
- package/site/dist/assets/to-ui-error-DWe-qwS3-DgCO-Y70.js +0 -4
- package/site/dist/assets/toViemAccount-B5iDGq1b-DZ1MBqUW.js +0 -2
- package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
- package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
- package/site/dist/assets/transformers.web-D4RvOP4-.js +0 -40
- package/site/dist/assets/transformers.web-D8W1u_I2.js +0 -49
- package/site/dist/assets/triangle-alert-CWjYH75H.js +0 -1
- package/site/dist/assets/twitch-5IOe4sIQ-lDtgk0os.js +0 -1
- package/site/dist/assets/types-4CuOD23i.js +0 -1
- package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-Dn6_8w-Y.js +0 -1
- package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-DgsQ75lh.js +0 -1
- package/site/dist/assets/useActiveWallet-B-yZYGc6-B6JWREbk.js +0 -123
- package/site/dist/assets/useGetSolPrice-x7gfUIHJ-D1hmQfpA.js +0 -1
- package/site/dist/assets/useGetTokenPrice-CaoUkb79-ZnbuJ-vP.js +0 -1
- package/site/dist/assets/useI18n-C-5n_noF-BcqC76yj.js +0 -1
- package/site/dist/assets/useWalletBalance-CZnP5f_f-L3V0jDjL.js +0 -1
- package/site/dist/assets/useWallets-X1ndvJ_U-CLwUcevw.js +0 -1
- package/site/dist/assets/utils-BkR7EQjg.js +0 -1
- package/site/dist/assets/utils-DJhBNOZA.js +0 -1
- package/site/dist/assets/utils-DdckVfQr.js +0 -1
- package/site/dist/assets/w3m-modal-BMubXAoG.js +0 -1174
- package/site/dist/assets/wallet-Bn85Sg2W.js +0 -1
- package/site/dist/assets/wallet-connect-BvJtUsDt-Y03hhqFN.js +0 -1
- package/site/dist/assets/wallet-uSKqQsgP.js +0 -2
- package/site/dist/assets/walletconnect-registry-xxG8XLCb-D1SSFvMp.js +0 -1
- package/site/dist/assets/walletconnect-v2-3frQFwbg-Dv6JY_ko.js +0 -12
- package/site/dist/assets/x-rlSp9U7W.js +0 -1
package/src/publish/ship.js
CHANGED
|
@@ -1,42 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// westmarch-ship: one config, one command, a live Fangorn app.
|
|
2
|
+
// westmarch-ship: one config, one command, a live Fangorn app on the free tier.
|
|
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: claim,
|
|
6
|
+
// westmarch-ship app.json --register the app alone: claim, empty site, card bound
|
|
7
|
+
// westmarch-ship app.json --devnet [--only …] the whole app on this machine (see devnetShip)
|
|
8
|
+
// westmarch-ship app.json --index the deployed app's indexer, once, here (see indexHere)
|
|
7
9
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
+
// The app is one Cloudflare Worker (site/worker.js): its indexer (a cron that runs the
|
|
11
|
+
// embedding lens over everything the app presents, into R2), its view, its MCP endpoint
|
|
12
|
+
// (/mcp) and its site. Nothing is embedded here. Every step checks the chain (or
|
|
13
|
+
// Cloudflare) first and only acts on a difference, so re-running is how you update.
|
|
10
14
|
//
|
|
11
|
-
// 1. claim the app (first run only; the claim block becomes the card's fromBlock)
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// 8. register the ERC-8004 agent and bind the card, if the chain points elsewhere
|
|
15
|
+
// 1. claim the app (first run only; the claim block becomes the card's fromBlock).
|
|
16
|
+
// The claimer is the app's first member, so its own updates are accepted.
|
|
17
|
+
// 2. commit the schema, if the one on chain differs from app.json's `types`
|
|
18
|
+
// 3. run each source, which crawls and publishes straight into the app
|
|
19
|
+
// 4. the stock page, the card and _headers → .ship/site
|
|
20
|
+
// 5. deploy the Worker: its R2 bucket, Workers AI, the cron, CONFIG
|
|
21
|
+
// 6. register the ERC-8004 agent and bind the card, if the chain points elsewhere
|
|
19
22
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
23
|
+
// The Worker picks up what 2 and 3 published on its next cron run (every few minutes),
|
|
24
|
+
// so the view follows the chain with nobody running anything.
|
|
25
|
+
//
|
|
26
|
+
// --register is the fast path, run before there is any data: 1, 4, 5 and 6 binding the card
|
|
27
|
+
// without minting. The mint copies the card's skills into its IPFS registration file and is
|
|
28
|
+
// never redone, so it waits for the first full ship, when the card lists what the app does.
|
|
24
29
|
//
|
|
25
30
|
// Chain writes go through the `fangorn` CLI (its configured wallet signs, and
|
|
26
31
|
// `fangorn wallet` shows which). Reads go through the SDK. State lives in `.ship/` next
|
|
27
|
-
// to app.json: the site, the
|
|
32
|
+
// to app.json: the site, the Worker's config and a small state.json.
|
|
28
33
|
|
|
29
|
-
import { spawnSync } from "node:child_process";
|
|
30
|
-
import { createHash } from "node:crypto";
|
|
34
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
35
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
31
36
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
32
37
|
import { createRequire } from "node:module";
|
|
33
38
|
import { dirname, join, resolve } from "node:path";
|
|
34
39
|
import { homedir } from "node:os";
|
|
35
40
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
41
|
+
import { RELATION_TAG, TYPE_TAG } from "./view.js";
|
|
36
42
|
|
|
37
43
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
// USDC per x402 network (CAIP-2): its address and EIP-712 domain, as each contract reports them.
|
|
45
|
+
// They differ: Base Sepolia's is "USDC", Base's "USD Coin", and a payment signed under the wrong
|
|
46
|
+
// one fails at the facilitator.
|
|
47
|
+
const USDC = {
|
|
48
|
+
"eip155:84532": { asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e", name: "USDC", version: "2" },
|
|
49
|
+
"eip155:8453": { asset: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", name: "USD Coin", version: "2" },
|
|
50
|
+
"eip155:421614": { asset: "0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d", name: "USD Coin", version: "2" },
|
|
51
|
+
"eip155:42161": { asset: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", name: "USD Coin", version: "2" },
|
|
52
|
+
};
|
|
53
|
+
const X402_NETWORK = { "base-sepolia": "eip155:84532", base: "eip155:8453", "arbitrum-sepolia": "eip155:421614", arbitrum: "eip155:42161" };
|
|
54
|
+
|
|
55
|
+
/** How a sale settles, from `paid` or `private` in app.json: the network (`network`, else the
|
|
56
|
+
* app's own), the token and its EIP-712 domain, the payee (`payTo`, else the owner) and the facilitator. */
|
|
57
|
+
export function settlement(o, { network: own, owner }) {
|
|
58
|
+
const network = o.network ? X402_NETWORK[o.network] ?? o.network : own;
|
|
59
|
+
const usdc = o.asset ? {} : USDC[network];
|
|
60
|
+
if (!usdc) throw new Error(`network ${network}: no default USDC; set asset, tokenName and tokenVersion`);
|
|
61
|
+
const name = o.tokenName ?? usdc.name, version = o.tokenVersion ?? usdc.version;
|
|
62
|
+
if (!name || !version) throw new Error("asset needs tokenName and tokenVersion: its EIP-712 domain");
|
|
63
|
+
return { network, asset: o.asset ?? usdc.asset, name, version, decimals: o.decimals ?? 6, symbol: o.symbol ?? "USDC",
|
|
64
|
+
payTo: o.payTo ?? owner, facilitator: (o.facilitator ?? "https://facilitator.payai.network").replace(/\/$/, "") };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** "0.01" → "10000": a decimal price in the token's base units. */
|
|
68
|
+
export function units(price, decimals = 6) {
|
|
69
|
+
if (!/^\d+(\.\d+)?$/.test(String(price))) throw new Error(`price must be a decimal like "0.01", got ${price}`);
|
|
70
|
+
const [whole, frac = ""] = String(price).split(".");
|
|
71
|
+
return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt((frac + "0".repeat(decimals)).slice(0, decimals) || "0")).toString();
|
|
72
|
+
}
|
|
40
73
|
const STOCK = join(HERE, "../../site/dist");
|
|
41
74
|
const log = (s) => console.error(`[ship] ${s}`);
|
|
42
75
|
|
|
@@ -44,27 +77,46 @@ const log = (s) => console.error(`[ship] ${s}`);
|
|
|
44
77
|
//
|
|
45
78
|
// {
|
|
46
79
|
// "app": "quorum", "name": "Quorum", "description": "…", "tags": ["civic"],
|
|
47
|
-
// "site": { "project": "quorum",
|
|
80
|
+
// "site": { "project": "quorum", // the Worker's name (and its R2 bucket's: <project>-view)
|
|
81
|
+
// "account": "<cloudflare account id>",
|
|
82
|
+
// "url": "https://quorum.example", // optional: a custom domain routed to the Worker
|
|
83
|
+
// "cron": "*/5 * * * *", // optional: how often the indexer runs
|
|
48
84
|
// "pages": "pages", // optional: the app's own files, copied over the stock page
|
|
49
85
|
// // (pages/intro.html, a fragment, replaces the description on the front page)
|
|
50
86
|
// "nav": [{ "href": "coverage.html", "label": "Coverage" }], // optional: header links to them
|
|
87
|
+
// "examples": ["…"], // optional: questions to start from, on the front page (and a private app's gate)
|
|
51
88
|
// "agent": "app/agent.js" }, // optional: the module whose registerAgent the card lists, when
|
|
52
89
|
// // `pages` replaces the stock index.html with a page of its own
|
|
53
90
|
// "types": { "<tag>": { "description": "…", "role_map": {…}, "presentation": {…} } },
|
|
54
91
|
// "relations": [{ "rel": "…", "from": "<tag>", "to": "<tag>" }],
|
|
55
92
|
// "sources": [{ "namespace": "…", "cwd": "…", "command": ["python", "-m", "…", …] }],
|
|
56
|
-
// "
|
|
57
|
-
//
|
|
93
|
+
// "network": "baseSepolia", // required: the SDK's network name. baseSepolia for a new app; an app
|
|
94
|
+
// // already claimed on arbitrumSepolia keeps it (a different name re-claims it there)
|
|
95
|
+
// "fangorn": "fangorn", // the CLI that signs; e.g. "node ~/fangorn/fangorn/lib/cli/cli.js",
|
|
96
|
+
// // or "env HOME=<dir> node …/cli.js" to sign with <dir>/.fangorn/config.json
|
|
97
|
+
// "paid": { "price": "0.01", "description": "…" } // settles in USDC on the app's network, or "network": "base" / "eip155:8453"…
|
|
58
98
|
// }
|
|
59
99
|
//
|
|
60
|
-
// `paid` sells per-record
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
// `
|
|
100
|
+
// `paid` sells per-record keys over x402 v2 (site/worker.js), and the price goes in the card,
|
|
101
|
+
// /openapi.json and /.well-known/x402. The payee is the app owner unless `paid.payTo` says
|
|
102
|
+
// otherwise; the asset is USDC on `paid.network` unless `paid.asset` (with `tokenName` and
|
|
103
|
+
// `tokenVersion`, its EIP-712 domain) does. PAID_SECRET is a Worker secret
|
|
104
|
+
// (`wrangler secret put PAID_SECRET`), the same value the sources encrypt with.
|
|
105
|
+
// The facilitator (PayAI unless `paid.facilitator`) lists POST /paid in its Bazaar on the first
|
|
106
|
+
// sale; each ship says whether it has.
|
|
107
|
+
//
|
|
108
|
+
// "private": { "pass": { "price": "5", "days": 30 }, "call": "0.01", "description": "…" }
|
|
109
|
+
//
|
|
110
|
+
// `private` makes the data itself the product. Each source stages its records (it needs an
|
|
111
|
+
// --output-dir), ship seals every row whole (x402.js's sealRow) and commits them, so IPFS holds
|
|
112
|
+
// only ciphertext; the Worker opens them in its cron with PAID_SECRET (made once in
|
|
113
|
+
// .ship/secret, or $PAID_SECRET, and put to the Worker on every deploy) and serves the view and
|
|
114
|
+
// the MCP tools only with a pass (POST /pass, x402) or one x402 payment per tool call. The card,
|
|
115
|
+
// the catalog and `describe` stay free. Settles like `paid` (network, payTo, asset, facilitator).
|
|
64
116
|
//
|
|
65
|
-
// A source is any command that publishes into `--namespace` when given
|
|
66
|
-
//
|
|
67
|
-
//
|
|
117
|
+
// A source is any command that publishes into the repo `--namespace` names when given
|
|
118
|
+
// `--publish`. It runs with FANGORN_APP_ID set, so it uploads through this app's storage
|
|
119
|
+
// whatever the CLI's saved app is.
|
|
68
120
|
|
|
69
121
|
/** `{field}`, `{field|slug}` and `{field|lower}` from a row; an argv item that is exactly
|
|
70
122
|
* `{args...}` splices the row's own `args` array (for flags only some rows need). */
|
|
@@ -93,20 +145,39 @@ export function loadConfig(path) {
|
|
|
93
145
|
c.sources = expandSources(c.sources ?? [], dirname(resolve(path)));
|
|
94
146
|
for (const k of ["app", "name", "description", "site"]) // types and sources can come after --register
|
|
95
147
|
if (!c[k]) throw new Error(`${path}: missing "${k}"`);
|
|
96
|
-
if (!c.site.project) throw new Error(`${path}: site.project names the Cloudflare
|
|
148
|
+
if (!c.site.project) throw new Error(`${path}: site.project names the Cloudflare Worker`);
|
|
97
149
|
for (const s of c.sources) if (!s.namespace || !Array.isArray(s.command))
|
|
98
150
|
throw new Error(`${path}: each source needs a namespace and a command (an argv array)`);
|
|
99
151
|
return { relations: [], tags: [], types: {}, ...c };
|
|
100
152
|
}
|
|
101
153
|
|
|
102
|
-
/** The schema commit for `types` + `relations`: one fangorn.type.v1
|
|
103
|
-
*
|
|
104
|
-
export function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
vertices: Object.entries(types).map(([tag, t]) => ({ id: id(tag), tag: "fangorn.type.v1", payload: {
|
|
154
|
+
/** The schema commit for `types` + `relations`: one fangorn.type.v1 record per type, one
|
|
155
|
+
* fangorn.relation.v1 record per allowed reference. A file for `fangorn commit --replace`. */
|
|
156
|
+
export function schemaRecords(types, relations = []) {
|
|
157
|
+
return { records: [
|
|
158
|
+
...Object.entries(types).map(([tag, t]) => ({ id: `type:${tag}`, type: TYPE_TAG, payload: {
|
|
108
159
|
tag, description: t.description ?? "", role_map: t.role_map ?? {}, presentation: t.presentation ?? {} } })),
|
|
109
|
-
|
|
160
|
+
...relations.map((r) => ({ id: `rel:${r.rel}:${r.from}:${r.to}`, type: RELATION_TAG, payload: { rel: r.rel, from: r.from, to: r.to } })),
|
|
161
|
+
] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The Worker's wrangler config: the stock worker, the site as its assets, its bucket, the AI
|
|
165
|
+
* binding, the cron and CONFIG. `worker` is site/worker.js unless a test points elsewhere. */
|
|
166
|
+
export function wranglerConfig({ name, site, account, cron = "*/5 * * * *", config, url, ipfsGateway, worker = join(HERE, "../../site/worker.js") }) {
|
|
167
|
+
return {
|
|
168
|
+
name, main: worker, compatibility_date: "2025-09-15", compatibility_flags: ["nodejs_compat"],
|
|
169
|
+
...(account ? { account_id: account } : {}),
|
|
170
|
+
assets: { directory: site, binding: "ASSETS" },
|
|
171
|
+
r2_buckets: [{ binding: "VIEW", bucket_name: `${name}-view` }],
|
|
172
|
+
ai: { binding: "AI" },
|
|
173
|
+
triggers: { crons: [cron] },
|
|
174
|
+
// ipfs.io answers a Worker's reads with 429s; the owner's gateway serves the owner's pins.
|
|
175
|
+
// ponytail: a dedicated Pinata gateway serves only its account's pins unless opened up;
|
|
176
|
+
// an app presenting other publishers' repos needs an open gateway here.
|
|
177
|
+
vars: { CONFIG: config, ...(ipfsGateway ? { IPFS_GATEWAY: ipfsGateway } : {}) },
|
|
178
|
+
// The Worker embeds with its AI binding; the in-process model is ~1.5 MB of bundle it never runs.
|
|
179
|
+
alias: { "@huggingface/transformers": join(HERE, "../../site/no-model.js") },
|
|
180
|
+
...(url ? { routes: [{ pattern: new URL(url).hostname, custom_domain: true }] } : {}),
|
|
110
181
|
};
|
|
111
182
|
}
|
|
112
183
|
|
|
@@ -128,12 +199,13 @@ const canonical = (types, relations = []) => JSON.stringify(stable({
|
|
|
128
199
|
* and in CI (e.g. `"${PYTHON:-python3}"`). */
|
|
129
200
|
export const expandEnv = (s) => s.replace(/\$\{(\w+)(?::-([^}]*))?\}/g, (_, k, d) => process.env[k] ?? d ?? "");
|
|
130
201
|
|
|
131
|
-
function run(cmd, args, { cwd, env, capture = false, dry = false } = {}) {
|
|
202
|
+
function run(cmd, args, { cwd, env, capture = false, dry = false, input } = {}) {
|
|
132
203
|
cmd = expandEnv(cmd); args = args.map(expandEnv);
|
|
133
204
|
log(`$ ${[cmd, ...args].join(" ")}${cwd ? ` (in ${cwd})` : ""}`);
|
|
134
205
|
if (dry) return "";
|
|
135
|
-
|
|
136
|
-
|
|
206
|
+
// `input`: what the command reads on stdin (a secret: never on the command line, never logged).
|
|
207
|
+
const r = spawnSync(cmd, args, { cwd, env: { ...process.env, ...env }, encoding: "utf8", input,
|
|
208
|
+
stdio: [input != null ? "pipe" : capture ? "ignore" : "inherit", capture ? "pipe" : "inherit", "inherit"] });
|
|
137
209
|
if (r.status !== 0) throw new Error(`${cmd} ${args[0]} failed (exit ${r.status})`);
|
|
138
210
|
return r.stdout ?? "";
|
|
139
211
|
}
|
|
@@ -147,6 +219,10 @@ const fangornCli = (app, args, opts = {}) => run(CLI[0], [...CLI.slice(1), "--ap
|
|
|
147
219
|
* and every read says the app is unclaimed. (It happened: a global 2026.08.18-dev CLI
|
|
148
220
|
* claimed and published onto the previous registries.) */
|
|
149
221
|
function assertSameDeployment(cli) {
|
|
222
|
+
// "env HOME=… node cli.js": a CLI with its own config (another wallet); find the script past the env.
|
|
223
|
+
let i = cli[0] === "env" ? 1 : 0;
|
|
224
|
+
while (cli[i]?.includes("=")) i++;
|
|
225
|
+
cli = cli.slice(i);
|
|
150
226
|
const which = (c) => spawnSync("sh", ["-c", `command -v ${c}`], { encoding: "utf8" }).stdout.trim();
|
|
151
227
|
const script = cli.length > 1 && /(^|\/)node$/.test(cli[0]) ? cli[1] : which(cli[0]);
|
|
152
228
|
if (!script) throw new Error(`fangorn CLI not found: ${cli.join(" ")}`);
|
|
@@ -162,10 +238,12 @@ function assertSameDeployment(cli) {
|
|
|
162
238
|
|
|
163
239
|
// ── the steps ─────────────────────────────────────────────────────────────────
|
|
164
240
|
|
|
165
|
-
export async function ship(configPath, { crawl = true, deploy = true, dry = false, replace = false, only = null, register = false
|
|
241
|
+
export async function ship(configPath, { crawl = true, deploy = true, dry = false, replace = false, only = null, register = false } = {}) {
|
|
166
242
|
const cfg = loadConfig(configPath);
|
|
167
243
|
if (!register && (!Object.keys(cfg.types).length || !cfg.sources.length))
|
|
168
244
|
throw new Error(`${configPath}: no types or sources yet, so nothing to build; --register sets up the app alone`);
|
|
245
|
+
// No default: one that changed would claim every app that leaned on it again, elsewhere.
|
|
246
|
+
if (!cfg.network) throw new Error(`${configPath}: "network" is required: "baseSepolia" for a new app, or the network the app was claimed on (before 2026-10-20 the default was "arbitrumSepolia")`);
|
|
169
247
|
CLI = cliArgv(cfg);
|
|
170
248
|
assertSameDeployment(CLI);
|
|
171
249
|
const base = dirname(resolve(configPath));
|
|
@@ -174,18 +252,27 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
174
252
|
mkdirSync(dir, { recursive: true });
|
|
175
253
|
const state = existsSync(statePath) ? JSON.parse(readFileSync(statePath, "utf8")) : {};
|
|
176
254
|
const save = () => !dry && writeFileSync(statePath, JSON.stringify(state, null, 1));
|
|
255
|
+
// A private app's PAID_SECRET: $PAID_SECRET, else made once in .ship/secret. Losing it loses
|
|
256
|
+
// every sealed row, so it is never made again over one that exists.
|
|
257
|
+
const secretPath = join(dir, "secret");
|
|
258
|
+
const secret = () => {
|
|
259
|
+
if (process.env.PAID_SECRET) return process.env.PAID_SECRET;
|
|
260
|
+
if (!existsSync(secretPath)) writeFileSync(secretPath, randomBytes(32).toString("hex"), { mode: 0o600 });
|
|
261
|
+
return readFileSync(secretPath, "utf8").trim();
|
|
262
|
+
};
|
|
177
263
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
env.cacheDir = `${homedir()}/.cache/fangorn-mcp/models`;
|
|
264
|
+
// The SDK's network name: view.js reads with it, and the CLI and the sources spawned below sign on it.
|
|
265
|
+
process.env.FANGORN_NETWORK = process.env.CHAIN_NAME = cfg.network;
|
|
266
|
+
const [{ createPublicClient, http }, view] = await Promise.all([import("viem"), import("./view.js")]);
|
|
267
|
+
const FangornConfig = await view.network(cfg.network);
|
|
183
268
|
const fangorn = await view.readOnlyFangorn();
|
|
184
269
|
fangorn.setAppId(cfg.app);
|
|
185
270
|
const appId = fangorn.getAppId();
|
|
186
271
|
const registry = fangorn.getAppRegistry();
|
|
272
|
+
const cf = cfg.site.account ? { CLOUDFLARE_ACCOUNT_ID: cfg.site.account } : {};
|
|
187
273
|
|
|
188
|
-
// 1. claim
|
|
274
|
+
// 1. claim: pays the subscription (the app sponsors its members' storage) and makes the
|
|
275
|
+
// claimer the app's first member.
|
|
189
276
|
const me = (fangornCli(cfg.app, ["wallet"]).match(/Address:\s+(0x[0-9a-fA-F]{40})/) ?? [])[1]?.toLowerCase();
|
|
190
277
|
if (!me) throw new Error("`fangorn wallet` shows no address; run `fangorn init`");
|
|
191
278
|
let owner = (await registry.getAppOwner()).toLowerCase();
|
|
@@ -199,31 +286,25 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
199
286
|
} else if (owner !== me) {
|
|
200
287
|
throw new Error(`${cfg.app} belongs to ${owner}, not this wallet (${me}); pick another name`);
|
|
201
288
|
}
|
|
202
|
-
state.fromBlock ??= String(cfg.fromBlock ??
|
|
289
|
+
state.fromBlock ??= String(cfg.fromBlock ?? FangornConfig.appRegistryFromBlock);
|
|
203
290
|
log(`app ${cfg.app} = ${appId}, owner ${owner}, fromBlock ${state.fromBlock}`);
|
|
204
291
|
|
|
205
|
-
// 2.
|
|
206
|
-
// Both registries: claiming joins the owner to the app, but commits also need the
|
|
207
|
-
// wallet's global DataRegistry standing, or every push reverts NotRegistered().
|
|
208
|
-
const info = fangornCli(cfg.app, ["app", "info"]);
|
|
209
|
-
if (!/DataRegistry:\s+registered/.test(info) || !/This app:\s+joined/.test(info)) fangornCli(cfg.app, ["register"], { dry });
|
|
210
|
-
|
|
211
|
-
// 3. schema
|
|
212
|
-
let onChain = null;
|
|
292
|
+
// 2. schema
|
|
213
293
|
if (!register) {
|
|
214
|
-
|
|
294
|
+
let onChain = null;
|
|
295
|
+
try { onChain = view.parseSchema((await fangorn.readRepo(owner, view.SCHEMA_REPO)).contents); } catch { /* none yet */ }
|
|
215
296
|
if (!onChain || canonical(onChain.types, onChain.relations) !== canonical(cfg.types, cfg.relations)) {
|
|
216
297
|
const sdir = join(dir, "schema");
|
|
217
298
|
mkdirSync(sdir, { recursive: true });
|
|
218
|
-
writeFileSync(join(sdir, "
|
|
299
|
+
writeFileSync(join(sdir, "schema.json"), JSON.stringify(schemaRecords(cfg.types, cfg.relations), null, 1));
|
|
219
300
|
log(`schema ${onChain ? "changed" : "is new"}: committing ${Object.keys(cfg.types).join(", ")}`);
|
|
220
|
-
fangornCli(cfg.app, ["repo", "init", view.
|
|
221
|
-
fangornCli(cfg.app, ["commit", "
|
|
301
|
+
fangornCli(cfg.app, ["repo", "init", view.SCHEMA_REPO], { cwd: sdir, dry });
|
|
302
|
+
fangornCli(cfg.app, ["commit", "schema.json", "-m", "schema", "--replace"], { cwd: sdir, dry });
|
|
222
303
|
fangornCli(cfg.app, ["push"], { cwd: sdir, dry });
|
|
223
304
|
} else log("schema unchanged");
|
|
224
305
|
}
|
|
225
306
|
|
|
226
|
-
//
|
|
307
|
+
// 3. sources: crawl + publish straight into the app. One source failing (a site that
|
|
227
308
|
// blocks us, a feed that is down) keeps its last published data and does not stop the
|
|
228
309
|
// others; the ship still exits non-zero at the end, naming it.
|
|
229
310
|
const failed = [];
|
|
@@ -231,101 +312,79 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
231
312
|
const cwd = s.cwd ? resolve(base, s.cwd.replace(/^~/, homedir())) : base;
|
|
232
313
|
const paidArgs = cfg.paid && s.paid_dir ? ["--paid-dir", resolve(cwd, s.paid_dir)] : [];
|
|
233
314
|
try {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
315
|
+
if (cfg.private) await publishSealed(cfg, base, s, { secret: secret(), dry });
|
|
316
|
+
else run(s.command[0], [...s.command.slice(1), ...paidArgs, ...(replace ? ["--replace"] : []),
|
|
317
|
+
"--publish", "--namespace", s.namespace, "--fangorn-bin", CLI.join(" ")],
|
|
318
|
+
{ cwd, env: { FANGORN_APP_ID: cfg.app }, dry });
|
|
237
319
|
} catch (e) {
|
|
238
320
|
log(`✗ ${s.namespace}: ${e.message}; its last published records stay live`);
|
|
239
321
|
failed.push(s.namespace);
|
|
240
322
|
}
|
|
241
323
|
}
|
|
242
324
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
325
|
+
// 4. the page, the card, the headers
|
|
326
|
+
const { site, writeCard } = await buildSite(cfg, base);
|
|
327
|
+
// site.url: the app's own domain, routed to the Worker. Otherwise its workers.dev URL,
|
|
328
|
+
// which is only known once it is deployed: the first deploy learns it and the card follows.
|
|
329
|
+
let url = cfg.site.url?.replace(/\/$/, "") ?? state.url ?? null;
|
|
330
|
+
const repos = [...new Set(cfg.sources.map((s) => s.namespace))];
|
|
331
|
+
// Paid records are in the committed rows already, encrypted (`paid_ct`: AES-256-GCM under
|
|
332
|
+
// HMAC-SHA256(PAID_SECRET, id), see x402.js). The Worker sells keys, never records, so the
|
|
333
|
+
// data a buyer pays for is on IPFS, committed, and in their hands before they pay.
|
|
334
|
+
const terms = (o) => settlement(o, { network: `eip155:${FangornConfig.caip2}`, owner });
|
|
335
|
+
const sale = (u) => {
|
|
336
|
+
if (!cfg.paid) return { paid: null, sale: null };
|
|
337
|
+
const T = terms(cfg.paid), price = units(cfg.paid.price, T.decimals), description = cfg.paid.description ?? "";
|
|
338
|
+
// A membership (MembershipRegistry, joined and claimed through Barliman) reads for free.
|
|
339
|
+
const membership = cfg.paid.membership ? { registry: cfg.paid.membership.registry, barliman: cfg.paid.membership.barliman.replace(/\/$/, ""),
|
|
340
|
+
chainId: cfg.paid.membership.chainId ?? FangornConfig.caip2, appId } : null;
|
|
341
|
+
const paid = { url: `${u}/paid/{id}`, key_url: `${u}/key`, price, asset: T.asset, network: T.network, decimals: T.decimals, symbol: T.symbol,
|
|
342
|
+
description, ...(membership ? { membership } : {}) };
|
|
343
|
+
return { paid, sale: { ...T, price, description, ...(membership ? { membership } : {}) } };
|
|
344
|
+
};
|
|
345
|
+
const access = (u) => {
|
|
346
|
+
const X = cfg.private;
|
|
347
|
+
if (!X) return { card: null, config: null };
|
|
348
|
+
if (!X.pass?.price || !X.call) throw new Error(`${configPath}: private needs pass.price and call, decimal prices like "5" and "0.01"`);
|
|
349
|
+
const T = terms(X), days = X.pass.days ?? 30, description = X.description ?? "";
|
|
350
|
+
const pass = { price: units(X.pass.price, T.decimals), seconds: Math.round(days * 86400) }, call = units(X.call, T.decimals);
|
|
351
|
+
return { card: { pass_url: `${u}/pass`, pass: pass.price, days, call, network: T.network, asset: T.asset, symbol: T.symbol, decimals: T.decimals, description },
|
|
352
|
+
config: { ...T, pass, call, description } };
|
|
353
|
+
};
|
|
354
|
+
const writeSite = (u) => writeCard(u, { fromBlock: state.fromBlock, repos, paid: sale(u).paid, access: access(u).card });
|
|
355
|
+
writeSite(url ?? `https://${cfg.site.project}.workers.dev`);
|
|
251
356
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
let paid = null, sale = null;
|
|
263
|
-
const live = view.liveFields(join(site, "view"));
|
|
264
|
-
if (cfg.paid) {
|
|
265
|
-
const network = cfg.paid.network ?? "arbitrum-sepolia";
|
|
266
|
-
const asset = cfg.paid.asset ?? USDC[network];
|
|
267
|
-
if (!asset) throw new Error(`paid.network ${network}: no default USDC address; set paid.asset`);
|
|
268
|
-
const decimals = cfg.paid.decimals ?? 6;
|
|
269
|
-
const [whole, frac = ""] = String(cfg.paid.price).split(".");
|
|
270
|
-
const price = (BigInt(whole) * 10n ** BigInt(decimals) + BigInt((frac + "0".repeat(decimals)).slice(0, decimals) || "0")).toString();
|
|
271
|
-
paid = { url: `${url}/paid/{id}`, price, asset, network, decimals, symbol: cfg.paid.symbol ?? "USDC", description: cfg.paid.description ?? "" };
|
|
272
|
-
// Only records a live row vouches for (its committed paid_sha256): one left over from
|
|
273
|
-
// an item since removed or re-extracted is not for sale, whatever is still on disk.
|
|
274
|
-
rmSync(join(site, "_paid"), { recursive: true, force: true });
|
|
275
|
-
mkdirSync(join(site, "_paid"), { recursive: true });
|
|
276
|
-
const vouched = new Set(live.map((f) => f.paid_sha256).filter(Boolean));
|
|
277
|
-
let n = 0;
|
|
278
|
-
for (const s of cfg.sources) {
|
|
279
|
-
const d = s.paid_dir && resolve(s.cwd ? resolve(base, s.cwd.replace(/^~/, homedir())) : base, s.paid_dir);
|
|
280
|
-
if (d && existsSync(d)) for (const f of readdirSync(d)) {
|
|
281
|
-
const bytes = readFileSync(join(d, f));
|
|
282
|
-
if (!vouched.has(createHash("sha256").update(bytes).digest("hex"))) continue;
|
|
283
|
-
writeFileSync(join(site, "_paid", f), bytes); n++;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
sale = { price, network, asset, payTo: cfg.paid.payTo ?? owner, facilitator: cfg.paid.facilitator ?? "https://facilitator.payai.network",
|
|
287
|
-
name: cfg.paid.tokenName ?? "USD Coin", version: cfg.paid.tokenVersion ?? "2", description: paid.description };
|
|
288
|
-
log(`paid: ${n} record(s) at ${cfg.paid.price} ${paid.symbol} on ${network}, to ${sale.payTo}`);
|
|
289
|
-
}
|
|
290
|
-
// The worker: paid records if any, and each record's source document shown in the page.
|
|
291
|
-
// Only the hosts the app's own records link to (their media role) are proxied.
|
|
292
|
-
const media = Object.values(cfg.types).flatMap((t) => [t.role_map?.media ?? []].flat());
|
|
293
|
-
const docHosts = [...new Set(live.flatMap((f) => media.map((m) => f[m])).filter((u) => typeof u === "string" && u.startsWith("https://"))
|
|
294
|
-
.map((u) => { try { return new URL(u).hostname; } catch { return null; } }).filter(Boolean))].sort();
|
|
295
|
-
writeFileSync(join(site, "_worker.js"), readFileSync(join(HERE, "../../site/worker.js"), "utf8")
|
|
296
|
-
.replace(/\/\*CONFIG\*\/[\s\S]*?\/\*END\*\//, JSON.stringify({ docHosts, paid: sale })));
|
|
297
|
-
log(`documents: shown inline from ${docHosts.length} source host(s)`);
|
|
298
|
-
// The card lists the tools the page at `url` registers: the stock page's, or the app's own.
|
|
299
|
-
const { registerAgent } = await import(cfg.site.agent ? pathToFileURL(resolve(base, cfg.site.agent)).href : "../../site/agent.js");
|
|
300
|
-
const agentCard = card.agentCard({
|
|
301
|
-
name: cfg.name, description: cfg.description, url: `${url}/`, version: new Date().toISOString().slice(0, 10),
|
|
302
|
-
tools: await card.captureTools(() => registerAgent({})), tags: cfg.tags,
|
|
303
|
-
fangorn: { app: cfg.app, fromBlock: state.fromBlock, namespaces, views: [`${url}/view`], ...(paid ? { paid } : {}) },
|
|
304
|
-
});
|
|
305
|
-
mkdirSync(join(site, ".well-known"), { recursive: true });
|
|
306
|
-
writeFileSync(join(site, ".well-known/agent-card.json"), JSON.stringify(agentCard, null, 1));
|
|
307
|
-
const immutable = " Cache-Control: public, max-age=31536000, immutable\n";
|
|
308
|
-
const domains = existsSync(join(site, "view/cdn/domains")) ? readdirSync(join(site, "view/cdn/domains")) : [];
|
|
309
|
-
writeFileSync(join(site, "_headers"),
|
|
310
|
-
"/.well-known/agent-card.json\n Access-Control-Allow-Origin: *\n"
|
|
311
|
-
+ "/view/*\n Access-Control-Allow-Origin: *\n"
|
|
312
|
-
+ `/assets/*\n${immutable}`
|
|
313
|
-
// A shard's name is its content hash, so a browser may keep it for good: a change or a
|
|
314
|
-
// retraction writes new names and the manifest stops pointing at the old ones. `private`
|
|
315
|
-
// keeps it out of shared caches, so a retracted record's bytes stop being served the
|
|
316
|
-
// moment the deploy drops the file (see view.js). An hour made readers on a phone fetch
|
|
317
|
-
// the whole catalog again after every hour away.
|
|
318
|
-
+ domains.map((d) => `/view/cdn/domains/${d}/shards/*\n Cache-Control: private, max-age=31536000, immutable\n`).join(""));
|
|
319
|
-
|
|
320
|
-
// 7. deploy
|
|
357
|
+
// 5. the Worker
|
|
358
|
+
const workerConfig = (u) => {
|
|
359
|
+
const path = join(dir, "wrangler.json");
|
|
360
|
+
writeFileSync(path, JSON.stringify(wranglerConfig({
|
|
361
|
+
name: cfg.site.project, site, account: cfg.site.account, cron: cfg.site.cron, url: cfg.site.url, ipfsGateway: view.ipfsGateway(),
|
|
362
|
+
config: { app: cfg.app, name: cfg.name, description: cfg.description, fromBlock: state.fromBlock, network: cfg.network,
|
|
363
|
+
repos: register ? [] : repos, paid: sale(u).sale, private: access(u).config },
|
|
364
|
+
}), null, 1));
|
|
365
|
+
return path;
|
|
366
|
+
};
|
|
321
367
|
if (deploy) {
|
|
322
|
-
|
|
323
|
-
|
|
368
|
+
const bucket = `${cfg.site.project}-view`;
|
|
369
|
+
const buckets = run("npx", ["wrangler", "r2", "bucket", "list"], { env: cf, capture: true, dry });
|
|
370
|
+
if (!dry && !buckets.split("\n").some((l) => l.trim() === `name: ${bucket}` || l.includes(` ${bucket}`)))
|
|
371
|
+
run("npx", ["wrangler", "r2", "bucket", "create", bucket], { env: cf });
|
|
372
|
+
const out = run("npx", ["wrangler", "deploy", "--config", workerConfig(url ?? "https://pending.invalid")], { env: cf, capture: true, dry });
|
|
373
|
+
const deployed = (out.match(/https:\/\/[a-z0-9.-]+\.workers\.dev/) ?? [])[0];
|
|
374
|
+
if (!url) {
|
|
375
|
+
if (!deployed && !dry) throw new Error("wrangler deploy printed no workers.dev URL; set site.url");
|
|
376
|
+
url = deployed ?? `https://${cfg.site.project}.workers.dev`;
|
|
377
|
+
// The card and CONFIG name the URL, which the first deploy only now learned.
|
|
378
|
+
writeSite(url);
|
|
379
|
+
run("npx", ["wrangler", "deploy", "--config", workerConfig(url)], { env: cf, capture: true, dry });
|
|
380
|
+
}
|
|
324
381
|
state.url = url;
|
|
325
382
|
save();
|
|
326
|
-
|
|
383
|
+
if (cfg.private) run("npx", ["wrangler", "secret", "put", "PAID_SECRET", "--config", workerConfig(url)], { env: cf, capture: true, dry, input: secret() });
|
|
384
|
+
else if (cfg.paid) log("paid: set the key secret once with `npx wrangler secret put PAID_SECRET --config .ship/wrangler.json`");
|
|
385
|
+
} else workerConfig(url ?? `https://${cfg.site.project}.workers.dev`);
|
|
327
386
|
|
|
328
|
-
//
|
|
387
|
+
// 6. ERC-8004 + the binding
|
|
329
388
|
const cardUrl = `${url}/.well-known/agent-card.json`;
|
|
330
389
|
const bound = await registry.appAgentUri(appId).catch(() => "");
|
|
331
390
|
const mint = () => {
|
|
@@ -347,39 +406,151 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
|
|
|
347
406
|
mint();
|
|
348
407
|
} else if (deploy) log("card already bound");
|
|
349
408
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
409
|
+
// A new app has no view until the cron runs, which may be hours off: build the first one now.
|
|
410
|
+
if (deploy && !dry && !register && !await fetch(`${url}/view/cdn/catalog`).then((r) => r.ok, () => false)) {
|
|
411
|
+
log("no view yet: indexing it here rather than waiting for the cron");
|
|
412
|
+
await indexHere(configPath).catch((e) => log(`✗ first index: ${e.message}; the cron will build it`));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// The Bazaar: the facilitator lists POST /paid once it settles a sale (and decides to);
|
|
416
|
+
// nothing here can list it sooner, so ship only says where it stands.
|
|
417
|
+
if (deploy && (cfg.paid || cfg.private) && !dry) for (const [route, T] of [["paid", sale(url).sale], ["pass", access(url).config]]) {
|
|
418
|
+
if (!T) continue;
|
|
419
|
+
const listed = await fetch(`${T.facilitator}/discovery/resources?payTo=${T.payTo}`).then((r) => r.json())
|
|
420
|
+
.then((d) => (d.items ?? []).some((i) => i.resource === `${url}/${route}`), () => null);
|
|
421
|
+
log(listed ? `the Bazaar at ${T.facilitator} lists ${url}/${route}`
|
|
422
|
+
: listed === false ? `the Bazaar at ${T.facilitator} does not list ${url}/${route} yet: it is listed on its first sale`
|
|
423
|
+
: `could not read the Bazaar at ${T.facilitator}`);
|
|
353
424
|
}
|
|
354
|
-
|
|
355
|
-
log(`done: ${url} · card ${cardUrl} ·
|
|
425
|
+
|
|
426
|
+
log(`done: ${url} · card ${cardUrl} · MCP ${url}/mcp · the view fills on the Worker's next cron run`);
|
|
356
427
|
if (failed.length) throw new Error(`deployed, but these sources failed and kept their old records: ${failed.join(", ")}`);
|
|
357
|
-
return { url, cardUrl, agentId: state.agentId
|
|
428
|
+
return { url, cardUrl, agentId: state.agentId };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** A private app's source: staged (it needs an --output-dir), every row sealed whole, and committed
|
|
432
|
+
* as a full snapshot. Unchanged rows seal to the same bytes, so the Worker embeds only what changed. */
|
|
433
|
+
async function publishSealed(cfg, base, s, { secret, dry }) {
|
|
434
|
+
const { sealRow } = await import("../agent/x402.js");
|
|
435
|
+
const rows = readStage({ ...cfg, sources: [s] }, base, { crawl: true })[s.namespace];
|
|
436
|
+
const sdir = join(base, ".ship/sealed", s.namespace);
|
|
437
|
+
mkdirSync(sdir, { recursive: true });
|
|
438
|
+
const records = await Promise.all(rows.map(async (r) => ({ id: r.id, type: r.type, payload: await sealRow(secret, r.payload) })));
|
|
439
|
+
writeFileSync(join(sdir, "commit.json"), JSON.stringify({ records }));
|
|
440
|
+
log(`${s.namespace}: ${records.length} rows sealed`);
|
|
441
|
+
try { fangornCli(cfg.app, ["status"], { cwd: sdir, dry }); } catch { fangornCli(cfg.app, ["repo", "init", s.namespace], { cwd: sdir, dry }); }
|
|
442
|
+
fangornCli(cfg.app, ["commit", "commit.json", "-m", `${s.namespace}: ${records.length} rows, sealed`, "--replace"], { cwd: sdir, dry });
|
|
443
|
+
fangornCli(cfg.app, ["push"], { cwd: sdir, dry });
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** What x402scan reads (docs/DISCOVERY.md in Merit-Systems/x402scan): /openapi.json with
|
|
447
|
+
* `x-payment-info` on each paid route, and /.well-known/x402 naming them. POST /paid (keys to
|
|
448
|
+
* sealed records) and POST /pass (a private app's pass); not GET /paid/{id}, a template no probe
|
|
449
|
+
* can call. The worker's live 402 is authoritative over both. A private app's tools, paid per
|
|
450
|
+
* call over MCP, are listed in the Bazaar instead: x402scan reads HTTP routes only.
|
|
451
|
+
* ponytail: currency "USD" assumes the asset is USDC, the only default ship knows. */
|
|
452
|
+
export function discovery(cfg, u, paid = null, access = null) {
|
|
453
|
+
const token = paid ?? access;
|
|
454
|
+
const usd = (n) => String(Number(n) / 10 ** (token.decimals ?? 6));
|
|
455
|
+
const ids = { type: "array", items: { type: "string" }, minItems: 1, maxItems: 1000 };
|
|
456
|
+
const sym = token.symbol ?? "USDC";
|
|
457
|
+
const paths = {};
|
|
458
|
+
if (paid) paths["/paid"] = { post: {
|
|
459
|
+
operationId: "buyKeys",
|
|
460
|
+
summary: `Keys to ${cfg.name}'s sealed records, ${usd(paid.price)} ${sym} each`,
|
|
461
|
+
description: `${paid.description ? `${paid.description}. ` : ""}Each record is in the app's rows already, sealed (paid_ct, `
|
|
462
|
+
+ `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; `
|
|
463
|
+
+ `the agent card is ${u}/.well-known/agent-card.json.`,
|
|
464
|
+
requestBody: { required: true, content: { "application/json": { schema: { type: "object", required: ["ids"], properties: { ids } } } } },
|
|
465
|
+
responses: {
|
|
466
|
+
200: { description: "A hex key per id; ids it could not sell are in missing", content: { "application/json": { schema: { type: "object", properties: {
|
|
467
|
+
keys: { type: "object", additionalProperties: { type: "string" } }, missing: ids, receipts: { type: "array", items: { type: "object" } },
|
|
468
|
+
paid: { type: "array", items: { type: "string" } } } } } } },
|
|
469
|
+
402: { description: "Payment required: x402 v2, the PaymentRequired in the PAYMENT-REQUIRED header" },
|
|
470
|
+
},
|
|
471
|
+
"x-payment-info": { protocols: [{ x402: {} }], price: { mode: "dynamic", currency: "USD", min: usd(paid.price), max: usd(BigInt(paid.price) * 1000n) } },
|
|
472
|
+
} };
|
|
473
|
+
if (access) paths["/pass"] = { post: {
|
|
474
|
+
operationId: "buyPass",
|
|
475
|
+
summary: `A ${access.days}-day pass to ${cfg.name}, ${usd(access.pass)} ${sym}`,
|
|
476
|
+
description: `${access.description ? `${access.description}. ` : ""}Every tool of the MCP server at ${u}/mcp (search, get, similar, count, `
|
|
477
|
+
+ `browse) and the view at ${u}/view, for ${access.days} days: send Authorization: Bearer <token>. Without a pass each tool call costs `
|
|
478
|
+
+ `${usd(access.call)} ${sym} over x402's MCP transport.`,
|
|
479
|
+
responses: {
|
|
480
|
+
200: { description: "The pass", content: { "application/json": { schema: { type: "object", properties: {
|
|
481
|
+
token: { type: "string" }, expires: { type: "integer", description: "Unix seconds" }, use: { type: "string" } } } } } },
|
|
482
|
+
402: { description: "Payment required: x402 v2, the PaymentRequired in the PAYMENT-REQUIRED header" },
|
|
483
|
+
},
|
|
484
|
+
"x-payment-info": { protocols: [{ x402: {} }], price: { mode: "fixed", currency: "USD", amount: usd(access.pass) } },
|
|
485
|
+
} };
|
|
486
|
+
const guidance = access
|
|
487
|
+
? `${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 `
|
|
488
|
+
+ `Authorization: Bearer <token> to ${u}/mcp, or pay each tool call over x402's MCP transport (_meta["x402/payment"]).`
|
|
489
|
+
: `Search ${cfg.name} with the MCP server at ${u}/mcp (free). A row with paid_ct is for sale: POST its id to /paid, `
|
|
490
|
+
+ `pay the 402 (x402 v2), and open paid_ct with the key that comes back (AES-256-GCM, nonce first; check paid_sha256).`;
|
|
491
|
+
return {
|
|
492
|
+
openapi: {
|
|
493
|
+
openapi: "3.1.0",
|
|
494
|
+
info: { title: cfg.name, version: new Date().toISOString().slice(0, 10), description: cfg.description, "x-guidance": guidance },
|
|
495
|
+
servers: [{ url: u }],
|
|
496
|
+
paths,
|
|
497
|
+
},
|
|
498
|
+
x402: { version: 1, resources: Object.keys(paths).map((p) => `${u}${p}`) },
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** The stock page with the app's own files over it, in .ship/site; `writeCard(url, …)` adds the card for where it is served. */
|
|
503
|
+
export async function buildSite(cfg, base) {
|
|
504
|
+
if (!existsSync(join(STOCK, "index.html"))) throw new Error(`no stock page at ${STOCK}; run \`npx vite build\` in westmarch/site`);
|
|
505
|
+
const site = join(base, ".ship/site");
|
|
506
|
+
rmSync(site, { recursive: true, force: true });
|
|
507
|
+
mkdirSync(site, { recursive: true });
|
|
508
|
+
cpSync(STOCK, site, { recursive: true });
|
|
509
|
+
if (cfg.site.pages) cpSync(resolve(base, cfg.site.pages), site, { recursive: true });
|
|
510
|
+
writeFileSync(join(site, "nav.json"), JSON.stringify(cfg.site.nav ?? []));
|
|
511
|
+
writeFileSync(join(site, "examples.json"), JSON.stringify(cfg.site.examples ?? []));
|
|
512
|
+
// The card lists the tools the page at `url` registers: the stock page's, or the app's own.
|
|
513
|
+
const card = await import("../agent/agent-card.js");
|
|
514
|
+
const { registerAgent } = await import(cfg.site.agent ? pathToFileURL(resolve(base, cfg.site.agent)).href : "../../site/agent.js");
|
|
515
|
+
const tools = await card.captureTools(() => registerAgent({}));
|
|
516
|
+
const config = await (await import("./view.js")).network(cfg.network);
|
|
517
|
+
const writeCard = (u, { fromBlock, repos, paid = null, access = null }) => {
|
|
518
|
+
const agentCard = card.agentCard({
|
|
519
|
+
name: cfg.name, description: cfg.description, url: `${u}/`, version: new Date().toISOString().slice(0, 10), tools, tags: cfg.tags,
|
|
520
|
+
// What is sold, as a skill tagged x402: ERC-8004 registration reads x402Support off the tags.
|
|
521
|
+
skills: [
|
|
522
|
+
...(access ? [{ id: "pass", name: "Pass", tags: [...(cfg.tags ?? []), "x402"],
|
|
523
|
+
description: `A ${access.days}-day pass to every tool and the view (POST ${u}/pass), or one x402 payment per tool call.` }] : []),
|
|
524
|
+
...(paid ? [{ id: "keys", name: "Keys", tags: [...(cfg.tags ?? []), "x402"],
|
|
525
|
+
description: `The keys to sealed records (POST ${u}/paid), per record.` }] : []),
|
|
526
|
+
],
|
|
527
|
+
fangorn: { app: cfg.app, fromBlock, repos, views: [`${u}/view`], mcp: `${u}/mcp`, config, ...(paid ? { paid } : {}), ...(access ? { access } : {}) },
|
|
528
|
+
});
|
|
529
|
+
mkdirSync(join(site, ".well-known"), { recursive: true });
|
|
530
|
+
writeFileSync(join(site, ".well-known/agent-card.json"), JSON.stringify(agentCard, null, 1));
|
|
531
|
+
if (paid || access) {
|
|
532
|
+
const d = discovery(cfg, u, paid, access);
|
|
533
|
+
writeFileSync(join(site, "openapi.json"), JSON.stringify(d.openapi, null, 1));
|
|
534
|
+
writeFileSync(join(site, ".well-known/x402"), JSON.stringify(d.x402, null, 1));
|
|
535
|
+
}
|
|
536
|
+
writeFileSync(join(site, "_headers"), "/.well-known/agent-card.json\n Access-Control-Allow-Origin: *\n"
|
|
537
|
+
+ "/openapi.json\n Access-Control-Allow-Origin: *\n"
|
|
538
|
+
+ "/.well-known/x402\n Access-Control-Allow-Origin: *\n Content-Type: application/json\n"
|
|
539
|
+
+ "/assets/*\n Cache-Control: public, max-age=31536000, immutable\n");
|
|
540
|
+
};
|
|
541
|
+
return { site, writeCard };
|
|
358
542
|
}
|
|
359
543
|
|
|
360
544
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* The sources run without --publish, or not at all with crawl=false. Every build is whole
|
|
364
|
-
* (a changed `text` role must reach every row, and publishView re-embeds only new CIDs);
|
|
365
|
-
* `.ship/vectors.ndjson` keeps it cheap, so two recipes over the same data embed only the
|
|
366
|
-
* text that differs.
|
|
545
|
+
* What each source staged in its --output-dir, as records: `{namespace: [{id, type, payload}]}`.
|
|
546
|
+
* The sources run first (without --publish) with crawl=true.
|
|
367
547
|
*
|
|
368
548
|
* ponytail: reads every `volume_<n>_*.json` in a source's --output-dir; the
|
|
369
549
|
* harness publishes only the stems its source declares, so a stale file left by an older
|
|
370
|
-
* shape of a source is
|
|
550
|
+
* shape of a source is read here and not on chain. Clear the stage dir if that bites.
|
|
371
551
|
*/
|
|
372
|
-
export
|
|
373
|
-
const
|
|
374
|
-
const base = dirname(resolve(configPath));
|
|
375
|
-
const [{ env }, view] = await Promise.all([import("@huggingface/transformers"), import("./view.js")]);
|
|
376
|
-
env.cacheDir = `${homedir()}/.cache/fangorn-mcp/models`;
|
|
377
|
-
// Not the app's real id or owner: nothing here is compared with the chain.
|
|
378
|
-
const appId = `0x${createHash("sha256").update(cfg.app).digest("hex")}`, owner = `0x${"10ca1".padStart(40, "0")}`;
|
|
379
|
-
const g = schemaGraph(cfg.types, cfg.relations);
|
|
380
|
-
const chain = { [`${owner}/${view.SCHEMA_NAMESPACE}`]: {
|
|
381
|
-
vertices: g.vertices.map((v) => ({ cid: v.id, schemaId: v.tag, payload: v.payload })),
|
|
382
|
-
edges: g.edges.map((e) => ({ relation: e.rel, sourceCid: e.from, targetCid: e.to })) } };
|
|
552
|
+
export function readStage(cfg, base, { crawl = false, only = null, log: say = log } = {}) {
|
|
553
|
+
const out = {};
|
|
383
554
|
for (const s of cfg.sources.filter((s) => !only || only.includes(s.namespace))) {
|
|
384
555
|
const cwd = s.cwd ? resolve(base, s.cwd.replace(/^~/, homedir())) : base;
|
|
385
556
|
if (crawl) run(s.command[0], s.command.slice(1), { cwd, env: { FANGORN_APP_ID: cfg.app } });
|
|
@@ -387,80 +558,169 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
|
|
|
387
558
|
if (at < 0) { say(`${s.namespace}: no --output-dir in its command, so nothing staged to read`); continue; }
|
|
388
559
|
const dir = resolve(cwd, expandEnv(s.command[at + 1]));
|
|
389
560
|
const volume = s.command.includes("--volume") ? s.command[s.command.indexOf("--volume") + 1] : "1";
|
|
390
|
-
const
|
|
391
|
-
const
|
|
561
|
+
const records = (out[s.namespace] ??= []);
|
|
562
|
+
const byName = new Map();
|
|
392
563
|
for (const f of existsSync(dir) ? readdirSync(dir).sort() : []) {
|
|
393
564
|
const m = new RegExp(`^volume_${volume}_(.+)\\.json$`).exec(f);
|
|
394
565
|
if (!m || m[1] === "edges") continue;
|
|
395
|
-
// The harness
|
|
396
|
-
// addresses it by the payload's hash, and so does this.
|
|
566
|
+
// The harness stages {name, fields}: the record's id and its payload.
|
|
397
567
|
for (const n of JSON.parse(readFileSync(join(dir, f), "utf8"))) {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
568
|
+
const r = { id: n.name, type: n.fields?.entityType ?? m[1][0].toUpperCase() + m[1].slice(1), payload: { ...n.fields } };
|
|
569
|
+
byName.set(n.name, r);
|
|
570
|
+
records.push(r);
|
|
401
571
|
}
|
|
402
572
|
}
|
|
403
|
-
// Edges name
|
|
573
|
+
// Edges name records by the harness's ids: each becomes a reference on its source.
|
|
404
574
|
const edges = join(dir, `volume_${volume}_edges.json`);
|
|
405
575
|
for (const e of existsSync(edges) ? JSON.parse(readFileSync(edges, "utf8")) : [])
|
|
406
|
-
if (
|
|
576
|
+
if (byName.has(e.from) && byName.has(e.to)) byName.get(e.from).payload[e.rel] = { local: e.to };
|
|
407
577
|
}
|
|
408
578
|
// An empty stage is a crawl that did not happen (or a cache that was not restored), not
|
|
409
579
|
// an app with no records; a view of it would grade as a failure for the wrong reason.
|
|
410
|
-
|
|
411
|
-
|
|
580
|
+
if (!Object.values(out).some((rs) => rs.length))
|
|
581
|
+
throw new Error("no staged records under any source's --output-dir: run the sources (--crawl, or a ship) or restore .ship/stage first");
|
|
582
|
+
return out;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* The whole app on this machine (devnet.js): a local chain and IPFS, the app claimed and its
|
|
587
|
+
* schema committed there, what the sources staged published into it, and its Worker under
|
|
588
|
+
* `wrangler dev` with a local R2 and an encoder on localhost (or Workers AI with ai=true).
|
|
589
|
+
* Runs the indexer once, says where everything is, and stays up until interrupted.
|
|
590
|
+
*/
|
|
591
|
+
export async function devnetShip(configPath, { crawl = false, only = null, port = 8787, ai = false } = {}) {
|
|
592
|
+
const cfg = loadConfig(configPath);
|
|
593
|
+
const base = dirname(resolve(configPath));
|
|
594
|
+
const [{ devnet }, { SCHEMA_REPO }] = await Promise.all([import("./devnet.js"), import("./view.js")]);
|
|
595
|
+
const staged = readStage(cfg, base, { crawl, only });
|
|
596
|
+
const net = await devnet({ log });
|
|
597
|
+
const owner = await net.client(cfg.app);
|
|
598
|
+
await owner.getAppRegistry().registerApp();
|
|
599
|
+
await owner.initRepo(SCHEMA_REPO);
|
|
600
|
+
await owner.uploadBatch(SCHEMA_REPO, schemaRecords(cfg.types, cfg.relations).records);
|
|
601
|
+
// The claimer publishes the sources, as `fangorn` does in a ship.
|
|
602
|
+
for (const [ns, records] of Object.entries(staged)) {
|
|
603
|
+
if (!records.length) continue;
|
|
604
|
+
await owner.initRepo(ns);
|
|
605
|
+
await owner.uploadBatch(ns, records);
|
|
606
|
+
log(`published ${ns}: ${records.length} records`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const url = `http://127.0.0.1:${port}`;
|
|
610
|
+
const repos = Object.keys(staged);
|
|
611
|
+
const { site, writeCard } = await buildSite(cfg, base);
|
|
612
|
+
writeCard(url, { fromBlock: "0", repos });
|
|
613
|
+
const wcfg = wranglerConfig({ name: cfg.site.project, site, config: { app: cfg.app, name: cfg.name, description: cfg.description,
|
|
614
|
+
fromBlock: "0", repos, paid: null, deployment: net.deployment } });
|
|
615
|
+
// Its own R2 and state, apart from a deployed app's.
|
|
616
|
+
wcfg.vars.RPC_URL = net.rpcUrl;
|
|
617
|
+
wcfg.vars.IPFS_GATEWAY = net.gatewayUrl;
|
|
618
|
+
if (!ai) { wcfg.vars.EMBED_URL = await net.encoder(); delete wcfg.ai; } // no binding: wrangler would reach Cloudflare for it
|
|
619
|
+
delete wcfg.routes;
|
|
620
|
+
const wpath = join(base, ".ship/wrangler.devnet.json");
|
|
621
|
+
writeFileSync(wpath, JSON.stringify(wcfg, null, 1));
|
|
622
|
+
rmSync(join(base, ".ship/devnet-state"), { recursive: true, force: true }); // a fresh chain: last run's view is of another one
|
|
623
|
+
const wrangler = spawn("npx", ["wrangler", "dev", "--config", wpath, "--port", String(port), "--ip", "127.0.0.1", "--test-scheduled",
|
|
624
|
+
"--persist-to", join(base, ".ship/devnet-state")], { cwd: join(HERE, "../.."), stdio: ["ignore", "inherit", "inherit"],
|
|
625
|
+
env: { ...process.env, ...(ai && cfg.site.account ? { CLOUDFLARE_ACCOUNT_ID: cfg.site.account } : {}) } });
|
|
626
|
+
net.kids.push(wrangler);
|
|
627
|
+
const up = async () => { for (;;) { if (wrangler.exitCode !== null) throw new Error("wrangler dev exited");
|
|
628
|
+
if (await fetch(url).then((r) => r.ok, () => false)) return; await new Promise((r) => setTimeout(r, 1000)); } };
|
|
629
|
+
await up();
|
|
630
|
+
log(`indexing (the cron, once): ${repos.length} repos`);
|
|
631
|
+
await fetch(`${url}/__scheduled?cron=${encodeURIComponent(wcfg.triggers.crons[0])}`);
|
|
632
|
+
const want = Object.values(staged).filter((rs) => rs.length).length;
|
|
633
|
+
let catalog = null;
|
|
634
|
+
while ((catalog?.domains?.length ?? 0) < want) {
|
|
635
|
+
if (wrangler.exitCode !== null) throw new Error("wrangler dev exited");
|
|
636
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
637
|
+
catalog = await fetch(`${url}/view/cdn/catalog`).then((r) => r.ok ? r.json() : null, () => null);
|
|
638
|
+
}
|
|
639
|
+
log(`done: ${catalog?.domains?.length ?? 0} domains, ${(catalog?.domains ?? []).reduce((n, d) => n + d.count, 0)} rows`);
|
|
640
|
+
log(`site ${url} · MCP ${url}/mcp · view ${url}/view · reindex: curl '${url}/__scheduled' · Ctrl-C stops it all`);
|
|
641
|
+
log(`grade it: npx westmarch-eval ${url}/view --golden eval/golden.jsonl`);
|
|
642
|
+
await new Promise((r) => wrangler.on("exit", r));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* The deployed app's indexer, run once on this machine: its Worker under `wrangler dev` with
|
|
647
|
+
* the app's own R2 bucket and Workers AI (remote bindings), its CONFIG, and for a private app
|
|
648
|
+
* its PAID_SECRET (in .ship/.dev.vars for the run, then removed). The first view right after a
|
|
649
|
+
* deploy, without waiting for the cron; and a catalog too big for one cron run's limits, since
|
|
650
|
+
* nothing here is capped. Writes the same view the cron would.
|
|
651
|
+
* ponytail: Workers AI still embeds; an encoder on this machine (EMBED_URL) would record another
|
|
652
|
+
* embedder, and the deployed Worker's queries would not match it.
|
|
653
|
+
*/
|
|
654
|
+
export async function indexHere(configPath, { port = 8791 } = {}) {
|
|
655
|
+
const cfg = loadConfig(configPath);
|
|
656
|
+
const base = dirname(resolve(configPath)), dir = join(base, ".ship");
|
|
657
|
+
if (!existsSync(join(dir, "wrangler.json"))) throw new Error(`${dir}/wrangler.json: ship the app first`);
|
|
658
|
+
const w = JSON.parse(readFileSync(join(dir, "wrangler.json"), "utf8"));
|
|
659
|
+
w.r2_buckets = w.r2_buckets.map((b) => ({ ...b, remote: true }));
|
|
660
|
+
w.ai = { ...w.ai, remote: true };
|
|
661
|
+
delete w.routes;
|
|
662
|
+
const wpath = join(dir, "wrangler.index.json"), vars = join(dir, ".dev.vars");
|
|
663
|
+
writeFileSync(wpath, JSON.stringify(w, null, 1));
|
|
664
|
+
const secret = process.env.PAID_SECRET ?? (existsSync(join(dir, "secret")) ? readFileSync(join(dir, "secret"), "utf8").trim() : null);
|
|
665
|
+
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");
|
|
666
|
+
if (secret) writeFileSync(vars, `PAID_SECRET=${secret}\n`, { mode: 0o600 });
|
|
667
|
+
const url = `http://127.0.0.1:${port}`, start = Math.floor(Date.now() / 1000);
|
|
668
|
+
const wrangler = spawn("npx", ["wrangler", "dev", "--config", wpath, "--port", String(port), "--ip", "127.0.0.1", "--test-scheduled"],
|
|
669
|
+
{ cwd: join(HERE, "../.."), stdio: ["ignore", "inherit", "inherit"],
|
|
670
|
+
env: { ...process.env, ...(cfg.site.account ? { CLOUDFLARE_ACCOUNT_ID: cfg.site.account } : {}) } });
|
|
671
|
+
try {
|
|
672
|
+
for (;;) {
|
|
673
|
+
if (wrangler.exitCode !== null) throw new Error("wrangler dev exited");
|
|
674
|
+
if (await fetch(`${url}/.well-known/agent-card.json`).then((r) => r.ok, () => false)) break;
|
|
675
|
+
await new Promise((ok) => setTimeout(ok, 1000));
|
|
676
|
+
}
|
|
677
|
+
log(`indexing ${cfg.app} here, into ${w.r2_buckets[0].bucket_name}`);
|
|
678
|
+
await fetch(`${url}/__scheduled?cron=${encodeURIComponent(w.triggers.crons[0])}`);
|
|
679
|
+
let catalog = null;
|
|
680
|
+
while (!(catalog?.generated_at >= start)) {
|
|
681
|
+
if (wrangler.exitCode !== null) throw new Error("wrangler dev exited");
|
|
682
|
+
await new Promise((ok) => setTimeout(ok, 5000));
|
|
683
|
+
catalog = await fetch(`${url}/view/cdn/catalog`).then((r) => (r.ok ? r.json() : null), () => null);
|
|
684
|
+
}
|
|
685
|
+
log(`indexed: ${catalog.domains.length} domain(s), ${catalog.domains.reduce((n, d) => n + d.count, 0)} rows`);
|
|
686
|
+
return catalog;
|
|
687
|
+
} finally {
|
|
688
|
+
wrangler.kill();
|
|
689
|
+
rmSync(vars, { force: true });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* A view from what the sources staged, with app.json's schema, touching no chain and no
|
|
695
|
+
* site: what a recipe change would ship, built so it can be graded first (westmarch-eval).
|
|
696
|
+
* The sources run without --publish, or not at all with crawl=false. Every build is whole
|
|
697
|
+
* (a changed `text` role must reach every row, and publishView re-embeds only new CIDs);
|
|
698
|
+
* `.ship/vectors.ndjson` keeps it cheap, so two recipes over the same data embed only the
|
|
699
|
+
* text that differs.
|
|
700
|
+
*/
|
|
701
|
+
export async function buildLocal(configPath, out, { crawl = false, only = null, log: say = log } = {}) {
|
|
702
|
+
const cfg = loadConfig(configPath);
|
|
703
|
+
const base = dirname(resolve(configPath));
|
|
704
|
+
const [{ env }, view] = await Promise.all([import("@huggingface/transformers"), import("./view.js")]);
|
|
705
|
+
env.cacheDir = `${homedir()}/.cache/fangorn-mcp/models`;
|
|
706
|
+
// Not the app's real id or owner: nothing here is compared with the chain.
|
|
707
|
+
const appId = `0x${createHash("sha256").update(cfg.app).digest("hex")}`, owner = `0x${"10ca1".padStart(40, "0")}`;
|
|
708
|
+
const chain = { [`${owner}/${view.SCHEMA_REPO}`]: {
|
|
709
|
+
records: schemaRecords(cfg.types, cfg.relations).records.map((r) => ({ ...r, cid: r.id })) } };
|
|
710
|
+
// Storage addresses a record by its content, and so does this.
|
|
711
|
+
for (const [ns, records] of Object.entries(readStage(cfg, base, { crawl, only, log: say })))
|
|
712
|
+
chain[`${owner}/${ns}`] = { records: records.map((r) => ({ ...r, cid: createHash("sha256").update(JSON.stringify(stable(r.payload))).digest("hex") })) };
|
|
412
713
|
rmSync(out, { recursive: true, force: true });
|
|
413
714
|
mkdirSync(join(base, ".ship"), { recursive: true });
|
|
414
715
|
const embed = view.cachedEmbed(join(base, ".ship/vectors.ndjson"));
|
|
415
|
-
const
|
|
716
|
+
const repos = [...new Set(cfg.sources.map((s) => s.namespace))].filter((n) => !only || only.includes(n));
|
|
416
717
|
const report = await view.publishView({ fangorn: view.localFangorn(chain, { appId, owner }), app: cfg.app,
|
|
417
|
-
|
|
718
|
+
repos, out, embed, log: say });
|
|
418
719
|
const rows = Object.values(report).reduce((n, r) => n + r.added, 0);
|
|
419
720
|
say(`local view: ${out} · ${rows} records, ${embed.misses} embedded, ${rows - embed.misses} from the vector cache`);
|
|
420
721
|
return report;
|
|
421
722
|
}
|
|
422
723
|
|
|
423
|
-
/** Copy a deployed view (catalog, manifests, shards) to `dir`, so the next publish appends. */
|
|
424
|
-
async function mirrorView(base, dir) {
|
|
425
|
-
const get = async (p) => { const r = await fetch(`${base}/${p}`); if (!r.ok) throw new Error(`${base}/${p}: HTTP ${r.status}`); return r; };
|
|
426
|
-
let catalog;
|
|
427
|
-
try { catalog = await (await get("cdn/catalog")).json(); } catch (e) { log(`no deployed view to start from (${e.message})`); return; }
|
|
428
|
-
const { createHash } = await import("node:crypto");
|
|
429
|
-
let files = 0;
|
|
430
|
-
for (const d of catalog.domains ?? []) {
|
|
431
|
-
const m = await (await get(`cdn/domains/${d.name}/manifest`)).json();
|
|
432
|
-
mkdirSync(join(dir, "cdn/domains", d.name, "shards"), { recursive: true });
|
|
433
|
-
writeFileSync(join(dir, "cdn/domains", d.name, "manifest"), JSON.stringify(m, null, 1));
|
|
434
|
-
for (const s of m.shards ?? []) {
|
|
435
|
-
const buf = Buffer.from(await (await get(`cdn/domains/${d.name}/shards/${s.file}`)).arrayBuffer());
|
|
436
|
-
if (s.sha256 && createHash("sha256").update(buf).digest("hex") !== s.sha256) throw new Error(`${s.file} does not match its manifest`);
|
|
437
|
-
writeFileSync(join(dir, "cdn/domains", d.name, "shards", s.file), buf);
|
|
438
|
-
files++;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
writeFileSync(join(dir, "cdn/catalog"), JSON.stringify(catalog, null, 1));
|
|
442
|
-
log(`started from the deployed view: ${catalog.domains?.length ?? 0} domain(s), ${files} shard(s)`);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** The project's pages.dev URL, creating the project on first use. Pages appends a
|
|
446
|
-
* suffix when the name is taken elsewhere, so the URL is read back, never assumed. */
|
|
447
|
-
function pagesUrl(cfg) {
|
|
448
|
-
const env = cfg.site.account ? { CLOUDFLARE_ACCOUNT_ID: cfg.site.account } : {};
|
|
449
|
-
const find = () => {
|
|
450
|
-
const out = run("npx", ["wrangler", "pages", "project", "list"], { env, capture: true });
|
|
451
|
-
const row = out.split("\n").find((l) => l.split("│")[1]?.trim() === cfg.site.project);
|
|
452
|
-
const host = row?.match(/([a-z0-9-]+\.pages\.dev)/)?.[1];
|
|
453
|
-
return host ? `https://${host}` : null;
|
|
454
|
-
};
|
|
455
|
-
let url = find();
|
|
456
|
-
if (!url) {
|
|
457
|
-
run("npx", ["wrangler", "pages", "project", "create", cfg.site.project, "--production-branch", "main"], { env });
|
|
458
|
-
url = find();
|
|
459
|
-
}
|
|
460
|
-
if (!url) throw new Error(`could not read the pages.dev URL of project ${cfg.site.project}`);
|
|
461
|
-
return url;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
724
|
// ── cli ───────────────────────────────────────────────────────────────────────
|
|
465
725
|
if (process.argv[1]?.endsWith("ship.js") || process.argv[1]?.endsWith("westmarch-ship")) {
|
|
466
726
|
if (process.argv[2] === "--selfcheck") {
|
|
@@ -470,39 +730,60 @@ if (process.argv[1]?.endsWith("ship.js") || process.argv[1]?.endsWith("westmarch
|
|
|
470
730
|
const types = { "t.v1": { description: "d", role_map: { title: "name", text: ["body"] }, presentation: { externalUrl: { "t.v1": "{url}" } } },
|
|
471
731
|
"u.v1": { description: "e", role_map: { title: "label" } } };
|
|
472
732
|
const relations = [{ rel: "cites", from: "t.v1", to: "u.v1" }];
|
|
473
|
-
const g =
|
|
733
|
+
const g = schemaRecords(types, relations);
|
|
474
734
|
// Keys reversed at every depth, as a canonical (sorted) encoding hands them back.
|
|
475
735
|
const reverse = (v) => Array.isArray(v) ? v.map(reverse)
|
|
476
736
|
: v && typeof v === "object" ? Object.fromEntries(Object.keys(v).reverse().map((k) => [k, reverse(v[k])])) : v;
|
|
477
|
-
const readBack = parseSchema({
|
|
478
|
-
vertices: g.vertices.map((v) => ({ cid: `cid-${v.id}`, schemaId: v.tag, payload: reverse(v.payload) })),
|
|
479
|
-
edges: g.edges.map((e) => ({ relation: e.rel, sourceCid: `cid-${e.from}`, targetCid: `cid-${e.to}` })),
|
|
480
|
-
});
|
|
737
|
+
const readBack = parseSchema({ records: g.records.map((r) => ({ ...r, cid: `cid-${r.id}`, payload: reverse(r.payload) })) });
|
|
481
738
|
if (canonical(readBack.types, readBack.relations) !== canonical(types, relations)) throw new Error("schema does not round-trip");
|
|
482
739
|
if (canonical(readBack.types, readBack.relations) === canonical({ ...types, "v.v1": {} }, relations)) throw new Error("a new type must count as a change");
|
|
483
740
|
const row = { city: "Wisconsin Rapids", state: "WI", site: "https://x", args: ["--archive-page", "/569/A"] };
|
|
484
741
|
const cmd = fill(["py", "--site", "{site}", "--city", "{city}", "{args...}", "--ns", "us-{state|lower}-{city|slug}"], row);
|
|
485
742
|
if (JSON.stringify(cmd) !== JSON.stringify(["py", "--site", "https://x", "--city", "Wisconsin Rapids", "--archive-page", "/569/A", "--ns", "us-wi-wisconsin-rapids"]))
|
|
486
743
|
throw new Error(`template fill: ${JSON.stringify(cmd)}`);
|
|
487
|
-
|
|
744
|
+
const w = wranglerConfig({ name: "quorum", site: "/s", config: { app: "quorum" }, url: "https://quorum.example" });
|
|
745
|
+
if (w.r2_buckets[0].bucket_name !== "quorum-view" || !w.ai || !w.triggers.crons.length || w.routes[0].pattern !== "quorum.example"
|
|
746
|
+
|| !w.compatibility_flags.includes("nodejs_compat") || !existsSync(w.main) || !existsSync(w.alias["@huggingface/transformers"]))
|
|
747
|
+
throw new Error(`wrangler config: ${JSON.stringify(w)}`);
|
|
748
|
+
const d = discovery({ name: "Q", description: "q" }, "https://q.example", { price: "10000", decimals: 6, symbol: "USDC" });
|
|
749
|
+
const op = d.openapi.paths["/paid"].post;
|
|
750
|
+
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")
|
|
751
|
+
throw new Error(`discovery: ${JSON.stringify(d)}`);
|
|
752
|
+
const dp = discovery({ name: "Q", description: "q" }, "https://q.example", null, { pass: "5000000", days: 30, call: "10000", decimals: 6 });
|
|
753
|
+
if (dp.openapi.paths["/pass"].post["x-payment-info"].price.amount !== "5" || dp.x402.resources.join() !== "https://q.example/pass" || dp.openapi.paths["/paid"])
|
|
754
|
+
throw new Error(`private discovery: ${JSON.stringify(dp.x402)}`);
|
|
755
|
+
if (units("0.01") !== "10000" || units("5") !== "5000000" || units("1.5", 2) !== "150") throw new Error("units");
|
|
756
|
+
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");
|
|
757
|
+
console.log("ship.js self-check ok — a committed schema reads back equal to its config, a new type counts as a change, 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");
|
|
488
758
|
process.exit(0);
|
|
489
759
|
}
|
|
490
760
|
const [path, ...flags] = process.argv.slice(2);
|
|
491
761
|
if (!path || flags.includes("--help")) {
|
|
492
|
-
console.error("usage: westmarch-ship app.json [--no-crawl] [--no-deploy] [--dry-run] [--replace] [--
|
|
493
|
-
" westmarch-ship app.json --register [--dry-run] (claim,
|
|
494
|
-
" westmarch-ship app.json --local <out> [--crawl] [--only ns1,ns2] (no chain, no deploy)"
|
|
762
|
+
console.error("usage: westmarch-ship app.json [--no-crawl] [--no-deploy] [--dry-run] [--replace] [--only ns1,ns2]\n" +
|
|
763
|
+
" westmarch-ship app.json --register [--dry-run] (claim, empty site, card bound: no data)\n" +
|
|
764
|
+
" westmarch-ship app.json --local <out> [--crawl] [--only ns1,ns2] (no chain, no deploy)\n" +
|
|
765
|
+
" westmarch-ship app.json --index (the deployed app's indexer, once, on this machine)\n" +
|
|
766
|
+
" westmarch-ship app.json --devnet [--crawl] [--only ns1,ns2] [--port 8787] [--ai] (the whole app on this machine)");
|
|
495
767
|
process.exit(path ? 0 : 2);
|
|
496
768
|
}
|
|
497
769
|
const only = flags.includes("--only") ? flags[flags.indexOf("--only") + 1].split(",") : null;
|
|
770
|
+
if (flags.includes("--devnet")) {
|
|
771
|
+
await devnetShip(path, { crawl: flags.includes("--crawl"), only, ai: flags.includes("--ai"),
|
|
772
|
+
port: flags.includes("--port") ? Number(flags[flags.indexOf("--port") + 1]) : 8787 })
|
|
773
|
+
.catch((e) => { console.error(`[ship] ✗ ${e.message}`); process.exit(1); });
|
|
774
|
+
process.exit(0);
|
|
775
|
+
}
|
|
776
|
+
if (flags.includes("--index")) {
|
|
777
|
+
await indexHere(path).catch((e) => { console.error(`[ship] ✗ ${e.message}`); process.exit(1); });
|
|
778
|
+
process.exit(0);
|
|
779
|
+
}
|
|
498
780
|
if (flags.includes("--local")) {
|
|
499
781
|
await buildLocal(path, resolve(flags[flags.indexOf("--local") + 1]), { crawl: flags.includes("--crawl"), only })
|
|
500
782
|
.catch((e) => { console.error(`[ship] ✗ ${e.message}`); process.exit(1); });
|
|
501
783
|
process.exit(0);
|
|
502
784
|
}
|
|
503
785
|
await ship(path, { crawl: !flags.includes("--no-crawl"), deploy: !flags.includes("--no-deploy"), dry: flags.includes("--dry-run"),
|
|
504
|
-
replace: flags.includes("--replace"), register: flags.includes("--register"),
|
|
505
|
-
only })
|
|
786
|
+
replace: flags.includes("--replace"), register: flags.includes("--register"), only })
|
|
506
787
|
.catch((e) => { console.error(`[ship] ✗ ${e.message}`); process.exit(1); });
|
|
507
788
|
process.exit(0); // a CUDA embedder keeps the process alive (and can abort in its teardown)
|
|
508
789
|
}
|