@flatkey-ai/cli 0.1.25 → 0.2.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/package.json +1 -1
- package/src/api.js +55 -4
- package/src/artifacts.js +3 -1
- package/src/cli.js +37 -11
- package/src/help.js +1 -0
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -29,12 +29,16 @@ export async function uploadTempMediaImage(options) {
|
|
|
29
29
|
export function planImageRequest(options) {
|
|
30
30
|
const model = options.model ?? "nano-banana-pro-preview";
|
|
31
31
|
if (model.startsWith("gpt")) {
|
|
32
|
+
const responseFormat = options.response_format ?? options.responseFormat ?? "url";
|
|
33
|
+
const tempUrl = options.temp_url ?? options.tempUrl;
|
|
32
34
|
return planJsonPost(options, "/v1/images/generations", cleanObject({
|
|
33
35
|
model,
|
|
34
36
|
prompt: options.prompt,
|
|
35
37
|
size: options.size,
|
|
36
38
|
n: parseOptionalInteger(options.n),
|
|
37
39
|
quality: options.quality,
|
|
40
|
+
response_format: responseFormat,
|
|
41
|
+
temp_url: tempUrl ?? true,
|
|
38
42
|
}));
|
|
39
43
|
}
|
|
40
44
|
|
|
@@ -47,6 +51,7 @@ export function planImageRequest(options) {
|
|
|
47
51
|
},
|
|
48
52
|
body: {
|
|
49
53
|
contents: [{ parts: [{ text: options.prompt }] }],
|
|
54
|
+
temp_url: true,
|
|
50
55
|
},
|
|
51
56
|
});
|
|
52
57
|
}
|
|
@@ -88,6 +93,7 @@ export function planVideoRequest(options) {
|
|
|
88
93
|
resolution,
|
|
89
94
|
quality: resolution,
|
|
90
95
|
fps: parseOptionalInteger(options.fps),
|
|
96
|
+
temp_url: true,
|
|
91
97
|
images: !isSeedanceModel(model) && imageUrls.length > 0 ? imageUrls : undefined,
|
|
92
98
|
});
|
|
93
99
|
const seedanceContent = buildSeedanceContent(options);
|
|
@@ -232,7 +238,7 @@ function planRequest(options, path, init = {}) {
|
|
|
232
238
|
|
|
233
239
|
async function requestJsonFromPlan(options, plan) {
|
|
234
240
|
const fetchImpl = options.fetch ?? fetch;
|
|
235
|
-
const
|
|
241
|
+
const request = {
|
|
236
242
|
method: plan.method,
|
|
237
243
|
headers: plan.headers,
|
|
238
244
|
body: plan.body === undefined
|
|
@@ -240,8 +246,12 @@ async function requestJsonFromPlan(options, plan) {
|
|
|
240
246
|
: isFormData(plan.body)
|
|
241
247
|
? plan.body
|
|
242
248
|
: JSON.stringify(plan.body),
|
|
243
|
-
}
|
|
249
|
+
};
|
|
250
|
+
logRequest(options, plan.url, request);
|
|
251
|
+
const response = await fetchImpl(plan.url, request);
|
|
252
|
+
logResponse(options, response);
|
|
244
253
|
const body = await readJson(response);
|
|
254
|
+
logResponseBody(options, body);
|
|
245
255
|
if (!response.ok || body?.success === false) {
|
|
246
256
|
throw new FlatkeyError(extractErrorMessage(body, response.status), {
|
|
247
257
|
status: response.status,
|
|
@@ -256,13 +266,17 @@ function isFormData(value) {
|
|
|
256
266
|
|
|
257
267
|
async function requestBinaryArtifactFromPlan(options, plan) {
|
|
258
268
|
const fetchImpl = options.fetch ?? fetch;
|
|
259
|
-
const
|
|
269
|
+
const request = {
|
|
260
270
|
method: plan.method,
|
|
261
271
|
headers: plan.headers,
|
|
262
272
|
body: plan.body === undefined ? undefined : JSON.stringify(plan.body),
|
|
263
|
-
}
|
|
273
|
+
};
|
|
274
|
+
logRequest(options, plan.url, request);
|
|
275
|
+
const response = await fetchImpl(plan.url, request);
|
|
276
|
+
logResponse(options, response);
|
|
264
277
|
if (!response.ok) {
|
|
265
278
|
const body = await readJson(response);
|
|
279
|
+
logResponseBody(options, body);
|
|
266
280
|
throw new FlatkeyError(extractErrorMessage(body, response.status), {
|
|
267
281
|
status: response.status,
|
|
268
282
|
});
|
|
@@ -272,6 +286,43 @@ async function requestBinaryArtifactFromPlan(options, plan) {
|
|
|
272
286
|
};
|
|
273
287
|
}
|
|
274
288
|
|
|
289
|
+
function logRequest(options, url, request) {
|
|
290
|
+
if (!options?.verbose) return;
|
|
291
|
+
const safeHeaders = redactHeaders(request.headers);
|
|
292
|
+
const safeBody = request.body === undefined
|
|
293
|
+
? undefined
|
|
294
|
+
: isFormData(request.body)
|
|
295
|
+
? "<form-data>"
|
|
296
|
+
: request.body;
|
|
297
|
+
options.verboseLog?.(`-> ${request.method} ${url}`);
|
|
298
|
+
options.verboseLog?.(`headers: ${JSON.stringify(safeHeaders)}`);
|
|
299
|
+
if (safeBody !== undefined) {
|
|
300
|
+
options.verboseLog?.(`body: ${safeBody}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function logResponse(options, response) {
|
|
305
|
+
if (!options?.verbose) return;
|
|
306
|
+
options.verboseLog?.(`<- ${response.status} ${response.statusText || ""}`.trim());
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function logResponseBody(options, body) {
|
|
310
|
+
if (!options?.verbose) return;
|
|
311
|
+
options.verboseLog?.(`response: ${truncateLogValue(JSON.stringify(body))}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function redactHeaders(headers) {
|
|
315
|
+
return Object.fromEntries(Object.entries(headers ?? {}).map(([key, value]) => [
|
|
316
|
+
key,
|
|
317
|
+
key.toLowerCase() === "authorization" ? "Bearer <redacted>" : `${value}`,
|
|
318
|
+
]));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function truncateLogValue(value) {
|
|
322
|
+
if (value === undefined) return "";
|
|
323
|
+
return value.length > 1200 ? `${value.slice(0, 1200)}…` : value;
|
|
324
|
+
}
|
|
325
|
+
|
|
275
326
|
async function requestJson(options, path, init = {}) {
|
|
276
327
|
return requestJsonFromPlan(options, planRequest(options, path, {
|
|
277
328
|
method: init.method ?? "GET",
|
package/src/artifacts.js
CHANGED
|
@@ -47,7 +47,7 @@ function expandHomePath(path) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function persistItem({ kind, item, outDir, output, index, fetchImpl }) {
|
|
50
|
-
const dataUrl = getString(item, ["url", "video_url", "data_url", "dataUrl"]);
|
|
50
|
+
const dataUrl = getString(item, ["url", "video_url", "data_url", "dataUrl", "temp_url"]);
|
|
51
51
|
if (dataUrl?.startsWith("data:")) {
|
|
52
52
|
const parsed = parseDataUrl(dataUrl);
|
|
53
53
|
const path = artifactPath({ kind, outDir, output, index, extension: parsed.extension });
|
|
@@ -94,6 +94,8 @@ function extractItems(response) {
|
|
|
94
94
|
if (Array.isArray(response?.candidates)) {
|
|
95
95
|
return response.candidates.flatMap((candidate) => candidate.content?.parts ?? []);
|
|
96
96
|
}
|
|
97
|
+
if (typeof response?.temp_url === "string") return [{ url: response.temp_url }];
|
|
98
|
+
if (typeof response?.metadata?.temp_url === "string") return [{ url: response.metadata.temp_url }];
|
|
97
99
|
if (typeof response?.metadata?.url === "string") return [{ url: response.metadata.url }];
|
|
98
100
|
if (typeof response?.url === "string") return [response];
|
|
99
101
|
if (typeof response?.video_url?.url === "string") return [{ url: response.video_url.url }];
|
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", "console_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", "verbose"]);
|
|
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"]),
|
|
@@ -187,7 +187,7 @@ export async function runCommand(command, deps = {}) {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
if (command.group === "models") {
|
|
190
|
-
return handleModels(command, deps);
|
|
190
|
+
return handleModels(command, { ...deps, stdout, stderr });
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
if (command.group === "login") {
|
|
@@ -206,12 +206,12 @@ export async function runCommand(command, deps = {}) {
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
if (command.group === "credits" || command.group === "status") {
|
|
209
|
-
return handleUtility(command, deps);
|
|
209
|
+
return handleUtility(command, { ...deps, stdout, stderr });
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
if (["image", "video", "audio", "text"].includes(command.group)) {
|
|
213
213
|
if (command.group === "audio" && command.action === "voices") {
|
|
214
|
-
return handleVoices(command, deps);
|
|
214
|
+
return handleVoices(command, { ...deps, stdout, stderr });
|
|
215
215
|
}
|
|
216
216
|
const validAction = command.group === "audio"
|
|
217
217
|
? ["generate", "sfx", "music"].includes(command.action)
|
|
@@ -222,7 +222,7 @@ export async function runCommand(command, deps = {}) {
|
|
|
222
222
|
throw new Error(`Unknown action for ${command.group}: ${command.action}`);
|
|
223
223
|
}
|
|
224
224
|
if (command.group === "image" && command.action === "upload") {
|
|
225
|
-
return handleImageUpload(command, deps);
|
|
225
|
+
return handleImageUpload(command, { ...deps, stdout, stderr });
|
|
226
226
|
}
|
|
227
227
|
return handleGenerate(command, { ...deps, stdout, stderr });
|
|
228
228
|
}
|
|
@@ -236,12 +236,17 @@ async function handleLogin(command, deps) {
|
|
|
236
236
|
const deviceId = await ensureDeviceId({ configDir: deps.configDir });
|
|
237
237
|
const version = await readPackageVersion();
|
|
238
238
|
const consoleOrigin = firstNonEmpty(command.options.console_url, DEFAULT_CONSOLE_URL);
|
|
239
|
-
const
|
|
239
|
+
const requestOptions = {
|
|
240
240
|
consoleUrl: consoleOrigin,
|
|
241
|
+
fetch: deps.fetch,
|
|
242
|
+
verbose: command.options.verbose,
|
|
243
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
244
|
+
};
|
|
245
|
+
const authorization = await createDeviceAuthorization({
|
|
241
246
|
deviceId,
|
|
242
247
|
clientName: "flatkey-cli",
|
|
243
248
|
clientVersion: version,
|
|
244
|
-
|
|
249
|
+
...requestOptions,
|
|
245
250
|
});
|
|
246
251
|
const data = authorization?.data ?? authorization;
|
|
247
252
|
if (!data?.device_code || !data?.verification_uri_complete) {
|
|
@@ -261,9 +266,8 @@ async function handleLogin(command, deps) {
|
|
|
261
266
|
while (Date.now() < deadline) {
|
|
262
267
|
await delay(nextLoginPollDelay(startedAt, initialIntervalMs), deps);
|
|
263
268
|
const poll = await pollDeviceAuthorization({
|
|
264
|
-
consoleUrl: consoleOrigin,
|
|
265
269
|
deviceCode: data.device_code,
|
|
266
|
-
|
|
270
|
+
...requestOptions,
|
|
267
271
|
});
|
|
268
272
|
const pollData = poll?.data ?? poll;
|
|
269
273
|
if (pollData?.status === "approved") {
|
|
@@ -432,6 +436,8 @@ async function handleImageUpload(command, deps) {
|
|
|
432
436
|
filename: basename(file),
|
|
433
437
|
contentType: imageContentTypeFromPath(file),
|
|
434
438
|
fetch: deps.fetch,
|
|
439
|
+
verbose: command.options.verbose,
|
|
440
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
435
441
|
});
|
|
436
442
|
const data = response?.data ?? response;
|
|
437
443
|
const signedUrl = extractUploadedImageUrl(response);
|
|
@@ -494,6 +500,7 @@ async function handleGenerate(command, deps) {
|
|
|
494
500
|
tempMediaBaseUrl: tempMediaBaseUrl ?? DEFAULT_CONSOLE_URL,
|
|
495
501
|
env: deps.env ?? process.env,
|
|
496
502
|
fetch: deps.fetch,
|
|
503
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
497
504
|
};
|
|
498
505
|
if (command.group === "video" && !command.options.dry_run) {
|
|
499
506
|
await uploadVideoLocalImages(options, { uploadTempMediaImage });
|
|
@@ -581,6 +588,8 @@ async function uploadLocalImage(file, options, deps) {
|
|
|
581
588
|
filename: basename(file),
|
|
582
589
|
contentType: imageContentTypeFromPath(file),
|
|
583
590
|
fetch: options.fetch,
|
|
591
|
+
verbose: options.verbose,
|
|
592
|
+
verboseLog: options.verboseLog,
|
|
584
593
|
});
|
|
585
594
|
const url = extractUploadedImageUrl(response);
|
|
586
595
|
if (!url) throw new Error(`Image upload failed: missing signed_url for ${file}`);
|
|
@@ -632,10 +641,12 @@ async function waitForVideoResult(response, options) {
|
|
|
632
641
|
function hasArtifact(response) {
|
|
633
642
|
return Boolean(
|
|
634
643
|
response?.metadata?.url
|
|
644
|
+
|| response?.metadata?.temp_url
|
|
635
645
|
|| response?.url
|
|
646
|
+
|| response?.temp_url
|
|
636
647
|
|| response?.video_url?.url
|
|
637
|
-
|| response?.data?.some?.((item) => item?.url || item?.b64_json || item?.base64 || item?.data)
|
|
638
|
-
|| response?.artifacts?.some?.((item) => item?.url || item?.path)
|
|
648
|
+
|| response?.data?.some?.((item) => item?.url || item?.temp_url || item?.b64_json || item?.base64 || item?.data)
|
|
649
|
+
|| response?.artifacts?.some?.((item) => item?.url || item?.temp_url || item?.path)
|
|
639
650
|
|| response?.content?.some?.((item) => item?.url || item?.video_url?.url || item?.data_url || item?.b64_json || item?.base64 || item?.data),
|
|
640
651
|
);
|
|
641
652
|
}
|
|
@@ -696,6 +707,8 @@ async function handleVoices(command, deps) {
|
|
|
696
707
|
apiKey,
|
|
697
708
|
baseUrl: routerOrigin,
|
|
698
709
|
fetch: deps.fetch,
|
|
710
|
+
verbose: command.options.verbose,
|
|
711
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
699
712
|
});
|
|
700
713
|
}
|
|
701
714
|
|
|
@@ -755,6 +768,8 @@ async function handleUtility(command, deps) {
|
|
|
755
768
|
apiKey,
|
|
756
769
|
baseUrl: consoleOrigin,
|
|
757
770
|
fetch: deps.fetch,
|
|
771
|
+
verbose: command.options.verbose,
|
|
772
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
758
773
|
};
|
|
759
774
|
if (command.group === "credits") return getCredits(options);
|
|
760
775
|
|
|
@@ -841,10 +856,21 @@ async function handleModels(command, deps) {
|
|
|
841
856
|
apiKey,
|
|
842
857
|
baseUrl: consoleOrigin,
|
|
843
858
|
fetch: deps.fetch,
|
|
859
|
+
verbose: command.options.verbose,
|
|
860
|
+
verboseLog: verboseLogger(deps.stderr, command.options.verbose),
|
|
844
861
|
});
|
|
845
862
|
return { models: normalizeModels(response, command.options.type) };
|
|
846
863
|
}
|
|
847
864
|
|
|
865
|
+
function verboseLogger(stream, enabled) {
|
|
866
|
+
if (!enabled) return () => {};
|
|
867
|
+
return (line) => {
|
|
868
|
+
if (typeof line === "string") {
|
|
869
|
+
stream?.write?.(`${line}\n`);
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
|
|
848
874
|
function formatHuman(result) {
|
|
849
875
|
if (typeof result === "string") return result;
|
|
850
876
|
if (!result || typeof result !== "object") return String(result);
|
package/src/help.js
CHANGED