@autoposting.ai/cli 0.3.4 → 0.3.6
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 +217 -31
- package/dist/cli.cjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -42,11 +42,44 @@ function createError(status, body) {
|
|
|
42
42
|
if (status >= 500) return new ServerError(message, status, code);
|
|
43
43
|
return new AutopostingError(message, status, code);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
function resolvePartSize(fileSize, requested) {
|
|
46
|
+
let size = requested && requested > 0 ? requested : DEFAULT_PART_SIZE;
|
|
47
|
+
if (Math.ceil(fileSize / size) > MAX_PARTS) size = Math.ceil(fileSize / MAX_PARTS);
|
|
48
|
+
return Math.max(size, MIN_PART_SIZE);
|
|
49
|
+
}
|
|
50
|
+
function guessVideoMime(fileName) {
|
|
51
|
+
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
52
|
+
return VIDEO_MIME[ext] ?? "video/mp4";
|
|
53
|
+
}
|
|
54
|
+
async function putPart(url, body) {
|
|
55
|
+
let res;
|
|
56
|
+
try {
|
|
57
|
+
res = await fetch(url, { method: "PUT", body });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw new AutopostingError(
|
|
60
|
+
`Failed to upload part: ${err instanceof Error ? err.message : String(err)}`,
|
|
61
|
+
0,
|
|
62
|
+
"UPLOAD_FAILED"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
throw new AutopostingError(
|
|
67
|
+
`Failed to upload part: HTTP ${res.status} ${res.statusText}`,
|
|
68
|
+
res.status,
|
|
69
|
+
"UPLOAD_FAILED"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const etag = res.headers.get("etag");
|
|
73
|
+
if (!etag) {
|
|
74
|
+
throw new AutopostingError("Upload part response is missing the ETag header.", res.status, "UPLOAD_FAILED");
|
|
75
|
+
}
|
|
76
|
+
return etag;
|
|
77
|
+
}
|
|
78
|
+
var VERSION, AutopostingError, AuthenticationError, ScopeError, NotFoundError, ValidationError, RateLimitError, ServerError, Resource, MediaResource, AgentsResource, BillingResource, BrandsResource, CarouselsResource, MIN_PART_SIZE, DEFAULT_PART_SIZE, MAX_PARTS, VIDEO_MIME, ClipsResource, PostsResource, KbResource, IdeasResource, UsageResource, WebhooksResource, WorkspacesResource, IDEMPOTENT_METHODS, RETRYABLE_STATUS, sleep, Autoposting;
|
|
46
79
|
var init_dist = __esm({
|
|
47
80
|
"../sdk/dist/index.mjs"() {
|
|
48
81
|
"use strict";
|
|
49
|
-
VERSION = "0.3.
|
|
82
|
+
VERSION = "0.3.6";
|
|
50
83
|
AutopostingError = class extends Error {
|
|
51
84
|
status;
|
|
52
85
|
code;
|
|
@@ -180,6 +213,17 @@ var init_dist = __esm({
|
|
|
180
213
|
return this.client.request("DELETE", `/carousels/${id}`);
|
|
181
214
|
}
|
|
182
215
|
};
|
|
216
|
+
MIN_PART_SIZE = 5 * 1024 * 1024;
|
|
217
|
+
DEFAULT_PART_SIZE = 16 * 1024 * 1024;
|
|
218
|
+
MAX_PARTS = 1e4;
|
|
219
|
+
VIDEO_MIME = {
|
|
220
|
+
mp4: "video/mp4",
|
|
221
|
+
mov: "video/quicktime",
|
|
222
|
+
webm: "video/webm",
|
|
223
|
+
mkv: "video/x-matroska",
|
|
224
|
+
avi: "video/x-msvideo",
|
|
225
|
+
m4v: "video/x-m4v"
|
|
226
|
+
};
|
|
183
227
|
ClipsResource = class extends Resource {
|
|
184
228
|
/** GET /clips — backend returns `{ clips, pagination }`, not a bare array. */
|
|
185
229
|
list() {
|
|
@@ -188,24 +232,82 @@ var init_dist = __esm({
|
|
|
188
232
|
retrieve(id) {
|
|
189
233
|
return this.client.request("GET", `/clips/${id}`);
|
|
190
234
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Upload a local video file as a clip by driving the backend multipart flow:
|
|
237
|
+
* `init → presign → PUT each part → complete`. On any failure after init, the
|
|
238
|
+
* multipart upload is aborted so it leaves no dangling clip. Returns the created clip.
|
|
239
|
+
*
|
|
240
|
+
* Node-only: reads the file in chunks (memory-bounded for multi-GB videos). `node:fs`
|
|
241
|
+
* is imported dynamically so the rest of the SDK stays runtime-agnostic.
|
|
242
|
+
*/
|
|
243
|
+
async upload(filePath, options) {
|
|
244
|
+
if (!options?.brandId) {
|
|
245
|
+
throw new AutopostingError("brandId is required to upload a clip.", 0, "VALIDATION_ERROR");
|
|
246
|
+
}
|
|
247
|
+
const { open: open2, stat } = await import("fs/promises");
|
|
248
|
+
const { basename } = await import("path");
|
|
249
|
+
const fileName = basename(filePath);
|
|
250
|
+
const title = options.title ?? fileName;
|
|
251
|
+
const mimeType = options.contentType ?? guessVideoMime(fileName);
|
|
252
|
+
const { size: fileSize } = await stat(filePath);
|
|
253
|
+
if (fileSize <= 0) {
|
|
254
|
+
throw new AutopostingError(`File is empty: ${filePath}`, 0, "VALIDATION_ERROR");
|
|
255
|
+
}
|
|
256
|
+
const partSize = resolvePartSize(fileSize, options.partSize);
|
|
257
|
+
const partNumbers = Array.from(
|
|
258
|
+
{ length: Math.max(1, Math.ceil(fileSize / partSize)) },
|
|
259
|
+
(_, i) => i + 1
|
|
200
260
|
);
|
|
261
|
+
const { clipId, uploadId, key } = await this.client.request("POST", "/clips/upload/init", { brandId: options.brandId, title, fileName, fileSize, mimeType });
|
|
262
|
+
let completed;
|
|
263
|
+
const handle = await open2(filePath, "r");
|
|
264
|
+
try {
|
|
265
|
+
const { presignedUrls } = await this.client.request("POST", "/clips/upload/presign", { clipId, uploadId, key, partNumbers });
|
|
266
|
+
const urlByPart = new Map(presignedUrls.map((p) => [p.partNumber, p.url]));
|
|
267
|
+
const parts = [];
|
|
268
|
+
let uploaded = 0;
|
|
269
|
+
for (const partNumber of partNumbers) {
|
|
270
|
+
const url = urlByPart.get(partNumber);
|
|
271
|
+
if (!url) {
|
|
272
|
+
throw new AutopostingError(`Missing presigned URL for part ${partNumber}.`, 0, "UPLOAD_FAILED");
|
|
273
|
+
}
|
|
274
|
+
const offset = (partNumber - 1) * partSize;
|
|
275
|
+
const length = Math.min(partSize, fileSize - offset);
|
|
276
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
277
|
+
await handle.read(buffer, 0, length, offset);
|
|
278
|
+
parts.push({ partNumber, etag: await putPart(url, buffer) });
|
|
279
|
+
uploaded += length;
|
|
280
|
+
options.onProgress?.(Math.round(uploaded / fileSize * 100));
|
|
281
|
+
}
|
|
282
|
+
completed = await this.client.request(
|
|
283
|
+
"POST",
|
|
284
|
+
"/clips/upload/complete",
|
|
285
|
+
{ clipId, uploadId, key, parts }
|
|
286
|
+
);
|
|
287
|
+
} catch (err) {
|
|
288
|
+
await this.client.request("POST", "/clips/upload/abort", { clipId, uploadId, key }).catch(() => {
|
|
289
|
+
});
|
|
290
|
+
throw err;
|
|
291
|
+
} finally {
|
|
292
|
+
await handle.close();
|
|
293
|
+
}
|
|
294
|
+
return this.retrieve(completed.clipId);
|
|
201
295
|
}
|
|
202
296
|
/** POST /clips/import-url — backend returns `{ clipId }`, not a full Clip. */
|
|
203
297
|
importUrl(params) {
|
|
204
298
|
return this.client.request("POST", "/clips/import-url", params);
|
|
205
299
|
}
|
|
206
300
|
/** POST /clips/:id/render — backend returns render job ids, not a Clip. */
|
|
207
|
-
render(id) {
|
|
208
|
-
|
|
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);
|
|
209
311
|
}
|
|
210
312
|
remove(id) {
|
|
211
313
|
return this.client.request("DELETE", `/clips/${id}`);
|
|
@@ -562,10 +664,25 @@ var init_dist = __esm({
|
|
|
562
664
|
});
|
|
563
665
|
|
|
564
666
|
// src/mcp/tools.ts
|
|
565
|
-
var ALL_TOOLS;
|
|
667
|
+
var MEDIA_ITEMS, ALL_TOOLS;
|
|
566
668
|
var init_tools = __esm({
|
|
567
669
|
"src/mcp/tools.ts"() {
|
|
568
670
|
"use strict";
|
|
671
|
+
MEDIA_ITEMS = {
|
|
672
|
+
type: "array",
|
|
673
|
+
maxItems: 10,
|
|
674
|
+
description: "Images or videos to attach, as public https URLs. To attach a local file, use `ap posts create --media <file>` instead.",
|
|
675
|
+
items: {
|
|
676
|
+
type: "object",
|
|
677
|
+
properties: {
|
|
678
|
+
url: { type: "string", description: "Public https URL of the image or video" },
|
|
679
|
+
type: { type: "string", enum: ["image", "video", "gif"] },
|
|
680
|
+
altText: { type: "string", description: "Optional alt text" }
|
|
681
|
+
},
|
|
682
|
+
required: ["url", "type"],
|
|
683
|
+
additionalProperties: false
|
|
684
|
+
}
|
|
685
|
+
};
|
|
569
686
|
ALL_TOOLS = [
|
|
570
687
|
// Posts
|
|
571
688
|
{
|
|
@@ -613,7 +730,8 @@ var init_tools = __esm({
|
|
|
613
730
|
scheduledAt: {
|
|
614
731
|
type: "string",
|
|
615
732
|
description: "ISO 8601 datetime to schedule the post"
|
|
616
|
-
}
|
|
733
|
+
},
|
|
734
|
+
media: MEDIA_ITEMS
|
|
617
735
|
},
|
|
618
736
|
required: ["brandSlug", "text", "platforms"],
|
|
619
737
|
additionalProperties: false
|
|
@@ -621,14 +739,15 @@ var init_tools = __esm({
|
|
|
621
739
|
},
|
|
622
740
|
{
|
|
623
741
|
name: "update-post",
|
|
624
|
-
description: "Update the text, platforms,
|
|
742
|
+
description: "Update the text, platforms, scheduled time, or media of an existing post.",
|
|
625
743
|
inputSchema: {
|
|
626
744
|
type: "object",
|
|
627
745
|
properties: {
|
|
628
746
|
id: { type: "string", description: "Post ID" },
|
|
629
747
|
text: { type: "string", description: "New post text" },
|
|
630
748
|
platforms: { type: "string", description: "Comma-separated new platform list" },
|
|
631
|
-
scheduledAt: { type: "string", description: "New ISO 8601 scheduled datetime" }
|
|
749
|
+
scheduledAt: { type: "string", description: "New ISO 8601 scheduled datetime" },
|
|
750
|
+
media: MEDIA_ITEMS
|
|
632
751
|
},
|
|
633
752
|
required: ["id"],
|
|
634
753
|
additionalProperties: false
|
|
@@ -1062,24 +1181,41 @@ var init_tools = __esm({
|
|
|
1062
1181
|
},
|
|
1063
1182
|
{
|
|
1064
1183
|
name: "import-clip",
|
|
1065
|
-
description: "
|
|
1184
|
+
description: "Start AI clipping: import a long video from a public URL (e.g. a YouTube video). Poll get-clip until status is ready.",
|
|
1066
1185
|
inputSchema: {
|
|
1067
1186
|
type: "object",
|
|
1068
1187
|
properties: {
|
|
1069
1188
|
url: { type: "string", description: "URL of the video to import" },
|
|
1189
|
+
brandId: { type: "string", description: "Brand ID (the `id` from list-brands)" },
|
|
1070
1190
|
name: { type: "string", description: "Optional name for the imported clip" }
|
|
1071
1191
|
},
|
|
1072
|
-
required: ["url"],
|
|
1192
|
+
required: ["url", "brandId"],
|
|
1073
1193
|
additionalProperties: false
|
|
1074
1194
|
}
|
|
1075
1195
|
},
|
|
1076
1196
|
{
|
|
1077
1197
|
name: "render-clip",
|
|
1078
|
-
description: "
|
|
1198
|
+
description: "Render a clip (uses credits). Call get-clip first for its editRevision and candidates.",
|
|
1199
|
+
inputSchema: {
|
|
1200
|
+
type: "object",
|
|
1201
|
+
properties: {
|
|
1202
|
+
id: { type: "string", description: "Clip ID to render" },
|
|
1203
|
+
editRevision: { type: "number", description: "Current editRevision from get-clip" },
|
|
1204
|
+
candidateId: { type: "string", description: "Optional: render only this candidate" }
|
|
1205
|
+
},
|
|
1206
|
+
required: ["id", "editRevision"],
|
|
1207
|
+
additionalProperties: false
|
|
1208
|
+
}
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
name: "create-clip-draft",
|
|
1212
|
+
description: "Create a draft post from a rendered clip. Publish it with publish-post or schedule it.",
|
|
1079
1213
|
inputSchema: {
|
|
1080
1214
|
type: "object",
|
|
1081
1215
|
properties: {
|
|
1082
|
-
id: { type: "string", description: "Clip ID
|
|
1216
|
+
id: { type: "string", description: "Clip ID" },
|
|
1217
|
+
candidateId: { type: "string", description: "Optional candidate ID (defaults to the first rendered one)" },
|
|
1218
|
+
aspectRatio: { type: "string", enum: ["9:16", "16:9", "1:1", "4:5"], description: "Optional render format" }
|
|
1083
1219
|
},
|
|
1084
1220
|
required: ["id"],
|
|
1085
1221
|
additionalProperties: false
|
|
@@ -1330,7 +1466,8 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1330
1466
|
brandSlug: args.brandSlug,
|
|
1331
1467
|
text: args.text,
|
|
1332
1468
|
platforms: parsePlatforms2(args.platforms),
|
|
1333
|
-
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {}
|
|
1469
|
+
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1470
|
+
...args.media ? { media: args.media } : {}
|
|
1334
1471
|
});
|
|
1335
1472
|
return ok(result);
|
|
1336
1473
|
}
|
|
@@ -1340,7 +1477,8 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1340
1477
|
...args.platforms ? {
|
|
1341
1478
|
platforms: parsePlatforms2(args.platforms)
|
|
1342
1479
|
} : {},
|
|
1343
|
-
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {}
|
|
1480
|
+
...args.scheduledAt ? { scheduledAt: args.scheduledAt } : {},
|
|
1481
|
+
...args.media ? { media: args.media } : {}
|
|
1344
1482
|
});
|
|
1345
1483
|
return ok(result);
|
|
1346
1484
|
}
|
|
@@ -1528,12 +1666,23 @@ async function dispatchToolCall(name, args, client) {
|
|
|
1528
1666
|
case "import-clip": {
|
|
1529
1667
|
const result = await client.clips.importUrl({
|
|
1530
1668
|
url: args.url,
|
|
1531
|
-
|
|
1669
|
+
brandId: args.brandId,
|
|
1670
|
+
...args.name ? { title: args.name } : {}
|
|
1532
1671
|
});
|
|
1533
1672
|
return ok(result);
|
|
1534
1673
|
}
|
|
1535
1674
|
case "render-clip": {
|
|
1536
|
-
const result = await client.clips.render(args.id
|
|
1675
|
+
const result = await client.clips.render(args.id, {
|
|
1676
|
+
editRevision: args.editRevision,
|
|
1677
|
+
...args.candidateId ? { candidateId: args.candidateId } : {}
|
|
1678
|
+
});
|
|
1679
|
+
return ok(result);
|
|
1680
|
+
}
|
|
1681
|
+
case "create-clip-draft": {
|
|
1682
|
+
const result = await client.clips.createDraft(args.id, {
|
|
1683
|
+
...args.candidateId ? { candidateId: args.candidateId } : {},
|
|
1684
|
+
...args.aspectRatio ? { aspectRatio: args.aspectRatio } : {}
|
|
1685
|
+
});
|
|
1537
1686
|
return ok(result);
|
|
1538
1687
|
}
|
|
1539
1688
|
case "delete-clip": {
|
|
@@ -2950,6 +3099,17 @@ function resolveExitCode4(err) {
|
|
|
2950
3099
|
if (typeof attached === "number") return attached;
|
|
2951
3100
|
return exitCodeFromError(err);
|
|
2952
3101
|
}
|
|
3102
|
+
async function resolveBrandId(client, explicit) {
|
|
3103
|
+
if (explicit) return explicit;
|
|
3104
|
+
const brands = await client.brands.list();
|
|
3105
|
+
if (brands.length === 1) return brands[0].id;
|
|
3106
|
+
if (brands.length === 0) {
|
|
3107
|
+
throw new Error("No brands found. Create a brand first, then pass --brand <id>.");
|
|
3108
|
+
}
|
|
3109
|
+
throw new Error(
|
|
3110
|
+
`Multiple brands found \u2014 pass --brand <id>. Available: ${brands.map((b) => `${b.name} (${b.id})`).join(", ")}`
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
2953
3113
|
function createClipsCommand() {
|
|
2954
3114
|
const clips = new import_commander6.Command("clips").description("Manage video clips");
|
|
2955
3115
|
clips.command("list").description("List all clips").action(async (_opts, cmd) => {
|
|
@@ -2990,15 +3150,17 @@ function createClipsCommand() {
|
|
|
2990
3150
|
process.exit(resolveExitCode4(err));
|
|
2991
3151
|
}
|
|
2992
3152
|
});
|
|
2993
|
-
clips.command("upload <file>").description("Upload a video file as a clip").option("--name <name>", "Override the clip
|
|
3153
|
+
clips.command("upload <file>").description("Upload a video file as a clip").option("--brand <id>", "Brand to attach the clip to (defaults to your only brand)").option("--name <name>", "Override the clip title (defaults to filename)").action(async (file, opts, cmd) => {
|
|
2994
3154
|
const globals = cmd.optsWithGlobals();
|
|
2995
3155
|
const printer = createPrinter(globals);
|
|
2996
3156
|
const spinner = printer.spinner("Uploading clip\u2026");
|
|
2997
3157
|
try {
|
|
2998
3158
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
2999
3159
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3160
|
+
const brandId = await resolveBrandId(client, opts.brand);
|
|
3000
3161
|
const clip = await client.clips.upload(file, {
|
|
3001
|
-
|
|
3162
|
+
brandId,
|
|
3163
|
+
...opts.name ? { title: opts.name } : {},
|
|
3002
3164
|
onProgress: (pct) => {
|
|
3003
3165
|
if (pct > 0 && pct < 100) spinner.stop();
|
|
3004
3166
|
}
|
|
@@ -3011,16 +3173,18 @@ function createClipsCommand() {
|
|
|
3011
3173
|
process.exit(resolveExitCode4(err));
|
|
3012
3174
|
}
|
|
3013
3175
|
});
|
|
3014
|
-
clips.command("import").description("
|
|
3176
|
+
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) => {
|
|
3015
3177
|
const globals = cmd.optsWithGlobals();
|
|
3016
3178
|
const printer = createPrinter(globals);
|
|
3017
3179
|
const spinner = printer.spinner("Importing clip\u2026");
|
|
3018
3180
|
try {
|
|
3019
3181
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3020
3182
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3183
|
+
const brandId = await resolveBrandId(client, opts.brand);
|
|
3021
3184
|
const { clipId } = await client.clips.importUrl({
|
|
3022
3185
|
url: opts.url,
|
|
3023
|
-
|
|
3186
|
+
brandId,
|
|
3187
|
+
...opts.name ? { title: opts.name } : {}
|
|
3024
3188
|
});
|
|
3025
3189
|
spinner.stop();
|
|
3026
3190
|
printer.log({ clipId });
|
|
@@ -3030,14 +3194,17 @@ function createClipsCommand() {
|
|
|
3030
3194
|
process.exit(resolveExitCode4(err));
|
|
3031
3195
|
}
|
|
3032
3196
|
});
|
|
3033
|
-
clips.command("render <id>").description("
|
|
3197
|
+
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) => {
|
|
3034
3198
|
const globals = cmd.optsWithGlobals();
|
|
3035
3199
|
const printer = createPrinter(globals);
|
|
3036
3200
|
const spinner = printer.spinner(`Rendering clip "${id}"\u2026`);
|
|
3037
3201
|
try {
|
|
3038
3202
|
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3039
3203
|
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3040
|
-
const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id
|
|
3204
|
+
const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id, {
|
|
3205
|
+
...opts.revision !== void 0 ? { editRevision: opts.revision } : {},
|
|
3206
|
+
...opts.candidate ? { candidateId: opts.candidate } : {}
|
|
3207
|
+
});
|
|
3041
3208
|
spinner.stop();
|
|
3042
3209
|
printer.log({ jobIds, activeJobIds, reusedJobIds });
|
|
3043
3210
|
} catch (err) {
|
|
@@ -3046,6 +3213,25 @@ function createClipsCommand() {
|
|
|
3046
3213
|
process.exit(resolveExitCode4(err));
|
|
3047
3214
|
}
|
|
3048
3215
|
});
|
|
3216
|
+
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) => {
|
|
3217
|
+
const globals = cmd.optsWithGlobals();
|
|
3218
|
+
const printer = createPrinter(globals);
|
|
3219
|
+
const spinner = printer.spinner(`Creating draft from clip "${id}"\u2026`);
|
|
3220
|
+
try {
|
|
3221
|
+
const cred = resolveAuth({ apiKey: globals.apiKey });
|
|
3222
|
+
const client = new Autoposting({ apiKey: cred.apiKey });
|
|
3223
|
+
const { postId } = await client.clips.createDraft(id, {
|
|
3224
|
+
...opts.candidate ? { candidateId: opts.candidate } : {},
|
|
3225
|
+
...opts.aspectRatio ? { aspectRatio: opts.aspectRatio } : {}
|
|
3226
|
+
});
|
|
3227
|
+
spinner.stop();
|
|
3228
|
+
printer.log({ postId });
|
|
3229
|
+
} catch (err) {
|
|
3230
|
+
spinner.fail();
|
|
3231
|
+
printer.error(err);
|
|
3232
|
+
process.exit(resolveExitCode4(err));
|
|
3233
|
+
}
|
|
3234
|
+
});
|
|
3049
3235
|
clips.command("delete <id>").description("Delete a clip (requires --force)").option("--force", "Confirm deletion without interactive prompt").action(async (id, opts, cmd) => {
|
|
3050
3236
|
const globals = cmd.optsWithGlobals();
|
|
3051
3237
|
const printer = createPrinter(globals);
|