@conceptcraft/mindframes 0.1.4 → 0.1.5

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/dist/index.js CHANGED
@@ -293,6 +293,12 @@ function progressBar(current, total, width = 30, showPercentage = true) {
293
293
  const bar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
294
294
  return showPercentage ? `[${bar}] ${percentage}%` : `[${bar}]`;
295
295
  }
296
+ function formatJson(data) {
297
+ return JSON.stringify(data, null, 2);
298
+ }
299
+ function printJson(data) {
300
+ console.log(formatJson(data));
301
+ }
296
302
  var init_output = __esm({
297
303
  "src/lib/output.ts"() {
298
304
  "use strict";
@@ -300,11 +306,19 @@ var init_output = __esm({
300
306
  }
301
307
  });
302
308
 
309
+ // src/types/media.ts
310
+ var init_media = __esm({
311
+ "src/types/media.ts"() {
312
+ "use strict";
313
+ }
314
+ });
315
+
303
316
  // src/types/index.ts
304
317
  var EXIT_CODES;
305
318
  var init_types = __esm({
306
319
  "src/types/index.ts"() {
307
320
  "use strict";
321
+ init_media();
308
322
  EXIT_CODES = {
309
323
  SUCCESS: 0,
310
324
  GENERAL_ERROR: 1,
@@ -889,6 +903,123 @@ async function validateGeneration(mode, slideCount, teamId) {
889
903
  }
890
904
  return limits;
891
905
  }
906
+ async function generateSpeech(ttsRequest) {
907
+ const apiUrl = getApiUrl();
908
+ if (!hasAuth()) {
909
+ throw new ApiError(
910
+ "Not authenticated. Run 'cc login' or set CC_SLIDES_API_KEY environment variable.",
911
+ 401,
912
+ 2
913
+ );
914
+ }
915
+ const authHeaders = await getAuthHeaders();
916
+ let response;
917
+ try {
918
+ response = await fetch(`${apiUrl}/api/cli/tts`, {
919
+ method: "POST",
920
+ headers: {
921
+ "Content-Type": "application/json",
922
+ ...authHeaders
923
+ },
924
+ body: JSON.stringify(ttsRequest)
925
+ });
926
+ } catch (error2) {
927
+ throw new ApiError(
928
+ `Network error: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
929
+ 0,
930
+ 5
931
+ );
932
+ }
933
+ if (!response.ok) {
934
+ const errorText = await response.text().catch(() => "Unknown error");
935
+ let errorMessage;
936
+ try {
937
+ const errorJson = JSON.parse(errorText);
938
+ errorMessage = errorJson.error || errorJson.message || errorText;
939
+ } catch {
940
+ errorMessage = errorText;
941
+ }
942
+ throw new ApiError(errorMessage, response.status, response.status === 401 ? 2 : 1);
943
+ }
944
+ const audioData = Buffer.from(await response.arrayBuffer());
945
+ const duration = parseFloat(response.headers.get("X-Duration-Seconds") || "0");
946
+ const cost = parseFloat(response.headers.get("X-Cost-USD") || "0");
947
+ const provider = response.headers.get("X-Provider") || "unknown";
948
+ const format = response.headers.get("X-Audio-Format") || "mp3";
949
+ return {
950
+ audioData,
951
+ duration,
952
+ cost,
953
+ provider,
954
+ format
955
+ };
956
+ }
957
+ async function getVoices() {
958
+ return request("/api/cli/tts");
959
+ }
960
+ async function generateMusic(musicRequest) {
961
+ const response = await request("/api/cli/music", {
962
+ method: "POST",
963
+ body: musicRequest
964
+ });
965
+ if (!response.data) {
966
+ throw new ApiError(`Invalid API response: ${JSON.stringify(response)}`, 500, 1);
967
+ }
968
+ return {
969
+ requestId: response.data.id,
970
+ status: response.data.status,
971
+ audioUrl: response.data.audioUrl,
972
+ duration: response.data.duration,
973
+ cost: response.data.cost,
974
+ error: response.data.error
975
+ };
976
+ }
977
+ async function checkMusicStatus(requestId) {
978
+ const response = await request(
979
+ `/api/cli/music?requestId=${encodeURIComponent(requestId)}`
980
+ );
981
+ return {
982
+ requestId: response.data.id,
983
+ status: response.data.status,
984
+ audioUrl: response.data.audioUrl,
985
+ duration: response.data.duration,
986
+ cost: response.data.cost,
987
+ error: response.data.error
988
+ };
989
+ }
990
+ async function mixAudio(mixRequest) {
991
+ return request("/api/cli/mix", {
992
+ method: "POST",
993
+ body: mixRequest
994
+ });
995
+ }
996
+ async function checkMixStatus(requestId) {
997
+ return request(
998
+ `/api/cli/mix?requestId=${encodeURIComponent(requestId)}`
999
+ );
1000
+ }
1001
+ async function searchImages(searchRequest) {
1002
+ return request("/api/cli/images/search", {
1003
+ method: "POST",
1004
+ body: searchRequest
1005
+ });
1006
+ }
1007
+ async function searchVideos(searchRequest) {
1008
+ return request("/api/cli/videos/search", {
1009
+ method: "POST",
1010
+ body: searchRequest
1011
+ });
1012
+ }
1013
+ async function pollForCompletion(checkFn, maxAttempts = 60, intervalMs = 2e3) {
1014
+ for (let i = 0; i < maxAttempts; i++) {
1015
+ const result = await checkFn();
1016
+ if (result.status === "completed" || result.status === "failed") {
1017
+ return result;
1018
+ }
1019
+ await new Promise((resolve4) => setTimeout(resolve4, intervalMs));
1020
+ }
1021
+ throw new ApiError("Operation timed out", 408, 1);
1022
+ }
892
1023
  var ApiError;
893
1024
  var init_api = __esm({
894
1025
  "src/lib/api.ts"() {
@@ -1054,6 +1185,20 @@ async function exchangeCodeForTokens(tokenEndpoint, code, codeVerifier, redirect
1054
1185
  }
1055
1186
  function startCallbackServer(port, expectedState) {
1056
1187
  return new Promise((resolve4, reject) => {
1188
+ let timeoutId;
1189
+ let settled = false;
1190
+ const cleanup = () => {
1191
+ if (settled) return;
1192
+ settled = true;
1193
+ clearTimeout(timeoutId);
1194
+ process.off("SIGINT", onCancel);
1195
+ process.off("SIGTERM", onCancel);
1196
+ server.close();
1197
+ };
1198
+ const onCancel = () => {
1199
+ cleanup();
1200
+ reject(new Error("Login cancelled"));
1201
+ };
1057
1202
  const server = http.createServer((req, res) => {
1058
1203
  const url = new URL(req.url || "", `http://localhost:${port}`);
1059
1204
  if (url.pathname !== "/callback") {
@@ -1078,7 +1223,7 @@ function startCallbackServer(port, expectedState) {
1078
1223
  </body>
1079
1224
  </html>
1080
1225
  `);
1081
- server.close();
1226
+ cleanup();
1082
1227
  reject(new Error(errorDescription || errorParam));
1083
1228
  return;
1084
1229
  }
@@ -1094,7 +1239,7 @@ function startCallbackServer(port, expectedState) {
1094
1239
  </body>
1095
1240
  </html>
1096
1241
  `);
1097
- server.close();
1242
+ cleanup();
1098
1243
  reject(new Error("Missing authorization code or state"));
1099
1244
  return;
1100
1245
  }
@@ -1110,7 +1255,7 @@ function startCallbackServer(port, expectedState) {
1110
1255
  </body>
1111
1256
  </html>
1112
1257
  `);
1113
- server.close();
1258
+ cleanup();
1114
1259
  reject(new Error("State mismatch"));
1115
1260
  return;
1116
1261
  }
@@ -1124,20 +1269,14 @@ function startCallbackServer(port, expectedState) {
1124
1269
  </body>
1125
1270
  </html>
1126
1271
  `);
1127
- server.close();
1272
+ cleanup();
1128
1273
  resolve4({ code, state });
1129
1274
  });
1130
1275
  server.listen(port);
1131
- const cleanup = () => {
1132
- server.close();
1133
- reject(new Error("Login cancelled"));
1134
- };
1135
- process.once("SIGINT", cleanup);
1136
- process.once("SIGTERM", cleanup);
1137
- setTimeout(() => {
1138
- process.off("SIGINT", cleanup);
1139
- process.off("SIGTERM", cleanup);
1140
- server.close();
1276
+ process.once("SIGINT", onCancel);
1277
+ process.once("SIGTERM", onCancel);
1278
+ timeoutId = setTimeout(() => {
1279
+ cleanup();
1141
1280
  reject(new Error("Login timed out - no callback received within 5 minutes"));
1142
1281
  }, 5 * 60 * 1e3);
1143
1282
  });
@@ -1238,6 +1377,7 @@ var init_login = __esm({
1238
1377
  }
1239
1378
  try {
1240
1379
  await runLoginFlow(options);
1380
+ process.exit(0);
1241
1381
  } catch {
1242
1382
  process.exit(1);
1243
1383
  }
@@ -1246,7 +1386,7 @@ var init_login = __esm({
1246
1386
  });
1247
1387
 
1248
1388
  // src/index.ts
1249
- import { Command as Command15 } from "commander";
1389
+ import { Command as Command20 } from "commander";
1250
1390
  import chalk13 from "chalk";
1251
1391
 
1252
1392
  // src/lib/brand.ts
@@ -3295,9 +3435,687 @@ function installSkill(skillPath, content, force) {
3295
3435
  writeFileSync(skillFile, content, "utf-8");
3296
3436
  }
3297
3437
 
3438
+ // src/commands/tts.ts
3439
+ init_api();
3440
+ init_output();
3441
+ init_types();
3442
+ import { Command as Command15 } from "commander";
3443
+ import ora8 from "ora";
3444
+ import { writeFile as writeFile2 } from "fs/promises";
3445
+ var generateCommand = new Command15("generate").description("Generate speech from text").requiredOption("-t, --text <text>", "Text to convert to speech").requiredOption("-o, --output <path>", "Output file path").option("-v, --voice <voice>", "Voice name or ID (e.g., Kore, Rachel, alloy)").option("-p, --provider <provider>", "Provider: gemini, elevenlabs, openai").option("-m, --model <model>", "Model (provider-specific)").option("-s, --speed <speed>", "Speech speed 0.25-4.0 (default: 1.0)").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
3446
+ const format = options.format;
3447
+ const spinner = format === "human" ? ora8("Generating speech...").start() : null;
3448
+ let speed;
3449
+ if (options.speed) {
3450
+ speed = parseFloat(options.speed);
3451
+ if (isNaN(speed) || speed < 0.25 || speed > 4) {
3452
+ spinner?.stop();
3453
+ error("Speed must be between 0.25 and 4.0");
3454
+ process.exit(EXIT_CODES.INVALID_INPUT);
3455
+ }
3456
+ }
3457
+ try {
3458
+ const result = await generateSpeech({
3459
+ text: options.text,
3460
+ options: {
3461
+ provider: options.provider,
3462
+ voice: options.voice,
3463
+ model: options.model,
3464
+ speed
3465
+ }
3466
+ });
3467
+ spinner?.stop();
3468
+ const outputPath = options.output.endsWith(`.${result.format}`) ? options.output : `${options.output}.${result.format}`;
3469
+ await writeFile2(outputPath, result.audioData);
3470
+ if (format === "json") {
3471
+ printJson({
3472
+ status: "completed",
3473
+ output: outputPath,
3474
+ duration: result.duration,
3475
+ cost: result.cost,
3476
+ provider: result.provider,
3477
+ format: result.format
3478
+ });
3479
+ return;
3480
+ }
3481
+ if (format === "quiet") {
3482
+ console.log(outputPath);
3483
+ return;
3484
+ }
3485
+ success(`Saved to: ${outputPath}`);
3486
+ info(`Duration: ${result.duration.toFixed(2)}s`);
3487
+ info(`Provider: ${result.provider}`);
3488
+ info(`Cost: $${result.cost.toFixed(6)}`);
3489
+ } catch (err) {
3490
+ spinner?.stop();
3491
+ error(err instanceof Error ? err.message : "Unknown error");
3492
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3493
+ }
3494
+ });
3495
+ var voicesCommand = new Command15("voices").description("List available voices").option("-p, --provider <provider>", "Filter by provider: gemini, elevenlabs, openai").option("-f, --format <format>", "Output format: human, json", "human").action(async (options) => {
3496
+ const spinner = options.format === "human" ? ora8("Fetching voices...").start() : null;
3497
+ try {
3498
+ const result = await getVoices();
3499
+ spinner?.stop();
3500
+ if (options.format === "json") {
3501
+ if (options.provider) {
3502
+ const providerVoices = result.voices[options.provider];
3503
+ printJson(providerVoices || []);
3504
+ } else {
3505
+ printJson(result.voices);
3506
+ }
3507
+ return;
3508
+ }
3509
+ const providers = options.provider ? [options.provider] : ["gemini", "elevenlabs", "openai"];
3510
+ for (const provider of providers) {
3511
+ const voices = result.voices[provider];
3512
+ if (!voices || voices.length === 0) continue;
3513
+ console.log();
3514
+ console.log(`${provider.toUpperCase()} Voices:`);
3515
+ console.log("-".repeat(50));
3516
+ for (const voice of voices) {
3517
+ console.log(` ${voice.name} (${voice.id})`);
3518
+ console.log(` ${voice.description}`);
3519
+ }
3520
+ }
3521
+ } catch (err) {
3522
+ spinner?.stop();
3523
+ error(err instanceof Error ? err.message : "Unknown error");
3524
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3525
+ }
3526
+ });
3527
+ var ttsCommand = new Command15("tts").description("Text-to-speech commands").addCommand(generateCommand).addCommand(voicesCommand);
3528
+
3529
+ // src/commands/music.ts
3530
+ init_api();
3531
+ init_output();
3532
+ init_types();
3533
+ import { Command as Command16 } from "commander";
3534
+ import ora9 from "ora";
3535
+ import { writeFile as writeFile3 } from "fs/promises";
3536
+ function outputResult(result, format) {
3537
+ if (format === "json") {
3538
+ printJson(result);
3539
+ return;
3540
+ }
3541
+ if (format === "quiet") {
3542
+ if (result.audioUrl) {
3543
+ console.log(result.audioUrl);
3544
+ } else {
3545
+ console.log(result.requestId);
3546
+ }
3547
+ return;
3548
+ }
3549
+ info(`Request ID: ${result.requestId}`);
3550
+ info(`Status: ${result.status}`);
3551
+ if (result.duration) {
3552
+ info(`Duration: ${result.duration}s`);
3553
+ }
3554
+ if (result.audioUrl) {
3555
+ success(`Audio URL: ${result.audioUrl}`);
3556
+ }
3557
+ if (result.cost !== void 0) {
3558
+ info(`Cost: $${result.cost.toFixed(4)}`);
3559
+ }
3560
+ if (result.error) {
3561
+ error(`Error: ${result.error}`);
3562
+ }
3563
+ }
3564
+ async function downloadFile(url, outputPath) {
3565
+ if (url.startsWith("data:")) {
3566
+ const matches = url.match(/^data:[^;]+;base64,(.+)$/);
3567
+ if (!matches) {
3568
+ throw new Error("Invalid data URL format");
3569
+ }
3570
+ const buffer2 = Buffer.from(matches[1], "base64");
3571
+ await writeFile3(outputPath, buffer2);
3572
+ return;
3573
+ }
3574
+ const response = await fetch(url);
3575
+ if (!response.ok) {
3576
+ throw new Error(`Failed to download: ${response.status}`);
3577
+ }
3578
+ const buffer = await response.arrayBuffer();
3579
+ await writeFile3(outputPath, Buffer.from(buffer));
3580
+ }
3581
+ var generateCommand2 = new Command16("generate").description("Generate music from a text prompt").requiredOption("-p, --prompt <text>", "Music description").option("-d, --duration <seconds>", "Duration in seconds (3-30)", "30").option("-s, --style <style>", "Style preset").option("--provider <provider>", "Provider (elevenlabs, suno)").option("-o, --output <path>", "Output file path").option("--no-wait", "Do not wait for completion").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
3582
+ const duration = parseInt(options.duration, 10);
3583
+ if (isNaN(duration) || duration < 3 || duration > 30) {
3584
+ error("Duration must be between 3 and 30 seconds");
3585
+ process.exit(EXIT_CODES.INVALID_INPUT);
3586
+ }
3587
+ const format = options.format;
3588
+ const spinner = format === "human" ? ora9("Generating music...").start() : null;
3589
+ try {
3590
+ const result = await generateMusic({
3591
+ prompt: options.prompt,
3592
+ duration,
3593
+ options: {
3594
+ provider: options.provider,
3595
+ style: options.style
3596
+ }
3597
+ });
3598
+ if (!options.wait) {
3599
+ spinner?.stop();
3600
+ outputResult(result, format);
3601
+ return;
3602
+ }
3603
+ let finalResult = result;
3604
+ if (result.status !== "completed" && result.status !== "failed") {
3605
+ if (spinner) spinner.text = `Processing (ID: ${result.requestId})...`;
3606
+ finalResult = await pollForCompletion(
3607
+ () => checkMusicStatus(result.requestId),
3608
+ 60,
3609
+ 2e3
3610
+ );
3611
+ }
3612
+ spinner?.stop();
3613
+ if (finalResult.status === "failed") {
3614
+ error(finalResult.error || "Music generation failed");
3615
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3616
+ }
3617
+ outputResult(finalResult, format);
3618
+ if (options.output && finalResult.audioUrl) {
3619
+ const downloadSpinner = format === "human" ? ora9("Downloading...").start() : null;
3620
+ try {
3621
+ await downloadFile(finalResult.audioUrl, options.output);
3622
+ downloadSpinner?.stop();
3623
+ if (format === "human") {
3624
+ success(`Saved to: ${options.output}`);
3625
+ }
3626
+ } catch (err) {
3627
+ downloadSpinner?.stop();
3628
+ warn(`Failed to download: ${err instanceof Error ? err.message : "Unknown error"}`);
3629
+ }
3630
+ }
3631
+ } catch (err) {
3632
+ spinner?.stop();
3633
+ error(err instanceof Error ? err.message : "Unknown error");
3634
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3635
+ }
3636
+ });
3637
+ var statusCommand = new Command16("status").description("Check status of a music generation request").argument("<id>", "Request ID").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (id, options) => {
3638
+ const spinner = options.format === "human" ? ora9("Checking status...").start() : null;
3639
+ try {
3640
+ const result = await checkMusicStatus(id);
3641
+ spinner?.stop();
3642
+ outputResult(result, options.format);
3643
+ } catch (err) {
3644
+ spinner?.stop();
3645
+ error(err instanceof Error ? err.message : "Unknown error");
3646
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3647
+ }
3648
+ });
3649
+ var musicCommand = new Command16("music").description("Music generation commands").addCommand(generateCommand2).addCommand(statusCommand);
3650
+
3651
+ // src/commands/mix.ts
3652
+ init_api();
3653
+ init_output();
3654
+ init_types();
3655
+ import { Command as Command17 } from "commander";
3656
+ import ora10 from "ora";
3657
+ import { writeFile as writeFile4 } from "fs/promises";
3658
+ function outputResult2(result, format) {
3659
+ if (format === "json") {
3660
+ printJson(result);
3661
+ return;
3662
+ }
3663
+ if (format === "quiet") {
3664
+ if (result.outputUrl) {
3665
+ console.log(result.outputUrl);
3666
+ } else {
3667
+ console.log(result.requestId);
3668
+ }
3669
+ return;
3670
+ }
3671
+ info(`Request ID: ${result.requestId}`);
3672
+ info(`Status: ${result.status}`);
3673
+ if (result.duration) {
3674
+ info(`Duration: ${result.duration}s`);
3675
+ }
3676
+ if (result.outputUrl) {
3677
+ success(`Output URL: ${result.outputUrl}`);
3678
+ }
3679
+ if (result.cost !== void 0) {
3680
+ info(`Cost: $${result.cost.toFixed(4)}`);
3681
+ }
3682
+ if (result.error) {
3683
+ error(`Error: ${result.error}`);
3684
+ }
3685
+ }
3686
+ async function downloadFile2(url, outputPath) {
3687
+ const response = await fetch(url);
3688
+ if (!response.ok) {
3689
+ throw new Error(`Failed to download: ${response.status}`);
3690
+ }
3691
+ const buffer = await response.arrayBuffer();
3692
+ await writeFile4(outputPath, Buffer.from(buffer));
3693
+ }
3694
+ var mixCommand = new Command17("create").description("Mix audio tracks into a video").requiredOption("--video <url>", "Input video file/URL").option("--music <url>", "Background music file/URL").option("--voice <url>", "Voiceover file/URL").option("--music-volume <percent>", "Music volume 0-100", "50").option("--voice-volume <percent>", "Voice volume 0-100", "100").option("-o, --output <path>", "Output file path").option("--no-wait", "Do not wait for completion").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
3695
+ if (!options.music && !options.voice) {
3696
+ error("At least one of --music or --voice must be provided");
3697
+ process.exit(EXIT_CODES.INVALID_INPUT);
3698
+ }
3699
+ const musicVolume = parseInt(options.musicVolume, 10) / 100;
3700
+ const voiceVolume = parseInt(options.voiceVolume, 10) / 100;
3701
+ if (isNaN(musicVolume) || musicVolume < 0 || musicVolume > 1) {
3702
+ error("Music volume must be between 0 and 100");
3703
+ process.exit(EXIT_CODES.INVALID_INPUT);
3704
+ }
3705
+ if (isNaN(voiceVolume) || voiceVolume < 0 || voiceVolume > 2) {
3706
+ error("Voice volume must be between 0 and 200");
3707
+ process.exit(EXIT_CODES.INVALID_INPUT);
3708
+ }
3709
+ const format = options.format;
3710
+ const spinner = format === "human" ? ora10("Mixing audio...").start() : null;
3711
+ const inputs = [{ url: options.video, role: "video" }];
3712
+ if (options.music) {
3713
+ inputs.push({ url: options.music, role: "background", volume: musicVolume * 5 });
3714
+ }
3715
+ if (options.voice) {
3716
+ inputs.push({ url: options.voice, role: "voice", volume: voiceVolume * 2 });
3717
+ }
3718
+ try {
3719
+ const result = await mixAudio({
3720
+ operation: "add-to-video",
3721
+ inputs,
3722
+ options: {
3723
+ musicVolume,
3724
+ voiceVolume
3725
+ }
3726
+ });
3727
+ if (!options.wait) {
3728
+ spinner?.stop();
3729
+ outputResult2(result, format);
3730
+ return;
3731
+ }
3732
+ if (spinner) spinner.text = `Processing (ID: ${result.requestId})...`;
3733
+ const finalResult = await pollForCompletion(
3734
+ () => checkMixStatus(result.requestId),
3735
+ 120,
3736
+ 3e3
3737
+ );
3738
+ spinner?.stop();
3739
+ if (finalResult.status === "failed") {
3740
+ error(finalResult.error || "Audio mixing failed");
3741
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3742
+ }
3743
+ outputResult2(finalResult, format);
3744
+ if (options.output && finalResult.outputUrl) {
3745
+ const downloadSpinner = format === "human" ? ora10("Downloading...").start() : null;
3746
+ try {
3747
+ await downloadFile2(finalResult.outputUrl, options.output);
3748
+ downloadSpinner?.stop();
3749
+ if (format === "human") {
3750
+ success(`Saved to: ${options.output}`);
3751
+ }
3752
+ } catch (err) {
3753
+ downloadSpinner?.stop();
3754
+ warn(`Failed to download: ${err instanceof Error ? err.message : "Unknown error"}`);
3755
+ }
3756
+ }
3757
+ } catch (err) {
3758
+ spinner?.stop();
3759
+ error(err instanceof Error ? err.message : "Unknown error");
3760
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3761
+ }
3762
+ });
3763
+ var statusCommand2 = new Command17("status").description("Check status of an audio mix request").argument("<id>", "Request ID").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (id, options) => {
3764
+ const spinner = options.format === "human" ? ora10("Checking status...").start() : null;
3765
+ try {
3766
+ const result = await checkMixStatus(id);
3767
+ spinner?.stop();
3768
+ outputResult2(result, options.format);
3769
+ } catch (err) {
3770
+ spinner?.stop();
3771
+ error(err instanceof Error ? err.message : "Unknown error");
3772
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3773
+ }
3774
+ });
3775
+ var mixAudioCommand = new Command17("mix").description("Audio mixing commands").addCommand(mixCommand).addCommand(statusCommand2);
3776
+
3777
+ // src/commands/image.ts
3778
+ init_api();
3779
+ init_output();
3780
+ init_types();
3781
+ import { Command as Command18 } from "commander";
3782
+ import ora11 from "ora";
3783
+ var searchCommand = new Command18("search").description("Search for images").requiredOption("-q, --query <query>", "Search query").option("-n, --max-results <number>", "Maximum number of results (default: 10)").option("-s, --size <size>", "Image size: small, medium, large, any", "large").option("--safe-search", "Enable safe search (default: true)", true).option("--no-safe-search", "Disable safe search").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
3784
+ const format = options.format;
3785
+ const spinner = format === "human" ? ora11("Searching for images...").start() : null;
3786
+ let maxResults;
3787
+ if (options.maxResults) {
3788
+ maxResults = parseInt(options.maxResults, 10);
3789
+ if (isNaN(maxResults) || maxResults < 1) {
3790
+ spinner?.stop();
3791
+ error("Max results must be a positive number");
3792
+ process.exit(EXIT_CODES.INVALID_INPUT);
3793
+ }
3794
+ }
3795
+ try {
3796
+ const result = await searchImages({
3797
+ query: options.query,
3798
+ options: {
3799
+ maxResults: maxResults || 10,
3800
+ size: options.size,
3801
+ safeSearch: options.safeSearch
3802
+ }
3803
+ });
3804
+ spinner?.stop();
3805
+ if (!result.success) {
3806
+ error("Search failed");
3807
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3808
+ }
3809
+ const allImages = result.data.results.flatMap(
3810
+ (providerResult) => providerResult.results.map((img) => ({
3811
+ ...img,
3812
+ provider: providerResult.providerName
3813
+ }))
3814
+ );
3815
+ if (format === "json") {
3816
+ printJson({
3817
+ success: true,
3818
+ query: options.query,
3819
+ totalResults: allImages.length,
3820
+ totalCost: result.data.totalCost,
3821
+ images: allImages
3822
+ });
3823
+ return;
3824
+ }
3825
+ if (format === "quiet") {
3826
+ for (const img of allImages) {
3827
+ console.log(img.url);
3828
+ }
3829
+ return;
3830
+ }
3831
+ if (allImages.length === 0) {
3832
+ info("No images found");
3833
+ return;
3834
+ }
3835
+ success(`Found ${allImages.length} images for "${options.query}"`);
3836
+ console.log();
3837
+ for (let i = 0; i < allImages.length; i++) {
3838
+ const img = allImages[i];
3839
+ console.log(`[${i + 1}] ${img.title || "Untitled"}`);
3840
+ console.log(` URL: ${img.url}`);
3841
+ console.log(` Size: ${img.width}x${img.height}`);
3842
+ if (img.author) {
3843
+ console.log(` Author: ${img.author}`);
3844
+ }
3845
+ console.log(` Provider: ${img.provider}`);
3846
+ console.log();
3847
+ }
3848
+ info(`Total cost: $${result.data.totalCost.toFixed(4)}`);
3849
+ } catch (err) {
3850
+ spinner?.stop();
3851
+ error(err instanceof Error ? err.message : "Unknown error");
3852
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3853
+ }
3854
+ });
3855
+ var imageCommand = new Command18("image").description("Image search commands").addCommand(searchCommand);
3856
+
3857
+ // src/commands/video.ts
3858
+ init_api();
3859
+ init_output();
3860
+ init_types();
3861
+ import { Command as Command19 } from "commander";
3862
+ import ora12 from "ora";
3863
+ import { mkdir, writeFile as writeFile5, readFile as readFile2 } from "fs/promises";
3864
+ import { join as join2 } from "path";
3865
+ async function downloadFile3(url, outputPath) {
3866
+ if (url.startsWith("data:")) {
3867
+ const matches = url.match(/^data:[^;]+;base64,(.+)$/);
3868
+ if (!matches) {
3869
+ throw new Error("Invalid data URL format");
3870
+ }
3871
+ const buffer2 = Buffer.from(matches[1], "base64");
3872
+ await writeFile5(outputPath, buffer2);
3873
+ return;
3874
+ }
3875
+ const response = await fetch(url);
3876
+ if (!response.ok) {
3877
+ throw new Error(`Failed to download: ${response.status}`);
3878
+ }
3879
+ const buffer = await response.arrayBuffer();
3880
+ await writeFile5(outputPath, Buffer.from(buffer));
3881
+ }
3882
+ function getExtension(url) {
3883
+ try {
3884
+ const urlObj = new URL(url);
3885
+ const pathname = urlObj.pathname;
3886
+ const ext = pathname.split(".").pop()?.toLowerCase();
3887
+ if (ext && ["jpg", "jpeg", "png", "gif", "webp"].includes(ext)) {
3888
+ return ext;
3889
+ }
3890
+ } catch {
3891
+ }
3892
+ return "jpg";
3893
+ }
3894
+ var createCommand2 = new Command19("create").description("Create video assets (voiceover, music, images)").option("-s, --script <text>", "Narration script text").option("--script-file <path>", "Path to script file").option("-t, --topic <text>", "Topic for image search (inferred from script if not provided)").option("-d, --duration <seconds>", "Target duration (auto-calculated from script if not set)").option("-v, --voice <name>", "TTS voice (Kore, Puck, Rachel, alloy)", "Kore").option("-m, --music-prompt <text>", "Music description (auto-generated if not provided)").option("-n, --num-images <number>", "Number of images to search/download", "5").option("-o, --output <dir>", "Output directory", "./public").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (options) => {
3895
+ const format = options.format;
3896
+ const spinner = format === "human" ? ora12("Initializing...").start() : null;
3897
+ try {
3898
+ let script = options.script;
3899
+ if (options.scriptFile) {
3900
+ try {
3901
+ script = await readFile2(options.scriptFile, "utf-8");
3902
+ } catch (err) {
3903
+ spinner?.stop();
3904
+ error(`Failed to read script file: ${err instanceof Error ? err.message : "Unknown error"}`);
3905
+ process.exit(EXIT_CODES.INVALID_INPUT);
3906
+ }
3907
+ }
3908
+ if (!script || script.trim().length === 0) {
3909
+ spinner?.stop();
3910
+ error("Either --script or --script-file is required");
3911
+ process.exit(EXIT_CODES.INVALID_INPUT);
3912
+ }
3913
+ script = script.trim();
3914
+ const topic = options.topic || script.split(".")[0].slice(0, 50);
3915
+ const numImages = parseInt(options.numImages, 10);
3916
+ if (isNaN(numImages) || numImages < 1 || numImages > 20) {
3917
+ spinner?.stop();
3918
+ error("Number of images must be between 1 and 20");
3919
+ process.exit(EXIT_CODES.INVALID_INPUT);
3920
+ }
3921
+ const audioDir = join2(options.output, "audio");
3922
+ const imagesDir = join2(options.output, "images");
3923
+ if (spinner) spinner.text = "Creating directories...";
3924
+ await mkdir(audioDir, { recursive: true });
3925
+ await mkdir(imagesDir, { recursive: true });
3926
+ let totalCost = 0;
3927
+ if (spinner) spinner.text = "Generating voiceover...";
3928
+ const ttsResult = await generateSpeech({
3929
+ text: script,
3930
+ options: { voice: options.voice }
3931
+ });
3932
+ const voiceoverPath = join2(audioDir, `voiceover.${ttsResult.format}`);
3933
+ await writeFile5(voiceoverPath, ttsResult.audioData);
3934
+ totalCost += ttsResult.cost;
3935
+ const voiceoverInfo = {
3936
+ path: `audio/voiceover.${ttsResult.format}`,
3937
+ duration: ttsResult.duration,
3938
+ voice: options.voice,
3939
+ provider: ttsResult.provider,
3940
+ cost: ttsResult.cost
3941
+ };
3942
+ if (format === "human") {
3943
+ spinner?.stop();
3944
+ success(`Voiceover: ${voiceoverPath} (${ttsResult.duration.toFixed(1)}s)`);
3945
+ spinner?.start();
3946
+ }
3947
+ const musicDuration = Math.min(30, Math.ceil(ttsResult.duration) + 5);
3948
+ const musicPrompt = options.musicPrompt || "uplifting background music, positive energy";
3949
+ if (spinner) spinner.text = "Generating music...";
3950
+ let musicResult = await generateMusic({
3951
+ prompt: musicPrompt,
3952
+ duration: musicDuration
3953
+ });
3954
+ if (musicResult.status !== "completed" && musicResult.status !== "failed") {
3955
+ if (spinner) spinner.text = `Processing music (ID: ${musicResult.requestId})...`;
3956
+ musicResult = await pollForCompletion(
3957
+ () => checkMusicStatus(musicResult.requestId),
3958
+ 60,
3959
+ 2e3
3960
+ );
3961
+ }
3962
+ if (musicResult.status === "failed") {
3963
+ spinner?.stop();
3964
+ error(`Music generation failed: ${musicResult.error || "Unknown error"}`);
3965
+ process.exit(EXIT_CODES.GENERAL_ERROR);
3966
+ }
3967
+ const musicPath = join2(audioDir, "music.mp3");
3968
+ if (musicResult.audioUrl) {
3969
+ await downloadFile3(musicResult.audioUrl, musicPath);
3970
+ }
3971
+ totalCost += musicResult.cost || 0;
3972
+ const musicInfo = {
3973
+ path: "audio/music.mp3",
3974
+ duration: musicResult.duration || musicDuration,
3975
+ prompt: musicPrompt,
3976
+ cost: musicResult.cost || 0
3977
+ };
3978
+ if (format === "human") {
3979
+ spinner?.stop();
3980
+ success(`Music: ${musicPath} (${musicInfo.duration}s)`);
3981
+ spinner?.start();
3982
+ }
3983
+ if (spinner) spinner.text = "Searching for images...";
3984
+ const imageResults = await searchImages({
3985
+ query: topic,
3986
+ options: {
3987
+ maxResults: numImages,
3988
+ size: "large",
3989
+ safeSearch: true
3990
+ }
3991
+ });
3992
+ const allImages = imageResults.data.results.flatMap(
3993
+ (providerResult) => providerResult.results.map((img) => ({
3994
+ ...img,
3995
+ provider: providerResult.providerName
3996
+ }))
3997
+ );
3998
+ totalCost += imageResults.data.totalCost;
3999
+ const downloadedImages = [];
4000
+ for (let i = 0; i < Math.min(allImages.length, numImages); i++) {
4001
+ const img = allImages[i];
4002
+ const ext = getExtension(img.url);
4003
+ const filename = `scene-${i + 1}.${ext}`;
4004
+ const imagePath = join2(imagesDir, filename);
4005
+ if (spinner) spinner.text = `Downloading image ${i + 1}/${Math.min(allImages.length, numImages)}...`;
4006
+ try {
4007
+ await downloadFile3(img.url, imagePath);
4008
+ downloadedImages.push({
4009
+ path: `images/${filename}`,
4010
+ url: img.url,
4011
+ width: img.width,
4012
+ height: img.height,
4013
+ query: topic
4014
+ });
4015
+ } catch (err) {
4016
+ if (format === "human") {
4017
+ spinner?.stop();
4018
+ warn(`Failed to download image ${i + 1}: ${err instanceof Error ? err.message : "Unknown error"}`);
4019
+ spinner?.start();
4020
+ }
4021
+ }
4022
+ }
4023
+ if (format === "human") {
4024
+ spinner?.stop();
4025
+ success(`Images: Downloaded ${downloadedImages.length} images to ${imagesDir}`);
4026
+ spinner?.start();
4027
+ }
4028
+ if (spinner) spinner.text = "Writing manifest...";
4029
+ const manifest = {
4030
+ topic,
4031
+ script,
4032
+ voiceover: voiceoverInfo,
4033
+ music: musicInfo,
4034
+ images: downloadedImages,
4035
+ totalCost,
4036
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
4037
+ };
4038
+ const manifestPath = join2(options.output, "video-manifest.json");
4039
+ await writeFile5(manifestPath, JSON.stringify(manifest, null, 2));
4040
+ spinner?.stop();
4041
+ if (format === "json") {
4042
+ printJson(manifest);
4043
+ return;
4044
+ }
4045
+ if (format === "quiet") {
4046
+ console.log(manifestPath);
4047
+ return;
4048
+ }
4049
+ console.log();
4050
+ success("Video assets created successfully!");
4051
+ console.log();
4052
+ info(`Topic: ${topic}`);
4053
+ info(`Voiceover: ${voiceoverInfo.path} (${voiceoverInfo.duration.toFixed(1)}s, ${voiceoverInfo.voice})`);
4054
+ info(`Music: ${musicInfo.path} (${musicInfo.duration}s)`);
4055
+ info(`Images: ${downloadedImages.length} downloaded`);
4056
+ info(`Manifest: ${manifestPath}`);
4057
+ console.log();
4058
+ info(`Total cost: $${totalCost.toFixed(4)}`);
4059
+ console.log();
4060
+ info("Next steps:");
4061
+ info(" 1. Create Remotion project (see remotion-best-practices skill)");
4062
+ info(" 2. Use manifest data to configure video composition");
4063
+ info(" 3. Run: npm start (preview) / npm run render (build)");
4064
+ } catch (err) {
4065
+ spinner?.stop();
4066
+ error(err instanceof Error ? err.message : "Unknown error");
4067
+ process.exit(EXIT_CODES.GENERAL_ERROR);
4068
+ }
4069
+ });
4070
+ var searchCommand2 = new Command19("search").description("Search for stock videos").argument("<query>", "Search query").option("-n, --max-results <count>", "Maximum number of results", "10").option("-o, --orientation <type>", "Video orientation: landscape, portrait, square, any", "any").option("-l, --license <type>", "License type: free, premium, any", "any").option("-f, --format <format>", "Output format: human, json, quiet", "human").action(async (query, options) => {
4071
+ const { maxResults, orientation, license, format } = options;
4072
+ const spinner = format === "human" ? ora12("Searching for videos...").start() : null;
4073
+ try {
4074
+ const result = await searchVideos({
4075
+ query,
4076
+ options: {
4077
+ maxResults: parseInt(maxResults, 10),
4078
+ orientation,
4079
+ license
4080
+ }
4081
+ });
4082
+ spinner?.stop();
4083
+ const allVideos = result.data.results.flatMap((provider) => provider.results);
4084
+ if (format === "json") {
4085
+ printJson(result);
4086
+ return;
4087
+ }
4088
+ if (format === "quiet") {
4089
+ allVideos.forEach((video) => {
4090
+ console.log(video.previewUrl || video.thumbnailUrl);
4091
+ });
4092
+ return;
4093
+ }
4094
+ if (allVideos.length === 0) {
4095
+ info("No videos found");
4096
+ return;
4097
+ }
4098
+ success(`Found ${allVideos.length} videos for "${query}"`);
4099
+ console.log();
4100
+ allVideos.forEach((video, index) => {
4101
+ console.log(`[${index + 1}] ${video.title}`);
4102
+ console.log(` URL: ${video.previewUrl || video.thumbnailUrl}`);
4103
+ console.log(` Duration: ${video.duration}s | Size: ${video.width}x${video.height}`);
4104
+ console.log(` Provider: ${video.provider}`);
4105
+ console.log();
4106
+ });
4107
+ info(`Total cost: $${result.data.totalCost.toFixed(4)}`);
4108
+ } catch (err) {
4109
+ spinner?.stop();
4110
+ error(err instanceof Error ? err.message : "Unknown error");
4111
+ process.exit(EXIT_CODES.GENERAL_ERROR);
4112
+ }
4113
+ });
4114
+ var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(createCommand2).addCommand(searchCommand2);
4115
+
3298
4116
  // src/index.ts
3299
- var VERSION = "0.1.4";
3300
- var program = new Command15();
4117
+ var VERSION = "0.1.5";
4118
+ var program = new Command20();
3301
4119
  var cmdName = brand.commands[0];
3302
4120
  program.name(cmdName).description(brand.description).version(VERSION, "-v, --version", "Show version number").option("--debug", "Enable debug logging").option("--no-color", "Disable colored output").configureOutput({
3303
4121
  outputError: (str, write) => {
@@ -3317,6 +4135,11 @@ program.addCommand(brandingCommand);
3317
4135
  program.addCommand(ideasCommand);
3318
4136
  program.addCommand(whoamiCommand);
3319
4137
  program.addCommand(skillCommand);
4138
+ program.addCommand(ttsCommand);
4139
+ program.addCommand(musicCommand);
4140
+ program.addCommand(mixAudioCommand);
4141
+ program.addCommand(imageCommand);
4142
+ program.addCommand(videoCommand);
3320
4143
  var deriveCommand = buildDeriveCommand();
3321
4144
  if (deriveCommand.commands.length > 0) {
3322
4145
  program.addCommand(deriveCommand);