@adsuploader/cli 0.1.8 → 0.2.0
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 +36 -0
- package/dist/cli.cjs +345 -170
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ This package includes a `SKILL.md` file that describes every command and option
|
|
|
37
37
|
| `ads login` | Authenticate via browser |
|
|
38
38
|
| `ads accounts` | List ad accounts |
|
|
39
39
|
| `ads account <id>` | Set default account |
|
|
40
|
+
| `ads pages` | List Facebook Pages available for profile overrides |
|
|
40
41
|
| `ads campaigns` | List campaigns |
|
|
41
42
|
| `ads campaign <id>` | Show ad sets in a campaign |
|
|
42
43
|
| `ads adset <id>` | Show ads in an ad set |
|
package/SKILL.md
CHANGED
|
@@ -45,6 +45,14 @@ ads upload ./my-creatives/ # entire directory
|
|
|
45
45
|
|
|
46
46
|
Returns a **batch ID** (e.g. `batch_abc123`) — you'll need this for the spec file. Files are uploaded directly to the selected ad account's Facebook media library, so the batch ID is tied to that account.
|
|
47
47
|
|
|
48
|
+
Files are staged in parallel and transient network failures are retried automatically. Tune the defaults if needed:
|
|
49
|
+
|
|
50
|
+
| Upload option | Description |
|
|
51
|
+
|---------------|-------------|
|
|
52
|
+
| `--concurrency <n>` | Parallel staging uploads, 1-6 (default: 4) |
|
|
53
|
+
| `--upload-timeout <ms>` | Per-file R2 upload timeout (default: 120000) |
|
|
54
|
+
| `--api-timeout <ms>` | API request timeout (default: 60000) |
|
|
55
|
+
|
|
48
56
|
### 2. Preview (dry run)
|
|
49
57
|
|
|
50
58
|
```bash
|
|
@@ -78,6 +86,7 @@ Ads are created **ACTIVE** by default. Use `--status PAUSED` to create them paus
|
|
|
78
86
|
|---------|-------------|
|
|
79
87
|
| `accounts` | List ad accounts |
|
|
80
88
|
| `account <id>` | Set default account |
|
|
89
|
+
| `pages` | List Facebook Pages available for `--page` / `profile.pageId` |
|
|
81
90
|
| `campaigns` | List active campaigns (`--status all` for all, `--search <text>` to filter) |
|
|
82
91
|
| `campaign <id>` | Show ad sets in a campaign |
|
|
83
92
|
| `adsets --campaign <id>` | List ad sets (supports `--search`, `--status`) |
|
|
@@ -115,6 +124,9 @@ Ads are created **ACTIVE** by default. Use `--status PAUSED` to create them paus
|
|
|
115
124
|
| `--pause-at <level>` | Pause level: `ad` (default), `adSet`, or `campaign` |
|
|
116
125
|
| `--daily-budget <amount>` | Override daily budget (currency units) |
|
|
117
126
|
| `--bid-amount <amount>` | Override bid/cost cap (currency units) |
|
|
127
|
+
| `--page <id>` | Override the Facebook Page ID used by created ads |
|
|
128
|
+
| `--instagram <id>` | Override the Instagram account ID used by created ads |
|
|
129
|
+
| `--threads <id>` | Override the Threads profile ID used by created ads |
|
|
118
130
|
| `--text-file <path>` | Load text config from a JSON file |
|
|
119
131
|
| `--expanded` | Show full headline, primary text, and description values in previews |
|
|
120
132
|
|
|
@@ -165,6 +177,30 @@ When using `copyFromAd`, provide the upload batch and optionally campaign/ad set
|
|
|
165
177
|
}
|
|
166
178
|
```
|
|
167
179
|
|
|
180
|
+
### Profile Options
|
|
181
|
+
|
|
182
|
+
By default, created ads inherit the Facebook Page, Instagram account, and Threads profile from the template ad or preset. Override them with `profile`:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"copyFromAd": "120233848667930472",
|
|
187
|
+
"uploadId": "batch_abc123",
|
|
188
|
+
"profile": {
|
|
189
|
+
"pageId": "123456789012345",
|
|
190
|
+
"instagramId": "17841400000000000",
|
|
191
|
+
"threadsId": "987654321098765"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
You can also use flags:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
ads create:preview spec.json --page 123456789012345 --instagram 17841400000000000 --threads 987654321098765
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
If you override only `pageId`, the CLI/API does not carry over the template ad's Instagram or Threads IDs, because they may belong to the old page. Add `instagramId` and `threadsId` explicitly when you want them attached. Run `ads pages` to list available Facebook Page IDs for `--page`; use the web app's Profile Options selector or Meta Business settings for Instagram/Threads IDs when needed. Multi-campaign per-campaign profile overrides are not supported in CLI specs yet.
|
|
203
|
+
|
|
168
204
|
### Campaign Structure
|
|
169
205
|
|
|
170
206
|
**Single campaign** (default): ads go into the template ad's campaign, or a new campaign if `campaign.name` is provided.
|
package/dist/cli.cjs
CHANGED
|
@@ -3464,7 +3464,7 @@ var {
|
|
|
3464
3464
|
} = import_index.default;
|
|
3465
3465
|
|
|
3466
3466
|
// src/cli.js
|
|
3467
|
-
var
|
|
3467
|
+
var import_picocolors14 = __toESM(require_picocolors(), 1);
|
|
3468
3468
|
|
|
3469
3469
|
// src/lib/config.js
|
|
3470
3470
|
var import_fs = __toESM(require("fs"), 1);
|
|
@@ -4131,10 +4131,51 @@ var import_crypto = __toESM(require("crypto"), 1);
|
|
|
4131
4131
|
|
|
4132
4132
|
// src/lib/client.js
|
|
4133
4133
|
var USER_AGENT = "adsuploader-cli/0.1.0";
|
|
4134
|
+
var DEFAULT_API_TIMEOUT_MS = 6e4;
|
|
4135
|
+
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
4136
|
+
function sleep(ms) {
|
|
4137
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4138
|
+
}
|
|
4139
|
+
function parsePositiveInt(value, fallback) {
|
|
4140
|
+
const parsed = Number.parseInt(value, 10);
|
|
4141
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
4142
|
+
}
|
|
4143
|
+
function isRetryableNetworkError(err) {
|
|
4144
|
+
const message = String(err?.message || "").toLowerCase();
|
|
4145
|
+
return err?.name === "AbortError" || err?.code === "ECONNRESET" || err?.code === "ETIMEDOUT" || message.includes("fetch failed") || message.includes("network") || message.includes("timeout");
|
|
4146
|
+
}
|
|
4147
|
+
function isRetryableStatus(status) {
|
|
4148
|
+
return status === 429 || status >= 500;
|
|
4149
|
+
}
|
|
4150
|
+
function shouldRetryResponse(method, status) {
|
|
4151
|
+
if (status === 429) return true;
|
|
4152
|
+
return method === "GET" && status >= 500;
|
|
4153
|
+
}
|
|
4154
|
+
function timeoutError(timeoutMs) {
|
|
4155
|
+
return new Error(`API request timed out after ${Math.ceil(timeoutMs / 1e3)}s`);
|
|
4156
|
+
}
|
|
4157
|
+
function getRetryDelayMs(attempt, retryAfter, { baseDelayMs = 500, maxDelayMs = 8e3, jitterMs = 250 } = {}) {
|
|
4158
|
+
if (retryAfter != null && Number.isFinite(retryAfter) && retryAfter > 0) {
|
|
4159
|
+
return retryAfter * 1e3;
|
|
4160
|
+
}
|
|
4161
|
+
const exponential = Math.min(maxDelayMs, baseDelayMs * 2 ** Math.max(0, attempt - 1));
|
|
4162
|
+
return exponential + Math.floor(Math.random() * jitterMs);
|
|
4163
|
+
}
|
|
4164
|
+
function formatApiError(resp, data) {
|
|
4165
|
+
const message = data?.error || data?.message || `API error ${resp.status}`;
|
|
4166
|
+
if (resp.status === 403 && /different IP address|created from a different IP|ads login/i.test(message)) {
|
|
4167
|
+
return "Your network IP changed since this CLI token was created. Run `ads login` again to refresh your session.";
|
|
4168
|
+
}
|
|
4169
|
+
return message;
|
|
4170
|
+
}
|
|
4134
4171
|
function createClient(opts = {}) {
|
|
4135
4172
|
const token = opts.token || getToken();
|
|
4136
4173
|
const baseUrl = opts.baseUrl || getBaseUrl();
|
|
4137
4174
|
const accountId = opts.accountId || getDefaultAccount();
|
|
4175
|
+
const fetchImpl = opts.fetchImpl || fetch;
|
|
4176
|
+
const delay = opts.sleep || sleep;
|
|
4177
|
+
const maxAttempts = opts.maxAttempts || DEFAULT_MAX_ATTEMPTS;
|
|
4178
|
+
const timeoutMs = opts.timeoutMs || parsePositiveInt(process.env.ADS_API_TIMEOUT_MS, DEFAULT_API_TIMEOUT_MS);
|
|
4138
4179
|
if (!token) {
|
|
4139
4180
|
throw new Error("Not authenticated. Run `ads login` first.");
|
|
4140
4181
|
}
|
|
@@ -4162,32 +4203,51 @@ function createClient(opts = {}) {
|
|
|
4162
4203
|
if (body) {
|
|
4163
4204
|
headers["Content-Type"] = "application/json";
|
|
4164
4205
|
}
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4206
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
4207
|
+
const controller = new AbortController();
|
|
4208
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
4209
|
+
try {
|
|
4210
|
+
const resp = await fetchImpl(url.toString(), {
|
|
4211
|
+
method,
|
|
4212
|
+
headers,
|
|
4213
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
4214
|
+
redirect: "manual",
|
|
4215
|
+
// Never follow redirects — prevents token leakage to redirect targets
|
|
4216
|
+
signal: controller.signal
|
|
4217
|
+
});
|
|
4218
|
+
clearTimeout(timeout);
|
|
4219
|
+
if (resp.status >= 300 && resp.status < 400) {
|
|
4220
|
+
throw new Error(`Unexpected redirect (${resp.status}). This may indicate a configuration issue.`);
|
|
4221
|
+
}
|
|
4222
|
+
if (shouldRetryResponse(method, resp.status) && attempt < maxAttempts) {
|
|
4223
|
+
const retryAfter = resp.status === 429 ? parsePositiveInt(resp.headers.get("retry-after"), null) : null;
|
|
4224
|
+
await delay(getRetryDelayMs(attempt, retryAfter));
|
|
4225
|
+
continue;
|
|
4226
|
+
}
|
|
4227
|
+
if (raw) return resp;
|
|
4228
|
+
const data = await resp.json();
|
|
4229
|
+
if (!resp.ok) {
|
|
4230
|
+
const err = new Error(formatApiError(resp, data));
|
|
4231
|
+
err.code = data.errorCode;
|
|
4232
|
+
err.status = resp.status;
|
|
4233
|
+
err.details = data.details;
|
|
4234
|
+
throw err;
|
|
4235
|
+
}
|
|
4236
|
+
return data;
|
|
4237
|
+
} catch (err) {
|
|
4238
|
+
clearTimeout(timeout);
|
|
4239
|
+
if (err.status) {
|
|
4240
|
+
throw err;
|
|
4241
|
+
}
|
|
4242
|
+
const normalizedError = err?.name === "AbortError" ? timeoutError(timeoutMs) : err;
|
|
4243
|
+
if (method === "GET" && attempt < maxAttempts && isRetryableNetworkError(normalizedError)) {
|
|
4244
|
+
await delay(getRetryDelayMs(attempt));
|
|
4245
|
+
continue;
|
|
4246
|
+
}
|
|
4247
|
+
throw normalizedError;
|
|
4248
|
+
}
|
|
4189
4249
|
}
|
|
4190
|
-
|
|
4250
|
+
throw new Error(`API request failed after ${maxAttempts} attempt(s)`);
|
|
4191
4251
|
}
|
|
4192
4252
|
return {
|
|
4193
4253
|
get: (path3, opts2) => request("GET", path3, { ...opts2, requiresAccount: opts2?.requiresAccount ?? true }),
|
|
@@ -4199,6 +4259,9 @@ function createClient(opts = {}) {
|
|
|
4199
4259
|
campaigns: {
|
|
4200
4260
|
list: (query) => request("GET", "/campaigns", { query })
|
|
4201
4261
|
},
|
|
4262
|
+
pages: {
|
|
4263
|
+
list: (query) => request("GET", "/pages", { query })
|
|
4264
|
+
},
|
|
4202
4265
|
adsets: {
|
|
4203
4266
|
list: (campaignId, query) => request("GET", `/campaigns/${campaignId}/adsets`, { query })
|
|
4204
4267
|
},
|
|
@@ -4705,8 +4768,39 @@ function adsetCommand() {
|
|
|
4705
4768
|
});
|
|
4706
4769
|
}
|
|
4707
4770
|
|
|
4708
|
-
// src/commands/
|
|
4771
|
+
// src/commands/pages.js
|
|
4709
4772
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
4773
|
+
function formatInstagram(page) {
|
|
4774
|
+
if (!page.instagramId && !page.instagramUsername) return "";
|
|
4775
|
+
if (page.instagramUsername && page.instagramId) return `${page.instagramUsername} (${page.instagramId})`;
|
|
4776
|
+
return page.instagramUsername || page.instagramId;
|
|
4777
|
+
}
|
|
4778
|
+
function pagesCommand() {
|
|
4779
|
+
return new Command("pages").description("List Facebook Pages available for profile overrides").option("--account <id>", "Ad account ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
4780
|
+
const client = createClient({ accountId: opts.account });
|
|
4781
|
+
const { pages } = await client.pages.list();
|
|
4782
|
+
if (shouldOutputJson(opts)) {
|
|
4783
|
+
printJson(pages);
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
const rows = pages.map((page) => ({
|
|
4787
|
+
...page,
|
|
4788
|
+
instagram: formatInstagram(page)
|
|
4789
|
+
}));
|
|
4790
|
+
console.log(`
|
|
4791
|
+
${import_picocolors7.default.bold("Pages")} (${pages.length})
|
|
4792
|
+
`);
|
|
4793
|
+
printTable(rows, [
|
|
4794
|
+
{ key: "id", label: "ID", maxWidth: 25 },
|
|
4795
|
+
{ key: "name", label: "Name", maxWidth: 40 },
|
|
4796
|
+
{ key: "instagram", label: "Instagram", maxWidth: 36 }
|
|
4797
|
+
]);
|
|
4798
|
+
console.log("");
|
|
4799
|
+
});
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/commands/ad.js
|
|
4803
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
4710
4804
|
function truncate(str, max = 120, expanded = false) {
|
|
4711
4805
|
if (!str || expanded || str.length <= max) return str;
|
|
4712
4806
|
return str.slice(0, max) + "...";
|
|
@@ -4720,17 +4814,17 @@ function adCommand() {
|
|
|
4720
4814
|
return;
|
|
4721
4815
|
}
|
|
4722
4816
|
console.log(`
|
|
4723
|
-
${
|
|
4817
|
+
${import_picocolors8.default.bold(ad.name)}
|
|
4724
4818
|
`);
|
|
4725
|
-
console.log(` ${
|
|
4726
|
-
console.log(` ${
|
|
4727
|
-
console.log(` ${
|
|
4728
|
-
console.log(` ${
|
|
4819
|
+
console.log(` ${import_picocolors8.default.bold("ID:")} ${ad.id}`);
|
|
4820
|
+
console.log(` ${import_picocolors8.default.bold("Status:")} ${statusColor(ad.status)}`);
|
|
4821
|
+
console.log(` ${import_picocolors8.default.bold("Campaign:")} ${ad.campaignId}`);
|
|
4822
|
+
console.log(` ${import_picocolors8.default.bold("Ad Set:")} ${ad.adSetId}`);
|
|
4729
4823
|
if (ad.creative) {
|
|
4730
|
-
const label = (name) =>
|
|
4824
|
+
const label = (name) => import_picocolors8.default.bold(name.padEnd(15));
|
|
4731
4825
|
const exp = !!opts.expanded;
|
|
4732
4826
|
console.log("");
|
|
4733
|
-
console.log(` ${
|
|
4827
|
+
console.log(` ${import_picocolors8.default.bold("Creative")}`);
|
|
4734
4828
|
if (ad.creative.headline) console.log(` ${label("Headline:")}${truncate(ad.creative.headline, 120, exp)}`);
|
|
4735
4829
|
if (ad.creative.headlines) console.log(` ${label("Headlines:")}${ad.creative.headlines.map((h2) => truncate(h2, 60, exp)).join(" | ")}`);
|
|
4736
4830
|
if (ad.creative.primaryText) console.log(` ${label("Primary Text:")}${truncate(ad.creative.primaryText, 120, exp)}`);
|
|
@@ -4744,13 +4838,13 @@ function adCommand() {
|
|
|
4744
4838
|
if (ad.creative.enhancements) console.log(` ${label("Enhancements:")}${ad.creative.enhancements.join(", ")}`);
|
|
4745
4839
|
}
|
|
4746
4840
|
console.log(`
|
|
4747
|
-
${
|
|
4841
|
+
${import_picocolors8.default.dim("Use this ad as a template:")} ads create --copy-from ${ad.id}
|
|
4748
4842
|
`);
|
|
4749
4843
|
});
|
|
4750
4844
|
}
|
|
4751
4845
|
|
|
4752
4846
|
// src/commands/presets.js
|
|
4753
|
-
var
|
|
4847
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
4754
4848
|
function presetsCommand() {
|
|
4755
4849
|
return new Command("presets").description("List saved ad template presets").option("--account <id>", "Ad account ID").option("--json", "Output as JSON").argument("[id]", "Preset ID to show details").action(async (id, opts) => {
|
|
4756
4850
|
const client = createClient({ accountId: opts.account });
|
|
@@ -4761,12 +4855,12 @@ function presetsCommand() {
|
|
|
4761
4855
|
return;
|
|
4762
4856
|
}
|
|
4763
4857
|
console.log(`
|
|
4764
|
-
${
|
|
4858
|
+
${import_picocolors9.default.bold(preset.name)}
|
|
4765
4859
|
`);
|
|
4766
4860
|
if (preset.config) {
|
|
4767
|
-
console.log(` ${
|
|
4768
|
-
console.log(` ${
|
|
4769
|
-
console.log(` ${
|
|
4861
|
+
console.log(` ${import_picocolors9.default.bold("Campaign:")} ${preset.config.campaign?.name || import_picocolors9.default.dim("\u2014")} ${import_picocolors9.default.dim(preset.config.campaign?.id || "")}`);
|
|
4862
|
+
console.log(` ${import_picocolors9.default.bold("Ad Set:")} ${preset.config.adSet?.name || import_picocolors9.default.dim("\u2014")} ${import_picocolors9.default.dim(preset.config.adSet?.id || "")}`);
|
|
4863
|
+
console.log(` ${import_picocolors9.default.bold("Ad:")} ${preset.config.ad?.name || import_picocolors9.default.dim("\u2014")} ${import_picocolors9.default.dim(preset.config.ad?.id || "")}`);
|
|
4770
4864
|
}
|
|
4771
4865
|
console.log("");
|
|
4772
4866
|
return;
|
|
@@ -4778,15 +4872,15 @@ function presetsCommand() {
|
|
|
4778
4872
|
return;
|
|
4779
4873
|
}
|
|
4780
4874
|
console.log(`
|
|
4781
|
-
${
|
|
4875
|
+
${import_picocolors9.default.bold("Ad Template Presets")} (${all.length})
|
|
4782
4876
|
`);
|
|
4783
4877
|
if (all.length === 0) {
|
|
4784
|
-
console.log(
|
|
4878
|
+
console.log(import_picocolors9.default.dim(" No presets. Create one in the web app by saving an ad configuration."));
|
|
4785
4879
|
} else {
|
|
4786
4880
|
printTable(all, [
|
|
4787
4881
|
{ key: "id", label: "ID", maxWidth: 28 },
|
|
4788
4882
|
{ key: "name", label: "Name", maxWidth: 40 },
|
|
4789
|
-
{ key: "shared", label: "Shared", color: (v2) => v2 ?
|
|
4883
|
+
{ key: "shared", label: "Shared", color: (v2) => v2 ? import_picocolors9.default.cyan("team") : import_picocolors9.default.dim("\u2014") }
|
|
4790
4884
|
]);
|
|
4791
4885
|
}
|
|
4792
4886
|
console.log("");
|
|
@@ -4802,13 +4896,13 @@ function textPresetsCommand() {
|
|
|
4802
4896
|
return;
|
|
4803
4897
|
}
|
|
4804
4898
|
console.log(`
|
|
4805
|
-
${
|
|
4899
|
+
${import_picocolors9.default.bold(preset.name)}
|
|
4806
4900
|
`);
|
|
4807
|
-
console.log(` ${
|
|
4901
|
+
console.log(` ${import_picocolors9.default.bold("Includes:")} ${preset.includedFields?.join(", ") || "all fields"}`);
|
|
4808
4902
|
if (preset.text) {
|
|
4809
|
-
if (preset.text.titles?.length) console.log(` ${
|
|
4810
|
-
if (preset.text.bodies?.length) console.log(` ${
|
|
4811
|
-
if (preset.text.descriptions?.length) console.log(` ${
|
|
4903
|
+
if (preset.text.titles?.length) console.log(` ${import_picocolors9.default.bold("Headlines:")} ${preset.text.titles.join(" | ")}`);
|
|
4904
|
+
if (preset.text.bodies?.length) console.log(` ${import_picocolors9.default.bold("Bodies:")} ${preset.text.bodies.join(" | ")}`);
|
|
4905
|
+
if (preset.text.descriptions?.length) console.log(` ${import_picocolors9.default.bold("Desc:")} ${preset.text.descriptions.join(" | ")}`);
|
|
4812
4906
|
}
|
|
4813
4907
|
console.log("");
|
|
4814
4908
|
return;
|
|
@@ -4820,16 +4914,16 @@ function textPresetsCommand() {
|
|
|
4820
4914
|
return;
|
|
4821
4915
|
}
|
|
4822
4916
|
console.log(`
|
|
4823
|
-
${
|
|
4917
|
+
${import_picocolors9.default.bold("Text Presets")} (${all.length})
|
|
4824
4918
|
`);
|
|
4825
4919
|
if (all.length === 0) {
|
|
4826
|
-
console.log(
|
|
4920
|
+
console.log(import_picocolors9.default.dim(" No text presets. Create one in the web app."));
|
|
4827
4921
|
} else {
|
|
4828
4922
|
printTable(all, [
|
|
4829
4923
|
{ key: "id", label: "ID", maxWidth: 28 },
|
|
4830
4924
|
{ key: "name", label: "Name", maxWidth: 30 },
|
|
4831
|
-
{ key: "scope", label: "Scope", color: (v2) => v2 === "global" ?
|
|
4832
|
-
{ key: "shared", label: "Shared", color: (v2) => v2 ?
|
|
4925
|
+
{ key: "scope", label: "Scope", color: (v2) => v2 === "global" ? import_picocolors9.default.cyan("global") : import_picocolors9.default.dim("account") },
|
|
4926
|
+
{ key: "shared", label: "Shared", color: (v2) => v2 ? import_picocolors9.default.cyan("team") : import_picocolors9.default.dim("\u2014") }
|
|
4833
4927
|
]);
|
|
4834
4928
|
}
|
|
4835
4929
|
console.log("");
|
|
@@ -4867,11 +4961,11 @@ function presetsSaveCommand() {
|
|
|
4867
4961
|
return;
|
|
4868
4962
|
}
|
|
4869
4963
|
printSuccess(`Saved preset "${preset.name}"`);
|
|
4870
|
-
console.log(` ${
|
|
4871
|
-
console.log(` ${
|
|
4872
|
-
if (preset.shared) console.log(` ${
|
|
4964
|
+
console.log(` ${import_picocolors9.default.bold("ID:")} ${preset.id}`);
|
|
4965
|
+
console.log(` ${import_picocolors9.default.bold("Type:")} ${preset.type}`);
|
|
4966
|
+
if (preset.shared) console.log(` ${import_picocolors9.default.bold("Shared:")} ${import_picocolors9.default.cyan("team")}`);
|
|
4873
4967
|
console.log(`
|
|
4874
|
-
${
|
|
4968
|
+
${import_picocolors9.default.dim("Use it:")} ads create --preset ${preset.id} ...
|
|
4875
4969
|
`);
|
|
4876
4970
|
} catch (err) {
|
|
4877
4971
|
printError(err.message || "Failed to save preset");
|
|
@@ -4883,10 +4977,15 @@ function presetsSaveCommand() {
|
|
|
4883
4977
|
// src/commands/upload.js
|
|
4884
4978
|
var import_fs2 = __toESM(require("fs"), 1);
|
|
4885
4979
|
var import_path2 = __toESM(require("path"), 1);
|
|
4886
|
-
var
|
|
4980
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
4887
4981
|
var IMAGE_EXTS = /* @__PURE__ */ new Set([".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"]);
|
|
4888
4982
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"]);
|
|
4889
4983
|
var MAX_FILE_SIZE = 4 * 1024 * 1024 * 1024;
|
|
4984
|
+
var DEFAULT_UPLOAD_CONCURRENCY = 4;
|
|
4985
|
+
var MAX_UPLOAD_CONCURRENCY = 6;
|
|
4986
|
+
var DEFAULT_UPLOAD_TIMEOUT_MS = 12e4;
|
|
4987
|
+
var DEFAULT_R2_MAX_ATTEMPTS = 4;
|
|
4988
|
+
var DEFAULT_MAX_IN_FLIGHT_BYTES = 512 * 1024 * 1024;
|
|
4890
4989
|
var MAGIC_BYTES = {
|
|
4891
4990
|
"image/jpeg": [[255, 216, 255]],
|
|
4892
4991
|
"image/png": [[137, 80, 78, 71]],
|
|
@@ -4950,6 +5049,75 @@ var CONTENT_TYPES = {
|
|
|
4950
5049
|
".webm": "video/webm",
|
|
4951
5050
|
".m4v": "video/x-m4v"
|
|
4952
5051
|
};
|
|
5052
|
+
function parsePositiveInt2(value, fallback) {
|
|
5053
|
+
const parsed = Number.parseInt(value, 10);
|
|
5054
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
5055
|
+
}
|
|
5056
|
+
function normalizeConcurrency(value) {
|
|
5057
|
+
return Math.min(MAX_UPLOAD_CONCURRENCY, parsePositiveInt2(value, DEFAULT_UPLOAD_CONCURRENCY));
|
|
5058
|
+
}
|
|
5059
|
+
function calculateUploadConcurrency(files, requestedConcurrency, maxInFlightBytes = DEFAULT_MAX_IN_FLIGHT_BYTES) {
|
|
5060
|
+
const requested = normalizeConcurrency(requestedConcurrency);
|
|
5061
|
+
const largestFileSize = Math.max(0, ...files.map((file) => Number(file.size) || 0));
|
|
5062
|
+
if (largestFileSize <= 0) return requested;
|
|
5063
|
+
const memoryLimited = Math.max(1, Math.floor(maxInFlightBytes / largestFileSize));
|
|
5064
|
+
return Math.min(requested, memoryLimited);
|
|
5065
|
+
}
|
|
5066
|
+
function sleep2(ms) {
|
|
5067
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5068
|
+
}
|
|
5069
|
+
async function runBoundedConcurrency(items, concurrency, worker) {
|
|
5070
|
+
const limit = Math.max(1, Math.min(items.length || 1, concurrency));
|
|
5071
|
+
const results = new Array(items.length);
|
|
5072
|
+
let nextIndex = 0;
|
|
5073
|
+
async function runNext() {
|
|
5074
|
+
while (nextIndex < items.length) {
|
|
5075
|
+
const index = nextIndex++;
|
|
5076
|
+
results[index] = await worker(items[index], index);
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
await Promise.all(Array.from({ length: limit }, runNext));
|
|
5080
|
+
return results;
|
|
5081
|
+
}
|
|
5082
|
+
async function putR2WithRetry(uploadUrl, fileBuffer, contentType, {
|
|
5083
|
+
fetchImpl = fetch,
|
|
5084
|
+
timeoutMs = DEFAULT_UPLOAD_TIMEOUT_MS,
|
|
5085
|
+
maxAttempts = DEFAULT_R2_MAX_ATTEMPTS,
|
|
5086
|
+
delay = sleep2
|
|
5087
|
+
} = {}) {
|
|
5088
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
5089
|
+
const controller = new AbortController();
|
|
5090
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
5091
|
+
try {
|
|
5092
|
+
const resp = await fetchImpl(uploadUrl, {
|
|
5093
|
+
method: "PUT",
|
|
5094
|
+
headers: {
|
|
5095
|
+
"Content-Type": contentType,
|
|
5096
|
+
"Content-Length": String(fileBuffer.length)
|
|
5097
|
+
},
|
|
5098
|
+
body: fileBuffer,
|
|
5099
|
+
signal: controller.signal
|
|
5100
|
+
});
|
|
5101
|
+
clearTimeout(timeout);
|
|
5102
|
+
if (resp.ok) return resp;
|
|
5103
|
+
if (isRetryableStatus(resp.status) && attempt < maxAttempts) {
|
|
5104
|
+
const retryAfter = resp.status === 429 ? parsePositiveInt2(resp.headers.get("retry-after"), null) : null;
|
|
5105
|
+
await delay(getRetryDelayMs(attempt, retryAfter));
|
|
5106
|
+
continue;
|
|
5107
|
+
}
|
|
5108
|
+
throw new Error(`R2 upload failed: ${resp.status}`);
|
|
5109
|
+
} catch (err) {
|
|
5110
|
+
clearTimeout(timeout);
|
|
5111
|
+
const normalizedError = err?.name === "AbortError" ? new Error(`R2 upload timed out after ${Math.ceil(timeoutMs / 1e3)}s`) : err;
|
|
5112
|
+
if (attempt < maxAttempts && isRetryableNetworkError(normalizedError)) {
|
|
5113
|
+
await delay(getRetryDelayMs(attempt));
|
|
5114
|
+
continue;
|
|
5115
|
+
}
|
|
5116
|
+
throw normalizedError;
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
throw new Error(`R2 upload failed after ${maxAttempts} attempt(s)`);
|
|
5120
|
+
}
|
|
4953
5121
|
function getFileType(filePath) {
|
|
4954
5122
|
const ext = import_path2.default.extname(filePath).toLowerCase();
|
|
4955
5123
|
if (IMAGE_EXTS.has(ext)) return "image";
|
|
@@ -4994,13 +5162,17 @@ function resolveFiles(inputs) {
|
|
|
4994
5162
|
}
|
|
4995
5163
|
}
|
|
4996
5164
|
if (skipped.length > 0) {
|
|
4997
|
-
console.error(
|
|
5165
|
+
console.error(import_picocolors10.default.yellow(` Skipped ${skipped.length} file(s): ${skipped.join(", ")}`));
|
|
4998
5166
|
}
|
|
4999
5167
|
return files;
|
|
5000
5168
|
}
|
|
5001
5169
|
async function stageFiles(paths, opts) {
|
|
5002
|
-
const client = createClient({
|
|
5170
|
+
const client = createClient({
|
|
5171
|
+
accountId: opts.account,
|
|
5172
|
+
timeoutMs: parsePositiveInt2(opts.apiTimeout, void 0)
|
|
5173
|
+
});
|
|
5003
5174
|
const jsonMode = shouldOutputJson(opts);
|
|
5175
|
+
const uploadTimeoutMs = parsePositiveInt2(opts.uploadTimeout, DEFAULT_UPLOAD_TIMEOUT_MS);
|
|
5004
5176
|
let files;
|
|
5005
5177
|
try {
|
|
5006
5178
|
files = resolveFiles(paths);
|
|
@@ -5014,7 +5186,7 @@ async function stageFiles(paths, opts) {
|
|
|
5014
5186
|
}
|
|
5015
5187
|
if (!jsonMode) {
|
|
5016
5188
|
console.log(`
|
|
5017
|
-
${
|
|
5189
|
+
${import_picocolors10.default.bold("Uploading")} ${files.length} file(s)
|
|
5018
5190
|
`);
|
|
5019
5191
|
}
|
|
5020
5192
|
const filesMeta = files.map((f) => ({
|
|
@@ -5025,39 +5197,33 @@ async function stageFiles(paths, opts) {
|
|
|
5025
5197
|
}));
|
|
5026
5198
|
const initResult = await client.uploads.init(filesMeta);
|
|
5027
5199
|
const { batchId } = initResult;
|
|
5028
|
-
const
|
|
5029
|
-
|
|
5030
|
-
const file = files[i];
|
|
5200
|
+
const concurrency = calculateUploadConcurrency(files, opts.concurrency);
|
|
5201
|
+
const uploadedFiles = await runBoundedConcurrency(files, concurrency, async (file, i) => {
|
|
5031
5202
|
const r2Info = initResult.files[i];
|
|
5032
5203
|
if (!jsonMode) {
|
|
5033
|
-
console.log(` ${
|
|
5204
|
+
console.log(` ${import_picocolors10.default.dim("\u2192")} ${import_picocolors10.default.dim(`Uploading ${file.name}... ${formatSize(file.size)}`)}`);
|
|
5034
5205
|
}
|
|
5035
5206
|
try {
|
|
5036
5207
|
const fileBuffer = import_fs2.default.readFileSync(file.path);
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
});
|
|
5045
|
-
if (!resp.ok) {
|
|
5046
|
-
throw new Error(`R2 upload failed: ${resp.status}`);
|
|
5047
|
-
}
|
|
5048
|
-
uploadedFiles.push({
|
|
5208
|
+
await putR2WithRetry(
|
|
5209
|
+
r2Info.uploadUrl,
|
|
5210
|
+
fileBuffer,
|
|
5211
|
+
file.contentType || (file.type === "video" ? "video/mp4" : "image/jpeg"),
|
|
5212
|
+
{ timeoutMs: uploadTimeoutMs }
|
|
5213
|
+
);
|
|
5214
|
+
return {
|
|
5049
5215
|
name: file.name,
|
|
5050
5216
|
type: file.type,
|
|
5051
5217
|
size: file.size,
|
|
5052
5218
|
fileKey: r2Info.fileKey
|
|
5053
|
-
}
|
|
5219
|
+
};
|
|
5054
5220
|
} catch (err) {
|
|
5055
5221
|
if (!jsonMode) {
|
|
5056
|
-
console.log(` ${
|
|
5222
|
+
console.log(` ${import_picocolors10.default.red("\u2717")} ${file.name}: ${err.message}`);
|
|
5057
5223
|
}
|
|
5058
|
-
|
|
5224
|
+
return { name: file.name, type: file.type, fileKey: null, error: err.message };
|
|
5059
5225
|
}
|
|
5060
|
-
}
|
|
5226
|
+
});
|
|
5061
5227
|
return { client, batchId, uploadedFiles, jsonMode };
|
|
5062
5228
|
}
|
|
5063
5229
|
async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
@@ -5067,7 +5233,7 @@ async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
|
5067
5233
|
process.exit(1);
|
|
5068
5234
|
}
|
|
5069
5235
|
if (!jsonMode) {
|
|
5070
|
-
console.log(` ${
|
|
5236
|
+
console.log(` ${import_picocolors10.default.dim("\u2192")} ${import_picocolors10.default.dim("Finalizing uploads to Facebook...")}`);
|
|
5071
5237
|
}
|
|
5072
5238
|
const results = [];
|
|
5073
5239
|
let completed = 0;
|
|
@@ -5085,19 +5251,19 @@ async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
|
5085
5251
|
completed++;
|
|
5086
5252
|
if (!jsonMode) {
|
|
5087
5253
|
const id = result.videoId || result.mediaHash || "";
|
|
5088
|
-
console.log(` ${
|
|
5254
|
+
console.log(` ${import_picocolors10.default.green("\u2713")} ${file.name} uploaded successfully ${import_picocolors10.default.dim(id)}`);
|
|
5089
5255
|
}
|
|
5090
5256
|
} else {
|
|
5091
5257
|
failed++;
|
|
5092
5258
|
if (!jsonMode) {
|
|
5093
|
-
console.log(` ${
|
|
5259
|
+
console.log(` ${import_picocolors10.default.red("\u2717")} ${file.name}: ${result.error}`);
|
|
5094
5260
|
}
|
|
5095
5261
|
}
|
|
5096
5262
|
} catch (err) {
|
|
5097
5263
|
failed++;
|
|
5098
5264
|
results.push({ name: file.name, type: file.type, status: "error", error: err.message });
|
|
5099
5265
|
if (!jsonMode) {
|
|
5100
|
-
console.log(` ${
|
|
5266
|
+
console.log(` ${import_picocolors10.default.red("\u2717")} ${file.name}: ${err.message}`);
|
|
5101
5267
|
}
|
|
5102
5268
|
}
|
|
5103
5269
|
}
|
|
@@ -5115,30 +5281,30 @@ async function processAndPrint(client, batchId, uploadedFiles, jsonMode) {
|
|
|
5115
5281
|
}
|
|
5116
5282
|
if (groups.length > 0) {
|
|
5117
5283
|
console.log(`
|
|
5118
|
-
${
|
|
5284
|
+
${import_picocolors10.default.bold("Variant Groups")} (${groups.length})
|
|
5119
5285
|
`);
|
|
5120
5286
|
for (const group of groups) {
|
|
5121
5287
|
const assets = group.assets.map((a) => {
|
|
5122
|
-
const placement = a.placementType === "default" ? "" :
|
|
5288
|
+
const placement = a.placementType === "default" ? "" : import_picocolors10.default.cyan(` [${a.placementType}]`);
|
|
5123
5289
|
return `${a.name}${placement}`;
|
|
5124
5290
|
});
|
|
5125
|
-
console.log(` ${
|
|
5291
|
+
console.log(` ${import_picocolors10.default.bold(group.baseName || group.groupId)}`);
|
|
5126
5292
|
for (const asset of assets) {
|
|
5127
5293
|
console.log(` ${asset}`);
|
|
5128
5294
|
}
|
|
5129
5295
|
}
|
|
5130
5296
|
}
|
|
5131
5297
|
console.log(`
|
|
5132
|
-
${
|
|
5298
|
+
${import_picocolors10.default.bold("Batch:")} ${batchId}`);
|
|
5133
5299
|
const countStr = `${completed} complete, ${failed} failed`;
|
|
5134
5300
|
console.log(` ${countStr}`);
|
|
5135
5301
|
if (groups.length > 0) {
|
|
5136
|
-
console.log(` ${
|
|
5302
|
+
console.log(` ${import_picocolors10.default.bold("Groups:")} ${groups.length} variant group(s) detected`);
|
|
5137
5303
|
}
|
|
5138
5304
|
console.log("");
|
|
5139
5305
|
}
|
|
5140
5306
|
function uploadCommand() {
|
|
5141
|
-
return new Command("upload").description("Upload media files and process to Meta").argument("<paths...>", "Files or directories to upload").option("--account <id>", "Ad account ID").option("--json", "Output as JSON").action(async (paths, opts) => {
|
|
5307
|
+
return new Command("upload").description("Upload media files and process to Meta").argument("<paths...>", "Files or directories to upload").option("--account <id>", "Ad account ID").option("--json", "Output as JSON").option("--concurrency <n>", "Concurrent R2 staging uploads (1-6)", String(DEFAULT_UPLOAD_CONCURRENCY)).option("--upload-timeout <ms>", "R2 PUT timeout in milliseconds", String(DEFAULT_UPLOAD_TIMEOUT_MS)).option("--api-timeout <ms>", "API request timeout in milliseconds").action(async (paths, opts) => {
|
|
5142
5308
|
const { client, batchId, uploadedFiles, jsonMode } = await stageFiles(paths, opts);
|
|
5143
5309
|
await processAndPrint(client, batchId, uploadedFiles, jsonMode);
|
|
5144
5310
|
});
|
|
@@ -5155,10 +5321,10 @@ function uploadsCommand() {
|
|
|
5155
5321
|
}
|
|
5156
5322
|
const accountLabel = batch.accountName ? `${batch.accountName} (${batch.accountId})` : batch.accountId;
|
|
5157
5323
|
console.log(`
|
|
5158
|
-
${
|
|
5324
|
+
${import_picocolors10.default.bold("Batch")} ${batch.batchId}
|
|
5159
5325
|
`);
|
|
5160
|
-
console.log(` ${
|
|
5161
|
-
console.log(` ${
|
|
5326
|
+
console.log(` ${import_picocolors10.default.bold("Account:")} ${accountLabel}`);
|
|
5327
|
+
console.log(` ${import_picocolors10.default.bold("Files:")} ${batch.total}
|
|
5162
5328
|
`);
|
|
5163
5329
|
const rows = batch.files.map((f) => ({
|
|
5164
5330
|
...f,
|
|
@@ -5167,19 +5333,19 @@ function uploadsCommand() {
|
|
|
5167
5333
|
printTable(rows, [
|
|
5168
5334
|
{ key: "name", label: "Name", maxWidth: 35 },
|
|
5169
5335
|
{ key: "type", label: "Type", maxWidth: 6 },
|
|
5170
|
-
{ key: "id", label: "Hash / Video ID", maxWidth: 35, color: (v2) => v2 ||
|
|
5336
|
+
{ key: "id", label: "Hash / Video ID", maxWidth: 35, color: (v2) => v2 || import_picocolors10.default.dim("\u2014") }
|
|
5171
5337
|
]);
|
|
5172
5338
|
if (batch.groups?.length > 0) {
|
|
5173
5339
|
console.log(`
|
|
5174
|
-
${
|
|
5340
|
+
${import_picocolors10.default.bold("Variant Groups")} (${batch.groups.length})
|
|
5175
5341
|
`);
|
|
5176
5342
|
for (const group of batch.groups) {
|
|
5177
5343
|
const primaryAsset = group.assets.find((a) => a.isPrimary);
|
|
5178
5344
|
const groupLabel = group.baseName || primaryAsset?.name || group.groupId;
|
|
5179
|
-
console.log(` ${
|
|
5345
|
+
console.log(` ${import_picocolors10.default.bold(groupLabel)}`);
|
|
5180
5346
|
for (const asset of group.assets) {
|
|
5181
|
-
const placement = asset.placementType === "default" ? "" :
|
|
5182
|
-
const primary = asset.isPrimary ?
|
|
5347
|
+
const placement = asset.placementType === "default" ? "" : import_picocolors10.default.cyan(` [${asset.placementType}]`);
|
|
5348
|
+
const primary = asset.isPrimary ? import_picocolors10.default.dim(" (primary)") : "";
|
|
5183
5349
|
console.log(` ${asset.name}${placement}${primary}`);
|
|
5184
5350
|
}
|
|
5185
5351
|
}
|
|
@@ -5193,10 +5359,10 @@ function uploadsCommand() {
|
|
|
5193
5359
|
return;
|
|
5194
5360
|
}
|
|
5195
5361
|
console.log(`
|
|
5196
|
-
${
|
|
5362
|
+
${import_picocolors10.default.bold("Recent Uploads")} (${batches.length})
|
|
5197
5363
|
`);
|
|
5198
5364
|
if (batches.length === 0) {
|
|
5199
|
-
console.log(
|
|
5365
|
+
console.log(import_picocolors10.default.dim(" No uploads found for this account.\n"));
|
|
5200
5366
|
return;
|
|
5201
5367
|
}
|
|
5202
5368
|
const formatDate = (d3) => {
|
|
@@ -5225,10 +5391,10 @@ function uploadsCommand() {
|
|
|
5225
5391
|
|
|
5226
5392
|
// src/commands/create.js
|
|
5227
5393
|
var import_fs3 = __toESM(require("fs"), 1);
|
|
5228
|
-
var
|
|
5394
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
5229
5395
|
|
|
5230
5396
|
// src/lib/poll.js
|
|
5231
|
-
var
|
|
5397
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
5232
5398
|
var POLL_INTERVAL = 500;
|
|
5233
5399
|
var TIMEOUT_MS = 30 * 60 * 1e3;
|
|
5234
5400
|
var TIMEOUT_MINUTES = TIMEOUT_MS / 6e4;
|
|
@@ -5240,7 +5406,7 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5240
5406
|
if (!jsonMode) {
|
|
5241
5407
|
if (tty) {
|
|
5242
5408
|
console.log(`
|
|
5243
|
-
${
|
|
5409
|
+
${import_picocolors11.default.bold("Creating ads")}
|
|
5244
5410
|
`);
|
|
5245
5411
|
} else {
|
|
5246
5412
|
console.log(`[start] Creating ads \u2014 ${jobId}`);
|
|
@@ -5266,7 +5432,7 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5266
5432
|
process.exitCode = 2;
|
|
5267
5433
|
return { status: "still_running", jobId };
|
|
5268
5434
|
}
|
|
5269
|
-
await
|
|
5435
|
+
await sleep3(POLL_INTERVAL);
|
|
5270
5436
|
continue;
|
|
5271
5437
|
}
|
|
5272
5438
|
const ops = data.progress?.operations || [];
|
|
@@ -5277,7 +5443,7 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5277
5443
|
const time = formatTimestamp(op.timestamp);
|
|
5278
5444
|
const { icon, color } = opStyle(op.type);
|
|
5279
5445
|
if (tty) {
|
|
5280
|
-
console.log(` ${
|
|
5446
|
+
console.log(` ${import_picocolors11.default.dim(time)} ${color(icon)} ${color(op.message)}`);
|
|
5281
5447
|
} else {
|
|
5282
5448
|
const tag = op.type === "error" ? "[err]" : op.type === "retry" ? "[..]" : op.type === "completion" || (op.type || "").endsWith("_complete") ? "[ok ]" : "[..]";
|
|
5283
5449
|
console.log(`${time} ${tag} ${op.message}`);
|
|
@@ -5298,7 +5464,7 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5298
5464
|
const failed = data.result?.created?.failedAds?.length || 0;
|
|
5299
5465
|
console.log("");
|
|
5300
5466
|
if (failed > 0 && adCount > 0) {
|
|
5301
|
-
console.log(` ${
|
|
5467
|
+
console.log(` ${import_picocolors11.default.yellow("\u2212")} ${adCount} ads created, ${failed} failed in ${elapsed}`);
|
|
5302
5468
|
} else if (adCount > 0) {
|
|
5303
5469
|
printSuccess(`Created ${adCount} ads in ${elapsed}`);
|
|
5304
5470
|
} else {
|
|
@@ -5313,14 +5479,14 @@ async function pollJob(client, jobId, opts = {}) {
|
|
|
5313
5479
|
console.log(JSON.stringify({ event: "still_running", jobId }));
|
|
5314
5480
|
} else {
|
|
5315
5481
|
console.log("");
|
|
5316
|
-
console.log(` ${
|
|
5317
|
-
console.log(` Resume with: ${
|
|
5482
|
+
console.log(` ${import_picocolors11.default.yellow("\u2212")} ${import_picocolors11.default.yellow(`Still running after ${TIMEOUT_MINUTES} min \u2014 job continues on the server.`)}`);
|
|
5483
|
+
console.log(` Resume with: ${import_picocolors11.default.bold(`ads jobs ${jobId} --follow`)}`);
|
|
5318
5484
|
console.log("");
|
|
5319
5485
|
}
|
|
5320
5486
|
process.exitCode = 2;
|
|
5321
5487
|
return { status: "still_running", jobId };
|
|
5322
5488
|
}
|
|
5323
|
-
await
|
|
5489
|
+
await sleep3(POLL_INTERVAL);
|
|
5324
5490
|
}
|
|
5325
5491
|
}
|
|
5326
5492
|
function opStyle(type) {
|
|
@@ -5328,13 +5494,13 @@ function opStyle(type) {
|
|
|
5328
5494
|
case "completion":
|
|
5329
5495
|
case "campaign_complete":
|
|
5330
5496
|
case "adset_complete":
|
|
5331
|
-
return { icon: "\u2713", color:
|
|
5497
|
+
return { icon: "\u2713", color: import_picocolors11.default.green };
|
|
5332
5498
|
case "error":
|
|
5333
|
-
return { icon: "\u2717", color:
|
|
5499
|
+
return { icon: "\u2717", color: import_picocolors11.default.red };
|
|
5334
5500
|
case "retry":
|
|
5335
|
-
return { icon: "\u2212", color:
|
|
5501
|
+
return { icon: "\u2212", color: import_picocolors11.default.yellow };
|
|
5336
5502
|
default:
|
|
5337
|
-
return { icon: "\u2192", color:
|
|
5503
|
+
return { icon: "\u2192", color: import_picocolors11.default.dim };
|
|
5338
5504
|
}
|
|
5339
5505
|
}
|
|
5340
5506
|
function formatTimestamp(iso) {
|
|
@@ -5346,7 +5512,7 @@ function formatTimestamp(iso) {
|
|
|
5346
5512
|
return " ";
|
|
5347
5513
|
}
|
|
5348
5514
|
}
|
|
5349
|
-
function
|
|
5515
|
+
function sleep3(ms) {
|
|
5350
5516
|
return new Promise((r) => setTimeout(r, ms));
|
|
5351
5517
|
}
|
|
5352
5518
|
|
|
@@ -5370,29 +5536,29 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5370
5536
|
const ts = result.totalAdSets || 1;
|
|
5371
5537
|
const ta = result.totalAds || result.ads?.length || 0;
|
|
5372
5538
|
const parts = [];
|
|
5373
|
-
if (tc > 1) parts.push(`${
|
|
5374
|
-
parts.push(`${
|
|
5375
|
-
parts.push(`${
|
|
5539
|
+
if (tc > 1) parts.push(`${import_picocolors12.default.blue(tc)} campaigns`);
|
|
5540
|
+
parts.push(`${import_picocolors12.default.blue(ts)} ad set${ts !== 1 ? "s" : ""}`);
|
|
5541
|
+
parts.push(`${import_picocolors12.default.blue(ta)} ad${ta !== 1 ? "s" : ""}`);
|
|
5376
5542
|
let statusStr = statusColor(result.status || "PAUSED");
|
|
5377
5543
|
if (result.status === "PAUSED" && result.pauseAt && result.pauseAt !== "ad") {
|
|
5378
5544
|
const levelLabel = result.pauseAt === "campaign" ? "campaign level" : "ad set level";
|
|
5379
|
-
statusStr +=
|
|
5545
|
+
statusStr += import_picocolors12.default.dim(` (${levelLabel})`);
|
|
5380
5546
|
}
|
|
5381
5547
|
let enhLabel = "";
|
|
5382
|
-
if (result.enhancements === "metaDefaults") enhLabel =
|
|
5383
|
-
else if (result.enhancements === "all") enhLabel =
|
|
5384
|
-
else if (result.enhancements === "none") enhLabel =
|
|
5385
|
-
else if (Array.isArray(result.enhancements)) enhLabel =
|
|
5386
|
-
const enhPart = enhLabel ? ` ${
|
|
5387
|
-
console.log(` ${parts.join(", ")} ${
|
|
5548
|
+
if (result.enhancements === "metaDefaults") enhLabel = import_picocolors12.default.dim("All Off");
|
|
5549
|
+
else if (result.enhancements === "all") enhLabel = import_picocolors12.default.dim("All On");
|
|
5550
|
+
else if (result.enhancements === "none") enhLabel = import_picocolors12.default.dim("All Off");
|
|
5551
|
+
else if (Array.isArray(result.enhancements)) enhLabel = import_picocolors12.default.dim(`${result.enhancements.length} custom`);
|
|
5552
|
+
const enhPart = enhLabel ? ` ${import_picocolors12.default.dim("\xB7")} ${enhLabel}` : "";
|
|
5553
|
+
console.log(` ${parts.join(", ")} ${import_picocolors12.default.dim("\xB7")} ${statusStr}${enhPart}
|
|
5388
5554
|
`);
|
|
5389
5555
|
if (result.ads?.length) {
|
|
5390
5556
|
const tableRows = [];
|
|
5391
5557
|
let lastCampaign = null;
|
|
5392
5558
|
let lastAdSet = null;
|
|
5393
5559
|
for (const ad of result.ads) {
|
|
5394
|
-
const campaignName = ad.campaignName ||
|
|
5395
|
-
const adSetName = ad.adSetName ||
|
|
5560
|
+
const campaignName = ad.campaignName || import_picocolors12.default.dim("\u2014");
|
|
5561
|
+
const adSetName = ad.adSetName || import_picocolors12.default.dim("\u2014");
|
|
5396
5562
|
tableRows.push({
|
|
5397
5563
|
campaign: campaignName === lastCampaign ? "" : campaignName,
|
|
5398
5564
|
adSet: campaignName === lastCampaign && adSetName === lastAdSet ? "" : adSetName,
|
|
@@ -5440,54 +5606,54 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5440
5606
|
for (const ad of result.ads) {
|
|
5441
5607
|
if (showAdSetHeaders && ad.adSetName && ad.adSetName !== lastDetailAdSet) {
|
|
5442
5608
|
if (lastDetailAdSet !== null) console.log("");
|
|
5443
|
-
console.log(` ${
|
|
5609
|
+
console.log(` ${import_picocolors12.default.dim("\u25B8 Ad Set:")} ${import_picocolors12.default.bold(ad.adSetName)}`);
|
|
5444
5610
|
lastDetailAdSet = ad.adSetName;
|
|
5445
5611
|
}
|
|
5446
|
-
const typeLabel = ad.mediaType ?
|
|
5612
|
+
const typeLabel = ad.mediaType ? import_picocolors12.default.dim(` [${ad.mediaType}]`) : "";
|
|
5447
5613
|
const indent = showAdSetHeaders ? " " : " ";
|
|
5448
5614
|
const fieldIndent = showAdSetHeaders ? " " : " ";
|
|
5449
5615
|
const fieldIndentLen = fieldIndent.length;
|
|
5450
|
-
console.log(`${indent}${
|
|
5616
|
+
console.log(`${indent}${import_picocolors12.default.bold(import_picocolors12.default.blue(ad.name))}${typeLabel}`);
|
|
5451
5617
|
const headline = (ad.headline || []).join(" | ");
|
|
5452
5618
|
const primary = (ad.primaryText || []).join(" | ");
|
|
5453
5619
|
const desc = (ad.description || []).join(" | ");
|
|
5454
|
-
if (headline) console.log(`${fieldIndent}${
|
|
5455
|
-
if (primary) console.log(`${fieldIndent}${
|
|
5456
|
-
if (desc) console.log(`${fieldIndent}${
|
|
5457
|
-
if (ad.cta && !sharedCta) console.log(`${fieldIndent}${
|
|
5458
|
-
if (ad.link && !sharedLink) console.log(`${fieldIndent}${
|
|
5459
|
-
if (ad.urlTags && !sharedUrlTags) console.log(`${fieldIndent}${
|
|
5620
|
+
if (headline) console.log(`${fieldIndent}${import_picocolors12.default.dim("Headline:")} ${trunc(headline)}`);
|
|
5621
|
+
if (primary) console.log(`${fieldIndent}${import_picocolors12.default.dim("Primary Text:")} ${trunc(primary)}`);
|
|
5622
|
+
if (desc) console.log(`${fieldIndent}${import_picocolors12.default.dim("Description:")} ${trunc(desc)}`);
|
|
5623
|
+
if (ad.cta && !sharedCta) console.log(`${fieldIndent}${import_picocolors12.default.dim("CTA:")} ${ad.cta}`);
|
|
5624
|
+
if (ad.link && !sharedLink) console.log(`${fieldIndent}${import_picocolors12.default.dim("Link:")} ${wrapLine(ad.link, fieldIndentLen)}`);
|
|
5625
|
+
if (ad.urlTags && !sharedUrlTags) console.log(`${fieldIndent}${import_picocolors12.default.dim("URL Tags:")} ${wrapLine(ad.urlTags, fieldIndentLen)}`);
|
|
5460
5626
|
console.log("");
|
|
5461
5627
|
}
|
|
5462
5628
|
const valCol = 18;
|
|
5463
|
-
if (sharedCta) console.log(` ${
|
|
5464
|
-
if (sharedLink) console.log(` ${
|
|
5465
|
-
if (sharedUrlTags) console.log(` ${
|
|
5629
|
+
if (sharedCta) console.log(` ${import_picocolors12.default.bold("CTA:")} ${sharedCta}`);
|
|
5630
|
+
if (sharedLink) console.log(` ${import_picocolors12.default.bold("Link:")} ${wrapLine(sharedLink, valCol)}`);
|
|
5631
|
+
if (sharedUrlTags) console.log(` ${import_picocolors12.default.bold("URL Tags:")} ${wrapLine(sharedUrlTags, valCol)}`);
|
|
5466
5632
|
const enh = result.enhancements;
|
|
5467
5633
|
let enhDetail;
|
|
5468
5634
|
if (enh === "metaDefaults") enhDetail = "Meta Defaults";
|
|
5469
5635
|
else if (enh === "all") enhDetail = "All On";
|
|
5470
5636
|
else if (enh === "none") enhDetail = "All Off";
|
|
5471
5637
|
else if (Array.isArray(enh)) enhDetail = enh.join(", ");
|
|
5472
|
-
if (enhDetail) console.log(` ${
|
|
5638
|
+
if (enhDetail) console.log(` ${import_picocolors12.default.bold("Enhancements:")} ${wrapLine(enhDetail, valCol)}`);
|
|
5473
5639
|
if (result.budget) {
|
|
5474
5640
|
const curr = result.budget.currency ? ` ${result.budget.currency}` : "";
|
|
5475
5641
|
const renderLine = (label, amount) => {
|
|
5476
5642
|
const pad = " ".repeat(Math.max(1, 16 - label.length - 1));
|
|
5477
|
-
console.log(` ${
|
|
5643
|
+
console.log(` ${import_picocolors12.default.bold(`${label}:`)}${pad}${amount}${curr}`);
|
|
5478
5644
|
};
|
|
5479
5645
|
if (result.budget.dailyBudget != null) renderLine("Daily Budget", result.budget.dailyBudget);
|
|
5480
5646
|
if (result.budget.bidAmount != null) renderLine("Bid Amount", result.budget.bidAmount);
|
|
5481
5647
|
}
|
|
5482
5648
|
console.log("");
|
|
5483
5649
|
} else if (result.plan?.totals) {
|
|
5484
|
-
console.log(` ${
|
|
5650
|
+
console.log(` ${import_picocolors12.default.bold("Plan:")}`);
|
|
5485
5651
|
console.log(` Campaigns: ${result.plan.totals.campaigns}`);
|
|
5486
5652
|
console.log(` Ad Sets: ${result.plan.totals.adSets}`);
|
|
5487
5653
|
console.log(` Ads: ${result.plan.totals.ads}`);
|
|
5488
5654
|
}
|
|
5489
5655
|
console.log(`
|
|
5490
|
-
${
|
|
5656
|
+
${import_picocolors12.default.dim("This is a preview. No ads were created.")}
|
|
5491
5657
|
`);
|
|
5492
5658
|
return;
|
|
5493
5659
|
}
|
|
@@ -5508,13 +5674,13 @@ async function executeCreate(body, opts, { preview = false, test = false } = {})
|
|
|
5508
5674
|
printSuccess(`${result.status || "Complete"}`);
|
|
5509
5675
|
if (result.result?.created?.ads?.length) {
|
|
5510
5676
|
console.log(`
|
|
5511
|
-
${
|
|
5677
|
+
${import_picocolors12.default.bold("Created:")} ${result.result.created.ads.length} ad(s)`);
|
|
5512
5678
|
}
|
|
5513
5679
|
console.log("");
|
|
5514
5680
|
} catch (err) {
|
|
5515
5681
|
printError(err.message);
|
|
5516
5682
|
if (err.details) {
|
|
5517
|
-
console.error(
|
|
5683
|
+
console.error(import_picocolors12.default.dim(` Details: ${JSON.stringify(err.details)}`));
|
|
5518
5684
|
}
|
|
5519
5685
|
process.exit(1);
|
|
5520
5686
|
}
|
|
@@ -5575,6 +5741,14 @@ function buildBodyFromOpts(specFile, opts) {
|
|
|
5575
5741
|
body.adSet = body.adSet || {};
|
|
5576
5742
|
body.adSet.bidAmount = bid;
|
|
5577
5743
|
}
|
|
5744
|
+
if (opts.page || opts.instagram || opts.threads) {
|
|
5745
|
+
body.profile = {
|
|
5746
|
+
...body.profile || {},
|
|
5747
|
+
...opts.page ? { pageId: opts.page } : {},
|
|
5748
|
+
...opts.instagram ? { instagramId: opts.instagram } : {},
|
|
5749
|
+
...opts.threads ? { threadsId: opts.threads } : {}
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5578
5752
|
if (opts.pauseAt) {
|
|
5579
5753
|
if (!["ad", "adSet", "campaign"].includes(opts.pauseAt)) {
|
|
5580
5754
|
printError('--pause-at must be "ad", "adSet", or "campaign"');
|
|
@@ -5585,7 +5759,7 @@ function buildBodyFromOpts(specFile, opts) {
|
|
|
5585
5759
|
}
|
|
5586
5760
|
return body;
|
|
5587
5761
|
}
|
|
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");
|
|
5762
|
+
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("--page <id>", "Override Facebook Page ID for created ads").option("--instagram <id>", "Override Instagram account ID for created ads").option("--threads <id>", "Override Threads profile ID for created ads").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");
|
|
5589
5763
|
function createCommand2() {
|
|
5590
5764
|
const cmd = new Command("create").description("Create ads from spec file or flags").argument("[specFile]", "JSON spec file with ad configuration");
|
|
5591
5765
|
sharedOpts(cmd);
|
|
@@ -5627,7 +5801,7 @@ async function interactiveCreate(client) {
|
|
|
5627
5801
|
printError("Interactive mode requires a TTY. Use a spec file or flags instead.");
|
|
5628
5802
|
process.exit(1);
|
|
5629
5803
|
}
|
|
5630
|
-
Ie(
|
|
5804
|
+
Ie(import_picocolors12.default.bold("Ads Uploader \u2014 Create Ads"));
|
|
5631
5805
|
if (!client.accountId) {
|
|
5632
5806
|
const { accounts } = await client.accounts.list();
|
|
5633
5807
|
const accountChoice = await ve({
|
|
@@ -5764,7 +5938,7 @@ async function interactiveCreate(client) {
|
|
|
5764
5938
|
});
|
|
5765
5939
|
if (pD(namePattern)) process.exit(0);
|
|
5766
5940
|
body.adNamePattern = namePattern;
|
|
5767
|
-
M2.info(
|
|
5941
|
+
M2.info(import_picocolors12.default.dim("Variables: {filename}, {index:01}, {variation}, {campaign}, {date}, {timestamp}"));
|
|
5768
5942
|
}
|
|
5769
5943
|
const textMethod = await ve({
|
|
5770
5944
|
message: "Ad text",
|
|
@@ -5796,7 +5970,7 @@ async function interactiveCreate(client) {
|
|
|
5796
5970
|
]
|
|
5797
5971
|
});
|
|
5798
5972
|
if (pD(textStrategy)) process.exit(0);
|
|
5799
|
-
M2.info(
|
|
5973
|
+
M2.info(import_picocolors12.default.dim("Enter headlines (one per line). Leave blank and press enter to finish."));
|
|
5800
5974
|
const headlines = [];
|
|
5801
5975
|
for (; ; ) {
|
|
5802
5976
|
const h2 = await he({
|
|
@@ -5807,7 +5981,7 @@ async function interactiveCreate(client) {
|
|
|
5807
5981
|
if (!h2) break;
|
|
5808
5982
|
headlines.push(h2);
|
|
5809
5983
|
}
|
|
5810
|
-
M2.info(
|
|
5984
|
+
M2.info(import_picocolors12.default.dim("Enter primary text (one per line). Leave blank to finish."));
|
|
5811
5985
|
const bodies = [];
|
|
5812
5986
|
for (; ; ) {
|
|
5813
5987
|
const b4 = await he({
|
|
@@ -5916,7 +6090,7 @@ async function interactiveCreate(client) {
|
|
|
5916
6090
|
}
|
|
5917
6091
|
}
|
|
5918
6092
|
console.log("");
|
|
5919
|
-
M2.info(
|
|
6093
|
+
M2.info(import_picocolors12.default.bold("Summary"));
|
|
5920
6094
|
if (body.adPresetId) M2.info(` Preset: ${body.adPresetId}`);
|
|
5921
6095
|
if (body.copyFromAd) M2.info(` Copy from: ${body.copyFromAd}`);
|
|
5922
6096
|
M2.info(` Upload: ${body.uploadId}`);
|
|
@@ -5938,13 +6112,13 @@ async function interactiveCreate(client) {
|
|
|
5938
6112
|
}
|
|
5939
6113
|
|
|
5940
6114
|
// src/commands/jobs.js
|
|
5941
|
-
var
|
|
6115
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
5942
6116
|
function jobsCommand() {
|
|
5943
6117
|
const cmd = new Command("jobs").description("Manage ad creation jobs").option("--json", "Output as JSON").option("--follow", "Follow job progress in real-time").argument("[jobId]", "Job ID to check status").action(async (jobId, opts) => {
|
|
5944
6118
|
if (!jobId) {
|
|
5945
|
-
console.log(
|
|
5946
|
-
console.log(
|
|
5947
|
-
console.log(
|
|
6119
|
+
console.log(import_picocolors13.default.dim(" Usage: ads jobs <jobId>"));
|
|
6120
|
+
console.log(import_picocolors13.default.dim(" ads jobs <jobId> --follow"));
|
|
6121
|
+
console.log(import_picocolors13.default.dim(" ads jobs cancel <jobId>"));
|
|
5948
6122
|
return;
|
|
5949
6123
|
}
|
|
5950
6124
|
const client = createClient({});
|
|
@@ -5958,16 +6132,16 @@ function jobsCommand() {
|
|
|
5958
6132
|
return;
|
|
5959
6133
|
}
|
|
5960
6134
|
console.log(`
|
|
5961
|
-
${
|
|
6135
|
+
${import_picocolors13.default.bold("Job")} ${jobId}
|
|
5962
6136
|
`);
|
|
5963
|
-
console.log(` ${
|
|
5964
|
-
console.log(` ${
|
|
6137
|
+
console.log(` ${import_picocolors13.default.bold("Status:")} ${statusLabel(result.status)}`);
|
|
6138
|
+
console.log(` ${import_picocolors13.default.bold("Complete:")} ${result.complete ? import_picocolors13.default.green("yes") : import_picocolors13.default.yellow("no")}`);
|
|
5965
6139
|
if (result.progress) {
|
|
5966
6140
|
const prog = result.progress;
|
|
5967
|
-
console.log(` ${
|
|
6141
|
+
console.log(` ${import_picocolors13.default.bold("Progress:")} ${prog.completed || 0}/${prog.total || "?"}`);
|
|
5968
6142
|
}
|
|
5969
6143
|
if (result.error) {
|
|
5970
|
-
console.log(` ${
|
|
6144
|
+
console.log(` ${import_picocolors13.default.bold("Error:")} ${import_picocolors13.default.red(result.error.message || result.error)}`);
|
|
5971
6145
|
}
|
|
5972
6146
|
console.log("");
|
|
5973
6147
|
});
|
|
@@ -5988,27 +6162,27 @@ function jobsCommand() {
|
|
|
5988
6162
|
function statusLabel(status) {
|
|
5989
6163
|
switch (status) {
|
|
5990
6164
|
case "complete":
|
|
5991
|
-
return
|
|
6165
|
+
return import_picocolors13.default.green("Complete");
|
|
5992
6166
|
case "running":
|
|
5993
6167
|
case "in_progress":
|
|
5994
|
-
return
|
|
6168
|
+
return import_picocolors13.default.cyan("Running");
|
|
5995
6169
|
case "cancelled":
|
|
5996
|
-
return
|
|
6170
|
+
return import_picocolors13.default.yellow("Cancelled");
|
|
5997
6171
|
case "error":
|
|
5998
6172
|
case "failed":
|
|
5999
|
-
return
|
|
6173
|
+
return import_picocolors13.default.red("Failed");
|
|
6000
6174
|
default:
|
|
6001
|
-
return
|
|
6175
|
+
return import_picocolors13.default.dim(status || "unknown");
|
|
6002
6176
|
}
|
|
6003
6177
|
}
|
|
6004
6178
|
|
|
6005
6179
|
// src/cli.js
|
|
6006
|
-
var VERSION = true ? "0.
|
|
6180
|
+
var VERSION = true ? "0.2.0" : "0.0.0";
|
|
6007
6181
|
var apiUrl = process.env.ADS_API_URL || getBaseUrl();
|
|
6008
6182
|
if (apiUrl && (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"))) {
|
|
6009
6183
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
6010
6184
|
}
|
|
6011
|
-
var b3 = (s) =>
|
|
6185
|
+
var b3 = (s) => import_picocolors14.default.bold(import_picocolors14.default.blue(s));
|
|
6012
6186
|
var BANNER = [
|
|
6013
6187
|
"",
|
|
6014
6188
|
" " + b3(" ___ __ __ __ __ __"),
|
|
@@ -6017,7 +6191,7 @@ var BANNER = [
|
|
|
6017
6191
|
" " + b3("/_/ |_\\_,_/___/ \\____/ .__/_/\\___/\\_,_/\\_,_/\\__/_/"),
|
|
6018
6192
|
" " + b3(" /_/"),
|
|
6019
6193
|
"",
|
|
6020
|
-
" " +
|
|
6194
|
+
" " + import_picocolors14.default.dim("Create Meta ads from the command line"),
|
|
6021
6195
|
""
|
|
6022
6196
|
].join("\n");
|
|
6023
6197
|
var program2 = new Command().name("ads").description("Ads Uploader CLI").version(VERSION).addHelpText("before", BANNER);
|
|
@@ -6027,6 +6201,7 @@ program2.addCommand(whoamiCommand());
|
|
|
6027
6201
|
program2.addCommand(configCommand());
|
|
6028
6202
|
program2.addCommand(accountsCommand());
|
|
6029
6203
|
program2.addCommand(accountCommand());
|
|
6204
|
+
program2.addCommand(pagesCommand());
|
|
6030
6205
|
program2.addCommand(campaignsCommand());
|
|
6031
6206
|
program2.addCommand(campaignCommand());
|
|
6032
6207
|
program2.addCommand(adsetsCommand());
|
|
@@ -6064,8 +6239,8 @@ async function checkForUpdates() {
|
|
|
6064
6239
|
}
|
|
6065
6240
|
if (latest !== VERSION) {
|
|
6066
6241
|
console.error(`
|
|
6067
|
-
${
|
|
6068
|
-
console.error(` Run ${
|
|
6242
|
+
${import_picocolors14.default.yellow("Update available:")} ${import_picocolors14.default.dim(VERSION)} \u2192 ${import_picocolors14.default.green(latest)}`);
|
|
6243
|
+
console.error(` Run ${import_picocolors14.default.cyan("npm update -g @adsuploader/cli")} to update
|
|
6069
6244
|
`);
|
|
6070
6245
|
}
|
|
6071
6246
|
} catch {
|
|
@@ -6089,7 +6264,7 @@ if (process.argv.length <= 2) {
|
|
|
6089
6264
|
}
|
|
6090
6265
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
6091
6266
|
console.error(`
|
|
6092
|
-
${
|
|
6267
|
+
${import_picocolors14.default.red("Error:")} ${err.message}
|
|
6093
6268
|
`);
|
|
6094
6269
|
process.exit(1);
|
|
6095
6270
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adsuploader/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "node build.cjs",
|
|
34
34
|
"dev": "node src/cli.js",
|
|
35
|
+
"test": "node --test test/*.test.mjs",
|
|
35
36
|
"link": "npm link",
|
|
36
37
|
"prepublishOnly": "npm run build",
|
|
37
38
|
"version": "npm run build"
|