@koda-sl/baker-cli 0.255.0-dev.9688d6d20 → 0.255.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 +15 -1
- package/dist/{chunk-5JHSX3ZB.js → chunk-6F52WYB7.js} +2 -2
- package/dist/{chunk-JKSMLAQN.js → chunk-GU7IWEBC.js} +7 -3
- package/dist/chunk-GU7IWEBC.js.map +1 -0
- package/dist/{chunk-6MAVG6LO.js → chunk-KSOGK7SJ.js} +3 -3
- package/dist/{chunk-HBDGUVUH.js → chunk-SJPRTSGY.js} +4 -4
- package/dist/chunk-SJPRTSGY.js.map +1 -0
- package/dist/cli.js +135 -48
- package/dist/cli.js.map +1 -1
- package/dist/{client-3TNO43FO.js → client-VDCCDEHE.js} +4 -2
- package/dist/engine/index.js +1 -1
- package/dist/{output-B3OCLLS5.js → output-JBYI5IVX.js} +3 -3
- package/dist/{shared-J24NXBW4.js → shared-GWFXJXKJ.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-HBDGUVUH.js.map +0 -1
- package/dist/chunk-JKSMLAQN.js.map +0 -1
- /package/dist/{chunk-5JHSX3ZB.js.map → chunk-6F52WYB7.js.map} +0 -0
- /package/dist/{chunk-6MAVG6LO.js.map → chunk-KSOGK7SJ.js.map} +0 -0
- /package/dist/{client-3TNO43FO.js.map → client-VDCCDEHE.js.map} +0 -0
- /package/dist/{output-B3OCLLS5.js.map → output-JBYI5IVX.js.map} +0 -0
- /package/dist/{shared-J24NXBW4.js.map → shared-GWFXJXKJ.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
ulid,
|
|
59
59
|
validateCanvasDeep,
|
|
60
60
|
ytDlpBlockSignal
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-SJPRTSGY.js";
|
|
62
62
|
import {
|
|
63
63
|
csvOrJson,
|
|
64
64
|
daysAgoIso,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
resolveAccountIdArg,
|
|
68
68
|
resolveEffectiveStatus,
|
|
69
69
|
todayIso
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-KSOGK7SJ.js";
|
|
71
71
|
import {
|
|
72
72
|
buildQueryCacheKey,
|
|
73
73
|
cacheGet,
|
|
@@ -83,13 +83,14 @@ import {
|
|
|
83
83
|
writeAdsJson,
|
|
84
84
|
writeAdsOutput,
|
|
85
85
|
writeJsonEnvelope
|
|
86
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-6F52WYB7.js";
|
|
87
87
|
import {
|
|
88
88
|
ApiError,
|
|
89
89
|
apiGet,
|
|
90
|
+
apiGetWithHeaders,
|
|
90
91
|
apiPost,
|
|
91
92
|
validateConvexId
|
|
92
|
-
} from "./chunk-
|
|
93
|
+
} from "./chunk-GU7IWEBC.js";
|
|
93
94
|
import {
|
|
94
95
|
installStreamTaps,
|
|
95
96
|
logInvocation
|
|
@@ -2152,8 +2153,18 @@ var ADVERTISING_CHANNEL_TYPES = [
|
|
|
2152
2153
|
"LOCAL",
|
|
2153
2154
|
"SMART",
|
|
2154
2155
|
"TRAVEL",
|
|
2155
|
-
"HOTEL"
|
|
2156
|
-
|
|
2156
|
+
"HOTEL"
|
|
2157
|
+
// This constant has ONE consumer — `campaignCreateSchema.channelType` — so it is a write-side
|
|
2158
|
+
// allowlist, not a vocabulary. A value belongs here only if Baker can legitimately create a
|
|
2159
|
+
// campaign of that type. GAQL reads never pass through it (`listSizeRequirement` takes the raw
|
|
2160
|
+
// string straight off the row, deliberately), so adding a channel here buys no read coverage.
|
|
2161
|
+
//
|
|
2162
|
+
// Two omissions are therefore deliberate. DISCOVERY used to sit here: Google renamed it
|
|
2163
|
+
// DEMAND_GEN and dropped the old name from the v23 enum, so a campaign staged as DISCOVERY could
|
|
2164
|
+
// only ever fail at publish — inside an atomic batch, taking every co-staged op down with it.
|
|
2165
|
+
// LOCAL_SERVICES is a real v23 channel but is not creatable through the API; Google's own LSA
|
|
2166
|
+
// migration produces those campaigns. Accepting either one would stage a campaign that passes
|
|
2167
|
+
// validation, reaches a review card a user approves, and then fails the whole publish.
|
|
2157
2168
|
];
|
|
2158
2169
|
var ADVERTISING_CHANNEL_SUB_TYPES = [
|
|
2159
2170
|
"SEARCH_MOBILE_APP",
|
|
@@ -2183,7 +2194,13 @@ var BIDDING_STRATEGY_TYPES = [
|
|
|
2183
2194
|
"TARGET_IMPRESSION_SHARE",
|
|
2184
2195
|
"MANUAL_CPM",
|
|
2185
2196
|
"MANUAL_CPV",
|
|
2186
|
-
"PERCENT_CPC"
|
|
2197
|
+
"PERCENT_CPC",
|
|
2198
|
+
// Demand Gen's "maximize clicks at a target CPC" — `Campaign.target_cpc`, overridable per ad
|
|
2199
|
+
// group via `AdGroup.target_cpc_micros`. Confirmed present in v23.
|
|
2200
|
+
// It is one of the target-driven strategies governed by the August 2026 budget-limited
|
|
2201
|
+
// change, so modelling it as a strategy without a target would leave a Demand Gen
|
|
2202
|
+
// campaign's exposure invisible to `biddingTargetWarnings`.
|
|
2203
|
+
"TARGET_CPC"
|
|
2187
2204
|
];
|
|
2188
2205
|
var MERGED_TARGET_STRATEGY_CHANNELS = /* @__PURE__ */ new Set(["SEARCH"]);
|
|
2189
2206
|
var MERGED_TARGET_CPA_MESSAGE = "Google doesn't accept Target CPA as a bidding strategy of its own on a Search campaign \u2014 the target sits inside Maximize conversions. Choose Maximize conversions and give it the same target CPA; it bids exactly the same.";
|
|
@@ -2197,6 +2214,13 @@ function mergedTargetStrategyRefusal(channelType, biddingType) {
|
|
|
2197
2214
|
}
|
|
2198
2215
|
return biddingType === "TARGET_ROAS" ? MERGED_TARGET_ROAS_MESSAGE : void 0;
|
|
2199
2216
|
}
|
|
2217
|
+
var TARGET_CPC_DEMAND_GEN_MESSAGE = "Google only accepts Target CPC on a Demand Gen campaign \u2014 no other campaign type has a target CPC. Pick the bidding this campaign's type supports (Maximize conversions with a target CPA on Search), or set the target CPC on the Demand Gen campaign that should carry it.";
|
|
2218
|
+
function demandGenOnlyStrategyRefusal(channelType, biddingType) {
|
|
2219
|
+
if (biddingType !== "TARGET_CPC" || channelType === void 0 || channelType === "DEMAND_GEN") {
|
|
2220
|
+
return void 0;
|
|
2221
|
+
}
|
|
2222
|
+
return TARGET_CPC_DEMAND_GEN_MESSAGE;
|
|
2223
|
+
}
|
|
2200
2224
|
var POSITIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
|
|
2201
2225
|
var NEGATIVE_GEO_TARGET_TYPES = ["PRESENCE_OR_INTEREST", "PRESENCE"];
|
|
2202
2226
|
var BUDGET_DELIVERY_METHODS = ["STANDARD", "ACCELERATED"];
|
|
@@ -2849,6 +2873,8 @@ var biddingConfigSchema = z4.object({
|
|
|
2849
2873
|
type: z4.enum(BIDDING_STRATEGY_TYPES),
|
|
2850
2874
|
targetCpaMicros: microsSchema.optional(),
|
|
2851
2875
|
targetRoas: z4.number().positive().optional(),
|
|
2876
|
+
/** Demand Gen only — `Campaign.target_cpc.target_cpc_micros`. */
|
|
2877
|
+
targetCpcMicros: microsSchema.optional(),
|
|
2852
2878
|
cpcBidCeilingMicros: microsSchema.optional(),
|
|
2853
2879
|
enhancedCpcEnabled: z4.boolean().optional()
|
|
2854
2880
|
}).superRefine((p, ctx) => {
|
|
@@ -2858,6 +2884,9 @@ var biddingConfigSchema = z4.object({
|
|
|
2858
2884
|
if (p.type === "TARGET_ROAS" && p.targetRoas === void 0) {
|
|
2859
2885
|
ctx.addIssue({ code: "custom", path: ["targetRoas"], message: "TARGET_ROAS needs targetRoas" });
|
|
2860
2886
|
}
|
|
2887
|
+
if (p.type === "TARGET_CPC" && p.targetCpcMicros === void 0) {
|
|
2888
|
+
ctx.addIssue({ code: "custom", path: ["targetCpcMicros"], message: "TARGET_CPC needs targetCpcMicros" });
|
|
2889
|
+
}
|
|
2861
2890
|
});
|
|
2862
2891
|
var networkSettingsSchema = z4.strictObject({
|
|
2863
2892
|
targetGoogleSearch: z4.boolean().optional(),
|
|
@@ -2921,9 +2950,9 @@ var campaignCreateSchema2 = z4.strictObject({
|
|
|
2921
2950
|
if (p.channelType === "PERFORMANCE_MAX" && p.bidding && p.bidding.type === "MANUAL_CPC") {
|
|
2922
2951
|
ctx.addIssue({ code: "custom", path: ["bidding"], message: "Performance Max does not support Manual CPC" });
|
|
2923
2952
|
}
|
|
2924
|
-
const
|
|
2925
|
-
if (
|
|
2926
|
-
ctx.addIssue({ code: "custom", path: ["bidding"], message:
|
|
2953
|
+
const channelRefusal = mergedTargetStrategyRefusal(p.channelType, p.bidding?.type) ?? demandGenOnlyStrategyRefusal(p.channelType, p.bidding?.type);
|
|
2954
|
+
if (channelRefusal) {
|
|
2955
|
+
ctx.addIssue({ code: "custom", path: ["bidding"], message: channelRefusal });
|
|
2927
2956
|
}
|
|
2928
2957
|
if (p.startDate && p.endDate && p.endDate <= p.startDate) {
|
|
2929
2958
|
ctx.addIssue({ code: "custom", path: ["endDate"], message: "endDate must be after startDate" });
|
|
@@ -3259,6 +3288,7 @@ var conversionGoalSetSchema = z4.object({
|
|
|
3259
3288
|
/** True = conversions of this category/origin count toward automated bidding; false = reported only. */
|
|
3260
3289
|
biddable: z4.boolean()
|
|
3261
3290
|
});
|
|
3291
|
+
var PORTFOLIO_TARGET_CPC_MESSAGE = "portfolio strategies cannot be Target CPC \u2014 it is set on the Demand Gen campaign itself";
|
|
3262
3292
|
var biddingStrategyCreateSchema = z4.object({
|
|
3263
3293
|
name: z4.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax),
|
|
3264
3294
|
config: biddingConfigSchema
|
|
@@ -3266,11 +3296,21 @@ var biddingStrategyCreateSchema = z4.object({
|
|
|
3266
3296
|
if (p.config.type === "MANUAL_CPC") {
|
|
3267
3297
|
ctx.addIssue({ code: "custom", path: ["config", "type"], message: "portfolio strategies cannot be Manual CPC" });
|
|
3268
3298
|
}
|
|
3299
|
+
if (p.config.type === "TARGET_CPC") {
|
|
3300
|
+
ctx.addIssue({ code: "custom", path: ["config", "type"], message: PORTFOLIO_TARGET_CPC_MESSAGE });
|
|
3301
|
+
}
|
|
3269
3302
|
});
|
|
3270
3303
|
var biddingStrategyUpdateSchema = z4.object({
|
|
3271
3304
|
name: z4.string().min(1).max(GOOGLE_ADS_LIMITS.biddingStrategy.nameMax).optional(),
|
|
3272
3305
|
config: biddingConfigSchema.optional()
|
|
3273
|
-
}).
|
|
3306
|
+
}).superRefine((p, ctx) => {
|
|
3307
|
+
if (Object.values(p).every((v) => v === void 0)) {
|
|
3308
|
+
ctx.addIssue({ code: "custom", message: "update needs at least one field" });
|
|
3309
|
+
}
|
|
3310
|
+
if (p.config?.type === "TARGET_CPC") {
|
|
3311
|
+
ctx.addIssue({ code: "custom", path: ["config", "type"], message: PORTFOLIO_TARGET_CPC_MESSAGE });
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3274
3314
|
var labelCreateSchema = z4.object({
|
|
3275
3315
|
name: z4.string().min(1).max(GOOGLE_ADS_LIMITS.label.nameMax),
|
|
3276
3316
|
backgroundColor: z4.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
|
@@ -4593,24 +4633,6 @@ var FLOW_SIDE_EFFECT_CATALOG = [
|
|
|
4593
4633
|
gotcha: "`eventTagId` comes from X Events Manager and is passed verbatim. `eventName` is an optional label only.",
|
|
4594
4634
|
reference: TAGS_REF
|
|
4595
4635
|
},
|
|
4596
|
-
{
|
|
4597
|
-
kind: "side-effect",
|
|
4598
|
-
type: "microsoftAds",
|
|
4599
|
-
summary: "Fires a Microsoft Ads (UET) conversion event.",
|
|
4600
|
-
skeleton: {
|
|
4601
|
-
...SIDE_EFFECT_ENVELOPE,
|
|
4602
|
-
type: "microsoftAds",
|
|
4603
|
-
tagIds: TAG_REF_SLOT,
|
|
4604
|
-
eventName: "<UET event action, e.g. lead_submitted>",
|
|
4605
|
-
userMatching: matchingSlots("em", "ph"),
|
|
4606
|
-
value: null,
|
|
4607
|
-
currency: null,
|
|
4608
|
-
transactionId: null
|
|
4609
|
-
},
|
|
4610
|
-
owners: { tagIds: "tags" },
|
|
4611
|
-
gotcha: "`eventName` is the UET *event action*, and it only counts as a conversion once someone creates a matching custom-event conversion goal in Microsoft Advertising \u2014 the tag alone records nothing. `page_view` is refused outright (UET reserves it for SPA navigation), so pick any other action. `eventCategory` and `eventLabel` are optional plain strings (not mapped slots) \u2014 add them only when the goal filters on them. `userMatching` needs Enhanced conversions turned on for that goal, and `ph` must carry a country code (`+34\u2026`/`0034\u2026`) \u2014 map it from a phone field with the country picker, not a plain text field, or it is dropped rather than guessed at.",
|
|
4612
|
-
reference: TAGS_REF
|
|
4613
|
-
},
|
|
4614
4636
|
eventSideEffect("posthog", "Captures a PostHog event.", { eventParams: [] }),
|
|
4615
4637
|
{
|
|
4616
4638
|
kind: "side-effect",
|
|
@@ -6053,8 +6075,7 @@ var TAG_TYPES = [
|
|
|
6053
6075
|
"posthog",
|
|
6054
6076
|
"datafast",
|
|
6055
6077
|
"recaptcha",
|
|
6056
|
-
"twitterAds"
|
|
6057
|
-
"microsoftAds"
|
|
6078
|
+
"twitterAds"
|
|
6058
6079
|
];
|
|
6059
6080
|
var tagTypeSchema = z19.enum(TAG_TYPES);
|
|
6060
6081
|
var TAG_IDENTIFYING_FIELD = {
|
|
@@ -6075,8 +6096,7 @@ var TAG_IDENTIFYING_FIELD = {
|
|
|
6075
6096
|
posthog: "projectToken",
|
|
6076
6097
|
datafast: "websiteId",
|
|
6077
6098
|
recaptcha: "siteKey",
|
|
6078
|
-
twitterAds: "pixelId"
|
|
6079
|
-
microsoftAds: "tagId"
|
|
6099
|
+
twitterAds: "pixelId"
|
|
6080
6100
|
};
|
|
6081
6101
|
var tagDraftOpKindSchema = z19.enum(["create", "update", "delete"]);
|
|
6082
6102
|
var tagDraftOpViewSchema = z19.object({
|
|
@@ -6203,7 +6223,10 @@ var testimonialsListRequestSchema = z20.object({
|
|
|
6203
6223
|
status: testimonialStatusSchema.optional(),
|
|
6204
6224
|
sentiment: testimonialSentimentSchema.optional(),
|
|
6205
6225
|
language: z20.string().min(2).max(5).optional(),
|
|
6206
|
-
limit: z20.coerce.number().int().positive().max(200).optional()
|
|
6226
|
+
limit: z20.coerce.number().int().positive().max(200).optional(),
|
|
6227
|
+
// Opaque, from the previous response's `X-Baker-Next-Cursor`. Absent on the
|
|
6228
|
+
// first page; its absence on a response means the corpus is exhausted.
|
|
6229
|
+
cursor: z20.string().optional()
|
|
6207
6230
|
});
|
|
6208
6231
|
var testimonialsListResponseSchema = z20.array(testimonialDocSchema);
|
|
6209
6232
|
var testimonialsGetRequestSchema = z20.object({ id: z20.string().min(1, "Missing id parameter") });
|
|
@@ -7970,6 +7993,14 @@ var FIELD_DESCRIPTIONS = {
|
|
|
7970
7993
|
"campaign.status": "ENABLED, PAUSED, or REMOVED",
|
|
7971
7994
|
"campaign.advertising_channel_type": "Campaign type: SEARCH, DISPLAY, SHOPPING, VIDEO, PERFORMANCE_MAX, DEMAND_GEN",
|
|
7972
7995
|
"campaign.bidding_strategy_type": "Bidding strategy (TARGET_CPA, TARGET_ROAS, MAXIMIZE_CONVERSIONS, etc.)",
|
|
7996
|
+
"campaign.primary_status": "Whether the campaign can serve: ELIGIBLE, PAUSED, REMOVED, ENDED, PENDING, MISCONFIGURED, LIMITED, LEARNING, NOT_ELIGIBLE. Unlike the keyword equivalent, LIMITED is a real value here \u2014 but Google publishes no reason-to-status mapping, so decide 'Limited by budget' from primary_status_reasons, not from this field.",
|
|
7997
|
+
"campaign.primary_status_reasons": "Why the campaign is limited or not serving (repeated field \u2014 an array). BUDGET_CONSTRAINED is 'Limited by budget'; BUDGET_MISCONFIGURED is a broken budget setting, not a cap. Since 17 Aug 2026 a budget-limited campaign on a target-based strategy delivers toward its target instead of toward its recent actual cost, so BUDGET_CONSTRAINED is the flag that says a target above actual now costs money. BIDDING_STRATEGY_LEARNING means the bidding system is still adjusting to a change.",
|
|
7998
|
+
"campaign.target_cpa.target_cpa_micros": "Target CPA in micros on a standalone Target CPA strategy",
|
|
7999
|
+
"campaign.maximize_conversions.target_cpa_micros": "Target CPA in micros when the target is held inside a Maximize Conversions strategy \u2014 the shape Google uses on Search",
|
|
8000
|
+
"campaign.target_roas.target_roas": "Target ROAS as a ratio (2.5 = 250%) on a standalone Target ROAS strategy",
|
|
8001
|
+
"campaign.maximize_conversion_value.target_roas": "Target ROAS as a ratio when held inside a Maximize Conversion Value strategy",
|
|
8002
|
+
"campaign.target_cpc.target_cpc_micros": "Target CPC in micros on a Demand Gen Target CPC strategy. Governed by the same August 2026 change as Target CPA/ROAS: on a budget-limited campaign it is what delivery moves toward, not a ceiling.",
|
|
8003
|
+
"ad_group.target_cpc_micros": "Ad-group override of the campaign's Demand Gen target CPC, in micros",
|
|
7973
8004
|
"campaign.start_date": "Campaign start date (YYYY-MM-DD)",
|
|
7974
8005
|
"campaign.end_date": "Campaign end date (YYYY-MM-DD), blank if ongoing",
|
|
7975
8006
|
// Campaign budget
|
|
@@ -8022,6 +8053,8 @@ var FIELD_DESCRIPTIONS = {
|
|
|
8022
8053
|
"metrics.average_cpc": "Average cost per click in micros (\xF7 1,000,000)",
|
|
8023
8054
|
"metrics.average_cpm": "Average cost per 1000 impressions in micros (\xF7 1,000,000)",
|
|
8024
8055
|
"metrics.search_impression_share": "Impression share as decimal (0.0\u20131.0)",
|
|
8056
|
+
"metrics.search_budget_lost_impression_share": "Share of impressions lost because the budget ran out, as a decimal (0.0\u20131.0). The corroborating signal for a 'Limited by budget' campaign \u2014 a high value here alongside a target above actual cost per conversion is the combination that now raises spend.",
|
|
8057
|
+
"metrics.cost_per_conversion": "Actual cost per conversion in micros (\xF7 1,000,000) \u2014 compare against the campaign's target",
|
|
8025
8058
|
"metrics.interaction_rate": "Interaction rate as decimal",
|
|
8026
8059
|
"metrics.video_views": "Total video views (integer)",
|
|
8027
8060
|
"metrics.video_view_rate": "Video view rate as decimal",
|
|
@@ -10830,6 +10863,14 @@ var PRESETS = [
|
|
|
10830
10863
|
defaultLimit: 200,
|
|
10831
10864
|
statusFields: ["campaign.status"]
|
|
10832
10865
|
},
|
|
10866
|
+
{
|
|
10867
|
+
name: "budget-limited",
|
|
10868
|
+
description: "Campaigns Google reports as limited by budget, with each one's bid target next to its actual cost per conversion \u2014 the state where a target above actual now drives delivery up to the target instead of holding recent cost",
|
|
10869
|
+
gaqlTemplate: `SELECT campaign.id, campaign.name, campaign.status, campaign.advertising_channel_type, campaign.bidding_strategy_type, campaign.primary_status, campaign.primary_status_reasons, campaign.target_cpa.target_cpa_micros, campaign.maximize_conversions.target_cpa_micros, campaign.target_roas.target_roas, campaign.maximize_conversion_value.target_roas, campaign.target_cpc.target_cpc_micros, campaign_budget.amount_micros, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.cost_per_conversion, metrics.search_budget_lost_impression_share FROM campaign WHERE campaign.primary_status_reasons CONTAINS ANY (BUDGET_CONSTRAINED) AND segments.date DURING {dateRange}{statusScope} ORDER BY metrics.cost_micros DESC LIMIT {limit}`,
|
|
10870
|
+
defaultDateRange: "LAST_30_DAYS",
|
|
10871
|
+
defaultLimit: 200,
|
|
10872
|
+
statusFields: ["campaign.status"]
|
|
10873
|
+
},
|
|
10833
10874
|
{
|
|
10834
10875
|
name: "keyword-analysis",
|
|
10835
10876
|
description: "Keyword performance with match type and quality",
|
|
@@ -13866,15 +13907,27 @@ function biddingFromFlags(args) {
|
|
|
13866
13907
|
"--max-cpc needs --bidding-strategy too (the ceiling belongs to a strategy) \u2014 e.g. --bidding-strategy TARGET_IMPRESSION_SHARE --max-cpc 12"
|
|
13867
13908
|
);
|
|
13868
13909
|
}
|
|
13910
|
+
if (args["target-cpc"] !== void 0) {
|
|
13911
|
+
failWriteValidation(
|
|
13912
|
+
"--target-cpc needs --bidding-strategy too (the target belongs to a strategy) \u2014 use --bidding-strategy TARGET_CPC --target-cpc 1.5"
|
|
13913
|
+
);
|
|
13914
|
+
}
|
|
13869
13915
|
return void 0;
|
|
13870
13916
|
}
|
|
13871
13917
|
if (maxCpcMicros !== void 0 && !MAX_CPC_STRATEGIES.includes(type.toUpperCase())) {
|
|
13872
13918
|
failWriteValidation(`${type} does not take --max-cpc \u2014 only ${MAX_CPC_STRATEGIES.join(", ")} carry a bid ceiling`);
|
|
13873
13919
|
}
|
|
13920
|
+
const targetCpcMicros = microsFlag(args["target-cpc"], "--target-cpc");
|
|
13921
|
+
if (targetCpcMicros !== void 0 && type.toUpperCase() !== "TARGET_CPC") {
|
|
13922
|
+
failWriteValidation(
|
|
13923
|
+
`${type} does not take --target-cpc \u2014 pair it with --bidding-strategy TARGET_CPC (Demand Gen campaigns only)`
|
|
13924
|
+
);
|
|
13925
|
+
}
|
|
13874
13926
|
return {
|
|
13875
13927
|
type,
|
|
13876
13928
|
targetCpaMicros: microsFlag(args["target-cpa"], "--target-cpa"),
|
|
13877
13929
|
targetRoas: args["target-roas"] !== void 0 ? Number(args["target-roas"]) : void 0,
|
|
13930
|
+
targetCpcMicros,
|
|
13878
13931
|
cpcBidCeilingMicros: maxCpcMicros
|
|
13879
13932
|
};
|
|
13880
13933
|
}
|
|
@@ -14059,6 +14112,10 @@ var campaignsCommand = defineCommand30({
|
|
|
14059
14112
|
type: "string",
|
|
14060
14113
|
description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
|
|
14061
14114
|
},
|
|
14115
|
+
"target-cpc": {
|
|
14116
|
+
type: "string",
|
|
14117
|
+
description: "Target CPC (major units) for Demand Gen \u2014 pair it with --bidding-strategy TARGET_CPC. Demand Gen only; other channels have no target CPC."
|
|
14118
|
+
},
|
|
14062
14119
|
...maxCpcArg,
|
|
14063
14120
|
...finalUrlSuffixArg,
|
|
14064
14121
|
...urlOptionsArgs,
|
|
@@ -14117,6 +14174,10 @@ var campaignsCommand = defineCommand30({
|
|
|
14117
14174
|
type: "string",
|
|
14118
14175
|
description: "Target ROAS (e.g. 4.0). On a Search campaign pair it with --bidding-strategy MAXIMIZE_CONVERSION_VALUE \u2014 Google keeps the target inside that strategy and rejects a standalone TARGET_ROAS."
|
|
14119
14176
|
},
|
|
14177
|
+
"target-cpc": {
|
|
14178
|
+
type: "string",
|
|
14179
|
+
description: "Target CPC (major units) for Demand Gen \u2014 pair it with --bidding-strategy TARGET_CPC. Demand Gen only; other channels have no target CPC."
|
|
14180
|
+
},
|
|
14120
14181
|
...maxCpcArg,
|
|
14121
14182
|
...finalUrlSuffixArg,
|
|
14122
14183
|
...urlOptionsArgs,
|
|
@@ -21034,10 +21095,10 @@ function duplicateCommand2(entity, label) {
|
|
|
21034
21095
|
replace: { type: "boolean", description: "Pause the original once the copy publishes" }
|
|
21035
21096
|
},
|
|
21036
21097
|
run: async ({ args }) => {
|
|
21037
|
-
const { apiPost: apiPost2 } = await import("./client-
|
|
21098
|
+
const { apiPost: apiPost2 } = await import("./client-VDCCDEHE.js");
|
|
21038
21099
|
const { requireChatId: requireChatId2 } = await import("./env-FWMZXMQK.js");
|
|
21039
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
21040
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
21100
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-JBYI5IVX.js");
|
|
21101
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-GWFXJXKJ.js");
|
|
21041
21102
|
try {
|
|
21042
21103
|
const accountId = bareAccountId2(args);
|
|
21043
21104
|
const chatId = requireChatId2();
|
|
@@ -34723,10 +34784,6 @@ var MAPPING_SLOTS = {
|
|
|
34723
34784
|
twitterAds: [
|
|
34724
34785
|
{ kind: "record", key: "userMatching", allowed: ["em", "ph"] },
|
|
34725
34786
|
{ kind: "scalar", keys: ["value", "currency"] }
|
|
34726
|
-
],
|
|
34727
|
-
microsoftAds: [
|
|
34728
|
-
{ kind: "record", key: "userMatching", allowed: ["em", "ph"] },
|
|
34729
|
-
{ kind: "scalar", keys: ["value", "currency", "transactionId"] }
|
|
34730
34787
|
]
|
|
34731
34788
|
};
|
|
34732
34789
|
var UNMAPPABLE = {
|
|
@@ -50129,14 +50186,30 @@ function emptyReasonDetail(reason, sourceCount, activeFilters) {
|
|
|
50129
50186
|
}
|
|
50130
50187
|
return `${sourceCount} review source${sourceCount === 1 ? " is" : "s are"} connected but hold no testimonials yet \u2014 the source is there, the rows are not. Ingestion may still be running.`;
|
|
50131
50188
|
}
|
|
50189
|
+
function morePagesHint(resultCount, cursor) {
|
|
50190
|
+
const found = resultCount === 0 ? "This page held no matches" : `This page held ${resultCount}`;
|
|
50191
|
+
return `${found}, and the library continues past it \u2014 so this is one page, not the client's whole set. Do not report it as the corpus and do not conclude anything is missing from it. Read the next page with \`baker testimonials list --cursor ${cursor}\` (same filters), or go straight at the rows you want with \`baker testimonials search <query>\`.`;
|
|
50192
|
+
}
|
|
50193
|
+
function endOfWalkHint(command) {
|
|
50194
|
+
const noun = command === "search" ? "results" : "rows";
|
|
50195
|
+
return `That was the end of the library \u2014 no more ${noun} past the page before this one. Nothing here says the corpus is empty (the earlier pages held it), so answer from what those pages returned rather than re-reading.`;
|
|
50196
|
+
}
|
|
50132
50197
|
async function measureEmptyCorpus({
|
|
50133
50198
|
command,
|
|
50134
50199
|
resultCount,
|
|
50135
|
-
activeFilters
|
|
50200
|
+
activeFilters,
|
|
50201
|
+
nextCursor = null,
|
|
50202
|
+
continued = false
|
|
50136
50203
|
}) {
|
|
50204
|
+
if (nextCursor) {
|
|
50205
|
+
return { hints: [morePagesHint(resultCount, nextCursor)] };
|
|
50206
|
+
}
|
|
50137
50207
|
if (resultCount > 0) {
|
|
50138
50208
|
return { hints: [] };
|
|
50139
50209
|
}
|
|
50210
|
+
if (continued) {
|
|
50211
|
+
return { hints: [endOfWalkHint(command)] };
|
|
50212
|
+
}
|
|
50140
50213
|
if (activeFilters.length > 0) {
|
|
50141
50214
|
const reason2 = "filtered_out";
|
|
50142
50215
|
return {
|
|
@@ -50215,7 +50288,12 @@ registerSchema({
|
|
|
50215
50288
|
},
|
|
50216
50289
|
language: { type: "string", description: "Filter by language code (e.g. en, es)", required: false },
|
|
50217
50290
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
|
|
50218
|
-
limit: { type: "number", description: "Max results (default 50)", required: false, default: 50 }
|
|
50291
|
+
limit: { type: "number", description: "Max results per page (default 50)", required: false, default: 50 },
|
|
50292
|
+
cursor: {
|
|
50293
|
+
type: "string",
|
|
50294
|
+
description: "Read the next page: pass meta.next_cursor from the previous call, with the same filters.",
|
|
50295
|
+
required: false
|
|
50296
|
+
}
|
|
50219
50297
|
}
|
|
50220
50298
|
});
|
|
50221
50299
|
var PARAM_NAMES = {
|
|
@@ -50226,7 +50304,8 @@ var PARAM_NAMES = {
|
|
|
50226
50304
|
sentiment: "sentiment",
|
|
50227
50305
|
language: "language",
|
|
50228
50306
|
tags: "tags",
|
|
50229
|
-
limit: "limit"
|
|
50307
|
+
limit: "limit",
|
|
50308
|
+
cursor: "cursor"
|
|
50230
50309
|
};
|
|
50231
50310
|
function buildListParams(args) {
|
|
50232
50311
|
const params = {};
|
|
@@ -50251,22 +50330,30 @@ var listCommand18 = defineCommand205({
|
|
|
50251
50330
|
sentiment: { type: "string", description: "Filter: positive|neutral|negative", required: false },
|
|
50252
50331
|
language: { type: "string", description: "Filter by language code (e.g. en, es)", required: false },
|
|
50253
50332
|
tags: { type: "string", description: "Comma-separated tags to filter by", required: false },
|
|
50254
|
-
limit: { type: "string", description: "Max results (default 50)", required: false },
|
|
50333
|
+
limit: { type: "string", description: "Max results per page (default 50)", required: false },
|
|
50334
|
+
cursor: { type: "string", description: "meta.next_cursor from the previous page", required: false },
|
|
50255
50335
|
output: { type: "string", description: "Output format: json|files|md", required: false, default: "json" },
|
|
50256
50336
|
fields: { type: "string", description: "Comma-separated field names to include", required: false },
|
|
50257
50337
|
full: { type: "boolean", description: "Include full metadata", required: false, default: false }
|
|
50258
50338
|
},
|
|
50259
50339
|
run: async ({ args }) => {
|
|
50260
50340
|
try {
|
|
50261
|
-
const data = await
|
|
50341
|
+
const { data, headers } = await apiGetWithHeaders(
|
|
50342
|
+
"/api/testimonials",
|
|
50343
|
+
buildListParams(args)
|
|
50344
|
+
);
|
|
50345
|
+
const nextCursor = headers.get("x-baker-next-cursor");
|
|
50262
50346
|
const { hints: hints2, empty } = await measureEmptyCorpus({
|
|
50263
50347
|
command: "list",
|
|
50264
50348
|
resultCount: data.length,
|
|
50265
|
-
activeFilters: activeFilterFlags(args, FILTER_FLAGS)
|
|
50349
|
+
activeFilters: activeFilterFlags(args, FILTER_FLAGS),
|
|
50350
|
+
nextCursor,
|
|
50351
|
+
continued: Boolean(args.cursor)
|
|
50266
50352
|
});
|
|
50267
50353
|
const outputFormat = args.output || "json";
|
|
50354
|
+
const meta = { ...empty ? { empty } : {}, ...nextCursor ? { next_cursor: nextCursor } : {} };
|
|
50268
50355
|
writeOutput(
|
|
50269
|
-
{ ok: true, data, ...
|
|
50356
|
+
{ ok: true, data, ...Object.keys(meta).length > 0 ? { meta } : {}, ...hints2.length > 0 ? { hints: hints2 } : {} },
|
|
50270
50357
|
outputFormat,
|
|
50271
50358
|
args.fields ? args.fields.split(",") : void 0,
|
|
50272
50359
|
args.full,
|