@koda-sl/baker-cli 0.109.0-dev.2acd65459 → 0.110.1-dev.02d8aed71
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 +2 -2
- package/dist/cli.js +103 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,7 +236,7 @@ The CLI rejects or automatically corrects common GAQL mistakes before hitting th
|
|
|
236
236
|
| Pattern | Behavior | Warning/error emitted |
|
|
237
237
|
|---------|----------|-----------------------|
|
|
238
238
|
| Missing LIMIT | Adds `LIMIT 200` | `"Added LIMIT 200. Use --limit to override."` |
|
|
239
|
-
| `keyword.text` | Rewrites to `ad_group_criterion.keyword.text` | `"keyword.text → ad_group_criterion.keyword.text"` |
|
|
239
|
+
| Bare `keyword.text` / `keyword.match_type` | Rewrites to `ad_group_criterion.*` (resource-scoped forms like `shared_criterion.keyword.text` pass through untouched) | `"keyword.text → ad_group_criterion.keyword.text"` |
|
|
240
240
|
| `shopping_performance_view.product_*` | Rewrites to `segments.product_*` | Field renamed |
|
|
241
241
|
| `campaign.status = 'ACTIVE'` | Rewrites to `= 'ENABLED'` | Enum corrected |
|
|
242
242
|
| `segments.conversion_action_name` with `metrics.cost_micros` | Rejects; split into separate conversion-action and spend queries | `INCOMPATIBLE_FIELDS` with `fix.action: "split_query"` |
|
|
@@ -1029,7 +1029,7 @@ Notes:
|
|
|
1029
1029
|
|
|
1030
1030
|
- All write commands take `--file <json>` payloads; explicit flags override file keys. `baker schema ads.linkedin.campaigns.create` for exact args.
|
|
1031
1031
|
- Money flags (`--bid`, `--daily-budget`, `--total-budget`) require `--currency`.
|
|
1032
|
-
- Creative media comes from the Baker library (`--image-id`/`--video-id` from `baker images`/`baker videos` — uploaded to LinkedIn at publish) or as LinkedIn URNs (`--image-urn`/`--video-urn`). Formats: `image|video|text|spotlight|follower|document|carousel|conversation|tla|jobs`; complex formats take `--file` with the full content object. Limits: headline ≤70, text-ad 25/75, intro soft-truncates at 600 chars. TLA sponsors an existing post via `--post-urn`.
|
|
1032
|
+
- Creative media comes from the Baker library (`--image-id`/`--video-id` from `baker images`/`baker videos` — uploaded to LinkedIn at publish) or as LinkedIn URNs (`--image-urn`/`--video-urn`). Formats: `image|video|text|spotlight|follower|document|carousel|conversation|tla|jobs`; complex formats take `--file` with the full content object; conversation ads take `--file` with the message flow (`{message: {subject, body, senderName?, buttons[]}}` — buttons `NESTED` (with `nestedMessage`) or `LANDING_PAGE` (with `landingPageUrl`), ≤25 messages, bodies ≤500 chars, labels ≤25). Limits: headline ≤70, text-ad 25/75, intro soft-truncates at 600 chars. TLA sponsors an existing post via `--post-urn`.
|
|
1033
1033
|
- Lead forms are file-first (`lead-forms create --file form.json`): name, headline (≤60), privacyPolicyUrl, questions[] (≤12; playbook: ≤4 for completion).
|
|
1034
1034
|
|
|
1035
1035
|
#### `audit` — playbook diagnostic
|
package/dist/cli.js
CHANGED
|
@@ -957,7 +957,11 @@ var LINKEDIN_LIMITS = {
|
|
|
957
957
|
carouselCardHeadlineMax: 45,
|
|
958
958
|
carouselCardsMin: 2,
|
|
959
959
|
carouselCardsMax: 10,
|
|
960
|
-
conversationButtonLabelMax: 25
|
|
960
|
+
conversationButtonLabelMax: 25,
|
|
961
|
+
conversationBodyMax: 500,
|
|
962
|
+
conversationButtonsMax: 5,
|
|
963
|
+
conversationMessagesMax: 25,
|
|
964
|
+
conversationSubjectMax: 60
|
|
961
965
|
},
|
|
962
966
|
audience: {
|
|
963
967
|
nameMax: 100,
|
|
@@ -1263,10 +1267,68 @@ var carouselCreativeSchema = z3.object({
|
|
|
1263
1267
|
})
|
|
1264
1268
|
).min(LINKEDIN_LIMITS.creative.carouselCardsMin).max(LINKEDIN_LIMITS.creative.carouselCardsMax)
|
|
1265
1269
|
});
|
|
1270
|
+
var conversationButtonSchema = z3.lazy(
|
|
1271
|
+
() => z3.object({
|
|
1272
|
+
label: z3.string().min(1).max(LINKEDIN_LIMITS.creative.conversationButtonLabelMax),
|
|
1273
|
+
type: z3.enum(["NESTED", "LANDING_PAGE"]),
|
|
1274
|
+
nestedMessage: conversationMessageSchema.optional(),
|
|
1275
|
+
landingPageUrl: httpsUrlSchema.optional()
|
|
1276
|
+
}).superRefine((button, ctx) => {
|
|
1277
|
+
if (button.type === "NESTED" && !button.nestedMessage) {
|
|
1278
|
+
ctx.addIssue({ code: "custom", path: ["nestedMessage"], message: "NESTED buttons need a nestedMessage" });
|
|
1279
|
+
}
|
|
1280
|
+
if (button.type === "LANDING_PAGE" && !button.landingPageUrl) {
|
|
1281
|
+
ctx.addIssue({
|
|
1282
|
+
code: "custom",
|
|
1283
|
+
path: ["landingPageUrl"],
|
|
1284
|
+
message: "LANDING_PAGE buttons need a landingPageUrl"
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
if (button.type === "LANDING_PAGE" && button.nestedMessage) {
|
|
1288
|
+
ctx.addIssue({
|
|
1289
|
+
code: "custom",
|
|
1290
|
+
path: ["nestedMessage"],
|
|
1291
|
+
message: "LANDING_PAGE buttons cannot nest a message"
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
})
|
|
1295
|
+
);
|
|
1296
|
+
var conversationMessageSchema = z3.lazy(
|
|
1297
|
+
() => z3.object({
|
|
1298
|
+
subject: z3.string().min(1).max(LINKEDIN_LIMITS.creative.conversationSubjectMax).optional(),
|
|
1299
|
+
body: z3.string().min(1).max(LINKEDIN_LIMITS.creative.conversationBodyMax),
|
|
1300
|
+
senderName: z3.string().min(1).max(100).optional(),
|
|
1301
|
+
buttons: z3.array(conversationButtonSchema).max(LINKEDIN_LIMITS.creative.conversationButtonsMax).optional()
|
|
1302
|
+
})
|
|
1303
|
+
);
|
|
1304
|
+
function countConversationMessages(message) {
|
|
1305
|
+
return 1 + (message.buttons ?? []).reduce(
|
|
1306
|
+
(sum, button) => sum + (button.nestedMessage ? countConversationMessages(button.nestedMessage) : 0),
|
|
1307
|
+
0
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1266
1310
|
var conversationCreativeSchema = z3.object({
|
|
1267
1311
|
format: z3.literal("conversation"),
|
|
1268
1312
|
name: z3.string().max(255).optional(),
|
|
1269
|
-
|
|
1313
|
+
/** Structured flow — publishable via the Conversation Ads API. */
|
|
1314
|
+
message: conversationMessageSchema.optional(),
|
|
1315
|
+
/** Legacy free-form passthrough — preview-only; restage with `message` to publish. */
|
|
1316
|
+
raw: z3.record(z3.string(), z3.unknown()).optional()
|
|
1317
|
+
}).superRefine((p, ctx) => {
|
|
1318
|
+
if (Boolean(p.message) === Boolean(p.raw)) {
|
|
1319
|
+
ctx.addIssue({
|
|
1320
|
+
code: "custom",
|
|
1321
|
+
path: ["message"],
|
|
1322
|
+
message: "provide the conversation flow as `message` (legacy `raw` is accepted alone, not alongside it)"
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
if (p.message && countConversationMessages(p.message) > LINKEDIN_LIMITS.creative.conversationMessagesMax) {
|
|
1326
|
+
ctx.addIssue({
|
|
1327
|
+
code: "custom",
|
|
1328
|
+
path: ["message"],
|
|
1329
|
+
message: `conversation flows are capped at ${LINKEDIN_LIMITS.creative.conversationMessagesMax} messages`
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1270
1332
|
});
|
|
1271
1333
|
var tlaCreativeSchema = z3.object({
|
|
1272
1334
|
format: z3.literal("tla"),
|
|
@@ -1323,10 +1385,9 @@ var creativeUpdateSchema = z3.object({
|
|
|
1323
1385
|
landingUrl: httpsUrlSchema.optional(),
|
|
1324
1386
|
cta: z3.enum(CTA_TYPES).optional(),
|
|
1325
1387
|
/**
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
* full creative schema.
|
|
1329
|
-
* content; the backend rejects it for real targets.
|
|
1388
|
+
* Content patch: merged into the staged create (li_temp_* targets) or the
|
|
1389
|
+
* live ad's snapshot content (real targets, direct-content formats) and
|
|
1390
|
+
* re-validated against the full creative schema. Post-based ads reject it.
|
|
1330
1391
|
*/
|
|
1331
1392
|
content: z3.record(z3.string(), z3.unknown()).optional()
|
|
1332
1393
|
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
@@ -3203,12 +3264,12 @@ function buildCommand(query, ctx) {
|
|
|
3203
3264
|
return `baker ads google query "${query}" --customer-id ${ctx.customerId}`;
|
|
3204
3265
|
}
|
|
3205
3266
|
var CORRECTION_RULES = [
|
|
3206
|
-
// 1. keyword.text → ad_group_criterion.keyword.text
|
|
3267
|
+
// 1. bare keyword.text → ad_group_criterion.keyword.text (leaves <resource>.keyword.text untouched)
|
|
3207
3268
|
{
|
|
3208
|
-
matchQuery:
|
|
3269
|
+
matchQuery: /(?<![\w.])keyword\.text\b/,
|
|
3209
3270
|
matchApiError: /keyword\.text/i,
|
|
3210
3271
|
fix: (ctx) => {
|
|
3211
|
-
const corrected = ctx.originalQuery.replace(
|
|
3272
|
+
const corrected = ctx.originalQuery.replace(/(?<![\w.])keyword\.text\b/g, "ad_group_criterion.keyword.text");
|
|
3212
3273
|
return {
|
|
3213
3274
|
action: "retry_with_modified_query",
|
|
3214
3275
|
correctedCommand: buildCommand(corrected, ctx),
|
|
@@ -3216,12 +3277,15 @@ var CORRECTION_RULES = [
|
|
|
3216
3277
|
};
|
|
3217
3278
|
}
|
|
3218
3279
|
},
|
|
3219
|
-
// 2. keyword.match_type → ad_group_criterion.keyword.match_type
|
|
3280
|
+
// 2. bare keyword.match_type → ad_group_criterion.keyword.match_type (leaves <resource>.keyword.match_type untouched)
|
|
3220
3281
|
{
|
|
3221
|
-
matchQuery:
|
|
3282
|
+
matchQuery: /(?<![\w.])keyword\.match_type\b/,
|
|
3222
3283
|
matchApiError: /keyword\.match_type/i,
|
|
3223
3284
|
fix: (ctx) => {
|
|
3224
|
-
const corrected = ctx.originalQuery.replace(
|
|
3285
|
+
const corrected = ctx.originalQuery.replace(
|
|
3286
|
+
/(?<![\w.])keyword\.match_type\b/g,
|
|
3287
|
+
"ad_group_criterion.keyword.match_type"
|
|
3288
|
+
);
|
|
3225
3289
|
return {
|
|
3226
3290
|
action: "retry_with_modified_query",
|
|
3227
3291
|
correctedCommand: buildCommand(corrected, ctx),
|
|
@@ -4573,12 +4637,12 @@ function buildCommand2(query, customerId) {
|
|
|
4573
4637
|
function applyAutoFixes(query, limit) {
|
|
4574
4638
|
let corrected = query;
|
|
4575
4639
|
const warnings = [];
|
|
4576
|
-
if (
|
|
4577
|
-
corrected = corrected.replace(
|
|
4640
|
+
if (/(?<![\w.])keyword\.text\b/.test(corrected)) {
|
|
4641
|
+
corrected = corrected.replace(/(?<![\w.])keyword\.text\b/g, "ad_group_criterion.keyword.text");
|
|
4578
4642
|
warnings.push({ code: "FIELD_RENAMED", message: "keyword.text \u2192 ad_group_criterion.keyword.text" });
|
|
4579
4643
|
}
|
|
4580
|
-
if (
|
|
4581
|
-
corrected = corrected.replace(
|
|
4644
|
+
if (/(?<![\w.])keyword\.match_type\b/.test(corrected)) {
|
|
4645
|
+
corrected = corrected.replace(/(?<![\w.])keyword\.match_type\b/g, "ad_group_criterion.keyword.match_type");
|
|
4582
4646
|
warnings.push({ code: "FIELD_RENAMED", message: "keyword.match_type \u2192 ad_group_criterion.keyword.match_type" });
|
|
4583
4647
|
}
|
|
4584
4648
|
if (/shopping_performance_view\.product_\w+/.test(corrected)) {
|
|
@@ -5492,6 +5556,7 @@ registerSchema({
|
|
|
5492
5556
|
...writeAccountArgs,
|
|
5493
5557
|
name: { type: "string", description: "New name", required: false },
|
|
5494
5558
|
objective: { type: "string", description: "New objective", required: false },
|
|
5559
|
+
"cost-type": { type: "string", description: "CPC|CPM|CPV", required: false },
|
|
5495
5560
|
bid: { type: "string", description: "New manual bid", required: false },
|
|
5496
5561
|
"daily-budget": { type: "string", description: "New daily budget", required: false },
|
|
5497
5562
|
"total-budget": { type: "string", description: "New lifetime budget", required: false },
|
|
@@ -5592,7 +5657,7 @@ registerSchema({
|
|
|
5592
5657
|
});
|
|
5593
5658
|
registerSchema({
|
|
5594
5659
|
command: "ads.linkedin.creatives.create",
|
|
5595
|
-
description: "Stage a new creative (ad). Formats: image|video|text|spotlight|follower|document|carousel|conversation|tla|jobs|event|article. Media: --image-id/--video-id reference the Baker library (`baker images`/`baker videos`) and upload to LinkedIn at publish; --image-urn/--video-urn reference media already on LinkedIn. Limits: headline \u226470 (text ads \u226425), intro soft-truncates at 600 chars, landing URL must be https. Complex formats take --file with the full content object. Staged until publish.",
|
|
5660
|
+
description: "Stage a new creative (ad). Formats: image|video|text|spotlight|follower|document|carousel|conversation|tla|jobs|event|article. Media: --image-id/--video-id reference the Baker library (`baker images`/`baker videos`) and upload to LinkedIn at publish; --image-urn/--video-urn reference media already on LinkedIn. Limits: headline \u226470 (text ads \u226425), intro soft-truncates at 600 chars, landing URL must be https. Complex formats take --file with the full content object; conversation ads take --file with the message flow ({message: {subject, body (\u2264500), senderName?, buttons \u22645 \xD7 {label \u226425, type NESTED|LANDING_PAGE, nestedMessage?|landingPageUrl?}}}, \u226425 messages). Staged until publish.",
|
|
5596
5661
|
args: {
|
|
5597
5662
|
...writeAccountArgs,
|
|
5598
5663
|
campaign: { type: "string", description: "Campaign id/URN or li_temp_* ref", required: true },
|
|
@@ -5613,8 +5678,24 @@ registerSchema({
|
|
|
5613
5678
|
cta: { type: "string", description: "LEARN_MORE|REQUEST_DEMO|SIGN_UP|REGISTER|DOWNLOAD|\u2026", required: false },
|
|
5614
5679
|
"cta-label": { type: "string", description: "Spotlight CTA label (\u226418 chars)", required: false },
|
|
5615
5680
|
"post-urn": { type: "string", description: "TLA: post to sponsor (urn:li:share|ugcPost:*)", required: false },
|
|
5681
|
+
"event-urn": {
|
|
5682
|
+
type: "string",
|
|
5683
|
+
description: "Event ads: the LinkedIn event to sponsor (urn:li:event:*)",
|
|
5684
|
+
required: false
|
|
5685
|
+
},
|
|
5686
|
+
"article-url": {
|
|
5687
|
+
type: "string",
|
|
5688
|
+
description: "Article ads: https URL of the article/newsletter to sponsor",
|
|
5689
|
+
required: false
|
|
5690
|
+
},
|
|
5691
|
+
"thumbnail-image-id": {
|
|
5692
|
+
type: "string",
|
|
5693
|
+
description: "Article ads: Baker image id for the link thumbnail",
|
|
5694
|
+
required: false
|
|
5695
|
+
},
|
|
5696
|
+
"thumbnail-urn": { type: "string", description: "Article ads: existing urn:li:image:* thumbnail", required: false },
|
|
5616
5697
|
"intended-status": { type: "string", description: "DRAFT|ACTIVE|PAUSED (default DRAFT)", required: false },
|
|
5617
|
-
title: { type: "string", description: "Media title", required: false },
|
|
5698
|
+
title: { type: "string", description: "Media title (image/video) or article title", required: false },
|
|
5618
5699
|
file: { type: "string", description: "JSON file with the full content object", required: false }
|
|
5619
5700
|
}
|
|
5620
5701
|
});
|
|
@@ -5631,15 +5712,18 @@ registerSchema({
|
|
|
5631
5712
|
description: { type: "string", description: "Description (text/spotlight)", required: false },
|
|
5632
5713
|
"landing-url": { type: "string", description: "New https destination URL", required: false },
|
|
5633
5714
|
cta: { type: "string", description: "New CTA", required: false },
|
|
5715
|
+
"cta-label": { type: "string", description: "Spotlight custom CTA label", required: false },
|
|
5634
5716
|
"image-id": { type: "string", description: "Baker image library id", required: false },
|
|
5635
5717
|
"image-urn": { type: "string", description: "Existing urn:li:image:*", required: false },
|
|
5636
5718
|
"video-id": { type: "string", description: "Baker video library id", required: false },
|
|
5719
|
+
"video-urn": { type: "string", description: "Existing urn:li:video:*", required: false },
|
|
5637
5720
|
"logo-image-id": { type: "string", description: "Baker image id for the logo", required: false },
|
|
5638
5721
|
"thumbnail-image-id": {
|
|
5639
5722
|
type: "string",
|
|
5640
5723
|
description: "Baker image id for the article thumbnail",
|
|
5641
5724
|
required: false
|
|
5642
5725
|
},
|
|
5726
|
+
"thumbnail-urn": { type: "string", description: "Existing urn:li:image:* thumbnail", required: false },
|
|
5643
5727
|
title: { type: "string", description: "Media or article title", required: false },
|
|
5644
5728
|
file: { type: "string", description: "JSON file with fields to change; flags override file keys", required: false }
|
|
5645
5729
|
}
|
|
@@ -7112,7 +7196,7 @@ var creativesCreateCommand = defineCommand36({
|
|
|
7112
7196
|
meta: {
|
|
7113
7197
|
name: "create",
|
|
7114
7198
|
description: `Stage a new creative (ad). ${STAGED_NOTE}
|
|
7115
|
-
Formats: ${CREATIVE_FORMATS.join("|")}. Media comes from the Baker library (--image-id / --video-id from \`baker images\`/\`baker videos\`) or as LinkedIn URNs. Complex formats (carousel,
|
|
7199
|
+
Formats: ${CREATIVE_FORMATS.join("|")}. Media comes from the Baker library (--image-id / --video-id from \`baker images\`/\`baker videos\`) or as LinkedIn URNs. Complex formats (carousel, jobs) take --file with the full content object. Conversation ads take --file with the message flow ({message: {subject, body, buttons[]}} \u2014 buttons NESTED|LANDING_PAGE, nested messages recurse).
|
|
7116
7200
|
Examples:
|
|
7117
7201
|
baker ads linkedin creatives create --campaign li_temp_x2 --format image --image-id <bakerImageId> --intro "Meet the platform\u2026" --headline "Book a demo" --landing-url https://example.com/demo --cta REQUEST_DEMO
|
|
7118
7202
|
baker ads linkedin creatives create --campaign 123456 --format tla --post-urn urn:li:ugcPost:789`
|