@koda-sl/baker-cli 0.107.0-dev.aaab11a02 → 0.108.0-dev.d66dc8b3d
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 +4 -0
- package/dist/cli.js +120 -28
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1006,6 +1006,10 @@ 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 — LinkedIn makes ad content immutable, so this is ONE command:
|
|
1010
|
+
# duplicate with overrides (unchanged fields carry over) + --replace pauses the original once the copy publishes
|
|
1011
|
+
baker ads linkedin creatives duplicate 1458413484 --headline "New headline" --description "New copy." --replace
|
|
1012
|
+
|
|
1009
1013
|
# Amend staged ops in place — `update <li_temp_ref>` merges into the staged create and re-validates
|
|
1010
1014
|
baker ads linkedin creatives update li_temp_x3 --headline "Sharper headline" --image-id <bakerImageId>
|
|
1011
1015
|
baker ads linkedin campaigns update li_temp_x2 --daily-budget 100 --currency EUR
|
package/dist/cli.js
CHANGED
|
@@ -1056,7 +1056,8 @@ var parentRefSchema = z3.union([
|
|
|
1056
1056
|
]);
|
|
1057
1057
|
var moneySchema = z3.object({
|
|
1058
1058
|
amount: z3.string().regex(/^\d+(\.\d{1,2})?$/, "expected a decimal amount like 50 or 50.00").refine((val) => Number(val) > 0, "amount must be greater than zero"),
|
|
1059
|
-
|
|
1059
|
+
/** LinkedIn ad accounts are single-currency — when omitted, staging fills it from the account. */
|
|
1060
|
+
currencyCode: z3.string().length(3).optional()
|
|
1060
1061
|
});
|
|
1061
1062
|
var runScheduleSchema = z3.object({
|
|
1062
1063
|
start: z3.number().int().positive(),
|
|
@@ -1125,7 +1126,7 @@ function validateCampaignBudgets(p, ctx, { requireBudget }) {
|
|
|
1125
1126
|
message: "totalBudget-only campaigns need runSchedule.end"
|
|
1126
1127
|
});
|
|
1127
1128
|
}
|
|
1128
|
-
if (p.dailyBudget) {
|
|
1129
|
+
if (p.dailyBudget?.currencyCode) {
|
|
1129
1130
|
const min = currencyMinimums(p.dailyBudget.currencyCode).dailyBudgetMin;
|
|
1130
1131
|
if (Number(p.dailyBudget.amount) < min) {
|
|
1131
1132
|
ctx.addIssue({
|
|
@@ -1135,7 +1136,7 @@ function validateCampaignBudgets(p, ctx, { requireBudget }) {
|
|
|
1135
1136
|
});
|
|
1136
1137
|
}
|
|
1137
1138
|
}
|
|
1138
|
-
if (p.unitCost) {
|
|
1139
|
+
if (p.unitCost?.currencyCode) {
|
|
1139
1140
|
const min = currencyMinimums(p.unitCost.currencyCode).unitCostMin;
|
|
1140
1141
|
if (Number(p.unitCost.amount) < min) {
|
|
1141
1142
|
ctx.addIssue({
|
|
@@ -5413,7 +5414,11 @@ registerSchema({
|
|
|
5413
5414
|
start: { type: "string", description: "Run schedule start (ISO date or epoch ms)", required: true },
|
|
5414
5415
|
end: { type: "string", description: "Run schedule end", required: false },
|
|
5415
5416
|
"total-budget": { type: "string", description: "Lifetime budget amount", required: false },
|
|
5416
|
-
currency: {
|
|
5417
|
+
currency: {
|
|
5418
|
+
type: "string",
|
|
5419
|
+
description: "3-letter currency code (defaults to the ad account currency)",
|
|
5420
|
+
required: false
|
|
5421
|
+
},
|
|
5417
5422
|
status: { type: "string", description: "DRAFT|ACTIVE|PAUSED (default DRAFT)", required: false },
|
|
5418
5423
|
file: { type: "string", description: "JSON payload file; flags override file keys", required: false }
|
|
5419
5424
|
}
|
|
@@ -5432,7 +5437,11 @@ registerSchema({
|
|
|
5432
5437
|
start: { type: "string", description: "New schedule start", required: false },
|
|
5433
5438
|
end: { type: "string", description: "New schedule end", required: false },
|
|
5434
5439
|
"total-budget": { type: "string", description: "New lifetime budget", required: false },
|
|
5435
|
-
currency: {
|
|
5440
|
+
currency: {
|
|
5441
|
+
type: "string",
|
|
5442
|
+
description: "3-letter currency code (defaults to the ad account currency)",
|
|
5443
|
+
required: false
|
|
5444
|
+
},
|
|
5436
5445
|
status: { type: "string", description: "ACTIVE|PAUSED|ARCHIVED", required: false },
|
|
5437
5446
|
file: { type: "string", description: "JSON file with fields to change", required: false }
|
|
5438
5447
|
}
|
|
@@ -5450,7 +5459,11 @@ registerSchema({
|
|
|
5450
5459
|
bid: { type: "string", description: "Manual bid amount (unitCost)", required: false },
|
|
5451
5460
|
"daily-budget": { type: "string", description: "Daily budget amount", required: false },
|
|
5452
5461
|
"total-budget": { type: "string", description: "Lifetime budget amount", required: false },
|
|
5453
|
-
currency: {
|
|
5462
|
+
currency: {
|
|
5463
|
+
type: "string",
|
|
5464
|
+
description: "3-letter currency code (defaults to the ad account currency)",
|
|
5465
|
+
required: false
|
|
5466
|
+
},
|
|
5454
5467
|
start: { type: "string", description: "Run schedule start", required: false },
|
|
5455
5468
|
end: { type: "string", description: "Run schedule end", required: false },
|
|
5456
5469
|
locale: { type: "string", description: "Profile locale like en_US (default en_US)", required: false },
|
|
@@ -5479,7 +5492,11 @@ registerSchema({
|
|
|
5479
5492
|
bid: { type: "string", description: "New manual bid", required: false },
|
|
5480
5493
|
"daily-budget": { type: "string", description: "New daily budget", required: false },
|
|
5481
5494
|
"total-budget": { type: "string", description: "New lifetime budget", required: false },
|
|
5482
|
-
currency: {
|
|
5495
|
+
currency: {
|
|
5496
|
+
type: "string",
|
|
5497
|
+
description: "3-letter currency code (defaults to the ad account currency)",
|
|
5498
|
+
required: false
|
|
5499
|
+
},
|
|
5483
5500
|
start: { type: "string", description: "New schedule start", required: false },
|
|
5484
5501
|
end: { type: "string", description: "New schedule end", required: false },
|
|
5485
5502
|
"targeting-file": { type: "string", description: "JSON file with replacement targetingCriteria", required: false },
|
|
@@ -5548,8 +5565,27 @@ registerSchema({
|
|
|
5548
5565
|
});
|
|
5549
5566
|
registerSchema({
|
|
5550
5567
|
command: "ads.linkedin.creatives.duplicate",
|
|
5551
|
-
description: "Stage a copy of an existing
|
|
5552
|
-
args:
|
|
5568
|
+
description: "Stage a copy of an existing ad, optionally with new content \u2014 the ONLY way to change copy/media/URL on a live ad (LinkedIn makes ad content immutable after creation). Direct-content ads (text/spotlight/follower/jobs) copy field-by-field; post-based ads re-sponsor the same post. --replace also stages pausing the original, gated on the new ad publishing successfully.",
|
|
5569
|
+
args: {
|
|
5570
|
+
id: { type: "positional", description: "Source creative id or URN", required: true },
|
|
5571
|
+
...writeAccountArgs,
|
|
5572
|
+
campaign: { type: "string", description: "Stage the copy under a different ad set", required: false },
|
|
5573
|
+
"intended-status": { type: "string", description: "Status for the copy (default DRAFT)", required: false },
|
|
5574
|
+
replace: { type: "boolean", description: "Also pause the original once the new ad publishes", required: false },
|
|
5575
|
+
headline: { type: "string", description: "New headline", required: false },
|
|
5576
|
+
description: { type: "string", description: "New description (text/spotlight)", required: false },
|
|
5577
|
+
intro: { type: "string", description: "New commentary / intro text", required: false },
|
|
5578
|
+
"landing-url": { type: "string", description: "New https destination URL", required: false },
|
|
5579
|
+
cta: { type: "string", description: "New CTA", required: false },
|
|
5580
|
+
"cta-label": { type: "string", description: "Spotlight custom CTA label", required: false },
|
|
5581
|
+
"image-id": { type: "string", description: "Baker image library id", required: false },
|
|
5582
|
+
"image-urn": { type: "string", description: "Existing urn:li:image:*", required: false },
|
|
5583
|
+
"video-id": { type: "string", description: "Baker video library id", required: false },
|
|
5584
|
+
"video-urn": { type: "string", description: "Existing urn:li:video:*", required: false },
|
|
5585
|
+
"logo-image-id": { type: "string", description: "Baker image id for the logo", required: false },
|
|
5586
|
+
title: { type: "string", description: "Media title", required: false },
|
|
5587
|
+
file: { type: "string", description: "JSON file with field overrides for the copy", required: false }
|
|
5588
|
+
}
|
|
5553
5589
|
});
|
|
5554
5590
|
registerSchema({
|
|
5555
5591
|
command: "ads.linkedin.creatives.create",
|
|
@@ -5581,13 +5617,13 @@ registerSchema({
|
|
|
5581
5617
|
});
|
|
5582
5618
|
registerSchema({
|
|
5583
5619
|
command: "ads.linkedin.creatives.update",
|
|
5584
|
-
description: "Stage changes to an existing creative (intendedStatus, name
|
|
5620
|
+
description: "Stage changes to an existing creative (intendedStatus, name only \u2014 LinkedIn makes ad content immutable; to change copy/media/URL use creatives.duplicate with --replace), or AMEND a creative staged in this chat by passing its li_temp_* ref \u2014 amends accept the content flags from creatives.create (headline, intro, description, image-id, landing-url, cta, \u2026) and merge into the staged ad, re-validating the result. Use amends to apply feedback to staged ads instead of remove + re-create. Applies on chat publish.",
|
|
5585
5621
|
args: {
|
|
5586
5622
|
id: { type: "positional", description: "Creative id/URN, or li_temp_* ref staged in this chat", required: true },
|
|
5587
5623
|
...writeAccountArgs,
|
|
5588
5624
|
"intended-status": { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|DRAFT", required: false },
|
|
5589
5625
|
name: { type: "string", description: "New creative name", required: false },
|
|
5590
|
-
headline: { type: "string", description: "
|
|
5626
|
+
headline: { type: "string", description: "Amend only: new headline", required: false },
|
|
5591
5627
|
intro: { type: "string", description: "Amend only: commentary / intro text", required: false },
|
|
5592
5628
|
description: { type: "string", description: "Amend only: description (text/spotlight)", required: false },
|
|
5593
5629
|
"landing-url": { type: "string", description: "New https destination URL", required: false },
|
|
@@ -6606,8 +6642,11 @@ function parseMoneyFlag(amount, currency, flag) {
|
|
|
6606
6642
|
if (amount === void 0 || amount === null || amount === "") {
|
|
6607
6643
|
return void 0;
|
|
6608
6644
|
}
|
|
6645
|
+
if (currency === void 0 || currency === null || currency === "") {
|
|
6646
|
+
return { amount: String(amount) };
|
|
6647
|
+
}
|
|
6609
6648
|
if (typeof currency !== "string" || currency.length !== 3) {
|
|
6610
|
-
failWriteValidation(`${flag}
|
|
6649
|
+
failWriteValidation(`${flag} got an invalid --currency (expected a 3-letter code like EUR)`);
|
|
6611
6650
|
}
|
|
6612
6651
|
return { amount: String(amount), currencyCode: currency.toUpperCase() };
|
|
6613
6652
|
}
|
|
@@ -6787,7 +6826,7 @@ Example: baker ads linkedin campaign-groups create --name "Q3 ABM" --start 2026-
|
|
|
6787
6826
|
start: { type: "string", description: "Run schedule start (ISO date or epoch ms)" },
|
|
6788
6827
|
end: { type: "string", description: "Run schedule end" },
|
|
6789
6828
|
"total-budget": { type: "string", description: "Lifetime budget amount" },
|
|
6790
|
-
currency: { type: "string", description: "3-letter currency code (
|
|
6829
|
+
currency: { type: "string", description: "3-letter currency code (defaults to the ad account currency)" },
|
|
6791
6830
|
status: { type: "string", description: "DRAFT|ACTIVE|PAUSED (default DRAFT)" },
|
|
6792
6831
|
file: { type: "string", description: "JSON file with the full payload; flags override file keys" }
|
|
6793
6832
|
},
|
|
@@ -6816,7 +6855,7 @@ Example: baker ads linkedin campaign-groups update 635137195 --total-budget 8000
|
|
|
6816
6855
|
start: { type: "string", description: "New run schedule start" },
|
|
6817
6856
|
end: { type: "string", description: "New run schedule end" },
|
|
6818
6857
|
"total-budget": { type: "string", description: "New lifetime budget" },
|
|
6819
|
-
currency: { type: "string", description: "3-letter currency code" },
|
|
6858
|
+
currency: { type: "string", description: "3-letter currency code (defaults to the ad account currency)" },
|
|
6820
6859
|
status: { type: "string", description: "ACTIVE|PAUSED|ARCHIVED|\u2026" },
|
|
6821
6860
|
file: { type: "string", description: "JSON file with fields to change" }
|
|
6822
6861
|
},
|
|
@@ -6867,7 +6906,7 @@ var campaignFlagArgs = {
|
|
|
6867
6906
|
bid: { type: "string", description: "Manual bid (unitCost) \u2014 playbook mandates manual bidding" },
|
|
6868
6907
|
"daily-budget": { type: "string", description: "Daily budget amount" },
|
|
6869
6908
|
"total-budget": { type: "string", description: "Lifetime budget amount" },
|
|
6870
|
-
currency: { type: "string", description: "3-letter currency code (
|
|
6909
|
+
currency: { type: "string", description: "3-letter currency code (defaults to the ad account currency)" },
|
|
6871
6910
|
start: { type: "string", description: "Run schedule start (ISO date or epoch ms)" },
|
|
6872
6911
|
end: { type: "string", description: "Run schedule end" },
|
|
6873
6912
|
locale: { type: "string", description: "Profile locale like en_US" },
|
|
@@ -6989,7 +7028,63 @@ Example: baker ads linkedin ${cliGroup} duplicate 123456 --name "New variant"`
|
|
|
6989
7028
|
}
|
|
6990
7029
|
var campaignsDuplicateCommand = duplicateCommand("campaign", "campaigns");
|
|
6991
7030
|
var campaignGroupsDuplicateCommand = duplicateCommand("campaignGroup", "campaign-groups");
|
|
6992
|
-
var creativesDuplicateCommand =
|
|
7031
|
+
var creativesDuplicateCommand = defineCommand36({
|
|
7032
|
+
meta: {
|
|
7033
|
+
name: "duplicate",
|
|
7034
|
+
description: `Stage a copy of an existing ad, optionally with new content \u2014 the ONLY way to change copy/media/URL on a live ad (LinkedIn makes ad content immutable). ${STAGED_NOTE}
|
|
7035
|
+
--replace also pauses the original once the new ad publishes (skipped if the new ad fails).
|
|
7036
|
+
Examples:
|
|
7037
|
+
baker ads linkedin creatives duplicate 1458413484 --headline "10x Demos, Same Team" --description "AI handles every demo request." --replace
|
|
7038
|
+
baker ads linkedin creatives duplicate urn:li:sponsoredCreative:789 --campaign 799305464`
|
|
7039
|
+
},
|
|
7040
|
+
args: {
|
|
7041
|
+
id: { type: "positional", description: "Source creative id or URN", required: true },
|
|
7042
|
+
...accountArgs,
|
|
7043
|
+
campaign: { type: "string", description: "Stage the copy under a different ad set (id/URN or li_temp_*)" },
|
|
7044
|
+
"intended-status": { type: "string", description: "Status for the copy (default DRAFT)" },
|
|
7045
|
+
replace: { type: "boolean", description: "Also pause the original once the new ad publishes" },
|
|
7046
|
+
intro: { type: "string", description: "New commentary / intro text" },
|
|
7047
|
+
headline: { type: "string", description: "New headline" },
|
|
7048
|
+
description: { type: "string", description: "New description (text/spotlight ads)" },
|
|
7049
|
+
"landing-url": { type: "string", description: "New https destination URL" },
|
|
7050
|
+
cta: { type: "string", description: "New CTA (LEARN_MORE|REQUEST_DEMO|\u2026)" },
|
|
7051
|
+
"cta-label": { type: "string", description: "Spotlight custom CTA label" },
|
|
7052
|
+
"image-id": { type: "string", description: "Baker image library id" },
|
|
7053
|
+
"image-urn": { type: "string", description: "Existing urn:li:image:*" },
|
|
7054
|
+
"video-id": { type: "string", description: "Baker video library id" },
|
|
7055
|
+
"video-urn": { type: "string", description: "Existing urn:li:video:*" },
|
|
7056
|
+
"logo-image-id": { type: "string", description: "Baker image id for the logo" },
|
|
7057
|
+
title: { type: "string", description: "Media title" },
|
|
7058
|
+
file: { type: "string", description: "JSON file with field overrides for the copy" }
|
|
7059
|
+
},
|
|
7060
|
+
run: async ({ args }) => {
|
|
7061
|
+
try {
|
|
7062
|
+
const chatId = requireChatId();
|
|
7063
|
+
const file = loadJsonFileArg(args.file);
|
|
7064
|
+
const fileContent = file.content !== null && typeof file.content === "object" && !Array.isArray(file.content) ? file.content : {};
|
|
7065
|
+
const content = { ...fileContent, ...creativeContentPatch(args) };
|
|
7066
|
+
const overrides = mergePayload(file, {
|
|
7067
|
+
campaign: args.campaign,
|
|
7068
|
+
intendedStatus: args["intended-status"] ? String(args["intended-status"]).toUpperCase() : void 0,
|
|
7069
|
+
content: Object.keys(content).length > 0 ? content : void 0
|
|
7070
|
+
});
|
|
7071
|
+
const response = await apiPost(
|
|
7072
|
+
"/api/ads/linkedin/draft/duplicate",
|
|
7073
|
+
{
|
|
7074
|
+
chatId,
|
|
7075
|
+
accountId: bareAccountId(args),
|
|
7076
|
+
entity: "creative",
|
|
7077
|
+
sourceId: requireTarget(args, "creative"),
|
|
7078
|
+
...Object.keys(overrides).length > 0 ? { overrides } : {},
|
|
7079
|
+
...args.replace ? { replace: true } : {}
|
|
7080
|
+
}
|
|
7081
|
+
);
|
|
7082
|
+
writeJsonEnvelope(response);
|
|
7083
|
+
} catch (err) {
|
|
7084
|
+
handleLinkedinError(err);
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
7087
|
+
});
|
|
6993
7088
|
var CREATIVE_FORMATS = [
|
|
6994
7089
|
"image",
|
|
6995
7090
|
"video",
|
|
@@ -7093,7 +7188,7 @@ var CREATIVE_AMEND_CONTENT_FLAGS = [
|
|
|
7093
7188
|
["thumbnail-urn", "thumbnailUrn"],
|
|
7094
7189
|
["title", "title"]
|
|
7095
7190
|
];
|
|
7096
|
-
var
|
|
7191
|
+
var CREATIVE_LIVE_BLOCKED_FILE_KEYS = ["headline", "landingUrl", "cta", "content"];
|
|
7097
7192
|
function creativeContentPatch(args) {
|
|
7098
7193
|
const patch = {};
|
|
7099
7194
|
for (const [flag, key] of CREATIVE_AMEND_CONTENT_FLAGS) {
|
|
@@ -7110,7 +7205,7 @@ var creativesUpdateCommand = defineCommand36({
|
|
|
7110
7205
|
meta: {
|
|
7111
7206
|
name: "update",
|
|
7112
7207
|
description: `Stage changes to an existing creative, or amend a creative staged in this chat by passing its li_temp_* ref. ${STAGED_NOTE}
|
|
7113
|
-
Staged creatives (li_temp_*) accept the content flags from \`creatives create\` \u2014 fields merge into the staged ad and re-validate. Creatives already on LinkedIn only accept --intended-status/--name
|
|
7208
|
+
Staged creatives (li_temp_*) accept the content flags from \`creatives create\` \u2014 fields merge into the staged ad and re-validate. Creatives already on LinkedIn only accept --intended-status/--name (LinkedIn makes ad content immutable \u2014 to change copy/media/URL use \`creatives duplicate <id> --headline "\u2026" --replace\`).
|
|
7114
7209
|
Examples:
|
|
7115
7210
|
baker ads linkedin creatives update urn:li:sponsoredCreative:789 --intended-status PAUSED
|
|
7116
7211
|
baker ads linkedin creatives update li_temp_a1b2 --headline "Sharper headline" --image-id <bakerImageId>`
|
|
@@ -7152,19 +7247,16 @@ Examples:
|
|
|
7152
7247
|
await stageOp({ kind: "creative.update", accountId, target, payload: payload2 });
|
|
7153
7248
|
return;
|
|
7154
7249
|
}
|
|
7155
|
-
const
|
|
7156
|
-
|
|
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) {
|
|
7157
7255
|
failWriteValidation(
|
|
7158
|
-
`
|
|
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.)`
|
|
7159
7257
|
);
|
|
7160
7258
|
}
|
|
7161
|
-
const payload = mergePayload(file, {
|
|
7162
|
-
intendedStatus,
|
|
7163
|
-
name: args.name,
|
|
7164
|
-
headline: contentPatch.headline,
|
|
7165
|
-
landingUrl: contentPatch.landingUrl,
|
|
7166
|
-
cta: contentPatch.cta
|
|
7167
|
-
});
|
|
7259
|
+
const payload = mergePayload(file, { intendedStatus, name: args.name });
|
|
7168
7260
|
await stageOp({ kind: "creative.update", accountId, target, payload });
|
|
7169
7261
|
}
|
|
7170
7262
|
});
|