@conceptcraft/mindframes 0.1.1

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 ADDED
@@ -0,0 +1,2757 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+
4
+ // src/index.ts
5
+ import { Command as Command13 } from "commander";
6
+ import chalk12 from "chalk";
7
+
8
+ // src/lib/brand.ts
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
+ },
20
+ mindframes: {
21
+ id: "mindframes",
22
+ name: "mindframes",
23
+ displayName: "Mindframes",
24
+ description: "CLI tool for Mindframes presentation generation",
25
+ commands: ["mf", "mindframes"],
26
+ apiUrl: "https://mindframes.app",
27
+ docsUrl: "https://docs.mindframes.app"
28
+ }
29
+ };
30
+ var COMMAND_TO_BRAND = {
31
+ cc: "conceptcraft",
32
+ conceptcraft: "conceptcraft",
33
+ mf: "mindframes",
34
+ mindframes: "mindframes"
35
+ };
36
+ function detectBrand() {
37
+ const binaryPath = process.argv[1] || "";
38
+ const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
39
+ const brandId = COMMAND_TO_BRAND[binaryName];
40
+ if (brandId) {
41
+ return BRANDS[brandId];
42
+ }
43
+ for (const [cmd2, id] of Object.entries(COMMAND_TO_BRAND)) {
44
+ if (binaryPath.includes(`/${cmd2}`) || binaryPath.includes(`\\${cmd2}`)) {
45
+ return BRANDS[id];
46
+ }
47
+ }
48
+ return BRANDS.mindframes;
49
+ }
50
+ var brand = detectBrand();
51
+
52
+ // src/commands/config.ts
53
+ import { Command } from "commander";
54
+ import chalk3 from "chalk";
55
+ import { input, password, confirm, select } from "@inquirer/prompts";
56
+ import ora from "ora";
57
+
58
+ // src/lib/config.ts
59
+ import Conf from "conf";
60
+ var DEFAULT_API_URL = "https://www.mindframes.app";
61
+ var schema = {
62
+ apiKey: {
63
+ type: "string"
64
+ },
65
+ apiUrl: {
66
+ type: "string",
67
+ default: DEFAULT_API_URL
68
+ },
69
+ defaultTeamId: {
70
+ type: "string"
71
+ }
72
+ };
73
+ var config = new Conf({
74
+ projectName: "conceptcraft",
75
+ schema
76
+ });
77
+ function getConfig() {
78
+ return {
79
+ apiKey: getApiKey(),
80
+ apiUrl: getApiUrl(),
81
+ defaultTeamId: config.get("defaultTeamId")
82
+ };
83
+ }
84
+ function getApiKey() {
85
+ const envKey = process.env.CC_SLIDES_API_KEY ?? process.env.CONCEPTCRAFT_API_KEY;
86
+ if (envKey) {
87
+ return envKey;
88
+ }
89
+ return config.get("apiKey");
90
+ }
91
+ function setApiKey(key) {
92
+ config.set("apiKey", key);
93
+ }
94
+ function getApiUrl() {
95
+ const envUrl = process.env.CC_SLIDES_API_URL ?? process.env.CONCEPTCRAFT_API_URL;
96
+ if (envUrl) {
97
+ return envUrl;
98
+ }
99
+ return config.get("apiUrl") ?? DEFAULT_API_URL;
100
+ }
101
+ function setApiUrl(url) {
102
+ config.set("apiUrl", url);
103
+ }
104
+ function getDefaultTeamId() {
105
+ return config.get("defaultTeamId");
106
+ }
107
+ function setDefaultTeamId(teamId) {
108
+ config.set("defaultTeamId", teamId);
109
+ }
110
+ function clearConfig() {
111
+ config.clear();
112
+ }
113
+ function getConfigPath() {
114
+ return config.path;
115
+ }
116
+ function hasApiKey() {
117
+ return !!getApiKey();
118
+ }
119
+
120
+ // src/lib/auth.ts
121
+ import chalk from "chalk";
122
+
123
+ // src/types/index.ts
124
+ var EXIT_CODES = {
125
+ SUCCESS: 0,
126
+ GENERAL_ERROR: 1,
127
+ AUTH_ERROR: 2,
128
+ NOT_FOUND: 3,
129
+ RATE_LIMIT: 4,
130
+ NETWORK_ERROR: 5,
131
+ INVALID_INPUT: 6
132
+ };
133
+
134
+ // src/lib/auth.ts
135
+ function requireAuth() {
136
+ if (!hasApiKey()) {
137
+ console.error(chalk.red("Error: Not authenticated."));
138
+ console.error();
139
+ console.error("To authenticate, either:");
140
+ console.error(
141
+ chalk.gray(" 1. Set the CC_SLIDES_API_KEY environment variable")
142
+ );
143
+ console.error(
144
+ chalk.gray(" 2. Run: conceptcraft config init")
145
+ );
146
+ console.error();
147
+ console.error(
148
+ chalk.gray(`Config file location: ${getConfigPath()}`)
149
+ );
150
+ process.exit(EXIT_CODES.AUTH_ERROR);
151
+ }
152
+ }
153
+ function maskApiKey(key) {
154
+ if (key.length <= 14) {
155
+ return "****";
156
+ }
157
+ return `${key.slice(0, 10)}...${key.slice(-4)}`;
158
+ }
159
+ function getMaskedApiKey() {
160
+ const key = getApiKey();
161
+ if (!key) return void 0;
162
+ return maskApiKey(key);
163
+ }
164
+ function isValidApiKeyFormat(key) {
165
+ const validPrefixes = ["cc_slides_", "mcp_", "cc_sk_", "sk_"];
166
+ return validPrefixes.some((prefix) => key.startsWith(prefix));
167
+ }
168
+
169
+ // src/lib/api.ts
170
+ import { readFileSync, statSync } from "fs";
171
+ import { basename } from "path";
172
+ import { randomUUID } from "crypto";
173
+ var ApiError = class extends Error {
174
+ constructor(message, statusCode, exitCode = 1) {
175
+ super(message);
176
+ this.statusCode = statusCode;
177
+ this.exitCode = exitCode;
178
+ this.name = "ApiError";
179
+ }
180
+ };
181
+ async function request(endpoint, options = {}) {
182
+ const apiKey = getApiKey();
183
+ const apiUrl = getApiUrl();
184
+ if (!apiKey) {
185
+ throw new ApiError(
186
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
187
+ 401,
188
+ 2
189
+ // AUTH_ERROR
190
+ );
191
+ }
192
+ const url = `${apiUrl}${endpoint}`;
193
+ const headers = {
194
+ "x-api-key": apiKey,
195
+ ...options.headers
196
+ };
197
+ if (options.body && !options.stream) {
198
+ headers["Content-Type"] = "application/json";
199
+ }
200
+ const fetchOptions = {
201
+ method: options.method ?? "GET",
202
+ headers
203
+ };
204
+ if (options.body) {
205
+ fetchOptions.body = JSON.stringify(options.body);
206
+ }
207
+ let response;
208
+ try {
209
+ response = await fetch(url, fetchOptions);
210
+ } catch (error2) {
211
+ throw new ApiError(
212
+ `Network error: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
213
+ 0,
214
+ 5
215
+ // NETWORK_ERROR
216
+ );
217
+ }
218
+ if (!response.ok) {
219
+ const errorText = await response.text().catch(() => "");
220
+ let exitCode = 1;
221
+ switch (response.status) {
222
+ case 401:
223
+ exitCode = 2;
224
+ break;
225
+ case 403:
226
+ exitCode = 2;
227
+ break;
228
+ case 404:
229
+ exitCode = 3;
230
+ break;
231
+ case 429:
232
+ exitCode = 4;
233
+ break;
234
+ case 500:
235
+ exitCode = 1;
236
+ break;
237
+ }
238
+ const message = errorText.trim() || `HTTP ${response.status}: ${response.statusText || "Server error"}`;
239
+ throw new ApiError(message, response.status, exitCode);
240
+ }
241
+ if (options.stream) {
242
+ return response;
243
+ }
244
+ const contentType = response.headers.get("content-type");
245
+ if (contentType?.includes("application/json")) {
246
+ return response.json();
247
+ }
248
+ return response.text();
249
+ }
250
+ async function streamRequest(endpoint, body) {
251
+ const apiKey = getApiKey();
252
+ const apiUrl = getApiUrl();
253
+ if (!apiKey) {
254
+ throw new ApiError(
255
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
256
+ 401,
257
+ 2
258
+ );
259
+ }
260
+ const url = `${apiUrl}${endpoint}`;
261
+ let response;
262
+ try {
263
+ response = await fetch(url, {
264
+ method: "POST",
265
+ headers: {
266
+ "Content-Type": "application/json",
267
+ "x-api-key": apiKey
268
+ },
269
+ body: JSON.stringify(body)
270
+ });
271
+ } catch (error2) {
272
+ throw new ApiError(
273
+ `Network error: ${error2 instanceof Error ? error2.message : "Unknown error"}`,
274
+ 0,
275
+ 5
276
+ );
277
+ }
278
+ if (!response.ok) {
279
+ const errorText = await response.text().catch(() => "Unknown error");
280
+ let exitCode = 1;
281
+ switch (response.status) {
282
+ case 401:
283
+ exitCode = 2;
284
+ break;
285
+ case 403:
286
+ exitCode = 2;
287
+ break;
288
+ case 404:
289
+ exitCode = 3;
290
+ break;
291
+ case 429:
292
+ exitCode = 4;
293
+ break;
294
+ }
295
+ throw new ApiError(errorText, response.status, exitCode);
296
+ }
297
+ return response;
298
+ }
299
+ function getMimeType(filePath) {
300
+ const ext = filePath.toLowerCase().split(".").pop();
301
+ const mimeTypes = {
302
+ // Documents
303
+ pdf: "application/pdf",
304
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
305
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
306
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
307
+ // Images
308
+ jpg: "image/jpeg",
309
+ jpeg: "image/jpeg",
310
+ png: "image/png",
311
+ gif: "image/gif",
312
+ webp: "image/webp",
313
+ // Text
314
+ txt: "text/plain",
315
+ md: "text/markdown",
316
+ csv: "text/csv",
317
+ json: "application/json",
318
+ html: "text/html",
319
+ css: "text/css",
320
+ js: "application/javascript",
321
+ ts: "text/x-typescript",
322
+ py: "text/x-python"
323
+ };
324
+ return mimeTypes[ext || ""] || "application/octet-stream";
325
+ }
326
+ async function uploadFile(filePath) {
327
+ const apiKey = getApiKey();
328
+ const apiUrl = getApiUrl();
329
+ if (!apiKey) {
330
+ throw new ApiError(
331
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
332
+ 401,
333
+ 2
334
+ );
335
+ }
336
+ const stat = statSync(filePath);
337
+ const fileName = basename(filePath);
338
+ const mimeType = getMimeType(filePath);
339
+ const presignedResponse = await fetch(`${apiUrl}/api/cli/files/upload`, {
340
+ method: "POST",
341
+ headers: {
342
+ "Content-Type": "application/json",
343
+ "x-api-key": apiKey
344
+ },
345
+ body: JSON.stringify({
346
+ fileMetadata: {
347
+ name: fileName,
348
+ type: mimeType,
349
+ size: stat.size
350
+ }
351
+ })
352
+ });
353
+ if (!presignedResponse.ok) {
354
+ const errorText = await presignedResponse.text();
355
+ throw new ApiError(
356
+ errorText || "Failed to get upload URL",
357
+ presignedResponse.status,
358
+ 1
359
+ );
360
+ }
361
+ const presigned = await presignedResponse.json();
362
+ const fileBuffer = readFileSync(filePath);
363
+ const formData = new FormData();
364
+ for (const [key, value] of Object.entries(presigned.fields || {})) {
365
+ formData.append(key, value);
366
+ }
367
+ const blob = new Blob([fileBuffer], { type: mimeType });
368
+ formData.append("file", blob, fileName);
369
+ const uploadResponse = await fetch(presigned.url, {
370
+ method: "POST",
371
+ body: formData
372
+ });
373
+ if (!uploadResponse.ok) {
374
+ throw new ApiError(
375
+ "Failed to upload file to storage",
376
+ uploadResponse.status,
377
+ 1
378
+ );
379
+ }
380
+ return {
381
+ id: randomUUID(),
382
+ name: fileName,
383
+ url: presigned.fileUrl,
384
+ size: stat.size,
385
+ type: mimeType,
386
+ selected: true
387
+ };
388
+ }
389
+ async function uploadFiles(filePaths, onProgress) {
390
+ const results = [];
391
+ for (let i = 0; i < filePaths.length; i++) {
392
+ const filePath = filePaths[i];
393
+ const fileName = basename(filePath);
394
+ onProgress?.(i, filePaths.length, fileName);
395
+ const result = await uploadFile(filePath);
396
+ results.push(result);
397
+ }
398
+ onProgress?.(filePaths.length, filePaths.length, "");
399
+ return results;
400
+ }
401
+ async function createPresentation(options) {
402
+ const body = {
403
+ topic: options.topic,
404
+ slideCount: options.slideCount ?? 10,
405
+ mode: options.mode ?? "balanced",
406
+ tone: options.tone ?? "professional",
407
+ amount: options.amount ?? "concise",
408
+ audience: options.audience ?? "General Audience",
409
+ language: options.language ?? "en",
410
+ stylingMode: options.stylingMode ?? "freeform"
411
+ };
412
+ if (options.referenceUrl) {
413
+ body.referenceUrl = options.referenceUrl;
414
+ }
415
+ if (options.thinkingDepth) {
416
+ body.thinkingDepth = options.thinkingDepth;
417
+ }
418
+ if (options.goal) {
419
+ body.goal = options.goal;
420
+ }
421
+ if (options.customGoal) {
422
+ body.goal = options.customGoal;
423
+ }
424
+ const uploadedFiles = [];
425
+ const contextFiles = [];
426
+ if (options.uploadedFiles && options.uploadedFiles.length > 0) {
427
+ uploadedFiles.push(...options.uploadedFiles);
428
+ }
429
+ if (options.sources && options.sources.length > 0) {
430
+ for (const url of options.sources) {
431
+ contextFiles.push({ url });
432
+ }
433
+ }
434
+ if (options.stdinContent) {
435
+ uploadedFiles.push({
436
+ id: `stdin-${Date.now()}`,
437
+ content: options.stdinContent,
438
+ title: "Piped Content",
439
+ mime: "text/plain",
440
+ selected: true
441
+ });
442
+ }
443
+ if (options.context) {
444
+ uploadedFiles.push({
445
+ id: `context-${Date.now()}`,
446
+ content: options.context,
447
+ title: "Context",
448
+ mime: "text/plain",
449
+ selected: true
450
+ });
451
+ }
452
+ if (uploadedFiles.length > 0 || contextFiles.length > 0) {
453
+ body.contentSources = {
454
+ uploadedFiles,
455
+ contextFiles
456
+ };
457
+ }
458
+ if (options.brandId) {
459
+ body.designBrandReference = options.brandId;
460
+ }
461
+ if (options.teamId) {
462
+ body.teamId = options.teamId;
463
+ }
464
+ return streamRequest("/api/slides/skills/create-presentation", body);
465
+ }
466
+ async function listPresentations(teamId, limit = 20) {
467
+ const params = new URLSearchParams();
468
+ params.set("limit", String(limit));
469
+ if (teamId) {
470
+ params.set("teamId", teamId);
471
+ }
472
+ const response = await request(
473
+ `/api/cli/presentations?${params}`
474
+ );
475
+ return response.presentations;
476
+ }
477
+ async function getPresentation(slugOrId) {
478
+ return request(`/api/cli/presentation/${slugOrId}`);
479
+ }
480
+ async function deletePresentation(slugOrId) {
481
+ await request(`/api/cli/presentation/${slugOrId}`, { method: "DELETE" });
482
+ }
483
+ async function exportPresentation(presentationId, options = {}) {
484
+ const apiKey = getApiKey();
485
+ const apiUrl = getApiUrl();
486
+ if (!apiKey) {
487
+ throw new ApiError(
488
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
489
+ 401,
490
+ 2
491
+ );
492
+ }
493
+ const response = await fetch(`${apiUrl}/api/presentations/export`, {
494
+ method: "POST",
495
+ headers: {
496
+ "Content-Type": "application/json",
497
+ "x-api-key": apiKey
498
+ },
499
+ body: JSON.stringify({
500
+ presentationId,
501
+ options: {
502
+ includeImages: options.includeImages ?? true,
503
+ includeBranding: options.includeBranding ?? true,
504
+ downloadExternalImages: options.includeExternal ?? true
505
+ }
506
+ })
507
+ });
508
+ if (!response.ok) {
509
+ throw new ApiError(
510
+ await response.text(),
511
+ response.status,
512
+ response.status === 404 ? 3 : 1
513
+ );
514
+ }
515
+ return response.arrayBuffer();
516
+ }
517
+ async function importPresentation(fileBuffer, fileName, options = {}) {
518
+ const apiKey = getApiKey();
519
+ const apiUrl = getApiUrl();
520
+ if (!apiKey) {
521
+ throw new ApiError(
522
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
523
+ 401,
524
+ 2
525
+ );
526
+ }
527
+ const formData = new FormData();
528
+ const blob = new Blob([fileBuffer], { type: "application/zip" });
529
+ formData.append("file", blob, fileName);
530
+ formData.append(
531
+ "options",
532
+ JSON.stringify({
533
+ dryRun: options.dryRun ?? false,
534
+ remapIds: true
535
+ })
536
+ );
537
+ const response = await fetch(`${apiUrl}/api/presentations/import`, {
538
+ method: "POST",
539
+ headers: {
540
+ "x-api-key": apiKey
541
+ },
542
+ body: formData
543
+ });
544
+ const result = await response.json();
545
+ if (!response.ok) {
546
+ throw new ApiError(
547
+ result.errors?.[0]?.message ?? "Import failed",
548
+ response.status,
549
+ 1
550
+ );
551
+ }
552
+ return result;
553
+ }
554
+ async function listBrandings() {
555
+ return request("/api/branding");
556
+ }
557
+ async function getBranding(id) {
558
+ return request(`/api/branding/${id}`);
559
+ }
560
+ async function extractBranding(url, teamId) {
561
+ return request("/api/branding/extract", {
562
+ method: "POST",
563
+ body: { url, teamId }
564
+ });
565
+ }
566
+ async function whoami() {
567
+ return request("/api/cli/whoami");
568
+ }
569
+ async function getFeatureFlags() {
570
+ return request("/api/cli/features");
571
+ }
572
+ async function generateBlog(presentationId, documentCreatedAt, options = {}) {
573
+ const apiKey = getApiKey();
574
+ const apiUrl = getApiUrl();
575
+ if (!apiKey) {
576
+ throw new ApiError(
577
+ "API key not configured. Run 'conceptcraft config init' or set CC_SLIDES_API_KEY environment variable.",
578
+ 401,
579
+ 2
580
+ );
581
+ }
582
+ const response = await fetch(
583
+ `${apiUrl}/api/presentations/${presentationId}/generate-blog`,
584
+ {
585
+ method: "POST",
586
+ headers: {
587
+ "Content-Type": "application/json",
588
+ "x-api-key": apiKey
589
+ },
590
+ body: JSON.stringify({
591
+ documentCreatedAt,
592
+ targetWordCount: options.targetWordCount ?? 300,
593
+ tone: options.tone ?? "professional",
594
+ language: options.language ?? "en",
595
+ targetAudience: options.targetAudience ?? "General Audience",
596
+ blogFormat: options.blogFormat ?? "narrative",
597
+ customInstructions: options.customInstructions ?? "",
598
+ includeImages: options.includeImages ?? true,
599
+ includeTableOfContents: options.includeTableOfContents ?? false
600
+ })
601
+ }
602
+ );
603
+ if (!response.ok) {
604
+ throw new ApiError(
605
+ await response.text(),
606
+ response.status,
607
+ response.status === 404 ? 3 : 1
608
+ );
609
+ }
610
+ return response;
611
+ }
612
+ async function checkLimits(teamId) {
613
+ const params = new URLSearchParams();
614
+ if (teamId) {
615
+ params.set("teamId", teamId);
616
+ }
617
+ const query = params.toString();
618
+ return request(`/api/cli/limits${query ? `?${query}` : ""}`);
619
+ }
620
+ async function validateGeneration(mode, slideCount, teamId) {
621
+ const limits = await checkLimits(teamId);
622
+ if (!limits.canGenerate[mode]) {
623
+ const remaining = limits.remaining[mode] ?? 0;
624
+ const limit = limits.limits[mode] ?? 0;
625
+ if (limit === 0) {
626
+ throw new ApiError(
627
+ `The "${mode}" mode is not available on your ${limits.planName} plan. Available modes: ${limits.availableModes.join(", ") || "none"}`,
628
+ 403,
629
+ 2
630
+ );
631
+ }
632
+ throw new ApiError(
633
+ `You have reached your ${mode} generation limit for this billing cycle (${limits.usage[mode] ?? 0}/${limit} used). Try a different mode: ${limits.availableModes.join(", ") || "none available"}`,
634
+ 403,
635
+ 4
636
+ );
637
+ }
638
+ if (slideCount > limits.maxSlidesPerPresentation) {
639
+ throw new ApiError(
640
+ `Maximum ${limits.maxSlidesPerPresentation} slides allowed on your ${limits.planName} plan (requested: ${slideCount})`,
641
+ 403,
642
+ 6
643
+ );
644
+ }
645
+ return limits;
646
+ }
647
+
648
+ // src/lib/feature-cache.ts
649
+ import Conf2 from "conf";
650
+ var cache = new Conf2({
651
+ projectName: "conceptcraft",
652
+ configName: "feature-cache"
653
+ });
654
+ var FRESH_TTL = 60 * 60 * 1e3;
655
+ var STALE_TTL = 24 * 60 * 60 * 1e3;
656
+ function hashApiKey(key) {
657
+ let hash = 0;
658
+ for (let i = 0; i < key.length; i++) {
659
+ const char = key.charCodeAt(i);
660
+ hash = (hash << 5) - hash + char;
661
+ hash = hash & hash;
662
+ }
663
+ return hash.toString(16);
664
+ }
665
+ function getCachedFlags() {
666
+ if (!hasApiKey()) return null;
667
+ const cached = cache.get("featureFlags");
668
+ if (!cached) return null;
669
+ const currentKeyHash = hashApiKey(getApiKey());
670
+ if (cached.apiKeyHash && cached.apiKeyHash !== currentKeyHash) {
671
+ invalidateCache();
672
+ return null;
673
+ }
674
+ const age = Date.now() - cached.fetchedAt;
675
+ if (age > STALE_TTL) return null;
676
+ if (age > FRESH_TTL) {
677
+ refreshInBackground();
678
+ }
679
+ return cached.flags;
680
+ }
681
+ function refreshInBackground() {
682
+ fetchAndCache().catch(() => {
683
+ });
684
+ }
685
+ async function fetchAndCache() {
686
+ const flags = await getFeatureFlags();
687
+ const apiKey = getApiKey();
688
+ cache.set("featureFlags", {
689
+ flags,
690
+ fetchedAt: Date.now(),
691
+ apiKeyHash: apiKey ? hashApiKey(apiKey) : void 0
692
+ });
693
+ return flags;
694
+ }
695
+ function invalidateCache() {
696
+ cache.delete("featureFlags");
697
+ }
698
+ function getCachePath() {
699
+ return cache.path;
700
+ }
701
+
702
+ // src/lib/output.ts
703
+ import chalk2 from "chalk";
704
+ import Table from "cli-table3";
705
+ function isTTY() {
706
+ return process.stdout.isTTY ?? false;
707
+ }
708
+ function success(message, format = "human") {
709
+ if (format === "quiet") return;
710
+ if (format === "json") return;
711
+ console.log(chalk2.green("\u2713"), message);
712
+ }
713
+ function error(message, format = "human") {
714
+ if (format === "quiet") return;
715
+ if (format === "json") {
716
+ console.error(JSON.stringify({ error: message }));
717
+ return;
718
+ }
719
+ console.error(chalk2.red("\u2717"), message);
720
+ }
721
+ function warn(message, format = "human") {
722
+ if (format === "quiet") return;
723
+ if (format === "json") return;
724
+ console.warn(chalk2.yellow("\u26A0"), message);
725
+ }
726
+ function info(message, format = "human") {
727
+ if (format === "quiet") return;
728
+ if (format === "json") return;
729
+ console.log(chalk2.blue("\u2139"), message);
730
+ }
731
+ function buildViewUrl(slug, language = "en") {
732
+ if (!slug) return "N/A";
733
+ const apiUrl = getApiUrl();
734
+ return `${apiUrl}/${language}/view/presentations/${slug}`;
735
+ }
736
+ function formatPresentationTable(presentations, options = {}) {
737
+ const { showLinks = false, language = "en" } = options;
738
+ const truncate = (str, len) => str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
739
+ const shortDateTime = (dateStr) => {
740
+ try {
741
+ const d = new Date(dateStr);
742
+ return d.toLocaleString("en-US", {
743
+ month: "short",
744
+ day: "numeric",
745
+ hour: "numeric",
746
+ minute: "2-digit",
747
+ hour12: true
748
+ }).replace(" at ", ", ");
749
+ } catch {
750
+ return "-";
751
+ }
752
+ };
753
+ if (showLinks) {
754
+ const table2 = new Table({
755
+ head: [
756
+ chalk2.cyan("Slug"),
757
+ chalk2.cyan("Title"),
758
+ chalk2.cyan("Slides"),
759
+ chalk2.cyan("Created"),
760
+ chalk2.cyan("URL")
761
+ ]
762
+ });
763
+ for (const p of presentations) {
764
+ table2.push([
765
+ truncate(p.slug || p.id.slice(0, 8), 30),
766
+ truncate(p.title || "Untitled", 22),
767
+ String(p.numberOfSlides || "-"),
768
+ shortDateTime(p.createdAt),
769
+ buildViewUrl(p.slug, language)
770
+ ]);
771
+ }
772
+ return table2.toString();
773
+ }
774
+ const table = new Table({
775
+ head: [
776
+ chalk2.cyan("Slug"),
777
+ chalk2.cyan("Title"),
778
+ chalk2.cyan("Slides"),
779
+ chalk2.cyan("Mode"),
780
+ chalk2.cyan("Created")
781
+ ],
782
+ colWidths: [32, 28, 8, 11, 18]
783
+ });
784
+ for (const p of presentations) {
785
+ table.push([
786
+ p.slug || p.id.slice(0, 8),
787
+ p.title || "Untitled",
788
+ String(p.numberOfSlides || "-"),
789
+ p.mode || "-",
790
+ shortDateTime(p.createdAt)
791
+ ]);
792
+ }
793
+ return table.toString();
794
+ }
795
+ function formatPresentationIds(presentations) {
796
+ return presentations.map((p) => p.slug || p.id).join("\n");
797
+ }
798
+ function formatBrandingTable(brandings) {
799
+ const sorted = [...brandings].sort((a, b) => {
800
+ if (a.isDefault && !b.isDefault) return -1;
801
+ if (!a.isDefault && b.isDefault) return 1;
802
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
803
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
804
+ return dateB - dateA;
805
+ });
806
+ const formatUrl = (url) => {
807
+ if (!url) return "-";
808
+ try {
809
+ const u = new URL(url);
810
+ return u.href;
811
+ } catch {
812
+ return url.startsWith("http") ? url : `https://${url}`;
813
+ }
814
+ };
815
+ const table = new Table({
816
+ head: [
817
+ chalk2.cyan("Name"),
818
+ chalk2.cyan("Source"),
819
+ chalk2.cyan("Color"),
820
+ chalk2.cyan("Logo"),
821
+ chalk2.cyan("Default"),
822
+ chalk2.cyan("ID")
823
+ ]
824
+ });
825
+ for (const b of sorted) {
826
+ const colorDisplay = b.primaryColor ? `${chalk2.bgHex(b.primaryColor)(" ")} ${b.primaryColor}` : "-";
827
+ table.push([
828
+ b.name,
829
+ formatUrl(b.sourceUrl),
830
+ colorDisplay,
831
+ b.logoUrl ? chalk2.green("\u2713") : chalk2.gray("\u2717"),
832
+ b.isDefault ? chalk2.green("\u2605") : "",
833
+ b.id
834
+ ]);
835
+ }
836
+ return table.toString();
837
+ }
838
+ function formatDate(dateStr) {
839
+ try {
840
+ const date = new Date(dateStr);
841
+ return date.toLocaleString();
842
+ } catch {
843
+ return dateStr;
844
+ }
845
+ }
846
+ function header(text) {
847
+ console.log();
848
+ console.log(chalk2.bold(text));
849
+ console.log(chalk2.gray("\u2500".repeat(text.length)));
850
+ }
851
+ function keyValue(key, value) {
852
+ console.log(` ${chalk2.gray(key + ":")} ${value ?? "-"}`);
853
+ }
854
+ function progressBar(current, total, width = 30, showPercentage = true) {
855
+ const percentage = Math.min(100, Math.round(current / total * 100));
856
+ const filled = Math.round(width * current / total);
857
+ const empty = width - filled;
858
+ const bar = chalk2.green("\u2588".repeat(filled)) + chalk2.gray("\u2591".repeat(empty));
859
+ return showPercentage ? `[${bar}] ${percentage}%` : `[${bar}]`;
860
+ }
861
+
862
+ // src/commands/config.ts
863
+ var configCommand = new Command("config").description("Manage CLI configuration").addCommand(
864
+ new Command("init").description("Initialize configuration interactively").action(async () => {
865
+ console.log();
866
+ console.log(chalk3.bold("ConceptCraft CLI Configuration"));
867
+ console.log(chalk3.gray("\u2500".repeat(35)));
868
+ console.log();
869
+ try {
870
+ const apiKey = await password({
871
+ message: "Enter your API key:",
872
+ mask: "*",
873
+ validate: (value) => {
874
+ if (!value || value.trim().length === 0) {
875
+ return "API key is required";
876
+ }
877
+ if (!isValidApiKeyFormat(value.trim())) {
878
+ return "Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'";
879
+ }
880
+ return true;
881
+ }
882
+ });
883
+ setApiKey(apiKey.trim());
884
+ const useCustomUrl = await confirm({
885
+ message: "Use a custom API URL? (default: www.mindframes.app)",
886
+ default: false
887
+ });
888
+ if (useCustomUrl) {
889
+ const customUrl = await input({
890
+ message: "Enter API URL:",
891
+ default: getApiUrl(),
892
+ validate: (value) => {
893
+ try {
894
+ new URL(value);
895
+ return true;
896
+ } catch {
897
+ return "Invalid URL format";
898
+ }
899
+ }
900
+ });
901
+ setApiUrl(customUrl);
902
+ }
903
+ console.log();
904
+ const spinner = ora("Verifying API key...").start();
905
+ try {
906
+ const result = await whoami();
907
+ spinner.succeed("API key verified!");
908
+ console.log();
909
+ info(`Logged in as: ${result.user.email}`);
910
+ if (result.teams.length === 0) {
911
+ warn("No teams found for this user.");
912
+ } else if (result.teams.length === 1) {
913
+ const team = result.teams[0];
914
+ setDefaultTeamId(team.id);
915
+ info(`Team: ${team.name} (${team.planName})`);
916
+ } else {
917
+ console.log();
918
+ info(`You have access to ${result.teams.length} teams.`);
919
+ const selectedTeamId = await select({
920
+ message: "Select your default team:",
921
+ choices: result.teams.map((team) => ({
922
+ name: `${team.name} (${team.planName}) - ${team.role}${team.isCurrent ? " [current]" : ""}`,
923
+ value: team.id
924
+ })),
925
+ default: result.currentTeam?.id
926
+ });
927
+ setDefaultTeamId(selectedTeamId);
928
+ const selectedTeam = result.teams.find((t) => t.id === selectedTeamId);
929
+ success(`Selected team: ${selectedTeam?.name}`);
930
+ }
931
+ try {
932
+ await fetchAndCache();
933
+ } catch {
934
+ }
935
+ } catch (apiErr) {
936
+ spinner.fail("Failed to verify API key");
937
+ warn(
938
+ `Could not verify API key: ${apiErr instanceof Error ? apiErr.message : String(apiErr)}`
939
+ );
940
+ warn("You may need to set the team ID manually: conceptcraft config set team-id <id>");
941
+ }
942
+ console.log();
943
+ success("Configuration saved!");
944
+ info(`Config file: ${getConfigPath()}`);
945
+ console.log();
946
+ } catch (err) {
947
+ if (err.name === "ExitPromptError") {
948
+ console.log();
949
+ info("Configuration cancelled.");
950
+ return;
951
+ }
952
+ throw err;
953
+ }
954
+ })
955
+ ).addCommand(
956
+ new Command("show").description("Show current configuration").option("--verify", "Verify API key and show team details").action(async (options) => {
957
+ const config2 = getConfig();
958
+ header("Current Configuration");
959
+ console.log();
960
+ keyValue("API Key", getMaskedApiKey() ?? chalk3.red("Not set"));
961
+ keyValue("API URL", config2.apiUrl);
962
+ keyValue("Default Team ID", config2.defaultTeamId ?? chalk3.gray("Not set"));
963
+ console.log();
964
+ keyValue("Config file", getConfigPath());
965
+ if (options.verify && config2.apiKey) {
966
+ console.log();
967
+ const spinner = ora("Verifying...").start();
968
+ try {
969
+ const result = await whoami();
970
+ spinner.succeed("Verified");
971
+ console.log();
972
+ keyValue("User", result.user.email);
973
+ if (result.currentTeam) {
974
+ keyValue("Current Team", `${result.currentTeam.name} (${result.currentTeam.planName})`);
975
+ }
976
+ if (result.teams.length > 1) {
977
+ keyValue("Total Teams", String(result.teams.length));
978
+ }
979
+ } catch (err) {
980
+ spinner.fail("Verification failed");
981
+ warn(err instanceof Error ? err.message : String(err));
982
+ }
983
+ }
984
+ console.log();
985
+ })
986
+ ).addCommand(
987
+ new Command("set").description("Set a configuration value").argument("<key>", "Configuration key (api-key, api-url, team-id)").argument("<value>", "Value to set").action(async (key, value) => {
988
+ switch (key) {
989
+ case "api-key":
990
+ if (!isValidApiKeyFormat(value)) {
991
+ error("Invalid API key format. Keys should start with 'cc_slides_' or 'mcp_'");
992
+ process.exit(6);
993
+ }
994
+ setApiKey(value);
995
+ invalidateCache();
996
+ success("API key updated");
997
+ fetchAndCache().catch(() => {
998
+ });
999
+ break;
1000
+ case "api-url":
1001
+ try {
1002
+ new URL(value);
1003
+ setApiUrl(value);
1004
+ success(`API URL set to: ${value}`);
1005
+ } catch {
1006
+ error("Invalid URL format");
1007
+ process.exit(6);
1008
+ }
1009
+ break;
1010
+ case "team-id":
1011
+ setDefaultTeamId(value);
1012
+ success(`Default team ID set to: ${value}`);
1013
+ break;
1014
+ default:
1015
+ error(`Unknown config key: ${key}`);
1016
+ console.log(chalk3.gray("Valid keys: api-key, api-url, team-id"));
1017
+ process.exit(6);
1018
+ }
1019
+ })
1020
+ ).addCommand(
1021
+ new Command("clear").description("Clear all configuration").action(async () => {
1022
+ try {
1023
+ const confirmed = await confirm({
1024
+ message: "Are you sure you want to clear all configuration?",
1025
+ default: false
1026
+ });
1027
+ if (confirmed) {
1028
+ clearConfig();
1029
+ invalidateCache();
1030
+ success("Configuration cleared");
1031
+ } else {
1032
+ info("Cancelled");
1033
+ }
1034
+ } catch {
1035
+ info("Cancelled");
1036
+ }
1037
+ })
1038
+ ).addCommand(
1039
+ new Command("refresh").description("Refresh cached feature flags").action(async () => {
1040
+ const spinner = ora("Refreshing feature flags...").start();
1041
+ try {
1042
+ await fetchAndCache();
1043
+ spinner.succeed("Feature flags refreshed");
1044
+ info("Run 'conceptcraft --help' to see updated commands");
1045
+ } catch (err) {
1046
+ spinner.fail("Failed to refresh");
1047
+ error(err instanceof Error ? err.message : String(err));
1048
+ process.exit(1);
1049
+ }
1050
+ })
1051
+ ).addCommand(
1052
+ new Command("path").description("Show configuration file path").option("--cache", "Show feature cache file path").action((options) => {
1053
+ if (options.cache) {
1054
+ console.log(getCachePath());
1055
+ } else {
1056
+ console.log(getConfigPath());
1057
+ }
1058
+ })
1059
+ );
1060
+
1061
+ // src/commands/create.ts
1062
+ import { Command as Command2 } from "commander";
1063
+ import chalk5 from "chalk";
1064
+
1065
+ // src/lib/streaming.ts
1066
+ import { createParser } from "eventsource-parser";
1067
+ import chalk4 from "chalk";
1068
+ import ora2 from "ora";
1069
+ async function handleStream(response, callbacks, options = {}) {
1070
+ const reader = response.body?.getReader();
1071
+ if (!reader) {
1072
+ throw new Error("Response body is not readable");
1073
+ }
1074
+ const decoder = new TextDecoder();
1075
+ let documentId;
1076
+ let slug;
1077
+ let title;
1078
+ let totalSlides = 0;
1079
+ let currentSlide = 0;
1080
+ let totalTokens = 0;
1081
+ let slideTokens = {};
1082
+ const parser = createParser({
1083
+ onEvent: (event) => {
1084
+ if (event.data === "[DONE]") return;
1085
+ try {
1086
+ const parsed = JSON.parse(event.data);
1087
+ const { type, data } = parsed;
1088
+ if (options.debug) {
1089
+ console.log(chalk4.gray(`[SSE] ${type}:`), data);
1090
+ }
1091
+ callbacks.onData?.(type, data);
1092
+ switch (type) {
1093
+ case "data-id":
1094
+ documentId = data;
1095
+ break;
1096
+ case "data-slug":
1097
+ slug = data;
1098
+ break;
1099
+ case "data-title":
1100
+ title = data;
1101
+ break;
1102
+ case "data-slides-number-of-slides":
1103
+ totalSlides = data;
1104
+ break;
1105
+ case "data-slide-progress": {
1106
+ const progress = data;
1107
+ currentSlide = progress.current;
1108
+ totalSlides = progress.total;
1109
+ callbacks.onProgress?.(currentSlide, totalSlides);
1110
+ callbacks.onSlide?.(currentSlide, totalSlides);
1111
+ break;
1112
+ }
1113
+ case "data-overall-progress": {
1114
+ const progress = data;
1115
+ callbacks.onPhase?.(progress.phase, progress.percentage);
1116
+ if (progress.details?.slides) {
1117
+ currentSlide = progress.details.slides.complete;
1118
+ totalSlides = progress.details.slides.total;
1119
+ callbacks.onProgress?.(currentSlide, totalSlides);
1120
+ callbacks.onSlide?.(currentSlide, totalSlides);
1121
+ }
1122
+ break;
1123
+ }
1124
+ case "data-structure-tokens": {
1125
+ totalTokens = data;
1126
+ callbacks.onTokens?.(totalTokens);
1127
+ break;
1128
+ }
1129
+ case "data-slide-meta-tokens": {
1130
+ const [slideId, tokens] = data.split(":");
1131
+ if (slideId && tokens) {
1132
+ slideTokens[slideId] = parseInt(tokens, 10);
1133
+ const allSlideTokens = Object.values(slideTokens).reduce((sum, t) => sum + t, 0);
1134
+ totalTokens = Math.max(totalTokens, allSlideTokens);
1135
+ callbacks.onTokens?.(totalTokens + allSlideTokens);
1136
+ }
1137
+ break;
1138
+ }
1139
+ case "data-generation-meta-estimated-time": {
1140
+ callbacks.onEstimatedTime?.(data);
1141
+ break;
1142
+ }
1143
+ case "data-finish":
1144
+ callbacks.onComplete?.({
1145
+ id: documentId,
1146
+ slug,
1147
+ title,
1148
+ slidesCount: totalSlides
1149
+ });
1150
+ break;
1151
+ case "error":
1152
+ if (data) {
1153
+ callbacks.onError?.(data);
1154
+ }
1155
+ break;
1156
+ }
1157
+ } catch (e) {
1158
+ if (options.debug) {
1159
+ console.log(chalk4.gray("[SSE Raw]"), event.data);
1160
+ }
1161
+ }
1162
+ }
1163
+ });
1164
+ let done = false;
1165
+ while (!done) {
1166
+ const result = await reader.read();
1167
+ done = result.done;
1168
+ if (result.value) {
1169
+ const chunk = decoder.decode(result.value, { stream: true });
1170
+ parser.feed(chunk);
1171
+ }
1172
+ }
1173
+ return {
1174
+ id: documentId,
1175
+ slug,
1176
+ title,
1177
+ slidesCount: totalSlides
1178
+ };
1179
+ }
1180
+ async function streamWithProgress(response, topic, options) {
1181
+ const useTTY = isTTY() && !options.noStream && !options.quiet && !options.json;
1182
+ if (!options.json) {
1183
+ console.log();
1184
+ console.log(chalk4.bold(`Creating presentation: "${topic}"`));
1185
+ console.log(
1186
+ chalk4.gray(
1187
+ `Quality: ${options.mode} | Tone: ${options.tone ?? "professional"} | Slides: ${options.slideCount} | Language: ${options.language}`
1188
+ )
1189
+ );
1190
+ console.log();
1191
+ }
1192
+ let spinner;
1193
+ let lastProgress = "";
1194
+ let currentPhase = "Preparing";
1195
+ let currentPercentage = 0;
1196
+ let currentSlides = { done: 0, total: options.slideCount };
1197
+ let currentTokens = 0;
1198
+ let estimatedTime = 0;
1199
+ let startTime = Date.now();
1200
+ let timer;
1201
+ const formatTime = (seconds) => {
1202
+ const mins = Math.floor(seconds / 60);
1203
+ const secs = seconds % 60;
1204
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
1205
+ };
1206
+ const getElapsed = () => Math.floor((Date.now() - startTime) / 1e3);
1207
+ const getRemaining = () => {
1208
+ if (!estimatedTime) return " \u2014:\u2014\u2014";
1209
+ const remaining = Math.max(0, estimatedTime - getElapsed());
1210
+ if (remaining === 0 && currentPercentage < 100) return "soon!";
1211
+ return formatTime(remaining);
1212
+ };
1213
+ const formatTokens = (tokens) => {
1214
+ if (tokens >= 1e3) {
1215
+ return `${(tokens / 1e3).toFixed(1)}k`.padStart(5, " ");
1216
+ }
1217
+ return `${tokens}tk`.padStart(5, " ");
1218
+ };
1219
+ const getPhaseLabel = (phase) => {
1220
+ const phaseMap = {
1221
+ "structure-generation": "Planning ",
1222
+ "slide-generation": "Writing ",
1223
+ "image-generation": "Images ",
1224
+ "assembling": "Finishing",
1225
+ "completed": "Done "
1226
+ };
1227
+ return phaseMap[phase] || "Working ";
1228
+ };
1229
+ const buildProgressLine = () => {
1230
+ const bar = progressBar(currentPercentage, 100, 20, false);
1231
+ const pct = String(currentPercentage).padStart(3, " ");
1232
+ const phase = getPhaseLabel(currentPhase);
1233
+ const slides = `${String(currentSlides.done).padStart(2, " ")}/${String(currentSlides.total).padEnd(2, " ")}`;
1234
+ const tokens = formatTokens(currentTokens);
1235
+ const elapsed = formatTime(getElapsed()).padStart(5, " ");
1236
+ const remaining = getRemaining().padStart(5, " ");
1237
+ return `${bar} ${chalk4.bold(pct)}${chalk4.gray("%")} ${chalk4.cyan(phase)} ${chalk4.gray(slides)} ${chalk4.yellow(tokens)} ${chalk4.gray(elapsed)} ${chalk4.green(remaining + " left")}`;
1238
+ };
1239
+ if (useTTY) {
1240
+ spinner = ora2({
1241
+ text: buildProgressLine(),
1242
+ spinner: "dots"
1243
+ }).start();
1244
+ timer = setInterval(() => {
1245
+ if (spinner && spinner.isSpinning) {
1246
+ spinner.text = buildProgressLine();
1247
+ }
1248
+ }, 100);
1249
+ }
1250
+ const result = await handleStream(
1251
+ response,
1252
+ {
1253
+ onProgress: (current, total) => {
1254
+ currentSlides = { done: current, total };
1255
+ if (!useTTY && !options.quiet && !options.json) {
1256
+ const progress = `${currentPhase.trim()}: slide ${current}/${total}`;
1257
+ if (progress !== lastProgress) {
1258
+ console.log(progress);
1259
+ lastProgress = progress;
1260
+ }
1261
+ }
1262
+ },
1263
+ onPhase: (phase, percentage) => {
1264
+ currentPhase = getPhaseLabel(phase);
1265
+ currentPercentage = percentage;
1266
+ },
1267
+ onTokens: (tokens) => {
1268
+ currentTokens = tokens;
1269
+ },
1270
+ onEstimatedTime: (seconds) => {
1271
+ estimatedTime = seconds;
1272
+ },
1273
+ onError: (error2) => {
1274
+ if (timer) clearInterval(timer);
1275
+ if (spinner) {
1276
+ spinner.fail(chalk4.red(`Error: ${error2}`));
1277
+ } else if (!options.json) {
1278
+ console.error(chalk4.red(`Error: ${error2}`));
1279
+ }
1280
+ },
1281
+ onComplete: (data) => {
1282
+ if (timer) clearInterval(timer);
1283
+ if (spinner) {
1284
+ currentPercentage = 100;
1285
+ currentPhase = "Done";
1286
+ spinner.succeed(buildProgressLine());
1287
+ } else if (!options.json) {
1288
+ const tokenStr = currentTokens > 0 ? ` | ${currentTokens.toLocaleString()} tokens` : "";
1289
+ console.log(`Done! [${formatTime(getElapsed())}${tokenStr}]`);
1290
+ }
1291
+ }
1292
+ },
1293
+ { debug: options.debug }
1294
+ );
1295
+ return {
1296
+ ...result,
1297
+ elapsedSeconds: getElapsed(),
1298
+ totalTokens: currentTokens
1299
+ };
1300
+ }
1301
+ async function streamTextContent(response, options = {}) {
1302
+ const reader = response.body?.getReader();
1303
+ if (!reader) {
1304
+ throw new Error("Response body is not readable");
1305
+ }
1306
+ const decoder = new TextDecoder();
1307
+ let content = "";
1308
+ let done = false;
1309
+ while (!done) {
1310
+ const result = await reader.read();
1311
+ done = result.done;
1312
+ if (result.value) {
1313
+ const chunk = decoder.decode(result.value, { stream: true });
1314
+ content += chunk;
1315
+ if (!options.quiet) {
1316
+ process.stdout.write(chunk);
1317
+ }
1318
+ }
1319
+ }
1320
+ if (!options.quiet) {
1321
+ console.log();
1322
+ }
1323
+ return content;
1324
+ }
1325
+
1326
+ // src/commands/create.ts
1327
+ import { readFileSync as readFileSync2, existsSync } from "fs";
1328
+ import { resolve } from "path";
1329
+ var VALID_GOALS = [
1330
+ "inform",
1331
+ "persuade",
1332
+ "train",
1333
+ "learn",
1334
+ "entertain",
1335
+ "report"
1336
+ ];
1337
+ var createCommand = new Command2("create").description("Create a new presentation").argument("<topic>", "The topic or title for the presentation").option("-n, --slides <count>", "Number of slides (1-20)", "10").option(
1338
+ "-m, --mode <mode>",
1339
+ "Generation quality mode (best, balanced, fast, ultrafast, instant)",
1340
+ "balanced"
1341
+ ).option(
1342
+ "-t, --tone <tone>",
1343
+ "Presentation tone (creative, professional, educational, formal, casual)",
1344
+ "professional"
1345
+ ).option(
1346
+ "--amount <amount>",
1347
+ "Content density (minimal, concise, detailed, extensive)",
1348
+ "concise"
1349
+ ).option("--audience <text>", "Target audience description", "General Audience").option("-l, --language <lang>", "Output language", "en").option("-b, --brand <id|url>", "Branding ID or URL to extract from").option(
1350
+ "-c, --context <text>",
1351
+ "Text context to inform slide content (e.g., research notes, key facts)"
1352
+ ).option(
1353
+ "--context-file <path>",
1354
+ "Path to a file containing context (text, markdown, or JSON)"
1355
+ ).option(
1356
+ "--sources <urls...>",
1357
+ "URLs to scrape for context (space-separated). Content will be fetched and used"
1358
+ ).option("--stdin", "Read context content from stdin (pipe data into the command)").option(
1359
+ "-f, --file <paths...>",
1360
+ "Files to upload (PDF, PPTX, DOCX, images). Use multiple times for multiple files"
1361
+ ).option(
1362
+ "-g, --goal <type>",
1363
+ "Presentation goal (inform, persuade, train, learn, entertain, report)"
1364
+ ).option(
1365
+ "--custom-goal <text>",
1366
+ "Custom goal description (overrides --goal)"
1367
+ ).option(
1368
+ "--styling <mode>",
1369
+ "Styling mode (freeform, brand-only, brand-plus-style, style-only, no-styling)",
1370
+ "freeform"
1371
+ ).option(
1372
+ "--reference-url <url>",
1373
+ "URL to an image or template to use as a style reference"
1374
+ ).option(
1375
+ "--thinking-depth <depth>",
1376
+ "AI thinking depth (quick, moderate, deep, profound)",
1377
+ "moderate"
1378
+ ).option("-o, --output <format>", "Output format (human, json, quiet)", "human").option("--no-stream", "Wait for completion without progress display").option("--debug", "Enable debug logging").option("--team-id <id>", "Team ID to create presentation for (switch teams)").addHelpText(
1379
+ "after",
1380
+ `
1381
+ ${chalk5.bold("Recommended Usage:")}
1382
+ Always specify slides (-n), mode (-m), audience, and context for best results.
1383
+
1384
+ ${chalk5.bold("Examples:")}
1385
+ ${chalk5.gray("# Content from PDF + style from image reference")}
1386
+ $ conceptcraft create "Quarterly Report" \\
1387
+ --file ./report.pdf \\
1388
+ --reference-url https://example.com/style-template.png \\
1389
+ -n 12 -m best --audience "Executive team"
1390
+
1391
+ ${chalk5.gray("# Upload content files (PDF, PPTX, DOCX)")}
1392
+ $ conceptcraft create "Product Demo" \\
1393
+ --file ./existing-deck.pptx --file ./specs.pdf \\
1394
+ --goal persuade --audience "Enterprise buyers"
1395
+
1396
+ ${chalk5.gray("# Inline context with custom styling")}
1397
+ $ conceptcraft create "Q4 Business Review" \\
1398
+ -n 12 -m best --goal inform \\
1399
+ --context "Revenue: $50M (+25% YoY), EBITDA: $8M" \\
1400
+ --reference-url https://example.com/brand-style.jpg
1401
+
1402
+ ${chalk5.gray("# Research presentation from URLs")}
1403
+ $ conceptcraft create "AI Industry Trends" \\
1404
+ -n 10 -m best -t educational \\
1405
+ --sources https://example.com/ai-report.pdf
1406
+
1407
+ ${chalk5.gray("# Pipe content from another command")}
1408
+ $ cat meeting-notes.md | conceptcraft create "Meeting Summary" \\
1409
+ -n 6 -m balanced --goal inform
1410
+
1411
+ ${chalk5.bold("Content Options (what to include in slides):")}
1412
+ -f, --file <paths...> Upload files for content extraction (PDF, PPTX, DOCX)
1413
+ -c, --context <text> Inline text (key facts, data points)
1414
+ --context-file <path> Read content from file (markdown, text, JSON)
1415
+ --sources <urls...> URLs to scrape for content
1416
+ --stdin Pipe content from another command
1417
+
1418
+ ${chalk5.bold("Styling Options (how slides look):")}
1419
+ --reference-url <url> ${chalk5.yellow("Image URL to guide visual style")} (colors, layout, feel)
1420
+ --styling <mode> freeform, brand-only, style-only, no-styling
1421
+ -b, --brand <id> Apply saved brand settings
1422
+
1423
+ ${chalk5.gray("NOTE: --file uploads provide CONTENT (data, text to include)")}
1424
+ ${chalk5.gray(" --reference-url provides STYLE (visual design inspiration)")}
1425
+
1426
+ ${chalk5.bold("Goal Options:")}
1427
+ -g, --goal <type> inform, persuade, train, learn, entertain, report
1428
+ --custom-goal <text> Custom goal description
1429
+
1430
+ ${chalk5.bold("Mode Reference:")}
1431
+ best Highest quality, thorough research (recommended for important decks)
1432
+ balanced Good quality with reasonable speed (default)
1433
+ fast Quick generation, less refinement
1434
+ ultrafast Very quick, minimal processing
1435
+ instant Fastest, basic output
1436
+ `
1437
+ ).action(async (topic, options) => {
1438
+ requireAuth();
1439
+ const slideCount = parseInt(options.slides, 10);
1440
+ if (isNaN(slideCount) || slideCount < 1 || slideCount > 20) {
1441
+ error("Slide count must be between 1 and 20");
1442
+ process.exit(6);
1443
+ }
1444
+ const validModes = [
1445
+ "best",
1446
+ "balanced",
1447
+ "fast",
1448
+ "ultrafast",
1449
+ "instant"
1450
+ ];
1451
+ if (!validModes.includes(options.mode)) {
1452
+ error(
1453
+ `Invalid mode: ${options.mode}. Valid modes: ${validModes.join(", ")}`
1454
+ );
1455
+ process.exit(6);
1456
+ }
1457
+ const validTones = [
1458
+ "creative",
1459
+ "professional",
1460
+ "educational",
1461
+ "formal",
1462
+ "casual"
1463
+ ];
1464
+ if (!validTones.includes(options.tone)) {
1465
+ error(
1466
+ `Invalid tone: ${options.tone}. Valid tones: ${validTones.join(", ")}`
1467
+ );
1468
+ process.exit(6);
1469
+ }
1470
+ const validAmounts = [
1471
+ "minimal",
1472
+ "concise",
1473
+ "detailed",
1474
+ "extensive"
1475
+ ];
1476
+ if (!validAmounts.includes(options.amount)) {
1477
+ error(
1478
+ `Invalid amount: ${options.amount}. Valid amounts: ${validAmounts.join(", ")}`
1479
+ );
1480
+ process.exit(6);
1481
+ }
1482
+ const validStyling = [
1483
+ "freeform",
1484
+ "brand-only",
1485
+ "brand-plus-style",
1486
+ "style-only",
1487
+ "no-styling"
1488
+ ];
1489
+ if (!validStyling.includes(options.styling)) {
1490
+ error(
1491
+ `Invalid styling: ${options.styling}. Valid modes: ${validStyling.join(", ")}`
1492
+ );
1493
+ process.exit(6);
1494
+ }
1495
+ const validThinkingDepths = [
1496
+ "quick",
1497
+ "moderate",
1498
+ "deep",
1499
+ "profound"
1500
+ ];
1501
+ if (!validThinkingDepths.includes(options.thinkingDepth)) {
1502
+ error(
1503
+ `Invalid thinking depth: ${options.thinkingDepth}. Valid depths: ${validThinkingDepths.join(", ")}`
1504
+ );
1505
+ process.exit(6);
1506
+ }
1507
+ if (options.goal && !VALID_GOALS.includes(options.goal)) {
1508
+ error(
1509
+ `Invalid goal: ${options.goal}. Valid goals: ${VALID_GOALS.join(", ")}`
1510
+ );
1511
+ process.exit(6);
1512
+ }
1513
+ try {
1514
+ if (options.output !== "json" && options.output !== "quiet") {
1515
+ process.stdout.write(chalk5.gray("Checking generation limits... "));
1516
+ }
1517
+ const limits = await validateGeneration(options.mode, slideCount, options.teamId);
1518
+ if (options.output !== "json" && options.output !== "quiet") {
1519
+ console.log(chalk5.green("\u2713"));
1520
+ console.log(
1521
+ chalk5.gray(
1522
+ ` Plan: ${limits.planName} | ${options.mode}: ${limits.remaining[options.mode]}/${limits.limits[options.mode]} remaining`
1523
+ )
1524
+ );
1525
+ }
1526
+ } catch (err) {
1527
+ if (options.output === "json") {
1528
+ console.log(
1529
+ JSON.stringify({
1530
+ success: false,
1531
+ error: err instanceof Error ? err.message : String(err)
1532
+ })
1533
+ );
1534
+ } else {
1535
+ console.log(chalk5.red("\u2717"));
1536
+ error(err instanceof Error ? err.message : String(err));
1537
+ }
1538
+ process.exit(err.exitCode ?? 1);
1539
+ }
1540
+ let uploadedFiles = [];
1541
+ if (options.file && options.file.length > 0) {
1542
+ for (const filePath of options.file) {
1543
+ const resolved = resolve(filePath);
1544
+ if (!existsSync(resolved)) {
1545
+ error(`File not found: ${filePath}`);
1546
+ process.exit(6);
1547
+ }
1548
+ }
1549
+ if (options.output !== "json" && options.output !== "quiet") {
1550
+ console.log(chalk5.gray(`
1551
+ Uploading ${options.file.length} file(s)...`));
1552
+ }
1553
+ try {
1554
+ uploadedFiles = await uploadFiles(
1555
+ options.file.map((f) => resolve(f)),
1556
+ (completed, total, fileName) => {
1557
+ if (options.output !== "json" && options.output !== "quiet" && fileName) {
1558
+ process.stdout.write(
1559
+ `\r ${chalk5.cyan("\u2B06")} Uploading: ${fileName} (${completed + 1}/${total})`
1560
+ );
1561
+ }
1562
+ }
1563
+ );
1564
+ if (options.output !== "json" && options.output !== "quiet") {
1565
+ console.log(`\r ${chalk5.green("\u2713")} Uploaded ${uploadedFiles.length} file(s) `);
1566
+ }
1567
+ } catch (err) {
1568
+ error(
1569
+ `Failed to upload files: ${err instanceof Error ? err.message : String(err)}`
1570
+ );
1571
+ process.exit(1);
1572
+ }
1573
+ }
1574
+ let stdinContent;
1575
+ const hasStdinData = !process.stdin.isTTY;
1576
+ if (options.stdin || hasStdinData) {
1577
+ stdinContent = await readStdin();
1578
+ if (options.stdin && !stdinContent) {
1579
+ error("No content received from stdin");
1580
+ process.exit(6);
1581
+ }
1582
+ }
1583
+ let contextContent = options.context;
1584
+ if (options.contextFile) {
1585
+ try {
1586
+ contextContent = readFileSync2(options.contextFile, "utf-8");
1587
+ if (!contextContent.trim()) {
1588
+ error(`Context file is empty: ${options.contextFile}`);
1589
+ process.exit(6);
1590
+ }
1591
+ } catch (err) {
1592
+ error(`Failed to read context file: ${options.contextFile}`);
1593
+ process.exit(6);
1594
+ }
1595
+ }
1596
+ let brandId = options.brand;
1597
+ if (options.brand && isUrl(options.brand)) {
1598
+ brandId = options.brand;
1599
+ }
1600
+ const hasContext = stdinContent || contextContent || options.sources && options.sources.length > 0 || uploadedFiles.length > 0;
1601
+ if (!hasContext) {
1602
+ error("Context is required to create a presentation.");
1603
+ console.log();
1604
+ console.log(chalk5.gray("Provide context using one of these methods:"));
1605
+ console.log(chalk5.gray(" -f, --file <paths...> Upload files (PDF, PPTX, images)"));
1606
+ console.log(chalk5.gray(" -c, --context <text> Direct text context"));
1607
+ console.log(chalk5.gray(" --context-file <path> Read from a file"));
1608
+ console.log(chalk5.gray(" --sources <urls...> URLs to scrape"));
1609
+ console.log(chalk5.gray(" cat file | conceptcraft Pipe content"));
1610
+ console.log();
1611
+ console.log(chalk5.gray("Example:"));
1612
+ console.log(chalk5.cyan(' conceptcraft create "Q4 Report" --file ./report.pdf'));
1613
+ console.log(chalk5.cyan(' conceptcraft create "Q4 Report" --context "Revenue: $10M, Growth: 25%"'));
1614
+ process.exit(6);
1615
+ }
1616
+ try {
1617
+ const response = await createPresentation({
1618
+ topic,
1619
+ slideCount,
1620
+ mode: options.mode,
1621
+ tone: options.tone,
1622
+ amount: options.amount,
1623
+ audience: options.audience,
1624
+ language: options.language,
1625
+ brandId,
1626
+ sources: options.sources,
1627
+ stdinContent,
1628
+ context: contextContent,
1629
+ stylingMode: options.styling,
1630
+ referenceUrl: options.referenceUrl,
1631
+ thinkingDepth: options.thinkingDepth,
1632
+ // New options
1633
+ uploadedFiles: uploadedFiles.length > 0 ? uploadedFiles : void 0,
1634
+ goal: options.goal,
1635
+ customGoal: options.customGoal,
1636
+ teamId: options.teamId
1637
+ });
1638
+ const result = await streamWithProgress(response, topic, {
1639
+ slideCount,
1640
+ mode: options.mode,
1641
+ tone: options.tone,
1642
+ language: options.language,
1643
+ noStream: options.noStream,
1644
+ debug: options.debug,
1645
+ quiet: options.output === "quiet",
1646
+ json: options.output === "json"
1647
+ });
1648
+ if (options.output === "json") {
1649
+ console.log(
1650
+ JSON.stringify(
1651
+ {
1652
+ success: true,
1653
+ presentation: {
1654
+ slug: result.slug,
1655
+ title: result.title ?? topic,
1656
+ slidesCount: result.slidesCount,
1657
+ elapsedSeconds: result.elapsedSeconds,
1658
+ totalTokens: result.totalTokens
1659
+ },
1660
+ viewUrl: buildViewUrl(result.slug, options.language)
1661
+ },
1662
+ null,
1663
+ 2
1664
+ )
1665
+ );
1666
+ } else {
1667
+ console.log();
1668
+ success("Presentation created successfully");
1669
+ console.log();
1670
+ keyValue("Title", result.title ?? topic);
1671
+ keyValue("Slides", String(result.slidesCount ?? slideCount));
1672
+ const stats = [];
1673
+ if (result.elapsedSeconds) {
1674
+ const mins = Math.floor(result.elapsedSeconds / 60);
1675
+ const secs = result.elapsedSeconds % 60;
1676
+ stats.push(mins > 0 ? `${mins}m ${secs}s` : `${secs}s`);
1677
+ }
1678
+ if (result.totalTokens) {
1679
+ stats.push(`${result.totalTokens.toLocaleString()} tokens`);
1680
+ }
1681
+ if (stats.length > 0) {
1682
+ keyValue("Generated in", stats.join(" \xB7 "));
1683
+ }
1684
+ console.log();
1685
+ const viewUrl = buildViewUrl(result.slug, options.language);
1686
+ if (viewUrl !== "N/A") {
1687
+ console.log(chalk5.bold(" Open: ") + chalk5.cyan.underline(viewUrl));
1688
+ }
1689
+ console.log();
1690
+ }
1691
+ } catch (err) {
1692
+ if (options.output === "json") {
1693
+ console.log(
1694
+ JSON.stringify({
1695
+ success: false,
1696
+ error: err instanceof Error ? err.message : String(err)
1697
+ })
1698
+ );
1699
+ } else {
1700
+ error(err instanceof Error ? err.message : String(err));
1701
+ }
1702
+ process.exit(err.exitCode ?? 1);
1703
+ }
1704
+ });
1705
+ async function readStdin() {
1706
+ return new Promise((resolve4) => {
1707
+ let data = "";
1708
+ process.stdin.setEncoding("utf8");
1709
+ if (process.stdin.isTTY) {
1710
+ resolve4("");
1711
+ return;
1712
+ }
1713
+ process.stdin.on("data", (chunk) => {
1714
+ data += chunk;
1715
+ });
1716
+ process.stdin.on("end", () => {
1717
+ resolve4(data.trim());
1718
+ });
1719
+ setTimeout(() => {
1720
+ resolve4(data.trim());
1721
+ }, 100);
1722
+ });
1723
+ }
1724
+ function isUrl(str) {
1725
+ try {
1726
+ new URL(str);
1727
+ return true;
1728
+ } catch {
1729
+ return str.startsWith("http://") || str.startsWith("https://");
1730
+ }
1731
+ }
1732
+
1733
+ // src/commands/list.ts
1734
+ import { Command as Command3 } from "commander";
1735
+ var listCommand = new Command3("list").description("List presentations").option("-n, --limit <count>", "Number of results to return", "20").option(
1736
+ "-f, --format <format>",
1737
+ "Output format (table, json, ids)",
1738
+ "table"
1739
+ ).option("--sort <field>", "Sort by field (created, updated, title)").option("--team-id <id>", "Team ID (uses default if not specified)").option("-d, --detail", "Show detailed output including URLs").option("-l, --language <lang>", "Language for URLs", "en").action(async (options) => {
1740
+ requireAuth();
1741
+ const limit = parseInt(options.limit, 10);
1742
+ if (isNaN(limit) || limit < 1) {
1743
+ error("Invalid limit value");
1744
+ process.exit(6);
1745
+ }
1746
+ const teamId = options.teamId ?? getDefaultTeamId();
1747
+ if (!teamId) {
1748
+ error(
1749
+ "Team ID required. Set a default with 'conceptcraft config set team-id <id>' or use --team-id"
1750
+ );
1751
+ process.exit(6);
1752
+ }
1753
+ try {
1754
+ const presentations = await listPresentations(teamId, limit);
1755
+ if (presentations.length === 0) {
1756
+ if (options.format === "json") {
1757
+ console.log(JSON.stringify([]));
1758
+ } else if (options.format !== "ids") {
1759
+ info("No presentations found");
1760
+ }
1761
+ return;
1762
+ }
1763
+ if (options.sort) {
1764
+ presentations.sort((a, b) => {
1765
+ switch (options.sort) {
1766
+ case "created":
1767
+ return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
1768
+ case "title":
1769
+ return (a.title || "").localeCompare(b.title || "");
1770
+ default:
1771
+ return 0;
1772
+ }
1773
+ });
1774
+ }
1775
+ switch (options.format) {
1776
+ case "json":
1777
+ console.log(JSON.stringify(presentations, null, 2));
1778
+ break;
1779
+ case "ids":
1780
+ console.log(formatPresentationIds(presentations));
1781
+ break;
1782
+ case "table":
1783
+ default:
1784
+ console.log(formatPresentationTable(presentations, {
1785
+ showLinks: options.detail,
1786
+ language: options.language
1787
+ }));
1788
+ break;
1789
+ }
1790
+ } catch (err) {
1791
+ error(err instanceof Error ? err.message : String(err));
1792
+ process.exit(err.exitCode ?? 1);
1793
+ }
1794
+ });
1795
+
1796
+ // src/commands/get.ts
1797
+ import { Command as Command4 } from "commander";
1798
+ import chalk6 from "chalk";
1799
+ var getCommand = new Command4("get").description("Get presentation details").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option(
1800
+ "-f, --format <format>",
1801
+ "Output format (summary, full, json)",
1802
+ "summary"
1803
+ ).option("--include <fields>", "Fields to include (comma-separated: slides,styling)").option("-l, --language <lang>", "Language for view URL", "en").action(async (slug, options) => {
1804
+ requireAuth();
1805
+ try {
1806
+ const presentation = await getPresentation(slug);
1807
+ if (options.format === "json") {
1808
+ console.log(JSON.stringify(presentation, null, 2));
1809
+ return;
1810
+ }
1811
+ const viewUrl = buildViewUrl(presentation.slug, options.language);
1812
+ header("Presentation Details");
1813
+ console.log();
1814
+ keyValue("Slug", presentation.slug);
1815
+ keyValue("Title", presentation.title || "Untitled");
1816
+ keyValue("Description", presentation.description || chalk6.gray("None"));
1817
+ keyValue("Slides", String(presentation.numberOfSlides || "-"));
1818
+ keyValue("Mode", presentation.mode || "-");
1819
+ keyValue("Created", formatDate(presentation.createdAt));
1820
+ if (presentation.updatedAt) {
1821
+ keyValue("Updated", formatDate(presentation.updatedAt));
1822
+ }
1823
+ console.log();
1824
+ if (viewUrl !== "N/A") {
1825
+ console.log(chalk6.bold(" Open: ") + chalk6.cyan.underline(viewUrl));
1826
+ }
1827
+ console.log();
1828
+ if (options.format === "full" && options.include?.includes("slides")) {
1829
+ console.log(chalk6.gray("(Full slide content available in JSON format)"));
1830
+ console.log();
1831
+ }
1832
+ } catch (err) {
1833
+ error(err instanceof Error ? err.message : String(err));
1834
+ process.exit(err.exitCode ?? 1);
1835
+ }
1836
+ });
1837
+
1838
+ // src/commands/delete.ts
1839
+ import { Command as Command5 } from "commander";
1840
+ import { confirm as confirm2 } from "@inquirer/prompts";
1841
+ var deleteCommand = new Command5("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) => {
1842
+ requireAuth();
1843
+ if (!options.force) {
1844
+ try {
1845
+ const confirmed = await confirm2({
1846
+ message: `Are you sure you want to delete presentation "${slug}"?`,
1847
+ default: false
1848
+ });
1849
+ if (!confirmed) {
1850
+ if (!options.quiet) {
1851
+ warn("Deletion cancelled");
1852
+ }
1853
+ return;
1854
+ }
1855
+ } catch {
1856
+ if (!options.quiet) {
1857
+ warn("Deletion cancelled");
1858
+ }
1859
+ return;
1860
+ }
1861
+ }
1862
+ try {
1863
+ await deletePresentation(slug);
1864
+ if (!options.quiet) {
1865
+ success(`Presentation "${slug}" deleted`);
1866
+ }
1867
+ } catch (err) {
1868
+ error(err instanceof Error ? err.message : String(err));
1869
+ process.exit(err.exitCode ?? 1);
1870
+ }
1871
+ });
1872
+
1873
+ // src/commands/export.ts
1874
+ import { Command as Command6 } from "commander";
1875
+ import { writeFile } from "fs/promises";
1876
+ import { resolve as resolve2 } from "path";
1877
+ import ora3 from "ora";
1878
+ var exportCommand = new Command6("export").description("Export a presentation to ZIP").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option("-o, --output <path>", "Output file path").option("--include-images", "Include images (default: true)", true).option("--include-branding", "Include branding (default: true)", true).option("--no-external", "Skip external image downloads").action(async (slug, options) => {
1879
+ requireAuth();
1880
+ const spinner = ora3("Fetching presentation...").start();
1881
+ try {
1882
+ const presentation = await getPresentation(slug);
1883
+ const title = presentation.title || slug;
1884
+ const presentationId = presentation.id;
1885
+ const sanitizedSlug = slug.replace(/[^a-z0-9-]/gi, "_").toLowerCase().substring(0, 50);
1886
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1887
+ const defaultFileName = `${sanitizedSlug}_${timestamp}.zip`;
1888
+ const outputPath = options.output ? resolve2(options.output) : resolve2(process.cwd(), defaultFileName);
1889
+ spinner.text = `Exporting "${title}"...`;
1890
+ const buffer = await exportPresentation(presentationId, {
1891
+ includeImages: options.includeImages,
1892
+ includeBranding: options.includeBranding,
1893
+ includeExternal: !options.noExternal
1894
+ });
1895
+ spinner.text = "Saving file...";
1896
+ await writeFile(outputPath, Buffer.from(buffer));
1897
+ spinner.succeed("Export complete!");
1898
+ console.log();
1899
+ info(`File saved: ${outputPath}`);
1900
+ info(`Size: ${formatBytes(buffer.byteLength)}`);
1901
+ console.log();
1902
+ } catch (err) {
1903
+ spinner.fail("Export failed");
1904
+ error(err instanceof Error ? err.message : String(err));
1905
+ process.exit(err.exitCode ?? 1);
1906
+ }
1907
+ });
1908
+ function formatBytes(bytes) {
1909
+ if (bytes < 1024) return `${bytes} B`;
1910
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1911
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1912
+ }
1913
+
1914
+ // src/commands/import.ts
1915
+ import { Command as Command7 } from "commander";
1916
+ import { readFile } from "fs/promises";
1917
+ import { resolve as resolve3, basename as basename2 } from "path";
1918
+ import ora4 from "ora";
1919
+ var cmd = new Command7("import").description("Import a presentation from ZIP (admin only)").argument("<file>", "Path to ZIP file");
1920
+ cmd._hidden = true;
1921
+ var importCommand = cmd.option("--dry-run", "Validate without importing").option("--overwrite", "Overwrite existing presentations").option("--remap-ids", "Generate new IDs (default: true)", true).action(async (file, options) => {
1922
+ requireAuth();
1923
+ try {
1924
+ const me = await whoami();
1925
+ if (me.user.role !== "admin") {
1926
+ error("This command is only available to administrators");
1927
+ process.exit(2);
1928
+ }
1929
+ } catch (err) {
1930
+ error("Failed to verify permissions");
1931
+ process.exit(2);
1932
+ }
1933
+ const filePath = resolve3(file);
1934
+ const fileName = basename2(filePath);
1935
+ if (!fileName.endsWith(".zip")) {
1936
+ error("File must be a ZIP archive");
1937
+ process.exit(6);
1938
+ }
1939
+ const spinner = ora4("Reading file...").start();
1940
+ try {
1941
+ const fileBuffer = await readFile(filePath);
1942
+ if (options.dryRun) {
1943
+ spinner.text = "Validating...";
1944
+ } else {
1945
+ spinner.text = "Importing presentation...";
1946
+ }
1947
+ const result = await importPresentation(fileBuffer, fileName, {
1948
+ dryRun: options.dryRun
1949
+ });
1950
+ if (result.success) {
1951
+ if (options.dryRun) {
1952
+ spinner.succeed("Validation passed!");
1953
+ console.log();
1954
+ info("The file is valid and can be imported.");
1955
+ if (result.statistics) {
1956
+ console.log();
1957
+ keyValue("Slides", String(result.statistics.slidesImported));
1958
+ keyValue("Images", String(result.statistics.imagesUploaded));
1959
+ }
1960
+ } else {
1961
+ spinner.succeed("Import complete!");
1962
+ console.log();
1963
+ keyValue("Presentation ID", result.presentationId ?? "N/A");
1964
+ keyValue("Document ID", result.documentId ?? "N/A");
1965
+ if (result.statistics) {
1966
+ keyValue("Slides imported", String(result.statistics.slidesImported));
1967
+ keyValue("Images uploaded", String(result.statistics.imagesUploaded));
1968
+ keyValue("Time", `${result.statistics.totalTimeMs}ms`);
1969
+ }
1970
+ if (result.presentationId) {
1971
+ const apiUrl = getApiUrl();
1972
+ keyValue("View URL", `${apiUrl}/p/${result.presentationId}`);
1973
+ }
1974
+ }
1975
+ if (result.warnings && result.warnings.length > 0) {
1976
+ console.log();
1977
+ for (const warning of result.warnings) {
1978
+ warn(warning);
1979
+ }
1980
+ }
1981
+ console.log();
1982
+ } else {
1983
+ spinner.fail("Import failed");
1984
+ console.log();
1985
+ if (result.errors) {
1986
+ for (const err of result.errors) {
1987
+ error(`${err.type}: ${err.message}`);
1988
+ }
1989
+ }
1990
+ process.exit(1);
1991
+ }
1992
+ } catch (err) {
1993
+ spinner.fail("Import failed");
1994
+ if (err.code === "ENOENT") {
1995
+ error(`File not found: ${filePath}`);
1996
+ process.exit(3);
1997
+ }
1998
+ error(err instanceof Error ? err.message : String(err));
1999
+ process.exit(err.exitCode ?? 1);
2000
+ }
2001
+ });
2002
+
2003
+ // src/commands/branding.ts
2004
+ import { Command as Command8 } from "commander";
2005
+ import chalk7 from "chalk";
2006
+ import ora5 from "ora";
2007
+ var brandingCommand = new Command8("branding").description("Manage brand profiles").addCommand(
2008
+ new Command8("list").description("List brand profiles").option("-f, --format <format>", "Output format (table, json)", "table").action(async (options) => {
2009
+ requireAuth();
2010
+ try {
2011
+ const result = await listBrandings();
2012
+ if (result.brandings.length === 0) {
2013
+ if (options.format === "json") {
2014
+ console.log(JSON.stringify([]));
2015
+ } else {
2016
+ info("No brand profiles found");
2017
+ console.log();
2018
+ console.log(
2019
+ chalk7.gray(
2020
+ "Create one with: conceptcraft branding extract <url>"
2021
+ )
2022
+ );
2023
+ }
2024
+ return;
2025
+ }
2026
+ if (options.format === "json") {
2027
+ console.log(JSON.stringify(result.brandings, null, 2));
2028
+ } else {
2029
+ console.log(formatBrandingTable(result.brandings));
2030
+ }
2031
+ } catch (err) {
2032
+ error(err instanceof Error ? err.message : String(err));
2033
+ process.exit(err.exitCode ?? 1);
2034
+ }
2035
+ })
2036
+ ).addCommand(
2037
+ new Command8("get").description("Get brand profile details").argument("<id>", "Brand profile ID").option("-f, --format <format>", "Output format (summary, json)", "summary").action(async (id, options) => {
2038
+ requireAuth();
2039
+ try {
2040
+ const brand2 = await getBranding(id);
2041
+ if (options.format === "json") {
2042
+ console.log(JSON.stringify(brand2, null, 2));
2043
+ } else {
2044
+ header("Brand Profile");
2045
+ console.log();
2046
+ keyValue("ID", brand2.id);
2047
+ keyValue("Name", brand2.name);
2048
+ keyValue("Source URL", brand2.sourceUrl ?? chalk7.gray("None"));
2049
+ keyValue("Default", brand2.isDefault ? chalk7.green("Yes") : "No");
2050
+ if (brand2.createdAt) {
2051
+ keyValue("Created", new Date(brand2.createdAt).toLocaleString());
2052
+ }
2053
+ console.log();
2054
+ if (brand2.primaryColor || brand2.colors.length > 0) {
2055
+ console.log(chalk7.bold(" Colors"));
2056
+ if (brand2.primaryColor) {
2057
+ const swatch = chalk7.bgHex(brand2.primaryColor)(" ");
2058
+ console.log(` Primary: ${swatch} ${brand2.primaryColor}`);
2059
+ }
2060
+ if (brand2.colors.length > 0) {
2061
+ console.log(" Palette:");
2062
+ for (const c of brand2.colors.slice(0, 10)) {
2063
+ const swatch = chalk7.bgHex(c.hex)(" ");
2064
+ const role = c.role ? chalk7.gray(` (${c.role})`) : "";
2065
+ console.log(` ${swatch} ${c.hex}${role}`);
2066
+ }
2067
+ }
2068
+ console.log();
2069
+ }
2070
+ if (brand2.logoUrl) {
2071
+ console.log(chalk7.bold(" Logo"));
2072
+ console.log(` ${brand2.logoUrl}`);
2073
+ console.log();
2074
+ }
2075
+ const typo = brand2.typography;
2076
+ if (typo && Object.keys(typo).length > 0) {
2077
+ console.log(chalk7.bold(" Typography"));
2078
+ if (typo.primary || typo.headings) {
2079
+ console.log(` Headings: ${typo.primary || typo.headings || "-"}`);
2080
+ }
2081
+ if (typo.secondary || typo.body) {
2082
+ console.log(` Body: ${typo.secondary || typo.body || "-"}`);
2083
+ }
2084
+ console.log();
2085
+ }
2086
+ if (brand2.confidence || brand2.extractionMethod) {
2087
+ console.log(chalk7.bold(" Extraction"));
2088
+ if (brand2.confidence) {
2089
+ console.log(` Confidence: ${Math.round(brand2.confidence * 100)}%`);
2090
+ }
2091
+ if (brand2.extractionMethod) {
2092
+ console.log(` Method: ${brand2.extractionMethod}`);
2093
+ }
2094
+ console.log();
2095
+ }
2096
+ console.log(chalk7.gray(" Use --format json for full details"));
2097
+ console.log();
2098
+ }
2099
+ } catch (err) {
2100
+ error(err instanceof Error ? err.message : String(err));
2101
+ process.exit(err.exitCode ?? 1);
2102
+ }
2103
+ })
2104
+ ).addCommand(
2105
+ new Command8("extract").description("Extract brand profile from a website").argument("<url>", "Website URL to extract branding from").option("--team-id <id>", "Team ID").option("--no-save", "Extract without saving").action(async (url, options) => {
2106
+ requireAuth();
2107
+ try {
2108
+ new URL(url.startsWith("http") ? url : `https://${url}`);
2109
+ } catch {
2110
+ error("Invalid URL format");
2111
+ process.exit(6);
2112
+ }
2113
+ const fullUrl = url.startsWith("http") ? url : `https://${url}`;
2114
+ const teamId = options.teamId ?? getDefaultTeamId();
2115
+ const spinner = ora5({ text: `Extracting branding from ${fullUrl}...`, stream: process.stdout }).start();
2116
+ try {
2117
+ const result = await extractBranding(fullUrl, teamId);
2118
+ const brand2 = await getBranding(result.id);
2119
+ spinner.succeed("Branding extracted!");
2120
+ console.log();
2121
+ header("Brand Profile");
2122
+ console.log();
2123
+ keyValue("ID", brand2.id);
2124
+ keyValue("Name", brand2.name);
2125
+ keyValue("Source URL", brand2.sourceUrl ?? fullUrl);
2126
+ console.log();
2127
+ if (brand2.primaryColor || brand2.colors.length > 0) {
2128
+ console.log(chalk7.bold(" Colors"));
2129
+ if (brand2.primaryColor) {
2130
+ const swatch = chalk7.bgHex(brand2.primaryColor)(" ");
2131
+ console.log(` Primary: ${swatch} ${brand2.primaryColor}`);
2132
+ }
2133
+ if (brand2.colors.length > 0) {
2134
+ console.log(" Palette:");
2135
+ for (const c of brand2.colors.slice(0, 6)) {
2136
+ const swatch = chalk7.bgHex(c.hex)(" ");
2137
+ const role = c.role ? chalk7.gray(` (${c.role})`) : "";
2138
+ console.log(` ${swatch} ${c.hex}${role}`);
2139
+ }
2140
+ }
2141
+ console.log();
2142
+ }
2143
+ if (brand2.logoUrl) {
2144
+ console.log(chalk7.bold(" Logo"));
2145
+ console.log(` ${brand2.logoUrl}`);
2146
+ console.log();
2147
+ }
2148
+ if (brand2.confidence) {
2149
+ console.log(chalk7.bold(" Extraction"));
2150
+ console.log(` Confidence: ${Math.round(brand2.confidence * 100)}%`);
2151
+ console.log();
2152
+ }
2153
+ info(`Create a presentation with this brand: conceptcraft create "Your Topic" -b ${result.id}`);
2154
+ console.log();
2155
+ } catch (err) {
2156
+ spinner.fail("Extraction failed");
2157
+ error(err instanceof Error ? err.message : String(err));
2158
+ process.exit(err.exitCode ?? 1);
2159
+ }
2160
+ })
2161
+ ).addCommand(
2162
+ new Command8("set-default").description("Set a brand profile as default").argument("<id>", "Brand profile ID").action(async (id) => {
2163
+ requireAuth();
2164
+ info(
2165
+ `To set brand ${id} as default, use the web dashboard.`
2166
+ );
2167
+ console.log();
2168
+ console.log(
2169
+ chalk7.gray(
2170
+ "API support for setting default brand is coming soon."
2171
+ )
2172
+ );
2173
+ })
2174
+ );
2175
+
2176
+ // src/commands/derive.ts
2177
+ import { Command as Command9 } from "commander";
2178
+ import chalk8 from "chalk";
2179
+ import ora6 from "ora";
2180
+ function createBlogCommand() {
2181
+ return new Command9("blog").description("Generate a blog post from a presentation").argument("<slug>", "Presentation slug").option("--words <count>", "Target word count (50-2000)", "300").option(
2182
+ "--tone <tone>",
2183
+ "Writing tone (professional, casual, educational)",
2184
+ "professional"
2185
+ ).option("--audience <text>", "Target audience description").option(
2186
+ "-f, --format <format>",
2187
+ "Output format (human, json, markdown)",
2188
+ "human"
2189
+ ).option("--instructions <text>", "Custom instructions for the blog").option("--no-images", "Exclude image references").option("--toc", "Include table of contents").action(async (presentationId, options) => {
2190
+ requireAuth();
2191
+ const wordCount = parseInt(options.words, 10);
2192
+ if (isNaN(wordCount) || wordCount < 50 || wordCount > 2e3) {
2193
+ error("Word count must be between 50 and 2000");
2194
+ process.exit(6);
2195
+ }
2196
+ const validTones = ["professional", "casual", "educational"];
2197
+ if (!validTones.includes(options.tone)) {
2198
+ error(`Invalid tone. Valid options: ${validTones.join(", ")}`);
2199
+ process.exit(6);
2200
+ }
2201
+ const spinner = ora6("Fetching presentation...").start();
2202
+ try {
2203
+ const presentation = await getPresentation(presentationId);
2204
+ spinner.text = "Generating blog post...";
2205
+ const response = await generateBlog(
2206
+ presentation.id,
2207
+ presentation.documentCreatedAt || presentation.createdAt,
2208
+ {
2209
+ targetWordCount: wordCount,
2210
+ tone: options.tone,
2211
+ targetAudience: options.audience ?? "General Audience",
2212
+ customInstructions: options.instructions ?? "",
2213
+ includeImages: !options.noImages,
2214
+ includeTableOfContents: options.toc ?? false
2215
+ }
2216
+ );
2217
+ const content = await streamTextContent(response, { quiet: true });
2218
+ spinner.succeed("Blog generated");
2219
+ if (options.format === "json") {
2220
+ console.log(
2221
+ JSON.stringify(
2222
+ {
2223
+ presentationId,
2224
+ title: presentation.title,
2225
+ wordCount,
2226
+ tone: options.tone,
2227
+ content
2228
+ },
2229
+ null,
2230
+ 2
2231
+ )
2232
+ );
2233
+ } else if (options.format === "markdown") {
2234
+ console.log(content);
2235
+ } else {
2236
+ console.log();
2237
+ console.log(chalk8.bold(`Blog: ${presentation.title}`));
2238
+ console.log(chalk8.gray("\u2500".repeat(40)));
2239
+ console.log();
2240
+ console.log(content);
2241
+ console.log();
2242
+ }
2243
+ } catch (err) {
2244
+ spinner.fail("Generation failed");
2245
+ error(err instanceof Error ? err.message : String(err));
2246
+ process.exit(err.exitCode ?? 1);
2247
+ }
2248
+ });
2249
+ }
2250
+ function createTweetsCommand() {
2251
+ return new Command9("tweets").description("Generate tweets from a presentation").argument("<slug>", "Presentation slug").option("--count <n>", "Number of tweets to generate", "5").option("-f, --format <format>", "Output format (human, json)", "human").action(async (presentationId, options) => {
2252
+ requireAuth();
2253
+ info("Tweet generation coming soon.");
2254
+ });
2255
+ }
2256
+ function createLinkedInCommand() {
2257
+ return new Command9("linkedin").description("Generate a LinkedIn carousel from a presentation").argument("<slug>", "Presentation slug").option("-f, --format <format>", "Output format (human, json)", "human").action(async (presentationId) => {
2258
+ requireAuth();
2259
+ info("LinkedIn carousel generation coming soon.");
2260
+ });
2261
+ }
2262
+ function createQuestionsCommand() {
2263
+ return new Command9("questions").description("Generate questions for a presentation").argument("<slug>", "Presentation slug").option("--count <n>", "Number of questions", "10").option("-f, --format <format>", "Output format (human, json)", "human").action(async (presentationId, options) => {
2264
+ requireAuth();
2265
+ info("Question generation coming soon.");
2266
+ });
2267
+ }
2268
+ function createCheatsheetCommand() {
2269
+ return new Command9("cheatsheet").description("Generate a presenter cheat sheet").argument("<slug>", "Presentation slug").option("--mode <mode>", "Cheat sheet mode (qa-prep, talking-points)", "qa-prep").option(
2270
+ "-f, --format <format>",
2271
+ "Output format (human, json, markdown)",
2272
+ "human"
2273
+ ).action(async (presentationId, options) => {
2274
+ requireAuth();
2275
+ info("Cheat sheet generation coming soon.");
2276
+ });
2277
+ }
2278
+ function buildDeriveCommand() {
2279
+ const derive = new Command9("derive").description("Generate derivative content from a presentation");
2280
+ const flags = getCachedFlags();
2281
+ if (!flags) {
2282
+ return derive;
2283
+ }
2284
+ if (flags.deckToBlog) {
2285
+ derive.addCommand(createBlogCommand());
2286
+ }
2287
+ if (flags.deckToTweets) {
2288
+ derive.addCommand(createTweetsCommand());
2289
+ }
2290
+ if (flags.linkedInCarousel) {
2291
+ derive.addCommand(createLinkedInCommand());
2292
+ }
2293
+ if (flags.redTeamQuestions) {
2294
+ derive.addCommand(createQuestionsCommand());
2295
+ }
2296
+ if (flags.presenterCheatSheet) {
2297
+ derive.addCommand(createCheatsheetCommand());
2298
+ }
2299
+ return derive;
2300
+ }
2301
+
2302
+ // src/commands/ideas.ts
2303
+ import { Command as Command10 } from "commander";
2304
+ import chalk9 from "chalk";
2305
+ var ideasCommand = new Command10("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) => {
2306
+ requireAuth();
2307
+ info("Idea generation is available in the web dashboard.");
2308
+ console.log();
2309
+ console.log(
2310
+ chalk9.gray("The CLI will support idea generation in a future release.")
2311
+ );
2312
+ console.log();
2313
+ console.log("For now, try these approaches:");
2314
+ console.log(
2315
+ chalk9.gray(
2316
+ " 1. Visit the ConceptCraft dashboard and use the idea generator"
2317
+ )
2318
+ );
2319
+ console.log(
2320
+ chalk9.gray(" 2. Create a presentation with a broad topic and refine it")
2321
+ );
2322
+ console.log();
2323
+ });
2324
+
2325
+ // src/commands/whoami.ts
2326
+ import { Command as Command11 } from "commander";
2327
+ import chalk10 from "chalk";
2328
+ var whoamiCommand = new Command11("whoami").description("Show current user and team information").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
2329
+ requireAuth();
2330
+ try {
2331
+ const result = await whoami();
2332
+ refreshInBackground();
2333
+ if (options.format === "json") {
2334
+ console.log(JSON.stringify(result, null, 2));
2335
+ return;
2336
+ }
2337
+ header("User");
2338
+ console.log();
2339
+ keyValue("Email", result.user.email);
2340
+ if (result.user.username) {
2341
+ keyValue("Username", result.user.username);
2342
+ }
2343
+ if (result.user.firstName || result.user.lastName) {
2344
+ keyValue(
2345
+ "Name",
2346
+ [result.user.firstName, result.user.lastName].filter(Boolean).join(" ")
2347
+ );
2348
+ }
2349
+ keyValue("Auth Type", result.authType === "apiKey" ? "API Key" : "Session");
2350
+ if (result.currentTeam) {
2351
+ console.log();
2352
+ header("Current Team");
2353
+ console.log();
2354
+ keyValue("ID", result.currentTeam.id);
2355
+ keyValue("Name", result.currentTeam.name);
2356
+ keyValue("Plan", result.currentTeam.planName);
2357
+ keyValue("Role", result.currentTeam.isOwner ? "Owner" : "Member");
2358
+ }
2359
+ if (result.teams.length > 1) {
2360
+ console.log();
2361
+ header("All Teams");
2362
+ console.log();
2363
+ for (const team of result.teams) {
2364
+ const current = team.isCurrent ? chalk10.green(" (current)") : "";
2365
+ console.log(
2366
+ ` ${chalk10.bold(team.name)}${current}`
2367
+ );
2368
+ console.log(
2369
+ chalk10.gray(` ID: ${team.id} | Plan: ${team.planName} | Role: ${team.role}`)
2370
+ );
2371
+ }
2372
+ }
2373
+ console.log();
2374
+ } catch (err) {
2375
+ error(err instanceof Error ? err.message : String(err));
2376
+ process.exit(err.exitCode ?? 1);
2377
+ }
2378
+ });
2379
+
2380
+ // src/commands/skill.ts
2381
+ import { Command as Command12 } from "commander";
2382
+ import chalk11 from "chalk";
2383
+ import { mkdirSync, writeFileSync, existsSync as existsSync2 } from "fs";
2384
+ import { join } from "path";
2385
+ import { homedir } from "os";
2386
+ var SKILL_CONTENT = `---
2387
+ name: conceptcraft
2388
+ 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.
2389
+ ---
2390
+
2391
+ # ConceptCraft CLI
2392
+
2393
+ Create professional presentations directly from your terminal. The CLI generates AI-powered slides from any context you provide - text, files, URLs, or piped content.
2394
+
2395
+ ## Prerequisites
2396
+
2397
+ \`\`\`bash
2398
+ # Install globally
2399
+ npm install -g @conceptcraft/cli
2400
+
2401
+ # Configure API key (get from https://conceptcraft.ai/settings/api-keys)
2402
+ conceptcraft config init
2403
+ \`\`\`
2404
+
2405
+ ## Core Workflow
2406
+
2407
+ 1. **Gather context** - Read relevant files, code, or documentation
2408
+ 2. **Create presentation** - Pass context to \`conceptcraft create\`
2409
+ 3. **Share URL** - Return the presentation link to the user
2410
+
2411
+ ## Commands
2412
+
2413
+ ### Create Presentation
2414
+
2415
+ Context is **required**. Provide it via one of these methods:
2416
+
2417
+ \`\`\`bash
2418
+ # Upload files (PDFs, PPTX, images, docs)
2419
+ conceptcraft create "Product Overview" --file ./deck.pptx --file ./logo.png
2420
+
2421
+ # Direct text context
2422
+ conceptcraft create "Topic Title" --context "Key points, data, facts..."
2423
+
2424
+ # From a text file
2425
+ conceptcraft create "Topic Title" --context-file ./notes.md
2426
+
2427
+ # Pipe content (auto-detected)
2428
+ cat README.md | conceptcraft create "Project Overview"
2429
+
2430
+ # From URLs (scraped automatically)
2431
+ conceptcraft create "Competitor Analysis" --sources https://example.com/report
2432
+
2433
+ # Combine multiple sources
2434
+ cat src/auth/*.ts | conceptcraft create "Auth System" \\
2435
+ --file ./architecture.png \\
2436
+ --context "Focus on security patterns"
2437
+ \`\`\`
2438
+
2439
+ ### Create Options
2440
+
2441
+ | Option | Description | Default |
2442
+ |--------|-------------|---------|
2443
+ | \`-n, --slides <count>\` | Number of slides (1-20) | 10 |
2444
+ | \`-m, --mode <mode>\` | Quality: \`instant\`, \`fast\`, \`balanced\`, \`best\` | balanced |
2445
+ | \`-t, --tone <tone>\` | Tone: \`professional\`, \`educational\`, \`creative\`, \`formal\`, \`casual\` | professional |
2446
+ | \`--amount <amount>\` | Density: \`minimal\`, \`concise\`, \`detailed\`, \`extensive\` | concise |
2447
+ | \`--audience <text>\` | Target audience | General Audience |
2448
+ | \`-g, --goal <type>\` | Purpose: \`inform\`, \`persuade\`, \`train\`, \`learn\`, \`entertain\`, \`report\` | - |
2449
+ | \`--custom-goal <text>\` | Custom goal description | - |
2450
+ | \`-f, --file <paths...>\` | Files to upload (PDF, PPTX, images, docs) | - |
2451
+ | \`-l, --language <lang>\` | Output language | en |
2452
+ | \`-b, --brand <id>\` | Branding ID to apply | - |
2453
+ | \`-o, --output <format>\` | Output: \`human\`, \`json\`, \`quiet\` | human |
2454
+
2455
+ ### Other Commands
2456
+
2457
+ \`\`\`bash
2458
+ # Check authentication
2459
+ conceptcraft whoami
2460
+
2461
+ # List presentations
2462
+ conceptcraft list
2463
+ conceptcraft list --format json
2464
+
2465
+ # Get presentation details
2466
+ conceptcraft get <id-or-slug>
2467
+
2468
+ # Export to ZIP
2469
+ conceptcraft export <id-or-slug> -o presentation.zip
2470
+
2471
+ # Import presentation
2472
+ conceptcraft import ./presentation.zip
2473
+
2474
+ # Manage branding
2475
+ conceptcraft branding list
2476
+ conceptcraft branding extract https://company.com
2477
+
2478
+ # Install/manage this skill
2479
+ conceptcraft skill install
2480
+ conceptcraft skill show
2481
+ \`\`\`
2482
+
2483
+ ## Examples
2484
+
2485
+ ### Present a Codebase Feature
2486
+
2487
+ \`\`\`bash
2488
+ # Read the relevant files and create presentation
2489
+ cat src/lib/auth.ts src/lib/session.ts | conceptcraft create "Authentication System" \\
2490
+ --slides 8 --tone educational --audience "New developers" \\
2491
+ --goal train
2492
+ \`\`\`
2493
+
2494
+ ### Technical Documentation with Diagrams
2495
+
2496
+ \`\`\`bash
2497
+ conceptcraft create "API Reference" \\
2498
+ --file ./docs/api.md \\
2499
+ --file ./diagrams/architecture.png \\
2500
+ --mode best --amount detailed \\
2501
+ --goal inform
2502
+ \`\`\`
2503
+
2504
+ ### Quick Project Overview
2505
+
2506
+ \`\`\`bash
2507
+ cat README.md package.json | conceptcraft create "Project Introduction" \\
2508
+ -m instant --slides 5
2509
+ \`\`\`
2510
+
2511
+ ### Sales Deck from Existing Presentation
2512
+
2513
+ \`\`\`bash
2514
+ conceptcraft create "Product Demo" \\
2515
+ --file ./existing-deck.pptx \\
2516
+ --goal persuade \\
2517
+ --audience "Enterprise buyers" \\
2518
+ --tone professional
2519
+ \`\`\`
2520
+
2521
+ ### Research Presentation
2522
+
2523
+ \`\`\`bash
2524
+ conceptcraft create "Market Analysis" \\
2525
+ --file ./research.pdf \\
2526
+ --sources https://report.com/industry.pdf \\
2527
+ --tone formal --audience "Executive team" \\
2528
+ --goal report
2529
+ \`\`\`
2530
+
2531
+ ## Output
2532
+
2533
+ Successful creation returns:
2534
+ \`\`\`
2535
+ \u2713 Presentation created successfully
2536
+
2537
+ Title: Authentication System
2538
+ Slides: 8
2539
+ Generated in: 45s \xB7 12,500 tokens
2540
+
2541
+ Open: https://conceptcraft.ai/en/view/presentations/auth-system-v1-abc123
2542
+ \`\`\`
2543
+
2544
+ For scripting, use JSON output:
2545
+ \`\`\`bash
2546
+ URL=$(conceptcraft create "Demo" --context "..." -o json | jq -r '.viewUrl')
2547
+ \`\`\`
2548
+
2549
+ ## Best Practices
2550
+
2551
+ 1. **Provide rich context** - More context = better slides. Include code, docs, data.
2552
+ 2. **Use file uploads for binary content** - PDFs, images, PPTX files need \`--file\`.
2553
+ 3. **Specify a goal** - Helps tailor the presentation structure and messaging.
2554
+ 4. **Use appropriate mode** - \`instant\` for quick drafts, \`best\` for important presentations.
2555
+ 5. **Specify audience** - Helps tailor complexity and terminology.
2556
+ 6. **Combine sources** - Pipe multiple files for comprehensive presentations.
2557
+
2558
+ ## Supported File Types
2559
+
2560
+ - **Documents**: PDF, DOCX, XLSX, PPTX
2561
+ - **Images**: JPEG, PNG, GIF, WebP
2562
+ - **Text**: Markdown, TXT, CSV, JSON
2563
+
2564
+ ## Troubleshooting
2565
+
2566
+ \`\`\`bash
2567
+ # Check if authenticated
2568
+ conceptcraft whoami
2569
+
2570
+ # Verify API key
2571
+ conceptcraft config show
2572
+
2573
+ # Debug mode
2574
+ conceptcraft create "Test" --context "test" --debug
2575
+ \`\`\`
2576
+ `;
2577
+ var EDITORS = [
2578
+ { name: "Claude Code", dir: ".claude" },
2579
+ { name: "Cursor", dir: ".cursor" },
2580
+ { name: "Codex", dir: ".codex" },
2581
+ { name: "OpenCode", dir: ".opencode" },
2582
+ { name: "Windsurf", dir: ".windsurf" },
2583
+ { name: "Agent", dir: ".agent" }
2584
+ ];
2585
+ var skillCommand = new Command12("skill").description("Manage ConceptCraft skill for AI coding assistants").addHelpText(
2586
+ "after",
2587
+ `
2588
+ ${chalk11.bold("Examples:")}
2589
+ ${chalk11.gray("# Install skill for all detected editors")}
2590
+ $ conceptcraft skill install
2591
+
2592
+ ${chalk11.gray("# Install to specific directory")}
2593
+ $ conceptcraft skill install --dir ~/.claude
2594
+
2595
+ ${chalk11.gray("# Show skill content")}
2596
+ $ conceptcraft skill show
2597
+ `
2598
+ );
2599
+ skillCommand.command("install").description("Install the ConceptCraft skill for AI coding assistants").option("-d, --dir <path>", "Install to specific directory").option("-g, --global", "Install globally (to home directory)", true).option("-l, --local", "Install locally (to current directory)").option("-f, --force", "Overwrite existing skill files").action(async (options) => {
2600
+ const installed = [];
2601
+ const skipped = [];
2602
+ const errors = [];
2603
+ const baseDir = options.local ? process.cwd() : homedir();
2604
+ if (options.dir) {
2605
+ const skillPath = join(options.dir, "skills", "conceptcraft");
2606
+ try {
2607
+ installSkill(skillPath, options.force);
2608
+ installed.push(options.dir);
2609
+ } catch (err) {
2610
+ errors.push(`${options.dir}: ${err instanceof Error ? err.message : String(err)}`);
2611
+ }
2612
+ } else {
2613
+ for (const editor of EDITORS) {
2614
+ const editorDir = join(baseDir, editor.dir);
2615
+ const skillPath = join(editorDir, "skills", "conceptcraft");
2616
+ const skillFile = join(skillPath, "SKILL.md");
2617
+ if (!existsSync2(editorDir)) {
2618
+ continue;
2619
+ }
2620
+ if (existsSync2(skillFile) && !options.force) {
2621
+ skipped.push(editor.name);
2622
+ continue;
2623
+ }
2624
+ try {
2625
+ installSkill(skillPath, options.force);
2626
+ installed.push(editor.name);
2627
+ } catch (err) {
2628
+ errors.push(`${editor.name}: ${err instanceof Error ? err.message : String(err)}`);
2629
+ }
2630
+ }
2631
+ }
2632
+ console.log();
2633
+ if (installed.length > 0) {
2634
+ success("Skill installed successfully");
2635
+ console.log();
2636
+ keyValue("Installed to", installed.join(", "));
2637
+ }
2638
+ if (skipped.length > 0) {
2639
+ console.log();
2640
+ info(`Skipped (already exists): ${skipped.join(", ")}`);
2641
+ console.log(chalk11.gray(" Use --force to overwrite"));
2642
+ }
2643
+ if (errors.length > 0) {
2644
+ console.log();
2645
+ for (const err of errors) {
2646
+ error(err);
2647
+ }
2648
+ }
2649
+ if (installed.length === 0 && skipped.length === 0 && errors.length === 0) {
2650
+ info("No supported AI coding assistants detected.");
2651
+ console.log();
2652
+ console.log(chalk11.gray("Supported editors: " + EDITORS.map((e) => e.name).join(", ")));
2653
+ console.log(chalk11.gray("Use --dir <path> to install to a specific directory"));
2654
+ }
2655
+ console.log();
2656
+ });
2657
+ skillCommand.command("show").description("Display the skill content").action(() => {
2658
+ console.log(SKILL_CONTENT);
2659
+ });
2660
+ skillCommand.command("uninstall").description("Remove the ConceptCraft skill from AI coding assistants").option("-g, --global", "Uninstall globally (from home directory)", true).option("-l, --local", "Uninstall locally (from current directory)").action(async (options) => {
2661
+ const { rmSync } = await import("fs");
2662
+ const removed = [];
2663
+ const baseDir = options.local ? process.cwd() : homedir();
2664
+ for (const editor of EDITORS) {
2665
+ const skillPath = join(baseDir, editor.dir, "skills", "conceptcraft");
2666
+ if (existsSync2(skillPath)) {
2667
+ try {
2668
+ rmSync(skillPath, { recursive: true });
2669
+ removed.push(editor.name);
2670
+ } catch {
2671
+ }
2672
+ }
2673
+ }
2674
+ console.log();
2675
+ if (removed.length > 0) {
2676
+ success("Skill uninstalled");
2677
+ keyValue("Removed from", removed.join(", "));
2678
+ } else {
2679
+ info("No installed skills found");
2680
+ }
2681
+ console.log();
2682
+ });
2683
+ function installSkill(skillPath, force) {
2684
+ const skillFile = join(skillPath, "SKILL.md");
2685
+ mkdirSync(skillPath, { recursive: true });
2686
+ writeFileSync(skillFile, SKILL_CONTENT, "utf-8");
2687
+ }
2688
+
2689
+ // src/index.ts
2690
+ var VERSION = "0.1.0";
2691
+ var program = new Command13();
2692
+ var cmdName = brand.commands[0];
2693
+ 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({
2694
+ outputError: (str, write) => {
2695
+ write(chalk12.red(str));
2696
+ }
2697
+ });
2698
+ program.addCommand(configCommand);
2699
+ program.addCommand(createCommand);
2700
+ program.addCommand(listCommand);
2701
+ program.addCommand(getCommand);
2702
+ program.addCommand(deleteCommand);
2703
+ program.addCommand(exportCommand);
2704
+ program.addCommand(importCommand);
2705
+ program.addCommand(brandingCommand);
2706
+ program.addCommand(ideasCommand);
2707
+ program.addCommand(whoamiCommand);
2708
+ program.addCommand(skillCommand);
2709
+ var deriveCommand = buildDeriveCommand();
2710
+ if (deriveCommand.commands.length > 0) {
2711
+ program.addCommand(deriveCommand);
2712
+ }
2713
+ program.on("command:*", (operands) => {
2714
+ console.error(chalk12.red(`Error: Unknown command '${operands[0]}'`));
2715
+ console.error();
2716
+ console.error(`Run '${cmdName} --help' to see available commands.`);
2717
+ process.exit(1);
2718
+ });
2719
+ program.addHelpText(
2720
+ "after",
2721
+ `
2722
+ ${chalk12.bold("Examples:")}
2723
+ ${chalk12.gray("# Configure CLI")}
2724
+ $ ${cmdName} config init
2725
+
2726
+ ${chalk12.gray("# Create a presentation (recommended pattern)")}
2727
+ $ ${cmdName} create "Q4 Business Review" \\
2728
+ -n 12 -m best \\
2729
+ --audience "Executive leadership team" \\
2730
+ --context "Revenue: $50M (+25% YoY), New customers: 150"
2731
+
2732
+ ${chalk12.gray("# Create from a file")}
2733
+ $ ${cmdName} create "Product Launch" \\
2734
+ -n 10 -m balanced \\
2735
+ --audience "Sales team and partners" \\
2736
+ --context-file ./launch-brief.md
2737
+
2738
+ ${chalk12.gray("# Create from URLs")}
2739
+ $ ${cmdName} create "Market Analysis" \\
2740
+ -n 15 -m best \\
2741
+ --audience "Strategy team" \\
2742
+ --sources https://example.com/report.pdf
2743
+
2744
+ ${chalk12.gray("# List and export")}
2745
+ $ ${cmdName} list --format table
2746
+ $ ${cmdName} export <slug> -o backup.zip
2747
+
2748
+ ${chalk12.bold("Environment Variables:")}
2749
+ CC_SLIDES_API_KEY API key for authentication
2750
+ CC_SLIDES_API_URL Custom API URL (optional)
2751
+
2752
+ ${chalk12.bold("More Info:")}
2753
+ ${chalk12.blue(brand.docsUrl)}
2754
+ `
2755
+ );
2756
+ program.parse();
2757
+ //# sourceMappingURL=index.js.map