@koda-sl/baker-cli 0.455.0 → 0.456.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -17
- package/dist/{account-2OBWFPRG.js → account-AO6PNNPO.js} +6 -6
- package/dist/account-reads-W3XO7CIW.js +218 -0
- package/dist/account-reads-W3XO7CIW.js.map +1 -0
- package/dist/{accounts-TDTME6RQ.js → accounts-4TM4UZEJ.js} +5 -5
- package/dist/{accounts-TZQVAJ3V.js → accounts-XDC26KK6.js} +7 -7
- package/dist/{actions-FHYESJEE.js → actions-CJB4GZYA.js} +18 -18
- package/dist/{add-Q6YFHEOR.js → add-BP4CUUKB.js} +4 -4
- package/dist/{add-7XB5X3D3.js → add-RYR3QGOP.js} +5 -5
- package/dist/{ads-PGTFZA5Q.js → ads-GII5LEOH.js} +6 -6
- package/dist/{ads-6UEDYT3O.js → ads-SATXH4OD.js} +9 -9
- package/dist/{adsets-Q5DWGANV.js → adsets-PMXNW3EG.js} +8 -8
- package/dist/{advertisers-RKWQDCOI.js → advertisers-GM5UFI44.js} +7 -7
- package/dist/{analytics-DEJKJNLN.js → analytics-4CD7CH7H.js} +5 -5
- package/dist/{analytics-U5TTTX7K.js → analytics-JDPRLCHQ.js} +15 -15
- package/dist/{assign-RHUKQWH2.js → assign-XI4HIIIM.js} +5 -5
- package/dist/{audience-size-ICEKQRE4.js → audience-size-DHZ65PGA.js} +5 -5
- package/dist/{audiences-AMRFZRZ7.js → audiences-D7CXP3ZF.js} +3 -3
- package/dist/{audit-GWLQVB4A.js → audit-M7BZYBIS.js} +5 -5
- package/dist/{autocomplete-NAEVFMKZ.js → autocomplete-B45I26R4.js} +7 -7
- package/dist/{avatars-PMV6IMFV.js → avatars-75BALNB7.js} +4 -4
- package/dist/{bid-pricing-XF3LD65L.js → bid-pricing-4RCP24BE.js} +5 -5
- package/dist/{brand-ZC5HDP7B.js → brand-2TOLKYDY.js} +2 -2
- package/dist/{brief-3EZ2JS5F.js → brief-5G4NEZ4O.js} +16 -16
- package/dist/{campaign-groups-JSHBFYDG.js → campaign-groups-G7B26V7K.js} +10 -10
- package/dist/{campaign-writes-5PHXENPN.js → campaign-writes-HZ7ART7W.js} +5 -5
- package/dist/{campaigns-D6B6Z67Y.js → campaigns-MEEM3CNC.js} +8 -8
- package/dist/{campaigns-HPRQVXTH.js → campaigns-OIJGTO7W.js} +5 -5
- package/dist/{campaigns-LQXELRHZ.js → campaigns-RW3QJQ2F.js} +13 -13
- package/dist/{canvas-4NVEZNW5.js → canvas-LXRLDE5G.js} +12 -12
- package/dist/{capabilities-VQRNPBOZ.js → capabilities-BWSB7IFT.js} +5 -5
- package/dist/{changes-TP7ZZHB3.js → changes-AYLNY6KN.js} +5 -5
- package/dist/{chatgpt-QNUN63YS.js → chatgpt-TNSRA3L3.js} +1 -1
- package/dist/{chats-Q4QPA3OQ.js → chats-MMHBJQFM.js} +5 -5
- package/dist/{chunk-RXGR63TP.js → chunk-24GELJP3.js} +2 -2
- package/dist/{chunk-ZKYNGME7.js → chunk-2D4ASTGN.js} +3 -3
- package/dist/{chunk-PRIHMSJH.js → chunk-2K4LMCV2.js} +2 -2
- package/dist/{chunk-4GACYQUI.js → chunk-2YJL6MEB.js} +3 -3
- package/dist/{chunk-L27H2R2N.js → chunk-2ZY7HON7.js} +2 -2
- package/dist/{chunk-RZTNH2TX.js → chunk-45OQOUJA.js} +2 -2
- package/dist/{chunk-LLOOWRBB.js → chunk-4KCKNAVW.js} +189 -20
- package/dist/chunk-4KCKNAVW.js.map +1 -0
- package/dist/{chunk-GX6VAHY7.js → chunk-5FWMRTNB.js} +31 -15
- package/dist/chunk-5FWMRTNB.js.map +1 -0
- package/dist/{chunk-YT2BSTD3.js → chunk-7TNPEURJ.js} +2 -2
- package/dist/chunk-7VHPAGPK.js +228 -0
- package/dist/chunk-7VHPAGPK.js.map +1 -0
- package/dist/{chunk-WKS6ZDUP.js → chunk-BSOWFWEU.js} +7 -7
- package/dist/{chunk-X5ECCSHP.js → chunk-D23EKXOP.js} +2 -2
- package/dist/{chunk-FC3U5XKS.js → chunk-D7PFF6UC.js} +4 -4
- package/dist/{chunk-OU5YA2QX.js → chunk-DT2XXF6O.js} +2 -2
- package/dist/{chunk-ARBKEE6E.js → chunk-DYOI4FQ7.js} +2 -2
- package/dist/{chunk-4EVLVA2R.js → chunk-EFM7OODQ.js} +2 -2
- package/dist/{chunk-P6KWFODZ.js → chunk-EIIANSBX.js} +2 -2
- package/dist/{chunk-HJMRWBR5.js → chunk-EYK6G4XE.js} +2 -2
- package/dist/{chunk-PKC6QBYJ.js → chunk-EZWYX343.js} +4 -4
- package/dist/{chunk-LIQ4SQ2T.js → chunk-FEELV5IV.js} +2 -2
- package/dist/{chunk-YCWIGXH6.js → chunk-GP7QUF5A.js} +2 -2
- package/dist/{chunk-ZGGGA3JR.js → chunk-HOXZOOVA.js} +8 -8
- package/dist/{chunk-CYEO5ZIO.js → chunk-JPK34QPS.js} +3 -2
- package/dist/{chunk-CYEO5ZIO.js.map → chunk-JPK34QPS.js.map} +1 -1
- package/dist/{chunk-TJOGO2MK.js → chunk-JU7FIHQ2.js} +2 -2
- package/dist/{chunk-MVYLY2SF.js → chunk-KN7DFVKW.js} +2 -2
- package/dist/{chunk-K5H3XOYQ.js → chunk-LI6PGY3L.js} +2 -2
- package/dist/{chunk-3KEWXALS.js → chunk-MCWYIEWI.js} +3 -3
- package/dist/{chunk-LIWBVGRE.js → chunk-MFQ5SUJT.js} +3 -3
- package/dist/{credentials-4V55RYZ6.js → chunk-ML7DLFFY.js} +3421 -249
- package/dist/chunk-ML7DLFFY.js.map +1 -0
- package/dist/{chunk-HEOWTR7Z.js → chunk-OW4XDJV2.js} +2 -2
- package/dist/{chunk-KYR4EG3E.js → chunk-PHEEJUFL.js} +2 -2
- package/dist/chunk-QM6T4BFA.js +76 -0
- package/dist/chunk-QM6T4BFA.js.map +1 -0
- package/dist/{chunk-KHV5GALI.js → chunk-QNDLOCAI.js} +2 -2
- package/dist/{chunk-GQ7JE7A7.js → chunk-UZUHXN7H.js} +2 -2
- package/dist/{chunk-YWK4UBVU.js → chunk-V5QJBDBC.js} +2 -2
- package/dist/{chunk-LP2QDRO4.js → chunk-WBUMUELD.js} +7 -7
- package/dist/{chunk-EP5TYLNV.js → chunk-WEU6GKUQ.js} +3 -3
- package/dist/{chunk-46TPPYCI.js → chunk-X5VHGXFB.js} +2 -2
- package/dist/{chunk-3V57D7GE.js → chunk-XJRBFLBD.js} +2 -2
- package/dist/{query-IEZRY6HV.js → chunk-XPOIIQCI.js} +406 -148
- package/dist/chunk-XPOIIQCI.js.map +1 -0
- package/dist/{chunk-ZJWUBJYQ.js → chunk-XQIOCIDO.js} +1 -1
- package/dist/chunk-XQIOCIDO.js.map +1 -0
- package/dist/{chunk-T4DD5YMP.js → chunk-XY7BZSDQ.js} +12 -12
- package/dist/{chunk-T4DD5YMP.js.map → chunk-XY7BZSDQ.js.map} +1 -1
- package/dist/{chunk-QS2BDKFL.js → chunk-Y63Y4QSY.js} +2 -2
- package/dist/{chunk-PAQ3T6OT.js → chunk-YRVDCIJ2.js} +2 -2
- package/dist/{claim-RHZVTMQZ.js → claim-UDNXCJIU.js} +5 -5
- package/dist/cli.js +29 -29
- package/dist/{companies-LIKSVYSR.js → companies-RPZN4JTR.js} +5 -5
- package/dist/{complete-OM7R6VOC.js → complete-HLSRQE3N.js} +5 -5
- package/dist/{connect-AVX3NKP6.js → connect-TIJZO7OV.js} +8 -8
- package/dist/{conversation-3HGRWXKO.js → conversation-BS7TF6LZ.js} +5 -5
- package/dist/{conversion-upload-command-AUF3LNTH.js → conversion-upload-command-CSD344BY.js} +6 -6
- package/dist/{conversions-XADFSQ4W.js → conversions-FEWAX64H.js} +9 -9
- package/dist/{create-PQSDE4J3.js → create-4SH4BZXP.js} +5 -5
- package/dist/{create-BWHYYATN.js → create-B4UVIJZI.js} +4 -4
- package/dist/{create-Z4JLLTPG.js → create-F5FNHJKE.js} +5 -5
- package/dist/{create-3I64N6RJ.js → create-L3JHP6J4.js} +5 -5
- package/dist/{creatives-WPRN6CU4.js → creatives-LK2NZHRX.js} +10 -10
- package/dist/{creatives-UEPLNCPN.js → creatives-RQ2DFHGI.js} +3 -3
- package/dist/{creatives-6Z7CEW5O.js → creatives-UILBPOKO.js} +10 -10
- package/dist/credentials-GC34ZPYP.js +287 -0
- package/dist/credentials-GC34ZPYP.js.map +1 -0
- package/dist/{critique-M5TMIG2D.js → critique-FAOH4FCP.js} +7 -7
- package/dist/{critique-K632OHFP.js → critique-X7HYYEJ4.js} +5 -5
- package/dist/{crm-CWT6BMMX.js → crm-RB5TVV3M.js} +6 -6
- package/dist/{crm-Q2QK4ZMS.js → crm-XEQDSU2U.js} +6 -6
- package/dist/{currency-XCGKQ7FE.js → currency-47QHFHDA.js} +4 -4
- package/dist/{custom-conversions-2VLJGPRV.js → custom-conversions-7HOWR6X6.js} +2 -2
- package/dist/{customers-7TS64ZM5.js → customers-P5WOUY6G.js} +7 -7
- package/dist/{delete-H7W3V6QG.js → delete-DLQ2ZZDA.js} +4 -4
- package/dist/{delete-BPSOLCGB.js → delete-GZKGPBBD.js} +5 -5
- package/dist/{delete-RIY5VUTU.js → delete-SW5OSIGN.js} +5 -5
- package/dist/{demographics-NHGM4QP7.js → demographics-ZLZOIWUE.js} +5 -5
- package/dist/{discard-IBMOGEYB.js → discard-VNVNJWNX.js} +5 -5
- package/dist/{discover-BJPZNXUQ.js → discover-P6TUGZ45.js} +4 -4
- package/dist/{domain-Y3N2UY5I.js → domain-OBICRJP3.js} +5 -5
- package/dist/draft-666YKA3P.js +307 -0
- package/dist/draft-666YKA3P.js.map +1 -0
- package/dist/{draft-322ZG6JG.js → draft-UECMJ6B4.js} +5 -5
- package/dist/{draft-6KBILDJK.js → draft-V75RBORX.js} +5 -5
- package/dist/{draft-P5T2QHCY.js → draft-Z3VUQ4LJ.js} +7 -7
- package/dist/{facets-3SDK42HY.js → facets-7J66L3NK.js} +5 -5
- package/dist/{feedback-P2JZTJY5.js → feedback-3LDG4WVJ.js} +2 -2
- package/dist/{feedback-record-5O6UUWTZ.js → feedback-record-CTAUHLMF.js} +9 -9
- package/dist/{fetch-LN5W3J4E.js → fetch-4RXXZGO4.js} +5 -5
- package/dist/{flows-MN26KBWB.js → flows-GGB46DLH.js} +12 -12
- package/dist/{fonts-7TXPTNCS.js → fonts-S3GWKOUJ.js} +7 -7
- package/dist/{forecast-ZHBKYQFP.js → forecast-6JVKXNFB.js} +5 -5
- package/dist/forecast-SVD3LMMY.js +168 -0
- package/dist/forecast-SVD3LMMY.js.map +1 -0
- package/dist/{funnel-UAXVM7JN.js → funnel-RCQOIQZ5.js} +6 -6
- package/dist/{ga4-DV2AXCSL.js → ga4-U6DMJ6K4.js} +1 -1
- package/dist/{get-EKYSQHNB.js → get-KQSM2MXM.js} +5 -5
- package/dist/{get-P7SSZ46N.js → get-MN2GRXGZ.js} +5 -5
- package/dist/{google-M7KEXVK5.js → google-CI2AMJKN.js} +1518 -200
- package/dist/google-CI2AMJKN.js.map +1 -0
- package/dist/{google-ads-LZDXDFPW.js → google-ads-YAJ33LTW.js} +5 -5
- package/dist/{history-BFGVCJ4J.js → history-QWYLRQBO.js} +5 -5
- package/dist/{hubspot-I3PNNF3E.js → hubspot-DDONLJ7R.js} +9 -9
- package/dist/{ideas-ZNP5BOGE.js → ideas-N44Z2SIT.js} +4 -4
- package/dist/{images-MWWGN672.js → images-BKHCBCE7.js} +4 -4
- package/dist/{ingest-H7BWHY7L.js → ingest-HBECWLOB.js} +4 -4
- package/dist/{ingest-NYP2ZAAK.js → ingest-VNRTFD4Y.js} +5 -5
- package/dist/{inspect-FNNDBZQX.js → inspect-7GGJDJBJ.js} +5 -5
- package/dist/{inspiration-S3DU4CMQ.js → inspiration-CGM3NK6X.js} +1 -1
- package/dist/{intent-E3UIVZBN.js → intent-HKNBXEDS.js} +4 -4
- package/dist/{keyword-gap-BG6M665A.js → keyword-gap-HUSP6MOT.js} +4 -4
- package/dist/{keyword-metrics-WG5LLBUW.js → keyword-metrics-2C3VFGDQ.js} +7 -7
- package/dist/{keywords-for-site-HVEAAODA.js → keywords-for-site-4GV4KS4X.js} +7 -7
- package/dist/{landing-G5MLO4CZ.js → landing-V2FMPQOK.js} +3 -3
- package/dist/{languages-HORKPDNH.js → languages-75YJZAHL.js} +4 -4
- package/dist/{leads-XCAE4OVV.js → leads-DTULNGMU.js} +5 -5
- package/dist/{leads-ARUWPHBM.js → leads-OPRVJBQG.js} +4 -4
- package/dist/{learn-5WSN4PT5.js → learn-EW2CKOFS.js} +9 -9
- package/dist/{line-item-writes-4EOQBQSC.js → line-item-writes-EF3BJ5KV.js} +5 -5
- package/dist/{line-items-45X5BVQW.js → line-items-G467XOZF.js} +5 -5
- package/dist/{link-XAXNBIKU.js → link-5SU4MR6M.js} +5 -5
- package/dist/{linkedin-62HOT3HB.js → linkedin-OFM3VVOB.js} +29 -29
- package/dist/{list-PLWORN75.js → list-HEWXAVCL.js} +4 -4
- package/dist/{list-NYTQ3DBH.js → list-SUTCQLGI.js} +5 -5
- package/dist/{list-H4SN4QYE.js → list-VFZPXFMA.js} +5 -5
- package/dist/{locations-J4G5RNPH.js → locations-6XADFU3V.js} +4 -4
- package/dist/{log-S7TEGEPL.js → log-I3MAU44R.js} +5 -5
- package/dist/{make-F6FOFAS4.js → make-ALBJZKJZ.js} +21 -21
- package/dist/{map-V3YC6Y5I.js → map-OT6F33VF.js} +6 -6
- package/dist/{mcp-3R3FOL2H.js → mcp-LC3L2JQO.js} +4 -4
- package/dist/{media-53DJRC4N.js → media-MYZ5I7WK.js} +2 -2
- package/dist/{media-sources-356UORTR.js → media-sources-RK6EJYBQ.js} +2 -2
- package/dist/{members-KATLJNPJ.js → members-7CHDHW7H.js} +5 -5
- package/dist/{meta-A7KM6TF5.js → meta-7XP5AWYU.js} +10 -10
- package/dist/{metrics-SQDJ5NQO.js → metrics-73LI743C.js} +4 -4
- package/dist/{normalize-RUPEDCAV.js → normalize-CXMYHNSA.js} +5 -5
- package/dist/{options-SCYGSRQX.js → options-E4IO5SR5.js} +8 -8
- package/dist/{performance-U3MA6YXN.js → performance-UQLSBPYN.js} +6 -6
- package/dist/{plan-4QP2G7GV.js → plan-NBFLYAOW.js} +2 -2
- package/dist/{promoted-tweet-writes-5UMGETBC.js → promoted-tweet-writes-ZW3FUPFG.js} +5 -5
- package/dist/{promoted-tweets-A3V53KZI.js → promoted-tweets-ZLQ6GZD5.js} +3 -3
- package/dist/{publish-V22K43GA.js → publish-H2PBR5XN.js} +5 -5
- package/dist/query-GDUTZ4R7.js +61 -0
- package/dist/{reddit-UACCQN3B.js → reddit-TGQYQUNZ.js} +6 -6
- package/dist/{refresh-PSXGRCJE.js → refresh-HVBCC2ZI.js} +4 -4
- package/dist/{release-S2X7UL4U.js → release-VI3JT6O6.js} +5 -5
- package/dist/{relevant-pages-OWMNUCP7.js → relevant-pages-2FW3E5KD.js} +4 -4
- package/dist/{remove-4GTKRBUW.js → remove-4EE7STMS.js} +4 -4
- package/dist/{reopen-3KUS6UZ2.js → reopen-LEGLAOMA.js} +5 -5
- package/dist/{rerun-ZHD7C4W2.js → rerun-A5DPZFOO.js} +14 -14
- package/dist/{research-7LO3G3EP.js → research-O6YMV2TG.js} +13 -13
- package/dist/{research-UF6PKQPH.js → research-WOJELJ4K.js} +4 -4
- package/dist/{reset-PVXTS3FZ.js → reset-PQHURJDC.js} +4 -4
- package/dist/{reset-PU57HTAP.js → reset-V2X7DFHD.js} +4 -4
- package/dist/{resolve-KBIYWRF7.js → resolve-Q4AACDIC.js} +5 -5
- package/dist/{retry-HKWNIGON.js → retry-BB6NJBC4.js} +4 -4
- package/dist/{retry-LSCVGZM5.js → retry-WMGEUJPB.js} +4 -4
- package/dist/{run-HMFS4KQZ.js → run-OBYA2F3E.js} +14 -14
- package/dist/{run-DU6RVXPA.js → run-V6E36GXW.js} +5 -5
- package/dist/{scaffold-ad-SLUGKZIW.js → scaffold-ad-KKYI7FZJ.js} +10 -10
- package/dist/{scaffold-static-ad-NRFAA5HL.js → scaffold-static-ad-R5UE4YIZ.js} +16 -16
- package/dist/{scaffold-video-Q7BSFVS4.js → scaffold-video-Y2BWXZSX.js} +12 -12
- package/dist/{schedule-VNTGMFLQ.js → schedule-EO27AZTT.js} +4 -4
- package/dist/{scheduled-agents-6TNWXJVH.js → scheduled-agents-OIJBX7OO.js} +11 -11
- package/dist/{schema-ZEOL467O.js → schema-7GSCTQIC.js} +6 -6
- package/dist/{search-XUINK3C2.js → search-Q7PAV7NY.js} +4 -4
- package/dist/{select-U3DVKV72.js → select-ASR5SBYE.js} +9 -9
- package/dist/{set-prompt-EQIVJ5LR.js → set-prompt-I32464V4.js} +7 -7
- package/dist/{setup-links-CLPE6UUM.js → setup-links-NP2HULRC.js} +5 -5
- package/dist/{site-GIZB7NWB.js → site-PTIYQQ3Z.js} +6 -6
- package/dist/{sources-7RGXA35S.js → sources-5FRMFKDA.js} +6 -6
- package/dist/{sources-2PG4U4BC.js → sources-FJLCIG6X.js} +8 -8
- package/dist/{stale-ODM7IOO2.js → stale-YUYMETFC.js} +8 -8
- package/dist/{stats-NNXZMPZR.js → stats-F5JZVPYG.js} +1 -1
- package/dist/{status-HKCIXG7P.js → status-UYIQFC7L.js} +5 -5
- package/dist/{studio-EKIPLLHD.js → studio-6ZL76TZR.js} +1 -1
- package/dist/{sync-LGDTHM6C.js → sync-527ZCA4A.js} +4 -4
- package/dist/{sync-definition-6WBQN4Q3.js → sync-definition-M4KRPGTN.js} +9 -9
- package/dist/sync-definition-M4KRPGTN.js.map +1 -0
- package/dist/{tags-BQFPK55V.js → tags-2MX3332V.js} +5 -5
- package/dist/{tags-N72F6AMR.js → tags-7767I2D6.js} +4 -4
- package/dist/{tags-DRZ7GPP4.js → tags-J3BGX3E6.js} +5 -5
- package/dist/{tags-Q5NWILJO.js → tags-O27SK3YX.js} +4 -4
- package/dist/{tags-4NICM2LC.js → tags-VAYVSQM6.js} +5 -5
- package/dist/{targeting-criteria-DXHMEEC2.js → targeting-criteria-GOTI75Y4.js} +3 -3
- package/dist/{targets-A325AP5G.js → targets-FC7CGFRD.js} +5 -5
- package/dist/{templates-WWY4ZVKR.js → templates-EF5LOWP5.js} +5 -5
- package/dist/{testimonials-YVHSZTWW.js → testimonials-MRMRWF2T.js} +6 -6
- package/dist/themes-2ZV7OCJ3.js +94 -0
- package/dist/themes-2ZV7OCJ3.js.map +1 -0
- package/dist/{top-companies-S2Y4I5HF.js → top-companies-VTLR6GTW.js} +5 -5
- package/dist/{translate-BTUMXQSK.js → translate-UMTKAUJ3.js} +15 -15
- package/dist/{triggers-T47AIYVB.js → triggers-Y7ZMMY3C.js} +5 -5
- package/dist/{unlink-XRKX6DVP.js → unlink-HZCFLXH4.js} +5 -5
- package/dist/{unstage-77RAGX3F.js → unstage-562HOOE7.js} +5 -5
- package/dist/{update-R3QSTHUD.js → update-ATSXY5UN.js} +5 -5
- package/dist/{update-RJLMUP7I.js → update-DKU2UDFA.js} +4 -4
- package/dist/{update-YRO5IHDX.js → update-I5A5TOZJ.js} +5 -5
- package/dist/{update-NQIRRGOW.js → update-ZF3VFYLB.js} +5 -5
- package/dist/{upload-KWFKNKAF.js → upload-SIAI6NMH.js} +5 -5
- package/dist/{url-options-GGXAL3J4.js → url-options-JSX6HBZL.js} +10 -7
- package/dist/url-options-JSX6HBZL.js.map +1 -0
- package/dist/{url-params-IMC43AIM.js → url-params-AZUI3CW3.js} +5 -5
- package/dist/{validate-Y6R6F5T3.js → validate-MC4EIQI5.js} +7 -7
- package/dist/{videos-U2FPMNFS.js → videos-S7BYFSU3.js} +5 -5
- package/dist/{web-AHTOJNQY.js → web-5R7LHKIT.js} +5 -5
- package/dist/{winning-ads-TUYN2BST.js → winning-ads-PDCY4YW5.js} +2 -2
- package/dist/{write-commands-CT74VHH6.js → write-commands-MFRZD6Q6.js} +4 -4
- package/dist/{write-commands-2XFNH4BW.js → write-commands-VXL5QKMT.js} +7 -7
- package/dist/{x-5GDVUJ2I.js → x-22PKVRYX.js} +7 -7
- package/package.json +1 -1
- package/dist/chunk-GX6VAHY7.js.map +0 -1
- package/dist/chunk-LLOOWRBB.js.map +0 -1
- package/dist/chunk-NE4F3FHN.js +0 -1854
- package/dist/chunk-NE4F3FHN.js.map +0 -1
- package/dist/chunk-XCM6XWOB.js +0 -417
- package/dist/chunk-XCM6XWOB.js.map +0 -1
- package/dist/chunk-ZJWUBJYQ.js.map +0 -1
- package/dist/credentials-4V55RYZ6.js.map +0 -1
- package/dist/draft-5YAYCEYY.js +0 -107
- package/dist/draft-5YAYCEYY.js.map +0 -1
- package/dist/google-M7KEXVK5.js.map +0 -1
- package/dist/query-IEZRY6HV.js.map +0 -1
- package/dist/url-options-GGXAL3J4.js.map +0 -1
- /package/dist/{account-2OBWFPRG.js.map → account-AO6PNNPO.js.map} +0 -0
- /package/dist/{accounts-TDTME6RQ.js.map → accounts-4TM4UZEJ.js.map} +0 -0
- /package/dist/{accounts-TZQVAJ3V.js.map → accounts-XDC26KK6.js.map} +0 -0
- /package/dist/{actions-FHYESJEE.js.map → actions-CJB4GZYA.js.map} +0 -0
- /package/dist/{add-Q6YFHEOR.js.map → add-BP4CUUKB.js.map} +0 -0
- /package/dist/{add-7XB5X3D3.js.map → add-RYR3QGOP.js.map} +0 -0
- /package/dist/{ads-PGTFZA5Q.js.map → ads-GII5LEOH.js.map} +0 -0
- /package/dist/{ads-6UEDYT3O.js.map → ads-SATXH4OD.js.map} +0 -0
- /package/dist/{adsets-Q5DWGANV.js.map → adsets-PMXNW3EG.js.map} +0 -0
- /package/dist/{advertisers-RKWQDCOI.js.map → advertisers-GM5UFI44.js.map} +0 -0
- /package/dist/{analytics-DEJKJNLN.js.map → analytics-4CD7CH7H.js.map} +0 -0
- /package/dist/{analytics-U5TTTX7K.js.map → analytics-JDPRLCHQ.js.map} +0 -0
- /package/dist/{assign-RHUKQWH2.js.map → assign-XI4HIIIM.js.map} +0 -0
- /package/dist/{audience-size-ICEKQRE4.js.map → audience-size-DHZ65PGA.js.map} +0 -0
- /package/dist/{audiences-AMRFZRZ7.js.map → audiences-D7CXP3ZF.js.map} +0 -0
- /package/dist/{audit-GWLQVB4A.js.map → audit-M7BZYBIS.js.map} +0 -0
- /package/dist/{autocomplete-NAEVFMKZ.js.map → autocomplete-B45I26R4.js.map} +0 -0
- /package/dist/{avatars-PMV6IMFV.js.map → avatars-75BALNB7.js.map} +0 -0
- /package/dist/{bid-pricing-XF3LD65L.js.map → bid-pricing-4RCP24BE.js.map} +0 -0
- /package/dist/{brand-ZC5HDP7B.js.map → brand-2TOLKYDY.js.map} +0 -0
- /package/dist/{brief-3EZ2JS5F.js.map → brief-5G4NEZ4O.js.map} +0 -0
- /package/dist/{campaign-groups-JSHBFYDG.js.map → campaign-groups-G7B26V7K.js.map} +0 -0
- /package/dist/{campaign-writes-5PHXENPN.js.map → campaign-writes-HZ7ART7W.js.map} +0 -0
- /package/dist/{campaigns-D6B6Z67Y.js.map → campaigns-MEEM3CNC.js.map} +0 -0
- /package/dist/{campaigns-HPRQVXTH.js.map → campaigns-OIJGTO7W.js.map} +0 -0
- /package/dist/{campaigns-LQXELRHZ.js.map → campaigns-RW3QJQ2F.js.map} +0 -0
- /package/dist/{canvas-4NVEZNW5.js.map → canvas-LXRLDE5G.js.map} +0 -0
- /package/dist/{capabilities-VQRNPBOZ.js.map → capabilities-BWSB7IFT.js.map} +0 -0
- /package/dist/{changes-TP7ZZHB3.js.map → changes-AYLNY6KN.js.map} +0 -0
- /package/dist/{chatgpt-QNUN63YS.js.map → chatgpt-TNSRA3L3.js.map} +0 -0
- /package/dist/{chats-Q4QPA3OQ.js.map → chats-MMHBJQFM.js.map} +0 -0
- /package/dist/{chunk-RXGR63TP.js.map → chunk-24GELJP3.js.map} +0 -0
- /package/dist/{chunk-ZKYNGME7.js.map → chunk-2D4ASTGN.js.map} +0 -0
- /package/dist/{chunk-PRIHMSJH.js.map → chunk-2K4LMCV2.js.map} +0 -0
- /package/dist/{chunk-4GACYQUI.js.map → chunk-2YJL6MEB.js.map} +0 -0
- /package/dist/{chunk-L27H2R2N.js.map → chunk-2ZY7HON7.js.map} +0 -0
- /package/dist/{chunk-RZTNH2TX.js.map → chunk-45OQOUJA.js.map} +0 -0
- /package/dist/{chunk-YT2BSTD3.js.map → chunk-7TNPEURJ.js.map} +0 -0
- /package/dist/{chunk-WKS6ZDUP.js.map → chunk-BSOWFWEU.js.map} +0 -0
- /package/dist/{chunk-X5ECCSHP.js.map → chunk-D23EKXOP.js.map} +0 -0
- /package/dist/{chunk-FC3U5XKS.js.map → chunk-D7PFF6UC.js.map} +0 -0
- /package/dist/{chunk-OU5YA2QX.js.map → chunk-DT2XXF6O.js.map} +0 -0
- /package/dist/{chunk-ARBKEE6E.js.map → chunk-DYOI4FQ7.js.map} +0 -0
- /package/dist/{chunk-4EVLVA2R.js.map → chunk-EFM7OODQ.js.map} +0 -0
- /package/dist/{chunk-P6KWFODZ.js.map → chunk-EIIANSBX.js.map} +0 -0
- /package/dist/{chunk-HJMRWBR5.js.map → chunk-EYK6G4XE.js.map} +0 -0
- /package/dist/{chunk-PKC6QBYJ.js.map → chunk-EZWYX343.js.map} +0 -0
- /package/dist/{chunk-LIQ4SQ2T.js.map → chunk-FEELV5IV.js.map} +0 -0
- /package/dist/{chunk-YCWIGXH6.js.map → chunk-GP7QUF5A.js.map} +0 -0
- /package/dist/{chunk-ZGGGA3JR.js.map → chunk-HOXZOOVA.js.map} +0 -0
- /package/dist/{chunk-TJOGO2MK.js.map → chunk-JU7FIHQ2.js.map} +0 -0
- /package/dist/{chunk-MVYLY2SF.js.map → chunk-KN7DFVKW.js.map} +0 -0
- /package/dist/{chunk-K5H3XOYQ.js.map → chunk-LI6PGY3L.js.map} +0 -0
- /package/dist/{chunk-3KEWXALS.js.map → chunk-MCWYIEWI.js.map} +0 -0
- /package/dist/{chunk-LIWBVGRE.js.map → chunk-MFQ5SUJT.js.map} +0 -0
- /package/dist/{chunk-HEOWTR7Z.js.map → chunk-OW4XDJV2.js.map} +0 -0
- /package/dist/{chunk-KYR4EG3E.js.map → chunk-PHEEJUFL.js.map} +0 -0
- /package/dist/{chunk-KHV5GALI.js.map → chunk-QNDLOCAI.js.map} +0 -0
- /package/dist/{chunk-GQ7JE7A7.js.map → chunk-UZUHXN7H.js.map} +0 -0
- /package/dist/{chunk-YWK4UBVU.js.map → chunk-V5QJBDBC.js.map} +0 -0
- /package/dist/{chunk-LP2QDRO4.js.map → chunk-WBUMUELD.js.map} +0 -0
- /package/dist/{chunk-EP5TYLNV.js.map → chunk-WEU6GKUQ.js.map} +0 -0
- /package/dist/{chunk-46TPPYCI.js.map → chunk-X5VHGXFB.js.map} +0 -0
- /package/dist/{chunk-3V57D7GE.js.map → chunk-XJRBFLBD.js.map} +0 -0
- /package/dist/{chunk-QS2BDKFL.js.map → chunk-Y63Y4QSY.js.map} +0 -0
- /package/dist/{chunk-PAQ3T6OT.js.map → chunk-YRVDCIJ2.js.map} +0 -0
- /package/dist/{claim-RHZVTMQZ.js.map → claim-UDNXCJIU.js.map} +0 -0
- /package/dist/{companies-LIKSVYSR.js.map → companies-RPZN4JTR.js.map} +0 -0
- /package/dist/{complete-OM7R6VOC.js.map → complete-HLSRQE3N.js.map} +0 -0
- /package/dist/{connect-AVX3NKP6.js.map → connect-TIJZO7OV.js.map} +0 -0
- /package/dist/{conversation-3HGRWXKO.js.map → conversation-BS7TF6LZ.js.map} +0 -0
- /package/dist/{conversion-upload-command-AUF3LNTH.js.map → conversion-upload-command-CSD344BY.js.map} +0 -0
- /package/dist/{conversions-XADFSQ4W.js.map → conversions-FEWAX64H.js.map} +0 -0
- /package/dist/{create-PQSDE4J3.js.map → create-4SH4BZXP.js.map} +0 -0
- /package/dist/{create-BWHYYATN.js.map → create-B4UVIJZI.js.map} +0 -0
- /package/dist/{create-Z4JLLTPG.js.map → create-F5FNHJKE.js.map} +0 -0
- /package/dist/{create-3I64N6RJ.js.map → create-L3JHP6J4.js.map} +0 -0
- /package/dist/{creatives-WPRN6CU4.js.map → creatives-LK2NZHRX.js.map} +0 -0
- /package/dist/{creatives-UEPLNCPN.js.map → creatives-RQ2DFHGI.js.map} +0 -0
- /package/dist/{creatives-6Z7CEW5O.js.map → creatives-UILBPOKO.js.map} +0 -0
- /package/dist/{critique-M5TMIG2D.js.map → critique-FAOH4FCP.js.map} +0 -0
- /package/dist/{critique-K632OHFP.js.map → critique-X7HYYEJ4.js.map} +0 -0
- /package/dist/{crm-CWT6BMMX.js.map → crm-RB5TVV3M.js.map} +0 -0
- /package/dist/{crm-Q2QK4ZMS.js.map → crm-XEQDSU2U.js.map} +0 -0
- /package/dist/{currency-XCGKQ7FE.js.map → currency-47QHFHDA.js.map} +0 -0
- /package/dist/{custom-conversions-2VLJGPRV.js.map → custom-conversions-7HOWR6X6.js.map} +0 -0
- /package/dist/{customers-7TS64ZM5.js.map → customers-P5WOUY6G.js.map} +0 -0
- /package/dist/{delete-H7W3V6QG.js.map → delete-DLQ2ZZDA.js.map} +0 -0
- /package/dist/{delete-BPSOLCGB.js.map → delete-GZKGPBBD.js.map} +0 -0
- /package/dist/{delete-RIY5VUTU.js.map → delete-SW5OSIGN.js.map} +0 -0
- /package/dist/{demographics-NHGM4QP7.js.map → demographics-ZLZOIWUE.js.map} +0 -0
- /package/dist/{discard-IBMOGEYB.js.map → discard-VNVNJWNX.js.map} +0 -0
- /package/dist/{discover-BJPZNXUQ.js.map → discover-P6TUGZ45.js.map} +0 -0
- /package/dist/{domain-Y3N2UY5I.js.map → domain-OBICRJP3.js.map} +0 -0
- /package/dist/{draft-322ZG6JG.js.map → draft-UECMJ6B4.js.map} +0 -0
- /package/dist/{draft-6KBILDJK.js.map → draft-V75RBORX.js.map} +0 -0
- /package/dist/{draft-P5T2QHCY.js.map → draft-Z3VUQ4LJ.js.map} +0 -0
- /package/dist/{facets-3SDK42HY.js.map → facets-7J66L3NK.js.map} +0 -0
- /package/dist/{feedback-P2JZTJY5.js.map → feedback-3LDG4WVJ.js.map} +0 -0
- /package/dist/{feedback-record-5O6UUWTZ.js.map → feedback-record-CTAUHLMF.js.map} +0 -0
- /package/dist/{fetch-LN5W3J4E.js.map → fetch-4RXXZGO4.js.map} +0 -0
- /package/dist/{flows-MN26KBWB.js.map → flows-GGB46DLH.js.map} +0 -0
- /package/dist/{fonts-7TXPTNCS.js.map → fonts-S3GWKOUJ.js.map} +0 -0
- /package/dist/{forecast-ZHBKYQFP.js.map → forecast-6JVKXNFB.js.map} +0 -0
- /package/dist/{funnel-UAXVM7JN.js.map → funnel-RCQOIQZ5.js.map} +0 -0
- /package/dist/{ga4-DV2AXCSL.js.map → ga4-U6DMJ6K4.js.map} +0 -0
- /package/dist/{get-EKYSQHNB.js.map → get-KQSM2MXM.js.map} +0 -0
- /package/dist/{get-P7SSZ46N.js.map → get-MN2GRXGZ.js.map} +0 -0
- /package/dist/{google-ads-LZDXDFPW.js.map → google-ads-YAJ33LTW.js.map} +0 -0
- /package/dist/{history-BFGVCJ4J.js.map → history-QWYLRQBO.js.map} +0 -0
- /package/dist/{hubspot-I3PNNF3E.js.map → hubspot-DDONLJ7R.js.map} +0 -0
- /package/dist/{ideas-ZNP5BOGE.js.map → ideas-N44Z2SIT.js.map} +0 -0
- /package/dist/{images-MWWGN672.js.map → images-BKHCBCE7.js.map} +0 -0
- /package/dist/{ingest-H7BWHY7L.js.map → ingest-HBECWLOB.js.map} +0 -0
- /package/dist/{ingest-NYP2ZAAK.js.map → ingest-VNRTFD4Y.js.map} +0 -0
- /package/dist/{inspect-FNNDBZQX.js.map → inspect-7GGJDJBJ.js.map} +0 -0
- /package/dist/{inspiration-S3DU4CMQ.js.map → inspiration-CGM3NK6X.js.map} +0 -0
- /package/dist/{intent-E3UIVZBN.js.map → intent-HKNBXEDS.js.map} +0 -0
- /package/dist/{keyword-gap-BG6M665A.js.map → keyword-gap-HUSP6MOT.js.map} +0 -0
- /package/dist/{keyword-metrics-WG5LLBUW.js.map → keyword-metrics-2C3VFGDQ.js.map} +0 -0
- /package/dist/{keywords-for-site-HVEAAODA.js.map → keywords-for-site-4GV4KS4X.js.map} +0 -0
- /package/dist/{landing-G5MLO4CZ.js.map → landing-V2FMPQOK.js.map} +0 -0
- /package/dist/{languages-HORKPDNH.js.map → languages-75YJZAHL.js.map} +0 -0
- /package/dist/{leads-XCAE4OVV.js.map → leads-DTULNGMU.js.map} +0 -0
- /package/dist/{leads-ARUWPHBM.js.map → leads-OPRVJBQG.js.map} +0 -0
- /package/dist/{learn-5WSN4PT5.js.map → learn-EW2CKOFS.js.map} +0 -0
- /package/dist/{line-item-writes-4EOQBQSC.js.map → line-item-writes-EF3BJ5KV.js.map} +0 -0
- /package/dist/{line-items-45X5BVQW.js.map → line-items-G467XOZF.js.map} +0 -0
- /package/dist/{link-XAXNBIKU.js.map → link-5SU4MR6M.js.map} +0 -0
- /package/dist/{linkedin-62HOT3HB.js.map → linkedin-OFM3VVOB.js.map} +0 -0
- /package/dist/{list-PLWORN75.js.map → list-HEWXAVCL.js.map} +0 -0
- /package/dist/{list-NYTQ3DBH.js.map → list-SUTCQLGI.js.map} +0 -0
- /package/dist/{list-H4SN4QYE.js.map → list-VFZPXFMA.js.map} +0 -0
- /package/dist/{locations-J4G5RNPH.js.map → locations-6XADFU3V.js.map} +0 -0
- /package/dist/{log-S7TEGEPL.js.map → log-I3MAU44R.js.map} +0 -0
- /package/dist/{make-F6FOFAS4.js.map → make-ALBJZKJZ.js.map} +0 -0
- /package/dist/{map-V3YC6Y5I.js.map → map-OT6F33VF.js.map} +0 -0
- /package/dist/{mcp-3R3FOL2H.js.map → mcp-LC3L2JQO.js.map} +0 -0
- /package/dist/{media-53DJRC4N.js.map → media-MYZ5I7WK.js.map} +0 -0
- /package/dist/{media-sources-356UORTR.js.map → media-sources-RK6EJYBQ.js.map} +0 -0
- /package/dist/{members-KATLJNPJ.js.map → members-7CHDHW7H.js.map} +0 -0
- /package/dist/{meta-A7KM6TF5.js.map → meta-7XP5AWYU.js.map} +0 -0
- /package/dist/{metrics-SQDJ5NQO.js.map → metrics-73LI743C.js.map} +0 -0
- /package/dist/{normalize-RUPEDCAV.js.map → normalize-CXMYHNSA.js.map} +0 -0
- /package/dist/{options-SCYGSRQX.js.map → options-E4IO5SR5.js.map} +0 -0
- /package/dist/{performance-U3MA6YXN.js.map → performance-UQLSBPYN.js.map} +0 -0
- /package/dist/{plan-4QP2G7GV.js.map → plan-NBFLYAOW.js.map} +0 -0
- /package/dist/{promoted-tweet-writes-5UMGETBC.js.map → promoted-tweet-writes-ZW3FUPFG.js.map} +0 -0
- /package/dist/{promoted-tweets-A3V53KZI.js.map → promoted-tweets-ZLQ6GZD5.js.map} +0 -0
- /package/dist/{publish-V22K43GA.js.map → publish-H2PBR5XN.js.map} +0 -0
- /package/dist/{reddit-UACCQN3B.js.map → query-GDUTZ4R7.js.map} +0 -0
- /package/dist/{run-HMFS4KQZ.js.map → reddit-TGQYQUNZ.js.map} +0 -0
- /package/dist/{refresh-PSXGRCJE.js.map → refresh-HVBCC2ZI.js.map} +0 -0
- /package/dist/{release-S2X7UL4U.js.map → release-VI3JT6O6.js.map} +0 -0
- /package/dist/{relevant-pages-OWMNUCP7.js.map → relevant-pages-2FW3E5KD.js.map} +0 -0
- /package/dist/{remove-4GTKRBUW.js.map → remove-4EE7STMS.js.map} +0 -0
- /package/dist/{reopen-3KUS6UZ2.js.map → reopen-LEGLAOMA.js.map} +0 -0
- /package/dist/{rerun-ZHD7C4W2.js.map → rerun-A5DPZFOO.js.map} +0 -0
- /package/dist/{research-7LO3G3EP.js.map → research-O6YMV2TG.js.map} +0 -0
- /package/dist/{research-UF6PKQPH.js.map → research-WOJELJ4K.js.map} +0 -0
- /package/dist/{reset-PVXTS3FZ.js.map → reset-PQHURJDC.js.map} +0 -0
- /package/dist/{reset-PU57HTAP.js.map → reset-V2X7DFHD.js.map} +0 -0
- /package/dist/{resolve-KBIYWRF7.js.map → resolve-Q4AACDIC.js.map} +0 -0
- /package/dist/{retry-HKWNIGON.js.map → retry-BB6NJBC4.js.map} +0 -0
- /package/dist/{retry-LSCVGZM5.js.map → retry-WMGEUJPB.js.map} +0 -0
- /package/dist/{sync-definition-6WBQN4Q3.js.map → run-OBYA2F3E.js.map} +0 -0
- /package/dist/{run-DU6RVXPA.js.map → run-V6E36GXW.js.map} +0 -0
- /package/dist/{scaffold-ad-SLUGKZIW.js.map → scaffold-ad-KKYI7FZJ.js.map} +0 -0
- /package/dist/{scaffold-static-ad-NRFAA5HL.js.map → scaffold-static-ad-R5UE4YIZ.js.map} +0 -0
- /package/dist/{scaffold-video-Q7BSFVS4.js.map → scaffold-video-Y2BWXZSX.js.map} +0 -0
- /package/dist/{schedule-VNTGMFLQ.js.map → schedule-EO27AZTT.js.map} +0 -0
- /package/dist/{scheduled-agents-6TNWXJVH.js.map → scheduled-agents-OIJBX7OO.js.map} +0 -0
- /package/dist/{schema-ZEOL467O.js.map → schema-7GSCTQIC.js.map} +0 -0
- /package/dist/{search-XUINK3C2.js.map → search-Q7PAV7NY.js.map} +0 -0
- /package/dist/{select-U3DVKV72.js.map → select-ASR5SBYE.js.map} +0 -0
- /package/dist/{set-prompt-EQIVJ5LR.js.map → set-prompt-I32464V4.js.map} +0 -0
- /package/dist/{setup-links-CLPE6UUM.js.map → setup-links-NP2HULRC.js.map} +0 -0
- /package/dist/{site-GIZB7NWB.js.map → site-PTIYQQ3Z.js.map} +0 -0
- /package/dist/{sources-7RGXA35S.js.map → sources-5FRMFKDA.js.map} +0 -0
- /package/dist/{sources-2PG4U4BC.js.map → sources-FJLCIG6X.js.map} +0 -0
- /package/dist/{stale-ODM7IOO2.js.map → stale-YUYMETFC.js.map} +0 -0
- /package/dist/{stats-NNXZMPZR.js.map → stats-F5JZVPYG.js.map} +0 -0
- /package/dist/{status-HKCIXG7P.js.map → status-UYIQFC7L.js.map} +0 -0
- /package/dist/{studio-EKIPLLHD.js.map → studio-6ZL76TZR.js.map} +0 -0
- /package/dist/{sync-LGDTHM6C.js.map → sync-527ZCA4A.js.map} +0 -0
- /package/dist/{tags-BQFPK55V.js.map → tags-2MX3332V.js.map} +0 -0
- /package/dist/{tags-N72F6AMR.js.map → tags-7767I2D6.js.map} +0 -0
- /package/dist/{tags-DRZ7GPP4.js.map → tags-J3BGX3E6.js.map} +0 -0
- /package/dist/{tags-Q5NWILJO.js.map → tags-O27SK3YX.js.map} +0 -0
- /package/dist/{tags-4NICM2LC.js.map → tags-VAYVSQM6.js.map} +0 -0
- /package/dist/{targeting-criteria-DXHMEEC2.js.map → targeting-criteria-GOTI75Y4.js.map} +0 -0
- /package/dist/{targets-A325AP5G.js.map → targets-FC7CGFRD.js.map} +0 -0
- /package/dist/{templates-WWY4ZVKR.js.map → templates-EF5LOWP5.js.map} +0 -0
- /package/dist/{testimonials-YVHSZTWW.js.map → testimonials-MRMRWF2T.js.map} +0 -0
- /package/dist/{top-companies-S2Y4I5HF.js.map → top-companies-VTLR6GTW.js.map} +0 -0
- /package/dist/{translate-BTUMXQSK.js.map → translate-UMTKAUJ3.js.map} +0 -0
- /package/dist/{triggers-T47AIYVB.js.map → triggers-Y7ZMMY3C.js.map} +0 -0
- /package/dist/{unlink-XRKX6DVP.js.map → unlink-HZCFLXH4.js.map} +0 -0
- /package/dist/{unstage-77RAGX3F.js.map → unstage-562HOOE7.js.map} +0 -0
- /package/dist/{update-R3QSTHUD.js.map → update-ATSXY5UN.js.map} +0 -0
- /package/dist/{update-RJLMUP7I.js.map → update-DKU2UDFA.js.map} +0 -0
- /package/dist/{update-YRO5IHDX.js.map → update-I5A5TOZJ.js.map} +0 -0
- /package/dist/{update-NQIRRGOW.js.map → update-ZF3VFYLB.js.map} +0 -0
- /package/dist/{upload-KWFKNKAF.js.map → upload-SIAI6NMH.js.map} +0 -0
- /package/dist/{url-params-IMC43AIM.js.map → url-params-AZUI3CW3.js.map} +0 -0
- /package/dist/{validate-Y6R6F5T3.js.map → validate-MC4EIQI5.js.map} +0 -0
- /package/dist/{videos-U2FPMNFS.js.map → videos-S7BYFSU3.js.map} +0 -0
- /package/dist/{web-AHTOJNQY.js.map → web-5R7LHKIT.js.map} +0 -0
- /package/dist/{winning-ads-TUYN2BST.js.map → winning-ads-PDCY4YW5.js.map} +0 -0
- /package/dist/{write-commands-CT74VHH6.js.map → write-commands-MFRZD6Q6.js.map} +0 -0
- /package/dist/{write-commands-2XFNH4BW.js.map → write-commands-VXL5QKMT.js.map} +0 -0
- /package/dist/{x-5GDVUJ2I.js.map → x-22PKVRYX.js.map} +0 -0
|
@@ -1,9 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runQuery
|
|
3
|
+
} from "./chunk-XPOIIQCI.js";
|
|
4
|
+
import "./chunk-JPK34QPS.js";
|
|
5
|
+
import "./chunk-5FWMRTNB.js";
|
|
1
6
|
import {
|
|
2
7
|
AD_COPY_LIST_USAGE,
|
|
8
|
+
bidModifierFlag,
|
|
9
|
+
customerIdArg,
|
|
3
10
|
failWriteValidation,
|
|
11
|
+
fileArg,
|
|
12
|
+
fileBatchArg,
|
|
13
|
+
fileCreateCommand,
|
|
14
|
+
fileLines,
|
|
15
|
+
handleGoogleError,
|
|
4
16
|
keywordEntries,
|
|
5
17
|
listFlag,
|
|
6
18
|
loadJsonFileArg,
|
|
19
|
+
loadJsonPayloads,
|
|
7
20
|
mergePayload,
|
|
8
21
|
microsFlag,
|
|
9
22
|
requireCustomerId,
|
|
@@ -13,24 +26,22 @@ import {
|
|
|
13
26
|
stageGoogleOp,
|
|
14
27
|
stageGoogleOps,
|
|
15
28
|
stageTarget,
|
|
16
|
-
stageUpdate
|
|
17
|
-
|
|
29
|
+
stageUpdate,
|
|
30
|
+
targetEntries
|
|
31
|
+
} from "./chunk-4KCKNAVW.js";
|
|
18
32
|
import {
|
|
19
33
|
repeatedFlagValues,
|
|
20
34
|
repeatedValues
|
|
21
35
|
} from "./chunk-ERSX3MKH.js";
|
|
22
36
|
import {
|
|
23
37
|
booleanWordPositionalError
|
|
24
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-XY7BZSDQ.js";
|
|
25
39
|
import "./chunk-4NZ47VXO.js";
|
|
26
40
|
import "./chunk-D5FY4FAK.js";
|
|
27
41
|
import "./chunk-F2S5BLPE.js";
|
|
42
|
+
import "./chunk-7VHPAGPK.js";
|
|
28
43
|
import {
|
|
29
|
-
|
|
30
|
-
ACCOUNT_URL_OPTIONS_NOT_STAGEABLE
|
|
31
|
-
} from "./chunk-XCM6XWOB.js";
|
|
32
|
-
import {
|
|
33
|
-
writeJsonEnvelope
|
|
44
|
+
getManagerIdForCustomer
|
|
34
45
|
} from "./chunk-VFIOJD5M.js";
|
|
35
46
|
import "./chunk-WTOYCUKH.js";
|
|
36
47
|
import "./chunk-DZSIUEGH.js";
|
|
@@ -38,46 +49,231 @@ import "./chunk-VAJBWA2U.js";
|
|
|
38
49
|
import "./chunk-HRYOZUSK.js";
|
|
39
50
|
import "./chunk-VVZBBKRC.js";
|
|
40
51
|
import "./chunk-YBMVHGY3.js";
|
|
41
|
-
import "./chunk-
|
|
52
|
+
import "./chunk-XQIOCIDO.js";
|
|
42
53
|
import {
|
|
43
54
|
ASSET_GROUP_ASSET_FIELD_TYPES,
|
|
55
|
+
CALL_AD_RETIRED_MESSAGE,
|
|
44
56
|
CONVERSION_ACTION_CATEGORIES,
|
|
45
57
|
CONVERSION_ORIGINS,
|
|
58
|
+
GOOGLE_DRAFT_BATCH_MAX,
|
|
46
59
|
NEGATIVE_GEO_TARGET_TYPES,
|
|
47
60
|
POSITIVE_GEO_TARGET_TYPES
|
|
48
|
-
} from "./chunk-
|
|
49
|
-
import
|
|
61
|
+
} from "./chunk-ML7DLFFY.js";
|
|
62
|
+
import {
|
|
63
|
+
apiPost
|
|
64
|
+
} from "./chunk-XBOK23HI.js";
|
|
50
65
|
import "./chunk-IFWP3ZDO.js";
|
|
51
66
|
import "./chunk-NAWWC5I3.js";
|
|
52
|
-
import
|
|
67
|
+
import {
|
|
68
|
+
registerSchema
|
|
69
|
+
} from "./chunk-NU7JSWVA.js";
|
|
53
70
|
import "./chunk-YQSBHWOI.js";
|
|
54
71
|
|
|
55
72
|
// src/commands/ads/google/index.ts
|
|
56
|
-
import { defineCommand as
|
|
73
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
57
74
|
|
|
58
75
|
// src/commands/ads/google/keywords/index.ts
|
|
59
76
|
import { defineCommand } from "citty";
|
|
60
77
|
var keywordsCommand = defineCommand({
|
|
61
78
|
meta: {
|
|
62
79
|
name: "keywords",
|
|
63
|
-
description: `Keyword research
|
|
80
|
+
description: `Keyword research, plus staged keyword changes. Research: discover, metrics, forecast, themes, locations, languages. Changes: list, add, update, pause, resume, remove \u2014 pause/resume/add take many keywords in one call.
|
|
64
81
|
|
|
65
82
|
Examples:
|
|
66
83
|
baker ads google keywords discover --customer-id 1234567890 --seeds "running shoes"
|
|
67
84
|
baker ads google keywords metrics --customer-id 1234567890 --keywords "running shoes,nike shoes"
|
|
85
|
+
baker ads google keywords forecast --customer-id 1234567890 --text "running shoes:PHRASE" --max-cpc 1.2
|
|
86
|
+
baker ads google keywords themes --customer-id 1234567890 --keywords "trail shoes" --ad-groups 111,222
|
|
68
87
|
baker ads google keywords locations
|
|
69
88
|
baker ads google keywords languages`
|
|
70
89
|
},
|
|
71
90
|
subCommands: {
|
|
72
|
-
discover: () => import("./discover-
|
|
73
|
-
metrics: () => import("./metrics-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
discover: () => import("./discover-P6TUGZ45.js").then((m) => m.discoverCommand),
|
|
92
|
+
metrics: () => import("./metrics-73LI743C.js").then((m) => m.metricsCommand),
|
|
93
|
+
forecast: () => import("./forecast-SVD3LMMY.js").then((m) => m.forecastCommand),
|
|
94
|
+
themes: () => import("./themes-2ZV7OCJ3.js").then((m) => m.themesCommand),
|
|
95
|
+
locations: () => import("./locations-6XADFU3V.js").then((m) => m.locationsCommand),
|
|
96
|
+
languages: () => import("./languages-75YJZAHL.js").then((m) => m.languagesCommand)
|
|
76
97
|
}
|
|
77
98
|
});
|
|
78
99
|
|
|
79
|
-
// src/commands/ads/google/
|
|
100
|
+
// src/commands/ads/google/list-commands.ts
|
|
80
101
|
import { defineCommand as defineCommand2 } from "citty";
|
|
102
|
+
var LIST_ARGS = {
|
|
103
|
+
"customer-id": { type: "string", description: "Google Ads customer ID (10 digits, no dashes)" },
|
|
104
|
+
limit: { type: "string", description: "Rows per page (default: the list's page size)" },
|
|
105
|
+
cursor: { type: "string", description: "pagination.cursor from the previous page" },
|
|
106
|
+
all: { type: "boolean", description: "Follow every page (use with --out for large lists)" },
|
|
107
|
+
out: { type: "string", description: "Write rows to a file (.csv, .jsonl, .json)" },
|
|
108
|
+
output: { type: "string", description: "Format: json|csv|jsonl|md", default: "json" },
|
|
109
|
+
"serving-only": {
|
|
110
|
+
type: "boolean",
|
|
111
|
+
description: "Only entities whose whole chain is ENABLED (campaign, ad group, \u2026) \u2014 paused ones are listed by default"
|
|
112
|
+
},
|
|
113
|
+
"no-cache": { type: "boolean", description: "Skip cache" }
|
|
114
|
+
};
|
|
115
|
+
var LIST_SPECS = [
|
|
116
|
+
{
|
|
117
|
+
thing: "campaigns",
|
|
118
|
+
preset: "campaigns",
|
|
119
|
+
description: "List campaigns: id, name, status, channel, bidding, budget and dates",
|
|
120
|
+
example: "baker ads google campaigns list --customer-id 1234567890"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
thing: "budgets",
|
|
124
|
+
preset: "budgets",
|
|
125
|
+
description: "List campaign budgets: id, name, daily amount, delivery, shared or not, campaigns using it",
|
|
126
|
+
example: "baker ads google budgets list --customer-id 1234567890"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
thing: "ad-groups",
|
|
130
|
+
preset: "ad-groups",
|
|
131
|
+
description: "List ad groups under their campaigns: id, name, status, type, default CPC bid",
|
|
132
|
+
example: "baker ads google ad-groups list --customer-id 1234567890"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
thing: "keywords",
|
|
136
|
+
preset: "keywords",
|
|
137
|
+
description: "List positive keywords with the resource name `keywords update|remove` take",
|
|
138
|
+
example: "baker ads google keywords list --customer-id 1234567890 --all --out keywords.csv"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
thing: "negative-keywords",
|
|
142
|
+
preset: (args) => args.level === "adGroup" ? "ad-group-negative-keywords" : "campaign-negative-keywords",
|
|
143
|
+
description: "List negative keywords (campaign level by default) with the resource name `negative-keywords remove` takes",
|
|
144
|
+
extraArgs: { level: { type: "string", description: "campaign (default) | adGroup" } },
|
|
145
|
+
example: "baker ads google negative-keywords list --customer-id 1234567890 --level adGroup"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
thing: "keyword-lists",
|
|
149
|
+
preset: (args) => args.members ? "negative-keyword-lists" : "shared-sets",
|
|
150
|
+
description: "List shared lists (id, name, type, member and campaign counts); --members lists the terms in each",
|
|
151
|
+
extraArgs: {
|
|
152
|
+
members: { type: "boolean", description: "List each negative list's terms with their resource names" }
|
|
153
|
+
},
|
|
154
|
+
example: "baker ads google keyword-lists list --customer-id 1234567890 --members"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
thing: "ads",
|
|
158
|
+
preset: "ads",
|
|
159
|
+
description: "List ads under their ad groups: id, type, name, status, approval, final URLs",
|
|
160
|
+
example: "baker ads google ads list --customer-id 1234567890"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
thing: "assets",
|
|
164
|
+
preset: "assets",
|
|
165
|
+
description: "List the account's asset library: id, name, type, text, final URLs",
|
|
166
|
+
example: "baker ads google assets list --customer-id 1234567890"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
thing: "asset-groups",
|
|
170
|
+
preset: "asset-groups",
|
|
171
|
+
description: "List Performance Max asset groups: id, name, status, final URLs, ad strength",
|
|
172
|
+
example: "baker ads google asset-groups list --customer-id 1234567890"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
thing: "audiences",
|
|
176
|
+
preset: "audiences",
|
|
177
|
+
description: "List audiences (user lists): id, name, type, size on search and display, membership duration",
|
|
178
|
+
example: "baker ads google audiences list --customer-id 1234567890"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
thing: "conversions",
|
|
182
|
+
preset: "conversion-actions",
|
|
183
|
+
description: "List conversion actions: id, name, type, category, origin, status, primary or secondary",
|
|
184
|
+
example: "baker ads google conversions list --customer-id 1234567890"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
thing: "bidding-strategies",
|
|
188
|
+
preset: "bidding-strategies",
|
|
189
|
+
description: "List portfolio bidding strategies: id, name, type, status, campaigns using each",
|
|
190
|
+
example: "baker ads google bidding-strategies list --customer-id 1234567890"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
thing: "labels",
|
|
194
|
+
preset: "labels",
|
|
195
|
+
description: "List labels: id, name, colour, description",
|
|
196
|
+
example: "baker ads google labels list --customer-id 1234567890"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
thing: "merchant-center",
|
|
200
|
+
preset: "merchant-center",
|
|
201
|
+
description: "List the Merchant Center accounts linked to this account \u2014 the merchantId a Shopping or retail Performance Max campaign takes",
|
|
202
|
+
example: "baker ads google merchant-center list --customer-id 1234567890"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
thing: "campaign-criteria",
|
|
206
|
+
preset: "campaign-criteria",
|
|
207
|
+
description: "List campaign targeting and exclusions (locations, languages, devices, schedules, radius, placements, topics, demographics, content types, IP addresses, web pages, brand lists, audiences) with the resource name `campaign-criteria update|remove` take",
|
|
208
|
+
example: "baker ads google campaign-criteria list --customer-id 1234567890"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
thing: "ad-group-criteria",
|
|
212
|
+
preset: "ad-group-criteria",
|
|
213
|
+
description: "List ad group targeting and exclusions other than keywords (placements, topics, demographics, web pages, Demand Gen locations and audiences) with the resource name `ad-group-criteria update|remove` take",
|
|
214
|
+
example: "baker ads google ad-group-criteria list --customer-id 1234567890"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
thing: "account-exclusions",
|
|
218
|
+
preset: "account-exclusions",
|
|
219
|
+
description: "List the account-wide exclusions (sites, apps, YouTube, content types, IP addresses, account negative keyword lists) with the resource name `account-exclusions remove` takes",
|
|
220
|
+
example: "baker ads google account-exclusions list --customer-id 1234567890"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
thing: "ad-group-bid-modifiers",
|
|
224
|
+
preset: "ad-group-bid-modifiers",
|
|
225
|
+
description: "List ad groups' own device bid adjustments with the resource name `ad-group-bid-modifiers update|remove` take",
|
|
226
|
+
example: "baker ads google ad-group-bid-modifiers list --customer-id 1234567890"
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
function listQueryArgs(spec, args) {
|
|
230
|
+
const preset = typeof spec.preset === "function" ? spec.preset(args) : spec.preset;
|
|
231
|
+
return {
|
|
232
|
+
preset,
|
|
233
|
+
"customer-id": args["customer-id"],
|
|
234
|
+
limit: args.limit,
|
|
235
|
+
cursor: args.cursor,
|
|
236
|
+
all: args.all,
|
|
237
|
+
out: args.out,
|
|
238
|
+
output: args.output,
|
|
239
|
+
"no-cache": args["no-cache"],
|
|
240
|
+
"include-paused": !args["serving-only"]
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function listCommand(spec) {
|
|
244
|
+
const args = { ...LIST_ARGS, ...spec.extraArgs };
|
|
245
|
+
registerSchema({
|
|
246
|
+
command: `ads.google.${spec.thing}.list`,
|
|
247
|
+
description: `${spec.description}. Start here: \`${spec.example}\`. One page per call (pagination.cursor \u2192 --cursor for the next; --all --out <file> for everything). Paused entities are included; removed ones are not.`,
|
|
248
|
+
args: Object.fromEntries(
|
|
249
|
+
Object.entries(args).map(([name, arg]) => [
|
|
250
|
+
name,
|
|
251
|
+
{
|
|
252
|
+
type: arg.type,
|
|
253
|
+
description: arg.description ?? "",
|
|
254
|
+
required: false,
|
|
255
|
+
..."default" in arg ? { default: arg.default } : {}
|
|
256
|
+
}
|
|
257
|
+
])
|
|
258
|
+
)
|
|
259
|
+
});
|
|
260
|
+
return defineCommand2({
|
|
261
|
+
meta: { name: "list", description: `${spec.description}.
|
|
262
|
+
|
|
263
|
+
Example:
|
|
264
|
+
${spec.example}` },
|
|
265
|
+
args,
|
|
266
|
+
run: async ({ args: parsed }) => {
|
|
267
|
+
await runQuery(listQueryArgs(spec, parsed));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
var LIST_COMMANDS = Object.fromEntries(
|
|
272
|
+
LIST_SPECS.map((spec) => [spec.thing, listCommand(spec)])
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
// src/commands/ads/google/write-commands.ts
|
|
276
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
81
277
|
|
|
82
278
|
// src/commands/ads/google/ad-copy-flags.ts
|
|
83
279
|
var AD_COPY_LIST_FLAGS = ["headlines", "descriptions", "long-headlines"];
|
|
@@ -184,27 +380,711 @@ function copyListSplitHints(args) {
|
|
|
184
380
|
}
|
|
185
381
|
return hints;
|
|
186
382
|
}
|
|
187
|
-
|
|
188
|
-
// src/commands/ads/google/
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
383
|
+
|
|
384
|
+
// src/commands/ads/google/channel-flags.ts
|
|
385
|
+
function str(args, name) {
|
|
386
|
+
const value = args[name];
|
|
387
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
388
|
+
}
|
|
389
|
+
function bool(args, name) {
|
|
390
|
+
const value = args[name];
|
|
391
|
+
return typeof value === "boolean" ? value : void 0;
|
|
392
|
+
}
|
|
393
|
+
function defined(entries) {
|
|
394
|
+
const kept = Object.entries(entries).filter(([, value]) => value !== void 0);
|
|
395
|
+
return kept.length > 0 ? Object.fromEntries(kept) : void 0;
|
|
396
|
+
}
|
|
397
|
+
var SHOPPING_PRIORITIES = { low: 0, medium: 1, high: 2 };
|
|
398
|
+
var shoppingArgs = {
|
|
399
|
+
"merchant-id": {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "Shopping / retail Performance Max: the Merchant Center account whose products the campaign advertises. It must be linked to the account \u2014 `baker ads google merchant-center list` shows the linked ones."
|
|
402
|
+
},
|
|
403
|
+
"feed-label": {
|
|
404
|
+
type: "string",
|
|
405
|
+
description: 'Shopping / retail Performance Max: only products with this feed label (e.g. "ES").'
|
|
406
|
+
},
|
|
407
|
+
"shopping-priority": {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "Shopping only: low (default) | medium | high \u2014 which Shopping campaign wins when several advertise the same product."
|
|
410
|
+
},
|
|
411
|
+
"local-inventory": {
|
|
412
|
+
type: "boolean",
|
|
413
|
+
description: "Shopping only: also advertise products sold in nearby stores (local inventory ads)."
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
function shoppingSettingFromFlags(args) {
|
|
417
|
+
const priority = str(args, "shopping-priority");
|
|
418
|
+
const campaignPriority = priority === void 0 ? void 0 : SHOPPING_PRIORITIES[priority.toLowerCase()];
|
|
419
|
+
if (priority !== void 0 && campaignPriority === void 0) {
|
|
420
|
+
failWriteValidation("--shopping-priority must be low, medium or high");
|
|
421
|
+
}
|
|
422
|
+
return defined({
|
|
423
|
+
merchantId: str(args, "merchant-id"),
|
|
424
|
+
feedLabel: str(args, "feed-label"),
|
|
425
|
+
campaignPriority,
|
|
426
|
+
enableLocal: bool(args, "local-inventory")
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
var shoppingUpdateArgs = {
|
|
430
|
+
"merchant-id": {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "Not changeable: a campaign keeps the Merchant Center account it was created with."
|
|
433
|
+
},
|
|
434
|
+
"feed-label": shoppingArgs["feed-label"],
|
|
435
|
+
"clear-feed-label": {
|
|
436
|
+
type: "boolean",
|
|
437
|
+
description: "Shopping / retail Performance Max: advertise every feed of the Merchant Center again."
|
|
438
|
+
},
|
|
439
|
+
"shopping-priority": shoppingArgs["shopping-priority"],
|
|
440
|
+
"local-inventory": {
|
|
441
|
+
type: "boolean",
|
|
442
|
+
description: "Shopping only: --local-inventory turns local inventory ads on, --local-inventory=false turns them off."
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
function shoppingSettingUpdateFromFlags(args) {
|
|
446
|
+
if (str(args, "merchant-id") !== void 0) {
|
|
447
|
+
failWriteValidation(
|
|
448
|
+
"A campaign keeps the Merchant Center account it was created with \u2014 Google does not move it. Create a new campaign on the other account instead."
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (args["clear-feed-label"] === true && str(args, "feed-label") !== void 0) {
|
|
452
|
+
failWriteValidation("--clear-feed-label removes the feed label \u2014 pass it OR --feed-label, not both");
|
|
453
|
+
}
|
|
454
|
+
const { merchantId: _fixed, ...rest } = shoppingSettingFromFlags(args) ?? {};
|
|
455
|
+
return defined({ ...rest, ...args["clear-feed-label"] === true ? { feedLabel: "" } : {} });
|
|
456
|
+
}
|
|
457
|
+
var APP_STORES = { google: "GOOGLE_APP_STORE", apple: "APPLE_APP_STORE" };
|
|
458
|
+
var APP_GOALS = {
|
|
459
|
+
installs: "OPTIMIZE_INSTALLS_TARGET_INSTALL_COST",
|
|
460
|
+
"installs-volume": "OPTIMIZE_INSTALLS_WITHOUT_TARGET_INSTALL_COST",
|
|
461
|
+
"in-app": "OPTIMIZE_IN_APP_CONVERSIONS_TARGET_CONVERSION_COST",
|
|
462
|
+
"in-app-install-cost": "OPTIMIZE_IN_APP_CONVERSIONS_TARGET_INSTALL_COST",
|
|
463
|
+
value: "OPTIMIZE_RETURN_ON_ADVERTISING_SPEND"
|
|
464
|
+
};
|
|
465
|
+
var appArgs = {
|
|
466
|
+
"app-id": {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "App campaigns (--channel-type MULTI_CHANNEL --sub-type APP_CAMPAIGN): the app's package name (Android, e.g. com.example.app) or numeric App Store id. Fixed once created."
|
|
469
|
+
},
|
|
470
|
+
"app-store": { type: "string", description: "App campaigns: google | apple" },
|
|
471
|
+
"app-goal": {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "App campaigns: installs (TARGET_CPA = target cost per install) | installs-volume (MAXIMIZE_CONVERSIONS) | in-app (TARGET_CPA per in-app conversion) | in-app-install-cost (TARGET_CPA per install, optimizing in-app) | value (TARGET_ROAS). Each goal takes the one bidding strategy in brackets."
|
|
474
|
+
},
|
|
475
|
+
"app-conversions": {
|
|
476
|
+
type: "string",
|
|
477
|
+
description: "App campaigns with an in-app or value goal: comma-separated ids of the APP's conversion actions to optimize toward (required for those goals)."
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
function appSettingsFromFlags(args) {
|
|
481
|
+
const store = str(args, "app-store");
|
|
482
|
+
const goal = str(args, "app-goal");
|
|
483
|
+
const appStore = store === void 0 ? void 0 : APP_STORES[store.toLowerCase()] ?? store.toUpperCase();
|
|
484
|
+
const biddingStrategyGoalType = goal === void 0 ? void 0 : APP_GOALS[goal.toLowerCase()] ?? goal.toUpperCase();
|
|
485
|
+
const conversions = str(args, "app-conversions")?.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
486
|
+
const appCampaignSetting = defined({ appId: str(args, "app-id"), appStore, biddingStrategyGoalType });
|
|
487
|
+
return {
|
|
488
|
+
...appCampaignSetting ? { appCampaignSetting } : {},
|
|
489
|
+
...conversions && conversions.length > 0 ? { selectiveOptimization: { conversionActions: conversions } } : {}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
var linkedAccountArgs = {
|
|
493
|
+
"travel-account-id": {
|
|
494
|
+
type: "string",
|
|
495
|
+
description: "Travel campaigns (--channel-type TRAVEL): the travel account linked to this Google Ads account."
|
|
496
|
+
},
|
|
497
|
+
"hotel-center-id": {
|
|
498
|
+
type: "string",
|
|
499
|
+
description: "Hotel campaigns (--channel-type HOTEL): the Hotel Center account linked to this Google Ads account."
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
function linkedAccountFromFlags(args) {
|
|
503
|
+
const travel = str(args, "travel-account-id");
|
|
504
|
+
const hotel = str(args, "hotel-center-id");
|
|
505
|
+
return {
|
|
506
|
+
...travel ? { travelCampaignSettings: { travelAccountId: travel } } : {},
|
|
507
|
+
...hotel ? { hotelSetting: { hotelCenterId: hotel } } : {}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function defaultAdStatus(format, status) {
|
|
511
|
+
if (status !== void 0) {
|
|
512
|
+
return status;
|
|
513
|
+
}
|
|
514
|
+
return format === "app" ? "ENABLED" : void 0;
|
|
515
|
+
}
|
|
516
|
+
var dynamicSearchAdsArgs = {
|
|
517
|
+
"dsa-domain": {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "Search: the site dynamic ads are built from, as a bare domain (withbaker.com \u2014 no https://, no path)."
|
|
520
|
+
},
|
|
521
|
+
"dsa-language": {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: `Search: the language of that site, Google's lowercase code ("en", "es"; "zh_CN" with a region).`
|
|
524
|
+
},
|
|
525
|
+
"dsa-supplied-urls-only": {
|
|
526
|
+
type: "boolean",
|
|
527
|
+
description: "Search: build dynamic ads only from the pages a page feed lists, not the whole site."
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
function dynamicSearchAdsFromFlags(args) {
|
|
531
|
+
return defined({
|
|
532
|
+
domainName: str(args, "dsa-domain"),
|
|
533
|
+
languageCode: str(args, "dsa-language"),
|
|
534
|
+
useSuppliedUrlsOnly: bool(args, "dsa-supplied-urls-only")
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
var frequencyCapArgs = {
|
|
538
|
+
"frequency-cap": {
|
|
539
|
+
type: "string",
|
|
540
|
+
description: `Display (and existing Video): how often one person sees the ads \u2014 "<cap>/<day|week|month>[/<campaign|ad-group|ad>][/<impressions|video-views>]", e.g. --frequency-cap 3/day or --frequency-cap 10/week/ad-group. Repeat for several; the list REPLACES the campaign's caps.`
|
|
541
|
+
},
|
|
542
|
+
"clear-frequency-caps": { type: "boolean", description: "Remove every frequency cap from the campaign." }
|
|
543
|
+
};
|
|
544
|
+
var CAP_UNITS = { day: "DAY", week: "WEEK", month: "MONTH" };
|
|
545
|
+
var CAP_LEVELS = { campaign: "CAMPAIGN", "ad-group": "AD_GROUP", ad: "AD_GROUP_AD" };
|
|
546
|
+
var CAP_EVENTS = { impressions: "IMPRESSION", "video-views": "VIDEO_VIEW" };
|
|
547
|
+
function parseCap(entry) {
|
|
548
|
+
const [count, unit, level = "campaign", event = "impressions"] = entry.split("/").map((part) => part.trim().toLowerCase());
|
|
549
|
+
const cap = Number(count);
|
|
550
|
+
const timeUnit = CAP_UNITS[unit ?? ""];
|
|
551
|
+
if (!Number.isInteger(cap) || cap < 1 || !timeUnit || !CAP_LEVELS[level] || !CAP_EVENTS[event]) {
|
|
552
|
+
failWriteValidation(
|
|
553
|
+
`--frequency-cap "${entry}" must be <cap>/<day|week|month>[/<campaign|ad-group|ad>][/<impressions|video-views>] (e.g. 3/day)`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return { level: CAP_LEVELS[level], eventType: CAP_EVENTS[event], timeUnit, cap };
|
|
557
|
+
}
|
|
558
|
+
function frequencyCapsFromFlags(args, rawArgs) {
|
|
559
|
+
const entries = repeatedValues(rawArgs, "frequency-cap", args["frequency-cap"]).filter((v) => v.length > 0);
|
|
560
|
+
if (entries.length > 0 && args["clear-frequency-caps"] === true) {
|
|
561
|
+
failWriteValidation("--clear-frequency-caps removes every cap \u2014 pass it OR --frequency-cap, not both");
|
|
562
|
+
}
|
|
563
|
+
if (entries.length === 0) {
|
|
564
|
+
return args["clear-frequency-caps"] === true ? [] : void 0;
|
|
565
|
+
}
|
|
566
|
+
return entries.map(parseCap);
|
|
567
|
+
}
|
|
568
|
+
var assetAutomationArgs = {
|
|
569
|
+
"generated-text": {
|
|
570
|
+
type: "boolean",
|
|
571
|
+
description: "Performance Max: --generated-text lets Google write text from the landing page and assets; --generated-text=false stops it (needs --url-expansion=false with it)."
|
|
572
|
+
},
|
|
573
|
+
"url-expansion": {
|
|
574
|
+
type: "boolean",
|
|
575
|
+
description: "Performance Max: --url-expansion=false keeps every click on the asset groups' final URLs; --url-expansion lets Google send traffic to the most relevant page of the site."
|
|
576
|
+
},
|
|
577
|
+
"video-enhancement": {
|
|
578
|
+
type: "boolean",
|
|
579
|
+
description: "Performance Max: Google makes enhanced versions of the campaign's YouTube videos (=false stops it)."
|
|
580
|
+
},
|
|
581
|
+
"image-enhancement": {
|
|
582
|
+
type: "boolean",
|
|
583
|
+
description: "Performance Max: Google enhances the campaign's images (=false stops it)."
|
|
584
|
+
},
|
|
585
|
+
"image-extraction": {
|
|
586
|
+
type: "boolean",
|
|
587
|
+
description: "Performance Max: Google takes images from the landing page (=false stops it)."
|
|
588
|
+
},
|
|
589
|
+
"image-design-versions": {
|
|
590
|
+
type: "boolean",
|
|
591
|
+
description: "Performance Max: Google makes new designs from the campaign's images (=false stops it)."
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
function assetAutomationFromFlags(args) {
|
|
595
|
+
return defined({
|
|
596
|
+
textAssets: bool(args, "generated-text"),
|
|
597
|
+
finalUrlExpansion: bool(args, "url-expansion"),
|
|
598
|
+
videoEnhancement: bool(args, "video-enhancement"),
|
|
599
|
+
imageEnhancement: bool(args, "image-enhancement"),
|
|
600
|
+
imageExtraction: bool(args, "image-extraction"),
|
|
601
|
+
imageDesignVersions: bool(args, "image-design-versions")
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
var brandStyleArgs = {
|
|
605
|
+
"main-color": {
|
|
606
|
+
type: "string",
|
|
607
|
+
description: "Performance Max brand guidelines: main color, #RRGGBB (with --accent-color)."
|
|
608
|
+
},
|
|
609
|
+
"accent-color": {
|
|
610
|
+
type: "string",
|
|
611
|
+
description: "Performance Max brand guidelines: accent color, #RRGGBB (with --main-color)."
|
|
612
|
+
},
|
|
613
|
+
font: {
|
|
614
|
+
type: "string",
|
|
615
|
+
description: `Performance Max brand guidelines: one of Google's fonts, e.g. "Roboto".`
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
var brandGuidelinesCreateArgs = {
|
|
619
|
+
"brand-guidelines": {
|
|
620
|
+
type: "boolean",
|
|
621
|
+
description: "Performance Max: create the campaign with brand guidelines on \u2014 link its business name and a square logo at campaign level in the same draft (assets attach --file with level campaign, fieldType BUSINESS_NAME and LOGO)."
|
|
622
|
+
},
|
|
623
|
+
...brandStyleArgs
|
|
624
|
+
};
|
|
625
|
+
function brandGuidelinesFromFlags(args, mode) {
|
|
626
|
+
const style = {
|
|
627
|
+
mainColor: str(args, "main-color"),
|
|
628
|
+
accentColor: str(args, "accent-color"),
|
|
629
|
+
fontFamily: str(args, "font")
|
|
630
|
+
};
|
|
631
|
+
if (mode === "update") {
|
|
632
|
+
return defined(style);
|
|
633
|
+
}
|
|
634
|
+
const enabled = bool(args, "brand-guidelines");
|
|
635
|
+
const stated = defined(style);
|
|
636
|
+
if (enabled === void 0 && !stated) {
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
return { enabled: enabled ?? false, ...stated };
|
|
640
|
+
}
|
|
641
|
+
var list = (args, name) => str(args, name)?.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
642
|
+
var enableBrandGuidelinesArgs = {
|
|
643
|
+
"auto-populate": {
|
|
644
|
+
type: "boolean",
|
|
645
|
+
description: "Let Google take the business name and logo from the campaign's asset groups (needs at least one asset group)."
|
|
646
|
+
},
|
|
647
|
+
"business-name-asset": {
|
|
648
|
+
type: "string",
|
|
649
|
+
description: "The business name text asset (id) to link to the campaign."
|
|
650
|
+
},
|
|
651
|
+
"logo-assets": { type: "string", description: "Comma-separated square logo image asset ids (1\u20135)." },
|
|
652
|
+
"landscape-logo-assets": { type: "string", description: "Comma-separated landscape logo image asset ids." },
|
|
653
|
+
"final-url-domain": { type: "string", description: "The site's domain, e.g. withbaker.com." },
|
|
654
|
+
...brandStyleArgs
|
|
655
|
+
};
|
|
656
|
+
function enableBrandGuidelinesFromFlags(args) {
|
|
657
|
+
return defined({
|
|
658
|
+
autoPopulateBrandAssets: bool(args, "auto-populate"),
|
|
659
|
+
businessNameAsset: str(args, "business-name-asset"),
|
|
660
|
+
logoAssets: list(args, "logo-assets"),
|
|
661
|
+
landscapeLogoAssets: list(args, "landscape-logo-assets"),
|
|
662
|
+
finalUriDomain: str(args, "final-url-domain"),
|
|
663
|
+
mainColor: str(args, "main-color"),
|
|
664
|
+
accentColor: str(args, "accent-color"),
|
|
665
|
+
fontFamily: str(args, "font")
|
|
666
|
+
}) ?? {};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/commands/ads/google/criteria-commands.ts
|
|
670
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
671
|
+
var CRITERION_FILE_HELP = 'The `criterion` object is one `criterionType` with its fields, e.g. {"criterionType":"placement","url":"example.com"}, {"criterionType":"ageRange","ageRange":"AGE_RANGE_18_24"}, {"criterionType":"topic","topicConstant":"5"} \u2014 every type with an example: __tooling__/docs/tools/baker/ads-google.md \xA7 Targeting and exclusions.';
|
|
672
|
+
var CRITERION_HINTS = [
|
|
673
|
+
"Run `baker ads google draft validate` once the targeting is staged: Google's dry run is the only thing that proves a criterion is accepted on this campaign before the user publishes."
|
|
674
|
+
];
|
|
675
|
+
var campaignCriteriaCommand = defineCommand3({
|
|
676
|
+
meta: {
|
|
677
|
+
name: "campaign-criteria",
|
|
678
|
+
description: "Stage what a campaign targets or excludes \u2014 locations, languages, ad schedule, devices, a radius, carriers, operating systems, and exclusions (placements, apps, YouTube, topics, demographics, content types, IP addresses, web pages, brand lists) \u2014 or change a criterion's bid adjustment"
|
|
679
|
+
},
|
|
680
|
+
subCommands: {
|
|
681
|
+
add: fileCreateCommand(
|
|
682
|
+
"add",
|
|
683
|
+
"google.campaignCriterion.add",
|
|
684
|
+
`Add a campaign criterion. Start here: --file '{"campaign":"23837707642","negative":true,"criterion":{"criterionType":"placement","url":"example.com"}}'. A bid adjustment rides on the criterion ("bidModifier": 1.2 on a location, schedule, device or radius). Audience lists, interests and custom segments go through \`audiences attach\`. ${CRITERION_FILE_HELP}`,
|
|
685
|
+
CRITERION_HINTS
|
|
686
|
+
),
|
|
687
|
+
update: defineCommand3({
|
|
688
|
+
meta: {
|
|
689
|
+
name: "update",
|
|
690
|
+
description: "Change a criterion's bid adjustment \u2014 the only way to change a device setting the campaign already has, since Google never lets one be removed. Start here: `campaign-criteria update customers/<cid>/campaignCriteria/<campaignId>~<criterionId> --bid-modifier 1` resets a boost to neutral; --bid-modifier 0 stops ads serving on that device. Use `campaign-criteria add` instead when the campaign has no criterion for that device yet. Read the current ones with `baker ads google campaign-criteria list`."
|
|
691
|
+
},
|
|
692
|
+
args: {
|
|
693
|
+
...customerIdArg,
|
|
694
|
+
id: {
|
|
695
|
+
type: "positional",
|
|
696
|
+
description: "Criterion resource name (customers/\u2026/campaignCriteria/{campaignId}~{criterionId})",
|
|
697
|
+
required: false
|
|
698
|
+
},
|
|
699
|
+
"bid-modifier": {
|
|
700
|
+
type: "string",
|
|
701
|
+
description: "1 = no adjustment, 1.25 = bid 25% more, 0.9 = bid 10% less, 0 = don't serve on it (a device only; 0.1\u201310 elsewhere)"
|
|
702
|
+
}
|
|
703
|
+
},
|
|
704
|
+
run: async ({ args }) => {
|
|
705
|
+
const customerId = requireCustomerId(args);
|
|
706
|
+
const target = requireTarget(args, "campaign criterion");
|
|
707
|
+
await stageUpdate("google.campaignCriterion.update", customerId, target, {
|
|
708
|
+
bidModifier: bidModifierFlag(args["bid-modifier"])
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
}),
|
|
712
|
+
remove: defineCommand3({
|
|
713
|
+
meta: {
|
|
714
|
+
name: "remove",
|
|
715
|
+
description: "Stage a campaign criterion remove. Works for what you added \u2014 locations, languages, ad schedules, exclusions. A device setting CANNOT be removed once the campaign has one: use `campaign-criteria update --bid-modifier` to change or neutralise it instead."
|
|
716
|
+
},
|
|
717
|
+
args: {
|
|
718
|
+
...customerIdArg,
|
|
719
|
+
id: { type: "positional", description: "Criterion resource name", required: false }
|
|
720
|
+
},
|
|
721
|
+
run: async ({ args }) => {
|
|
722
|
+
const customerId = requireCustomerId(args);
|
|
723
|
+
await stageTarget("google.campaignCriterion.remove", customerId, requireTarget(args, "campaign criterion"));
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
});
|
|
728
|
+
function statusFlag(value) {
|
|
729
|
+
if (value === void 0) {
|
|
730
|
+
return void 0;
|
|
731
|
+
}
|
|
732
|
+
if (value !== "ENABLED" && value !== "PAUSED") {
|
|
733
|
+
failWriteValidation("--status must be ENABLED or PAUSED");
|
|
734
|
+
}
|
|
735
|
+
return value;
|
|
736
|
+
}
|
|
737
|
+
var adGroupCriteriaCommand = defineCommand3({
|
|
738
|
+
meta: {
|
|
739
|
+
name: "ad-group-criteria",
|
|
740
|
+
description: "Stage what an ad group targets or excludes \u2014 placements, apps, YouTube channels and videos, topics, demographics (age, gender, parental status, household income), web pages for Dynamic Search Ads, brand lists, and on Demand Gen its locations, languages and combined audience \u2014 or change one's bid adjustment, max CPC or status"
|
|
741
|
+
},
|
|
742
|
+
subCommands: {
|
|
743
|
+
add: fileCreateCommand(
|
|
744
|
+
"add",
|
|
745
|
+
"google.adGroupCriterion.add",
|
|
746
|
+
`Add an ad group criterion. Start here: --file '{"adGroup":"154000000001","criterion":{"criterionType":"placement","url":"example.com","bidModifier":1.2}}' \u2014 add "negative":true to exclude it instead, "status":"PAUSED" to add it paused, "cpcBidMicros" for a Display placement's own max CPC. On Demand Gen, locations and languages go here, not on the campaign. Audience lists, interests and custom segments go through \`audiences attach\`. ${CRITERION_FILE_HELP}`,
|
|
747
|
+
CRITERION_HINTS
|
|
748
|
+
),
|
|
749
|
+
update: defineCommand3({
|
|
750
|
+
meta: {
|
|
751
|
+
name: "update",
|
|
752
|
+
description: "Change a live ad group criterion's bid adjustment, own max CPC or status. Start here: `ad-group-criteria update customers/<cid>/adGroupCriteria/<adGroupId>~<criterionId> --bid-modifier 1.3`. What it targets cannot change \u2014 remove it and add the replacement. Read them with `baker ads google ad-group-criteria list`."
|
|
753
|
+
},
|
|
754
|
+
args: {
|
|
755
|
+
...customerIdArg,
|
|
756
|
+
id: {
|
|
757
|
+
type: "positional",
|
|
758
|
+
description: "Criterion resource name (customers/\u2026/adGroupCriteria/{adGroupId}~{criterionId})",
|
|
759
|
+
required: false
|
|
760
|
+
},
|
|
761
|
+
"bid-modifier": { type: "string", description: "1 = no adjustment, 1.25 = bid 25% more, 0.9 = 10% less" },
|
|
762
|
+
"max-cpc": { type: "string", description: "The criterion's own max CPC, in the account currency" },
|
|
763
|
+
status: { type: "string", description: "ENABLED | PAUSED" }
|
|
764
|
+
},
|
|
765
|
+
run: async ({ args }) => {
|
|
766
|
+
const customerId = requireCustomerId(args);
|
|
767
|
+
const target = requireTarget(args, "ad group criterion");
|
|
768
|
+
const payload = {
|
|
769
|
+
...args["bid-modifier"] !== void 0 ? { bidModifier: bidModifierFlag(args["bid-modifier"]) } : {},
|
|
770
|
+
...args["max-cpc"] !== void 0 ? { cpcBidMicros: microsFlag(args["max-cpc"], "--max-cpc") } : {},
|
|
771
|
+
...args.status !== void 0 ? { status: statusFlag(args.status) } : {}
|
|
772
|
+
};
|
|
773
|
+
if (Object.keys(payload).length === 0) {
|
|
774
|
+
failWriteValidation("pass --bid-modifier, --max-cpc or --status");
|
|
775
|
+
}
|
|
776
|
+
await stageUpdate("google.adGroupCriterion.update", customerId, target, payload);
|
|
777
|
+
}
|
|
778
|
+
}),
|
|
779
|
+
remove: defineCommand3({
|
|
780
|
+
meta: { name: "remove", description: "Stage an ad group criterion remove (targeting or exclusion)." },
|
|
781
|
+
args: {
|
|
782
|
+
...customerIdArg,
|
|
783
|
+
id: {
|
|
784
|
+
type: "positional",
|
|
785
|
+
description: "Criterion resource name (customers/\u2026/adGroupCriteria/{adGroupId}~{criterionId})",
|
|
786
|
+
required: false
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
run: async ({ args }) => {
|
|
790
|
+
const customerId = requireCustomerId(args);
|
|
791
|
+
await stageTarget("google.adGroupCriterion.remove", customerId, requireTarget(args, "ad group criterion"));
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
var DEVICES = ["MOBILE", "TABLET", "DESKTOP", "CONNECTED_TV"];
|
|
797
|
+
var adGroupBidModifiersCommand = defineCommand3({
|
|
798
|
+
meta: {
|
|
799
|
+
name: "ad-group-bid-modifiers",
|
|
800
|
+
description: "Stage an ad group's own device bid adjustment (bid more on mobile in this ad group only), change it, or remove it"
|
|
801
|
+
},
|
|
802
|
+
subCommands: {
|
|
803
|
+
add: defineCommand3({
|
|
804
|
+
meta: {
|
|
805
|
+
name: "add",
|
|
806
|
+
description: "Bid differently on one device in this ad group. Start here: `ad-group-bid-modifiers add --customer-id 1234567890 --ad-group 154000000001 --device MOBILE --bid-modifier 1.2`. 0 stops this ad group serving on that device. Search and Shopping take MOBILE, TABLET, DESKTOP; Display also CONNECTED_TV; Demand Gen takes none (set it on the campaign with `campaign-criteria add`)."
|
|
807
|
+
},
|
|
808
|
+
args: {
|
|
809
|
+
...customerIdArg,
|
|
810
|
+
"ad-group": { type: "string", description: "Ad group id or a g_temp_* ref" },
|
|
811
|
+
device: { type: "string", description: DEVICES.join(" | ") },
|
|
812
|
+
"bid-modifier": { type: "string", description: "1.2 = bid 20% more, 0.8 = 20% less, 0 = don't serve on it" }
|
|
813
|
+
},
|
|
814
|
+
run: async ({ args }) => {
|
|
815
|
+
const customerId = requireCustomerId(args);
|
|
816
|
+
const device = requireStringFlag(args.device, "--device");
|
|
817
|
+
if (!DEVICES.includes(device)) {
|
|
818
|
+
failWriteValidation(`--device must be one of ${DEVICES.join(", ")}`);
|
|
819
|
+
}
|
|
820
|
+
await stageGoogleOp({
|
|
821
|
+
kind: "google.adGroupBidModifier.add",
|
|
822
|
+
customerId,
|
|
823
|
+
payload: {
|
|
824
|
+
adGroup: requireStringFlag(args["ad-group"], "--ad-group"),
|
|
825
|
+
device,
|
|
826
|
+
bidModifier: bidModifierFlag(args["bid-modifier"])
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}),
|
|
831
|
+
update: defineCommand3({
|
|
832
|
+
meta: {
|
|
833
|
+
name: "update",
|
|
834
|
+
description: "Change an ad group device bid. Start here: `ad-group-bid-modifiers update customers/<cid>/adGroupBidModifiers/<adGroupId>~<criterionId> --bid-modifier 1`."
|
|
835
|
+
},
|
|
836
|
+
args: {
|
|
837
|
+
...customerIdArg,
|
|
838
|
+
id: {
|
|
839
|
+
type: "positional",
|
|
840
|
+
description: "customers/\u2026/adGroupBidModifiers/{adGroupId}~{criterionId}",
|
|
841
|
+
required: false
|
|
842
|
+
},
|
|
843
|
+
"bid-modifier": { type: "string", description: "1 = no adjustment, 0 = don't serve on it" }
|
|
844
|
+
},
|
|
845
|
+
run: async ({ args }) => {
|
|
846
|
+
const customerId = requireCustomerId(args);
|
|
847
|
+
await stageUpdate(
|
|
848
|
+
"google.adGroupBidModifier.update",
|
|
849
|
+
customerId,
|
|
850
|
+
requireTarget(args, "ad group bid modifier"),
|
|
851
|
+
{ bidModifier: bidModifierFlag(args["bid-modifier"]) }
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}),
|
|
855
|
+
remove: defineCommand3({
|
|
856
|
+
meta: {
|
|
857
|
+
name: "remove",
|
|
858
|
+
description: "Stage an ad group device bid remove \u2014 the ad group bids like its campaign again."
|
|
859
|
+
},
|
|
860
|
+
args: {
|
|
861
|
+
...customerIdArg,
|
|
862
|
+
id: {
|
|
863
|
+
type: "positional",
|
|
864
|
+
description: "customers/\u2026/adGroupBidModifiers/{adGroupId}~{criterionId}",
|
|
865
|
+
required: false
|
|
866
|
+
}
|
|
867
|
+
},
|
|
868
|
+
run: async ({ args }) => {
|
|
869
|
+
const customerId = requireCustomerId(args);
|
|
870
|
+
await stageTarget("google.adGroupBidModifier.remove", customerId, requireTarget(args, "ad group bid modifier"));
|
|
871
|
+
}
|
|
872
|
+
})
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
function criteriaFromFlags(args, flags) {
|
|
876
|
+
return flags.flatMap(({ flag, criterion }) => {
|
|
877
|
+
const value = args[flag];
|
|
878
|
+
return typeof value === "string" ? value.split(",").map((part) => part.trim()).filter((part) => part.length > 0).map(criterion) : [];
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
var PLACEMENT_FLAGS = [
|
|
882
|
+
{ flag: "placement", criterion: (url) => ({ criterionType: "placement", url }) },
|
|
883
|
+
{ flag: "youtube-channel", criterion: (channelId) => ({ criterionType: "youtubeChannel", channelId }) },
|
|
884
|
+
{ flag: "youtube-video", criterion: (videoId) => ({ criterionType: "youtubeVideo", videoId }) },
|
|
885
|
+
{ flag: "app", criterion: (appId) => ({ criterionType: "mobileApp", appId }) }
|
|
886
|
+
];
|
|
887
|
+
var ACCOUNT_EXCLUSION_FLAGS = [
|
|
888
|
+
...PLACEMENT_FLAGS,
|
|
889
|
+
{ flag: "content-label", criterion: (contentLabel) => ({ criterionType: "contentLabel", contentLabel }) },
|
|
890
|
+
{ flag: "ip", criterion: (ipAddress) => ({ criterionType: "ipBlock", ipAddress }) }
|
|
891
|
+
];
|
|
892
|
+
var LIST_MEMBER_FLAGS = [
|
|
893
|
+
...PLACEMENT_FLAGS,
|
|
894
|
+
{ flag: "brand", criterion: (entityId) => ({ criterionType: "brand", entityId }) }
|
|
895
|
+
];
|
|
896
|
+
function accountExclusionCriteria(args) {
|
|
897
|
+
return criteriaFromFlags(args, ACCOUNT_EXCLUSION_FLAGS);
|
|
898
|
+
}
|
|
899
|
+
function listMemberCriteria(args) {
|
|
900
|
+
return criteriaFromFlags(args, LIST_MEMBER_FLAGS);
|
|
901
|
+
}
|
|
902
|
+
var LIST_MEMBER_ARGS = {
|
|
903
|
+
placement: { type: "string", description: "Sites for a NEGATIVE_PLACEMENTS list (example.com), comma-separated" },
|
|
904
|
+
"youtube-channel": {
|
|
905
|
+
type: "string",
|
|
906
|
+
description: "YouTube channel ids for a NEGATIVE_PLACEMENTS list, comma-separated"
|
|
907
|
+
},
|
|
908
|
+
"youtube-video": { type: "string", description: "YouTube video ids for a NEGATIVE_PLACEMENTS list, comma-separated" },
|
|
909
|
+
app: { type: "string", description: "App ids (1-<App Store id> / 2-<package>) for a NEGATIVE_PLACEMENTS list" },
|
|
910
|
+
brand: {
|
|
911
|
+
type: "string",
|
|
912
|
+
description: "Brand entity ids (/g/\u2026) for a BRANDS list, comma-separated \u2014 find them with `baker ads google brands`"
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
var accountExclusionsCommand = defineCommand3({
|
|
916
|
+
meta: {
|
|
917
|
+
name: "account-exclusions",
|
|
918
|
+
description: "Stage exclusions for the WHOLE account \u2014 sites, apps, YouTube channels/videos, content types, IP addresses, an account-level negative keyword list \u2014 or remove one. They reach every campaign, Performance Max included (which takes no placement exclusion of its own)."
|
|
919
|
+
},
|
|
920
|
+
subCommands: {
|
|
921
|
+
add: defineCommand3({
|
|
922
|
+
meta: {
|
|
923
|
+
name: "add",
|
|
924
|
+
description: `Exclude across the whole account. Start here: \`account-exclusions add --customer-id 1234567890 --placement "example.com,spam-site.net" --content-label SEXUALLY_SUGGESTIVE\`. Flags take comma-separated values and stage one exclusion each; --file takes {"criterion": {...}} objects (an array for many) for app categories or an account-level negative keyword list ({"criterionType":"negativeKeywordList","sharedSet":"<ACCOUNT_LEVEL_NEGATIVE_KEYWORDS list>"}). Read what is already excluded with \`account-exclusions list\` first \u2014 they apply to every campaign. ${CRITERION_FILE_HELP}`
|
|
925
|
+
},
|
|
926
|
+
args: {
|
|
927
|
+
...customerIdArg,
|
|
928
|
+
...fileBatchArg,
|
|
929
|
+
placement: { type: "string", description: "Sites to exclude (example.com), comma-separated" },
|
|
930
|
+
"youtube-channel": { type: "string", description: "YouTube channel ids (UC\u2026), comma-separated" },
|
|
931
|
+
"youtube-video": { type: "string", description: "YouTube video ids, comma-separated" },
|
|
932
|
+
app: { type: "string", description: "App ids (1-<App Store id> / 2-<package>), comma-separated" },
|
|
933
|
+
"content-label": {
|
|
934
|
+
type: "string",
|
|
935
|
+
description: "Content types: SEXUALLY_SUGGESTIVE, BELOW_THE_FOLD, PARKED_DOMAIN, JUVENILE, PROFANITY, TRAGEDY, VIDEO_RATING_DV_*, EMBEDDED_VIDEO, LIVE_STREAMING_VIDEO, SOCIAL_ISSUES, BRAND_SUITABILITY_* (comma-separated)"
|
|
936
|
+
},
|
|
937
|
+
ip: { type: "string", description: "IP addresses to exclude, comma-separated" }
|
|
938
|
+
},
|
|
939
|
+
run: async ({ args }) => {
|
|
940
|
+
const customerId = requireCustomerId(args);
|
|
941
|
+
const payloads = [
|
|
942
|
+
...args.file ? loadJsonPayloads(args.file) : [],
|
|
943
|
+
...accountExclusionCriteria(args).map((criterion) => ({ criterion }))
|
|
944
|
+
];
|
|
945
|
+
if (payloads.length === 0) {
|
|
946
|
+
failWriteValidation(
|
|
947
|
+
"name what to exclude: --placement, --youtube-channel, --youtube-video, --app, --content-label, --ip or --file"
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
await stageGoogleOps(
|
|
951
|
+
payloads.map((payload) => ({ kind: "google.customerNegativeCriterion.add", customerId, payload })),
|
|
952
|
+
["Account exclusions reach every campaign in the account \u2014 say so when you report them."]
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
}),
|
|
956
|
+
remove: defineCommand3({
|
|
957
|
+
meta: {
|
|
958
|
+
name: "remove",
|
|
959
|
+
description: "Stage removing an account exclusion. Start here: `account-exclusions remove customers/<cid>/customerNegativeCriteria/<criterionId>` (read it with `account-exclusions list`)."
|
|
960
|
+
},
|
|
961
|
+
args: {
|
|
962
|
+
...customerIdArg,
|
|
963
|
+
id: { type: "positional", description: "customers/\u2026/customerNegativeCriteria/{criterionId}", required: false }
|
|
964
|
+
},
|
|
965
|
+
run: async ({ args }) => {
|
|
966
|
+
const customerId = requireCustomerId(args);
|
|
967
|
+
await stageTarget(
|
|
968
|
+
"google.customerNegativeCriterion.remove",
|
|
969
|
+
customerId,
|
|
970
|
+
requireTarget(args, "account exclusion")
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
})
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
// src/commands/ads/google/keyword-list-members.ts
|
|
978
|
+
var MATCH_TYPES = /* @__PURE__ */ new Set(["EXACT", "PHRASE", "BROAD"]);
|
|
979
|
+
function parseTerm(raw) {
|
|
980
|
+
const colon = raw.lastIndexOf(":");
|
|
981
|
+
const suffix = colon > 0 ? raw.slice(colon + 1).trim().toUpperCase() : void 0;
|
|
982
|
+
return suffix && MATCH_TYPES.has(suffix) ? { text: raw.slice(0, colon).trim(), matchType: suffix } : { text: raw.trim() };
|
|
983
|
+
}
|
|
984
|
+
var normalized = (text) => text.trim().replace(/\s+/g, " ").toLowerCase();
|
|
985
|
+
function memberRemovals(rows, terms) {
|
|
986
|
+
const targets = [];
|
|
987
|
+
const missing = [];
|
|
988
|
+
for (const raw of terms) {
|
|
989
|
+
const term = parseTerm(raw);
|
|
990
|
+
const found = rows.filter(
|
|
991
|
+
(row) => normalized(String(row["shared_criterion.keyword.text"] ?? "")) === normalized(term.text) && (!term.matchType || row["shared_criterion.keyword.match_type"] === term.matchType)
|
|
992
|
+
);
|
|
993
|
+
if (found.length === 0) {
|
|
994
|
+
missing.push(raw);
|
|
995
|
+
}
|
|
996
|
+
for (const row of found) {
|
|
997
|
+
const name = String(row["shared_criterion.resource_name"] ?? "");
|
|
998
|
+
if (name && !targets.includes(name)) {
|
|
999
|
+
targets.push(name);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return { targets, missing };
|
|
1004
|
+
}
|
|
1005
|
+
function listIdOf(listRef) {
|
|
1006
|
+
const id = listRef.split("/").pop() ?? "";
|
|
1007
|
+
if (!/^\d+$/.test(id)) {
|
|
1008
|
+
failWriteValidation(
|
|
1009
|
+
`--list-ref must be the list's id or customers/{cid}/sharedSets/{id} \u2014 a list staged in this chat has no published members yet, so take a term out of it with \`draft remove\` on the member's add instead`
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
return id;
|
|
1013
|
+
}
|
|
1014
|
+
function memberTarget(customerId, listId, value) {
|
|
1015
|
+
if (value.startsWith("customers/")) {
|
|
1016
|
+
if (!value.includes("/sharedCriteria/")) {
|
|
1017
|
+
failWriteValidation(
|
|
1018
|
+
`${value} is not a keyword list member \u2014 members are customers/{cid}/sharedCriteria/{listId}~{criterionId}`
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
return value;
|
|
1022
|
+
}
|
|
1023
|
+
if (/^\d+~\d+$/.test(value)) {
|
|
1024
|
+
return `customers/${customerId}/sharedCriteria/${value}`;
|
|
1025
|
+
}
|
|
1026
|
+
if (/^\d+$/.test(value)) {
|
|
1027
|
+
return `customers/${customerId}/sharedCriteria/${listId}~${value}`;
|
|
1028
|
+
}
|
|
1029
|
+
failWriteValidation(
|
|
1030
|
+
`"${value}" is not a member resource name, listId~criterionId or criterion id \u2014 pass terms with --text`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
async function readListMembers(customerId, listId) {
|
|
1034
|
+
const query = `SELECT shared_criterion.resource_name, shared_criterion.keyword.text, shared_criterion.keyword.match_type FROM shared_criterion WHERE shared_set.id = ${listId}`;
|
|
1035
|
+
const managerId = getManagerIdForCustomer(customerId);
|
|
1036
|
+
const rows = [];
|
|
1037
|
+
let pageToken;
|
|
1038
|
+
do {
|
|
1039
|
+
const response = await apiPost("/api/ads/google/query", {
|
|
1040
|
+
customerId,
|
|
1041
|
+
query,
|
|
1042
|
+
pageSize: 8192,
|
|
1043
|
+
skipCache: true,
|
|
1044
|
+
...managerId ? { managerId } : {},
|
|
1045
|
+
...pageToken ? { pageToken } : {}
|
|
1046
|
+
});
|
|
1047
|
+
rows.push(...response.rows ?? []);
|
|
1048
|
+
pageToken = response.pageToken;
|
|
1049
|
+
} while (pageToken);
|
|
1050
|
+
return rows;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// src/commands/ads/google/write-commands.ts
|
|
1054
|
+
var STAGED_WRITE = "Stages only \u2014 applies to the live account when the chat is published. Field contract: `baker capabilities google-ads`.";
|
|
1055
|
+
var BUDGET_DEMAND_HINTS = [
|
|
1056
|
+
"Check this daily budget against the search volume you measured for these keywords, not against the plan you had before you measured it. A budget many times larger than the traffic the target pool can absorb never spends, and reads to the client as a number nobody costed.",
|
|
1057
|
+
"No volume measured yet? Pull it first \u2014 `baker ads google query --preset search-terms` for what the account already captures, `baker ads google keywords metrics` for the market estimate (those are planner figures, never account performance)."
|
|
1058
|
+
];
|
|
1059
|
+
function budgetAmountsFromFlags(args) {
|
|
1060
|
+
if (args.amount !== void 0 && args.total !== void 0) {
|
|
1061
|
+
failWriteValidation(
|
|
1062
|
+
"pass --amount OR --total: a budget is either daily or one total for the campaign's flight, never both"
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const amountMicros = microsFlag(args.amount, "--amount");
|
|
1066
|
+
const totalAmountMicros = microsFlag(args.total, "--total");
|
|
1067
|
+
return {
|
|
1068
|
+
...amountMicros !== void 0 ? { amountMicros } : {},
|
|
1069
|
+
...totalAmountMicros !== void 0 ? { totalAmountMicros } : {}
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
var totalArg = {
|
|
1073
|
+
total: {
|
|
192
1074
|
type: "string",
|
|
193
|
-
description: "
|
|
1075
|
+
description: "A TOTAL budget instead of a daily one: one amount in major units spent across the campaign's start and end dates (e.g. 300). Search, Performance Max and Demand Gen only; the campaign needs --end-date; never shared. A budget stays daily or total for life."
|
|
194
1076
|
}
|
|
195
1077
|
};
|
|
196
|
-
var
|
|
197
|
-
|
|
198
|
-
"Check this daily budget against the search volume you measured for these keywords, not against the plan you had before you measured it. A budget many times larger than the traffic the target pool can absorb never spends, and reads to the client as a number nobody costed.",
|
|
199
|
-
"No volume measured yet? Pull it first \u2014 `baker ads google query --preset search-terms` for what the account already captures, `baker ads google keywords metrics` for the market estimate (those are planner figures, never account performance)."
|
|
1078
|
+
var BUDGET_REMOVE_HINTS = [
|
|
1079
|
+
"A budget can be removed only once no campaign uses it: staging refuses it while one does, unless this draft removes those campaigns or moves them to another budget (publish sends the campaign changes first)."
|
|
200
1080
|
];
|
|
201
|
-
var budgetsCommand =
|
|
202
|
-
meta: { name: "budgets", description: "Stage campaign budget create/update" },
|
|
1081
|
+
var budgetsCommand = defineCommand4({
|
|
1082
|
+
meta: { name: "budgets", description: "Stage campaign budget create/update/remove" },
|
|
203
1083
|
subCommands: {
|
|
204
1084
|
// Fires where the budget is actually set, rather than on whatever the chat happened to stage
|
|
205
1085
|
// first. A budget several times what the target pool can absorb never spends, and reads to the
|
|
206
1086
|
// client as a plan nobody costed.
|
|
207
|
-
create:
|
|
1087
|
+
create: defineCommand4({
|
|
208
1088
|
meta: {
|
|
209
1089
|
name: "create",
|
|
210
1090
|
description: `Stage a daily campaign budget; campaigns reference it by the g_temp_* ref it returns (--budget-ref). ${STAGED_WRITE}`
|
|
@@ -214,21 +1094,25 @@ var budgetsCommand = defineCommand2({
|
|
|
214
1094
|
...fileArg,
|
|
215
1095
|
name: { type: "string", description: 'Budget name as shown in Google Ads (e.g. "Brand search \u2014 daily")' },
|
|
216
1096
|
amount: { type: "string", description: "Daily amount in major currency units (e.g. 50)" },
|
|
217
|
-
|
|
1097
|
+
...totalArg,
|
|
1098
|
+
delivery: {
|
|
1099
|
+
type: "string",
|
|
1100
|
+
description: "STANDARD \u2014 the only delivery Google still runs (ACCELERATED is refused: Google will not attach it to a campaign)"
|
|
1101
|
+
},
|
|
218
1102
|
shared: { type: "boolean", description: "Explicitly shared budget" }
|
|
219
1103
|
},
|
|
220
1104
|
run: async ({ args }) => {
|
|
221
1105
|
const customerId = requireCustomerId(args);
|
|
222
1106
|
const payload = mergePayload(loadJsonFileArg(args.file), {
|
|
223
1107
|
name: args.name,
|
|
224
|
-
|
|
1108
|
+
...budgetAmountsFromFlags(args),
|
|
225
1109
|
deliveryMethod: args.delivery,
|
|
226
1110
|
explicitlyShared: args.shared || void 0
|
|
227
1111
|
});
|
|
228
1112
|
await stageCreate("google.budget.create", customerId, payload, BUDGET_DEMAND_HINTS);
|
|
229
1113
|
}
|
|
230
1114
|
}),
|
|
231
|
-
update:
|
|
1115
|
+
update: defineCommand4({
|
|
232
1116
|
meta: {
|
|
233
1117
|
name: "update",
|
|
234
1118
|
description: `Stage a change to an existing budget's name, daily amount or delivery. ${STAGED_WRITE}`
|
|
@@ -238,22 +1122,28 @@ var budgetsCommand = defineCommand2({
|
|
|
238
1122
|
...fileArg,
|
|
239
1123
|
name: { type: "string", description: "New budget name as shown in Google Ads" },
|
|
240
1124
|
amount: { type: "string", description: "New daily amount in major currency units" },
|
|
241
|
-
|
|
1125
|
+
total: { type: "string", description: "A total budget's new total, in major units (total budgets only)" },
|
|
1126
|
+
delivery: {
|
|
1127
|
+
type: "string",
|
|
1128
|
+
description: "STANDARD (ACCELERATED is refused \u2014 Google will not attach it to a campaign)"
|
|
1129
|
+
}
|
|
242
1130
|
},
|
|
243
1131
|
run: async ({ args }) => {
|
|
244
1132
|
const customerId = requireCustomerId(args);
|
|
245
1133
|
const target = requireTarget(args, "budget");
|
|
246
1134
|
const payload = mergePayload(loadJsonFileArg(args.file), {
|
|
247
1135
|
name: args.name,
|
|
248
|
-
|
|
1136
|
+
...budgetAmountsFromFlags(args),
|
|
249
1137
|
deliveryMethod: args.delivery
|
|
250
1138
|
});
|
|
251
1139
|
await stageUpdate("google.budget.update", customerId, target, payload, BUDGET_DEMAND_HINTS);
|
|
252
1140
|
}
|
|
253
|
-
})
|
|
1141
|
+
}),
|
|
1142
|
+
remove: statusCommand("google.budget.remove", "budget", BUDGET_REMOVE_HINTS)
|
|
254
1143
|
}
|
|
255
1144
|
});
|
|
256
1145
|
var MAX_CPC_STRATEGIES = ["TARGET_IMPRESSION_SHARE", "TARGET_SPEND", "PERCENT_CPC"];
|
|
1146
|
+
var STRATEGY_SETTING_FLAGS = ["target-cpa", "target-roas", "impression-share", "impression-share-location"];
|
|
257
1147
|
function biddingFromFlags(args) {
|
|
258
1148
|
const type = args["bidding-strategy"];
|
|
259
1149
|
const maxCpcMicros = microsFlag(args["max-cpc"], "--max-cpc");
|
|
@@ -268,6 +1158,12 @@ function biddingFromFlags(args) {
|
|
|
268
1158
|
"--target-cpc needs --bidding-strategy too (the target belongs to a strategy) \u2014 use --bidding-strategy TARGET_CPC --target-cpc 1.5"
|
|
269
1159
|
);
|
|
270
1160
|
}
|
|
1161
|
+
const orphan = STRATEGY_SETTING_FLAGS.find((flag) => args[flag] !== void 0);
|
|
1162
|
+
if (orphan) {
|
|
1163
|
+
failWriteValidation(
|
|
1164
|
+
`--${orphan} needs --bidding-strategy too (it belongs to a strategy) \u2014 e.g. --bidding-strategy TARGET_IMPRESSION_SHARE --impression-share 70, or TARGET_CPA --target-cpa 35`
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
271
1167
|
return void 0;
|
|
272
1168
|
}
|
|
273
1169
|
if (maxCpcMicros !== void 0 && !MAX_CPC_STRATEGIES.includes(type.toUpperCase())) {
|
|
@@ -284,19 +1180,54 @@ function biddingFromFlags(args) {
|
|
|
284
1180
|
targetCpaMicros: microsFlag(args["target-cpa"], "--target-cpa"),
|
|
285
1181
|
targetRoas: args["target-roas"] !== void 0 ? Number(args["target-roas"]) : void 0,
|
|
286
1182
|
targetCpcMicros,
|
|
287
|
-
cpcBidCeilingMicros: maxCpcMicros
|
|
1183
|
+
cpcBidCeilingMicros: maxCpcMicros,
|
|
1184
|
+
...impressionShareGoalFromFlags(args, type)
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function impressionShareGoalFromFlags(args, type) {
|
|
1188
|
+
const share = args["impression-share"];
|
|
1189
|
+
const location = args["impression-share-location"];
|
|
1190
|
+
if (share === void 0 && location === void 0) {
|
|
1191
|
+
return {};
|
|
1192
|
+
}
|
|
1193
|
+
if (type.toUpperCase() !== "TARGET_IMPRESSION_SHARE") {
|
|
1194
|
+
failWriteValidation(
|
|
1195
|
+
`${type} does not take --impression-share / --impression-share-location \u2014 they set the goal of --bidding-strategy TARGET_IMPRESSION_SHARE`
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
const percent = share === void 0 ? void 0 : Number(share);
|
|
1199
|
+
if (percent !== void 0 && !(percent > 0 && percent <= 100)) {
|
|
1200
|
+
failWriteValidation(`--impression-share is a percentage between 0 and 100 (e.g. 70), got ${String(share)}`);
|
|
1201
|
+
}
|
|
1202
|
+
return {
|
|
1203
|
+
...typeof location === "string" ? { location: location.toUpperCase() } : {},
|
|
1204
|
+
...percent !== void 0 ? { locationFractionMicros: Math.round(percent * 1e4) } : {}
|
|
288
1205
|
};
|
|
289
1206
|
}
|
|
290
1207
|
var maxCpcArg = {
|
|
291
1208
|
"max-cpc": {
|
|
292
1209
|
type: "string",
|
|
293
1210
|
description: "Max CPC bid ceiling (major units) for TARGET_IMPRESSION_SHARE, TARGET_SPEND or PERCENT_CPC"
|
|
1211
|
+
},
|
|
1212
|
+
"impression-share": {
|
|
1213
|
+
type: "string",
|
|
1214
|
+
description: "TARGET_IMPRESSION_SHARE goal: the share of auctions to show in, as a percentage (e.g. 70). A new Target impression share needs this, --impression-share-location and --max-cpc."
|
|
1215
|
+
},
|
|
1216
|
+
"impression-share-location": {
|
|
1217
|
+
type: "string",
|
|
1218
|
+
description: "TARGET_IMPRESSION_SHARE goal: ANYWHERE_ON_PAGE | TOP_OF_PAGE | ABSOLUTE_TOP_OF_PAGE"
|
|
1219
|
+
}
|
|
1220
|
+
};
|
|
1221
|
+
var portfolioRefArg = {
|
|
1222
|
+
"portfolio-ref": {
|
|
1223
|
+
type: "string",
|
|
1224
|
+
description: "Put the campaign on a portfolio bid strategy (g_temp_* from `bidding-strategies create`, id, or resource name) instead of its own bidding. Which campaign types join which portfolios is fixed by Google \u2014 staging refuses the rest. To take a campaign OFF its portfolio, stage its own --bidding-strategy: staging clears the portfolio with it and says so."
|
|
294
1225
|
}
|
|
295
1226
|
};
|
|
296
1227
|
var finalUrlSuffixArg = {
|
|
297
1228
|
"final-url-suffix": {
|
|
298
1229
|
type: "string",
|
|
299
|
-
description: 'Final URL suffix for this campaign \u2014 bare query params, no leading "?" (e.g. "utm_source=google&utm_agency=baker"). REPLACES the account-level suffix for this campaign; pass "" to clear it back to the account value. Account-wide tagging belongs at the account level: --level account
|
|
1230
|
+
description: 'Final URL suffix for this campaign \u2014 bare query params, no leading "?" (e.g. "utm_source=google&utm_agency=baker"). REPLACES the account-level suffix for this campaign; pass "" to clear it back to the account value. Account-wide tagging belongs at the account level: --level account stages it there.'
|
|
300
1231
|
}
|
|
301
1232
|
};
|
|
302
1233
|
var urlOptionsArgs = {
|
|
@@ -489,30 +1420,114 @@ function geoTargetTypeHints(payload) {
|
|
|
489
1420
|
`No --geo-target-type set, so this campaign will use Google's default location option "Presence or interest" \u2014 it reaches people interested in your locations, not only people in them. Re-stage with --geo-target-type PRESENCE if the offer is local (in-store, delivery radius, service area), and tell the user which of the two this campaign uses.`
|
|
490
1421
|
];
|
|
491
1422
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1423
|
+
var accountSettingsArgs = {
|
|
1424
|
+
"auto-tagging": {
|
|
1425
|
+
type: "boolean",
|
|
1426
|
+
description: "Auto-tagging: --auto-tagging adds Google's click id (gclid) to every landing page; --auto-tagging=false stops it, and conversion tracking, offline conversion uploads and the Google Analytics link lose the click they match on."
|
|
1427
|
+
},
|
|
1428
|
+
"tracking-template": {
|
|
1429
|
+
type: "string",
|
|
1430
|
+
description: 'Account tracking template \u2014 the click-measurement URL every ad routes through unless a campaign, ad group or ad sets its own. Must carry the landing page through {lpurl}, e.g. "{lpurl}?src=google". Pass "" to clear it.'
|
|
1431
|
+
},
|
|
1432
|
+
"final-url-suffix": {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
description: 'Account final URL suffix \u2014 bare query params appended to every landing page, no leading "?" (e.g. "utm_source=google&utm_agency=baker"). A campaign, ad group or ad suffix REPLACES it there. Pass "" to clear it.'
|
|
1435
|
+
},
|
|
1436
|
+
"call-reporting": {
|
|
1437
|
+
type: "boolean",
|
|
1438
|
+
description: "Call reporting: --call-reporting counts and reports calls from call assets; --call-reporting=false stops it."
|
|
1439
|
+
},
|
|
1440
|
+
"call-conversions": {
|
|
1441
|
+
type: "boolean",
|
|
1442
|
+
description: "Whether calls from ads count as conversions: --call-conversions / --call-conversions=false. Needs call reporting on."
|
|
1443
|
+
},
|
|
1444
|
+
"video-inventory": {
|
|
1445
|
+
type: "string",
|
|
1446
|
+
description: "Video inventory (brand safety) for YouTube and video placements: EXPANDED | STANDARD | LIMITED \u2014 limited shows ads beside the narrowest, safest range of content."
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
var VIDEO_INVENTORY_LEVELS = ["EXPANDED", "STANDARD", "LIMITED"];
|
|
1450
|
+
function accountSettingsFromFlags(args) {
|
|
1451
|
+
const flag = (name) => typeof args[name] === "boolean" ? args[name] : void 0;
|
|
1452
|
+
const text = (name) => typeof args[name] === "string" ? args[name] : void 0;
|
|
1453
|
+
const inventory = text("video-inventory")?.toUpperCase().replace(/_INVENTORY$/, "");
|
|
1454
|
+
if (inventory !== void 0 && !VIDEO_INVENTORY_LEVELS.includes(inventory)) {
|
|
1455
|
+
failWriteValidation(`--video-inventory must be ${VIDEO_INVENTORY_LEVELS.join(" | ")}`);
|
|
1456
|
+
}
|
|
1457
|
+
const payload = {
|
|
1458
|
+
autoTaggingEnabled: flag("auto-tagging"),
|
|
1459
|
+
trackingUrlTemplate: text("tracking-template"),
|
|
1460
|
+
finalUrlSuffix: text("final-url-suffix"),
|
|
1461
|
+
callReportingEnabled: flag("call-reporting"),
|
|
1462
|
+
callConversionReportingEnabled: flag("call-conversions"),
|
|
1463
|
+
videoBrandSafety: inventory === void 0 ? void 0 : `${inventory}_INVENTORY`
|
|
1464
|
+
};
|
|
1465
|
+
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== void 0));
|
|
1466
|
+
}
|
|
1467
|
+
var ACCOUNT_LEVEL_URL_FLAGS = {
|
|
1468
|
+
"final-url-suffix": "finalUrlSuffix",
|
|
1469
|
+
"tracking-template": "trackingUrlTemplate"
|
|
1470
|
+
};
|
|
1471
|
+
function accountLevelUrlOptions(args) {
|
|
1472
|
+
const kebab = (key) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
1473
|
+
const ignored = /* @__PURE__ */ new Set(["level", "customer-id", "_", "id", ...Object.keys(ACCOUNT_LEVEL_URL_FLAGS)]);
|
|
1474
|
+
const other = [
|
|
1475
|
+
...new Set(
|
|
1476
|
+
Object.entries(args).filter(([key, value]) => !ignored.has(kebab(key)) && value !== void 0 && value !== false).map(([key]) => kebab(key))
|
|
1477
|
+
)
|
|
1478
|
+
];
|
|
1479
|
+
if (other.length > 0) {
|
|
1480
|
+
failWriteValidation(
|
|
1481
|
+
`--level account stages the account's final URL suffix and tracking template only \u2014 ${other.map((key) => `--${key}`).join(", ")} ${other.length === 1 ? "is a campaign setting" : "are campaign settings"}. Stage the account change alone, and the rest with \`baker ads google campaigns update <campaign-id>\` (or other account settings with \`baker ads google account update\`).`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
const payload = Object.fromEntries(
|
|
1485
|
+
Object.entries(ACCOUNT_LEVEL_URL_FLAGS).flatMap(
|
|
1486
|
+
([flag, key]) => typeof args[flag] === "string" ? [[key, args[flag]]] : []
|
|
1487
|
+
)
|
|
1488
|
+
);
|
|
1489
|
+
if (Object.keys(payload).length === 0) {
|
|
1490
|
+
failWriteValidation('--level account needs --final-url-suffix or --tracking-template (pass "" to clear one)');
|
|
1491
|
+
}
|
|
1492
|
+
return payload;
|
|
511
1493
|
}
|
|
512
|
-
|
|
1494
|
+
function accountUrlOptionHints(customerId) {
|
|
1495
|
+
return [
|
|
1496
|
+
`A campaign, ad group or ad that sets its own suffix or template REPLACES the account one there \u2014 run \`baker ads google url-options --customer-id ${customerId}\` and tell the user which ones will not pick this up.`
|
|
1497
|
+
];
|
|
1498
|
+
}
|
|
1499
|
+
var accountCommand = defineCommand4({
|
|
1500
|
+
meta: {
|
|
1501
|
+
name: "account",
|
|
1502
|
+
description: "Stage the account's own settings \u2014 auto-tagging, account tracking template and final URL suffix, call reporting, video inventory (brand safety). Start here for an account-wide UTM or agency tag."
|
|
1503
|
+
},
|
|
1504
|
+
subCommands: {
|
|
1505
|
+
update: defineCommand4({
|
|
1506
|
+
meta: {
|
|
1507
|
+
name: "update",
|
|
1508
|
+
description: `Stage a change to the account's settings (Google Ads \u2192 Admin \u2192 Account settings). One op per account; restaging amends it. ${STAGED_WRITE}
|
|
1509
|
+
|
|
1510
|
+
Examples:
|
|
1511
|
+
baker ads google account update --customer-id 1234567890 --final-url-suffix "utm_source=google&utm_agency=baker"
|
|
1512
|
+
baker ads google account update --customer-id 1234567890 --auto-tagging
|
|
1513
|
+
baker ads google account update --customer-id 1234567890 --call-reporting --call-conversions
|
|
1514
|
+
baker ads google account update --customer-id 1234567890 --video-inventory LIMITED
|
|
1515
|
+
baker ads google account update --customer-id 1234567890 --final-url-suffix "" \u2014 clear it`
|
|
1516
|
+
},
|
|
1517
|
+
args: { ...customerIdArg, ...fileArg, ...accountSettingsArgs },
|
|
1518
|
+
run: async ({ args }) => {
|
|
1519
|
+
const customerId = requireCustomerId(args);
|
|
1520
|
+
const payload = mergePayload(loadJsonFileArg(args.file), accountSettingsFromFlags(args));
|
|
1521
|
+
const urlHints = payload.finalUrlSuffix !== void 0 || payload.trackingUrlTemplate !== void 0 ? accountUrlOptionHints(customerId) : [];
|
|
1522
|
+
await stageUpdate("google.customer.update", customerId, customerId, payload, urlHints);
|
|
1523
|
+
}
|
|
1524
|
+
})
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
var campaignsCommand = defineCommand4({
|
|
513
1528
|
meta: { name: "campaigns", description: "Stage campaign create/update/pause/resume/remove" },
|
|
514
1529
|
subCommands: {
|
|
515
|
-
create:
|
|
1530
|
+
create: defineCommand4({
|
|
516
1531
|
meta: {
|
|
517
1532
|
name: "create",
|
|
518
1533
|
description: `Stage a new campaign on a budget (--budget-ref); it is created paused unless --status ENABLED. ${STAGED_WRITE}`
|
|
@@ -523,13 +1538,17 @@ var campaignsCommand = defineCommand2({
|
|
|
523
1538
|
name: { type: "string", description: 'Campaign name, unique in the account (e.g. "Search \u2014 Brand \u2014 ES")' },
|
|
524
1539
|
"channel-type": {
|
|
525
1540
|
type: "string",
|
|
526
|
-
description: "SEARCH | DISPLAY |
|
|
1541
|
+
description: "SEARCH | DISPLAY | PERFORMANCE_MAX | DEMAND_GEN | SHOPPING (--merchant-id) | MULTI_CHANNEL = App (--sub-type APP_CAMPAIGN --app-id --app-store --app-goal) | TRAVEL (--travel-account-id) | HOTEL (--hotel-center-id). Video, Smart and Local campaigns are refused with the reason and the route (video on YouTube \u2192 DEMAND_GEN)."
|
|
1542
|
+
},
|
|
1543
|
+
"sub-type": {
|
|
1544
|
+
type: "string",
|
|
1545
|
+
description: "Leave out for a standard campaign. APP_CAMPAIGN (installs) or APP_CAMPAIGN_FOR_ENGAGEMENT on an App campaign (required); DISPLAY_MOBILE_APP on Display. Travel's own sub-type is sent for you."
|
|
527
1546
|
},
|
|
528
|
-
"sub-type": { type: "string", description: "Advertising channel sub-type" },
|
|
529
1547
|
"budget-ref": { type: "string", description: "Budget ref (g_temp_*, id, or resource name)" },
|
|
1548
|
+
...portfolioRefArg,
|
|
530
1549
|
"bidding-strategy": {
|
|
531
1550
|
type: "string",
|
|
532
|
-
description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \
|
|
1551
|
+
description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | TARGET_IMPRESSION_SHARE | TARGET_CPA | TARGET_ROAS | TARGET_CPC | MANUAL_CPM. Each campaign type takes only some \u2014 staging refuses the rest and names the ones that work."
|
|
533
1552
|
},
|
|
534
1553
|
"target-cpa": {
|
|
535
1554
|
type: "string",
|
|
@@ -549,6 +1568,14 @@ var campaignsCommand = defineCommand2({
|
|
|
549
1568
|
...geoTargetTypeArgs,
|
|
550
1569
|
...networksArg,
|
|
551
1570
|
...aiMaxArgs,
|
|
1571
|
+
...shoppingArgs,
|
|
1572
|
+
...appArgs,
|
|
1573
|
+
...linkedAccountArgs,
|
|
1574
|
+
...dynamicSearchAdsArgs,
|
|
1575
|
+
...frequencyCapArgs,
|
|
1576
|
+
...textGuidelinesArgs,
|
|
1577
|
+
...assetAutomationArgs,
|
|
1578
|
+
...brandGuidelinesCreateArgs,
|
|
552
1579
|
objective: { type: "string", description: "Advisory UI objective (SALES, LEADS, \u2026)" },
|
|
553
1580
|
"start-date": { type: "string", description: "YYYY-MM-DD" },
|
|
554
1581
|
"end-date": { type: "string", description: "YYYY-MM-DD" },
|
|
@@ -566,6 +1593,7 @@ var campaignsCommand = defineCommand2({
|
|
|
566
1593
|
channelSubType: args["sub-type"],
|
|
567
1594
|
budget: args["budget-ref"],
|
|
568
1595
|
bidding: biddingFromFlags(args),
|
|
1596
|
+
biddingStrategy: args["portfolio-ref"],
|
|
569
1597
|
geoTargetTypeSetting: geoTargetTypeFromFlags(args),
|
|
570
1598
|
networkSettings: networkSettingsFromFlags(args),
|
|
571
1599
|
finalUrlSuffix: args["final-url-suffix"],
|
|
@@ -576,6 +1604,14 @@ var campaignsCommand = defineCommand2({
|
|
|
576
1604
|
endDate: args["end-date"],
|
|
577
1605
|
euPoliticalAds: args["eu-political-ads"],
|
|
578
1606
|
aiMax: aiMaxFromFlags(args),
|
|
1607
|
+
shoppingSetting: shoppingSettingFromFlags(args),
|
|
1608
|
+
...appSettingsFromFlags(args),
|
|
1609
|
+
...linkedAccountFromFlags(args),
|
|
1610
|
+
dynamicSearchAdsSetting: dynamicSearchAdsFromFlags(args),
|
|
1611
|
+
frequencyCaps: frequencyCapsFromFlags(args, rawArgs),
|
|
1612
|
+
textGuidelines: textGuidelinesFromFlags(args, rawArgs),
|
|
1613
|
+
assetAutomation: assetAutomationFromFlags(args),
|
|
1614
|
+
brandGuidelines: brandGuidelinesFromFlags(args, "create"),
|
|
579
1615
|
status: args.status
|
|
580
1616
|
});
|
|
581
1617
|
await stageCreate("google.campaign.create", customerId, payload, [
|
|
@@ -584,7 +1620,7 @@ var campaignsCommand = defineCommand2({
|
|
|
584
1620
|
]);
|
|
585
1621
|
}
|
|
586
1622
|
}),
|
|
587
|
-
update:
|
|
1623
|
+
update: defineCommand4({
|
|
588
1624
|
meta: {
|
|
589
1625
|
name: "update",
|
|
590
1626
|
description: `Stage a change to an existing campaign's name, budget, bidding, settings, AI Max switches or text guidelines (what AI Max and Performance Max ad text must avoid). ${STAGED_WRITE}`
|
|
@@ -594,9 +1630,10 @@ var campaignsCommand = defineCommand2({
|
|
|
594
1630
|
...fileArg,
|
|
595
1631
|
name: { type: "string", description: "New campaign name, unique in the account" },
|
|
596
1632
|
"budget-ref": { type: "string" },
|
|
1633
|
+
...portfolioRefArg,
|
|
597
1634
|
"bidding-strategy": {
|
|
598
1635
|
type: "string",
|
|
599
|
-
description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | \
|
|
1636
|
+
description: "MANUAL_CPC | MAXIMIZE_CONVERSIONS | MAXIMIZE_CONVERSION_VALUE | TARGET_SPEND | TARGET_IMPRESSION_SHARE | TARGET_CPA | TARGET_ROAS | TARGET_CPC | MANUAL_CPM. Each campaign type takes only some \u2014 staging refuses the rest and names the ones that work."
|
|
600
1637
|
},
|
|
601
1638
|
"target-cpa": {
|
|
602
1639
|
type: "string",
|
|
@@ -617,13 +1654,18 @@ var campaignsCommand = defineCommand2({
|
|
|
617
1654
|
...networksArg,
|
|
618
1655
|
...textGuidelinesArgs,
|
|
619
1656
|
...aiMaxArgs,
|
|
1657
|
+
...shoppingUpdateArgs,
|
|
1658
|
+
...dynamicSearchAdsArgs,
|
|
1659
|
+
...frequencyCapArgs,
|
|
1660
|
+
...assetAutomationArgs,
|
|
1661
|
+
...brandStyleArgs,
|
|
620
1662
|
"eu-political-ads": {
|
|
621
1663
|
type: "boolean",
|
|
622
1664
|
description: "Corrects the campaign's EU political advertising declaration: --eu-political-ads if it contains EU political ads, --eu-political-ads=false if it does not. Existing campaigns without a declaration block location-targeting changes until one is set."
|
|
623
1665
|
},
|
|
624
1666
|
level: {
|
|
625
1667
|
type: "string",
|
|
626
|
-
description: "campaign (default) | account. `account` stages
|
|
1668
|
+
description: "campaign (default) | account. `account` stages --final-url-suffix / --tracking-template on the ACCOUNT (every campaign without its own inherits it) \u2014 same as `baker ads google account update`."
|
|
627
1669
|
},
|
|
628
1670
|
status: { type: "string" }
|
|
629
1671
|
},
|
|
@@ -631,7 +1673,15 @@ var campaignsCommand = defineCommand2({
|
|
|
631
1673
|
const customerId = requireCustomerId(args);
|
|
632
1674
|
const level = typeof args.level === "string" && args.level.length > 0 ? args.level : "campaign";
|
|
633
1675
|
if (level === "account") {
|
|
634
|
-
|
|
1676
|
+
const payload2 = accountLevelUrlOptions(args);
|
|
1677
|
+
await stageUpdate(
|
|
1678
|
+
"google.customer.update",
|
|
1679
|
+
customerId,
|
|
1680
|
+
customerId,
|
|
1681
|
+
payload2,
|
|
1682
|
+
accountUrlOptionHints(customerId)
|
|
1683
|
+
);
|
|
1684
|
+
return;
|
|
635
1685
|
}
|
|
636
1686
|
if (level !== "campaign") {
|
|
637
1687
|
failWriteValidation("--level must be campaign or account");
|
|
@@ -641,6 +1691,7 @@ var campaignsCommand = defineCommand2({
|
|
|
641
1691
|
name: args.name,
|
|
642
1692
|
budget: args["budget-ref"],
|
|
643
1693
|
bidding: biddingFromFlags(args),
|
|
1694
|
+
biddingStrategy: args["portfolio-ref"],
|
|
644
1695
|
geoTargetTypeSetting: geoTargetTypeFromFlags(args),
|
|
645
1696
|
networkSettings: networkSettingsFromFlags(args),
|
|
646
1697
|
finalUrlSuffix: args["final-url-suffix"],
|
|
@@ -649,6 +1700,11 @@ var campaignsCommand = defineCommand2({
|
|
|
649
1700
|
euPoliticalAds: args["eu-political-ads"],
|
|
650
1701
|
textGuidelines: textGuidelinesFromFlags(args, rawArgs),
|
|
651
1702
|
aiMax: aiMaxFromFlags(args),
|
|
1703
|
+
shoppingSetting: shoppingSettingUpdateFromFlags(args),
|
|
1704
|
+
dynamicSearchAdsSetting: dynamicSearchAdsFromFlags(args),
|
|
1705
|
+
frequencyCaps: frequencyCapsFromFlags(args, rawArgs),
|
|
1706
|
+
assetAutomation: assetAutomationFromFlags(args),
|
|
1707
|
+
brandGuidelines: brandGuidelinesFromFlags(args, "update"),
|
|
652
1708
|
status: args.status
|
|
653
1709
|
});
|
|
654
1710
|
await stageUpdate("google.campaign.update", customerId, target, payload);
|
|
@@ -656,12 +1712,37 @@ var campaignsCommand = defineCommand2({
|
|
|
656
1712
|
}),
|
|
657
1713
|
pause: statusCommand("google.campaign.pause", "campaign"),
|
|
658
1714
|
resume: statusCommand("google.campaign.resume", "campaign"),
|
|
659
|
-
remove: statusCommand("google.campaign.remove", "campaign")
|
|
1715
|
+
remove: statusCommand("google.campaign.remove", "campaign"),
|
|
1716
|
+
"enable-brand-guidelines": defineCommand4({
|
|
1717
|
+
meta: {
|
|
1718
|
+
name: "enable-brand-guidelines",
|
|
1719
|
+
description: `Turn brand guidelines on for an existing Performance Max campaign (Google fixes them at creation otherwise). Start here: \`baker ads google campaigns enable-brand-guidelines <id> --business-name-asset <id> --logo-assets <id> --final-url-domain example.com\`, or \`--auto-populate\` to take them from its asset groups. Applied at publish; \`draft validate\` cannot check it (Google has no dry run for it). ${STAGED_WRITE}`
|
|
1720
|
+
},
|
|
1721
|
+
args: {
|
|
1722
|
+
...customerIdArg,
|
|
1723
|
+
...fileArg,
|
|
1724
|
+
id: { type: "positional", description: "Performance Max campaign id or resource name", required: false },
|
|
1725
|
+
...enableBrandGuidelinesArgs
|
|
1726
|
+
},
|
|
1727
|
+
run: async ({ args }) => {
|
|
1728
|
+
const customerId = requireCustomerId(args);
|
|
1729
|
+
const payload = mergePayload(loadJsonFileArg(args.file), enableBrandGuidelinesFromFlags(args));
|
|
1730
|
+
await stageUpdate(
|
|
1731
|
+
"google.campaign.enableBrandGuidelines",
|
|
1732
|
+
customerId,
|
|
1733
|
+
requireTarget(args, "campaign"),
|
|
1734
|
+
payload
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
})
|
|
660
1738
|
}
|
|
661
1739
|
});
|
|
662
1740
|
function statusCommand(kind, entity, hints) {
|
|
663
|
-
return
|
|
664
|
-
meta: {
|
|
1741
|
+
return defineCommand4({
|
|
1742
|
+
meta: {
|
|
1743
|
+
name: kind.split(".")[2],
|
|
1744
|
+
description: `Stage a ${entity} ${kind.split(".")[2]}. Several? Stage them in one call with \`baker ads google draft stage --file ops.json\` \u2014 never one command per row.`
|
|
1745
|
+
},
|
|
665
1746
|
args: {
|
|
666
1747
|
...customerIdArg,
|
|
667
1748
|
id: { type: "positional", description: `${entity} resource name or id`, required: false }
|
|
@@ -672,10 +1753,10 @@ function statusCommand(kind, entity, hints) {
|
|
|
672
1753
|
}
|
|
673
1754
|
});
|
|
674
1755
|
}
|
|
675
|
-
var adGroupsCommand =
|
|
1756
|
+
var adGroupsCommand = defineCommand4({
|
|
676
1757
|
meta: { name: "ad-groups", description: "Stage ad group create/update/pause/resume/remove" },
|
|
677
1758
|
subCommands: {
|
|
678
|
-
create:
|
|
1759
|
+
create: defineCommand4({
|
|
679
1760
|
meta: {
|
|
680
1761
|
name: "create",
|
|
681
1762
|
description: `Stage a new ad group in a campaign (--campaign-ref); it is created paused unless --status ENABLED. ${STAGED_WRITE}`
|
|
@@ -707,7 +1788,7 @@ var adGroupsCommand = defineCommand2({
|
|
|
707
1788
|
await stageCreate("google.adGroup.create", customerId, payload);
|
|
708
1789
|
}
|
|
709
1790
|
}),
|
|
710
|
-
update:
|
|
1791
|
+
update: defineCommand4({
|
|
711
1792
|
meta: {
|
|
712
1793
|
name: "update",
|
|
713
1794
|
description: `Stage a change to an existing ad group's name, default CPC bid or status. ${STAGED_WRITE}`
|
|
@@ -767,7 +1848,7 @@ function keywordUpdateFinalUrls(args) {
|
|
|
767
1848
|
return [finalUrl];
|
|
768
1849
|
}
|
|
769
1850
|
var keywordWriteSubcommands = {
|
|
770
|
-
add:
|
|
1851
|
+
add: defineCommand4({
|
|
771
1852
|
meta: { name: "add", description: "Add keyword(s) to an ad group \u2014 one --text or a whole batch" },
|
|
772
1853
|
args: {
|
|
773
1854
|
...customerIdArg,
|
|
@@ -800,8 +1881,11 @@ var keywordWriteSubcommands = {
|
|
|
800
1881
|
);
|
|
801
1882
|
}
|
|
802
1883
|
}),
|
|
803
|
-
update:
|
|
804
|
-
meta: {
|
|
1884
|
+
update: defineCommand4({
|
|
1885
|
+
meta: {
|
|
1886
|
+
name: "update",
|
|
1887
|
+
description: "Update one keyword's bid, status or final URL. Many keywords? `keywords pause|resume` take a list, and `baker ads google draft stage --file` stages any mix of updates in one call."
|
|
1888
|
+
},
|
|
805
1889
|
args: {
|
|
806
1890
|
...customerIdArg,
|
|
807
1891
|
"cpc-bid": { type: "string" },
|
|
@@ -827,16 +1911,49 @@ var keywordWriteSubcommands = {
|
|
|
827
1911
|
);
|
|
828
1912
|
}
|
|
829
1913
|
}),
|
|
830
|
-
remove: statusCommand("google.keyword.remove", "keyword", KEYWORD_KILL_HINTS)
|
|
1914
|
+
remove: statusCommand("google.keyword.remove", "keyword", KEYWORD_KILL_HINTS),
|
|
1915
|
+
pause: keywordStatusCommand("pause", "PAUSED"),
|
|
1916
|
+
resume: keywordStatusCommand("resume", "ENABLED")
|
|
831
1917
|
};
|
|
1918
|
+
function keywordStatusCommand(verb, status) {
|
|
1919
|
+
return defineCommand4({
|
|
1920
|
+
meta: {
|
|
1921
|
+
name: verb,
|
|
1922
|
+
description: `${verb === "pause" ? "Pause" : "Resume"} keyword(s) \u2014 every target in ONE call. Start here:
|
|
1923
|
+
baker ads google keywords ${verb} customers/1234567890/adGroupCriteria/111~222 customers/1234567890/adGroupCriteria/111~333 --customer-id 1234567890
|
|
1924
|
+
baker ads google keywords ${verb} --file keywords.txt --customer-id 1234567890 # one resource name per line, up to 500
|
|
1925
|
+
Take each keyword's resource name (adGroupId~criterionId) from \`--preset keyword-analysis\` rows \u2014 the same text in another ad group is a different keyword. ${STAGED_WRITE}`
|
|
1926
|
+
},
|
|
1927
|
+
args: {
|
|
1928
|
+
...customerIdArg,
|
|
1929
|
+
id: { type: "positional", description: "Keyword resource name(s) \u2014 several allowed", required: false },
|
|
1930
|
+
file: { type: "string", description: "Text file with one keyword resource name per line" }
|
|
1931
|
+
},
|
|
1932
|
+
run: async ({ args }) => {
|
|
1933
|
+
const customerId = requireCustomerId(args);
|
|
1934
|
+
await stageGoogleOps(
|
|
1935
|
+
targetEntries(args, "keyword").map((target) => ({
|
|
1936
|
+
kind: "google.keyword.update",
|
|
1937
|
+
customerId,
|
|
1938
|
+
target,
|
|
1939
|
+
payload: { status }
|
|
1940
|
+
})),
|
|
1941
|
+
keywordKillHints(status)
|
|
1942
|
+
);
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
832
1946
|
var NEGATIVE_COVERAGE_HINTS = [
|
|
833
1947
|
"Before publishing, confirm each term isn't already blocked: run `baker ads google query --preset negative-keyword-lists` (shared-list terms) and `--preset negative-keywords` (campaign/ad-group negatives).",
|
|
834
1948
|
"If a term is already in a negative list but still shows in search terms, the list likely isn't attached to this campaign (check `--preset negative-list-attachments`) or its match type is narrower than the query \u2014 explain that instead of staging a duplicate negative."
|
|
835
1949
|
];
|
|
836
|
-
var negativeKeywordsCommand =
|
|
837
|
-
meta: {
|
|
1950
|
+
var negativeKeywordsCommand = defineCommand4({
|
|
1951
|
+
meta: {
|
|
1952
|
+
name: "negative-keywords",
|
|
1953
|
+
description: "Stage negative keyword add/update/remove (ad-group or campaign level) \u2014 update changes text or match type"
|
|
1954
|
+
},
|
|
838
1955
|
subCommands: {
|
|
839
|
-
add:
|
|
1956
|
+
add: defineCommand4({
|
|
840
1957
|
meta: { name: "add", description: "Add negative keyword(s) \u2014 one --text or a whole batch" },
|
|
841
1958
|
args: {
|
|
842
1959
|
...customerIdArg,
|
|
@@ -863,23 +1980,57 @@ var negativeKeywordsCommand = defineCommand2({
|
|
|
863
1980
|
);
|
|
864
1981
|
}
|
|
865
1982
|
}),
|
|
866
|
-
|
|
1983
|
+
update: defineCommand4({
|
|
1984
|
+
meta: {
|
|
1985
|
+
name: "update",
|
|
1986
|
+
description: "Change a negative keyword's text or match type. Start here: `negative-keywords update customers/<cid>/adGroupCriteria/<adGroupId>~<criterionId> --match-type PHRASE`. Google never edits either on a live negative, so this publishes as a replace \u2014 the new negative added and the old one removed together, all or nothing. The field you leave out keeps its live value. Read them with `baker ads google negative-keywords list`."
|
|
1987
|
+
},
|
|
1988
|
+
args: {
|
|
1989
|
+
...customerIdArg,
|
|
1990
|
+
id: {
|
|
1991
|
+
type: "positional",
|
|
1992
|
+
description: "customers/\u2026/adGroupCriteria/{adGroupId}~{criterionId} or campaignCriteria/{campaignId}~{criterionId}",
|
|
1993
|
+
required: false
|
|
1994
|
+
},
|
|
1995
|
+
text: { type: "string", description: "The new text" },
|
|
1996
|
+
"match-type": { type: "string", description: "EXACT | PHRASE | BROAD" }
|
|
1997
|
+
},
|
|
1998
|
+
run: async ({ args }) => {
|
|
1999
|
+
const customerId = requireCustomerId(args);
|
|
2000
|
+
const payload = {
|
|
2001
|
+
...args.text !== void 0 ? { text: args.text } : {},
|
|
2002
|
+
...args["match-type"] !== void 0 ? { matchType: args["match-type"] } : {}
|
|
2003
|
+
};
|
|
2004
|
+
if (Object.keys(payload).length === 0) {
|
|
2005
|
+
failWriteValidation("pass --text and/or --match-type");
|
|
2006
|
+
}
|
|
2007
|
+
await stageUpdate(
|
|
2008
|
+
"google.negativeKeyword.update",
|
|
2009
|
+
customerId,
|
|
2010
|
+
requireTarget(args, "negative keyword"),
|
|
2011
|
+
payload
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
2014
|
+
}),
|
|
2015
|
+
remove: statusCommand("google.negativeKeyword.remove", "negative keyword", [
|
|
2016
|
+
"Pass the full resource name \u2014 customers/{cid}/campaignCriteria/{campaignId}~{criterionId} or \u2026/adGroupCriteria/{adGroupId}~{criterionId}, read from the row's resource_name; a bare id is refused. A shared keyword list's member (\u2026/sharedCriteria/{listId}~{criterionId}) is staged as a removal from that list, which every campaign the list covers loses."
|
|
2017
|
+
])
|
|
867
2018
|
}
|
|
868
2019
|
});
|
|
869
|
-
var keywordListsCommand =
|
|
2020
|
+
var keywordListsCommand = defineCommand4({
|
|
870
2021
|
meta: {
|
|
871
2022
|
name: "keyword-lists",
|
|
872
|
-
description: "Stage shared negative keyword lists (create/add/attach/detach). Build at least one shared negative list (brand, competitor, junk-intent terms) and attach it to every campaign to protect spend."
|
|
2023
|
+
description: "Stage shared lists \u2014 negative keyword lists, placement exclusion lists (sites, apps, YouTube) and brand lists (create/rename/delete a list; add/remove its members; attach/detach a keyword or placement list on a campaign \u2014 a brand list goes on a campaign as a `brandList` criterion). Build at least one shared negative list (brand, competitor, junk-intent terms) and attach it to every campaign to protect spend."
|
|
873
2024
|
},
|
|
874
2025
|
subCommands: {
|
|
875
|
-
create:
|
|
2026
|
+
create: defineCommand4({
|
|
876
2027
|
meta: { name: "create", description: "Create a shared negative keyword list" },
|
|
877
2028
|
args: {
|
|
878
2029
|
...customerIdArg,
|
|
879
2030
|
name: { type: "string" },
|
|
880
2031
|
type: {
|
|
881
2032
|
type: "string",
|
|
882
|
-
description: "NEGATIVE_KEYWORDS (default) | NEGATIVE_PLACEMENTS | ACCOUNT_LEVEL_NEGATIVE_KEYWORDS"
|
|
2033
|
+
description: "NEGATIVE_KEYWORDS (default) | NEGATIVE_PLACEMENTS (sites, apps, YouTube to exclude) | ACCOUNT_LEVEL_NEGATIVE_KEYWORDS (applied with `account-exclusions add`) | BRANDS (a brand list for brand exclusions / AI Max)"
|
|
883
2034
|
}
|
|
884
2035
|
},
|
|
885
2036
|
run: async ({ args }) => {
|
|
@@ -890,8 +2041,11 @@ var keywordListsCommand = defineCommand2({
|
|
|
890
2041
|
});
|
|
891
2042
|
}
|
|
892
2043
|
}),
|
|
893
|
-
add:
|
|
894
|
-
meta: {
|
|
2044
|
+
add: defineCommand4({
|
|
2045
|
+
meta: {
|
|
2046
|
+
name: "add",
|
|
2047
|
+
description: 'Add members to a shared list \u2014 keywords to a negative keyword list (--text / --file), or sites, apps and YouTube to a NEGATIVE_PLACEMENTS list (--placement, --app, --youtube-channel, --youtube-video) and brands to a BRANDS list (--brand). Start here: `keyword-lists add --list-ref <list> --placement "example.com,spam-site.net"`. A member the list type cannot hold is refused at staging.'
|
|
2048
|
+
},
|
|
895
2049
|
args: {
|
|
896
2050
|
...customerIdArg,
|
|
897
2051
|
"list-ref": { type: "string", description: "Shared set ref" },
|
|
@@ -900,21 +2054,29 @@ var keywordListsCommand = defineCommand2({
|
|
|
900
2054
|
description: 'Keyword text \u2014 comma-separate for a batch, optional ":MATCH_TYPE" suffix per entry'
|
|
901
2055
|
},
|
|
902
2056
|
file: { type: "string", description: "Text file with one keyword[:MATCH_TYPE] per line" },
|
|
903
|
-
"match-type": { type: "string", description: "Default match type: EXACT | PHRASE | BROAD" }
|
|
2057
|
+
"match-type": { type: "string", description: "Default match type: EXACT | PHRASE | BROAD" },
|
|
2058
|
+
...LIST_MEMBER_ARGS
|
|
904
2059
|
},
|
|
905
2060
|
run: async ({ args, rawArgs }) => {
|
|
906
2061
|
const customerId = requireCustomerId(args);
|
|
907
2062
|
const sharedSet = requireStringFlag(args["list-ref"], "--list-ref");
|
|
908
|
-
|
|
909
|
-
|
|
2063
|
+
const criteria = listMemberCriteria(args);
|
|
2064
|
+
const keywords = criteria.length > 0 && args.text === void 0 && args.file === void 0 ? [] : keywordEntries(args, rawArgs);
|
|
2065
|
+
await stageGoogleOps([
|
|
2066
|
+
...keywords.map((entry) => ({
|
|
910
2067
|
kind: "google.sharedSetMember.add",
|
|
911
2068
|
customerId,
|
|
912
2069
|
payload: { sharedSet, text: entry.text, matchType: entry.matchType }
|
|
2070
|
+
})),
|
|
2071
|
+
...criteria.map((criterion) => ({
|
|
2072
|
+
kind: "google.sharedSetMember.add",
|
|
2073
|
+
customerId,
|
|
2074
|
+
payload: { sharedSet, criterion }
|
|
913
2075
|
}))
|
|
914
|
-
);
|
|
2076
|
+
]);
|
|
915
2077
|
}
|
|
916
2078
|
}),
|
|
917
|
-
attach:
|
|
2079
|
+
attach: defineCommand4({
|
|
918
2080
|
meta: { name: "attach", description: "Attach a keyword list to a campaign" },
|
|
919
2081
|
args: { ...customerIdArg, "campaign-ref": { type: "string" }, "list-ref": { type: "string" } },
|
|
920
2082
|
run: async ({ args }) => {
|
|
@@ -925,9 +2087,99 @@ var keywordListsCommand = defineCommand2({
|
|
|
925
2087
|
});
|
|
926
2088
|
}
|
|
927
2089
|
}),
|
|
928
|
-
|
|
2090
|
+
remove: defineCommand4({
|
|
2091
|
+
meta: {
|
|
2092
|
+
name: "remove",
|
|
2093
|
+
description: `Take term(s) out of a shared negative keyword list \u2014 every campaign the list covers stops excluding them. Start here:
|
|
2094
|
+
baker ads google keyword-lists remove --list-ref 1234 --text "free,jobs:EXACT" --customer-id 1234567890
|
|
2095
|
+
baker ads google keyword-lists remove --list-ref 1234 --file terms.txt --customer-id 1234567890 # one term[:MATCH_TYPE] per line
|
|
2096
|
+
Baker reads the list and finds each term (no :MATCH_TYPE = every match type of it); a term the list does not hold is reported, not staged. Members named directly also work: their resource name (customers/{cid}/sharedCriteria/{listId}~{criterionId}), listId~criterionId, or a criterion id. ${STAGED_WRITE}`
|
|
2097
|
+
},
|
|
2098
|
+
args: {
|
|
2099
|
+
...customerIdArg,
|
|
2100
|
+
"list-ref": { type: "string", description: "The list's id or customers/{cid}/sharedSets/{id}" },
|
|
2101
|
+
text: { type: "string", description: 'Terms to take out \u2014 comma-separated, optional ":MATCH_TYPE" each' },
|
|
2102
|
+
file: { type: "string", description: "Text file with one term[:MATCH_TYPE] per line" },
|
|
2103
|
+
id: {
|
|
2104
|
+
type: "positional",
|
|
2105
|
+
description: "Member resource name(s), listId~criterionId or criterion id(s) \u2014 instead of --text",
|
|
2106
|
+
required: false
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
run: async ({ args, rawArgs }) => {
|
|
2110
|
+
const customerId = requireCustomerId(args);
|
|
2111
|
+
const listId = listIdOf(requireStringFlag(args["list-ref"], "--list-ref"));
|
|
2112
|
+
const terms = [
|
|
2113
|
+
...repeatedValues(rawArgs, "text", args.text).flatMap((v) => v.split(",")),
|
|
2114
|
+
...typeof args.file === "string" && args.file.length > 0 ? fileLines(args.file) : []
|
|
2115
|
+
].map((v) => v.trim()).filter(Boolean);
|
|
2116
|
+
const direct = (Array.isArray(args._) ? args._ : []).filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean).map((v) => memberTarget(customerId, listId, v));
|
|
2117
|
+
if (terms.length === 0 && direct.length === 0) {
|
|
2118
|
+
failWriteValidation("pass the terms to take out with --text / --file, or the members' resource names");
|
|
2119
|
+
}
|
|
2120
|
+
const found = terms.length > 0 ? memberRemovals(await readListMembersOrExit(customerId, listId), terms) : { targets: [], missing: [] };
|
|
2121
|
+
const targets = [.../* @__PURE__ */ new Set([...direct, ...found.targets])];
|
|
2122
|
+
if (targets.length === 0) {
|
|
2123
|
+
failWriteValidation(
|
|
2124
|
+
`list ${listId} holds none of: ${found.missing.join(", ")} \u2014 nothing to remove. Read its terms with \`baker ads google keyword-lists list --members --customer-id ${customerId}\``
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
await stageGoogleOps(
|
|
2128
|
+
targets.map((target) => ({ kind: "google.sharedSetMember.remove", customerId, target })),
|
|
2129
|
+
[
|
|
2130
|
+
...found.missing.length > 0 ? [
|
|
2131
|
+
`NOT STAGED \u2014 list ${listId} does not hold: ${found.missing.join(", ")}. Nothing to remove for those; say so.`
|
|
2132
|
+
] : [],
|
|
2133
|
+
"Every campaign this list is attached to stops excluding these terms once published \u2014 check `--preset negative-list-attachments` if the user should hear which."
|
|
2134
|
+
]
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
}),
|
|
2138
|
+
detach: statusCommand("google.campaignSharedSet.detach", "campaign shared set"),
|
|
2139
|
+
rename: defineCommand4({
|
|
2140
|
+
meta: {
|
|
2141
|
+
name: "rename",
|
|
2142
|
+
description: `Rename a keyword list (its type never changes). Start here:
|
|
2143
|
+
baker ads google keyword-lists rename 12263133655 --name "Junk terms" --customer-id 1234567890
|
|
2144
|
+
Names are unique in the account. ${STAGED_WRITE}`
|
|
2145
|
+
},
|
|
2146
|
+
args: {
|
|
2147
|
+
...customerIdArg,
|
|
2148
|
+
id: { type: "positional", description: "The list's id or customers/{cid}/sharedSets/{id}", required: false },
|
|
2149
|
+
name: { type: "string", description: "New name \u2014 unique among the account's lists" }
|
|
2150
|
+
},
|
|
2151
|
+
run: async ({ args }) => {
|
|
2152
|
+
const customerId = requireCustomerId(args);
|
|
2153
|
+
await stageUpdate("google.sharedSet.update", customerId, requireTarget(args, "keyword list"), {
|
|
2154
|
+
name: requireStringFlag(args.name, "--name")
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}),
|
|
2158
|
+
delete: defineCommand4({
|
|
2159
|
+
meta: {
|
|
2160
|
+
name: "delete",
|
|
2161
|
+
description: `Remove a whole keyword list (to take terms OUT of a list, use \`keyword-lists remove\`). Start here:
|
|
2162
|
+
baker ads google keyword-lists delete 12263133655 --customer-id 1234567890
|
|
2163
|
+
Google refuses to remove a list that is still attached \u2014 to a campaign (even one removed in the same publish), to the whole account, or as a campaign's brand list: take it off in the same draft first (\`keyword-lists detach\`, \`account-exclusions remove\`, \`campaign-criteria remove\`) \u2014 staging refuses the delete until then, naming each one. Those campaigns stop excluding its terms; staging says how many. ${STAGED_WRITE}`
|
|
2164
|
+
},
|
|
2165
|
+
args: {
|
|
2166
|
+
...customerIdArg,
|
|
2167
|
+
id: { type: "positional", description: "The list's id or customers/{cid}/sharedSets/{id}", required: false }
|
|
2168
|
+
},
|
|
2169
|
+
run: async ({ args }) => {
|
|
2170
|
+
const customerId = requireCustomerId(args);
|
|
2171
|
+
await stageTarget("google.sharedSet.remove", customerId, requireTarget(args, "keyword list"));
|
|
2172
|
+
}
|
|
2173
|
+
})
|
|
929
2174
|
}
|
|
930
2175
|
});
|
|
2176
|
+
async function readListMembersOrExit(customerId, listId) {
|
|
2177
|
+
try {
|
|
2178
|
+
return await readListMembers(customerId, listId);
|
|
2179
|
+
} catch (err) {
|
|
2180
|
+
handleGoogleError(err);
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
931
2183
|
function rsaContentFromFlags(args) {
|
|
932
2184
|
const headlines = copyListFlag(args.headlines, "headlines");
|
|
933
2185
|
const descriptions = copyListFlag(args.descriptions, "descriptions");
|
|
@@ -993,6 +2245,13 @@ function videoContentFromFlags(args, base) {
|
|
|
993
2245
|
if (videoAssets) {
|
|
994
2246
|
content.videoAssets = videoAssets;
|
|
995
2247
|
}
|
|
2248
|
+
if (typeof args["business-name"] === "string") {
|
|
2249
|
+
content.businessName = args["business-name"];
|
|
2250
|
+
}
|
|
2251
|
+
const logoImages = listFlag(args["logo-image-assets"]);
|
|
2252
|
+
if (logoImages) {
|
|
2253
|
+
content.logoImageAssets = logoImages;
|
|
2254
|
+
}
|
|
996
2255
|
const finalUrls = listFlag(args["final-url"]);
|
|
997
2256
|
if (finalUrls) {
|
|
998
2257
|
content.finalUrls = finalUrls;
|
|
@@ -1112,7 +2371,7 @@ var FORMAT_CONTENT_FLAGS = {
|
|
|
1112
2371
|
"square-marketing-images",
|
|
1113
2372
|
"logo-images"
|
|
1114
2373
|
],
|
|
1115
|
-
video: ["video-assets", "final-url"],
|
|
2374
|
+
video: ["video-assets", "business-name", "logo-image-assets", "final-url"],
|
|
1116
2375
|
demandGen: [
|
|
1117
2376
|
"ad-name",
|
|
1118
2377
|
"headlines",
|
|
@@ -1135,14 +2394,16 @@ var FORMAT_CONTENT_FLAGS = {
|
|
|
1135
2394
|
"call-to-action-assets"
|
|
1136
2395
|
],
|
|
1137
2396
|
app: ["headlines", "descriptions", "image-assets", "video-assets"],
|
|
1138
|
-
//
|
|
1139
|
-
|
|
1140
|
-
call: []
|
|
2397
|
+
// No copy at all: Google builds each product ad from the Merchant Center feed.
|
|
2398
|
+
shoppingProduct: []
|
|
1141
2399
|
};
|
|
1142
2400
|
function formatsReading(flag) {
|
|
1143
2401
|
return Object.entries(FORMAT_CONTENT_FLAGS).filter(([, flags]) => flags.includes(flag)).map(([format]) => format);
|
|
1144
2402
|
}
|
|
1145
2403
|
function assertFlagsFitFormat(args, format) {
|
|
2404
|
+
if (format === "call") {
|
|
2405
|
+
failWriteValidation(CALL_AD_RETIRED_MESSAGE);
|
|
2406
|
+
}
|
|
1146
2407
|
const allowed = FORMAT_CONTENT_FLAGS[format];
|
|
1147
2408
|
if (!allowed) {
|
|
1148
2409
|
return;
|
|
@@ -1181,12 +2442,15 @@ function adContentFromFlags(args, format, fileContent) {
|
|
|
1181
2442
|
if (format === "app") {
|
|
1182
2443
|
return appContentFromFlags(args, fileContent);
|
|
1183
2444
|
}
|
|
2445
|
+
if (format === "shoppingProduct") {
|
|
2446
|
+
return { format: "shoppingProduct" };
|
|
2447
|
+
}
|
|
1184
2448
|
return fileContent ?? failWriteValidation(`--format ${format} needs --file with the ad content`);
|
|
1185
2449
|
}
|
|
1186
2450
|
var adContentArgs = {
|
|
1187
2451
|
format: {
|
|
1188
2452
|
type: "string",
|
|
1189
|
-
description: "responsiveSearch (default) | responsiveDisplay |
|
|
2453
|
+
description: "responsiveSearch (default) | responsiveDisplay | app | video | demandGen | demandGenVideo | shoppingProduct (a Shopping ad group's product ad \u2014 no copy flags)"
|
|
1190
2454
|
},
|
|
1191
2455
|
headlines: {
|
|
1192
2456
|
type: "string",
|
|
@@ -1207,7 +2471,7 @@ var adContentArgs = {
|
|
|
1207
2471
|
type: "string",
|
|
1208
2472
|
description: `Long headlines, up to 5 of 90 chars (demandGenVideo \u2014 no other format has them) \u2014 ${AD_COPY_LIST_USAGE}`
|
|
1209
2473
|
},
|
|
1210
|
-
"business-name": { type: "string", description: "Business name (responsiveDisplay/demandGen/demandGenVideo)" },
|
|
2474
|
+
"business-name": { type: "string", description: "Business name (responsiveDisplay/demandGen/demandGenVideo/video)" },
|
|
1211
2475
|
"marketing-images": {
|
|
1212
2476
|
type: "string",
|
|
1213
2477
|
description: "Comma-separated marketing image asset refs (responsiveDisplay)"
|
|
@@ -1225,7 +2489,7 @@ var adContentArgs = {
|
|
|
1225
2489
|
"square-image-assets": { type: "string", description: "Comma-separated square image asset refs (demandGen)" },
|
|
1226
2490
|
"logo-image-assets": {
|
|
1227
2491
|
type: "string",
|
|
1228
|
-
description: "Comma-separated logo image asset refs (demandGen/demandGenVideo)"
|
|
2492
|
+
description: "Comma-separated logo image asset refs (demandGen/demandGenVideo/video)"
|
|
1229
2493
|
},
|
|
1230
2494
|
"call-to-action-assets": {
|
|
1231
2495
|
type: "string",
|
|
@@ -1237,14 +2501,14 @@ function assertNoContentFlags(args) {
|
|
|
1237
2501
|
const passed = Object.keys(adContentArgs).filter((flag) => flag !== "format" && args[flag] !== void 0);
|
|
1238
2502
|
if (passed.length > 0) {
|
|
1239
2503
|
failWriteValidation(
|
|
1240
|
-
`${passed.map((flag) => `--${flag}`).join(", ")} only build an ad's content alongside --format (responsiveSearch | responsiveDisplay |
|
|
2504
|
+
`${passed.map((flag) => `--${flag}`).join(", ")} only build an ad's content alongside --format (responsiveSearch | responsiveDisplay | app | video | demandGen | demandGenVideo | shoppingProduct) \u2014 add it, or pass --file with the complete content`
|
|
1241
2505
|
);
|
|
1242
2506
|
}
|
|
1243
2507
|
}
|
|
1244
|
-
var adsCommand =
|
|
2508
|
+
var adsCommand = defineCommand4({
|
|
1245
2509
|
meta: { name: "ads", description: "Stage ad create/update/pause/resume/remove" },
|
|
1246
2510
|
subCommands: {
|
|
1247
|
-
create:
|
|
2511
|
+
create: defineCommand4({
|
|
1248
2512
|
meta: {
|
|
1249
2513
|
name: "create",
|
|
1250
2514
|
description: "Stage an ad (responsive search via flags; other formats via --file). RSA needs 3\u201315 headlines and 2\u20134 descriptions \u2014 give Google 8\u201312 varied headlines to test, and stage 2\u20134 ads per ad group. Performance Max has no ad format of its own \u2014 stage its copy with `asset-groups create`."
|
|
@@ -1267,14 +2531,14 @@ var adsCommand = defineCommand2({
|
|
|
1267
2531
|
customerId,
|
|
1268
2532
|
{
|
|
1269
2533
|
adGroup: requireStringFlag(args["ad-group-ref"] ?? file.adGroup, "--ad-group-ref"),
|
|
1270
|
-
status: args.status ?? file.status,
|
|
2534
|
+
status: defaultAdStatus(format, args.status ?? file.status),
|
|
1271
2535
|
content
|
|
1272
2536
|
},
|
|
1273
2537
|
copyListSplitHints(args)
|
|
1274
2538
|
);
|
|
1275
2539
|
}
|
|
1276
2540
|
}),
|
|
1277
|
-
update:
|
|
2541
|
+
update: defineCommand4({
|
|
1278
2542
|
meta: {
|
|
1279
2543
|
name: "update",
|
|
1280
2544
|
description: "Stage an ad update. Any content change needs --format plus EVERY required field of that format (or --file with the complete content) \u2014 a field you send replaces its current value wholesale, so a list you pass must already include the entries you want to keep. Optional asset lists you omit entirely are left untouched on the ad. Status-only changes need neither --format nor content flags."
|
|
@@ -1305,13 +2569,13 @@ var adsCommand = defineCommand2({
|
|
|
1305
2569
|
remove: statusCommand("google.ad.remove", "ad")
|
|
1306
2570
|
}
|
|
1307
2571
|
});
|
|
1308
|
-
var assetGroupsCommand =
|
|
2572
|
+
var assetGroupsCommand = defineCommand4({
|
|
1309
2573
|
meta: {
|
|
1310
2574
|
name: "asset-groups",
|
|
1311
2575
|
description: "Stage Performance Max asset group create/update \u2014 the PMax campaign's copy and images."
|
|
1312
2576
|
},
|
|
1313
2577
|
subCommands: {
|
|
1314
|
-
create:
|
|
2578
|
+
create: defineCommand4({
|
|
1315
2579
|
meta: {
|
|
1316
2580
|
name: "create",
|
|
1317
2581
|
description: "Stage a Performance Max asset group: 3\u201315 headlines (\u226430ch), 1\u20135 long headlines (\u226490ch), 2\u20135 descriptions (\u226490ch), a business name, and image refs."
|
|
@@ -1364,7 +2628,7 @@ var assetGroupsCommand = defineCommand2({
|
|
|
1364
2628
|
await stageCreate("google.assetGroup.create", customerId, payload, copyListSplitHints(args));
|
|
1365
2629
|
}
|
|
1366
2630
|
}),
|
|
1367
|
-
update:
|
|
2631
|
+
update: defineCommand4({
|
|
1368
2632
|
meta: { name: "update", description: "Stage a Performance Max asset group update" },
|
|
1369
2633
|
args: {
|
|
1370
2634
|
...customerIdArg,
|
|
@@ -1384,7 +2648,7 @@ var assetGroupsCommand = defineCommand2({
|
|
|
1384
2648
|
await stageUpdate("google.assetGroup.update", customerId, target, payload);
|
|
1385
2649
|
}
|
|
1386
2650
|
}),
|
|
1387
|
-
attach:
|
|
2651
|
+
attach: defineCommand4({
|
|
1388
2652
|
meta: {
|
|
1389
2653
|
name: "attach",
|
|
1390
2654
|
description: "Add one existing asset to an existing asset group. Start here to refresh a live PMax group's creatives: attach the new image, then `detach` the old one \u2014 never detach first."
|
|
@@ -1409,30 +2673,16 @@ var assetGroupsCommand = defineCommand2({
|
|
|
1409
2673
|
await stageCreate("google.assetGroupAsset.attach", customerId, payload, [
|
|
1410
2674
|
"Swapping a creative? Stage the matching `asset-groups detach` for the image this one replaces \u2014 an attach alone grows the group instead of refreshing it.",
|
|
1411
2675
|
"The field type must match the image's real ratio (1.91:1 \u2192 MARKETING_IMAGE, 1:1 \u2192 SQUARE_MARKETING_IMAGE, 4:5 \u2192 PORTRAIT_MARKETING_IMAGE); Google rejects a mismatch at publish.",
|
|
1412
|
-
"Slots have ceilings as well as minimums: BUSINESS_NAME and CALL_TO_ACTION_SELECTION hold 1, LOGO 5, LONG_HEADLINE and DESCRIPTION 5, HEADLINE 15, YOUTUBE_VIDEO 15, images 20.
|
|
2676
|
+
"Slots have ceilings as well as minimums: BUSINESS_NAME and CALL_TO_ACTION_SELECTION hold 1, LOGO 5, LONG_HEADLINE and DESCRIPTION 5, HEADLINE 15, YOUTUBE_VIDEO 15, images 20. Staging counts what the group holds plus this chat's attaches and detaches, and refuses an attach to a full slot \u2014 stage the detach alongside it; removals are sent after additions. Replacing the business name (exactly 1): stage its detach first, then this attach."
|
|
1413
2677
|
]);
|
|
1414
2678
|
}
|
|
1415
2679
|
}),
|
|
1416
2680
|
detach: statusCommand("google.assetGroupAsset.detach", "asset group asset", [
|
|
1417
|
-
"Stage
|
|
2681
|
+
"Replacing this image? Stage its `asset-groups attach` too \u2014 removals publish after every addition whatever order they are staged in, so the new image is in place before this one leaves and the group never drops below its per-ratio minimum.",
|
|
1418
2682
|
"Pass the full composite resource name (customers/{cid}/assetGroupAssets/{assetGroupId}~{assetId}~{FIELD_TYPE}) \u2014 read it from `asset_group_asset.resource_name` via `ads google query`, scoping `asset_group_asset.status != 'REMOVED'`: the link carries its own status and Google returns an already-detached one like any other row."
|
|
1419
2683
|
])
|
|
1420
2684
|
}
|
|
1421
2685
|
});
|
|
1422
|
-
function fileCreateCommand(name, kind, description, hints) {
|
|
1423
|
-
return defineCommand2({
|
|
1424
|
-
meta: { name, description },
|
|
1425
|
-
args: { ...customerIdArg, ...fileArg },
|
|
1426
|
-
run: async ({ args }) => {
|
|
1427
|
-
const customerId = requireCustomerId(args);
|
|
1428
|
-
const payload = loadJsonFileArg(args.file);
|
|
1429
|
-
if (Object.keys(payload).length === 0) {
|
|
1430
|
-
failWriteValidation(`${kind} needs --file with the op payload`);
|
|
1431
|
-
}
|
|
1432
|
-
await stageGoogleOp({ kind, customerId, payload }, typeof hints === "function" ? hints(payload) : hints);
|
|
1433
|
-
}
|
|
1434
|
-
});
|
|
1435
|
-
}
|
|
1436
2686
|
function imageAssetHints(payload) {
|
|
1437
2687
|
if (payload.type !== "image") {
|
|
1438
2688
|
return [];
|
|
@@ -1442,7 +2692,7 @@ function imageAssetHints(payload) {
|
|
|
1442
2692
|
"Until a link to it is staged, Baker keeps a Task for this image in this chat's changes \u2014 the user adding it to the campaign under Assets \u2014 and withdraws it when an accepted link names the image; do not stage a second one with `baker actions`. If that Task is still there after the attach, tell the user the images are already in their Google Ads account and only need adding to the campaign under Assets, and name them. Never tell them to upload images they already have. (A Performance Max asset group is different \u2014 images link there with `asset-groups attach`.)"
|
|
1443
2693
|
];
|
|
1444
2694
|
}
|
|
1445
|
-
var assetsUpdateCommand =
|
|
2695
|
+
var assetsUpdateCommand = defineCommand4({
|
|
1446
2696
|
meta: {
|
|
1447
2697
|
name: "update",
|
|
1448
2698
|
description: "Update a staged or live asset in place (flat partial \u2014 set only the fields that changed). Works across asset types: sitelink (--link-text/--description1/--description2/--final-urls), callout (--callout-text), structured snippet (--header/--values), call-to-action (--cta), text (--text), or --name."
|
|
@@ -1479,7 +2729,7 @@ var assetsUpdateCommand = defineCommand2({
|
|
|
1479
2729
|
await stageUpdate("google.asset.update", customerId, target, payload);
|
|
1480
2730
|
}
|
|
1481
2731
|
});
|
|
1482
|
-
var assetsCommand =
|
|
2732
|
+
var assetsCommand = defineCommand4({
|
|
1483
2733
|
meta: {
|
|
1484
2734
|
name: "assets",
|
|
1485
2735
|
description: "Stage asset create/update + asset-link attach/detach (via --file). For a high-performance Search campaign add \u22654 sitelinks, \u22653 callouts and \u22651 structured snippet per campaign. Image extensions: `create` uploads the image into the advertiser's account, then `attach` links it with field type AD_IMAGE (campaign or ad group). Google opens image links per account, so Baker asks Google as you stage: an account that takes them stages the link like a sitelink; one that does not skips it and stages a handover Task instead \u2014 never tell them to upload anything. The advertiser's logo links as BUSINESS_LOGO, at campaign or customer level, on a verified account."
|
|
@@ -1526,10 +2776,10 @@ var AUDIENCE_ATTACH_HINTS = [
|
|
|
1526
2776
|
'Read back what the parent ended up with before you report: `baker ads google query "SELECT campaign.id, campaign.targeting_setting.target_restrictions FROM campaign"` (or the ad_group equivalent). A missing AUDIENCE row means TARGETING \u2014 Google does not serialize an empty setting, so absence is a value, not a gap.',
|
|
1527
2777
|
"Staging checks the list is big enough to serve on this campaign's network (1,000 users on search, 100 on display) and refuses a targeting attach that is not. It does NOT check delivery afterwards, and Google reports no error when an audience stops a campaign serving \u2014 after publishing, read the campaign's impressions for the hour that follows and say what you saw."
|
|
1528
2778
|
];
|
|
1529
|
-
var audiencesAttachCommand =
|
|
2779
|
+
var audiencesAttachCommand = defineCommand4({
|
|
1530
2780
|
meta: {
|
|
1531
2781
|
name: "attach",
|
|
1532
|
-
description: "Target or exclude an audience (user
|
|
2782
|
+
description: "Target or exclude an audience on a campaign or ad group \u2014 a list (--user-list), an interest or in-market segment (--interest) or a custom segment (--custom-segment). Start here: baker ads google audiences attach --customer-id 1234567890 --level campaign --parent 23837707642 --user-list 9398951971 --mode observation. `--mode` is required for a positive attach and decides whether the list only adjusts bids (observation) or narrows who sees the ads (targeting) \u2014 with --mode observation the audience setting is staged alongside the attach, because Google's API otherwise defaults to targeting."
|
|
1533
2783
|
},
|
|
1534
2784
|
args: {
|
|
1535
2785
|
...customerIdArg,
|
|
@@ -1538,6 +2788,14 @@ var audiencesAttachCommand = defineCommand2({
|
|
|
1538
2788
|
level: { type: "string", description: "campaign | adGroup" },
|
|
1539
2789
|
parent: { type: "string", description: "Campaign or ad group id (or a g_temp_* ref staged earlier)" },
|
|
1540
2790
|
"user-list": { type: "string", description: "User list id (or a g_temp_* ref from `audiences create`)" },
|
|
2791
|
+
interest: {
|
|
2792
|
+
type: "string",
|
|
2793
|
+
description: "Interest or in-market segment id (`user_interest.user_interest_id`) \u2014 instead of --user-list"
|
|
2794
|
+
},
|
|
2795
|
+
"custom-segment": {
|
|
2796
|
+
type: "string",
|
|
2797
|
+
description: "Custom segment id (`custom_audience.id`) built in Google Ads \u2014 instead of --user-list"
|
|
2798
|
+
},
|
|
1541
2799
|
negative: { type: "boolean", description: "Exclude this audience instead of targeting it" }
|
|
1542
2800
|
},
|
|
1543
2801
|
run: async ({ args }) => {
|
|
@@ -1546,6 +2804,8 @@ var audiencesAttachCommand = defineCommand2({
|
|
|
1546
2804
|
level: args.level,
|
|
1547
2805
|
parent: args.parent,
|
|
1548
2806
|
userList: args["user-list"],
|
|
2807
|
+
userInterest: args.interest,
|
|
2808
|
+
customAudience: args["custom-segment"],
|
|
1549
2809
|
negative: args.negative
|
|
1550
2810
|
});
|
|
1551
2811
|
if (payload.negative === true) {
|
|
@@ -1575,7 +2835,7 @@ function audienceAttachOps(customerId, payload, mode) {
|
|
|
1575
2835
|
attachOp
|
|
1576
2836
|
];
|
|
1577
2837
|
}
|
|
1578
|
-
var audiencesTargetingCommand =
|
|
2838
|
+
var audiencesTargetingCommand = defineCommand4({
|
|
1579
2839
|
meta: {
|
|
1580
2840
|
name: "targeting",
|
|
1581
2841
|
description: "Switch the audiences already on a campaign or ad group between observation (they adjust bids only) and targeting (the ads are shown only to people in them) \u2014 the Observation/Targeting selector in the Google Ads interface. Start here: baker ads google audiences targeting 195883284706 --customer-id 1234567890 --level adGroup --mode observation."
|
|
@@ -1595,13 +2855,22 @@ var audiencesTargetingCommand = defineCommand2({
|
|
|
1595
2855
|
});
|
|
1596
2856
|
}
|
|
1597
2857
|
});
|
|
1598
|
-
var audiencesCommand =
|
|
2858
|
+
var audiencesCommand = defineCommand4({
|
|
1599
2859
|
meta: {
|
|
1600
2860
|
name: "audiences",
|
|
1601
2861
|
description: "Stage audience (user list) create, criterion attach/detach, and the observation/targeting setting the criteria are read under"
|
|
1602
2862
|
},
|
|
1603
2863
|
subCommands: {
|
|
1604
|
-
create: fileCreateCommand(
|
|
2864
|
+
create: fileCreateCommand(
|
|
2865
|
+
"create",
|
|
2866
|
+
"google.audience.create",
|
|
2867
|
+
`Stage a user-list audience from --file. Each type carries what Google builds it from. Start here:
|
|
2868
|
+
website visitors {"name":"Pricing visitors","type":"RULE_BASED","rule":{"include":[{"conditions":[{"operator":"CONTAINS","value":"/pricing"}],"lookbackWindowDays":30}],"prefill":true}}
|
|
2869
|
+
converters {"name":"Buyers","type":"BASIC","conversionActions":["<existing conversion action id>"],"membershipLifeSpanDays":90}
|
|
2870
|
+
combined {"name":"Visitors, not buyers","type":"LOGICAL","logic":[{"operator":"ANY","audiences":["<existing id>"]},{"operator":"NONE","audiences":["<existing id>"]}]}
|
|
2871
|
+
lookalike {"name":"Like buyers","type":"LOOKALIKE","lookalike":{"seedAudiences":["<existing audience id>"],"expansion":"BALANCED","countryCodes":["ES"]}} (Demand Gen only)
|
|
2872
|
+
customer list {"name":"Customers","type":"CRM_BASED","members":[{"email":"\u2026"}]}`
|
|
2873
|
+
),
|
|
1605
2874
|
attach: audiencesAttachCommand,
|
|
1606
2875
|
detach: statusCommand("google.audienceCriterion.detach", "audience criterion"),
|
|
1607
2876
|
targeting: audiencesTargetingCommand
|
|
@@ -1638,20 +2907,20 @@ var CONVERSION_GOAL_QUERY = 'baker ads google query "SELECT customer_conversion_
|
|
|
1638
2907
|
var CONVERSION_GOAL_HINTS = [
|
|
1639
2908
|
`A conversion drives automated bidding only when BOTH halves are on: the action is primary (\`conversions update --primary\`) AND its category's account-level goal is included (this command). Before publishing, read back what the account will bid toward: ${CONVERSION_GOAL_QUERY} \u2014 and say plainly which conversions this leaves driving spend.`
|
|
1640
2909
|
];
|
|
1641
|
-
var conversionsCommand =
|
|
2910
|
+
var conversionsCommand = defineCommand4({
|
|
1642
2911
|
meta: {
|
|
1643
2912
|
name: "conversions",
|
|
1644
2913
|
description: "Stage conversion action create/update (via --file), the account-level goal, and manual conversion uploads for a period where tracking was broken"
|
|
1645
2914
|
},
|
|
1646
2915
|
subCommands: {
|
|
1647
|
-
upload: () => import("./conversion-upload-command-
|
|
2916
|
+
upload: () => import("./conversion-upload-command-CSD344BY.js").then((m) => m.conversionsUploadCommand),
|
|
1648
2917
|
create: fileCreateCommand(
|
|
1649
2918
|
"create",
|
|
1650
2919
|
"google.conversionAction.create",
|
|
1651
2920
|
"Stage a conversion action (secondary unless the payload sets primaryForGoal)",
|
|
1652
2921
|
CONVERSION_CREATE_HINTS
|
|
1653
2922
|
),
|
|
1654
|
-
update:
|
|
2923
|
+
update: defineCommand4({
|
|
1655
2924
|
meta: {
|
|
1656
2925
|
name: "update",
|
|
1657
2926
|
description: "Stage a conversion action update. --primary makes it a primary action (automated bidding optimizes toward it); --no-primary demotes it to secondary (reported only)."
|
|
@@ -1678,7 +2947,7 @@ var conversionsCommand = defineCommand2({
|
|
|
1678
2947
|
);
|
|
1679
2948
|
}
|
|
1680
2949
|
}),
|
|
1681
|
-
goal:
|
|
2950
|
+
goal: defineCommand4({
|
|
1682
2951
|
meta: {
|
|
1683
2952
|
name: "goal",
|
|
1684
2953
|
description: `Stage the ACCOUNT-LEVEL conversion goal for one (category, origin) pair \u2014 Google Ads' "Included in account-level goals" switch. Start here when a conversion action is already Primary but bidding still ignores it: both halves have to be on. One command per pair. --biddable includes the category in bidding; --no-biddable measures it only.`
|
|
@@ -1709,11 +2978,14 @@ var conversionsCommand = defineCommand2({
|
|
|
1709
2978
|
})
|
|
1710
2979
|
}
|
|
1711
2980
|
});
|
|
1712
|
-
var biddingStrategiesCommand =
|
|
1713
|
-
meta: {
|
|
2981
|
+
var biddingStrategiesCommand = defineCommand4({
|
|
2982
|
+
meta: {
|
|
2983
|
+
name: "bidding-strategies",
|
|
2984
|
+
description: "Stage portfolio bidding strategy create/update (via --file) and remove"
|
|
2985
|
+
},
|
|
1714
2986
|
subCommands: {
|
|
1715
2987
|
create: fileCreateCommand("create", "google.biddingStrategy.create", "Stage a portfolio bidding strategy"),
|
|
1716
|
-
update:
|
|
2988
|
+
update: defineCommand4({
|
|
1717
2989
|
meta: { name: "update", description: "Stage a portfolio bidding strategy update" },
|
|
1718
2990
|
args: { ...customerIdArg, ...fileArg },
|
|
1719
2991
|
run: async ({ args }) => {
|
|
@@ -1725,18 +2997,39 @@ var biddingStrategiesCommand = defineCommand2({
|
|
|
1725
2997
|
loadJsonFileArg(args.file)
|
|
1726
2998
|
);
|
|
1727
2999
|
}
|
|
1728
|
-
})
|
|
3000
|
+
}),
|
|
3001
|
+
remove: statusCommand("google.biddingStrategy.remove", "portfolio bid strategy", [
|
|
3002
|
+
"A portfolio can be removed only once no campaign bids with it: staging refuses it while one does, unless this draft removes those campaigns or moves them to their own bidding or another portfolio."
|
|
3003
|
+
])
|
|
1729
3004
|
}
|
|
1730
3005
|
});
|
|
1731
|
-
|
|
1732
|
-
|
|
3006
|
+
function labelAttachFromFlags(args) {
|
|
3007
|
+
return {
|
|
3008
|
+
level: requireStringFlag(args.level, "--level"),
|
|
3009
|
+
parent: requireStringFlag(args.on, "--on"),
|
|
3010
|
+
label: requireStringFlag(args["label-ref"], "--label-ref")
|
|
3011
|
+
};
|
|
3012
|
+
}
|
|
3013
|
+
function labelUpdateFromFlags(args) {
|
|
3014
|
+
return {
|
|
3015
|
+
name: args.name,
|
|
3016
|
+
backgroundColor: args["background-color"],
|
|
3017
|
+
description: args.description
|
|
3018
|
+
};
|
|
3019
|
+
}
|
|
3020
|
+
var LABEL_LEVEL_HELP = "campaign | adGroup | ad | keyword. An ad or keyword is named by its full resource name (customers/{cid}/adGroupAds/{adGroupId}~{adId}, customers/{cid}/adGroupCriteria/{adGroupId}~{criterionId}). Google has no labels on assets, and labels on the account itself come only from a manager account \u2014 both are refused.";
|
|
3021
|
+
var labelsCommand = defineCommand4({
|
|
3022
|
+
meta: {
|
|
3023
|
+
name: "labels",
|
|
3024
|
+
description: "Stage labels: create, update (name/colour/description), remove, attach to a campaign/ad group/ad/keyword, detach"
|
|
3025
|
+
},
|
|
1733
3026
|
subCommands: {
|
|
1734
|
-
create:
|
|
1735
|
-
meta: { name: "create", description:
|
|
3027
|
+
create: defineCommand4({
|
|
3028
|
+
meta: { name: "create", description: `Create a label. Names are unique in the account. ${STAGED_WRITE}` },
|
|
1736
3029
|
args: {
|
|
1737
3030
|
...customerIdArg,
|
|
1738
3031
|
name: { type: "string" },
|
|
1739
|
-
"background-color": { type: "string" },
|
|
3032
|
+
"background-color": { type: "string", description: "Hex colour, e.g. #1A73E8" },
|
|
1740
3033
|
description: { type: "string" }
|
|
1741
3034
|
},
|
|
1742
3035
|
run: async ({ args }) => {
|
|
@@ -1748,70 +3041,67 @@ var labelsCommand = defineCommand2({
|
|
|
1748
3041
|
});
|
|
1749
3042
|
}
|
|
1750
3043
|
}),
|
|
1751
|
-
|
|
1752
|
-
}
|
|
1753
|
-
});
|
|
1754
|
-
function bidModifierFlag(value) {
|
|
1755
|
-
if (value === void 0 || value === null || value === "") {
|
|
1756
|
-
failWriteValidation(
|
|
1757
|
-
"--bid-modifier is required: 1 for no adjustment, 1.25 to bid 25% more, 0.9 to bid 10% less, 0 to stop serving on it"
|
|
1758
|
-
);
|
|
1759
|
-
}
|
|
1760
|
-
const num = Number(value);
|
|
1761
|
-
if (Number.isNaN(num) || num < 0 || num > 10 || num > 0 && num < 0.1) {
|
|
1762
|
-
failWriteValidation("--bid-modifier must be 0 (stop serving on it) or between 0.1 and 10.0");
|
|
1763
|
-
}
|
|
1764
|
-
return num;
|
|
1765
|
-
}
|
|
1766
|
-
var campaignCriteriaCommand = defineCommand2({
|
|
1767
|
-
meta: {
|
|
1768
|
-
name: "campaign-criteria",
|
|
1769
|
-
description: "Stage campaign criteria (location/language/adSchedule/device): add/remove a targeting criterion, or update a device bid adjustment"
|
|
1770
|
-
},
|
|
1771
|
-
subCommands: {
|
|
1772
|
-
add: fileCreateCommand("add", "google.campaignCriterion.add", "Add a campaign criterion"),
|
|
1773
|
-
update: defineCommand2({
|
|
3044
|
+
update: defineCommand4({
|
|
1774
3045
|
meta: {
|
|
1775
3046
|
name: "update",
|
|
1776
|
-
description:
|
|
3047
|
+
description: `Rename a label or change its colour or description. Start here:
|
|
3048
|
+
baker ads google labels update 22297940102 --name "Q4 push" --customer-id 1234567890
|
|
3049
|
+
\`--description ""\` clears the description. ${STAGED_WRITE}`
|
|
1777
3050
|
},
|
|
1778
3051
|
args: {
|
|
1779
3052
|
...customerIdArg,
|
|
1780
|
-
id: {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
},
|
|
1785
|
-
"bid-modifier": {
|
|
1786
|
-
type: "string",
|
|
1787
|
-
description: "1 = no adjustment, 1.25 = bid 25% more, 0.9 = bid 10% less, 0 = don't serve on it"
|
|
1788
|
-
}
|
|
3053
|
+
id: { type: "positional", description: "Label id or customers/{cid}/labels/{id}", required: false },
|
|
3054
|
+
name: { type: "string", description: "New name \u2014 unique in the account" },
|
|
3055
|
+
"background-color": { type: "string", description: "Hex colour, e.g. #1A73E8" },
|
|
3056
|
+
description: { type: "string", description: 'New description ("" clears it)' }
|
|
1789
3057
|
},
|
|
1790
3058
|
run: async ({ args }) => {
|
|
1791
3059
|
const customerId = requireCustomerId(args);
|
|
1792
|
-
|
|
1793
|
-
await stageUpdate("google.campaignCriterion.update", customerId, target, {
|
|
1794
|
-
bidModifier: bidModifierFlag(args["bid-modifier"])
|
|
1795
|
-
});
|
|
3060
|
+
await stageUpdate("google.label.update", customerId, requireTarget(args, "label"), labelUpdateFromFlags(args));
|
|
1796
3061
|
}
|
|
1797
3062
|
}),
|
|
1798
|
-
remove:
|
|
3063
|
+
remove: statusCommand("google.label.remove", "label", [
|
|
3064
|
+
"This removes the label itself \u2014 no need to detach it first. To take it off one thing only, use `labels detach <link resource name>`."
|
|
3065
|
+
]),
|
|
3066
|
+
attach: defineCommand4({
|
|
1799
3067
|
meta: {
|
|
1800
|
-
name: "
|
|
1801
|
-
description:
|
|
3068
|
+
name: "attach",
|
|
3069
|
+
description: `Put a label on a campaign, ad group, ad or keyword. Start here:
|
|
3070
|
+
baker ads google labels attach --level campaign --on 23456789 --label-ref g_temp_\u2026 --customer-id 1234567890
|
|
3071
|
+
baker ads google labels attach --level keyword --on customers/1234567890/adGroupCriteria/55~66 --label-ref 22297940102 --customer-id 1234567890
|
|
3072
|
+
Many at once: --file labels.json (a JSON array of {level, parent, label}). ${STAGED_WRITE}`
|
|
1802
3073
|
},
|
|
1803
3074
|
args: {
|
|
1804
3075
|
...customerIdArg,
|
|
1805
|
-
|
|
3076
|
+
...fileBatchArg,
|
|
3077
|
+
level: { type: "string", description: LABEL_LEVEL_HELP },
|
|
3078
|
+
on: {
|
|
3079
|
+
type: "string",
|
|
3080
|
+
description: "What gets the label: campaign/ad group ref, or an ad's/keyword's resource name"
|
|
3081
|
+
},
|
|
3082
|
+
"label-ref": {
|
|
3083
|
+
type: "string",
|
|
3084
|
+
description: "The label: g_temp_* from `labels create`, its id, or resource name"
|
|
3085
|
+
}
|
|
1806
3086
|
},
|
|
1807
3087
|
run: async ({ args }) => {
|
|
1808
3088
|
const customerId = requireCustomerId(args);
|
|
1809
|
-
|
|
3089
|
+
const payloads = args.file ? loadJsonPayloads(args.file) : [labelAttachFromFlags(args)];
|
|
3090
|
+
if (payloads.length > GOOGLE_DRAFT_BATCH_MAX) {
|
|
3091
|
+
failWriteValidation(
|
|
3092
|
+
`${payloads.length} payloads exceeds the batch limit of ${GOOGLE_DRAFT_BATCH_MAX} \u2014 split the file`
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
await stageGoogleOps(payloads.map((payload) => ({ kind: "google.label.attach", customerId, payload })));
|
|
1810
3096
|
}
|
|
1811
|
-
})
|
|
3097
|
+
}),
|
|
3098
|
+
detach: statusCommand("google.label.detach", "label link", [
|
|
3099
|
+
'A label link is named by what it sits on and the label: customers/{cid}/campaignLabels/{campaignId}~{labelId}, adGroupLabels/{adGroupId}~{labelId}, adGroupAdLabels/{adGroupId}~{adId}~{labelId} or adGroupCriterionLabels/{adGroupId}~{criterionId}~{labelId} \u2014 read it with `baker ads google query "SELECT campaign_label.resource_name, label.name FROM campaign_label"`.'
|
|
3100
|
+
])
|
|
1812
3101
|
}
|
|
1813
3102
|
});
|
|
1814
3103
|
var googleWriteCommands = {
|
|
3104
|
+
account: accountCommand,
|
|
1815
3105
|
budgets: budgetsCommand,
|
|
1816
3106
|
campaigns: campaignsCommand,
|
|
1817
3107
|
"ad-groups": adGroupsCommand,
|
|
@@ -1824,21 +3114,41 @@ var googleWriteCommands = {
|
|
|
1824
3114
|
conversions: conversionsCommand,
|
|
1825
3115
|
"bidding-strategies": biddingStrategiesCommand,
|
|
1826
3116
|
labels: labelsCommand,
|
|
1827
|
-
"campaign-criteria": campaignCriteriaCommand
|
|
3117
|
+
"campaign-criteria": campaignCriteriaCommand,
|
|
3118
|
+
"ad-group-criteria": adGroupCriteriaCommand,
|
|
3119
|
+
"ad-group-bid-modifiers": adGroupBidModifiersCommand,
|
|
3120
|
+
"account-exclusions": accountExclusionsCommand
|
|
1828
3121
|
};
|
|
1829
3122
|
|
|
1830
3123
|
// src/commands/ads/google/index.ts
|
|
1831
|
-
var keywordsWithWrites =
|
|
3124
|
+
var keywordsWithWrites = defineCommand5({
|
|
1832
3125
|
meta: keywordsCommand.meta,
|
|
1833
|
-
subCommands: {
|
|
3126
|
+
subCommands: {
|
|
3127
|
+
...keywordsCommand.subCommands,
|
|
3128
|
+
...LIST_COMMANDS.keywords ? { list: LIST_COMMANDS.keywords } : {},
|
|
3129
|
+
...keywordWriteSubcommands
|
|
3130
|
+
}
|
|
3131
|
+
});
|
|
3132
|
+
function withList(thing, command) {
|
|
3133
|
+
const list2 = LIST_COMMANDS[thing];
|
|
3134
|
+
if (!list2) return command;
|
|
3135
|
+
return defineCommand5({ meta: command.meta, args: command.args, subCommands: { list: list2, ...command.subCommands } });
|
|
3136
|
+
}
|
|
3137
|
+
var googleWriteCommandsWithLists = Object.fromEntries(
|
|
3138
|
+
Object.entries(googleWriteCommands).map(([thing, command]) => [thing, withList(thing, command)])
|
|
3139
|
+
);
|
|
3140
|
+
var merchantCenterCommand = defineCommand5({
|
|
3141
|
+
meta: { name: "merchant-center", description: "Merchant Center accounts linked to this Google Ads account" },
|
|
3142
|
+
subCommands: LIST_COMMANDS["merchant-center"] ? { list: LIST_COMMANDS["merchant-center"] } : {}
|
|
1834
3143
|
});
|
|
1835
|
-
var googleCommand =
|
|
3144
|
+
var googleCommand = defineCommand5({
|
|
1836
3145
|
meta: {
|
|
1837
3146
|
name: "google",
|
|
1838
3147
|
description: `Google Ads commands. Query campaigns, keywords, search terms, and more via GAQL.
|
|
1839
3148
|
|
|
1840
3149
|
Start here:
|
|
1841
3150
|
baker ads google accounts \u2014 list accessible accounts
|
|
3151
|
+
baker ads google campaigns list \u2014 what exists, with ids (also ad-groups, keywords, ads, \u2026 list)
|
|
1842
3152
|
baker ads google query --list-presets \u2014 see available query templates
|
|
1843
3153
|
|
|
1844
3154
|
Examples:
|
|
@@ -1847,6 +3157,9 @@ Examples:
|
|
|
1847
3157
|
baker ads google currency --customer-id 1234567890
|
|
1848
3158
|
baker ads google url-options --customer-id 1234567890 \u2014 UTM / tracking parameters in force
|
|
1849
3159
|
baker ads google keywords discover --customer-id 1234567890 --seeds "running shoes"
|
|
3160
|
+
baker ads google keywords forecast --customer-id 1234567890 --text "running shoes:PHRASE" --max-cpc 1.2
|
|
3161
|
+
baker ads google previews --customer-id 1234567890 --asset-groups 111 \u2014 shareable preview link
|
|
3162
|
+
baker ads google invoices --customer-id 1234567890 \u2014 last month's invoices
|
|
1850
3163
|
|
|
1851
3164
|
Staged writes (never touch Google until the chat is published):
|
|
1852
3165
|
baker ads google budgets create --customer-id 1234567890 --name "Search" --amount 50
|
|
@@ -1857,16 +3170,21 @@ Full guide: __tooling__/docs/tools/baker/ads-google.md`
|
|
|
1857
3170
|
},
|
|
1858
3171
|
subCommands: {
|
|
1859
3172
|
accounts: () => import("./accounts-KYJO3JC6.js").then((m) => m.accountsCommand),
|
|
1860
|
-
currency: () => import("./currency-
|
|
1861
|
-
"url-options": () => import("./url-options-
|
|
1862
|
-
changes: () => import("./changes-
|
|
1863
|
-
|
|
3173
|
+
currency: () => import("./currency-47QHFHDA.js").then((m) => m.currencyCommand),
|
|
3174
|
+
"url-options": () => import("./url-options-JSX6HBZL.js").then((m) => m.urlOptionsCommand),
|
|
3175
|
+
changes: () => import("./changes-AYLNY6KN.js").then((m) => m.changesCommand),
|
|
3176
|
+
invoices: () => import("./account-reads-W3XO7CIW.js").then((m) => m.invoicesCommand),
|
|
3177
|
+
"identity-verification": () => import("./account-reads-W3XO7CIW.js").then((m) => m.identityVerificationCommand),
|
|
3178
|
+
previews: () => import("./account-reads-W3XO7CIW.js").then((m) => m.previewsCommand),
|
|
3179
|
+
brands: () => import("./account-reads-W3XO7CIW.js").then((m) => m.brandsCommand),
|
|
3180
|
+
query: () => import("./query-GDUTZ4R7.js").then((m) => m.queryCommand),
|
|
1864
3181
|
keywords: keywordsWithWrites,
|
|
1865
|
-
...
|
|
1866
|
-
|
|
3182
|
+
...googleWriteCommandsWithLists,
|
|
3183
|
+
"merchant-center": merchantCenterCommand,
|
|
3184
|
+
draft: () => import("./draft-666YKA3P.js").then((m) => m.draftCommand)
|
|
1867
3185
|
}
|
|
1868
3186
|
});
|
|
1869
3187
|
export {
|
|
1870
3188
|
googleCommand
|
|
1871
3189
|
};
|
|
1872
|
-
//# sourceMappingURL=google-
|
|
3190
|
+
//# sourceMappingURL=google-CI2AMJKN.js.map
|