@koda-sl/baker-cli 0.108.0-dev.d66dc8b3d → 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 +9 -5
- package/dist/cli.js +148 -69
- 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"` |
|
|
@@ -1006,9 +1006,13 @@ baker ads linkedin conversions create --name "Demo booked" --type LEAD --method
|
|
|
1006
1006
|
# Safe duplication — stage-time read of the source → staged DRAFT create ("Duplicate ad", never "Link to original")
|
|
1007
1007
|
baker ads linkedin campaigns duplicate 123456 --name "New variant"
|
|
1008
1008
|
|
|
1009
|
-
# Change copy/media/URL on a LIVE ad —
|
|
1010
|
-
#
|
|
1011
|
-
baker ads linkedin creatives
|
|
1009
|
+
# Change copy/media/URL on a LIVE ad — direct-content ads (text/spotlight/follower/jobs) update IN PLACE;
|
|
1010
|
+
# pass only the changed fields, the rest of the ad's current content carries over
|
|
1011
|
+
baker ads linkedin creatives update 1458423674 --headline "New headline" --description "New copy."
|
|
1012
|
+
|
|
1013
|
+
# Post-based ads (image/video/carousel/…) sponsor a post whose content can't be edited — replace in ONE command:
|
|
1014
|
+
# duplicate with overrides (unchanged fields + status carry over) + --replace pauses the original once the copy publishes
|
|
1015
|
+
baker ads linkedin creatives duplicate 1458413484 --headline "New headline" --replace
|
|
1012
1016
|
|
|
1013
1017
|
# Amend staged ops in place — `update <li_temp_ref>` merges into the staged create and re-validates
|
|
1014
1018
|
baker ads linkedin creatives update li_temp_x3 --headline "Sharper headline" --image-id <bakerImageId>
|
|
@@ -1025,7 +1029,7 @@ Notes:
|
|
|
1025
1029
|
|
|
1026
1030
|
- All write commands take `--file <json>` payloads; explicit flags override file keys. `baker schema ads.linkedin.campaigns.create` for exact args.
|
|
1027
1031
|
- Money flags (`--bid`, `--daily-budget`, `--total-budget`) require `--currency`.
|
|
1028
|
-
- 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`.
|
|
1029
1033
|
- Lead forms are file-first (`lead-forms create --file form.json`): name, headline (≤60), privacyPolicyUrl, questions[] (≤12; playbook: ≤4 for completion).
|
|
1030
1034
|
|
|
1031
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,
|
|
@@ -1226,14 +1230,17 @@ var spotlightCreativeSchema = z3.object({
|
|
|
1226
1230
|
ctaLabel: z3.string().min(1).max(LINKEDIN_LIMITS.creative.spotlightCtaMax),
|
|
1227
1231
|
logoImageId: bakerMediaIdSchema.optional(),
|
|
1228
1232
|
logoUrn: z3.string().regex(IMAGE_URN_REGEX).optional(),
|
|
1229
|
-
showMemberProfilePhoto: z3.boolean().default(true)
|
|
1233
|
+
showMemberProfilePhoto: z3.boolean().default(true),
|
|
1234
|
+
/** Carried from the live snapshot on updates so LinkedIn's whole-content $set preserves it. */
|
|
1235
|
+
organizationName: z3.string().min(1).max(200).optional()
|
|
1230
1236
|
});
|
|
1231
1237
|
var followerCreativeSchema = z3.object({
|
|
1232
1238
|
format: z3.literal("follower"),
|
|
1233
1239
|
headline: z3.string().max(LINKEDIN_LIMITS.creative.spotlightHeadlineMax).optional(),
|
|
1234
1240
|
description: z3.string().max(LINKEDIN_LIMITS.creative.spotlightDescriptionMax).optional(),
|
|
1235
1241
|
logoImageId: bakerMediaIdSchema.optional(),
|
|
1236
|
-
logoUrn: z3.string().regex(IMAGE_URN_REGEX).optional()
|
|
1242
|
+
logoUrn: z3.string().regex(IMAGE_URN_REGEX).optional(),
|
|
1243
|
+
organizationName: z3.string().min(1).max(200).optional()
|
|
1237
1244
|
});
|
|
1238
1245
|
var documentCreativeSchema = z3.object({
|
|
1239
1246
|
format: z3.literal("document"),
|
|
@@ -1260,10 +1267,68 @@ var carouselCreativeSchema = z3.object({
|
|
|
1260
1267
|
})
|
|
1261
1268
|
).min(LINKEDIN_LIMITS.creative.carouselCardsMin).max(LINKEDIN_LIMITS.creative.carouselCardsMax)
|
|
1262
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
|
+
}
|
|
1263
1310
|
var conversationCreativeSchema = z3.object({
|
|
1264
1311
|
format: z3.literal("conversation"),
|
|
1265
1312
|
name: z3.string().max(255).optional(),
|
|
1266
|
-
|
|
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
|
+
}
|
|
1267
1332
|
});
|
|
1268
1333
|
var tlaCreativeSchema = z3.object({
|
|
1269
1334
|
format: z3.literal("tla"),
|
|
@@ -1320,10 +1385,9 @@ var creativeUpdateSchema = z3.object({
|
|
|
1320
1385
|
landingUrl: httpsUrlSchema.optional(),
|
|
1321
1386
|
cta: z3.enum(CTA_TYPES).optional(),
|
|
1322
1387
|
/**
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
* full creative schema.
|
|
1326
|
-
* 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.
|
|
1327
1391
|
*/
|
|
1328
1392
|
content: z3.record(z3.string(), z3.unknown()).optional()
|
|
1329
1393
|
}).refine((p) => Object.values(p).some((val) => val !== void 0), "update needs at least one field");
|
|
@@ -3200,12 +3264,12 @@ function buildCommand(query, ctx) {
|
|
|
3200
3264
|
return `baker ads google query "${query}" --customer-id ${ctx.customerId}`;
|
|
3201
3265
|
}
|
|
3202
3266
|
var CORRECTION_RULES = [
|
|
3203
|
-
// 1. keyword.text → ad_group_criterion.keyword.text
|
|
3267
|
+
// 1. bare keyword.text → ad_group_criterion.keyword.text (leaves <resource>.keyword.text untouched)
|
|
3204
3268
|
{
|
|
3205
|
-
matchQuery:
|
|
3269
|
+
matchQuery: /(?<![\w.])keyword\.text\b/,
|
|
3206
3270
|
matchApiError: /keyword\.text/i,
|
|
3207
3271
|
fix: (ctx) => {
|
|
3208
|
-
const corrected = ctx.originalQuery.replace(
|
|
3272
|
+
const corrected = ctx.originalQuery.replace(/(?<![\w.])keyword\.text\b/g, "ad_group_criterion.keyword.text");
|
|
3209
3273
|
return {
|
|
3210
3274
|
action: "retry_with_modified_query",
|
|
3211
3275
|
correctedCommand: buildCommand(corrected, ctx),
|
|
@@ -3213,12 +3277,15 @@ var CORRECTION_RULES = [
|
|
|
3213
3277
|
};
|
|
3214
3278
|
}
|
|
3215
3279
|
},
|
|
3216
|
-
// 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)
|
|
3217
3281
|
{
|
|
3218
|
-
matchQuery:
|
|
3282
|
+
matchQuery: /(?<![\w.])keyword\.match_type\b/,
|
|
3219
3283
|
matchApiError: /keyword\.match_type/i,
|
|
3220
3284
|
fix: (ctx) => {
|
|
3221
|
-
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
|
+
);
|
|
3222
3289
|
return {
|
|
3223
3290
|
action: "retry_with_modified_query",
|
|
3224
3291
|
correctedCommand: buildCommand(corrected, ctx),
|
|
@@ -4570,12 +4637,12 @@ function buildCommand2(query, customerId) {
|
|
|
4570
4637
|
function applyAutoFixes(query, limit) {
|
|
4571
4638
|
let corrected = query;
|
|
4572
4639
|
const warnings = [];
|
|
4573
|
-
if (
|
|
4574
|
-
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");
|
|
4575
4642
|
warnings.push({ code: "FIELD_RENAMED", message: "keyword.text \u2192 ad_group_criterion.keyword.text" });
|
|
4576
4643
|
}
|
|
4577
|
-
if (
|
|
4578
|
-
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");
|
|
4579
4646
|
warnings.push({ code: "FIELD_RENAMED", message: "keyword.match_type \u2192 ad_group_criterion.keyword.match_type" });
|
|
4580
4647
|
}
|
|
4581
4648
|
if (/shopping_performance_view\.product_\w+/.test(corrected)) {
|
|
@@ -5489,6 +5556,7 @@ registerSchema({
|
|
|
5489
5556
|
...writeAccountArgs,
|
|
5490
5557
|
name: { type: "string", description: "New name", required: false },
|
|
5491
5558
|
objective: { type: "string", description: "New objective", required: false },
|
|
5559
|
+
"cost-type": { type: "string", description: "CPC|CPM|CPV", required: false },
|
|
5492
5560
|
bid: { type: "string", description: "New manual bid", required: false },
|
|
5493
5561
|
"daily-budget": { type: "string", description: "New daily budget", required: false },
|
|
5494
5562
|
"total-budget": { type: "string", description: "New lifetime budget", required: false },
|
|
@@ -5565,7 +5633,7 @@ registerSchema({
|
|
|
5565
5633
|
});
|
|
5566
5634
|
registerSchema({
|
|
5567
5635
|
command: "ads.linkedin.creatives.duplicate",
|
|
5568
|
-
description: "Stage a copy of an existing ad, optionally with new content
|
|
5636
|
+
description: "Stage a copy of an existing ad, optionally with new content. Direct-content ads (text/spotlight/follower/jobs) copy field-by-field; post-based ads re-sponsor the same post \u2014 and since post content can't be edited, duplicate --replace is how post-based ads change copy (direct-content ads update in place via creatives.update instead). --replace stages pausing the original, gated on the new ad publishing successfully, and the copy inherits the source's status.",
|
|
5569
5637
|
args: {
|
|
5570
5638
|
id: { type: "positional", description: "Source creative id or URN", required: true },
|
|
5571
5639
|
...writeAccountArgs,
|
|
@@ -5589,7 +5657,7 @@ registerSchema({
|
|
|
5589
5657
|
});
|
|
5590
5658
|
registerSchema({
|
|
5591
5659
|
command: "ads.linkedin.creatives.create",
|
|
5592
|
-
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.",
|
|
5593
5661
|
args: {
|
|
5594
5662
|
...writeAccountArgs,
|
|
5595
5663
|
campaign: { type: "string", description: "Campaign id/URN or li_temp_* ref", required: true },
|
|
@@ -5610,34 +5678,53 @@ registerSchema({
|
|
|
5610
5678
|
cta: { type: "string", description: "LEARN_MORE|REQUEST_DEMO|SIGN_UP|REGISTER|DOWNLOAD|\u2026", required: false },
|
|
5611
5679
|
"cta-label": { type: "string", description: "Spotlight CTA label (\u226418 chars)", required: false },
|
|
5612
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 },
|
|
5613
5697
|
"intended-status": { type: "string", description: "DRAFT|ACTIVE|PAUSED (default DRAFT)", required: false },
|
|
5614
|
-
title: { type: "string", description: "Media title", required: false },
|
|
5698
|
+
title: { type: "string", description: "Media title (image/video) or article title", required: false },
|
|
5615
5699
|
file: { type: "string", description: "JSON file with the full content object", required: false }
|
|
5616
5700
|
}
|
|
5617
5701
|
});
|
|
5618
5702
|
registerSchema({
|
|
5619
5703
|
command: "ads.linkedin.creatives.update",
|
|
5620
|
-
description: "Stage changes to an existing creative
|
|
5704
|
+
description: "Stage changes to an existing creative. Direct-content ads (text/spotlight/follower/jobs) update copy/media/URL IN PLACE \u2014 pass only the changed fields (headline, description, landing-url, image-id, \u2026); the ad's current content carries over. Post-based ads (image/video/carousel/\u2026) can't edit content \u2014 use creatives.duplicate with --replace for those. Passing a li_temp_* ref AMENDS the creative staged in this chat instead. Applies on chat publish.",
|
|
5621
5705
|
args: {
|
|
5622
5706
|
id: { type: "positional", description: "Creative id/URN, or li_temp_* ref staged in this chat", required: true },
|
|
5623
5707
|
...writeAccountArgs,
|
|
5624
5708
|
"intended-status": { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|DRAFT", required: false },
|
|
5625
5709
|
name: { type: "string", description: "New creative name", required: false },
|
|
5626
|
-
headline: { type: "string", description: "
|
|
5627
|
-
intro: { type: "string", description: "
|
|
5628
|
-
description: { type: "string", description: "
|
|
5710
|
+
headline: { type: "string", description: "New headline", required: false },
|
|
5711
|
+
intro: { type: "string", description: "Commentary / intro text", required: false },
|
|
5712
|
+
description: { type: "string", description: "Description (text/spotlight)", required: false },
|
|
5629
5713
|
"landing-url": { type: "string", description: "New https destination URL", required: false },
|
|
5630
5714
|
cta: { type: "string", description: "New CTA", required: false },
|
|
5631
|
-
"
|
|
5632
|
-
"image-
|
|
5633
|
-
"
|
|
5634
|
-
"
|
|
5715
|
+
"cta-label": { type: "string", description: "Spotlight custom CTA label", required: false },
|
|
5716
|
+
"image-id": { type: "string", description: "Baker image library id", required: false },
|
|
5717
|
+
"image-urn": { type: "string", description: "Existing urn:li:image:*", required: false },
|
|
5718
|
+
"video-id": { type: "string", description: "Baker video library id", required: false },
|
|
5719
|
+
"video-urn": { type: "string", description: "Existing urn:li:video:*", required: false },
|
|
5720
|
+
"logo-image-id": { type: "string", description: "Baker image id for the logo", required: false },
|
|
5635
5721
|
"thumbnail-image-id": {
|
|
5636
5722
|
type: "string",
|
|
5637
|
-
description: "
|
|
5723
|
+
description: "Baker image id for the article thumbnail",
|
|
5638
5724
|
required: false
|
|
5639
5725
|
},
|
|
5640
|
-
|
|
5726
|
+
"thumbnail-urn": { type: "string", description: "Existing urn:li:image:* thumbnail", required: false },
|
|
5727
|
+
title: { type: "string", description: "Media or article title", required: false },
|
|
5641
5728
|
file: { type: "string", description: "JSON file with fields to change; flags override file keys", required: false }
|
|
5642
5729
|
}
|
|
5643
5730
|
});
|
|
@@ -7041,8 +7128,14 @@ Examples:
|
|
|
7041
7128
|
id: { type: "positional", description: "Source creative id or URN", required: true },
|
|
7042
7129
|
...accountArgs,
|
|
7043
7130
|
campaign: { type: "string", description: "Stage the copy under a different ad set (id/URN or li_temp_*)" },
|
|
7044
|
-
"intended-status": {
|
|
7045
|
-
|
|
7131
|
+
"intended-status": {
|
|
7132
|
+
type: "string",
|
|
7133
|
+
description: "Status for the copy (default DRAFT; with --replace, the source's status)"
|
|
7134
|
+
},
|
|
7135
|
+
replace: {
|
|
7136
|
+
type: "boolean",
|
|
7137
|
+
description: "Also pause the original once the new ad publishes; the copy inherits the source's status"
|
|
7138
|
+
},
|
|
7046
7139
|
intro: { type: "string", description: "New commentary / intro text" },
|
|
7047
7140
|
headline: { type: "string", description: "New headline" },
|
|
7048
7141
|
description: { type: "string", description: "New description (text/spotlight ads)" },
|
|
@@ -7103,7 +7196,7 @@ var creativesCreateCommand = defineCommand36({
|
|
|
7103
7196
|
meta: {
|
|
7104
7197
|
name: "create",
|
|
7105
7198
|
description: `Stage a new creative (ad). ${STAGED_NOTE}
|
|
7106
|
-
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).
|
|
7107
7200
|
Examples:
|
|
7108
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
|
|
7109
7202
|
baker ads linkedin creatives create --campaign 123456 --format tla --post-urn urn:li:ugcPost:789`
|
|
@@ -7188,7 +7281,6 @@ var CREATIVE_AMEND_CONTENT_FLAGS = [
|
|
|
7188
7281
|
["thumbnail-urn", "thumbnailUrn"],
|
|
7189
7282
|
["title", "title"]
|
|
7190
7283
|
];
|
|
7191
|
-
var CREATIVE_LIVE_BLOCKED_FILE_KEYS = ["headline", "landingUrl", "cta", "content"];
|
|
7192
7284
|
function creativeContentPatch(args) {
|
|
7193
7285
|
const patch = {};
|
|
7194
7286
|
for (const [flag, key] of CREATIVE_AMEND_CONTENT_FLAGS) {
|
|
@@ -7205,30 +7297,30 @@ var creativesUpdateCommand = defineCommand36({
|
|
|
7205
7297
|
meta: {
|
|
7206
7298
|
name: "update",
|
|
7207
7299
|
description: `Stage changes to an existing creative, or amend a creative staged in this chat by passing its li_temp_* ref. ${STAGED_NOTE}
|
|
7208
|
-
|
|
7300
|
+
Direct-content ads (text/spotlight/follower/jobs) update copy/media/URL IN PLACE \u2014 changed fields merge into the ad's current content. Post-based ads (image/video/carousel/\u2026) can't edit content; use \`creatives duplicate <id> --headline "\u2026" --replace\` for those. Staged creatives (li_temp_*) accept all content flags and re-validate.
|
|
7209
7301
|
Examples:
|
|
7210
|
-
baker ads linkedin creatives update
|
|
7211
|
-
baker ads linkedin creatives update
|
|
7302
|
+
baker ads linkedin creatives update 1458423674 --headline "Sharper headline" --description "New copy."
|
|
7303
|
+
baker ads linkedin creatives update urn:li:sponsoredCreative:789 --intended-status PAUSED`
|
|
7212
7304
|
},
|
|
7213
7305
|
args: {
|
|
7214
7306
|
id: { type: "positional", description: "Creative id/URN, or li_temp_* ref staged in this chat", required: true },
|
|
7215
7307
|
...accountArgs,
|
|
7216
7308
|
"intended-status": { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|DRAFT" },
|
|
7217
7309
|
name: { type: "string", description: "New creative name" },
|
|
7218
|
-
intro: { type: "string", description: "
|
|
7310
|
+
intro: { type: "string", description: "Commentary / intro text" },
|
|
7219
7311
|
headline: { type: "string", description: "New headline" },
|
|
7220
|
-
description: { type: "string", description: "
|
|
7312
|
+
description: { type: "string", description: "Description (text/spotlight ads)" },
|
|
7221
7313
|
"landing-url": { type: "string", description: "New https destination URL" },
|
|
7222
7314
|
cta: { type: "string", description: "New CTA (LEARN_MORE|REQUEST_DEMO|\u2026)" },
|
|
7223
|
-
"cta-label": { type: "string", description: "
|
|
7224
|
-
"image-id": { type: "string", description: "
|
|
7225
|
-
"image-urn": { type: "string", description: "
|
|
7226
|
-
"video-id": { type: "string", description: "
|
|
7227
|
-
"video-urn": { type: "string", description: "
|
|
7228
|
-
"logo-image-id": { type: "string", description: "
|
|
7229
|
-
"thumbnail-image-id": { type: "string", description: "
|
|
7230
|
-
"thumbnail-urn": { type: "string", description: "
|
|
7231
|
-
title: { type: "string", description: "
|
|
7315
|
+
"cta-label": { type: "string", description: "Spotlight custom CTA label" },
|
|
7316
|
+
"image-id": { type: "string", description: "Baker image library id" },
|
|
7317
|
+
"image-urn": { type: "string", description: "Existing urn:li:image:*" },
|
|
7318
|
+
"video-id": { type: "string", description: "Baker video library id" },
|
|
7319
|
+
"video-urn": { type: "string", description: "Existing urn:li:video:*" },
|
|
7320
|
+
"logo-image-id": { type: "string", description: "Baker image id for the logo" },
|
|
7321
|
+
"thumbnail-image-id": { type: "string", description: "Baker image id for the article thumbnail" },
|
|
7322
|
+
"thumbnail-urn": { type: "string", description: "Existing urn:li:image:* thumbnail" },
|
|
7323
|
+
title: { type: "string", description: "Media or article title" },
|
|
7232
7324
|
file: { type: "string", description: "JSON file with fields to change; flags override file keys" }
|
|
7233
7325
|
},
|
|
7234
7326
|
run: async ({ args }) => {
|
|
@@ -7237,26 +7329,13 @@ Examples:
|
|
|
7237
7329
|
const file = loadJsonFileArg(args.file);
|
|
7238
7330
|
const contentPatch = creativeContentPatch(args);
|
|
7239
7331
|
const intendedStatus = args["intended-status"] ? String(args["intended-status"]).toUpperCase() : void 0;
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
await stageOp({ kind: "creative.update", accountId, target, payload: payload2 });
|
|
7248
|
-
return;
|
|
7249
|
-
}
|
|
7250
|
-
const blocked = [
|
|
7251
|
-
...Object.keys(contentPatch),
|
|
7252
|
-
...CREATIVE_LIVE_BLOCKED_FILE_KEYS.filter((key) => file[key] !== void 0)
|
|
7253
|
-
];
|
|
7254
|
-
if (blocked.length > 0) {
|
|
7255
|
-
failWriteValidation(
|
|
7256
|
-
`LinkedIn can't change ad content once created (${[...new Set(blocked)].join(", ")}) \u2014 stage a replacement instead: \`baker ads linkedin creatives duplicate ${target} --headline "\u2026" --replace\` copies this ad with your changes and pauses the original when published. (li_temp_* refs staged in this chat stay fully editable.)`
|
|
7257
|
-
);
|
|
7258
|
-
}
|
|
7259
|
-
const payload = mergePayload(file, { intendedStatus, name: args.name });
|
|
7332
|
+
const fileContent = file.content !== null && typeof file.content === "object" && !Array.isArray(file.content) ? file.content : {};
|
|
7333
|
+
const content = { ...fileContent, ...contentPatch };
|
|
7334
|
+
const payload = mergePayload(file, {
|
|
7335
|
+
intendedStatus,
|
|
7336
|
+
name: args.name,
|
|
7337
|
+
content: Object.keys(content).length > 0 ? content : void 0
|
|
7338
|
+
});
|
|
7260
7339
|
await stageOp({ kind: "creative.update", accountId, target, payload });
|
|
7261
7340
|
}
|
|
7262
7341
|
});
|