@conceptcraft/mindframes 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,84 +1,27 @@
1
1
  #!/usr/bin/env node
2
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
- }
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
29
7
  };
30
- var COMMAND_TO_BRAND = {
31
- cc: "conceptcraft",
32
- conceptcraft: "conceptcraft",
33
- mf: "mindframes",
34
- mindframes: "mindframes"
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
35
11
  };
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
12
 
58
13
  // src/lib/config.ts
59
14
  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: "mindframes",
75
- schema
76
- });
77
15
  function getConfig() {
78
16
  return {
79
17
  apiKey: getApiKey(),
80
18
  apiUrl: getApiUrl(),
81
- defaultTeamId: config.get("defaultTeamId")
19
+ defaultTeamId: config.get("defaultTeamId"),
20
+ accessToken: config.get("accessToken"),
21
+ refreshToken: config.get("refreshToken"),
22
+ tokenExpiresAt: config.get("tokenExpiresAt"),
23
+ clientId: config.get("clientId"),
24
+ clientSecret: config.get("clientSecret")
82
25
  };
83
26
  }
84
27
  function getApiKey() {
@@ -116,38 +59,329 @@ function getConfigPath() {
116
59
  function hasApiKey() {
117
60
  return !!getApiKey();
118
61
  }
62
+ function getAccessToken() {
63
+ return config.get("accessToken");
64
+ }
65
+ function getRefreshToken() {
66
+ return config.get("refreshToken");
67
+ }
68
+ function setOAuthTokens(accessToken, refreshToken, expiresIn) {
69
+ config.set("accessToken", accessToken);
70
+ config.set("refreshToken", refreshToken);
71
+ config.set("tokenExpiresAt", Date.now() + (expiresIn - 60) * 1e3);
72
+ }
73
+ function clearOAuthTokens() {
74
+ config.delete("accessToken");
75
+ config.delete("refreshToken");
76
+ config.delete("tokenExpiresAt");
77
+ }
78
+ function isTokenExpired() {
79
+ const expiresAt = config.get("tokenExpiresAt");
80
+ if (!expiresAt) return true;
81
+ return Date.now() >= expiresAt;
82
+ }
83
+ function hasOAuthTokens() {
84
+ return !!config.get("accessToken") && !!config.get("refreshToken");
85
+ }
86
+ function getClientId() {
87
+ return config.get("clientId");
88
+ }
89
+ function getClientSecret() {
90
+ return config.get("clientSecret");
91
+ }
92
+ function setOAuthClient(clientId, clientSecret) {
93
+ config.set("clientId", clientId);
94
+ config.set("clientSecret", clientSecret);
95
+ }
96
+ var DEFAULT_API_URL, schema, config;
97
+ var init_config = __esm({
98
+ "src/lib/config.ts"() {
99
+ "use strict";
100
+ DEFAULT_API_URL = "https://www.mindframes.app";
101
+ schema = {
102
+ apiKey: {
103
+ type: "string"
104
+ },
105
+ apiUrl: {
106
+ type: "string",
107
+ default: DEFAULT_API_URL
108
+ },
109
+ defaultTeamId: {
110
+ type: "string"
111
+ },
112
+ // OAuth tokens (preferred over API key)
113
+ accessToken: {
114
+ type: "string"
115
+ },
116
+ refreshToken: {
117
+ type: "string"
118
+ },
119
+ tokenExpiresAt: {
120
+ type: "number"
121
+ },
122
+ // OAuth client registration (cached per-server)
123
+ clientId: {
124
+ type: "string"
125
+ },
126
+ clientSecret: {
127
+ type: "string"
128
+ }
129
+ };
130
+ config = new Conf({
131
+ projectName: "mindframes",
132
+ schema
133
+ });
134
+ }
135
+ });
119
136
 
120
- // src/lib/auth.ts
137
+ // src/lib/output.ts
121
138
  import chalk from "chalk";
139
+ import Table from "cli-table3";
140
+ function isTTY() {
141
+ return process.stdout.isTTY ?? false;
142
+ }
143
+ function success(message, format = "human") {
144
+ if (format === "quiet") return;
145
+ if (format === "json") return;
146
+ console.log(chalk.green("\u2713"), message);
147
+ }
148
+ function error(message, format = "human") {
149
+ if (format === "quiet") return;
150
+ if (format === "json") {
151
+ console.error(JSON.stringify({ error: message }));
152
+ return;
153
+ }
154
+ console.error(chalk.red("\u2717"), message);
155
+ }
156
+ function warn(message, format = "human") {
157
+ if (format === "quiet") return;
158
+ if (format === "json") return;
159
+ console.warn(chalk.yellow("\u26A0"), message);
160
+ }
161
+ function info(message, format = "human") {
162
+ if (format === "quiet") return;
163
+ if (format === "json") return;
164
+ console.log(chalk.blue("\u2139"), message);
165
+ }
166
+ function buildViewUrl(slug, language = "en") {
167
+ if (!slug) return "N/A";
168
+ const apiUrl = getApiUrl();
169
+ return `${apiUrl}/${language}/view/presentations/${slug}`;
170
+ }
171
+ function formatPresentationTable(presentations, options = {}) {
172
+ const { showLinks = false, language = "en" } = options;
173
+ const truncate = (str, len) => str.length > len ? str.slice(0, len - 1) + "\u2026" : str;
174
+ const shortDateTime = (dateStr) => {
175
+ try {
176
+ const d = new Date(dateStr);
177
+ return d.toLocaleString("en-US", {
178
+ month: "short",
179
+ day: "numeric",
180
+ hour: "numeric",
181
+ minute: "2-digit",
182
+ hour12: true
183
+ }).replace(" at ", ", ");
184
+ } catch {
185
+ return "-";
186
+ }
187
+ };
188
+ if (showLinks) {
189
+ const table2 = new Table({
190
+ head: [
191
+ chalk.cyan("Slug"),
192
+ chalk.cyan("Title"),
193
+ chalk.cyan("Slides"),
194
+ chalk.cyan("Created"),
195
+ chalk.cyan("URL")
196
+ ]
197
+ });
198
+ for (const p of presentations) {
199
+ table2.push([
200
+ truncate(p.slug || p.id.slice(0, 8), 30),
201
+ truncate(p.title || "Untitled", 22),
202
+ String(p.numberOfSlides || "-"),
203
+ shortDateTime(p.createdAt),
204
+ buildViewUrl(p.slug, language)
205
+ ]);
206
+ }
207
+ return table2.toString();
208
+ }
209
+ const table = new Table({
210
+ head: [
211
+ chalk.cyan("Slug"),
212
+ chalk.cyan("Title"),
213
+ chalk.cyan("Slides"),
214
+ chalk.cyan("Mode"),
215
+ chalk.cyan("Created")
216
+ ],
217
+ colWidths: [32, 28, 8, 11, 18]
218
+ });
219
+ for (const p of presentations) {
220
+ table.push([
221
+ p.slug || p.id.slice(0, 8),
222
+ p.title || "Untitled",
223
+ String(p.numberOfSlides || "-"),
224
+ p.mode || "-",
225
+ shortDateTime(p.createdAt)
226
+ ]);
227
+ }
228
+ return table.toString();
229
+ }
230
+ function formatPresentationIds(presentations) {
231
+ return presentations.map((p) => p.slug || p.id).join("\n");
232
+ }
233
+ function formatBrandingTable(brandings) {
234
+ const sorted = [...brandings].sort((a, b) => {
235
+ if (a.isDefault && !b.isDefault) return -1;
236
+ if (!a.isDefault && b.isDefault) return 1;
237
+ const dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
238
+ const dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
239
+ return dateB - dateA;
240
+ });
241
+ const formatUrl = (url) => {
242
+ if (!url) return "-";
243
+ try {
244
+ const u = new URL(url);
245
+ return u.href;
246
+ } catch {
247
+ return url.startsWith("http") ? url : `https://${url}`;
248
+ }
249
+ };
250
+ const table = new Table({
251
+ head: [
252
+ chalk.cyan("Name"),
253
+ chalk.cyan("Source"),
254
+ chalk.cyan("Color"),
255
+ chalk.cyan("Logo"),
256
+ chalk.cyan("Default"),
257
+ chalk.cyan("ID")
258
+ ]
259
+ });
260
+ for (const b of sorted) {
261
+ const colorDisplay = b.primaryColor ? `${chalk.bgHex(b.primaryColor)(" ")} ${b.primaryColor}` : "-";
262
+ table.push([
263
+ b.name,
264
+ formatUrl(b.sourceUrl),
265
+ colorDisplay,
266
+ b.logoUrl ? chalk.green("\u2713") : chalk.gray("\u2717"),
267
+ b.isDefault ? chalk.green("\u2605") : "",
268
+ b.id
269
+ ]);
270
+ }
271
+ return table.toString();
272
+ }
273
+ function formatDate(dateStr) {
274
+ try {
275
+ const date = new Date(dateStr);
276
+ return date.toLocaleString();
277
+ } catch {
278
+ return dateStr;
279
+ }
280
+ }
281
+ function header(text) {
282
+ console.log();
283
+ console.log(chalk.bold(text));
284
+ console.log(chalk.gray("\u2500".repeat(text.length)));
285
+ }
286
+ function keyValue(key, value) {
287
+ console.log(` ${chalk.gray(key + ":")} ${value ?? "-"}`);
288
+ }
289
+ function progressBar(current, total, width = 30, showPercentage = true) {
290
+ const percentage = Math.min(100, Math.round(current / total * 100));
291
+ const filled = Math.round(width * current / total);
292
+ const empty = width - filled;
293
+ const bar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
294
+ return showPercentage ? `[${bar}] ${percentage}%` : `[${bar}]`;
295
+ }
296
+ var init_output = __esm({
297
+ "src/lib/output.ts"() {
298
+ "use strict";
299
+ init_config();
300
+ }
301
+ });
122
302
 
123
303
  // 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
- };
304
+ var EXIT_CODES;
305
+ var init_types = __esm({
306
+ "src/types/index.ts"() {
307
+ "use strict";
308
+ EXIT_CODES = {
309
+ SUCCESS: 0,
310
+ GENERAL_ERROR: 1,
311
+ AUTH_ERROR: 2,
312
+ NOT_FOUND: 3,
313
+ RATE_LIMIT: 4,
314
+ NETWORK_ERROR: 5,
315
+ INVALID_INPUT: 6
316
+ };
317
+ }
318
+ });
133
319
 
134
320
  // 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_MINDFRAMES_API_KEY environment variable")
142
- );
143
- console.error(
144
- chalk.gray(" 2. Run: mindframes config init")
145
- );
146
- console.error();
147
- console.error(
148
- chalk.gray(`Config file location: ${getConfigPath()}`)
149
- );
150
- process.exit(EXIT_CODES.AUTH_ERROR);
321
+ import chalk2 from "chalk";
322
+ async function refreshAccessToken() {
323
+ const refreshToken = getRefreshToken();
324
+ const clientId = getClientId();
325
+ const apiUrl = getApiUrl();
326
+ if (!refreshToken || !clientId) {
327
+ return null;
328
+ }
329
+ try {
330
+ const metaResponse = await fetch(`${apiUrl}/.well-known/oauth-authorization-server`);
331
+ if (!metaResponse.ok) {
332
+ return null;
333
+ }
334
+ const metadata = await metaResponse.json();
335
+ const params = new URLSearchParams({
336
+ grant_type: "refresh_token",
337
+ refresh_token: refreshToken,
338
+ client_id: clientId
339
+ });
340
+ const response = await fetch(metadata.token_endpoint, {
341
+ method: "POST",
342
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
343
+ body: params.toString()
344
+ });
345
+ if (!response.ok) {
346
+ clearOAuthTokens();
347
+ return null;
348
+ }
349
+ const tokens = await response.json();
350
+ setOAuthTokens(tokens.access_token, tokens.refresh_token, tokens.expires_in);
351
+ return tokens.access_token;
352
+ } catch {
353
+ return null;
354
+ }
355
+ }
356
+ async function getValidAccessToken() {
357
+ if (!hasOAuthTokens()) {
358
+ return null;
359
+ }
360
+ if (isTokenExpired()) {
361
+ return refreshAccessToken();
362
+ }
363
+ return getAccessToken() || null;
364
+ }
365
+ function hasAuth() {
366
+ return hasOAuthTokens() || hasApiKey();
367
+ }
368
+ async function requireAuth() {
369
+ if (!hasAuth()) {
370
+ const { confirm: confirm4 } = await import("@inquirer/prompts");
371
+ try {
372
+ const shouldLogin = await confirm4({
373
+ message: chalk2.red("Not authenticated.") + " Log in now?",
374
+ default: true
375
+ });
376
+ if (!shouldLogin) {
377
+ console.log(chalk2.gray("\nTip: You can also set CC_MINDFRAMES_API_KEY environment variable."));
378
+ process.exit(EXIT_CODES.AUTH_ERROR);
379
+ }
380
+ const { runLoginFlow: runLoginFlow2 } = await Promise.resolve().then(() => (init_login(), login_exports));
381
+ await runLoginFlow2({ browser: true });
382
+ } catch {
383
+ process.exit(EXIT_CODES.AUTH_ERROR);
384
+ }
151
385
  }
152
386
  }
153
387
  function maskApiKey(key) {
@@ -165,33 +399,43 @@ function isValidApiKeyFormat(key) {
165
399
  const validPrefixes = ["cc_slides_", "mcp_", "cc_sk_", "sk_"];
166
400
  return validPrefixes.some((prefix) => key.startsWith(prefix));
167
401
  }
402
+ var init_auth = __esm({
403
+ "src/lib/auth.ts"() {
404
+ "use strict";
405
+ init_config();
406
+ init_types();
407
+ }
408
+ });
168
409
 
169
410
  // src/lib/api.ts
170
411
  import { readFileSync, statSync } from "fs";
171
412
  import { basename } from "path";
172
413
  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";
414
+ async function getAuthHeaders() {
415
+ const accessToken = await getValidAccessToken();
416
+ if (accessToken) {
417
+ return { Authorization: `Bearer ${accessToken}` };
179
418
  }
180
- };
181
- async function request(endpoint, options = {}) {
182
419
  const apiKey = getApiKey();
420
+ if (apiKey) {
421
+ return { "x-api-key": apiKey };
422
+ }
423
+ return {};
424
+ }
425
+ async function request(endpoint, options = {}) {
183
426
  const apiUrl = getApiUrl();
184
- if (!apiKey) {
427
+ if (!hasAuth()) {
185
428
  throw new ApiError(
186
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
429
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
187
430
  401,
188
431
  2
189
432
  // AUTH_ERROR
190
433
  );
191
434
  }
435
+ const authHeaders = await getAuthHeaders();
192
436
  const url = `${apiUrl}${endpoint}`;
193
437
  const headers = {
194
- "x-api-key": apiKey,
438
+ ...authHeaders,
195
439
  ...options.headers
196
440
  };
197
441
  if (options.body && !options.stream) {
@@ -248,15 +492,15 @@ async function request(endpoint, options = {}) {
248
492
  return response.text();
249
493
  }
250
494
  async function streamRequest(endpoint, body) {
251
- const apiKey = getApiKey();
252
495
  const apiUrl = getApiUrl();
253
- if (!apiKey) {
496
+ if (!hasAuth()) {
254
497
  throw new ApiError(
255
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
498
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
256
499
  401,
257
500
  2
258
501
  );
259
502
  }
503
+ const authHeaders = await getAuthHeaders();
260
504
  const url = `${apiUrl}${endpoint}`;
261
505
  let response;
262
506
  try {
@@ -264,7 +508,7 @@ async function streamRequest(endpoint, body) {
264
508
  method: "POST",
265
509
  headers: {
266
510
  "Content-Type": "application/json",
267
- "x-api-key": apiKey
511
+ ...authHeaders
268
512
  },
269
513
  body: JSON.stringify(body)
270
514
  });
@@ -324,15 +568,15 @@ function getMimeType(filePath) {
324
568
  return mimeTypes[ext || ""] || "application/octet-stream";
325
569
  }
326
570
  async function uploadFile(filePath) {
327
- const apiKey = getApiKey();
328
571
  const apiUrl = getApiUrl();
329
- if (!apiKey) {
572
+ if (!hasAuth()) {
330
573
  throw new ApiError(
331
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
574
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
332
575
  401,
333
576
  2
334
577
  );
335
578
  }
579
+ const authHeaders = await getAuthHeaders();
336
580
  const stat = statSync(filePath);
337
581
  const fileName = basename(filePath);
338
582
  const mimeType = getMimeType(filePath);
@@ -340,7 +584,7 @@ async function uploadFile(filePath) {
340
584
  method: "POST",
341
585
  headers: {
342
586
  "Content-Type": "application/json",
343
- "x-api-key": apiKey
587
+ ...authHeaders
344
588
  },
345
589
  body: JSON.stringify({
346
590
  fileMetadata: {
@@ -461,6 +705,9 @@ async function createPresentation(options) {
461
705
  if (options.teamId) {
462
706
  body.teamId = options.teamId;
463
707
  }
708
+ if (options.theme) {
709
+ body.theme = options.theme;
710
+ }
464
711
  return streamRequest("/api/slides/skills/create-presentation", body);
465
712
  }
466
713
  async function listPresentations(teamId, limit = 20) {
@@ -481,20 +728,20 @@ async function deletePresentation(slugOrId) {
481
728
  await request(`/api/cli/presentation/${slugOrId}`, { method: "DELETE" });
482
729
  }
483
730
  async function exportPresentation(presentationId, options = {}) {
484
- const apiKey = getApiKey();
485
731
  const apiUrl = getApiUrl();
486
- if (!apiKey) {
732
+ if (!hasAuth()) {
487
733
  throw new ApiError(
488
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
734
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
489
735
  401,
490
736
  2
491
737
  );
492
738
  }
739
+ const authHeaders = await getAuthHeaders();
493
740
  const response = await fetch(`${apiUrl}/api/presentations/export`, {
494
741
  method: "POST",
495
742
  headers: {
496
743
  "Content-Type": "application/json",
497
- "x-api-key": apiKey
744
+ ...authHeaders
498
745
  },
499
746
  body: JSON.stringify({
500
747
  presentationId,
@@ -515,15 +762,15 @@ async function exportPresentation(presentationId, options = {}) {
515
762
  return response.arrayBuffer();
516
763
  }
517
764
  async function importPresentation(fileBuffer, fileName, options = {}) {
518
- const apiKey = getApiKey();
519
765
  const apiUrl = getApiUrl();
520
- if (!apiKey) {
766
+ if (!hasAuth()) {
521
767
  throw new ApiError(
522
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
768
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
523
769
  401,
524
770
  2
525
771
  );
526
772
  }
773
+ const authHeaders = await getAuthHeaders();
527
774
  const formData = new FormData();
528
775
  const blob = new Blob([fileBuffer], { type: "application/zip" });
529
776
  formData.append("file", blob, fileName);
@@ -536,9 +783,7 @@ async function importPresentation(fileBuffer, fileName, options = {}) {
536
783
  );
537
784
  const response = await fetch(`${apiUrl}/api/presentations/import`, {
538
785
  method: "POST",
539
- headers: {
540
- "x-api-key": apiKey
541
- },
786
+ headers: authHeaders,
542
787
  body: formData
543
788
  });
544
789
  const result = await response.json();
@@ -570,22 +815,22 @@ async function getFeatureFlags() {
570
815
  return request("/api/cli/features");
571
816
  }
572
817
  async function generateBlog(presentationId, documentCreatedAt, options = {}) {
573
- const apiKey = getApiKey();
574
818
  const apiUrl = getApiUrl();
575
- if (!apiKey) {
819
+ if (!hasAuth()) {
576
820
  throw new ApiError(
577
- "API key not configured. Run 'mindframes config init' or set CC_MINDFRAMES_API_KEY environment variable.",
821
+ "Not authenticated. Run 'mindframes login' or set CC_MINDFRAMES_API_KEY environment variable.",
578
822
  401,
579
823
  2
580
824
  );
581
825
  }
826
+ const authHeaders = await getAuthHeaders();
582
827
  const response = await fetch(
583
828
  `${apiUrl}/api/presentations/${presentationId}/generate-blog`,
584
829
  {
585
830
  method: "POST",
586
831
  headers: {
587
832
  "Content-Type": "application/json",
588
- "x-api-key": apiKey
833
+ ...authHeaders
589
834
  },
590
835
  body: JSON.stringify({
591
836
  documentCreatedAt,
@@ -644,15 +889,25 @@ async function validateGeneration(mode, slideCount, teamId) {
644
889
  }
645
890
  return limits;
646
891
  }
892
+ var ApiError;
893
+ var init_api = __esm({
894
+ "src/lib/api.ts"() {
895
+ "use strict";
896
+ init_config();
897
+ init_auth();
898
+ ApiError = class extends Error {
899
+ constructor(message, statusCode, exitCode = 1) {
900
+ super(message);
901
+ this.statusCode = statusCode;
902
+ this.exitCode = exitCode;
903
+ this.name = "ApiError";
904
+ }
905
+ };
906
+ }
907
+ });
647
908
 
648
909
  // src/lib/feature-cache.ts
649
910
  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
911
  function hashApiKey(key) {
657
912
  let hash = 0;
658
913
  for (let i = 0; i < key.length; i++) {
@@ -698,173 +953,410 @@ function invalidateCache() {
698
953
  function getCachePath() {
699
954
  return cache.path;
700
955
  }
956
+ var cache, FRESH_TTL, STALE_TTL;
957
+ var init_feature_cache = __esm({
958
+ "src/lib/feature-cache.ts"() {
959
+ "use strict";
960
+ init_api();
961
+ init_config();
962
+ cache = new Conf2({
963
+ projectName: "conceptcraft",
964
+ configName: "feature-cache"
965
+ });
966
+ FRESH_TTL = 60 * 60 * 1e3;
967
+ STALE_TTL = 24 * 60 * 60 * 1e3;
968
+ }
969
+ });
701
970
 
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;
971
+ // src/commands/login.ts
972
+ var login_exports = {};
973
+ __export(login_exports, {
974
+ loginCommand: () => loginCommand,
975
+ runLoginFlow: () => runLoginFlow
976
+ });
977
+ import { Command } from "commander";
978
+ import chalk3 from "chalk";
979
+ import ora from "ora";
980
+ import http from "http";
981
+ import { randomBytes, createHash } from "crypto";
982
+ import open from "open";
983
+ function generateCodeVerifier() {
984
+ return randomBytes(32).toString("base64url");
707
985
  }
708
- function success(message, format = "human") {
709
- if (format === "quiet") return;
710
- if (format === "json") return;
711
- console.log(chalk2.green("\u2713"), message);
986
+ function generateCodeChallenge(verifier) {
987
+ return createHash("sha256").update(verifier).digest("base64url");
712
988
  }
713
- function error(message, format = "human") {
714
- if (format === "quiet") return;
715
- if (format === "json") {
716
- console.error(JSON.stringify({ error: message }));
717
- return;
989
+ function generateState() {
990
+ return randomBytes(16).toString("hex");
991
+ }
992
+ async function findAvailablePort(start, end) {
993
+ for (let port = start; port <= end; port++) {
994
+ try {
995
+ await new Promise((resolve4, reject) => {
996
+ const server = http.createServer();
997
+ server.listen(port, () => {
998
+ server.close(() => resolve4());
999
+ });
1000
+ server.on("error", reject);
1001
+ });
1002
+ return port;
1003
+ } catch {
1004
+ continue;
1005
+ }
718
1006
  }
719
- console.error(chalk2.red("\u2717"), message);
1007
+ throw new Error(`No available port found between ${start} and ${end}`);
720
1008
  }
721
- function warn(message, format = "human") {
722
- if (format === "quiet") return;
723
- if (format === "json") return;
724
- console.warn(chalk2.yellow("\u26A0"), message);
1009
+ async function getAuthServerMetadata(apiUrl) {
1010
+ const response = await fetch(`${apiUrl}/.well-known/oauth-authorization-server`);
1011
+ if (!response.ok) {
1012
+ throw new Error(`Failed to fetch OAuth metadata: ${response.status}`);
1013
+ }
1014
+ return response.json();
1015
+ }
1016
+ async function registerClient(registrationEndpoint, redirectUri) {
1017
+ const response = await fetch(registrationEndpoint, {
1018
+ method: "POST",
1019
+ headers: { "Content-Type": "application/json" },
1020
+ body: JSON.stringify({
1021
+ client_name: CLI_CLIENT_NAME,
1022
+ redirect_uris: [redirectUri],
1023
+ grant_types: ["authorization_code", "refresh_token"],
1024
+ response_types: ["code"],
1025
+ token_endpoint_auth_method: "none",
1026
+ // Public client with PKCE
1027
+ scope: "presentations:read presentations:write"
1028
+ })
1029
+ });
1030
+ if (!response.ok) {
1031
+ const err = await response.text();
1032
+ throw new Error(`Client registration failed: ${err}`);
1033
+ }
1034
+ return response.json();
1035
+ }
1036
+ async function exchangeCodeForTokens(tokenEndpoint, code, codeVerifier, redirectUri, clientId) {
1037
+ const params = new URLSearchParams({
1038
+ grant_type: "authorization_code",
1039
+ code,
1040
+ redirect_uri: redirectUri,
1041
+ client_id: clientId,
1042
+ code_verifier: codeVerifier
1043
+ });
1044
+ const response = await fetch(tokenEndpoint, {
1045
+ method: "POST",
1046
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1047
+ body: params.toString()
1048
+ });
1049
+ if (!response.ok) {
1050
+ const err = await response.text();
1051
+ throw new Error(`Token exchange failed: ${err}`);
1052
+ }
1053
+ return response.json();
1054
+ }
1055
+ function startCallbackServer(port, expectedState) {
1056
+ return new Promise((resolve4, reject) => {
1057
+ const server = http.createServer((req, res) => {
1058
+ const url = new URL(req.url || "", `http://localhost:${port}`);
1059
+ if (url.pathname !== "/callback") {
1060
+ res.writeHead(404);
1061
+ res.end("Not found");
1062
+ return;
1063
+ }
1064
+ const code = url.searchParams.get("code");
1065
+ const state = url.searchParams.get("state");
1066
+ const errorParam = url.searchParams.get("error");
1067
+ const errorDescription = url.searchParams.get("error_description");
1068
+ res.writeHead(200, { "Content-Type": "text/html" });
1069
+ if (errorParam) {
1070
+ res.end(`
1071
+ <!DOCTYPE html>
1072
+ <html>
1073
+ <head><title>Login Failed</title></head>
1074
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
1075
+ <h1 style="color: #dc2626;">Login Failed</h1>
1076
+ <p>${errorDescription || errorParam}</p>
1077
+ <p>You can close this window.</p>
1078
+ </body>
1079
+ </html>
1080
+ `);
1081
+ server.close();
1082
+ reject(new Error(errorDescription || errorParam));
1083
+ return;
1084
+ }
1085
+ if (!code || !state) {
1086
+ res.end(`
1087
+ <!DOCTYPE html>
1088
+ <html>
1089
+ <head><title>Login Failed</title></head>
1090
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
1091
+ <h1 style="color: #dc2626;">Login Failed</h1>
1092
+ <p>Missing authorization code or state</p>
1093
+ <p>You can close this window.</p>
1094
+ </body>
1095
+ </html>
1096
+ `);
1097
+ server.close();
1098
+ reject(new Error("Missing authorization code or state"));
1099
+ return;
1100
+ }
1101
+ if (state !== expectedState) {
1102
+ res.end(`
1103
+ <!DOCTYPE html>
1104
+ <html>
1105
+ <head><title>Login Failed</title></head>
1106
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
1107
+ <h1 style="color: #dc2626;">Login Failed</h1>
1108
+ <p>State mismatch - possible CSRF attack</p>
1109
+ <p>You can close this window.</p>
1110
+ </body>
1111
+ </html>
1112
+ `);
1113
+ server.close();
1114
+ reject(new Error("State mismatch"));
1115
+ return;
1116
+ }
1117
+ res.end(`
1118
+ <!DOCTYPE html>
1119
+ <html>
1120
+ <head><title>Login Successful</title></head>
1121
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
1122
+ <h1 style="color: #16a34a;">Login Successful!</h1>
1123
+ <p>You can close this window and return to the terminal.</p>
1124
+ </body>
1125
+ </html>
1126
+ `);
1127
+ server.close();
1128
+ resolve4({ code, state });
1129
+ });
1130
+ server.listen(port);
1131
+ const cleanup = () => {
1132
+ server.close();
1133
+ reject(new Error("Login cancelled"));
1134
+ };
1135
+ process.once("SIGINT", cleanup);
1136
+ process.once("SIGTERM", cleanup);
1137
+ setTimeout(() => {
1138
+ process.off("SIGINT", cleanup);
1139
+ process.off("SIGTERM", cleanup);
1140
+ server.close();
1141
+ reject(new Error("Login timed out - no callback received within 5 minutes"));
1142
+ }, 5 * 60 * 1e3);
1143
+ });
725
1144
  }
726
- function info(message, format = "human") {
727
- if (format === "quiet") return;
728
- if (format === "json") return;
729
- console.log(chalk2.blue("\u2139"), message);
1145
+ async function fetchWhoami(apiUrl, accessToken) {
1146
+ const response = await fetch(`${apiUrl}/api/cli/whoami`, {
1147
+ headers: { Authorization: `Bearer ${accessToken}` }
1148
+ });
1149
+ if (!response.ok) {
1150
+ throw new Error(`Failed to fetch user info: ${response.status}`);
1151
+ }
1152
+ return response.json();
730
1153
  }
731
- function buildViewUrl(slug, language = "en") {
732
- if (!slug) return "N/A";
1154
+ async function runLoginFlow(options) {
733
1155
  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) => {
1156
+ let spinner = ora("Discovering OAuth server...").start();
1157
+ try {
1158
+ const metadata = await getAuthServerMetadata(apiUrl);
1159
+ spinner.succeed("Connecting to " + apiUrl);
1160
+ const port = await findAvailablePort(CALLBACK_PORT_START, CALLBACK_PORT_END);
1161
+ const redirectUri = `http://localhost:${port}/callback`;
1162
+ let clientId = getClientId();
1163
+ let clientSecret = getClientSecret();
1164
+ if (!clientId) {
1165
+ const client = await registerClient(metadata.registration_endpoint, redirectUri);
1166
+ clientId = client.client_id;
1167
+ clientSecret = client.client_secret;
1168
+ setOAuthClient(clientId, clientSecret);
1169
+ }
1170
+ const codeVerifier = generateCodeVerifier();
1171
+ const codeChallenge = generateCodeChallenge(codeVerifier);
1172
+ const state = generateState();
1173
+ const authParams = new URLSearchParams({
1174
+ client_id: clientId,
1175
+ redirect_uri: redirectUri,
1176
+ response_type: "code",
1177
+ scope: "presentations:read presentations:write",
1178
+ state,
1179
+ code_challenge: codeChallenge,
1180
+ code_challenge_method: "S256"
1181
+ });
1182
+ const authUrl = `${metadata.authorization_endpoint}?${authParams}`;
1183
+ if (options.browser) {
1184
+ info("Opening browser...");
1185
+ await open(authUrl);
1186
+ } else {
1187
+ console.log(chalk3.bold("Open this URL in your browser:"));
1188
+ console.log(chalk3.cyan(authUrl));
1189
+ }
1190
+ const callbackPromise = startCallbackServer(port, state);
1191
+ const { code } = await callbackPromise;
1192
+ spinner = ora("Completing login...").start();
1193
+ const tokens = await exchangeCodeForTokens(
1194
+ metadata.token_endpoint,
1195
+ code,
1196
+ codeVerifier,
1197
+ redirectUri,
1198
+ clientId
1199
+ );
1200
+ setOAuthTokens(tokens.access_token, tokens.refresh_token, tokens.expires_in);
1201
+ const whoami2 = await fetchWhoami(apiUrl, tokens.access_token);
1202
+ spinner.succeed("Logged in!");
1203
+ console.log();
1204
+ keyValue("Logged in as", whoami2.user.email);
1205
+ if (whoami2.currentTeam) {
1206
+ setDefaultTeamId(whoami2.currentTeam.id);
1207
+ keyValue("Team", `${whoami2.currentTeam.name} (${whoami2.currentTeam.planName})`);
1208
+ }
740
1209
  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 ", ", ");
1210
+ await fetchAndCache();
749
1211
  } catch {
750
- return "-";
751
1212
  }
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
- ]
1213
+ console.log();
1214
+ success("You're all set!");
1215
+ console.log();
1216
+ } catch (err) {
1217
+ spinner.fail("Login failed");
1218
+ error(err instanceof Error ? err.message : String(err));
1219
+ throw err;
1220
+ }
1221
+ }
1222
+ var CLI_CLIENT_NAME, CALLBACK_PORT_START, CALLBACK_PORT_END, loginCommand;
1223
+ var init_login = __esm({
1224
+ "src/commands/login.ts"() {
1225
+ "use strict";
1226
+ init_config();
1227
+ init_output();
1228
+ init_feature_cache();
1229
+ CLI_CLIENT_NAME = "ConceptCraft CLI";
1230
+ CALLBACK_PORT_START = 8765;
1231
+ CALLBACK_PORT_END = 8775;
1232
+ loginCommand = new Command("login").description("Authenticate with ConceptCraft (opens browser)").option("--no-browser", "Print URL instead of opening browser").action(async (options) => {
1233
+ console.log();
1234
+ if (hasOAuthTokens()) {
1235
+ warn("You are already logged in.");
1236
+ info("Run 'conceptcraft logout' to log out first, or continue to re-authenticate.");
1237
+ console.log();
1238
+ }
1239
+ try {
1240
+ await runLoginFlow(options);
1241
+ } catch {
1242
+ process.exit(1);
1243
+ }
762
1244
  });
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
1245
  }
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
- ]);
1246
+ });
1247
+
1248
+ // src/index.ts
1249
+ import { Command as Command15 } from "commander";
1250
+ import chalk13 from "chalk";
1251
+
1252
+ // src/lib/brand.ts
1253
+ import path from "path";
1254
+ var BRANDS = {
1255
+ conceptcraft: {
1256
+ id: "conceptcraft",
1257
+ name: "conceptcraft",
1258
+ displayName: "ConceptCraft",
1259
+ description: "CLI tool for ConceptCraft presentation generation",
1260
+ commands: ["cc", "conceptcraft"],
1261
+ apiUrl: "https://conceptcraft.ai",
1262
+ docsUrl: "https://docs.conceptcraft.ai",
1263
+ packageName: "@conceptcraft/cli",
1264
+ configDir: ".conceptcraft"
1265
+ },
1266
+ mindframes: {
1267
+ id: "mindframes",
1268
+ name: "mindframes",
1269
+ displayName: "Mindframes",
1270
+ description: "CLI tool for Mindframes presentation generation",
1271
+ commands: ["mf", "mindframes"],
1272
+ apiUrl: "https://mindframes.app",
1273
+ docsUrl: "https://docs.mindframes.app",
1274
+ packageName: "@mindframes/cli",
1275
+ configDir: ".mindframes"
792
1276
  }
793
- return table.toString();
794
- }
795
- function formatPresentationIds(presentations) {
796
- return presentations.map((p) => p.slug || p.id).join("\n");
1277
+ };
1278
+ var COMMAND_TO_BRAND = {
1279
+ cc: "conceptcraft",
1280
+ conceptcraft: "conceptcraft",
1281
+ mf: "mindframes",
1282
+ mindframes: "mindframes"
1283
+ };
1284
+ function detectBrand() {
1285
+ const binaryPath = process.argv[1] || "";
1286
+ const binaryName = path.basename(binaryPath).replace(/\.(js|ts)$/, "");
1287
+ const brandId = COMMAND_TO_BRAND[binaryName];
1288
+ if (brandId) {
1289
+ return BRANDS[brandId];
1290
+ }
1291
+ for (const [cmd2, id] of Object.entries(COMMAND_TO_BRAND)) {
1292
+ if (binaryPath.includes(`/${cmd2}`) || binaryPath.includes(`\\${cmd2}`)) {
1293
+ return BRANDS[id];
1294
+ }
1295
+ }
1296
+ return BRANDS.mindframes;
797
1297
  }
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 "-";
1298
+ var brand = detectBrand();
1299
+
1300
+ // src/index.ts
1301
+ init_login();
1302
+
1303
+ // src/commands/logout.ts
1304
+ init_config();
1305
+ init_feature_cache();
1306
+ init_output();
1307
+ import { Command as Command2 } from "commander";
1308
+ import { confirm } from "@inquirer/prompts";
1309
+ var logoutCommand = new Command2("logout").description("Log out and clear authentication").option("--all", "Clear all config including API key").action(async (options) => {
1310
+ console.log();
1311
+ const hasTokens = hasOAuthTokens();
1312
+ const hasKey = hasApiKey();
1313
+ if (!hasTokens && !hasKey) {
1314
+ warn("You are not logged in.");
1315
+ console.log();
1316
+ return;
1317
+ }
1318
+ if (options.all) {
808
1319
  try {
809
- const u = new URL(url);
810
- return u.href;
1320
+ const confirmed = await confirm({
1321
+ message: "Clear all configuration including API key?",
1322
+ default: false
1323
+ });
1324
+ if (confirmed) {
1325
+ clearConfig();
1326
+ invalidateCache();
1327
+ success("All configuration cleared.");
1328
+ } else {
1329
+ info("Cancelled.");
1330
+ }
811
1331
  } catch {
812
- return url.startsWith("http") ? url : `https://${url}`;
1332
+ info("Cancelled.");
1333
+ }
1334
+ } else {
1335
+ clearOAuthTokens();
1336
+ invalidateCache();
1337
+ success("Logged out successfully.");
1338
+ if (hasKey) {
1339
+ info("Note: Your API key is still configured. Use --all to clear everything.");
813
1340
  }
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
1341
  }
845
- }
846
- function header(text) {
847
1342
  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
- }
1343
+ });
861
1344
 
862
1345
  // 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 () => {
1346
+ init_config();
1347
+ init_auth();
1348
+ init_api();
1349
+ init_feature_cache();
1350
+ init_output();
1351
+ import { Command as Command3 } from "commander";
1352
+ import chalk4 from "chalk";
1353
+ import { input, password, confirm as confirm2, select } from "@inquirer/prompts";
1354
+ import ora2 from "ora";
1355
+ var configCommand = new Command3("config").description("Manage CLI configuration").addCommand(
1356
+ new Command3("init").description("Initialize configuration interactively").action(async () => {
865
1357
  console.log();
866
- console.log(chalk3.bold("ConceptCraft CLI Configuration"));
867
- console.log(chalk3.gray("\u2500".repeat(35)));
1358
+ console.log(chalk4.bold("ConceptCraft CLI Configuration"));
1359
+ console.log(chalk4.gray("\u2500".repeat(35)));
868
1360
  console.log();
869
1361
  try {
870
1362
  const apiKey = await password({
@@ -881,7 +1373,7 @@ var configCommand = new Command("config").description("Manage CLI configuration"
881
1373
  }
882
1374
  });
883
1375
  setApiKey(apiKey.trim());
884
- const useCustomUrl = await confirm({
1376
+ const useCustomUrl = await confirm2({
885
1377
  message: "Use a custom API URL? (default: www.mindframes.app)",
886
1378
  default: false
887
1379
  });
@@ -901,7 +1393,7 @@ var configCommand = new Command("config").description("Manage CLI configuration"
901
1393
  setApiUrl(customUrl);
902
1394
  }
903
1395
  console.log();
904
- const spinner = ora("Verifying API key...").start();
1396
+ const spinner = ora2("Verifying API key...").start();
905
1397
  try {
906
1398
  const result = await whoami();
907
1399
  spinner.succeed("API key verified!");
@@ -953,18 +1445,18 @@ var configCommand = new Command("config").description("Manage CLI configuration"
953
1445
  }
954
1446
  })
955
1447
  ).addCommand(
956
- new Command("show").description("Show current configuration").option("--verify", "Verify API key and show team details").action(async (options) => {
1448
+ new Command3("show").description("Show current configuration").option("--verify", "Verify API key and show team details").action(async (options) => {
957
1449
  const config2 = getConfig();
958
1450
  header("Current Configuration");
959
1451
  console.log();
960
- keyValue("API Key", getMaskedApiKey() ?? chalk3.red("Not set"));
1452
+ keyValue("API Key", getMaskedApiKey() ?? chalk4.red("Not set"));
961
1453
  keyValue("API URL", config2.apiUrl);
962
- keyValue("Default Team ID", config2.defaultTeamId ?? chalk3.gray("Not set"));
1454
+ keyValue("Default Team ID", config2.defaultTeamId ?? chalk4.gray("Not set"));
963
1455
  console.log();
964
1456
  keyValue("Config file", getConfigPath());
965
1457
  if (options.verify && config2.apiKey) {
966
1458
  console.log();
967
- const spinner = ora("Verifying...").start();
1459
+ const spinner = ora2("Verifying...").start();
968
1460
  try {
969
1461
  const result = await whoami();
970
1462
  spinner.succeed("Verified");
@@ -984,7 +1476,7 @@ var configCommand = new Command("config").description("Manage CLI configuration"
984
1476
  console.log();
985
1477
  })
986
1478
  ).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) => {
1479
+ new Command3("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
1480
  switch (key) {
989
1481
  case "api-key":
990
1482
  if (!isValidApiKeyFormat(value)) {
@@ -1013,14 +1505,14 @@ var configCommand = new Command("config").description("Manage CLI configuration"
1013
1505
  break;
1014
1506
  default:
1015
1507
  error(`Unknown config key: ${key}`);
1016
- console.log(chalk3.gray("Valid keys: api-key, api-url, team-id"));
1508
+ console.log(chalk4.gray("Valid keys: api-key, api-url, team-id"));
1017
1509
  process.exit(6);
1018
1510
  }
1019
1511
  })
1020
1512
  ).addCommand(
1021
- new Command("clear").description("Clear all configuration").action(async () => {
1513
+ new Command3("clear").description("Clear all configuration").action(async () => {
1022
1514
  try {
1023
- const confirmed = await confirm({
1515
+ const confirmed = await confirm2({
1024
1516
  message: "Are you sure you want to clear all configuration?",
1025
1517
  default: false
1026
1518
  });
@@ -1036,8 +1528,8 @@ var configCommand = new Command("config").description("Manage CLI configuration"
1036
1528
  }
1037
1529
  })
1038
1530
  ).addCommand(
1039
- new Command("refresh").description("Refresh cached feature flags").action(async () => {
1040
- const spinner = ora("Refreshing feature flags...").start();
1531
+ new Command3("refresh").description("Refresh cached feature flags").action(async () => {
1532
+ const spinner = ora2("Refreshing feature flags...").start();
1041
1533
  try {
1042
1534
  await fetchAndCache();
1043
1535
  spinner.succeed("Feature flags refreshed");
@@ -1049,7 +1541,7 @@ var configCommand = new Command("config").description("Manage CLI configuration"
1049
1541
  }
1050
1542
  })
1051
1543
  ).addCommand(
1052
- new Command("path").description("Show configuration file path").option("--cache", "Show feature cache file path").action((options) => {
1544
+ new Command3("path").description("Show configuration file path").option("--cache", "Show feature cache file path").action((options) => {
1053
1545
  if (options.cache) {
1054
1546
  console.log(getCachePath());
1055
1547
  } else {
@@ -1059,13 +1551,16 @@ var configCommand = new Command("config").description("Manage CLI configuration"
1059
1551
  );
1060
1552
 
1061
1553
  // src/commands/create.ts
1062
- import { Command as Command2 } from "commander";
1063
- import chalk5 from "chalk";
1554
+ init_api();
1555
+ init_auth();
1556
+ import { Command as Command4 } from "commander";
1557
+ import chalk6 from "chalk";
1064
1558
 
1065
1559
  // src/lib/streaming.ts
1560
+ init_output();
1066
1561
  import { createParser } from "eventsource-parser";
1067
- import chalk4 from "chalk";
1068
- import ora2 from "ora";
1562
+ import chalk5 from "chalk";
1563
+ import ora3 from "ora";
1069
1564
  async function handleStream(response, callbacks, options = {}) {
1070
1565
  const reader = response.body?.getReader();
1071
1566
  if (!reader) {
@@ -1086,7 +1581,7 @@ async function handleStream(response, callbacks, options = {}) {
1086
1581
  const parsed = JSON.parse(event.data);
1087
1582
  const { type, data } = parsed;
1088
1583
  if (options.debug) {
1089
- console.log(chalk4.gray(`[SSE] ${type}:`), data);
1584
+ console.log(chalk5.gray(`[SSE] ${type}:`), data);
1090
1585
  }
1091
1586
  callbacks.onData?.(type, data);
1092
1587
  switch (type) {
@@ -1156,7 +1651,7 @@ async function handleStream(response, callbacks, options = {}) {
1156
1651
  }
1157
1652
  } catch (e) {
1158
1653
  if (options.debug) {
1159
- console.log(chalk4.gray("[SSE Raw]"), event.data);
1654
+ console.log(chalk5.gray("[SSE Raw]"), event.data);
1160
1655
  }
1161
1656
  }
1162
1657
  }
@@ -1181,9 +1676,9 @@ async function streamWithProgress(response, topic, options) {
1181
1676
  const useTTY = isTTY() && !options.noStream && !options.quiet && !options.json;
1182
1677
  if (!options.json) {
1183
1678
  console.log();
1184
- console.log(chalk4.bold(`Creating presentation: "${topic}"`));
1679
+ console.log(chalk5.bold(`Creating presentation: "${topic}"`));
1185
1680
  console.log(
1186
- chalk4.gray(
1681
+ chalk5.gray(
1187
1682
  `Quality: ${options.mode} | Tone: ${options.tone ?? "professional"} | Slides: ${options.slideCount} | Language: ${options.language}`
1188
1683
  )
1189
1684
  );
@@ -1234,10 +1729,10 @@ async function streamWithProgress(response, topic, options) {
1234
1729
  const tokens = formatTokens(currentTokens);
1235
1730
  const elapsed = formatTime(getElapsed()).padStart(5, " ");
1236
1731
  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")}`;
1732
+ return `${bar} ${chalk5.bold(pct)}${chalk5.gray("%")} ${chalk5.cyan(phase)} ${chalk5.gray(slides)} ${chalk5.yellow(tokens)} ${chalk5.gray(elapsed)} ${chalk5.green(remaining + " left")}`;
1238
1733
  };
1239
1734
  if (useTTY) {
1240
- spinner = ora2({
1735
+ spinner = ora3({
1241
1736
  text: buildProgressLine(),
1242
1737
  spinner: "dots"
1243
1738
  }).start();
@@ -1273,9 +1768,9 @@ async function streamWithProgress(response, topic, options) {
1273
1768
  onError: (error2) => {
1274
1769
  if (timer) clearInterval(timer);
1275
1770
  if (spinner) {
1276
- spinner.fail(chalk4.red(`Error: ${error2}`));
1771
+ spinner.fail(chalk5.red(`Error: ${error2}`));
1277
1772
  } else if (!options.json) {
1278
- console.error(chalk4.red(`Error: ${error2}`));
1773
+ console.error(chalk5.red(`Error: ${error2}`));
1279
1774
  }
1280
1775
  },
1281
1776
  onComplete: (data) => {
@@ -1324,6 +1819,7 @@ async function streamTextContent(response, options = {}) {
1324
1819
  }
1325
1820
 
1326
1821
  // src/commands/create.ts
1822
+ init_output();
1327
1823
  import { readFileSync as readFileSync2, existsSync } from "fs";
1328
1824
  import { resolve } from "path";
1329
1825
  var VALID_GOALS = [
@@ -1334,7 +1830,7 @@ var VALID_GOALS = [
1334
1830
  "entertain",
1335
1831
  "report"
1336
1832
  ];
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(
1833
+ var createCommand = new Command4("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
1834
  "-m, --mode <mode>",
1339
1835
  "Generation quality mode (best, balanced, fast, ultrafast, instant)",
1340
1836
  "balanced"
@@ -1375,67 +1871,83 @@ var createCommand = new Command2("create").description("Create a new presentatio
1375
1871
  "--thinking-depth <depth>",
1376
1872
  "AI thinking depth (quick, moderate, deep, profound)",
1377
1873
  "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(
1874
+ ).option(
1875
+ "--theme <preset>",
1876
+ "Color theme preset (blue, violet, rose, orange, green)"
1877
+ ).option("--primary-color <hex>", "Primary color in hex (e.g., #0066CC)").option("--secondary-color <hex>", "Secondary color in hex").option("--accent-color <hex>", "Accent color in hex").option("--background-color <hex>", "Background color in hex").option("--foreground-color <hex>", "Foreground/text color in hex").option(
1878
+ "--decorations <style>",
1879
+ "Background decoration style (none, waves-bottom-left, waves-top-right, blob-corners, minimal)"
1880
+ ).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)").option("--open", "Open the presentation in browser after creation").addHelpText(
1379
1881
  "after",
1380
1882
  `
1381
- ${chalk5.bold("Recommended Usage:")}
1883
+ ${chalk6.bold("Recommended Usage:")}
1382
1884
  Always specify slides (-n), mode (-m), audience, and context for best results.
1383
1885
 
1384
- ${chalk5.bold("Examples:")}
1385
- ${chalk5.gray("# Content from PDF + style from image reference")}
1886
+ ${chalk6.bold("Examples:")}
1887
+ ${chalk6.gray("# Content from PDF + style from image reference")}
1386
1888
  $ conceptcraft create "Quarterly Report" \\
1387
1889
  --file ./report.pdf \\
1388
1890
  --reference-url https://example.com/style-template.png \\
1389
1891
  -n 12 -m best --audience "Executive team"
1390
1892
 
1391
- ${chalk5.gray("# Upload content files (PDF, PPTX, DOCX)")}
1893
+ ${chalk6.gray("# Upload content files (PDF, PPTX, DOCX)")}
1392
1894
  $ conceptcraft create "Product Demo" \\
1393
1895
  --file ./existing-deck.pptx --file ./specs.pdf \\
1394
1896
  --goal persuade --audience "Enterprise buyers"
1395
1897
 
1396
- ${chalk5.gray("# Inline context with custom styling")}
1898
+ ${chalk6.gray("# Inline context with custom styling")}
1397
1899
  $ conceptcraft create "Q4 Business Review" \\
1398
1900
  -n 12 -m best --goal inform \\
1399
1901
  --context "Revenue: $50M (+25% YoY), EBITDA: $8M" \\
1400
1902
  --reference-url https://example.com/brand-style.jpg
1401
1903
 
1402
- ${chalk5.gray("# Research presentation from URLs")}
1904
+ ${chalk6.gray("# Research presentation from URLs")}
1403
1905
  $ conceptcraft create "AI Industry Trends" \\
1404
1906
  -n 10 -m best -t educational \\
1405
1907
  --sources https://example.com/ai-report.pdf
1406
1908
 
1407
- ${chalk5.gray("# Pipe content from another command")}
1909
+ ${chalk6.gray("# Pipe content from another command")}
1408
1910
  $ cat meeting-notes.md | conceptcraft create "Meeting Summary" \\
1409
1911
  -n 6 -m balanced --goal inform
1410
1912
 
1411
- ${chalk5.bold("Content Options (what to include in slides):")}
1913
+ ${chalk6.bold("Content Options (what to include in slides):")}
1412
1914
  -f, --file <paths...> Upload files for content extraction (PDF, PPTX, DOCX)
1413
1915
  -c, --context <text> Inline text (key facts, data points)
1414
1916
  --context-file <path> Read content from file (markdown, text, JSON)
1415
1917
  --sources <urls...> URLs to scrape for content
1416
1918
  --stdin Pipe content from another command
1417
1919
 
1418
- ${chalk5.bold("Styling Options (how slides look):")}
1419
- --reference-url <url> ${chalk5.yellow("Image URL to guide visual style")} (colors, layout, feel)
1920
+ ${chalk6.bold("Styling Options (how slides look):")}
1921
+ --reference-url <url> ${chalk6.yellow("Image URL to guide visual style")} (colors, layout, feel)
1420
1922
  --styling <mode> freeform, brand-only, style-only, no-styling
1421
1923
  -b, --brand <id> Apply saved brand settings
1422
1924
 
1423
- ${chalk5.gray("NOTE: --file uploads provide CONTENT (data, text to include)")}
1424
- ${chalk5.gray(" --reference-url provides STYLE (visual design inspiration)")}
1925
+ ${chalk6.gray("NOTE: --file uploads provide CONTENT (data, text to include)")}
1926
+ ${chalk6.gray(" --reference-url provides STYLE (visual design inspiration)")}
1425
1927
 
1426
- ${chalk5.bold("Goal Options:")}
1928
+ ${chalk6.bold("Goal Options:")}
1427
1929
  -g, --goal <type> inform, persuade, train, learn, entertain, report
1428
1930
  --custom-goal <text> Custom goal description
1429
1931
 
1430
- ${chalk5.bold("Mode Reference:")}
1932
+ ${chalk6.bold("Theme Options:")}
1933
+ --theme <preset> Preset color scheme (blue, violet, rose, orange, green)
1934
+ --primary-color <hex> Primary brand color (e.g., #0066CC)
1935
+ --secondary-color <hex> Secondary color for accents
1936
+ --decorations <style> Background style (none, waves-bottom-left, waves-top-right, blob-corners, minimal)
1937
+
1938
+ ${chalk6.gray("Example: Apply corporate colors")}
1939
+ $ conceptcraft create "Brand Deck" \\
1940
+ --theme blue --primary-color "#1E40AF" --decorations waves-bottom-left
1941
+
1942
+ ${chalk6.bold("Mode Reference:")}
1431
1943
  best Highest quality, thorough research (recommended for important decks)
1432
1944
  balanced Good quality with reasonable speed (default)
1433
1945
  fast Quick generation, less refinement
1434
1946
  ultrafast Very quick, minimal processing
1435
- instant Fastest, basic output
1947
+ instant Fastest, basic output (theme options work best with instant mode)
1436
1948
  `
1437
1949
  ).action(async (topic, options) => {
1438
- requireAuth();
1950
+ await requireAuth();
1439
1951
  const slideCount = parseInt(options.slides, 10);
1440
1952
  if (isNaN(slideCount) || slideCount < 1 || slideCount > 20) {
1441
1953
  error("Slide count must be between 1 and 20");
@@ -1510,15 +2022,68 @@ ${chalk5.bold("Mode Reference:")}
1510
2022
  );
1511
2023
  process.exit(6);
1512
2024
  }
2025
+ const validThemePresets = ["blue", "violet", "rose", "orange", "green"];
2026
+ if (options.theme && !validThemePresets.includes(options.theme)) {
2027
+ error(
2028
+ `Invalid theme: ${options.theme}. Valid themes: ${validThemePresets.join(", ")}`
2029
+ );
2030
+ process.exit(6);
2031
+ }
2032
+ const validDecorations = [
2033
+ "none",
2034
+ "waves-bottom-left",
2035
+ "waves-top-right",
2036
+ "blob-corners",
2037
+ "minimal"
2038
+ ];
2039
+ if (options.decorations && !validDecorations.includes(options.decorations)) {
2040
+ error(
2041
+ `Invalid decorations: ${options.decorations}. Valid styles: ${validDecorations.join(", ")}`
2042
+ );
2043
+ process.exit(6);
2044
+ }
2045
+ const hexColorRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
2046
+ const colorOptions = [
2047
+ { name: "primary-color", value: options.primaryColor },
2048
+ { name: "secondary-color", value: options.secondaryColor },
2049
+ { name: "accent-color", value: options.accentColor },
2050
+ { name: "background-color", value: options.backgroundColor },
2051
+ { name: "foreground-color", value: options.foregroundColor }
2052
+ ];
2053
+ for (const { name, value } of colorOptions) {
2054
+ if (value && !hexColorRegex.test(value)) {
2055
+ error(`Invalid ${name}: ${value}. Must be a hex color (e.g., #0066CC or #06C)`);
2056
+ process.exit(6);
2057
+ }
2058
+ }
2059
+ let theme;
2060
+ const hasCustomColors = options.primaryColor || options.secondaryColor || options.accentColor || options.backgroundColor || options.foregroundColor;
2061
+ if (options.theme || hasCustomColors || options.decorations) {
2062
+ theme = {};
2063
+ if (options.theme) {
2064
+ theme.preset = options.theme;
2065
+ }
2066
+ if (hasCustomColors) {
2067
+ theme.custom = {};
2068
+ if (options.primaryColor) theme.custom.primary = options.primaryColor;
2069
+ if (options.secondaryColor) theme.custom.secondary = options.secondaryColor;
2070
+ if (options.accentColor) theme.custom.accent = options.accentColor;
2071
+ if (options.backgroundColor) theme.custom.background = options.backgroundColor;
2072
+ if (options.foregroundColor) theme.custom.foreground = options.foregroundColor;
2073
+ }
2074
+ if (options.decorations) {
2075
+ theme.decorations = options.decorations;
2076
+ }
2077
+ }
1513
2078
  try {
1514
2079
  if (options.output !== "json" && options.output !== "quiet") {
1515
- process.stdout.write(chalk5.gray("Checking generation limits... "));
2080
+ process.stdout.write(chalk6.gray("Checking generation limits... "));
1516
2081
  }
1517
2082
  const limits = await validateGeneration(options.mode, slideCount, options.teamId);
1518
2083
  if (options.output !== "json" && options.output !== "quiet") {
1519
- console.log(chalk5.green("\u2713"));
2084
+ console.log(chalk6.green("\u2713"));
1520
2085
  console.log(
1521
- chalk5.gray(
2086
+ chalk6.gray(
1522
2087
  ` Plan: ${limits.planName} | ${options.mode}: ${limits.remaining[options.mode]}/${limits.limits[options.mode]} remaining`
1523
2088
  )
1524
2089
  );
@@ -1532,7 +2097,7 @@ ${chalk5.bold("Mode Reference:")}
1532
2097
  })
1533
2098
  );
1534
2099
  } else {
1535
- console.log(chalk5.red("\u2717"));
2100
+ console.log(chalk6.red("\u2717"));
1536
2101
  error(err instanceof Error ? err.message : String(err));
1537
2102
  }
1538
2103
  process.exit(err.exitCode ?? 1);
@@ -1547,7 +2112,7 @@ ${chalk5.bold("Mode Reference:")}
1547
2112
  }
1548
2113
  }
1549
2114
  if (options.output !== "json" && options.output !== "quiet") {
1550
- console.log(chalk5.gray(`
2115
+ console.log(chalk6.gray(`
1551
2116
  Uploading ${options.file.length} file(s)...`));
1552
2117
  }
1553
2118
  try {
@@ -1556,13 +2121,13 @@ Uploading ${options.file.length} file(s)...`));
1556
2121
  (completed, total, fileName) => {
1557
2122
  if (options.output !== "json" && options.output !== "quiet" && fileName) {
1558
2123
  process.stdout.write(
1559
- `\r ${chalk5.cyan("\u2B06")} Uploading: ${fileName} (${completed + 1}/${total})`
2124
+ `\r ${chalk6.cyan("\u2B06")} Uploading: ${fileName} (${completed + 1}/${total})`
1560
2125
  );
1561
2126
  }
1562
2127
  }
1563
2128
  );
1564
2129
  if (options.output !== "json" && options.output !== "quiet") {
1565
- console.log(`\r ${chalk5.green("\u2713")} Uploaded ${uploadedFiles.length} file(s) `);
2130
+ console.log(`\r ${chalk6.green("\u2713")} Uploaded ${uploadedFiles.length} file(s) `);
1566
2131
  }
1567
2132
  } catch (err) {
1568
2133
  error(
@@ -1601,16 +2166,16 @@ Uploading ${options.file.length} file(s)...`));
1601
2166
  if (!hasContext) {
1602
2167
  error("Context is required to create a presentation.");
1603
2168
  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"));
2169
+ console.log(chalk6.gray("Provide context using one of these methods:"));
2170
+ console.log(chalk6.gray(" -f, --file <paths...> Upload files (PDF, PPTX, images)"));
2171
+ console.log(chalk6.gray(" -c, --context <text> Direct text context"));
2172
+ console.log(chalk6.gray(" --context-file <path> Read from a file"));
2173
+ console.log(chalk6.gray(" --sources <urls...> URLs to scrape"));
2174
+ console.log(chalk6.gray(" cat file | conceptcraft Pipe content"));
1610
2175
  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%"'));
2176
+ console.log(chalk6.gray("Example:"));
2177
+ console.log(chalk6.cyan(' conceptcraft create "Q4 Report" --file ./report.pdf'));
2178
+ console.log(chalk6.cyan(' conceptcraft create "Q4 Report" --context "Revenue: $10M, Growth: 25%"'));
1614
2179
  process.exit(6);
1615
2180
  }
1616
2181
  try {
@@ -1633,7 +2198,8 @@ Uploading ${options.file.length} file(s)...`));
1633
2198
  uploadedFiles: uploadedFiles.length > 0 ? uploadedFiles : void 0,
1634
2199
  goal: options.goal,
1635
2200
  customGoal: options.customGoal,
1636
- teamId: options.teamId
2201
+ teamId: options.teamId,
2202
+ theme
1637
2203
  });
1638
2204
  const result = await streamWithProgress(response, topic, {
1639
2205
  slideCount,
@@ -1684,7 +2250,11 @@ Uploading ${options.file.length} file(s)...`));
1684
2250
  console.log();
1685
2251
  const viewUrl = buildViewUrl(result.slug, options.language);
1686
2252
  if (viewUrl !== "N/A") {
1687
- console.log(chalk5.bold(" Open: ") + chalk5.cyan.underline(viewUrl));
2253
+ console.log(chalk6.bold(" Open: ") + chalk6.cyan.underline(viewUrl));
2254
+ if (options.open) {
2255
+ const open2 = await import("open");
2256
+ await open2.default(viewUrl);
2257
+ }
1688
2258
  }
1689
2259
  console.log();
1690
2260
  }
@@ -1731,13 +2301,17 @@ function isUrl(str) {
1731
2301
  }
1732
2302
 
1733
2303
  // 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(
2304
+ init_api();
2305
+ init_auth();
2306
+ init_config();
2307
+ init_output();
2308
+ import { Command as Command5 } from "commander";
2309
+ var listCommand = new Command5("list").description("List presentations").option("-n, --limit <count>", "Number of results to return", "20").option(
1736
2310
  "-f, --format <format>",
1737
2311
  "Output format (table, json, ids)",
1738
2312
  "table"
1739
2313
  ).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();
2314
+ await requireAuth();
1741
2315
  const limit = parseInt(options.limit, 10);
1742
2316
  if (isNaN(limit) || limit < 1) {
1743
2317
  error("Invalid limit value");
@@ -1794,14 +2368,17 @@ var listCommand = new Command3("list").description("List presentations").option(
1794
2368
  });
1795
2369
 
1796
2370
  // 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(
2371
+ init_api();
2372
+ init_auth();
2373
+ init_output();
2374
+ import { Command as Command6 } from "commander";
2375
+ import chalk7 from "chalk";
2376
+ var getCommand = new Command6("get").description("Get presentation details").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option(
1800
2377
  "-f, --format <format>",
1801
2378
  "Output format (summary, full, json)",
1802
2379
  "summary"
1803
2380
  ).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();
2381
+ await requireAuth();
1805
2382
  try {
1806
2383
  const presentation = await getPresentation(slug);
1807
2384
  if (options.format === "json") {
@@ -1813,7 +2390,7 @@ var getCommand = new Command4("get").description("Get presentation details").arg
1813
2390
  console.log();
1814
2391
  keyValue("Slug", presentation.slug);
1815
2392
  keyValue("Title", presentation.title || "Untitled");
1816
- keyValue("Description", presentation.description || chalk6.gray("None"));
2393
+ keyValue("Description", presentation.description || chalk7.gray("None"));
1817
2394
  keyValue("Slides", String(presentation.numberOfSlides || "-"));
1818
2395
  keyValue("Mode", presentation.mode || "-");
1819
2396
  keyValue("Created", formatDate(presentation.createdAt));
@@ -1822,11 +2399,11 @@ var getCommand = new Command4("get").description("Get presentation details").arg
1822
2399
  }
1823
2400
  console.log();
1824
2401
  if (viewUrl !== "N/A") {
1825
- console.log(chalk6.bold(" Open: ") + chalk6.cyan.underline(viewUrl));
2402
+ console.log(chalk7.bold(" Open: ") + chalk7.cyan.underline(viewUrl));
1826
2403
  }
1827
2404
  console.log();
1828
2405
  if (options.format === "full" && options.include?.includes("slides")) {
1829
- console.log(chalk6.gray("(Full slide content available in JSON format)"));
2406
+ console.log(chalk7.gray("(Full slide content available in JSON format)"));
1830
2407
  console.log();
1831
2408
  }
1832
2409
  } catch (err) {
@@ -1836,13 +2413,16 @@ var getCommand = new Command4("get").description("Get presentation details").arg
1836
2413
  });
1837
2414
 
1838
2415
  // 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();
2416
+ init_api();
2417
+ init_auth();
2418
+ init_output();
2419
+ import { Command as Command7 } from "commander";
2420
+ import { confirm as confirm3 } from "@inquirer/prompts";
2421
+ var deleteCommand = new Command7("delete").description("Delete a presentation").argument("<slug>", "Presentation slug (e.g., my-presentation-v1-abc123)").option("-f, --force", "Skip confirmation prompt").option("-q, --quiet", "Suppress output").action(async (slug, options) => {
2422
+ await requireAuth();
1843
2423
  if (!options.force) {
1844
2424
  try {
1845
- const confirmed = await confirm2({
2425
+ const confirmed = await confirm3({
1846
2426
  message: `Are you sure you want to delete presentation "${slug}"?`,
1847
2427
  default: false
1848
2428
  });
@@ -1871,13 +2451,16 @@ var deleteCommand = new Command5("delete").description("Delete a presentation").
1871
2451
  });
1872
2452
 
1873
2453
  // src/commands/export.ts
1874
- import { Command as Command6 } from "commander";
2454
+ init_api();
2455
+ init_auth();
2456
+ init_output();
2457
+ import { Command as Command8 } from "commander";
1875
2458
  import { writeFile } from "fs/promises";
1876
2459
  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();
2460
+ import ora4 from "ora";
2461
+ var exportCommand = new Command8("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) => {
2462
+ await requireAuth();
2463
+ const spinner = ora4("Fetching presentation...").start();
1881
2464
  try {
1882
2465
  const presentation = await getPresentation(slug);
1883
2466
  const title = presentation.title || slug;
@@ -1912,14 +2495,18 @@ function formatBytes(bytes) {
1912
2495
  }
1913
2496
 
1914
2497
  // src/commands/import.ts
1915
- import { Command as Command7 } from "commander";
2498
+ init_api();
2499
+ init_auth();
2500
+ init_output();
2501
+ init_config();
2502
+ import { Command as Command9 } from "commander";
1916
2503
  import { readFile } from "fs/promises";
1917
2504
  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");
2505
+ import ora5 from "ora";
2506
+ var cmd = new Command9("import").description("Import a presentation from ZIP (admin only)").argument("<file>", "Path to ZIP file");
1920
2507
  cmd._hidden = true;
1921
2508
  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();
2509
+ await requireAuth();
1923
2510
  try {
1924
2511
  const me = await whoami();
1925
2512
  if (me.user.role !== "admin") {
@@ -1936,7 +2523,7 @@ var importCommand = cmd.option("--dry-run", "Validate without importing").option
1936
2523
  error("File must be a ZIP archive");
1937
2524
  process.exit(6);
1938
2525
  }
1939
- const spinner = ora4("Reading file...").start();
2526
+ const spinner = ora5("Reading file...").start();
1940
2527
  try {
1941
2528
  const fileBuffer = await readFile(filePath);
1942
2529
  if (options.dryRun) {
@@ -2001,12 +2588,16 @@ var importCommand = cmd.option("--dry-run", "Validate without importing").option
2001
2588
  });
2002
2589
 
2003
2590
  // 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();
2591
+ init_api();
2592
+ init_auth();
2593
+ init_config();
2594
+ init_output();
2595
+ import { Command as Command10 } from "commander";
2596
+ import chalk8 from "chalk";
2597
+ import ora6 from "ora";
2598
+ var brandingCommand = new Command10("branding").description("Manage brand profiles").addCommand(
2599
+ new Command10("list").description("List brand profiles").option("-f, --format <format>", "Output format (table, json)", "table").action(async (options) => {
2600
+ await requireAuth();
2010
2601
  try {
2011
2602
  const result = await listBrandings();
2012
2603
  if (result.brandings.length === 0) {
@@ -2016,7 +2607,7 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2016
2607
  info("No brand profiles found");
2017
2608
  console.log();
2018
2609
  console.log(
2019
- chalk7.gray(
2610
+ chalk8.gray(
2020
2611
  "Create one with: conceptcraft branding extract <url>"
2021
2612
  )
2022
2613
  );
@@ -2034,8 +2625,8 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2034
2625
  }
2035
2626
  })
2036
2627
  ).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();
2628
+ new Command10("get").description("Get brand profile details").argument("<id>", "Brand profile ID").option("-f, --format <format>", "Output format (summary, json)", "summary").action(async (id, options) => {
2629
+ await requireAuth();
2039
2630
  try {
2040
2631
  const brand2 = await getBranding(id);
2041
2632
  if (options.format === "json") {
@@ -2045,36 +2636,36 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2045
2636
  console.log();
2046
2637
  keyValue("ID", brand2.id);
2047
2638
  keyValue("Name", brand2.name);
2048
- keyValue("Source URL", brand2.sourceUrl ?? chalk7.gray("None"));
2049
- keyValue("Default", brand2.isDefault ? chalk7.green("Yes") : "No");
2639
+ keyValue("Source URL", brand2.sourceUrl ?? chalk8.gray("None"));
2640
+ keyValue("Default", brand2.isDefault ? chalk8.green("Yes") : "No");
2050
2641
  if (brand2.createdAt) {
2051
2642
  keyValue("Created", new Date(brand2.createdAt).toLocaleString());
2052
2643
  }
2053
2644
  console.log();
2054
2645
  if (brand2.primaryColor || brand2.colors.length > 0) {
2055
- console.log(chalk7.bold(" Colors"));
2646
+ console.log(chalk8.bold(" Colors"));
2056
2647
  if (brand2.primaryColor) {
2057
- const swatch = chalk7.bgHex(brand2.primaryColor)(" ");
2648
+ const swatch = chalk8.bgHex(brand2.primaryColor)(" ");
2058
2649
  console.log(` Primary: ${swatch} ${brand2.primaryColor}`);
2059
2650
  }
2060
2651
  if (brand2.colors.length > 0) {
2061
2652
  console.log(" Palette:");
2062
2653
  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})`) : "";
2654
+ const swatch = chalk8.bgHex(c.hex)(" ");
2655
+ const role = c.role ? chalk8.gray(` (${c.role})`) : "";
2065
2656
  console.log(` ${swatch} ${c.hex}${role}`);
2066
2657
  }
2067
2658
  }
2068
2659
  console.log();
2069
2660
  }
2070
2661
  if (brand2.logoUrl) {
2071
- console.log(chalk7.bold(" Logo"));
2662
+ console.log(chalk8.bold(" Logo"));
2072
2663
  console.log(` ${brand2.logoUrl}`);
2073
2664
  console.log();
2074
2665
  }
2075
2666
  const typo = brand2.typography;
2076
2667
  if (typo && Object.keys(typo).length > 0) {
2077
- console.log(chalk7.bold(" Typography"));
2668
+ console.log(chalk8.bold(" Typography"));
2078
2669
  if (typo.primary || typo.headings) {
2079
2670
  console.log(` Headings: ${typo.primary || typo.headings || "-"}`);
2080
2671
  }
@@ -2084,7 +2675,7 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2084
2675
  console.log();
2085
2676
  }
2086
2677
  if (brand2.confidence || brand2.extractionMethod) {
2087
- console.log(chalk7.bold(" Extraction"));
2678
+ console.log(chalk8.bold(" Extraction"));
2088
2679
  if (brand2.confidence) {
2089
2680
  console.log(` Confidence: ${Math.round(brand2.confidence * 100)}%`);
2090
2681
  }
@@ -2093,7 +2684,7 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2093
2684
  }
2094
2685
  console.log();
2095
2686
  }
2096
- console.log(chalk7.gray(" Use --format json for full details"));
2687
+ console.log(chalk8.gray(" Use --format json for full details"));
2097
2688
  console.log();
2098
2689
  }
2099
2690
  } catch (err) {
@@ -2102,8 +2693,8 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2102
2693
  }
2103
2694
  })
2104
2695
  ).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();
2696
+ new Command10("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) => {
2697
+ await requireAuth();
2107
2698
  try {
2108
2699
  new URL(url.startsWith("http") ? url : `https://${url}`);
2109
2700
  } catch {
@@ -2112,7 +2703,7 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2112
2703
  }
2113
2704
  const fullUrl = url.startsWith("http") ? url : `https://${url}`;
2114
2705
  const teamId = options.teamId ?? getDefaultTeamId();
2115
- const spinner = ora5({ text: `Extracting branding from ${fullUrl}...`, stream: process.stdout }).start();
2706
+ const spinner = ora6({ text: `Extracting branding from ${fullUrl}...`, stream: process.stdout }).start();
2116
2707
  try {
2117
2708
  const result = await extractBranding(fullUrl, teamId);
2118
2709
  const brand2 = await getBranding(result.id);
@@ -2125,28 +2716,28 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2125
2716
  keyValue("Source URL", brand2.sourceUrl ?? fullUrl);
2126
2717
  console.log();
2127
2718
  if (brand2.primaryColor || brand2.colors.length > 0) {
2128
- console.log(chalk7.bold(" Colors"));
2719
+ console.log(chalk8.bold(" Colors"));
2129
2720
  if (brand2.primaryColor) {
2130
- const swatch = chalk7.bgHex(brand2.primaryColor)(" ");
2721
+ const swatch = chalk8.bgHex(brand2.primaryColor)(" ");
2131
2722
  console.log(` Primary: ${swatch} ${brand2.primaryColor}`);
2132
2723
  }
2133
2724
  if (brand2.colors.length > 0) {
2134
2725
  console.log(" Palette:");
2135
2726
  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})`) : "";
2727
+ const swatch = chalk8.bgHex(c.hex)(" ");
2728
+ const role = c.role ? chalk8.gray(` (${c.role})`) : "";
2138
2729
  console.log(` ${swatch} ${c.hex}${role}`);
2139
2730
  }
2140
2731
  }
2141
2732
  console.log();
2142
2733
  }
2143
2734
  if (brand2.logoUrl) {
2144
- console.log(chalk7.bold(" Logo"));
2735
+ console.log(chalk8.bold(" Logo"));
2145
2736
  console.log(` ${brand2.logoUrl}`);
2146
2737
  console.log();
2147
2738
  }
2148
2739
  if (brand2.confidence) {
2149
- console.log(chalk7.bold(" Extraction"));
2740
+ console.log(chalk8.bold(" Extraction"));
2150
2741
  console.log(` Confidence: ${Math.round(brand2.confidence * 100)}%`);
2151
2742
  console.log();
2152
2743
  }
@@ -2159,14 +2750,14 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2159
2750
  }
2160
2751
  })
2161
2752
  ).addCommand(
2162
- new Command8("set-default").description("Set a brand profile as default").argument("<id>", "Brand profile ID").action(async (id) => {
2163
- requireAuth();
2753
+ new Command10("set-default").description("Set a brand profile as default").argument("<id>", "Brand profile ID").action(async (id) => {
2754
+ await requireAuth();
2164
2755
  info(
2165
2756
  `To set brand ${id} as default, use the web dashboard.`
2166
2757
  );
2167
2758
  console.log();
2168
2759
  console.log(
2169
- chalk7.gray(
2760
+ chalk8.gray(
2170
2761
  "API support for setting default brand is coming soon."
2171
2762
  )
2172
2763
  );
@@ -2174,11 +2765,15 @@ var brandingCommand = new Command8("branding").description("Manage brand profile
2174
2765
  );
2175
2766
 
2176
2767
  // src/commands/derive.ts
2177
- import { Command as Command9 } from "commander";
2178
- import chalk8 from "chalk";
2179
- import ora6 from "ora";
2768
+ init_api();
2769
+ init_auth();
2770
+ init_feature_cache();
2771
+ import { Command as Command11 } from "commander";
2772
+ import chalk9 from "chalk";
2773
+ import ora7 from "ora";
2774
+ init_output();
2180
2775
  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(
2776
+ return new Command11("blog").description("Generate a blog post from a presentation").argument("<slug>", "Presentation slug").option("--words <count>", "Target word count (50-2000)", "300").option(
2182
2777
  "--tone <tone>",
2183
2778
  "Writing tone (professional, casual, educational)",
2184
2779
  "professional"
@@ -2187,7 +2782,7 @@ function createBlogCommand() {
2187
2782
  "Output format (human, json, markdown)",
2188
2783
  "human"
2189
2784
  ).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();
2785
+ await requireAuth();
2191
2786
  const wordCount = parseInt(options.words, 10);
2192
2787
  if (isNaN(wordCount) || wordCount < 50 || wordCount > 2e3) {
2193
2788
  error("Word count must be between 50 and 2000");
@@ -2198,7 +2793,7 @@ function createBlogCommand() {
2198
2793
  error(`Invalid tone. Valid options: ${validTones.join(", ")}`);
2199
2794
  process.exit(6);
2200
2795
  }
2201
- const spinner = ora6("Fetching presentation...").start();
2796
+ const spinner = ora7("Fetching presentation...").start();
2202
2797
  try {
2203
2798
  const presentation = await getPresentation(presentationId);
2204
2799
  spinner.text = "Generating blog post...";
@@ -2234,8 +2829,8 @@ function createBlogCommand() {
2234
2829
  console.log(content);
2235
2830
  } else {
2236
2831
  console.log();
2237
- console.log(chalk8.bold(`Blog: ${presentation.title}`));
2238
- console.log(chalk8.gray("\u2500".repeat(40)));
2832
+ console.log(chalk9.bold(`Blog: ${presentation.title}`));
2833
+ console.log(chalk9.gray("\u2500".repeat(40)));
2239
2834
  console.log();
2240
2835
  console.log(content);
2241
2836
  console.log();
@@ -2248,35 +2843,35 @@ function createBlogCommand() {
2248
2843
  });
2249
2844
  }
2250
2845
  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();
2846
+ return new Command11("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) => {
2847
+ await requireAuth();
2253
2848
  info("Tweet generation coming soon.");
2254
2849
  });
2255
2850
  }
2256
2851
  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();
2852
+ return new Command11("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) => {
2853
+ await requireAuth();
2259
2854
  info("LinkedIn carousel generation coming soon.");
2260
2855
  });
2261
2856
  }
2262
2857
  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();
2858
+ return new Command11("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) => {
2859
+ await requireAuth();
2265
2860
  info("Question generation coming soon.");
2266
2861
  });
2267
2862
  }
2268
2863
  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(
2864
+ return new Command11("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
2865
  "-f, --format <format>",
2271
2866
  "Output format (human, json, markdown)",
2272
2867
  "human"
2273
2868
  ).action(async (presentationId, options) => {
2274
- requireAuth();
2869
+ await requireAuth();
2275
2870
  info("Cheat sheet generation coming soon.");
2276
2871
  });
2277
2872
  }
2278
2873
  function buildDeriveCommand() {
2279
- const derive = new Command9("derive").description("Generate derivative content from a presentation");
2874
+ const derive = new Command11("derive").description("Generate derivative content from a presentation");
2280
2875
  const flags = getCachedFlags();
2281
2876
  if (!flags) {
2282
2877
  return derive;
@@ -2300,33 +2895,39 @@ function buildDeriveCommand() {
2300
2895
  }
2301
2896
 
2302
2897
  // 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();
2898
+ init_auth();
2899
+ init_output();
2900
+ import { Command as Command12 } from "commander";
2901
+ import chalk10 from "chalk";
2902
+ var ideasCommand = new Command12("ideas").description("Generate presentation topic ideas").option("--context <text>", "Custom context for idea generation").option("--count <n>", "Number of ideas to generate", "5").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
2903
+ await requireAuth();
2307
2904
  info("Idea generation is available in the web dashboard.");
2308
2905
  console.log();
2309
2906
  console.log(
2310
- chalk9.gray("The CLI will support idea generation in a future release.")
2907
+ chalk10.gray("The CLI will support idea generation in a future release.")
2311
2908
  );
2312
2909
  console.log();
2313
2910
  console.log("For now, try these approaches:");
2314
2911
  console.log(
2315
- chalk9.gray(
2912
+ chalk10.gray(
2316
2913
  " 1. Visit the ConceptCraft dashboard and use the idea generator"
2317
2914
  )
2318
2915
  );
2319
2916
  console.log(
2320
- chalk9.gray(" 2. Create a presentation with a broad topic and refine it")
2917
+ chalk10.gray(" 2. Create a presentation with a broad topic and refine it")
2321
2918
  );
2322
2919
  console.log();
2323
2920
  });
2324
2921
 
2325
2922
  // 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();
2923
+ init_api();
2924
+ init_auth();
2925
+ init_feature_cache();
2926
+ init_output();
2927
+ import { Command as Command13 } from "commander";
2928
+ import chalk11 from "chalk";
2929
+ var whoamiCommand = new Command13("whoami").description("Show current user and team information").option("-f, --format <format>", "Output format (human, json)", "human").action(async (options) => {
2930
+ await requireAuth();
2330
2931
  try {
2331
2932
  const result = await whoami();
2332
2933
  refreshInBackground();
@@ -2361,12 +2962,12 @@ var whoamiCommand = new Command11("whoami").description("Show current user and t
2361
2962
  header("All Teams");
2362
2963
  console.log();
2363
2964
  for (const team of result.teams) {
2364
- const current = team.isCurrent ? chalk10.green(" (current)") : "";
2965
+ const current = team.isCurrent ? chalk11.green(" (current)") : "";
2365
2966
  console.log(
2366
- ` ${chalk10.bold(team.name)}${current}`
2967
+ ` ${chalk11.bold(team.name)}${current}`
2367
2968
  );
2368
2969
  console.log(
2369
- chalk10.gray(` ID: ${team.id} | Plan: ${team.planName} | Role: ${team.role}`)
2970
+ chalk11.gray(` ID: ${team.id} | Plan: ${team.planName} | Role: ${team.role}`)
2370
2971
  );
2371
2972
  }
2372
2973
  }
@@ -2378,17 +2979,23 @@ var whoamiCommand = new Command11("whoami").description("Show current user and t
2378
2979
  });
2379
2980
 
2380
2981
  // src/commands/skill.ts
2381
- import { Command as Command12 } from "commander";
2382
- import chalk11 from "chalk";
2982
+ init_output();
2983
+ import { Command as Command14 } from "commander";
2984
+ import chalk12 from "chalk";
2383
2985
  import { mkdirSync, writeFileSync, existsSync as existsSync2 } from "fs";
2384
2986
  import { join } from "path";
2385
2987
  import { homedir } from "os";
2386
- var SKILL_CONTENT = `---
2387
- name: conceptcraft
2988
+ function generateSkillContent(b) {
2989
+ const cmd2 = b.name;
2990
+ const pkg = b.packageName;
2991
+ const url = b.apiUrl;
2992
+ const name = b.displayName;
2993
+ return `---
2994
+ name: ${cmd2}
2388
2995
  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
2996
  ---
2390
2997
 
2391
- # ConceptCraft CLI
2998
+ # ${name} CLI
2392
2999
 
2393
3000
  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
3001
 
@@ -2396,16 +3003,16 @@ Create professional presentations directly from your terminal. The CLI generates
2396
3003
 
2397
3004
  \`\`\`bash
2398
3005
  # Install globally
2399
- npm install -g @conceptcraft/cli
3006
+ npm install -g ${pkg}
2400
3007
 
2401
- # Configure API key (get from https://conceptcraft.ai/settings/api-keys)
2402
- mindframes config init
3008
+ # Configure API key (get from ${url}/settings/api-keys)
3009
+ ${cmd2} config init
2403
3010
  \`\`\`
2404
3011
 
2405
3012
  ## Core Workflow
2406
3013
 
2407
3014
  1. **Gather context** - Read relevant files, code, or documentation
2408
- 2. **Create presentation** - Pass context to \`conceptcraft create\`
3015
+ 2. **Create presentation** - Pass context to \`${cmd2} create\`
2409
3016
  3. **Share URL** - Return the presentation link to the user
2410
3017
 
2411
3018
  ## Commands
@@ -2416,22 +3023,22 @@ Context is **required**. Provide it via one of these methods:
2416
3023
 
2417
3024
  \`\`\`bash
2418
3025
  # Upload files (PDFs, PPTX, images, docs)
2419
- conceptcraft create "Product Overview" --file ./deck.pptx --file ./logo.png
3026
+ ${cmd2} create "Product Overview" --file ./deck.pptx --file ./logo.png
2420
3027
 
2421
3028
  # Direct text context
2422
- conceptcraft create "Topic Title" --context "Key points, data, facts..."
3029
+ ${cmd2} create "Topic Title" --context "Key points, data, facts..."
2423
3030
 
2424
3031
  # From a text file
2425
- conceptcraft create "Topic Title" --context-file ./notes.md
3032
+ ${cmd2} create "Topic Title" --context-file ./notes.md
2426
3033
 
2427
3034
  # Pipe content (auto-detected)
2428
- cat README.md | conceptcraft create "Project Overview"
3035
+ cat README.md | ${cmd2} create "Project Overview"
2429
3036
 
2430
3037
  # From URLs (scraped automatically)
2431
- conceptcraft create "Competitor Analysis" --sources https://example.com/report
3038
+ ${cmd2} create "Competitor Analysis" --sources https://example.com/report
2432
3039
 
2433
3040
  # Combine multiple sources
2434
- cat src/auth/*.ts | conceptcraft create "Auth System" \\
3041
+ cat src/auth/*.ts | ${cmd2} create "Auth System" \\
2435
3042
  --file ./architecture.png \\
2436
3043
  --context "Focus on security patterns"
2437
3044
  \`\`\`
@@ -2456,28 +3063,28 @@ cat src/auth/*.ts | conceptcraft create "Auth System" \\
2456
3063
 
2457
3064
  \`\`\`bash
2458
3065
  # Check authentication
2459
- conceptcraft whoami
3066
+ ${cmd2} whoami
2460
3067
 
2461
3068
  # List presentations
2462
- conceptcraft list
2463
- conceptcraft list --format json
3069
+ ${cmd2} list
3070
+ ${cmd2} list --format json
2464
3071
 
2465
3072
  # Get presentation details
2466
- conceptcraft get <id-or-slug>
3073
+ ${cmd2} get <id-or-slug>
2467
3074
 
2468
3075
  # Export to ZIP
2469
- conceptcraft export <id-or-slug> -o presentation.zip
3076
+ ${cmd2} export <id-or-slug> -o presentation.zip
2470
3077
 
2471
3078
  # Import presentation
2472
- conceptcraft import ./presentation.zip
3079
+ ${cmd2} import ./presentation.zip
2473
3080
 
2474
3081
  # Manage branding
2475
- conceptcraft branding list
2476
- conceptcraft branding extract https://company.com
3082
+ ${cmd2} branding list
3083
+ ${cmd2} branding extract https://company.com
2477
3084
 
2478
3085
  # Install/manage this skill
2479
- conceptcraft skill install
2480
- conceptcraft skill show
3086
+ ${cmd2} skill install
3087
+ ${cmd2} skill show
2481
3088
  \`\`\`
2482
3089
 
2483
3090
  ## Examples
@@ -2486,7 +3093,7 @@ conceptcraft skill show
2486
3093
 
2487
3094
  \`\`\`bash
2488
3095
  # Read the relevant files and create presentation
2489
- cat src/lib/auth.ts src/lib/session.ts | conceptcraft create "Authentication System" \\
3096
+ cat src/lib/auth.ts src/lib/session.ts | ${cmd2} create "Authentication System" \\
2490
3097
  --slides 8 --tone educational --audience "New developers" \\
2491
3098
  --goal train
2492
3099
  \`\`\`
@@ -2494,7 +3101,7 @@ cat src/lib/auth.ts src/lib/session.ts | conceptcraft create "Authentication Sys
2494
3101
  ### Technical Documentation with Diagrams
2495
3102
 
2496
3103
  \`\`\`bash
2497
- conceptcraft create "API Reference" \\
3104
+ ${cmd2} create "API Reference" \\
2498
3105
  --file ./docs/api.md \\
2499
3106
  --file ./diagrams/architecture.png \\
2500
3107
  --mode best --amount detailed \\
@@ -2504,14 +3111,14 @@ conceptcraft create "API Reference" \\
2504
3111
  ### Quick Project Overview
2505
3112
 
2506
3113
  \`\`\`bash
2507
- cat README.md package.json | conceptcraft create "Project Introduction" \\
3114
+ cat README.md package.json | ${cmd2} create "Project Introduction" \\
2508
3115
  -m instant --slides 5
2509
3116
  \`\`\`
2510
3117
 
2511
3118
  ### Sales Deck from Existing Presentation
2512
3119
 
2513
3120
  \`\`\`bash
2514
- conceptcraft create "Product Demo" \\
3121
+ ${cmd2} create "Product Demo" \\
2515
3122
  --file ./existing-deck.pptx \\
2516
3123
  --goal persuade \\
2517
3124
  --audience "Enterprise buyers" \\
@@ -2521,7 +3128,7 @@ conceptcraft create "Product Demo" \\
2521
3128
  ### Research Presentation
2522
3129
 
2523
3130
  \`\`\`bash
2524
- conceptcraft create "Market Analysis" \\
3131
+ ${cmd2} create "Market Analysis" \\
2525
3132
  --file ./research.pdf \\
2526
3133
  --sources https://report.com/industry.pdf \\
2527
3134
  --tone formal --audience "Executive team" \\
@@ -2538,12 +3145,12 @@ Successful creation returns:
2538
3145
  Slides: 8
2539
3146
  Generated in: 45s \xB7 12,500 tokens
2540
3147
 
2541
- Open: https://conceptcraft.ai/en/view/presentations/auth-system-v1-abc123
3148
+ Open: ${url}/en/view/presentations/auth-system-v1-abc123
2542
3149
  \`\`\`
2543
3150
 
2544
3151
  For scripting, use JSON output:
2545
3152
  \`\`\`bash
2546
- URL=$(conceptcraft create "Demo" --context "..." -o json | jq -r '.viewUrl')
3153
+ URL=$(${cmd2} create "Demo" --context "..." -o json | jq -r '.viewUrl')
2547
3154
  \`\`\`
2548
3155
 
2549
3156
  ## Best Practices
@@ -2565,15 +3172,16 @@ URL=$(conceptcraft create "Demo" --context "..." -o json | jq -r '.viewUrl')
2565
3172
 
2566
3173
  \`\`\`bash
2567
3174
  # Check if authenticated
2568
- conceptcraft whoami
3175
+ ${cmd2} whoami
2569
3176
 
2570
3177
  # Verify API key
2571
- mindframes config show
3178
+ ${cmd2} config show
2572
3179
 
2573
3180
  # Debug mode
2574
- conceptcraft create "Test" --context "test" --debug
3181
+ ${cmd2} create "Test" --context "test" --debug
2575
3182
  \`\`\`
2576
3183
  `;
3184
+ }
2577
3185
  var EDITORS = [
2578
3186
  { name: "Claude Code", dir: ".claude" },
2579
3187
  { name: "Cursor", dir: ".cursor" },
@@ -2582,29 +3190,30 @@ var EDITORS = [
2582
3190
  { name: "Windsurf", dir: ".windsurf" },
2583
3191
  { name: "Agent", dir: ".agent" }
2584
3192
  ];
2585
- var skillCommand = new Command12("skill").description("Manage ConceptCraft skill for AI coding assistants").addHelpText(
3193
+ var skillCommand = new Command14("skill").description(`Manage ${brand.displayName} skill for AI coding assistants`).addHelpText(
2586
3194
  "after",
2587
3195
  `
2588
- ${chalk11.bold("Examples:")}
2589
- ${chalk11.gray("# Install skill for all detected editors")}
2590
- $ conceptcraft skill install
3196
+ ${chalk12.bold("Examples:")}
3197
+ ${chalk12.gray("# Install skill for all detected editors")}
3198
+ $ ${brand.name} skill install
2591
3199
 
2592
- ${chalk11.gray("# Install to specific directory")}
2593
- $ conceptcraft skill install --dir ~/.claude
3200
+ ${chalk12.gray("# Install to specific directory")}
3201
+ $ ${brand.name} skill install --dir ~/.claude
2594
3202
 
2595
- ${chalk11.gray("# Show skill content")}
2596
- $ conceptcraft skill show
3203
+ ${chalk12.gray("# Show skill content")}
3204
+ $ ${brand.name} skill show
2597
3205
  `
2598
3206
  );
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) => {
3207
+ skillCommand.command("install").description(`Install the ${brand.displayName} skill for AI coding assistants`).option("-d, --dir <path>", "Install to specific directory").option("-g, --global", "Install globally (to home directory)", true).option("-l, --local", "Install locally (to current directory)").option("-f, --force", "Overwrite existing skill files").action(async (options) => {
2600
3208
  const installed = [];
2601
3209
  const skipped = [];
2602
3210
  const errors = [];
2603
3211
  const baseDir = options.local ? process.cwd() : homedir();
3212
+ const skillContent = generateSkillContent(brand);
2604
3213
  if (options.dir) {
2605
- const skillPath = join(options.dir, "skills", "conceptcraft");
3214
+ const skillPath = join(options.dir, "skills", brand.name);
2606
3215
  try {
2607
- installSkill(skillPath, options.force);
3216
+ installSkill(skillPath, skillContent, options.force);
2608
3217
  installed.push(options.dir);
2609
3218
  } catch (err) {
2610
3219
  errors.push(`${options.dir}: ${err instanceof Error ? err.message : String(err)}`);
@@ -2612,7 +3221,7 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
2612
3221
  } else {
2613
3222
  for (const editor of EDITORS) {
2614
3223
  const editorDir = join(baseDir, editor.dir);
2615
- const skillPath = join(editorDir, "skills", "conceptcraft");
3224
+ const skillPath = join(editorDir, "skills", brand.name);
2616
3225
  const skillFile = join(skillPath, "SKILL.md");
2617
3226
  if (!existsSync2(editorDir)) {
2618
3227
  continue;
@@ -2622,7 +3231,7 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
2622
3231
  continue;
2623
3232
  }
2624
3233
  try {
2625
- installSkill(skillPath, options.force);
3234
+ installSkill(skillPath, skillContent, options.force);
2626
3235
  installed.push(editor.name);
2627
3236
  } catch (err) {
2628
3237
  errors.push(`${editor.name}: ${err instanceof Error ? err.message : String(err)}`);
@@ -2638,7 +3247,7 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
2638
3247
  if (skipped.length > 0) {
2639
3248
  console.log();
2640
3249
  info(`Skipped (already exists): ${skipped.join(", ")}`);
2641
- console.log(chalk11.gray(" Use --force to overwrite"));
3250
+ console.log(chalk12.gray(" Use --force to overwrite"));
2642
3251
  }
2643
3252
  if (errors.length > 0) {
2644
3253
  console.log();
@@ -2649,20 +3258,20 @@ skillCommand.command("install").description("Install the ConceptCraft skill for
2649
3258
  if (installed.length === 0 && skipped.length === 0 && errors.length === 0) {
2650
3259
  info("No supported AI coding assistants detected.");
2651
3260
  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"));
3261
+ console.log(chalk12.gray("Supported editors: " + EDITORS.map((e) => e.name).join(", ")));
3262
+ console.log(chalk12.gray("Use --dir <path> to install to a specific directory"));
2654
3263
  }
2655
3264
  console.log();
2656
3265
  });
2657
3266
  skillCommand.command("show").description("Display the skill content").action(() => {
2658
- console.log(SKILL_CONTENT);
3267
+ console.log(generateSkillContent(brand));
2659
3268
  });
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) => {
3269
+ skillCommand.command("uninstall").description(`Remove the ${brand.displayName} skill from AI coding assistants`).option("-g, --global", "Uninstall globally (from home directory)", true).option("-l, --local", "Uninstall locally (from current directory)").action(async (options) => {
2661
3270
  const { rmSync } = await import("fs");
2662
3271
  const removed = [];
2663
3272
  const baseDir = options.local ? process.cwd() : homedir();
2664
3273
  for (const editor of EDITORS) {
2665
- const skillPath = join(baseDir, editor.dir, "skills", "conceptcraft");
3274
+ const skillPath = join(baseDir, editor.dir, "skills", brand.name);
2666
3275
  if (existsSync2(skillPath)) {
2667
3276
  try {
2668
3277
  rmSync(skillPath, { recursive: true });
@@ -2680,21 +3289,23 @@ skillCommand.command("uninstall").description("Remove the ConceptCraft skill fro
2680
3289
  }
2681
3290
  console.log();
2682
3291
  });
2683
- function installSkill(skillPath, force) {
3292
+ function installSkill(skillPath, content, force) {
2684
3293
  const skillFile = join(skillPath, "SKILL.md");
2685
3294
  mkdirSync(skillPath, { recursive: true });
2686
- writeFileSync(skillFile, SKILL_CONTENT, "utf-8");
3295
+ writeFileSync(skillFile, content, "utf-8");
2687
3296
  }
2688
3297
 
2689
3298
  // src/index.ts
2690
- var VERSION = "0.1.0";
2691
- var program = new Command13();
3299
+ var VERSION = "0.1.4";
3300
+ var program = new Command15();
2692
3301
  var cmdName = brand.commands[0];
2693
3302
  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
3303
  outputError: (str, write) => {
2695
- write(chalk12.red(str));
3304
+ write(chalk13.red(str));
2696
3305
  }
2697
3306
  });
3307
+ program.addCommand(loginCommand);
3308
+ program.addCommand(logoutCommand);
2698
3309
  program.addCommand(configCommand);
2699
3310
  program.addCommand(createCommand);
2700
3311
  program.addCommand(listCommand);
@@ -2711,7 +3322,7 @@ if (deriveCommand.commands.length > 0) {
2711
3322
  program.addCommand(deriveCommand);
2712
3323
  }
2713
3324
  program.on("command:*", (operands) => {
2714
- console.error(chalk12.red(`Error: Unknown command '${operands[0]}'`));
3325
+ console.error(chalk13.red(`Error: Unknown command '${operands[0]}'`));
2715
3326
  console.error();
2716
3327
  console.error(`Run '${cmdName} --help' to see available commands.`);
2717
3328
  process.exit(1);
@@ -2719,38 +3330,38 @@ program.on("command:*", (operands) => {
2719
3330
  program.addHelpText(
2720
3331
  "after",
2721
3332
  `
2722
- ${chalk12.bold("Examples:")}
2723
- ${chalk12.gray("# Configure CLI")}
2724
- $ ${cmdName} config init
3333
+ ${chalk13.bold("Examples:")}
3334
+ ${chalk13.gray("# Log in (opens browser)")}
3335
+ $ ${cmdName} login
2725
3336
 
2726
- ${chalk12.gray("# Create a presentation (recommended pattern)")}
3337
+ ${chalk13.gray("# Create a presentation (recommended pattern)")}
2727
3338
  $ ${cmdName} create "Q4 Business Review" \\
2728
3339
  -n 12 -m best \\
2729
3340
  --audience "Executive leadership team" \\
2730
3341
  --context "Revenue: $50M (+25% YoY), New customers: 150"
2731
3342
 
2732
- ${chalk12.gray("# Create from a file")}
3343
+ ${chalk13.gray("# Create from a file")}
2733
3344
  $ ${cmdName} create "Product Launch" \\
2734
3345
  -n 10 -m balanced \\
2735
3346
  --audience "Sales team and partners" \\
2736
3347
  --context-file ./launch-brief.md
2737
3348
 
2738
- ${chalk12.gray("# Create from URLs")}
3349
+ ${chalk13.gray("# Create from URLs")}
2739
3350
  $ ${cmdName} create "Market Analysis" \\
2740
3351
  -n 15 -m best \\
2741
3352
  --audience "Strategy team" \\
2742
3353
  --sources https://example.com/report.pdf
2743
3354
 
2744
- ${chalk12.gray("# List and export")}
3355
+ ${chalk13.gray("# List and export")}
2745
3356
  $ ${cmdName} list --format table
2746
3357
  $ ${cmdName} export <slug> -o backup.zip
2747
3358
 
2748
- ${chalk12.bold("Environment Variables:")}
2749
- CC_MINDFRAMES_API_KEY API key for authentication
2750
- CC_SLIDES_API_URL Custom API URL (optional)
3359
+ ${chalk13.bold("Authentication:")}
3360
+ Run '${cmdName} login' to authenticate (recommended).
3361
+ Or set CC_MINDFRAMES_API_KEY environment variable for API key auth.
2751
3362
 
2752
- ${chalk12.bold("More Info:")}
2753
- ${chalk12.blue(brand.docsUrl)}
3363
+ ${chalk13.bold("More Info:")}
3364
+ ${chalk13.blue(brand.docsUrl)}
2754
3365
  `
2755
3366
  );
2756
3367
  program.parse();