@adsuploader/cli 0.1.6 → 0.1.8
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 +1 -0
- package/SKILL.md +65 -1
- package/dist/cli.cjs +103 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ This package includes a `SKILL.md` file that describes every command and option
|
|
|
42
42
|
| `ads adset <id>` | Show ads in an ad set |
|
|
43
43
|
| `ads ad <id>` | Ad details and creative |
|
|
44
44
|
| `ads presets` | List saved presets |
|
|
45
|
+
| `ads presets:save --from-ad <id> --name <name>` | Save an existing ad as an API preset |
|
|
45
46
|
| `ads upload <files...>` | Upload images and videos |
|
|
46
47
|
| `ads uploads` | List recent upload batches |
|
|
47
48
|
| `ads create spec.json` | Create ads from spec |
|
package/SKILL.md
CHANGED
|
@@ -84,6 +84,7 @@ Ads are created **ACTIVE** by default. Use `--status PAUSED` to create them paus
|
|
|
84
84
|
| `adset <adSetId>` | Show ads in an ad set |
|
|
85
85
|
| `ad <adId>` | Ad details + creative config |
|
|
86
86
|
| `presets` | List saved API presets (or `presets <id>` for details) |
|
|
87
|
+
| `presets:save --from-ad <adId> --name <name>` | Save an existing ad as an API preset |
|
|
87
88
|
| `text-presets` | List saved text presets (or `text-presets <id>` for details) |
|
|
88
89
|
| `uploads` | List recent upload batches (or `uploads <batchId>` for details) |
|
|
89
90
|
|
|
@@ -115,6 +116,12 @@ Ads are created **ACTIVE** by default. Use `--status PAUSED` to create them paus
|
|
|
115
116
|
| `--daily-budget <amount>` | Override daily budget (currency units) |
|
|
116
117
|
| `--bid-amount <amount>` | Override bid/cost cap (currency units) |
|
|
117
118
|
| `--text-file <path>` | Load text config from a JSON file |
|
|
119
|
+
| `--expanded` | Show full headline, primary text, and description values in previews |
|
|
120
|
+
|
|
121
|
+
### Detail Flags
|
|
122
|
+
| Flag | Description |
|
|
123
|
+
|------|-------------|
|
|
124
|
+
| `ad --expanded` | Show full headline, primary text, and description values |
|
|
118
125
|
|
|
119
126
|
### Common Flags
|
|
120
127
|
| Flag | Description |
|
|
@@ -366,7 +373,7 @@ When cherry-picking features, only list ones relevant to the media type. `video_
|
|
|
366
373
|
|
|
367
374
|
### Carousel Ads
|
|
368
375
|
|
|
369
|
-
Group uploaded files into carousel ads with per-card text
|
|
376
|
+
Group uploaded files into carousel ads with per-card text and optional overall carousel text. `cardTexts` controls individual cards. Overall carousel text can be colocated on the carousel object or set in `texts.perAd` using the carousel `name`; colocated fields win when both are present.
|
|
370
377
|
|
|
371
378
|
```json
|
|
372
379
|
{
|
|
@@ -374,6 +381,12 @@ Group uploaded files into carousel ads with per-card text:
|
|
|
374
381
|
{
|
|
375
382
|
"name": "My Carousel",
|
|
376
383
|
"cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"],
|
|
384
|
+
"headlines": ["Overall Carousel Headline"],
|
|
385
|
+
"bodies": ["Overall primary text"],
|
|
386
|
+
"descriptions": ["Overall description"],
|
|
387
|
+
"cta": "SHOP_NOW",
|
|
388
|
+
"link": "https://example.com/carousel",
|
|
389
|
+
"urlTags": "utm_content=my_carousel",
|
|
377
390
|
"cardTexts": [
|
|
378
391
|
{ "headline": "Slide 1", "description": "First card", "link": "https://example.com/1" },
|
|
379
392
|
{ "headline": "Slide 2", "description": "Second card", "link": "https://example.com/2" }
|
|
@@ -383,6 +396,31 @@ Group uploaded files into carousel ads with per-card text:
|
|
|
383
396
|
}
|
|
384
397
|
```
|
|
385
398
|
|
|
399
|
+
Alternative overall-text shape:
|
|
400
|
+
|
|
401
|
+
```json
|
|
402
|
+
{
|
|
403
|
+
"texts": {
|
|
404
|
+
"perAd": {
|
|
405
|
+
"My Carousel": {
|
|
406
|
+
"headlines": ["Overall Carousel Headline"],
|
|
407
|
+
"bodies": ["Overall primary text"],
|
|
408
|
+
"descriptions": ["Overall description"],
|
|
409
|
+
"cta": "SHOP_NOW",
|
|
410
|
+
"link": "https://example.com/carousel",
|
|
411
|
+
"urlTags": "utm_content=my_carousel"
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
"carousel": [
|
|
416
|
+
{
|
|
417
|
+
"name": "My Carousel",
|
|
418
|
+
"cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"]
|
|
419
|
+
}
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
386
424
|
Cards must reference filenames from the upload batch. Minimum 2 cards per carousel. Files claimed by a carousel are removed from the standard ad list.
|
|
387
425
|
|
|
388
426
|
### Flexible Ads
|
|
@@ -402,6 +440,32 @@ Group multiple assets into a single flexible ad (Meta picks the best asset per p
|
|
|
402
440
|
|
|
403
441
|
Minimum 2 assets per group. Files claimed by a flexible group are removed from the standard ad list.
|
|
404
442
|
|
|
443
|
+
### Multi Media Ads
|
|
444
|
+
|
|
445
|
+
Group 2-10 uploaded images or videos into a Meta Multi Media ad:
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"multimedia": [
|
|
450
|
+
{
|
|
451
|
+
"name": "Mixed Media Ad",
|
|
452
|
+
"assets": ["hero.jpg", "promo.mp4", "banner.jpg"],
|
|
453
|
+
"assetTexts": [
|
|
454
|
+
{
|
|
455
|
+
"headline": "Hero headline",
|
|
456
|
+
"primaryText": "Hero primary text",
|
|
457
|
+
"description": "Hero description",
|
|
458
|
+
"link": "https://example.com/hero",
|
|
459
|
+
"displayUrl": "example.com/hero"
|
|
460
|
+
}
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Assets must reference filenames from the upload batch. `assetTexts` is optional and lines up with `assets` by index; each field is a single override for that asset and blank fields fall back to the ad's main text and URLs. The primary rendered asset uses the ad's main text and URL, and any primary asset text override becomes the first main text option. Videos need a cached public thumbnail URL from upload processing. Files claimed by a Multi Media group are removed from the standard ad list.
|
|
468
|
+
|
|
405
469
|
### Ad Naming
|
|
406
470
|
|
|
407
471
|
```json
|
package/dist/cli.cjs
CHANGED
|
@@ -4210,7 +4210,8 @@ function createClient(opts = {}) {
|
|
|
4210
4210
|
},
|
|
4211
4211
|
presets: {
|
|
4212
4212
|
list: (query) => request("GET", "/presets", { query }),
|
|
4213
|
-
get: (id) => request("GET", `/presets/${id}`)
|
|
4213
|
+
get: (id) => request("GET", `/presets/${id}`),
|
|
4214
|
+
create: (body) => request("POST", "/presets", { body })
|
|
4214
4215
|
},
|
|
4215
4216
|
uploads: {
|
|
4216
4217
|
list: (query) => request("GET", "/uploads", { query }),
|
|
@@ -4706,12 +4707,12 @@ function adsetCommand() {
|
|
|
4706
4707
|
|
|
4707
4708
|
// src/commands/ad.js
|
|
4708
4709
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
4709
|
-
function truncate(str, max = 120) {
|
|
4710
|
-
if (!str || str.length <= max) return str;
|
|
4710
|
+
function truncate(str, max = 120, expanded = false) {
|
|
4711
|
+
if (!str || expanded || str.length <= max) return str;
|
|
4711
4712
|
return str.slice(0, max) + "...";
|
|
4712
4713
|
}
|
|
4713
4714
|
function adCommand() {
|
|
4714
|
-
return new Command("ad").description("View ad details and creative").argument("<id>", "Ad ID").option("--account <id>", "Ad account ID").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
4715
|
+
return new Command("ad").description("View ad details and creative").argument("<id>", "Ad ID").option("--account <id>", "Ad account ID").option("--expanded", "Show full headline / primary text / description without truncation").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
4715
4716
|
const client = createClient({ accountId: opts.account });
|
|
4716
4717
|
const { ad } = await client.ads.get(id);
|
|
4717
4718
|
if (shouldOutputJson(opts)) {
|
|
@@ -4727,14 +4728,15 @@ function adCommand() {
|
|
|
4727
4728
|
console.log(` ${import_picocolors7.default.bold("Ad Set:")} ${ad.adSetId}`);
|
|
4728
4729
|
if (ad.creative) {
|
|
4729
4730
|
const label = (name) => import_picocolors7.default.bold(name.padEnd(15));
|
|
4731
|
+
const exp = !!opts.expanded;
|
|
4730
4732
|
console.log("");
|
|
4731
4733
|
console.log(` ${import_picocolors7.default.bold("Creative")}`);
|
|
4732
|
-
if (ad.creative.headline) console.log(` ${label("Headline:")}${truncate(ad.creative.headline)}`);
|
|
4733
|
-
if (ad.creative.headlines) console.log(` ${label("Headlines:")}${ad.creative.headlines.map((h2) => truncate(h2, 60)).join(" | ")}`);
|
|
4734
|
-
if (ad.creative.primaryText) console.log(` ${label("Primary Text:")}${truncate(ad.creative.primaryText)}`);
|
|
4735
|
-
if (ad.creative.primaryTexts) console.log(` ${label("Primary Texts:")}${ad.creative.primaryTexts.map((t) => truncate(t, 60)).join(" | ")}`);
|
|
4736
|
-
if (ad.creative.description) console.log(` ${label("Description:")}${truncate(ad.creative.description)}`);
|
|
4737
|
-
if (ad.creative.descriptions) console.log(` ${label("Descriptions:")}${ad.creative.descriptions.map((d3) => truncate(d3, 60)).join(" | ")}`);
|
|
4734
|
+
if (ad.creative.headline) console.log(` ${label("Headline:")}${truncate(ad.creative.headline, 120, exp)}`);
|
|
4735
|
+
if (ad.creative.headlines) console.log(` ${label("Headlines:")}${ad.creative.headlines.map((h2) => truncate(h2, 60, exp)).join(" | ")}`);
|
|
4736
|
+
if (ad.creative.primaryText) console.log(` ${label("Primary Text:")}${truncate(ad.creative.primaryText, 120, exp)}`);
|
|
4737
|
+
if (ad.creative.primaryTexts) console.log(` ${label("Primary Texts:")}${ad.creative.primaryTexts.map((t) => truncate(t, 60, exp)).join(" | ")}`);
|
|
4738
|
+
if (ad.creative.description) console.log(` ${label("Description:")}${truncate(ad.creative.description, 120, exp)}`);
|
|
4739
|
+
if (ad.creative.descriptions) console.log(` ${label("Descriptions:")}${ad.creative.descriptions.map((d3) => truncate(d3, 60, exp)).join(" | ")}`);
|
|
4738
4740
|
if (ad.creative.cta) console.log(` ${label("CTA:")}${ad.creative.cta}`);
|
|
4739
4741
|
if (ad.creative.link) console.log(` ${label("Link:")}${ad.creative.link}`);
|
|
4740
4742
|
if (ad.creative.displayUrl) console.log(` ${label("Display URL:")}${ad.creative.displayUrl}`);
|
|
@@ -4833,6 +4835,50 @@ function textPresetsCommand() {
|
|
|
4833
4835
|
console.log("");
|
|
4834
4836
|
});
|
|
4835
4837
|
}
|
|
4838
|
+
function presetsSaveCommand() {
|
|
4839
|
+
return new Command("presets:save").description("Save an existing ad as a reusable API preset").option("--account <id>", "Ad account ID").option("--from-ad <adId>", "Ad ID to save settings from (required)").option("--name <name>", "Preset name (prompted if omitted)").option("--share", "Share with team (if you are on a team plan)").option("--json", "Output as JSON").action(async (opts) => {
|
|
4840
|
+
const client = createClient({ accountId: opts.account });
|
|
4841
|
+
if (!opts.fromAd) {
|
|
4842
|
+
printError("--from-ad <adId> is required.");
|
|
4843
|
+
process.exit(1);
|
|
4844
|
+
}
|
|
4845
|
+
let name = opts.name;
|
|
4846
|
+
if (!name && isInteractive()) {
|
|
4847
|
+
name = await he({
|
|
4848
|
+
message: "Preset name",
|
|
4849
|
+
placeholder: "e.g. Spring 2026 - Standard Purchase",
|
|
4850
|
+
validate: (v2) => v2?.trim() ? void 0 : "Name is required"
|
|
4851
|
+
});
|
|
4852
|
+
if (pD(name)) process.exit(0);
|
|
4853
|
+
}
|
|
4854
|
+
if (!name?.trim()) {
|
|
4855
|
+
printError("--name <name> is required (or run in an interactive TTY to be prompted).");
|
|
4856
|
+
process.exit(1);
|
|
4857
|
+
}
|
|
4858
|
+
try {
|
|
4859
|
+
const { preset } = await client.presets.create({
|
|
4860
|
+
type: "api",
|
|
4861
|
+
fromAdId: opts.fromAd,
|
|
4862
|
+
name: name.trim(),
|
|
4863
|
+
shareWithTeam: !!opts.share
|
|
4864
|
+
});
|
|
4865
|
+
if (shouldOutputJson(opts)) {
|
|
4866
|
+
printJson(preset);
|
|
4867
|
+
return;
|
|
4868
|
+
}
|
|
4869
|
+
printSuccess(`Saved preset "${preset.name}"`);
|
|
4870
|
+
console.log(` ${import_picocolors8.default.bold("ID:")} ${preset.id}`);
|
|
4871
|
+
console.log(` ${import_picocolors8.default.bold("Type:")} ${preset.type}`);
|
|
4872
|
+
if (preset.shared) console.log(` ${import_picocolors8.default.bold("Shared:")} ${import_picocolors8.default.cyan("team")}`);
|
|
4873
|
+
console.log(`
|
|
4874
|
+
${import_picocolors8.default.dim("Use it:")} ads create --preset ${preset.id} ...
|
|
4875
|
+
`);
|
|
4876
|
+
} catch (err) {
|
|
4877
|
+
printError(err.message || "Failed to save preset");
|
|
4878
|
+
process.exit(1);
|
|
4879
|
+
}
|
|
4880
|
+
});
|
|
4881
|
+
}
|
|
4836
4882
|
|
|
4837
4883
|
// src/commands/upload.js
|
|
4838
4884
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
@@ -5002,6 +5048,7 @@ async function stageFiles(paths, opts) {
|
|
|
5002
5048
|
uploadedFiles.push({
|
|
5003
5049
|
name: file.name,
|
|
5004
5050
|
type: file.type,
|
|
5051
|
+
size: file.size,
|
|
5005
5052
|
fileKey: r2Info.fileKey
|
|
5006
5053
|
});
|
|
5007
5054
|
} catch (err) {
|
|
@@ -5030,6 +5077,7 @@ async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
|
5030
5077
|
const result = await client.uploads.process(batchId, {
|
|
5031
5078
|
name: file.name,
|
|
5032
5079
|
type: file.type,
|
|
5080
|
+
size: file.size,
|
|
5033
5081
|
fileKey: file.fileKey
|
|
5034
5082
|
});
|
|
5035
5083
|
results.push(result);
|
|
@@ -5331,7 +5379,7 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5331
5379
|
statusStr += import_picocolors11.default.dim(` (${levelLabel})`);
|
|
5332
5380
|
}
|
|
5333
5381
|
let enhLabel = "";
|
|
5334
|
-
if (result.enhancements === "metaDefaults") enhLabel = import_picocolors11.default.dim("
|
|
5382
|
+
if (result.enhancements === "metaDefaults") enhLabel = import_picocolors11.default.dim("All Off");
|
|
5335
5383
|
else if (result.enhancements === "all") enhLabel = import_picocolors11.default.dim("All On");
|
|
5336
5384
|
else if (result.enhancements === "none") enhLabel = import_picocolors11.default.dim("All Off");
|
|
5337
5385
|
else if (Array.isArray(result.enhancements)) enhLabel = import_picocolors11.default.dim(`${result.enhancements.length} custom`);
|
|
@@ -5339,24 +5387,6 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5339
5387
|
console.log(` ${parts.join(", ")} ${import_picocolors11.default.dim("\xB7")} ${statusStr}${enhPart}
|
|
5340
5388
|
`);
|
|
5341
5389
|
if (result.ads?.length) {
|
|
5342
|
-
let wrapLine = function(text, startCol) {
|
|
5343
|
-
const width = Math.max(termWidth - startCol, 20);
|
|
5344
|
-
if (text.length <= width) return text;
|
|
5345
|
-
const pad = " ".repeat(startCol);
|
|
5346
|
-
const lines = [];
|
|
5347
|
-
let remaining = text;
|
|
5348
|
-
while (remaining.length > 0) {
|
|
5349
|
-
if (remaining.length <= width) {
|
|
5350
|
-
lines.push(remaining);
|
|
5351
|
-
break;
|
|
5352
|
-
}
|
|
5353
|
-
let breakAt = remaining.lastIndexOf(" ", width);
|
|
5354
|
-
if (breakAt <= 0) breakAt = width;
|
|
5355
|
-
lines.push(remaining.slice(0, breakAt));
|
|
5356
|
-
remaining = remaining.slice(breakAt + 1);
|
|
5357
|
-
}
|
|
5358
|
-
return lines.join("\n" + pad);
|
|
5359
|
-
};
|
|
5360
5390
|
const tableRows = [];
|
|
5361
5391
|
let lastCampaign = null;
|
|
5362
5392
|
let lastAdSet = null;
|
|
@@ -5378,6 +5408,24 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5378
5408
|
]);
|
|
5379
5409
|
console.log("");
|
|
5380
5410
|
const termWidth = process.stdout.columns || 80;
|
|
5411
|
+
const wrapLine = (text, startCol) => {
|
|
5412
|
+
const width = Math.max(termWidth - startCol, 20);
|
|
5413
|
+
if (text.length <= width) return text;
|
|
5414
|
+
const pad = " ".repeat(startCol);
|
|
5415
|
+
const lines = [];
|
|
5416
|
+
let remaining = text;
|
|
5417
|
+
while (remaining.length > 0) {
|
|
5418
|
+
if (remaining.length <= width) {
|
|
5419
|
+
lines.push(remaining);
|
|
5420
|
+
break;
|
|
5421
|
+
}
|
|
5422
|
+
let breakAt = remaining.lastIndexOf(" ", width);
|
|
5423
|
+
if (breakAt <= 0) breakAt = width;
|
|
5424
|
+
lines.push(remaining.slice(0, breakAt));
|
|
5425
|
+
remaining = remaining.slice(breakAt + 1);
|
|
5426
|
+
}
|
|
5427
|
+
return lines.join("\n" + pad);
|
|
5428
|
+
};
|
|
5381
5429
|
const allLinks = result.ads.map((a) => a.link).filter(Boolean);
|
|
5382
5430
|
const allUrlTags = result.ads.map((a) => a.urlTags).filter(Boolean);
|
|
5383
5431
|
const allCtas = result.ads.map((a) => a.cta).filter(Boolean);
|
|
@@ -5385,7 +5433,7 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5385
5433
|
const sharedUrlTags = allUrlTags.length > 0 && allUrlTags.every((t) => t === allUrlTags[0]) ? allUrlTags[0] : null;
|
|
5386
5434
|
const sharedCta = allCtas.length > 0 && allCtas.every((c) => c === allCtas[0]) ? allCtas[0] : null;
|
|
5387
5435
|
const maxText = 80;
|
|
5388
|
-
const trunc = (s) => s.length
|
|
5436
|
+
const trunc = (s) => opts.expanded || s.length <= maxText ? s : s.slice(0, maxText - 1) + "\u2026";
|
|
5389
5437
|
let lastDetailAdSet = null;
|
|
5390
5438
|
const distinctAdSets = new Set(result.ads.map((a) => a.adSetName).filter(Boolean));
|
|
5391
5439
|
const showAdSetHeaders = distinctAdSets.size > 1;
|
|
@@ -5471,6 +5519,19 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5471
5519
|
process.exit(1);
|
|
5472
5520
|
}
|
|
5473
5521
|
}
|
|
5522
|
+
async function assertCopyFromAdIsUsable(client, adId) {
|
|
5523
|
+
let ad;
|
|
5524
|
+
try {
|
|
5525
|
+
({ ad } = await client.ads.get(adId));
|
|
5526
|
+
} catch (err) {
|
|
5527
|
+
printError(`Could not fetch ad ${adId}: ${err.message}`);
|
|
5528
|
+
process.exit(1);
|
|
5529
|
+
}
|
|
5530
|
+
if (ad?.creative && ad.creative.canCopySettingsFrom === false) {
|
|
5531
|
+
printError(ad.creative.cannotCopyReason || "This ad cannot be used as a template.");
|
|
5532
|
+
process.exit(1);
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5474
5535
|
function buildBodyFromOpts(specFile, opts) {
|
|
5475
5536
|
let body;
|
|
5476
5537
|
if (specFile) {
|
|
@@ -5524,12 +5585,13 @@ function buildBodyFromOpts(specFile, opts) {
|
|
|
5524
5585
|
}
|
|
5525
5586
|
return body;
|
|
5526
5587
|
}
|
|
5527
|
-
var sharedOpts = (cmd) => cmd.option("--account <id>", "Ad account ID").option("--preset <id>", "API preset ID").option("--text-preset <id>", "Text preset ID").option("--copy-from <adId>", "Ad ID to copy settings from").option("--upload <batchId>", "Upload batch ID").option("--status <status>", "PAUSED or ACTIVE (default: ACTIVE)").option("--daily-budget <amount>", "Override daily budget per ad set (in currency units, e.g. 50 for $50)").option("--bid-amount <amount>", "Override bid/cost cap per ad set (in currency units, e.g. 5 for $5)").option("--pause-at <level>", "Pause level: ad (default), adSet, or campaign").option("--text-file <path>", "Load text configuration from JSON file").option("--json", "Output as JSON");
|
|
5588
|
+
var sharedOpts = (cmd) => cmd.option("--account <id>", "Ad account ID").option("--preset <id>", "API preset ID").option("--text-preset <id>", "Text preset ID").option("--copy-from <adId>", "Ad ID to copy settings from").option("--upload <batchId>", "Upload batch ID").option("--status <status>", "PAUSED or ACTIVE (default: ACTIVE)").option("--daily-budget <amount>", "Override daily budget per ad set (in currency units, e.g. 50 for $50)").option("--bid-amount <amount>", "Override bid/cost cap per ad set (in currency units, e.g. 5 for $5)").option("--pause-at <level>", "Pause level: ad (default), adSet, or campaign").option("--text-file <path>", "Load text configuration from JSON file").option("--expanded", "Show full headline / primary text / description without truncation").option("--json", "Output as JSON");
|
|
5528
5589
|
function createCommand2() {
|
|
5529
5590
|
const cmd = new Command("create").description("Create ads from spec file or flags").argument("[specFile]", "JSON spec file with ad configuration");
|
|
5530
5591
|
sharedOpts(cmd);
|
|
5531
5592
|
return cmd.action(async (specFile, opts) => {
|
|
5532
5593
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5594
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5533
5595
|
await executeCreate(body, opts);
|
|
5534
5596
|
});
|
|
5535
5597
|
}
|
|
@@ -5547,6 +5609,7 @@ function createPreviewCommand() {
|
|
|
5547
5609
|
sharedOpts(cmd);
|
|
5548
5610
|
return cmd.action(async (specFile, opts) => {
|
|
5549
5611
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5612
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5550
5613
|
await executeCreate(body, opts, { preview: true });
|
|
5551
5614
|
});
|
|
5552
5615
|
}
|
|
@@ -5555,6 +5618,7 @@ function createTestCommand() {
|
|
|
5555
5618
|
sharedOpts(cmd);
|
|
5556
5619
|
return cmd.action(async (specFile, opts) => {
|
|
5557
5620
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5621
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5558
5622
|
await executeCreate(body, opts, { test: true });
|
|
5559
5623
|
});
|
|
5560
5624
|
}
|
|
@@ -5620,6 +5684,7 @@ async function interactiveCreate(client) {
|
|
|
5620
5684
|
});
|
|
5621
5685
|
if (pD(adChoice)) process.exit(0);
|
|
5622
5686
|
body.copyFromAd = adChoice;
|
|
5687
|
+
await assertCopyFromAdIsUsable(client, adChoice);
|
|
5623
5688
|
}
|
|
5624
5689
|
const uploadId = await he({
|
|
5625
5690
|
message: "Upload batch ID (from `ads upload`)",
|
|
@@ -5733,7 +5798,7 @@ async function interactiveCreate(client) {
|
|
|
5733
5798
|
if (pD(textStrategy)) process.exit(0);
|
|
5734
5799
|
M2.info(import_picocolors11.default.dim("Enter headlines (one per line). Leave blank and press enter to finish."));
|
|
5735
5800
|
const headlines = [];
|
|
5736
|
-
|
|
5801
|
+
for (; ; ) {
|
|
5737
5802
|
const h2 = await he({
|
|
5738
5803
|
message: `Headline ${headlines.length + 1}`,
|
|
5739
5804
|
placeholder: headlines.length === 0 ? "Your headline..." : "(blank to finish)"
|
|
@@ -5744,7 +5809,7 @@ async function interactiveCreate(client) {
|
|
|
5744
5809
|
}
|
|
5745
5810
|
M2.info(import_picocolors11.default.dim("Enter primary text (one per line). Leave blank to finish."));
|
|
5746
5811
|
const bodies = [];
|
|
5747
|
-
|
|
5812
|
+
for (; ; ) {
|
|
5748
5813
|
const b4 = await he({
|
|
5749
5814
|
message: `Primary text ${bodies.length + 1}`,
|
|
5750
5815
|
placeholder: bodies.length === 0 ? "Your ad copy..." : "(blank to finish)"
|
|
@@ -5808,14 +5873,12 @@ async function interactiveCreate(client) {
|
|
|
5808
5873
|
const enhancementsChoice = await ve({
|
|
5809
5874
|
message: "Creative enhancements",
|
|
5810
5875
|
options: [
|
|
5811
|
-
{ value: "
|
|
5812
|
-
{ value: "
|
|
5876
|
+
{ value: "none", label: "No enhancements (recommended)" },
|
|
5877
|
+
{ value: "all", label: "All enhancements" }
|
|
5813
5878
|
]
|
|
5814
5879
|
});
|
|
5815
5880
|
if (pD(enhancementsChoice)) process.exit(0);
|
|
5816
|
-
|
|
5817
|
-
body.creativeEnhancements = "none";
|
|
5818
|
-
}
|
|
5881
|
+
body.creativeEnhancements = enhancementsChoice;
|
|
5819
5882
|
const status = await ve({
|
|
5820
5883
|
message: "Launch status",
|
|
5821
5884
|
options: [
|
|
@@ -5940,7 +6003,7 @@ function statusLabel(status) {
|
|
|
5940
6003
|
}
|
|
5941
6004
|
|
|
5942
6005
|
// src/cli.js
|
|
5943
|
-
var VERSION = true ? "0.1.
|
|
6006
|
+
var VERSION = true ? "0.1.8" : "0.0.0";
|
|
5944
6007
|
var apiUrl = process.env.ADS_API_URL || getBaseUrl();
|
|
5945
6008
|
if (apiUrl && (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"))) {
|
|
5946
6009
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
@@ -5970,6 +6033,7 @@ program2.addCommand(adsetsCommand());
|
|
|
5970
6033
|
program2.addCommand(adsetCommand());
|
|
5971
6034
|
program2.addCommand(adCommand());
|
|
5972
6035
|
program2.addCommand(presetsCommand());
|
|
6036
|
+
program2.addCommand(presetsSaveCommand());
|
|
5973
6037
|
program2.addCommand(textPresetsCommand());
|
|
5974
6038
|
program2.addCommand(uploadsCommand());
|
|
5975
6039
|
program2.addCommand(uploadCommand());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adsuploader/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Create Facebook ads from the command line — bulk upload media, preview configurations, and launch ads at scale",
|
|
5
5
|
"author": "Ads Uploader <support@adsuploader.com> (https://adsuploader.com)",
|
|
6
6
|
"license": "UNLICENSED",
|