@clickraft/cli 0.13.0 → 0.15.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/CHANGELOG.md +7 -10
- package/README.md +29 -0
- package/dist/cli.js +330 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.js +330 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
8
8
|
// ../sdk/dist/index.js
|
|
@@ -151,10 +151,10 @@ function parseProductCreateRequest(input) {
|
|
|
151
151
|
{ retryable: false, details: parsed.error.issues }
|
|
152
152
|
);
|
|
153
153
|
}
|
|
154
|
-
const
|
|
154
|
+
const request3 = parsed.data;
|
|
155
155
|
const seen = /* @__PURE__ */ new Set();
|
|
156
156
|
const duplicates = /* @__PURE__ */ new Set();
|
|
157
|
-
for (const img of
|
|
157
|
+
for (const img of request3.images) {
|
|
158
158
|
if (seen.has(img.assetId)) duplicates.add(img.assetId);
|
|
159
159
|
seen.add(img.assetId);
|
|
160
160
|
}
|
|
@@ -165,7 +165,7 @@ function parseProductCreateRequest(input) {
|
|
|
165
165
|
{ retryable: false }
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
|
-
const bytes = Buffer.byteLength(JSON.stringify(
|
|
168
|
+
const bytes = Buffer.byteLength(JSON.stringify(request3), "utf8");
|
|
169
169
|
if (bytes > MAX_PRODUCT_CREATE_BODY_BYTES) {
|
|
170
170
|
throw new ApiError(
|
|
171
171
|
"INPUT_INVALID_FORMAT",
|
|
@@ -173,7 +173,7 @@ function parseProductCreateRequest(input) {
|
|
|
173
173
|
{ retryable: false }
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
|
-
return
|
|
176
|
+
return request3;
|
|
177
177
|
}
|
|
178
178
|
var TokenRowSchema = z4.object({
|
|
179
179
|
id: z4.string().uuid(),
|
|
@@ -235,6 +235,10 @@ var GenerateCreateRequestSchema = z6.object({
|
|
|
235
235
|
"resolution must match WxH (e.g. 1280x720), p-token (e.g. 720p), or K-token (e.g. 1K)"
|
|
236
236
|
).optional(),
|
|
237
237
|
durationSeconds: z6.number().int().min(1).max(60).optional(),
|
|
238
|
+
// The model's quality tier (e.g. 'low' | 'medium' | 'high'). The valid set is
|
|
239
|
+
// per model, so it is checked server-side: an unoffered value returns 400
|
|
240
|
+
// INPUT_INVALID_FORMAT with the supported options in `details.allowed`.
|
|
241
|
+
quality: z6.string().min(1).max(40).optional(),
|
|
238
242
|
width: z6.number().int().min(64).max(8192).optional(),
|
|
239
243
|
height: z6.number().int().min(64).max(8192).optional(),
|
|
240
244
|
providerParams: z6.record(z6.string(), z6.unknown()).optional()
|
|
@@ -280,6 +284,34 @@ var GenerationResultSchema = z6.object({
|
|
|
280
284
|
startedAt: z6.string().nullable(),
|
|
281
285
|
completedAt: z6.string().nullable()
|
|
282
286
|
}).strict();
|
|
287
|
+
var GenerationListItemSchema = z6.object({
|
|
288
|
+
jobId: z6.string().uuid(),
|
|
289
|
+
status: GenerationStatusSchema,
|
|
290
|
+
modelSlug: z6.string().nullable(),
|
|
291
|
+
contentType: z6.enum(["image", "video", "audio"]),
|
|
292
|
+
prompt: z6.string().nullable(),
|
|
293
|
+
resultUrl: z6.string().nullable(),
|
|
294
|
+
thumbnailUrl: z6.string().nullable(),
|
|
295
|
+
errorCode: z6.string().nullable(),
|
|
296
|
+
errorMessage: z6.string().nullable(),
|
|
297
|
+
creditsCharged: z6.number().int().nonnegative(),
|
|
298
|
+
creditsRefunded: z6.boolean(),
|
|
299
|
+
createdAt: z6.string(),
|
|
300
|
+
completedAt: z6.string().nullable()
|
|
301
|
+
}).strict();
|
|
302
|
+
var GenerationListResponseSchema = z6.object({
|
|
303
|
+
jobs: z6.array(GenerationListItemSchema),
|
|
304
|
+
cursor: z6.string().nullable(),
|
|
305
|
+
hasMore: z6.boolean()
|
|
306
|
+
}).strict();
|
|
307
|
+
var GenerateEstimateResponseSchema = z6.object({
|
|
308
|
+
modelSlug: z6.string(),
|
|
309
|
+
creditCost: z6.number().int().nonnegative(),
|
|
310
|
+
estimatedSeconds: z6.number().int().nonnegative().nullable(),
|
|
311
|
+
balance: z6.number().nullable(),
|
|
312
|
+
affordable: z6.boolean(),
|
|
313
|
+
blockedReason: z6.enum(["insufficient_credits", "feature_disabled", "subscription_required", "past_due"]).nullable()
|
|
314
|
+
}).strict();
|
|
283
315
|
var MAX_UPLOAD_SIZE_BYTES = 15 * 1024 * 1024;
|
|
284
316
|
var UploadRequestSchema = z7.object({
|
|
285
317
|
filename: z7.string().min(1).max(255),
|
|
@@ -532,10 +564,10 @@ async function sleep(delayMs, signal) {
|
|
|
532
564
|
if (signal?.aborted) {
|
|
533
565
|
throw signalToAbortError(signal);
|
|
534
566
|
}
|
|
535
|
-
await new Promise((
|
|
567
|
+
await new Promise((resolve2, reject) => {
|
|
536
568
|
const timer = setTimeout(() => {
|
|
537
569
|
signal?.removeEventListener("abort", onAbort);
|
|
538
|
-
|
|
570
|
+
resolve2();
|
|
539
571
|
}, delayMs);
|
|
540
572
|
const onAbort = () => {
|
|
541
573
|
clearTimeout(timer);
|
|
@@ -1083,7 +1115,8 @@ async function createGeneration(params) {
|
|
|
1083
1115
|
path: "/jobs",
|
|
1084
1116
|
body: params.body,
|
|
1085
1117
|
responseSchema: params.createSchema,
|
|
1086
|
-
signal: params.signal
|
|
1118
|
+
signal: params.signal,
|
|
1119
|
+
idempotencyKey: params.idempotencyKey
|
|
1087
1120
|
});
|
|
1088
1121
|
if (!params.wait) {
|
|
1089
1122
|
return params.projectCreated(created, params.modelSlug);
|
|
@@ -2184,8 +2217,7 @@ async function generateWait(opts) {
|
|
|
2184
2217
|
|
|
2185
2218
|
// src/commands/generate/create.ts
|
|
2186
2219
|
var PREFLIGHT_PLACEHOLDER_URL = "https://placeholder.invalid/pending-upload";
|
|
2187
|
-
async function
|
|
2188
|
-
const client = opts.client ?? await buildGenerateClient(opts);
|
|
2220
|
+
async function prepareCreateBody(opts, client) {
|
|
2189
2221
|
const preflightRefs = (opts.referenceImages ?? []).map(
|
|
2190
2222
|
(v) => isUrlLike(v) ? v : PREFLIGHT_PLACEHOLDER_URL
|
|
2191
2223
|
);
|
|
@@ -2201,7 +2233,11 @@ async function generateCreate(opts) {
|
|
|
2201
2233
|
client,
|
|
2202
2234
|
signal: opts.signal
|
|
2203
2235
|
}))[0];
|
|
2204
|
-
|
|
2236
|
+
return buildRequestBody({ ...opts, referenceImages, startFrame });
|
|
2237
|
+
}
|
|
2238
|
+
async function generateCreate(opts) {
|
|
2239
|
+
const client = opts.client ?? await buildGenerateClient(opts);
|
|
2240
|
+
const body = await prepareCreateBody(opts, client);
|
|
2205
2241
|
return createGeneration({
|
|
2206
2242
|
client,
|
|
2207
2243
|
body,
|
|
@@ -2245,6 +2281,7 @@ function buildRequestBody(args) {
|
|
|
2245
2281
|
if (args.aspectRatio !== void 0) input.aspectRatio = args.aspectRatio;
|
|
2246
2282
|
if (args.resolution !== void 0) input.resolution = args.resolution;
|
|
2247
2283
|
if (args.durationSeconds !== void 0) input.durationSeconds = args.durationSeconds;
|
|
2284
|
+
if (args.quality !== void 0) input.quality = args.quality;
|
|
2248
2285
|
if (args.brandModels !== void 0 && args.brandModels.length > 0) {
|
|
2249
2286
|
input.providerParams = {
|
|
2250
2287
|
brandModels: args.brandModels.map((spec) => {
|
|
@@ -2297,6 +2334,87 @@ function projectCreateToResult(response, modelSlug) {
|
|
|
2297
2334
|
};
|
|
2298
2335
|
}
|
|
2299
2336
|
|
|
2337
|
+
// src/commands/generate/download.ts
|
|
2338
|
+
import { createWriteStream } from "fs";
|
|
2339
|
+
import { mkdir as mkdir2, rename as rename2, rm as rm2, stat as stat3 } from "fs/promises";
|
|
2340
|
+
import { basename as basename2, dirname as dirname2, extname, join as join2, resolve, sep } from "path";
|
|
2341
|
+
import { Readable } from "stream";
|
|
2342
|
+
import { pipeline } from "stream/promises";
|
|
2343
|
+
import { request } from "undici";
|
|
2344
|
+
var DOWNLOAD_TIMEOUT_MS = 12e4;
|
|
2345
|
+
async function isDirectory(path) {
|
|
2346
|
+
try {
|
|
2347
|
+
return (await stat3(path)).isDirectory();
|
|
2348
|
+
} catch {
|
|
2349
|
+
return false;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
function extensionOf(url) {
|
|
2353
|
+
const ext = extname(new URL(url).pathname);
|
|
2354
|
+
return /^\.[a-z0-9]{1,5}$/i.test(ext) ? ext.toLowerCase() : "";
|
|
2355
|
+
}
|
|
2356
|
+
async function resolveOutputPath(output, result) {
|
|
2357
|
+
if (!result.resultUrl) {
|
|
2358
|
+
throw new ApiError("INPUT_INVALID_FORMAT", "The job has no result to save.", {
|
|
2359
|
+
retryable: false
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
const asDir = output.endsWith("/") || output.endsWith(sep) || await isDirectory(output);
|
|
2363
|
+
return asDir ? resolve(output, `${result.jobId}${extensionOf(result.resultUrl)}`) : resolve(output);
|
|
2364
|
+
}
|
|
2365
|
+
async function downloadResult(result, output, options = {}) {
|
|
2366
|
+
if (result.status !== "completed" || !result.resultUrl) {
|
|
2367
|
+
throw new ApiError(
|
|
2368
|
+
"INPUT_INVALID_FORMAT",
|
|
2369
|
+
`Job ${result.jobId} is ${result.status}; only a completed job can be saved.`,
|
|
2370
|
+
{ retryable: false }
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
const url = result.resultUrl;
|
|
2374
|
+
if (!url.startsWith("https://") && !url.startsWith("http://")) {
|
|
2375
|
+
throw new ApiError("INPUT_INVALID_FORMAT", `Refusing to download a non-HTTP URL: ${url}`, {
|
|
2376
|
+
retryable: false
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
const target = await resolveOutputPath(output, result);
|
|
2380
|
+
await mkdir2(dirname2(target), { recursive: true });
|
|
2381
|
+
const partial = join2(dirname2(target), `.${basename2(target)}.part`);
|
|
2382
|
+
const signals = [AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)];
|
|
2383
|
+
if (options.signal) signals.push(options.signal);
|
|
2384
|
+
const res = await request(url, {
|
|
2385
|
+
method: "GET",
|
|
2386
|
+
signal: AbortSignal.any(signals),
|
|
2387
|
+
dispatcher: options.dispatcher
|
|
2388
|
+
});
|
|
2389
|
+
if (res.statusCode !== 200) {
|
|
2390
|
+
await res.body.dump();
|
|
2391
|
+
throw new ApiError("NETWORK_ERROR", `Could not download the result (HTTP ${res.statusCode}).`, {
|
|
2392
|
+
retryable: res.statusCode >= 500
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
try {
|
|
2396
|
+
await pipeline(Readable.from(res.body), createWriteStream(partial));
|
|
2397
|
+
await rename2(partial, target);
|
|
2398
|
+
} catch (err) {
|
|
2399
|
+
await rm2(partial, { force: true });
|
|
2400
|
+
throw err;
|
|
2401
|
+
}
|
|
2402
|
+
return target;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
// src/commands/generate/estimate.ts
|
|
2406
|
+
async function generateEstimate(opts) {
|
|
2407
|
+
const client = opts.client ?? await buildGenerateClient(opts);
|
|
2408
|
+
const body = await prepareCreateBody(opts, client);
|
|
2409
|
+
return client.request({
|
|
2410
|
+
method: "POST",
|
|
2411
|
+
path: "/jobs/estimate",
|
|
2412
|
+
body,
|
|
2413
|
+
responseSchema: GenerateEstimateResponseSchema,
|
|
2414
|
+
signal: opts.signal
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2300
2418
|
// src/commands/generate/get.ts
|
|
2301
2419
|
async function generateGet(opts) {
|
|
2302
2420
|
const client = opts.client ?? await buildGenerateClient(opts);
|
|
@@ -2309,6 +2427,23 @@ async function generateGet(opts) {
|
|
|
2309
2427
|
});
|
|
2310
2428
|
}
|
|
2311
2429
|
|
|
2430
|
+
// src/commands/generate/list.ts
|
|
2431
|
+
async function generateList(opts) {
|
|
2432
|
+
const client = opts.client ?? await buildGenerateClient(opts);
|
|
2433
|
+
return client.request({
|
|
2434
|
+
method: "GET",
|
|
2435
|
+
path: "/jobs",
|
|
2436
|
+
query: {
|
|
2437
|
+
limit: opts.limit,
|
|
2438
|
+
status: opts.status,
|
|
2439
|
+
contentType: opts.contentType,
|
|
2440
|
+
cursor: opts.cursor
|
|
2441
|
+
},
|
|
2442
|
+
responseSchema: GenerationListResponseSchema,
|
|
2443
|
+
signal: opts.signal
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2312
2447
|
// src/cli/args.ts
|
|
2313
2448
|
var ArgError = class extends Error {
|
|
2314
2449
|
constructor(message) {
|
|
@@ -2495,7 +2630,7 @@ function validateProductSpecs(specs) {
|
|
|
2495
2630
|
|
|
2496
2631
|
// src/auth/device-flow.ts
|
|
2497
2632
|
import { setTimeout as delay } from "timers/promises";
|
|
2498
|
-
import { request } from "undici";
|
|
2633
|
+
import { request as request2 } from "undici";
|
|
2499
2634
|
var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
2500
2635
|
var FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
2501
2636
|
async function runDeviceFlow(opts) {
|
|
@@ -2610,7 +2745,7 @@ async function attemptDeviceAuthorization(opts, scope) {
|
|
|
2610
2745
|
}
|
|
2611
2746
|
let resp;
|
|
2612
2747
|
try {
|
|
2613
|
-
resp = await
|
|
2748
|
+
resp = await request2(`${opts.apiBaseUrl}/oauth/device-authorization`, {
|
|
2614
2749
|
method: "POST",
|
|
2615
2750
|
headers,
|
|
2616
2751
|
body,
|
|
@@ -2692,7 +2827,7 @@ async function pollToken(args) {
|
|
|
2692
2827
|
}
|
|
2693
2828
|
let resp;
|
|
2694
2829
|
try {
|
|
2695
|
-
resp = await
|
|
2830
|
+
resp = await request2(`${args.apiBaseUrl}/oauth/token`, {
|
|
2696
2831
|
method: "POST",
|
|
2697
2832
|
headers,
|
|
2698
2833
|
body,
|
|
@@ -4333,6 +4468,8 @@ function renderRootHelp(version) {
|
|
|
4333
4468
|
" generate create Submit a generation job and wait for completion",
|
|
4334
4469
|
" generate wait Wait for an existing job to reach a terminal state",
|
|
4335
4470
|
" generate get Fetch the current state of a job",
|
|
4471
|
+
" generate list List your recent generations, newest first",
|
|
4472
|
+
" generate estimate Price a generation without running it",
|
|
4336
4473
|
" jobs wait Alias of `generate wait`",
|
|
4337
4474
|
" workflow create Create a new empty workflow",
|
|
4338
4475
|
" workflow list List workflows in your organization",
|
|
@@ -4604,6 +4741,8 @@ function renderCommandHelp(command) {
|
|
|
4604
4741
|
" create Submit a generation job (waits for completion by default)",
|
|
4605
4742
|
" wait Long-poll an existing job until terminal",
|
|
4606
4743
|
" get Fetch the current state of a job (no polling)",
|
|
4744
|
+
" list List your recent generations, newest first",
|
|
4745
|
+
" estimate Price a generation without running it (same flags as create)",
|
|
4607
4746
|
""
|
|
4608
4747
|
].join("\n");
|
|
4609
4748
|
case "generate create":
|
|
@@ -4627,10 +4766,13 @@ function renderCommandHelp(command) {
|
|
|
4627
4766
|
" --aspect-ratio <a:b> e.g. 16:9",
|
|
4628
4767
|
" --resolution <size> e.g. 1024x768, 720p, 1K",
|
|
4629
4768
|
" --duration-seconds <n> For video/audio models (1-60)",
|
|
4769
|
+
" --quality <tier> e.g. low, medium, high (valid values depend on the model)",
|
|
4630
4770
|
" --workflow-id <uuid> Attach to an existing workflow",
|
|
4631
4771
|
" --node-id <id> Attach to a specific node",
|
|
4632
4772
|
" --no-wait, --async Return immediately with the jobId",
|
|
4633
4773
|
" --timeout <seconds> Override the 120s default wait",
|
|
4774
|
+
" --output <path> Save the completed result to a file, or into a",
|
|
4775
|
+
" directory as <jobId>.<ext>",
|
|
4634
4776
|
" --profile <name> Credentials profile name",
|
|
4635
4777
|
" --api-base-url <url> Override the API base URL",
|
|
4636
4778
|
" --token <token> Override the access token",
|
|
@@ -4647,6 +4789,42 @@ function renderCommandHelp(command) {
|
|
|
4647
4789
|
"",
|
|
4648
4790
|
"Options:",
|
|
4649
4791
|
" --timeout <seconds> Overall wait deadline (default: 120)",
|
|
4792
|
+
" --output <path> Save the completed result (file, or directory \u2192 <jobId>.<ext>)",
|
|
4793
|
+
" --profile <name>",
|
|
4794
|
+
" --api-base-url <url>",
|
|
4795
|
+
" --token <token>",
|
|
4796
|
+
" --json",
|
|
4797
|
+
""
|
|
4798
|
+
].join("\n");
|
|
4799
|
+
case "generate list":
|
|
4800
|
+
return [
|
|
4801
|
+
"Usage: clickraft generate list [options]",
|
|
4802
|
+
"",
|
|
4803
|
+
"Lists your recent generations in the organization, newest first.",
|
|
4804
|
+
"",
|
|
4805
|
+
"Options:",
|
|
4806
|
+
" --limit <n> Results per page (1-50, default: 20)",
|
|
4807
|
+
" --status <status> queued, processing, uploading, completed, failed, cancelled",
|
|
4808
|
+
" --content-type <type> image, video, or audio",
|
|
4809
|
+
" --cursor <token> Pagination cursor from a previous response",
|
|
4810
|
+
" --profile <name>",
|
|
4811
|
+
" --api-base-url <url>",
|
|
4812
|
+
" --token <token>",
|
|
4813
|
+
" --json",
|
|
4814
|
+
""
|
|
4815
|
+
].join("\n");
|
|
4816
|
+
case "generate estimate":
|
|
4817
|
+
return [
|
|
4818
|
+
"Usage: clickraft generate estimate [prompt] --model-slug <slug> [options]",
|
|
4819
|
+
"",
|
|
4820
|
+
"Prices exactly what `generate create` with the same flags would charge, and",
|
|
4821
|
+
"says whether the account can run it now. Nothing is created or charged.",
|
|
4822
|
+
"Accepts the same generation flags as `generate create` (--prompt,",
|
|
4823
|
+
"--model-slug, --reference-image, --start-frame, --aspect-ratio, --resolution,",
|
|
4824
|
+
"--duration-seconds, --quality, --brand-model, --product, --workflow-id,",
|
|
4825
|
+
"--node-id). Local reference paths are uploaded first, as with create.",
|
|
4826
|
+
"",
|
|
4827
|
+
"Options:",
|
|
4650
4828
|
" --profile <name>",
|
|
4651
4829
|
" --api-base-url <url>",
|
|
4652
4830
|
" --token <token>",
|
|
@@ -4660,6 +4838,7 @@ function renderCommandHelp(command) {
|
|
|
4660
4838
|
"Fetches the current state of a generation job without polling.",
|
|
4661
4839
|
"",
|
|
4662
4840
|
"Options:",
|
|
4841
|
+
" --output <path> Save the completed result (file, or directory \u2192 <jobId>.<ext>)",
|
|
4663
4842
|
" --profile <name>",
|
|
4664
4843
|
" --api-base-url <url>",
|
|
4665
4844
|
" --token <token>",
|
|
@@ -4791,6 +4970,8 @@ function matchNewCommand(argv) {
|
|
|
4791
4970
|
if (sub === "create") return { kind: "cmd", path: "generate.create", verbTokenCount: 2 };
|
|
4792
4971
|
if (sub === "wait") return { kind: "cmd", path: "generate.wait", verbTokenCount: 2 };
|
|
4793
4972
|
if (sub === "get") return { kind: "cmd", path: "generate.get", verbTokenCount: 2 };
|
|
4973
|
+
if (sub === "list") return { kind: "cmd", path: "generate.list", verbTokenCount: 2 };
|
|
4974
|
+
if (sub === "estimate") return { kind: "cmd", path: "generate.estimate", verbTokenCount: 2 };
|
|
4794
4975
|
if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "generate" };
|
|
4795
4976
|
return { kind: "unknown-sub", verb: "generate", sub };
|
|
4796
4977
|
}
|
|
@@ -4883,6 +5064,10 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
|
|
|
4883
5064
|
return await runGenerateWait(argvAfterVerb, ctx, rc);
|
|
4884
5065
|
case "generate.get":
|
|
4885
5066
|
return await runGenerateGet(argvAfterVerb, ctx, rc);
|
|
5067
|
+
case "generate.list":
|
|
5068
|
+
return await runGenerateList(argvAfterVerb, ctx, rc);
|
|
5069
|
+
case "generate.estimate":
|
|
5070
|
+
return await runGenerateEstimate(argvAfterVerb, ctx, rc);
|
|
4886
5071
|
case "jobs.wait":
|
|
4887
5072
|
return await runJobsWait(argvAfterVerb, ctx, rc);
|
|
4888
5073
|
case "upload":
|
|
@@ -4945,9 +5130,11 @@ async function runGenerateCreate(argv, ctx, rc) {
|
|
|
4945
5130
|
"aspect-ratio",
|
|
4946
5131
|
"resolution",
|
|
4947
5132
|
"duration-seconds",
|
|
5133
|
+
"quality",
|
|
4948
5134
|
"workflow-id",
|
|
4949
5135
|
"node-id",
|
|
4950
|
-
"timeout"
|
|
5136
|
+
"timeout",
|
|
5137
|
+
"output"
|
|
4951
5138
|
],
|
|
4952
5139
|
boolean: [...COMMON_RICH_BOOL_FLAGS, "wait", "async"],
|
|
4953
5140
|
array: ["reference-image", "brand-model", "product"]
|
|
@@ -4968,6 +5155,7 @@ async function runGenerateCreate(argv, ctx, rc) {
|
|
|
4968
5155
|
aspectRatio: parsed.string["aspect-ratio"],
|
|
4969
5156
|
resolution: parsed.string.resolution,
|
|
4970
5157
|
durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
|
|
5158
|
+
quality: parsed.string.quality,
|
|
4971
5159
|
brandModels,
|
|
4972
5160
|
products,
|
|
4973
5161
|
workflowId: parsed.string["workflow-id"],
|
|
@@ -4979,12 +5167,12 @@ async function runGenerateCreate(argv, ctx, rc) {
|
|
|
4979
5167
|
token: parsed.string.token,
|
|
4980
5168
|
signal: rc.signal
|
|
4981
5169
|
});
|
|
4982
|
-
emitJob(result, ctx, rc);
|
|
5170
|
+
await emitJob(result, ctx, rc, parsed.string.output);
|
|
4983
5171
|
return exitCodeForResult(result);
|
|
4984
5172
|
}
|
|
4985
5173
|
async function runGenerateWait(argv, ctx, rc) {
|
|
4986
5174
|
const parsed = parseArgs(argv, {
|
|
4987
|
-
string: [...COMMON_RICH_STRING_FLAGS, "timeout"],
|
|
5175
|
+
string: [...COMMON_RICH_STRING_FLAGS, "timeout", "output"],
|
|
4988
5176
|
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
4989
5177
|
});
|
|
4990
5178
|
const jobId = parsed.positional[0];
|
|
@@ -4997,12 +5185,12 @@ async function runGenerateWait(argv, ctx, rc) {
|
|
|
4997
5185
|
token: parsed.string.token,
|
|
4998
5186
|
signal: rc.signal
|
|
4999
5187
|
});
|
|
5000
|
-
emitJob(result, ctx, rc);
|
|
5188
|
+
await emitJob(result, ctx, rc, parsed.string.output);
|
|
5001
5189
|
return exitCodeForResult(result);
|
|
5002
5190
|
}
|
|
5003
5191
|
async function runGenerateGet(argv, ctx, rc) {
|
|
5004
5192
|
const parsed = parseArgs(argv, {
|
|
5005
|
-
string: [...COMMON_RICH_STRING_FLAGS],
|
|
5193
|
+
string: [...COMMON_RICH_STRING_FLAGS, "output"],
|
|
5006
5194
|
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
5007
5195
|
});
|
|
5008
5196
|
const jobId = parsed.positional[0];
|
|
@@ -5014,12 +5202,84 @@ async function runGenerateGet(argv, ctx, rc) {
|
|
|
5014
5202
|
token: parsed.string.token,
|
|
5015
5203
|
signal: rc.signal
|
|
5016
5204
|
});
|
|
5017
|
-
emitJob(result, ctx, rc);
|
|
5205
|
+
await emitJob(result, ctx, rc, parsed.string.output);
|
|
5018
5206
|
return exitCodeForResult(result);
|
|
5019
5207
|
}
|
|
5208
|
+
async function runGenerateList(argv, ctx, rc) {
|
|
5209
|
+
const parsed = parseArgs(argv, {
|
|
5210
|
+
string: [...COMMON_RICH_STRING_FLAGS, "limit", "status", "content-type", "cursor"],
|
|
5211
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
5212
|
+
});
|
|
5213
|
+
const page = await generateList({
|
|
5214
|
+
limit: optionalInt(parsed.string.limit, "limit"),
|
|
5215
|
+
status: parsed.string.status,
|
|
5216
|
+
contentType: parsed.string["content-type"],
|
|
5217
|
+
cursor: parsed.string.cursor,
|
|
5218
|
+
profile: parsed.string.profile,
|
|
5219
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
5220
|
+
token: parsed.string.token,
|
|
5221
|
+
signal: rc.signal
|
|
5222
|
+
});
|
|
5223
|
+
if (rc.jsonMode) {
|
|
5224
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(page, ctx)) + "\n");
|
|
5225
|
+
return 0;
|
|
5226
|
+
}
|
|
5227
|
+
rc.stdout.write(renderGenerationList(page) + "\n");
|
|
5228
|
+
return 0;
|
|
5229
|
+
}
|
|
5230
|
+
async function runGenerateEstimate(argv, ctx, rc) {
|
|
5231
|
+
const parsed = parseArgs(argv, {
|
|
5232
|
+
string: [
|
|
5233
|
+
...COMMON_RICH_STRING_FLAGS,
|
|
5234
|
+
"prompt",
|
|
5235
|
+
"model-slug",
|
|
5236
|
+
"start-frame",
|
|
5237
|
+
"aspect-ratio",
|
|
5238
|
+
"resolution",
|
|
5239
|
+
"duration-seconds",
|
|
5240
|
+
"quality",
|
|
5241
|
+
"workflow-id",
|
|
5242
|
+
"node-id"
|
|
5243
|
+
],
|
|
5244
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS],
|
|
5245
|
+
array: ["reference-image", "brand-model", "product"]
|
|
5246
|
+
});
|
|
5247
|
+
const modelSlug = parsed.string["model-slug"];
|
|
5248
|
+
if (!modelSlug) throw new ArgError("--model-slug is required.");
|
|
5249
|
+
const rawBrandModels = parsed.array["brand-model"] ?? [];
|
|
5250
|
+
const brandModels = rawBrandModels.length > 0 ? rawBrandModels.map(parseBrandModelSpec) : void 0;
|
|
5251
|
+
if (brandModels) validateBrandModelSpecs(brandModels);
|
|
5252
|
+
const rawProducts = parsed.array["product"] ?? [];
|
|
5253
|
+
const products = rawProducts.length > 0 ? rawProducts.map(parseProductSpec) : void 0;
|
|
5254
|
+
if (products) validateProductSpecs(products);
|
|
5255
|
+
const estimate = await generateEstimate({
|
|
5256
|
+
prompt: parsed.string.prompt ?? parsed.positional[0],
|
|
5257
|
+
modelSlug,
|
|
5258
|
+
referenceImages: parsed.array["reference-image"],
|
|
5259
|
+
startFrame: parsed.string["start-frame"],
|
|
5260
|
+
aspectRatio: parsed.string["aspect-ratio"],
|
|
5261
|
+
resolution: parsed.string.resolution,
|
|
5262
|
+
durationSeconds: optionalInt(parsed.string["duration-seconds"], "duration-seconds"),
|
|
5263
|
+
quality: parsed.string.quality,
|
|
5264
|
+
brandModels,
|
|
5265
|
+
products,
|
|
5266
|
+
workflowId: parsed.string["workflow-id"],
|
|
5267
|
+
nodeId: parsed.string["node-id"],
|
|
5268
|
+
profile: parsed.string.profile,
|
|
5269
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
5270
|
+
token: parsed.string.token,
|
|
5271
|
+
signal: rc.signal
|
|
5272
|
+
});
|
|
5273
|
+
if (rc.jsonMode) {
|
|
5274
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(estimate, ctx)) + "\n");
|
|
5275
|
+
return 0;
|
|
5276
|
+
}
|
|
5277
|
+
rc.stdout.write(renderEstimate(estimate) + "\n");
|
|
5278
|
+
return 0;
|
|
5279
|
+
}
|
|
5020
5280
|
async function runJobsWait(argv, ctx, rc) {
|
|
5021
5281
|
const parsed = parseArgs(argv, {
|
|
5022
|
-
string: [...COMMON_RICH_STRING_FLAGS, "timeout"],
|
|
5282
|
+
string: [...COMMON_RICH_STRING_FLAGS, "timeout", "output"],
|
|
5023
5283
|
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
5024
5284
|
});
|
|
5025
5285
|
const jobId = parsed.positional[0];
|
|
@@ -5032,7 +5292,7 @@ async function runJobsWait(argv, ctx, rc) {
|
|
|
5032
5292
|
token: parsed.string.token,
|
|
5033
5293
|
signal: rc.signal
|
|
5034
5294
|
});
|
|
5035
|
-
emitJob(result, ctx, rc);
|
|
5295
|
+
await emitJob(result, ctx, rc, parsed.string.output);
|
|
5036
5296
|
return exitCodeForResult(result);
|
|
5037
5297
|
}
|
|
5038
5298
|
async function runUploadCmd(argv, ctx, rc) {
|
|
@@ -5053,15 +5313,27 @@ async function runUploadCmd(argv, ctx, rc) {
|
|
|
5053
5313
|
emitUpload(result, ctx, rc);
|
|
5054
5314
|
return 0;
|
|
5055
5315
|
}
|
|
5056
|
-
function emitJob(result, ctx, rc) {
|
|
5316
|
+
async function emitJob(result, ctx, rc, output) {
|
|
5057
5317
|
if (rc.signal?.aborted) {
|
|
5058
5318
|
throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
|
|
5059
5319
|
}
|
|
5320
|
+
let savedPath;
|
|
5321
|
+
if (output !== void 0) {
|
|
5322
|
+
if (result.status === "completed" && result.resultUrl) {
|
|
5323
|
+
savedPath = await downloadResult(result, output, { signal: rc.signal });
|
|
5324
|
+
} else {
|
|
5325
|
+
rc.stderr.write(`Not saved: job ${result.jobId} is ${result.status}.
|
|
5326
|
+
`);
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5060
5329
|
if (rc.jsonMode) {
|
|
5061
|
-
|
|
5330
|
+
const data = savedPath ? { ...result, savedPath } : result;
|
|
5331
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(data, ctx)) + "\n");
|
|
5062
5332
|
return;
|
|
5063
5333
|
}
|
|
5064
5334
|
rc.stdout.write(renderGenerationResult(result) + "\n");
|
|
5335
|
+
if (savedPath) rc.stdout.write(`Saved: ${savedPath}
|
|
5336
|
+
`);
|
|
5065
5337
|
}
|
|
5066
5338
|
function emitUpload(result, ctx, rc) {
|
|
5067
5339
|
if (rc.signal?.aborted) {
|
|
@@ -5084,6 +5356,38 @@ function renderGenerationResult(r) {
|
|
|
5084
5356
|
}
|
|
5085
5357
|
return lines.join("\n");
|
|
5086
5358
|
}
|
|
5359
|
+
function renderGenerationList(page) {
|
|
5360
|
+
if (page.jobs.length === 0) return "No generations.";
|
|
5361
|
+
const headers = ["JOB ID", "STATUS", "MODEL", "CREATED", "CREDITS", "PROMPT"];
|
|
5362
|
+
const PROMPT_WIDTH = 48;
|
|
5363
|
+
const data = page.jobs.map((job) => {
|
|
5364
|
+
const prompt = (job.prompt ?? "\u2014").replace(/\s+/g, " ");
|
|
5365
|
+
return [
|
|
5366
|
+
job.jobId,
|
|
5367
|
+
job.status,
|
|
5368
|
+
job.modelSlug ?? "\u2014",
|
|
5369
|
+
job.createdAt.slice(0, 16),
|
|
5370
|
+
String(job.creditsCharged),
|
|
5371
|
+
prompt.length > PROMPT_WIDTH ? `${prompt.slice(0, PROMPT_WIDTH - 1)}\u2026` : prompt
|
|
5372
|
+
];
|
|
5373
|
+
});
|
|
5374
|
+
const widths = headers.map(
|
|
5375
|
+
(header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
|
|
5376
|
+
);
|
|
5377
|
+
const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
|
|
5378
|
+
const lines = [padRow(headers), ...data.map(padRow)];
|
|
5379
|
+
if (page.hasMore && page.cursor) lines.push("", `More: --cursor ${page.cursor}`);
|
|
5380
|
+
return lines.join("\n");
|
|
5381
|
+
}
|
|
5382
|
+
function renderEstimate(e) {
|
|
5383
|
+
const lines = [`Model: ${e.modelSlug}`, `Estimated cost: ${e.creditCost} credits`];
|
|
5384
|
+
if (e.estimatedSeconds !== null) lines.push(`Typical duration: ~${e.estimatedSeconds}s`);
|
|
5385
|
+
if (e.balance !== null) lines.push(`Balance: ${e.balance} credits`);
|
|
5386
|
+
lines.push(
|
|
5387
|
+
e.affordable ? "Can run now: yes" : `Can run now: no (${e.blockedReason?.replace(/_/g, " ") ?? "blocked"})`
|
|
5388
|
+
);
|
|
5389
|
+
return lines.join("\n");
|
|
5390
|
+
}
|
|
5087
5391
|
function renderUploadResult(r) {
|
|
5088
5392
|
return [
|
|
5089
5393
|
`Uploaded: ${r.publicUrl}`,
|
|
@@ -5614,8 +5918,8 @@ async function readJsonFile(filePath) {
|
|
|
5614
5918
|
|
|
5615
5919
|
// src/index.ts
|
|
5616
5920
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
5617
|
-
var __dirname2 =
|
|
5618
|
-
var pkg = JSON.parse(readFileSync(
|
|
5921
|
+
var __dirname2 = dirname3(__filename2);
|
|
5922
|
+
var pkg = JSON.parse(readFileSync(join3(__dirname2, "..", "package.json"), "utf8"));
|
|
5619
5923
|
async function run(args, signal) {
|
|
5620
5924
|
return dispatch({
|
|
5621
5925
|
argv: args,
|