@koda-sl/baker-cli 0.384.0-dev.4699b7308 → 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.
- package/README.md +1098 -217
- package/canvas/tiktok-captions-composition/index.html +5 -6
- package/canvas/tiktok-captions-composition/meta.json +5 -0
- package/dist/account-4UEZAY5T.js +49 -0
- package/dist/account-4UEZAY5T.js.map +1 -0
- package/dist/account-OINZG6UP.js +80 -0
- package/dist/account-OINZG6UP.js.map +1 -0
- package/dist/account-QMDCUUSG.js +79 -0
- package/dist/account-QMDCUUSG.js.map +1 -0
- package/dist/account-reads-ZKKDYHK5.js +218 -0
- package/dist/account-reads-ZKKDYHK5.js.map +1 -0
- package/dist/account-writes-N4JDIDRB.js +66 -0
- package/dist/account-writes-N4JDIDRB.js.map +1 -0
- package/dist/accounts-AEBQFCNH.js +114 -0
- package/dist/accounts-AEBQFCNH.js.map +1 -0
- package/dist/accounts-EBGJ2YO7.js +611 -0
- package/dist/accounts-EBGJ2YO7.js.map +1 -0
- package/dist/accounts-HP4BLKK5.js +75 -0
- package/dist/accounts-HP4BLKK5.js.map +1 -0
- package/dist/accounts-YE2SQ3UQ.js +77 -0
- package/dist/accounts-YE2SQ3UQ.js.map +1 -0
- package/dist/accounts-Z4CLGQXJ.js +115 -0
- package/dist/accounts-Z4CLGQXJ.js.map +1 -0
- package/dist/actions-ADRIEQZX.js +55 -0
- package/dist/actions-ADRIEQZX.js.map +1 -0
- package/dist/active-entities-YB46ADQN.js +103 -0
- package/dist/active-entities-YB46ADQN.js.map +1 -0
- package/dist/activities-DUAIQJLN.js +74 -0
- package/dist/activities-DUAIQJLN.js.map +1 -0
- package/dist/ad-group-writes-HL3EZ3BT.js +170 -0
- package/dist/ad-group-writes-HL3EZ3BT.js.map +1 -0
- package/dist/ad-groups-FGS5JJGN.js +68 -0
- package/dist/ad-groups-FGS5JJGN.js.map +1 -0
- package/dist/ad-writes-LX4ACUHB.js +174 -0
- package/dist/ad-writes-LX4ACUHB.js.map +1 -0
- package/dist/add-5CUXNOUM.js +97 -0
- package/dist/add-5CUXNOUM.js.map +1 -0
- package/dist/add-6OXOQNET.js +302 -0
- package/dist/add-6OXOQNET.js.map +1 -0
- package/dist/add-E66HBIZV.js +105 -0
- package/dist/add-E66HBIZV.js.map +1 -0
- package/dist/add-ROK3ETQC.js +104 -0
- package/dist/add-ROK3ETQC.js.map +1 -0
- package/dist/add-accounts-YME5SCXD.js +91 -0
- package/dist/add-accounts-YME5SCXD.js.map +1 -0
- package/dist/ads-65R3WMDU.js +128 -0
- package/dist/ads-65R3WMDU.js.map +1 -0
- package/dist/ads-O63J7BDF.js +113 -0
- package/dist/ads-O63J7BDF.js.map +1 -0
- package/dist/ads-TFZ5PXKS.js +77 -0
- package/dist/ads-TFZ5PXKS.js.map +1 -0
- package/dist/ads-XSVRH45E.js +40 -0
- package/dist/ads-XSVRH45E.js.map +1 -0
- package/dist/adsets-Y62JOOTV.js +176 -0
- package/dist/adsets-Y62JOOTV.js.map +1 -0
- package/dist/advertisers-AIC4QPEK.js +120 -0
- package/dist/advertisers-AIC4QPEK.js.map +1 -0
- package/dist/advertisers-LJ2HKJ4W.js +125 -0
- package/dist/advertisers-LJ2HKJ4W.js.map +1 -0
- package/dist/analytics-N4PGXXEQ.js +1976 -0
- package/dist/analytics-N4PGXXEQ.js.map +1 -0
- package/dist/analytics-X3DR3UVG.js +433 -0
- package/dist/analytics-X3DR3UVG.js.map +1 -0
- package/dist/animate-E3SKQ6SI.js +480 -0
- package/dist/animate-E3SKQ6SI.js.map +1 -0
- package/dist/assign-Y7HGSQVJ.js +77 -0
- package/dist/assign-Y7HGSQVJ.js.map +1 -0
- package/dist/audience-size-SWX5YJQF.js +94 -0
- package/dist/audience-size-SWX5YJQF.js.map +1 -0
- package/dist/audiences-5A6BCWSA.js +268 -0
- package/dist/audiences-5A6BCWSA.js.map +1 -0
- package/dist/audiences-6QOCASVV.js +78 -0
- package/dist/audiences-6QOCASVV.js.map +1 -0
- package/dist/audiences-O67NGIP5.js +85 -0
- package/dist/audiences-O67NGIP5.js.map +1 -0
- package/dist/audit-2KVLWSAI.js +151 -0
- package/dist/audit-2KVLWSAI.js.map +1 -0
- package/dist/audit-HWK24VXI.js +88 -0
- package/dist/audit-HWK24VXI.js.map +1 -0
- package/dist/autocomplete-JJSGC5WU.js +111 -0
- package/dist/autocomplete-JJSGC5WU.js.map +1 -0
- package/dist/avatars-XIEKQ77J.js +38 -0
- package/dist/avatars-XIEKQ77J.js.map +1 -0
- package/dist/bid-pricing-D2HGRWZL.js +99 -0
- package/dist/bid-pricing-D2HGRWZL.js.map +1 -0
- package/dist/brand-AYYWRGIE.js +22 -0
- package/dist/brand-AYYWRGIE.js.map +1 -0
- package/dist/brief-3HUIDK5F.js +490 -0
- package/dist/brief-3HUIDK5F.js.map +1 -0
- package/dist/businesses-65EWTK5E.js +53 -0
- package/dist/businesses-65EWTK5E.js.map +1 -0
- package/dist/campaign-groups-6HCYR2JX.js +95 -0
- package/dist/campaign-groups-6HCYR2JX.js.map +1 -0
- package/dist/campaign-writes-M6JLF5AC.js +170 -0
- package/dist/campaign-writes-M6JLF5AC.js.map +1 -0
- package/dist/campaign-writes-Y6QPRQ4E.js +165 -0
- package/dist/campaign-writes-Y6QPRQ4E.js.map +1 -0
- package/dist/campaigns-6SM2ZSIH.js +76 -0
- package/dist/campaigns-6SM2ZSIH.js.map +1 -0
- package/dist/campaigns-7RKJZRY2.js +107 -0
- package/dist/campaigns-7RKJZRY2.js.map +1 -0
- package/dist/campaigns-KRI25JDZ.js +109 -0
- package/dist/campaigns-KRI25JDZ.js.map +1 -0
- package/dist/campaigns-TD7ZB5DG.js +110 -0
- package/dist/campaigns-TD7ZB5DG.js.map +1 -0
- package/dist/canvas-DMYY2DWI.js +46 -0
- package/dist/canvas-DMYY2DWI.js.map +1 -0
- package/dist/capabilities-3WJTXS2O.js +144 -0
- package/dist/capabilities-3WJTXS2O.js.map +1 -0
- package/dist/cards-7HRC4UQQ.js +85 -0
- package/dist/cards-7HRC4UQQ.js.map +1 -0
- package/dist/catalog-N6MDMIVL.js +64 -0
- package/dist/catalog-N6MDMIVL.js.map +1 -0
- package/dist/catalogs-PB46L7FH.js +128 -0
- package/dist/catalogs-PB46L7FH.js.map +1 -0
- package/dist/changes-SASUQK62.js +135 -0
- package/dist/changes-SASUQK62.js.map +1 -0
- package/dist/chatgpt-JKBSAG25.js +47 -0
- package/dist/chatgpt-JKBSAG25.js.map +1 -0
- package/dist/chats-WRVSQQB2.js +262 -0
- package/dist/chats-WRVSQQB2.js.map +1 -0
- package/dist/chunk-2FGKJY63.js +455 -0
- package/dist/chunk-2FGKJY63.js.map +1 -0
- package/dist/chunk-2GVXNKAE.js +42 -0
- package/dist/chunk-2GVXNKAE.js.map +1 -0
- package/dist/chunk-2J3PTIQB.js +479 -0
- package/dist/chunk-2J3PTIQB.js.map +1 -0
- package/dist/chunk-2J7XC2NO.js +346 -0
- package/dist/chunk-2J7XC2NO.js.map +1 -0
- package/dist/{chunk-VLUGGVJL.js → chunk-2UFRHW3T.js} +12 -4
- package/dist/chunk-2UFRHW3T.js.map +1 -0
- package/dist/chunk-2Y5V23PU.js +29 -0
- package/dist/chunk-2Y5V23PU.js.map +1 -0
- package/dist/chunk-3ACMDRSE.js +39 -0
- package/dist/chunk-3ACMDRSE.js.map +1 -0
- package/dist/chunk-3EWF7VII.js +1079 -0
- package/dist/chunk-3EWF7VII.js.map +1 -0
- package/dist/chunk-3LYMIKKN.js +625 -0
- package/dist/chunk-3LYMIKKN.js.map +1 -0
- package/dist/chunk-3M6RNCDD.js +26 -0
- package/dist/chunk-3M6RNCDD.js.map +1 -0
- package/dist/chunk-3MDO4RR2.js +310 -0
- package/dist/chunk-3MDO4RR2.js.map +1 -0
- package/dist/chunk-3NPOIHSX.js +6667 -0
- package/dist/chunk-3NPOIHSX.js.map +1 -0
- package/dist/chunk-3SK7TWGP.js +103 -0
- package/dist/chunk-3SK7TWGP.js.map +1 -0
- package/dist/chunk-3VXQYDJN.js +1743 -0
- package/dist/chunk-3VXQYDJN.js.map +1 -0
- package/dist/chunk-44VXKO65.js +106 -0
- package/dist/chunk-44VXKO65.js.map +1 -0
- package/dist/chunk-4NZ47VXO.js +137 -0
- package/dist/chunk-4NZ47VXO.js.map +1 -0
- package/dist/chunk-4X3UR372.js +225 -0
- package/dist/chunk-4X3UR372.js.map +1 -0
- package/dist/chunk-553XSIFW.js +304 -0
- package/dist/chunk-553XSIFW.js.map +1 -0
- package/dist/chunk-5FEBUCJU.js +25 -0
- package/dist/chunk-5FEBUCJU.js.map +1 -0
- package/dist/chunk-5HYSKH77.js +315 -0
- package/dist/chunk-5HYSKH77.js.map +1 -0
- package/dist/chunk-5MBPIONY.js +29 -0
- package/dist/chunk-5MBPIONY.js.map +1 -0
- package/dist/chunk-5UCP4EEQ.js +111 -0
- package/dist/chunk-5UCP4EEQ.js.map +1 -0
- package/dist/chunk-5VPJNNJL.js +494 -0
- package/dist/chunk-5VPJNNJL.js.map +1 -0
- package/dist/{chunk-VZEDAFAR.js → chunk-6YYSUISH.js} +47 -8
- package/dist/chunk-6YYSUISH.js.map +1 -0
- package/dist/chunk-7OHWEF3I.js +98 -0
- package/dist/chunk-7OHWEF3I.js.map +1 -0
- package/dist/chunk-A636XRD7.js +159 -0
- package/dist/chunk-A636XRD7.js.map +1 -0
- package/dist/chunk-A76FRBSF.js +297 -0
- package/dist/chunk-A76FRBSF.js.map +1 -0
- package/dist/chunk-AADVCEL4.js +817 -0
- package/dist/chunk-AADVCEL4.js.map +1 -0
- package/dist/chunk-AG62YO7C.js +96 -0
- package/dist/chunk-AG62YO7C.js.map +1 -0
- package/dist/chunk-AIRIT4GF.js +75 -0
- package/dist/chunk-AIRIT4GF.js.map +1 -0
- package/dist/chunk-AMCY5YCB.js +206 -0
- package/dist/chunk-AMCY5YCB.js.map +1 -0
- package/dist/chunk-AP3TP6VG.js +139 -0
- package/dist/chunk-AP3TP6VG.js.map +1 -0
- package/dist/chunk-APP475IQ.js +474 -0
- package/dist/chunk-APP475IQ.js.map +1 -0
- package/dist/chunk-AR7PU7BB.js +76 -0
- package/dist/chunk-AR7PU7BB.js.map +1 -0
- package/dist/chunk-B4S72KSS.js +172 -0
- package/dist/chunk-B4S72KSS.js.map +1 -0
- package/dist/chunk-BIPIL4QA.js +486 -0
- package/dist/chunk-BIPIL4QA.js.map +1 -0
- package/dist/chunk-BPUSL5Y2.js +58 -0
- package/dist/chunk-BPUSL5Y2.js.map +1 -0
- package/dist/chunk-BR32GHYD.js +65 -0
- package/dist/chunk-BR32GHYD.js.map +1 -0
- package/dist/chunk-CAMX6OHF.js +3182 -0
- package/dist/chunk-CAMX6OHF.js.map +1 -0
- package/dist/{chunk-MF7YA62W.js → chunk-CUL3VB4L.js} +10 -5
- package/dist/chunk-CUL3VB4L.js.map +1 -0
- package/dist/chunk-CVEY353K.js +66 -0
- package/dist/chunk-CVEY353K.js.map +1 -0
- package/dist/chunk-CWXADUKL.js +1599 -0
- package/dist/chunk-CWXADUKL.js.map +1 -0
- package/dist/chunk-D5FY4FAK.js +24 -0
- package/dist/chunk-D5FY4FAK.js.map +1 -0
- package/dist/chunk-DGCUPJZD.js +39 -0
- package/dist/chunk-DGCUPJZD.js.map +1 -0
- package/dist/chunk-DNCLWSHO.js +41 -0
- package/dist/chunk-DNCLWSHO.js.map +1 -0
- package/dist/chunk-DUPURL26.js +97 -0
- package/dist/chunk-DUPURL26.js.map +1 -0
- package/dist/chunk-E2ZOSNF5.js +96 -0
- package/dist/chunk-E2ZOSNF5.js.map +1 -0
- package/dist/chunk-E3HYHT7C.js +274 -0
- package/dist/chunk-E3HYHT7C.js.map +1 -0
- package/dist/chunk-EDLZGTGN.js +216 -0
- package/dist/chunk-EDLZGTGN.js.map +1 -0
- package/dist/chunk-EI5QN4UO.js +363 -0
- package/dist/chunk-EI5QN4UO.js.map +1 -0
- package/dist/{chunk-XHC4LMK4.js → chunk-EIGXX6KI.js} +16 -8
- package/dist/chunk-EIGXX6KI.js.map +1 -0
- package/dist/chunk-EPDE7ZRU.js +174 -0
- package/dist/chunk-EPDE7ZRU.js.map +1 -0
- package/dist/chunk-ERSX3MKH.js +34 -0
- package/dist/chunk-ERSX3MKH.js.map +1 -0
- package/dist/chunk-EVDRF4CI.js +509 -0
- package/dist/chunk-EVDRF4CI.js.map +1 -0
- package/dist/chunk-F4IEILTR.js +382 -0
- package/dist/chunk-F4IEILTR.js.map +1 -0
- package/dist/chunk-F7HRLPR5.js +67 -0
- package/dist/chunk-F7HRLPR5.js.map +1 -0
- package/dist/chunk-FEG6MN4H.js +84 -0
- package/dist/chunk-FEG6MN4H.js.map +1 -0
- package/dist/chunk-FKOECEVC.js +126 -0
- package/dist/chunk-FKOECEVC.js.map +1 -0
- package/dist/chunk-FMGW2NZI.js +106 -0
- package/dist/chunk-FMGW2NZI.js.map +1 -0
- package/dist/chunk-FPZKKNDK.js +98 -0
- package/dist/chunk-FPZKKNDK.js.map +1 -0
- package/dist/chunk-FUVCY63N.js +169 -0
- package/dist/chunk-FUVCY63N.js.map +1 -0
- package/dist/chunk-GBNJQNDS.js +19 -0
- package/dist/chunk-GBNJQNDS.js.map +1 -0
- package/dist/chunk-GIWHHCD6.js +145 -0
- package/dist/chunk-GIWHHCD6.js.map +1 -0
- package/dist/chunk-GOJXKIFQ.js +1605 -0
- package/dist/chunk-GOJXKIFQ.js.map +1 -0
- package/dist/chunk-GRQ2AXDX.js +9 -0
- package/dist/chunk-GRQ2AXDX.js.map +1 -0
- package/dist/chunk-GXDLWBOY.js +73 -0
- package/dist/chunk-GXDLWBOY.js.map +1 -0
- package/dist/chunk-HAE4WWFQ.js +455 -0
- package/dist/chunk-HAE4WWFQ.js.map +1 -0
- package/dist/chunk-I2DNBEV6.js +4227 -0
- package/dist/chunk-I2DNBEV6.js.map +1 -0
- package/dist/chunk-I4FTS5EE.js +455 -0
- package/dist/chunk-I4FTS5EE.js.map +1 -0
- package/dist/chunk-IELPT4K2.js +74 -0
- package/dist/chunk-IELPT4K2.js.map +1 -0
- package/dist/chunk-IJMWEO4P.js +57 -0
- package/dist/chunk-IJMWEO4P.js.map +1 -0
- package/dist/chunk-IREMSVKW.js +175 -0
- package/dist/chunk-IREMSVKW.js.map +1 -0
- package/dist/chunk-IVIJJXXT.js +62 -0
- package/dist/chunk-IVIJJXXT.js.map +1 -0
- package/dist/chunk-JD3W3ON7.js +188 -0
- package/dist/chunk-JD3W3ON7.js.map +1 -0
- package/dist/chunk-JLWBK3VC.js +5187 -0
- package/dist/chunk-JLWBK3VC.js.map +1 -0
- package/dist/chunk-JPK34QPS.js +134 -0
- package/dist/chunk-JPK34QPS.js.map +1 -0
- package/dist/chunk-JUY47NZ2.js +183 -0
- package/dist/chunk-JUY47NZ2.js.map +1 -0
- package/dist/chunk-JXMZEOHV.js +333 -0
- package/dist/chunk-JXMZEOHV.js.map +1 -0
- package/dist/chunk-K37K447U.js +51 -0
- package/dist/chunk-K37K447U.js.map +1 -0
- package/dist/chunk-K6TDB2KV.js +1442 -0
- package/dist/chunk-K6TDB2KV.js.map +1 -0
- package/dist/chunk-KIGGMKAE.js +66 -0
- package/dist/chunk-KIGGMKAE.js.map +1 -0
- package/dist/chunk-KKJ4KJCW.js +29 -0
- package/dist/chunk-KKJ4KJCW.js.map +1 -0
- package/dist/chunk-KONA5PSR.js +55 -0
- package/dist/chunk-KONA5PSR.js.map +1 -0
- package/dist/chunk-KVHRW7KO.js +60 -0
- package/dist/chunk-KVHRW7KO.js.map +1 -0
- package/dist/chunk-LA6PPSNA.js +65 -0
- package/dist/chunk-LA6PPSNA.js.map +1 -0
- package/dist/chunk-LKPJADL2.js +74 -0
- package/dist/chunk-LKPJADL2.js.map +1 -0
- package/dist/chunk-LWAEHLKV.js +74 -0
- package/dist/chunk-LWAEHLKV.js.map +1 -0
- package/dist/chunk-M6UNISGO.js +66 -0
- package/dist/chunk-M6UNISGO.js.map +1 -0
- package/dist/chunk-MIJAGBEP.js +23992 -0
- package/dist/chunk-MIJAGBEP.js.map +1 -0
- package/dist/chunk-MP4EXZ45.js +3151 -0
- package/dist/chunk-MP4EXZ45.js.map +1 -0
- package/dist/chunk-MYTDK3AL.js +2102 -0
- package/dist/chunk-MYTDK3AL.js.map +1 -0
- package/dist/chunk-MZQ5DX6S.js +125 -0
- package/dist/chunk-MZQ5DX6S.js.map +1 -0
- package/dist/chunk-NFSLRRIE.js +77 -0
- package/dist/chunk-NFSLRRIE.js.map +1 -0
- package/dist/chunk-NOK7MSVJ.js +533 -0
- package/dist/chunk-NOK7MSVJ.js.map +1 -0
- package/dist/chunk-NR5RZDAW.js +592 -0
- package/dist/chunk-NR5RZDAW.js.map +1 -0
- package/dist/chunk-O2EVJALZ.js +133 -0
- package/dist/chunk-O2EVJALZ.js.map +1 -0
- package/dist/chunk-O6UM5WWH.js +275 -0
- package/dist/chunk-O6UM5WWH.js.map +1 -0
- package/dist/chunk-O6XZIQXZ.js +6212 -0
- package/dist/chunk-O6XZIQXZ.js.map +1 -0
- package/dist/chunk-OIL6PU5W.js +35 -0
- package/dist/chunk-OIL6PU5W.js.map +1 -0
- package/dist/chunk-P4NSJS54.js +20 -0
- package/dist/chunk-P4NSJS54.js.map +1 -0
- package/dist/chunk-P5T7OZKV.js +26 -0
- package/dist/chunk-P5T7OZKV.js.map +1 -0
- package/dist/chunk-PFBQERWX.js +10 -0
- package/dist/chunk-PFBQERWX.js.map +1 -0
- package/dist/chunk-PSWVSLJU.js +56 -0
- package/dist/chunk-PSWVSLJU.js.map +1 -0
- package/dist/chunk-PY2NJ6XZ.js +191 -0
- package/dist/chunk-PY2NJ6XZ.js.map +1 -0
- package/dist/chunk-PZ4OM2LK.js +45 -0
- package/dist/chunk-PZ4OM2LK.js.map +1 -0
- package/dist/chunk-PZNOJ76W.js +541 -0
- package/dist/chunk-PZNOJ76W.js.map +1 -0
- package/dist/chunk-Q6ULQMWG.js +41 -0
- package/dist/chunk-Q6ULQMWG.js.map +1 -0
- package/dist/chunk-QKFD6PPX.js +57 -0
- package/dist/chunk-QKFD6PPX.js.map +1 -0
- package/dist/chunk-QMLGJ5OW.js +2891 -0
- package/dist/chunk-QMLGJ5OW.js.map +1 -0
- package/dist/chunk-R4TPDVBD.js +207 -0
- package/dist/chunk-R4TPDVBD.js.map +1 -0
- package/dist/chunk-R7QV5N2S.js +846 -0
- package/dist/chunk-R7QV5N2S.js.map +1 -0
- package/dist/chunk-RR3KT2E7.js +665 -0
- package/dist/chunk-RR3KT2E7.js.map +1 -0
- package/dist/chunk-SEPPFM5S.js +228 -0
- package/dist/chunk-SEPPFM5S.js.map +1 -0
- package/dist/chunk-SQ7DRYCC.js +150 -0
- package/dist/chunk-SQ7DRYCC.js.map +1 -0
- package/dist/chunk-SYULM665.js +610 -0
- package/dist/chunk-SYULM665.js.map +1 -0
- package/dist/chunk-TKT3OHZ6.js +170 -0
- package/dist/chunk-TKT3OHZ6.js.map +1 -0
- package/dist/chunk-TNOSX3PJ.js +136 -0
- package/dist/chunk-TNOSX3PJ.js.map +1 -0
- package/dist/chunk-TTVKI54A.js +160 -0
- package/dist/chunk-TTVKI54A.js.map +1 -0
- package/dist/chunk-U7ZVXFS3.js +23 -0
- package/dist/chunk-U7ZVXFS3.js.map +1 -0
- package/dist/chunk-UI6BKYQD.js +225 -0
- package/dist/chunk-UI6BKYQD.js.map +1 -0
- package/dist/chunk-UKLDW4UW.js +46 -0
- package/dist/chunk-UKLDW4UW.js.map +1 -0
- package/dist/chunk-V6XXXEXZ.js +76 -0
- package/dist/chunk-V6XXXEXZ.js.map +1 -0
- package/dist/chunk-V7BW2CIQ.js +92 -0
- package/dist/chunk-V7BW2CIQ.js.map +1 -0
- package/dist/chunk-VSU5R34H.js +76 -0
- package/dist/chunk-VSU5R34H.js.map +1 -0
- package/dist/chunk-W4SA4CMR.js +296 -0
- package/dist/chunk-W4SA4CMR.js.map +1 -0
- package/dist/chunk-WFUMNDCE.js +111 -0
- package/dist/chunk-WFUMNDCE.js.map +1 -0
- package/dist/chunk-WHWNXLTD.js +166 -0
- package/dist/chunk-WHWNXLTD.js.map +1 -0
- package/dist/chunk-WN4KX7GE.js +25 -0
- package/dist/chunk-WN4KX7GE.js.map +1 -0
- package/dist/chunk-XIY7P54H.js +84 -0
- package/dist/chunk-XIY7P54H.js.map +1 -0
- package/dist/chunk-YFPLNMPZ.js +337 -0
- package/dist/chunk-YFPLNMPZ.js.map +1 -0
- package/dist/chunk-YMBB4JBD.js +1830 -0
- package/dist/chunk-YMBB4JBD.js.map +1 -0
- package/dist/chunk-YQFM2P5W.js +197 -0
- package/dist/chunk-YQFM2P5W.js.map +1 -0
- package/dist/chunk-YQSBHWOI.js +41 -0
- package/dist/chunk-YTF3IVMF.js +149 -0
- package/dist/chunk-YTF3IVMF.js.map +1 -0
- package/dist/chunk-Z3ENMCFE.js +94 -0
- package/dist/chunk-Z3ENMCFE.js.map +1 -0
- package/dist/chunk-Z7MWSXLG.js +94 -0
- package/dist/chunk-Z7MWSXLG.js.map +1 -0
- package/dist/chunk-ZIXHS6AM.js +42 -0
- package/dist/chunk-ZIXHS6AM.js.map +1 -0
- package/dist/chunk-ZJ7TQUCU.js +4410 -0
- package/dist/chunk-ZJ7TQUCU.js.map +1 -0
- package/dist/chunk-ZNQRDLSS.js +52 -0
- package/dist/chunk-ZNQRDLSS.js.map +1 -0
- package/dist/claim-ROWH6JVB.js +197 -0
- package/dist/claim-ROWH6JVB.js.map +1 -0
- package/dist/cli.js +104 -69986
- package/dist/cli.js.map +1 -1
- package/dist/client-EURRWXPW.js +19 -0
- package/dist/code-FKQW7V7N.js +76 -0
- package/dist/code-FKQW7V7N.js.map +1 -0
- package/dist/companies-MD2MZKU6.js +195 -0
- package/dist/companies-MD2MZKU6.js.map +1 -0
- package/dist/complete-FCAJM6Z4.js +81 -0
- package/dist/complete-FCAJM6Z4.js.map +1 -0
- package/dist/config-OZOHIAOJ.js +88 -0
- package/dist/config-OZOHIAOJ.js.map +1 -0
- package/dist/connect-4YXFMXYX.js +42 -0
- package/dist/content-PCGSX2QU.js +85 -0
- package/dist/content-PCGSX2QU.js.map +1 -0
- package/dist/conversation-UX5NZ3IO.js +93 -0
- package/dist/conversation-UX5NZ3IO.js.map +1 -0
- package/dist/conversion-report-7QSWB2FY.js +125 -0
- package/dist/conversion-report-7QSWB2FY.js.map +1 -0
- package/dist/conversion-upload-command-XESIAQPU.js +438 -0
- package/dist/conversion-upload-command-XESIAQPU.js.map +1 -0
- package/dist/conversion-writes-BGOVLBS5.js +120 -0
- package/dist/conversion-writes-BGOVLBS5.js.map +1 -0
- package/dist/conversions-IYIWW4O7.js +76 -0
- package/dist/conversions-IYIWW4O7.js.map +1 -0
- package/dist/conversions-RLIJAE7E.js +135 -0
- package/dist/conversions-RLIJAE7E.js.map +1 -0
- package/dist/countries-5OBUVLLD.js +82 -0
- package/dist/countries-5OBUVLLD.js.map +1 -0
- package/dist/create-CUJLJUAQ.js +138 -0
- package/dist/create-CUJLJUAQ.js.map +1 -0
- package/dist/create-FENOT6DX.js +236 -0
- package/dist/create-FENOT6DX.js.map +1 -0
- package/dist/create-OFZQMOWO.js +59 -0
- package/dist/create-OFZQMOWO.js.map +1 -0
- package/dist/create-Z5SS2AWB.js +207 -0
- package/dist/create-Z5SS2AWB.js.map +1 -0
- package/dist/creatives-UJW2QBLH.js +182 -0
- package/dist/creatives-UJW2QBLH.js.map +1 -0
- package/dist/creatives-XI2DGQUZ.js +87 -0
- package/dist/creatives-XI2DGQUZ.js.map +1 -0
- package/dist/creatives-YLXQSROD.js +51 -0
- package/dist/creatives-YLXQSROD.js.map +1 -0
- package/dist/credentials-CH73QMD2.js +287 -0
- package/dist/credentials-CH73QMD2.js.map +1 -0
- package/dist/critique-C4WRQ4AJ.js +1449 -0
- package/dist/critique-C4WRQ4AJ.js.map +1 -0
- package/dist/critique-ZBNJ6MQ3.js +213 -0
- package/dist/critique-ZBNJ6MQ3.js.map +1 -0
- package/dist/crm-MJTT6GOJ.js +224 -0
- package/dist/crm-MJTT6GOJ.js.map +1 -0
- package/dist/crm-ZXHSEFYI.js +318 -0
- package/dist/crm-ZXHSEFYI.js.map +1 -0
- package/dist/crop-B52KIQ4O.js +113 -0
- package/dist/crop-B52KIQ4O.js.map +1 -0
- package/dist/currency-GGWF5DMU.js +88 -0
- package/dist/currency-GGWF5DMU.js.map +1 -0
- package/dist/custom-conversions-7WDBTC4X.js +125 -0
- package/dist/custom-conversions-7WDBTC4X.js.map +1 -0
- package/dist/customers-7E4HBSEY.js +56 -0
- package/dist/decide-PK7WNSQ2.js +199 -0
- package/dist/decide-PK7WNSQ2.js.map +1 -0
- package/dist/delete-56LKNEGA.js +71 -0
- package/dist/delete-56LKNEGA.js.map +1 -0
- package/dist/delete-DQY4ZSFN.js +54 -0
- package/dist/delete-DQY4ZSFN.js.map +1 -0
- package/dist/delete-EZ7HHOOD.js +69 -0
- package/dist/delete-EZ7HHOOD.js.map +1 -0
- package/dist/delete-JV4KAA6Y.js +66 -0
- package/dist/delete-JV4KAA6Y.js.map +1 -0
- package/dist/delete-YONJW4UZ.js +63 -0
- package/dist/delete-YONJW4UZ.js.map +1 -0
- package/dist/delete-YSYVFL6W.js +71 -0
- package/dist/delete-YSYVFL6W.js.map +1 -0
- package/dist/demographics-6N3TTYWC.js +138 -0
- package/dist/demographics-6N3TTYWC.js.map +1 -0
- package/dist/describe-AAVYIFE3.js +97 -0
- package/dist/describe-AAVYIFE3.js.map +1 -0
- package/dist/describe-UQFLWVFR.js +97 -0
- package/dist/describe-UQFLWVFR.js.map +1 -0
- package/dist/dimensions-QDXBG2XC.js +101 -0
- package/dist/dimensions-QDXBG2XC.js.map +1 -0
- package/dist/discard-S3NGGUSK.js +66 -0
- package/dist/discard-S3NGGUSK.js.map +1 -0
- package/dist/discover-6TEBPVNC.js +93 -0
- package/dist/discover-6TEBPVNC.js.map +1 -0
- package/dist/discover-SBZPB7T2.js +172 -0
- package/dist/discover-SBZPB7T2.js.map +1 -0
- package/dist/domain-AMAR76PB.js +98 -0
- package/dist/domain-AMAR76PB.js.map +1 -0
- package/dist/download-LV3GPOWV.js +299 -0
- package/dist/download-LV3GPOWV.js.map +1 -0
- package/dist/draft-6QZ7WMWM.js +217 -0
- package/dist/draft-6QZ7WMWM.js.map +1 -0
- package/dist/draft-DDFFTZ43.js +233 -0
- package/dist/draft-DDFFTZ43.js.map +1 -0
- package/dist/draft-FIKJXNIQ.js +281 -0
- package/dist/draft-FIKJXNIQ.js.map +1 -0
- package/dist/draft-GYGWTMBU.js +307 -0
- package/dist/draft-GYGWTMBU.js.map +1 -0
- package/dist/draft-KXL62WFS.js +328 -0
- package/dist/draft-KXL62WFS.js.map +1 -0
- package/dist/draft-N4LFPKTA.js +100 -0
- package/dist/draft-N4LFPKTA.js.map +1 -0
- package/dist/draft-POSLJCWG.js +306 -0
- package/dist/draft-POSLJCWG.js.map +1 -0
- package/dist/draft-XPO5RWST.js +103 -0
- package/dist/draft-XPO5RWST.js.map +1 -0
- package/dist/engine/index.d.ts +213 -127
- package/dist/engine/index.js +18 -4
- package/dist/{env-4P5HOGNA.js → env-TQ2XS2KY.js} +12 -2
- package/dist/env-TQ2XS2KY.js.map +1 -0
- package/dist/experiment-FQNKMORZ.js +1397 -0
- package/dist/experiment-FQNKMORZ.js.map +1 -0
- package/dist/export-4IZQZAPF.js +168 -0
- package/dist/export-4IZQZAPF.js.map +1 -0
- package/dist/extract-DZMGVQ7D.js +113 -0
- package/dist/extract-DZMGVQ7D.js.map +1 -0
- package/dist/facets-223V5P47.js +114 -0
- package/dist/facets-223V5P47.js.map +1 -0
- package/dist/favorites-DD2LNSTG.js +178 -0
- package/dist/favorites-DD2LNSTG.js.map +1 -0
- package/dist/feed-TUVFNX2U.js +177 -0
- package/dist/feed-TUVFNX2U.js.map +1 -0
- package/dist/feedback-MIW23MI2.js +66 -0
- package/dist/feedback-MIW23MI2.js.map +1 -0
- package/dist/feedback-record-RGKRKPGZ.js +175 -0
- package/dist/feedback-record-RGKRKPGZ.js.map +1 -0
- package/dist/fetch-O6UHNXWB.js +139 -0
- package/dist/fetch-O6UHNXWB.js.map +1 -0
- package/dist/fetch-TZ4UCMJK.js +176 -0
- package/dist/fetch-TZ4UCMJK.js.map +1 -0
- package/dist/find-4XKIBF4D.js +134 -0
- package/dist/find-4XKIBF4D.js.map +1 -0
- package/dist/fit-ODVVXV2Q.js +45 -0
- package/dist/fit-ODVVXV2Q.js.map +1 -0
- package/dist/flows-HVWBFZ7T.js +247 -0
- package/dist/flows-HVWBFZ7T.js.map +1 -0
- package/dist/follow-RCOXTZGY.js +94 -0
- package/dist/follow-RCOXTZGY.js.map +1 -0
- package/dist/follow-competitors-SQ5XBFTK.js +112 -0
- package/dist/follow-competitors-SQ5XBFTK.js.map +1 -0
- package/dist/following-VDU3BHC4.js +113 -0
- package/dist/following-VDU3BHC4.js.map +1 -0
- package/dist/fonts-NBIQAVQN.js +34 -0
- package/dist/fonts-NBIQAVQN.js.map +1 -0
- package/dist/forecast-2GJKBNBM.js +168 -0
- package/dist/forecast-2GJKBNBM.js.map +1 -0
- package/dist/forecast-76MUJBPO.js +104 -0
- package/dist/forecast-76MUJBPO.js.map +1 -0
- package/dist/funding-HTFVZLLG.js +85 -0
- package/dist/funding-HTFVZLLG.js.map +1 -0
- package/dist/funnel-FE2HIRVQ.js +394 -0
- package/dist/funnel-FE2HIRVQ.js.map +1 -0
- package/dist/ga4-2RC4EH7V.js +55 -0
- package/dist/ga4-2RC4EH7V.js.map +1 -0
- package/dist/generate-TPDOBCKQ.js +262 -0
- package/dist/generate-TPDOBCKQ.js.map +1 -0
- package/dist/get-6JNOVI27.js +56 -0
- package/dist/get-6JNOVI27.js.map +1 -0
- package/dist/get-FH2LIEYL.js +63 -0
- package/dist/get-FH2LIEYL.js.map +1 -0
- package/dist/get-GZLCDXXW.js +61 -0
- package/dist/get-GZLCDXXW.js.map +1 -0
- package/dist/get-KVAOPAQW.js +78 -0
- package/dist/get-KVAOPAQW.js.map +1 -0
- package/dist/get-TSPQYEKC.js +59 -0
- package/dist/get-TSPQYEKC.js.map +1 -0
- package/dist/get-TY3RXYNZ.js +63 -0
- package/dist/get-TY3RXYNZ.js.map +1 -0
- package/dist/get-VPW33XTN.js +92 -0
- package/dist/get-VPW33XTN.js.map +1 -0
- package/dist/gif-JFEYPFFI.js +94 -0
- package/dist/gif-JFEYPFFI.js.map +1 -0
- package/dist/google-R6RD4L6Z.js +136 -0
- package/dist/google-R6RD4L6Z.js.map +1 -0
- package/dist/google-VPQNRVIB.js +3190 -0
- package/dist/google-VPQNRVIB.js.map +1 -0
- package/dist/google-ads-JEQMNNMN.js +195 -0
- package/dist/google-ads-JEQMNNMN.js.map +1 -0
- package/dist/group-7AV6UL26.js +43 -0
- package/dist/group-7AV6UL26.js.map +1 -0
- package/dist/group-BVQZEZIC.js +43 -0
- package/dist/group-BVQZEZIC.js.map +1 -0
- package/dist/gsc-SQ42WAQE.js +30 -0
- package/dist/gsc-SQ42WAQE.js.map +1 -0
- package/dist/history-BNNHFURR.js +67 -0
- package/dist/history-BNNHFURR.js.map +1 -0
- package/dist/history-EHUQG4CE.js +133 -0
- package/dist/history-EHUQG4CE.js.map +1 -0
- package/dist/hubspot-JABE4USC.js +779 -0
- package/dist/hubspot-JABE4USC.js.map +1 -0
- package/dist/icon-OYTTSNSL.js +137 -0
- package/dist/icon-OYTTSNSL.js.map +1 -0
- package/dist/ideas-OD76WOQ3.js +32 -0
- package/dist/ideas-OD76WOQ3.js.map +1 -0
- package/dist/images-LY7AWWBK.js +90 -0
- package/dist/images-LY7AWWBK.js.map +1 -0
- package/dist/improve-W6GZNT2A.js +86 -0
- package/dist/improve-W6GZNT2A.js.map +1 -0
- package/dist/ingest-47WON6HT.js +23 -0
- package/dist/ingest-47WON6HT.js.map +1 -0
- package/dist/ingest-HDGGHBSJ.js +107 -0
- package/dist/ingest-HDGGHBSJ.js.map +1 -0
- package/dist/insights-IW7LGAWU.js +423 -0
- package/dist/insights-IW7LGAWU.js.map +1 -0
- package/dist/insights-QH54F56A.js +122 -0
- package/dist/insights-QH54F56A.js.map +1 -0
- package/dist/inspect-AJLG2HGL.js +189 -0
- package/dist/inspect-AJLG2HGL.js.map +1 -0
- package/dist/inspiration-RDQ3Y3N3.js +53 -0
- package/dist/inspiration-RDQ3Y3N3.js.map +1 -0
- package/dist/intent-5GSSCFZ2.js +95 -0
- package/dist/intent-5GSSCFZ2.js.map +1 -0
- package/dist/job-URVXJP75.js +202 -0
- package/dist/job-URVXJP75.js.map +1 -0
- package/dist/job-create-S4SECUVF.js +109 -0
- package/dist/job-create-S4SECUVF.js.map +1 -0
- package/dist/job-status-253SUCTJ.js +74 -0
- package/dist/job-status-253SUCTJ.js.map +1 -0
- package/dist/keep-GPSYDIGJ.js +78 -0
- package/dist/keep-GPSYDIGJ.js.map +1 -0
- package/dist/keyword-gap-NTFDX43B.js +130 -0
- package/dist/keyword-gap-NTFDX43B.js.map +1 -0
- package/dist/keyword-metrics-XSFLMBZX.js +197 -0
- package/dist/keyword-metrics-XSFLMBZX.js.map +1 -0
- package/dist/keywords-for-site-U4JLYXVE.js +128 -0
- package/dist/keywords-for-site-U4JLYXVE.js.map +1 -0
- package/dist/landing-AO72EGJ6.js +28 -0
- package/dist/landing-AO72EGJ6.js.map +1 -0
- package/dist/landings-NEF4PQL2.js +151 -0
- package/dist/landings-NEF4PQL2.js.map +1 -0
- package/dist/languages-3RDPPPTE.js +55 -0
- package/dist/languages-3RDPPPTE.js.map +1 -0
- package/dist/languages-CQX3YOIT.js +40 -0
- package/dist/languages-CQX3YOIT.js.map +1 -0
- package/dist/launches-GBHLJH5S.js +19 -0
- package/dist/launches-GBHLJH5S.js.map +1 -0
- package/dist/layerize-Q56C6LFG.js +190 -0
- package/dist/layerize-Q56C6LFG.js.map +1 -0
- package/dist/leads-JHUM6FYR.js +293 -0
- package/dist/leads-JHUM6FYR.js.map +1 -0
- package/dist/leads-UYZM2XQX.js +90 -0
- package/dist/leads-UYZM2XQX.js.map +1 -0
- package/dist/learn-D2KBGJHZ.js +203 -0
- package/dist/learn-D2KBGJHZ.js.map +1 -0
- package/dist/library-6VUFHTEK.js +109 -0
- package/dist/library-6VUFHTEK.js.map +1 -0
- package/dist/lighthouse-57U2GMPV.js +75 -0
- package/dist/lighthouse-57U2GMPV.js.map +1 -0
- package/dist/line-item-writes-R5GO6GTL.js +208 -0
- package/dist/line-item-writes-R5GO6GTL.js.map +1 -0
- package/dist/line-items-RBMH6YBZ.js +103 -0
- package/dist/line-items-RBMH6YBZ.js.map +1 -0
- package/dist/link-WS2RMCOP.js +64 -0
- package/dist/link-WS2RMCOP.js.map +1 -0
- package/dist/linkedin-FSIALZTS.js +1008 -0
- package/dist/linkedin-FSIALZTS.js.map +1 -0
- package/dist/list-2AE2Q4ZD.js +151 -0
- package/dist/list-2AE2Q4ZD.js.map +1 -0
- package/dist/list-E635Y6M4.js +303 -0
- package/dist/list-E635Y6M4.js.map +1 -0
- package/dist/list-G57ZO4PG.js +50 -0
- package/dist/list-G57ZO4PG.js.map +1 -0
- package/dist/list-JCSWZWDI.js +57 -0
- package/dist/list-JCSWZWDI.js.map +1 -0
- package/dist/list-KT6NXVLV.js +63 -0
- package/dist/list-KT6NXVLV.js.map +1 -0
- package/dist/list-RINCJOAC.js +76 -0
- package/dist/list-RINCJOAC.js.map +1 -0
- package/dist/list-SRNXHS6L.js +51 -0
- package/dist/list-SRNXHS6L.js.map +1 -0
- package/dist/list-YJSO4M5Q.js +65 -0
- package/dist/list-YJSO4M5Q.js.map +1 -0
- package/dist/locations-HW2Y5OJO.js +67 -0
- package/dist/locations-HW2Y5OJO.js.map +1 -0
- package/dist/locations-XOHI3VWI.js +40 -0
- package/dist/locations-XOHI3VWI.js.map +1 -0
- package/dist/log-3HX5BIO6.js +92 -0
- package/dist/log-3HX5BIO6.js.map +1 -0
- package/dist/logo-KXTGIYBI.js +18 -0
- package/dist/logo-KXTGIYBI.js.map +1 -0
- package/dist/lookup-X6O5UNJH.js +22 -0
- package/dist/lookup-X6O5UNJH.js.map +1 -0
- package/dist/make-QH77XOWM.js +633 -0
- package/dist/make-QH77XOWM.js.map +1 -0
- package/dist/map-QABBPYZM.js +404 -0
- package/dist/map-QABBPYZM.js.map +1 -0
- package/dist/mcp-HSD3W3OB.js +463 -0
- package/dist/mcp-HSD3W3OB.js.map +1 -0
- package/dist/media-MI4EE7L4.js +89 -0
- package/dist/media-MI4EE7L4.js.map +1 -0
- package/dist/media-QUTQOBA7.js +15 -0
- package/dist/media-QUTQOBA7.js.map +1 -0
- package/dist/media-sources-HO4DXRX5.js +32 -0
- package/dist/media-sources-HO4DXRX5.js.map +1 -0
- package/dist/members-YEYYJGY6.js +44 -0
- package/dist/members-YEYYJGY6.js.map +1 -0
- package/dist/meta-E3JOUOSS.js +69 -0
- package/dist/meta-E3JOUOSS.js.map +1 -0
- package/dist/metrics-HQ7XJAYQ.js +152 -0
- package/dist/metrics-HQ7XJAYQ.js.map +1 -0
- package/dist/models-PGX3FBHU.js +155 -0
- package/dist/models-PGX3FBHU.js.map +1 -0
- package/dist/normalize-FPJ6GSOY.js +806 -0
- package/dist/normalize-FPJ6GSOY.js.map +1 -0
- package/dist/normalize-GSQWN77S.js +141 -0
- package/dist/normalize-GSQWN77S.js.map +1 -0
- package/dist/options-F5XZMQLK.js +121 -0
- package/dist/options-F5XZMQLK.js.map +1 -0
- package/dist/output-GQFJ2T5B.js +24 -0
- package/dist/output-GQFJ2T5B.js.map +1 -0
- package/dist/page-VTTSBC6Y.js +105 -0
- package/dist/page-VTTSBC6Y.js.map +1 -0
- package/dist/pages-PMZVNVQM.js +66 -0
- package/dist/pages-PMZVNVQM.js.map +1 -0
- package/dist/performance-G67JQE5J.js +24 -0
- package/dist/performance-G67JQE5J.js.map +1 -0
- package/dist/pinterest-UDNGOWDM.js +96 -0
- package/dist/pinterest-UDNGOWDM.js.map +1 -0
- package/dist/pixels-OYZDGCH3.js +108 -0
- package/dist/pixels-OYZDGCH3.js.map +1 -0
- package/dist/pixels-XKGNTCFX.js +60 -0
- package/dist/pixels-XKGNTCFX.js.map +1 -0
- package/dist/plan-DZS3TVIJ.js +207 -0
- package/dist/plan-DZS3TVIJ.js.map +1 -0
- package/dist/preview-DR6MXOGI.js +169 -0
- package/dist/preview-DR6MXOGI.js.map +1 -0
- package/dist/profile-VOVZG44G.js +159 -0
- package/dist/profile-VOVZG44G.js.map +1 -0
- package/dist/promoted-tweet-writes-VCK2ADVJ.js +180 -0
- package/dist/promoted-tweet-writes-VCK2ADVJ.js.map +1 -0
- package/dist/promoted-tweets-4ACBP2RV.js +119 -0
- package/dist/promoted-tweets-4ACBP2RV.js.map +1 -0
- package/dist/properties-MKVF7MUK.js +101 -0
- package/dist/properties-MKVF7MUK.js.map +1 -0
- package/dist/publish-6XVA35T6.js +322 -0
- package/dist/publish-6XVA35T6.js.map +1 -0
- package/dist/query-CNWQ6STR.js +61 -0
- package/dist/query-CNWQ6STR.js.map +1 -0
- package/dist/query-DBSBZ73C.js +247 -0
- package/dist/query-DBSBZ73C.js.map +1 -0
- package/dist/query-QK4U4TEL.js +236 -0
- package/dist/query-QK4U4TEL.js.map +1 -0
- package/dist/questionnaire-OVJWNUVV.js +93 -0
- package/dist/questionnaire-OVJWNUVV.js.map +1 -0
- package/dist/read-L2QIVJTL.js +141 -0
- package/dist/read-L2QIVJTL.js.map +1 -0
- package/dist/reddit-SCEB2JQL.js +26 -0
- package/dist/reddit-SCEB2JQL.js.map +1 -0
- package/dist/refresh-TIH5Y3NP.js +94 -0
- package/dist/refresh-TIH5Y3NP.js.map +1 -0
- package/dist/release-LKMUKVXM.js +60 -0
- package/dist/release-LKMUKVXM.js.map +1 -0
- package/dist/relevant-pages-CRI2NLWG.js +99 -0
- package/dist/relevant-pages-CRI2NLWG.js.map +1 -0
- package/dist/remove-6ABOB7TZ.js +50 -0
- package/dist/remove-6ABOB7TZ.js.map +1 -0
- package/dist/remove-JKSCGKQG.js +48 -0
- package/dist/remove-JKSCGKQG.js.map +1 -0
- package/dist/reopen-27TDNXFP.js +63 -0
- package/dist/reopen-27TDNXFP.js.map +1 -0
- package/dist/rerun-XHECBJD5.js +84 -0
- package/dist/rerun-XHECBJD5.js.map +1 -0
- package/dist/research-4MOXIMDX.js +62 -0
- package/dist/research-4MOXIMDX.js.map +1 -0
- package/dist/research-XYXP7D4R.js +262 -0
- package/dist/research-XYXP7D4R.js.map +1 -0
- package/dist/reset-NLYEBPLX.js +54 -0
- package/dist/reset-NLYEBPLX.js.map +1 -0
- package/dist/reset-RDQOYBXX.js +54 -0
- package/dist/reset-RDQOYBXX.js.map +1 -0
- package/dist/resolve-VHKWC5KW.js +121 -0
- package/dist/resolve-VHKWC5KW.js.map +1 -0
- package/dist/retry-MTQSHXKH.js +60 -0
- package/dist/retry-MTQSHXKH.js.map +1 -0
- package/dist/retry-ZI5ECSFD.js +62 -0
- package/dist/retry-ZI5ECSFD.js.map +1 -0
- package/dist/retry-failed-2OYMCB55.js +45 -0
- package/dist/retry-failed-2OYMCB55.js.map +1 -0
- package/dist/run-BMJJRJL2.js +65 -0
- package/dist/run-BMJJRJL2.js.map +1 -0
- package/dist/run-IHXS6F5V.js +59 -0
- package/dist/run-IHXS6F5V.js.map +1 -0
- package/dist/scaffold-ad-QEADKYPL.js +1548 -0
- package/dist/scaffold-ad-QEADKYPL.js.map +1 -0
- package/dist/scaffold-static-ad-PTJ73ZNF.js +971 -0
- package/dist/scaffold-static-ad-PTJ73ZNF.js.map +1 -0
- package/dist/scaffold-video-ZQEZRVD6.js +872 -0
- package/dist/scaffold-video-ZQEZRVD6.js.map +1 -0
- package/dist/schedule-JD2EQJCT.js +61 -0
- package/dist/schedule-JD2EQJCT.js.map +1 -0
- package/dist/schedule-TLYT5UXM.js +66 -0
- package/dist/schedule-TLYT5UXM.js.map +1 -0
- package/dist/scheduled-agents-GIVKWTXX.js +49 -0
- package/dist/scheduled-agents-GIVKWTXX.js.map +1 -0
- package/dist/schema-MZTVCNP4.js +63 -0
- package/dist/schema-MZTVCNP4.js.map +1 -0
- package/dist/schema-RYTVKPLZ.js +132 -0
- package/dist/schema-RYTVKPLZ.js.map +1 -0
- package/dist/scrape-T3DA7ER7.js +2241 -0
- package/dist/scrape-T3DA7ER7.js.map +1 -0
- package/dist/screenshot-EIGC6SZB.js +102 -0
- package/dist/screenshot-EIGC6SZB.js.map +1 -0
- package/dist/search-2SVUDZIX.js +224 -0
- package/dist/search-2SVUDZIX.js.map +1 -0
- package/dist/search-34NFJ5F6.js +26 -0
- package/dist/search-34NFJ5F6.js.map +1 -0
- package/dist/search-AOFAN5NB.js +174 -0
- package/dist/search-AOFAN5NB.js.map +1 -0
- package/dist/search-BWE3OPOY.js +91 -0
- package/dist/search-BWE3OPOY.js.map +1 -0
- package/dist/search-EYYAOZND.js +92 -0
- package/dist/search-EYYAOZND.js.map +1 -0
- package/dist/search-copy-VKQ2OOBM.js +168 -0
- package/dist/search-copy-VKQ2OOBM.js.map +1 -0
- package/dist/seeds-HQYSVFIX.js +101 -0
- package/dist/seeds-HQYSVFIX.js.map +1 -0
- package/dist/select-HJMOAWAU.js +94 -0
- package/dist/select-HJMOAWAU.js.map +1 -0
- package/dist/sends-I7VQFE4S.js +309 -0
- package/dist/sends-I7VQFE4S.js.map +1 -0
- package/dist/sequences-JHSEJJGN.js +146 -0
- package/dist/sequences-JHSEJJGN.js.map +1 -0
- package/dist/set-prompt-TSS33JQZ.js +185 -0
- package/dist/set-prompt-TSS33JQZ.js.map +1 -0
- package/dist/setup-links-IW4BUANK.js +141 -0
- package/dist/setup-links-IW4BUANK.js.map +1 -0
- package/dist/{shared-J7DQZ2ED.js → shared-AT3FXPAG.js} +8 -6
- package/dist/shared-AT3FXPAG.js.map +1 -0
- package/dist/shopify-RQKO5TMR.js +3819 -0
- package/dist/shopify-RQKO5TMR.js.map +1 -0
- package/dist/site-3NU2BDAR.js +177 -0
- package/dist/site-3NU2BDAR.js.map +1 -0
- package/dist/sitemaps-FG37N5EB.js +87 -0
- package/dist/sitemaps-FG37N5EB.js.map +1 -0
- package/dist/sites-FIXK4Y3O.js +101 -0
- package/dist/sites-FIXK4Y3O.js.map +1 -0
- package/dist/skills-OKVCVQVD.js +47 -0
- package/dist/skills-OKVCVQVD.js.map +1 -0
- package/dist/sources-SEODRUIC.js +30 -0
- package/dist/sources-SEODRUIC.js.map +1 -0
- package/dist/sources-Z6BGTBGO.js +421 -0
- package/dist/sources-Z6BGTBGO.js.map +1 -0
- package/dist/stale-SWPDWXG7.js +63 -0
- package/dist/stale-SWPDWXG7.js.map +1 -0
- package/dist/stats-ZWZ7BAHV.js +34 -0
- package/dist/stats-ZWZ7BAHV.js.map +1 -0
- package/dist/status-7Y7UNSO6.js +62 -0
- package/dist/status-7Y7UNSO6.js.map +1 -0
- package/dist/sticker-FLRZM3BI.js +94 -0
- package/dist/sticker-FLRZM3BI.js.map +1 -0
- package/dist/stock-2CSMWRWV.js +179 -0
- package/dist/stock-2CSMWRWV.js.map +1 -0
- package/dist/studio-TTTR6N7D.js +49 -0
- package/dist/studio-TTTR6N7D.js.map +1 -0
- package/dist/sync-FH773IFH.js +54 -0
- package/dist/sync-FH773IFH.js.map +1 -0
- package/dist/sync-G5CS2RYV.js +54 -0
- package/dist/sync-G5CS2RYV.js.map +1 -0
- package/dist/sync-UADA6M33.js +265 -0
- package/dist/sync-UADA6M33.js.map +1 -0
- package/dist/sync-definition-77KLGPMA.js +52 -0
- package/dist/sync-definition-77KLGPMA.js.map +1 -0
- package/dist/tag-manager-NK3ZHSJB.js +43 -0
- package/dist/tag-manager-NK3ZHSJB.js.map +1 -0
- package/dist/tags-ANGHHLAC.js +20 -0
- package/dist/tags-ANGHHLAC.js.map +1 -0
- package/dist/tags-DKPUSQTJ.js +20 -0
- package/dist/tags-DKPUSQTJ.js.map +1 -0
- package/dist/tags-HSCV4ZAQ.js +20 -0
- package/dist/tags-HSCV4ZAQ.js.map +1 -0
- package/dist/tags-J3RMQEQM.js +26 -0
- package/dist/tags-J3RMQEQM.js.map +1 -0
- package/dist/tags-VZGTR3VJ.js +224 -0
- package/dist/tags-VZGTR3VJ.js.map +1 -0
- package/dist/targeting-constants-ZGBX3VWU.js +118 -0
- package/dist/targeting-constants-ZGBX3VWU.js.map +1 -0
- package/dist/targeting-criteria-BSG3CLHU.js +113 -0
- package/dist/targeting-criteria-BSG3CLHU.js.map +1 -0
- package/dist/targets-DVOAFYZQ.js +102 -0
- package/dist/targets-DVOAFYZQ.js.map +1 -0
- package/dist/templates-KJ5O44FF.js +178 -0
- package/dist/templates-KJ5O44FF.js.map +1 -0
- package/dist/testimonials-NB7V3DUO.js +32 -0
- package/dist/testimonials-NB7V3DUO.js.map +1 -0
- package/dist/themes-3RV3HNNE.js +94 -0
- package/dist/themes-3RV3HNNE.js.map +1 -0
- package/dist/top-companies-KRB3OZOT.js +108 -0
- package/dist/top-companies-KRB3OZOT.js.map +1 -0
- package/dist/translate-5FP32BIA.js +598 -0
- package/dist/translate-5FP32BIA.js.map +1 -0
- package/dist/triggers-EURHTCYC.js +141 -0
- package/dist/triggers-EURHTCYC.js.map +1 -0
- package/dist/tweets-X64YDEAA.js +123 -0
- package/dist/tweets-X64YDEAA.js.map +1 -0
- package/dist/unfollow-PIONCSBL.js +46 -0
- package/dist/unfollow-PIONCSBL.js.map +1 -0
- package/dist/unlink-HVVTZ3MB.js +67 -0
- package/dist/unlink-HVVTZ3MB.js.map +1 -0
- package/dist/unstage-BJJ5M7MK.js +95 -0
- package/dist/unstage-BJJ5M7MK.js.map +1 -0
- package/dist/unstage-Y7ZE3NUJ.js +84 -0
- package/dist/unstage-Y7ZE3NUJ.js.map +1 -0
- package/dist/update-3IP7FB6Y.js +98 -0
- package/dist/update-3IP7FB6Y.js.map +1 -0
- package/dist/update-CSDSK5NO.js +63 -0
- package/dist/update-CSDSK5NO.js.map +1 -0
- package/dist/update-FGJXVDB7.js +142 -0
- package/dist/update-FGJXVDB7.js.map +1 -0
- package/dist/update-MF7LY2OT.js +155 -0
- package/dist/update-MF7LY2OT.js.map +1 -0
- package/dist/upload-DDPI7KJV.js +126 -0
- package/dist/upload-DDPI7KJV.js.map +1 -0
- package/dist/upload-IWFYKD7A.js +150 -0
- package/dist/upload-IWFYKD7A.js.map +1 -0
- package/dist/upscale-EYCEYRWE.js +109 -0
- package/dist/upscale-EYCEYRWE.js.map +1 -0
- package/dist/url-options-RA3XISLN.js +130 -0
- package/dist/url-options-RA3XISLN.js.map +1 -0
- package/dist/url-params-SN4APXCK.js +98 -0
- package/dist/url-params-SN4APXCK.js.map +1 -0
- package/dist/use-E2TA2K6J.js +95 -0
- package/dist/use-E2TA2K6J.js.map +1 -0
- package/dist/validate-W52JOEXI.js +88 -0
- package/dist/validate-W52JOEXI.js.map +1 -0
- package/dist/variant-ETHB6XUM.js +338 -0
- package/dist/variant-ETHB6XUM.js.map +1 -0
- package/dist/videos-653WAFPG.js +39 -0
- package/dist/videos-653WAFPG.js.map +1 -0
- package/dist/view-BVEZRRV2.js +121 -0
- package/dist/view-BVEZRRV2.js.map +1 -0
- package/dist/viewer-check-W4ICSBHO.js +68 -0
- package/dist/viewer-check-W4ICSBHO.js.map +1 -0
- package/dist/web-JWIGFGIU.js +140 -0
- package/dist/web-JWIGFGIU.js.map +1 -0
- package/dist/winners-MJDKCGEZ.js +113 -0
- package/dist/winners-MJDKCGEZ.js.map +1 -0
- package/dist/winning-ads-O3IMUGGO.js +84 -0
- package/dist/winning-ads-O3IMUGGO.js.map +1 -0
- package/dist/write-commands-DA6FIFAO.js +737 -0
- package/dist/write-commands-DA6FIFAO.js.map +1 -0
- package/dist/write-commands-EMN2WENA.js +1015 -0
- package/dist/write-commands-EMN2WENA.js.map +1 -0
- package/dist/write-commands-KTPISQRE.js +206 -0
- package/dist/write-commands-KTPISQRE.js.map +1 -0
- package/dist/write-commands-QBGIWU27.js +713 -0
- package/dist/write-commands-QBGIWU27.js.map +1 -0
- package/dist/x-BT3SNCTU.js +47 -0
- package/dist/x-BT3SNCTU.js.map +1 -0
- package/package.json +9 -2
- package/dist/chunk-CHJOTJTZ.js +0 -9989
- package/dist/chunk-CHJOTJTZ.js.map +0 -1
- package/dist/chunk-HJEMOSPH.js +0 -1292
- package/dist/chunk-HJEMOSPH.js.map +0 -1
- package/dist/chunk-MF7YA62W.js.map +0 -1
- package/dist/chunk-VLUGGVJL.js.map +0 -1
- package/dist/chunk-VZEDAFAR.js.map +0 -1
- package/dist/chunk-XHC4LMK4.js.map +0 -1
- package/dist/client-GR6YL3YX.js +0 -17
- package/dist/output-NMQXZF3Z.js +0 -18
- /package/dist/{client-GR6YL3YX.js.map → chunk-YQSBHWOI.js.map} +0 -0
- /package/dist/{env-4P5HOGNA.js.map → client-EURRWXPW.js.map} +0 -0
- /package/dist/{output-NMQXZF3Z.js.map → connect-4YXFMXYX.js.map} +0 -0
- /package/dist/{shared-J7DQZ2ED.js.map → customers-7E4HBSEY.js.map} +0 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ export BAKER_X_ADS_ACCOUNT_ID="18ce53xyz"
|
|
|
36
36
|
export BAKER_CHAT_ID="<chat-id>"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
- `BAKER_API_KEY` must start with `bk_` (or `bkr_` for the read-only credential a Session review runs with)
|
|
39
|
+
- `BAKER_API_KEY` must start with `bk_` (or `bkr_` for the read-only credential a Session review runs with, or `bkv_` for the one a published report runs with)
|
|
40
40
|
- A missing or malformed `BAKER_API_KEY` / `BAKER_API_URL` ends any command that talks to Baker with `NOT_CONFIGURED` (non-retryable, with a `fix`) naming the variable and the rule it broke — never its value
|
|
41
41
|
- A malformed `BAKER_GOOGLE_ADS_CUSTOMER_ID`, `BAKER_LINKEDIN_AD_ACCOUNT_ID` or `BAKER_X_ADS_ACCOUNT_ID` ends it with `VALIDATION_ERROR` instead, whose `fix` says to correct or unset the variable
|
|
42
42
|
- `BAKER_API_URL` is your Convex site URL — the deployment the CLI talks to, not an address to show anybody
|
|
@@ -46,6 +46,8 @@ export BAKER_CHAT_ID="<chat-id>"
|
|
|
46
46
|
- `BAKER_GSC_SITE_URL` — default GSC site URL. Used when `--site-url` is not passed. If neither is set and exactly one site is verified, it is auto-selected.
|
|
47
47
|
- `BAKER_X_ADS_ACCOUNT_ID` — default X Ads account ID (base36). Used when `--account-id` is not passed. If neither is set and exactly one X Ads account is connected, it is auto-selected.
|
|
48
48
|
- `BAKER_CHAT_ID` — chat context for staged Work Action commands and staged Scheduled agent create/update/delete/unstage. `scheduled-agents run` does not require it. When set, it is also sent as an `x-baker-chat-id` header on every request, so library additions that apply immediately (images, videos, followed brands) are listed in that chat's changes as already applied.
|
|
49
|
+
- `BAKER_THREAD_ID` — set per turn by the sandbox. Sent as an `x-baker-thread-id` header alongside the chat header, so the tools a command reports or changes are those of whoever sent this turn, even while someone else writes in another thread of the same chat.
|
|
50
|
+
- `BAKER_KEY_SCOPE` — `report` while a report's data endpoint runs in the chat preview (set by the reports runtime). Sent as an `x-baker-key-scope` header, Baker then holds the key to what a published report may do — reads only — so a report that writes fails in the preview rather than once published. It can only narrow the key.
|
|
49
51
|
|
|
50
52
|
## Debug logging
|
|
51
53
|
|
|
@@ -106,7 +108,7 @@ offered, so a short option is never pointed at an unrelated one (`--from` at `--
|
|
|
106
108
|
Previously such an option was accepted and ignored, so a call that
|
|
107
109
|
looked like it had filtered or narrowed a result was in fact answering the unfiltered
|
|
108
110
|
question — and nothing in the output said so. Both spellings of an option work
|
|
109
|
-
(`--run-id` and `--
|
|
111
|
+
(`--run-id` and `--run-id`), as they always did. The error's `fix` names the exact call
|
|
110
112
|
that lists every option: `baker schema <command>` (`check_schema`) when the command
|
|
111
113
|
registers a schema, `baker <command> --help` (`check_help`) when it does not.
|
|
112
114
|
|
|
@@ -157,18 +159,21 @@ Use `--output` to change format:
|
|
|
157
159
|
|
|
158
160
|
### Ad Platforms (`baker ads`)
|
|
159
161
|
|
|
160
|
-
Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **staged writes** (create/update campaigns, ad sets/ad groups, ads, creatives, audiences — applied on chat publish); X (Twitter) is read + analytics. TikTok is coming.
|
|
162
|
+
Multi-platform ad commands. Google, Meta, and LinkedIn support both reads and **staged writes** (create/update campaigns, ad sets/ad groups, ads, creatives, audiences — applied on chat publish); X (Twitter) is read + analytics; ChatGPT Ads is read + analytics + staged writes. TikTok is coming.
|
|
161
163
|
|
|
162
164
|
---
|
|
163
165
|
|
|
164
166
|
### `baker ads google accounts`
|
|
165
167
|
|
|
166
|
-
List
|
|
168
|
+
List the Google Ads accounts selected for this company. Run this first to find account IDs.
|
|
167
169
|
|
|
168
170
|
```bash
|
|
169
171
|
baker ads google accounts
|
|
172
|
+
baker ads google accounts --available
|
|
170
173
|
```
|
|
171
174
|
|
|
175
|
+
`--available` lists every account the connected login reaches instead, each `{ id, name, selected }`. Read-only: it never changes the selection. When the account the user means comes back `selected: false`, the hint names it and points at `request_connection` with `pick: true`, which opens the account picker in the chat. The same flag works on `ads meta accounts`, `ads linkedin accounts`, `ads x accounts`, `ga4 properties`, `gsc sites` and `tag-manager containers`.
|
|
176
|
+
|
|
172
177
|
**Response:**
|
|
173
178
|
|
|
174
179
|
```json
|
|
@@ -217,10 +222,10 @@ baker ads google currency --customer-id 5904042878
|
|
|
217
222
|
|
|
218
223
|
### `baker ads google url-options`
|
|
219
224
|
|
|
220
|
-
The UTM / tracking parameters in force on the account: the account-level Final URL Suffix and tracking template, plus every campaign that overrides them. Start here for any "add `utm_agency=…`" or agency-tag question.
|
|
225
|
+
The UTM / tracking parameters in force on the account: the account-level Final URL Suffix and tracking template, plus every campaign, ad group and ad that overrides them. Start here for any "add `utm_agency=…`" or agency-tag question.
|
|
221
226
|
|
|
222
227
|
```bash
|
|
223
|
-
baker ads google url-options --customer-id 1234567890 # overriding campaigns
|
|
228
|
+
baker ads google url-options --customer-id 1234567890 # overriding campaigns, ad groups and ads
|
|
224
229
|
baker ads google url-options --customer-id 1234567890 --full # every campaign read
|
|
225
230
|
```
|
|
226
231
|
|
|
@@ -231,15 +236,19 @@ Every value is a tagged union, never a bare string, so an unread level can never
|
|
|
231
236
|
"ok": true,
|
|
232
237
|
"data": {
|
|
233
238
|
"account": { "final_url_suffix": { "state": "set", "value": "utm_source=google" } },
|
|
234
|
-
"campaigns": { "
|
|
235
|
-
"
|
|
239
|
+
"campaigns": { "overrides": [], "omitted": 12 },
|
|
240
|
+
"ad_groups": { "read": true, "overrides": [], "truncated": false },
|
|
241
|
+
"ads": { "read": true, "overrides": [], "truncated": false },
|
|
242
|
+
"levels_not_read": []
|
|
236
243
|
}
|
|
237
244
|
}
|
|
238
245
|
```
|
|
239
246
|
|
|
240
247
|
`set` holds a value · `not_set` means Google carries none at that level · `not_read` means it was never observed. **Only `not_set` is a finding.**
|
|
241
248
|
|
|
242
|
-
Writing it: `baker ads google campaigns update <id> --final-url-suffix "utm_source=google&utm_agency=baker"` stages a campaign-level override (it *replaces* the account value for that campaign, it does not merge). The **account** level
|
|
249
|
+
Writing it: `baker ads google campaigns update <id> --final-url-suffix "utm_source=google&utm_agency=baker"` stages a campaign-level override (it *replaces* the account value for that campaign, it does not merge). The **account** level stages too: `baker ads google account update --customer-id … --final-url-suffix "utm_source=google&utm_agency=baker"` (or `campaigns update --level account --final-url-suffix …`, the same op) applies at publish with the rest of the draft; `""` clears it.
|
|
250
|
+
|
|
251
|
+
**Account settings** — `baker ads google account update --customer-id …` stages the account's own settings: `--final-url-suffix`, `--tracking-template`, `--auto-tagging` / `--auto-tagging=false`, `--call-reporting`, `--call-conversions`, `--video-inventory EXPANDED|STANDARD|LIMITED`. One card per account (staging again amends it), shown before → after; a setting the account already holds is a no-op. Turning auto-tagging off stages with a warning (conversion tracking loses the gclid). The conversion action that counts calls is not writable through Google's API.
|
|
243
252
|
|
|
244
253
|
---
|
|
245
254
|
|
|
@@ -313,19 +322,19 @@ baker ads google query --list-presets
|
|
|
313
322
|
|
|
314
323
|
Read it before interpreting any null. Field coverage is folded page by page on the `--out` path too, so the file summary reports it; `.resource_name` is never reported (the server strips it from every row).
|
|
315
324
|
|
|
316
|
-
**A page is not the data set.**
|
|
325
|
+
**A page is not the data set.** Every query is read a page at a time: without `--all` it returns one page of `--limit` rows (the preset's page size, else 200) and, when the account holds more, `pagination.cursor` — pass it back with `--cursor` (same query, same `--limit`) for the next page. `--all` follows every page until the account has no more rows; only a `LIMIT` written in the statement caps it. When the API returns a cursor the run did not follow, or the rows fill the statement's own `LIMIT`, the response carries a `TRUNCATED_RESULTS` warning:
|
|
317
326
|
|
|
318
327
|
```json
|
|
319
328
|
{
|
|
320
329
|
"ok": true,
|
|
321
330
|
"warnings": [{ "code": "TRUNCATED_RESULTS", "message": "INCOMPLETE READ — ..." }],
|
|
322
|
-
"pagination": { "hasMore": true, "cursor": "
|
|
331
|
+
"pagination": { "hasMore": true, "cursor": "skip:200.k3x9q2:" },
|
|
323
332
|
"data": [...],
|
|
324
333
|
"notice": "Read the \"warnings\" above before using these rows: TRUNCATED_RESULTS."
|
|
325
334
|
}
|
|
326
335
|
```
|
|
327
336
|
|
|
328
|
-
Never total or count from a response carrying it — re-run with `--all --
|
|
337
|
+
Never total or count from a response carrying it — re-run with `--all --out data.csv` (it pages through every row, however many) and check the re-run came back without the warning. A read that returns one row by construction — `FROM customer` with no segment selected, such as `--preset account-summary` — has no page to fill, so it never carries `TRUNCATED_RESULTS`, nor `CONVERSION_MIX`, since one account-wide row has no campaigns to total or compare. Warnings reach every output format, so no `--output` choice can hide one:
|
|
329
338
|
|
|
330
339
|
| `--output` | Where warnings land |
|
|
331
340
|
|------------|---------------------|
|
|
@@ -366,9 +375,10 @@ The same lines also go to stderr. A read with nothing to report prints none of i
|
|
|
366
375
|
| `--preset` | Named query template (see presets below) |
|
|
367
376
|
| `--date-range` | Override preset date range: a Google literal (LAST_7_DAYS, LAST_30_DAYS, LAST_MONTH, etc.) or `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` — there is no LAST_90_DAYS |
|
|
368
377
|
| `--include-paused` | Include paused entities in preset results. Off by default: presets return only actually-serving entities (enabled campaign **and** ad group **and** ad). Google Ads has no single serving flag, so the whole chain must be `ENABLED` for an entity to serve. |
|
|
369
|
-
| `--
|
|
370
|
-
| `--
|
|
371
|
-
| `--
|
|
378
|
+
| `--gaql` | The GAQL statement as a flag, instead of the positional |
|
|
379
|
+
| `--limit` | Rows per page (default: the preset's page size, else 200; at most 8,192). A page size, not a cap |
|
|
380
|
+
| `--cursor` | `pagination.cursor` from the previous page (same query, same `--limit`) |
|
|
381
|
+
| `--all` | Follow every page until the account has no more rows. Only a `LIMIT` written in the statement stops it early, and a read that fills that `LIMIT` is flagged `TRUNCATED_RESULTS` |
|
|
372
382
|
| `--out` | Write data to file (format from extension: .csv, .jsonl, .json) |
|
|
373
383
|
| `--append` | Append to existing file (skip CSV headers) |
|
|
374
384
|
| `--output` | Output format: `json` \| `csv` \| `jsonl` \| `md` |
|
|
@@ -389,9 +399,21 @@ The same lines also go to stderr. A read with nothing to report prints none of i
|
|
|
389
399
|
| `negative-list-attachments`| Which campaigns each shared list covers | ALL_TIME |
|
|
390
400
|
| `search-terms` | Actual user queries triggering ads | LAST_7_DAYS |
|
|
391
401
|
| `ad-copy-performance` | Ad headline/description effectiveness | LAST_30_DAYS |
|
|
392
|
-
| `asset-performance` | PMax
|
|
402
|
+
| `asset-performance` | PMax assets: serving status + metrics | LAST_30_DAYS |
|
|
393
403
|
| `shopping-products` | Product-level shopping metrics | LAST_30_DAYS |
|
|
394
404
|
| `account-summary` | Account-level totals | LAST_30_DAYS |
|
|
405
|
+
| `pmax-channels` | PMax spend/results per channel (Search, YouTube, Display, …) | LAST_30_DAYS |
|
|
406
|
+
| `pmax-placements` | Where PMax ads showed (impressions) | LAST_30_DAYS |
|
|
407
|
+
| `placements` | Display / Video / Demand Gen placements with cost and conversions | LAST_30_DAYS |
|
|
408
|
+
| `rsa-assets` | Each RSA headline/description: pin, approval, own metrics | LAST_30_DAYS |
|
|
409
|
+
| `landing-pages` | Final URLs with clicks, cost, speed score, mobile-friendly share | LAST_30_DAYS |
|
|
410
|
+
| `geo-performance` | Results by country and location type (in vs interested in) | LAST_30_DAYS |
|
|
411
|
+
| `age-performance` / `gender-performance` | Results by age range / gender per ad group | LAST_30_DAYS |
|
|
412
|
+
| `ai-max-search-terms` | AI Max search terms with the headline and landing page used | LAST_30_DAYS |
|
|
413
|
+
| `account-budget` | Account budget: limit, served, pending change | — |
|
|
414
|
+
| `recommendations` | Google's open recommendations with impact | — |
|
|
415
|
+
| `campaign-simulations` / `keyword-bid-simulations` | Google's what-if points (budget, tCPA, tROAS, CPC bid) | — |
|
|
416
|
+
| `campaigns`, `budgets`, `ad-groups`, `keywords`, `ads`, `assets`, `asset-groups`, `audiences`, `conversion-actions`, `bidding-strategies`, `labels`, `campaign-criteria`, `ad-group-criteria`, `ad-group-bid-modifiers`, `account-exclusions`, `shared-sets`, `campaign-negative-keywords`, `ad-group-negative-keywords` | Inventories behind the `<thing> list` commands | — |
|
|
395
417
|
|
|
396
418
|
Every preset except the negatives lists and `account-summary` filters to **actually-serving entities** by default — the full status chain (`campaign.status = 'ENABLED' AND ad_group.status = 'ENABLED' AND ad_group_ad.status = 'ENABLED'`, as applicable) — and selects the status columns so you can see them. Pass `--include-paused` to widen to `!= 'REMOVED'` (keeps paused, still drops removed). Raw GAQL (no `--preset`) has no such default: a query over a serving-hierarchy resource without a status filter emits a `SERVING_SCOPE` warning reminding you to add the chain, because Google Ads has no single serving flag.
|
|
397
419
|
|
|
@@ -407,13 +429,12 @@ The CLI rejects or automatically corrects common GAQL mistakes before hitting th
|
|
|
407
429
|
|
|
408
430
|
| Pattern | Behavior | Warning/error emitted |
|
|
409
431
|
|---------|----------|-----------------------|
|
|
410
|
-
| Missing LIMIT | Adds `LIMIT 200` | `"Added LIMIT 200. Use --limit to override."` |
|
|
411
432
|
| Bare `keyword.text` / `keyword.match_type` | Rewrites to `ad_group_criterion.*` (resource-scoped forms like `shared_criterion.keyword.text` pass through untouched) | `"keyword.text → ad_group_criterion.keyword.text"` |
|
|
412
433
|
| `shopping_performance_view.product_*` | Rewrites to `segments.product_*` | Field renamed |
|
|
413
434
|
| `campaign.status = 'ACTIVE'` | Rewrites to `= 'ENABLED'` | Enum corrected |
|
|
414
435
|
| `segments.conversion_action_name` with `metrics.cost_micros` | Rejects; split into separate conversion-action and spend queries | `INCOMPATIBLE_FIELDS` with `fix.action: "split_query"` |
|
|
415
436
|
| `DURING` with a literal Google does not accept (`LAST_90_DAYS`, `LAST_12_MONTHS`, `THIS_YEAR` …) | Rejects before any request; `DURING` takes only `TODAY`, `YESTERDAY`, `LAST_7_DAYS`, `LAST_14_DAYS`, `LAST_30_DAYS`, `LAST_BUSINESS_WEEK`, `THIS_MONTH`, `LAST_MONTH`, `THIS_WEEK_SUN_TODAY`, `THIS_WEEK_MON_TODAY`, `LAST_WEEK_SUN_SAT`, `LAST_WEEK_MON_SUN` | `INVALID_DATE_RANGE`, with `fix.correctedCommand` spelling the same window as `BETWEEN 'YYYY-MM-DD' AND 'YYYY-MM-DD'` |
|
|
416
|
-
| `--limit` outside 1–8,192
|
|
437
|
+
| `--limit` outside 1–8,192 | Rejects before any request — one page holds at most 8,192 rows | `VALIDATION_ERROR` pointing at `--all --out` for bigger reads |
|
|
417
438
|
| `FROM ad_group_criterion` without `negative` field | Warns | `"ad_group_criterion returns BOTH positive and negative keywords. Use --preset positive-keywords or negative-keywords."` |
|
|
418
439
|
|
|
419
440
|
Auto-fixed queries proceed normally. Warnings appear in the response:
|
|
@@ -452,7 +473,7 @@ Google keeps account history in two layers, and neither reaches past 90 days:
|
|
|
452
473
|
|
|
453
474
|
| Scope | Reach | What you get |
|
|
454
475
|
|-------------|---------|---------------------------------------------------------------------|
|
|
455
|
-
| `detail` | 30 days | What changed, old
|
|
476
|
+
| `detail` | 30 days | What changed — a `changes` list of `{ field, old, new }` per changed field — and who changed it |
|
|
456
477
|
| `summary` | 90 days | Which resources were added, changed or removed — no values, no user |
|
|
457
478
|
|
|
458
479
|
Beyond 90 days there is no API. Infer the change from a monthly spend trend per campaign
|
|
@@ -477,6 +498,43 @@ baker ads google changes --customer-id 1234567890 --days 60 --scope summary
|
|
|
477
498
|
|
|
478
499
|
---
|
|
479
500
|
|
|
501
|
+
### `baker ads google keywords forecast` / `keywords themes`
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
baker ads google keywords forecast --customer-id 1234567890 --text "running shoes:PHRASE,trail shoes:EXACT" --max-cpc 1.2 --location 2724 --language 1003
|
|
505
|
+
baker ads google keywords forecast --customer-id 1234567890 --text "crm software" --match-type BROAD --daily-spend 20 --strategy MAXIMIZE_CONVERSIONS --start 2026-11-01 --end 2026-11-30
|
|
506
|
+
baker ads google keywords themes --customer-id 1234567890 --keywords "trail shoes,road shoes" --ad-groups 111,222
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
`forecast` returns Google's Keyword Planner forecast (v25) for the whole period under one bidding strategy: `{ source: "keyword_planner_forecast", period, forecast_clicks, forecast_cost_micros, forecast_conversions, forecast_average_cpc_micros, forecast_average_cpa_micros }`. `--max-cpc` alone is Manual CPC; `--daily-spend` is Maximize clicks (or Maximize conversions with `--strategy`). Without `--start/--end` it forecasts the 30 days from tomorrow. `themes` returns which existing ad group each keyword fits (`suggestions`) and the ad groups Google could not read a theme from (`unusable_ad_groups`).
|
|
510
|
+
|
|
511
|
+
### `baker ads google previews` / `invoices` / `identity-verification` / `brands`
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
baker ads google previews --customer-id 1234567890 --asset-groups 6743268912 # Performance Max, Google's preview page
|
|
515
|
+
baker ads google previews --customer-id 1234567890 --ads 111~222 # a Video campaign's ad, live on YouTube
|
|
516
|
+
baker ads google invoices --customer-id 1234567890 --month 2026-09
|
|
517
|
+
baker ads google identity-verification --customer-id 1234567890
|
|
518
|
+
baker ads google brands --customer-id 1234567890 --prefix nike # brand entity ids for a BRANDS list
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Read-only. A refusal that is the account's situation comes back as a structured error the agent carries on from: `NOT_INVOICED` (not billed by monthly invoicing — no invoices through the API), `VERIFICATION_NOT_READABLE` (verification status is only readable through the API on monthly invoicing), `NO_PREVIEW_FOR_AD_TYPE` (only video ads of Video campaigns preview by ad). Each `error.fix` says where the answer lives instead.
|
|
522
|
+
|
|
523
|
+
### `baker ads google <thing> list`
|
|
524
|
+
|
|
525
|
+
What exists, with the id or resource name each write command takes — one inventory preset read through `query`, so it pages (`--limit`, `--cursor`, `--all --out`) and formats (`--output`) the same way. Paused entities are listed; removed ones are not; `--serving-only` narrows to what serves.
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
baker ads google campaigns list --customer-id 1234567890
|
|
529
|
+
baker ads google keywords list --customer-id 1234567890 --all --out keywords.csv
|
|
530
|
+
baker ads google negative-keywords list --customer-id 1234567890 --level adGroup
|
|
531
|
+
baker ads google keyword-lists list --customer-id 1234567890 --members
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Lists exist for `campaigns`, `budgets`, `ad-groups`, `keywords`, `negative-keywords` (`--level campaign|adGroup`), `keyword-lists` (`--members` for the terms), `ads`, `assets`, `asset-groups`, `audiences`, `conversions`, `bidding-strategies`, `labels`, `campaign-criteria`, `ad-group-criteria`, `ad-group-bid-modifiers` and `account-exclusions`.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
480
538
|
### `baker ads google keywords discover`
|
|
481
539
|
|
|
482
540
|
Discover keyword ideas from seed keywords or URLs.
|
|
@@ -484,6 +542,7 @@ Discover keyword ideas from seed keywords or URLs.
|
|
|
484
542
|
```bash
|
|
485
543
|
baker ads google keywords discover --customer-id 1234567890 --seeds "running shoes,athletic footwear"
|
|
486
544
|
baker ads google keywords discover --customer-id 1234567890 --url "https://competitor.com" --limit 50
|
|
545
|
+
baker ads google keywords discover --customer-id 1234567890 --seeds "running shoes" --cursor <next_page_token> # next page
|
|
487
546
|
```
|
|
488
547
|
|
|
489
548
|
**Response:**
|
|
@@ -592,11 +651,13 @@ Write commands **never touch the Google Ads API at stage time**. Each command st
|
|
|
592
651
|
|
|
593
652
|
Reference not-yet-created resources with `g_temp_*` refs returned by earlier `create` commands (they map to Google's temporary negative-ID resource names inside one atomic batch).
|
|
594
653
|
|
|
595
|
-
**Staged ad copy is scanned for AI writing tells** (Google, Meta, and LinkedIn alike). When a headline, description, or primary text carries a construction that reads as machine-written — a negation pivot, a fake-candid opener, a participle hung off a comma, a vague attribution — the stage response carries an `AI COPY TELL` entry in `hints[]`. A headline set where several headlines are one claim with a swapped verb is reported as `synonym-cycling`: an AI tell *and* a real auction defect. These are advisory and can never fail a staged write, deliberately — refusing an ad over a fallible prose heuristic costs more than a tell that ships. Thresholds are stricter than the landing critic's (one occurrence is enough, since a 30-character headline has no room for a cluster), and conventions that are legitimate in ad copy are excluded outright: Title Case headlines, staccato ad rhythm, emoji, and curly quotes.
|
|
654
|
+
**Staged ad copy is scanned for AI writing tells** (Google, Meta, and LinkedIn alike), in whatever language the ad is written. When a headline, description, or primary text carries a construction that reads as machine-written — a negation pivot, a fake-candid opener, a participle hung off a comma, a vague attribution — the stage response carries an `AI COPY TELL` entry in `hints[]`. A headline set where several headlines are one claim with a swapped verb is reported as `synonym-cycling`: an AI tell *and* a real auction defect. Each sentence and each pair of headlines is read by the decision model (Jev, a fraction of a cent, billed with the workspace's next run; answers are kept in `canvas/.cache/`, so the same copy reads the same); when it cannot answer, no tell is reported rather than a guess. These are advisory and can never fail a staged write, deliberately — refusing an ad over a fallible prose heuristic costs more than a tell that ships. Thresholds are stricter than the landing critic's (one occurrence is enough, since a 30-character headline has no room for a cluster), and conventions that are legitimate in ad copy are excluded outright: Title Case headlines, staccato ad rhythm, emoji, and curly quotes.
|
|
596
655
|
|
|
597
656
|
```bash
|
|
598
657
|
# Build a Search campaign bottom-up, chaining temp refs
|
|
599
658
|
baker ads google budgets create --customer-id 1234567890 --name "Search budget" --amount 50
|
|
659
|
+
# Delivery is always STANDARD: --delivery ACCELERATED is refused (Google stores such a budget but will
|
|
660
|
+
# not attach it to a campaign).
|
|
600
661
|
# → { ref: "g_temp_ab12", ... }
|
|
601
662
|
baker ads google campaigns create --customer-id 1234567890 --name "Brand — Search" \
|
|
602
663
|
--channel-type SEARCH --budget-ref g_temp_ab12 --bidding-strategy MANUAL_CPC \
|
|
@@ -617,6 +678,22 @@ baker ads google campaigns create --customer-id 1234567890 --name "Brand — Sea
|
|
|
617
678
|
# New campaigns are declared as NOT containing EU political advertising (Google requires the
|
|
618
679
|
# declaration on every create); pass --eu-political-ads only when the campaign does contain it.
|
|
619
680
|
# On update, --eu-political-ads / --eu-political-ads=false corrects an existing declaration.
|
|
681
|
+
# Text guidelines (AI Max Search and Performance Max) stage on update only:
|
|
682
|
+
baker ads google campaigns update <campaign-id> --customer-id 1234567890 \
|
|
683
|
+
--term-exclusion "cheap" --messaging-restriction "Never promise a delivery date"
|
|
684
|
+
# --term-exclusion (repeatable, up to 25 × 30 chars) and --messaging-restriction (repeatable,
|
|
685
|
+
# up to 40 × 300 chars) each REPLACE the campaign's list; --clear-term-exclusions /
|
|
686
|
+
# --clear-messaging-restrictions empty one. A Search campaign needs AI Max with text
|
|
687
|
+
# customization on (in the same update or before), or staging is refused with the reason.
|
|
688
|
+
# AI Max for Search stages on create and update: --ai-max / --ai-max=false (off turns both
|
|
689
|
+
# features off too), --ai-max-text (text customization, needs AI Max) and
|
|
690
|
+
# --ai-max-url-expansion (final URL expansion, needs text customization). An omitted switch
|
|
691
|
+
# keeps the campaign's value; a combination Google refuses is refused at staging.
|
|
692
|
+
baker ads google campaigns update <campaign-id> --customer-id 1234567890 --ai-max --ai-max-text
|
|
693
|
+
# Bidding is per campaign type — staging refuses a pair Google does not take and names the ones it does.
|
|
694
|
+
# Target impression share (Search only) needs its whole goal on a new strategy:
|
|
695
|
+
baker ads google campaigns update <campaign-id> --customer-id 1234567890 --bidding-strategy TARGET_IMPRESSION_SHARE \
|
|
696
|
+
--impression-share 70 --impression-share-location ABSOLUTE_TOP_OF_PAGE --max-cpc 2
|
|
620
697
|
baker ads google ad-groups create --customer-id 1234567890 --name "Brand terms" --campaign-ref g_temp_<campaign>
|
|
621
698
|
baker ads google keywords add --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --text "brand name" --match-type EXACT
|
|
622
699
|
# Shared negative keyword list → attach to the campaign
|
|
@@ -650,7 +727,7 @@ baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgr
|
|
|
650
727
|
# Video ad — the YouTube video is its own asset first, then referenced by --video-assets
|
|
651
728
|
baker ads google assets create --customer-id 1234567890 --file '{"type":"youtubeVideo","youtubeVideoId":"dQw4w9WgXcQ"}'
|
|
652
729
|
baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --format video \
|
|
653
|
-
--video-assets g_temp_<video> --final-url https://example.com
|
|
730
|
+
--video-assets g_temp_<video> --business-name "Acme" --logo-image-assets g_temp_<img_logo> --final-url https://example.com
|
|
654
731
|
# Demand Gen ad — image refs the same way
|
|
655
732
|
baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --format demandGen \
|
|
656
733
|
--headlines "Discover the app" --descriptions "Try it free today" --business-name "Acme" --final-url https://example.com \
|
|
@@ -665,6 +742,8 @@ baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgr
|
|
|
665
742
|
# App campaign ad — images are fields on the ad itself, same asset-ref flow
|
|
666
743
|
baker ads google ads create --customer-id 1234567890 --ad-group-ref g_temp_<adgroup> --format app \
|
|
667
744
|
--headlines "Insure your dog" --descriptions "Refunds in 2 hours" --image-assets g_temp_<img1>,g_temp_<img2>
|
|
745
|
+
# Call ads are retired — Google's API has no call ad, so --format call is refused. Stage a responsive
|
|
746
|
+
# search ad and have the user add the phone number as a call asset in Google Ads.
|
|
668
747
|
# Change a live ad's content — every field you pass replaces its current value, so list what you want to keep
|
|
669
748
|
baker ads google ads update <adId> --customer-id 1234567890 --format app \
|
|
670
749
|
--headlines "Insure your dog" --descriptions "Refunds in 2 hours" \
|
|
@@ -686,9 +765,16 @@ baker ads google asset-groups attach --customer-id 1234567890 \
|
|
|
686
765
|
baker ads google asset-groups detach customers/1234567890/assetGroupAssets/6718302454~402654762568~PORTRAIT_MARKETING_IMAGE \
|
|
687
766
|
--customer-id 1234567890
|
|
688
767
|
|
|
768
|
+
# Audiences — each type carries what Google builds it from (a type alone is refused)
|
|
769
|
+
baker ads google audiences create --customer-id 1234567890 --file '{"name":"Pricing visitors","type":"RULE_BASED","rule":{"include":[{"conditions":[{"operator":"CONTAINS","value":"/pricing"}],"lookbackWindowDays":30}],"prefill":true}}'
|
|
770
|
+
# BASIC takes "conversionActions", LOGICAL "logic", LOOKALIKE "lookalike" — each naming lists/actions
|
|
771
|
+
# that already exist in the account (not g_temp_* refs). `baker ads google audiences create --help` has one example per type.
|
|
772
|
+
|
|
689
773
|
# Review / undo / amend staged changes
|
|
690
774
|
baker ads google draft list # readable campaign ▸ ad group ▸ ad tree + completeness advisories
|
|
691
775
|
baker ads google draft list --json # raw JSON envelope for scripting
|
|
776
|
+
baker ads google draft validate # dry run the whole draft against Google (validateOnly) — per-op verdicts
|
|
777
|
+
baker ads google draft validate --full # every op, with the built request and Google's raw answer
|
|
692
778
|
baker ads google draft show g_temp_ab12 # full staged payload — the receipt before publish
|
|
693
779
|
baker ads google draft amend g_temp_ab12 --patch '{"amountMicros": 60000000}' # update in place, never remove+recreate
|
|
694
780
|
baker ads google draft remove g_temp_ab12 # cascades to dependents
|
|
@@ -703,11 +789,21 @@ baker tag-manager draft show --chat <chat-id> gtm_temp_ab12
|
|
|
703
789
|
baker actions draft --chat <chat-id>
|
|
704
790
|
```
|
|
705
791
|
|
|
706
|
-
|
|
792
|
+
**Budgets and portfolios** — `budgets create --total 300` stages a total budget (one amount over the campaign's flight; Search, Performance Max and Demand Gen; the campaign needs `--end-date`; never shared) instead of `--amount`'s daily one, and `budgets update --total` moves it. `campaigns create|update --portfolio-ref <ref>` puts a campaign on a portfolio bid strategy (checked against the campaign type); staging a campaign's own `--bidding-strategy` while it is on a portfolio takes it off (with a warning). `budgets remove <id>` and `bidding-strategies remove <id>` remove a budget / portfolio once no campaign uses it — staging refuses while one does, unless the same draft removes or moves those campaigns; publish removes the campaigns first.
|
|
793
|
+
|
|
794
|
+
**Shopping campaigns** — `campaigns create --channel-type SHOPPING --merchant-id <id>` (`merchant-center list` shows the Merchant Center accounts linked to the account; staging refuses any other) with optional `--feed-label`, `--shopping-priority low|medium|high` and `--local-inventory`; `campaigns update` changes those three (`--clear-feed-label`), never the Merchant Center. `ad-groups create --type SHOPPING_PRODUCT_ADS` creates the ad group with its "All products" listing group, and `ads create --format shoppingProduct` its product ad (no copy — the feed supplies it). A retail Performance Max campaign takes `--merchant-id` / `--feed-label` too.
|
|
795
|
+
|
|
796
|
+
**App, Travel and Hotel campaigns** — App: `campaigns create --channel-type MULTI_CHANNEL --sub-type APP_CAMPAIGN|APP_CAMPAIGN_FOR_ENGAGEMENT --app-id <package or App Store id> --app-store google|apple --app-goal installs|installs-volume|in-app|in-app-install-cost|value [--app-conversions <ids>]` (each goal takes one bidding strategy; App ads are created enabled — Google refuses a paused one, so `ads create --format app` defaults to `ENABLED`). Travel: `--channel-type TRAVEL --travel-account-id <id>` on Maximize conversion value with `--target-roas`. Hotel: `--channel-type HOTEL --hotel-center-id <id>`. Staging asks Google (validate-only) whether the app, the travel account or Hotel Center is there and refuses with what to ask the user when it is not. Smart campaigns are refused (their business setting cannot be validated).
|
|
797
|
+
|
|
798
|
+
**Dynamic Search Ads and frequency caps** — `campaigns create|update --dsa-domain <domain> --dsa-language <code> [--dsa-supplied-urls-only]` (Search only; an update may change one field; it cannot be removed) and `--frequency-cap <cap>/<day|week|month>[/<campaign|ad-group|ad>][/<impressions|video-views>]` (repeatable, replaces the campaign's caps; `--clear-frequency-caps`; Display and existing Video campaigns only).
|
|
799
|
+
|
|
800
|
+
**Performance Max creative controls** — `campaigns create|update` take `--term-exclusion` / `--messaging-restriction` (text guidelines; a Search create needs `--ai-max --ai-max-text`), the generated-asset switches `--generated-text --url-expansion --video-enhancement --image-enhancement --image-extraction --image-design-versions` (each `=false` to turn off), `--brand-guidelines` on create (link the business name and a square logo to the campaign in the same draft) and `--main-color --accent-color --font`. `campaigns enable-brand-guidelines <id> --business-name-asset --logo-assets [--landscape-logo-assets] [--final-url-domain] | --auto-populate` turns brand guidelines on for an existing campaign at publish (no dry run: `draft validate` shows it unchecked).
|
|
801
|
+
|
|
802
|
+
Command groups: `account` (update), `budgets` (create/update/remove), `campaigns`, `ad-groups`, `keywords` (add/update/pause/resume/remove), `negative-keywords` (add/update/remove — `update <resource name> --text --match-type` publishes as a replace of the live negative), `keyword-lists` (create/rename/delete a list — `--type NEGATIVE_KEYWORDS|NEGATIVE_PLACEMENTS|BRANDS|ACCOUNT_LEVEL_NEGATIVE_KEYWORDS`; add/remove its members — keywords, or `--placement/--app/--youtube-channel/--youtube-video/--brand`; attach/detach), `ads`, `assets` (create/update/attach/detach), `asset-groups` (create/update/attach/detach — Performance Max), `audiences`, `conversions` (create/update/goal/upload), `bidding-strategies` (create/update/remove), `labels` (create/update/remove; attach/detach on a campaign, ad group, ad or keyword — `--level --on --label-ref`), `campaign-criteria` (add/update/remove — locations, languages, schedule, devices, radius, carriers, operating systems, and exclusions of placements, apps, YouTube, topics, demographics, content types, IP addresses, web pages, brand lists), `ad-group-criteria` (add/update/remove — placements, topics, demographics, web pages, Demand Gen locations/languages/combined audience; `update --bid-modifier --max-cpc --status`), `ad-group-bid-modifiers` (add/update/remove — one ad group's own device bid), `account-exclusions` (add/remove — account-wide site, app, YouTube, content-type, IP and negative-keyword-list exclusions; `add --placement --youtube-channel --youtube-video --app --content-label --ip` comma-separated, or `--file`), `merchant-center` (list), and `draft`. `audiences attach` takes `--user-list`, `--interest` or `--custom-segment`. Amounts are in major currency units (converted to micros). Money/bids: `--amount`, `--cpc-bid`, `--target-cpa`, `--target-cpc`, `--max-cpc` take major units; `--target-roas` a ratio. `--target-cpc` pairs with `--bidding-strategy TARGET_CPC` and is Demand Gen only. `--max-cpc` sets the max CPC bid ceiling for `TARGET_IMPRESSION_SHARE` / `TARGET_SPEND` / `PERCENT_CPC` and must be paired with `--bidding-strategy`. Less-common ops accept a `--file` payload — a path to a JSON file, or the JSON object written inline (flags override its keys). Updates target a resource name or bare id as the positional argument; a target that names an op staged earlier **amends it in place**.
|
|
707
803
|
|
|
708
804
|
**A device criterion can be added, but never removed** — `campaign-criteria add` with a device criterion works, but Google then rejects `campaign-criteria remove` on it forever, including on a criterion you added yourself (and Baker refuses the removal before publish). Adjust it instead with `campaign-criteria update customers/<cid>/campaignCriteria/<campaignId>~<criterionId> --bid-modifier <n>`: `1` for no adjustment, `1.25` to bid 25% more, `0.9` to bid 10% less, `0` to stop serving on that device. Locations, languages and ad schedules remove normally.
|
|
709
805
|
|
|
710
|
-
**Images on a Search campaign** — `assets create` with an image publishes it into the advertiser's Google Ads asset library,
|
|
806
|
+
**Images on a Search campaign** — `assets create` with an image publishes it into the advertiser's Google Ads asset library, and `assets attach` with field type `AD_IMAGE` (campaign or ad group level) puts it on a Search campaign. Google opens Search image links per advertiser account, so staging asks Google with a check that changes nothing: an account that takes them stages the link, which applies at publish like a sitelink; one that does not — or that Google could not confirm either way — has the link **skipped** — never stored, the rest of a batch stages normally — and a **Task** is staged instead, "Add N images to <campaign> in Google Ads", which applies with the rest of the chat. An image staged with no accepted link raises that Task too, and it withdraws itself once an accepted link (or a Performance Max `asset-groups attach`) names the image. `MARKETING_IMAGE` and its square/portrait variants are Performance Max asset-group slots and are sent back to `AD_IMAGE`. The image must be 1.91:1 or 1:1; another shape is sent back to be cropped. The advertiser's logo links as `BUSINESS_LOGO`, at campaign or customer level, on a verified account.
|
|
711
807
|
|
|
712
808
|
**Primary vs secondary conversion actions** — `conversions update <id> --primary` makes an action a primary action (automated bidding optimizes toward it); `--no-primary` (or `--primary=false`) demotes it to secondary (reported only) — the spaced form `--primary false` is rejected, since a boolean flag written with a space is set to true and the word dropped. `conversions create` stages a new action as **secondary** unless its `--file` payload sets `"primaryForGoal": true`, so a new action never silently joins the bidding target of a Maximize Conversions or Target CPA campaign. The staged card shows an "Action optimization" row with the before/after role.
|
|
713
809
|
|
|
@@ -717,16 +813,22 @@ Command groups: `budgets`, `campaigns`, `ad-groups`, `keywords` (add/update/remo
|
|
|
717
813
|
|
|
718
814
|
**Retiring a conversion action** — `status` accepts `ENABLED` and `REMOVED` only. Google denies `HIDDEN` on every conversion-action write, so to stop bidding toward an action while it keeps measuring use `--no-primary`, and to stop it counting at all use `"status": "REMOVED"`. Conversion actions **Google owns** cannot be changed at all — Smart campaign call/directions actions, the in-ad lead form's submit, YouTube subscriptions and follow-on views, Floodlight and Search Ads 360 imports, store visits, codeless webpage actions. Staging any edit to one, a rename included, is refused up front; there is no manual route either, since the Google Ads interface will not change them.
|
|
719
815
|
|
|
816
|
+
**Validating the draft** — `baker ads google draft validate` sends the whole staged draft to Google exactly as publish would (same batching and order), with `validateOnly: true`, so nothing is applied. Each op comes back `accepted`, `refused` (with the reason publish would record), `blocked` (a change it depends on is refused), `unchecked` (only publish can check it — a new asset group, whose text assets are created first) or `applied` (an earlier publish did). Compact by default (counts plus the ops that need attention); `--full` lists every op with its request and Google's raw answer.
|
|
817
|
+
|
|
720
818
|
**Reviewing the draft** — `baker ads google draft list` renders everything you've staged as a grouped campaign ▸ ad group ▸ ad tree (with non-blocking completeness advisories), the CLI counterpart to the dashboard's Google Ads tab. Pass `--json` for the raw envelope. Once the chat has published it reports the outcome instead — `published · N applied, M failed`, with a `✗` line naming each op that did not land and why. Aim for a fully built campaign — 2–4 ad groups, ≥5 keywords each, 2–4 RSAs with 8–12 headlines, ≥4 sitelinks / ≥3 callouts / ≥1 structured snippet, and ≥1 shared negative list; the advisories flag what's still thin.
|
|
721
819
|
|
|
722
820
|
**Amending a staged op** — `draft amend <ref> (--file patch.json | --patch '<json>')` merges a JSON patch into an already-staged op's payload (objects deep-merge, `null` deletes a key, arrays/scalars replace) and re-validates it in full. Always prefer this over removing and re-staging — it preserves the op's ref (so dependents don't break) and its position in the draft. `draft show <ref>` prints the full staged payload (including warnings/annotations) so you can verify a change looks right before publish.
|
|
723
821
|
|
|
822
|
+
**Tracking warnings** — Tag Manager stage, amend, list and show responses provide repair hints whenever warnings remain. The compact `tag-manager draft list` prints the warnings beside their changes. Resolve the intended event role and technical gaps, then read the draft again before finishing; progress and diagnostics are not counted just to clear an alert. Adding and then removing a conversion that was never live cancels that proposal, so it disappears from the review.
|
|
823
|
+
|
|
724
824
|
**Reading an earlier chat's changes** — `draft list` and `draft show` take `--chat <chat-id>` to read another chat in the same company instead of the current one. Every staged-draft family accepts it: `ads google|meta|linkedin draft list|show`, `tag-manager draft list|show`, `actions draft`, `tags draft`, and `scheduled-agents list|get` (`list` leaves briefs out; `get <id>` or `list --full` returns them). A published draft is kept in full, so this returns what that chat actually wrote — every headline, bid and target — plus each op's outcome and error. Use it when asked to redo or reapply earlier work rather than reconstructing copy from a summary. Read-only: staging, `amend`, `remove` and `clear` stay bound to `BAKER_CHAT_ID`, so one chat can never edit another's draft. To re-run only the changes a publish failed to land, use the chat's **Retry** control in the dashboard — it re-sends exactly those and leaves the ones that applied untouched.
|
|
725
825
|
|
|
726
826
|
**Already-satisfied writes aren't staged** — an update or pause/resume whose every field already holds the requested value on the live account is dropped instead of staged: the envelope returns `"staged": false, "noop": true` with a `reason` ("this ad is already Paused on Google Ads"), plus a hint. This is a success, not an error — there is nothing to publish. Only a positively-read live snapshot can trigger it; a failed or empty read always stages. Batch responses list the dropped ops under `skipped`, and `count` reflects what actually entered the draft.
|
|
727
827
|
|
|
728
828
|
**Batch keyword adds** — `keywords add`, `negative-keywords add`, and `keyword-lists add` take a whole batch in one command: comma-separate `--text` entries and/or pass `--file <list.txt>` (one keyword per line). A `:EXACT`/`:PHRASE`/`:BROAD` suffix per entry overrides the `--match-type` default. Batches stage all-or-nothing as one request (limit 500); each keyword still lands as its own draft op, so it stays individually removable/amendable.
|
|
729
829
|
|
|
830
|
+
**Bulk changes — one file, one call** — `draft stage --file ops.json [--customer-id <id>]` stages up to 500 ops of any mix of kinds in one request: the file is a JSON array (or JSON Lines) of whole ops `{"kind","customerId"?,"target"?,"payload"?}`; `--customer-id` fills ops that leave it out. All-or-nothing on validity (an error names `ops[i]`); ops the account already satisfies come back as `skipped`. An op may name a `g_temp_*` ref from an earlier call, not one from the same file. `draft amend --file amends.json` (no ref) amends many staged ops at once from `[{"ref","patch"}, …]`, all or none. The `--file` create commands (`assets create|attach`, `audiences create`, `conversions create`, `bidding-strategies create`, `labels attach`, `campaign-criteria add`, `ad-group-criteria add`) take a JSON array of payloads too. `keywords pause|resume <target>…` (or `--file`, one resource name per line) change many keywords' status in one call, and `keyword-lists remove --list-ref <id> --text "a,b:EXACT"` takes terms out of a shared negative list — the CLI reads the list to find each member (no `:MATCH_TYPE` = every match type), or takes members by resource name / `listId~criterionId`. Five single stages of one kind in a row on a chat earn a `LOOP:` hint pointing at these. A chat's draft holds any number of ops (each is its own record); only one op over `GOOGLE_DRAFT_OP_MAX_BYTES` (512 KB) is refused — the stage or amend stages nothing and answers `OP_TOO_LARGE` with an `error.fix` (split that change).
|
|
831
|
+
|
|
730
832
|
**Pausing or removing a keyword returns a per-ad-group hint** — `keywords update --status PAUSED|REMOVED` and `keywords remove` come back with hints telling you to base the decision on `keyword_view` rows carrying `ad_group.id` + `ad_group_criterion.criterion_id`, never on campaign totals. A keyword's criterion id is derived from its text + match type, so every copy of the same keyword across ad groups shares one id and any aggregate blends the copy holding the history with the copies that never served. The `keyword-analysis` and `positive-keywords` presets select both ids so one query identifies each copy. A bid or URL edit gets no hint — only a pause/removal can strand history.
|
|
731
833
|
|
|
732
834
|
---
|
|
@@ -788,7 +890,7 @@ Read X Ads campaigns, line items, promoted tweets, creatives, audiences, and ana
|
|
|
788
890
|
|
|
789
891
|
| Subcommand | What it returns |
|
|
790
892
|
|---|---|
|
|
791
|
-
| `accounts` |
|
|
893
|
+
| `accounts` | X Ads accounts selected for this company that X currently lists |
|
|
792
894
|
| `funding` | Funding instruments for an account |
|
|
793
895
|
| `campaigns` | Campaigns (filter by `--funding-instrument-ids`, `--campaign-ids`) |
|
|
794
896
|
| `line-items` | Line items / ad groups (filter by `--campaign-ids`, `--line-item-ids`) |
|
|
@@ -863,6 +965,48 @@ baker ads x targeting-constants --constant tv_shows --locale en-US
|
|
|
863
965
|
|
|
864
966
|
---
|
|
865
967
|
|
|
968
|
+
### ChatGPT Ads (`baker ads chatgpt`)
|
|
969
|
+
|
|
970
|
+
Read the company's ChatGPT Ads account (OpenAI's ads inside ChatGPT conversations) and how it performs, and stage changes to it that apply when the chat is published. The company connects one account with an API key from its Ads Manager, pasted in the chat's connect card (`request_connection` `{ platform: "chatgpt-ads" }`) or in Brain → Integrations → ChatGPT Ads; the key stays in Baker and never reaches the CLI, so there is no account id to pass.
|
|
971
|
+
|
|
972
|
+
| Subcommand | What it returns |
|
|
973
|
+
|---|---|
|
|
974
|
+
| `account` | Name, currency, timezone, status, and the brand and account reviews that decide whether anything serves |
|
|
975
|
+
| `campaigns` | Campaigns: objective, daily or lifetime budget (currency units), schedule, locations, platforms, serving issues |
|
|
976
|
+
| `ad-groups` | Ad groups (`--campaign-id`): bid strategy, max bid, context hints, serving issues |
|
|
977
|
+
| `ads` | Ads (`--ad-group-id`): the card's copy, image, destination, review outcome, serving issues |
|
|
978
|
+
| `conversions` | Pixels (with the pixel id a landing page installs) and conversions, with the campaigns using each |
|
|
979
|
+
| `insights` | Impressions, clicks, spend, CTR, CPC, CPM, conversions, CPA, ROAS — per `--level account\|campaign\|ad_group\|ad`, over `--days N` or `--since/--until`, `--granularity`, `--segment country\|device\|platform\|product` |
|
|
980
|
+
| `campaigns create\|update\|pause\|resume\|archive` | Stage a campaign change: objective, daily or lifetime budget (currency units), schedule, countries and regions included and excluded, audiences, platforms, tracking template, status, the conversions it measures (`--conversion-event-ids`), a catalog (`--catalog-id`) |
|
|
981
|
+
| `ad-groups create\|update\|pause\|resume\|archive` | Stage an ad group change: context hints, bid strategy, max bid, audience bid adjustments, tracking template, product filters, status |
|
|
982
|
+
| `ads create\|update\|pause\|resume\|archive` | Stage a chat card: title, body, landing page, image and its crop, tracking template, status |
|
|
983
|
+
| `ads create-product` | Stage a catalog ad group's product ad: a title template the catalog's products fill in |
|
|
984
|
+
| `pixels create` | Stage a new website pixel |
|
|
985
|
+
| `audiences` | Custom audiences, with `create\|add\|remove\|archive` — members hashed on your machine, never sent raw |
|
|
986
|
+
| `catalogs` | Product catalogs, with `uploads` (how each upload went) and `products` (preview filters) |
|
|
987
|
+
| `conversions report` | Conversions split into click-through and view-through, with the attribution windows |
|
|
988
|
+
| `history` | What changed in the account, by anyone, with each field before and after |
|
|
989
|
+
| `account update` | Stage the advertiser's brand name or icon (`--favicon-url`) |
|
|
990
|
+
| `conversions create` | Stage a conversion — a standard event (`lead_created`, `order_created`, …) or a custom one by name — counted from the account's pixel |
|
|
991
|
+
| `conversions events` | What the pixel delivered in the last ~15 minutes — proof a site sends the event |
|
|
992
|
+
| `locations <place>` | Regions, cities and markets ChatGPT Ads can target, with the id `--location-ids` takes |
|
|
993
|
+
| `draft` | Review the staged changes as a tree; `show`, `amend`, `remove`, `clear`. After a publish, what applied and what failed |
|
|
994
|
+
|
|
995
|
+
`--full` on the list commands returns ChatGPT Ads' own objects. ChatGPT Ads refuses some metric and breakdown combinations (CPA, conversion rate and sales cannot be broken down; conversions come daily or as one total); `insights` refuses them first and says why.
|
|
996
|
+
|
|
997
|
+
Writes need `BAKER_CHAT_ID`: they stage on the chat and reach ChatGPT Ads only when it is published. New entities are created paused unless `--status active`.
|
|
998
|
+
|
|
999
|
+
```bash
|
|
1000
|
+
baker ads chatgpt account
|
|
1001
|
+
baker ads chatgpt insights --days 30 --granularity daily --level account
|
|
1002
|
+
baker ads chatgpt insights --campaign-id cmpn_123 --level ad --sort spend
|
|
1003
|
+
baker ads chatgpt campaigns create --name "Spring push" --objective clicks --daily-budget 20 --countries ES
|
|
1004
|
+
baker ads chatgpt ad-groups create --campaign-id chatgpt_temp_ab12 --name "Rooftop owners" --context-hints "solar panels for my house" --max-bid 2.50
|
|
1005
|
+
baker ads chatgpt draft
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
---
|
|
1009
|
+
|
|
866
1010
|
### Meta Ads (`baker ads meta`)
|
|
867
1011
|
|
|
868
1012
|
Meta Marketing API (Facebook + Instagram). Connect via OAuth in the dashboard, pick which ad accounts to scope to, then call from the CLI. Account ID via `--account-id act_123` or `BAKER_META_AD_ACCOUNT_ID` env var.
|
|
@@ -892,7 +1036,7 @@ baker ads meta insights --object act_123 --level adset \
|
|
|
892
1036
|
baker ads meta activities --account-id act_123 --days 7
|
|
893
1037
|
|
|
894
1038
|
# "Which Page should this ad run from?"
|
|
895
|
-
baker ads meta pages #
|
|
1039
|
+
baker ads meta pages # Pages with promotingAccountIds and hasPageAccessToken
|
|
896
1040
|
|
|
897
1041
|
# "What is the lead form collecting, and how many came in?"
|
|
898
1042
|
baker ads meta lead-forms --page-id 100635183117758 # instant forms on a Page + lead counts
|
|
@@ -908,6 +1052,11 @@ baker ads meta ads --account-id act_123 --all-statuses # widen to
|
|
|
908
1052
|
# "Find the October ad" — Meta matches the name, so only that row is read
|
|
909
1053
|
baker ads meta ads --account-id act_123 --all-statuses --name-contains octubre --fields id,name,adset_id,creative_id
|
|
910
1054
|
|
|
1055
|
+
# "Who will see an ad in this campaign?" — a viewer per ad set: cold/warm/hot, what they
|
|
1056
|
+
# already know (brand/offer/product), placements, and why — from targeting, audiences, objective,
|
|
1057
|
+
# and what each audience's name says (buyers, a lookalike), read in the advertiser's language
|
|
1058
|
+
baker ads meta adsets --campaign-id 6123 --all-statuses --audience --output json
|
|
1059
|
+
|
|
911
1060
|
# "Tell me about ad 9988"
|
|
912
1061
|
baker ads meta creatives --creative-id 9988
|
|
913
1062
|
baker ads meta preview --creative-id 9988 --ad-format MOBILE_FEED_STANDARD --out-file /tmp/p.html
|
|
@@ -930,6 +1079,8 @@ baker ads meta custom-conversions --account-id act_123 # id, event cate
|
|
|
930
1079
|
baker ads meta custom-conversions --account-id act_123 --full # raw rule JSON, retention, timestamps
|
|
931
1080
|
```
|
|
932
1081
|
|
|
1082
|
+
Use a Page only with an ad account listed in its `promotingAccountIds`. Instant-form reads need `hasPageAccessToken: true` on that Page and the company's lead-ads permissions.
|
|
1083
|
+
|
|
933
1084
|
**Custom Conversions** — a marketer's own conversion (a pixel event narrowed by a rule, e.g. "PageView on /gracias"), which is what "my conversions" almost always means. To optimize an ad set toward one, pass `--custom-conversion-id <id>` and nothing else: Baker reads the conversion off Meta at stage time and fills its pixel, event category and rule, the same four fields Ads Manager stores. Staging refuses an archived or unavailable conversion, one not built on a pixel, and one belonging to another ad account. Deleting one — in Events Manager or over the API — archives it: it leaves the panel but stays readable on the account, so the listing still shows it flagged `archived`.
|
|
934
1085
|
|
|
935
1086
|
`custom-conversions create` stages a new one (`--name`, `--pixel-id`, `--custom-event-type`, plus `--url-contains "/gracias"` or `--rule-file`). A rule is a tree of `and`/`or` over `{"<field>":{"<operator>":<value>}}` leaves, where the field is `URL`, `event`, `referrer` or any event parameter the pixel sends (`value`, `content_category`, or your own), so `--url-contains` is only the single-URL shortcut and `--rule-file` reaches the rest. Meta accepts an unmatchable rule and reports success, so staging refuses unknown operators, bare values, empty branches and two conditions written side by side where `and` was meant, before the op is staged. A case-sensitive `contains` is a real operator and gets through — on a URL always write `i_contains`. It is create-only, because Meta's edge offers neither update nor a delete that removes — which is also why staging refuses a create whose pixel, event category and rule already exist, live on the account or staged earlier in the same chat, or one that would pass Meta's cap of 100 per account. An ad set in the same chat can chain to it with `--custom-conversion-id meta_temp_*`.
|
|
@@ -952,6 +1103,11 @@ Chain a full ad from scratch with `meta_temp_*` refs (each create returns one):
|
|
|
952
1103
|
# 1. (optional) upload creative media from the Baker library → publishes an image hash
|
|
953
1104
|
baker ads meta media upload --account-id act_123 --kind image --image-id <bakerImageId> # → meta_temp_img
|
|
954
1105
|
|
|
1106
|
+
# For on-ad leads, choose a Page with this account in promotingAccountIds and hasPageAccessToken: true
|
|
1107
|
+
baker ads meta lead-forms create --account-id act_123 --page-id 555 --name "Free audit" \
|
|
1108
|
+
--questions FULL_NAME,EMAIL,PHONE --privacy-policy-url https://example.com/privacy \
|
|
1109
|
+
--follow-up-url https://example.com # → meta_temp_form
|
|
1110
|
+
|
|
955
1111
|
# 2. campaign → ad set → creative → ad
|
|
956
1112
|
baker ads meta campaigns create --account-id act_123 --name "Q3 Prospecting" --objective OUTCOME_LEADS --daily-budget 50 # → meta_temp_camp
|
|
957
1113
|
baker ads meta adsets create --account-id act_123 --campaign meta_temp_camp --name "US 25-54" \
|
|
@@ -997,9 +1153,8 @@ To find one entity by name, pass `--name-contains <text>` to `campaigns`, `adset
|
|
|
997
1153
|
|
|
998
1154
|
```
|
|
999
1155
|
accounts # ad accounts in the connected scope
|
|
1000
|
-
accounts --include-all # every account the token can see (admin/debug)
|
|
1001
1156
|
account # single-account detail (currency, balance, business, status)
|
|
1002
|
-
businesses #
|
|
1157
|
+
businesses # businesses linked to selected ad accounts
|
|
1003
1158
|
campaigns # ACTIVE-only by default
|
|
1004
1159
|
adsets # filter by --campaign-id
|
|
1005
1160
|
ads # filter by --adset-id or --campaign-id
|
|
@@ -1061,10 +1216,24 @@ baker ads linkedin analytics --level campaign --campaign-id 1234 \
|
|
|
1061
1216
|
# "Top companies seeing this campaign" (ABM feedback loop) — org names auto-resolved
|
|
1062
1217
|
baker ads linkedin top-companies --campaign-id 1234 --last-days 30
|
|
1063
1218
|
|
|
1219
|
+
# "Which companies are our ads reaching?" — one row per company over the whole period,
|
|
1220
|
+
# most engaged first ({org, name, impressions, clicks, engagements, costLocal}).
|
|
1221
|
+
# Defaults: last 12 calendar months, the only selected ad account, top 100 (--full for all).
|
|
1222
|
+
baker ads linkedin companies --account-id 1234
|
|
1223
|
+
baker ads linkedin companies --page 2 # companies 101-200, most engaged first
|
|
1224
|
+
baker ads linkedin companies --by month --min-impressions 50 # months[] per company
|
|
1225
|
+
baker ads linkedin companies --campaign-id 1,2 --by campaign # campaigns[] per company
|
|
1226
|
+
baker ads linkedin companies --with-domains --stats --output csv # + domain/domainSource; read-quality stats
|
|
1227
|
+
|
|
1064
1228
|
# "Who are these org URNs?" — resolve org URNs to company names (probe leakage)
|
|
1065
1229
|
baker ads linkedin resolve --urns urn:li:organization:17719,urn:li:organization:19022
|
|
1066
1230
|
baker ads linkedin resolve --ids 17719,19022 --output csv
|
|
1067
1231
|
|
|
1232
|
+
# "What are these companies' website domains?" — for matching against CRM companies.
|
|
1233
|
+
# Per org: vanityName (LinkedIn page), website, domain, domainSource (website | subdomain | none);
|
|
1234
|
+
# ids LinkedIn doesn't know land in `errors`. Shortlinks are never followed.
|
|
1235
|
+
baker ads linkedin resolve --ids 1035,1337 --with-domains
|
|
1236
|
+
|
|
1068
1237
|
# "Who is in our audience by industry/seniority/function/title — all at once?"
|
|
1069
1238
|
baker ads linkedin demographics --campaign-id 1234 --last-days 30
|
|
1070
1239
|
|
|
@@ -1101,7 +1270,8 @@ baker ads linkedin bid-pricing --account-id 503001492 \
|
|
|
1101
1270
|
**`analytics`** — the 3-axis workhorse:
|
|
1102
1271
|
- `--level account` (override: `campaign-group|campaign|creative`)
|
|
1103
1272
|
- `--intent baseline` field bundle. Intents: `baseline | revenue | funnel | engagement | video | lead-gen | inmail | document | ranking | identity`. List with `--list-intents`.
|
|
1104
|
-
- `--pivot none`. Pivots: `none | campaign | campaign-group | creative | company | account | conversion | job-title | job-function | seniority | industry | company-size | country | region | device | placement | serving-location | card-index | objective | conversation-node | conversation-node-button`. List with `--list-pivots`.
|
|
1273
|
+
- `--pivot none`. Pivots: `none | campaign | campaign-group | creative | company | account | conversion | job-title | job-function | seniority | industry | company-size | country | region | dma | device | placement | serving-location | card-index | objective | conversation-node | conversation-node-button`. List with `--list-pivots`.
|
|
1274
|
+
- `--pivot dma` — US Nielsen Designated Market Areas, with region names resolved. LinkedIn's terms: any report built from it writes "DMA®" each time and carries "DMA® is a registered service mark of The Nielsen Company." — the `DMA_ATTRIBUTION_REQUIRED` warning repeats this on every call.
|
|
1105
1275
|
- `--granularity DAILY`. Auto-forced to `ALL` when pivoting on a `MEMBER_*` dim (LinkedIn rejects DAILY + demographic) — surfaced in stderr + `warnings`.
|
|
1106
1276
|
- `--last-days 7` unless you pass `--start`/`--end`.
|
|
1107
1277
|
- Sort: `costInUsd` descending. `--no-sort` to disable.
|
|
@@ -1125,7 +1295,6 @@ baker ads linkedin bid-pricing --account-id 503001492 \
|
|
|
1125
1295
|
|
|
1126
1296
|
```
|
|
1127
1297
|
accounts # accounts in the connected picker scope
|
|
1128
|
-
accounts --include-all # every account the token can see
|
|
1129
1298
|
account # single-account detail (currency, type, status)
|
|
1130
1299
|
|
|
1131
1300
|
campaign-groups # default ACTIVE-only
|
|
@@ -1134,6 +1303,7 @@ creatives # default ACTIVE intended-status
|
|
|
1134
1303
|
|
|
1135
1304
|
analytics # see "Smart defaults" above (3-axis: level × intent × pivot)
|
|
1136
1305
|
demographics # sweep all firmographic pivots in one call
|
|
1306
|
+
companies # which companies the ads reached, one row per company (--by month|campaign, --with-domains)
|
|
1137
1307
|
top-companies # pivot=MEMBER_COMPANY shortcut (ABM)
|
|
1138
1308
|
conversation # pivot=CONVERSATION_NODE_BUTTON shortcut (Sponsored Messaging)
|
|
1139
1309
|
|
|
@@ -1153,6 +1323,7 @@ audit --format md # deliverable-ready markdown table
|
|
|
1153
1323
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
1154
1324
|
campaigns create|update|pause|resume|archive|duplicate
|
|
1155
1325
|
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
1326
|
+
campaigns exclude-companies <ad-set-id> --org <ids> # stop an ad set reaching these companies
|
|
1156
1327
|
account url-params # UTMs for every ad in the whole account
|
|
1157
1328
|
url-params [--ad-sets] # read what is tagged now, and what lands twice
|
|
1158
1329
|
creatives create|update|pause|resume|duplicate
|
|
@@ -1193,12 +1364,21 @@ baker ads linkedin account url-params --param "utm_source=linkedin&utm_medium=pa
|
|
|
1193
1364
|
baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
|
|
1194
1365
|
baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
|
|
1195
1366
|
|
|
1196
|
-
# Matched audiences (CSV with header; email columns SHA-256 hashed locally)
|
|
1367
|
+
# Matched audiences (CSV with header; email columns SHA-256 hashed locally).
|
|
1368
|
+
# Company lists take companyName,domain,organizationUrn — organizationUrn matches the company page exactly
|
|
1197
1369
|
baker ads linkedin audiences create --name "ABM Tier-1" --type company-list --list-file accounts.csv
|
|
1198
1370
|
|
|
1371
|
+
# Stop an ad set reaching customers / open deals — add-only; at publish Baker re-reads the ad set
|
|
1372
|
+
# and merges into its targeting as it stands then. --org takes company ids, ≤200 per call;
|
|
1373
|
+
# a longer list goes into a company-list audience passed as --segment
|
|
1374
|
+
baker ads linkedin campaigns exclude-companies 123456 --org 1035,1441
|
|
1375
|
+
baker ads linkedin campaigns exclude-companies 123456 --segment urn:li:dmpSegment:42
|
|
1376
|
+
|
|
1199
1377
|
# Conversions with playbook-default windows
|
|
1200
1378
|
baker ads linkedin conversions create --name "Demo booked" --type LEAD --method INSIGHT_TAG \
|
|
1201
1379
|
--post-click-window 30 --view-window 7
|
|
1380
|
+
# Windows are 1|7|30|90 days; 180 and 365 only for LEAD, QUALIFIED_LEAD, MARKETING_QUALIFIED_LEAD,
|
|
1381
|
+
# SALES_QUALIFIED_LEAD, PURCHASE, ADD_TO_CART, SUBMIT_APPLICATION — refused at stage time otherwise
|
|
1202
1382
|
|
|
1203
1383
|
# Safe duplication — stage-time read of the source → staged DRAFT create ("Duplicate ad", never "Link to original")
|
|
1204
1384
|
baker ads linkedin campaigns duplicate 123456 --name "New variant"
|
|
@@ -1257,7 +1437,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
|
|
|
1257
1437
|
- OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
|
|
1258
1438
|
- Account scoping: backend rejects calls against an account not in the picker selection.
|
|
1259
1439
|
- Pinned API version: `Linkedin-Version: 202606` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window. The floor is `TRACKING_PARAM_DYNAMIC_MIN_API_VERSION` (`CREATIVE_NAME` needs 202606), asserted in `ads/linkedin/utils.test.ts`.
|
|
1260
|
-
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, bid-pricing/forecast 6h, audience-size 1h.
|
|
1440
|
+
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, org domains 7d, bid-pricing/forecast 6h, audience-size 1h.
|
|
1261
1441
|
- `--no-cache` on any command skips both cache layers (the CLI's own and the server's).
|
|
1262
1442
|
|
|
1263
1443
|
#### LinkedIn-specific gotchas
|
|
@@ -1313,6 +1493,7 @@ baker analytics tracking # do the ad URLs carry the campaign, a
|
|
|
1313
1493
|
baker analytics tracking --platform meta # the same for one platform, read from their own ad account
|
|
1314
1494
|
baker analytics map --platform google --set kw=keyword # name a parameter, seen or not yet
|
|
1315
1495
|
baker analytics map --platform google --remove kw # take that answer back
|
|
1496
|
+
baker analytics unstage --kind mapping --ref google/kw # take back what this chat staged (mapping|conversion|send|source), or --all
|
|
1316
1497
|
baker analytics conversions # what counts, what each produced, and which Forms count nothing
|
|
1317
1498
|
baker analytics conversions --candidates --output md # everything these pages do, and which of it is counted, as tables
|
|
1318
1499
|
baker analytics conversions --candidates # the same as compact JSON: one line per page event, Form keys folded onto `flows`
|
|
@@ -1385,7 +1566,7 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
|
|
|
1385
1566
|
|
|
1386
1567
|
**`tracking` answers "can this account be reported on at all", and it reads the ad account to do it.** `analytics ads` can only describe parameters that have already arrived, which leaves two blind spots that mislead in the expensive direction: a platform with no traffic in the window looks exactly like a platform whose ads carry nothing, and a dimension arriving under a name nobody has mapped looks exactly like one nothing carries. `baker analytics tracking` reads the mapping, the traffic **and** the client's own Google tracking templates / Meta URL parameters, so it can answer for a platform that has not had a click yet. Every dimension comes back in one of four states, and they are four different jobs: `covered` is done; `unmapped` means the URLs already carry it under a name Baker does not read, so run the `mapCommand` in the response — free, and it repairs the traffic already collected; `missing` means nothing carries it; `unknown` means no visit arrived *and* the account could not be read, which is not a finding and must never be reported as untagged.
|
|
1387
1568
|
|
|
1388
|
-
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write.
|
|
1569
|
+
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write. ChatGPT Ads is one Baker can write: its campaign and ad group `--tracking-template` (`baker ads chatgpt campaigns|ad-groups update`), which should carry `campaign_id={campaign_id}&ad_group_id={ad_group_id}&ad_id={ad_id}` — an id that only rides in `utm_campaign` or `utm_content` is reported as that UTM and never as the campaign or the ad, so `--platform chatgpt --role adgroup_id` has nothing to split by.
|
|
1389
1570
|
|
|
1390
1571
|
**The one recommendation about the ad account itself.** `baker analytics ads --platform <p>` warns when that platform's traffic carries no campaign id, ad group id or ad id — the three tiers every "which ad worked" question is grouped by — and returns the exact URL suffix, in the platform's own macro language, that would start sending the missing ones. It is the only mapping gap that cannot be fixed afterwards: naming a parameter Baker already captures corrects the history, because roles are resolved when a report is read, but a parameter the ads never sent has no stored values for a meaning to apply to. The hint stays quiet for a platform with little traffic, and defers to the untagged-account finding rather than repeating it.
|
|
1391
1572
|
|
|
@@ -1458,14 +1639,15 @@ baker experiment update --id a3f91c2b --because "…" # a live one
|
|
|
1458
1639
|
baker experiment status # the verdict on every test, first; dates as calendar dates
|
|
1459
1640
|
baker experiment status --id a3f91c2b --full # one test, with everything the dashboard reads and the posteriors
|
|
1460
1641
|
baker experiment history --landing oferta # every finished test on a page: claim, numbers, decision, learning
|
|
1642
|
+
baker experiment start … --hold # publish both versions with the split off, for the client to approve by link first
|
|
1461
1643
|
baker experiment pause --id a3f91c2b # everyone sees the original for now
|
|
1462
|
-
baker experiment resume --id a3f91c2b # the split picks up with the same sides
|
|
1644
|
+
baker experiment resume --id a3f91c2b # the split picks up with the same sides; a held test starts here, dated from now
|
|
1463
1645
|
baker experiment finish --id a3f91c2b --keep variant --learning "…" # end it now; say which version stays and what it taught
|
|
1464
1646
|
baker experiment cancel --id a3f91c2b # call it off: staged → gone, live → ended, original kept
|
|
1465
1647
|
baker experiment fold # make the page's own content the version that was kept
|
|
1466
1648
|
```
|
|
1467
1649
|
|
|
1468
|
-
**A page has variants. A variant is never a page.** The variant is a real built folder at `src/pages/oferta--1/`, so it gets every quality gate a page gets — which matters, because it is about to be shown to half the client's visitors. What it does not get is a public address: it is absent from the Landings list and the sitemap, names the page's URL as its canonical, and `/oferta--1/` returns **404** at the edge. It is as indexable as the page it is a version of, deliberately — half the crawls of `/oferta/` are served the variant, so a `noindex` on it is a `noindex` on the live page. Both versions answer at `/oferta/`. Assignment happens while the HTML is served, so there is no flicker and nothing for an ad blocker to suppress, and the split is a path rewrite and never a redirect. To look at one side deliberately, `/oferta/?_bkr_preview=a` and `?_bkr_preview=b` serve it exactly, with no assignment and no cookie.
|
|
1650
|
+
**A page has variants. A variant is never a page.** The variant is a real built folder at `src/pages/oferta--1/`, so it gets every quality gate a page gets — which matters, because it is about to be shown to half the client's visitors. What it does not get is a public address: it is absent from the Landings list and the sitemap, names the page's URL as its canonical, and `/oferta--1/` returns **404** at the edge. It is as indexable as the page it is a version of, deliberately — half the crawls of `/oferta/` are served the variant, so a `noindex` on it is a `noindex` on the live page. Both versions answer at `/oferta/`. Assignment happens while the HTML is served, so there is no flicker and nothing for an ad blocker to suppress, and the split is a path rewrite and never a redirect. To look at one side deliberately, `/oferta/?_bkr_preview=a` and `?_bkr_preview=b` serve it exactly, with no assignment and no cookie — and they keep answering while the test is paused and after it ends, up to `fold`, which removes the other version's file. That window is how a client is shown a version at all.
|
|
1469
1651
|
|
|
1470
1652
|
**The goal is one event, named by its key.** `--goal submit:book-a-call` — the same vocabulary the event stream uses (`form:<flow>:<node>:<trigger>`, `submit:<flow>`, `page:<name>`, `exit:<host>`), and `baker analytics conversions --candidates` lists the keys these pages produce. Not a conversion *name*, which groups several keys: a variant that swaps the page's Form for a shorter one converts on a different key, and a name would either count both endings as one outcome or count the new version as never converting at all — `--variant-goal` gives the new version its own event when it has one. The goal is fixed for the life of the test and the verdict is read on it alone. Every other conversion the company counts comes back under `secondary` on every result, with the same interval and a one-word reading, and decides nothing. A conversion counts for a visitor wherever it happens after they were shown a version — on the page, on another page, in a delivered lead, or in a deal the CRM posted a week later by email.
|
|
1471
1653
|
|
|
@@ -1485,6 +1667,26 @@ baker experiment fold # make the page
|
|
|
1485
1667
|
|
|
1486
1668
|
**`plan` before building anything.** At a 3% conversion rate a +20% lift needs roughly 14,000 visitors *per variant*; `plan` says how long that is at this page's traffic, lists what this page's visitors actually went on to do, and what a 14, 28, 56 or 90-day window could settle. The duration is a guide and never a finish line — the test ends when the numbers are clear, and a page with no traffic yet still runs, sized on a stand-in that `warning` names (`--baseline 0.05` states the rate to expect). What `plan` refuses is what cannot be measured: no conversion defined, or a goal the page has demonstrably never produced. `--lift` sets the smallest lift worth detecting (default `0.2`) — halving it roughly quadruples the traffic needed.
|
|
1487
1669
|
|
|
1670
|
+
### Site settings (`baker site`)
|
|
1671
|
+
|
|
1672
|
+
The company's site settings — cookie consent, what Baker records, whether search engines show the site, the home page, the time zone, the landing download format, and custom domains. Every change stages on the chat and applies when the chat is published; discarding the chat drops it.
|
|
1673
|
+
|
|
1674
|
+
```bash
|
|
1675
|
+
baker site show # settings after this chat publishes, and what it staged (before/after)
|
|
1676
|
+
baker site set --consent recovery # bypass | recovery | strict | external
|
|
1677
|
+
baker site set --collection aggregate # full | aggregate | off (+ --payloads on|off with full)
|
|
1678
|
+
baker site set --indexing off # on | off
|
|
1679
|
+
baker site set --home oferta --home-mode redirect # a landing slug (rewrite by default), or --home none
|
|
1680
|
+
baker site set --timezone Europe/Madrid # IANA name
|
|
1681
|
+
baker site set --export-profile @profile.json # landing download format, JSON or @file
|
|
1682
|
+
baker site domain add shop.acme.com # staged; the client adds CNAME shop.acme.com → customers.withbaker.com
|
|
1683
|
+
baker site domain list # status of every domain: staged, pending_verification, active, error
|
|
1684
|
+
baker site domain verify shop.acme.com # re-check a pending domain now
|
|
1685
|
+
baker site unstage consent # take back one staged setting (or --all)
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
`set` takes several flags in one call and stages all of them or none. A staged setting that cannot apply at publish — a home page whose landing is not published yet, a domain another company registered in the meantime — is skipped with its reason on the chat's row and in `baker chats view` → `effects.siteSettings`; everything else in the chat still publishes.
|
|
1689
|
+
|
|
1488
1690
|
### Google Analytics 4 (`baker ga4`)
|
|
1489
1691
|
|
|
1490
1692
|
GA4 commands for multi-channel audits **and** for configuring what the property measures. Playbook-aligned report presets, property health audits, free-form Data API queries, and staged Admin API writes.
|
|
@@ -1497,7 +1699,7 @@ A Google Analytics connection made before writes shipped is read-only and every
|
|
|
1497
1699
|
|
|
1498
1700
|
### `baker ga4 properties`
|
|
1499
1701
|
|
|
1500
|
-
List
|
|
1702
|
+
List the GA4 properties selected for this company. Run this first to find property IDs.
|
|
1501
1703
|
|
|
1502
1704
|
```bash
|
|
1503
1705
|
baker ga4 properties
|
|
@@ -1728,6 +1930,25 @@ baker gsc sitemaps --site-url "https://example.com/"
|
|
|
1728
1930
|
|
|
1729
1931
|
---
|
|
1730
1932
|
|
|
1933
|
+
### Making ads (`baker creatives brief` / `ideas` / `make`)
|
|
1934
|
+
|
|
1935
|
+
Ads start from what customers said. Three commands carry the process; the agent writes the ideas and the copy.
|
|
1936
|
+
|
|
1937
|
+
```bash
|
|
1938
|
+
baker creatives brief [--adset <id>] [--account-id act_<id>] [--no-research] # one read: viewer, account bottleneck, what works, taste, customer quotes, ideas on file
|
|
1939
|
+
baker creatives ideas [--full] # check and rank src/creatives/_ideas/*.md; build_next
|
|
1940
|
+
baker creatives make <idea> [--slug <s>] [--adset <id>] [--reference <adId> [--platform meta|linkedin|tiktok|google]] [--spec <beats.json>] [--from-idea] [--series <lead>]
|
|
1941
|
+
baker creatives translate --slug <ad> --to es-MX,es-CO,pt-BR [--copy-file <json>] [--note "<brief>"] [--again] # the same ad in other languages, one design
|
|
1942
|
+
baker creatives learn [--days <n>] [--dry-run] # what each idea's ads did, written back onto the idea
|
|
1943
|
+
```
|
|
1944
|
+
|
|
1945
|
+
- `brief` returns a verdict, not raw numbers: `account.bottleneck` is `hook`, `after_click`, `none` or `too_little_data`. A part that cannot be read says `unavailable`; the market searches (the followed brands, and the library for what COMPANY.md's first line says the company sells, in a few words) run at once and get 12 seconds, so a slow ad library leaves them out rather than holding the brief. With few customer quotes on file it runs `research customers` itself alongside the reads (about 30 s) and returns what it wrote; `--no-research` skips that. With no ideas on file, its hint lists the values `awareness`, `angle` and `format` take. `market` says what competitors' ads already say (the followed brands, else the library), how sophisticated the market is, and which awareness stages nobody speaks to.
|
|
1946
|
+
- `ideas` is local and nearly free: two readings of the words go to the decision model (a fraction of a cent, billed with the workspace's next run, answers kept in `canvas/.cache/`). An insight filed as `observed` reads as `interpreted` when its source cannot be opened or its quote is a statistic about people rather than someone's words ("39% de los propietarios…"), and a new idea is warned when its message says what a failed idea said, however reworded or translated. Without an answer, an insight keeps its status and only the same words count as the same message. An idea's `evidence` resolves to `src/content/insights/` entries, or it is an `explore` bet. Numbers must be in the Knowledge or our own customers' quotes (a Reddit, forum or web quote never sources a claim), no two ideas may share an angle and format, and a review or testimonial format needs a quote from our own customer.
|
|
1947
|
+
- `learn` pools each built idea's creatives, judges them against the rest of the account's cost per result and writes `status` (`winner`, `failed`, `inconclusive`, `testing`) and a frozen `result` onto the idea. `ideas` then warns when a new idea repeats a failed one.
|
|
1948
|
+
- `make` builds one idea, on a proven winning ad whenever one fits. A static searches the idea's container on the idea's message for our viewer, then for the container's strongest winners from any category (no query, by winner score), pools the candidates (each ad once, the better search first), checks up to six with `winning-ads fit`, three at a time (the next only once one does not fit), and keeps the best-ranked one that fits as soon as it and every better one have answered. The fit says what made that ad a winner, so what the creative carries of it (`reference.carry`: `whole_ad`, `layout`, `hook` or `angle`); make seeds it into `prompt.json` as `concept`, the canvas keeps the ad as its `original` node, and the renders see it for its structure only at `whole_ad` or `layout`. The first hint names the ad and what it carries (for a `--reference` chosen by hand, only the ad: nothing checked what made it win); if the concept could not be written into `prompt.json`, a hint says so and how to write it from the fit already made. For a new creative it starts scaffolding the best-ranked one while the fits are asked (kept when it fits, stopped and removed whole when another or none is kept). Finding, checking and describing a reference gets 150 seconds; past that the build is stopped and the static starts from the idea (`REFERENCE_SLOW`). A creative whose ad is already written is never built over (`ALREADY_MADE`): edit its `prompt.json`, or pass `--slug` for another ad of the same idea. A video idea takes its beats spec. Either way it records the idea on `_definition.md`, sets `prompt.json`'s `message` and syncs the definition. Drafted copy lines claiming a figure the company never wrote come back first in `hints`. Only when no proven ad fits (or with `--from-idea`) does a static start from the idea itself, and the first hint says why (none in the container, what the ones checked won on that the company lacks, the time ran out, or the proven ad it picked could not be read; a `--reference` chosen by hand that cannot be read fails instead): `prompt.json` holds the message and placeholders, and `canvas run` refuses to render until the scene and copy are written.
|
|
1949
|
+
- `make --series <lead>` makes a static idea as another ad of a series: several messages in one style. Nothing is searched or rendered; the ad's `_definition.md` names its lead (`series: <lead>`), its `prompt.json` starts as the lead's ready version with the idea's message, and only its `copy` is written. Its canvas is written from the lead's on every run: the lead's finished picture with this ad's words, every other size the lead's own size with the same words. `baker canvas run --series <lead>` renders every ad of the series at once, one reviewed and corrected run each, skipping an ad already ready from the lead's current version.
|
|
1950
|
+
- `translate --slug <ad> --to <langs>` makes the ad in other languages as ads of the series it leads: one creative per language or country variant, `<ad>-<lang>` (the BCP-47 tag in lowercase: `-es-mx`), whose `_definition.md` says `series: <ad>` and `language: <lang>` (canonical case, `es-MX`, as the spec's market and the dashboard store it; tags are compared without case) and whose `prompt.json` is the ad's as last drawn with its words translated. Every language is translated in one text call (transcreated, each regional tag in that country's own variety and form of address, `es-MX` and `es-CO` two ads, each line keeping its role and about its length, brand names, prices and numbers kept, a customer's quote and the name on it never translated, and the ad's `message` translated with the lines; the spend is billed with the workspace's next run), or read from `--copy-file`: `{"<lang>": ["line 1", …]}` is every line with no model call, `{"<lang>": {"headline": "…", "3": "…"}}` only the lines given (by role or number), the model writing the rest. `--note` is the agent's brief for the translator (tone, form of address, terms to keep). Each made language lists the lines it `kept` as a customer said them, and the answer warns when the ad's `prompt.json` changed after its last render, since translations are made from the version rendered. The ad may itself be an ad of a series (an angle ad), so angles × languages stay one design. A language already made is left as it is, so adding one later re-renders nothing; `--again` translates it again. Then `baker canvas run --series <ad>` renders them; a new version of the ad reaches every language on the next `--series` run.
|
|
1951
|
+
|
|
1731
1952
|
### Competitive Intelligence (`baker research`)
|
|
1732
1953
|
|
|
1733
1954
|
Market and competitor research powered by DataForSEO and AI-powered Google Search grounding. Shows who's competing for keywords, search intent classification, competitor keyword strategies, keyword gaps, landing page performance, and open-ended AI research.
|
|
@@ -1826,11 +2047,52 @@ baker research fetch "https://competitor.com" --format html --whole-page
|
|
|
1826
2047
|
- Each URL is cached server-side for 24 hours, so a follow-up `--full` call costs no extra credit.
|
|
1827
2048
|
- Failures carry `error.fix` with `action: "continue_without"` for pages that will not open — finish the rest of the job and report the gap rather than retrying.
|
|
1828
2049
|
|
|
2050
|
+
|
|
2051
|
+
A Reddit thread link is not sent to Firecrawl (which refuses Reddit): it is read by the Reddit reader below and returned as the thread written out as a page, with `"provider": "reddit"`. Any other Reddit page is refused with a pointer to `baker research reddit`.
|
|
2052
|
+
|
|
2053
|
+
---
|
|
2054
|
+
|
|
2055
|
+
### `baker research reddit subreddits` / `search` / `thread`
|
|
2056
|
+
|
|
2057
|
+
Read Reddit verbatim. Reddit refuses ordinary web reads, Firecrawl, and datacenter addresses, so these commands read it through a ladder of sources — Reddit's own API (when the backend holds `REDDIT_CLIENT_ID` / `REDDIT_CLIENT_SECRET`), the Arctic Shift public archive, then Reddit's public JSON through the proxy tiers — and report which one answered.
|
|
2058
|
+
|
|
2059
|
+
```bash
|
|
2060
|
+
baker research reddit subreddits "amazon flex uk"
|
|
2061
|
+
baker research reddit search "agency never paid" --subreddit couriersofreddit,AmazonFlexUK --kind comments
|
|
2062
|
+
baker research reddit search "visa" --subreddit UberEATS --kind comments --since 2025-01-01 --limit 10
|
|
2063
|
+
baker research reddit thread "https://www.reddit.com/r/AmazonFlexUK/comments/1abc2de/agency_still_hasnt_paid_me/"
|
|
2064
|
+
baker research reddit thread "https://www.reddit.com/r/running/s/3TzXiyxaMD" --comments 100
|
|
2065
|
+
```
|
|
2066
|
+
|
|
2067
|
+
| Flag | Description |
|
|
2068
|
+
|------|-------------|
|
|
2069
|
+
| `--subreddit` | Subreddits (up to 10), comma-separated or the flag repeated. Required for `--kind comments`, and for any search when the Reddit API is not configured |
|
|
2070
|
+
| `--kind` | `posts` (default) or `comments` |
|
|
2071
|
+
| `--since` | Only things written on or after `YYYY-MM-DD`; results then come newest first instead of best match first |
|
|
2072
|
+
| `--limit` | Search results, 1-50 (default 25) |
|
|
2073
|
+
| `--limit` (subreddits) | Subreddits to return, 1-25 (default 10) |
|
|
2074
|
+
| `--comments` | Thread comments, 0-200 (default 40), best conversations first |
|
|
2075
|
+
| `--full` | Whole texts instead of 1,500-character previews |
|
|
2076
|
+
|
|
2077
|
+
`subreddits` returns `name`, `subscribers` (size, not activity), `access` (`public` / `restricted` / `private`) and `description` — use it instead of guessing subreddit names. Cached 24h.
|
|
2078
|
+
|
|
2079
|
+
Every search or thread item carries verbatim `text`, `author`, `score`, `created` and a `permalink` built from the ids the source returned. `thread` refuses a link whose post lives in a different subreddit than the link names (`NOT_FOUND`, `fix.action: "reject"`) — the shape of a link invented by a summary. `source` is `reddit_api`, `reddit_web` or `archive`; archive points on posts under ~36h old are as first captured. A link to one comment opens the thread at that comment; a post on a user's profile (`/user/<name>/comments/<id>`) reads like any other thread. Cached 6h; an empty search is not cached.
|
|
2080
|
+
|
|
1829
2081
|
---
|
|
1830
2082
|
|
|
2083
|
+
### `baker research customers`
|
|
2084
|
+
|
|
2085
|
+
Finds what real customers say and writes each quote as an insight in `src/content/insights/` (type, quote, source, date, `status: observed`). Sources: the company's reviews library, Reddit, and with `--web` pages from web research. A quote is kept only when it appears word for word in the text that was fetched; the model classifies quotes and never writes them.
|
|
2086
|
+
|
|
2087
|
+
```bash
|
|
2088
|
+
baker research customers --focus "solar panels" # reviews + Reddit
|
|
2089
|
+
baker research customers --query "payroll mistakes" --web --limit 15
|
|
2090
|
+
baker research customers --dry-run # print, write nothing
|
|
2091
|
+
```
|
|
2092
|
+
|
|
1831
2093
|
### `baker research advertisers "keyword"`
|
|
1832
2094
|
|
|
1833
|
-
Returns a ranked list of **domains** competing for one keyword in Google, with average SERP position, relevance rating, estimated traffic value and visibility — domain economics, **NOT ad copy**. Despite the name it returns no headlines, descriptions or creative of any kind; for a competitor's actual ad copy use `baker winning-ads content <adId>` (Meta
|
|
2095
|
+
Returns a ranked list of **domains** competing for one keyword in Google, with average SERP position, relevance rating, estimated traffic value and visibility — domain economics, **NOT ad copy**. Despite the name it returns no headlines, descriptions or creative of any kind; for a competitor's actual ad copy use `baker winning-ads content <adId>` (Meta, LinkedIn, TikTok and Google, for brands the library tracks) or, for a one-off Google read, `baker research google-ads <domain> --copy`.
|
|
1834
2096
|
|
|
1835
2097
|
Defaults to `--location us` and `--language en` when omitted; the response carries a `query_context` showing which pair was used, so always set both for non-US or non-English markets.
|
|
1836
2098
|
|
|
@@ -2260,15 +2522,17 @@ Each external source is its own subcommand. Pick the verb that matches the sourc
|
|
|
2260
2522
|
| `baker images get <id>` | Single record | n/a |
|
|
2261
2523
|
| `baker images upload <file\|url>` | Upload a local file OR remote URL to the library | always on |
|
|
2262
2524
|
| `baker images delete <id>` | Delete a record | n/a |
|
|
2525
|
+
| `baker images retry <id>` | Process a failed image (status `error`) again | n/a |
|
|
2263
2526
|
| `baker images describe <id>` | Correct a record's stored name / description / tags | n/a |
|
|
2264
2527
|
| `baker images normalize <files>` | Local recolor + bg removal + trim + resize | n/a (operates on disk, no upload) |
|
|
2265
|
-
| `baker images upscale <imageId>` | Real-ESRGAN super-resolution via backend ($0.05/image, cost-tracked) | n/a (operates on library image) |
|
|
2528
|
+
| `baker images upscale <imageId>` | Real-ESRGAN super-resolution via backend ($0.05/image, cost-tracked). `--remove` drops the upscaled copy and goes back to the original (free) | n/a (operates on library image) |
|
|
2266
2529
|
| `baker images layerize <imageId>` | Split into editable layers — transparent cutouts per element plus baked-in copy recovered as editable text (costs credits) | n/a (operates on library image) |
|
|
2267
2530
|
| `baker images layers <imageId>` | Read the layers of an already-split image (free) | n/a (operates on library image) |
|
|
2268
2531
|
| `baker images crop <file>` | Coordinate-based rectangular extract — local file or URL | n/a |
|
|
2269
2532
|
| `baker images dimensions <file\|url>` | Read width / height / aspect / format without decoding | n/a |
|
|
2270
2533
|
| `baker images download <targets>` | Remote URLs and/or library ids → local files, so the local transforms can read them | n/a (writes to disk, no upload) |
|
|
2271
2534
|
| `baker images tags` | List available image tag names (defaults + company custom tags) | n/a |
|
|
2535
|
+
| `baker images tags create\|update\|delete <name>` | Manage the company's custom tags — one pool shared by images, videos and testimonials (also under `baker videos tags` / `baker testimonials tags`) | n/a |
|
|
2272
2536
|
|
|
2273
2537
|
**Every image record carries `width` and `height`, without `--full`.** They are there to be passed straight to the component that places the image: a remote image rendered with no declared size makes the site build download the file purely to measure it, which is the only reason building a site needs the network at all — and a dropped connection there fails the entire quality-check run for a reason nothing on the page caused.
|
|
2274
2538
|
|
|
@@ -2623,7 +2887,7 @@ baker images ingest https://images.pexels.com/photos/13219418/pexels-photo-13219
|
|
|
2623
2887
|
baker images ingest https://acme.com/hero.png --source firecrawl --external-url https://acme.com/pricing --context "competitor pricing hero"
|
|
2624
2888
|
```
|
|
2625
2889
|
|
|
2626
|
-
`--source` is a closed set, validated before the request so a wrong value costs nothing: `uploaded`, `website`, `google_testimonial`, `trustpilot_testimonial`, `instagram`, `pexels`, `pixabay`, `magnific` (retired provider, kept for existing rows), `brandfetch`, `google_images`, `firecrawl`, `screenshotone`, `iconify`, `giphy`, `pinterest`, `ai_generated`, `layer_edit`. Use `website` for an asset pulled off a company's own site.
|
|
2890
|
+
`--source` is a closed set, validated before the request so a wrong value costs nothing: `uploaded`, `website`, `google_testimonial`, `trustpilot_testimonial`, `instagram`, `tiktok`, `linkedin`, `facebook`, `pexels`, `pixabay`, `magnific` (retired provider, kept for existing rows), `brandfetch`, `google_images`, `firecrawl`, `screenshotone`, `iconify`, `giphy`, `pinterest`, `ai_generated`, `layer_edit`. Use `website` for an asset pulled off a company's own site.
|
|
2627
2891
|
|
|
2628
2892
|
Returns `{ imageId, deduped, contentHash }`. When `deduped: true`, an existing library row is returned — no new bytes are stored. Max ingest size 25MB.
|
|
2629
2893
|
|
|
@@ -2901,6 +3165,8 @@ baker videos search "product demo"
|
|
|
2901
3165
|
baker videos search "tutorial" --tags explainer,ugc --limit 5
|
|
2902
3166
|
```
|
|
2903
3167
|
|
|
3168
|
+
Each hit carries `format` (e.g. `testimonial`, `trend-skit`), `tone` (`sincere` … `parody`) and `productClaims`: what the clip says about the offer. Hints name any parody result, and any clip catalogued before these fields existed.
|
|
3169
|
+
|
|
2904
3170
|
### `baker videos get <id>`
|
|
2905
3171
|
|
|
2906
3172
|
Get a single video by ID. `--full` includes the auto-generated **transcript** (Baker transcribes every upload with speech-to-text), a `transcript_segment_count`, and the titled/timestamped **scene breakdown** — so you can read what a clip says and shows without watching it. Default JSON also returns the raw `transcript` + timed `transcriptSegments`; `--full` is what surfaces them in `--output md`/`files` too.
|
|
@@ -2957,7 +3223,7 @@ Re-ingesting the same link returns the existing row with `deduped: true` instead
|
|
|
2957
3223
|
|
|
2958
3224
|
| Flag | Purpose |
|
|
2959
3225
|
|---|---|
|
|
2960
|
-
| `--source` | Provenance. Inferred from the link's host when omitted, and that inference covers
|
|
3226
|
+
| `--source` | Provenance. Inferred from the link's host when omitted, and that inference covers 21 video platforms — `youtube`, `instagram`, `tiktok`, `twitter`, `facebook`, `linkedin`, `reddit`, `vimeo`, `dailymotion`, `twitch`, `bluesky`, `bilibili`, `vk`, `ok`, `pinterest`, `tumblr`, `snapchat`, `loom`, `streamable`, `newgrounds`, `rutube` — plus `uploaded` and `url`. A link from anywhere else still ingests, recorded as `url`. |
|
|
2961
3227
|
| `--external-id` | Dedupe key — re-running the same link returns the existing video. Derived from the link automatically on the download route. |
|
|
2962
3228
|
| `--external-url` | Canonical page URL. Defaults to the input for a page URL. |
|
|
2963
3229
|
| `--download` | Skip the direct attempt and always download. |
|
|
@@ -2989,10 +3255,24 @@ Requires `yt-dlp` on `PATH` (present in the Baker Runtime). Private, age-restric
|
|
|
2989
3255
|
|
|
2990
3256
|
**yt-dlp goes stale, and that looks like a broken video.** Large platforms change their players every few weeks and an out-of-date `yt-dlp` then resolves *no* formats at all, failing with "Requested format is not available" or "Only images are available" rather than anything that names the real cause. The error classifies this case separately and says the link is probably fine. The Runtime image installs `yt-dlp` unpinned at build time (`packages/e2b-template/src/template.ts`), so it ages with the image — if platform ingests start failing across the board, rebuild the template before investigating anything else. Direct file URLs don't depend on `yt-dlp` at all and keep working regardless.
|
|
2991
3257
|
|
|
3258
|
+
### `baker videos fetch <url> --out <dir>`
|
|
3259
|
+
|
|
3260
|
+
Download a video page (YouTube, TikTok, Vimeo, …) to a local `.mp4` without adding it to the library — the download half of `ingest`, with the same proxy fallback and duration check. `--max-seconds` lowers the length limit (default 3 hours); `--timeout-ms` bounds the download. By default it keeps the best quality the site offers; `--max-height 1080` keeps a version whose shorter side is at most that when there is one (a vertical Short keeps its 1080×1920) (a 4K master can be five times the bytes). Returns `{ file, durationSeconds, egress }`. The file is named after the video (e.g. `youtube-abc123.mp4`), so several fetches can share one folder; an existing file is never overwritten — a second copy gets a `-2` suffix. Nothing else is left in the folder.
|
|
3261
|
+
|
|
3262
|
+
```bash
|
|
3263
|
+
baker videos fetch https://www.youtube.com/watch?v=abc123 --out ./clips
|
|
3264
|
+
```
|
|
3265
|
+
|
|
3266
|
+
Baker's YouTube source sync runs this same command on a disposable machine, with `--max-height 1080`, because a YouTube file URL only works from the IP that requested it.
|
|
3267
|
+
|
|
2992
3268
|
### `baker videos delete <id>`
|
|
2993
3269
|
|
|
2994
3270
|
Delete a video by ID.
|
|
2995
3271
|
|
|
3272
|
+
### `baker videos retry <id>`
|
|
3273
|
+
|
|
3274
|
+
Process a video whose status is `error` again. When the upload itself never finished it answers `status: "error"` with a `message` saying the file has to be uploaded again.
|
|
3275
|
+
|
|
2996
3276
|
### `baker videos describe <id>`
|
|
2997
3277
|
|
|
2998
3278
|
The same correction for a clip: fix the stored `name`, `description` or `tags` when the analysis read the video wrong. Only the flags you pass change, `--tags` replaces the whole set, and the search index rebuilds in the background.
|
|
@@ -3008,8 +3288,13 @@ List the available video tag names — built-in defaults plus the company's cust
|
|
|
3008
3288
|
|
|
3009
3289
|
```bash
|
|
3010
3290
|
baker videos tags
|
|
3291
|
+
baker videos tags create "summer sale" --description "Assets for the summer sale campaign"
|
|
3292
|
+
baker videos tags update summer-sale --description "Assets for the 2026 summer sale"
|
|
3293
|
+
baker videos tags delete summer-sale
|
|
3011
3294
|
```
|
|
3012
3295
|
|
|
3296
|
+
`tags create|update|delete` manage the company's custom tags, one pool shared by images, videos and testimonials. Built-in tags cannot be changed. Like every library change from a chat, they apply immediately and are listed on the chat as already applied.
|
|
3297
|
+
|
|
3013
3298
|
### `baker testimonials search <query>`
|
|
3014
3299
|
|
|
3015
3300
|
Semantic search testimonials. For landing pages, query the product/offer and USP before filtering by rating so selected quotes match what the landing sells.
|
|
@@ -3023,6 +3308,26 @@ baker testimonials search "competitor support" --source trustpilot --sentiment n
|
|
|
3023
3308
|
baker testimonials search "[product/offer] for [persona] that delivers [USP/outcome]" --rating-min 4 --language en --output md --full --limit 12
|
|
3024
3309
|
```
|
|
3025
3310
|
|
|
3311
|
+
### `baker testimonials sources`
|
|
3312
|
+
|
|
3313
|
+
The review pages testimonials are imported from — Google Maps, Trustpilot, Yelp, Tripadvisor, G2, Capterra, Amazon, App Store, Google Play. Adding or removing one applies immediately and shows on the chat as an already-applied change.
|
|
3314
|
+
|
|
3315
|
+
| Command | What it does |
|
|
3316
|
+
| --- | --- |
|
|
3317
|
+
| `baker testimonials sources add <link \| business name> [--platform <site>] [--every never\|daily\|weekly\|monthly]` | Recognise the page from its link (a business name finds a Google Maps listing) and import its reviews in the background. Idempotent. |
|
|
3318
|
+
| `baker testimonials sources list` | Every source: id, site, status, reviews imported, auto-sync. |
|
|
3319
|
+
| `baker testimonials sources sync <id>` | Import new reviews now. |
|
|
3320
|
+
| `baker testimonials sources schedule <id> --every <interval>` | Set the auto-sync cadence. |
|
|
3321
|
+
| `baker testimonials sources reset <id>` | Unstick a source whose import never finished, so it can import again. Keeps its reviews. |
|
|
3322
|
+
| `baker testimonials sources remove <id>` | Remove the source and every testimonial it imported. |
|
|
3323
|
+
|
|
3324
|
+
`baker testimonials delete <id> [--dry-run]` removes one testimonial (and the images it brought in) from the library. It applies immediately.
|
|
3325
|
+
|
|
3326
|
+
```bash
|
|
3327
|
+
baker testimonials sources add https://www.trustpilot.com/review/acme.com
|
|
3328
|
+
baker testimonials sources add "Acme Bakery Madrid"
|
|
3329
|
+
```
|
|
3330
|
+
|
|
3026
3331
|
### `baker testimonials get <id>`
|
|
3027
3332
|
|
|
3028
3333
|
Get a single testimonial by ID.
|
|
@@ -3066,7 +3371,7 @@ baker testimonials tags
|
|
|
3066
3371
|
|
|
3067
3372
|
### Avatars (`baker avatars`)
|
|
3068
3373
|
|
|
3069
|
-
Reusable AI presenters a company casts into images and video. An avatar holds one settled identity: an **identity sheet** (`sheetUrl`,
|
|
3374
|
+
Reusable AI presenters a company casts into images and video — for an actor who has to come back across ads; a person in a single ad needs no avatar. An avatar holds one settled identity: an **identity sheet** (`sheetUrl`, four candid phone photos of the same person in their own setting) plus the **subject description** (`subjectDescription`) that person is described with. Every render grounds on the sheet via `--reference` and reuses the subject description verbatim — that pairing is what keeps the same face across a whole campaign.
|
|
3070
3375
|
|
|
3071
3376
|
`status` is `generating | ready | error | archived`; only `ready` can be cast. `create` returns immediately with `generating` because the identity sheet renders in the background (~30s). `likeness` is `synthetic` (invented from a description) or `licensed` (built from photographs of a real person, which requires `--likeness-confirmed`).
|
|
3072
3377
|
|
|
@@ -3118,6 +3423,20 @@ next one that shares her name. A `--handle` you pass yourself still conflicts: t
|
|
|
3118
3423
|
decision, and moving it quietly would point `@marta` at somebody else. The returned `handle`
|
|
3119
3424
|
is the one the avatar actually got.
|
|
3120
3425
|
|
|
3426
|
+
#### `baker avatars research "<who>"`
|
|
3427
|
+
|
|
3428
|
+
Find a real, named person — a testimonial author, the company's founder, a team member — from a description, and gather their public photos into the image library. `--create` builds their avatar from those photos right away (likeness `licensed`, no face picker), or returns the avatar they already have.
|
|
3429
|
+
|
|
3430
|
+
```bash
|
|
3431
|
+
baker avatars research "Lautaro Suarez, CTO at Vibiz"
|
|
3432
|
+
baker avatars research "the CEO of Vibiz" --create
|
|
3433
|
+
baker avatars research "Lautaro Suarez" --company Vibiz --role CTO --create --handle lautaro
|
|
3434
|
+
baker avatars research "Ana Botín, presidenta del Banco Santander" --site santander.com
|
|
3435
|
+
baker avatars research "Pau Gasol" --role "basketball player" --context "Spanish"
|
|
3436
|
+
```
|
|
3437
|
+
|
|
3438
|
+
The answer's `status` is `found` (their `person` record, `photos`, a `subjectDescription` and, with `--create`, the `avatar`), `possible` (`candidates` that matched only in part, each with its `profile` link — pick one with `--profile <link>`) or `not_found` (the `searched` queries, and `possibleSites`: sites whose domain spells part of the company's name; nothing was taken). It reads LinkedIn, the company's own site and, for well-known people, Wikidata. A profile counts as the person only when both the name and the company match it, or, with no company, the name, the role and `--context` (where they are from or based, their field). `--site <domain>` is the company's own website when its domain is not its name spelled out; the client's own is used for its own people. It checks the company's avatars first: a person who already has one (the same full name, and the same company when both name one; for a role alone, the researched avatar holding it) comes back `found` with that `avatar` (`reused: true`) — nothing searched, billed or copied again; an invented face (likeness `synthetic`) or one whose build failed never counts as them. `--profile` (or `--linkedin`) skips that check. When a lookup service does not answer (a timeout, a rate limit), the command fails with `UNAVAILABLE` rather than answering `not_found`, and nothing of that run is kept, so the next try searches again. `--name`, `--role` and `--company` override the description; `--full` adds their about text, location and every search run.
|
|
3439
|
+
|
|
3121
3440
|
#### `baker avatars update <handle>`
|
|
3122
3441
|
|
|
3123
3442
|
Edit an avatar. Profile flags are merged over the stored profile, so `--persona` alone keeps the subject description intact; an empty value clears the field it names (except `--subject`, which can only be replaced). Changing `--subject` requires `--regenerate-sheet` in the same call — otherwise the sheet still shows the person described before.
|
|
@@ -3142,67 +3461,124 @@ baker avatars delete marta --archive
|
|
|
3142
3461
|
|
|
3143
3462
|
---
|
|
3144
3463
|
|
|
3464
|
+
### Media Sources (`baker media-sources`)
|
|
3465
|
+
|
|
3466
|
+
Connect a public social account so its posts fill the image and video libraries: **Instagram**, **TikTok**, **YouTube** (videos and Shorts only), **LinkedIn** company pages and **Facebook** pages — or a **Google Drive folder** the user picks from the company's Google Drive connection. Each social sync reaches back up to a year, stops paging at the first page it already has, imports only what the library doesn't have yet, keeps carousels/albums/documents together as a set (`images group` / `videos group`), and passes the post caption to the analysis. A source is named by its key, `<platform>:<handle>`.
|
|
3467
|
+
|
|
3468
|
+
Connecting and removing apply immediately and show on the chat as an already-applied change.
|
|
3469
|
+
|
|
3470
|
+
| Command | What it does |
|
|
3471
|
+
|---|---|
|
|
3472
|
+
| `baker media-sources add <profile URL \| @handle> [--platform <p>] [--every never\|daily\|weekly\|monthly]` | Look the account up (must exist and be public) and connect it; the import starts in the background. `--platform` only for a bare handle. Idempotent. |
|
|
3473
|
+
| `baker media-sources list` | Every source: key, platform, label, auto-sync, and per library `{ status, stored, pending?, failed?, error? }`. |
|
|
3474
|
+
| `baker media-sources sync <key>` | Import new posts now. |
|
|
3475
|
+
| `baker media-sources schedule <key> --every <interval>` | Set the auto-sync cadence. |
|
|
3476
|
+
| `baker media-sources reset <key>` | Unstick a source whose import never finished, so it can sync again. Keeps what it imported. |
|
|
3477
|
+
| `baker media-sources remove <key>` | Disconnect; imported media stays in the library. |
|
|
3478
|
+
|
|
3479
|
+
```bash
|
|
3480
|
+
baker media-sources add https://www.tiktok.com/@acme
|
|
3481
|
+
baker media-sources add @acme --platform instagram --every daily
|
|
3482
|
+
baker media-sources list
|
|
3483
|
+
```
|
|
3484
|
+
|
|
3485
|
+
YouTube downloads are the expensive import (a disposable machine per batch), so a YouTube sync takes the newest 25 videos it hasn't stored, skips videos over 20 minutes, and continues with older ones on the next sync.
|
|
3486
|
+
|
|
3487
|
+
A Google Drive folder is never added with `add`: the agent calls `request_connection` with `platform: "google-drive"` and `choose: "media-folder"`, and the user picks the folder in the chat card (or in Baker, Add source → Google Drive) — only the Baker team can for now. `add <drive link>` is refused with that `error.fix`; the card's result carries the new source's `mediaSourceKey`. Once connected it is managed by key like any source. Everything in it and its subfolders is imported in batches of 150 images and 20 videos (60 minutes / 5 GB at most each), new files arrive with each scheduled sync (weekly unless changed), and images are stored resized to 2560 px. Imported images from every source are resized the same way.
|
|
3488
|
+
|
|
3489
|
+
---
|
|
3490
|
+
|
|
3145
3491
|
### Winning Ads (`baker winning-ads`)
|
|
3146
3492
|
|
|
3147
3493
|
Search the **ad-dna** corpus of scored "winning" competitor ads for reference creatives to reproduce (e.g. with `baker canvas`), and manage the brands your library tracks (`follow` / `following` / `feed` / `unfollow`). Each result carries a presigned media URL (~1h TTL), the ad's DNA summary, and scores. The CLI authenticates with the normal `BAKER_API_KEY`; the Baker backend proxies the request to the ad-dna service with a server-held token — no extra credential in the sandbox.
|
|
3148
3494
|
|
|
3149
|
-
> The corpus
|
|
3495
|
+
> The corpus reads **Meta, LinkedIn, TikTok and Google Ads** (`--platform meta,linkedin,tiktok,google`). Google search text ads are `--format text`; only Google display and YouTube ads can be a design reference.
|
|
3496
|
+
|
|
3497
|
+
> Rows are compact by default. `--fields a,b` keeps only those columns in every `--output` on `search`, `feed`, `winners`, `ads`, `advertisers`, `following` and `content`; on ad rows a name only `--full` carries (`angle`, `hook_archetype`, …) comes back on its own, and a name no row carries is refused with `error.fix.validValues`.
|
|
3150
3498
|
|
|
3151
3499
|
> Backend env: the Convex deployment must have `AD_DNA_API_TOKEN` set (`npx convex env set AD_DNA_API_TOKEN …`). `AD_DNA_API_URL` is optional and defaults to `https://ads.withbaker.com`.
|
|
3152
3500
|
|
|
3153
3501
|
> Replaces the old `baker ads google library` tree, which has been removed. Competitor-by-keyword discovery still lives at `baker research advertisers`.
|
|
3154
3502
|
|
|
3503
|
+
`baker winning-ads profile <advertiserId>` returns what one brand is doing: an estimated spend in USD (a range over every ad since `since`, the last 30 days, and a minimum, split by channel in `by_platform` — a channel that publishes no delivery is marked `unpriced`; `ads_counted` says how many ads were measured, estimated from similar ads, or not counted for lack of reach), what it launched lately and which of those still run, the concepts it tested and the version it kept, its format and platform mix, top landing pages, audiences and similar brands. `--platform` narrows it, `--full` returns everything. `baker winning-ads launches [--since 7] [--advertiser a,b]` lists what the followed brands (or the named ones) launched in the last N days and which still run. Both say, for every launch, whether it was a `new` idea, another version of one still in `testing`, or a version built on a `winner` the brand had already kept; the profile also counts new ideas per week and `unique_ideas` overall.
|
|
3504
|
+
|
|
3505
|
+
`baker winning-ads landings <advertiserId>` lists every landing page the brand's ads point to, most ads first, with how many ads point there, how many still run, the platforms and when each was last used. `address_only` marks an address only Google search ads print: Google publishes no search ad's destination, so it may not be a real page and is never analysed. `--analyse` studies the top 5 pages not analysed yet (`--pages a,b` to name them): each page's headline, value propositions, offer, pricing, calls to action, proof, audience and sections in order. Analysis takes a few minutes per page and is shared by every company, so a page analysed once is ready for all. `--platform` narrows it, `--full` returns every field.
|
|
3506
|
+
|
|
3507
|
+
`baker winning-ads search-copy <advertiserId>` reads what a brand says on Google Search from its search ads: every headline with its hook (category, brand, free, price_discount, outcome, proof, new_timely, comparison, audience, location, feature, call_to_action, question), its descriptions, sitelinks and callouts. Each is `running` or `dropped` (every ad that carried it has stopped), with how many ads carried it and the longest any ran. `hooks` compares the running headlines' hooks with the dropped ones; `rival_headlines` lists headlines naming a competitor. `part_totals` counts every part of each kind; `--part <headlines|descriptions|sitelinks|callouts> --offset <n>` reads one list a page at a time (`next_offset` until null). `--running` keeps what still runs, `--limit` sets rows per list or page (default 15), `--full` adds dates and every address the ads show (Google publishes no search ad's destination, only that address).
|
|
3508
|
+
|
|
3509
|
+
`baker winning-ads refresh <advertiserId>` re-scrapes a followed brand so its newest ads land in the library. It applies immediately and runs at most once every 6 hours per brand; inside that window it answers `RATE_LIMITED` with an `error.fix` naming when the brand can be refreshed again. While a follow or the last refresh is still collecting the brand's ads, it starts nothing and says so.
|
|
3510
|
+
|
|
3155
3511
|
### `baker winning-ads search <query>`
|
|
3156
3512
|
|
|
3157
|
-
Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url
|
|
3513
|
+
Semantic search (dense recall + BM25 + rerank) → `POST /api/ad-library/winners/search`. The CLI projects each result to a **lean, decision-focused** shape so the agent's context stays small — default fields: `advertiser`, `advertiser_id`, `platform`, `format`, `relevance`, `winner_score`, `summary` (what the ad is about), `media_url`, and `visual` (what the viewer sees) + `container` (a static's layout pattern) + `mechanism` (why it works) when known; plus top-level `pool_size`, `match_confidence` (`high|medium|low`), and `below_floor_count` (matches dropped under the relevance floor). The proof signals ride along (`winner_category`, `days_active`, `active`), and so does who the ad was made for when the library knows it: `audience_temperature` (`0_cold|1_warm_engaged|2_hot_visited|3_customer`), `funnel_stage`, `advertiser_size`, and `presumes_brand_knowledge` (whether the copy only works on viewers who already know the brand, with `brand_knowledge_reason` when `true`; absent while the ad is not judged yet); `--full` adds DNA detail (`angle`, `target_persona`, `hook_archetype`, `awareness_stage`, `industry`) + `reach` and `media_kind`. By default the search draws from the brands the company follows first (`--scope followed-first`) and tops up from the shared library; every result says `source: followed|library`. `--output json` (default) returns the lean objects; `--output md` prints a table.
|
|
3158
3514
|
|
|
3159
3515
|
> `media_url` is the creative itself: for `static` it's the image, for `video` it's the video file. ad-dna stores **no separate poster** for videos, so a video result has only the video URL.
|
|
3160
3516
|
|
|
3161
|
-
> Results are already de-junked and winner-first: the corpus auto-drops measured failures (duds, thrash) and ranks `final = relevance^0.
|
|
3517
|
+
> Results are already de-junked and winner-first: the corpus auto-drops measured failures (duds, thrash) and ranks `final = relevance^0.5 × winner_score^0.5`, so a direct match beats a stronger-but-tangential ad. So `--winner-category winner` is usually unnecessary — it's a stricter filter that also removes `untested` (unproven-but-maybe-fresh) ads. Rely on the default + `--min-relevance`; only add `--winner-category` to force-exclude unproven creatives. (With a query, restricting to a single `--advertiser-id` turns the failure-exclusion and the per-advertiser cap off: you then see that brand's duds too. A search with no query is a browse, which always drops failures and `untested` ads. A company that follows one brand keeps both with `--scope followed`/`followed-first`.)
|
|
3162
3518
|
|
|
3163
3519
|
```bash
|
|
3164
3520
|
# Only LinkedIn video ads:
|
|
3165
3521
|
baker winning-ads search "B2B lead gen demo" --platform linkedin --format video --output md
|
|
3166
|
-
# Cross-platform,
|
|
3167
|
-
baker winning-ads search "fintech onboarding" --exclude-advertiser adv_ourbrand
|
|
3522
|
+
# Cross-platform, excluding our own brand (an ad we already adapted comes back last, marked already_adapted):
|
|
3523
|
+
baker winning-ads search "fintech onboarding" --exclude-advertiser adv_ourbrand --output md
|
|
3168
3524
|
# Similar-to a reference ad, recent only:
|
|
3169
3525
|
baker winning-ads search --ref-ad-id a_12345 --first-seen-after 2026-01-01T00:00:00Z --limit 5 --output md
|
|
3526
|
+
# One varied reference per concept (different brands, containers, hooks) in one call:
|
|
3527
|
+
baker winning-ads search "meal kit for busy parents" --scope followed-first --diverse --limit 5 --output md
|
|
3528
|
+
# The best proven ads in one container, from any category (no query needed):
|
|
3529
|
+
baker winning-ads search --format-pattern comparison_table --platform meta --output md
|
|
3530
|
+
# References made for strangers that don't lean on their brand being known:
|
|
3531
|
+
baker winning-ads search "delivery driver recruitment" --audience-temperature 0_cold --exclude-brand-reliant --output md
|
|
3170
3532
|
```
|
|
3171
3533
|
|
|
3172
3534
|
| Flag | Purpose |
|
|
3173
3535
|
|---|---|
|
|
3174
3536
|
| `query` (positional) | Free-text natural-language query |
|
|
3175
3537
|
| `--ref-ad-id <id>` | Find ads similar to this ad (instead of free text) |
|
|
3538
|
+
| `--scope followed-first\|followed\|all` | Which brands to draw from (default `followed-first`: the company's followed brands, topped up from the shared library; the whole library when it follows nobody) |
|
|
3539
|
+
| `--sort winner\|longest_running\|newest` | Order of a no-query browse (default `winner`); ignored when a query is given |
|
|
3176
3540
|
| `--limit <n>` | Max results 1–100 (**default 10** — shortlist size) |
|
|
3177
|
-
| `--max-per-advertiser <n>` |
|
|
3541
|
+
| `--max-per-advertiser <n>` | Unset (default): brands spread, each brand's best 3 first and the rest after the other brands, none dropped. `1`–`50`: drop a brand's ads past N (`1` = one reference per brand). `0`: rank order. Brands the query names ("HubSpot video ads") are never capped |
|
|
3178
3542
|
| `--min-relevance <0-1>` | Relevance floor; trims weak matches |
|
|
3179
|
-
| `--platform <list>` | `meta,linkedin` — pass a single value to search **only** that platform |
|
|
3543
|
+
| `--platform <list>` | `meta,linkedin,tiktok` — pass a single value to search **only** that platform |
|
|
3180
3544
|
| `--format <list>` | `video,static,carousel` |
|
|
3181
3545
|
| `--winner-category <list>` | `winner,scaled_winner,evergreen,rising,untested,dud,…` (default: all) |
|
|
3182
3546
|
| `--awareness <list>` | `unaware,problem_aware,solution_aware,product_aware,most_aware` |
|
|
3547
|
+
| `--audience-temperature <list>` | Who the ad was made for: `0_cold,1_warm_engaged,2_hot_visited,3_customer` |
|
|
3548
|
+
| `--funnel-stage <list>` | `TOF,MOF,BOF` |
|
|
3549
|
+
| `--advertiser-size <list>` | `solo,smb,mid,enterprise` |
|
|
3550
|
+
| `--exclude-brand-reliant` | Drop ads whose copy only works if the viewer already knows the brand (ads not judged yet are kept) |
|
|
3183
3551
|
| `--advertiser-id <list>` | **Restrict to** these advertiser ids (browse one brand's winners) |
|
|
3184
|
-
| `--exclude-advertiser <list>` | **Drop** these advertiser ids
|
|
3185
|
-
| `--country <list>`
|
|
3186
|
-
| `--
|
|
3552
|
+
| `--exclude-advertiser <list>` | **Drop** these advertiser ids, usually your own brand |
|
|
3553
|
+
| `--country <list>` | ISO country codes: ads known to run there, plus ads with no country data (every Meta ad) in that country's main language |
|
|
3554
|
+
| `--language <list>` | ISO 639-1 codes (`es,en`) for the language of the ad's copy and voiceover |
|
|
3555
|
+
| `--first-seen-after` / `--first-seen-before` | ISO date (`2026-01-01`, read as the start of that day in UTC) or datetime bounds (recency) |
|
|
3187
3556
|
| `--hook-archetype <list>` | Narrow to ads on a proven hook **seed** (from `winning-ads hooks`) |
|
|
3557
|
+
| `--format-pattern <list>` | Narrow to statics in these containers (from `winning-ads formats`, e.g. `comparison_table`) |
|
|
3558
|
+
| `--diverse` | One varied page (different brands, containers, hooks) instead of rank order; ignores paging |
|
|
3559
|
+
| `--all-kinds` | Also return Google search text, Shopping and app ads. By default only designed pictures and videos come back; `--format text` (or a query asking for search ads) returns search ads alone |
|
|
3188
3560
|
| `--output json\|md\|files` | Output format (default json) |
|
|
3561
|
+
| `--fields <list>` | Only these row fields (e.g. `advertiser,container,mechanism`); a `--full`-only field turns `--full` on, an unknown one is refused with `error.fix.validValues` |
|
|
3189
3562
|
| `--full` | Include DNA detail + longevity |
|
|
3190
3563
|
|
|
3564
|
+
A value outside a closed list (`--platform`, `--format`, `--winner-category`, `--awareness`, `--audience-temperature`, `--funnel-stage`, `--advertiser-size`, `--format-pattern`), a non-numeric `--limit`/`--max-per-advertiser`/`--min-relevance`, or a bound that is not a date is refused before the search runs; a refused list value comes with `error.fix.validValues`. References this company already adapted (`already_adapted`) never take a fresh reference's place: `--limit N` returns up to N fresh ones first. The search waits up to 120 s (`followed-first` can run two library searches). A library that does not answer in time is `UNAVAILABLE`, with an `error.fix` to retry once narrower. An empty answer carries a hint naming the one filter to loosen.
|
|
3565
|
+
|
|
3191
3566
|
Reading the scores: **`relevance`** (0–1) = match of the creative to your query; **`winner_score`** = how proven the ad is in-market. Pick references that are both relevant *and* proven.
|
|
3192
3567
|
|
|
3193
3568
|
### `baker winning-ads hooks|mechanisms|formats`
|
|
3194
3569
|
|
|
3195
|
-
Read the
|
|
3196
|
-
mechanism / static-format patterns that
|
|
3197
|
-
|
|
3198
|
-
|
|
3570
|
+
Read the pattern rollup of the ad-dna corpus → `GET /api/ad-library/seed-catalog` — the hook /
|
|
3571
|
+
mechanism / static-format patterns that win **inside brands**, among the creatives of the segment you pass
|
|
3572
|
+
(`--platform`, `--awareness`, `--industry`; an unset one means any), keyed on the closed seeds/enums the
|
|
3573
|
+
extractor emits (no clustering). Ground a creative in a **proven** pattern for the exact segment instead of
|
|
3574
|
+
guessing. The rollup is recomputed daily.
|
|
3199
3575
|
|
|
3200
3576
|
```bash
|
|
3201
3577
|
# Top winning hook seeds for problem-aware SaaS on Meta:
|
|
3202
|
-
baker winning-ads hooks --platform meta --awareness problem_aware --industry
|
|
3578
|
+
baker winning-ads hooks --platform meta --awareness problem_aware --industry "Software & SaaS" --output md
|
|
3203
3579
|
# Then narrow a reference search to that proven hook:
|
|
3204
3580
|
baker winning-ads search "onboarding demo" --hook-archetype callout --output md
|
|
3205
|
-
# Also: mechanisms (
|
|
3581
|
+
# Also: mechanisms (how the product claims to work, not the ad's device), formats (static ad layouts):
|
|
3206
3582
|
baker winning-ads mechanisms --platform linkedin --output md
|
|
3207
3583
|
baker winning-ads formats --awareness solution_aware --output md
|
|
3208
3584
|
```
|
|
@@ -3211,12 +3587,37 @@ baker winning-ads formats --awareness solution_aware --output md
|
|
|
3211
3587
|
|---|---|
|
|
3212
3588
|
| `--platform <p>` | Segment on a single platform |
|
|
3213
3589
|
| `--awareness <stage>` | Segment on a single awareness stage |
|
|
3214
|
-
| `--industry <seed>` | Segment on a single industry seed |
|
|
3590
|
+
| `--industry <seed>` | Segment on a single industry seed from the extractor vocabulary (`seedVocabularies.yaml`), any case; a term naming exactly one seed resolves to it (`saas` → `Software & SaaS`), an ambiguous or unknown one is refused with the valid list |
|
|
3215
3591
|
| `--limit <n>` | Max keys 1–100 (default 20) |
|
|
3216
3592
|
| `--full` | Include the per-key segment breakdown |
|
|
3217
3593
|
|
|
3218
|
-
Each key returns `count`, `mean_winner_score
|
|
3219
|
-
|
|
3594
|
+
Each key returns `lift`, `brands_for`, `brands_against`, `count`, `mean_winner_score` and `winner_share`. The
|
|
3595
|
+
ranking is `lift`: how much more often a brand's creatives using the pattern kept running 60 days than that
|
|
3596
|
+
brand's others, one vote per brand, 0 until five brands have a verdict. `brands_for` / `brands_against` count
|
|
3597
|
+
the brands it won and lost inside. A common pattern is not a winning one: `count` is how often it is used.
|
|
3598
|
+
|
|
3599
|
+
### `baker winning-ads fit <adId>`
|
|
3600
|
+
|
|
3601
|
+
Before building on a reference: why the ad wins, read against **this** company. One billed vision call, cached per ad
|
|
3602
|
+
under `canvas/.cache/fit/<platform>-<adId>.json` (asking again is free).
|
|
3603
|
+
|
|
3604
|
+
```bash
|
|
3605
|
+
baker winning-ads fit a_12345 --platform meta
|
|
3606
|
+
```
|
|
3607
|
+
|
|
3608
|
+
Returns `why_it_wins` in five layers (`disguise`, `voice`, `trigger`, `attention`, `logic`), the `load_bearing`
|
|
3609
|
+
elements that carry them (each with what it `needs`, whether the company has it: `have` | `adapt` | `missing`, and
|
|
3610
|
+
`ours`, what replaces it), a `verdict`: `use`, `adapt` or `reject`, and what made the ad a winner, so what an ad built
|
|
3611
|
+
on it carries (`carry`: `whole_ad`, `layout`, `hook` or `angle`, with `carry_why`; renders see the reference's picture
|
|
3612
|
+
only at `whole_ad` or `layout`). The company's Knowledge, reviews and market, and the library's evidence that the ad won
|
|
3613
|
+
(its winner category, angle, hook and composition), ground the answer. `baker canvas scaffold-static-ad --source-ad-id <adId> --source-platform <platform>` reads the cached
|
|
3614
|
+
answer, writes it into `prompt.json` as `concept`, and refuses a `reject` with `concept_does_not_fit` before anything is
|
|
3615
|
+
billed; the hint names the platform the fit was asked on, so a TikTok or LinkedIn reference is read from its own library.
|
|
3616
|
+
|
|
3617
|
+
| Flag | Default | Description |
|
|
3618
|
+
|---|---|---|
|
|
3619
|
+
| `--platform <meta\|linkedin\|tiktok\|google>` | `meta` | Which platform the id is on. The fit reads a still picture: a video ad (YouTube included) is refused (`VIDEO_REFERENCE`, read it with `content`), and on Google a search text ad, Shopping listing or app-install ad is refused (`NOT_A_DESIGN_REFERENCE`) |
|
|
3620
|
+
| `--full` | off | The whole answer, with the reasoning per element |
|
|
3220
3621
|
|
|
3221
3622
|
### `baker winning-ads advertisers <brand>`
|
|
3222
3623
|
|
|
@@ -3227,17 +3628,31 @@ baker winning-ads advertisers "Acme" --output md # find our own adverti
|
|
|
3227
3628
|
baker winning-ads advertisers "Deel" --platform meta --output md
|
|
3228
3629
|
```
|
|
3229
3630
|
|
|
3230
|
-
### `baker winning-ads follow "<
|
|
3631
|
+
### `baker winning-ads follow "<website | brand | link>"`
|
|
3231
3632
|
|
|
3232
|
-
Add **all** of a brand's ads to your library — every platform (Meta
|
|
3633
|
+
Add **all** of a brand's ads to your library — every platform (Meta, LinkedIn, TikTok, Google Ads), every account it runs there, and every country → `POST /api/ad-library/follow`. Paste whatever you have: a website is best — we resolve every Facebook page and LinkedIn company the site links (a brand often runs one per market), its TikTok account, and every Google Ads entity running its ads (never a reseller) — but a brand name or any profile / ad-library link works too. Discovery is never region-scoped. `--platform` is accepted and ignored. The result `status` is one of:
|
|
3233
3634
|
|
|
3234
3635
|
- `following` — the brand is already in the corpus; you're now subscribed (no wait).
|
|
3235
3636
|
- `added` — a new brand was queued for ingestion; its ads appear as discovery completes (a `hints[]` note flags this).
|
|
3236
|
-
- `ambiguous` — the input mapped to multiple brands; pick one from `candidates` and re-run with a more specific
|
|
3637
|
+
- `ambiguous` — the input mapped to multiple brands; pick one from `candidates` and re-run with a more specific website/link.
|
|
3638
|
+
|
|
3639
|
+
`suggestions` lists accounts only *named* like the brand (say, another country's Facebook page its site doesn't link). They are not tracked; `hints[]` carries a ready `add-accounts` command for them.
|
|
3237
3640
|
|
|
3238
3641
|
```bash
|
|
3239
|
-
baker winning-ads follow "deel.com"
|
|
3240
|
-
baker winning-ads follow "https://www.linkedin.com/company/acme" --
|
|
3642
|
+
baker winning-ads follow "deel.com"
|
|
3643
|
+
baker winning-ads follow "https://www.linkedin.com/company/acme" --label "Acme (competitor)"
|
|
3644
|
+
```
|
|
3645
|
+
|
|
3646
|
+
### `baker winning-ads discover "<website | brand | link>"`
|
|
3647
|
+
|
|
3648
|
+
The `accounts` and `suggestions` a follow would find, without following anything → `POST /api/ad-library/discover`. Paid lookups. Each account is compact — `platform`, `name`, `followers`, `ads_url` (its public ad library, to check it is the brand's) and `input` (what to paste into `add-accounts`); `--full` adds every other field the lookup read.
|
|
3649
|
+
|
|
3650
|
+
### `baker winning-ads add-accounts <advertiser> "<link,link,...>"`
|
|
3651
|
+
|
|
3652
|
+
Track more accounts under a brand you already follow — another market's Facebook page, a second LinkedIn company, another Google Ads entity → `POST /api/ad-library/accounts/add`. Each input is a profile or ad-library link, a Google advertiser id (`AR…`) or a website; up to 20. The response lists `added`, `already_tracked`, and `failed` (with a reason per input); one input failing never blocks the rest. An account that already belongs to a different brand in the library is refused — follow it as its own brand.
|
|
3653
|
+
|
|
3654
|
+
```bash
|
|
3655
|
+
baker winning-ads add-accounts adv_123 "https://www.facebook.com/barkibu.it,AR01614014350098432001"
|
|
3241
3656
|
```
|
|
3242
3657
|
|
|
3243
3658
|
### `baker winning-ads following`
|
|
@@ -3259,7 +3674,7 @@ Winners across **every** brand you follow, in one library → `GET /api/ad-libra
|
|
|
3259
3674
|
Response `data`: `winners`, `following_count` (how many brands you follow), `advertiser_ids` (the ids actually queried = follows ∩ requested), and — when relevant — `hints` (e.g. you follow nobody yet, or a requested id isn't in your follows and was skipped). In `--output md`/`files` the table is the payload and the hints are written to **stderr** so they still surface without corrupting the output.
|
|
3260
3675
|
|
|
3261
3676
|
```bash
|
|
3262
|
-
baker winning-ads feed --
|
|
3677
|
+
baker winning-ads feed --output md
|
|
3263
3678
|
baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
|
|
3264
3679
|
```
|
|
3265
3680
|
|
|
@@ -3267,26 +3682,36 @@ baker winning-ads feed --advertiser adv_123,adv_456 --platform meta --output md
|
|
|
3267
3682
|
|---|---|
|
|
3268
3683
|
| `--advertiser <ids>` | Trim to a comma-separated subset of your followed advertiser ids (ids you don't follow are skipped and reported) |
|
|
3269
3684
|
| `--platform meta\|linkedin` | Filter to a single platform |
|
|
3270
|
-
| `--per-advertiser <n>` |
|
|
3685
|
+
| `--per-advertiser <n>` | Unset (default): brands spread, each brand's best 3 first and the rest after, none dropped. `1`–`50`: keep at most N per brand. `0`: rank order |
|
|
3271
3686
|
| `--limit <n>` | Total winners across all follows 1–200 (default 40) |
|
|
3272
3687
|
| `--winner-category <list>` | Restrict to specific winner categories |
|
|
3273
3688
|
| `--format <list>` | Restrict to specific formats (e.g. `static,video`) |
|
|
3689
|
+
| `--all-kinds` | Also return Google search text, Shopping and app ads (default: designed pictures and videos only; `--format text` returns search ads alone) |
|
|
3274
3690
|
|
|
3275
3691
|
### `baker winning-ads winners <advertiser>`
|
|
3276
3692
|
|
|
3277
|
-
Top winning ads for one advertiser id → `GET /api/ad-library/advertiser-winners` (use `feed` for all your follows at once). Same lean winner cards as `search` (add `--full` for DNA + longevity). Supports `--top N` and `--platform
|
|
3693
|
+
Top winning ads for one advertiser id → `GET /api/ad-library/advertiser-winners` (use `feed` for all your follows at once). Same lean winner cards as `search` (add `--full` for DNA + longevity). Supports `--top N`, `--platform meta|linkedin|tiktok|google` (omit it for the top winners across every platform) and `--per-platform`, which adds `by_platform`: each platform's own top winners, since scores don't compare across platforms.
|
|
3694
|
+
|
|
3695
|
+
```bash
|
|
3696
|
+
baker winning-ads winners adv_123 --per-platform --top 5 --output md
|
|
3697
|
+
```
|
|
3698
|
+
|
|
3699
|
+
### `baker winning-ads ads <advertiser>`
|
|
3700
|
+
|
|
3701
|
+
Every ad one advertiser has run → `GET /api/ad-library/advertiser-ads`, one row per distinct ad, the same cards as `winners`. `--platform meta|linkedin|tiktok|google` narrows it to one platform (omit it for all together), `--format video,static,carousel,text` to those formats (`text` is a Google search ad), `--running all|running|stopped` to what still runs or what stopped. Rows come strongest first. Pages with `--limit` (1-100, default 24) and `--offset`: the response carries `total` and `next_offset` (`null` on the last page).
|
|
3278
3702
|
|
|
3279
3703
|
```bash
|
|
3280
|
-
baker winning-ads
|
|
3704
|
+
baker winning-ads ads adv_123 --platform google --running running --output md
|
|
3705
|
+
baker winning-ads ads adv_123 --offset 24
|
|
3281
3706
|
```
|
|
3282
3707
|
|
|
3283
3708
|
### `baker winning-ads content <ad-id>`
|
|
3284
3709
|
|
|
3285
|
-
Read what's **inside** one ad → `GET /api/ad-library/ad-content`. `search`/`winners`/`feed` return a lean shortlist; this opens a single `ad_id` so you can understand a reference before reproducing it. For a **video** it returns the spoken `transcript`, the `on_screen_text` overlays, and the ad `copy` (`primary_text`/`headline`/`cta`
|
|
3710
|
+
Read what's **inside** one ad → `GET /api/ad-library/ad-content`. `search`/`winners`/`feed` return a lean shortlist; this opens a single `ad_id` so you can understand a reference before reproducing it. For a **video** it returns the spoken `transcript`, the `on_screen_text` overlays, and the ad `copy` (`primary_text`/`headline`/`cta`, plus `landing_url`; a Google ad also carries `google_ad_type`). A **static** or **carousel** ad returns the text drawn into the image, each line led by its job and card (`card 2 · headline: …`); a static also returns its `format_pattern`. Add `--full` for how the ad is built (composition, colours, CTA drawn in the image, markers such as price tags or badges), how it persuades (awareness stage, mechanism, pain points, desires, value props, proof, objections handled), what it offers (offer type, pricing, entry price, guarantee, urgency, bonus), and for video its speech, pacing, soundtrack and each overlay's role + position (e.g. `GLOBAL PAYROLL @0.5s [hook, top]`). Fields the ad does not carry are left out. `--platform meta|linkedin|tiktok|google` selects which platform the id is on (default `meta`).
|
|
3286
3711
|
|
|
3287
3712
|
```bash
|
|
3288
3713
|
baker winning-ads content adg_123 # transcript + on-screen text + copy
|
|
3289
|
-
baker winning-ads content adg_123 --platform meta --full # +
|
|
3714
|
+
baker winning-ads content adg_123 --platform meta --full # + build, persuasion, offer, video speech/pacing/soundtrack
|
|
3290
3715
|
baker winning-ads content adg_123 --full --output md
|
|
3291
3716
|
```
|
|
3292
3717
|
|
|
@@ -3298,23 +3723,21 @@ Stop following a brand by advertiser id → `POST /api/ad-library/unfollow`. Ret
|
|
|
3298
3723
|
baker winning-ads unfollow adv_123
|
|
3299
3724
|
```
|
|
3300
3725
|
|
|
3301
|
-
### `baker
|
|
3726
|
+
### Decisions about text (`baker decide`)
|
|
3302
3727
|
|
|
3303
|
-
|
|
3728
|
+
Typed questions about text, answered by Jev (a decision model) with a probability: a yes/no (`noul`), one option of your set (`choice`) or a level on a scale (`score`). Read-only, text only, never generates prose. Works with a `bk_`, `bkr_` or `bkv_` key, so a published Report can call it.
|
|
3304
3729
|
|
|
3305
3730
|
```bash
|
|
3306
|
-
baker
|
|
3731
|
+
baker decide --questions '{"b2b":{"type":"noul","question":"Is `message` written for a business buyer?"}}' --state '{"message":"…"}'
|
|
3732
|
+
baker decide --questions questions.json --items ads.json --state '{"advertiser":"Notion"}' [--full]
|
|
3307
3733
|
```
|
|
3308
3734
|
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
```
|
|
3316
|
-
|
|
3317
|
-
---
|
|
3735
|
+
- `--questions` (required): `{ "<name>": { "type": "noul", "question" } | { "type": "choice", "question", "options": { "<key>": "<meaning>" } } | { "type": "score", "question", "levels": [lowest, …, highest] } }`.
|
|
3736
|
+
- `--items`: `[{ "id", "text": "…" | { "<field>": "…" } }]`. Every question is asked about every item, which reaches Jev as `` `item` `` inside its own question. Up to 500 items of 4,000 characters, 2,000 questions per call; packed into requests of up to 200 questions sent at once.
|
|
3737
|
+
- `--state`: shared context, or the text itself when there are no items (inline JSON, a file, `-`, or plain text).
|
|
3738
|
+
- Each flag that takes JSON accepts it inline, as a file path, or `-` for stdin.
|
|
3739
|
+
- Output: `{ model, answers | items: [{ id, answers | null }], unanswered, requests, costUsd }`. A noul is `{ noul }`; a choice `{ choice, probability, confidence }`; a score `{ score, level, confidence }`. `--full` adds every option's `probabilities`.
|
|
3740
|
+
- When the model does not answer, the error carries `fix.action: "degrade"`: carry on without the answers rather than abort.
|
|
3318
3741
|
|
|
3319
3742
|
### Scheduled agents (`baker scheduled-agents`)
|
|
3320
3743
|
|
|
@@ -3335,6 +3758,7 @@ baker scheduled-agents create --name "Celebrate the first sale" --description "O
|
|
|
3335
3758
|
baker scheduled-agents update <id-or-temp_agent_id> --enabled false
|
|
3336
3759
|
baker scheduled-agents update <id-or-temp_agent_id> --run-at "2026-09-02T07:00:00Z" --prompt "Use the launch checklist"
|
|
3337
3760
|
baker scheduled-agents update <id-or-temp_agent_id> --trigger experiment.ended
|
|
3761
|
+
baker scheduled-agents update <id-or-temp_agent_id> --slack "#marketing" # each run opens a thread there; also here | me | off
|
|
3338
3762
|
baker scheduled-agents delete <id-or-temp_agent_id>
|
|
3339
3763
|
baker scheduled-agents unstage <id-or-temp_agent_id> # everything this chat staged on it
|
|
3340
3764
|
baker scheduled-agents unstage <id-or-temp_agent_id> --index 2 # one staged change only
|
|
@@ -3344,8 +3768,11 @@ baker scheduled-agents run <id>
|
|
|
3344
3768
|
baker scheduled-agents templates --list # every recipe this company can run
|
|
3345
3769
|
baker scheduled-agents templates --category measurement # one group only
|
|
3346
3770
|
baker scheduled-agents create --template baker-ads --name "Weekly ad review" --description "..." --cron "0 8 * * MON"
|
|
3771
|
+
baker scheduled-agents create --template baker-store-out-of-stock # --name and --description default to the recipe's own
|
|
3347
3772
|
```
|
|
3348
3773
|
|
|
3774
|
+
With `--template`, `--name`, `--description` and `--prompt` are optional: the recipe's name, line and brief are used, and `--prompt` is appended to the brief. An item on a stock event whose name or line says it acts automatically is refused — every run's ad changes wait for review.
|
|
3775
|
+
|
|
3349
3776
|
Triggers:
|
|
3350
3777
|
|
|
3351
3778
|
| `--trigger` | Fires when | Narrow it with |
|
|
@@ -3411,13 +3838,14 @@ baker scheduled-agents templates --delete weekly-posthog-check
|
|
|
3411
3838
|
|
|
3412
3839
|
Read the client's marketing/analytics tags (Meta pixel, ChatGPT Ads pixel, GA4, Google Ads, GTM, Clarity, Hotjar, PostHog, …) — production tags overlaid with the changes staged in this chat.
|
|
3413
3840
|
|
|
3414
|
-
**The CLI
|
|
3841
|
+
**The CLI never creates or edits a tag.** Every tag change (create, edit, delete) goes through the `request_tag_input` tool (`baker_ui` MCP server): the agent proposes one or more changes — each becomes a tab in one blocking approval form — pre-filling the non-secret fields it knows; the user reviews, edits, fills secret fields, and approves or skips each tab. Approved changes stage on the chat and apply when the chat is published; discarding the chat drops them.
|
|
3415
3842
|
|
|
3416
3843
|
`BAKER_CHAT_ID` must be set.
|
|
3417
3844
|
|
|
3418
3845
|
```bash
|
|
3419
3846
|
baker tags list # effective view: production + staged, full readable config + secret status
|
|
3420
3847
|
baker tags draft # review the staged changes awaiting publish
|
|
3848
|
+
baker tags unstage tag_temp_abc123 # take back one change this chat staged (or --all)
|
|
3421
3849
|
```
|
|
3422
3850
|
|
|
3423
3851
|
Notes:
|
|
@@ -3427,7 +3855,7 @@ Notes:
|
|
|
3427
3855
|
- **Secrets never travel through this CLI or the chat.** Secret fields (`accessToken`, `apiSecret`, `authorizationToken`, `apiKey`, `conversionToken`, `oauthProviderId`) are entered only in the dashboard's secure tag form and flow straight into the staged draft; tool responses only ever name which secret fields are set/pending.
|
|
3428
3856
|
- A `chatgptAds` tag takes a Pixel ID. Its optional `apiKey` is the Conversions API key and enables the server copy of page and Form events; the key stays out of the CLI and published page.
|
|
3429
3857
|
- Staged creates get a server-generated `tag_temp_*` ref (returned in the tool result and printed by `list`). Use it (or a real tag id) as flow side-effect `tagIds` — the published tag keeps resolving under the temp ref.
|
|
3430
|
-
-
|
|
3858
|
+
- **`unstage` is the one write.** It takes back a change this chat staged — the same as discarding the row in the change review — by the ref `draft` prints, or `--all`. It carries no config and no secret, never touches a live tag, and refuses a ref nothing is staged under. Taking back a staged create drops the secrets entered for it with it.
|
|
3431
3859
|
- Single-instance types (`code`, `posthog`, `datafast`) reject a second instance against the chat's effective view.
|
|
3432
3860
|
- Conflicts at publish (tag deleted in the dashboard, config invalid) skip the op with a recorded reason — they never block the publish.
|
|
3433
3861
|
|
|
@@ -3437,11 +3865,11 @@ Read and shape this workspace's Forms (flows). Operates on the local `_data.json
|
|
|
3437
3865
|
|
|
3438
3866
|
For ChatGPT Ads conversions, add a `chatgptAds` side effect to the Form step, choose a supported event name, and use `baker flows map` to map `advancedMatching` and optional `eventParams` (`value` and `currency`).
|
|
3439
3867
|
|
|
3440
|
-
**A Form has two halves, split by a trust boundary.** Its *structure* — steps, conditions, side effects, field mappings — is yours, and the write subcommands below edit exactly that. Its *confidential* half — connection secrets, OAuth accounts, third-party field definitions (HubSpot, Calendly, HighLevel, SavvyCal) — is
|
|
3868
|
+
**A Form has two halves, split by a trust boundary.** Its *structure* — steps, conditions, side effects, field mappings — is yours, and the write subcommands below edit exactly that. Its *confidential* half — connection secrets, OAuth accounts, third-party field definitions (HubSpot, Calendly, HighLevel, SavvyCal) — is set with `baker flows connect` whenever the agent knows the answer (an email's recipients and subject always; a connected platform's resource; a webhook with no token), and otherwise through the `request_flow_input` tool (`baker_ui` MCP server): the agent authors the surrounding structure and references it by `nodeId`/`sideEffectId`, and the user enters secrets, connects accounts, and picks resources in the dashboard. Those values are written (encrypted, where a secret) into the Form on the chat's branch and go live when the chat is published. Nothing any of these commands does touches a live system.
|
|
3441
3869
|
|
|
3442
3870
|
```bash
|
|
3443
3871
|
baker flows list # every Form + how many confidential fields still need setup
|
|
3444
|
-
baker flows show contact # confidential fields
|
|
3872
|
+
baker flows show contact # confidential fields, where each sealed destination sends (never a credential), and drift
|
|
3445
3873
|
baker flows show contact --full # include the full flow tree (secret values redacted)
|
|
3446
3874
|
|
|
3447
3875
|
baker flows schema # both vocabularies + the customForm field types
|
|
@@ -3450,6 +3878,16 @@ baker flows schema side-effect googleSpreadsheet
|
|
|
3450
3878
|
|
|
3451
3879
|
baker flows add-node contact --type text --name "Thanks"
|
|
3452
3880
|
baker flows add-side-effect contact --node <nodeId> --type googleSpreadsheet # triggerId comes from the step
|
|
3881
|
+
baker flows connect contact --side-effect se-hubspotForm-1 --hubspot-form <formId> # attach the connected HubSpot account + form, no card
|
|
3882
|
+
baker flows connect contact --all --hubspot-form <formId> # every hubspotForm step still unwired, one call
|
|
3883
|
+
baker flows options calendly # what a step/destination can use: calendly, savvycal, highlevel, highlevelForm, googleSpreadsheet
|
|
3884
|
+
baker flows connect contact --node book --resource <id> # fill a Calendly/SavvyCal/GoHighLevel step yourself
|
|
3885
|
+
baker flows connect contact --side-effect se-gsheet-1 --resource <spreadsheetId> # a sheet Baker can reach (--tab to pick the tab)
|
|
3886
|
+
baker flows connect contact --side-effect se-gsheet-1 --new-sheet "Contact leads" --columns "Name,Email" # create one and wire it
|
|
3887
|
+
baker flows connect contact --side-effect se-pipedrive-1 # Pipedrive / GoHighLevel contact: the account is the destination
|
|
3888
|
+
baker flows connect contact --side-effect se-email-1 --email "ana@x.com, luis@x.com" --subject "New lead — Contact" # an email destination: the agent states it, never a card
|
|
3889
|
+
baker flows connect contact --side-effect se-httpWebhook-1 --url https://hooks.example.com/lead # a webhook that needs no token
|
|
3890
|
+
baker flows connect contact --all --type googleSpreadsheet --same-as madrid:se-gsheet-1 # copy another Form's destination
|
|
3453
3891
|
baker flows map contact --from map.json --write # a whole mapping at once — past ~3 fields, use this
|
|
3454
3892
|
baker flows map contact --map A=nombre --write # one or two fields
|
|
3455
3893
|
baker flows map contact --hubspot-form <guid> --from map.json --write # HubSpot: field types read off the form
|
|
@@ -3461,7 +3899,10 @@ baker flows normalize contact --write # repairs a nulled optional;
|
|
|
3461
3899
|
Notes:
|
|
3462
3900
|
|
|
3463
3901
|
- **`schema` takes the kind as an argument because `hubspot` is both** — a node type (embeds a form on the page) and a side-effect type (fires a behavioural event). They are different things with one name.
|
|
3464
|
-
-
|
|
3902
|
+
- **`connect` wires a HubSpot form without asking the user.** When HubSpot is connected and the agent knows which form is meant, `baker flows connect <slug> --hubspot-form <formId> [--side-effect <id>]` attaches the account and the form to a `hubspotForm` side effect (`--all`: every one in the Form still missing it, never repointing one already wired) — the same thing the chat card writes, sealed by Baker so the config never passes through the agent. It refuses a form with spam prevention (captcha) on, and returns `destination.formGuid` for `map --hubspot-form`. The card (`request_flow_input`) stays for an unconnected account or an ambiguous choice.
|
|
3903
|
+
- **`options` + `connect` do the same for every other connected platform.** `baker flows options <kind>` lists what the connected account holds (Calendly events, SavvyCal links, GoHighLevel calendars and forms, the Google spreadsheets Baker can reach); `connect --node|--side-effect <id> --resource <id>` writes exactly what the card would. Pipedrive and a GoHighLevel contact take no resource. `--new-sheet` creates a spreadsheet in the connected Google account (Google only lets Baker reach sheets it created or the user chose), `--same-as <slug>:<id>` copies another Form's wired destination, `--all --type <kind>` fills every unwired one of a kind. Nothing connected answers `NOT_CONNECTED` with the `request_connection` fix; several accounts connected need `--account <id>`.
|
|
3904
|
+
- **`--email` / `--url` replace a destination whole, so they leave alone what only the user can set.** `--all --email` passes over an email that replies to the visitor's own answer (and says so in its hints); naming one is refused. A webhook that sends a credential keeps its address until the user re-enters both through `request_flow_input`. Unreadable settings are also left alone by default. For one unreadable email, `--replace-unreadable --email <complete list> --subject <text>` replaces the old setup when the user intends replacement, without revealing it. Never use this to add to an unknown recipient list; it cannot override `--all`, a known visitor reply, or a webhook. `flows show` reads back only destinations sealed for this company.
|
|
3905
|
+
- **Configure the destination before you map.** `columnMapping`/`fieldMapping` describe a *specific* destination — sheet columns, a portal's field names — that only `connect` or `request_flow_input` can set, and both return it. A mapping written first is valid JSON that ships and drops every submission; `verify` blocks the half-wired result.
|
|
3465
3906
|
- **`map` reads the record shape off the side effect's type.** One property name, four record shapes: a Sheet is addressed by column letter, HubSpot by property name, Pipedrive and GoHighLevel by field key, Crmble by field id. That is why there is no `--key`, and why `--entity` and `--custom`/`--standard` are required rather than defaulted.
|
|
3466
3907
|
- **A mapping is usually eight to fifteen fields, so `--from` is the main door.** It takes `{ "map": { "<destField>": "<value>" } }` from a file or stdin; `--map` flags are for one or two. Both produce identical output.
|
|
3467
3908
|
- **Most CRM fields are not fed by a form field.** The value grammar is a bare form field (`nombre`), `field:Step.name` when two steps share a name, `tracking:utm_source`, `global:Name`, `code:exposed_value`, a `"literal"`, or any of those joined with `+`. Tracking names are validated against the engine's own `TRACKING_FIELD_IDS`, read out of the workspace rather than copied — attribution columns are the most common CRM mapping there is, and they were unexpressible before.
|
|
@@ -3527,6 +3968,8 @@ baker actions create --name "Build hero" --priority high --tags landing,creative
|
|
|
3527
3968
|
# fuller fan-out hint: re-read the draft and merge actions that touch the same system into one.
|
|
3528
3969
|
# If no --tags are passed, create emits a MISSING --tags hint — the action is invisible to the backlog's tag filter.
|
|
3529
3970
|
# If no --priority is passed, create emits a MISSING --priority hint — the action ranks as normal/medium in the do-first order.
|
|
3971
|
+
# If the name or description holds a shell name such as /bin/bash or bash where a `$0` stood (double quotes let the
|
|
3972
|
+
# shell expand `$` amounts), create leads with a hint to fix it with `baker actions update <tempId>` in single quotes.
|
|
3530
3973
|
baker actions update <action-id> --name "Better name" --tags google-ads,strategy # --tags REPLACES the set; '' clears
|
|
3531
3974
|
baker actions update <action-id> --priority urgent # urgent|high|medium|low; 'none' clears
|
|
3532
3975
|
baker actions update <tempId> --description "..." # a tempId from `create` in THIS chat edits the still-staged task in place
|
|
@@ -3535,10 +3978,17 @@ baker actions complete <action-id-or-tempId> --note "What was done" # stages
|
|
|
3535
3978
|
# --note is REQUIRED (≥20 chars): what changed, where, and the outcome. It is the durable, client-facing
|
|
3536
3979
|
# record surfaced by `baker actions log`. A missing/too-short note is rejected with a VALIDATION_ERROR.
|
|
3537
3980
|
baker actions discard <action-id> --reason "obsolete"
|
|
3981
|
+
baker actions reopen <action-id> # stages — a completed Task goes back to the backlog on publish
|
|
3982
|
+
|
|
3983
|
+
# Assigning — staged, applies on publish. --to is a userId or email from `members`, or `none`
|
|
3984
|
+
baker actions members # teammates a Task can be handed to (freelancers hidden unless you are a superadmin)
|
|
3985
|
+
baker actions assign <id-or-tempId> --to maria@empresa.com
|
|
3986
|
+
baker actions assign <id> --to none # unassign
|
|
3538
3987
|
|
|
3539
3988
|
# Tags — taxonomy is built-in defaults + company custom tags; --tags is STRICT (unknown slugs are rejected)
|
|
3540
3989
|
baker actions tags list # available tags (defaults + company custom); errors → stderr (inline-safe)
|
|
3541
3990
|
baker actions tags create --slug pmax --description "Performance Max work" # mint a custom tag
|
|
3991
|
+
baker actions tags update --slug pmax --description "Performance Max campaigns" # what it means; applies at once
|
|
3542
3992
|
baker actions tags delete --slug pmax # remove a custom tag (default tags can't be deleted)
|
|
3543
3993
|
|
|
3544
3994
|
baker actions link --blocker <id-or-tempId> --blocked <id-or-tempId>
|
|
@@ -3550,7 +4000,7 @@ baker actions draft --output md # one row per staged op (json defaul
|
|
|
3550
4000
|
baker actions draft --output md --fields kind,name,ref,tags # pick the columns
|
|
3551
4001
|
baker actions draft --output md --full # add each op's payload (description, note, reason, blocker)
|
|
3552
4002
|
baker actions draft remove temp_hero # drop a staged create (cascades its complete/link ops)
|
|
3553
|
-
baker actions draft remove <id> --op complete # drop a staged op on a real action (--op update|complete|discard)
|
|
4003
|
+
baker actions draft remove <id> --op complete # drop a staged op on a real action (--op update|complete|discard|assign|reopen)
|
|
3554
4004
|
baker actions draft clear # drop everything staged in this chat
|
|
3555
4005
|
```
|
|
3556
4006
|
|
|
@@ -3588,26 +4038,33 @@ Permissions enforced server-side:
|
|
|
3588
4038
|
- Only `claim`/`release` mutate live state.
|
|
3589
4039
|
- `claim` returns action details (`id`, `name`, `description`) plus `recommendedSkills` — a fast model reads the action's name/description/tags against the local `.claude/skills/` catalog and returns the skills (`{ name, reason }`) most worth loading for the work, echoed as `/skill — reason` lines in `hints`. It is best-effort: if the catalog is absent or the model call fails, the claim still succeeds and falls back to a generic review hint. Every claim also carries a routing hint: actions often span several surfaces, so load every skill that owns part of the work and read the tool doc (`__tooling__/docs/tools/baker/<family>.md`) for each baker CLI family the work needs.
|
|
3590
4040
|
- `complete` and `unlink` require the action to be claimed by the current chat — otherwise the API returns `FORBIDDEN` with a hint to claim first. Completing is the record that the work is done, so it stays claim-gated.
|
|
4041
|
+
- `assign` and `reopen` do not require a claim either, and are blocked the same way. `assign --to` is resolved only among the teammates the key's person can see (`members`), so a hidden freelancer cannot be named by id or email; restaging replaces the earlier assignment. At publish an assignee who lost access to the company is skipped as a conflict, never a failed publish.
|
|
4042
|
+
- `reopen` refuses a Task that is not completed, at stage time and again at publish.
|
|
4043
|
+
- `tags update` changes only what a custom tag means; the slug stays, because every Task carrying it stores it.
|
|
3591
4044
|
- `update` and `discard` do NOT require a claim — a chat may edit or retire an action it never claimed. They are blocked only when another chat currently holds the claim (`CONFLICT` with that chat's title).
|
|
3592
4045
|
- Claiming an action already claimed by another chat returns `CONFLICT` with the other chat's title — pick a different action.
|
|
3593
4046
|
- All staged ops are reverted automatically when the chat is discarded.
|
|
3594
4047
|
|
|
3595
4048
|
---
|
|
3596
4049
|
|
|
3597
|
-
### `baker mcp connected | list | add | remove`
|
|
4050
|
+
### `baker mcp connected | catalog | list | add | remove | verify | disable`
|
|
3598
4051
|
|
|
3599
4052
|
See everything a chat can reach — managed integrations, custom servers, and connected platforms — and manage **custom MCP servers** by pointing the agent at any remote HTTPS MCP endpoint. Registered tools appear as `mcp__<name>__*` on the next chat message.
|
|
3600
4053
|
|
|
3601
4054
|
```bash
|
|
3602
4055
|
baker mcp connected
|
|
4056
|
+
baker mcp catalog --search notion
|
|
3603
4057
|
baker mcp list
|
|
3604
4058
|
baker mcp add --name weather --url https://mcp.example.com/mcp
|
|
3605
4059
|
baker mcp add --name acme --url https://acme.dev/mcp --header "Authorization: Bearer sk-…"
|
|
3606
4060
|
baker mcp add --name mine --url https://my.dev/mcp --scope user
|
|
3607
4061
|
baker mcp remove --name weather
|
|
4062
|
+
baker mcp verify --name weather # check it now (the dashboard's "Check now")
|
|
4063
|
+
baker mcp disable --name weather # stop its tools loading; only a person turns it back on (org-wide servers: a person only)
|
|
3608
4064
|
```
|
|
3609
4065
|
|
|
3610
4066
|
- `connected` returns three lists. `managed` and `custom` are MCP tools the agent calls directly; `platforms` are the accounts the company signed in to (HubSpot, Google Ads, GA4, Search Console, Tag Manager, LinkedIn Ads), each with its login and the resources it may read. A platform has **no** `mcp__*` tools — it is read through its own command family — so it is listed here to keep the agent from concluding a working connection is missing.
|
|
4067
|
+
- `catalog --search <name> [--limit 1-20]` searches the managed apps a user can connect when a vendor has no MCP server of its own. Each app reports its slug (`app`), how the user connects it (`sign-in` or `key`; apps that need no sign-in cannot be connected and are left out) — never a managed copy of one of Baker's own connections: those come back under `platforms`, to ask for with `{ platform }` and whether this chat already reaches it (`connected`).
|
|
3611
4068
|
- `--url` must be the **full MCP endpoint** (commonly ending in `/mcp`), HTTPS and public; loopback / private / cloud-metadata hosts are rejected. A bare domain frequently accepts the OAuth sign-in and then serves no tools, so every address is handshake-checked after it changes: `connected` and `list` report `usable: false` plus a `problem` (and a `suggestedAddress` when the server advertises a better endpoint) for a server that isn't serving tools, or `lastCheck: failed` when the last check only hit an outage — that one may work anyway, and a later turn re-checks it automatically. Repointing a server at a different host clears its saved sign-in: a token belongs to the host that issued it, so the server has to be connected again.
|
|
3612
4069
|
- Scope is two independent choices — audience (just you / everyone) × level (this company / whole org):
|
|
3613
4070
|
- `--scope company` (default) — everyone in this company.
|
|
@@ -3654,7 +4111,40 @@ baker history list --category ads --full # ad writes with raw metadata
|
|
|
3654
4111
|
|
|
3655
4112
|
---
|
|
3656
4113
|
|
|
3657
|
-
### `baker
|
|
4114
|
+
### `baker credentials list | put | delete`
|
|
4115
|
+
|
|
4116
|
+
Credentials are a company's stored API keys, tokens and passwords. The CLI never returns a value: each credential has a placeholder, `{{credential:NAME}}`, that a request inside a Baker Session can carry in any header, URL or body. The Session's HTTPS proxy fills in the value only for the hosts the credential is bound to, and puts the placeholder back in responses.
|
|
4117
|
+
|
|
4118
|
+
```bash
|
|
4119
|
+
baker credentials list # name, placeholder, allowedHosts, label
|
|
4120
|
+
baker credentials list --full # + source, lastUsedAt
|
|
4121
|
+
printf %s "$KEY" | baker credentials put stripe-key --host api.stripe.com --label "Stripe key"
|
|
4122
|
+
baker credentials delete stripe-key
|
|
4123
|
+
```
|
|
4124
|
+
|
|
4125
|
+
- `put` reads the value from **stdin only** — a `--value` flag or a positional value is refused, because argv is visible to other processes. One trailing newline is trimmed; empty stdin is refused. `--host` is repeatable (or comma-separated) and accepts `*.example.com` wildcards.
|
|
4126
|
+
- `put` and `delete` act only on credentials the agent created. Credentials a person added are managed in the dashboard (Settings → Credentials).
|
|
4127
|
+
- The company must have credentials switched on; otherwise every command answers `FORBIDDEN`.
|
|
4128
|
+
|
|
4129
|
+
---
|
|
4130
|
+
|
|
4131
|
+
### `baker setup-links create | list`
|
|
4132
|
+
|
|
4133
|
+
A setup link lets whoever holds the company's accounts connect them without a Baker login. `create` makes one at once and returns its `url` (valid 30 days); the link is also listed on the chat as already made. Revoke links in the dashboard (Settings → Setup links).
|
|
4134
|
+
|
|
4135
|
+
```bash
|
|
4136
|
+
baker setup-links create --connect google-ads,meta --tags meta # sign in to accounts, add tracking tags
|
|
4137
|
+
baker setup-links create --keys chatgpt-ads --domains shop.acme.com # paste a key, set a domain's DNS
|
|
4138
|
+
baker setup-links create --apps slack --mcp crm=https://mcp.example.com/sse
|
|
4139
|
+
baker setup-links list # active links: asks, progress, url, expiresAt
|
|
4140
|
+
```
|
|
4141
|
+
|
|
4142
|
+
- Every flag is repeatable or comma-separated; at least one is required. Unknown tag, connection or key-account names are refused with the valid list. A `--domains` hostname must already be one of the company's domains.
|
|
4143
|
+
- `list` returns what each link asks for in the same words `create` takes. A read-only key sees no `url`.
|
|
4144
|
+
|
|
4145
|
+
---
|
|
4146
|
+
|
|
4147
|
+
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot companies search` · `baker hubspot deals list | properties | fields` · `baker hubspot meetings list | view`
|
|
3658
4148
|
|
|
3659
4149
|
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there, the leads those forms received, and the workflows, deal pipelines, and contact records a lead runs through afterwards. Fetched from HubSpot live on every call; nothing is cached and nothing is ever written back.
|
|
3660
4150
|
|
|
@@ -3676,6 +4166,8 @@ baker hubspot pipelines list # where a lead lands, and the st
|
|
|
3676
4166
|
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
3677
4167
|
baker hubspot contacts lookup lead@example.com # was this one lead picked up?
|
|
3678
4168
|
baker hubspot contacts properties # what a HubSpot-read source can watch, with each value
|
|
4169
|
+
baker hubspot companies search --domains acme.com,globex.io --with-deals # CRM accounts by domain, with their deals
|
|
4170
|
+
baker hubspot deals list --pipeline default --modified-since 2026-01-01 # deals: amount, stage, closed won
|
|
3679
4171
|
baker hubspot meetings list # every calendar + whether booking redirects
|
|
3680
4172
|
baker hubspot meetings view <slug> # booking-form fields for one calendar
|
|
3681
4173
|
baker hubspot meetings view <slug> --as-node # the form.external blob for a flow node
|
|
@@ -3686,7 +4178,9 @@ baker hubspot meetings view <slug> --as-node # the form.external blob for a f
|
|
|
3686
4178
|
**`forms submissions`:** positional `<formId>`; `--days <1-365>` (default 30) sets the window; `--full` adds each submission's field values. The default response is counts and dates only — `total`, `inWindow`, `lastSubmittedAt`, and per-day counts — because submissions carry the lead's own contact details. `truncated: true` means the page budget ran out before the window was covered, so `inWindow` is a floor rather than an exact count.
|
|
3687
4179
|
**`workflows list` flags:** `--search <text>`, `--enabled-only`. Disabled workflows are listed too — a form wired to one that is off looks connected and does nothing.
|
|
3688
4180
|
**`workflows view`:** positional `<workflowId>` (the id from `workflows list`, and the one in HubSpot's own URL — the command translates it to HubSpot's separate internal flow id for you). Returns the enrolment criteria and whether it re-enrols (`null` = HubSpot does not report it for that criteria type, so it is unknown rather than off), plus every step with the property it writes. On a `LIST_BRANCH` step, branches come back in **evaluation order** (`order: 1` wins over `order: 2`) — a broad branch above a narrower one silently swallows the records the narrow one was written for, and HubSpot reports nothing. A `STATIC_BRANCH` matches on a value rather than top down, so its `order` is display only. `steps` is **not** in execution order; follow `nextActionId`. `--full` adds the raw payload, the only place the branch filter criteria (which property is compared to which value) can be read.
|
|
3689
|
-
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot.
|
|
4181
|
+
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot, each with `isClosed` and `isWon` (closed with probability 1).
|
|
4182
|
+
**`companies search` flags:** `--domains <a.com,b.com>` (up to 100; a URL or `www.` is reduced to the bare domain), `--names <"Acme, Inc.;Globex">` (semicolon-separated), `--with-deals`, `--limit <1-500>` (default 100), `--output json|csv|jsonl|md`. Rows: `{ id, name, domain, linkedinPage, deals? }`; `notFound` lists the searched domains no CRM company carries; `truncated: true` means more matched than `--limit`.
|
|
4183
|
+
**`deals list` flags:** `--pipeline <id,id>`, `--modified-since <YYYY-MM-DD>`, `--limit <1-1000>` (default 200), `--after <cursor>`, `--output json|csv|jsonl|md`. Rows, oldest change first: `{ id, name, amount, currency, pipelineId, stageId, stageLabel, isClosed, isWon, createdAt, closedAt, modifiedAt, companyIds }` — `amount: null` is none entered. `next` is the `--after` for the following page; `restartFrom` appears when HubSpot's 10,000-result search ceiling is reached (read on with `--modified-since <restartFrom>`); `companiesReadable: false` means the connection cannot read companies, so no deal carries `companyIds`.
|
|
3690
4184
|
**`contacts summary` flags:** `--days <1-365>` (default 30) over contact creation date. Returns counts only — total contacts, how many have an owner, and the lifecycle-stage split — never a name or an email. `truncated: true` means the page budget ran out, so the counts are floors.
|
|
3691
4185
|
**`contacts lookup`:** positional `<email>`; returns that contact's lifecycle stage, owner, the account (company) it was filed under, and associated deals with their stage. `dealsReadable: false` / `companyReadable: false` mean the connection does not cover deals or accounts, so an empty `deals` or a null `company` is unknown rather than absent. `found: false` means no contact with that email — the lead never reached the CRM, which is a finding rather than an error.
|
|
3692
4186
|
**`contacts properties`:** no flags; returns every contact property with a closed list of values — `{ name, label, groupName, options: [{ value, label }] }` — the ones a HubSpot-read source can watch. Names are HubSpot's internal ones; hand them to `baker analytics sources --create-hubspot --property <name> --value <value>` as printed. No personal data.
|
|
@@ -3718,6 +4212,133 @@ No write surface against HubSpot itself — a form's fields, redirect, or consen
|
|
|
3718
4212
|
|
|
3719
4213
|
---
|
|
3720
4214
|
|
|
4215
|
+
### Shopify sign-up offers
|
|
4216
|
+
|
|
4217
|
+
`baker shopify signup-offers` lists active, ended and this chat's staged offers, permissions, codes given and recent refused sign-ups. Stage an offer with `baker shopify change signup-offer set welcome-10 --name "Welcome" --percent 10 --valid-days 14 --max-codes 1000`; use `--amount` instead of `--percent` for a fixed amount, `--products` or `--collections` for eligibility, and `--minimum`, `--ends`, `--prefix` or `--combines` for its terms. `--json` / `--file` also accept the terms. `baker shopify change signup-offer end welcome-10` stops giving new codes after Publish; existing codes keep their terms. Both changes require the user's approval.
|
|
4218
|
+
|
|
4219
|
+
Forms use the `shopifyCustomer` side effect: map its `customer` fields with `baker flows map`, name an optional offer handle and show the result with `<ShopifySignupCode form="newsletter" />`. Email and SMS consent each require a separate optional, visible, initially unticked checkbox. Every lead receives at most one code for an offer; Shopify shows one discount per lead.
|
|
4220
|
+
|
|
4221
|
+
### `baker shopify store | products | product | collections | stock | sales | buyers | buyer | countries | best-sellers | cohorts | segments | promotions | stock-holds`
|
|
4222
|
+
|
|
4223
|
+
Read-only view of the company's **connected Shopify store**, from **Baker's own copy** of its catalog and the store's own orders — never a live call to Shopify. Baker keeps the copy current from the store (a change usually shows within about a minute) and checks the whole catalog daily; product photos are already in the image library.
|
|
4224
|
+
|
|
4225
|
+
```bash
|
|
4226
|
+
baker shopify store # name, currency, home country, counts, prices by country, the Products page link
|
|
4227
|
+
baker shopify products --search "snowboard" # what the store sells: price, sale price, stock, where it can be bought
|
|
4228
|
+
baker shopify products --stock out_of_stock # --stock in_stock|partly_out|out_of_stock, --collection <handle|id>
|
|
4229
|
+
baker shopify products --limit 10 --cursor <c> # page with meta.next_cursor (25 by default, up to 100)
|
|
4230
|
+
baker shopify product <id|handle|link> # one product: options with buyUrl, prices by country, stock, photos, videos
|
|
4231
|
+
baker shopify product <handle> --content # its whole details: description, custom fields, subscriptions, translations
|
|
4232
|
+
baker shopify product <handle> --field "Size chart" # one custom field, whole
|
|
4233
|
+
baker shopify product <handle> --quantity 2 # buy links that put 2 of the option in the cart
|
|
4234
|
+
baker shopify product <id> --out-of-stock # only the options that are out of stock (page with --variants-cursor)
|
|
4235
|
+
baker shopify collections --search "summer" # collections, with the handle --collection takes (--full: descriptions, images)
|
|
4236
|
+
baker shopify stock --limit 20 # every product with every option out, and every product with some out
|
|
4237
|
+
baker shopify sales --days 7 --by campaign # what sold and which campaigns brought it (--by day|product|campaign|landing)
|
|
4238
|
+
baker shopify sales --all --by weekday # when buyers order, in the store's time (--by hour|month|month_of_year)
|
|
4239
|
+
baker shopify buyers --days 90 --by channel # who buys: new and returning, repeat rate, segments, value per acquisition
|
|
4240
|
+
baker shopify buyer 1042 # one numbered customer: profile, every order, what they bought
|
|
4241
|
+
baker shopify countries --country DE # where buyers are: by country, a country's regions (--region for cities)
|
|
4242
|
+
baker shopify best-sellers --view repeat # what sells (--view products|variants|trend|together|repeat|momentum)
|
|
4243
|
+
baker shopify segments # best, loyal, at risk, new, one-time, with this store's own rules
|
|
4244
|
+
baker shopify cohorts --months 12 # buyers by first-order month: who came back, spend per buyer
|
|
4245
|
+
baker shopify promotions <id|handle|link> # what advertises a product: ads (via page, buy link, landing), landings, Shopping
|
|
4246
|
+
baker shopify stock-holds --product <id> # ads Baker paused because the product ran out: what to resume, and what to tell the user
|
|
4247
|
+
```
|
|
4248
|
+
|
|
4249
|
+
**`meta.store`** is on every answer: `connection` (`connected` · `needs_reconnect` · `disconnected`), `catalog` (`starting` · `importing` · `ready` · `failed`, with `importProgress` while importing), `unreadable` (products Baker couldn't read; `unreadableCapped` past 100), `checksFailing` (Baker couldn't check the store for changes lately), `storefront` (`online_store`, or `own` for a store that sells through its own site) and `lastActivity` (when Baker last heard from the store — not a promise the copy is current). `hints[]` turn each of these into what to tell the user.
|
|
4250
|
+
**`store`:** `--full` adds the admin link and every country. `canChange` (`products`, `publishing`, `discounts`, `redirects`, `marketing`) says which kinds of store change the store allows Baker to stage; a `false` one is allowed by the user in Shopify. `capped: true` means a count stopped at its ceiling — `outOfStock` and `partlyOut` at 100, `collections` at 500 — so that count reads "that many or more"; a hint says which.
|
|
4251
|
+
**`products`:** `--search <words>`, `--collection <handle|id>`, `--stock`, `--limit <1-100>`, `--cursor`; `--full` adds vendor, type, option count, photo link, visibility and status. Every price, here and in `product` (`price`, `compareAt`, each option's price, `pricesByCountry`), is one string with its currency beside each amount: `"USD 785.95"`, `"USD 9.95 – USD 49.95"`. A row's `availability` is absent when the product is on the store, `link_only` when it is buyable only through its link, `not_for_sale` (with `status`) when it can't be bought on the online store.
|
|
4252
|
+
**`product`:** positional `<ref>` — the numeric id, the handle, a store product link (`…/products/<handle>`, even an all-digit handle), a Baker Products link, a Shopify admin product link (`admin.shopify.com/store/<shop>/products/<n>`), or `gid://shopify/Product/<n>`. Returns options (50 per page, `--variants-cursor` for more; 250 with `--full`), `outOptions` (up to 50, `outOptionsMore` for the rest), `pricesByCountry` when the store sells at more than one price, `photos[]` (`imageId` + a Baker-hosted `url`, never a Shopify link), `url` (the product's page in Baker) and `storeUrl` (the product's page on the store — never a buy button's link). `--full` adds the whole description, SEO, category, the admin link and each option's SKU, barcode, feed id and prices by country. `NOT_FOUND` carries a hint to search instead.
|
|
4253
|
+
|
|
4254
|
+
**Details and buy links (`product`):** every option carries `buyUrl` — its buy link for ads, emails and messages (a landing places `<ShopifyProduct product="…" />` instead, which shows the options, price and stock and hands the shopper to the store), following the store's own setting (`buyDestination`: `checkout`, `store_cart` or `product_page`) — with `buyUrlKind`, `unavailable` when it can't be bought, and `notes` for what a link can't do (`password_protected`, `subscription_only`, `subscription_optional`, `own_storefront`, `details_pending`). `--quantity <1-99>` sets how many it adds. `videos[]` lists the product's videos in the library (`videoId`, `playbackId`); `contentSummary` names what else Baker keeps. `--content` adds `content`: the formatted description, every custom field by its name (long values previewed, `truncated: true`), the rating, subscriptions, bundle, YouTube/Vimeo and 3D links, translations, and `unreadable` for content the store owner hasn't let Baker read yet. `--field <name|namespace.key>` returns one field whole. `--full` adds `--content`, all three links per option and each option's weight, unit price, shipping and tax. `store` adds `buyDestination`, `passwordProtected`, `languages`, `policies` (`--full`: their text, and the store's brand).
|
|
4255
|
+
**`collections`:** `--search <words>`; each row has `handle`, Shopify's product count and, on a store with an online store, `onStore`.
|
|
4256
|
+
**`stock`:** `--collection`, `--limit <1-200>` (default 50 per list), `--full` (SKUs and Baker links). Options that keep selling at zero or don't track stock are not out of stock.
|
|
4257
|
+
**`sales`:** `--days <1-395>` (default 30), `--by day|product|campaign|landing` (default `day`), `--limit <1-100>`, `--full` (each row's source, medium, campaign, click platform, landing or product id). `data.totals` (`orders`, `sales`, `added`, `refunded`, `net`) in `data.currency`; `data.rows[]` with a `label` and, by campaign or landing, a `credit` (`campaign` · `order` · `landing` · `unattributed` · `before_baker` · `other`), adding up to the totals. A sale counts when it is paid, at what was received; by day and product each fact counts when it happened, by campaign and landing each order paid in the period carries what was added and refunded on it since. `meta.salesReporting` (`meta`, `google`: `baker` · `shopify_channel` · `undecided`) says who reports the sales to the ad platforms; `meta.history` is `importing` while past orders still come in.
|
|
4258
|
+
|
|
4259
|
+
**`sales --by weekday|hour|month|month_of_year`:** orders placed in the period by when they were placed, in the store's timezone (`data.window.timezone`), from the store's whole order history — `--days` up to 3650 or `--all` (only these breakdowns take them).
|
|
4260
|
+
|
|
4261
|
+
**Buyers, countries and best sellers** (`buyers`, `buyer`, `countries`, `best-sellers`, `cohorts`, `segments`) read the store's whole order history as Baker keeps it. Every answer carries `meta.window` (local `from`/`to`, `days` or `"all"`, the store's `timezone`), `meta.history` (`state`: `importing` · `done` · `failed` · `none`; `reach`: `all` or `60_days`; the first day held) and `meta.basis` (`all_orders`, or `last_60_days` when the store lets Baker read only its last 60 days — then a buyer whose earlier orders are older counts as new). The period is orders **placed** in it, with what was refunded on them since. A buyer is a numbered customer (`Customer #1042`) with a country, region and city — never a name or an email; guest orders count in money, never as buyers. Shared flags: `--days <1-3650>` (default 365) or `--all`, `--country <ISO2>`, `--full`.
|
|
4262
|
+
|
|
4263
|
+
- **`buyers`:** `data.summary` (buyers, new and returning, guest orders, orders, net, average order, new/returning orders and net, repeat rate, median days to a second order), `data.segments`, `data.thresholds`, `data.buyers[]` (`--sort net|orders|recent|first`, `--limit`, `--offset`, `--segment best|loyal|at_risk|new|one_time`, `--channel <key>`), and with `--by campaign|channel` `data.acquisition.rows[]` (buyers whose first order is in the period, first-order net, lifetime net per buyer, average orders, repeat rate). Baker keeps no ad spend.
|
|
4264
|
+
- **`buyer <number>`:** `1042`, `#1042` or `Customer #1042`. A buyer erased by their store or merged into another answers `NOT_FOUND` "This buyer is no longer in Baker."
|
|
4265
|
+
- **`countries`:** `data.total`, `data.rows[]` (orders, buyers, new buyers, net, average order, share, growth against the period before, the currency paid in), `data.unknown` (no readable address) and `data.other` (past `--limit`), adding up to the total; `--country` for its regions, `--region` for a region's cities, `--product <id|handle>` for one product's orders and money.
|
|
4266
|
+
- **`best-sellers`:** `--view products` (default; units ordered beside refunded units, orders, buyers, revenue, refunds, refund rate, growth; `--sort units|revenue|orders`) · `variants` (with `--product`) · `trend` · `together` · `repeat` · `momentum`.
|
|
4267
|
+
- **`cohorts`:** `--months <3-36>` (default 12); retention at months 1, 2, 3, 6, 12 and net per buyer by month 12 (`--full`: every month).
|
|
4268
|
+
- **`segments`:** each segment's buyers, share, net, average orders, days since the last order, top countries and first products, and its rule in this store's numbers.
|
|
4269
|
+
|
|
4270
|
+
**`promotions`:** positional `<ref>` like `product`. Reads the product, scans this workspace's `src/pages/*` (import graph, ≤ 300 pages, files ≤ 256 KB) for its buy link, `/products/<handle>`, an option id or a `<ShopifyProduct>` / `<ShopifyBuyButton>` naming it (evidence `buy_button`), then `POST /api/shopify/promotions` with the linked landings. `data.ads[]` (`platform`, `level` ad|asset_group, `name`, `campaign`, `status` active|paused, `via` product_page|buy_link|landing), `data.landings[]` (`published`, `via` links_to_product|visitors_went_on), `data.shopping[]`, `data.catalog_ads`, `data.checked` (`not_checked` names a platform it could not read). A read made outside a Session says so in `hints`: only a read that carried a Session and named an ad lets “Restart ads when a product is back” later stage turning a pause from it back on, for review. `--full` adds ids, accounts, campaign / ad group / ad set ids, the Google `resource`, the URL and `data.remembered`.
|
|
4271
|
+
|
|
4272
|
+
**`stock-holds`:** `--product <ref>`, `--all` (resolved holds too). Compact: `data.resume[]` — the one command to stage per ad Baker may turn back on now (`{ ad, command }`); `data.tell_user[]` — one sentence per ad, and per piece of out-of-stock work still waiting for review, written for the marketer; `data.pages[]` — landings the out-of-stock run changed, with the `baker chats diff … --surface landings` read of those changes. With `--product` it speaks only of that product's ads, including the ones Baker did not pause when it ran out. `--full` adds the codes behind each sentence: `holds[]` with each `verdict` read live from the ad platform, `pending_sessions[]`, `recently_closed[]`, `not_paused[]`, `pages_changed[]`.
|
|
4273
|
+
|
|
4274
|
+
**Refusals:** `NO_SHOPIFY_CONNECTION` — no store is connected; `error.fix.action` is `request_connection` (`{ platform: "shopify" }`). `SHOPIFY_NOT_AVAILABLE` — Shopify can't be connected on this Baker yet; `error.fix.action` is `continue_without`. Both exit 1.
|
|
4275
|
+
|
|
4276
|
+
---
|
|
4277
|
+
|
|
4278
|
+
### `baker shopify change | draft | changes | undo | tracking`
|
|
4279
|
+
|
|
4280
|
+
**Changes to the connected store, staged on the Session (`BAKER_CHAT_ID`)** and made only when the user publishes it — reviewed first in the chat's Store tab. Nothing here calls Shopify to change anything mid-Session; every applied change can be put back by `undo`, through another publish.
|
|
4281
|
+
|
|
4282
|
+
```bash
|
|
4283
|
+
baker shopify change product update the-snowboard --title "The Snowboard" --seo-title "…" --add-tags summer
|
|
4284
|
+
baker shopify change variant update 456 --sku SNOW-L --barcode 4006381333931 --weight 1.5 --weight-unit kg # kg, g, lb or oz
|
|
4285
|
+
baker shopify change variant update 456 --price 18.95 --sale-price none # money: the user says yes first
|
|
4286
|
+
baker shopify change field set product:the-snowboard custom.care --value "Hand wash"
|
|
4287
|
+
baker shopify change collection create --title "Summer boards" --products a,b # shown on the online store unless --hidden
|
|
4288
|
+
baker shopify change collection hide summer-boards # also update, show, delete, add, remove
|
|
4289
|
+
baker shopify change discount create --code SPRING10 --percent 10 --ends <iso> # money
|
|
4290
|
+
baker shopify change discount stop SPRING10 # money
|
|
4291
|
+
baker shopify change discount dates SPRING10 --starts <iso> --ends never # money
|
|
4292
|
+
baker shopify change redirect create --from /old --to /new
|
|
4293
|
+
baker shopify change redirect remove /old
|
|
4294
|
+
baker shopify change campaign add --platform google-ads --account … --campaign … # show a Baker campaign in Shopify Marketing
|
|
4295
|
+
baker shopify change measurement reconnect # the fix `tracking audit` names
|
|
4296
|
+
baker shopify change product update --json '[{"kind":"shopify.product.update","target":"<handle>","payload":{"addTags":["summer"]}}, …]'
|
|
4297
|
+
baker shopify draft list # also show <ref>, remove <ref>, clear
|
|
4298
|
+
baker shopify draft amend <ref> --patch '{…}'
|
|
4299
|
+
baker shopify changes --limit 20 # applied changes, each with its changeId
|
|
4300
|
+
baker shopify undo <changeId>… # stage putting them back, as one batch
|
|
4301
|
+
baker shopify tracking audit # read-only: is the store's tracking right?
|
|
4302
|
+
```
|
|
4303
|
+
|
|
4304
|
+
**Money.** A price, a sale price or a discount code changes money: the first call exits 1 with `NEEDS_USER_APPROVAL` and `error.fix.askUserQuestion` — the exact question to ask with AskUserQuestion. When the user answers "Yes, make these changes", run the same command again; a declined one is refused, still carrying the question in case the user asks for it again later. `amend` of a price asks again, and publish refuses any money change whose lines are not the approved ones.
|
|
4305
|
+
|
|
4306
|
+
**Stage-time checks.** Each change is read against the live store when it is staged (the "before" the Store tab shows), checked, and refused with a reason the agent can act on — a collection whose undo could not rebuild it (rules, an image, custom fields, a discount or menu pointing at it), descriptions with scripts or frames, unknown products. Publish re-reads each target first: a change made in Shopify since staging is not overwritten; the op is marked failed with why. Up to 100 changes per call, 250 per Session.
|
|
4307
|
+
|
|
4308
|
+
**Undo.** `changes` lists what Baker applied (`changeId`, `undoable`, why not). `undo` stages the inverses in one batch, read from the store as it was before each change; a money undo asks once for all its lines. A deleted collection comes back with the same address, content and products (as a new collection).
|
|
4309
|
+
|
|
4310
|
+
**`tracking audit`:** reads only. `data.checks[]` (`measurement`, `theme_switch`, `visitor_on_orders`, `sales_reporting`, `consent`, `storefront`) each with a `status` and a `fix` that is `staged` (a command to stage), `merchant` (something the user does in Shopify) or `decision` (a choice only the user makes).
|
|
4311
|
+
|
|
4312
|
+
**Refusals:** `SHOPIFY_PERMISSION_NEEDED` — the store has not allowed this kind of change yet; `error.fix.action` is `request_connection` (`{ platform: "shopify", reason }`) and the user allows it in Shopify. `NEEDS_USER_APPROVAL` — the money answer: `fix.action` `ask_user` with the question, or `continue_without` after a no. A payload with a key its kind doesn't take (`--json`, `draft amend --patch`) is refused naming the keys it takes. All exit 1.
|
|
4313
|
+
|
|
4314
|
+
---
|
|
4315
|
+
|
|
4316
|
+
### `baker accounts influenced | engagement | warm | company | campaigns | crm`
|
|
4317
|
+
|
|
4318
|
+
Account insights: which companies the LinkedIn ads are warming, and which CRM deals they influenced. Reads Baker's nightly copy of LinkedIn and the CRM, so it is fast and covers up to 24 months; nothing is changed anywhere. Every company has it once a LinkedIn ad account is chosen; before the first nightly read the reports are empty — read live with `baker ads linkedin companies`. `--start`/`--end` must be real calendar days within 24 calendar months; a bad flag or an unsupported `--output` exits 1 with `INVALID_ARGS`.
|
|
4319
|
+
|
|
4320
|
+
```bash
|
|
4321
|
+
baker accounts influenced # influenced pipeline, last 12 calendar months
|
|
4322
|
+
baker accounts influenced --basis revenue --start 2026-01-01 --end 2026-06-30
|
|
4323
|
+
baker accounts influenced --full # every deal in the period, influenced or not
|
|
4324
|
+
baker accounts engagement --last-months 3 --limit 50 # companies reached, New/Rising, CRM match and deals
|
|
4325
|
+
baker accounts engagement --last-months 3 --limit 50 --page 2 # the next 50 (pageInfo.total counts them all)
|
|
4326
|
+
baker accounts engagement --campaign-id 123456 --output csv
|
|
4327
|
+
baker accounts warm --output csv > warm.csv # companyName,domain,organizationUrn
|
|
4328
|
+
baker accounts company acme.com # or a LinkedIn company id / urn:li:organization:N
|
|
4329
|
+
baker accounts campaigns --last-months 6 --output md # per campaign: reached, engaged, influenced pipeline (--basis revenue)
|
|
4330
|
+
baker accounts influenced --model awareness --threshold 10 --output csv # another rule, this call only
|
|
4331
|
+
baker accounts warm --min 0 # every New/Rising company, below the threshold too
|
|
4332
|
+
baker accounts crm status # deals from HubSpot or sent by the client; what arrived, linked Google Sheets
|
|
4333
|
+
baker accounts crm sheet # create and link the deals sheet in the client's Google Drive
|
|
4334
|
+
baker accounts crm check --file deals.csv # judge a deals or company CSV offline, nothing sent
|
|
4335
|
+
baker accounts crm contract # the send-your-CRM contract, as Markdown for a developer
|
|
4336
|
+
```
|
|
4337
|
+
|
|
4338
|
+
A deal is **influenced** when its company reached the company's influence rule — by default 3+ LinkedIn ad engagements (Awareness: 7+ impressions; Intent: 3+ clicks) — in the 12 months up to and including the month the deal was created (`--basis pipeline`, deals not won) or closed won (`--basis revenue`). It is a labelled rule, not credit: campaign rows overlap, and money is reported per currency, never summed across currencies. Deals whose company match is only a guess come back under `lowConfidence` and stay out of `summary` until the match is confirmed in Measure → Accounts. Without the deals set up there — HubSpot's pipelines and won/lost stages, or Another CRM — `influenced` and `campaigns` exit 1 with `ACCOUNTS_NOT_SET_UP`. A CRM Baker cannot read sends its companies and deals to `POST /v1/accounts/crm` (a `crm_ingest` key, `X-Baker-Mode: test` to rehearse, up to 500 records per request, records naming a person refused), is uploaded as a CSV in Measure → Accounts, or lives in a Google Sheet linked there that Baker reads every night (a row deleted from the sheet leaves Baker). A file is a deals export or a company list — a company needs only its website or LinkedIn page; `crm check` runs the same rules offline and `crm contract` prints the whole contract. The CLI never sends deals or links a sheet of the client's; `crm sheet` creates the empty deals sheet in their Google Drive and links it, applied at once (asking again returns the same sheet). `--model engagement|awareness|intent` and `--threshold N` on `influenced`, `campaigns` and `warm` read with another rule for that call only; the saved setting is unchanged. A **warm** account is New or Rising, reached the company's influence threshold this period on the rule's metric (`--min N` overrides, `--min 0` drops it), and has no open deal, no won deal and no deal lost in the last 6 months; `engagement` and `warm` rows carry `dealStatus` (`won` > `open` > `lost` > `none`), `engagement --output csv` adds `lastMonth` and `previousImpressions/Clicks/Engagements`, and `campaigns` / `company` carry each campaign's `campaignName` (null when LinkedIn could not name it). `company` returns `months` (the company's totals), `byCampaign` (the same numbers per campaign — overlapping, never add them) and its `deals` with `touchValue` and `influenced`. `warm --output csv` is the file `baker ads linkedin audiences create --type company-list --list-file` takes.
|
|
4339
|
+
|
|
4340
|
+
---
|
|
4341
|
+
|
|
3721
4342
|
### `baker chats list | view | transcript | diff`
|
|
3722
4343
|
|
|
3723
4344
|
Read-only inspection of **other Sessions (chats) on the same account** — so an agent can reuse prior work ("like we did in that other chat, but for X"). Same-account only; never crosses a company boundary and has no write surface.
|
|
@@ -3754,6 +4375,10 @@ baker schema tag-manager read # Kebab-case paths resolve to their registered n
|
|
|
3754
4375
|
|
|
3755
4376
|
Not every command registers a schema. On `NOT_FOUND` the error carries a structured `fix` pointing at `--help` and the family's agent doc (`__tooling__/docs/tools/baker/<family>.md` in sandboxes). Every top-level family's `--help` also ends with a `Full guide:` line pointing at that doc; `packages/cli/__tests__/doc-coverage.test.ts` fails CI if a mounted command is missing from its family doc.
|
|
3756
4377
|
|
|
4378
|
+
### `baker tag-manager containers`
|
|
4379
|
+
|
|
4380
|
+
Lists the company's selected Tag Manager containers that the connected Google account can still open. If access to a selected container was lost, the response names it in `unavailableContainers` and asks the user to reconnect with an account that can open it.
|
|
4381
|
+
|
|
3757
4382
|
## `baker studio` — make new imagery
|
|
3758
4383
|
|
|
3759
4384
|
The generation lane. `baker images` **sources** what already exists; `baker studio` **makes** what doesn't. Every call opens a *batch* — one brief, 1-8 takes, rendered in parallel — recorded against the calling chat and shown in the dashboard Studio next to the batches the client ran themselves.
|
|
@@ -3782,7 +4407,7 @@ Starting a batch returns 202 + a `generationId`; the CLI polls until the takes s
|
|
|
3782
4407
|
```bash
|
|
3783
4408
|
baker studio generate "a friendly golden retriever in a bright modern living room" --aspect-ratio 16:9
|
|
3784
4409
|
baker studio generate "hero shot of a matte black water bottle on wet marble, studio light" \
|
|
3785
|
-
--model google/gemini-3-pro-image
|
|
4410
|
+
--model google/gemini-3-pro-image --image-size 2K --aspect-ratio 4:5 --count 3
|
|
3786
4411
|
baker studio generate "home office hero, warm minimalist, natural light, 35mm photo" \
|
|
3787
4412
|
--reference "https://i.pinimg.com/…/pin1.jpg,./src/brand/logos/product.png,j57abc123def456ghi789"
|
|
3788
4413
|
baker studio generate "founder-style selfie, kitchen background" --skill ugc-selfie-hook
|
|
@@ -3790,13 +4415,13 @@ baker studio generate "flat geometric mascot, brand palette" \
|
|
|
3790
4415
|
--model recraft/recraft-v4.1-pro-vector --rgb-colors "[[10,10,10],[255,80,0]]" --bg-rgb "[255,255,255]"
|
|
3791
4416
|
```
|
|
3792
4417
|
|
|
3793
|
-
**Models** (`--model`, default `google/gemini-3.1-flash-image
|
|
4418
|
+
**Models** (`--model`, default `google/gemini-3.1-flash-image`):
|
|
3794
4419
|
|
|
3795
4420
|
| Model | Best for | Aspect ratios | Sizes |
|
|
3796
4421
|
|---|---|---|---|
|
|
3797
|
-
| `google/gemini-3.1-flash-image
|
|
4422
|
+
| `google/gemini-3.1-flash-image` **(default)** | Fast iteration, reference editing, extreme aspect ratios (Nano Banana flash) | standard **+** `1:4` `4:1` `1:8` `8:1` | `0.5K`–`4K` |
|
|
3798
4423
|
| `google/gemini-3.1-flash-lite-image` | Cheapest tier (Nano Banana 2 Lite) — half the flash price per output token. High-volume variants and any 1K-sufficient frame, portrait ad formats included | standard **+** `1:4` `4:1` `1:8` `8:1` | `1K` **only** |
|
|
3799
|
-
| `google/gemini-3-pro-image
|
|
4424
|
+
| `google/gemini-3-pro-image` | Highest fidelity (Nano Banana Pro) — strong prompt adherence and reference grounding | standard set | `1K` `2K` `4K` |
|
|
3800
4425
|
| `openai/gpt-image-2` | Photoreal + cleanest in-image text — ad/landing reproduction | standard set **minus** `4:5` `5:4` | *(none — derived from the ratio)* |
|
|
3801
4426
|
| `openai/gpt-5.4-image-2` | **Legacy** — accepted so canvases authored before the gpt-image-2 switch still re-run. Don't pick it for new work. | standard set | `1K` `2K` `4K` (ignored upstream) |
|
|
3802
4427
|
| `recraft/recraft-v4.1-pro-vector` | Vector/flat/SVG-style with palette control. Writes **SVG**, so its output cannot be fed back as a reference | `1:1` `4:3` `3:4` `16:9` `9:16` **only** | *(none — refuses `image_size`)* |
|
|
@@ -3805,7 +4430,7 @@ Standard aspect ratios: `1:1` `2:3` `3:2` `3:4` `4:3` `4:5` `5:4` `9:16` `16:9`
|
|
|
3805
4430
|
|
|
3806
4431
|
`openai/gpt-image-2` accepts neither `image_size` nor the `4:5` / `5:4` portrait ratios — OpenAI picks the pixel dimensions from the ratio. To ship a 4:5 Meta feed ad from it, render the hero at `3:4` and adapt with `image_aspect_adapt`; the scaffolders do this for you.
|
|
3807
4432
|
|
|
3808
|
-
`google/gemini-3.1-flash-lite-image` renders at `1K` and nothing else — `2K` / `4K` is a provider 400 (`resolution: not supported. Accepted: 1K`), not a soft clamp. It costs half of `google/gemini-3.1-flash-image
|
|
4433
|
+
`google/gemini-3.1-flash-lite-image` renders at `1K` and nothing else — `2K` / `4K` is a provider 400 (`resolution: not supported. Accepted: 1K`), not a soft clamp. It costs half of `google/gemini-3.1-flash-image` per output image token ($0.00003 vs $0.00006), so it is the cheap-iteration tier for images. Keep hero stills and reference sheets that need `2K`+ on the flash or pro tier.
|
|
3809
4434
|
|
|
3810
4435
|
`recraft/recraft-v4.1-pro-vector` is the narrowest of the roster in three ways, all read off its live endpoint schema: it renders **five** ratios (no `4:5` / `5:4` / `2:3` / `3:2` / `21:9`, so a portrait ad shape snaps to `3:4`), it has **no `image_size`** — the provider accepts one and ignores it, which is why it is refused here — and it takes **exactly one** reference image against 14-16 elsewhere. Its output is an SVG, and the image models read only JPG, PNG and WebP, so a Recraft take cannot be used as a reference for the next render.
|
|
3811
4436
|
|
|
@@ -3815,7 +4440,7 @@ A knob the chosen model does not advertise is **refused, not silently dropped**
|
|
|
3815
4440
|
|
|
3816
4441
|
| Flag | Description |
|
|
3817
4442
|
|---|---|
|
|
3818
|
-
| `--model` | Model id (default `google/gemini-3.1-flash-image
|
|
4443
|
+
| `--model` | Model id (default `google/gemini-3.1-flash-image`) |
|
|
3819
4444
|
| `--aspect-ratio` | Output aspect ratio (default `1:1`) |
|
|
3820
4445
|
| `--count` | Takes to render, 1–8 (default 1) |
|
|
3821
4446
|
| `--reference` | Comma-separated visual references, in order: **library image ids**, **local file paths**, and/or **image URLs**. Paths and URLs are added to the media library first and passed as ids — the studio takes library ids only, so the client can see what grounded the render and the reference stays reusable. The biggest quality lever there is. |
|
|
@@ -3879,7 +4504,7 @@ baker studio animate "she keeps walking, camera stays with her, then she stops a
|
|
|
3879
4504
|
- **480p / 720p only** — 1080p and 4K are real Seedance tiers but not on this route.
|
|
3880
4505
|
- **Sound costs nothing extra** here, unlike every other model that offers it.
|
|
3881
4506
|
- **No watermark** unless `--watermark` asks for one; it cannot be removed after the render.
|
|
3882
|
-
- **No people from an uploaded picture.** A privacy pre-check refuses any recognisable face in the input image — invented characters included, and images from ByteDance's own image model included.
|
|
4507
|
+
- **No people from an uploaded picture.** A privacy pre-check refuses any recognisable face in the input image — invented characters included, and images from ByteDance's own image model included. A reference-mode clip that hits it is re-rendered automatically on the same model through a second route that accepts faces (about twice the price); a frame-mode clip is reported as refused and never moved to another model — re-run it on Veo.
|
|
3883
4508
|
|
|
3884
4509
|
| Flag | Description |
|
|
3885
4510
|
|---|---|
|
|
@@ -3923,16 +4548,20 @@ Compact output per take is `{ slot, status, imageId | videoId, url, inLibrary, e
|
|
|
3923
4548
|
| `lifestyle-scene` | `openai/gpt-image-2` | `4:5` | 3 | **yes** |
|
|
3924
4549
|
| `cinematic-still` | `google/gemini-3-pro-image-preview` | `16:9` | 3 | no |
|
|
3925
4550
|
| `brand-icon` | `recraft/recraft-v4.1-pro-vector` | `1:1` | 4 | no |
|
|
3926
|
-
| `background-plate` | `google/gemini-3.1-flash-image
|
|
4551
|
+
| `background-plate` | `google/gemini-3.1-flash-image` | `4:5` | 3 | no |
|
|
3927
4552
|
|
|
3928
4553
|
A skill fills only what you did **not** pin — `--model`, `--aspect-ratio` and `--count` always win. Which means a skill can change the model, and the model decides what the rest of the flags may be: `--skill brand-icon` lands on Recraft, so it takes one reference image rather than fourteen, refuses `--image-size`, and writes SVG that cannot be fed back in as a reference. The CLI resolves the skill's model before checking the reference count, so an over-long `--reference` list is refused before anything is uploaded.
|
|
3929
4554
|
|
|
3930
4555
|
## Creative Canvas
|
|
3931
4556
|
|
|
4557
|
+
Static-ad guidance prefers a ready company Avatar whenever an actor is needed, chosen for the buyer context. A real founder or testimonial author uses their verified likeness; if it cannot be found, use initials or no face. The shared creative instructions also call for deliberate complete-logo selection, one consistent primary CTA treatment, and deterministic rendering of recognizable interfaces. Screen guidance distinguishes laptops and notebook computers from paper notebooks beside a phone.
|
|
4558
|
+
|
|
3932
4559
|
The creative canvas runs declarative JSON pipelines. You author a graph of nodes (text, image, video, audio, composition, control-flow), `baker canvas run` it, and every node's output drops to disk under `canvas/<run_id>/` so you can watch progress and consume the artifacts.
|
|
3933
4560
|
|
|
3934
4561
|
Auth: same `BAKER_API_KEY` + `BAKER_API_URL` as the rest of the CLI.
|
|
3935
4562
|
|
|
4563
|
+
Static resizes, fixes and series continuations keep why the original ad works and the elements its concept depends on, alongside the previous image. Older specs carry their persuasion mechanism and its reason instead. Automatic spec repairs preserve the concept, mechanism, message, brand (including the CTA color) and native interface; they can correct the execution without changing the idea. Change those choices explicitly in `prompt.json` when the brief changes.
|
|
4564
|
+
|
|
3936
4565
|
### Quick start
|
|
3937
4566
|
|
|
3938
4567
|
```bash
|
|
@@ -3942,9 +4571,9 @@ baker canvas validate my-canvas.json
|
|
|
3942
4571
|
# legal config to review before billing — a frame that describes a recurring element
|
|
3943
4572
|
# (a pet/product) but doesn't wire its reference (VIDEO_REFERENCE_MISSING), or a scene
|
|
3944
4573
|
# span that exceeds the assigned model's max clip duration (VIDEO_SPAN_EXCEEDS_MODEL),
|
|
3945
|
-
# or the same ad rendered once per aspect ratio instead of one hero + a
|
|
3946
|
-
# image_aspect_adapt (IMAGE_PARALLEL_FORMAT_RENDERS — separate renders drift
|
|
3947
|
-
# crop, and color, so the sizes stop matching).
|
|
4574
|
+
# or the same ad rendered once per aspect ratio instead of one hero + cuts or a
|
|
4575
|
+
# terminal image_aspect_adapt (IMAGE_PARALLEL_FORMAT_RENDERS — separate renders drift
|
|
4576
|
+
# in type, crop, and color, so the sizes stop matching).
|
|
3948
4577
|
# A generator prompt is measured with its {{slots}} filled from text nodes and local
|
|
3949
4578
|
# prompt.json/text ingests: past the model's character cap it is refused as
|
|
3950
4579
|
# PROMPT_OVER_MODEL_LIMIT, naming the node and the overshoot, before anything bills.
|
|
@@ -3956,6 +4585,8 @@ baker canvas run my-canvas.json
|
|
|
3956
4585
|
# Advisory warnings print as `[warn] <CODE>: …` before anything bills.
|
|
3957
4586
|
# Only nodes reachable from the declared output execute (orphaned nodes are skipped,
|
|
3958
4587
|
# not billed). Add --keep-runs N to prune old r_* run dirs, keeping the N newest.
|
|
4588
|
+
# The JSON ends with `verdict` (static ads), `run_id` and `files` (each delivered size's
|
|
4589
|
+
# local file by its label), so `| tail` keeps them.
|
|
3959
4590
|
# A failed run reports `error.code: "input_invalid"` when the backend refused a node's
|
|
3960
4591
|
# own input (it fails the same way until the input changes), `"runtime"` otherwise.
|
|
3961
4592
|
|
|
@@ -4019,7 +4650,7 @@ baker canvas set-prompt my-canvas.json s0_start --text-file ./hook-frame.txt
|
|
|
4019
4650
|
`baker canvas scaffold-ad` renders everything with a known, fixed appearance locally,
|
|
4020
4651
|
because a video model garbles a wordmark every time:
|
|
4021
4652
|
|
|
4022
|
-
- **`brand.logo`** — the repo path to the mark. It is copied into the
|
|
4653
|
+
- **`brand.logo`** — the repo path to the mark. It is copied into the caption composition
|
|
4023
4654
|
and drawn as a real `<img>`: small top-left for the whole ad, large on the closing card.
|
|
4024
4655
|
- **`end_card`** — on by default whenever the spec carries a brand. The last beat's PICTURE
|
|
4025
4656
|
becomes a flat brand colour plate (rendered by ffmpeg, not generated), with the mark and
|
|
@@ -4033,15 +4664,41 @@ because a video model garbles a wordmark every time:
|
|
|
4033
4664
|
order, page-furniture whites and blacks dropped) and `src/brand/logos/` for the mark
|
|
4034
4665
|
(vector first, plain mark over a mono/white/icon variant). Pass them yourself only when
|
|
4035
4666
|
the ad needs something other than the client's default.
|
|
4036
|
-
- **The spoken language is read off the script** when the spec
|
|
4037
|
-
|
|
4667
|
+
- **The spoken language is read off the script** when neither the spec (`voice.language`, a
|
|
4668
|
+
BCP-47 tag) nor the creative's `_definition.md` (a translation's `language`) declares it, in
|
|
4669
|
+
any language, so a Spanish ad is cast from Spanish voices without anyone having to say so.
|
|
4670
|
+
The same read, in any language and in a line that mixes two, finds a line where the speaker
|
|
4671
|
+
introduces themselves with nobody on camera, a shot brief that asks for readable text (both
|
|
4672
|
+
refused), a cast who works for the business, each voice description's gender, and the words
|
|
4673
|
+
a caption card may not end on. It is a fraction of a cent, kept until the spec changes and
|
|
4674
|
+
billed with the next run; when it cannot be read, nothing is refused and the hints say so.
|
|
4675
|
+
- **`market` is where the ad is set**, stated on every frame. Omitted, it is the country of a
|
|
4676
|
+
regional language (`es-MX` → Mexico), the voice's or the translation's. A language alone
|
|
4677
|
+
names no country (Spanish is not Spain): with neither, the frames name no place and a hint
|
|
4678
|
+
asks for one.
|
|
4038
4679
|
- **`sound`** (per beat) — one sound effect for that shot, rendered by ElevenLabs
|
|
4039
4680
|
alongside the music bed. Most beats should not have one: it belongs where the picture
|
|
4040
4681
|
shows a specific physical event (a drill, a door, rain on glass), not on a talking head
|
|
4041
4682
|
or a calm lifestyle shot. A bed plus two well-placed effects beats eight.
|
|
4042
|
-
- **
|
|
4043
|
-
|
|
4044
|
-
|
|
4683
|
+
- **The logo is optional.** With no mark in `src/brand/logos/` (and none confirmed for the
|
|
4684
|
+
company's own domain), the ad is built without the corner mark, and one hint says so and
|
|
4685
|
+
how to add one: put the client's logo file in `src/brand/logos/` and re-run. Meta shows
|
|
4686
|
+
the page name and picture above every ad either way. The closing card still carries the
|
|
4687
|
+
call to action when the spec has a `cta`; with neither a mark nor a `cta`, the last beat
|
|
4688
|
+
keeps its footage. A typed stand-in (an SVG wordmark in live `<text>`) is still refused,
|
|
4689
|
+
and `--no-brand` still means no brand layer at all: no mark and no closing card.
|
|
4690
|
+
- **Captions and the mark sit inside the safe area** (`videoSafeArea` in
|
|
4691
|
+
`src/engine/scaffold/platform-safe-area.ts`, the one owner of those margins). On 9:16 the
|
|
4692
|
+
caption cards end above the Reels interface at the bottom and the mark starts below the
|
|
4693
|
+
Stories and Reels header; 4:5 and 1:1 use modest feed margins. The closing card is centred
|
|
4694
|
+
in the same area. Every composition is sized to the spec's
|
|
4695
|
+
`format.aspect_ratio`, so a 4:5 ad lays its captions out on a 4:5 frame.
|
|
4696
|
+
- **Captions and the closing button are set in the brand's face**: a font file in
|
|
4697
|
+
`src/brand/fonts/`, else the typeface `tokens.json` or `BRAND.md` names, loaded as a web
|
|
4698
|
+
font. With neither, they keep the default heavy sans.
|
|
4699
|
+
- **A spoken ad's captions print the script.** The voice track is transcribed only to time
|
|
4700
|
+
the words: each caption word is the `say` line as written, at the moment it was said, so
|
|
4701
|
+
a word the recogniser mishears never reaches the screen.
|
|
4045
4702
|
- **An avatar with no pinned voice cannot speak on camera.** Their lines would be read by
|
|
4046
4703
|
a separately cast voice, which is dubbing and looks like it.
|
|
4047
4704
|
- **`voiceover: false`** — a music-led ad. No voice, no transcription: the `say` lines
|
|
@@ -4052,10 +4709,17 @@ because a video model garbles a wordmark every time:
|
|
|
4052
4709
|
every beat they appear in on that avatar's identity sheet and copies its subject
|
|
4053
4710
|
description verbatim, so the ad shows the same face as the rest of the company's work.
|
|
4054
4711
|
This is the whole journey: `baker avatars create` → `cast.avatar` in the spec → render.
|
|
4055
|
-
- **`cast.description`** — a person described in words, when there is no avatar.
|
|
4712
|
+
- **`cast.description`** — a person described in words, when there is no avatar: a candid portrait is generated from it and every beat grounds on that one invented person.
|
|
4056
4713
|
|
|
4057
4714
|
Each of these also removes a generation: the closing beat costs no image and no clip.
|
|
4058
4715
|
|
|
4716
|
+
**The spec is kept, and the canvas is checked.** `scaffold-ad` writes the spec it built
|
|
4717
|
+
from beside the canvas as `ad.spec.json` (with the cast it resolved), so a later change
|
|
4718
|
+
edits that file and re-runs with `--spec` on it and the same `--slug`. With `--slug` it
|
|
4719
|
+
also writes the creative's `_definition.md` and syncs its workflow graph, as
|
|
4720
|
+
`scaffold-video` does. The canvas is validated before the command reports success: a
|
|
4721
|
+
blocking issue is recorded in the canvas and the command exits `2` with the issues.
|
|
4722
|
+
|
|
4059
4723
|
#### The finished video is reviewed
|
|
4060
4724
|
|
|
4061
4725
|
When a run's output is a video, `baker canvas run` samples twelve frames across it and
|
|
@@ -4115,7 +4779,7 @@ A re-run with no changes hits the cache for every node — total runtime drops t
|
|
|
4115
4779
|
"type": "text_generate",
|
|
4116
4780
|
"inputs": { "topic": "$ref:topic.text" },
|
|
4117
4781
|
"params": {
|
|
4118
|
-
"model": "google/gemini-3.1-flash-image
|
|
4782
|
+
"model": "google/gemini-3.1-flash-image",
|
|
4119
4783
|
"prompt": "Write a 6-word tagline for {{topic}}."
|
|
4120
4784
|
}
|
|
4121
4785
|
}
|
|
@@ -4146,6 +4810,8 @@ The top-level `output` declares the canvas's final result — the engine returns
|
|
|
4146
4810
|
|
|
4147
4811
|
Every node shares the envelope `{ id, type, inputs?, params? }`. `inputs` wires other nodes' output slots in; `params` configures the node. The validator enforces required input kinds and per-model param shape **before** anything is billed.
|
|
4148
4812
|
|
|
4813
|
+
A node may also carry `tags` (`{ scene?, role?, region?, on_camera? }`) stating what it is in the ad (scene 3, region 1, the presenter's clip, the spine). `scaffold-video` stamps them and `canvas validate` reads them in place of id conventions like `s3_r1_clip`. They are descriptive only: tags never enter the cache key, so adding or editing them never re-renders or re-bills a node. Canvases without tags validate as they always did.
|
|
4814
|
+
|
|
4149
4815
|
`baker canvas catalog` prints a one-line index of every node; `baker canvas catalog --node <id>` prints that node's always-current machine-readable schema (params, inputs, outputs, every model), and `--full` prints every node's. The tables below mirror the catalog at time of write.
|
|
4150
4816
|
|
|
4151
4817
|
In every table:
|
|
@@ -4200,6 +4866,44 @@ Local ffmpeg passthrough. Write the argv you'd type, declare outputs, the engine
|
|
|
4200
4866
|
|
|
4201
4867
|
---
|
|
4202
4868
|
|
|
4869
|
+
##### `video_concat`
|
|
4870
|
+
|
|
4871
|
+
Join clips and stills into one continuous video: the spine of a video ad. Every input is normalized onto one raster (size, frame rate, square pixels, `yuv420p`), cut or held to its timeline length, and joined with hard cuts or `xfade` transitions. Local ffmpeg, free. Because the length and size live in params, `canvas validate` sums the spine against the timeline (`VIDEO_TIMELINE_TOTAL_MISMATCH`) before anything renders.
|
|
4872
|
+
|
|
4873
|
+
**Inputs:** one video or image per clip, wired as `c0`, `c1`, … in timeline order (deep validation flags a missing or extra slot).
|
|
4874
|
+
|
|
4875
|
+
**Params:**
|
|
4876
|
+
|
|
4877
|
+
| Name | Type | Required | Notes |
|
|
4878
|
+
|---|---|---|---|
|
|
4879
|
+
| `width`, `height` | integer | yes | output raster; must be even (libx264 `yuv420p` rejects odd sizes) |
|
|
4880
|
+
| `fps` | integer | no | default `30` |
|
|
4881
|
+
| `seam_dedup` | `head` \| `tail` \| `off` | no | default `head`: which copy of a repeated splice frame survives on a `continues_frame` clip; the timeline length never changes |
|
|
4882
|
+
| `clips` | array | yes | per clip: `duration_s` (its timeline length), `length` (`source` default: play as rendered; `trim`: cut out of a longer input from `offset_s`; `still`: hold an image), `fit` (`fill` default crops, `pad` letterboxes a screenshot), `transition` (`{ type, duration_s }`, the xfade out of this clip; the clip carries that much extra footage), `continues_frame` |
|
|
4883
|
+
|
|
4884
|
+
The joined length is the sum of the clips' `duration_s` (a `source` clip counts what is wired into it). **Outputs:** `video` (mp4).
|
|
4885
|
+
|
|
4886
|
+
---
|
|
4887
|
+
|
|
4888
|
+
##### `av_mux`
|
|
4889
|
+
|
|
4890
|
+
Put the soundtrack under a finished picture: the last step of a video ad. The video is stream-copied, the audio loudness-normalized, upmixed to stereo and encoded to AAC. Local ffmpeg, free.
|
|
4891
|
+
|
|
4892
|
+
**Inputs:** `video`, `audio`.
|
|
4893
|
+
|
|
4894
|
+
**Params:**
|
|
4895
|
+
|
|
4896
|
+
| Name | Type | Required | Notes |
|
|
4897
|
+
|---|---|---|---|
|
|
4898
|
+
| `loudnorm` | `{ i, tp, lra }` \| `false` | no | default `{ i: -14, tp: -1.5, lra: 11 }` (the social target); `false` keeps the mix level |
|
|
4899
|
+
| `audio_bitrate` | string | no | default `"192k"` |
|
|
4900
|
+
| `sample_rate` | integer | no | default `48000` |
|
|
4901
|
+
| `length` | `"shortest"` \| `{ total_s }` | no | default `shortest`: end with the shorter stream; `{ total_s }` cuts both at that second |
|
|
4902
|
+
|
|
4903
|
+
**Outputs:** `video` (mp4).
|
|
4904
|
+
|
|
4905
|
+
---
|
|
4906
|
+
|
|
4203
4907
|
##### `imagemagick`
|
|
4204
4908
|
|
|
4205
4909
|
Local ImageMagick passthrough. Identical schema and safety contract to `ffmpeg`; uses `magick` (v7+) or `convert` (v6) on PATH.
|
|
@@ -4260,6 +4964,14 @@ Open record keyed by the composition's `meta.json` inputs.
|
|
|
4260
4964
|
|
|
4261
4965
|
---
|
|
4262
4966
|
|
|
4967
|
+
##### `static_prefilter`
|
|
4968
|
+
|
|
4969
|
+
Local; no credits (one box-reader call per variant, about $0.005, which rides on the run record's review spend). Ranks a static ad's variants before the judge by the deterministic checks (blocking defects, then every line of the ad spec's copy read back with its box, the brand's logo found, the button's words, every word and the logo inside the master's band) and passes on only the best-ranked, so the judge only breaks an exact tie. A variant the reader could not read ties with the best. **Inputs:** `images` (the variants' whole list), `spec` (the ad spec, `$ref:prompt.asset`). **Params:** `formats` (the delivered sizes). **Outputs:** `images` (the best-ranked variants, in an order seeded by the pictures rather than the render order; never empty: when every variant fails, the one with the fewest defects passes as `flagged`), `order` (which variant each of `images` is), `dropped` (each variant set aside, with its index and reasons), `flagged` (the variant kept when all failed), `image` (the chosen picture: when a variant rendered at another shape, a 3:4 for a 4:5, would lose a word or the button to a cut about its centre, it comes cut to the master's frame where every word stays whole, else as rendered) and `usage` (the reader's spend). Scaffolded by `scaffold-static-ad` between the variants and the judge.
|
|
4970
|
+
|
|
4971
|
+
##### `static_safe_fit`
|
|
4972
|
+
|
|
4973
|
+
Local; no credits (one box-reader call, about $0.005, which rides on the run record's review spend). Brings a finished 9:16's words and logo inside the band Reels or Stories leave clear without rendering again: reads where every line and the logo sit and, when any reaches past the band, shrinks the whole picture a little (to 85% at most) and shifts it, carrying its edge into the uncovered margin, only when that edge is plain ground; where the review blocks a line under Reels' or TikTok's buttons down the right (an ad set that runs there and not in Stories), the fit clears them too (a busy edge would read as a blurred duplicate, so the picture is passed on as rendered). The image model cannot move an element by a few points on request, so this replaces re-rendering for small misses. **Inputs:** `image`. **Params:** `format`, `placements` (the ad set's, as the sizes were planned). **Outputs:** `image` (same pixel size), `fit` (`{ scale, x, y }`, or null when passed on unchanged), `reason` (`not_read`, `inside_band`, `too_far` when clearing the band needs more than the 85% shrink, or `busy_edge` when the edge it would carry is not plain ground) and `usage`. Scaffolded by `scaffold-static-ad` after every 9:16 Reels or Stories covers (`safe_<size>`, tagged `static_safe_fit`). It never sets the picture over a blurred or mirrored copy of itself: a layout `too_far` or `busy_edge` is passed on as it is, the run review names what Reels or Stories covers, and `baker canvas run` lays that size out again by itself.
|
|
4974
|
+
|
|
4263
4975
|
##### `font_specimen`
|
|
4264
4976
|
|
|
4265
4977
|
Render specimen text in a given font file to a **PNG** — black `#000` text on a white `#fff` background — via headless Chromium at 2× device-scale. Produces a **typeface reference image**: wire `image` into `image_generate`'s `reference` input so the model replicates the font when generating images that contain text.
|
|
@@ -4297,7 +5009,7 @@ Example pipeline:
|
|
|
4297
5009
|
{ "id": "font", "type": "ingest", "params": { "source": "url", "url": "https://cdn.example.com/brand.woff2", "expect": "font" } }
|
|
4298
5010
|
{ "id": "specimen", "type": "font_specimen", "inputs": { "font": "$ref:font.asset" }, "params": {} }
|
|
4299
5011
|
{ "id": "gen", "type": "image_generate", "inputs": { "reference": "$ref:specimen.image" },
|
|
4300
|
-
"params": { "model": "google/gemini-3.1-flash-image
|
|
5012
|
+
"params": { "model": "google/gemini-3.1-flash-image", "prompt": "Launch poster headlined 'SALE ENDS FRIDAY' using the exact typeface from the reference image" } }
|
|
4301
5013
|
```
|
|
4302
5014
|
|
|
4303
5015
|
---
|
|
@@ -4355,8 +5067,8 @@ None.
|
|
|
4355
5067
|
| `model` | enum | yes | one of `~google/gemini-flash-latest`, `~google/gemini-pro-latest` |
|
|
4356
5068
|
| `prompt` | string | yes | non-empty |
|
|
4357
5069
|
| `system` | string | no | system prompt |
|
|
4358
|
-
| `response_format` | enum | no | `text` (default) or `json_object`. Set `json_object` when a downstream `{{slot}}` consumes the output as JSON (e.g.
|
|
4359
|
-
| `web_search` | boolean | no | When `true`, routes through OpenRouter's `:online` web plugin so the model searches the live web before answering. Use it on
|
|
5070
|
+
| `response_format` | enum | no | `text` (default) or `json_object`. Set `json_object` when a downstream `{{slot}}` consumes the output as JSON (e.g. a hand-built transform step) so the model returns clean JSON with no markdown fences or prose. |
|
|
5071
|
+
| `web_search` | boolean | no | When `true`, routes through OpenRouter's `:online` web plugin so the model searches the live web before answering. Use it on a text step that must adapt copy to the target brand's real facts (current pricing, the trust signals it actually has) instead of guessing. Adds web-search cost. |
|
|
4360
5072
|
| `temperature` | number | no | 0–2 |
|
|
4361
5073
|
| `max_tokens` | number | no | positive int |
|
|
4362
5074
|
|
|
@@ -4394,7 +5106,7 @@ Generate images via OpenRouter. The Zod schema is a permissive union; per-model
|
|
|
4394
5106
|
| `reference` | image **or** image[] | no | `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
|
|
4395
5107
|
|
|
4396
5108
|
`reference` accepts a single image or an **array** of images. Wire several to combine references in one
|
|
4397
|
-
generation — e.g. a subject reference sheet + a font specimen
|
|
5109
|
+
generation — e.g. a subject reference sheet + a product shot + a font specimen (never someone else's ad: the model copies what it is shown). Each image is
|
|
4398
5110
|
forwarded to the model as a separate input, in array order (the provider accepts multiple). Wire an
|
|
4399
5111
|
array as a literal list: `"reference": ["$ref:subject.sheet", "$ref:type.image"]`.
|
|
4400
5112
|
|
|
@@ -4404,7 +5116,9 @@ array as a literal list: `"reference": ["$ref:subject.sheet", "$ref:type.image"]
|
|
|
4404
5116
|
|---|---|---|
|
|
4405
5117
|
| `images` | image[] | `image/png \| image/jpeg \| image/webp` (provider-determined) |
|
|
4406
5118
|
|
|
4407
|
-
**Cost** — 5 credits estimate per
|
|
5119
|
+
**Cost** — 5 credits estimate per image asked for (`n`, default 1), 20 on Gemini 3 Pro Image (as `image_aspect_adapt` quotes it) and 20 at quality `max`; the backend charges the call's actual OpenRouter usage.
|
|
5120
|
+
|
|
5121
|
+
**Several candidates from one call** — `n` (integer, default 1) asks one call for several images, returned together in `images`. Each image is billed as if it were its own call (`n: 3` costs three images); what it saves is requests and waiting. Only models that declare it take it (today `openai/gpt-image-2.5-sunburst`, up to 10); any other model refuses it before anything is billed. Wire the whole array into `image_select` to keep the best.
|
|
4408
5122
|
|
|
4409
5123
|
> `seed` is not supported — OpenRouter's `image_config` schema has no seed slot for image-gen.
|
|
4410
5124
|
|
|
@@ -4434,15 +5148,15 @@ No `image_size`: OpenAI derives the pixel dimensions from the ratio, so a size i
|
|
|
4434
5148
|
|
|
4435
5149
|
###### Model: `openai/gpt-5.4-image-2` (legacy)
|
|
4436
5150
|
|
|
4437
|
-
Kept only so canvases authored before the gpt-image-2 switch still validate and re-run unchanged — `baker canvas
|
|
5151
|
+
Kept only so canvases authored before the gpt-image-2 switch still validate and re-run unchanged — `baker canvas run --slug` replays a recorded canvas verbatim. It keeps the full standard ratio set (including `4:5` / `5:4`) and still accepts `image_size`, which the provider ignores. Prefer `openai/gpt-image-2` for anything new.
|
|
4438
5152
|
|
|
4439
|
-
###### Model: `google/gemini-3.1-flash-image-preview`
|
|
5153
|
+
###### Model: `google/gemini-3.1-flash-image` (legacy id `google/gemini-3.1-flash-image-preview` still accepted)
|
|
4440
5154
|
|
|
4441
5155
|
Gemini flash image model — the default. Fast iteration, reference editing, extreme aspect ratios, and the `0.5K` size for cheap previews.
|
|
4442
5156
|
|
|
4443
5157
|
| Name | Type | Required | Notes |
|
|
4444
5158
|
|---|---|---|---|
|
|
4445
|
-
| `model` | literal | yes | `"google/gemini-3.1-flash-image
|
|
5159
|
+
| `model` | literal | yes | `"google/gemini-3.1-flash-image"` |
|
|
4446
5160
|
| `prompt` | string | yes | non-empty |
|
|
4447
5161
|
| `aspect_ratio` | enum | no | EXTREME AR |
|
|
4448
5162
|
| `image_size` | enum | no | `0.5K \| 1K \| 2K \| 4K` |
|
|
@@ -4458,20 +5172,20 @@ Gemini flash image model — the default. Fast iteration, reference editing, ext
|
|
|
4458
5172
|
| `aspect_ratio` | enum | no | EXTREME AR — keeps `4:5` / `5:4`, so Meta/Instagram portrait formats come straight out of it |
|
|
4459
5173
|
| `image_size` | enum | no | `1K` only — `2K` / `4K` is a hard provider 400 (`resolution: not supported. Accepted: 1K`), so `validate` rejects it before the call costs anything |
|
|
4460
5174
|
|
|
4461
|
-
Reach for it for high-volume variants and any frame `1K` covers. Send hero stills and the `image_reference_sheet` subject sheet — anything that needs `2K`+ — to `google/gemini-3.1-flash-image
|
|
5175
|
+
Reach for it for high-volume variants and any frame `1K` covers. Send hero stills and the `image_reference_sheet` subject sheet — anything that needs `2K`+ — to `google/gemini-3.1-flash-image` or `google/gemini-3-pro-image`. Registered for `image_generate` only; `image_reference_sheet` and `image_aspect_adapt` do not accept it.
|
|
4462
5176
|
|
|
4463
5177
|
```json
|
|
4464
5178
|
{ "id": "variant_a", "type": "image_generate",
|
|
4465
5179
|
"params": { "model": "google/gemini-3.1-flash-lite-image", "prompt": "Portrait feed ad for a citrus energy drink …", "aspect_ratio": "4:5", "image_size": "1K" } }
|
|
4466
5180
|
```
|
|
4467
5181
|
|
|
4468
|
-
###### Model: `google/gemini-3-pro-image-preview`
|
|
5182
|
+
###### Model: `google/gemini-3-pro-image` (legacy id `google/gemini-3-pro-image-preview` still accepted)
|
|
4469
5183
|
|
|
4470
5184
|
"Nano Banana Pro" — heavier, higher-quality Gemini.
|
|
4471
5185
|
|
|
4472
5186
|
| Name | Type | Required | Notes |
|
|
4473
5187
|
|---|---|---|---|
|
|
4474
|
-
| `model` | literal | yes | `"google/gemini-3-pro-image
|
|
5188
|
+
| `model` | literal | yes | `"google/gemini-3-pro-image"` |
|
|
4475
5189
|
| `prompt` | string | yes | non-empty |
|
|
4476
5190
|
| `aspect_ratio` | enum | no | STD AR |
|
|
4477
5191
|
| `image_size` | enum | no | `1K \| 2K \| 4K` |
|
|
@@ -4515,7 +5229,7 @@ The sheet prompt is baked into the backend — there is no `prompt` param. `subj
|
|
|
4515
5229
|
|
|
4516
5230
|
| Name | Type | Required | Notes |
|
|
4517
5231
|
|---|---|---|---|
|
|
4518
|
-
| `model` | enum | yes | one of `google/gemini-3-pro-image
|
|
5232
|
+
| `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
|
|
4519
5233
|
| `subject_description` | string | yes | exact subject wording — reuse verbatim downstream |
|
|
4520
5234
|
| `subject_type` | enum | yes | `character \| person \| product` — picks the template + default views |
|
|
4521
5235
|
| `views` | string[] | no | 2–6 view labels; defaults per `subject_type` (below) |
|
|
@@ -4542,7 +5256,7 @@ Default views per `subject_type`:
|
|
|
4542
5256
|
|
|
4543
5257
|
Guidance:
|
|
4544
5258
|
- 3–4 full-body views at `16:9` is the sweet spot for characters; more views shrink each figure and lose face detail.
|
|
4545
|
-
- Use `google/gemini-3.1-flash-image
|
|
5259
|
+
- Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` at `2K`+ for final 6-view sheets.
|
|
4546
5260
|
- Feed the whole sheet downstream as one `reference` input — don't crop individual views.
|
|
4547
5261
|
|
|
4548
5262
|
**Example** — two photos of a sneaker → product sheet → consistent hero shot:
|
|
@@ -4555,14 +5269,14 @@ Guidance:
|
|
|
4555
5269
|
{ "id": "b", "type": "ingest", "params": { "source": "url", "url": "https://example.com/sneaker-side.jpg", "expect": "image" } },
|
|
4556
5270
|
{ "id": "sheet", "type": "image_reference_sheet",
|
|
4557
5271
|
"inputs": { "references": ["$ref:a.asset", "$ref:b.asset"] },
|
|
4558
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
5272
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4559
5273
|
"subject_description": "navy leather low-top sneaker with white sole and gold logo",
|
|
4560
5274
|
"subject_type": "product",
|
|
4561
5275
|
"style": "matte black studio background, soft even key light",
|
|
4562
5276
|
"aspect_ratio": "16:9", "image_size": "2K" } },
|
|
4563
5277
|
{ "id": "hero", "type": "image_generate",
|
|
4564
5278
|
"inputs": { "reference": "$ref:sheet.sheet" },
|
|
4565
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
5279
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4566
5280
|
"prompt": "The navy leather low-top sneaker with white sole and gold logo from the reference sheet, hero shot on a rain-soaked neon street at night" } }
|
|
4567
5281
|
],
|
|
4568
5282
|
"output": { "node": "hero", "output": "images" }
|
|
@@ -4575,7 +5289,7 @@ Guidance:
|
|
|
4575
5289
|
|
|
4576
5290
|
Adapt ONE creative into multiple aspect ratios in a single step — the standard fan-out when a hero creative must ship to every placement (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9…). The AI recomposes the layout per format: identical subject, faces, products, text (verbatim, same typography), logos, colors, and style; the scene is extended/restructured for the new canvas — never stretched, cropped, or letterboxed.
|
|
4577
5291
|
|
|
4578
|
-
The adaptation prompt is baked into the backend and is direction-aware (a
|
|
5292
|
+
The adaptation prompt is baked into the backend and is direction-aware by orientation: a portrait source adapted to landscape (or the reverse) is recomposed; a same-orientation change only extends the scene at the sides or at the ends, every element kept where it is. Optional `copy` (the ad's exact lines: each kept once, spelled as given) and `safe_areas` (per format, the fraction of each edge the platform's interface covers) are set by the static scaffold. Formats that already match the source ratio (within ~2%) pass the source asset through unchanged — no model call, no cost.
|
|
4579
5293
|
|
|
4580
5294
|
**Inputs**
|
|
4581
5295
|
|
|
@@ -4587,7 +5301,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
|
|
|
4587
5301
|
|
|
4588
5302
|
| Name | Type | Required | Notes |
|
|
4589
5303
|
|---|---|---|---|
|
|
4590
|
-
| `model` | enum | yes | one of `google/gemini-3-pro-image
|
|
5304
|
+
| `model` | enum | yes | one of `google/gemini-3-pro-image`, `google/gemini-3.1-flash-image` (their `-preview` ids still accepted) |
|
|
4591
5305
|
| `formats` | enum[] | one of `formats`/`platform` | 1–6 unique target ratios from `1:1 \| 2:3 \| 3:2 \| 3:4 \| 4:3 \| 4:5 \| 5:4 \| 9:16 \| 16:9 \| 21:9` — output order follows this list |
|
|
4592
5306
|
| `platform` | enum | one of `formats`/`platform` | a placement preset that expands to that platform's canonical on-grid set: `meta_feed` (4:5), `meta_feed_stories` (4:5, 9:16), `meta_all` (4:5, 9:16, 1:1), `linkedin` (1:1), `google_demandgen` (1:1, 4:5, 16:9), `tiktok` (9:16), `youtube` (16:9). Mutually exclusive with `formats`. Off-grid platform targets (e.g. Google 1.91:1) are not rendered here — crop them from the nearest rendered ratio with `baker images crop`. |
|
|
4593
5307
|
| `guidance` | string | no | hints appended to the baked prompt, e.g. `"keep the CTA button fully visible"` |
|
|
@@ -4604,7 +5318,7 @@ The adaptation prompt is baked into the backend and is direction-aware (a wider
|
|
|
4604
5318
|
Guidance:
|
|
4605
5319
|
- Reach for `platform` when you want a placement's whole set without naming ratios (e.g. `"platform": "meta_feed_stories"`); use `formats` when you need an exact, custom ratio list. Set one, never both.
|
|
4606
5320
|
- List the source's own ratio in `formats` too when you want the full set in one output — it passes through for free.
|
|
4607
|
-
- Use `google/gemini-3.1-flash-image
|
|
5321
|
+
- Use `google/gemini-3.1-flash-image` while iterating; switch to `google/gemini-3-pro-image` for final-quality adaptation.
|
|
4608
5322
|
- Indexes are positional: `$ref:adapt.images#1` is always the second entry of `formats`.
|
|
4609
5323
|
|
|
4610
5324
|
**Example** — one 9:16 story creative fanned out to feed and landscape:
|
|
@@ -4614,12 +5328,12 @@ Guidance:
|
|
|
4614
5328
|
"schema": "baker-canvas/1",
|
|
4615
5329
|
"nodes": [
|
|
4616
5330
|
{ "id": "hero", "type": "image_generate",
|
|
4617
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
5331
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4618
5332
|
"prompt": "Story-format ad for a citrus energy drink: can centered, bold OFFER text top, logo bottom",
|
|
4619
5333
|
"aspect_ratio": "9:16", "image_size": "2K" } },
|
|
4620
5334
|
{ "id": "adapt", "type": "image_aspect_adapt",
|
|
4621
5335
|
"inputs": { "source": "$ref:hero.images#0" },
|
|
4622
|
-
"params": { "model": "google/gemini-3-pro-image
|
|
5336
|
+
"params": { "model": "google/gemini-3-pro-image",
|
|
4623
5337
|
"formats": ["9:16", "1:1", "16:9"],
|
|
4624
5338
|
"guidance": "keep the offer text and logo fully visible" } }
|
|
4625
5339
|
],
|
|
@@ -4717,13 +5431,13 @@ Search the web for real images instead of generating them. A backend LLM agent p
|
|
|
4717
5431
|
|
|
4718
5432
|
##### `image_select`
|
|
4719
5433
|
|
|
4720
|
-
Pick the best `count` images out of
|
|
5434
|
+
Pick the best `count` images out of the candidates with a vision LLM, judged against a prompt. The output is a passthrough subset of the input refs — no new pixels are generated, so selection is cheap. Use it after an `image_generate` run with `n` > 1, several `image_generate` variants, or an `image_search` batch, to keep only the strongest before expensive downstream steps. A single candidate (a generation run at `n: 1`) passes through unjudged: no call, no charge, no `reasoning`.
|
|
4721
5435
|
|
|
4722
5436
|
**Inputs**
|
|
4723
5437
|
|
|
4724
5438
|
| Slot | Kind | Required | Accepted MIMEs |
|
|
4725
5439
|
|---|---|---|---|
|
|
4726
|
-
| `images` | image[] | yes — at least
|
|
5440
|
+
| `images` | image[] | yes — at least 1 | `image/png`, `image/jpeg`, `image/webp`, `image/gif` |
|
|
4727
5441
|
|
|
4728
5442
|
Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list collecting refs from several nodes (`["$ref:a.images#0", "$ref:b.images#0"]`).
|
|
4729
5443
|
|
|
@@ -4734,7 +5448,7 @@ Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list colle
|
|
|
4734
5448
|
| `model` | enum | yes | one of `~google/gemini-flash-latest`, `~google/gemini-pro-latest` (`pro` for harder aesthetic judgement) |
|
|
4735
5449
|
| `prompt` | string | yes | selection criteria, e.g. `"most professional product shot, clean background"` |
|
|
4736
5450
|
| `count` | int | no | images to select, ≥ 1 (default 1); must not exceed the wired image count |
|
|
4737
|
-
| `temperature` | number | no | 0–2,
|
|
5451
|
+
| `temperature` | number | no | 0–2; unset by default, so the Gemini judge keeps its own default |
|
|
4738
5452
|
| `max_tokens` | number | no | positive int |
|
|
4739
5453
|
|
|
4740
5454
|
**Outputs**
|
|
@@ -4742,7 +5456,7 @@ Wire either a whole upstream array (`"$ref:gen.images"`) or a literal list colle
|
|
|
4742
5456
|
| Slot | Kind | MIME |
|
|
4743
5457
|
|---|---|---|
|
|
4744
5458
|
| `images` | image[] | per input — exactly `count` refs, in the model's preference order, so `images#0`…`images#count-1` are always safe to wire |
|
|
4745
|
-
| `reasoning` | text | `text/plain` — why the winners won, compared against the rejected candidates |
|
|
5459
|
+
| `reasoning` | text | `text/plain` — why the winners won, compared against the rejected candidates; absent when a single candidate passed through |
|
|
4746
5460
|
|
|
4747
5461
|
**Cost** — 1 credit estimate per call (backend charges actual OpenRouter usage).
|
|
4748
5462
|
|
|
@@ -4754,9 +5468,9 @@ Fail-fast: an invalid model selection (wrong count, duplicate or out-of-range pi
|
|
|
4754
5468
|
{
|
|
4755
5469
|
"schema": "baker-canvas/1",
|
|
4756
5470
|
"nodes": [
|
|
4757
|
-
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
4758
|
-
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
4759
|
-
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image
|
|
5471
|
+
{ "id": "a", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, warm morning light" } },
|
|
5472
|
+
{ "id": "b", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, studio softbox" } },
|
|
5473
|
+
{ "id": "c", "type": "image_generate", "params": { "model": "google/gemini-3.1-flash-image", "prompt": "hero shot of a ceramic mug, dramatic side light" } },
|
|
4760
5474
|
{ "id": "best", "type": "image_select",
|
|
4761
5475
|
"inputs": { "images": ["$ref:a.images#0", "$ref:b.images#0", "$ref:c.images#0"] },
|
|
4762
5476
|
"params": { "model": "~google/gemini-pro-latest", "prompt": "most premium-feeling lighting, no harsh shadows" } }
|
|
@@ -4788,7 +5502,7 @@ Accepted ref-image MIMEs vary by model — see per-model sections below.
|
|
|
4788
5502
|
|---|---|---|
|
|
4789
5503
|
| `video` | video | `video/mp4` |
|
|
4790
5504
|
|
|
4791
|
-
**Cost** —
|
|
5505
|
+
**Cost** — priced per model from the shared contract table (duration x resolution x audio; the fal route and source-clip premium when they apply), the same table the backend charges from. One clip may not cost more than $20: the backend refuses a dearer one before it runs, and no clip the registry allows reaches it.
|
|
4792
5506
|
|
|
4793
5507
|
---
|
|
4794
5508
|
|
|
@@ -4852,7 +5566,7 @@ Ref-image MIMEs: `image/png`, `image/jpeg`, `image/webp`.
|
|
|
4852
5566
|
|---|---|---|---|
|
|
4853
5567
|
| `model` | literal | yes | `"bytedance/seedance-2.0"` |
|
|
4854
5568
|
| `prompt` | string | yes | non-empty |
|
|
4855
|
-
| `duration` | enum | no |
|
|
5569
|
+
| `duration` | enum | no | every whole second `4`-`15` |
|
|
4856
5570
|
| `resolution` | enum | no | `480p \| 720p \| 1080p` |
|
|
4857
5571
|
| `aspect_ratio` | enum | no | `1:1, 3:4, 9:16, 4:3, 16:9, 21:9, 9:21` |
|
|
4858
5572
|
| `generate_audio` | boolean | no | native audio track |
|
|
@@ -4895,18 +5609,18 @@ Ref-image MIMEs: `image/png`, `image/jpeg`, `image/webp`, `image/gif` (via OpenR
|
|
|
4895
5609
|
|
|
4896
5610
|
> **Switching a clip's model has cross-node constraints** — validate after every model edit. `validate` gates all of these before any billed run:
|
|
4897
5611
|
> - **One aspect_ratio for the whole canvas.** Every `video_generate` node must agree (`VIDEO_ASPECT_MISMATCH`), or the composite silently crops/letterboxes. Switching one clip to Veo (which only offers `16:9`/`9:16`) forces every other clip to a shared ratio too.
|
|
4898
|
-
> - **`duration` is a per-model hard enum.** Seedance `4
|
|
5612
|
+
> - **`duration` is a per-model hard enum.** Seedance 2.0 `4`-`15`, Seedance 2.5 `4`-`30`, Kling `3`-`15`, Omni `3`-`10`, Veo `4,6,8` only. The scaffolders snap clip lengths to the same registry sets. A value outside the model's set is rejected; a scene whose natural span exceeds the model's max is flagged (`VIDEO_SPAN_EXCEEDS_MODEL`, advisory) — a 9.2s scene can't fit Veo's 8s cap and would truncate; split the scene or keep it on a longer-max model.
|
|
4899
5613
|
> - **`person_generation` differs.** Veo **image-to-video** accepts only `allow_adult` (the sole legal value for keyframe-driven modes, and the only one permitted in the EU/UK); `allow_all` is text-to-video only. The scaffolder sets `allow_adult` on every Veo clip automatically (`VIDEO_PERSON_GENERATION_MISSING` warns if it's missing on a hand-edited canvas).
|
|
4900
5614
|
>
|
|
4901
5615
|
> A scaffolded canvas carries this table inline at `metadata.todo.model_constraints`.
|
|
4902
5616
|
|
|
4903
|
-
> **Per-model prompt & param profiles (applied automatically).** The scaffolder shapes each clip/frame prompt to the model's documented best practice and sets its request-param levers — see `packages/cli/src/engine/
|
|
5617
|
+
> **Per-model prompt & param profiles (applied automatically).** The scaffolder shapes each clip/frame prompt to the model's documented best practice and sets its request-param levers — see `packages/cli/src/engine/lib/prompt-profiles.ts`:
|
|
4904
5618
|
> - **Seedance 2.0** (default video): dialogue in quotes with delivery in **prose** (not `[brackets]`), a `Preserve the composition and colors` keyframe hold, and an **affirmative** stability tail — `Keep it clean and stable: crisp anatomically-correct hands, one consistent face, smooth steady motion` — instead of an `Avoid: …` list (Seedance has no `negative_prompt` param, and inline negation tends to summon the artifact it names). Prompt kept to the ~120-word quality budget (`VIDEO_PROMPT_OVER_BUDGET` warns past ~120).
|
|
4905
5619
|
> - **Veo 3.1** (fallback): `person_generation=allow_adult` + a caption-suppressing `negative_prompt` set as params; durations snapped to `{4,6,8}`.
|
|
4906
5620
|
> - **gpt-image** (default frames): clean-plate exclusions placed **LAST** (OpenAI weights early text as composition), an explicit `photorealistic` cue, identity-critical reference **first**, and `quality=high` (gpt-image-2 forces high input fidelity automatically — the `input_fidelity` param is deliberately **not** sent). Gemini keeps exclusions inline; Recraft drops the photoreal cue.
|
|
4907
5621
|
> - New advisories: `VIDEO_RAW_FACE_KEYFRAME` (a Seedance clip animating a RAW ingested image — real-person 422 risk), `IMAGE_PROMPT_PROFILE_MISSING`, `VIDEO_PROMPT_PROFILE_MISSING`.
|
|
4908
5622
|
|
|
4909
|
-
> **Content-policy blocks are deterministic, not flaky
|
|
5623
|
+
> **Content-policy blocks are deterministic, not flaky.** ByteDance's Seedance rejects a first/last frame or reference that reads as a real-person likeness (even an AI-generated face), surfaced as `content_policy_blocked` (HTTP 422/`E005` "flagged as sensitive", **non-retryable on the same route**). A blocked clip is **not** moved to another model: one guided only by reference images is re-rendered on the same Seedance model through the fal route, which accepts faces (priced at that route, about twice the rate); one that starts from a frame fails with `content_policy_blocked`, nothing billed, and is re-run on `google/veo-3.1` (`person_generation: allow_adult`). To skip the wasted attempt, scaffold a photoreal-cast ad on Veo up front (`--real-face`, which also covers AI-generated photoreal faces), or make the source frame less photorealistic.
|
|
4910
5624
|
|
|
4911
5625
|
---
|
|
4912
5626
|
|
|
@@ -4925,7 +5639,7 @@ None.
|
|
|
4925
5639
|
| `audio` | audio | provider-determined (mp3 by default) |
|
|
4926
5640
|
| `timestamps` | json | `application/json` — present only when `with_timestamps: true` |
|
|
4927
5641
|
|
|
4928
|
-
**Cost** —
|
|
5642
|
+
**Cost** — ElevenLabs characters x $0.00022 (0.022 credits a character), rounded up to whole credits; the backend charges the same rate on the character count ElevenLabs reports.
|
|
4929
5643
|
|
|
4930
5644
|
###### Model: `elevenlabs/eleven_v3`
|
|
4931
5645
|
|
|
@@ -4957,7 +5671,7 @@ None.
|
|
|
4957
5671
|
|
|
4958
5672
|
##### `video_lipsync`
|
|
4959
5673
|
|
|
4960
|
-
Lip-sync a video to an audio track
|
|
5674
|
+
Lip-sync a video to an audio track. Model `replicate/sync-lipsync-2`, which runs Sync Labs `sync/lipsync-2` on Replicate.
|
|
4961
5675
|
|
|
4962
5676
|
**Inputs**
|
|
4963
5677
|
|
|
@@ -4970,7 +5684,7 @@ Lip-sync a video to an audio track via Sync Labs `sync/lipsync-2` (Replicate).
|
|
|
4970
5684
|
|
|
4971
5685
|
| Name | Type | Required | Notes |
|
|
4972
5686
|
|---|---|---|---|
|
|
4973
|
-
| `model` |
|
|
5687
|
+
| `model` | enum | yes | `"replicate/sync-lipsync-2"`. The legacy id `"fal/veed-lipsync"` is still accepted and runs the same model. |
|
|
4974
5688
|
|
|
4975
5689
|
**Outputs**
|
|
4976
5690
|
|
|
@@ -4978,7 +5692,7 @@ Lip-sync a video to an audio track via Sync Labs `sync/lipsync-2` (Replicate).
|
|
|
4978
5692
|
|---|---|---|
|
|
4979
5693
|
| `video` | video | `video/mp4` |
|
|
4980
5694
|
|
|
4981
|
-
**Cost** —
|
|
5695
|
+
**Cost** — $0.40 per minute of the longer input (40 credits for a minute, the quote's assumption when the clips carry no duration). Runs as a background job.
|
|
4982
5696
|
|
|
4983
5697
|
---
|
|
4984
5698
|
|
|
@@ -4996,8 +5710,8 @@ Strip a video's background → transparent alpha WebM (VP9) or MOV (ProRes 4444)
|
|
|
4996
5710
|
|
|
4997
5711
|
| Name | Type | Required | Default | Notes |
|
|
4998
5712
|
|---|---|---|---|---|
|
|
4999
|
-
| `model` |
|
|
5000
|
-
| `edge_refinement` | boolean | no | `true` |
|
|
5713
|
+
| `model` | enum | no | `"replicate/birefnet-video"` | The legacy id `fal/veed-video-background-removal` is still accepted and runs the same model. |
|
|
5714
|
+
| `edge_refinement` | boolean | no | `true` | Refines soft edges against background bleed (the model's `refine_fg`). Off bills the lower rate. |
|
|
5001
5715
|
| `output_codec` | enum | no | `"vp9"` | `vp9 \| h264` |
|
|
5002
5716
|
|
|
5003
5717
|
**Outputs**
|
|
@@ -5006,7 +5720,7 @@ Strip a video's background → transparent alpha WebM (VP9) or MOV (ProRes 4444)
|
|
|
5006
5720
|
|---|---|---|
|
|
5007
5721
|
| `video` | video | `video/webm` (VP9 + alpha) or `video/mp4` (H264 RGB + alpha) |
|
|
5008
5722
|
|
|
5009
|
-
**Cost** —
|
|
5723
|
+
**Cost** — $0.012 per 30 frames with edge refinement, $0.008 without, at 30fps (36 credits for the 30s the quote assumes). Runs as a background job.
|
|
5010
5724
|
|
|
5011
5725
|
---
|
|
5012
5726
|
|
|
@@ -5027,6 +5741,7 @@ Word-level transcription of a video OR a bare audio track (the `video` input acc
|
|
|
5027
5741
|
| `language` | string | no | auto | ISO 639-1, 2–8 chars (e.g. `en`, `es`) |
|
|
5028
5742
|
| `transcriber` | enum | no | `groq` | `groq` (Whisper) \| `deepgram` (Nova-3, adds the `rich` output) |
|
|
5029
5743
|
| `keyterms` | string[] | no | — | Up to 30 names the recogniser must not mangle — a product, a person, a phrase the script coins. The **company's own name is always applied server-side**, so it never needs listing here |
|
|
5744
|
+
| `script` | string | no | — | The words the track was asked to say. Given, the `transcript` output is the script's words, spelled as written, at the times the recogniser heard them: aligned locally (misheard words, fillers, accents and numbers said as words are all tolerated), never sent to the recogniser. `scaffold-ad` sets it on every spoken ad |
|
|
5030
5745
|
|
|
5031
5746
|
**Outputs**
|
|
5032
5747
|
|
|
@@ -5037,7 +5752,7 @@ Word-level transcription of a video OR a bare audio track (the `video` input acc
|
|
|
5037
5752
|
|
|
5038
5753
|
**Known names are spelled the way the brand spells them.** Every term (the company's own name, plus `keyterms`) is biased into the request — Deepgram `keyterm`, Whisper `prompt` — and its spelling is then enforced on the words that come back, because bias is a probability and a caption prints the mistake at full size over the ad. Verified against the take that shipped wrong: the same Spanish clip returns "Cubicap" bare and "Cubicup" with the name applied. Corrections are listed on the output's `brand_spelling_corrections` metadata, so a rewrite is visible rather than silent. A short name (under 6 characters) gets the bias only — at that length ordinary words sit one edit away, and rewriting those to protect a brand would corrupt the line. **Never patch a mishearing inside a caption composition**: that fix dies with that one creative.
|
|
5039
5754
|
|
|
5040
|
-
**Cost** —
|
|
5755
|
+
**Cost** — Groq: $0.04/hr, 10s minimum; Deepgram Nova-3: $0.0043/min. Quoted on one minute of audio; the backend charges the transcribed duration.
|
|
5041
5756
|
|
|
5042
5757
|
**Requirements** — `ffmpeg` on PATH for audio extraction (already in the E2B sandbox image). Without it the node still works but uploads the full video and Groq's 100 MB cap applies.
|
|
5043
5758
|
|
|
@@ -5168,7 +5883,7 @@ Place and mix several audio clips onto one timeline — a music bed plus timed v
|
|
|
5168
5883
|
|
|
5169
5884
|
##### `image_background_remove`
|
|
5170
5885
|
|
|
5171
|
-
Strip background → transparent PNG.
|
|
5886
|
+
Strip background → transparent PNG. Model `replicate/birefnet`, which runs `men1scus/birefnet` on Replicate. The model takes an image and a resolution only: it has no variants and returns no mask.
|
|
5172
5887
|
|
|
5173
5888
|
**Inputs**
|
|
5174
5889
|
|
|
@@ -5180,19 +5895,18 @@ Strip background → transparent PNG. Powered by `men1scus/birefnet` (Replicate)
|
|
|
5180
5895
|
|
|
5181
5896
|
| Name | Type | Required | Default | Notes |
|
|
5182
5897
|
|---|---|---|---|---|
|
|
5183
|
-
| `model` |
|
|
5184
|
-
| `model_variant` | enum | no | `"General Use (Light)"` | `General Use (Light) \| General Use (Heavy) \| Matting \| Portrait \| DIS \| HRSOD \| COD` |
|
|
5898
|
+
| `model` | enum | no | `"replicate/birefnet"` | The legacy id `fal/birefnet-v2` is still accepted and runs the same model. |
|
|
5185
5899
|
| `operating_resolution` | enum | no | — | `1024x1024 \| 2048x2048 \| 2304x2304` |
|
|
5186
|
-
| `
|
|
5900
|
+
| `model_variant` | enum | no | — | **Deprecated, ignored.** Accepted so older canvases still parse. |
|
|
5901
|
+
| `mask_only` | boolean | no | — | **Deprecated, ignored.** The cutout is always returned; there is no mask. |
|
|
5187
5902
|
|
|
5188
5903
|
**Outputs**
|
|
5189
5904
|
|
|
5190
5905
|
| Slot | Kind | MIME |
|
|
5191
5906
|
|---|---|---|
|
|
5192
5907
|
| `image` | image | `image/png` (with alpha) |
|
|
5193
|
-
| `mask` | image | `image/png` (only when `mask_only: true`) |
|
|
5194
5908
|
|
|
5195
|
-
**Cost** — 1 credit per call.
|
|
5909
|
+
**Cost** — 1 credit per call. Runs as a background job.
|
|
5196
5910
|
|
|
5197
5911
|
---
|
|
5198
5912
|
|
|
@@ -5215,7 +5929,7 @@ Generate music from prompt OR score an existing video clip.
|
|
|
5215
5929
|
| `audio` | audio | provider-determined |
|
|
5216
5930
|
| `timestamps` | json | `application/json` (optional) |
|
|
5217
5931
|
|
|
5218
|
-
**Cost** —
|
|
5932
|
+
**Cost** — $0.022 per second of audio (2.2 credits a second), 30s when `music_length_ms` is unset; rounded up to whole credits once, not per second. `elevenlabs/video-background-music-v1` runs as a background job.
|
|
5219
5933
|
|
|
5220
5934
|
###### Model: `elevenlabs/music-v1`
|
|
5221
5935
|
|
|
@@ -5273,7 +5987,7 @@ None.
|
|
|
5273
5987
|
|
|
5274
5988
|
Same as `tts`.
|
|
5275
5989
|
|
|
5276
|
-
**Cost** —
|
|
5990
|
+
**Cost** — total characters x $0.00022 (0.022 credits a character), rounded up to whole credits.
|
|
5277
5991
|
|
|
5278
5992
|
---
|
|
5279
5993
|
|
|
@@ -5302,7 +6016,7 @@ None.
|
|
|
5302
6016
|
|---|---|---|
|
|
5303
6017
|
| `audio` | audio | provider-determined |
|
|
5304
6018
|
|
|
5305
|
-
**Cost** —
|
|
6019
|
+
**Cost** — $0.022 per second of audio (2.2 credits a second), 5s when `duration_seconds` is unset.
|
|
5306
6020
|
|
|
5307
6021
|
---
|
|
5308
6022
|
|
|
@@ -5377,7 +6091,7 @@ Cache policies (via `--cache-policy`):
|
|
|
5377
6091
|
### Safety limits
|
|
5378
6092
|
|
|
5379
6093
|
- **Asset size cap: 2 GiB per asset.** Applies to every download (remote node outputs, `ingest` fetches, yt-dlp output) and to local files ingested via `source: "path"`. Oversized assets fail the node with a `file_too_large` / byte-limit error.
|
|
5380
|
-
- **Per-request cost cap: $
|
|
6094
|
+
- **Per-request cost cap: $20 of estimated provider cost for a `video_generate` clip, $10 for every other node.** The backend pre-estimates cost from the shared contract table (for a Seedance clip, on the dearest route it may take) and rejects a single execution above the cap with `cost_limit_exceeded`. No clip the registry allows exceeds $20; split long audio or long scripts into smaller nodes.
|
|
5381
6095
|
- **Retries never double-charge.** Every remote node execution carries a run-scoped idempotency key; if a request is retried after a transient failure, the backend replays the stored response instead of dispatching (and billing) the provider again. A fresh run (including `--cache-policy bypass`) always generates fresh.
|
|
5382
6096
|
|
|
5383
6097
|
---
|
|
@@ -5388,6 +6102,8 @@ Cache policies (via `--cache-policy`):
|
|
|
5388
6102
|
|
|
5389
6103
|
Parse + schema-check + per-node deep validation (composition `meta.json` is consulted for `hyperframe_*`). Prints a structured result with a per-node cost preview. Exits non-zero on issues. Never calls Convex, never spends credits.
|
|
5390
6104
|
|
|
6105
|
+
**The canvas file is brought up to date first.** Parts of a creative's canvas are projections of its blueprint: each video scene's frame and clip prompts (from `scenes/sNN.json`), a static ad's render prompts, judge criteria and type specimen (compiled from the ad spec in `prompt.json`), and the derived style file the frames read (`prompt.style.json`). `validate`, `run`, `critique` and `set-prompt` share one preflight that applies every projection and **writes the result back to the canvas file** (only when something changed; `canvas_updated` says so; `sync-definition`, which the file watcher runs over every creative, applies it without writing), so the file on disk, the graph the dashboard shows, the graph the critic scores and the graph that renders are the same. A scene edit that changes timing or the number of scenes is structural and still asks for a re-scaffold. Every preflight failure prints `{ ok: false, error: { code, message, fix? } }` and exits 2.
|
|
6106
|
+
|
|
5391
6107
|
```bash
|
|
5392
6108
|
$ baker canvas validate my-canvas.json
|
|
5393
6109
|
{
|
|
@@ -5401,7 +6117,7 @@ $ baker canvas validate my-canvas.json
|
|
|
5401
6117
|
}
|
|
5402
6118
|
```
|
|
5403
6119
|
|
|
5404
|
-
On error you get
|
|
6120
|
+
On error you get `{ ok: false, error: { code: "validation", issues } }`, each issue with `path`, `code`, `message`, and optional `did_you_mean`.
|
|
5405
6121
|
|
|
5406
6122
|
**Absolute asset paths are healed, not reported.** A `source: "path"` ingest (or a `hyperframe_render`
|
|
5407
6123
|
composition) written as an absolute path inside the workspace is rewritten to its canvas-relative form
|
|
@@ -5432,7 +6148,8 @@ Pre-spend creative critic — **advisory, never blocks**. Scores a scaffolded cr
|
|
|
5432
6148
|
so a weak spot gets fixed for free. It branches on modality: a **video** creative is scored on **hook strength**,
|
|
5433
6149
|
**sound-off first-frame legibility**, **retention risk**, and **mechanism clarity**; a **static ad** on **baked-text
|
|
5434
6150
|
legibility risk**, **identity grounding** (Rule 1 — every subject on a real reference), **brand-type fidelity** (a
|
|
5435
|
-
font specimen wired), and **mechanism clarity**.
|
|
6151
|
+
font specimen wired), and **mechanism clarity**. Scores the canvas after the shared preflight (so scene and copy
|
|
6152
|
+
edits are what gets scored); no generation, no Convex. Ground a video
|
|
5436
6153
|
hook against a proven pattern with `baker winning-ads hooks`.
|
|
5437
6154
|
|
|
5438
6155
|
```bash
|
|
@@ -5445,25 +6162,67 @@ $ baker canvas critique my-static-ad.json
|
|
|
5445
6162
|
"dimensions": [ { "dimension": "text_legibility_risk", "score": 1, "note": "2 copy block(s) — reads cleanly" }, … ] }
|
|
5446
6163
|
```
|
|
5447
6164
|
|
|
5448
|
-
#### `baker canvas
|
|
6165
|
+
#### `baker canvas viewer-check <slug>`
|
|
6166
|
+
|
|
6167
|
+
Would the person a static ad reaches understand it? A model plays that viewer — what they already know and
|
|
6168
|
+
where they scroll — and says, from `prompt.json`'s copy and scene alone, what is offered, to whom and what to do
|
|
6169
|
+
next; the decision model (Jev) compares the answer with `prompt.json`'s `message` (or, for a message that names no
|
|
6170
|
+
product, what the company sells), in whatever language each is written (`scripts/viewer-read-evals`). The ad fails
|
|
6171
|
+
when the viewer can only guess what is offered, is sure of something else, or, for a viewer who does not know the brand, when the words say they already did something with it (a
|
|
6172
|
+
cart, a trial, a donation, a registration); who it is for is reported but never fails an ad alone. The viewer is the `viewer` recorded in
|
|
6173
|
+
`_definition.md`, else the one its `targetAdSet` reaches (as `baker ads meta adsets --audience` reads it), else
|
|
6174
|
+
the persona and `awareness` from the Knowledge, else a cold stranger. Not understood → exit 2 with
|
|
6175
|
+
`viewer_check_failed` and a `fix` that starts with the one rewrite answering a missed offer (`Do this: make the biggest words say "<message>"`) and names what the viewer missed. After two failures of one ad in a chat turn the check no longer holds it back: the command answers `ok: true` with `understood: false` and what was missed, and `baker canvas run` renders it, printing the miss. The casting check is asked alongside the viewer. Answers are cached per copy, scene and viewer.
|
|
6176
|
+
Before either, a static ad whose scene quotes words, or names a price or a share, that the copy (or `scene_text`) does not hold is refused (`scene_words_not_in_copy`): the picture would print them and a correction round would erase them. An unreadable `prompt.json` names each wrong field in a line and shows that field's shape. `baker canvas run` makes the same check on a static ad before it spends (skipped for `--fix`, an autofix round, `--use-variant`, a re-layout and resumed runs, never for an edit of the words;
|
|
6177
|
+
`--skip-viewer-check` renders without it; a model outage lets the render through with a warning). `--full` adds
|
|
6178
|
+
the viewer's whole answer and how the viewer was chosen.
|
|
6179
|
+
|
|
6180
|
+
```bash
|
|
6181
|
+
$ baker canvas viewer-check club-whiteboard
|
|
6182
|
+
{ "ok": false, "error": { "code": "viewer_check_failed",
|
|
6183
|
+
"message": "Not rendered: a cold viewer who knows nothing about the brand yet, on automatic placements would not understand this ad.",
|
|
6184
|
+
"fix": "Seen by a cold viewer …, the words and picture alone do not say what is offered. …", "missed": [ … ] } }
|
|
6185
|
+
```
|
|
6186
|
+
|
|
6187
|
+
#### `baker canvas run <file.json> [flags]` / `baker canvas run --slug <slug> [flags]`
|
|
5449
6188
|
|
|
5450
6189
|
Validate, then execute the graph. Blocks until done. Logs one line per node. Returns `{ ok, run_id, outputs_dir, output, stats }` on stdout.
|
|
5451
6190
|
|
|
6191
|
+
`--slug <slug>` runs a creative by name instead of by file: its `src/creatives/<slug>/<slug>.canvas.json` when this workspace has it, else its files restored from the newest run that recorded a portable snapshot (sha-verified, into `src/creatives/<slug>/`), then the normal run flow. Local files are never overwritten unless `--force-remote` is passed. This replaces `baker canvas rerun <slug>`, which still works as a deprecated alias.
|
|
6192
|
+
|
|
6193
|
+
A completed render the review calls ready (`verdict` `ready` or `ready_with_notes` for a static ad, no blocking finding for a video) sets `status: rendered` in the creative's `_definition.md` (never moving a `published` or `archived` creative back); the output then carries `definition_status: "rendered"`.
|
|
6194
|
+
|
|
5452
6195
|
| Flag | Default | Effect |
|
|
5453
6196
|
|---|---|---|
|
|
5454
|
-
| `--
|
|
5455
|
-
| `--
|
|
6197
|
+
| `--slug <slug>` | none | Run a creative by name (see above). |
|
|
6198
|
+
| `--force-remote` | off | With `--slug`: restore the recorded files even over local ones. |
|
|
5456
6199
|
| `--run-id <id>` | auto ULID | Override the generated run id. |
|
|
5457
6200
|
| `--cache-policy <policy>` | `read_write` | `read_write`, `bypass`, or `read_only`. |
|
|
5458
|
-
| `--concurrency <n>` | `
|
|
5459
|
-
| `--
|
|
5460
|
-
| `--
|
|
6201
|
+
| `--concurrency <n>` / `--parallel <n>` | `8` (or `BAKER_CANVAS_CONCURRENCY`) | Max nodes executing at once within a layer. |
|
|
6202
|
+
| `--fresh` | off | Start a new run id instead of resuming an interrupted run of this canvas. |
|
|
6203
|
+
| `--regenerate <ids\|roles>` | none | Force these nodes, and everything downstream, fresh this run only. A role names every node carrying it: on a static ad `static_variants` (or its id `gen`) renders the variants again. An unknown name exits 2 (`unknown_regenerate_node`). |
|
|
6204
|
+
| `--fix "<what to change>"` | none | Static ad only: correct a finished version with one render that changes only what you say; every size is made again from it (see below). The same fix after it left its defect in place is refused (`no_progress`). |
|
|
6205
|
+
| `--use-variant <sha256>` | none | Static ad only: make every size again from another of a version's variants, the one the person chose; the only requests re-lay it out for each other size (see below). |
|
|
6206
|
+
| `--from <run_id>` | the last render | With `--fix` or `--use-variant`: the version to start from. |
|
|
6207
|
+
| `--autofix` / `--no-autofix` | on | Static ad only: when the review finds a blocking defect, make up to 3 more renders (`AUTO_FIX_ROUNDS`) that correct it before reporting (see below). `--no-autofix` reports after the first render. |
|
|
6208
|
+
| `--skip-viewer-check` | off | Static ad only: render without the viewer check (`baker canvas viewer-check`), when the person heard what the viewer would miss and wants the copy as it is. |
|
|
6209
|
+
| `--remote-cache <on\|off>` | `on` (or `BAKER_CANVAS_REMOTE_CACHE`) | Operator only. Company-scoped remote cache + durable asset persistence. |
|
|
5461
6210
|
| `--no-record` | records | Skip posting the durable run-history record (and its live progress). |
|
|
6211
|
+
| `--until frames` | none | Stop before the clips with the frames settled; the run is recorded as `awaiting_approval` (not failed, not interrupted) and resumes with `--run-id`. |
|
|
6212
|
+
| `--cache-dir <path>` | `<workspace root>/canvas/.cache` | Operator only. Where content-addressed bytes + cache index live. |
|
|
6213
|
+
| `--outputs-dir <path>` | `<workspace root>/canvas` | Operator only. Root for per-run output directories. |
|
|
6214
|
+
| `--keep-runs <n>` | off | Operator only. Prune old `r_*` run dirs after the run, keeping the N newest. |
|
|
5462
6215
|
|
|
5463
6216
|
**Run history streams live.** The run posts its plan (every node + its dependency edges) the moment validation passes, then re-posts a progress snapshot as each node starts and settles — the dashboard's creative workflow graph shows nodes flipping pending → running → done in real time, with each node's outputs attached as they land. A failed run keeps its per-node trail (what completed, what died). All best-effort: an unreachable backend never changes the run's outcome.
|
|
5464
6217
|
|
|
5465
6218
|
**Failures don't abandon sibling work.** Nodes in a layer run under the concurrency cap and every one **settles** — a failed clip no longer kills its in-flight siblings, whose finished results still land in the content-addressed cache. One failure re-throws as-is; several are reported together (each failed node named). Re-running `baker canvas run` resumes from the cache and re-executes **only** the failed nodes and their descendants — never hand-orchestrate per-node renders. **One render per canvas at a time:** a `run` of a canvas that another live process in the same workspace is still rendering (a backgrounded render, or a subagent's) is refused with `CONFLICT` instead of sharing its run, `--run-id` included — wait for it and use its result. Long `video_generate` clips execute as **backend jobs** (the CLI polls; a CDN/proxy timeout can no longer kill a generation mid-flight).
|
|
5466
6219
|
|
|
6220
|
+
**Resuming an interrupted run.** A long `baker canvas run` (multi-clip video) that is killed mid-render — session end, sandbox pause — leaves a marker under the outputs dir. The next `baker canvas run` of the same canvas automatically **resumes** that run: it reuses the run id so still-running billed jobs re-attach instead of being abandoned and re-billed, and completed nodes come from the cache. A live render keeps its marker's heartbeat fresh, so a second `run` of the same canvas is refused while it is alive (checked with `kill(pid, 0)` plus the heartbeat, on Linux and macOS alike). Resume also works from a **different workspace or a fresh sandbox**: when no local marker survives, the run history is consulted for a resumable run of the same source (canvas, `prompt.json`, scenes, compositions and formats), and it is adopted when the backend says it may be: interrupted, stopped at a checkpoint, or a `running` row whose keepalive is older than the backend's staleness threshold. Ctrl-C / SIGTERM aborts gracefully — no new nodes dispatch, a resumable snapshot is flushed, and the marker survives. A clean completion (or a handled failure) clears the marker, so a normal re-run starts a fresh generation. Force a new run with `--fresh`, or pin a specific run with `--run-id <id>` (also the escape hatch to adopt a run that is reported as concurrently live). Independent same-layer nodes (e.g. video clips) fan out in parallel up to `--parallel`/`--concurrency` (default 8; env `BAKER_CANVAS_CONCURRENCY`).
|
|
6221
|
+
|
|
6222
|
+
**Static ads: the run corrects a blocking defect itself.** When the review of a static ad finds a blocking defect, `baker canvas run` makes up to 3 more renders (`AUTO_FIX_ROUNDS` in `static-autofix.ts`) before it reports, correcting each defect at the layer that causes it: words or a logo where an interface covers them (Reels, Stories) or the frame cuts them are **placed and extended** (`withExtendedSizes`): a `static_place` node reads where everything sits and shrinks the ad (to 60% at most) into the size's safe area, and a `static_extend_merge` node has the ad's own model paint the margins that leaves and keeps its painting as one picture across the whole frame, laying the ad's own pixels back only over the words, logo and buttons the placing read (padded and feathered, so no edge of the shrunk ad shows; the whole ad is laid back, as before, when nothing was read or the painting moved the words), so the model is never asked to move anything (a size already inside its area is passed on with nothing painted or billed, and one whose words sit too far out for a shrink to clear, or that the placing found inside its area while the review still blocks it, is laid out again instead, never placed the same way twice; a button is placed by its shape, its words grown by 0.7 of their height on every side, as the review judges it); a defect the spec asked for (a second subject, stars with no named source, a concept that does not read) and any defect of a native screen corrects the spec first (`static-spec-repair.ts`: a model rewrites `prompt.json` for those findings alone, every line of the copy kept word for word), then draws the ad again from it (a native screen redraws free); a picture wrong as a whole or in three places or more is made again from its brief, told what the last one got wrong, but only in a round with another after it unless the brief caused the defect; anything else on the picture is a `--fix` of the chosen picture for its own findings (`correctionOf`: the words to erase, the line to re-letter or add), naming what the last edit was already asked for, while each re-laid size the review flagged follows as an edit of its own last layout (`withSizesFollowingFix`), keeping what an earlier round fixed in it, and a size it found nothing blocking on is delivered as it was (`keep`, nothing in it made again or placed); anything else on a re-laid size alone edits just that size from its own last layout (`withRelayEdit`); sizes that do not show the same ad (`SIZES:`) are always the re-laid ones' fault, since every size follows the chosen picture, so they are laid out again to show exactly what it shows, never the picture edited to match them. A size a round did not touch (its file the same byte for byte) keeps the round before's verdict (`carryUnchanged`) and is not read again: the review reads only the sizes the round changed. Each correction is told what earlier rounds fixed so it keeps it right (`keptRules`), and starts from the best attempt so far: one that came out with more blocking defects than an earlier attempt is set aside and the earlier one corrected instead (`correctFrom`). Each round is one render (a fix, or one request per re-laid size) and prints `[autofix] round N of 3: …` on stdout; the final JSON's hints start with "Corrected automatically before this result, …" and its review is of the corrected version. Blocking findings left after the rounds keep the hints below. A round that would only repeat the correction that just left its defect in place (a chosen option, `--use-variant`, is only ever corrected) is not run: that version is reported as it is. `--no-autofix` reports after the first render.
|
|
6223
|
+
|
|
6224
|
+
**Static ads: correct a version, or use another of its variants.** Correct a finished version with `--fix "<what to change>"`: one render takes that version (the last one, or `--from <run_id>`) as its first reference and changes only what you said, keeping the picture, the layout and every other word, and every size is made again from the corrected picture. **A correction grows on top of the version it starts from**: the run executes that version's own graph (its variants, the prefilter and pick, every earlier correction) with each of those steps pinned to the outputs that version recorded (free, never re-rendered, whatever `prompt.json` says now), then appends `edit_1` (the picture as its first reference, framed for the master as a variant is) and makes the sizes from it; a second correction appends `edit_2` on `edit_1`. `--use-variant` wires the chosen variant from the version's own `gen` as a `chosen` step, and a `--regenerate adapt_<size>` keeps the version's picture the same way. So the run record, and the dashboard's workflow of each version, show the whole history: options, pick, edits, sizes. The canvas file is never rewritten. Every static run keeps the graph it executed in the asset store (`graphUrl` on its run record, up to 4 MB), so a version rendered in another workspace ("Edit creative …" from a new chat, `--from <run_id>`) grows the same way, its steps pinned to the outputs its run record holds; only a run recorded before graphs were kept is corrected from its picture by URL. **A `prompt.json` changed only in its words, their positions or its colours** since the version shown is made the same way, as an edit of that version (`[edit]` in the output, the instruction read off the change); three new options are asked for only with `--regenerate static_variants`. A `--fix` whose defect is still blocking afterwards is recorded, and asking for it again with the same words exits 2 with `no_progress`. When every blocking finding is on re-laid-out sizes, the first hint says to lay out those sizes again (`--regenerate adapt_<size>`) before any `--fix`. Every static run's hints say what it cost in credits and what for. There is no render limit per creative. Use another variant with `--use-variant <sha256>` (`--from <run_id>`, default the last render): when the person asks in the chat for another of a version's variants over the judge's pick, every size is made again from that picture, and the only requests re-lay it out for each other size; `--regenerate adapt_9x16` lays out one size again from the same chosen picture when only that size came out wrong (after a `--fix` or `--use-variant`, from that corrected or chosen picture, so the size never brings back the uncorrected one; `--from <run_id>` names another version).
|
|
6225
|
+
|
|
5467
6226
|
#### `baker canvas scaffold-video <video> [flags]`
|
|
5468
6227
|
|
|
5469
6228
|
Turn a reference video into a **runnable, self-validated reproduction canvas** in one command — the video counterpart of `scaffold-static-ad`. The `<video>` positional is a **local path OR an http(s) URL** (a `baker winning-ads` `media_url`, a library URL, any reel link) — a URL is downloaded for you (no manual `curl` first), so pass `--slug`/`--out` with it to give the canvas a home. It runs **billed passes** up front:
|
|
@@ -5477,7 +6236,7 @@ Before the deconstruct it runs a **local shot-cut pass** on the source file with
|
|
|
5477
6236
|
|
|
5478
6237
|
A shot longer than the video model's per-clip ceiling (Seedance's 15s, passed as `video_deconstruct`'s `max_clip_s`) is split into equal **continuation sub-scenes** that share their splice boundary exactly — so a long shot is reproduced in **full** (no truncation) and joins seamlessly. Each sub-scene carries `continues_previous`.
|
|
5479
6238
|
|
|
5480
|
-
It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are cast
|
|
6239
|
+
It then scaffolds the full pipeline like an **editing timeline**: each clip gets a **static-ad-grade start AND end keyframe** (`image_generate`, each with its **own `params.prompt`** regenerated from that scene's `scenes/sNN.json` — edit the scene file to change that frame, never the node prompt by hand; the slim `prompt.style.json` wired as the **shared `target_blueprint`** style reference, plus a per-element reference legend). Each keyframe is **fully recast** to the dropped `el_*` reference images. The original extracted frame is kept LAST as a **pure composition anchor** (framing / camera angle / shot size / pose) only in an ad with **nobody in it** — no deconstructed cast and no recurring person. In any ad with people, no generated frame takes a source frame as a reference: an image model copies the face of whoever is in its references, whatever the prompt says, and which scenes a person is in is itself a model's guess — so framing is carried by the frame prompt instead. **People are invented or cast**: a person with no avatar gets a generated candid portrait from their description (an `image_generate` node at the `el_*` slot, fused into a sheet), so every frame shows one realistic invented person; with `--avatar <handle>` each recurring person grounds on that avatar's identity sheet, used as-is. Never a found photo. Both keyframes feed `video_generate` (`first_frame`+`last_frame`, so Seedance interpolates real in-shot motion; ultra-detailed motion brief; duration snapped to the nearest allowed clip length). Every keyframe grounds **only on its own extracted frame + `el_*` slots** — no reference to any other generated frame — so all images render **in parallel** (no cascade). Source-frame URLs are **deduped** (each ingested once). `--frames reuse` wires the real source frame straight in — only on a video with nobody in it: when the deconstruct found a cast or a recurring person, reuse is refused (`frames_reuse_refused`) and every frame is generated, so the source's actors never reach the ad.
|
|
5481
6240
|
|
|
5482
6241
|
**Composited scenes (split-screen / picture-in-picture / keyed presenter).** Real ads aren't always one full-frame shot — a frame can be **persistently divided** (b-roll on top, a presenter talking on the bottom) or **layer a presenter** over background footage (boxed in a corner, or green-screen keyed). The deconstruct now reports this per scene as `scene.composition` (`layout: split_screen | pip | keyed_overlay`, with one `region` per stream — each its own clean-plate frame + motion brief, the talking-head region flagged `is_presenter`). The scaffold reproduces a composited scene by building **one clip per region** (`s<i>_r0_*`, `s<i>_r1_*`, …) and compositing them with ffmpeg: a split-screen `vstack`/`hstack` (stack direction read from the region **panels**, so a top/bottom split always stacks vertically), or a picture-in-picture `overlay` of the presenter inset at its corner. A **keyed** presenter is first cut to transparency by `video_background_remove` (`s<i>_key`), then overlaid. The presenter region carries the native lip-synced voice; b-roll/render panels stay silent. To change a layout, edit `composition` in that scene's `scenes/sNN.json` and re-scaffold, or hand-edit the `s<i>_composite` ffmpeg args. Plain full-frame scenes (the default) are unaffected.
|
|
5483
6242
|
|
|
@@ -5505,7 +6264,7 @@ It then scaffolds the full pipeline like an **editing timeline**: each clip gets
|
|
|
5505
6264
|
|
|
5506
6265
|
**Timing-faithful clip + extract (no overlap).** Each phrase clip is generated to its **coverage window** (the deconstruct's real scene/line timing, capped at the gateway-safe ~10s ceiling) and its converted voice is extracted to the **spoken window** (pause to pause) — *not* padded to a word-count estimate. Padding past the window was what ran the voice the clip's whole length and overlapped the next phrase; trusting the deconstruct's timing keeps consecutive phrases back-to-back and lets Seedance pace the quoted text to fit. `metadata.video.talking_scenes` records each phrase's `scene_s` vs `est_speech_s`; on top of that the scaffold flags any scene whose estimated speech overruns its window by more than ~1.3× as **`metadata.todo.overstuffed_scenes`** (also in the stdout checklist) — a loud advisory to shorten the copy or lengthen the scene before rendering, since an over-stuffed line pushes the picture off the audio timeline. It similarly flags **`oversize_scenes`** — a single scene whose own footage exceeds the gateway-safe ~10s clip ceiling (a b-roll shot or one-shot monologue). The phrase splitter only breaks at scene boundaries, so it can't shrink a single over-long scene; its clip would 524 at the gateway, so the advisory tells you to split that scene into two before rendering.
|
|
5507
6266
|
|
|
5508
|
-
**Timeline-accurate picture — ONE clock.** Seedance can't render under 4s, so each clip is generated at the smallest allowed duration ≥ the scene length and **cut back to the exact scene duration inside the spine's own per-input filter chain** (no standalone trim nodes — one fewer re-encode per scene, and the graph stays a third smaller). Real screenshots/graphics loop fit-padded, generated stills loop fill-cropped, on the same chains. The spine is the ONE clock: every voice, SFX, music, and overlay placement is mapped onto where each scene's picture actually sits in the butted concat (not the reference video's own timestamps, which can carry dead air the picture doesn't reproduce), the audio mix's `total_ms` is pinned to the spine length, and every concat input is normalized to one raster clock (`yuv420p`, 30fps, square pixels, shared timebase) so a generated clip's unadvertised frame rate can never stretch the picture off the audio. The contract is stamped as `metadata.video.timeline` and `baker canvas validate` re-proves it from the live node params after any hand edit. Frames are also prompted as **clean text-free plates** (no baked captions/lower-thirds/tickers/logos-as-text) so the overlay layer is the single source of on-screen text.
|
|
6267
|
+
**Timeline-accurate picture — ONE clock.** Seedance can't render under 4s, so each clip is generated at the smallest allowed duration ≥ the scene length and **cut back to the exact scene duration inside the spine's own per-input filter chain** (no standalone trim nodes — one fewer re-encode per scene, and the graph stays a third smaller). Real screenshots/graphics loop fit-padded, generated stills loop fill-cropped, on the same chains. The spine is the ONE clock: every voice, SFX, music, and overlay placement is mapped onto where each scene's picture actually sits in the butted concat (not the reference video's own timestamps, which can carry dead air the picture doesn't reproduce), the audio mix's `total_ms` is pinned to the spine length, and every concat input is normalized to one raster clock (`yuv420p`, 30fps, square pixels, shared timebase) so a generated clip's unadvertised frame rate can never stretch the picture off the audio. The contract is stamped as `metadata.video.timeline` and `baker canvas validate` re-proves it from the live node params after any hand edit. The spine is a `video_concat` node (each clip's `duration_s`, trim or hold is a param, never argv) and the final mux an `av_mux`, and every scene node carries `tags` (its scene, region and role), which is what the validator reads. Frames are also prompted as **clean text-free plates** (no baked captions/lower-thirds/tickers/logos-as-text) so the overlay layer is the single source of on-screen text.
|
|
5509
6268
|
|
|
5510
6269
|
**Scene transitions.** When the deconstruct flags a boundary as `fade`/`whip`/`zoom`/`dissolve`/`swipe` (`scene.transition_out`), the spine reproduces it as an ffmpeg **`xfade`** instead of a hard cut; plain `cut`/`match_cut` stay hard cuts. The overlap is consumed from **extra generated footage** (each transitioning clip is trimmed to `scene_s + transition` and the xfade `offset` lands on the cumulative scene start), so the total length still equals the sum of the scene lengths — the picture stays exactly on the audio timeline.
|
|
5511
6270
|
|
|
@@ -5537,12 +6296,14 @@ baker canvas run ./reference-ad.video.canvas.json
|
|
|
5537
6296
|
| `--max-scenes <n>` | all source scenes | **Cost lever that reduces fidelity** — caps the deconstruct, MERGING away every scene beyond the cap (fewer cuts, lost beats). Prints a warning when set; omit it to reproduce every scene. |
|
|
5538
6297
|
| `--language <code>` | auto | Transcript/dialogue language hint (e.g. `fr`, `en`). |
|
|
5539
6298
|
| `--focus <text>` | — | Known provenance/emphasis to ground the deconstruct. |
|
|
5540
|
-
| `--deconstruct-model <id>` | `~google/gemini-
|
|
6299
|
+
| `--deconstruct-model <id>` | `~google/gemini-flash-latest` | Override the `video_deconstruct` model. |
|
|
5541
6300
|
| `--select-model <id>` | `~google/gemini-flash-latest` | Override the element-selection `text_generate` model. |
|
|
5542
|
-
| `--image-model <id>` | `openai/gpt-image-2` | Override the per-frame `image_generate` model (defaults to the strongest, matching `scaffold-static-ad`). |
|
|
6301
|
+
| `--image-model <id>` | `openai/gpt-image-2.5-sunburst` | Override the per-frame `image_generate` model (defaults to the strongest, matching `scaffold-static-ad`). |
|
|
5543
6302
|
| `--video-model <id>` | scored router | Override the `video_generate` model **and skip the router**. Curated roster: `google/gemini-omni-1.1-flash` (**the default video model** — native audio + reference images, `360p`\|`720p`\|`1080p`\|`4k` at ~$0.034\|$0.101\|$0.152\|$0.304 per second, `16:9`\|`9:16`, `duration` 3–10s and 10s when unset), `bytedance/seedance-2.0` (workhorse), `google/veo-3.1` (cine ceiling + real-face), `google/veo-3.1-fast` (cheap Veo), `kwaivgi/kling-v3.0-pro` (motion-transfer). |
|
|
5544
6303
|
| `--real-face` | auto-detected | Force the Veo route for a photoreal human presenter (dodges the ByteDance real-person filter). **Usually unnecessary** — the scaffold auto-detects a photoreal on-camera person/animal cast from the deconstruct and routes it to Veo on its own; set this only to force it when the cast isn't detected. Covers AI-generated photoreal faces too, not just real ones. |
|
|
5545
6304
|
| `--motion-transfer` | off | Motion driven from a reference / hyper-dynamic → the router picks Kling. |
|
|
6305
|
+
|
|
6306
|
+
A model flag naming a model the registry does not have for that node fails the command (`unknown_model`, listing the models it does have) before anything is billed; it no longer falls back silently to another model.
|
|
5546
6307
|
| `--identity` | off | Same character/product across clips → the router picks the Seedance workhorse. |
|
|
5547
6308
|
| `--budget <tier>` | `standard` | Router cost posture: `economy` (cheap Veo-fast tier) / `standard` / `premium`. |
|
|
5548
6309
|
| `--avatar <handles>` | — | Cast company avatars as the reference's people, comma-separated in the order they appear (`marta` or `marta,joan`). Each recurring person grounds on that avatar's identity sheet and takes its subject description; a `same_as` look of them uses the same sheet. Resolved before anything is billed — a handle that is not `ready` fails the command. |
|
|
@@ -5565,23 +6326,40 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
|
|
|
5565
6326
|
|
|
5566
6327
|
#### `baker canvas scaffold-static-ad <image> [flags]`
|
|
5567
6328
|
|
|
5568
|
-
|
|
6329
|
+
**New-creative limit:** a chat makes at most 10 new creatives. The eleventh `scaffold-static-ad`, `scaffold-ad` or `scaffold-video` with a new `--slug` exits 2 with `CREATIVE_LIMIT` before any billed pass. Re-scaffolding a creative already made in the chat, or one whose `_definition.md` existed before, does not count.
|
|
6330
|
+
|
|
6331
|
+
`<image>` is a **local path or an `http(s)` URL** (e.g. a presigned S3 link from ad-dna), and it is **optional when `--source-ad-id` is given**: the library's picture is used. With `--slug` the source is localized to durable, content-addressed storage before anything bills, so the emitted canvas stays runnable in any sandbox; without a slug a URL is passed straight into the `original` ingest node (a presigned one expires). For the URL case, `--out` defaults to the current directory — pass `--out` to scaffold two URL sources into distinct files, or the second run overwrites the first.
|
|
6332
|
+
|
|
6333
|
+
Turn a source/inspiration image into a **runnable, self-validated static-ad canvas** — the static counterpart of `scaffold-video`. It runs **billed Gemini passes** up front:
|
|
6334
|
+
|
|
6335
|
+
**A library ad is read once for every company.** With `--source-ad-id` and the library's own picture (no `<image>`, `--context`, `--describe-model` or `--select-model`), both passes below come from `POST /api/ad-library/reference-read`: the backend reads the ad from Ad DNA's picture, grounded in its full DNA, and keeps the read for every company. The first build on an ad pays for it (~40 s); every later one, in any company, gets it at once and free. When the shared read cannot be had, the passes run here as before.
|
|
5569
6336
|
|
|
5570
|
-
|
|
6337
|
+
1. **`image_describe`** (`~google/gemini-flash-latest`) — reverse-engineers the image (layout by what fills each region, copy verbatim, typefaces, capture in words, the `mechanism` and the `audience` it is built for) and writes it next to the canvas as **`prompt.json`**, an **ad spec** (`"schema": "baker-static-ad/3"`; `/2` still reads) you rewrite into the ad you want: `viewer` (who sees it and what they already know), `message` (the one thing it tells them, never printed), `copy` (each line with `lettering`: `in_scene`, written on something in the picture (`on`, `look`), or `designed`, set in the brand's face), `scene` (`description`, `look`: photo | designed | illustration, `capture` in words, `mood`, `layout`), `brand`, `colors` (each tied to an object, from the brand palette), `references` (one picture each, labelled by role: `PERSON_1`, `ANIMAL_1`, `PRODUCT_1`, `BADGE_1`, `PROP_1`), `logo` (and `logo_position`), `third_party_brands` (other brands the offer is about, a car brand on a car loan: `name`, `what`, and `logo` naming the `BADGE_n` reference of its logo; their logos may appear), an optional `direction`, the `mechanism` to rebuild for our brand, and in `/3`: `viewer.market` (`country`, `language`, `currency`), `brand_presence` (`standard` | `native`: a native disguise carries the logo as its sender and no big button), `recognition` (the kind of product a stranger must take it for at a glance, never the format), `native` (the ad is a screen people know, drawn exactly from HTML at every size by the free `static_native` node instead of painted: `x_post` (a post or a thread, with `reply_to`), `whatsapp`, `imessage`, `trustpilot`, `google_reviews`, `notes`, `slack`; on a 9:16 the app is drawn full screen as a phone shows it: status bar and app header pinned at the top, the reply bar and tab bar (or a keyboard under a chat or note) pinned at the bottom, the posts or messages as large as the clear band holds, and the frame's headline, caption and logo a card in the brand's colours under the app's header; a wall of reviews (`trustpilot`, `google_reviews`) takes up to 16 real reviews, the first two (`FEATURED_REVIEWS`) the ad's copy set whole in the safe area and the rest filling the whole frame behind them as large, sharp cards, turned and cut by its edges, because a wall wins on how many there are; a review longer than 160 characters is shown cut at a word with "…", and that cut text is the copy the review reads back; a chat (`whatsapp`, `imessage`) on a 9:16 never shrinks below about 62% of the phone scale: its earliest messages scroll up under the header, as the app does, the node's optional `scrolled` output lists the copy lines scrolled off that size, and the run review expects those lines nowhere on it; a logo file whose drawing runs off its own edge is set aside for a whole one, and a square mark where a wordmark goes is drawn larger; the run review never flags the app's own interface words (status bar, keyboard) as under Reels; those words ("Today", "Verified", "Post your reply") are printed in the viewer's language: Spanish, Portuguese and English as checked against the apps, any other written once by a text model and cached per language (`scripts/interface-words-evals`), English when it cannot; the image model renders only the screen's `photos`, each a plain phone photo of ordinary people of the market; adding the block switches an existing ad on its next run, `--fix` does not apply, an invented brand handle or a rating the Knowledge does not hold (either decimal mark) is refused as `native_screen_invalid`, and a photo of ours goes in as its `image`: a native photo takes no `avatar`, since nothing would cast it), a role on each third-party brand (`partner` | `proof` | `category_signal`), a reference's `image` (wired as a picture of ours) and a person's `avatar` (the company avatar cast), and `concept` (`why_it_wins` in five layers, `load_bearing` elements with `have`/`adapt`/`missing` and `ours`, `verdict` `use`|`adapt`|`reject`, `transfer_note`; seeded from `baker winning-ads fit`). Every render keeps the load-bearing elements; `validate`/`run` refuse a `reject` or a `missing` element with nothing named (`concept_does_not_fit`) and a rating, or a promised price, share, multiple or time, the Knowledge does not hold with that unit, or a superlative it does not make (`proof_without_source`), in any language or several in one ad: a text model reads each claim of the copy and of the Knowledge as a kind, a number and a unit (cached by text, billed with the next run), code compares them exactly, and the decision model (Jev) judges a superlative; with no model, every number the copy writes in digits must be in the Knowledge (`claim-check.ts`, eval: `scripts/claim-check-evals`); a line citing a review platform gets its real badge wired (Trustpilot, G2 and the like by name; Google, Google Play or the App Store when the line shows stars or a score, or the decision model reads it as citing them for reviews in any language, `scripts/review-citation-evals`). **You never write a model prompt**: every render prompt is compiled from this file for the model it runs on (short labelled sections for gpt-image, sentences with reasons for Gemini; each line quoted once; pictures numbered with one role each; colours from the brand; realism as capture cues; the safe area from its one owner; constraints last), on every `validate` and `run`, and shown on its node. A `prompt.json` written before the ad spec (the describe blueprint) is read through an adapter and keeps working. The source's own read (`source`, and the library's `reference`) stays as bookkeeping and is never sent to a model.
|
|
6338
|
+
2. **element selection** (`~google/gemini-flash-latest`) — picks the **main, identity-critical** elements (the brand logo, a showcased product, a trust badge) **plus any foreground/hero person or animal** — the emotional focal point — even a generic one, because a free-generated face/muzzle reads as AI and grows artifacts; the emotional hero always gets a real-reference slot. When the advertiser's logo appears in **more than one lockup** (a square/icon **mark** and a horizontal **wordmark**), each is emitted as its **own** element (e.g. `LOGO_MARK`, `LOGO_WORDMARK`). The describe pass also records the ad's **typography** (each typeface's classification, a best-guess family, and its weight/case), which seeds the spec's `brand.typeface`. Background extras are dropped. Each element becomes a `references` entry in the spec, named by its role and naming the canvas slot it fills, so the file says which slot grounds which subject.
|
|
5571
6339
|
|
|
5572
|
-
|
|
5573
|
-
2. **element selection** (`~google/gemini-flash-latest`) — picks the **main, identity-critical** elements (the brand logo, a showcased product, a trust badge) **plus any foreground/hero person or animal** — the emotional focal point — even a generic one, because a free-generated face/muzzle reads as AI and grows artifacts; the emotional hero always gets a real-reference slot. When the advertiser's logo appears in **more than one lockup** (a square/icon **mark** and a horizontal **wordmark**), each is emitted as its **own** element (e.g. `LOGO_MARK`, `LOGO_WORDMARK`) so you drop the right file in each slot instead of stretching one logo to cover both. The describe pass also records the ad's **typography** under a `fonts` block (each typeface's classification, a best-guess family, and its weight/case) so you know exactly what to drop at the brand-font slot. Background extras are dropped. Each element is stamped back onto its blueprint entry as a `reference_image` label so the JSON self-documents which slot grounds which subject.
|
|
5574
|
-
3. **global layout** (`~google/gemini-flash-latest`) — produces a structured `layout` block in `prompt.json`: the column/row grid, each region's `x_pct`/`y_pct` bounds, panel splits, background/shape, and every text block's relative size/weight/case/alignment. This is what gives the generator a precise composition to rebuild.
|
|
6340
|
+
It then scaffolds a canvas on which **the image model designs the finished ad**, holding only what `prompt.json` holds; no element is mandatory:
|
|
5575
6341
|
|
|
5576
|
-
|
|
6342
|
+
- Up to **three variants** (`--takes`, default 3) render on the model the router picks for the concept (today `openai/gpt-image-2.5-sunburst` at quality `xhigh`, about 45 s a picture against 80 s at `max`; `--gen-model` overrides), each a finished ad: the picture and every word of the copy. Each variant is **its own request** (tagged `static_variant_call`), run in parallel on its own direction (the scene as written; another picture of the same message, by the scene's look; another line leading, named by number so no word changes), and a `collect` tagged `static_variants` gathers them, on every model: one request with `n` costs the same (`n` is billed per picture) but gave fewer distinct options, so the graph never uses it. The brief quotes every line of `copy` verbatim and asks for each once; it is compiled again from `prompt.json` on every `validate` and `run`, so an edit re-keys exactly the renders it touches. The brand's **typeface** is shown to the model as a `font_specimen` set on the `designed` lines: a file in `src/brand/fonts/` (via `src/brand/tokens.json` when present), else the heading family `tokens.json` or `BRAND.md` names, fetched as a web font file; an `in_scene` line keeps its own lettering only on the real packaging, signage or screen its `on` names (handwriting on post-its, whiteboards or chalkboards, and a line naming no surface, are set as designed type). Each line's `role` sets its size: a price or stat (else the headline) leads, the `cta` is a large solid button, fine print is smallest. **The logo is optional**: the real file in `src/brand/logos/` goes to the model as a reference to reproduce once, at least a fifth of the frame wide, where it contrasts or on a small brand-colour plate, only when `prompt.json` has `"logo": true` (an older blueprint: an `advertiser_brand` entry in `brands_logos`) and the file exists; no logo is never an error, never a slot to fill and never a stand-in. Setting it false drops the logo on the next run; adding one to a canvas scaffolded without the file needs the file in `src/brand/logos/` and a re-scaffold. `prompt.json` also carries a **`brand`** block (ground, ink, accent, CTA and palette, from `src/brand/tokens.json` roles or `BRAND.md`); a colour outside it never reaches the model. The brief describes a complete ad at the master's shape and asks only for its edge margin (see below). `baker canvas set-prompt` refuses these nodes and names `prompt.json`.
|
|
6343
|
+
- A **`static_prefilter`** node (tagged `variant_prefilter`) drops variants that fail the deterministic checks before the judge sees them, never all of them; its **judge** (`judge_model`, `judge_prompt`) compares the rest pairwise against the compiled brief when the checks tie (one variant passes through unjudged). A canvas scaffolded with a separate `image_select` judge (tagged `pick`) has it folded into the prefilter on its next run.
|
|
6344
|
+
- **Every placement format is made from the chosen picture, and no size is a crop or a paste of another.** The pick goes to exact pixels as the master (an `imagemagick` node tagged `static_master`) at the lead size: the 4:5 when a feed size is delivered, else the square, else the first portrait size, rendered at the model's nearest ratio (3:4 on GPT Image) and filled to Meta's pixels. It is delivered as it is. Every other size (9:16, 1:1, 16:9, …) is **re-laid out** from the master by one request (`image_generate` node `adapt_<size>`, tagged `static_adapt_format`, on the router's `relayout` model, GPT Image 2.5 at quality `xhigh`): the master, the variants' own product and people pictures and the logo file as references and a brief compiled from `prompt.json` for that frame on every run (`static-adapt.ts`): the exact lines and their hierarchy, the same people (same face, same clothes), product and label, logo and none the chosen ad does not show, the words and the button re-scaled for the frame, the frame stated as zones inside its safe area, never the master shrunk onto a background, set as a square or cut. A `size_<size>` node (`static_size`) brings it to Meta's exact pixels (1:1 at 1080×1080; a landscape size the same way). Every 9:16 Reels or Stories covers then passes a free `static_safe_fit` node (`safe_<size>`), which shrinks and shifts the picture just enough (to 85% at most) to bring a word or the logo a few points past the band back inside it; a layout further out passes unchanged and the run lays it out again. The 9:16 is never extended instead (the 4:5 set whole in the clear band, its scene continued to the frame) nor set over its own blurred copy: rated in October 2026, the first showed the ad small inside a continued scene ("text super tiny") and the second "looks bad". The master shape is one decision in the code (`DEFAULT_MASTER_SHAPE`: `relayout`; the `9x16_band` and `4x5_adapt` shapes, kept for the bench, make the sizes by cutting or reframing the master, and the person rating them rejected every such way; a canvas from the retired band master, which extended a square master into the 9:16, is upgraded to the re-layout graph on its next run). Placing the logo file on every size instead of having the model draw it (`logo_composite` nodes) is a scaffold option, off until it is proven on the bench.
|
|
6345
|
+
- Readers go by these roles, never by node ids: the run review, `--fix`, `--use-variant`, `--regenerate static_variants`, and the dashboard. A static ad scaffolded before this graph (`ad_a`/`ad_b`/`ad_c`, `pick`, re-lays `ad_4x5`/`ad_9x16`, `fit_4x5`) is **upgraded in place** on its next `validate` or `run`: prompt.json, every asset slot, the model and the formats are kept, the render part becomes this graph, and the creative is not reported as changed by the upgrade. Its old runs stay readable.
|
|
6346
|
+
- Detected elements are **never slots to fill**: the source's people, products, animals and badges are its world. The original image stays on the canvas as the record of the source; when `prompt.json` keeps its concept (`concept.verdict` `use` or `adapt`) the first renders are shown it **for its structure only**, and otherwise it never reaches a render. Only the people cast from a company Avatar and the brand's logo become pictures on the canvas; everything else is described in words (a picture of ours is added through a reference's `image`). A **person** with no avatar needs no slot and no casting: the render draws an invented, realistic person from the element's description. With `--avatar <handle>` (for an actor who recurs across ads) the slot ingests the Avatar's identity sheet and `prompt.json` describes that person in the Avatar's own words. A reference's `avatar` names who was cast and draws no face on its own: its face reaches the render only with a picture behind it (the `--avatar` slot, or the identity sheet URL from `baker avatars get <handle>` as the reference's `image`).
|
|
5577
6347
|
|
|
5578
|
-
|
|
6348
|
+
The canvas is validated before it is written. stdout returns `{ ok, canvas_path, prompt_path, models, aspect_ratio, placements, output_formats, stats, checklist }`; the **checklist** carries `summary` (what a run does: candidates, judge, type, logo, formats), `font`, `logo`, `in_scene_text`, `formats` and every real asset to drop in.
|
|
5579
6349
|
|
|
5580
|
-
The
|
|
6350
|
+
**Placement formats.** The canvas delivers every size where the ad runs, each at its exact size, collected under `final` with the format as its label. `--platforms meta,tiktok,google_display,…` (platforms or placement ids from the registry in `@baker/canvas-contract` `AD_PLACEMENTS`: meta, google_display, google_pmax, google_demandgen, google_search, youtube, linkedin, tiktok, x, reddit, pinterest, snapchat, chatgpt) delivers every size their placements require, each kept clear of its own placement's interface (a TikTok 9:16 of TikTok's buttons and caption), and lists fixed banners (`728x90`, …) in the report as `banners`, never made from the picture (a destination of banners alone is refused with `invalid_platforms` before anything bills). A 9:16 for another platform keeps clear of its interface even when `--target-adset` narrows Meta's placements. Without it the set is derived from `--platform` (`meta` → 4:5 + 9:16, `google` → 1:1 + 4:5 + 16:9, `linkedin` → 1:1, `tiktok` → 9:16, `youtube` → 16:9); override it with `--placements <preset>` or `--placements none` for a single format. The report's `aspect_ratio` is the shape the variants really render at (the model's nearest to the master's format), and `format_plan` names the master and how each size is made from it (`master`, `crop` with its rectangle, or `adapt`).
|
|
5581
6351
|
|
|
5582
|
-
|
|
6352
|
+
**Who sees the ad.** `--target-adset <id>` names the Meta ad set the ad will run in. Before anything is billed, the viewer its targeting reaches is read exactly as `baker ads meta adsets --audience` reads it (`temperature`, what they already `knows`, `placements`) and written to `_definition.md` as `targetAdSet` and `viewer` (an existing definition keeps every other field). The sizes, the brief and the prefilter keep clear of that viewer's placements (`staticSafeArea`; `automatic`, a bare `facebook`/`instagram` or no placements is Advantage+, Reels included), and every `baker canvas run` checks the ad against that viewer. With no ad account, give the viewer from the Knowledge: `--viewer-temperature cold|warm|hot [--viewer-knows brand,offer,product] [--viewer-placements <placement,…>]`, recorded with `source: knowledge`; next to `--target-adset` they correct the read, and its `reasons` say so. A bad value exits 2 (`invalid_viewer`) before any billed pass; an ad set that cannot be read never stops the scaffold (a hint says why, and the sizes keep clear of every placement). With neither flag, a `viewer` (or `targetAdSet`) already in `_definition.md` is used as it is. stdout carries `target_adset`, `viewer` and a `checklist.viewer` line.
|
|
6353
|
+
|
|
6354
|
+
**After a run** the CLI reads the finished ad back on what makes a static win, not a checklist of elements: every delivered image's pixel shape against its format; **every line of `prompt.json`'s copy (in-scene and designed alike) read back with its box by a box reader and compared** (a line more than 2% off, missing, or joined by words nobody asked for is blocking; case and punctuation fold, as does a capital I read as an l (one stroke in a sans-serif), accents do not; words the picture adds are blocking only when set at the copy's usual size or making a claim (a figure, a price, an offer), and otherwise a note, as a screen's code, a poster or a mug in the scene, except a price (a currency sign beside a figure) the copy does not hold, blocking at any size and blurred by the correction, the document it is on kept; every render is asked for no readable prices on a bill, quote, receipt, screen or price tag beyond the copy; a copy line is "on the ad twice" only when most of its words repeat, never a word or two echoed on a prop; on a native screen, which code typesets from the spec, a line read differently is the reader's error and only a missing one counts); **the ad's own copy and its logo measured against the format's safe area** (`staticSafeArea`, the one owner of those areas; `must-show.ts` decides what is measured: never what the scene prints, nor a copy line printed again in the scene while the line itself sits clear). On a 9:16, which runs in Stories and Reels alike, what Stories' header or bottom bar covers (and any other app's interface named) is said as "where Stories and Reels cover it" ("where Reels covers it" for an ad set that runs only in Reels) and blocks for the logo, the button, the headline, the figure and the offer, while a supporting line (body, caption, label, small print) there is a note; what only Reels' taller footer covers is one note, and only for the lead line, since Stories shows it and the 9:16 is laid out in Stories' band; words merely close to where an app draws are no finding. The steps that place a size clear of an interface (`static_safe_fit`, `static_place`) are told the same copy (`prompt.json`'s lines, their roles, and the brand's name and the logo's words, a wordmark read close to them counting as the logo) and move the ad for those alone, a supporting line included only where the frame cuts it; on a feed image such as a 4:5, only a design margin, so words close to the edge are a warning and only words the frame cuts are blocking); a copy line written over two lines (`"15%\noff"`) is matched part by part wherever the reader finds each; designed type in a colour far from the brand palette is a warning; supporting lines too small to read on a phone are one note per size; the main line set smaller than a supporting one is a note, the brand's wordmark never counted as part of a line that starts with its name; the logo set twice is a note, a far smaller mark printed on something in the scene (a hoodie, a bag) is not. When the copy names a real person ("Lautaro Suarez, CTO at Vibiz", "田中 美咲、Acme 創業者"; read by a text model in any language, cached by copy, `scripts/named-people-evals`), the run casts them as themselves before it renders (`baker avatars research --create`, their avatar's sheet added to `prompt.json` `references` as a `PERSON_n`), and a face shown as a named person the ad has no photo of is blocking. When the reader cannot answer, the vision pass transcribes the copy instead and judges the safe zones by eye. Then one vision pass for clipping, overlaps, artifacts, one dominant idea, the brand with the logo covered and the concept's mechanisms (each finding typed by the model with the item it answers, what it is about, what is wrong and the edges that cut it, which the grading and the automatic correction read instead of its words), with every format shown a second time at phone-feed thumbnail size (240px wide, same call): a lead message unreadable at that size is blocking. A missing logo, CTA or copy is never a finding; a logo that is drawn and broken is. stdout carries `verdict` (`ready` | `ready_with_notes` | `needs_fixes` | `not_reviewed`), `judge` (the per-candidate scores) and hints that end with what the run made; the findings are stored on the run as its `review`. `needs_fixes` means the ad is not done even though every node completed, and the run is recorded with `presentable: false` so it is never shown as a version; every fix is an edit to `prompt.json` and a re-run (wrong words: the lines in `copy`; wrong picture: the `scene`; a bad roll: `--regenerate static_variants`; one size wrong while the chosen picture is right: `--regenerate adapt_<size>`, which re-lays it out, named in the hints); a blocking defect is first corrected by the run itself (`--autofix`, above). The checks rank the variants before the pick and the judge only breaks an exact tie, comparing pairwise against the compiled brief, knowing the brand and the viewer; every variant stays on offer to the person in Baker. Within one chat turn an ad gets two runs you start (each with its automatic corrections) and about 12 minutes in all: a correction round that would take it past that is not started (`[autofix] not started`), the version shown as it stands, and no render starts once it is spent; once the ad is `ready` or `ready_with_notes` only a correction of it starts (`--fix`, `--regenerate adapt_<size>`, or an edit of its words or colours), within the same two runs, and anything else is refused (`CONFLICT`, exit 2) with what to show instead; the person's next message starts a new turn (`turn-renders.ts`). When a defect still stands after the automatic rounds, the hints say to fix it with a `--fix`, then by changing the spec or the approach, and if it still cannot be fixed to show the best version and name what is left in one plain sentence, without a question.
|
|
6355
|
+
|
|
6356
|
+
The render prompt is **model-aware**: each model's profile (`src/engine/lib/prompt-profiles.ts`) sets its shape, word budget, picture order and request params. gpt-image gets short labelled sections with the constraints last; Gemini gets sentences that give the reason and a positive closing list of the only words allowed. A profile change, or a new model on the router (`src/engine/lib/image-model-router.ts`), is re-run on the static bench (`scripts/creative-evals`, `--job static`) before it ships. Run `baker canvas critique` on the scaffolded canvas for an advisory read before you spend.
|
|
6357
|
+
|
|
6358
|
+
**A reference from the winners library goes in by id.** `--source-ad-id <ad_id> --source-platform meta|linkedin|tiktok|google [--source-advertiser-id <id>]` (the ids beside the ad in `baker winning-ads search`) fetches the library's structured read of that ad, the same one `baker winning-ads content <ad_id> --full` returns, and writes it into `prompt.json` as a `reference` block: `{ ad_id, platform, advertiser, advertiser_id, format_pattern, layout: { composition, focal_subject, aspect_ratio, background, palette_mood, colors, production, aesthetic, cta_in_image, markers }, message: { mechanism, awareness, sophistication, angle, hook_archetype, pain_points, desires, value_props, objections_handled, proof, risk_reversal }, offer: { offer_type, pricing_model, entry_price, no_credit_card, guarantee, guarantee_days, urgency, bonus }, text_blocks: [{ text, role, position }], copy: { primary_text, headline, cta } }` (filled fields only). The describe pass still reads the pixels but is told that read as grounding, so the two cannot disagree on the container or the mechanism; when it names no `winning_mechanisms`, the library's container + mechanism seeds the list. The source ids (`sourceAdId`, `sourcePlatform`, `sourceAdvertiserId`, and `sourceAdvertiser` from the library when `--advertiser` is absent) are written to `_definition.md` and synced to the creative at scaffold time. A library read that fails warns, records the ids anyway and scaffolds from the picture alone (`hints`: `REFERENCE NOT READ`); with no picture either, the command exits 2 (`source_unavailable`). `baker canvas sync-definition` re-reads the three ids from `_definition.md` on every sync.
|
|
5583
6359
|
|
|
5584
6360
|
```bash
|
|
6361
|
+
baker canvas scaffold-static-ad --source-ad-id adg_123 --source-platform meta --slug spring-offer --platform meta --target-adset 120249211706990208
|
|
6362
|
+
# → the library's picture and read, made for the viewer that ad set reaches; writes src/creatives/spring-offer/{spring-offer.canvas.json, prompt.json, _definition.md}
|
|
5585
6363
|
baker canvas scaffold-static-ad ./reference-ad.png --context "competitor ad for <brand>, <category>, <market>"
|
|
5586
6364
|
# → writes ./static-ad.canvas.json + ./prompt.json
|
|
5587
6365
|
# edit prompt.json into your ad + replace each [TODO] ingest path with a real file, then:
|
|
@@ -5591,45 +6369,44 @@ baker canvas run ./static-ad.canvas.json
|
|
|
5591
6369
|
|
|
5592
6370
|
| Flag | Default | Effect |
|
|
5593
6371
|
|---|---|---|
|
|
5594
|
-
| `--
|
|
6372
|
+
| `--source-ad-id <ad_id>` | — | The winners-library ad this creative adapts. Fetches its structured read into `prompt.json` `reference`, grounds the describe pass with it, and records the source on the creative. Makes `<image>` optional. |
|
|
6373
|
+
| `--source-platform <platform>` | `meta` | Platform the library ad is on: `meta`\|`linkedin`\|`tiktok`. Another value exits 2 (`invalid_source_platform`). |
|
|
6374
|
+
| `--source-advertiser-id <id>` | the library's | The reference's library advertiser id, recorded as `sourceAdvertiserId`. |
|
|
6375
|
+
| `--context <text>` | — | Known provenance (advertiser, category, market) to ground the describe. Appended to the library's read when `--source-ad-id` is given. |
|
|
5595
6376
|
| `--out <path>` | `<image-dir>/static-ad.canvas.json` (cwd when `<image>` is a URL) | Where to write the canvas (`prompt.json` is written alongside). |
|
|
5596
|
-
| `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. With a slug, the reference image is
|
|
6377
|
+
| `--slug <slug>` | — | Creative slug (lowercase kebab): writes the canvas to `src/creatives/<slug>/<slug>.canvas.json` — the repo convention that attaches every run to the creative's dashboard generation history. `--out` wins over `--slug`. With a slug, the reference image is localized to durable storage and normalized to a model-safe format (SVG/AVIF/HEIC/… → PNG), so the canvas ingests a stable URL instead of an expiring one. |
|
|
5597
6378
|
| `--describe-model <id>` | registry default (`~google/gemini-pro-latest`) | Override the `image_describe` model. |
|
|
5598
6379
|
| `--select-model <id>` | registry default (`~google/gemini-flash-latest`) | Override the element-selection `text_generate` model. |
|
|
5599
|
-
| `--
|
|
5600
|
-
| `--
|
|
5601
|
-
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set
|
|
5602
|
-
| `--
|
|
5603
|
-
| `--
|
|
5604
|
-
| `--
|
|
5605
|
-
| `--skip-actor-sheets` | off | Ground each animal hero on its lone dropped photo instead of a generated multi-view reference sheet. |
|
|
6380
|
+
| `--gen-model <id>` | the router's pick for the concept | Override the render model. An unknown model id in any `--*-model` flag fails the command (`unknown_model`) before anything is billed. |
|
|
6381
|
+
| `--takes <n>` | `3` | Variants of the finished ad rendered (one request each) and judged (1-3); `1` skips the judge. |
|
|
6382
|
+
| `--platform <channel>` | `meta` | `_definition.md` channel **and** the default placement set (`meta`\|`google`\|`linkedin`\|`tiktok`\|`youtube`\|`x`\|`other`; `x`/`other` deliver one format). |
|
|
6383
|
+
| `--platforms <list>` | none | Where the ad runs, comma-separated platforms or placement ids: every size they require, each kept clear of its own interface. Wins over `--placements`. Banners alone are refused. |
|
|
6384
|
+
| `--placements <preset\|none>` | derived from `--platform` | An older fixed set: `meta_feed`\|`meta_feed_stories`\|`meta_all`\|`linkedin`\|`google_demandgen`\|`tiktok`\|`youtube`, or `none` for a single base ad. |
|
|
6385
|
+
| `--aspect <ratio>` | inferred from the image, else `9:16` | Force the lead format (it leads the placement set when the set has it). |
|
|
5606
6386
|
| `--avatar <handles>` | — | Cast company avatars as the image's people, comma-separated in the order they were detected (`marta` or `marta,joan`). Each person slot grounds on that avatar's identity sheet and takes its subject description. Checked before any billed pass; an unknown, not-ready or sheetless avatar fails with code `avatar`. A handle with no person to play is reported in `hints` as `NOT CAST`. |
|
|
5607
6387
|
|
|
5608
|
-
Scaffolding runs (and bills) the two vision passes; **running** the result generates
|
|
6388
|
+
Scaffolding runs (and bills) the two vision passes; **running** the result generates billed images. `baker canvas validate` does not check that the `[TODO]` paths exist — supply the real files before `run`.
|
|
5609
6389
|
|
|
5610
|
-
|
|
6390
|
+
Size adaptations move and proportionally scale the existing composition first, keeping its background treatment, including intentional flat colour. Generation, adaptation, correction and series briefs preserve plausible anatomy, device proportions, scale, perspective and support, and reflow text while preserving letter shapes. Recognizable app controls remain native; marketing overlays stay visibly separate. Shared guidance records applicable intent and constraints briefly in `_definition.md`, then carries them into `prompt.json` (Markdown is not a render input). Direct briefs need no invented winner or fixed template; reference adaptations reason through the mechanism, transfer hypothesis and what must survive. `creatives make` returns this preparation hint before rendering. Choose the represented role and inspect existing Avatars before creating a new actor; a real founder or customer keeps their verified identity.
|
|
5611
6391
|
|
|
5612
|
-
|
|
6392
|
+
During continuations, the accepted image's screen content takes priority over geometry references unless explicitly changed. Laptop scenes receive guidance for a real laptop and consistent display, keyboard and hinge perspective.
|
|
5613
6393
|
|
|
5614
|
-
|
|
6394
|
+
#### `baker canvas rerun <slug> [flags]` (deprecated)
|
|
5615
6395
|
|
|
5616
|
-
|
|
5617
|
-
| --- | --- | --- |
|
|
5618
|
-
| `--force-remote` | off | Overwrite local files whose content differs from the snapshot (otherwise a conflict aborts with the differing paths). |
|
|
5619
|
-
| `--fresh` | off | Start a new run id instead of resuming an interrupted one. |
|
|
5620
|
-
| `--regenerate <ids>` | — | Same as `canvas run --regenerate`. |
|
|
5621
|
-
| `--concurrency <n>` | — | Same as `canvas run --concurrency`. |
|
|
6396
|
+
Now `baker canvas run --slug <slug>`; `rerun` stays as a hidden alias for one release and prints a deprecation note. Every `canvas run` of a creative records a portable snapshot (the canvas JSON plus the local files it ingests: prompt blueprints, composition dirs, reference images) in durable storage, so `run --slug` can rebuild `src/creatives/<slug>/` in a workspace that never had it. A snapshot whose files are exactly the last one uploaded for the creative is reused without a request, and a file the store already holds is not uploaded again. Files the snapshot could not include (missing at run time, or oversized) are listed as warnings.
|
|
5622
6397
|
|
|
5623
6398
|
```bash
|
|
5624
|
-
baker canvas
|
|
5625
|
-
baker canvas
|
|
6399
|
+
baker canvas run --slug spring-offer
|
|
6400
|
+
baker canvas run --slug spring-offer --force-remote --regenerate gen
|
|
5626
6401
|
```
|
|
5627
6402
|
|
|
5628
|
-
|
|
6403
|
+
#### `baker canvas stale`
|
|
6404
|
+
|
|
6405
|
+
Lists the creatives edited since their last render in this workspace, each with the command to re-render it: `{ ok, data: { stale: [{ slug, canvasPath, renderedRunId, sourceSha }] }, hints? }`. "Edited" is one content fingerprint of what the models would be sent: the canvas as it renders (after the copy, logo and scene projections), `prompt.json` without its bookkeeping (`reference`, `source_context`, `notes`), every `scenes/sNN.json`, every local composition directory the canvas renders, and the `formats` in `_definition.md`, so a change counts however it was made and reformatting a JSON file is not a change. The fingerprint is prefixed `v2:`; a render fingerprinted by the older rule (a bare sha) is never reported stale, so upgrading the CLI re-renders nothing. A completed `run` records the fingerprint it drew in `canvas/.rendered/<slug>.json`, and a render attempt of the current source is not asked for again until the source moves. Creatives never rendered in this workspace, and ones with an unfilled `[TODO]` asset slot, are not listed. Read-only, offline, free. The scaffold's turn-end reminder and its `validate-creative-renders` check read this list (at Publish it is a warning: Publish ships the last version the person was shown and never renders); the same fingerprint is what `sync-definition` and `run` post as `sourceSha`.
|
|
5629
6406
|
|
|
5630
6407
|
#### `baker canvas inspect <run_id> [--thumbnails]`
|
|
5631
6408
|
|
|
5632
|
-
One-page summary of a
|
|
6409
|
+
One-page summary of a run: per-node duration + cache status, list of files in the run dir, optional video thumbnails (start/middle/end frames extracted via ffmpeg). When the run has no folder in this workspace (an older version, or a run from another chat), it is read from the company's run history instead (`source: "run_history"`): status, stats, per-node results and the durable URL of every output.
|
|
5633
6410
|
|
|
5634
6411
|
```bash
|
|
5635
6412
|
baker canvas inspect r_01JXYZ...
|
|
@@ -5691,14 +6468,20 @@ baker canvas catalog --category audio --full
|
|
|
5691
6468
|
// Path source — an absolute path is used as-is, and so resolves only on the machine that wrote it.
|
|
5692
6469
|
// Keep canvases portable: creative canvases are validated for this (`absolute-reference`).
|
|
5693
6470
|
{ "id": "src", "type": "ingest", "params": { "source": "path", "path": "/abs/work/clip.mp4", "expect": "video" } }
|
|
6471
|
+
|
|
6472
|
+
// URL source — an earlier output, by its durable canvas-assets URL: the same bytes and sha back
|
|
6473
|
+
{ "id": "src", "type": "ingest", "params": { "source": "url", "url": "https://media.withbaker.com/canvas-assets/5b/5b50…c672.mp3", "expect": "audio" } }
|
|
5694
6474
|
```
|
|
5695
6475
|
|
|
6476
|
+
**Never point `path` into `canvas/`.** `canvas/.cache/` and `canvas/r_<id>/` are the engine's own working folder and are never committed, so a canvas that reads from them renders only where it was written. `canvas validate` and `canvas run` rewrite such a path to the durable URL of the same bytes (reported under `normalized_paths`), keeping the file first if the store does not hold it yet.
|
|
6477
|
+
|
|
5696
6478
|
**Strategy resolution** (deterministic from `source` + `expect` + URL extension for text):
|
|
5697
6479
|
|
|
5698
6480
|
| `source` | `expect` | URL / path | Strategy |
|
|
5699
6481
|
|----------|---------------|----------------------------------|-----------------|
|
|
5700
6482
|
| path | any | any | `local_file` — read from disk, sniff mime, upload to R2 via presign |
|
|
5701
|
-
| url |
|
|
6483
|
+
| url | any | `…/canvas-assets/<xx>/<sha>.<ext>` | `canvas_asset` — the stored asset, verbatim, its sha checked |
|
|
6484
|
+
| url | video / audio | any other | `yt_dlp` (works on platform URLs *and* direct file URLs via the generic extractor; verifies TLS against the system bundle) |
|
|
5702
6485
|
| url | image / json / font | any | `direct_fetch` (HTTP GET) |
|
|
5703
6486
|
| url | text | ends in `.txt` or `.md` | `direct_fetch` |
|
|
5704
6487
|
| url | text | anything else (HTML, PDF, …) | `firecrawl` `/v2/scrape` (extracted markdown — HTML/PDF made readable) |
|
|
@@ -6099,23 +6882,95 @@ import {
|
|
|
6099
6882
|
|
|
6100
6883
|
## Creatives
|
|
6101
6884
|
|
|
6102
|
-
|
|
6885
|
+
Save a finished canvas render as a Baker creative: a static image (PNG, JPG, WebP) or a finished video (MP4, MOV, WebM). Choose and save the best finished version without waiting for the user to choose, including in the turn that rendered it. Saving pins the version as the agent's choice without recording user approval or putting an ad live. An image uploads to the Baker image library (tagged `creative`); a video uploads to the Baker video library and is saved once it plays (bounded at 3 minutes). A creative record is created/updated, and the command prints the creative reference JSON the dashboard renders in chat.
|
|
6103
6886
|
|
|
6104
6887
|
```bash
|
|
6105
|
-
baker creatives publish
|
|
6106
|
-
--slug spring-offer-4x5 --run-id r_01JXYZ... \
|
|
6888
|
+
baker creatives publish --title "Spring Offer 4x5" \
|
|
6889
|
+
--slug spring-offer-4x5 --run-id r_01JXYZ... --output 4:5 \
|
|
6107
6890
|
--source-reference-url "https://www.facebook.com/ads/library/?id=..."
|
|
6891
|
+
# or from a local file
|
|
6892
|
+
baker creatives publish ./canvas/<run_id>/<final>.png --title "Spring Offer 4x5" --slug spring-offer-4x5 --run-id r_01JXYZ...
|
|
6893
|
+
# a finished video render (needs the file and --slug)
|
|
6894
|
+
baker creatives publish ./canvas/<run_id>/final.mp4 --title "Spring Offer Reel" \
|
|
6895
|
+
--slug spring-offer-reel --run-id r_01JXYZ...
|
|
6108
6896
|
```
|
|
6109
6897
|
|
|
6110
6898
|
| Flag | Effect |
|
|
6111
6899
|
|---|---|
|
|
6900
|
+
| `<file>` | The image or video. For an image it is optional with `--run-id`: the run's own saved output (from its run record, sha-verified) is used instead, so a workspace that never had the run's folder can publish it. |
|
|
6112
6901
|
| `--title <text>` | Required. Human title for the creative. |
|
|
6113
|
-
| `--slug <slug>` | Creative slug (`src/creatives/<slug>/`)
|
|
6114
|
-
| `--run-id <r_…>` | Pins the
|
|
6902
|
+
| `--slug <slug>` | Creative slug (`src/creatives/<slug>/`): attaches the image or video to that creative's row, marks it `published`. Required for a video. With `--run-id` an image publish also sets `status: published` in the creative's `_definition.md` (`definitionStatus` in the output). |
|
|
6903
|
+
| `--run-id <r_…>` | Pins the finished generation from the creative's run history as the saved one. |
|
|
6904
|
+
| `--output <label\|slot\|n>` | With `--run-id` and no file: which of the run's images to save when it made several (a label such as `4:5`, a slot, or a 1-based number). |
|
|
6115
6905
|
| `--source-reference-url <url>` | Original reference ad URL, recorded on the creative. |
|
|
6116
|
-
| `--context <text>` | Optional describe context for the uploaded image
|
|
6906
|
+
| `--context <text>` | Optional describe context for the uploaded image or video. |
|
|
6907
|
+
|
|
6908
|
+
An image prints `{ ok, data: { creativeId, imageId, reference } }`. A video prints `{ ok, data: { creativeId, videoId, reference }, hints }`, where `reference.asset.kind` is `video` (with `videoId`, `muxPlaybackId`, the poster as `thumbnailUrl`, size and duration) and `hints` names the next step to put it in a Meta ad: `baker ads meta media upload --kind video --video-id <videoId>`.
|
|
6909
|
+
|
|
6910
|
+
Without `--slug` an image behaves as before (one creative record per published image). With `--slug` it upserts the repo-convention row — the same one the dashboard's Creatives tab and the `src/creatives/{slug}/` folder describe — so publish, repo sync, and run history all land on a single record regardless of order.
|
|
6117
6911
|
|
|
6118
|
-
|
|
6912
|
+
### `baker creatives performance`
|
|
6913
|
+
|
|
6914
|
+
How each published creative is doing in the live Meta ads that show it. A creative is linked to an ad when a chat's publish uploads its image or video to Meta and creates an ad with it (or swaps an existing ad onto it); the results are read live from Meta.
|
|
6915
|
+
|
|
6916
|
+
```bash
|
|
6917
|
+
baker creatives performance # every creative in an ad, last 30 days
|
|
6918
|
+
baker creatives performance --slug spring-offer # one creative
|
|
6919
|
+
baker creatives performance --days 14 --full # + ids, last-7-day figures, one row per ad
|
|
6920
|
+
```
|
|
6921
|
+
|
|
6922
|
+
| Flag | Effect |
|
|
6923
|
+
|---|---|
|
|
6924
|
+
| `--slug <slug>` | Only this creative. |
|
|
6925
|
+
| `--days <n>` | Window ending today, 1-90 (default 30). |
|
|
6926
|
+
| `--full` | Adds creative, image and video ids, result type, days live, last-7-day CTR and cost per result, a video's watch funnel (`video`: 3-second views, ThruPlays, 25/50/75/100% watched), and one row per ad. |
|
|
6927
|
+
|
|
6928
|
+
Each creative reports `spend`, `impressions`, `ctr` (percent), `cpc`, `results`, `costPerResult`, `frequency`, a `fatigue` verdict (`fresh`, `fading`, `fatigued`, `dead`, `too_early`, `no_delivery`) with its reason, and `definition`: the angle, hook, mechanism, dominantSlot, persona, awareness, hypothesis and reference fields read from the local `src/creatives/<slug>/_definition.md`. A video creative also reports `hookRate` (percent of impressions that watched 3 seconds) and `holdRate` (percent of those that reached a ThruPlay); a static one carries neither. `hints` name worn-out creatives, videos whose hook rate is under 25% (the first 3 seconds need new hooks) or whose hold rate is under 30% behind a working hook (the body loses people), the angle, mechanism and dominant slot with the lowest cost per result (each ranked once two or more values have results), and unreadable ad accounts.
|
|
6929
|
+
|
|
6930
|
+
### `baker creatives feedback [--slug <slug>] [--full]`
|
|
6931
|
+
|
|
6932
|
+
What people said about creative versions in their chats (an approval, or a rejection with reasons and a note; keeping a version counts as an approval). Compact by default: approved and rejected counts, the top rejection reasons, what approved versions share (format, platform, option) and the 8 latest verdicts, plus `hints[]` saying what to do differently. `--slug` narrows to one creative; `--full` returns every verdict (up to 200) with whole notes.
|
|
6933
|
+
|
|
6934
|
+
```bash
|
|
6935
|
+
baker creatives feedback
|
|
6936
|
+
baker creatives feedback --slug spring-offer --full
|
|
6937
|
+
```
|
|
6938
|
+
|
|
6939
|
+
Each entry carries its `source`: `review` (the review buttons Baker had before, kept on older verdicts), `selection` (choosing or saving the version) or `chat` (what the person wrote in a chat, recorded with `feedback record`).
|
|
6940
|
+
|
|
6941
|
+
When the person asks in the chat for another variant of a version, make every size from it with `baker canvas run --slug <slug> --use-variant <sha256> --from <runId>`; Baker has no control to choose one.
|
|
6942
|
+
|
|
6943
|
+
### `baker creatives feedback record`
|
|
6944
|
+
|
|
6945
|
+
Record what the person said about one version in a chat: an approval, or a rejection with reasons and their own words. It is stored as the verdict of the person who wrote the chat's latest message (`recordedFor: "sender"`), or of the chat itself when nobody has written in it (`"chat"`). One verdict per person per version or option: a later one replaces theirs. Must run from a chat.
|
|
6946
|
+
|
|
6947
|
+
```bash
|
|
6948
|
+
baker creatives feedback record --slug spring-offer --verdict rejected \
|
|
6949
|
+
--reasons text_mistakes,weak_hook --note "the price is 49 not 59"
|
|
6950
|
+
baker creatives feedback record --slug spring-offer --run r_01K… --option 2 --verdict approved
|
|
6951
|
+
```
|
|
6952
|
+
|
|
6953
|
+
| Flag | Effect |
|
|
6954
|
+
|---|---|
|
|
6955
|
+
| `--slug <slug>` | Required. The creative (`src/creatives/<slug>/`). |
|
|
6956
|
+
| `--run <r_…>` | The version. Left out, the one the creative shows now (its last render in this workspace). |
|
|
6957
|
+
| `--verdict approved\|rejected` | Required. |
|
|
6958
|
+
| `--option <o>` | One option of a version that made several: its label, its number (`2`) or its slot. |
|
|
6959
|
+
| `--reasons <a,b>` | Comma-separated: `text_mistakes`, `logo_or_brand_wrong`, `weak_hook`, `wrong_message`, `looks_fake`, `not_our_style`, `voice_or_sound`, `pacing`, `other`. The two video reasons: `voice_or_sound` (the voiceover, music or mix) and `pacing` (too slow, too fast, the hook lands late). A rejection needs reasons or a note. |
|
|
6960
|
+
| `--note <text>` | The person's own words (stored up to 1000 characters). |
|
|
6961
|
+
|
|
6962
|
+
After a rejection, `hints[]` restates what to change, starting from that version.
|
|
6963
|
+
|
|
6964
|
+
### `baker creatives select`
|
|
6965
|
+
|
|
6966
|
+
Record the user's choice of a creative version, or only some of its options, the way choosing it in Baker does: the creative shows that version as the chosen one, and the choice records the chat sender's approval (`source: "selection"`). Naming options replaces the earlier pick. It does not add the image to the library; that is `baker creatives publish`, which pins the agent's saved version without recording user approval. Use `select` only for a choice the user actually made; autonomous saving uses `publish`.
|
|
6967
|
+
|
|
6968
|
+
```bash
|
|
6969
|
+
baker creatives select --slug spring-offer # the version it shows now
|
|
6970
|
+
baker creatives select --slug spring-offer --run r_01K… --options "beach,2"
|
|
6971
|
+
```
|
|
6972
|
+
|
|
6973
|
+
Prints `{ ok, data: { creative, runId, kept?, options, approvalRecorded }, hints }`, where `options` lists every option the version offers.
|
|
6119
6974
|
|
|
6120
6975
|
## Landing Quality (`baker landing`)
|
|
6121
6976
|
|
|
@@ -6132,7 +6987,9 @@ baker landing inspiration code <section id> # the standalone bundle
|
|
|
6132
6987
|
baker landing inspiration page <source id> # a whole page as a section sequence
|
|
6133
6988
|
baker landing inspiration sequences "b2b saas pricing page" --scope all # what follows what, across many pages
|
|
6134
6989
|
baker landing inspiration add https://linear.app --note "client likes this density"
|
|
6990
|
+
baker landing inspiration add https://linear.app --refresh # read a saved page again — the site changed
|
|
6135
6991
|
baker landing inspiration favorites # what this company has saved
|
|
6992
|
+
baker landing inspiration retry-failed # study again every saved page that failed
|
|
6136
6993
|
baker landing inspiration scrape <url> --out .baker/<dir> # capture any page now, synchronously
|
|
6137
6994
|
```
|
|
6138
6995
|
|
|
@@ -6146,6 +7003,7 @@ baker landing inspiration scrape <url> --out .baker/<dir> # capture any page n
|
|
|
6146
7003
|
- **Compact by default, `--full` when you have chosen something.** Every list command returns what you need to *pick* a row; the 600-character "why it works" paragraph, the classification facets and the markup ride behind `--full` on `search`, `view`, `page`, `favorites` and `code`. A default `favorites` used to cost more than a default search for a question — "what does this client keep saving?" — that the facets alone answer.
|
|
6147
7004
|
- **`sequences` answers the ordering question search structurally can't.** Search ranks individual sections and caps each site at three, so a page's top-to-bottom order never survives its results, and `page` recovers exactly one page's order. `sequences` counts adjacency across many pages at once and returns `openers`, `transitions` (`{ from, to, count }`, most frequent first) and `closers`, filtered by `--scope --domain --section-type --limit`. It returns page **ids only** — `page` stays the one way to read a single page — and always reports `pages_considered` alongside `pages_returned`, because a frequency over an unstated denominator is not evidence. `--full` counts over every matching page and prints the whole table.
|
|
6148
7005
|
- **`page_id` on every search row** is what `page <id>` takes, so "how does this page sequence its sections?" is reachable from a result rather than only right after `add`. `favorites --limit` is capped at 100.
|
|
7006
|
+
- **Pages are re-read, on request and on a schedule.** A page is a copy taken when it was read, so `add --refresh` reads it again when the live site has changed (refused while a read is under way or within 10 minutes of the last one), and the library re-reads pages older than 30 days on its own, a small batch a day. A re-read that fails before it starts replacing the copy keeps the previous one and is reported as `refresh_failed_at`. `page` reports `read_at` and hints at `--refresh` (with the address the page is filed under) when the copy is over 30 days old.
|
|
6149
7007
|
- **`add` studies in the background; `scrape` returns with the page on disk.** `add` grows the shared library and takes minutes, so it can never answer "build our page like this one" within the same turn. `scrape` runs the identical capture locally and blocks until it finishes, writing section screenshots, standalone markup, a whole-page reproduction and `report.html`. `--no-motion` is the single biggest lever on runtime; `--no-mobile`, `--no-code` and `--no-report` skip further passes.
|
|
6150
7008
|
- **Filming is the expensive pass, and it runs three sections at a time.** Every moving section is filmed in its own fresh page load — the only way to catch an entrance animation before it fires — so a page with ten moving sections pays for ten full loads. Measured on one heavy page, filming was 74% of the capture. The takes are independent, so they overlap; a page that cannot be captured inside the library's budget now says so instead of timing out silently.
|
|
6151
7009
|
- **A page we can't open says why.** A dead certificate, an address that doesn't resolve, a site that won't answer or one that is simply too slow each produce their own plain-language reason rather than a browser error.
|
|
@@ -6172,9 +7030,24 @@ baker landing variant oferta --fork Hero.astro,Proof.astro --because "…" --cha
|
|
|
6172
7030
|
- **Errors carry the retry.** `NOT_FOUND` returns `availableSlugs` (wrong page) or `availableComponents` (wrong `--fork`); `INVALID_SLUG` fires when the slug is not a page, or is already a variant of one. Omitting `--fork` succeeds with a hint saying the page currently renders exactly the control, so a test between them cannot conclude.
|
|
6173
7031
|
- **Run `baker experiment plan` first.** Most pages never get the traffic to settle most questions.
|
|
6174
7032
|
|
|
7033
|
+
### `baker landing export`
|
|
7034
|
+
|
|
7035
|
+
A download of the company's **published** landings as a ZIP of plain HTML — the same pack Landings → Export makes. It reads what is live, so it starts at once; landings changed in the chat and not yet published are not in it.
|
|
7036
|
+
|
|
7037
|
+
```bash
|
|
7038
|
+
baker landing export start # → { exportId }, returns at once
|
|
7039
|
+
baker landing export start --profile '{"htmlOutput":"fragment"}' # download format for this export only (JSON or @file)
|
|
7040
|
+
baker landing export status <id> # progress; downloadUrl once ready
|
|
7041
|
+
baker landing export list # the last 10 downloads
|
|
7042
|
+
```
|
|
7043
|
+
|
|
7044
|
+
- **No password flag.** A protected ZIP is made from the dashboard — secrets never pass through the agent.
|
|
7045
|
+
- **Listed on the chat as already applied**, with a download button once ready.
|
|
7046
|
+
- `--profile` fields sit on top of the company's saved download format; a field that cannot be built is refused by name (`BAD_REQUEST`).
|
|
7047
|
+
|
|
6175
7048
|
### `baker landing critique`
|
|
6176
7049
|
|
|
6177
|
-
|
|
7050
|
+
Design-quality critic for landing pages. The design rules are code over the landing's `.astro` source (gradient text, overused fonts like Inter, side-tab colored borders, cream/parchment grounds, purple-on-heading palettes, broken images, and more); the copy rules read what the page says, in whatever language it is written, through the decision model (Jev): buzzwords, AI writing tells, agent washing and positioning integrity. Each sentence is asked once, in one fanned-out set of requests (about half a second and a fraction of a cent for a whole page, billed with the workspace's next run), and the answers are kept in `.cache/landing-critique/`, so unchanged copy scores the same on every run. It returns findings tiered **block / warn / advisory**.
|
|
6178
7051
|
|
|
6179
7052
|
```bash
|
|
6180
7053
|
baker landing critique spring-offer # score src/pages/spring-offer/
|
|
@@ -6184,14 +7057,15 @@ baker landing critique spring-offer summer-offer claude # score three landings
|
|
|
6184
7057
|
|
|
6185
7058
|
- **Takes several slugs at once.** `data.landings` is always an array with one entry per slug (`{ slug, overall, counts, dimensions, findings }`). For a single slug those fields are also spread at the top of `data`, so existing one-landing readers are unaffected. A wrong slug anywhere in the batch fails the whole call before any landing is scored.
|
|
6186
7059
|
- **Advisory by design — findings never fail it.** A finding is guidance, not a gate; the command errors (exit 2) only on a wrong slug (`NOT_FOUND` / `INVALID_SLUG`, with `error.fix.availableSlugs`).
|
|
6187
|
-
- **The
|
|
7060
|
+
- **The copy is never judged on a guess.** When the decision model cannot answer (no key, a timeout), no copy rule reports anything — a concession included — and a hint says the copy went unchecked and to re-run.
|
|
7061
|
+
- **The brand is the allowlist.** A font, hue, or ground the client's brand commits is a decision, not a tell — matched values downgrade to advisory `brandDrift` so the critic never fights a real brand. The allowlist reads `src/brand/tokens.json` (the canonical machine-readable brand token file) when present; workspaces that predate it fall back to scanning the `@theme` block in `src/styles/global.css` (hex or oklch) plus the faces and colours `src/brand/BRAND.md` says the brand uses, read in any language by the brand reader — a face or colour BRAND.md bans ("nunca uses Inter") is never allowlisted.
|
|
6188
7062
|
- **Records a freshness snapshot** to `.cache/landing-critique/<slug>.json` (the source hash it scored). The publish quality gate reads it to enforce that every changed landing was critiqued and ships no block-tier tell. Re-run after edits so the snapshot stays fresh.
|
|
6189
7063
|
|
|
6190
|
-
- **AI writing tells are scored in the `copy` family, in
|
|
7064
|
+
- **AI writing tells are scored in the `copy` family, in any language.** Beyond buzzwords and em-dash density, the critic detects the constructions models over-produce: the negation pivot ("it's not just X, it's Y", "Not a tool. A partner."), fake-candid openers ("Honestly?", "La verdad es que"), participles hung off a comma (", ensuring…", ", permitiendo…"), copula avoidance ("serves as", "cuenta con"), vague attributions, signposting, filler, hollow closers, aphorism formulas, false ranges, and chatbot framing that leaked into the page — each asked of every sentence; staccato runs and Title Case headings are measured in code. Most need a **cluster** (≥2 on the page) before they fire — the source guidance is explicit that isolated tells prove nothing — with two deliberate exceptions: chatbot framing fires at one, and curly quotes are not detected at all, since every editor inserts them automatically. Nothing in this family is block-tier, so it never fails the publish gate. Pattern set adapted from Wikipedia's "Signs of AI writing" (WikiProject AI Cleanup).
|
|
6191
7065
|
|
|
6192
|
-
- **Agent washing is a copy tell.** A page
|
|
7066
|
+
- **Agent washing is a copy tell.** A page with a sentence claiming autonomy ("fully autonomous", "while you sleep", "no human intervention", in any language) that never says anywhere what the buyer approves, overrides, or audits scores an `agent-washing` warn. Oversight of the agent's actions — approval, review, override, undo/rollback, audit log, escalation — said anywhere on the page clears it; the rule is page-scope precisely because the hero can defer the trust story to a section below it. Generic privacy boilerplate ("GDPR", "encrypted", "your data") deliberately does **not** clear it, since it appears in most footers and says nothing about who is in charge of the agent.
|
|
6193
7067
|
|
|
6194
|
-
- **Positioning integrity is scored too, and it is the one copy rule that blocks on a single sentence.** `competitor-concession` fires when the page ranks somebody else above the client, or says the client does not lead at what the page sells ("su constructor es mejor que el nuestro", "we're not the best"). Every other prose rule waits for a cluster because one AI tell proves nothing; this one is the whole defect on its own — the page is paid traffic, and it is arguing the visitor should leave. Two shapes that read identically are exempt by design: a concession about **price**, in either direction ("we are not the cheapest / the most expensive, and here is why" is premium positioning), and a superlative flipped by a negation ("ningún equipo trabaja más rápido que el nuestro"), which is the strongest claim *for* the client. Its warn-tier sibling `competitor-named-in-comparison` fires when a name from `src/content/competitors/` appears in a
|
|
7068
|
+
- **Positioning integrity is scored too, and it is the one copy rule that blocks on a single sentence.** `competitor-concession` fires when the page ranks somebody else above the client, or says the client does not lead at what the page sells ("su constructor es mejor que el nuestro", "we're not the best"). Every other prose rule waits for a cluster because one AI tell proves nothing; this one is the whole defect on its own — the page is paid traffic, and it is arguing the visitor should leave. Two shapes that read identically are exempt by design: a concession about **price**, in either direction ("we are not the cheapest / the most expensive, and here is why" is premium positioning), and a superlative flipped by a negation ("ningún equipo trabaja más rápido que el nuestro"), which is the strongest claim *for* the client. Its warn-tier sibling `competitor-named-in-comparison` fires when a name from `src/content/competitors/` appears in a sentence outside a `<table>` that compares the two (naming it as an integration is not a comparison) — a comparison table is a section the landing skill teaches, but naming a rival anywhere else on paid traffic is the client's call, not the build's. Any language.
|
|
6195
7069
|
|
|
6196
7070
|
- **Comments are not markup, and no rule reads one.** Astro expression comments (`{/* … */}`) and HTML comments (`<!-- … -->`) are blanked before detection, offsets preserved so `file:line` stays exact. A shared scaffold component explaining its `preconnect` in prose that spelled `<img>` twice was read as two broken images — block-tier — so every landing rendering a video failed the publish gate on a file the client cannot edit. Comment prose is also where a component's reasoning lives, em-dashes and buzzwords included, so reading it inflated the count-threshold tells too.
|
|
6197
7071
|
|
|
@@ -6263,6 +7137,7 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
6263
7137
|
|
|
6264
7138
|
## Internal Notes
|
|
6265
7139
|
|
|
7140
|
+
- **Each command loads only itself.** Every subcommand is declared lazily and `baker` loads the one a call names (`src/command-loading.ts`); `schema`, `--help` and an unknown name load the whole tree. Loading everything made each `baker` process hold ~124 MB while it waited on one request, and sixty of them, run by parallel agents, filled an 8 GB Runtime; it is now ~48 MB. A new subcommand is mounted as `name: () => import("./x.ts").then((m) => m.xCommand)`, and a test that walks the tree synchronously loads it first with `loadAllCommands`. `__tests__/command-loading-budget.test.ts` builds the CLI as published and fails when startup loads more than 5% of it, or any command path more than half (a canvas render, the heaviest, loads under a third).
|
|
6266
7141
|
- **0.316.1**: `baker flows show <slug>` reports **destination drift** — what the HubSpot form a Form submits to has changed under it — as `data.drift[]` plus a `DESTINATION DRIFT` hint carrying the repair as a ready-to-run command. It is the first time a Runtime can see this at all: the findings live in Baker and the HubSpot form id the repair needs is inside an encrypted config, so a Company Workspace could read neither. The classification is the one the dashboard badge reads (`classifyDrift` in `@baker/api`), so the two surfaces cannot disagree about whether a Form is healthy; a failed check answers `drift: null`, which is "unknown", never "clean". `baker flows map <slug> --side-effect <id> --hubspot-form <guid> --prune-missing [--write]` is the repair: it drops only the mappings whose destination field is absent from the form's live field list, names each in `dropped`, needs no `--map`, and refuses to run without a readable destination rather than guessing. Reach for it instead of `--replace`, which drops every mapping the call does not name.
|
|
6267
7142
|
|
|
6268
7143
|
- **0.103.0**: `baker actions ...` commands now type their `/api/actions/...` request/response payloads from the shared `@baker/api` contract package (also consumed by the backend) instead of hand-written local interfaces. No command, flag, or output-shape changes.
|
|
@@ -6397,3 +7272,9 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
6397
7272
|
npx convex env set BAKER_CLI_VERSION <version> # Override sandbox version
|
|
6398
7273
|
npx convex env remove BAKER_CLI_VERSION # Clean up after testing
|
|
6399
7274
|
```
|
|
7275
|
+
|
|
7276
|
+
### Agreed tracking setup and external questionnaire diagnostics
|
|
7277
|
+
|
|
7278
|
+
`baker analytics plan [--full]` reads the effective session plan. `--json @preparation.json` stages journeys, event purposes and readiness, optionally wiring conversion definitions, funnel steps and a managed Tag Manager installation. The generated base queues early events and runs before event tags; repeating preparation replaces its own draft operations. These changes apply only at Publish. Waiting events record the missing change and owner; prepared tags do not imply accepted events. The full request contract and example are in the scaffold's [analytics tool doc](../../apps/scaffold-template/__tooling__/docs/tools/baker/analytics.md#shared-journey-and-tracking-plan).
|
|
7279
|
+
|
|
7280
|
+
`baker analytics questionnaire [--name questionnaire_step] [--days 7] [--inactivity 30]` reports distinct attempts reaching/completing/skipping questions and distinguishes pending, abandoned and incomplete steps. The external app must emit the documented attempt/version/sequence contract. The report refuses truncated histories above 600 events and excludes attempts without an observed start; no observations do not establish zero abandonment.
|