@autoposting.ai/cli 0.3.6 → 0.4.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/dist/cli.cjs +375 -131
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -79,7 +79,7 @@ var VERSION, AutopostingError, AuthenticationError, ScopeError, NotFoundError, V
|
|
|
79
79
|
var init_dist = __esm({
|
|
80
80
|
"../sdk/dist/index.mjs"() {
|
|
81
81
|
"use strict";
|
|
82
|
-
VERSION = "0.
|
|
82
|
+
VERSION = "0.4.0";
|
|
83
83
|
AutopostingError = class extends Error {
|
|
84
84
|
status;
|
|
85
85
|
code;
|
|
@@ -245,8 +245,8 @@ var init_dist = __esm({
|
|
|
245
245
|
throw new AutopostingError("brandId is required to upload a clip.", 0, "VALIDATION_ERROR");
|
|
246
246
|
}
|
|
247
247
|
const { open: open2, stat } = await import("fs/promises");
|
|
248
|
-
const { basename } = await import("path");
|
|
249
|
-
const fileName =
|
|
248
|
+
const { basename: basename2 } = await import("path");
|
|
249
|
+
const fileName = basename2(filePath);
|
|
250
250
|
const title = options.title ?? fileName;
|
|
251
251
|
const mimeType = options.contentType ?? guessVideoMime(fileName);
|
|
252
252
|
const { size: fileSize } = await stat(filePath);
|
|
@@ -663,6 +663,131 @@ var init_dist = __esm({
|
|
|
663
663
|
}
|
|
664
664
|
});
|
|
665
665
|
|
|
666
|
+
// src/lib/media-flags.ts
|
|
667
|
+
function extToMime(filename) {
|
|
668
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
669
|
+
const mime = EXT_TO_MIME[ext];
|
|
670
|
+
if (!mime) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`Unsupported file extension ".${ext || "(none)"}" in "${filename}". Supported: ${Object.keys(EXT_TO_MIME).join(", ")}`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
return mime;
|
|
676
|
+
}
|
|
677
|
+
function parsePairs(flag, vals) {
|
|
678
|
+
const result = {};
|
|
679
|
+
for (const val of vals) {
|
|
680
|
+
const eqIdx = val.indexOf("=");
|
|
681
|
+
if (eqIdx < 1) {
|
|
682
|
+
throw new Error(`${flag}: expected "platform=value" format, got "${val}"`);
|
|
683
|
+
}
|
|
684
|
+
const p = val.slice(0, eqIdx).trim();
|
|
685
|
+
const v = val.slice(eqIdx + 1);
|
|
686
|
+
if (!VALID_PLATFORMS.includes(p)) {
|
|
687
|
+
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
688
|
+
}
|
|
689
|
+
result[p] = v;
|
|
690
|
+
}
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
function parsePlatformMediaPairs(flag, vals) {
|
|
694
|
+
const result = {};
|
|
695
|
+
for (const val of vals) {
|
|
696
|
+
const eqIdx = val.indexOf("=");
|
|
697
|
+
if (eqIdx < 1) {
|
|
698
|
+
throw new Error(`${flag}: expected "platform=path[,path...]" format, got "${val}"`);
|
|
699
|
+
}
|
|
700
|
+
const p = val.slice(0, eqIdx).trim();
|
|
701
|
+
const paths = val.slice(eqIdx + 1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
702
|
+
if (!VALID_PLATFORMS.includes(p)) {
|
|
703
|
+
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
704
|
+
}
|
|
705
|
+
result[p] = paths;
|
|
706
|
+
}
|
|
707
|
+
return result;
|
|
708
|
+
}
|
|
709
|
+
function validateMediaExtensions(paths) {
|
|
710
|
+
for (const p of paths) extToMime(p);
|
|
711
|
+
}
|
|
712
|
+
function validateMediaCount(paths) {
|
|
713
|
+
if (paths.length > 10) {
|
|
714
|
+
throw new Error(`--media accepts at most 10 files (received ${paths.length}).`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function validateMediaPaths(paths) {
|
|
718
|
+
for (const p of paths) {
|
|
719
|
+
if (!(0, import_node_fs2.existsSync)(p)) {
|
|
720
|
+
throw new Error(`Media file not found: "${p}"`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function alignAltText(mediaPaths, altTexts) {
|
|
725
|
+
if (altTexts.length > mediaPaths.length) {
|
|
726
|
+
throw new Error(
|
|
727
|
+
`--alt-text has more values (${altTexts.length}) than --media (${mediaPaths.length}). Alt text aligns to media by index.`
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
return mediaPaths.map((_, i) => altTexts[i]);
|
|
731
|
+
}
|
|
732
|
+
function buildYoutubeOptions(opts) {
|
|
733
|
+
const { ytTitle, ytDescription, ytTags, ytPrivacy, ytCategory, ytMadeForKids } = opts;
|
|
734
|
+
const o = {};
|
|
735
|
+
if (ytTitle) o.title = ytTitle;
|
|
736
|
+
if (ytDescription) o.description = ytDescription;
|
|
737
|
+
if (ytTags) o.tags = ytTags.split(",").map((t) => t.trim()).filter(Boolean);
|
|
738
|
+
if (ytPrivacy) o.privacyStatus = ytPrivacy;
|
|
739
|
+
if (ytCategory) o.categoryId = ytCategory;
|
|
740
|
+
if (ytMadeForKids !== void 0) o.madeForKids = ytMadeForKids;
|
|
741
|
+
return Object.keys(o).length > 0 ? o : void 0;
|
|
742
|
+
}
|
|
743
|
+
function buildInstagramOptions(opts) {
|
|
744
|
+
const { igReel, igShareToFeed, igCoverUrl, igThumbOffsetMs, igCollaborators } = opts;
|
|
745
|
+
const hasAnyReelFlag = igReel !== void 0 || igShareToFeed !== void 0 || igCoverUrl !== void 0 || igThumbOffsetMs !== void 0 || igCollaborators !== void 0;
|
|
746
|
+
if (!hasAnyReelFlag) return void 0;
|
|
747
|
+
const reel = {};
|
|
748
|
+
if (igShareToFeed !== void 0) reel.shareToFeed = igShareToFeed;
|
|
749
|
+
if (igCoverUrl) reel.coverUrl = igCoverUrl;
|
|
750
|
+
if (igThumbOffsetMs !== void 0) {
|
|
751
|
+
const n = Number(igThumbOffsetMs);
|
|
752
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`--ig-thumb-offset-ms must be a non-negative number (received "${igThumbOffsetMs}")`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
reel.thumbOffsetMs = n;
|
|
758
|
+
}
|
|
759
|
+
if (igCollaborators) {
|
|
760
|
+
reel.collaborators = igCollaborators.split(",").map((s) => s.trim()).filter(Boolean);
|
|
761
|
+
}
|
|
762
|
+
return { reel };
|
|
763
|
+
}
|
|
764
|
+
function buildThreadsOptions(opts) {
|
|
765
|
+
const { threadsReplyTo, threadsReplyControl } = opts;
|
|
766
|
+
if (!threadsReplyTo && !threadsReplyControl) return void 0;
|
|
767
|
+
const o = {};
|
|
768
|
+
if (threadsReplyTo) o.replyToId = threadsReplyTo;
|
|
769
|
+
if (threadsReplyControl) o.replyControl = threadsReplyControl;
|
|
770
|
+
return o;
|
|
771
|
+
}
|
|
772
|
+
var import_node_fs2, VALID_PLATFORMS, EXT_TO_MIME;
|
|
773
|
+
var init_media_flags = __esm({
|
|
774
|
+
"src/lib/media-flags.ts"() {
|
|
775
|
+
"use strict";
|
|
776
|
+
import_node_fs2 = require("fs");
|
|
777
|
+
VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
778
|
+
EXT_TO_MIME = {
|
|
779
|
+
jpg: "image/jpeg",
|
|
780
|
+
jpeg: "image/jpeg",
|
|
781
|
+
png: "image/png",
|
|
782
|
+
gif: "image/gif",
|
|
783
|
+
webp: "image/webp",
|
|
784
|
+
mp4: "video/mp4",
|
|
785
|
+
mov: "video/quicktime",
|
|
786
|
+
webm: "video/webm"
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
666
791
|
// src/mcp/tools.ts
|
|
667
792
|
var MEDIA_ITEMS, ALL_TOOLS;
|
|
668
793
|
var init_tools = __esm({
|
|
@@ -671,7 +796,7 @@ var init_tools = __esm({
|
|
|
671
796
|
MEDIA_ITEMS = {
|
|
672
797
|
type: "array",
|
|
673
798
|
maxItems: 10,
|
|
674
|
-
description: "Images or videos to attach, as
|
|
799
|
+
description: "Images or videos to attach, as https URLs. For a local file, call upload-media first and pass its result here.",
|
|
675
800
|
items: {
|
|
676
801
|
type: "object",
|
|
677
802
|
properties: {
|
|
@@ -1221,6 +1346,39 @@ var init_tools = __esm({
|
|
|
1221
1346
|
additionalProperties: false
|
|
1222
1347
|
}
|
|
1223
1348
|
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "upload-clip",
|
|
1351
|
+
description: "Upload a local video file for AI clipping (multipart, any size). Poll get-clip until status is ready, then render-clip.",
|
|
1352
|
+
inputSchema: {
|
|
1353
|
+
type: "object",
|
|
1354
|
+
properties: {
|
|
1355
|
+
filePath: { type: "string", description: "Absolute path to the video file on this machine" },
|
|
1356
|
+
brandId: { type: "string", description: "Brand ID (the `id` from list-brands)" },
|
|
1357
|
+
title: { type: "string", description: "Optional clip title" }
|
|
1358
|
+
},
|
|
1359
|
+
required: ["filePath", "brandId"],
|
|
1360
|
+
additionalProperties: false
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
name: "publish-clip",
|
|
1365
|
+
description: "Publish, schedule, or save as a draft a rendered clip candidate.",
|
|
1366
|
+
inputSchema: {
|
|
1367
|
+
type: "object",
|
|
1368
|
+
properties: {
|
|
1369
|
+
id: { type: "string", description: "Clip ID" },
|
|
1370
|
+
mode: { type: "string", enum: ["publish", "schedule", "draft"] },
|
|
1371
|
+
candidateId: { type: "string", description: "Candidate to post (defaults to the best rendered one)" },
|
|
1372
|
+
platforms: { type: "string", description: 'Comma-separated platforms, e.g. "x,linkedin"' },
|
|
1373
|
+
text: { type: "string", description: "Post caption" },
|
|
1374
|
+
brandSlug: { type: "string", description: "Brand slug" },
|
|
1375
|
+
scheduledAt: { type: "string", description: 'ISO 8601 time, for mode "schedule"' },
|
|
1376
|
+
aspectRatio: { type: "string", enum: ["9:16", "4:5", "1:1", "16:9"] }
|
|
1377
|
+
},
|
|
1378
|
+
required: ["id", "mode"],
|
|
1379
|
+
additionalProperties: false
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1224
1382
|
{
|
|
1225
1383
|
name: "delete-clip",
|
|
1226
1384
|
description: "Permanently delete a video clip by ID.",
|
|
@@ -1233,6 +1391,45 @@ var init_tools = __esm({
|
|
|
1233
1391
|
additionalProperties: false
|
|
1234
1392
|
}
|
|
1235
1393
|
},
|
|
1394
|
+
// Media
|
|
1395
|
+
{
|
|
1396
|
+
name: "upload-media",
|
|
1397
|
+
description: "Upload a local image or video and get back a media item ({url, type, altText}) to pass in create-post or update-post `media`.",
|
|
1398
|
+
inputSchema: {
|
|
1399
|
+
type: "object",
|
|
1400
|
+
properties: {
|
|
1401
|
+
filePath: { type: "string", description: "Absolute path to the file on this machine" },
|
|
1402
|
+
altText: { type: "string", description: "Optional alt text" }
|
|
1403
|
+
},
|
|
1404
|
+
required: ["filePath"],
|
|
1405
|
+
additionalProperties: false
|
|
1406
|
+
}
|
|
1407
|
+
},
|
|
1408
|
+
// Any other REST route
|
|
1409
|
+
{
|
|
1410
|
+
name: "list-api-routes",
|
|
1411
|
+
description: 'List the REST routes api-request may call, as "METHOD /path/:param". Optional prefix filter, e.g. "/clips".',
|
|
1412
|
+
inputSchema: {
|
|
1413
|
+
type: "object",
|
|
1414
|
+
properties: { prefix: { type: "string", description: "Only routes whose path starts with this" } },
|
|
1415
|
+
additionalProperties: false
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
name: "api-request",
|
|
1420
|
+
description: "Call any Autoposting REST route on the allow-list (see list-api-routes) with your API key. Replace :params with real IDs.",
|
|
1421
|
+
inputSchema: {
|
|
1422
|
+
type: "object",
|
|
1423
|
+
properties: {
|
|
1424
|
+
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
1425
|
+
path: { type: "string", description: "Path such as /clips/abc123/jobs, no query string" },
|
|
1426
|
+
query: { type: "object", description: "Query string parameters" },
|
|
1427
|
+
body: { type: "object", description: "JSON body for POST, PUT and PATCH" }
|
|
1428
|
+
},
|
|
1429
|
+
required: ["method", "path"],
|
|
1430
|
+
additionalProperties: false
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1236
1433
|
// Carousels
|
|
1237
1434
|
{
|
|
1238
1435
|
name: "list-carousels",
|
|
@@ -1415,6 +1612,40 @@ var init_tools = __esm({
|
|
|
1415
1612
|
}
|
|
1416
1613
|
});
|
|
1417
1614
|
|
|
1615
|
+
// src/lib/api-routes.ts
|
|
1616
|
+
async function fetchApiRoutes(client) {
|
|
1617
|
+
const res = await client.request("POST", "/mcp", {
|
|
1618
|
+
jsonrpc: "2.0",
|
|
1619
|
+
id: 1,
|
|
1620
|
+
method: "tools/call",
|
|
1621
|
+
params: { name: "list-api-routes", arguments: {} }
|
|
1622
|
+
});
|
|
1623
|
+
try {
|
|
1624
|
+
const routes = JSON.parse(res.result?.content?.[0]?.text ?? "").routes;
|
|
1625
|
+
if (Array.isArray(routes)) return routes;
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
throw new Error(`Could not load the API allow-list from the server${res.error?.message ? `: ${res.error.message}` : ""}.`);
|
|
1629
|
+
}
|
|
1630
|
+
function assertApiRoute(routes, method, path3) {
|
|
1631
|
+
const m = method.toUpperCase();
|
|
1632
|
+
const segs = path3.startsWith("/") ? path3.slice(1).split("/") : [];
|
|
1633
|
+
const safe = segs.length > 0 && segs.every((s) => SAFE_SEGMENT.test(s) && s !== "." && s !== "..");
|
|
1634
|
+
const match = safe && routes.some((r) => {
|
|
1635
|
+
const [rm, rp] = r.split(" ");
|
|
1636
|
+
const rs = rp.slice(1).split("/");
|
|
1637
|
+
return rm === m && rs.length === segs.length && rs.every((s, i) => s.startsWith(":") || s === segs[i]);
|
|
1638
|
+
});
|
|
1639
|
+
if (!match) throw new Error(`${m} ${path3} is not available. Run list-api-routes (or \`ap api --list\`) to see what is.`);
|
|
1640
|
+
}
|
|
1641
|
+
var SAFE_SEGMENT;
|
|
1642
|
+
var init_api_routes = __esm({
|
|
1643
|
+
"src/lib/api-routes.ts"() {
|
|
1644
|
+
"use strict";
|
|
1645
|
+
SAFE_SEGMENT = /^[\w\-.~]+$/;
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1418
1649
|
// src/mcp/handler.ts
|
|
1419
1650
|
function ok(data) {
|
|
1420
1651
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -1434,6 +1665,15 @@ function parsePlatforms2(raw) {
|
|
|
1434
1665
|
}
|
|
1435
1666
|
return platforms;
|
|
1436
1667
|
}
|
|
1668
|
+
function apiRoutes(client) {
|
|
1669
|
+
let routes = routeCache.get(client);
|
|
1670
|
+
if (!routes) {
|
|
1671
|
+
routes = fetchApiRoutes(client);
|
|
1672
|
+
routeCache.set(client, routes);
|
|
1673
|
+
routes.catch(() => routeCache.delete(client));
|
|
1674
|
+
}
|
|
1675
|
+
return routes;
|
|
1676
|
+
}
|
|
1437
1677
|
function parseEvents(raw) {
|
|
1438
1678
|
if (typeof raw !== "string") return [];
|
|
1439
1679
|
return raw.split(",").map((e) => e.trim());
|
|
@@ -1685,6 +1925,21 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1685
1925
|
});
|
|
1686
1926
|
return ok(result);
|
|
1687
1927
|
}
|
|
1928
|
+
case "upload-clip": {
|
|
1929
|
+
const result = await client.clips.upload(args.filePath, {
|
|
1930
|
+
brandId: args.brandId,
|
|
1931
|
+
...args.title ? { title: args.title } : {}
|
|
1932
|
+
});
|
|
1933
|
+
return ok(result);
|
|
1934
|
+
}
|
|
1935
|
+
case "publish-clip": {
|
|
1936
|
+
const body = { mode: args.mode };
|
|
1937
|
+
for (const k of ["candidateId", "text", "brandSlug", "scheduledAt", "aspectRatio"]) {
|
|
1938
|
+
if (args[k] !== void 0) body[k] = args[k];
|
|
1939
|
+
}
|
|
1940
|
+
if (args.platforms !== void 0) body.platforms = parsePlatforms2(args.platforms);
|
|
1941
|
+
return ok(await client.request("POST", `/clips/${encodeURIComponent(args.id)}/publish`, body));
|
|
1942
|
+
}
|
|
1688
1943
|
case "delete-clip": {
|
|
1689
1944
|
await client.clips.remove(args.id);
|
|
1690
1945
|
return ok({ deleted: true, id: args.id });
|
|
@@ -1753,6 +2008,31 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1753
2008
|
await client.webhooks.test(args.id);
|
|
1754
2009
|
return ok({ sent: true, id: args.id });
|
|
1755
2010
|
}
|
|
2011
|
+
// Media
|
|
2012
|
+
case "upload-media": {
|
|
2013
|
+
const filePath = args.filePath;
|
|
2014
|
+
const filename = (0, import_node_path4.basename)(filePath);
|
|
2015
|
+
const uploaded = await client.media.upload({
|
|
2016
|
+
data: new Uint8Array(await (0, import_promises2.readFile)(filePath)),
|
|
2017
|
+
filename,
|
|
2018
|
+
contentType: extToMime(filename)
|
|
2019
|
+
});
|
|
2020
|
+
return ok({ url: uploaded.url, type: uploaded.type, ...args.altText ? { altText: args.altText } : {} });
|
|
2021
|
+
}
|
|
2022
|
+
// Any other REST route on the server's allow-list
|
|
2023
|
+
case "list-api-routes": {
|
|
2024
|
+
const prefix = typeof args.prefix === "string" ? args.prefix : "/";
|
|
2025
|
+
const routes = await apiRoutes(client);
|
|
2026
|
+
return ok({ routes: routes.filter((r) => r.split(" ")[1].startsWith(prefix)) });
|
|
2027
|
+
}
|
|
2028
|
+
case "api-request": {
|
|
2029
|
+
const method = String(args.method ?? "").toUpperCase();
|
|
2030
|
+
const path3 = String(args.path ?? "");
|
|
2031
|
+
assertApiRoute(await apiRoutes(client), method, path3);
|
|
2032
|
+
const query = typeof args.query === "object" && args.query ? args.query : void 0;
|
|
2033
|
+
const body = method !== "GET" && method !== "DELETE" && typeof args.body === "object" && args.body ? args.body : void 0;
|
|
2034
|
+
return ok(await client.request(method, path3, body, query));
|
|
2035
|
+
}
|
|
1756
2036
|
// Billing
|
|
1757
2037
|
case "billing-status": {
|
|
1758
2038
|
const result = await client.billing.status();
|
|
@@ -1774,10 +2054,14 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1774
2054
|
};
|
|
1775
2055
|
}
|
|
1776
2056
|
}
|
|
1777
|
-
var PLATFORMS, PLATFORM_SET;
|
|
2057
|
+
var import_promises2, import_node_path4, PLATFORMS, PLATFORM_SET, routeCache;
|
|
1778
2058
|
var init_handler = __esm({
|
|
1779
2059
|
"src/mcp/handler.ts"() {
|
|
1780
2060
|
"use strict";
|
|
2061
|
+
import_promises2 = require("fs/promises");
|
|
2062
|
+
import_node_path4 = require("path");
|
|
2063
|
+
init_api_routes();
|
|
2064
|
+
init_media_flags();
|
|
1781
2065
|
PLATFORMS = [
|
|
1782
2066
|
"x",
|
|
1783
2067
|
"linkedin",
|
|
@@ -1786,6 +2070,7 @@ var init_handler = __esm({
|
|
|
1786
2070
|
"youtube"
|
|
1787
2071
|
];
|
|
1788
2072
|
PLATFORM_SET = new Set(PLATFORMS);
|
|
2073
|
+
routeCache = /* @__PURE__ */ new WeakMap();
|
|
1789
2074
|
}
|
|
1790
2075
|
});
|
|
1791
2076
|
|
|
@@ -1824,7 +2109,7 @@ var init_server = __esm({
|
|
|
1824
2109
|
});
|
|
1825
2110
|
|
|
1826
2111
|
// src/cli.ts
|
|
1827
|
-
var
|
|
2112
|
+
var import_commander21 = require("commander");
|
|
1828
2113
|
init_dist();
|
|
1829
2114
|
|
|
1830
2115
|
// src/commands/agents.ts
|
|
@@ -2375,16 +2660,26 @@ async function requestDeviceCode(baseUrl) {
|
|
|
2375
2660
|
const text = await res.text().catch(() => res.statusText);
|
|
2376
2661
|
throw new Error(`Device code request failed (${res.status}): ${text}`);
|
|
2377
2662
|
}
|
|
2378
|
-
|
|
2663
|
+
const { data } = await parseJson(res);
|
|
2664
|
+
if (!data?.deviceCode || !data.userCode || !data.verificationUri) {
|
|
2665
|
+
throw new Error("Device code response is missing deviceCode, userCode or verificationUri.");
|
|
2666
|
+
}
|
|
2667
|
+
return data;
|
|
2379
2668
|
}
|
|
2380
2669
|
async function pollDeviceCode(baseUrl, deviceCode) {
|
|
2381
2670
|
const url = `${baseUrl}/auth/cli/poll?device_code=${encodeURIComponent(deviceCode)}`;
|
|
2382
2671
|
const res = await fetchWithTimeout(url);
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2672
|
+
const text = await res.text().catch(() => res.statusText);
|
|
2673
|
+
let body = {};
|
|
2674
|
+
try {
|
|
2675
|
+
body = JSON.parse(text);
|
|
2676
|
+
} catch {
|
|
2677
|
+
}
|
|
2678
|
+
if (res.ok || res.status === 400 && body.status) {
|
|
2679
|
+
if (!body.status) throw new Error(`Poll response (HTTP ${res.status}) has no status.`);
|
|
2680
|
+
return body;
|
|
2386
2681
|
}
|
|
2387
|
-
|
|
2682
|
+
throw new Error(`Poll request failed (${res.status}): ${text}`);
|
|
2388
2683
|
}
|
|
2389
2684
|
|
|
2390
2685
|
// src/commands/auth.ts
|
|
@@ -2618,125 +2913,7 @@ function createBillingCommand() {
|
|
|
2618
2913
|
// src/commands/brands.ts
|
|
2619
2914
|
var import_commander4 = require("commander");
|
|
2620
2915
|
init_dist();
|
|
2621
|
-
|
|
2622
|
-
// src/lib/media-flags.ts
|
|
2623
|
-
var import_node_fs2 = require("fs");
|
|
2624
|
-
var VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
2625
|
-
var EXT_TO_MIME = {
|
|
2626
|
-
jpg: "image/jpeg",
|
|
2627
|
-
jpeg: "image/jpeg",
|
|
2628
|
-
png: "image/png",
|
|
2629
|
-
gif: "image/gif",
|
|
2630
|
-
webp: "image/webp",
|
|
2631
|
-
mp4: "video/mp4",
|
|
2632
|
-
mov: "video/quicktime",
|
|
2633
|
-
webm: "video/webm"
|
|
2634
|
-
};
|
|
2635
|
-
function extToMime(filename) {
|
|
2636
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
2637
|
-
const mime = EXT_TO_MIME[ext];
|
|
2638
|
-
if (!mime) {
|
|
2639
|
-
throw new Error(
|
|
2640
|
-
`Unsupported file extension ".${ext || "(none)"}" in "${filename}". Supported: ${Object.keys(EXT_TO_MIME).join(", ")}`
|
|
2641
|
-
);
|
|
2642
|
-
}
|
|
2643
|
-
return mime;
|
|
2644
|
-
}
|
|
2645
|
-
function parsePairs(flag, vals) {
|
|
2646
|
-
const result = {};
|
|
2647
|
-
for (const val of vals) {
|
|
2648
|
-
const eqIdx = val.indexOf("=");
|
|
2649
|
-
if (eqIdx < 1) {
|
|
2650
|
-
throw new Error(`${flag}: expected "platform=value" format, got "${val}"`);
|
|
2651
|
-
}
|
|
2652
|
-
const p = val.slice(0, eqIdx).trim();
|
|
2653
|
-
const v = val.slice(eqIdx + 1);
|
|
2654
|
-
if (!VALID_PLATFORMS.includes(p)) {
|
|
2655
|
-
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
2656
|
-
}
|
|
2657
|
-
result[p] = v;
|
|
2658
|
-
}
|
|
2659
|
-
return result;
|
|
2660
|
-
}
|
|
2661
|
-
function parsePlatformMediaPairs(flag, vals) {
|
|
2662
|
-
const result = {};
|
|
2663
|
-
for (const val of vals) {
|
|
2664
|
-
const eqIdx = val.indexOf("=");
|
|
2665
|
-
if (eqIdx < 1) {
|
|
2666
|
-
throw new Error(`${flag}: expected "platform=path[,path...]" format, got "${val}"`);
|
|
2667
|
-
}
|
|
2668
|
-
const p = val.slice(0, eqIdx).trim();
|
|
2669
|
-
const paths = val.slice(eqIdx + 1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2670
|
-
if (!VALID_PLATFORMS.includes(p)) {
|
|
2671
|
-
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
2672
|
-
}
|
|
2673
|
-
result[p] = paths;
|
|
2674
|
-
}
|
|
2675
|
-
return result;
|
|
2676
|
-
}
|
|
2677
|
-
function validateMediaExtensions(paths) {
|
|
2678
|
-
for (const p of paths) extToMime(p);
|
|
2679
|
-
}
|
|
2680
|
-
function validateMediaCount(paths) {
|
|
2681
|
-
if (paths.length > 10) {
|
|
2682
|
-
throw new Error(`--media accepts at most 10 files (received ${paths.length}).`);
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
function validateMediaPaths(paths) {
|
|
2686
|
-
for (const p of paths) {
|
|
2687
|
-
if (!(0, import_node_fs2.existsSync)(p)) {
|
|
2688
|
-
throw new Error(`Media file not found: "${p}"`);
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
function alignAltText(mediaPaths, altTexts) {
|
|
2693
|
-
if (altTexts.length > mediaPaths.length) {
|
|
2694
|
-
throw new Error(
|
|
2695
|
-
`--alt-text has more values (${altTexts.length}) than --media (${mediaPaths.length}). Alt text aligns to media by index.`
|
|
2696
|
-
);
|
|
2697
|
-
}
|
|
2698
|
-
return mediaPaths.map((_, i) => altTexts[i]);
|
|
2699
|
-
}
|
|
2700
|
-
function buildYoutubeOptions(opts) {
|
|
2701
|
-
const { ytTitle, ytDescription, ytTags, ytPrivacy, ytCategory, ytMadeForKids } = opts;
|
|
2702
|
-
const o = {};
|
|
2703
|
-
if (ytTitle) o.title = ytTitle;
|
|
2704
|
-
if (ytDescription) o.description = ytDescription;
|
|
2705
|
-
if (ytTags) o.tags = ytTags.split(",").map((t) => t.trim()).filter(Boolean);
|
|
2706
|
-
if (ytPrivacy) o.privacyStatus = ytPrivacy;
|
|
2707
|
-
if (ytCategory) o.categoryId = ytCategory;
|
|
2708
|
-
if (ytMadeForKids !== void 0) o.madeForKids = ytMadeForKids;
|
|
2709
|
-
return Object.keys(o).length > 0 ? o : void 0;
|
|
2710
|
-
}
|
|
2711
|
-
function buildInstagramOptions(opts) {
|
|
2712
|
-
const { igReel, igShareToFeed, igCoverUrl, igThumbOffsetMs, igCollaborators } = opts;
|
|
2713
|
-
const hasAnyReelFlag = igReel !== void 0 || igShareToFeed !== void 0 || igCoverUrl !== void 0 || igThumbOffsetMs !== void 0 || igCollaborators !== void 0;
|
|
2714
|
-
if (!hasAnyReelFlag) return void 0;
|
|
2715
|
-
const reel = {};
|
|
2716
|
-
if (igShareToFeed !== void 0) reel.shareToFeed = igShareToFeed;
|
|
2717
|
-
if (igCoverUrl) reel.coverUrl = igCoverUrl;
|
|
2718
|
-
if (igThumbOffsetMs !== void 0) {
|
|
2719
|
-
const n = Number(igThumbOffsetMs);
|
|
2720
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
2721
|
-
throw new Error(
|
|
2722
|
-
`--ig-thumb-offset-ms must be a non-negative number (received "${igThumbOffsetMs}")`
|
|
2723
|
-
);
|
|
2724
|
-
}
|
|
2725
|
-
reel.thumbOffsetMs = n;
|
|
2726
|
-
}
|
|
2727
|
-
if (igCollaborators) {
|
|
2728
|
-
reel.collaborators = igCollaborators.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2729
|
-
}
|
|
2730
|
-
return { reel };
|
|
2731
|
-
}
|
|
2732
|
-
function buildThreadsOptions(opts) {
|
|
2733
|
-
const { threadsReplyTo, threadsReplyControl } = opts;
|
|
2734
|
-
if (!threadsReplyTo && !threadsReplyControl) return void 0;
|
|
2735
|
-
const o = {};
|
|
2736
|
-
if (threadsReplyTo) o.replyToId = threadsReplyTo;
|
|
2737
|
-
if (threadsReplyControl) o.replyControl = threadsReplyControl;
|
|
2738
|
-
return o;
|
|
2739
|
-
}
|
|
2916
|
+
init_media_flags();
|
|
2740
2917
|
|
|
2741
2918
|
// src/auth/config-store.ts
|
|
2742
2919
|
var import_node_fs3 = __toESM(require("fs"));
|
|
@@ -3262,6 +3439,7 @@ init_dist();
|
|
|
3262
3439
|
// src/lib/post-create.ts
|
|
3263
3440
|
var import_promises = __toESM(require("fs/promises"));
|
|
3264
3441
|
var import_node_path3 = __toESM(require("path"));
|
|
3442
|
+
init_media_flags();
|
|
3265
3443
|
|
|
3266
3444
|
// src/lib/account-select.ts
|
|
3267
3445
|
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
@@ -4719,8 +4897,73 @@ function createConfigCommand() {
|
|
|
4719
4897
|
return config;
|
|
4720
4898
|
}
|
|
4721
4899
|
|
|
4900
|
+
// src/commands/api.ts
|
|
4901
|
+
var import_commander20 = require("commander");
|
|
4902
|
+
init_dist();
|
|
4903
|
+
init_api_routes();
|
|
4904
|
+
function validationError(message) {
|
|
4905
|
+
return Object.assign(new Error(message), { exitCode: EXIT_CODES.VALIDATION_ERROR });
|
|
4906
|
+
}
|
|
4907
|
+
function collect(value, previous) {
|
|
4908
|
+
return [...previous, value];
|
|
4909
|
+
}
|
|
4910
|
+
function parseFields(pairs) {
|
|
4911
|
+
const out = {};
|
|
4912
|
+
for (const pair of pairs) {
|
|
4913
|
+
const i = pair.indexOf("=");
|
|
4914
|
+
if (i < 1) throw validationError(`Expected key=value, got "${pair}".`);
|
|
4915
|
+
const raw = pair.slice(i + 1);
|
|
4916
|
+
let value = raw;
|
|
4917
|
+
try {
|
|
4918
|
+
value = JSON.parse(raw);
|
|
4919
|
+
} catch {
|
|
4920
|
+
}
|
|
4921
|
+
out[pair.slice(0, i)] = value;
|
|
4922
|
+
}
|
|
4923
|
+
return out;
|
|
4924
|
+
}
|
|
4925
|
+
function createApiCommand() {
|
|
4926
|
+
return new import_commander20.Command("api").description("Call any Autoposting REST route on the server allow-list, like `gh api`").argument("[method]", "GET, POST, PUT, PATCH or DELETE").argument("[path]", "Route path, e.g. /clips/abc123/jobs").option("-d, --data <json>", "JSON request body").option("-f, --field <key=value>", "Body field (repeatable)", collect, []).option("-q, --query <key=value>", "Query parameter (repeatable)", collect, []).option("--list [prefix]", "List the routes you may call").action(async (method, path3, opts, cmd) => {
|
|
4927
|
+
const globals = cmd.optsWithGlobals();
|
|
4928
|
+
const printer = createPrinter(globals);
|
|
4929
|
+
try {
|
|
4930
|
+
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4931
|
+
let body;
|
|
4932
|
+
if (opts.data !== void 0) {
|
|
4933
|
+
try {
|
|
4934
|
+
body = JSON.parse(opts.data);
|
|
4935
|
+
} catch {
|
|
4936
|
+
throw validationError("--data is not valid JSON.");
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
if (opts.field.length > 0) body = { ...body, ...parseFields(opts.field) };
|
|
4940
|
+
const query = opts.query.length > 0 ? parseFields(opts.query) : void 0;
|
|
4941
|
+
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4942
|
+
const routes = await fetchApiRoutes(client);
|
|
4943
|
+
if (opts.list !== void 0) {
|
|
4944
|
+
const prefix = typeof opts.list === "string" ? opts.list : "/";
|
|
4945
|
+
printer.log(routes.filter((r) => r.split(" ")[1].startsWith(prefix)));
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
if (!method || !path3) throw validationError("Usage: ap api <METHOD> <path>, or ap api --list.");
|
|
4949
|
+
const m = method.toUpperCase();
|
|
4950
|
+
try {
|
|
4951
|
+
assertApiRoute(routes, m, path3);
|
|
4952
|
+
} catch (err) {
|
|
4953
|
+
throw validationError(err.message);
|
|
4954
|
+
}
|
|
4955
|
+
const sendBody = m === "GET" || m === "DELETE" ? void 0 : body;
|
|
4956
|
+
printer.log(await client.request(m, path3, sendBody, query));
|
|
4957
|
+
} catch (err) {
|
|
4958
|
+
printer.error(err);
|
|
4959
|
+
const attached = err.exitCode;
|
|
4960
|
+
process.exit(typeof attached === "number" ? attached : exitCodeFromError(err));
|
|
4961
|
+
}
|
|
4962
|
+
});
|
|
4963
|
+
}
|
|
4964
|
+
|
|
4722
4965
|
// src/cli.ts
|
|
4723
|
-
var program = new
|
|
4966
|
+
var program = new import_commander21.Command().name("autoposting").description("Autoposting CLI \u2014 manage social media from the terminal").version(VERSION).option("--json", "Output as JSON").option("--quiet", "Suppress spinners and non-essential output").option("--format <type>", "Output format: auto (table on a terminal, JSON when piped), table, json", "auto").option("--jq <expr>", 'Filter JSON output with a minimal jq expression (e.g. ".[].id")').option("--api-key <key>", "API key (overrides env/stored credentials)").option("--no-color", "Disable color output").hook("preAction", (thisCommand) => {
|
|
4724
4967
|
const opts = thisCommand.opts();
|
|
4725
4968
|
if (!opts.color) disableColor();
|
|
4726
4969
|
});
|
|
@@ -4729,6 +4972,7 @@ program.addCommand(createBillingCommand());
|
|
|
4729
4972
|
program.addCommand(createBrandsCommand());
|
|
4730
4973
|
program.addCommand(createCarouselsCommand());
|
|
4731
4974
|
program.addCommand(createClipsCommand());
|
|
4975
|
+
program.addCommand(createApiCommand());
|
|
4732
4976
|
program.addCommand(createPostsCommand());
|
|
4733
4977
|
program.addCommand(createAgentsCommand());
|
|
4734
4978
|
program.addCommand(createKbCommand());
|