@koda-sl/baker-cli 0.383.0 → 0.384.0-dev.557795bef

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.
Files changed (964) hide show
  1. package/README.md +1102 -218
  2. package/canvas/tiktok-captions-composition/index.html +5 -6
  3. package/canvas/tiktok-captions-composition/meta.json +5 -0
  4. package/dist/account-4UEZAY5T.js +49 -0
  5. package/dist/account-4UEZAY5T.js.map +1 -0
  6. package/dist/account-OINZG6UP.js +80 -0
  7. package/dist/account-OINZG6UP.js.map +1 -0
  8. package/dist/account-QMDCUUSG.js +79 -0
  9. package/dist/account-QMDCUUSG.js.map +1 -0
  10. package/dist/account-reads-ZKKDYHK5.js +218 -0
  11. package/dist/account-reads-ZKKDYHK5.js.map +1 -0
  12. package/dist/account-writes-N4JDIDRB.js +66 -0
  13. package/dist/account-writes-N4JDIDRB.js.map +1 -0
  14. package/dist/accounts-AEBQFCNH.js +114 -0
  15. package/dist/accounts-AEBQFCNH.js.map +1 -0
  16. package/dist/accounts-EBGJ2YO7.js +611 -0
  17. package/dist/accounts-EBGJ2YO7.js.map +1 -0
  18. package/dist/accounts-HP4BLKK5.js +75 -0
  19. package/dist/accounts-HP4BLKK5.js.map +1 -0
  20. package/dist/accounts-YE2SQ3UQ.js +77 -0
  21. package/dist/accounts-YE2SQ3UQ.js.map +1 -0
  22. package/dist/accounts-Z4CLGQXJ.js +115 -0
  23. package/dist/accounts-Z4CLGQXJ.js.map +1 -0
  24. package/dist/actions-ADRIEQZX.js +55 -0
  25. package/dist/actions-ADRIEQZX.js.map +1 -0
  26. package/dist/active-entities-YB46ADQN.js +103 -0
  27. package/dist/active-entities-YB46ADQN.js.map +1 -0
  28. package/dist/activities-DUAIQJLN.js +74 -0
  29. package/dist/activities-DUAIQJLN.js.map +1 -0
  30. package/dist/ad-group-writes-HL3EZ3BT.js +170 -0
  31. package/dist/ad-group-writes-HL3EZ3BT.js.map +1 -0
  32. package/dist/ad-groups-FGS5JJGN.js +68 -0
  33. package/dist/ad-groups-FGS5JJGN.js.map +1 -0
  34. package/dist/ad-writes-LX4ACUHB.js +174 -0
  35. package/dist/ad-writes-LX4ACUHB.js.map +1 -0
  36. package/dist/add-5CUXNOUM.js +97 -0
  37. package/dist/add-5CUXNOUM.js.map +1 -0
  38. package/dist/add-6OXOQNET.js +302 -0
  39. package/dist/add-6OXOQNET.js.map +1 -0
  40. package/dist/add-E66HBIZV.js +105 -0
  41. package/dist/add-E66HBIZV.js.map +1 -0
  42. package/dist/add-ROK3ETQC.js +104 -0
  43. package/dist/add-ROK3ETQC.js.map +1 -0
  44. package/dist/add-accounts-YME5SCXD.js +91 -0
  45. package/dist/add-accounts-YME5SCXD.js.map +1 -0
  46. package/dist/ads-65R3WMDU.js +128 -0
  47. package/dist/ads-65R3WMDU.js.map +1 -0
  48. package/dist/ads-O63J7BDF.js +113 -0
  49. package/dist/ads-O63J7BDF.js.map +1 -0
  50. package/dist/ads-TFZ5PXKS.js +77 -0
  51. package/dist/ads-TFZ5PXKS.js.map +1 -0
  52. package/dist/ads-XSVRH45E.js +40 -0
  53. package/dist/ads-XSVRH45E.js.map +1 -0
  54. package/dist/adsets-Y62JOOTV.js +176 -0
  55. package/dist/adsets-Y62JOOTV.js.map +1 -0
  56. package/dist/advertisers-AIC4QPEK.js +120 -0
  57. package/dist/advertisers-AIC4QPEK.js.map +1 -0
  58. package/dist/advertisers-LJ2HKJ4W.js +125 -0
  59. package/dist/advertisers-LJ2HKJ4W.js.map +1 -0
  60. package/dist/analytics-N4PGXXEQ.js +1976 -0
  61. package/dist/analytics-N4PGXXEQ.js.map +1 -0
  62. package/dist/analytics-X3DR3UVG.js +433 -0
  63. package/dist/analytics-X3DR3UVG.js.map +1 -0
  64. package/dist/animate-E3SKQ6SI.js +480 -0
  65. package/dist/animate-E3SKQ6SI.js.map +1 -0
  66. package/dist/assign-Y7HGSQVJ.js +77 -0
  67. package/dist/assign-Y7HGSQVJ.js.map +1 -0
  68. package/dist/audience-size-SWX5YJQF.js +94 -0
  69. package/dist/audience-size-SWX5YJQF.js.map +1 -0
  70. package/dist/audiences-5A6BCWSA.js +268 -0
  71. package/dist/audiences-5A6BCWSA.js.map +1 -0
  72. package/dist/audiences-6QOCASVV.js +78 -0
  73. package/dist/audiences-6QOCASVV.js.map +1 -0
  74. package/dist/audiences-O67NGIP5.js +85 -0
  75. package/dist/audiences-O67NGIP5.js.map +1 -0
  76. package/dist/audit-2KVLWSAI.js +151 -0
  77. package/dist/audit-2KVLWSAI.js.map +1 -0
  78. package/dist/audit-HWK24VXI.js +88 -0
  79. package/dist/audit-HWK24VXI.js.map +1 -0
  80. package/dist/autocomplete-JJSGC5WU.js +111 -0
  81. package/dist/autocomplete-JJSGC5WU.js.map +1 -0
  82. package/dist/avatars-XIEKQ77J.js +38 -0
  83. package/dist/avatars-XIEKQ77J.js.map +1 -0
  84. package/dist/bid-pricing-D2HGRWZL.js +99 -0
  85. package/dist/bid-pricing-D2HGRWZL.js.map +1 -0
  86. package/dist/brand-AYYWRGIE.js +22 -0
  87. package/dist/brand-AYYWRGIE.js.map +1 -0
  88. package/dist/brief-3HUIDK5F.js +490 -0
  89. package/dist/brief-3HUIDK5F.js.map +1 -0
  90. package/dist/businesses-65EWTK5E.js +53 -0
  91. package/dist/businesses-65EWTK5E.js.map +1 -0
  92. package/dist/campaign-groups-6HCYR2JX.js +95 -0
  93. package/dist/campaign-groups-6HCYR2JX.js.map +1 -0
  94. package/dist/campaign-writes-M6JLF5AC.js +170 -0
  95. package/dist/campaign-writes-M6JLF5AC.js.map +1 -0
  96. package/dist/campaign-writes-Y6QPRQ4E.js +165 -0
  97. package/dist/campaign-writes-Y6QPRQ4E.js.map +1 -0
  98. package/dist/campaigns-6SM2ZSIH.js +76 -0
  99. package/dist/campaigns-6SM2ZSIH.js.map +1 -0
  100. package/dist/campaigns-7RKJZRY2.js +107 -0
  101. package/dist/campaigns-7RKJZRY2.js.map +1 -0
  102. package/dist/campaigns-KRI25JDZ.js +109 -0
  103. package/dist/campaigns-KRI25JDZ.js.map +1 -0
  104. package/dist/campaigns-TD7ZB5DG.js +110 -0
  105. package/dist/campaigns-TD7ZB5DG.js.map +1 -0
  106. package/dist/canvas-DMYY2DWI.js +46 -0
  107. package/dist/canvas-DMYY2DWI.js.map +1 -0
  108. package/dist/capabilities-3WJTXS2O.js +144 -0
  109. package/dist/capabilities-3WJTXS2O.js.map +1 -0
  110. package/dist/cards-7HRC4UQQ.js +85 -0
  111. package/dist/cards-7HRC4UQQ.js.map +1 -0
  112. package/dist/catalog-N6MDMIVL.js +64 -0
  113. package/dist/catalog-N6MDMIVL.js.map +1 -0
  114. package/dist/catalogs-PB46L7FH.js +128 -0
  115. package/dist/catalogs-PB46L7FH.js.map +1 -0
  116. package/dist/changes-SASUQK62.js +135 -0
  117. package/dist/changes-SASUQK62.js.map +1 -0
  118. package/dist/chatgpt-JKBSAG25.js +47 -0
  119. package/dist/chatgpt-JKBSAG25.js.map +1 -0
  120. package/dist/chats-WRVSQQB2.js +262 -0
  121. package/dist/chats-WRVSQQB2.js.map +1 -0
  122. package/dist/chunk-2FGKJY63.js +455 -0
  123. package/dist/chunk-2FGKJY63.js.map +1 -0
  124. package/dist/chunk-2GVXNKAE.js +42 -0
  125. package/dist/chunk-2GVXNKAE.js.map +1 -0
  126. package/dist/chunk-2J3PTIQB.js +479 -0
  127. package/dist/chunk-2J3PTIQB.js.map +1 -0
  128. package/dist/chunk-2J7XC2NO.js +346 -0
  129. package/dist/chunk-2J7XC2NO.js.map +1 -0
  130. package/dist/{chunk-VLUGGVJL.js → chunk-2UFRHW3T.js} +12 -4
  131. package/dist/chunk-2UFRHW3T.js.map +1 -0
  132. package/dist/chunk-2Y5V23PU.js +29 -0
  133. package/dist/chunk-2Y5V23PU.js.map +1 -0
  134. package/dist/chunk-3ACMDRSE.js +39 -0
  135. package/dist/chunk-3ACMDRSE.js.map +1 -0
  136. package/dist/chunk-3EWF7VII.js +1079 -0
  137. package/dist/chunk-3EWF7VII.js.map +1 -0
  138. package/dist/chunk-3LYMIKKN.js +625 -0
  139. package/dist/chunk-3LYMIKKN.js.map +1 -0
  140. package/dist/chunk-3M6RNCDD.js +26 -0
  141. package/dist/chunk-3M6RNCDD.js.map +1 -0
  142. package/dist/chunk-3MDO4RR2.js +310 -0
  143. package/dist/chunk-3MDO4RR2.js.map +1 -0
  144. package/dist/chunk-3NPOIHSX.js +6667 -0
  145. package/dist/chunk-3NPOIHSX.js.map +1 -0
  146. package/dist/chunk-3SK7TWGP.js +103 -0
  147. package/dist/chunk-3SK7TWGP.js.map +1 -0
  148. package/dist/chunk-3VXQYDJN.js +1743 -0
  149. package/dist/chunk-3VXQYDJN.js.map +1 -0
  150. package/dist/chunk-44VXKO65.js +106 -0
  151. package/dist/chunk-44VXKO65.js.map +1 -0
  152. package/dist/chunk-4NZ47VXO.js +137 -0
  153. package/dist/chunk-4NZ47VXO.js.map +1 -0
  154. package/dist/chunk-4X3UR372.js +225 -0
  155. package/dist/chunk-4X3UR372.js.map +1 -0
  156. package/dist/chunk-553XSIFW.js +304 -0
  157. package/dist/chunk-553XSIFW.js.map +1 -0
  158. package/dist/chunk-5FEBUCJU.js +25 -0
  159. package/dist/chunk-5FEBUCJU.js.map +1 -0
  160. package/dist/chunk-5HYSKH77.js +315 -0
  161. package/dist/chunk-5HYSKH77.js.map +1 -0
  162. package/dist/chunk-5MBPIONY.js +29 -0
  163. package/dist/chunk-5MBPIONY.js.map +1 -0
  164. package/dist/chunk-5UCP4EEQ.js +111 -0
  165. package/dist/chunk-5UCP4EEQ.js.map +1 -0
  166. package/dist/chunk-5VPJNNJL.js +494 -0
  167. package/dist/chunk-5VPJNNJL.js.map +1 -0
  168. package/dist/{chunk-VZEDAFAR.js → chunk-6YYSUISH.js} +47 -8
  169. package/dist/chunk-6YYSUISH.js.map +1 -0
  170. package/dist/chunk-7OHWEF3I.js +98 -0
  171. package/dist/chunk-7OHWEF3I.js.map +1 -0
  172. package/dist/chunk-A636XRD7.js +159 -0
  173. package/dist/chunk-A636XRD7.js.map +1 -0
  174. package/dist/chunk-A76FRBSF.js +297 -0
  175. package/dist/chunk-A76FRBSF.js.map +1 -0
  176. package/dist/chunk-AADVCEL4.js +817 -0
  177. package/dist/chunk-AADVCEL4.js.map +1 -0
  178. package/dist/chunk-AG62YO7C.js +96 -0
  179. package/dist/chunk-AG62YO7C.js.map +1 -0
  180. package/dist/chunk-AIRIT4GF.js +75 -0
  181. package/dist/chunk-AIRIT4GF.js.map +1 -0
  182. package/dist/chunk-AMCY5YCB.js +206 -0
  183. package/dist/chunk-AMCY5YCB.js.map +1 -0
  184. package/dist/chunk-AP3TP6VG.js +139 -0
  185. package/dist/chunk-AP3TP6VG.js.map +1 -0
  186. package/dist/chunk-APP475IQ.js +474 -0
  187. package/dist/chunk-APP475IQ.js.map +1 -0
  188. package/dist/chunk-AR7PU7BB.js +76 -0
  189. package/dist/chunk-AR7PU7BB.js.map +1 -0
  190. package/dist/chunk-B4S72KSS.js +172 -0
  191. package/dist/chunk-B4S72KSS.js.map +1 -0
  192. package/dist/chunk-BIPIL4QA.js +486 -0
  193. package/dist/chunk-BIPIL4QA.js.map +1 -0
  194. package/dist/chunk-BPUSL5Y2.js +58 -0
  195. package/dist/chunk-BPUSL5Y2.js.map +1 -0
  196. package/dist/chunk-BR32GHYD.js +65 -0
  197. package/dist/chunk-BR32GHYD.js.map +1 -0
  198. package/dist/chunk-CAMX6OHF.js +3182 -0
  199. package/dist/chunk-CAMX6OHF.js.map +1 -0
  200. package/dist/{chunk-MF7YA62W.js → chunk-CUL3VB4L.js} +10 -5
  201. package/dist/chunk-CUL3VB4L.js.map +1 -0
  202. package/dist/chunk-CVEY353K.js +66 -0
  203. package/dist/chunk-CVEY353K.js.map +1 -0
  204. package/dist/chunk-CWXADUKL.js +1599 -0
  205. package/dist/chunk-CWXADUKL.js.map +1 -0
  206. package/dist/chunk-D5FY4FAK.js +24 -0
  207. package/dist/chunk-D5FY4FAK.js.map +1 -0
  208. package/dist/chunk-DGCUPJZD.js +39 -0
  209. package/dist/chunk-DGCUPJZD.js.map +1 -0
  210. package/dist/chunk-DNCLWSHO.js +41 -0
  211. package/dist/chunk-DNCLWSHO.js.map +1 -0
  212. package/dist/chunk-DUPURL26.js +97 -0
  213. package/dist/chunk-DUPURL26.js.map +1 -0
  214. package/dist/chunk-E2ZOSNF5.js +96 -0
  215. package/dist/chunk-E2ZOSNF5.js.map +1 -0
  216. package/dist/chunk-E3HYHT7C.js +274 -0
  217. package/dist/chunk-E3HYHT7C.js.map +1 -0
  218. package/dist/chunk-EDLZGTGN.js +216 -0
  219. package/dist/chunk-EDLZGTGN.js.map +1 -0
  220. package/dist/chunk-EI5QN4UO.js +363 -0
  221. package/dist/chunk-EI5QN4UO.js.map +1 -0
  222. package/dist/{chunk-XHC4LMK4.js → chunk-EIGXX6KI.js} +16 -8
  223. package/dist/chunk-EIGXX6KI.js.map +1 -0
  224. package/dist/chunk-EPDE7ZRU.js +174 -0
  225. package/dist/chunk-EPDE7ZRU.js.map +1 -0
  226. package/dist/chunk-ERSX3MKH.js +34 -0
  227. package/dist/chunk-ERSX3MKH.js.map +1 -0
  228. package/dist/chunk-EVDRF4CI.js +509 -0
  229. package/dist/chunk-EVDRF4CI.js.map +1 -0
  230. package/dist/chunk-F4IEILTR.js +382 -0
  231. package/dist/chunk-F4IEILTR.js.map +1 -0
  232. package/dist/chunk-F7HRLPR5.js +67 -0
  233. package/dist/chunk-F7HRLPR5.js.map +1 -0
  234. package/dist/chunk-FEG6MN4H.js +84 -0
  235. package/dist/chunk-FEG6MN4H.js.map +1 -0
  236. package/dist/chunk-FKOECEVC.js +126 -0
  237. package/dist/chunk-FKOECEVC.js.map +1 -0
  238. package/dist/chunk-FMGW2NZI.js +106 -0
  239. package/dist/chunk-FMGW2NZI.js.map +1 -0
  240. package/dist/chunk-FPZKKNDK.js +98 -0
  241. package/dist/chunk-FPZKKNDK.js.map +1 -0
  242. package/dist/chunk-FUVCY63N.js +169 -0
  243. package/dist/chunk-FUVCY63N.js.map +1 -0
  244. package/dist/chunk-GBNJQNDS.js +19 -0
  245. package/dist/chunk-GBNJQNDS.js.map +1 -0
  246. package/dist/chunk-GIWHHCD6.js +145 -0
  247. package/dist/chunk-GIWHHCD6.js.map +1 -0
  248. package/dist/chunk-GOJXKIFQ.js +1605 -0
  249. package/dist/chunk-GOJXKIFQ.js.map +1 -0
  250. package/dist/chunk-GRQ2AXDX.js +9 -0
  251. package/dist/chunk-GRQ2AXDX.js.map +1 -0
  252. package/dist/chunk-GXDLWBOY.js +73 -0
  253. package/dist/chunk-GXDLWBOY.js.map +1 -0
  254. package/dist/chunk-HAE4WWFQ.js +455 -0
  255. package/dist/chunk-HAE4WWFQ.js.map +1 -0
  256. package/dist/chunk-I2DNBEV6.js +4227 -0
  257. package/dist/chunk-I2DNBEV6.js.map +1 -0
  258. package/dist/chunk-I4FTS5EE.js +455 -0
  259. package/dist/chunk-I4FTS5EE.js.map +1 -0
  260. package/dist/chunk-IELPT4K2.js +74 -0
  261. package/dist/chunk-IELPT4K2.js.map +1 -0
  262. package/dist/chunk-IJMWEO4P.js +57 -0
  263. package/dist/chunk-IJMWEO4P.js.map +1 -0
  264. package/dist/chunk-IREMSVKW.js +175 -0
  265. package/dist/chunk-IREMSVKW.js.map +1 -0
  266. package/dist/chunk-IVIJJXXT.js +62 -0
  267. package/dist/chunk-IVIJJXXT.js.map +1 -0
  268. package/dist/chunk-JD3W3ON7.js +188 -0
  269. package/dist/chunk-JD3W3ON7.js.map +1 -0
  270. package/dist/chunk-JLWBK3VC.js +5187 -0
  271. package/dist/chunk-JLWBK3VC.js.map +1 -0
  272. package/dist/chunk-JPK34QPS.js +134 -0
  273. package/dist/chunk-JPK34QPS.js.map +1 -0
  274. package/dist/chunk-JUY47NZ2.js +183 -0
  275. package/dist/chunk-JUY47NZ2.js.map +1 -0
  276. package/dist/chunk-JXMZEOHV.js +333 -0
  277. package/dist/chunk-JXMZEOHV.js.map +1 -0
  278. package/dist/chunk-K37K447U.js +51 -0
  279. package/dist/chunk-K37K447U.js.map +1 -0
  280. package/dist/chunk-K6TDB2KV.js +1442 -0
  281. package/dist/chunk-K6TDB2KV.js.map +1 -0
  282. package/dist/chunk-KIGGMKAE.js +66 -0
  283. package/dist/chunk-KIGGMKAE.js.map +1 -0
  284. package/dist/chunk-KKJ4KJCW.js +29 -0
  285. package/dist/chunk-KKJ4KJCW.js.map +1 -0
  286. package/dist/chunk-KONA5PSR.js +55 -0
  287. package/dist/chunk-KONA5PSR.js.map +1 -0
  288. package/dist/chunk-KVHRW7KO.js +60 -0
  289. package/dist/chunk-KVHRW7KO.js.map +1 -0
  290. package/dist/chunk-LA6PPSNA.js +65 -0
  291. package/dist/chunk-LA6PPSNA.js.map +1 -0
  292. package/dist/chunk-LKPJADL2.js +74 -0
  293. package/dist/chunk-LKPJADL2.js.map +1 -0
  294. package/dist/chunk-LWAEHLKV.js +74 -0
  295. package/dist/chunk-LWAEHLKV.js.map +1 -0
  296. package/dist/chunk-M6UNISGO.js +66 -0
  297. package/dist/chunk-M6UNISGO.js.map +1 -0
  298. package/dist/chunk-MIJAGBEP.js +23992 -0
  299. package/dist/chunk-MIJAGBEP.js.map +1 -0
  300. package/dist/chunk-MP4EXZ45.js +3151 -0
  301. package/dist/chunk-MP4EXZ45.js.map +1 -0
  302. package/dist/chunk-MYTDK3AL.js +2102 -0
  303. package/dist/chunk-MYTDK3AL.js.map +1 -0
  304. package/dist/chunk-MZQ5DX6S.js +125 -0
  305. package/dist/chunk-MZQ5DX6S.js.map +1 -0
  306. package/dist/chunk-NFSLRRIE.js +77 -0
  307. package/dist/chunk-NFSLRRIE.js.map +1 -0
  308. package/dist/chunk-NOK7MSVJ.js +533 -0
  309. package/dist/chunk-NOK7MSVJ.js.map +1 -0
  310. package/dist/chunk-NR5RZDAW.js +592 -0
  311. package/dist/chunk-NR5RZDAW.js.map +1 -0
  312. package/dist/chunk-O2EVJALZ.js +133 -0
  313. package/dist/chunk-O2EVJALZ.js.map +1 -0
  314. package/dist/chunk-O6UM5WWH.js +275 -0
  315. package/dist/chunk-O6UM5WWH.js.map +1 -0
  316. package/dist/chunk-O6XZIQXZ.js +6212 -0
  317. package/dist/chunk-O6XZIQXZ.js.map +1 -0
  318. package/dist/chunk-OIL6PU5W.js +35 -0
  319. package/dist/chunk-OIL6PU5W.js.map +1 -0
  320. package/dist/chunk-P4NSJS54.js +20 -0
  321. package/dist/chunk-P4NSJS54.js.map +1 -0
  322. package/dist/chunk-P5T7OZKV.js +26 -0
  323. package/dist/chunk-P5T7OZKV.js.map +1 -0
  324. package/dist/chunk-PFBQERWX.js +10 -0
  325. package/dist/chunk-PFBQERWX.js.map +1 -0
  326. package/dist/chunk-PSWVSLJU.js +56 -0
  327. package/dist/chunk-PSWVSLJU.js.map +1 -0
  328. package/dist/chunk-PY2NJ6XZ.js +191 -0
  329. package/dist/chunk-PY2NJ6XZ.js.map +1 -0
  330. package/dist/chunk-PZ4OM2LK.js +45 -0
  331. package/dist/chunk-PZ4OM2LK.js.map +1 -0
  332. package/dist/chunk-PZNOJ76W.js +541 -0
  333. package/dist/chunk-PZNOJ76W.js.map +1 -0
  334. package/dist/chunk-Q6ULQMWG.js +41 -0
  335. package/dist/chunk-Q6ULQMWG.js.map +1 -0
  336. package/dist/chunk-QKFD6PPX.js +57 -0
  337. package/dist/chunk-QKFD6PPX.js.map +1 -0
  338. package/dist/chunk-QMLGJ5OW.js +2891 -0
  339. package/dist/chunk-QMLGJ5OW.js.map +1 -0
  340. package/dist/chunk-R4TPDVBD.js +207 -0
  341. package/dist/chunk-R4TPDVBD.js.map +1 -0
  342. package/dist/chunk-R7QV5N2S.js +846 -0
  343. package/dist/chunk-R7QV5N2S.js.map +1 -0
  344. package/dist/chunk-RR3KT2E7.js +665 -0
  345. package/dist/chunk-RR3KT2E7.js.map +1 -0
  346. package/dist/chunk-SEPPFM5S.js +228 -0
  347. package/dist/chunk-SEPPFM5S.js.map +1 -0
  348. package/dist/chunk-SQ7DRYCC.js +150 -0
  349. package/dist/chunk-SQ7DRYCC.js.map +1 -0
  350. package/dist/chunk-SYULM665.js +610 -0
  351. package/dist/chunk-SYULM665.js.map +1 -0
  352. package/dist/chunk-TKT3OHZ6.js +170 -0
  353. package/dist/chunk-TKT3OHZ6.js.map +1 -0
  354. package/dist/chunk-TNOSX3PJ.js +136 -0
  355. package/dist/chunk-TNOSX3PJ.js.map +1 -0
  356. package/dist/chunk-TTVKI54A.js +160 -0
  357. package/dist/chunk-TTVKI54A.js.map +1 -0
  358. package/dist/chunk-U7ZVXFS3.js +23 -0
  359. package/dist/chunk-U7ZVXFS3.js.map +1 -0
  360. package/dist/chunk-UI6BKYQD.js +225 -0
  361. package/dist/chunk-UI6BKYQD.js.map +1 -0
  362. package/dist/chunk-UKLDW4UW.js +46 -0
  363. package/dist/chunk-UKLDW4UW.js.map +1 -0
  364. package/dist/chunk-V6XXXEXZ.js +76 -0
  365. package/dist/chunk-V6XXXEXZ.js.map +1 -0
  366. package/dist/chunk-V7BW2CIQ.js +92 -0
  367. package/dist/chunk-V7BW2CIQ.js.map +1 -0
  368. package/dist/chunk-VSU5R34H.js +76 -0
  369. package/dist/chunk-VSU5R34H.js.map +1 -0
  370. package/dist/chunk-W4SA4CMR.js +296 -0
  371. package/dist/chunk-W4SA4CMR.js.map +1 -0
  372. package/dist/chunk-WFUMNDCE.js +111 -0
  373. package/dist/chunk-WFUMNDCE.js.map +1 -0
  374. package/dist/chunk-WHWNXLTD.js +166 -0
  375. package/dist/chunk-WHWNXLTD.js.map +1 -0
  376. package/dist/chunk-WN4KX7GE.js +25 -0
  377. package/dist/chunk-WN4KX7GE.js.map +1 -0
  378. package/dist/chunk-XIY7P54H.js +84 -0
  379. package/dist/chunk-XIY7P54H.js.map +1 -0
  380. package/dist/chunk-YFPLNMPZ.js +337 -0
  381. package/dist/chunk-YFPLNMPZ.js.map +1 -0
  382. package/dist/chunk-YMBB4JBD.js +1830 -0
  383. package/dist/chunk-YMBB4JBD.js.map +1 -0
  384. package/dist/chunk-YQFM2P5W.js +197 -0
  385. package/dist/chunk-YQFM2P5W.js.map +1 -0
  386. package/dist/chunk-YQSBHWOI.js +41 -0
  387. package/dist/chunk-YTF3IVMF.js +149 -0
  388. package/dist/chunk-YTF3IVMF.js.map +1 -0
  389. package/dist/chunk-Z3ENMCFE.js +94 -0
  390. package/dist/chunk-Z3ENMCFE.js.map +1 -0
  391. package/dist/chunk-Z7MWSXLG.js +94 -0
  392. package/dist/chunk-Z7MWSXLG.js.map +1 -0
  393. package/dist/chunk-ZIXHS6AM.js +42 -0
  394. package/dist/chunk-ZIXHS6AM.js.map +1 -0
  395. package/dist/chunk-ZJ7TQUCU.js +4410 -0
  396. package/dist/chunk-ZJ7TQUCU.js.map +1 -0
  397. package/dist/chunk-ZNQRDLSS.js +52 -0
  398. package/dist/chunk-ZNQRDLSS.js.map +1 -0
  399. package/dist/claim-ROWH6JVB.js +197 -0
  400. package/dist/claim-ROWH6JVB.js.map +1 -0
  401. package/dist/cli.js +104 -69952
  402. package/dist/cli.js.map +1 -1
  403. package/dist/client-EURRWXPW.js +19 -0
  404. package/dist/code-FKQW7V7N.js +76 -0
  405. package/dist/code-FKQW7V7N.js.map +1 -0
  406. package/dist/companies-MD2MZKU6.js +195 -0
  407. package/dist/companies-MD2MZKU6.js.map +1 -0
  408. package/dist/complete-FCAJM6Z4.js +81 -0
  409. package/dist/complete-FCAJM6Z4.js.map +1 -0
  410. package/dist/config-OZOHIAOJ.js +88 -0
  411. package/dist/config-OZOHIAOJ.js.map +1 -0
  412. package/dist/connect-4YXFMXYX.js +42 -0
  413. package/dist/content-PCGSX2QU.js +85 -0
  414. package/dist/content-PCGSX2QU.js.map +1 -0
  415. package/dist/conversation-UX5NZ3IO.js +93 -0
  416. package/dist/conversation-UX5NZ3IO.js.map +1 -0
  417. package/dist/conversion-report-7QSWB2FY.js +125 -0
  418. package/dist/conversion-report-7QSWB2FY.js.map +1 -0
  419. package/dist/conversion-upload-command-XESIAQPU.js +438 -0
  420. package/dist/conversion-upload-command-XESIAQPU.js.map +1 -0
  421. package/dist/conversion-writes-BGOVLBS5.js +120 -0
  422. package/dist/conversion-writes-BGOVLBS5.js.map +1 -0
  423. package/dist/conversions-IYIWW4O7.js +76 -0
  424. package/dist/conversions-IYIWW4O7.js.map +1 -0
  425. package/dist/conversions-RLIJAE7E.js +135 -0
  426. package/dist/conversions-RLIJAE7E.js.map +1 -0
  427. package/dist/countries-5OBUVLLD.js +82 -0
  428. package/dist/countries-5OBUVLLD.js.map +1 -0
  429. package/dist/create-CUJLJUAQ.js +138 -0
  430. package/dist/create-CUJLJUAQ.js.map +1 -0
  431. package/dist/create-FENOT6DX.js +236 -0
  432. package/dist/create-FENOT6DX.js.map +1 -0
  433. package/dist/create-OFZQMOWO.js +59 -0
  434. package/dist/create-OFZQMOWO.js.map +1 -0
  435. package/dist/create-Z5SS2AWB.js +207 -0
  436. package/dist/create-Z5SS2AWB.js.map +1 -0
  437. package/dist/creatives-UJW2QBLH.js +182 -0
  438. package/dist/creatives-UJW2QBLH.js.map +1 -0
  439. package/dist/creatives-XI2DGQUZ.js +87 -0
  440. package/dist/creatives-XI2DGQUZ.js.map +1 -0
  441. package/dist/creatives-YLXQSROD.js +51 -0
  442. package/dist/creatives-YLXQSROD.js.map +1 -0
  443. package/dist/credentials-CH73QMD2.js +287 -0
  444. package/dist/credentials-CH73QMD2.js.map +1 -0
  445. package/dist/critique-C4WRQ4AJ.js +1449 -0
  446. package/dist/critique-C4WRQ4AJ.js.map +1 -0
  447. package/dist/critique-ZBNJ6MQ3.js +213 -0
  448. package/dist/critique-ZBNJ6MQ3.js.map +1 -0
  449. package/dist/crm-MJTT6GOJ.js +224 -0
  450. package/dist/crm-MJTT6GOJ.js.map +1 -0
  451. package/dist/crm-ZXHSEFYI.js +318 -0
  452. package/dist/crm-ZXHSEFYI.js.map +1 -0
  453. package/dist/crop-B52KIQ4O.js +113 -0
  454. package/dist/crop-B52KIQ4O.js.map +1 -0
  455. package/dist/currency-GGWF5DMU.js +88 -0
  456. package/dist/currency-GGWF5DMU.js.map +1 -0
  457. package/dist/custom-conversions-7WDBTC4X.js +125 -0
  458. package/dist/custom-conversions-7WDBTC4X.js.map +1 -0
  459. package/dist/customers-7E4HBSEY.js +56 -0
  460. package/dist/decide-PK7WNSQ2.js +199 -0
  461. package/dist/decide-PK7WNSQ2.js.map +1 -0
  462. package/dist/delete-56LKNEGA.js +71 -0
  463. package/dist/delete-56LKNEGA.js.map +1 -0
  464. package/dist/delete-DQY4ZSFN.js +54 -0
  465. package/dist/delete-DQY4ZSFN.js.map +1 -0
  466. package/dist/delete-EZ7HHOOD.js +69 -0
  467. package/dist/delete-EZ7HHOOD.js.map +1 -0
  468. package/dist/delete-JV4KAA6Y.js +66 -0
  469. package/dist/delete-JV4KAA6Y.js.map +1 -0
  470. package/dist/delete-YONJW4UZ.js +63 -0
  471. package/dist/delete-YONJW4UZ.js.map +1 -0
  472. package/dist/delete-YSYVFL6W.js +71 -0
  473. package/dist/delete-YSYVFL6W.js.map +1 -0
  474. package/dist/demographics-6N3TTYWC.js +138 -0
  475. package/dist/demographics-6N3TTYWC.js.map +1 -0
  476. package/dist/describe-AAVYIFE3.js +97 -0
  477. package/dist/describe-AAVYIFE3.js.map +1 -0
  478. package/dist/describe-UQFLWVFR.js +97 -0
  479. package/dist/describe-UQFLWVFR.js.map +1 -0
  480. package/dist/dimensions-QDXBG2XC.js +101 -0
  481. package/dist/dimensions-QDXBG2XC.js.map +1 -0
  482. package/dist/discard-S3NGGUSK.js +66 -0
  483. package/dist/discard-S3NGGUSK.js.map +1 -0
  484. package/dist/discover-6TEBPVNC.js +93 -0
  485. package/dist/discover-6TEBPVNC.js.map +1 -0
  486. package/dist/discover-SBZPB7T2.js +172 -0
  487. package/dist/discover-SBZPB7T2.js.map +1 -0
  488. package/dist/domain-AMAR76PB.js +98 -0
  489. package/dist/domain-AMAR76PB.js.map +1 -0
  490. package/dist/download-LV3GPOWV.js +299 -0
  491. package/dist/download-LV3GPOWV.js.map +1 -0
  492. package/dist/draft-6QZ7WMWM.js +217 -0
  493. package/dist/draft-6QZ7WMWM.js.map +1 -0
  494. package/dist/draft-DDFFTZ43.js +233 -0
  495. package/dist/draft-DDFFTZ43.js.map +1 -0
  496. package/dist/draft-FIKJXNIQ.js +281 -0
  497. package/dist/draft-FIKJXNIQ.js.map +1 -0
  498. package/dist/draft-GYGWTMBU.js +307 -0
  499. package/dist/draft-GYGWTMBU.js.map +1 -0
  500. package/dist/draft-KXL62WFS.js +328 -0
  501. package/dist/draft-KXL62WFS.js.map +1 -0
  502. package/dist/draft-N4LFPKTA.js +100 -0
  503. package/dist/draft-N4LFPKTA.js.map +1 -0
  504. package/dist/draft-POSLJCWG.js +306 -0
  505. package/dist/draft-POSLJCWG.js.map +1 -0
  506. package/dist/draft-XPO5RWST.js +103 -0
  507. package/dist/draft-XPO5RWST.js.map +1 -0
  508. package/dist/engine/index.d.ts +213 -127
  509. package/dist/engine/index.js +18 -4
  510. package/dist/{env-4P5HOGNA.js → env-TQ2XS2KY.js} +12 -2
  511. package/dist/env-TQ2XS2KY.js.map +1 -0
  512. package/dist/experiment-FQNKMORZ.js +1397 -0
  513. package/dist/experiment-FQNKMORZ.js.map +1 -0
  514. package/dist/export-4IZQZAPF.js +168 -0
  515. package/dist/export-4IZQZAPF.js.map +1 -0
  516. package/dist/extract-DZMGVQ7D.js +113 -0
  517. package/dist/extract-DZMGVQ7D.js.map +1 -0
  518. package/dist/facets-223V5P47.js +114 -0
  519. package/dist/facets-223V5P47.js.map +1 -0
  520. package/dist/favorites-DD2LNSTG.js +178 -0
  521. package/dist/favorites-DD2LNSTG.js.map +1 -0
  522. package/dist/feed-TUVFNX2U.js +177 -0
  523. package/dist/feed-TUVFNX2U.js.map +1 -0
  524. package/dist/feedback-MIW23MI2.js +66 -0
  525. package/dist/feedback-MIW23MI2.js.map +1 -0
  526. package/dist/feedback-record-RGKRKPGZ.js +175 -0
  527. package/dist/feedback-record-RGKRKPGZ.js.map +1 -0
  528. package/dist/fetch-O6UHNXWB.js +139 -0
  529. package/dist/fetch-O6UHNXWB.js.map +1 -0
  530. package/dist/fetch-TZ4UCMJK.js +176 -0
  531. package/dist/fetch-TZ4UCMJK.js.map +1 -0
  532. package/dist/find-4XKIBF4D.js +134 -0
  533. package/dist/find-4XKIBF4D.js.map +1 -0
  534. package/dist/fit-ODVVXV2Q.js +45 -0
  535. package/dist/fit-ODVVXV2Q.js.map +1 -0
  536. package/dist/flows-HVWBFZ7T.js +247 -0
  537. package/dist/flows-HVWBFZ7T.js.map +1 -0
  538. package/dist/follow-RCOXTZGY.js +94 -0
  539. package/dist/follow-RCOXTZGY.js.map +1 -0
  540. package/dist/follow-competitors-SQ5XBFTK.js +112 -0
  541. package/dist/follow-competitors-SQ5XBFTK.js.map +1 -0
  542. package/dist/following-VDU3BHC4.js +113 -0
  543. package/dist/following-VDU3BHC4.js.map +1 -0
  544. package/dist/fonts-NBIQAVQN.js +34 -0
  545. package/dist/fonts-NBIQAVQN.js.map +1 -0
  546. package/dist/forecast-2GJKBNBM.js +168 -0
  547. package/dist/forecast-2GJKBNBM.js.map +1 -0
  548. package/dist/forecast-76MUJBPO.js +104 -0
  549. package/dist/forecast-76MUJBPO.js.map +1 -0
  550. package/dist/funding-HTFVZLLG.js +85 -0
  551. package/dist/funding-HTFVZLLG.js.map +1 -0
  552. package/dist/funnel-FE2HIRVQ.js +394 -0
  553. package/dist/funnel-FE2HIRVQ.js.map +1 -0
  554. package/dist/ga4-2RC4EH7V.js +55 -0
  555. package/dist/ga4-2RC4EH7V.js.map +1 -0
  556. package/dist/generate-TPDOBCKQ.js +262 -0
  557. package/dist/generate-TPDOBCKQ.js.map +1 -0
  558. package/dist/get-6JNOVI27.js +56 -0
  559. package/dist/get-6JNOVI27.js.map +1 -0
  560. package/dist/get-FH2LIEYL.js +63 -0
  561. package/dist/get-FH2LIEYL.js.map +1 -0
  562. package/dist/get-GZLCDXXW.js +61 -0
  563. package/dist/get-GZLCDXXW.js.map +1 -0
  564. package/dist/get-KVAOPAQW.js +78 -0
  565. package/dist/get-KVAOPAQW.js.map +1 -0
  566. package/dist/get-TSPQYEKC.js +59 -0
  567. package/dist/get-TSPQYEKC.js.map +1 -0
  568. package/dist/get-TY3RXYNZ.js +63 -0
  569. package/dist/get-TY3RXYNZ.js.map +1 -0
  570. package/dist/get-VPW33XTN.js +92 -0
  571. package/dist/get-VPW33XTN.js.map +1 -0
  572. package/dist/gif-JFEYPFFI.js +94 -0
  573. package/dist/gif-JFEYPFFI.js.map +1 -0
  574. package/dist/google-R6RD4L6Z.js +136 -0
  575. package/dist/google-R6RD4L6Z.js.map +1 -0
  576. package/dist/google-VPQNRVIB.js +3190 -0
  577. package/dist/google-VPQNRVIB.js.map +1 -0
  578. package/dist/google-ads-JEQMNNMN.js +195 -0
  579. package/dist/google-ads-JEQMNNMN.js.map +1 -0
  580. package/dist/group-7AV6UL26.js +43 -0
  581. package/dist/group-7AV6UL26.js.map +1 -0
  582. package/dist/group-BVQZEZIC.js +43 -0
  583. package/dist/group-BVQZEZIC.js.map +1 -0
  584. package/dist/gsc-SQ42WAQE.js +30 -0
  585. package/dist/gsc-SQ42WAQE.js.map +1 -0
  586. package/dist/history-BNNHFURR.js +67 -0
  587. package/dist/history-BNNHFURR.js.map +1 -0
  588. package/dist/history-EHUQG4CE.js +133 -0
  589. package/dist/history-EHUQG4CE.js.map +1 -0
  590. package/dist/hubspot-JABE4USC.js +779 -0
  591. package/dist/hubspot-JABE4USC.js.map +1 -0
  592. package/dist/icon-OYTTSNSL.js +137 -0
  593. package/dist/icon-OYTTSNSL.js.map +1 -0
  594. package/dist/ideas-OD76WOQ3.js +32 -0
  595. package/dist/ideas-OD76WOQ3.js.map +1 -0
  596. package/dist/images-LY7AWWBK.js +90 -0
  597. package/dist/images-LY7AWWBK.js.map +1 -0
  598. package/dist/improve-W6GZNT2A.js +86 -0
  599. package/dist/improve-W6GZNT2A.js.map +1 -0
  600. package/dist/ingest-47WON6HT.js +23 -0
  601. package/dist/ingest-47WON6HT.js.map +1 -0
  602. package/dist/ingest-HDGGHBSJ.js +107 -0
  603. package/dist/ingest-HDGGHBSJ.js.map +1 -0
  604. package/dist/insights-IW7LGAWU.js +423 -0
  605. package/dist/insights-IW7LGAWU.js.map +1 -0
  606. package/dist/insights-QH54F56A.js +122 -0
  607. package/dist/insights-QH54F56A.js.map +1 -0
  608. package/dist/inspect-AJLG2HGL.js +189 -0
  609. package/dist/inspect-AJLG2HGL.js.map +1 -0
  610. package/dist/inspiration-RDQ3Y3N3.js +53 -0
  611. package/dist/inspiration-RDQ3Y3N3.js.map +1 -0
  612. package/dist/intent-5GSSCFZ2.js +95 -0
  613. package/dist/intent-5GSSCFZ2.js.map +1 -0
  614. package/dist/job-URVXJP75.js +202 -0
  615. package/dist/job-URVXJP75.js.map +1 -0
  616. package/dist/job-create-S4SECUVF.js +109 -0
  617. package/dist/job-create-S4SECUVF.js.map +1 -0
  618. package/dist/job-status-253SUCTJ.js +74 -0
  619. package/dist/job-status-253SUCTJ.js.map +1 -0
  620. package/dist/keep-GPSYDIGJ.js +78 -0
  621. package/dist/keep-GPSYDIGJ.js.map +1 -0
  622. package/dist/keyword-gap-NTFDX43B.js +130 -0
  623. package/dist/keyword-gap-NTFDX43B.js.map +1 -0
  624. package/dist/keyword-metrics-XSFLMBZX.js +197 -0
  625. package/dist/keyword-metrics-XSFLMBZX.js.map +1 -0
  626. package/dist/keywords-for-site-U4JLYXVE.js +128 -0
  627. package/dist/keywords-for-site-U4JLYXVE.js.map +1 -0
  628. package/dist/landing-AO72EGJ6.js +28 -0
  629. package/dist/landing-AO72EGJ6.js.map +1 -0
  630. package/dist/landings-NEF4PQL2.js +151 -0
  631. package/dist/landings-NEF4PQL2.js.map +1 -0
  632. package/dist/languages-3RDPPPTE.js +55 -0
  633. package/dist/languages-3RDPPPTE.js.map +1 -0
  634. package/dist/languages-CQX3YOIT.js +40 -0
  635. package/dist/languages-CQX3YOIT.js.map +1 -0
  636. package/dist/launches-GBHLJH5S.js +19 -0
  637. package/dist/launches-GBHLJH5S.js.map +1 -0
  638. package/dist/layerize-Q56C6LFG.js +190 -0
  639. package/dist/layerize-Q56C6LFG.js.map +1 -0
  640. package/dist/leads-JHUM6FYR.js +293 -0
  641. package/dist/leads-JHUM6FYR.js.map +1 -0
  642. package/dist/leads-UYZM2XQX.js +90 -0
  643. package/dist/leads-UYZM2XQX.js.map +1 -0
  644. package/dist/learn-D2KBGJHZ.js +203 -0
  645. package/dist/learn-D2KBGJHZ.js.map +1 -0
  646. package/dist/library-6VUFHTEK.js +109 -0
  647. package/dist/library-6VUFHTEK.js.map +1 -0
  648. package/dist/lighthouse-57U2GMPV.js +75 -0
  649. package/dist/lighthouse-57U2GMPV.js.map +1 -0
  650. package/dist/line-item-writes-R5GO6GTL.js +208 -0
  651. package/dist/line-item-writes-R5GO6GTL.js.map +1 -0
  652. package/dist/line-items-RBMH6YBZ.js +103 -0
  653. package/dist/line-items-RBMH6YBZ.js.map +1 -0
  654. package/dist/link-WS2RMCOP.js +64 -0
  655. package/dist/link-WS2RMCOP.js.map +1 -0
  656. package/dist/linkedin-FSIALZTS.js +1008 -0
  657. package/dist/linkedin-FSIALZTS.js.map +1 -0
  658. package/dist/list-2AE2Q4ZD.js +151 -0
  659. package/dist/list-2AE2Q4ZD.js.map +1 -0
  660. package/dist/list-E635Y6M4.js +303 -0
  661. package/dist/list-E635Y6M4.js.map +1 -0
  662. package/dist/list-G57ZO4PG.js +50 -0
  663. package/dist/list-G57ZO4PG.js.map +1 -0
  664. package/dist/list-JCSWZWDI.js +57 -0
  665. package/dist/list-JCSWZWDI.js.map +1 -0
  666. package/dist/list-KT6NXVLV.js +63 -0
  667. package/dist/list-KT6NXVLV.js.map +1 -0
  668. package/dist/list-RINCJOAC.js +76 -0
  669. package/dist/list-RINCJOAC.js.map +1 -0
  670. package/dist/list-SRNXHS6L.js +51 -0
  671. package/dist/list-SRNXHS6L.js.map +1 -0
  672. package/dist/list-YJSO4M5Q.js +65 -0
  673. package/dist/list-YJSO4M5Q.js.map +1 -0
  674. package/dist/locations-HW2Y5OJO.js +67 -0
  675. package/dist/locations-HW2Y5OJO.js.map +1 -0
  676. package/dist/locations-XOHI3VWI.js +40 -0
  677. package/dist/locations-XOHI3VWI.js.map +1 -0
  678. package/dist/log-3HX5BIO6.js +92 -0
  679. package/dist/log-3HX5BIO6.js.map +1 -0
  680. package/dist/logo-KXTGIYBI.js +18 -0
  681. package/dist/logo-KXTGIYBI.js.map +1 -0
  682. package/dist/lookup-X6O5UNJH.js +22 -0
  683. package/dist/lookup-X6O5UNJH.js.map +1 -0
  684. package/dist/make-QH77XOWM.js +633 -0
  685. package/dist/make-QH77XOWM.js.map +1 -0
  686. package/dist/map-QABBPYZM.js +404 -0
  687. package/dist/map-QABBPYZM.js.map +1 -0
  688. package/dist/mcp-HSD3W3OB.js +463 -0
  689. package/dist/mcp-HSD3W3OB.js.map +1 -0
  690. package/dist/media-MI4EE7L4.js +89 -0
  691. package/dist/media-MI4EE7L4.js.map +1 -0
  692. package/dist/media-QUTQOBA7.js +15 -0
  693. package/dist/media-QUTQOBA7.js.map +1 -0
  694. package/dist/media-sources-HO4DXRX5.js +32 -0
  695. package/dist/media-sources-HO4DXRX5.js.map +1 -0
  696. package/dist/members-YEYYJGY6.js +44 -0
  697. package/dist/members-YEYYJGY6.js.map +1 -0
  698. package/dist/meta-E3JOUOSS.js +69 -0
  699. package/dist/meta-E3JOUOSS.js.map +1 -0
  700. package/dist/metrics-HQ7XJAYQ.js +152 -0
  701. package/dist/metrics-HQ7XJAYQ.js.map +1 -0
  702. package/dist/models-PGX3FBHU.js +155 -0
  703. package/dist/models-PGX3FBHU.js.map +1 -0
  704. package/dist/normalize-FPJ6GSOY.js +806 -0
  705. package/dist/normalize-FPJ6GSOY.js.map +1 -0
  706. package/dist/normalize-GSQWN77S.js +141 -0
  707. package/dist/normalize-GSQWN77S.js.map +1 -0
  708. package/dist/options-F5XZMQLK.js +121 -0
  709. package/dist/options-F5XZMQLK.js.map +1 -0
  710. package/dist/output-GQFJ2T5B.js +24 -0
  711. package/dist/output-GQFJ2T5B.js.map +1 -0
  712. package/dist/page-VTTSBC6Y.js +105 -0
  713. package/dist/page-VTTSBC6Y.js.map +1 -0
  714. package/dist/pages-PMZVNVQM.js +66 -0
  715. package/dist/pages-PMZVNVQM.js.map +1 -0
  716. package/dist/performance-G67JQE5J.js +24 -0
  717. package/dist/performance-G67JQE5J.js.map +1 -0
  718. package/dist/pinterest-UDNGOWDM.js +96 -0
  719. package/dist/pinterest-UDNGOWDM.js.map +1 -0
  720. package/dist/pixels-OYZDGCH3.js +108 -0
  721. package/dist/pixels-OYZDGCH3.js.map +1 -0
  722. package/dist/pixels-XKGNTCFX.js +60 -0
  723. package/dist/pixels-XKGNTCFX.js.map +1 -0
  724. package/dist/plan-DZS3TVIJ.js +207 -0
  725. package/dist/plan-DZS3TVIJ.js.map +1 -0
  726. package/dist/preview-DR6MXOGI.js +169 -0
  727. package/dist/preview-DR6MXOGI.js.map +1 -0
  728. package/dist/profile-VOVZG44G.js +159 -0
  729. package/dist/profile-VOVZG44G.js.map +1 -0
  730. package/dist/promoted-tweet-writes-VCK2ADVJ.js +180 -0
  731. package/dist/promoted-tweet-writes-VCK2ADVJ.js.map +1 -0
  732. package/dist/promoted-tweets-4ACBP2RV.js +119 -0
  733. package/dist/promoted-tweets-4ACBP2RV.js.map +1 -0
  734. package/dist/properties-MKVF7MUK.js +101 -0
  735. package/dist/properties-MKVF7MUK.js.map +1 -0
  736. package/dist/publish-6XVA35T6.js +322 -0
  737. package/dist/publish-6XVA35T6.js.map +1 -0
  738. package/dist/query-CNWQ6STR.js +61 -0
  739. package/dist/query-CNWQ6STR.js.map +1 -0
  740. package/dist/query-DBSBZ73C.js +247 -0
  741. package/dist/query-DBSBZ73C.js.map +1 -0
  742. package/dist/query-QK4U4TEL.js +236 -0
  743. package/dist/query-QK4U4TEL.js.map +1 -0
  744. package/dist/questionnaire-OVJWNUVV.js +93 -0
  745. package/dist/questionnaire-OVJWNUVV.js.map +1 -0
  746. package/dist/read-L2QIVJTL.js +141 -0
  747. package/dist/read-L2QIVJTL.js.map +1 -0
  748. package/dist/reddit-SCEB2JQL.js +26 -0
  749. package/dist/reddit-SCEB2JQL.js.map +1 -0
  750. package/dist/refresh-TIH5Y3NP.js +94 -0
  751. package/dist/refresh-TIH5Y3NP.js.map +1 -0
  752. package/dist/release-LKMUKVXM.js +60 -0
  753. package/dist/release-LKMUKVXM.js.map +1 -0
  754. package/dist/relevant-pages-CRI2NLWG.js +99 -0
  755. package/dist/relevant-pages-CRI2NLWG.js.map +1 -0
  756. package/dist/remove-6ABOB7TZ.js +50 -0
  757. package/dist/remove-6ABOB7TZ.js.map +1 -0
  758. package/dist/remove-JKSCGKQG.js +48 -0
  759. package/dist/remove-JKSCGKQG.js.map +1 -0
  760. package/dist/reopen-27TDNXFP.js +63 -0
  761. package/dist/reopen-27TDNXFP.js.map +1 -0
  762. package/dist/rerun-XHECBJD5.js +84 -0
  763. package/dist/rerun-XHECBJD5.js.map +1 -0
  764. package/dist/research-4MOXIMDX.js +62 -0
  765. package/dist/research-4MOXIMDX.js.map +1 -0
  766. package/dist/research-XYXP7D4R.js +262 -0
  767. package/dist/research-XYXP7D4R.js.map +1 -0
  768. package/dist/reset-NLYEBPLX.js +54 -0
  769. package/dist/reset-NLYEBPLX.js.map +1 -0
  770. package/dist/reset-RDQOYBXX.js +54 -0
  771. package/dist/reset-RDQOYBXX.js.map +1 -0
  772. package/dist/resolve-VHKWC5KW.js +121 -0
  773. package/dist/resolve-VHKWC5KW.js.map +1 -0
  774. package/dist/retry-MTQSHXKH.js +60 -0
  775. package/dist/retry-MTQSHXKH.js.map +1 -0
  776. package/dist/retry-ZI5ECSFD.js +62 -0
  777. package/dist/retry-ZI5ECSFD.js.map +1 -0
  778. package/dist/retry-failed-2OYMCB55.js +45 -0
  779. package/dist/retry-failed-2OYMCB55.js.map +1 -0
  780. package/dist/run-BMJJRJL2.js +65 -0
  781. package/dist/run-BMJJRJL2.js.map +1 -0
  782. package/dist/run-IHXS6F5V.js +59 -0
  783. package/dist/run-IHXS6F5V.js.map +1 -0
  784. package/dist/scaffold-ad-QEADKYPL.js +1548 -0
  785. package/dist/scaffold-ad-QEADKYPL.js.map +1 -0
  786. package/dist/scaffold-static-ad-PTJ73ZNF.js +971 -0
  787. package/dist/scaffold-static-ad-PTJ73ZNF.js.map +1 -0
  788. package/dist/scaffold-video-ZQEZRVD6.js +872 -0
  789. package/dist/scaffold-video-ZQEZRVD6.js.map +1 -0
  790. package/dist/schedule-JD2EQJCT.js +61 -0
  791. package/dist/schedule-JD2EQJCT.js.map +1 -0
  792. package/dist/schedule-TLYT5UXM.js +66 -0
  793. package/dist/schedule-TLYT5UXM.js.map +1 -0
  794. package/dist/scheduled-agents-GIVKWTXX.js +49 -0
  795. package/dist/scheduled-agents-GIVKWTXX.js.map +1 -0
  796. package/dist/schema-MZTVCNP4.js +63 -0
  797. package/dist/schema-MZTVCNP4.js.map +1 -0
  798. package/dist/schema-RYTVKPLZ.js +132 -0
  799. package/dist/schema-RYTVKPLZ.js.map +1 -0
  800. package/dist/scrape-T3DA7ER7.js +2241 -0
  801. package/dist/scrape-T3DA7ER7.js.map +1 -0
  802. package/dist/screenshot-EIGC6SZB.js +102 -0
  803. package/dist/screenshot-EIGC6SZB.js.map +1 -0
  804. package/dist/search-2SVUDZIX.js +224 -0
  805. package/dist/search-2SVUDZIX.js.map +1 -0
  806. package/dist/search-34NFJ5F6.js +26 -0
  807. package/dist/search-34NFJ5F6.js.map +1 -0
  808. package/dist/search-AOFAN5NB.js +174 -0
  809. package/dist/search-AOFAN5NB.js.map +1 -0
  810. package/dist/search-BWE3OPOY.js +91 -0
  811. package/dist/search-BWE3OPOY.js.map +1 -0
  812. package/dist/search-EYYAOZND.js +92 -0
  813. package/dist/search-EYYAOZND.js.map +1 -0
  814. package/dist/search-copy-VKQ2OOBM.js +168 -0
  815. package/dist/search-copy-VKQ2OOBM.js.map +1 -0
  816. package/dist/seeds-HQYSVFIX.js +101 -0
  817. package/dist/seeds-HQYSVFIX.js.map +1 -0
  818. package/dist/select-HJMOAWAU.js +94 -0
  819. package/dist/select-HJMOAWAU.js.map +1 -0
  820. package/dist/sends-I7VQFE4S.js +309 -0
  821. package/dist/sends-I7VQFE4S.js.map +1 -0
  822. package/dist/sequences-JHSEJJGN.js +146 -0
  823. package/dist/sequences-JHSEJJGN.js.map +1 -0
  824. package/dist/set-prompt-TSS33JQZ.js +185 -0
  825. package/dist/set-prompt-TSS33JQZ.js.map +1 -0
  826. package/dist/setup-links-IW4BUANK.js +141 -0
  827. package/dist/setup-links-IW4BUANK.js.map +1 -0
  828. package/dist/{shared-J7DQZ2ED.js → shared-AT3FXPAG.js} +8 -6
  829. package/dist/shared-AT3FXPAG.js.map +1 -0
  830. package/dist/shopify-RQKO5TMR.js +3819 -0
  831. package/dist/shopify-RQKO5TMR.js.map +1 -0
  832. package/dist/site-3NU2BDAR.js +177 -0
  833. package/dist/site-3NU2BDAR.js.map +1 -0
  834. package/dist/sitemaps-FG37N5EB.js +87 -0
  835. package/dist/sitemaps-FG37N5EB.js.map +1 -0
  836. package/dist/sites-FIXK4Y3O.js +101 -0
  837. package/dist/sites-FIXK4Y3O.js.map +1 -0
  838. package/dist/skills-OKVCVQVD.js +47 -0
  839. package/dist/skills-OKVCVQVD.js.map +1 -0
  840. package/dist/sources-SEODRUIC.js +30 -0
  841. package/dist/sources-SEODRUIC.js.map +1 -0
  842. package/dist/sources-Z6BGTBGO.js +421 -0
  843. package/dist/sources-Z6BGTBGO.js.map +1 -0
  844. package/dist/stale-SWPDWXG7.js +63 -0
  845. package/dist/stale-SWPDWXG7.js.map +1 -0
  846. package/dist/stats-ZWZ7BAHV.js +34 -0
  847. package/dist/stats-ZWZ7BAHV.js.map +1 -0
  848. package/dist/status-7Y7UNSO6.js +62 -0
  849. package/dist/status-7Y7UNSO6.js.map +1 -0
  850. package/dist/sticker-FLRZM3BI.js +94 -0
  851. package/dist/sticker-FLRZM3BI.js.map +1 -0
  852. package/dist/stock-2CSMWRWV.js +179 -0
  853. package/dist/stock-2CSMWRWV.js.map +1 -0
  854. package/dist/studio-TTTR6N7D.js +49 -0
  855. package/dist/studio-TTTR6N7D.js.map +1 -0
  856. package/dist/sync-FH773IFH.js +54 -0
  857. package/dist/sync-FH773IFH.js.map +1 -0
  858. package/dist/sync-G5CS2RYV.js +54 -0
  859. package/dist/sync-G5CS2RYV.js.map +1 -0
  860. package/dist/sync-UADA6M33.js +265 -0
  861. package/dist/sync-UADA6M33.js.map +1 -0
  862. package/dist/sync-definition-77KLGPMA.js +52 -0
  863. package/dist/sync-definition-77KLGPMA.js.map +1 -0
  864. package/dist/tag-manager-NK3ZHSJB.js +43 -0
  865. package/dist/tag-manager-NK3ZHSJB.js.map +1 -0
  866. package/dist/tags-ANGHHLAC.js +20 -0
  867. package/dist/tags-ANGHHLAC.js.map +1 -0
  868. package/dist/tags-DKPUSQTJ.js +20 -0
  869. package/dist/tags-DKPUSQTJ.js.map +1 -0
  870. package/dist/tags-HSCV4ZAQ.js +20 -0
  871. package/dist/tags-HSCV4ZAQ.js.map +1 -0
  872. package/dist/tags-J3RMQEQM.js +26 -0
  873. package/dist/tags-J3RMQEQM.js.map +1 -0
  874. package/dist/tags-VZGTR3VJ.js +224 -0
  875. package/dist/tags-VZGTR3VJ.js.map +1 -0
  876. package/dist/targeting-constants-ZGBX3VWU.js +118 -0
  877. package/dist/targeting-constants-ZGBX3VWU.js.map +1 -0
  878. package/dist/targeting-criteria-BSG3CLHU.js +113 -0
  879. package/dist/targeting-criteria-BSG3CLHU.js.map +1 -0
  880. package/dist/targets-DVOAFYZQ.js +102 -0
  881. package/dist/targets-DVOAFYZQ.js.map +1 -0
  882. package/dist/templates-KJ5O44FF.js +178 -0
  883. package/dist/templates-KJ5O44FF.js.map +1 -0
  884. package/dist/testimonials-NB7V3DUO.js +32 -0
  885. package/dist/testimonials-NB7V3DUO.js.map +1 -0
  886. package/dist/themes-3RV3HNNE.js +94 -0
  887. package/dist/themes-3RV3HNNE.js.map +1 -0
  888. package/dist/top-companies-KRB3OZOT.js +108 -0
  889. package/dist/top-companies-KRB3OZOT.js.map +1 -0
  890. package/dist/translate-5FP32BIA.js +598 -0
  891. package/dist/translate-5FP32BIA.js.map +1 -0
  892. package/dist/triggers-EURHTCYC.js +141 -0
  893. package/dist/triggers-EURHTCYC.js.map +1 -0
  894. package/dist/tweets-X64YDEAA.js +123 -0
  895. package/dist/tweets-X64YDEAA.js.map +1 -0
  896. package/dist/unfollow-PIONCSBL.js +46 -0
  897. package/dist/unfollow-PIONCSBL.js.map +1 -0
  898. package/dist/unlink-HVVTZ3MB.js +67 -0
  899. package/dist/unlink-HVVTZ3MB.js.map +1 -0
  900. package/dist/unstage-BJJ5M7MK.js +95 -0
  901. package/dist/unstage-BJJ5M7MK.js.map +1 -0
  902. package/dist/unstage-Y7ZE3NUJ.js +84 -0
  903. package/dist/unstage-Y7ZE3NUJ.js.map +1 -0
  904. package/dist/update-3IP7FB6Y.js +98 -0
  905. package/dist/update-3IP7FB6Y.js.map +1 -0
  906. package/dist/update-CSDSK5NO.js +63 -0
  907. package/dist/update-CSDSK5NO.js.map +1 -0
  908. package/dist/update-FGJXVDB7.js +142 -0
  909. package/dist/update-FGJXVDB7.js.map +1 -0
  910. package/dist/update-MF7LY2OT.js +155 -0
  911. package/dist/update-MF7LY2OT.js.map +1 -0
  912. package/dist/upload-DDPI7KJV.js +126 -0
  913. package/dist/upload-DDPI7KJV.js.map +1 -0
  914. package/dist/upload-IWFYKD7A.js +150 -0
  915. package/dist/upload-IWFYKD7A.js.map +1 -0
  916. package/dist/upscale-EYCEYRWE.js +109 -0
  917. package/dist/upscale-EYCEYRWE.js.map +1 -0
  918. package/dist/url-options-RA3XISLN.js +130 -0
  919. package/dist/url-options-RA3XISLN.js.map +1 -0
  920. package/dist/url-params-SN4APXCK.js +98 -0
  921. package/dist/url-params-SN4APXCK.js.map +1 -0
  922. package/dist/use-E2TA2K6J.js +95 -0
  923. package/dist/use-E2TA2K6J.js.map +1 -0
  924. package/dist/validate-W52JOEXI.js +88 -0
  925. package/dist/validate-W52JOEXI.js.map +1 -0
  926. package/dist/variant-ETHB6XUM.js +338 -0
  927. package/dist/variant-ETHB6XUM.js.map +1 -0
  928. package/dist/videos-653WAFPG.js +39 -0
  929. package/dist/videos-653WAFPG.js.map +1 -0
  930. package/dist/view-BVEZRRV2.js +121 -0
  931. package/dist/view-BVEZRRV2.js.map +1 -0
  932. package/dist/viewer-check-W4ICSBHO.js +68 -0
  933. package/dist/viewer-check-W4ICSBHO.js.map +1 -0
  934. package/dist/web-JWIGFGIU.js +140 -0
  935. package/dist/web-JWIGFGIU.js.map +1 -0
  936. package/dist/winners-MJDKCGEZ.js +113 -0
  937. package/dist/winners-MJDKCGEZ.js.map +1 -0
  938. package/dist/winning-ads-O3IMUGGO.js +84 -0
  939. package/dist/winning-ads-O3IMUGGO.js.map +1 -0
  940. package/dist/write-commands-DA6FIFAO.js +737 -0
  941. package/dist/write-commands-DA6FIFAO.js.map +1 -0
  942. package/dist/write-commands-EMN2WENA.js +1015 -0
  943. package/dist/write-commands-EMN2WENA.js.map +1 -0
  944. package/dist/write-commands-KTPISQRE.js +206 -0
  945. package/dist/write-commands-KTPISQRE.js.map +1 -0
  946. package/dist/write-commands-QBGIWU27.js +713 -0
  947. package/dist/write-commands-QBGIWU27.js.map +1 -0
  948. package/dist/x-BT3SNCTU.js +47 -0
  949. package/dist/x-BT3SNCTU.js.map +1 -0
  950. package/package.json +9 -2
  951. package/dist/chunk-FGHMWBUD.js +0 -9989
  952. package/dist/chunk-FGHMWBUD.js.map +0 -1
  953. package/dist/chunk-HJEMOSPH.js +0 -1292
  954. package/dist/chunk-HJEMOSPH.js.map +0 -1
  955. package/dist/chunk-MF7YA62W.js.map +0 -1
  956. package/dist/chunk-VLUGGVJL.js.map +0 -1
  957. package/dist/chunk-VZEDAFAR.js.map +0 -1
  958. package/dist/chunk-XHC4LMK4.js.map +0 -1
  959. package/dist/client-GR6YL3YX.js +0 -17
  960. package/dist/output-NMQXZF3Z.js +0 -18
  961. /package/dist/{client-GR6YL3YX.js.map → chunk-YQSBHWOI.js.map} +0 -0
  962. /package/dist/{env-4P5HOGNA.js.map → client-EURRWXPW.js.map} +0 -0
  963. /package/dist/{output-NMQXZF3Z.js.map → connect-4YXFMXYX.js.map} +0 -0
  964. /package/dist/{shared-J7DQZ2ED.js.map → customers-7E4HBSEY.js.map} +0 -0
@@ -0,0 +1,3819 @@
1
+ import {
2
+ landingGraphFiles
3
+ } from "./chunk-Z3ENMCFE.js";
4
+ import {
5
+ failValidation,
6
+ loadJsonArg,
7
+ loadJsonPayloads
8
+ } from "./chunk-PY2NJ6XZ.js";
9
+ import "./chunk-D5FY4FAK.js";
10
+ import "./chunk-EIGXX6KI.js";
11
+ import {
12
+ CHAT_READ_ARG
13
+ } from "./chunk-PFBQERWX.js";
14
+ import {
15
+ failApi
16
+ } from "./chunk-JUY47NZ2.js";
17
+ import "./chunk-O6XZIQXZ.js";
18
+ import "./chunk-GXDLWBOY.js";
19
+ import "./chunk-GIWHHCD6.js";
20
+ import "./chunk-UI6BKYQD.js";
21
+ import "./chunk-I2DNBEV6.js";
22
+ import {
23
+ ApiError,
24
+ apiGet,
25
+ apiPost
26
+ } from "./chunk-CUL3VB4L.js";
27
+ import "./chunk-6YYSUISH.js";
28
+ import {
29
+ optionalChatId,
30
+ requireChatId,
31
+ resolveChatId
32
+ } from "./chunk-I4FTS5EE.js";
33
+ import {
34
+ registerSchema,
35
+ writeJson
36
+ } from "./chunk-MYTDK3AL.js";
37
+ import "./chunk-YQSBHWOI.js";
38
+
39
+ // src/commands/shopify/index.ts
40
+ import { defineCommand as defineCommand8 } from "citty";
41
+
42
+ // src/commands/shopify/buyers.ts
43
+ import { defineCommand } from "citty";
44
+
45
+ // src/commands/shopify/buyerHints.ts
46
+ var AUDIENCE_PATH = "To reach more people like your buyers on Meta, stage an audience of the store's purchasers (`baker ads meta audiences create --subtype WEBSITE`) and a lookalike of it (`--subtype LOOKALIKE`). Baker cannot upload a list of buyers to an ad platform.";
47
+ var MIN_ORDERS_TO_SCALE = 5;
48
+ function storeFactsHint(currency) {
49
+ 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.`;
50
+ }
51
+ var percent = (value) => `${Math.round(value * 100)}%`;
52
+ function buyerHistoryHints(history, basis) {
53
+ const hints = [];
54
+ if (history.state === "importing") {
55
+ hints.push(
56
+ `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.`
57
+ );
58
+ }
59
+ if (history.state === "failed") {
60
+ hints.push("Baker couldn't finish bringing in this store's past orders; older periods may be incomplete.");
61
+ }
62
+ if (basis === "last_60_days") {
63
+ hints.push(
64
+ "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."
65
+ );
66
+ }
67
+ return hints;
68
+ }
69
+ function countriesHints(answer) {
70
+ const hints = buyerHistoryHints(answer.history, answer.basis);
71
+ if (answer.total.orders === 0) {
72
+ hints.push("No orders were placed in this period.");
73
+ return hints;
74
+ }
75
+ hints.push(storeFactsHint(answer.currency));
76
+ const money = (amount) => `${answer.currency ?? ""} ${amount}`.trim();
77
+ 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];
78
+ if (growing && answer.level === "country") {
79
+ hints.push(
80
+ `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.`
81
+ );
82
+ }
83
+ const average = answer.total.averageOrder;
84
+ 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];
85
+ if (promising && answer.level === "country") {
86
+ hints.push(
87
+ `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.`
88
+ );
89
+ }
90
+ if (answer.unknown && answer.total.orders > 0 && answer.unknown.orders / answer.total.orders > 0.25) {
91
+ const lack = answer.level === "country" ? "have no address Baker could read (Unknown)" : `name no ${answer.level} on their address (${answer.unknown.name})`;
92
+ hints.push(
93
+ `${answer.unknown.orders} of these orders ${lack}: say so rather than leaving them out of a place's share.`
94
+ );
95
+ }
96
+ return hints;
97
+ }
98
+ function bestSellersHints(answer) {
99
+ const hints = buyerHistoryHints(answer.history, answer.basis);
100
+ const leader = answer.repeat?.[0];
101
+ if (leader && leader.buyersAgain > 0) {
102
+ hints.push(
103
+ `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.`
104
+ );
105
+ }
106
+ const top = answer.products?.[0];
107
+ if (top && top.units > 0) {
108
+ hints.push(
109
+ `"${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\`.`
110
+ );
111
+ }
112
+ const losing = answer.momentum?.[0];
113
+ if (losing) {
114
+ hints.push(
115
+ `"${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.`
116
+ );
117
+ }
118
+ const sellers = answer.products ?? answer.variants ?? [];
119
+ if (sellers.some((row) => row.units > 0)) hints.push(storeFactsHint(answer.currency));
120
+ const withRefunds = sellers.filter((row) => row.refundedUnits > 0).sort((a, b) => b.refundRate - a.refundRate).slice(0, 5);
121
+ if (withRefunds.length > 0) {
122
+ const money = (amount) => `${answer.currency ?? ""} ${amount}`.trim();
123
+ hints.push(
124
+ `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.`
125
+ );
126
+ }
127
+ const refunded = answer.products?.find((row) => row.units >= 5 && row.refundRate >= 0.2);
128
+ if (refunded) {
129
+ hints.push(
130
+ `"${refunded.title}" comes back often (${percent(refunded.refundRate)} of units refunded): don't lead a campaign with it before finding out why.`
131
+ );
132
+ }
133
+ return hints;
134
+ }
135
+ function bestBuyersHint(best) {
136
+ if (!best || best.buyers === 0) return [];
137
+ const countries = best.topCountries.map((country) => country.name).join(", ");
138
+ const products = best.topFirstProducts.map((product) => `"${product.title}"`).join(", ");
139
+ if (!countries && !products) return [];
140
+ return [
141
+ `Find more buyers like your best (${best.buyers}): ${countries ? `target ${countries}` : ""}${countries && products ? "; " : ""}${products ? `lead with ${products}, what their first orders held` : ""}.`
142
+ ];
143
+ }
144
+ function segmentsHints(answer) {
145
+ const hints = buyerHistoryHints(answer.history, answer.basis);
146
+ hints.push(...bestBuyersHint(answer.segments.find((segment) => segment.segment === "best")));
147
+ const atRisk = answer.segments.find((segment) => segment.segment === "at_risk");
148
+ if (atRisk && atRisk.buyers > 0) {
149
+ hints.push(
150
+ `${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.`
151
+ );
152
+ }
153
+ if (answer.buyers > 0) hints.push(AUDIENCE_PATH);
154
+ return hints;
155
+ }
156
+ function buyersHints(answer) {
157
+ const hints = buyerHistoryHints(answer.history, answer.basis);
158
+ if (answer.summary.orders === 0) {
159
+ hints.push("No orders were placed in this period.");
160
+ return hints;
161
+ }
162
+ hints.push(storeFactsHint(answer.currency));
163
+ if (answer.acquisition?.by === "campaign" && answer.acquisition.rows.length > 0) {
164
+ hints.push(
165
+ "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."
166
+ );
167
+ }
168
+ if (!answer.acquisition) {
169
+ hints.push(
170
+ "For lifetime value by the campaign or channel that brought each buyer, add `--by campaign` or `--by channel`."
171
+ );
172
+ }
173
+ if (answer.summary.guestOrders > 0 && answer.summary.guestOrders >= answer.summary.orders / 2) {
174
+ hints.push(
175
+ `${answer.summary.guestOrders} of ${answer.summary.orders} orders were placed as guests: they count in sales, never as buyers, so repeat rates read low.`
176
+ );
177
+ }
178
+ if (answer.summary.buyers > 0) hints.push(AUDIENCE_PATH);
179
+ return hints;
180
+ }
181
+ function cohortsHints(answer) {
182
+ const hints = buyerHistoryHints(answer.history, answer.basis);
183
+ const reached = answer.cohorts.filter((cohort) => cohort.retention.m3 !== null && cohort.size >= 5);
184
+ if (reached.length >= 2) {
185
+ const best = [...reached].sort((a, b) => (b.retention.m3 ?? 0) - (a.retention.m3 ?? 0))[0];
186
+ if (best) {
187
+ hints.push(
188
+ `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\`).`
189
+ );
190
+ }
191
+ }
192
+ return hints;
193
+ }
194
+
195
+ // ../api/src/shopify/buyers.ts
196
+ import { z as z2 } from "zod";
197
+
198
+ // ../api/src/shopify/wire.ts
199
+ import { z } from "zod";
200
+ var SHOPIFY_NOT_CONNECTED_MESSAGE = "No Shopify store is connected for this company.";
201
+ var SHOPIFY_NOT_OFFERED_MESSAGE = "Baker can't read Shopify stores yet.";
202
+ var SHOPIFY_PRODUCT_NOT_FOUND_PREFIX = "No product matches";
203
+ var shopifyStockStateSchema = z.enum(["in_stock", "partly_out", "out_of_stock"]);
204
+ var shopifyOptionStockSchema = z.enum(["in_stock", "out_of_stock", "not_tracked", "selling_when_out"]);
205
+ var shopifyVisibilitySchema = z.enum(["listed", "link_only", "not_on_store", "no_store"]);
206
+ var shopifyProductStatusSchema = z.enum(["ACTIVE", "ARCHIVED", "DRAFT", "UNLISTED"]);
207
+ var shopifyStoreConnectionSchema = z.enum(["connected", "needs_reconnect", "disconnected"]);
208
+ var shopifyCatalogStateSchema = z.enum(["starting", "importing", "ready", "failed"]);
209
+ var shopifyStorefrontSchema = z.enum(["online_store", "own"]);
210
+ var shopifyAvailabilitySchema = z.enum(["link_only", "not_for_sale"]);
211
+ var shopifyBuyDestinationSchema = z.enum(["checkout", "store_cart", "product_page"]);
212
+ var shopifyBuyNoteSchema = z.enum([
213
+ "subscription_only",
214
+ "subscription_optional",
215
+ "password_protected",
216
+ "own_storefront",
217
+ "details_pending"
218
+ ]);
219
+ var shopifyUnreadablePartSchema = z.enum(["metaobjects", "definitions", "languages", "policies"]);
220
+ var shopifyStoreStateSchema = z.object({
221
+ name: z.string(),
222
+ connection: shopifyStoreConnectionSchema,
223
+ catalog: shopifyCatalogStateSchema,
224
+ importProgress: z.object({ done: z.number(), total: z.number().optional() }).optional(),
225
+ /** Products Baker could not read (it keeps retrying); present only when > 0 and not importing. */
226
+ unreadable: z.number().optional(),
227
+ unreadableCapped: z.literal(true).optional(),
228
+ /** Baker could not check the store for changes lately. */
229
+ checksFailing: z.literal(true).optional(),
230
+ storefront: shopifyStorefrontSchema.optional(),
231
+ /** When Baker last heard from or checked the store — not a promise the copy is current. */
232
+ lastActivity: z.string().optional()
233
+ });
234
+ var flag = z.enum(["true", "false"]).transform((value) => value === "true");
235
+ var shopifyStoreRequestSchema = z.object({ full: flag.optional() });
236
+ var shopifyProductsRequestSchema = z.object({
237
+ search: z.string().max(200).optional(),
238
+ collection: z.string().min(1).max(500).optional(),
239
+ stock: shopifyStockStateSchema.optional(),
240
+ limit: z.coerce.number().int().min(1).max(100).optional(),
241
+ cursor: z.string().optional(),
242
+ full: flag.optional()
243
+ });
244
+ var SHOPIFY_MAX_BUY_QUANTITY = 99;
245
+ var shopifyProductRequestSchema = z.object({
246
+ ref: z.string().min(1).max(500),
247
+ variantsCursor: z.string().optional(),
248
+ outOfStock: flag.optional(),
249
+ full: flag.optional(),
250
+ /** The product's whole details: formatted description, custom fields, subscriptions, translations. */
251
+ content: flag.optional(),
252
+ /** One custom field whole, by its name or `namespace.key`. */
253
+ field: z.string().min(1).max(200).optional(),
254
+ /** With `field`: the option (its `variantId`) whose field to return, when options share one. */
255
+ variant: z.string().regex(/^\d{1,30}$/, "an option's variantId").optional(),
256
+ /** How many of the option each buy link puts in the cart (1-99, default 1). */
257
+ quantity: z.coerce.number().int().min(1).max(SHOPIFY_MAX_BUY_QUANTITY).optional()
258
+ });
259
+ var shopifyCollectionsRequestSchema = z.object({
260
+ search: z.string().max(200).optional(),
261
+ /** Each collection's description, image and SEO. */
262
+ full: flag.optional()
263
+ });
264
+ var shopifyStockRequestSchema = z.object({
265
+ collection: z.string().min(1).max(500).optional(),
266
+ limit: z.coerce.number().int().min(1).max(200).optional(),
267
+ full: flag.optional()
268
+ });
269
+ var AMOUNT = String.raw`[A-Z]{3} \d+(?:\.\d+)?`;
270
+ var shopifyPriceSchema = z.string().regex(new RegExp(`^${AMOUNT}$`), "a price is '<CURRENCY> <amount>'");
271
+ var shopifyPriceRangeSchema = z.string().regex(new RegExp(`^${AMOUNT}(?: \u2013 ${AMOUNT})?$`), "a price range is '<CURRENCY> <min> \u2013 <CURRENCY> <max>'");
272
+ var SHOPIFY_STOCK_COUNT_CAP = 100;
273
+ var SHOPIFY_COLLECTION_COUNT_CAP = 500;
274
+ var shopifyStoreSummarySchema = z.object({
275
+ name: z.string(),
276
+ domain: z.string(),
277
+ storefrontUrl: z.string().optional(),
278
+ currency: z.string().optional(),
279
+ homeCountry: z.string().optional(),
280
+ products: z.number(),
281
+ collections: z.number(),
282
+ outOfStock: z.number(),
283
+ partlyOut: z.number(),
284
+ /** A count stopped at its cap: `SHOPIFY_STOCK_COUNT_CAP` for stock counts, `SHOPIFY_COLLECTION_COUNT_CAP` for collections. */
285
+ capped: z.literal(true).optional(),
286
+ pricePoints: z.array(
287
+ z.object({
288
+ name: z.string(),
289
+ currency: z.string(),
290
+ countries: z.array(z.string()),
291
+ moreCountries: z.number().optional()
292
+ })
293
+ ).optional(),
294
+ productsUrl: z.string(),
295
+ /** Where the buy links send a shopper (S3b): the store's own setting, checkout unless changed. */
296
+ buyDestination: shopifyBuyDestinationSchema,
297
+ /** The store asks for a password before anyone can see it: a buy link meets it first. */
298
+ passwordProtected: z.literal(true).optional(),
299
+ /** The store's published languages, primary first. */
300
+ languages: z.array(z.object({ locale: z.string(), name: z.string(), primary: z.boolean() })).optional(),
301
+ policies: z.array(
302
+ z.object({
303
+ type: z.string(),
304
+ title: z.string(),
305
+ url: z.string().optional(),
306
+ // --full
307
+ text: z.string().optional()
308
+ })
309
+ ).optional(),
310
+ /** Store content Baker can't read until the store owner accepts Baker's new permissions. */
311
+ unreadable: z.array(shopifyUnreadablePartSchema).optional(),
312
+ // --full
313
+ adminUrl: z.string().optional(),
314
+ brand: z.object({
315
+ slogan: z.string().optional(),
316
+ shortDescription: z.string().optional(),
317
+ logoUrl: z.string().optional(),
318
+ squareLogoUrl: z.string().optional(),
319
+ coverImageUrl: z.string().optional(),
320
+ colors: z.array(
321
+ z.object({ role: z.string(), background: z.string().optional(), foreground: z.string().optional() })
322
+ )
323
+ }).optional()
324
+ });
325
+ var shopifyStoreResponseSchema = z.object({
326
+ store: shopifyStoreStateSchema,
327
+ summary: shopifyStoreSummarySchema,
328
+ /**
329
+ * Which store changes Baker may make (S9), by what the store allowed: false
330
+ * means `baker shopify change …` will ask the user to allow it first.
331
+ */
332
+ canChange: z.object({
333
+ products: z.boolean(),
334
+ publishing: z.boolean(),
335
+ discounts: z.boolean(),
336
+ redirects: z.boolean(),
337
+ marketing: z.boolean()
338
+ }).optional()
339
+ });
340
+ var shopifyProductRowSchema = z.object({
341
+ productId: z.string(),
342
+ title: z.string(),
343
+ price: shopifyPriceRangeSchema,
344
+ compareAt: shopifyPriceRangeSchema.optional(),
345
+ stock: shopifyStockStateSchema,
346
+ /** "<out>/<all>" options out of stock, when partly out. */
347
+ optionsOut: z.string().optional(),
348
+ availability: shopifyAvailabilitySchema.optional(),
349
+ status: shopifyProductStatusSchema.optional(),
350
+ photo: z.enum(["stored", "adding", "none"]),
351
+ // --full
352
+ vendor: z.string().optional(),
353
+ productType: z.string().optional(),
354
+ variantCount: z.number().optional(),
355
+ imageUrl: z.string().optional(),
356
+ visibility: shopifyVisibilitySchema.optional()
357
+ });
358
+ var shopifyProductsResponseSchema = z.object({
359
+ store: shopifyStoreStateSchema,
360
+ products: z.array(shopifyProductRowSchema),
361
+ nextCursor: z.string().nullable()
362
+ });
363
+ var outOptionSchema = z.object({ variantId: z.string(), title: z.string(), sku: z.string().optional() });
364
+ var countryPriceSchema = z.object({
365
+ name: z.string(),
366
+ price: shopifyPriceSchema,
367
+ compareAtPrice: shopifyPriceSchema.optional()
368
+ });
369
+ var shopifyVariantSchema = z.object({
370
+ variantId: z.string(),
371
+ title: z.string(),
372
+ price: shopifyPriceSchema,
373
+ compareAtPrice: shopifyPriceSchema.optional(),
374
+ stock: shopifyOptionStockSchema,
375
+ quantity: z.number().optional(),
376
+ /** The option's buy link for ads, emails and messages (a landing places `<ShopifyProduct>` instead): never build a Shopify link by hand. Absent when the option can't be bought. */
377
+ buyUrl: z.string().optional(),
378
+ buyUrlKind: shopifyBuyDestinationSchema.optional(),
379
+ /** Why there is no buy link. */
380
+ unavailable: z.enum(["not_for_sale", "out_of_stock"]).optional(),
381
+ notes: z.array(shopifyBuyNoteSchema).optional(),
382
+ // --full
383
+ checkout: z.string().optional(),
384
+ storeCart: z.string().optional(),
385
+ productPage: z.string().optional(),
386
+ weight: z.string().optional(),
387
+ unitPrice: z.string().optional(),
388
+ requiresShipping: z.boolean().optional(),
389
+ taxable: z.boolean().optional(),
390
+ sku: z.string().optional(),
391
+ barcode: z.string().optional(),
392
+ selectedOptions: z.array(z.object({ name: z.string(), value: z.string() })).optional(),
393
+ feedId: z.string().optional(),
394
+ inventoryPolicy: z.enum(["DENY", "CONTINUE"]).optional(),
395
+ prices: z.array(countryPriceSchema).optional()
396
+ });
397
+ var shopifyProductVideoSchema = z.object({
398
+ videoId: z.string(),
399
+ playbackId: z.string(),
400
+ alt: z.string().optional(),
401
+ /** Seconds. */
402
+ duration: z.number().optional()
403
+ });
404
+ var shopifyFieldRefSchema = z.object({
405
+ kind: z.enum(["image", "file", "video", "product", "variant", "collection", "metaobject", "other"]),
406
+ id: z.string(),
407
+ url: z.string().optional(),
408
+ mimeType: z.string().optional(),
409
+ alt: z.string().optional(),
410
+ /** A video's preview image; its `url` is the video file itself. */
411
+ previewUrl: z.string().optional(),
412
+ /** A product's or a collection's title, from Baker's copy. */
413
+ title: z.string().optional(),
414
+ /** An image brought into the library: its imageId, to place like the product's photos. */
415
+ imageId: z.string().optional(),
416
+ /** A custom content entry (a size chart, a care guide): its name and fields. */
417
+ entry: z.object({
418
+ name: z.string(),
419
+ fields: z.array(z.object({ name: z.string(), text: z.string(), truncated: z.literal(true).optional() }))
420
+ }).optional()
421
+ });
422
+ var shopifyProductFieldSchema = z.object({
423
+ name: z.string(),
424
+ /** `namespace.key`: what `--field` takes when two fields share a name. */
425
+ key: z.string(),
426
+ type: z.string(),
427
+ owner: z.enum(["product", "option"]),
428
+ variantId: z.string().optional(),
429
+ text: z.string(),
430
+ html: z.string().optional(),
431
+ refs: z.array(shopifyFieldRefSchema).optional(),
432
+ refsMore: z.number().optional(),
433
+ /** The text or the list is longer than shown: `--field <key>` returns it whole. */
434
+ truncated: z.literal(true).optional(),
435
+ /** The store owner hasn't let Baker read what this field points at yet. */
436
+ unreadable: z.literal(true).optional()
437
+ });
438
+ var shopifyProductContentSchema = z.object({
439
+ descriptionHtml: z.string(),
440
+ descriptionTruncated: z.literal(true).optional(),
441
+ category: z.string().optional(),
442
+ giftCard: z.literal(true).optional(),
443
+ fields: z.array(shopifyProductFieldSchema),
444
+ rating: z.object({ value: z.number(), scaleMax: z.number(), count: z.number().optional() }).optional(),
445
+ /** Subscriptions the product can be bought with; `subscriptionOnly` when only by subscription. */
446
+ subscriptions: z.array(
447
+ z.object({ name: z.string(), plans: z.array(z.object({ name: z.string(), category: z.string().optional() })) })
448
+ ).optional(),
449
+ subscriptionOnly: z.literal(true).optional(),
450
+ /** YouTube or Vimeo videos and 3D models: links with their preview image. */
451
+ mediaLinks: z.array(
452
+ z.object({
453
+ kind: z.enum(["external_video", "model_3d"]),
454
+ url: z.string(),
455
+ embedUrl: z.string().optional(),
456
+ previewUrl: z.string().optional(),
457
+ alt: z.string().optional()
458
+ })
459
+ ),
460
+ bundle: z.array(z.object({ productId: z.string(), title: z.string().optional(), quantity: z.number() })).optional(),
461
+ combinedListing: z.object({ role: z.string(), parentProductId: z.string().optional(), childProductIds: z.array(z.string()) }).optional(),
462
+ translations: z.array(
463
+ z.object({
464
+ locale: z.string(),
465
+ title: z.string().optional(),
466
+ description: z.string().optional(),
467
+ seoTitle: z.string().optional(),
468
+ seoDescription: z.string().optional(),
469
+ options: z.array(
470
+ z.object({
471
+ name: z.string(),
472
+ translatedName: z.string().optional(),
473
+ values: z.array(z.object({ value: z.string(), translated: z.string().optional() }))
474
+ })
475
+ ),
476
+ truncated: z.literal(true).optional()
477
+ })
478
+ ),
479
+ /** Content the store owner hasn't let Baker read yet. */
480
+ unreadable: z.array(shopifyUnreadablePartSchema).optional(),
481
+ /** Baker hasn't read this product's details yet, or Shopify didn't send some of them. */
482
+ pending: z.literal(true).optional(),
483
+ failure: z.string().optional()
484
+ });
485
+ var shopifyProductDetailSchema = z.object({
486
+ productId: z.string(),
487
+ title: z.string(),
488
+ handle: z.string(),
489
+ /** The product's page in Baker — the link to share with the user. */
490
+ url: z.string(),
491
+ /** The product's page on the online store: to look at, never a buy button's link (that is each option's `buyUrl`, or `<ShopifyProduct>` on a landing). */
492
+ storeUrl: z.string().optional(),
493
+ availability: shopifyAvailabilitySchema.optional(),
494
+ status: shopifyProductStatusSchema.optional(),
495
+ vendor: z.string(),
496
+ productType: z.string(),
497
+ price: shopifyPriceRangeSchema,
498
+ compareAt: shopifyPriceRangeSchema.optional(),
499
+ stock: z.object({ state: shopifyStockStateSchema, optionsOut: z.number(), options: z.number() }),
500
+ outOptions: z.array(outOptionSchema),
501
+ outOptionsMore: z.number().optional(),
502
+ options: z.array(z.object({ name: z.string(), values: z.array(z.string()) })),
503
+ variants: z.array(shopifyVariantSchema),
504
+ variantTotal: z.number(),
505
+ variantsNextCursor: z.string().optional(),
506
+ photos: z.array(z.object({ imageId: z.string(), url: z.string(), alt: z.string().optional() })),
507
+ photosAdding: z.number(),
508
+ photosNotBrought: z.number(),
509
+ collections: z.array(z.object({ collectionId: z.string(), title: z.string() })),
510
+ description: z.string(),
511
+ descriptionTruncated: z.literal(true).optional(),
512
+ tags: z.array(z.string()),
513
+ pricesByCountry: z.array(
514
+ z.object({
515
+ name: z.string(),
516
+ price: shopifyPriceRangeSchema,
517
+ countries: z.array(z.string()),
518
+ moreCountries: z.number().optional()
519
+ })
520
+ ).optional(),
521
+ /** When Baker last read this product from the store. */
522
+ observedAt: z.string(),
523
+ /** What else Baker keeps about it, in a line: "formatted description, 2 videos, Size chart, 4.7★ from 120 reviews". */
524
+ contentSummary: z.string().optional(),
525
+ /** Its Shopify videos in the video library: place one with `<Video playbackId=…/>`. */
526
+ videos: z.array(shopifyProductVideoSchema),
527
+ videosAdding: z.number(),
528
+ videosNotBrought: z.number(),
529
+ /** Where the buy links send a shopper, and how many each puts in the cart. */
530
+ buyDestination: shopifyBuyDestinationSchema,
531
+ quantity: z.number(),
532
+ /** --content (or --full): the whole of it. */
533
+ content: shopifyProductContentSchema.optional(),
534
+ /** --field: one custom field, whole. */
535
+ field: shopifyProductFieldSchema.optional(),
536
+ // --full
537
+ visibility: shopifyVisibilitySchema.optional(),
538
+ seo: z.object({ title: z.string().optional(), description: z.string().optional() }).optional(),
539
+ category: z.string().optional(),
540
+ adminUrl: z.string().optional()
541
+ });
542
+ var shopifyProductResponseSchema = z.object({
543
+ store: shopifyStoreStateSchema,
544
+ product: shopifyProductDetailSchema
545
+ });
546
+ var shopifyCollectionRowSchema = z.object({
547
+ collectionId: z.string(),
548
+ title: z.string(),
549
+ handle: z.string(),
550
+ /** Shopify's count at Baker's last read. */
551
+ products: z.number(),
552
+ onStore: z.boolean().optional(),
553
+ // --full
554
+ description: z.string().optional(),
555
+ imageUrl: z.string().optional(),
556
+ seo: z.object({ title: z.string().optional(), description: z.string().optional() }).optional()
557
+ });
558
+ var shopifyCollectionsResponseSchema = z.object({
559
+ store: shopifyStoreStateSchema,
560
+ collections: z.array(shopifyCollectionRowSchema)
561
+ });
562
+ var shopifyOutOfStockRowSchema = z.object({
563
+ productId: z.string(),
564
+ title: z.string(),
565
+ options: z.number(),
566
+ // --full
567
+ url: z.string().optional()
568
+ });
569
+ var shopifyPartlyOutRowSchema = z.object({
570
+ productId: z.string(),
571
+ title: z.string(),
572
+ optionsOut: z.number(),
573
+ options: z.number(),
574
+ outOptions: z.array(outOptionSchema),
575
+ outOptionsMore: z.number().optional(),
576
+ // --full
577
+ url: z.string().optional()
578
+ });
579
+ var shopifyStockResponseSchema = z.object({
580
+ store: shopifyStoreStateSchema,
581
+ outOfStock: z.array(shopifyOutOfStockRowSchema),
582
+ partlyOut: z.array(shopifyPartlyOutRowSchema),
583
+ /** More products in that group than `limit`. */
584
+ more: z.object({ outOfStock: z.boolean(), partlyOut: z.boolean() })
585
+ });
586
+ var shopifySalesBySchema = z.enum([
587
+ "day",
588
+ "product",
589
+ "campaign",
590
+ "landing",
591
+ "weekday",
592
+ "hour",
593
+ "month",
594
+ "month_of_year"
595
+ ]);
596
+ var SHOPIFY_SALES_TIMING_BY = ["weekday", "hour", "month", "month_of_year"];
597
+ var SHOPIFY_SALES_MAX_DAYS = 395;
598
+ var SHOPIFY_SALES_TIMING_MAX_DAYS = 3650;
599
+ var shopifySalesRequestSchema = z.object({
600
+ days: z.coerce.number().int().min(1).max(SHOPIFY_SALES_TIMING_MAX_DAYS).optional(),
601
+ /** Every order the store has (timing breakdowns only). */
602
+ all: flag.optional(),
603
+ by: shopifySalesBySchema.optional(),
604
+ limit: z.coerce.number().int().min(1).max(100).optional(),
605
+ full: flag.optional()
606
+ }).superRefine((request, ctx) => {
607
+ if (SHOPIFY_SALES_TIMING_BY.includes(request.by ?? "day")) return;
608
+ if (request.all) {
609
+ ctx.addIssue({
610
+ code: "custom",
611
+ path: ["all"],
612
+ message: "all is only for --by weekday, hour, month or month_of_year"
613
+ });
614
+ }
615
+ if ((request.days ?? 0) > SHOPIFY_SALES_MAX_DAYS) {
616
+ ctx.addIssue({
617
+ code: "custom",
618
+ path: ["days"],
619
+ message: `by day, product, campaign or landing reads up to ${SHOPIFY_SALES_MAX_DAYS} days`
620
+ });
621
+ }
622
+ });
623
+ var shopifySalesCreditSchema = z.enum([
624
+ "campaign",
625
+ "order",
626
+ "landing",
627
+ "before_baker",
628
+ "unattributed",
629
+ "other"
630
+ ]);
631
+ var shopifySalesRowSchema = z.object({
632
+ /** A campaign, a landing's name, a day (YYYY-MM-DD) or a product's title. */
633
+ label: z.string(),
634
+ credit: shopifySalesCreditSchema.optional(),
635
+ orders: z.number(),
636
+ sales: z.number(),
637
+ refunded: z.number(),
638
+ net: z.number(),
639
+ /** By day: money received after the sale (an edit paid for). */
640
+ added: z.number().optional(),
641
+ /** By product: units sold and given back. */
642
+ units: z.number().optional(),
643
+ refundedUnits: z.number().optional(),
644
+ /** By weekday, hour or month: the buyers who ordered. */
645
+ buyers: z.number().optional(),
646
+ // --full
647
+ source: z.string().optional(),
648
+ medium: z.string().optional(),
649
+ campaign: z.string().optional(),
650
+ /** The ad platform of the click that brought the buyer: google, meta, linkedin, … */
651
+ clickPlatform: z.string().optional(),
652
+ landingId: z.string().optional(),
653
+ productId: z.string().optional()
654
+ });
655
+ var shopifySalesTotalsSchema = z.object({
656
+ orders: z.number(),
657
+ /** What the orders came to, with money added after the sale. */
658
+ sales: z.number(),
659
+ added: z.number(),
660
+ refunded: z.number(),
661
+ net: z.number()
662
+ });
663
+ var shopifySalesReportingSchema = z.enum(["shopify_channel", "baker", "undecided"]);
664
+ var shopifySalesResponseSchema = z.object({
665
+ store: shopifyStoreStateSchema,
666
+ /** `days` is "all" with `all`; `timezone` names the store's time a timing breakdown was read in. */
667
+ window: z.object({
668
+ days: z.union([z.number(), z.literal("all")]),
669
+ from: z.string(),
670
+ to: z.string(),
671
+ timezone: z.string().optional()
672
+ }),
673
+ by: shopifySalesBySchema,
674
+ /** The store's currency; null when nothing sold in the window. */
675
+ currency: z.string().nullable(),
676
+ totals: shopifySalesTotalsSchema,
677
+ rows: z.array(shopifySalesRowSchema),
678
+ /** Sales in another currency, which are never added to these. */
679
+ otherCurrencies: z.array(z.object({ currency: z.string(), net: z.number() })).optional(),
680
+ salesReporting: z.object({ meta: shopifySalesReportingSchema, google: shopifySalesReportingSchema }),
681
+ /** The order history Baker imports when a store connects: still coming in, done, or failed. */
682
+ history: z.enum(["importing", "done", "failed", "none"]),
683
+ /** Baker could not read this store's orders (the store must be reconnected). */
684
+ ordersUnreadable: z.literal(true).optional()
685
+ });
686
+
687
+ // ../api/src/shopify/buyers.ts
688
+ var flag2 = z2.enum(["true", "false"]).transform((value) => value === "true");
689
+ var SHOPIFY_STORE_WINDOW_MAX_DAYS = 3650;
690
+ var days = z2.coerce.number().int().min(1).max(SHOPIFY_STORE_WINDOW_MAX_DAYS);
691
+ var countryCode = z2.string().regex(/^[A-Za-z]{2}$/, "a two-letter country code, like DE").transform((value) => value.toUpperCase());
692
+ var limit = z2.coerce.number().int().min(1).max(100);
693
+ var windowRequest = {
694
+ days: days.optional(),
695
+ /** Every order the store has. */
696
+ all: flag2.optional(),
697
+ country: countryCode.optional(),
698
+ full: flag2.optional()
699
+ };
700
+ var shopifyStoreWindowSchema = z2.object({
701
+ from: z2.string().nullable(),
702
+ to: z2.string(),
703
+ days: z2.union([z2.number(), z2.literal("all")]),
704
+ timezone: z2.string()
705
+ });
706
+ var shopifyBuyerHistorySchema = z2.object({
707
+ state: z2.enum(["importing", "done", "failed", "none"]),
708
+ reach: z2.enum(["all", "60_days"]).nullable(),
709
+ /** The day of the oldest order Baker holds (YYYY-MM-DD). */
710
+ from: z2.string().nullable(),
711
+ /** Orders brought in so far. */
712
+ orders: z2.number()
713
+ });
714
+ var shopifyBuyerBasisSchema = z2.enum(["all_orders", "last_60_days"]);
715
+ var answerCommon = {
716
+ store: shopifyStoreStateSchema,
717
+ window: shopifyStoreWindowSchema,
718
+ /** The store's currency; null when it has no orders yet. */
719
+ currency: z2.string().nullable(),
720
+ /** Orders in another currency (the store changed currency), never added to these. */
721
+ otherCurrencies: z2.array(z2.object({ currency: z2.string(), orders: z2.number(), net: z2.number() })).optional(),
722
+ history: shopifyBuyerHistorySchema,
723
+ basis: shopifyBuyerBasisSchema
724
+ };
725
+ var shopifyPlaceSchema = z2.object({
726
+ country: z2.string(),
727
+ countryName: z2.string(),
728
+ region: z2.string(),
729
+ city: z2.string()
730
+ });
731
+ var shopifyBuyerSegmentSchema = z2.enum(["best", "loyal", "at_risk", "new", "one_time"]);
732
+ var shopifySegmentThresholdsSchema = z2.object({
733
+ /** The store's typical days between a repeat buyer's orders (90 with too few to go on). */
734
+ gapDays: z2.number(),
735
+ /** The lifetime net that puts a buyer in the top fifth. */
736
+ netP80: z2.number()
737
+ });
738
+ var shopifySegmentRowSchema = z2.object({
739
+ segment: shopifyBuyerSegmentSchema,
740
+ label: z2.string(),
741
+ /** The rule in words, with this store's numbers. */
742
+ rule: z2.string(),
743
+ buyers: z2.number(),
744
+ /** Of every buyer, 0–1. */
745
+ share: z2.number(),
746
+ net: z2.number(),
747
+ averageOrders: z2.number(),
748
+ averageDaysSinceLastOrder: z2.number(),
749
+ topCountries: z2.array(z2.object({ code: z2.string(), name: z2.string(), buyers: z2.number() })),
750
+ /** The products their first orders held most often. */
751
+ topFirstProducts: z2.array(z2.object({ title: z2.string(), buyers: z2.number(), productId: z2.string().optional() }))
752
+ });
753
+ var shopifySegmentsRequestSchema = z2.object({ ...windowRequest });
754
+ var shopifySegmentsResponseSchema = z2.object({
755
+ ...answerCommon,
756
+ thresholds: shopifySegmentThresholdsSchema,
757
+ buyers: z2.number(),
758
+ segments: z2.array(shopifySegmentRowSchema)
759
+ });
760
+ var shopifyBuyersSortSchema = z2.enum(["net", "orders", "recent", "first"]);
761
+ var shopifyAcquisitionBySchema = z2.enum(["campaign", "channel"]);
762
+ var shopifyBuyersRequestSchema = z2.object({
763
+ ...windowRequest,
764
+ segment: shopifyBuyerSegmentSchema.optional(),
765
+ /** Lifetime value by the campaign or channel of each buyer's first order. */
766
+ by: shopifyAcquisitionBySchema.optional(),
767
+ /** Only buyers whose first order came through this channel (a key from `by=channel`). */
768
+ channel: z2.string().max(200).optional(),
769
+ sort: shopifyBuyersSortSchema.optional(),
770
+ limit: limit.optional(),
771
+ offset: z2.coerce.number().int().min(0).max(1e5).optional()
772
+ });
773
+ var shopifyBuyersSummarySchema = z2.object({
774
+ /** Buyers who ordered in the period. */
775
+ buyers: z2.number(),
776
+ /** Of them, whose first order was in the period. */
777
+ newBuyers: z2.number(),
778
+ returningBuyers: z2.number(),
779
+ /** Orders with no customer: counted in money, never a buyer. */
780
+ guestOrders: z2.number(),
781
+ orders: z2.number(),
782
+ net: z2.number(),
783
+ averageOrder: z2.number(),
784
+ /** Orders that were their buyer's first, and the rest (Shopify's new vs returning). */
785
+ newOrders: z2.number(),
786
+ returningOrders: z2.number(),
787
+ newNet: z2.number(),
788
+ returningNet: z2.number(),
789
+ /** Of every buyer up to the period's end, the share who ordered again (0–1). */
790
+ repeatRate: z2.number(),
791
+ /** Median days from a buyer's first order to their second; null when nobody ordered twice. */
792
+ medianDaysToSecond: z2.number().nullable()
793
+ });
794
+ var shopifyCameFromSchema = z2.object({
795
+ channel: z2.string(),
796
+ label: z2.string(),
797
+ campaign: z2.string().optional(),
798
+ // --full
799
+ source: z2.string().optional(),
800
+ medium: z2.string().optional(),
801
+ clickPlatform: z2.string().optional(),
802
+ landingId: z2.string().optional()
803
+ });
804
+ var shopifyBuyerRowSchema = z2.object({
805
+ number: z2.number(),
806
+ /** "Customer #1042". */
807
+ label: z2.string(),
808
+ place: shopifyPlaceSchema.nullable(),
809
+ orders: z2.number(),
810
+ net: z2.number(),
811
+ firstOrderAt: z2.string(),
812
+ lastOrderAt: z2.string(),
813
+ segment: shopifyBuyerSegmentSchema,
814
+ cameFrom: shopifyCameFromSchema,
815
+ // --full
816
+ gross: z2.number().optional(),
817
+ refunded: z2.number().optional(),
818
+ ordersWithDiscount: z2.number().optional(),
819
+ averageDaysBetweenOrders: z2.number().nullable().optional()
820
+ });
821
+ var shopifyAcquisitionRowSchema = z2.object({
822
+ key: z2.string(),
823
+ label: z2.string(),
824
+ buyers: z2.number(),
825
+ /** What their first orders came to. */
826
+ firstNet: z2.number(),
827
+ /** What they have spent since, to date. */
828
+ lifetimeNet: z2.number(),
829
+ lifetimePerBuyer: z2.number(),
830
+ averageOrders: z2.number(),
831
+ repeatRate: z2.number(),
832
+ // --full
833
+ source: z2.string().optional(),
834
+ medium: z2.string().optional(),
835
+ campaign: z2.string().optional(),
836
+ clickPlatform: z2.string().optional()
837
+ });
838
+ var shopifyBuyersResponseSchema = z2.object({
839
+ ...answerCommon,
840
+ summary: shopifyBuyersSummarySchema,
841
+ thresholds: shopifySegmentThresholdsSchema,
842
+ segments: z2.array(z2.object({ segment: shopifyBuyerSegmentSchema, label: z2.string(), buyers: z2.number() })),
843
+ /** Buyers matching the filters, before the limit. */
844
+ total: z2.number(),
845
+ buyers: z2.array(shopifyBuyerRowSchema),
846
+ acquisition: z2.object({ by: shopifyAcquisitionBySchema, rows: z2.array(shopifyAcquisitionRowSchema) }).optional()
847
+ });
848
+ 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));
849
+ var shopifyBuyerRequestSchema = z2.object({ number: shopifyBuyerNumberSchema, full: flag2.optional() });
850
+ var SHOPIFY_BUYER_GONE_MESSAGE = "This buyer is no longer in Baker.";
851
+ var shopifyBuyerOrderSchema = z2.object({
852
+ name: z2.string(),
853
+ /** When it was placed, in the store's time (YYYY-MM-DD HH:MM). */
854
+ placedAt: z2.string(),
855
+ net: z2.number(),
856
+ gross: z2.number(),
857
+ refunded: z2.number(),
858
+ products: z2.array(z2.object({ title: z2.string(), quantity: z2.number() })),
859
+ discountCodes: z2.array(z2.string()),
860
+ place: shopifyPlaceSchema.nullable(),
861
+ /** Placed before Baker measured the store: counted, credited to no campaign. */
862
+ beforeBaker: z2.boolean(),
863
+ // --full
864
+ orderId: z2.string().optional(),
865
+ items: z2.array(z2.object({ productId: z2.string(), variantId: z2.string(), quantity: z2.number(), amount: z2.number() })).optional(),
866
+ refundedItems: z2.array(z2.object({ productId: z2.string(), variantId: z2.string(), quantity: z2.number(), amount: z2.number() })).optional(),
867
+ cameFrom: shopifyCameFromSchema.optional()
868
+ });
869
+ var shopifyBuyerProfileSchema = z2.object({
870
+ number: z2.number(),
871
+ label: z2.string(),
872
+ place: shopifyPlaceSchema.nullable(),
873
+ orders: z2.number(),
874
+ gross: z2.number(),
875
+ refunded: z2.number(),
876
+ net: z2.number(),
877
+ averageOrder: z2.number(),
878
+ /** Average days between their orders; null with one order. */
879
+ averageDaysBetweenOrders: z2.number().nullable(),
880
+ firstOrderAt: z2.string(),
881
+ lastOrderAt: z2.string(),
882
+ ordersWithDiscount: z2.number(),
883
+ segment: shopifyBuyerSegmentSchema,
884
+ segmentLabel: z2.string(),
885
+ cameFrom: shopifyCameFromSchema
886
+ });
887
+ var shopifyBuyerResponseSchema = z2.object({
888
+ ...answerCommon,
889
+ buyer: shopifyBuyerProfileSchema,
890
+ /** Newest first. */
891
+ orders: z2.array(shopifyBuyerOrderSchema),
892
+ products: z2.array(
893
+ z2.object({ title: z2.string(), quantity: z2.number(), orders: z2.number(), productId: z2.string().optional() })
894
+ )
895
+ });
896
+ var shopifyCountriesRequestSchema = z2.object({
897
+ ...windowRequest,
898
+ /** Inside one region of `country` (its code or name): its cities. */
899
+ region: z2.string().max(100).optional(),
900
+ /** Only orders holding this product (its id or handle); money is that product's lines. */
901
+ product: z2.string().max(255).optional(),
902
+ limit: limit.optional()
903
+ });
904
+ var shopifyPlaceRowSchema = z2.object({
905
+ /** A country code, a region code (or name) or a city. */
906
+ key: z2.string(),
907
+ name: z2.string(),
908
+ orders: z2.number(),
909
+ buyers: z2.number(),
910
+ newBuyers: z2.number(),
911
+ net: z2.number(),
912
+ averageOrder: z2.number(),
913
+ /** Of the total's net, 0–1. */
914
+ share: z2.number(),
915
+ /** Net against the period before; null when there was none before. */
916
+ growth: z2.number().nullable(),
917
+ ordersGrowth: z2.number().nullable(),
918
+ /** The currency its buyers paid in most. */
919
+ paidIn: z2.string(),
920
+ // --full
921
+ previousNet: z2.number().optional(),
922
+ previousOrders: z2.number().optional()
923
+ });
924
+ var shopifyCountriesResponseSchema = z2.object({
925
+ ...answerCommon,
926
+ level: z2.enum(["country", "region", "city"]),
927
+ country: z2.string().optional(),
928
+ region: z2.string().optional(),
929
+ product: z2.object({ id: z2.string(), title: z2.string() }).optional(),
930
+ total: shopifyPlaceRowSchema,
931
+ /** Named places, best first; with `unknown` and `other` they add up to `total`. */
932
+ rows: z2.array(shopifyPlaceRowSchema),
933
+ /** Orders whose address Baker could not read. Always shown, never folded into `other`. */
934
+ unknown: shopifyPlaceRowSchema.nullable(),
935
+ /** Places past the limit, together. */
936
+ other: shopifyPlaceRowSchema.nullable()
937
+ });
938
+ var shopifyBestSellersViewSchema = z2.enum(["products", "variants", "trend", "together", "repeat", "momentum"]);
939
+ var shopifyBestSellersSortSchema = z2.enum(["units", "revenue", "orders"]);
940
+ var shopifyBestSellersRequestSchema = z2.object({
941
+ ...windowRequest,
942
+ view: shopifyBestSellersViewSchema.optional(),
943
+ sort: shopifyBestSellersSortSchema.optional(),
944
+ /** Only this product (its id or handle): its variants, its trend. */
945
+ product: z2.string().max(255).optional(),
946
+ limit: limit.optional()
947
+ });
948
+ var shopifyBestSellerRowSchema = z2.object({
949
+ title: z2.string(),
950
+ /** The option's own title, by variant. */
951
+ variantTitle: z2.string().optional(),
952
+ units: z2.number(),
953
+ refundedUnits: z2.number(),
954
+ orders: z2.number(),
955
+ buyers: z2.number(),
956
+ revenue: z2.number(),
957
+ refunded: z2.number(),
958
+ /** Refunded units of units ordered, 0–1. */
959
+ refundRate: z2.number(),
960
+ /** Units against the period before; null when none sold before. */
961
+ growth: z2.number().nullable(),
962
+ // --full
963
+ productId: z2.string().optional(),
964
+ variantId: z2.string().optional(),
965
+ previousUnits: z2.number().optional(),
966
+ previousRevenue: z2.number().optional()
967
+ });
968
+ var shopifyTrendRowSchema = z2.object({
969
+ /** YYYY-MM, in the store's time. */
970
+ month: z2.string(),
971
+ title: z2.string(),
972
+ units: z2.number(),
973
+ revenue: z2.number(),
974
+ productId: z2.string().optional()
975
+ });
976
+ var shopifyTogetherRowSchema = z2.object({
977
+ first: z2.string(),
978
+ second: z2.string(),
979
+ /** Orders holding both. */
980
+ orders: z2.number(),
981
+ /** Of the orders holding the first, the share that also held the second (0–1). */
982
+ confidence: z2.number(),
983
+ firstId: z2.string().optional(),
984
+ secondId: z2.string().optional()
985
+ });
986
+ var shopifyRepeatRowSchema = z2.object({
987
+ title: z2.string(),
988
+ /** Buyers whose first order held it. */
989
+ buyers: z2.number(),
990
+ /** Of them, who ordered again. */
991
+ buyersAgain: z2.number(),
992
+ share: z2.number(),
993
+ medianDaysToSecond: z2.number().nullable(),
994
+ productId: z2.string().optional()
995
+ });
996
+ var shopifyMomentumRowSchema = z2.object({
997
+ title: z2.string(),
998
+ units: z2.number(),
999
+ previousUnits: z2.number(),
1000
+ /** Units against the period before (−0.4 = 40% fewer). */
1001
+ change: z2.number(),
1002
+ revenue: z2.number(),
1003
+ productId: z2.string().optional()
1004
+ });
1005
+ var shopifyBestSellersResponseSchema = z2.object({
1006
+ ...answerCommon,
1007
+ view: shopifyBestSellersViewSchema,
1008
+ sort: shopifyBestSellersSortSchema,
1009
+ product: z2.object({ id: z2.string(), title: z2.string() }).optional(),
1010
+ products: z2.array(shopifyBestSellerRowSchema).optional(),
1011
+ variants: z2.array(shopifyBestSellerRowSchema).optional(),
1012
+ trend: z2.array(shopifyTrendRowSchema).optional(),
1013
+ together: z2.array(shopifyTogetherRowSchema).optional(),
1014
+ repeat: z2.array(shopifyRepeatRowSchema).optional(),
1015
+ momentum: z2.array(shopifyMomentumRowSchema).optional()
1016
+ });
1017
+ var shopifyCohortsRequestSchema = z2.object({
1018
+ months: z2.coerce.number().int().min(3).max(36).optional(),
1019
+ country: countryCode.optional(),
1020
+ full: flag2.optional()
1021
+ });
1022
+ var shopifyCohortMonthSchema = z2.object({
1023
+ /** Months since the first order: 0 is the month they first bought. */
1024
+ offset: z2.number(),
1025
+ activeBuyers: z2.number(),
1026
+ orders: z2.number(),
1027
+ net: z2.number(),
1028
+ /** Of the cohort, who ordered in that month (0–1). */
1029
+ retention: z2.number(),
1030
+ /** What the cohort spent up to that month, per buyer. */
1031
+ cumulativeNetPerBuyer: z2.number()
1032
+ });
1033
+ var shopifyCohortSchema = z2.object({
1034
+ /** The month of their first order, YYYY-MM in the store's time. */
1035
+ month: z2.string(),
1036
+ size: z2.number(),
1037
+ /** Who ordered again 1, 2, 3, 6 and 12 months after; null for a month not reached yet. */
1038
+ retention: z2.object({
1039
+ m1: z2.number().nullable(),
1040
+ m2: z2.number().nullable(),
1041
+ m3: z2.number().nullable(),
1042
+ m6: z2.number().nullable(),
1043
+ m12: z2.number().nullable()
1044
+ }),
1045
+ netPerBuyerAt12: z2.number().nullable(),
1046
+ // --full
1047
+ matrix: z2.array(shopifyCohortMonthSchema).optional()
1048
+ });
1049
+ var shopifyCohortsResponseSchema = z2.object({
1050
+ ...answerCommon,
1051
+ months: z2.number(),
1052
+ cohorts: z2.array(shopifyCohortSchema)
1053
+ });
1054
+
1055
+ // ../api/src/shopify/writes.ts
1056
+ import { z as z3 } from "zod";
1057
+ var SHOPIFY_TEMP_REF_PREFIX = "shopify_temp_";
1058
+ var SHOPIFY_PERMISSION_NEEDED_PREFIX = "Baker isn't allowed to change this Shopify store yet";
1059
+ var shopifyWriteEntitySchema = z3.enum([
1060
+ "product",
1061
+ "variant",
1062
+ "metafield",
1063
+ "collection",
1064
+ "discountCode",
1065
+ "urlRedirect",
1066
+ "marketingActivity",
1067
+ "webPixel",
1068
+ "signupOffer"
1069
+ ]);
1070
+ var shopifyDraftOpKindSchema = z3.enum([
1071
+ "shopify.product.update",
1072
+ "shopify.variant.update",
1073
+ "shopify.metafield.set",
1074
+ "shopify.metafield.delete",
1075
+ "shopify.collection.create",
1076
+ "shopify.collection.publish",
1077
+ "shopify.collection.unpublish",
1078
+ "shopify.collection.update",
1079
+ "shopify.collection.delete",
1080
+ "shopify.collection.addProducts",
1081
+ "shopify.collection.removeProducts",
1082
+ "shopify.discountCode.create",
1083
+ "shopify.discountCode.deactivate",
1084
+ "shopify.discountCode.schedule",
1085
+ "shopify.urlRedirect.create",
1086
+ "shopify.urlRedirect.delete",
1087
+ "shopify.marketingActivity.upsert",
1088
+ "shopify.marketingActivity.delete",
1089
+ "shopify.webPixel.update",
1090
+ "shopify.signupOffer.set",
1091
+ "shopify.signupOffer.end"
1092
+ ]);
1093
+ var shopifyDecimalSchema = z3.string().regex(/^\d{1,12}(\.\d{1,4})?$/, 'write amounts as a decimal string, e.g. "19.95"');
1094
+ var tagSchema = z3.string().trim().min(1).max(255);
1095
+ var seoSchema = z3.object({ title: z3.string().max(255).optional(), description: z3.string().max(320).optional() }).strict();
1096
+ var SHOPIFY_DESCRIPTION_MAX_CHARS = 2e5;
1097
+ var descriptionHtmlSchema = z3.string().max(SHOPIFY_DESCRIPTION_MAX_CHARS);
1098
+ var productUpdateSchema = z3.object({
1099
+ title: z3.string().trim().min(1).max(255).optional(),
1100
+ descriptionHtml: descriptionHtmlSchema.optional(),
1101
+ seo: seoSchema.optional(),
1102
+ /** The WHOLE tag list after the change (Shopify replaces it). `addTags`/`removeTags` are resolved against the live tags. */
1103
+ tags: z3.array(tagSchema).max(250).optional(),
1104
+ addTags: z3.array(tagSchema).max(250).optional(),
1105
+ removeTags: z3.array(tagSchema).max(250).optional(),
1106
+ productType: z3.string().max(255).optional()
1107
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change").refine(
1108
+ (payload) => payload.tags === void 0 || payload.addTags === void 0 && payload.removeTags === void 0,
1109
+ "give either the whole `tags` list or `addTags`/`removeTags`, not both"
1110
+ );
1111
+ var shopifyWeightUnitSchema = z3.enum(["GRAMS", "KILOGRAMS", "OUNCES", "POUNDS"]);
1112
+ var variantUpdateSchema = z3.object({
1113
+ barcode: z3.string().trim().max(255).optional(),
1114
+ sku: z3.string().trim().max(255).optional(),
1115
+ weight: z3.object({ value: z3.number().nonnegative(), unit: shopifyWeightUnitSchema }).strict().optional(),
1116
+ price: shopifyDecimalSchema.optional(),
1117
+ /** `null` clears the sale ("compare at") price. */
1118
+ compareAtPrice: shopifyDecimalSchema.nullable().optional()
1119
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
1120
+ var metafieldKeySchema = z3.string().min(2).max(64).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
1121
+ var metafieldNamespaceSchema = z3.string().min(2).max(255).regex(/^[A-Za-z0-9_-]+$/, "letters, digits, `_` and `-` only");
1122
+ var metafieldSetSchema = z3.object({
1123
+ namespace: metafieldNamespaceSchema,
1124
+ key: metafieldKeySchema,
1125
+ /** Shopify's metafield type, e.g. `single_line_text_field`, `multi_line_text_field`, `rich_text_field`, `number_integer`, `boolean`, `json`. */
1126
+ type: z3.string().min(1).max(100),
1127
+ value: z3.string().max(1e5)
1128
+ }).strict();
1129
+ var metafieldDeleteSchema = z3.object({ namespace: metafieldNamespaceSchema, key: metafieldKeySchema }).strict();
1130
+ var shopifyCollectionSortOrderSchema = z3.enum([
1131
+ "MANUAL",
1132
+ "BEST_SELLING",
1133
+ "ALPHA_ASC",
1134
+ "ALPHA_DESC",
1135
+ "PRICE_DESC",
1136
+ "PRICE_ASC",
1137
+ "CREATED",
1138
+ "CREATED_DESC"
1139
+ ]);
1140
+ var handleSchema = z3.string().min(1).max(255).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "lowercase letters, digits and single dashes, e.g. summer-snowboards");
1141
+ var SHOPIFY_COLLECTION_PRODUCTS_MAX = 250;
1142
+ var collectionCreateSchema = z3.object({
1143
+ title: z3.string().trim().min(1).max(255),
1144
+ handle: handleSchema.optional(),
1145
+ descriptionHtml: descriptionHtmlSchema.optional(),
1146
+ seo: seoSchema.optional(),
1147
+ sortOrder: shopifyCollectionSortOrderSchema.optional(),
1148
+ /** The theme template the collection page uses (the part after `collection.`). Kept when a deleted collection is put back. */
1149
+ templateSuffix: z3.string().max(255).optional(),
1150
+ /** Product ids, handles or links, in the order shown with sort order MANUAL. */
1151
+ products: z3.array(z3.string().min(1)).max(SHOPIFY_COLLECTION_PRODUCTS_MAX).optional(),
1152
+ /** Show it on the online store (a second change, staged with it). Default true. */
1153
+ onlineStore: z3.boolean().optional()
1154
+ }).strict();
1155
+ var collectionUpdateSchema = z3.object({
1156
+ title: z3.string().trim().min(1).max(255).optional(),
1157
+ descriptionHtml: descriptionHtmlSchema.optional(),
1158
+ seo: seoSchema.optional(),
1159
+ sortOrder: shopifyCollectionSortOrderSchema.optional()
1160
+ }).strict().refine((payload) => Object.keys(payload).length > 0, "name at least one thing to change");
1161
+ var collectionProductsSchema = z3.object({ products: z3.array(z3.string().min(1)).min(1).max(SHOPIFY_COLLECTION_PRODUCTS_MAX) }).strict();
1162
+ var isoDateTimeSchema = z3.iso.datetime({ offset: true });
1163
+ var discountCodeSchema = z3.string().regex(/^[A-Za-z0-9_-]{3,64}$/, "3\u201364 letters, digits, `_` or `-`, e.g. SPRING10");
1164
+ var discountValueSchema = z3.union([
1165
+ z3.object({ percentage: z3.number().min(1).max(100) }).strict(),
1166
+ z3.object({ amount: shopifyDecimalSchema }).strict()
1167
+ ]);
1168
+ var discountAppliesToSchema = z3.union([
1169
+ z3.object({ all: z3.literal(true) }).strict(),
1170
+ z3.object({ products: z3.array(z3.string().min(1)).min(1).max(100) }).strict(),
1171
+ z3.object({ collections: z3.array(z3.string().min(1)).min(1).max(100) }).strict()
1172
+ ]);
1173
+ var discountCreateSchema = z3.object({
1174
+ code: discountCodeSchema,
1175
+ /** What the store's staff see; defaults to the code. */
1176
+ title: z3.string().trim().min(1).max(255).optional(),
1177
+ value: discountValueSchema,
1178
+ appliesTo: discountAppliesToSchema,
1179
+ minimumSubtotal: shopifyDecimalSchema.optional(),
1180
+ minimumQuantity: z3.number().int().min(1).max(1e4).optional(),
1181
+ /** Default: the moment it is published. */
1182
+ startsAt: isoDateTimeSchema.optional(),
1183
+ endsAt: isoDateTimeSchema.optional(),
1184
+ usageLimit: z3.number().int().min(1).max(1e6).optional(),
1185
+ /** Default true. */
1186
+ oncePerCustomer: z3.boolean().optional(),
1187
+ combinesWith: z3.object({
1188
+ productDiscounts: z3.boolean().optional(),
1189
+ orderDiscounts: z3.boolean().optional(),
1190
+ shippingDiscounts: z3.boolean().optional()
1191
+ }).strict().optional()
1192
+ }).strict().refine(
1193
+ (payload) => payload.minimumSubtotal === void 0 || payload.minimumQuantity === void 0,
1194
+ "a discount takes a minimum amount or a minimum quantity, not both"
1195
+ );
1196
+ var discountScheduleSchema = z3.object({ startsAt: isoDateTimeSchema, endsAt: isoDateTimeSchema.nullable() }).strict();
1197
+ var storePathSchema = z3.string().min(2).max(1024).regex(/^\/[^\s]*$/, 'a path on the store starting with "/", e.g. /collections/old');
1198
+ var urlRedirectCreateSchema = z3.object({
1199
+ from: storePathSchema,
1200
+ to: z3.union([storePathSchema, z3.url({ protocol: /^https$/ })])
1201
+ }).strict();
1202
+ var shopifyMarketingPlatformSchema = z3.enum(["google-ads", "meta"]);
1203
+ var marketingUpsertSchema = z3.object({
1204
+ platform: shopifyMarketingPlatformSchema,
1205
+ accountId: z3.string().min(1).max(64),
1206
+ campaignId: z3.string().regex(/^\d+$/, "the campaign's numeric id"),
1207
+ utm: z3.object({
1208
+ campaign: z3.string().trim().min(1).max(255),
1209
+ source: z3.string().trim().min(1).max(255),
1210
+ medium: z3.string().trim().min(1).max(255)
1211
+ }).strict()
1212
+ }).strict();
1213
+ var signupOfferHandleSchema = z3.string().regex(/^[a-z0-9][a-z0-9-]{1,39}$/, "2\u201340 lowercase letters, digits and dashes, e.g. welcome-10");
1214
+ var signupOfferSetSchema = z3.object({
1215
+ /** What the store's staff see in front of each code, and the Store page's row. */
1216
+ name: z3.string().trim().min(1).max(80),
1217
+ value: discountValueSchema,
1218
+ appliesTo: discountAppliesToSchema,
1219
+ minimumSubtotal: shopifyDecimalSchema.optional(),
1220
+ /** Each code's life, from the moment it is given. */
1221
+ validForDays: z3.number().int().min(1).max(365),
1222
+ /** The offer stops giving codes after this; no code outlives it. */
1223
+ endsAt: isoDateTimeSchema.optional(),
1224
+ /** How many codes the offer gives in all. */
1225
+ maxCodes: z3.number().int().min(1).max(1e5).optional(),
1226
+ /** The start of every code (WELCOME-7Q2M9XKD); default: the handle in capitals. */
1227
+ codePrefix: z3.string().regex(/^[A-Z0-9]{2,12}$/, "2\u201312 capital letters or digits, e.g. WELCOME").optional(),
1228
+ combinesWith: z3.object({
1229
+ productDiscounts: z3.boolean().optional(),
1230
+ orderDiscounts: z3.boolean().optional(),
1231
+ shippingDiscounts: z3.boolean().optional()
1232
+ }).strict().optional()
1233
+ }).strict();
1234
+ var emptySchema = z3.object({}).strict();
1235
+ var opFields = {
1236
+ /** Optional caller-chosen ref for a create, so later ops in the batch can name it (`shopify_temp_…`). */
1237
+ ref: z3.string().startsWith(SHOPIFY_TEMP_REF_PREFIX).max(80).optional()
1238
+ };
1239
+ var target = z3.string().trim().min(1).max(2048);
1240
+ var shopifyDraftOpInputSchema = z3.discriminatedUnion("kind", [
1241
+ z3.object({ kind: z3.literal("shopify.product.update"), target, payload: productUpdateSchema, ...opFields }),
1242
+ z3.object({ kind: z3.literal("shopify.variant.update"), target, payload: variantUpdateSchema, ...opFields }),
1243
+ z3.object({ kind: z3.literal("shopify.metafield.set"), target, payload: metafieldSetSchema, ...opFields }),
1244
+ z3.object({ kind: z3.literal("shopify.metafield.delete"), target, payload: metafieldDeleteSchema, ...opFields }),
1245
+ z3.object({ kind: z3.literal("shopify.collection.create"), payload: collectionCreateSchema, ...opFields }),
1246
+ z3.object({ kind: z3.literal("shopify.collection.publish"), target, payload: emptySchema.optional(), ...opFields }),
1247
+ z3.object({ kind: z3.literal("shopify.collection.unpublish"), target, payload: emptySchema.optional(), ...opFields }),
1248
+ z3.object({ kind: z3.literal("shopify.collection.update"), target, payload: collectionUpdateSchema, ...opFields }),
1249
+ z3.object({ kind: z3.literal("shopify.collection.delete"), target, payload: emptySchema.optional(), ...opFields }),
1250
+ z3.object({
1251
+ kind: z3.literal("shopify.collection.addProducts"),
1252
+ target,
1253
+ payload: collectionProductsSchema,
1254
+ ...opFields
1255
+ }),
1256
+ z3.object({
1257
+ kind: z3.literal("shopify.collection.removeProducts"),
1258
+ target,
1259
+ payload: collectionProductsSchema,
1260
+ ...opFields
1261
+ }),
1262
+ z3.object({ kind: z3.literal("shopify.discountCode.create"), payload: discountCreateSchema, ...opFields }),
1263
+ z3.object({
1264
+ kind: z3.literal("shopify.discountCode.deactivate"),
1265
+ target,
1266
+ payload: emptySchema.optional(),
1267
+ ...opFields
1268
+ }),
1269
+ z3.object({ kind: z3.literal("shopify.discountCode.schedule"), target, payload: discountScheduleSchema, ...opFields }),
1270
+ z3.object({ kind: z3.literal("shopify.urlRedirect.create"), payload: urlRedirectCreateSchema, ...opFields }),
1271
+ z3.object({ kind: z3.literal("shopify.urlRedirect.delete"), target, payload: emptySchema.optional(), ...opFields }),
1272
+ z3.object({ kind: z3.literal("shopify.marketingActivity.upsert"), payload: marketingUpsertSchema, ...opFields }),
1273
+ z3.object({
1274
+ kind: z3.literal("shopify.marketingActivity.delete"),
1275
+ target,
1276
+ payload: emptySchema.optional(),
1277
+ ...opFields
1278
+ }),
1279
+ z3.object({ kind: z3.literal("shopify.webPixel.update"), payload: emptySchema.optional(), ...opFields }),
1280
+ z3.object({
1281
+ kind: z3.literal("shopify.signupOffer.set"),
1282
+ target: signupOfferHandleSchema,
1283
+ payload: signupOfferSetSchema,
1284
+ ...opFields
1285
+ }),
1286
+ z3.object({
1287
+ kind: z3.literal("shopify.signupOffer.end"),
1288
+ target: signupOfferHandleSchema,
1289
+ payload: emptySchema.optional(),
1290
+ ...opFields
1291
+ })
1292
+ ]);
1293
+ var PAYLOAD_KEYS = new Map(
1294
+ shopifyDraftOpInputSchema.options.map((option) => {
1295
+ const payload = option.shape.payload;
1296
+ const object = payload instanceof z3.ZodOptional ? payload.unwrap() : payload;
1297
+ return [option.shape.kind.value, object instanceof z3.ZodObject ? Object.keys(object.shape) : []];
1298
+ })
1299
+ );
1300
+ var askUserQuestionInputSchema = z3.object({
1301
+ questions: z3.array(
1302
+ z3.object({
1303
+ question: z3.string(),
1304
+ header: z3.string(),
1305
+ multiSelect: z3.boolean(),
1306
+ options: z3.array(z3.object({ label: z3.string(), description: z3.string() })).min(2)
1307
+ })
1308
+ ).min(1)
1309
+ });
1310
+ var shopifyNeedsApprovalSchema = z3.object({
1311
+ /** Pass to AskUserQuestion exactly as given. */
1312
+ askUserQuestion: askUserQuestionInputSchema,
1313
+ /** The user already said no to (at least one of) these changes: don't ask again in this turn. */
1314
+ declined: z3.boolean()
1315
+ });
1316
+ var shopifyVerificationSchema = z3.object({
1317
+ status: z3.enum(["confirmed", "drifted", "unconfirmed"]),
1318
+ checkedAt: z3.number(),
1319
+ note: z3.string().optional()
1320
+ });
1321
+ var shopifyDraftOpResultSchema = z3.object({
1322
+ status: z3.enum(["applied", "failed", "skipped"]),
1323
+ createdId: z3.string().optional(),
1324
+ error: z3.string().optional(),
1325
+ note: z3.string().optional(),
1326
+ skippedBecause: z3.string().optional(),
1327
+ executedAt: z3.number().optional(),
1328
+ pendingJob: z3.string().optional(),
1329
+ verification: shopifyVerificationSchema.optional()
1330
+ });
1331
+ var stagedOpSchema = z3.object({
1332
+ ref: z3.string(),
1333
+ kind: shopifyDraftOpKindSchema,
1334
+ summary: z3.string(),
1335
+ warnings: z3.array(z3.string())
1336
+ });
1337
+ var shopifyDraftStageResponseSchema = z3.union([
1338
+ z3.object({
1339
+ staged: z3.literal(true),
1340
+ count: z3.number(),
1341
+ ops: z3.array(stagedOpSchema),
1342
+ amended: z3.boolean().optional()
1343
+ }),
1344
+ z3.object({ staged: z3.literal(false), needsApproval: shopifyNeedsApprovalSchema })
1345
+ ]);
1346
+ var SHOPIFY_DRAFT_BATCH_MAX = 100;
1347
+ var shopifyDraftStageRequestSchema = z3.object({ chatId: z3.string(), op: shopifyDraftOpInputSchema });
1348
+ var shopifyDraftStageBatchRequestSchema = z3.object({
1349
+ chatId: z3.string(),
1350
+ ops: z3.array(shopifyDraftOpInputSchema).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
1351
+ });
1352
+ var shopifyDraftAmendRequestSchema = z3.object({
1353
+ chatId: z3.string(),
1354
+ ref: z3.string(),
1355
+ patch: z3.record(z3.string(), z3.unknown())
1356
+ });
1357
+ var shopifyDraftOpViewSchema = z3.object({
1358
+ ref: z3.string(),
1359
+ kind: shopifyDraftOpKindSchema,
1360
+ entity: shopifyWriteEntitySchema,
1361
+ target: z3.string().optional(),
1362
+ targetLabel: z3.string().optional(),
1363
+ summary: z3.string(),
1364
+ money: z3.boolean(),
1365
+ warnings: z3.array(z3.string()).optional(),
1366
+ dependsOn: z3.array(z3.string()),
1367
+ undoOf: z3.string().optional(),
1368
+ stagedAt: z3.number(),
1369
+ result: shopifyDraftOpResultSchema.optional()
1370
+ });
1371
+ var shopifyDraftListRequestSchema = z3.object({ chatId: z3.string() });
1372
+ var shopifyDraftListResponseSchema = z3.object({
1373
+ status: z3.enum(["active", "publishing", "applied", "discarded", "none"]),
1374
+ store: z3.string().optional(),
1375
+ count: z3.number(),
1376
+ ops: z3.array(shopifyDraftOpViewSchema),
1377
+ publishError: z3.string().optional()
1378
+ });
1379
+ var shopifyDraftShowRequestSchema = z3.object({ chatId: z3.string(), ref: z3.string() });
1380
+ var shopifyDraftShowResponseSchema = z3.object({
1381
+ op: shopifyDraftOpViewSchema.extend({ payload: z3.unknown().optional(), before: z3.unknown().optional() })
1382
+ });
1383
+ var shopifyDraftRemoveRequestSchema = z3.object({ chatId: z3.string(), ref: z3.string() });
1384
+ var shopifyDraftRemoveResponseSchema = z3.object({ removed: z3.array(z3.string()) });
1385
+ var shopifyDraftClearRequestSchema = z3.object({ chatId: z3.string() });
1386
+ var shopifyDraftClearResponseSchema = z3.object({ cleared: z3.number() });
1387
+ var shopifyChangeViewSchema = z3.object({
1388
+ changeId: z3.string(),
1389
+ when: z3.string(),
1390
+ kind: shopifyDraftOpKindSchema,
1391
+ summary: z3.string(),
1392
+ status: z3.enum(["applied", "failed", "undone"]),
1393
+ chatId: z3.string(),
1394
+ undoable: z3.boolean(),
1395
+ /** Why it can't be undone, when it can't. Whether the store still holds Baker's change is checked when undo is staged. */
1396
+ notUndoableBecause: z3.string().optional()
1397
+ });
1398
+ var shopifyChangesRequestSchema = z3.object({
1399
+ limit: z3.coerce.number().int().min(1).max(100).optional(),
1400
+ chatId: z3.string().optional()
1401
+ });
1402
+ var shopifyChangesResponseSchema = z3.object({ changes: z3.array(shopifyChangeViewSchema) });
1403
+ var shopifyUndoRequestSchema = z3.object({
1404
+ chatId: z3.string(),
1405
+ changeIds: z3.array(z3.string().min(1)).min(1).max(SHOPIFY_DRAFT_BATCH_MAX)
1406
+ });
1407
+
1408
+ // src/commands/shopify/refusal.ts
1409
+ var CONTINUE = "Keep going with the wider task: say which part you could not read, skip it, and finish everything else.";
1410
+ function shopifyRefusal(code, message) {
1411
+ if (code !== "NOT_FOUND") return null;
1412
+ if (message.includes(SHOPIFY_NOT_CONNECTED_MESSAGE)) {
1413
+ return {
1414
+ code: "NO_SHOPIFY_CONNECTION",
1415
+ fix: {
1416
+ action: "request_connection",
1417
+ 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
1418
+ }
1419
+ };
1420
+ }
1421
+ if (message.includes(SHOPIFY_NOT_OFFERED_MESSAGE)) {
1422
+ return {
1423
+ code: "SHOPIFY_NOT_AVAILABLE",
1424
+ fix: {
1425
+ action: "continue_without",
1426
+ 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."
1427
+ }
1428
+ };
1429
+ }
1430
+ return null;
1431
+ }
1432
+ function storeChangeRefusal(code, message) {
1433
+ if (code !== "FORBIDDEN" || !message.startsWith(SHOPIFY_PERMISSION_NEEDED_PREFIX)) return null;
1434
+ return {
1435
+ code: "SHOPIFY_PERMISSION_NEEDED",
1436
+ fix: {
1437
+ action: "request_connection",
1438
+ 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.'
1439
+ }
1440
+ };
1441
+ }
1442
+ function approvalRefusal(needsApproval) {
1443
+ if (needsApproval.declined) {
1444
+ return {
1445
+ code: "NEEDS_USER_APPROVAL",
1446
+ fix: {
1447
+ action: "continue_without",
1448
+ askUserQuestion: needsApproval.askUserQuestion,
1449
+ 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."
1450
+ }
1451
+ };
1452
+ }
1453
+ return {
1454
+ code: "NEEDS_USER_APPROVAL",
1455
+ fix: {
1456
+ action: "ask_user",
1457
+ askUserQuestion: needsApproval.askUserQuestion,
1458
+ 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.'
1459
+ }
1460
+ };
1461
+ }
1462
+
1463
+ // src/commands/shopify/common.ts
1464
+ 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.";
1465
+ function fail(err) {
1466
+ if (err instanceof ApiError) {
1467
+ const refusal = shopifyRefusal(err.code, err.message);
1468
+ if (refusal) {
1469
+ writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
1470
+ process.exit(1);
1471
+ }
1472
+ if (err.code === "NOT_FOUND" && err.message === SHOPIFY_BUYER_GONE_MESSAGE) {
1473
+ writeJson({
1474
+ ok: false,
1475
+ error: { code: err.code, message: err.message },
1476
+ hints: [
1477
+ "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."
1478
+ ]
1479
+ });
1480
+ process.exit(1);
1481
+ }
1482
+ if (err.code === "NOT_FOUND" && err.message.startsWith(SHOPIFY_PRODUCT_NOT_FOUND_PREFIX)) {
1483
+ writeJson({ ok: false, error: { code: err.code, message: err.message }, hints: [PRODUCT_NOT_FOUND_HINT] });
1484
+ process.exit(1);
1485
+ }
1486
+ }
1487
+ failApi(err);
1488
+ }
1489
+ function emit(data, meta, hints) {
1490
+ writeJson({ ok: true, ...hints.length > 0 ? { hints } : {}, data, meta });
1491
+ }
1492
+ function params(entries) {
1493
+ const out = {};
1494
+ for (const [name, value] of Object.entries(entries)) {
1495
+ if (value === void 0 || value === "" || value === false) continue;
1496
+ out[name] = String(value);
1497
+ }
1498
+ return out;
1499
+ }
1500
+ var fullArg = {
1501
+ type: "boolean",
1502
+ description: "Everything Baker keeps, not just what answers the question",
1503
+ required: false,
1504
+ default: false
1505
+ };
1506
+
1507
+ // src/commands/shopify/hints.ts
1508
+ 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.';
1509
+ 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.";
1510
+ function day(iso) {
1511
+ return iso ? iso.slice(0, 10) : null;
1512
+ }
1513
+ function progress(store) {
1514
+ const done = store.importProgress?.done;
1515
+ const total = store.importProgress?.total;
1516
+ if (done === void 0) return "";
1517
+ return total === void 0 ? ` (${done} so far)` : ` (${done} of ${total})`;
1518
+ }
1519
+ function storeStateHints(store) {
1520
+ const hints = [];
1521
+ if (store.connection === "needs_reconnect") hints.push(RECONNECT);
1522
+ if (store.connection === "disconnected") {
1523
+ const last = day(store.lastActivity);
1524
+ hints.push(
1525
+ `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.`
1526
+ );
1527
+ }
1528
+ if (store.catalog === "starting" || store.catalog === "importing") {
1529
+ hints.push(
1530
+ `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.`
1531
+ );
1532
+ }
1533
+ if (store.catalog === "failed") {
1534
+ hints.push("Baker couldn't finish reading this store's products; the list may be incomplete.");
1535
+ }
1536
+ if (store.unreadable !== void 0) {
1537
+ const count = store.unreadableCapped ? `${store.unreadable}+` : String(store.unreadable);
1538
+ hints.push(
1539
+ `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.`
1540
+ );
1541
+ }
1542
+ if (store.checksFailing) {
1543
+ hints.push("Baker couldn't check this store for changes lately; recent edits in Shopify may not show yet.");
1544
+ }
1545
+ if (store.storefront === "own") hints.push(OWN_STOREFRONT);
1546
+ return hints;
1547
+ }
1548
+ function cleanAndReady(store) {
1549
+ return store.catalog === "ready" && store.unreadable === void 0 && !store.checksFailing;
1550
+ }
1551
+ function productsHints(answer, options) {
1552
+ const hints = [];
1553
+ const notForSale = answer.products.filter((row) => row.availability === "not_for_sale").length;
1554
+ const linkOnly = answer.products.filter((row) => row.availability === "link_only").length;
1555
+ if (notForSale > 0) {
1556
+ hints.push(
1557
+ `${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.`
1558
+ );
1559
+ }
1560
+ if (linkOnly > 0) {
1561
+ hints.push(
1562
+ `${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.`
1563
+ );
1564
+ }
1565
+ if (answer.nextCursor)
1566
+ hints.push(`More products: rerun with \`--cursor ${answer.nextCursor}\` and the same filters.`);
1567
+ if (answer.products.length === 0 && !answer.nextCursor) {
1568
+ if (options.paged) hints.push("No more products: the earlier pages were the whole list.");
1569
+ else if (options.filtered) hints.push("No products match; drop `--search`/`--stock`/`--collection`.");
1570
+ else if (cleanAndReady(answer.store)) hints.push("The store has no products yet.");
1571
+ else {
1572
+ hints.push(
1573
+ "Baker has no products for this store right now \u2014 see the notes above; don't tell the user the store is empty."
1574
+ );
1575
+ }
1576
+ }
1577
+ return hints;
1578
+ }
1579
+ function photoHints(product) {
1580
+ const hints = [];
1581
+ if (product.photos.length > 0) {
1582
+ const ids = product.photos.map((photo) => photo.imageId).join(" ");
1583
+ hints.push(
1584
+ `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. Never use a cdn.shopify.com link \u2014 pages refuse it.`
1585
+ );
1586
+ }
1587
+ if (product.photosNotBrought > 0) {
1588
+ hints.push(
1589
+ `${product.photosNotBrought} more photos stay in Shopify (Baker keeps up to 10 per product); use the ones listed.`
1590
+ );
1591
+ }
1592
+ if (product.photosAdding > 0) {
1593
+ hints.push(`${product.photosAdding} photos are still being added; run this again in a minute if you need them.`);
1594
+ }
1595
+ return hints;
1596
+ }
1597
+ function stockHintsFor(product) {
1598
+ const hints = [];
1599
+ if (product.stock.optionsOut > 0) {
1600
+ const names = product.outOptions.map((option) => option.title).join(", ");
1601
+ hints.push(
1602
+ 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.`
1603
+ );
1604
+ }
1605
+ if (product.outOptionsMore) {
1606
+ hints.push(
1607
+ `${product.outOptionsMore} more options are out of stock: \`baker shopify product ${product.productId} --out-of-stock\` (then \`--variants-cursor\` for the next page).`
1608
+ );
1609
+ }
1610
+ if (product.variantsNextCursor) {
1611
+ hints.push(`More options: rerun with \`--variants-cursor ${product.variantsNextCursor}\`.`);
1612
+ }
1613
+ return hints;
1614
+ }
1615
+ var BUY_NOTES = {
1616
+ 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.",
1617
+ subscription_optional: "It can also be bought by subscription: on a landing `<ShopifyProduct>` offers the plans; a `buyUrl` elsewhere adds it once.",
1618
+ 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.",
1619
+ details_pending: "Baker hasn't read this product's details yet (subscriptions among them): its buy links may change.",
1620
+ 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."
1621
+ };
1622
+ function buyHints(product) {
1623
+ const hints = [];
1624
+ if (product.variants.some((variant) => variant.buyUrl)) {
1625
+ hints.push(
1626
+ `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\`.`,
1627
+ `\`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).`
1628
+ );
1629
+ }
1630
+ const notes = new Set(product.variants.flatMap((variant) => variant.notes ?? []));
1631
+ for (const note of notes) hints.push(BUY_NOTES[note]);
1632
+ return hints;
1633
+ }
1634
+ function contentHints(product) {
1635
+ const hints = [];
1636
+ const [video] = product.videos;
1637
+ if (video) {
1638
+ hints.push(
1639
+ `Its videos are already in the video library: place one with \`<Video playbackId="${video.playbackId}" />\` \u2014 never a Shopify video link.`
1640
+ );
1641
+ }
1642
+ if (product.videosNotBrought > 0) {
1643
+ hints.push(
1644
+ `${product.videosNotBrought} more videos stay in Shopify (Baker keeps up to 5 per product); use the ones listed.`
1645
+ );
1646
+ }
1647
+ if (product.videosAdding > 0) hints.push(`${product.videosAdding} videos are still being added.`);
1648
+ if (product.contentSummary && !product.content) {
1649
+ hints.push(
1650
+ "For a landing or an ad, read the whole of it with `--content` (formatted description, custom fields, subscriptions, translations)."
1651
+ );
1652
+ }
1653
+ const truncated = product.content?.fields.find((field) => field.truncated);
1654
+ if (truncated) {
1655
+ const option = truncated.variantId ? ` --variant ${truncated.variantId}` : "";
1656
+ hints.push(`Some fields are shown in part: read one whole with \`--field ${truncated.key}${option}\`.`);
1657
+ }
1658
+ if (product.content?.unreadable?.length || product.content?.fields.some((field) => field.unreadable)) {
1659
+ hints.push(
1660
+ "Some content isn't readable yet: ask the user to open Baker in their Shopify admin and accept the new permissions."
1661
+ );
1662
+ }
1663
+ return hints;
1664
+ }
1665
+ function productHints(product, store) {
1666
+ const hints = [...photoHints(product), ...stockHintsFor(product), ...buyHints(product), ...contentHints(product)];
1667
+ if (product.availability === "not_for_sale") {
1668
+ hints.push(
1669
+ `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.`
1670
+ );
1671
+ }
1672
+ if (product.availability === "link_only") {
1673
+ hints.push(
1674
+ `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.`
1675
+ );
1676
+ }
1677
+ if (product.compareAt) {
1678
+ hints.push(`On sale: was ${product.compareAt}, now ${product.price}.`);
1679
+ }
1680
+ if (product.pricesByCountry) {
1681
+ hints.push(
1682
+ "Prices differ by country \u2014 quote the country's price from pricesByCountry, never convert the home price."
1683
+ );
1684
+ }
1685
+ if (store.storefront === "own" && !product.storeUrl) {
1686
+ hints.push("Ask the user for this product's page on their own site before linking to it.");
1687
+ }
1688
+ return hints;
1689
+ }
1690
+ function stockHints(answer, limit2) {
1691
+ const hints = [];
1692
+ const any = answer.outOfStock.length > 0 || answer.partlyOut.length > 0;
1693
+ const unfinished = answer.more.outOfStock || answer.more.partlyOut;
1694
+ const pageThrough = "page through with `baker shopify products --stock out_of_stock` / `--stock partly_out` (and the same `--collection`)";
1695
+ if (any) {
1696
+ hints.push(
1697
+ "Before promoting these, check which ads and pages point at them; pausing or swapping them is a staged change."
1698
+ );
1699
+ } else if (!unfinished) hints.push("Nothing is out of stock.");
1700
+ if (unfinished) {
1701
+ const full = answer.more.outOfStock && answer.outOfStock.length >= limit2 || answer.more.partlyOut && answer.partlyOut.length >= limit2;
1702
+ hints.push(
1703
+ 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}.`
1704
+ );
1705
+ }
1706
+ const big = answer.partlyOut.find((row) => row.outOptionsMore);
1707
+ if (big) {
1708
+ hints.push(
1709
+ `Not every out-of-stock option is listed: \`baker shopify product ${big.productId} --out-of-stock\` lists them all.`
1710
+ );
1711
+ }
1712
+ return hints;
1713
+ }
1714
+ function storeHints(summary) {
1715
+ const hints = ["Share the Products page link (productsUrl) when the user wants to browse the catalog."];
1716
+ if (summary.unreadable?.length) {
1717
+ hints.push(
1718
+ "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."
1719
+ );
1720
+ }
1721
+ if (summary.passwordProtected) {
1722
+ hints.push(
1723
+ "The store asks for a password before anyone can see it: shoppers from a landing or an ad meet it first."
1724
+ );
1725
+ }
1726
+ if (summary.capped) {
1727
+ const atCeiling = [
1728
+ summary.outOfStock >= SHOPIFY_STOCK_COUNT_CAP ? `${SHOPIFY_STOCK_COUNT_CAP} or more products out of stock` : null,
1729
+ summary.partlyOut >= SHOPIFY_STOCK_COUNT_CAP ? `${SHOPIFY_STOCK_COUNT_CAP} or more products partly out` : null,
1730
+ summary.collections >= SHOPIFY_COLLECTION_COUNT_CAP ? `${SHOPIFY_COLLECTION_COUNT_CAP} or more collections` : null
1731
+ ].filter((count) => count !== null);
1732
+ hints.push(
1733
+ `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.`
1734
+ );
1735
+ }
1736
+ return hints;
1737
+ }
1738
+ function timingHints(answer) {
1739
+ const peak = [...answer.rows].sort((a, b) => b.orders - a.orders)[0];
1740
+ if (!peak || peak.orders === 0) return [];
1741
+ const zone = answer.window.timezone ? `, ${answer.window.timezone}` : "";
1742
+ const when = answer.by === "hour" ? `at ${peak.label}` : answer.by === "month_of_year" ? `in ${peak.label}` : `on ${peak.label}`;
1743
+ return [`Most orders are placed ${when} (store time${zone}): weight budgets and sends towards it.`];
1744
+ }
1745
+ function salesHints(answer) {
1746
+ const hints = [];
1747
+ if (answer.ordersUnreadable) {
1748
+ hints.push(
1749
+ "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\" }."
1750
+ );
1751
+ }
1752
+ if (answer.history === "importing") {
1753
+ hints.push(
1754
+ "Baker is still bringing in this store's past orders; earlier periods are incomplete \u2014 say so rather than reporting a drop."
1755
+ );
1756
+ }
1757
+ if (answer.totals.orders === 0) {
1758
+ hints.push("No orders were paid in this period.");
1759
+ return hints;
1760
+ }
1761
+ if (answer.by === "weekday" || answer.by === "hour" || answer.by === "month_of_year") {
1762
+ return [...hints, ...timingHints(answer)];
1763
+ }
1764
+ const notBaker = ["meta", "google"].filter((family) => answer.salesReporting[family] !== "baker");
1765
+ if (notBaker.length > 0) {
1766
+ const names = notBaker.map((family) => family === "meta" ? "Meta" : "Google").join(" and ");
1767
+ hints.push(
1768
+ `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.`
1769
+ );
1770
+ }
1771
+ const unattributed = answer.rows.filter((row) => row.credit === "unattributed").reduce((total, row) => total + row.net, 0);
1772
+ if ((answer.by === "campaign" || answer.by === "landing") && answer.totals.net > 0 && unattributed / answer.totals.net > 0.5) {
1773
+ hints.push(
1774
+ "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."
1775
+ );
1776
+ }
1777
+ const leading = answer.by === "landing" ? answer.rows.find((row) => row.credit === "landing") : void 0;
1778
+ if (leading && leading.net > 0) {
1779
+ hints.push(
1780
+ `"${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.`
1781
+ );
1782
+ }
1783
+ return hints;
1784
+ }
1785
+
1786
+ // src/commands/shopify/buyers.ts
1787
+ 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).";
1788
+ var periodArgs = {
1789
+ days: { type: "number", description: "The last N days, 1-3650 (default 365)", required: false },
1790
+ all: { type: "boolean", description: "Every order the store has", required: false, default: false },
1791
+ country: {
1792
+ type: "string",
1793
+ description: "Only orders to this country: its two-letter code, like DE",
1794
+ required: false
1795
+ }
1796
+ };
1797
+ var periodFlags = {
1798
+ days: { type: "string", description: "The last N days, 1-3650 (default 365)", required: false },
1799
+ all: { type: "boolean", description: "Every order the store has", required: false, default: false },
1800
+ country: { type: "string", description: "Only orders to this country (two-letter code, like DE)", required: false }
1801
+ };
1802
+ function split(answer) {
1803
+ const { store, window, history, basis, ...data } = answer;
1804
+ return { data, meta: { store, window, history, basis } };
1805
+ }
1806
+ registerSchema({
1807
+ command: "shopify.buyers",
1808
+ 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.`,
1809
+ args: {
1810
+ ...periodArgs,
1811
+ segment: {
1812
+ type: "string",
1813
+ description: "Only one segment's buyers: best | loyal | at_risk | new | one_time",
1814
+ required: false,
1815
+ enum: ["best", "loyal", "at_risk", "new", "one_time"]
1816
+ },
1817
+ by: {
1818
+ type: "string",
1819
+ description: "Lifetime value by the campaign or channel of each buyer's first order",
1820
+ required: false,
1821
+ enum: ["campaign", "channel"]
1822
+ },
1823
+ channel: {
1824
+ type: "string",
1825
+ description: "Only buyers who first came through this channel (a key from --by channel)",
1826
+ required: false
1827
+ },
1828
+ sort: {
1829
+ type: "string",
1830
+ description: "net (lifetime spend, default) | orders | recent (last order) | first (first order)",
1831
+ required: false,
1832
+ enum: ["net", "orders", "recent", "first"]
1833
+ },
1834
+ limit: { type: "number", description: "Buyers listed, 1-100 (default 10)", required: false },
1835
+ offset: { type: "number", description: "Skip this many buyers (paging)", required: false },
1836
+ full: {
1837
+ ...fullArg,
1838
+ description: "Add each buyer's gross, refunds, orders with a discount, days between orders and first-order campaign"
1839
+ }
1840
+ }
1841
+ });
1842
+ var buyersCommand = defineCommand({
1843
+ meta: {
1844
+ name: "buyers",
1845
+ description: "Who buys: buyers, new and returning, repeat rate, segments and the top buyers. Start here: baker shopify buyers --days 90 --by channel"
1846
+ },
1847
+ args: {
1848
+ ...periodFlags,
1849
+ segment: { type: "string", description: "best | loyal | at_risk | new | one_time", required: false },
1850
+ by: { type: "string", description: "campaign | channel", required: false },
1851
+ channel: { type: "string", description: "A channel key from --by channel", required: false },
1852
+ sort: { type: "string", description: "net | orders | recent | first", required: false },
1853
+ limit: { type: "string", description: "Buyers listed, 1-100", required: false },
1854
+ offset: { type: "string", description: "Skip this many buyers", required: false },
1855
+ full: { type: "boolean", description: "Add each buyer's detail", required: false, default: false }
1856
+ },
1857
+ run: async ({ args }) => {
1858
+ try {
1859
+ const answer = await apiGet(
1860
+ "/api/shopify/buyers",
1861
+ params({
1862
+ days: args.days,
1863
+ all: args.all,
1864
+ country: args.country,
1865
+ segment: args.segment,
1866
+ by: args.by,
1867
+ channel: args.channel,
1868
+ sort: args.sort,
1869
+ limit: args.limit,
1870
+ offset: args.offset,
1871
+ full: args.full
1872
+ })
1873
+ );
1874
+ const { data, meta } = split(answer);
1875
+ emit(data, meta, [...storeStateHints(answer.store), ...buyersHints(answer)]);
1876
+ } catch (err) {
1877
+ fail(err);
1878
+ }
1879
+ }
1880
+ });
1881
+ registerSchema({
1882
+ command: "shopify.buyer",
1883
+ 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'.",
1884
+ args: {
1885
+ number: { type: "positional", description: "The buyer's number: 1042, #1042 or 'Customer #1042'", required: true },
1886
+ full: { ...fullArg, description: "Add each order's id, lines with their amounts, refunded lines and campaign" }
1887
+ }
1888
+ });
1889
+ var buyerCommand = defineCommand({
1890
+ meta: {
1891
+ name: "buyer",
1892
+ description: "One buyer's history by number. Example: baker shopify buyer 1042"
1893
+ },
1894
+ args: {
1895
+ number: { type: "positional", description: "1042, #1042 or 'Customer #1042'", required: true },
1896
+ full: { type: "boolean", description: "Add ids, lines and campaigns", required: false, default: false }
1897
+ },
1898
+ run: async ({ args }) => {
1899
+ try {
1900
+ const answer = await apiGet(
1901
+ "/api/shopify/buyer",
1902
+ params({ number: args.number, full: args.full })
1903
+ );
1904
+ const { data, meta } = split(answer);
1905
+ emit(data, meta, [...storeStateHints(answer.store), ...buyerHistoryHints(answer.history, answer.basis)]);
1906
+ } catch (err) {
1907
+ fail(err);
1908
+ }
1909
+ }
1910
+ });
1911
+ registerSchema({
1912
+ command: "shopify.countries",
1913
+ 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}`,
1914
+ args: {
1915
+ ...periodArgs,
1916
+ region: { type: "string", description: "Inside --country, one region (code or name): its cities", required: false },
1917
+ product: { type: "string", description: "Only orders holding this product: its id or handle", required: false },
1918
+ limit: { type: "number", description: "Places listed, 1-100 (default 25; the rest are 'Other')", required: false },
1919
+ full: { ...fullArg, description: "Add the period before's net and orders per place" }
1920
+ }
1921
+ });
1922
+ var countriesCommand = defineCommand({
1923
+ meta: {
1924
+ name: "countries",
1925
+ description: "Where buyers are: orders, buyers and net by country, region or city, with growth. Start here: baker shopify countries --days 90"
1926
+ },
1927
+ args: {
1928
+ ...periodFlags,
1929
+ region: { type: "string", description: "A region inside --country", required: false },
1930
+ product: { type: "string", description: "A product's id or handle", required: false },
1931
+ limit: { type: "string", description: "Places listed, 1-100", required: false },
1932
+ full: { type: "boolean", description: "Add the period before", required: false, default: false }
1933
+ },
1934
+ run: async ({ args }) => {
1935
+ try {
1936
+ const answer = await apiGet(
1937
+ "/api/shopify/countries",
1938
+ params({
1939
+ days: args.days,
1940
+ all: args.all,
1941
+ country: args.country,
1942
+ region: args.region,
1943
+ product: args.product,
1944
+ limit: args.limit,
1945
+ full: args.full
1946
+ })
1947
+ );
1948
+ const { data, meta } = split(answer);
1949
+ emit(data, meta, [...storeStateHints(answer.store), ...countriesHints(answer)]);
1950
+ } catch (err) {
1951
+ fail(err);
1952
+ }
1953
+ }
1954
+ });
1955
+ registerSchema({
1956
+ command: "shopify.bestSellers",
1957
+ 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\`.`,
1958
+ args: {
1959
+ ...periodArgs,
1960
+ view: {
1961
+ type: "string",
1962
+ description: "products (default) | variants | trend | together | repeat | momentum",
1963
+ required: false,
1964
+ enum: ["products", "variants", "trend", "together", "repeat", "momentum"]
1965
+ },
1966
+ sort: {
1967
+ type: "string",
1968
+ description: "units (default) | revenue | orders",
1969
+ required: false,
1970
+ enum: ["units", "revenue", "orders"]
1971
+ },
1972
+ product: { type: "string", description: "One product (id or handle): its variants or its trend", required: false },
1973
+ limit: { type: "number", description: "Rows, 1-100 (default 10; 5 products for trend)", required: false },
1974
+ full: { ...fullArg, description: "Add product and variant ids and the period before" }
1975
+ }
1976
+ });
1977
+ var bestSellersCommand = defineCommand({
1978
+ meta: {
1979
+ name: "best-sellers",
1980
+ description: "What sells: best sellers, variants, trend, bought together, repeat leaders, losing momentum. Start here: baker shopify best-sellers --days 90"
1981
+ },
1982
+ args: {
1983
+ ...periodFlags,
1984
+ view: {
1985
+ type: "string",
1986
+ description: "products | variants | trend | together | repeat | momentum",
1987
+ required: false
1988
+ },
1989
+ sort: { type: "string", description: "units | revenue | orders", required: false },
1990
+ product: { type: "string", description: "A product's id or handle", required: false },
1991
+ limit: { type: "string", description: "Rows, 1-100", required: false },
1992
+ full: { type: "boolean", description: "Add ids and the period before", required: false, default: false }
1993
+ },
1994
+ run: async ({ args }) => {
1995
+ try {
1996
+ const answer = await apiGet(
1997
+ "/api/shopify/best-sellers",
1998
+ params({
1999
+ days: args.days,
2000
+ all: args.all,
2001
+ country: args.country,
2002
+ view: args.view,
2003
+ sort: args.sort,
2004
+ product: args.product,
2005
+ limit: args.limit,
2006
+ full: args.full
2007
+ })
2008
+ );
2009
+ const { data, meta } = split(answer);
2010
+ emit(data, meta, [...storeStateHints(answer.store), ...bestSellersHints(answer)]);
2011
+ } catch (err) {
2012
+ fail(err);
2013
+ }
2014
+ }
2015
+ });
2016
+ registerSchema({
2017
+ command: "shopify.cohorts",
2018
+ 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.",
2019
+ args: {
2020
+ months: { type: "number", description: "Cohorts, the last 3-36 months (default 12)", required: false },
2021
+ country: { type: "string", description: "Only orders to this country (two-letter code)", required: false },
2022
+ full: {
2023
+ ...fullArg,
2024
+ description: "Add every month of every cohort: active buyers, orders, net, cumulative net per buyer"
2025
+ }
2026
+ }
2027
+ });
2028
+ var cohortsCommand = defineCommand({
2029
+ meta: {
2030
+ name: "cohorts",
2031
+ description: "Buyers by first-order month: who came back and what they spent. Example: baker shopify cohorts --months 12"
2032
+ },
2033
+ args: {
2034
+ months: { type: "string", description: "The last 3-36 months", required: false },
2035
+ country: { type: "string", description: "A two-letter country code", required: false },
2036
+ full: { type: "boolean", description: "Every month of every cohort", required: false, default: false }
2037
+ },
2038
+ run: async ({ args }) => {
2039
+ try {
2040
+ const answer = await apiGet(
2041
+ "/api/shopify/cohorts",
2042
+ params({ months: args.months, country: args.country, full: args.full })
2043
+ );
2044
+ const { data, meta } = split(answer);
2045
+ emit(data, meta, [...storeStateHints(answer.store), ...cohortsHints(answer)]);
2046
+ } catch (err) {
2047
+ fail(err);
2048
+ }
2049
+ }
2050
+ });
2051
+ registerSchema({
2052
+ command: "shopify.segments",
2053
+ 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'.",
2054
+ args: {
2055
+ ...periodArgs,
2056
+ full: { ...fullArg, description: "Add the first products' ids" }
2057
+ }
2058
+ });
2059
+ var segmentsCommand = defineCommand({
2060
+ meta: {
2061
+ name: "segments",
2062
+ description: "Buyers by segment: best, loyal, at risk, new, one-time. Example: baker shopify segments"
2063
+ },
2064
+ args: {
2065
+ ...periodFlags,
2066
+ full: { type: "boolean", description: "Add product ids", required: false, default: false }
2067
+ },
2068
+ run: async ({ args }) => {
2069
+ try {
2070
+ const answer = await apiGet(
2071
+ "/api/shopify/segments",
2072
+ params({ days: args.days, all: args.all, country: args.country, full: args.full })
2073
+ );
2074
+ const { data, meta } = split(answer);
2075
+ emit(data, meta, [...storeStateHints(answer.store), ...segmentsHints(answer)]);
2076
+ } catch (err) {
2077
+ fail(err);
2078
+ }
2079
+ }
2080
+ });
2081
+
2082
+ // src/commands/shopify/change.ts
2083
+ import { defineCommand as defineCommand3 } from "citty";
2084
+
2085
+ // src/commands/shopify/writeCommon.ts
2086
+ import { defineCommand as defineCommand2 } from "citty";
2087
+
2088
+ // src/commands/shopify/writeHints.ts
2089
+ 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.";
2090
+ var WARNINGS = "Read the warnings above before telling the user this is ready.";
2091
+ var DISCOUNT = "The code works only after the chat is published; don't announce it as live or share it as working yet.";
2092
+ var EMPTY_COLLECTION = "The new collection has no products yet: add them with `baker shopify change collection add <collection> <product\u2026>`.";
2093
+ 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).";
2094
+ function repointedRedirects(ops) {
2095
+ const removed = ops.flatMap(
2096
+ (op) => op.kind === "shopify.urlRedirect.delete" && op.ref.startsWith("urlRedirect:") ? [op.ref.slice("urlRedirect:".length)] : []
2097
+ );
2098
+ return removed.flatMap((path2) => {
2099
+ const made = ops.find(
2100
+ (op) => op.kind === "shopify.urlRedirect.create" && op.summary.startsWith(`Redirect ${path2} to `)
2101
+ );
2102
+ if (!made) return [];
2103
+ const to = made.summary.slice(`Redirect ${path2} to `.length);
2104
+ return [
2105
+ `${path2} still redirects after publish, now to ${to}: tell the user the old link goes there, not that its redirect is removed.`
2106
+ ];
2107
+ });
2108
+ }
2109
+ function stageHints(data) {
2110
+ if (!data.staged) return [];
2111
+ const hints = [STAGED, ...repointedRedirects(data.ops)];
2112
+ if (data.ops.some((op) => op.warnings.length > 0)) hints.push(WARNINGS);
2113
+ if (data.ops.some((op) => op.kind === "shopify.discountCode.create")) hints.push(DISCOUNT);
2114
+ if (data.ops.some((op) => op.kind === "shopify.collection.delete")) hints.push(COLLECTION_DELETE);
2115
+ if (data.ops.some(
2116
+ (op) => op.kind === "shopify.collection.create" && op.warnings.some((warning) => /no products/.test(warning))
2117
+ )) {
2118
+ hints.push(EMPTY_COLLECTION);
2119
+ }
2120
+ return hints;
2121
+ }
2122
+ function changesHints(data) {
2123
+ if (data.changes.length === 0) return ["Baker hasn't changed this store yet."];
2124
+ return data.changes.some((change) => change.undoable) ? ["Undo one with `baker shopify undo <changeId>` (staged like any change, applied when the user publishes)."] : [];
2125
+ }
2126
+ function auditHints(data) {
2127
+ const hints = [];
2128
+ for (const check of data.checks) {
2129
+ if (check.fix?.kind === "staged") hints.push(`Stage the fix for "${check.title}": \`${check.fix.command}\`.`);
2130
+ }
2131
+ for (const check of data.checks) {
2132
+ if (check.fix?.kind === "merchant") {
2133
+ hints.push(
2134
+ `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" : ""}.`
2135
+ );
2136
+ }
2137
+ }
2138
+ for (const check of data.checks) {
2139
+ if (check.fix?.kind === "decision") hints.push(`A person decides "${check.title}": ${check.fix.where}.`);
2140
+ }
2141
+ return hints;
2142
+ }
2143
+
2144
+ // src/commands/shopify/writeCommon.ts
2145
+ function failChange(err) {
2146
+ if (err instanceof ApiError) {
2147
+ const refusal = storeChangeRefusal(err.code, err.message);
2148
+ if (refusal) {
2149
+ writeJson({ ok: false, error: { code: refusal.code, message: err.message, fix: refusal.fix } });
2150
+ process.exit(1);
2151
+ }
2152
+ }
2153
+ fail(err);
2154
+ }
2155
+ function emitStage(data) {
2156
+ if (!data.staged) {
2157
+ const refusal = approvalRefusal(data.needsApproval);
2158
+ writeJson({
2159
+ ok: false,
2160
+ error: {
2161
+ code: refusal.code,
2162
+ 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.",
2163
+ fix: refusal.fix
2164
+ }
2165
+ });
2166
+ process.exit(1);
2167
+ }
2168
+ writeJson({ ok: true, hints: stageHints(data), data });
2169
+ }
2170
+ async function stage(ops) {
2171
+ const chatId = requireChatId();
2172
+ try {
2173
+ const [only] = ops;
2174
+ const data = ops.length === 1 && only ? await apiPost("/api/shopify/draft/stage", { chatId, op: only }) : await apiPost("/api/shopify/draft/stage-batch", { chatId, ops });
2175
+ emitStage(data);
2176
+ } catch (err) {
2177
+ failChange(err);
2178
+ }
2179
+ }
2180
+ function list(value) {
2181
+ if (typeof value !== "string" || value.trim() === "") return void 0;
2182
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
2183
+ }
2184
+ function defined(entries) {
2185
+ return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0 && value !== ""));
2186
+ }
2187
+ function payloadsFrom(args) {
2188
+ if (typeof args.json !== "string" && typeof args.file !== "string") return null;
2189
+ return loadJsonPayloads(args);
2190
+ }
2191
+ var JSON_ARGS = {
2192
+ json: {
2193
+ type: "string",
2194
+ 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',
2195
+ required: false
2196
+ },
2197
+ file: { type: "string", description: "A JSON file holding the same", required: false }
2198
+ };
2199
+ function leaf(schemaName, meta, args, run) {
2200
+ registerSchema({
2201
+ command: schemaName,
2202
+ description: meta.description,
2203
+ args: Object.fromEntries(
2204
+ Object.entries(args).map(([name, arg]) => [
2205
+ name,
2206
+ {
2207
+ type: String(arg.type ?? "string"),
2208
+ description: String(arg.description ?? ""),
2209
+ required: arg.required === true
2210
+ }
2211
+ ])
2212
+ )
2213
+ });
2214
+ return defineCommand2({ meta, args, run: async ({ args: parsed }) => await run(parsed) });
2215
+ }
2216
+
2217
+ // src/commands/shopify/change.ts
2218
+ var TARGET = (what) => ({ type: "positional", description: what, required: false });
2219
+ function positionals(args) {
2220
+ return Array.isArray(args._) ? args._.filter((value) => typeof value === "string") : [];
2221
+ }
2222
+ function target2(args, what) {
2223
+ const [first] = positionals(args);
2224
+ if (!first) failValidation(`pass the ${what} as the first argument`);
2225
+ return first;
2226
+ }
2227
+ async function stageFor(kind, args, flags, targetOf) {
2228
+ const payloads = payloadsFrom(args);
2229
+ if (payloads) {
2230
+ await stage(
2231
+ payloads.map(
2232
+ (payload2) => "kind" in payload2 ? payload2 : { kind, ...targetOf ? { target: targetOf() } : {}, payload: payload2 }
2233
+ )
2234
+ );
2235
+ return;
2236
+ }
2237
+ const payload = flags();
2238
+ if (Object.keys(payload).length === 0)
2239
+ failValidation("name at least one thing to change (see `baker schema` for the flags)");
2240
+ await stage([{ kind, ...targetOf ? { target: targetOf() } : {}, payload }]);
2241
+ }
2242
+ var productUpdate = leaf(
2243
+ "shopify.change.product.update",
2244
+ {
2245
+ name: "update",
2246
+ 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.`
2247
+ },
2248
+ {
2249
+ product: TARGET("Product id, handle or link"),
2250
+ title: { type: "string", description: "New title", required: false },
2251
+ description: { type: "string", description: "New description, as HTML (no scripts or frames)", required: false },
2252
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
2253
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
2254
+ tags: { type: "string", description: "The whole tag list, comma separated (replaces it)", required: false },
2255
+ "add-tags": { type: "string", description: "Tags to add, comma separated", required: false },
2256
+ "remove-tags": { type: "string", description: "Tags to remove, comma separated", required: false },
2257
+ type: { type: "string", description: "Product type", required: false },
2258
+ ...JSON_ARGS
2259
+ },
2260
+ async (args) => await stageFor(
2261
+ "shopify.product.update",
2262
+ args,
2263
+ () => defined({
2264
+ title: args.title,
2265
+ descriptionHtml: args.description,
2266
+ seo: args["seo-title"] !== void 0 || args["seo-description"] !== void 0 ? defined({ title: args["seo-title"], description: args["seo-description"] }) : void 0,
2267
+ tags: list(args.tags),
2268
+ addTags: list(args["add-tags"]),
2269
+ removeTags: list(args["remove-tags"]),
2270
+ productType: args.type
2271
+ }),
2272
+ positionals(args).length > 0 ? () => target2(args, "product") : void 0
2273
+ )
2274
+ );
2275
+ var WEIGHT_UNITS = {
2276
+ g: "GRAMS",
2277
+ gram: "GRAMS",
2278
+ grams: "GRAMS",
2279
+ kg: "KILOGRAMS",
2280
+ kgs: "KILOGRAMS",
2281
+ kilogram: "KILOGRAMS",
2282
+ kilograms: "KILOGRAMS",
2283
+ oz: "OUNCES",
2284
+ ounce: "OUNCES",
2285
+ ounces: "OUNCES",
2286
+ lb: "POUNDS",
2287
+ lbs: "POUNDS",
2288
+ pound: "POUNDS",
2289
+ pounds: "POUNDS"
2290
+ };
2291
+ function weightOf(value, unit) {
2292
+ if (value === void 0) return void 0;
2293
+ const named = typeof unit === "string" ? unit.trim() : "";
2294
+ return {
2295
+ value: Number(value),
2296
+ unit: named ? WEIGHT_UNITS[named.toLowerCase()] ?? named.toUpperCase() : "KILOGRAMS"
2297
+ };
2298
+ }
2299
+ var variantUpdate = leaf(
2300
+ "shopify.change.variant.update",
2301
+ {
2302
+ name: "update",
2303
+ 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."
2304
+ },
2305
+ {
2306
+ variant: TARGET("The option's id (variantId from baker shopify product)"),
2307
+ sku: { type: "string", description: "SKU", required: false },
2308
+ barcode: { type: "string", description: "Barcode / GTIN (8, 12, 13 or 14 digits)", required: false },
2309
+ weight: { type: "string", description: "Weight value", required: false },
2310
+ "weight-unit": {
2311
+ type: "string",
2312
+ description: "kg | g | lb | oz (default kg)",
2313
+ required: false
2314
+ },
2315
+ price: {
2316
+ type: "string",
2317
+ description: `Price as a decimal, e.g. "649.95" (needs the user's yes)`,
2318
+ required: false
2319
+ },
2320
+ "sale-price": {
2321
+ type: "string",
2322
+ description: `Compare-at price, or "none" to clear it (needs the user's yes)`,
2323
+ required: false
2324
+ },
2325
+ ...JSON_ARGS
2326
+ },
2327
+ async (args) => await stageFor(
2328
+ "shopify.variant.update",
2329
+ args,
2330
+ () => defined({
2331
+ sku: args.sku,
2332
+ barcode: args.barcode,
2333
+ weight: weightOf(args.weight, args["weight-unit"]),
2334
+ price: args.price,
2335
+ compareAtPrice: args["sale-price"] === "none" ? null : args["sale-price"]
2336
+ }),
2337
+ positionals(args).length > 0 ? () => target2(args, "option id") : void 0
2338
+ )
2339
+ );
2340
+ var fieldSet = leaf(
2341
+ "shopify.change.field.set",
2342
+ {
2343
+ name: "set",
2344
+ 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".'
2345
+ },
2346
+ {
2347
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
2348
+ namespace: { type: "string", description: "Namespace, e.g. custom", required: false },
2349
+ key: { type: "string", description: "Key, e.g. care", required: false },
2350
+ type: {
2351
+ type: "string",
2352
+ description: "Shopify's type, e.g. single_line_text_field, rich_text_field, json",
2353
+ required: false
2354
+ },
2355
+ value: { type: "string", description: "The value, as Shopify takes it for that type", required: false },
2356
+ ...JSON_ARGS
2357
+ },
2358
+ async (args) => await stageFor(
2359
+ "shopify.metafield.set",
2360
+ args,
2361
+ () => defined({ namespace: args.namespace, key: args.key, type: args.type, value: args.value }),
2362
+ () => target2(args, "owner")
2363
+ )
2364
+ );
2365
+ var fieldRemove = leaf(
2366
+ "shopify.change.field.remove",
2367
+ {
2368
+ name: "remove",
2369
+ description: "Remove a custom field from a product, an option or a collection. Example: baker shopify change field remove product:the-snowboard custom.care"
2370
+ },
2371
+ {
2372
+ owner: TARGET("product:<ref>, variant:<id> or collection:<ref>"),
2373
+ field: TARGET("namespace.key, e.g. custom.care")
2374
+ },
2375
+ async (args) => {
2376
+ const [owner, field] = positionals(args);
2377
+ const [namespace, ...rest] = (field ?? "").split(".");
2378
+ if (!owner || !namespace || rest.length === 0) failValidation("pass the owner and the field as namespace.key");
2379
+ await stage([{ kind: "shopify.metafield.delete", target: owner, payload: { namespace, key: rest.join(".") } }]);
2380
+ }
2381
+ );
2382
+ var collectionCreate = leaf(
2383
+ "shopify.change.collection.create",
2384
+ {
2385
+ name: "create",
2386
+ 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.'
2387
+ },
2388
+ {
2389
+ title: { type: "string", description: "Title", required: false },
2390
+ handle: {
2391
+ type: "string",
2392
+ description: "Its address (/collections/<handle>); default from the title",
2393
+ required: false
2394
+ },
2395
+ description: { type: "string", description: "Description, as HTML (no scripts or frames)", required: false },
2396
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
2397
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
2398
+ products: {
2399
+ type: "string",
2400
+ description: "Products, comma separated (ids, handles or links), in order",
2401
+ required: false
2402
+ },
2403
+ sort: {
2404
+ type: "string",
2405
+ description: "MANUAL | BEST_SELLING | ALPHA_ASC | ALPHA_DESC | PRICE_ASC | PRICE_DESC | CREATED | CREATED_DESC",
2406
+ required: false
2407
+ },
2408
+ hidden: { type: "boolean", description: "Don't show it on the online store yet", required: false },
2409
+ ...JSON_ARGS
2410
+ },
2411
+ async (args) => await stageFor(
2412
+ "shopify.collection.create",
2413
+ args,
2414
+ () => defined({
2415
+ title: args.title,
2416
+ handle: args.handle,
2417
+ descriptionHtml: args.description,
2418
+ seo: args["seo-title"] !== void 0 || args["seo-description"] !== void 0 ? defined({ title: args["seo-title"], description: args["seo-description"] }) : void 0,
2419
+ products: list(args.products),
2420
+ sortOrder: typeof args.sort === "string" ? args.sort.toUpperCase() : void 0,
2421
+ onlineStore: args.hidden === true ? false : void 0
2422
+ })
2423
+ )
2424
+ );
2425
+ var collectionUpdate = leaf(
2426
+ "shopify.change.collection.update",
2427
+ {
2428
+ name: "update",
2429
+ 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"`
2430
+ },
2431
+ {
2432
+ collection: TARGET("Collection id, handle or link"),
2433
+ title: { type: "string", description: "New title", required: false },
2434
+ description: { type: "string", description: "New description, as HTML", required: false },
2435
+ "seo-title": { type: "string", description: "Title shown in search results", required: false },
2436
+ "seo-description": { type: "string", description: "Description shown in search results", required: false },
2437
+ sort: { type: "string", description: "Sort order (see collection create)", required: false },
2438
+ ...JSON_ARGS
2439
+ },
2440
+ async (args) => await stageFor(
2441
+ "shopify.collection.update",
2442
+ args,
2443
+ () => defined({
2444
+ title: args.title,
2445
+ descriptionHtml: args.description,
2446
+ seo: args["seo-title"] !== void 0 || args["seo-description"] !== void 0 ? defined({ title: args["seo-title"], description: args["seo-description"] }) : void 0,
2447
+ sortOrder: typeof args.sort === "string" ? args.sort.toUpperCase() : void 0
2448
+ }),
2449
+ () => target2(args, "collection")
2450
+ )
2451
+ );
2452
+ function collectionVerb(verb, kind, description2) {
2453
+ return leaf(
2454
+ `shopify.change.collection.${verb}`,
2455
+ { name: verb, description: description2 },
2456
+ { collection: TARGET("Collection id, handle, link or a shopify_temp_ ref staged in this chat") },
2457
+ async (args) => await stage([{ kind, target: target2(args, "collection") }])
2458
+ );
2459
+ }
2460
+ function membership(verb, kind, description2) {
2461
+ return leaf(
2462
+ `shopify.change.collection.${verb}`,
2463
+ { name: verb, description: description2 },
2464
+ {
2465
+ collection: TARGET("Collection id, handle, link or a shopify_temp_ ref"),
2466
+ products: TARGET("Products (ids, handles or links), space separated")
2467
+ },
2468
+ async (args) => {
2469
+ const [collection, ...products] = positionals(args);
2470
+ if (!collection || products.length === 0) failValidation("pass the collection, then the products");
2471
+ await stage([{ kind, target: collection, payload: { products } }]);
2472
+ }
2473
+ );
2474
+ }
2475
+ function discountValue(args) {
2476
+ if (args.percent !== void 0) return { percentage: Number(args.percent) };
2477
+ if (args.amount !== void 0) return { amount: args.amount };
2478
+ return void 0;
2479
+ }
2480
+ function appliesToOf(args) {
2481
+ const products = list(args.products);
2482
+ if (products) return { products };
2483
+ const collections = list(args.collections);
2484
+ return collections ? { collections } : { all: true };
2485
+ }
2486
+ function combinesOf(value) {
2487
+ const combines = list(value) ?? [];
2488
+ if (combines.length === 0) return void 0;
2489
+ return {
2490
+ productDiscounts: combines.includes("product"),
2491
+ orderDiscounts: combines.includes("order"),
2492
+ shippingDiscounts: combines.includes("shipping")
2493
+ };
2494
+ }
2495
+ function numberOf(value) {
2496
+ return value === void 0 ? void 0 : Number(value);
2497
+ }
2498
+ function discountPayload(args) {
2499
+ return defined({
2500
+ code: args.code,
2501
+ title: args.title,
2502
+ value: discountValue(args),
2503
+ appliesTo: appliesToOf(args),
2504
+ minimumSubtotal: args["min-subtotal"],
2505
+ minimumQuantity: numberOf(args["min-quantity"]),
2506
+ startsAt: args.starts,
2507
+ endsAt: args.ends,
2508
+ usageLimit: numberOf(args.limit),
2509
+ oncePerCustomer: args["any-times"] === true ? false : void 0,
2510
+ combinesWith: combinesOf(args.combines)
2511
+ });
2512
+ }
2513
+ function signupOfferFlagsProblem(args) {
2514
+ if (typeof args.name !== "string" || args.name.trim() === "")
2515
+ return 'Add --name "<what shoppers and store staff see, e.g. 10% off>": every sign-up offer needs a name.';
2516
+ if (args["valid-days"] === void 0)
2517
+ return "Add --valid-days <1-365>: how many days each code lasts after it is given.";
2518
+ return void 0;
2519
+ }
2520
+ function signupOfferPayload(args) {
2521
+ return defined({
2522
+ name: args.name,
2523
+ value: discountValue(args),
2524
+ appliesTo: appliesToOf(args),
2525
+ minimumSubtotal: args.minimum,
2526
+ validForDays: numberOf(args["valid-days"]),
2527
+ endsAt: args.ends,
2528
+ maxCodes: numberOf(args["max-codes"]),
2529
+ codePrefix: args.prefix,
2530
+ combinesWith: combinesOf(args.combines)
2531
+ });
2532
+ }
2533
+ var signupOfferSet = leaf(
2534
+ "shopify.change.signupOffer.set",
2535
+ {
2536
+ name: "set",
2537
+ 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'
2538
+ },
2539
+ {
2540
+ handle: TARGET("Offer handle, e.g. welcome-10"),
2541
+ name: {
2542
+ type: "string",
2543
+ description: 'Offer name shoppers and store staff see (required), e.g. "10% off"',
2544
+ required: false
2545
+ },
2546
+ percent: { type: "string", description: "Percentage off (choose percent or amount)", required: false },
2547
+ amount: { type: "string", description: "Amount off in the store currency", required: false },
2548
+ products: { type: "string", description: "Product handles or ids, comma separated", required: false },
2549
+ collections: { type: "string", description: "Collection handles or ids, comma separated", required: false },
2550
+ minimum: { type: "string", description: "Minimum subtotal", required: false },
2551
+ "valid-days": { type: "string", description: "Each code lasts 1\u2013365 days (required)", required: false },
2552
+ ends: { type: "string", description: "Offer end as ISO date and time; no code outlives it", required: false },
2553
+ "max-codes": { type: "string", description: "Total codes the offer may give, 1\u2013100000", required: false },
2554
+ prefix: { type: "string", description: "Code prefix: 2\u201312 uppercase letters or digits", required: false },
2555
+ combines: {
2556
+ type: "string",
2557
+ description: "Comma-separated product,order,shipping discounts to combine",
2558
+ required: false
2559
+ },
2560
+ ...JSON_ARGS
2561
+ },
2562
+ async (args) => await stageFor(
2563
+ "shopify.signupOffer.set",
2564
+ args,
2565
+ () => {
2566
+ const problem = signupOfferFlagsProblem(args);
2567
+ if (problem) failValidation(problem);
2568
+ return signupOfferPayload(args);
2569
+ },
2570
+ () => target2(args, "offer handle")
2571
+ )
2572
+ );
2573
+ var signupOfferEnd = leaf(
2574
+ "shopify.change.signupOffer.end",
2575
+ {
2576
+ name: "end",
2577
+ description: "End a sign-up offer at Publish. Existing codes keep their terms. Example: baker shopify change signup-offer end welcome-10"
2578
+ },
2579
+ { handle: TARGET("Offer handle") },
2580
+ async (args) => await stage([{ kind: "shopify.signupOffer.end", target: target2(args, "offer handle") }])
2581
+ );
2582
+ var discountCreate = leaf(
2583
+ "shopify.change.discount.create",
2584
+ {
2585
+ name: "create",
2586
+ 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."
2587
+ },
2588
+ {
2589
+ code: { type: "string", description: "The code shoppers type, e.g. SPRING10", required: false },
2590
+ title: { type: "string", description: "What the store's staff see (default: the code)", required: false },
2591
+ percent: { type: "string", description: "Percentage off, 1-100", required: false },
2592
+ amount: { type: "string", description: "Amount off, as a decimal in the store's currency", required: false },
2593
+ products: { type: "string", description: "Only these products, comma separated", required: false },
2594
+ collections: { type: "string", description: "Only these collections, comma separated", required: false },
2595
+ "min-subtotal": { type: "string", description: "Minimum order amount", required: false },
2596
+ "min-quantity": { type: "string", description: "Minimum number of items", required: false },
2597
+ starts: { type: "string", description: "Start, ISO date-time (default: when published)", required: false },
2598
+ ends: { type: "string", description: "End, ISO date-time (default: no end)", required: false },
2599
+ limit: { type: "string", description: "Total number of uses", required: false },
2600
+ "any-times": { type: "boolean", description: "A customer may use it more than once", required: false },
2601
+ combines: {
2602
+ type: "string",
2603
+ description: "What it combines with, comma separated: product, order, shipping",
2604
+ required: false
2605
+ },
2606
+ ...JSON_ARGS
2607
+ },
2608
+ async (args) => await stageFor("shopify.discountCode.create", args, () => discountPayload(args))
2609
+ );
2610
+ var discountStop = leaf(
2611
+ "shopify.change.discount.stop",
2612
+ {
2613
+ name: "stop",
2614
+ 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"
2615
+ },
2616
+ { code: TARGET("The discount code") },
2617
+ async (args) => await stage([{ kind: "shopify.discountCode.deactivate", target: target2(args, "code") }])
2618
+ );
2619
+ var discountDates = leaf(
2620
+ "shopify.change.discount.dates",
2621
+ {
2622
+ name: "dates",
2623
+ 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"
2624
+ },
2625
+ {
2626
+ code: TARGET("The discount code"),
2627
+ starts: { type: "string", description: "Start, ISO date-time", required: false },
2628
+ ends: { type: "string", description: 'End, ISO date-time, or "never"', required: false }
2629
+ },
2630
+ async (args) => {
2631
+ if (typeof args.starts !== "string" || typeof args.ends !== "string") failValidation("pass --starts and --ends");
2632
+ await stage([
2633
+ {
2634
+ kind: "shopify.discountCode.schedule",
2635
+ target: target2(args, "code"),
2636
+ payload: { startsAt: args.starts, endsAt: args.ends === "never" ? null : args.ends }
2637
+ }
2638
+ ]);
2639
+ }
2640
+ );
2641
+ var redirectCreate = leaf(
2642
+ "shopify.change.redirect.create",
2643
+ {
2644
+ name: "create",
2645
+ 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"
2646
+ },
2647
+ {
2648
+ from: { type: "string", description: "The old path, starting with /", required: false },
2649
+ to: { type: "string", description: "Where it sends shoppers: a path or an https link", required: false },
2650
+ ...JSON_ARGS
2651
+ },
2652
+ async (args) => await stageFor("shopify.urlRedirect.create", args, () => defined({ from: args.from, to: args.to }))
2653
+ );
2654
+ var redirectRemove = leaf(
2655
+ "shopify.change.redirect.remove",
2656
+ {
2657
+ name: "remove",
2658
+ description: "Remove a redirect, by the path it redirects. Example: baker shopify change redirect remove /old"
2659
+ },
2660
+ { from: TARGET("The redirected path") },
2661
+ async (args) => await stage([{ kind: "shopify.urlRedirect.delete", target: target2(args, "path") }])
2662
+ );
2663
+ var campaignAdd = leaf(
2664
+ "shopify.change.campaign.add",
2665
+ {
2666
+ name: "add",
2667
+ 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"
2668
+ },
2669
+ {
2670
+ platform: { type: "string", description: "google-ads | meta", required: false },
2671
+ account: { type: "string", description: "The ad account id", required: false },
2672
+ campaign: { type: "string", description: "The campaign id", required: false },
2673
+ "utm-campaign": { type: "string", description: "utm_campaign its links carry", required: false },
2674
+ "utm-source": { type: "string", description: "utm_source", required: false },
2675
+ "utm-medium": { type: "string", description: "utm_medium", required: false }
2676
+ },
2677
+ async (args) => await stage([
2678
+ {
2679
+ kind: "shopify.marketingActivity.upsert",
2680
+ payload: {
2681
+ platform: args.platform,
2682
+ accountId: args.account,
2683
+ campaignId: args.campaign,
2684
+ utm: { campaign: args["utm-campaign"], source: args["utm-source"], medium: args["utm-medium"] }
2685
+ }
2686
+ }
2687
+ ])
2688
+ );
2689
+ var campaignRemove = leaf(
2690
+ "shopify.change.campaign.remove",
2691
+ {
2692
+ name: "remove",
2693
+ description: "Take a Baker campaign out of Shopify Marketing. Example: baker shopify change campaign remove google-ads:3865083115:2101"
2694
+ },
2695
+ { campaign: TARGET("<platform>:<account>:<campaign>") },
2696
+ async (args) => await stage([{ kind: "shopify.marketingActivity.delete", target: target2(args, "campaign") }])
2697
+ );
2698
+ var measurementReconnect = leaf(
2699
+ "shopify.change.measurement.reconnect",
2700
+ {
2701
+ name: "reconnect",
2702
+ 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."
2703
+ },
2704
+ {},
2705
+ async () => await stage([{ kind: "shopify.webPixel.update" }])
2706
+ );
2707
+ var group = (name, description2, subCommands) => defineCommand3({ meta: { name, description: description2 }, subCommands });
2708
+ var changeCommand = defineCommand3({
2709
+ meta: {
2710
+ name: "change",
2711
+ description: `Stage a change to the connected store. Nothing changes until the user publishes the chat; every
2712
+ change can be put back with \`baker shopify undo\`. Prices and discounts need the user's yes first.
2713
+
2714
+ baker shopify change product update <product> --title \u2026 --seo-title \u2026 --add-tags a,b
2715
+ baker shopify change variant update <variantId> --sku \u2026 --barcode \u2026 --price \u2026
2716
+ baker shopify change field set product:<product> --namespace custom --key \u2026 --type \u2026 --value \u2026
2717
+ baker shopify change collection create --title \u2026 --products a,b
2718
+ baker shopify change discount create --code SPRING10 --percent 10 --ends <iso>
2719
+ baker shopify change redirect create --from /old --to /new
2720
+ baker shopify change campaign add --platform google-ads --account \u2026 --campaign \u2026
2721
+ baker shopify change measurement reconnect`
2722
+ },
2723
+ subCommands: {
2724
+ "signup-offer": group("signup-offer", "Stage or end a sign-up offer", { set: signupOfferSet, end: signupOfferEnd }),
2725
+ product: group("product", "Change a product's title, description, SEO, tags or type", { update: productUpdate }),
2726
+ variant: group("variant", "Change an option's SKU, barcode, weight or price", { update: variantUpdate }),
2727
+ field: group("field", "Set or remove a custom field", { set: fieldSet, remove: fieldRemove }),
2728
+ collection: group("collection", "Create, change, show, hide, fill or delete a collection", {
2729
+ create: collectionCreate,
2730
+ update: collectionUpdate,
2731
+ show: collectionVerb(
2732
+ "show",
2733
+ "shopify.collection.publish",
2734
+ "Show a collection on the online store. Example: baker shopify change collection show summer-boards"
2735
+ ),
2736
+ hide: collectionVerb(
2737
+ "hide",
2738
+ "shopify.collection.unpublish",
2739
+ "Hide a collection from the online store. Example: baker shopify change collection hide summer-boards"
2740
+ ),
2741
+ delete: collectionVerb(
2742
+ "delete",
2743
+ "shopify.collection.delete",
2744
+ "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"
2745
+ ),
2746
+ add: membership(
2747
+ "add",
2748
+ "shopify.collection.addProducts",
2749
+ "Add products to a collection. Example: baker shopify change collection add summer-boards snowboard-a snowboard-b"
2750
+ ),
2751
+ remove: membership(
2752
+ "remove",
2753
+ "shopify.collection.removeProducts",
2754
+ "Remove products from a collection. Example: baker shopify change collection remove summer-boards snowboard-a"
2755
+ )
2756
+ }),
2757
+ discount: group("discount", "Create, stop or reschedule a discount code (needs the user's yes)", {
2758
+ create: discountCreate,
2759
+ stop: discountStop,
2760
+ dates: discountDates
2761
+ }),
2762
+ redirect: group("redirect", "Create or remove a redirect", { create: redirectCreate, remove: redirectRemove }),
2763
+ campaign: group("campaign", "Show or remove a Baker campaign in Shopify Marketing", {
2764
+ add: campaignAdd,
2765
+ remove: campaignRemove
2766
+ }),
2767
+ measurement: group("measurement", "Reconnect Baker's measurement on the store", {
2768
+ reconnect: measurementReconnect
2769
+ })
2770
+ }
2771
+ });
2772
+
2773
+ // src/commands/shopify/changes.ts
2774
+ var changesCommand = leaf(
2775
+ "shopify.changes",
2776
+ {
2777
+ name: "changes",
2778
+ 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)."
2779
+ },
2780
+ {
2781
+ limit: { type: "string", description: "How many, 1-100 (default 20)", required: false },
2782
+ chat: { type: "string", description: "Only one chat's changes", required: false }
2783
+ },
2784
+ async (args) => {
2785
+ try {
2786
+ const data = await apiGet(
2787
+ "/api/shopify/changes",
2788
+ params({ limit: args.limit, chatId: args.chat })
2789
+ );
2790
+ writeJson({ ok: true, hints: changesHints(data), data });
2791
+ } catch (err) {
2792
+ failChange(err);
2793
+ }
2794
+ }
2795
+ );
2796
+ var undoCommand = leaf(
2797
+ "shopify.undo",
2798
+ {
2799
+ name: "undo",
2800
+ 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]"
2801
+ },
2802
+ { changeIds: { type: "positional", description: "changeIds from `baker shopify changes`", required: false } },
2803
+ async (args) => {
2804
+ const changeIds = Array.isArray(args._) ? args._.filter((value) => typeof value === "string") : [];
2805
+ if (changeIds.length === 0) failValidation("pass the changeIds to undo (see `baker shopify changes`)");
2806
+ try {
2807
+ const data = await apiPost("/api/shopify/undo", {
2808
+ chatId: requireChatId(),
2809
+ changeIds
2810
+ });
2811
+ emitStage(data);
2812
+ } catch (err) {
2813
+ failChange(err);
2814
+ }
2815
+ }
2816
+ );
2817
+
2818
+ // src/commands/shopify/draft.ts
2819
+ import { defineCommand as defineCommand4 } from "citty";
2820
+ function refOf(args) {
2821
+ const [ref] = Array.isArray(args._) ? args._ : [];
2822
+ if (typeof ref !== "string" || ref === "")
2823
+ failValidation("pass the staged change's ref (see `baker shopify draft list`)");
2824
+ return ref;
2825
+ }
2826
+ function listHints(data) {
2827
+ if (data.status === "none" || data.count === 0) return ["No store changes are staged on this chat."];
2828
+ if (data.status === "active") {
2829
+ return [
2830
+ "Staged only: these apply when the user publishes the chat. Correct one with `baker shopify draft amend <ref>`, drop one with `remove`."
2831
+ ];
2832
+ }
2833
+ const failed = data.ops.filter((op) => op.result?.status === "failed" || op.result?.status === "skipped");
2834
+ return failed.length > 0 ? [`${failed.length} of ${data.count} changes did not go through \u2014 say which and why; never report them as made.`] : [];
2835
+ }
2836
+ var list2 = leaf(
2837
+ "shopify.draft.list",
2838
+ {
2839
+ name: "list",
2840
+ 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."
2841
+ },
2842
+ { chat: CHAT_READ_ARG },
2843
+ async (args) => {
2844
+ try {
2845
+ const data = await apiPost("/api/shopify/draft/list", {
2846
+ chatId: resolveChatId(args.chat)
2847
+ });
2848
+ writeJson({ ok: true, hints: listHints(data), data });
2849
+ } catch (err) {
2850
+ failChange(err);
2851
+ }
2852
+ }
2853
+ );
2854
+ var show = leaf(
2855
+ "shopify.draft.show",
2856
+ {
2857
+ name: "show",
2858
+ description: "One staged store change in full: what it sends and the store as it was read when it was staged (the before)."
2859
+ },
2860
+ { ref: { type: "positional", description: "The staged change's ref", required: false }, chat: CHAT_READ_ARG },
2861
+ async (args) => {
2862
+ try {
2863
+ const data = await apiPost("/api/shopify/draft/show", { chatId: resolveChatId(args.chat), ref: refOf(args) });
2864
+ writeJson({ ok: true, data });
2865
+ } catch (err) {
2866
+ failChange(err);
2867
+ }
2868
+ }
2869
+ );
2870
+ var amend = leaf(
2871
+ "shopify.draft.amend",
2872
+ {
2873
+ name: "amend",
2874
+ 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.`
2875
+ },
2876
+ {
2877
+ ref: { type: "positional", description: "The staged change's ref", required: false },
2878
+ patch: { type: "string", description: "Inline JSON patch", required: false },
2879
+ file: { type: "string", description: "A JSON file with the patch", required: false }
2880
+ },
2881
+ async (args) => {
2882
+ try {
2883
+ const data = await apiPost("/api/shopify/draft/amend", {
2884
+ chatId: requireChatId(),
2885
+ ref: refOf(args),
2886
+ patch: loadJsonArg(args, "patch")
2887
+ });
2888
+ emitStage(data);
2889
+ } catch (err) {
2890
+ failChange(err);
2891
+ }
2892
+ }
2893
+ );
2894
+ var remove = leaf(
2895
+ "shopify.draft.remove",
2896
+ {
2897
+ name: "remove",
2898
+ description: "Drop one staged store change, and any change that needs it (a new collection's show)."
2899
+ },
2900
+ { ref: { type: "positional", description: "The staged change's ref", required: false } },
2901
+ async (args) => {
2902
+ try {
2903
+ writeJson({
2904
+ ok: true,
2905
+ data: await apiPost("/api/shopify/draft/remove", { chatId: requireChatId(), ref: refOf(args) })
2906
+ });
2907
+ } catch (err) {
2908
+ failChange(err);
2909
+ }
2910
+ }
2911
+ );
2912
+ var clear = leaf(
2913
+ "shopify.draft.clear",
2914
+ { name: "clear", description: "Drop every store change staged on this chat." },
2915
+ {},
2916
+ async () => {
2917
+ try {
2918
+ writeJson({ ok: true, data: await apiPost("/api/shopify/draft/clear", { chatId: requireChatId() }) });
2919
+ } catch (err) {
2920
+ failChange(err);
2921
+ }
2922
+ }
2923
+ );
2924
+ var draftCommand = defineCommand4({
2925
+ meta: { name: "draft", description: "List, show, amend, remove or clear the store changes staged on this chat" },
2926
+ subCommands: { list: list2, show, amend, remove, clear }
2927
+ });
2928
+
2929
+ // src/commands/shopify/promotions.ts
2930
+ import { defineCommand as defineCommand5 } from "citty";
2931
+
2932
+ // src/commands/shopify/landingLinks.ts
2933
+ import { readdir, readFile, stat } from "fs/promises";
2934
+ import path from "path";
2935
+ import { parse } from "@astrojs/compiler/sync";
2936
+ var PAGES_MAX = 300;
2937
+ var FILE_BYTES_MAX = 256 * 1024;
2938
+ function escaped(text) {
2939
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2940
+ }
2941
+ var BUY_COMPONENTS = /* @__PURE__ */ new Set(["ShopifyProduct", "ShopifyBuyButton"]);
2942
+ var STRING_LITERAL = /^\s*(["'`])([^"'`]*)\1\s*$/;
2943
+ var ITEM_PRODUCT = /\bproduct\s*:\s*(["'`])([^"'`]*)\1/g;
2944
+ function namedBy(attribute) {
2945
+ if (attribute.name === "product") {
2946
+ if (attribute.kind === "quoted" || attribute.kind === "template-literal") return [attribute.value];
2947
+ if (attribute.kind === "expression") return [STRING_LITERAL.exec(attribute.value)?.[2] ?? ""];
2948
+ }
2949
+ if (attribute.name === "items" && attribute.kind === "expression") {
2950
+ return [...attribute.value.matchAll(ITEM_PRODUCT)].map((match) => match[2] ?? "");
2951
+ }
2952
+ return [];
2953
+ }
2954
+ function componentProducts(node, into) {
2955
+ if (node.type === "component" && BUY_COMPONENTS.has(node.name)) {
2956
+ for (const attribute of node.attributes) into.push(...namedBy(attribute));
2957
+ }
2958
+ if ("children" in node) for (const child of node.children) componentProducts(child, into);
2959
+ return into;
2960
+ }
2961
+ function buyComponentProducts(file) {
2962
+ if (!file.path.endsWith(".astro")) return [];
2963
+ try {
2964
+ return componentProducts(parse(file.text, void 0).ast, []);
2965
+ } catch {
2966
+ return [];
2967
+ }
2968
+ }
2969
+ function namesProduct(file, keys) {
2970
+ const wanted = new Set([keys.handle.toLowerCase(), ...keys.productId ? [keys.productId] : []].filter(Boolean));
2971
+ return buyComponentProducts(file).some((named) => wanted.has(named.trim().toLowerCase()));
2972
+ }
2973
+ function evidenceIn(file, keys) {
2974
+ const { text } = file;
2975
+ const evidence = [];
2976
+ if (keys.buyUrls.some((url) => url && text.includes(url))) evidence.push("buy_link");
2977
+ const page = new RegExp(`/products/${escaped(keys.handle)}(?![A-Za-z0-9_-])`, "i");
2978
+ if (keys.handle && page.test(text)) evidence.push("product_page");
2979
+ const option = keys.variantIds.some(
2980
+ (id) => new RegExp(`(?:/cart/(?:\\d+:\\d+,)*${id}:|variant=${id}(?!\\d))`).test(text)
2981
+ );
2982
+ if (option) evidence.push("variant_id");
2983
+ if (namesProduct(file, keys)) evidence.push("buy_button");
2984
+ return evidence;
2985
+ }
2986
+ function landingLinksOf(pages, keys) {
2987
+ const bySlug = /* @__PURE__ */ new Map();
2988
+ for (const page of pages) {
2989
+ const found = new Set(page.files.flatMap((file) => evidenceIn(file, keys)));
2990
+ if (found.size === 0) continue;
2991
+ const slug = page.slug.replace(/--\d+$/, "");
2992
+ const held = bySlug.get(slug) ?? /* @__PURE__ */ new Set();
2993
+ for (const evidence of found) held.add(evidence);
2994
+ bySlug.set(slug, held);
2995
+ }
2996
+ const order = ["buy_link", "product_page", "variant_id", "buy_button"];
2997
+ return [...bySlug].map(([slug, evidence]) => ({ slug, evidence: order.filter((entry) => evidence.has(entry)) }));
2998
+ }
2999
+ async function isDir(target3) {
3000
+ try {
3001
+ return (await stat(target3)).isDirectory();
3002
+ } catch {
3003
+ return false;
3004
+ }
3005
+ }
3006
+ async function readWorkspacePages(root) {
3007
+ const dir = path.join(root, "src/pages");
3008
+ if (!await isDir(dir)) return null;
3009
+ let entries;
3010
+ try {
3011
+ entries = await readdir(dir, { withFileTypes: true });
3012
+ } catch {
3013
+ return null;
3014
+ }
3015
+ const slugs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
3016
+ const pages = [];
3017
+ for (const slug of slugs.slice(0, PAGES_MAX)) {
3018
+ const files = [];
3019
+ for (const file of await landingGraphFiles(root, slug)) {
3020
+ try {
3021
+ const absolute = path.join(root, file);
3022
+ if ((await stat(absolute)).size > FILE_BYTES_MAX) continue;
3023
+ files.push({ path: file, text: await readFile(absolute, "utf8") });
3024
+ } catch {
3025
+ }
3026
+ }
3027
+ pages.push({ slug, files });
3028
+ }
3029
+ return { pages, skipped: Math.max(0, slugs.length - PAGES_MAX) };
3030
+ }
3031
+
3032
+ // src/commands/shopify/promotionHints.ts
3033
+ var PLATFORM = { "google-ads": "Google Ads", meta: "Meta" };
3034
+ function plural(count, one, many = `${one}s`) {
3035
+ return `${count} ${count === 1 ? one : many}`;
3036
+ }
3037
+ function pauseCommand(ad) {
3038
+ if (ad.platform === "meta") return `\`baker ads meta ads pause ${ad.id ?? "<id>"}\``;
3039
+ const target3 = `${ad.resource ?? "<resource>"} --customer-id ${ad.account_id ?? "<account_id>"}`;
3040
+ return ad.level === "asset_group" ? `\`baker ads google asset-groups update ${target3} --status PAUSED\`` : `\`baker ads google ads pause ${target3}\``;
3041
+ }
3042
+ function pauseHint(answer) {
3043
+ if (answer.product.stock === "in_stock" || answer.ads.length === 0) return null;
3044
+ const active = answer.ads.filter((ad) => ad.status === "active");
3045
+ if (active.length === 0) {
3046
+ 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.`;
3047
+ }
3048
+ const one = active.length === 1;
3049
+ 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.`;
3050
+ }
3051
+ function promotionHints(answer, scan) {
3052
+ const hints = [];
3053
+ const pause = pauseHint(answer);
3054
+ if (pause) hints.push(pause);
3055
+ if (answer.shopping.length > 0 || answer.catalog_ads > 0) {
3056
+ hints.push(
3057
+ "Shopping campaigns and catalog ads stop showing a product nobody can buy by themselves \u2014 never pause them for one product."
3058
+ );
3059
+ }
3060
+ for (const missed of answer.checked.not_checked) {
3061
+ hints.push(
3062
+ `${PLATFORM[missed.platform] ?? missed.platform} could not be checked (${missed.reason}); say so rather than that nothing runs there.`
3063
+ );
3064
+ }
3065
+ for (const landing of answer.landings.filter((entry) => !entry.published)) {
3066
+ hints.push(`/${landing.slug} links to this product but is not live; no ad can send people to it yet.`);
3067
+ }
3068
+ if (answer.ads.length === 0 && answer.landings.length === 0 && answer.shopping.length === 0) {
3069
+ const nothing = `Nothing advertises \u201C${answer.product.title}\u201D that Baker can see`;
3070
+ hints.push(
3071
+ 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.`
3072
+ );
3073
+ }
3074
+ if (!scan.scanned) {
3075
+ hints.push(
3076
+ "This workspace's pages were not read (no src/pages here), so landings are from Baker's last scan and measured visits only."
3077
+ );
3078
+ } else if (scan.skippedPages > 0) {
3079
+ hints.push(`${plural(scan.skippedPages, "page")} not checked for links to this product (past the 300-page bound).`);
3080
+ }
3081
+ if (!answer.remembered && answer.ads.length > 0) {
3082
+ hints.push(
3083
+ "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."
3084
+ );
3085
+ }
3086
+ return hints;
3087
+ }
3088
+
3089
+ // src/commands/shopify/stockHoldReport.ts
3090
+ var PLATFORM2 = { "google-ads": "Google Ads", meta: "Meta" };
3091
+ var LEVEL = {
3092
+ campaign: "campaign",
3093
+ ad_group: "ad group",
3094
+ ad: "ad",
3095
+ asset_group: "asset group",
3096
+ adset: "ad set"
3097
+ };
3098
+ function adName(ad) {
3099
+ return `${PLATFORM2[ad.platform]} ${LEVEL[ad.level]} ${ad.name ? `\u201C${ad.name}\u201D` : ad.entity_id}`;
3100
+ }
3101
+ var GOOGLE_GROUP = {
3102
+ campaign: "campaigns",
3103
+ ad_group: "ad-groups",
3104
+ ad: "ads",
3105
+ asset_group: "asset-groups",
3106
+ adset: "ads"
3107
+ };
3108
+ var META_GROUP = {
3109
+ campaign: "campaigns",
3110
+ ad_group: "adsets",
3111
+ ad: "ads",
3112
+ asset_group: "ads",
3113
+ adset: "adsets"
3114
+ };
3115
+ function resumeCommand(hold) {
3116
+ if (hold.platform === "meta") return `baker ads meta ${META_GROUP[hold.level]} resume ${hold.entity_id}`;
3117
+ const target3 = `${hold.entity_id} --customer-id ${hold.account_id}`;
3118
+ return hold.level === "asset_group" ? `baker ads google asset-groups update ${target3} --status ENABLED` : `baker ads google ${GOOGLE_GROUP[hold.level]} resume ${target3}`;
3119
+ }
3120
+ function quoted(title, fallback) {
3121
+ return title ? `\u201C${title}\u201D` : fallback;
3122
+ }
3123
+ function heldLine(hold) {
3124
+ const name = adName(hold);
3125
+ const product = quoted(hold.product.title, "the product");
3126
+ switch (hold.verdict) {
3127
+ case "resumable":
3128
+ return `Ready to turn back on when you publish: ${name} \u2014 Baker paused it because ${product} ran out.`;
3129
+ case "still_out_of_stock":
3130
+ return `Stays paused: ${name} \u2014 ${product} still cannot be bought.`;
3131
+ case "changed_since":
3132
+ return `Left as it is: ${name} \u2014 someone changed it after Baker paused it when ${product} ran out, so check it yourself.`;
3133
+ case "still_promotes_out_of_stock":
3134
+ return `Stays paused: ${name} \u2014 it also sends people to ${quoted(hold.verdict_detail, "another product")}, which is still out of stock.`;
3135
+ case "cannot_confirm":
3136
+ 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.`;
3137
+ case "already_active":
3138
+ return `Nothing to do: ${name} is already running again.`;
3139
+ default:
3140
+ return null;
3141
+ }
3142
+ }
3143
+ var CLOSED_HOW = {
3144
+ resumed_by_back_in_stock_run: "turned back on by an earlier back-in-stock run",
3145
+ changed_by_baker: "turned back on by other work published in Baker",
3146
+ already_active: "found running again, turned back on by someone",
3147
+ removed: "removed",
3148
+ product_removed: "let go when the product left the store"
3149
+ };
3150
+ function plural2(count, one) {
3151
+ return `${count} ${count === 1 ? one : `${one}s`}`;
3152
+ }
3153
+ function nothingHeldLines(answer, asked, product) {
3154
+ if (!asked.product) {
3155
+ 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."];
3156
+ }
3157
+ if (answer.pending_sessions.length > 0) {
3158
+ return [
3159
+ `Baker's pause for ${product} is still waiting for your review and was never published, so there is nothing to turn back on.`
3160
+ ];
3161
+ }
3162
+ if (answer.recently_closed.length > 0) {
3163
+ return answer.recently_closed.map(
3164
+ (hold) => `Already handled: Baker paused ${adName(hold)} when ${product} ran out, and it was later ${CLOSED_HOW[hold.resolved_reason]}.`
3165
+ );
3166
+ }
3167
+ return [`Baker has nothing paused because ${product} ran out, so there is nothing to turn back on.`];
3168
+ }
3169
+ function stockHoldReport(answer, asked) {
3170
+ const product = quoted(answer.product?.title, "the product that came back");
3171
+ const held = answer.holds.filter((hold) => hold.state === "held");
3172
+ const tell_user = [
3173
+ ...held.length === 0 ? nothingHeldLines(answer, asked, product) : [],
3174
+ ...held.flatMap((hold) => heldLine(hold) ?? []),
3175
+ ...answer.not_paused.map((ad) => `Left as it is: ${adName(ad)} \u2014 Baker did not pause it when ${product} ran out.`),
3176
+ ...answer.pending_sessions.map(
3177
+ (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.`
3178
+ )
3179
+ ];
3180
+ const resume = held.filter((hold) => hold.verdict === "resumable").map((hold) => ({ ad: adName(hold), command: resumeCommand(hold) }));
3181
+ const pages = answer.pages_changed.map((run) => ({
3182
+ pages: run.pages.map((slug) => `/${slug}`),
3183
+ read: `baker chats diff ${run.chat_id} --surface landings --full`
3184
+ }));
3185
+ const hints = [
3186
+ resume.length > 0 ? "Stage each `resume` command, and nothing else." : `Stage nothing${asked.product ? ` for ${product}` : ""}.`,
3187
+ "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.",
3188
+ ...pages.map(
3189
+ (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}.`
3190
+ )
3191
+ ];
3192
+ const compact = { tell_user, resume, ...pages.length > 0 ? { pages } : {} };
3193
+ if (!asked.full) return { data: compact, hints };
3194
+ const { store: _store, ...everything } = answer;
3195
+ return { data: { ...compact, ...everything }, hints };
3196
+ }
3197
+
3198
+ // src/commands/shopify/promotions.ts
3199
+ registerSchema({
3200
+ command: "shopify.promotions",
3201
+ 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 \u201CRestart ads when a product is back\u201D, if the company has it, 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.",
3202
+ args: {
3203
+ product: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
3204
+ full: {
3205
+ ...fullArg,
3206
+ 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)"
3207
+ }
3208
+ }
3209
+ });
3210
+ registerSchema({
3211
+ command: "shopify.stock-holds",
3212
+ 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.",
3213
+ args: {
3214
+ product: { type: "string", description: "Only one product's holds: its id, handle or link", required: false },
3215
+ all: { type: "boolean", description: "Resolved holds too (resumed or released)", required: false, default: false },
3216
+ full: fullArg
3217
+ }
3218
+ });
3219
+ function promotionsShown(answer, full) {
3220
+ if (full) return answer;
3221
+ const { remembered: _remembered, ...shown } = answer;
3222
+ return shown;
3223
+ }
3224
+ var promotionsCommand = defineCommand5({
3225
+ meta: {
3226
+ name: "promotions",
3227
+ description: "What advertises one product: ads that lead to it, landings that link to it, Shopping that shows it. Example: baker shopify promotions 10310413942969"
3228
+ },
3229
+ args: {
3230
+ product: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
3231
+ full: {
3232
+ type: "boolean",
3233
+ description: "Add campaign and ad group or ad set ids and URLs",
3234
+ required: false,
3235
+ default: false
3236
+ }
3237
+ },
3238
+ run: async ({ args }) => {
3239
+ try {
3240
+ const positionals2 = Array.isArray(args._) ? args._ : [];
3241
+ if (positionals2.length > 1) {
3242
+ throw new ApiError("VALIDATION_ERROR", "product: Pass one product id, handle or product link.");
3243
+ }
3244
+ const { product, store } = await apiGet(
3245
+ "/api/shopify/product",
3246
+ params({ ref: args.product })
3247
+ );
3248
+ const scan = await readWorkspacePages(process.cwd());
3249
+ const linked = scan ? landingLinksOf(scan.pages, {
3250
+ handle: product.handle,
3251
+ productId: product.productId,
3252
+ variantIds: product.variants.map((variant) => variant.variantId),
3253
+ buyUrls: product.variants.flatMap((variant) => variant.buyUrl ? [variant.buyUrl] : [])
3254
+ }) : void 0;
3255
+ const answer = await apiPost("/api/shopify/promotions", {
3256
+ product: product.productId,
3257
+ ...linked ? { linked_landings: linked.slice(0, 50) } : {},
3258
+ scanned: scan !== null,
3259
+ ...args.full ? { full: true } : {}
3260
+ });
3261
+ emit(promotionsShown(answer, args.full === true), { store }, [
3262
+ ...storeStateHints(store),
3263
+ ...promotionHints(answer, { scanned: scan !== null, skippedPages: scan?.skipped ?? 0 })
3264
+ ]);
3265
+ } catch (err) {
3266
+ fail(err);
3267
+ }
3268
+ }
3269
+ });
3270
+ var stockHoldsCommand = defineCommand5({
3271
+ meta: {
3272
+ name: "stock-holds",
3273
+ description: "The ads Baker paused because a product ran out, and whether each may be resumed now. Example: baker shopify stock-holds --product 10310413942969"
3274
+ },
3275
+ args: {
3276
+ product: { type: "string", description: "Only one product: its id, handle or link", required: false },
3277
+ all: { type: "boolean", description: "Resolved holds too", required: false, default: false },
3278
+ full: { type: "boolean", description: "Everything Baker keeps", required: false, default: false }
3279
+ },
3280
+ run: async ({ args }) => {
3281
+ try {
3282
+ const answer = await apiGet(
3283
+ "/api/shopify/stock-holds",
3284
+ params({ product: args.product, all: args.all, full: args.full })
3285
+ );
3286
+ const report = stockHoldReport(answer, { product: args.product !== void 0, full: args.full === true });
3287
+ emit(report.data, { store: answer.store, count: answer.holds.length }, [
3288
+ ...storeStateHints(answer.store),
3289
+ ...report.hints
3290
+ ]);
3291
+ } catch (err) {
3292
+ fail(err);
3293
+ }
3294
+ }
3295
+ });
3296
+
3297
+ // ../api/src/shopify/signups.ts
3298
+ import { z as z4 } from "zod";
3299
+ var SIGNUP_CODE_PATTERN = /^[A-Z0-9]{2,12}-[0-9A-HJKMNP-TV-Z]{8}$/;
3300
+ var SIGNUP_STATES = [
3301
+ "waiting",
3302
+ "ready",
3303
+ "used",
3304
+ "expired",
3305
+ "already",
3306
+ "ended",
3307
+ "none",
3308
+ "unavailable"
3309
+ ];
3310
+ var CODE_STATES = /* @__PURE__ */ new Set(["ready", "used", "expired"]);
3311
+ var shopifySignupReplySchema = z4.object({
3312
+ state: z4.enum(SIGNUP_STATES),
3313
+ code: z4.string().regex(SIGNUP_CODE_PATTERN).optional(),
3314
+ endsAt: z4.string().min(1).max(40).optional()
3315
+ }).strict().refine(
3316
+ (reply) => CODE_STATES.has(reply.state) ? reply.code !== void 0 && reply.endsAt !== void 0 : true,
3317
+ "a code state carries the code and when it ends"
3318
+ ).refine(
3319
+ (reply) => CODE_STATES.has(reply.state) ? true : reply.code === void 0 && reply.endsAt === void 0,
3320
+ "only the browser the code was given to is told it"
3321
+ );
3322
+ var shopifySignupResponseSchema = z4.union([
3323
+ z4.object({ ok: z4.literal(true), signup: shopifySignupReplySchema }).passthrough(),
3324
+ z4.object({ error: z4.string(), signup: shopifySignupReplySchema }).passthrough()
3325
+ ]);
3326
+ var signupOfferStatusSchema = z4.enum(["active", "ended", "staged"]);
3327
+ var shopifySignupOfferViewSchema = z4.object({
3328
+ handle: z4.string(),
3329
+ name: z4.string(),
3330
+ /** "10% off everything, each code valid 14 days, at most 1,000 codes". */
3331
+ summary: z4.string(),
3332
+ status: signupOfferStatusSchema,
3333
+ validForDays: z4.number(),
3334
+ endsAt: z4.string().optional(),
3335
+ maxCodes: z4.number().optional(),
3336
+ codesGiven: z4.number()
3337
+ });
3338
+ var shopifySignupOffersResponseSchema = z4.object({
3339
+ /** The company has a store connection Baker can use (connected, not waiting on a reconnect). */
3340
+ connected: z4.boolean(),
3341
+ /** The store allows Baker to add customers (sign-ups work). */
3342
+ canAddCustomers: z4.boolean(),
3343
+ /** The store allows Baker to make discount codes (offers give codes). */
3344
+ canGiveCodes: z4.boolean(),
3345
+ offers: z4.array(shopifySignupOfferViewSchema),
3346
+ /** Sign-ups the store didn't take in the last 7 days: for a missing permission, and for anything else. */
3347
+ refusedLast7Days: z4.object({ permission: z4.number(), other: z4.number() })
3348
+ });
3349
+
3350
+ // src/commands/shopify/signupOffers.ts
3351
+ import { defineCommand as defineCommand6 } from "citty";
3352
+ var description = "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.";
3353
+ registerSchema({ command: "shopify.signupOffers", description, args: {} });
3354
+ var signupOffersCommand = defineCommand6({
3355
+ meta: { name: "signup-offers", description },
3356
+ async run() {
3357
+ try {
3358
+ const data = shopifySignupOffersResponseSchema.parse(
3359
+ await apiGet("/api/shopify/signup-offers", params({ chatId: optionalChatId() }))
3360
+ );
3361
+ const hints = [];
3362
+ if (!data.canAddCustomers)
3363
+ hints.push(
3364
+ '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.'
3365
+ );
3366
+ if (!data.canGiveCodes)
3367
+ hints.push(
3368
+ "Codes need the store to allow discounts: use request_connection for Shopify and keep building the page."
3369
+ );
3370
+ if (data.offers.some((offer) => offer.status === "ended"))
3371
+ hints.push(
3372
+ "Forms naming ended offers still add customers but give no code: stage a new offer or remove offer from the Form."
3373
+ );
3374
+ if (data.offers.length === 0)
3375
+ hints.push(
3376
+ "No sign-up offer exists yet. Omit offer for sign-ups without a code, or stage one after the user approves its terms."
3377
+ );
3378
+ writeJson({ ok: true, hints, data });
3379
+ } catch (error) {
3380
+ fail(error);
3381
+ }
3382
+ }
3383
+ });
3384
+
3385
+ // src/commands/shopify/tracking.ts
3386
+ import { defineCommand as defineCommand7 } from "citty";
3387
+ var audit = leaf(
3388
+ "shopify.tracking.audit",
3389
+ {
3390
+ name: "audit",
3391
+ 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."
3392
+ },
3393
+ { full: { type: "boolean", description: "Add the facts behind each check", required: false } },
3394
+ async (args) => {
3395
+ try {
3396
+ const data = await apiPost("/api/shopify/tracking/audit", { full: args.full === true });
3397
+ writeJson({ ok: true, hints: auditHints(data), data });
3398
+ } catch (err) {
3399
+ fail(err);
3400
+ }
3401
+ }
3402
+ );
3403
+ var trackingCommand = defineCommand7({
3404
+ meta: { name: "tracking", description: "Check how the store is measured: baker shopify tracking audit" },
3405
+ subCommands: { audit }
3406
+ });
3407
+
3408
+ // src/commands/shopify/index.ts
3409
+ registerSchema({
3410
+ command: "shopify.store",
3411
+ 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`).',
3412
+ args: {
3413
+ full: {
3414
+ ...fullArg,
3415
+ description: "Add the store's products page in Shopify admin, every country, each policy's text and the store's brand"
3416
+ }
3417
+ }
3418
+ });
3419
+ registerSchema({
3420
+ command: "shopify.products",
3421
+ 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.",
3422
+ args: {
3423
+ search: { type: "string", description: "Words in the title, vendor, type or tags", required: false },
3424
+ collection: {
3425
+ type: "string",
3426
+ description: "Only one collection's products: its handle or id (`baker shopify collections`)",
3427
+ required: false
3428
+ },
3429
+ stock: {
3430
+ type: "string",
3431
+ description: "in_stock | partly_out (some options out) | out_of_stock (every option out)",
3432
+ required: false,
3433
+ enum: ["in_stock", "partly_out", "out_of_stock"]
3434
+ },
3435
+ limit: { type: "number", description: "Products per page, 1-100 (default 25)", required: false },
3436
+ cursor: {
3437
+ type: "string",
3438
+ description: "meta.next_cursor from the previous page, with the same filters",
3439
+ required: false
3440
+ },
3441
+ full: { ...fullArg, description: "Add vendor, type, option count, photo link, visibility and status" }
3442
+ }
3443
+ });
3444
+ registerSchema({
3445
+ command: "shopify.product",
3446
+ 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.",
3447
+ args: {
3448
+ ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
3449
+ "variants-cursor": {
3450
+ type: "string",
3451
+ description: "data.variantsNextCursor from the previous call: the next page of options (same flags)",
3452
+ required: false
3453
+ },
3454
+ "out-of-stock": {
3455
+ type: "boolean",
3456
+ description: "Only the options that are out of stock, whatever their position",
3457
+ required: false,
3458
+ default: false
3459
+ },
3460
+ content: {
3461
+ type: "boolean",
3462
+ description: "Its whole details: formatted description, custom fields by name (long ones previewed), rating, subscriptions, YouTube/3D links, translations",
3463
+ required: false,
3464
+ default: false
3465
+ },
3466
+ field: {
3467
+ type: "string",
3468
+ description: 'One custom field whole, by its name or its key (e.g. "Size chart" or custom.size_chart)',
3469
+ required: false
3470
+ },
3471
+ variant: {
3472
+ type: "string",
3473
+ description: "With --field: the option whose field to return, by its variantId (options share their fields, e.g. --field custom.fit --variant 102)",
3474
+ required: false
3475
+ },
3476
+ quantity: {
3477
+ type: "number",
3478
+ description: "How many of the option each buy link puts in the cart, 1-99 (default 1)",
3479
+ required: false
3480
+ },
3481
+ full: {
3482
+ ...fullArg,
3483
+ 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"
3484
+ }
3485
+ }
3486
+ });
3487
+ registerSchema({
3488
+ command: "shopify.collections",
3489
+ description: "The connected store's collections, with the handle `--collection` takes and how many products Shopify counts in each. `--full` adds each one's description, image and SEO.",
3490
+ args: {
3491
+ search: { type: "string", description: "Words in the collection's title or handle", required: false },
3492
+ full: { ...fullArg, description: "Add each collection's description, image and SEO" }
3493
+ }
3494
+ });
3495
+ registerSchema({
3496
+ command: "shopify.stock",
3497
+ 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.",
3498
+ args: {
3499
+ collection: { type: "string", description: "Only one collection: its handle or id", required: false },
3500
+ limit: { type: "number", description: "Products per list, 1-200 (default 50)", required: false },
3501
+ full: { ...fullArg, description: "Add each option's SKU and each product's Baker link" }
3502
+ }
3503
+ });
3504
+ registerSchema({
3505
+ command: "shopify.sales",
3506
+ 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.",
3507
+ args: {
3508
+ days: {
3509
+ type: "number",
3510
+ description: "The last N days: 1-395 by day, product, campaign or landing; 1-3650 by weekday, hour or month (default 30)",
3511
+ required: false
3512
+ },
3513
+ all: {
3514
+ type: "boolean",
3515
+ description: "Every order the store has (by weekday, hour, month or month_of_year only)",
3516
+ required: false,
3517
+ default: false
3518
+ },
3519
+ by: {
3520
+ type: "string",
3521
+ description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
3522
+ required: false,
3523
+ enum: ["day", "product", "campaign", "landing", "weekday", "hour", "month", "month_of_year"]
3524
+ },
3525
+ limit: {
3526
+ type: "number",
3527
+ description: "Rows of a breakdown, 1-100 (by campaign or landing the rest are folded into one 'other' row)",
3528
+ required: false
3529
+ },
3530
+ full: {
3531
+ ...fullArg,
3532
+ description: "Add each row's source, medium, campaign, click platform, landing id or product id"
3533
+ }
3534
+ }
3535
+ });
3536
+ var salesCommand = defineCommand8({
3537
+ meta: {
3538
+ name: "sales",
3539
+ 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"
3540
+ },
3541
+ args: {
3542
+ days: {
3543
+ type: "string",
3544
+ description: "The last N days (1-395; up to 3650 by weekday, hour or month)",
3545
+ required: false
3546
+ },
3547
+ all: {
3548
+ type: "boolean",
3549
+ description: "Every order (by weekday, hour or month only)",
3550
+ required: false,
3551
+ default: false
3552
+ },
3553
+ by: {
3554
+ type: "string",
3555
+ description: "day | product | campaign | landing | weekday | hour | month | month_of_year (default day)",
3556
+ required: false
3557
+ },
3558
+ limit: { type: "string", description: "Rows of a breakdown, 1-100", required: false },
3559
+ full: { type: "boolean", description: "Add sources, campaigns and ids per row", required: false, default: false }
3560
+ },
3561
+ run: async ({ args }) => {
3562
+ try {
3563
+ const answer = await apiGet(
3564
+ "/api/shopify/sales",
3565
+ params({ days: args.days, all: args.all, by: args.by, limit: args.limit, full: args.full })
3566
+ );
3567
+ const { store, salesReporting, history, ...data } = answer;
3568
+ emit(data, { store, salesReporting, history }, [...storeStateHints(store), ...salesHints(answer)]);
3569
+ } catch (err) {
3570
+ fail(err);
3571
+ }
3572
+ }
3573
+ });
3574
+ var storeCommand = defineCommand8({
3575
+ meta: {
3576
+ name: "store",
3577
+ description: "The connected Shopify store: currency, countries, how many products, what is out of stock. Example: baker shopify store"
3578
+ },
3579
+ args: {
3580
+ full: { type: "boolean", description: "Add the admin link and every country", required: false, default: false }
3581
+ },
3582
+ run: async ({ args }) => {
3583
+ try {
3584
+ const answer = await apiGet("/api/shopify/store", params({ full: args.full }));
3585
+ const data = answer.canChange ? { ...answer.summary, canChange: answer.canChange } : answer.summary;
3586
+ emit(data, { store: answer.store }, [...storeStateHints(answer.store), ...storeHints(answer.summary)]);
3587
+ } catch (err) {
3588
+ fail(err);
3589
+ }
3590
+ }
3591
+ });
3592
+ var productsCommand = defineCommand8({
3593
+ meta: {
3594
+ name: "products",
3595
+ description: 'What the store sells, with price and stock. Example: baker shopify products --search "snowboard" --stock out_of_stock'
3596
+ },
3597
+ args: {
3598
+ search: { type: "string", description: "Words in the title, vendor, type or tags", required: false },
3599
+ collection: { type: "string", description: "A collection's handle or id", required: false },
3600
+ stock: { type: "string", description: "in_stock | partly_out | out_of_stock", required: false },
3601
+ limit: { type: "string", description: "Products per page, 1-100 (default 25)", required: false },
3602
+ cursor: { type: "string", description: "meta.next_cursor from the previous page", required: false },
3603
+ full: {
3604
+ type: "boolean",
3605
+ description: "Add vendor, type, photo link, visibility, status",
3606
+ required: false,
3607
+ default: false
3608
+ }
3609
+ },
3610
+ run: async ({ args }) => {
3611
+ try {
3612
+ const answer = await apiGet(
3613
+ "/api/shopify/products",
3614
+ params({
3615
+ search: args.search,
3616
+ collection: args.collection,
3617
+ stock: args.stock,
3618
+ limit: args.limit,
3619
+ cursor: args.cursor,
3620
+ full: args.full
3621
+ })
3622
+ );
3623
+ const filtered = Boolean(args.search || args.collection || args.stock);
3624
+ emit(
3625
+ answer.products,
3626
+ {
3627
+ store: answer.store,
3628
+ count: answer.products.length,
3629
+ ...answer.nextCursor ? { next_cursor: answer.nextCursor } : {}
3630
+ },
3631
+ [...storeStateHints(answer.store), ...productsHints(answer, { filtered, paged: Boolean(args.cursor) })]
3632
+ );
3633
+ } catch (err) {
3634
+ fail(err);
3635
+ }
3636
+ }
3637
+ });
3638
+ var productCommand = defineCommand8({
3639
+ meta: {
3640
+ name: "product",
3641
+ 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"
3642
+ },
3643
+ args: {
3644
+ ref: { type: "positional", description: "Product id, handle, or a link to the product", required: true },
3645
+ "variants-cursor": {
3646
+ type: "string",
3647
+ description: "data.variantsNextCursor from the previous call",
3648
+ required: false
3649
+ },
3650
+ "out-of-stock": { type: "boolean", description: "Only the options out of stock", required: false, default: false },
3651
+ content: {
3652
+ type: "boolean",
3653
+ description: "Its whole details: formatted description, custom fields, subscriptions, translations",
3654
+ required: false,
3655
+ default: false
3656
+ },
3657
+ field: { type: "string", description: "One custom field whole, by its name or key", required: false },
3658
+ variant: {
3659
+ type: "string",
3660
+ description: "With --field: the option whose field it is, by its variantId",
3661
+ required: false
3662
+ },
3663
+ quantity: {
3664
+ type: "string",
3665
+ description: "How many each buy link puts in the cart, 1-99 (default 1)",
3666
+ required: false
3667
+ },
3668
+ full: { type: "boolean", description: "Everything Baker keeps about it", required: false, default: false }
3669
+ },
3670
+ run: async ({ args }) => {
3671
+ try {
3672
+ const positionals2 = Array.isArray(args._) ? args._ : [];
3673
+ if (positionals2.length > 1) {
3674
+ throw new ApiError(
3675
+ "VALIDATION_ERROR",
3676
+ "ref: Pass one product id, handle or product link \u2014 quote a link, and search by words with `baker shopify products --search`."
3677
+ );
3678
+ }
3679
+ const answer = await apiGet(
3680
+ "/api/shopify/product",
3681
+ params({
3682
+ ref: args.ref,
3683
+ variantsCursor: args["variants-cursor"],
3684
+ outOfStock: args["out-of-stock"],
3685
+ content: args.content,
3686
+ field: args.field,
3687
+ variant: args.variant,
3688
+ quantity: args.quantity,
3689
+ full: args.full
3690
+ })
3691
+ );
3692
+ emit(answer.product, { store: answer.store }, [
3693
+ ...storeStateHints(answer.store),
3694
+ ...productHints(answer.product, answer.store)
3695
+ ]);
3696
+ } catch (err) {
3697
+ fail(err);
3698
+ }
3699
+ }
3700
+ });
3701
+ var collectionsCommand = defineCommand8({
3702
+ meta: {
3703
+ name: "collections",
3704
+ description: "The store's collections, with the handles --collection takes. Example: baker shopify collections --full"
3705
+ },
3706
+ args: {
3707
+ search: { type: "string", description: "Words in the title or handle", required: false },
3708
+ full: { type: "boolean", description: "Add description, image and SEO", required: false, default: false }
3709
+ },
3710
+ run: async ({ args }) => {
3711
+ try {
3712
+ const answer = await apiGet(
3713
+ "/api/shopify/collections",
3714
+ params({ search: args.search, full: args.full })
3715
+ );
3716
+ emit(
3717
+ answer.collections,
3718
+ { store: answer.store, count: answer.collections.length },
3719
+ storeStateHints(answer.store)
3720
+ );
3721
+ } catch (err) {
3722
+ fail(err);
3723
+ }
3724
+ }
3725
+ });
3726
+ var stockCommand = defineCommand8({
3727
+ meta: {
3728
+ name: "stock",
3729
+ description: "Every product with options out of stock, naming them. Example: baker shopify stock"
3730
+ },
3731
+ args: {
3732
+ collection: { type: "string", description: "A collection's handle or id", required: false },
3733
+ limit: { type: "string", description: "Products per list, 1-200 (default 50)", required: false },
3734
+ full: { type: "boolean", description: "Add SKUs and Baker links", required: false, default: false }
3735
+ },
3736
+ run: async ({ args }) => {
3737
+ try {
3738
+ const answer = await apiGet(
3739
+ "/api/shopify/stock",
3740
+ params({ collection: args.collection, limit: args.limit, full: args.full })
3741
+ );
3742
+ const { store, ...data } = answer;
3743
+ const limit2 = Number(args.limit ?? 50);
3744
+ emit(data, { store }, [...storeStateHints(store), ...stockHints(answer, Number.isFinite(limit2) ? limit2 : 50)]);
3745
+ } catch (err) {
3746
+ fail(err);
3747
+ }
3748
+ }
3749
+ });
3750
+ var shopifyCommand = defineCommand8({
3751
+ meta: {
3752
+ name: "shopify",
3753
+ description: `Read the connected Shopify store as Baker keeps it \u2014 products, prices by country, stock, collections,
3754
+ product photos, what it sold, who buys, where and what, and what advertises each product \u2014 and stage
3755
+ changes to it (\`change\`), applied when the user publishes the chat and undone with \`undo\`.
3756
+
3757
+ Examples:
3758
+ baker shopify store # currency, countries, how many products, what is out of stock
3759
+ baker shopify products --search "snowboard" # what we sell, with price and stock
3760
+ baker shopify products --stock out_of_stock # --stock in_stock|partly_out|out_of_stock, --collection <handle>
3761
+ baker shopify product the-complete-snowboard # one product: options with buyUrl, prices, photos, videos
3762
+ baker shopify product <handle> --content # its whole details: description, custom fields, subscriptions
3763
+ baker shopify product <handle> --field "Size chart" # one custom field, whole
3764
+ baker shopify product <handle> --field custom.fit --variant <variantId> # one option's field, whole
3765
+ baker shopify product <handle> --quantity 2 # buy links that put 2 in the cart
3766
+ baker shopify product <id> --out-of-stock # only its options that are out of stock
3767
+ baker shopify stock # every product with options out of stock
3768
+ baker shopify collections # collections, with handles for --collection
3769
+ baker shopify sales --days 7 --by campaign # what sold and which campaigns brought it (--by day|product|landing)
3770
+ baker shopify sales --all --by weekday # when buyers order, in the store's time (--by hour|month|month_of_year)
3771
+ baker shopify countries --days 90 # where buyers are, with growth (--country DE for its regions)
3772
+ baker shopify best-sellers --days 90 # what sells (--view variants|trend|together|repeat|momentum)
3773
+ baker shopify buyers --days 90 --by channel # who buys, repeat rate, segments, value by acquisition channel
3774
+ baker shopify buyer 1042 # one numbered customer's orders
3775
+ baker shopify segments # best, loyal, at risk, new, one-time \u2014 and who to find more of
3776
+ baker shopify cohorts --months 12 # buyers by first-order month: who came back
3777
+ baker shopify promotions <product> # what advertises it: ads, landings, Shopping (read before pausing)
3778
+ baker shopify stock-holds --product <product> # what Baker paused because it ran out: what to resume, what to tell the user
3779
+ baker shopify change product update <product> --title "\u2026" # stage a store change (product|variant|field|collection|discount|redirect|campaign|measurement)
3780
+ baker shopify draft list # the store changes staged on this chat
3781
+ baker shopify changes # what Baker changed in the store; baker shopify undo <changeId> puts one back
3782
+ baker shopify tracking audit # is the store measured right? (reads only)
3783
+
3784
+ Product photos are already in the image library: place them by imageId
3785
+ (baker images download <imageIds> --out src/pages/<page>/_images/), never by a cdn.shopify.com link.
3786
+ Product videos are in the video library: place them with <Video playbackId="\u2026" />.
3787
+ On a landing, the buy button is <ShopifyProduct product="\u2026"/> \u2014 never a buyUrl or a Shopify link
3788
+ written by hand. buyUrl is for ads, emails and messages; never use storeUrl (a page to look at) for it.
3789
+ NO_SHOPIFY_CONNECTION means no store is connected: follow error.fix. SHOPIFY_NOT_AVAILABLE means it cannot be
3790
+ connected here yet: carry on without it.
3791
+ Full guide: __tooling__/docs/tools/baker/shopify.md`
3792
+ },
3793
+ subCommands: {
3794
+ "signup-offers": signupOffersCommand,
3795
+ store: storeCommand,
3796
+ products: productsCommand,
3797
+ product: productCommand,
3798
+ collections: collectionsCommand,
3799
+ stock: stockCommand,
3800
+ sales: salesCommand,
3801
+ buyers: buyersCommand,
3802
+ buyer: buyerCommand,
3803
+ countries: countriesCommand,
3804
+ "best-sellers": bestSellersCommand,
3805
+ cohorts: cohortsCommand,
3806
+ segments: segmentsCommand,
3807
+ promotions: promotionsCommand,
3808
+ "stock-holds": stockHoldsCommand,
3809
+ change: changeCommand,
3810
+ draft: draftCommand,
3811
+ changes: changesCommand,
3812
+ undo: undoCommand,
3813
+ tracking: trackingCommand
3814
+ }
3815
+ });
3816
+ export {
3817
+ shopifyCommand
3818
+ };
3819
+ //# sourceMappingURL=shopify-RQKO5TMR.js.map