@autoposting.ai/cli 0.3.6 → 0.4.1
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 +381 -136
- 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.1";
|
|
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: {
|
|
@@ -1041,9 +1166,10 @@ var init_tools = __esm({
|
|
|
1041
1166
|
inputSchema: {
|
|
1042
1167
|
type: "object",
|
|
1043
1168
|
properties: {
|
|
1044
|
-
name: { type: "string", description: "Knowledge base name" }
|
|
1169
|
+
name: { type: "string", minLength: 4, maxLength: 20, description: "Knowledge base name" },
|
|
1170
|
+
description: { type: "string", minLength: 20, maxLength: 500, description: "Knowledge base description" }
|
|
1045
1171
|
},
|
|
1046
|
-
required: ["name"],
|
|
1172
|
+
required: ["name", "description"],
|
|
1047
1173
|
additionalProperties: false
|
|
1048
1174
|
}
|
|
1049
1175
|
},
|
|
@@ -1221,6 +1347,39 @@ var init_tools = __esm({
|
|
|
1221
1347
|
additionalProperties: false
|
|
1222
1348
|
}
|
|
1223
1349
|
},
|
|
1350
|
+
{
|
|
1351
|
+
name: "upload-clip",
|
|
1352
|
+
description: "Upload a local video file for AI clipping (multipart, any size). Poll get-clip until status is ready, then render-clip.",
|
|
1353
|
+
inputSchema: {
|
|
1354
|
+
type: "object",
|
|
1355
|
+
properties: {
|
|
1356
|
+
filePath: { type: "string", description: "Absolute path to the video file on this machine" },
|
|
1357
|
+
brandId: { type: "string", description: "Brand ID (the `id` from list-brands)" },
|
|
1358
|
+
title: { type: "string", description: "Optional clip title" }
|
|
1359
|
+
},
|
|
1360
|
+
required: ["filePath", "brandId"],
|
|
1361
|
+
additionalProperties: false
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
name: "publish-clip",
|
|
1366
|
+
description: "Publish, schedule, or save as a draft a rendered clip candidate.",
|
|
1367
|
+
inputSchema: {
|
|
1368
|
+
type: "object",
|
|
1369
|
+
properties: {
|
|
1370
|
+
id: { type: "string", description: "Clip ID" },
|
|
1371
|
+
mode: { type: "string", enum: ["publish", "schedule", "draft"] },
|
|
1372
|
+
candidateId: { type: "string", description: "Candidate to post (defaults to the best rendered one)" },
|
|
1373
|
+
platforms: { type: "string", description: 'Comma-separated platforms, e.g. "x,linkedin"' },
|
|
1374
|
+
text: { type: "string", description: "Post caption" },
|
|
1375
|
+
brandSlug: { type: "string", description: "Brand slug" },
|
|
1376
|
+
scheduledAt: { type: "string", description: 'ISO 8601 time, for mode "schedule"' },
|
|
1377
|
+
aspectRatio: { type: "string", enum: ["9:16", "4:5", "1:1", "16:9"] }
|
|
1378
|
+
},
|
|
1379
|
+
required: ["id", "mode"],
|
|
1380
|
+
additionalProperties: false
|
|
1381
|
+
}
|
|
1382
|
+
},
|
|
1224
1383
|
{
|
|
1225
1384
|
name: "delete-clip",
|
|
1226
1385
|
description: "Permanently delete a video clip by ID.",
|
|
@@ -1233,6 +1392,45 @@ var init_tools = __esm({
|
|
|
1233
1392
|
additionalProperties: false
|
|
1234
1393
|
}
|
|
1235
1394
|
},
|
|
1395
|
+
// Media
|
|
1396
|
+
{
|
|
1397
|
+
name: "upload-media",
|
|
1398
|
+
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`.",
|
|
1399
|
+
inputSchema: {
|
|
1400
|
+
type: "object",
|
|
1401
|
+
properties: {
|
|
1402
|
+
filePath: { type: "string", description: "Absolute path to the file on this machine" },
|
|
1403
|
+
altText: { type: "string", description: "Optional alt text" }
|
|
1404
|
+
},
|
|
1405
|
+
required: ["filePath"],
|
|
1406
|
+
additionalProperties: false
|
|
1407
|
+
}
|
|
1408
|
+
},
|
|
1409
|
+
// Any other REST route
|
|
1410
|
+
{
|
|
1411
|
+
name: "list-api-routes",
|
|
1412
|
+
description: 'List the REST routes api-request may call, as "METHOD /path/:param". Optional prefix filter, e.g. "/clips".',
|
|
1413
|
+
inputSchema: {
|
|
1414
|
+
type: "object",
|
|
1415
|
+
properties: { prefix: { type: "string", description: "Only routes whose path starts with this" } },
|
|
1416
|
+
additionalProperties: false
|
|
1417
|
+
}
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: "api-request",
|
|
1421
|
+
description: "Call any Autoposting REST route on the allow-list (see list-api-routes) with your API key. Replace :params with real IDs.",
|
|
1422
|
+
inputSchema: {
|
|
1423
|
+
type: "object",
|
|
1424
|
+
properties: {
|
|
1425
|
+
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
1426
|
+
path: { type: "string", description: "Path such as /clips/abc123/jobs, no query string" },
|
|
1427
|
+
query: { type: "object", description: "Query string parameters" },
|
|
1428
|
+
body: { type: "object", description: "JSON body for POST, PUT and PATCH" }
|
|
1429
|
+
},
|
|
1430
|
+
required: ["method", "path"],
|
|
1431
|
+
additionalProperties: false
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1236
1434
|
// Carousels
|
|
1237
1435
|
{
|
|
1238
1436
|
name: "list-carousels",
|
|
@@ -1415,6 +1613,40 @@ var init_tools = __esm({
|
|
|
1415
1613
|
}
|
|
1416
1614
|
});
|
|
1417
1615
|
|
|
1616
|
+
// src/lib/api-routes.ts
|
|
1617
|
+
async function fetchApiRoutes(client) {
|
|
1618
|
+
const res = await client.request("POST", "/mcp", {
|
|
1619
|
+
jsonrpc: "2.0",
|
|
1620
|
+
id: 1,
|
|
1621
|
+
method: "tools/call",
|
|
1622
|
+
params: { name: "list-api-routes", arguments: {} }
|
|
1623
|
+
});
|
|
1624
|
+
try {
|
|
1625
|
+
const routes = JSON.parse(res.result?.content?.[0]?.text ?? "").routes;
|
|
1626
|
+
if (Array.isArray(routes)) return routes;
|
|
1627
|
+
} catch {
|
|
1628
|
+
}
|
|
1629
|
+
throw new Error(`Could not load the API allow-list from the server${res.error?.message ? `: ${res.error.message}` : ""}.`);
|
|
1630
|
+
}
|
|
1631
|
+
function assertApiRoute(routes, method, path3) {
|
|
1632
|
+
const m = method.toUpperCase();
|
|
1633
|
+
const segs = path3.startsWith("/") ? path3.slice(1).split("/") : [];
|
|
1634
|
+
const safe = segs.length > 0 && segs.every((s) => SAFE_SEGMENT.test(s) && s !== "." && s !== "..");
|
|
1635
|
+
const match = safe && routes.some((r) => {
|
|
1636
|
+
const [rm, rp] = r.split(" ");
|
|
1637
|
+
const rs = rp.slice(1).split("/");
|
|
1638
|
+
return rm === m && rs.length === segs.length && rs.every((s, i) => s.startsWith(":") || s === segs[i]);
|
|
1639
|
+
});
|
|
1640
|
+
if (!match) throw new Error(`${m} ${path3} is not available. Run list-api-routes (or \`ap api --list\`) to see what is.`);
|
|
1641
|
+
}
|
|
1642
|
+
var SAFE_SEGMENT;
|
|
1643
|
+
var init_api_routes = __esm({
|
|
1644
|
+
"src/lib/api-routes.ts"() {
|
|
1645
|
+
"use strict";
|
|
1646
|
+
SAFE_SEGMENT = /^[\w\-.~]+$/;
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1418
1650
|
// src/mcp/handler.ts
|
|
1419
1651
|
function ok(data) {
|
|
1420
1652
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -1434,6 +1666,15 @@ function parsePlatforms2(raw) {
|
|
|
1434
1666
|
}
|
|
1435
1667
|
return platforms;
|
|
1436
1668
|
}
|
|
1669
|
+
function apiRoutes(client) {
|
|
1670
|
+
let routes = routeCache.get(client);
|
|
1671
|
+
if (!routes) {
|
|
1672
|
+
routes = fetchApiRoutes(client);
|
|
1673
|
+
routeCache.set(client, routes);
|
|
1674
|
+
routes.catch(() => routeCache.delete(client));
|
|
1675
|
+
}
|
|
1676
|
+
return routes;
|
|
1677
|
+
}
|
|
1437
1678
|
function parseEvents(raw) {
|
|
1438
1679
|
if (typeof raw !== "string") return [];
|
|
1439
1680
|
return raw.split(",").map((e) => e.trim());
|
|
@@ -1598,7 +1839,7 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1598
1839
|
return ok(result);
|
|
1599
1840
|
}
|
|
1600
1841
|
case "create-kb": {
|
|
1601
|
-
const result = await client.kb.create({ name: args.name });
|
|
1842
|
+
const result = await client.kb.create({ name: args.name, description: args.description });
|
|
1602
1843
|
return ok(result);
|
|
1603
1844
|
}
|
|
1604
1845
|
case "delete-kb": {
|
|
@@ -1685,6 +1926,21 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1685
1926
|
});
|
|
1686
1927
|
return ok(result);
|
|
1687
1928
|
}
|
|
1929
|
+
case "upload-clip": {
|
|
1930
|
+
const result = await client.clips.upload(args.filePath, {
|
|
1931
|
+
brandId: args.brandId,
|
|
1932
|
+
...args.title ? { title: args.title } : {}
|
|
1933
|
+
});
|
|
1934
|
+
return ok(result);
|
|
1935
|
+
}
|
|
1936
|
+
case "publish-clip": {
|
|
1937
|
+
const body = { mode: args.mode };
|
|
1938
|
+
for (const k of ["candidateId", "text", "brandSlug", "scheduledAt", "aspectRatio"]) {
|
|
1939
|
+
if (args[k] !== void 0) body[k] = args[k];
|
|
1940
|
+
}
|
|
1941
|
+
if (args.platforms !== void 0) body.platforms = parsePlatforms2(args.platforms);
|
|
1942
|
+
return ok(await client.request("POST", `/clips/${encodeURIComponent(args.id)}/publish`, body));
|
|
1943
|
+
}
|
|
1688
1944
|
case "delete-clip": {
|
|
1689
1945
|
await client.clips.remove(args.id);
|
|
1690
1946
|
return ok({ deleted: true, id: args.id });
|
|
@@ -1753,6 +2009,31 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1753
2009
|
await client.webhooks.test(args.id);
|
|
1754
2010
|
return ok({ sent: true, id: args.id });
|
|
1755
2011
|
}
|
|
2012
|
+
// Media
|
|
2013
|
+
case "upload-media": {
|
|
2014
|
+
const filePath = args.filePath;
|
|
2015
|
+
const filename = (0, import_node_path4.basename)(filePath);
|
|
2016
|
+
const uploaded = await client.media.upload({
|
|
2017
|
+
data: new Uint8Array(await (0, import_promises2.readFile)(filePath)),
|
|
2018
|
+
filename,
|
|
2019
|
+
contentType: extToMime(filename)
|
|
2020
|
+
});
|
|
2021
|
+
return ok({ url: uploaded.url, type: uploaded.type, ...args.altText ? { altText: args.altText } : {} });
|
|
2022
|
+
}
|
|
2023
|
+
// Any other REST route on the server's allow-list
|
|
2024
|
+
case "list-api-routes": {
|
|
2025
|
+
const prefix = typeof args.prefix === "string" ? args.prefix : "/";
|
|
2026
|
+
const routes = await apiRoutes(client);
|
|
2027
|
+
return ok({ routes: routes.filter((r) => r.split(" ")[1].startsWith(prefix)) });
|
|
2028
|
+
}
|
|
2029
|
+
case "api-request": {
|
|
2030
|
+
const method = String(args.method ?? "").toUpperCase();
|
|
2031
|
+
const path3 = String(args.path ?? "");
|
|
2032
|
+
assertApiRoute(await apiRoutes(client), method, path3);
|
|
2033
|
+
const query = typeof args.query === "object" && args.query ? args.query : void 0;
|
|
2034
|
+
const body = method !== "GET" && method !== "DELETE" && typeof args.body === "object" && args.body ? args.body : void 0;
|
|
2035
|
+
return ok(await client.request(method, path3, body, query));
|
|
2036
|
+
}
|
|
1756
2037
|
// Billing
|
|
1757
2038
|
case "billing-status": {
|
|
1758
2039
|
const result = await client.billing.status();
|
|
@@ -1774,10 +2055,14 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1774
2055
|
};
|
|
1775
2056
|
}
|
|
1776
2057
|
}
|
|
1777
|
-
var PLATFORMS, PLATFORM_SET;
|
|
2058
|
+
var import_promises2, import_node_path4, PLATFORMS, PLATFORM_SET, routeCache;
|
|
1778
2059
|
var init_handler = __esm({
|
|
1779
2060
|
"src/mcp/handler.ts"() {
|
|
1780
2061
|
"use strict";
|
|
2062
|
+
import_promises2 = require("fs/promises");
|
|
2063
|
+
import_node_path4 = require("path");
|
|
2064
|
+
init_api_routes();
|
|
2065
|
+
init_media_flags();
|
|
1781
2066
|
PLATFORMS = [
|
|
1782
2067
|
"x",
|
|
1783
2068
|
"linkedin",
|
|
@@ -1786,6 +2071,7 @@ var init_handler = __esm({
|
|
|
1786
2071
|
"youtube"
|
|
1787
2072
|
];
|
|
1788
2073
|
PLATFORM_SET = new Set(PLATFORMS);
|
|
2074
|
+
routeCache = /* @__PURE__ */ new WeakMap();
|
|
1789
2075
|
}
|
|
1790
2076
|
});
|
|
1791
2077
|
|
|
@@ -1824,7 +2110,7 @@ var init_server = __esm({
|
|
|
1824
2110
|
});
|
|
1825
2111
|
|
|
1826
2112
|
// src/cli.ts
|
|
1827
|
-
var
|
|
2113
|
+
var import_commander21 = require("commander");
|
|
1828
2114
|
init_dist();
|
|
1829
2115
|
|
|
1830
2116
|
// src/commands/agents.ts
|
|
@@ -2375,16 +2661,26 @@ async function requestDeviceCode(baseUrl) {
|
|
|
2375
2661
|
const text = await res.text().catch(() => res.statusText);
|
|
2376
2662
|
throw new Error(`Device code request failed (${res.status}): ${text}`);
|
|
2377
2663
|
}
|
|
2378
|
-
|
|
2664
|
+
const { data } = await parseJson(res);
|
|
2665
|
+
if (!data?.deviceCode || !data.userCode || !data.verificationUri) {
|
|
2666
|
+
throw new Error("Device code response is missing deviceCode, userCode or verificationUri.");
|
|
2667
|
+
}
|
|
2668
|
+
return data;
|
|
2379
2669
|
}
|
|
2380
2670
|
async function pollDeviceCode(baseUrl, deviceCode) {
|
|
2381
2671
|
const url = `${baseUrl}/auth/cli/poll?device_code=${encodeURIComponent(deviceCode)}`;
|
|
2382
2672
|
const res = await fetchWithTimeout(url);
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2673
|
+
const text = await res.text().catch(() => res.statusText);
|
|
2674
|
+
let body = {};
|
|
2675
|
+
try {
|
|
2676
|
+
body = JSON.parse(text);
|
|
2677
|
+
} catch {
|
|
2678
|
+
}
|
|
2679
|
+
if (res.ok || res.status === 400 && body.status) {
|
|
2680
|
+
if (!body.status) throw new Error(`Poll response (HTTP ${res.status}) has no status.`);
|
|
2681
|
+
return body;
|
|
2386
2682
|
}
|
|
2387
|
-
|
|
2683
|
+
throw new Error(`Poll request failed (${res.status}): ${text}`);
|
|
2388
2684
|
}
|
|
2389
2685
|
|
|
2390
2686
|
// src/commands/auth.ts
|
|
@@ -2618,125 +2914,7 @@ function createBillingCommand() {
|
|
|
2618
2914
|
// src/commands/brands.ts
|
|
2619
2915
|
var import_commander4 = require("commander");
|
|
2620
2916
|
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
|
-
}
|
|
2917
|
+
init_media_flags();
|
|
2740
2918
|
|
|
2741
2919
|
// src/auth/config-store.ts
|
|
2742
2920
|
var import_node_fs3 = __toESM(require("fs"));
|
|
@@ -3262,6 +3440,7 @@ init_dist();
|
|
|
3262
3440
|
// src/lib/post-create.ts
|
|
3263
3441
|
var import_promises = __toESM(require("fs/promises"));
|
|
3264
3442
|
var import_node_path3 = __toESM(require("path"));
|
|
3443
|
+
init_media_flags();
|
|
3265
3444
|
|
|
3266
3445
|
// src/lib/account-select.ts
|
|
3267
3446
|
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
@@ -3935,14 +4114,14 @@ function createKbCommand() {
|
|
|
3935
4114
|
process.exit(resolveExitCode6(err));
|
|
3936
4115
|
}
|
|
3937
4116
|
});
|
|
3938
|
-
kb.command("create").description("Create a new knowledge base").requiredOption("--name <name>", "Knowledge base name").action(async (opts, cmd) => {
|
|
4117
|
+
kb.command("create").description("Create a new knowledge base").requiredOption("--name <name>", "Knowledge base name").requiredOption("--description <description>", "Knowledge base description (20\u2013500 characters)").action(async (opts, cmd) => {
|
|
3939
4118
|
const globals = cmd.optsWithGlobals();
|
|
3940
4119
|
const printer = createPrinter(globals);
|
|
3941
4120
|
const spinner = printer.spinner("Creating knowledge base\u2026");
|
|
3942
4121
|
try {
|
|
3943
4122
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3944
4123
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3945
|
-
const result = await client.kb.create({ name: opts.name });
|
|
4124
|
+
const result = await client.kb.create({ name: opts.name, description: opts.description });
|
|
3946
4125
|
spinner.stop();
|
|
3947
4126
|
printer.log(result);
|
|
3948
4127
|
} catch (err) {
|
|
@@ -4719,8 +4898,73 @@ function createConfigCommand() {
|
|
|
4719
4898
|
return config;
|
|
4720
4899
|
}
|
|
4721
4900
|
|
|
4901
|
+
// src/commands/api.ts
|
|
4902
|
+
var import_commander20 = require("commander");
|
|
4903
|
+
init_dist();
|
|
4904
|
+
init_api_routes();
|
|
4905
|
+
function validationError(message) {
|
|
4906
|
+
return Object.assign(new Error(message), { exitCode: EXIT_CODES.VALIDATION_ERROR });
|
|
4907
|
+
}
|
|
4908
|
+
function collect(value, previous) {
|
|
4909
|
+
return [...previous, value];
|
|
4910
|
+
}
|
|
4911
|
+
function parseFields(pairs) {
|
|
4912
|
+
const out = {};
|
|
4913
|
+
for (const pair of pairs) {
|
|
4914
|
+
const i = pair.indexOf("=");
|
|
4915
|
+
if (i < 1) throw validationError(`Expected key=value, got "${pair}".`);
|
|
4916
|
+
const raw = pair.slice(i + 1);
|
|
4917
|
+
let value = raw;
|
|
4918
|
+
try {
|
|
4919
|
+
value = JSON.parse(raw);
|
|
4920
|
+
} catch {
|
|
4921
|
+
}
|
|
4922
|
+
out[pair.slice(0, i)] = value;
|
|
4923
|
+
}
|
|
4924
|
+
return out;
|
|
4925
|
+
}
|
|
4926
|
+
function createApiCommand() {
|
|
4927
|
+
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) => {
|
|
4928
|
+
const globals = cmd.optsWithGlobals();
|
|
4929
|
+
const printer = createPrinter(globals);
|
|
4930
|
+
try {
|
|
4931
|
+
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
4932
|
+
let body;
|
|
4933
|
+
if (opts.data !== void 0) {
|
|
4934
|
+
try {
|
|
4935
|
+
body = JSON.parse(opts.data);
|
|
4936
|
+
} catch {
|
|
4937
|
+
throw validationError("--data is not valid JSON.");
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
if (opts.field.length > 0) body = { ...body, ...parseFields(opts.field) };
|
|
4941
|
+
const query = opts.query.length > 0 ? parseFields(opts.query) : void 0;
|
|
4942
|
+
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
4943
|
+
const routes = await fetchApiRoutes(client);
|
|
4944
|
+
if (opts.list !== void 0) {
|
|
4945
|
+
const prefix = typeof opts.list === "string" ? opts.list : "/";
|
|
4946
|
+
printer.log(routes.filter((r) => r.split(" ")[1].startsWith(prefix)));
|
|
4947
|
+
return;
|
|
4948
|
+
}
|
|
4949
|
+
if (!method || !path3) throw validationError("Usage: ap api <METHOD> <path>, or ap api --list.");
|
|
4950
|
+
const m = method.toUpperCase();
|
|
4951
|
+
try {
|
|
4952
|
+
assertApiRoute(routes, m, path3);
|
|
4953
|
+
} catch (err) {
|
|
4954
|
+
throw validationError(err.message);
|
|
4955
|
+
}
|
|
4956
|
+
const sendBody = m === "GET" || m === "DELETE" ? void 0 : body;
|
|
4957
|
+
printer.log(await client.request(m, path3, sendBody, query));
|
|
4958
|
+
} catch (err) {
|
|
4959
|
+
printer.error(err);
|
|
4960
|
+
const attached = err.exitCode;
|
|
4961
|
+
process.exit(typeof attached === "number" ? attached : exitCodeFromError(err));
|
|
4962
|
+
}
|
|
4963
|
+
});
|
|
4964
|
+
}
|
|
4965
|
+
|
|
4722
4966
|
// src/cli.ts
|
|
4723
|
-
var program = new
|
|
4967
|
+
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
4968
|
const opts = thisCommand.opts();
|
|
4725
4969
|
if (!opts.color) disableColor();
|
|
4726
4970
|
});
|
|
@@ -4729,6 +4973,7 @@ program.addCommand(createBillingCommand());
|
|
|
4729
4973
|
program.addCommand(createBrandsCommand());
|
|
4730
4974
|
program.addCommand(createCarouselsCommand());
|
|
4731
4975
|
program.addCommand(createClipsCommand());
|
|
4976
|
+
program.addCommand(createApiCommand());
|
|
4732
4977
|
program.addCommand(createPostsCommand());
|
|
4733
4978
|
program.addCommand(createAgentsCommand());
|
|
4734
4979
|
program.addCommand(createKbCommand());
|