@autoposting.ai/cli 0.5.1 → 0.5.2
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/dist/cli.cjs +283 -66
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -132,7 +132,7 @@ var init_dist = __esm({
|
|
|
132
132
|
);
|
|
133
133
|
}
|
|
134
134
|
};
|
|
135
|
-
VERSION = "0.5.
|
|
135
|
+
VERSION = "0.5.2";
|
|
136
136
|
AutopostingError = class extends Error {
|
|
137
137
|
status;
|
|
138
138
|
code;
|
|
@@ -222,6 +222,14 @@ var init_dist = __esm({
|
|
|
222
222
|
authStatus(slug) {
|
|
223
223
|
return this.get(`/brands/${slug}/auth/status`);
|
|
224
224
|
}
|
|
225
|
+
/** Browser session owns beta checks, official authorization and explicit Page selection. */
|
|
226
|
+
async facebookConnection(slug) {
|
|
227
|
+
const brand = await this.retrieve(slug);
|
|
228
|
+
const url = new URL("/brands", this.client.baseUrl);
|
|
229
|
+
url.searchParams.set("brand", brand.slug);
|
|
230
|
+
url.searchParams.set("platform", "facebook");
|
|
231
|
+
return { url: url.toString(), brandSlug: brand.slug, platform: "facebook" };
|
|
232
|
+
}
|
|
225
233
|
};
|
|
226
234
|
CarouselsResource = class extends Resource {
|
|
227
235
|
/** GET /carousels — backend paginates: `{ items, total, limit, offset }`. */
|
|
@@ -373,15 +381,20 @@ var init_dist = __esm({
|
|
|
373
381
|
publish(id) {
|
|
374
382
|
return this.client.request("POST", `/posts/${id}/publish`);
|
|
375
383
|
}
|
|
376
|
-
schedule(id, scheduledAt) {
|
|
377
|
-
return this.client.request("PUT", `/posts/${id}/schedule`, { scheduledAt });
|
|
384
|
+
schedule(id, scheduledAt, platform) {
|
|
385
|
+
return this.client.request("PUT", `/posts/${id}/schedule`, { scheduledAt, ...platform ? { platform } : {} });
|
|
378
386
|
}
|
|
379
|
-
/** PUT /posts/:id/schedule with { cancel: true } —
|
|
387
|
+
/** PUT /posts/:id/schedule with { cancel: true } — cancels scheduling; recovery restores the previous outcome. */
|
|
380
388
|
unschedule(id) {
|
|
381
389
|
return this.client.request("PUT", `/posts/${id}/schedule`, { cancel: true });
|
|
382
390
|
}
|
|
383
|
-
retry(id) {
|
|
384
|
-
return this.client.request(
|
|
391
|
+
retry(id, platform) {
|
|
392
|
+
return this.client.request(
|
|
393
|
+
"POST",
|
|
394
|
+
`/posts/${id}/retry`,
|
|
395
|
+
void 0,
|
|
396
|
+
platform ? { platform } : void 0
|
|
397
|
+
);
|
|
385
398
|
}
|
|
386
399
|
rewrite(id) {
|
|
387
400
|
return this.client.request("POST", `/posts/${id}/rewrite`);
|
|
@@ -808,7 +821,7 @@ var init_media_flags = __esm({
|
|
|
808
821
|
"src/lib/media-flags.ts"() {
|
|
809
822
|
"use strict";
|
|
810
823
|
import_node_fs2 = require("fs");
|
|
811
|
-
VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
824
|
+
VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube", "facebook"];
|
|
812
825
|
EXT_TO_MIME = {
|
|
813
826
|
jpg: "image/jpeg",
|
|
814
827
|
jpeg: "image/jpeg",
|
|
@@ -823,7 +836,7 @@ var init_media_flags = __esm({
|
|
|
823
836
|
});
|
|
824
837
|
|
|
825
838
|
// src/mcp/tools.ts
|
|
826
|
-
var MEDIA_ITEMS, ALL_TOOLS;
|
|
839
|
+
var MEDIA_ITEMS, FACEBOOK_AUTHORING, ALL_TOOLS;
|
|
827
840
|
var init_tools = __esm({
|
|
828
841
|
"src/mcp/tools.ts"() {
|
|
829
842
|
"use strict";
|
|
@@ -842,6 +855,20 @@ var init_tools = __esm({
|
|
|
842
855
|
additionalProperties: false
|
|
843
856
|
}
|
|
844
857
|
};
|
|
858
|
+
FACEBOOK_AUTHORING = {
|
|
859
|
+
facebookOptions: { type: "object", properties: {
|
|
860
|
+
format: { type: "string", enum: ["text", "link", "photo", "multi-photo", "video", "reel"] },
|
|
861
|
+
link: { type: "string", description: "Public HTTPS article URL for link format" }
|
|
862
|
+
}, required: ["format"], additionalProperties: false },
|
|
863
|
+
targetAccountIds: {
|
|
864
|
+
type: "object",
|
|
865
|
+
description: "Explicit selected connected Page IDs, including a sole Page",
|
|
866
|
+
properties: { facebook: { type: "array", items: { type: "string" }, minItems: 1, uniqueItems: true } },
|
|
867
|
+
additionalProperties: { type: "array", items: { type: "string" } }
|
|
868
|
+
},
|
|
869
|
+
platformTexts: { type: "object", additionalProperties: { type: "string" }, description: "Platform captions; empty Facebook caption is intentional" },
|
|
870
|
+
platformMedia: { type: "object", additionalProperties: MEDIA_ITEMS, description: "Platform media; empty Facebook array excludes shared attachments" }
|
|
871
|
+
};
|
|
845
872
|
ALL_TOOLS = [
|
|
846
873
|
{ name: "delete-account-import", description: "Delete import progress only. Connected accounts and brands remain. Ask the user before discarding their progress.", inputSchema: { type: "object", properties: { id: { type: "string", description: "Import id" } }, required: ["id"] } },
|
|
847
874
|
{ name: "import-accounts", description: "Import 1\u20131000 social account rows from CSV. Headers: brand_name, platform, account_url; optional brand_slug, timezone, account_type. Returns a durable import id. Repeating the same import reuses progress. No passwords or provider tokens.", inputSchema: { type: "object", properties: { csv: { type: "string", maxLength: 9e4, description: "CSV contents, including header row" } }, required: ["csv"] } },
|
|
@@ -889,14 +916,16 @@ var init_tools = __esm({
|
|
|
889
916
|
brandSlug: { type: "string", description: "Brand slug to post under" },
|
|
890
917
|
text: { type: "string", description: "Post text content" },
|
|
891
918
|
platforms: {
|
|
892
|
-
type: "string",
|
|
893
|
-
|
|
919
|
+
type: ["string", "array"],
|
|
920
|
+
items: { type: "string" },
|
|
921
|
+
description: "Platform array or comma-separated platform list (e.g. x,linkedin)"
|
|
894
922
|
},
|
|
895
923
|
scheduledAt: {
|
|
896
924
|
type: "string",
|
|
897
925
|
description: "ISO 8601 datetime to schedule the post"
|
|
898
926
|
},
|
|
899
|
-
media: MEDIA_ITEMS
|
|
927
|
+
media: MEDIA_ITEMS,
|
|
928
|
+
...FACEBOOK_AUTHORING
|
|
900
929
|
},
|
|
901
930
|
required: ["brandSlug", "text", "platforms"],
|
|
902
931
|
additionalProperties: false
|
|
@@ -910,9 +939,10 @@ var init_tools = __esm({
|
|
|
910
939
|
properties: {
|
|
911
940
|
id: { type: "string", description: "Post ID" },
|
|
912
941
|
text: { type: "string", description: "New post text" },
|
|
913
|
-
platforms: { type: "string", description: "
|
|
942
|
+
platforms: { type: ["string", "array"], items: { type: "string" }, description: "Platform array or comma-separated new platform list" },
|
|
914
943
|
scheduledAt: { type: "string", description: "New ISO 8601 scheduled datetime" },
|
|
915
|
-
media: MEDIA_ITEMS
|
|
944
|
+
media: MEDIA_ITEMS,
|
|
945
|
+
...FACEBOOK_AUTHORING
|
|
916
946
|
},
|
|
917
947
|
required: ["id"],
|
|
918
948
|
additionalProperties: false
|
|
@@ -949,7 +979,8 @@ var init_tools = __esm({
|
|
|
949
979
|
type: "object",
|
|
950
980
|
properties: {
|
|
951
981
|
id: { type: "string", description: "Post ID to schedule" },
|
|
952
|
-
scheduledAt: { type: "string", description: "ISO 8601 datetime for publishing" }
|
|
982
|
+
scheduledAt: { type: "string", description: "ISO 8601 datetime for publishing" },
|
|
983
|
+
platform: { type: "string", enum: ["facebook"], description: "Explicit failed-Page recovery; preserves Published and Unknown outcomes" }
|
|
953
984
|
},
|
|
954
985
|
required: ["id", "scheduledAt"],
|
|
955
986
|
additionalProperties: false
|
|
@@ -961,7 +992,8 @@ var init_tools = __esm({
|
|
|
961
992
|
inputSchema: {
|
|
962
993
|
type: "object",
|
|
963
994
|
properties: {
|
|
964
|
-
id: { type: "string", description: "Post ID to retry" }
|
|
995
|
+
id: { type: "string", description: "Post ID to retry" },
|
|
996
|
+
platform: { type: "string", enum: ["x", "linkedin", "instagram", "threads", "youtube", "facebook"], description: "Optional explicit platform; facebook retries only failed Pages without remote evidence, preserving Published and Unknown outcomes." }
|
|
965
997
|
},
|
|
966
998
|
required: ["id"],
|
|
967
999
|
additionalProperties: false
|
|
@@ -1688,6 +1720,42 @@ var init_api_routes = __esm({
|
|
|
1688
1720
|
});
|
|
1689
1721
|
|
|
1690
1722
|
// src/mcp/handler.ts
|
|
1723
|
+
function validateAuthoringIntent(args) {
|
|
1724
|
+
const object = (value, field) => {
|
|
1725
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${field} must be an object.`);
|
|
1726
|
+
return value;
|
|
1727
|
+
};
|
|
1728
|
+
if (args.facebookOptions !== void 0) {
|
|
1729
|
+
const options = object(args.facebookOptions, "facebookOptions");
|
|
1730
|
+
if (!["text", "link", "photo", "multi-photo", "video", "reel"].includes(options.format)) {
|
|
1731
|
+
throw new Error("facebookOptions.format must be a supported Facebook format.");
|
|
1732
|
+
}
|
|
1733
|
+
if (options.link !== void 0 && typeof options.link !== "string") throw new Error("facebookOptions.link must be a string.");
|
|
1734
|
+
if (Object.keys(options).some((key) => !["format", "link"].includes(key))) throw new Error("Unknown facebookOptions field.");
|
|
1735
|
+
}
|
|
1736
|
+
for (const field of ["targetAccountIds", "platformTexts", "platformMedia"]) {
|
|
1737
|
+
if (args[field] === void 0) continue;
|
|
1738
|
+
for (const [platform, value] of Object.entries(object(args[field], field))) {
|
|
1739
|
+
if (!PLATFORM_SET.has(platform)) throw new Error(`Invalid platform in ${field}.`);
|
|
1740
|
+
if (field === "platformTexts") {
|
|
1741
|
+
if (typeof value !== "string") throw new Error("Platform captions must be strings.");
|
|
1742
|
+
} else if (field === "targetAccountIds") {
|
|
1743
|
+
if (!Array.isArray(value) || value.length === 0 || value.some((id) => typeof id !== "string" || !id.trim()) || new Set(value).size !== value.length) {
|
|
1744
|
+
throw new Error("Target account IDs must be a nonempty array of unique IDs.");
|
|
1745
|
+
}
|
|
1746
|
+
} else {
|
|
1747
|
+
if (!Array.isArray(value)) throw new Error("Platform media must be an array.");
|
|
1748
|
+
for (const item of value) {
|
|
1749
|
+
const media = object(item, "Media item");
|
|
1750
|
+
if (typeof media.url !== "string" || !media.url.trim() || !["image", "video", "gif"].includes(media.type)) {
|
|
1751
|
+
throw new Error("Media items require a URL and image, video or gif type.");
|
|
1752
|
+
}
|
|
1753
|
+
if (media.altText !== void 0 && typeof media.altText !== "string") throw new Error("Media altText must be a string.");
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1691
1759
|
function ok(data) {
|
|
1692
1760
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1693
1761
|
}
|
|
@@ -1696,8 +1764,15 @@ function fail(error) {
|
|
|
1696
1764
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
1697
1765
|
}
|
|
1698
1766
|
function parsePlatforms2(raw) {
|
|
1699
|
-
if (
|
|
1700
|
-
|
|
1767
|
+
if (raw === void 0) return [];
|
|
1768
|
+
if (typeof raw !== "string" && !Array.isArray(raw)) {
|
|
1769
|
+
throw new Error("Platforms must be a comma-separated string or an array of platform names.");
|
|
1770
|
+
}
|
|
1771
|
+
const values = typeof raw === "string" ? raw.split(",") : raw;
|
|
1772
|
+
if (values.some((value) => typeof value !== "string")) {
|
|
1773
|
+
throw new Error("Every platform must be a platform name.");
|
|
1774
|
+
}
|
|
1775
|
+
const platforms = values.map((p) => p.trim()).filter(Boolean);
|
|
1701
1776
|
const invalid = platforms.filter((platform) => !PLATFORM_SET.has(platform));
|
|
1702
1777
|
if (invalid.length > 0) {
|
|
1703
1778
|
throw new Error(
|
|
@@ -1727,6 +1802,7 @@ async function handleToolCall(name, args, client) {
|
|
|
1727
1802
|
}
|
|
1728
1803
|
}
|
|
1729
1804
|
async function dispatchToolCall(name, args, client) {
|
|
1805
|
+
if (name === "create-post" || name === "update-post") validateAuthoringIntent(args);
|
|
1730
1806
|
switch (name) {
|
|
1731
1807
|
case "delete-account-import":
|
|
1732
1808
|
return ok(await client.accountOnboarding.remove(args.id));
|
|
@@ -1760,18 +1836,26 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1760
1836
|
text: args.text,
|
|
1761
1837
|
platforms: parsePlatforms2(args.platforms),
|
|
1762
1838
|
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1763
|
-
...args.media ? { media: args.media } : {}
|
|
1839
|
+
...args.media ? { media: args.media } : {},
|
|
1840
|
+
...args.facebookOptions !== void 0 ? { facebookOptions: args.facebookOptions } : {},
|
|
1841
|
+
...args.targetAccountIds !== void 0 ? { targetAccountIds: args.targetAccountIds } : {},
|
|
1842
|
+
...args.platformTexts !== void 0 ? { platformTexts: args.platformTexts } : {},
|
|
1843
|
+
...args.platformMedia !== void 0 ? { platformMedia: args.platformMedia } : {}
|
|
1764
1844
|
});
|
|
1765
1845
|
return ok(result);
|
|
1766
1846
|
}
|
|
1767
1847
|
case "update-post": {
|
|
1768
1848
|
const result = await client.posts.update(args.id, {
|
|
1769
|
-
...args.text ? { text: args.text } : {},
|
|
1849
|
+
...args.text !== void 0 ? { text: args.text } : {},
|
|
1770
1850
|
...args.platforms ? {
|
|
1771
1851
|
platforms: parsePlatforms2(args.platforms)
|
|
1772
1852
|
} : {},
|
|
1773
1853
|
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1774
|
-
...args.media ? { media: args.media } : {}
|
|
1854
|
+
...args.media ? { media: args.media } : {},
|
|
1855
|
+
...args.facebookOptions !== void 0 ? { facebookOptions: args.facebookOptions } : {},
|
|
1856
|
+
...args.targetAccountIds !== void 0 ? { targetAccountIds: args.targetAccountIds } : {},
|
|
1857
|
+
...args.platformTexts !== void 0 ? { platformTexts: args.platformTexts } : {},
|
|
1858
|
+
...args.platformMedia !== void 0 ? { platformMedia: args.platformMedia } : {}
|
|
1775
1859
|
});
|
|
1776
1860
|
return ok(result);
|
|
1777
1861
|
}
|
|
@@ -1784,14 +1868,14 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1784
1868
|
return ok(result);
|
|
1785
1869
|
}
|
|
1786
1870
|
case "schedule-post": {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
args.scheduledAt
|
|
1790
|
-
);
|
|
1871
|
+
if (args.platform !== void 0 && args.platform !== "facebook") throw new Error("Scheduled recovery requires platform facebook");
|
|
1872
|
+
const result = args.platform === "facebook" ? await client.posts.schedule(args.id, args.scheduledAt, "facebook") : await client.posts.schedule(args.id, args.scheduledAt);
|
|
1791
1873
|
return ok(result);
|
|
1792
1874
|
}
|
|
1793
1875
|
case "retry-post": {
|
|
1794
|
-
const
|
|
1876
|
+
const platforms = args.platform === void 0 ? void 0 : parsePlatforms2(args.platform);
|
|
1877
|
+
if (platforms && platforms.length !== 1) throw new Error("Retry requires exactly one platform");
|
|
1878
|
+
const result = platforms ? await client.posts.retry(args.id, platforms[0]) : await client.posts.retry(args.id);
|
|
1795
1879
|
return ok(result);
|
|
1796
1880
|
}
|
|
1797
1881
|
case "rewrite-post": {
|
|
@@ -2066,7 +2150,7 @@ async function dispatchToolCall(name, args, client) {
|
|
|
2066
2150
|
const filePath = args.filePath;
|
|
2067
2151
|
const filename = (0, import_node_path4.basename)(filePath);
|
|
2068
2152
|
const uploaded = await client.media.upload({
|
|
2069
|
-
data: new Uint8Array(await (0,
|
|
2153
|
+
data: new Uint8Array(await (0, import_promises4.readFile)(filePath)),
|
|
2070
2154
|
filename,
|
|
2071
2155
|
contentType: extToMime(filename)
|
|
2072
2156
|
});
|
|
@@ -2107,11 +2191,11 @@ async function dispatchToolCall(name, args, client) {
|
|
|
2107
2191
|
};
|
|
2108
2192
|
}
|
|
2109
2193
|
}
|
|
2110
|
-
var
|
|
2194
|
+
var import_promises4, import_node_path4, PLATFORMS, PLATFORM_SET, routeCache;
|
|
2111
2195
|
var init_handler = __esm({
|
|
2112
2196
|
"src/mcp/handler.ts"() {
|
|
2113
2197
|
"use strict";
|
|
2114
|
-
|
|
2198
|
+
import_promises4 = require("fs/promises");
|
|
2115
2199
|
import_node_path4 = require("path");
|
|
2116
2200
|
init_api_routes();
|
|
2117
2201
|
init_media_flags();
|
|
@@ -2120,7 +2204,8 @@ var init_handler = __esm({
|
|
|
2120
2204
|
"linkedin",
|
|
2121
2205
|
"instagram",
|
|
2122
2206
|
"threads",
|
|
2123
|
-
"youtube"
|
|
2207
|
+
"youtube",
|
|
2208
|
+
"facebook"
|
|
2124
2209
|
];
|
|
2125
2210
|
PLATFORM_SET = new Set(PLATFORMS);
|
|
2126
2211
|
routeCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -3133,6 +3218,28 @@ function buildAuthStatusRow(c, now = Date.now()) {
|
|
|
3133
3218
|
}
|
|
3134
3219
|
function createBrandsCommand() {
|
|
3135
3220
|
const brands = new import_commander5.Command("brands").description("Manage brands");
|
|
3221
|
+
brands.command("connect-facebook <slug>").description("Open the app to connect Facebook Pages; sign in, check beta access and explicitly select Pages there").option("--no-browser", "Print the application link without opening a browser").action(async (slug, opts, cmd) => {
|
|
3222
|
+
const globals = cmd.optsWithGlobals();
|
|
3223
|
+
const printer = createPrinter(globals);
|
|
3224
|
+
try {
|
|
3225
|
+
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3226
|
+
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3227
|
+
const handoff = await client.brands.facebookConnection(slug);
|
|
3228
|
+
printer.log(handoff);
|
|
3229
|
+
if (opts.browser) {
|
|
3230
|
+
try {
|
|
3231
|
+
const { default: open2 } = await import("open");
|
|
3232
|
+
await open2(handoff.url, { wait: true });
|
|
3233
|
+
} catch {
|
|
3234
|
+
printer.error(`Could not open the browser. Open ${handoff.url} manually, then check ap brands auth-status ${slug}.`);
|
|
3235
|
+
process.exitCode = 1;
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
} catch (error) {
|
|
3239
|
+
printer.error(error);
|
|
3240
|
+
process.exitCode = exitCodeFromError(error);
|
|
3241
|
+
}
|
|
3242
|
+
});
|
|
3136
3243
|
brands.command("list").description("List all brands").action(async (_opts, cmd) => {
|
|
3137
3244
|
const globals = cmd.optsWithGlobals();
|
|
3138
3245
|
const printer = createPrinter(globals);
|
|
@@ -3563,6 +3670,7 @@ function createClipsCommand() {
|
|
|
3563
3670
|
|
|
3564
3671
|
// src/commands/posts.ts
|
|
3565
3672
|
var import_commander8 = require("commander");
|
|
3673
|
+
var import_promises3 = require("fs/promises");
|
|
3566
3674
|
init_dist();
|
|
3567
3675
|
|
|
3568
3676
|
// src/lib/post-create.ts
|
|
@@ -3571,7 +3679,7 @@ var import_node_path3 = __toESM(require("path"));
|
|
|
3571
3679
|
init_media_flags();
|
|
3572
3680
|
|
|
3573
3681
|
// src/lib/account-select.ts
|
|
3574
|
-
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
3682
|
+
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube", "facebook"];
|
|
3575
3683
|
var FANOUT_CONFIRM_THRESHOLD = 5;
|
|
3576
3684
|
var PAGED_PICKER_THRESHOLD = 8;
|
|
3577
3685
|
var isAll = (v) => v === "all" || v === "*";
|
|
@@ -3634,24 +3742,28 @@ async function resolveTargetAccounts({
|
|
|
3634
3742
|
}
|
|
3635
3743
|
if (ids.length > 0) result[platform] = ids;
|
|
3636
3744
|
} else if (specifiedValue !== void 0) {
|
|
3637
|
-
const
|
|
3638
|
-
const
|
|
3639
|
-
(
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3745
|
+
const values = platform === "facebook" ? specifiedValue.split(",").map((value) => value.trim()) : [specifiedValue];
|
|
3746
|
+
for (const value of values) {
|
|
3747
|
+
if (!value) throw Object.assign(new Error("--account: Page selection must not contain empty members."), { exitCode: 1 });
|
|
3748
|
+
const normalized = value.startsWith("@") ? value.slice(1) : value;
|
|
3749
|
+
const match = accounts.find(
|
|
3750
|
+
(a) => a.platformUsername?.toLowerCase() === normalized.toLowerCase() || a.platformUserId === value
|
|
3751
|
+
);
|
|
3752
|
+
if (!match?.platformUserId) {
|
|
3753
|
+
const valid = accounts.map((a) => ` @${a.platformUsername ?? "?"} (${a.platformUserId ?? "?"})`).join("\n");
|
|
3754
|
+
throw Object.assign(
|
|
3755
|
+
new Error(
|
|
3756
|
+
`--account: unknown ${platform} account "${value}".
|
|
3646
3757
|
Connected accounts:
|
|
3647
3758
|
${valid || " (none)"}`
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3759
|
+
),
|
|
3760
|
+
{ exitCode: 1 }
|
|
3761
|
+
);
|
|
3762
|
+
}
|
|
3763
|
+
if (!result[platform]) result[platform] = [];
|
|
3764
|
+
if (!result[platform].includes(match.platformUserId)) result[platform].push(match.platformUserId);
|
|
3651
3765
|
}
|
|
3652
|
-
|
|
3653
|
-
result[platform].push(match.platformUserId);
|
|
3654
|
-
} else if (accounts.length >= 2) {
|
|
3766
|
+
} else if (accounts.length >= 2 || platform === "facebook" && accounts.length > 0) {
|
|
3655
3767
|
if (isTty) {
|
|
3656
3768
|
const { checkbox } = await import("@inquirer/prompts");
|
|
3657
3769
|
const choices = accounts.map((a) => ({
|
|
@@ -3682,12 +3794,15 @@ Pass --account ${platform}=<handle|id> to select.`
|
|
|
3682
3794
|
);
|
|
3683
3795
|
}
|
|
3684
3796
|
}
|
|
3797
|
+
if (platform === "facebook" && !result.facebook?.length) {
|
|
3798
|
+
throw Object.assign(new Error("Facebook requires at least one explicitly selected connected Page. Pass --account facebook=<handle|id>."), { exitCode: 1 });
|
|
3799
|
+
}
|
|
3685
3800
|
}
|
|
3686
3801
|
return result;
|
|
3687
3802
|
}
|
|
3688
3803
|
|
|
3689
3804
|
// src/lib/post-create.ts
|
|
3690
|
-
var VALID_PLATFORMS3 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
3805
|
+
var VALID_PLATFORMS3 = ["x", "linkedin", "instagram", "threads", "youtube", "facebook"];
|
|
3691
3806
|
function parsePlatforms(raw) {
|
|
3692
3807
|
const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
3693
3808
|
if (parts.length === 0) {
|
|
@@ -3715,21 +3830,73 @@ function validateScheduledAt(value) {
|
|
|
3715
3830
|
}
|
|
3716
3831
|
return value;
|
|
3717
3832
|
}
|
|
3833
|
+
function validateCanonicalMedia(items) {
|
|
3834
|
+
for (const item of items) {
|
|
3835
|
+
if (!item || typeof item !== "object" || typeof item.url !== "string" || !["image", "video", "gif"].includes(item.type)) throw new Error("Media items require a URL and supported type");
|
|
3836
|
+
let url;
|
|
3837
|
+
try {
|
|
3838
|
+
url = new URL(item.url);
|
|
3839
|
+
} catch {
|
|
3840
|
+
throw new Error("Media URL must use HTTPS");
|
|
3841
|
+
}
|
|
3842
|
+
if (url.protocol !== "https:" || url.username || url.password) throw new Error("Media URL must use credential-free HTTPS");
|
|
3843
|
+
if (item.altText !== void 0 && typeof item.altText !== "string") throw new Error("Media altText must be a string");
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3718
3846
|
async function buildAndCreatePost(client, fields, opts) {
|
|
3719
3847
|
const platforms = parsePlatforms(fields.platforms);
|
|
3720
3848
|
const scheduledAt = fields.at ? validateScheduledAt(fields.at) : void 0;
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3849
|
+
const sharedMedia = fields.media ?? [];
|
|
3850
|
+
if (!Array.isArray(sharedMedia)) throw new Error("media must be an array");
|
|
3851
|
+
const localMedia = sharedMedia.every((item) => typeof item === "string") ? sharedMedia : [];
|
|
3852
|
+
const sharedCanonicalMedia = localMedia.length || !sharedMedia.length ? [] : sharedMedia;
|
|
3853
|
+
validateCanonicalMedia(sharedCanonicalMedia);
|
|
3854
|
+
if (localMedia.length) {
|
|
3855
|
+
validateMediaCount(localMedia);
|
|
3856
|
+
validateMediaPaths(localMedia);
|
|
3857
|
+
validateMediaExtensions(localMedia);
|
|
3725
3858
|
}
|
|
3726
3859
|
const platformTexts = fields.platformText && fields.platformText.length > 0 ? parsePairs("--platform-text", fields.platformText) : void 0;
|
|
3727
|
-
const
|
|
3860
|
+
const suppliedMedia = fields.platformMedia;
|
|
3861
|
+
const canonicalMedia = {};
|
|
3862
|
+
if (suppliedMedia !== void 0 && !Array.isArray(suppliedMedia)) {
|
|
3863
|
+
if (!suppliedMedia || typeof suppliedMedia !== "object") throw new Error("platformMedia must be an object or local path flags");
|
|
3864
|
+
for (const [platform, items] of Object.entries(suppliedMedia)) {
|
|
3865
|
+
if (!VALID_PLATFORMS3.includes(platform) || !Array.isArray(items)) throw new Error("platformMedia requires supported platforms and media arrays");
|
|
3866
|
+
validateCanonicalMedia(items);
|
|
3867
|
+
canonicalMedia[platform] = items;
|
|
3868
|
+
}
|
|
3869
|
+
}
|
|
3870
|
+
const platformMediaPaths = Array.isArray(suppliedMedia) && suppliedMedia.length > 0 ? parsePlatformMediaPairs("--platform-media", suppliedMedia) : {};
|
|
3728
3871
|
for (const paths of Object.values(platformMediaPaths)) {
|
|
3729
3872
|
validateMediaPaths(paths);
|
|
3730
3873
|
validateMediaExtensions(paths);
|
|
3731
3874
|
}
|
|
3732
|
-
|
|
3875
|
+
let facebookOptions;
|
|
3876
|
+
if (platforms.includes("facebook")) {
|
|
3877
|
+
const format = fields.facebookFormat;
|
|
3878
|
+
if (!format || !["text", "link", "photo", "multi-photo", "video", "reel"].includes(format)) {
|
|
3879
|
+
throw new Error("--facebook-format must be text, link, photo, multi-photo, video or reel");
|
|
3880
|
+
}
|
|
3881
|
+
const paths = canonicalMedia.facebook ?? platformMediaPaths.facebook ?? fields.media ?? [];
|
|
3882
|
+
const images = paths.every((item) => typeof item === "string" ? extToMime(item).startsWith("image/") : item.type === "image");
|
|
3883
|
+
const videos = paths.every((item) => typeof item === "string" ? extToMime(item).startsWith("video/") : item.type === "video");
|
|
3884
|
+
if ((format === "text" || format === "link") && paths.length || format === "photo" && (paths.length !== 1 || !images) || format === "multi-photo" && (paths.length < 2 || !images) || (format === "video" || format === "reel") && (paths.length !== 1 || !videos)) {
|
|
3885
|
+
throw new Error("Facebook attachments do not match --facebook-format");
|
|
3886
|
+
}
|
|
3887
|
+
if (format === "text" && !(platformTexts?.facebook ?? fields.text).trim()) throw new Error("Facebook text format requires a caption");
|
|
3888
|
+
if (format === "link") {
|
|
3889
|
+
let link;
|
|
3890
|
+
try {
|
|
3891
|
+
link = new URL(fields.facebookLink ?? "");
|
|
3892
|
+
} catch {
|
|
3893
|
+
throw new Error("--facebook-link must be a public HTTPS URL");
|
|
3894
|
+
}
|
|
3895
|
+
if (link.protocol !== "https:" || link.username || link.password) throw new Error("--facebook-link must be a public HTTPS URL");
|
|
3896
|
+
}
|
|
3897
|
+
facebookOptions = { format, ...format === "link" ? { link: fields.facebookLink } : {} };
|
|
3898
|
+
}
|
|
3899
|
+
const altTexts = alignAltText(localMedia, fields.altText ?? []);
|
|
3733
3900
|
const youtubeOptions = buildYoutubeOptions({
|
|
3734
3901
|
ytTitle: fields.ytTitle,
|
|
3735
3902
|
ytDescription: fields.ytDescription,
|
|
@@ -3768,6 +3935,7 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3768
3935
|
...instagramOptions ? { instagramOptions } : {},
|
|
3769
3936
|
...threadsOptions ? { threadsOptions } : {},
|
|
3770
3937
|
...youtubeOptions ? { youtubeOptions } : {},
|
|
3938
|
+
...facebookOptions ? { facebookOptions } : {},
|
|
3771
3939
|
source: "cli"
|
|
3772
3940
|
};
|
|
3773
3941
|
if (opts.dryRun) {
|
|
@@ -3776,19 +3944,19 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3776
3944
|
request: {
|
|
3777
3945
|
...common,
|
|
3778
3946
|
...fields.media && fields.media.length > 0 ? {
|
|
3779
|
-
media:
|
|
3947
|
+
media: sharedCanonicalMedia.length ? sharedCanonicalMedia : localMedia.map((path3, i) => ({
|
|
3780
3948
|
path: path3,
|
|
3781
3949
|
...altTexts[i] ? { altText: altTexts[i] } : {}
|
|
3782
3950
|
}))
|
|
3783
3951
|
} : {},
|
|
3784
|
-
...Object.keys(platformMediaPaths).length > 0 ? { platformMedia: platformMediaPaths } : {}
|
|
3952
|
+
...Object.keys(platformMediaPaths).length + Object.keys(canonicalMedia).length > 0 ? { platformMedia: { ...canonicalMedia, ...platformMediaPaths } } : {}
|
|
3785
3953
|
}
|
|
3786
3954
|
};
|
|
3787
3955
|
}
|
|
3788
3956
|
opts.onBeforeNetwork?.();
|
|
3789
|
-
const mediaInputs = [];
|
|
3790
|
-
for (let i = 0; i <
|
|
3791
|
-
const filePath =
|
|
3957
|
+
const mediaInputs = [...sharedCanonicalMedia];
|
|
3958
|
+
for (let i = 0; i < localMedia.length; i++) {
|
|
3959
|
+
const filePath = localMedia[i];
|
|
3792
3960
|
const data = await import_promises2.default.readFile(filePath);
|
|
3793
3961
|
const filename = import_node_path3.default.basename(filePath);
|
|
3794
3962
|
const uploaded = await client.media.upload({
|
|
@@ -3802,7 +3970,7 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3802
3970
|
...altTexts[i] ? { altText: altTexts[i] } : {}
|
|
3803
3971
|
});
|
|
3804
3972
|
}
|
|
3805
|
-
const platformMediaResult = {};
|
|
3973
|
+
const platformMediaResult = { ...canonicalMedia };
|
|
3806
3974
|
for (const [p, paths] of Object.entries(platformMediaPaths)) {
|
|
3807
3975
|
const uploads = [];
|
|
3808
3976
|
for (const filePath of paths) {
|
|
@@ -3894,16 +4062,44 @@ function rowToFields(row, cliBrand) {
|
|
|
3894
4062
|
if (!brand) {
|
|
3895
4063
|
throw new Error('No brand: set "brand" in the row, pass --brand, or set a default context.');
|
|
3896
4064
|
}
|
|
3897
|
-
if (
|
|
4065
|
+
if (typeof row.text !== "string") throw new Error('Missing "text" or text is not a string.');
|
|
3898
4066
|
if (!platforms) throw new Error('Missing "platforms".');
|
|
3899
4067
|
for (const key of ["media", "thread", "account", "altText"]) {
|
|
3900
4068
|
if (row[key] !== void 0 && !Array.isArray(row[key])) {
|
|
3901
4069
|
throw new Error(`"${key}" is multi-value \u2014 use a JSON input file with an array (CSV can't express it).`);
|
|
3902
4070
|
}
|
|
3903
4071
|
}
|
|
4072
|
+
let platformText = row.platformText;
|
|
4073
|
+
if (row.platformTexts !== void 0) {
|
|
4074
|
+
if (!row.platformTexts || typeof row.platformTexts !== "object" || Array.isArray(row.platformTexts)) throw new Error("platformTexts must map platforms to caption strings");
|
|
4075
|
+
const captions = Object.entries(row.platformTexts).map(([platform, text]) => {
|
|
4076
|
+
if (typeof text !== "string") throw new Error("platformTexts values must be strings");
|
|
4077
|
+
return `${platform}=${text}`;
|
|
4078
|
+
});
|
|
4079
|
+
platformText = platformText ?? captions;
|
|
4080
|
+
}
|
|
4081
|
+
let facebook;
|
|
4082
|
+
if (row.facebookOptions !== void 0) {
|
|
4083
|
+
if (!row.facebookOptions || typeof row.facebookOptions !== "object" || Array.isArray(row.facebookOptions)) throw new Error("facebookOptions must be an object");
|
|
4084
|
+
facebook = row.facebookOptions;
|
|
4085
|
+
if (typeof facebook.format !== "string" || facebook.link !== void 0 && typeof facebook.link !== "string") throw new Error("facebookOptions requires a string format and optional string link");
|
|
4086
|
+
}
|
|
4087
|
+
let account = row.account;
|
|
4088
|
+
if (row.targetAccountIds !== void 0) {
|
|
4089
|
+
if (!row.targetAccountIds || typeof row.targetAccountIds !== "object" || Array.isArray(row.targetAccountIds)) throw new Error("targetAccountIds must map platforms to account ID arrays");
|
|
4090
|
+
const flags = Object.entries(row.targetAccountIds).map(([platform, ids]) => {
|
|
4091
|
+
if (!Array.isArray(ids) || ids.length === 0 || ids.some((id) => typeof id !== "string" || !id.trim() || id.includes(","))) throw new Error("targetAccountIds requires nonempty account ID arrays");
|
|
4092
|
+
return `${platform}=${ids.join(",")}`;
|
|
4093
|
+
});
|
|
4094
|
+
account = account ?? flags;
|
|
4095
|
+
}
|
|
3904
4096
|
return {
|
|
3905
4097
|
...row,
|
|
4098
|
+
...facebook ? { facebookFormat: facebook.format, facebookLink: facebook.link } : {},
|
|
4099
|
+
...account ? { account } : {},
|
|
4100
|
+
...platformText ? { platformText } : {},
|
|
3906
4101
|
brandSlug: brand,
|
|
4102
|
+
at: row.at ?? row.scheduledAt,
|
|
3907
4103
|
text: String(row.text),
|
|
3908
4104
|
platforms: String(platforms)
|
|
3909
4105
|
};
|
|
@@ -3992,7 +4188,7 @@ function createPostsCommand() {
|
|
|
3992
4188
|
posts.command("create").description("Create a new post").option("--brand <slug>", "Brand slug (defaults to the saved context: ap config set-context)").option("--text <text>", "Post text content (required unless --from)").option("--platforms <list>", "Comma-separated platforms, e.g. x,linkedin (required unless --from)").option("--from <file>", "Create posts in bulk from a CSV or JSON file (one post per row)").option("--at <iso>", "Schedule date/time (ISO 8601)").option(
|
|
3993
4189
|
"--thread <text...>",
|
|
3994
4190
|
"Additional posts appended after --text to form a thread (X/Threads only, max 25)"
|
|
3995
|
-
).option("--media <path...>", "Media file paths to attach (max 10)").option("--alt-text <text...>", "Alt text for --media files, aligned by index").option("--platform-text <pairs...>", "Per-platform text, e.g. x=Hello linkedin=World").option("--platform-media <pairs...>", "Per-platform media, e.g. instagram=photo.jpg x=banner.png").option("--yt-title <t>", "YouTube video title").option("--yt-description <d>", "YouTube video description").option("--yt-tags <list>", "YouTube tags (comma-separated)").option("--yt-privacy <v>", "YouTube privacy status (public|unlisted|private)").option("--yt-category <id>", "YouTube category ID").option("--yt-made-for-kids", "Mark YouTube video as made for kids").option("--ig-reel", "Post as an Instagram Reel").option("--ig-share-to-feed", "Share Reel to feed").option("--ig-cover-url <url>", "Reel cover image URL").option("--ig-thumb-offset-ms <n>", "Reel thumbnail offset in milliseconds").option("--ig-collaborators <list>", "Reel collaborator handles (comma-separated)").option("--threads-reply-to <id>", "Threads post ID to reply to").option("--threads-reply-control <v>", "Threads reply control (everyone|accounts_you_follow|mentioned_only)").option("--account <p=handle|id...>", "Target specific account per platform, e.g. x=@handle").option("--dry-run", "Print the resolved request body without uploading media or creating the post").option("--preview", "Alias for --dry-run").action(
|
|
4191
|
+
).option("--media <path...>", "Media file paths to attach (max 10)").option("--alt-text <text...>", "Alt text for --media files, aligned by index").option("--platform-text <pairs...>", "Per-platform text, e.g. x=Hello linkedin=World").option("--platform-media <pairs...>", "Per-platform media, e.g. instagram=photo.jpg x=banner.png").option("--yt-title <t>", "YouTube video title").option("--yt-description <d>", "YouTube video description").option("--yt-tags <list>", "YouTube tags (comma-separated)").option("--yt-privacy <v>", "YouTube privacy status (public|unlisted|private)").option("--yt-category <id>", "YouTube category ID").option("--yt-made-for-kids", "Mark YouTube video as made for kids").option("--ig-reel", "Post as an Instagram Reel").option("--ig-share-to-feed", "Share Reel to feed").option("--ig-cover-url <url>", "Reel cover image URL").option("--ig-thumb-offset-ms <n>", "Reel thumbnail offset in milliseconds").option("--ig-collaborators <list>", "Reel collaborator handles (comma-separated)").option("--threads-reply-to <id>", "Threads post ID to reply to").option("--threads-reply-control <v>", "Threads reply control (everyone|accounts_you_follow|mentioned_only)").option("--facebook-format <format>", "Facebook Pages: text|link|photo|multi-photo|video|reel").option("--facebook-link <url>", "Public HTTPS article URL for Facebook link format").option("--account <p=handle|id...>", "Target specific account per platform, e.g. x=@handle; Facebook Page lists: facebook=page-id-1,page-id-2").option("--dry-run", "Print the resolved request body without uploading media or creating the post").option("--preview", "Alias for --dry-run").action(
|
|
3996
4192
|
async (opts, cmd) => {
|
|
3997
4193
|
const globals = cmd.optsWithGlobals();
|
|
3998
4194
|
const printer = createPrinter(globals);
|
|
@@ -4058,7 +4254,7 @@ function createPostsCommand() {
|
|
|
4058
4254
|
}
|
|
4059
4255
|
}
|
|
4060
4256
|
);
|
|
4061
|
-
posts.command("update <id>").description("Update a post").option("--text <text>", "New post text").option("--platforms <list>", "New comma-separated platforms").option("--at <iso>", "New scheduled date/time (ISO 8601)").action(
|
|
4257
|
+
posts.command("update <id>").description("Update a post").option("--from <file>", "JSON update object with formats, target account IDs, captions and media URLs; explicit flags override file fields").option("--text <text>", "New post text").option("--platforms <list>", "New comma-separated platforms").option("--at <iso>", "New scheduled date/time (ISO 8601)").action(
|
|
4062
4258
|
async (id, opts, cmd) => {
|
|
4063
4259
|
const globals = cmd.optsWithGlobals();
|
|
4064
4260
|
const printer = createPrinter(globals);
|
|
@@ -4066,8 +4262,26 @@ function createPostsCommand() {
|
|
|
4066
4262
|
try {
|
|
4067
4263
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4068
4264
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4265
|
+
let patch = {};
|
|
4266
|
+
if (opts.from) {
|
|
4267
|
+
const parsed = JSON.parse(await (0, import_promises3.readFile)(opts.from, "utf8"));
|
|
4268
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("--from requires one JSON update object");
|
|
4269
|
+
const allowed = ["text", "platforms", "scheduledAt", "media", "platformMedia", "platformTexts", "targetAccountIds", "facebookOptions"];
|
|
4270
|
+
if (Object.keys(parsed).some((key) => !allowed.includes(key))) throw new Error("--from contains an unsupported update field");
|
|
4271
|
+
patch = parsed;
|
|
4272
|
+
if (patch.text !== void 0 && typeof patch.text !== "string") throw new Error("Update text must be a string");
|
|
4273
|
+
if (patch.platforms !== void 0) {
|
|
4274
|
+
if (!Array.isArray(patch.platforms) || patch.platforms.some((platform) => typeof platform !== "string" || !platform.trim() || platform.includes(","))) throw new Error("Update platforms must be an array of platform names");
|
|
4275
|
+
patch.platforms = parsePlatforms(patch.platforms.join(","));
|
|
4276
|
+
}
|
|
4277
|
+
if (patch.scheduledAt !== void 0) {
|
|
4278
|
+
if (typeof patch.scheduledAt !== "string") throw new Error("Update scheduledAt must be an ISO datetime string");
|
|
4279
|
+
patch.scheduledAt = validateScheduledAt(patch.scheduledAt);
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4069
4282
|
const post = await client.posts.update(id, {
|
|
4070
|
-
...
|
|
4283
|
+
...patch,
|
|
4284
|
+
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
4071
4285
|
...opts.platforms ? { platforms: parsePlatforms(opts.platforms) } : {},
|
|
4072
4286
|
...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {}
|
|
4073
4287
|
});
|
|
@@ -4119,7 +4333,7 @@ function createPostsCommand() {
|
|
|
4119
4333
|
process.exit(resolveExitCode5(err));
|
|
4120
4334
|
}
|
|
4121
4335
|
});
|
|
4122
|
-
posts.command("schedule <id>").description("Schedule a post for a specific time, or --cancel to unschedule it").option("--at <iso>", "ISO 8601 datetime to schedule the post").option("--cancel", "
|
|
4336
|
+
posts.command("schedule <id>").description("Schedule a post for a specific time, or --cancel to unschedule it").option("--at <iso>", "ISO 8601 datetime to schedule the post").option("--cancel", "Cancel scheduling").option("--platform <platform>", "facebook schedules only eligible failed Pages for recovery").action(async (id, opts, cmd) => {
|
|
4123
4337
|
const globals = cmd.optsWithGlobals();
|
|
4124
4338
|
const printer = createPrinter(globals);
|
|
4125
4339
|
const spinner = printer.spinner(
|
|
@@ -4132,9 +4346,10 @@ function createPostsCommand() {
|
|
|
4132
4346
|
if (!opts.cancel && !opts.at) {
|
|
4133
4347
|
throw new Error("Provide either --at <iso> to schedule, or --cancel to unschedule.");
|
|
4134
4348
|
}
|
|
4349
|
+
if (opts.platform && (opts.platform !== "facebook" || opts.cancel)) throw new Error("--platform facebook requires --at and cannot be used with --cancel.");
|
|
4135
4350
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4136
4351
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4137
|
-
const post = opts.cancel ? await client.posts.unschedule(id) : await client.posts.schedule(id, validateScheduledAt(opts.at));
|
|
4352
|
+
const post = opts.cancel ? await client.posts.unschedule(id) : opts.platform === "facebook" ? await client.posts.schedule(id, validateScheduledAt(opts.at), "facebook") : await client.posts.schedule(id, validateScheduledAt(opts.at));
|
|
4138
4353
|
spinner.stop();
|
|
4139
4354
|
printer.log(post);
|
|
4140
4355
|
} catch (err) {
|
|
@@ -4143,14 +4358,16 @@ function createPostsCommand() {
|
|
|
4143
4358
|
process.exit(resolveExitCode5(err));
|
|
4144
4359
|
}
|
|
4145
4360
|
});
|
|
4146
|
-
posts.command("retry <id>").description("Retry a failed post").action(async (id,
|
|
4361
|
+
posts.command("retry <id>").description("Retry a failed post").option("--platform <platform>", "Select a platform; facebook retries only failed Pages without remote evidence").action(async (id, opts, cmd) => {
|
|
4147
4362
|
const globals = cmd.optsWithGlobals();
|
|
4148
4363
|
const printer = createPrinter(globals);
|
|
4149
4364
|
const spinner = printer.spinner(`Retrying post "${id}"\u2026`);
|
|
4150
4365
|
try {
|
|
4151
4366
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4152
4367
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4153
|
-
const
|
|
4368
|
+
const platform = opts.platform ? parsePlatforms(opts.platform) : void 0;
|
|
4369
|
+
if (platform && platform.length !== 1) throw new Error("--platform requires exactly one platform");
|
|
4370
|
+
const post = platform ? await client.posts.retry(id, platform[0]) : await client.posts.retry(id);
|
|
4154
4371
|
spinner.stop();
|
|
4155
4372
|
printer.log(post);
|
|
4156
4373
|
} catch (err) {
|