@autoposting.ai/cli 0.3.5 → 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 +471 -148
- 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);
|
|
@@ -298,8 +298,16 @@ var init_dist = __esm({
|
|
|
298
298
|
return this.client.request("POST", "/clips/import-url", params);
|
|
299
299
|
}
|
|
300
300
|
/** POST /clips/:id/render — backend returns render job ids, not a Clip. */
|
|
301
|
-
render(id) {
|
|
302
|
-
|
|
301
|
+
async render(id, params = {}) {
|
|
302
|
+
const editRevision = params.editRevision ?? (await this.retrieve(id)).editRevision ?? 0;
|
|
303
|
+
return this.client.request("POST", `/clips/${id}/render`, {
|
|
304
|
+
editRevision,
|
|
305
|
+
...params.candidateId ? { candidateId: params.candidateId } : {}
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/** POST /clips/:id/create-draft — turns a rendered clip into a draft post. */
|
|
309
|
+
createDraft(id, params = {}) {
|
|
310
|
+
return this.client.request("POST", `/clips/${id}/create-draft`, params);
|
|
303
311
|
}
|
|
304
312
|
remove(id) {
|
|
305
313
|
return this.client.request("DELETE", `/clips/${id}`);
|
|
@@ -655,11 +663,151 @@ var init_dist = __esm({
|
|
|
655
663
|
}
|
|
656
664
|
});
|
|
657
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
|
+
|
|
658
791
|
// src/mcp/tools.ts
|
|
659
|
-
var ALL_TOOLS;
|
|
792
|
+
var MEDIA_ITEMS, ALL_TOOLS;
|
|
660
793
|
var init_tools = __esm({
|
|
661
794
|
"src/mcp/tools.ts"() {
|
|
662
795
|
"use strict";
|
|
796
|
+
MEDIA_ITEMS = {
|
|
797
|
+
type: "array",
|
|
798
|
+
maxItems: 10,
|
|
799
|
+
description: "Images or videos to attach, as https URLs. For a local file, call upload-media first and pass its result here.",
|
|
800
|
+
items: {
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {
|
|
803
|
+
url: { type: "string", description: "Public https URL of the image or video" },
|
|
804
|
+
type: { type: "string", enum: ["image", "video", "gif"] },
|
|
805
|
+
altText: { type: "string", description: "Optional alt text" }
|
|
806
|
+
},
|
|
807
|
+
required: ["url", "type"],
|
|
808
|
+
additionalProperties: false
|
|
809
|
+
}
|
|
810
|
+
};
|
|
663
811
|
ALL_TOOLS = [
|
|
664
812
|
// Posts
|
|
665
813
|
{
|
|
@@ -707,7 +855,8 @@ var init_tools = __esm({
|
|
|
707
855
|
scheduledAt: {
|
|
708
856
|
type: "string",
|
|
709
857
|
description: "ISO 8601 datetime to schedule the post"
|
|
710
|
-
}
|
|
858
|
+
},
|
|
859
|
+
media: MEDIA_ITEMS
|
|
711
860
|
},
|
|
712
861
|
required: ["brandSlug", "text", "platforms"],
|
|
713
862
|
additionalProperties: false
|
|
@@ -715,14 +864,15 @@ var init_tools = __esm({
|
|
|
715
864
|
},
|
|
716
865
|
{
|
|
717
866
|
name: "update-post",
|
|
718
|
-
description: "Update the text, platforms,
|
|
867
|
+
description: "Update the text, platforms, scheduled time, or media of an existing post.",
|
|
719
868
|
inputSchema: {
|
|
720
869
|
type: "object",
|
|
721
870
|
properties: {
|
|
722
871
|
id: { type: "string", description: "Post ID" },
|
|
723
872
|
text: { type: "string", description: "New post text" },
|
|
724
873
|
platforms: { type: "string", description: "Comma-separated new platform list" },
|
|
725
|
-
scheduledAt: { type: "string", description: "New ISO 8601 scheduled datetime" }
|
|
874
|
+
scheduledAt: { type: "string", description: "New ISO 8601 scheduled datetime" },
|
|
875
|
+
media: MEDIA_ITEMS
|
|
726
876
|
},
|
|
727
877
|
required: ["id"],
|
|
728
878
|
additionalProperties: false
|
|
@@ -1156,29 +1306,79 @@ var init_tools = __esm({
|
|
|
1156
1306
|
},
|
|
1157
1307
|
{
|
|
1158
1308
|
name: "import-clip",
|
|
1159
|
-
description: "
|
|
1309
|
+
description: "Start AI clipping: import a long video from a public URL (e.g. a YouTube video). Poll get-clip until status is ready.",
|
|
1160
1310
|
inputSchema: {
|
|
1161
1311
|
type: "object",
|
|
1162
1312
|
properties: {
|
|
1163
1313
|
url: { type: "string", description: "URL of the video to import" },
|
|
1314
|
+
brandId: { type: "string", description: "Brand ID (the `id` from list-brands)" },
|
|
1164
1315
|
name: { type: "string", description: "Optional name for the imported clip" }
|
|
1165
1316
|
},
|
|
1166
|
-
required: ["url"],
|
|
1317
|
+
required: ["url", "brandId"],
|
|
1167
1318
|
additionalProperties: false
|
|
1168
1319
|
}
|
|
1169
1320
|
},
|
|
1170
1321
|
{
|
|
1171
1322
|
name: "render-clip",
|
|
1172
|
-
description: "
|
|
1323
|
+
description: "Render a clip (uses credits). Call get-clip first for its editRevision and candidates.",
|
|
1324
|
+
inputSchema: {
|
|
1325
|
+
type: "object",
|
|
1326
|
+
properties: {
|
|
1327
|
+
id: { type: "string", description: "Clip ID to render" },
|
|
1328
|
+
editRevision: { type: "number", description: "Current editRevision from get-clip" },
|
|
1329
|
+
candidateId: { type: "string", description: "Optional: render only this candidate" }
|
|
1330
|
+
},
|
|
1331
|
+
required: ["id", "editRevision"],
|
|
1332
|
+
additionalProperties: false
|
|
1333
|
+
}
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
name: "create-clip-draft",
|
|
1337
|
+
description: "Create a draft post from a rendered clip. Publish it with publish-post or schedule it.",
|
|
1173
1338
|
inputSchema: {
|
|
1174
1339
|
type: "object",
|
|
1175
1340
|
properties: {
|
|
1176
|
-
id: { type: "string", description: "Clip ID
|
|
1341
|
+
id: { type: "string", description: "Clip ID" },
|
|
1342
|
+
candidateId: { type: "string", description: "Optional candidate ID (defaults to the first rendered one)" },
|
|
1343
|
+
aspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1", "4:5"], description: "Optional render format" }
|
|
1177
1344
|
},
|
|
1178
1345
|
required: ["id"],
|
|
1179
1346
|
additionalProperties: false
|
|
1180
1347
|
}
|
|
1181
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
|
+
},
|
|
1182
1382
|
{
|
|
1183
1383
|
name: "delete-clip",
|
|
1184
1384
|
description: "Permanently delete a video clip by ID.",
|
|
@@ -1191,6 +1391,45 @@ var init_tools = __esm({
|
|
|
1191
1391
|
additionalProperties: false
|
|
1192
1392
|
}
|
|
1193
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
|
+
},
|
|
1194
1433
|
// Carousels
|
|
1195
1434
|
{
|
|
1196
1435
|
name: "list-carousels",
|
|
@@ -1373,6 +1612,40 @@ var init_tools = __esm({
|
|
|
1373
1612
|
}
|
|
1374
1613
|
});
|
|
1375
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
|
+
|
|
1376
1649
|
// src/mcp/handler.ts
|
|
1377
1650
|
function ok(data) {
|
|
1378
1651
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
@@ -1392,6 +1665,15 @@ function parsePlatforms2(raw) {
|
|
|
1392
1665
|
}
|
|
1393
1666
|
return platforms;
|
|
1394
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
|
+
}
|
|
1395
1677
|
function parseEvents(raw) {
|
|
1396
1678
|
if (typeof raw !== "string") return [];
|
|
1397
1679
|
return raw.split(",").map((e) => e.trim());
|
|
@@ -1424,7 +1706,8 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1424
1706
|
brandSlug: args.brandSlug,
|
|
1425
1707
|
text: args.text,
|
|
1426
1708
|
platforms: parsePlatforms2(args.platforms),
|
|
1427
|
-
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {}
|
|
1709
|
+
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1710
|
+
...args.media ? { media: args.media } : {}
|
|
1428
1711
|
});
|
|
1429
1712
|
return ok(result);
|
|
1430
1713
|
}
|
|
@@ -1434,7 +1717,8 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1434
1717
|
...args.platforms ? {
|
|
1435
1718
|
platforms: parsePlatforms2(args.platforms)
|
|
1436
1719
|
} : {},
|
|
1437
|
-
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {}
|
|
1720
|
+
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1721
|
+
...args.media ? { media: args.media } : {}
|
|
1438
1722
|
});
|
|
1439
1723
|
return ok(result);
|
|
1440
1724
|
}
|
|
@@ -1622,14 +1906,40 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1622
1906
|
case "import-clip": {
|
|
1623
1907
|
const result = await client.clips.importUrl({
|
|
1624
1908
|
url: args.url,
|
|
1625
|
-
|
|
1909
|
+
brandId: args.brandId,
|
|
1910
|
+
...args.name ? { title: args.name } : {}
|
|
1626
1911
|
});
|
|
1627
1912
|
return ok(result);
|
|
1628
1913
|
}
|
|
1629
1914
|
case "render-clip": {
|
|
1630
|
-
const result = await client.clips.render(args.id
|
|
1915
|
+
const result = await client.clips.render(args.id, {
|
|
1916
|
+
editRevision: args.editRevision,
|
|
1917
|
+
...args.candidateId ? { candidateId: args.candidateId } : {}
|
|
1918
|
+
});
|
|
1631
1919
|
return ok(result);
|
|
1632
1920
|
}
|
|
1921
|
+
case "create-clip-draft": {
|
|
1922
|
+
const result = await client.clips.createDraft(args.id, {
|
|
1923
|
+
...args.candidateId ? { candidateId: args.candidateId } : {},
|
|
1924
|
+
...args.aspectRatio ? { aspectRatio: args.aspectRatio } : {}
|
|
1925
|
+
});
|
|
1926
|
+
return ok(result);
|
|
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
|
+
}
|
|
1633
1943
|
case "delete-clip": {
|
|
1634
1944
|
await client.clips.remove(args.id);
|
|
1635
1945
|
return ok({ deleted: true, id: args.id });
|
|
@@ -1698,6 +2008,31 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1698
2008
|
await client.webhooks.test(args.id);
|
|
1699
2009
|
return ok({ sent: true, id: args.id });
|
|
1700
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
|
+
}
|
|
1701
2036
|
// Billing
|
|
1702
2037
|
case "billing-status": {
|
|
1703
2038
|
const result = await client.billing.status();
|
|
@@ -1719,10 +2054,14 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1719
2054
|
};
|
|
1720
2055
|
}
|
|
1721
2056
|
}
|
|
1722
|
-
var PLATFORMS, PLATFORM_SET;
|
|
2057
|
+
var import_promises2, import_node_path4, PLATFORMS, PLATFORM_SET, routeCache;
|
|
1723
2058
|
var init_handler = __esm({
|
|
1724
2059
|
"src/mcp/handler.ts"() {
|
|
1725
2060
|
"use strict";
|
|
2061
|
+
import_promises2 = require("fs/promises");
|
|
2062
|
+
import_node_path4 = require("path");
|
|
2063
|
+
init_api_routes();
|
|
2064
|
+
init_media_flags();
|
|
1726
2065
|
PLATFORMS = [
|
|
1727
2066
|
"x",
|
|
1728
2067
|
"linkedin",
|
|
@@ -1731,6 +2070,7 @@ var init_handler = __esm({
|
|
|
1731
2070
|
"youtube"
|
|
1732
2071
|
];
|
|
1733
2072
|
PLATFORM_SET = new Set(PLATFORMS);
|
|
2073
|
+
routeCache = /* @__PURE__ */ new WeakMap();
|
|
1734
2074
|
}
|
|
1735
2075
|
});
|
|
1736
2076
|
|
|
@@ -1769,7 +2109,7 @@ var init_server = __esm({
|
|
|
1769
2109
|
});
|
|
1770
2110
|
|
|
1771
2111
|
// src/cli.ts
|
|
1772
|
-
var
|
|
2112
|
+
var import_commander21 = require("commander");
|
|
1773
2113
|
init_dist();
|
|
1774
2114
|
|
|
1775
2115
|
// src/commands/agents.ts
|
|
@@ -2320,16 +2660,26 @@ async function requestDeviceCode(baseUrl) {
|
|
|
2320
2660
|
const text = await res.text().catch(() => res.statusText);
|
|
2321
2661
|
throw new Error(`Device code request failed (${res.status}): ${text}`);
|
|
2322
2662
|
}
|
|
2323
|
-
|
|
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;
|
|
2324
2668
|
}
|
|
2325
2669
|
async function pollDeviceCode(baseUrl, deviceCode) {
|
|
2326
2670
|
const url = `${baseUrl}/auth/cli/poll?device_code=${encodeURIComponent(deviceCode)}`;
|
|
2327
2671
|
const res = await fetchWithTimeout(url);
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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;
|
|
2331
2681
|
}
|
|
2332
|
-
|
|
2682
|
+
throw new Error(`Poll request failed (${res.status}): ${text}`);
|
|
2333
2683
|
}
|
|
2334
2684
|
|
|
2335
2685
|
// src/commands/auth.ts
|
|
@@ -2563,125 +2913,7 @@ function createBillingCommand() {
|
|
|
2563
2913
|
// src/commands/brands.ts
|
|
2564
2914
|
var import_commander4 = require("commander");
|
|
2565
2915
|
init_dist();
|
|
2566
|
-
|
|
2567
|
-
// src/lib/media-flags.ts
|
|
2568
|
-
var import_node_fs2 = require("fs");
|
|
2569
|
-
var VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
2570
|
-
var EXT_TO_MIME = {
|
|
2571
|
-
jpg: "image/jpeg",
|
|
2572
|
-
jpeg: "image/jpeg",
|
|
2573
|
-
png: "image/png",
|
|
2574
|
-
gif: "image/gif",
|
|
2575
|
-
webp: "image/webp",
|
|
2576
|
-
mp4: "video/mp4",
|
|
2577
|
-
mov: "video/quicktime",
|
|
2578
|
-
webm: "video/webm"
|
|
2579
|
-
};
|
|
2580
|
-
function extToMime(filename) {
|
|
2581
|
-
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
2582
|
-
const mime = EXT_TO_MIME[ext];
|
|
2583
|
-
if (!mime) {
|
|
2584
|
-
throw new Error(
|
|
2585
|
-
`Unsupported file extension ".${ext || "(none)"}" in "${filename}". Supported: ${Object.keys(EXT_TO_MIME).join(", ")}`
|
|
2586
|
-
);
|
|
2587
|
-
}
|
|
2588
|
-
return mime;
|
|
2589
|
-
}
|
|
2590
|
-
function parsePairs(flag, vals) {
|
|
2591
|
-
const result = {};
|
|
2592
|
-
for (const val of vals) {
|
|
2593
|
-
const eqIdx = val.indexOf("=");
|
|
2594
|
-
if (eqIdx < 1) {
|
|
2595
|
-
throw new Error(`${flag}: expected "platform=value" format, got "${val}"`);
|
|
2596
|
-
}
|
|
2597
|
-
const p = val.slice(0, eqIdx).trim();
|
|
2598
|
-
const v = val.slice(eqIdx + 1);
|
|
2599
|
-
if (!VALID_PLATFORMS.includes(p)) {
|
|
2600
|
-
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
2601
|
-
}
|
|
2602
|
-
result[p] = v;
|
|
2603
|
-
}
|
|
2604
|
-
return result;
|
|
2605
|
-
}
|
|
2606
|
-
function parsePlatformMediaPairs(flag, vals) {
|
|
2607
|
-
const result = {};
|
|
2608
|
-
for (const val of vals) {
|
|
2609
|
-
const eqIdx = val.indexOf("=");
|
|
2610
|
-
if (eqIdx < 1) {
|
|
2611
|
-
throw new Error(`${flag}: expected "platform=path[,path...]" format, got "${val}"`);
|
|
2612
|
-
}
|
|
2613
|
-
const p = val.slice(0, eqIdx).trim();
|
|
2614
|
-
const paths = val.slice(eqIdx + 1).split(",").map((s) => s.trim()).filter(Boolean);
|
|
2615
|
-
if (!VALID_PLATFORMS.includes(p)) {
|
|
2616
|
-
throw new Error(`${flag}: unknown platform "${p}". Valid: ${VALID_PLATFORMS.join(", ")}`);
|
|
2617
|
-
}
|
|
2618
|
-
result[p] = paths;
|
|
2619
|
-
}
|
|
2620
|
-
return result;
|
|
2621
|
-
}
|
|
2622
|
-
function validateMediaExtensions(paths) {
|
|
2623
|
-
for (const p of paths) extToMime(p);
|
|
2624
|
-
}
|
|
2625
|
-
function validateMediaCount(paths) {
|
|
2626
|
-
if (paths.length > 10) {
|
|
2627
|
-
throw new Error(`--media accepts at most 10 files (received ${paths.length}).`);
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
function validateMediaPaths(paths) {
|
|
2631
|
-
for (const p of paths) {
|
|
2632
|
-
if (!(0, import_node_fs2.existsSync)(p)) {
|
|
2633
|
-
throw new Error(`Media file not found: "${p}"`);
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
function alignAltText(mediaPaths, altTexts) {
|
|
2638
|
-
if (altTexts.length > mediaPaths.length) {
|
|
2639
|
-
throw new Error(
|
|
2640
|
-
`--alt-text has more values (${altTexts.length}) than --media (${mediaPaths.length}). Alt text aligns to media by index.`
|
|
2641
|
-
);
|
|
2642
|
-
}
|
|
2643
|
-
return mediaPaths.map((_, i) => altTexts[i]);
|
|
2644
|
-
}
|
|
2645
|
-
function buildYoutubeOptions(opts) {
|
|
2646
|
-
const { ytTitle, ytDescription, ytTags, ytPrivacy, ytCategory, ytMadeForKids } = opts;
|
|
2647
|
-
const o = {};
|
|
2648
|
-
if (ytTitle) o.title = ytTitle;
|
|
2649
|
-
if (ytDescription) o.description = ytDescription;
|
|
2650
|
-
if (ytTags) o.tags = ytTags.split(",").map((t) => t.trim()).filter(Boolean);
|
|
2651
|
-
if (ytPrivacy) o.privacyStatus = ytPrivacy;
|
|
2652
|
-
if (ytCategory) o.categoryId = ytCategory;
|
|
2653
|
-
if (ytMadeForKids !== void 0) o.madeForKids = ytMadeForKids;
|
|
2654
|
-
return Object.keys(o).length > 0 ? o : void 0;
|
|
2655
|
-
}
|
|
2656
|
-
function buildInstagramOptions(opts) {
|
|
2657
|
-
const { igReel, igShareToFeed, igCoverUrl, igThumbOffsetMs, igCollaborators } = opts;
|
|
2658
|
-
const hasAnyReelFlag = igReel !== void 0 || igShareToFeed !== void 0 || igCoverUrl !== void 0 || igThumbOffsetMs !== void 0 || igCollaborators !== void 0;
|
|
2659
|
-
if (!hasAnyReelFlag) return void 0;
|
|
2660
|
-
const reel = {};
|
|
2661
|
-
if (igShareToFeed !== void 0) reel.shareToFeed = igShareToFeed;
|
|
2662
|
-
if (igCoverUrl) reel.coverUrl = igCoverUrl;
|
|
2663
|
-
if (igThumbOffsetMs !== void 0) {
|
|
2664
|
-
const n = Number(igThumbOffsetMs);
|
|
2665
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
2666
|
-
throw new Error(
|
|
2667
|
-
`--ig-thumb-offset-ms must be a non-negative number (received "${igThumbOffsetMs}")`
|
|
2668
|
-
);
|
|
2669
|
-
}
|
|
2670
|
-
reel.thumbOffsetMs = n;
|
|
2671
|
-
}
|
|
2672
|
-
if (igCollaborators) {
|
|
2673
|
-
reel.collaborators = igCollaborators.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2674
|
-
}
|
|
2675
|
-
return { reel };
|
|
2676
|
-
}
|
|
2677
|
-
function buildThreadsOptions(opts) {
|
|
2678
|
-
const { threadsReplyTo, threadsReplyControl } = opts;
|
|
2679
|
-
if (!threadsReplyTo && !threadsReplyControl) return void 0;
|
|
2680
|
-
const o = {};
|
|
2681
|
-
if (threadsReplyTo) o.replyToId = threadsReplyTo;
|
|
2682
|
-
if (threadsReplyControl) o.replyControl = threadsReplyControl;
|
|
2683
|
-
return o;
|
|
2684
|
-
}
|
|
2916
|
+
init_media_flags();
|
|
2685
2917
|
|
|
2686
2918
|
// src/auth/config-store.ts
|
|
2687
2919
|
var import_node_fs3 = __toESM(require("fs"));
|
|
@@ -3118,16 +3350,18 @@ function createClipsCommand() {
|
|
|
3118
3350
|
process.exit(resolveExitCode4(err));
|
|
3119
3351
|
}
|
|
3120
3352
|
});
|
|
3121
|
-
clips.command("import").description("
|
|
3353
|
+
clips.command("import").description("Start AI clipping from a video URL (e.g. a YouTube video)").requiredOption("--url <url>", "URL of the video to import").option("--brand <id>", "Brand ID (auto-picked when the workspace has exactly one brand)").option("--name <name>", "Name for the imported clip").action(async (opts, cmd) => {
|
|
3122
3354
|
const globals = cmd.optsWithGlobals();
|
|
3123
3355
|
const printer = createPrinter(globals);
|
|
3124
3356
|
const spinner = printer.spinner("Importing clip\u2026");
|
|
3125
3357
|
try {
|
|
3126
3358
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3127
3359
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3360
|
+
const brandId = await resolveBrandId(client, opts.brand);
|
|
3128
3361
|
const { clipId } = await client.clips.importUrl({
|
|
3129
3362
|
url: opts.url,
|
|
3130
|
-
|
|
3363
|
+
brandId,
|
|
3364
|
+
...opts.name ? { title: opts.name } : {}
|
|
3131
3365
|
});
|
|
3132
3366
|
spinner.stop();
|
|
3133
3367
|
printer.log({ clipId });
|
|
@@ -3137,14 +3371,17 @@ function createClipsCommand() {
|
|
|
3137
3371
|
process.exit(resolveExitCode4(err));
|
|
3138
3372
|
}
|
|
3139
3373
|
});
|
|
3140
|
-
clips.command("render <id>").description("
|
|
3374
|
+
clips.command("render <id>").description("Render a clip (uses credits)").option("--candidate <id>", "Render only this candidate").option("--revision <n>", "Clip editRevision (read from the clip when omitted)", (v) => Number(v)).action(async (id, opts, cmd) => {
|
|
3141
3375
|
const globals = cmd.optsWithGlobals();
|
|
3142
3376
|
const printer = createPrinter(globals);
|
|
3143
3377
|
const spinner = printer.spinner(`Rendering clip "${id}"\u2026`);
|
|
3144
3378
|
try {
|
|
3145
3379
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3146
3380
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3147
|
-
const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id
|
|
3381
|
+
const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id, {
|
|
3382
|
+
...opts.revision !== void 0 ? { editRevision: opts.revision } : {},
|
|
3383
|
+
...opts.candidate ? { candidateId: opts.candidate } : {}
|
|
3384
|
+
});
|
|
3148
3385
|
spinner.stop();
|
|
3149
3386
|
printer.log({ jobIds, activeJobIds, reusedJobIds });
|
|
3150
3387
|
} catch (err) {
|
|
@@ -3153,6 +3390,25 @@ function createClipsCommand() {
|
|
|
3153
3390
|
process.exit(resolveExitCode4(err));
|
|
3154
3391
|
}
|
|
3155
3392
|
});
|
|
3393
|
+
clips.command("draft <id>").description("Create a draft post from a rendered clip").option("--candidate <id>", "Candidate to use (defaults to the first rendered one)").option("--aspect-ratio <ratio>", "Render format: 9:16, 16:9, 1:1 or 4:5").action(async (id, opts, cmd) => {
|
|
3394
|
+
const globals = cmd.optsWithGlobals();
|
|
3395
|
+
const printer = createPrinter(globals);
|
|
3396
|
+
const spinner = printer.spinner(`Creating draft from clip "${id}"\u2026`);
|
|
3397
|
+
try {
|
|
3398
|
+
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3399
|
+
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3400
|
+
const { postId } = await client.clips.createDraft(id, {
|
|
3401
|
+
...opts.candidate ? { candidateId: opts.candidate } : {},
|
|
3402
|
+
...opts.aspectRatio ? { aspectRatio: opts.aspectRatio } : {}
|
|
3403
|
+
});
|
|
3404
|
+
spinner.stop();
|
|
3405
|
+
printer.log({ postId });
|
|
3406
|
+
} catch (err) {
|
|
3407
|
+
spinner.fail();
|
|
3408
|
+
printer.error(err);
|
|
3409
|
+
process.exit(resolveExitCode4(err));
|
|
3410
|
+
}
|
|
3411
|
+
});
|
|
3156
3412
|
clips.command("delete <id>").description("Delete a clip (requires --force)").option("--force", "Confirm deletion without interactive prompt").action(async (id, opts, cmd) => {
|
|
3157
3413
|
const globals = cmd.optsWithGlobals();
|
|
3158
3414
|
const printer = createPrinter(globals);
|
|
@@ -3183,6 +3439,7 @@ init_dist();
|
|
|
3183
3439
|
// src/lib/post-create.ts
|
|
3184
3440
|
var import_promises = __toESM(require("fs/promises"));
|
|
3185
3441
|
var import_node_path3 = __toESM(require("path"));
|
|
3442
|
+
init_media_flags();
|
|
3186
3443
|
|
|
3187
3444
|
// src/lib/account-select.ts
|
|
3188
3445
|
var VALID_PLATFORMS2 = ["x", "linkedin", "instagram", "threads", "youtube"];
|
|
@@ -4640,8 +4897,73 @@ function createConfigCommand() {
|
|
|
4640
4897
|
return config;
|
|
4641
4898
|
}
|
|
4642
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
|
+
|
|
4643
4965
|
// src/cli.ts
|
|
4644
|
-
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) => {
|
|
4645
4967
|
const opts = thisCommand.opts();
|
|
4646
4968
|
if (!opts.color) disableColor();
|
|
4647
4969
|
});
|
|
@@ -4650,6 +4972,7 @@ program.addCommand(createBillingCommand());
|
|
|
4650
4972
|
program.addCommand(createBrandsCommand());
|
|
4651
4973
|
program.addCommand(createCarouselsCommand());
|
|
4652
4974
|
program.addCommand(createClipsCommand());
|
|
4975
|
+
program.addCommand(createApiCommand());
|
|
4653
4976
|
program.addCommand(createPostsCommand());
|
|
4654
4977
|
program.addCommand(createAgentsCommand());
|
|
4655
4978
|
program.addCommand(createKbCommand());
|