@koda-sl/baker-cli 0.364.0 → 0.365.0-dev.846b2e487
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 +18 -3
- package/dist/{chunk-AI4HWQVN.js → chunk-K2IKLMCM.js} +4 -4
- package/dist/chunk-K2IKLMCM.js.map +1 -0
- package/dist/{chunk-BLMXG42J.js → chunk-SR56QQTD.js} +2 -2
- package/dist/{chunk-A2LM5EDF.js → chunk-VVQLRFCI.js} +2 -2
- package/dist/chunk-VVQLRFCI.js.map +1 -0
- package/dist/cli.js +240 -121
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.js +1 -1
- package/dist/{output-M2DBCZFG.js → output-OSSBUGT7.js} +2 -2
- package/dist/{shared-BROA3UXS.js → shared-MSI4UMZ3.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-A2LM5EDF.js.map +0 -1
- package/dist/chunk-AI4HWQVN.js.map +0 -1
- /package/dist/{chunk-BLMXG42J.js.map → chunk-SR56QQTD.js.map} +0 -0
- /package/dist/{output-M2DBCZFG.js.map → output-OSSBUGT7.js.map} +0 -0
- /package/dist/{shared-BROA3UXS.js.map → shared-MSI4UMZ3.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
validateCanvasDeep,
|
|
70
70
|
withoutCastName,
|
|
71
71
|
ytDlpBlockSignal
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-K2IKLMCM.js";
|
|
73
73
|
import {
|
|
74
74
|
csvOrJson,
|
|
75
75
|
daysAgoIso,
|
|
@@ -78,7 +78,7 @@ import {
|
|
|
78
78
|
resolveAccountIdArg,
|
|
79
79
|
resolveEffectiveStatus,
|
|
80
80
|
todayIso
|
|
81
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-SR56QQTD.js";
|
|
82
82
|
import {
|
|
83
83
|
buildQueryCacheKey,
|
|
84
84
|
cacheGet,
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
writeAdsJson,
|
|
95
95
|
writeAdsOutput,
|
|
96
96
|
writeJsonEnvelope
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-VVQLRFCI.js";
|
|
98
98
|
import {
|
|
99
99
|
ApiError,
|
|
100
100
|
apiGet,
|
|
@@ -4394,7 +4394,7 @@ var CAPABILITY_SURFACE_SPECS = {
|
|
|
4394
4394
|
provider: "google-analytics",
|
|
4395
4395
|
resourceNoun: "property",
|
|
4396
4396
|
// Like Tag Manager: staged during the chat, applied for real at publish,
|
|
4397
|
-
//
|
|
4397
|
+
// with no simulated mode. A connection made before writes existed is
|
|
4398
4398
|
// read-only until it is reconnected, and `access` does NOT say so — it
|
|
4399
4399
|
// reports what the Baker app asks Google for, which is now app-wide
|
|
4400
4400
|
// read-write. The per-company signal is `scopeBlockers`, which compares
|
|
@@ -8538,7 +8538,7 @@ var SURFACE_RULES = [
|
|
|
8538
8538
|
];
|
|
8539
8539
|
var FAN_OUT_NUDGE_THRESHOLD = 2;
|
|
8540
8540
|
var FAN_OUT_THRESHOLD = 3;
|
|
8541
|
-
var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled agent instead \u2014 baker scheduled-agents create --cron "0 9 * * MON" (or --run-at, or --trigger for a reaction rather than a clock; guide: `__tooling__/docs/tools/baker/scheduled-agents.md`). Do
|
|
8541
|
+
var SCHEDULED_HINT = 'This reads as recurring/scheduled work. If it should run on a cadence or a future date, create a Scheduled agent instead \u2014 baker scheduled-agents create --cron "0 9 * * MON" (or --run-at, or --trigger for a reaction rather than a clock; guide: `__tooling__/docs/tools/baker/scheduled-agents.md`). Do NOT capture "set up a schedule" as a Work Action.';
|
|
8542
8542
|
function looksExecutable(name, description) {
|
|
8543
8543
|
const foldedName = fold(name);
|
|
8544
8544
|
const full = `${foldedName}
|
|
@@ -8789,7 +8789,7 @@ var claimCommand = defineCommand({
|
|
|
8789
8789
|
import { defineCommand as defineCommand2 } from "citty";
|
|
8790
8790
|
registerSchema({
|
|
8791
8791
|
command: "actions.complete",
|
|
8792
|
-
description: "Stage completion of an action (it was
|
|
8792
|
+
description: "Stage completion of an action (it was DONE). Accepts a real action ID (must be claimed) or a temp ID from the same draft. The action becomes completed when the chat is published. --note is REQUIRED and must describe what you actually did to resolve it (what changed, where, and the outcome) \u2014 it is the client-facing record surfaced by `baker actions log`. Do NOT use this to drop an action you no longer want \u2014 to remove a staged op use `actions draft remove`, to close an unwanted published action use `actions discard`.",
|
|
8793
8793
|
args: {
|
|
8794
8794
|
id: { type: "string", description: "Action ID or temp ID", required: true },
|
|
8795
8795
|
note: {
|
|
@@ -12529,7 +12529,7 @@ var PLANNER_FIELD_DESCRIPTIONS = {
|
|
|
12529
12529
|
// src/commands/ads/google/keywords/discover.ts
|
|
12530
12530
|
registerSchema({
|
|
12531
12531
|
command: "ads.google.keywords.discover",
|
|
12532
|
-
description: 'Discover keyword ideas from seed keywords or URLs. Returns { source: "keyword_planner_estimate", keywords, total_results, next_page_token? }.
|
|
12532
|
+
description: 'Discover keyword ideas from seed keywords or URLs. Returns { source: "keyword_planner_estimate", keywords, total_results, next_page_token? }. THESE ARE ESTIMATES OF TOTAL MARKET SEARCH VOLUME, NOT THIS ACCOUNT\'S DATA: every row carries a source column and every metric is prefixed planner_ (planner_avg_monthly_searches, planner_competition, ...). Never report a planner_ number as impressions, clicks or any account metric \u2014 read those with `baker ads google query` against the account. IMPORTANT: If --location and --language are omitted, defaults to United States (2840) and English (1000). The response includes a query_context object showing which location/language were used.',
|
|
12533
12533
|
args: {
|
|
12534
12534
|
"customer-id": {
|
|
12535
12535
|
type: "string",
|
|
@@ -12540,12 +12540,12 @@ registerSchema({
|
|
|
12540
12540
|
url: { type: "string", description: "URL to extract keyword ideas from", required: false },
|
|
12541
12541
|
location: {
|
|
12542
12542
|
type: "string",
|
|
12543
|
-
description: "Geo target ID.
|
|
12543
|
+
description: "Geo target ID. DEFAULTS TO 2840 (United States) if omitted \u2014 always set this for non-US markets",
|
|
12544
12544
|
required: false
|
|
12545
12545
|
},
|
|
12546
12546
|
language: {
|
|
12547
12547
|
type: "string",
|
|
12548
|
-
description: "Language ID.
|
|
12548
|
+
description: "Language ID. DEFAULTS TO 1000 (English) if omitted \u2014 always set this for non-English markets",
|
|
12549
12549
|
required: false
|
|
12550
12550
|
},
|
|
12551
12551
|
limit: { type: "string", description: "Max keyword ideas (default: 20, max: 100)", required: false, default: 20 },
|
|
@@ -12706,7 +12706,7 @@ var locationsCommand = defineCommand25({
|
|
|
12706
12706
|
import { defineCommand as defineCommand26 } from "citty";
|
|
12707
12707
|
registerSchema({
|
|
12708
12708
|
command: "ads.google.keywords.metrics",
|
|
12709
|
-
description: 'Get historical market metrics for specific keywords. Returns { source: "keyword_planner_estimate", historical_metrics: [...] }.
|
|
12709
|
+
description: 'Get historical market metrics for specific keywords. Returns { source: "keyword_planner_estimate", historical_metrics: [...] }. THESE ARE KEYWORD PLANNER ESTIMATES OF TOTAL MARKET SEARCH VOLUME, NOT THIS ACCOUNT\'S DATA: every row carries a source column and every metric is prefixed planner_ (planner_avg_monthly_searches, planner_competition, ...). Never report a planner_ number as impressions, clicks or any account metric \u2014 read those with `baker ads google query` against the account. IMPORTANT: If --location and --language are omitted, defaults to United States (2840) and English (1000). The response includes a query_context object showing which location/language were used.',
|
|
12710
12710
|
args: {
|
|
12711
12711
|
"customer-id": {
|
|
12712
12712
|
type: "string",
|
|
@@ -12716,12 +12716,12 @@ registerSchema({
|
|
|
12716
12716
|
keywords: { type: "string", description: "Comma-separated keywords to analyze (max 200)", required: true },
|
|
12717
12717
|
location: {
|
|
12718
12718
|
type: "string",
|
|
12719
|
-
description: "Geo target ID.
|
|
12719
|
+
description: "Geo target ID. DEFAULTS TO 2840 (United States) if omitted \u2014 always set this for non-US markets",
|
|
12720
12720
|
required: false
|
|
12721
12721
|
},
|
|
12722
12722
|
language: {
|
|
12723
12723
|
type: "string",
|
|
12724
|
-
description: "Language ID.
|
|
12724
|
+
description: "Language ID. DEFAULTS TO 1000 (English) if omitted \u2014 always set this for non-English markets",
|
|
12725
12725
|
required: false
|
|
12726
12726
|
},
|
|
12727
12727
|
output: { type: "string", description: "Format: json|csv|jsonl|md", required: false, default: "json" },
|
|
@@ -13635,7 +13635,7 @@ function completenessWarnings(read) {
|
|
|
13635
13635
|
return [
|
|
13636
13636
|
{
|
|
13637
13637
|
code: "TRUNCATED_RESULTS",
|
|
13638
|
-
message: `INCOMPLETE READ \u2014 ${cause} These rows are a page, not the data set.
|
|
13638
|
+
message: `INCOMPLETE READ \u2014 ${cause} These rows are a page, not the data set. Do NOT count, total or describe them as the whole picture. ${fix}`
|
|
13639
13639
|
}
|
|
13640
13640
|
];
|
|
13641
13641
|
}
|
|
@@ -18212,7 +18212,6 @@ var fileArg = {
|
|
|
18212
18212
|
description: "The op payload: a JSON file path, or the JSON object inline (flags override it)"
|
|
18213
18213
|
}
|
|
18214
18214
|
};
|
|
18215
|
-
var STAGED_WRITE = "Stages only \u2014 applies to the live account when the chat is published. Field contract: `baker capabilities google-ads`.";
|
|
18216
18215
|
var BUDGET_DEMAND_HINTS = [
|
|
18217
18216
|
"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.",
|
|
18218
18217
|
"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)."
|
|
@@ -18224,14 +18223,11 @@ var budgetsCommand = defineCommand31({
|
|
|
18224
18223
|
// first. A budget several times what the target pool can absorb never spends, and reads to the
|
|
18225
18224
|
// client as a plan nobody costed.
|
|
18226
18225
|
create: defineCommand31({
|
|
18227
|
-
meta: {
|
|
18228
|
-
name: "create",
|
|
18229
|
-
description: `Stage a daily campaign budget; campaigns reference it by the g_temp_* ref it returns (--budget-ref). ${STAGED_WRITE}`
|
|
18230
|
-
},
|
|
18226
|
+
meta: { name: "create", description: "Stage a campaign budget" },
|
|
18231
18227
|
args: {
|
|
18232
18228
|
...customerIdArg,
|
|
18233
18229
|
...fileArg,
|
|
18234
|
-
name: { type: "string", description:
|
|
18230
|
+
name: { type: "string", description: "Budget name" },
|
|
18235
18231
|
amount: { type: "string", description: "Daily amount in major currency units (e.g. 50)" },
|
|
18236
18232
|
delivery: { type: "string", description: "STANDARD | ACCELERATED" },
|
|
18237
18233
|
shared: { type: "boolean", description: "Explicitly shared budget" }
|
|
@@ -18248,14 +18244,11 @@ var budgetsCommand = defineCommand31({
|
|
|
18248
18244
|
}
|
|
18249
18245
|
}),
|
|
18250
18246
|
update: defineCommand31({
|
|
18251
|
-
meta: {
|
|
18252
|
-
name: "update",
|
|
18253
|
-
description: `Stage a change to an existing budget's name, daily amount or delivery. ${STAGED_WRITE}`
|
|
18254
|
-
},
|
|
18247
|
+
meta: { name: "update", description: "Stage a budget update" },
|
|
18255
18248
|
args: {
|
|
18256
18249
|
...customerIdArg,
|
|
18257
18250
|
...fileArg,
|
|
18258
|
-
name: { type: "string"
|
|
18251
|
+
name: { type: "string" },
|
|
18259
18252
|
amount: { type: "string", description: "New daily amount in major currency units" },
|
|
18260
18253
|
delivery: { type: "string" }
|
|
18261
18254
|
},
|
|
@@ -18464,14 +18457,11 @@ var campaignsCommand = defineCommand31({
|
|
|
18464
18457
|
meta: { name: "campaigns", description: "Stage campaign create/update/pause/resume/remove" },
|
|
18465
18458
|
subCommands: {
|
|
18466
18459
|
create: defineCommand31({
|
|
18467
|
-
meta: {
|
|
18468
|
-
name: "create",
|
|
18469
|
-
description: `Stage a new campaign on a budget (--budget-ref); it is created paused unless --status ENABLED. ${STAGED_WRITE}`
|
|
18470
|
-
},
|
|
18460
|
+
meta: { name: "create", description: "Stage a campaign" },
|
|
18471
18461
|
args: {
|
|
18472
18462
|
...customerIdArg,
|
|
18473
18463
|
...fileArg,
|
|
18474
|
-
name: { type: "string"
|
|
18464
|
+
name: { type: "string" },
|
|
18475
18465
|
"channel-type": {
|
|
18476
18466
|
type: "string",
|
|
18477
18467
|
description: "SEARCH | DISPLAY | SHOPPING | VIDEO | PERFORMANCE_MAX | DEMAND_GEN | \u2026"
|
|
@@ -18534,14 +18524,11 @@ var campaignsCommand = defineCommand31({
|
|
|
18534
18524
|
}
|
|
18535
18525
|
}),
|
|
18536
18526
|
update: defineCommand31({
|
|
18537
|
-
meta: {
|
|
18538
|
-
name: "update",
|
|
18539
|
-
description: `Stage a change to an existing campaign's name, budget, bidding or settings. ${STAGED_WRITE}`
|
|
18540
|
-
},
|
|
18527
|
+
meta: { name: "update", description: "Stage a campaign update" },
|
|
18541
18528
|
args: {
|
|
18542
18529
|
...customerIdArg,
|
|
18543
18530
|
...fileArg,
|
|
18544
|
-
name: { type: "string"
|
|
18531
|
+
name: { type: "string" },
|
|
18545
18532
|
"budget-ref": { type: "string" },
|
|
18546
18533
|
"bidding-strategy": {
|
|
18547
18534
|
type: "string",
|
|
@@ -18621,21 +18608,15 @@ var adGroupsCommand = defineCommand31({
|
|
|
18621
18608
|
meta: { name: "ad-groups", description: "Stage ad group create/update/pause/resume/remove" },
|
|
18622
18609
|
subCommands: {
|
|
18623
18610
|
create: defineCommand31({
|
|
18624
|
-
meta: {
|
|
18625
|
-
name: "create",
|
|
18626
|
-
description: `Stage a new ad group in a campaign (--campaign-ref); it is created paused unless --status ENABLED. ${STAGED_WRITE}`
|
|
18627
|
-
},
|
|
18611
|
+
meta: { name: "create", description: "Stage an ad group" },
|
|
18628
18612
|
args: {
|
|
18629
18613
|
...customerIdArg,
|
|
18630
18614
|
...fileArg,
|
|
18631
|
-
name: {
|
|
18632
|
-
type: "string",
|
|
18633
|
-
description: 'Ad group name, unique within its campaign (e.g. "Solar panels \u2014 price")'
|
|
18634
|
-
},
|
|
18615
|
+
name: { type: "string" },
|
|
18635
18616
|
"campaign-ref": { type: "string", description: "Campaign ref (g_temp_*, id, or resource name)" },
|
|
18636
18617
|
type: {
|
|
18637
18618
|
type: "string",
|
|
18638
|
-
description: "Ad group type \u2014 leave it out and Google derives it from the campaign's channel, which is
|
|
18619
|
+
description: "Ad group type \u2014 leave it out and Google derives it from the campaign's channel, which is REQUIRED on a Demand Gen campaign (there is no Demand Gen ad group type). SEARCH_STANDARD | DISPLAY_STANDARD | SHOPPING_PRODUCT_ADS | VIDEO_*"
|
|
18639
18620
|
},
|
|
18640
18621
|
"cpc-bid": { type: "string", description: "Default CPC bid in major units" },
|
|
18641
18622
|
status: { type: "string" }
|
|
@@ -18653,14 +18634,11 @@ var adGroupsCommand = defineCommand31({
|
|
|
18653
18634
|
}
|
|
18654
18635
|
}),
|
|
18655
18636
|
update: defineCommand31({
|
|
18656
|
-
meta: {
|
|
18657
|
-
name: "update",
|
|
18658
|
-
description: `Stage a change to an existing ad group's name, default CPC bid or status. ${STAGED_WRITE}`
|
|
18659
|
-
},
|
|
18637
|
+
meta: { name: "update", description: "Stage an ad group update" },
|
|
18660
18638
|
args: {
|
|
18661
18639
|
...customerIdArg,
|
|
18662
18640
|
...fileArg,
|
|
18663
|
-
name: { type: "string"
|
|
18641
|
+
name: { type: "string" },
|
|
18664
18642
|
"cpc-bid": { type: "string" },
|
|
18665
18643
|
status: { type: "string" }
|
|
18666
18644
|
},
|
|
@@ -19438,7 +19416,7 @@ var assetsCommand = defineCommand31({
|
|
|
19438
19416
|
),
|
|
19439
19417
|
update: assetsUpdateCommand,
|
|
19440
19418
|
attach: fileCreateCommand("attach", "google.assetLink.attach", "Attach an asset to a campaign/adGroup/customer", [
|
|
19441
|
-
"Image extensions cannot be linked \u2014 Google refuses AD_IMAGE and every marketing-image field type at every level, on every API version \u2014 but stage the link anyway: it is skipped rather than stored, the rest of a batch is unaffected, and it is what records the handover Task naming those images and that campaign. That is the
|
|
19419
|
+
"Image extensions cannot be linked \u2014 Google refuses AD_IMAGE and every marketing-image field type at every level, on every API version \u2014 but stage the link anyway: it is skipped rather than stored, the rest of a batch is unaffected, and it is what records the handover Task naming those images and that campaign. That is the ONLY record when the images are already in the account and you are uploading nothing. Then tell the user they are already in their Google Ads account and only need adding to the campaign under Assets; do not stage a second Task with `baker actions`. Two images that DO link: the advertiser's logo through this command (`BUSINESS_LOGO`, campaign or customer level, on a verified account), and Performance Max asset-group images, which are a different mechanism (`asset-groups attach`)."
|
|
19442
19420
|
]),
|
|
19443
19421
|
detach: statusCommand2("google.assetLink.detach", "asset link", [
|
|
19444
19422
|
"Pass the resource name Google gave you \u2014 `SELECT campaign_asset.resource_name \u2026 FROM campaign_asset WHERE campaign_asset.status != 'REMOVED'` (or `ad_group_asset` / `customer_asset`) \u2014 never one composed out of an owner id and an asset id. A link's own status is not its campaign's and not its ad group's, so an inventory read scoped only to those lists extensions that are already detached, and a link between an asset and an owner that never held it parses fine and exists nowhere. Either way the target is read back before it is staged, and a detach of a link that is not there is refused here rather than at publish."
|
|
@@ -19448,14 +19426,14 @@ var assetsCommand = defineCommand31({
|
|
|
19448
19426
|
var audienceModeArg = {
|
|
19449
19427
|
mode: {
|
|
19450
19428
|
type: "string",
|
|
19451
|
-
description: "observation = the audience only adjusts bids, reach is unchanged | targeting = the ads are shown
|
|
19429
|
+
description: "observation = the audience only adjusts bids, reach is unchanged | targeting = the ads are shown ONLY to people in the audiences attached to this campaign/ad group"
|
|
19452
19430
|
}
|
|
19453
19431
|
};
|
|
19454
19432
|
function requireAudienceMode(value) {
|
|
19455
19433
|
const mode = typeof value === "string" ? value.toLowerCase() : void 0;
|
|
19456
19434
|
if (mode !== "observation" && mode !== "targeting") {
|
|
19457
19435
|
failWriteValidation(
|
|
19458
|
-
`pass --mode observation (the audience adjusts bids and reach is unchanged \u2014 the Google Ads interface's default) or --mode targeting (the ads are shown
|
|
19436
|
+
`pass --mode observation (the audience adjusts bids and reach is unchanged \u2014 the Google Ads interface's default) or --mode targeting (the ads are shown ONLY to people in the audiences on this campaign/ad group). There is no safe default: the API's own default is targeting, which can cut a campaign's reach to the size of the list.`
|
|
19459
19437
|
);
|
|
19460
19438
|
}
|
|
19461
19439
|
return mode === "observation" ? "OBSERVATION" : "TARGETING";
|
|
@@ -23949,7 +23927,7 @@ Performance \u2014 the workhorse (3 axes):
|
|
|
23949
23927
|
baker ads linkedin analytics --list-intents
|
|
23950
23928
|
baker ads linkedin analytics --list-pivots
|
|
23951
23929
|
|
|
23952
|
-
Staged writes (
|
|
23930
|
+
Staged writes (NEVER hit LinkedIn directly \u2014 ops stage on this chat's draft,
|
|
23953
23931
|
show in the dashboard as pending LinkedIn changes, and apply when the chat is
|
|
23954
23932
|
published, at which point they reach the real ad account):
|
|
23955
23933
|
baker ads linkedin account url-params \u2014 UTMs for every ad in the account
|
|
@@ -26381,8 +26359,8 @@ Example: baker ads meta ads retag 120252158038400247 --url-tags "utm_source={{si
|
|
|
26381
26359
|
run: async ({ args: args5 }) => {
|
|
26382
26360
|
const { apiPost: apiPost2 } = await import("./client-3DGBQ2H3.js");
|
|
26383
26361
|
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
26384
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
26385
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
26362
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-OSSBUGT7.js");
|
|
26363
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-MSI4UMZ3.js");
|
|
26386
26364
|
try {
|
|
26387
26365
|
const accountId = bareAccountId2(args5);
|
|
26388
26366
|
const urlTags = typeof args5["url-tags"] === "string" ? args5["url-tags"] : void 0;
|
|
@@ -26529,8 +26507,8 @@ function duplicateCommand2(entity, label) {
|
|
|
26529
26507
|
run: async ({ args: args5 }) => {
|
|
26530
26508
|
const { apiPost: apiPost2 } = await import("./client-3DGBQ2H3.js");
|
|
26531
26509
|
const { requireChatId: requireChatId2 } = await import("./env-BSG7TY5Y.js");
|
|
26532
|
-
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-
|
|
26533
|
-
const { handleMetaError: handleMetaError2 } = await import("./shared-
|
|
26510
|
+
const { writeJsonEnvelope: writeJsonEnvelope2 } = await import("./output-OSSBUGT7.js");
|
|
26511
|
+
const { handleMetaError: handleMetaError2 } = await import("./shared-MSI4UMZ3.js");
|
|
26534
26512
|
try {
|
|
26535
26513
|
const accountId = bareAccountId2(args5);
|
|
26536
26514
|
const chatId = requireChatId2();
|
|
@@ -33047,7 +33025,7 @@ var sourcesResponseSchema = z40.strictObject({
|
|
|
33047
33025
|
import { defineCommand as defineCommand98 } from "citty";
|
|
33048
33026
|
|
|
33049
33027
|
// src/commands/hubspot/refusal.ts
|
|
33050
|
-
var CONTINUE = "
|
|
33028
|
+
var CONTINUE = "Do not abort the wider task: say which part you could not read, skip it, and finish everything else. If the `request_connection` tool is not available to you, point them at the dashboard, Brain -> Integrations -> Tools -> HubSpot.";
|
|
33051
33029
|
function hubspotRefusal(code, message) {
|
|
33052
33030
|
if (code === "NOT_FOUND" && message.includes(HUBSPOT_NOT_CONNECTED_MESSAGE)) {
|
|
33053
33031
|
return {
|
|
@@ -33063,7 +33041,7 @@ function hubspotRefusal(code, message) {
|
|
|
33063
33041
|
code: "HUBSPOT_RECONNECT_REQUIRED",
|
|
33064
33042
|
fix: {
|
|
33065
33043
|
action: "request_connection",
|
|
33066
|
-
explanation: 'AGENT INSTRUCTION: HubSpot
|
|
33044
|
+
explanation: 'AGENT INSTRUCTION: HubSpot IS connected \u2014 do not tell the user it is missing, and do not retry. Its access does not cover this read (it was connected before Baker asked for it, or the access was withdrawn), and only the user can widen it. Call the `request_connection` tool with { platform: "hubspot", reason } naming what you need to read; the card offers them a Reconnect button. Run this command again once they have. ' + CONTINUE
|
|
33067
33045
|
}
|
|
33068
33046
|
};
|
|
33069
33047
|
}
|
|
@@ -34566,8 +34544,8 @@ import { defineCommand as defineCommand105 } from "citty";
|
|
|
34566
34544
|
import { defineCommand as defineCommand100 } from "citty";
|
|
34567
34545
|
|
|
34568
34546
|
// src/commands/avatars/casting.ts
|
|
34569
|
-
var CAST_FLAG_RULE = "Cast with `--avatar <handle>` on `baker studio generate`, `baker studio animate`, `baker canvas scaffold-ad`, `baker canvas scaffold-static-ad` and `baker canvas scaffold-video`.
|
|
34570
|
-
var VERBATIM_RULE = "When you do write the description yourself \u2014 a hand-built canvas node, a landing image, anywhere `--avatar` does not exist \u2014 copy `subjectDescription`
|
|
34547
|
+
var CAST_FLAG_RULE = "Cast with `--avatar <handle>` on `baker studio generate`, `baker studio animate`, `baker canvas scaffold-ad`, `baker canvas scaffold-static-ad` and `baker canvas scaffold-video`. Do NOT hand-roll it by pasting `subjectDescription` into the prompt or by passing the sheet through `--reference` \u2014 both render something that merely resembles them. `--avatar` is also the ONLY thing that carries their ACCENT and delivery into a clip. An avatar has no pinned voice by design (ADR 0005): the video model renders the voice, and the region on their profile is the only thing steering it \u2014 without the flag the clip is performed flat, in whatever accent the model picks on its own. `baker canvas scaffold-video` casts only their FACE: a reproduced video keeps the source ad's accent direction, so never tell the user it speaks in theirs.";
|
|
34548
|
+
var VERBATIM_RULE = "When you do write the description yourself \u2014 a hand-built canvas node, a landing image, anywhere `--avatar` does not exist \u2014 copy `subjectDescription` VERBATIM, word for word. Re-phrasing it per generation is the other reason a face drifts across a set.";
|
|
34571
34549
|
var VIDEO_ROUTING = "In video, a photoreal presenter renders on `google/veo-3.1` (or `google/veo-3.1-fast`), never `bytedance/seedance-2.0` \u2014 it refuses photoreal human faces, AI-generated ones included. Reproducing a video the user gave you: `baker canvas scaffold-video \u2026 --avatar <handle> --real-face`.";
|
|
34572
34550
|
function castingHints(avatar) {
|
|
34573
34551
|
if (avatar.status === "generating" && avatar.needsFace) {
|
|
@@ -34661,7 +34639,7 @@ function creationHints(created, profile) {
|
|
|
34661
34639
|
const casting = created.likeness === "synthetic" && created.faceChoice === "auto" ? [
|
|
34662
34640
|
`No one was asked about this face \u2014 \`--face auto\` cast it and it is kept. Do not tell the user they chose it, and do not offer them a pick you are not going to run. If this should have been theirs, say so and create it again with \`--face ask\`.`
|
|
34663
34641
|
] : created.likeness === "synthetic" ? [
|
|
34664
|
-
`The user is being shown real portraits of @${created.handle} right now, and the turn waits for them to pick \u2014 this happens on its own, triggered by the command you just ran.
|
|
34642
|
+
`The user is being shown real portraits of @${created.handle} right now, and the turn waits for them to pick \u2014 this happens on its own, triggered by the command you just ran. Do NOT call request_avatar_casting, do not ask about the face in a question of your own, and do not wait or poll: whatever you do next simply runs after they have chosen. Say what you created and get on with the work.`
|
|
34665
34643
|
] : [];
|
|
34666
34644
|
const hints2 = [
|
|
34667
34645
|
...casting,
|
|
@@ -34747,7 +34725,7 @@ function avatarFailureFix(err, handle, context = {}) {
|
|
|
34747
34725
|
if (err.code === "CONFLICT" && handle) {
|
|
34748
34726
|
return {
|
|
34749
34727
|
action: "use_the_existing_avatar",
|
|
34750
|
-
explanation: `@${handle} already exists in
|
|
34728
|
+
explanation: `@${handle} already exists in THIS company \u2014 avatars belong to the company, not to a chat, so one made in an earlier conversation is still here and still castable. If it is the person you want, use it as it is: \`--avatar ${handle}\`. Read it first with \`baker avatars get ${handle} --full\` and say who they already are. Do NOT create the same person under a second name: the user would be told about one avatar and handed another, and the company ends up with duplicates nobody chose a face for. If you genuinely need a different person, ask the user what to call them.`
|
|
34751
34729
|
};
|
|
34752
34730
|
}
|
|
34753
34731
|
return void 0;
|
|
@@ -34849,7 +34827,7 @@ registerSchema({
|
|
|
34849
34827
|
},
|
|
34850
34828
|
subject: {
|
|
34851
34829
|
type: "string",
|
|
34852
|
-
description: "The identity, reused
|
|
34830
|
+
description: "The identity, reused VERBATIM in every render: one person, plainly described (age, build, hair, skin, face). No scene, no lighting, no camera, no wardrobe \u2014 those belong in the render prompt or in --wardrobe/--setting.",
|
|
34853
34831
|
required: true
|
|
34854
34832
|
},
|
|
34855
34833
|
persona: { type: "string", description: "Who they are on camera \u2014 role, attitude, energy", required: false },
|
|
@@ -35077,7 +35055,7 @@ async function waitForSheet(first, read, sleep4 = (ms) => new Promise((resolve5)
|
|
|
35077
35055
|
// src/commands/avatars/get.ts
|
|
35078
35056
|
registerSchema({
|
|
35079
35057
|
command: "avatars.get",
|
|
35080
|
-
description: "Read one avatar by handle \u2014 the command you run before casting it. Returns its status (only `ready` can be cast), the identity sheet URL every render must be grounded on via --reference, and the subject description that goes into the prompt
|
|
35058
|
+
description: "Read one avatar by handle \u2014 the command you run before casting it. Returns its status (only `ready` can be cast), the identity sheet URL every render must be grounded on via --reference, and the subject description that goes into the prompt VERBATIM. Add --full for the whole written profile (persona, speech, motion, wardrobe, setting, guardrails, voice, source photos).",
|
|
35081
35059
|
args: {
|
|
35082
35060
|
handle: { type: "string", description: 'Avatar handle, e.g. "marta" (lowercase, dashes)', required: true },
|
|
35083
35061
|
full: {
|
|
@@ -35326,7 +35304,7 @@ How casting works:
|
|
|
35326
35304
|
2. baker avatars get <handle> Status, identity sheet URL, subject description
|
|
35327
35305
|
3. baker images generate "<scene>" --reference <sheetUrl>
|
|
35328
35306
|
Ground the render on the identity sheet, never on a source photo,
|
|
35329
|
-
and copy the subject description into the prompt
|
|
35307
|
+
and copy the subject description into the prompt VERBATIM
|
|
35330
35308
|
|
|
35331
35309
|
Examples:
|
|
35332
35310
|
baker avatars list --status ready --output md
|
|
@@ -36186,7 +36164,7 @@ async function unresolvedAction(css, unresolved) {
|
|
|
36186
36164
|
const { plans } = planFontAdoption(await astroSources(), unresolved);
|
|
36187
36165
|
if (plans.length > 0) {
|
|
36188
36166
|
const names = plans.map((p) => `"${p.family}"`).join(", ");
|
|
36189
|
-
return `Run \`baker brand fonts adopt\` \u2014 ${names} is already self-hosted by ${plans[0]?.files.join(", ")}, so it only needs moving into ${GLOBAL_CSS} to load on every page.
|
|
36167
|
+
return `Run \`baker brand fonts adopt\` \u2014 ${names} is already self-hosted by ${plans[0]?.files.join(", ")}, so it only needs moving into ${GLOBAL_CSS} to load on every page. Do NOT fetch it: that downloads a different build of the same face.`;
|
|
36190
36168
|
}
|
|
36191
36169
|
const viaFontsource = [...fontsourceFamilies(css)];
|
|
36192
36170
|
const stranded = unresolved.filter((family) => viaFontsource.includes(family.toLowerCase()));
|
|
@@ -38652,7 +38630,7 @@ function emitScreenScene(i, scene, lengths, out, surfaceIngests, nodes, clips) {
|
|
|
38652
38630
|
type: "ingest",
|
|
38653
38631
|
params: {
|
|
38654
38632
|
source: "path",
|
|
38655
|
-
path: `[TODO: supply the
|
|
38633
|
+
path: `[TODO: supply the REAL screen for "${label}" \u2014 NEVER AI-generate a UI. Capture a clean, text-free screenshot with \`baker images screenshot https://<brand-domain>/<path>\` (image-library skill); spoken/overlay text rides the overlay layer, not the screenshot. Wire it in as a path relative to this canvas file, never an absolute path]`,
|
|
38656
38634
|
expect: "image"
|
|
38657
38635
|
}
|
|
38658
38636
|
});
|
|
@@ -38704,7 +38682,7 @@ function emitGraphicScene(i, scene, lengths, out, surfaceIngests, nodes, clips)
|
|
|
38704
38682
|
type: "ingest",
|
|
38705
38683
|
params: {
|
|
38706
38684
|
source: "path",
|
|
38707
|
-
path: `[TODO: supply the
|
|
38685
|
+
path: `[TODO: supply the REAL designed graphic for "${label}" \u2014 NEVER AI-generate a designed panel (its typography/wordmarks garble). Rebuild it as brand HTML in video-overlay-composition (the scene's text/logos are already seeded there), export the design from the brand kit, or source stock art; text rides the overlay layer, not this plate. Wire it in as a path relative to this canvas file, never an absolute path]`,
|
|
38708
38686
|
expect: "image"
|
|
38709
38687
|
}
|
|
38710
38688
|
});
|
|
@@ -39787,13 +39765,13 @@ function uiSurfaceStub(blueprint, run, clock) {
|
|
|
39787
39765
|
}) : [];
|
|
39788
39766
|
const isGraphic = scenes.some((s) => (compositeRegionsOf(s) ?? []).some((r) => r.kind === "static_graphic"));
|
|
39789
39767
|
const sourcing = isGraphic ? [
|
|
39790
|
-
" Build it as a
|
|
39768
|
+
" Build it as a REAL designed panel, NEVER AI-generate its text/typography:",
|
|
39791
39769
|
" hand-build a brand-accurate HTML block in this composition (restyle the seeded",
|
|
39792
39770
|
" overlay text, or `npx hyperframes add <block>` for a ready-made card),",
|
|
39793
39771
|
" \u2014 OR drop the design asset (export it from the brand kit) as panel.png here;",
|
|
39794
39772
|
" then nest it as a timed clip:"
|
|
39795
39773
|
] : [
|
|
39796
|
-
" Build it as a
|
|
39774
|
+
" Build it as a REAL surface, NEVER AI: capture the live page \u2014",
|
|
39797
39775
|
" baker images screenshot https://<brand-domain>/<path> (image-library skill)",
|
|
39798
39776
|
" \u2014 OR hand-build a brand-accurate HTML screen; then frame it in a phone mockup:",
|
|
39799
39777
|
" npx hyperframes add phone-scroll (writes compositions/phone-scroll.html)",
|
|
@@ -40195,7 +40173,7 @@ function buildModelConstraints() {
|
|
|
40195
40173
|
};
|
|
40196
40174
|
}
|
|
40197
40175
|
return {
|
|
40198
|
-
note: "Switching a clip's video model has cross-node constraints.
|
|
40176
|
+
note: "Switching a clip's video model has cross-node constraints. ALL video_generate nodes in one canvas MUST share ONE aspect_ratio (the composite silently crops otherwise). Each model's `duration` is a hard enum and differs per model \u2014 a scene longer than the model's max must be split. `person_generation` differs (Veo requires \"allow_all\"). `baker canvas validate` gates duration, aspect agreement, per-model params, AND a scene-span-vs-model-max advisory before any billed run \u2014 run it after every model edit.",
|
|
40199
40177
|
models
|
|
40200
40178
|
};
|
|
40201
40179
|
}
|
|
@@ -40270,7 +40248,7 @@ function buildMotionBoard(blueprint) {
|
|
|
40270
40248
|
});
|
|
40271
40249
|
}
|
|
40272
40250
|
var VIDEO_GUIDE = [
|
|
40273
|
-
"Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video, built like an editing timeline. It is a sequence of clear SHOTS separated at COMPLETE BREAKS (hard cuts): two adjacent presenter shots at a cut are TWO clips, never glued into one take. What stays continuous is the VOICE \u2014 a voiceover narration is ONE read across the b-roll it plays over, and a b-roll CUTAWAY between two on-camera moments leaves the presenter shot continuous with the insert sliced in \u2014 and each person keeps ONE brand voice (all their clips' native audio re-voiced in a single per-speaker pass), so timbre holds across the cuts. A presenter shot is ONE Seedance clip (native lip-sync + audio); a pure-voiceover stretch is one ElevenLabs tts read; a sub-2s flash is a still hold. A single shot too long for one clip splits into takes that share a boundary frame \u2014 the spine drops the duplicated frame (`--seam-dedup head|tail|off`). Every clip gets a CLEAN-PLATE start AND end keyframe (no baked text), RECAST to your dropped reference assets \u2014 Seedance interpolates real in-shot motion between them. Each frame grounds
|
|
40251
|
+
"Scaffolded by `baker canvas scaffold-video` \u2014 a runnable reproduction of your reference video, built like an editing timeline. It is a sequence of clear SHOTS separated at COMPLETE BREAKS (hard cuts): two adjacent presenter shots at a cut are TWO clips, never glued into one take. What stays continuous is the VOICE \u2014 a voiceover narration is ONE read across the b-roll it plays over, and a b-roll CUTAWAY between two on-camera moments leaves the presenter shot continuous with the insert sliced in \u2014 and each person keeps ONE brand voice (all their clips' native audio re-voiced in a single per-speaker pass), so timbre holds across the cuts. A presenter shot is ONE Seedance clip (native lip-sync + audio); a pure-voiceover stretch is one ElevenLabs tts read; a sub-2s flash is a still hold. A single shot too long for one clip splits into takes that share a boundary frame \u2014 the spine drops the duplicated frame (`--seam-dedup head|tail|off`). Every clip gets a CLEAN-PLATE start AND end keyframe (no baked text), RECAST to your dropped reference assets \u2014 Seedance interpolates real in-shot motion between them. Each frame grounds ONLY on its own extracted frame + el_* slots (never another generated frame), so all frames render in PARALLEL (no cross-frame cascade). A SPLIT-SCREEN / PICTURE-IN-PICTURE / KEYED-PRESENTER scene is reproduced as one clip PER REGION, stacked or overlaid (see `metadata.todo.composition`). On-screen text/graphics are a separate HTML overlay layer you paint; audio is the voice + SFX + a ducked music bed, normalized stereo. It is a STARTING POINT, not a locked render: add, delete, reorder, split, merge, or re-time scenes freely \u2014 see `metadata.todo.full_flexibility`.",
|
|
40274
40252
|
"",
|
|
40275
40253
|
"WHAT TO DO NEXT:",
|
|
40276
40254
|
"0. RE-CRAFT THE SCRIPT FIRST (don't clone). This reference already won in-market, but copying a video is much harder than a static: the hook is targeting and may not transfer, and the message must become TRUE for our brand. Work the `metadata.todo.script_recraft` checklist \u2014 for each scene judge its role (hook/body/CTA), decide keep/cut/reorder/replace, and re-author every line for OUR customer's pain + OUR offer. See `references/script-craft.md` (hook/body/CTA framework) and the `meta-ads-playbook` skill. Most of the work lives here.",
|
|
@@ -40311,7 +40289,7 @@ function recraftTextForRole(role, sceneIndex) {
|
|
|
40311
40289
|
return `[RECRAFT HOOK \u2014 the #1 decision, but usually the LIGHTEST move wins: this hook already worked, so default to KEEPING it and building on top (swap only the specifics for ours). REBUILD only when it doesn't transfer \u2014 a claim we lack or a different funnel/awareness stage \u2014 by reaching for its deeper INNER MECHANIC and delivering that truthfully. DIAGNOSE (device + mechanic + stage) \u2192 DECIDE keep/adapt/rebuild \u2192 hold the opener to criteria: ${HOOK_OPENER_CRITERIA}. It lives on s${sceneIndex}_start (first frame) + the scene-${sceneIndex} overlay. See references/hook-craft.md + meta-ads-playbook \xA710/\xA739.]`;
|
|
40312
40290
|
}
|
|
40313
40291
|
if (role === "cta") {
|
|
40314
|
-
return "[RECRAFT CTA \u2014 the close: state what to do (where to go) + what to expect (offer/bundle/discount) for OUR brand; soft for top-of-funnel, hard for bottom.
|
|
40292
|
+
return "[RECRAFT CTA \u2014 the close: state what to do (where to go) + what to expect (offer/bundle/discount) for OUR brand; soft for top-of-funnel, hard for bottom. Do NOT render the reference's words. See references/script-craft.md.]";
|
|
40315
40293
|
}
|
|
40316
40294
|
return `[RECRAFT: rewrite this ${role} for OUR brand \u2014 true claims only; do NOT render the reference's words. See references/script-craft.md + meta-ads-playbook.]`;
|
|
40317
40295
|
}
|
|
@@ -40394,7 +40372,7 @@ function buildVideoTodo(report, overlayCount, floatingCount, opts, blueprint) {
|
|
|
40394
40372
|
// ALWAYS-ON safety net: the scaffold can only seed what the deconstruct
|
|
40395
40373
|
// cataloged, and it under-detects on-image graphics + sound cues. Never trust
|
|
40396
40374
|
// "none detected" — re-watch the reference and fill the gaps with the right tool.
|
|
40397
|
-
completeness_check: 'The scaffold mirrors the deconstruct\'s catalog, which UNDER-DETECTS \u2014 never trust a 0 count. Re-watch the reference frame-by-frame and add anything missing: (1) ON-IMAGE GRAPHICS not in floating_elements (dollar/coin icons, emojis, checkmarks, rating stars, price tags, arrows, progress bars, app UI) \u2192 source each with `baker images icon "<desc>"` / `baker images sticker` / `baker images gif` / `baker images logo <domain>` and add it as an <img class="ov pos-* " data-start data-dur> in video-overlay-composition/index.html (
|
|
40375
|
+
completeness_check: 'The scaffold mirrors the deconstruct\'s catalog, which UNDER-DETECTS \u2014 never trust a 0 count. Re-watch the reference frame-by-frame and add anything missing: (1) ON-IMAGE GRAPHICS not in floating_elements (dollar/coin icons, emojis, checkmarks, rating stars, price tags, arrows, progress bars, app UI) \u2192 source each with `baker images icon "<desc>"` / `baker images sticker` / `baker images gif` / `baker images logo <domain>` and add it as an <img class="ov pos-* " data-start data-dur> in video-overlay-composition/index.html (NEVER bake graphics into the frame plates). (2) SOUND CUES not in sound_effects (cha-ching/coin, whoosh, ding, pop, notification, keyboard) \u2192 add a `sound_effect` node (eleven_text_to_sound) and wire it onto `audio_mix` at its timestamp. (3) RECURRING people/animals/products/logos/sets with no el_* slot \u2192 add an `ingest` [TODO] slot and reference it from the frames they appear in. (4) Burned-in captions/text not in text_overlays \u2192 add an <img>-free <div class="ov"> in index.html. (5) ONE person playing MULTIPLE personas/wardrobes (skeptic vs believer, before vs after, two outfits) collapsed into a single el_* slot \u2192 split into one el_* slot PER look, each linked as the SAME individual via `same_as` so every outfit has its own reference image but the face/identity stays identical.',
|
|
40398
40376
|
scenes_clamped_to_15s: report.clamped_scenes,
|
|
40399
40377
|
oversize_scenes: report.oversize_scenes.length > 0 ? {
|
|
40400
40378
|
scenes: report.oversize_scenes,
|
|
@@ -42085,9 +42063,9 @@ ${describeRewrites(healed.rewrites)}
|
|
|
42085
42063
|
}) : null;
|
|
42086
42064
|
const hints2 = reviewable ? review === null ? [RENDER_REVIEW_UNAVAILABLE] : review.length === 0 ? ["Reviewed the finished video frame by frame \u2014 nothing to report."] : [
|
|
42087
42065
|
`Reviewed the finished video and found ${review.length} thing${review.length === 1 ? "" : "s"} to fix. Show the user what is wrong before you show them the ad.`,
|
|
42088
|
-
...review.map((f) => `${f.severity === "blocking" ? "
|
|
42066
|
+
...review.map((f) => `${f.severity === "blocking" ? "MUST FIX" : "SHOULD FIX"} \u2014 ${f.what}`),
|
|
42089
42067
|
...review.some((f) => f.scene) ? [
|
|
42090
|
-
`Anything about the
|
|
42068
|
+
`Anything about the MARK or the closing card is a composition fix, not a re-render: both are drawn as HTML by the caption composition, so editing it and re-running re-renders only that pass \u2014 the clips are cached and no generation is paid for again. Fix them, then re-run: edit the \`show\` of the scene files named above \u2014 a shot with a person handling equipment is where limbs and objects go wrong, so widen it, move the hands out of frame, or drop the person from that beat. Re-running re-renders ONLY the scenes you edited; everything else comes from the cache (this run: ${result.stats?.cached_nodes ?? 0} of ${result.stats?.total_nodes ?? 0} nodes were cached).`
|
|
42091
42069
|
] : []
|
|
42092
42070
|
] : [];
|
|
42093
42071
|
if (poster) {
|
|
@@ -42991,12 +42969,12 @@ function fail3(code, message) {
|
|
|
42991
42969
|
process.exit(2);
|
|
42992
42970
|
}
|
|
42993
42971
|
function resolveModels(args5) {
|
|
42994
|
-
const
|
|
42972
|
+
const pick2 = (flag, kind, fallback) => args5[flag] ? String(args5[flag]) : resolveModel(kind, fallback);
|
|
42995
42973
|
return {
|
|
42996
|
-
describeModel:
|
|
42997
|
-
selectModel:
|
|
42998
|
-
layoutModel:
|
|
42999
|
-
genModel:
|
|
42974
|
+
describeModel: pick2("describe-model", "image_describe", "~google/gemini-pro-latest"),
|
|
42975
|
+
selectModel: pick2("select-model", "text_generate", "~google/gemini-flash-latest"),
|
|
42976
|
+
layoutModel: pick2("layout-model", "text_generate", "~google/gemini-flash-latest"),
|
|
42977
|
+
genModel: pick2("gen-model", "image_generate", "openai/gpt-image-2")
|
|
43000
42978
|
};
|
|
43001
42979
|
}
|
|
43002
42980
|
var PLATFORM_PLACEMENTS = {
|
|
@@ -44197,7 +44175,7 @@ registerSchema({
|
|
|
44197
44175
|
spec: {
|
|
44198
44176
|
type: "string",
|
|
44199
44177
|
required: true,
|
|
44200
|
-
description: 'Path to the ad spec JSON. Shape: { format?, market?, brand?, cast?, end_card?, voice?, music?, beats: [{ say, show, on_camera?, cast? }] }. Fill `brand` from src/brand/BRAND.md \u2014 `palette` (its hex tokens, most important first) and `logo` (the repo path to the mark) are what make the ad look like the client rather than like stock. `market` is where the ad is
|
|
44178
|
+
description: 'Path to the ad spec JSON. Shape: { format?, market?, brand?, cast?, end_card?, voice?, music?, beats: [{ say, show, on_camera?, cast? }] }. Fill `brand` from src/brand/BRAND.md \u2014 `palette` (its hex tokens, most important first) and `logo` (the repo path to the mark) are what make the ad look like the client rather than like stock. `market` is where the ad is SET; omitted, it is inferred from the voice language, and getting it wrong is what fills a Spanish ad with British houses. `say` is ONE clause ending in its own punctuation \u2014 it becomes a caption card verbatim, so two sentences in one beat produce a card holding both. `show` is the shot brief for that line. Set `on_camera` ONLY when that beat\'s subject talks to camera. A beat\'s `sound` is one sound EFFECT for that shot \u2014 and MOST BEATS SHOULD NOT HAVE ONE. Add it only where the picture shows a specific physical event the viewer expects to hear (a drill tightening a bolt, a door closing, rain on glass, a graphic snapping in). On a talking head or a calm lifestyle shot it reads as a stock sting, and one per beat turns a quiet ad into a noisy one. A music bed plus two well-placed effects beats eight. `voiceover: false` makes it a MUSIC-LED ad: nobody speaks, the `say` lines become on-screen text captioned straight from the script, and it needs `music`. `cast` is WHO the ad is about, and an ad with people in it needs one: `{ "avatar": "marta" }` names a cast avatar (`baker avatars list`) and every beat is grounded on that avatar\'s identity sheet with its subject description copied verbatim \u2014 the same face here as in the rest of the company\'s work. Without it each beat invents its own stranger, which is how one 28-second ad came back with five different men playing one customer. `{ "description": "..." }` is the fallback when there is no avatar, and it scaffolds a canvas that asks you to drop a photo before it can run \u2014 so prefer the avatar. A beat sets `cast: false` for a shot they are not in. `end_card` is on by default whenever `brand` is set: the last beat becomes a flat brand plate with the mark and a call to action drawn over it. `{ "cta": "..." }` sets the button copy \u2014 there is no default, so with no `cta` the card carries the mark alone, and a `cta` repeating the closing line is dropped rather than printed twice. `false` keeps the footage.'
|
|
44201
44179
|
},
|
|
44202
44180
|
avatar: {
|
|
44203
44181
|
type: "string",
|
|
@@ -44236,7 +44214,7 @@ function avatarVoiceBrief(avatar) {
|
|
|
44236
44214
|
var scaffoldAdCommand = defineCommand115({
|
|
44237
44215
|
meta: {
|
|
44238
44216
|
name: "scaffold-ad",
|
|
44239
|
-
description: 'Build a captioned vertical ad from a written spec, with no reference video to copy.\n\nA spec is a list of BEATS. One beat = one line the voice says + one shot on screen while it is said. That pairing is what keeps the ad honest: the caption card IS the line, so it can never end mid-sentence, and the picture can never drift from the read.\n\nTwo rules the spec enforces for you, both learned the expensive way:\n \u2022 `say` is
|
|
44217
|
+
description: 'Build a captioned vertical ad from a written spec, with no reference video to copy.\n\nA spec is a list of BEATS. One beat = one line the voice says + one shot on screen while it is said. That pairing is what keeps the ad honest: the caption card IS the line, so it can never end mid-sentence, and the picture can never drift from the read.\n\nTwo rules the spec enforces for you, both learned the expensive way:\n \u2022 `say` is ONE clause with its own punctuation. Two sentences in a beat is rejected.\n \u2022 Nobody in shot is speaking unless you set `on_camera` on that beat. A person visibly talking under a voice that is not theirs reads as bad dubbing.\n\nExample spec:\n { "beats": [\n { "say": "Mientras t\xFA sigues pagando la luz a precio de oro,", "show": "Aerial over Spanish rooftops at golden hour." },\n { "say": "Haz clic en el enlace y reserva tu estudio gratuito.", "show": "A couple walking home at sunset." } ] }\n\nWho is in it:\n `cast: { avatar: "marta" }` grounds every beat on that avatar\'s face \u2014 cast one with `baker avatars`.\n Without a cast, each beat invents its own person and the ad reads as a stock-footage collage.\n\nHow it ends: with a `brand`, the last beat closes on a flat brand plate carrying the mark and the CTA.\n\nThen: baker canvas run <the canvas it writes>'
|
|
44240
44218
|
},
|
|
44241
44219
|
args: {
|
|
44242
44220
|
spec: { type: "string", required: true, description: "Path to the ad spec JSON" },
|
|
@@ -44422,7 +44400,7 @@ var scaffoldAdCommand = defineCommand115({
|
|
|
44422
44400
|
message: resolvedLogo ? `The brand mark at \`${resolvedLogo}\` does not exist, so this ad has no logo to put on screen.` : `This company has no brand mark in \`${BRAND_DIR}/logos/\`, so an ad cannot carry its logo.`,
|
|
44423
44401
|
// What the lookup against their own domain actually found, so the agent does not
|
|
44424
44402
|
// go looking for the mark somewhere else — which is the loop this replaced.
|
|
44425
|
-
fix: `${fetched?.reason ?? ""}
|
|
44403
|
+
fix: `${fetched?.reason ?? ""} Do NOT draw one and do NOT take one from an icon library: the mark is drawn on every frame and on the closing card, so a stand-in ships an ad wearing the wrong brand.`
|
|
44426
44404
|
}
|
|
44427
44405
|
});
|
|
44428
44406
|
process.exit(1);
|
|
@@ -44603,7 +44581,7 @@ var scaffoldAdCommand = defineCommand115({
|
|
|
44603
44581
|
// getting it wrong is expensive: the pictures cost a fraction of the clips, and
|
|
44604
44582
|
// every defect anyone has caught in a finished ad was visible in them. The run
|
|
44605
44583
|
// stops, the frames are shown, and continuing pays for none of them twice.
|
|
44606
|
-
handle || staged ? `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --until frames --brief "<what they asked for, their words>"\`,
|
|
44584
|
+
handle || staged ? `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --until frames --brief "<what they asked for, their words>"\`, SHOW the frames it returns and ask whether to carry on \u2014 then continue with the \`--run-id\` it gives you. This ad carries ${handle ? "a person's face" : "the client's mark"}, and a wrong look costs a whole ad to discover after the clips.` : `Wrote ${spec.data.beats.length} beats to ${outPath}. Run it with \`baker canvas run ${outPath} --brief "<what they asked for, their words>"\`.`,
|
|
44607
44585
|
...autoCast ? [
|
|
44608
44586
|
`Cast \`${autoCast}\` \u2014 the spec described this person in words, and they are one of this company's avatars. Every shot now grounds on their identity sheet and carries their accent and delivery. Write \`"cast": { "avatar": "` + autoCast + '" }` yourself next time; a written description is for someone the company does not have.'
|
|
44609
44587
|
] : [],
|
|
@@ -44645,7 +44623,7 @@ var scaffoldAdCommand = defineCommand115({
|
|
|
44645
44623
|
// ad with no palette and no mark anywhere in 28 seconds.
|
|
44646
44624
|
`This spec has no ${!spec.data.brand?.palette?.length ? "`brand.palette`" : ""}${!spec.data.brand?.palette?.length && !spec.data.brand?.logo ? " and no " : ""}${!spec.data.brand?.logo ? "`brand.logo`" : ""}. Read src/brand/BRAND.md and re-run with them \u2014 a name alone leaves the ad in someone else's clothes.`
|
|
44647
44625
|
],
|
|
44648
|
-
"`brand.palette` colours the closing plate the card sits on; `brand.logo` is the repo path to the mark, and it is
|
|
44626
|
+
"`brand.palette` colours the closing plate the card sits on; `brand.logo` is the repo path to the mark, and it is DRAWN by the composition \u2014 top-left throughout and large on the closing card \u2014 never generated as a picture, so it cannot come back garbled. Give it whenever the client has one."
|
|
44649
44627
|
]
|
|
44650
44628
|
});
|
|
44651
44629
|
}
|
|
@@ -44653,7 +44631,7 @@ var scaffoldAdCommand = defineCommand115({
|
|
|
44653
44631
|
function shortBeatHint(short, total) {
|
|
44654
44632
|
if (short.length === 0) return [];
|
|
44655
44633
|
return [
|
|
44656
|
-
`Beat${short.length === 1 ? "" : "s"} ${short.join(", ")} of ${total} ${short.length === 1 ? "is" : "are"} a fragment rather than a line \u2014 a few words with a whole shot to themselves. Every beat's picture holds for at least ${SPOKEN_CLIP_DURATION_S}s, so a two-word beat buys six seconds of somebody standing still, and a run of those plays as a slideshow rather than an ad.
|
|
44634
|
+
`Beat${short.length === 1 ? "" : "s"} ${short.join(", ")} of ${total} ${short.length === 1 ? "is" : "are"} a fragment rather than a line \u2014 a few words with a whole shot to themselves. Every beat's picture holds for at least ${SPOKEN_CLIP_DURATION_S}s, so a two-word beat buys six seconds of somebody standing still, and a run of those plays as a slideshow rather than an ad. Do NOT pad them. Merge them into the beat before or after, so each one carries a whole thought. Edit the spec and re-run this command.`
|
|
44657
44635
|
];
|
|
44658
44636
|
}
|
|
44659
44637
|
async function fetchClientLogo() {
|
|
@@ -45030,18 +45008,18 @@ function parseBudget(raw) {
|
|
|
45030
45008
|
throw new Error(`--budget must be one of ${VIDEO_BUDGETS.join(", ")} (got "${v}")`);
|
|
45031
45009
|
}
|
|
45032
45010
|
function resolveModels2(args5) {
|
|
45033
|
-
const
|
|
45011
|
+
const pick2 = (flag, kind, fallback) => args5[flag] ? String(args5[flag]) : resolveModel2(kind, fallback);
|
|
45034
45012
|
return {
|
|
45035
45013
|
// Flash by default: ~2-3× faster per call than Pro, which keeps each
|
|
45036
45014
|
// per-scene deconstruct step well inside the action time budget and turns a
|
|
45037
45015
|
// ~9 min run into ~3-4 min. Override with --deconstruct-model
|
|
45038
45016
|
// ~google/gemini-pro-latest for the densest extraction when speed matters less.
|
|
45039
|
-
deconstructModel:
|
|
45040
|
-
selectModel:
|
|
45017
|
+
deconstructModel: pick2("deconstruct-model", "video_deconstruct", "~google/gemini-flash-latest"),
|
|
45018
|
+
selectModel: pick2("select-model", "text_generate", "~google/gemini-flash-latest"),
|
|
45041
45019
|
// Default to the strongest image model (matches the static-ad scaffold); the
|
|
45042
45020
|
// frame generators need the most faithful text/identity reproduction. Override
|
|
45043
45021
|
// with --image-model for a cheaper/faster pass.
|
|
45044
|
-
imageModel:
|
|
45022
|
+
imageModel: pick2("image-model", "image_generate", "openai/gpt-image-2.5-sunburst")
|
|
45045
45023
|
};
|
|
45046
45024
|
}
|
|
45047
45025
|
function hasPhotorealCast(elements) {
|
|
@@ -45673,7 +45651,7 @@ function capabilityHints(surfaces, tools = []) {
|
|
|
45673
45651
|
const immediate = surfaces.filter((surface) => surface.writeMode === "applies-at-publish");
|
|
45674
45652
|
if (immediate.length > 0) {
|
|
45675
45653
|
hints2.push(
|
|
45676
|
-
`REAL AND IMMEDIATE: ${immediate.map((s) => s.label).join(", ")}. Completing this chat changes the user's live setup, so agree the changes before you finish, not after.`
|
|
45654
|
+
`REAL AND IMMEDIATE: ${immediate.map((s) => s.label).join(", ")} \u2014 no simulated mode. Completing this chat changes the user's live setup, so agree the changes before you finish, not after.`
|
|
45677
45655
|
);
|
|
45678
45656
|
}
|
|
45679
45657
|
const blocked = surfaces.filter((surface) => surface.blockers.length > 0);
|
|
@@ -50795,12 +50773,12 @@ function handleError5(err) {
|
|
|
50795
50773
|
...readOnly ? {
|
|
50796
50774
|
fix: {
|
|
50797
50775
|
action: "reconnect",
|
|
50798
|
-
explanation: "The Google Analytics connection is read-only. Ask the user to reconnect Google Analytics \u2014 in the dashboard, Brain \u2192 Integrations \u2192 Tools \u2192 Google Analytics \u2014 which grants the permission to change the property. Reporting (`baker ga4 query`, `baker ga4 audit`) keeps working meanwhile, so carry on with the rest of the job and tell them what you will set up once they have reconnected.
|
|
50776
|
+
explanation: "The Google Analytics connection is read-only. Ask the user to reconnect Google Analytics \u2014 in the dashboard, Brain \u2192 Integrations \u2192 Tools \u2192 Google Analytics \u2014 which grants the permission to change the property. Reporting (`baker ga4 query`, `baker ga4 audit`) keeps working meanwhile, so carry on with the rest of the job and tell them what you will set up once they have reconnected. Do NOT claim anything was configured."
|
|
50799
50777
|
}
|
|
50800
50778
|
} : err.code === "UNAUTHORIZED" || err.code === "FORBIDDEN" && isNotConnectedError(err.code, err.message) ? {
|
|
50801
50779
|
fix: {
|
|
50802
50780
|
action: "request_connection",
|
|
50803
|
-
explanation: 'Google Analytics is not connected for this company yet, or no property has been picked.
|
|
50781
|
+
explanation: 'Google Analytics is not connected for this company yet, or no property has been picked. Do NOT send the user to Settings \u2014 call the `request_connection` tool with { platform: "google-analytics", reason } and they connect and pick their property from inside the chat. If `request_connection` is not available to you, say so plainly and point them at dashboard \u2192 Brain \u2192 Integrations \u2192 Tools \u2192 Google Analytics. Carry on with the rest of the task meanwhile.'
|
|
50804
50782
|
}
|
|
50805
50783
|
} : {},
|
|
50806
50784
|
retryable: RETRYABLE_CODES.has(err.code)
|
|
@@ -51673,7 +51651,7 @@ Examples:
|
|
|
51673
51651
|
});
|
|
51674
51652
|
registerSchema({
|
|
51675
51653
|
command: "ga4.audience",
|
|
51676
|
-
description: "Create an audience (a saved group of users, e.g. everyone who started a demo but never finished), rename one, or archive one. Read `baker ga4 config` first to see the audiences that already exist.
|
|
51654
|
+
description: "Create an audience (a saved group of users, e.g. everyone who started a demo but never finished), rename one, or archive one. Read `baker ga4 config` first to see the audiences that already exist. IMPORTANT: everything that defines who is in an audience \u2014 the conditions, the membership window \u2014 is frozen the moment it is created and cannot be edited afterwards, so get it right in one go. An audience only reaches Google Ads for retargeting if the property is linked to a Google Ads account.",
|
|
51677
51655
|
args: {
|
|
51678
51656
|
json: { type: "string", description: createJsonDescription("audience"), required: false },
|
|
51679
51657
|
file: { type: "string", description: "Path to a JSON file with the definition", required: false },
|
|
@@ -58474,7 +58452,7 @@ var addCommand = defineCommand172({
|
|
|
58474
58452
|
"This page was already in the library, so nothing was re-studied \u2014 its sections are searchable now.",
|
|
58475
58453
|
`Search it with: baker landing inspiration search --domain ${new URL(data.canonicalUrl).hostname}`
|
|
58476
58454
|
] : [
|
|
58477
|
-
"Studying this page takes a few minutes.
|
|
58455
|
+
"Studying this page takes a few minutes. Do NOT wait on it \u2014 carry on, and search for it later in the turn or in a later one.",
|
|
58478
58456
|
"Meanwhile, search what is already in the library: baker landing inspiration search '<what you want to see>' --scope all",
|
|
58479
58457
|
"This is saved and being studied now, so it is listed as already applied \u2014 tell the user it is kept whatever they do with this chat.",
|
|
58480
58458
|
// The save is not a promise that the study succeeded. An agent that
|
|
@@ -61820,7 +61798,7 @@ var variantCommand = defineCommand181({
|
|
|
61820
61798
|
change: String(args5.change)
|
|
61821
61799
|
});
|
|
61822
61800
|
const hints2 = [
|
|
61823
|
-
forks.length === 0 ? `MISSING --fork: ${variantSlug2} currently renders exactly ${controlSlug}, so a test between them cannot conclude anything. Re-run with --fork <Component>, or copy the one section your change touches into src/pages/${variantSlug2}/_components/ and repoint that import.` : `Edit
|
|
61801
|
+
forks.length === 0 ? `MISSING --fork: ${variantSlug2} currently renders exactly ${controlSlug}, so a test between them cannot conclude anything. Re-run with --fork <Component>, or copy the one section your change touches into src/pages/${variantSlug2}/_components/ and repoint that import.` : `Edit ONLY src/pages/${variantSlug2}/_components/${forks.join(", ")} \u2014 every other section is shared with ${controlSlug}, and editing a shared one changes both arms at once.`,
|
|
61824
61802
|
`Preview both at http://localhost:4321/${controlSlug}/ and http://localhost:4321/${variantSlug2}/ and check they differ only where you meant. Once it is live, only the first of those two answers \u2014 a variant has no public address of its own, and \`/${controlSlug}/?_bkr_preview=b\` is how anyone looks at it.`,
|
|
61825
61803
|
`Then: baker experiment start --landing ${controlSlug} --variant ${variantSlug2} --because "${String(args5.because)}" --change "${String(args5.change)}". Nothing is split until the session is published \u2014 the person publishing is the review of this page.`
|
|
61826
61804
|
];
|
|
@@ -62137,7 +62115,7 @@ import { defineCommand as defineCommand196 } from "citty";
|
|
|
62137
62115
|
import { defineCommand as defineCommand184 } from "citty";
|
|
62138
62116
|
|
|
62139
62117
|
// src/commands/research/hints.ts
|
|
62140
|
-
var AD_COPY_ROUTE = 'This returns competing
|
|
62118
|
+
var AD_COPY_ROUTE = 'This returns competing DOMAINS and their SERP economics \u2014 no ad copy, no headlines, no creative. For the actual copy of a competitor\'s ads: `baker winning-ads advertisers "<brand>"` \u2192 `baker winning-ads search "<brief>" --advertiser-id <id>` \u2192 `baker winning-ads content <adId>` (`primary_text` / `headline` / `cta`, plus the spoken transcript and on-screen text for video). That corpus is Meta and LinkedIn only \u2014 Google SERP ad copy is not available through any Baker command, so do not keep querying for it here.';
|
|
62141
62119
|
function noSerpRowsHints(rowCount2, queryContext) {
|
|
62142
62120
|
if (rowCount2 > 0) {
|
|
62143
62121
|
return [];
|
|
@@ -62308,7 +62286,7 @@ function handleResearchError(err, fixFor) {
|
|
|
62308
62286
|
// src/commands/research/advertisers.ts
|
|
62309
62287
|
registerSchema({
|
|
62310
62288
|
command: "research.advertisers",
|
|
62311
|
-
description: 'Returns a ranked list of
|
|
62289
|
+
description: 'Returns a ranked list of DOMAINS competing for one keyword in Google, with avg SERP position, relevance rating, estimated traffic value and visibility \u2014 domain economics, NOT ad copy. Despite the name it returns no headlines, descriptions or creative of any kind; for a competitor\'s actual ad copy use `baker winning-ads content <adId>` (Meta/LinkedIn only \u2014 Google SERP ad copy is not available anywhere in Baker). Start here: `baker research advertisers "<keyword>" --location <cc> --language <code>` to size up who owns a term. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.',
|
|
62312
62290
|
args: {
|
|
62313
62291
|
keyword: {
|
|
62314
62292
|
type: "string",
|
|
@@ -62317,12 +62295,12 @@ registerSchema({
|
|
|
62317
62295
|
},
|
|
62318
62296
|
location: {
|
|
62319
62297
|
type: "string",
|
|
62320
|
-
description: "Country code (us, uk, es, de...) or DataForSEO location code.
|
|
62298
|
+
description: "Country code (us, uk, es, de...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted \u2014 always set this for non-US markets",
|
|
62321
62299
|
required: false
|
|
62322
62300
|
},
|
|
62323
62301
|
language: {
|
|
62324
62302
|
type: "string",
|
|
62325
|
-
description: "Language code or name (en, es, spanish...).
|
|
62303
|
+
description: "Language code or name (en, es, spanish...). DEFAULTS TO en (English) if omitted \u2014 always set this for non-English markets",
|
|
62326
62304
|
required: false
|
|
62327
62305
|
},
|
|
62328
62306
|
limit: { type: "string", description: "Max results (default: 20, max: 100)", required: false },
|
|
@@ -62395,17 +62373,17 @@ Examples:
|
|
|
62395
62373
|
import { defineCommand as defineCommand185 } from "citty";
|
|
62396
62374
|
registerSchema({
|
|
62397
62375
|
command: "research.autocomplete",
|
|
62398
|
-
description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
62376
|
+
description: "Get Google Autocomplete suggestions for a seed keyword. Useful for keyword expansion and discovering what people actually search for. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
62399
62377
|
args: {
|
|
62400
62378
|
keyword: { type: "string", description: "Seed keyword (positional)", required: true },
|
|
62401
62379
|
location: {
|
|
62402
62380
|
type: "string",
|
|
62403
|
-
description: "Country code (us, uk, es, de...) or DataForSEO location code.
|
|
62381
|
+
description: "Country code (us, uk, es, de...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted",
|
|
62404
62382
|
required: false
|
|
62405
62383
|
},
|
|
62406
62384
|
language: {
|
|
62407
62385
|
type: "string",
|
|
62408
|
-
description: "Language code or name.
|
|
62386
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted",
|
|
62409
62387
|
required: false
|
|
62410
62388
|
},
|
|
62411
62389
|
limit: { type: "string", description: "Max suggestions (default: 10, max: 20)", required: false },
|
|
@@ -62691,7 +62669,7 @@ Examples:
|
|
|
62691
62669
|
import { defineCommand as defineCommand188 } from "citty";
|
|
62692
62670
|
registerSchema({
|
|
62693
62671
|
command: "research.intent",
|
|
62694
|
-
description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
|
|
62672
|
+
description: "Classify Google Search intent for keywords. Determines if someone searching is looking to buy, research, or navigate. IMPORTANT: If --language is omitted, defaults to English (en). The response includes a query_context object showing which language was used.",
|
|
62695
62673
|
args: {
|
|
62696
62674
|
keywords: {
|
|
62697
62675
|
type: "string",
|
|
@@ -62700,7 +62678,7 @@ registerSchema({
|
|
|
62700
62678
|
},
|
|
62701
62679
|
language: {
|
|
62702
62680
|
type: "string",
|
|
62703
|
-
description: "Language code or name.
|
|
62681
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted \u2014 always set this for non-English keywords",
|
|
62704
62682
|
required: false
|
|
62705
62683
|
},
|
|
62706
62684
|
"no-cache": { type: "boolean", description: "Skip server cache, hit API directly", required: false }
|
|
@@ -62762,18 +62740,18 @@ Examples:
|
|
|
62762
62740
|
import { defineCommand as defineCommand189 } from "citty";
|
|
62763
62741
|
registerSchema({
|
|
62764
62742
|
command: "research.keyword-gap",
|
|
62765
|
-
description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
62743
|
+
description: "Find keywords a competitor ranks for (organic or paid) that you don't. Discovers expansion opportunities. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
62766
62744
|
args: {
|
|
62767
62745
|
competitor: { type: "string", description: "Competitor domain (positional 1)", required: true },
|
|
62768
62746
|
ours: { type: "string", description: "Your domain (positional 2)", required: true },
|
|
62769
62747
|
location: {
|
|
62770
62748
|
type: "string",
|
|
62771
|
-
description: "Country code (us, uk, es, de...) or DataForSEO location code.
|
|
62749
|
+
description: "Country code (us, uk, es, de...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted \u2014 always set this for non-US markets",
|
|
62772
62750
|
required: false
|
|
62773
62751
|
},
|
|
62774
62752
|
language: {
|
|
62775
62753
|
type: "string",
|
|
62776
|
-
description: "Language code or name.
|
|
62754
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted \u2014 always set this for non-English markets",
|
|
62777
62755
|
required: false
|
|
62778
62756
|
},
|
|
62779
62757
|
type: { type: "string", description: "Filter: paid, organic, all (default: all)", required: false },
|
|
@@ -62913,7 +62891,7 @@ function keywordMetricsFields(options) {
|
|
|
62913
62891
|
// src/commands/research/keyword-metrics.ts
|
|
62914
62892
|
registerSchema({
|
|
62915
62893
|
command: "research.keyword-metrics",
|
|
62916
|
-
description: "Search volume, CPC, top-of-page bids and competition for a list of keywords
|
|
62894
|
+
description: "Search volume, CPC, top-of-page bids and competition for a list of keywords YOU name \u2014 no domain needed. This is the command for sizing a market you have keywords for; the domain-bound ones (keywords-for-site, keyword-gap) answer a different question and should not be used as a workaround for this one. The volume is Google's Keyword Planner figure resold by DataForSEO, so it is NOT a second opinion on `baker ads google keywords metrics` \u2014 for that, pass --clickstream. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
62917
62895
|
args: {
|
|
62918
62896
|
keywords: {
|
|
62919
62897
|
type: "string",
|
|
@@ -62922,12 +62900,12 @@ registerSchema({
|
|
|
62922
62900
|
},
|
|
62923
62901
|
location: {
|
|
62924
62902
|
type: "string",
|
|
62925
|
-
description: "Country code (us, mx, br, es...) or DataForSEO location code.
|
|
62903
|
+
description: "Country code (us, mx, br, es...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted \u2014 always set this for non-US markets",
|
|
62926
62904
|
required: false
|
|
62927
62905
|
},
|
|
62928
62906
|
language: {
|
|
62929
62907
|
type: "string",
|
|
62930
|
-
description: "Language code or name.
|
|
62908
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted \u2014 always set this for non-English markets",
|
|
62931
62909
|
required: false
|
|
62932
62910
|
},
|
|
62933
62911
|
clickstream: {
|
|
@@ -63028,17 +63006,17 @@ Examples:
|
|
|
63028
63006
|
import { defineCommand as defineCommand191 } from "citty";
|
|
63029
63007
|
registerSchema({
|
|
63030
63008
|
command: "research.keywords-for-site",
|
|
63031
|
-
description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
63009
|
+
description: "Get keywords a competitor targets in Google. Use --type paid to see only paid keywords, --type organic for organic only. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en). The response includes a query_context object showing which location/language were used.",
|
|
63032
63010
|
args: {
|
|
63033
63011
|
target: { type: "string", description: "Domain or URL to analyze (positional)", required: true },
|
|
63034
63012
|
location: {
|
|
63035
63013
|
type: "string",
|
|
63036
|
-
description: "Country code (us, uk, es, de...) or DataForSEO location code.
|
|
63014
|
+
description: "Country code (us, uk, es, de...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted \u2014 always set this for non-US markets",
|
|
63037
63015
|
required: false
|
|
63038
63016
|
},
|
|
63039
63017
|
language: {
|
|
63040
63018
|
type: "string",
|
|
63041
|
-
description: "Language code or name.
|
|
63019
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted \u2014 always set this for non-English markets",
|
|
63042
63020
|
required: false
|
|
63043
63021
|
},
|
|
63044
63022
|
sort: {
|
|
@@ -63221,17 +63199,17 @@ Examples:
|
|
|
63221
63199
|
import { defineCommand as defineCommand194 } from "citty";
|
|
63222
63200
|
registerSchema({
|
|
63223
63201
|
command: "research.relevant-pages",
|
|
63224
|
-
description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
63202
|
+
description: "Get the top pages of a competitor domain with organic traffic and ranking data. Shows which pages drive the most traffic. IMPORTANT: If --location and --language are omitted, defaults to United States (us) and English (en).",
|
|
63225
63203
|
args: {
|
|
63226
63204
|
target: { type: "string", description: "Domain to analyze (positional, e.g. competitor.com)", required: true },
|
|
63227
63205
|
location: {
|
|
63228
63206
|
type: "string",
|
|
63229
|
-
description: "Country code (us, uk, es, de...) or DataForSEO location code.
|
|
63207
|
+
description: "Country code (us, uk, es, de...) or DataForSEO location code. DEFAULTS TO us (United States) if omitted",
|
|
63230
63208
|
required: false
|
|
63231
63209
|
},
|
|
63232
63210
|
language: {
|
|
63233
63211
|
type: "string",
|
|
63234
|
-
description: "Language code or name.
|
|
63212
|
+
description: "Language code or name. DEFAULTS TO en (English) if omitted",
|
|
63235
63213
|
required: false
|
|
63236
63214
|
},
|
|
63237
63215
|
limit: { type: "string", description: "Max results (default: 20, max: 1000)", required: false },
|
|
@@ -65869,7 +65847,7 @@ function handleError8(err) {
|
|
|
65869
65847
|
...err.code === "UNAUTHORIZED" || err.code === "FORBIDDEN" && isNotConnectedError(err.code, err.message) ? {
|
|
65870
65848
|
fix: {
|
|
65871
65849
|
action: "request_connection",
|
|
65872
|
-
explanation: 'Tag Manager is not connected for this company yet, or no container has been picked.
|
|
65850
|
+
explanation: 'Tag Manager is not connected for this company yet, or no container has been picked. Do NOT send the user to Settings \u2014 call the `request_connection` tool with { platform: "google-tag-manager", reason } and they connect and pick their containers (they can pick more than one) from inside the chat. `request_tag_input` is NOT the route: that form manages the container snippet on the site, not what runs inside the container, and it cannot pick a container. If `request_connection` is not available to you, say so plainly and point them at dashboard \u2192 Brain \u2192 Integrations \u2192 Tools \u2192 Google Tag Manager, where they also pick the container. Carry on with the rest of the task meanwhile.'
|
|
65873
65851
|
}
|
|
65874
65852
|
} : {},
|
|
65875
65853
|
retryable: RETRYABLE_CODES2.has(err.code)
|
|
@@ -66383,7 +66361,7 @@ ${configLines.join("\n")}` : header;
|
|
|
66383
66361
|
function tagsListHints() {
|
|
66384
66362
|
return [
|
|
66385
66363
|
"To create, edit or delete a tag, call the `request_tag_input` approval form (tool `mcp__baker_ui__request_tag_input`, server `baker_ui`) \u2014 it is attached to every turn of this chat and collects any secret values itself.",
|
|
66386
|
-
"
|
|
66364
|
+
"Do NOT conclude the form is unavailable from your tool list: call it, and report what it returns. And never file a tag change as a Task \u2014 a Task hands the user work this chat can stage in one call."
|
|
66387
66365
|
];
|
|
66388
66366
|
}
|
|
66389
66367
|
|
|
@@ -66455,7 +66433,7 @@ var tagsCommand4 = defineCommand222({
|
|
|
66455
66433
|
name: "tags",
|
|
66456
66434
|
description: `Read the client's marketing/analytics tags (Meta pixel, GA4, Google Ads, GTM, Clarity, Hotjar, \u2026) \u2014 production tags plus the changes staged in this chat.
|
|
66457
66435
|
|
|
66458
|
-
This command is
|
|
66436
|
+
This command is READ-ONLY. Every tag change \u2014 create, edit, or delete \u2014 goes through the request_tag_input tool (baker_ui MCP server): propose one or more changes (each becomes a tab in one approval form), pre-fill the non-secret fields you know, and the user reviews, fills secrets, and approves or skips each. Secret values (accessToken, apiSecret, authorizationToken, apiKey, conversionToken, OAuth connections) never pass through the CLI or the chat.
|
|
66459
66437
|
|
|
66460
66438
|
Refs: \`baker tags list\` prints each tag's ref \u2014 a real tag id, or tag_temp_* for creates staged in this chat. Use these refs as flow side-effect tagIds; they keep resolving after publish.
|
|
66461
66439
|
|
|
@@ -69246,6 +69224,141 @@ function outputFlagForCommand(root, argv) {
|
|
|
69246
69224
|
});
|
|
69247
69225
|
}
|
|
69248
69226
|
|
|
69227
|
+
// src/field-mask.ts
|
|
69228
|
+
var FLAG2 = "--fields";
|
|
69229
|
+
function parseFields(value) {
|
|
69230
|
+
return value.split(",").map((field) => field.trim()).filter((field) => field.length > 0);
|
|
69231
|
+
}
|
|
69232
|
+
function takeFieldsFlag(root, argv) {
|
|
69233
|
+
const end = argv.indexOf("--");
|
|
69234
|
+
const limit = end === -1 ? argv.length : end;
|
|
69235
|
+
const taken = /* @__PURE__ */ new Set();
|
|
69236
|
+
const fields = [];
|
|
69237
|
+
for (let index = 0; index < limit; index++) {
|
|
69238
|
+
const token = argv[index] ?? "";
|
|
69239
|
+
if (token.startsWith(`${FLAG2}=`)) {
|
|
69240
|
+
taken.add(index);
|
|
69241
|
+
fields.push(...parseFields(token.slice(FLAG2.length + 1)));
|
|
69242
|
+
} else if (token === FLAG2) {
|
|
69243
|
+
const value = argv[index + 1];
|
|
69244
|
+
if (value === void 0 || index + 1 >= limit || value.startsWith("-")) {
|
|
69245
|
+
return { argv, fields: null };
|
|
69246
|
+
}
|
|
69247
|
+
taken.add(index).add(index + 1);
|
|
69248
|
+
fields.push(...parseFields(value));
|
|
69249
|
+
index++;
|
|
69250
|
+
}
|
|
69251
|
+
}
|
|
69252
|
+
if (taken.size === 0 || fields.length === 0) {
|
|
69253
|
+
return { argv, fields: null };
|
|
69254
|
+
}
|
|
69255
|
+
const command = commandFor(root, argv);
|
|
69256
|
+
if (!command || declaredArg(command, "fields") || PRINTS_NO_ENVELOPE.has(commandPathOf(root, argv))) {
|
|
69257
|
+
return { argv, fields: null };
|
|
69258
|
+
}
|
|
69259
|
+
return { argv: argv.filter((_, index) => !taken.has(index)), fields };
|
|
69260
|
+
}
|
|
69261
|
+
function isRecord3(value) {
|
|
69262
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
69263
|
+
}
|
|
69264
|
+
function pick(record, fields) {
|
|
69265
|
+
return Object.fromEntries(fields.filter((field) => field in record).map((field) => [field, record[field]]));
|
|
69266
|
+
}
|
|
69267
|
+
function wrappedList(data, fields) {
|
|
69268
|
+
if (fields.some((field) => field in data)) {
|
|
69269
|
+
return null;
|
|
69270
|
+
}
|
|
69271
|
+
const lists = Object.entries(data).filter(
|
|
69272
|
+
([, value]) => Array.isArray(value) && value.length > 0 && value.every((item) => isRecord3(item))
|
|
69273
|
+
);
|
|
69274
|
+
return lists.length === 1 ? lists[0]?.[0] ?? null : null;
|
|
69275
|
+
}
|
|
69276
|
+
function recordsOf(data, fields) {
|
|
69277
|
+
if (Array.isArray(data)) {
|
|
69278
|
+
return data;
|
|
69279
|
+
}
|
|
69280
|
+
if (!isRecord3(data)) {
|
|
69281
|
+
return [];
|
|
69282
|
+
}
|
|
69283
|
+
const list = wrappedList(data, fields);
|
|
69284
|
+
return list === null ? [data] : data[list];
|
|
69285
|
+
}
|
|
69286
|
+
function maskData(data, fields) {
|
|
69287
|
+
const mask = (item) => isRecord3(item) ? pick(item, fields) : item;
|
|
69288
|
+
if (Array.isArray(data)) {
|
|
69289
|
+
return data.map(mask);
|
|
69290
|
+
}
|
|
69291
|
+
if (!isRecord3(data)) {
|
|
69292
|
+
return data;
|
|
69293
|
+
}
|
|
69294
|
+
const list = wrappedList(data, fields);
|
|
69295
|
+
return list === null ? pick(data, fields) : { ...data, [list]: recordsOf(data, fields).map(mask) };
|
|
69296
|
+
}
|
|
69297
|
+
function unmatchedHint(data, fields) {
|
|
69298
|
+
const records = recordsOf(data, fields).filter(isRecord3);
|
|
69299
|
+
if (records.length === 0) {
|
|
69300
|
+
return null;
|
|
69301
|
+
}
|
|
69302
|
+
const carried = new Set(records.flatMap((record) => Object.keys(record)));
|
|
69303
|
+
const unmatched = fields.filter((field) => !carried.has(field));
|
|
69304
|
+
if (unmatched.length === 0) {
|
|
69305
|
+
return null;
|
|
69306
|
+
}
|
|
69307
|
+
return `--fields matched nothing for: ${unmatched.join(", ")}. The keys data carries are: ${[...carried].join(", ")}.`;
|
|
69308
|
+
}
|
|
69309
|
+
function reprint(text2, envelope) {
|
|
69310
|
+
const indent = text2.trimStart().startsWith("{\n") ? 2 : void 0;
|
|
69311
|
+
return `${JSON.stringify(envelope, null, indent)}${text2.endsWith("\n") ? "\n" : ""}`;
|
|
69312
|
+
}
|
|
69313
|
+
function withHint(envelope, hint) {
|
|
69314
|
+
return { ...envelope, hints: [...Array.isArray(envelope.hints) ? envelope.hints : [], hint] };
|
|
69315
|
+
}
|
|
69316
|
+
var NO_DATA_HINT = "--fields was not applied: this result carries no data to narrow, so it is printed whole. Drop --fields on this command.";
|
|
69317
|
+
function maskPrintedEnvelope(text2, fields) {
|
|
69318
|
+
let parsed;
|
|
69319
|
+
try {
|
|
69320
|
+
parsed = JSON.parse(text2);
|
|
69321
|
+
} catch {
|
|
69322
|
+
return null;
|
|
69323
|
+
}
|
|
69324
|
+
if (!isRecord3(parsed) || typeof parsed.ok !== "boolean") {
|
|
69325
|
+
return null;
|
|
69326
|
+
}
|
|
69327
|
+
if (parsed.ok !== true || "dryRun" in parsed) {
|
|
69328
|
+
return text2;
|
|
69329
|
+
}
|
|
69330
|
+
if (!("data" in parsed)) {
|
|
69331
|
+
return reprint(text2, withHint(parsed, NO_DATA_HINT));
|
|
69332
|
+
}
|
|
69333
|
+
const masked = { ...parsed, data: maskData(parsed.data, fields) };
|
|
69334
|
+
const miss = unmatchedHint(parsed.data, fields);
|
|
69335
|
+
return reprint(text2, miss ? withHint(masked, miss) : masked);
|
|
69336
|
+
}
|
|
69337
|
+
function maskWrites(stream, fields) {
|
|
69338
|
+
let applied = false;
|
|
69339
|
+
const write = stream.write.bind(stream);
|
|
69340
|
+
stream.write = (chunk, ...rest) => {
|
|
69341
|
+
const masked = typeof chunk === "string" ? maskPrintedEnvelope(chunk, fields) : null;
|
|
69342
|
+
if (masked === null) {
|
|
69343
|
+
return write(chunk, ...rest);
|
|
69344
|
+
}
|
|
69345
|
+
applied = true;
|
|
69346
|
+
return write(masked, ...rest);
|
|
69347
|
+
};
|
|
69348
|
+
return { sawEnvelope: () => applied };
|
|
69349
|
+
}
|
|
69350
|
+
function installFieldMask(fields, commandPath) {
|
|
69351
|
+
const mask = maskWrites(process.stdout, fields);
|
|
69352
|
+
process.on("exit", () => {
|
|
69353
|
+
if (!mask.sawEnvelope()) {
|
|
69354
|
+
process.stderr.write(
|
|
69355
|
+
`--fields was not applied: \`baker ${commandPath}\` printed no JSON envelope to narrow. Add --output json to get the envelope, or drop --fields.
|
|
69356
|
+
`
|
|
69357
|
+
);
|
|
69358
|
+
}
|
|
69359
|
+
});
|
|
69360
|
+
}
|
|
69361
|
+
|
|
69249
69362
|
// src/version.ts
|
|
69250
69363
|
import { readFileSync as readFileSync21 } from "fs";
|
|
69251
69364
|
function packageJsonUrl() {
|
|
@@ -69272,6 +69385,7 @@ var main = defineCommand248({
|
|
|
69272
69385
|
Auth: Set BAKER_API_KEY (starts with bk_) and BAKER_API_URL environment variables.
|
|
69273
69386
|
Chat: Set BAKER_CHAT_ID for action and scheduled-agent commands that stage changes against a chat.
|
|
69274
69387
|
Output: All commands return JSON envelopes: { ok: true, data: ... } or { ok: false, error: { code, message } }.
|
|
69388
|
+
Fields: --fields a,b,c keeps only those keys of each record in data, on every command that prints the envelope.
|
|
69275
69389
|
Formats: --output json works on every command that prints the envelope. --output md|files only where 'baker schema <command>' lists output. (The research commands additionally accept csv and jsonl.)
|
|
69276
69390
|
Introspection: Run 'baker schema <command>' to inspect argument schemas.`
|
|
69277
69391
|
},
|
|
@@ -69311,6 +69425,11 @@ installStreamTaps();
|
|
|
69311
69425
|
logInvocation(process.argv.slice(2));
|
|
69312
69426
|
installExitRecord();
|
|
69313
69427
|
process.argv = [...process.argv.slice(0, 2), ...outputFlagForCommand(main, process.argv.slice(2))];
|
|
69428
|
+
var fieldsFlag = takeFieldsFlag(main, process.argv.slice(2));
|
|
69429
|
+
if (fieldsFlag.fields) {
|
|
69430
|
+
process.argv = [...process.argv.slice(0, 2), ...fieldsFlag.argv];
|
|
69431
|
+
installFieldMask(fieldsFlag.fields, commandPathOf(main, fieldsFlag.argv));
|
|
69432
|
+
}
|
|
69314
69433
|
refuseUnknownFlags(main, process.argv.slice(2));
|
|
69315
69434
|
process.argv = [...process.argv.slice(0, 2), ...cacheFlagForParser(process.argv.slice(2))];
|
|
69316
69435
|
runMain(main);
|