@conceptcraft/mindframes 0.1.3 → 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
@@ -1255,11 +1395,13 @@ var BRANDS = {
1255
1395
  conceptcraft: {
1256
1396
  id: "conceptcraft",
1257
1397
  name: "conceptcraft",
1258
- displayName: "Conceptcraft",
1259
- description: "CLI tool for Conceptcraft presentation generation",
1398
+ displayName: "ConceptCraft",
1399
+ description: "CLI tool for ConceptCraft presentation generation",
1260
1400
  commands: ["cc", "conceptcraft"],
1261
1401
  apiUrl: "https://conceptcraft.ai",
1262
- docsUrl: "https://docs.conceptcraft.ai"
1402
+ docsUrl: "https://docs.conceptcraft.ai",
1403
+ packageName: "@conceptcraft/cli",
1404
+ configDir: ".conceptcraft"
1263
1405
  },
1264
1406
  mindframes: {
1265
1407
  id: "mindframes",
@@ -1268,7 +1410,9 @@ var BRANDS = {
1268
1410
  description: "CLI tool for Mindframes presentation generation",
1269
1411
  commands: ["mf", "mindframes"],
1270
1412
  apiUrl: "https://mindframes.app",
1271
- docsUrl: "https://docs.mindframes.app"
1413
+ docsUrl: "https://docs.mindframes.app",
1414
+ packageName: "@mindframes/cli",
1415
+ configDir: ".mindframes"
1272
1416
  }
1273
1417
  };
1274
1418
  var COMMAND_TO_BRAND = {
@@ -2981,12 +3125,17 @@ import chalk12 from "chalk";
2981
3125
  import { mkdirSync, writeFileSync, existsSync as existsSync2 } from "fs";
2982
3126
  import { join } from "path";
2983
3127
  import { homedir } from "os";
2984
- var SKILL_CONTENT = `---
2985
- name: conceptcraft
3128
+ function generateSkillContent(b) {
3129
+ const cmd2 = b.name;
3130
+ const pkg = b.packageName;
3131
+ const url = b.apiUrl;
3132
+ const name = b.displayName;
3133
+ return `---
3134
+ name: ${cmd2}
2986
3135
  description: Create AI-powered presentations from code, documentation, files, or any content. Use when the user wants to generate slides, presentations, or decks about their project, codebase, research, or ideas.
2987
3136
  ---
2988
3137
 
2989
- # ConceptCraft CLI
3138
+ # ${name} CLI
2990
3139
 
2991
3140
  Create professional presentations directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content.
2992
3141
 
@@ -2994,16 +3143,16 @@ Create professional presentations directly from your terminal. The CLI generates
2994
3143
 
2995
3144
  \`\`\`bash
2996
3145
  # Install globally
2997
- npm install -g @conceptcraft/cli
3146
+ npm install -g ${pkg}
2998
3147
 
2999
- # Configure API key (get from https://conceptcraft.ai/settings/api-keys)
3000
- mindframes config init
3148
+ # Configure API key (get from ${url}/settings/api-keys)
3149
+ ${cmd2} config init
3001
3150
  \`\`\`
3002
3151
 
3003
3152
  ## Core Workflow
3004
3153
 
3005
3154
  1. **Gather context** - Read relevant files, code, or documentation
3006
- 2. **Create presentation** - Pass context to \`conceptcraft create\`
3155
+ 2. **Create presentation** - Pass context to \`${cmd2} create\`
3007
3156
  3. **Share URL** - Return the presentation link to the user
3008
3157
 
3009
3158
  ## Commands
@@ -3014,22 +3163,22 @@ Context is **required**. Provide it via one of these methods:
3014
3163
 
3015
3164
  \`\`\`bash
3016
3165
  # Upload files (PDFs, PPTX, images, docs)
3017
- conceptcraft create "Product Overview" --file ./deck.pptx --file ./logo.png
3166
+ ${cmd2} create "Product Overview" --file ./deck.pptx --file ./logo.png
3018
3167
 
3019
3168
  # Direct text context
3020
- conceptcraft create "Topic Title" --context "Key points, data, facts..."
3169
+ ${cmd2} create "Topic Title" --context "Key points, data, facts..."
3021
3170
 
3022
3171
  # From a text file
3023
- conceptcraft create "Topic Title" --context-file ./notes.md
3172
+ ${cmd2} create "Topic Title" --context-file ./notes.md
3024
3173
 
3025
3174
  # Pipe content (auto-detected)
3026
- cat README.md | conceptcraft create "Project Overview"
3175
+ cat README.md | ${cmd2} create "Project Overview"
3027
3176
 
3028
3177
  # From URLs (scraped automatically)
3029
- conceptcraft create "Competitor Analysis" --sources https://example.com/report
3178
+ ${cmd2} create "Competitor Analysis" --sources https://example.com/report
3030
3179
 
3031
3180
  # Combine multiple sources
3032
- cat src/auth/*.ts | conceptcraft create "Auth System" \\
3181
+ cat src/auth/*.ts | ${cmd2} create "Auth System" \\
3033
3182
  --file ./architecture.png \\
3034
3183
  --context "Focus on security patterns"
3035
3184
  \`\`\`
@@ -3054,28 +3203,28 @@ cat src/auth/*.ts | conceptcraft create "Auth System" \\
3054
3203
 
3055
3204
  \`\`\`bash
3056
3205
  # Check authentication
3057
- conceptcraft whoami
3206
+ ${cmd2} whoami
3058
3207
 
3059
3208
  # List presentations
3060
- conceptcraft list
3061
- conceptcraft list --format json
3209
+ ${cmd2} list
3210
+ ${cmd2} list --format json
3062
3211
 
3063
3212
  # Get presentation details
3064
- conceptcraft get <id-or-slug>
3213
+ ${cmd2} get <id-or-slug>
3065
3214
 
3066
3215
  # Export to ZIP
3067
- conceptcraft export <id-or-slug> -o presentation.zip
3216
+ ${cmd2} export <id-or-slug> -o presentation.zip
3068
3217
 
3069
3218
  # Import presentation
3070
- conceptcraft import ./presentation.zip
3219
+ ${cmd2} import ./presentation.zip
3071
3220
 
3072
3221
  # Manage branding
3073
- conceptcraft branding list
3074
- conceptcraft branding extract https://company.com
3222
+ ${cmd2} branding list
3223
+ ${cmd2} branding extract https://company.com
3075
3224
 
3076
3225
  # Install/manage this skill
3077
- conceptcraft skill install
3078
- conceptcraft skill show
3226
+ ${cmd2} skill install
3227
+ ${cmd2} skill show
3079
3228
  \`\`\`
3080
3229
 
3081
3230
  ## Examples
@@ -3084,7 +3233,7 @@ conceptcraft skill show
3084
3233
 
3085
3234
  \`\`\`bash
3086
3235
  # Read the relevant files and create presentation
3087
- cat src/lib/auth.ts src/lib/session.ts | conceptcraft create "Authentication System" \\
3236
+ cat src/lib/auth.ts src/lib/session.ts | ${cmd2} create "Authentication System" \\
3088
3237
  --slides 8 --tone educational --audience "New developers" \\
3089
3238
  --goal train
3090
3239
  \`\`\`
@@ -3092,7 +3241,7 @@ cat src/lib/auth.ts src/lib/session.ts | conceptcraft create "Authentication Sys
3092
3241
  ### Technical Documentation with Diagrams
3093
3242
 
3094
3243
  \`\`\`bash
3095
- conceptcraft create "API Reference" \\
3244
+ ${cmd2} create "API Reference" \\
3096
3245
  --file ./docs/api.md \\
3097
3246
  --file ./diagrams/architecture.png \\
3098
3247
  --mode best --amount detailed \\
@@ -3102,14 +3251,14 @@ conceptcraft create "API Reference" \\
3102
3251
  ### Quick Project Overview
3103
3252
 
3104
3253
  \`\`\`bash
3105
- cat README.md package.json | conceptcraft create "Project Introduction" \\
3254
+ cat README.md package.json | ${cmd2} create "Project Introduction" \\
3106
3255
  -m instant --slides 5
3107
3256
  \`\`\`
3108
3257
 
3109
3258
  ### Sales Deck from Existing Presentation
3110
3259
 
3111
3260
  \`\`\`bash
3112
- conceptcraft create "Product Demo" \\
3261
+ ${cmd2} create "Product Demo" \\
3113
3262
  --file ./existing-deck.pptx \\
3114
3263
  --goal persuade \\
3115
3264
  --audience "Enterprise buyers" \\
@@ -3119,7 +3268,7 @@ conceptcraft create "Product Demo" \\
3119
3268
  ### Research Presentation
3120
3269
 
3121
3270
  \`\`\`bash
3122
- conceptcraft create "Market Analysis" \\
3271
+ ${cmd2} create "Market Analysis" \\
3123
3272
  --file ./research.pdf \\
3124
3273
  --sources https://report.com/industry.pdf \\
3125
3274
  --tone formal --audience "Executive team" \\
@@ -3136,12 +3285,12 @@ Successful creation returns:
3136
3285
  Slides: 8
3137
3286
  Generated in: 45s \xB7 12,500 tokens
3138
3287
 
3139
- Open: https://conceptcraft.ai/en/view/presentations/auth-system-v1-abc123
3288
+ Open: ${url}/en/view/presentations/auth-system-v1-abc123
3140
3289
  \`\`\`
3141
3290
 
3142
3291
  For scripting, use JSON output:
3143
3292
  \`\`\`bash
3144
- URL=$(conceptcraft create "Demo" --context "..." -o json | jq -r '.viewUrl')
3293
+ URL=$(${cmd2} create "Demo" --context "..." -o json | jq -r '.viewUrl')
3145
3294
  \`\`\`
3146
3295
 
3147
3296
  ## Best Practices
@@ -3163,15 +3312,16 @@ URL=$(conceptcraft create "Demo" --context "..." -o json | jq -r '.viewUrl')
3163
3312
 
3164
3313
  \`\`\`bash
3165
3314
  # Check if authenticated
3166
- conceptcraft whoami
3315
+ ${cmd2} whoami
3167
3316
 
3168
3317
  # Verify API key
3169
- mindframes config show
3318
+ ${cmd2} config show
3170
3319
 
3171
3320
  # Debug mode
3172
- conceptcraft create "Test" --context "test" --debug
3321
+ ${cmd2} create "Test" --context "test" --debug
3173
3322
  \`\`\`
3174
3323
  `;
3324
+ }
3175
3325
  var EDITORS = [
3176
3326
  { name: "Claude Code", dir: ".claude" },
3177
3327
  { name: "Cursor", dir: ".cursor" },
@@ -3180,29 +3330,30 @@ var EDITORS = [
3180
3330
  { name: "Windsurf", dir: ".windsurf" },
3181
3331
  { name: "Agent", dir: ".agent" }
3182
3332
  ];
3183
- var skillCommand = new Command14("skill").description("Manage ConceptCraft skill for AI coding assistants").addHelpText(
3333
+ var skillCommand = new Command14("skill").description(`Manage ${brand.displayName} skill for AI coding assistants`).addHelpText(
3184
3334
  "after",
3185
3335
  `
3186
3336
  ${chalk12.bold("Examples:")}
3187
3337
  ${chalk12.gray("# Install skill for all detected editors")}
3188
- $ conceptcraft skill install
3338
+ $ ${brand.name} skill install
3189
3339
 
3190
3340
  ${chalk12.gray("# Install to specific directory")}
3191
- $ conceptcraft skill install --dir ~/.claude
3341
+ $ ${brand.name} skill install --dir ~/.claude
3192
3342
 
3193
3343
  ${chalk12.gray("# Show skill content")}
3194
- $ conceptcraft skill show
3344
+ $ ${brand.name} skill show
3195
3345
  `
3196
3346
  );
3197
- skillCommand.command("install").description("Install the ConceptCraft skill for AI coding assistants").option("-d, --dir <path>", "Install to specific directory").option("-g, --global", "Install globally (to home directory)", true).option("-l, --local", "Install locally (to current directory)").option("-f, --force", "Overwrite existing skill files").action(async (options) => {
3347
+ skillCommand.command("install").description(`Install the ${brand.displayName} skill for AI coding assistants`).option("-d, --dir <path>", "Install to specific directory").option("-g, --global", "Install globally (to home directory)", true).option("-l, --local", "Install locally (to current directory)").option("-f, --force", "Overwrite existing skill files").action(async (options) => {
3198
3348
  const installed = [];
3199
3349
  const skipped = [];
3200
3350
  const errors = [];
3201
3351
  const baseDir = options.local ? process.cwd() : homedir();
3352
+ const skillContent = generateSkillContent(brand);
3202
3353
  if (options.dir) {
3203
- const skillPath = join(options.dir, "skills", "conceptcraft");
3354
+ const skillPath = join(options.dir, "skills", brand.name);
3204
3355
  try {
3205
- installSkill(skillPath, options.force);
3356
+ installSkill(skillPath, skillContent, options.force);
3206
3357
  installed.push(options.dir);
3207
3358
  } catch (err) {
3208
3359
  errors.push(`${options.dir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3210,7 +3361,7 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
3210
3361
  } else {
3211
3362
  for (const editor of EDITORS) {
3212
3363
  const editorDir = join(baseDir, editor.dir);
3213
- const skillPath = join(editorDir, "skills", "conceptcraft");
3364
+ const skillPath = join(editorDir, "skills", brand.name);
3214
3365
  const skillFile = join(skillPath, "SKILL.md");
3215
3366
  if (!existsSync2(editorDir)) {
3216
3367
  continue;
@@ -3220,7 +3371,7 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
3220
3371
  continue;
3221
3372
  }
3222
3373
  try {
3223
- installSkill(skillPath, options.force);
3374
+ installSkill(skillPath, skillContent, options.force);
3224
3375
  installed.push(editor.name);
3225
3376
  } catch (err) {
3226
3377
  errors.push(`${editor.name}: ${err instanceof Error ? err.message : String(err)}`);
@@ -3253,14 +3404,14 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
3253
3404
  console.log();
3254
3405
  });
3255
3406
  skillCommand.command("show").description("Display the skill content").action(() => {
3256
- console.log(SKILL_CONTENT);
3407
+ console.log(generateSkillContent(brand));
3257
3408
  });
3258
- skillCommand.command("uninstall").description("Remove the ConceptCraft skill from AI coding assistants").option("-g, --global", "Uninstall globally (from home directory)", true).option("-l, --local", "Uninstall locally (from current directory)").action(async (options) => {
3409
+ skillCommand.command("uninstall").description(`Remove the ${brand.displayName} skill from AI coding assistants`).option("-g, --global", "Uninstall globally (from home directory)", true).option("-l, --local", "Uninstall locally (from current directory)").action(async (options) => {
3259
3410
  const { rmSync } = await import("fs");
3260
3411
  const removed = [];
3261
3412
  const baseDir = options.local ? process.cwd() : homedir();
3262
3413
  for (const editor of EDITORS) {
3263
- const skillPath = join(baseDir, editor.dir, "skills", "conceptcraft");
3414
+ const skillPath = join(baseDir, editor.dir, "skills", brand.name);
3264
3415
  if (existsSync2(skillPath)) {
3265
3416
  try {
3266
3417
  rmSync(skillPath, { recursive: true });
@@ -3278,15 +3429,693 @@ skillCommand.command("uninstall").description("Remove the ConceptCraft skill fro
3278
3429
  }
3279
3430
  console.log();
3280
3431
  });
3281
- function installSkill(skillPath, force) {
3432
+ function installSkill(skillPath, content, force) {
3282
3433
  const skillFile = join(skillPath, "SKILL.md");
3283
3434
  mkdirSync(skillPath, { recursive: true });
3284
- writeFileSync(skillFile, SKILL_CONTENT, "utf-8");
3435
+ writeFileSync(skillFile, content, "utf-8");
3436
+ }
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));
3285
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);
3286
4115
 
3287
4116
  // src/index.ts
3288
- var VERSION = "0.1.3";
3289
- var program = new Command15();
4117
+ var VERSION = "0.1.5";
4118
+ var program = new Command20();
3290
4119
  var cmdName = brand.commands[0];
3291
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({
3292
4121
  outputError: (str, write) => {
@@ -3306,6 +4135,11 @@ program.addCommand(brandingCommand);
3306
4135
  program.addCommand(ideasCommand);
3307
4136
  program.addCommand(whoamiCommand);
3308
4137
  program.addCommand(skillCommand);
4138
+ program.addCommand(ttsCommand);
4139
+ program.addCommand(musicCommand);
4140
+ program.addCommand(mixAudioCommand);
4141
+ program.addCommand(imageCommand);
4142
+ program.addCommand(videoCommand);
3309
4143
  var deriveCommand = buildDeriveCommand();
3310
4144
  if (deriveCommand.commands.length > 0) {
3311
4145
  program.addCommand(deriveCommand);