create-rubrica 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +308 -323
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { intro as intro3 } from "@clack/prompts";
5
5
 
6
6
  // src/commands/create.ts
7
- import { outro, text as text2, select as select2, multiselect, confirm } from "@clack/prompts";
7
+ import { outro, select as select2, multiselect, confirm } from "@clack/prompts";
8
8
  import * as nodeFsPromises9 from "fs/promises";
9
9
 
10
10
  // src/prompts/identityPrompt.ts
@@ -146,17 +146,28 @@ async function downloadRelease(targetDir, version) {
146
146
  import * as nodeFsPromises from "fs/promises";
147
147
  import * as path from "path";
148
148
  var LAYOUT_FILES = {
149
- sidebar: {
149
+ cyberpunk: {
150
150
  copy: ["Layout.tsx", "Sidebar.tsx"],
151
151
  remove: ["Navbar.tsx", "Footer.tsx"]
152
152
  },
153
- topbar: {
153
+ brutalist: {
154
154
  copy: ["Layout.tsx", "Navbar.tsx"],
155
- remove: ["Sidebar.tsx", "Footer.tsx"]
155
+ remove: ["Sidebar.tsx", "Footer.tsx"],
156
+ pages: ["Home.tsx", "Resume.tsx", "Portfolio.tsx", "About.tsx", "Blog.tsx"]
157
+ },
158
+ swiss: {
159
+ copy: ["Layout.tsx", "Sidebar.tsx"],
160
+ remove: ["Navbar.tsx", "Footer.tsx"]
156
161
  },
157
- centered: {
158
- copy: ["Layout.tsx", "Footer.tsx"],
159
- remove: ["Sidebar.tsx", "Navbar.tsx"]
162
+ bento: {
163
+ copy: ["Layout.tsx", "FloatingDock.tsx"],
164
+ remove: [],
165
+ pages: ["Home.tsx", "Resume.tsx", "Portfolio.tsx", "About.tsx", "Blog.tsx"]
166
+ },
167
+ magazine: {
168
+ copy: ["Layout.tsx", "Masthead.tsx"],
169
+ remove: ["Sidebar.tsx", "Footer.tsx"],
170
+ pages: ["Home.tsx", "Resume.tsx", "Portfolio.tsx", "About.tsx", "Blog.tsx"]
160
171
  }
161
172
  };
162
173
  var defaultFs = {
@@ -188,105 +199,22 @@ async function applyLayout(layout, options, fsModule = defaultFs) {
188
199
  await fsModule.unlink(filePath);
189
200
  }
190
201
  }
191
- }
192
-
193
- // src/transforms/applyTheme.ts
194
- import * as nodeFsPromises2 from "fs/promises";
195
-
196
- // src/utils/hexToHsl.ts
197
- function hexToHsl(hex) {
198
- const clean = hex.startsWith("#") ? hex.slice(1) : hex;
199
- if (!/^[0-9a-fA-F]{6}$/.test(clean)) {
200
- throw new Error(`hexToHsl: valor inv\xE1lido "${hex}". Esperado hex de 6 d\xEDgitos (ex: #ff0000).`);
201
- }
202
- const r = parseInt(clean.slice(0, 2), 16) / 255;
203
- const g = parseInt(clean.slice(2, 4), 16) / 255;
204
- const b = parseInt(clean.slice(4, 6), 16) / 255;
205
- const max = Math.max(r, g, b);
206
- const min = Math.min(r, g, b);
207
- const delta = max - min;
208
- const l = (max + min) / 2;
209
- let s = 0;
210
- if (delta !== 0) {
211
- s = delta / (1 - Math.abs(2 * l - 1));
212
- }
213
- let h = 0;
214
- if (delta !== 0) {
215
- if (max === r) h = (g - b) / delta % 6;
216
- else if (max === g) h = (b - r) / delta + 2;
217
- else h = (r - g) / delta + 4;
218
- h = h * 60;
219
- if (h < 0) h += 360;
202
+ if (spec.pages?.length) {
203
+ const pagesDestDir = path.join(projectDir, "src", "pages");
204
+ await fsModule.mkdir(pagesDestDir, { recursive: true });
205
+ for (const file of spec.pages) {
206
+ await fsModule.copyFile(
207
+ path.join(srcDir, "pages", file),
208
+ path.join(pagesDestDir, file)
209
+ );
210
+ }
220
211
  }
221
- return {
222
- h: Math.round(h),
223
- s: Math.round(s * 100),
224
- l: Math.round(l * 100)
225
- };
226
212
  }
227
213
 
228
214
  // src/transforms/applyTheme.ts
215
+ import * as nodeFsPromises2 from "fs/promises";
229
216
  var PRESETS = {
230
- forest: {
231
- root: ` --background: 60 20% 97%;
232
- --foreground: 120 8% 12%;
233
- --card: 60 20% 99%;
234
- --card-foreground: 120 8% 12%;
235
- --popover: 60 20% 99%;
236
- --popover-foreground: 120 8% 12%;
237
- --primary: 150 40% 35%;
238
- --primary-foreground: 0 0% 100%;
239
- --secondary: 80 15% 90%;
240
- --secondary-foreground: 120 8% 12%;
241
- --muted: 80 15% 90%;
242
- --muted-foreground: 120 5% 45%;
243
- --accent: 80 15% 90%;
244
- --accent-foreground: 120 8% 12%;
245
- --destructive: 0 84% 60%;
246
- --destructive-foreground: 0 0% 100%;
247
- --border: 80 15% 84%;
248
- --input: 80 15% 84%;
249
- --ring: 150 40% 35%;
250
- --radius: 0.5rem;
251
- --sidebar: 60 20% 95%;
252
- --sidebar-foreground: 120 5% 45%;
253
- --sidebar-primary: 150 40% 35%;
254
- --sidebar-primary-foreground: 0 0% 100%;
255
- --sidebar-accent: 80 15% 90%;
256
- --sidebar-accent-foreground: 120 8% 12%;
257
- --sidebar-border: 80 15% 84%;
258
- --sidebar-ring: 150 40% 35%;
259
- --neon-purple: 150 40% 35%;
260
- --neon-lime: 80 50% 40%;`,
261
- dark: ` --background: 30 15% 8%;
262
- --foreground: 60 15% 88%;
263
- --card: 30 12% 12%;
264
- --card-foreground: 60 15% 88%;
265
- --popover: 30 12% 12%;
266
- --popover-foreground: 60 15% 88%;
267
- --primary: 150 40% 35%;
268
- --primary-foreground: 0 0% 100%;
269
- --secondary: 30 10% 18%;
270
- --secondary-foreground: 60 15% 88%;
271
- --muted: 30 10% 18%;
272
- --muted-foreground: 60 8% 55%;
273
- --accent: 30 10% 18%;
274
- --accent-foreground: 60 15% 88%;
275
- --destructive: 0 62% 50%;
276
- --destructive-foreground: 0 0% 100%;
277
- --border: 30 10% 18%;
278
- --input: 30 10% 18%;
279
- --ring: 150 40% 35%;
280
- --sidebar: 30 15% 6%;
281
- --sidebar-foreground: 60 8% 55%;
282
- --sidebar-primary: 150 40% 35%;
283
- --sidebar-primary-foreground: 0 0% 100%;
284
- --sidebar-accent: 30 10% 18%;
285
- --sidebar-accent-foreground: 60 15% 88%;
286
- --sidebar-border: 30 10% 18%;
287
- --sidebar-ring: 150 40% 35%;`
288
- },
289
- editorial: {
217
+ "editorial-cream": {
290
218
  root: ` --background: 38 30% 96%;
291
219
  --foreground: 24 10% 10%;
292
220
  --card: 38 30% 99%;
@@ -306,7 +234,6 @@ var PRESETS = {
306
234
  --border: 35 20% 85%;
307
235
  --input: 35 20% 85%;
308
236
  --ring: 35 90% 50%;
309
- --radius: 0.375rem;
310
237
  --sidebar: 38 30% 94%;
311
238
  --sidebar-foreground: 24 8% 45%;
312
239
  --sidebar-primary: 35 90% 50%;
@@ -345,38 +272,66 @@ var PRESETS = {
345
272
  --sidebar-border: 25 15% 18%;
346
273
  --sidebar-ring: 35 90% 50%;`
347
274
  },
348
- minimal: {
275
+ "paper-noir": {
349
276
  root: ` --background: 0 0% 98%;
350
- --foreground: 222 47% 11%;
277
+ --foreground: 0 0% 5%;
351
278
  --card: 0 0% 100%;
352
- --card-foreground: 222 47% 11%;
279
+ --card-foreground: 0 0% 5%;
353
280
  --popover: 0 0% 100%;
354
- --popover-foreground: 222 47% 11%;
355
- --primary: 220 90% 56%;
281
+ --popover-foreground: 0 0% 5%;
282
+ --primary: 0 84% 50%;
356
283
  --primary-foreground: 0 0% 100%;
357
- --secondary: 210 40% 96%;
358
- --secondary-foreground: 222 47% 11%;
359
- --muted: 210 40% 96%;
360
- --muted-foreground: 215 16% 47%;
361
- --accent: 210 40% 96%;
362
- --accent-foreground: 222 47% 11%;
284
+ --secondary: 0 0% 92%;
285
+ --secondary-foreground: 0 0% 5%;
286
+ --muted: 0 0% 92%;
287
+ --muted-foreground: 0 0% 40%;
288
+ --accent: 0 0% 92%;
289
+ --accent-foreground: 0 0% 5%;
363
290
  --destructive: 0 84% 60%;
364
291
  --destructive-foreground: 0 0% 100%;
365
- --border: 214 32% 91%;
366
- --input: 214 32% 91%;
367
- --ring: 220 90% 56%;
368
- --radius: 0.5rem;
369
- --sidebar: 210 40% 98%;
370
- --sidebar-foreground: 215 16% 47%;
371
- --sidebar-primary: 220 90% 56%;
292
+ --border: 0 0% 88%;
293
+ --input: 0 0% 88%;
294
+ --ring: 0 84% 50%;
295
+ --sidebar: 0 0% 96%;
296
+ --sidebar-foreground: 0 0% 40%;
297
+ --sidebar-primary: 0 84% 50%;
372
298
  --sidebar-primary-foreground: 0 0% 100%;
373
- --sidebar-accent: 210 40% 96%;
374
- --sidebar-accent-foreground: 222 47% 11%;
375
- --sidebar-border: 214 32% 91%;
376
- --sidebar-ring: 220 90% 56%;
377
- --neon-purple: 220 90% 56%;
378
- --neon-lime: 142 76% 36%;`,
379
- dark: ` --background: 220 14% 10%;
299
+ --sidebar-accent: 0 0% 92%;
300
+ --sidebar-accent-foreground: 0 0% 5%;
301
+ --sidebar-border: 0 0% 88%;
302
+ --sidebar-ring: 0 84% 50%;
303
+ --neon-purple: 0 84% 50%;
304
+ --neon-lime: 0 0% 20%;`,
305
+ dark: ` --background: 0 0% 5%;
306
+ --foreground: 0 0% 95%;
307
+ --card: 0 0% 8%;
308
+ --card-foreground: 0 0% 95%;
309
+ --popover: 0 0% 8%;
310
+ --popover-foreground: 0 0% 95%;
311
+ --primary: 0 84% 50%;
312
+ --primary-foreground: 0 0% 100%;
313
+ --secondary: 0 0% 12%;
314
+ --secondary-foreground: 0 0% 95%;
315
+ --muted: 0 0% 12%;
316
+ --muted-foreground: 0 0% 60%;
317
+ --accent: 0 0% 12%;
318
+ --accent-foreground: 0 0% 95%;
319
+ --destructive: 0 62% 50%;
320
+ --destructive-foreground: 0 0% 100%;
321
+ --border: 0 0% 15%;
322
+ --input: 0 0% 15%;
323
+ --ring: 0 84% 50%;
324
+ --sidebar: 0 0% 3%;
325
+ --sidebar-foreground: 0 0% 60%;
326
+ --sidebar-primary: 0 84% 50%;
327
+ --sidebar-primary-foreground: 0 0% 100%;
328
+ --sidebar-accent: 0 0% 12%;
329
+ --sidebar-accent-foreground: 0 0% 95%;
330
+ --sidebar-border: 0 0% 15%;
331
+ --sidebar-ring: 0 84% 50%;`
332
+ },
333
+ "midnight-blue": {
334
+ root: ` --background: 222 47% 8%;
380
335
  --foreground: 210 40% 96%;
381
336
  --card: 222 47% 11%;
382
337
  --card-foreground: 210 40% 96%;
@@ -395,84 +350,103 @@ var PRESETS = {
395
350
  --border: 217 33% 17%;
396
351
  --input: 217 33% 17%;
397
352
  --ring: 220 90% 56%;
398
- --sidebar: 222 47% 8%;
353
+ --sidebar: 222 47% 6%;
399
354
  --sidebar-foreground: 215 20% 65%;
400
355
  --sidebar-primary: 220 90% 56%;
401
356
  --sidebar-primary-foreground: 0 0% 100%;
402
357
  --sidebar-accent: 217 33% 17%;
403
358
  --sidebar-accent-foreground: 210 40% 96%;
404
359
  --sidebar-border: 217 33% 17%;
360
+ --sidebar-ring: 220 90% 56%;
361
+ --neon-purple: 220 90% 56%;
362
+ --neon-lime: 190 90% 50%;`,
363
+ dark: ` --background: 220 14% 5%;
364
+ --foreground: 210 40% 96%;
365
+ --card: 222 47% 8%;
366
+ --card-foreground: 210 40% 96%;
367
+ --popover: 222 47% 8%;
368
+ --popover-foreground: 210 40% 96%;
369
+ --primary: 220 90% 56%;
370
+ --primary-foreground: 0 0% 100%;
371
+ --secondary: 217 33% 13%;
372
+ --secondary-foreground: 210 40% 96%;
373
+ --muted: 217 33% 13%;
374
+ --muted-foreground: 215 20% 65%;
375
+ --accent: 217 33% 13%;
376
+ --accent-foreground: 210 40% 96%;
377
+ --destructive: 0 62% 50%;
378
+ --destructive-foreground: 0 0% 100%;
379
+ --border: 217 33% 13%;
380
+ --input: 217 33% 13%;
381
+ --ring: 220 90% 56%;
382
+ --sidebar: 222 47% 3%;
383
+ --sidebar-foreground: 215 20% 65%;
384
+ --sidebar-primary: 220 90% 56%;
385
+ --sidebar-primary-foreground: 0 0% 100%;
386
+ --sidebar-accent: 217 33% 13%;
387
+ --sidebar-accent-foreground: 210 40% 96%;
388
+ --sidebar-border: 217 33% 13%;
405
389
  --sidebar-ring: 220 90% 56%;`
406
390
  },
407
- cyberpunk: {
408
- root: ` --background: 0 0% 14.5%;
409
- --foreground: 0 0% 90%;
410
- --card: 0 0% 4%;
411
- --card-foreground: 0 0% 90%;
412
- --popover: 0 0% 4%;
413
- --popover-foreground: 0 0% 90%;
414
- --primary: 263 90% 66%;
391
+ "solar-warm": {
392
+ root: ` --background: 20 30% 8%;
393
+ --foreground: 35 30% 92%;
394
+ --card: 20 28% 11%;
395
+ --card-foreground: 35 30% 92%;
396
+ --popover: 20 28% 11%;
397
+ --popover-foreground: 35 30% 92%;
398
+ --primary: 25 95% 53%;
415
399
  --primary-foreground: 0 0% 100%;
416
- --secondary: 215 28% 17%;
417
- --secondary-foreground: 0 0% 90%;
418
- --muted: 0 0% 9%;
419
- --muted-foreground: 0 0% 64%;
420
- --accent: 0 0% 9%;
421
- --accent-foreground: 0 0% 90%;
422
- --destructive: 0 84% 60%;
400
+ --secondary: 20 25% 18%;
401
+ --secondary-foreground: 35 30% 92%;
402
+ --muted: 20 25% 18%;
403
+ --muted-foreground: 30 15% 60%;
404
+ --accent: 20 25% 18%;
405
+ --accent-foreground: 35 30% 92%;
406
+ --destructive: 0 62% 50%;
423
407
  --destructive-foreground: 0 0% 100%;
424
- --border: 0 0% 15%;
425
- --input: 0 0% 15%;
426
- --ring: 263 90% 66%;
427
- --radius: 0.5rem;
428
- --sidebar: 0 0% 0%;
429
- --sidebar-foreground: 0 0% 64%;
430
- --sidebar-primary: 263 90% 66%;
408
+ --border: 20 25% 18%;
409
+ --input: 20 25% 18%;
410
+ --ring: 25 95% 53%;
411
+ --sidebar: 20 30% 6%;
412
+ --sidebar-foreground: 30 15% 60%;
413
+ --sidebar-primary: 25 95% 53%;
431
414
  --sidebar-primary-foreground: 0 0% 100%;
432
- --sidebar-accent: 0 0% 9%;
433
- --sidebar-accent-foreground: 0 0% 90%;
434
- --sidebar-border: 0 0% 15%;
435
- --sidebar-ring: 263 90% 66%;
436
- --neon-purple: 271 91% 65%;
437
- --neon-lime: 82 85% 67%;`,
438
- dark: ` --background: 0 0% 8%;
439
- --foreground: 0 0% 90%;
440
- --card: 0 0% 4%;
441
- --card-foreground: 0 0% 90%;
442
- --popover: 0 0% 4%;
443
- --popover-foreground: 0 0% 90%;
444
- --primary: 263 90% 66%;
415
+ --sidebar-accent: 20 25% 18%;
416
+ --sidebar-accent-foreground: 35 30% 92%;
417
+ --sidebar-border: 20 25% 18%;
418
+ --sidebar-ring: 25 95% 53%;
419
+ --neon-purple: 25 95% 53%;
420
+ --neon-lime: 45 93% 47%;`,
421
+ dark: ` --background: 20 35% 5%;
422
+ --foreground: 35 30% 92%;
423
+ --card: 20 30% 8%;
424
+ --card-foreground: 35 30% 92%;
425
+ --popover: 20 30% 8%;
426
+ --popover-foreground: 35 30% 92%;
427
+ --primary: 25 95% 53%;
445
428
  --primary-foreground: 0 0% 100%;
446
- --secondary: 215 28% 17%;
447
- --secondary-foreground: 0 0% 90%;
448
- --muted: 0 0% 9%;
449
- --muted-foreground: 0 0% 64%;
450
- --accent: 0 0% 9%;
451
- --accent-foreground: 0 0% 90%;
452
- --destructive: 0 84% 60%;
429
+ --secondary: 20 30% 13%;
430
+ --secondary-foreground: 35 30% 92%;
431
+ --muted: 20 30% 13%;
432
+ --muted-foreground: 30 15% 60%;
433
+ --accent: 20 30% 13%;
434
+ --accent-foreground: 35 30% 92%;
435
+ --destructive: 0 62% 50%;
453
436
  --destructive-foreground: 0 0% 100%;
454
- --border: 0 0% 15%;
455
- --input: 0 0% 15%;
456
- --ring: 263 90% 66%;
457
- --sidebar: 0 0% 4%;
458
- --sidebar-foreground: 0 0% 64%;
459
- --sidebar-primary: 263 90% 66%;
437
+ --border: 20 30% 13%;
438
+ --input: 20 30% 13%;
439
+ --ring: 25 95% 53%;
440
+ --sidebar: 20 35% 3%;
441
+ --sidebar-foreground: 30 15% 60%;
442
+ --sidebar-primary: 25 95% 53%;
460
443
  --sidebar-primary-foreground: 0 0% 100%;
461
- --sidebar-accent: 0 0% 9%;
462
- --sidebar-accent-foreground: 0 0% 90%;
463
- --sidebar-border: 0 0% 15%;
464
- --sidebar-ring: 263 90% 66%;`
444
+ --sidebar-accent: 20 30% 13%;
445
+ --sidebar-accent-foreground: 35 30% 92%;
446
+ --sidebar-border: 20 30% 13%;
447
+ --sidebar-ring: 25 95% 53%;`
465
448
  }
466
449
  };
467
- function buildCustomThemeVars(accentColor) {
468
- const { h, s, l } = hexToHsl(accentColor);
469
- const hsl = `${h} ${s}% ${l}%`;
470
- const vars = ` --primary: ${hsl};
471
- --ring: ${hsl};
472
- --sidebar-primary: ${hsl};
473
- --sidebar-ring: ${hsl};`;
474
- return { root: vars, dark: vars };
475
- }
476
450
  function replaceBlock(css, selector, newVars) {
477
451
  const re = new RegExp(`(${selector.replace(".", "\\.")} \\{)[^}]*(\\})`, "s");
478
452
  return css.replace(re, `$1
@@ -480,21 +454,13 @@ ${newVars}
480
454
  $2`);
481
455
  }
482
456
  async function applyTheme(options, cssPath, fsModule = nodeFsPromises2) {
483
- let vars;
484
- if (options.preset) {
485
- const theme = PRESETS[options.preset];
486
- if (!theme) {
487
- throw new Error(`applyTheme: preset "${options.preset}" n\xE3o encontrado.`);
488
- }
489
- vars = theme;
490
- } else if (options.accentColor) {
491
- vars = buildCustomThemeVars(options.accentColor);
492
- } else {
493
- throw new Error("applyTheme: forne\xE7a preset ou accentColor.");
457
+ const theme = PRESETS[options.preset];
458
+ if (!theme) {
459
+ throw new Error(`applyTheme: preset "${options.preset}" n\xE3o encontrado.`);
494
460
  }
495
461
  const css = await fsModule.readFile(cssPath, "utf-8");
496
- let updated = replaceBlock(css, ":root", vars.root);
497
- updated = replaceBlock(updated, ".dark", vars.dark);
462
+ let updated = replaceBlock(css, ":root", theme.root);
463
+ updated = replaceBlock(updated, ".dark", theme.dark);
498
464
  await fsModule.writeFile(cssPath, updated);
499
465
  }
500
466
 
@@ -511,7 +477,7 @@ function buildFontLink(font) {
511
477
  />`;
512
478
  }
513
479
  async function applyFont(options, paths, fsModule = nodeFsPromises3) {
514
- const { fontSans, fontMono, radius } = options;
480
+ const { fontSans, fontMono } = options;
515
481
  let css = await fsModule.readFile(paths.css, "utf-8");
516
482
  css = css.replace(
517
483
  /(--font-sans:\s*")[^"]*(")/,
@@ -521,10 +487,6 @@ async function applyFont(options, paths, fsModule = nodeFsPromises3) {
521
487
  /(--font-mono:\s*")[^"]*(")/,
522
488
  `$1${fontMono}$2`
523
489
  );
524
- css = css.replace(
525
- /(--radius:\s*)[^;]+(;)/,
526
- `$1${radius}$2`
527
- );
528
490
  await fsModule.writeFile(paths.css, css);
529
491
  let html = await fsModule.readFile(paths.html, "utf-8");
530
492
  const newLink = buildFontLink(fontSans);
@@ -615,10 +577,8 @@ function generateContent(config) {
615
577
  rssTitle: ${JSON.stringify(config.rssTitle)},
616
578
  rssDescription: ${JSON.stringify(config.rssDescription)},
617
579
  ogImageUrl: ${JSON.stringify(config.ogImageUrl)},
618
- accentColor: ${JSON.stringify(config.accentColor)},
619
580
  fontSans: ${JSON.stringify(config.fontSans)},
620
581
  fontMono: ${JSON.stringify(config.fontMono)},
621
- radius: ${JSON.stringify(config.radius)},
622
582
  };
623
583
  `;
624
584
  }
@@ -637,12 +597,10 @@ import * as nodeFsPromises7 from "fs/promises";
637
597
  import * as path3 from "path";
638
598
  var DEFAULT_STATE = {
639
599
  version: "0.0.0",
640
- layout: "sidebar",
641
- theme: "cyberpunk",
642
- accentColor: null,
600
+ layout: "cyberpunk",
601
+ theme: "editorial-cream",
643
602
  fontSans: "Inter",
644
603
  fontMono: "JetBrains Mono",
645
- radius: "0.5rem",
646
604
  plugins: {}
647
605
  };
648
606
  async function readState(projectDir, fsModule = nodeFsPromises7) {
@@ -686,6 +644,13 @@ async function writeState(projectDir, partial, fsModule = nodeFsPromises8) {
686
644
  }
687
645
 
688
646
  // src/commands/create.ts
647
+ var TEMPLATE_FONTS = {
648
+ cyberpunk: { fontSans: "Chakra Petch", fontMono: "JetBrains Mono" },
649
+ magazine: { fontSans: ["Bodoni Moda", "IBM Plex Serif"], fontMono: "JetBrains Mono" },
650
+ bento: { fontSans: "Manrope", fontMono: "JetBrains Mono" },
651
+ brutalist: { fontSans: "JetBrains Mono", fontMono: "JetBrains Mono" },
652
+ swiss: { fontSans: "Archivo", fontMono: "" }
653
+ };
689
654
  var ALL_PLUGINS = [
690
655
  "contact-wizard",
691
656
  "proposals",
@@ -752,9 +717,11 @@ async function runCreate(projectName, deps) {
752
717
  const layout = await select2({
753
718
  message: "Layout",
754
719
  options: [
755
- { value: "sidebar", label: "Sidebar \u2014 navega\xE7\xE3o lateral com perfil completo" },
756
- { value: "topbar", label: "Topbar \u2014 navbar horizontal fixa no topo" },
757
- { value: "centered", label: "Centered \u2014 sem nav persistente, foco no conte\xFAdo" }
720
+ { value: "cyberpunk", label: "Cyberpunk", hint: "sidebar lateral com foto de perfil \u2014 dev, designer, freelancer" },
721
+ { value: "brutalist", label: "Brutalism Terminal", hint: "identidade hacker total \u2014 monospace, sem bordas arredondadas" },
722
+ { value: "swiss", label: "Swiss Grid", hint: "grid de 12 colunas, tipografia forte, design su\xED\xE7o rigoroso" },
723
+ { value: "bento", label: "Bento", hint: "grid assim\xE9trico com floating dock \u2014 moderno, orientado a produto" },
724
+ { value: "magazine", label: "Magazine", hint: "editorial cl\xE1ssico \xB7 display serifado \xB7 grid assim\xE9trico \xB7 \xE1reas de respiro generosas" }
758
725
  ]
759
726
  });
760
727
  await applyLayoutFn(
@@ -765,60 +732,44 @@ async function runCreate(projectName, deps) {
765
732
  const themeChoice = await select2({
766
733
  message: "Tema visual",
767
734
  options: [
768
- { value: "cyberpunk", label: "Cyberpunk \u2014 neon purple + lime, atmosfera dark" },
769
- { value: "minimal", label: "Minimal \u2014 clean, azul neutro, m\xE1xima legibilidade" },
770
- { value: "editorial", label: "Editorial \u2014 creme, \xE2mbar, tipografia serifada" },
771
- { value: "forest", label: "Forest \u2014 verde musgo, off-white, org\xE2nico" },
772
- { value: "custom", label: "Personalizado..." }
735
+ { value: "editorial-cream", label: "Editorial Cream", hint: "refinado e atemporal \u2014 blogs e portf\xF3lios liter\xE1rios" },
736
+ { value: "paper-noir", label: "Paper Noir", hint: "contraste m\xE1ximo \u2014 identidade editorial forte" },
737
+ { value: "midnight-blue", label: "Midnight Blue", hint: "tech e premium \u2014 devs e designers noturnos" },
738
+ { value: "solar-warm", label: "Solar Warm", hint: "energia e criatividade \u2014 marcas pessoais vibrantes" }
773
739
  ]
774
740
  });
775
- let accentColor;
776
- if (themeChoice === "custom") {
777
- const hexInput = await text2({
778
- message: "Cor de destaque (hex, ex: #0065fe)",
779
- validate: (v) => {
780
- if (!/^#[0-9a-fA-F]{6}$/.test(v)) return "Informe um hex v\xE1lido (ex: #0065fe)";
781
- }
782
- });
783
- accentColor = hexInput;
784
- }
785
- const themeOptions = themeChoice === "custom" ? { accentColor } : { preset: themeChoice };
786
741
  await applyThemeFn(
787
- themeOptions,
742
+ { preset: themeChoice },
788
743
  `${projectDir}/src/index.css`,
789
744
  fs
790
745
  );
746
+ const recommendedFonts = TEMPLATE_FONTS[layout] ?? { fontSans: "", fontMono: "" };
747
+ const recommendedSans = Array.isArray(recommendedFonts.fontSans) ? recommendedFonts.fontSans : [recommendedFonts.fontSans];
791
748
  const fontSans = await select2({
792
749
  message: "Fonte principal",
793
750
  options: [
794
- { value: "Inter", label: "Inter \u2014 neutra, leg\xEDvel, padr\xE3o de produtos digitais" },
795
- { value: "Chakra Petch", label: "Chakra Petch \u2014 geom\xE9trica, tech, futurista" },
796
- { value: "Playfair Display", label: "Playfair Display \u2014 elegante, editorial" },
797
- { value: "Space Grotesk", label: "Space Grotesk \u2014 moderna, startup" },
798
- { value: "DM Sans", label: "DM Sans \u2014 limpa, amig\xE1vel, vers\xE1til" }
751
+ { value: "Inter", label: "Inter", hint: "neutra, leg\xEDvel, padr\xE3o de produtos digitais" },
752
+ { value: "Chakra Petch", label: "Chakra Petch", hint: recommendedSans.includes("Chakra Petch") ? "recomendada" : "geom\xE9trica, tech, futurista" },
753
+ { value: "Playfair Display", label: "Playfair Display", hint: "elegante, editorial cl\xE1ssico" },
754
+ { value: "Space Grotesk", label: "Space Grotesk", hint: "moderna, startup" },
755
+ { value: "DM Sans", label: "DM Sans", hint: "limpa, amig\xE1vel, vers\xE1til" },
756
+ { value: "Bodoni Moda", label: "Bodoni Moda", hint: recommendedSans.includes("Bodoni Moda") ? "recomendada" : "display serifado elegante, editorial cl\xE1ssico" },
757
+ { value: "IBM Plex Serif", label: "IBM Plex Serif", hint: recommendedSans.includes("IBM Plex Serif") ? "recomendada" : "serifa t\xE9cnica, confian\xE7a e rigor" },
758
+ { value: "Manrope", label: "Manrope", hint: recommendedSans.includes("Manrope") ? "recomendada" : "geom\xE9trica humanista, moderna e acolhedora" },
759
+ { value: "Archivo", label: "Archivo", hint: recommendedSans.includes("Archivo") ? "recomendada" : "grotesca utilit\xE1ria, clareza e for\xE7a tipogr\xE1fica" }
799
760
  ]
800
761
  });
801
- const fontMono = await select2({
762
+ const fontMono = recommendedFonts.fontMono === "" ? "" : await select2({
802
763
  message: "Fonte mono",
803
764
  options: [
804
- { value: "JetBrains Mono", label: "JetBrains Mono \u2014 dev-friendly, clara" },
805
- { value: "Fira Code", label: "Fira Code \u2014 cl\xE1ssico, com ligatures" },
806
- { value: "Space Mono", label: "Space Mono \u2014 retro digital" },
807
- { value: "IBM Plex Mono", label: "IBM Plex Mono \u2014 corporativo-tech" }
808
- ]
809
- });
810
- const radius = await select2({
811
- message: "Border radius",
812
- options: [
813
- { value: "0rem", label: "Nenhum \u2014 bordas retas, minimalismo severo" },
814
- { value: "0.375rem", label: "Suave \u2014 sutil arredondamento" },
815
- { value: "0.5rem", label: "M\xE9dio \u2014 padr\xE3o shadcn/ui" },
816
- { value: "0.75rem", label: "Arredondado \u2014 amig\xE1vel, moderno" },
817
- { value: "1rem", label: "Pill \u2014 muito arredondado, jovial" }
765
+ { value: "JetBrains Mono", label: "JetBrains Mono", hint: recommendedFonts.fontMono === "JetBrains Mono" ? "recomendada" : "dev-friendly, clara" },
766
+ { value: "Fira Code", label: "Fira Code", hint: "cl\xE1ssico, com ligatures" },
767
+ { value: "Space Mono", label: "Space Mono", hint: "retro digital" },
768
+ { value: "IBM Plex Mono", label: "IBM Plex Mono", hint: "corporativo-tech" }
818
769
  ]
819
770
  });
820
771
  await applyFontFn(
821
- { fontSans, fontMono, radius },
772
+ { fontSans, fontMono },
822
773
  { css: `${projectDir}/src/index.css`, html: `${projectDir}/index.html` },
823
774
  fs
824
775
  );
@@ -854,7 +805,7 @@ async function runCreate(projectName, deps) {
854
805
  await applyIndexHtmlFn(
855
806
  {
856
807
  fontFamily: fontSans,
857
- themeColor: accentColor ?? "#6d28d9",
808
+ themeColor: "",
858
809
  siteName: identity.siteName,
859
810
  siteUrl: identity.siteUrl,
860
811
  twitterHandle: identity.twitterHandle,
@@ -877,7 +828,6 @@ async function runCreate(projectName, deps) {
877
828
  rssTitle: identity.siteName,
878
829
  rssDescription: identity.siteDescription,
879
830
  ogImageUrl: `${identity.siteUrl}/og.png`,
880
- accentColor: accentColor ?? "#6d28d9",
881
831
  fontSans,
882
832
  fontMono
883
833
  },
@@ -894,11 +844,9 @@ async function runCreate(projectName, deps) {
894
844
  {
895
845
  version: "0.1.0",
896
846
  layout,
897
- theme: themeChoice === "custom" ? "custom" : themeChoice,
898
- accentColor: accentColor ?? null,
847
+ theme: themeChoice,
899
848
  fontSans,
900
849
  fontMono,
901
- radius,
902
850
  plugins: pluginsRecord
903
851
  },
904
852
  fs
@@ -1006,8 +954,8 @@ async function runConfig(deps = {}) {
1006
954
  message: "O que deseja reconfigurar?",
1007
955
  options: [
1008
956
  { value: "identity", label: "Identidade (nome, URL, SEO, RSS)" },
1009
- { value: "appearance", label: "Apar\xEAncia (tema, cores, fonte, radius)" },
1010
- { value: "layout", label: "Layout (sidebar / topbar / centered)" },
957
+ { value: "appearance", label: "Apar\xEAncia (tema, fonte)" },
958
+ { value: "layout", label: "Layout" },
1011
959
  { value: "plugins", label: "Plugins" }
1012
960
  ],
1013
961
  required: false
@@ -1033,10 +981,8 @@ async function runConfig(deps = {}) {
1033
981
  rssTitle: "",
1034
982
  rssDescription: "",
1035
983
  ogImageUrl: "",
1036
- accentColor: currentState.accentColor ?? "",
1037
984
  fontSans: currentState.fontSans,
1038
- fontMono: currentState.fontMono,
1039
- radius: currentState.radius
985
+ fontMono: currentState.fontMono
1040
986
  },
1041
987
  projectDir,
1042
988
  fs
@@ -1047,42 +993,35 @@ async function runConfig(deps = {}) {
1047
993
  message: "Tema visual",
1048
994
  initialValue: currentState.theme,
1049
995
  options: [
1050
- { value: "cyberpunk", label: "Cyberpunk \u2014 neon purple + lime" },
1051
- { value: "minimal", label: "Minimal \u2014 clean, azul neutro" },
1052
- { value: "editorial", label: "Editorial \u2014 creme, \xE2mbar, serifado" },
1053
- { value: "forest", label: "Forest \u2014 verde musgo, off-white" }
996
+ { value: "editorial-cream", label: "Editorial Cream", hint: "refinado e atemporal \u2014 blogs e portf\xF3lios liter\xE1rios" },
997
+ { value: "paper-noir", label: "Paper Noir", hint: "contraste m\xE1ximo \u2014 identidade editorial forte" },
998
+ { value: "midnight-blue", label: "Midnight Blue", hint: "tech e premium \u2014 devs e designers noturnos" },
999
+ { value: "solar-warm", label: "Solar Warm", hint: "energia e criatividade \u2014 marcas pessoais vibrantes" }
1054
1000
  ]
1055
1001
  });
1056
1002
  const fontSans = await select3({
1057
1003
  message: "Fonte principal",
1058
1004
  initialValue: currentState.fontSans,
1059
1005
  options: [
1060
- { value: "Inter", label: "Inter \u2014 neutra, padr\xE3o de produtos digitais" },
1061
- { value: "Chakra Petch", label: "Chakra Petch \u2014 geom\xE9trica, tech" },
1062
- { value: "Playfair Display", label: "Playfair Display \u2014 elegante, editorial" },
1063
- { value: "Space Grotesk", label: "Space Grotesk \u2014 moderna, startup" },
1064
- { value: "DM Sans", label: "DM Sans \u2014 limpa, amig\xE1vel" }
1006
+ { value: "Inter", label: "Inter", hint: "neutra, leg\xEDvel, padr\xE3o de produtos digitais" },
1007
+ { value: "Chakra Petch", label: "Chakra Petch", hint: "geom\xE9trica, tech, futurista" },
1008
+ { value: "Playfair Display", label: "Playfair Display", hint: "elegante, editorial cl\xE1ssico" },
1009
+ { value: "Space Grotesk", label: "Space Grotesk", hint: "moderna, startup" },
1010
+ { value: "DM Sans", label: "DM Sans", hint: "limpa, amig\xE1vel, vers\xE1til" },
1011
+ { value: "Bodoni Moda", label: "Bodoni Moda", hint: "display serifado elegante, editorial cl\xE1ssico" },
1012
+ { value: "IBM Plex Serif", label: "IBM Plex Serif", hint: "serifa t\xE9cnica, confian\xE7a e rigor" },
1013
+ { value: "Manrope", label: "Manrope", hint: "geom\xE9trica humanista, moderna e acolhedora" },
1014
+ { value: "Archivo", label: "Archivo", hint: "grotesca utilit\xE1ria, clareza e for\xE7a tipogr\xE1fica" }
1065
1015
  ]
1066
1016
  });
1067
1017
  const fontMono = await select3({
1068
1018
  message: "Fonte mono",
1069
1019
  initialValue: currentState.fontMono,
1070
1020
  options: [
1071
- { value: "JetBrains Mono", label: "JetBrains Mono" },
1072
- { value: "Fira Code", label: "Fira Code" },
1073
- { value: "Space Mono", label: "Space Mono" },
1074
- { value: "IBM Plex Mono", label: "IBM Plex Mono" }
1075
- ]
1076
- });
1077
- const radius = await select3({
1078
- message: "Border radius",
1079
- initialValue: currentState.radius,
1080
- options: [
1081
- { value: "0rem", label: "Nenhum \u2014 0rem" },
1082
- { value: "0.375rem", label: "Suave \u2014 0.375rem" },
1083
- { value: "0.5rem", label: "M\xE9dio \u2014 0.5rem" },
1084
- { value: "0.75rem", label: "Arredondado \u2014 0.75rem" },
1085
- { value: "1rem", label: "Pill \u2014 1rem" }
1021
+ { value: "JetBrains Mono", label: "JetBrains Mono", hint: "dev-friendly, clara" },
1022
+ { value: "Fira Code", label: "Fira Code", hint: "cl\xE1ssico, com ligatures" },
1023
+ { value: "Space Mono", label: "Space Mono", hint: "retro digital" },
1024
+ { value: "IBM Plex Mono", label: "IBM Plex Mono", hint: "corporativo-tech" }
1086
1025
  ]
1087
1026
  });
1088
1027
  const cssPath = path6.join(projectDir, "src", "index.css");
@@ -1093,7 +1032,7 @@ async function runConfig(deps = {}) {
1093
1032
  fs
1094
1033
  );
1095
1034
  await applyFontFn(
1096
- { fontSans, fontMono, radius },
1035
+ { fontSans, fontMono },
1097
1036
  { css: cssPath, html: htmlPath },
1098
1037
  fs
1099
1038
  );
@@ -1105,17 +1044,16 @@ async function runConfig(deps = {}) {
1105
1044
  stateUpdate.theme = theme;
1106
1045
  stateUpdate.fontSans = fontSans;
1107
1046
  stateUpdate.fontMono = fontMono;
1108
- stateUpdate.radius = radius;
1109
- stateUpdate.accentColor = null;
1110
1047
  }
1111
1048
  if (selected.includes("layout")) {
1112
1049
  const newLayout = await select3({
1113
1050
  message: "Layout",
1114
1051
  initialValue: currentState.layout,
1115
1052
  options: [
1116
- { value: "sidebar", label: "Sidebar \u2014 navega\xE7\xE3o lateral com perfil completo" },
1117
- { value: "topbar", label: "Topbar \u2014 navbar horizontal fixa no topo" },
1118
- { value: "centered", label: "Centered \u2014 sem nav persistente, foco no conte\xFAdo" }
1053
+ { value: "cyberpunk", label: "Cyberpunk", hint: "sidebar lateral com foto de perfil \u2014 dev, designer, freelancer" },
1054
+ { value: "brutalist", label: "Brutalism Terminal", hint: "identidade hacker total \u2014 monospace, sem bordas arredondadas" },
1055
+ { value: "swiss", label: "Swiss Grid", hint: "grid de 12 colunas, tipografia forte, design su\xED\xE7o rigoroso" },
1056
+ { value: "bento", label: "Bento", hint: "grid assim\xE9trico com floating dock \u2014 moderno, orientado a produto" }
1119
1057
  ]
1120
1058
  });
1121
1059
  const confirmed = await confirm2({
@@ -1279,11 +1217,10 @@ Consulte o guia de migra\xE7\xE3o antes de prosseguir.`
1279
1217
  fs
1280
1218
  );
1281
1219
  const cssPath = path7.join(projectDir, "src", "index.css");
1282
- const themeOpts = state.theme && state.theme !== "custom" ? { preset: state.theme } : { accentColor: state.accentColor ?? "#6366f1" };
1283
- await applyThemeFn(themeOpts, cssPath, fs);
1220
+ await applyThemeFn({ preset: state.theme }, cssPath, fs);
1284
1221
  const htmlPath = path7.join(projectDir, "index.html");
1285
1222
  await applyFontFn(
1286
- { fontSans: state.fontSans, fontMono: state.fontMono, radius: state.radius },
1223
+ { fontSans: state.fontSans, fontMono: state.fontMono },
1287
1224
  { css: cssPath, html: htmlPath },
1288
1225
  fs
1289
1226
  );
@@ -1306,15 +1243,13 @@ Consulte o guia de migra\xE7\xE3o antes de prosseguir.`
1306
1243
  rssTitle: "",
1307
1244
  rssDescription: "",
1308
1245
  ogImageUrl: "",
1309
- accentColor: state.accentColor ?? "#6366f1",
1310
1246
  fontSans: state.fontSans,
1311
- fontMono: state.fontMono,
1312
- radius: state.radius
1247
+ fontMono: state.fontMono
1313
1248
  };
1314
1249
  await applyIndexHtmlFn(
1315
1250
  {
1316
1251
  fontFamily: identity.fontSans,
1317
- themeColor: identity.accentColor,
1252
+ themeColor: "",
1318
1253
  siteName: identity.siteName,
1319
1254
  siteUrl: identity.siteUrl,
1320
1255
  twitterHandle: identity.twitterHandle,
@@ -1342,7 +1277,7 @@ Configure-as em: https://dashboard.convex.dev`
1342
1277
  }
1343
1278
 
1344
1279
  // src/commands/setup.ts
1345
- import { outro as outro4, text as text3, password, spinner, isCancel as isCancel5, cancel as cancel5 } from "@clack/prompts";
1280
+ import { outro as outro4, text as text2, password, spinner, isCancel as isCancel5, cancel as cancel5 } from "@clack/prompts";
1346
1281
  import * as nodeFsPromises13 from "fs/promises";
1347
1282
  import * as path8 from "path";
1348
1283
  import * as nodeCrypto from "crypto";
@@ -1425,7 +1360,7 @@ async function runSetup(deps = {}) {
1425
1360
  execFileSync("npx", ["convex", "env", "set", "--", "JWKS", JWKS], {
1426
1361
  stdio: "pipe"
1427
1362
  });
1428
- const siteUrl = await text3({
1363
+ const siteUrl = await text2({
1429
1364
  message: "URL p\xFAblica do site (SITE_URL no Convex):",
1430
1365
  initialValue: siteUrlDefault,
1431
1366
  validate: (v) => {
@@ -1443,18 +1378,18 @@ async function runSetup(deps = {}) {
1443
1378
  const state = await readState2(cwd, fs);
1444
1379
  const plugins = state.plugins;
1445
1380
  if (plugins["contact-wizard"] || plugins["testimonials-intake"]) {
1446
- const telegramToken = await text3({
1381
+ const telegramToken = await text2({
1447
1382
  message: "TELEGRAM_BOT_TOKEN (Enter para pular):",
1448
1383
  initialValue: ""
1449
1384
  });
1450
- if (!isCancel5(telegramToken) && telegramToken.trim()) {
1385
+ if (!isCancel5(telegramToken) && telegramToken && telegramToken.trim()) {
1451
1386
  execFileSync("npx", ["convex", "env", "set", "--", "TELEGRAM_BOT_TOKEN", telegramToken], { stdio: "pipe" });
1452
1387
  }
1453
- const telegramChatId = await text3({
1388
+ const telegramChatId = await text2({
1454
1389
  message: "TELEGRAM_ADMIN_CHAT_ID (Enter para pular):",
1455
1390
  initialValue: ""
1456
1391
  });
1457
- if (!isCancel5(telegramChatId) && telegramChatId.trim()) {
1392
+ if (!isCancel5(telegramChatId) && telegramChatId && telegramChatId.trim()) {
1458
1393
  execFileSync("npx", ["convex", "env", "set", "--", "TELEGRAM_ADMIN_CHAT_ID", telegramChatId], { stdio: "pipe" });
1459
1394
  }
1460
1395
  }
@@ -1463,31 +1398,31 @@ async function runSetup(deps = {}) {
1463
1398
  execFileSync("npx", ["convex", "env", "set", "--", "PLAYGROUND_KEY_PEPPER", pepper], { stdio: "pipe" });
1464
1399
  }
1465
1400
  if (plugins["ai-resumes"] || plugins["i18n"] || plugins["playground"]) {
1466
- const openrouterKey = await text3({
1401
+ const openrouterKey = await text2({
1467
1402
  message: "OPENROUTER_API_KEY (Enter para pular):",
1468
1403
  initialValue: ""
1469
1404
  });
1470
- if (!isCancel5(openrouterKey) && openrouterKey.trim()) {
1405
+ if (!isCancel5(openrouterKey) && openrouterKey && openrouterKey.trim()) {
1471
1406
  execFileSync("npx", ["convex", "env", "set", "--", "OPENROUTER_API_KEY", openrouterKey], { stdio: "pipe" });
1472
1407
  }
1473
1408
  }
1474
1409
  if (plugins["payments"]) {
1475
- const stripeSecret = await text3({
1410
+ const stripeSecret = await text2({
1476
1411
  message: "STRIPE_WEBHOOK_SECRET (Enter para pular):",
1477
1412
  initialValue: ""
1478
1413
  });
1479
- if (!isCancel5(stripeSecret) && stripeSecret.trim()) {
1414
+ if (!isCancel5(stripeSecret) && stripeSecret && stripeSecret.trim()) {
1480
1415
  execFileSync("npx", ["convex", "env", "set", "--", "STRIPE_WEBHOOK_SECRET", stripeSecret], { stdio: "pipe" });
1481
1416
  }
1482
- const asaasToken = await text3({
1417
+ const asaasToken = await text2({
1483
1418
  message: "ASAAS_WEBHOOK_TOKEN (Enter para pular):",
1484
1419
  initialValue: ""
1485
1420
  });
1486
- if (!isCancel5(asaasToken) && asaasToken.trim()) {
1421
+ if (!isCancel5(asaasToken) && asaasToken && asaasToken.trim()) {
1487
1422
  execFileSync("npx", ["convex", "env", "set", "--", "ASAAS_WEBHOOK_TOKEN", asaasToken], { stdio: "pipe" });
1488
1423
  }
1489
1424
  }
1490
- const adminEmail = await text3({
1425
+ const adminEmail = await text2({
1491
1426
  message: "Email do usu\xE1rio root:",
1492
1427
  validate: (v) => {
1493
1428
  if (!v.includes("@") || !v.includes("."))
@@ -1534,20 +1469,74 @@ async function runSetup(deps = {}) {
1534
1469
  }
1535
1470
  return;
1536
1471
  }
1537
- const vercelHookUrl = await text3({
1472
+ if (plugins["proposals"]) {
1473
+ try {
1474
+ execFileSync(
1475
+ "npx",
1476
+ ["convex", "run", "contractTemplates:seedDefaultTemplate"],
1477
+ { stdio: "pipe" }
1478
+ );
1479
+ } catch {
1480
+ }
1481
+ }
1482
+ try {
1483
+ execFileSync("npx", ["convex", "run", "siteTexts:seed"], { stdio: "pipe" });
1484
+ } catch {
1485
+ }
1486
+ const vercelHookUrl = await text2({
1538
1487
  message: "VERCEL_DEPLOY_HOOK_URL (Enter para pular):",
1539
1488
  initialValue: ""
1540
1489
  });
1541
- if (!isCancel5(vercelHookUrl) && vercelHookUrl.trim()) {
1490
+ if (!isCancel5(vercelHookUrl) && vercelHookUrl && vercelHookUrl.trim()) {
1542
1491
  execFileSync("npx", ["convex", "env", "set", "--", "VERCEL_DEPLOY_HOOK_URL", vercelHookUrl], { stdio: "pipe" });
1543
1492
  }
1544
- const vercelWebhookSecret = await text3({
1493
+ const vercelWebhookSecret = await text2({
1545
1494
  message: "VERCEL_WEBHOOK_SECRET (Enter para pular):",
1546
1495
  initialValue: ""
1547
1496
  });
1548
- if (!isCancel5(vercelWebhookSecret) && vercelWebhookSecret.trim()) {
1497
+ if (!isCancel5(vercelWebhookSecret) && vercelWebhookSecret && vercelWebhookSecret.trim()) {
1549
1498
  execFileSync("npx", ["convex", "env", "set", "--", "VERCEL_WEBHOOK_SECRET", vercelWebhookSecret], { stdio: "pipe" });
1550
1499
  }
1500
+ const profession = await text2({
1501
+ message: "Qual \xE9 a sua profiss\xE3o ou \xE1rea de atua\xE7\xE3o? (Enter para usar padr\xE3o tech)",
1502
+ placeholder: "Ex: Designer UX, Fot\xF3grafa, Advogada, Engenheira Civil...",
1503
+ initialValue: ""
1504
+ });
1505
+ const specialties = await text2({
1506
+ message: "Quais s\xE3o suas principais especialidades? (separadas por v\xEDrgula, Enter para pular)",
1507
+ placeholder: "Ex: Branding, Motion Design, Fotografia de Produto",
1508
+ initialValue: ""
1509
+ });
1510
+ const professionStr = isCancel5(profession) ? "" : (profession || "").trim();
1511
+ const specialtiesStr = isCancel5(specialties) ? "" : (specialties || "").trim();
1512
+ if (professionStr || specialtiesStr) {
1513
+ const tags = specialtiesStr ? specialtiesStr.split(",").map((s2) => s2.trim()).filter(Boolean).slice(0, 4).map((label, i) => ({ label, color: i % 2 === 0 ? "primary" : "secondary" })) : [
1514
+ { label: "React", color: "primary" },
1515
+ { label: "TypeScript", color: "primary" },
1516
+ { label: "Arquitetura de Sistemas", color: "secondary" },
1517
+ { label: "DevOps & Infraestrutura", color: "secondary" }
1518
+ ];
1519
+ try {
1520
+ execFileSync(
1521
+ "npx",
1522
+ ["convex", "run", "homeContent:set", JSON.stringify({ key: "hero_tags", value: tags })],
1523
+ { stdio: "pipe" }
1524
+ );
1525
+ } catch {
1526
+ }
1527
+ if (professionStr) {
1528
+ const name = adminEmail.split("@")[0];
1529
+ const aiContext = specialtiesStr ? `${name} \xE9 ${professionStr} com especialidade em ${specialtiesStr}. Entrega projetos com qualidade e comprometimento.` : `${name} \xE9 ${professionStr}. Entrega projetos com qualidade e comprometimento.`;
1530
+ try {
1531
+ execFileSync(
1532
+ "npx",
1533
+ ["convex", "run", "contactInfo:update", JSON.stringify({ proposalAiContext: aiContext })],
1534
+ { stdio: "pipe" }
1535
+ );
1536
+ } catch {
1537
+ }
1538
+ }
1539
+ }
1551
1540
  outro4(
1552
1541
  `Setup conclu\xEDdo!
1553
1542
 
@@ -1560,11 +1549,7 @@ async function runSetup(deps = {}) {
1560
1549
 
1561
1550
  // src/index.ts
1562
1551
  async function runCli(argv, cwd) {
1563
- let [, , command, projectName] = argv;
1564
- if (command && command !== "create" && command !== "config" && command !== "update" && command !== "setup") {
1565
- projectName = command;
1566
- command = "create";
1567
- }
1552
+ const [, , command, projectName] = argv;
1568
1553
  intro3("Rubrica CLI");
1569
1554
  if (command === "create") {
1570
1555
  if (!projectName) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rubrica",
3
- "version": "0.1.1",
3
+ "version": "0.3.1",
4
4
  "description": "CLI para criar e configurar projetos Rubrica — portfólio profissional com React + Convex",
5
5
  "type": "module",
6
6
  "bin": {