@adsuploader/cli 0.1.4 → 0.1.7
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/SKILL.md +87 -2
- package/dist/cli.cjs +25 -14
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -308,6 +308,34 @@ Top-level CTA applies to all ads (overridden by per-ad CTA):
|
|
|
308
308
|
}
|
|
309
309
|
```
|
|
310
310
|
|
|
311
|
+
### URL Split-Test (LP A/B)
|
|
312
|
+
|
|
313
|
+
Provide 2–5 destination URLs under `texts.urlVariants` and every generated ad set is duplicated once per URL so Meta optimizes each ad+LP combination independently (Barry Hott's method — don't split traffic inside one ad).
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"texts": {
|
|
318
|
+
"common": { "headlines": ["Hero"], "bodies": ["Copy"] },
|
|
319
|
+
"urlVariants": [
|
|
320
|
+
{ "link": "https://example.com/homepage", "label": "homepage" },
|
|
321
|
+
{ "link": "https://example.com/quiz", "label": "quiz-v2" }
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- `label` is optional; when omitted, the last URL path slug is used (`/quiz-v2` → `quiz-v2`), falling back to hostname for root URLs.
|
|
328
|
+
- Naming depends on ad set mode:
|
|
329
|
+
- `single`: each variant's label becomes the full ad set name.
|
|
330
|
+
- `perUpload` / `autoGroup`: each duplicated ad set name is `{base}-{label}`, or substitutes a `{destination}` token if present in the pattern.
|
|
331
|
+
- Explicit `adSet.groups[]`: use `{group}` (aliases: `{name}`, `{base}`, `{adset}`) in `adSet.namePattern` to reference `groups[i].name`.
|
|
332
|
+
- Variation grouping: use `{variation}` in `adSet.namePattern` to reference the filename prefix before `variationIdentifier`.
|
|
333
|
+
- The `{date}` token resolves to today's date in labels (`launch-{date}` → `launch-2026-04-27`).
|
|
334
|
+
- Each destination URL must be distinct. Trailing-slash and case variants of the same URL collapse to one — supply at least 2 truly different destinations.
|
|
335
|
+
- Your ad set budget is multiplied by the number of variants.
|
|
336
|
+
- Not compatible with per-ad `link` overrides — the variant URL wins when both are set.
|
|
337
|
+
- Rejected with a 400 ConfigError if the source ad (preset or `copyFromAd`) targets a special destination (lead form, Messenger, WhatsApp, Instagram DM, Call) — those formats don't route via `cta.link`.
|
|
338
|
+
|
|
311
339
|
**Standard CTA types:** `LEARN_MORE`, `SHOP_NOW`, `SIGN_UP`, `SUBSCRIBE`, `GET_OFFER`, `CONTACT_US`, `DOWNLOAD`, `ORDER_NOW`, `BUY_NOW`, `BOOK_NOW`, `APPLY_NOW`, `GET_QUOTE`, `GET_IN_TOUCH`, `WATCH_MORE`
|
|
312
340
|
|
|
313
341
|
**Objective-specific CTAs** (inherited from template ad — do not set manually):
|
|
@@ -338,7 +366,7 @@ When cherry-picking features, only list ones relevant to the media type. `video_
|
|
|
338
366
|
|
|
339
367
|
### Carousel Ads
|
|
340
368
|
|
|
341
|
-
Group uploaded files into carousel ads with per-card text
|
|
369
|
+
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.
|
|
342
370
|
|
|
343
371
|
```json
|
|
344
372
|
{
|
|
@@ -346,6 +374,12 @@ Group uploaded files into carousel ads with per-card text:
|
|
|
346
374
|
{
|
|
347
375
|
"name": "My Carousel",
|
|
348
376
|
"cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"],
|
|
377
|
+
"headlines": ["Overall Carousel Headline"],
|
|
378
|
+
"bodies": ["Overall primary text"],
|
|
379
|
+
"descriptions": ["Overall description"],
|
|
380
|
+
"cta": "SHOP_NOW",
|
|
381
|
+
"link": "https://example.com/carousel",
|
|
382
|
+
"urlTags": "utm_content=my_carousel",
|
|
349
383
|
"cardTexts": [
|
|
350
384
|
{ "headline": "Slide 1", "description": "First card", "link": "https://example.com/1" },
|
|
351
385
|
{ "headline": "Slide 2", "description": "Second card", "link": "https://example.com/2" }
|
|
@@ -355,6 +389,31 @@ Group uploaded files into carousel ads with per-card text:
|
|
|
355
389
|
}
|
|
356
390
|
```
|
|
357
391
|
|
|
392
|
+
Alternative overall-text shape:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"texts": {
|
|
397
|
+
"perAd": {
|
|
398
|
+
"My Carousel": {
|
|
399
|
+
"headlines": ["Overall Carousel Headline"],
|
|
400
|
+
"bodies": ["Overall primary text"],
|
|
401
|
+
"descriptions": ["Overall description"],
|
|
402
|
+
"cta": "SHOP_NOW",
|
|
403
|
+
"link": "https://example.com/carousel",
|
|
404
|
+
"urlTags": "utm_content=my_carousel"
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
"carousel": [
|
|
409
|
+
{
|
|
410
|
+
"name": "My Carousel",
|
|
411
|
+
"cards": ["slide1.jpg", "slide2.jpg", "slide3.jpg"]
|
|
412
|
+
}
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
358
417
|
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.
|
|
359
418
|
|
|
360
419
|
### Flexible Ads
|
|
@@ -374,13 +433,39 @@ Group multiple assets into a single flexible ad (Meta picks the best asset per p
|
|
|
374
433
|
|
|
375
434
|
Minimum 2 assets per group. Files claimed by a flexible group are removed from the standard ad list.
|
|
376
435
|
|
|
436
|
+
### Multi Media Ads
|
|
437
|
+
|
|
438
|
+
Group 2-10 uploaded images or videos into a Meta Multi Media ad:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"multimedia": [
|
|
443
|
+
{
|
|
444
|
+
"name": "Mixed Media Ad",
|
|
445
|
+
"assets": ["hero.jpg", "promo.mp4", "banner.jpg"],
|
|
446
|
+
"assetTexts": [
|
|
447
|
+
{
|
|
448
|
+
"headline": "Hero headline",
|
|
449
|
+
"primaryText": "Hero primary text",
|
|
450
|
+
"description": "Hero description",
|
|
451
|
+
"link": "https://example.com/hero",
|
|
452
|
+
"displayUrl": "example.com/hero"
|
|
453
|
+
}
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
]
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
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.
|
|
461
|
+
|
|
377
462
|
### Ad Naming
|
|
378
463
|
|
|
379
464
|
```json
|
|
380
465
|
{ "adNamePattern": "{filename} - {date}" }
|
|
381
466
|
```
|
|
382
467
|
|
|
383
|
-
Placeholders: `{filename}`, `{index:01}`, `{variation}`, `{campaign}`, `{date}`, `{date:short}`, `{timestamp}`
|
|
468
|
+
Placeholders: `{filename}`, `{index:01}`, `{variation}`, `{group}`, `{adset}`, `{campaign}`, `{date}`, `{date:short}`, `{timestamp}`
|
|
384
469
|
|
|
385
470
|
### Options
|
|
386
471
|
|
package/dist/cli.cjs
CHANGED
|
@@ -5002,6 +5002,7 @@ async function stageFiles(paths, opts) {
|
|
|
5002
5002
|
uploadedFiles.push({
|
|
5003
5003
|
name: file.name,
|
|
5004
5004
|
type: file.type,
|
|
5005
|
+
size: file.size,
|
|
5005
5006
|
fileKey: r2Info.fileKey
|
|
5006
5007
|
});
|
|
5007
5008
|
} catch (err) {
|
|
@@ -5030,6 +5031,7 @@ async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
|
5030
5031
|
const result = await client.uploads.process(batchId, {
|
|
5031
5032
|
name: file.name,
|
|
5032
5033
|
type: file.type,
|
|
5034
|
+
size: file.size,
|
|
5033
5035
|
fileKey: file.fileKey
|
|
5034
5036
|
});
|
|
5035
5037
|
results.push(result);
|
|
@@ -5331,7 +5333,7 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5331
5333
|
statusStr += import_picocolors11.default.dim(` (${levelLabel})`);
|
|
5332
5334
|
}
|
|
5333
5335
|
let enhLabel = "";
|
|
5334
|
-
if (result.enhancements === "metaDefaults") enhLabel = import_picocolors11.default.dim("
|
|
5336
|
+
if (result.enhancements === "metaDefaults") enhLabel = import_picocolors11.default.dim("All Off");
|
|
5335
5337
|
else if (result.enhancements === "all") enhLabel = import_picocolors11.default.dim("All On");
|
|
5336
5338
|
else if (result.enhancements === "none") enhLabel = import_picocolors11.default.dim("All Off");
|
|
5337
5339
|
else if (Array.isArray(result.enhancements)) enhLabel = import_picocolors11.default.dim(`${result.enhancements.length} custom`);
|
|
@@ -5386,18 +5388,29 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5386
5388
|
const sharedCta = allCtas.length > 0 && allCtas.every((c) => c === allCtas[0]) ? allCtas[0] : null;
|
|
5387
5389
|
const maxText = 80;
|
|
5388
5390
|
const trunc = (s) => s.length > maxText ? s.slice(0, maxText - 1) + "\u2026" : s;
|
|
5391
|
+
let lastDetailAdSet = null;
|
|
5392
|
+
const distinctAdSets = new Set(result.ads.map((a) => a.adSetName).filter(Boolean));
|
|
5393
|
+
const showAdSetHeaders = distinctAdSets.size > 1;
|
|
5389
5394
|
for (const ad of result.ads) {
|
|
5395
|
+
if (showAdSetHeaders && ad.adSetName && ad.adSetName !== lastDetailAdSet) {
|
|
5396
|
+
if (lastDetailAdSet !== null) console.log("");
|
|
5397
|
+
console.log(` ${import_picocolors11.default.dim("\u25B8 Ad Set:")} ${import_picocolors11.default.bold(ad.adSetName)}`);
|
|
5398
|
+
lastDetailAdSet = ad.adSetName;
|
|
5399
|
+
}
|
|
5390
5400
|
const typeLabel = ad.mediaType ? import_picocolors11.default.dim(` [${ad.mediaType}]`) : "";
|
|
5391
|
-
|
|
5401
|
+
const indent = showAdSetHeaders ? " " : " ";
|
|
5402
|
+
const fieldIndent = showAdSetHeaders ? " " : " ";
|
|
5403
|
+
const fieldIndentLen = fieldIndent.length;
|
|
5404
|
+
console.log(`${indent}${import_picocolors11.default.bold(import_picocolors11.default.blue(ad.name))}${typeLabel}`);
|
|
5392
5405
|
const headline = (ad.headline || []).join(" | ");
|
|
5393
5406
|
const primary = (ad.primaryText || []).join(" | ");
|
|
5394
5407
|
const desc = (ad.description || []).join(" | ");
|
|
5395
|
-
if (headline) console.log(
|
|
5396
|
-
if (primary) console.log(
|
|
5397
|
-
if (desc) console.log(
|
|
5398
|
-
if (ad.cta && !sharedCta) console.log(
|
|
5399
|
-
if (ad.link && !sharedLink) console.log(
|
|
5400
|
-
if (ad.urlTags && !sharedUrlTags) console.log(
|
|
5408
|
+
if (headline) console.log(`${fieldIndent}${import_picocolors11.default.dim("Headline:")} ${trunc(headline)}`);
|
|
5409
|
+
if (primary) console.log(`${fieldIndent}${import_picocolors11.default.dim("Primary Text:")} ${trunc(primary)}`);
|
|
5410
|
+
if (desc) console.log(`${fieldIndent}${import_picocolors11.default.dim("Description:")} ${trunc(desc)}`);
|
|
5411
|
+
if (ad.cta && !sharedCta) console.log(`${fieldIndent}${import_picocolors11.default.dim("CTA:")} ${ad.cta}`);
|
|
5412
|
+
if (ad.link && !sharedLink) console.log(`${fieldIndent}${import_picocolors11.default.dim("Link:")} ${wrapLine(ad.link, fieldIndentLen)}`);
|
|
5413
|
+
if (ad.urlTags && !sharedUrlTags) console.log(`${fieldIndent}${import_picocolors11.default.dim("URL Tags:")} ${wrapLine(ad.urlTags, fieldIndentLen)}`);
|
|
5401
5414
|
console.log("");
|
|
5402
5415
|
}
|
|
5403
5416
|
const valCol = 18;
|
|
@@ -5797,14 +5810,12 @@ async function interactiveCreate(client) {
|
|
|
5797
5810
|
const enhancementsChoice = await ve({
|
|
5798
5811
|
message: "Creative enhancements",
|
|
5799
5812
|
options: [
|
|
5800
|
-
{ value: "
|
|
5801
|
-
{ value: "
|
|
5813
|
+
{ value: "none", label: "No enhancements (recommended)" },
|
|
5814
|
+
{ value: "all", label: "All enhancements" }
|
|
5802
5815
|
]
|
|
5803
5816
|
});
|
|
5804
5817
|
if (pD(enhancementsChoice)) process.exit(0);
|
|
5805
|
-
|
|
5806
|
-
body.creativeEnhancements = "none";
|
|
5807
|
-
}
|
|
5818
|
+
body.creativeEnhancements = enhancementsChoice;
|
|
5808
5819
|
const status = await ve({
|
|
5809
5820
|
message: "Launch status",
|
|
5810
5821
|
options: [
|
|
@@ -5929,7 +5940,7 @@ function statusLabel(status) {
|
|
|
5929
5940
|
}
|
|
5930
5941
|
|
|
5931
5942
|
// src/cli.js
|
|
5932
|
-
var VERSION = true ? "0.1.
|
|
5943
|
+
var VERSION = true ? "0.1.7" : "0.0.0";
|
|
5933
5944
|
var apiUrl = process.env.ADS_API_URL || getBaseUrl();
|
|
5934
5945
|
if (apiUrl && (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"))) {
|
|
5935
5946
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adsuploader/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|