@adsuploader/cli 0.1.1 → 0.1.3
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 +17 -2
- package/dist/cli.cjs +36 -26
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -228,14 +228,18 @@ When using `copyFromAd`, provide the upload batch and optionally campaign/ad set
|
|
|
228
228
|
|
|
229
229
|
### Budget / Bid Override
|
|
230
230
|
|
|
231
|
-
Values in account currency units (e.g. `50` = $50).
|
|
231
|
+
Values in account currency units (e.g. `50` = $50). `dailyBudget` and `bidAmount` are independent Meta fields.
|
|
232
|
+
|
|
233
|
+
- **ABO** (budget on the ad set): set `dailyBudget`, `bidAmount`, or both. Bid-cap strategies (`COST_CAP`, `LOWEST_COST_WITH_BID_CAP`, `TARGET_COST`) need `bidAmount` alongside the budget.
|
|
234
|
+
- **CBO** (budget on the campaign): don't set `dailyBudget` on the ad set — Meta rejects that since budget comes from the campaign. For bid-cap strategies, set only `bidAmount`.
|
|
232
235
|
|
|
233
236
|
```json
|
|
234
237
|
{ "adSet": { "dailyBudget": 50 } }
|
|
235
238
|
{ "adSet": { "bidAmount": 5 } }
|
|
239
|
+
{ "adSet": { "dailyBudget": 50, "bidAmount": 5 } }
|
|
236
240
|
```
|
|
237
241
|
|
|
238
|
-
Also available as CLI flags: `--daily-budget 50
|
|
242
|
+
Also available as CLI flags: `--daily-budget 50`, `--bid-amount 5`, or both.
|
|
239
243
|
|
|
240
244
|
### Text Configuration
|
|
241
245
|
|
|
@@ -502,6 +506,15 @@ ads create spec.json
|
|
|
502
506
|
ads jobs JOB_ID --follow
|
|
503
507
|
```
|
|
504
508
|
|
|
509
|
+
`ads create` and `ads jobs --follow` watch for up to 30 minutes. On very large batches (hundreds of creatives), the command may exit before the job finishes with:
|
|
510
|
+
|
|
511
|
+
```
|
|
512
|
+
− Still running after 30 min — job continues on the server.
|
|
513
|
+
Resume with: ads jobs JOB_ID --follow
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
This is **not** a failure. The backend job keeps creating ads. Re-run the suggested command to resume watching. Exit code is `2` for this case (vs `0` for success, `1` for real errors) — shell scripts and agents should branch on the exit code to distinguish.
|
|
517
|
+
|
|
505
518
|
---
|
|
506
519
|
|
|
507
520
|
## Critical Gotchas
|
|
@@ -515,3 +528,5 @@ ads jobs JOB_ID --follow
|
|
|
515
528
|
7. **`textPresetId` and `texts` are mutually exclusive** — use one or the other
|
|
516
529
|
8. **Objective-specific CTAs** (`MESSAGE_PAGE`, `WHATSAPP_MESSAGE`, etc.) are inherited from the template — don't set them manually
|
|
517
530
|
9. **Do not use `--json`** — it's for shell scripts, not interactive use. The human-readable output has everything you need
|
|
531
|
+
10. **Never wrap CLI commands in polling loops** (`watch`, `while true; do ads adset X; sleep 5; done`, or equivalent). If you need to follow a job, use `ads jobs JOB_ID --follow`. If you need to verify ads landed after a create, run `ads adset <id>` **once** after the create returns — the job engine only marks complete when every ad is created on Facebook. Polling loops trigger rate limits (per-user-per-operation; list/read endpoints cap at 20/min) and return `429 Rate Limit Exceeded` with a `Retry-After` header.
|
|
532
|
+
11. **`ads create` exit code 2 means "still running," not "failed."** On very large batches the CLI stops watching after 30 minutes while the backend job continues. Resume with `ads jobs JOB_ID --follow`. Treat exit code 2 as "come back later," exit code 1 as a real error.
|
package/dist/cli.cjs
CHANGED
|
@@ -4726,21 +4726,20 @@ function adCommand() {
|
|
|
4726
4726
|
console.log(` ${import_picocolors7.default.bold("Campaign:")} ${ad.campaignId}`);
|
|
4727
4727
|
console.log(` ${import_picocolors7.default.bold("Ad Set:")} ${ad.adSetId}`);
|
|
4728
4728
|
if (ad.creative) {
|
|
4729
|
+
const label = (name) => import_picocolors7.default.bold(name.padEnd(15));
|
|
4729
4730
|
console.log("");
|
|
4730
4731
|
console.log(` ${import_picocolors7.default.bold("Creative")}`);
|
|
4731
|
-
if (ad.creative.headline) console.log(` ${
|
|
4732
|
-
if (ad.creative.headlines) console.log(` ${
|
|
4733
|
-
if (ad.creative.primaryText) console.log(` ${
|
|
4734
|
-
if (ad.creative.primaryTexts) console.log(` ${
|
|
4735
|
-
if (ad.creative.description) console.log(` ${
|
|
4736
|
-
if (ad.creative.descriptions) console.log(` ${
|
|
4737
|
-
if (ad.creative.cta) console.log(` ${
|
|
4738
|
-
if (ad.creative.link) console.log(` ${
|
|
4739
|
-
if (ad.creative.displayUrl) console.log(` ${
|
|
4740
|
-
if (ad.creative.urlTags) console.log(` ${
|
|
4741
|
-
if (ad.creative.enhancements) {
|
|
4742
|
-
console.log(` ${import_picocolors7.default.bold("Enhancements:")} ${ad.creative.enhancements.join(", ")}`);
|
|
4743
|
-
}
|
|
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(" | ")}`);
|
|
4738
|
+
if (ad.creative.cta) console.log(` ${label("CTA:")}${ad.creative.cta}`);
|
|
4739
|
+
if (ad.creative.link) console.log(` ${label("Link:")}${ad.creative.link}`);
|
|
4740
|
+
if (ad.creative.displayUrl) console.log(` ${label("Display URL:")}${ad.creative.displayUrl}`);
|
|
4741
|
+
if (ad.creative.urlTags) console.log(` ${label("URL Tags:")}${ad.creative.urlTags}`);
|
|
4742
|
+
if (ad.creative.enhancements) console.log(` ${label("Enhancements:")}${ad.creative.enhancements.join(", ")}`);
|
|
4744
4743
|
}
|
|
4745
4744
|
console.log(`
|
|
4746
4745
|
${import_picocolors7.default.dim("Use this ad as a template:")} ads create --copy-from ${ad.id}
|
|
@@ -5183,7 +5182,8 @@ var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
|
5183
5182
|
// src/lib/poll.js
|
|
5184
5183
|
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
5185
5184
|
var POLL_INTERVAL = 500;
|
|
5186
|
-
var TIMEOUT_MS =
|
|
5185
|
+
var TIMEOUT_MS = 30 * 60 * 1e3;
|
|
5186
|
+
var TIMEOUT_MINUTES = TIMEOUT_MS / 6e4;
|
|
5187
5187
|
async function pollJob(client, jobId, opts = {}) {
|
|
5188
5188
|
const jsonMode = opts.json;
|
|
5189
5189
|
const tty = process.stdout.isTTY && !jsonMode;
|
|
@@ -5213,6 +5213,11 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5213
5213
|
if (jsonMode) {
|
|
5214
5214
|
console.log(JSON.stringify(data));
|
|
5215
5215
|
if (data.complete) return data;
|
|
5216
|
+
if (Date.now() - startMs > TIMEOUT_MS) {
|
|
5217
|
+
console.log(JSON.stringify({ event: "still_running", jobId }));
|
|
5218
|
+
process.exitCode = 2;
|
|
5219
|
+
return { status: "still_running", jobId };
|
|
5220
|
+
}
|
|
5216
5221
|
await sleep(POLL_INTERVAL);
|
|
5217
5222
|
continue;
|
|
5218
5223
|
}
|
|
@@ -5256,10 +5261,16 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5256
5261
|
return data;
|
|
5257
5262
|
}
|
|
5258
5263
|
if (Date.now() - startMs > TIMEOUT_MS) {
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5264
|
+
if (jsonMode) {
|
|
5265
|
+
console.log(JSON.stringify({ event: "still_running", jobId }));
|
|
5266
|
+
} else {
|
|
5267
|
+
console.log("");
|
|
5268
|
+
console.log(` ${import_picocolors10.default.yellow("\u2212")} ${import_picocolors10.default.yellow(`Still running after ${TIMEOUT_MINUTES} min \u2014 job continues on the server.`)}`);
|
|
5269
|
+
console.log(` Resume with: ${import_picocolors10.default.bold(`ads jobs ${jobId} --follow`)}`);
|
|
5270
|
+
console.log("");
|
|
5271
|
+
}
|
|
5272
|
+
process.exitCode = 2;
|
|
5273
|
+
return { status: "still_running", jobId };
|
|
5263
5274
|
}
|
|
5264
5275
|
await sleep(POLL_INTERVAL);
|
|
5265
5276
|
}
|
|
@@ -5401,10 +5412,13 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5401
5412
|
else if (Array.isArray(enh)) enhDetail = enh.join(", ");
|
|
5402
5413
|
if (enhDetail) console.log(` ${import_picocolors11.default.bold("Enhancements:")} ${wrapLine(enhDetail, valCol)}`);
|
|
5403
5414
|
if (result.budget) {
|
|
5404
|
-
const label = result.budget.type === "dailyBudget" ? "Daily Budget" : "Bid Amount";
|
|
5405
|
-
const pad = " ".repeat(Math.max(1, 16 - label.length - 1));
|
|
5406
5415
|
const curr = result.budget.currency ? ` ${result.budget.currency}` : "";
|
|
5407
|
-
|
|
5416
|
+
const renderLine = (label, amount) => {
|
|
5417
|
+
const pad = " ".repeat(Math.max(1, 16 - label.length - 1));
|
|
5418
|
+
console.log(` ${import_picocolors11.default.bold(`${label}:`)}${pad}${amount}${curr}`);
|
|
5419
|
+
};
|
|
5420
|
+
if (result.budget.dailyBudget != null) renderLine("Daily Budget", result.budget.dailyBudget);
|
|
5421
|
+
if (result.budget.bidAmount != null) renderLine("Bid Amount", result.budget.bidAmount);
|
|
5408
5422
|
}
|
|
5409
5423
|
console.log("");
|
|
5410
5424
|
} else if (result.plan?.totals) {
|
|
@@ -5471,10 +5485,6 @@ function buildBodyFromOpts(specFile, opts) {
|
|
|
5471
5485
|
}
|
|
5472
5486
|
}
|
|
5473
5487
|
}
|
|
5474
|
-
if (opts.dailyBudget != null && opts.bidAmount != null) {
|
|
5475
|
-
printError("Cannot set both --daily-budget and --bid-amount. Use one or the other.");
|
|
5476
|
-
process.exit(1);
|
|
5477
|
-
}
|
|
5478
5488
|
if (opts.dailyBudget != null) {
|
|
5479
5489
|
const budget = parseFloat(opts.dailyBudget);
|
|
5480
5490
|
if (isNaN(budget) || budget <= 0) {
|
|
@@ -5919,7 +5929,7 @@ function statusLabel(status) {
|
|
|
5919
5929
|
}
|
|
5920
5930
|
|
|
5921
5931
|
// src/cli.js
|
|
5922
|
-
var VERSION = true ? "0.1.
|
|
5932
|
+
var VERSION = true ? "0.1.2" : "0.0.0";
|
|
5923
5933
|
var apiUrl = process.env.ADS_API_URL || getBaseUrl();
|
|
5924
5934
|
if (apiUrl && (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"))) {
|
|
5925
5935
|
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.3",
|
|
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",
|