@flatkey-ai/cli 0.1.20 → 0.1.22
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/package.json +1 -1
- package/src/api.js +4 -2
- package/src/cli.js +71 -11
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -16,7 +16,9 @@ export async function generateImage(options) {
|
|
|
16
16
|
|
|
17
17
|
export async function uploadTempMediaImage(options) {
|
|
18
18
|
const form = new FormData();
|
|
19
|
-
form.append("file", new Blob([options.file]
|
|
19
|
+
form.append("file", new Blob([options.file], cleanObject({
|
|
20
|
+
type: options.contentType,
|
|
21
|
+
})), options.filename ?? "image");
|
|
20
22
|
return requestJsonFromPlan(options, planRequest(options, "/v1/temp-media/images", {
|
|
21
23
|
method: "POST",
|
|
22
24
|
headers: authHeaders(options.apiKey),
|
|
@@ -240,7 +242,7 @@ async function requestJsonFromPlan(options, plan) {
|
|
|
240
242
|
: JSON.stringify(plan.body),
|
|
241
243
|
});
|
|
242
244
|
const body = await readJson(response);
|
|
243
|
-
if (!response.ok) {
|
|
245
|
+
if (!response.ok || body?.success === false) {
|
|
244
246
|
throw new FlatkeyError(extractErrorMessage(body, response.status), {
|
|
245
247
|
status: response.status,
|
|
246
248
|
});
|
package/src/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ const COMMANDS = new Set([
|
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
17
|
const GROUP_ACTIONS = new Set(["audio", "auth", "image", "text", "video"]);
|
|
18
|
-
const GLOBAL_OPTIONS = new Set(["api_key", "base_url", "dry_run", "help", "json", "output", "out"]);
|
|
18
|
+
const GLOBAL_OPTIONS = new Set(["api_key", "base_url", "console_url", "dry_run", "help", "json", "output", "out"]);
|
|
19
19
|
const REPEATABLE_OPTIONS = new Set(["image", "image_url", "video_url"]);
|
|
20
20
|
const COMMAND_OPTIONS = {
|
|
21
21
|
"audio generate": new Set(["model", "prompt", "similarity_boost", "stability", "style", "voice_id"]),
|
|
@@ -404,7 +404,7 @@ async function handleImageUpload(command, deps) {
|
|
|
404
404
|
const { basename } = await import("node:path");
|
|
405
405
|
const { readFile } = await import("node:fs/promises");
|
|
406
406
|
const { resolveApiKey, resolveOrigins } = await import("./config.js");
|
|
407
|
-
const { uploadTempMediaImage } = await import("./api.js");
|
|
407
|
+
const { DEFAULT_CONSOLE_URL, uploadTempMediaImage } = await import("./api.js");
|
|
408
408
|
const file = command.options.file;
|
|
409
409
|
if (typeof file !== "string" || file.trim() === "") {
|
|
410
410
|
throw new Error("Missing --file value. Run `flatkey image upload --help` to see supported options.");
|
|
@@ -414,25 +414,27 @@ async function handleImageUpload(command, deps) {
|
|
|
414
414
|
env: deps.env ?? process.env,
|
|
415
415
|
configDir: deps.configDir,
|
|
416
416
|
});
|
|
417
|
-
const {
|
|
418
|
-
|
|
417
|
+
const { consoleOrigin } = await resolveOrigins({
|
|
418
|
+
consoleUrl: command.options.console_url,
|
|
419
419
|
env: deps.env ?? process.env,
|
|
420
420
|
configDir: deps.configDir,
|
|
421
421
|
});
|
|
422
422
|
const response = await uploadTempMediaImage({
|
|
423
423
|
apiKey,
|
|
424
|
-
baseUrl:
|
|
424
|
+
baseUrl: consoleOrigin ?? DEFAULT_CONSOLE_URL,
|
|
425
425
|
file: await readFile(await expandHomePath(file)),
|
|
426
426
|
filename: basename(file),
|
|
427
|
+
contentType: imageContentTypeFromPath(file),
|
|
427
428
|
fetch: deps.fetch,
|
|
428
429
|
});
|
|
429
430
|
const data = response?.data ?? response;
|
|
430
|
-
|
|
431
|
+
const signedUrl = extractUploadedImageUrl(response);
|
|
432
|
+
if (!signedUrl) {
|
|
431
433
|
throw new Error("Image upload failed: missing signed_url.");
|
|
432
434
|
}
|
|
433
435
|
return {
|
|
434
436
|
kind: "upload",
|
|
435
|
-
url:
|
|
437
|
+
url: signedUrl,
|
|
436
438
|
objectKey: data?.object_key,
|
|
437
439
|
expiresAt: data?.expires_at,
|
|
438
440
|
expiresIn: data?.expires_in,
|
|
@@ -449,6 +451,7 @@ async function handleGenerate(command, deps) {
|
|
|
449
451
|
generateImage,
|
|
450
452
|
generateText,
|
|
451
453
|
generateVideo,
|
|
454
|
+
DEFAULT_CONSOLE_URL,
|
|
452
455
|
getVideo,
|
|
453
456
|
planAudioMusicRequest,
|
|
454
457
|
planAudioRequest,
|
|
@@ -469,6 +472,12 @@ async function handleGenerate(command, deps) {
|
|
|
469
472
|
});
|
|
470
473
|
const { routerOrigin } = await resolveOrigins({
|
|
471
474
|
baseUrl: command.options.base_url,
|
|
475
|
+
consoleUrl: command.options.console_url,
|
|
476
|
+
env: deps.env ?? process.env,
|
|
477
|
+
configDir: deps.configDir,
|
|
478
|
+
});
|
|
479
|
+
const { consoleOrigin: tempMediaBaseUrl } = await resolveOrigins({
|
|
480
|
+
consoleUrl: command.options.console_url,
|
|
472
481
|
env: deps.env ?? process.env,
|
|
473
482
|
configDir: deps.configDir,
|
|
474
483
|
});
|
|
@@ -476,6 +485,7 @@ async function handleGenerate(command, deps) {
|
|
|
476
485
|
...command.options,
|
|
477
486
|
apiKey,
|
|
478
487
|
baseUrl: routerOrigin,
|
|
488
|
+
tempMediaBaseUrl: tempMediaBaseUrl ?? DEFAULT_CONSOLE_URL,
|
|
479
489
|
env: deps.env ?? process.env,
|
|
480
490
|
fetch: deps.fetch,
|
|
481
491
|
};
|
|
@@ -560,16 +570,30 @@ async function uploadLocalImage(file, options, deps) {
|
|
|
560
570
|
const { readFile } = await import("node:fs/promises");
|
|
561
571
|
const response = await deps.uploadTempMediaImage({
|
|
562
572
|
apiKey: options.apiKey,
|
|
563
|
-
baseUrl: options.baseUrl,
|
|
573
|
+
baseUrl: options.tempMediaBaseUrl ?? options.baseUrl,
|
|
564
574
|
file: await readFile(await expandHomePath(file)),
|
|
565
575
|
filename: basename(file),
|
|
576
|
+
contentType: imageContentTypeFromPath(file),
|
|
566
577
|
fetch: options.fetch,
|
|
567
578
|
});
|
|
568
|
-
const url = response
|
|
579
|
+
const url = extractUploadedImageUrl(response);
|
|
569
580
|
if (!url) throw new Error(`Image upload failed: missing signed_url for ${file}`);
|
|
570
581
|
return url;
|
|
571
582
|
}
|
|
572
583
|
|
|
584
|
+
function extractUploadedImageUrl(response) {
|
|
585
|
+
const data = response?.data ?? response;
|
|
586
|
+
return data?.signed_url ?? data?.signedUrl ?? data?.url ?? response?.signed_url ?? response?.signedUrl ?? response?.url;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function imageContentTypeFromPath(file) {
|
|
590
|
+
const lower = String(file).toLowerCase();
|
|
591
|
+
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
|
592
|
+
if (lower.endsWith(".png")) return "image/png";
|
|
593
|
+
if (lower.endsWith(".webp")) return "image/webp";
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
|
|
573
597
|
async function waitForVideoResult(response, options) {
|
|
574
598
|
if (hasArtifact(response)) return response;
|
|
575
599
|
const taskId = videoTaskId(response);
|
|
@@ -828,9 +852,45 @@ function formatVoices(voices) {
|
|
|
828
852
|
function formatKeyValues(result) {
|
|
829
853
|
const entries = Object.entries(result);
|
|
830
854
|
if (entries.length === 0) return "OK";
|
|
831
|
-
|
|
855
|
+
const lines = entries
|
|
832
856
|
.map(([key, value]) => `${humanLabel(key)}: ${formatScalar(value)}`)
|
|
833
|
-
|
|
857
|
+
const warning = lowCreditWarning(result);
|
|
858
|
+
if (warning) lines.push(warning);
|
|
859
|
+
return lines.join("\n");
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function lowCreditWarning(result) {
|
|
863
|
+
const credits = extractCreditAmount(result);
|
|
864
|
+
if (credits === undefined || credits >= 10) return "";
|
|
865
|
+
return `Low credit warning: remaining credits are ${credits}. Top up soon.`;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function extractCreditAmount(result) {
|
|
869
|
+
const creditKeys = new Set([
|
|
870
|
+
"credit",
|
|
871
|
+
"credits",
|
|
872
|
+
"remaining",
|
|
873
|
+
"balance",
|
|
874
|
+
"total_available",
|
|
875
|
+
"totalAvailable",
|
|
876
|
+
"available_balance",
|
|
877
|
+
"availableBalance",
|
|
878
|
+
]);
|
|
879
|
+
const stack = [result];
|
|
880
|
+
const seen = new Set();
|
|
881
|
+
while (stack.length > 0) {
|
|
882
|
+
const value = stack.shift();
|
|
883
|
+
if (!value || typeof value !== "object" || seen.has(value)) continue;
|
|
884
|
+
seen.add(value);
|
|
885
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
886
|
+
if (creditKeys.has(key)) {
|
|
887
|
+
const number = Number(entry);
|
|
888
|
+
if (Number.isFinite(number)) return number;
|
|
889
|
+
}
|
|
890
|
+
if (entry && typeof entry === "object") stack.push(entry);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return undefined;
|
|
834
894
|
}
|
|
835
895
|
|
|
836
896
|
function formatScalar(value) {
|