@mrgoonie/multix 0.8.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 +14 -0
- package/README.md +4 -1
- package/dist/cli.js +540 -256
- package/package.json +1 -1
- package/skill/SKILL.md +5 -3
package/dist/cli.js
CHANGED
|
@@ -280,22 +280,78 @@ init_redact();
|
|
|
280
280
|
import fs3 from "fs";
|
|
281
281
|
import os from "os";
|
|
282
282
|
import path3 from "path";
|
|
283
|
-
import { config as dotenvConfig } from "dotenv";
|
|
283
|
+
import { config as dotenvConfig, parse as dotenvParse } from "dotenv";
|
|
284
284
|
var loaded = false;
|
|
285
|
+
var loadedFiles = [];
|
|
286
|
+
var LEGACY_SKILL_DIRS = ["ai-multimodal", "ak-ai-multimodal"];
|
|
287
|
+
function legacyFilesUnder(root) {
|
|
288
|
+
const claude = path3.join(root, ".claude");
|
|
289
|
+
return [
|
|
290
|
+
...LEGACY_SKILL_DIRS.map((dir) => path3.join(claude, "skills", dir, ".env")),
|
|
291
|
+
path3.join(claude, "skills", ".env"),
|
|
292
|
+
path3.join(claude, ".env")
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
function samePath(a, b) {
|
|
296
|
+
const norm = (p) => {
|
|
297
|
+
const r = path3.resolve(p);
|
|
298
|
+
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
299
|
+
};
|
|
300
|
+
return norm(a) === norm(b);
|
|
301
|
+
}
|
|
302
|
+
function legacyClaudeEnvFiles(cwd, home) {
|
|
303
|
+
const files = legacyFilesUnder(cwd);
|
|
304
|
+
if (home && !samePath(home, cwd)) files.push(...legacyFilesUnder(home));
|
|
305
|
+
return files;
|
|
306
|
+
}
|
|
307
|
+
function legacyClaudeEnvEnabled(env = process.env) {
|
|
308
|
+
const value = env.MULTIX_LEGACY_CLAUDE_ENV;
|
|
309
|
+
return value !== void 0 && ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
310
|
+
}
|
|
311
|
+
var LEGACY_ALLOWED_NAME = /^[A-Z][A-Z0-9_]*_(API_KEY(_\d+)?|API_TOKEN|ACCOUNT_ID|GATEWAY_ID)$|^(FAL_KEY|ARK_API_KEY)$/;
|
|
312
|
+
function isLegacyAllowedName(name) {
|
|
313
|
+
return LEGACY_ALLOWED_NAME.test(name);
|
|
314
|
+
}
|
|
315
|
+
function isReadableFile(file) {
|
|
316
|
+
try {
|
|
317
|
+
return fs3.statSync(file).isFile();
|
|
318
|
+
} catch {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function loadFile(file) {
|
|
323
|
+
if (!isReadableFile(file)) return;
|
|
324
|
+
const result = dotenvConfig({ path: file, override: false });
|
|
325
|
+
if (!result.error) loadedFiles.push(file);
|
|
326
|
+
}
|
|
327
|
+
function loadLegacyFile(file) {
|
|
328
|
+
if (!isReadableFile(file)) return;
|
|
329
|
+
let parsed;
|
|
330
|
+
try {
|
|
331
|
+
parsed = dotenvParse(fs3.readFileSync(file));
|
|
332
|
+
} catch {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
336
|
+
if (isLegacyAllowedName(name) && process.env[name] === void 0) process.env[name] = value;
|
|
337
|
+
}
|
|
338
|
+
loadedFiles.push(file);
|
|
339
|
+
}
|
|
285
340
|
function loadEnv() {
|
|
286
341
|
if (loaded) return;
|
|
287
342
|
loaded = true;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
dotenvConfig({ path: homeEnv, override: false });
|
|
296
|
-
}
|
|
343
|
+
loadedFiles = [];
|
|
344
|
+
const cwd = process.cwd();
|
|
345
|
+
const home = process.env.MULTIX_DISABLE_HOME_ENV ? void 0 : os.homedir();
|
|
346
|
+
loadFile(path3.resolve(cwd, ".env"));
|
|
347
|
+
if (home) loadFile(path3.join(home, ".multix", ".env"));
|
|
348
|
+
if (legacyClaudeEnvEnabled()) {
|
|
349
|
+
for (const file of legacyClaudeEnvFiles(cwd, home)) loadLegacyFile(file);
|
|
297
350
|
}
|
|
298
351
|
}
|
|
352
|
+
function getLoadedEnvFiles() {
|
|
353
|
+
return [...loadedFiles];
|
|
354
|
+
}
|
|
299
355
|
function resolveKey(name) {
|
|
300
356
|
return process.env[name] || void 0;
|
|
301
357
|
}
|
|
@@ -475,9 +531,9 @@ var BytePlusClient = class {
|
|
|
475
531
|
}
|
|
476
532
|
apiKey;
|
|
477
533
|
baseUrl;
|
|
478
|
-
async request(
|
|
534
|
+
async request(path50, opts = {}) {
|
|
479
535
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
480
|
-
const url = new URL(base +
|
|
536
|
+
const url = new URL(base + path50);
|
|
481
537
|
if (opts.query) {
|
|
482
538
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
483
539
|
if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
|
|
@@ -495,14 +551,14 @@ var BytePlusClient = class {
|
|
|
495
551
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
496
552
|
});
|
|
497
553
|
}
|
|
498
|
-
get(
|
|
499
|
-
return this.request(
|
|
554
|
+
get(path50, query, logger) {
|
|
555
|
+
return this.request(path50, { method: "GET", query, logger });
|
|
500
556
|
}
|
|
501
|
-
post(
|
|
502
|
-
return this.request(
|
|
557
|
+
post(path50, body, logger) {
|
|
558
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
503
559
|
}
|
|
504
|
-
delete(
|
|
505
|
-
return this.request(
|
|
560
|
+
delete(path50, logger) {
|
|
561
|
+
return this.request(path50, { method: "DELETE", logger });
|
|
506
562
|
}
|
|
507
563
|
};
|
|
508
564
|
function createBytePlusClient() {
|
|
@@ -1242,10 +1298,10 @@ function toRef(kind, resolved, role) {
|
|
|
1242
1298
|
return { kind, url: resolved.dataUrl, mime: resolved.mime, role };
|
|
1243
1299
|
}
|
|
1244
1300
|
async function resolveRefSpec(kind, spec, logger) {
|
|
1245
|
-
const { path:
|
|
1246
|
-
if (kind === "image") return toRef(kind, await resolveImageInput(
|
|
1247
|
-
if (kind === "video") return toRef(kind, await resolveVideoInput(
|
|
1248
|
-
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);
|
|
1249
1305
|
}
|
|
1250
1306
|
async function resolveRefSpecs(specs, logger) {
|
|
1251
1307
|
const out = [];
|
|
@@ -2701,9 +2757,9 @@ var FalClient = class {
|
|
|
2701
2757
|
apiKey;
|
|
2702
2758
|
baseUrl;
|
|
2703
2759
|
/** `path` is appended verbatim to the base queue URL, e.g. "/fal-ai/flux/schnell". */
|
|
2704
|
-
async request(
|
|
2760
|
+
async request(path50, opts = {}) {
|
|
2705
2761
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
2706
|
-
const suffix =
|
|
2762
|
+
const suffix = path50.startsWith("/") ? path50 : `/${path50}`;
|
|
2707
2763
|
return this.requestAbsolute(base + suffix, opts);
|
|
2708
2764
|
}
|
|
2709
2765
|
/** Same auth/headers as `request`, but `url` is used verbatim (e.g. a status_url/response_url returned by submit). */
|
|
@@ -2720,11 +2776,11 @@ var FalClient = class {
|
|
|
2720
2776
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
2721
2777
|
});
|
|
2722
2778
|
}
|
|
2723
|
-
get(
|
|
2724
|
-
return this.request(
|
|
2779
|
+
get(path50, logger) {
|
|
2780
|
+
return this.request(path50, { method: "GET", logger });
|
|
2725
2781
|
}
|
|
2726
|
-
post(
|
|
2727
|
-
return this.request(
|
|
2782
|
+
post(path50, body, logger) {
|
|
2783
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
2728
2784
|
}
|
|
2729
2785
|
getAbsolute(url, logger) {
|
|
2730
2786
|
return this.requestAbsolute(url, { method: "GET", logger });
|
|
@@ -3085,14 +3141,214 @@ function registerFalCommands(program2) {
|
|
|
3085
3141
|
registerFalResultCommand(fal);
|
|
3086
3142
|
}
|
|
3087
3143
|
|
|
3088
|
-
// src/
|
|
3144
|
+
// src/core/stdin-file.ts
|
|
3089
3145
|
init_errors();
|
|
3090
|
-
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
|
+
}
|
|
3091
3347
|
|
|
3092
3348
|
// src/providers/gemini/client.ts
|
|
3093
3349
|
init_errors();
|
|
3094
|
-
import
|
|
3095
|
-
import
|
|
3350
|
+
import fs19 from "fs";
|
|
3351
|
+
import path23 from "path";
|
|
3096
3352
|
|
|
3097
3353
|
// src/providers/gemini/key-pool.ts
|
|
3098
3354
|
init_errors();
|
|
@@ -3158,7 +3414,7 @@ async function withGeminiKey(fn, opts = {}) {
|
|
|
3158
3414
|
}
|
|
3159
3415
|
|
|
3160
3416
|
// src/providers/gemini/task-resolver.ts
|
|
3161
|
-
import
|
|
3417
|
+
import path22 from "path";
|
|
3162
3418
|
var MIME_MAP = {
|
|
3163
3419
|
// Audio
|
|
3164
3420
|
".mp3": "audio/mp3",
|
|
@@ -3199,7 +3455,7 @@ var MIME_MAP = {
|
|
|
3199
3455
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
3200
3456
|
};
|
|
3201
3457
|
function getMimeType(filePath) {
|
|
3202
|
-
const ext =
|
|
3458
|
+
const ext = path22.extname(filePath).toLowerCase();
|
|
3203
3459
|
return MIME_MAP[ext] ?? "application/octet-stream";
|
|
3204
3460
|
}
|
|
3205
3461
|
function requiresProcessingWait(mimeType) {
|
|
@@ -3222,8 +3478,8 @@ async function uploadFile(filePath, opts = {}) {
|
|
|
3222
3478
|
async function uploadFileWithKey(filePath, apiKey, opts) {
|
|
3223
3479
|
const { timeoutMs = 3e5, logger } = opts;
|
|
3224
3480
|
const mimeType = getMimeType(filePath);
|
|
3225
|
-
const fileBytes =
|
|
3226
|
-
const displayName =
|
|
3481
|
+
const fileBytes = fs19.readFileSync(filePath);
|
|
3482
|
+
const displayName = path23.basename(filePath);
|
|
3227
3483
|
const boundary = `multix_${Date.now()}`;
|
|
3228
3484
|
const metaJson = JSON.stringify({ file: { displayName, mimeType } });
|
|
3229
3485
|
const body = Buffer.concat([
|
|
@@ -3340,12 +3596,12 @@ function extractImages(resp) {
|
|
|
3340
3596
|
).map((p) => ({ mimeType: p.inlineData.mimeType, data: p.inlineData.data }));
|
|
3341
3597
|
}
|
|
3342
3598
|
function inlinePart(filePath) {
|
|
3343
|
-
const data =
|
|
3599
|
+
const data = fs19.readFileSync(filePath).toString("base64");
|
|
3344
3600
|
const mimeType = getMimeType(filePath);
|
|
3345
3601
|
return { inlineData: { mimeType, data } };
|
|
3346
3602
|
}
|
|
3347
3603
|
function shouldUseFileApi(filePath) {
|
|
3348
|
-
const stat =
|
|
3604
|
+
const stat = fs19.statSync(filePath);
|
|
3349
3605
|
return stat.size > 20 * 1024 * 1024;
|
|
3350
3606
|
}
|
|
3351
3607
|
function sleep2(ms) {
|
|
@@ -3399,13 +3655,13 @@ function getDefaultModel(task) {
|
|
|
3399
3655
|
}
|
|
3400
3656
|
|
|
3401
3657
|
// src/providers/gemini/result-formatter.ts
|
|
3402
|
-
import
|
|
3403
|
-
import
|
|
3658
|
+
import fs20 from "fs";
|
|
3659
|
+
import path24 from "path";
|
|
3404
3660
|
function formatResults(results, format, outputPath) {
|
|
3405
3661
|
const content = renderFormat(results, format);
|
|
3406
3662
|
if (outputPath) {
|
|
3407
|
-
|
|
3408
|
-
|
|
3663
|
+
fs20.mkdirSync(path24.dirname(path24.resolve(outputPath)), { recursive: true });
|
|
3664
|
+
fs20.writeFileSync(outputPath, content, "utf8");
|
|
3409
3665
|
} else {
|
|
3410
3666
|
process.stdout.write(content);
|
|
3411
3667
|
}
|
|
@@ -3467,17 +3723,17 @@ ${r.response}
|
|
|
3467
3723
|
|
|
3468
3724
|
// src/providers/gemini/commands/analyze.ts
|
|
3469
3725
|
function registerAnalyzeCommand(parent) {
|
|
3470
|
-
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(
|
|
3471
3727
|
async (opts) => {
|
|
3472
3728
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3473
3729
|
const model = opts.model ?? getDefaultModel("analyze");
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
}
|
|
3730
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
3731
|
+
stdinExt: opts.stdinExt
|
|
3732
|
+
});
|
|
3477
3733
|
const results = [];
|
|
3478
|
-
for (let i = 0; i <
|
|
3479
|
-
const filePath =
|
|
3480
|
-
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}`);
|
|
3481
3737
|
try {
|
|
3482
3738
|
const resp = await withGeminiKey(
|
|
3483
3739
|
async (apiKey) => {
|
|
@@ -3504,17 +3760,21 @@ function registerAnalyzeCommand(parent) {
|
|
|
3504
3760
|
{ logger }
|
|
3505
3761
|
);
|
|
3506
3762
|
results.push({
|
|
3507
|
-
file:
|
|
3763
|
+
file: label,
|
|
3508
3764
|
status: "success",
|
|
3509
3765
|
response: extractText(resp)
|
|
3510
3766
|
});
|
|
3511
3767
|
} catch (e) {
|
|
3512
|
-
const msg =
|
|
3513
|
-
|
|
3514
|
-
|
|
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 });
|
|
3515
3774
|
}
|
|
3516
3775
|
}
|
|
3517
3776
|
formatResults(results, opts.format, opts.output);
|
|
3777
|
+
cleanup();
|
|
3518
3778
|
logger.debug(
|
|
3519
3779
|
`Done: ${results.filter((r) => r.status === "success").length}/${results.length} succeeded`
|
|
3520
3780
|
);
|
|
@@ -3525,21 +3785,19 @@ function registerAnalyzeCommand(parent) {
|
|
|
3525
3785
|
}
|
|
3526
3786
|
|
|
3527
3787
|
// src/providers/gemini/commands/extract.ts
|
|
3528
|
-
init_errors();
|
|
3529
|
-
import fs21 from "fs";
|
|
3530
3788
|
function registerExtractCommand(parent) {
|
|
3531
|
-
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(
|
|
3532
3790
|
async (opts) => {
|
|
3533
3791
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
3534
3792
|
const model = opts.model ?? getDefaultModel("extract");
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
}
|
|
3793
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
3794
|
+
stdinExt: opts.stdinExt
|
|
3795
|
+
});
|
|
3538
3796
|
const generationConfig = opts.format === "json" ? { responseMimeType: "application/json" } : void 0;
|
|
3539
3797
|
const results = [];
|
|
3540
|
-
for (let i = 0; i <
|
|
3541
|
-
const filePath =
|
|
3542
|
-
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}`);
|
|
3543
3801
|
try {
|
|
3544
3802
|
const resp = await withGeminiKey(
|
|
3545
3803
|
async (apiKey) => {
|
|
@@ -3565,14 +3823,18 @@ function registerExtractCommand(parent) {
|
|
|
3565
3823
|
},
|
|
3566
3824
|
{ logger }
|
|
3567
3825
|
);
|
|
3568
|
-
results.push({ file:
|
|
3826
|
+
results.push({ file: label, status: "success", response: extractText(resp) });
|
|
3569
3827
|
} catch (e) {
|
|
3570
|
-
const msg =
|
|
3571
|
-
|
|
3572
|
-
|
|
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 });
|
|
3573
3834
|
}
|
|
3574
3835
|
}
|
|
3575
3836
|
formatResults(results, opts.format, opts.output);
|
|
3837
|
+
cleanup();
|
|
3576
3838
|
const failed = results.filter((r) => r.status === "error").length;
|
|
3577
3839
|
if (failed > 0) process.exit(1);
|
|
3578
3840
|
}
|
|
@@ -3583,8 +3845,8 @@ function registerExtractCommand(parent) {
|
|
|
3583
3845
|
init_errors();
|
|
3584
3846
|
|
|
3585
3847
|
// src/providers/gemini/generators/speech.ts
|
|
3586
|
-
import
|
|
3587
|
-
import
|
|
3848
|
+
import fs21 from "fs";
|
|
3849
|
+
import path25 from "path";
|
|
3588
3850
|
|
|
3589
3851
|
// src/providers/gemini/voices.ts
|
|
3590
3852
|
var GEMINI_TTS_VOICES = [
|
|
@@ -3750,12 +4012,12 @@ function extractWavData(wav) {
|
|
|
3750
4012
|
}
|
|
3751
4013
|
function saveSpeech(outBytes, outputFormat, output, logger) {
|
|
3752
4014
|
const outDir = getOutputDir();
|
|
3753
|
-
const dest =
|
|
3754
|
-
|
|
4015
|
+
const dest = path25.join(outDir, `gemini_speech_${Date.now()}.${outputFormat}`);
|
|
4016
|
+
fs21.writeFileSync(dest, outBytes);
|
|
3755
4017
|
logger?.success(`Saved: ${dest} (${(outBytes.length / 1024).toFixed(1)} KB)`);
|
|
3756
4018
|
if (output) {
|
|
3757
|
-
|
|
3758
|
-
|
|
4019
|
+
fs21.mkdirSync(path25.dirname(path25.resolve(output)), { recursive: true });
|
|
4020
|
+
fs21.copyFileSync(dest, output);
|
|
3759
4021
|
logger?.success(`Copied to: ${output}`);
|
|
3760
4022
|
}
|
|
3761
4023
|
return dest;
|
|
@@ -3868,11 +4130,11 @@ function collectSpeakers(value, prev) {
|
|
|
3868
4130
|
|
|
3869
4131
|
// src/providers/gemini/commands/generate-video.ts
|
|
3870
4132
|
init_errors();
|
|
3871
|
-
import
|
|
3872
|
-
import
|
|
4133
|
+
import fs23 from "fs";
|
|
4134
|
+
import path26 from "path";
|
|
3873
4135
|
|
|
3874
4136
|
// src/providers/gemini/veo.ts
|
|
3875
|
-
import
|
|
4137
|
+
import fs22 from "fs";
|
|
3876
4138
|
|
|
3877
4139
|
// src/core/async-poll.ts
|
|
3878
4140
|
init_errors();
|
|
@@ -3939,13 +4201,13 @@ var BASE2 = `https://${API_HOST}/v1beta`;
|
|
|
3939
4201
|
var VEO_IMAGE_TYPES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp"]);
|
|
3940
4202
|
var VEO_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3941
4203
|
function veoImageFromFile(imgPath) {
|
|
3942
|
-
if (!
|
|
4204
|
+
if (!fs22.existsSync(imgPath)) throw new ValidationError(`Image not found: ${imgPath}`);
|
|
3943
4205
|
const detected = getMimeType(imgPath);
|
|
3944
4206
|
const mimeType = detected === "application/octet-stream" ? "image/jpeg" : detected;
|
|
3945
4207
|
if (!VEO_IMAGE_TYPES.has(mimeType)) {
|
|
3946
4208
|
throw new ValidationError(`Veo accepts JPEG, PNG or WebP images: ${imgPath} (${mimeType})`);
|
|
3947
4209
|
}
|
|
3948
|
-
return { bytesBase64Encoded:
|
|
4210
|
+
return { bytesBase64Encoded: fs22.readFileSync(imgPath).toString("base64"), mimeType };
|
|
3949
4211
|
}
|
|
3950
4212
|
function veoFramesFromPaths(paths = []) {
|
|
3951
4213
|
if (paths.length > 2) {
|
|
@@ -4070,7 +4332,7 @@ function registerGenerateVideoCommand(parent) {
|
|
|
4070
4332
|
logger.debug(`Submitting video generation with ${model}...`);
|
|
4071
4333
|
logger.info("This may take 30s\u20136 minutes. Veo requires billing.");
|
|
4072
4334
|
const frames = veoFramesFromPaths(opts.referenceImages);
|
|
4073
|
-
const dest =
|
|
4335
|
+
const dest = path26.join(getOutputDir(), `veo_generated_${Date.now()}.mp4`);
|
|
4074
4336
|
const finalOp = await runVeo(
|
|
4075
4337
|
model,
|
|
4076
4338
|
{
|
|
@@ -4084,8 +4346,8 @@ function registerGenerateVideoCommand(parent) {
|
|
|
4084
4346
|
);
|
|
4085
4347
|
logger.success(`Saved video: ${dest}`);
|
|
4086
4348
|
if (opts.output) {
|
|
4087
|
-
|
|
4088
|
-
|
|
4349
|
+
fs23.mkdirSync(path26.dirname(path26.resolve(opts.output)), { recursive: true });
|
|
4350
|
+
fs23.copyFileSync(dest, opts.output);
|
|
4089
4351
|
logger.success(`Copied to: ${opts.output}`);
|
|
4090
4352
|
}
|
|
4091
4353
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4101,8 +4363,8 @@ Generated video: ${dest}`);
|
|
|
4101
4363
|
|
|
4102
4364
|
// src/providers/gemini/commands/generate.ts
|
|
4103
4365
|
init_errors();
|
|
4104
|
-
import
|
|
4105
|
-
import
|
|
4366
|
+
import fs24 from "fs";
|
|
4367
|
+
import path27 from "path";
|
|
4106
4368
|
function registerGenerateCommand(parent) {
|
|
4107
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(
|
|
4108
4370
|
async (opts) => {
|
|
@@ -4147,8 +4409,8 @@ function registerGenerateCommand(parent) {
|
|
|
4147
4409
|
const outDir = getOutputDir();
|
|
4148
4410
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
4149
4411
|
const fname = `gemini_generated_${Date.now()}_${i}.${ext}`;
|
|
4150
|
-
const dest =
|
|
4151
|
-
|
|
4412
|
+
const dest = path27.join(outDir, fname);
|
|
4413
|
+
fs24.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
4152
4414
|
savedFiles.push(dest);
|
|
4153
4415
|
logger.success(`Saved: ${dest}`);
|
|
4154
4416
|
}
|
|
@@ -4163,8 +4425,8 @@ function registerGenerateCommand(parent) {
|
|
|
4163
4425
|
process.exit(1);
|
|
4164
4426
|
}
|
|
4165
4427
|
if (opts.output && savedFiles[0]) {
|
|
4166
|
-
|
|
4167
|
-
|
|
4428
|
+
fs24.mkdirSync(path27.dirname(path27.resolve(opts.output)), { recursive: true });
|
|
4429
|
+
fs24.copyFileSync(savedFiles[0], opts.output);
|
|
4168
4430
|
logger.success(`Copied to: ${opts.output}`);
|
|
4169
4431
|
}
|
|
4170
4432
|
const finalImages = finalizeGeneratedImages(savedFiles, { ...opts, logger });
|
|
@@ -4176,8 +4438,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4176
4438
|
}
|
|
4177
4439
|
|
|
4178
4440
|
// src/providers/gemini/commands/image-to-image.ts
|
|
4179
|
-
import
|
|
4180
|
-
import
|
|
4441
|
+
import fs25 from "fs";
|
|
4442
|
+
import path28 from "path";
|
|
4181
4443
|
var SOFT_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
4182
4444
|
var HARD_LIMIT_BYTES = 7 * 1024 * 1024;
|
|
4183
4445
|
function registerGeminiImageToImageCommand(parent) {
|
|
@@ -4238,14 +4500,14 @@ function registerGeminiImageToImageCommand(parent) {
|
|
|
4238
4500
|
const img = images[i];
|
|
4239
4501
|
if (!img) continue;
|
|
4240
4502
|
const ext = img.mimeType.includes("png") ? "png" : "jpg";
|
|
4241
|
-
const dest =
|
|
4242
|
-
|
|
4503
|
+
const dest = path28.join(outDir, `gemini-i2i-${ts}-${i + 1}.${ext}`);
|
|
4504
|
+
fs25.writeFileSync(dest, Buffer.from(img.data, "base64"));
|
|
4243
4505
|
saved.push(dest);
|
|
4244
4506
|
logger.success(`Saved: ${dest}`);
|
|
4245
4507
|
}
|
|
4246
4508
|
if (opts.output && saved[0]) {
|
|
4247
|
-
|
|
4248
|
-
|
|
4509
|
+
fs25.mkdirSync(path28.dirname(path28.resolve(opts.output)), { recursive: true });
|
|
4510
|
+
fs25.copyFileSync(saved[0], opts.output);
|
|
4249
4511
|
logger.success(`Copied to: ${opts.output}`);
|
|
4250
4512
|
}
|
|
4251
4513
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4273,7 +4535,7 @@ function sniffImageMime(buf, url) {
|
|
|
4273
4535
|
if (buf.length >= 12 && buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
4274
4536
|
return "image/webp";
|
|
4275
4537
|
}
|
|
4276
|
-
const ext =
|
|
4538
|
+
const ext = path28.extname(new URL(url).pathname).toLowerCase();
|
|
4277
4539
|
if (ext === ".png") return "image/png";
|
|
4278
4540
|
if (ext === ".webp") return "image/webp";
|
|
4279
4541
|
if (ext === ".gif") return "image/gif";
|
|
@@ -4282,8 +4544,8 @@ function sniffImageMime(buf, url) {
|
|
|
4282
4544
|
|
|
4283
4545
|
// src/providers/gemini/commands/image-to-video.ts
|
|
4284
4546
|
init_errors();
|
|
4285
|
-
import
|
|
4286
|
-
import
|
|
4547
|
+
import fs26 from "fs";
|
|
4548
|
+
import path29 from "path";
|
|
4287
4549
|
function registerGeminiImageToVideoCommand(parent) {
|
|
4288
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(
|
|
4289
4551
|
async (imagePath, opts) => {
|
|
@@ -4303,7 +4565,7 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4303
4565
|
logger.info(
|
|
4304
4566
|
"Submitting image-to-video. This may take 30s\u20136 minutes. Veo requires billing."
|
|
4305
4567
|
);
|
|
4306
|
-
const dest =
|
|
4568
|
+
const dest = path29.join(getOutputDir(), `veo_i2v_${Date.now()}.mp4`);
|
|
4307
4569
|
const finalOp = await runVeo(
|
|
4308
4570
|
model,
|
|
4309
4571
|
{
|
|
@@ -4318,8 +4580,8 @@ function registerGeminiImageToVideoCommand(parent) {
|
|
|
4318
4580
|
);
|
|
4319
4581
|
logger.success(`Saved video: ${dest}`);
|
|
4320
4582
|
if (opts.output) {
|
|
4321
|
-
|
|
4322
|
-
|
|
4583
|
+
fs26.mkdirSync(path29.dirname(path29.resolve(opts.output)), { recursive: true });
|
|
4584
|
+
fs26.copyFileSync(dest, opts.output);
|
|
4323
4585
|
logger.success(`Copied to: ${opts.output}`);
|
|
4324
4586
|
}
|
|
4325
4587
|
await maybeDownloadThumb(finalOp.response, dest, {
|
|
@@ -4334,20 +4596,21 @@ Generated video: ${dest}`);
|
|
|
4334
4596
|
}
|
|
4335
4597
|
|
|
4336
4598
|
// src/providers/gemini/commands/transcribe.ts
|
|
4337
|
-
init_errors();
|
|
4338
|
-
import fs28 from "fs";
|
|
4339
4599
|
function registerTranscribeCommand2(parent) {
|
|
4340
|
-
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(
|
|
4341
4604
|
async (opts) => {
|
|
4342
4605
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
4343
4606
|
const model = opts.model ?? getDefaultModel("transcribe");
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
}
|
|
4607
|
+
const { inputs, cleanup } = await resolveFileInputs(opts.files, {
|
|
4608
|
+
stdinExt: opts.stdinExt
|
|
4609
|
+
});
|
|
4347
4610
|
const results = [];
|
|
4348
|
-
for (let i = 0; i <
|
|
4349
|
-
const filePath =
|
|
4350
|
-
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}`);
|
|
4351
4614
|
try {
|
|
4352
4615
|
const resp = await withGeminiKey(
|
|
4353
4616
|
async (apiKey) => {
|
|
@@ -4374,17 +4637,21 @@ function registerTranscribeCommand2(parent) {
|
|
|
4374
4637
|
{ logger }
|
|
4375
4638
|
);
|
|
4376
4639
|
results.push({
|
|
4377
|
-
file:
|
|
4640
|
+
file: label,
|
|
4378
4641
|
status: "success",
|
|
4379
4642
|
response: extractText(resp)
|
|
4380
4643
|
});
|
|
4381
4644
|
} catch (e) {
|
|
4382
|
-
const msg =
|
|
4383
|
-
|
|
4384
|
-
|
|
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 });
|
|
4385
4651
|
}
|
|
4386
4652
|
}
|
|
4387
4653
|
formatResults(results, opts.format, opts.output);
|
|
4654
|
+
cleanup();
|
|
4388
4655
|
const failed = results.filter((r) => r.status === "error").length;
|
|
4389
4656
|
if (failed > 0) process.exit(1);
|
|
4390
4657
|
}
|
|
@@ -4407,8 +4674,8 @@ function registerGeminiCommands(program2) {
|
|
|
4407
4674
|
}
|
|
4408
4675
|
|
|
4409
4676
|
// src/providers/leonardo/commands/generate.ts
|
|
4410
|
-
import
|
|
4411
|
-
import
|
|
4677
|
+
import fs27 from "fs";
|
|
4678
|
+
import path30 from "path";
|
|
4412
4679
|
|
|
4413
4680
|
// src/providers/leonardo/client.ts
|
|
4414
4681
|
init_errors();
|
|
@@ -4481,11 +4748,11 @@ var LeonardoClient = class {
|
|
|
4481
4748
|
}
|
|
4482
4749
|
apiKey;
|
|
4483
4750
|
baseUrl;
|
|
4484
|
-
async request(
|
|
4751
|
+
async request(path50, opts = {}) {
|
|
4485
4752
|
const base = this.baseUrl.replace(/\/$/, "");
|
|
4486
|
-
const v2Override =
|
|
4753
|
+
const v2Override = path50.startsWith("v2:");
|
|
4487
4754
|
const finalBase = v2Override ? base.replace(/\/v1$/, "/v2") : base;
|
|
4488
|
-
const finalPath = v2Override ?
|
|
4755
|
+
const finalPath = v2Override ? path50.slice(3) : path50;
|
|
4489
4756
|
const url = new URL(finalBase + finalPath);
|
|
4490
4757
|
if (opts.query) {
|
|
4491
4758
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
@@ -4504,14 +4771,14 @@ var LeonardoClient = class {
|
|
|
4504
4771
|
timeoutMs: opts.timeoutMs ?? 12e4
|
|
4505
4772
|
});
|
|
4506
4773
|
}
|
|
4507
|
-
get(
|
|
4508
|
-
return this.request(
|
|
4774
|
+
get(path50, query, logger) {
|
|
4775
|
+
return this.request(path50, { method: "GET", query, logger });
|
|
4509
4776
|
}
|
|
4510
|
-
post(
|
|
4511
|
-
return this.request(
|
|
4777
|
+
post(path50, body, logger) {
|
|
4778
|
+
return this.request(path50, { method: "POST", body, logger });
|
|
4512
4779
|
}
|
|
4513
|
-
delete(
|
|
4514
|
-
return this.request(
|
|
4780
|
+
delete(path50, logger) {
|
|
4781
|
+
return this.request(path50, { method: "DELETE", logger });
|
|
4515
4782
|
}
|
|
4516
4783
|
};
|
|
4517
4784
|
function createLeonardoClient() {
|
|
@@ -4522,7 +4789,7 @@ function createLeonardoClient() {
|
|
|
4522
4789
|
function extFromUrl2(url, fallback = ".png") {
|
|
4523
4790
|
try {
|
|
4524
4791
|
const u = new URL(url);
|
|
4525
|
-
const e =
|
|
4792
|
+
const e = path30.extname(u.pathname);
|
|
4526
4793
|
return e.length > 0 ? e : fallback;
|
|
4527
4794
|
} catch {
|
|
4528
4795
|
return fallback;
|
|
@@ -4607,7 +4874,7 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4607
4874
|
if (!img) continue;
|
|
4608
4875
|
const ext = extFromUrl2(img.url);
|
|
4609
4876
|
const filename = autoFilename(generationId, i + 1, ext);
|
|
4610
|
-
const filepath =
|
|
4877
|
+
const filepath = path30.join(outDir, filename);
|
|
4611
4878
|
try {
|
|
4612
4879
|
await downloadFile(img.url, filepath);
|
|
4613
4880
|
saved.push(filepath);
|
|
@@ -4617,9 +4884,9 @@ function registerLeonardoGenerateCommand(parent) {
|
|
|
4617
4884
|
}
|
|
4618
4885
|
}
|
|
4619
4886
|
if (opts.output && saved[0]) {
|
|
4620
|
-
const dest =
|
|
4621
|
-
|
|
4622
|
-
|
|
4887
|
+
const dest = path30.resolve(opts.output);
|
|
4888
|
+
fs27.mkdirSync(path30.dirname(dest), { recursive: true });
|
|
4889
|
+
fs27.copyFileSync(saved[0], dest);
|
|
4623
4890
|
logger.success(`Copied to ${dest}`);
|
|
4624
4891
|
}
|
|
4625
4892
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4631,11 +4898,11 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4631
4898
|
}
|
|
4632
4899
|
|
|
4633
4900
|
// src/providers/leonardo/commands/image-to-image.ts
|
|
4634
|
-
import
|
|
4635
|
-
import
|
|
4901
|
+
import fs28 from "fs";
|
|
4902
|
+
import path31 from "path";
|
|
4636
4903
|
function extFromUrl3(url, fallback = ".png") {
|
|
4637
4904
|
try {
|
|
4638
|
-
return
|
|
4905
|
+
return path31.extname(new URL(url).pathname) || fallback;
|
|
4639
4906
|
} catch {
|
|
4640
4907
|
return fallback;
|
|
4641
4908
|
}
|
|
@@ -4697,7 +4964,7 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4697
4964
|
if (!img) continue;
|
|
4698
4965
|
const ext = extFromUrl3(img.url);
|
|
4699
4966
|
const filename = `leonardo-i2i-${generationId.slice(0, 8)}-${String(i + 1).padStart(2, "0")}${ext}`;
|
|
4700
|
-
const filepath =
|
|
4967
|
+
const filepath = path31.join(outDir, filename);
|
|
4701
4968
|
try {
|
|
4702
4969
|
await downloadFile(img.url, filepath);
|
|
4703
4970
|
saved.push(filepath);
|
|
@@ -4707,9 +4974,9 @@ function registerLeonardoImageToImageCommand(parent) {
|
|
|
4707
4974
|
}
|
|
4708
4975
|
}
|
|
4709
4976
|
if (opts.output && saved[0]) {
|
|
4710
|
-
const dest =
|
|
4711
|
-
|
|
4712
|
-
|
|
4977
|
+
const dest = path31.resolve(opts.output);
|
|
4978
|
+
fs28.mkdirSync(path31.dirname(dest), { recursive: true });
|
|
4979
|
+
fs28.copyFileSync(saved[0], dest);
|
|
4713
4980
|
logger.success(`Copied to ${dest}`);
|
|
4714
4981
|
}
|
|
4715
4982
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -4721,7 +4988,7 @@ Generated ${finalImages.length} image(s):`);
|
|
|
4721
4988
|
}
|
|
4722
4989
|
|
|
4723
4990
|
// src/providers/leonardo/commands/video.ts
|
|
4724
|
-
import
|
|
4991
|
+
import path32 from "path";
|
|
4725
4992
|
function v2DimsFor(resolution) {
|
|
4726
4993
|
if (resolution === "RESOLUTION_1080") return { width: 1920, height: 1080 };
|
|
4727
4994
|
if (resolution === "RESOLUTION_480") return { width: 854, height: 480 };
|
|
@@ -4832,7 +5099,7 @@ async function pollAndDownload(client, jobId, opts) {
|
|
|
4832
5099
|
logger.error(`No video URL in completed generation: ${JSON.stringify(gen)}`);
|
|
4833
5100
|
process.exit(1);
|
|
4834
5101
|
}
|
|
4835
|
-
const outPath = opts.output ??
|
|
5102
|
+
const outPath = opts.output ?? path32.join(getOutputDir(), `${opts.basename}-${jobId.slice(0, 8)}.mp4`);
|
|
4836
5103
|
await downloadFile(videoUrl2, outPath);
|
|
4837
5104
|
logger.success(`Saved ${outPath}`);
|
|
4838
5105
|
await maybeDownloadThumb(gen, outPath, {
|
|
@@ -4938,7 +5205,7 @@ function registerLeonardoModelsCommand(parent) {
|
|
|
4938
5205
|
}
|
|
4939
5206
|
|
|
4940
5207
|
// src/providers/leonardo/commands/status.ts
|
|
4941
|
-
import
|
|
5208
|
+
import path33 from "path";
|
|
4942
5209
|
var VIDEO_KEYS = ["motionMP4URL", "url"];
|
|
4943
5210
|
var IMAGE_EXT_RE2 = /\.(jpe?g|png|webp|gif)(\?|$)/i;
|
|
4944
5211
|
var VIDEO_EXT_RE = /\.(mp4|webm|mov|m4v)(\?|$)/i;
|
|
@@ -5003,7 +5270,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
5003
5270
|
const outDir = getOutputDir();
|
|
5004
5271
|
let idx = 0;
|
|
5005
5272
|
for (const url of videos) {
|
|
5006
|
-
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`);
|
|
5007
5274
|
await downloadFile(url, dest);
|
|
5008
5275
|
logger.success(`Saved ${dest}`);
|
|
5009
5276
|
await maybeDownloadThumb(gen, dest, {
|
|
@@ -5016,7 +5283,7 @@ function registerLeonardoStatusCommand(parent) {
|
|
|
5016
5283
|
for (const url of images) {
|
|
5017
5284
|
const m = url.match(IMAGE_EXT_RE2);
|
|
5018
5285
|
const ext = m?.[1] ? m[1].toLowerCase().replace("jpeg", "jpg") : "png";
|
|
5019
|
-
const dest =
|
|
5286
|
+
const dest = path33.join(outDir, `leonardo-${generationId.slice(0, 8)}-${idx}.${ext}`);
|
|
5020
5287
|
await downloadFile(url, dest);
|
|
5021
5288
|
logger.success(`Saved ${dest}`);
|
|
5022
5289
|
idx++;
|
|
@@ -5173,8 +5440,8 @@ function sleep3(ms) {
|
|
|
5173
5440
|
}
|
|
5174
5441
|
|
|
5175
5442
|
// src/providers/minimax/generators/music.ts
|
|
5176
|
-
import
|
|
5177
|
-
import
|
|
5443
|
+
import fs29 from "fs";
|
|
5444
|
+
import path34 from "path";
|
|
5178
5445
|
async function generateMinimaxMusic(opts) {
|
|
5179
5446
|
const {
|
|
5180
5447
|
apiKey,
|
|
@@ -5219,14 +5486,14 @@ async function generateMinimaxMusic(opts) {
|
|
|
5219
5486
|
}
|
|
5220
5487
|
const durationMs = resp.extra_info?.music_duration ?? 0;
|
|
5221
5488
|
const outDir = getOutputDir();
|
|
5222
|
-
const dest =
|
|
5489
|
+
const dest = path34.join(outDir, `minimax_music_${Date.now()}.${outputFormat}`);
|
|
5223
5490
|
try {
|
|
5224
5491
|
if (audioData.startsWith("http")) {
|
|
5225
5492
|
const bytes = await fetchBytes(audioData);
|
|
5226
|
-
|
|
5493
|
+
fs29.writeFileSync(dest, bytes);
|
|
5227
5494
|
} else {
|
|
5228
5495
|
const audioBytes = Buffer.from(audioData, "hex");
|
|
5229
|
-
|
|
5496
|
+
fs29.writeFileSync(dest, audioBytes);
|
|
5230
5497
|
}
|
|
5231
5498
|
} catch (e) {
|
|
5232
5499
|
return {
|
|
@@ -5237,8 +5504,8 @@ async function generateMinimaxMusic(opts) {
|
|
|
5237
5504
|
const durSec = durationMs / 1e3;
|
|
5238
5505
|
logger?.success(`Saved: ${dest} (${durSec.toFixed(1)}s)`);
|
|
5239
5506
|
if (output) {
|
|
5240
|
-
|
|
5241
|
-
|
|
5507
|
+
fs29.mkdirSync(path34.dirname(path34.resolve(output)), { recursive: true });
|
|
5508
|
+
fs29.copyFileSync(dest, output);
|
|
5242
5509
|
}
|
|
5243
5510
|
return { status: "success", generatedAudio: dest, durationMs, model };
|
|
5244
5511
|
}
|
|
@@ -5312,8 +5579,8 @@ Generated music: ${result.generatedAudio}`);
|
|
|
5312
5579
|
init_errors();
|
|
5313
5580
|
|
|
5314
5581
|
// src/providers/minimax/generators/speech.ts
|
|
5315
|
-
import
|
|
5316
|
-
import
|
|
5582
|
+
import fs30 from "fs";
|
|
5583
|
+
import path35 from "path";
|
|
5317
5584
|
async function generateMinimaxSpeech(opts) {
|
|
5318
5585
|
const {
|
|
5319
5586
|
apiKey,
|
|
@@ -5360,12 +5627,12 @@ async function generateMinimaxSpeech(opts) {
|
|
|
5360
5627
|
const audioBytes = Buffer.from(audioHex, "hex");
|
|
5361
5628
|
const ext = ["mp3", "wav", "flac"].includes(outputFormat) ? outputFormat : "mp3";
|
|
5362
5629
|
const outDir = getOutputDir();
|
|
5363
|
-
const dest =
|
|
5364
|
-
|
|
5630
|
+
const dest = path35.join(outDir, `minimax_speech_${Date.now()}.${ext}`);
|
|
5631
|
+
fs30.writeFileSync(dest, audioBytes);
|
|
5365
5632
|
logger?.success(`Saved: ${dest} (${(audioBytes.length / 1024).toFixed(1)} KB)`);
|
|
5366
5633
|
if (output) {
|
|
5367
|
-
|
|
5368
|
-
|
|
5634
|
+
fs30.mkdirSync(path35.dirname(path35.resolve(output)), { recursive: true });
|
|
5635
|
+
fs30.copyFileSync(dest, output);
|
|
5369
5636
|
}
|
|
5370
5637
|
return { status: "success", generatedAudio: dest, model };
|
|
5371
5638
|
}
|
|
@@ -5412,8 +5679,8 @@ Generated audio: ${result.generatedAudio}`);
|
|
|
5412
5679
|
init_errors();
|
|
5413
5680
|
|
|
5414
5681
|
// src/providers/minimax/generators/video.ts
|
|
5415
|
-
import
|
|
5416
|
-
import
|
|
5682
|
+
import fs31 from "fs";
|
|
5683
|
+
import path36 from "path";
|
|
5417
5684
|
async function generateMinimaxVideo(opts) {
|
|
5418
5685
|
const {
|
|
5419
5686
|
apiKey,
|
|
@@ -5459,18 +5726,18 @@ async function generateMinimaxVideo(opts) {
|
|
|
5459
5726
|
return { status: "error", error: `No file_id in poll result: ${JSON.stringify(pollResult)}` };
|
|
5460
5727
|
}
|
|
5461
5728
|
const outDir = getOutputDir();
|
|
5462
|
-
const dest =
|
|
5729
|
+
const dest = path36.join(outDir, `minimax_video_${Date.now()}.mp4`);
|
|
5463
5730
|
try {
|
|
5464
5731
|
await downloadMinimaxFile(fileId, apiKey, dest, logger);
|
|
5465
5732
|
} catch (e) {
|
|
5466
5733
|
return { status: "error", error: e instanceof Error ? e.message : String(e) };
|
|
5467
5734
|
}
|
|
5468
5735
|
const elapsed = (Date.now() - start) / 1e3;
|
|
5469
|
-
const stat =
|
|
5736
|
+
const stat = fs31.statSync(dest);
|
|
5470
5737
|
const fileSizeMb = stat.size / (1024 * 1024);
|
|
5471
5738
|
if (output) {
|
|
5472
|
-
|
|
5473
|
-
|
|
5739
|
+
fs31.mkdirSync(path36.dirname(path36.resolve(output)), { recursive: true });
|
|
5740
|
+
fs31.copyFileSync(dest, output);
|
|
5474
5741
|
}
|
|
5475
5742
|
await maybeDownloadThumb(pollResult, dest, {
|
|
5476
5743
|
skip: thumb === false,
|
|
@@ -5532,8 +5799,8 @@ Generated video: ${result.generatedVideo}`);
|
|
|
5532
5799
|
|
|
5533
5800
|
// src/providers/minimax/generators/image.ts
|
|
5534
5801
|
init_errors();
|
|
5535
|
-
import
|
|
5536
|
-
import
|
|
5802
|
+
import fs32 from "fs";
|
|
5803
|
+
import path37 from "path";
|
|
5537
5804
|
async function generateMinimaxImage(opts) {
|
|
5538
5805
|
const {
|
|
5539
5806
|
apiKey,
|
|
@@ -5571,8 +5838,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5571
5838
|
try {
|
|
5572
5839
|
const bytes = await fetchBytes(url);
|
|
5573
5840
|
const fname = `minimax_image_${Date.now()}_${i}.png`;
|
|
5574
|
-
const dest =
|
|
5575
|
-
|
|
5841
|
+
const dest = path37.join(outDir, fname);
|
|
5842
|
+
fs32.writeFileSync(dest, bytes);
|
|
5576
5843
|
savedFiles.push(dest);
|
|
5577
5844
|
logger?.success(`Saved: ${dest}`);
|
|
5578
5845
|
} catch (e) {
|
|
@@ -5583,8 +5850,8 @@ async function generateMinimaxImage(opts) {
|
|
|
5583
5850
|
}
|
|
5584
5851
|
}
|
|
5585
5852
|
if (output && savedFiles[0]) {
|
|
5586
|
-
|
|
5587
|
-
|
|
5853
|
+
fs32.mkdirSync(path37.dirname(path37.resolve(output)), { recursive: true });
|
|
5854
|
+
fs32.copyFileSync(savedFiles[0], output);
|
|
5588
5855
|
}
|
|
5589
5856
|
return { status: "success", generatedImages: savedFiles, model };
|
|
5590
5857
|
}
|
|
@@ -5623,8 +5890,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
5623
5890
|
|
|
5624
5891
|
// src/providers/minimax/commands/image-to-image.ts
|
|
5625
5892
|
init_errors();
|
|
5626
|
-
import
|
|
5627
|
-
import
|
|
5893
|
+
import fs33 from "fs";
|
|
5894
|
+
import path38 from "path";
|
|
5628
5895
|
function registerMinimaxImageToImageCommand(parent) {
|
|
5629
5896
|
parent.command("image-to-image").alias("i2i").description(
|
|
5630
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."
|
|
@@ -5679,8 +5946,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5679
5946
|
if (!url) continue;
|
|
5680
5947
|
try {
|
|
5681
5948
|
const bytes = await fetchBytes(url);
|
|
5682
|
-
const dest =
|
|
5683
|
-
|
|
5949
|
+
const dest = path38.join(outDir, `minimax-i2i-${ts}-${i + 1}.png`);
|
|
5950
|
+
fs33.writeFileSync(dest, bytes);
|
|
5684
5951
|
saved.push(dest);
|
|
5685
5952
|
logger.success(`Saved: ${dest}`);
|
|
5686
5953
|
} catch (e) {
|
|
@@ -5691,8 +5958,8 @@ function registerMinimaxImageToImageCommand(parent) {
|
|
|
5691
5958
|
}
|
|
5692
5959
|
}
|
|
5693
5960
|
if (opts.output && saved[0]) {
|
|
5694
|
-
|
|
5695
|
-
|
|
5961
|
+
fs33.mkdirSync(path38.dirname(path38.resolve(opts.output)), { recursive: true });
|
|
5962
|
+
fs33.copyFileSync(saved[0], opts.output);
|
|
5696
5963
|
logger.success(`Copied to: ${opts.output}`);
|
|
5697
5964
|
}
|
|
5698
5965
|
const finalImages = finalizeGeneratedImages(saved, { ...opts, logger });
|
|
@@ -5826,12 +6093,12 @@ function validateKnownSpeakerPairing(names = [], refs = []) {
|
|
|
5826
6093
|
}
|
|
5827
6094
|
|
|
5828
6095
|
// src/providers/openai/openai-audio.ts
|
|
5829
|
-
import
|
|
5830
|
-
import
|
|
6096
|
+
import fs35 from "fs";
|
|
6097
|
+
import path40 from "path";
|
|
5831
6098
|
|
|
5832
6099
|
// src/providers/openai/client.ts
|
|
5833
|
-
import
|
|
5834
|
-
import
|
|
6100
|
+
import fs34 from "fs";
|
|
6101
|
+
import path39 from "path";
|
|
5835
6102
|
init_errors();
|
|
5836
6103
|
var DEFAULT_TIMEOUT_MS3 = 24e4;
|
|
5837
6104
|
function requireOpenAIKey() {
|
|
@@ -5897,10 +6164,10 @@ async function postOpenAIBinary(pathname, body, apiKey, timeoutMs = DEFAULT_TIME
|
|
|
5897
6164
|
return Buffer.from(await res.arrayBuffer());
|
|
5898
6165
|
}
|
|
5899
6166
|
function fileFromPath(filePath) {
|
|
5900
|
-
const abs =
|
|
5901
|
-
if (!
|
|
5902
|
-
const bytes =
|
|
5903
|
-
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));
|
|
5904
6171
|
}
|
|
5905
6172
|
|
|
5906
6173
|
// src/providers/openai/openai-audio.ts
|
|
@@ -5939,7 +6206,7 @@ async function resolveAudioReferenceDataUrl(ref) {
|
|
|
5939
6206
|
}
|
|
5940
6207
|
function audioMimeFromReference(ref) {
|
|
5941
6208
|
const pathname = new URL(ref).pathname;
|
|
5942
|
-
const ext =
|
|
6209
|
+
const ext = path40.extname(pathname).toLowerCase();
|
|
5943
6210
|
switch (ext) {
|
|
5944
6211
|
case ".mp3":
|
|
5945
6212
|
return "audio/mpeg";
|
|
@@ -5958,12 +6225,12 @@ function audioMimeFromReference(ref) {
|
|
|
5958
6225
|
async function generateOpenAISpeech(opts) {
|
|
5959
6226
|
const bytes = await postOpenAIBinary("/audio/speech", buildSpeechBody(opts), requireOpenAIKey());
|
|
5960
6227
|
const ext = extFromAudioFormat(opts.outputFormat);
|
|
5961
|
-
const dest =
|
|
5962
|
-
|
|
6228
|
+
const dest = path40.join(getOutputDir(), `openai-speech-${Date.now()}.${ext}`);
|
|
6229
|
+
fs35.writeFileSync(dest, bytes);
|
|
5963
6230
|
opts.logger?.success(`Saved: ${dest} (${(bytes.length / 1024).toFixed(1)} KB)`);
|
|
5964
6231
|
if (opts.output) {
|
|
5965
|
-
|
|
5966
|
-
|
|
6232
|
+
fs35.mkdirSync(path40.dirname(path40.resolve(opts.output)), { recursive: true });
|
|
6233
|
+
fs35.copyFileSync(dest, opts.output);
|
|
5967
6234
|
}
|
|
5968
6235
|
return dest;
|
|
5969
6236
|
}
|
|
@@ -5985,9 +6252,9 @@ async function transcribeOpenAIAudio(opts) {
|
|
|
5985
6252
|
);
|
|
5986
6253
|
const text = typeof data.text === "string" ? data.text : void 0;
|
|
5987
6254
|
const ext = opts.format === "text" ? "txt" : "json";
|
|
5988
|
-
const dest = opts.output ?
|
|
5989
|
-
|
|
5990
|
-
|
|
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));
|
|
5991
6258
|
opts.logger?.success(`Saved transcript: ${dest}`);
|
|
5992
6259
|
return { path: dest, text };
|
|
5993
6260
|
}
|
|
@@ -6014,9 +6281,9 @@ Generated audio: ${dest}`);
|
|
|
6014
6281
|
}
|
|
6015
6282
|
|
|
6016
6283
|
// src/providers/openai/codex-image-driver.ts
|
|
6017
|
-
import
|
|
6018
|
-
import
|
|
6019
|
-
import
|
|
6284
|
+
import fs36 from "fs";
|
|
6285
|
+
import os3 from "os";
|
|
6286
|
+
import path41 from "path";
|
|
6020
6287
|
import { execa } from "execa";
|
|
6021
6288
|
init_errors();
|
|
6022
6289
|
var defaultRunner = async (cmd, args, opts) => {
|
|
@@ -6065,12 +6332,12 @@ function sanitizeCodexEnv() {
|
|
|
6065
6332
|
async function runCodexImageGeneration(opts) {
|
|
6066
6333
|
const runner = opts.runner ?? defaultRunner;
|
|
6067
6334
|
const ext = extFromImageFormat(opts.outputFormat);
|
|
6068
|
-
const dest = opts.output ?
|
|
6069
|
-
|
|
6070
|
-
const tmp =
|
|
6071
|
-
const schema =
|
|
6072
|
-
const result =
|
|
6073
|
-
|
|
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(
|
|
6074
6341
|
schema,
|
|
6075
6342
|
JSON.stringify({
|
|
6076
6343
|
type: "object",
|
|
@@ -6100,7 +6367,7 @@ async function runCodexImageGeneration(opts) {
|
|
|
6100
6367
|
if (res.exitCode !== 0) {
|
|
6101
6368
|
throw new ProviderError(`Codex image driver failed: ${res.stderr || res.stdout}`, "openai");
|
|
6102
6369
|
}
|
|
6103
|
-
if (!
|
|
6370
|
+
if (!fs36.existsSync(dest) || fs36.statSync(dest).size === 0) {
|
|
6104
6371
|
throw new ProviderError(`Codex did not create a non-empty image at ${dest}`, "openai");
|
|
6105
6372
|
}
|
|
6106
6373
|
return { path: dest };
|
|
@@ -6117,8 +6384,8 @@ function buildCodexPrompt(userPrompt, outputPath) {
|
|
|
6117
6384
|
|
|
6118
6385
|
// src/providers/openai/openai-image.ts
|
|
6119
6386
|
init_errors();
|
|
6120
|
-
import
|
|
6121
|
-
import
|
|
6387
|
+
import fs37 from "fs";
|
|
6388
|
+
import path42 from "path";
|
|
6122
6389
|
function buildImageGenerationBody(opts) {
|
|
6123
6390
|
return {
|
|
6124
6391
|
model: opts.model ?? DEFAULT_OPENAI_IMAGE_MODEL,
|
|
@@ -6151,14 +6418,14 @@ function saveOpenAIImages(opts) {
|
|
|
6151
6418
|
for (let i = 0; i < items.length; i++) {
|
|
6152
6419
|
const b64 = items[i]?.b64_json;
|
|
6153
6420
|
if (!b64) throw new ProviderError("No b64_json in OpenAI image response", "openai");
|
|
6154
|
-
const dest =
|
|
6155
|
-
|
|
6421
|
+
const dest = path42.join(outDir, `openai-${opts.task}-${ts}-${i + 1}.${ext}`);
|
|
6422
|
+
fs37.writeFileSync(dest, Buffer.from(b64, "base64"));
|
|
6156
6423
|
saved.push(dest);
|
|
6157
6424
|
opts.logger?.success(`Saved: ${dest}`);
|
|
6158
6425
|
}
|
|
6159
6426
|
if (opts.output && saved[0]) {
|
|
6160
|
-
|
|
6161
|
-
|
|
6427
|
+
fs37.mkdirSync(path42.dirname(path42.resolve(opts.output)), { recursive: true });
|
|
6428
|
+
fs37.copyFileSync(saved[0], opts.output);
|
|
6162
6429
|
opts.logger?.success(`Copied to: ${opts.output}`);
|
|
6163
6430
|
}
|
|
6164
6431
|
return saved;
|
|
@@ -6167,7 +6434,7 @@ async function resolveImageRefBytes(ref) {
|
|
|
6167
6434
|
if (isHttpUrl(ref)) {
|
|
6168
6435
|
const bytes2 = await fetchBytes(ref);
|
|
6169
6436
|
const pathname = new URL(ref).pathname;
|
|
6170
|
-
const filename =
|
|
6437
|
+
const filename = path42.basename(pathname) || "reference.png";
|
|
6171
6438
|
return { filename, bytes: bytes2, mime: sniffImageMime2(bytes2, filename) };
|
|
6172
6439
|
}
|
|
6173
6440
|
const file = fileFromPath(ref);
|
|
@@ -6212,7 +6479,7 @@ function sniffImageMime2(buf, name) {
|
|
|
6212
6479
|
if (buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71) return "image/png";
|
|
6213
6480
|
if (buf.slice(0, 4).toString() === "RIFF" && buf.slice(8, 12).toString() === "WEBP")
|
|
6214
6481
|
return "image/webp";
|
|
6215
|
-
const ext =
|
|
6482
|
+
const ext = path42.extname(name).toLowerCase();
|
|
6216
6483
|
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
6217
6484
|
if (ext === ".webp") return "image/webp";
|
|
6218
6485
|
return "image/png";
|
|
@@ -6356,8 +6623,8 @@ function registerOpenAICommands(program2) {
|
|
|
6356
6623
|
}
|
|
6357
6624
|
|
|
6358
6625
|
// src/providers/openrouter/client.ts
|
|
6359
|
-
import
|
|
6360
|
-
import
|
|
6626
|
+
import fs38 from "fs";
|
|
6627
|
+
import path43 from "path";
|
|
6361
6628
|
init_errors();
|
|
6362
6629
|
|
|
6363
6630
|
// src/providers/openrouter/payload.ts
|
|
@@ -6521,8 +6788,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6521
6788
|
if (!url) continue;
|
|
6522
6789
|
try {
|
|
6523
6790
|
const bytes = await fetchBytes(url);
|
|
6524
|
-
const dest =
|
|
6525
|
-
|
|
6791
|
+
const dest = path43.join(outDir, `openrouter_image_${Date.now()}_${i}.png`);
|
|
6792
|
+
fs38.writeFileSync(dest, bytes);
|
|
6526
6793
|
savedFiles.push(dest);
|
|
6527
6794
|
logger?.success(`Saved: ${dest}`);
|
|
6528
6795
|
} catch (e) {
|
|
@@ -6533,8 +6800,8 @@ async function generateOpenRouterImage(opts) {
|
|
|
6533
6800
|
}
|
|
6534
6801
|
}
|
|
6535
6802
|
if (output && savedFiles[0]) {
|
|
6536
|
-
|
|
6537
|
-
|
|
6803
|
+
fs38.mkdirSync(path43.dirname(path43.resolve(output)), { recursive: true });
|
|
6804
|
+
fs38.writeFileSync(output, fs38.readFileSync(savedFiles[0]));
|
|
6538
6805
|
}
|
|
6539
6806
|
return { status: "success", generatedImages: savedFiles, model: usedModel };
|
|
6540
6807
|
}
|
|
@@ -6575,8 +6842,8 @@ Generated ${finalImages.length} image(s):`);
|
|
|
6575
6842
|
}
|
|
6576
6843
|
|
|
6577
6844
|
// src/providers/openrouter/commands/image-to-image.ts
|
|
6578
|
-
import
|
|
6579
|
-
import
|
|
6845
|
+
import fs39 from "fs";
|
|
6846
|
+
import path44 from "path";
|
|
6580
6847
|
init_errors();
|
|
6581
6848
|
var DEFAULT_MODEL = "google/gemini-2.5-flash-image";
|
|
6582
6849
|
var SOFT_LIMIT_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -6668,14 +6935,14 @@ async function runImageToImage(opts) {
|
|
|
6668
6935
|
const url = parsed.urls[i];
|
|
6669
6936
|
if (!url) continue;
|
|
6670
6937
|
const bytes = await fetchBytes(url);
|
|
6671
|
-
const dest =
|
|
6672
|
-
|
|
6938
|
+
const dest = path44.join(outDir, `openrouter-i2i-${ts}-${i + 1}.png`);
|
|
6939
|
+
fs39.writeFileSync(dest, bytes);
|
|
6673
6940
|
saved.push(dest);
|
|
6674
6941
|
opts.logger.success(`Saved: ${dest}`);
|
|
6675
6942
|
}
|
|
6676
6943
|
if (opts.output && saved[0]) {
|
|
6677
|
-
|
|
6678
|
-
|
|
6944
|
+
fs39.mkdirSync(path44.dirname(path44.resolve(opts.output)), { recursive: true });
|
|
6945
|
+
fs39.copyFileSync(saved[0], opts.output);
|
|
6679
6946
|
opts.logger.success(`Copied to: ${opts.output}`);
|
|
6680
6947
|
}
|
|
6681
6948
|
return saved;
|
|
@@ -6720,13 +6987,13 @@ async function listVideoModels(apiKey = requireOpenRouterKey()) {
|
|
|
6720
6987
|
}
|
|
6721
6988
|
|
|
6722
6989
|
// src/providers/openrouter/commands/video-status.ts
|
|
6723
|
-
import
|
|
6724
|
-
import
|
|
6990
|
+
import fs40 from "fs";
|
|
6991
|
+
import path45 from "path";
|
|
6725
6992
|
async function downloadOpenRouterVideo(opts) {
|
|
6726
6993
|
const { jobId, logger, source } = opts;
|
|
6727
6994
|
const apiKey = requireOpenRouterKey();
|
|
6728
6995
|
const url = videoContentUrl(jobId);
|
|
6729
|
-
const dest =
|
|
6996
|
+
const dest = path45.join(getOutputDir(), `openrouter_video_${jobId}.mp4`);
|
|
6730
6997
|
const r = await globalThis.fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
|
|
6731
6998
|
if (!r.ok) {
|
|
6732
6999
|
logger.error(`Download HTTP ${r.status}`);
|
|
@@ -6736,14 +7003,14 @@ async function downloadOpenRouterVideo(opts) {
|
|
|
6736
7003
|
logger.error("Empty response body");
|
|
6737
7004
|
process.exit(1);
|
|
6738
7005
|
}
|
|
6739
|
-
|
|
6740
|
-
const ws =
|
|
7006
|
+
fs40.mkdirSync(path45.dirname(dest), { recursive: true });
|
|
7007
|
+
const ws = fs40.createWriteStream(dest);
|
|
6741
7008
|
const { pipeline: pipeline2 } = await import("stream/promises");
|
|
6742
7009
|
await pipeline2(r.body, ws);
|
|
6743
7010
|
logger.success(`Saved ${dest}`);
|
|
6744
7011
|
if (opts.output) {
|
|
6745
|
-
|
|
6746
|
-
|
|
7012
|
+
fs40.mkdirSync(path45.dirname(path45.resolve(opts.output)), { recursive: true });
|
|
7013
|
+
fs40.copyFileSync(dest, opts.output);
|
|
6747
7014
|
logger.success(`Copied to ${opts.output}`);
|
|
6748
7015
|
}
|
|
6749
7016
|
await maybeDownloadThumb(source, dest, {
|
|
@@ -6908,6 +7175,10 @@ function registerOpenRouterCommands(program2) {
|
|
|
6908
7175
|
registerOpenRouterVideoModelsCommand(openrouter);
|
|
6909
7176
|
}
|
|
6910
7177
|
|
|
7178
|
+
// src/commands/check.ts
|
|
7179
|
+
import fs41 from "fs";
|
|
7180
|
+
import os4 from "os";
|
|
7181
|
+
|
|
6911
7182
|
// src/commands/check-helpers/binary-check.ts
|
|
6912
7183
|
import { execa as execa2 } from "execa";
|
|
6913
7184
|
async function checkBinary(binary, versionFlag = "--version") {
|
|
@@ -7079,6 +7350,19 @@ function registerCheckCommand(program2) {
|
|
|
7079
7350
|
if (opts.verbose) console.log(` ${MAGICK_HINT}`);
|
|
7080
7351
|
}
|
|
7081
7352
|
logger.header("API Keys");
|
|
7353
|
+
if (opts.verbose) {
|
|
7354
|
+
const files = getLoadedEnvFiles();
|
|
7355
|
+
logger.info(`Env files loaded: ${files.length > 0 ? files.join(", ") : "none"}`);
|
|
7356
|
+
}
|
|
7357
|
+
if (!legacyClaudeEnvEnabled()) {
|
|
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));
|
|
7360
|
+
if (legacy.length > 0) {
|
|
7361
|
+
logger.info(
|
|
7362
|
+
`Found ${legacy.length} legacy .claude env file(s); set MULTIX_LEGACY_CLAUDE_ENV=1 to load them`
|
|
7363
|
+
);
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7082
7366
|
let anyKey = false;
|
|
7083
7367
|
const resolved = /* @__PURE__ */ new Map();
|
|
7084
7368
|
for (const p of PROVIDERS) {
|
|
@@ -7177,8 +7461,8 @@ function registerCheckCommand(program2) {
|
|
|
7177
7461
|
|
|
7178
7462
|
// src/commands/doc/convert.ts
|
|
7179
7463
|
init_errors();
|
|
7180
|
-
import
|
|
7181
|
-
import
|
|
7464
|
+
import fs42 from "fs";
|
|
7465
|
+
import path46 from "path";
|
|
7182
7466
|
|
|
7183
7467
|
// src/commands/doc/default-prompt.ts
|
|
7184
7468
|
var DEFAULT_CONVERT_PROMPT = `Convert this document to clean, well-formatted Markdown.
|
|
@@ -7199,18 +7483,18 @@ async function convertDocuments(opts) {
|
|
|
7199
7483
|
const model = opts.model ?? DOC_MODEL_DEFAULT;
|
|
7200
7484
|
const prompt = opts.prompt ?? DEFAULT_CONVERT_PROMPT;
|
|
7201
7485
|
for (const f of inputs) {
|
|
7202
|
-
if (!
|
|
7486
|
+
if (!fs42.existsSync(f)) throw new ValidationError(`Input file not found: ${f}`);
|
|
7203
7487
|
}
|
|
7204
7488
|
let outputPath;
|
|
7205
7489
|
if (output) {
|
|
7206
|
-
outputPath =
|
|
7490
|
+
outputPath = path46.resolve(output);
|
|
7207
7491
|
} else if (autoName && inputs.length === 1 && inputs[0]) {
|
|
7208
|
-
const base =
|
|
7209
|
-
outputPath =
|
|
7492
|
+
const base = path46.basename(inputs[0], path46.extname(inputs[0]));
|
|
7493
|
+
outputPath = path46.join(getOutputDir(), `${base}-extraction.md`);
|
|
7210
7494
|
} else {
|
|
7211
|
-
outputPath =
|
|
7495
|
+
outputPath = path46.join(getOutputDir(), "document-extraction.md");
|
|
7212
7496
|
}
|
|
7213
|
-
|
|
7497
|
+
fs42.mkdirSync(path46.dirname(outputPath), { recursive: true });
|
|
7214
7498
|
const results = [];
|
|
7215
7499
|
for (let i = 0; i < inputs.length; i++) {
|
|
7216
7500
|
const filePath = inputs[i];
|
|
@@ -7270,7 +7554,7 @@ async function convertDocuments(opts) {
|
|
|
7270
7554
|
`);
|
|
7271
7555
|
for (const r of results) {
|
|
7272
7556
|
lines.push(`
|
|
7273
|
-
## ${
|
|
7557
|
+
## ${path46.basename(r.file)}
|
|
7274
7558
|
`);
|
|
7275
7559
|
if (r.status === "success" && r.markdown) {
|
|
7276
7560
|
lines.push(`${r.markdown}
|
|
@@ -7283,7 +7567,7 @@ async function convertDocuments(opts) {
|
|
|
7283
7567
|
}
|
|
7284
7568
|
lines.push("\n---\n");
|
|
7285
7569
|
}
|
|
7286
|
-
|
|
7570
|
+
fs42.writeFileSync(outputPath, lines.join("\n"), "utf8");
|
|
7287
7571
|
const succeeded = results.filter((r) => r.status === "success").length;
|
|
7288
7572
|
console.log(`
|
|
7289
7573
|
${"=".repeat(50)}`);
|
|
@@ -7331,12 +7615,12 @@ function registerDocCommands(program2) {
|
|
|
7331
7615
|
|
|
7332
7616
|
// src/commands/media/index.ts
|
|
7333
7617
|
init_errors();
|
|
7334
|
-
import
|
|
7335
|
-
import
|
|
7618
|
+
import fs45 from "fs";
|
|
7619
|
+
import path49 from "path";
|
|
7336
7620
|
|
|
7337
7621
|
// src/commands/media/batch.ts
|
|
7338
|
-
import
|
|
7339
|
-
import
|
|
7622
|
+
import fs43 from "fs";
|
|
7623
|
+
import path47 from "path";
|
|
7340
7624
|
|
|
7341
7625
|
// src/commands/media/detect-type.ts
|
|
7342
7626
|
var VIDEO_EXTS = /* @__PURE__ */ new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".mpg", ".mpeg"]);
|
|
@@ -7532,10 +7816,10 @@ async function batchOptimize(opts) {
|
|
|
7532
7816
|
verbose,
|
|
7533
7817
|
logger
|
|
7534
7818
|
} = opts;
|
|
7535
|
-
|
|
7536
|
-
const entries =
|
|
7819
|
+
fs43.mkdirSync(outputDir, { recursive: true });
|
|
7820
|
+
const entries = fs43.readdirSync(inputDir);
|
|
7537
7821
|
const mediaFiles = entries.filter((f) => {
|
|
7538
|
-
const ext =
|
|
7822
|
+
const ext = path47.extname(f).toLowerCase();
|
|
7539
7823
|
return ALL_EXTS.has(ext);
|
|
7540
7824
|
});
|
|
7541
7825
|
if (mediaFiles.length === 0) {
|
|
@@ -7546,9 +7830,9 @@ async function batchOptimize(opts) {
|
|
|
7546
7830
|
let succeeded = 0;
|
|
7547
7831
|
const errors = [];
|
|
7548
7832
|
for (const file of mediaFiles) {
|
|
7549
|
-
const inputPath =
|
|
7550
|
-
const outputPath =
|
|
7551
|
-
const ext =
|
|
7833
|
+
const inputPath = path47.join(inputDir, file);
|
|
7834
|
+
const outputPath = path47.join(outputDir, file);
|
|
7835
|
+
const ext = path47.extname(file).toLowerCase();
|
|
7552
7836
|
const kind = extToKind(ext);
|
|
7553
7837
|
logger?.debug(`Processing: ${file}`);
|
|
7554
7838
|
try {
|
|
@@ -7585,8 +7869,8 @@ async function batchOptimize(opts) {
|
|
|
7585
7869
|
|
|
7586
7870
|
// src/commands/media/split-video.ts
|
|
7587
7871
|
init_errors();
|
|
7588
|
-
import
|
|
7589
|
-
import
|
|
7872
|
+
import fs44 from "fs";
|
|
7873
|
+
import path48 from "path";
|
|
7590
7874
|
async function splitVideo(opts) {
|
|
7591
7875
|
await assertFfmpeg();
|
|
7592
7876
|
const { input, outputDir, chunkDuration = 3600, verbose, logger } = opts;
|
|
@@ -7597,10 +7881,10 @@ async function splitVideo(opts) {
|
|
|
7597
7881
|
logger?.info("Video is short enough \u2014 no splitting needed");
|
|
7598
7882
|
return [input];
|
|
7599
7883
|
}
|
|
7600
|
-
|
|
7601
|
-
const ext =
|
|
7602
|
-
const baseName =
|
|
7603
|
-
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}`);
|
|
7604
7888
|
logger?.debug(`Splitting into ~${numChunks} chunks of ${chunkDuration}s each`);
|
|
7605
7889
|
const args = [
|
|
7606
7890
|
"-i",
|
|
@@ -7619,8 +7903,8 @@ async function splitVideo(opts) {
|
|
|
7619
7903
|
await runFfmpeg(args, { verbose, logger });
|
|
7620
7904
|
const files = [];
|
|
7621
7905
|
for (let i = 0; i < numChunks + 1; i++) {
|
|
7622
|
-
const fname =
|
|
7623
|
-
if (
|
|
7906
|
+
const fname = path48.join(outputDir, `${baseName}_chunk_${String(i).padStart(3, "0")}${ext}`);
|
|
7907
|
+
if (fs44.existsSync(fname)) files.push(fname);
|
|
7624
7908
|
}
|
|
7625
7909
|
logger?.success(`Created ${files.length} chunks in ${outputDir}`);
|
|
7626
7910
|
return files;
|
|
@@ -7634,9 +7918,9 @@ function registerMediaCommands(program2) {
|
|
|
7634
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(
|
|
7635
7919
|
async (opts) => {
|
|
7636
7920
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7637
|
-
if (!
|
|
7638
|
-
|
|
7639
|
-
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();
|
|
7640
7924
|
const kind = extToKind(ext);
|
|
7641
7925
|
if (!kind) throw new ValidationError(`Unsupported file type: ${ext}`);
|
|
7642
7926
|
const quality = Number.parseInt(opts.quality, 10);
|
|
@@ -7676,7 +7960,7 @@ function registerMediaCommands(program2) {
|
|
|
7676
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(
|
|
7677
7961
|
async (opts) => {
|
|
7678
7962
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7679
|
-
if (!
|
|
7963
|
+
if (!fs45.existsSync(opts.input)) throw new ValidationError(`Input not found: ${opts.input}`);
|
|
7680
7964
|
const chunks = await splitVideo({
|
|
7681
7965
|
input: opts.input,
|
|
7682
7966
|
outputDir: opts.outputDir,
|
|
@@ -7692,7 +7976,7 @@ Created ${chunks.length} chunk(s) in ${opts.outputDir}`);
|
|
|
7692
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(
|
|
7693
7977
|
async (opts) => {
|
|
7694
7978
|
const logger = createLogger({ verbose: opts.verbose ?? false });
|
|
7695
|
-
if (!
|
|
7979
|
+
if (!fs45.existsSync(opts.inputDir))
|
|
7696
7980
|
throw new ValidationError(`Input dir not found: ${opts.inputDir}`);
|
|
7697
7981
|
const result = await batchOptimize({
|
|
7698
7982
|
inputDir: opts.inputDir,
|