@adsuploader/cli 0.1.7 → 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 +7 -0
- package/dist/cli.cjs +97 -33
- 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 |
|
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);
|
|
@@ -5341,24 +5387,6 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5341
5387
|
console.log(` ${parts.join(", ")} ${import_picocolors11.default.dim("\xB7")} ${statusStr}${enhPart}
|
|
5342
5388
|
`);
|
|
5343
5389
|
if (result.ads?.length) {
|
|
5344
|
-
let wrapLine = function(text, startCol) {
|
|
5345
|
-
const width = Math.max(termWidth - startCol, 20);
|
|
5346
|
-
if (text.length <= width) return text;
|
|
5347
|
-
const pad = " ".repeat(startCol);
|
|
5348
|
-
const lines = [];
|
|
5349
|
-
let remaining = text;
|
|
5350
|
-
while (remaining.length > 0) {
|
|
5351
|
-
if (remaining.length <= width) {
|
|
5352
|
-
lines.push(remaining);
|
|
5353
|
-
break;
|
|
5354
|
-
}
|
|
5355
|
-
let breakAt = remaining.lastIndexOf(" ", width);
|
|
5356
|
-
if (breakAt <= 0) breakAt = width;
|
|
5357
|
-
lines.push(remaining.slice(0, breakAt));
|
|
5358
|
-
remaining = remaining.slice(breakAt + 1);
|
|
5359
|
-
}
|
|
5360
|
-
return lines.join("\n" + pad);
|
|
5361
|
-
};
|
|
5362
5390
|
const tableRows = [];
|
|
5363
5391
|
let lastCampaign = null;
|
|
5364
5392
|
let lastAdSet = null;
|
|
@@ -5380,6 +5408,24 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5380
5408
|
]);
|
|
5381
5409
|
console.log("");
|
|
5382
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
|
+
};
|
|
5383
5429
|
const allLinks = result.ads.map((a) => a.link).filter(Boolean);
|
|
5384
5430
|
const allUrlTags = result.ads.map((a) => a.urlTags).filter(Boolean);
|
|
5385
5431
|
const allCtas = result.ads.map((a) => a.cta).filter(Boolean);
|
|
@@ -5387,7 +5433,7 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5387
5433
|
const sharedUrlTags = allUrlTags.length > 0 && allUrlTags.every((t) => t === allUrlTags[0]) ? allUrlTags[0] : null;
|
|
5388
5434
|
const sharedCta = allCtas.length > 0 && allCtas.every((c) => c === allCtas[0]) ? allCtas[0] : null;
|
|
5389
5435
|
const maxText = 80;
|
|
5390
|
-
const trunc = (s) => s.length
|
|
5436
|
+
const trunc = (s) => opts.expanded || s.length <= maxText ? s : s.slice(0, maxText - 1) + "\u2026";
|
|
5391
5437
|
let lastDetailAdSet = null;
|
|
5392
5438
|
const distinctAdSets = new Set(result.ads.map((a) => a.adSetName).filter(Boolean));
|
|
5393
5439
|
const showAdSetHeaders = distinctAdSets.size > 1;
|
|
@@ -5473,6 +5519,19 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5473
5519
|
process.exit(1);
|
|
5474
5520
|
}
|
|
5475
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
|
+
}
|
|
5476
5535
|
function buildBodyFromOpts(specFile, opts) {
|
|
5477
5536
|
let body;
|
|
5478
5537
|
if (specFile) {
|
|
@@ -5526,12 +5585,13 @@ function buildBodyFromOpts(specFile, opts) {
|
|
|
5526
5585
|
}
|
|
5527
5586
|
return body;
|
|
5528
5587
|
}
|
|
5529
|
-
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");
|
|
5530
5589
|
function createCommand2() {
|
|
5531
5590
|
const cmd = new Command("create").description("Create ads from spec file or flags").argument("[specFile]", "JSON spec file with ad configuration");
|
|
5532
5591
|
sharedOpts(cmd);
|
|
5533
5592
|
return cmd.action(async (specFile, opts) => {
|
|
5534
5593
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5594
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5535
5595
|
await executeCreate(body, opts);
|
|
5536
5596
|
});
|
|
5537
5597
|
}
|
|
@@ -5549,6 +5609,7 @@ function createPreviewCommand() {
|
|
|
5549
5609
|
sharedOpts(cmd);
|
|
5550
5610
|
return cmd.action(async (specFile, opts) => {
|
|
5551
5611
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5612
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5552
5613
|
await executeCreate(body, opts, { preview: true });
|
|
5553
5614
|
});
|
|
5554
5615
|
}
|
|
@@ -5557,6 +5618,7 @@ function createTestCommand() {
|
|
|
5557
5618
|
sharedOpts(cmd);
|
|
5558
5619
|
return cmd.action(async (specFile, opts) => {
|
|
5559
5620
|
const body = buildBodyFromOpts(specFile, opts);
|
|
5621
|
+
if (body.copyFromAd) await assertCopyFromAdIsUsable(createClient({ accountId: opts.account }), body.copyFromAd);
|
|
5560
5622
|
await executeCreate(body, opts, { test: true });
|
|
5561
5623
|
});
|
|
5562
5624
|
}
|
|
@@ -5622,6 +5684,7 @@ async function interactiveCreate(client) {
|
|
|
5622
5684
|
});
|
|
5623
5685
|
if (pD(adChoice)) process.exit(0);
|
|
5624
5686
|
body.copyFromAd = adChoice;
|
|
5687
|
+
await assertCopyFromAdIsUsable(client, adChoice);
|
|
5625
5688
|
}
|
|
5626
5689
|
const uploadId = await he({
|
|
5627
5690
|
message: "Upload batch ID (from `ads upload`)",
|
|
@@ -5735,7 +5798,7 @@ async function interactiveCreate(client) {
|
|
|
5735
5798
|
if (pD(textStrategy)) process.exit(0);
|
|
5736
5799
|
M2.info(import_picocolors11.default.dim("Enter headlines (one per line). Leave blank and press enter to finish."));
|
|
5737
5800
|
const headlines = [];
|
|
5738
|
-
|
|
5801
|
+
for (; ; ) {
|
|
5739
5802
|
const h2 = await he({
|
|
5740
5803
|
message: `Headline ${headlines.length + 1}`,
|
|
5741
5804
|
placeholder: headlines.length === 0 ? "Your headline..." : "(blank to finish)"
|
|
@@ -5746,7 +5809,7 @@ async function interactiveCreate(client) {
|
|
|
5746
5809
|
}
|
|
5747
5810
|
M2.info(import_picocolors11.default.dim("Enter primary text (one per line). Leave blank to finish."));
|
|
5748
5811
|
const bodies = [];
|
|
5749
|
-
|
|
5812
|
+
for (; ; ) {
|
|
5750
5813
|
const b4 = await he({
|
|
5751
5814
|
message: `Primary text ${bodies.length + 1}`,
|
|
5752
5815
|
placeholder: bodies.length === 0 ? "Your ad copy..." : "(blank to finish)"
|
|
@@ -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",
|