@koda-sl/baker-cli 0.513.0 → 0.514.0-dev.555f7007e
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-QX4G4BJ7.js +82 -0
- package/dist/account-QX4G4BJ7.js.map +1 -0
- package/dist/account-UFYZS3K6.js +82 -0
- package/dist/account-UFYZS3K6.js.map +1 -0
- package/dist/account-assets-J5Z4URLQ.js +290 -0
- package/dist/account-level-4SBYYHRS.js +611 -0
- package/dist/account-level-4SBYYHRS.js.map +1 -0
- package/dist/account-reads-FOEVPWDP.js +219 -0
- package/dist/account-users-UTIF7MIT.js +64 -0
- package/dist/account-users-UTIF7MIT.js.map +1 -0
- package/dist/account-writes-QAQXRIEY.js +68 -0
- package/dist/account-writes-QAQXRIEY.js.map +1 -0
- package/dist/accounts-JK3OAS5U.js +701 -0
- package/dist/accounts-JK3OAS5U.js.map +1 -0
- package/dist/accounts-OE33CKWD.js +80 -0
- package/dist/accounts-OE33CKWD.js.map +1 -0
- package/dist/actions-QZDSLFFK.js +55 -0
- package/dist/activecampaign-U6TKRR45.js +938 -0
- package/dist/activecampaign-U6TKRR45.js.map +1 -0
- package/dist/ad-group-writes-XY5JYTKT.js +172 -0
- package/dist/ad-group-writes-XY5JYTKT.js.map +1 -0
- package/dist/ad-groups-YEO5ZS5U.js +70 -0
- package/dist/ad-groups-YEO5ZS5U.js.map +1 -0
- package/dist/ad-writes-XRCPK4SP.js +176 -0
- package/dist/ad-writes-XRCPK4SP.js.map +1 -0
- package/dist/add-2EF7S4NK.js +305 -0
- package/dist/add-2EF7S4NK.js.map +1 -0
- package/dist/add-YCJ6RXPG.js +107 -0
- package/dist/add-YCJ6RXPG.js.map +1 -0
- package/dist/ads-AYJAYLFR.js +48 -0
- package/dist/ads-H7H7H2CM.js +79 -0
- package/dist/ads-H7H7H2CM.js.map +1 -0
- package/dist/ads-MAOI7S6V.js +115 -0
- package/dist/adsets-QRKICEFD.js +182 -0
- package/dist/advertisers-JLSU3G35.js +123 -0
- package/dist/advertisers-JLSU3G35.js.map +1 -0
- package/dist/analytics-ADIWEM6G.js +2437 -0
- package/dist/analytics-ADIWEM6G.js.map +1 -0
- package/dist/analytics-XB6FRNVJ.js +447 -0
- package/dist/analytics-XB6FRNVJ.js.map +1 -0
- package/dist/animate-HRWFVE2N.js +480 -0
- package/dist/asset-writes-JLVUAVUZ.js +50 -0
- package/dist/assign-IWZ2OPFL.js +80 -0
- package/dist/assign-IWZ2OPFL.js.map +1 -0
- package/dist/audience-size-P5L6MZ4C.js +76 -0
- package/dist/audience-size-P5L6MZ4C.js.map +1 -0
- package/dist/audience-writes-CDKQB3SM.js +327 -0
- package/dist/audience-writes-CDKQB3SM.js.map +1 -0
- package/dist/audiences-CDDI5HR5.js +86 -0
- package/dist/audiences-XKERWKKE.js +280 -0
- package/dist/audiences-XKERWKKE.js.map +1 -0
- package/dist/audit-SEADLC6F.js +154 -0
- package/dist/audit-SEADLC6F.js.map +1 -0
- package/dist/autocomplete-AJQBJS5R.js +114 -0
- package/dist/autocomplete-AJQBJS5R.js.map +1 -0
- package/dist/avatars-4HCGWBOL.js +38 -0
- package/dist/bid-pricing-NWSUO3VM.js +103 -0
- package/dist/bid-pricing-NWSUO3VM.js.map +1 -0
- package/dist/brand-C6OB6Q4U.js +22 -0
- package/dist/brief-LCWWQ4OC.js +543 -0
- package/dist/brief-LCWWQ4OC.js.map +1 -0
- package/dist/campaign-groups-PH64UXZJ.js +99 -0
- package/dist/campaign-groups-PH64UXZJ.js.map +1 -0
- package/dist/campaign-writes-BN3EGDK5.js +168 -0
- package/dist/campaign-writes-BN3EGDK5.js.map +1 -0
- package/dist/campaign-writes-CLV5ZSUK.js +176 -0
- package/dist/campaign-writes-CLV5ZSUK.js.map +1 -0
- package/dist/campaigns-5XRY27JZ.js +78 -0
- package/dist/campaigns-5XRY27JZ.js.map +1 -0
- package/dist/campaigns-HES5MDUG.js +130 -0
- package/dist/campaigns-JF4TYYUP.js +113 -0
- package/dist/campaigns-JF4TYYUP.js.map +1 -0
- package/dist/campaigns-ZYU4BXDP.js +111 -0
- package/dist/canvas-AI34UBGU.js +46 -0
- package/dist/capabilities-TRHAPTUU.js +147 -0
- package/dist/capabilities-TRHAPTUU.js.map +1 -0
- package/dist/catalog-ZY3MFT55.js +65 -0
- package/dist/catalogs-6XLBWAUS.js +130 -0
- package/dist/catalogs-6XLBWAUS.js.map +1 -0
- package/dist/catalogs-TGLSJEK5.js +781 -0
- package/dist/catalogs-TGLSJEK5.js.map +1 -0
- package/dist/changes-OOUVQDBY.js +135 -0
- package/dist/chatgpt-NAXIFWXV.js +47 -0
- package/dist/chats-I5RHBRS5.js +279 -0
- package/dist/chats-I5RHBRS5.js.map +1 -0
- package/dist/check-3E64VBUE.js +107 -0
- package/dist/check-3E64VBUE.js.map +1 -0
- package/dist/chunk-3CHYJYSS.js +319 -0
- package/dist/chunk-3G3GC5N4.js +429 -0
- package/dist/chunk-3M45PYU7.js +274 -0
- package/dist/chunk-3QHXF3QI.js +23 -0
- package/dist/chunk-3XPHUMLJ.js +3868 -0
- package/dist/chunk-45EERHDB.js +783 -0
- package/dist/chunk-4FGTANFK.js +387 -0
- package/dist/chunk-4IHWT7PH.js +167 -0
- package/dist/chunk-4LF4AYPT.js +846 -0
- package/dist/chunk-4UQRXKYJ.js +219 -0
- package/dist/chunk-5JJD33Y2.js +455 -0
- package/dist/chunk-5K4QYYOJ.js +831 -0
- package/dist/chunk-5LJ6E4YU.js +163 -0
- package/dist/chunk-5T73MMBL.js +275 -0
- package/dist/chunk-64MAHLTK.js +363 -0
- package/dist/chunk-6OIKNJ7G.js +26 -0
- package/dist/chunk-6ZFSDYWV.js +131 -0
- package/dist/chunk-7BUIOP36.js +287 -0
- package/dist/chunk-7I6ZVBLM.js +358 -0
- package/dist/chunk-7I6ZVBLM.js.map +1 -0
- package/dist/chunk-AN4OEQXE.js +122 -0
- package/dist/chunk-AULSX5ZA.js +5368 -0
- package/dist/chunk-AVYUFJQQ.js +46 -0
- package/dist/chunk-B4YF7VQF.js +97 -0
- package/dist/chunk-BDOYNGUL.js +98 -0
- package/dist/chunk-BJOK7QD4.js +207 -0
- package/dist/chunk-CMCGFSUS.js +197 -0
- package/dist/chunk-DMJUIUAL.js +676 -0
- package/dist/chunk-DUCUOZLS.js +225 -0
- package/dist/chunk-EW7JSTRZ.js +24026 -0
- package/dist/chunk-EW7JSTRZ.js.map +1 -0
- package/dist/chunk-F3CZFUXZ.js +3187 -0
- package/dist/chunk-F3CZFUXZ.js.map +1 -0
- package/dist/chunk-FJZYOVSW.js +843 -0
- package/dist/chunk-FKP4ECJY.js +19 -0
- package/dist/chunk-G4FG5EQO.js +110 -0
- package/dist/chunk-G4FG5EQO.js.map +1 -0
- package/dist/chunk-GAYRBIEF.js +712 -0
- package/dist/chunk-GAYRBIEF.js.map +1 -0
- package/dist/chunk-GNMEOU46.js +8954 -0
- package/dist/chunk-GYKYXWUQ.js +156 -0
- package/dist/chunk-GYPLRU4Y.js +297 -0
- package/dist/chunk-I43ZEWJQ.js +285 -0
- package/dist/chunk-IZABTTW5.js +228 -0
- package/dist/chunk-JOE42ZW6.js +2984 -0
- package/dist/chunk-JOE42ZW6.js.map +1 -0
- package/dist/chunk-K7IE6UYV.js +1646 -0
- package/dist/chunk-KH5YHHJV.js +604 -0
- package/dist/chunk-KMY7AD3N.js +51 -0
- package/dist/chunk-KPCHWDJM.js +8914 -0
- package/dist/chunk-KTZCLJ2D.js +148 -0
- package/dist/chunk-MKUA54QE.js +638 -0
- package/dist/chunk-MTV2RSGY.js +63 -0
- package/dist/chunk-MUS2UTWU.js +4254 -0
- package/dist/chunk-MUS2UTWU.js.map +1 -0
- package/dist/chunk-N2QX57K3.js +392 -0
- package/dist/chunk-NB2FU6EZ.js +179 -0
- package/dist/chunk-NEYWS2DQ.js +479 -0
- package/dist/chunk-NKQWDEB7.js +115 -0
- package/dist/chunk-NKQWDEB7.js.map +1 -0
- package/dist/chunk-NWF2LRKO.js +188 -0
- package/dist/chunk-NZ34KM46.js +7211 -0
- package/dist/chunk-NZ34KM46.js.map +1 -0
- package/dist/chunk-OTC6UMJ2.js +191 -0
- package/dist/chunk-OUY2IAWL.js +224 -0
- package/dist/chunk-PJXNCRMR.js +344 -0
- package/dist/chunk-PYQ3GF7R.js +91 -0
- package/dist/chunk-Q3EIVZAR.js +170 -0
- package/dist/chunk-Q3GSVLE5.js +4274 -0
- package/dist/chunk-Q3GSVLE5.js.map +1 -0
- package/dist/chunk-QDQZ62FL.js +207 -0
- package/dist/chunk-QHZE62GP.js +1919 -0
- package/dist/chunk-RGR6TSTJ.js +103 -0
- package/dist/chunk-SAAXU7QR.js +203 -0
- package/dist/chunk-SHTKLRI6.js +66 -0
- package/dist/chunk-SIKCVWM3.js +76 -0
- package/dist/chunk-SOPVIIUW.js +68 -0
- package/dist/chunk-SOPVIIUW.js.map +1 -0
- package/dist/chunk-SSXG53CZ.js +89 -0
- package/dist/chunk-TBWAM577.js +547 -0
- package/dist/chunk-TGPGQTZA.js +169 -0
- package/dist/chunk-UMIX2BMM.js +1518 -0
- package/dist/chunk-URMAEVZO.js +150 -0
- package/dist/chunk-VBO3ZVUU.js +61 -0
- package/dist/chunk-VFLYMHTO.js +592 -0
- package/dist/chunk-VGYIVXIZ.js +249 -0
- package/dist/chunk-VIB7R533.js +57 -0
- package/dist/chunk-WLESMH4J.js +45 -0
- package/dist/chunk-WVWLPXBO.js +910 -0
- package/dist/chunk-XEH5XAP6.js +494 -0
- package/dist/chunk-XEOKVL4E.js +142 -0
- package/dist/chunk-XOGQJYXH.js +64 -0
- package/dist/chunk-XOQNM7I4.js +396 -0
- package/dist/chunk-XOQNM7I4.js.map +1 -0
- package/dist/chunk-XUXRA7IB.js +942 -0
- package/dist/claim-FIK2YTLM.js +200 -0
- package/dist/claim-FIK2YTLM.js.map +1 -0
- package/dist/cli.js +34 -33
- package/dist/cli.js.map +1 -1
- package/dist/companies-KMMV6EJW.js +198 -0
- package/dist/companies-KMMV6EJW.js.map +1 -0
- package/dist/company-B54URPJG.js +308 -0
- package/dist/company-B54URPJG.js.map +1 -0
- package/dist/complete-PPCWEXS3.js +84 -0
- package/dist/complete-PPCWEXS3.js.map +1 -0
- package/dist/connect-IAXSZPGA.js +45 -0
- package/dist/conversation-KHTZBGX2.js +96 -0
- package/dist/conversation-KHTZBGX2.js.map +1 -0
- package/dist/conversion-report-U66HW6JM.js +127 -0
- package/dist/conversion-report-U66HW6JM.js.map +1 -0
- package/dist/conversion-upload-command-IHFKVX2M.js +439 -0
- package/dist/conversion-writes-RSGDOAET.js +122 -0
- package/dist/conversion-writes-RSGDOAET.js.map +1 -0
- package/dist/conversions-BVOL43W4.js +78 -0
- package/dist/conversions-BVOL43W4.js.map +1 -0
- package/dist/conversions-SUMZAV2T.js +140 -0
- package/dist/conversions-SUMZAV2T.js.map +1 -0
- package/dist/copy-writes-CIHIFLY6.js +170 -0
- package/dist/copy-writes-CIHIFLY6.js.map +1 -0
- package/dist/create-732HQL3C.js +210 -0
- package/dist/create-732HQL3C.js.map +1 -0
- package/dist/create-TFR6R2KF.js +239 -0
- package/dist/create-TFR6R2KF.js.map +1 -0
- package/dist/create-UC57ULRL.js +62 -0
- package/dist/create-UC57ULRL.js.map +1 -0
- package/dist/create-UL2OYXOA.js +135 -0
- package/dist/create-UL2OYXOA.js.map +1 -0
- package/dist/creatives-BB6S2OF6.js +89 -0
- package/dist/creatives-BZEVJ6ER.js +58 -0
- package/dist/creatives-FU3YELGU.js +223 -0
- package/dist/creatives-FU3YELGU.js.map +1 -0
- package/dist/credentials-AYV5YQE4.js +290 -0
- package/dist/credentials-AYV5YQE4.js.map +1 -0
- package/dist/critique-FKIJB72I.js +1451 -0
- package/dist/critique-FKIJB72I.js.map +1 -0
- package/dist/critique-IQYNDXCN.js +232 -0
- package/dist/critique-IQYNDXCN.js.map +1 -0
- package/dist/crm-ILQMNJPX.js +323 -0
- package/dist/crm-ILQMNJPX.js.map +1 -0
- package/dist/crm-MQQRBITZ.js +227 -0
- package/dist/crm-MQQRBITZ.js.map +1 -0
- package/dist/currency-CGKO5GKH.js +88 -0
- package/dist/custom-conversions-HAGG72LF.js +128 -0
- package/dist/customers-NKKU6S7G.js +59 -0
- package/dist/delete-2TCCNRMH.js +57 -0
- package/dist/delete-2TCCNRMH.js.map +1 -0
- package/dist/delete-HCZTISBT.js +69 -0
- package/dist/delete-HCZTISBT.js.map +1 -0
- package/dist/delete-UIKB5ZXS.js +67 -0
- package/dist/delete-UIKB5ZXS.js.map +1 -0
- package/dist/demographics-ABZODD45.js +141 -0
- package/dist/demographics-ABZODD45.js.map +1 -0
- package/dist/discard-V3K5PZ4W.js +69 -0
- package/dist/discard-V3K5PZ4W.js.map +1 -0
- package/dist/discover-VM3O7CQM.js +172 -0
- package/dist/domain-A66DHOTQ.js +101 -0
- package/dist/domain-A66DHOTQ.js.map +1 -0
- package/dist/download-LSAZD5JO.js +172 -0
- package/dist/download-LSAZD5JO.js.map +1 -0
- package/dist/draft-5UUJC675.js +335 -0
- package/dist/draft-5UUJC675.js.map +1 -0
- package/dist/draft-BATA5CVI.js +283 -0
- package/dist/draft-BATA5CVI.js.map +1 -0
- package/dist/draft-CRDBU6LF.js +308 -0
- package/dist/draft-P7IDMXWR.js +310 -0
- package/dist/draft-P7IDMXWR.js.map +1 -0
- package/dist/draft-YBNL4ZZA.js +220 -0
- package/dist/draft-YBNL4ZZA.js.map +1 -0
- package/dist/engine/index.js +7 -7
- package/dist/experiments-MLLTHMMS.js +236 -0
- package/dist/experiments-MLLTHMMS.js.map +1 -0
- package/dist/facets-KSHR3MBE.js +117 -0
- package/dist/facets-KSHR3MBE.js.map +1 -0
- package/dist/feedback-4GCPXZSI.js +111 -0
- package/dist/feedback-4GCPXZSI.js.map +1 -0
- package/dist/feedback-record-VEFHUIUJ.js +265 -0
- package/dist/feedback-record-VEFHUIUJ.js.map +1 -0
- package/dist/fetch-42QL56JC.js +179 -0
- package/dist/fetch-42QL56JC.js.map +1 -0
- package/dist/fit-Q5GRVF2O.js +65 -0
- package/dist/flows-M3WBZQET.js +250 -0
- package/dist/flows-M3WBZQET.js.map +1 -0
- package/dist/fonts-GD4YRXQU.js +39 -0
- package/dist/forecast-HT46MFL4.js +121 -0
- package/dist/forecast-HT46MFL4.js.map +1 -0
- package/dist/forecast-ONVS2QAI.js +169 -0
- package/dist/funnel-76BAD64Q.js +397 -0
- package/dist/funnel-76BAD64Q.js.map +1 -0
- package/dist/generate-VBEY3N3L.js +263 -0
- package/dist/get-4KFAO475.js +82 -0
- package/dist/get-4KFAO475.js.map +1 -0
- package/dist/get-QV5NIADE.js +59 -0
- package/dist/get-QV5NIADE.js.map +1 -0
- package/dist/google-7EZP7I6J.js +3267 -0
- package/dist/google-ads-J7WHTJO4.js +198 -0
- package/dist/google-ads-J7WHTJO4.js.map +1 -0
- package/dist/history-EULKWQUS.js +136 -0
- package/dist/history-EULKWQUS.js.map +1 -0
- package/dist/history-FEBJHFHJ.js +69 -0
- package/dist/history-FEBJHFHJ.js.map +1 -0
- package/dist/history-RDVV7KBN.js +102 -0
- package/dist/history-RDVV7KBN.js.map +1 -0
- package/dist/hubspot-A3RQNMYN.js +782 -0
- package/dist/hubspot-A3RQNMYN.js.map +1 -0
- package/dist/ideas-WX7EBPSF.js +45 -0
- package/dist/images-4AXF4LAC.js +92 -0
- package/dist/ingest-AM5YDTRH.js +110 -0
- package/dist/ingest-AM5YDTRH.js.map +1 -0
- package/dist/ingest-C3M4GUO6.js +26 -0
- package/dist/insights-UTCMSWSC.js +132 -0
- package/dist/insights-UTCMSWSC.js.map +1 -0
- package/dist/insights-XZL2EM53.js +439 -0
- package/dist/insights-XZL2EM53.js.map +1 -0
- package/dist/inspect-DWCLRZGR.js +192 -0
- package/dist/inspect-DWCLRZGR.js.map +1 -0
- package/dist/instant-experiences-RM4MLJTL.js +260 -0
- package/dist/instant-experiences-RM4MLJTL.js.map +1 -0
- package/dist/intent-ZMYMFGB4.js +95 -0
- package/dist/keyword-gap-XSJO2R77.js +130 -0
- package/dist/keyword-metrics-M46FCTAS.js +200 -0
- package/dist/keyword-metrics-M46FCTAS.js.map +1 -0
- package/dist/keywords-for-site-XNSLX7EA.js +131 -0
- package/dist/keywords-for-site-XNSLX7EA.js.map +1 -0
- package/dist/landing-MUNYZQNA.js +28 -0
- package/dist/languages-DIQ3OG3E.js +40 -0
- package/dist/lead-routes-XBO2GOBL.js +608 -0
- package/dist/lead-routes-XBO2GOBL.js.map +1 -0
- package/dist/leads-LAATTW5G.js +115 -0
- package/dist/leads-LAATTW5G.js.map +1 -0
- package/dist/leads-ULYHTG22.js +540 -0
- package/dist/leads-ULYHTG22.js.map +1 -0
- package/dist/learn-3GLY4G3H.js +207 -0
- package/dist/learn-3GLY4G3H.js.map +1 -0
- package/dist/line-item-writes-XJTTA3X5.js +211 -0
- package/dist/line-item-writes-XJTTA3X5.js.map +1 -0
- package/dist/line-items-JR25T2C5.js +107 -0
- package/dist/link-7DSLIF3I.js +67 -0
- package/dist/link-7DSLIF3I.js.map +1 -0
- package/dist/linkedin-W5BJ6HEH.js +1316 -0
- package/dist/linkedin-W5BJ6HEH.js.map +1 -0
- package/dist/linkedin-person-2ZYSOHYR.js +139 -0
- package/dist/linkedin-person-2ZYSOHYR.js.map +1 -0
- package/dist/list-3653DIYB.js +67 -0
- package/dist/list-3653DIYB.js.map +1 -0
- package/dist/list-G66HVYCR.js +306 -0
- package/dist/list-G66HVYCR.js.map +1 -0
- package/dist/list-Z4M53MS5.js +154 -0
- package/dist/list-Z4M53MS5.js.map +1 -0
- package/dist/locations-HTVYJSDJ.js +40 -0
- package/dist/locations-M2B5XDU4.js +69 -0
- package/dist/locations-M2B5XDU4.js.map +1 -0
- package/dist/log-2RJU5BOW.js +95 -0
- package/dist/log-2RJU5BOW.js.map +1 -0
- package/dist/lookup-S5VG7AWH.js +23 -0
- package/dist/make-MJ6JMU5T.js +1370 -0
- package/dist/make-MJ6JMU5T.js.map +1 -0
- package/dist/map-27IPOBY6.js +436 -0
- package/dist/map-27IPOBY6.js.map +1 -0
- package/dist/mcp-TFQTME4P.js +717 -0
- package/dist/mcp-TFQTME4P.js.map +1 -0
- package/dist/media-P4T4ZBNS.js +18 -0
- package/dist/media-sources-P246GM7W.js +35 -0
- package/dist/members-KOKBTUC2.js +47 -0
- package/dist/members-KOKBTUC2.js.map +1 -0
- package/dist/meta-IPLWNYC7.js +122 -0
- package/dist/metrics-WYWUX6Y5.js +152 -0
- package/dist/models-HJ7Y55AH.js +155 -0
- package/dist/normalize-GXBUNTYJ.js +144 -0
- package/dist/normalize-GXBUNTYJ.js.map +1 -0
- package/dist/options-MA25RNXF.js +125 -0
- package/dist/options-MA25RNXF.js.map +1 -0
- package/dist/organization-NLEVNGKQ.js +949 -0
- package/dist/organization-NLEVNGKQ.js.map +1 -0
- package/dist/page-photos-OQ5QATQH.js +118 -0
- package/dist/page-photos-OQ5QATQH.js.map +1 -0
- package/dist/performance-34EUP5KL.js +5541 -0
- package/dist/performance-34EUP5KL.js.map +1 -0
- package/dist/performance-J3DLNI2W.js +27 -0
- package/dist/pixels-THEGYRXT.js +62 -0
- package/dist/pixels-THEGYRXT.js.map +1 -0
- package/dist/plan-7UF76Q6U.js +208 -0
- package/dist/plan-7UF76Q6U.js.map +1 -0
- package/dist/preview-LRJOQ7LM.js +218 -0
- package/dist/preview-LRJOQ7LM.js.map +1 -0
- package/dist/promoted-tweet-writes-SBXWKO23.js +183 -0
- package/dist/promoted-tweet-writes-SBXWKO23.js.map +1 -0
- package/dist/promoted-tweets-W42FNWS7.js +119 -0
- package/dist/publish-YG76GOA7.js +344 -0
- package/dist/publish-YG76GOA7.js.map +1 -0
- package/dist/query-IQICLABN.js +62 -0
- package/dist/questionnaire-DWEH5II4.js +94 -0
- package/dist/questionnaire-DWEH5II4.js.map +1 -0
- package/dist/reddit-X4CERXYF.js +29 -0
- package/dist/refresh-A36GA7JN.js +97 -0
- package/dist/refresh-A36GA7JN.js.map +1 -0
- package/dist/release-3NCMZIDI.js +63 -0
- package/dist/release-3NCMZIDI.js.map +1 -0
- package/dist/relevant-pages-UDDQOYLD.js +99 -0
- package/dist/remove-6LE3XNRG.js +53 -0
- package/dist/remove-6LE3XNRG.js.map +1 -0
- package/dist/reopen-XLADJ2UU.js +66 -0
- package/dist/reopen-XLADJ2UU.js.map +1 -0
- package/dist/rerun-QVMFJZA2.js +95 -0
- package/dist/rerun-QVMFJZA2.js.map +1 -0
- package/dist/research-D5UUJF74.js +72 -0
- package/dist/research-QCI5ISUE.js +265 -0
- package/dist/research-QCI5ISUE.js.map +1 -0
- package/dist/reset-DD37KDXQ.js +57 -0
- package/dist/reset-DD37KDXQ.js.map +1 -0
- package/dist/reset-FLZPKY2N.js +57 -0
- package/dist/reset-FLZPKY2N.js.map +1 -0
- package/dist/resolve-KRKIJKT7.js +124 -0
- package/dist/resolve-KRKIJKT7.js.map +1 -0
- package/dist/retry-ALXYEKFV.js +65 -0
- package/dist/retry-ALXYEKFV.js.map +1 -0
- package/dist/retry-MIXKW4EW.js +63 -0
- package/dist/retry-MIXKW4EW.js.map +1 -0
- package/dist/revenue-UBZUTSXN.js +91 -0
- package/dist/revenue-UBZUTSXN.js.map +1 -0
- package/dist/rule-writes-UAVWT4XS.js +525 -0
- package/dist/rule-writes-UAVWT4XS.js.map +1 -0
- package/dist/rules-SO6P44B6.js +99 -0
- package/dist/rules-SO6P44B6.js.map +1 -0
- package/dist/rules-ZVS2MWED.js +217 -0
- package/dist/rules-add-XN3BZKFO.js +465 -0
- package/dist/rules-add-XN3BZKFO.js.map +1 -0
- package/dist/run-K2DOMOAN.js +69 -0
- package/dist/run-K2DOMOAN.js.map +1 -0
- package/dist/run-T7XAH2W5.js +70 -0
- package/dist/scaffold-ad-TWIT7R4U.js +1516 -0
- package/dist/scaffold-ad-TWIT7R4U.js.map +1 -0
- package/dist/scaffold-static-ad-GPJKOGOI.js +1015 -0
- package/dist/scaffold-static-ad-GPJKOGOI.js.map +1 -0
- package/dist/scaffold-video-7BQNOJ4S.js +888 -0
- package/dist/scaffold-video-7BQNOJ4S.js.map +1 -0
- package/dist/schedule-UTRVEWPE.js +64 -0
- package/dist/schedule-UTRVEWPE.js.map +1 -0
- package/dist/scheduled-agents-ZGCUZ6SB.js +55 -0
- package/dist/schema-D6BMZAPB.js +69 -0
- package/dist/schema-DSMF6ELE.js +135 -0
- package/dist/schema-DSMF6ELE.js.map +1 -0
- package/dist/search-2P72GSKY.js +177 -0
- package/dist/search-2P72GSKY.js.map +1 -0
- package/dist/select-N7VWQ2ZQ.js +106 -0
- package/dist/select-N7VWQ2ZQ.js.map +1 -0
- package/dist/sends-LAY5C74B.js +349 -0
- package/dist/sends-LAY5C74B.js.map +1 -0
- package/dist/set-prompt-3N3HMHT6.js +198 -0
- package/dist/set-prompt-3N3HMHT6.js.map +1 -0
- package/dist/setup-links-I5RFZTS4.js +144 -0
- package/dist/setup-links-I5RFZTS4.js.map +1 -0
- package/dist/shopify-LXSR37XI.js +3958 -0
- package/dist/shopify-LXSR37XI.js.map +1 -0
- package/dist/site-AI3BFWRK.js +180 -0
- package/dist/site-AI3BFWRK.js.map +1 -0
- package/dist/sources-AOF2YDYX.js +457 -0
- package/dist/sources-AOF2YDYX.js.map +1 -0
- package/dist/sources-DVMJEXEX.js +30 -0
- package/dist/stale-P4GGOLIB.js +76 -0
- package/dist/stale-P4GGOLIB.js.map +1 -0
- package/dist/status-O25MXHU4.js +65 -0
- package/dist/status-O25MXHU4.js.map +1 -0
- package/dist/studies-EPO2AM4M.js +250 -0
- package/dist/studio-5Z4NMA4K.js +49 -0
- package/dist/study-writes-7FP443QQ.js +470 -0
- package/dist/study-writes-7FP443QQ.js.map +1 -0
- package/dist/sync-2AI7LKKN.js +57 -0
- package/dist/sync-2AI7LKKN.js.map +1 -0
- package/dist/sync-definition-F3CXG6KG.js +69 -0
- package/dist/tags-SEZ2CQJM.js +26 -0
- package/dist/tags-TJDZXOXS.js +227 -0
- package/dist/tags-TJDZXOXS.js.map +1 -0
- package/dist/tags-YSC7ZILI.js +23 -0
- package/dist/tags-YSC7ZILI.js.map +1 -0
- package/dist/tags-Z6EALYOS.js +23 -0
- package/dist/tags-Z6EALYOS.js.map +1 -0
- package/dist/tags-ZXSVW6XO.js +23 -0
- package/dist/tags-ZXSVW6XO.js.map +1 -0
- package/dist/targeting-NKZFEZD7.js +762 -0
- package/dist/targeting-NKZFEZD7.js.map +1 -0
- package/dist/targeting-criteria-AU5CSMAU.js +113 -0
- package/dist/targets-IOIH5JYS.js +106 -0
- package/dist/targets-IOIH5JYS.js.map +1 -0
- package/dist/testimonials-Y6PUV6AL.js +32 -0
- package/dist/themes-PGPIAKAO.js +95 -0
- package/dist/top-companies-FSITN3ZR.js +111 -0
- package/dist/top-companies-FSITN3ZR.js.map +1 -0
- package/dist/transcript-CGPX7Y37.js +165 -0
- package/dist/transcript-CGPX7Y37.js.map +1 -0
- package/dist/translate-QCBMGOKR.js +609 -0
- package/dist/translate-QCBMGOKR.js.map +1 -0
- package/dist/triggers-MONMZSKT.js +144 -0
- package/dist/triggers-MONMZSKT.js.map +1 -0
- package/dist/unlink-I7QFQQKO.js +70 -0
- package/dist/unlink-I7QFQQKO.js.map +1 -0
- package/dist/unstage-JXG5AU5Q.js +88 -0
- package/dist/unstage-JXG5AU5Q.js.map +1 -0
- package/dist/unstage-ZA3WQBEA.js +96 -0
- package/dist/unstage-ZA3WQBEA.js.map +1 -0
- package/dist/update-BVZGM54I.js +66 -0
- package/dist/update-BVZGM54I.js.map +1 -0
- package/dist/update-EI2BHUM2.js +101 -0
- package/dist/update-EI2BHUM2.js.map +1 -0
- package/dist/update-RZSV4NYP.js +155 -0
- package/dist/update-RZSV4NYP.js.map +1 -0
- package/dist/update-WLDNOZ5U.js +192 -0
- package/dist/update-WLDNOZ5U.js.map +1 -0
- package/dist/upload-BP3YEQSF.js +129 -0
- package/dist/upload-BP3YEQSF.js.map +1 -0
- package/dist/url-options-63DED6CR.js +131 -0
- package/dist/url-options-63DED6CR.js.map +1 -0
- package/dist/url-params-TRFI2PMD.js +101 -0
- package/dist/url-params-TRFI2PMD.js.map +1 -0
- package/dist/validate-SNCR25A7.js +122 -0
- package/dist/validate-SNCR25A7.js.map +1 -0
- package/dist/videos-SVZV5YYC.js +41 -0
- package/dist/viewer-check-LUAEGV6H.js +83 -0
- package/dist/web-MJQEK7BB.js +143 -0
- package/dist/web-MJQEK7BB.js.map +1 -0
- package/dist/winning-ads-KNLJHHCO.js +86 -0
- package/dist/write-commands-NVCX7IAU.js +1502 -0
- package/dist/write-commands-NVCX7IAU.js.map +1 -0
- package/dist/write-commands-OGVDWWBV.js +1172 -0
- package/dist/write-commands-OGVDWWBV.js.map +1 -0
- package/dist/x-ZQNHYIDU.js +47 -0
- package/package.json +1 -1
- package/dist/account-UUAEQWO3.js +0 -80
- package/dist/account-UUAEQWO3.js.map +0 -1
- package/dist/account-WLQBAQMD.js +0 -80
- package/dist/account-WLQBAQMD.js.map +0 -1
- package/dist/account-assets-O7IA37RE.js +0 -290
- package/dist/account-level-EU7BWCO7.js +0 -609
- package/dist/account-level-EU7BWCO7.js.map +0 -1
- package/dist/account-reads-SNOWDHHF.js +0 -219
- package/dist/account-users-NV37Z2XJ.js +0 -62
- package/dist/account-users-NV37Z2XJ.js.map +0 -1
- package/dist/account-writes-TU3WJRSI.js +0 -66
- package/dist/account-writes-TU3WJRSI.js.map +0 -1
- package/dist/accounts-MZ6SETJJ.js +0 -78
- package/dist/accounts-MZ6SETJJ.js.map +0 -1
- package/dist/accounts-U7ZBEAY6.js +0 -699
- package/dist/accounts-U7ZBEAY6.js.map +0 -1
- package/dist/actions-4JYHXRFH.js +0 -55
- package/dist/activecampaign-IDBV26HH.js +0 -936
- package/dist/activecampaign-IDBV26HH.js.map +0 -1
- package/dist/ad-group-writes-HLBWN4E4.js +0 -170
- package/dist/ad-group-writes-HLBWN4E4.js.map +0 -1
- package/dist/ad-groups-33NZD6J3.js +0 -68
- package/dist/ad-groups-33NZD6J3.js.map +0 -1
- package/dist/ad-writes-7ZDNDZ2E.js +0 -174
- package/dist/ad-writes-7ZDNDZ2E.js.map +0 -1
- package/dist/add-AF42LQQQ.js +0 -105
- package/dist/add-AF42LQQQ.js.map +0 -1
- package/dist/add-LJRZYJ7I.js +0 -303
- package/dist/add-LJRZYJ7I.js.map +0 -1
- package/dist/ads-KBE5JXCH.js +0 -115
- package/dist/ads-LXWXOQHX.js +0 -48
- package/dist/ads-X3AM6M7L.js +0 -77
- package/dist/ads-X3AM6M7L.js.map +0 -1
- package/dist/adsets-YWBJFPAZ.js +0 -182
- package/dist/advertisers-6FOCN2W3.js +0 -121
- package/dist/advertisers-6FOCN2W3.js.map +0 -1
- package/dist/analytics-64HBRJJE.js +0 -2435
- package/dist/analytics-64HBRJJE.js.map +0 -1
- package/dist/analytics-VY5NL2GL.js +0 -445
- package/dist/analytics-VY5NL2GL.js.map +0 -1
- package/dist/animate-2BMHZ4FM.js +0 -480
- package/dist/asset-writes-72DEVXGO.js +0 -48
- package/dist/assign-N2I2BM67.js +0 -78
- package/dist/assign-N2I2BM67.js.map +0 -1
- package/dist/audience-size-H5DEZXJN.js +0 -74
- package/dist/audience-size-H5DEZXJN.js.map +0 -1
- package/dist/audience-writes-KCHLNFSI.js +0 -325
- package/dist/audience-writes-KCHLNFSI.js.map +0 -1
- package/dist/audiences-QAH42HIM.js +0 -86
- package/dist/audiences-WCDWN64L.js +0 -268
- package/dist/audiences-WCDWN64L.js.map +0 -1
- package/dist/audit-2AAF6AOX.js +0 -152
- package/dist/audit-2AAF6AOX.js.map +0 -1
- package/dist/autocomplete-3GSKRNCZ.js +0 -112
- package/dist/autocomplete-3GSKRNCZ.js.map +0 -1
- package/dist/avatars-N6W6HUPP.js +0 -38
- package/dist/bid-pricing-IMITXHXN.js +0 -101
- package/dist/bid-pricing-IMITXHXN.js.map +0 -1
- package/dist/brand-ITWQVZD2.js +0 -22
- package/dist/brief-JJNEYPYB.js +0 -541
- package/dist/brief-JJNEYPYB.js.map +0 -1
- package/dist/campaign-groups-TTQLP32Q.js +0 -97
- package/dist/campaign-groups-TTQLP32Q.js.map +0 -1
- package/dist/campaign-writes-MAKPHQ3V.js +0 -166
- package/dist/campaign-writes-MAKPHQ3V.js.map +0 -1
- package/dist/campaign-writes-XO74BALZ.js +0 -174
- package/dist/campaign-writes-XO74BALZ.js.map +0 -1
- package/dist/campaigns-5BIDXYSR.js +0 -111
- package/dist/campaigns-LS65QM6W.js +0 -76
- package/dist/campaigns-LS65QM6W.js.map +0 -1
- package/dist/campaigns-XZOQ2VTK.js +0 -130
- package/dist/campaigns-XZPXCS7I.js +0 -111
- package/dist/campaigns-XZPXCS7I.js.map +0 -1
- package/dist/canvas-SL3RMOY5.js +0 -46
- package/dist/capabilities-LSR5GCE6.js +0 -145
- package/dist/capabilities-LSR5GCE6.js.map +0 -1
- package/dist/catalog-TZLU5NWI.js +0 -65
- package/dist/catalogs-JLCSFZYC.js +0 -779
- package/dist/catalogs-JLCSFZYC.js.map +0 -1
- package/dist/catalogs-T23RKYFC.js +0 -128
- package/dist/catalogs-T23RKYFC.js.map +0 -1
- package/dist/changes-36C754R4.js +0 -135
- package/dist/chatgpt-3WFOPURY.js +0 -47
- package/dist/chats-GGYTXNJS.js +0 -277
- package/dist/chats-GGYTXNJS.js.map +0 -1
- package/dist/check-P7UPBBG7.js +0 -105
- package/dist/check-P7UPBBG7.js.map +0 -1
- package/dist/chunk-2TOIY2WN.js +0 -4254
- package/dist/chunk-2TOIY2WN.js.map +0 -1
- package/dist/chunk-3YJNSUNF.js +0 -131
- package/dist/chunk-4D46PS6J.js +0 -344
- package/dist/chunk-4GAEB6NK.js +0 -228
- package/dist/chunk-5XTGG4PV.js +0 -274
- package/dist/chunk-6JCX3YLM.js +0 -249
- package/dist/chunk-6KWEF6HF.js +0 -8914
- package/dist/chunk-7HZSORAB.js +0 -843
- package/dist/chunk-7J3NZBPV.js +0 -2982
- package/dist/chunk-7J3NZBPV.js.map +0 -1
- package/dist/chunk-A3IQAONL.js +0 -638
- package/dist/chunk-ALMYGH5U.js +0 -479
- package/dist/chunk-AQKRSEBQ.js +0 -103
- package/dist/chunk-ASRS37QE.js +0 -710
- package/dist/chunk-ASRS37QE.js.map +0 -1
- package/dist/chunk-BL6RTOK3.js +0 -297
- package/dist/chunk-C6CBBZBO.js +0 -203
- package/dist/chunk-CCEDZTP7.js +0 -391
- package/dist/chunk-CCEDZTP7.js.map +0 -1
- package/dist/chunk-CKQTJ2LV.js +0 -89
- package/dist/chunk-D3KLDIPQ.js +0 -45
- package/dist/chunk-DL2UJNJ7.js +0 -8954
- package/dist/chunk-DQ54POZN.js +0 -363
- package/dist/chunk-EDLZGTGN.js +0 -216
- package/dist/chunk-EDLZGTGN.js.map +0 -1
- package/dist/chunk-EXSHBYNQ.js +0 -349
- package/dist/chunk-EXSHBYNQ.js.map +0 -1
- package/dist/chunk-F4YQ5PVR.js +0 -1518
- package/dist/chunk-FFFJBNHA.js +0 -63
- package/dist/chunk-FFG2OYVV.js +0 -64
- package/dist/chunk-FKQK22IX.js +0 -51
- package/dist/chunk-FLK6QHOY.js +0 -142
- package/dist/chunk-GACSA5A6.js +0 -1646
- package/dist/chunk-HJKYTNAZ.js +0 -61
- package/dist/chunk-HML3VQIW.js +0 -225
- package/dist/chunk-INUU3752.js +0 -392
- package/dist/chunk-IQHO2ABF.js +0 -197
- package/dist/chunk-J3UWMBOP.js +0 -547
- package/dist/chunk-JAC6JVCI.js +0 -26
- package/dist/chunk-JDR3HUEL.js +0 -191
- package/dist/chunk-JI77AKT6.js +0 -19
- package/dist/chunk-JNURZHZJ.js +0 -942
- package/dist/chunk-JSNMKTFM.js +0 -592
- package/dist/chunk-K4FLWDEX.js +0 -122
- package/dist/chunk-KBRJSPIM.js +0 -676
- package/dist/chunk-LAC6YHHX.js +0 -97
- package/dist/chunk-LRV6M2WB.js +0 -1919
- package/dist/chunk-M7HD42M6.js +0 -24017
- package/dist/chunk-M7HD42M6.js.map +0 -1
- package/dist/chunk-MCJ5I5LR.js +0 -3187
- package/dist/chunk-MCJ5I5LR.js.map +0 -1
- package/dist/chunk-MGYC2IRC.js +0 -783
- package/dist/chunk-MI4HXE7M.js +0 -23
- package/dist/chunk-MJ7RNDD4.js +0 -910
- package/dist/chunk-MVD7QIXI.js +0 -46
- package/dist/chunk-MZPS46OR.js +0 -76
- package/dist/chunk-OBJCNGZL.js +0 -207
- package/dist/chunk-OJGYYH6K.js +0 -179
- package/dist/chunk-OLBLXJNI.js +0 -224
- package/dist/chunk-PFTYKINE.js +0 -207
- package/dist/chunk-PLBITHV6.js +0 -846
- package/dist/chunk-PX3PUDK7.js +0 -167
- package/dist/chunk-Q6JMMBSQ.js +0 -831
- package/dist/chunk-RAQHUMKS.js +0 -169
- package/dist/chunk-SA22KXWT.js +0 -163
- package/dist/chunk-SIXU5PDF.js +0 -66
- package/dist/chunk-SWN6OHXG.js +0 -148
- package/dist/chunk-SXMK5AEJ.js +0 -494
- package/dist/chunk-TTLD66NI.js +0 -319
- package/dist/chunk-TVAIOC77.js +0 -287
- package/dist/chunk-TVTSA4EN.js +0 -188
- package/dist/chunk-TX7CQTD4.js +0 -5368
- package/dist/chunk-UKSSNYLU.js +0 -98
- package/dist/chunk-V5NHJU3M.js +0 -604
- package/dist/chunk-VIPKDXGU.js +0 -4229
- package/dist/chunk-VIPKDXGU.js.map +0 -1
- package/dist/chunk-W4D7C74B.js +0 -387
- package/dist/chunk-WF25CCLD.js +0 -170
- package/dist/chunk-WQPUX3BA.js +0 -275
- package/dist/chunk-WR3FR3K3.js +0 -156
- package/dist/chunk-XDNJC62G.js +0 -91
- package/dist/chunk-XE6N4SZI.js +0 -429
- package/dist/chunk-XGPU2VDU.js +0 -219
- package/dist/chunk-XUJZO7MZ.js +0 -57
- package/dist/chunk-Y67LUJBE.js +0 -455
- package/dist/chunk-Y7XSDJEA.js +0 -3868
- package/dist/chunk-YNYAT2TZ.js +0 -285
- package/dist/chunk-ZNNQK366.js +0 -7198
- package/dist/chunk-ZNNQK366.js.map +0 -1
- package/dist/chunk-ZNRGH4NS.js +0 -150
- package/dist/claim-4KUETSW3.js +0 -198
- package/dist/claim-4KUETSW3.js.map +0 -1
- package/dist/companies-XFO6WPVT.js +0 -196
- package/dist/companies-XFO6WPVT.js.map +0 -1
- package/dist/company-OJGISG7K.js +0 -306
- package/dist/company-OJGISG7K.js.map +0 -1
- package/dist/complete-CXOST5J3.js +0 -82
- package/dist/complete-CXOST5J3.js.map +0 -1
- package/dist/connect-HPQ32KGV.js +0 -43
- package/dist/conversation-D5HKCTDA.js +0 -94
- package/dist/conversation-D5HKCTDA.js.map +0 -1
- package/dist/conversion-report-DKXFHLLX.js +0 -125
- package/dist/conversion-report-DKXFHLLX.js.map +0 -1
- package/dist/conversion-upload-command-KTYAY7IG.js +0 -439
- package/dist/conversion-writes-7ODJDAUP.js +0 -120
- package/dist/conversion-writes-7ODJDAUP.js.map +0 -1
- package/dist/conversions-BKVGNNC6.js +0 -76
- package/dist/conversions-BKVGNNC6.js.map +0 -1
- package/dist/conversions-SYNDEB6D.js +0 -138
- package/dist/conversions-SYNDEB6D.js.map +0 -1
- package/dist/copy-writes-P4WK6OPM.js +0 -168
- package/dist/copy-writes-P4WK6OPM.js.map +0 -1
- package/dist/create-7D4VD4TC.js +0 -208
- package/dist/create-7D4VD4TC.js.map +0 -1
- package/dist/create-7QDEAWZS.js +0 -133
- package/dist/create-7QDEAWZS.js.map +0 -1
- package/dist/create-IQSYEC4M.js +0 -60
- package/dist/create-IQSYEC4M.js.map +0 -1
- package/dist/create-TQSCM7MG.js +0 -237
- package/dist/create-TQSCM7MG.js.map +0 -1
- package/dist/creatives-JPKA2RHU.js +0 -89
- package/dist/creatives-RNOL4F4E.js +0 -58
- package/dist/creatives-RXI27T2D.js +0 -221
- package/dist/creatives-RXI27T2D.js.map +0 -1
- package/dist/credentials-PYW55B52.js +0 -288
- package/dist/credentials-PYW55B52.js.map +0 -1
- package/dist/critique-2SYHNAW5.js +0 -1449
- package/dist/critique-2SYHNAW5.js.map +0 -1
- package/dist/critique-72GFZSHA.js +0 -230
- package/dist/critique-72GFZSHA.js.map +0 -1
- package/dist/crm-EATDSXXB.js +0 -225
- package/dist/crm-EATDSXXB.js.map +0 -1
- package/dist/crm-XXP3VBYD.js +0 -321
- package/dist/crm-XXP3VBYD.js.map +0 -1
- package/dist/currency-R4ORE2U3.js +0 -88
- package/dist/custom-conversions-5HJBB5XL.js +0 -128
- package/dist/customers-OFZTEXST.js +0 -57
- package/dist/delete-FGVZIK4R.js +0 -55
- package/dist/delete-FGVZIK4R.js.map +0 -1
- package/dist/delete-HBZZL4UA.js +0 -67
- package/dist/delete-HBZZL4UA.js.map +0 -1
- package/dist/delete-JOUXMRIX.js +0 -65
- package/dist/delete-JOUXMRIX.js.map +0 -1
- package/dist/demographics-SB7NBYOI.js +0 -139
- package/dist/demographics-SB7NBYOI.js.map +0 -1
- package/dist/discard-YYGIRDII.js +0 -67
- package/dist/discard-YYGIRDII.js.map +0 -1
- package/dist/discover-GSJRZUOC.js +0 -172
- package/dist/domain-3KRAJAUH.js +0 -99
- package/dist/domain-3KRAJAUH.js.map +0 -1
- package/dist/download-QCBKJ6OR.js +0 -170
- package/dist/download-QCBKJ6OR.js.map +0 -1
- package/dist/draft-7CGFFZTH.js +0 -333
- package/dist/draft-7CGFFZTH.js.map +0 -1
- package/dist/draft-BI4NW6AU.js +0 -308
- package/dist/draft-BI4NW6AU.js.map +0 -1
- package/dist/draft-LPVK6YHI.js +0 -281
- package/dist/draft-LPVK6YHI.js.map +0 -1
- package/dist/draft-QF5JEUTH.js +0 -218
- package/dist/draft-QF5JEUTH.js.map +0 -1
- package/dist/draft-WYDMLNYA.js +0 -308
- package/dist/experiments-ZCEYRP3H.js +0 -234
- package/dist/experiments-ZCEYRP3H.js.map +0 -1
- package/dist/facets-G7ZDPI5Q.js +0 -115
- package/dist/facets-G7ZDPI5Q.js.map +0 -1
- package/dist/feedback-3U3CL2VB.js +0 -109
- package/dist/feedback-3U3CL2VB.js.map +0 -1
- package/dist/feedback-record-4ZHPENIP.js +0 -263
- package/dist/feedback-record-4ZHPENIP.js.map +0 -1
- package/dist/fetch-PNVNNWTP.js +0 -177
- package/dist/fetch-PNVNNWTP.js.map +0 -1
- package/dist/fit-53DGYRFD.js +0 -63
- package/dist/flows-JXLQBP5Q.js +0 -248
- package/dist/flows-JXLQBP5Q.js.map +0 -1
- package/dist/fonts-LFMBUT4R.js +0 -37
- package/dist/forecast-4AG5F7YC.js +0 -169
- package/dist/forecast-VZAXI74O.js +0 -119
- package/dist/forecast-VZAXI74O.js.map +0 -1
- package/dist/funnel-EACC35UD.js +0 -395
- package/dist/funnel-EACC35UD.js.map +0 -1
- package/dist/generate-NWHPM2GQ.js +0 -263
- package/dist/get-AXBVYGAC.js +0 -57
- package/dist/get-AXBVYGAC.js.map +0 -1
- package/dist/get-D6A27Q4E.js +0 -80
- package/dist/get-D6A27Q4E.js.map +0 -1
- package/dist/google-NAA4BASO.js +0 -3267
- package/dist/google-ads-OIYUNDXQ.js +0 -196
- package/dist/google-ads-OIYUNDXQ.js.map +0 -1
- package/dist/history-5MRLNTCI.js +0 -67
- package/dist/history-5MRLNTCI.js.map +0 -1
- package/dist/history-IDYNIB6R.js +0 -134
- package/dist/history-IDYNIB6R.js.map +0 -1
- package/dist/history-IOUKN3WD.js +0 -100
- package/dist/history-IOUKN3WD.js.map +0 -1
- package/dist/hubspot-HL6WGC4G.js +0 -780
- package/dist/hubspot-HL6WGC4G.js.map +0 -1
- package/dist/ideas-NK3KQHAK.js +0 -43
- package/dist/images-2XS3JT7E.js +0 -92
- package/dist/ingest-DHXWZZX4.js +0 -24
- package/dist/ingest-FGTJRCWK.js +0 -108
- package/dist/ingest-FGTJRCWK.js.map +0 -1
- package/dist/insights-M3CYB22P.js +0 -437
- package/dist/insights-M3CYB22P.js.map +0 -1
- package/dist/insights-XCT3ONC7.js +0 -130
- package/dist/insights-XCT3ONC7.js.map +0 -1
- package/dist/inspect-53GGYRC4.js +0 -190
- package/dist/inspect-53GGYRC4.js.map +0 -1
- package/dist/instant-experiences-HT3HB4QZ.js +0 -258
- package/dist/instant-experiences-HT3HB4QZ.js.map +0 -1
- package/dist/intent-NSHW7EJT.js +0 -95
- package/dist/keyword-gap-ITTWKBWZ.js +0 -130
- package/dist/keyword-metrics-LWPE3HHW.js +0 -198
- package/dist/keyword-metrics-LWPE3HHW.js.map +0 -1
- package/dist/keywords-for-site-5K25VJ35.js +0 -129
- package/dist/keywords-for-site-5K25VJ35.js.map +0 -1
- package/dist/landing-QONUUP3C.js +0 -28
- package/dist/languages-MEFQQBJ2.js +0 -40
- package/dist/leads-FSSISJGL.js +0 -113
- package/dist/leads-FSSISJGL.js.map +0 -1
- package/dist/leads-SZOGCQFK.js +0 -538
- package/dist/leads-SZOGCQFK.js.map +0 -1
- package/dist/learn-FWWJWBDN.js +0 -205
- package/dist/learn-FWWJWBDN.js.map +0 -1
- package/dist/line-item-writes-A3UQ44NG.js +0 -209
- package/dist/line-item-writes-A3UQ44NG.js.map +0 -1
- package/dist/line-items-OH426JGT.js +0 -107
- package/dist/link-67E4NVEX.js +0 -65
- package/dist/link-67E4NVEX.js.map +0 -1
- package/dist/linkedin-CT2OSQ2I.js +0 -1314
- package/dist/linkedin-CT2OSQ2I.js.map +0 -1
- package/dist/linkedin-person-OSNLPLJJ.js +0 -137
- package/dist/linkedin-person-OSNLPLJJ.js.map +0 -1
- package/dist/list-J4OUXAKN.js +0 -65
- package/dist/list-J4OUXAKN.js.map +0 -1
- package/dist/list-L2OQK4W3.js +0 -304
- package/dist/list-L2OQK4W3.js.map +0 -1
- package/dist/list-LHMDAA2D.js +0 -152
- package/dist/list-LHMDAA2D.js.map +0 -1
- package/dist/locations-RLWAS6AC.js +0 -40
- package/dist/locations-UKBOT6SB.js +0 -67
- package/dist/locations-UKBOT6SB.js.map +0 -1
- package/dist/log-4EIV54V4.js +0 -93
- package/dist/log-4EIV54V4.js.map +0 -1
- package/dist/lookup-ZJIP2M4X.js +0 -22
- package/dist/make-MFU3CSN7.js +0 -1368
- package/dist/make-MFU3CSN7.js.map +0 -1
- package/dist/map-VWWWMQLK.js +0 -434
- package/dist/map-VWWWMQLK.js.map +0 -1
- package/dist/mcp-EKLO2VD7.js +0 -715
- package/dist/mcp-EKLO2VD7.js.map +0 -1
- package/dist/media-ZWN7E777.js +0 -18
- package/dist/media-sources-7L622UTF.js +0 -35
- package/dist/members-QK5M5NCJ.js +0 -45
- package/dist/members-QK5M5NCJ.js.map +0 -1
- package/dist/meta-EHKXH257.js +0 -122
- package/dist/metrics-K6FDCMK7.js +0 -152
- package/dist/models-JQEPSPNQ.js +0 -155
- package/dist/normalize-WI4OGKH5.js +0 -142
- package/dist/normalize-WI4OGKH5.js.map +0 -1
- package/dist/options-FKJ2QXI6.js +0 -123
- package/dist/options-FKJ2QXI6.js.map +0 -1
- package/dist/organization-7A7MPUDG.js +0 -947
- package/dist/organization-7A7MPUDG.js.map +0 -1
- package/dist/page-photos-HWKQOSCU.js +0 -116
- package/dist/page-photos-HWKQOSCU.js.map +0 -1
- package/dist/performance-K4VB6L6Z.js +0 -25
- package/dist/performance-LHAL53YO.js +0 -5539
- package/dist/performance-LHAL53YO.js.map +0 -1
- package/dist/pixels-Z7ZGXAGB.js +0 -60
- package/dist/pixels-Z7ZGXAGB.js.map +0 -1
- package/dist/plan-TVQSEKYL.js +0 -207
- package/dist/plan-TVQSEKYL.js.map +0 -1
- package/dist/preview-CWRGDFCP.js +0 -216
- package/dist/preview-CWRGDFCP.js.map +0 -1
- package/dist/promoted-tweet-writes-V4ZVJOJU.js +0 -181
- package/dist/promoted-tweet-writes-V4ZVJOJU.js.map +0 -1
- package/dist/promoted-tweets-KPG4IUVD.js +0 -119
- package/dist/publish-Y465CVUD.js +0 -342
- package/dist/publish-Y465CVUD.js.map +0 -1
- package/dist/query-QM7MBQD7.js +0 -62
- package/dist/questionnaire-LEZJLMAV.js +0 -93
- package/dist/questionnaire-LEZJLMAV.js.map +0 -1
- package/dist/reddit-Z7DOVIGR.js +0 -27
- package/dist/refresh-A6TW2Z2F.js +0 -95
- package/dist/refresh-A6TW2Z2F.js.map +0 -1
- package/dist/release-UFESJ4TM.js +0 -61
- package/dist/release-UFESJ4TM.js.map +0 -1
- package/dist/relevant-pages-VOICMQ7U.js +0 -99
- package/dist/remove-YONNYA4G.js +0 -51
- package/dist/remove-YONNYA4G.js.map +0 -1
- package/dist/reopen-FYJU2IPO.js +0 -64
- package/dist/reopen-FYJU2IPO.js.map +0 -1
- package/dist/rerun-B5SBG6ZE.js +0 -93
- package/dist/rerun-B5SBG6ZE.js.map +0 -1
- package/dist/research-HW4EVOZU.js +0 -72
- package/dist/research-OV53DIBX.js +0 -263
- package/dist/research-OV53DIBX.js.map +0 -1
- package/dist/reset-R4SIQXFH.js +0 -55
- package/dist/reset-R4SIQXFH.js.map +0 -1
- package/dist/reset-X6ODAKYH.js +0 -55
- package/dist/reset-X6ODAKYH.js.map +0 -1
- package/dist/resolve-DNRJCLCR.js +0 -122
- package/dist/resolve-DNRJCLCR.js.map +0 -1
- package/dist/retry-D5MKBHUB.js +0 -63
- package/dist/retry-D5MKBHUB.js.map +0 -1
- package/dist/retry-DVFLE7JY.js +0 -61
- package/dist/retry-DVFLE7JY.js.map +0 -1
- package/dist/revenue-QGI6XWSK.js +0 -89
- package/dist/revenue-QGI6XWSK.js.map +0 -1
- package/dist/rule-writes-S3RN47OG.js +0 -523
- package/dist/rule-writes-S3RN47OG.js.map +0 -1
- package/dist/rules-KTJ2ZZ62.js +0 -217
- package/dist/rules-TR6BAC23.js +0 -97
- package/dist/rules-TR6BAC23.js.map +0 -1
- package/dist/rules-add-T5ZO5RLR.js +0 -463
- package/dist/rules-add-T5ZO5RLR.js.map +0 -1
- package/dist/run-WZ5IXWA4.js +0 -67
- package/dist/run-WZ5IXWA4.js.map +0 -1
- package/dist/run-YRR3KOUU.js +0 -68
- package/dist/scaffold-ad-DNUCGCIH.js +0 -1514
- package/dist/scaffold-ad-DNUCGCIH.js.map +0 -1
- package/dist/scaffold-static-ad-VR65EZAG.js +0 -1013
- package/dist/scaffold-static-ad-VR65EZAG.js.map +0 -1
- package/dist/scaffold-video-JLMKCCWE.js +0 -886
- package/dist/scaffold-video-JLMKCCWE.js.map +0 -1
- package/dist/schedule-VRHCVBZA.js +0 -62
- package/dist/schedule-VRHCVBZA.js.map +0 -1
- package/dist/scheduled-agents-DG7FTCUZ.js +0 -55
- package/dist/schema-6Y3IXFDC.js +0 -69
- package/dist/schema-WZKDWF5N.js +0 -133
- package/dist/schema-WZKDWF5N.js.map +0 -1
- package/dist/search-MN66QIKJ.js +0 -175
- package/dist/search-MN66QIKJ.js.map +0 -1
- package/dist/select-IPSM2BF2.js +0 -104
- package/dist/select-IPSM2BF2.js.map +0 -1
- package/dist/sends-6FI7WELX.js +0 -348
- package/dist/sends-6FI7WELX.js.map +0 -1
- package/dist/set-prompt-N6BD3DME.js +0 -196
- package/dist/set-prompt-N6BD3DME.js.map +0 -1
- package/dist/setup-links-GTA2AROS.js +0 -142
- package/dist/setup-links-GTA2AROS.js.map +0 -1
- package/dist/shopify-S4TG6WJH.js +0 -3956
- package/dist/shopify-S4TG6WJH.js.map +0 -1
- package/dist/site-NY622E3H.js +0 -178
- package/dist/site-NY622E3H.js.map +0 -1
- package/dist/sources-4F2XO7Z6.js +0 -455
- package/dist/sources-4F2XO7Z6.js.map +0 -1
- package/dist/sources-562ITR77.js +0 -30
- package/dist/stale-JKM6MXJF.js +0 -74
- package/dist/stale-JKM6MXJF.js.map +0 -1
- package/dist/status-AUWMUDA4.js +0 -63
- package/dist/status-AUWMUDA4.js.map +0 -1
- package/dist/studies-ZOEUY6YP.js +0 -250
- package/dist/studio-T5L7UEX6.js +0 -49
- package/dist/study-writes-DTGMCNFP.js +0 -468
- package/dist/study-writes-DTGMCNFP.js.map +0 -1
- package/dist/sync-76MCGHX4.js +0 -55
- package/dist/sync-76MCGHX4.js.map +0 -1
- package/dist/sync-definition-NJPT6ABG.js +0 -67
- package/dist/tags-5KBOJJ5R.js +0 -26
- package/dist/tags-CSMLTHTU.js +0 -21
- package/dist/tags-CSMLTHTU.js.map +0 -1
- package/dist/tags-IFRZYM3G.js +0 -21
- package/dist/tags-IFRZYM3G.js.map +0 -1
- package/dist/tags-S44ADJ75.js +0 -21
- package/dist/tags-S44ADJ75.js.map +0 -1
- package/dist/tags-WR7IY3OT.js +0 -225
- package/dist/tags-WR7IY3OT.js.map +0 -1
- package/dist/targeting-VBQY3HUN.js +0 -760
- package/dist/targeting-VBQY3HUN.js.map +0 -1
- package/dist/targeting-criteria-FJWNK53T.js +0 -113
- package/dist/targets-2EHMMXD4.js +0 -104
- package/dist/targets-2EHMMXD4.js.map +0 -1
- package/dist/testimonials-WAPFIGFJ.js +0 -32
- package/dist/themes-B4LCQUXB.js +0 -95
- package/dist/top-companies-DJ2XQKMW.js +0 -109
- package/dist/top-companies-DJ2XQKMW.js.map +0 -1
- package/dist/transcript-J3LGOHCM.js +0 -163
- package/dist/transcript-J3LGOHCM.js.map +0 -1
- package/dist/translate-6RXO4C6Z.js +0 -607
- package/dist/translate-6RXO4C6Z.js.map +0 -1
- package/dist/triggers-CYXPNHQB.js +0 -142
- package/dist/triggers-CYXPNHQB.js.map +0 -1
- package/dist/unlink-5FJI7XZB.js +0 -68
- package/dist/unlink-5FJI7XZB.js.map +0 -1
- package/dist/unstage-54ZE2SZK.js +0 -86
- package/dist/unstage-54ZE2SZK.js.map +0 -1
- package/dist/unstage-MDSSPUKV.js +0 -95
- package/dist/unstage-MDSSPUKV.js.map +0 -1
- package/dist/update-BVBFNFMO.js +0 -99
- package/dist/update-BVBFNFMO.js.map +0 -1
- package/dist/update-GV3T7ZRC.js +0 -190
- package/dist/update-GV3T7ZRC.js.map +0 -1
- package/dist/update-PUQWUGPJ.js +0 -64
- package/dist/update-PUQWUGPJ.js.map +0 -1
- package/dist/update-YQBK6C3L.js +0 -153
- package/dist/update-YQBK6C3L.js.map +0 -1
- package/dist/upload-PXT5JAEV.js +0 -127
- package/dist/upload-PXT5JAEV.js.map +0 -1
- package/dist/url-options-IHL4OU4C.js +0 -130
- package/dist/url-options-IHL4OU4C.js.map +0 -1
- package/dist/url-params-IYUI6SFJ.js +0 -99
- package/dist/url-params-IYUI6SFJ.js.map +0 -1
- package/dist/validate-AD7F7LQ5.js +0 -120
- package/dist/validate-AD7F7LQ5.js.map +0 -1
- package/dist/videos-ZJCTODEB.js +0 -41
- package/dist/viewer-check-GWZNGWCO.js +0 -81
- package/dist/web-76CWAZNW.js +0 -141
- package/dist/web-76CWAZNW.js.map +0 -1
- package/dist/winning-ads-W7GTXTX7.js +0 -86
- package/dist/write-commands-K6M3RTVK.js +0 -1170
- package/dist/write-commands-K6M3RTVK.js.map +0 -1
- package/dist/write-commands-VPU53DLB.js +0 -1498
- package/dist/write-commands-VPU53DLB.js.map +0 -1
- package/dist/x-V4NNNWRU.js +0 -47
- /package/dist/{account-assets-O7IA37RE.js.map → account-assets-J5Z4URLQ.js.map} +0 -0
- /package/dist/{account-reads-SNOWDHHF.js.map → account-reads-FOEVPWDP.js.map} +0 -0
- /package/dist/{actions-4JYHXRFH.js.map → actions-QZDSLFFK.js.map} +0 -0
- /package/dist/{ads-LXWXOQHX.js.map → ads-AYJAYLFR.js.map} +0 -0
- /package/dist/{ads-KBE5JXCH.js.map → ads-MAOI7S6V.js.map} +0 -0
- /package/dist/{adsets-YWBJFPAZ.js.map → adsets-QRKICEFD.js.map} +0 -0
- /package/dist/{animate-2BMHZ4FM.js.map → animate-HRWFVE2N.js.map} +0 -0
- /package/dist/{asset-writes-72DEVXGO.js.map → asset-writes-JLVUAVUZ.js.map} +0 -0
- /package/dist/{audiences-QAH42HIM.js.map → audiences-CDDI5HR5.js.map} +0 -0
- /package/dist/{avatars-N6W6HUPP.js.map → avatars-4HCGWBOL.js.map} +0 -0
- /package/dist/{brand-ITWQVZD2.js.map → brand-C6OB6Q4U.js.map} +0 -0
- /package/dist/{campaigns-XZOQ2VTK.js.map → campaigns-HES5MDUG.js.map} +0 -0
- /package/dist/{campaigns-5BIDXYSR.js.map → campaigns-ZYU4BXDP.js.map} +0 -0
- /package/dist/{canvas-SL3RMOY5.js.map → canvas-AI34UBGU.js.map} +0 -0
- /package/dist/{catalog-TZLU5NWI.js.map → catalog-ZY3MFT55.js.map} +0 -0
- /package/dist/{changes-36C754R4.js.map → changes-OOUVQDBY.js.map} +0 -0
- /package/dist/{chatgpt-3WFOPURY.js.map → chatgpt-NAXIFWXV.js.map} +0 -0
- /package/dist/{chunk-TTLD66NI.js.map → chunk-3CHYJYSS.js.map} +0 -0
- /package/dist/{chunk-XE6N4SZI.js.map → chunk-3G3GC5N4.js.map} +0 -0
- /package/dist/{chunk-5XTGG4PV.js.map → chunk-3M45PYU7.js.map} +0 -0
- /package/dist/{chunk-MI4HXE7M.js.map → chunk-3QHXF3QI.js.map} +0 -0
- /package/dist/{chunk-Y7XSDJEA.js.map → chunk-3XPHUMLJ.js.map} +0 -0
- /package/dist/{chunk-MGYC2IRC.js.map → chunk-45EERHDB.js.map} +0 -0
- /package/dist/{chunk-W4D7C74B.js.map → chunk-4FGTANFK.js.map} +0 -0
- /package/dist/{chunk-PX3PUDK7.js.map → chunk-4IHWT7PH.js.map} +0 -0
- /package/dist/{chunk-PLBITHV6.js.map → chunk-4LF4AYPT.js.map} +0 -0
- /package/dist/{chunk-XGPU2VDU.js.map → chunk-4UQRXKYJ.js.map} +0 -0
- /package/dist/{chunk-Y67LUJBE.js.map → chunk-5JJD33Y2.js.map} +0 -0
- /package/dist/{chunk-Q6JMMBSQ.js.map → chunk-5K4QYYOJ.js.map} +0 -0
- /package/dist/{chunk-SA22KXWT.js.map → chunk-5LJ6E4YU.js.map} +0 -0
- /package/dist/{chunk-WQPUX3BA.js.map → chunk-5T73MMBL.js.map} +0 -0
- /package/dist/{chunk-DQ54POZN.js.map → chunk-64MAHLTK.js.map} +0 -0
- /package/dist/{chunk-JAC6JVCI.js.map → chunk-6OIKNJ7G.js.map} +0 -0
- /package/dist/{chunk-3YJNSUNF.js.map → chunk-6ZFSDYWV.js.map} +0 -0
- /package/dist/{chunk-TVAIOC77.js.map → chunk-7BUIOP36.js.map} +0 -0
- /package/dist/{chunk-K4FLWDEX.js.map → chunk-AN4OEQXE.js.map} +0 -0
- /package/dist/{chunk-TX7CQTD4.js.map → chunk-AULSX5ZA.js.map} +0 -0
- /package/dist/{chunk-MVD7QIXI.js.map → chunk-AVYUFJQQ.js.map} +0 -0
- /package/dist/{chunk-LAC6YHHX.js.map → chunk-B4YF7VQF.js.map} +0 -0
- /package/dist/{chunk-UKSSNYLU.js.map → chunk-BDOYNGUL.js.map} +0 -0
- /package/dist/{chunk-PFTYKINE.js.map → chunk-BJOK7QD4.js.map} +0 -0
- /package/dist/{chunk-IQHO2ABF.js.map → chunk-CMCGFSUS.js.map} +0 -0
- /package/dist/{chunk-KBRJSPIM.js.map → chunk-DMJUIUAL.js.map} +0 -0
- /package/dist/{chunk-HML3VQIW.js.map → chunk-DUCUOZLS.js.map} +0 -0
- /package/dist/{chunk-7HZSORAB.js.map → chunk-FJZYOVSW.js.map} +0 -0
- /package/dist/{chunk-JI77AKT6.js.map → chunk-FKP4ECJY.js.map} +0 -0
- /package/dist/{chunk-DL2UJNJ7.js.map → chunk-GNMEOU46.js.map} +0 -0
- /package/dist/{chunk-WR3FR3K3.js.map → chunk-GYKYXWUQ.js.map} +0 -0
- /package/dist/{chunk-BL6RTOK3.js.map → chunk-GYPLRU4Y.js.map} +0 -0
- /package/dist/{chunk-YNYAT2TZ.js.map → chunk-I43ZEWJQ.js.map} +0 -0
- /package/dist/{chunk-4GAEB6NK.js.map → chunk-IZABTTW5.js.map} +0 -0
- /package/dist/{chunk-GACSA5A6.js.map → chunk-K7IE6UYV.js.map} +0 -0
- /package/dist/{chunk-V5NHJU3M.js.map → chunk-KH5YHHJV.js.map} +0 -0
- /package/dist/{chunk-FKQK22IX.js.map → chunk-KMY7AD3N.js.map} +0 -0
- /package/dist/{chunk-6KWEF6HF.js.map → chunk-KPCHWDJM.js.map} +0 -0
- /package/dist/{chunk-SWN6OHXG.js.map → chunk-KTZCLJ2D.js.map} +0 -0
- /package/dist/{chunk-A3IQAONL.js.map → chunk-MKUA54QE.js.map} +0 -0
- /package/dist/{chunk-FFFJBNHA.js.map → chunk-MTV2RSGY.js.map} +0 -0
- /package/dist/{chunk-INUU3752.js.map → chunk-N2QX57K3.js.map} +0 -0
- /package/dist/{chunk-OJGYYH6K.js.map → chunk-NB2FU6EZ.js.map} +0 -0
- /package/dist/{chunk-ALMYGH5U.js.map → chunk-NEYWS2DQ.js.map} +0 -0
- /package/dist/{chunk-TVTSA4EN.js.map → chunk-NWF2LRKO.js.map} +0 -0
- /package/dist/{chunk-JDR3HUEL.js.map → chunk-OTC6UMJ2.js.map} +0 -0
- /package/dist/{chunk-OLBLXJNI.js.map → chunk-OUY2IAWL.js.map} +0 -0
- /package/dist/{chunk-4D46PS6J.js.map → chunk-PJXNCRMR.js.map} +0 -0
- /package/dist/{chunk-XDNJC62G.js.map → chunk-PYQ3GF7R.js.map} +0 -0
- /package/dist/{chunk-WF25CCLD.js.map → chunk-Q3EIVZAR.js.map} +0 -0
- /package/dist/{chunk-OBJCNGZL.js.map → chunk-QDQZ62FL.js.map} +0 -0
- /package/dist/{chunk-LRV6M2WB.js.map → chunk-QHZE62GP.js.map} +0 -0
- /package/dist/{chunk-AQKRSEBQ.js.map → chunk-RGR6TSTJ.js.map} +0 -0
- /package/dist/{chunk-C6CBBZBO.js.map → chunk-SAAXU7QR.js.map} +0 -0
- /package/dist/{chunk-SIXU5PDF.js.map → chunk-SHTKLRI6.js.map} +0 -0
- /package/dist/{chunk-MZPS46OR.js.map → chunk-SIKCVWM3.js.map} +0 -0
- /package/dist/{chunk-CKQTJ2LV.js.map → chunk-SSXG53CZ.js.map} +0 -0
- /package/dist/{chunk-J3UWMBOP.js.map → chunk-TBWAM577.js.map} +0 -0
- /package/dist/{chunk-RAQHUMKS.js.map → chunk-TGPGQTZA.js.map} +0 -0
- /package/dist/{chunk-F4YQ5PVR.js.map → chunk-UMIX2BMM.js.map} +0 -0
- /package/dist/{chunk-ZNRGH4NS.js.map → chunk-URMAEVZO.js.map} +0 -0
- /package/dist/{chunk-HJKYTNAZ.js.map → chunk-VBO3ZVUU.js.map} +0 -0
- /package/dist/{chunk-JSNMKTFM.js.map → chunk-VFLYMHTO.js.map} +0 -0
- /package/dist/{chunk-6JCX3YLM.js.map → chunk-VGYIVXIZ.js.map} +0 -0
- /package/dist/{chunk-XUJZO7MZ.js.map → chunk-VIB7R533.js.map} +0 -0
- /package/dist/{chunk-D3KLDIPQ.js.map → chunk-WLESMH4J.js.map} +0 -0
- /package/dist/{chunk-MJ7RNDD4.js.map → chunk-WVWLPXBO.js.map} +0 -0
- /package/dist/{chunk-SXMK5AEJ.js.map → chunk-XEH5XAP6.js.map} +0 -0
- /package/dist/{chunk-FLK6QHOY.js.map → chunk-XEOKVL4E.js.map} +0 -0
- /package/dist/{chunk-FFG2OYVV.js.map → chunk-XOGQJYXH.js.map} +0 -0
- /package/dist/{chunk-JNURZHZJ.js.map → chunk-XUXRA7IB.js.map} +0 -0
- /package/dist/{connect-HPQ32KGV.js.map → connect-IAXSZPGA.js.map} +0 -0
- /package/dist/{conversion-upload-command-KTYAY7IG.js.map → conversion-upload-command-IHFKVX2M.js.map} +0 -0
- /package/dist/{creatives-JPKA2RHU.js.map → creatives-BB6S2OF6.js.map} +0 -0
- /package/dist/{creatives-RNOL4F4E.js.map → creatives-BZEVJ6ER.js.map} +0 -0
- /package/dist/{currency-R4ORE2U3.js.map → currency-CGKO5GKH.js.map} +0 -0
- /package/dist/{custom-conversions-5HJBB5XL.js.map → custom-conversions-HAGG72LF.js.map} +0 -0
- /package/dist/{customers-OFZTEXST.js.map → customers-NKKU6S7G.js.map} +0 -0
- /package/dist/{discover-GSJRZUOC.js.map → discover-VM3O7CQM.js.map} +0 -0
- /package/dist/{draft-WYDMLNYA.js.map → draft-CRDBU6LF.js.map} +0 -0
- /package/dist/{fit-53DGYRFD.js.map → fit-Q5GRVF2O.js.map} +0 -0
- /package/dist/{fonts-LFMBUT4R.js.map → fonts-GD4YRXQU.js.map} +0 -0
- /package/dist/{forecast-4AG5F7YC.js.map → forecast-ONVS2QAI.js.map} +0 -0
- /package/dist/{generate-NWHPM2GQ.js.map → generate-VBEY3N3L.js.map} +0 -0
- /package/dist/{google-NAA4BASO.js.map → google-7EZP7I6J.js.map} +0 -0
- /package/dist/{ideas-NK3KQHAK.js.map → ideas-WX7EBPSF.js.map} +0 -0
- /package/dist/{images-2XS3JT7E.js.map → images-4AXF4LAC.js.map} +0 -0
- /package/dist/{ingest-DHXWZZX4.js.map → ingest-C3M4GUO6.js.map} +0 -0
- /package/dist/{intent-NSHW7EJT.js.map → intent-ZMYMFGB4.js.map} +0 -0
- /package/dist/{keyword-gap-ITTWKBWZ.js.map → keyword-gap-XSJO2R77.js.map} +0 -0
- /package/dist/{landing-QONUUP3C.js.map → landing-MUNYZQNA.js.map} +0 -0
- /package/dist/{languages-MEFQQBJ2.js.map → languages-DIQ3OG3E.js.map} +0 -0
- /package/dist/{line-items-OH426JGT.js.map → line-items-JR25T2C5.js.map} +0 -0
- /package/dist/{locations-RLWAS6AC.js.map → locations-HTVYJSDJ.js.map} +0 -0
- /package/dist/{lookup-ZJIP2M4X.js.map → lookup-S5VG7AWH.js.map} +0 -0
- /package/dist/{media-ZWN7E777.js.map → media-P4T4ZBNS.js.map} +0 -0
- /package/dist/{media-sources-7L622UTF.js.map → media-sources-P246GM7W.js.map} +0 -0
- /package/dist/{meta-EHKXH257.js.map → meta-IPLWNYC7.js.map} +0 -0
- /package/dist/{metrics-K6FDCMK7.js.map → metrics-WYWUX6Y5.js.map} +0 -0
- /package/dist/{models-JQEPSPNQ.js.map → models-HJ7Y55AH.js.map} +0 -0
- /package/dist/{performance-K4VB6L6Z.js.map → performance-J3DLNI2W.js.map} +0 -0
- /package/dist/{promoted-tweets-KPG4IUVD.js.map → promoted-tweets-W42FNWS7.js.map} +0 -0
- /package/dist/{query-QM7MBQD7.js.map → query-IQICLABN.js.map} +0 -0
- /package/dist/{reddit-Z7DOVIGR.js.map → reddit-X4CERXYF.js.map} +0 -0
- /package/dist/{relevant-pages-VOICMQ7U.js.map → relevant-pages-UDDQOYLD.js.map} +0 -0
- /package/dist/{research-HW4EVOZU.js.map → research-D5UUJF74.js.map} +0 -0
- /package/dist/{rules-KTJ2ZZ62.js.map → rules-ZVS2MWED.js.map} +0 -0
- /package/dist/{run-YRR3KOUU.js.map → run-T7XAH2W5.js.map} +0 -0
- /package/dist/{scheduled-agents-DG7FTCUZ.js.map → scheduled-agents-ZGCUZ6SB.js.map} +0 -0
- /package/dist/{schema-6Y3IXFDC.js.map → schema-D6BMZAPB.js.map} +0 -0
- /package/dist/{sources-562ITR77.js.map → sources-DVMJEXEX.js.map} +0 -0
- /package/dist/{studies-ZOEUY6YP.js.map → studies-EPO2AM4M.js.map} +0 -0
- /package/dist/{studio-T5L7UEX6.js.map → studio-5Z4NMA4K.js.map} +0 -0
- /package/dist/{sync-definition-NJPT6ABG.js.map → sync-definition-F3CXG6KG.js.map} +0 -0
- /package/dist/{tags-5KBOJJ5R.js.map → tags-SEZ2CQJM.js.map} +0 -0
- /package/dist/{targeting-criteria-FJWNK53T.js.map → targeting-criteria-AU5CSMAU.js.map} +0 -0
- /package/dist/{testimonials-WAPFIGFJ.js.map → testimonials-Y6PUV6AL.js.map} +0 -0
- /package/dist/{themes-B4LCQUXB.js.map → themes-PGPIAKAO.js.map} +0 -0
- /package/dist/{videos-ZJCTODEB.js.map → videos-SVZV5YYC.js.map} +0 -0
- /package/dist/{viewer-check-GWZNGWCO.js.map → viewer-check-LUAEGV6H.js.map} +0 -0
- /package/dist/{winning-ads-W7GTXTX7.js.map → winning-ads-KNLJHHCO.js.map} +0 -0
- /package/dist/{x-V4NNNWRU.js.map → x-ZQNHYIDU.js.map} +0 -0
|
@@ -0,0 +1,3958 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SHOPIFY_COLLECTION_COUNT_CAP,
|
|
3
|
+
SHOPIFY_NOT_CONNECTED_MESSAGE,
|
|
4
|
+
SHOPIFY_NOT_OFFERED_MESSAGE,
|
|
5
|
+
SHOPIFY_ORDERS_IMPORTING_MESSAGE,
|
|
6
|
+
SHOPIFY_PRODUCT_NOT_FOUND_PREFIX,
|
|
7
|
+
SHOPIFY_STOCK_COUNT_CAP,
|
|
8
|
+
shopifyStoreStateSchema,
|
|
9
|
+
withEveryOption
|
|
10
|
+
} from "./chunk-EKX37C3T.js";
|
|
11
|
+
import {
|
|
12
|
+
landingGraphFiles
|
|
13
|
+
} from "./chunk-Z3ENMCFE.js";
|
|
14
|
+
import {
|
|
15
|
+
failValidation,
|
|
16
|
+
loadJsonArg,
|
|
17
|
+
loadJsonPayloads
|
|
18
|
+
} from "./chunk-GU5MXV2Y.js";
|
|
19
|
+
import "./chunk-D5FY4FAK.js";
|
|
20
|
+
import {
|
|
21
|
+
termsRefusalFix
|
|
22
|
+
} from "./chunk-Y2JZX6JB.js";
|
|
23
|
+
import "./chunk-ZRP7Y2DM.js";
|
|
24
|
+
import {
|
|
25
|
+
CHAT_READ_ARG
|
|
26
|
+
} from "./chunk-PFBQERWX.js";
|
|
27
|
+
import {
|
|
28
|
+
failApi
|
|
29
|
+
} from "./chunk-SAAXU7QR.js";
|
|
30
|
+
import "./chunk-NZ34KM46.js";
|
|
31
|
+
import "./chunk-CAZGM2NX.js";
|
|
32
|
+
import "./chunk-NZS3KEQ2.js";
|
|
33
|
+
import "./chunk-GIWHHCD6.js";
|
|
34
|
+
import "./chunk-SOPVIIUW.js";
|
|
35
|
+
import "./chunk-G4FG5EQO.js";
|
|
36
|
+
import "./chunk-NSBNIPIW.js";
|
|
37
|
+
import "./chunk-MUS2UTWU.js";
|
|
38
|
+
import {
|
|
39
|
+
ApiError,
|
|
40
|
+
apiGet,
|
|
41
|
+
apiPost
|
|
42
|
+
} from "./chunk-X7VQFLHI.js";
|
|
43
|
+
import "./chunk-LU4HI4KI.js";
|
|
44
|
+
import {
|
|
45
|
+
optionalChatId,
|
|
46
|
+
requireChatId,
|
|
47
|
+
resolveChatId
|
|
48
|
+
} from "./chunk-LGK7UWBN.js";
|
|
49
|
+
import {
|
|
50
|
+
registerSchema,
|
|
51
|
+
writeJson
|
|
52
|
+
} from "./chunk-Q7US4OOA.js";
|
|
53
|
+
import "./chunk-YQSBHWOI.js";
|
|
54
|
+
|
|
55
|
+
// src/commands/shopify/index.ts
|
|
56
|
+
import { defineCommand as defineCommand10 } from "citty";
|
|
57
|
+
|
|
58
|
+
// ../api/src/shopify/audiences.ts
|
|
59
|
+
import { z } from "zod";
|
|
60
|
+
var SHOPIFY_AUDIENCE_SEGMENTS = ["best", "loyal", "at_risk", "new", "one_time", "all"];
|
|
61
|
+
var shopifyAudienceSegmentSchema = z.enum(SHOPIFY_AUDIENCE_SEGMENTS);
|
|
62
|
+
var shopifyAudiencePlatformSchema = z.enum(["meta", "google"]);
|
|
63
|
+
var SHOPIFY_AUDIENCE_PLATFORM_LABEL = {
|
|
64
|
+
meta: "Meta",
|
|
65
|
+
google: "Google Ads"
|
|
66
|
+
};
|
|
67
|
+
var shopifyAudienceConsentSchema = z.enum(["marketing_subscribed"]);
|
|
68
|
+
var shopifyAudienceRefreshSchema = z.enum(["daily", "off"]);
|
|
69
|
+
var accountIdSchema = z.string().trim().regex(/^(act_)?\d+$|^\d{3}-?\d{3}-?\d{4}$/, "an ad account id: act_123\u2026 for Meta, 123-456-7890 for Google Ads");
|
|
70
|
+
var shopifyAudienceNameSchema = z.string().trim().min(1).max(120);
|
|
71
|
+
var shopifyAudienceDefinitionSchema = z.object({
|
|
72
|
+
segment: shopifyAudienceSegmentSchema,
|
|
73
|
+
platform: shopifyAudiencePlatformSchema,
|
|
74
|
+
accountId: accountIdSchema,
|
|
75
|
+
/** The audience's name on the platform; default "Shopify: <segment label>". */
|
|
76
|
+
name: shopifyAudienceNameSchema.optional(),
|
|
77
|
+
consent: shopifyAudienceConsentSchema.optional(),
|
|
78
|
+
refresh: shopifyAudienceRefreshSchema.optional()
|
|
79
|
+
}).strict();
|
|
80
|
+
var shopifyAudienceRemovalSchema = z.object({
|
|
81
|
+
/** Leave the audience on the ad platform (with whoever it holds) and only stop updating it. */
|
|
82
|
+
keepOnPlatform: z.boolean().optional()
|
|
83
|
+
}).strict();
|
|
84
|
+
var shopifyAudienceCountsSchema = z.object({
|
|
85
|
+
people: z.number(),
|
|
86
|
+
withConsent: z.number(),
|
|
87
|
+
willSend: z.number(),
|
|
88
|
+
emails: z.number(),
|
|
89
|
+
phones: z.number(),
|
|
90
|
+
largestCountry: z.object({ country: z.string(), people: z.number() }).nullable()
|
|
91
|
+
});
|
|
92
|
+
var GOOGLE_CUSTOMER_LIST_MIN_ACTIVE = 100;
|
|
93
|
+
var META_CUSTOMER_LIST_RECOMMENDED = 1e3;
|
|
94
|
+
var META_LOOKALIKE_MIN_PER_COUNTRY = 100;
|
|
95
|
+
var shopifyAudienceCreateRequestSchema = z.object({
|
|
96
|
+
chatId: z.string(),
|
|
97
|
+
definition: shopifyAudienceDefinitionSchema
|
|
98
|
+
});
|
|
99
|
+
var shopifyAudienceCreateResponseSchema = z.object({
|
|
100
|
+
ref: z.string(),
|
|
101
|
+
staged: z.literal(true),
|
|
102
|
+
summary: z.string(),
|
|
103
|
+
platform: shopifyAudiencePlatformSchema,
|
|
104
|
+
accountId: z.string(),
|
|
105
|
+
counts: shopifyAudienceCountsSchema,
|
|
106
|
+
warnings: z.array(z.string())
|
|
107
|
+
});
|
|
108
|
+
var shopifyAudienceStatusSchema = z.enum(["active", "stopped", "removed"]);
|
|
109
|
+
var shopifyAudienceRunSchema = z.object({
|
|
110
|
+
at: z.number(),
|
|
111
|
+
outcome: z.enum(["sent", "waiting", "failed"]),
|
|
112
|
+
people: z.number(),
|
|
113
|
+
withConsent: z.number(),
|
|
114
|
+
sent: z.number(),
|
|
115
|
+
/** How many of those sent the platform said it took in, when it says (Meta's `num_received`). */
|
|
116
|
+
received: z.number().optional(),
|
|
117
|
+
/** What the platform said it could not use, when it says (Meta). */
|
|
118
|
+
invalid: z.number().optional(),
|
|
119
|
+
/** On a waiting run: when the people in `sent` were sent. */
|
|
120
|
+
sentAt: z.number().optional(),
|
|
121
|
+
error: z.string().optional(),
|
|
122
|
+
/** Google's Data Manager request id(s), comma-joined, for reading its processing report (`requestStatus:retrieve`). */
|
|
123
|
+
requestId: z.string().optional(),
|
|
124
|
+
/**
|
|
125
|
+
* Google only: what Google finally made of what was sent, from its processing report, read from
|
|
126
|
+
* 30 minutes after the run and backing off up to 24 hours. `processing` until then; `unknown` when
|
|
127
|
+
* Google never finished or could not be read in that time. `refused` people Google could not use,
|
|
128
|
+
* `reason` in plain words, `codes` Google's own, `checkedAt` the last read.
|
|
129
|
+
*/
|
|
130
|
+
processing: z.object({
|
|
131
|
+
status: z.enum(["processing", "success", "partial", "failed", "unknown"]),
|
|
132
|
+
checkedAt: z.number(),
|
|
133
|
+
records: z.number().optional(),
|
|
134
|
+
refused: z.number().optional(),
|
|
135
|
+
reason: z.string().optional(),
|
|
136
|
+
codes: z.array(z.string()).optional()
|
|
137
|
+
}).optional()
|
|
138
|
+
});
|
|
139
|
+
var shopifyAudienceViewSchema = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
name: z.string(),
|
|
142
|
+
segment: shopifyAudienceSegmentSchema,
|
|
143
|
+
platform: shopifyAudiencePlatformSchema,
|
|
144
|
+
accountId: z.string(),
|
|
145
|
+
platformAudienceId: z.string(),
|
|
146
|
+
refresh: shopifyAudienceRefreshSchema,
|
|
147
|
+
status: shopifyAudienceStatusSchema,
|
|
148
|
+
stoppedBecause: z.string().optional(),
|
|
149
|
+
lastRun: shopifyAudienceRunSchema.optional(),
|
|
150
|
+
/** Full only. */
|
|
151
|
+
createdAt: z.number().optional(),
|
|
152
|
+
consent: shopifyAudienceConsentSchema.optional()
|
|
153
|
+
});
|
|
154
|
+
var flag = z.enum(["true", "false"]).transform((value) => value === "true");
|
|
155
|
+
var shopifyAudienceListRequestSchema = z.object({
|
|
156
|
+
chatId: z.string().optional(),
|
|
157
|
+
full: flag.optional()
|
|
158
|
+
});
|
|
159
|
+
var shopifyAudienceListResponseSchema = z.object({
|
|
160
|
+
audiences: z.array(shopifyAudienceViewSchema),
|
|
161
|
+
/** This chat's staged audience changes, not yet published. */
|
|
162
|
+
staged: z.array(z.object({ ref: z.string(), kind: z.string(), summary: z.string() }))
|
|
163
|
+
});
|
|
164
|
+
var shopifyAudienceRemoveRequestSchema = z.object({
|
|
165
|
+
chatId: z.string(),
|
|
166
|
+
id: z.string().min(1),
|
|
167
|
+
keepOnPlatform: z.boolean().optional()
|
|
168
|
+
});
|
|
169
|
+
var shopifyAudienceRemoveResponseSchema = z.object({
|
|
170
|
+
ref: z.string(),
|
|
171
|
+
staged: z.literal(true),
|
|
172
|
+
summary: z.string()
|
|
173
|
+
});
|
|
174
|
+
var shopifyAudienceRefreshRequestSchema = z.object({ id: z.string().min(1) });
|
|
175
|
+
var shopifyAudienceRefreshResponseSchema = z.object({
|
|
176
|
+
id: z.string(),
|
|
177
|
+
status: z.literal("running")
|
|
178
|
+
});
|
|
179
|
+
var SHOPIFY_AUDIENCE_PROTECTED_DATA_PREFIX = "Shopify hasn't approved Baker to read buyers' emails and phones";
|
|
180
|
+
|
|
181
|
+
// src/commands/shopify/audiences.ts
|
|
182
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
183
|
+
|
|
184
|
+
// src/commands/shopify/audienceHints.ts
|
|
185
|
+
var LOOKALIKE_MIN = META_LOOKALIKE_MIN_PER_COUNTRY;
|
|
186
|
+
function sizeHint(answer) {
|
|
187
|
+
const sends = answer.counts.willSend;
|
|
188
|
+
if (sends === 0) return void 0;
|
|
189
|
+
const floor = answer.platform === "google" ? sends < GOOGLE_CUSTOMER_LIST_MIN_ACTIVE && `Google shows ads to a customer list only once at least ${GOOGLE_CUSTOMER_LIST_MIN_ACTIVE} of its people are matched and active` : sends < META_CUSTOMER_LIST_RECOMMENDED && `Meta recommends a customer list of at least ${META_CUSTOMER_LIST_RECOMMENDED.toLocaleString("en-US")} people`;
|
|
190
|
+
if (!floor) return void 0;
|
|
191
|
+
return `${floor}; this sends ${sends}, so it may not show ads yet \u2014 don't call it enough to reach them. The platform shows the matched size once the list is uploaded. Don't offer a bigger segment as the fix unless \`baker shopify segments\` shows one with that many buyers (only those who accept marketing are sent); otherwise say the list grows as more people buy and subscribe.`;
|
|
192
|
+
}
|
|
193
|
+
function audienceCreateHints(answer) {
|
|
194
|
+
const hints = [
|
|
195
|
+
`Staged on this chat until Publish: nothing is sent before the user publishes it. Tell them the audience will hold ${answer.counts.willSend} of the ${answer.counts.people} buyers in this group \u2014 only people who accept marketing.`
|
|
196
|
+
];
|
|
197
|
+
const size = sizeHint(answer);
|
|
198
|
+
if (size) hints.push(size);
|
|
199
|
+
const largest = answer.counts.largestCountry;
|
|
200
|
+
if (answer.platform === "meta") {
|
|
201
|
+
if (!largest || largest.people < LOOKALIKE_MIN) {
|
|
202
|
+
hints.push(
|
|
203
|
+
`A lookalike needs at least ${LOOKALIKE_MIN} people from one country in its source; this sends ${largest ? `${largest.people} from ${largest.country}` : "nobody with a known country"}. Don't stage a lookalike of it yet \u2014 it grows as more buyers subscribe.`
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
hints.push(
|
|
207
|
+
`To reach people like them, stage a lookalike of this audience once it is published: \`baker ads meta audiences create --subtype LOOKALIKE\` with the audience's id from \`baker shopify audiences list\` (country ${largest.country}).`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return hints;
|
|
212
|
+
}
|
|
213
|
+
function audienceListHints(answer) {
|
|
214
|
+
const hints = [];
|
|
215
|
+
if (answer.audiences.length === 0 && answer.staged.length === 0) {
|
|
216
|
+
hints.push(
|
|
217
|
+
"No buyer audience yet. Stage one with `baker shopify audiences create --segment <best|loyal|at_risk|new|one_time|all> --to meta|google --account-id <id>`."
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
for (const audience of answer.audiences) {
|
|
221
|
+
if (audience.status !== "active") continue;
|
|
222
|
+
if (audience.lastRun?.outcome === "waiting") {
|
|
223
|
+
const platform = SHOPIFY_AUDIENCE_PLATFORM_LABEL[audience.platform];
|
|
224
|
+
const next = audience.refresh === "daily" ? "Baker updates it at its next daily update" : `this audience has no daily update, so run \`baker shopify audiences refresh ${audience.id}\` again tomorrow, not sooner`;
|
|
225
|
+
hints.push(
|
|
226
|
+
`"${audience.name}" was not updated last time because ${platform} is still processing its previous update (${platform} says that can take up to 24 hours). Nothing failed and it keeps the people it already holds; ${next}. Say so in plain words and don't retry it now \u2014 it would be refused the same way.`
|
|
227
|
+
);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const processing = processingHint(audience);
|
|
231
|
+
if (processing) {
|
|
232
|
+
hints.push(processing);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (audience.lastRun?.outcome === "failed") {
|
|
236
|
+
hints.push(
|
|
237
|
+
`"${audience.name}" did not update last time: ${(audience.lastRun.error ?? "the ad platform refused").replace(/\.$/, "")}. Say so in plain words; \`baker shopify audiences refresh ${audience.id}\` tries again.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return hints;
|
|
242
|
+
}
|
|
243
|
+
var TERMS_CODES = ["PROCESSING_ERROR_REASON_DESTINATION_ACCOUNT_ENHANCED_CONVERSIONS_TERMS_NOT_SIGNED"];
|
|
244
|
+
var GOOGLE_SIDE_CODES = [
|
|
245
|
+
"PROCESSING_ERROR_REASON_INTERNAL_ERROR",
|
|
246
|
+
"PROCESSING_ERROR_REASON_DESTINATION_TOO_RECENTLY_CREATED"
|
|
247
|
+
];
|
|
248
|
+
function processingFix(audience, codes) {
|
|
249
|
+
const refresh = `\`baker shopify audiences refresh ${audience.id}\``;
|
|
250
|
+
if (codes.some((code) => TERMS_CODES.includes(code))) {
|
|
251
|
+
return `Run \`baker ads terms --platform google --account-id ${audience.accountId}\` and walk the user through its steps; once they are done, ${refresh} sends the people again.`;
|
|
252
|
+
}
|
|
253
|
+
if (codes.length > 0 && codes.every((code) => GOOGLE_SIDE_CODES.includes(code))) {
|
|
254
|
+
return audience.refresh === "daily" ? "Nothing for the user to fix: the next daily update sends them again." : `Nothing for the user to fix: ${refresh} sends them again.`;
|
|
255
|
+
}
|
|
256
|
+
return `Once the cause is fixed, ${refresh} sends them again.`;
|
|
257
|
+
}
|
|
258
|
+
function processingHint(audience) {
|
|
259
|
+
const run = audience.lastRun;
|
|
260
|
+
const processing = run?.outcome === "sent" ? run.processing : void 0;
|
|
261
|
+
if (!processing) return void 0;
|
|
262
|
+
const reason = (processing.reason ?? "Google did not say why").replace(/\.$/, "");
|
|
263
|
+
const codes = processing.codes ?? [];
|
|
264
|
+
if (processing.status === "failed") {
|
|
265
|
+
return `"${audience.name}": Google later refused everyone sent: ${reason}. Tell the user in plain words that Google holds none of the people from the last update, and why. ${processingFix(audience, codes)}`;
|
|
266
|
+
}
|
|
267
|
+
if (processing.status === "partial") {
|
|
268
|
+
const share = processing.refused !== void 0 && processing.records !== void 0 ? `${processing.refused} of the ${processing.records} people sent` : "some of the people sent";
|
|
269
|
+
return `"${audience.name}": Google later refused ${share}: ${reason}. Tell the user the rest are on the list and why those were not taken. ${processingFix(audience, codes)}`;
|
|
270
|
+
}
|
|
271
|
+
if (processing.status === "unknown") {
|
|
272
|
+
return `"${audience.name}": Baker can't confirm Google added the people from the last update (${reason}). Tell the user it is unconfirmed, not failed; Google Ads shows the list's size.`;
|
|
273
|
+
}
|
|
274
|
+
return void 0;
|
|
275
|
+
}
|
|
276
|
+
function audienceRefusal(code, message) {
|
|
277
|
+
if (code !== "FORBIDDEN") return null;
|
|
278
|
+
if (message.startsWith(SHOPIFY_AUDIENCE_PROTECTED_DATA_PREFIX)) {
|
|
279
|
+
return {
|
|
280
|
+
code: "SHOPIFY_CUSTOMER_DATA_NOT_APPROVED",
|
|
281
|
+
fix: {
|
|
282
|
+
action: "continue_without",
|
|
283
|
+
explanation: "Shopify hasn't approved Baker to read buyers' emails and phones, so no buyer audience can be made for this store yet; the user cannot change that from the chat. Say so plainly, offer an audience of the store's purchasers seen on the website instead (`baker ads meta audiences create --subtype WEBSITE`), and finish the rest of the task."
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return termsRefusalFix(code, message);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ../api/src/shopify/buyers.ts
|
|
291
|
+
import { z as z2 } from "zod";
|
|
292
|
+
var flag2 = z2.enum(["true", "false"]).transform((value) => value === "true");
|
|
293
|
+
var SHOPIFY_STORE_WINDOW_MAX_DAYS = 3650;
|
|
294
|
+
var days = z2.coerce.number().int().min(1).max(SHOPIFY_STORE_WINDOW_MAX_DAYS);
|
|
295
|
+
var countryCode = z2.string().regex(/^[A-Za-z]{2}$/, "a two-letter country code, like DE").transform((value) => value.toUpperCase());
|
|
296
|
+
var limit = z2.coerce.number().int().min(1).max(100);
|
|
297
|
+
var windowRequest = {
|
|
298
|
+
days: days.optional(),
|
|
299
|
+
/** Every order the store has. */
|
|
300
|
+
all: flag2.optional(),
|
|
301
|
+
country: countryCode.optional(),
|
|
302
|
+
full: flag2.optional()
|
|
303
|
+
};
|
|
304
|
+
var shopifyStoreWindowSchema = z2.object({
|
|
305
|
+
from: z2.string().nullable(),
|
|
306
|
+
to: z2.string(),
|
|
307
|
+
days: z2.union([z2.number(), z2.literal("all")]),
|
|
308
|
+
timezone: z2.string()
|
|
309
|
+
});
|
|
310
|
+
var shopifyBuyerHistorySchema = z2.object({
|
|
311
|
+
state: z2.enum(["importing", "done", "failed", "none"]),
|
|
312
|
+
reach: z2.enum(["all", "60_days"]).nullable(),
|
|
313
|
+
/** The day of the oldest order Baker holds (YYYY-MM-DD). */
|
|
314
|
+
from: z2.string().nullable(),
|
|
315
|
+
/** Orders brought in so far. */
|
|
316
|
+
orders: z2.number()
|
|
317
|
+
});
|
|
318
|
+
var shopifyBuyerBasisSchema = z2.enum(["all_orders", "last_60_days"]);
|
|
319
|
+
var answerCommon = {
|
|
320
|
+
store: shopifyStoreStateSchema,
|
|
321
|
+
window: shopifyStoreWindowSchema,
|
|
322
|
+
/** The store's currency; null when it has no orders yet. */
|
|
323
|
+
currency: z2.string().nullable(),
|
|
324
|
+
/** Orders in another currency (the store changed currency), never added to these. */
|
|
325
|
+
otherCurrencies: z2.array(z2.object({ currency: z2.string(), orders: z2.number(), net: z2.number() })).optional(),
|
|
326
|
+
history: shopifyBuyerHistorySchema,
|
|
327
|
+
basis: shopifyBuyerBasisSchema
|
|
328
|
+
};
|
|
329
|
+
var shopifyPlaceSchema = z2.object({
|
|
330
|
+
country: z2.string(),
|
|
331
|
+
countryName: z2.string(),
|
|
332
|
+
region: z2.string(),
|
|
333
|
+
city: z2.string()
|
|
334
|
+
});
|
|
335
|
+
var shopifyBuyerSegmentSchema = z2.enum(["best", "loyal", "at_risk", "new", "one_time"]);
|
|
336
|
+
var shopifySegmentThresholdsSchema = z2.object({
|
|
337
|
+
/** The store's typical days between a repeat buyer's orders (90 with too few to go on). */
|
|
338
|
+
gapDays: z2.number(),
|
|
339
|
+
/** The lifetime net that puts a buyer in the top fifth. */
|
|
340
|
+
netP80: z2.number()
|
|
341
|
+
});
|
|
342
|
+
var shopifySegmentRowSchema = z2.object({
|
|
343
|
+
segment: shopifyBuyerSegmentSchema,
|
|
344
|
+
label: z2.string(),
|
|
345
|
+
/** The rule in words, with this store's numbers. */
|
|
346
|
+
rule: z2.string(),
|
|
347
|
+
buyers: z2.number(),
|
|
348
|
+
/** Of every buyer, 0–1. */
|
|
349
|
+
share: z2.number(),
|
|
350
|
+
net: z2.number(),
|
|
351
|
+
averageOrders: z2.number(),
|
|
352
|
+
averageDaysSinceLastOrder: z2.number(),
|
|
353
|
+
topCountries: z2.array(z2.object({ code: z2.string(), name: z2.string(), buyers: z2.number() })),
|
|
354
|
+
/** The products their first orders held most often. */
|
|
355
|
+
topFirstProducts: z2.array(z2.object({ title: z2.string(), buyers: z2.number(), productId: z2.string().optional() }))
|
|
356
|
+
});
|
|
357
|
+
var shopifySegmentsRequestSchema = z2.object({ ...windowRequest });
|
|
358
|
+
var shopifySegmentsResponseSchema = z2.object({
|
|
359
|
+
...answerCommon,
|
|
360
|
+
thresholds: shopifySegmentThresholdsSchema,
|
|
361
|
+
buyers: z2.number(),
|
|
362
|
+
segments: z2.array(shopifySegmentRowSchema)
|
|
363
|
+
});
|
|
364
|
+
var shopifyBuyersSortSchema = z2.enum(["net", "orders", "recent", "first"]);
|
|
365
|
+
var shopifyAcquisitionBySchema = z2.enum(["campaign", "channel"]);
|
|
366
|
+
var shopifyBuyersRequestSchema = z2.object({
|
|
367
|
+
...windowRequest,
|
|
368
|
+
segment: shopifyBuyerSegmentSchema.optional(),
|
|
369
|
+
/** Lifetime value by the campaign or channel of each buyer's first order. */
|
|
370
|
+
by: shopifyAcquisitionBySchema.optional(),
|
|
371
|
+
/** Only buyers whose first order came through this channel (a key from `by=channel`). */
|
|
372
|
+
channel: z2.string().max(200).optional(),
|
|
373
|
+
sort: shopifyBuyersSortSchema.optional(),
|
|
374
|
+
limit: limit.optional(),
|
|
375
|
+
offset: z2.coerce.number().int().min(0).max(1e5).optional()
|
|
376
|
+
});
|
|
377
|
+
var shopifyBuyersSummarySchema = z2.object({
|
|
378
|
+
/** Buyers who ordered in the period. */
|
|
379
|
+
buyers: z2.number(),
|
|
380
|
+
/** Of them, whose first order was in the period. */
|
|
381
|
+
newBuyers: z2.number(),
|
|
382
|
+
returningBuyers: z2.number(),
|
|
383
|
+
/** Orders with no customer: counted in money, never a buyer. */
|
|
384
|
+
guestOrders: z2.number(),
|
|
385
|
+
orders: z2.number(),
|
|
386
|
+
net: z2.number(),
|
|
387
|
+
averageOrder: z2.number(),
|
|
388
|
+
/** Orders that were their buyer's first, and the rest (Shopify's new vs returning). */
|
|
389
|
+
newOrders: z2.number(),
|
|
390
|
+
returningOrders: z2.number(),
|
|
391
|
+
newNet: z2.number(),
|
|
392
|
+
returningNet: z2.number(),
|
|
393
|
+
/** Of every buyer up to the period's end, the share who ordered again (0–1). */
|
|
394
|
+
repeatRate: z2.number(),
|
|
395
|
+
/** Median days from a buyer's first order to their second; null when nobody ordered twice. */
|
|
396
|
+
medianDaysToSecond: z2.number().nullable()
|
|
397
|
+
});
|
|
398
|
+
var shopifyCameFromSchema = z2.object({
|
|
399
|
+
channel: z2.string(),
|
|
400
|
+
label: z2.string(),
|
|
401
|
+
campaign: z2.string().optional(),
|
|
402
|
+
// --full
|
|
403
|
+
source: z2.string().optional(),
|
|
404
|
+
medium: z2.string().optional(),
|
|
405
|
+
clickPlatform: z2.string().optional(),
|
|
406
|
+
landingId: z2.string().optional()
|
|
407
|
+
});
|
|
408
|
+
var shopifyBuyerRowSchema = z2.object({
|
|
409
|
+
number: z2.number(),
|
|
410
|
+
/** "Customer #1042". */
|
|
411
|
+
label: z2.string(),
|
|
412
|
+
place: shopifyPlaceSchema.nullable(),
|
|
413
|
+
orders: z2.number(),
|
|
414
|
+
net: z2.number(),
|
|
415
|
+
firstOrderAt: z2.string(),
|
|
416
|
+
lastOrderAt: z2.string(),
|
|
417
|
+
segment: shopifyBuyerSegmentSchema,
|
|
418
|
+
cameFrom: shopifyCameFromSchema,
|
|
419
|
+
// --full
|
|
420
|
+
gross: z2.number().optional(),
|
|
421
|
+
refunded: z2.number().optional(),
|
|
422
|
+
ordersWithDiscount: z2.number().optional(),
|
|
423
|
+
averageDaysBetweenOrders: z2.number().nullable().optional()
|
|
424
|
+
});
|
|
425
|
+
var shopifyAcquisitionRowSchema = z2.object({
|
|
426
|
+
key: z2.string(),
|
|
427
|
+
label: z2.string(),
|
|
428
|
+
buyers: z2.number(),
|
|
429
|
+
/** What their first orders came to. */
|
|
430
|
+
firstNet: z2.number(),
|
|
431
|
+
/** What they have spent since, to date. */
|
|
432
|
+
lifetimeNet: z2.number(),
|
|
433
|
+
lifetimePerBuyer: z2.number(),
|
|
434
|
+
averageOrders: z2.number(),
|
|
435
|
+
repeatRate: z2.number(),
|
|
436
|
+
// --full
|
|
437
|
+
source: z2.string().optional(),
|
|
438
|
+
medium: z2.string().optional(),
|
|
439
|
+
campaign: z2.string().optional(),
|
|
440
|
+
clickPlatform: z2.string().optional()
|
|
441
|
+
});
|
|
442
|
+
var shopifyBuyersResponseSchema = z2.object({
|
|
443
|
+
...answerCommon,
|
|
444
|
+
summary: shopifyBuyersSummarySchema,
|
|
445
|
+
thresholds: shopifySegmentThresholdsSchema,
|
|
446
|
+
segments: z2.array(z2.object({ segment: shopifyBuyerSegmentSchema, label: z2.string(), buyers: z2.number() })),
|
|
447
|
+
/** Buyers matching the filters, before the limit. */
|
|
448
|
+
total: z2.number(),
|
|
449
|
+
buyers: z2.array(shopifyBuyerRowSchema),
|
|
450
|
+
acquisition: z2.object({ by: shopifyAcquisitionBySchema, rows: z2.array(shopifyAcquisitionRowSchema) }).optional()
|
|
451
|
+
});
|
|
452
|
+
var shopifyBuyerNumberSchema = z2.string().trim().transform((value) => value.replace(/^customer\s*/i, "").replace(/^#/, "")).pipe(z2.string().regex(/^\d+$/, "a buyer's number, like 1042 or #1042")).transform((value) => Number(value)).pipe(z2.number().int().min(1).max(4e9));
|
|
453
|
+
var shopifyBuyerRequestSchema = z2.object({ number: shopifyBuyerNumberSchema, full: flag2.optional() });
|
|
454
|
+
var SHOPIFY_BUYER_GONE_MESSAGE = "This buyer is no longer in Baker.";
|
|
455
|
+
var shopifyBuyerOrderSchema = z2.object({
|
|
456
|
+
name: z2.string(),
|
|
457
|
+
/** When it was placed, in the store's time (YYYY-MM-DD HH:MM). */
|
|
458
|
+
placedAt: z2.string(),
|
|
459
|
+
net: z2.number(),
|
|
460
|
+
gross: z2.number(),
|
|
461
|
+
refunded: z2.number(),
|
|
462
|
+
products: z2.array(z2.object({ title: z2.string(), quantity: z2.number() })),
|
|
463
|
+
discountCodes: z2.array(z2.string()),
|
|
464
|
+
place: shopifyPlaceSchema.nullable(),
|
|
465
|
+
/** Placed before Baker measured the store: counted, credited to no campaign. */
|
|
466
|
+
beforeBaker: z2.boolean(),
|
|
467
|
+
// --full
|
|
468
|
+
orderId: z2.string().optional(),
|
|
469
|
+
items: z2.array(z2.object({ productId: z2.string(), variantId: z2.string(), quantity: z2.number(), amount: z2.number() })).optional(),
|
|
470
|
+
refundedItems: z2.array(z2.object({ productId: z2.string(), variantId: z2.string(), quantity: z2.number(), amount: z2.number() })).optional(),
|
|
471
|
+
cameFrom: shopifyCameFromSchema.optional()
|
|
472
|
+
});
|
|
473
|
+
var shopifyBuyerProfileSchema = z2.object({
|
|
474
|
+
number: z2.number(),
|
|
475
|
+
label: z2.string(),
|
|
476
|
+
place: shopifyPlaceSchema.nullable(),
|
|
477
|
+
orders: z2.number(),
|
|
478
|
+
gross: z2.number(),
|
|
479
|
+
refunded: z2.number(),
|
|
480
|
+
net: z2.number(),
|
|
481
|
+
averageOrder: z2.number(),
|
|
482
|
+
/** Average days between their orders; null with one order. */
|
|
483
|
+
averageDaysBetweenOrders: z2.number().nullable(),
|
|
484
|
+
firstOrderAt: z2.string(),
|
|
485
|
+
lastOrderAt: z2.string(),
|
|
486
|
+
ordersWithDiscount: z2.number(),
|
|
487
|
+
segment: shopifyBuyerSegmentSchema,
|
|
488
|
+
segmentLabel: z2.string(),
|
|
489
|
+
cameFrom: shopifyCameFromSchema
|
|
490
|
+
});
|
|
491
|
+
var shopifyBuyerResponseSchema = z2.object({
|
|
492
|
+
...answerCommon,
|
|
493
|
+
buyer: shopifyBuyerProfileSchema,
|
|
494
|
+
/** Newest first. */
|
|
495
|
+
orders: z2.array(shopifyBuyerOrderSchema),
|
|
496
|
+
products: z2.array(
|
|
497
|
+
z2.object({ title: z2.string(), quantity: z2.number(), orders: z2.number(), productId: z2.string().optional() })
|
|
498
|
+
)
|
|
499
|
+
});
|
|
500
|
+
var shopifyCountriesRequestSchema = z2.object({
|
|
501
|
+
...windowRequest,
|
|
502
|
+
/** Inside one region of `country` (its code or name): its cities. */
|
|
503
|
+
region: z2.string().max(100).optional(),
|
|
504
|
+
/** Only orders holding this product (its id or handle); money is that product's lines. */
|
|
505
|
+
product: z2.string().max(255).optional(),
|
|
506
|
+
limit: limit.optional()
|
|
507
|
+
});
|
|
508
|
+
var shopifyPlaceRowSchema = z2.object({
|
|
509
|
+
/** A country code, a region code (or name) or a city. */
|
|
510
|
+
key: z2.string(),
|
|
511
|
+
name: z2.string(),
|
|
512
|
+
orders: z2.number(),
|
|
513
|
+
buyers: z2.number(),
|
|
514
|
+
newBuyers: z2.number(),
|
|
515
|
+
net: z2.number(),
|
|
516
|
+
averageOrder: z2.number(),
|
|
517
|
+
/** Of the total's net, 0–1. */
|
|
518
|
+
share: z2.number(),
|
|
519
|
+
/** Net against the period before; null when there was none before. */
|
|
520
|
+
growth: z2.number().nullable(),
|
|
521
|
+
ordersGrowth: z2.number().nullable(),
|
|
522
|
+
/** The currency its buyers paid in most. */
|
|
523
|
+
paidIn: z2.string(),
|
|
524
|
+
// --full
|
|
525
|
+
previousNet: z2.number().optional(),
|
|
526
|
+
previousOrders: z2.number().optional()
|
|
527
|
+
});
|
|
528
|
+
var shopifyCountriesResponseSchema = z2.object({
|
|
529
|
+
...answerCommon,
|
|
530
|
+
level: z2.enum(["country", "region", "city"]),
|
|
531
|
+
country: z2.string().optional(),
|
|
532
|
+
region: z2.string().optional(),
|
|
533
|
+
product: z2.object({ id: z2.string(), title: z2.string() }).optional(),
|
|
534
|
+
total: shopifyPlaceRowSchema,
|
|
535
|
+
/** Named places, best first; with `unknown` and `other` they add up to `total`. */
|
|
536
|
+
rows: z2.array(shopifyPlaceRowSchema),
|
|
537
|
+
/** Orders whose address Baker could not read. Always shown, never folded into `other`. */
|
|
538
|
+
unknown: shopifyPlaceRowSchema.nullable(),
|
|
539
|
+
/** Places past the limit, together. */
|
|
540
|
+
other: shopifyPlaceRowSchema.nullable()
|
|
541
|
+
});
|
|
542
|
+
var shopifyBestSellersViewSchema = z2.enum(["products", "variants", "trend", "together", "repeat", "momentum"]);
|
|
543
|
+
var shopifyBestSellersSortSchema = z2.enum(["units", "revenue", "orders"]);
|
|
544
|
+
var shopifyBestSellersRequestSchema = z2.object({
|
|
545
|
+
...windowRequest,
|
|
546
|
+
view: shopifyBestSellersViewSchema.optional(),
|
|
547
|
+
sort: shopifyBestSellersSortSchema.optional(),
|
|
548
|
+
/** Only this product (its id or handle): its variants, its trend. */
|
|
549
|
+
product: z2.string().max(255).optional(),
|
|
550
|
+
limit: limit.optional()
|
|
551
|
+
});
|
|
552
|
+
var shopifyBestSellerRowSchema = z2.object({
|
|
553
|
+
title: z2.string(),
|
|
554
|
+
/** The option's own title, by variant. */
|
|
555
|
+
variantTitle: z2.string().optional(),
|
|
556
|
+
units: z2.number(),
|
|
557
|
+
refundedUnits: z2.number(),
|
|
558
|
+
orders: z2.number(),
|
|
559
|
+
buyers: z2.number(),
|
|
560
|
+
revenue: z2.number(),
|
|
561
|
+
refunded: z2.number(),
|
|
562
|
+
/** Refunded units of units ordered, 0–1. */
|
|
563
|
+
refundRate: z2.number(),
|
|
564
|
+
/** Units against the period before; null when none sold before. */
|
|
565
|
+
growth: z2.number().nullable(),
|
|
566
|
+
// --full
|
|
567
|
+
productId: z2.string().optional(),
|
|
568
|
+
variantId: z2.string().optional(),
|
|
569
|
+
previousUnits: z2.number().optional(),
|
|
570
|
+
previousRevenue: z2.number().optional()
|
|
571
|
+
});
|
|
572
|
+
var shopifyTrendRowSchema = z2.object({
|
|
573
|
+
/** YYYY-MM, in the store's time. */
|
|
574
|
+
month: z2.string(),
|
|
575
|
+
title: z2.string(),
|
|
576
|
+
units: z2.number(),
|
|
577
|
+
revenue: z2.number(),
|
|
578
|
+
productId: z2.string().optional()
|
|
579
|
+
});
|
|
580
|
+
var shopifyTogetherRowSchema = z2.object({
|
|
581
|
+
first: z2.string(),
|
|
582
|
+
second: z2.string(),
|
|
583
|
+
/** Orders holding both. */
|
|
584
|
+
orders: z2.number(),
|
|
585
|
+
/** Of the orders holding the first, the share that also held the second (0–1). */
|
|
586
|
+
confidence: z2.number(),
|
|
587
|
+
firstId: z2.string().optional(),
|
|
588
|
+
secondId: z2.string().optional()
|
|
589
|
+
});
|
|
590
|
+
var shopifyRepeatRowSchema = z2.object({
|
|
591
|
+
title: z2.string(),
|
|
592
|
+
/** Buyers whose first order held it. */
|
|
593
|
+
buyers: z2.number(),
|
|
594
|
+
/** Of them, who ordered again. */
|
|
595
|
+
buyersAgain: z2.number(),
|
|
596
|
+
share: z2.number(),
|
|
597
|
+
medianDaysToSecond: z2.number().nullable(),
|
|
598
|
+
productId: z2.string().optional()
|
|
599
|
+
});
|
|
600
|
+
var shopifyMomentumRowSchema = z2.object({
|
|
601
|
+
title: z2.string(),
|
|
602
|
+
units: z2.number(),
|
|
603
|
+
previousUnits: z2.number(),
|
|
604
|
+
/** Units against the period before (−0.4 = 40% fewer). */
|
|
605
|
+
change: z2.number(),
|
|
606
|
+
revenue: z2.number(),
|
|
607
|
+
productId: z2.string().optional()
|
|
608
|
+
});
|
|
609
|
+
var shopifyBestSellersResponseSchema = z2.object({
|
|
610
|
+
...answerCommon,
|
|
611
|
+
view: shopifyBestSellersViewSchema,
|
|
612
|
+
sort: shopifyBestSellersSortSchema,
|
|
613
|
+
product: z2.object({ id: z2.string(), title: z2.string() }).optional(),
|
|
614
|
+
products: z2.array(shopifyBestSellerRowSchema).optional(),
|
|
615
|
+
variants: z2.array(shopifyBestSellerRowSchema).optional(),
|
|
616
|
+
trend: z2.array(shopifyTrendRowSchema).optional(),
|
|
617
|
+
together: z2.array(shopifyTogetherRowSchema).optional(),
|
|
618
|
+
repeat: z2.array(shopifyRepeatRowSchema).optional(),
|
|
619
|
+
momentum: z2.array(shopifyMomentumRowSchema).optional()
|
|
620
|
+
});
|
|
621
|
+
var shopifyCohortsRequestSchema = z2.object({
|
|
622
|
+
months: z2.coerce.number().int().min(3).max(36).optional(),
|
|
623
|
+
country: countryCode.optional(),
|
|
624
|
+
full: flag2.optional()
|
|
625
|
+
});
|
|
626
|
+
var shopifyCohortMonthSchema = z2.object({
|
|
627
|
+
/** Months since the first order: 0 is the month they first bought. */
|
|
628
|
+
offset: z2.number(),
|
|
629
|
+
activeBuyers: z2.number(),
|
|
630
|
+
orders: z2.number(),
|
|
631
|
+
net: z2.number(),
|
|
632
|
+
/** Of the cohort, who ordered in that month (0–1). */
|
|
633
|
+
retention: z2.number(),
|
|
634
|
+
/** What the cohort spent up to that month, per buyer. */
|
|
635
|
+
cumulativeNetPerBuyer: z2.number()
|
|
636
|
+
});
|
|
637
|
+
var shopifyCohortSchema = z2.object({
|
|
638
|
+
/** The month of their first order, YYYY-MM in the store's time. */
|
|
639
|
+
month: z2.string(),
|
|
640
|
+
size: z2.number(),
|
|
641
|
+
/** Who ordered again 1, 2, 3, 6 and 12 months after; null for a month not reached yet. */
|
|
642
|
+
retention: z2.object({
|
|
643
|
+
m1: z2.number().nullable(),
|
|
644
|
+
m2: z2.number().nullable(),
|
|
645
|
+
m3: z2.number().nullable(),
|
|
646
|
+
m6: z2.number().nullable(),
|
|
647
|
+
m12: z2.number().nullable()
|
|
648
|
+
}),
|
|
649
|
+
netPerBuyerAt12: z2.number().nullable(),
|
|
650
|
+
// --full
|
|
651
|
+
matrix: z2.array(shopifyCohortMonthSchema).optional()
|
|
652
|
+
});
|
|
653
|
+
var shopifyCohortsResponseSchema = z2.object({
|
|
654
|
+
...answerCommon,
|
|
655
|
+
months: z2.number(),
|
|
656
|
+
cohorts: z2.array(shopifyCohortSchema)
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// ../api/src/shopify/writes.ts
|
|
660
|
+
import { z as z4 } from "zod";
|
|
661
|
+
|
|
662
|
+
// ../api/src/shopify/signups.ts
|
|
663
|
+
import { z as z3 } from "zod";
|
|
664
|
+
var SIGNUP_CODE_PATTERN = /^[A-Z0-9]{2,12}-[0-9A-HJKMNP-TV-Z]{8}$/;
|
|
665
|
+
var signupOfferHandleSchema = z3.string().regex(/^[a-z0-9][a-z0-9-]{1,39}$/, "2\u201340 lowercase letters, digits and dashes, e.g. welcome-10");
|
|
666
|
+
var SIGNUP_STATES = [
|
|
667
|
+
"waiting",
|
|
668
|
+
"ready",
|
|
669
|
+
"used",
|
|
670
|
+
"expired",
|
|
671
|
+
"already",
|
|
672
|
+
"ended",
|
|
673
|
+
"none",
|
|
674
|
+
"unavailable"
|
|
675
|
+
];
|
|
676
|
+
var CODE_STATES = /* @__PURE__ */ new Set(["ready", "used", "expired"]);
|
|
677
|
+
var shopifySignupReplySchema = z3.object({
|
|
678
|
+
state: z3.enum(SIGNUP_STATES),
|
|
679
|
+
code: z3.string().regex(SIGNUP_CODE_PATTERN).optional(),
|
|
680
|
+
endsAt: z3.string().min(1).max(40).optional()
|
|
681
|
+
}).strict().refine(
|
|
682
|
+
(reply) => CODE_STATES.has(reply.state) ? reply.code !== void 0 && reply.endsAt !== void 0 : true,
|
|
683
|
+
"a code state carries the code and when it ends"
|
|
684
|
+
).refine(
|
|
685
|
+
(reply) => CODE_STATES.has(reply.state) ? true : reply.code === void 0 && reply.endsAt === void 0,
|
|
686
|
+
"only the browser the code was given to is told it"
|
|
687
|
+
);
|
|
688
|
+
var shopifySignupResponseSchema = z3.union([
|
|
689
|
+
z3.object({ ok: z3.literal(true), signup: shopifySignupReplySchema }).passthrough(),
|
|
690
|
+
z3.object({ error: z3.string(), signup: shopifySignupReplySchema }).passthrough()
|
|
691
|
+
]);
|
|
692
|
+
var signupOfferStatusSchema = z3.enum(["active", "ended", "staged"]);
|
|
693
|
+
var shopifySignupOfferViewSchema = z3.object({
|
|
694
|
+
handle: z3.string(),
|
|
695
|
+
name: z3.string(),
|
|
696
|
+
/** "10% off everything, each code valid 14 days, at most 1,000 codes". */
|
|
697
|
+
summary: z3.string(),
|
|
698
|
+
status: signupOfferStatusSchema,
|
|
699
|
+
validForDays: z3.number(),
|
|
700
|
+
endsAt: z3.string().optional(),
|
|
701
|
+
maxCodes: z3.number().optional(),
|
|
702
|
+
codesGiven: z3.number()
|
|
703
|
+
});
|
|
704
|
+
var shopifySignupOffersResponseSchema = z3.object({
|
|
705
|
+
/** The company has a store connection Baker can use (connected, not waiting on a reconnect). */
|
|
706
|
+
connected: z3.boolean(),
|
|
707
|
+
/** The store allows Baker to add customers (sign-ups work). */
|
|
708
|
+
canAddCustomers: z3.boolean(),
|
|
709
|
+
/** The store allows Baker to make discount codes (offers give codes). */
|
|
710
|
+
canGiveCodes: z3.boolean(),
|
|
711
|
+
offers: z3.array(shopifySignupOfferViewSchema),
|
|
712
|
+
/** Sign-ups the store didn't take in the last 7 days: for a missing permission, and for anything else. */
|
|
713
|
+
refusedLast7Days: z3.object({ permission: z3.number(), other: z3.number() })
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// ../api/src/shopify/writes.ts
|
|
717
|
+
var SHOPIFY_TEMP_REF_PREFIX = "shopify_temp_";
|
|
718
|
+
var SHOPIFY_PERMISSION_NEEDED_PREFIX = "Baker isn't allowed to change this Shopify store yet";
|
|
719
|
+
var shopifyWriteEntitySchema = z4.enum([
|
|
720
|
+
"product",
|
|
721
|
+
"variant",
|
|
722
|
+
"metafield",
|
|
723
|
+
"collection",
|
|
724
|
+
"discountCode",
|
|
725
|
+
"urlRedirect",
|
|
726
|
+
"marketingActivity",
|
|
727
|
+
"webPixel",
|
|
728
|
+
"signupOffer",
|
|
729
|
+
"audience"
|
|
730
|
+
]);
|
|
731
|
+
var shopifyDraftOpKindSchema = z4.enum([
|
|
732
|
+
"shopify.product.update",
|
|
733
|
+
"shopify.variant.update",
|
|
734
|
+
"shopify.metafield.set",
|
|
735
|
+
"shopify.metafield.delete",
|
|
736
|
+
"shopify.collection.create",
|
|
737
|
+
"shopify.collection.publish",
|
|
738
|
+
"shopify.collection.unpublish",
|
|
739
|
+
"shopify.collection.update",
|
|
740
|
+
"shopify.collection.delete",
|
|
741
|
+
"shopify.collection.addProducts",
|
|
742
|
+
"shopify.collection.removeProducts",
|
|
743
|
+
"shopify.discountCode.create",
|
|
744
|
+
"shopify.discountCode.deactivate",
|
|
745
|
+
"shopify.discountCode.schedule",
|
|
746
|
+
"shopify.urlRedirect.create",
|
|
747
|
+
"shopify.urlRedirect.delete",
|
|
748
|
+
"shopify.marketingActivity.upsert",
|
|
749
|
+
"shopify.marketingActivity.delete",
|
|
750
|
+
"shopify.webPixel.update",
|
|
751
|
+
"shopify.signupOffer.set",
|
|
752
|
+
"shopify.signupOffer.end",
|
|
753
|
+
"shopify.audience.create",
|
|
754
|
+
"shopify.audience.remove"
|
|
755
|
+
]);
|
|
756
|
+
var shopifyDecimalSchema = z4.string().regex(/^\d{1,12}(\.\d{1,4})?$/, 'write amounts as a decimal string, e.g. "19.95"');
|
|
757
|
+
var tagSchema = z4.string().trim().min(1).max(255);
|
|
758
|
+
var seoSchema = z4.object({ title: z4.string().max(255).optional(), description: z4.string().max(320).optional() }).strict();
|
|
759
|
+
var SHOPIFY_DESCRIPTION_MAX_CHARS = 2e5;
|
|
760
|
+
var descriptionHtmlSchema = z4.string().max(SHOPIFY_DESCRIPTION_MAX_CHARS);
|
|
761
|
+
var productUpdateSchema = z4.object({
|
|
762
|
+
title: z4.string().trim().min(1).max(255).optional(),
|
|
763
|
+
descriptionHtml: descriptionHtmlSchema.optional(),
|
|
764
|
+
seo: seoSchema.optional(),
|
|
765
|
+
/** The WHOLE tag list after the change (Shopify replaces it). `addTags`/`removeTags` are resolved against the live tags. */
|
|
766
|
+
tags: z4.array(tagSchema).max(250).optional(),
|
|
767
|
+
addTags: z4.array(tagSchema).max(250).optional(),
|
|
768
|
+
removeTags: z4.array(tagSchema).max(250).optional(),
|
|
769
|
+
productType: z4.string().max(255).optional()
|
|
770
|
+
}).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change").refine(
|
|
771
|
+
(payload) => payload.tags === void 0 || payload.addTags === void 0 && payload.removeTags === void 0,
|
|
772
|
+
"give either the whole `tags` list or `addTags`/`removeTags`, not both"
|
|
773
|
+
);
|
|
774
|
+
var shopifyWeightUnitSchema = z4.enum(["GRAMS", "KILOGRAMS", "OUNCES", "POUNDS"]);
|
|
775
|
+
var variantUpdateSchema = z4.object({
|
|
776
|
+
barcode: z4.string().trim().max(255).optional(),
|
|
777
|
+
sku: z4.string().trim().max(255).optional(),
|
|
778
|
+
weight: z4.object({ value: z4.number().nonnegative(), unit: shopifyWeightUnitSchema }).strict().optional(),
|
|
779
|
+
price: shopifyDecimalSchema.optional(),
|
|
780
|
+
/** `null` clears the sale ("compare at") price. */
|
|
781
|
+
compareAtPrice: shopifyDecimalSchema.nullable().optional()
|
|
782
|
+
}).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
|
|
783
|
+
var metafieldKeySchema = z4.string().min(2).max(64).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
|
|
784
|
+
var metafieldNamespaceSchema = z4.string().min(2).max(255).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
|
|
785
|
+
var metafieldSetSchema = z4.object({
|
|
786
|
+
namespace: metafieldNamespaceSchema,
|
|
787
|
+
key: metafieldKeySchema,
|
|
788
|
+
/** Shopify's metafield type, e.g. `single_line_text_field`, `multi_line_text_field`, `rich_text_field`, `number_integer`, `boolean`, `json`. */
|
|
789
|
+
type: z4.string().min(1).max(100),
|
|
790
|
+
value: z4.string().max(1e5)
|
|
791
|
+
}).strict();
|
|
792
|
+
var metafieldDeleteSchema = z4.object({ namespace: metafieldNamespaceSchema, key: metafieldKeySchema }).strict();
|
|
793
|
+
var shopifyCollectionSortOrderSchema = z4.enum([
|
|
794
|
+
"MANUAL",
|
|
795
|
+
"BEST_SELLING",
|
|
796
|
+
"ALPHA_ASC",
|
|
797
|
+
"ALPHA_DESC",
|
|
798
|
+
"PRICE_DESC",
|
|
799
|
+
"PRICE_ASC",
|
|
800
|
+
"CREATED",
|
|
801
|
+
"CREATED_DESC"
|
|
802
|
+
]);
|
|
803
|
+
var handleSchema = z4.string().min(1).max(255).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "lowercase letters, digits and single dashes, e.g. summer-snowboards");
|
|
804
|
+
var SHOPIFY_COLLECTION_PRODUCTS_MAX = 250;
|
|
805
|
+
var collectionCreateSchema = z4.object({
|
|
806
|
+
title: z4.string().trim().min(1).max(255),
|
|
807
|
+
handle: handleSchema.optional(),
|
|
808
|
+
descriptionHtml: descriptionHtmlSchema.optional(),
|
|
809
|
+
seo: seoSchema.optional(),
|
|
810
|
+
sortOrder: shopifyCollectionSortOrderSchema.optional(),
|
|
811
|
+
/** The theme template the collection page uses (the part after `collection.`). Kept when a deleted collection is put back. */
|
|
812
|
+
templateSuffix: z4.string().max(255).optional(),
|
|
813
|
+
/** Product ids, handles or links, in the order shown with sort order MANUAL. */
|
|
814
|
+
products: z4.array(z4.string().min(1)).max(SHOPIFY_COLLECTION_PRODUCTS_MAX).optional(),
|
|
815
|
+
/** Show it on the online store (a second change, staged with it). Default true. */
|
|
816
|
+
onlineStore: z4.boolean().optional()
|
|
817
|
+
}).strict();
|
|
818
|
+
var collectionUpdateSchema = z4.object({
|
|
819
|
+
title: z4.string().trim().min(1).max(255).optional(),
|
|
820
|
+
descriptionHtml: descriptionHtmlSchema.optional(),
|
|
821
|
+
seo: seoSchema.optional(),
|
|
822
|
+
sortOrder: shopifyCollectionSortOrderSchema.optional()
|
|
823
|
+
}).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
|
|
824
|
+
var collectionProductsSchema = z4.object({ products: z4.array(z4.string().min(1)).min(1).max(SHOPIFY_COLLECTION_PRODUCTS_MAX) }).strict();
|
|
825
|
+
var isoDateTimeSchema = z4.iso.datetime({ offset: true });
|
|
826
|
+
var discountCodeSchema = z4.string().regex(/^[A-Za-z0-9_-]{3,64}$/, "3\u201364 letters, digits, `_` or `-`, e.g. SPRING10");
|
|
827
|
+
var discountValueSchema = z4.union([
|
|
828
|
+
z4.object({ percentage: z4.number().min(1).max(100) }).strict(),
|
|
829
|
+
z4.object({ amount: shopifyDecimalSchema }).strict()
|
|
830
|
+
]);
|
|
831
|
+
var discountAppliesToSchema = z4.union([
|
|
832
|
+
z4.object({ all: z4.literal(true) }).strict(),
|
|
833
|
+
z4.object({ products: z4.array(z4.string().min(1)).min(1).max(100) }).strict(),
|
|
834
|
+
z4.object({ collections: z4.array(z4.string().min(1)).min(1).max(100) }).strict()
|
|
835
|
+
]);
|
|
836
|
+
var discountCreateSchema = z4.object({
|
|
837
|
+
code: discountCodeSchema,
|
|
838
|
+
/** What the store's staff see; defaults to the code. */
|
|
839
|
+
title: z4.string().trim().min(1).max(255).optional(),
|
|
840
|
+
value: discountValueSchema,
|
|
841
|
+
appliesTo: discountAppliesToSchema,
|
|
842
|
+
minimumSubtotal: shopifyDecimalSchema.optional(),
|
|
843
|
+
minimumQuantity: z4.number().int().min(1).max(1e4).optional(),
|
|
844
|
+
/** Default: the moment it is published. */
|
|
845
|
+
startsAt: isoDateTimeSchema.optional(),
|
|
846
|
+
endsAt: isoDateTimeSchema.optional(),
|
|
847
|
+
usageLimit: z4.number().int().min(1).max(1e6).optional(),
|
|
848
|
+
/** Default true. */
|
|
849
|
+
oncePerCustomer: z4.boolean().optional(),
|
|
850
|
+
combinesWith: z4.object({
|
|
851
|
+
productDiscounts: z4.boolean().optional(),
|
|
852
|
+
orderDiscounts: z4.boolean().optional(),
|
|
853
|
+
shippingDiscounts: z4.boolean().optional()
|
|
854
|
+
}).strict().optional()
|
|
855
|
+
}).strict().refine(
|
|
856
|
+
(payload) => payload.minimumSubtotal === void 0 || payload.minimumQuantity === void 0,
|
|
857
|
+
"a discount takes a minimum amount or a minimum quantity, not both"
|
|
858
|
+
);
|
|
859
|
+
var discountScheduleSchema = z4.object({ startsAt: isoDateTimeSchema, endsAt: isoDateTimeSchema.nullable() }).strict();
|
|
860
|
+
var storePathSchema = z4.string().min(2).max(1024).regex(/^\/[^\s]*$/, 'a path on the store starting with "/", e.g. /collections/old');
|
|
861
|
+
var urlRedirectCreateSchema = z4.object({
|
|
862
|
+
from: storePathSchema,
|
|
863
|
+
to: z4.union([storePathSchema, z4.url({ protocol: /^https$/ })])
|
|
864
|
+
}).strict();
|
|
865
|
+
var shopifyMarketingPlatformSchema = z4.enum(["google-ads", "meta"]);
|
|
866
|
+
var marketingUpsertSchema = z4.object({
|
|
867
|
+
platform: shopifyMarketingPlatformSchema,
|
|
868
|
+
accountId: z4.string().min(1).max(64),
|
|
869
|
+
campaignId: z4.string().regex(/^\d+$/, "the campaign's numeric id"),
|
|
870
|
+
utm: z4.object({
|
|
871
|
+
campaign: z4.string().trim().min(1).max(255),
|
|
872
|
+
source: z4.string().trim().min(1).max(255),
|
|
873
|
+
medium: z4.string().trim().min(1).max(255)
|
|
874
|
+
}).strict()
|
|
875
|
+
}).strict();
|
|
876
|
+
var signupOfferSetSchema = z4.object({
|
|
877
|
+
/** What the store's staff see in front of each code, and the Store page's row. */
|
|
878
|
+
name: z4.string().trim().min(1).max(80),
|
|
879
|
+
value: discountValueSchema,
|
|
880
|
+
appliesTo: discountAppliesToSchema,
|
|
881
|
+
minimumSubtotal: shopifyDecimalSchema.optional(),
|
|
882
|
+
/** Each code's life, from the moment it is given. */
|
|
883
|
+
validForDays: z4.number().int().min(1).max(365),
|
|
884
|
+
/** The offer stops giving codes after this; no code outlives it. */
|
|
885
|
+
endsAt: isoDateTimeSchema.optional(),
|
|
886
|
+
/** How many codes the offer gives in all. */
|
|
887
|
+
maxCodes: z4.number().int().min(1).max(1e5).optional(),
|
|
888
|
+
/** The start of every code (WELCOME-7Q2M9XKD); default: the handle in capitals. */
|
|
889
|
+
codePrefix: z4.string().regex(/^[A-Z0-9]{2,12}$/, "2\u201312 capital letters or digits, e.g. WELCOME").optional(),
|
|
890
|
+
combinesWith: z4.object({
|
|
891
|
+
productDiscounts: z4.boolean().optional(),
|
|
892
|
+
orderDiscounts: z4.boolean().optional(),
|
|
893
|
+
shippingDiscounts: z4.boolean().optional()
|
|
894
|
+
}).strict().optional()
|
|
895
|
+
}).strict();
|
|
896
|
+
var emptySchema = z4.object({}).strict();
|
|
897
|
+
var opFields = {
|
|
898
|
+
/** Optional caller-chosen ref for a create, so later ops in the batch can name it (`shopify_temp_…`). */
|
|
899
|
+
ref: z4.string().startsWith(SHOPIFY_TEMP_REF_PREFIX).max(80).optional()
|
|
900
|
+
};
|
|
901
|
+
var target = z4.string().trim().min(1).max(2048);
|
|
902
|
+
var shopifyDraftOpInputSchema = z4.discriminatedUnion("kind", [
|
|
903
|
+
z4.object({ kind: z4.literal("shopify.product.update"), target, payload: productUpdateSchema, ...opFields }),
|
|
904
|
+
z4.object({ kind: z4.literal("shopify.variant.update"), target, payload: variantUpdateSchema, ...opFields }),
|
|
905
|
+
z4.object({ kind: z4.literal("shopify.metafield.set"), target, payload: metafieldSetSchema, ...opFields }),
|
|
906
|
+
z4.object({ kind: z4.literal("shopify.metafield.delete"), target, payload: metafieldDeleteSchema, ...opFields }),
|
|
907
|
+
z4.object({ kind: z4.literal("shopify.collection.create"), payload: collectionCreateSchema, ...opFields }),
|
|
908
|
+
z4.object({ kind: z4.literal("shopify.collection.publish"), target, payload: emptySchema.optional(), ...opFields }),
|
|
909
|
+
z4.object({ kind: z4.literal("shopify.collection.unpublish"), target, payload: emptySchema.optional(), ...opFields }),
|
|
910
|
+
z4.object({ kind: z4.literal("shopify.collection.update"), target, payload: collectionUpdateSchema, ...opFields }),
|
|
911
|
+
z4.object({ kind: z4.literal("shopify.collection.delete"), target, payload: emptySchema.optional(), ...opFields }),
|
|
912
|
+
z4.object({
|
|
913
|
+
kind: z4.literal("shopify.collection.addProducts"),
|
|
914
|
+
target,
|
|
915
|
+
payload: collectionProductsSchema,
|
|
916
|
+
...opFields
|
|
917
|
+
}),
|
|
918
|
+
z4.object({
|
|
919
|
+
kind: z4.literal("shopify.collection.removeProducts"),
|
|
920
|
+
target,
|
|
921
|
+
payload: collectionProductsSchema,
|
|
922
|
+
...opFields
|
|
923
|
+
}),
|
|
924
|
+
z4.object({ kind: z4.literal("shopify.discountCode.create"), payload: discountCreateSchema, ...opFields }),
|
|
925
|
+
z4.object({
|
|
926
|
+
kind: z4.literal("shopify.discountCode.deactivate"),
|
|
927
|
+
target,
|
|
928
|
+
payload: emptySchema.optional(),
|
|
929
|
+
...opFields
|
|
930
|
+
}),
|
|
931
|
+
z4.object({ kind: z4.literal("shopify.discountCode.schedule"), target, payload: discountScheduleSchema, ...opFields }),
|
|
932
|
+
z4.object({ kind: z4.literal("shopify.urlRedirect.create"), payload: urlRedirectCreateSchema, ...opFields }),
|
|
933
|
+
z4.object({ kind: z4.literal("shopify.urlRedirect.delete"), target, payload: emptySchema.optional(), ...opFields }),
|
|
934
|
+
z4.object({ kind: z4.literal("shopify.marketingActivity.upsert"), payload: marketingUpsertSchema, ...opFields }),
|
|
935
|
+
z4.object({
|
|
936
|
+
kind: z4.literal("shopify.marketingActivity.delete"),
|
|
937
|
+
target,
|
|
938
|
+
payload: emptySchema.optional(),
|
|
939
|
+
...opFields
|
|
940
|
+
}),
|
|
941
|
+
z4.object({ kind: z4.literal("shopify.webPixel.update"), payload: emptySchema.optional(), ...opFields }),
|
|
942
|
+
z4.object({
|
|
943
|
+
kind: z4.literal("shopify.signupOffer.set"),
|
|
944
|
+
target: signupOfferHandleSchema,
|
|
945
|
+
payload: signupOfferSetSchema,
|
|
946
|
+
...opFields
|
|
947
|
+
}),
|
|
948
|
+
z4.object({
|
|
949
|
+
kind: z4.literal("shopify.signupOffer.end"),
|
|
950
|
+
target: signupOfferHandleSchema,
|
|
951
|
+
payload: emptySchema.optional(),
|
|
952
|
+
...opFields
|
|
953
|
+
}),
|
|
954
|
+
z4.object({ kind: z4.literal("shopify.audience.create"), payload: shopifyAudienceDefinitionSchema, ...opFields }),
|
|
955
|
+
/** `target`: the audience's id (`baker shopify audiences list`). */
|
|
956
|
+
z4.object({
|
|
957
|
+
kind: z4.literal("shopify.audience.remove"),
|
|
958
|
+
target,
|
|
959
|
+
payload: shopifyAudienceRemovalSchema.optional(),
|
|
960
|
+
...opFields
|
|
961
|
+
})
|
|
962
|
+
]);
|
|
963
|
+
var PAYLOAD_KEYS = new Map(
|
|
964
|
+
shopifyDraftOpInputSchema.options.map((option) => {
|
|
965
|
+
const payload = option.shape.payload;
|
|
966
|
+
const object = payload instanceof z4.ZodOptional ? payload.unwrap() : payload;
|
|
967
|
+
return [option.shape.kind.value, object instanceof z4.ZodObject ? Object.keys(object.shape) : []];
|
|
968
|
+
})
|
|
969
|
+
);
|
|
970
|
+
var askUserQuestionInputSchema = z4.object({
|
|
971
|
+
questions: z4.array(
|
|
972
|
+
z4.object({
|
|
973
|
+
question: z4.string(),
|
|
974
|
+
header: z4.string(),
|
|
975
|
+
multiSelect: z4.boolean(),
|
|
976
|
+
options: z4.array(z4.object({ label: z4.string(), description: z4.string() })).min(2)
|
|
977
|
+
})
|
|
978
|
+
).min(1)
|
|
979
|
+
});
|
|
980
|
+
var shopifyNeedsApprovalSchema = z4.object({
|
|
981
|
+
/** Pass to AskUserQuestion exactly as given. */
|
|
982
|
+
askUserQuestion: askUserQuestionInputSchema,
|
|
983
|
+
/** The user already said no to (at least one of) these changes: don't ask again in this turn. */
|
|
984
|
+
declined: z4.boolean()
|
|
985
|
+
});
|
|
986
|
+
var shopifyVerificationSchema = z4.object({
|
|
987
|
+
status: z4.enum(["confirmed", "drifted", "unconfirmed"]),
|
|
988
|
+
checkedAt: z4.number(),
|
|
989
|
+
note: z4.string().optional()
|
|
990
|
+
});
|
|
991
|
+
var shopifyDraftOpResultSchema = z4.object({
|
|
992
|
+
status: z4.enum(["applied", "failed", "skipped"]),
|
|
993
|
+
createdId: z4.string().optional(),
|
|
994
|
+
error: z4.string().optional(),
|
|
995
|
+
note: z4.string().optional(),
|
|
996
|
+
skippedBecause: z4.string().optional(),
|
|
997
|
+
executedAt: z4.number().optional(),
|
|
998
|
+
pendingJob: z4.string().optional(),
|
|
999
|
+
verification: shopifyVerificationSchema.optional()
|
|
1000
|
+
});
|
|
1001
|
+
var stagedOpSchema = z4.object({
|
|
1002
|
+
ref: z4.string(),
|
|
1003
|
+
kind: shopifyDraftOpKindSchema,
|
|
1004
|
+
summary: z4.string(),
|
|
1005
|
+
warnings: z4.array(z4.string())
|
|
1006
|
+
});
|
|
1007
|
+
var shopifyDraftStageResponseSchema = z4.union([
|
|
1008
|
+
z4.object({
|
|
1009
|
+
staged: z4.literal(true),
|
|
1010
|
+
count: z4.number(),
|
|
1011
|
+
ops: z4.array(stagedOpSchema),
|
|
1012
|
+
amended: z4.boolean().optional()
|
|
1013
|
+
}),
|
|
1014
|
+
z4.object({ staged: z4.literal(false), needsApproval: shopifyNeedsApprovalSchema })
|
|
1015
|
+
]);
|
|
1016
|
+
var SHOPIFY_DRAFT_BATCH_MAX = 100;
|
|
1017
|
+
var shopifyDraftStageRequestSchema = z4.object({ chatId: z4.string(), op: shopifyDraftOpInputSchema });
|
|
1018
|
+
var shopifyDraftStageBatchRequestSchema = z4.object({
|
|
1019
|
+
chatId: z4.string(),
|
|
1020
|
+
ops: z4.array(shopifyDraftOpInputSchema).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
|
|
1021
|
+
});
|
|
1022
|
+
var shopifyDraftAmendRequestSchema = z4.object({
|
|
1023
|
+
chatId: z4.string(),
|
|
1024
|
+
ref: z4.string(),
|
|
1025
|
+
patch: z4.record(z4.string(), z4.unknown())
|
|
1026
|
+
});
|
|
1027
|
+
var shopifyDraftOpViewSchema = z4.object({
|
|
1028
|
+
ref: z4.string(),
|
|
1029
|
+
kind: shopifyDraftOpKindSchema,
|
|
1030
|
+
entity: shopifyWriteEntitySchema,
|
|
1031
|
+
target: z4.string().optional(),
|
|
1032
|
+
targetLabel: z4.string().optional(),
|
|
1033
|
+
summary: z4.string(),
|
|
1034
|
+
money: z4.boolean(),
|
|
1035
|
+
warnings: z4.array(z4.string()).optional(),
|
|
1036
|
+
dependsOn: z4.array(z4.string()),
|
|
1037
|
+
undoOf: z4.string().optional(),
|
|
1038
|
+
stagedAt: z4.number(),
|
|
1039
|
+
result: shopifyDraftOpResultSchema.optional()
|
|
1040
|
+
});
|
|
1041
|
+
var shopifyDraftListRequestSchema = z4.object({ chatId: z4.string() });
|
|
1042
|
+
var shopifyDraftListResponseSchema = z4.object({
|
|
1043
|
+
status: z4.enum(["active", "publishing", "applied", "discarded", "none"]),
|
|
1044
|
+
store: z4.string().optional(),
|
|
1045
|
+
count: z4.number(),
|
|
1046
|
+
ops: z4.array(shopifyDraftOpViewSchema),
|
|
1047
|
+
publishError: z4.string().optional()
|
|
1048
|
+
});
|
|
1049
|
+
var shopifyDraftShowRequestSchema = z4.object({ chatId: z4.string(), ref: z4.string() });
|
|
1050
|
+
var shopifyDraftShowResponseSchema = z4.object({
|
|
1051
|
+
op: shopifyDraftOpViewSchema.extend({ payload: z4.unknown().optional(), before: z4.unknown().optional() })
|
|
1052
|
+
});
|
|
1053
|
+
var shopifyDraftRemoveRequestSchema = z4.object({ chatId: z4.string(), ref: z4.string() });
|
|
1054
|
+
var shopifyDraftRemoveResponseSchema = z4.object({ removed: z4.array(z4.string()) });
|
|
1055
|
+
var shopifyDraftClearRequestSchema = z4.object({ chatId: z4.string() });
|
|
1056
|
+
var shopifyDraftClearResponseSchema = z4.object({ cleared: z4.number() });
|
|
1057
|
+
var shopifyChangeViewSchema = z4.object({
|
|
1058
|
+
changeId: z4.string(),
|
|
1059
|
+
when: z4.string(),
|
|
1060
|
+
kind: shopifyDraftOpKindSchema,
|
|
1061
|
+
summary: z4.string(),
|
|
1062
|
+
status: z4.enum(["applied", "failed", "undone"]),
|
|
1063
|
+
chatId: z4.string(),
|
|
1064
|
+
undoable: z4.boolean(),
|
|
1065
|
+
/** Why it can't be undone, when it can't. Whether the store still holds Baker's change is checked when undo is staged. */
|
|
1066
|
+
notUndoableBecause: z4.string().optional()
|
|
1067
|
+
});
|
|
1068
|
+
var shopifyChangesRequestSchema = z4.object({
|
|
1069
|
+
limit: z4.coerce.number().int().min(1).max(100).optional(),
|
|
1070
|
+
chatId: z4.string().optional()
|
|
1071
|
+
});
|
|
1072
|
+
var shopifyChangesResponseSchema = z4.object({ changes: z4.array(shopifyChangeViewSchema) });
|
|
1073
|
+
var shopifyUndoRequestSchema = z4.object({
|
|
1074
|
+
chatId: z4.string(),
|
|
1075
|
+
changeIds: z4.array(z4.string().min(1)).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// src/commands/shopify/refusal.ts
|
|
1079
|
+
var CONTINUE = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
|
|
1080
|
+
function shopifyRefusal(code, message) {
|
|
1081
|
+
if (code === "UNAVAILABLE" && message.includes(SHOPIFY_ORDERS_IMPORTING_MESSAGE)) {
|
|
1082
|
+
return {
|
|
1083
|
+
code: "SHOPIFY_ORDERS_IMPORTING",
|
|
1084
|
+
fix: {
|
|
1085
|
+
action: "continue_without",
|
|
1086
|
+
explanation: "The store IS connected: do not call `request_connection` and do not ask the user to connect it. Baker is still bringing in the store's orders (it started on its own), so buyers, segments and audiences are not ready yet. Tell the user in those words, run this command again in a few minutes, and meanwhile finish everything else."
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
if (code !== "NOT_FOUND") return null;
|
|
1091
|
+
if (message.includes(SHOPIFY_NOT_CONNECTED_MESSAGE)) {
|
|
1092
|
+
return {
|
|
1093
|
+
code: "NO_SHOPIFY_CONNECTION",
|
|
1094
|
+
fix: {
|
|
1095
|
+
action: "request_connection",
|
|
1096
|
+
explanation: 'AGENT INSTRUCTION: no Shopify store is connected, and only the user can connect it. Call the `request_connection` tool with { platform: "shopify", reason } \u2014 reason in their words, e.g. "so I can use your real products and prices" \u2014 and they connect it inside the chat; then run this command again. ' + CONTINUE
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
if (message.includes(SHOPIFY_NOT_OFFERED_MESSAGE)) {
|
|
1101
|
+
return {
|
|
1102
|
+
code: "SHOPIFY_NOT_AVAILABLE",
|
|
1103
|
+
fix: {
|
|
1104
|
+
action: "continue_without",
|
|
1105
|
+
explanation: "Do not call `request_connection` for Shopify and do not tell the user to connect it: a Shopify store cannot be connected on this Baker yet. Answer from the knowledge vault and the client's own website instead (`baker images extract <store url>` for product photos, `baker research fetch <url>` for a product page), say the product facts are unverified, and finish the rest of the task."
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
function storeChangeRefusal(code, message) {
|
|
1112
|
+
if (code !== "FORBIDDEN" || !message.startsWith(SHOPIFY_PERMISSION_NEEDED_PREFIX)) return null;
|
|
1113
|
+
return {
|
|
1114
|
+
code: "SHOPIFY_PERMISSION_NEEDED",
|
|
1115
|
+
fix: {
|
|
1116
|
+
action: "request_connection",
|
|
1117
|
+
explanation: 'AGENT INSTRUCTION: the store hasn\'t allowed Baker to make this kind of change yet, and only the user can. Call `request_connection` with { platform: "shopify", reason } \u2014 reason in their words, e.g. "so I can update your product titles". They allow it in Shopify; the card finishes when Shopify confirms. If its result lists something under cannotRead, that change is still not allowed: don\'t promise it. Then run this command again. Keep going with the rest of the task.'
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function approvalRefusal(needsApproval) {
|
|
1122
|
+
if (needsApproval.declined) {
|
|
1123
|
+
return {
|
|
1124
|
+
code: "NEEDS_USER_APPROVAL",
|
|
1125
|
+
fix: {
|
|
1126
|
+
action: "continue_without",
|
|
1127
|
+
askUserQuestion: needsApproval.askUserQuestion,
|
|
1128
|
+
explanation: "The user said no to this change. Don't stage it, don't ask again on your own, and don't find another way to make it; carry on with the rest of the task. Ask again only if the user asks for it again (they changed their mind): then call AskUserQuestion with `askUserQuestion` exactly as given, and if they choose \"Yes, make these changes\", run this same command again."
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
return {
|
|
1133
|
+
code: "NEEDS_USER_APPROVAL",
|
|
1134
|
+
fix: {
|
|
1135
|
+
action: "ask_user",
|
|
1136
|
+
askUserQuestion: needsApproval.askUserQuestion,
|
|
1137
|
+
explanation: 'This changes money in the store, so the user says yes first. Call AskUserQuestion with `askUserQuestion` exactly as given (you may say a sentence before it, never change it). If the user chooses "Yes, make these changes", run this same command again. Do not stage it any other way.'
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/commands/shopify/common.ts
|
|
1143
|
+
var PRODUCT_NOT_FOUND_HINT = "Find it with `baker shopify products --search <words>`. If `baker shopify store` shows products Baker couldn't read or products still coming in, it may be one of those \u2014 say so rather than that it doesn't exist.";
|
|
1144
|
+
function fail(err) {
|
|
1145
|
+
if (err instanceof ApiError) {
|
|
1146
|
+
const refusal = shopifyRefusal(err.code, err.message);
|
|
1147
|
+
if (refusal) {
|
|
1148
|
+
writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
1151
|
+
if (err.code === "NOT_FOUND" && err.message === SHOPIFY_BUYER_GONE_MESSAGE) {
|
|
1152
|
+
writeJson({
|
|
1153
|
+
ok: false,
|
|
1154
|
+
error: { code: err.code, message: err.message },
|
|
1155
|
+
hints: [
|
|
1156
|
+
"Their store asked Baker to erase this buyer, or they were merged into another one: say the buyer is no longer in Baker, and never guess who they were."
|
|
1157
|
+
]
|
|
1158
|
+
});
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
if (err.code === "NOT_FOUND" && err.message.startsWith(SHOPIFY_PRODUCT_NOT_FOUND_PREFIX)) {
|
|
1162
|
+
writeJson({ ok: false, error: { code: err.code, message: err.message }, hints: [PRODUCT_NOT_FOUND_HINT] });
|
|
1163
|
+
process.exit(1);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
failApi(err);
|
|
1167
|
+
}
|
|
1168
|
+
function emit(data, meta, hints) {
|
|
1169
|
+
writeJson({ ok: true, ...hints.length > 0 ? { hints } : {}, data, meta });
|
|
1170
|
+
}
|
|
1171
|
+
function params(entries) {
|
|
1172
|
+
const out = {};
|
|
1173
|
+
for (const [name, value] of Object.entries(entries)) {
|
|
1174
|
+
if (value === void 0 || value === "" || value === false) continue;
|
|
1175
|
+
out[name] = String(value);
|
|
1176
|
+
}
|
|
1177
|
+
return out;
|
|
1178
|
+
}
|
|
1179
|
+
var fullArg = {
|
|
1180
|
+
type: "boolean",
|
|
1181
|
+
description: "Everything Baker keeps, not just what answers the question",
|
|
1182
|
+
required: false,
|
|
1183
|
+
default: false
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/commands/shopify/writeCommon.ts
|
|
1187
|
+
import { defineCommand } from "citty";
|
|
1188
|
+
|
|
1189
|
+
// src/commands/shopify/writeHints.ts
|
|
1190
|
+
var STAGED = "Staged \u2014 nothing changes in the store until the user publishes the chat. Review: `baker shopify draft list`. Say the change is ready and happens when they publish, never that it is done.";
|
|
1191
|
+
var WARNINGS = "Read the warnings above before telling the user this is ready.";
|
|
1192
|
+
var DISCOUNT = "The code works only after the chat is published; don't announce it as live or share it as working yet.";
|
|
1193
|
+
var EMPTY_COLLECTION = "The new collection has no products yet: add them with `baker shopify change collection add <collection> <product\u2026>`.";
|
|
1194
|
+
var COLLECTION_DELETE = "Its address stops working when it is deleted: keep old links alive with `baker shopify change redirect create --from /collections/<handle> --to <page>` (ask the user where they should land).";
|
|
1195
|
+
function repointedRedirects(ops) {
|
|
1196
|
+
const removed = ops.flatMap(
|
|
1197
|
+
(op) => op.kind === "shopify.urlRedirect.delete" && op.ref.startsWith("urlRedirect:") ? [op.ref.slice("urlRedirect:".length)] : []
|
|
1198
|
+
);
|
|
1199
|
+
return removed.flatMap((path2) => {
|
|
1200
|
+
const made = ops.find(
|
|
1201
|
+
(op) => op.kind === "shopify.urlRedirect.create" && op.summary.startsWith(`Redirect ${path2} to `)
|
|
1202
|
+
);
|
|
1203
|
+
if (!made) return [];
|
|
1204
|
+
const to = made.summary.slice(`Redirect ${path2} to `.length);
|
|
1205
|
+
return [
|
|
1206
|
+
`${path2} still redirects after publish, now to ${to}: tell the user the old link goes there, not that its redirect is removed.`
|
|
1207
|
+
];
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
function stageHints(data) {
|
|
1211
|
+
if (!data.staged) return [];
|
|
1212
|
+
const hints = [STAGED, ...repointedRedirects(data.ops)];
|
|
1213
|
+
if (data.ops.some((op) => op.warnings.length > 0)) hints.push(WARNINGS);
|
|
1214
|
+
if (data.ops.some((op) => op.kind === "shopify.discountCode.create")) hints.push(DISCOUNT);
|
|
1215
|
+
if (data.ops.some((op) => op.kind === "shopify.collection.delete")) hints.push(COLLECTION_DELETE);
|
|
1216
|
+
if (data.ops.some(
|
|
1217
|
+
(op) => op.kind === "shopify.collection.create" && op.warnings.some((warning) => /no products/.test(warning))
|
|
1218
|
+
)) {
|
|
1219
|
+
hints.push(EMPTY_COLLECTION);
|
|
1220
|
+
}
|
|
1221
|
+
return hints;
|
|
1222
|
+
}
|
|
1223
|
+
function changesHints(data) {
|
|
1224
|
+
if (data.changes.length === 0) return ["Baker hasn't changed this store yet."];
|
|
1225
|
+
return data.changes.some((change) => change.undoable) ? ["Undo one with `baker shopify undo <changeId>` (staged like any change, applied when the user publishes)."] : [];
|
|
1226
|
+
}
|
|
1227
|
+
function auditHints(data) {
|
|
1228
|
+
const hints = [];
|
|
1229
|
+
for (const check of data.checks) {
|
|
1230
|
+
if (check.fix?.kind === "staged") hints.push(`Stage the fix for "${check.title}": \`${check.fix.command}\`.`);
|
|
1231
|
+
}
|
|
1232
|
+
for (const check of data.checks) {
|
|
1233
|
+
if (check.fix?.kind === "merchant") {
|
|
1234
|
+
hints.push(
|
|
1235
|
+
`Only the merchant can fix "${check.title}": ${check.fix.task}. Make it a Task with \`baker actions create\`${check.fix.link ? " and include the link" : ""}.`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
for (const check of data.checks) {
|
|
1240
|
+
if (check.fix?.kind === "decision") hints.push(`A person decides "${check.title}": ${check.fix.where}.`);
|
|
1241
|
+
}
|
|
1242
|
+
return hints;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
// src/commands/shopify/writeCommon.ts
|
|
1246
|
+
function failChange(err) {
|
|
1247
|
+
if (err instanceof ApiError) {
|
|
1248
|
+
const refusal = storeChangeRefusal(err.code, err.message);
|
|
1249
|
+
if (refusal) {
|
|
1250
|
+
writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
|
|
1251
|
+
process.exit(1);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
fail(err);
|
|
1255
|
+
}
|
|
1256
|
+
function emitStage(data) {
|
|
1257
|
+
if (!data.staged) {
|
|
1258
|
+
const refusal = approvalRefusal(data.needsApproval);
|
|
1259
|
+
writeJson({
|
|
1260
|
+
ok: false,
|
|
1261
|
+
error: {
|
|
1262
|
+
code: refusal.code,
|
|
1263
|
+
message: data.needsApproval.declined ? "The user said no to this change in the chat." : "This changes money in the store: the user has to say yes to it in the chat first.",
|
|
1264
|
+
fix: refusal.fix
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
process.exit(1);
|
|
1268
|
+
}
|
|
1269
|
+
writeJson({ ok: true, hints: stageHints(data), data });
|
|
1270
|
+
}
|
|
1271
|
+
async function stage(ops) {
|
|
1272
|
+
const chatId = requireChatId();
|
|
1273
|
+
try {
|
|
1274
|
+
const [only] = ops;
|
|
1275
|
+
const data = ops.length === 1 && only ? await apiPost("/api/shopify/draft/stage", { chatId, op: only }) : await apiPost("/api/shopify/draft/stage-batch", { chatId, ops });
|
|
1276
|
+
emitStage(data);
|
|
1277
|
+
} catch (err) {
|
|
1278
|
+
failChange(err);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
function list(value) {
|
|
1282
|
+
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
1283
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
1284
|
+
}
|
|
1285
|
+
function defined(entries) {
|
|
1286
|
+
return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0 && value !== ""));
|
|
1287
|
+
}
|
|
1288
|
+
function payloadsFrom(args2) {
|
|
1289
|
+
if (typeof args2.json !== "string" && typeof args2.file !== "string") return null;
|
|
1290
|
+
return loadJsonPayloads(args2);
|
|
1291
|
+
}
|
|
1292
|
+
var JSON_ARGS = {
|
|
1293
|
+
json: {
|
|
1294
|
+
type: "string",
|
|
1295
|
+
description: 'The change as inline JSON: a payload for the target given (e.g. {"addTags":["summer"]}), or an array of {"kind","target","payload"} items to stage together in one store read',
|
|
1296
|
+
required: false
|
|
1297
|
+
},
|
|
1298
|
+
file: { type: "string", description: "A JSON file holding the same", required: false }
|
|
1299
|
+
};
|
|
1300
|
+
function leaf(schemaName, meta, args2, run) {
|
|
1301
|
+
registerSchema({
|
|
1302
|
+
command: schemaName,
|
|
1303
|
+
description: meta.description,
|
|
1304
|
+
args: Object.fromEntries(
|
|
1305
|
+
Object.entries(args2).map(([name, arg]) => [
|
|
1306
|
+
name,
|
|
1307
|
+
{
|
|
1308
|
+
type: String(arg.type ?? "string"),
|
|
1309
|
+
description: String(arg.description ?? ""),
|
|
1310
|
+
required: arg.required === true
|
|
1311
|
+
}
|
|
1312
|
+
])
|
|
1313
|
+
)
|
|
1314
|
+
});
|
|
1315
|
+
return defineCommand({ meta, args: args2, run: async ({ args: parsed }) => await run(parsed) });
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/commands/shopify/audiences.ts
|
|
1319
|
+
function failAudience(err) {
|
|
1320
|
+
if (err instanceof ApiError) {
|
|
1321
|
+
const refusal = audienceRefusal(err.code, err.message);
|
|
1322
|
+
if (refusal) {
|
|
1323
|
+
writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
|
|
1324
|
+
process.exit(1);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
failChange(err);
|
|
1328
|
+
}
|
|
1329
|
+
var SEGMENTS = ["best", "loyal", "at_risk", "new", "one_time", "all"];
|
|
1330
|
+
var createDescription = "Start here to reach the store's own buyers with ads: stage a Meta or Google Ads audience made of one buyer segment (`baker shopify segments` explains each). Example: baker shopify audiences create --segment at_risk --to meta --account-id act_123. Only buyers who accept email or SMS marketing are sent, hashed; Baker never shows or keeps who they are. Staged until the user publishes the chat; then it is made, filled, and updated daily (--refresh off: filled once). Answers counts: people in the segment, how many accept marketing, how many will be sent.";
|
|
1331
|
+
registerSchema({
|
|
1332
|
+
command: "shopify.audiences.create",
|
|
1333
|
+
description: createDescription,
|
|
1334
|
+
args: {
|
|
1335
|
+
segment: {
|
|
1336
|
+
type: "string",
|
|
1337
|
+
description: "best | loyal | at_risk | new | one_time | all",
|
|
1338
|
+
required: true,
|
|
1339
|
+
enum: SEGMENTS
|
|
1340
|
+
},
|
|
1341
|
+
to: { type: "string", description: "meta | google", required: true, enum: ["meta", "google"] },
|
|
1342
|
+
"account-id": {
|
|
1343
|
+
type: "string",
|
|
1344
|
+
description: "The ad account: act_123\u2026 for Meta, 123-456-7890 for Google Ads",
|
|
1345
|
+
required: true
|
|
1346
|
+
},
|
|
1347
|
+
name: { type: "string", description: 'Its name on the platform (default "Shopify: <segment>")', required: false },
|
|
1348
|
+
refresh: { type: "string", description: "daily (default) | off", required: false, enum: ["daily", "off"] }
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
var createCommand = defineCommand2({
|
|
1352
|
+
meta: { name: "create", description: createDescription },
|
|
1353
|
+
args: {
|
|
1354
|
+
segment: { type: "string", description: "best | loyal | at_risk | new | one_time | all", required: true },
|
|
1355
|
+
to: { type: "string", description: "meta | google", required: true },
|
|
1356
|
+
"account-id": { type: "string", description: "act_123\u2026 for Meta, 123-456-7890 for Google Ads", required: true },
|
|
1357
|
+
name: { type: "string", description: "Its name on the platform", required: false },
|
|
1358
|
+
refresh: { type: "string", description: "daily (default) | off", required: false },
|
|
1359
|
+
full: { type: "boolean", description: "Add the definition that was staged", required: false, default: false }
|
|
1360
|
+
},
|
|
1361
|
+
async run({ args: args2 }) {
|
|
1362
|
+
const parsed = shopifyAudienceDefinitionSchema.safeParse({
|
|
1363
|
+
segment: args2.segment,
|
|
1364
|
+
platform: args2.to,
|
|
1365
|
+
accountId: args2["account-id"],
|
|
1366
|
+
...args2.name ? { name: args2.name } : {},
|
|
1367
|
+
...args2.refresh ? { refresh: args2.refresh } : {}
|
|
1368
|
+
});
|
|
1369
|
+
if (!parsed.success) {
|
|
1370
|
+
const issue = parsed.error.issues[0];
|
|
1371
|
+
failValidation(`${issue?.path.join(".") ?? ""}: ${issue?.message ?? "invalid audience"}`);
|
|
1372
|
+
}
|
|
1373
|
+
const definition = parsed.data;
|
|
1374
|
+
const chatId = requireChatId();
|
|
1375
|
+
try {
|
|
1376
|
+
const data = await apiPost(
|
|
1377
|
+
"/api/shopify/audiences/create",
|
|
1378
|
+
{ chatId, definition },
|
|
1379
|
+
{ timeoutMs: 3e5 }
|
|
1380
|
+
);
|
|
1381
|
+
const compact = {
|
|
1382
|
+
ref: data.ref,
|
|
1383
|
+
staged: data.staged,
|
|
1384
|
+
platform: data.platform,
|
|
1385
|
+
accountId: data.accountId,
|
|
1386
|
+
people: data.counts.people,
|
|
1387
|
+
withConsent: data.counts.withConsent,
|
|
1388
|
+
willSend: data.counts.willSend,
|
|
1389
|
+
...data.warnings.length > 0 ? { warnings: data.warnings } : {},
|
|
1390
|
+
...args2.full ? { summary: data.summary, counts: data.counts, definition } : {}
|
|
1391
|
+
};
|
|
1392
|
+
writeJson({ ok: true, hints: audienceCreateHints(data), data: compact });
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
failAudience(err);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
var listDescription = "The store's buyer audiences on Meta and Google Ads: each one's id, segment, ad account, whether it updates daily, and its last update in counts (people, how many accept marketing, how many were sent). With a chat, also this chat's staged audience changes. Example: baker shopify audiences list";
|
|
1399
|
+
registerSchema({
|
|
1400
|
+
command: "shopify.audiences.list",
|
|
1401
|
+
description: listDescription,
|
|
1402
|
+
args: {
|
|
1403
|
+
full: {
|
|
1404
|
+
type: "boolean",
|
|
1405
|
+
description: "Add removed audiences, when each was made and its consent rule",
|
|
1406
|
+
required: false
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
var listCommand = defineCommand2({
|
|
1411
|
+
meta: { name: "list", description: listDescription },
|
|
1412
|
+
args: { full: { type: "boolean", description: "Add removed ones and more detail", required: false, default: false } },
|
|
1413
|
+
async run({ args: args2 }) {
|
|
1414
|
+
try {
|
|
1415
|
+
const data = await apiGet(
|
|
1416
|
+
"/api/shopify/audiences",
|
|
1417
|
+
params({ chatId: optionalChatId(), full: args2.full })
|
|
1418
|
+
);
|
|
1419
|
+
writeJson({ ok: true, hints: audienceListHints(data), data });
|
|
1420
|
+
} catch (err) {
|
|
1421
|
+
failAudience(err);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
var removeDescription = "Stage removing a buyer audience: Baker stops updating it and deletes it from the ad platform when the user publishes (--keep-on-platform: only stop updating). Ads using it stop reaching it once it is deleted. Example: baker shopify audiences remove <id>";
|
|
1426
|
+
registerSchema({
|
|
1427
|
+
command: "shopify.audiences.remove",
|
|
1428
|
+
description: removeDescription,
|
|
1429
|
+
args: {
|
|
1430
|
+
id: { type: "positional", description: "The audience's id (`baker shopify audiences list`)", required: true },
|
|
1431
|
+
"keep-on-platform": {
|
|
1432
|
+
type: "boolean",
|
|
1433
|
+
description: "Leave it on the ad platform, only stop updating it",
|
|
1434
|
+
required: false
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
var removeCommand = defineCommand2({
|
|
1439
|
+
meta: { name: "remove", description: removeDescription },
|
|
1440
|
+
args: {
|
|
1441
|
+
id: { type: "positional", description: "The audience's id", required: true },
|
|
1442
|
+
"keep-on-platform": { type: "boolean", description: "Only stop updating it", required: false, default: false }
|
|
1443
|
+
},
|
|
1444
|
+
async run({ args: args2 }) {
|
|
1445
|
+
const chatId = requireChatId();
|
|
1446
|
+
try {
|
|
1447
|
+
const data = await apiPost("/api/shopify/audiences/remove", {
|
|
1448
|
+
chatId,
|
|
1449
|
+
id: args2.id,
|
|
1450
|
+
...args2["keep-on-platform"] ? { keepOnPlatform: true } : {}
|
|
1451
|
+
});
|
|
1452
|
+
writeJson({
|
|
1453
|
+
ok: true,
|
|
1454
|
+
hints: ["Staged on this chat until Publish: the audience keeps updating until the user publishes it."],
|
|
1455
|
+
data
|
|
1456
|
+
});
|
|
1457
|
+
} catch (err) {
|
|
1458
|
+
failAudience(err);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
var refreshDescription = "Update a published buyer audience now instead of waiting for its daily update: Baker reads the segment again and sends who is in it now (Meta: the audience then holds exactly them; Google: they are added, people who unsubscribed are taken out, and anyone who only left the group drops off once Google's membership period runs out \u2014 30 days on a daily audience). Not staged \u2014 the audience was approved when it was published. Example: baker shopify audiences refresh <id>; read the outcome with `baker shopify audiences list` a minute later.";
|
|
1463
|
+
registerSchema({
|
|
1464
|
+
command: "shopify.audiences.refresh",
|
|
1465
|
+
description: refreshDescription,
|
|
1466
|
+
args: {
|
|
1467
|
+
id: { type: "positional", description: "The audience's id (`baker shopify audiences list`)", required: true }
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
var refreshCommand = defineCommand2({
|
|
1471
|
+
meta: { name: "refresh", description: refreshDescription },
|
|
1472
|
+
args: { id: { type: "positional", description: "The audience's id", required: true } },
|
|
1473
|
+
async run({ args: args2 }) {
|
|
1474
|
+
try {
|
|
1475
|
+
const data = await apiPost("/api/shopify/audiences/refresh", { id: args2.id });
|
|
1476
|
+
writeJson({
|
|
1477
|
+
ok: true,
|
|
1478
|
+
hints: ["Updating now; `baker shopify audiences list` shows the outcome under lastRun in a minute or two."],
|
|
1479
|
+
data
|
|
1480
|
+
});
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
failAudience(err);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
var audiencesCommand = defineCommand2({
|
|
1487
|
+
meta: {
|
|
1488
|
+
name: "audiences",
|
|
1489
|
+
description: "The store's buyers as Meta and Google Ads audiences: create (staged), list, remove (staged), refresh. Example: baker shopify audiences create --segment best --to meta --account-id act_123"
|
|
1490
|
+
},
|
|
1491
|
+
subCommands: { create: createCommand, list: listCommand, remove: removeCommand, refresh: refreshCommand }
|
|
1492
|
+
});
|
|
1493
|
+
|
|
1494
|
+
// src/commands/shopify/buyers.ts
|
|
1495
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
1496
|
+
|
|
1497
|
+
// src/commands/shopify/buyerHints.ts
|
|
1498
|
+
var AUDIENCE_PATH = "To show ads to these buyers, stage an audience of a segment on Meta or Google Ads (`baker shopify audiences create --segment best --to meta|google --account-id <id>`): only buyers who accept marketing are sent, staged until Publish. On Meta, a lookalike of it (`baker ads meta audiences create --subtype LOOKALIKE`) reaches more people like them once it holds 100 people from one country.";
|
|
1499
|
+
var MIN_ORDERS_TO_SCALE = 5;
|
|
1500
|
+
function storeFactsHint(currency) {
|
|
1501
|
+
return `These are the store's own orders \u2014 never a demo or sample store: build the answer on them and quote their numbers${currency ? ` in ${currency}` : ""}. If Knowledge describes other products or markets, say the two disagree and ask which is current \u2014 never drop these numbers for it.`;
|
|
1502
|
+
}
|
|
1503
|
+
var percent = (value) => `${Math.round(value * 100)}%`;
|
|
1504
|
+
function buyerHistoryHints(history, basis) {
|
|
1505
|
+
const hints = [];
|
|
1506
|
+
if (history.state === "importing") {
|
|
1507
|
+
hints.push(
|
|
1508
|
+
`Baker is still bringing in this store's past orders (${history.orders} so far): older periods, repeat buyers and cohorts are incomplete \u2014 say so rather than reporting a drop.`
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
if (history.state === "failed") {
|
|
1512
|
+
hints.push("Baker couldn't finish bringing in this store's past orders; older periods may be incomplete.");
|
|
1513
|
+
}
|
|
1514
|
+
if (basis === "last_60_days") {
|
|
1515
|
+
hints.push(
|
|
1516
|
+
"Shopify gives Baker this store's last 60 days of orders only: older behaviour is not here, and a buyer whose earlier orders are older counts as new."
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
return hints;
|
|
1520
|
+
}
|
|
1521
|
+
function countriesHints(answer) {
|
|
1522
|
+
const hints = buyerHistoryHints(answer.history, answer.basis);
|
|
1523
|
+
if (answer.total.orders === 0) {
|
|
1524
|
+
hints.push("No orders were placed in this period.");
|
|
1525
|
+
return hints;
|
|
1526
|
+
}
|
|
1527
|
+
hints.push(storeFactsHint(answer.currency));
|
|
1528
|
+
const money = (amount) => `${answer.currency ?? ""} ${amount}`.trim();
|
|
1529
|
+
const growing = answer.rows.filter((row) => row.orders >= MIN_ORDERS_TO_SCALE && (row.ordersGrowth ?? 0) > 0).sort((a, b) => (b.ordersGrowth ?? 0) - (a.ordersGrowth ?? 0))[0];
|
|
1530
|
+
if (growing && answer.level === "country") {
|
|
1531
|
+
hints.push(
|
|
1532
|
+
`Scale ${growing.name}: its orders grew ${percent(growing.ordersGrowth ?? 0)} against the period before (${growing.orders} orders, ${money(growing.net)}). Check which campaigns already target it (\`baker ads meta campaigns\`, \`baker ads google query --preset campaign-performance\`) before staging a change.`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
const average = answer.total.averageOrder;
|
|
1536
|
+
const promising = answer.rows.filter((row) => row.orders < MIN_ORDERS_TO_SCALE && average > 0 && row.averageOrder >= 1.5 * average).sort((a, b) => b.averageOrder - a.averageOrder)[0];
|
|
1537
|
+
if (promising && answer.level === "country") {
|
|
1538
|
+
hints.push(
|
|
1539
|
+
`Test ${promising.name}: its average order is ${money(promising.averageOrder)}, ${(promising.averageOrder / average).toFixed(1)}\xD7 the store's, from only ${promising.orders} orders \u2014 a small budget there would show whether it scales.`
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
if (answer.unknown && answer.total.orders > 0 && answer.unknown.orders / answer.total.orders > 0.25) {
|
|
1543
|
+
const lack = answer.level === "country" ? "have no address Baker could read (Unknown)" : `name no ${answer.level} on their address (${answer.unknown.name})`;
|
|
1544
|
+
hints.push(
|
|
1545
|
+
`${answer.unknown.orders} of these orders ${lack}: say so rather than leaving them out of a place's share.`
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
return hints;
|
|
1549
|
+
}
|
|
1550
|
+
function bestSellersHints(answer) {
|
|
1551
|
+
const hints = buyerHistoryHints(answer.history, answer.basis);
|
|
1552
|
+
const leader = answer.repeat?.[0];
|
|
1553
|
+
if (leader && leader.buyersAgain > 0) {
|
|
1554
|
+
hints.push(
|
|
1555
|
+
`Lead a landing with "${leader.title}" (find it with \`baker shopify products --search\`): ${percent(leader.share)} of the buyers whose first order held it came back.`
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
const top = answer.products?.[0];
|
|
1559
|
+
if (top && top.units > 0) {
|
|
1560
|
+
hints.push(
|
|
1561
|
+
`"${top.title}" is the best seller (${top.units} units). For which first buys bring buyers back, run \`baker shopify best-sellers --view repeat\`; for what is losing steam, \`--view momentum\`.`
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
const losing = answer.momentum?.[0];
|
|
1565
|
+
if (losing) {
|
|
1566
|
+
hints.push(
|
|
1567
|
+
`"${losing.title}" sold ${losing.units} against ${losing.previousUnits} the period before: check its stock (\`baker shopify stock\`) and the ads that promote it before cutting it.`
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
const sellers = answer.products ?? answer.variants ?? [];
|
|
1571
|
+
if (sellers.some((row) => row.units > 0)) hints.push(storeFactsHint(answer.currency));
|
|
1572
|
+
const withRefunds = sellers.filter((row) => row.refundedUnits > 0).sort((a, b) => b.refundRate - a.refundRate).slice(0, 5);
|
|
1573
|
+
if (withRefunds.length > 0) {
|
|
1574
|
+
const money = (amount) => `${answer.currency ?? ""} ${amount}`.trim();
|
|
1575
|
+
hints.push(
|
|
1576
|
+
`Refunds: ${withRefunds.map((row) => `"${row.title}" ${percent(row.refundRate)} of units (${money(row.refunded)})`).join(", ")}. Only a product not named here had no refunds in the period.`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
const refunded = answer.products?.find((row) => row.units >= 5 && row.refundRate >= 0.2);
|
|
1580
|
+
if (refunded) {
|
|
1581
|
+
hints.push(
|
|
1582
|
+
`"${refunded.title}" comes back often (${percent(refunded.refundRate)} of units refunded): don't lead a campaign with it before finding out why.`
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
return hints;
|
|
1586
|
+
}
|
|
1587
|
+
function bestBuyersHint(best) {
|
|
1588
|
+
if (!best || best.buyers === 0) return [];
|
|
1589
|
+
const countries = best.topCountries.map((country) => country.name).join(", ");
|
|
1590
|
+
const products = best.topFirstProducts.map((product) => `"${product.title}"`).join(", ");
|
|
1591
|
+
if (!countries && !products) return [];
|
|
1592
|
+
return [
|
|
1593
|
+
`Find more buyers like your best (${best.buyers}): ${countries ? `target ${countries}` : ""}${countries && products ? "; " : ""}${products ? `lead with ${products}, what their first orders held` : ""}.`
|
|
1594
|
+
];
|
|
1595
|
+
}
|
|
1596
|
+
function segmentsHints(answer) {
|
|
1597
|
+
const hints = buyerHistoryHints(answer.history, answer.basis);
|
|
1598
|
+
hints.push(...bestBuyersHint(answer.segments.find((segment) => segment.segment === "best")));
|
|
1599
|
+
const atRisk = answer.segments.find((segment) => segment.segment === "at_risk");
|
|
1600
|
+
if (atRisk && atRisk.buyers > 0) {
|
|
1601
|
+
hints.push(
|
|
1602
|
+
`${atRisk.buyers} repeat buyers have not ordered in ${Math.round(2 * answer.thresholds.gapDays)} days (At risk): a win-back offer to them is worth more than a new buyer of the same value.`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
if (answer.buyers > 0) hints.push(AUDIENCE_PATH);
|
|
1606
|
+
return hints;
|
|
1607
|
+
}
|
|
1608
|
+
function buyersHints(answer) {
|
|
1609
|
+
const hints = buyerHistoryHints(answer.history, answer.basis);
|
|
1610
|
+
if (answer.summary.orders === 0) {
|
|
1611
|
+
hints.push("No orders were placed in this period.");
|
|
1612
|
+
return hints;
|
|
1613
|
+
}
|
|
1614
|
+
hints.push(storeFactsHint(answer.currency));
|
|
1615
|
+
if (answer.acquisition?.by === "campaign" && answer.acquisition.rows.length > 0) {
|
|
1616
|
+
hints.push(
|
|
1617
|
+
"Compare each campaign's lifetime value with what it cost for the same dates (`baker ads meta insights`, `baker ads google query --preset campaign-performance`): Baker keeps no ad spend."
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
if (!answer.acquisition) {
|
|
1621
|
+
hints.push(
|
|
1622
|
+
"For lifetime value by the campaign or channel that brought each buyer, add `--by campaign` or `--by channel`."
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
if (answer.summary.guestOrders > 0 && answer.summary.guestOrders >= answer.summary.orders / 2) {
|
|
1626
|
+
hints.push(
|
|
1627
|
+
`${answer.summary.guestOrders} of ${answer.summary.orders} orders were placed as guests: they count in sales, never as buyers, so repeat rates read low.`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
if (answer.summary.buyers > 0) hints.push(AUDIENCE_PATH);
|
|
1631
|
+
return hints;
|
|
1632
|
+
}
|
|
1633
|
+
function cohortsHints(answer) {
|
|
1634
|
+
const hints = buyerHistoryHints(answer.history, answer.basis);
|
|
1635
|
+
const reached = answer.cohorts.filter((cohort) => cohort.retention.m3 !== null && cohort.size >= 5);
|
|
1636
|
+
if (reached.length >= 2) {
|
|
1637
|
+
const best = [...reached].sort((a, b) => (b.retention.m3 ?? 0) - (a.retention.m3 ?? 0))[0];
|
|
1638
|
+
if (best) {
|
|
1639
|
+
hints.push(
|
|
1640
|
+
`Buyers who first ordered in ${best.month} came back best (${percent(best.retention.m3 ?? 0)} ordered again in their third month): look at what was running then (\`baker shopify buyers --by campaign\`).`
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return hints;
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/commands/shopify/hints.ts
|
|
1648
|
+
var RECONNECT = 'Baker has lost access to this Shopify store, so these products may be out of date. Say so, and ask the user to reconnect with `request_connection` { platform: "shopify" } \u2014 the card offers Reconnect.';
|
|
1649
|
+
var OWN_STOREFRONT = "This store sells through its own storefront, not a Shopify online store: Baker links to its product pages once the website's address is set in Brain \u2192 Store (`buyUrlKind` says where each link goes); until then, link to the product pages on the client's own site, and ask the user for them if you don't know them.";
|
|
1650
|
+
function day(iso) {
|
|
1651
|
+
return iso ? iso.slice(0, 10) : null;
|
|
1652
|
+
}
|
|
1653
|
+
function progress(store) {
|
|
1654
|
+
const done = store.importProgress?.done;
|
|
1655
|
+
const total = store.importProgress?.total;
|
|
1656
|
+
if (done === void 0) return "";
|
|
1657
|
+
return total === void 0 ? ` (${done} so far)` : ` (${done} of ${total})`;
|
|
1658
|
+
}
|
|
1659
|
+
function storeStateHints(store) {
|
|
1660
|
+
const hints = [];
|
|
1661
|
+
if (store.connection === "needs_reconnect") hints.push(RECONNECT);
|
|
1662
|
+
if (store.connection === "disconnected") {
|
|
1663
|
+
const last = day(store.lastActivity);
|
|
1664
|
+
hints.push(
|
|
1665
|
+
`This Shopify store is disconnected; these are its products as Baker last saw them${last ? ` (last activity ${last})` : ""}. Don't present prices or stock as current.`
|
|
1666
|
+
);
|
|
1667
|
+
}
|
|
1668
|
+
if (store.catalog === "starting" || store.catalog === "importing") {
|
|
1669
|
+
hints.push(
|
|
1670
|
+
`Baker is still bringing in this store's products${progress(store)}; the list is incomplete \u2014 say so rather than concluding a product is missing.`
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
if (store.catalog === "failed") {
|
|
1674
|
+
hints.push("Baker couldn't finish reading this store's products; the list may be incomplete.");
|
|
1675
|
+
}
|
|
1676
|
+
if (store.unreadable !== void 0) {
|
|
1677
|
+
const count = store.unreadableCapped ? `${store.unreadable}+` : String(store.unreadable);
|
|
1678
|
+
hints.push(
|
|
1679
|
+
`Baker couldn't read ${count} of this store's products (it keeps retrying); they are missing from these answers \u2014 never tell the user a product doesn't exist or that the catalog is complete.`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
if (store.checksFailing) {
|
|
1683
|
+
hints.push("Baker couldn't check this store for changes lately; recent edits in Shopify may not show yet.");
|
|
1684
|
+
}
|
|
1685
|
+
if (store.storefront === "own") hints.push(OWN_STOREFRONT);
|
|
1686
|
+
return hints;
|
|
1687
|
+
}
|
|
1688
|
+
function cleanAndReady(store) {
|
|
1689
|
+
return store.catalog === "ready" && store.unreadable === void 0 && !store.checksFailing;
|
|
1690
|
+
}
|
|
1691
|
+
function productsHints(answer, options) {
|
|
1692
|
+
const hints = [];
|
|
1693
|
+
const notForSale = answer.products.filter((row) => row.availability === "not_for_sale").length;
|
|
1694
|
+
const linkOnly = answer.products.filter((row) => row.availability === "link_only").length;
|
|
1695
|
+
if (notForSale > 0) {
|
|
1696
|
+
hints.push(
|
|
1697
|
+
`${notForSale} of these can't be bought on the online store (draft, archived or hidden from it) \u2014 don't send ads or pages to them.`
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
if (linkOnly > 0) {
|
|
1701
|
+
hints.push(
|
|
1702
|
+
`${linkOnly} of these are only reachable by direct link \u2014 fine to link from an ad or a landing; they won't show in the store's search or collections.`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (answer.nextCursor)
|
|
1706
|
+
hints.push(`More products: rerun with \`--cursor ${answer.nextCursor}\` and the same filters.`);
|
|
1707
|
+
if (answer.products.length === 0 && !answer.nextCursor) {
|
|
1708
|
+
if (options.paged) hints.push("No more products: the earlier pages were the whole list.");
|
|
1709
|
+
else if (options.filtered) hints.push("No products match; drop `--search`/`--stock`/`--collection`.");
|
|
1710
|
+
else if (cleanAndReady(answer.store)) hints.push("The store has no products yet.");
|
|
1711
|
+
else {
|
|
1712
|
+
hints.push(
|
|
1713
|
+
"Baker has no products for this store right now \u2014 see the notes above; don't tell the user the store is empty."
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
return hints;
|
|
1718
|
+
}
|
|
1719
|
+
function photoHints(product) {
|
|
1720
|
+
const hints = [];
|
|
1721
|
+
if (product.photos.length > 0) {
|
|
1722
|
+
const ids = product.photos.map((photo) => photo.imageId).join(" ");
|
|
1723
|
+
hints.push(
|
|
1724
|
+
`Its photos are already in the image library. For a landing: \`mkdir -p src/pages/<page>/_images && baker images download ${ids} --out src/pages/<page>/_images/\`; for a creative, pass the imageIds as references and set \`product: ${product.handle}\` in its idea, so the store's price, was price and saving count as sourced. Never use a cdn.shopify.com link \u2014 pages refuse it.`
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
if (product.photosNotBrought > 0) {
|
|
1728
|
+
hints.push(
|
|
1729
|
+
`${product.photosNotBrought} more photos stay in Shopify (Baker keeps up to 10 per product); use the ones listed.`
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (product.photosAdding > 0) {
|
|
1733
|
+
hints.push(`${product.photosAdding} photos are still being added; run this again in a minute if you need them.`);
|
|
1734
|
+
}
|
|
1735
|
+
return hints;
|
|
1736
|
+
}
|
|
1737
|
+
function stockHintsFor(product) {
|
|
1738
|
+
const hints = [];
|
|
1739
|
+
if (product.stock.optionsOut > 0) {
|
|
1740
|
+
const names = product.outOptions.map((option) => option.title).join(", ");
|
|
1741
|
+
hints.push(
|
|
1742
|
+
product.stock.state === "out_of_stock" ? "Every option is out of stock \u2014 don't promote it; `baker shopify stock` lists every product like this." : `Options out of stock: ${names} \u2014 don't promote them; \`baker shopify stock\` lists every product like this.`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
if (product.outOptionsMore) {
|
|
1746
|
+
hints.push(
|
|
1747
|
+
`${product.outOptionsMore} more options are out of stock: \`baker shopify product ${product.productId} --out-of-stock\` (then \`--variants-cursor\` for the next page).`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
if (product.variantsNextCursor) {
|
|
1751
|
+
hints.push(`More options: rerun with \`--variants-cursor ${product.variantsNextCursor}\`.`);
|
|
1752
|
+
}
|
|
1753
|
+
return hints;
|
|
1754
|
+
}
|
|
1755
|
+
var BUY_NOTES = {
|
|
1756
|
+
password_protected: "The store asks for a password before anyone can see it: a shopper following a buy link meets it first \u2014 tell the user before sending traffic.",
|
|
1757
|
+
subscription_optional: "It can also be bought by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere adds it once.",
|
|
1758
|
+
own_storefront: "The store sells through its own website: `buyUrlKind` says where each link goes \u2014 its product page, or its cart page once its developers confirm it in Brain \u2192 Store.",
|
|
1759
|
+
details_pending: "Baker hasn't read this product's details yet (subscriptions among them): its buy links may change.",
|
|
1760
|
+
subscription_only: "It's sold only by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere is its product page, where the shopper picks one."
|
|
1761
|
+
};
|
|
1762
|
+
function buyHints(product) {
|
|
1763
|
+
const hints = [];
|
|
1764
|
+
if (product.variants.some((variant) => variant.buyUrl)) {
|
|
1765
|
+
hints.push(
|
|
1766
|
+
`On a landing, place \`<ShopifyProduct product="${product.handle}" />\` (and \`<ShopifyBuyButton product="${product.handle}">\` for a second button): it shows the options, price and stock, and its button hands the shopper to the store. Never put a \`buyUrl\` on a landing yourself, and never build an option picker. See /landing \`.claude/skills/landing/references/verticals/ecommerce.md\`.`,
|
|
1767
|
+
`\`buyUrl\` is for links outside a landing \u2014 an ad, an email, a message: one per option; never \`storeUrl\` (a page to look at), never a Shopify link built yourself (it follows the store's setting: ${product.buyDestination.replace("_", " ")}; \`--quantity <n>\` sets how many).`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
const notes = new Set(product.variants.flatMap((variant) => variant.notes ?? []));
|
|
1771
|
+
for (const note of notes) hints.push(BUY_NOTES[note]);
|
|
1772
|
+
return hints;
|
|
1773
|
+
}
|
|
1774
|
+
function contentHints(product) {
|
|
1775
|
+
const hints = [];
|
|
1776
|
+
const [video] = product.videos;
|
|
1777
|
+
if (video) {
|
|
1778
|
+
hints.push(
|
|
1779
|
+
`Its videos are already in the video library: place one with \`<Video playbackId="${video.playbackId}" />\` \u2014 never a Shopify video link.`
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
if (product.videosNotBrought > 0) {
|
|
1783
|
+
hints.push(
|
|
1784
|
+
`${product.videosNotBrought} more videos stay in Shopify (Baker keeps up to 5 per product); use the ones listed.`
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1787
|
+
if (product.videosAdding > 0) hints.push(`${product.videosAdding} videos are still being added.`);
|
|
1788
|
+
if (product.contentSummary && !product.content) {
|
|
1789
|
+
hints.push(
|
|
1790
|
+
"For a landing or an ad, read the whole of it with `--content` (formatted description, custom fields, subscriptions, translations)."
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1793
|
+
const truncated = product.content?.fields.find((field) => field.truncated);
|
|
1794
|
+
if (truncated) {
|
|
1795
|
+
const option = truncated.variantId ? ` --variant ${truncated.variantId}` : "";
|
|
1796
|
+
hints.push(`Some fields are shown in part: read one whole with \`--field ${truncated.key}${option}\`.`);
|
|
1797
|
+
}
|
|
1798
|
+
if (product.content?.unreadable?.length || product.content?.fields.some((field) => field.unreadable)) {
|
|
1799
|
+
hints.push(
|
|
1800
|
+
"Some content isn't readable yet: ask the user to open Baker in their Shopify admin and accept the new permissions."
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
return hints;
|
|
1804
|
+
}
|
|
1805
|
+
function productHints(product, store) {
|
|
1806
|
+
const hints = [...photoHints(product), ...stockHintsFor(product), ...buyHints(product), ...contentHints(product)];
|
|
1807
|
+
if (product.availability === "not_for_sale") {
|
|
1808
|
+
hints.push(
|
|
1809
|
+
`Can't be bought on the online store (${(product.status ?? "hidden").toLowerCase()}): a page or ad linking to it sends people to a page they can't buy from.`
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
if (product.availability === "link_only") {
|
|
1813
|
+
hints.push(
|
|
1814
|
+
`This product is only reachable by direct link${product.storeUrl ? ` (${product.storeUrl})` : ""} \u2014 fine to link from an ad or a landing; it won't show in the store's search or collections.`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
if (product.compareAt) {
|
|
1818
|
+
hints.push(`On sale: was ${product.compareAt}, now ${product.price}.`);
|
|
1819
|
+
}
|
|
1820
|
+
if (product.pricesByCountry) {
|
|
1821
|
+
hints.push(
|
|
1822
|
+
"Prices differ by country \u2014 quote the country's price from pricesByCountry, never convert the home price."
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
if (store.storefront === "own" && !product.storeUrl) {
|
|
1826
|
+
hints.push("Ask the user for this product's page on their own site before linking to it.");
|
|
1827
|
+
}
|
|
1828
|
+
return hints;
|
|
1829
|
+
}
|
|
1830
|
+
function stockHints(answer, limit2) {
|
|
1831
|
+
const hints = [];
|
|
1832
|
+
const any = answer.outOfStock.length > 0 || answer.partlyOut.length > 0;
|
|
1833
|
+
const unfinished = answer.more.outOfStock || answer.more.partlyOut;
|
|
1834
|
+
const pageThrough = "page through with `baker shopify products --stock out_of_stock` / `--stock partly_out` (and the same `--collection`)";
|
|
1835
|
+
if (any) {
|
|
1836
|
+
hints.push(
|
|
1837
|
+
"Before promoting these, check which ads and pages point at them; pausing or swapping them is a staged change."
|
|
1838
|
+
);
|
|
1839
|
+
} else if (!unfinished) hints.push("Nothing is out of stock.");
|
|
1840
|
+
if (unfinished) {
|
|
1841
|
+
const full = answer.more.outOfStock && answer.outOfStock.length >= limit2 || answer.more.partlyOut && answer.partlyOut.length >= limit2;
|
|
1842
|
+
hints.push(
|
|
1843
|
+
full ? `More than ${limit2} products in a list; raise \`--limit\` (up to 200) or ${pageThrough}.` : `Baker didn't check every product in one go, so these lists may be missing some \u2014 don't say nothing else is out of stock; ${pageThrough}.`
|
|
1844
|
+
);
|
|
1845
|
+
}
|
|
1846
|
+
const big = answer.partlyOut.find((row) => row.outOptionsMore);
|
|
1847
|
+
if (big) {
|
|
1848
|
+
hints.push(
|
|
1849
|
+
`Not every out-of-stock option is listed: \`baker shopify product ${big.productId} --out-of-stock\` lists them all.`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
return hints;
|
|
1853
|
+
}
|
|
1854
|
+
function storeHints(summary) {
|
|
1855
|
+
const hints = ["Share the Products page link (productsUrl) when the user wants to browse the catalog."];
|
|
1856
|
+
if (summary.unreadable?.length) {
|
|
1857
|
+
hints.push(
|
|
1858
|
+
"Some of the store's content isn't readable yet: ask the user to open Baker in their Shopify admin and accept the new permissions."
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
if (summary.passwordProtected) {
|
|
1862
|
+
hints.push(
|
|
1863
|
+
"The store asks for a password before anyone can see it: shoppers from a landing or an ad meet it first."
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
if (summary.capped) {
|
|
1867
|
+
const atCeiling = [
|
|
1868
|
+
summary.outOfStock >= SHOPIFY_STOCK_COUNT_CAP ? `${SHOPIFY_STOCK_COUNT_CAP} or more products out of stock` : null,
|
|
1869
|
+
summary.partlyOut >= SHOPIFY_STOCK_COUNT_CAP ? `${SHOPIFY_STOCK_COUNT_CAP} or more products partly out` : null,
|
|
1870
|
+
summary.collections >= SHOPIFY_COLLECTION_COUNT_CAP ? `${SHOPIFY_COLLECTION_COUNT_CAP} or more collections` : null
|
|
1871
|
+
].filter((count) => count !== null);
|
|
1872
|
+
hints.push(
|
|
1873
|
+
`Some counts stopped at Baker's ceiling: say ${atCeiling.join(", ")} \u2014 never the number as exact. \`baker shopify stock\` and \`baker shopify products --stock out_of_stock\` page through them.`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
return hints;
|
|
1877
|
+
}
|
|
1878
|
+
function timingHints(answer) {
|
|
1879
|
+
const peak = [...answer.rows].sort((a, b) => b.orders - a.orders)[0];
|
|
1880
|
+
if (!peak || peak.orders === 0) return [];
|
|
1881
|
+
const zone = answer.window.timezone ? `, ${answer.window.timezone}` : "";
|
|
1882
|
+
const when = answer.by === "hour" ? `at ${peak.label}` : answer.by === "month_of_year" ? `in ${peak.label}` : `on ${peak.label}`;
|
|
1883
|
+
return [`Most orders are placed ${when} (store time${zone}): weight budgets and sends towards it.`];
|
|
1884
|
+
}
|
|
1885
|
+
function salesHints(answer) {
|
|
1886
|
+
const hints = [];
|
|
1887
|
+
if (answer.ordersUnreadable) {
|
|
1888
|
+
hints.push(
|
|
1889
|
+
"Baker can't read this store's orders right now, so these sales may be incomplete. Ask the user to reconnect the store with `request_connection` { platform: \"shopify\" }."
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
if (answer.history === "importing") {
|
|
1893
|
+
hints.push(
|
|
1894
|
+
"Baker is still bringing in this store's past orders; earlier periods are incomplete \u2014 say so rather than reporting a drop."
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
if (answer.totals.orders === 0) {
|
|
1898
|
+
hints.push("No orders were paid in this period.");
|
|
1899
|
+
return hints;
|
|
1900
|
+
}
|
|
1901
|
+
if (answer.by === "weekday" || answer.by === "hour" || answer.by === "month_of_year") {
|
|
1902
|
+
return [...hints, ...timingHints(answer)];
|
|
1903
|
+
}
|
|
1904
|
+
const notBaker = ["meta", "google"].filter((family) => answer.salesReporting[family] !== "baker");
|
|
1905
|
+
if (notBaker.length > 0) {
|
|
1906
|
+
const names = notBaker.map((family) => family === "meta" ? "Meta" : "Google").join(" and ");
|
|
1907
|
+
hints.push(
|
|
1908
|
+
`Baker only reports these sales; it does not send them to ${names}. A manager chooses in Brain \u2192 Store whether Baker sends them (where Shopify's own channel already reports sales, its purchase tracking has to be turned off first). Never set up a purchase send yourself.`
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
const unattributed = answer.rows.filter((row) => row.credit === "unattributed").reduce((total, row) => total + row.net, 0);
|
|
1912
|
+
if ((answer.by === "campaign" || answer.by === "landing") && answer.totals.net > 0 && unattributed / answer.totals.net > 0.5) {
|
|
1913
|
+
hints.push(
|
|
1914
|
+
"Most of these sales came from no campaign Baker found: buyers who came straight to the store, or from links without campaign tags. Say so rather than crediting them to a campaign; links with UTM tags and Baker landings make the next ones traceable."
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
const leading = answer.by === "landing" ? answer.rows.find((row) => row.credit === "landing") : void 0;
|
|
1918
|
+
if (leading && leading.net > 0) {
|
|
1919
|
+
hints.push(
|
|
1920
|
+
`"${leading.label}" brought the most sales of the Baker pages (${answer.currency ?? ""} ${leading.net}). It is the page to learn from before building the next one.`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
return hints;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// src/commands/shopify/buyers.ts
|
|
1927
|
+
var PERIOD_NOTE = "Orders placed in the period, in the store's own time, with what was refunded on them since (Shopify's own reports date a refund when it happens instead, so a short period can differ by the refunds that cross its edges).";
|
|
1928
|
+
var periodArgs = {
|
|
1929
|
+
days: { type: "number", description: "The last N days, 1-3650 (default 365)", required: false },
|
|
1930
|
+
all: { type: "boolean", description: "Every order the store has", required: false, default: false },
|
|
1931
|
+
country: {
|
|
1932
|
+
type: "string",
|
|
1933
|
+
description: "Only orders to this country: its two-letter code, like DE",
|
|
1934
|
+
required: false
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
var periodFlags = {
|
|
1938
|
+
days: { type: "string", description: "The last N days, 1-3650 (default 365)", required: false },
|
|
1939
|
+
all: { type: "boolean", description: "Every order the store has", required: false, default: false },
|
|
1940
|
+
country: { type: "string", description: "Only orders to this country (two-letter code, like DE)", required: false }
|
|
1941
|
+
};
|
|
1942
|
+
function split(answer) {
|
|
1943
|
+
const { store, window, history, basis, ...data } = answer;
|
|
1944
|
+
return { data, meta: { store, window, history, basis } };
|
|
1945
|
+
}
|
|
1946
|
+
registerSchema({
|
|
1947
|
+
command: "shopify.buyers",
|
|
1948
|
+
description: `Start here for 'who buys from us', 'how many come back' and 'which campaign brings the best customers': the store's buyers in the period \u2014 how many, how many were new, repeat rate, days to a second order, guest orders \u2014 their segments (best, loyal, at risk, new, one-time, by this store's own rhythm) and the top buyers as numbered customers with their country, lifetime spend and the channel of their first order. --by campaign|channel adds lifetime value per acquisition campaign or channel (Baker keeps no ad spend: read it with the ads commands for the same dates). ${PERIOD_NOTE} A buyer is a Shopify customer: guest orders count in money, never as buyers.`,
|
|
1949
|
+
args: {
|
|
1950
|
+
...periodArgs,
|
|
1951
|
+
segment: {
|
|
1952
|
+
type: "string",
|
|
1953
|
+
description: "Only one segment's buyers: best | loyal | at_risk | new | one_time",
|
|
1954
|
+
required: false,
|
|
1955
|
+
enum: ["best", "loyal", "at_risk", "new", "one_time"]
|
|
1956
|
+
},
|
|
1957
|
+
by: {
|
|
1958
|
+
type: "string",
|
|
1959
|
+
description: "Lifetime value by the campaign or channel of each buyer's first order",
|
|
1960
|
+
required: false,
|
|
1961
|
+
enum: ["campaign", "channel"]
|
|
1962
|
+
},
|
|
1963
|
+
channel: {
|
|
1964
|
+
type: "string",
|
|
1965
|
+
description: "Only buyers who first came through this channel (a key from --by channel)",
|
|
1966
|
+
required: false
|
|
1967
|
+
},
|
|
1968
|
+
sort: {
|
|
1969
|
+
type: "string",
|
|
1970
|
+
description: "net (lifetime spend, default) | orders | recent (last order) | first (first order)",
|
|
1971
|
+
required: false,
|
|
1972
|
+
enum: ["net", "orders", "recent", "first"]
|
|
1973
|
+
},
|
|
1974
|
+
limit: { type: "number", description: "Buyers listed, 1-100 (default 10)", required: false },
|
|
1975
|
+
offset: { type: "number", description: "Skip this many buyers (paging)", required: false },
|
|
1976
|
+
full: {
|
|
1977
|
+
...fullArg,
|
|
1978
|
+
description: "Add each buyer's gross, refunds, orders with a discount, days between orders and first-order campaign"
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
var buyersCommand = defineCommand3({
|
|
1983
|
+
meta: {
|
|
1984
|
+
name: "buyers",
|
|
1985
|
+
description: "Who buys: buyers, new and returning, repeat rate, segments and the top buyers. Start here: baker shopify buyers --days 90 --by channel"
|
|
1986
|
+
},
|
|
1987
|
+
args: {
|
|
1988
|
+
...periodFlags,
|
|
1989
|
+
segment: { type: "string", description: "best | loyal | at_risk | new | one_time", required: false },
|
|
1990
|
+
by: { type: "string", description: "campaign | channel", required: false },
|
|
1991
|
+
channel: { type: "string", description: "A channel key from --by channel", required: false },
|
|
1992
|
+
sort: { type: "string", description: "net | orders | recent | first", required: false },
|
|
1993
|
+
limit: { type: "string", description: "Buyers listed, 1-100", required: false },
|
|
1994
|
+
offset: { type: "string", description: "Skip this many buyers", required: false },
|
|
1995
|
+
full: { type: "boolean", description: "Add each buyer's detail", required: false, default: false }
|
|
1996
|
+
},
|
|
1997
|
+
run: async ({ args: args2 }) => {
|
|
1998
|
+
try {
|
|
1999
|
+
const answer = await apiGet(
|
|
2000
|
+
"/api/shopify/buyers",
|
|
2001
|
+
params({
|
|
2002
|
+
days: args2.days,
|
|
2003
|
+
all: args2.all,
|
|
2004
|
+
country: args2.country,
|
|
2005
|
+
segment: args2.segment,
|
|
2006
|
+
by: args2.by,
|
|
2007
|
+
channel: args2.channel,
|
|
2008
|
+
sort: args2.sort,
|
|
2009
|
+
limit: args2.limit,
|
|
2010
|
+
offset: args2.offset,
|
|
2011
|
+
full: args2.full
|
|
2012
|
+
})
|
|
2013
|
+
);
|
|
2014
|
+
const { data, meta } = split(answer);
|
|
2015
|
+
emit(data, meta, [...storeStateHints(answer.store), ...buyersHints(answer)]);
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
fail(err);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
2021
|
+
registerSchema({
|
|
2022
|
+
command: "shopify.buyer",
|
|
2023
|
+
description: "One buyer by their number (1042, #1042 or 'Customer #1042'): where they are, how many orders and how much they spent net of refunds, average order, days between orders, first and last order, discount use, segment and how they first came \u2014 then every order (date in the store's time, products and quantities, net, refunded, discount codes, city), newest first, and what they bought. Never a name or an email. A buyer their store asked Baker to erase, or merged into another, answers 'This buyer is no longer in Baker'.",
|
|
2024
|
+
args: {
|
|
2025
|
+
number: { type: "positional", description: "The buyer's number: 1042, #1042 or 'Customer #1042'", required: true },
|
|
2026
|
+
full: { ...fullArg, description: "Add each order's id, lines with their amounts, refunded lines and campaign" }
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
var buyerCommand = defineCommand3({
|
|
2030
|
+
meta: {
|
|
2031
|
+
name: "buyer",
|
|
2032
|
+
description: "One buyer's history by number. Example: baker shopify buyer 1042"
|
|
2033
|
+
},
|
|
2034
|
+
args: {
|
|
2035
|
+
number: { type: "positional", description: "1042, #1042 or 'Customer #1042'", required: true },
|
|
2036
|
+
full: { type: "boolean", description: "Add ids, lines and campaigns", required: false, default: false }
|
|
2037
|
+
},
|
|
2038
|
+
run: async ({ args: args2 }) => {
|
|
2039
|
+
try {
|
|
2040
|
+
const answer = await apiGet(
|
|
2041
|
+
"/api/shopify/buyer",
|
|
2042
|
+
params({ number: args2.number, full: args2.full })
|
|
2043
|
+
);
|
|
2044
|
+
const { data, meta } = split(answer);
|
|
2045
|
+
emit(data, meta, [...storeStateHints(answer.store), ...buyerHistoryHints(answer.history, answer.basis)]);
|
|
2046
|
+
} catch (err) {
|
|
2047
|
+
fail(err);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
});
|
|
2051
|
+
registerSchema({
|
|
2052
|
+
command: "shopify.countries",
|
|
2053
|
+
description: `Start here for 'where do our buyers come from' and 'which countries should we push': orders, buyers, new buyers, net, average order, share and growth against the period before, by country \u2014 or by region inside --country (straight by city for a country whose addresses carry no region, like Germany), or by city inside --country and --region \u2014 with the currency buyers paid in. Orders whose address Baker could not read are 'Unknown' ('No region given' / 'No city given' inside a country), always shown; the rows, that row and 'Other' add up to the total. --product narrows to the orders holding one product, with that product's money. ${PERIOD_NOTE}`,
|
|
2054
|
+
args: {
|
|
2055
|
+
...periodArgs,
|
|
2056
|
+
region: { type: "string", description: "Inside --country, one region (code or name): its cities", required: false },
|
|
2057
|
+
product: { type: "string", description: "Only orders holding this product: its id or handle", required: false },
|
|
2058
|
+
limit: { type: "number", description: "Places listed, 1-100 (default 25; the rest are 'Other')", required: false },
|
|
2059
|
+
full: { ...fullArg, description: "Add the period before's net and orders per place" }
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
var countriesCommand = defineCommand3({
|
|
2063
|
+
meta: {
|
|
2064
|
+
name: "countries",
|
|
2065
|
+
description: "Where buyers are: orders, buyers and net by country, region or city, with growth. Start here: baker shopify countries --days 90"
|
|
2066
|
+
},
|
|
2067
|
+
args: {
|
|
2068
|
+
...periodFlags,
|
|
2069
|
+
region: { type: "string", description: "A region inside --country", required: false },
|
|
2070
|
+
product: { type: "string", description: "A product's id or handle", required: false },
|
|
2071
|
+
limit: { type: "string", description: "Places listed, 1-100", required: false },
|
|
2072
|
+
full: { type: "boolean", description: "Add the period before", required: false, default: false }
|
|
2073
|
+
},
|
|
2074
|
+
run: async ({ args: args2 }) => {
|
|
2075
|
+
try {
|
|
2076
|
+
const answer = await apiGet(
|
|
2077
|
+
"/api/shopify/countries",
|
|
2078
|
+
params({
|
|
2079
|
+
days: args2.days,
|
|
2080
|
+
all: args2.all,
|
|
2081
|
+
country: args2.country,
|
|
2082
|
+
region: args2.region,
|
|
2083
|
+
product: args2.product,
|
|
2084
|
+
limit: args2.limit,
|
|
2085
|
+
full: args2.full
|
|
2086
|
+
})
|
|
2087
|
+
);
|
|
2088
|
+
const { data, meta } = split(answer);
|
|
2089
|
+
emit(data, meta, [...storeStateHints(answer.store), ...countriesHints(answer)]);
|
|
2090
|
+
} catch (err) {
|
|
2091
|
+
fail(err);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
registerSchema({
|
|
2096
|
+
command: "shopify.bestSellers",
|
|
2097
|
+
description: `Start here for 'what sells best' and 'which product should we lead with': best sellers by units ordered (before anything came back) beside refunded units, orders, buyers, revenue (the lines' price after discounts, before tax and shipping), refunds, refund rate and growth. --view variants (one product's options, with --product), trend (top products by month), together (products bought in one order), repeat (first buys whose buyers came back most) or momentum (products selling 30% or more below the period before). ${PERIOD_NOTE} For the catalog itself use \`baker shopify products\`.`,
|
|
2098
|
+
args: {
|
|
2099
|
+
...periodArgs,
|
|
2100
|
+
view: {
|
|
2101
|
+
type: "string",
|
|
2102
|
+
description: "products (default) | variants | trend | together | repeat | momentum",
|
|
2103
|
+
required: false,
|
|
2104
|
+
enum: ["products", "variants", "trend", "together", "repeat", "momentum"]
|
|
2105
|
+
},
|
|
2106
|
+
sort: {
|
|
2107
|
+
type: "string",
|
|
2108
|
+
description: "units (default) | revenue | orders",
|
|
2109
|
+
required: false,
|
|
2110
|
+
enum: ["units", "revenue", "orders"]
|
|
2111
|
+
},
|
|
2112
|
+
product: { type: "string", description: "One product (id or handle): its variants or its trend", required: false },
|
|
2113
|
+
limit: { type: "number", description: "Rows, 1-100 (default 10; 5 products for trend)", required: false },
|
|
2114
|
+
full: { ...fullArg, description: "Add product and variant ids and the period before" }
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
var bestSellersCommand = defineCommand3({
|
|
2118
|
+
meta: {
|
|
2119
|
+
name: "best-sellers",
|
|
2120
|
+
description: "What sells: best sellers, variants, trend, bought together, repeat leaders, losing momentum. Start here: baker shopify best-sellers --days 90"
|
|
2121
|
+
},
|
|
2122
|
+
args: {
|
|
2123
|
+
...periodFlags,
|
|
2124
|
+
view: {
|
|
2125
|
+
type: "string",
|
|
2126
|
+
description: "products | variants | trend | together | repeat | momentum",
|
|
2127
|
+
required: false
|
|
2128
|
+
},
|
|
2129
|
+
sort: { type: "string", description: "units | revenue | orders", required: false },
|
|
2130
|
+
product: { type: "string", description: "A product's id or handle", required: false },
|
|
2131
|
+
limit: { type: "string", description: "Rows, 1-100", required: false },
|
|
2132
|
+
full: { type: "boolean", description: "Add ids and the period before", required: false, default: false }
|
|
2133
|
+
},
|
|
2134
|
+
run: async ({ args: args2 }) => {
|
|
2135
|
+
try {
|
|
2136
|
+
const answer = await apiGet(
|
|
2137
|
+
"/api/shopify/best-sellers",
|
|
2138
|
+
params({
|
|
2139
|
+
days: args2.days,
|
|
2140
|
+
all: args2.all,
|
|
2141
|
+
country: args2.country,
|
|
2142
|
+
view: args2.view,
|
|
2143
|
+
sort: args2.sort,
|
|
2144
|
+
product: args2.product,
|
|
2145
|
+
limit: args2.limit,
|
|
2146
|
+
full: args2.full
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
const { data, meta } = split(answer);
|
|
2150
|
+
emit(data, meta, [...storeStateHints(answer.store), ...bestSellersHints(answer)]);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
fail(err);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
});
|
|
2156
|
+
registerSchema({
|
|
2157
|
+
command: "shopify.cohorts",
|
|
2158
|
+
description: "Buyers grouped by the month of their first order (the last --months, default 12): how many, the share who ordered again 1, 2, 3, 6 and 12 months later, and what they spent per buyer by month 12. Months not reached yet are null. Use it for 'are buyers coming back more than they used to' and lifetime value over time.",
|
|
2159
|
+
args: {
|
|
2160
|
+
months: { type: "number", description: "Cohorts, the last 3-36 months (default 12)", required: false },
|
|
2161
|
+
country: { type: "string", description: "Only orders to this country (two-letter code)", required: false },
|
|
2162
|
+
full: {
|
|
2163
|
+
...fullArg,
|
|
2164
|
+
description: "Add every month of every cohort: active buyers, orders, net, cumulative net per buyer"
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
var cohortsCommand = defineCommand3({
|
|
2169
|
+
meta: {
|
|
2170
|
+
name: "cohorts",
|
|
2171
|
+
description: "Buyers by first-order month: who came back and what they spent. Example: baker shopify cohorts --months 12"
|
|
2172
|
+
},
|
|
2173
|
+
args: {
|
|
2174
|
+
months: { type: "string", description: "The last 3-36 months", required: false },
|
|
2175
|
+
country: { type: "string", description: "A two-letter country code", required: false },
|
|
2176
|
+
full: { type: "boolean", description: "Every month of every cohort", required: false, default: false }
|
|
2177
|
+
},
|
|
2178
|
+
run: async ({ args: args2 }) => {
|
|
2179
|
+
try {
|
|
2180
|
+
const answer = await apiGet(
|
|
2181
|
+
"/api/shopify/cohorts",
|
|
2182
|
+
params({ months: args2.months, country: args2.country, full: args2.full })
|
|
2183
|
+
);
|
|
2184
|
+
const { data, meta } = split(answer);
|
|
2185
|
+
emit(data, meta, [...storeStateHints(answer.store), ...cohortsHints(answer)]);
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
fail(err);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
registerSchema({
|
|
2192
|
+
command: "shopify.segments",
|
|
2193
|
+
description: "Every buyer at the period's end by segment \u2014 best buyers, loyal, at risk, new and one-time \u2014 with how many, their share, net, average orders, days since their last order, the countries they are in and the products their first orders held. Each segment's rule is given in words with this store's own numbers (its typical gap between orders and the spend of its top fifth). Start here for 'who are our best customers' and 'who should we win back'.",
|
|
2194
|
+
args: {
|
|
2195
|
+
...periodArgs,
|
|
2196
|
+
full: { ...fullArg, description: "Add the first products' ids" }
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
var segmentsCommand = defineCommand3({
|
|
2200
|
+
meta: {
|
|
2201
|
+
name: "segments",
|
|
2202
|
+
description: "Buyers by segment: best, loyal, at risk, new, one-time. Example: baker shopify segments"
|
|
2203
|
+
},
|
|
2204
|
+
args: {
|
|
2205
|
+
...periodFlags,
|
|
2206
|
+
full: { type: "boolean", description: "Add product ids", required: false, default: false }
|
|
2207
|
+
},
|
|
2208
|
+
run: async ({ args: args2 }) => {
|
|
2209
|
+
try {
|
|
2210
|
+
const answer = await apiGet(
|
|
2211
|
+
"/api/shopify/segments",
|
|
2212
|
+
params({ days: args2.days, all: args2.all, country: args2.country, full: args2.full })
|
|
2213
|
+
);
|
|
2214
|
+
const { data, meta } = split(answer);
|
|
2215
|
+
emit(data, meta, [...storeStateHints(answer.store), ...segmentsHints(answer)]);
|
|
2216
|
+
} catch (err) {
|
|
2217
|
+
fail(err);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
|
|
2222
|
+
// ../api/src/shopify/catalogAds.ts
|
|
2223
|
+
import { z as z5 } from "zod";
|
|
2224
|
+
var catalogAuditRequestSchema = z5.strictObject({
|
|
2225
|
+
cursor: z5.string().max(2e3).optional(),
|
|
2226
|
+
limit: z5.number().int().min(1).max(20).default(10),
|
|
2227
|
+
/** Which linked Merchant Center account to compare against, when several are linked. */
|
|
2228
|
+
merchantId: z5.string().regex(/^[1-9][0-9]{0,18}$/, "merchantId is the numeric Merchant Center account id").optional()
|
|
2229
|
+
});
|
|
2230
|
+
var catalogAuditCheckSchema = z5.object({
|
|
2231
|
+
key: z5.enum([
|
|
2232
|
+
"title",
|
|
2233
|
+
"description",
|
|
2234
|
+
"images",
|
|
2235
|
+
"category",
|
|
2236
|
+
"identifiers",
|
|
2237
|
+
"feed",
|
|
2238
|
+
"price",
|
|
2239
|
+
"availability",
|
|
2240
|
+
"googleReview"
|
|
2241
|
+
]),
|
|
2242
|
+
state: z5.enum(["ok", "problem", "unknown"]),
|
|
2243
|
+
message: z5.string(),
|
|
2244
|
+
variantId: z5.string().optional(),
|
|
2245
|
+
fix: z5.string().optional()
|
|
2246
|
+
});
|
|
2247
|
+
var catalogProductAuditSchema = z5.object({
|
|
2248
|
+
productId: z5.string(),
|
|
2249
|
+
title: z5.string(),
|
|
2250
|
+
checks: z5.array(catalogAuditCheckSchema),
|
|
2251
|
+
variantsTruncated: z5.boolean().optional()
|
|
2252
|
+
});
|
|
2253
|
+
var catalogAuditResponseSchema = z5.object({
|
|
2254
|
+
products: z5.array(catalogProductAuditSchema),
|
|
2255
|
+
nextCursor: z5.string().nullable(),
|
|
2256
|
+
feedCompared: z5.boolean(),
|
|
2257
|
+
merchantCenter: z5.object({
|
|
2258
|
+
/** The account compared against, when one was. */
|
|
2259
|
+
merchantId: z5.string().optional(),
|
|
2260
|
+
/** Why nothing was compared, in words the agent can pass on. */
|
|
2261
|
+
note: z5.string().optional(),
|
|
2262
|
+
/** More offers than one audit reads: variants past the ceiling read as unmatched. */
|
|
2263
|
+
truncated: z5.boolean().optional()
|
|
2264
|
+
})
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
// src/commands/shopify/catalog.ts
|
|
2268
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
2269
|
+
|
|
2270
|
+
// src/commands/shopify/catalogReport.ts
|
|
2271
|
+
function grouped(checks) {
|
|
2272
|
+
const findings = /* @__PURE__ */ new Map();
|
|
2273
|
+
for (const check of checks) {
|
|
2274
|
+
const id = `${check.key}\0${check.message}`;
|
|
2275
|
+
const finding = findings.get(id) ?? { check: check.key, message: check.message };
|
|
2276
|
+
if (check.variantId) finding.variants = [...finding.variants ?? [], check.variantId];
|
|
2277
|
+
findings.set(id, finding);
|
|
2278
|
+
}
|
|
2279
|
+
return [...findings.values()];
|
|
2280
|
+
}
|
|
2281
|
+
function collectFixes(checks, fixes) {
|
|
2282
|
+
for (const check of checks) {
|
|
2283
|
+
if (check.state === "problem" && check.fix) fixes[check.key] ??= check.fix;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
function compactAudit(products) {
|
|
2287
|
+
const fixes = {};
|
|
2288
|
+
const rows = [];
|
|
2289
|
+
for (const product of products) {
|
|
2290
|
+
const problems = grouped(product.checks.filter((check) => check.state === "problem"));
|
|
2291
|
+
const unknown = grouped(product.checks.filter((check) => check.state === "unknown"));
|
|
2292
|
+
collectFixes(product.checks, fixes);
|
|
2293
|
+
if (problems.length === 0 && unknown.length === 0 && !product.variantsTruncated) continue;
|
|
2294
|
+
rows.push({
|
|
2295
|
+
productId: product.productId,
|
|
2296
|
+
title: product.title,
|
|
2297
|
+
...problems.length > 0 ? { problems } : {},
|
|
2298
|
+
...unknown.length > 0 ? { unknown } : {},
|
|
2299
|
+
...product.variantsTruncated ? { variantsTruncated: true } : {}
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
return { products: rows, fixes };
|
|
2303
|
+
}
|
|
2304
|
+
var LIMIT_FIX = "Pass --limit as a whole number from 1 to 20 (default 10); page on with --cursor from data.nextCursor.";
|
|
2305
|
+
var CATALOG_FLAG_FIX = {
|
|
2306
|
+
limit: LIMIT_FIX,
|
|
2307
|
+
merchantId: "Pass --merchant-id as the numeric Merchant Center account id, or leave it out.",
|
|
2308
|
+
cursor: "Pass --cursor exactly as data.nextCursor of the previous page, or leave it out to start over."
|
|
2309
|
+
};
|
|
2310
|
+
function catalogAuditRequest(args2) {
|
|
2311
|
+
const parsed = catalogAuditRequestSchema.safeParse({
|
|
2312
|
+
cursor: args2.cursor,
|
|
2313
|
+
...args2.limit === void 0 ? {} : { limit: Number(args2.limit) },
|
|
2314
|
+
...args2.merchantId ? { merchantId: args2.merchantId } : {}
|
|
2315
|
+
});
|
|
2316
|
+
if (parsed.success) return { ok: true, request: parsed.data };
|
|
2317
|
+
const issue = parsed.error.issues[0];
|
|
2318
|
+
const field = String(issue?.path[0] ?? "limit");
|
|
2319
|
+
return {
|
|
2320
|
+
ok: false,
|
|
2321
|
+
error: {
|
|
2322
|
+
code: "VALIDATION_ERROR",
|
|
2323
|
+
message: `${field}: ${issue?.message ?? "invalid value"}`,
|
|
2324
|
+
fix: {
|
|
2325
|
+
action: "retry_with_flag",
|
|
2326
|
+
explanation: CATALOG_FLAG_FIX[field] ?? LIMIT_FIX
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// src/commands/shopify/catalog.ts
|
|
2333
|
+
var description = "Check products before Shopping, Performance Max or catalog ads. Start here: `baker shopify catalog audit`. Reads Baker's copy of the store and, when the company's Google Ads links one, Merchant Center: titles, descriptions, images, identifiers, category, the feed's price and stock against Shopify, and Google's own verdict on each product. Changes nothing; fixes to Shopify are staged with baker shopify change.";
|
|
2334
|
+
var args = {
|
|
2335
|
+
cursor: { type: "string", description: "data.nextCursor from the previous page", required: false },
|
|
2336
|
+
limit: { type: "string", description: "Products per page, 1\u201320 (default 10)", required: false },
|
|
2337
|
+
"merchant-id": {
|
|
2338
|
+
type: "string",
|
|
2339
|
+
description: "Merchant Center account to compare against, when Google Ads links several",
|
|
2340
|
+
required: false
|
|
2341
|
+
},
|
|
2342
|
+
full: fullArg
|
|
2343
|
+
};
|
|
2344
|
+
registerSchema({ command: "shopify.catalog.audit", description, args });
|
|
2345
|
+
var audit = defineCommand4({
|
|
2346
|
+
meta: { name: "audit", description },
|
|
2347
|
+
args,
|
|
2348
|
+
async run({ args: args2 }) {
|
|
2349
|
+
const parsed = catalogAuditRequest({ cursor: args2.cursor, limit: args2.limit, merchantId: args2["merchant-id"] });
|
|
2350
|
+
if (!parsed.ok) {
|
|
2351
|
+
writeJson({ ok: false, error: parsed.error });
|
|
2352
|
+
process.exit(1);
|
|
2353
|
+
}
|
|
2354
|
+
const { request } = parsed;
|
|
2355
|
+
try {
|
|
2356
|
+
const data = catalogAuditResponseSchema.parse(await apiPost("/api/shopify/catalog/audit", request));
|
|
2357
|
+
writeJson({
|
|
2358
|
+
ok: true,
|
|
2359
|
+
data: args2.full ? data : { ...data, ...compactAudit(data.products) },
|
|
2360
|
+
hints: [
|
|
2361
|
+
...!data.feedCompared ? [
|
|
2362
|
+
`Merchant Center was not compared${data.merchantCenter.note ? `: ${data.merchantCenter.note}` : "."} Do not claim Shopping is ready from this audit alone.`
|
|
2363
|
+
] : [],
|
|
2364
|
+
...data.merchantCenter.truncated ? [
|
|
2365
|
+
"Merchant Center holds more products than one audit reads; unmatched variants may simply be past that point."
|
|
2366
|
+
] : [],
|
|
2367
|
+
...data.products.some(
|
|
2368
|
+
(product) => product.checks.some((check) => check.key === "googleReview" && check.state === "problem")
|
|
2369
|
+
) ? [
|
|
2370
|
+
"Google's verdicts are fixed in Merchant Center or the Google & YouTube channel, not in Shopify: tell the user what Google said, never promise Baker will fix it."
|
|
2371
|
+
] : [],
|
|
2372
|
+
...data.nextCursor ? ["Continue with --cursor from data.nextCursor to check the rest of the products."] : [],
|
|
2373
|
+
...data.products.some((product) => product.variantsTruncated) ? [
|
|
2374
|
+
"Some products have more than 100 options. Read their remaining options before claiming every option was checked."
|
|
2375
|
+
] : []
|
|
2376
|
+
]
|
|
2377
|
+
});
|
|
2378
|
+
} catch (error) {
|
|
2379
|
+
fail(error);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
var catalogCommand = defineCommand4({
|
|
2384
|
+
meta: { name: "catalog", description: "Check products for Shopping, Performance Max and catalog ads" },
|
|
2385
|
+
subCommands: { audit }
|
|
2386
|
+
});
|
|
2387
|
+
|
|
2388
|
+
// src/commands/shopify/change.ts
|
|
2389
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
2390
|
+
var TARGET = (what) => ({ type: "positional", description: what, required: false });
|
|
2391
|
+
function positionals(args2) {
|
|
2392
|
+
return Array.isArray(args2._) ? args2._.filter((value) => typeof value === "string") : [];
|
|
2393
|
+
}
|
|
2394
|
+
function target2(args2, what) {
|
|
2395
|
+
const [first] = positionals(args2);
|
|
2396
|
+
if (!first) failValidation(`pass the ${what} as the first argument`);
|
|
2397
|
+
return first;
|
|
2398
|
+
}
|
|
2399
|
+
async function stageFor(kind, args2, flags, targetOf) {
|
|
2400
|
+
const payloads = payloadsFrom(args2);
|
|
2401
|
+
if (payloads) {
|
|
2402
|
+
await stage(
|
|
2403
|
+
payloads.map(
|
|
2404
|
+
(payload2) => "kind" in payload2 ? payload2 : { kind, ...targetOf ? { target: targetOf() } : {}, payload: payload2 }
|
|
2405
|
+
)
|
|
2406
|
+
);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
const payload = flags();
|
|
2410
|
+
if (Object.keys(payload).length === 0)
|
|
2411
|
+
failValidation("name at least one thing to change (see `baker schema` for the flags)");
|
|
2412
|
+
await stage([{ kind, ...targetOf ? { target: targetOf() } : {}, payload }]);
|
|
2413
|
+
}
|
|
2414
|
+
var productUpdate = leaf(
|
|
2415
|
+
"shopify.change.product.update",
|
|
2416
|
+
{
|
|
2417
|
+
name: "update",
|
|
2418
|
+
description: `Start here to change a product's title, description, SEO, tags or type. Example: baker shopify change product update the-snowboard --title "The Snowboard" --seo-title "\u2026" --add-tags summer. Tags: --tags replaces the whole list, --add-tags/--remove-tags edit it. Descriptions are HTML without scripts or frames. Many products in one call: --json '[{"kind":"shopify.product.update","target":"<handle>","payload":{"addTags":["summer"]}}, \u2026]' \u2014 payload keys: title, descriptionHtml, seo {title, description}, tags | addTags | removeTags, productType.`
|
|
2419
|
+
},
|
|
2420
|
+
{
|
|
2421
|
+
product: TARGET("Product id, handle or link"),
|
|
2422
|
+
title: { type: "string", description: "New title", required: false },
|
|
2423
|
+
description: { type: "string", description: "New description, as HTML (no scripts or frames)", required: false },
|
|
2424
|
+
"seo-title": { type: "string", description: "Title shown in search results", required: false },
|
|
2425
|
+
"seo-description": { type: "string", description: "Description shown in search results", required: false },
|
|
2426
|
+
tags: { type: "string", description: "The whole tag list, comma separated (replaces it)", required: false },
|
|
2427
|
+
"add-tags": { type: "string", description: "Tags to add, comma separated", required: false },
|
|
2428
|
+
"remove-tags": { type: "string", description: "Tags to remove, comma separated", required: false },
|
|
2429
|
+
type: { type: "string", description: "Product type", required: false },
|
|
2430
|
+
...JSON_ARGS
|
|
2431
|
+
},
|
|
2432
|
+
async (args2) => await stageFor(
|
|
2433
|
+
"shopify.product.update",
|
|
2434
|
+
args2,
|
|
2435
|
+
() => defined({
|
|
2436
|
+
title: args2.title,
|
|
2437
|
+
descriptionHtml: args2.description,
|
|
2438
|
+
seo: args2["seo-title"] !== void 0 || args2["seo-description"] !== void 0 ? defined({ title: args2["seo-title"], description: args2["seo-description"] }) : void 0,
|
|
2439
|
+
tags: list(args2.tags),
|
|
2440
|
+
addTags: list(args2["add-tags"]),
|
|
2441
|
+
removeTags: list(args2["remove-tags"]),
|
|
2442
|
+
productType: args2.type
|
|
2443
|
+
}),
|
|
2444
|
+
positionals(args2).length > 0 ? () => target2(args2, "product") : void 0
|
|
2445
|
+
)
|
|
2446
|
+
);
|
|
2447
|
+
var WEIGHT_UNITS = {
|
|
2448
|
+
g: "GRAMS",
|
|
2449
|
+
gram: "GRAMS",
|
|
2450
|
+
grams: "GRAMS",
|
|
2451
|
+
kg: "KILOGRAMS",
|
|
2452
|
+
kgs: "KILOGRAMS",
|
|
2453
|
+
kilogram: "KILOGRAMS",
|
|
2454
|
+
kilograms: "KILOGRAMS",
|
|
2455
|
+
oz: "OUNCES",
|
|
2456
|
+
ounce: "OUNCES",
|
|
2457
|
+
ounces: "OUNCES",
|
|
2458
|
+
lb: "POUNDS",
|
|
2459
|
+
lbs: "POUNDS",
|
|
2460
|
+
pound: "POUNDS",
|
|
2461
|
+
pounds: "POUNDS"
|
|
2462
|
+
};
|
|
2463
|
+
function weightOf(value, unit) {
|
|
2464
|
+
if (value === void 0) return void 0;
|
|
2465
|
+
const named = typeof unit === "string" ? unit.trim() : "";
|
|
2466
|
+
return {
|
|
2467
|
+
value: Number(value),
|
|
2468
|
+
unit: named ? WEIGHT_UNITS[named.toLowerCase()] ?? named.toUpperCase() : "KILOGRAMS"
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
var variantUpdate = leaf(
|
|
2472
|
+
"shopify.change.variant.update",
|
|
2473
|
+
{
|
|
2474
|
+
name: "update",
|
|
2475
|
+
description: "Change one option (variant) of a product: SKU, barcode (GTIN), weight \u2014 and its price or sale price, which the user must approve in the chat first (the command hands you the question). Take the option's id from `baker shopify product <product>` (variantId). Example: baker shopify change variant update 456 --sku SNOW-L --barcode 4006381333931 --weight 1.5 --weight-unit kg. --sale-price none clears it."
|
|
2476
|
+
},
|
|
2477
|
+
{
|
|
2478
|
+
variant: TARGET("The option's id (variantId from baker shopify product)"),
|
|
2479
|
+
sku: { type: "string", description: "SKU", required: false },
|
|
2480
|
+
barcode: { type: "string", description: "Barcode / GTIN (8, 12, 13 or 14 digits)", required: false },
|
|
2481
|
+
weight: { type: "string", description: "Weight value", required: false },
|
|
2482
|
+
"weight-unit": {
|
|
2483
|
+
type: "string",
|
|
2484
|
+
description: "kg | g | lb | oz (default kg)",
|
|
2485
|
+
required: false
|
|
2486
|
+
},
|
|
2487
|
+
price: {
|
|
2488
|
+
type: "string",
|
|
2489
|
+
description: `Price as a decimal, e.g. "649.95" (needs the user's yes)`,
|
|
2490
|
+
required: false
|
|
2491
|
+
},
|
|
2492
|
+
"sale-price": {
|
|
2493
|
+
type: "string",
|
|
2494
|
+
description: `Compare-at price, or "none" to clear it (needs the user's yes)`,
|
|
2495
|
+
required: false
|
|
2496
|
+
},
|
|
2497
|
+
...JSON_ARGS
|
|
2498
|
+
},
|
|
2499
|
+
async (args2) => await stageFor(
|
|
2500
|
+
"shopify.variant.update",
|
|
2501
|
+
args2,
|
|
2502
|
+
() => defined({
|
|
2503
|
+
sku: args2.sku,
|
|
2504
|
+
barcode: args2.barcode,
|
|
2505
|
+
weight: weightOf(args2.weight, args2["weight-unit"]),
|
|
2506
|
+
price: args2.price,
|
|
2507
|
+
compareAtPrice: args2["sale-price"] === "none" ? null : args2["sale-price"]
|
|
2508
|
+
}),
|
|
2509
|
+
positionals(args2).length > 0 ? () => target2(args2, "option id") : void 0
|
|
2510
|
+
)
|
|
2511
|
+
);
|
|
2512
|
+
var fieldSet = leaf(
|
|
2513
|
+
"shopify.change.field.set",
|
|
2514
|
+
{
|
|
2515
|
+
name: "set",
|
|
2516
|
+
description: 'Set a custom field (metafield) on a product, an option or a collection. The owner is product:<ref>, variant:<id>, collection:<ref> or a collection staged in this chat (its shopify_temp_ ref); a bare reference is a product. If the store defines the field, send its type. Example: baker shopify change field set product:the-snowboard --namespace custom --key care --type single_line_text_field --value "Wash cold".'
|
|
2517
|
+
},
|
|
2518
|
+
{
|
|
2519
|
+
owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
|
|
2520
|
+
namespace: { type: "string", description: "Namespace, e.g. custom", required: false },
|
|
2521
|
+
key: { type: "string", description: "Key, e.g. care", required: false },
|
|
2522
|
+
type: {
|
|
2523
|
+
type: "string",
|
|
2524
|
+
description: "Shopify's type, e.g. single_line_text_field, rich_text_field, json",
|
|
2525
|
+
required: false
|
|
2526
|
+
},
|
|
2527
|
+
value: { type: "string", description: "The value, as Shopify takes it for that type", required: false },
|
|
2528
|
+
...JSON_ARGS
|
|
2529
|
+
},
|
|
2530
|
+
async (args2) => await stageFor(
|
|
2531
|
+
"shopify.metafield.set",
|
|
2532
|
+
args2,
|
|
2533
|
+
() => defined({ namespace: args2.namespace, key: args2.key, type: args2.type, value: args2.value }),
|
|
2534
|
+
() => target2(args2, "owner")
|
|
2535
|
+
)
|
|
2536
|
+
);
|
|
2537
|
+
var fieldRemove = leaf(
|
|
2538
|
+
"shopify.change.field.remove",
|
|
2539
|
+
{
|
|
2540
|
+
name: "remove",
|
|
2541
|
+
description: "Remove a custom field from a product, an option or a collection. Example: baker shopify change field remove product:the-snowboard custom.care"
|
|
2542
|
+
},
|
|
2543
|
+
{
|
|
2544
|
+
owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
|
|
2545
|
+
field: TARGET("namespace.key, e.g. custom.care")
|
|
2546
|
+
},
|
|
2547
|
+
async (args2) => {
|
|
2548
|
+
const [owner, field] = positionals(args2);
|
|
2549
|
+
const [namespace, ...rest] = (field ?? "").split(".");
|
|
2550
|
+
if (!owner || !namespace || rest.length === 0) failValidation("pass the owner and the field as namespace.key");
|
|
2551
|
+
await stage([{ kind: "shopify.metafield.delete", target: owner, payload: { namespace, key: rest.join(".") } }]);
|
|
2552
|
+
}
|
|
2553
|
+
);
|
|
2554
|
+
var collectionCreate = leaf(
|
|
2555
|
+
"shopify.change.collection.create",
|
|
2556
|
+
{
|
|
2557
|
+
name: "create",
|
|
2558
|
+
description: 'Create a collection of hand-picked products, shown on the online store (a second change, staged with it) unless --hidden. Example: baker shopify change collection create --title "Summer boards" --products snowboard-a,snowboard-b. Collections whose products join by rules are made in Shopify.'
|
|
2559
|
+
},
|
|
2560
|
+
{
|
|
2561
|
+
title: { type: "string", description: "Title", required: false },
|
|
2562
|
+
handle: {
|
|
2563
|
+
type: "string",
|
|
2564
|
+
description: "Its address (/collections/<handle>); default from the title",
|
|
2565
|
+
required: false
|
|
2566
|
+
},
|
|
2567
|
+
description: { type: "string", description: "Description, as HTML (no scripts or frames)", required: false },
|
|
2568
|
+
"seo-title": { type: "string", description: "Title shown in search results", required: false },
|
|
2569
|
+
"seo-description": { type: "string", description: "Description shown in search results", required: false },
|
|
2570
|
+
products: {
|
|
2571
|
+
type: "string",
|
|
2572
|
+
description: "Products, comma separated (ids, handles or links), in order",
|
|
2573
|
+
required: false
|
|
2574
|
+
},
|
|
2575
|
+
sort: {
|
|
2576
|
+
type: "string",
|
|
2577
|
+
description: "MANUAL | BEST_SELLING | ALPHA_ASC | ALPHA_DESC | PRICE_ASC | PRICE_DESC | CREATED | CREATED_DESC",
|
|
2578
|
+
required: false
|
|
2579
|
+
},
|
|
2580
|
+
hidden: { type: "boolean", description: "Don't show it on the online store yet", required: false },
|
|
2581
|
+
...JSON_ARGS
|
|
2582
|
+
},
|
|
2583
|
+
async (args2) => await stageFor(
|
|
2584
|
+
"shopify.collection.create",
|
|
2585
|
+
args2,
|
|
2586
|
+
() => defined({
|
|
2587
|
+
title: args2.title,
|
|
2588
|
+
handle: args2.handle,
|
|
2589
|
+
descriptionHtml: args2.description,
|
|
2590
|
+
seo: args2["seo-title"] !== void 0 || args2["seo-description"] !== void 0 ? defined({ title: args2["seo-title"], description: args2["seo-description"] }) : void 0,
|
|
2591
|
+
products: list(args2.products),
|
|
2592
|
+
sortOrder: typeof args2.sort === "string" ? args2.sort.toUpperCase() : void 0,
|
|
2593
|
+
onlineStore: args2.hidden === true ? false : void 0
|
|
2594
|
+
})
|
|
2595
|
+
)
|
|
2596
|
+
);
|
|
2597
|
+
var collectionUpdate = leaf(
|
|
2598
|
+
"shopify.change.collection.update",
|
|
2599
|
+
{
|
|
2600
|
+
name: "update",
|
|
2601
|
+
description: `Change a collection's title, description, SEO or sort order (its address stays: stage a redirect to move it). Example: baker shopify change collection update summer-boards --title "Summer"`
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
collection: TARGET("Collection id, handle or link"),
|
|
2605
|
+
title: { type: "string", description: "New title", required: false },
|
|
2606
|
+
description: { type: "string", description: "New description, as HTML", required: false },
|
|
2607
|
+
"seo-title": { type: "string", description: "Title shown in search results", required: false },
|
|
2608
|
+
"seo-description": { type: "string", description: "Description shown in search results", required: false },
|
|
2609
|
+
sort: { type: "string", description: "Sort order (see collection create)", required: false },
|
|
2610
|
+
...JSON_ARGS
|
|
2611
|
+
},
|
|
2612
|
+
async (args2) => await stageFor(
|
|
2613
|
+
"shopify.collection.update",
|
|
2614
|
+
args2,
|
|
2615
|
+
() => defined({
|
|
2616
|
+
title: args2.title,
|
|
2617
|
+
descriptionHtml: args2.description,
|
|
2618
|
+
seo: args2["seo-title"] !== void 0 || args2["seo-description"] !== void 0 ? defined({ title: args2["seo-title"], description: args2["seo-description"] }) : void 0,
|
|
2619
|
+
sortOrder: typeof args2.sort === "string" ? args2.sort.toUpperCase() : void 0
|
|
2620
|
+
}),
|
|
2621
|
+
() => target2(args2, "collection")
|
|
2622
|
+
)
|
|
2623
|
+
);
|
|
2624
|
+
function collectionVerb(verb, kind, description3) {
|
|
2625
|
+
return leaf(
|
|
2626
|
+
`shopify.change.collection.${verb}`,
|
|
2627
|
+
{ name: verb, description: description3 },
|
|
2628
|
+
{ collection: TARGET("Collection id, handle, link or a shopify_temp_ ref staged in this chat") },
|
|
2629
|
+
async (args2) => await stage([{ kind, target: target2(args2, "collection") }])
|
|
2630
|
+
);
|
|
2631
|
+
}
|
|
2632
|
+
function membership(verb, kind, description3) {
|
|
2633
|
+
return leaf(
|
|
2634
|
+
`shopify.change.collection.${verb}`,
|
|
2635
|
+
{ name: verb, description: description3 },
|
|
2636
|
+
{
|
|
2637
|
+
collection: TARGET("Collection id, handle, link or a shopify_temp_ ref"),
|
|
2638
|
+
products: TARGET("Products (ids, handles or links), space separated")
|
|
2639
|
+
},
|
|
2640
|
+
async (args2) => {
|
|
2641
|
+
const [collection, ...products] = positionals(args2);
|
|
2642
|
+
if (!collection || products.length === 0) failValidation("pass the collection, then the products");
|
|
2643
|
+
await stage([{ kind, target: collection, payload: { products } }]);
|
|
2644
|
+
}
|
|
2645
|
+
);
|
|
2646
|
+
}
|
|
2647
|
+
function discountValue(args2) {
|
|
2648
|
+
if (args2.percent !== void 0) return { percentage: Number(args2.percent) };
|
|
2649
|
+
if (args2.amount !== void 0) return { amount: args2.amount };
|
|
2650
|
+
return void 0;
|
|
2651
|
+
}
|
|
2652
|
+
function appliesToOf(args2) {
|
|
2653
|
+
const products = list(args2.products);
|
|
2654
|
+
if (products) return { products };
|
|
2655
|
+
const collections = list(args2.collections);
|
|
2656
|
+
return collections ? { collections } : { all: true };
|
|
2657
|
+
}
|
|
2658
|
+
function combinesOf(value) {
|
|
2659
|
+
const combines = list(value) ?? [];
|
|
2660
|
+
if (combines.length === 0) return void 0;
|
|
2661
|
+
return {
|
|
2662
|
+
productDiscounts: combines.includes("product"),
|
|
2663
|
+
orderDiscounts: combines.includes("order"),
|
|
2664
|
+
shippingDiscounts: combines.includes("shipping")
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
function numberOf(value) {
|
|
2668
|
+
return value === void 0 ? void 0 : Number(value);
|
|
2669
|
+
}
|
|
2670
|
+
function discountPayload(args2) {
|
|
2671
|
+
return defined({
|
|
2672
|
+
code: args2.code,
|
|
2673
|
+
title: args2.title,
|
|
2674
|
+
value: discountValue(args2),
|
|
2675
|
+
appliesTo: appliesToOf(args2),
|
|
2676
|
+
minimumSubtotal: args2["min-subtotal"],
|
|
2677
|
+
minimumQuantity: numberOf(args2["min-quantity"]),
|
|
2678
|
+
startsAt: args2.starts,
|
|
2679
|
+
endsAt: args2.ends,
|
|
2680
|
+
usageLimit: numberOf(args2.limit),
|
|
2681
|
+
oncePerCustomer: args2["any-times"] === true ? false : void 0,
|
|
2682
|
+
combinesWith: combinesOf(args2.combines)
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
function signupOfferFlagsProblem(args2) {
|
|
2686
|
+
if (typeof args2.name !== "string" || args2.name.trim() === "")
|
|
2687
|
+
return 'Add --name "<what shoppers and store staff see, e.g. 10% off>": every sign-up offer needs a name.';
|
|
2688
|
+
if (args2["valid-days"] === void 0)
|
|
2689
|
+
return "Add --valid-days <1-365>: how many days each code lasts after it is given.";
|
|
2690
|
+
return void 0;
|
|
2691
|
+
}
|
|
2692
|
+
function signupOfferPayload(args2) {
|
|
2693
|
+
return defined({
|
|
2694
|
+
name: args2.name,
|
|
2695
|
+
value: discountValue(args2),
|
|
2696
|
+
appliesTo: appliesToOf(args2),
|
|
2697
|
+
minimumSubtotal: args2.minimum,
|
|
2698
|
+
validForDays: numberOf(args2["valid-days"]),
|
|
2699
|
+
endsAt: args2.ends,
|
|
2700
|
+
maxCodes: numberOf(args2["max-codes"]),
|
|
2701
|
+
codePrefix: args2.prefix,
|
|
2702
|
+
combinesWith: combinesOf(args2.combines)
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
var signupOfferSet = leaf(
|
|
2706
|
+
"shopify.change.signupOffer.set",
|
|
2707
|
+
{
|
|
2708
|
+
name: "set",
|
|
2709
|
+
description: 'Stage a sign-up offer. Changes money: ask the exact approval question returned by this command. Example: baker shopify change signup-offer set welcome-10 --name "Welcome" --percent 10 --valid-days 14 --max-codes 1000'
|
|
2710
|
+
},
|
|
2711
|
+
{
|
|
2712
|
+
handle: TARGET("Offer handle, e.g. welcome-10"),
|
|
2713
|
+
name: {
|
|
2714
|
+
type: "string",
|
|
2715
|
+
description: 'Offer name shoppers and store staff see (required), e.g. "10% off"',
|
|
2716
|
+
required: false
|
|
2717
|
+
},
|
|
2718
|
+
percent: { type: "string", description: "Percentage off (choose percent or amount)", required: false },
|
|
2719
|
+
amount: { type: "string", description: "Amount off in the store currency", required: false },
|
|
2720
|
+
products: { type: "string", description: "Product handles or ids, comma separated", required: false },
|
|
2721
|
+
collections: { type: "string", description: "Collection handles or ids, comma separated", required: false },
|
|
2722
|
+
minimum: { type: "string", description: "Minimum subtotal", required: false },
|
|
2723
|
+
"valid-days": { type: "string", description: "Each code lasts 1\u2013365 days (required)", required: false },
|
|
2724
|
+
ends: { type: "string", description: "Offer end as ISO date and time; no code outlives it", required: false },
|
|
2725
|
+
"max-codes": { type: "string", description: "Total codes the offer may give, 1\u2013100000", required: false },
|
|
2726
|
+
prefix: { type: "string", description: "Code prefix: 2\u201312 uppercase letters or digits", required: false },
|
|
2727
|
+
combines: {
|
|
2728
|
+
type: "string",
|
|
2729
|
+
description: "Comma-separated product,order,shipping discounts to combine",
|
|
2730
|
+
required: false
|
|
2731
|
+
},
|
|
2732
|
+
...JSON_ARGS
|
|
2733
|
+
},
|
|
2734
|
+
async (args2) => await stageFor(
|
|
2735
|
+
"shopify.signupOffer.set",
|
|
2736
|
+
args2,
|
|
2737
|
+
() => {
|
|
2738
|
+
const problem = signupOfferFlagsProblem(args2);
|
|
2739
|
+
if (problem) failValidation(problem);
|
|
2740
|
+
return signupOfferPayload(args2);
|
|
2741
|
+
},
|
|
2742
|
+
() => target2(args2, "offer handle")
|
|
2743
|
+
)
|
|
2744
|
+
);
|
|
2745
|
+
var signupOfferEnd = leaf(
|
|
2746
|
+
"shopify.change.signupOffer.end",
|
|
2747
|
+
{
|
|
2748
|
+
name: "end",
|
|
2749
|
+
description: "End a sign-up offer at Publish. Existing codes keep their terms. Example: baker shopify change signup-offer end welcome-10"
|
|
2750
|
+
},
|
|
2751
|
+
{ handle: TARGET("Offer handle") },
|
|
2752
|
+
async (args2) => await stage([{ kind: "shopify.signupOffer.end", target: target2(args2, "offer handle") }])
|
|
2753
|
+
);
|
|
2754
|
+
var discountCreate = leaf(
|
|
2755
|
+
"shopify.change.discount.create",
|
|
2756
|
+
{
|
|
2757
|
+
name: "create",
|
|
2758
|
+
description: "Create a discount code for everyone who has it. Changes money: the user must say yes in the chat first \u2014 the command hands you the exact question. Example: baker shopify change discount create --code SPRING10 --percent 10 --ends 2026-10-12T23:59:00Z --limit 500. Applies to everything unless --products or --collections; starts when published unless --starts."
|
|
2759
|
+
},
|
|
2760
|
+
{
|
|
2761
|
+
code: { type: "string", description: "The code shoppers type, e.g. SPRING10", required: false },
|
|
2762
|
+
title: { type: "string", description: "What the store's staff see (default: the code)", required: false },
|
|
2763
|
+
percent: { type: "string", description: "Percentage off, 1-100", required: false },
|
|
2764
|
+
amount: { type: "string", description: "Amount off, as a decimal in the store's currency", required: false },
|
|
2765
|
+
products: { type: "string", description: "Only these products, comma separated", required: false },
|
|
2766
|
+
collections: { type: "string", description: "Only these collections, comma separated", required: false },
|
|
2767
|
+
"min-subtotal": { type: "string", description: "Minimum order amount", required: false },
|
|
2768
|
+
"min-quantity": { type: "string", description: "Minimum number of items", required: false },
|
|
2769
|
+
starts: { type: "string", description: "Start, ISO date-time (default: when published)", required: false },
|
|
2770
|
+
ends: { type: "string", description: "End, ISO date-time (default: no end)", required: false },
|
|
2771
|
+
limit: { type: "string", description: "Total number of uses", required: false },
|
|
2772
|
+
"any-times": { type: "boolean", description: "A customer may use it more than once", required: false },
|
|
2773
|
+
combines: {
|
|
2774
|
+
type: "string",
|
|
2775
|
+
description: "What it combines with, comma separated: product, order, shipping",
|
|
2776
|
+
required: false
|
|
2777
|
+
},
|
|
2778
|
+
...JSON_ARGS
|
|
2779
|
+
},
|
|
2780
|
+
async (args2) => await stageFor("shopify.discountCode.create", args2, () => discountPayload(args2))
|
|
2781
|
+
);
|
|
2782
|
+
var discountStop = leaf(
|
|
2783
|
+
"shopify.change.discount.stop",
|
|
2784
|
+
{
|
|
2785
|
+
name: "stop",
|
|
2786
|
+
description: "Stop a discount code (it ends when published). Changes money: needs the user's yes. Undo puts its own dates back. Example: baker shopify change discount stop SPRING10"
|
|
2787
|
+
},
|
|
2788
|
+
{ code: TARGET("The discount code") },
|
|
2789
|
+
async (args2) => await stage([{ kind: "shopify.discountCode.deactivate", target: target2(args2, "code") }])
|
|
2790
|
+
);
|
|
2791
|
+
var discountDates = leaf(
|
|
2792
|
+
"shopify.change.discount.dates",
|
|
2793
|
+
{
|
|
2794
|
+
name: "dates",
|
|
2795
|
+
description: "Change when a discount code runs. Changes money: needs the user's yes. Example: baker shopify change discount dates SPRING10 --starts 2026-10-01T00:00:00Z --ends never"
|
|
2796
|
+
},
|
|
2797
|
+
{
|
|
2798
|
+
code: TARGET("The discount code"),
|
|
2799
|
+
starts: { type: "string", description: "Start, ISO date-time", required: false },
|
|
2800
|
+
ends: { type: "string", description: 'End, ISO date-time, or "never"', required: false }
|
|
2801
|
+
},
|
|
2802
|
+
async (args2) => {
|
|
2803
|
+
if (typeof args2.starts !== "string" || typeof args2.ends !== "string") failValidation("pass --starts and --ends");
|
|
2804
|
+
await stage([
|
|
2805
|
+
{
|
|
2806
|
+
kind: "shopify.discountCode.schedule",
|
|
2807
|
+
target: target2(args2, "code"),
|
|
2808
|
+
payload: { startsAt: args2.starts, endsAt: args2.ends === "never" ? null : args2.ends }
|
|
2809
|
+
}
|
|
2810
|
+
]);
|
|
2811
|
+
}
|
|
2812
|
+
);
|
|
2813
|
+
var redirectCreate = leaf(
|
|
2814
|
+
"shopify.change.redirect.create",
|
|
2815
|
+
{
|
|
2816
|
+
name: "create",
|
|
2817
|
+
description: "Redirect an old address on the store to a new one (store paths, or a full https link). Example: baker shopify change redirect create --from /collections/old-boards --to /collections/summer-boards"
|
|
2818
|
+
},
|
|
2819
|
+
{
|
|
2820
|
+
from: { type: "string", description: "The old path, starting with /", required: false },
|
|
2821
|
+
to: { type: "string", description: "Where it sends shoppers: a path or an https link", required: false },
|
|
2822
|
+
...JSON_ARGS
|
|
2823
|
+
},
|
|
2824
|
+
async (args2) => await stageFor("shopify.urlRedirect.create", args2, () => defined({ from: args2.from, to: args2.to }))
|
|
2825
|
+
);
|
|
2826
|
+
var redirectRemove = leaf(
|
|
2827
|
+
"shopify.change.redirect.remove",
|
|
2828
|
+
{
|
|
2829
|
+
name: "remove",
|
|
2830
|
+
description: "Remove a redirect, by the path it redirects. Example: baker shopify change redirect remove /old"
|
|
2831
|
+
},
|
|
2832
|
+
{ from: TARGET("The redirected path") },
|
|
2833
|
+
async (args2) => await stage([{ kind: "shopify.urlRedirect.delete", target: target2(args2, "path") }])
|
|
2834
|
+
);
|
|
2835
|
+
var campaignAdd = leaf(
|
|
2836
|
+
"shopify.change.campaign.add",
|
|
2837
|
+
{
|
|
2838
|
+
name: "add",
|
|
2839
|
+
description: "Show a Baker ad campaign in the store's Shopify Marketing, so Shopify's reports credit its sales; Baker then sends its daily figures. Title, status, budget and spend come from the ad platform. Example: baker shopify change campaign add --platform google-ads --account 3865083115 --campaign 2101 --utm-campaign spring --utm-source google --utm-medium cpc"
|
|
2840
|
+
},
|
|
2841
|
+
{
|
|
2842
|
+
platform: { type: "string", description: "google-ads | meta", required: false },
|
|
2843
|
+
account: { type: "string", description: "The ad account id", required: false },
|
|
2844
|
+
campaign: { type: "string", description: "The campaign id", required: false },
|
|
2845
|
+
"utm-campaign": { type: "string", description: "utm_campaign its links carry", required: false },
|
|
2846
|
+
"utm-source": { type: "string", description: "utm_source", required: false },
|
|
2847
|
+
"utm-medium": { type: "string", description: "utm_medium", required: false }
|
|
2848
|
+
},
|
|
2849
|
+
async (args2) => await stage([
|
|
2850
|
+
{
|
|
2851
|
+
kind: "shopify.marketingActivity.upsert",
|
|
2852
|
+
payload: {
|
|
2853
|
+
platform: args2.platform,
|
|
2854
|
+
accountId: args2.account,
|
|
2855
|
+
campaignId: args2.campaign,
|
|
2856
|
+
utm: { campaign: args2["utm-campaign"], source: args2["utm-source"], medium: args2["utm-medium"] }
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
])
|
|
2860
|
+
);
|
|
2861
|
+
var campaignRemove = leaf(
|
|
2862
|
+
"shopify.change.campaign.remove",
|
|
2863
|
+
{
|
|
2864
|
+
name: "remove",
|
|
2865
|
+
description: "Take a Baker campaign out of Shopify Marketing. Example: baker shopify change campaign remove google-ads:3865083115:2101"
|
|
2866
|
+
},
|
|
2867
|
+
{ campaign: TARGET("<platform>:<account>:<campaign>") },
|
|
2868
|
+
async (args2) => await stage([{ kind: "shopify.marketingActivity.delete", target: target2(args2, "campaign") }])
|
|
2869
|
+
);
|
|
2870
|
+
var measurementReconnect = leaf(
|
|
2871
|
+
"shopify.change.measurement.reconnect",
|
|
2872
|
+
{
|
|
2873
|
+
name: "reconnect",
|
|
2874
|
+
description: "Reconnect Baker's measurement on the store (the fix `baker shopify tracking audit` names when it is gone). Applies when published; Baker also repairs it on its own once a day."
|
|
2875
|
+
},
|
|
2876
|
+
{},
|
|
2877
|
+
async () => await stage([{ kind: "shopify.webPixel.update" }])
|
|
2878
|
+
);
|
|
2879
|
+
var group = (name, description3, subCommands) => defineCommand5({ meta: { name, description: description3 }, subCommands });
|
|
2880
|
+
var changeCommand = defineCommand5({
|
|
2881
|
+
meta: {
|
|
2882
|
+
name: "change",
|
|
2883
|
+
description: `Stage a change to the connected store. Nothing changes until the user publishes the chat; every
|
|
2884
|
+
change can be put back with \`baker shopify undo\`. Prices and discounts need the user's yes first.
|
|
2885
|
+
|
|
2886
|
+
baker shopify change product update <product> --title \u2026 --seo-title \u2026 --add-tags a,b
|
|
2887
|
+
baker shopify change variant update <variantId> --sku \u2026 --barcode \u2026 --price \u2026
|
|
2888
|
+
baker shopify change field set product:<product> --namespace custom --key \u2026 --type \u2026 --value \u2026
|
|
2889
|
+
baker shopify change collection create --title \u2026 --products a,b
|
|
2890
|
+
baker shopify change discount create --code SPRING10 --percent 10 --ends <iso>
|
|
2891
|
+
baker shopify change redirect create --from /old --to /new
|
|
2892
|
+
baker shopify change campaign add --platform google-ads --account \u2026 --campaign \u2026
|
|
2893
|
+
baker shopify change measurement reconnect`
|
|
2894
|
+
},
|
|
2895
|
+
subCommands: {
|
|
2896
|
+
"signup-offer": group("signup-offer", "Stage or end a sign-up offer", { set: signupOfferSet, end: signupOfferEnd }),
|
|
2897
|
+
product: group("product", "Change a product's title, description, SEO, tags or type", { update: productUpdate }),
|
|
2898
|
+
variant: group("variant", "Change an option's SKU, barcode, weight or price", { update: variantUpdate }),
|
|
2899
|
+
field: group("field", "Set or remove a custom field", { set: fieldSet, remove: fieldRemove }),
|
|
2900
|
+
collection: group("collection", "Create, change, show, hide, fill or delete a collection", {
|
|
2901
|
+
create: collectionCreate,
|
|
2902
|
+
update: collectionUpdate,
|
|
2903
|
+
show: collectionVerb(
|
|
2904
|
+
"show",
|
|
2905
|
+
"shopify.collection.publish",
|
|
2906
|
+
"Show a collection on the online store. Example: baker shopify change collection show summer-boards"
|
|
2907
|
+
),
|
|
2908
|
+
hide: collectionVerb(
|
|
2909
|
+
"hide",
|
|
2910
|
+
"shopify.collection.unpublish",
|
|
2911
|
+
"Hide a collection from the online store. Example: baker shopify change collection hide summer-boards"
|
|
2912
|
+
),
|
|
2913
|
+
delete: collectionVerb(
|
|
2914
|
+
"delete",
|
|
2915
|
+
"shopify.collection.delete",
|
|
2916
|
+
"Delete a collection \u2014 only one undo could rebuild exactly (hand-picked products, no image, no custom fields, no discount or menu pointing at it); refused otherwise with the reason. Stage a redirect for its address. Example: baker shopify change collection delete old-boards"
|
|
2917
|
+
),
|
|
2918
|
+
add: membership(
|
|
2919
|
+
"add",
|
|
2920
|
+
"shopify.collection.addProducts",
|
|
2921
|
+
"Add products to a collection. Example: baker shopify change collection add summer-boards snowboard-a snowboard-b"
|
|
2922
|
+
),
|
|
2923
|
+
remove: membership(
|
|
2924
|
+
"remove",
|
|
2925
|
+
"shopify.collection.removeProducts",
|
|
2926
|
+
"Remove products from a collection. Example: baker shopify change collection remove summer-boards snowboard-a"
|
|
2927
|
+
)
|
|
2928
|
+
}),
|
|
2929
|
+
discount: group("discount", "Create, stop or reschedule a discount code (needs the user's yes)", {
|
|
2930
|
+
create: discountCreate,
|
|
2931
|
+
stop: discountStop,
|
|
2932
|
+
dates: discountDates
|
|
2933
|
+
}),
|
|
2934
|
+
redirect: group("redirect", "Create or remove a redirect", { create: redirectCreate, remove: redirectRemove }),
|
|
2935
|
+
campaign: group("campaign", "Show or remove a Baker campaign in Shopify Marketing", {
|
|
2936
|
+
add: campaignAdd,
|
|
2937
|
+
remove: campaignRemove
|
|
2938
|
+
}),
|
|
2939
|
+
measurement: group("measurement", "Reconnect Baker's measurement on the store", {
|
|
2940
|
+
reconnect: measurementReconnect
|
|
2941
|
+
})
|
|
2942
|
+
}
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2945
|
+
// src/commands/shopify/changes.ts
|
|
2946
|
+
var changesCommand = leaf(
|
|
2947
|
+
"shopify.changes",
|
|
2948
|
+
{
|
|
2949
|
+
name: "changes",
|
|
2950
|
+
description: "The changes Baker made to the store, newest first, each with its changeId and whether it can be undone. Example: baker shopify changes --limit 20 (or --chat <id> for one chat's)."
|
|
2951
|
+
},
|
|
2952
|
+
{
|
|
2953
|
+
limit: { type: "string", description: "How many, 1-100 (default 20)", required: false },
|
|
2954
|
+
chat: { type: "string", description: "Only one chat's changes", required: false }
|
|
2955
|
+
},
|
|
2956
|
+
async (args2) => {
|
|
2957
|
+
try {
|
|
2958
|
+
const data = await apiGet(
|
|
2959
|
+
"/api/shopify/changes",
|
|
2960
|
+
params({ limit: args2.limit, chatId: args2.chat })
|
|
2961
|
+
);
|
|
2962
|
+
writeJson({ ok: true, hints: changesHints(data), data });
|
|
2963
|
+
} catch (err) {
|
|
2964
|
+
failChange(err);
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
);
|
|
2968
|
+
var undoCommand = leaf(
|
|
2969
|
+
"shopify.undo",
|
|
2970
|
+
{
|
|
2971
|
+
name: "undo",
|
|
2972
|
+
description: "Stage the change that puts an applied store change back, read from the store as it was before it \u2014 applied when the user publishes, like any change. Prices and discounts ask the user again. Refused when the store changed since. Example: baker shopify undo <changeId> [<changeId>\u2026]"
|
|
2973
|
+
},
|
|
2974
|
+
{ changeIds: { type: "positional", description: "changeIds from `baker shopify changes`", required: false } },
|
|
2975
|
+
async (args2) => {
|
|
2976
|
+
const changeIds = Array.isArray(args2._) ? args2._.filter((value) => typeof value === "string") : [];
|
|
2977
|
+
if (changeIds.length === 0) failValidation("pass the changeIds to undo (see `baker shopify changes`)");
|
|
2978
|
+
try {
|
|
2979
|
+
const data = await apiPost("/api/shopify/undo", {
|
|
2980
|
+
chatId: requireChatId(),
|
|
2981
|
+
changeIds
|
|
2982
|
+
});
|
|
2983
|
+
emitStage(data);
|
|
2984
|
+
} catch (err) {
|
|
2985
|
+
failChange(err);
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
);
|
|
2989
|
+
|
|
2990
|
+
// src/commands/shopify/draft.ts
|
|
2991
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
2992
|
+
function refOf(args2) {
|
|
2993
|
+
const [ref] = Array.isArray(args2._) ? args2._ : [];
|
|
2994
|
+
if (typeof ref !== "string" || ref === "")
|
|
2995
|
+
failValidation("pass the staged change's ref (see `baker shopify draft list`)");
|
|
2996
|
+
return ref;
|
|
2997
|
+
}
|
|
2998
|
+
function listHints(data) {
|
|
2999
|
+
if (data.status === "none" || data.count === 0) return ["No store changes are staged on this chat."];
|
|
3000
|
+
if (data.status === "active") {
|
|
3001
|
+
return [
|
|
3002
|
+
"Staged only: these apply when the user publishes the chat. Correct one with `baker shopify draft amend <ref>`, drop one with `remove`."
|
|
3003
|
+
];
|
|
3004
|
+
}
|
|
3005
|
+
const failed = data.ops.filter((op) => op.result?.status === "failed" || op.result?.status === "skipped");
|
|
3006
|
+
return failed.length > 0 ? [`${failed.length} of ${data.count} changes did not go through \u2014 say which and why; never report them as made.`] : [];
|
|
3007
|
+
}
|
|
3008
|
+
var list2 = leaf(
|
|
3009
|
+
"shopify.draft.list",
|
|
3010
|
+
{
|
|
3011
|
+
name: "list",
|
|
3012
|
+
description: "Start here to review the store changes staged on this chat (or another chat's with --chat): what each is, whether it changes money, and after publish what happened to it."
|
|
3013
|
+
},
|
|
3014
|
+
{ chat: CHAT_READ_ARG },
|
|
3015
|
+
async (args2) => {
|
|
3016
|
+
try {
|
|
3017
|
+
const data = await apiPost("/api/shopify/draft/list", {
|
|
3018
|
+
chatId: resolveChatId(args2.chat)
|
|
3019
|
+
});
|
|
3020
|
+
writeJson({ ok: true, hints: listHints(data), data });
|
|
3021
|
+
} catch (err) {
|
|
3022
|
+
failChange(err);
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
);
|
|
3026
|
+
var show = leaf(
|
|
3027
|
+
"shopify.draft.show",
|
|
3028
|
+
{
|
|
3029
|
+
name: "show",
|
|
3030
|
+
description: "One staged store change in full: what it sends and the store as it was read when it was staged (the before)."
|
|
3031
|
+
},
|
|
3032
|
+
{ ref: { type: "positional", description: "The staged change's ref", required: false }, chat: CHAT_READ_ARG },
|
|
3033
|
+
async (args2) => {
|
|
3034
|
+
try {
|
|
3035
|
+
const data = await apiPost("/api/shopify/draft/show", { chatId: resolveChatId(args2.chat), ref: refOf(args2) });
|
|
3036
|
+
writeJson({ ok: true, data });
|
|
3037
|
+
} catch (err) {
|
|
3038
|
+
failChange(err);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
);
|
|
3042
|
+
var amend = leaf(
|
|
3043
|
+
"shopify.draft.amend",
|
|
3044
|
+
{
|
|
3045
|
+
name: "amend",
|
|
3046
|
+
description: `Correct a staged change in place: a JSON patch merged into its payload (objects merge, null removes a key) and every check run again \u2014 a changed price asks the user again. Example: baker shopify draft amend product:123 --patch '{"title":"Better"}'. Keys are the payload's, not the flags': productType (not type), seo {title, description}, addTags, compareAtPrice; a key it doesn't take is refused with the ones it does.`
|
|
3047
|
+
},
|
|
3048
|
+
{
|
|
3049
|
+
ref: { type: "positional", description: "The staged change's ref", required: false },
|
|
3050
|
+
patch: { type: "string", description: "Inline JSON patch", required: false },
|
|
3051
|
+
file: { type: "string", description: "A JSON file with the patch", required: false }
|
|
3052
|
+
},
|
|
3053
|
+
async (args2) => {
|
|
3054
|
+
try {
|
|
3055
|
+
const data = await apiPost("/api/shopify/draft/amend", {
|
|
3056
|
+
chatId: requireChatId(),
|
|
3057
|
+
ref: refOf(args2),
|
|
3058
|
+
patch: loadJsonArg(args2, "patch")
|
|
3059
|
+
});
|
|
3060
|
+
emitStage(data);
|
|
3061
|
+
} catch (err) {
|
|
3062
|
+
failChange(err);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
);
|
|
3066
|
+
var remove = leaf(
|
|
3067
|
+
"shopify.draft.remove",
|
|
3068
|
+
{
|
|
3069
|
+
name: "remove",
|
|
3070
|
+
description: "Drop one staged store change, and any change that needs it (a new collection's show)."
|
|
3071
|
+
},
|
|
3072
|
+
{ ref: { type: "positional", description: "The staged change's ref", required: false } },
|
|
3073
|
+
async (args2) => {
|
|
3074
|
+
try {
|
|
3075
|
+
writeJson({
|
|
3076
|
+
ok: true,
|
|
3077
|
+
data: await apiPost("/api/shopify/draft/remove", { chatId: requireChatId(), ref: refOf(args2) })
|
|
3078
|
+
});
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
failChange(err);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
);
|
|
3084
|
+
var clear = leaf(
|
|
3085
|
+
"shopify.draft.clear",
|
|
3086
|
+
{ name: "clear", description: "Drop every store change staged on this chat." },
|
|
3087
|
+
{},
|
|
3088
|
+
async () => {
|
|
3089
|
+
try {
|
|
3090
|
+
writeJson({ ok: true, data: await apiPost("/api/shopify/draft/clear", { chatId: requireChatId() }) });
|
|
3091
|
+
} catch (err) {
|
|
3092
|
+
failChange(err);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
);
|
|
3096
|
+
var draftCommand = defineCommand6({
|
|
3097
|
+
meta: { name: "draft", description: "List, show, amend, remove or clear the store changes staged on this chat" },
|
|
3098
|
+
subCommands: { list: list2, show, amend, remove, clear }
|
|
3099
|
+
});
|
|
3100
|
+
|
|
3101
|
+
// src/commands/shopify/promotions.ts
|
|
3102
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
3103
|
+
|
|
3104
|
+
// src/commands/shopify/landingLinks.ts
|
|
3105
|
+
import { readdir, readFile, stat } from "fs/promises";
|
|
3106
|
+
import path from "path";
|
|
3107
|
+
import { parse } from "@astrojs/compiler/sync";
|
|
3108
|
+
var PAGES_MAX = 300;
|
|
3109
|
+
var FILE_BYTES_MAX = 256 * 1024;
|
|
3110
|
+
function escaped(text) {
|
|
3111
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3112
|
+
}
|
|
3113
|
+
var BUY_COMPONENTS = /* @__PURE__ */ new Set(["ShopifyProduct", "ShopifyBuyButton"]);
|
|
3114
|
+
var STRING_LITERAL = /^\s*(["'`])([^"'`]*)\1\s*$/;
|
|
3115
|
+
var ITEM_PRODUCT = /\bproduct\s*:\s*(["'`])([^"'`]*)\1/g;
|
|
3116
|
+
function namedBy(attribute) {
|
|
3117
|
+
if (attribute.name === "product") {
|
|
3118
|
+
if (attribute.kind === "quoted" || attribute.kind === "template-literal") return [attribute.value];
|
|
3119
|
+
if (attribute.kind === "expression") return [STRING_LITERAL.exec(attribute.value)?.[2] ?? ""];
|
|
3120
|
+
}
|
|
3121
|
+
if (attribute.name === "items" && attribute.kind === "expression") {
|
|
3122
|
+
return [...attribute.value.matchAll(ITEM_PRODUCT)].map((match) => match[2] ?? "");
|
|
3123
|
+
}
|
|
3124
|
+
return [];
|
|
3125
|
+
}
|
|
3126
|
+
function componentProducts(node, into) {
|
|
3127
|
+
if (node.type === "component" && BUY_COMPONENTS.has(node.name)) {
|
|
3128
|
+
for (const attribute of node.attributes) into.push(...namedBy(attribute));
|
|
3129
|
+
}
|
|
3130
|
+
if ("children" in node) for (const child of node.children) componentProducts(child, into);
|
|
3131
|
+
return into;
|
|
3132
|
+
}
|
|
3133
|
+
function buyComponentProducts(file) {
|
|
3134
|
+
if (!file.path.endsWith(".astro")) return [];
|
|
3135
|
+
try {
|
|
3136
|
+
return componentProducts(parse(file.text, void 0).ast, []);
|
|
3137
|
+
} catch {
|
|
3138
|
+
return [];
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function namesProduct(file, keys) {
|
|
3142
|
+
const wanted = new Set([keys.handle.toLowerCase(), ...keys.productId ? [keys.productId] : []].filter(Boolean));
|
|
3143
|
+
return buyComponentProducts(file).some((named) => wanted.has(named.trim().toLowerCase()));
|
|
3144
|
+
}
|
|
3145
|
+
function evidenceIn(file, keys) {
|
|
3146
|
+
const { text } = file;
|
|
3147
|
+
const evidence = [];
|
|
3148
|
+
if (keys.buyUrls.some((url) => url && text.includes(url))) evidence.push("buy_link");
|
|
3149
|
+
const page = new RegExp(`/products/${escaped(keys.handle)}(?![A-Za-z0-9_-])`, "i");
|
|
3150
|
+
if (keys.handle && page.test(text)) evidence.push("product_page");
|
|
3151
|
+
const option = keys.variantIds.some(
|
|
3152
|
+
(id) => new RegExp(`(?:/cart/(?:\\d+:\\d+,)*${id}:|variant=${id}(?!\\d))`).test(text)
|
|
3153
|
+
);
|
|
3154
|
+
if (option) evidence.push("variant_id");
|
|
3155
|
+
if (namesProduct(file, keys)) evidence.push("buy_button");
|
|
3156
|
+
return evidence;
|
|
3157
|
+
}
|
|
3158
|
+
function landingLinksOf(pages, keys) {
|
|
3159
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
3160
|
+
for (const page of pages) {
|
|
3161
|
+
const found = new Set(page.files.flatMap((file) => evidenceIn(file, keys)));
|
|
3162
|
+
if (found.size === 0) continue;
|
|
3163
|
+
const slug = page.slug.replace(/--\d+$/, "");
|
|
3164
|
+
const held = bySlug.get(slug) ?? /* @__PURE__ */ new Set();
|
|
3165
|
+
for (const evidence of found) held.add(evidence);
|
|
3166
|
+
bySlug.set(slug, held);
|
|
3167
|
+
}
|
|
3168
|
+
const order = ["buy_link", "product_page", "variant_id", "buy_button"];
|
|
3169
|
+
return [...bySlug].map(([slug, evidence]) => ({ slug, evidence: order.filter((entry) => evidence.has(entry)) }));
|
|
3170
|
+
}
|
|
3171
|
+
async function isDir(target3) {
|
|
3172
|
+
try {
|
|
3173
|
+
return (await stat(target3)).isDirectory();
|
|
3174
|
+
} catch {
|
|
3175
|
+
return false;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
async function readWorkspacePages(root) {
|
|
3179
|
+
const dir = path.join(root, "src/pages");
|
|
3180
|
+
if (!await isDir(dir)) return null;
|
|
3181
|
+
let entries;
|
|
3182
|
+
try {
|
|
3183
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
3184
|
+
} catch {
|
|
3185
|
+
return null;
|
|
3186
|
+
}
|
|
3187
|
+
const slugs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
3188
|
+
const pages = [];
|
|
3189
|
+
for (const slug of slugs.slice(0, PAGES_MAX)) {
|
|
3190
|
+
const files = [];
|
|
3191
|
+
for (const file of await landingGraphFiles(root, slug)) {
|
|
3192
|
+
try {
|
|
3193
|
+
const absolute = path.join(root, file);
|
|
3194
|
+
if ((await stat(absolute)).size > FILE_BYTES_MAX) continue;
|
|
3195
|
+
files.push({ path: file, text: await readFile(absolute, "utf8") });
|
|
3196
|
+
} catch {
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
pages.push({ slug, files });
|
|
3200
|
+
}
|
|
3201
|
+
return { pages, skipped: Math.max(0, slugs.length - PAGES_MAX) };
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/commands/shopify/promotionHints.ts
|
|
3205
|
+
var PLATFORM = { "google-ads": "Google Ads", meta: "Meta" };
|
|
3206
|
+
function plural(count, one, many = `${one}s`) {
|
|
3207
|
+
return `${count} ${count === 1 ? one : many}`;
|
|
3208
|
+
}
|
|
3209
|
+
function pauseCommand(ad) {
|
|
3210
|
+
if (ad.platform === "meta") return `\`baker ads meta ads pause ${ad.id ?? "<id>"}\``;
|
|
3211
|
+
const target3 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
|
|
3212
|
+
return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target3} --status PAUSED\`` : `\`baker ads google ads pause ${target3}\``;
|
|
3213
|
+
}
|
|
3214
|
+
function waitingHints(answer) {
|
|
3215
|
+
if (answer.product.stock === "in_stock") return [];
|
|
3216
|
+
return answer.ads.flatMap(
|
|
3217
|
+
(ad) => ad.status === "active" && ad.pause_waiting_in ? [
|
|
3218
|
+
`Pausing \u201C${ad.name}\u201D is already waiting for review in the chat \u201C${ad.pause_waiting_in}\u201D: stage nothing for it, and tell the user it is waiting there.`
|
|
3219
|
+
] : []
|
|
3220
|
+
);
|
|
3221
|
+
}
|
|
3222
|
+
function pauseHint(answer) {
|
|
3223
|
+
if (answer.product.stock === "in_stock" || answer.ads.length === 0) return null;
|
|
3224
|
+
const active = answer.ads.filter((ad) => ad.status === "active" && !ad.pause_waiting_in);
|
|
3225
|
+
if (active.length === 0 && answer.ads.some((ad) => ad.status === "active")) return null;
|
|
3226
|
+
if (active.length === 0) {
|
|
3227
|
+
return `Every ad that sends people to \u201C${answer.product.title}\u201D is already paused: stage nothing for it. Tell the user this run paused nothing, and promise nothing about these ads coming back when it is back in stock.`;
|
|
3228
|
+
}
|
|
3229
|
+
const one = active.length === 1;
|
|
3230
|
+
return `${plural(active.length, "active ad")} still ${one ? "sends" : "send"} people to \u201C${answer.product.title}\u201D, which is out of stock. Stage pausing ${one ? "it" : "them"}: ${active.map(pauseCommand).join("; ")}. Each pause waits for the user to review and publish, and nothing turns it back on by itself.`;
|
|
3231
|
+
}
|
|
3232
|
+
function promotionHints(answer, scan) {
|
|
3233
|
+
const hints = [];
|
|
3234
|
+
const pause = pauseHint(answer);
|
|
3235
|
+
if (pause) hints.push(pause);
|
|
3236
|
+
hints.push(...waitingHints(answer));
|
|
3237
|
+
if (answer.shopping.length > 0 || answer.catalog_ads > 0) {
|
|
3238
|
+
hints.push(
|
|
3239
|
+
"Shopping campaigns and catalog ads stop showing a product nobody can buy by themselves \u2014 never pause them for one product."
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
for (const missed of answer.checked.not_checked) {
|
|
3243
|
+
hints.push(
|
|
3244
|
+
`${PLATFORM[missed.platform] ?? missed.platform} could not be checked (${missed.reason}); say so rather than that nothing runs there.`
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
for (const landing of answer.landings.filter((entry) => !entry.published)) {
|
|
3248
|
+
hints.push(`/${landing.slug} links to this product but is not live; no ad can send people to it yet.`);
|
|
3249
|
+
}
|
|
3250
|
+
if (answer.ads.length === 0 && answer.landings.length === 0 && answer.shopping.length === 0) {
|
|
3251
|
+
const nothing = `Nothing advertises \u201C${answer.product.title}\u201D that Baker can see`;
|
|
3252
|
+
hints.push(
|
|
3253
|
+
answer.product.stock === "in_stock" ? `${nothing}.` : `${nothing}: stage nothing for it. Tell the user this run paused nothing for it, and promise nothing about ads coming back when it is back in stock.`
|
|
3254
|
+
);
|
|
3255
|
+
}
|
|
3256
|
+
if (!scan.scanned) {
|
|
3257
|
+
hints.push(
|
|
3258
|
+
"This workspace's pages were not read (no src/pages here), so landings are from Baker's last scan and measured visits only."
|
|
3259
|
+
);
|
|
3260
|
+
} else if (scan.skippedPages > 0) {
|
|
3261
|
+
hints.push(`${plural(scan.skippedPages, "page")} not checked for links to this product (past the 300-page bound).`);
|
|
3262
|
+
}
|
|
3263
|
+
if (!answer.remembered && answer.ads.length > 0) {
|
|
3264
|
+
hints.push(
|
|
3265
|
+
"This read was made outside a Session (no chat): a pause staged from it will never be offered back for restarting when the product returns."
|
|
3266
|
+
);
|
|
3267
|
+
}
|
|
3268
|
+
return hints;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
// src/commands/shopify/stockHoldReport.ts
|
|
3272
|
+
var PLATFORM2 = { "google-ads": "Google Ads", meta: "Meta" };
|
|
3273
|
+
var LEVEL = {
|
|
3274
|
+
campaign: "campaign",
|
|
3275
|
+
ad_group: "ad group",
|
|
3276
|
+
ad: "ad",
|
|
3277
|
+
asset_group: "asset group",
|
|
3278
|
+
adset: "ad set"
|
|
3279
|
+
};
|
|
3280
|
+
function adName(ad) {
|
|
3281
|
+
return `${PLATFORM2[ad.platform]} ${LEVEL[ad.level]} ${ad.name ? `\u201C${ad.name}\u201D` : ad.entity_id}`;
|
|
3282
|
+
}
|
|
3283
|
+
var GOOGLE_GROUP = {
|
|
3284
|
+
campaign: "campaigns",
|
|
3285
|
+
ad_group: "ad-groups",
|
|
3286
|
+
ad: "ads",
|
|
3287
|
+
asset_group: "asset-groups",
|
|
3288
|
+
adset: "ads"
|
|
3289
|
+
};
|
|
3290
|
+
var META_GROUP = {
|
|
3291
|
+
campaign: "campaigns",
|
|
3292
|
+
ad_group: "adsets",
|
|
3293
|
+
ad: "ads",
|
|
3294
|
+
asset_group: "ads",
|
|
3295
|
+
adset: "adsets"
|
|
3296
|
+
};
|
|
3297
|
+
function resumeCommand(hold) {
|
|
3298
|
+
if (hold.platform === "meta") return `baker ads meta ${META_GROUP[hold.level]} resume ${hold.entity_id}`;
|
|
3299
|
+
const target3 = `${hold.entity_id} --customer-id ${hold.account_id}`;
|
|
3300
|
+
return hold.level === "asset_group" ? `baker ads google asset-groups update ${target3} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target3}`;
|
|
3301
|
+
}
|
|
3302
|
+
function quoted(title, fallback) {
|
|
3303
|
+
return title ? `\u201C${title}\u201D` : fallback;
|
|
3304
|
+
}
|
|
3305
|
+
function heldLine(hold) {
|
|
3306
|
+
const name = adName(hold);
|
|
3307
|
+
const product = quoted(hold.product.title, "the product");
|
|
3308
|
+
switch (hold.verdict) {
|
|
3309
|
+
case "resumable":
|
|
3310
|
+
return `Ready to turn back on when you publish: ${name} \u2014 Baker paused it because ${product} ran out.`;
|
|
3311
|
+
case "still_out_of_stock":
|
|
3312
|
+
return `Stays paused: ${name} \u2014 ${product} still cannot be bought.`;
|
|
3313
|
+
case "changed_since":
|
|
3314
|
+
return `Left as it is: ${name} \u2014 someone changed it after Baker paused it when ${product} ran out, so check it yourself.`;
|
|
3315
|
+
case "still_promotes_out_of_stock":
|
|
3316
|
+
return `Stays paused: ${name} \u2014 it also sends people to ${quoted(hold.verdict_detail, "another product")}, which is still out of stock.`;
|
|
3317
|
+
case "cannot_confirm":
|
|
3318
|
+
return `Left as it is: ${name} \u2014 Baker cannot tell whether someone changed it after pausing it when ${product} ran out, so check it before turning it back on.`;
|
|
3319
|
+
case "already_active":
|
|
3320
|
+
return `Nothing to do: ${name} is already running again.`;
|
|
3321
|
+
default:
|
|
3322
|
+
return null;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
var CLOSED_HOW = {
|
|
3326
|
+
resumed_by_back_in_stock_run: "turned back on by an earlier back-in-stock run",
|
|
3327
|
+
changed_by_baker: "turned back on by other work published in Baker",
|
|
3328
|
+
already_active: "found running again, turned back on by someone",
|
|
3329
|
+
removed: "removed",
|
|
3330
|
+
product_removed: "let go when the product left the store"
|
|
3331
|
+
};
|
|
3332
|
+
function plural2(count, one) {
|
|
3333
|
+
return `${count} ${count === 1 ? one : `${one}s`}`;
|
|
3334
|
+
}
|
|
3335
|
+
function nothingHeldLines(answer, asked, product) {
|
|
3336
|
+
if (!asked.product) {
|
|
3337
|
+
return answer.holds.length > 0 ? ["None of these is still paused because its product ran out, so there is nothing to turn back on."] : ["Baker has no ad paused because a product ran out, so there is nothing to turn back on."];
|
|
3338
|
+
}
|
|
3339
|
+
if (answer.pending_sessions.length > 0) {
|
|
3340
|
+
return [
|
|
3341
|
+
`Baker's pause for ${product} is still waiting for your review and was never published, so there is nothing to turn back on.`
|
|
3342
|
+
];
|
|
3343
|
+
}
|
|
3344
|
+
if (answer.recently_closed.length > 0) {
|
|
3345
|
+
return answer.recently_closed.map(
|
|
3346
|
+
(hold) => `Already handled: Baker paused ${adName(hold)} when ${product} ran out, and it was later ${CLOSED_HOW[hold.resolved_reason]}.`
|
|
3347
|
+
);
|
|
3348
|
+
}
|
|
3349
|
+
return [`Baker has nothing paused because ${product} ran out, so there is nothing to turn back on.`];
|
|
3350
|
+
}
|
|
3351
|
+
function stockHoldReport(answer, asked) {
|
|
3352
|
+
const product = quoted(answer.product?.title, "the product that came back");
|
|
3353
|
+
const held = answer.holds.filter((hold) => hold.state === "held");
|
|
3354
|
+
const tell_user = [
|
|
3355
|
+
...held.length === 0 ? nothingHeldLines(answer, asked, product) : [],
|
|
3356
|
+
...held.flatMap((hold) => heldLine(hold) ?? []),
|
|
3357
|
+
...answer.not_paused.map((ad) => `Left as it is: ${adName(ad)} \u2014 Baker did not pause it when ${product} ran out.`),
|
|
3358
|
+
...answer.pending_sessions.map(
|
|
3359
|
+
(pending) => `Discard the work waiting for your review, \u201C${pending.title}\u201D: it would pause ${plural2(pending.staged_pauses, "ad")} for ${product}, which can be bought again.`
|
|
3360
|
+
)
|
|
3361
|
+
];
|
|
3362
|
+
const resume = held.filter((hold) => hold.verdict === "resumable").map((hold) => ({ ad: adName(hold), command: resumeCommand(hold) }));
|
|
3363
|
+
const pages = answer.pages_changed.map((run) => ({
|
|
3364
|
+
pages: run.pages.map((slug) => `/${slug}`),
|
|
3365
|
+
read: `baker chats diff ${run.chat_id} --surface landings --full`
|
|
3366
|
+
}));
|
|
3367
|
+
const hints = [
|
|
3368
|
+
resume.length > 0 ? "Stage each `resume` command, and nothing else." : `Stage nothing${asked.product ? ` for ${product}` : ""}.`,
|
|
3369
|
+
"Tell the user the `tell_user` lines as they are written, and nothing about any other ad: an ad this read does not name is not this run's to explain.",
|
|
3370
|
+
...pages.map(
|
|
3371
|
+
(run) => `The out-of-stock run also changed ${run.pages.join(", ")}: read those changes with its \`read\` command, and put back only what it changed for ${product}.`
|
|
3372
|
+
)
|
|
3373
|
+
];
|
|
3374
|
+
const compact = { tell_user, resume, ...pages.length > 0 ? { pages } : {} };
|
|
3375
|
+
if (!asked.full) return { data: compact, hints };
|
|
3376
|
+
const { store: _store, ...everything } = answer;
|
|
3377
|
+
return { data: { ...compact, ...everything }, hints };
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// src/commands/shopify/promotions.ts
|
|
3381
|
+
registerSchema({
|
|
3382
|
+
command: "shopify.promotions",
|
|
3383
|
+
description: "Start here when a product runs out of stock or comes back: what is advertising it \u2014 ads on Google Ads and Meta that lead to its store page, its buy link or a landing that links to it (`via`), the landings themselves (from this workspace's pages, and from measured visits), and the Shopping campaigns and catalog ads that show it (they stop by themselves when it runs out \u2014 never pause them). Read it in the Session that pauses: a pause is remembered only for a product whose promotions that Session read, so that a scheduled agent on the product coming back in stock, if the company has one, can later stage turning it back on for the user to review \u2014 nothing restarts by itself, and every pause waits for the user to review and publish. `checked.not_checked` names a platform Baker could not read.",
|
|
3384
|
+
args: {
|
|
3385
|
+
product: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
3386
|
+
full: {
|
|
3387
|
+
...fullArg,
|
|
3388
|
+
description: "Add each ad's campaign and ad group or ad set ids and its URL (its id, account and Google resource name are always there)"
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
});
|
|
3392
|
+
registerSchema({
|
|
3393
|
+
command: "shopify.stock-holds",
|
|
3394
|
+
description: "Start here when a product is back in stock: what Baker paused because it ran out, answered for the marketer. `resume` holds the one command to stage per ad Baker may turn back on now \u2014 stage those and nothing else. `tell_user` holds one sentence per ad (and per piece of out-of-stock work still waiting for review), written for the marketer: tell them those, as written. `pages` names landings the out-of-stock run changed and the read that shows those page changes. Use `--product` for the product that came back: the answer then speaks only of its ads. `--full` adds the codes behind each sentence (verdicts, ids) for debugging, never for the user.",
|
|
3395
|
+
args: {
|
|
3396
|
+
product: { type: "string", description: "Only one product's holds: its id, handle or link", required: false },
|
|
3397
|
+
all: { type: "boolean", description: "Resolved holds too (resumed or released)", required: false, default: false },
|
|
3398
|
+
full: fullArg
|
|
3399
|
+
}
|
|
3400
|
+
});
|
|
3401
|
+
function promotionsShown(answer, full) {
|
|
3402
|
+
if (full) return answer;
|
|
3403
|
+
const { remembered: _remembered, ...shown } = answer;
|
|
3404
|
+
return shown;
|
|
3405
|
+
}
|
|
3406
|
+
var promotionsCommand = defineCommand7({
|
|
3407
|
+
meta: {
|
|
3408
|
+
name: "promotions",
|
|
3409
|
+
description: "What advertises one product: ads that lead to it, landings that link to it, Shopping that shows it. Example: baker shopify promotions 10310413942969"
|
|
3410
|
+
},
|
|
3411
|
+
args: {
|
|
3412
|
+
product: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
3413
|
+
full: {
|
|
3414
|
+
type: "boolean",
|
|
3415
|
+
description: "Add campaign and ad group or ad set ids and URLs",
|
|
3416
|
+
required: false,
|
|
3417
|
+
default: false
|
|
3418
|
+
}
|
|
3419
|
+
},
|
|
3420
|
+
run: async ({ args: args2 }) => {
|
|
3421
|
+
try {
|
|
3422
|
+
const positionals2 = Array.isArray(args2._) ? args2._ : [];
|
|
3423
|
+
if (positionals2.length > 1) {
|
|
3424
|
+
throw new ApiError("VALIDATION_ERROR", "product: Pass one product id, handle or product link.");
|
|
3425
|
+
}
|
|
3426
|
+
const first = await apiGet("/api/shopify/product", params({ ref: args2.product }));
|
|
3427
|
+
const { store } = first;
|
|
3428
|
+
const product = await withEveryOption(
|
|
3429
|
+
first.product,
|
|
3430
|
+
async (variantsCursor) => (await apiGet(
|
|
3431
|
+
"/api/shopify/product",
|
|
3432
|
+
params({ ref: first.product.productId, variantsCursor })
|
|
3433
|
+
)).product
|
|
3434
|
+
);
|
|
3435
|
+
const scan = await readWorkspacePages(process.cwd());
|
|
3436
|
+
const linked = scan ? landingLinksOf(scan.pages, {
|
|
3437
|
+
handle: product.handle,
|
|
3438
|
+
productId: product.productId,
|
|
3439
|
+
variantIds: product.variants.map((variant) => variant.variantId),
|
|
3440
|
+
buyUrls: product.variants.flatMap((variant) => variant.buyUrl ? [variant.buyUrl] : [])
|
|
3441
|
+
}) : void 0;
|
|
3442
|
+
const answer = await apiPost("/api/shopify/promotions", {
|
|
3443
|
+
product: product.productId,
|
|
3444
|
+
...linked ? { linked_landings: linked.slice(0, 50) } : {},
|
|
3445
|
+
scanned: scan !== null,
|
|
3446
|
+
...args2.full ? { full: true } : {}
|
|
3447
|
+
});
|
|
3448
|
+
emit(promotionsShown(answer, args2.full === true), { store }, [
|
|
3449
|
+
...storeStateHints(store),
|
|
3450
|
+
...promotionHints(answer, { scanned: scan !== null, skippedPages: scan?.skipped ?? 0 })
|
|
3451
|
+
]);
|
|
3452
|
+
} catch (err) {
|
|
3453
|
+
fail(err);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
});
|
|
3457
|
+
var stockHoldsCommand = defineCommand7({
|
|
3458
|
+
meta: {
|
|
3459
|
+
name: "stock-holds",
|
|
3460
|
+
description: "The ads Baker paused because a product ran out, and whether each may be resumed now. Example: baker shopify stock-holds --product 10310413942969"
|
|
3461
|
+
},
|
|
3462
|
+
args: {
|
|
3463
|
+
product: { type: "string", description: "Only one product: its id, handle or link", required: false },
|
|
3464
|
+
all: { type: "boolean", description: "Resolved holds too", required: false, default: false },
|
|
3465
|
+
full: { type: "boolean", description: "Everything Baker keeps", required: false, default: false }
|
|
3466
|
+
},
|
|
3467
|
+
run: async ({ args: args2 }) => {
|
|
3468
|
+
try {
|
|
3469
|
+
const answer = await apiGet(
|
|
3470
|
+
"/api/shopify/stock-holds",
|
|
3471
|
+
params({ product: args2.product, all: args2.all, full: args2.full })
|
|
3472
|
+
);
|
|
3473
|
+
const report = stockHoldReport(answer, { product: args2.product !== void 0, full: args2.full === true });
|
|
3474
|
+
emit(report.data, { store: answer.store, count: answer.holds.length }, [
|
|
3475
|
+
...storeStateHints(answer.store),
|
|
3476
|
+
...report.hints
|
|
3477
|
+
]);
|
|
3478
|
+
} catch (err) {
|
|
3479
|
+
fail(err);
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
});
|
|
3483
|
+
|
|
3484
|
+
// src/commands/shopify/signupOffers.ts
|
|
3485
|
+
import { defineCommand as defineCommand8 } from "citty";
|
|
3486
|
+
var description2 = "Start here for landing sign-ups: published and this chat's staged offers, codes given and store permissions. Example: baker shopify signup-offers. Forms name an offer handle; terms are staged with baker shopify change signup-offer set.";
|
|
3487
|
+
registerSchema({ command: "shopify.signupOffers", description: description2, args: {} });
|
|
3488
|
+
var signupOffersCommand = defineCommand8({
|
|
3489
|
+
meta: { name: "signup-offers", description: description2 },
|
|
3490
|
+
async run() {
|
|
3491
|
+
try {
|
|
3492
|
+
const data = shopifySignupOffersResponseSchema.parse(
|
|
3493
|
+
await apiGet("/api/shopify/signup-offers", params({ chatId: optionalChatId() }))
|
|
3494
|
+
);
|
|
3495
|
+
const hints = [];
|
|
3496
|
+
if (!data.canAddCustomers)
|
|
3497
|
+
hints.push(
|
|
3498
|
+
'Sign-ups cannot be added to the store yet: call request_connection with { platform: "shopify", reason: "Add sign-ups from your landing" }. Keep building the page; the Form works once allowed.'
|
|
3499
|
+
);
|
|
3500
|
+
if (!data.canGiveCodes)
|
|
3501
|
+
hints.push(
|
|
3502
|
+
"Codes need the store to allow discounts: use request_connection for Shopify and keep building the page."
|
|
3503
|
+
);
|
|
3504
|
+
if (data.offers.some((offer) => offer.status === "ended"))
|
|
3505
|
+
hints.push(
|
|
3506
|
+
"Forms naming ended offers still add customers but give no code: stage a new offer or remove offer from the Form."
|
|
3507
|
+
);
|
|
3508
|
+
if (data.offers.length === 0)
|
|
3509
|
+
hints.push(
|
|
3510
|
+
"No sign-up offer exists yet. Omit offer for sign-ups without a code, or stage one after the user approves its terms."
|
|
3511
|
+
);
|
|
3512
|
+
writeJson({ ok: true, hints, data });
|
|
3513
|
+
} catch (error) {
|
|
3514
|
+
fail(error);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
|
|
3519
|
+
// src/commands/shopify/tracking.ts
|
|
3520
|
+
import { defineCommand as defineCommand9 } from "citty";
|
|
3521
|
+
var audit2 = leaf(
|
|
3522
|
+
"shopify.tracking.audit",
|
|
3523
|
+
{
|
|
3524
|
+
name: "audit",
|
|
3525
|
+
description: "Start here for 'is our store's tracking set up right?': Baker's measurement on the store, Baker switched on in the theme, whether orders carry Baker's visitor, who reports sales to Meta and Google, shoppers' consent, and whether the store sells through its own website. Each check is ok, problem (with its fix: a change to stage, a task for the merchant, or a decision) or unknown (with why). Reads only \u2014 it changes nothing."
|
|
3526
|
+
},
|
|
3527
|
+
{ full: { type: "boolean", description: "Add the facts behind each check", required: false } },
|
|
3528
|
+
async (args2) => {
|
|
3529
|
+
try {
|
|
3530
|
+
const data = await apiPost("/api/shopify/tracking/audit", { full: args2.full === true });
|
|
3531
|
+
writeJson({ ok: true, hints: auditHints(data), data });
|
|
3532
|
+
} catch (err) {
|
|
3533
|
+
fail(err);
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
);
|
|
3537
|
+
var trackingCommand = defineCommand9({
|
|
3538
|
+
meta: { name: "tracking", description: "Check how the store is measured: baker shopify tracking audit" },
|
|
3539
|
+
subCommands: { audit: audit2 }
|
|
3540
|
+
});
|
|
3541
|
+
|
|
3542
|
+
// src/commands/shopify/index.ts
|
|
3543
|
+
registerSchema({
|
|
3544
|
+
command: "shopify.store",
|
|
3545
|
+
description: 'Start here for the connected Shopify store: its name, currency, home country, how many products and collections it has, how many products are out of stock or partly out, the prices by country it sells at (only when it has more than one), where its buy links send a shopper (`buyDestination`: checkout, store_cart or product_page), its languages and policies, whether it asks for a password, and the link to the Products page in Baker. `capped: true` means a count stopped at its ceiling (100 out of stock or partly out, 500 collections): say "100 or more", never the number as exact. `meta.store` says whether Baker\'s copy is complete and whether Baker can still reach the store. `canChange` says which kinds of store change the store allows Baker to stage (products, publishing, discounts, redirects, marketing); a `false` one is allowed by the user in Shopify (`request_connection`).',
|
|
3546
|
+
args: {
|
|
3547
|
+
full: {
|
|
3548
|
+
...fullArg,
|
|
3549
|
+
description: "Add the store's products page in Shopify admin, every country, each policy's text and the store's brand"
|
|
3550
|
+
}
|
|
3551
|
+
}
|
|
3552
|
+
});
|
|
3553
|
+
registerSchema({
|
|
3554
|
+
command: "shopify.products",
|
|
3555
|
+
description: "Start here for 'what do we sell' and 'at what price': the connected store's products with price, sale price, stock and where they can be bought. A product flagged `availability: not_for_sale` can't be bought on the online store (draft, archived or hidden from it) \u2014 never send ads or pages to it; `link_only` is buyable through its link but hidden from the store's search. Filter with --search, --stock or --collection; page with --cursor.",
|
|
3556
|
+
args: {
|
|
3557
|
+
search: { type: "string", description: "Words in the title, vendor, type or tags", required: false },
|
|
3558
|
+
collection: {
|
|
3559
|
+
type: "string",
|
|
3560
|
+
description: "Only one collection's products: its handle or id (`baker shopify collections`)",
|
|
3561
|
+
required: false
|
|
3562
|
+
},
|
|
3563
|
+
stock: {
|
|
3564
|
+
type: "string",
|
|
3565
|
+
description: "in_stock | partly_out (some options out) | out_of_stock (every option out)",
|
|
3566
|
+
required: false,
|
|
3567
|
+
enum: ["in_stock", "partly_out", "out_of_stock"]
|
|
3568
|
+
},
|
|
3569
|
+
limit: { type: "number", description: "Products per page, 1-100 (default 25)", required: false },
|
|
3570
|
+
cursor: {
|
|
3571
|
+
type: "string",
|
|
3572
|
+
description: "meta.next_cursor from the previous page, with the same filters",
|
|
3573
|
+
required: false
|
|
3574
|
+
},
|
|
3575
|
+
full: { ...fullArg, description: "Add vendor, type, option count, photo link, visibility and status" }
|
|
3576
|
+
}
|
|
3577
|
+
});
|
|
3578
|
+
registerSchema({
|
|
3579
|
+
command: "shopify.product",
|
|
3580
|
+
description: "One product of the connected store \u2014 accepts its id, its handle, a link to it on the store, in Baker or in Shopify admin. Returns its options with price, stock and `buyUrl` (each option's buy link for ads, emails and messages; on a landing use `<ShopifyProduct>` instead \u2014 never build a Shopify link yourself, never use `storeUrl` for it), prices by country, the options out of stock, its collections, its photos' library imageIds to place on a landing (`baker images download <imageIds> --out src/pages/<page>/_images/`) or pass to a creative, its videos' playbackIds (`<Video playbackId=\u2026/>`), and `contentSummary`: what else Baker keeps about it. Start here for a landing from a product: add `--content` for the formatted description, every custom field by its name (a size chart, care instructions), the rating, subscriptions and translations; `--field <name>` returns one field whole. `--out-of-stock` lists only the options out of stock.",
|
|
3581
|
+
args: {
|
|
3582
|
+
ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
3583
|
+
"variants-cursor": {
|
|
3584
|
+
type: "string",
|
|
3585
|
+
description: "data.variantsNextCursor from the previous call: the next page of options (same flags)",
|
|
3586
|
+
required: false
|
|
3587
|
+
},
|
|
3588
|
+
"out-of-stock": {
|
|
3589
|
+
type: "boolean",
|
|
3590
|
+
description: "Only the options that are out of stock, whatever their position",
|
|
3591
|
+
required: false,
|
|
3592
|
+
default: false
|
|
3593
|
+
},
|
|
3594
|
+
content: {
|
|
3595
|
+
type: "boolean",
|
|
3596
|
+
description: "Its whole details: formatted description, custom fields by name (long ones previewed), rating, subscriptions, YouTube/3D links, translations",
|
|
3597
|
+
required: false,
|
|
3598
|
+
default: false
|
|
3599
|
+
},
|
|
3600
|
+
field: {
|
|
3601
|
+
type: "string",
|
|
3602
|
+
description: 'One custom field whole, by its name or its key (e.g. "Size chart" or custom.size_chart)',
|
|
3603
|
+
required: false
|
|
3604
|
+
},
|
|
3605
|
+
variant: {
|
|
3606
|
+
type: "string",
|
|
3607
|
+
description: "With --field: the option whose field to return, by its variantId (options share their fields, e.g. --field custom.fit --variant 102)",
|
|
3608
|
+
required: false
|
|
3609
|
+
},
|
|
3610
|
+
quantity: {
|
|
3611
|
+
type: "number",
|
|
3612
|
+
description: "How many of the option each buy link puts in the cart, 1-99 (default 1)",
|
|
3613
|
+
required: false
|
|
3614
|
+
},
|
|
3615
|
+
full: {
|
|
3616
|
+
...fullArg,
|
|
3617
|
+
description: "Its details (as --content), whole description, SEO, category, admin link, 250 options per page with SKU, prices by country, every buy link and each option's weight, unit price, shipping and tax"
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
});
|
|
3621
|
+
registerSchema({
|
|
3622
|
+
command: "shopify.collections",
|
|
3623
|
+
description: "The connected store's collections, with the handle `--collection` takes, how many products Shopify counts in each, and `storeUrl`: the collection's page on the store, the link for an ad, an email or a message, and on a landing `<ShopifyStoreLink href=\"<storeUrl>\">\u2026</ShopifyStoreLink>` \u2014 use it as given, never build a /collections/ link yourself; no `storeUrl` means shoppers can't open that collection. `--full` adds each one's description, image and SEO.",
|
|
3624
|
+
args: {
|
|
3625
|
+
search: { type: "string", description: "Words in the collection's title or handle", required: false },
|
|
3626
|
+
full: { ...fullArg, description: "Add each collection's description, image and SEO" }
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
registerSchema({
|
|
3630
|
+
command: "shopify.stock",
|
|
3631
|
+
description: "Start here for 'what's out of stock': every product with every option out of stock, and every product with some options out, naming them. An option that keeps selling at zero or doesn't track stock is not out of stock \u2014 a buyer can still buy it.",
|
|
3632
|
+
args: {
|
|
3633
|
+
collection: { type: "string", description: "Only one collection: its handle or id", required: false },
|
|
3634
|
+
limit: { type: "number", description: "Products per list, 1-200 (default 50)", required: false },
|
|
3635
|
+
full: { ...fullArg, description: "Add each option's SKU and each product's Baker link" }
|
|
3636
|
+
}
|
|
3637
|
+
});
|
|
3638
|
+
registerSchema({
|
|
3639
|
+
command: "shopify.sales",
|
|
3640
|
+
description: "Start here for 'how much did we sell', 'which campaigns or pages brought sales' and 'which products sold most': the connected store's orders, sales, refunds and net, in the store's currency. --by day (default) and --by product count each sale, addition and refund in the period it happened; --by campaign and --by landing take the orders paid in the period and credit each to the campaign or Baker page that brought its buyer, with what was added to and refunded on it since \u2014 so the rows add up to `totals` exactly, with named rows for sales from before Baker measured the store and sales from no campaign. A sale counts when it is paid, at what was received. `salesReporting` says who reports the sales to Meta and Google: `undecided` and `shopify_channel` mean Baker only reports them, and only a manager can change that in Brain \u2192 Store. --by weekday|hour|month|month_of_year says when buyers order, in the store's own time, from the store's whole order history (orders placed in the period, with what was refunded on them since): use it for when to run or schedule campaigns; only these take --days up to 3650 or --all.",
|
|
3641
|
+
args: {
|
|
3642
|
+
days: {
|
|
3643
|
+
type: "number",
|
|
3644
|
+
description: "The last N days: 1-395 by day, product, campaign or landing; 1-3650 by weekday, hour or month (default 30)",
|
|
3645
|
+
required: false
|
|
3646
|
+
},
|
|
3647
|
+
all: {
|
|
3648
|
+
type: "boolean",
|
|
3649
|
+
description: "Every order the store has (by weekday, hour, month or month_of_year only)",
|
|
3650
|
+
required: false,
|
|
3651
|
+
default: false
|
|
3652
|
+
},
|
|
3653
|
+
by: {
|
|
3654
|
+
type: "string",
|
|
3655
|
+
description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
|
|
3656
|
+
required: false,
|
|
3657
|
+
enum: ["day", "product", "campaign", "landing", "weekday", "hour", "month", "month_of_year"]
|
|
3658
|
+
},
|
|
3659
|
+
limit: {
|
|
3660
|
+
type: "number",
|
|
3661
|
+
description: "Rows of a breakdown, 1-100 (by campaign or landing the rest are folded into one 'other' row)",
|
|
3662
|
+
required: false
|
|
3663
|
+
},
|
|
3664
|
+
full: {
|
|
3665
|
+
...fullArg,
|
|
3666
|
+
description: "Add each row's source, medium, campaign, click platform, landing id or product id"
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
});
|
|
3670
|
+
var salesCommand = defineCommand10({
|
|
3671
|
+
meta: {
|
|
3672
|
+
name: "sales",
|
|
3673
|
+
description: "What the store sold: orders, sales, refunds and net, by day, product, campaign or landing \u2014 or when buyers order (weekday, hour, month). Example: baker shopify sales --days 7 --by campaign"
|
|
3674
|
+
},
|
|
3675
|
+
args: {
|
|
3676
|
+
days: {
|
|
3677
|
+
type: "string",
|
|
3678
|
+
description: "The last N days (1-395; up to 3650 by weekday, hour or month)",
|
|
3679
|
+
required: false
|
|
3680
|
+
},
|
|
3681
|
+
all: {
|
|
3682
|
+
type: "boolean",
|
|
3683
|
+
description: "Every order (by weekday, hour or month only)",
|
|
3684
|
+
required: false,
|
|
3685
|
+
default: false
|
|
3686
|
+
},
|
|
3687
|
+
by: {
|
|
3688
|
+
type: "string",
|
|
3689
|
+
description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
|
|
3690
|
+
required: false
|
|
3691
|
+
},
|
|
3692
|
+
limit: { type: "string", description: "Rows of a breakdown, 1-100", required: false },
|
|
3693
|
+
full: { type: "boolean", description: "Add sources, campaigns and ids per row", required: false, default: false }
|
|
3694
|
+
},
|
|
3695
|
+
run: async ({ args: args2 }) => {
|
|
3696
|
+
try {
|
|
3697
|
+
const answer = await apiGet(
|
|
3698
|
+
"/api/shopify/sales",
|
|
3699
|
+
params({ days: args2.days, all: args2.all, by: args2.by, limit: args2.limit, full: args2.full })
|
|
3700
|
+
);
|
|
3701
|
+
const { store, salesReporting, history, ...data } = answer;
|
|
3702
|
+
emit(data, { store, salesReporting, history }, [...storeStateHints(store), ...salesHints(answer)]);
|
|
3703
|
+
} catch (err) {
|
|
3704
|
+
fail(err);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
});
|
|
3708
|
+
var storeCommand = defineCommand10({
|
|
3709
|
+
meta: {
|
|
3710
|
+
name: "store",
|
|
3711
|
+
description: "The connected Shopify store: currency, countries, how many products, what is out of stock. Example: baker shopify store"
|
|
3712
|
+
},
|
|
3713
|
+
args: {
|
|
3714
|
+
full: { type: "boolean", description: "Add the admin link and every country", required: false, default: false }
|
|
3715
|
+
},
|
|
3716
|
+
run: async ({ args: args2 }) => {
|
|
3717
|
+
try {
|
|
3718
|
+
const answer = await apiGet("/api/shopify/store", params({ full: args2.full }));
|
|
3719
|
+
const data = answer.canChange ? { ...answer.summary, canChange: answer.canChange } : answer.summary;
|
|
3720
|
+
emit(data, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
|
|
3721
|
+
} catch (err) {
|
|
3722
|
+
fail(err);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
});
|
|
3726
|
+
var productsCommand = defineCommand10({
|
|
3727
|
+
meta: {
|
|
3728
|
+
name: "products",
|
|
3729
|
+
description: 'What the store sells, with price and stock. Example: baker shopify products --search "snowboard" --stock out_of_stock'
|
|
3730
|
+
},
|
|
3731
|
+
args: {
|
|
3732
|
+
search: { type: "string", description: "Words in the title, vendor, type or tags", required: false },
|
|
3733
|
+
collection: { type: "string", description: "A collection's handle or id", required: false },
|
|
3734
|
+
stock: { type: "string", description: "in_stock | partly_out | out_of_stock", required: false },
|
|
3735
|
+
limit: { type: "string", description: "Products per page, 1-100 (default 25)", required: false },
|
|
3736
|
+
cursor: { type: "string", description: "meta.next_cursor from the previous page", required: false },
|
|
3737
|
+
full: {
|
|
3738
|
+
type: "boolean",
|
|
3739
|
+
description: "Add vendor, type, photo link, visibility, status",
|
|
3740
|
+
required: false,
|
|
3741
|
+
default: false
|
|
3742
|
+
}
|
|
3743
|
+
},
|
|
3744
|
+
run: async ({ args: args2 }) => {
|
|
3745
|
+
try {
|
|
3746
|
+
const answer = await apiGet(
|
|
3747
|
+
"/api/shopify/products",
|
|
3748
|
+
params({
|
|
3749
|
+
search: args2.search,
|
|
3750
|
+
collection: args2.collection,
|
|
3751
|
+
stock: args2.stock,
|
|
3752
|
+
limit: args2.limit,
|
|
3753
|
+
cursor: args2.cursor,
|
|
3754
|
+
full: args2.full
|
|
3755
|
+
})
|
|
3756
|
+
);
|
|
3757
|
+
const filtered = Boolean(args2.search || args2.collection || args2.stock);
|
|
3758
|
+
emit(
|
|
3759
|
+
answer.products,
|
|
3760
|
+
{
|
|
3761
|
+
store: answer.store,
|
|
3762
|
+
count: answer.products.length,
|
|
3763
|
+
...answer.nextCursor ? { next_cursor: answer.nextCursor } : {}
|
|
3764
|
+
},
|
|
3765
|
+
[...storeStateHints(answer.store), ...productsHints(answer, { filtered, paged: Boolean(args2.cursor) })]
|
|
3766
|
+
);
|
|
3767
|
+
} catch (err) {
|
|
3768
|
+
fail(err);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
});
|
|
3772
|
+
var productCommand = defineCommand10({
|
|
3773
|
+
meta: {
|
|
3774
|
+
name: "product",
|
|
3775
|
+
description: "One product: options with buy links, prices by country, stock, photo imageIds, video playbackIds. Accepts an id, handle or link. Example: baker shopify product the-complete-snowboard --content"
|
|
3776
|
+
},
|
|
3777
|
+
args: {
|
|
3778
|
+
ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
|
|
3779
|
+
"variants-cursor": {
|
|
3780
|
+
type: "string",
|
|
3781
|
+
description: "data.variantsNextCursor from the previous call",
|
|
3782
|
+
required: false
|
|
3783
|
+
},
|
|
3784
|
+
"out-of-stock": { type: "boolean", description: "Only the options out of stock", required: false, default: false },
|
|
3785
|
+
content: {
|
|
3786
|
+
type: "boolean",
|
|
3787
|
+
description: "Its whole details: formatted description, custom fields, subscriptions, translations",
|
|
3788
|
+
required: false,
|
|
3789
|
+
default: false
|
|
3790
|
+
},
|
|
3791
|
+
field: { type: "string", description: "One custom field whole, by its name or key", required: false },
|
|
3792
|
+
variant: {
|
|
3793
|
+
type: "string",
|
|
3794
|
+
description: "With --field: the option whose field it is, by its variantId",
|
|
3795
|
+
required: false
|
|
3796
|
+
},
|
|
3797
|
+
quantity: {
|
|
3798
|
+
type: "string",
|
|
3799
|
+
description: "How many each buy link puts in the cart, 1-99 (default 1)",
|
|
3800
|
+
required: false
|
|
3801
|
+
},
|
|
3802
|
+
full: { type: "boolean", description: "Everything Baker keeps about it", required: false, default: false }
|
|
3803
|
+
},
|
|
3804
|
+
run: async ({ args: args2 }) => {
|
|
3805
|
+
try {
|
|
3806
|
+
const positionals2 = Array.isArray(args2._) ? args2._ : [];
|
|
3807
|
+
if (positionals2.length > 1) {
|
|
3808
|
+
throw new ApiError(
|
|
3809
|
+
"VALIDATION_ERROR",
|
|
3810
|
+
"ref: Pass one product id, handle or product link \u2014 quote a link, and search by words with `baker shopify products --search`."
|
|
3811
|
+
);
|
|
3812
|
+
}
|
|
3813
|
+
const answer = await apiGet(
|
|
3814
|
+
"/api/shopify/product",
|
|
3815
|
+
params({
|
|
3816
|
+
ref: args2.ref,
|
|
3817
|
+
variantsCursor: args2["variants-cursor"],
|
|
3818
|
+
outOfStock: args2["out-of-stock"],
|
|
3819
|
+
content: args2.content,
|
|
3820
|
+
field: args2.field,
|
|
3821
|
+
variant: args2.variant,
|
|
3822
|
+
quantity: args2.quantity,
|
|
3823
|
+
full: args2.full
|
|
3824
|
+
})
|
|
3825
|
+
);
|
|
3826
|
+
emit(answer.product, { store: answer.store }, [
|
|
3827
|
+
...storeStateHints(answer.store),
|
|
3828
|
+
...productHints(answer.product, answer.store)
|
|
3829
|
+
]);
|
|
3830
|
+
} catch (err) {
|
|
3831
|
+
fail(err);
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
});
|
|
3835
|
+
var collectionsCommand = defineCommand10({
|
|
3836
|
+
meta: {
|
|
3837
|
+
name: "collections",
|
|
3838
|
+
description: "The store's collections, with the handles --collection takes and each one's page on the store (storeUrl). Example: baker shopify collections --full"
|
|
3839
|
+
},
|
|
3840
|
+
args: {
|
|
3841
|
+
search: { type: "string", description: "Words in the title or handle", required: false },
|
|
3842
|
+
full: { type: "boolean", description: "Add description, image and SEO", required: false, default: false }
|
|
3843
|
+
},
|
|
3844
|
+
run: async ({ args: args2 }) => {
|
|
3845
|
+
try {
|
|
3846
|
+
const answer = await apiGet(
|
|
3847
|
+
"/api/shopify/collections",
|
|
3848
|
+
params({ search: args2.search, full: args2.full })
|
|
3849
|
+
);
|
|
3850
|
+
emit(
|
|
3851
|
+
answer.collections,
|
|
3852
|
+
{ store: answer.store, count: answer.collections.length },
|
|
3853
|
+
storeStateHints(answer.store)
|
|
3854
|
+
);
|
|
3855
|
+
} catch (err) {
|
|
3856
|
+
fail(err);
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
});
|
|
3860
|
+
var stockCommand = defineCommand10({
|
|
3861
|
+
meta: {
|
|
3862
|
+
name: "stock",
|
|
3863
|
+
description: "Every product with options out of stock, naming them. Example: baker shopify stock"
|
|
3864
|
+
},
|
|
3865
|
+
args: {
|
|
3866
|
+
collection: { type: "string", description: "A collection's handle or id", required: false },
|
|
3867
|
+
limit: { type: "string", description: "Products per list, 1-200 (default 50)", required: false },
|
|
3868
|
+
full: { type: "boolean", description: "Add SKUs and Baker links", required: false, default: false }
|
|
3869
|
+
},
|
|
3870
|
+
run: async ({ args: args2 }) => {
|
|
3871
|
+
try {
|
|
3872
|
+
const answer = await apiGet(
|
|
3873
|
+
"/api/shopify/stock",
|
|
3874
|
+
params({ collection: args2.collection, limit: args2.limit, full: args2.full })
|
|
3875
|
+
);
|
|
3876
|
+
const { store, ...data } = answer;
|
|
3877
|
+
const limit2 = Number(args2.limit ?? 50);
|
|
3878
|
+
emit(data, { store }, [...storeStateHints(store), ...stockHints(answer, Number.isFinite(limit2) ? limit2 : 50)]);
|
|
3879
|
+
} catch (err) {
|
|
3880
|
+
fail(err);
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
});
|
|
3884
|
+
var shopifyCommand = defineCommand10({
|
|
3885
|
+
meta: {
|
|
3886
|
+
name: "shopify",
|
|
3887
|
+
description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections,
|
|
3888
|
+
product photos, what it sold, who buys, where and what, and what advertises each product \u2014 and stage
|
|
3889
|
+
changes to it (\`change\`), applied when the user publishes the chat and undone with \`undo\`.
|
|
3890
|
+
|
|
3891
|
+
Examples:
|
|
3892
|
+
baker shopify store # currency, countries, how many products, what is out of stock
|
|
3893
|
+
baker shopify products --search "snowboard" # what we sell, with price and stock
|
|
3894
|
+
baker shopify products --stock out_of_stock # --stock in_stock|partly_out|out_of_stock, --collection <handle>
|
|
3895
|
+
baker shopify product the-complete-snowboard # one product: options with buyUrl, prices, photos, videos
|
|
3896
|
+
baker shopify product <handle> --content # its whole details: description, custom fields, subscriptions
|
|
3897
|
+
baker shopify product <handle> --field "Size chart" # one custom field, whole
|
|
3898
|
+
baker shopify product <handle> --field custom.fit --variant <variantId> # one option's field, whole
|
|
3899
|
+
baker shopify product <handle> --quantity 2 # buy links that put 2 in the cart
|
|
3900
|
+
baker shopify product <id> --out-of-stock # only its options that are out of stock
|
|
3901
|
+
baker shopify stock # every product with options out of stock
|
|
3902
|
+
baker shopify collections # collections, with handles for --collection
|
|
3903
|
+
baker shopify sales --days 7 --by campaign # what sold and which campaigns brought it (--by day|product|landing)
|
|
3904
|
+
baker shopify sales --all --by weekday # when buyers order, in the store's time (--by hour|month|month_of_year)
|
|
3905
|
+
baker shopify countries --days 90 # where buyers are, with growth (--country DE for its regions)
|
|
3906
|
+
baker shopify best-sellers --days 90 # what sells (--view variants|trend|together|repeat|momentum)
|
|
3907
|
+
baker shopify buyers --days 90 --by channel # who buys, repeat rate, segments, value by acquisition channel
|
|
3908
|
+
baker shopify buyer 1042 # one numbered customer's orders
|
|
3909
|
+
baker shopify segments # best, loyal, at risk, new, one-time \u2014 and who to find more of
|
|
3910
|
+
baker shopify cohorts --months 12 # buyers by first-order month: who came back
|
|
3911
|
+
baker shopify promotions <product> # what advertises it: ads, landings, Shopping (read before pausing)
|
|
3912
|
+
baker shopify stock-holds --product <product> # what Baker paused because it ran out: what to resume, what to tell the user
|
|
3913
|
+
baker shopify change product update <product> --title "\u2026" # stage a store change (product|variant|field|collection|discount|redirect|campaign|measurement)
|
|
3914
|
+
baker shopify draft list # the store changes staged on this chat
|
|
3915
|
+
baker shopify changes # what Baker changed in the store; baker shopify undo <changeId> puts one back
|
|
3916
|
+
baker shopify tracking audit # is the store measured right? (reads only)
|
|
3917
|
+
|
|
3918
|
+
Product photos are already in the image library: place them by imageId
|
|
3919
|
+
(baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
|
|
3920
|
+
Product videos are in the video library: place them with <Video playbackId="\u2026" />.
|
|
3921
|
+
On a landing, the buy button is <ShopifyProduct product="\u2026"/> \u2014 never a buyUrl or a Shopify link
|
|
3922
|
+
written by hand. buyUrl is for ads, emails and messages; never use storeUrl (a page to look at) for it.
|
|
3923
|
+
A landing links to a page of the store (a collection, another product, the home) with
|
|
3924
|
+
<ShopifyStoreLink href="<storeUrl or storefrontUrl as given>">\u2026</ShopifyStoreLink>.
|
|
3925
|
+
NO_SHOPIFY_CONNECTION means no store is connected: follow error.fix. SHOPIFY_NOT_AVAILABLE means it cannot be
|
|
3926
|
+
connected here yet: carry on without it. SHOPIFY_ORDERS_IMPORTING means it IS connected and its orders are still
|
|
3927
|
+
coming in: try buyers/segments/audiences again in a few minutes.
|
|
3928
|
+
Full guide: __tooling__/docs/tools/baker/shopify.md`
|
|
3929
|
+
},
|
|
3930
|
+
subCommands: {
|
|
3931
|
+
"signup-offers": signupOffersCommand,
|
|
3932
|
+
store: storeCommand,
|
|
3933
|
+
products: productsCommand,
|
|
3934
|
+
product: productCommand,
|
|
3935
|
+
collections: collectionsCommand,
|
|
3936
|
+
stock: stockCommand,
|
|
3937
|
+
sales: salesCommand,
|
|
3938
|
+
buyers: buyersCommand,
|
|
3939
|
+
buyer: buyerCommand,
|
|
3940
|
+
countries: countriesCommand,
|
|
3941
|
+
"best-sellers": bestSellersCommand,
|
|
3942
|
+
cohorts: cohortsCommand,
|
|
3943
|
+
segments: segmentsCommand,
|
|
3944
|
+
audiences: audiencesCommand,
|
|
3945
|
+
promotions: promotionsCommand,
|
|
3946
|
+
"stock-holds": stockHoldsCommand,
|
|
3947
|
+
change: changeCommand,
|
|
3948
|
+
draft: draftCommand,
|
|
3949
|
+
changes: changesCommand,
|
|
3950
|
+
undo: undoCommand,
|
|
3951
|
+
tracking: trackingCommand,
|
|
3952
|
+
catalog: catalogCommand
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3955
|
+
export {
|
|
3956
|
+
shopifyCommand
|
|
3957
|
+
};
|
|
3958
|
+
//# sourceMappingURL=shopify-LXSR37XI.js.map
|