@mrgoonie/multix 0.9.0 → 0.10.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 -0
- package/README.md +3 -0
- package/dist/cli.js +461 -250
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
package/dist/cli.js
CHANGED
|
@@ -531,9 +531,9 @@ var BytePlusClient = class {
|
|
|
531
531
|
}
|
|
532
532
|
apiKey;
|
|
533
533
|
baseUrl;
|
|
534
|
-
async request(
|
|
534
|
+
async request(path50, opts = {}) {
|
|
535
535
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
536
|
-
const url = new URL(base +
|
|
536
|
+
const url = new URL(base + path50);
|
|
537
537
|
if (opts.query) {
|
|
538
538
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
539
539
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -551,14 +551,14 @@ var BytePlusClient = class {
|
|
|
551
551
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
552
552
|
});
|
|
553
553
|
}
|
|
554
|
-
get(
|
|
555
|
-
return this.request(
|
|
554
|
+
get(path50, query, logger) {
|
|
555
|
+
return this.request(path50, { method: "GET", query, logger });
|
|
556
556
|
}
|
|
557
|
-
post(
|
|
558
|
-
return this.request(
|
|
557
|
+
post(path50, body, logger) {
|
|
558
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
559
559
|
}
|
|
560
|
-
delete(
|
|
561
|
-
return this.request(
|
|
560
|
+
delete(path50, logger) {
|
|
561
|
+
return this.request(path50, { method: "DELETE", logger });
|
|
562
562
|
}
|
|
563
563
|
};
|
|
564
564
|
function createBytePlusClient() {
|
|
@@ -1298,10 +1298,10 @@ function toRef(kind, resolved, role) {
|
|
|
1298
1298
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
1299
1299
|
}
|
|
1300
1300
|
async function resolveRefSpec(kind, spec, logger) {
|
|
1301
|
-
const { path:
|
|
1302
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
1303
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
1304
|
-
return toRef(kind, await resolveAudioInput(
|
|
1301
|
+
const { path: path50, role } = parseRefSpec(spec);
|
|
1302
|
+
if (kind === "image") return toRef(kind, await resolveImageInput(path50, logger), role);
|
|
1303
|
+
if (kind === "video") return toRef(kind, await resolveVideoInput(path50, logger), role);
|
|
1304
|
+
return toRef(kind, await resolveAudioInput(path50, logger), role);
|
|
1305
1305
|
}
|
|
1306
1306
|
async function resolveRefSpecs(specs, logger) {
|
|
1307
1307
|
const out = [];
|
|
@@ -2757,9 +2757,9 @@ var FalClient = class {
|
|
|
2757
2757
|
apiKey;
|
|
2758
2758
|
baseUrl;
|
|
2759
2759
|
/** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
|
|
2760
|
-
async request(
|
|
2760
|
+
async request(path50, opts = {}) {
|
|
2761
2761
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
2762
|
-
const suffix =
|
|
2762
|
+
const suffix = path50.startsWith("/") ? path50 : `/${path50}`;
|
|
2763
2763
|
return this.requestAbsolute(base + suffix, opts);
|
|
2764
2764
|
}
|
|
2765
2765
|
/** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
|
|
@@ -2776,11 +2776,11 @@ var FalClient = class {
|
|
|
2776
2776
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
2777
2777
|
});
|
|
2778
2778
|
}
|
|
2779
|
-
get(
|
|
2780
|
-
return this.request(
|
|
2779
|
+
get(path50, logger) {
|
|
2780
|
+
return this.request(path50, { method: "GET", logger });
|
|
2781
2781
|
}
|
|
2782
|
-
post(
|
|
2783
|
-
return this.request(
|
|
2782
|
+
post(path50, body, logger) {
|
|
2783
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
2784
2784
|
}
|
|
2785
2785
|
getAbsolute(url, logger) {
|
|
2786
2786
|
return this.requestAbsolute(url, { method: "GET", logger });
|
|
@@ -3141,14 +3141,214 @@ function registerFalCommands(program2) {
|
|
|
3141
3141
|
registerFalResultCommand(fal);
|
|
3142
3142
|
}
|
|
3143
3143
|
|
|
3144
|
-
// src/
|
|
3144
|
+
// src/core/stdin-file.ts
|
|
3145
3145
|
init_errors();
|
|
3146
|
-
import
|
|
3146
|
+
import fs18 from "fs";
|
|
3147
|
+
import os2 from "os";
|
|
3148
|
+
import path21 from "path";
|
|
3149
|
+
var STDIN_ARG = "-";
|
|
3150
|
+
var STDIN_LABEL = "<stdin>";
|
|
3151
|
+
var HEAD_BYTES = 4096;
|
|
3152
|
+
var MAX_STDIN_BYTES = 2 * 1024 ** 3;
|
|
3153
|
+
var WAIT_HINT_MS = 2e3;
|
|
3154
|
+
var ascii = (buf, start, end) => buf.toString("latin1", start, end);
|
|
3155
|
+
var HEIF_BRANDS = /* @__PURE__ */ new Set([
|
|
3156
|
+
"heic",
|
|
3157
|
+
"heix",
|
|
3158
|
+
"hevc",
|
|
3159
|
+
"hevx",
|
|
3160
|
+
"heim",
|
|
3161
|
+
"heis",
|
|
3162
|
+
"hevm",
|
|
3163
|
+
"hevs",
|
|
3164
|
+
"mif1",
|
|
3165
|
+
"msf1"
|
|
3166
|
+
]);
|
|
3167
|
+
var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
|
|
3168
|
+
function isMpegAudioFrame(head) {
|
|
3169
|
+
if (head.length < 4 || head[0] !== 255) return false;
|
|
3170
|
+
const b1 = head[1] ?? 0;
|
|
3171
|
+
const b2 = head[2] ?? 0;
|
|
3172
|
+
if ((b1 & 224) !== 224) return false;
|
|
3173
|
+
const version = b1 >> 3 & 3;
|
|
3174
|
+
const layer = b1 >> 1 & 3;
|
|
3175
|
+
const bitrate = b2 >> 4;
|
|
3176
|
+
const sampleRate = b2 >> 2 & 3;
|
|
3177
|
+
return version !== 1 && layer !== 0 && bitrate !== 15 && bitrate !== 0 && sampleRate !== 3;
|
|
3178
|
+
}
|
|
3179
|
+
function detectExtension(head) {
|
|
3180
|
+
if (head.length >= 3 && head[0] === 239 && head[1] === 187 && head[2] === 191) return ".txt";
|
|
3181
|
+
if (head.length >= 2 && (head[0] === 255 && head[1] === 254 || head[0] === 254 && head[1] === 255)) {
|
|
3182
|
+
return void 0;
|
|
3183
|
+
}
|
|
3184
|
+
if (head.length >= 8 && head.readUInt32BE(0) === 2303741511) return ".png";
|
|
3185
|
+
if (head.length >= 3 && head[0] === 255 && head[1] === 216 && head[2] === 255) return ".jpg";
|
|
3186
|
+
if (ascii(head, 0, 4) === "GIF8") return ".gif";
|
|
3187
|
+
if (ascii(head, 0, 5) === "%PDF-") return ".pdf";
|
|
3188
|
+
if (ascii(head, 0, 2) === "BM" && head.length >= 26 && head.readUInt32LE(14) >= 12) return ".bmp";
|
|
3189
|
+
if (ascii(head, 0, 4) === "RIFF") {
|
|
3190
|
+
const kind = ascii(head, 8, 12);
|
|
3191
|
+
if (kind === "WEBP") return ".webp";
|
|
3192
|
+
if (kind === "WAVE") return ".wav";
|
|
3193
|
+
if (kind === "AVI ") return ".avi";
|
|
3194
|
+
}
|
|
3195
|
+
if (ascii(head, 0, 4) === "OggS") return ".ogg";
|
|
3196
|
+
if (ascii(head, 0, 4) === "fLaC") return ".flac";
|
|
3197
|
+
if (ascii(head, 0, 4) === "FORM" && ascii(head, 8, 12) === "AIFF") return ".aiff";
|
|
3198
|
+
if (ascii(head, 0, 3) === "ID3" && (head[3] ?? 0) >= 2 && (head[3] ?? 0) <= 4 && head[4] !== 255) {
|
|
3199
|
+
return ".mp3";
|
|
3200
|
+
}
|
|
3201
|
+
if (ascii(head, 4, 8) === "ftyp") {
|
|
3202
|
+
const brand = ascii(head, 8, 12);
|
|
3203
|
+
if (HEIF_BRANDS.has(brand)) return ".heic";
|
|
3204
|
+
if (AVIF_BRANDS.has(brand)) return void 0;
|
|
3205
|
+
if (brand.startsWith("M4A") || brand.startsWith("M4B")) return ".m4a";
|
|
3206
|
+
if (brand === "qt ") return ".mov";
|
|
3207
|
+
if (brand.startsWith("3g")) return ".3gpp";
|
|
3208
|
+
return ".mp4";
|
|
3209
|
+
}
|
|
3210
|
+
if (head.length >= 4 && head.readUInt32BE(0) === 440786851) {
|
|
3211
|
+
return head.includes("webm", 0, "latin1") ? ".webm" : void 0;
|
|
3212
|
+
}
|
|
3213
|
+
if (head.length >= 2 && head[0] === 255 && ((head[1] ?? 0) & 246) === 240) return ".aac";
|
|
3214
|
+
if (isMpegAudioFrame(head)) return ".mp3";
|
|
3215
|
+
if (looksLikeText(head)) return ".txt";
|
|
3216
|
+
return void 0;
|
|
3217
|
+
}
|
|
3218
|
+
function looksLikeText(head) {
|
|
3219
|
+
if (head.length === 0) return false;
|
|
3220
|
+
for (const byte of head) {
|
|
3221
|
+
if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 12 && byte !== 13 && byte !== 27) {
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
try {
|
|
3226
|
+
new TextDecoder("utf-8", { fatal: true }).decode(head, { stream: true });
|
|
3227
|
+
return true;
|
|
3228
|
+
} catch {
|
|
3229
|
+
return false;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
function normalizeExt(ext) {
|
|
3233
|
+
const clean = ext.trim().toLowerCase();
|
|
3234
|
+
if (!/^\.?[a-z0-9]{1,10}$/.test(clean)) {
|
|
3235
|
+
throw new ValidationError(`Invalid --stdin-ext: ${ext} (expected e.g. .mp3 or txt)`);
|
|
3236
|
+
}
|
|
3237
|
+
return clean.startsWith(".") ? clean : `.${clean}`;
|
|
3238
|
+
}
|
|
3239
|
+
var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
3240
|
+
function trackTempDir(dir) {
|
|
3241
|
+
let done = false;
|
|
3242
|
+
const remove = () => {
|
|
3243
|
+
try {
|
|
3244
|
+
fs18.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
3245
|
+
} catch {
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
const onSignal = (signal) => {
|
|
3249
|
+
dispose();
|
|
3250
|
+
process.kill(process.pid, signal);
|
|
3251
|
+
};
|
|
3252
|
+
const dispose = () => {
|
|
3253
|
+
if (done) return;
|
|
3254
|
+
done = true;
|
|
3255
|
+
process.removeListener("exit", dispose);
|
|
3256
|
+
for (const s of SIGNALS) process.removeListener(s, onSignal);
|
|
3257
|
+
remove();
|
|
3258
|
+
};
|
|
3259
|
+
process.once("exit", dispose);
|
|
3260
|
+
for (const s of SIGNALS) process.once(s, onSignal);
|
|
3261
|
+
return dispose;
|
|
3262
|
+
}
|
|
3263
|
+
async function stdinToTempFile(opts = {}) {
|
|
3264
|
+
const input = opts.input ?? process.stdin;
|
|
3265
|
+
if (input.isTTY) {
|
|
3266
|
+
throw new ValidationError(
|
|
3267
|
+
"`--files -` reads piped input, but stdin is a terminal. Example: cat clip.mp3 | multix gemini transcribe --files -"
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
const forcedExt = opts.ext ? normalizeExt(opts.ext) : void 0;
|
|
3271
|
+
const maxBytes = opts.maxBytes ?? MAX_STDIN_BYTES;
|
|
3272
|
+
const dir = fs18.mkdtempSync(path21.join(os2.tmpdir(), "multix-stdin-"));
|
|
3273
|
+
const cleanup = trackTempDir(dir);
|
|
3274
|
+
const rawPath = path21.join(dir, "input");
|
|
3275
|
+
const hint = setTimeout(() => {
|
|
3276
|
+
process.stderr.write("Waiting for data on stdin (`--files -`)... press Ctrl+C to cancel\n");
|
|
3277
|
+
}, WAIT_HINT_MS);
|
|
3278
|
+
hint.unref?.();
|
|
3279
|
+
let head = Buffer.alloc(0);
|
|
3280
|
+
let size = 0;
|
|
3281
|
+
try {
|
|
3282
|
+
const fd = fs18.openSync(rawPath, "w", 384);
|
|
3283
|
+
try {
|
|
3284
|
+
for await (const chunk of input) {
|
|
3285
|
+
clearTimeout(hint);
|
|
3286
|
+
const buf = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
3287
|
+
if (size + buf.length > maxBytes) {
|
|
3288
|
+
throw new ValidationError(
|
|
3289
|
+
`Piped stdin exceeds ${Math.floor(maxBytes / 1024 ** 2)} MB; save it to a file instead`
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
if (head.length < HEAD_BYTES) {
|
|
3293
|
+
head = Buffer.concat([head, buf.subarray(0, HEAD_BYTES - head.length)]);
|
|
3294
|
+
}
|
|
3295
|
+
let offset = 0;
|
|
3296
|
+
while (offset < buf.length) offset += fs18.writeSync(fd, buf, offset);
|
|
3297
|
+
size += buf.length;
|
|
3298
|
+
}
|
|
3299
|
+
} finally {
|
|
3300
|
+
clearTimeout(hint);
|
|
3301
|
+
fs18.closeSync(fd);
|
|
3302
|
+
}
|
|
3303
|
+
if (size === 0) throw new ValidationError("`--files -` was given but stdin was empty");
|
|
3304
|
+
const detectedExt = forcedExt ?? detectExtension(head);
|
|
3305
|
+
const finalPath = path21.join(dir, `stdin${detectedExt ?? ".bin"}`);
|
|
3306
|
+
fs18.renameSync(rawPath, finalPath);
|
|
3307
|
+
return { path: finalPath, size, detected: detectedExt !== void 0, cleanup };
|
|
3308
|
+
} catch (error) {
|
|
3309
|
+
cleanup();
|
|
3310
|
+
throw error;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
function labelError(message, input) {
|
|
3314
|
+
return input.path === input.label ? message : message.split(input.path).join(input.label);
|
|
3315
|
+
}
|
|
3316
|
+
async function resolveFileInputs(files, opts = {}) {
|
|
3317
|
+
const stdinCount = files.filter((f) => f === STDIN_ARG).length;
|
|
3318
|
+
if (stdinCount > 1) throw new ValidationError("`-` (stdin) can only be passed once to --files");
|
|
3319
|
+
if (opts.stdinExt && stdinCount === 0) {
|
|
3320
|
+
throw new ValidationError("--stdin-ext only applies when --files includes `-`");
|
|
3321
|
+
}
|
|
3322
|
+
for (const f of files) {
|
|
3323
|
+
if (f !== STDIN_ARG && !fs18.existsSync(f)) throw new ValidationError(`File not found: ${f}`);
|
|
3324
|
+
}
|
|
3325
|
+
if (stdinCount === 0) {
|
|
3326
|
+
return { inputs: files.map((f) => ({ path: f, label: f })), cleanup: () => {
|
|
3327
|
+
} };
|
|
3328
|
+
}
|
|
3329
|
+
const stdin = await stdinToTempFile({
|
|
3330
|
+
input: opts.input,
|
|
3331
|
+
ext: opts.stdinExt,
|
|
3332
|
+
maxBytes: opts.maxBytes
|
|
3333
|
+
});
|
|
3334
|
+
if (!stdin.detected) {
|
|
3335
|
+
stdin.cleanup();
|
|
3336
|
+
throw new ValidationError(
|
|
3337
|
+
"Could not detect the type of piped stdin; pass --stdin-ext (e.g. --stdin-ext mp3)"
|
|
3338
|
+
);
|
|
3339
|
+
}
|
|
3340
|
+
return {
|
|
3341
|
+
inputs: files.map(
|
|
3342
|
+
(f) => f === STDIN_ARG ? { path: stdin.path, label: STDIN_LABEL } : { path: f, label: f }
|
|
3343
|
+
),
|
|
3344
|
+
cleanup: stdin.cleanup
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3147
3347
|
|
|
3148
3348
|
// src/providers/gemini/client.ts
|
|
3149
3349
|
init_errors();
|
|
3150
|
-
import
|
|
3151
|
-
import
|
|
3350
|
+
import fs19 from "fs";
|
|
3351
|
+
import path23 from "path";
|
|
3152
3352
|
|
|
3153
3353
|
// src/providers/gemini/key-pool.ts
|
|
3154
3354
|
init_errors();
|
|
@@ -3214,7 +3414,7 @@ async function withGeminiKey(fn, opts = {}) {
|
|
|
3214
3414
|
}
|
|
3215
3415
|
|
|
3216
3416
|
// src/providers/gemini/task-resolver.ts
|
|
3217
|
-
import
|
|
3417
|
+
import path22 from "path";
|
|
3218
3418
|
var MIME_MAP = {
|
|
3219
3419
|
// Audio
|
|
3220
3420
|
".mp3": "audio/mp3",
|
|
@@ -3255,7 +3455,7 @@ var MIME_MAP = {
|
|
|
3255
3455
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
3256
3456
|
};
|
|
3257
3457
|
function getMimeType(filePath) {
|
|
3258
|
-
const ext =
|
|
3458
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
3259
3459
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
3260
3460
|
}
|
|
3261
3461
|
function requiresProcessingWait(mimeType) {
|
|
@@ -3278,8 +3478,8 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
3278
3478
|
async function uploadFileWithKey(filePath, apiKey, opts) {
|
|
3279
3479
|
const { timeoutMs = 3e5, logger } = opts;
|
|
3280
3480
|
const mimeType = getMimeType(filePath);
|
|
3281
|
-
const fileBytes =
|
|
3282
|
-
const displayName =
|
|
3481
|
+
const fileBytes = fs19.readFileSync(filePath);
|
|
3482
|
+
const displayName = path23.basename(filePath);
|
|
3283
3483
|
const boundary = `multix_${Date.now()}`;
|
|
3284
3484
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
3285
3485
|
const body = Buffer.concat([
|
|
@@ -3396,12 +3596,12 @@ function extractImages(resp) {
|
|
|
3396
3596
|
).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
|
|
3397
3597
|
}
|
|
3398
3598
|
function inlinePart(filePath) {
|
|
3399
|
-
const data =
|
|
3599
|
+
const data = fs19.readFileSync(filePath).toString("base64");
|
|
3400
3600
|
const mimeType = getMimeType(filePath);
|
|
3401
3601
|
return { inlineData: { mimeType, data } };
|
|
3402
3602
|
}
|
|
3403
3603
|
function shouldUseFileApi(filePath) {
|
|
3404
|
-
const stat =
|
|
3604
|
+
const stat = fs19.statSync(filePath);
|
|
3405
3605
|
return stat.size > 20 * 1024 * 1024;
|
|
3406
3606
|
}
|
|
3407
3607
|
function sleep2(ms) {
|
|
@@ -3455,13 +3655,13 @@ function getDefaultModel(task) {
|
|
|
3455
3655
|
}
|
|
3456
3656
|
|
|
3457
3657
|
// src/providers/gemini/result-formatter.ts
|
|
3458
|
-
import
|
|
3459
|
-
import
|
|
3658
|
+
import fs20 from "fs";
|
|
3659
|
+
import path24 from "path";
|
|
3460
3660
|
function formatResults(results, format, outputPath) {
|
|
3461
3661
|
const content = renderFormat(results, format);
|
|
3462
3662
|
if (outputPath) {
|
|
3463
|
-
|
|
3464
|
-
|
|
3663
|
+
fs20.mkdirSync(path24.dirname(path24.resolve(outputPath)), { recursive: true });
|
|
3664
|
+
fs20.writeFileSync(outputPath, content, "utf8");
|
|
3465
3665
|
} else {
|
|
3466
3666
|
process.stdout.write(content);
|
|
3467
3667
|
}
|
|
@@ -3523,17 +3723,17 @@ ${r.response}
|
|
|
3523
3723
|
|
|
3524
3724
|
// src/providers/gemini/commands/analyze.ts
|
|
3525
3725
|
function registerAnalyzeCommand(parent) {
|
|
3526
|
-
parent.command("analyze").description("Analyze files with Gemini (images, video, audio, documents)").requiredOption("--files <paths...>", "Input file paths to analyze").option("--prompt <text>", "Analysis prompt", "Analyze this content").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file instead of stdout").option("-v, --verbose", "Verbose logging").action(
|
|
3726
|
+
parent.command("analyze").description("Analyze files with Gemini (images, video, audio, documents)").requiredOption("--files <paths...>", "Input file paths to analyze (use - to read piped stdin)").option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").option("--prompt <text>", "Analysis prompt", "Analyze this content").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file instead of stdout").option("-v, --verbose", "Verbose logging").action(
|
|
3527
3727
|
async (opts) => {
|
|
3528
3728
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3529
3729
|
const model = opts.model ?? getDefaultModel("analyze");
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
}
|
|
3730
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
3731
|
+
stdinExt: opts.stdinExt
|
|
3732
|
+
});
|
|
3533
3733
|
const results = [];
|
|
3534
|
-
for (let i = 0; i <
|
|
3535
|
-
const filePath =
|
|
3536
|
-
logger.debug(`[${i + 1}/${
|
|
3734
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
3735
|
+
const { path: filePath, label } = inputs[i];
|
|
3736
|
+
logger.debug(`[${i + 1}/${inputs.length}] Processing: ${label}`);
|
|
3537
3737
|
try {
|
|
3538
3738
|
const resp = await withGeminiKey(
|
|
3539
3739
|
async (apiKey) => {
|
|
@@ -3560,17 +3760,21 @@ function registerAnalyzeCommand(parent) {
|
|
|
3560
3760
|
{ logger }
|
|
3561
3761
|
);
|
|
3562
3762
|
results.push({
|
|
3563
|
-
file:
|
|
3763
|
+
file: label,
|
|
3564
3764
|
status: "success",
|
|
3565
3765
|
response: extractText(resp)
|
|
3566
3766
|
});
|
|
3567
3767
|
} catch (e) {
|
|
3568
|
-
const msg =
|
|
3569
|
-
|
|
3570
|
-
|
|
3768
|
+
const msg = labelError(
|
|
3769
|
+
e instanceof Error ? e.message : String(e),
|
|
3770
|
+
inputs[i]
|
|
3771
|
+
);
|
|
3772
|
+
logger.error(`Failed: ${label} \u2014 ${msg}`);
|
|
3773
|
+
results.push({ file: label, status: "error", error: msg });
|
|
3571
3774
|
}
|
|
3572
3775
|
}
|
|
3573
3776
|
formatResults(results, opts.format, opts.output);
|
|
3777
|
+
cleanup();
|
|
3574
3778
|
logger.debug(
|
|
3575
3779
|
`Done: ${results.filter((r) => r.status === "success").length}/${results.length} succeeded`
|
|
3576
3780
|
);
|
|
@@ -3581,21 +3785,19 @@ function registerAnalyzeCommand(parent) {
|
|
|
3581
3785
|
}
|
|
3582
3786
|
|
|
3583
3787
|
// src/providers/gemini/commands/extract.ts
|
|
3584
|
-
init_errors();
|
|
3585
|
-
import fs21 from "fs";
|
|
3586
3788
|
function registerExtractCommand(parent) {
|
|
3587
|
-
parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths").requiredOption("--prompt <text>", "Extraction prompt (required)").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
3789
|
+
parent.command("extract").description("Extract structured information from files with Gemini").requiredOption("--files <paths...>", "Input file paths (use - to read piped stdin)").option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").requiredOption("--prompt <text>", "Extraction prompt (required)").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
3588
3790
|
async (opts) => {
|
|
3589
3791
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3590
3792
|
const model = opts.model ?? getDefaultModel("extract");
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
}
|
|
3793
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
3794
|
+
stdinExt: opts.stdinExt
|
|
3795
|
+
});
|
|
3594
3796
|
const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
|
|
3595
3797
|
const results = [];
|
|
3596
|
-
for (let i = 0; i <
|
|
3597
|
-
const filePath =
|
|
3598
|
-
logger.debug(`[${i + 1}/${
|
|
3798
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
3799
|
+
const { path: filePath, label } = inputs[i];
|
|
3800
|
+
logger.debug(`[${i + 1}/${inputs.length}] Extracting: ${label}`);
|
|
3599
3801
|
try {
|
|
3600
3802
|
const resp = await withGeminiKey(
|
|
3601
3803
|
async (apiKey) => {
|
|
@@ -3621,14 +3823,18 @@ function registerExtractCommand(parent) {
|
|
|
3621
3823
|
},
|
|
3622
3824
|
{ logger }
|
|
3623
3825
|
);
|
|
3624
|
-
results.push({ file:
|
|
3826
|
+
results.push({ file: label, status: "success", response: extractText(resp) });
|
|
3625
3827
|
} catch (e) {
|
|
3626
|
-
const msg =
|
|
3627
|
-
|
|
3628
|
-
|
|
3828
|
+
const msg = labelError(
|
|
3829
|
+
e instanceof Error ? e.message : String(e),
|
|
3830
|
+
inputs[i]
|
|
3831
|
+
);
|
|
3832
|
+
logger.error(`Failed: ${label} \u2014 ${msg}`);
|
|
3833
|
+
results.push({ file: label, status: "error", error: msg });
|
|
3629
3834
|
}
|
|
3630
3835
|
}
|
|
3631
3836
|
formatResults(results, opts.format, opts.output);
|
|
3837
|
+
cleanup();
|
|
3632
3838
|
const failed = results.filter((r) => r.status === "error").length;
|
|
3633
3839
|
if (failed > 0) process.exit(1);
|
|
3634
3840
|
}
|
|
@@ -3639,8 +3845,8 @@ function registerExtractCommand(parent) {
|
|
|
3639
3845
|
init_errors();
|
|
3640
3846
|
|
|
3641
3847
|
// src/providers/gemini/generators/speech.ts
|
|
3642
|
-
import
|
|
3643
|
-
import
|
|
3848
|
+
import fs21 from "fs";
|
|
3849
|
+
import path25 from "path";
|
|
3644
3850
|
|
|
3645
3851
|
// src/providers/gemini/voices.ts
|
|
3646
3852
|
var GEMINI_TTS_VOICES = [
|
|
@@ -3806,12 +4012,12 @@ function extractWavData(wav) {
|
|
|
3806
4012
|
}
|
|
3807
4013
|
function saveSpeech(outBytes, outputFormat, output, logger) {
|
|
3808
4014
|
const outDir = getOutputDir();
|
|
3809
|
-
const dest =
|
|
3810
|
-
|
|
4015
|
+
const dest = path25.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
4016
|
+
fs21.writeFileSync(dest, outBytes);
|
|
3811
4017
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
3812
4018
|
if (output) {
|
|
3813
|
-
|
|
3814
|
-
|
|
4019
|
+
fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
|
|
4020
|
+
fs21.copyFileSync(dest, output);
|
|
3815
4021
|
logger?.success(`Copied to: ${output}`);
|
|
3816
4022
|
}
|
|
3817
4023
|
return dest;
|
|
@@ -3924,11 +4130,11 @@ function collectSpeakers(value, prev) {
|
|
|
3924
4130
|
|
|
3925
4131
|
// src/providers/gemini/commands/generate-video.ts
|
|
3926
4132
|
init_errors();
|
|
3927
|
-
import
|
|
3928
|
-
import
|
|
4133
|
+
import fs23 from "fs";
|
|
4134
|
+
import path26 from "path";
|
|
3929
4135
|
|
|
3930
4136
|
// src/providers/gemini/veo.ts
|
|
3931
|
-
import
|
|
4137
|
+
import fs22 from "fs";
|
|
3932
4138
|
|
|
3933
4139
|
// src/core/async-poll.ts
|
|
3934
4140
|
init_errors();
|
|
@@ -3995,13 +4201,13 @@ var BASE2 = `https://${API_HOST}/v1beta`;
|
|
|
3995
4201
|
var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
3996
4202
|
var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3997
4203
|
function veoImageFromFile(imgPath) {
|
|
3998
|
-
if (!
|
|
4204
|
+
if (!fs22.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
|
|
3999
4205
|
const detected = getMimeType(imgPath);
|
|
4000
4206
|
const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
|
|
4001
4207
|
if (!VEO_IMAGE_TYPES.has(mimeType)) {
|
|
4002
4208
|
throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
|
|
4003
4209
|
}
|
|
4004
|
-
return { bytesBase64Encoded:
|
|
4210
|
+
return { bytesBase64Encoded: fs22.readFileSync(imgPath).toString("base64"), mimeType };
|
|
4005
4211
|
}
|
|
4006
4212
|
function veoFramesFromPaths(paths = []) {
|
|
4007
4213
|
if (paths.length > 2) {
|
|
@@ -4126,7 +4332,7 @@ function registerGenerateVideoCommand(parent) {
|
|
|
4126
4332
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
4127
4333
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
4128
4334
|
const frames = veoFramesFromPaths(opts.referenceImages);
|
|
4129
|
-
const dest =
|
|
4335
|
+
const dest = path26.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
|
|
4130
4336
|
const finalOp = await runVeo(
|
|
4131
4337
|
model,
|
|
4132
4338
|
{
|
|
@@ -4140,8 +4346,8 @@ function registerGenerateVideoCommand(parent) {
|
|
|
4140
4346
|
);
|
|
4141
4347
|
logger.success(`Saved video: ${dest}`);
|
|
4142
4348
|
if (opts.output) {
|
|
4143
|
-
|
|
4144
|
-
|
|
4349
|
+
fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
|
|
4350
|
+
fs23.copyFileSync(dest, opts.output);
|
|
4145
4351
|
logger.success(`Copied to: ${opts.output}`);
|
|
4146
4352
|
}
|
|
4147
4353
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4157,8 +4363,8 @@ Generated video: ${dest}`);
|
|
|
4157
4363
|
|
|
4158
4364
|
// src/providers/gemini/commands/generate.ts
|
|
4159
4365
|
init_errors();
|
|
4160
|
-
import
|
|
4161
|
-
import
|
|
4366
|
+
import fs24 from "fs";
|
|
4367
|
+
import path27 from "path";
|
|
4162
4368
|
function registerGenerateCommand(parent) {
|
|
4163
4369
|
parent.command("generate").description("Generate images with Gemini (Nano Banana / Imagen 4)").requiredOption("--prompt <text>", "Image generation prompt").option("--model <id>", "Gemini image model to use").option("--aspect-ratio <ratio>", `Aspect ratio (${ASPECT_RATIOS.join("|")})`, "1:1").option("--num-images <n>", "Number of images to generate (1-4)", "1").option("--size <sz>", `Image size (${IMAGE_SIZES.join("|")})`).option("--output <path>", "Copy first generated image to this path").addOption(imageFormatOption()).addOption(noWebpOption()).option("-v, --verbose", "Verbose logging").action(
|
|
4164
4370
|
async (opts) => {
|
|
@@ -4203,8 +4409,8 @@ function registerGenerateCommand(parent) {
|
|
|
4203
4409
|
const outDir = getOutputDir();
|
|
4204
4410
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
4205
4411
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
4206
|
-
const dest =
|
|
4207
|
-
|
|
4412
|
+
const dest = path27.join(outDir, fname);
|
|
4413
|
+
fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
4208
4414
|
savedFiles.push(dest);
|
|
4209
4415
|
logger.success(`Saved: ${dest}`);
|
|
4210
4416
|
}
|
|
@@ -4219,8 +4425,8 @@ function registerGenerateCommand(parent) {
|
|
|
4219
4425
|
process.exit(1);
|
|
4220
4426
|
}
|
|
4221
4427
|
if (opts.output && savedFiles[0]) {
|
|
4222
|
-
|
|
4223
|
-
|
|
4428
|
+
fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
|
|
4429
|
+
fs24.copyFileSync(savedFiles[0], opts.output);
|
|
4224
4430
|
logger.success(`Copied to: ${opts.output}`);
|
|
4225
4431
|
}
|
|
4226
4432
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -4232,8 +4438,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4232
4438
|
}
|
|
4233
4439
|
|
|
4234
4440
|
// src/providers/gemini/commands/image-to-image.ts
|
|
4235
|
-
import
|
|
4236
|
-
import
|
|
4441
|
+
import fs25 from "fs";
|
|
4442
|
+
import path28 from "path";
|
|
4237
4443
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
4238
4444
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
4239
4445
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -4294,14 +4500,14 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
4294
4500
|
const img = images[i];
|
|
4295
4501
|
if (!img) continue;
|
|
4296
4502
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
4297
|
-
const dest =
|
|
4298
|
-
|
|
4503
|
+
const dest = path28.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
4504
|
+
fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
4299
4505
|
saved.push(dest);
|
|
4300
4506
|
logger.success(`Saved: ${dest}`);
|
|
4301
4507
|
}
|
|
4302
4508
|
if (opts.output && saved[0]) {
|
|
4303
|
-
|
|
4304
|
-
|
|
4509
|
+
fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
|
|
4510
|
+
fs25.copyFileSync(saved[0], opts.output);
|
|
4305
4511
|
logger.success(`Copied to: ${opts.output}`);
|
|
4306
4512
|
}
|
|
4307
4513
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4329,7 +4535,7 @@ function sniffImageMime(buf, url) {
|
|
|
4329
4535
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
4330
4536
|
return "image/webp";
|
|
4331
4537
|
}
|
|
4332
|
-
const ext =
|
|
4538
|
+
const ext = path28.extname(new URL(url).pathname).toLowerCase();
|
|
4333
4539
|
if (ext === ".png") return "image/png";
|
|
4334
4540
|
if (ext === ".webp") return "image/webp";
|
|
4335
4541
|
if (ext === ".gif") return "image/gif";
|
|
@@ -4338,8 +4544,8 @@ function sniffImageMime(buf, url) {
|
|
|
4338
4544
|
|
|
4339
4545
|
// src/providers/gemini/commands/image-to-video.ts
|
|
4340
4546
|
init_errors();
|
|
4341
|
-
import
|
|
4342
|
-
import
|
|
4547
|
+
import fs26 from "fs";
|
|
4548
|
+
import path29 from "path";
|
|
4343
4549
|
function registerGeminiImageToVideoCommand(parent) {
|
|
4344
4550
|
parent.command("image-to-video <imagePath>").alias("i2v").description("Generate video from a starting image with Veo (image-to-video) [EXPERIMENTAL]").requiredOption("--prompt <text>", "Motion/scene prompt").option("--last-frame <path>", "Optional closing frame image (Veo 3.1)").option("--model <id>", "Veo model id").option("--resolution <res>", "720p|1080p", "1080p").option("--aspect-ratio <ratio>", "Aspect ratio", "16:9").option("--output <path>", "Copy generated video to this path").option("--no-thumb", "Skip downloading the thumbnail if the API returns one").option("-v, --verbose", "Verbose logging").action(
|
|
4345
4551
|
async (imagePath, opts) => {
|
|
@@ -4359,7 +4565,7 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4359
4565
|
logger.info(
|
|
4360
4566
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
4361
4567
|
);
|
|
4362
|
-
const dest =
|
|
4568
|
+
const dest = path29.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
4363
4569
|
const finalOp = await runVeo(
|
|
4364
4570
|
model,
|
|
4365
4571
|
{
|
|
@@ -4374,8 +4580,8 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4374
4580
|
);
|
|
4375
4581
|
logger.success(`Saved video: ${dest}`);
|
|
4376
4582
|
if (opts.output) {
|
|
4377
|
-
|
|
4378
|
-
|
|
4583
|
+
fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
|
|
4584
|
+
fs26.copyFileSync(dest, opts.output);
|
|
4379
4585
|
logger.success(`Copied to: ${opts.output}`);
|
|
4380
4586
|
}
|
|
4381
4587
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4390,20 +4596,21 @@ Generated video: ${dest}`);
|
|
|
4390
4596
|
}
|
|
4391
4597
|
|
|
4392
4598
|
// src/providers/gemini/commands/transcribe.ts
|
|
4393
|
-
init_errors();
|
|
4394
|
-
import fs28 from "fs";
|
|
4395
4599
|
function registerTranscribeCommand2(parent) {
|
|
4396
|
-
parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption(
|
|
4600
|
+
parent.command("transcribe").description("Transcribe audio or video files with Gemini").requiredOption(
|
|
4601
|
+
"--files <paths...>",
|
|
4602
|
+
"Audio/video file paths to transcribe (use - to read piped stdin)"
|
|
4603
|
+
).option("--stdin-ext <ext>", "Extension for piped stdin when auto-detection fails (e.g. mp3)").option("--prompt <text>", "Transcription prompt", "Generate a transcript with timestamps").option("--model <id>", "Gemini model to use").option("--format <fmt>", "Output format: text|json|csv|markdown", "text").option("--output <path>", "Write results to file").option("-v, --verbose", "Verbose logging").action(
|
|
4397
4604
|
async (opts) => {
|
|
4398
4605
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4399
4606
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
}
|
|
4607
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
4608
|
+
stdinExt: opts.stdinExt
|
|
4609
|
+
});
|
|
4403
4610
|
const results = [];
|
|
4404
|
-
for (let i = 0; i <
|
|
4405
|
-
const filePath =
|
|
4406
|
-
logger.debug(`[${i + 1}/${
|
|
4611
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
4612
|
+
const { path: filePath, label } = inputs[i];
|
|
4613
|
+
logger.debug(`[${i + 1}/${inputs.length}] Transcribing: ${label}`);
|
|
4407
4614
|
try {
|
|
4408
4615
|
const resp = await withGeminiKey(
|
|
4409
4616
|
async (apiKey) => {
|
|
@@ -4430,17 +4637,21 @@ function registerTranscribeCommand2(parent) {
|
|
|
4430
4637
|
{ logger }
|
|
4431
4638
|
);
|
|
4432
4639
|
results.push({
|
|
4433
|
-
file:
|
|
4640
|
+
file: label,
|
|
4434
4641
|
status: "success",
|
|
4435
4642
|
response: extractText(resp)
|
|
4436
4643
|
});
|
|
4437
4644
|
} catch (e) {
|
|
4438
|
-
const msg =
|
|
4439
|
-
|
|
4440
|
-
|
|
4645
|
+
const msg = labelError(
|
|
4646
|
+
e instanceof Error ? e.message : String(e),
|
|
4647
|
+
inputs[i]
|
|
4648
|
+
);
|
|
4649
|
+
logger.error(`Failed: ${label} \u2014 ${msg}`);
|
|
4650
|
+
results.push({ file: label, status: "error", error: msg });
|
|
4441
4651
|
}
|
|
4442
4652
|
}
|
|
4443
4653
|
formatResults(results, opts.format, opts.output);
|
|
4654
|
+
cleanup();
|
|
4444
4655
|
const failed = results.filter((r) => r.status === "error").length;
|
|
4445
4656
|
if (failed > 0) process.exit(1);
|
|
4446
4657
|
}
|
|
@@ -4463,8 +4674,8 @@ function registerGeminiCommands(program2) {
|
|
|
4463
4674
|
}
|
|
4464
4675
|
|
|
4465
4676
|
// src/providers/leonardo/commands/generate.ts
|
|
4466
|
-
import
|
|
4467
|
-
import
|
|
4677
|
+
import fs27 from "fs";
|
|
4678
|
+
import path30 from "path";
|
|
4468
4679
|
|
|
4469
4680
|
// src/providers/leonardo/client.ts
|
|
4470
4681
|
init_errors();
|
|
@@ -4537,11 +4748,11 @@ var LeonardoClient = class {
|
|
|
4537
4748
|
}
|
|
4538
4749
|
apiKey;
|
|
4539
4750
|
baseUrl;
|
|
4540
|
-
async request(
|
|
4751
|
+
async request(path50, opts = {}) {
|
|
4541
4752
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
4542
|
-
const v2Override =
|
|
4753
|
+
const v2Override = path50.startsWith("v2:");
|
|
4543
4754
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
4544
|
-
const finalPath = v2Override ?
|
|
4755
|
+
const finalPath = v2Override ? path50.slice(3) : path50;
|
|
4545
4756
|
const url = new URL(finalBase + finalPath);
|
|
4546
4757
|
if (opts.query) {
|
|
4547
4758
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -4560,14 +4771,14 @@ var LeonardoClient = class {
|
|
|
4560
4771
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
4561
4772
|
});
|
|
4562
4773
|
}
|
|
4563
|
-
get(
|
|
4564
|
-
return this.request(
|
|
4774
|
+
get(path50, query, logger) {
|
|
4775
|
+
return this.request(path50, { method: "GET", query, logger });
|
|
4565
4776
|
}
|
|
4566
|
-
post(
|
|
4567
|
-
return this.request(
|
|
4777
|
+
post(path50, body, logger) {
|
|
4778
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
4568
4779
|
}
|
|
4569
|
-
delete(
|
|
4570
|
-
return this.request(
|
|
4780
|
+
delete(path50, logger) {
|
|
4781
|
+
return this.request(path50, { method: "DELETE", logger });
|
|
4571
4782
|
}
|
|
4572
4783
|
};
|
|
4573
4784
|
function createLeonardoClient() {
|
|
@@ -4578,7 +4789,7 @@ function createLeonardoClient() {
|
|
|
4578
4789
|
function extFromUrl2(url, fallback = ".png") {
|
|
4579
4790
|
try {
|
|
4580
4791
|
const u = new URL(url);
|
|
4581
|
-
const e =
|
|
4792
|
+
const e = path30.extname(u.pathname);
|
|
4582
4793
|
return e.length > 0 ? e : fallback;
|
|
4583
4794
|
} catch {
|
|
4584
4795
|
return fallback;
|
|
@@ -4663,7 +4874,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4663
4874
|
if (!img) continue;
|
|
4664
4875
|
const ext = extFromUrl2(img.url);
|
|
4665
4876
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
4666
|
-
const filepath =
|
|
4877
|
+
const filepath = path30.join(outDir, filename);
|
|
4667
4878
|
try {
|
|
4668
4879
|
await downloadFile(img.url, filepath);
|
|
4669
4880
|
saved.push(filepath);
|
|
@@ -4673,9 +4884,9 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4673
4884
|
}
|
|
4674
4885
|
}
|
|
4675
4886
|
if (opts.output && saved[0]) {
|
|
4676
|
-
const dest =
|
|
4677
|
-
|
|
4678
|
-
|
|
4887
|
+
const dest = path30.resolve(opts.output);
|
|
4888
|
+
fs27.mkdirSync(path30.dirname(dest), { recursive: true });
|
|
4889
|
+
fs27.copyFileSync(saved[0], dest);
|
|
4679
4890
|
logger.success(`Copied to ${dest}`);
|
|
4680
4891
|
}
|
|
4681
4892
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4687,11 +4898,11 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4687
4898
|
}
|
|
4688
4899
|
|
|
4689
4900
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
4690
|
-
import
|
|
4691
|
-
import
|
|
4901
|
+
import fs28 from "fs";
|
|
4902
|
+
import path31 from "path";
|
|
4692
4903
|
function extFromUrl3(url, fallback = ".png") {
|
|
4693
4904
|
try {
|
|
4694
|
-
return
|
|
4905
|
+
return path31.extname(new URL(url).pathname) || fallback;
|
|
4695
4906
|
} catch {
|
|
4696
4907
|
return fallback;
|
|
4697
4908
|
}
|
|
@@ -4753,7 +4964,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4753
4964
|
if (!img) continue;
|
|
4754
4965
|
const ext = extFromUrl3(img.url);
|
|
4755
4966
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
4756
|
-
const filepath =
|
|
4967
|
+
const filepath = path31.join(outDir, filename);
|
|
4757
4968
|
try {
|
|
4758
4969
|
await downloadFile(img.url, filepath);
|
|
4759
4970
|
saved.push(filepath);
|
|
@@ -4763,9 +4974,9 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4763
4974
|
}
|
|
4764
4975
|
}
|
|
4765
4976
|
if (opts.output && saved[0]) {
|
|
4766
|
-
const dest =
|
|
4767
|
-
|
|
4768
|
-
|
|
4977
|
+
const dest = path31.resolve(opts.output);
|
|
4978
|
+
fs28.mkdirSync(path31.dirname(dest), { recursive: true });
|
|
4979
|
+
fs28.copyFileSync(saved[0], dest);
|
|
4769
4980
|
logger.success(`Copied to ${dest}`);
|
|
4770
4981
|
}
|
|
4771
4982
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4777,7 +4988,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4777
4988
|
}
|
|
4778
4989
|
|
|
4779
4990
|
// src/providers/leonardo/commands/video.ts
|
|
4780
|
-
import
|
|
4991
|
+
import path32 from "path";
|
|
4781
4992
|
function v2DimsFor(resolution) {
|
|
4782
4993
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
4783
4994
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -4888,7 +5099,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
4888
5099
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
4889
5100
|
process.exit(1);
|
|
4890
5101
|
}
|
|
4891
|
-
const outPath = opts.output ??
|
|
5102
|
+
const outPath = opts.output ?? path32.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
4892
5103
|
await downloadFile(videoUrl2, outPath);
|
|
4893
5104
|
logger.success(`Saved ${outPath}`);
|
|
4894
5105
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -4994,7 +5205,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
4994
5205
|
}
|
|
4995
5206
|
|
|
4996
5207
|
// src/providers/leonardo/commands/status.ts
|
|
4997
|
-
import
|
|
5208
|
+
import path33 from "path";
|
|
4998
5209
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
4999
5210
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
5000
5211
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -5059,7 +5270,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
5059
5270
|
const outDir = getOutputDir();
|
|
5060
5271
|
let idx = 0;
|
|
5061
5272
|
for (const url of videos) {
|
|
5062
|
-
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output :
|
|
5273
|
+
const dest = opts.output && videos.length === 1 && images.length === 0 ? opts.output : path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.mp4`);
|
|
5063
5274
|
await downloadFile(url, dest);
|
|
5064
5275
|
logger.success(`Saved ${dest}`);
|
|
5065
5276
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -5072,7 +5283,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
5072
5283
|
for (const url of images) {
|
|
5073
5284
|
const m = url.match(IMAGE_EXT_RE2);
|
|
5074
5285
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
5075
|
-
const dest =
|
|
5286
|
+
const dest = path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
5076
5287
|
await downloadFile(url, dest);
|
|
5077
5288
|
logger.success(`Saved ${dest}`);
|
|
5078
5289
|
idx++;
|
|
@@ -5229,8 +5440,8 @@ function sleep3(ms) {
|
|
|
5229
5440
|
}
|
|
5230
5441
|
|
|
5231
5442
|
// src/providers/minimax/generators/music.ts
|
|
5232
|
-
import
|
|
5233
|
-
import
|
|
5443
|
+
import fs29 from "fs";
|
|
5444
|
+
import path34 from "path";
|
|
5234
5445
|
async function generateMinimaxMusic(opts) {
|
|
5235
5446
|
const {
|
|
5236
5447
|
apiKey,
|
|
@@ -5275,14 +5486,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
5275
5486
|
}
|
|
5276
5487
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
5277
5488
|
const outDir = getOutputDir();
|
|
5278
|
-
const dest =
|
|
5489
|
+
const dest = path34.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
5279
5490
|
try {
|
|
5280
5491
|
if (audioData.startsWith("http")) {
|
|
5281
5492
|
const bytes = await fetchBytes(audioData);
|
|
5282
|
-
|
|
5493
|
+
fs29.writeFileSync(dest, bytes);
|
|
5283
5494
|
} else {
|
|
5284
5495
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
5285
|
-
|
|
5496
|
+
fs29.writeFileSync(dest, audioBytes);
|
|
5286
5497
|
}
|
|
5287
5498
|
} catch (e) {
|
|
5288
5499
|
return {
|
|
@@ -5293,8 +5504,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
5293
5504
|
const durSec = durationMs / 1e3;
|
|
5294
5505
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
5295
5506
|
if (output) {
|
|
5296
|
-
|
|
5297
|
-
|
|
5507
|
+
fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
|
|
5508
|
+
fs29.copyFileSync(dest, output);
|
|
5298
5509
|
}
|
|
5299
5510
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
5300
5511
|
}
|
|
@@ -5368,8 +5579,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
5368
5579
|
init_errors();
|
|
5369
5580
|
|
|
5370
5581
|
// src/providers/minimax/generators/speech.ts
|
|
5371
|
-
import
|
|
5372
|
-
import
|
|
5582
|
+
import fs30 from "fs";
|
|
5583
|
+
import path35 from "path";
|
|
5373
5584
|
async function generateMinimaxSpeech(opts) {
|
|
5374
5585
|
const {
|
|
5375
5586
|
apiKey,
|
|
@@ -5416,12 +5627,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
5416
5627
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
5417
5628
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
5418
5629
|
const outDir = getOutputDir();
|
|
5419
|
-
const dest =
|
|
5420
|
-
|
|
5630
|
+
const dest = path35.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
5631
|
+
fs30.writeFileSync(dest, audioBytes);
|
|
5421
5632
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
5422
5633
|
if (output) {
|
|
5423
|
-
|
|
5424
|
-
|
|
5634
|
+
fs30.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
|
|
5635
|
+
fs30.copyFileSync(dest, output);
|
|
5425
5636
|
}
|
|
5426
5637
|
return { status: "success", generatedAudio: dest, model };
|
|
5427
5638
|
}
|
|
@@ -5468,8 +5679,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
5468
5679
|
init_errors();
|
|
5469
5680
|
|
|
5470
5681
|
// src/providers/minimax/generators/video.ts
|
|
5471
|
-
import
|
|
5472
|
-
import
|
|
5682
|
+
import fs31 from "fs";
|
|
5683
|
+
import path36 from "path";
|
|
5473
5684
|
async function generateMinimaxVideo(opts) {
|
|
5474
5685
|
const {
|
|
5475
5686
|
apiKey,
|
|
@@ -5515,18 +5726,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
5515
5726
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
5516
5727
|
}
|
|
5517
5728
|
const outDir = getOutputDir();
|
|
5518
|
-
const dest =
|
|
5729
|
+
const dest = path36.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
5519
5730
|
try {
|
|
5520
5731
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
5521
5732
|
} catch (e) {
|
|
5522
5733
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
5523
5734
|
}
|
|
5524
5735
|
const elapsed = (Date.now() - start) / 1e3;
|
|
5525
|
-
const stat =
|
|
5736
|
+
const stat = fs31.statSync(dest);
|
|
5526
5737
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
5527
5738
|
if (output) {
|
|
5528
|
-
|
|
5529
|
-
|
|
5739
|
+
fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
|
|
5740
|
+
fs31.copyFileSync(dest, output);
|
|
5530
5741
|
}
|
|
5531
5742
|
await maybeDownloadThumb(pollResult, dest, {
|
|
5532
5743
|
skip: thumb === false,
|
|
@@ -5588,8 +5799,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
5588
5799
|
|
|
5589
5800
|
// src/providers/minimax/generators/image.ts
|
|
5590
5801
|
init_errors();
|
|
5591
|
-
import
|
|
5592
|
-
import
|
|
5802
|
+
import fs32 from "fs";
|
|
5803
|
+
import path37 from "path";
|
|
5593
5804
|
async function generateMinimaxImage(opts) {
|
|
5594
5805
|
const {
|
|
5595
5806
|
apiKey,
|
|
@@ -5627,8 +5838,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5627
5838
|
try {
|
|
5628
5839
|
const bytes = await fetchBytes(url);
|
|
5629
5840
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
5630
|
-
const dest =
|
|
5631
|
-
|
|
5841
|
+
const dest = path37.join(outDir, fname);
|
|
5842
|
+
fs32.writeFileSync(dest, bytes);
|
|
5632
5843
|
savedFiles.push(dest);
|
|
5633
5844
|
logger?.success(`Saved: ${dest}`);
|
|
5634
5845
|
} catch (e) {
|
|
@@ -5639,8 +5850,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5639
5850
|
}
|
|
5640
5851
|
}
|
|
5641
5852
|
if (output && savedFiles[0]) {
|
|
5642
|
-
|
|
5643
|
-
|
|
5853
|
+
fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
|
|
5854
|
+
fs32.copyFileSync(savedFiles[0], output);
|
|
5644
5855
|
}
|
|
5645
5856
|
return { status: "success", generatedImages: savedFiles, model };
|
|
5646
5857
|
}
|
|
@@ -5679,8 +5890,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
5679
5890
|
|
|
5680
5891
|
// src/providers/minimax/commands/image-to-image.ts
|
|
5681
5892
|
init_errors();
|
|
5682
|
-
import
|
|
5683
|
-
import
|
|
5893
|
+
import fs33 from "fs";
|
|
5894
|
+
import path38 from "path";
|
|
5684
5895
|
function registerMinimaxImageToImageCommand(parent) {
|
|
5685
5896
|
parent.command("image-to-image").alias("i2i").description(
|
|
5686
5897
|
"MiniMax character/subject preservation. CAVEAT: NOT free-form image editing \u2014 the --ref image is used only as a character/face reference for a NEW scene described by --prompt."
|
|
@@ -5735,8 +5946,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5735
5946
|
if (!url) continue;
|
|
5736
5947
|
try {
|
|
5737
5948
|
const bytes = await fetchBytes(url);
|
|
5738
|
-
const dest =
|
|
5739
|
-
|
|
5949
|
+
const dest = path38.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
5950
|
+
fs33.writeFileSync(dest, bytes);
|
|
5740
5951
|
saved.push(dest);
|
|
5741
5952
|
logger.success(`Saved: ${dest}`);
|
|
5742
5953
|
} catch (e) {
|
|
@@ -5747,8 +5958,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5747
5958
|
}
|
|
5748
5959
|
}
|
|
5749
5960
|
if (opts.output && saved[0]) {
|
|
5750
|
-
|
|
5751
|
-
|
|
5961
|
+
fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
|
|
5962
|
+
fs33.copyFileSync(saved[0], opts.output);
|
|
5752
5963
|
logger.success(`Copied to: ${opts.output}`);
|
|
5753
5964
|
}
|
|
5754
5965
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -5882,12 +6093,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
5882
6093
|
}
|
|
5883
6094
|
|
|
5884
6095
|
// src/providers/openai/openai-audio.ts
|
|
5885
|
-
import
|
|
5886
|
-
import
|
|
6096
|
+
import fs35 from "fs";
|
|
6097
|
+
import path40 from "path";
|
|
5887
6098
|
|
|
5888
6099
|
// src/providers/openai/client.ts
|
|
5889
|
-
import
|
|
5890
|
-
import
|
|
6100
|
+
import fs34 from "fs";
|
|
6101
|
+
import path39 from "path";
|
|
5891
6102
|
init_errors();
|
|
5892
6103
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
5893
6104
|
function requireOpenAIKey() {
|
|
@@ -5953,10 +6164,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
5953
6164
|
return Buffer.from(await res.arrayBuffer());
|
|
5954
6165
|
}
|
|
5955
6166
|
function fileFromPath(filePath) {
|
|
5956
|
-
const abs =
|
|
5957
|
-
if (!
|
|
5958
|
-
const bytes =
|
|
5959
|
-
return new File([new Uint8Array(bytes)],
|
|
6167
|
+
const abs = path39.resolve(filePath);
|
|
6168
|
+
if (!fs34.existsSync(abs)) throw new ValidationError(`File not found: ${abs}`);
|
|
6169
|
+
const bytes = fs34.readFileSync(abs);
|
|
6170
|
+
return new File([new Uint8Array(bytes)], path39.basename(abs));
|
|
5960
6171
|
}
|
|
5961
6172
|
|
|
5962
6173
|
// src/providers/openai/openai-audio.ts
|
|
@@ -5995,7 +6206,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
5995
6206
|
}
|
|
5996
6207
|
function audioMimeFromReference(ref) {
|
|
5997
6208
|
const pathname = new URL(ref).pathname;
|
|
5998
|
-
const ext =
|
|
6209
|
+
const ext = path40.extname(pathname).toLowerCase();
|
|
5999
6210
|
switch (ext) {
|
|
6000
6211
|
case ".mp3":
|
|
6001
6212
|
return "audio/mpeg";
|
|
@@ -6014,12 +6225,12 @@ function audioMimeFromReference(ref) {
|
|
|
6014
6225
|
async function generateOpenAISpeech(opts) {
|
|
6015
6226
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
6016
6227
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
6017
|
-
const dest =
|
|
6018
|
-
|
|
6228
|
+
const dest = path40.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
6229
|
+
fs35.writeFileSync(dest, bytes);
|
|
6019
6230
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
6020
6231
|
if (opts.output) {
|
|
6021
|
-
|
|
6022
|
-
|
|
6232
|
+
fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
|
|
6233
|
+
fs35.copyFileSync(dest, opts.output);
|
|
6023
6234
|
}
|
|
6024
6235
|
return dest;
|
|
6025
6236
|
}
|
|
@@ -6041,9 +6252,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
6041
6252
|
);
|
|
6042
6253
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
6043
6254
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
6044
|
-
const dest = opts.output ?
|
|
6045
|
-
|
|
6046
|
-
|
|
6255
|
+
const dest = opts.output ? path40.resolve(opts.output) : path40.join(getOutputDir(), `openai-transcribe-${Date.now()}.${ext}`);
|
|
6256
|
+
fs35.mkdirSync(path40.dirname(dest), { recursive: true });
|
|
6257
|
+
fs35.writeFileSync(dest, opts.format === "text" ? text ?? "" : JSON.stringify(data, null, 2));
|
|
6047
6258
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
6048
6259
|
return { path: dest, text };
|
|
6049
6260
|
}
|
|
@@ -6070,9 +6281,9 @@ Generated audio: ${dest}`);
|
|
|
6070
6281
|
}
|
|
6071
6282
|
|
|
6072
6283
|
// src/providers/openai/codex-image-driver.ts
|
|
6073
|
-
import
|
|
6074
|
-
import
|
|
6075
|
-
import
|
|
6284
|
+
import fs36 from "fs";
|
|
6285
|
+
import os3 from "os";
|
|
6286
|
+
import path41 from "path";
|
|
6076
6287
|
import { execa } from "execa";
|
|
6077
6288
|
init_errors();
|
|
6078
6289
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -6121,12 +6332,12 @@ function sanitizeCodexEnv() {
|
|
|
6121
6332
|
async function runCodexImageGeneration(opts) {
|
|
6122
6333
|
const runner = opts.runner ?? defaultRunner;
|
|
6123
6334
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
6124
|
-
const dest = opts.output ?
|
|
6125
|
-
|
|
6126
|
-
const tmp =
|
|
6127
|
-
const schema =
|
|
6128
|
-
const result =
|
|
6129
|
-
|
|
6335
|
+
const dest = opts.output ? path41.resolve(opts.output) : path41.join(getOutputDir(), `codex-image-${Date.now()}.${ext}`);
|
|
6336
|
+
fs36.mkdirSync(path41.dirname(dest), { recursive: true });
|
|
6337
|
+
const tmp = fs36.mkdtempSync(path41.join(os3.tmpdir(), "multix-codex-image-"));
|
|
6338
|
+
const schema = path41.join(tmp, "schema.json");
|
|
6339
|
+
const result = path41.join(tmp, "result.json");
|
|
6340
|
+
fs36.writeFileSync(
|
|
6130
6341
|
schema,
|
|
6131
6342
|
JSON.stringify({
|
|
6132
6343
|
type: "object",
|
|
@@ -6156,7 +6367,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
6156
6367
|
if (res.exitCode !== 0) {
|
|
6157
6368
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
6158
6369
|
}
|
|
6159
|
-
if (!
|
|
6370
|
+
if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
|
|
6160
6371
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
6161
6372
|
}
|
|
6162
6373
|
return { path: dest };
|
|
@@ -6173,8 +6384,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
6173
6384
|
|
|
6174
6385
|
// src/providers/openai/openai-image.ts
|
|
6175
6386
|
init_errors();
|
|
6176
|
-
import
|
|
6177
|
-
import
|
|
6387
|
+
import fs37 from "fs";
|
|
6388
|
+
import path42 from "path";
|
|
6178
6389
|
function buildImageGenerationBody(opts) {
|
|
6179
6390
|
return {
|
|
6180
6391
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -6207,14 +6418,14 @@ function saveOpenAIImages(opts) {
|
|
|
6207
6418
|
for (let i = 0; i < items.length; i++) {
|
|
6208
6419
|
const b64 = items[i]?.b64_json;
|
|
6209
6420
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
6210
|
-
const dest =
|
|
6211
|
-
|
|
6421
|
+
const dest = path42.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
6422
|
+
fs37.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
6212
6423
|
saved.push(dest);
|
|
6213
6424
|
opts.logger?.success(`Saved: ${dest}`);
|
|
6214
6425
|
}
|
|
6215
6426
|
if (opts.output && saved[0]) {
|
|
6216
|
-
|
|
6217
|
-
|
|
6427
|
+
fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
|
|
6428
|
+
fs37.copyFileSync(saved[0], opts.output);
|
|
6218
6429
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
6219
6430
|
}
|
|
6220
6431
|
return saved;
|
|
@@ -6223,7 +6434,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
6223
6434
|
if (isHttpUrl(ref)) {
|
|
6224
6435
|
const bytes2 = await fetchBytes(ref);
|
|
6225
6436
|
const pathname = new URL(ref).pathname;
|
|
6226
|
-
const filename =
|
|
6437
|
+
const filename = path42.basename(pathname) || "reference.png";
|
|
6227
6438
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
6228
6439
|
}
|
|
6229
6440
|
const file = fileFromPath(ref);
|
|
@@ -6268,7 +6479,7 @@ function sniffImageMime2(buf, name) {
|
|
|
6268
6479
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
6269
6480
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
6270
6481
|
return "image/webp";
|
|
6271
|
-
const ext =
|
|
6482
|
+
const ext = path42.extname(name).toLowerCase();
|
|
6272
6483
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
6273
6484
|
if (ext === ".webp") return "image/webp";
|
|
6274
6485
|
return "image/png";
|
|
@@ -6412,8 +6623,8 @@ function registerOpenAICommands(program2) {
|
|
|
6412
6623
|
}
|
|
6413
6624
|
|
|
6414
6625
|
// src/providers/openrouter/client.ts
|
|
6415
|
-
import
|
|
6416
|
-
import
|
|
6626
|
+
import fs38 from "fs";
|
|
6627
|
+
import path43 from "path";
|
|
6417
6628
|
init_errors();
|
|
6418
6629
|
|
|
6419
6630
|
// src/providers/openrouter/payload.ts
|
|
@@ -6577,8 +6788,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6577
6788
|
if (!url) continue;
|
|
6578
6789
|
try {
|
|
6579
6790
|
const bytes = await fetchBytes(url);
|
|
6580
|
-
const dest =
|
|
6581
|
-
|
|
6791
|
+
const dest = path43.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
6792
|
+
fs38.writeFileSync(dest, bytes);
|
|
6582
6793
|
savedFiles.push(dest);
|
|
6583
6794
|
logger?.success(`Saved: ${dest}`);
|
|
6584
6795
|
} catch (e) {
|
|
@@ -6589,8 +6800,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6589
6800
|
}
|
|
6590
6801
|
}
|
|
6591
6802
|
if (output && savedFiles[0]) {
|
|
6592
|
-
|
|
6593
|
-
|
|
6803
|
+
fs38.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
|
|
6804
|
+
fs38.writeFileSync(output, fs38.readFileSync(savedFiles[0]));
|
|
6594
6805
|
}
|
|
6595
6806
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
6596
6807
|
}
|
|
@@ -6631,8 +6842,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6631
6842
|
}
|
|
6632
6843
|
|
|
6633
6844
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
6634
|
-
import
|
|
6635
|
-
import
|
|
6845
|
+
import fs39 from "fs";
|
|
6846
|
+
import path44 from "path";
|
|
6636
6847
|
init_errors();
|
|
6637
6848
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
6638
6849
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -6724,14 +6935,14 @@ async function runImageToImage(opts) {
|
|
|
6724
6935
|
const url = parsed.urls[i];
|
|
6725
6936
|
if (!url) continue;
|
|
6726
6937
|
const bytes = await fetchBytes(url);
|
|
6727
|
-
const dest =
|
|
6728
|
-
|
|
6938
|
+
const dest = path44.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
6939
|
+
fs39.writeFileSync(dest, bytes);
|
|
6729
6940
|
saved.push(dest);
|
|
6730
6941
|
opts.logger.success(`Saved: ${dest}`);
|
|
6731
6942
|
}
|
|
6732
6943
|
if (opts.output && saved[0]) {
|
|
6733
|
-
|
|
6734
|
-
|
|
6944
|
+
fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
6945
|
+
fs39.copyFileSync(saved[0], opts.output);
|
|
6735
6946
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
6736
6947
|
}
|
|
6737
6948
|
return saved;
|
|
@@ -6776,13 +6987,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
6776
6987
|
}
|
|
6777
6988
|
|
|
6778
6989
|
// src/providers/openrouter/commands/video-status.ts
|
|
6779
|
-
import
|
|
6780
|
-
import
|
|
6990
|
+
import fs40 from "fs";
|
|
6991
|
+
import path45 from "path";
|
|
6781
6992
|
async function downloadOpenRouterVideo(opts) {
|
|
6782
6993
|
const { jobId, logger, source } = opts;
|
|
6783
6994
|
const apiKey = requireOpenRouterKey();
|
|
6784
6995
|
const url = videoContentUrl(jobId);
|
|
6785
|
-
const dest =
|
|
6996
|
+
const dest = path45.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
6786
6997
|
const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
6787
6998
|
if (!r.ok) {
|
|
6788
6999
|
logger.error(`Download HTTP ${r.status}`);
|
|
@@ -6792,14 +7003,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
6792
7003
|
logger.error("Empty response body");
|
|
6793
7004
|
process.exit(1);
|
|
6794
7005
|
}
|
|
6795
|
-
|
|
6796
|
-
const ws =
|
|
7006
|
+
fs40.mkdirSync(path45.dirname(dest), { recursive: true });
|
|
7007
|
+
const ws = fs40.createWriteStream(dest);
|
|
6797
7008
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
6798
7009
|
await pipeline2(r.body, ws);
|
|
6799
7010
|
logger.success(`Saved ${dest}`);
|
|
6800
7011
|
if (opts.output) {
|
|
6801
|
-
|
|
6802
|
-
|
|
7012
|
+
fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
|
|
7013
|
+
fs40.copyFileSync(dest, opts.output);
|
|
6803
7014
|
logger.success(`Copied to ${opts.output}`);
|
|
6804
7015
|
}
|
|
6805
7016
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -6965,8 +7176,8 @@ function registerOpenRouterCommands(program2) {
|
|
|
6965
7176
|
}
|
|
6966
7177
|
|
|
6967
7178
|
// src/commands/check.ts
|
|
6968
|
-
import
|
|
6969
|
-
import
|
|
7179
|
+
import fs41 from "fs";
|
|
7180
|
+
import os4 from "os";
|
|
6970
7181
|
|
|
6971
7182
|
// src/commands/check-helpers/binary-check.ts
|
|
6972
7183
|
import { execa as execa2 } from "execa";
|
|
@@ -7144,8 +7355,8 @@ function registerCheckCommand(program2) {
|
|
|
7144
7355
|
logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
|
|
7145
7356
|
}
|
|
7146
7357
|
if (!legacyClaudeEnvEnabled()) {
|
|
7147
|
-
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 :
|
|
7148
|
-
const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) =>
|
|
7358
|
+
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os4.homedir();
|
|
7359
|
+
const legacy = legacyClaudeEnvFiles(process.cwd(), home).filter((f) => fs41.existsSync(f));
|
|
7149
7360
|
if (legacy.length > 0) {
|
|
7150
7361
|
logger.info(
|
|
7151
7362
|
`Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
|
|
@@ -7250,8 +7461,8 @@ function registerCheckCommand(program2) {
|
|
|
7250
7461
|
|
|
7251
7462
|
// src/commands/doc/convert.ts
|
|
7252
7463
|
init_errors();
|
|
7253
|
-
import
|
|
7254
|
-
import
|
|
7464
|
+
import fs42 from "fs";
|
|
7465
|
+
import path46 from "path";
|
|
7255
7466
|
|
|
7256
7467
|
// src/commands/doc/default-prompt.ts
|
|
7257
7468
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -7272,18 +7483,18 @@ async function convertDocuments(opts) {
|
|
|
7272
7483
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
7273
7484
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
7274
7485
|
for (const f of inputs) {
|
|
7275
|
-
if (!
|
|
7486
|
+
if (!fs42.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
7276
7487
|
}
|
|
7277
7488
|
let outputPath;
|
|
7278
7489
|
if (output) {
|
|
7279
|
-
outputPath =
|
|
7490
|
+
outputPath = path46.resolve(output);
|
|
7280
7491
|
} else if (autoName && inputs.length === 1 && inputs[0]) {
|
|
7281
|
-
const base =
|
|
7282
|
-
outputPath =
|
|
7492
|
+
const base = path46.basename(inputs[0], path46.extname(inputs[0]));
|
|
7493
|
+
outputPath = path46.join(getOutputDir(), `${base}-extraction.md`);
|
|
7283
7494
|
} else {
|
|
7284
|
-
outputPath =
|
|
7495
|
+
outputPath = path46.join(getOutputDir(), "document-extraction.md");
|
|
7285
7496
|
}
|
|
7286
|
-
|
|
7497
|
+
fs42.mkdirSync(path46.dirname(outputPath), { recursive: true });
|
|
7287
7498
|
const results = [];
|
|
7288
7499
|
for (let i = 0; i < inputs.length; i++) {
|
|
7289
7500
|
const filePath = inputs[i];
|
|
@@ -7343,7 +7554,7 @@ async function convertDocuments(opts) {
|
|
|
7343
7554
|
`);
|
|
7344
7555
|
for (const r of results) {
|
|
7345
7556
|
lines.push(`
|
|
7346
|
-
## ${
|
|
7557
|
+
## ${path46.basename(r.file)}
|
|
7347
7558
|
`);
|
|
7348
7559
|
if (r.status === "success" && r.markdown) {
|
|
7349
7560
|
lines.push(`${r.markdown}
|
|
@@ -7356,7 +7567,7 @@ async function convertDocuments(opts) {
|
|
|
7356
7567
|
}
|
|
7357
7568
|
lines.push("\n---\n");
|
|
7358
7569
|
}
|
|
7359
|
-
|
|
7570
|
+
fs42.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
7360
7571
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
7361
7572
|
console.log(`
|
|
7362
7573
|
${"=".repeat(50)}`);
|
|
@@ -7404,12 +7615,12 @@ function registerDocCommands(program2) {
|
|
|
7404
7615
|
|
|
7405
7616
|
// src/commands/media/index.ts
|
|
7406
7617
|
init_errors();
|
|
7407
|
-
import
|
|
7408
|
-
import
|
|
7618
|
+
import fs45 from "fs";
|
|
7619
|
+
import path49 from "path";
|
|
7409
7620
|
|
|
7410
7621
|
// src/commands/media/batch.ts
|
|
7411
|
-
import
|
|
7412
|
-
import
|
|
7622
|
+
import fs43 from "fs";
|
|
7623
|
+
import path47 from "path";
|
|
7413
7624
|
|
|
7414
7625
|
// src/commands/media/detect-type.ts
|
|
7415
7626
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -7605,10 +7816,10 @@ async function batchOptimize(opts) {
|
|
|
7605
7816
|
verbose,
|
|
7606
7817
|
logger
|
|
7607
7818
|
} = opts;
|
|
7608
|
-
|
|
7609
|
-
const entries =
|
|
7819
|
+
fs43.mkdirSync(outputDir, { recursive: true });
|
|
7820
|
+
const entries = fs43.readdirSync(inputDir);
|
|
7610
7821
|
const mediaFiles = entries.filter((f) => {
|
|
7611
|
-
const ext =
|
|
7822
|
+
const ext = path47.extname(f).toLowerCase();
|
|
7612
7823
|
return ALL_EXTS.has(ext);
|
|
7613
7824
|
});
|
|
7614
7825
|
if (mediaFiles.length === 0) {
|
|
@@ -7619,9 +7830,9 @@ async function batchOptimize(opts) {
|
|
|
7619
7830
|
let succeeded = 0;
|
|
7620
7831
|
const errors = [];
|
|
7621
7832
|
for (const file of mediaFiles) {
|
|
7622
|
-
const inputPath =
|
|
7623
|
-
const outputPath =
|
|
7624
|
-
const ext =
|
|
7833
|
+
const inputPath = path47.join(inputDir, file);
|
|
7834
|
+
const outputPath = path47.join(outputDir, file);
|
|
7835
|
+
const ext = path47.extname(file).toLowerCase();
|
|
7625
7836
|
const kind = extToKind(ext);
|
|
7626
7837
|
logger?.debug(`Processing: ${file}`);
|
|
7627
7838
|
try {
|
|
@@ -7658,8 +7869,8 @@ async function batchOptimize(opts) {
|
|
|
7658
7869
|
|
|
7659
7870
|
// src/commands/media/split-video.ts
|
|
7660
7871
|
init_errors();
|
|
7661
|
-
import
|
|
7662
|
-
import
|
|
7872
|
+
import fs44 from "fs";
|
|
7873
|
+
import path48 from "path";
|
|
7663
7874
|
async function splitVideo(opts) {
|
|
7664
7875
|
await assertFfmpeg();
|
|
7665
7876
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -7670,10 +7881,10 @@ async function splitVideo(opts) {
|
|
|
7670
7881
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
7671
7882
|
return [input];
|
|
7672
7883
|
}
|
|
7673
|
-
|
|
7674
|
-
const ext =
|
|
7675
|
-
const baseName =
|
|
7676
|
-
const outputPattern =
|
|
7884
|
+
fs44.mkdirSync(outputDir, { recursive: true });
|
|
7885
|
+
const ext = path48.extname(input) || ".mp4";
|
|
7886
|
+
const baseName = path48.basename(input, ext);
|
|
7887
|
+
const outputPattern = path48.join(outputDir, `${baseName}_chunk_%03d${ext}`);
|
|
7677
7888
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
7678
7889
|
const args = [
|
|
7679
7890
|
"-i",
|
|
@@ -7692,8 +7903,8 @@ async function splitVideo(opts) {
|
|
|
7692
7903
|
await runFfmpeg(args, { verbose, logger });
|
|
7693
7904
|
const files = [];
|
|
7694
7905
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
7695
|
-
const fname =
|
|
7696
|
-
if (
|
|
7906
|
+
const fname = path48.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
7907
|
+
if (fs44.existsSync(fname)) files.push(fname);
|
|
7697
7908
|
}
|
|
7698
7909
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
7699
7910
|
return files;
|
|
@@ -7707,9 +7918,9 @@ function registerMediaCommands(program2) {
|
|
|
7707
7918
|
media.command("optimize").description("Optimize a single media file (video, audio, or image)").requiredOption("--input <file>", "Input media file").requiredOption("--output <file>", "Output file path").option("--target-size <MB>", "Target file size in MB").option("--quality <n>", "Quality: video CRF (0-51) or image (1-100)", "85").option("--max-width <px>", "Max image/video width in pixels", "1920").option("--bitrate <rate>", "Audio bitrate (e.g. 64k)", "64k").option("--resolution <WxH>", "Force video resolution (e.g. 1920x1080)").option("-v, --verbose", "Verbose logging").action(
|
|
7708
7919
|
async (opts) => {
|
|
7709
7920
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7710
|
-
if (!
|
|
7711
|
-
|
|
7712
|
-
const ext =
|
|
7921
|
+
if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7922
|
+
fs45.mkdirSync(path49.dirname(path49.resolve(opts.output)), { recursive: true });
|
|
7923
|
+
const ext = path49.extname(opts.input).toLowerCase();
|
|
7713
7924
|
const kind = extToKind(ext);
|
|
7714
7925
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
7715
7926
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -7749,7 +7960,7 @@ function registerMediaCommands(program2) {
|
|
|
7749
7960
|
media.command("split").description("Split a long video into fixed-duration chunks").requiredOption("--input <file>", "Input video file").option("--output-dir <dir>", "Output directory for chunks", "./chunks").option("--chunk-duration <sec>", "Chunk duration in seconds", "3600").option("-v, --verbose", "Verbose logging").action(
|
|
7750
7961
|
async (opts) => {
|
|
7751
7962
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7752
|
-
if (!
|
|
7963
|
+
if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7753
7964
|
const chunks = await splitVideo({
|
|
7754
7965
|
input: opts.input,
|
|
7755
7966
|
outputDir: opts.outputDir,
|
|
@@ -7765,7 +7976,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
7765
7976
|
media.command("batch").description("Batch optimize all media files in a directory").requiredOption("--input-dir <dir>", "Input directory").requiredOption("--output-dir <dir>", "Output directory").option("--quality <n>", "Quality setting", "85").option("--max-width <px>", "Max image/video width", "1920").option("--bitrate <rate>", "Audio bitrate", "64k").option("-v, --verbose", "Verbose logging").action(
|
|
7766
7977
|
async (opts) => {
|
|
7767
7978
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7768
|
-
if (!
|
|
7979
|
+
if (!fs45.existsSync(opts.inputDir))
|
|
7769
7980
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
7770
7981
|
const result = await batchOptimize({
|
|
7771
7982
|
inputDir: opts.inputDir,
|