@flatkey-ai/cli 0.1.21 → 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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/api.js +4 -2
  3. package/src/cli.js +62 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flatkey-ai/cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Flatkey media generation CLI for image, video, audio, credits, status, and models.",
5
5
  "type": "module",
6
6
  "bin": {
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]), options.filename ?? "image");
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
@@ -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.");
@@ -421,18 +421,20 @@ async function handleImageUpload(command, deps) {
421
421
  });
422
422
  const response = await uploadTempMediaImage({
423
423
  apiKey,
424
- baseUrl: consoleOrigin,
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
- if (!data?.signed_url) {
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: data?.signed_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,
@@ -467,7 +470,7 @@ async function handleGenerate(command, deps) {
467
470
  env: deps.env ?? process.env,
468
471
  configDir: deps.configDir,
469
472
  });
470
- const { routerOrigin, consoleOrigin } = await resolveOrigins({
473
+ const { routerOrigin } = await resolveOrigins({
471
474
  baseUrl: command.options.base_url,
472
475
  consoleUrl: command.options.console_url,
473
476
  env: deps.env ?? process.env,
@@ -482,7 +485,7 @@ async function handleGenerate(command, deps) {
482
485
  ...command.options,
483
486
  apiKey,
484
487
  baseUrl: routerOrigin,
485
- tempMediaBaseUrl: tempMediaBaseUrl ?? consoleOrigin,
488
+ tempMediaBaseUrl: tempMediaBaseUrl ?? DEFAULT_CONSOLE_URL,
486
489
  env: deps.env ?? process.env,
487
490
  fetch: deps.fetch,
488
491
  };
@@ -570,13 +573,27 @@ async function uploadLocalImage(file, options, deps) {
570
573
  baseUrl: options.tempMediaBaseUrl ?? options.baseUrl,
571
574
  file: await readFile(await expandHomePath(file)),
572
575
  filename: basename(file),
576
+ contentType: imageContentTypeFromPath(file),
573
577
  fetch: options.fetch,
574
578
  });
575
- const url = response?.data?.signed_url ?? response?.signed_url;
579
+ const url = extractUploadedImageUrl(response);
576
580
  if (!url) throw new Error(`Image upload failed: missing signed_url for ${file}`);
577
581
  return url;
578
582
  }
579
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
+
580
597
  async function waitForVideoResult(response, options) {
581
598
  if (hasArtifact(response)) return response;
582
599
  const taskId = videoTaskId(response);
@@ -835,9 +852,45 @@ function formatVoices(voices) {
835
852
  function formatKeyValues(result) {
836
853
  const entries = Object.entries(result);
837
854
  if (entries.length === 0) return "OK";
838
- return entries
855
+ const lines = entries
839
856
  .map(([key, value]) => `${humanLabel(key)}: ${formatScalar(value)}`)
840
- .join("\n");
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;
841
894
  }
842
895
 
843
896
  function formatScalar(value) {