@koda-sl/baker-cli 0.453.0-dev.faf35eacb → 0.453.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/{account-4H7QVBRK.js → account-F6MHQ64I.js} +3 -3
- package/dist/{account-2SVI5OK7.js → account-HS7SDSEB.js} +4 -4
- package/dist/{account-writes-AK42HGRM.js → account-writes-ISE4U2FQ.js} +3 -3
- package/dist/{accounts-2XAOZX3U.js → accounts-3JKABIGU.js} +3 -3
- package/dist/{accounts-U4BGY3QU.js → accounts-ZZONEKPU.js} +5 -5
- package/dist/{actions-6EJU7CAQ.js → actions-VKHKHUUC.js} +18 -18
- package/dist/{ad-group-writes-QQS4223F.js → ad-group-writes-UU5NNKSO.js} +3 -3
- package/dist/{ad-groups-WGPDPUI2.js → ad-groups-CGFXZNIA.js} +7 -7
- package/dist/{ad-writes-XE2IE4DS.js → ad-writes-NALDYWOR.js} +3 -3
- package/dist/{add-IPGEYQQG.js → add-53DDLTVG.js} +2 -2
- package/dist/{add-P7CCVY2J.js → add-ATD4MBOO.js} +3 -3
- package/dist/{ads-BCOSQALY.js → ads-PV7GZXPR.js} +8 -8
- package/dist/{ads-XQCGFJOE.js → ads-QFAUJCWM.js} +4 -4
- package/dist/{advertisers-JG4DZ6SB.js → advertisers-HYUASJAS.js} +3 -3
- package/dist/{analytics-RNEUAKC4.js → analytics-RQROMSQ3.js} +3 -3
- package/dist/{analytics-IMYVNQIX.js → analytics-WLYXY5OT.js} +37 -19
- package/dist/analytics-WLYXY5OT.js.map +1 -0
- package/dist/{animate-AIZ3GS4J.js → animate-FWWHSYPL.js} +4 -4
- package/dist/{assign-46TXQRT5.js → assign-7332DMG5.js} +3 -3
- package/dist/{audience-size-5TNQO3IL.js → audience-size-UYX5RPTA.js} +3 -3
- package/dist/{audiences-TGD5US2M.js → audiences-VEKGLWBG.js} +3 -3
- package/dist/{audit-XRE7XH3Q.js → audit-ZGTG7VRD.js} +3 -3
- package/dist/{autocomplete-2ZWSQ6QQ.js → autocomplete-LPG4WWTY.js} +3 -3
- package/dist/{avatars-M6NSD3KS.js → avatars-HTQMIA2F.js} +4 -4
- package/dist/{bid-pricing-OIVPZCWW.js → bid-pricing-IK2C2IKE.js} +3 -3
- package/dist/{brand-YDNX7LYQ.js → brand-3WCTQWWX.js} +2 -2
- package/dist/{brief-PAHJODHS.js → brief-WDKTAKVA.js} +10 -10
- package/dist/{campaign-groups-TAP6LK6F.js → campaign-groups-6CJ45XMO.js} +8 -8
- package/dist/{campaign-writes-4SWPBZE6.js → campaign-writes-2MLRVFJK.js} +3 -3
- package/dist/{campaign-writes-SXLZFYF6.js → campaign-writes-SX52CJYW.js} +3 -3
- package/dist/{campaigns-AQAJN23T.js → campaigns-DTH5ADA7.js} +5 -5
- package/dist/{campaigns-YS3OGQXO.js → campaigns-X3DPCCSN.js} +7 -7
- package/dist/{campaigns-ROMLENHI.js → campaigns-ZVA7SUNN.js} +11 -11
- package/dist/{canvas-SMYHQUL5.js → canvas-EJLQ27CK.js} +14 -14
- package/dist/{capabilities-GGPTIAFO.js → capabilities-VWJGP43K.js} +3 -3
- package/dist/{catalog-GWN3WT7J.js → catalog-BKQOAVR2.js} +7 -7
- package/dist/{catalogs-BRXEDE56.js → catalogs-G3YEVG6S.js} +3 -3
- package/dist/{chatgpt-7W75X3ER.js → chatgpt-QNUN63YS.js} +13 -13
- package/dist/{chats-G343BSVB.js → chats-JOZKLOFU.js} +3 -3
- package/dist/chunk-237WE5LX.js +455 -0
- package/dist/chunk-237WE5LX.js.map +1 -0
- package/dist/{chunk-7J6EHOH6.js → chunk-35XWJEWH.js} +2 -2
- package/dist/{chunk-OKJWBB2K.js → chunk-3EUFAONS.js} +8 -8
- package/dist/{chunk-YCOP4FGJ.js → chunk-3JCF3NK6.js} +2 -2
- package/dist/{chunk-Q4JYX424.js → chunk-3XPFBSD7.js} +6 -6
- package/dist/chunk-3XPFBSD7.js.map +1 -0
- package/dist/{chunk-UC7VFFXR.js → chunk-4GOS37IA.js} +2 -2
- package/dist/{chunk-EIVYFZ4N.js → chunk-4JHUG6Y3.js} +2 -2
- package/dist/{chunk-D6744YSX.js → chunk-54UVGFX3.js} +4 -4
- package/dist/{chunk-3YOFT3AP.js → chunk-5PKB7LNI.js} +2 -2
- package/dist/{chunk-WVIJD2PP.js → chunk-5RPU6KG5.js} +8 -8
- package/dist/chunk-5RPU6KG5.js.map +1 -0
- package/dist/{chunk-TUB34FVS.js → chunk-5YGBRHO6.js} +4 -4
- package/dist/{chunk-YT7SSJFR.js → chunk-6QJLVNGU.js} +5 -5
- package/dist/{chunk-NJXNU2G6.js → chunk-7ZHMGXE2.js} +3 -3
- package/dist/{chunk-26NULPNS.js → chunk-BBDAVFTG.js} +26 -20
- package/dist/{chunk-26NULPNS.js.map → chunk-BBDAVFTG.js.map} +1 -1
- package/dist/{chunk-CAMX6OHF.js → chunk-CHSNHVYF.js} +4 -4
- package/dist/chunk-CHSNHVYF.js.map +1 -0
- package/dist/{chunk-IWP5MA6B.js → chunk-D76QEIFG.js} +6 -6
- package/dist/{chunk-F4F3PLN3.js → chunk-DGPLXY37.js} +2 -2
- package/dist/{chunk-5ARG3CQG.js → chunk-DTD2IRDA.js} +13 -5
- package/dist/chunk-DTD2IRDA.js.map +1 -0
- package/dist/{chunk-B6GITCGN.js → chunk-E6KL5ZRN.js} +2 -2
- package/dist/{chunk-3URK7DAA.js → chunk-EKCPMVZ3.js} +2 -2
- package/dist/{chunk-BAUSWB7O.js → chunk-EUFCY63X.js} +4 -4
- package/dist/chunk-EUFCY63X.js.map +1 -0
- package/dist/{chunk-4KF5MNHA.js → chunk-F4CUV4FV.js} +9 -9
- package/dist/{chunk-VCZOJSBL.js → chunk-G7HYW5TH.js} +3 -3
- package/dist/{chunk-P5T7OZKV.js → chunk-HQSCJYZO.js} +2 -2
- package/dist/chunk-HTW7FTP4.js +45 -0
- package/dist/chunk-HTW7FTP4.js.map +1 -0
- package/dist/{chunk-WDZOVF6P.js → chunk-JYWZRDLZ.js} +3 -3
- package/dist/{chunk-W2TJIZS2.js → chunk-K46X77UI.js} +3 -3
- package/dist/{chunk-TX7LZWZD.js → chunk-KFE3S63W.js} +50 -20
- package/dist/chunk-KFE3S63W.js.map +1 -0
- package/dist/{chunk-FTWJFDIN.js → chunk-KLBTXETO.js} +169 -169
- package/dist/chunk-KLBTXETO.js.map +1 -0
- package/dist/{chunk-HZUDRKGR.js → chunk-KQTYTCKQ.js} +2 -2
- package/dist/{chunk-VC7IJPTV.js → chunk-KRZV6VS2.js} +2 -2
- package/dist/{chunk-W3ZHJJVT.js → chunk-KS2QY6MK.js} +4 -4
- package/dist/{chunk-64WGDUXH.js → chunk-LF2PIA67.js} +3 -3
- package/dist/{chunk-VNUNE5AB.js → chunk-LYBXCXJB.js} +2 -2
- package/dist/{chunk-UX5HYEN3.js → chunk-M4G3NKDB.js} +2 -2
- package/dist/{chunk-OVT676K6.js → chunk-MM22NUA6.js} +2 -2
- package/dist/{chunk-UNI5AASR.js → chunk-MM5XO4MP.js} +2 -2
- package/dist/{chunk-NR2TDASR.js → chunk-MZ3SJ6FV.js} +2 -2
- package/dist/{chunk-FJGPG7H2.js → chunk-PROV2XDR.js} +3 -3
- package/dist/{chunk-ND5MJ3AA.js → chunk-PZ7EPJRF.js} +2 -2
- package/dist/{chunk-355DHOHD.js → chunk-QZOS6FXW.js} +2 -2
- package/dist/{chunk-MURPCCLG.js → chunk-RNDPEHCH.js} +2 -2
- package/dist/{chunk-TIASGFTA.js → chunk-TJFELVYS.js} +3 -3
- package/dist/{chunk-XFGAUP5B.js → chunk-ULJ5RFOV.js} +2 -2
- package/dist/{chunk-PZXCWSKV.js → chunk-VB6WJJR3.js} +2 -2
- package/dist/{chunk-QJNTI36W.js → chunk-XX53CHKP.js} +3 -3
- package/dist/{chunk-TESII3FM.js → chunk-XXHMVDLL.js} +2 -2
- package/dist/{chunk-EEEMQ7IY.js → chunk-YIWXHIVZ.js} +3 -3
- package/dist/{chunk-LZ73CZGP.js → chunk-YKFEHWHC.js} +2 -2
- package/dist/{chunk-W5VGWEAG.js → chunk-YTNJODFK.js} +2 -2
- package/dist/{chunk-V4RGDJAN.js → chunk-Z6XEMPOZ.js} +2 -2
- package/dist/{chunk-6Y7SIP2D.js → chunk-ZIHEIVJC.js} +54 -27
- package/dist/chunk-ZIHEIVJC.js.map +1 -0
- package/dist/{chunk-LZSXZB2F.js → chunk-ZWKPXWS5.js} +2 -2
- package/dist/{claim-INJHV4FM.js → claim-ITPOQ2I7.js} +3 -3
- package/dist/cli.js +32 -32
- package/dist/{companies-3REQZWKU.js → companies-Y5HPLJF5.js} +3 -3
- package/dist/{complete-DX2DAAEZ.js → complete-SRXMUS4R.js} +3 -3
- package/dist/{connect-O4OGKMAK.js → connect-CRHMP3NK.js} +6 -6
- package/dist/{conversation-6A2X5W64.js → conversation-BBOPGOMH.js} +3 -3
- package/dist/{conversion-report-XQBJ6LG4.js → conversion-report-N4HANYOB.js} +2 -2
- package/dist/{conversion-writes-UBCVXP2E.js → conversion-writes-LW4HQKK5.js} +3 -3
- package/dist/{conversions-PT6UDIX3.js → conversions-DH3Q6UTJ.js} +7 -7
- package/dist/{conversions-D236RTUK.js → conversions-TS723AZV.js} +5 -5
- package/dist/{create-I2UB7FWX.js → create-CW5E5Y2S.js} +3 -3
- package/dist/{create-EOLNJGAX.js → create-HOIUANHB.js} +2 -2
- package/dist/{create-BR3Q4A4C.js → create-M7TK4QUC.js} +3 -3
- package/dist/{create-S47CLYXJ.js → create-ZWSKHSPD.js} +3 -3
- package/dist/{creatives-ITL2SFA7.js → creatives-LLWNKS47.js} +10 -10
- package/dist/{creatives-FBH4DKSX.js → creatives-QQZNCA6V.js} +8 -8
- package/dist/{credentials-HKFHC4BR.js → credentials-CFKCUDES.js} +16 -16
- package/dist/credentials-CFKCUDES.js.map +1 -0
- package/dist/{critique-MCSROATT.js → critique-AQDXRULE.js} +9 -9
- package/dist/{critique-LU27A765.js → critique-ORSUUTHE.js} +3 -3
- package/dist/{crm-NEI6E2T2.js → crm-EPSZEOD2.js} +4 -4
- package/dist/{crm-GOE6ZO5N.js → crm-WNFEWAAM.js} +4 -4
- package/dist/{customers-HGBZPL6D.js → customers-NZDE7V3P.js} +6 -6
- package/dist/{delete-67XFFHUY.js → delete-6RAKC62Z.js} +2 -2
- package/dist/{delete-BT7I3EMN.js → delete-7HMQBDFC.js} +3 -3
- package/dist/{delete-ONE22VRX.js → delete-FKXR5ZOP.js} +3 -3
- package/dist/{demographics-R4LCQGDJ.js → demographics-PRZS2ZC4.js} +3 -3
- package/dist/{discard-BOPTV4V5.js → discard-YLTMCFE6.js} +3 -3
- package/dist/{domain-B54ILSOO.js → domain-Z533IR5M.js} +3 -3
- package/dist/{draft-BGUJDHXR.js → draft-3ZVCQSAG.js} +2 -2
- package/dist/{draft-DWSBVJUF.js → draft-4PURIMDM.js} +3 -3
- package/dist/{draft-ROERDGSK.js → draft-7ZWPABVG.js} +4 -4
- package/dist/{draft-MNFJ4G3C.js → draft-THPZFASM.js} +3 -3
- package/dist/{draft-J4SGNGGW.js → draft-ZXZEQRCL.js} +3 -3
- package/dist/engine/index.js +6 -6
- package/dist/{experiment-LKK77YAN.js → experiment-PYQPAZZN.js} +31 -11
- package/dist/experiment-PYQPAZZN.js.map +1 -0
- package/dist/{facets-FUTIWQXK.js → facets-453OFMAM.js} +3 -3
- package/dist/{feedback-XXEGMLPS.js → feedback-JFV6BMJW.js} +2 -2
- package/dist/{feedback-record-E3PLIVW7.js → feedback-record-HCOBRTY2.js} +11 -11
- package/dist/{fetch-D7JPGEYZ.js → fetch-44PPB6UO.js} +3 -3
- package/dist/{find-OTDIX6V4.js → find-FRRHAPWX.js} +4 -4
- package/dist/{fit-BOQL6OK3.js → fit-7237F5EB.js} +5 -5
- package/dist/{flows-L6BF3TDT.js → flows-N7UZ4ZO5.js} +10 -10
- package/dist/{fonts-THAAZY7Z.js → fonts-YW7SU5EY.js} +5 -5
- package/dist/{forecast-RYYVMNYT.js → forecast-W57WFVDL.js} +3 -3
- package/dist/{funnel-SK7WDPRS.js → funnel-VW542JTE.js} +8 -451
- package/dist/funnel-VW542JTE.js.map +1 -0
- package/dist/{ga4-UDQ2HA2J.js → ga4-DV2AXCSL.js} +1 -1
- package/dist/{generate-3XYYITU6.js → generate-MMBYB44G.js} +3 -3
- package/dist/{get-3STPGBX3.js → get-2NA3LPCN.js} +3 -3
- package/dist/{get-YBIF3H52.js → get-MEGPYKQR.js} +3 -3
- package/dist/{google-KTO3PLEU.js → google-QYYYSSXI.js} +5 -5
- package/dist/{google-ads-YD7LYWGY.js → google-ads-G2XD4RMT.js} +3 -3
- package/dist/{gsc-P3QYGDV2.js → gsc-TAKOF7J5.js} +1 -1
- package/dist/{history-Q3QCT64S.js → history-DBIEYQHY.js} +3 -3
- package/dist/{history-RWWBR4TN.js → history-RCKMMUWI.js} +3 -3
- package/dist/{hubspot-E65QNOI4.js → hubspot-CKS7F77W.js} +7 -7
- package/dist/{icon-I43F77FV.js → icon-L4RSS6PZ.js} +4 -4
- package/dist/{ideas-ZB7O7T5I.js → ideas-EYC6K33G.js} +2 -2
- package/dist/{images-U4DKZQTE.js → images-2HJ7YKTH.js} +9 -9
- package/dist/{ingest-MZXGMFA5.js → ingest-26O5Q462.js} +3 -3
- package/dist/{ingest-ROEMTZMG.js → ingest-RR5GAX3G.js} +2 -2
- package/dist/{insights-LNAXV2CG.js → insights-ZIU6FJMS.js} +2 -2
- package/dist/{inspect-MCMUJPXU.js → inspect-XPDUPSUP.js} +3 -3
- package/dist/{inspiration-P265QJ2H.js → inspiration-S3DU4CMQ.js} +1 -1
- package/dist/{keyword-metrics-MLFTDAS4.js → keyword-metrics-KOPXTILA.js} +3 -3
- package/dist/{keywords-for-site-4WUBJNTI.js → keywords-for-site-PONNELI5.js} +3 -3
- package/dist/{landing-5GDWJN4C.js → landing-RYRZ5VZ2.js} +3 -3
- package/dist/{leads-LDOOJI5K.js → leads-R7YSJDIG.js} +3 -3
- package/dist/{learn-3JPH2EZC.js → learn-IZ6QKTCX.js} +7 -7
- package/dist/{line-item-writes-LFDWJBQG.js → line-item-writes-25FE22UX.js} +3 -3
- package/dist/{line-items-T3EVXB4M.js → line-items-V522VKSU.js} +5 -5
- package/dist/{link-M5ESAD5Y.js → link-ZZZVDSUP.js} +3 -3
- package/dist/{linkedin-4FNKZNIR.js → linkedin-HXLYJVGI.js} +26 -26
- package/dist/{list-XWUTPJRQ.js → list-HSXOTG5R.js} +2 -2
- package/dist/{list-EF6QI4JT.js → list-KYEK3N34.js} +3 -3
- package/dist/{list-FNW254C3.js → list-LKHHCQZU.js} +3 -3
- package/dist/{locations-2NKBJKBT.js → locations-74TPQWAT.js} +2 -2
- package/dist/{log-5MESJJLM.js → log-6ELXS5EJ.js} +3 -3
- package/dist/{logo-2IP663QO.js → logo-Q5D7PZY6.js} +3 -3
- package/dist/{lookup-XK45W2SH.js → lookup-TCU2ZEZW.js} +3 -3
- package/dist/{make-BH5TNJUO.js → make-2PV4B2TT.js} +25 -22
- package/dist/{make-BH5TNJUO.js.map → make-2PV4B2TT.js.map} +1 -1
- package/dist/{map-5NONUYFP.js → map-SJLRAB5X.js} +4 -4
- package/dist/map-SJLRAB5X.js.map +1 -0
- package/dist/{mcp-CGITBQQA.js → mcp-W7DBSUF7.js} +2 -2
- package/dist/{media-sources-6TFUGPE2.js → media-sources-M35YZQY5.js} +2 -2
- package/dist/{members-R2GQVSVA.js → members-QXYMJQWU.js} +3 -3
- package/dist/{models-3Z6MDHQW.js → models-7U3GRYHF.js} +2 -2
- package/dist/{normalize-L6WBNGQA.js → normalize-DYJLUBQP.js} +3 -3
- package/dist/{options-YT2ZGB2Z.js → options-77SHB5KA.js} +6 -6
- package/dist/{performance-52IILAFE.js → performance-VVTYUSTW.js} +4 -4
- package/dist/{pixels-TZPF746S.js → pixels-45A55MOP.js} +3 -3
- package/dist/plan-4QP2G7GV.js +207 -0
- package/dist/plan-4QP2G7GV.js.map +1 -0
- package/dist/{promoted-tweet-writes-QVIKWEIZ.js → promoted-tweet-writes-QP37NMQC.js} +3 -3
- package/dist/{promoted-tweets-2AALRV5O.js → promoted-tweets-WPSM6MGX.js} +3 -3
- package/dist/{publish-XFLOZAOQ.js → publish-YF6ESBGQ.js} +3 -3
- package/dist/questionnaire-J56AATOX.js +93 -0
- package/dist/questionnaire-J56AATOX.js.map +1 -0
- package/dist/{read-LSF7DYTG.js → read-G5AC332V.js} +9 -8
- package/dist/read-G5AC332V.js.map +1 -0
- package/dist/{reddit-HFBAGWNP.js → reddit-VC3IOFNC.js} +4 -4
- package/dist/{refresh-INKIU3QV.js → refresh-UXBCTVCG.js} +2 -2
- package/dist/{release-C2MFXAPJ.js → release-PP5S5QHD.js} +3 -3
- package/dist/{remove-JWSVNUJ4.js → remove-OUWQ7NPN.js} +2 -2
- package/dist/{reopen-3GM4OSLZ.js → reopen-KXAM5LVX.js} +3 -3
- package/dist/{rerun-7K675UFG.js → rerun-ZVFP4FNN.js} +21 -21
- package/dist/{research-HUMDT7T4.js → research-52CURSLR.js} +2 -2
- package/dist/{research-ZMYQWEAT.js → research-WEW3TISC.js} +10 -10
- package/dist/{reset-3BEGDSBW.js → reset-MVLV7GEB.js} +2 -2
- package/dist/{reset-TVDYP2VK.js → reset-RBL2NSDV.js} +2 -2
- package/dist/{resolve-OQKUGWEI.js → resolve-JG3TGLCR.js} +3 -3
- package/dist/{retry-GUHOWYO2.js → retry-2B3V4CJO.js} +2 -2
- package/dist/{retry-EJBF6GBJ.js → retry-Y3VUKOZ3.js} +2 -2
- package/dist/{run-3D6QZC6L.js → run-3BCAHPCW.js} +3 -3
- package/dist/{run-7VWIB2KF.js → run-FZL7FYTL.js} +21 -21
- package/dist/{scaffold-ad-WYSO6VRA.js → scaffold-ad-TL5URKZF.js} +17 -17
- package/dist/{scaffold-static-ad-JYKHAAYB.js → scaffold-static-ad-PN6RR3IP.js} +41 -41
- package/dist/scaffold-static-ad-PN6RR3IP.js.map +1 -0
- package/dist/{scaffold-video-KFBLDBVW.js → scaffold-video-LRTELU5X.js} +15 -15
- package/dist/{schedule-QVYVMRIY.js → schedule-7QLWW5HA.js} +2 -2
- package/dist/{scheduled-agents-CNIGCLAP.js → scheduled-agents-HDALX27W.js} +11 -11
- package/dist/{schema-7UUVQ4RH.js → schema-EY3Y3KFB.js} +4 -4
- package/dist/{search-XUGIKFW3.js → search-4XS7AO5R.js} +2 -2
- package/dist/{select-FQVDWCMG.js → select-J3QVFWVE.js} +11 -11
- package/dist/{set-prompt-XCICIA6U.js → set-prompt-MEMSZEZK.js} +11 -11
- package/dist/{setup-links-4VILWID2.js → setup-links-4PCSBEJD.js} +3 -3
- package/dist/{site-TLJVSZQF.js → site-IG62FRYT.js} +4 -4
- package/dist/{sources-4MKBWFWG.js → sources-5YFONIJP.js} +6 -6
- package/dist/{sources-7FCRKPXJ.js → sources-AM7SIZUY.js} +6 -6
- package/dist/{stale-TVTZNIB7.js → stale-UMI4XCKX.js} +10 -10
- package/dist/{status-WZKAKUG4.js → status-F4XMET7U.js} +3 -3
- package/dist/{stock-2YCDN3N6.js → stock-KXJ26BY4.js} +5 -5
- package/dist/{studio-RA7QQH4O.js → studio-EKIPLLHD.js} +4 -4
- package/dist/{sync-DEWLEMTT.js → sync-LXYAJ7SU.js} +2 -2
- package/dist/{sync-definition-LGX25ZTO.js → sync-definition-WU6V5245.js} +14 -14
- package/dist/{tag-manager-DYZUOIAS.js → tag-manager-JAAMOUGB.js} +9 -9
- package/dist/{tags-RBB4TU7U.js → tags-6WV3PQH5.js} +3 -3
- package/dist/{tags-O76MDVID.js → tags-FTOD6CYS.js} +3 -3
- package/dist/{tags-V5FR3DYU.js → tags-KXNOWUMN.js} +2 -2
- package/dist/{tags-IWN3QPK5.js → tags-PJRNCPQJ.js} +3 -3
- package/dist/{tags-ANV54THE.js → tags-R5JCZRAV.js} +4 -4
- package/dist/{targeting-criteria-6IOXVE4K.js → targeting-criteria-HJ7YFAB2.js} +3 -3
- package/dist/{targets-FFLV6OMF.js → targets-RLZSBUCI.js} +3 -3
- package/dist/{templates-Y3ZLB7MK.js → templates-4SWUBLOC.js} +3 -3
- package/dist/{testimonials-FCYS4NKB.js → testimonials-YYLALSV4.js} +6 -6
- package/dist/{top-companies-BL4F7OPI.js → top-companies-SNOQQFUS.js} +3 -3
- package/dist/{translate-6RKX54GS.js → translate-CZRVAEUQ.js} +15 -15
- package/dist/{triggers-6Q3YRHZS.js → triggers-MFDFO2Y4.js} +3 -3
- package/dist/{unlink-WDSOVBFL.js → unlink-BVDKP7VQ.js} +3 -3
- package/dist/{unstage-4ZEULXEQ.js → unstage-V7GMMOWR.js} +2 -2
- package/dist/{unstage-JYJIKPB5.js → unstage-ZTOQEWRC.js} +3 -3
- package/dist/{update-WSG6RVFX.js → update-R46SJ55S.js} +2 -2
- package/dist/{update-IXSCDBM4.js → update-R572VWKO.js} +3 -3
- package/dist/{update-FUYXN33G.js → update-SQEWFFFG.js} +3 -3
- package/dist/{update-CWZVKOUB.js → update-VVK6SWSI.js} +3 -3
- package/dist/{upload-CKV2XS3G.js → upload-TUYWMS6E.js} +3 -3
- package/dist/{url-params-UJSNI4XC.js → url-params-CU3KVLI5.js} +3 -3
- package/dist/{validate-L76CIYDT.js → validate-WPJXPGWS.js} +12 -12
- package/dist/{videos-FAKURRTG.js → videos-KGNLYRUS.js} +5 -5
- package/dist/{viewer-check-L4B4LIAX.js → viewer-check-45T2HLAH.js} +4 -4
- package/dist/{web-RRGHSAFY.js → web-IEJBIF7U.js} +3 -3
- package/dist/{winning-ads-IJTD4TU3.js → winning-ads-GGQ3DZXX.js} +3 -3
- package/dist/{write-commands-TAG7DQKT.js → write-commands-CXRXLXX2.js} +4 -4
- package/dist/{write-commands-ERKVRHTC.js → write-commands-VWSLDBPT.js} +2 -2
- package/dist/{x-FBWHL3PL.js → x-WHE6PSDD.js} +6 -6
- package/package.json +1 -1
- package/dist/analytics-IMYVNQIX.js.map +0 -1
- package/dist/chunk-5ARG3CQG.js.map +0 -1
- package/dist/chunk-6Y7SIP2D.js.map +0 -1
- package/dist/chunk-BAUSWB7O.js.map +0 -1
- package/dist/chunk-CAMX6OHF.js.map +0 -1
- package/dist/chunk-FTWJFDIN.js.map +0 -1
- package/dist/chunk-MELOSESI.js +0 -20
- package/dist/chunk-MELOSESI.js.map +0 -1
- package/dist/chunk-Q4JYX424.js.map +0 -1
- package/dist/chunk-TX7LZWZD.js.map +0 -1
- package/dist/chunk-WVIJD2PP.js.map +0 -1
- package/dist/credentials-HKFHC4BR.js.map +0 -1
- package/dist/experiment-LKK77YAN.js.map +0 -1
- package/dist/funnel-SK7WDPRS.js.map +0 -1
- package/dist/map-5NONUYFP.js.map +0 -1
- package/dist/read-LSF7DYTG.js.map +0 -1
- package/dist/scaffold-static-ad-JYKHAAYB.js.map +0 -1
- /package/dist/{account-4H7QVBRK.js.map → account-F6MHQ64I.js.map} +0 -0
- /package/dist/{account-2SVI5OK7.js.map → account-HS7SDSEB.js.map} +0 -0
- /package/dist/{account-writes-AK42HGRM.js.map → account-writes-ISE4U2FQ.js.map} +0 -0
- /package/dist/{accounts-2XAOZX3U.js.map → accounts-3JKABIGU.js.map} +0 -0
- /package/dist/{accounts-U4BGY3QU.js.map → accounts-ZZONEKPU.js.map} +0 -0
- /package/dist/{actions-6EJU7CAQ.js.map → actions-VKHKHUUC.js.map} +0 -0
- /package/dist/{ad-group-writes-QQS4223F.js.map → ad-group-writes-UU5NNKSO.js.map} +0 -0
- /package/dist/{ad-groups-WGPDPUI2.js.map → ad-groups-CGFXZNIA.js.map} +0 -0
- /package/dist/{ad-writes-XE2IE4DS.js.map → ad-writes-NALDYWOR.js.map} +0 -0
- /package/dist/{add-IPGEYQQG.js.map → add-53DDLTVG.js.map} +0 -0
- /package/dist/{add-P7CCVY2J.js.map → add-ATD4MBOO.js.map} +0 -0
- /package/dist/{ads-BCOSQALY.js.map → ads-PV7GZXPR.js.map} +0 -0
- /package/dist/{ads-XQCGFJOE.js.map → ads-QFAUJCWM.js.map} +0 -0
- /package/dist/{advertisers-JG4DZ6SB.js.map → advertisers-HYUASJAS.js.map} +0 -0
- /package/dist/{analytics-RNEUAKC4.js.map → analytics-RQROMSQ3.js.map} +0 -0
- /package/dist/{animate-AIZ3GS4J.js.map → animate-FWWHSYPL.js.map} +0 -0
- /package/dist/{assign-46TXQRT5.js.map → assign-7332DMG5.js.map} +0 -0
- /package/dist/{audience-size-5TNQO3IL.js.map → audience-size-UYX5RPTA.js.map} +0 -0
- /package/dist/{audiences-TGD5US2M.js.map → audiences-VEKGLWBG.js.map} +0 -0
- /package/dist/{audit-XRE7XH3Q.js.map → audit-ZGTG7VRD.js.map} +0 -0
- /package/dist/{autocomplete-2ZWSQ6QQ.js.map → autocomplete-LPG4WWTY.js.map} +0 -0
- /package/dist/{avatars-M6NSD3KS.js.map → avatars-HTQMIA2F.js.map} +0 -0
- /package/dist/{bid-pricing-OIVPZCWW.js.map → bid-pricing-IK2C2IKE.js.map} +0 -0
- /package/dist/{brand-YDNX7LYQ.js.map → brand-3WCTQWWX.js.map} +0 -0
- /package/dist/{brief-PAHJODHS.js.map → brief-WDKTAKVA.js.map} +0 -0
- /package/dist/{campaign-groups-TAP6LK6F.js.map → campaign-groups-6CJ45XMO.js.map} +0 -0
- /package/dist/{campaign-writes-4SWPBZE6.js.map → campaign-writes-2MLRVFJK.js.map} +0 -0
- /package/dist/{campaign-writes-SXLZFYF6.js.map → campaign-writes-SX52CJYW.js.map} +0 -0
- /package/dist/{campaigns-AQAJN23T.js.map → campaigns-DTH5ADA7.js.map} +0 -0
- /package/dist/{campaigns-YS3OGQXO.js.map → campaigns-X3DPCCSN.js.map} +0 -0
- /package/dist/{campaigns-ROMLENHI.js.map → campaigns-ZVA7SUNN.js.map} +0 -0
- /package/dist/{canvas-SMYHQUL5.js.map → canvas-EJLQ27CK.js.map} +0 -0
- /package/dist/{capabilities-GGPTIAFO.js.map → capabilities-VWJGP43K.js.map} +0 -0
- /package/dist/{catalog-GWN3WT7J.js.map → catalog-BKQOAVR2.js.map} +0 -0
- /package/dist/{catalogs-BRXEDE56.js.map → catalogs-G3YEVG6S.js.map} +0 -0
- /package/dist/{chatgpt-7W75X3ER.js.map → chatgpt-QNUN63YS.js.map} +0 -0
- /package/dist/{chats-G343BSVB.js.map → chats-JOZKLOFU.js.map} +0 -0
- /package/dist/{chunk-7J6EHOH6.js.map → chunk-35XWJEWH.js.map} +0 -0
- /package/dist/{chunk-OKJWBB2K.js.map → chunk-3EUFAONS.js.map} +0 -0
- /package/dist/{chunk-YCOP4FGJ.js.map → chunk-3JCF3NK6.js.map} +0 -0
- /package/dist/{chunk-UC7VFFXR.js.map → chunk-4GOS37IA.js.map} +0 -0
- /package/dist/{chunk-EIVYFZ4N.js.map → chunk-4JHUG6Y3.js.map} +0 -0
- /package/dist/{chunk-D6744YSX.js.map → chunk-54UVGFX3.js.map} +0 -0
- /package/dist/{chunk-3YOFT3AP.js.map → chunk-5PKB7LNI.js.map} +0 -0
- /package/dist/{chunk-TUB34FVS.js.map → chunk-5YGBRHO6.js.map} +0 -0
- /package/dist/{chunk-YT7SSJFR.js.map → chunk-6QJLVNGU.js.map} +0 -0
- /package/dist/{chunk-NJXNU2G6.js.map → chunk-7ZHMGXE2.js.map} +0 -0
- /package/dist/{chunk-IWP5MA6B.js.map → chunk-D76QEIFG.js.map} +0 -0
- /package/dist/{chunk-F4F3PLN3.js.map → chunk-DGPLXY37.js.map} +0 -0
- /package/dist/{chunk-B6GITCGN.js.map → chunk-E6KL5ZRN.js.map} +0 -0
- /package/dist/{chunk-3URK7DAA.js.map → chunk-EKCPMVZ3.js.map} +0 -0
- /package/dist/{chunk-4KF5MNHA.js.map → chunk-F4CUV4FV.js.map} +0 -0
- /package/dist/{chunk-VCZOJSBL.js.map → chunk-G7HYW5TH.js.map} +0 -0
- /package/dist/{chunk-P5T7OZKV.js.map → chunk-HQSCJYZO.js.map} +0 -0
- /package/dist/{chunk-WDZOVF6P.js.map → chunk-JYWZRDLZ.js.map} +0 -0
- /package/dist/{chunk-W2TJIZS2.js.map → chunk-K46X77UI.js.map} +0 -0
- /package/dist/{chunk-HZUDRKGR.js.map → chunk-KQTYTCKQ.js.map} +0 -0
- /package/dist/{chunk-VC7IJPTV.js.map → chunk-KRZV6VS2.js.map} +0 -0
- /package/dist/{chunk-W3ZHJJVT.js.map → chunk-KS2QY6MK.js.map} +0 -0
- /package/dist/{chunk-64WGDUXH.js.map → chunk-LF2PIA67.js.map} +0 -0
- /package/dist/{chunk-VNUNE5AB.js.map → chunk-LYBXCXJB.js.map} +0 -0
- /package/dist/{chunk-UX5HYEN3.js.map → chunk-M4G3NKDB.js.map} +0 -0
- /package/dist/{chunk-OVT676K6.js.map → chunk-MM22NUA6.js.map} +0 -0
- /package/dist/{chunk-UNI5AASR.js.map → chunk-MM5XO4MP.js.map} +0 -0
- /package/dist/{chunk-NR2TDASR.js.map → chunk-MZ3SJ6FV.js.map} +0 -0
- /package/dist/{chunk-FJGPG7H2.js.map → chunk-PROV2XDR.js.map} +0 -0
- /package/dist/{chunk-ND5MJ3AA.js.map → chunk-PZ7EPJRF.js.map} +0 -0
- /package/dist/{chunk-355DHOHD.js.map → chunk-QZOS6FXW.js.map} +0 -0
- /package/dist/{chunk-MURPCCLG.js.map → chunk-RNDPEHCH.js.map} +0 -0
- /package/dist/{chunk-TIASGFTA.js.map → chunk-TJFELVYS.js.map} +0 -0
- /package/dist/{chunk-XFGAUP5B.js.map → chunk-ULJ5RFOV.js.map} +0 -0
- /package/dist/{chunk-PZXCWSKV.js.map → chunk-VB6WJJR3.js.map} +0 -0
- /package/dist/{chunk-QJNTI36W.js.map → chunk-XX53CHKP.js.map} +0 -0
- /package/dist/{chunk-TESII3FM.js.map → chunk-XXHMVDLL.js.map} +0 -0
- /package/dist/{chunk-EEEMQ7IY.js.map → chunk-YIWXHIVZ.js.map} +0 -0
- /package/dist/{chunk-LZ73CZGP.js.map → chunk-YKFEHWHC.js.map} +0 -0
- /package/dist/{chunk-W5VGWEAG.js.map → chunk-YTNJODFK.js.map} +0 -0
- /package/dist/{chunk-V4RGDJAN.js.map → chunk-Z6XEMPOZ.js.map} +0 -0
- /package/dist/{chunk-LZSXZB2F.js.map → chunk-ZWKPXWS5.js.map} +0 -0
- /package/dist/{claim-INJHV4FM.js.map → claim-ITPOQ2I7.js.map} +0 -0
- /package/dist/{companies-3REQZWKU.js.map → companies-Y5HPLJF5.js.map} +0 -0
- /package/dist/{complete-DX2DAAEZ.js.map → complete-SRXMUS4R.js.map} +0 -0
- /package/dist/{connect-O4OGKMAK.js.map → connect-CRHMP3NK.js.map} +0 -0
- /package/dist/{conversation-6A2X5W64.js.map → conversation-BBOPGOMH.js.map} +0 -0
- /package/dist/{conversion-report-XQBJ6LG4.js.map → conversion-report-N4HANYOB.js.map} +0 -0
- /package/dist/{conversion-writes-UBCVXP2E.js.map → conversion-writes-LW4HQKK5.js.map} +0 -0
- /package/dist/{conversions-PT6UDIX3.js.map → conversions-DH3Q6UTJ.js.map} +0 -0
- /package/dist/{conversions-D236RTUK.js.map → conversions-TS723AZV.js.map} +0 -0
- /package/dist/{create-I2UB7FWX.js.map → create-CW5E5Y2S.js.map} +0 -0
- /package/dist/{create-EOLNJGAX.js.map → create-HOIUANHB.js.map} +0 -0
- /package/dist/{create-BR3Q4A4C.js.map → create-M7TK4QUC.js.map} +0 -0
- /package/dist/{create-S47CLYXJ.js.map → create-ZWSKHSPD.js.map} +0 -0
- /package/dist/{creatives-ITL2SFA7.js.map → creatives-LLWNKS47.js.map} +0 -0
- /package/dist/{creatives-FBH4DKSX.js.map → creatives-QQZNCA6V.js.map} +0 -0
- /package/dist/{critique-MCSROATT.js.map → critique-AQDXRULE.js.map} +0 -0
- /package/dist/{critique-LU27A765.js.map → critique-ORSUUTHE.js.map} +0 -0
- /package/dist/{crm-NEI6E2T2.js.map → crm-EPSZEOD2.js.map} +0 -0
- /package/dist/{crm-GOE6ZO5N.js.map → crm-WNFEWAAM.js.map} +0 -0
- /package/dist/{customers-HGBZPL6D.js.map → customers-NZDE7V3P.js.map} +0 -0
- /package/dist/{delete-67XFFHUY.js.map → delete-6RAKC62Z.js.map} +0 -0
- /package/dist/{delete-BT7I3EMN.js.map → delete-7HMQBDFC.js.map} +0 -0
- /package/dist/{delete-ONE22VRX.js.map → delete-FKXR5ZOP.js.map} +0 -0
- /package/dist/{demographics-R4LCQGDJ.js.map → demographics-PRZS2ZC4.js.map} +0 -0
- /package/dist/{discard-BOPTV4V5.js.map → discard-YLTMCFE6.js.map} +0 -0
- /package/dist/{domain-B54ILSOO.js.map → domain-Z533IR5M.js.map} +0 -0
- /package/dist/{draft-BGUJDHXR.js.map → draft-3ZVCQSAG.js.map} +0 -0
- /package/dist/{draft-DWSBVJUF.js.map → draft-4PURIMDM.js.map} +0 -0
- /package/dist/{draft-ROERDGSK.js.map → draft-7ZWPABVG.js.map} +0 -0
- /package/dist/{draft-MNFJ4G3C.js.map → draft-THPZFASM.js.map} +0 -0
- /package/dist/{draft-J4SGNGGW.js.map → draft-ZXZEQRCL.js.map} +0 -0
- /package/dist/{facets-FUTIWQXK.js.map → facets-453OFMAM.js.map} +0 -0
- /package/dist/{feedback-XXEGMLPS.js.map → feedback-JFV6BMJW.js.map} +0 -0
- /package/dist/{feedback-record-E3PLIVW7.js.map → feedback-record-HCOBRTY2.js.map} +0 -0
- /package/dist/{fetch-D7JPGEYZ.js.map → fetch-44PPB6UO.js.map} +0 -0
- /package/dist/{find-OTDIX6V4.js.map → find-FRRHAPWX.js.map} +0 -0
- /package/dist/{fit-BOQL6OK3.js.map → fit-7237F5EB.js.map} +0 -0
- /package/dist/{flows-L6BF3TDT.js.map → flows-N7UZ4ZO5.js.map} +0 -0
- /package/dist/{fonts-THAAZY7Z.js.map → fonts-YW7SU5EY.js.map} +0 -0
- /package/dist/{forecast-RYYVMNYT.js.map → forecast-W57WFVDL.js.map} +0 -0
- /package/dist/{ga4-UDQ2HA2J.js.map → ga4-DV2AXCSL.js.map} +0 -0
- /package/dist/{generate-3XYYITU6.js.map → generate-MMBYB44G.js.map} +0 -0
- /package/dist/{get-3STPGBX3.js.map → get-2NA3LPCN.js.map} +0 -0
- /package/dist/{get-YBIF3H52.js.map → get-MEGPYKQR.js.map} +0 -0
- /package/dist/{google-KTO3PLEU.js.map → google-QYYYSSXI.js.map} +0 -0
- /package/dist/{google-ads-YD7LYWGY.js.map → google-ads-G2XD4RMT.js.map} +0 -0
- /package/dist/{gsc-P3QYGDV2.js.map → gsc-TAKOF7J5.js.map} +0 -0
- /package/dist/{history-Q3QCT64S.js.map → history-DBIEYQHY.js.map} +0 -0
- /package/dist/{history-RWWBR4TN.js.map → history-RCKMMUWI.js.map} +0 -0
- /package/dist/{hubspot-E65QNOI4.js.map → hubspot-CKS7F77W.js.map} +0 -0
- /package/dist/{icon-I43F77FV.js.map → icon-L4RSS6PZ.js.map} +0 -0
- /package/dist/{ideas-ZB7O7T5I.js.map → ideas-EYC6K33G.js.map} +0 -0
- /package/dist/{images-U4DKZQTE.js.map → images-2HJ7YKTH.js.map} +0 -0
- /package/dist/{ingest-MZXGMFA5.js.map → ingest-26O5Q462.js.map} +0 -0
- /package/dist/{ingest-ROEMTZMG.js.map → ingest-RR5GAX3G.js.map} +0 -0
- /package/dist/{insights-LNAXV2CG.js.map → insights-ZIU6FJMS.js.map} +0 -0
- /package/dist/{inspect-MCMUJPXU.js.map → inspect-XPDUPSUP.js.map} +0 -0
- /package/dist/{inspiration-P265QJ2H.js.map → inspiration-S3DU4CMQ.js.map} +0 -0
- /package/dist/{keyword-metrics-MLFTDAS4.js.map → keyword-metrics-KOPXTILA.js.map} +0 -0
- /package/dist/{keywords-for-site-4WUBJNTI.js.map → keywords-for-site-PONNELI5.js.map} +0 -0
- /package/dist/{landing-5GDWJN4C.js.map → landing-RYRZ5VZ2.js.map} +0 -0
- /package/dist/{leads-LDOOJI5K.js.map → leads-R7YSJDIG.js.map} +0 -0
- /package/dist/{learn-3JPH2EZC.js.map → learn-IZ6QKTCX.js.map} +0 -0
- /package/dist/{line-item-writes-LFDWJBQG.js.map → line-item-writes-25FE22UX.js.map} +0 -0
- /package/dist/{line-items-T3EVXB4M.js.map → line-items-V522VKSU.js.map} +0 -0
- /package/dist/{link-M5ESAD5Y.js.map → link-ZZZVDSUP.js.map} +0 -0
- /package/dist/{linkedin-4FNKZNIR.js.map → linkedin-HXLYJVGI.js.map} +0 -0
- /package/dist/{list-XWUTPJRQ.js.map → list-HSXOTG5R.js.map} +0 -0
- /package/dist/{list-EF6QI4JT.js.map → list-KYEK3N34.js.map} +0 -0
- /package/dist/{list-FNW254C3.js.map → list-LKHHCQZU.js.map} +0 -0
- /package/dist/{locations-2NKBJKBT.js.map → locations-74TPQWAT.js.map} +0 -0
- /package/dist/{log-5MESJJLM.js.map → log-6ELXS5EJ.js.map} +0 -0
- /package/dist/{logo-2IP663QO.js.map → logo-Q5D7PZY6.js.map} +0 -0
- /package/dist/{lookup-XK45W2SH.js.map → lookup-TCU2ZEZW.js.map} +0 -0
- /package/dist/{mcp-CGITBQQA.js.map → mcp-W7DBSUF7.js.map} +0 -0
- /package/dist/{media-sources-6TFUGPE2.js.map → media-sources-M35YZQY5.js.map} +0 -0
- /package/dist/{members-R2GQVSVA.js.map → members-QXYMJQWU.js.map} +0 -0
- /package/dist/{models-3Z6MDHQW.js.map → models-7U3GRYHF.js.map} +0 -0
- /package/dist/{normalize-L6WBNGQA.js.map → normalize-DYJLUBQP.js.map} +0 -0
- /package/dist/{options-YT2ZGB2Z.js.map → options-77SHB5KA.js.map} +0 -0
- /package/dist/{performance-52IILAFE.js.map → performance-VVTYUSTW.js.map} +0 -0
- /package/dist/{pixels-TZPF746S.js.map → pixels-45A55MOP.js.map} +0 -0
- /package/dist/{promoted-tweet-writes-QVIKWEIZ.js.map → promoted-tweet-writes-QP37NMQC.js.map} +0 -0
- /package/dist/{promoted-tweets-2AALRV5O.js.map → promoted-tweets-WPSM6MGX.js.map} +0 -0
- /package/dist/{publish-XFLOZAOQ.js.map → publish-YF6ESBGQ.js.map} +0 -0
- /package/dist/{reddit-HFBAGWNP.js.map → reddit-VC3IOFNC.js.map} +0 -0
- /package/dist/{refresh-INKIU3QV.js.map → refresh-UXBCTVCG.js.map} +0 -0
- /package/dist/{release-C2MFXAPJ.js.map → release-PP5S5QHD.js.map} +0 -0
- /package/dist/{remove-JWSVNUJ4.js.map → remove-OUWQ7NPN.js.map} +0 -0
- /package/dist/{reopen-3GM4OSLZ.js.map → reopen-KXAM5LVX.js.map} +0 -0
- /package/dist/{rerun-7K675UFG.js.map → rerun-ZVFP4FNN.js.map} +0 -0
- /package/dist/{research-HUMDT7T4.js.map → research-52CURSLR.js.map} +0 -0
- /package/dist/{research-ZMYQWEAT.js.map → research-WEW3TISC.js.map} +0 -0
- /package/dist/{reset-3BEGDSBW.js.map → reset-MVLV7GEB.js.map} +0 -0
- /package/dist/{reset-TVDYP2VK.js.map → reset-RBL2NSDV.js.map} +0 -0
- /package/dist/{resolve-OQKUGWEI.js.map → resolve-JG3TGLCR.js.map} +0 -0
- /package/dist/{retry-GUHOWYO2.js.map → retry-2B3V4CJO.js.map} +0 -0
- /package/dist/{retry-EJBF6GBJ.js.map → retry-Y3VUKOZ3.js.map} +0 -0
- /package/dist/{run-3D6QZC6L.js.map → run-3BCAHPCW.js.map} +0 -0
- /package/dist/{run-7VWIB2KF.js.map → run-FZL7FYTL.js.map} +0 -0
- /package/dist/{scaffold-ad-WYSO6VRA.js.map → scaffold-ad-TL5URKZF.js.map} +0 -0
- /package/dist/{scaffold-video-KFBLDBVW.js.map → scaffold-video-LRTELU5X.js.map} +0 -0
- /package/dist/{schedule-QVYVMRIY.js.map → schedule-7QLWW5HA.js.map} +0 -0
- /package/dist/{scheduled-agents-CNIGCLAP.js.map → scheduled-agents-HDALX27W.js.map} +0 -0
- /package/dist/{schema-7UUVQ4RH.js.map → schema-EY3Y3KFB.js.map} +0 -0
- /package/dist/{search-XUGIKFW3.js.map → search-4XS7AO5R.js.map} +0 -0
- /package/dist/{select-FQVDWCMG.js.map → select-J3QVFWVE.js.map} +0 -0
- /package/dist/{set-prompt-XCICIA6U.js.map → set-prompt-MEMSZEZK.js.map} +0 -0
- /package/dist/{setup-links-4VILWID2.js.map → setup-links-4PCSBEJD.js.map} +0 -0
- /package/dist/{site-TLJVSZQF.js.map → site-IG62FRYT.js.map} +0 -0
- /package/dist/{sources-4MKBWFWG.js.map → sources-5YFONIJP.js.map} +0 -0
- /package/dist/{sources-7FCRKPXJ.js.map → sources-AM7SIZUY.js.map} +0 -0
- /package/dist/{stale-TVTZNIB7.js.map → stale-UMI4XCKX.js.map} +0 -0
- /package/dist/{status-WZKAKUG4.js.map → status-F4XMET7U.js.map} +0 -0
- /package/dist/{stock-2YCDN3N6.js.map → stock-KXJ26BY4.js.map} +0 -0
- /package/dist/{studio-RA7QQH4O.js.map → studio-EKIPLLHD.js.map} +0 -0
- /package/dist/{sync-DEWLEMTT.js.map → sync-LXYAJ7SU.js.map} +0 -0
- /package/dist/{sync-definition-LGX25ZTO.js.map → sync-definition-WU6V5245.js.map} +0 -0
- /package/dist/{tag-manager-DYZUOIAS.js.map → tag-manager-JAAMOUGB.js.map} +0 -0
- /package/dist/{tags-RBB4TU7U.js.map → tags-6WV3PQH5.js.map} +0 -0
- /package/dist/{tags-O76MDVID.js.map → tags-FTOD6CYS.js.map} +0 -0
- /package/dist/{tags-V5FR3DYU.js.map → tags-KXNOWUMN.js.map} +0 -0
- /package/dist/{tags-IWN3QPK5.js.map → tags-PJRNCPQJ.js.map} +0 -0
- /package/dist/{tags-ANV54THE.js.map → tags-R5JCZRAV.js.map} +0 -0
- /package/dist/{targeting-criteria-6IOXVE4K.js.map → targeting-criteria-HJ7YFAB2.js.map} +0 -0
- /package/dist/{targets-FFLV6OMF.js.map → targets-RLZSBUCI.js.map} +0 -0
- /package/dist/{templates-Y3ZLB7MK.js.map → templates-4SWUBLOC.js.map} +0 -0
- /package/dist/{testimonials-FCYS4NKB.js.map → testimonials-YYLALSV4.js.map} +0 -0
- /package/dist/{top-companies-BL4F7OPI.js.map → top-companies-SNOQQFUS.js.map} +0 -0
- /package/dist/{translate-6RKX54GS.js.map → translate-CZRVAEUQ.js.map} +0 -0
- /package/dist/{triggers-6Q3YRHZS.js.map → triggers-MFDFO2Y4.js.map} +0 -0
- /package/dist/{unlink-WDSOVBFL.js.map → unlink-BVDKP7VQ.js.map} +0 -0
- /package/dist/{unstage-4ZEULXEQ.js.map → unstage-V7GMMOWR.js.map} +0 -0
- /package/dist/{unstage-JYJIKPB5.js.map → unstage-ZTOQEWRC.js.map} +0 -0
- /package/dist/{update-WSG6RVFX.js.map → update-R46SJ55S.js.map} +0 -0
- /package/dist/{update-IXSCDBM4.js.map → update-R572VWKO.js.map} +0 -0
- /package/dist/{update-FUYXN33G.js.map → update-SQEWFFFG.js.map} +0 -0
- /package/dist/{update-CWZVKOUB.js.map → update-VVK6SWSI.js.map} +0 -0
- /package/dist/{upload-CKV2XS3G.js.map → upload-TUYWMS6E.js.map} +0 -0
- /package/dist/{url-params-UJSNI4XC.js.map → url-params-CU3KVLI5.js.map} +0 -0
- /package/dist/{validate-L76CIYDT.js.map → validate-WPJXPGWS.js.map} +0 -0
- /package/dist/{videos-FAKURRTG.js.map → videos-KGNLYRUS.js.map} +0 -0
- /package/dist/{viewer-check-L4B4LIAX.js.map → viewer-check-45T2HLAH.js.map} +0 -0
- /package/dist/{web-RRGHSAFY.js.map → web-IEJBIF7U.js.map} +0 -0
- /package/dist/{winning-ads-IJTD4TU3.js.map → winning-ads-GGQ3DZXX.js.map} +0 -0
- /package/dist/{write-commands-TAG7DQKT.js.map → write-commands-CXRXLXX2.js.map} +0 -0
- /package/dist/{write-commands-ERKVRHTC.js.map → write-commands-VWSLDBPT.js.map} +0 -0
- /package/dist/{x-FBWHL3PL.js.map → x-WHE6PSDD.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/experiment/index.ts","../src/commands/experiment/composition.ts","../src/commands/experiment/fold.ts","../../api/src/experiments/hypothesis.ts","../../api/src/experiments/wait.ts","../src/commands/experiment/hints.ts","../src/commands/experiment/hypothesis.ts","../src/commands/experiment/identity.ts","../src/commands/experiment/measurement.ts","../src/commands/experiment/readout.ts","../src/commands/experiment/update.ts"],"sourcesContent":["import type {\n ExperimentFinishResponse,\n ExperimentFoldResponse,\n ExperimentHistoryResponse,\n ExperimentPlanResponse,\n ExperimentStatusResponse,\n ExperimentUpdateResponse,\n} from \"@baker/api/experiments\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../client.ts\";\nimport { registerSchema } from \"../../schemas.ts\";\nimport { writeJsonEnvelope } from \"../ads/output.ts\";\nimport { formsOnlyIn, readComposition, readForms } from \"./composition.ts\";\nimport { type FoldResult, foldExperiment } from \"./fold.ts\";\nimport { buildHistoryHints, buildPlanHints, buildStartHints, buildStatusHints } from \"./hints.ts\";\nimport { parseEvidence } from \"./hypothesis.ts\";\nimport { readPageInternalId } from \"./identity.ts\";\nimport { readMeasurementDrift } from \"./measurement.ts\";\nimport { statusReadout } from \"./readout.ts\";\nimport { buildUpdateRequest } from \"./update.ts\";\n\nconst GOAL_ARG = {\n type: \"string\",\n description:\n \"The ONE event the test is read on, by its key: `form:<flow>:<node>:<trigger>` (a Form reaching a step), `submit:<flow>` (a Form finished), `page:<name>` (a page event or one the client's systems post), `exit:<host>` (a click out). `baker analytics conversions --candidates` lists the keys these pages produce, and `plan` lists the ones this page's visitors went on to do. Fixed for the life of the test. Optional when exactly one of them is a named conversion; otherwise say which. Every conversion the company has named is reported beside the goal and decides nothing.\",\n required: false,\n} as const;\n\nconst VARIANT_GOAL_ARG = {\n type: \"string\",\n description:\n \"The event the NEW version is read on, when it is not the page's — a variant that swaps the Form for a shorter one converts on the new Form's ending (`submit:<new-flow>`, or its confirmation step's `form:` key). Same vocabulary as --goal. Leave it out when both versions convert on the same event, which is the common case.\",\n required: false,\n} as const;\n\nconst LIFT_ARG = {\n type: \"string\",\n description:\n \"The smallest improvement worth detecting, relative — 0.2 is +20% (default). The single most consequential number here: halving it roughly quadruples the traffic needed. Ask for the smallest lift that would actually change what the client does, not the smallest one you would like to see.\",\n required: false,\n} as const;\n\n/**\n * The hypothesis, in parts.\n *\n * Not one `--hypothesis` sentence, and the reason is not tidiness: the change,\n * the metric and the magnitude are already `--variant`, `--goal` and `--lift`\n * on this same command. A sentence restating them can disagree with them and\n * nothing would notice, while the one part with nowhere else to live — what was\n * observed that makes this worth testing — gets buried inside it.\n */\nconst BECAUSE_ARG = {\n type: \"string\",\n description:\n \"What you SAW that makes this worth testing — the observation, not the change. “60% of visitors never scroll past the hero”, “CPL doubled after the new headline”. This is the field that makes a losing test worth something: without it there is no belief for the result to be evidence against, and the next test is no better informed than this one.\",\n required: true,\n} as const;\n\nconst CHANGE_ARG = {\n type: \"string\",\n description:\n \"What is different about the alternative version, in the client's words — “put the booking form in the hero”. One thing. Say it as the reader of the report would.\",\n required: true,\n} as const;\n\nconst EXPECT_ARG = {\n type: \"string\",\n description:\n \"Which way you expect the goal to move: `increase` (default) or `decrease`. This is what lets Baker report whether your hypothesis HELD, rather than only which page won — a test the original page wins is not a win, it is the change having made things worse, and that is the most useful thing a test can tell you.\",\n required: false,\n} as const;\n\nconst EVIDENCE_ARG = {\n type: \"string\",\n description:\n \"Where the observation came from, as `source:what it showed`, optionally `|link`. Repeatable. Sources: baker_analytics, clarity, ads, search_console, research, inspiration, client, hunch. Use `hunch` honestly when there is no data — a stated guess is worth more than a guess dressed as analytics.\",\n required: false,\n} as const;\n\nconst BASELINE_ARG = {\n type: \"string\",\n description:\n \"The share of visitors you expect to do the goal, as a fraction — 0.05 is 5% — for a page whose history cannot say: a new page, or one about to get its first campaign. Without it a page with no conversions yet is sized on the company-wide rate for the outcome, or on 3%. The sizing is a guide either way: the test runs until the numbers are clear.\",\n required: false,\n} as const;\n\nconst ID_ARG = { type: \"string\", description: \"The test, by its id\", required: true } as const;\n\nconst LEARNING_ARG = {\n type: \"string\",\n description:\n \"What this test taught, in one or two sentences the client should believe about this page from now on — “the price is not the objection, the install date is”. The one part of the record the numbers cannot write, and what `plan` hands the next test on this page. Finished tests only.\",\n required: false,\n} as const;\n\nconst REASON_ARG = {\n type: \"string\",\n description:\n \"Why the test is being ended, as distinct from what the numbers said: `numbers_clear` (the result was clear enough to act on), `not_settling` (too little traffic or too small a difference — waiting would not help), `page_changing` (the page has to change for another reason), `test_flawed` (the wrong goal, a broken variant). Always say — the dashboard asks a person the same question, and the next test on this page is planned differently after each answer.\",\n required: false,\n} as const;\n\nconst LANDING_FILTER_ARG = {\n type: \"string\",\n description: \"Only the tests on one page, by its slug\",\n required: false,\n} as const;\n\n/**\n * The sizing stood on a stand-in. Said at start as well as at plan, because\n * the agent tells the client how long it will take from whichever of the two\n * it read last.\n */\nfunction sizingHints(data: Record<string, unknown>): string[] {\n return typeof data.warning === \"string\" ? [data.warning] : [];\n}\n\nfunction fail(message: string): never {\n writeJsonEnvelope({ ok: false, error: { code: \"VALIDATION_ERROR\", message } });\n process.exit(1);\n}\n\nfunction reportApiError(error: unknown): never {\n if (error instanceof ApiError) {\n writeJsonEnvelope({ ok: false, error: { code: error.code ?? \"ERROR\", message: error.message } });\n process.exit(1);\n }\n throw error;\n}\n\nregisterSchema({\n command: \"experiment.plan\",\n description: \"Whether a page can settle a question, and how long it might take, before anything is built\",\n args: {\n landing: { type: \"string\", description: \"The page under test, by slug\", required: true },\n variant: { type: \"string\", description: \"The new variant's slug, `<page>--<n>`\", required: true },\n goal: GOAL_ARG,\n variantGoal: VARIANT_GOAL_ARG,\n lift: LIFT_ARG,\n baseline: BASELINE_ARG,\n },\n});\n\nconst planCommand = defineCommand({\n meta: {\n name: \"plan\",\n description:\n \"Start here, BEFORE building the alternative page. Says which event the test would be read on, which events the page's visitors actually go on to do (by key, with the conversion name each is counted under), how long it might take at this page's traffic, and what a 14, 28, 56 or 90-day window could settle. The duration is a guide, never a finish line — a test ends when the numbers are clear, and a page with no traffic yet can still be tested (`warning` says what it was sized on). What IS refused is a test that could not be measured: a goal this page has demonstrably never produced.\",\n },\n args: {\n landing: { type: \"string\", description: \"The page under test, by slug\", required: true },\n variant: { type: \"string\", description: \"The new variant's slug, `<page>--<n>`\", required: true },\n goal: GOAL_ARG,\n variantGoal: VARIANT_GOAL_ARG,\n lift: LIFT_ARG,\n baseline: BASELINE_ARG,\n },\n run: async ({ args }) => {\n try {\n const response = await apiPost<{ ok: true; data: ExperimentPlanResponse }>(\"/api/experiments/plan\", {\n landingSlug: String(args.landing),\n variantSlug: String(args.variant),\n ...(args.goal ? { goal: String(args.goal) } : {}),\n ...(args.variantGoal ? { variantGoal: String(args.variantGoal) } : {}),\n ...(args.lift ? { minDetectableRelativeLift: Number(args.lift) } : {}),\n ...(args.baseline ? { baselineRate: Number(args.baseline) } : {}),\n });\n writeJsonEnvelope({ ok: true, data: response.data, hints: buildPlanHints(response.data) });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.start\",\n description: \"Stage an A/B test between a page and a new variant of it\",\n args: {\n landing: { type: \"string\", description: \"The page under test, by slug\", required: true },\n variant: { type: \"string\", description: \"The new variant's slug, `<page>--<n>`\", required: true },\n because: BECAUSE_ARG,\n change: CHANGE_ARG,\n expect: EXPECT_ARG,\n evidence: EVIDENCE_ARG,\n goal: GOAL_ARG,\n variantGoal: VARIANT_GOAL_ARG,\n lift: LIFT_ARG,\n baseline: BASELINE_ARG,\n },\n});\n\nconst startCommand = defineCommand({\n meta: {\n name: \"start\",\n description:\n \"Stage a test between the page and its new variant. Visitors are split between the two versions at the edge, so there is no flicker and nothing for an ad blocker to suppress. Nothing goes live until the session is published — the person publishing is the review of the new variant. Re-runs `plan` first and refuses on the same grounds.\",\n },\n args: {\n landing: { type: \"string\", description: \"The page under test, by slug\", required: true },\n variant: { type: \"string\", description: \"The new variant's slug, `<page>--<n>`\", required: true },\n because: BECAUSE_ARG,\n change: CHANGE_ARG,\n expect: EXPECT_ARG,\n evidence: EVIDENCE_ARG,\n goal: GOAL_ARG,\n variantGoal: VARIANT_GOAL_ARG,\n lift: LIFT_ARG,\n baseline: BASELINE_ARG,\n },\n run: async ({ args, rawArgs }) => {\n const expect = args.expect === undefined ? \"increase\" : String(args.expect);\n if (expect !== \"increase\" && expect !== \"decrease\") {\n fail(\"`--expect` is either `increase` or `decrease` — which way should the goal move if you are right?\");\n }\n const evidence = parseEvidence(args.evidence as string | string[] | undefined, rawArgs);\n if (evidence && \"error\" in evidence) fail(evidence.error);\n\n const { composition, newForms, drift, landingInternalId, variantInternalId } = await readStartContext(\n String(args.landing),\n String(args.variant),\n );\n\n try {\n const response = await apiPost<{ ok: true; data: Record<string, unknown> }>(\"/api/experiments/start\", {\n landingSlug: String(args.landing),\n variantSlug: String(args.variant),\n ...(landingInternalId ? { landingInternalId } : {}),\n ...(variantInternalId ? { variantInternalId } : {}),\n hypothesis: {\n because: String(args.because),\n change: String(args.change),\n expect,\n ...(evidence ? { evidence } : {}),\n },\n ...(composition ? { composition } : {}),\n ...(args.goal ? { goal: String(args.goal) } : {}),\n ...(args.variantGoal ? { variantGoal: String(args.variantGoal) } : {}),\n ...(args.lift ? { minDetectableRelativeLift: Number(args.lift) } : {}),\n ...(args.baseline ? { baselineRate: Number(args.baseline) } : {}),\n });\n writeJsonEnvelope({\n ok: true,\n data: response.data,\n hints: [\n ...sizingHints(response.data),\n ...buildStartHints({\n landing: String(args.landing),\n variant: String(args.variant),\n goal: String(response.data.goal),\n variantGoal: args.variantGoal === undefined ? undefined : String(args.variantGoal),\n composition,\n newForms,\n drift,\n }),\n \"Staged. The split starts when this session is published — publishing is the review of the new variant.\",\n \"Check it as often as you like — `baker experiment status` is safe to read at any moment and holds its error rate however often you ask. What is never safe is reading the numbers instead of the verdict: `keep_running` means this test cannot yet tell the two pages apart, whatever the rates happen to say today.\",\n ],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.status\",\n description: \"The verdict on every A/B test, running, paused and finished\",\n args: {\n id: { type: \"string\", description: \"One test, by its id\", required: false },\n landing: LANDING_FILTER_ARG,\n full: {\n type: \"boolean\",\n description:\n \"Every field the dashboard reads too — the hypothesis in parts, what each version owns, the preview pictures and the posteriors behind the verdict\",\n required: false,\n },\n },\n});\n\nconst statusCommand = defineCommand({\n meta: {\n name: \"status\",\n description:\n \"The verdict on each test. Read `verdict` and nothing else to decide: keep_running | winner | no_difference | cannot_win | invalid. `cannot_win` means even the best case is under the lift the test was looking for — end it and keep the original. `stillNeeded` is the data (visitors, conversions) the test still needs; a test is gated on data, never on days. `earliestDecisionAt` is the day this page's traffic would get it there (YYYY-MM-DD) and `earliestDecisionIn` how far off that is — say those, never work a date out yourself. `summary` is one sentence you can show the client, and `secondary` lists every other conversion the company counts on the same two arms — context, never a second decision. The numbers under `--full` are for printing, never for deciding — a test that says keep_running has not finished, however good its numbers look.\",\n },\n args: {\n id: { type: \"string\", description: \"One test, by its id\", required: false },\n landing: LANDING_FILTER_ARG,\n full: {\n type: \"boolean\",\n description:\n \"Every field the dashboard reads too — the hypothesis in parts, what each version owns, the preview pictures and the posteriors behind the verdict\",\n required: false,\n },\n },\n run: async ({ args }) => {\n try {\n const response = await apiPost<{ ok: true; data: ExperimentStatusResponse }>(\"/api/experiments/status\", {\n ...(args.id ? { experimentId: String(args.id) } : {}),\n ...(args.landing ? { landingSlug: String(args.landing) } : {}),\n ...(args.full === true ? { full: true } : {}),\n });\n const now = Date.now();\n writeJsonEnvelope({\n ok: true,\n data: {\n experiments: response.data.experiments.map((row) => statusReadout(row, { full: args.full === true, now })),\n },\n hints: buildStatusHints(response.data.experiments, now),\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.history\",\n description:\n \"Every finished A/B test with what it found and what it taught — the record to read before proposing the next one\",\n args: {\n landing: LANDING_FILTER_ARG,\n limit: { type: \"string\", description: \"At most this many, newest first (default all)\", required: false },\n },\n});\n\nconst historyCommand = defineCommand({\n meta: {\n name: \"history\",\n description:\n \"What was tested before and what it taught. Finished tests only, newest first: the claim, what was different, what the numbers said, what was kept and whether that went against them, and the `learning` somebody wrote down. Read it — `--landing <slug>` for one page — BEFORE proposing a test: a refuted idea proposed again spends the page's traffic to return the same answer, and a supported one is what the next test should build on. `plan` hands the same entries back as `pastTests` for the page it sizes.\",\n },\n args: {\n landing: LANDING_FILTER_ARG,\n limit: { type: \"string\", description: \"At most this many, newest first\", required: false },\n },\n run: async ({ args }) => {\n const limit = args.limit === undefined ? undefined : Number(args.limit);\n if (limit !== undefined && !(Number.isInteger(limit) && limit > 0)) fail(\"`--limit` is a whole number above zero\");\n try {\n const response = await apiPost<{ ok: true; data: ExperimentHistoryResponse }>(\"/api/experiments/history\", {\n ...(args.landing ? { landingSlug: String(args.landing) } : {}),\n ...(limit === undefined ? {} : { limit }),\n });\n writeJsonEnvelope({\n ok: true,\n data: response.data,\n hints: buildHistoryHints(response.data.tests, args.landing === undefined ? undefined : String(args.landing)),\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.finish\",\n description: \"End a test now and send all its traffic to the version you name\",\n args: {\n id: ID_ARG,\n keep: {\n type: \"string\",\n description:\n \"Which version stays live: `original` or `variant`. Required — the verdict is a recommendation and this is the decision. A test that has not concluded can be ended too; it is recorded as inconclusive, never as a finding. Going against a verdict is allowed and is recorded as such.\",\n required: true,\n },\n reason: REASON_ARG,\n learning: LEARNING_ARG,\n },\n});\n\nconst END_REASONS = [\"numbers_clear\", \"not_settling\", \"page_changing\", \"test_flawed\"] as const;\n\n/** What to do next, once a test has ended. */\nfunction finishHints(\n data: ExperimentFinishResponse,\n given: { reason: string | undefined; learning: unknown },\n): string[] {\n const { reason } = given;\n const args = { learning: given.learning };\n return [\n `Done. Everyone now sees the ${data.survivor === \"variant\" ? \"new variant\" : \"original\"}.`,\n ...(data.againstVerdict\n ? [\n `You kept the ${data.survivor === \"variant\" ? \"new variant\" : \"original\"} although the numbers favoured the other version (${data.verdict}). That is recorded on the test — say why in the write-up.`,\n ]\n : []),\n ...(data.verdict === \"keep_running\"\n ? [\n \"This test had not concluded, so it is recorded as inconclusive — nobody found out. Never report it as no difference.\",\n ]\n : []),\n ...(data.survivor === \"variant\"\n ? [\n \"Now run `baker experiment fold` — it moves the winning version into the page itself and archives the other one. Until that lands the page is served by a rewrite, and it cannot be tested again.\",\n ]\n : []),\n ...(args.learning || data.verdict === \"keep_running\"\n ? []\n : [\n 'Write down what this taught while it is fresh: `baker experiment update --id <id> --learning \"…\"` — one or two sentences the client should believe about this page now. It is what the next test on this page starts from.',\n ]),\n ...(reason\n ? []\n : [\n \"No `--reason` was given, so the record says what the numbers had reached but not why anyone stopped looking. Next time pass one: numbers_clear, not_settling, page_changing or test_flawed.\",\n ]),\n ];\n}\n\nconst finishCommand = defineCommand({\n meta: {\n name: \"finish\",\n description:\n \"End a test now and send all its traffic to the version you name. Takes effect within seconds — nothing to publish. Read `baker experiment status` first: `winner` says which version the numbers favour, `keep_running` means they have not spoken yet and ending records `inconclusive`. Keeping the variant owes a `fold`. A test staged in this session and not yet published is simply taken back off it.\",\n },\n args: {\n id: ID_ARG,\n keep: {\n type: \"string\",\n description: \"Which version stays live: `original` or `variant`.\",\n required: true,\n },\n reason: REASON_ARG,\n learning: LEARNING_ARG,\n },\n run: async ({ args }) => {\n const keep = String(args.keep);\n if (keep !== \"original\" && keep !== \"variant\") {\n fail(\"`--keep` is either `original` or `variant` — which version should everyone see from now on?\");\n }\n const reason = args.reason === undefined ? undefined : String(args.reason);\n if (reason !== undefined && !END_REASONS.some((known) => known === reason)) {\n fail(`\\`--reason\\` is one of ${END_REASONS.join(\", \")} — why is this test being ended?`);\n }\n try {\n const response = await apiPost<{ ok: true; data: ExperimentFinishResponse | { unstaged: true } }>(\n \"/api/experiments/finish\",\n {\n experimentId: String(args.id),\n keep,\n ...(reason ? { reason } : {}),\n ...(args.learning ? { learning: String(args.learning) } : {}),\n },\n );\n if (\"unstaged\" in response.data) {\n writeJsonEnvelope({\n ok: true,\n data: response.data,\n hints: [\"That test had not been published yet, so it was taken back off this session. Nothing was split.\"],\n });\n return;\n }\n const data = response.data;\n writeJsonEnvelope({ ok: true, data, hints: finishHints(data, { reason, learning: args.learning }) });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.cancel\",\n description: \"Call a test off — take a staged one back off the session, or end a live one keeping the original\",\n args: { id: ID_ARG },\n});\n\nconst cancelCommand = defineCommand({\n meta: {\n name: \"cancel\",\n description:\n \"Call a test off. A test staged in this session and not yet published is simply taken back off it — nothing was split. A test that is live is ended now with everyone sent back to the original: what the numbers had said is recorded as it stood (usually `inconclusive`), and the variant page stays in the workspace, unserved. `finish --keep` is the same act with the choice of version; this is the shortcut for “stop, keep the page as it is”.\",\n },\n args: { id: ID_ARG },\n run: async ({ args }) => {\n try {\n const response = await apiPost<{ ok: true; data: ExperimentFinishResponse | { unstaged: true } }>(\n \"/api/experiments/finish\",\n { experimentId: String(args.id), keep: \"original\" },\n );\n if (\"unstaged\" in response.data) {\n writeJsonEnvelope({\n ok: true,\n data: response.data,\n hints: [\"That test had not been published yet, so it was taken back off this session. Nothing was split.\"],\n });\n return;\n }\n const data = response.data;\n writeJsonEnvelope({\n ok: true,\n data,\n hints: [\n \"Ended. Everyone sees the original page now, and the split stopped within seconds.\",\n ...(data.verdict === \"keep_running\"\n ? [\n \"It had not concluded, so it is recorded as inconclusive — nobody found out. Never report it as no difference.\",\n ]\n : data.againstVerdict\n ? [\n `The numbers favoured the new variant (${data.verdict}), so this is recorded as a decision against them. Say why in the write-up.`,\n ]\n : []),\n \"The variant page is still in the workspace and served to nobody. Run `baker experiment fold` to archive it, or leave it for a later test.\",\n ],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.update\",\n description:\n \"Change what a test says about itself — its wording always, its measurement only while it is still staged\",\n args: {\n id: ID_ARG,\n because: { ...BECAUSE_ARG, required: false },\n change: { ...CHANGE_ARG, required: false },\n evidence: EVIDENCE_ARG,\n expect: { ...EXPECT_ARG, description: \"Staged tests only. `increase` or `decrease`.\" },\n goal: { ...GOAL_ARG, description: \"Staged tests only. The event the test is read on, by key.\" },\n variantGoal: {\n ...VARIANT_GOAL_ARG,\n description: \"Staged tests only. The new version's own event, when it differs.\",\n },\n lift: { ...LIFT_ARG, description: \"Staged tests only. The smallest lift worth detecting.\" },\n learning: LEARNING_ARG,\n },\n});\n\nconst updateCommand = defineCommand({\n meta: {\n name: \"update\",\n description:\n \"Change what a test says about itself. The observation (`--because`), the change in the client's words (`--change`) and the evidence cited (`--evidence`) can change at any time — they describe the test, and a write-up corrected after the fact is still the write-up of the same test. On a FINISHED test, `--learning` writes down what it taught. What the test MEASURES — `--goal`, `--lift`, `--expect` — can only change while it is still staged in this session: once visitors have been split, moving the goal or the finish line is how a result gets argued into existence. To measure something else on a live test, `finish` it and start a new one.\",\n },\n args: {\n id: ID_ARG,\n because: { ...BECAUSE_ARG, required: false },\n change: { ...CHANGE_ARG, required: false },\n evidence: EVIDENCE_ARG,\n expect: { ...EXPECT_ARG, description: \"Staged tests only. `increase` or `decrease`.\" },\n goal: { ...GOAL_ARG, description: \"Staged tests only. The event the test is read on, by key.\" },\n variantGoal: {\n ...VARIANT_GOAL_ARG,\n description: \"Staged tests only. The new version's own event, when it differs.\",\n },\n lift: { ...LIFT_ARG, description: \"Staged tests only. The smallest lift worth detecting.\" },\n learning: LEARNING_ARG,\n },\n run: async ({ args, rawArgs }) => {\n const request = buildUpdateRequest(args, rawArgs);\n if (\"error\" in request) fail(request.error);\n try {\n const response = await apiPost<{ ok: true; data: ExperimentUpdateResponse }>(\"/api/experiments/update\", request);\n writeJsonEnvelope({\n ok: true,\n data: response.data,\n hints: [\n response.data.staged\n ? \"Updated and re-sized. It still starts when this session is published.\"\n : request.learning !== undefined\n ? \"Updated. What this test taught is on its record now, and `plan` hands it to the next test on this page.\"\n : \"Updated. The test keeps running exactly as it was — only its wording changed.\",\n `It now reads: ${response.data.hypothesisStatement}`,\n ],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.pause\",\n description: \"Switch a test's split off — everyone sees the original until it is resumed\",\n args: { id: ID_ARG },\n});\n\nconst pauseCommand = defineCommand({\n meta: {\n name: \"pause\",\n description:\n \"Switch the split off now. Everyone sees the original; visitors already assigned keep their side, so resuming continues the same test rather than starting a new one. Use it when the new variant has to come down for a while — a broken price, a campaign that needs the original — without throwing the test away.\",\n },\n args: { id: ID_ARG },\n run: async ({ args }) => {\n try {\n await apiPost(\"/api/experiments/pause\", { experimentId: String(args.id) });\n writeJsonEnvelope({\n ok: true,\n data: { paused: true },\n hints: [\n `Paused. Everyone sees the original page now. \\`baker experiment resume --id ${String(args.id)}\\` restarts the split with the same sides.`,\n ],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.resume\",\n description: \"Switch a paused test's split back on\",\n args: { id: ID_ARG },\n});\n\nconst resumeCommand = defineCommand({\n meta: {\n name: \"resume\",\n description: \"Switch a paused test's split back on. Visitors already assigned see the same side they saw before.\",\n },\n args: { id: ID_ARG },\n run: async ({ args }) => {\n try {\n await apiPost(\"/api/experiments/resume\", { experimentId: String(args.id) });\n writeJsonEnvelope({\n ok: true,\n data: { resumed: true },\n hints: [\"Resumed. The split is back on, with the same sides.\"],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nregisterSchema({\n command: \"experiment.fold\",\n description: \"Make the page's own content the version that won, and archive the one that lost\",\n args: {\n id: { type: \"string\", description: \"One test, by its id. Omit to fold every test that owes one\", required: false },\n },\n});\n\nconst foldCommand = defineCommand({\n meta: {\n name: \"fold\",\n description:\n \"Finish the job a finished test started: move the winning version into the page's own folder, archive the version that lost, and delete the extra page. Until this runs, the page is served by a rewrite at the edge while its own source says something else — so the repo does not say what the URL serves, and the page cannot be tested again. Start here: run it with no arguments after any test ends, then publish.\",\n },\n args: {\n id: { type: \"string\", description: \"One test, by its id. Omit to fold every test that owes one\", required: false },\n },\n run: async ({ args }) => {\n try {\n const asked = await apiPost<{ ok: true; data: ExperimentFoldResponse }>(\"/api/experiments/fold\", {\n ...(args.id ? { experimentId: String(args.id) } : {}),\n });\n const owed = asked.data.owed;\n if (owed.length === 0) {\n writeJsonEnvelope({\n ok: true,\n data: { folded: [] },\n hints: [\"Nothing to fold — every finished test's result is already the page's own content.\"],\n });\n return;\n }\n\n const folded: FoldResult[] = [];\n const failures: string[] = [];\n for (const target of owed) {\n const result = await foldExperiment(process.cwd(), target);\n // One page that cannot be folded must not hold back the others: each is\n // an independent test on an independent page, and stopping would leave\n // every later one served by a rewrite for another week.\n if (\"error\" in result) failures.push(`${target.landingSlug}: ${result.error}`);\n else folded.push(result);\n }\n\n if (folded.length > 0) {\n // Name the ones that were written, never \"the company\". A target whose\n // fold failed has to stay owed: recorded, it would lose its promotion\n // when the build lands and the page would serve the version that lost.\n await apiPost(\"/api/experiments/fold\", {\n applied: true,\n experimentIds: folded.map((result) => result.experimentId),\n ...(args.id ? { experimentId: String(args.id) } : {}),\n });\n }\n\n writeJsonEnvelope({\n ok: true,\n data: { folded, failures },\n hints: [\n folded.length > 0\n ? `Publish this session to make it real. Until the build lands, the winning version is still served by a rewrite — Baker drops that by itself once the new pages are live, so there is nothing to do afterwards.`\n : \"Nothing was folded.\",\n ...(failures.length > 0 ? [`Could not fold: ${failures.join(\"; \")}`] : []),\n ...(folded.length > 0\n ? [\n `The version that lost is kept at ${folded.map((f) => f.archivedTo).join(\", \")} — a self-contained copy, so it still shows what was actually measured after the page moves on. Never edit it.`,\n ]\n : []),\n ],\n });\n } catch (error) {\n reportApiError(error);\n }\n },\n});\n\nexport const experimentCommand = defineCommand({\n meta: {\n name: \"experiment\",\n description:\n \"Run an A/B test between a landing page and a new variant of it, and be told which one is better. Always `plan` first: most pages do not get enough traffic to settle most questions, and a test that cannot conclude still produces a number somebody will act on. Nothing ends a test on its own — `finish` does, when you or the client decide.\",\n },\n subCommands: {\n plan: planCommand,\n start: startCommand,\n update: updateCommand,\n status: statusCommand,\n history: historyCommand,\n pause: pauseCommand,\n resume: resumeCommand,\n finish: finishCommand,\n cancel: cancelCommand,\n fold: foldCommand,\n },\n});\n\n/**\n * Everything about this test that only the workspace can answer, read once.\n *\n * All of it is best-effort: a graph that cannot be walked costs the brief its\n * detail, never the test. Gathered here rather than inline so the command's\n * handler stays a request and a response.\n */\nasync function readStartContext(landing: string, variant: string) {\n const root = process.cwd();\n // Read off the workspace's own import graph, so the review says which section\n // is under test rather than only which two pages are.\n const composition = await readComposition(root, landing, variant);\n const [controlForms, variantForms, landingInternalId, variantInternalId] = await Promise.all([\n readForms(root, landing),\n readForms(root, variant),\n // Each page's own identity, so a renamed folder cannot detach the test —\n // the variant has no row in Baker until this session is published.\n readPageInternalId(root, landing),\n readPageInternalId(root, variant),\n ]);\n return {\n composition,\n newForms: formsOnlyIn(variantForms, controlForms),\n // Whether the two versions still fire the company's conversions on the same\n // action. Nothing downstream can ask this — a split can be perfect while the\n // two pages disagree about what an event means — and the only place the\n // answer exists is the workspace this command is already standing in.\n drift: await readMeasurementDrift(root, landing, composition),\n landingInternalId,\n variantInternalId,\n };\n}\n","import { readdir, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ExperimentComposition } from \"@baker/api/experiments\";\nimport { landingGraphFiles } from \"../landing/import-graph.ts\";\n\n/**\n * What the two arms of a test are actually made of, read off the real import\n * graph rather than off whatever was typed when the page was built.\n *\n * A variant imports the control's sections and owns a copy of only the one\n * under test. So the split falls straight out of the variant's own transitive\n * graph: a section that lives under the variant's folder is the thing being\n * tested, and one that lives under the control's is shared with it.\n *\n * Computed at the moment the test starts, not passed along from\n * `baker landing variant`, because the page can be edited in between — and a\n * brief that describes the fork as it was when the folder was created would\n * describe a test that is not the one about to run.\n */\n\n/** Sections only. `index.astro` is the page, and every variant owns its own. */\nfunction sectionsUnder(files: readonly string[], slug: string): string[] {\n const prefix = `src/pages/${slug}/`;\n return files.filter((file) => file.startsWith(prefix) && !file.slice(prefix.length).startsWith(\"index.\")).sort();\n}\n\n/**\n * Pure half, so the rule is testable without a fixture tree on disk.\n *\n * `src/layouts/**` and `src/components/**` are deliberately absent from both\n * lists. They reach every page and are force-synced onto every client, so\n * naming them as \"shared with the control\" would bury the one fact this exists\n * to state — that the sections of these two pages are the same files.\n */\nexport function compositionOf(\n controlSlug: string,\n variantSlug: string,\n variantGraph: readonly string[],\n): ExperimentComposition {\n return {\n forked: sectionsUnder(variantGraph, variantSlug),\n shared: sectionsUnder(variantGraph, controlSlug),\n };\n}\n\n/**\n * The same answer, read from the workspace.\n *\n * Returns `undefined` rather than throwing when the graph cannot be walked: a\n * brief without the section names is worse than one with them and much better\n * than a test that could not be started. The backend takes the field as\n * optional for the same reason.\n */\nexport async function readComposition(\n root: string,\n controlSlug: string,\n variantSlug: string,\n): Promise<ExperimentComposition | undefined> {\n try {\n const graph = await landingGraphFiles(root, variantSlug);\n return compositionOf(controlSlug, variantSlug, [...graph]);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Ways a Form is named in source, each anchored to a quoted string so prose\n * cannot match — the same three shapes `detect-landing-flows.mjs` reads in the\n * scaffold: an import of the flow's module, the rendered markup, its selector.\n */\nconst FLOW_REFERENCE_RES = [\n /[\"'][^\"']*flow-engine\\/flows\\/([A-Za-z0-9._-]+)/g,\n /data-flow-form\\s*=\\s*[\"']([^\"']+)[\"']/g,\n /flowName\\s*=\\s*[\"']([^\"']+)[\"']/g,\n];\n\n/** The Forms these sources reference, among the ones that exist. Pure, for the tests. */\nexport function formsReferenced(texts: readonly string[], knownFlows: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const text of texts) {\n for (const re of FLOW_REFERENCE_RES) {\n for (const match of text.matchAll(re)) {\n const flow = match[1];\n if (flow && knownFlows.has(flow)) found.add(flow);\n }\n }\n }\n return [...found].sort();\n}\n\n/** The Forms one version uses that the other does not. */\nexport function formsOnlyIn(these: readonly string[], those: readonly string[]): string[] {\n const other = new Set(those);\n return these.filter((flow) => !other.has(flow));\n}\n\nasync function knownFlows(root: string): Promise<Set<string>> {\n try {\n const entries = await readdir(path.join(root, \"src/lib/flow-engine/flows\"), { withFileTypes: true });\n return new Set(entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));\n } catch {\n return new Set();\n }\n}\n\n/**\n * The Forms a version of the page renders, read off its import graph.\n *\n * Asked of both versions at `start`, because the goal is a conversion NAME and\n * a name counts only the events named under it: a variant that swaps the\n * page's Form for a new one converts on an event nobody has named yet, and a\n * test read on the old Form's ending would report the new version as harmful\n * for the whole of its run. Best effort — an unreadable tree returns nothing,\n * and the hint simply does not fire.\n */\nexport async function readForms(root: string, slug: string): Promise<string[]> {\n try {\n const [files, flows] = await Promise.all([landingGraphFiles(root, slug), knownFlows(root)]);\n if (flows.size === 0) return [];\n const texts = await Promise.all(\n [...files]\n .filter((file) => /\\.(astro|ts|tsx|js|mjs)$/.test(file))\n .map((file) => readFile(path.join(root, file), \"utf8\")),\n );\n return formsReferenced(texts, flows);\n } catch {\n return [];\n }\n}\n","import { type Dirent, statSync } from \"node:fs\";\nimport { cp, mkdir, readdir, readFile, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { variantNumberOf } from \"@baker/api/experiments/variants\";\nimport { landingGraphFiles } from \"../landing/import-graph.ts\";\nimport { repointMoved } from \"../landing/repoint.ts\";\n\n/**\n * Folding a finished test back into the page it was about.\n *\n * The resting state a test used to leave behind: `/oferta/` served the\n * variant's file through a permanent rewrite at the edge, `/oferta--3/` served\n * the same file at its own indexed URL, and `src/pages/oferta/` was served\n * nowhere. One page with two addresses, one address with no page, and a repo\n * that no longer said what a URL serves.\n *\n * The fold ends that. The page keeps its slug, its `_definition.md` and its\n * `internalId` — so history, leads, `landing_metadata` and every ad pointing at\n * the URL are untouched — and its content becomes whichever variant won. The\n * variant folder stops existing.\n *\n * ## Why the loser is archived rather than deleted\n *\n * A test's whole value is the finding, and the finding is unreadable six months\n * later if the version that lost is gone. The archive lives at\n * `src/_variants/<page internalId>/<n>/`, and every part of that path is a\n * decision:\n *\n * - **Outside `src/pages/`**, because `getLandingsOnRef` does not filter\n * `_`-prefixed folders while `discoverLandings` does — anything under\n * `src/pages/` that classify excludes and `git ls-tree` includes becomes a\n * permanent phantom deleted landing on every sync. And because `walkFiles`\n * recurses into `_`-dirs, so an archive under the page would fold into the\n * page's own import graph, move its source fingerprint, and make the\n * anti-slop gate re-score a page nobody touched.\n * - **Keyed on `internalId`, not the slug**, so renaming the page cannot orphan\n * its own history. Same identity-over-address rule as everything else here.\n * - **Numbered by the test's variant number**, which is never reused.\n * - **No `_definition.md`.** The collection loader's `**\\/_definition.md` glob is\n * depth-agnostic, so one would make the archive a collection entry and a\n * sitemap URL that 404s. It is a record, not a landing.\n *\n * ## Why the archive is self-contained\n *\n * A variant borrows the page's sections and forks only the one under test —\n * that sharing is what makes the test measure one difference. An archive that\n * kept those imports pointing at the live page would silently rewrite itself on\n * the page's next edit, which makes it a record of nothing. So every file the\n * archived arm renders is copied in and repointed to the copy.\n *\n * Self-contained stops at the arm's own folders. The shared library —\n * `src/layouts/**`, `src/components/**`, `src/lib/**` — is referenced rather\n * than copied, because snapshotting the whole scaffold for every test any\n * client ever ran is not affordable. Those references are still rewritten, for\n * the depth reason in `repointIntoArchive`.\n *\n * ## Why the archive is out of the verify gate\n *\n * A record nobody may edit cannot also be something a gate can turn red, and it\n * cannot be something the gate rewrites either — the first leaves destroying\n * the record as the only way past, the second does the destroying itself. Four\n * scopes in the scaffold therefore exclude `src/_variants`, pinned from this\n * side by `archive-scope.parity.test.ts` because the scaffold ships standalone\n * and cannot import this file: typecheck (`tsconfig.json`, and\n * `tsconfig.sandbox.json` separately — `exclude` replaces rather than merges),\n * biome (`_biome.json`) and the CSS import formatter, both of which write.\n *\n * Knip is the fourth and is not an exclusion at all. Dropping the archive from\n * its graph would make a shared component only an archive imports read as\n * unused, and deleting it on knip's word is this file's own \"record of nothing\"\n * arriving quietly instead of loudly. So every archived file is an `entry`: its\n * imports count as used, and `includeEntryExports: false` keeps its own exports\n * out of the report. Making only the arm's `index.astro` an entry does neither\n * — a section's export then reports unused, on a file nobody may edit.\n *\n * What is NOT excluded, deliberately: the validators that walk `src/**` and can\n * only warn (`validate-mux-video-library` and friends). A warning does not\n * block a publish and does not rewrite anything, so it fails neither half of\n * the rule above. It is noise on a file nobody can act on, and worth removing —\n * but not by widening a rule whose whole value is that it is about red gates\n * and writers.\n */\n\n/** Where a page's past versions live. Never built, never served, never synced. */\nexport const ARCHIVE_ROOT = \"src/_variants\";\n\nexport interface FoldTarget {\n readonly experimentId: string;\n readonly landingSlug: string;\n readonly variantSlug: string;\n readonly survivor: \"control\" | \"variant\";\n readonly landingInternalId: string | null;\n}\n\nexport interface FoldResult {\n readonly experimentId: string;\n readonly page: string;\n readonly archivedTo: string;\n readonly written: string[];\n readonly removed: string[];\n}\n\n/** Quoted relative specifiers: imports, and `src=\"./_images/…\"` in markup alike. */\nconst QUOTED_RELATIVE = /(['\"])(\\.\\.?\\/[^'\"]*)\\1/g;\n\nfunction posix(p: string): string {\n return p.split(path.sep).join(\"/\");\n}\n\nasync function isDir(p: string): Promise<boolean> {\n try {\n return (await stat(p)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Every file under `dir`, recursively, as paths relative to it. */\nasync function filesUnder(dir: string, prefix = \"\"): Promise<string[]> {\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return [];\n }\n const out: string[] = [];\n for (const entry of entries) {\n const rel = prefix ? `${prefix}/${entry.name}` : entry.name;\n if (entry.isDirectory()) out.push(...(await filesUnder(path.join(dir, entry.name), rel)));\n else out.push(rel);\n }\n return out.sort();\n}\n\nconst TEXT_EXT = /\\.(astro|ts|tsx|js|jsx|mjs|cjs|vue|svelte|md|json|css)$/;\n\n/**\n * Where a file lands inside the snapshot, or null when it is not `base`'s.\n *\n * The arm's own files keep their place; the ones it borrowed from the page keep\n * the place they had under the page. Two disjoint trees flattened into one, so\n * a borrowed `_components/Proof.astro` and a forked `_components/Hero.astro`\n * sit side by side exactly as the page rendered them.\n */\nfunction archiveRelPath(abs: string, base: string): string | null {\n if (!abs.startsWith(base + path.sep)) return null;\n return posix(path.relative(base, abs));\n}\n\n/**\n * Copy one arm into an archive that renders the same thing forever.\n *\n * Only the two landing folders are pulled in. `src/components/**` and\n * `src/layouts/**` are the shared library rather than this page's content, and\n * the archive is never built — copying them would snapshot the whole scaffold\n * for every test any client ever ran.\n */\nasync function archiveArm(opts: {\n root: string;\n armDir: string;\n pageDir: string;\n archiveDir: string;\n armSlug: string;\n}): Promise<string[]> {\n const { root, armDir, pageDir, archiveDir, armSlug } = opts;\n const targets = await archiveContents({ root, armDir, pageDir, armSlug });\n const archivedBySource = new Map([...targets].map(([rel, abs]) => [abs, path.join(archiveDir, rel)]));\n const written: string[] = [];\n for (const [rel, abs] of [...targets].sort()) {\n const dest = path.join(archiveDir, rel);\n await mkdir(path.dirname(dest), { recursive: true });\n if (!TEXT_EXT.test(rel)) {\n await cp(abs, dest);\n } else {\n const text = await readFile(abs, \"utf8\");\n await writeFile(dest, repointIntoArchive(text, abs, dest, archivedBySource), \"utf8\");\n }\n written.push(posix(path.relative(root, dest)));\n }\n return written;\n}\n\n/**\n * Every file the archive holds: archive-relative path → where it is copied from.\n *\n * Two passes, so the result does not depend on set iteration order — the page's\n * files first, then the arm's own on top. Where both hold a path the arm's\n * wins, and that fork is the entire difference the test was measuring.\n */\nasync function archiveContents(opts: {\n root: string;\n armDir: string;\n pageDir: string;\n armSlug: string;\n}): Promise<Map<string, string>> {\n const { root, armDir, pageDir, armSlug } = opts;\n const graph = await landingGraphFiles(root, armSlug);\n const reachable = new Set([...graph].map((rel) => path.resolve(root, rel)));\n // Assets are not in the import graph but are part of what the arm was. The\n // graph adds only the files it BORROWED from the page.\n for (const rel of await filesUnder(armDir)) reachable.add(path.join(armDir, rel));\n\n const sorted = [...reachable].sort();\n const targets = new Map<string, string>();\n for (const base of [pageDir, armDir]) {\n for (const abs of sorted) {\n const rel = archiveRelPath(abs, base);\n // Outside this folder, or not a landing — a `_definition.md` here would\n // become a collection entry and therefore a URL that 404s.\n if (rel === null || rel === \"_definition.md\") continue;\n targets.set(rel, abs);\n }\n }\n return targets;\n}\n\n/**\n * Re-aim one archived file's relative paths, so every one of them still names\n * the file it named when this arm was live.\n *\n * Two kinds, and both have to move. A specifier that resolves to something the\n * archive holds points at that copy — that is what makes the snapshot\n * self-contained. A specifier that escapes the arm's folder — the shared\n * layout, a component from `src/components/` — is referenced rather than\n * copied, deliberately: snapshotting `src/components/**` for every test any\n * client ever ran is not affordable.\n *\n * Leaving that second kind exactly as written was the first version of this,\n * and it was wrong for a reason that is pure arithmetic: the archive sits at\n * `src/_variants/<id>/<n>/`, one folder deeper than the `src/pages/<slug>/` it\n * was copied from, so every `../../layouts/Layout.astro` it inherited lands on\n * `src/_variants/layouts/Layout.astro` and names nothing at all. A reference\n * that drifts with the shared library still says which module rendered here; a\n * reference to a path that cannot exist says nothing, and it is what made the\n * first fold any client ever ran fail its own typecheck 16 times over.\n */\nfunction repointIntoArchive(\n text: string,\n sourceAbs: string,\n destAbs: string,\n archivedBySource: ReadonlyMap<string, string>,\n): string {\n const fromDir = path.dirname(sourceAbs);\n const toDir = path.dirname(destAbs);\n return text.replace(QUOTED_RELATIVE, (match, quote: string, spec: string, offset: number) => {\n const [specPath, suffix] = splitSuffix(spec);\n const target = path.resolve(fromDir, specPath);\n const resolved =\n resolveInArchive(target, archivedBySource) ?? resolveOnDisk(target, isImportPosition(text, offset));\n if (resolved === null) return match;\n const rel = posix(path.relative(toDir, resolved));\n return `${quote}${rel.startsWith(\".\") ? rel : `./${rel}`}${suffix}${quote}`;\n });\n}\n\n/**\n * `\"./shot.png?w=800\"` → `[\"./shot.png\", \"?w=800\"]`.\n *\n * A Vite query or a fragment is not part of the filename, and resolving one as\n * if it were is how `./_images/shot.png?w=800` missed the archive's own copy of\n * `shot.png` and got re-aimed at the variant folder the fold then deleted.\n */\nfunction splitSuffix(spec: string): [string, string] {\n const cut = spec.search(/[?#]/);\n return cut === -1 ? [spec, \"\"] : [spec.slice(0, cut), spec.slice(cut)];\n}\n\n/** Spellings of one specifier that name the same file: the path, or an elided extension. */\nconst EXTENSIONS = [\"\", \".astro\", \".ts\", \".tsx\", \".js\", \".jsx\", \".mjs\"];\n\n/**\n * An extensionless specifier still names a file the archive holds.\n *\n * `/index.*` is a candidate here and deliberately not in `resolveOnDisk`: this\n * map holds files the archive really contains, so a hit is proof. On disk it\n * would be a guess, and the wrong one — see below.\n */\nfunction resolveInArchive(base: string, archivedBySource: ReadonlyMap<string, string>): string | null {\n for (const suffix of [...EXTENSIONS, \"/index.astro\", \"/index.ts\"]) {\n const hit = archivedBySource.get(base + suffix);\n if (hit !== undefined) return hit;\n }\n return null;\n}\n\n/**\n * Is the quote at `offset` the start of an import specifier, or of a value?\n *\n * The distinction decides whether `../../components/Widget` may be read as\n * `Widget/index.astro`. Both a directory import and `<a href=\"../gracias/\">`\n * name a directory that exists, so nothing about the path itself separates\n * them — only where it is written does. `import \"./styles.css\"` has no `from`,\n * which is why a bare `import` counts.\n */\nfunction isImportPosition(text: string, offset: number): boolean {\n return /\\b(?:from|import|require)\\s*\\(?\\s*$/.test(text.slice(Math.max(0, offset - 64), offset));\n}\n\n/**\n * The same question asked of the repo, for the references the archive does not\n * hold — the shared layout, a component from `src/components/`.\n *\n * A **file**, never a directory, and that is the whole precision of it.\n * `QUOTED_RELATIVE` matches any quoted relative string, so `<a href=\"../gracias/\">`\n * and `const redirect = \"../thanks\"` arrive here looking exactly like an\n * import. Those are not paths into the source tree, and rewriting one restates\n * the journey the snapshot was measuring as a path through the repo.\n *\n * \"Does something live there\" is too weak to separate them: a link usually\n * points at a sibling page, whose directory exists. `/index.*` is the case\n * where that matters most in both directions — `import X from \"../../components/Widget\"`\n * is a real shape (`landing/import-graph.ts` resolves it, so an arm can hold\n * one), and `href=\"../gracias/\"` resolves to that page's own entry file. So the\n * index candidates are offered only where an import can actually be written,\n * and the `isFile` check governs everywhere.\n */\nfunction resolveOnDisk(base: string, allowDirectoryIndex: boolean): string | null {\n const candidates = allowDirectoryIndex ? [...EXTENSIONS, \"/index.astro\", \"/index.ts\", \"/index.js\"] : EXTENSIONS;\n for (const suffix of candidates) {\n try {\n if (statSync(base + suffix).isFile()) return base + suffix;\n } catch {\n // Nothing at that path. Try the next spelling.\n }\n }\n return null;\n}\n\n/**\n * Move the winning variant's files into the page's own folder.\n *\n * `_definition.md` is deliberately not copied: the page keeps its identity, and\n * that identity is what every analytics row, every lead and every ad already\n * points at. The variant's own definition dies with the folder.\n */\nasync function promoteVariantFiles(opts: { root: string; pageDir: string; variantDir: string }): Promise<string[]> {\n const { root, pageDir, variantDir } = opts;\n const written: string[] = [];\n for (const rel of await filesUnder(variantDir)) {\n if (rel === \"_definition.md\") continue;\n const from = path.join(variantDir, rel);\n const to = path.join(pageDir, rel);\n await mkdir(path.dirname(to), { recursive: true });\n if (!TEXT_EXT.test(rel)) {\n await cp(from, to);\n } else {\n const text = await readFile(from, \"utf8\");\n // A borrowed section written as `../oferta/_components/Proof.astro`\n // becomes `./_components/Proof.astro`; a forked one moves with the file\n // and stays `./_components/Hero.astro`; the shared layout is unchanged.\n await writeFile(\n to,\n repointMoved(text, {\n fromDir: path.dirname(from),\n toDir: path.dirname(to),\n movedFrom: variantDir,\n movedTo: pageDir,\n }),\n \"utf8\",\n );\n }\n written.push(posix(path.relative(root, to)));\n }\n return written;\n}\n\n/**\n * The record beside the snapshot: what this version was and why it stopped\n * being live.\n *\n * JSON rather than markdown with frontmatter, and named `_variant.json` rather\n * than `_definition.md`, for the reason above — a `_definition.md` anywhere\n * under `src/` becomes a landing collection entry and therefore a URL.\n */\nfunction archiveRecord(target: FoldTarget, variantNumber: number): string {\n return `${JSON.stringify(\n {\n experimentId: target.experimentId,\n page: target.landingSlug,\n pageInternalId: target.landingInternalId,\n variant: target.variantSlug,\n variantNumber,\n // Which arm this snapshot is. On a control win the variant lost; on a\n // variant win what lost is the page's own previous content, which never\n // had a number of its own — so it is filed under the test that rejected\n // it, which is the only number that means anything about it.\n arm: target.survivor === \"variant\" ? \"page-before-this-test\" : \"variant\",\n },\n null,\n 2,\n )}\\n`;\n}\n\n/**\n * Fold one finished test in, and report every path touched.\n *\n * Refuses rather than half-applies: a variant folder that is already gone means\n * somebody folded this test by hand, and writing an archive of the page's\n * current content over it would record the wrong thing as the loser.\n */\nexport async function foldExperiment(root: string, target: FoldTarget): Promise<FoldResult | { error: string }> {\n const pageDir = path.resolve(root, \"src/pages\", target.landingSlug);\n const variantDir = path.resolve(root, \"src/pages\", target.variantSlug);\n const variantNumber = variantNumberOf(target.variantSlug);\n\n if (variantNumber === null) {\n return {\n error: `“${target.variantSlug}” is not a variant of “${target.landingSlug}”, so there is nothing to fold`,\n };\n }\n if (!(await isDir(pageDir))) {\n return { error: `There is no page at src/pages/${target.landingSlug}/` };\n }\n if (!(await isDir(variantDir))) {\n return { error: `src/pages/${target.variantSlug}/ is already gone — this test looks folded in already` };\n }\n\n const archiveKey = target.landingInternalId ?? target.landingSlug;\n const archiveDir = path.resolve(root, ARCHIVE_ROOT, archiveKey, String(variantNumber));\n await rm(archiveDir, { recursive: true, force: true });\n await mkdir(archiveDir, { recursive: true });\n\n // The arm that stops being served is the one worth keeping. When the page as\n // it was won, that is the variant; when the new variant won, it is the page's\n // own outgoing content.\n const written = await archiveArm({\n root,\n armDir: target.survivor === \"variant\" ? pageDir : variantDir,\n pageDir,\n archiveDir,\n armSlug: target.survivor === \"variant\" ? target.landingSlug : target.variantSlug,\n });\n await writeFile(path.join(archiveDir, \"_variant.json\"), archiveRecord(target, variantNumber), \"utf8\");\n written.push(posix(path.join(ARCHIVE_ROOT, archiveKey, String(variantNumber), \"_variant.json\")));\n\n if (target.survivor === \"variant\") {\n written.push(...(await promoteVariantFiles({ root, pageDir, variantDir })));\n }\n\n await rm(variantDir, { recursive: true, force: true });\n\n return {\n experimentId: target.experimentId,\n page: target.landingSlug,\n archivedTo: posix(path.join(ARCHIVE_ROOT, archiveKey, String(variantNumber))),\n written,\n removed: [`src/pages/${target.variantSlug}`],\n };\n}\n","/**\n * Why a test is being run, in the shape the rest of the feature can read.\n *\n * ## Why this is not a sentence\n *\n * The canonical hypothesis is \"Because **[observation]**, if we **[change]**,\n * then **[metric]** will **[direction]** by about **[magnitude]**.\" Three of\n * those five parts are already first-class, machine-readable and immutable\n * elsewhere in this feature:\n *\n * - the change → `plan.variantSlug`, and exactly which component was forked\n * - the metric → {@link ExperimentGoal}, fixed at creation and refused if it\n * has never fired\n * - the magnitude → `plan.minDetectableRelativeLift`\n *\n * A free-text sentence therefore restates three stored fields in a worse form —\n * one that can silently disagree with them, because nothing compares the two —\n * while burying the one part that has nowhere else to live. Storing the parts\n * removes the disagreement by construction: {@link renderHypothesis} is the\n * only thing allowed to produce the sentence, and it reads the goal and the\n * lift from the plan rather than from the prose.\n *\n * ## Why `expect` is here\n *\n * It looks like decoration and it is the load-bearing field. A test is not\n * symmetric: \"the new hero converts better\" and \"the new hero converts worse\"\n * are different claims, and the verdict already knows which arm won. Pairing\n * the two gives {@link hypothesisOutcome}, which is derived and never entered —\n * so a `winner` the CONTROL won stops being announced as \"we have a winner\"\n * and starts being reported as what it is, the most useful thing a test can\n * find: the change made it worse.\n *\n * Without `expect` that finding is unreachable, because nothing in the stored\n * plan says which direction was hoped for.\n */\n\nimport { z } from \"zod\";\n\n/**\n * Where the observation came from.\n *\n * A closed set rather than free text, because the point of recording it is to\n * be able to ask later which kinds of evidence produced tests that held. The\n * names are the tools the agent already has (`baker ga4`, `baker gsc`,\n * `baker ads`, the Clarity MCP, the research agents, the inspiration library),\n * plus the two honest non-tool answers.\n *\n * `hunch` is deliberately spellable. A hypothesis with no data behind it is\n * still a hypothesis, and forcing the agent to dress one up as analytics is\n * worse than letting it say so.\n */\nexport const experimentEvidenceSourceSchema = z.enum([\n \"baker_analytics\",\n \"clarity\",\n \"ads\",\n \"search_console\",\n \"research\",\n \"inspiration\",\n \"client\",\n \"hunch\",\n]);\nexport type ExperimentEvidenceSource = z.infer<typeof experimentEvidenceSourceSchema>;\n\nexport const experimentEvidenceSchema = z.object({\n source: experimentEvidenceSourceSchema,\n note: z.string().min(1).max(200),\n /**\n * Where to go and look. Not a `z.url()` — a report inside Baker is reached by\n * a relative path, and rejecting those would push every in-product citation\n * into the note where nothing can link it.\n */\n href: z.string().max(500).optional(),\n});\nexport type ExperimentEvidence = z.infer<typeof experimentEvidenceSchema>;\n\nexport const experimentHypothesisSchema = z.object({\n /**\n * The observation. The one genuinely new field, and the reason for the rest.\n *\n * Without it a lost test teaches nothing, because there is no belief for the\n * result to be evidence against — which is how a testing programme ends up as\n * a list of tests instead of a list of things learned.\n */\n because: z.string().min(1).max(300),\n /**\n * The change, in the client's words.\n *\n * The machine-checkable truth lives in `plan.composition.forked`; this is what\n * a person reads. The two are allowed to differ in wording and cannot differ\n * in fact, because the brief shows both.\n */\n change: z.string().min(1).max(200),\n /** Which way the goal is expected to move. See the note at the top of this file. */\n expect: z.enum([\"increase\", \"decrease\"]).default(\"increase\"),\n evidence: z.array(experimentEvidenceSchema).max(5).optional(),\n});\nexport type ExperimentHypothesis = z.infer<typeof experimentHypothesisSchema>;\n\n/**\n * What the test found out about the belief.\n *\n * Deliberately not the verdict. The verdict says what the numbers did; this\n * says what it meant. They come apart in exactly the case worth catching — a\n * `winner` the control won.\n *\n * `refuted_no_effect` and `unanswered` are kept apart for the same reason\n * `no_difference` and `abandoned` are: \"measured and found alike\" and \"nobody\n * ever found out\" are opposite claims, and collapsing them manufactures a\n * finding that is not there.\n */\nexport const experimentHypothesisOutcomeSchema = z.enum([\n \"supported\",\n \"refuted_opposite\",\n \"refuted_no_effect\",\n \"unanswered\",\n]);\nexport type ExperimentHypothesisOutcome = z.infer<typeof experimentHypothesisOutcomeSchema>;\n\n/**\n * Derived from the verdict and the belief, never entered.\n *\n * **`winner` is the arm the NUMBERS favoured, never the arm that was kept.**\n * The two come apart in exactly the case this function exists for: a client may\n * keep the original after a variant win, and that decision does not make the\n * belief wrong — the belief is about the page, not about what anybody did next.\n * Passing the survivor here would report a supported hypothesis as\n * `refuted_opposite` on the card and in History. `stopped_early_harmful` has no\n * winner and needs none: nothing beat the control, so a rise was not seen.\n *\n * `null` on every verdict that decides nothing, which is why those all fall\n * through to `unanswered`.\n */\nexport function hypothesisOutcome(\n verdict: string,\n winner: \"control\" | \"variant\" | null,\n expect: ExperimentHypothesis[\"expect\"],\n): ExperimentHypothesisOutcome {\n if (verdict === \"no_difference\") {\n return \"refuted_no_effect\";\n }\n // Even its best case is under the lift the test was looking for: a rise did\n // not happen, and a fall may have.\n if (verdict === \"cannot_win\") {\n return expect === \"increase\" ? \"refuted_no_effect\" : \"unanswered\";\n }\n if (verdict !== \"winner\" && verdict !== \"stopped_early_harmful\") {\n return \"unanswered\";\n }\n // The variant is the page the belief is about, so the belief held when the\n // variant won a test that expected a rise, or lost one that expected a fall.\n const variantWon = winner === \"variant\";\n return variantWon === (expect === \"increase\") ? \"supported\" : \"refuted_opposite\";\n}\n\n/**\n * The sentence, built from the parts. Nothing else may store one.\n *\n * `goalLabel` comes from the plan rather than from the hypothesis, so the\n * sentence a person reads cannot disagree with the thing being measured.\n *\n * The lift the test was sized for is deliberately NOT in it. It used to end\n * \"by at least 20%\", and a marketer reading it asked what the 20% meant — it\n * is the sizing parameter, not part of the belief, and the stopping rule is\n * anytime-valid, so the test does not stop at 20% or wait for it. The claim is\n * the direction; the numbers are the evidence.\n */\nexport function renderHypothesis(hypothesis: ExperimentHypothesis, goalLabel: string): string {\n const direction = hypothesis.expect === \"increase\" ? \"go up\" : \"go down\";\n return `Because ${hypothesis.because}, if we ${hypothesis.change}, ${goalLabel} should ${direction}.`;\n}\n","const DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction count(value: number, unit: string): string {\n return `about ${value} ${unit}${value === 1 ? \"\" : \"s\"}`;\n}\n\n/**\n * How long until a running test is expected to have an answer, in the unit a\n * marketer plans in: days under two weeks, weeks under two months, then months.\n *\n * Shared because the card and the agent both say it, and a test \"about 4 weeks\"\n * out on the dashboard and \"2026-10-22\" in chat reads as two different answers.\n * Always \"about\": it is today's traffic projected forward, never a promise.\n */\nexport function describeWait(earliestDecisionAt: number, now: number): string {\n const days = (earliestDecisionAt - now) / DAY_MS;\n if (days <= 0) return \"any moment now\";\n if (days < 1) return \"within a day\";\n if (days < 14) return count(Math.round(days), \"day\");\n if (days < 60) return count(Math.round(days / 7), \"week\");\n return count(Math.round(days / 30), \"month\");\n}\n\n/**\n * `2,115` → `about 2,100`, `38` → `about 40`: a projected count said at the\n * precision it is known to. Shared for the same reason as {@link describeWait}.\n */\nexport function roughCount(count: number): string {\n if (count < 10) return `about ${Math.max(1, Math.round(count)).toLocaleString(\"en-US\")}`;\n const magnitude = 10 ** Math.max(1, Math.floor(Math.log10(count)) - 1);\n return `about ${(Math.round(count / magnitude) * magnitude).toLocaleString(\"en-US\")}`;\n}\n","/**\n * What to do next, given a verdict or a plan.\n *\n * The verdict already decides; these say what the decision means for the work.\n * They exist because the single most likely misuse of this command is an agent\n * reading `keep_running` and going looking for a number that agrees with it.\n *\n * Pure, so the wording is testable and cannot drift from the rules it describes.\n */\n\nimport {\n describeWait as describeWaitFor,\n type ExperimentComposition,\n type ExperimentHistoryEntry,\n type ExperimentPlanResponse,\n type ExperimentStatusRow,\n roughCount,\n} from \"@baker/api/experiments\";\nimport type { MeasurementDrift } from \"./measurement.ts\";\n\n/**\n * The one line to print for a live test, by its verdict.\n *\n * A record rather than a switch so the set stays exhaustive by type: a verdict\n * added upstream without a line here fails to compile, where a switch would\n * quietly print nothing for the very case somebody needs to act on.\n *\n * Nothing ends a test on its own, so every line that has an action names the\n * command that takes it.\n */\nconst LIVE_HINT: Record<ExperimentStatusRow[\"verdict\"], (row: ExperimentStatusRow, now: number) => string> = {\n keep_running: (row, now) =>\n `${row.experimentId} cannot yet tell the two pages apart — do not end it hoping for a result. ${describeWait(row, now)} To the user, say only this in plain words: \"${row.summary}\" plus how much more data it needs and roughly when. Never quote confidence, chance-better or intervals to them — those numbers confuse marketers and decide nothing.`,\n winner: (row) =>\n row.winner === \"variant\"\n ? `${row.experimentId} has a winner: the new variant. Run \\`baker experiment finish --id ${row.experimentId} --keep variant\\` to show it to everyone, then \\`baker experiment fold\\`.`\n : `${row.experimentId}: the new version is proven worse and is STILL being shown to half the visitors. Run \\`baker experiment finish --id ${row.experimentId} --keep original\\` now.`,\n no_difference: (row) =>\n `${row.experimentId} measured the two pages as alike — it collected enough traffic to see the change it was looking for, and there was none. End it with \\`--keep original\\` and test a bigger change — a headline, an offer, the shape of the page, not a button colour.`,\n cannot_win: (row) =>\n `${row.experimentId}: the new version cannot win — even its best case is under the lift this test was looking for, so there is nothing left to find. Run \\`baker experiment finish --id ${row.experimentId} --keep original --reason numbers_clear\\` and write down what it taught.`,\n stopped_early_harmful: (row) =>\n `${row.experimentId} is doing damage and is STILL being shown to visitors. Run \\`baker experiment finish --id ${row.experimentId} --keep original\\` now.`,\n // Unreachable on a live test — only ending one produces it. The Record is\n // exhaustive by type so a verdict added upstream fails to compile here rather\n // than printing nothing.\n inconclusive: (row) => `${row.experimentId} was ended before it concluded and found nothing.`,\n invalid: (row) =>\n `${row.experimentId} cannot be read: ${\n row.invalidReason === \"crossover\" ? \"too many visitors saw both versions\" : \"the traffic did not split evenly\"\n }. End it with \\`--keep original\\` and start again rather than reporting anything from it.`,\n};\n\n/**\n * When a running test could first say something, in one clause.\n *\n * Three states rather than two, because they need different actions: a page\n * that has not been seen yet, a page that will get there, and a page whose\n * traffic never will. The last one used to be indistinguishable from the\n * second, so a hopeless test simply ran forever.\n */\nfunction describeWait(row: ExperimentStatusRow, now: number): string {\n if (row.willNotConclude) {\n return \"This page's traffic will not settle it at any point, so end it with `--keep original` and test a bigger change.\";\n }\n if (row.earliestDecisionAt === null) {\n return \"This page has produced no visitors yet, so there is nothing to project a finish date from.\";\n }\n if (row.stillNeeded === null) {\n return `It already has the data it needs; the verdict waits only until it has seen a full day of visitors — ${describeWaitFor(row.earliestDecisionAt, now)}.`;\n }\n const needed = `It needs ${roughCount(row.stillNeeded.visitors)} more visitors (${roughCount(row.stillNeeded.conversions)} more conversions) for an answer if the gap holds — `;\n return `${needed}${describeWaitFor(row.earliestDecisionAt, now)} at today's traffic (${new Date(row.earliestDecisionAt).toISOString().slice(0, 10)}). A test is gated on this data, never on days.`;\n}\n\n/** A paused test says so first, because nothing else in the row does. */\nfunction pausedNote(row: ExperimentStatusRow): string {\n return row.status === \"paused\"\n ? `${row.experimentId} is paused — everyone sees the original. \\`baker experiment resume --id ${row.experimentId}\\` restarts the split with the same sides. `\n : \"\";\n}\n\n/**\n * A finished test with nothing written down is a result without a conclusion,\n * and the conclusion is the part the next test is built from.\n */\nfunction unlearnedHint(experiments: readonly ExperimentStatusRow[]): string | null {\n const unlearned = experiments.filter(\n (row) => row.status === \"finished\" && row.learning === null && row.verdict !== \"inconclusive\",\n );\n if (unlearned.length === 0) return null;\n const ids = unlearned.map((row) => row.experimentId).join(\", \");\n return `${unlearned.length === 1 ? \"One finished test has\" : `${unlearned.length} finished tests have`} no learning written down yet (${ids}). Say what it taught in one or two sentences — what the client should believe about this page now — with \\`baker experiment update --id <id> --learning \"…\"\\`. It is what \\`plan\\` hands the next test.`;\n}\n\nexport function buildStatusHints(experiments: readonly ExperimentStatusRow[], now = Date.now()): string[] {\n const hints = experiments\n .filter((row) => row.status !== \"finished\")\n .map((row) => pausedNote(row) + LIVE_HINT[row.verdict](row, now));\n const unlearned = unlearnedHint(experiments);\n if (unlearned) hints.push(unlearned);\n\n if (experiments.length === 0) {\n hints.push(\n \"No tests yet. `baker experiment plan --landing <slug> --variant <slug>` says whether a page has enough traffic for one before you build the alternative.\",\n );\n }\n return hints;\n}\n\n/** `+40%`, the way a lift is spoken about. */\nfunction asLift(value: number): string {\n return `+${Math.round(value * 100)}%`;\n}\n\n/**\n * What each fixed window would settle, as one line.\n *\n * The counterpart to a refusal. \"This page cannot settle +20%\" sends somebody\n * away to guess; \"in 28 days it could settle +40% or more\" tells them how big\n * the change has to be, which is a thing they can go and design.\n */\nfunction horizonsHint(plan: Pick<ExperimentPlanResponse, \"horizons\">): string | null {\n const settled = plan.horizons.filter((horizon) => horizon.detectableLift !== null);\n if (settled.length === 0) {\n return \"At this page's traffic no window up to 90 days would gather enough visitors to settle anything, at any size of change. The constraint is the traffic, not the test.\";\n }\n const parts = settled.map((horizon) => `${horizon.days} days → ${asLift(horizon.detectableLift ?? 0)}`);\n return `The smallest change each window could settle on this page's traffic: ${parts.join(\", \")}. Design the alternative to be at least that different.`;\n}\n\n/** The events this page produces, so the goal can be chosen from what is real. */\nfunction conversionsHint(plan: Pick<ExperimentPlanResponse, \"conversions\" | \"goal\">): string | null {\n if (plan.conversions.length <= 1) return null;\n const others = plan.conversions.filter((entry) => entry.eventKey !== plan.goal);\n return `This page's visitors also went on to: ${others\n .map((entry) => `\\`${entry.eventKey}\\`${entry.name ? ` (“${entry.name}”)` : \"\"} — ${entry.convertingVisitors}`)\n .join(\n \", \",\n )}. Any of them can be the goal (--goal <key>); the named ones are reported beside it on every result, and decide nothing.`;\n}\n\n/** One finished test, as a line the next hypothesis has to be chosen against. */\nfunction pastTestLine(test: ExperimentHistoryEntry): string {\n const outcome: Record<ExperimentHistoryEntry[\"hypothesisOutcome\"], string> = {\n supported: \"the idea held\",\n refuted_opposite: \"the idea was WRONG — the change made it worse\",\n refuted_no_effect: \"no difference — the change did not matter\",\n unanswered: \"never answered\",\n };\n const kept = test.survivor === \"variant\" ? \"the new version was kept\" : \"the original was kept\";\n const learning = test.learning ? ` Learning: ${test.learning}` : \"\";\n return `“${test.hypothesis.change}” (${test.experimentId}, ${test.ranForDays} days): ${outcome[test.hypothesisOutcome]}; ${kept}${test.againstVerdict ? \" against the numbers\" : \"\"}.${learning}`;\n}\n\n/**\n * What this page has already taught, before a new hypothesis is chosen.\n *\n * First among the hints because it is the one thing the agent cannot know\n * from the page and cannot recover once it has proposed something: a refuted\n * idea proposed again spends the page's traffic to return the same answer.\n */\nfunction pastTestsHint(plan: Pick<ExperimentPlanResponse, \"pastTests\">): string | null {\n if (plan.pastTests.length === 0) return null;\n const lines = plan.pastTests.slice(0, 5).map(pastTestLine);\n const more =\n plan.pastTests.length > 5\n ? ` …and ${plan.pastTests.length - 5} more — \\`baker experiment history --landing <slug>\\` lists them all.`\n : \"\";\n return `This page has been tested before — read this before choosing what to change, and build on it rather than repeating it: ${lines.join(\" \")}${more}`;\n}\n\nexport function buildPlanHints(plan: ExperimentPlanResponse): string[] {\n const horizons = horizonsHint(plan);\n const past = pastTestsHint(plan);\n if (!plan.canRun) {\n return [\n plan.reason ?? \"This test cannot run on this page.\",\n ...(horizons && plan.visitors > 0 ? [horizons] : []),\n ...(past ? [past] : []),\n \"A test that cannot conclude is worse than no test — it still produces a number, and somebody acts on it.\",\n ];\n }\n const hints = [\n ...(past ? [past] : []),\n // First, because it changes how every number below should be read: a plan\n // sized on a stand-in is a guide, and the agent must say so to the client\n // rather than quote \"about 70 days\" as though it were measured.\n ...(plan.warning ? [plan.warning] : []),\n `This test is read on “${plan.goal}”. That is fixed when it starts and cannot be changed later.`,\n // Fires at the exact moment the hypothesis gets invented, whether or not\n // the agent read the family doc. `plan` says a question CAN be settled here\n // and says nothing about which question is worth asking — and a well-run\n // test of a bad idea spends the page's traffic and returns `no_difference`.\n \"Now decide WHAT to change, from evidence rather than taste. These numbers say which page and which step; they cannot say why. A second opinion answers why — Microsoft Clarity for what visitors actually do on the page, `baker gsc` for what they searched, `baker ads <platform>` for what the ad promised, `baker ga4` for the client's own measurement. `baker capabilities` lists everything this client has in one call. Message match — what the ad or the search promised against what the page opens with — is the cheapest hypothesis worth testing and is wrong surprisingly often.\",\n // The base always works, so a missing integration is never a reason to\n // stop. Naming what it would have shown is worth more to the client than\n // either a silent gap or a run that waited.\n \"If none of that is connected, this page's own drop-off is enough to test on — say which source you lacked and what it would have told you, then run the test anyway. A test never waits on an integration.\",\n ];\n const conversions = conversionsHint(plan);\n if (conversions) hints.push(conversions);\n if (plan.estimatedDays !== null && plan.estimatedDays > 60) {\n hints.push(\n `At this page's recent traffic it would take about ${plan.estimatedDays} days — a guide, not a limit: it ends when the numbers are clear, and more traffic (a campaign, ads) shortens it. If the traffic will stay as it is, test a bigger change, which needs fewer visitors to detect.`,\n );\n }\n if (horizons) hints.push(horizons);\n return hints;\n}\n\n/**\n * What to make of the record, once it has been read.\n *\n * The record is for informing the next hypothesis, so the hints say what it\n * settles: which ideas held and which were refuted on this page, and which\n * tests still have no conclusion written down.\n */\nexport function buildHistoryHints(tests: readonly ExperimentHistoryEntry[], landing: string | undefined): string[] {\n if (tests.length === 0) {\n return [\n landing\n ? `No finished test on “${landing}” yet — nothing to build on, and nothing to avoid repeating. \\`baker experiment plan\\` says whether the page can settle one.`\n : \"No finished tests yet. `baker experiment plan --landing <slug> --variant <slug>` says whether a page can settle one.\",\n ];\n }\n const hints: string[] = [];\n const supported = tests.filter((test) => test.hypothesisOutcome === \"supported\");\n const refuted = tests.filter((test) => test.hypothesisOutcome === \"refuted_opposite\");\n const noEffect = tests.filter((test) => test.hypothesisOutcome === \"refuted_no_effect\");\n if (supported.length > 0) {\n hints.push(\n `Held: ${supported.map((test) => `“${test.hypothesis.change}” on ${test.landingSlug}`).join(\"; \")}. Build on these — the same insight usually has more in it.`,\n );\n }\n if (refuted.length > 0) {\n hints.push(\n `Made it worse: ${refuted.map((test) => `“${test.hypothesis.change}” on ${test.landingSlug}`).join(\"; \")}. Do not propose these again as they were; the observation behind each may still be right, the change was not.`,\n );\n }\n if (noEffect.length > 0) {\n hints.push(\n `Did not matter: ${noEffect.map((test) => `“${test.hypothesis.change}” on ${test.landingSlug}`).join(\"; \")}. The next change on these pages has to be bigger — a headline, an offer, the shape of the page.`,\n );\n }\n const unlearned = tests.filter((test) => test.learning === null && test.verdict !== \"inconclusive\");\n if (unlearned.length > 0) {\n hints.push(\n `${unlearned.length === 1 ? \"One test has\" : `${unlearned.length} tests have`} no learning written down (${unlearned.map((test) => test.experimentId).join(\", \")}). Read its numbers and write one: \\`baker experiment update --id <id> --learning \"…\"\\`.`,\n );\n }\n return hints;\n}\n\n/**\n * What is worth saying about the two versions as staged, before the split.\n *\n * Two things, both read off the workspace rather than off the request: a\n * variant that owns no section renders the page itself, so the test would run\n * for weeks against itself; and a variant that uses a Form the page does not\n * converts on that Form's ending — a different event from the page's — so\n * unless the caller said which event the new version is read on\n * (`--variant-goal`), it would read as never converting for the whole run.\n */\nexport function buildStartHints(staged: {\n landing: string;\n variant: string;\n goal: string;\n /** The new version's own event, when the caller gave one. */\n variantGoal: string | undefined;\n composition: ExperimentComposition | undefined;\n /** Forms the variant renders that the page does not. */\n newForms: readonly string[];\n /** Conversions the two versions may no longer fire on the same action. */\n drift: readonly MeasurementDrift[];\n}): string[] {\n const hints: string[] = [];\n for (const entry of staged.drift) {\n hints.push(driftHint(staged, entry));\n }\n if (staged.composition && staged.composition.forked.length === 0) {\n hints.push(\n `“${staged.variant}” does not have its own copy of any section — it renders exactly the same page as “${staged.landing}”, so this test cannot find anything. Fork the section you want to test (\\`baker landing variant ${staged.landing} --fork Hero.astro\\`) and edit only that file.`,\n );\n }\n if (staged.newForms.length > 0 && staged.variantGoal === undefined) {\n const forms = staged.newForms.map((flow) => `“${flow}”`).join(\", \");\n hints.push(\n `“${staged.variant}” uses ${staged.newForms.length === 1 ? \"a Form\" : \"Forms\"} the page does not: ${forms}. The test reads the new version on \\`${staged.goal}\\`, the page's event — if the new version converts by reaching this Form's ending instead, it can never convert on that key and the test will call it harmful. Say its own event: \\`baker experiment update --id <id> --variant-goal submit:${staged.newForms[0]}\\` (or the confirmation step's \\`form:${staged.newForms[0]}:<node>:<trigger>\\` key, from \\`baker analytics conversions --candidates\\` once the Form has been opened in the preview).`,\n );\n }\n return hints;\n}\n\n/**\n * One conversion the two versions may not be counting the same way.\n *\n * Phrased as a question about one named event rather than as a refusal,\n * because whether the trigger still means the same thing is a judgement about\n * markup that a person makes in a glance and this cannot: a forked section is\n * *supposed* to change, and most of these are harmless. What is not acceptable\n * is nobody looking — the measured cost of not looking was a step that \"won\" by\n * 2.6x on two pages while comparing a click against a scroll.\n *\n * It names `baker analytics conversions` rather than describing the fix,\n * because the answer is in what the two pages actually declare and the command\n * that lists it is one hop away.\n */\nfunction driftHint(staged: { landing: string; variant: string }, entry: MeasurementDrift): string {\n const suffix = ` Check both versions before reading that number, and give the new version's step its own name if it is not the same action (\\`baker analytics conversions --candidates\\`).`;\n if (entry.reason === \"trigger_differs\") {\n return `“${staged.landing}” and “${staged.variant}” both count \\`${entry.name}\\`, but one fires it when the element is **scrolled into view** and the other when the visitor **does something**. Those are two different questions with one answer, and the test will report a difference that is the trigger rather than the page.${suffix}`;\n }\n if (entry.reason === \"variant_only_missing\" || entry.reason === \"control_only_missing\") {\n const missing = entry.reason === \"variant_only_missing\" ? staged.variant : staged.landing;\n return `Only one version fires \\`${entry.name}\\` — “${missing}” does not declare it at all, so it can only ever read zero for that conversion and the comparison will look like a collapse.${suffix}`;\n }\n return `Both versions count \\`${entry.name}\\`, and the section the test changed is what fires it — the markup around it is not the same on the two pages. If the new version fires it on a different action (a block that is visible from the start rather than one the visitor opens), that conversion is comparing two different things.${suffix}`;\n}\n","import { type ExperimentEvidence, experimentEvidenceSourceSchema } from \"@baker/api/experiments\";\nimport { repeatedValues } from \"../../lib/repeatedFlags.ts\";\n\n/**\n * `--evidence` as an agent would type it, into the structured citation.\n *\n * `source:note`, with an optional `|href` on the end:\n *\n * ```\n * --evidence \"baker_analytics:60% of visitors never reach the price\"\n * --evidence \"research:ICP interviews|https://…\"\n * ```\n *\n * Split on the **first** colon only, because a note routinely contains one —\n * \"rage clicks: 18 on the CTA\" — and losing half a citation to a separator is\n * worse than the flag being slightly loose. The link goes after a pipe for the\n * same reason: a URL is mostly colons.\n *\n * The source is a closed set so that \"which kinds of evidence produced tests\n * that held\" is answerable later, and `hunch` is deliberately in it: a belief\n * with no data behind it is still a belief, and forcing the agent to dress one\n * up as analytics is worse than letting it say so.\n */\ntype EvidenceResult = ExperimentEvidence[] | { error: string } | undefined;\n\nconst SOURCES = experimentEvidenceSourceSchema.options.join(\", \");\n\nfunction parseOne(raw: string): ExperimentEvidence | { error: string } {\n const separator = raw.indexOf(\":\");\n if (separator === -1) {\n return { error: `Write the evidence as source:note — for example \\`${raw}:what you saw\\`. Sources: ${SOURCES}` };\n }\n const source = experimentEvidenceSourceSchema.safeParse(raw.slice(0, separator).trim());\n if (!source.success) {\n return { error: `“${raw.slice(0, separator).trim()}” is not one of the evidence sources. Use one of: ${SOURCES}` };\n }\n\n const rest = raw.slice(separator + 1).trim();\n const pipe = rest.lastIndexOf(\"|\");\n const note = (pipe === -1 ? rest : rest.slice(0, pipe)).trim();\n const href = pipe === -1 ? undefined : rest.slice(pipe + 1).trim();\n if (note === \"\") {\n return { error: `Say what ${source.data} showed: --evidence \"${source.data}:what you saw\"` };\n }\n return href ? { source: source.data, note, href } : { source: source.data, note };\n}\n\n/**\n * `rawArgs` rather than the parsed arg, because citty declares no array arg\n * type: a repeated `--evidence` collapses to its LAST value and arrives as a\n * plain string, so `Array.isArray` never fires and\n * `--evidence \"clarity:…\" --evidence \"ads:…\"` records only the second citation,\n * with `ok: true` and nothing saying the first was dropped. The arg\n * description, the family doc and the `ab-test` skill all call it repeatable,\n * and the sources it loses are the whole point of recording evidence at all.\n */\nexport function parseEvidence(raw: string | string[] | undefined, rawArgs?: readonly string[]): EvidenceResult {\n const entries = repeatedValues(rawArgs, \"evidence\", raw)\n .map((entry) => entry.trim())\n .filter((entry) => entry !== \"\");\n if (entries.length === 0) return undefined;\n\n const parsed: ExperimentEvidence[] = [];\n for (const entry of entries) {\n const one = parseOne(entry);\n if (\"error\" in one) return one;\n parsed.push(one);\n }\n return parsed;\n}\n","import { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\n/**\n * A page's own identity, read off its `_definition.md`.\n *\n * `experiment start` sends both pages' identities along with their slugs. The\n * backend prefers the `landings` rows, but the variant has no row until this\n * Session is published — which is after the test is staged — so without this\n * the variant's identity was never recorded: a renamed variant folder detached\n * its running test, and a fold archived the loser under the page's slug rather\n * than its id. Best-effort: a definition that cannot be read costs the test\n * its rename-safety, never the test.\n */\nexport async function readPageInternalId(root: string, slug: string): Promise<string | undefined> {\n try {\n const text = await readFile(path.resolve(root, \"src\", \"pages\", slug, \"_definition.md\"), \"utf8\");\n return /^internalId:\\s*\"?([^\"\\s]+)\"?\\s*$/m.exec(text)?.[1];\n } catch {\n return undefined;\n }\n}\n","/**\n * Do the two versions of a page measure the same thing?\n *\n * Every other validity check a test has asks whether the *split* was sound —\n * that the traffic divided as intended, that nobody saw both pages, that both\n * arms exist. This asks the question underneath all of them, and nothing else\n * asks it: **the two versions agree about what an event means only because a\n * person made them agree.**\n *\n * A conversion is a name the company chose, matched against `data-baker-*`\n * declarations in the page's own source. Move the thing that carries one and\n * the name keeps counting, from a different action, and every number\n * downstream is arithmetic on two incomparable quantities. Nothing detects it:\n * the split is fine, there is no crossover, both arms are counted the same way,\n * and the defect is entirely in what the two pages chose to call the same.\n *\n * Found on a live test, which is why this exists. The page fired\n * `data-baker-view=\"checkout_popup\"` from inside a modal that starts `hidden`,\n * so it counted a visitor who **opened** the purchase form. The new version\n * moved that form inline and kept the name, so the same attribute fired when\n * the block **scrolled into view**. The new version \"won\" that step by 2.6x on\n * two separate pages, and the comparison was between a click and a scroll. The\n * source comment on the variant said the name was kept \"so they can be\n * compared\": the intent was right and nobody could see the result was not.\n *\n * ## Only the sections the two arms do not share\n *\n * A shared section is literally the same file — the variant imports it out of\n * the page's folder — so an event declared there cannot differ, by\n * construction. The comparison is between the sections the variant **forked**\n * and the sections of the page they replaced. That is also what keeps this\n * quiet: it can only fire on markup the test itself changed.\n *\n * ## Declarative triggers only\n *\n * It reads `data-baker-view=\"x\"` and `data-baker-event=\"x\"` out of the source.\n * An event fired from script, or named by an expression rather than a literal,\n * is invisible to it — so a clean result is \"nothing declared differs\", never\n * \"the two versions measure the same thing\". That is worth knowing and does not\n * make the declarative half less worth catching: the defect above was\n * declarative, and so is every trigger the scaffold's own components emit.\n */\n\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { ExperimentComposition } from \"@baker/api/experiments\";\nimport { landingGraphFiles } from \"../landing/import-graph.ts\";\n\n/** One `data-baker-*` declaration, as the page's source carries it. */\nexport interface EventDeclaration {\n /** The event name a conversion is matched on — `page:<name>` once counted. */\n readonly name: string;\n /** `data-baker-view` (scrolled into view) or `data-baker-event` (did something). */\n readonly attribute: string;\n /** The opening tag that carries it — what actually decides when the event fires. */\n readonly markup: string;\n}\n\n/**\n * Why two versions may not be measuring the same thing, worst first.\n *\n * - `trigger_differs` — one version fires the name on a view and the other on\n * an action. Two different questions with one answer.\n * - `markup_differs` — the same kind of trigger on an element the test changed.\n * Often harmless and never safe to assume so: whether it still fires on the\n * same action is a question about the surrounding markup, which a person can\n * answer in a glance and this cannot.\n * - `variant_only_missing` / `control_only_missing` — only one version fires\n * the name at all, so the other can only ever read zero for it.\n */\nexport type MeasurementDriftReason =\n | \"trigger_differs\"\n | \"markup_differs\"\n | \"variant_only_missing\"\n | \"control_only_missing\";\n\nexport interface MeasurementDrift {\n readonly name: string;\n readonly reason: MeasurementDriftReason;\n}\n\n/**\n * `data-baker-view=\"x\"` and `data-baker-event=\"x\"`, with the tag around them.\n *\n * The tag is taken by scanning back to the `<` that opens it and forward to the\n * `>` that closes it, rather than by parsing: these are Astro files, so any\n * parser would have to understand the template syntax too, and the opening tag\n * is the whole of what this needs.\n */\nexport function eventDeclarationsIn(source: string): EventDeclaration[] {\n const found: EventDeclaration[] = [];\n const pattern = /\\b(data-baker-(?:view|event))\\s*=\\s*[\"']([^\"']+)[\"']/g;\n for (const match of source.matchAll(pattern)) {\n const at = match.index;\n const opens = source.lastIndexOf(\"<\", at);\n const closes = source.indexOf(\">\", at);\n found.push({\n name: match[2] as string,\n attribute: match[1] as string,\n markup: normalise(source.slice(opens < 0 ? at : opens, closes < 0 ? at + match[0].length : closes + 1)),\n });\n }\n return found;\n}\n\n/** Whitespace is layout, not meaning: a reflowed attribute list is the same element. */\nfunction normalise(markup: string): string {\n return markup.replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Every event name the two versions might not agree about.\n *\n * A name declared identically on both sides is left out — the point of this is\n * to be worth reading, and a warning on every event a forked section happens to\n * carry is a warning nobody reads.\n */\nexport function measurementDrift(\n control: readonly EventDeclaration[],\n variant: readonly EventDeclaration[],\n): MeasurementDrift[] {\n const names = [...new Set([...control, ...variant].map((entry) => entry.name))].sort();\n return names.flatMap((name): MeasurementDrift[] => {\n const left = control.filter((entry) => entry.name === name);\n const right = variant.filter((entry) => entry.name === name);\n if (right.length === 0) return [{ name, reason: \"variant_only_missing\" }];\n if (left.length === 0) return [{ name, reason: \"control_only_missing\" }];\n if (setOf(left, (entry) => entry.attribute) !== setOf(right, (entry) => entry.attribute)) {\n return [{ name, reason: \"trigger_differs\" }];\n }\n if (setOf(left, (entry) => entry.markup) !== setOf(right, (entry) => entry.markup)) {\n return [{ name, reason: \"markup_differs\" }];\n }\n return [];\n });\n}\n\n/**\n * Two sets of declarations, compared as one string. Joined on a newline because\n * {@link normalise} has already collapsed every run of whitespace, so no member\n * can contain one and no two different sets can join to the same value.\n */\nfunction setOf(entries: readonly EventDeclaration[], of: (entry: EventDeclaration) => string): string {\n return [...new Set(entries.map(of))].sort().join(\"\\n\");\n}\n\n/**\n * The same answer, read from the workspace.\n *\n * Compares only what the two arms do not share: the sections the variant owns\n * (`composition.forked`), against the sections of the page they replaced. The\n * variant's slug is not needed — the composition already names its files. Returns an empty list rather\n * than throwing when the files cannot be read — a brief without this warning is\n * worse than one with it and much better than a test that could not be started,\n * which is the same trade `readComposition` makes beside it.\n */\nexport async function readMeasurementDrift(\n root: string,\n controlSlug: string,\n composition: ExperimentComposition | undefined,\n): Promise<MeasurementDrift[]> {\n if (!composition) return [];\n try {\n const pageSections = sectionsOf([...(await landingGraphFiles(root, controlSlug))], controlSlug);\n const shared = new Set(composition.shared);\n const [control, variant] = await Promise.all([\n declarationsOf(\n root,\n pageSections.filter((file) => !shared.has(file)),\n ),\n declarationsOf(root, composition.forked),\n ]);\n return measurementDrift(control, variant);\n } catch {\n return [];\n }\n}\n\n/** Sections only. `index.astro` is the page, and every arm owns its own. */\nfunction sectionsOf(files: readonly string[], slug: string): string[] {\n const prefix = `src/pages/${slug}/`;\n return files.filter((file) => file.startsWith(prefix) && !file.slice(prefix.length).startsWith(\"index.\"));\n}\n\nasync function declarationsOf(root: string, files: readonly string[]): Promise<EventDeclaration[]> {\n const sources = await Promise.all(files.map((file) => readFile(path.join(root, file), \"utf8\").catch(() => \"\")));\n return sources.flatMap(eventDeclarationsIn);\n}\n","/**\n * One test's status row, as the agent reads it.\n *\n * The wire row is shaped for the dashboard, which formats its own dates and\n * draws its own pictures: epoch milliseconds (a projection is fractional),\n * preview and snapshot URLs, the hypothesis in parts. Printed as-is it cost an\n * agent three calls to find one date, and a hand-converted epoch once told a\n * client a test needed ten more months.\n *\n * So every moment is a date a reader can say aloud, the projection says how\n * far off it is in the same words the hint and the card use, and the fields\n * that decide come first. `--full` keeps every field the server sent, with the\n * same dates.\n */\n\nimport {\n describeWait,\n type ExperimentArm,\n type ExperimentMetric,\n type ExperimentStatusRow,\n} from \"@baker/api/experiments\";\n\ninterface ReadoutOptions {\n readonly full: boolean;\n readonly now: number;\n}\n\nfunction isoMoment(ms: number): string;\nfunction isoMoment(ms: number | null): string | null;\nfunction isoMoment(ms: number | null): string | null {\n return ms === null ? null : new Date(ms).toISOString();\n}\n\n/**\n * A projection from today's traffic is known to the day, never to the millisecond.\n * The day is UTC's: a company has no timezone to read it in, and a projection is\n * not precise enough for the hour to matter.\n */\nfunction isoDay(ms: number | null): string | null {\n return ms === null ? null : new Date(ms).toISOString().slice(0, 10);\n}\n\n/** A rate to the precision anyone reads one at: 4.61%, not 4.611650485436893%. */\nfunction compactArm(arm: ExperimentArm) {\n return { visitors: arm.visitors, conversions: arm.conversions, rate: Math.round(arm.rate * 10_000) / 10_000 };\n}\n\n/** A secondary is context, read by its one word; its rates, lift and interval are for --full. */\nfunction compactMetric(metric: ExperimentMetric) {\n return {\n name: metric.name,\n reading: metric.reading,\n control: metric.control.conversions,\n variant: metric.variant.conversions,\n };\n}\n\nfunction fullArm(arm: ExperimentArm) {\n return {\n ...arm,\n ...(arm.snapshotCapturedAt === undefined ? {} : { snapshotCapturedAt: isoMoment(arm.snapshotCapturedAt) }),\n };\n}\n\nexport function statusReadout(row: ExperimentStatusRow, { full, now }: ReadoutOptions): Record<string, unknown> {\n const decisionWhen = {\n earliestDecisionAt: isoDay(row.earliestDecisionAt),\n ...(row.earliestDecisionAt === null ? {} : { earliestDecisionIn: describeWait(row.earliestDecisionAt, now) }),\n };\n const moments = {\n startedAt: isoMoment(row.startedAt),\n pausedAt: isoMoment(row.pausedAt),\n finishedAt: isoMoment(row.finishedAt),\n };\n\n if (full) {\n return {\n ...row,\n ...moments,\n ...decisionWhen,\n control: fullArm(row.control),\n variant: fullArm(row.variant),\n };\n }\n\n return {\n experimentId: row.experimentId,\n landingSlug: row.landingSlug,\n variantSlug: row.variantSlug,\n status: row.status,\n verdict: row.verdict,\n summary: row.summary,\n ...(row.winner === null ? {} : { winner: row.winner }),\n ...(row.invalidReason === null ? {} : { invalidReason: row.invalidReason }),\n stillNeeded: row.stillNeeded,\n ...decisionWhen,\n willNotConclude: row.willNotConclude,\n control: compactArm(row.control),\n variant: compactArm(row.variant),\n secondary: row.secondary.map(compactMetric),\n hypothesisStatement: row.hypothesisStatement,\n minDetectableRelativeLift: row.minDetectableRelativeLift,\n goal: row.goal,\n ...(row.variantGoal === null ? {} : { variantGoal: row.variantGoal }),\n goalLabel: row.goalLabel,\n ...moments,\n ...(row.status === \"finished\"\n ? {\n survivor: row.survivor,\n hypothesisOutcome: row.hypothesisOutcome,\n fold: row.fold,\n learning: row.learning,\n endReason: row.endReason,\n }\n : {}),\n };\n}\n","import type { ExperimentUpdateRequest } from \"@baker/api/experiments\";\nimport { parseEvidence } from \"./hypothesis.ts\";\n\n/**\n * `baker experiment update`'s flags, into the request — or the reason they\n * cannot be.\n *\n * Pure so the two rules that matter are pinned: a call that changes nothing is\n * refused before it reaches the network, and `--expect` is checked here for the\n * same two values `start` accepts. Which fields a *live* test may change is\n * decided by the backend, which knows whether the test has started; this only\n * shapes what was typed.\n */\nexport function buildUpdateRequest(\n args: {\n id: unknown;\n because?: unknown;\n change?: unknown;\n expect?: unknown;\n evidence?: unknown;\n goal?: unknown;\n variantGoal?: unknown;\n lift?: unknown;\n learning?: unknown;\n },\n /** The command line, so a repeated `--evidence` keeps every citation. */\n rawArgs?: readonly string[],\n): ExperimentUpdateRequest | { error: string } {\n const expect = args.expect === undefined ? undefined : String(args.expect);\n if (expect !== undefined && expect !== \"increase\" && expect !== \"decrease\") {\n return {\n error: \"`--expect` is either `increase` or `decrease` — which way should the goal move if you are right?\",\n };\n }\n const evidence = parseEvidence(args.evidence as string | string[] | undefined, rawArgs);\n if (evidence && \"error\" in evidence) return evidence;\n\n const lift = args.lift === undefined ? undefined : Number(args.lift);\n if (lift !== undefined && !(lift > 0)) {\n return {\n error: \"`--lift` is the smallest improvement worth detecting, relative — 0.2 is +20%. It must be above zero.\",\n };\n }\n\n const request: ExperimentUpdateRequest = {\n experimentId: String(args.id),\n ...typed(\"because\", args.because),\n ...typed(\"change\", args.change),\n ...typed(\"goal\", args.goal),\n ...typed(\"variantGoal\", args.variantGoal),\n ...typed(\"learning\", args.learning),\n ...(expect === undefined ? {} : { expect }),\n ...(evidence ? { evidence } : {}),\n ...(lift === undefined ? {} : { minDetectableRelativeLift: lift }),\n };\n if (Object.keys(request).length === 1) {\n return {\n error:\n \"Nothing to change. Pass --because, --change or --evidence; on a finished test, --learning; on a test that has not been published yet, also --expect, --goal, --variant-goal or --lift\",\n };\n }\n return request;\n}\n\n/** The flag as typed, or nothing at all — so an absent flag never blanks a field. */\nfunction typed<K extends \"because\" | \"change\" | \"goal\" | \"variantGoal\" | \"learning\">(\n key: K,\n value: unknown,\n): { [P in K]?: string } {\n return value === undefined ? {} : ({ [key]: String(value) } as { [P in K]: string });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAQA,SAAS,qBAAqB;;;ACR9B,SAAS,SAAS,gBAAgB;AAClC,OAAO,UAAU;AAoBjB,SAAS,cAAc,OAA0B,MAAwB;AACvE,QAAM,SAAS,aAAa,IAAI;AAChC,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,MAAM,KAAK,CAAC,KAAK,MAAM,OAAO,MAAM,EAAE,WAAW,QAAQ,CAAC,EAAE,KAAK;AACjH;AAUO,SAAS,cACd,aACA,aACA,cACuB;AACvB,SAAO;AAAA,IACL,QAAQ,cAAc,cAAc,WAAW;AAAA,IAC/C,QAAQ,cAAc,cAAc,WAAW;AAAA,EACjD;AACF;AAUA,eAAsB,gBACpB,MACA,aACA,aAC4C;AAC5C,MAAI;AACF,UAAM,QAAQ,MAAM,kBAAkB,MAAM,WAAW;AACvD,WAAO,cAAc,aAAa,aAAa,CAAC,GAAG,KAAK,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,IAAM,qBAAqB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,gBAAgB,OAA0BA,aAA2C;AACnG,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,QAAQ,OAAO;AACxB,eAAW,MAAM,oBAAoB;AACnC,iBAAW,SAAS,KAAK,SAAS,EAAE,GAAG;AACrC,cAAM,OAAO,MAAM,CAAC;AACpB,YAAI,QAAQA,YAAW,IAAI,IAAI,EAAG,OAAM,IAAI,IAAI;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AACA,SAAO,CAAC,GAAG,KAAK,EAAE,KAAK;AACzB;AAGO,SAAS,YAAY,OAA0B,OAAoC;AACxF,QAAM,QAAQ,IAAI,IAAI,KAAK;AAC3B,SAAO,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC;AAChD;AAEA,eAAe,WAAW,MAAoC;AAC5D,MAAI;AACF,UAAM,UAAU,MAAM,QAAQ,KAAK,KAAK,MAAM,2BAA2B,GAAG,EAAE,eAAe,KAAK,CAAC;AACnG,WAAO,IAAI,IAAI,QAAQ,OAAO,CAAC,UAAU,MAAM,YAAY,CAAC,EAAE,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC;AAAA,EAC1F,QAAQ;AACN,WAAO,oBAAI,IAAI;AAAA,EACjB;AACF;AAYA,eAAsB,UAAU,MAAc,MAAiC;AAC7E,MAAI;AACF,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM,QAAQ,IAAI,CAAC,kBAAkB,MAAM,IAAI,GAAG,WAAW,IAAI,CAAC,CAAC;AAC1F,QAAI,MAAM,SAAS,EAAG,QAAO,CAAC;AAC9B,UAAM,QAAQ,MAAM,QAAQ;AAAA,MAC1B,CAAC,GAAG,KAAK,EACN,OAAO,CAAC,SAAS,2BAA2B,KAAK,IAAI,CAAC,EACtD,IAAI,CAAC,SAAS,SAAS,KAAK,KAAK,MAAM,IAAI,GAAG,MAAM,CAAC;AAAA,IAC1D;AACA,WAAO,gBAAgB,OAAO,KAAK;AAAA,EACrC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;ACjIA,SAAsB,gBAAgB;AACtC,SAAS,IAAI,OAAO,WAAAC,UAAS,YAAAC,WAAU,IAAI,MAAM,iBAAiB;AAClE,OAAOC,WAAU;AAkFV,IAAM,eAAe;AAmB5B,IAAM,kBAAkB;AAExB,SAAS,MAAM,GAAmB;AAChC,SAAO,EAAE,MAAMC,MAAK,GAAG,EAAE,KAAK,GAAG;AACnC;AAEA,eAAe,MAAM,GAA6B;AAChD,MAAI;AACF,YAAQ,MAAM,KAAK,CAAC,GAAG,YAAY;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,WAAW,KAAa,SAAS,IAAuB;AACrE,MAAI;AACJ,MAAI;AACF,cAAU,MAAMC,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,EACtD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM;AACvD,QAAI,MAAM,YAAY,EAAG,KAAI,KAAK,GAAI,MAAM,WAAWD,MAAK,KAAK,KAAK,MAAM,IAAI,GAAG,GAAG,CAAE;AAAA,QACnF,KAAI,KAAK,GAAG;AAAA,EACnB;AACA,SAAO,IAAI,KAAK;AAClB;AAEA,IAAM,WAAW;AAUjB,SAAS,eAAe,KAAa,MAA6B;AAChE,MAAI,CAAC,IAAI,WAAW,OAAOA,MAAK,GAAG,EAAG,QAAO;AAC7C,SAAO,MAAMA,MAAK,SAAS,MAAM,GAAG,CAAC;AACvC;AAUA,eAAe,WAAW,MAMJ;AACpB,QAAM,EAAE,MAAM,QAAQ,SAAS,YAAY,QAAQ,IAAI;AACvD,QAAM,UAAU,MAAM,gBAAgB,EAAE,MAAM,QAAQ,SAAS,QAAQ,CAAC;AACxE,QAAM,mBAAmB,IAAI,IAAI,CAAC,GAAG,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,KAAKA,MAAK,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC;AACpG,QAAM,UAAoB,CAAC;AAC3B,aAAW,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,EAAE,KAAK,GAAG;AAC5C,UAAM,OAAOA,MAAK,KAAK,YAAY,GAAG;AACtC,UAAM,MAAMA,MAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,QAAI,CAAC,SAAS,KAAK,GAAG,GAAG;AACvB,YAAM,GAAG,KAAK,IAAI;AAAA,IACpB,OAAO;AACL,YAAM,OAAO,MAAME,UAAS,KAAK,MAAM;AACvC,YAAM,UAAU,MAAM,mBAAmB,MAAM,KAAK,MAAM,gBAAgB,GAAG,MAAM;AAAA,IACrF;AACA,YAAQ,KAAK,MAAMF,MAAK,SAAS,MAAM,IAAI,CAAC,CAAC;AAAA,EAC/C;AACA,SAAO;AACT;AASA,eAAe,gBAAgB,MAKE;AAC/B,QAAM,EAAE,MAAM,QAAQ,SAAS,QAAQ,IAAI;AAC3C,QAAM,QAAQ,MAAM,kBAAkB,MAAM,OAAO;AACnD,QAAM,YAAY,IAAI,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,QAAQA,MAAK,QAAQ,MAAM,GAAG,CAAC,CAAC;AAG1E,aAAW,OAAO,MAAM,WAAW,MAAM,EAAG,WAAU,IAAIA,MAAK,KAAK,QAAQ,GAAG,CAAC;AAEhF,QAAM,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK;AACnC,QAAM,UAAU,oBAAI,IAAoB;AACxC,aAAW,QAAQ,CAAC,SAAS,MAAM,GAAG;AACpC,eAAW,OAAO,QAAQ;AACxB,YAAM,MAAM,eAAe,KAAK,IAAI;AAGpC,UAAI,QAAQ,QAAQ,QAAQ,iBAAkB;AAC9C,cAAQ,IAAI,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AACA,SAAO;AACT;AAsBA,SAAS,mBACP,MACA,WACA,SACA,kBACQ;AACR,QAAM,UAAUA,MAAK,QAAQ,SAAS;AACtC,QAAM,QAAQA,MAAK,QAAQ,OAAO;AAClC,SAAO,KAAK,QAAQ,iBAAiB,CAAC,OAAO,OAAe,MAAc,WAAmB;AAC3F,UAAM,CAAC,UAAU,MAAM,IAAI,YAAY,IAAI;AAC3C,UAAM,SAASA,MAAK,QAAQ,SAAS,QAAQ;AAC7C,UAAM,WACJ,iBAAiB,QAAQ,gBAAgB,KAAK,cAAc,QAAQ,iBAAiB,MAAM,MAAM,CAAC;AACpG,QAAI,aAAa,KAAM,QAAO;AAC9B,UAAM,MAAM,MAAMA,MAAK,SAAS,OAAO,QAAQ,CAAC;AAChD,WAAO,GAAG,KAAK,GAAG,IAAI,WAAW,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK;AAAA,EAC3E,CAAC;AACH;AASA,SAAS,YAAY,MAAgC;AACnD,QAAM,MAAM,KAAK,OAAO,MAAM;AAC9B,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,GAAG,CAAC;AACvE;AAGA,IAAM,aAAa,CAAC,IAAI,UAAU,OAAO,QAAQ,OAAO,QAAQ,MAAM;AAStE,SAAS,iBAAiB,MAAc,kBAA8D;AACpG,aAAW,UAAU,CAAC,GAAG,YAAY,gBAAgB,WAAW,GAAG;AACjE,UAAM,MAAM,iBAAiB,IAAI,OAAO,MAAM;AAC9C,QAAI,QAAQ,OAAW,QAAO;AAAA,EAChC;AACA,SAAO;AACT;AAWA,SAAS,iBAAiB,MAAc,QAAyB;AAC/D,SAAO,sCAAsC,KAAK,KAAK,MAAM,KAAK,IAAI,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC;AAChG;AAoBA,SAAS,cAAc,MAAc,qBAA6C;AAChF,QAAM,aAAa,sBAAsB,CAAC,GAAG,YAAY,gBAAgB,aAAa,WAAW,IAAI;AACrG,aAAW,UAAU,YAAY;AAC/B,QAAI;AACF,UAAI,SAAS,OAAO,MAAM,EAAE,OAAO,EAAG,QAAO,OAAO;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AASA,eAAe,oBAAoB,MAAgF;AACjH,QAAM,EAAE,MAAM,SAAS,WAAW,IAAI;AACtC,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,MAAM,WAAW,UAAU,GAAG;AAC9C,QAAI,QAAQ,iBAAkB;AAC9B,UAAM,OAAOA,MAAK,KAAK,YAAY,GAAG;AACtC,UAAM,KAAKA,MAAK,KAAK,SAAS,GAAG;AACjC,UAAM,MAAMA,MAAK,QAAQ,EAAE,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,QAAI,CAAC,SAAS,KAAK,GAAG,GAAG;AACvB,YAAM,GAAG,MAAM,EAAE;AAAA,IACnB,OAAO;AACL,YAAM,OAAO,MAAME,UAAS,MAAM,MAAM;AAIxC,YAAM;AAAA,QACJ;AAAA,QACA,aAAa,MAAM;AAAA,UACjB,SAASF,MAAK,QAAQ,IAAI;AAAA,UAC1B,OAAOA,MAAK,QAAQ,EAAE;AAAA,UACtB,WAAW;AAAA,UACX,SAAS;AAAA,QACX,CAAC;AAAA,QACD;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,MAAMA,MAAK,SAAS,MAAM,EAAE,CAAC,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAUA,SAAS,cAAc,QAAoB,eAA+B;AACxE,SAAO,GAAG,KAAK;AAAA,IACb;AAAA,MACE,cAAc,OAAO;AAAA,MACrB,MAAM,OAAO;AAAA,MACb,gBAAgB,OAAO;AAAA,MACvB,SAAS,OAAO;AAAA,MAChB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKA,KAAK,OAAO,aAAa,YAAY,0BAA0B;AAAA,IACjE;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAAA;AACH;AASA,eAAsB,eAAe,MAAc,QAA6D;AAC9G,QAAM,UAAUA,MAAK,QAAQ,MAAM,aAAa,OAAO,WAAW;AAClE,QAAM,aAAaA,MAAK,QAAQ,MAAM,aAAa,OAAO,WAAW;AACrE,QAAM,gBAAgB,gBAAgB,OAAO,WAAW;AAExD,MAAI,kBAAkB,MAAM;AAC1B,WAAO;AAAA,MACL,OAAO,SAAI,OAAO,WAAW,oCAA0B,OAAO,WAAW;AAAA,IAC3E;AAAA,EACF;AACA,MAAI,CAAE,MAAM,MAAM,OAAO,GAAI;AAC3B,WAAO,EAAE,OAAO,iCAAiC,OAAO,WAAW,IAAI;AAAA,EACzE;AACA,MAAI,CAAE,MAAM,MAAM,UAAU,GAAI;AAC9B,WAAO,EAAE,OAAO,aAAa,OAAO,WAAW,6DAAwD;AAAA,EACzG;AAEA,QAAM,aAAa,OAAO,qBAAqB,OAAO;AACtD,QAAM,aAAaA,MAAK,QAAQ,MAAM,cAAc,YAAY,OAAO,aAAa,CAAC;AACrF,QAAM,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACrD,QAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAK3C,QAAM,UAAU,MAAM,WAAW;AAAA,IAC/B;AAAA,IACA,QAAQ,OAAO,aAAa,YAAY,UAAU;AAAA,IAClD;AAAA,IACA;AAAA,IACA,SAAS,OAAO,aAAa,YAAY,OAAO,cAAc,OAAO;AAAA,EACvE,CAAC;AACD,QAAM,UAAUA,MAAK,KAAK,YAAY,eAAe,GAAG,cAAc,QAAQ,aAAa,GAAG,MAAM;AACpG,UAAQ,KAAK,MAAMA,MAAK,KAAK,cAAc,YAAY,OAAO,aAAa,GAAG,eAAe,CAAC,CAAC;AAE/F,MAAI,OAAO,aAAa,WAAW;AACjC,YAAQ,KAAK,GAAI,MAAM,oBAAoB,EAAE,MAAM,SAAS,WAAW,CAAC,CAAE;AAAA,EAC5E;AAEA,QAAM,GAAG,YAAY,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAErD,SAAO;AAAA,IACL,cAAc,OAAO;AAAA,IACrB,MAAM,OAAO;AAAA,IACb,YAAY,MAAMA,MAAK,KAAK,cAAc,YAAY,OAAO,aAAa,CAAC,CAAC;AAAA,IAC5E;AAAA,IACA,SAAS,CAAC,aAAa,OAAO,WAAW,EAAE;AAAA,EAC7C;AACF;;;AC5ZA,SAAS,SAAS;AAeX,IAAM,iCAAiC,EAAE,KAAK;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,IAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,QAAQ;AAAA,EACR,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AACrC,CAAC;AAGM,IAAM,6BAA6B,EAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjD,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA;AAAA,EAEjC,QAAQ,EAAE,KAAK,CAAC,YAAY,UAAU,CAAC,EAAE,QAAQ,UAAU;AAAA,EAC3D,UAAU,EAAE,MAAM,wBAAwB,EAAE,IAAI,CAAC,EAAE,SAAS;AAC9D,CAAC;AAeM,IAAM,oCAAoC,EAAE,KAAK;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;;;ACnHD,IAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,SAAS,MAAM,OAAe,MAAsB;AAClD,SAAO,SAAS,KAAK,IAAI,IAAI,GAAG,UAAU,IAAI,KAAK,GAAG;AACxD;AAUO,SAAS,aAAa,oBAA4B,KAAqB;AAC5E,QAAM,QAAQ,qBAAqB,OAAO;AAC1C,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,OAAO,EAAG,QAAO;AACrB,MAAI,OAAO,GAAI,QAAO,MAAM,KAAK,MAAM,IAAI,GAAG,KAAK;AACnD,MAAI,OAAO,GAAI,QAAO,MAAM,KAAK,MAAM,OAAO,CAAC,GAAG,MAAM;AACxD,SAAO,MAAM,KAAK,MAAM,OAAO,EAAE,GAAG,OAAO;AAC7C;AAMO,SAAS,WAAWG,QAAuB;AAChD,MAAIA,SAAQ,GAAI,QAAO,SAAS,KAAK,IAAI,GAAG,KAAK,MAAMA,MAAK,CAAC,EAAE,eAAe,OAAO,CAAC;AACtF,QAAM,YAAY,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,MAAMA,MAAK,CAAC,IAAI,CAAC;AACrE,SAAO,UAAU,KAAK,MAAMA,SAAQ,SAAS,IAAI,WAAW,eAAe,OAAO,CAAC;AACrF;;;ACDA,IAAM,YAAuG;AAAA,EAC3G,cAAc,CAAC,KAAK,QAClB,GAAG,IAAI,YAAY,kFAA6EC,cAAa,KAAK,GAAG,CAAC,gDAAgD,IAAI,OAAO;AAAA,EACnL,QAAQ,CAAC,QACP,IAAI,WAAW,YACX,GAAG,IAAI,YAAY,sEAAsE,IAAI,YAAY,8EACzG,GAAG,IAAI,YAAY,uHAAuH,IAAI,YAAY;AAAA,EAChK,eAAe,CAAC,QACd,GAAG,IAAI,YAAY;AAAA,EACrB,YAAY,CAAC,QACX,GAAG,IAAI,YAAY,4KAAuK,IAAI,YAAY;AAAA,EAC5M,uBAAuB,CAAC,QACtB,GAAG,IAAI,YAAY,6FAA6F,IAAI,YAAY;AAAA;AAAA;AAAA;AAAA,EAIlI,cAAc,CAAC,QAAQ,GAAG,IAAI,YAAY;AAAA,EAC1C,SAAS,CAAC,QACR,GAAG,IAAI,YAAY,oBACjB,IAAI,kBAAkB,cAAc,wCAAwC,kCAC9E;AACJ;AAUA,SAASA,cAAa,KAA0B,KAAqB;AACnE,MAAI,IAAI,iBAAiB;AACvB,WAAO;AAAA,EACT;AACA,MAAI,IAAI,uBAAuB,MAAM;AACnC,WAAO;AAAA,EACT;AACA,MAAI,IAAI,gBAAgB,MAAM;AAC5B,WAAO,4GAAuG,aAAgB,IAAI,oBAAoB,GAAG,CAAC;AAAA,EAC5J;AACA,QAAM,SAAS,YAAY,WAAW,IAAI,YAAY,QAAQ,CAAC,mBAAmB,WAAW,IAAI,YAAY,WAAW,CAAC;AACzH,SAAO,GAAG,MAAM,GAAG,aAAgB,IAAI,oBAAoB,GAAG,CAAC,wBAAwB,IAAI,KAAK,IAAI,kBAAkB,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACpJ;AAGA,SAAS,WAAW,KAAkC;AACpD,SAAO,IAAI,WAAW,WAClB,GAAG,IAAI,YAAY,gFAA2E,IAAI,YAAY,gDAC9G;AACN;AAMA,SAAS,cAAc,aAA4D;AACjF,QAAM,YAAY,YAAY;AAAA,IAC5B,CAAC,QAAQ,IAAI,WAAW,cAAc,IAAI,aAAa,QAAQ,IAAI,YAAY;AAAA,EACjF;AACA,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,QAAM,MAAM,UAAU,IAAI,CAAC,QAAQ,IAAI,YAAY,EAAE,KAAK,IAAI;AAC9D,SAAO,GAAG,UAAU,WAAW,IAAI,0BAA0B,GAAG,UAAU,MAAM,sBAAsB,kCAAkC,GAAG;AAC7I;AAEO,SAAS,iBAAiB,aAA6C,MAAM,KAAK,IAAI,GAAa;AACxG,QAAM,QAAQ,YACX,OAAO,CAAC,QAAQ,IAAI,WAAW,UAAU,EACzC,IAAI,CAAC,QAAQ,WAAW,GAAG,IAAI,UAAU,IAAI,OAAO,EAAE,KAAK,GAAG,CAAC;AAClE,QAAM,YAAY,cAAc,WAAW;AAC3C,MAAI,UAAW,OAAM,KAAK,SAAS;AAEnC,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,OAAO,OAAuB;AACrC,SAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AACpC;AASA,SAAS,aAAa,MAA+D;AACnF,QAAM,UAAU,KAAK,SAAS,OAAO,CAAC,YAAY,QAAQ,mBAAmB,IAAI;AACjF,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,QAAQ,IAAI,CAAC,YAAY,GAAG,QAAQ,IAAI,gBAAW,OAAO,QAAQ,kBAAkB,CAAC,CAAC,EAAE;AACtG,SAAO,wEAAwE,MAAM,KAAK,IAAI,CAAC;AACjG;AAGA,SAAS,gBAAgB,MAA2E;AAClG,MAAI,KAAK,YAAY,UAAU,EAAG,QAAO;AACzC,QAAM,SAAS,KAAK,YAAY,OAAO,CAAC,UAAU,MAAM,aAAa,KAAK,IAAI;AAC9E,SAAO,yCAAyC,OAC7C,IAAI,CAAC,UAAU,KAAK,MAAM,QAAQ,KAAK,MAAM,OAAO,WAAM,MAAM,IAAI,YAAO,EAAE,WAAM,MAAM,kBAAkB,EAAE,EAC7G;AAAA,IACC;AAAA,EACF,CAAC;AACL;AAGA,SAAS,aAAa,MAAsC;AAC1D,QAAM,UAAuE;AAAA,IAC3E,WAAW;AAAA,IACX,kBAAkB;AAAA,IAClB,mBAAmB;AAAA,IACnB,YAAY;AAAA,EACd;AACA,QAAM,OAAO,KAAK,aAAa,YAAY,6BAA6B;AACxE,QAAM,WAAW,KAAK,WAAW,cAAc,KAAK,QAAQ,KAAK;AACjE,SAAO,SAAI,KAAK,WAAW,MAAM,WAAM,KAAK,YAAY,KAAK,KAAK,UAAU,WAAW,QAAQ,KAAK,iBAAiB,CAAC,KAAK,IAAI,GAAG,KAAK,iBAAiB,yBAAyB,EAAE,IAAI,QAAQ;AACjM;AASA,SAAS,cAAc,MAAgE;AACrF,MAAI,KAAK,UAAU,WAAW,EAAG,QAAO;AACxC,QAAM,QAAQ,KAAK,UAAU,MAAM,GAAG,CAAC,EAAE,IAAI,YAAY;AACzD,QAAM,OACJ,KAAK,UAAU,SAAS,IACpB,cAAS,KAAK,UAAU,SAAS,CAAC,+EAClC;AACN,SAAO,+HAA0H,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI;AACzJ;AAEO,SAAS,eAAe,MAAwC;AACrE,QAAM,WAAW,aAAa,IAAI;AAClC,QAAM,OAAO,cAAc,IAAI;AAC/B,MAAI,CAAC,KAAK,QAAQ;AAChB,WAAO;AAAA,MACL,KAAK,UAAU;AAAA,MACf,GAAI,YAAY,KAAK,WAAW,IAAI,CAAC,QAAQ,IAAI,CAAC;AAAA,MAClD,GAAI,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACA,QAAM,QAAQ;AAAA,IACZ,GAAI,OAAO,CAAC,IAAI,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,IAIrB,GAAI,KAAK,UAAU,CAAC,KAAK,OAAO,IAAI,CAAC;AAAA,IACrC,8BAAyB,KAAK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlC;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,EACF;AACA,QAAM,cAAc,gBAAgB,IAAI;AACxC,MAAI,YAAa,OAAM,KAAK,WAAW;AACvC,MAAI,KAAK,kBAAkB,QAAQ,KAAK,gBAAgB,IAAI;AAC1D,UAAM;AAAA,MACJ,qDAAqD,KAAK,aAAa;AAAA,IACzE;AAAA,EACF;AACA,MAAI,SAAU,OAAM,KAAK,QAAQ;AACjC,SAAO;AACT;AASO,SAAS,kBAAkB,OAA0C,SAAuC;AACjH,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO;AAAA,MACL,UACI,6BAAwB,OAAO,2IAC/B;AAAA,IACN;AAAA,EACF;AACA,QAAM,QAAkB,CAAC;AACzB,QAAM,YAAY,MAAM,OAAO,CAAC,SAAS,KAAK,sBAAsB,WAAW;AAC/E,QAAM,UAAU,MAAM,OAAO,CAAC,SAAS,KAAK,sBAAsB,kBAAkB;AACpF,QAAM,WAAW,MAAM,OAAO,CAAC,SAAS,KAAK,sBAAsB,mBAAmB;AACtF,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM;AAAA,MACJ,SAAS,UAAU,IAAI,CAAC,SAAS,SAAI,KAAK,WAAW,MAAM,aAAQ,KAAK,WAAW,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IACnG;AAAA,EACF;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM;AAAA,MACJ,kBAAkB,QAAQ,IAAI,CAAC,SAAS,SAAI,KAAK,WAAW,MAAM,aAAQ,KAAK,WAAW,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IAC1G;AAAA,EACF;AACA,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM;AAAA,MACJ,mBAAmB,SAAS,IAAI,CAAC,SAAS,SAAI,KAAK,WAAW,MAAM,aAAQ,KAAK,WAAW,EAAE,EAAE,KAAK,IAAI,CAAC;AAAA,IAC5G;AAAA,EACF;AACA,QAAM,YAAY,MAAM,OAAO,CAAC,SAAS,KAAK,aAAa,QAAQ,KAAK,YAAY,cAAc;AAClG,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM;AAAA,MACJ,GAAG,UAAU,WAAW,IAAI,iBAAiB,GAAG,UAAU,MAAM,aAAa,8BAA8B,UAAU,IAAI,CAAC,SAAS,KAAK,YAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IAClK;AAAA,EACF;AACA,SAAO;AACT;AAYO,SAAS,gBAAgB,QAWnB;AACX,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,OAAO,OAAO;AAChC,UAAM,KAAK,UAAU,QAAQ,KAAK,CAAC;AAAA,EACrC;AACA,MAAI,OAAO,eAAe,OAAO,YAAY,OAAO,WAAW,GAAG;AAChE,UAAM;AAAA,MACJ,SAAI,OAAO,OAAO,qGAAsF,OAAO,OAAO,yGAAoG,OAAO,OAAO;AAAA,IAC1O;AAAA,EACF;AACA,MAAI,OAAO,SAAS,SAAS,KAAK,OAAO,gBAAgB,QAAW;AAClE,UAAM,QAAQ,OAAO,SAAS,IAAI,CAAC,SAAS,SAAI,IAAI,QAAG,EAAE,KAAK,IAAI;AAClE,UAAM;AAAA,MACJ,SAAI,OAAO,OAAO,eAAU,OAAO,SAAS,WAAW,IAAI,WAAW,OAAO,uBAAuB,KAAK,yCAAyC,OAAO,IAAI,oPAA+O,OAAO,SAAS,CAAC,CAAC,yCAAyC,OAAO,SAAS,CAAC,CAAC;AAAA,IAC3d;AAAA,EACF;AACA,SAAO;AACT;AAgBA,SAAS,UAAU,QAA8C,OAAiC;AAChG,QAAM,SAAS;AACf,MAAI,MAAM,WAAW,mBAAmB;AACtC,WAAO,SAAI,OAAO,OAAO,oBAAU,OAAO,OAAO,uBAAkB,MAAM,IAAI,wPAAwP,MAAM;AAAA,EAC7U;AACA,MAAI,MAAM,WAAW,0BAA0B,MAAM,WAAW,wBAAwB;AACtF,UAAM,UAAU,MAAM,WAAW,yBAAyB,OAAO,UAAU,OAAO;AAClF,WAAO,4BAA4B,MAAM,IAAI,mBAAS,OAAO,qIAAgI,MAAM;AAAA,EACrM;AACA,SAAO,yBAAyB,MAAM,IAAI,uSAAkS,MAAM;AACpV;;;ACrSA,IAAM,UAAU,+BAA+B,QAAQ,KAAK,IAAI;AAEhE,SAAS,SAAS,KAAqD;AACrE,QAAM,YAAY,IAAI,QAAQ,GAAG;AACjC,MAAI,cAAc,IAAI;AACpB,WAAO,EAAE,OAAO,0DAAqD,GAAG,6BAA6B,OAAO,GAAG;AAAA,EACjH;AACA,QAAM,SAAS,+BAA+B,UAAU,IAAI,MAAM,GAAG,SAAS,EAAE,KAAK,CAAC;AACtF,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,EAAE,OAAO,SAAI,IAAI,MAAM,GAAG,SAAS,EAAE,KAAK,CAAC,0DAAqD,OAAO,GAAG;AAAA,EACnH;AAEA,QAAM,OAAO,IAAI,MAAM,YAAY,CAAC,EAAE,KAAK;AAC3C,QAAM,OAAO,KAAK,YAAY,GAAG;AACjC,QAAM,QAAQ,SAAS,KAAK,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,KAAK;AAC7D,QAAM,OAAO,SAAS,KAAK,SAAY,KAAK,MAAM,OAAO,CAAC,EAAE,KAAK;AACjE,MAAI,SAAS,IAAI;AACf,WAAO,EAAE,OAAO,YAAY,OAAO,IAAI,wBAAwB,OAAO,IAAI,iBAAiB;AAAA,EAC7F;AACA,SAAO,OAAO,EAAE,QAAQ,OAAO,MAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,OAAO,MAAM,KAAK;AAClF;AAWO,SAAS,cAAc,KAAoC,SAA6C;AAC7G,QAAM,UAAU,eAAe,SAAS,YAAY,GAAG,EACpD,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,CAAC,UAAU,UAAU,EAAE;AACjC,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,SAA+B,CAAC;AACtC,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,SAAS,KAAK;AAC1B,QAAI,WAAW,IAAK,QAAO;AAC3B,WAAO,KAAK,GAAG;AAAA,EACjB;AACA,SAAO;AACT;;;ACrEA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;AAajB,eAAsB,mBAAmB,MAAc,MAA2C;AAChG,MAAI;AACF,UAAM,OAAO,MAAMD,UAASC,MAAK,QAAQ,MAAM,OAAO,SAAS,MAAM,gBAAgB,GAAG,MAAM;AAC9F,WAAO,oCAAoC,KAAK,IAAI,IAAI,CAAC;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACsBA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;AA6CV,SAAS,oBAAoB,QAAoC;AACtE,QAAM,QAA4B,CAAC;AACnC,QAAM,UAAU;AAChB,aAAW,SAAS,OAAO,SAAS,OAAO,GAAG;AAC5C,UAAM,KAAK,MAAM;AACjB,UAAM,QAAQ,OAAO,YAAY,KAAK,EAAE;AACxC,UAAM,SAAS,OAAO,QAAQ,KAAK,EAAE;AACrC,UAAM,KAAK;AAAA,MACT,MAAM,MAAM,CAAC;AAAA,MACb,WAAW,MAAM,CAAC;AAAA,MAClB,QAAQ,UAAU,OAAO,MAAM,QAAQ,IAAI,KAAK,OAAO,SAAS,IAAI,KAAK,MAAM,CAAC,EAAE,SAAS,SAAS,CAAC,CAAC;AAAA,IACxG,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,SAAS,UAAU,QAAwB;AACzC,SAAO,OAAO,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC1C;AASO,SAAS,iBACd,SACA,SACoB;AACpB,QAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,GAAG,OAAO,EAAE,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK;AACrF,SAAO,MAAM,QAAQ,CAAC,SAA6B;AACjD,UAAM,OAAO,QAAQ,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI;AAC1D,UAAM,QAAQ,QAAQ,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI;AAC3D,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC,EAAE,MAAM,QAAQ,uBAAuB,CAAC;AACxE,QAAI,KAAK,WAAW,EAAG,QAAO,CAAC,EAAE,MAAM,QAAQ,uBAAuB,CAAC;AACvE,QAAI,MAAM,MAAM,CAAC,UAAU,MAAM,SAAS,MAAM,MAAM,OAAO,CAAC,UAAU,MAAM,SAAS,GAAG;AACxF,aAAO,CAAC,EAAE,MAAM,QAAQ,kBAAkB,CAAC;AAAA,IAC7C;AACA,QAAI,MAAM,MAAM,CAAC,UAAU,MAAM,MAAM,MAAM,MAAM,OAAO,CAAC,UAAU,MAAM,MAAM,GAAG;AAClF,aAAO,CAAC,EAAE,MAAM,QAAQ,iBAAiB,CAAC;AAAA,IAC5C;AACA,WAAO,CAAC;AAAA,EACV,CAAC;AACH;AAOA,SAAS,MAAM,SAAsC,IAAiD;AACpG,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI;AACvD;AAYA,eAAsB,qBACpB,MACA,aACA,aAC6B;AAC7B,MAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,MAAI;AACF,UAAM,eAAe,WAAW,CAAC,GAAI,MAAM,kBAAkB,MAAM,WAAW,CAAE,GAAG,WAAW;AAC9F,UAAM,SAAS,IAAI,IAAI,YAAY,MAAM;AACzC,UAAM,CAAC,SAAS,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC3C;AAAA,QACE;AAAA,QACA,aAAa,OAAO,CAAC,SAAS,CAAC,OAAO,IAAI,IAAI,CAAC;AAAA,MACjD;AAAA,MACA,eAAe,MAAM,YAAY,MAAM;AAAA,IACzC,CAAC;AACD,WAAO,iBAAiB,SAAS,OAAO;AAAA,EAC1C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAGA,SAAS,WAAW,OAA0B,MAAwB;AACpE,QAAM,SAAS,aAAa,IAAI;AAChC,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,WAAW,MAAM,KAAK,CAAC,KAAK,MAAM,OAAO,MAAM,EAAE,WAAW,QAAQ,CAAC;AAC1G;AAEA,eAAe,eAAe,MAAc,OAAuD;AACjG,QAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,SAASC,UAASC,MAAK,KAAK,MAAM,IAAI,GAAG,MAAM,EAAE,MAAM,MAAM,EAAE,CAAC,CAAC;AAC9G,SAAO,QAAQ,QAAQ,mBAAmB;AAC5C;;;AC9JA,SAAS,UAAU,IAAkC;AACnD,SAAO,OAAO,OAAO,OAAO,IAAI,KAAK,EAAE,EAAE,YAAY;AACvD;AAOA,SAAS,OAAO,IAAkC;AAChD,SAAO,OAAO,OAAO,OAAO,IAAI,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACpE;AAGA,SAAS,WAAW,KAAoB;AACtC,SAAO,EAAE,UAAU,IAAI,UAAU,aAAa,IAAI,aAAa,MAAM,KAAK,MAAM,IAAI,OAAO,GAAM,IAAI,IAAO;AAC9G;AAGA,SAAS,cAAc,QAA0B;AAC/C,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,SAAS,OAAO;AAAA,IAChB,SAAS,OAAO,QAAQ;AAAA,IACxB,SAAS,OAAO,QAAQ;AAAA,EAC1B;AACF;AAEA,SAAS,QAAQ,KAAoB;AACnC,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,IAAI,uBAAuB,SAAY,CAAC,IAAI,EAAE,oBAAoB,UAAU,IAAI,kBAAkB,EAAE;AAAA,EAC1G;AACF;AAEO,SAAS,cAAc,KAA0B,EAAE,MAAM,IAAI,GAA4C;AAC9G,QAAM,eAAe;AAAA,IACnB,oBAAoB,OAAO,IAAI,kBAAkB;AAAA,IACjD,GAAI,IAAI,uBAAuB,OAAO,CAAC,IAAI,EAAE,oBAAoB,aAAa,IAAI,oBAAoB,GAAG,EAAE;AAAA,EAC7G;AACA,QAAM,UAAU;AAAA,IACd,WAAW,UAAU,IAAI,SAAS;AAAA,IAClC,UAAU,UAAU,IAAI,QAAQ;AAAA,IAChC,YAAY,UAAU,IAAI,UAAU;AAAA,EACtC;AAEA,MAAI,MAAM;AACR,WAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAG;AAAA,MACH,SAAS,QAAQ,IAAI,OAAO;AAAA,MAC5B,SAAS,QAAQ,IAAI,OAAO;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,cAAc,IAAI;AAAA,IAClB,aAAa,IAAI;AAAA,IACjB,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,IACb,GAAI,IAAI,WAAW,OAAO,CAAC,IAAI,EAAE,QAAQ,IAAI,OAAO;AAAA,IACpD,GAAI,IAAI,kBAAkB,OAAO,CAAC,IAAI,EAAE,eAAe,IAAI,cAAc;AAAA,IACzE,aAAa,IAAI;AAAA,IACjB,GAAG;AAAA,IACH,iBAAiB,IAAI;AAAA,IACrB,SAAS,WAAW,IAAI,OAAO;AAAA,IAC/B,SAAS,WAAW,IAAI,OAAO;AAAA,IAC/B,WAAW,IAAI,UAAU,IAAI,aAAa;AAAA,IAC1C,qBAAqB,IAAI;AAAA,IACzB,2BAA2B,IAAI;AAAA,IAC/B,MAAM,IAAI;AAAA,IACV,GAAI,IAAI,gBAAgB,OAAO,CAAC,IAAI,EAAE,aAAa,IAAI,YAAY;AAAA,IACnE,WAAW,IAAI;AAAA,IACf,GAAG;AAAA,IACH,GAAI,IAAI,WAAW,aACf;AAAA,MACE,UAAU,IAAI;AAAA,MACd,mBAAmB,IAAI;AAAA,MACvB,MAAM,IAAI;AAAA,MACV,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,IACjB,IACA,CAAC;AAAA,EACP;AACF;;;ACvGO,SAAS,mBACd,MAYA,SAC6C;AAC7C,QAAM,SAAS,KAAK,WAAW,SAAY,SAAY,OAAO,KAAK,MAAM;AACzE,MAAI,WAAW,UAAa,WAAW,cAAc,WAAW,YAAY;AAC1E,WAAO;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,WAAW,cAAc,KAAK,UAA2C,OAAO;AACtF,MAAI,YAAY,WAAW,SAAU,QAAO;AAE5C,QAAM,OAAO,KAAK,SAAS,SAAY,SAAY,OAAO,KAAK,IAAI;AACnE,MAAI,SAAS,UAAa,EAAE,OAAO,IAAI;AACrC,WAAO;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,UAAmC;AAAA,IACvC,cAAc,OAAO,KAAK,EAAE;AAAA,IAC5B,GAAG,MAAM,WAAW,KAAK,OAAO;AAAA,IAChC,GAAG,MAAM,UAAU,KAAK,MAAM;AAAA,IAC9B,GAAG,MAAM,QAAQ,KAAK,IAAI;AAAA,IAC1B,GAAG,MAAM,eAAe,KAAK,WAAW;AAAA,IACxC,GAAG,MAAM,YAAY,KAAK,QAAQ;AAAA,IAClC,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO;AAAA,IACzC,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IAC/B,GAAI,SAAS,SAAY,CAAC,IAAI,EAAE,2BAA2B,KAAK;AAAA,EAClE;AACA,MAAI,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AACrC,WAAO;AAAA,MACL,OACE;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,MACP,KACA,OACuB;AACvB,SAAO,UAAU,SAAY,CAAC,IAAK,EAAE,CAAC,GAAG,GAAG,OAAO,KAAK,EAAE;AAC5D;;;AVjDA,IAAM,WAAW;AAAA,EACf,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,mBAAmB;AAAA,EACvB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,WAAW;AAAA,EACf,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAWA,IAAM,cAAc;AAAA,EAClB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,SAAS,EAAE,MAAM,UAAU,aAAa,uBAAuB,UAAU,KAAK;AAEpF,IAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,aACE;AAAA,EACF,UAAU;AACZ;AAEA,IAAM,qBAAqB;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,UAAU;AACZ;AAOA,SAAS,YAAY,MAAyC;AAC5D,SAAO,OAAO,KAAK,YAAY,WAAW,CAAC,KAAK,OAAO,IAAI,CAAC;AAC9D;AAEA,SAAS,KAAK,SAAwB;AACpC,oBAAkB,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,oBAAoB,QAAQ,EAAE,CAAC;AAC7E,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,eAAe,OAAuB;AAC7C,MAAI,iBAAiB,UAAU;AAC7B,sBAAkB,EAAE,IAAI,OAAO,OAAO,EAAE,MAAM,MAAM,QAAQ,SAAS,SAAS,MAAM,QAAQ,EAAE,CAAC;AAC/F,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM;AACR;AAEA,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,IACJ,SAAS,EAAE,MAAM,UAAU,aAAa,gCAAgC,UAAU,KAAK;AAAA,IACvF,SAAS,EAAE,MAAM,UAAU,aAAa,yCAAyC,UAAU,KAAK;AAAA,IAChG,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AACF,CAAC;AAED,IAAM,cAAc,cAAc;AAAA,EAChC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,SAAS,EAAE,MAAM,UAAU,aAAa,gCAAgC,UAAU,KAAK;AAAA,IACvF,SAAS,EAAE,MAAM,UAAU,aAAa,yCAAyC,UAAU,KAAK;AAAA,IAChG,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,WAAW,MAAM,QAAoD,yBAAyB;AAAA,QAClG,aAAa,OAAO,KAAK,OAAO;AAAA,QAChC,aAAa,OAAO,KAAK,OAAO;AAAA,QAChC,GAAI,KAAK,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,QAC/C,GAAI,KAAK,cAAc,EAAE,aAAa,OAAO,KAAK,WAAW,EAAE,IAAI,CAAC;AAAA,QACpE,GAAI,KAAK,OAAO,EAAE,2BAA2B,OAAO,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,QACpE,GAAI,KAAK,WAAW,EAAE,cAAc,OAAO,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,MACjE,CAAC;AACD,wBAAkB,EAAE,IAAI,MAAM,MAAM,SAAS,MAAM,OAAO,eAAe,SAAS,IAAI,EAAE,CAAC;AAAA,IAC3F,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,IACJ,SAAS,EAAE,MAAM,UAAU,aAAa,gCAAgC,UAAU,KAAK;AAAA,IACvF,SAAS,EAAE,MAAM,UAAU,aAAa,yCAAyC,UAAU,KAAK;AAAA,IAChG,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AACF,CAAC;AAED,IAAM,eAAe,cAAc;AAAA,EACjC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,SAAS,EAAE,MAAM,UAAU,aAAa,gCAAgC,UAAU,KAAK;AAAA,IACvF,SAAS,EAAE,MAAM,UAAU,aAAa,yCAAyC,UAAU,KAAK;AAAA,IAChG,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,IACN,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,EACZ;AAAA,EACA,KAAK,OAAO,EAAE,MAAM,QAAQ,MAAM;AAChC,UAAM,SAAS,KAAK,WAAW,SAAY,aAAa,OAAO,KAAK,MAAM;AAC1E,QAAI,WAAW,cAAc,WAAW,YAAY;AAClD,WAAK,uGAAkG;AAAA,IACzG;AACA,UAAM,WAAW,cAAc,KAAK,UAA2C,OAAO;AACtF,QAAI,YAAY,WAAW,SAAU,MAAK,SAAS,KAAK;AAExD,UAAM,EAAE,aAAa,UAAU,OAAO,mBAAmB,kBAAkB,IAAI,MAAM;AAAA,MACnF,OAAO,KAAK,OAAO;AAAA,MACnB,OAAO,KAAK,OAAO;AAAA,IACrB;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,QAAqD,0BAA0B;AAAA,QACpG,aAAa,OAAO,KAAK,OAAO;AAAA,QAChC,aAAa,OAAO,KAAK,OAAO;AAAA,QAChC,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;AAAA,QACjD,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;AAAA,QACjD,YAAY;AAAA,UACV,SAAS,OAAO,KAAK,OAAO;AAAA,UAC5B,QAAQ,OAAO,KAAK,MAAM;AAAA,UAC1B;AAAA,UACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,QACjC;AAAA,QACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,QACrC,GAAI,KAAK,OAAO,EAAE,MAAM,OAAO,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,QAC/C,GAAI,KAAK,cAAc,EAAE,aAAa,OAAO,KAAK,WAAW,EAAE,IAAI,CAAC;AAAA,QACpE,GAAI,KAAK,OAAO,EAAE,2BAA2B,OAAO,KAAK,IAAI,EAAE,IAAI,CAAC;AAAA,QACpE,GAAI,KAAK,WAAW,EAAE,cAAc,OAAO,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,MACjE,CAAC;AACD,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,SAAS;AAAA,QACf,OAAO;AAAA,UACL,GAAG,YAAY,SAAS,IAAI;AAAA,UAC5B,GAAG,gBAAgB;AAAA,YACjB,SAAS,OAAO,KAAK,OAAO;AAAA,YAC5B,SAAS,OAAO,KAAK,OAAO;AAAA,YAC5B,MAAM,OAAO,SAAS,KAAK,IAAI;AAAA,YAC/B,aAAa,KAAK,gBAAgB,SAAY,SAAY,OAAO,KAAK,WAAW;AAAA,YACjF;AAAA,YACA;AAAA,YACA;AAAA,UACF,CAAC;AAAA,UACD;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,IACJ,IAAI,EAAE,MAAM,UAAU,aAAa,uBAAuB,UAAU,MAAM;AAAA,IAC1E,SAAS;AAAA,IACT,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aACE;AAAA,MACF,UAAU;AAAA,IACZ;AAAA,EACF;AACF,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,IAAI,EAAE,MAAM,UAAU,aAAa,uBAAuB,UAAU,MAAM;AAAA,IAC1E,SAAS;AAAA,IACT,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aACE;AAAA,MACF,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,WAAW,MAAM,QAAsD,2BAA2B;AAAA,QACtG,GAAI,KAAK,KAAK,EAAE,cAAc,OAAO,KAAK,EAAE,EAAE,IAAI,CAAC;AAAA,QACnD,GAAI,KAAK,UAAU,EAAE,aAAa,OAAO,KAAK,OAAO,EAAE,IAAI,CAAC;AAAA,QAC5D,GAAI,KAAK,SAAS,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAAA,MAC7C,CAAC;AACD,YAAM,MAAM,KAAK,IAAI;AACrB,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM;AAAA,UACJ,aAAa,SAAS,KAAK,YAAY,IAAI,CAAC,QAAQ,cAAc,KAAK,EAAE,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC,CAAC;AAAA,QAC3G;AAAA,QACA,OAAO,iBAAiB,SAAS,KAAK,aAAa,GAAG;AAAA,MACxD,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aACE;AAAA,EACF,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,OAAO,EAAE,MAAM,UAAU,aAAa,iDAAiD,UAAU,MAAM;AAAA,EACzG;AACF,CAAC;AAED,IAAM,iBAAiB,cAAc;AAAA,EACnC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,OAAO,EAAE,MAAM,UAAU,aAAa,mCAAmC,UAAU,MAAM;AAAA,EAC3F;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,UAAM,QAAQ,KAAK,UAAU,SAAY,SAAY,OAAO,KAAK,KAAK;AACtE,QAAI,UAAU,UAAa,EAAE,OAAO,UAAU,KAAK,KAAK,QAAQ,GAAI,MAAK,wCAAwC;AACjH,QAAI;AACF,YAAM,WAAW,MAAM,QAAuD,4BAA4B;AAAA,QACxG,GAAI,KAAK,UAAU,EAAE,aAAa,OAAO,KAAK,OAAO,EAAE,IAAI,CAAC;AAAA,QAC5D,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,MAAM;AAAA,MACzC,CAAC;AACD,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,SAAS;AAAA,QACf,OAAO,kBAAkB,SAAS,KAAK,OAAO,KAAK,YAAY,SAAY,SAAY,OAAO,KAAK,OAAO,CAAC;AAAA,MAC7G,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aACE;AAAA,MACF,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF,CAAC;AAED,IAAM,cAAc,CAAC,iBAAiB,gBAAgB,iBAAiB,aAAa;AAGpF,SAAS,YACP,MACA,OACU;AACV,QAAM,EAAE,OAAO,IAAI;AACnB,QAAM,OAAO,EAAE,UAAU,MAAM,SAAS;AACxC,SAAO;AAAA,IACL,+BAA+B,KAAK,aAAa,YAAY,gBAAgB,UAAU;AAAA,IACvF,GAAI,KAAK,iBACL;AAAA,MACE,gBAAgB,KAAK,aAAa,YAAY,gBAAgB,UAAU,qDAAqD,KAAK,OAAO;AAAA,IAC3I,IACA,CAAC;AAAA,IACL,GAAI,KAAK,YAAY,iBACjB;AAAA,MACE;AAAA,IACF,IACA,CAAC;AAAA,IACL,GAAI,KAAK,aAAa,YAClB;AAAA,MACE;AAAA,IACF,IACA,CAAC;AAAA,IACL,GAAI,KAAK,YAAY,KAAK,YAAY,iBAClC,CAAC,IACD;AAAA,MACE;AAAA,IACF;AAAA,IACJ,GAAI,SACA,CAAC,IACD;AAAA,MACE;AAAA,IACF;AAAA,EACN;AACF;AAEA,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,UAAM,OAAO,OAAO,KAAK,IAAI;AAC7B,QAAI,SAAS,cAAc,SAAS,WAAW;AAC7C,WAAK,kGAA6F;AAAA,IACpG;AACA,UAAM,SAAS,KAAK,WAAW,SAAY,SAAY,OAAO,KAAK,MAAM;AACzE,QAAI,WAAW,UAAa,CAAC,YAAY,KAAK,CAAC,UAAU,UAAU,MAAM,GAAG;AAC1E,WAAK,0BAA0B,YAAY,KAAK,IAAI,CAAC,uCAAkC;AAAA,IACzF;AACA,QAAI;AACF,YAAM,WAAW,MAAM;AAAA,QACrB;AAAA,QACA;AAAA,UACE,cAAc,OAAO,KAAK,EAAE;AAAA,UAC5B;AAAA,UACA,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,UAC3B,GAAI,KAAK,WAAW,EAAE,UAAU,OAAO,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,QAC7D;AAAA,MACF;AACA,UAAI,cAAc,SAAS,MAAM;AAC/B,0BAAkB;AAAA,UAChB,IAAI;AAAA,UACJ,MAAM,SAAS;AAAA,UACf,OAAO,CAAC,iGAAiG;AAAA,QAC3G,CAAC;AACD;AAAA,MACF;AACA,YAAM,OAAO,SAAS;AACtB,wBAAkB,EAAE,IAAI,MAAM,MAAM,OAAO,YAAY,MAAM,EAAE,QAAQ,UAAU,KAAK,SAAS,CAAC,EAAE,CAAC;AAAA,IACrG,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,EAAE,IAAI,OAAO;AACrB,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM,EAAE,IAAI,OAAO;AAAA,EACnB,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,WAAW,MAAM;AAAA,QACrB;AAAA,QACA,EAAE,cAAc,OAAO,KAAK,EAAE,GAAG,MAAM,WAAW;AAAA,MACpD;AACA,UAAI,cAAc,SAAS,MAAM;AAC/B,0BAAkB;AAAA,UAChB,IAAI;AAAA,UACJ,MAAM,SAAS;AAAA,UACf,OAAO,CAAC,iGAAiG;AAAA,QAC3G,CAAC;AACD;AAAA,MACF;AACA,YAAM,OAAO,SAAS;AACtB,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ;AAAA,QACA,OAAO;AAAA,UACL;AAAA,UACA,GAAI,KAAK,YAAY,iBACjB;AAAA,YACE;AAAA,UACF,IACA,KAAK,iBACH;AAAA,YACE,yCAAyC,KAAK,OAAO;AAAA,UACvD,IACA,CAAC;AAAA,UACP;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aACE;AAAA,EACF,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS,EAAE,GAAG,aAAa,UAAU,MAAM;AAAA,IAC3C,QAAQ,EAAE,GAAG,YAAY,UAAU,MAAM;AAAA,IACzC,UAAU;AAAA,IACV,QAAQ,EAAE,GAAG,YAAY,aAAa,+CAA+C;AAAA,IACrF,MAAM,EAAE,GAAG,UAAU,aAAa,4DAA4D;AAAA,IAC9F,aAAa;AAAA,MACX,GAAG;AAAA,MACH,aAAa;AAAA,IACf;AAAA,IACA,MAAM,EAAE,GAAG,UAAU,aAAa,wDAAwD;AAAA,IAC1F,UAAU;AAAA,EACZ;AACF,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,IAAI;AAAA,IACJ,SAAS,EAAE,GAAG,aAAa,UAAU,MAAM;AAAA,IAC3C,QAAQ,EAAE,GAAG,YAAY,UAAU,MAAM;AAAA,IACzC,UAAU;AAAA,IACV,QAAQ,EAAE,GAAG,YAAY,aAAa,+CAA+C;AAAA,IACrF,MAAM,EAAE,GAAG,UAAU,aAAa,4DAA4D;AAAA,IAC9F,aAAa;AAAA,MACX,GAAG;AAAA,MACH,aAAa;AAAA,IACf;AAAA,IACA,MAAM,EAAE,GAAG,UAAU,aAAa,wDAAwD;AAAA,IAC1F,UAAU;AAAA,EACZ;AAAA,EACA,KAAK,OAAO,EAAE,MAAM,QAAQ,MAAM;AAChC,UAAM,UAAU,mBAAmB,MAAM,OAAO;AAChD,QAAI,WAAW,QAAS,MAAK,QAAQ,KAAK;AAC1C,QAAI;AACF,YAAM,WAAW,MAAM,QAAsD,2BAA2B,OAAO;AAC/G,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,SAAS;AAAA,QACf,OAAO;AAAA,UACL,SAAS,KAAK,SACV,0EACA,QAAQ,aAAa,SACnB,4GACA;AAAA,UACN,iBAAiB,SAAS,KAAK,mBAAmB;AAAA,QACpD;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,EAAE,IAAI,OAAO;AACrB,CAAC;AAED,IAAM,eAAe,cAAc;AAAA,EACjC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM,EAAE,IAAI,OAAO;AAAA,EACnB,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,QAAQ,0BAA0B,EAAE,cAAc,OAAO,KAAK,EAAE,EAAE,CAAC;AACzE,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,EAAE,QAAQ,KAAK;AAAA,QACrB,OAAO;AAAA,UACL,+EAA+E,OAAO,KAAK,EAAE,CAAC;AAAA,QAChG;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,EAAE,IAAI,OAAO;AACrB,CAAC;AAED,IAAM,gBAAgB,cAAc;AAAA,EAClC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM,EAAE,IAAI,OAAO;AAAA,EACnB,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,QAAQ,2BAA2B,EAAE,cAAc,OAAO,KAAK,EAAE,EAAE,CAAC;AAC1E,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,EAAE,SAAS,KAAK;AAAA,QACtB,OAAO,CAAC,qDAAqD;AAAA,MAC/D,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAED,eAAe;AAAA,EACb,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM;AAAA,IACJ,IAAI,EAAE,MAAM,UAAU,aAAa,8DAA8D,UAAU,MAAM;AAAA,EACnH;AACF,CAAC;AAED,IAAM,cAAc,cAAc;AAAA,EAChC,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,MAAM;AAAA,IACJ,IAAI,EAAE,MAAM,UAAU,aAAa,8DAA8D,UAAU,MAAM;AAAA,EACnH;AAAA,EACA,KAAK,OAAO,EAAE,KAAK,MAAM;AACvB,QAAI;AACF,YAAM,QAAQ,MAAM,QAAoD,yBAAyB;AAAA,QAC/F,GAAI,KAAK,KAAK,EAAE,cAAc,OAAO,KAAK,EAAE,EAAE,IAAI,CAAC;AAAA,MACrD,CAAC;AACD,YAAM,OAAO,MAAM,KAAK;AACxB,UAAI,KAAK,WAAW,GAAG;AACrB,0BAAkB;AAAA,UAChB,IAAI;AAAA,UACJ,MAAM,EAAE,QAAQ,CAAC,EAAE;AAAA,UACnB,OAAO,CAAC,wFAAmF;AAAA,QAC7F,CAAC;AACD;AAAA,MACF;AAEA,YAAM,SAAuB,CAAC;AAC9B,YAAM,WAAqB,CAAC;AAC5B,iBAAW,UAAU,MAAM;AACzB,cAAM,SAAS,MAAM,eAAe,QAAQ,IAAI,GAAG,MAAM;AAIzD,YAAI,WAAW,OAAQ,UAAS,KAAK,GAAG,OAAO,WAAW,KAAK,OAAO,KAAK,EAAE;AAAA,YACxE,QAAO,KAAK,MAAM;AAAA,MACzB;AAEA,UAAI,OAAO,SAAS,GAAG;AAIrB,cAAM,QAAQ,yBAAyB;AAAA,UACrC,SAAS;AAAA,UACT,eAAe,OAAO,IAAI,CAAC,WAAW,OAAO,YAAY;AAAA,UACzD,GAAI,KAAK,KAAK,EAAE,cAAc,OAAO,KAAK,EAAE,EAAE,IAAI,CAAC;AAAA,QACrD,CAAC;AAAA,MACH;AAEA,wBAAkB;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM,EAAE,QAAQ,SAAS;AAAA,QACzB,OAAO;AAAA,UACL,OAAO,SAAS,IACZ,uNACA;AAAA,UACJ,GAAI,SAAS,SAAS,IAAI,CAAC,mBAAmB,SAAS,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC;AAAA,UACxE,GAAI,OAAO,SAAS,IAChB;AAAA,YACE,oCAAoC,OAAO,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;AAAA,UAChF,IACA,CAAC;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF;AACF,CAAC;AAEM,IAAM,oBAAoB,cAAc;AAAA,EAC7C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aACE;AAAA,EACJ;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AACF,CAAC;AASD,eAAe,iBAAiB,SAAiB,SAAiB;AAChE,QAAM,OAAO,QAAQ,IAAI;AAGzB,QAAM,cAAc,MAAM,gBAAgB,MAAM,SAAS,OAAO;AAChE,QAAM,CAAC,cAAc,cAAc,mBAAmB,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC3F,UAAU,MAAM,OAAO;AAAA,IACvB,UAAU,MAAM,OAAO;AAAA;AAAA;AAAA,IAGvB,mBAAmB,MAAM,OAAO;AAAA,IAChC,mBAAmB,MAAM,OAAO;AAAA,EAClC,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA,UAAU,YAAY,cAAc,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA,IAKhD,OAAO,MAAM,qBAAqB,MAAM,SAAS,WAAW;AAAA,IAC5D;AAAA,IACA;AAAA,EACF;AACF;","names":["knownFlows","readdir","readFile","path","path","readdir","readFile","count","describeWait","readFile","path","readFile","path","readFile","path"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/funnel/index.ts","../src/commands/funnel/formMoments.ts","../../api/src/funnel/vocabulary.ts","../../api/src/funnel/stepTemplates.ts","../../api/src/funnel/wire.ts","../src/commands/funnel/funnelArgs.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { FunnelRequest, FunnelResponse } from \"@baker/api/funnel\";\nimport { defineCommand } from \"citty\";\nimport { ApiError, apiPost } from \"../../client.ts\";\nimport { registerSchema } from \"../../schemas.ts\";\nimport { writeJsonEnvelope } from \"../ads/output.ts\";\nimport { flowDataPath, listFlowSlugs } from \"../flows/shared.ts\";\nimport { formsNamedHere, unplacedFormHints, type WorkspaceForm, withFormMoments } from \"./formMoments.ts\";\nimport { FunnelArgsError, funnelWrite, parseFunnelArgs } from \"./funnelArgs.ts\";\n\n/**\n * `baker funnel` — the company's own funnel: the steps it has, in its own\n * words, wired the way its business actually runs.\n *\n * One command rather than a family, because there is one thing to say: this is\n * the funnel. A read with no flags, a write that restates the whole graph. It\n * is the only staged write in the CLI that does not take operations, and the\n * reason is in `@baker/api/funnel/wire.ts`: a funnel is a graph of a handful of\n * steps, so restating it costs nothing and the person reviewing it sees the\n * funnel they will get rather than a list of edits.\n */\nconst args = {\n steps: {\n type: \"string\",\n description:\n 'The WHOLE funnel, as a JSON array. One object per step: {\"key\":\"meeting_held\",\"displayName\":\"Demo held\",\"stepClass\":\"engaged\",\"after\":[\"lead\"],\"triggers\":[{\"id\":\"t1\",\"kind\":\"event\",\"eventKey\":\"page:demo_held\"}]}. It REPLACES the funnel rather than adding to it, so read first and restate everything you want to keep. `key` is permanent and reaches reporting and URLs (lowercase, digits, underscores); `displayName` is the client\\'s own words and can be anything. `stepClass` is the stage, one of visited, lead, engaged, customer, repeat, lost — six plain words every business has, used to compare one company\\'s funnel with another\\'s. `after` is a LIST, so two ways into one step is expressible and normal. `triggers` is what MOVES somebody onto the step, any one of them being enough, and there are three kinds: `{\"id\":\"t1\",\"kind\":\"event\",\"eventKey\":\"submit:contact\"}` for anything the company\\'s Forms, pages or systems produced, used by its own key with nothing named first; `{\"id\":\"t2\",\"kind\":\"landing\",\"landingId\":\"<from --moments>\"}` for one landing page being viewed; `{\"id\":\"t3\",\"kind\":\"any_landing\"}` for any landing being viewed. Take every key and id from `--moments`. A trigger carries no amount: an event is worth what it says, and a step is worth the sum. Where a step SENDS its people is not written here: a send belongs to the system\\'s event (`baker analytics sends`), and the step reads it back as `sends[]`',\n required: false,\n },\n step: {\n type: \"string\",\n description:\n 'ONE step as a JSON object, in the same shape as an entry of --steps. It is added to the funnel as it now stands, or replaces the step with the same `key`, and every other step is left exactly as it is — the door for adding or changing a stage without restating the rest. Example: --step \\'{\"key\":\"demo\",\"displayName\":\"Demo held\",\"stepClass\":\"engaged\",\"after\":[\"lead\"],\"triggers\":[{\"id\":\"t1\",\"kind\":\"event\",\"eventKey\":\"page:demo_held\"}]}\\'',\n required: false,\n },\n \"add-trigger\": {\n type: \"string\",\n description:\n 'The key of ONE step to add a way in to, with `--event` naming what gets somebody there: `--add-trigger lead --event \"submit:contact\"`. Start here for \"this Form should count as a lead\" — it adds that one way in and leaves the step\\'s name, stage, `after[]` and every other trigger exactly as they are. Prefer it to `--step`, which takes a WHOLE step and therefore silently removes any trigger the restatement forgets. Adding a key the step already listens for changes nothing. Take the key from `--moments`, never from memory',\n required: false,\n },\n \"remove-trigger\": {\n type: \"string\",\n description:\n 'The key of ONE step to take a way in OFF, with `--event` naming which: `--remove-trigger lead --event \"submit:contact\"`. By event key rather than by trigger id, because the id is not something you ever saw. Everything else about the step is untouched',\n required: false,\n },\n event: {\n type: \"string\",\n description:\n \"The event key `--add-trigger` or `--remove-trigger` acts on — an `events[].eventKey` from `--moments`, never invented. Every Form's endings are listed there by the step each happens on, at zero before anybody has filled the Form in; which one means the step is your decision\",\n required: false,\n },\n \"remove-step\": {\n type: \"string\",\n description:\n \"The key of one step to take off the funnel. Whatever came after it moves up to whatever came before it, so the chain stays joined. The response lists it under `removed`\",\n required: false,\n },\n preset: {\n type: \"string\",\n description:\n \"Draw one of the ready-made graphs instead of stating steps: b2b_sales, b2b_inbound, saas_self_serve, saas_sales_assisted, ecommerce_purchase. It draws over the whole funnel — every company already has one, so prefer --step to change a step. Run --templates first to see what each one contains\",\n required: false,\n },\n moments: {\n type: \"boolean\",\n description:\n \"Also return everything a trigger can name, so a trigger is PICKED rather than invented: `events` (every event this company's Forms, pages and systems produced in the window, with volume, the Form it belongs to, `origin` — `site` for a page or Form, `systems` for the client's own server, `both` — and `countedAs` when the company also counts it as a page conversion, which a trigger does not need; PLUS **every workspace Form's endings** — each step's completion and its `submit:` — with the `step` each happens on, at zero and `seen: false` until somebody fires one, so a Form built minutes ago is pickable with nothing set up first, and `countedAs` when the company already counts it. Nothing is marked as the outcome: which ending means a step is YOUR decision — read the step names, and ask the person when they do not make it obvious (a Form that branches into “Not a fit” and “Quote confirmed”). If the Form is already counted, use that same key, so the step and the conversion agree. `baker analytics conversions --flow <slug>` also lists a widget's housekeeping), `sources` (the client's own systems that post events to Baker, by the `page:<name>` key each raises, pickable before its first send arrives) and `landings` (its pages, by the id a `landing` trigger takes). Use this before writing any `triggers`: a key typed from memory saves, publishes and counts nobody forever. A Form's events need NOTHING set up first — no Source, no conversion: a Baker page raises them by itself\",\n required: false,\n },\n days: {\n type: \"string\",\n description:\n \"How many days the counts beside each step cover (default 30, up to 400). Every step comes back with `people` (reached it), `atLeast` (reached it or anything after it), `before` (got as far as the step before) and `share` (atLeast / before) — counted in PEOPLE across visits and devices, not in visits\",\n required: false,\n },\n templates: {\n type: \"boolean\",\n description:\n \"Also return the libraries: the suggested steps with their stages, the presets, and `stages[]` (the six stages with the test to apply). Where a step can be sent is not here — a send belongs to the system's event, and `baker analytics sends` lists the platforms and the tags that can send\",\n required: false,\n },\n} as const;\n\nregisterSchema({\n command: \"funnel\",\n description: \"Read the company's funnel, or draw one\",\n args,\n});\n\nexport const funnelCommand = defineCommand({\n meta: {\n name: \"funnel\",\n description:\n 'Start here: run with no flags to see the funnel this company has, with the last 30 days of PEOPLE counted beside every step — `people` reached it, `atLeast` got at least that far, `before` got as far as the step before, `share` is the ratio, and `worstStep` names where the most people are lost. Every company has one from the day it exists: Visited (any landing) → Became a lead → Became a customer, the last two with no way in until somebody gives them one. Then `--moments` for everything a trigger can name (every event the company\\'s Forms, pages and systems produced, its sources, its landings, plus each Form\\'s own outcome at zero). **To make an event count on a step — the usual job — use `--add-trigger <step> --event <key>`:** it adds that one way in and touches nothing else about the step. `--step \\'<json>\\'` is for adding or renaming a STEP; it replaces the step with that key, so it means restating its `after` and every trigger it keeps, and one left out is gone. A step is reached by three kinds of thing and nothing else: an event by its own key (`{\"id\":\"t1\",\"kind\":\"event\",\"eventKey\":\"<from moments.events or moments.sources>\"}`), one landing being viewed (`{\"id\":\"t2\",\"kind\":\"landing\",\"landingId\":\"<from moments.landings>\"}`) or any landing (`{\"id\":\"t3\",\"kind\":\"any_landing\"}`). Nothing has to be named first. Pick every trigger from `--moments`, never from memory. A step with no `triggers` advances NOBODY and reports zero forever without warning — that is what the hints keep pointing at. There is no default door: a Form is only counted where a trigger names it. Keep it true: a chat that adds a Form, a Source or an event that moves people forward places it on its step in the same chat. Each step comes back with `sends[]`, read-only: where the people who reach it are sent and what happened, read off the system events behind its ways in. A send is set on the event with `baker analytics sends`, never on a step, and once published it spends ad budget — a failed one is named in the hints. Everything here is STAGED: it becomes the company\\'s funnel in one piece when the chat is published — the client sees it on the canvas in this chat and can edit it there too — and the previous version stays on file so what was already measured under it is not lost.',\n },\n args,\n run: async ({ args: raw }) => {\n try {\n const parsed = parseFunnelArgs(raw as Record<string, unknown>);\n // Read once: every Form in this workspace, including the one built in\n // this Session that the backend has never seen.\n const forms = workspaceForms();\n const namedByCaller = formsNamedHere(forms);\n const request: FunnelRequest = {\n ...funnelWrite(parsed),\n ...(namedByCaller.length === 0 ? {} : { namedByCaller }),\n };\n const response = await apiPost<FunnelResponse>(\"/api/funnel\", request);\n // What the company already counts, so a Form's ending can say so: the\n // agent choosing a way in should put the same key on the step.\n const counted = new Map((response.data.counted ?? []).map((row) => [row.eventKey, row.name] as const));\n const data =\n response.data.moments === undefined\n ? response.data\n : { ...response.data, moments: withFormMoments(response.data.moments, forms, counted) };\n const hints = [...(response.hints ?? []), ...unplacedFormHints(response.data.steps ?? [], forms, counted)];\n writeJsonEnvelope({\n ok: true,\n data,\n ...(hints.length > 0 ? { hints } : {}),\n });\n } catch (err) {\n handleError(err);\n }\n },\n});\n\n/**\n * A malformed `--steps` is the caller's own JSON, so it comes back as a\n * validation error carrying the parser's own sentence and its `fix` — the\n * request was never made, and \"that did not parse\" is something the agent can\n * act on in the same turn.\n */\nfunction handleError(err: unknown): never {\n if (err instanceof FunnelArgsError) {\n writeJsonEnvelope({\n ok: false,\n error: { code: \"VALIDATION_ERROR\", message: err.message, ...(err.fix ? { fix: err.fix } : {}) },\n });\n process.exit(1);\n }\n if (err instanceof ApiError) {\n writeJsonEnvelope({ ok: false, error: { code: err.code, message: err.message } });\n process.exit(1);\n }\n writeJsonEnvelope({\n ok: false,\n error: { code: \"NETWORK_ERROR\", message: err instanceof Error ? err.message : \"Unexpected error\" },\n });\n process.exit(1);\n}\n\n/**\n * Every Form in this workspace with its tree, skipping whatever cannot be read.\n *\n * Skipping rather than failing: `baker funnel` is one of the commands reached\n * for while a Form is half-written, and a flow-builder loop that has created\n * the folder but not yet the file would otherwise take the whole funnel down\n * with it. A Form left out costs one unfired ending in a list; a throw costs\n * the command.\n */\nfunction workspaceForms(): WorkspaceForm[] {\n const forms: WorkspaceForm[] = [];\n for (const slug of listFlowSlugs()) {\n try {\n forms.push({ slug, tree: JSON.parse(readFileSync(flowDataPath(slug), \"utf-8\")) });\n } catch {\n // Unreadable or not yet written.\n }\n }\n return forms;\n}\n","import { type FlowEventKey, flowEventKeys } from \"@baker/api/analytics/flowEventKeys\";\nimport type { FunnelResponse } from \"@baker/api/funnel\";\n\n/**\n * A Form's endings, added to `--moments` before any of them has fired.\n *\n * `moments.events` is built from the event store, so it knows an event only\n * once somebody has produced one. A Form published this morning offered\n * nothing, and the picker's remaining affordances — type the key by hand, or\n * add a Source — are both wrong: a Source is a door for the company's own\n * systems, and a Baker page already raises its events natively. That is how a\n * funnel step ends up naming a key nothing produces, which never errors and\n * counts zero forever.\n *\n * Facts only. Which ending is the point of a Form is the agent's decision:\n * it is a reading of what the steps mean — \"Quote confirmed\" against \"Not a\n * fit\" — and a rule over the tree's shape picked the step both branches pass\n * through, which would have counted every rejected visitor as a lead. So\n * nothing here is marked as the outcome; each ending says the step it happens\n * on and whether the company already counts it, and the agent chooses.\n *\n * Pure, and the reading of the trees is the caller's: a half-written\n * `_data.json` under a flow-builder loop must not take out `baker funnel`.\n */\n\ntype Moments = NonNullable<FunnelResponse[\"data\"][\"moments\"]>;\ntype MomentEvent = Moments[\"events\"][number];\n\nexport interface WorkspaceForm {\n slug: string;\n tree: unknown;\n}\n\n/** Event key → the name the company counts it under, this chat's staged counts included. */\nexport type CountedNames = ReadonlyMap<string, string>;\n\n/**\n * Each step's completion and the Form's own submit — the endings a step could\n * mean. A widget's housekeeping (\"the booking page was viewed\") is left out:\n * it is never what a person means by reaching a step, it roughly doubled the\n * list, and `baker analytics conversions --flow <slug>` still has it.\n */\nfunction offeredEndings(form: WorkspaceForm): FlowEventKey[] {\n return flowEventKeys(form.tree, form.slug).filter((ending) => ending.isCompletion || ending.nodeId === null);\n}\n\n/**\n * The store's events plus every workspace Form's endings, each with its step.\n *\n * An ending that has fired stays the store's own row, never replaced: it\n * carries a real volume, and a zero in its place would say the opposite of\n * what is true. It gains the step it happens on, which only the Form knows. An\n * ending nothing has fired yet is appended at zero, `seen: false`, with the\n * name the company counts it under when it counts it.\n */\nexport function formMoments(\n events: readonly MomentEvent[],\n forms: readonly WorkspaceForm[],\n counted: CountedNames,\n): MomentEvent[] {\n const endings = new Map<string, { slug: string; ending: FlowEventKey }>();\n for (const form of forms) {\n for (const ending of offeredEndings(form)) endings.set(ending.eventKey, { slug: form.slug, ending });\n }\n\n const known = events.map((event) => {\n const found = endings.get(event.eventKey);\n if (found === undefined) return event;\n endings.delete(event.eventKey);\n return { ...event, step: found.ending.nodeName };\n });\n const unfired = [...endings.values()].map(({ slug, ending }): MomentEvent => {\n const countedAs = counted.get(ending.eventKey);\n return {\n eventKey: ending.eventKey,\n origin: \"site\",\n flowSlug: slug,\n ...(ending.nodeType === null ? {} : { nodeType: ending.nodeType }),\n events: 0,\n sessions: 0,\n step: ending.nodeName,\n seen: false,\n ...(countedAs === undefined ? {} : { countedAs }),\n };\n });\n return [...known, ...unfired];\n}\n\n/** The moments as answered, with every workspace Form's endings alongside. */\nexport function withFormMoments(moments: Moments, forms: readonly WorkspaceForm[], counted: CountedNames): Moments {\n return { ...moments, events: formMoments(moments.events, forms, counted) };\n}\n\n/**\n * The Forms {@link unplacedFormHints} speaks for, by slug — every one that\n * raises an ending at all.\n *\n * Sent with the request so the backend leaves them out of its own \"on no step\"\n * hint, and every unplaced Form is named once. A Form whose tree holds no step\n * is left to the backend, so it is still named.\n */\nexport function formsNamedHere(forms: readonly WorkspaceForm[]): string[] {\n return forms.filter((form) => flowEventKeys(form.tree, form.slug).length > 0).map((form) => form.slug);\n}\n\n/**\n * The Forms in this workspace that no step counts, as ONE hint.\n *\n * The backend knows only PUBLISHED Forms, so a Form built in this Session was\n * named by nobody — on the one turn where the reminder is worth most. So this\n * owns the reminder for every Form in the workspace ({@link formsNamedHere})\n * and the backend's hint covers only the rest.\n *\n * It names Forms and never a key to place: the key is the agent's decision.\n * The one key it does print is a fact — the ending the company already counts\n * — because a step on a different ending of the same Form saves, publishes and\n * disagrees with the conversion for ever.\n *\n * One hint rather than one per Form, measured: a 27-Form client got 27\n * near-identical paragraphs, which is how an agent learns to skip the hints.\n * But every Form is named in it: the backend leaves all of them to this hint,\n * so a Form cut from the list here is named nowhere.\n */\nexport function unplacedFormHints(\n steps: readonly { key: string; triggers?: readonly { eventKey?: string }[] }[],\n forms: readonly WorkspaceForm[],\n counted: CountedNames,\n): string[] {\n const onAStep = new Set(\n steps.flatMap((step) => (step.triggers ?? []).flatMap((trigger) => (trigger.eventKey ? [trigger.eventKey] : []))),\n );\n const unplaced: string[] = [];\n for (const form of forms) {\n const endings = flowEventKeys(form.tree, form.slug);\n if (endings.length === 0 || endings.some((ending) => onAStep.has(ending.eventKey))) continue;\n const countedOn = endings.filter((ending) => counted.has(ending.eventKey));\n unplaced.push(\n countedOn.length === 0\n ? form.slug\n : `${form.slug} (counted as ${countedOn.map((ending) => `\"${counted.get(ending.eventKey)}\" on \\`${ending.eventKey}\\``).join(\", \")})`,\n );\n }\n if (unplaced.length === 0) return [];\n\n return [\n `${unplaced.length} Form(s) in this workspace reach no funnel step, so they move nobody along it however many people finish them: ${unplaced.join(\"; \")}. Which ending moves somebody to which step is your call: \\`baker funnel --moments\\` lists every Form's endings by the step each happens on, and when the step names do not make it obvious — a Form that branches into a rejection and a confirmation — ask the person which one means a lead or a customer. Place it with \\`baker funnel --add-trigger <step> --event <key>\\`. A Form already counted as a page conversion goes on the funnel by THAT SAME key, or the two numbers disagree for ever.`,\n ];\n}\n","/**\n * The one closed vocabulary in a funnel that is otherwise the company's own.\n *\n * A company invents its own steps, in its own words, wired its own way\n * (`graph.ts`). What it cannot invent is the *stage* a step stands for: the\n * six words below are what keeps one company's funnel comparable with\n * another's, and they can stay closed because they describe the shape every\n * business has — somebody saw the offer, told you who they are, did something\n * real with you, paid, paid again, or went away — rather than any one sector's\n * name for a rung on it.\n *\n * Six, down from eight. The old list split \"qualified\", \"engaged\" and\n * \"committed\" apart, and \"lost\" from \"disqualified\", which read as a sales\n * team's pipeline and meant nothing to a shop, a clinic or a course. Every\n * step that was one of those three is a real forward interaction and is\n * `engaged`; every step that ends the journey without money is `lost`.\n *\n * These lists are mirrored from `packages/backend/convex/lib/validators.ts`,\n * which is the source of truth for what can be stored. They are re-declared\n * here rather than imported because `@baker/api` must not depend on `convex`;\n * `vocabulary.parity.test.ts` reads that file and fails if the two ever drift.\n */\n\n/**\n * The stage a funnel step stands for, never the step itself.\n *\n * Ordered as a journey runs, and **the order is meaningful**: it is the\n * fallback for comparing one company's funnel against another's when their\n * graphs cannot be laid over each other, and the tie-break for \"how far did\n * this person get\" between two steps at the same depth.\n *\n * Where a step is sent is never derived from this list — that lives per step\n * — which is exactly why it does not have to be exhaustive.\n */\nexport const FUNNEL_STEP_CLASSES = [\n /** Saw the offer: a landing opened, an ad clicked through. */\n \"visited\",\n /** We know who they are: a form sent, an account created. */\n \"lead\",\n /** Something real happened: a call booked, a trial started, a quote asked for, a cart filled. */\n \"engaged\",\n /** Paid, or signed. */\n \"customer\",\n /** Paid again, renewed, or grew. */\n \"repeat\",\n /** Terminal. Did not go ahead, or was never a fit. */\n \"lost\",\n] as const;\nexport type FunnelStepClass = (typeof FUNNEL_STEP_CLASSES)[number];\n\n/**\n * Each stage in plain words, with an example from three shapes of business.\n *\n * The stage is the only thing in a funnel a company does not get to invent, so\n * it is the only thing anybody has to be *taught* — and it was taught in five\n * words on a `<select>`: \"Engaged — did something real with you\". A marketer\n * setting up a demo-led funnel, a self-serve trial and a shop cannot tell from\n * that whether a trial is engaged or customer, and getting it wrong is\n * invisible: nothing on their own reports changes, and the cross-company\n * comparison this list exists for quietly stops working.\n *\n * So each one says what it means in a sentence a child could follow, and then\n * shows the same stage in three businesses that look nothing like each other.\n * Three, because that is the range that makes the rule legible — a shop has no\n * demo and a self-serve product has no salesperson, and a stage that survives\n * all three is a stage somebody can place their own step into.\n *\n * Here rather than in the dashboard because the agent needs the same words:\n * `baker funnel --templates` returns it, the stage picker renders it, and\n * `docs/product-model.md` quotes it.\n */\nexport interface FunnelStageGuide {\n /** One word for the chip on a card. */\n title: string;\n /** What it means, said once, plainly. */\n meaning: string;\n /** The test somebody applies to their own step to decide if it belongs here. */\n test: string;\n /** The same stage in three businesses: an online shop, a self-serve product, a sales-led business. */\n examples: { shop: string; selfServe: string; salesLed: string };\n}\n\nexport const FUNNEL_STAGE_GUIDE: Readonly<Record<FunnelStepClass, FunnelStageGuide>> = {\n visited: {\n title: \"Visited\",\n meaning: \"They looked, and that is all. You still have no idea who they are.\",\n test: \"Could this have happened without them telling you anything about themselves?\",\n examples: {\n shop: \"Opened a product page\",\n selfServe: \"Read the pricing page\",\n salesLed: \"Read a case study\",\n },\n },\n lead: {\n title: \"Lead\",\n meaning: \"Now you know who they are. They handed over a way to reach them.\",\n test: \"Do you have their email, their phone, or an account in their name?\",\n examples: {\n shop: \"Joined the newsletter\",\n selfServe: \"Created a free account\",\n salesLed: \"Asked for a demo\",\n },\n },\n engaged: {\n title: \"Engaged\",\n meaning: \"They did something real — more than reading, not yet paying.\",\n test: \"Did they spend effort or time on you, without any money changing hands?\",\n examples: {\n shop: \"Put something in the basket\",\n selfServe: \"Started a trial, or invited a colleague\",\n salesLed: \"Turned up to the demo, or asked for a quote\",\n },\n },\n customer: {\n title: \"Customer\",\n meaning: \"Money. They paid, or they signed something that means they will.\",\n test: \"Is there a first payment or a signed contract behind this?\",\n examples: {\n shop: \"Placed their first order\",\n selfServe: \"Started paying for a plan\",\n salesLed: \"Signed the contract\",\n },\n },\n repeat: {\n title: \"Repeat\",\n meaning: \"Money again. They came back, renewed, or bought more.\",\n test: \"Had they already paid you once before this happened?\",\n examples: {\n shop: \"Ordered a second time\",\n selfServe: \"Renewed, or moved to a bigger plan\",\n salesLed: \"Renewed the contract, or added seats\",\n },\n },\n lost: {\n title: \"Lost\",\n meaning: \"It ended, and no money came. Nothing comes after this step.\",\n test: \"Is this journey over without a sale — for now or for good?\",\n examples: {\n shop: \"Left the basket and never came back\",\n selfServe: \"The trial ran out unused\",\n salesLed: \"They went with somebody else\",\n },\n },\n};\n\n/**\n * The stages that end a journey without money. One word now: the old split\n * between \"it was real and did not close\" and \"it never was real\" is a\n * judgement a sales team makes and nobody else does, and the two read as one\n * loss rate on every report anyway.\n */\nexport const TERMINAL_NEGATIVE_CLASSES = [\"lost\"] as const;\n\nexport function isTerminalNegative(stepClass: FunnelStepClass): boolean {\n return (TERMINAL_NEGATIVE_CLASSES as readonly string[]).includes(stepClass);\n}\n\n/** The stages that mean money arrived: where a funnel is expected to end. */\nexport const MONEY_CLASSES = [\"customer\", \"repeat\"] as const;\n\nexport function isMoneyClass(stepClass: FunnelStepClass): boolean {\n return (MONEY_CLASSES as readonly string[]).includes(stepClass);\n}\n\n/** Where a stage sits in the journey's order — the cross-company fallback. */\nexport function stepClassRank(stepClass: FunnelStepClass): number {\n return FUNNEL_STEP_CLASSES.indexOf(stepClass);\n}\n\n/**\n * A company step key: lowercase letters, digits and underscores, 1–64\n * characters, no leading or trailing underscore.\n *\n * Constrained on purpose even though the vocabulary is open. The key is the\n * stable identifier for the step — it ends up in reporting arguments, CLI\n * arguments and URLs — so it must not be able to be `\"Deal — won (EU)\"`. The\n * client's own words live in `displayName`, which has no constraint at all,\n * and renaming a step never touches its key.\n */\nconst STEP_KEY = /^[a-z0-9](?:[a-z0-9_]{0,62}[a-z0-9])?$/;\n\nexport function isFunnelStepKey(value: string): boolean {\n return STEP_KEY.test(value);\n}\n\nexport function isFunnelStepClass(value: string): value is FunnelStepClass {\n return (FUNNEL_STEP_CLASSES as readonly string[]).includes(value);\n}\n","/**\n * Step templates — a curated library of steps most funnels have, kept as\n * suggestions and never as law.\n *\n * The vocabulary is the company's: it invents its own steps, in its own words.\n * That does not mean everybody should have to invent \"Meeting booked\" from\n * nothing at kickoff, and it does not mean the research behind what each of\n * these moments means, whether it carries money, and what it becomes on Meta,\n * Google Ads and LinkedIn has to be re-derived per client.\n *\n * So the tables live here, as suggestion rather than schema:\n *\n * - **Nothing validates against this file.** A company step needs a class and a\n * graph, and neither has to come from a template. `graph.ts` never reads this\n * module.\n * - The funnel canvas offers templates when a step is created, and\n * `VERTICAL_PRESETS` offers four small ready-made graphs for the cold start.\n * - `platformDefaults` is the **prefill** for an offline conversion added to a\n * templated step — \"send this one to Meta\" on a won-like step should propose\n * `Purchase`, not an empty box (`defaultOfflineEvent`). Every value is\n * overridable; it is a default, not a mapping, and it is deliberately per\n * template rather than per class, so that where a conversion is sent never\n * becomes something the class decides.\n *\n * Pure data plus two derivations, tier-1 tested.\n */\n\nimport type { FunnelStepDefinition } from \"./graph\";\nimport type { FunnelStepClass } from \"./vocabulary\";\n\n/**\n * What a step usually becomes on Meta. The event name travels in the payload,\n * so anything goes — but a standard event optimizes better than a custom one,\n * which is why `standard` is recorded rather than left to be guessed from the\n * name.\n */\nexport interface MetaEventDefault {\n eventName: string;\n standard: boolean;\n}\n\n/**\n * The category Google Ads makes you pick when the conversion action is created.\n * `alternative` exists for the one genuine fork: a closed deal is \"Converted\n * lead\" in the lead funnel and \"Purchase\" once a revenue amount is reported,\n * and only the company knows which it is reporting.\n */\nexport interface GoogleAdsConversionDefault {\n category: string;\n alternative?: string;\n}\n\n/**\n * The `type` LinkedIn makes you pick when the conversion rule is created.\n *\n * `verified: false` carries a type taken from the documentation and never\n * checked against the live enum, so a creation attempt has to fall back to\n * `fallback` instead of failing.\n */\nexport interface LinkedinConversionDefault {\n type: string;\n verified: boolean;\n fallback?: string;\n}\n\nexport interface FunnelPlatformDefaults {\n meta?: MetaEventDefault;\n googleAds?: GoogleAdsConversionDefault;\n linkedin?: LinkedinConversionDefault;\n}\n\nexport interface FunnelStepTemplate {\n /** Suggested key. A company may rename it; nothing here is a stable identifier. */\n key: string;\n /** Suggested name, in a marketer's words. The company's own name wins. */\n displayName: string;\n /** One line, shown next to the suggestion so nobody has to guess what it covers. */\n meaning: string;\n stepClass: FunnelStepClass;\n /**\n * Other names for the same moment, lowercase and in English.\n *\n * Read only by `suggestStepForName`, which guesses a step for a name that has\n * arrived from somewhere else — a CRM stage, a spreadsheet column, whatever a\n * later source turns out to speak. Nothing validates against them and a match\n * is never applied on its own; a person still confirms it.\n */\n aliases: readonly string[];\n /**\n * Prefill for a destination created on a step made from this template.\n * Absent per platform where the old table said \"never sent\".\n */\n platformDefaults: FunnelPlatformDefaults;\n /** Why this class, where the answer is not obvious. Shown to nobody; read by whoever edits this file. */\n classNote?: string;\n}\n\n/**\n * Sixteen steps, each with the stage it belongs to.\n *\n * On the stage mapping, where it took a decision rather than a lookup:\n *\n * - **`lead` is \"we know who they are\", not \"they did something small\".**\n * `lead` and `signed_up` are the same fact reached two ways, so both are\n * `lead` even though one is a form and the other an account.\n * - **`engaged` is the wide one, and that is deliberate.** Qualified, cart,\n * checkout, trial, outreach, meeting booked, meeting held and an open\n * opportunity are all \"something real happened\" before any money. Splitting\n * them further is what the old eight-word list did, and it read as a sales\n * team's pipeline to everybody who was not one.\n * - **`customer` is the first money, `repeat` is the money after it.**\n * `subscribed` is therefore `customer`, not `repeat`: it is the moment a\n * subscription starts paying. `renewed` is what `repeat` is for.\n */\nexport const FUNNEL_STEP_TEMPLATES: readonly FunnelStepTemplate[] = [\n {\n key: \"saw_offer\",\n displayName: \"Saw the offer\",\n meaning: \"Someone opened one of your landing pages — where most journeys start\",\n stepClass: \"visited\",\n classNote: \"The only `visited` template: a landing being viewed is a step, never an outcome.\",\n aliases: [\"saw the offer\", \"visited\", \"landing viewed\", \"page viewed\", \"visit\", \"visitor\"],\n // Never sent anywhere: every platform already counts its own page views,\n // and uploading a view as a conversion would teach it to optimise for\n // nothing.\n platformDefaults: {},\n },\n {\n key: \"lead\",\n displayName: \"Lead\",\n meaning: \"Someone left their details — the starting point of most journeys\",\n stepClass: \"lead\",\n aliases: [\"lead\", \"new lead\", \"inbound lead\", \"enquiry\", \"inquiry\", \"form submission\"],\n platformDefaults: {\n meta: { eventName: \"Lead\", standard: true },\n googleAds: { category: \"Submit lead form\" },\n linkedin: { type: \"LEAD\", verified: true },\n },\n },\n {\n key: \"signed_up\",\n displayName: \"Signed up\",\n meaning: \"Someone created an account\",\n stepClass: \"lead\",\n classNote: \"Same fact as a lead reached a different way — we now know who they are.\",\n aliases: [\"signed up\", \"sign up\", \"registered\", \"registration\", \"account created\", \"new user\"],\n platformDefaults: {\n meta: { eventName: \"CompleteRegistration\", standard: true },\n googleAds: { category: \"Sign-up\" },\n linkedin: { type: \"SIGN_UP\", verified: true },\n },\n },\n {\n key: \"add_to_cart\",\n displayName: \"Added to cart\",\n meaning: \"Something went into the cart\",\n stepClass: \"engaged\",\n aliases: [\"added to cart\", \"add to cart\", \"cart\"],\n platformDefaults: {\n meta: { eventName: \"AddToCart\", standard: true },\n googleAds: { category: \"Add to cart\" },\n linkedin: { type: \"ADD_TO_CART\", verified: true },\n },\n },\n {\n key: \"checkout_started\",\n displayName: \"Started checkout\",\n meaning: \"The checkout began\",\n stepClass: \"engaged\",\n aliases: [\"started checkout\", \"checkout started\", \"checkout\", \"begin checkout\"],\n platformDefaults: {\n meta: { eventName: \"InitiateCheckout\", standard: true },\n googleAds: { category: \"Begin checkout\" },\n // Written from the documentation and never checked against the live\n // enum, which is what `verified: false` is for — carried as a caveat\n // rather than quietly resolved.\n linkedin: { type: \"START_CHECKOUT\", verified: false, fallback: \"OTHER\" },\n },\n },\n {\n key: \"trial_started\",\n displayName: \"Trial started\",\n meaning: \"A free trial began\",\n stepClass: \"engaged\",\n classNote: \"A trial is use, not payment — engaged, never customer.\",\n aliases: [\"trial\", \"trial started\", \"free trial\", \"in trial\"],\n platformDefaults: {\n meta: { eventName: \"StartTrial\", standard: true },\n googleAds: { category: \"Sign-up\" },\n linkedin: { type: \"OTHER\", verified: true },\n },\n },\n {\n key: \"qualified_lead\",\n displayName: \"Qualified lead\",\n meaning: \"The lead matches who you sell to\",\n stepClass: \"engaged\",\n aliases: [\"qualified lead\", \"qualified\", \"mql\", \"sql\", \"sales qualified lead\", \"marketing qualified lead\"],\n platformDefaults: {\n meta: { eventName: \"QualifiedLead\", standard: false },\n googleAds: { category: \"Qualified lead\" },\n linkedin: { type: \"QUALIFIED_LEAD\", verified: true },\n },\n },\n {\n key: \"contacted\",\n displayName: \"Contacted\",\n meaning: \"Your team reached out\",\n stepClass: \"engaged\",\n classNote: \"Engaged even though the move came from the seller: it is a real forward interaction.\",\n aliases: [\"contacted\", \"contact\", \"outreach\", \"follow up\", \"first contact\", \"attempting contact\"],\n platformDefaults: {\n meta: { eventName: \"Contact\", standard: true },\n googleAds: { category: \"Contact\" },\n linkedin: { type: \"CONTACT\", verified: true },\n },\n },\n {\n key: \"meeting_scheduled\",\n displayName: \"Meeting booked\",\n meaning: \"A meeting is on the calendar\",\n stepClass: \"engaged\",\n aliases: [\n \"meeting booked\",\n \"meeting scheduled\",\n \"demo scheduled\",\n \"demo booked\",\n \"appointment booked\",\n \"call booked\",\n ],\n platformDefaults: {\n meta: { eventName: \"Schedule\", standard: true },\n googleAds: { category: \"Book appointment\" },\n // LinkedIn has SCHEDULE and BOOK_APPOINTMENT separately; the second is the\n // one that means a reservation.\n linkedin: { type: \"BOOK_APPOINTMENT\", verified: true },\n },\n },\n {\n key: \"meeting_held\",\n displayName: \"Meeting held\",\n meaning: \"The meeting actually happened — kept apart from booking because no-show is a real number\",\n stepClass: \"engaged\",\n aliases: [\"meeting held\", \"meeting done\", \"demo held\", \"demo done\", \"demo completed\", \"call held\"],\n platformDefaults: {\n meta: { eventName: \"MeetingHeld\", standard: false },\n googleAds: { category: \"Other\" },\n linkedin: { type: \"OTHER\", verified: true },\n },\n },\n {\n key: \"opportunity\",\n displayName: \"Opportunity open\",\n meaning: \"A deal is open — send the estimated value if you have one\",\n stepClass: \"engaged\",\n classNote: \"A number behind an intention is still no money, so engaged rather than customer.\",\n aliases: [\"opportunity\", \"proposal sent\", \"proposal\", \"negotiation\", \"in negotiation\", \"quote sent\", \"deal open\"],\n platformDefaults: {\n meta: { eventName: \"Opportunity\", standard: false },\n googleAds: { category: \"Request quote\" },\n linkedin: { type: \"REQUEST_QUOTE\", verified: true },\n },\n },\n {\n key: \"won\",\n displayName: \"Deal won\",\n meaning: \"The deal closed — the amount is required here\",\n stepClass: \"customer\",\n aliases: [\"won\", \"closed won\", \"deal won\", \"purchase\", \"sale\"],\n platformDefaults: {\n meta: { eventName: \"Purchase\", standard: true },\n // Which of the two depends on whether the company reports a revenue\n // amount. It is a per-company answer, so it stays a fork, not a constant.\n googleAds: { category: \"Purchase\", alternative: \"Converted lead\" },\n linkedin: { type: \"PURCHASE\", verified: true },\n },\n },\n {\n key: \"subscribed\",\n displayName: \"Subscription started\",\n meaning: \"A paying subscription began — the amount is the recurring one\",\n stepClass: \"customer\",\n classNote: \"Customer, not repeat: it is the first money. Repeat is what happens after — see the renewed template.\",\n aliases: [\"subscribed\", \"subscription started\", \"new subscription\", \"paying\"],\n platformDefaults: {\n meta: { eventName: \"Subscribe\", standard: true },\n googleAds: { category: \"Purchase\" },\n linkedin: { type: \"PURCHASE\", verified: true },\n },\n },\n {\n key: \"renewed\",\n displayName: \"Subscription renewed\",\n meaning: \"An existing customer paid again or expanded — the amount is what they paid this time\",\n stepClass: \"repeat\",\n classNote:\n \"The smallest honest member of `repeat`. Without it every SaaS funnel drawn from this library ends at `subscribed` and the stage has nothing to hold. The recurring lifecycle beyond renewal — upgrades, churn, refunds — is a bigger conversation than a template.\",\n aliases: [\"renewed\", \"renewal\", \"subscription renewed\", \"expansion\", \"upsell\"],\n platformDefaults: {\n meta: { eventName: \"Renewed\", standard: false },\n googleAds: { category: \"Purchase\" },\n linkedin: { type: \"PURCHASE\", verified: true },\n },\n },\n {\n key: \"application_submitted\",\n displayName: \"Application submitted\",\n meaning: \"Someone submitted an application — a course, a policy, a credit line\",\n stepClass: \"engaged\",\n classNote: \"For education, events, fintech and insurance. Strong intent, no money yet.\",\n aliases: [\"application submitted\", \"application\", \"applied\", \"application received\"],\n platformDefaults: {\n meta: { eventName: \"SubmitApplication\", standard: true },\n googleAds: { category: \"Submit lead form\" },\n linkedin: { type: \"SUBMIT_APPLICATION\", verified: true },\n },\n },\n {\n key: \"lost\",\n displayName: \"Lost\",\n meaning: \"It did not go ahead — the deal fell through, or the lead was never a fit\",\n stepClass: \"lost\",\n classNote:\n \"Terminal and never sent anywhere. It can happen at any depth, which is why the order check leaves it alone.\",\n aliases: [\n \"lost\",\n \"closed lost\",\n \"deal lost\",\n \"no budget\",\n \"not interested\",\n \"disqualified\",\n \"unqualified\",\n \"not a fit\",\n \"spam\",\n \"duplicate\",\n \"junk\",\n ],\n platformDefaults: {},\n },\n];\n\nconst TEMPLATES_BY_KEY: ReadonlyMap<string, FunnelStepTemplate> = new Map(\n FUNNEL_STEP_TEMPLATES.map((template) => [template.key, template] as const),\n);\n\n/**\n * A name reduced to comparable words: lowercase, no separators, single spaces.\n * \"Won - Closed\", \"won_closed\" and \"WON / CLOSED\" all become the same two\n * words, which is the whole trick — every system writes the same moment its own\n * way.\n */\nfunction nameWords(value: string): string[] {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \" \")\n .split(\" \")\n .filter((word) => word !== \"\");\n}\n\n/** Whether `needle`'s words appear, in order and adjacent, inside `haystack`. */\nfunction containsWords(haystack: readonly string[], needle: readonly string[]): boolean {\n if (needle.length === 0 || needle.length > haystack.length) {\n return false;\n }\n for (let start = 0; start <= haystack.length - needle.length; start++) {\n if (needle.every((word, offset) => haystack[start + offset] === word)) {\n return true;\n }\n }\n return false;\n}\n\ninterface NameCandidate {\n stepKey: string;\n phrases: readonly string[];\n}\n\n/** The longest phrase that matches wins — \"closed won\" beats a bare \"won\". */\nfunction bestCandidate(subject: readonly string[], candidates: readonly NameCandidate[]): string | null {\n let best: string | null = null;\n let bestLength = 0;\n for (const candidate of candidates) {\n for (const phrase of candidate.phrases) {\n const words = nameWords(phrase);\n const matches = containsWords(subject, words) || containsWords(words, subject);\n if (matches && words.length > bestLength) {\n best = candidate.stepKey;\n bestLength = words.length;\n }\n }\n }\n return best;\n}\n\n/**\n * A guess at which of *this company's* steps some other system's name means.\n *\n * The company's own names are tried first, because a company that calls a step\n * \"Lead that fits\" has already said what its words are and no library should\n * talk over it. Only then do the template aliases get a turn, and only for\n * templates whose key the funnel actually contains — a suggestion has to be a\n * step the person can pick, so an alias of a step they never drew is no match\n * at all.\n *\n * `null` means \"no idea\", which is a fine answer: the picker just opens empty.\n */\nexport function suggestStepForName(\n incomingName: string,\n steps: readonly { key: string; displayName: string }[],\n templates: readonly FunnelStepTemplate[] = FUNNEL_STEP_TEMPLATES,\n): string | null {\n const subject = nameWords(incomingName);\n if (subject.length === 0) {\n return null;\n }\n const own = bestCandidate(\n subject,\n steps.map((step) => ({ stepKey: step.key, phrases: [step.displayName] })),\n );\n if (own !== null) {\n return own;\n }\n const drawn = new Set(steps.map((step) => step.key));\n return bestCandidate(\n subject,\n templates.filter((t) => drawn.has(t.key)).map((t) => ({ stepKey: t.key, phrases: t.aliases })),\n );\n}\n\n/** `undefined` rather than a throw: a caller offering suggestions can simply offer fewer. */\nexport function stepTemplate(key: string): FunnelStepTemplate | undefined {\n return TEMPLATES_BY_KEY.get(key);\n}\n\n/**\n * The preset a company picks once, at the cold start, to get a graph instead of\n * a blank canvas.\n *\n * **Two families, because that is who Baker sells to today.** There is no\n * e-commerce preset: no client runs that funnel and none is on the way, and a\n * preset nobody picks is a graph nobody checks. The e-commerce *templates* stay\n * in the library above — `add_to_cart`, `checkout_started`, `signed_up` are\n * offered one by one on the canvas like any other suggestion, and the day an\n * e-commerce client arrives the preset is four lines here, not new research.\n *\n * Within each family there are variants rather than one average funnel, because\n * the difference between the two B2B shapes — whether a human runs meetings —\n * changes almost every node, and a preset that splits the difference fits\n * neither.\n */\nexport const FUNNEL_PRESET_FAMILIES = [\"b2b\", \"saas\", \"ecommerce\"] as const;\nexport type FunnelPresetFamily = (typeof FUNNEL_PRESET_FAMILIES)[number];\n\nexport const FUNNEL_VERTICALS = [\n \"b2b_sales\",\n \"b2b_inbound\",\n \"saas_self_serve\",\n \"saas_sales_assisted\",\n \"ecommerce_purchase\",\n] as const;\nexport type FunnelVertical = (typeof FUNNEL_VERTICALS)[number];\n\nexport interface FunnelPresetStep {\n templateKey: string;\n /** Predecessors, by template key. Empty means the funnel starts here. */\n after: readonly string[];\n /** Overrides the template's name where the vertical says it differently — \"Purchase\", not \"Deal won\". */\n displayName?: string;\n}\n\nexport interface FunnelVerticalPreset {\n vertical: FunnelVertical;\n family: FunnelPresetFamily;\n label: string;\n /** One line for the picker, in the company's language rather than ours. */\n description: string;\n steps: readonly FunnelPresetStep[];\n}\n\n/**\n * Four small graphs — two shapes of B2B, two of SaaS — each the shortest funnel\n * of its kind that is still honest, not the fullest.\n *\n * Small on purpose. A preset is the first thing a company sees on the canvas,\n * and a fourteen-node suggestion invites deleting rather than editing; a step\n * that exists because a preset put it there and that nothing ever reaches shows\n * up as a gap in the funnel forever after.\n */\nexport const VERTICAL_PRESETS: readonly FunnelVerticalPreset[] = [\n {\n vertical: \"b2b_sales\",\n family: \"b2b\",\n label: \"B2B sales team\",\n description: \"Leads a salesperson qualifies, meets, quotes and closes.\",\n steps: [\n { templateKey: \"lead\", after: [] },\n { templateKey: \"qualified_lead\", after: [\"lead\"] },\n { templateKey: \"meeting_scheduled\", after: [\"qualified_lead\"] },\n // Booked and held stay apart because the gap between them is the no-show\n // rate, which is a number this kind of team actually manages.\n { templateKey: \"meeting_held\", after: [\"meeting_scheduled\"] },\n { templateKey: \"opportunity\", after: [\"meeting_held\"] },\n { templateKey: \"won\", after: [\"opportunity\"] },\n // The end hangs off the earliest step at which it becomes possible; the\n // order check ignores it anyway (`isOutOfOrder`), so the edge is there to\n // be drawn, not to be enforced.\n { templateKey: \"lost\", after: [\"opportunity\"] },\n ],\n },\n {\n vertical: \"b2b_inbound\",\n family: \"b2b\",\n label: \"B2B inbound, no meetings\",\n description: \"Enquiries answered and closed without a meeting stage.\",\n steps: [\n { templateKey: \"lead\", after: [] },\n { templateKey: \"qualified_lead\", after: [\"lead\"] },\n { templateKey: \"contacted\", after: [\"qualified_lead\"] },\n { templateKey: \"won\", after: [\"contacted\"] },\n { templateKey: \"lost\", after: [\"contacted\"] },\n ],\n },\n {\n vertical: \"saas_self_serve\",\n family: \"saas\",\n label: \"SaaS, self-serve\",\n description: \"People who sign up, trial the product and start paying on their own.\",\n steps: [\n { templateKey: \"signed_up\", after: [] },\n { templateKey: \"trial_started\", after: [\"signed_up\"] },\n { templateKey: \"subscribed\", after: [\"trial_started\"] },\n { templateKey: \"renewed\", after: [\"subscribed\"] },\n { templateKey: \"lost\", after: [\"trial_started\"], displayName: \"Trial expired\" },\n ],\n },\n {\n vertical: \"saas_sales_assisted\",\n family: \"saas\",\n label: \"SaaS, sales-assisted\",\n description: \"Sign-ups and demo requests, with a salesperson in the middle of the bigger ones.\",\n steps: [\n // Two entry points, which is the whole reason `after` is a list: people\n // arrive by signing up or by asking for a demo, never both, and a single\n // position integer calls that ordinary path an anomaly.\n { templateKey: \"signed_up\", after: [] },\n { templateKey: \"lead\", after: [], displayName: \"Demo requested\" },\n { templateKey: \"meeting_held\", after: [\"lead\"], displayName: \"Demo held\" },\n { templateKey: \"trial_started\", after: [\"signed_up\", \"meeting_held\"] },\n { templateKey: \"subscribed\", after: [\"trial_started\", \"meeting_held\"] },\n { templateKey: \"renewed\", after: [\"subscribed\"] },\n ],\n },\n {\n vertical: \"ecommerce_purchase\",\n family: \"ecommerce\",\n label: \"Online shop\",\n description: \"People who browse, fill a basket, check out and come back for a second order.\",\n steps: [\n { templateKey: \"saw_offer\", after: [], displayName: \"Viewed a product\" },\n { templateKey: \"add_to_cart\", after: [\"saw_offer\"] },\n { templateKey: \"checkout_started\", after: [\"add_to_cart\"] },\n // A shop's \"customer\" is the order, and its \"repeat\" is the next one —\n // the same two stages every other preset ends on, in a shop's words.\n { templateKey: \"won\", after: [\"checkout_started\"], displayName: \"Order placed\" },\n { templateKey: \"renewed\", after: [\"won\"], displayName: \"Ordered again\" },\n ],\n },\n];\n\n/**\n * The funnel every company starts with.\n *\n * Every company has a funnel from the day it exists — \"no funnel\" was a real\n * state, and it meant every \"how far did they get\" question had no answer\n * until somebody drew one. The default is the smallest honest journey: seeing\n * the offer, becoming a lead, becoming a customer. The first step listens for\n * any landing being viewed, so it counts from the first visit; the other two\n * start unwired and the canvas says so, because what a lead IS is the\n * company's to say.\n */\nexport function defaultFunnelSteps(): FunnelStepDefinition[] {\n return [\n {\n key: \"visited\",\n displayName: \"Visited\",\n stepClass: \"visited\",\n after: [],\n triggers: [{ id: \"t1\", kind: \"any_landing\" }],\n },\n { key: \"lead\", displayName: \"Became a lead\", stepClass: \"lead\", after: [\"visited\"], triggers: [] },\n { key: \"customer\", displayName: \"Became a customer\", stepClass: \"customer\", after: [\"lead\"], triggers: [] },\n ];\n}\n\nexport function verticalPreset(vertical: FunnelVertical): FunnelVerticalPreset | undefined {\n return VERTICAL_PRESETS.find((preset) => preset.vertical === vertical);\n}\n\n/**\n * A template turned into a real step of a company's funnel.\n *\n * The template stops existing at this point: what comes out is an ordinary\n * `FunnelStepDefinition` the company owns and can rename, reclassify or rewire,\n * with no link back. That is what makes this a suggestion library and not a\n * second schema.\n */\nexport function stepFromTemplate(\n template: FunnelStepTemplate,\n after: readonly string[],\n displayName?: string,\n): FunnelStepDefinition {\n return {\n key: template.key,\n displayName: displayName ?? template.displayName,\n stepClass: template.stepClass,\n after: [...after],\n // A template says what a moment IS, never which of this company's events\n // announces it — that is a per-company answer, and prefilling a guess would\n // make a step look wired when it advances nobody.\n triggers: [],\n };\n}\n\n/**\n * The steps a preset produces, ready to hand to `validateDefinition` or draw.\n *\n * A preset step whose template does not exist is dropped rather than faked: the\n * pair is checked by a test, so a miss here is a bug in this file and a\n * half-invented step would hide it.\n */\nexport function presetDefinition(preset: FunnelVerticalPreset): FunnelStepDefinition[] {\n return preset.steps.flatMap((step) => {\n const template = stepTemplate(step.templateKey);\n return template ? [stepFromTemplate(template, step.after, step.displayName)] : [];\n });\n}\n","/**\n * The request and response `baker funnel` and its Convex route both speak.\n *\n * One schema, used by both sides, because the funnel is the one payload in this\n * feature that is a graph rather than a list of fields: a shape the CLI and the\n * backend agreed on separately would disagree about a predecessor naming a step\n * that does not exist, and that disagreement is invisible until a client's\n * funnel comes back drawn wrong.\n */\n\nimport { z } from \"zod\";\nimport { FUNNEL_VERTICALS } from \"./stepTemplates\";\nimport { FUNNEL_STEP_CLASSES } from \"./vocabulary\";\n\n/** How many steps one funnel may have. */\nconst MAX_STEPS = 40;\n/** How many triggers one step may have. */\nconst MAX_PER_STEP = 20;\n\n/**\n * One trigger: an event, by its key, a landing page being viewed, or any\n * landing being viewed.\n */\nexport const funnelTriggerSchema = z.discriminatedUnion(\"kind\", [\n z.object({\n id: z.string().min(1).max(64),\n kind: z.literal(\"event\"),\n /** The event's key, from `--moments`: `form:…`, `submit:…`, `page:…`, `exit:…`. */\n eventKey: z.string().min(1).max(320),\n }),\n z.object({\n id: z.string().min(1).max(64),\n kind: z.literal(\"landing\"),\n /** The landing's internal id — what the page stamps on its rows, never its slug. */\n landingId: z.string().min(1).max(120),\n }),\n /** Any landing being viewed — what a funnel's first step usually listens for. */\n z.object({\n id: z.string().min(1).max(64),\n kind: z.literal(\"any_landing\"),\n }),\n]);\n\nexport const funnelStepSchema = z.object({\n /** Lowercase letters, digits and underscores. Stable — renaming never changes it. */\n key: z.string().min(1).max(64),\n /** The client's own words for this step. Shown everywhere; unconstrained. */\n displayName: z.string().min(1).max(120),\n stepClass: z.enum(FUNNEL_STEP_CLASSES),\n /** Predecessor keys. Empty means people come in here. */\n after: z.array(z.string().max(64)).max(MAX_STEPS).default([]),\n /** What moves somebody onto this step. Any one of them is enough. */\n triggers: z.array(funnelTriggerSchema).max(MAX_PER_STEP).default([]),\n});\nexport type FunnelStepWire = z.infer<typeof funnelStepSchema>;\n\n/**\n * With none of `steps`, `preset`, `step`, `removeStep`, `addTrigger` and\n * `removeTrigger` this is a read, which is what makes `baker funnel` with no\n * flags show the funnel rather than erroring.\n *\n * `steps` restates the WHOLE funnel, and the review screen shows the funnel\n * the client will get rather than a set of edits whose result has to be\n * computed. `step` and `removeStep` change one step and leave the rest as it\n * stands — the door for an agent that wants to add a stage without restating\n * six others, because a restatement that forgot one removed it. Whichever\n * door is used, the response names every step the proposal dropped.\n */\nexport const funnelRequestSchema = z.object({\n steps: z.array(funnelStepSchema).max(MAX_STEPS).optional(),\n /** One step, added or replaced by its key. Everything else is untouched. */\n step: funnelStepSchema.optional(),\n /** One step taken out by key; whatever came after it moves up to whatever came before it. */\n removeStep: z.string().min(1).max(64).optional(),\n /**\n * ONE way in added to a step, leaving the rest of that step alone.\n *\n * `step` takes a whole step, so adding a way in through it means restating\n * the step's name, stage, `after[]` and every trigger it already had — and a\n * trigger left out of that restatement is silently removed. This is the door\n * for the thing that is actually being asked (\"this Form's booking should\n * count here\"), and it cannot drop anything.\n */\n addTrigger: z.object({ stepKey: z.string().min(1).max(64), eventKey: z.string().min(1).max(320) }).optional(),\n /** ONE way in taken off a step, by the event key it listens for. */\n removeTrigger: z.object({ stepKey: z.string().min(1).max(64), eventKey: z.string().min(1).max(320) }).optional(),\n /** One of the ready-made graphs, instead of stating steps. */\n preset: z.enum(FUNNEL_VERTICALS).optional(),\n /** Also return the suggestion library and the presets. */\n templates: z.boolean().optional(),\n /**\n * Also return everything a trigger can name: every event this company's\n * Forms, pages and systems have actually produced, its sources, and its\n * landings. The list to pick a trigger FROM, so nobody has to guess.\n */\n moments: z.boolean().optional(),\n /** How many days the counts beside each step cover. Default 30. */\n days: z.number().int().min(1).max(400).optional(),\n /**\n * The Forms, by slug, the caller names itself when no step counts them —\n * `baker funnel` reads its own workspace and names each with the key that\n * places it. The backend leaves them out of its own hint, so no Form is\n * named twice.\n */\n namedByCaller: z.array(z.string().min(1).max(200)).max(1000).optional(),\n});\nexport type FunnelRequest = z.infer<typeof funnelRequestSchema>;\n\n/**\n * What comes back is loosely typed on purpose.\n *\n * The response is read by an agent and by a person, never re-submitted, so a\n * strict mirror of the request would only make the route fail on a field the\n * store has and this file has not caught up with. The request is where the\n * shape is enforced.\n */\nexport const funnelResponseSchema = z.object({\n ok: z.literal(true),\n data: z.object({\n /** The published version, or 0 when this company has never drawn a funnel. */\n version: z.number(),\n /** Whether `steps` is a proposal waiting on this chat's publish rather than what is live. */\n staged: z.boolean(),\n steps: z.array(\n z.object({\n key: z.string(),\n displayName: z.string(),\n stepClass: z.string(),\n after: z.array(z.string()),\n triggers: z.array(z.record(z.string(), z.unknown())),\n /**\n * Where the people who reach this step are sent: the sends of every\n * system event among its triggers. Read-only here — a send belongs to\n * the event and is set with `baker analytics sends`.\n */\n sends: z.array(\n z.object({\n id: z.string(),\n eventKey: z.string(),\n platform: z.string(),\n event: z.string(),\n /** What happened to it in the window, in one line. */\n outcome: z.string().optional(),\n }),\n ),\n /** How many people reached this step in the window. Counted in people, across visits and devices. */\n people: z.number().optional(),\n /** How many reached this step or any step after it. */\n atLeast: z.number().optional(),\n /** How many got as far as the step before it. Absent for a way in. */\n before: z.number().optional(),\n /** `atLeast / before`, 0–1. Absent where there is no before. */\n share: z.number().optional(),\n events: z.number().optional(),\n money: z.record(z.string(), z.number()).optional(),\n }),\n ),\n /** The window `people` and friends were counted over. */\n window: z.object({ startDate: z.string(), endDate: z.string(), days: z.number() }).optional(),\n /** The step that loses the most people, by key. Absent while nothing is lost. */\n worstStep: z.string().optional(),\n /**\n * The steps this write took off the funnel, by key. Present on every write,\n * empty when nothing was dropped — the answer to \"did my restatement forget\n * one\", given in the same turn.\n */\n removed: z.array(z.string()).optional(),\n /**\n * What this company counts as page conversions, this chat's staged counts\n * included, by event key and name. Here so a Form's ending can say it is\n * already counted — the agent choosing a step's way in should use that\n * same key — and outside `moments` because the plain read's hints say it too.\n */\n counted: z.array(z.object({ eventKey: z.string(), name: z.string() })).optional(),\n /** Everything a trigger can name, when asked for with `moments`. */\n moments: z\n .object({\n events: z.array(\n z.object({\n eventKey: z.string(),\n /** `site` for a page or Form, `systems` for the client's own server, `both` when both sent it. */\n origin: z.string().optional(),\n /** The Form it belongs to, when it belongs to one. */\n flowSlug: z.string().optional(),\n nodeType: z.string().optional(),\n events: z.number(),\n sessions: z.number(),\n /** The name it is counted under as a page conversion, when the company counts it. Informational. */\n countedAs: z.string().optional(),\n /**\n * The step of the Form this happens on, in the Form's own words —\n * never a node id. Present on a Form's ending read from the Form\n * itself, which is the only place the name is known.\n */\n step: z.string().optional(),\n /**\n * `false` on an ending a Form raises natively but nobody has fired\n * yet — derived from the Form, offered at zero, exactly as a\n * Source is offered before its first send. Absent means it came\n * from the event store and therefore happened.\n */\n seen: z.boolean().optional(),\n }),\n ),\n /** The company's sources: a system of theirs that posts events to Baker, by the event name it raises. */\n sources: z.array(z.object({ eventKey: z.string(), name: z.string(), status: z.string() })),\n landings: z.array(z.object({ landingId: z.string(), title: z.string(), slug: z.string() })),\n })\n .optional(),\n templates: z\n .array(\n z.object({\n key: z.string(),\n displayName: z.string(),\n meaning: z.string(),\n stepClass: z.string(),\n }),\n )\n .optional(),\n presets: z\n .array(z.object({ vertical: z.string(), label: z.string(), description: z.string(), steps: z.array(z.string()) }))\n .optional(),\n /**\n * What each stage means, the test to apply, and the same stage in three\n * shapes of business.\n *\n * The stage is the one thing in a funnel a company does not invent, so it\n * is the one thing the agent has to get right on the company's behalf —\n * and \"engaged: did something real\" cannot settle whether a trial is\n * engaged or customer. Same source as the dashboard's stage picker, so the\n * two cannot teach it differently.\n */\n stages: z\n .array(\n z.object({\n stepClass: z.string(),\n title: z.string(),\n meaning: z.string(),\n test: z.string(),\n examples: z.object({ shop: z.string(), selfServe: z.string(), salesLed: z.string() }),\n }),\n )\n .optional(),\n }),\n hints: z.array(z.string()).optional(),\n});\nexport type FunnelResponse = z.infer<typeof funnelResponseSchema>;\n","import { parseEventKey } from \"@baker/api/analytics\";\nimport { FUNNEL_VERTICALS, type FunnelRequest, funnelRequestSchema } from \"@baker/api/funnel\";\n\n/**\n * `baker funnel`'s flags, turned into the one request the route takes.\n *\n * Pure and tier-1 tested, because everything that can go wrong with this\n * command goes wrong here: hand-written JSON, a preset spelled from memory, and\n * a step whose ways in are half-written. Each of those has to come back as a\n * sentence the agent can act on in the same turn rather than as a request that\n * failed for a reason only the server can see.\n *\n * There is no `--forms` any more. It used to say which step each Form's\n * submissions entered, alongside a rule that dropped an unplaced Form on the\n * funnel's first step — so a funnel that measured a client's Forms\n * by accident was indistinguishable from one that measured them deliberately.\n * A Form reaches a step because a trigger on that step names it, and `baker\n * funnel`'s hints name every Form no step counts.\n */\n\nexport class FunnelArgsError extends Error {\n /** Structured remediation, in the shape the CLI's error envelope carries. */\n readonly fix?: { action: string; explanation: string };\n\n constructor(message: string, fix?: { action: string; explanation: string }) {\n super(message);\n this.name = \"FunnelArgsError\";\n if (fix) {\n this.fix = fix;\n }\n }\n}\n\nexport interface FunnelArgs {\n steps?: unknown[];\n /** One step, added or replaced by key; the rest of the funnel is left as it stands. */\n step?: unknown;\n /** One step taken off by key. */\n removeStep?: string;\n /** One way in added to a step, by the event key it listens for. */\n addTrigger?: { stepKey: string; eventKey: string };\n /** One way in taken off a step. */\n removeTrigger?: { stepKey: string; eventKey: string };\n preset?: string;\n templates: boolean;\n moments: boolean;\n days?: number;\n}\n\nexport function parseFunnelArgs(raw: Record<string, unknown>): FunnelArgs {\n const steps = parseSteps(raw.steps);\n const step = parseStep(raw.step);\n const removeStep = parseRemoveStep(raw[\"remove-step\"] ?? raw.removeStep);\n const preset = parsePreset(raw.preset);\n const addTrigger = parseWayIn(raw[\"add-trigger\"] ?? raw.addTrigger, raw.event, \"--add-trigger\");\n const removeTrigger = parseWayIn(raw[\"remove-trigger\"] ?? raw.removeTrigger, raw.event, \"--remove-trigger\");\n\n const doors = [steps, step, removeStep, preset, addTrigger, removeTrigger].filter(\n (door) => door !== undefined,\n ).length;\n if (doors > 1) {\n throw new FunnelArgsError(\n \"Give one of --steps, --step, --remove-step, --preset, --add-trigger or --remove-trigger, not two.\",\n );\n }\n\n const days = raw.days === undefined ? undefined : Number(raw.days);\n if (days !== undefined && (!Number.isInteger(days) || days <= 0 || days > 400)) {\n throw new FunnelArgsError(\"--days must be a whole number of days, from 1 to 400.\");\n }\n\n return {\n ...(steps === undefined ? {} : { steps }),\n ...(step === undefined ? {} : { step }),\n ...(removeStep === undefined ? {} : { removeStep }),\n ...(preset === undefined ? {} : { preset }),\n ...(addTrigger === undefined ? {} : { addTrigger }),\n ...(removeTrigger === undefined ? {} : { removeTrigger }),\n templates: raw.templates === true,\n moments: raw.moments === true,\n ...(days === undefined ? {} : { days }),\n };\n}\n\n/**\n * The request body. Validated against the shared schema here rather than only\n * on the server, so a malformed step is named before a round trip — and named\n * as the step it is, which the server's first-issue message cannot always do.\n */\nexport function funnelWrite(args: FunnelArgs): FunnelRequest {\n const body = {\n ...(args.steps === undefined ? {} : { steps: args.steps }),\n ...(args.step === undefined ? {} : { step: args.step }),\n ...(args.removeStep === undefined ? {} : { removeStep: args.removeStep }),\n ...(args.preset === undefined ? {} : { preset: args.preset }),\n ...(args.addTrigger === undefined ? {} : { addTrigger: args.addTrigger }),\n ...(args.removeTrigger === undefined ? {} : { removeTrigger: args.removeTrigger }),\n ...(args.templates ? { templates: true } : {}),\n ...(args.moments ? { moments: true } : {}),\n ...(args.days === undefined ? {} : { days: args.days }),\n };\n const parsed = funnelRequestSchema.safeParse(body);\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n throw new FunnelArgsError(\n `That funnel cannot be sent — ${issue?.path.join(\".\") || \"steps\"}: ${issue?.message ?? \"invalid\"}`,\n {\n action: \"read_the_schema\",\n explanation:\n \"Run `baker schema funnel` for the exact shape of one step, or `baker funnel --templates` for sixteen worked examples plus every field an event carries.\",\n },\n );\n }\n return parsed.data;\n}\n\nfunction parseSteps(raw: unknown): unknown[] | undefined {\n if (raw === undefined) {\n return undefined;\n }\n if (typeof raw !== \"string\") {\n throw new FunnelArgsError(\"--steps takes a JSON array of steps.\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new FunnelArgsError(`--steps is not valid JSON: ${cause instanceof Error ? cause.message : \"parse failed\"}`, {\n action: \"quote_the_json\",\n explanation:\n 'Wrap the whole array in single quotes so the shell leaves the double quotes alone: --steps \\'[{\"key\":\"lead\",...}]\\'.',\n });\n }\n if (!Array.isArray(parsed)) {\n throw new FunnelArgsError(\"--steps takes an ARRAY of steps, even when the funnel has one step.\");\n }\n refuseRetiredShapes(parsed);\n return parsed;\n}\n\n/**\n * One step, as JSON. The same shape one entry of `--steps` takes, and the same\n * refusals — a step written the old way is refused here too.\n */\nfunction parseStep(raw: unknown): unknown | undefined {\n if (raw === undefined) {\n return undefined;\n }\n if (typeof raw !== \"string\") {\n throw new FunnelArgsError(\"--step takes one step as a JSON object.\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new FunnelArgsError(`--step is not valid JSON: ${cause instanceof Error ? cause.message : \"parse failed\"}`, {\n action: \"quote_the_json\",\n explanation:\n 'Wrap the object in single quotes so the shell leaves the double quotes alone: --step \\'{\"key\":\"demo\",...}\\'.',\n });\n }\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new FunnelArgsError(\"--step takes ONE step as an object. To restate several, use --steps with an array.\");\n }\n refuseRetiredShapes([parsed]);\n return parsed;\n}\n\nfunction parseRemoveStep(raw: unknown): string | undefined {\n if (raw === undefined) {\n return undefined;\n }\n if (typeof raw !== \"string\" || raw.trim() === \"\") {\n throw new FunnelArgsError(\"--remove-step takes the key of the step to take off, as `baker funnel` lists it.\");\n }\n return raw.trim();\n}\n\n/**\n * The shapes a step used to have and no longer does, refused by name.\n *\n * The wire schema would strip them silently: a step written with\n * `offlineConversions` would be stored without them and answer `ok`, and an\n * `analytics` or `conversion` trigger would be refused with zod's generic\n * discriminator message. Each has a home now — a send is set on the event, and\n * a way in is an event by its key with nothing to name first — so the refusal\n * says where.\n */\nfunction refuseRetiredShapes(steps: readonly unknown[]): void {\n for (const step of steps) {\n if (typeof step !== \"object\" || step === null) continue;\n const key = String((step as { key?: unknown }).key ?? \"?\");\n if (\"offlineConversions\" in step) {\n throw new FunnelArgsError(`Step \"${key}\" puts sends on the step, and a send belongs to the conversion.`, {\n action: \"set_sends_on_the_conversion\",\n explanation:\n \"Drop `offlineConversions`. Name the outcome with `baker analytics conversions`, then `baker analytics sends --set --conversion <name> --platform <p> --tag <tagId> --event <e>`; the step reads its conversions' sends back as `sends[]`.\",\n });\n }\n const triggers = (step as { triggers?: unknown }).triggers;\n if (!Array.isArray(triggers)) continue;\n const kinds = triggers.map((trigger) =>\n typeof trigger === \"object\" && trigger !== null ? (trigger as { kind?: unknown }).kind : undefined,\n );\n if (kinds.includes(\"analytics\") || kinds.includes(\"conversion\")) {\n const word = kinds.includes(\"analytics\") ? \"analytics\" : \"conversion\";\n throw new FunnelArgsError(\n `Step \"${key}\" has a \\`${word}\\` trigger, and a step is reached by an event or a landing only.`,\n {\n action: \"use_an_event_trigger\",\n explanation:\n 'Pick the moment from `baker funnel --moments` and write it as `{\"kind\":\"event\",\"eventKey\":\"<from moments.events or moments.sources>\"}` — nothing has to be named first. A landing is `{\"kind\":\"landing\",\"landingId\":\"<from moments.landings>\"}`.',\n },\n );\n }\n }\n}\n\nfunction parsePreset(raw: unknown): string | undefined {\n if (raw === undefined) {\n return undefined;\n }\n if (typeof raw !== \"string\" || !(FUNNEL_VERTICALS as readonly string[]).includes(raw)) {\n throw new FunnelArgsError(`--preset must be one of: ${FUNNEL_VERTICALS.join(\", \")}`, {\n action: \"list_the_presets\",\n explanation: \"Run `baker funnel --templates` to see what each preset contains before picking one.\",\n });\n }\n return raw;\n}\n\n/**\n * `--add-trigger <step> --event <key>`, or a refusal naming the missing half.\n *\n * Two flags rather than one JSON object, because that is the whole point of\n * this door: the thing being asked is \"this event should count on that step\",\n * and asking for JSON here would put the caller back in the business of\n * hand-writing a shape. The event key is still never invented — `--moments`\n * is where it comes from, and the refusal says so.\n */\nfunction parseWayIn(\n stepKey: unknown,\n eventKey: unknown,\n flag: string,\n): { stepKey: string; eventKey: string } | undefined {\n if (stepKey === undefined) return undefined;\n if (typeof stepKey !== \"string\" || stepKey.trim() === \"\") {\n throw new FunnelArgsError(`${flag} takes the key of the step to change: \\`${flag} lead\\`.`);\n }\n if (typeof eventKey !== \"string\" || eventKey.trim() === \"\") {\n throw new FunnelArgsError(`${flag} needs --event, naming what gets somebody there.`, {\n action: \"read_the_moments\",\n explanation: `Run \\`baker funnel --moments\\` and copy an \\`events[].eventKey\\` — a Form's own outcome is listed there at zero before anybody has filled it in. Then: \\`${flag} ${stepKey.trim()} --event \"<key>\"\\`.`,\n });\n }\n const key = eventKey.trim();\n // Refused here rather than at the backend, and only when ADDING: a key\n // nothing can produce does not error downstream, it counts zero for ever.\n // Removing is the opposite case — a key already on a step precisely because\n // it was wrong has to stay removable, whatever it parses as.\n if (flag === \"--add-trigger\" && parseEventKey(key) === null) {\n throw new FunnelArgsError(`\"${key}\" is not an event key, so nothing would ever reach that step through it.`, {\n action: \"read_the_moments\",\n explanation:\n \"Run `baker funnel --moments` and copy an `events[].eventKey`. They look like form:<form>:<step>:<trigger>, submit:<form>, page:<name> or view:<landing>.\",\n });\n }\n return { stepKey: stepKey.trim(), eventKey: key };\n}\n"],"mappings":"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","names":["args"]}
|