@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.
Files changed (3) hide show
  1. package/SKILL.md +17 -2
  2. package/dist/cli.cjs +36 -26
  3. 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). Only one at a time.
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` or `--bid-amount 5`.
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(` ${import_picocolors7.default.bold("Headline:")} ${truncate(ad.creative.headline)}`);
4732
- if (ad.creative.headlines) console.log(` ${import_picocolors7.default.bold("Headlines:")} ${ad.creative.headlines.map((h2) => truncate(h2, 60)).join(" | ")}`);
4733
- if (ad.creative.primaryText) console.log(` ${import_picocolors7.default.bold("Primary Text:")} ${truncate(ad.creative.primaryText)}`);
4734
- if (ad.creative.primaryTexts) console.log(` ${import_picocolors7.default.bold("Primary Texts:")} ${ad.creative.primaryTexts.map((t) => truncate(t, 60)).join(" | ")}`);
4735
- if (ad.creative.description) console.log(` ${import_picocolors7.default.bold("Description:")} ${truncate(ad.creative.description)}`);
4736
- if (ad.creative.descriptions) console.log(` ${import_picocolors7.default.bold("Descriptions:")} ${ad.creative.descriptions.map((d3) => truncate(d3, 60)).join(" | ")}`);
4737
- if (ad.creative.cta) console.log(` ${import_picocolors7.default.bold("CTA:")} ${ad.creative.cta}`);
4738
- if (ad.creative.link) console.log(` ${import_picocolors7.default.bold("Link:")} ${ad.creative.link}`);
4739
- if (ad.creative.displayUrl) console.log(` ${import_picocolors7.default.bold("Display URL:")} ${ad.creative.displayUrl}`);
4740
- if (ad.creative.urlTags) console.log(` ${import_picocolors7.default.bold("URL Tags:")} ${ad.creative.urlTags}`);
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 = 5 * 60 * 1e3;
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
- console.log("");
5260
- printError(`Timed out after 5 minutes. Check: ads jobs ${jobId}`);
5261
- console.log("");
5262
- return data;
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
- console.log(` ${import_picocolors11.default.bold(`${label}:`)}${pad}${result.budget.amount}${curr}`);
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.1" : "0.0.0";
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.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",