@conceptcraft/mindframes 0.1.9 → 0.1.11

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
@@ -1,17 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __esm = (fn, res) => function __init() {
6
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
- };
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
3
+
4
+ // src/index.ts
5
+ import { Command as Command20 } from "commander";
6
+ import chalk13 from "chalk";
12
7
 
13
8
  // src/lib/brand.ts
14
9
  import path from "path";
10
+ var BRANDS = {
11
+ conceptcraft: {
12
+ id: "conceptcraft",
13
+ name: "conceptcraft",
14
+ displayName: "ConceptCraft",
15
+ description: "CLI tool for ConceptCraft presentation generation",
16
+ commands: ["cc", "conceptcraft"],
17
+ apiUrl: "https://conceptcraft.ai",
18
+ docsUrl: "https://docs.conceptcraft.ai",
19
+ packageName: "@conceptcraft/cli",
20
+ configDir: ".conceptcraft",
21
+ apiKeyEnvVar: "CONCEPTCRAFT_API_KEY",
22
+ apiUrlEnvVar: "CONCEPTCRAFT_API_URL"
23
+ },
24
+ mindframes: {
25
+ id: "mindframes",
26
+ name: "mindframes",
27
+ displayName: "Mindframes",
28
+ description: "CLI tool for Mindframes presentation generation",
29
+ commands: ["mf", "mindframes"],
30
+ apiUrl: "https://mindframes.app",
31
+ docsUrl: "https://docs.mindframes.app",
32
+ packageName: "@conceptcraft/mindframes",
33
+ configDir: ".mindframes",
34
+ apiKeyEnvVar: "MINDFRAMES_API_KEY",
35
+ apiUrlEnvVar: "MINDFRAMES_API_URL"
36
+ }
37
+ };
38
+ var COMMAND_TO_BRAND = {
39
+ cc: "conceptcraft",
40
+ conceptcraft: "conceptcraft",
41
+ mf: "mindframes",
42
+ mindframes: "mindframes"
43
+ };
15
44
  function detectBrand() {
16
45
  const binaryPath = process.argv[1] || "";
17
46
  const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
@@ -26,50 +55,52 @@ function detectBrand() {
26
55
  }
27
56
  return BRANDS.mindframes;
28
57
  }
29
- var BRANDS, COMMAND_TO_BRAND, brand;
30
- var init_brand = __esm({
31
- "src/lib/brand.ts"() {
32
- "use strict";
33
- BRANDS = {
34
- conceptcraft: {
35
- id: "conceptcraft",
36
- name: "conceptcraft",
37
- displayName: "ConceptCraft",
38
- description: "CLI tool for ConceptCraft presentation generation",
39
- commands: ["cc", "conceptcraft"],
40
- apiUrl: "https://conceptcraft.ai",
41
- docsUrl: "https://docs.conceptcraft.ai",
42
- packageName: "@conceptcraft/cli",
43
- configDir: ".conceptcraft",
44
- apiKeyEnvVar: "CONCEPTCRAFT_API_KEY",
45
- apiUrlEnvVar: "CONCEPTCRAFT_API_URL"
46
- },
47
- mindframes: {
48
- id: "mindframes",
49
- name: "mindframes",
50
- displayName: "Mindframes",
51
- description: "CLI tool for Mindframes presentation generation",
52
- commands: ["mf", "mindframes"],
53
- apiUrl: "https://mindframes.app",
54
- docsUrl: "https://docs.mindframes.app",
55
- packageName: "@conceptcraft/mindframes",
56
- configDir: ".mindframes",
57
- apiKeyEnvVar: "MINDFRAMES_API_KEY",
58
- apiUrlEnvVar: "MINDFRAMES_API_URL"
59
- }
60
- };
61
- COMMAND_TO_BRAND = {
62
- cc: "conceptcraft",
63
- conceptcraft: "conceptcraft",
64
- mf: "mindframes",
65
- mindframes: "mindframes"
66
- };
67
- brand = detectBrand();
68
- }
69
- });
58
+ var brand = detectBrand();
59
+
60
+ // src/commands/login.ts
61
+ import { Command } from "commander";
62
+ import chalk3 from "chalk";
63
+ import ora from "ora";
64
+ import http from "http";
65
+ import { randomBytes, createHash } from "crypto";
66
+ import open from "open";
70
67
 
71
68
  // src/lib/config.ts
72
69
  import Conf from "conf";
70
+ var DEFAULT_API_URL = "https://www.mindframes.app";
71
+ var schema = {
72
+ apiKey: {
73
+ type: "string"
74
+ },
75
+ apiUrl: {
76
+ type: "string",
77
+ default: DEFAULT_API_URL
78
+ },
79
+ defaultTeamId: {
80
+ type: "string"
81
+ },
82
+ // OAuth tokens (preferred over API key)
83
+ accessToken: {
84
+ type: "string"
85
+ },
86
+ refreshToken: {
87
+ type: "string"
88
+ },
89
+ tokenExpiresAt: {
90
+ type: "number"
91
+ },
92
+ // OAuth client registration (cached per-server)
93
+ clientId: {
94
+ type: "string"
95
+ },
96
+ clientSecret: {
97
+ type: "string"
98
+ }
99
+ };
100
+ var config = new Conf({
101
+ projectName: brand.name,
102
+ schema
103
+ });
73
104
  function getConfig() {
74
105
  return {
75
106
  apiKey: getApiKey(),
@@ -151,47 +182,6 @@ function setOAuthClient(clientId, clientSecret) {
151
182
  config.set("clientId", clientId);
152
183
  config.set("clientSecret", clientSecret);
153
184
  }
154
- var DEFAULT_API_URL, schema, config;
155
- var init_config = __esm({
156
- "src/lib/config.ts"() {
157
- "use strict";
158
- init_brand();
159
- DEFAULT_API_URL = "https://www.mindframes.app";
160
- schema = {
161
- apiKey: {
162
- type: "string"
163
- },
164
- apiUrl: {
165
- type: "string",
166
- default: DEFAULT_API_URL
167
- },
168
- defaultTeamId: {
169
- type: "string"
170
- },
171
- // OAuth tokens (preferred over API key)
172
- accessToken: {
173
- type: "string"
174
- },
175
- refreshToken: {
176
- type: "string"
177
- },
178
- tokenExpiresAt: {
179
- type: "number"
180
- },
181
- // OAuth client registration (cached per-server)
182
- clientId: {
183
- type: "string"
184
- },
185
- clientSecret: {
186
- type: "string"
187
- }
188
- };
189
- config = new Conf({
190
- projectName: brand.name,
191
- schema
192
- });
193
- }
194
- });
195
185
 
196
186
  // src/lib/output.ts
197
187
  import chalk from "chalk";
@@ -358,40 +348,25 @@ function formatJson(data) {
358
348
  function printJson(data) {
359
349
  console.log(formatJson(data));
360
350
  }
361
- var init_output = __esm({
362
- "src/lib/output.ts"() {
363
- "use strict";
364
- init_config();
365
- }
366
- });
367
351
 
368
- // src/types/media.ts
369
- var init_media = __esm({
370
- "src/types/media.ts"() {
371
- "use strict";
372
- }
373
- });
352
+ // src/lib/feature-cache.ts
353
+ import Conf2 from "conf";
354
+
355
+ // src/lib/auth.ts
356
+ import chalk2 from "chalk";
374
357
 
375
358
  // src/types/index.ts
376
- var EXIT_CODES;
377
- var init_types = __esm({
378
- "src/types/index.ts"() {
379
- "use strict";
380
- init_media();
381
- EXIT_CODES = {
382
- SUCCESS: 0,
383
- GENERAL_ERROR: 1,
384
- AUTH_ERROR: 2,
385
- NOT_FOUND: 3,
386
- RATE_LIMIT: 4,
387
- NETWORK_ERROR: 5,
388
- INVALID_INPUT: 6
389
- };
390
- }
391
- });
359
+ var EXIT_CODES = {
360
+ SUCCESS: 0,
361
+ GENERAL_ERROR: 1,
362
+ AUTH_ERROR: 2,
363
+ NOT_FOUND: 3,
364
+ RATE_LIMIT: 4,
365
+ NETWORK_ERROR: 5,
366
+ INVALID_INPUT: 6
367
+ };
392
368
 
393
369
  // src/lib/auth.ts
394
- import chalk2 from "chalk";
395
370
  async function refreshAccessToken() {
396
371
  const refreshToken = getRefreshToken();
397
372
  const clientId = getClientId();
@@ -440,21 +415,15 @@ function hasAuth() {
440
415
  }
441
416
  async function requireAuth() {
442
417
  if (!hasAuth()) {
443
- const { confirm: confirm4 } = await import("@inquirer/prompts");
444
- try {
445
- const shouldLogin = await confirm4({
446
- message: chalk2.red("Not authenticated.") + " Log in now?",
447
- default: true
448
- });
449
- if (!shouldLogin) {
450
- console.log(chalk2.gray("\nTip: You can also set CC_MINDFRAMES_API_KEY environment variable."));
451
- process.exit(EXIT_CODES.AUTH_ERROR);
452
- }
453
- const { runLoginFlow: runLoginFlow2 } = await Promise.resolve().then(() => (init_login(), login_exports));
454
- await runLoginFlow2({ browser: true });
455
- } catch {
456
- process.exit(EXIT_CODES.AUTH_ERROR);
457
- }
418
+ const cmd2 = brand.commands[0];
419
+ const envVar = brand.apiKeyEnvVar;
420
+ console.error(chalk2.red("\u2717 Not authenticated."));
421
+ console.error();
422
+ console.error(chalk2.bold("To authenticate, run:"));
423
+ console.error(chalk2.cyan(` ${cmd2} login`));
424
+ console.error();
425
+ console.error(chalk2.gray(`Or set ${envVar} environment variable.`));
426
+ process.exit(EXIT_CODES.AUTH_ERROR);
458
427
  }
459
428
  }
460
429
  function maskApiKey(key) {
@@ -472,13 +441,6 @@ function isValidApiKeyFormat(key) {
472
441
  const validPrefixes = ["cc_slides_", "mcp_", "cc_sk_", "sk_"];
473
442
  return validPrefixes.some((prefix) => key.startsWith(prefix));
474
443
  }
475
- var init_auth = __esm({
476
- "src/lib/auth.ts"() {
477
- "use strict";
478
- init_config();
479
- init_types();
480
- }
481
- });
482
444
 
483
445
  // src/lib/api.ts
484
446
  import { readFileSync, statSync } from "fs";
@@ -495,6 +457,14 @@ async function getAuthHeaders() {
495
457
  }
496
458
  return {};
497
459
  }
460
+ var ApiError = class extends Error {
461
+ constructor(message, statusCode, exitCode = 1) {
462
+ super(message);
463
+ this.statusCode = statusCode;
464
+ this.exitCode = exitCode;
465
+ this.name = "ApiError";
466
+ }
467
+ };
498
468
  async function request(endpoint, options = {}) {
499
469
  const apiUrl = getApiUrl();
500
470
  if (!hasAuth()) {
@@ -870,13 +840,13 @@ async function importPresentation(fileBuffer, fileName, options = {}) {
870
840
  return result;
871
841
  }
872
842
  async function listBrandings() {
873
- return request("/api/branding");
843
+ return request("/api/cli/branding");
874
844
  }
875
845
  async function getBranding(id) {
876
846
  return request(`/api/branding/${id}`);
877
847
  }
878
848
  async function extractBranding(url, teamId) {
879
- return request("/api/branding/extract", {
849
+ return request("/api/cli/branding/extract", {
880
850
  method: "POST",
881
851
  body: { url, teamId }
882
852
  });
@@ -1022,6 +992,51 @@ async function generateSpeech(ttsRequest) {
1022
992
  timestamps
1023
993
  };
1024
994
  }
995
+ async function generateSpeechBatch(batchRequest) {
996
+ const apiUrl = getApiUrl();
997
+ if (!hasAuth()) {
998
+ throw new ApiError(
999
+ `Not authenticated. Run '${brand.commands[0]} login' or set ${brand.apiKeyEnvVar} environment variable.`,
1000
+ 401,
1001
+ 2
1002
+ );
1003
+ }
1004
+ const authHeaders = await getAuthHeaders();
1005
+ let response;
1006
+ try {
1007
+ response = await fetch(`${apiUrl}/api/cli/tts/batch`, {
1008
+ method: "POST",
1009
+ headers: {
1010
+ "Content-Type": "application/json",
1011
+ ...authHeaders
1012
+ },
1013
+ body: JSON.stringify(batchRequest)
1014
+ });
1015
+ } catch (error2) {
1016
+ throw new ApiError(
1017
+ `Network error: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
1018
+ 0,
1019
+ 5
1020
+ );
1021
+ }
1022
+ if (!response.ok) {
1023
+ const errorText = await response.text().catch(() => "Unknown error");
1024
+ let errorMessage;
1025
+ try {
1026
+ const errorJson = JSON.parse(errorText);
1027
+ errorMessage = errorJson.error || errorJson.message || errorText;
1028
+ } catch {
1029
+ errorMessage = errorText;
1030
+ }
1031
+ throw new ApiError(errorMessage, response.status, response.status === 401 ? 2 : 1);
1032
+ }
1033
+ const result = await response.json();
1034
+ result.results = result.results.map((r) => ({
1035
+ ...r,
1036
+ audioData: Buffer.from(r.audioData, "base64")
1037
+ }));
1038
+ return result;
1039
+ }
1025
1040
  async function getVoices() {
1026
1041
  return request("/api/cli/tts");
1027
1042
  }
@@ -1088,26 +1103,14 @@ async function pollForCompletion(checkFn, maxAttempts = 60, intervalMs = 2e3) {
1088
1103
  }
1089
1104
  throw new ApiError("Operation timed out", 408, 1);
1090
1105
  }
1091
- var ApiError;
1092
- var init_api = __esm({
1093
- "src/lib/api.ts"() {
1094
- "use strict";
1095
- init_config();
1096
- init_auth();
1097
- init_brand();
1098
- ApiError = class extends Error {
1099
- constructor(message, statusCode, exitCode = 1) {
1100
- super(message);
1101
- this.statusCode = statusCode;
1102
- this.exitCode = exitCode;
1103
- this.name = "ApiError";
1104
- }
1105
- };
1106
- }
1107
- });
1108
1106
 
1109
1107
  // src/lib/feature-cache.ts
1110
- import Conf2 from "conf";
1108
+ var cache = new Conf2({
1109
+ projectName: "conceptcraft",
1110
+ configName: "feature-cache"
1111
+ });
1112
+ var FRESH_TTL = 60 * 60 * 1e3;
1113
+ var STALE_TTL = 24 * 60 * 60 * 1e3;
1111
1114
  function hashApiKey(key) {
1112
1115
  let hash = 0;
1113
1116
  for (let i = 0; i < key.length; i++) {
@@ -1153,33 +1156,11 @@ function invalidateCache() {
1153
1156
  function getCachePath() {
1154
1157
  return cache.path;
1155
1158
  }
1156
- var cache, FRESH_TTL, STALE_TTL;
1157
- var init_feature_cache = __esm({
1158
- "src/lib/feature-cache.ts"() {
1159
- "use strict";
1160
- init_api();
1161
- init_config();
1162
- cache = new Conf2({
1163
- projectName: "conceptcraft",
1164
- configName: "feature-cache"
1165
- });
1166
- FRESH_TTL = 60 * 60 * 1e3;
1167
- STALE_TTL = 24 * 60 * 60 * 1e3;
1168
- }
1169
- });
1170
1159
 
1171
1160
  // src/commands/login.ts
1172
- var login_exports = {};
1173
- __export(login_exports, {
1174
- loginCommand: () => loginCommand,
1175
- runLoginFlow: () => runLoginFlow
1176
- });
1177
- import { Command } from "commander";
1178
- import chalk3 from "chalk";
1179
- import ora from "ora";
1180
- import http from "http";
1181
- import { randomBytes, createHash } from "crypto";
1182
- import open from "open";
1161
+ var CLI_CLIENT_NAME = "ConceptCraft CLI";
1162
+ var CALLBACK_PORT_START = 8765;
1163
+ var CALLBACK_PORT_END = 8775;
1183
1164
  function generateCodeVerifier() {
1184
1165
  return randomBytes(32).toString("base64url");
1185
1166
  }
@@ -1427,43 +1408,22 @@ async function runLoginFlow(options) {
1427
1408
  throw err;
1428
1409
  }
1429
1410
  }
1430
- var CLI_CLIENT_NAME, CALLBACK_PORT_START, CALLBACK_PORT_END, loginCommand;
1431
- var init_login = __esm({
1432
- "src/commands/login.ts"() {
1433
- "use strict";
1434
- init_config();
1435
- init_output();
1436
- init_feature_cache();
1437
- CLI_CLIENT_NAME = "ConceptCraft CLI";
1438
- CALLBACK_PORT_START = 8765;
1439
- CALLBACK_PORT_END = 8775;
1440
- loginCommand = new Command("login").description("Authenticate with ConceptCraft (opens browser)").option("--no-browser", "Print URL instead of opening browser").action(async (options) => {
1441
- console.log();
1442
- if (hasOAuthTokens()) {
1443
- warn("You are already logged in.");
1444
- info("Run 'conceptcraft logout' to log out first, or continue to re-authenticate.");
1445
- console.log();
1446
- }
1447
- try {
1448
- await runLoginFlow(options);
1449
- process.exit(0);
1450
- } catch {
1451
- process.exit(1);
1452
- }
1453
- });
1411
+ var loginCommand = new Command("login").description("Authenticate with ConceptCraft (opens browser)").option("--no-browser", "Print URL instead of opening browser").action(async (options) => {
1412
+ console.log();
1413
+ if (hasOAuthTokens()) {
1414
+ warn("You are already logged in.");
1415
+ info("Run 'conceptcraft logout' to log out first, or continue to re-authenticate.");
1416
+ console.log();
1417
+ }
1418
+ try {
1419
+ await runLoginFlow(options);
1420
+ process.exit(0);
1421
+ } catch {
1422
+ process.exit(1);
1454
1423
  }
1455
1424
  });
1456
1425
 
1457
- // src/index.ts
1458
- init_brand();
1459
- init_login();
1460
- import { Command as Command20 } from "commander";
1461
- import chalk13 from "chalk";
1462
-
1463
1426
  // src/commands/logout.ts
1464
- init_config();
1465
- init_feature_cache();
1466
- init_output();
1467
1427
  import { Command as Command2 } from "commander";
1468
1428
  import { confirm } from "@inquirer/prompts";
1469
1429
  var logoutCommand = new Command2("logout").description("Log out and clear authentication").option("--all", "Clear all config including API key").action(async (options) => {
@@ -1503,11 +1463,6 @@ var logoutCommand = new Command2("logout").description("Log out and clear authen
1503
1463
  });
1504
1464
 
1505
1465
  // src/commands/config.ts
1506
- init_config();
1507
- init_auth();
1508
- init_api();
1509
- init_feature_cache();
1510
- init_output();
1511
1466
  import { Command as Command3 } from "commander";
1512
1467
  import chalk4 from "chalk";
1513
1468
  import { input, password, confirm as confirm2, select } from "@inquirer/prompts";
@@ -1711,13 +1666,10 @@ var configCommand = new Command3("config").description("Manage CLI configuration
1711
1666
  );
1712
1667
 
1713
1668
  // src/commands/create.ts
1714
- init_api();
1715
- init_auth();
1716
1669
  import { Command as Command4 } from "commander";
1717
1670
  import chalk6 from "chalk";
1718
1671
 
1719
1672
  // src/lib/streaming.ts
1720
- init_output();
1721
1673
  import { createParser } from "eventsource-parser";
1722
1674
  import chalk5 from "chalk";
1723
1675
  import ora3 from "ora";
@@ -1979,7 +1931,6 @@ async function streamTextContent(response, options = {}) {
1979
1931
  }
1980
1932
 
1981
1933
  // src/commands/create.ts
1982
- init_output();
1983
1934
  import { readFileSync as readFileSync2, existsSync } from "fs";
1984
1935
  import { resolve } from "path";
1985
1936
  var VALID_GOALS = [
@@ -2461,10 +2412,6 @@ function isUrl(str) {
2461
2412
  }
2462
2413
 
2463
2414
  // src/commands/list.ts
2464
- init_api();
2465
- init_auth();
2466
- init_config();
2467
- init_output();
2468
2415
  import { Command as Command5 } from "commander";
2469
2416
  var listCommand = new Command5("list").description("List presentations").option("-n, --limit <count>", "Number of results to return", "20").option(
2470
2417
  "-f, --format <format>",
@@ -2528,9 +2475,6 @@ var listCommand = new Command5("list").description("List presentations").option(
2528
2475
  });
2529
2476
 
2530
2477
  // src/commands/get.ts
2531
- init_api();
2532
- init_auth();
2533
- init_output();
2534
2478
  import { Command as Command6 } from "commander";
2535
2479
  import chalk7 from "chalk";
2536
2480
  var getCommand = new Command6("get").description("Get presentation details").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option(
@@ -2573,9 +2517,6 @@ var getCommand = new Command6("get").description("Get presentation details").arg
2573
2517
  });
2574
2518
 
2575
2519
  // src/commands/delete.ts
2576
- init_api();
2577
- init_auth();
2578
- init_output();
2579
2520
  import { Command as Command7 } from "commander";
2580
2521
  import { confirm as confirm3 } from "@inquirer/prompts";
2581
2522
  var deleteCommand = new Command7("delete").description("Delete a presentation").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --force", "Skip confirmation prompt").option("-q, --quiet", "Suppress output").action(async (slug, options) => {
@@ -2611,9 +2552,6 @@ var deleteCommand = new Command7("delete").description("Delete a presentation").
2611
2552
  });
2612
2553
 
2613
2554
  // src/commands/export.ts
2614
- init_api();
2615
- init_auth();
2616
- init_output();
2617
2555
  import { Command as Command8 } from "commander";
2618
2556
  import { writeFile } from "fs/promises";
2619
2557
  import { resolve as resolve2 } from "path";
@@ -2655,10 +2593,6 @@ function formatBytes(bytes) {
2655
2593
  }
2656
2594
 
2657
2595
  // src/commands/import.ts
2658
- init_api();
2659
- init_auth();
2660
- init_output();
2661
- init_config();
2662
2596
  import { Command as Command9 } from "commander";
2663
2597
  import { readFile } from "fs/promises";
2664
2598
  import { resolve as resolve3, basename as basename2 } from "path";
@@ -2748,10 +2682,6 @@ var importCommand = cmd.option("--dry-run", "Validate without importing").option
2748
2682
  });
2749
2683
 
2750
2684
  // src/commands/branding.ts
2751
- init_api();
2752
- init_auth();
2753
- init_config();
2754
- init_output();
2755
2685
  import { Command as Command10 } from "commander";
2756
2686
  import chalk8 from "chalk";
2757
2687
  import ora6 from "ora";
@@ -2925,13 +2855,9 @@ var brandingCommand = new Command10("branding").description("Manage brand profil
2925
2855
  );
2926
2856
 
2927
2857
  // src/commands/derive.ts
2928
- init_api();
2929
- init_auth();
2930
- init_feature_cache();
2931
2858
  import { Command as Command11 } from "commander";
2932
2859
  import chalk9 from "chalk";
2933
2860
  import ora7 from "ora";
2934
- init_output();
2935
2861
  function createBlogCommand() {
2936
2862
  return new Command11("blog").description("Generate a blog post from a presentation").argument("<slug>", "Presentation slug").option("--words <count>", "Target word count (50-2000)", "300").option(
2937
2863
  "--tone <tone>",
@@ -3055,8 +2981,6 @@ function buildDeriveCommand() {
3055
2981
  }
3056
2982
 
3057
2983
  // src/commands/ideas.ts
3058
- init_auth();
3059
- init_output();
3060
2984
  import { Command as Command12 } from "commander";
3061
2985
  import chalk10 from "chalk";
3062
2986
  var ideasCommand = new Command12("ideas").description("Generate presentation topic ideas").option("--context <text>", "Custom context for idea generation").option("--count <n>", "Number of ideas to generate", "5").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
@@ -3080,10 +3004,6 @@ var ideasCommand = new Command12("ideas").description("Generate presentation top
3080
3004
  });
3081
3005
 
3082
3006
  // src/commands/whoami.ts
3083
- init_api();
3084
- init_auth();
3085
- init_feature_cache();
3086
- init_output();
3087
3007
  import { Command as Command13 } from "commander";
3088
3008
  import chalk11 from "chalk";
3089
3009
  var whoamiCommand = new Command13("whoami").description("Show current user and team information").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
@@ -3139,21 +3059,19 @@ var whoamiCommand = new Command13("whoami").description("Show current user and t
3139
3059
  });
3140
3060
 
3141
3061
  // src/commands/skill/index.ts
3142
- init_output();
3143
- init_brand();
3144
3062
  import { Command as Command14 } from "commander";
3145
3063
  import chalk12 from "chalk";
3146
3064
 
3147
3065
  // src/commands/skill/generate-main-skill.ts
3148
3066
  function generateMainSkillContent(context) {
3149
- const { name, cmd: cmd2, displayName } = context;
3150
- const envPrefix = name.toUpperCase().replace(/-/g, "_");
3067
+ const { name, cmd: cmd2 } = context;
3068
+ const envPrefix = name.toUpperCase().replace(/[^A-Z0-9]/g, "_");
3151
3069
  return `---
3152
3070
  name: ${name}
3153
- description: ${displayName} CLI for AI-powered content creation. Use when user needs to create presentations, generate video assets (voiceover, music, images, stock videos), use text-to-speech, mix audio, search stock media, or manage branding. This is the main entry point - load specialized skills (${name}-video, ${name}-presentation) for detailed workflows.
3071
+ description: ${name} CLI for AI-powered content creation. Use when user needs to create presentations, generate video assets (voiceover, music, images, stock videos), use text-to-speech, mix audio, search stock media, or manage branding. This is the main entry point - load specialized skills (${name}-video, ${name}-presentation) for detailed workflows.
3154
3072
  ---
3155
3073
 
3156
- # ${displayName} CLI
3074
+ # ${name} CLI
3157
3075
 
3158
3076
  A comprehensive CLI for AI-powered content creation. Generate presentations, video assets, voiceovers, music, and search stock media - all from your terminal.
3159
3077
 
@@ -3177,14 +3095,20 @@ A comprehensive CLI for AI-powered content creation. Generate presentations, vid
3177
3095
 
3178
3096
  ## Authentication
3179
3097
 
3098
+ **IMPORTANT:** Most commands require authentication. If a command fails with "Not authenticated", run:
3099
+
3180
3100
  \`\`\`bash
3181
- # Login via OAuth (recommended)
3182
3101
  ${cmd2} login
3102
+ \`\`\`
3183
3103
 
3184
- # Or set API key directly
3104
+ This opens a browser for OAuth login. Alternatively, set an API key:
3105
+
3106
+ \`\`\`bash
3185
3107
  export ${envPrefix}_API_KEY="your-key-here"
3108
+ \`\`\`
3186
3109
 
3187
- # Verify authentication
3110
+ **Verify authentication:**
3111
+ \`\`\`bash
3188
3112
  ${cmd2} whoami
3189
3113
  \`\`\`
3190
3114
 
@@ -3560,1040 +3484,276 @@ ${cmd2} --version # Version info
3560
3484
  }
3561
3485
 
3562
3486
  // src/commands/skill/rules/video/content.ts
3563
- var VIDEO_RULE_CONTENTS = [
3564
- {
3565
- filename: "video-creation-guide.md",
3566
- content: `# Video Creation Guide
3567
-
3568
- ### Related Skills
3569
-
3570
- Use these installed skills for implementation details:
3571
- - \`remotion-best-practices\` \u2014 Remotion patterns and API
3572
- - \`threejs-*\` skills \u2014 for R3F/WebGL (particles, 3D elements)
3487
+ var THUMBNAIL_RULES = `Consider creating separate thumbnail component with Remotion (can be captured with remotion still, not used in actual video).
3573
3488
 
3574
- ---
3575
-
3576
- ## Core Rules
3489
+ **High-CTR principles:**
3490
+ - Expressive faces (emotion, not neutral) boost CTR 20-30%
3491
+ - High contrast, bold colors (yellow, orange stand out)
3492
+ - Simple: 3 main elements max (face + text + 1 visual)
3493
+ - Mobile-first: readable at 320px width (70% of views)
3494
+ - Minimal text: 3-5 words, bold legible fonts (60-80px)
3495
+ - Rule of thirds composition
3577
3496
 
3578
- Your task is not "making slideshows" \u2014 you are **simulating a real interface** that obeys cinematic physics.
3497
+ **Specs:** 1280x720, <2MB, 16:9 ratio
3579
3498
 
3580
- ### Hard Constraints
3581
-
3582
- 1. **No scene > 8 seconds** without cut or major action
3583
- 2. **No static pixels** \u2014 everything breathes, drifts, pulses
3584
- 3. **No linear interpolation** \u2014 use \`spring()\` physics
3585
- 4. **Scene overlap 15-20 frames** \u2014 no hard cuts
3586
- 5. **60 FPS mandatory** \u2014 30fps looks choppy
3587
- 6. **No screenshots for UI** \u2014 rebuild in React/CSS
3499
+ Can capture: \`pnpm exec remotion still ThumbnailScene out/thumb.png\`
3500
+ `;
3501
+ var MOTION_DESIGN_GUIDELINES = `# Motion Design Principles
3588
3502
 
3589
- ---
3503
+ **Core Philosophy:** "Atomic, Kinetic Construction" - nothing is static. Elements arrive and leave via physics-based transitions.
3590
3504
 
3591
- ## Code Organization
3505
+ ## Design System Approach
3592
3506
 
3593
- - Create separate files: \`Button.tsx\`, \`Window.tsx\`, \`Cursor.tsx\`
3594
- - Use Zod schemas for props validation
3595
- - Extract animation configs to constants
3507
+ **Separate content from logic:**
3508
+ - Theme object: colors (primary, accent, background, text), fonts, corner radiuses in config
3509
+ - Scene object: define by duration in frames and content type, not timecodes
3510
+ - Avoid hardcoding: colors, text, data values can be passed via props or config file
3596
3511
 
3597
- \`\`\`tsx
3598
- import { spring, interpolate, useCurrentFrame, useVideoConfig } from 'remotion';
3512
+ ## Animation Physics (Spring-Based)
3599
3513
 
3600
- // ALWAYS use spring for element entrances
3601
- // NEVER use magic numbers
3602
- \`\`\`
3514
+ **Spring Pop:**
3515
+ - UI cards, bubbles, logos "pop" with bounce
3516
+ - \`spring()\` function works well: low mass (0.5), moderate damping (10-12), high stiffness (100-200)
3517
+ - Spring value can map to scale (0 to 1) and opacity (0 to 1)
3518
+ - Consider \`transform-origin\` placement (center for bubbles, top for dropdowns)
3603
3519
 
3604
- ---
3520
+ **Kinetic Typography:**
3521
+ - Text entering line-by-line or word-by-word (not all at once)
3522
+ - Can split text into arrays, stagger with delays (index * 5 frames)
3523
+ - \`interpolate()\` works for opacity [0,1] and translateY [20px, 0px] - slide up
3524
+ - Cubic easing works well for slide-up motion
3605
3525
 
3606
- ## Aesthetics (Linear/Stripe Style)
3526
+ **Constructed UI:**
3527
+ - Building UI from HTML/CSS divs works better than screenshots
3528
+ - If user shares project: study actual UI components (buttons, cards, modals) and implement pixel-perfect recreations - match colors, fonts, shadows, border-radius
3529
+ - Bar charts: can animate height/width from 0% to target
3530
+ - Line charts: can animate SVG path \`stroke-dashoffset\`
3531
+ - Donut charts: can animate \`stroke-dasharray\` of SVG circle
3532
+ - Numbers: counter component interpolating from 0 to target over 30-60 frames
3607
3533
 
3608
- \`\`\`css
3609
- /* Shadows - soft, expensive */
3610
- box-shadow: 0 20px 50px -12px rgba(0,0,0,0.5);
3534
+ ## Visual Composition
3611
3535
 
3612
- /* Borders - thin, barely visible */
3613
- border: 1px solid rgba(255,255,255,0.1);
3614
- \`\`\`
3536
+ **Background Ambience:**
3537
+ - Static backgrounds feel flat - consider faint dots/patterns
3538
+ - Slow oscillation works well: \`Math.sin(frame / 100)\` applied to position/rotation for "floating" effect
3539
+ - Parallax adds depth: background moves slower than foreground
3615
3540
 
3616
- - Fonts: Inter or SF Pro
3617
- - Never pure \`#000000\` \u2014 use \`#050505\`
3618
- - Never pure \`#FFFFFF\` \u2014 use \`#F0F0F0\`
3541
+ **SVG Handling:**
3542
+ - Inline SVGs allow control of fill color via theme (better than img tags)
3543
+ - Chat bubbles can be constructed with SVG paths or heavy border-radius
3544
+ - Animating bubble "tail" separately adds polish
3619
3545
 
3620
- ---
3546
+ **Scene Transitions:**
3547
+ - Scenes can slide or camera can "pan" to new area
3548
+ - Slide-out approach: Scene A \`translateX\` 0% to -100%, Scene B 100% to 0%
3549
+ - Spatial pan approach: place scenes on giant canvas, animate parent container transform
3621
3550
 
3622
- ## Self-Check Before Render
3551
+ ## Suggested Component Architecture
3623
3552
 
3624
- - [ ] Camera rig wraps entire scene with drift/zoom
3625
- - [ ] Every UI element uses 2.5D rotation entrance
3626
- - [ ] Cursor moves in curves with overshoot
3627
- - [ ] Lists/grids stagger (never appear all at once)
3628
- - [ ] Background has moving orbs + vignette + noise
3629
- - [ ] Something is moving on EVERY frame
3630
- - [ ] Scene transitions overlap (no hard cuts)
3553
+ Consider these reusable patterns:
3554
+ - KineticText: text, delay, style props - handles word-splitting and stagger
3555
+ - SmartCard: container with Spring Pop entry and glassmorphism styles
3556
+ - AnimatedCounter: from, to, duration props - number ticking
3557
+ - ProgressBar/ChartElement: percentage, color props - growth animation from 0
3631
3558
 
3632
- **If your video looks like PowerPoint with voiceover \u2014 START OVER.**
3633
- `
3634
- },
3635
- {
3636
- filename: "animation-physics.md",
3637
- content: `# Animation Physics
3559
+ **Motion Blur (optional):** Can simulate by stretching element in direction of movement on fast transitions.
3560
+ `;
3561
+ var SVG_ANIMATION_GUIDELINES = `# SVG Line Animation (Write-On Effect)
3638
3562
 
3639
- ## Spring Configurations
3563
+ **Core Concept:** "Invisible Ink Rule" - lines draw in (don't fade in), as if hand-drawn in real-time.
3640
3564
 
3641
- ### Heavy UI (Modals, Sidebars)
3642
- \`\`\`tsx
3643
- config: { mass: 1, stiffness: 100, damping: 15 }
3644
- \`\`\`
3565
+ **Animation approach:**
3566
+ - Setting \`pathLength="1"\` on SVG path elements normalizes length
3567
+ - Animating \`strokeDashoffset\` from 1 (hidden) to 0 (drawn) creates write-on effect
3568
+ - \`strokeDasharray: 1\` with interpolate \`[1, 0]\` over 20-30 frames works well
3569
+ - \`stroke-linecap="round"\` creates friendly hand-drawn look
3645
3570
 
3646
- ### Light UI (Tooltips, Badges)
3647
- \`\`\`tsx
3648
- config: { mass: 0.6, stiffness: 180, damping: 12 }
3649
- \`\`\`
3571
+ **Draw & vanish sequence:**
3572
+ - Draw in: 20 frames (offset 1\u21920)
3573
+ - Hold: 10 frames
3574
+ - Draw out: fade opacity or continue offset
3650
3575
 
3651
- ### Standard (Snappy)
3652
- \`\`\`tsx
3653
- config: { mass: 1, damping: 15, stiffness: 120 }
3654
- \`\`\`
3576
+ **Reusable component pattern:**
3577
+ - Props: path data (d), color, width, delay, type (underline/spark/circle/arrow)
3578
+ - Pre-defined path dictionaries work better than generating random coordinates
3579
+ - Positioning with top/left/scale/rotation props for text accents
3580
+ `;
3581
+ var ASSET_USAGE_GUIDELINES = `# Asset Usage & Optimization
3655
3582
 
3656
- ---
3583
+ **For project-specific videos:** Study the project first - extract logos, colors, fonts, actual UI components. Recreate components pixel-perfect in Remotion (match exact colors, shadows, border-radius, fonts). Use project's actual branding and design system for authentic look.
3657
3584
 
3658
- ## Staggering
3585
+ **CLI provides flexible asset search** - images and videos can be used creatively throughout compositions.
3659
3586
 
3660
- **NEVER show a list all at once.**
3587
+ **Video assets (from CLI video search):**
3588
+ - Full-screen backgrounds (with overlays/text)
3589
+ - Embedded in UI cards or windows alongside text
3590
+ - Picture-in-picture style elements
3591
+ - Background layers with reduced opacity
3592
+ - Transitional footage between scenes
3661
3593
 
3662
- \`\`\`tsx
3663
- const STAGGER_FRAMES = 3; // 3-5 frames between items
3594
+ **Image assets (from CLI image search):**
3595
+ - Scene backgrounds (static or with animation)
3596
+ - Embedded elements within compositions
3597
+ - UI component content (cards, panels)
3598
+ - Layered for depth and parallax effects
3664
3599
 
3665
- {items.map((item, i) => {
3666
- const delay = i * STAGGER_FRAMES;
3667
- const progress = spring({
3668
- frame: frame - delay,
3669
- fps,
3670
- config: { damping: 15, stiffness: 120 },
3671
- });
3600
+ **Dynamic backgrounds (Three.js/WebGL):**
3601
+ - Three.js/React Three Fiber for performance-optimized animated backgrounds
3602
+ - Particle systems, procedural gradients, geometric patterns
3603
+ - SVG animations for abstract shapes and patterns
3604
+ - WebGL shaders for dynamic effects
3605
+ - Combines well with static assets for depth
3672
3606
 
3673
- return (
3674
- <div style={{
3675
- opacity: progress,
3676
- transform: \`translateY(\${interpolate(progress, [0, 1], [20, 0])}px)\`,
3677
- }}>
3678
- {item}
3679
- </div>
3680
- );
3681
- })}
3682
- \`\`\`
3607
+ **Best approach:** Mix CLI assets (images/videos) with generated elements (Three.js, SVG) for rich, performant compositions.
3608
+ `;
3683
3609
 
3610
+ // src/commands/skill/generate-video-skill.ts
3611
+ function generateVideoSkillContent(context) {
3612
+ const { name, cmd: cmd2 } = context;
3613
+ return `---
3614
+ name: ${name}-video
3615
+ description: Use when user asks to create videos (product demos, explainers, social content, promos). Handles video asset generation, Remotion implementation, and thumbnail embedding.
3684
3616
  ---
3685
3617
 
3686
- ## Cursor Movement
3687
-
3688
- **Cursor NEVER moves in straight lines.**
3689
-
3690
- \`\`\`tsx
3691
- const progress = spring({
3692
- frame: frame - startFrame,
3693
- fps,
3694
- config: { damping: 20, stiffness: 80 },
3695
- });
3696
-
3697
- const linearX = interpolate(progress, [0, 1], [start.x, end.x]);
3698
- const linearY = interpolate(progress, [0, 1], [start.y, end.y]);
3618
+ # ${name} Video Creation CLI
3699
3619
 
3700
- // THE ARC: Parabola that peaks mid-travel
3701
- const arcHeight = 100;
3702
- const arcOffset = Math.sin(progress * Math.PI) * arcHeight;
3703
- const cursorY = linearY - arcOffset;
3704
- \`\`\`
3620
+ Generate video assets (voiceover, music, images, stock videos) and render with Remotion.
3705
3621
 
3706
3622
  ---
3707
3623
 
3708
- ## Click Interaction
3624
+ ## Prerequisites
3709
3625
 
3710
- \`\`\`tsx
3711
- // On click:
3712
- const cursorScale = isClicking ? 0.95 : 1;
3713
- const buttonScaleX = isClicking ? 1.02 : 1;
3714
- const buttonScaleY = isClicking ? 0.95 : 1;
3715
- // Release both with spring
3626
+ **Authenticate:**
3627
+ \`\`\`bash
3628
+ ${cmd2} login
3716
3629
  \`\`\`
3717
3630
 
3718
3631
  ---
3719
3632
 
3720
- ## Timing Reference
3721
-
3722
- | Action | Frames (60fps) |
3723
- |--------|----------------|
3724
- | Element entrance | 15-20 |
3725
- | Stagger gap | 3-5 |
3726
- | Hold on key info | 45-60 |
3727
- | Scene transition | 20-30 |
3728
- | Fast interaction | 15-20 |
3729
- `
3730
- },
3731
- {
3732
- filename: "scene-structure.md",
3733
- content: `# Scene Structure
3734
-
3735
- ## SceneWrapper
3736
-
3737
- \`\`\`tsx
3738
- <SceneWrapper
3739
- durationInFrames={300}
3740
- transitionType="slideLeft"
3741
- cameraMotion="panRight"
3742
- >
3743
- <FeatureLayer />
3744
- <CursorLayer />
3745
- <ParticleLayer />
3746
- </SceneWrapper>
3747
- \`\`\`
3633
+ ## Video Creation Workflow
3748
3634
 
3749
- ---
3635
+ ### 1. Generate Assets
3750
3636
 
3751
- ## Layer Structure (Z-Index)
3637
+ Generate voiceover, music, and visual assets:
3752
3638
 
3753
- | Layer | Z-Index |
3754
- |-------|---------|
3755
- | Background orbs | 0 |
3756
- | Vignette | 1 |
3757
- | UI Base | 10 |
3758
- | UI Elements | 20 |
3759
- | Overlays | 30 |
3760
- | Text/Captions | 40 |
3761
- | Cursor | 50 |
3639
+ \`\`\`bash
3640
+ cat <<SCENES | ${cmd2} video create --output ./public
3641
+ {
3642
+ "scenes": [
3643
+ {
3644
+ "name": "Hook",
3645
+ "script": "Watch how we transformed this complex workflow into a single click.",
3646
+ "imageQuery": "modern dashboard interface dark theme"
3647
+ },
3648
+ {
3649
+ "name": "Demo",
3650
+ "script": "Our AI analyzes your data in real-time, surfacing insights that matter."
3651
+ }
3652
+ ],
3653
+ "voice": "Kore"
3654
+ }
3655
+ SCENES
3656
+ \`\`\`
3762
3657
 
3763
- ---
3658
+ **Output:**
3659
+ - \`public/audio/*.wav\` - scene voiceovers
3660
+ - \`public/audio/music.mp3\` - background music
3661
+ - \`public/video-manifest.json\` - timing, metadata, timeline
3764
3662
 
3765
- ## Case Study: SaaS Task Tracker
3663
+ ### 2. Initialize Remotion (MANDATORY)
3766
3664
 
3767
- ### Scene 1: "The Hook" (~5s)
3665
+ Scaffold the template and copy assets:
3768
3666
 
3769
- 1. Dark background (\`#0B0C10\`), grid drifting
3770
- 2. Scattered circles magnetically attract \u2192 morph into logo
3771
- 3. Logo expands \u2192 becomes sidebar navigation
3667
+ \`\`\`bash
3668
+ cd .. && ${cmd2} video init my-video
3669
+ cd my-video
3670
+ # Assets are now in public/ directory
3671
+ \`\`\`
3772
3672
 
3773
- ### Scene 2: "Micro-Interaction" (~6s)
3673
+ ### 3. Render Video
3774
3674
 
3775
- 1. Modal "Create Issue" appears
3776
- 2. Text types character by character (non-uniform speed)
3777
- 3. \`CMD + K\` hint glows, keys animate
3778
- 4. Cursor flies to "Save" in arc, slows on approach
3675
+ **For landscape videos (16:9):**
3676
+ \`\`\`bash
3677
+ pnpm exec remotion render FullVideo out/video.mp4
3678
+ \`\`\`
3779
3679
 
3780
- ### Scene 3: "The Connection" (~5s)
3680
+ **For vertical videos (9:16) with captions:**
3681
+ \`\`\`bash
3682
+ pnpm exec remotion render CaptionedVideo out/tiktok.mp4 \\
3683
+ --props='{"timeline":'$(cat public/video-manifest.json | jq -c .timeline)',"showCaptions":true}'
3684
+ \`\`\`
3781
3685
 
3782
- 1. Task card grabbed, scales 1.05, shadow deepens
3783
- 2. Other cards spread apart
3784
- 3. **Match Cut:** Zoom into avatar \u2192 color fills screen \u2192 becomes mobile notification background
3686
+ ### 4. Generate & Inject Thumbnail
3785
3687
 
3786
- ---
3688
+ ${THUMBNAIL_RULES}
3787
3689
 
3788
- ## Composition
3789
-
3790
- \`\`\`tsx
3791
- <AbsoluteFill>
3792
- <MovingBackground />
3793
- <Vignette />
3794
- <CameraRig>
3795
- <Sequence from={0} durationInFrames={100}>
3796
- <Scene1 />
3797
- </Sequence>
3798
- <Sequence from={85} durationInFrames={150}> {/* 15 frame overlap! */}
3799
- <Scene2 />
3800
- </Sequence>
3801
- </CameraRig>
3802
- <Audio src={music} volume={0.3} />
3803
- </AbsoluteFill>
3690
+ \`\`\`bash
3691
+ ${cmd2} video thumbnail out/video.mp4 --image out/thumb.png
3804
3692
  \`\`\`
3805
- `
3806
- },
3807
- {
3808
- filename: "scene-transitions.md",
3809
- content: `# Scene Transitions
3810
-
3811
- **No FadeIn/FadeOut.** Only contextual transitions.
3812
3693
 
3813
3694
  ---
3814
3695
 
3815
- ## Types
3696
+ ${MOTION_DESIGN_GUIDELINES}
3816
3697
 
3817
- ### 1. Object Persistence
3818
- Same chart transforms (data, color, scale) while UI changes around it.
3819
-
3820
- ### 2. Mask Reveal
3821
- Button expands to screen size via SVG \`clipPath\`.
3698
+ ---
3822
3699
 
3823
- ### 3. Speed Ramps
3824
- Scene A accelerates out, Scene B starts fast then slows.
3700
+ ${SVG_ANIMATION_GUIDELINES}
3825
3701
 
3826
3702
  ---
3827
3703
 
3828
- ## Match Cut Example
3704
+ ${ASSET_USAGE_GUIDELINES}
3829
3705
 
3830
- \`\`\`
3831
- Scene A: Zoom into avatar
3832
- \u2193
3833
- Avatar color fills screen
3834
- \u2193
3835
- Scene B: That color IS the notification background
3836
- \`\`\`
3706
+ ---
3707
+ `;
3708
+ }
3837
3709
 
3710
+ // src/commands/skill/generate-presentation-skill.ts
3711
+ function generatePresentationSkillContent(context) {
3712
+ const { name, cmd: cmd2 } = context;
3713
+ return `---
3714
+ name: ${name}-presentation
3715
+ description: Use when user asks to create presentations (slides, decks, pitch decks). Generates AI-powered presentations with structured content and professional design.
3838
3716
  ---
3839
3717
 
3840
- ## Overlapping Sequences (CRITICAL)
3718
+ # ${name} Presentation Creation CLI
3841
3719
 
3842
- \`\`\`tsx
3843
- <Sequence from={0} durationInFrames={100}>
3844
- <SceneOne />
3845
- </Sequence>
3846
- <Sequence from={85} durationInFrames={150}> {/* 15 frames early! */}
3847
- <SceneTwo />
3848
- </Sequence>
3849
- \`\`\`
3720
+ Create professional presentations (slides, decks, pitch decks) directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content. Also supports searching for stock images and videos.
3850
3721
 
3851
3722
  ---
3852
3723
 
3853
- ## TransitionSeries
3854
-
3855
- \`\`\`tsx
3856
- import { TransitionSeries, linearTiming } from '@remotion/transitions';
3857
- import { slide } from '@remotion/transitions/slide';
3858
-
3859
- <TransitionSeries>
3860
- <TransitionSeries.Sequence durationInFrames={100}>
3861
- <SceneOne />
3862
- </TransitionSeries.Sequence>
3863
- <TransitionSeries.Transition
3864
- presentation={slide({ direction: 'from-bottom' })}
3865
- timing={linearTiming({ durationInFrames: 20 })}
3866
- />
3867
- <TransitionSeries.Sequence durationInFrames={150}>
3868
- <SceneTwo />
3869
- </TransitionSeries.Sequence>
3870
- </TransitionSeries>
3871
- \`\`\`
3872
- `
3873
- },
3874
- {
3875
- filename: "polish-effects.md",
3876
- content: `# Polish Effects
3877
-
3878
- ## Reflection (Glass Glint)
3724
+ ## Authentication
3879
3725
 
3880
- Diagonal gradient sweeps every 5 seconds.
3726
+ \`\`\`bash
3727
+ # Login via OAuth
3728
+ ${cmd2} login
3881
3729
 
3882
- \`\`\`tsx
3883
- const cycleFrame = frame % 300;
3884
- const sweepProgress = interpolate(cycleFrame, [0, 60], [-100, 200], {
3885
- extrapolateRight: 'clamp',
3886
- });
3730
+ # Or set API key
3731
+ export ${name.toUpperCase().replace(/-/g, "_")}_API_KEY="your-key-here"
3887
3732
  \`\`\`
3888
3733
 
3889
3734
  ---
3890
3735
 
3891
- ## Background Breathing
3736
+ ## Creating Presentations
3892
3737
 
3893
- Background is NEVER static.
3738
+ ### From Text
3894
3739
 
3895
- \`\`\`tsx
3896
- const orb1X = Math.sin(frame / 60) * 200;
3897
- const orb1Y = Math.cos(frame / 80) * 100;
3740
+ \`\`\`bash
3741
+ ${cmd2} create "AI-powered product analytics platform"
3898
3742
  \`\`\`
3899
3743
 
3900
- ---
3744
+ ### From File
3901
3745
 
3902
- ## Typewriter Effect
3746
+ \`\`\`bash
3747
+ ${cmd2} create --file product-brief.md
3748
+ \`\`\`
3903
3749
 
3904
- \`\`\`tsx
3905
- const charIndex = Math.floor(frame / 3);
3906
- const showCursor = Math.floor(frame / 15) % 2 === 0;
3750
+ ### From URL
3907
3751
 
3908
- <span>
3909
- {text.slice(0, charIndex)}
3910
- {showCursor && <span>|</span>}
3911
- </span>
3752
+ \`\`\`bash
3753
+ ${cmd2} create --url https://company.com/product
3912
3754
  \`\`\`
3913
3755
 
3914
- ---
3915
-
3916
- ## Vignette & Noise
3917
-
3918
- \`\`\`tsx
3919
- // Noise
3920
- <AbsoluteFill style={{
3921
- backgroundImage: 'url(/noise.png)',
3922
- opacity: 0.03,
3923
- mixBlendMode: 'overlay',
3924
- }} />
3925
-
3926
- // Vignette
3927
- <AbsoluteFill style={{
3928
- background: 'radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.8) 100%)',
3929
- }} />
3930
- \`\`\`
3931
- `
3932
- },
3933
- {
3934
- filename: "advanced-techniques.md",
3935
- content: `# Advanced Techniques
3936
-
3937
- ## Audio-Reactive
3938
-
3939
- - **Kick:** \`scale(1.005)\` pulse
3940
- - **Snare:** Trigger scene changes
3941
- - **Hi-hats:** Cursor flicker, particle shimmer
3942
-
3943
- ---
3944
-
3945
- ## Motion Blur (Fake)
3946
-
3947
- \`\`\`tsx
3948
- // Trail: Render 3-4 copies with opacity 0.3, 1-frame delay
3949
-
3950
- // Or drop shadow for fast movement:
3951
- filter: \`drop-shadow(\${velocityX * 0.5}px \${velocityY * 0.5}px 10px rgba(0,0,0,0.3))\`
3952
- \`\`\`
3953
-
3954
- ---
3955
-
3956
- ## 3D Perspective
3957
-
3958
- \`\`\`tsx
3959
- <div style={{ perspective: '1000px' }}>
3960
- <div style={{
3961
- transform: 'rotateX(5deg) rotateY(10deg)',
3962
- transformStyle: 'preserve-3d',
3963
- }}>
3964
- {/* Your UI */}
3965
- </div>
3966
- </div>
3967
- \`\`\`
3968
-
3969
- ---
3970
-
3971
- ## Kinetic Typography
3972
-
3973
- ### Masked Reveal
3974
- \`\`\`tsx
3975
- <div style={{ overflow: 'hidden', height: 80 }}>
3976
- <h1 style={{
3977
- transform: \`translateY(\${interpolate(progress, [0, 1], [100, 0])}%)\`,
3978
- }}>
3979
- INTRODUCING
3980
- </h1>
3981
- </div>
3982
- \`\`\`
3983
-
3984
- ### Keyword Animation
3985
- Animate keywords, not whole sentences.
3986
- `
3987
- },
3988
- {
3989
- filename: "remotion-config.md",
3990
- content: `# Remotion Configuration
3991
-
3992
- ## FPS & Resolution
3993
-
3994
- - **60 FPS mandatory** \u2014 30fps looks choppy
3995
- - **1920\xD71080** Full HD
3996
- - **Center:** \`{x: 960, y: 540}\`
3997
-
3998
- ---
3999
-
4000
- ## Timing
4001
-
4002
- - 1 second = 60 frames
4003
- - Fast interaction = 15-20 frames
4004
- - No scene > 8 seconds without action
4005
-
4006
- ---
4007
-
4008
- ## Entry-Action-Exit Structure
4009
-
4010
- | Phase | Duration |
4011
- |-------|----------|
4012
- | Entry | 0.0s - 0.5s |
4013
- | Action | 0.5s - (duration - 1s) |
4014
- | Exit | last 1s |
4015
-
4016
- ---
4017
-
4018
- ## Font Loading
4019
-
4020
- \`\`\`tsx
4021
- const [handle] = useState(() => delayRender());
4022
-
4023
- useEffect(() => {
4024
- document.fonts.ready.then(() => {
4025
- continueRender(handle);
4026
- });
4027
- }, [handle]);
4028
- \`\`\`
4029
-
4030
- ---
4031
-
4032
- ## Zod Schema
4033
-
4034
- \`\`\`tsx
4035
- export const SceneSchema = z.object({
4036
- titleText: z.string(),
4037
- buttonColor: z.string(),
4038
- cursorPath: z.array(z.object({ x: z.number(), y: z.number() })),
4039
- });
4040
- \`\`\`
4041
-
4042
- ---
4043
-
4044
- ## SaaS Video Kit Components
4045
-
4046
- | Component | Purpose |
4047
- |-----------|---------|
4048
- | \`MockWindow\` | macOS window with traffic lights |
4049
- | \`SmartCursor\` | Bezier curves + click physics |
4050
- | \`NotificationToast\` | Slide in, wait, slide out |
4051
- | \`TypingText\` | Typewriter with cursor |
4052
- | \`Placeholder\` | For logos/icons |
4053
-
4054
- ---
4055
-
4056
- ## Code Rules
4057
-
4058
- 1. No \`transition: all 0.3s\` \u2014 use \`interpolate()\` or \`spring()\`
4059
- 2. Use \`AbsoluteFill\` for layout
4060
- 3. No magic numbers \u2014 extract to constants
4061
- `
4062
- },
4063
- {
4064
- filename: "elite-production.md",
4065
- content: `# Elite Production
4066
-
4067
- For Stripe/Apple/Linear quality.
4068
-
4069
- ---
4070
-
4071
- ## Global Lighting Engine
4072
-
4073
- \`\`\`tsx
4074
- const lightSource = { x: 0.2, y: -0.5 };
4075
- const gradientAngle = Math.atan2(lightSource.y, lightSource.x) * (180 / Math.PI);
4076
-
4077
- <button style={{
4078
- background: \`linear-gradient(\${gradientAngle}deg, rgba(255,255,255,0.1) 0%, transparent 50%)\`,
4079
- borderTop: '1px solid rgba(255,255,255,0.15)',
4080
- boxShadow: \`\${-lightSource.x * 20}px \${-lightSource.y * 20}px 40px rgba(0,0,0,0.3)\`,
4081
- }} />
4082
- \`\`\`
4083
-
4084
- ---
4085
-
4086
- ## Noise & Dithering
4087
-
4088
- Every background needs noise overlay (opacity 0.02-0.05). Prevents YouTube banding.
4089
-
4090
- ---
4091
-
4092
- ## React Three Fiber
4093
-
4094
- For particles, 3D globes \u2014 use WebGL via \`@remotion/three\`, not CSS 3D.
4095
-
4096
- \`\`\`tsx
4097
- import { ThreeCanvas } from '@remotion/three';
4098
-
4099
- <AbsoluteFill>
4100
- <HtmlUI />
4101
- <ThreeCanvas>
4102
- <Particles />
4103
- </ThreeCanvas>
4104
- </AbsoluteFill>
4105
- \`\`\`
4106
-
4107
- See \`threejs-*\` skills for implementation.
4108
-
4109
- ---
4110
-
4111
- ## Virtual Camera Rig
4112
-
4113
- Move camera, not elements:
4114
-
4115
- \`\`\`tsx
4116
- const CameraProvider = ({ children }) => {
4117
- const frame = useCurrentFrame();
4118
- const panX = interpolate(frame, [0, 300], [0, -100]);
4119
- const zoom = interpolate(frame, [0, 300], [1, 1.05]);
4120
-
4121
- return (
4122
- <div style={{
4123
- transform: \`translateX(\${panX}px) scale(\${zoom})\`,
4124
- transformOrigin: 'center',
4125
- }}>
4126
- {children}
4127
- </div>
4128
- );
4129
- };
4130
- \`\`\`
4131
-
4132
- ---
4133
-
4134
- ## Motion Rules
4135
-
4136
- - **Overshoot:** Modal scales to 1.02, settles to 1.0
4137
- - **Overlap:** Scene B starts 15 frames before Scene A ends
4138
- `
4139
- },
4140
- {
4141
- filename: "known-issues.md",
4142
- content: `# Known Issues & Fixes
4143
-
4144
- ## 1. Music Ends Before Video Finishes
4145
-
4146
- **Problem:** Music duration is shorter than video duration, causing awkward silence at the end.
4147
-
4148
- **Solution:** Loop music in Remotion using the \`loop\` prop:
4149
-
4150
- \`\`\`tsx
4151
- import { Audio } from 'remotion';
4152
-
4153
- <Audio src={musicSrc} volume={0.3} loop />
4154
- \`\`\`
4155
-
4156
- **How it works:**
4157
- - Music automatically loops to fill video duration
4158
- - Set volume to 0.3 (30% - less loud than voice)
4159
- - Add fade out at the end for smooth ending
4160
-
4161
- ---
4162
-
4163
- ## 2. Music Transitions Sound Abrupt
4164
-
4165
- **Problem:** Music cuts harshly when scenes change or video ends.
4166
-
4167
- **Fix in Remotion:**
4168
- \`\`\`tsx
4169
- import { interpolate, Audio } from 'remotion';
4170
-
4171
- // Fade music in/out at scene boundaries
4172
- const musicVolume = interpolate(
4173
- frame,
4174
- [0, 30, totalFrames - 60, totalFrames],
4175
- [0, 0.3, 0.3, 0],
4176
- { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
4177
- );
4178
-
4179
- <Audio src={music} volume={musicVolume} />
4180
- \`\`\`
4181
-
4182
- ---
4183
-
4184
- ## 3. Scene Transitions Too Harsh
4185
-
4186
- **Problem:** Scenes change abruptly without smooth transitions.
4187
-
4188
- **Fix:** Use \`@remotion/transitions\` with overlapping:
4189
- \`\`\`tsx
4190
- import { TransitionSeries, springTiming } from '@remotion/transitions';
4191
- import { slide } from '@remotion/transitions/slide';
4192
- import { fade } from '@remotion/transitions/fade';
4193
-
4194
- <TransitionSeries>
4195
- <TransitionSeries.Sequence durationInFrames={sceneA.frames}>
4196
- <SceneA />
4197
- </TransitionSeries.Sequence>
4198
- <TransitionSeries.Transition
4199
- presentation={slide({ direction: 'from-right' })}
4200
- timing={springTiming({ config: { damping: 20 } })}
4201
- />
4202
- <TransitionSeries.Sequence durationInFrames={sceneB.frames}>
4203
- <SceneB />
4204
- </TransitionSeries.Sequence>
4205
- </TransitionSeries>
4206
- \`\`\`
4207
-
4208
- ---
4209
-
4210
- ## 4. Voiceover Lacks Energy
4211
-
4212
- **Problem:** Voiceover sounds flat/monotone.
4213
-
4214
- **Fix:** Pass \`voiceSettings\` in scenes JSON:
4215
- \`\`\`json
4216
- {
4217
- "scenes": [...],
4218
- "voice": "Kore",
4219
- "voiceSettings": {
4220
- "style": 0.6,
4221
- "stability": 0.4,
4222
- "speed": 0.95
4223
- }
4224
- }
4225
- \`\`\`
4226
-
4227
- - \`style\`: 0.5-0.7 for more expressive delivery
4228
- - \`stability\`: 0.3-0.5 for more variation
4229
- - \`speed\`: 0.9-1.0 slightly slower = more impactful
4230
-
4231
- ---
4232
-
4233
- ## 5. Video Duration Mismatch
4234
-
4235
- **Problem:** Brief says 30-45s but video is 20s (because scene duration = voiceover duration).
4236
-
4237
- **Fixes:**
4238
- 1. **Slow voice:** Use \`speed: 0.85\` in voiceSettings
4239
- 2. **Add padding in Remotion:** Hold last frame, add breathing room
4240
- \`\`\`tsx
4241
- // Add 30 frames (0.5s) padding after voiceover ends
4242
- const paddedDuration = voiceoverFrames + 30;
4243
- \`\`\`
4244
- 3. **Brief should note:** "Duration based on voiceover length"
4245
-
4246
- ---
4247
-
4248
- ## 6. Not Using Project UI Components
4249
-
4250
- **Problem:** Generic UI instead of pixel-perfect project components.
4251
-
4252
- **Fix:** In Phase 1 Discovery:
4253
- 1. Find project's actual components (buttons, cards, modals, inputs)
4254
- 2. Copy their styles/structure into Remotion components
4255
- 3. Match colors, fonts, shadows, border-radius exactly
4256
-
4257
- \`\`\`tsx
4258
- // DON'T: Generic button
4259
- <button style={{ background: 'blue' }}>Click</button>
4260
-
4261
- // DO: Match project's actual button
4262
- <button style={{
4263
- background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
4264
- borderRadius: 8,
4265
- padding: '12px 24px',
4266
- boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)',
4267
- border: '1px solid rgba(255,255,255,0.1)',
4268
- }}>Click</button>
4269
- \`\`\`
4270
-
4271
- ---
4272
-
4273
- ## 7. Missing Physics & Lighting
4274
-
4275
- **Problem:** Video feels flat, no depth or motion.
4276
-
4277
- **Checklist:**
4278
- - [ ] Global light source defined (affects all shadows/gradients)
4279
- - [ ] Camera rig with subtle drift/zoom
4280
- - [ ] Spring physics on ALL entrances (no linear)
4281
- - [ ] Staggered animations (never all at once)
4282
- - [ ] Background orbs/particles moving
4283
- - [ ] Noise overlay (opacity 0.02-0.05)
4284
- - [ ] Vignette for depth
4285
- `
4286
- }
4287
- ];
4288
-
4289
- // src/commands/skill/generate-video-skill.ts
4290
- function generateVideoSkillContent(context) {
4291
- const { name, cmd: cmd2, displayName } = context;
4292
- return `---
4293
- name: ${name}-video
4294
- description: Use when user asks to create videos (product demos, explainers, social content, promos). Handles video asset generation, Remotion implementation, and thumbnail embedding.
4295
- ---
4296
-
4297
- # ${displayName} Video Creation CLI
4298
-
4299
- Create professional product videos directly from your terminal. The CLI generates AI-powered video assets (voiceover, music, images, stock videos) and provides tools for Remotion-based video production with React Three Fiber.
4300
-
4301
- **Stack:** Remotion (React video framework) + React Three Fiber (R3F) + Three.js for 3D/WebGL, particles, shaders, lighting.
4302
-
4303
- We create **elite product videos** (Stripe, Apple, Linear quality) using physics-based animation, dynamic lighting, and pixel-perfect UI components rebuilt from the real project \u2014 never boring screenshots or static images.
4304
-
4305
- **Core Philosophy:** "Nothing sits still. Everything is physics-based. Every pixel breathes."
4306
-
4307
- ---
4308
-
4309
- ## CRITICAL: Professional Composition Rules
4310
-
4311
- **These rules are MANDATORY for all marketing/product videos:**
4312
-
4313
- ### \u274C NEVER DO:
4314
- 1. **Walls of text** - No dense paragraphs or lists longer than 3 lines
4315
- 2. **Flying/floating cards** - No random floating animations across the screen
4316
- 3. **Stretched layouts** - No elements awkwardly stretched to fill space
4317
- 4. **Truncated text** - Never show "Text that gets cut off..."
4318
- 5. **Information overload** - Max 1-2 key points visible at once
4319
- 6. **Amateur motion** - No PowerPoint-style "fly in from left/right"
4320
-
4321
- ### \u2705 ALWAYS DO:
4322
- 1. **Hierarchy first** - One clear focal point per scene (headline OR stat OR visual, not all)
4323
- 2. **Breathing room** - Generous whitespace (min 100px padding around elements)
4324
- 3. **Purposeful motion** - Cards appear with subtle spring (0-20px translateY), not fly across screen
4325
- 4. **Readable text** - Max 2-3 lines per card, 24px+ font size
4326
- 5. **Grid alignment** - Use invisible grid (3-column or 4-column layout)
4327
- 6. **Professional entrance** - Elements fade + slight translate (15px max), hold for 2-3s, then exit
4328
-
4329
- ### Composition Examples:
4330
-
4331
- **\u274C BAD - Wall of Text:**
4332
- \`\`\`tsx
4333
- // DON'T: 10 bullet points crammed in a card
4334
- <Card>
4335
- <ul>
4336
- {[...10items].map(item => <li>{item.longText}...</li>)}
4337
- </ul>
4338
- </Card>
4339
- \`\`\`
4340
-
4341
- **\u2705 GOOD - Single Focus:**
4342
- \`\`\`tsx
4343
- // DO: One headline, one supporting stat
4344
- <AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>
4345
- <h1 style={{ fontSize: 72, marginBottom: 40 }}>12 hours wasted</h1>
4346
- <p style={{ fontSize: 28, opacity: 0.7 }}>per week on manual tasks</p>
4347
- </AbsoluteFill>
4348
- \`\`\`
4349
-
4350
- **\u274C BAD - Flying Cards:**
4351
- \`\`\`tsx
4352
- // DON'T: Cards flying from random positions
4353
- <Card style={{
4354
- transform: \`translateX(\${interpolate(progress, [0,1], [-500, 0])}px)\` // Flies from left
4355
- }} />
4356
- \`\`\`
4357
-
4358
- **\u2705 GOOD - Subtle Entrance:**
4359
- \`\`\`tsx
4360
- // DO: Gentle spring entrance with minimal movement
4361
- const progress = spring({ frame: frame - startFrame, fps, config: { damping: 20, stiffness: 100 }});
4362
- <Card style={{
4363
- opacity: progress,
4364
- transform: \`translateY(\${interpolate(progress, [0,1], [15, 0])}px)\` // Subtle 15px drop
4365
- }} />
4366
- \`\`\`
4367
-
4368
- ### Layout Grid System:
4369
-
4370
- **Use 12-column grid (like Bootstrap):**
4371
- \`\`\`tsx
4372
- const GRID = {
4373
- columns: 12,
4374
- gutter: 40,
4375
- padding: 120 // Edge padding
4376
- };
4377
-
4378
- // Center 6 columns for main content
4379
- const contentWidth = (1920 - (GRID.padding * 2) - (GRID.gutter * 5)) / 2;
4380
- \`\`\`
4381
-
4382
- **Positioning anchors:**
4383
- - **Top-left:** Brand logo, context (10% from edges)
4384
- - **Center:** Primary headline/stat/demo (50% transform)
4385
- - **Bottom:** CTA or tagline (10% from bottom)
4386
- - **Never:** Random floating between these zones
4387
-
4388
- ---
4389
-
4390
- ## Prerequisites
4391
-
4392
- Before using this skill, ensure you have:
4393
-
4394
- 1. **Load related skills:**
4395
- \`\`\`
4396
- remotion-best-practices
4397
- threejs-fundamentals
4398
- \`\`\`
4399
-
4400
- 2. **Authenticate:**
4401
- \`\`\`bash
4402
- ${cmd2} login
4403
- \`\`\`
4404
-
4405
- 3. **Remotion installed** (if creating videos):
4406
- \`\`\`bash
4407
- pnpm install remotion @remotion/cli
4408
- \`\`\`
4409
-
4410
- ---
4411
-
4412
- ## Video Creation Workflow
4413
-
4414
- ### Phase 0: Load Skills (MANDATORY)
4415
-
4416
- Before ANY video work, invoke these skills:
4417
- \`\`\`
4418
- remotion-best-practices
4419
- threejs-fundamentals
4420
- \`\`\`
4421
-
4422
- ### Phase 1: Discovery
4423
-
4424
- Explore current directory silently:
4425
- - Understand what the product does (README, docs, code)
4426
- - Find branding: logo, colors, fonts
4427
- - Find UI components to copy into Remotion (buttons, cards, modals, etc.) \u2014 rebuild pixel-perfect, no screenshots
4428
-
4429
- ### Phase 2: Video Brief
4430
-
4431
- Present a brief outline (scenes \u22648s each, duration, assets found) and get user approval before production.
4432
-
4433
- ### Phase 3: Production
4434
-
4435
- 1. **Generate audio assets** - \`${cmd2} video create\` with scenes JSON
4436
- - IMPORTANT: Music is generated LAST after all voiceover/audio to ensure exact duration match
4437
- 2. **Scaffold OUTSIDE project** - \`cd .. && ${cmd2} video init my-video\`
4438
- 3. **Copy assets + UI components** from project into video project
4439
- 4. **Implement** - follow rules below
4440
-
4441
- ### Phase 4: Render & Thumbnail (REQUIRED)
4442
-
4443
- \`\`\`bash
4444
- # 1. Render the video (with voiceover and music already included)
4445
- pnpm exec remotion render FullVideo
4446
-
4447
- # 2. ALWAYS embed thumbnail before delivering
4448
- ${cmd2} video thumbnail out/FullVideo.mp4 --frame 60
4449
- \`\`\`
4450
-
4451
- **Note:** Remotion videos include per-scene voiceovers and background music baked in during render.
4452
-
4453
- ---
4454
-
4455
- ## Asset Generation
4456
-
4457
- Generate voiceover, music, and visual assets for each scene:
4458
-
4459
- \`\`\`bash
4460
- cat <<SCENES | ${cmd2} video create --output ./public
4461
- {
4462
- "scenes": [
4463
- {
4464
- "name": "Hook",
4465
- "script": "Watch how we transformed this complex workflow into a single click.",
4466
- "imageQuery": "modern dashboard interface dark theme",
4467
- "videoQuery": "abstract tech particles animation"
4468
- },
4469
- {
4470
- "name": "Demo",
4471
- "script": "Our AI analyzes your data in real-time, surfacing insights that matter.",
4472
- "imageQuery": "data visualization charts analytics"
4473
- },
4474
- {
4475
- "name": "CTA",
4476
- "script": "Start your free trial today. No credit card required.",
4477
- "imageQuery": "call to action button modern"
4478
- }
4479
- ],
4480
- "voice": "Kore",
4481
- "voiceSettings": {
4482
- "style": 0.6,
4483
- "stability": 0.4,
4484
- "speed": 0.95
4485
- },
4486
- "musicPrompt": "upbeat corporate, positive energy, modern synth"
4487
- }
4488
- SCENES
4489
- \`\`\`
4490
-
4491
- **Output:**
4492
- - \`public/audio/Hook.mp3\` - scene voiceovers
4493
- - \`public/audio/music.mp3\` - background music (30s max)
4494
- - \`public/video-manifest.json\` - timing and metadata
4495
- - Stock images/videos (if requested)
4496
-
4497
- ---
4498
-
4499
- ## Core Video Rules
4500
-
4501
- ${VIDEO_RULE_CONTENTS.map((rule) => rule.content).join("\n\n---\n\n")}
4502
-
4503
- ## Useful Commands
4504
-
4505
- \`\`\`bash
4506
- # Generate video assets
4507
- ${cmd2} video create < scenes.json
4508
- cat scenes.json | ${cmd2} video create --output ./public
4509
-
4510
- # Initialize Remotion project
4511
- ${cmd2} video init my-video
4512
-
4513
- # Embed thumbnail
4514
- ${cmd2} video thumbnail out/video.mp4 --frame 60
4515
-
4516
- # Search for stock assets
4517
- ${cmd2} images search "mountain landscape" --limit 10
4518
- ${cmd2} videos search "ocean waves" --limit 5
4519
-
4520
- # Generate audio
4521
- ${cmd2} audio generate "Your script here" --voice Kore
4522
- ${cmd2} music generate "upbeat corporate" --duration 30
4523
- \`\`\`
4524
-
4525
- ---
4526
-
4527
- ## Best Practices
4528
-
4529
- 1. **Keep scenes under 8 seconds** without cuts or major action
4530
- 2. **Use spring physics** for all animations, never linear
4531
- 3. **Rebuild UI components** in React/CSS, no screenshots
4532
- 4. **Test with thumbnail embedding** before delivering
4533
- 5. **Music volume at 30%** (30-40% less loud than voice)
4534
- 6. **Read all video rules** in Phase 0 before implementation
4535
-
4536
- ---
4537
-
4538
- ## Troubleshooting
4539
-
4540
- If you encounter issues:
4541
- - Check authentication: \`${cmd2} whoami\`
4542
- - Verify asset generation: check \`video-manifest.json\`
4543
- - Voiceover flat: increase style (0.5-0.7), decrease stability (0.3-0.5)
4544
- - Duration mismatch: adjust \`voiceSettings.speed\` or add padding in Remotion
4545
-
4546
- For detailed troubleshooting, see "Known Issues" section above.
4547
- `;
4548
- }
4549
-
4550
- // src/commands/skill/generate-presentation-skill.ts
4551
- function generatePresentationSkillContent(context) {
4552
- const { name, cmd: cmd2, displayName } = context;
4553
- return `---
4554
- name: ${name}-presentation
4555
- description: Use when user asks to create presentations (slides, decks, pitch decks). Generates AI-powered presentations with structured content and professional design.
4556
- ---
4557
-
4558
- # ${displayName} Presentation CLI
4559
-
4560
- Create professional presentations (slides, decks, pitch decks) directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content. Also supports searching for stock images and videos.
4561
-
4562
- ---
4563
-
4564
- ## Authentication
4565
-
4566
- \`\`\`bash
4567
- # Login via OAuth
4568
- ${cmd2} login
4569
-
4570
- # Or set API key
4571
- export ${name.toUpperCase().replace(/-/g, "_")}_API_KEY="your-key-here"
4572
- \`\`\`
4573
-
4574
- ---
4575
-
4576
- ## Creating Presentations
4577
-
4578
- ### From Text
4579
-
4580
- \`\`\`bash
4581
- ${cmd2} create "AI-powered product analytics platform"
4582
- \`\`\`
4583
-
4584
- ### From File
4585
-
4586
- \`\`\`bash
4587
- ${cmd2} create --file product-brief.md
4588
- \`\`\`
4589
-
4590
- ### From URL
4591
-
4592
- \`\`\`bash
4593
- ${cmd2} create --url https://company.com/product
4594
- \`\`\`
4595
-
4596
- ### From Piped Content
3756
+ ### From Piped Content
4597
3757
 
4598
3758
  \`\`\`bash
4599
3759
  cat research.txt | ${cmd2} create
@@ -4822,9 +3982,10 @@ function getSupportedEditorNames() {
4822
3982
  // src/commands/skill/index.ts
4823
3983
  var SKILL_TYPES = ["main", "video", "presentation"];
4824
3984
  var skillContext = {
4825
- name: brand.name,
4826
3985
  cmd: brand.commands[0],
4827
- displayName: brand.displayName
3986
+ pkg: brand.packageName,
3987
+ url: brand.apiUrl,
3988
+ name: brand.displayName
4828
3989
  };
4829
3990
  var skillCommand = new Command14("skill").description(`Manage ${brand.displayName} skills for AI coding assistants`).addHelpText(
4830
3991
  "after",
@@ -4953,9 +4114,6 @@ skillCommand.command("uninstall").description(`Remove ${brand.displayName} skill
4953
4114
  });
4954
4115
 
4955
4116
  // src/commands/tts.ts
4956
- init_api();
4957
- init_output();
4958
- init_types();
4959
4117
  import { Command as Command15 } from "commander";
4960
4118
  import ora8 from "ora";
4961
4119
  import { writeFile as writeFile2 } from "fs/promises";
@@ -5044,9 +4202,6 @@ var voicesCommand = new Command15("voices").description("List available voices")
5044
4202
  var ttsCommand = new Command15("tts").description("Text-to-speech commands").addCommand(generateCommand).addCommand(voicesCommand);
5045
4203
 
5046
4204
  // src/commands/music.ts
5047
- init_api();
5048
- init_output();
5049
- init_types();
5050
4205
  import { Command as Command16 } from "commander";
5051
4206
  import ora9 from "ora";
5052
4207
  import { writeFile as writeFile3 } from "fs/promises";
@@ -5166,9 +4321,6 @@ var statusCommand = new Command16("status").description("Check status of a music
5166
4321
  var musicCommand = new Command16("music").description("Music generation commands").addCommand(generateCommand2).addCommand(statusCommand);
5167
4322
 
5168
4323
  // src/commands/mix.ts
5169
- init_api();
5170
- init_output();
5171
- init_types();
5172
4324
  import { Command as Command17 } from "commander";
5173
4325
  import ora10 from "ora";
5174
4326
  import { writeFile as writeFile4 } from "fs/promises";
@@ -5292,9 +4444,6 @@ var statusCommand2 = new Command17("status").description("Check status of an aud
5292
4444
  var mixAudioCommand = new Command17("mix").description("Audio mixing commands").addCommand(mixCommand).addCommand(statusCommand2);
5293
4445
 
5294
4446
  // src/commands/image.ts
5295
- init_api();
5296
- init_output();
5297
- init_types();
5298
4447
  import { Command as Command18 } from "commander";
5299
4448
  import ora11 from "ora";
5300
4449
  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) => {
@@ -5372,9 +4521,6 @@ var searchCommand = new Command18("search").description("Search for images").req
5372
4521
  var imageCommand = new Command18("image").description("Image search commands").addCommand(searchCommand);
5373
4522
 
5374
4523
  // src/commands/video.ts
5375
- init_api();
5376
- init_output();
5377
- init_types();
5378
4524
  import { Command as Command19 } from "commander";
5379
4525
  import ora12 from "ora";
5380
4526
  import { mkdir, writeFile as writeFile5, readFile as readFile2, access, rm, cp } from "fs/promises";
@@ -5424,6 +4570,13 @@ function calculateSectionTiming(sections, totalDuration, fps = DEFAULT_FPS, time
5424
4570
  const proportion = wordCount / totalWords;
5425
4571
  const durationInSeconds = totalDuration * proportion;
5426
4572
  const durationInFrames = Math.round(durationInSeconds * fps);
4573
+ const chars = text.split("");
4574
+ const charDuration = durationInSeconds / chars.length;
4575
+ const approximateTimestamps = {
4576
+ characters: chars,
4577
+ characterStartTimesSeconds: chars.map((_, i) => i * charDuration),
4578
+ characterEndTimesSeconds: chars.map((_, i) => (i + 1) * charDuration)
4579
+ };
5427
4580
  const section = {
5428
4581
  id: index + 1,
5429
4582
  text,
@@ -5431,7 +4584,9 @@ function calculateSectionTiming(sections, totalDuration, fps = DEFAULT_FPS, time
5431
4584
  startTime: currentTime,
5432
4585
  endTime: currentTime + durationInSeconds,
5433
4586
  durationInSeconds,
5434
- durationInFrames
4587
+ durationInFrames,
4588
+ timestamps: approximateTimestamps
4589
+ // Always include timestamps (approximate if needed)
5435
4590
  };
5436
4591
  currentTime += durationInSeconds;
5437
4592
  return section;
@@ -5458,6 +4613,13 @@ function calculateSectionTimingFromTimestamps(sections, timestamps, fps) {
5458
4613
  const endTime = characterEndTimesSeconds[Math.min(endCharIndex, characterEndTimesSeconds.length - 1)] || startTime + 1;
5459
4614
  const durationInSeconds = endTime - startTime;
5460
4615
  const durationInFrames = Math.round(durationInSeconds * fps);
4616
+ const sectionTimestamps = {
4617
+ characters: characters.slice(startCharIndex, endCharIndex + 1),
4618
+ characterStartTimesSeconds: characterStartTimesSeconds.slice(startCharIndex, endCharIndex + 1).map((t) => t - startTime),
4619
+ // Make relative to section start
4620
+ characterEndTimesSeconds: characterEndTimesSeconds.slice(startCharIndex, endCharIndex + 1).map((t) => t - startTime)
4621
+ // Make relative to section start
4622
+ };
5461
4623
  results.push({
5462
4624
  id: i + 1,
5463
4625
  text: sectionText,
@@ -5465,7 +4627,9 @@ function calculateSectionTimingFromTimestamps(sections, timestamps, fps) {
5465
4627
  startTime,
5466
4628
  endTime,
5467
4629
  durationInSeconds,
5468
- durationInFrames
4630
+ durationInFrames,
4631
+ timestamps: sectionTimestamps
4632
+ // Add section-specific timestamps
5469
4633
  });
5470
4634
  }
5471
4635
  return results;
@@ -5490,6 +4654,64 @@ async function readStdin2() {
5490
4654
  function toFilename(name) {
5491
4655
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
5492
4656
  }
4657
+ function createTimelineFromScenes(scenes) {
4658
+ const timeline = {
4659
+ shortTitle: "Video",
4660
+ elements: [],
4661
+ audio: [],
4662
+ text: []
4663
+ };
4664
+ let zoomIn = true;
4665
+ scenes.forEach((scene) => {
4666
+ const startMs = scene.startTime * 1e3;
4667
+ const endMs = scene.endTime * 1e3;
4668
+ const durationMs = scene.durationInSeconds * 1e3;
4669
+ if (scene.imagePath) {
4670
+ timeline.elements.push({
4671
+ startMs,
4672
+ endMs,
4673
+ imageUrl: scene.imagePath,
4674
+ enterTransition: "blur",
4675
+ exitTransition: "blur",
4676
+ animations: [{
4677
+ type: "scale",
4678
+ from: zoomIn ? 1.3 : 1,
4679
+ to: zoomIn ? 1 : 1.3,
4680
+ startMs: 0,
4681
+ endMs: durationMs
4682
+ }]
4683
+ });
4684
+ zoomIn = !zoomIn;
4685
+ } else if (scene.videoPath) {
4686
+ timeline.elements.push({
4687
+ startMs,
4688
+ endMs,
4689
+ videoUrl: scene.videoPath,
4690
+ enterTransition: "blur",
4691
+ exitTransition: "blur",
4692
+ animations: []
4693
+ });
4694
+ }
4695
+ if (scene.audioPath) {
4696
+ timeline.audio.push({
4697
+ startMs,
4698
+ endMs,
4699
+ audioUrl: scene.audioPath
4700
+ });
4701
+ }
4702
+ if (scene.timestamps) {
4703
+ timeline.text.push({
4704
+ startMs,
4705
+ endMs,
4706
+ text: scene.text,
4707
+ position: "bottom",
4708
+ animations: [],
4709
+ timestamps: scene.timestamps
4710
+ });
4711
+ }
4712
+ });
4713
+ return timeline;
4714
+ }
5493
4715
  async function downloadFile3(url, outputPath) {
5494
4716
  if (url.startsWith("data:")) {
5495
4717
  const matches = url.match(/^data:[^;]+;base64,(.+)$/);
@@ -5519,7 +4741,7 @@ function getExtension(url) {
5519
4741
  }
5520
4742
  return "jpg";
5521
4743
  }
5522
- var createCommand2 = new Command19("create").description("Create video assets (voiceover per scene, music, images)").option("-s, --script <text>", "Narration script (legacy single-script mode)").option("--script-file <path>", "Path to script file (legacy) or scenes JSON").option("-t, --topic <text>", "Topic for image search").option("-v, --voice <name>", "TTS voice (Kore, Puck, Rachel, alloy)", "Kore").option("-m, --music-prompt <text>", "Music description").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) => {
4744
+ var createCommand3 = new Command19("create").description("Create video assets (voiceover per scene, music, images)").option("-s, --script <text>", "Narration script (legacy single-script mode)").option("--script-file <path>", "Path to script file (legacy) or scenes JSON").option("-t, --topic <text>", "Topic for image search").option("-v, --voice <name>", "TTS voice (Kore, Puck, Rachel, alloy)", "Kore").option("-m, --music-prompt <text>", "Music description").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) => {
5523
4745
  const format = options.format;
5524
4746
  const spinner = format === "human" ? ora12("Initializing...").start() : null;
5525
4747
  try {
@@ -5564,21 +4786,27 @@ var createCommand2 = new Command19("create").description("Create video assets (v
5564
4786
  info(`Processing ${scenesInput.scenes.length} scenes...`);
5565
4787
  spinner?.start();
5566
4788
  }
4789
+ const ttsRequests = scenesInput.scenes.map((scene, i) => ({
4790
+ text: scene.script,
4791
+ id: `scene-${i}`
4792
+ }));
4793
+ if (spinner) spinner.text = "Generating speech for all scenes...";
4794
+ const batchResult = await generateSpeechBatch({
4795
+ texts: ttsRequests,
4796
+ options: {
4797
+ voice,
4798
+ voiceSettings: scenesInput.voiceSettings
4799
+ }
4800
+ });
4801
+ totalCost += batchResult.totalCost;
5567
4802
  let currentTime = 0;
5568
4803
  for (let i = 0; i < scenesInput.scenes.length; i++) {
5569
4804
  const scene = scenesInput.scenes[i];
5570
4805
  const filename = toFilename(scene.name);
5571
- if (spinner) spinner.text = `[${scene.name}] Generating speech...`;
5572
- const ttsResult = await generateSpeech({
5573
- text: scene.script,
5574
- options: {
5575
- voice,
5576
- voiceSettings: scenesInput.voiceSettings
5577
- }
5578
- });
4806
+ const ttsResult = batchResult.results[i];
4807
+ if (spinner) spinner.text = `[${scene.name}] Saving audio...`;
5579
4808
  const audioPath = join2(audioDir, `${filename}.${ttsResult.format}`);
5580
4809
  await writeFile5(audioPath, ttsResult.audioData);
5581
- totalCost += ttsResult.cost;
5582
4810
  const durationInSeconds = ttsResult.duration;
5583
4811
  const durationInFrames = Math.round(durationInSeconds * DEFAULT_FPS);
5584
4812
  const sceneData = {
@@ -5590,7 +4818,9 @@ var createCommand2 = new Command19("create").description("Create video assets (v
5590
4818
  endTime: currentTime + durationInSeconds,
5591
4819
  durationInSeconds,
5592
4820
  durationInFrames,
5593
- audioPath: `audio/${filename}.${ttsResult.format}`
4821
+ audioPath: `audio/${filename}.${ttsResult.format}`,
4822
+ timestamps: ttsResult.timestamps
4823
+ // Character-level timestamps for captions
5594
4824
  };
5595
4825
  if (scene.imageQuery) {
5596
4826
  if (spinner) spinner.text = `[${scene.name}] Searching image...`;
@@ -5714,66 +4944,93 @@ var createCommand2 = new Command19("create").description("Create video assets (v
5714
4944
  spinner?.start();
5715
4945
  }
5716
4946
  }
5717
- const musicDuration = Math.min(30, Math.ceil(totalDuration));
4947
+ if (spinner) spinner.text = "Creating timeline...";
4948
+ const timeline = createTimelineFromScenes(scenes);
4949
+ const videoEndTimeMs = Math.max(
4950
+ timeline.audio.length > 0 ? Math.max(...timeline.audio.map((a) => a.endMs)) : 0,
4951
+ timeline.text.length > 0 ? Math.max(...timeline.text.map((t) => t.endMs)) : 0,
4952
+ timeline.elements.length > 0 ? Math.max(...timeline.elements.map((e) => e.endMs)) : 0
4953
+ );
4954
+ const actualVideoDuration = videoEndTimeMs / 1e3;
4955
+ const musicDuration = Math.min(30, Math.ceil(actualVideoDuration));
5718
4956
  console.log(`[Music Generation] Requesting music:`, {
5719
4957
  prompt: musicPrompt,
5720
4958
  requestedDuration: musicDuration,
5721
- totalAudioDuration: totalDuration
5722
- });
5723
- if (spinner) spinner.text = "Generating music...";
5724
- let musicResult = await generateMusic({
5725
- prompt: musicPrompt,
5726
- duration: musicDuration
5727
- });
5728
- if (musicResult.status !== "completed" && musicResult.status !== "failed") {
5729
- if (spinner) spinner.text = `Processing music (ID: ${musicResult.requestId})...`;
5730
- musicResult = await pollForCompletion(
5731
- () => checkMusicStatus(musicResult.requestId),
5732
- 60,
5733
- 2e3
5734
- );
5735
- }
5736
- if (musicResult.status === "failed") {
5737
- spinner?.stop();
5738
- error(`Music generation failed: ${musicResult.error || "Unknown error"}`);
5739
- process.exit(EXIT_CODES.GENERAL_ERROR);
5740
- }
5741
- const musicPath = join2(audioDir, "music.mp3");
5742
- if (musicResult.audioUrl) {
5743
- await downloadFile3(musicResult.audioUrl, musicPath);
5744
- }
5745
- totalCost += musicResult.cost || 0;
5746
- const actualMusicDuration = musicResult.duration || musicDuration;
5747
- console.log(`[Music Generation] Received music:`, {
5748
- requestedDuration: musicDuration,
5749
- returnedDuration: musicResult.duration,
5750
- actualUsedDuration: actualMusicDuration,
5751
4959
  totalAudioDuration: totalDuration,
5752
- difference: actualMusicDuration - totalDuration,
5753
- audioUrl: musicResult.audioUrl?.substring(0, 50) + "..."
4960
+ actualVideoDuration,
4961
+ timelineDurationMs: videoEndTimeMs
5754
4962
  });
5755
- const musicInfo = {
5756
- path: "audio/music.mp3",
5757
- duration: actualMusicDuration,
5758
- prompt: musicPrompt,
5759
- cost: musicResult.cost || 0
5760
- };
5761
- if (format === "human") {
5762
- spinner?.stop();
5763
- success(`Music: ${musicPath} (${musicInfo.duration}s)`);
5764
- if (actualMusicDuration < totalDuration) {
5765
- warn(`Music duration (${actualMusicDuration.toFixed(1)}s) is shorter than video duration (${totalDuration.toFixed(1)}s).`);
5766
- warn(`Consider using audio looping or extending music in Remotion.`);
4963
+ let musicInfo;
4964
+ if (musicDuration < 3) {
4965
+ if (format === "human") {
4966
+ spinner?.stop();
4967
+ warn(`Video duration (${actualVideoDuration.toFixed(1)}s) is too short for music generation (minimum 3s).`);
4968
+ warn(`Skipping music generation...`);
4969
+ spinner?.start();
4970
+ }
4971
+ } else {
4972
+ try {
4973
+ if (spinner) spinner.text = "Generating music...";
4974
+ let musicResult = await generateMusic({
4975
+ prompt: musicPrompt,
4976
+ duration: musicDuration
4977
+ });
4978
+ if (musicResult.status !== "completed" && musicResult.status !== "failed") {
4979
+ if (spinner) spinner.text = `Processing music (ID: ${musicResult.requestId})...`;
4980
+ musicResult = await pollForCompletion(
4981
+ () => checkMusicStatus(musicResult.requestId),
4982
+ 60,
4983
+ 2e3
4984
+ );
4985
+ }
4986
+ if (musicResult.status === "failed") {
4987
+ throw new Error(musicResult.error || "Unknown error");
4988
+ }
4989
+ const musicPath = join2(audioDir, "music.mp3");
4990
+ if (musicResult.audioUrl) {
4991
+ await downloadFile3(musicResult.audioUrl, musicPath);
4992
+ }
4993
+ totalCost += musicResult.cost || 0;
4994
+ const actualMusicDuration = musicResult.duration || musicDuration;
4995
+ console.log(`[Music Generation] Received music:`, {
4996
+ requestedDuration: musicDuration,
4997
+ returnedDuration: musicResult.duration,
4998
+ actualUsedDuration: actualMusicDuration,
4999
+ totalAudioDuration: totalDuration,
5000
+ difference: actualMusicDuration - totalDuration,
5001
+ audioUrl: musicResult.audioUrl?.substring(0, 50) + "..."
5002
+ });
5003
+ musicInfo = {
5004
+ path: "audio/music.mp3",
5005
+ duration: actualMusicDuration,
5006
+ prompt: musicPrompt,
5007
+ cost: musicResult.cost || 0
5008
+ };
5009
+ if (format === "human") {
5010
+ spinner?.stop();
5011
+ success(`Music: ${musicPath} (${musicInfo.duration}s)`);
5012
+ if (actualMusicDuration < actualVideoDuration) {
5013
+ warn(`Music duration (${actualMusicDuration.toFixed(1)}s) is shorter than video duration (${actualVideoDuration.toFixed(1)}s).`);
5014
+ warn(`Consider using audio looping or extending music in Remotion.`);
5015
+ }
5016
+ spinner?.start();
5017
+ }
5018
+ } catch (musicError) {
5019
+ spinner?.stop();
5020
+ warn(`Music generation failed: ${musicError.message}`);
5021
+ warn(`Continuing without background music...`);
5022
+ if (spinner && format === "human") spinner?.start();
5767
5023
  }
5768
- spinner?.start();
5769
5024
  }
5770
5025
  if (spinner) spinner.text = "Writing manifest...";
5771
- const totalDurationInFrames = Math.round(totalDuration * DEFAULT_FPS);
5026
+ const totalDurationInFrames = Math.round(actualVideoDuration * DEFAULT_FPS);
5772
5027
  const manifest = {
5773
5028
  music: musicInfo,
5774
5029
  images: allImages,
5775
5030
  videos: allVideos,
5776
5031
  scenes,
5032
+ timeline,
5033
+ // Include Remotion timeline in manifest
5777
5034
  totalDurationInFrames,
5778
5035
  fps: DEFAULT_FPS,
5779
5036
  totalCost,
@@ -5802,7 +5059,7 @@ var createCommand2 = new Command19("create").description("Create video assets (v
5802
5059
  ].filter(Boolean).join(", ");
5803
5060
  info(` - ${scene.name}: ${scene.durationInSeconds.toFixed(1)}s [${assets}]`);
5804
5061
  }
5805
- info(`Music: ${musicInfo.path} (${musicInfo.duration}s)`);
5062
+ info(`Music: ${musicInfo?.path} (${musicInfo?.duration}s)`);
5806
5063
  info(`Manifest: ${manifestPath}`);
5807
5064
  console.log();
5808
5065
  info(`Total cost: $${totalCost.toFixed(4)}`);
@@ -6118,10 +5375,10 @@ var thumbnailCommand = new Command19("thumbnail").description("Embed a thumbnail
6118
5375
  process.exit(EXIT_CODES.GENERAL_ERROR);
6119
5376
  }
6120
5377
  });
6121
- var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(initCommand).addCommand(createCommand2).addCommand(searchCommand2).addCommand(thumbnailCommand);
5378
+ var videoCommand = new Command19("video").description("Video asset generation commands").addCommand(initCommand).addCommand(createCommand3).addCommand(searchCommand2).addCommand(thumbnailCommand);
6122
5379
 
6123
5380
  // src/index.ts
6124
- var VERSION = "0.1.9";
5381
+ var VERSION = "0.1.11";
6125
5382
  var program = new Command20();
6126
5383
  var cmdName = brand.commands[0];
6127
5384
  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({