@autoposting.ai/cli 0.5.0 → 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 +291 -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();
|
|
@@ -2936,6 +3021,10 @@ function createBillingCommand() {
|
|
|
2936
3021
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
2937
3022
|
const credits = await client.billing.credits();
|
|
2938
3023
|
spinner.stop();
|
|
3024
|
+
if (!printer.isTty()) {
|
|
3025
|
+
printer.log(credits);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
2939
3028
|
printer.table(
|
|
2940
3029
|
[
|
|
2941
3030
|
{
|
|
@@ -3129,6 +3218,28 @@ function buildAuthStatusRow(c, now = Date.now()) {
|
|
|
3129
3218
|
}
|
|
3130
3219
|
function createBrandsCommand() {
|
|
3131
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
|
+
});
|
|
3132
3243
|
brands.command("list").description("List all brands").action(async (_opts, cmd) => {
|
|
3133
3244
|
const globals = cmd.optsWithGlobals();
|
|
3134
3245
|
const printer = createPrinter(globals);
|
|
@@ -3559,6 +3670,7 @@ function createClipsCommand() {
|
|
|
3559
3670
|
|
|
3560
3671
|
// src/commands/posts.ts
|
|
3561
3672
|
var import_commander8 = require("commander");
|
|
3673
|
+
var import_promises3 = require("fs/promises");
|
|
3562
3674
|
init_dist();
|
|
3563
3675
|
|
|
3564
3676
|
// src/lib/post-create.ts
|
|
@@ -3567,7 +3679,7 @@ var import_node_path3 = __toESM(require("path"));
|
|
|
3567
3679
|
init_media_flags();
|
|
3568
3680
|
|
|
3569
3681
|
// src/lib/account-select.ts
|
|
3570
|
-
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
3682
|
+
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube", "facebook"];
|
|
3571
3683
|
var FANOUT_CONFIRM_THRESHOLD = 5;
|
|
3572
3684
|
var PAGED_PICKER_THRESHOLD = 8;
|
|
3573
3685
|
var isAll = (v) => v === "all" || v === "*";
|
|
@@ -3630,24 +3742,28 @@ async function resolveTargetAccounts({
|
|
|
3630
3742
|
}
|
|
3631
3743
|
if (ids.length > 0) result[platform] = ids;
|
|
3632
3744
|
} else if (specifiedValue !== void 0) {
|
|
3633
|
-
const
|
|
3634
|
-
const
|
|
3635
|
-
(
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
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}".
|
|
3642
3757
|
Connected accounts:
|
|
3643
3758
|
${valid || " (none)"}`
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3759
|
+
),
|
|
3760
|
+
{ exitCode: 1 }
|
|
3761
|
+
);
|
|
3762
|
+
}
|
|
3763
|
+
if (!result[platform]) result[platform] = [];
|
|
3764
|
+
if (!result[platform].includes(match.platformUserId)) result[platform].push(match.platformUserId);
|
|
3647
3765
|
}
|
|
3648
|
-
|
|
3649
|
-
result[platform].push(match.platformUserId);
|
|
3650
|
-
} else if (accounts.length >= 2) {
|
|
3766
|
+
} else if (accounts.length >= 2 || platform === "facebook" && accounts.length > 0) {
|
|
3651
3767
|
if (isTty) {
|
|
3652
3768
|
const { checkbox } = await import("@inquirer/prompts");
|
|
3653
3769
|
const choices = accounts.map((a) => ({
|
|
@@ -3678,12 +3794,15 @@ Pass --account ${platform}=<handle|id> to select.`
|
|
|
3678
3794
|
);
|
|
3679
3795
|
}
|
|
3680
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
|
+
}
|
|
3681
3800
|
}
|
|
3682
3801
|
return result;
|
|
3683
3802
|
}
|
|
3684
3803
|
|
|
3685
3804
|
// src/lib/post-create.ts
|
|
3686
|
-
var VALID_PLATFORMS3 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
3805
|
+
var VALID_PLATFORMS3 = ["x", "linkedin", "instagram", "threads", "youtube", "facebook"];
|
|
3687
3806
|
function parsePlatforms(raw) {
|
|
3688
3807
|
const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
3689
3808
|
if (parts.length === 0) {
|
|
@@ -3711,21 +3830,73 @@ function validateScheduledAt(value) {
|
|
|
3711
3830
|
}
|
|
3712
3831
|
return value;
|
|
3713
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
|
+
}
|
|
3714
3846
|
async function buildAndCreatePost(client, fields, opts) {
|
|
3715
3847
|
const platforms = parsePlatforms(fields.platforms);
|
|
3716
3848
|
const scheduledAt = fields.at ? validateScheduledAt(fields.at) : void 0;
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
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);
|
|
3721
3858
|
}
|
|
3722
3859
|
const platformTexts = fields.platformText && fields.platformText.length > 0 ? parsePairs("--platform-text", fields.platformText) : void 0;
|
|
3723
|
-
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) : {};
|
|
3724
3871
|
for (const paths of Object.values(platformMediaPaths)) {
|
|
3725
3872
|
validateMediaPaths(paths);
|
|
3726
3873
|
validateMediaExtensions(paths);
|
|
3727
3874
|
}
|
|
3728
|
-
|
|
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 ?? []);
|
|
3729
3900
|
const youtubeOptions = buildYoutubeOptions({
|
|
3730
3901
|
ytTitle: fields.ytTitle,
|
|
3731
3902
|
ytDescription: fields.ytDescription,
|
|
@@ -3764,6 +3935,7 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3764
3935
|
...instagramOptions ? { instagramOptions } : {},
|
|
3765
3936
|
...threadsOptions ? { threadsOptions } : {},
|
|
3766
3937
|
...youtubeOptions ? { youtubeOptions } : {},
|
|
3938
|
+
...facebookOptions ? { facebookOptions } : {},
|
|
3767
3939
|
source: "cli"
|
|
3768
3940
|
};
|
|
3769
3941
|
if (opts.dryRun) {
|
|
@@ -3772,19 +3944,19 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3772
3944
|
request: {
|
|
3773
3945
|
...common,
|
|
3774
3946
|
...fields.media && fields.media.length > 0 ? {
|
|
3775
|
-
media:
|
|
3947
|
+
media: sharedCanonicalMedia.length ? sharedCanonicalMedia : localMedia.map((path3, i) => ({
|
|
3776
3948
|
path: path3,
|
|
3777
3949
|
...altTexts[i] ? { altText: altTexts[i] } : {}
|
|
3778
3950
|
}))
|
|
3779
3951
|
} : {},
|
|
3780
|
-
...Object.keys(platformMediaPaths).length > 0 ? { platformMedia: platformMediaPaths } : {}
|
|
3952
|
+
...Object.keys(platformMediaPaths).length + Object.keys(canonicalMedia).length > 0 ? { platformMedia: { ...canonicalMedia, ...platformMediaPaths } } : {}
|
|
3781
3953
|
}
|
|
3782
3954
|
};
|
|
3783
3955
|
}
|
|
3784
3956
|
opts.onBeforeNetwork?.();
|
|
3785
|
-
const mediaInputs = [];
|
|
3786
|
-
for (let i = 0; i <
|
|
3787
|
-
const filePath =
|
|
3957
|
+
const mediaInputs = [...sharedCanonicalMedia];
|
|
3958
|
+
for (let i = 0; i < localMedia.length; i++) {
|
|
3959
|
+
const filePath = localMedia[i];
|
|
3788
3960
|
const data = await import_promises2.default.readFile(filePath);
|
|
3789
3961
|
const filename = import_node_path3.default.basename(filePath);
|
|
3790
3962
|
const uploaded = await client.media.upload({
|
|
@@ -3798,7 +3970,7 @@ async function buildAndCreatePost(client, fields, opts) {
|
|
|
3798
3970
|
...altTexts[i] ? { altText: altTexts[i] } : {}
|
|
3799
3971
|
});
|
|
3800
3972
|
}
|
|
3801
|
-
const platformMediaResult = {};
|
|
3973
|
+
const platformMediaResult = { ...canonicalMedia };
|
|
3802
3974
|
for (const [p, paths] of Object.entries(platformMediaPaths)) {
|
|
3803
3975
|
const uploads = [];
|
|
3804
3976
|
for (const filePath of paths) {
|
|
@@ -3890,16 +4062,44 @@ function rowToFields(row, cliBrand) {
|
|
|
3890
4062
|
if (!brand) {
|
|
3891
4063
|
throw new Error('No brand: set "brand" in the row, pass --brand, or set a default context.');
|
|
3892
4064
|
}
|
|
3893
|
-
if (
|
|
4065
|
+
if (typeof row.text !== "string") throw new Error('Missing "text" or text is not a string.');
|
|
3894
4066
|
if (!platforms) throw new Error('Missing "platforms".');
|
|
3895
4067
|
for (const key of ["media", "thread", "account", "altText"]) {
|
|
3896
4068
|
if (row[key] !== void 0 && !Array.isArray(row[key])) {
|
|
3897
4069
|
throw new Error(`"${key}" is multi-value \u2014 use a JSON input file with an array (CSV can't express it).`);
|
|
3898
4070
|
}
|
|
3899
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
|
+
}
|
|
3900
4096
|
return {
|
|
3901
4097
|
...row,
|
|
4098
|
+
...facebook ? { facebookFormat: facebook.format, facebookLink: facebook.link } : {},
|
|
4099
|
+
...account ? { account } : {},
|
|
4100
|
+
...platformText ? { platformText } : {},
|
|
3902
4101
|
brandSlug: brand,
|
|
4102
|
+
at: row.at ?? row.scheduledAt,
|
|
3903
4103
|
text: String(row.text),
|
|
3904
4104
|
platforms: String(platforms)
|
|
3905
4105
|
};
|
|
@@ -3988,7 +4188,7 @@ function createPostsCommand() {
|
|
|
3988
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(
|
|
3989
4189
|
"--thread <text...>",
|
|
3990
4190
|
"Additional posts appended after --text to form a thread (X/Threads only, max 25)"
|
|
3991
|
-
).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(
|
|
3992
4192
|
async (opts, cmd) => {
|
|
3993
4193
|
const globals = cmd.optsWithGlobals();
|
|
3994
4194
|
const printer = createPrinter(globals);
|
|
@@ -4054,7 +4254,7 @@ function createPostsCommand() {
|
|
|
4054
4254
|
}
|
|
4055
4255
|
}
|
|
4056
4256
|
);
|
|
4057
|
-
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(
|
|
4058
4258
|
async (id, opts, cmd) => {
|
|
4059
4259
|
const globals = cmd.optsWithGlobals();
|
|
4060
4260
|
const printer = createPrinter(globals);
|
|
@@ -4062,8 +4262,26 @@ function createPostsCommand() {
|
|
|
4062
4262
|
try {
|
|
4063
4263
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4064
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
|
+
}
|
|
4065
4282
|
const post = await client.posts.update(id, {
|
|
4066
|
-
...
|
|
4283
|
+
...patch,
|
|
4284
|
+
...opts.text !== void 0 ? { text: opts.text } : {},
|
|
4067
4285
|
...opts.platforms ? { platforms: parsePlatforms(opts.platforms) } : {},
|
|
4068
4286
|
...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {}
|
|
4069
4287
|
});
|
|
@@ -4115,7 +4333,7 @@ function createPostsCommand() {
|
|
|
4115
4333
|
process.exit(resolveExitCode5(err));
|
|
4116
4334
|
}
|
|
4117
4335
|
});
|
|
4118
|
-
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) => {
|
|
4119
4337
|
const globals = cmd.optsWithGlobals();
|
|
4120
4338
|
const printer = createPrinter(globals);
|
|
4121
4339
|
const spinner = printer.spinner(
|
|
@@ -4128,9 +4346,10 @@ function createPostsCommand() {
|
|
|
4128
4346
|
if (!opts.cancel && !opts.at) {
|
|
4129
4347
|
throw new Error("Provide either --at <iso> to schedule, or --cancel to unschedule.");
|
|
4130
4348
|
}
|
|
4349
|
+
if (opts.platform && (opts.platform !== "facebook" || opts.cancel)) throw new Error("--platform facebook requires --at and cannot be used with --cancel.");
|
|
4131
4350
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4132
4351
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4133
|
-
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));
|
|
4134
4353
|
spinner.stop();
|
|
4135
4354
|
printer.log(post);
|
|
4136
4355
|
} catch (err) {
|
|
@@ -4139,14 +4358,16 @@ function createPostsCommand() {
|
|
|
4139
4358
|
process.exit(resolveExitCode5(err));
|
|
4140
4359
|
}
|
|
4141
4360
|
});
|
|
4142
|
-
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) => {
|
|
4143
4362
|
const globals = cmd.optsWithGlobals();
|
|
4144
4363
|
const printer = createPrinter(globals);
|
|
4145
4364
|
const spinner = printer.spinner(`Retrying post "${id}"\u2026`);
|
|
4146
4365
|
try {
|
|
4147
4366
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4148
4367
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4149
|
-
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);
|
|
4150
4371
|
spinner.stop();
|
|
4151
4372
|
printer.log(post);
|
|
4152
4373
|
} catch (err) {
|
|
@@ -4479,6 +4700,10 @@ function createUsageCommand() {
|
|
|
4479
4700
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4480
4701
|
const summary = await client.usage.summary();
|
|
4481
4702
|
spinner.stop();
|
|
4703
|
+
if (!printer.isTty()) {
|
|
4704
|
+
printer.log(summary);
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4482
4707
|
printer.log(`Period: ${summary.range.from.slice(0, 10)} \u2192 ${summary.range.to.slice(0, 10)}`);
|
|
4483
4708
|
printer.table(
|
|
4484
4709
|
[
|