@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 +902 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1272
|
+
cleanup();
|
|
1128
1273
|
resolve4({ code, state });
|
|
1129
1274
|
});
|
|
1130
1275
|
server.listen(port);
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
|
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: "
|
|
1259
|
-
description: "CLI tool for
|
|
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
|
-
|
|
2985
|
-
name
|
|
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
|
-
#
|
|
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
|
|
3146
|
+
npm install -g ${pkg}
|
|
2998
3147
|
|
|
2999
|
-
# Configure API key (get from
|
|
3000
|
-
|
|
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
|
|
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
|
-
|
|
3166
|
+
${cmd2} create "Product Overview" --file ./deck.pptx --file ./logo.png
|
|
3018
3167
|
|
|
3019
3168
|
# Direct text context
|
|
3020
|
-
|
|
3169
|
+
${cmd2} create "Topic Title" --context "Key points, data, facts..."
|
|
3021
3170
|
|
|
3022
3171
|
# From a text file
|
|
3023
|
-
|
|
3172
|
+
${cmd2} create "Topic Title" --context-file ./notes.md
|
|
3024
3173
|
|
|
3025
3174
|
# Pipe content (auto-detected)
|
|
3026
|
-
cat README.md |
|
|
3175
|
+
cat README.md | ${cmd2} create "Project Overview"
|
|
3027
3176
|
|
|
3028
3177
|
# From URLs (scraped automatically)
|
|
3029
|
-
|
|
3178
|
+
${cmd2} create "Competitor Analysis" --sources https://example.com/report
|
|
3030
3179
|
|
|
3031
3180
|
# Combine multiple sources
|
|
3032
|
-
cat src/auth/*.ts |
|
|
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
|
-
|
|
3206
|
+
${cmd2} whoami
|
|
3058
3207
|
|
|
3059
3208
|
# List presentations
|
|
3060
|
-
|
|
3061
|
-
|
|
3209
|
+
${cmd2} list
|
|
3210
|
+
${cmd2} list --format json
|
|
3062
3211
|
|
|
3063
3212
|
# Get presentation details
|
|
3064
|
-
|
|
3213
|
+
${cmd2} get <id-or-slug>
|
|
3065
3214
|
|
|
3066
3215
|
# Export to ZIP
|
|
3067
|
-
|
|
3216
|
+
${cmd2} export <id-or-slug> -o presentation.zip
|
|
3068
3217
|
|
|
3069
3218
|
# Import presentation
|
|
3070
|
-
|
|
3219
|
+
${cmd2} import ./presentation.zip
|
|
3071
3220
|
|
|
3072
3221
|
# Manage branding
|
|
3073
|
-
|
|
3074
|
-
|
|
3222
|
+
${cmd2} branding list
|
|
3223
|
+
${cmd2} branding extract https://company.com
|
|
3075
3224
|
|
|
3076
3225
|
# Install/manage this skill
|
|
3077
|
-
|
|
3078
|
-
|
|
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 |
|
|
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
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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=$(
|
|
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
|
-
|
|
3315
|
+
${cmd2} whoami
|
|
3167
3316
|
|
|
3168
3317
|
# Verify API key
|
|
3169
|
-
|
|
3318
|
+
${cmd2} config show
|
|
3170
3319
|
|
|
3171
3320
|
# Debug mode
|
|
3172
|
-
|
|
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(
|
|
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
|
-
$
|
|
3338
|
+
$ ${brand.name} skill install
|
|
3189
3339
|
|
|
3190
3340
|
${chalk12.gray("# Install to specific directory")}
|
|
3191
|
-
$
|
|
3341
|
+
$ ${brand.name} skill install --dir ~/.claude
|
|
3192
3342
|
|
|
3193
3343
|
${chalk12.gray("# Show skill content")}
|
|
3194
|
-
$
|
|
3344
|
+
$ ${brand.name} skill show
|
|
3195
3345
|
`
|
|
3196
3346
|
);
|
|
3197
|
-
skillCommand.command("install").description(
|
|
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",
|
|
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",
|
|
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(
|
|
3407
|
+
console.log(generateSkillContent(brand));
|
|
3257
3408
|
});
|
|
3258
|
-
skillCommand.command("uninstall").description(
|
|
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",
|
|
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,
|
|
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.
|
|
3289
|
-
var program = new
|
|
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);
|