@revealui/cli 0.4.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { createLogger as createLogger12 } from "@revealui/setup/utils";
4
+ import { createLogger as createLogger14 } from "@revealui/setup/utils";
5
5
  import { Command } from "commander";
6
6
 
7
7
  // src/commands/agent.ts
@@ -215,20 +215,337 @@ function buildMinimalInstructions() {
215
215
  ].join("\n");
216
216
  }
217
217
 
218
+ // src/commands/auth.ts
219
+ import fs from "fs/promises";
220
+ import os from "os";
221
+ import path from "path";
222
+ import { createLogger as createLogger2 } from "@revealui/setup/utils";
223
+ import { execa as execa2 } from "execa";
224
+
225
+ // src/utils/command.ts
226
+ import net from "net";
227
+ import { execa } from "execa";
228
+ async function commandExists(command) {
229
+ try {
230
+ await execa("bash", ["-lc", `command -v ${command}`], {
231
+ stdio: "pipe"
232
+ });
233
+ return true;
234
+ } catch {
235
+ return false;
236
+ }
237
+ }
238
+ async function isTcpReachable(host, port, timeoutMs = 1500) {
239
+ return await new Promise((resolve2) => {
240
+ const socket = new net.Socket();
241
+ const finalize = (value) => {
242
+ socket.removeAllListeners();
243
+ socket.destroy();
244
+ resolve2(value);
245
+ };
246
+ socket.setTimeout(timeoutMs);
247
+ socket.once("connect", () => finalize(true));
248
+ socket.once("timeout", () => finalize(false));
249
+ socket.once("error", () => finalize(false));
250
+ socket.connect(port, host);
251
+ });
252
+ }
253
+
254
+ // src/commands/auth.ts
255
+ var logger2 = createLogger2({ prefix: "Auth" });
256
+ var REVVAULT_NPM_PATH = "revealui/env/npm";
257
+ var NPM_TOKEN_INTERPOLATION = "${NPM_TOKEN}";
258
+ var NPMRC_AUTH_LINE = `//registry.npmjs.org/:_authToken=${NPM_TOKEN_INTERPOLATION}`;
259
+ function write(text5) {
260
+ process.stdout.write(text5);
261
+ }
262
+ function writeJson(data) {
263
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
264
+ `);
265
+ }
266
+ function getNpmrcPaths() {
267
+ const user = path.join(os.homedir(), ".npmrc");
268
+ let dir = process.cwd();
269
+ let project = null;
270
+ for (let i = 0; i < 10; i++) {
271
+ const candidate = path.join(dir, ".npmrc");
272
+ if (dir !== os.homedir()) {
273
+ project ??= candidate;
274
+ }
275
+ const parent = path.dirname(dir);
276
+ if (parent === dir) break;
277
+ dir = parent;
278
+ }
279
+ return { user, project };
280
+ }
281
+ async function fileContains(filePath, needle) {
282
+ try {
283
+ const content = await fs.readFile(filePath, "utf-8");
284
+ return content.includes(needle);
285
+ } catch {
286
+ return false;
287
+ }
288
+ }
289
+ async function getTokenFromEnv() {
290
+ return process.env.NPM_TOKEN || void 0;
291
+ }
292
+ async function getTokenFromNpmrc() {
293
+ const userRc = path.join(os.homedir(), ".npmrc");
294
+ try {
295
+ const content = await fs.readFile(userRc, "utf-8");
296
+ const match = content.match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);
297
+ return match?.[1]?.trim() || void 0;
298
+ } catch {
299
+ return void 0;
300
+ }
301
+ }
302
+ function maskToken(token) {
303
+ if (token.length <= 8) return "***";
304
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
305
+ }
306
+ async function hasRevvault() {
307
+ return commandExists("revvault");
308
+ }
309
+ async function revvaultGet(secretPath) {
310
+ try {
311
+ const { stdout } = await execa2("revvault", ["get", secretPath], {
312
+ stdio: "pipe"
313
+ });
314
+ return stdout.trim() || void 0;
315
+ } catch {
316
+ return void 0;
317
+ }
318
+ }
319
+ async function revvaultSet(secretPath, value) {
320
+ try {
321
+ await execa2("revvault", ["set", secretPath], {
322
+ input: value,
323
+ stdio: ["pipe", "pipe", "pipe"]
324
+ });
325
+ return true;
326
+ } catch {
327
+ return false;
328
+ }
329
+ }
330
+ async function runAuthStatusCommand(options) {
331
+ const envToken = await getTokenFromEnv();
332
+ const npmrcToken = await getTokenFromNpmrc();
333
+ const hasVault = await hasRevvault();
334
+ const vaultValue = hasVault ? await revvaultGet(REVVAULT_NPM_PATH) : void 0;
335
+ const paths = getNpmrcPaths();
336
+ const projectUsesEnvVar = paths.project ? await fileContains(paths.project, NPM_TOKEN_INTERPOLATION) : false;
337
+ const effectiveToken = envToken || npmrcToken;
338
+ let npmUser;
339
+ try {
340
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
341
+ npmUser = stdout.trim();
342
+ } catch {
343
+ npmUser = void 0;
344
+ }
345
+ if (options.json) {
346
+ writeJson({
347
+ authenticated: !!npmUser,
348
+ user: npmUser ?? null,
349
+ tokenSource: envToken ? "env" : npmrcToken ? "npmrc" : null,
350
+ tokenMasked: effectiveToken ? maskToken(effectiveToken) : null,
351
+ revvaultConfigured: !!vaultValue,
352
+ projectNpmrcUsesEnvVar: projectUsesEnvVar,
353
+ paths: {
354
+ userNpmrc: paths.user,
355
+ projectNpmrc: paths.project
356
+ }
357
+ });
358
+ return;
359
+ }
360
+ logger2.header("npm Authentication Status");
361
+ if (npmUser) {
362
+ logger2.success(`Authenticated as: ${npmUser}`);
363
+ } else {
364
+ logger2.error("Not authenticated \u2014 npm whoami failed");
365
+ }
366
+ write("\n");
367
+ logger2.info(
368
+ `Token source: ${envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (file)" : "none"}`
369
+ );
370
+ if (effectiveToken) {
371
+ logger2.info(`Token: ${maskToken(effectiveToken)}`);
372
+ }
373
+ write("\n");
374
+ logger2.info(
375
+ `RevVault (${REVVAULT_NPM_PATH}): ${vaultValue ? "configured" : hasVault ? "not set" : "revvault not installed"}`
376
+ );
377
+ logger2.info(`Project .npmrc uses NPM_TOKEN: ${projectUsesEnvVar ? "yes" : "no"}`);
378
+ if (!projectUsesEnvVar && paths.project) {
379
+ logger2.warn(
380
+ "Project .npmrc does not reference NPM_TOKEN \u2014 direnv/RevVault tokens won't be used for publishing"
381
+ );
382
+ }
383
+ if (!vaultValue && hasVault && effectiveToken) {
384
+ logger2.warn("Token is not stored in RevVault. Run: revealui auth set-token to persist it.");
385
+ }
386
+ }
387
+ async function runAuthSetTokenCommand(options) {
388
+ logger2.header("Set npm Publish Token");
389
+ const token = options.token || process.env.NPM_TOKEN;
390
+ if (!token) {
391
+ logger2.error("No token provided. Pass --token <value> or set NPM_TOKEN in your environment.");
392
+ logger2.info("Create a Granular Access Token at: https://www.npmjs.com/settings/<user>/tokens");
393
+ logger2.info(" Type: Granular Access Token");
394
+ logger2.info(" Packages: All packages (read and write)");
395
+ logger2.info(" This bypasses 2FA \u2014 no OTP needed for publish");
396
+ process.exitCode = 1;
397
+ return;
398
+ }
399
+ if (!token.startsWith("npm_")) {
400
+ logger2.warn('Token does not start with "npm_" \u2014 this may not be a valid npm token');
401
+ }
402
+ if (!options.skipVault) {
403
+ const hasVault = await hasRevvault();
404
+ if (hasVault) {
405
+ const stored = await revvaultSet(REVVAULT_NPM_PATH, `NPM_TOKEN=${token}`);
406
+ if (stored) {
407
+ logger2.success(`Stored in RevVault (${REVVAULT_NPM_PATH})`);
408
+ } else {
409
+ logger2.error("Failed to store in RevVault");
410
+ }
411
+ } else {
412
+ logger2.warn("RevVault not installed \u2014 skipping vault storage");
413
+ }
414
+ }
415
+ if (!options.skipNpmrc) {
416
+ const paths = getNpmrcPaths();
417
+ if (paths.project) {
418
+ const hasInterpolation = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
419
+ if (!hasInterpolation) {
420
+ try {
421
+ const content = await fs.readFile(paths.project, "utf-8");
422
+ const updatedContent = `${content.trimEnd()}
423
+ ${NPMRC_AUTH_LINE}
424
+ `;
425
+ await fs.writeFile(paths.project, updatedContent, "utf-8");
426
+ logger2.success(`Added NPM_TOKEN interpolation to ${paths.project}`);
427
+ } catch (err) {
428
+ logger2.error(`Failed to update ${paths.project}: ${err}`);
429
+ }
430
+ } else {
431
+ logger2.info("Project .npmrc already uses NPM_TOKEN");
432
+ }
433
+ }
434
+ }
435
+ const userRc = path.join(os.homedir(), ".npmrc");
436
+ try {
437
+ const content = await fs.readFile(userRc, "utf-8");
438
+ const filtered = content.split("\n").filter((line) => !line.includes("//registry.npmjs.org/:_authToken=")).join("\n");
439
+ if (filtered !== content) {
440
+ await fs.writeFile(userRc, filtered, "utf-8");
441
+ logger2.success("Removed hardcoded token from ~/.npmrc (now managed via env var)");
442
+ }
443
+ } catch {
444
+ }
445
+ write("\n");
446
+ logger2.info("Verifying...");
447
+ process.env.NPM_TOKEN = token;
448
+ try {
449
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
450
+ logger2.success(`Authenticated as: ${stdout.trim()}`);
451
+ } catch {
452
+ logger2.error("npm whoami failed \u2014 token may be invalid or expired");
453
+ process.exitCode = 1;
454
+ return;
455
+ }
456
+ write("\n");
457
+ logger2.success("Token configured. Run `direnv reload` to load it in all terminals.");
458
+ }
459
+ async function runAuthVerifyCommand(options) {
460
+ const checks = [];
461
+ let npmUser;
462
+ try {
463
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
464
+ npmUser = stdout.trim();
465
+ checks.push({ name: "npm whoami", pass: true, detail: npmUser });
466
+ } catch {
467
+ checks.push({
468
+ name: "npm whoami",
469
+ pass: false,
470
+ detail: "Not authenticated"
471
+ });
472
+ }
473
+ const envToken = await getTokenFromEnv();
474
+ const npmrcToken = await getTokenFromNpmrc();
475
+ const source = envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (hardcoded)" : "none";
476
+ checks.push({
477
+ name: "Token source",
478
+ pass: !!envToken,
479
+ detail: source + (npmrcToken && !envToken ? " \u2014 consider migrating to RevVault" : "")
480
+ });
481
+ const hasVault = await hasRevvault();
482
+ if (hasVault) {
483
+ const vaultValue = await revvaultGet(REVVAULT_NPM_PATH);
484
+ checks.push({
485
+ name: "RevVault",
486
+ pass: !!vaultValue,
487
+ detail: vaultValue ? `${REVVAULT_NPM_PATH} configured` : "Not stored in vault"
488
+ });
489
+ }
490
+ const paths = getNpmrcPaths();
491
+ if (paths.project) {
492
+ const usesEnvVar = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
493
+ checks.push({
494
+ name: ".npmrc NPM_TOKEN",
495
+ pass: usesEnvVar,
496
+ detail: usesEnvVar ? "Project .npmrc uses env var" : "Missing \u2014 direnv token won't reach npm"
497
+ });
498
+ }
499
+ const envrcPath = path.join(process.cwd(), ".envrc");
500
+ const loadsNpm = await fileContains(envrcPath, REVVAULT_NPM_PATH);
501
+ checks.push({
502
+ name: ".envrc loads npm",
503
+ pass: loadsNpm,
504
+ detail: loadsNpm ? "revealui/env/npm in .envrc" : "Missing \u2014 direnv won't export NPM_TOKEN"
505
+ });
506
+ const hardcodedToken = await getTokenFromNpmrc();
507
+ checks.push({
508
+ name: "~/.npmrc clean",
509
+ pass: !hardcodedToken,
510
+ detail: hardcodedToken ? `Hardcoded token found (${maskToken(hardcodedToken)})` : "No hardcoded tokens"
511
+ });
512
+ if (options.json) {
513
+ const allPass2 = checks.every((c) => c.pass);
514
+ writeJson({ pass: allPass2, checks });
515
+ return;
516
+ }
517
+ logger2.header("npm Auth Verification");
518
+ for (const check of checks) {
519
+ if (check.pass) {
520
+ logger2.success(`${check.name}: ${check.detail}`);
521
+ } else {
522
+ logger2.error(`${check.name}: ${check.detail}`);
523
+ }
524
+ }
525
+ const allPass = checks.every((c) => c.pass);
526
+ write("\n");
527
+ if (allPass) {
528
+ logger2.success("All checks passed \u2014 ready to publish");
529
+ } else {
530
+ logger2.warn("Some checks failed. Run `revealui auth set-token` to fix.");
531
+ process.exitCode = 1;
532
+ }
533
+ }
534
+
218
535
  // src/commands/create-flow.ts
219
536
  import { readFileSync } from "fs";
220
537
  import { homedir } from "os";
221
538
  import { join } from "path";
222
- import { createLogger as createLogger7 } from "@revealui/setup/utils";
539
+ import { createLogger as createLogger8 } from "@revealui/setup/utils";
223
540
  import { importSPKI, jwtVerify } from "jose";
224
541
 
225
542
  // src/prompts/database.ts
226
- import inquirer from "inquirer";
543
+ import { isCancel, select, text } from "@clack/prompts";
227
544
 
228
545
  // src/validators/credentials.ts
229
- import { createLogger as createLogger2 } from "@revealui/setup/utils";
230
- var logger2 = createLogger2({ prefix: "Validator" });
231
- async function validateStripeKey(key) {
546
+ import { createLogger as createLogger3 } from "@revealui/setup/utils";
547
+ var logger3 = createLogger3({ prefix: "Validator" });
548
+ function validateStripeKey(key) {
232
549
  if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
233
550
  return {
234
551
  valid: false,
@@ -237,7 +554,7 @@ async function validateStripeKey(key) {
237
554
  }
238
555
  return { valid: true };
239
556
  }
240
- async function validateNeonUrl(url) {
557
+ function validateNeonUrl(url) {
241
558
  try {
242
559
  const parsed = new URL(url);
243
560
  if (!parsed.protocol.startsWith("postgres")) {
@@ -254,7 +571,7 @@ async function validateNeonUrl(url) {
254
571
  };
255
572
  }
256
573
  }
257
- async function validateVercelToken(token) {
574
+ function validateVercelToken(token) {
258
575
  if (!token || token.length < 20) {
259
576
  return {
260
577
  valid: false,
@@ -263,11 +580,11 @@ async function validateVercelToken(token) {
263
580
  }
264
581
  return { valid: true };
265
582
  }
266
- async function validateSupabaseUrl(url) {
583
+ function validateSupabaseUrl(url) {
267
584
  try {
268
585
  const parsed = new URL(url);
269
586
  if (!parsed.hostname.includes("supabase")) {
270
- logger2.warn("URL does not appear to be a Supabase URL");
587
+ logger3.warn("URL does not appear to be a Supabase URL");
271
588
  }
272
589
  return { valid: true };
273
590
  } catch {
@@ -277,150 +594,152 @@ async function validateSupabaseUrl(url) {
277
594
  };
278
595
  }
279
596
  }
597
+ function validateNpmToken(token) {
598
+ if (!token.startsWith("npm_")) {
599
+ return {
600
+ valid: false,
601
+ message: "npm token must start with npm_"
602
+ };
603
+ }
604
+ if (token.length < 20) {
605
+ return {
606
+ valid: false,
607
+ message: "npm token appears invalid (too short)"
608
+ };
609
+ }
610
+ return { valid: true };
611
+ }
280
612
 
281
613
  // src/prompts/database.ts
282
614
  async function promptDatabaseConfig() {
283
- const { provider } = await inquirer.prompt([
284
- {
285
- type: "list",
286
- name: "provider",
287
- message: "Which database provider would you like to use?",
288
- choices: [
289
- {
290
- name: "NeonDB - Serverless PostgreSQL (recommended)",
291
- value: "neon"
292
- },
293
- {
294
- name: "Supabase - PostgreSQL with built-in features",
295
- value: "supabase"
296
- },
297
- {
298
- name: "Local PostgreSQL - Use existing local database",
299
- value: "local"
300
- },
301
- {
302
- name: "Skip - Configure later",
303
- value: "skip"
304
- }
305
- ],
306
- default: "neon"
307
- }
308
- ]);
615
+ const provider = await select({
616
+ message: "Which database provider would you like to use?",
617
+ options: [
618
+ { value: "neon", label: "NeonDB - Serverless PostgreSQL (recommended)" },
619
+ { value: "supabase", label: "Supabase - PostgreSQL with built-in features" },
620
+ { value: "local", label: "Local PostgreSQL - Use existing local database" },
621
+ { value: "skip", label: "Skip - Configure later" }
622
+ ],
623
+ initialValue: "neon"
624
+ });
625
+ if (isCancel(provider)) {
626
+ process.exit(0);
627
+ }
309
628
  if (provider === "skip") {
310
629
  return { provider: "skip" };
311
630
  }
312
631
  if (provider === "local") {
313
- const { postgresUrl: postgresUrl2 } = await inquirer.prompt([
314
- {
315
- type: "input",
316
- name: "postgresUrl",
317
- message: "Enter your PostgreSQL connection string:",
318
- default: "postgresql://postgres:postgres@localhost:5432/revealui",
319
- validate: async (input) => {
320
- const result = await validateNeonUrl(input);
321
- return result.valid ? true : result.message || "Invalid database URL";
322
- }
632
+ const postgresUrl2 = await text({
633
+ message: "Enter your PostgreSQL connection string:",
634
+ defaultValue: "postgresql://postgres:postgres@localhost:5432/revealui",
635
+ validate: (input) => {
636
+ if (!input) return void 0;
637
+ const result = validateNeonUrl(input);
638
+ return result.valid ? void 0 : result.message || "Invalid database URL";
323
639
  }
324
- ]);
640
+ });
641
+ if (isCancel(postgresUrl2)) {
642
+ process.exit(0);
643
+ }
325
644
  return { provider: "local", postgresUrl: postgresUrl2 };
326
645
  }
327
- const { postgresUrl } = await inquirer.prompt([
328
- {
329
- type: "input",
330
- name: "postgresUrl",
331
- message: `Enter your ${provider === "neon" ? "Neon" : "Supabase"} database connection string:`,
332
- validate: async (input) => {
333
- if (!input || input.trim() === "") {
334
- return "Database URL is required";
335
- }
336
- const result = await validateNeonUrl(input);
337
- return result.valid ? true : result.message || "Invalid database URL";
646
+ const label = provider === "neon" ? "Neon" : "Supabase";
647
+ const postgresUrl = await text({
648
+ message: `Enter your ${label} database connection string:`,
649
+ validate: (input) => {
650
+ if (!input || input.trim() === "") {
651
+ return "Database URL is required";
338
652
  }
653
+ const result = validateNeonUrl(input);
654
+ return result.valid ? void 0 : result.message || "Invalid database URL";
339
655
  }
340
- ]);
656
+ });
657
+ if (isCancel(postgresUrl)) {
658
+ process.exit(0);
659
+ }
341
660
  return { provider, postgresUrl };
342
661
  }
343
662
 
344
663
  // src/prompts/devenv.ts
345
- import inquirer2 from "inquirer";
664
+ import { confirm, isCancel as isCancel2 } from "@clack/prompts";
346
665
  async function promptDevEnvConfig() {
347
- const answers = await inquirer2.prompt([
348
- {
349
- type: "confirm",
350
- name: "createDevContainer",
351
- message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
352
- default: true
353
- },
354
- {
355
- type: "confirm",
356
- name: "createDevbox",
357
- message: "Create Devbox configuration for Nix-powered development?",
358
- default: true
359
- }
360
- ]);
361
- return answers;
666
+ const createDevContainer = await confirm({
667
+ message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
668
+ initialValue: true
669
+ });
670
+ if (isCancel2(createDevContainer)) {
671
+ process.exit(0);
672
+ }
673
+ const createDevbox = await confirm({
674
+ message: "Create Devbox configuration for Nix-powered development?",
675
+ initialValue: true
676
+ });
677
+ if (isCancel2(createDevbox)) {
678
+ process.exit(0);
679
+ }
680
+ return { createDevContainer, createDevbox };
362
681
  }
363
682
 
364
683
  // src/prompts/payments.ts
365
- import inquirer3 from "inquirer";
684
+ import { confirm as confirm2, isCancel as isCancel3, text as text2 } from "@clack/prompts";
366
685
  async function promptPaymentConfig() {
367
- const { enabled } = await inquirer3.prompt([
368
- {
369
- type: "confirm",
370
- name: "enabled",
371
- message: "Do you want to configure Stripe payments?",
372
- default: true
373
- }
374
- ]);
686
+ const enabled = await confirm2({
687
+ message: "Do you want to configure Stripe payments?",
688
+ initialValue: true
689
+ });
690
+ if (isCancel3(enabled)) {
691
+ process.exit(0);
692
+ }
375
693
  if (!enabled) {
376
694
  return { enabled: false };
377
695
  }
378
- const answers = await inquirer3.prompt([
379
- {
380
- type: "input",
381
- name: "stripeSecretKey",
382
- message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
383
- validate: async (input) => {
384
- if (!input || input.trim() === "") {
385
- return "Stripe secret key is required";
386
- }
387
- const result = await validateStripeKey(input);
388
- return result.valid ? true : result.message || "Invalid Stripe key";
696
+ const stripeSecretKey = await text2({
697
+ message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
698
+ validate: (input) => {
699
+ if (!input || input.trim() === "") {
700
+ return "Stripe secret key is required";
389
701
  }
390
- },
391
- {
392
- type: "input",
393
- name: "stripePublishableKey",
394
- message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
395
- validate: (input) => {
396
- if (!input || input.trim() === "") {
397
- return "Stripe publishable key is required";
398
- }
399
- if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
400
- return "Stripe publishable key must start with pk_test_ or pk_live_";
401
- }
402
- return true;
702
+ const result = validateStripeKey(input);
703
+ return result.valid ? void 0 : result.message || "Invalid Stripe key";
704
+ }
705
+ });
706
+ if (isCancel3(stripeSecretKey)) {
707
+ process.exit(0);
708
+ }
709
+ const stripePublishableKey = await text2({
710
+ message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
711
+ validate: (input) => {
712
+ if (!input || input.trim() === "") {
713
+ return "Stripe publishable key is required";
403
714
  }
404
- },
405
- {
406
- type: "input",
407
- name: "stripeWebhookSecret",
408
- message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
409
- default: ""
715
+ if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
716
+ return "Stripe publishable key must start with pk_test_ or pk_live_";
717
+ }
718
+ return void 0;
410
719
  }
411
- ]);
720
+ });
721
+ if (isCancel3(stripePublishableKey)) {
722
+ process.exit(0);
723
+ }
724
+ const stripeWebhookSecret = await text2({
725
+ message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
726
+ defaultValue: ""
727
+ });
728
+ if (isCancel3(stripeWebhookSecret)) {
729
+ process.exit(0);
730
+ }
412
731
  return {
413
732
  enabled: true,
414
- stripeSecretKey: answers.stripeSecretKey,
415
- stripePublishableKey: answers.stripePublishableKey,
416
- stripeWebhookSecret: answers.stripeWebhookSecret || void 0
733
+ stripeSecretKey,
734
+ stripePublishableKey,
735
+ stripeWebhookSecret: stripeWebhookSecret || void 0
417
736
  };
418
737
  }
419
738
 
420
739
  // src/prompts/project.ts
421
- import fs from "fs";
422
- import path from "path";
423
- import inquirer4 from "inquirer";
740
+ import fs2 from "fs";
741
+ import path2 from "path";
742
+ import { isCancel as isCancel4, select as select2, text as text3 } from "@clack/prompts";
424
743
  var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
425
744
  async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
426
745
  let projectName;
@@ -429,25 +748,31 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
429
748
  } else if (nonInteractive) {
430
749
  projectName = "my-revealui-project";
431
750
  } else {
432
- const answers = await inquirer4.prompt([
433
- {
434
- type: "input",
435
- name: "projectName",
436
- message: "What is your project name?",
437
- default: "my-revealui-project",
438
- validate: (input) => {
439
- if (!/^[a-z0-9-]+$/.test(input)) {
440
- return "Project name must contain only lowercase letters, numbers, and hyphens";
441
- }
442
- const projectPath = path.resolve(process.cwd(), input);
443
- if (fs.existsSync(projectPath)) {
444
- return `Directory "${input}" already exists`;
445
- }
446
- return true;
751
+ const name = await text3({
752
+ message: "What is your project name?",
753
+ defaultValue: "my-revealui-project",
754
+ validate: (input) => {
755
+ if (!input) return void 0;
756
+ const isValid = input.split("").every((ch) => {
757
+ const code = ch.charCodeAt(0);
758
+ return code >= 97 && code <= 122 || // a-z
759
+ code >= 48 && code <= 57 || // 0-9
760
+ code === 45;
761
+ });
762
+ if (!isValid) {
763
+ return "Project name must contain only lowercase letters, numbers, and hyphens";
764
+ }
765
+ const projectPath = path2.resolve(process.cwd(), input);
766
+ if (fs2.existsSync(projectPath)) {
767
+ return `Directory "${input}" already exists`;
447
768
  }
769
+ return void 0;
448
770
  }
449
- ]);
450
- projectName = answers.projectName;
771
+ });
772
+ if (isCancel4(name)) {
773
+ process.exit(0);
774
+ }
775
+ projectName = name;
451
776
  }
452
777
  let template;
453
778
  if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
@@ -455,118 +780,106 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
455
780
  } else if (nonInteractive) {
456
781
  template = "basic-blog";
457
782
  } else {
458
- const answers = await inquirer4.prompt([
459
- {
460
- type: "list",
461
- name: "template",
462
- message: "Which template would you like to use?",
463
- choices: [
464
- { name: "Basic Blog - A simple blog with posts and pages", value: "basic-blog" },
465
- { name: "E-commerce - Product catalog with checkout", value: "e-commerce" },
466
- { name: "Portfolio - Personal portfolio site", value: "portfolio" }
467
- ],
468
- default: "basic-blog"
469
- }
470
- ]);
471
- template = answers.template;
783
+ const selected = await select2({
784
+ message: "Which template would you like to use?",
785
+ options: [
786
+ { value: "basic-blog", label: "Basic Blog - A simple blog with posts and pages" },
787
+ { value: "e-commerce", label: "E-commerce - Product catalog with checkout" },
788
+ { value: "portfolio", label: "Portfolio - Personal portfolio site" }
789
+ ],
790
+ initialValue: "basic-blog"
791
+ });
792
+ if (isCancel4(selected)) {
793
+ process.exit(0);
794
+ }
795
+ template = selected;
472
796
  }
473
797
  return {
474
798
  projectName,
475
- projectPath: path.resolve(process.cwd(), projectName),
799
+ projectPath: path2.resolve(process.cwd(), projectName),
476
800
  template
477
801
  };
478
802
  }
479
803
 
480
804
  // src/prompts/storage.ts
481
- import inquirer5 from "inquirer";
805
+ import { isCancel as isCancel5, select as select3, text as text4 } from "@clack/prompts";
482
806
  async function promptStorageConfig() {
483
- const { provider } = await inquirer5.prompt([
484
- {
485
- type: "list",
486
- name: "provider",
487
- message: "Which storage provider would you like to use?",
488
- choices: [
489
- {
490
- name: "Vercel Blob - Simple object storage (recommended)",
491
- value: "vercel-blob"
492
- },
493
- {
494
- name: "Supabase Storage - Integrated with Supabase",
495
- value: "supabase"
496
- },
497
- {
498
- name: "Skip - Configure later",
499
- value: "skip"
500
- }
501
- ],
502
- default: "vercel-blob"
503
- }
504
- ]);
807
+ const provider = await select3({
808
+ message: "Which storage provider would you like to use?",
809
+ options: [
810
+ { value: "vercel-blob", label: "Vercel Blob - Simple object storage (recommended)" },
811
+ { value: "supabase", label: "Supabase Storage - Integrated with Supabase" },
812
+ { value: "skip", label: "Skip - Configure later" }
813
+ ],
814
+ initialValue: "vercel-blob"
815
+ });
816
+ if (isCancel5(provider)) {
817
+ process.exit(0);
818
+ }
505
819
  if (provider === "skip") {
506
820
  return { provider: "skip" };
507
821
  }
508
822
  if (provider === "vercel-blob") {
509
- const { blobToken } = await inquirer5.prompt([
510
- {
511
- type: "input",
512
- name: "blobToken",
513
- message: "Enter your Vercel Blob read/write token:",
514
- validate: async (input) => {
515
- if (!input || input.trim() === "") {
516
- return "Blob token is required";
517
- }
518
- const result = await validateVercelToken(input);
519
- return result.valid ? true : result.message || "Invalid token";
823
+ const blobToken = await text4({
824
+ message: "Enter your Vercel Blob read/write token:",
825
+ validate: (input) => {
826
+ if (!input || input.trim() === "") {
827
+ return "Blob token is required";
520
828
  }
829
+ const result = validateVercelToken(input);
830
+ return result.valid ? void 0 : result.message || "Invalid token";
521
831
  }
522
- ]);
832
+ });
833
+ if (isCancel5(blobToken)) {
834
+ process.exit(0);
835
+ }
523
836
  return { provider: "vercel-blob", blobToken };
524
837
  }
525
- const answers = await inquirer5.prompt([
526
- {
527
- type: "input",
528
- name: "supabaseUrl",
529
- message: "Enter your Supabase project URL:",
530
- validate: async (input) => {
531
- if (!input || input.trim() === "") {
532
- return "Supabase URL is required";
533
- }
534
- const result = await validateSupabaseUrl(input);
535
- return result.valid ? true : result.message || "Invalid URL";
838
+ const supabaseUrl = await text4({
839
+ message: "Enter your Supabase project URL:",
840
+ validate: (input) => {
841
+ if (!input || input.trim() === "") {
842
+ return "Supabase URL is required";
536
843
  }
537
- },
538
- {
539
- type: "input",
540
- name: "supabasePublishableKey",
541
- message: "Enter your Supabase publishable key (sb_publishable_...):",
542
- validate: (input) => {
543
- if (!input || input.trim() === "") {
544
- return "Supabase publishable key is required";
545
- }
546
- return true;
844
+ const result = validateSupabaseUrl(input);
845
+ return result.valid ? void 0 : result.message || "Invalid URL";
846
+ }
847
+ });
848
+ if (isCancel5(supabaseUrl)) {
849
+ process.exit(0);
850
+ }
851
+ const supabasePublishableKey = await text4({
852
+ message: "Enter your Supabase publishable key (sb_publishable_...):",
853
+ validate: (input) => {
854
+ if (!input || input.trim() === "") {
855
+ return "Supabase publishable key is required";
547
856
  }
857
+ return void 0;
548
858
  }
549
- ]);
859
+ });
860
+ if (isCancel5(supabasePublishableKey)) {
861
+ process.exit(0);
862
+ }
550
863
  return {
551
864
  provider: "supabase",
552
- supabaseUrl: answers.supabaseUrl,
553
- supabasePublishableKey: answers.supabasePublishableKey
865
+ supabaseUrl,
866
+ supabasePublishableKey
554
867
  };
555
868
  }
556
869
 
557
870
  // src/validators/node-version.ts
558
- import { createLogger as createLogger3 } from "@revealui/setup/utils";
559
- var logger3 = createLogger3({ prefix: "Setup" });
871
+ import { createLogger as createLogger4 } from "@revealui/setup/utils";
872
+ var logger4 = createLogger4({ prefix: "Setup" });
560
873
  var REQUIRED_NODE_VERSION = "24.13.0";
561
874
  function validateNodeVersion() {
562
875
  const currentVersion = process.version.slice(1);
563
876
  const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
564
877
  const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
565
878
  if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
566
- logger3.error(
879
+ logger4.error(
567
880
  `Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
568
881
  );
569
- logger3.info("Please upgrade Node.js: https://nodejs.org/");
882
+ logger4.info("Please upgrade Node.js: https://nodejs.org/");
570
883
  return false;
571
884
  }
572
885
  return true;
@@ -575,15 +888,15 @@ function validateNodeVersion() {
575
888
  // src/commands/create.ts
576
889
  import crypto from "crypto";
577
890
  import { existsSync } from "fs";
578
- import fs5 from "fs/promises";
579
- import path5 from "path";
891
+ import fs6 from "fs/promises";
892
+ import path6 from "path";
580
893
  import { fileURLToPath } from "url";
581
- import { createLogger as createLogger6 } from "@revealui/setup/utils";
894
+ import { createLogger as createLogger7 } from "@revealui/setup/utils";
582
895
  import ora2 from "ora";
583
896
 
584
897
  // src/generators/devbox.ts
585
- import fs2 from "fs/promises";
586
- import path2 from "path";
898
+ import fs3 from "fs/promises";
899
+ import path3 from "path";
587
900
  async function generateDevbox(projectPath) {
588
901
  const devboxConfig = {
589
902
  packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
@@ -603,19 +916,19 @@ async function generateDevbox(projectPath) {
603
916
  NODE_ENV: "development"
604
917
  }
605
918
  };
606
- await fs2.writeFile(
607
- path2.join(projectPath, "devbox.json"),
919
+ await fs3.writeFile(
920
+ path3.join(projectPath, "devbox.json"),
608
921
  JSON.stringify(devboxConfig, null, 2),
609
922
  "utf-8"
610
923
  );
611
924
  }
612
925
 
613
926
  // src/generators/devcontainer.ts
614
- import fs3 from "fs/promises";
615
- import path3 from "path";
927
+ import fs4 from "fs/promises";
928
+ import path4 from "path";
616
929
  async function generateDevContainer(projectPath) {
617
- const devcontainerDir = path3.join(projectPath, ".devcontainer");
618
- await fs3.mkdir(devcontainerDir, { recursive: true });
930
+ const devcontainerDir = path4.join(projectPath, ".devcontainer");
931
+ await fs4.mkdir(devcontainerDir, { recursive: true });
619
932
  const devcontainerConfig = {
620
933
  name: "RevealUI Development",
621
934
  image: "mcr.microsoft.com/devcontainers/typescript-node:24",
@@ -662,8 +975,8 @@ async function generateDevContainer(projectPath) {
662
975
  },
663
976
  remoteUser: "node"
664
977
  };
665
- await fs3.writeFile(
666
- path3.join(devcontainerDir, "devcontainer.json"),
978
+ await fs4.writeFile(
979
+ path4.join(devcontainerDir, "devcontainer.json"),
667
980
  JSON.stringify(devcontainerConfig, null, 2),
668
981
  "utf-8"
669
982
  );
@@ -692,7 +1005,7 @@ services:
692
1005
  volumes:
693
1006
  postgres-data:
694
1007
  `;
695
- await fs3.writeFile(path3.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
1008
+ await fs4.writeFile(path4.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
696
1009
  const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
697
1010
 
698
1011
  # Install additional tools
@@ -702,7 +1015,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
702
1015
  # Enable pnpm
703
1016
  RUN corepack enable
704
1017
  `;
705
- await fs3.writeFile(path3.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
1018
+ await fs4.writeFile(path4.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
706
1019
  const readme = `# Dev Container Setup
707
1020
 
708
1021
  This directory contains the Dev Container configuration for RevealUI.
@@ -744,12 +1057,12 @@ For GitHub Codespaces, set secrets in your repository settings.
744
1057
  - 4000: CMS
745
1058
  - 5432: PostgreSQL database
746
1059
  `;
747
- await fs3.writeFile(path3.join(devcontainerDir, "README.md"), readme, "utf-8");
1060
+ await fs4.writeFile(path4.join(devcontainerDir, "README.md"), readme, "utf-8");
748
1061
  }
749
1062
 
750
1063
  // src/generators/readme.ts
751
- import fs4 from "fs/promises";
752
- import path4 from "path";
1064
+ import fs5 from "fs/promises";
1065
+ import path5 from "path";
753
1066
  async function generateReadme(projectPath, projectConfig) {
754
1067
  const readme = `# ${projectConfig.projectName}
755
1068
 
@@ -821,31 +1134,31 @@ This project was created using the **${projectConfig.template}** template.
821
1134
 
822
1135
  MIT
823
1136
  `;
824
- await fs4.writeFile(path4.join(projectPath, "README.md"), readme, "utf-8");
1137
+ await fs5.writeFile(path5.join(projectPath, "README.md"), readme, "utf-8");
825
1138
  }
826
1139
 
827
1140
  // src/installers/dependencies.ts
828
- import { createLogger as createLogger4 } from "@revealui/setup/utils";
829
- import { execa } from "execa";
1141
+ import { createLogger as createLogger5 } from "@revealui/setup/utils";
1142
+ import { execa as execa3 } from "execa";
830
1143
  import ora from "ora";
831
- var logger4 = createLogger4({ prefix: "Install" });
1144
+ var logger5 = createLogger5({ prefix: "Install" });
832
1145
  async function installDependencies(projectPath) {
833
1146
  const spinner = ora("Installing dependencies with pnpm...").start();
834
1147
  try {
835
- await execa("pnpm", ["install"], {
1148
+ await execa3("pnpm", ["install"], {
836
1149
  cwd: projectPath,
837
1150
  stdio: "pipe"
838
1151
  });
839
1152
  spinner.succeed("Dependencies installed successfully");
840
1153
  } catch (error) {
841
1154
  spinner.fail("Failed to install dependencies");
842
- logger4.error('Please run "pnpm install" manually');
1155
+ logger5.error('Please run "pnpm install" manually');
843
1156
  throw error;
844
1157
  }
845
1158
  }
846
1159
  async function isPnpmInstalled() {
847
1160
  try {
848
- await execa("pnpm", ["--version"]);
1161
+ await execa3("pnpm", ["--version"]);
849
1162
  return true;
850
1163
  } catch {
851
1164
  return false;
@@ -853,31 +1166,31 @@ async function isPnpmInstalled() {
853
1166
  }
854
1167
 
855
1168
  // src/utils/git.ts
856
- import { createLogger as createLogger5 } from "@revealui/setup/utils";
857
- import { execa as execa2 } from "execa";
858
- var logger5 = createLogger5({ prefix: "Git" });
1169
+ import { createLogger as createLogger6 } from "@revealui/setup/utils";
1170
+ import { execa as execa4 } from "execa";
1171
+ var logger6 = createLogger6({ prefix: "Git" });
859
1172
  async function initializeGitRepo(projectPath) {
860
1173
  try {
861
- await execa2("git", ["init"], { cwd: projectPath });
862
- logger5.success("Initialized git repository");
1174
+ await execa4("git", ["init"], { cwd: projectPath });
1175
+ logger6.success("Initialized git repository");
863
1176
  } catch (error) {
864
- logger5.warn("Failed to initialize git repository");
1177
+ logger6.warn("Failed to initialize git repository");
865
1178
  throw error;
866
1179
  }
867
1180
  }
868
1181
  async function createInitialCommit(projectPath) {
869
1182
  try {
870
- await execa2("git", ["add", "."], { cwd: projectPath });
871
- await execa2("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
872
- logger5.success("Created initial commit");
1183
+ await execa4("git", ["add", "."], { cwd: projectPath });
1184
+ await execa4("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
1185
+ logger6.success("Created initial commit");
873
1186
  } catch (error) {
874
- logger5.warn("Failed to create initial commit");
1187
+ logger6.warn("Failed to create initial commit");
875
1188
  throw error;
876
1189
  }
877
1190
  }
878
1191
  async function isGitInstalled() {
879
1192
  try {
880
- await execa2("git", ["--version"]);
1193
+ await execa4("git", ["--version"]);
881
1194
  return true;
882
1195
  } catch {
883
1196
  return false;
@@ -885,10 +1198,10 @@ async function isGitInstalled() {
885
1198
  }
886
1199
 
887
1200
  // src/commands/create.ts
888
- var logger6 = createLogger6({ prefix: "Create" });
1201
+ var logger7 = createLogger7({ prefix: "Create" });
889
1202
  var __filename2 = fileURLToPath(import.meta.url);
890
- var __dirname2 = path5.dirname(__filename2);
891
- var TEMPLATES_DIR = existsSync(path5.resolve(__dirname2, "../../templates")) ? path5.resolve(__dirname2, "../../templates") : path5.resolve(__dirname2, "../templates");
1203
+ var __dirname2 = path6.dirname(__filename2);
1204
+ var TEMPLATES_DIR = existsSync(path6.resolve(__dirname2, "../../templates")) ? path6.resolve(__dirname2, "../../templates") : path6.resolve(__dirname2, "../templates");
892
1205
  function buildEnvLocal(cfg) {
893
1206
  const lines = [
894
1207
  "# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
@@ -934,16 +1247,16 @@ function generateSecret() {
934
1247
  }
935
1248
  async function listAvailableTemplates() {
936
1249
  try {
937
- const entries = await fs5.readdir(TEMPLATES_DIR, { withFileTypes: true });
1250
+ const entries = await fs6.readdir(TEMPLATES_DIR, { withFileTypes: true });
938
1251
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
939
1252
  } catch {
940
1253
  return [];
941
1254
  }
942
1255
  }
943
1256
  async function copyTemplate(templateName, targetPath) {
944
- const templatePath = path5.join(TEMPLATES_DIR, templateName);
1257
+ const templatePath = path6.join(TEMPLATES_DIR, templateName);
945
1258
  try {
946
- await fs5.access(templatePath);
1259
+ await fs6.access(templatePath);
947
1260
  } catch {
948
1261
  const available = await listAvailableTemplates();
949
1262
  const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
@@ -952,16 +1265,16 @@ async function copyTemplate(templateName, targetPath) {
952
1265
  await copyDir(templatePath, targetPath);
953
1266
  }
954
1267
  async function copyDir(src, dest) {
955
- await fs5.mkdir(dest, { recursive: true });
956
- const entries = await fs5.readdir(src, { withFileTypes: true });
1268
+ await fs6.mkdir(dest, { recursive: true });
1269
+ const entries = await fs6.readdir(src, { withFileTypes: true });
957
1270
  for (const entry of entries) {
958
- const srcPath = path5.join(src, entry.name);
1271
+ const srcPath = path6.join(src, entry.name);
959
1272
  const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
960
- const destPath = path5.join(dest, destName);
1273
+ const destPath = path6.join(dest, destName);
961
1274
  if (entry.isDirectory()) {
962
1275
  await copyDir(srcPath, destPath);
963
1276
  } else {
964
- await fs5.copyFile(srcPath, destPath);
1277
+ await fs6.copyFile(srcPath, destPath);
965
1278
  }
966
1279
  }
967
1280
  }
@@ -974,7 +1287,17 @@ function resolveTemplateName(template) {
974
1287
  return map[template] ?? "starter";
975
1288
  }
976
1289
  async function pullContentRules(projectPath) {
977
- const baseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
1290
+ const rawBaseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
1291
+ let baseUrl;
1292
+ try {
1293
+ const parsed = new URL(rawBaseUrl);
1294
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
1295
+ return;
1296
+ }
1297
+ baseUrl = rawBaseUrl;
1298
+ } catch {
1299
+ return;
1300
+ }
978
1301
  const spinner = ora2("Pulling AI coding rules...").start();
979
1302
  try {
980
1303
  const manifestRes = await fetch(`${baseUrl}/manifest.json`, {
@@ -988,18 +1311,27 @@ async function pullContentRules(projectPath) {
988
1311
  const generatorId = "claude-code";
989
1312
  const ossDefs = manifest.definitions.filter((d) => d.tier === "oss");
990
1313
  let written = 0;
1314
+ const allowedExtensions = /* @__PURE__ */ new Set([".md", ".json", ".txt", ".yaml", ".yml", ".ts", ".js"]);
1315
+ const maxFileSize = 1048576;
991
1316
  for (const def of ossDefs) {
992
1317
  const paths = def.generatorPaths[generatorId] ?? [];
993
1318
  for (const relPath of paths) {
994
1319
  try {
1320
+ if (relPath.includes("..") || relPath.startsWith("/")) continue;
1321
+ const ext = path6.extname(relPath).toLowerCase();
1322
+ if (!allowedExtensions.has(ext)) continue;
1323
+ const absolutePath = path6.resolve(projectPath, relPath);
1324
+ if (!absolutePath.startsWith(path6.resolve(projectPath))) continue;
995
1325
  const fileRes = await fetch(`${baseUrl}/generators/${generatorId}/${relPath}`, {
996
1326
  signal: AbortSignal.timeout(5e3)
997
1327
  });
998
1328
  if (!fileRes.ok) continue;
1329
+ const contentLength = fileRes.headers.get("content-length");
1330
+ if (contentLength && Number.parseInt(contentLength, 10) > maxFileSize) continue;
999
1331
  const content = await fileRes.text();
1000
- const absolutePath = path5.join(projectPath, relPath);
1001
- await fs5.mkdir(path5.dirname(absolutePath), { recursive: true });
1002
- await fs5.writeFile(absolutePath, content, "utf-8");
1332
+ if (content.length > maxFileSize) continue;
1333
+ await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
1334
+ await fs6.writeFile(absolutePath, content, "utf-8");
1003
1335
  written++;
1004
1336
  } catch {
1005
1337
  }
@@ -1025,58 +1357,58 @@ async function createProject(cfg) {
1025
1357
  spinner.fail("Failed to copy template files");
1026
1358
  throw err;
1027
1359
  }
1028
- const pkgJsonPath = path5.join(projectPath, "package.json");
1360
+ const pkgJsonPath = path6.join(projectPath, "package.json");
1029
1361
  try {
1030
- const raw = await fs5.readFile(pkgJsonPath, "utf-8");
1031
- await fs5.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
1362
+ const raw = await fs6.readFile(pkgJsonPath, "utf-8");
1363
+ await fs6.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
1032
1364
  } catch {
1033
1365
  }
1034
1366
  const envSpinner = ora2("Writing .env.local...").start();
1035
1367
  try {
1036
- await fs5.writeFile(path5.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
1368
+ await fs6.writeFile(path6.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
1037
1369
  envSpinner.succeed(".env.local written");
1038
1370
  } catch (err) {
1039
1371
  envSpinner.fail("Failed to write .env.local");
1040
1372
  throw err;
1041
1373
  }
1042
1374
  await generateReadme(projectPath, project);
1043
- logger6.success("README.md generated");
1375
+ logger7.success("README.md generated");
1044
1376
  if (cfg.devenv.createDevContainer) {
1045
1377
  await generateDevContainer(projectPath);
1046
- logger6.success(".devcontainer/ generated");
1378
+ logger7.success(".devcontainer/ generated");
1047
1379
  }
1048
1380
  if (cfg.devenv.createDevbox) {
1049
1381
  await generateDevbox(projectPath);
1050
- logger6.success("devbox.json generated");
1382
+ logger7.success("devbox.json generated");
1051
1383
  }
1052
1384
  await pullContentRules(projectPath);
1053
1385
  if (!skipInstall) {
1054
1386
  const pnpmOk = await isPnpmInstalled();
1055
1387
  if (!pnpmOk) {
1056
- logger6.warn(
1388
+ logger7.warn(
1057
1389
  "pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
1058
1390
  );
1059
1391
  } else {
1060
1392
  await installDependencies(projectPath);
1061
1393
  }
1062
1394
  } else {
1063
- logger6.info("Skipping dependency installation (--skip-install)");
1395
+ logger7.info("Skipping dependency installation (--skip-install)");
1064
1396
  }
1065
1397
  if (!skipGit) {
1066
1398
  const gitOk = await isGitInstalled();
1067
1399
  if (!gitOk) {
1068
- logger6.warn("git not found \u2014 skipping repository initialisation.");
1400
+ logger7.warn("git not found \u2014 skipping repository initialisation.");
1069
1401
  } else {
1070
1402
  await initializeGitRepo(projectPath);
1071
1403
  await createInitialCommit(projectPath);
1072
1404
  }
1073
1405
  } else {
1074
- logger6.info("Skipping git initialisation (--skip-git)");
1406
+ logger7.info("Skipping git initialisation (--skip-git)");
1075
1407
  }
1076
1408
  }
1077
1409
 
1078
1410
  // src/commands/create-flow.ts
1079
- var logger7 = createLogger7({ prefix: "@revealui/cli" });
1411
+ var logger8 = createLogger8({ prefix: "@revealui/cli" });
1080
1412
  var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
1081
1413
  async function checkProLicense() {
1082
1414
  let key = process.env.REVEALUI_LICENSE_KEY;
@@ -1114,108 +1446,113 @@ async function checkProLicense() {
1114
1446
  }
1115
1447
  }
1116
1448
  function printBanner() {
1117
- logger7.divider();
1118
- logger7.info(" RevealUI \u2014 B.O.S.S.");
1119
- logger7.info(" Build your business, not your boilerplate.");
1120
- logger7.divider();
1121
- logger7.info("");
1449
+ logger8.divider();
1450
+ logger8.info(" RevealUI \u2014 The Agentic Business Runtime");
1451
+ logger8.info(" Build your business, not your boilerplate.");
1452
+ logger8.divider();
1453
+ logger8.info("");
1122
1454
  }
1123
1455
  function printPostCreateSummary(projectName) {
1124
- logger7.divider();
1125
- logger7.success("Your RevealUI project is ready!");
1126
- logger7.info("");
1127
- logger7.info(" Quick start:");
1128
- logger7.info(` cd ${projectName}`);
1129
- logger7.info(" pnpm install");
1130
- logger7.info(" pnpm dev");
1131
- logger7.info("");
1132
- logger7.info(" What was created:");
1133
- logger7.info(` ./${projectName}/ \u2014 project root`);
1134
- logger7.info(` ./${projectName}/.env.local \u2014 environment variables (edit before pnpm dev)`);
1135
- logger7.info(` ./${projectName}/README.md \u2014 getting started guide`);
1136
- logger7.info("");
1137
- logger7.info(" Helpful links:");
1138
- logger7.info(" Docs: https://docs.revealui.com");
1139
- logger7.info(" GitHub: https://github.com/RevealUIStudio/revealui");
1140
- logger7.info(" Support: support@revealui.com");
1141
- logger7.divider();
1456
+ logger8.divider();
1457
+ logger8.success("Your RevealUI project is ready!");
1458
+ logger8.info("");
1459
+ logger8.info(" Quick start:");
1460
+ logger8.info(` cd ${projectName}`);
1461
+ logger8.info(" pnpm install");
1462
+ logger8.info(" pnpm dev");
1463
+ logger8.info("");
1464
+ logger8.info(" What was created:");
1465
+ logger8.info(` ./${projectName}/ \u2014 project root`);
1466
+ logger8.info(` ./${projectName}/.env.local \u2014 environment variables (edit before pnpm dev)`);
1467
+ logger8.info(` ./${projectName}/README.md \u2014 getting started guide`);
1468
+ logger8.info("");
1469
+ logger8.info(" RevealUI ecosystem:");
1470
+ logger8.info(" Studio: Desktop companion for managing your dev environment");
1471
+ logger8.info(" Terminal: TUI client \u2014 run `revealui terminal install`");
1472
+ logger8.info(" CMS: Admin dashboard at your-domain.com/admin");
1473
+ logger8.info("");
1474
+ logger8.info(" Helpful links:");
1475
+ logger8.info(" Docs: https://docs.revealui.com");
1476
+ logger8.info(" GitHub: https://github.com/RevealUIStudio/revealui");
1477
+ logger8.info(" Support: support@revealui.com");
1478
+ logger8.divider();
1142
1479
  }
1143
1480
  function formatCreateError(err) {
1144
1481
  const message = err instanceof Error ? err.message : String(err);
1145
1482
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1146
- logger7.error("Project creation failed.");
1147
- logger7.info("");
1483
+ logger8.error("Project creation failed.");
1484
+ logger8.info("");
1148
1485
  if (code === "EACCES") {
1149
- logger7.info(" Permission denied. Try:");
1150
- logger7.info(" - Running from a directory you own");
1151
- logger7.info(" - Checking folder permissions with `ls -la`");
1486
+ logger8.info(" Permission denied. Try:");
1487
+ logger8.info(" - Running from a directory you own");
1488
+ logger8.info(" - Checking folder permissions with `ls -la`");
1152
1489
  } else if (code === "ENOENT") {
1153
- logger7.info(" A required file or directory was not found.");
1154
- logger7.info(" - Check that you are in the correct directory");
1155
- logger7.info(" - Try running `revealui doctor` to diagnose your environment");
1490
+ logger8.info(" A required file or directory was not found.");
1491
+ logger8.info(" - Check that you are in the correct directory");
1492
+ logger8.info(" - Try running `revealui doctor` to diagnose your environment");
1156
1493
  } else if (code === "ENOSPC") {
1157
- logger7.info(" Disk full. Free up space and try again.");
1494
+ logger8.info(" Disk full. Free up space and try again.");
1158
1495
  } else if (message.includes("fetch") || message.includes("ENOTFOUND") || message.includes("ETIMEDOUT")) {
1159
- logger7.info(" Network error. Check your internet connection and try again.");
1496
+ logger8.info(" Network error. Check your internet connection and try again.");
1160
1497
  } else {
1161
- logger7.info(` ${message}`);
1498
+ logger8.info(` ${message}`);
1162
1499
  }
1163
- logger7.info("");
1164
- logger7.info(" Troubleshooting:");
1165
- logger7.info(" revealui doctor \u2014 diagnose your environment");
1166
- logger7.info(" https://docs.revealui.com/docs/TROUBLESHOOTING");
1167
- logger7.info(" support@revealui.com \u2014 we are here to help");
1168
- logger7.divider();
1500
+ logger8.info("");
1501
+ logger8.info(" Troubleshooting:");
1502
+ logger8.info(" revealui doctor \u2014 diagnose your environment");
1503
+ logger8.info(" https://docs.revealui.com/docs/TROUBLESHOOTING");
1504
+ logger8.info(" support@revealui.com \u2014 we are here to help");
1505
+ logger8.divider();
1169
1506
  }
1170
1507
  async function runCreateFlow(projectName, options) {
1171
1508
  printBanner();
1172
1509
  try {
1173
- logger7.info("[1/8] Validating Node.js version...");
1510
+ logger8.info("[1/8] Validating Node.js version...");
1174
1511
  if (!validateNodeVersion()) {
1175
1512
  process.exit(1);
1176
1513
  }
1177
- logger7.success(`Node.js version: ${process.version}`);
1178
- logger7.info("[2/8] Configure your project");
1514
+ logger8.success(`Node.js version: ${process.version}`);
1515
+ logger8.info("[2/8] Configure your project");
1179
1516
  const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
1180
- logger7.success(`Project: ${projectConfig.projectName}`);
1181
- logger7.success(`Template: ${projectConfig.template}`);
1517
+ logger8.success(`Project: ${projectConfig.projectName}`);
1518
+ logger8.success(`Template: ${projectConfig.template}`);
1182
1519
  if (PRO_TEMPLATES.has(projectConfig.template)) {
1183
1520
  if (!await checkProLicense()) {
1184
- logger7.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
1185
- logger7.info("Get Pro at https://revealui.com/pricing");
1186
- logger7.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
1521
+ logger8.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
1522
+ logger8.info("Get Pro at https://revealui.com/pricing");
1523
+ logger8.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
1187
1524
  process.exit(2);
1188
1525
  }
1189
- logger7.success("Pro license verified");
1526
+ logger8.success("Pro license verified");
1190
1527
  }
1191
1528
  const nonInteractive = options.yes === true;
1192
- logger7.info("[3/8] Configure database");
1529
+ logger8.info("[3/8] Configure database");
1193
1530
  const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
1194
1531
  if (databaseConfig.provider !== "skip") {
1195
- logger7.success(`Database: ${databaseConfig.provider}`);
1532
+ logger8.success(`Database: ${databaseConfig.provider}`);
1196
1533
  } else {
1197
- logger7.info("Database configuration skipped");
1534
+ logger8.info("Database configuration skipped");
1198
1535
  }
1199
- logger7.info("[4/8] Configure storage");
1536
+ logger8.info("[4/8] Configure storage");
1200
1537
  const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
1201
1538
  if (storageConfig.provider !== "skip") {
1202
- logger7.success(`Storage: ${storageConfig.provider}`);
1539
+ logger8.success(`Storage: ${storageConfig.provider}`);
1203
1540
  } else {
1204
- logger7.info("Storage configuration skipped");
1541
+ logger8.info("Storage configuration skipped");
1205
1542
  }
1206
- logger7.info("[5/8] Configure payments");
1543
+ logger8.info("[5/8] Configure payments");
1207
1544
  const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
1208
1545
  if (paymentConfig.enabled) {
1209
- logger7.success("Stripe configured");
1546
+ logger8.success("Stripe configured");
1210
1547
  } else {
1211
- logger7.info("Payments disabled");
1548
+ logger8.info("Payments disabled");
1212
1549
  }
1213
- logger7.info("[6/8] Configure development environment");
1550
+ logger8.info("[6/8] Configure development environment");
1214
1551
  const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
1215
- logger7.success(
1552
+ logger8.success(
1216
1553
  `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
1217
1554
  );
1218
- logger7.info("[7/8] Creating project...");
1555
+ logger8.info("[7/8] Creating project...");
1219
1556
  await createProject({
1220
1557
  project: projectConfig,
1221
1558
  database: databaseConfig,
@@ -1225,8 +1562,8 @@ async function runCreateFlow(projectName, options) {
1225
1562
  skipGit: options.skipGit,
1226
1563
  skipInstall: options.skipInstall
1227
1564
  });
1228
- logger7.success("Project created successfully");
1229
- logger7.info("[8/8] Done!");
1565
+ logger8.success("Project created successfully");
1566
+ logger8.info("[8/8] Done!");
1230
1567
  printPostCreateSummary(projectConfig.projectName);
1231
1568
  } catch (err) {
1232
1569
  formatCreateError(err);
@@ -1235,45 +1572,16 @@ async function runCreateFlow(projectName, options) {
1235
1572
  }
1236
1573
 
1237
1574
  // src/commands/db.ts
1238
- import fs8 from "fs/promises";
1239
- import path8 from "path";
1240
- import { createLogger as createLogger8 } from "@revealui/setup/utils";
1241
- import { execa as execa4 } from "execa";
1242
-
1243
- // src/utils/command.ts
1244
- import net from "net";
1245
- import { execa as execa3 } from "execa";
1246
- async function commandExists(command) {
1247
- try {
1248
- await execa3("bash", ["-lc", `command -v ${command}`], {
1249
- stdio: "pipe"
1250
- });
1251
- return true;
1252
- } catch {
1253
- return false;
1254
- }
1255
- }
1256
- async function isTcpReachable(host, port, timeoutMs = 1500) {
1257
- return await new Promise((resolve) => {
1258
- const socket = new net.Socket();
1259
- const finalize = (value) => {
1260
- socket.removeAllListeners();
1261
- socket.destroy();
1262
- resolve(value);
1263
- };
1264
- socket.setTimeout(timeoutMs);
1265
- socket.once("connect", () => finalize(true));
1266
- socket.once("timeout", () => finalize(false));
1267
- socket.once("error", () => finalize(false));
1268
- socket.connect(port, host);
1269
- });
1270
- }
1575
+ import fs9 from "fs/promises";
1576
+ import path9 from "path";
1577
+ import { createLogger as createLogger9 } from "@revealui/setup/utils";
1578
+ import { execa as execa5 } from "execa";
1271
1579
 
1272
1580
  // src/utils/db.ts
1273
- import fs6 from "fs/promises";
1274
- import path6 from "path";
1581
+ import fs7 from "fs/promises";
1582
+ import path7 from "path";
1275
1583
  function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
1276
- const pgdata = env.PGDATA || path6.join(cwd, ".pgdata");
1584
+ const pgdata = env.PGDATA || path7.join(cwd, ".pgdata");
1277
1585
  const pghost = env.PGHOST || pgdata;
1278
1586
  const pgdatabase = env.PGDATABASE || "postgres";
1279
1587
  const pguser = env.PGUSER || "postgres";
@@ -1320,27 +1628,27 @@ host all all ::1/128 trust
1320
1628
  `.trimStart();
1321
1629
  }
1322
1630
  async function writeLocalDbConfigs(pgdata) {
1323
- await fs6.appendFile(path6.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1324
- await fs6.writeFile(path6.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1631
+ await fs7.appendFile(path7.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1632
+ await fs7.writeFile(path7.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1325
1633
  }
1326
1634
 
1327
1635
  // src/utils/workspace.ts
1328
- import fs7 from "fs";
1329
- import path7 from "path";
1636
+ import fs8 from "fs";
1637
+ import path8 from "path";
1330
1638
  function findWorkspaceRoot(startDir = process.cwd()) {
1331
- let current = path7.resolve(startDir);
1639
+ let current = path8.resolve(startDir);
1332
1640
  while (true) {
1333
- const packageJsonPath = path7.join(current, "package.json");
1334
- if (fs7.existsSync(packageJsonPath)) {
1641
+ const packageJsonPath = path8.join(current, "package.json");
1642
+ if (fs8.existsSync(packageJsonPath)) {
1335
1643
  try {
1336
- const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
1644
+ const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
1337
1645
  if (pkg.name === "revealui" && pkg.private === true) {
1338
1646
  return current;
1339
1647
  }
1340
1648
  } catch {
1341
1649
  }
1342
1650
  }
1343
- const parent = path7.dirname(current);
1651
+ const parent = path8.dirname(current);
1344
1652
  if (parent === current) {
1345
1653
  return null;
1346
1654
  }
@@ -1349,7 +1657,7 @@ function findWorkspaceRoot(startDir = process.cwd()) {
1349
1657
  }
1350
1658
 
1351
1659
  // src/commands/db.ts
1352
- var logger8 = createLogger8({ prefix: "DB" });
1660
+ var logger9 = createLogger9({ prefix: "DB" });
1353
1661
  function isPgCtlNotRunningError(error) {
1354
1662
  return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
1355
1663
  }
@@ -1390,18 +1698,18 @@ async function runDbInitCommand(options = {}) {
1390
1698
  const pgdata = getPgDataOrThrow(env);
1391
1699
  await requireCommand("initdb");
1392
1700
  try {
1393
- await fs8.access(pgdata);
1701
+ await fs9.access(pgdata);
1394
1702
  if (!options.force) {
1395
1703
  throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
1396
1704
  }
1397
- await fs8.rm(pgdata, { recursive: true, force: true });
1705
+ await fs9.rm(pgdata, { recursive: true, force: true });
1398
1706
  } catch (error) {
1399
1707
  if (error instanceof Error && !error.message.includes("already initialized")) {
1400
1708
  } else if (error instanceof Error) {
1401
1709
  throw error;
1402
1710
  }
1403
1711
  }
1404
- await execa4(
1712
+ await execa5(
1405
1713
  "initdb",
1406
1714
  ["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
1407
1715
  {
@@ -1410,17 +1718,17 @@ async function runDbInitCommand(options = {}) {
1410
1718
  }
1411
1719
  );
1412
1720
  await writeLocalDbConfigs(pgdata);
1413
- logger8.success(`PostgreSQL initialized at ${pgdata}`);
1721
+ logger9.success(`PostgreSQL initialized at ${pgdata}`);
1414
1722
  }
1415
1723
  async function runDbStartCommand() {
1416
1724
  const root = getWorkspaceRootOrThrow();
1417
1725
  const env = buildDbEnv(root);
1418
1726
  const pgdata = getPgDataOrThrow(env);
1419
1727
  await requireCommand("pg_ctl");
1420
- await fs8.access(pgdata);
1421
- await execa4(
1728
+ await fs9.access(pgdata);
1729
+ await execa5(
1422
1730
  "pg_ctl",
1423
- ["start", "-D", pgdata, "-l", path8.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1731
+ ["start", "-D", pgdata, "-l", path9.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1424
1732
  {
1425
1733
  env,
1426
1734
  stdio: "inherit"
@@ -1432,7 +1740,7 @@ async function runDbStopCommand() {
1432
1740
  const env = buildDbEnv(root);
1433
1741
  const pgdata = getPgDataOrThrow(env);
1434
1742
  await requireCommand("pg_ctl");
1435
- await execa4("pg_ctl", ["stop", "-D", pgdata], {
1743
+ await execa5("pg_ctl", ["stop", "-D", pgdata], {
1436
1744
  env,
1437
1745
  stdio: "inherit"
1438
1746
  });
@@ -1443,13 +1751,13 @@ async function runDbStatusCommand() {
1443
1751
  const pgdata = getPgDataOrThrow(env);
1444
1752
  await requireCommand("pg_ctl");
1445
1753
  try {
1446
- await execa4("pg_ctl", ["status", "-D", pgdata], {
1754
+ await execa5("pg_ctl", ["status", "-D", pgdata], {
1447
1755
  env,
1448
1756
  stdio: "inherit"
1449
1757
  });
1450
1758
  } catch (error) {
1451
1759
  if (isPgCtlNotRunningError(error)) {
1452
- logger8.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1760
+ logger9.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1453
1761
  return;
1454
1762
  }
1455
1763
  throw error;
@@ -1464,17 +1772,17 @@ async function runDbResetCommand(options = {}) {
1464
1772
  const pgdata = getPgDataOrThrow(env);
1465
1773
  if (await commandExists("pg_ctl")) {
1466
1774
  try {
1467
- await execa4("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1775
+ await execa5("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1468
1776
  } catch {
1469
1777
  }
1470
1778
  }
1471
- await fs8.rm(pgdata, { recursive: true, force: true });
1779
+ await fs9.rm(pgdata, { recursive: true, force: true });
1472
1780
  await runDbInitCommand({ force: false });
1473
1781
  }
1474
1782
  async function runDbMigrateCommand() {
1475
1783
  const root = getWorkspaceRootOrThrow();
1476
1784
  const env = buildDbEnv(root);
1477
- await execa4("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1785
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1478
1786
  cwd: root,
1479
1787
  env,
1480
1788
  stdio: "inherit"
@@ -1485,7 +1793,7 @@ async function runDbCleanupCommand(options = {}) {
1485
1793
  const env = { ...process.env };
1486
1794
  if (options.dryRun) env.DRY_RUN = "true";
1487
1795
  if (options.tables) env.TABLES = options.tables;
1488
- await execa4("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1796
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1489
1797
  cwd: root,
1490
1798
  env,
1491
1799
  stdio: "inherit"
@@ -1493,8 +1801,8 @@ async function runDbCleanupCommand(options = {}) {
1493
1801
  }
1494
1802
 
1495
1803
  // src/commands/dev.ts
1496
- import { createLogger as createLogger11 } from "@revealui/setup/utils";
1497
- import { execa as execa6 } from "execa";
1804
+ import { createLogger as createLogger12 } from "@revealui/setup/utils";
1805
+ import { execa as execa7 } from "execa";
1498
1806
 
1499
1807
  // src/runtime/doctor.ts
1500
1808
  function getMcpDetail(env) {
@@ -1513,6 +1821,139 @@ function getMcpDetail(env) {
1513
1821
  detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
1514
1822
  };
1515
1823
  }
1824
+ var ENV_VAR_SPECS = [
1825
+ // Core
1826
+ {
1827
+ key: "REVEALUI_SECRET",
1828
+ label: "App secret",
1829
+ required: false
1830
+ },
1831
+ {
1832
+ key: "REVEALUI_ADMIN_EMAIL",
1833
+ label: "Admin email",
1834
+ required: false
1835
+ },
1836
+ // Database
1837
+ {
1838
+ key: "POSTGRES_URL",
1839
+ label: "PostgreSQL URL",
1840
+ required: true,
1841
+ validate: validateNeonUrl
1842
+ },
1843
+ // Stripe
1844
+ {
1845
+ key: "STRIPE_SECRET_KEY",
1846
+ label: "Stripe secret key",
1847
+ required: false,
1848
+ validate: validateStripeKey
1849
+ },
1850
+ {
1851
+ key: "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
1852
+ label: "Stripe publishable key",
1853
+ required: false,
1854
+ validate: (v) => ({
1855
+ valid: v.startsWith("pk_test_") || v.startsWith("pk_live_"),
1856
+ message: "Must start with pk_test_ or pk_live_"
1857
+ })
1858
+ },
1859
+ {
1860
+ key: "STRIPE_WEBHOOK_SECRET",
1861
+ label: "Stripe webhook secret",
1862
+ required: false,
1863
+ validate: (v) => ({
1864
+ valid: v.startsWith("whsec_"),
1865
+ message: "Must start with whsec_"
1866
+ })
1867
+ },
1868
+ // Supabase
1869
+ {
1870
+ key: "NEXT_PUBLIC_SUPABASE_URL",
1871
+ label: "Supabase URL",
1872
+ required: false,
1873
+ validate: validateSupabaseUrl
1874
+ },
1875
+ {
1876
+ key: "SUPABASE_SERVICE_ROLE_KEY",
1877
+ label: "Supabase service role key",
1878
+ required: false,
1879
+ validate: (v) => ({
1880
+ valid: v.startsWith("eyJ"),
1881
+ message: "Must be a JWT (starts with eyJ)"
1882
+ })
1883
+ },
1884
+ // Services
1885
+ {
1886
+ key: "RESEND_API_KEY",
1887
+ label: "Resend API key",
1888
+ required: false,
1889
+ validate: (v) => ({
1890
+ valid: v.startsWith("re_"),
1891
+ message: "Must start with re_"
1892
+ })
1893
+ },
1894
+ // npm
1895
+ {
1896
+ key: "NPM_TOKEN",
1897
+ label: "npm publish token",
1898
+ required: false,
1899
+ validate: validateNpmToken
1900
+ },
1901
+ // License
1902
+ {
1903
+ key: "REVEALUI_LICENSE_PRIVATE_KEY",
1904
+ label: "License signing key",
1905
+ required: false
1906
+ },
1907
+ // CRON
1908
+ {
1909
+ key: "REVEALUI_CRON_SECRET",
1910
+ label: "Cron secret",
1911
+ required: false
1912
+ },
1913
+ // AI
1914
+ {
1915
+ key: "GROQ_API_KEY",
1916
+ label: "Groq API key",
1917
+ required: false,
1918
+ validate: (v) => ({
1919
+ valid: v.startsWith("gsk_"),
1920
+ message: "Must start with gsk_"
1921
+ })
1922
+ }
1923
+ ];
1924
+ function maskValue(value) {
1925
+ if (value.length <= 8) return "***";
1926
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
1927
+ }
1928
+ function gatherCredentialChecks(env) {
1929
+ const checks = [];
1930
+ for (const spec of ENV_VAR_SPECS) {
1931
+ const value = env[spec.key];
1932
+ if (!value) {
1933
+ checks.push({
1934
+ id: `env:${spec.key}`,
1935
+ ok: !spec.required,
1936
+ detail: spec.required ? `${spec.label} \u2014 missing (required)` : `${spec.label} \u2014 not set`
1937
+ });
1938
+ continue;
1939
+ }
1940
+ if (spec.validate) {
1941
+ const result = spec.validate(value);
1942
+ checks.push({
1943
+ id: `env:${spec.key}`,
1944
+ ok: result.valid,
1945
+ detail: result.valid ? `${spec.label} \u2014 ${maskValue(value)}` : `${spec.label} \u2014 ${result.message} (got ${maskValue(value)})`
1946
+ });
1947
+ } else {
1948
+ checks.push({
1949
+ id: `env:${spec.key}`,
1950
+ ok: true,
1951
+ detail: `${spec.label} \u2014 set`
1952
+ });
1953
+ }
1954
+ }
1955
+ return checks;
1956
+ }
1516
1957
  async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1517
1958
  const workspaceRoot = findWorkspaceRoot(cwd);
1518
1959
  const dbConfig = resolveLocalDbConfig(workspaceRoot ?? cwd, env);
@@ -1526,6 +1967,7 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1526
1967
  const dockerOk = await commandExists("docker");
1527
1968
  const postgresReachable = dbTarget === "local" ? await isTcpReachable("127.0.0.1", 5432, 1e3) : false;
1528
1969
  const mcp = getMcpDetail(env);
1970
+ const credentials = gatherCredentialChecks(env);
1529
1971
  return {
1530
1972
  workspaceRoot,
1531
1973
  dbTarget,
@@ -1584,7 +2026,8 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1584
2026
  id: "mcp",
1585
2027
  ok: mcp.ok,
1586
2028
  detail: mcp.detail
1587
- }
2029
+ },
2030
+ ...credentials
1588
2031
  ]
1589
2032
  };
1590
2033
  }
@@ -1597,22 +2040,22 @@ function formatDoctorReport(report) {
1597
2040
  }
1598
2041
 
1599
2042
  // src/utils/dev-config.ts
1600
- import fs9 from "fs";
1601
- import path9 from "path";
2043
+ import fs10 from "fs";
2044
+ import path10 from "path";
1602
2045
  function getDevConfigPath(startDir = process.cwd()) {
1603
2046
  const workspaceRoot = findWorkspaceRoot(startDir);
1604
2047
  if (!workspaceRoot) {
1605
2048
  return null;
1606
2049
  }
1607
- return path9.join(workspaceRoot, ".revealui", "dev.json");
2050
+ return path10.join(workspaceRoot, ".revealui", "dev.json");
1608
2051
  }
1609
2052
  function readDevConfig(startDir = process.cwd()) {
1610
2053
  const configPath = getDevConfigPath(startDir);
1611
- if (!(configPath && fs9.existsSync(configPath))) {
2054
+ if (!(configPath && fs10.existsSync(configPath))) {
1612
2055
  return {};
1613
2056
  }
1614
2057
  try {
1615
- return JSON.parse(fs9.readFileSync(configPath, "utf8"));
2058
+ return JSON.parse(fs10.readFileSync(configPath, "utf8"));
1616
2059
  } catch {
1617
2060
  return {};
1618
2061
  }
@@ -1622,15 +2065,15 @@ function writeDevConfig(config, startDir = process.cwd()) {
1622
2065
  if (!configPath) {
1623
2066
  throw new Error("RevealUI workspace root not found");
1624
2067
  }
1625
- fs9.mkdirSync(path9.dirname(configPath), { recursive: true });
1626
- fs9.writeFileSync(`${configPath}`, `${JSON.stringify(config, null, 2)}
2068
+ fs10.mkdirSync(path10.dirname(configPath), { recursive: true });
2069
+ fs10.writeFileSync(`${configPath}`, `${JSON.stringify(config, null, 2)}
1627
2070
  `, "utf8");
1628
2071
  return configPath;
1629
2072
  }
1630
2073
 
1631
2074
  // src/commands/doctor.ts
1632
- import { createLogger as createLogger9 } from "@revealui/setup/utils";
1633
- var logger9 = createLogger9({ prefix: "Doctor" });
2075
+ import { createLogger as createLogger10 } from "@revealui/setup/utils";
2076
+ var logger10 = createLogger10({ prefix: "Doctor" });
1634
2077
  function getDoctorFixPlan(report) {
1635
2078
  const attempted = [];
1636
2079
  const skipped = [];
@@ -1688,7 +2131,7 @@ async function runDoctorCommand(options = {}) {
1688
2131
  `);
1689
2132
  if (options.fix) {
1690
2133
  if (fixPlan.attempted.length === 0) {
1691
- logger9.warn("No safe automatic fixes available");
2134
+ logger10.warn("No safe automatic fixes available");
1692
2135
  } else {
1693
2136
  await applyDoctorFixes(report);
1694
2137
  report = await gatherDoctorReport();
@@ -1697,7 +2140,7 @@ async function runDoctorCommand(options = {}) {
1697
2140
  }
1698
2141
  }
1699
2142
  if (report.checks.some((check) => !check.ok)) {
1700
- logger9.warn("Some checks failed");
2143
+ logger10.warn("Some checks failed");
1701
2144
  if (options.strict || options.json || process.env.CI) {
1702
2145
  process.exitCode = 1;
1703
2146
  }
@@ -1705,15 +2148,15 @@ async function runDoctorCommand(options = {}) {
1705
2148
  }
1706
2149
 
1707
2150
  // src/commands/shell.ts
1708
- import { createLogger as createLogger10 } from "@revealui/setup/utils";
1709
- import { execa as execa5 } from "execa";
1710
- var logger10 = createLogger10({ prefix: "Shell" });
2151
+ import { createLogger as createLogger11 } from "@revealui/setup/utils";
2152
+ import { execa as execa6 } from "execa";
2153
+ var logger11 = createLogger11({ prefix: "Shell" });
1711
2154
  async function runShellCommand(options = {}) {
1712
2155
  if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
1713
2156
  const workspaceRoot = findWorkspaceRoot();
1714
2157
  if (workspaceRoot) {
1715
2158
  const reentryArgs = options.ensure ? ["dev", "up"] : ["dev", "status"];
1716
- await execa5(
2159
+ await execa6(
1717
2160
  "nix",
1718
2161
  [
1719
2162
  "develop",
@@ -1752,12 +2195,12 @@ async function runShellCommand(options = {}) {
1752
2195
  }
1753
2196
  process.stdout.write(`${formatDoctorReport(freshReport)}
1754
2197
  `);
1755
- logger10.info("Use `revealui doctor --json` for machine-readable status.");
2198
+ logger11.info("Use `revealui doctor --json` for machine-readable status.");
1756
2199
  return false;
1757
2200
  }
1758
2201
 
1759
2202
  // src/commands/dev.ts
1760
- var logger11 = createLogger11({ prefix: "Dev" });
2203
+ var logger12 = createLogger12({ prefix: "Dev" });
1761
2204
  var DEV_PROFILES = {
1762
2205
  local: {},
1763
2206
  agent: {
@@ -1883,21 +2326,21 @@ async function runDevUpCommand(options = {}) {
1883
2326
  process.stdout.write(`${formatDevActions(plan)}
1884
2327
  `);
1885
2328
  if (plan.dryRun) {
1886
- logger11.info("Dry run only; no migrations or services were started.");
2329
+ logger12.info("Dry run only; no migrations or services were started.");
1887
2330
  return;
1888
2331
  }
1889
2332
  if (options.fix) {
1890
2333
  await runDoctorCommand({ fix: true });
1891
2334
  }
1892
2335
  await runDbMigrateCommand();
1893
- logger11.success("Database migration complete");
2336
+ logger12.success("Database migration complete");
1894
2337
  if (shouldIncludeMcp(resolved.include)) {
1895
2338
  const workspaceRoot = findWorkspaceRoot();
1896
2339
  if (!workspaceRoot) {
1897
2340
  throw new Error("RevealUI workspace root not found");
1898
2341
  }
1899
- logger11.info("Validating MCP setup");
1900
- await execa6("pnpm", ["setup:mcp"], {
2342
+ logger12.info("Validating MCP setup");
2343
+ await execa7("pnpm", ["setup:mcp"], {
1901
2344
  cwd: workspaceRoot,
1902
2345
  stdio: "inherit"
1903
2346
  });
@@ -1907,8 +2350,8 @@ async function runDevUpCommand(options = {}) {
1907
2350
  if (!workspaceRoot) {
1908
2351
  throw new Error("RevealUI workspace root not found");
1909
2352
  }
1910
- logger11.info(`Starting dev script: ${resolved.script}`);
1911
- await execa6("pnpm", [resolved.script], {
2353
+ logger12.info(`Starting dev script: ${resolved.script}`);
2354
+ await execa7("pnpm", [resolved.script], {
1912
2355
  cwd: workspaceRoot,
1913
2356
  stdio: "inherit"
1914
2357
  });
@@ -1965,7 +2408,7 @@ async function runDevProfileSetCommand(profile) {
1965
2408
  );
1966
2409
  }
1967
2410
  const configPath = writeDevConfig({ defaultProfile: profile });
1968
- logger11.success(`Default dev profile set to "${profile}" in ${configPath}`);
2411
+ logger12.success(`Default dev profile set to "${profile}" in ${configPath}`);
1969
2412
  }
1970
2413
  async function runDevProfileShowCommand(options = {}) {
1971
2414
  const configuredProfile = getConfiguredProfile() ?? null;
@@ -1978,11 +2421,223 @@ async function runDevProfileShowCommand(options = {}) {
1978
2421
  `);
1979
2422
  }
1980
2423
 
2424
+ // src/commands/terminal.ts
2425
+ import { copyFile, mkdir, readdir, stat } from "fs/promises";
2426
+ import { homedir as homedir2, platform } from "os";
2427
+ import { dirname, join as join2, resolve } from "path";
2428
+ import { createLogger as createLogger13 } from "@revealui/setup/utils";
2429
+ var logger13 = createLogger13({ prefix: "Terminal" });
2430
+ async function dirExists(path11) {
2431
+ try {
2432
+ const s = await stat(path11);
2433
+ return s.isDirectory();
2434
+ } catch {
2435
+ return false;
2436
+ }
2437
+ }
2438
+ async function fileExists(path11) {
2439
+ try {
2440
+ const s = await stat(path11);
2441
+ return s.isFile();
2442
+ } catch {
2443
+ return false;
2444
+ }
2445
+ }
2446
+ function getMacProfiles(home) {
2447
+ return [
2448
+ {
2449
+ name: "iTerm2",
2450
+ sourceFile: "iterm2-revealui.json",
2451
+ destPath: join2(
2452
+ home,
2453
+ "Library",
2454
+ "Application Support",
2455
+ "iTerm2",
2456
+ "DynamicProfiles",
2457
+ "revealui.json"
2458
+ ),
2459
+ postInstall: 'Profile loaded automatically. Select "RevealUI" in iTerm2 > Settings > Profiles.',
2460
+ detect: async () => dirExists(join2(home, "Library", "Application Support", "iTerm2"))
2461
+ },
2462
+ {
2463
+ name: "Terminal.app",
2464
+ sourceFile: "Terminal.app-RevealUI.terminal",
2465
+ destPath: join2(home, "Desktop", "RevealUI.terminal"),
2466
+ postInstall: "Double-click ~/Desktop/RevealUI.terminal to import, then set as default in Terminal > Settings > Profiles.",
2467
+ detect: async () => fileExists("/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal")
2468
+ },
2469
+ {
2470
+ name: "Alacritty",
2471
+ sourceFile: "alacritty-revealui.toml",
2472
+ destPath: join2(home, ".config", "alacritty", "revealui.toml"),
2473
+ postInstall: 'Add `import = ["~/.config/alacritty/revealui.toml"]` to your alacritty.toml [general] section.',
2474
+ detect: async () => dirExists(join2(home, ".config", "alacritty"))
2475
+ },
2476
+ {
2477
+ name: "Kitty",
2478
+ sourceFile: "kitty-revealui.conf",
2479
+ destPath: join2(home, ".config", "kitty", "revealui.conf"),
2480
+ postInstall: "Add `include revealui.conf` to your ~/.config/kitty/kitty.conf.",
2481
+ detect: async () => dirExists(join2(home, ".config", "kitty"))
2482
+ }
2483
+ ];
2484
+ }
2485
+ function getLinuxProfiles(home) {
2486
+ return [
2487
+ {
2488
+ name: "Alacritty",
2489
+ sourceFile: "alacritty-revealui.toml",
2490
+ destPath: join2(home, ".config", "alacritty", "revealui.toml"),
2491
+ postInstall: 'Add `import = ["~/.config/alacritty/revealui.toml"]` to your alacritty.toml [general] section.',
2492
+ detect: async () => dirExists(join2(home, ".config", "alacritty"))
2493
+ },
2494
+ {
2495
+ name: "Kitty",
2496
+ sourceFile: "kitty-revealui.conf",
2497
+ destPath: join2(home, ".config", "kitty", "revealui.conf"),
2498
+ postInstall: "Add `include revealui.conf` to your ~/.config/kitty/kitty.conf.",
2499
+ detect: async () => dirExists(join2(home, ".config", "kitty"))
2500
+ },
2501
+ {
2502
+ name: "GNOME Terminal",
2503
+ sourceFile: "gnome-terminal-revealui.dconf",
2504
+ destPath: join2(home, ".config", "revealui", "gnome-terminal-revealui.dconf"),
2505
+ postInstall: "Import with: dconf load /org/gnome/terminal/legacy/profiles:/ < ~/.config/revealui/gnome-terminal-revealui.dconf",
2506
+ detect: async () => {
2507
+ const gnomeConfigDir = join2(home, ".config", "dconf");
2508
+ return dirExists(gnomeConfigDir);
2509
+ }
2510
+ }
2511
+ ];
2512
+ }
2513
+ async function findConfigDir() {
2514
+ const monorepoPath = resolve(
2515
+ dirname(new URL(import.meta.url).pathname),
2516
+ "..",
2517
+ "..",
2518
+ "..",
2519
+ "..",
2520
+ "config",
2521
+ "terminal"
2522
+ );
2523
+ if (await dirExists(monorepoPath)) return monorepoPath;
2524
+ const npmPath = resolve(dirname(new URL(import.meta.url).pathname), "..", "config", "terminal");
2525
+ if (await dirExists(npmPath)) return npmPath;
2526
+ return null;
2527
+ }
2528
+ async function runTerminalInstallCommand(options) {
2529
+ const os2 = platform();
2530
+ const home = homedir2();
2531
+ if (os2 !== "darwin" && os2 !== "linux") {
2532
+ logger13.error(
2533
+ `Unsupported platform: ${os2}. Terminal profiles are available for macOS and Linux.`
2534
+ );
2535
+ if (os2 === "win32") {
2536
+ logger13.info("For Windows Terminal, copy config/terminal/ profiles manually.");
2537
+ }
2538
+ process.exitCode = 1;
2539
+ return;
2540
+ }
2541
+ const profiles = os2 === "darwin" ? getMacProfiles(home) : getLinuxProfiles(home);
2542
+ if (options.list) {
2543
+ if (options.json) {
2544
+ const detected = await Promise.all(
2545
+ profiles.map(async (p) => ({
2546
+ name: p.name,
2547
+ sourceFile: p.sourceFile,
2548
+ destPath: p.destPath,
2549
+ detected: await p.detect()
2550
+ }))
2551
+ );
2552
+ process.stdout.write(`${JSON.stringify({ platform: os2, profiles: detected }, null, 2)}
2553
+ `);
2554
+ return;
2555
+ }
2556
+ logger13.header("Available Terminal Profiles");
2557
+ logger13.info(`Platform: ${os2 === "darwin" ? "macOS" : "Linux"}`);
2558
+ for (const profile of profiles) {
2559
+ const detected = await profile.detect();
2560
+ const icon = detected ? "[detected]" : "[not found]";
2561
+ logger13.info(` ${profile.name} ${icon} \u2014 ${profile.sourceFile}`);
2562
+ }
2563
+ return;
2564
+ }
2565
+ const configDir = await findConfigDir();
2566
+ if (!configDir) {
2567
+ logger13.error("Could not find config/terminal/ directory.");
2568
+ logger13.info(
2569
+ "Run this command from the RevealUI monorepo root, or install via npx create-revealui."
2570
+ );
2571
+ process.exitCode = 1;
2572
+ return;
2573
+ }
2574
+ const sourceFiles = await readdir(configDir);
2575
+ let targetProfiles = profiles;
2576
+ if (options.terminal) {
2577
+ const match = profiles.find((p) => p.name.toLowerCase() === options.terminal?.toLowerCase());
2578
+ if (!match) {
2579
+ logger13.error(`Unknown terminal: ${options.terminal}`);
2580
+ logger13.info(`Available: ${profiles.map((p) => p.name).join(", ")}`);
2581
+ process.exitCode = 1;
2582
+ return;
2583
+ }
2584
+ targetProfiles = [match];
2585
+ }
2586
+ let installed = 0;
2587
+ let skipped = 0;
2588
+ logger13.header("RevealUI Terminal Profile Installer");
2589
+ logger13.info(`Platform: ${os2 === "darwin" ? "macOS" : "Linux"}`);
2590
+ for (const profile of targetProfiles) {
2591
+ const detected = await profile.detect();
2592
+ if (!(detected || options.terminal)) {
2593
+ continue;
2594
+ }
2595
+ if (!detected && options.terminal) {
2596
+ logger13.warn(`${profile.name} not detected, installing anyway (--terminal flag).`);
2597
+ }
2598
+ if (!sourceFiles.includes(profile.sourceFile)) {
2599
+ logger13.warn(`Source file missing: ${profile.sourceFile} \u2014 skipping ${profile.name}`);
2600
+ skipped++;
2601
+ continue;
2602
+ }
2603
+ const sourcePath = join2(configDir, profile.sourceFile);
2604
+ const destPath = profile.destPath;
2605
+ if (await fileExists(destPath)) {
2606
+ if (!options.force) {
2607
+ logger13.warn(`${profile.name}: ${destPath} already exists. Use --force to overwrite.`);
2608
+ skipped++;
2609
+ continue;
2610
+ }
2611
+ }
2612
+ await mkdir(dirname(destPath), { recursive: true });
2613
+ await copyFile(sourcePath, destPath);
2614
+ installed++;
2615
+ logger13.success(`${profile.name}: installed to ${destPath}`);
2616
+ logger13.info(` ${profile.postInstall}`);
2617
+ }
2618
+ if (installed === 0 && skipped === 0) {
2619
+ logger13.info("No supported terminal emulators detected.");
2620
+ logger13.info(`Supported: ${profiles.map((p) => p.name).join(", ")}`);
2621
+ logger13.info("Use --terminal <name> to install for a specific terminal.");
2622
+ } else if (installed > 0) {
2623
+ logger13.success(
2624
+ `Installed ${installed} profile(s). ${skipped > 0 ? `Skipped ${skipped}.` : ""}`
2625
+ );
2626
+ }
2627
+ if (options.json) {
2628
+ process.stdout.write(`${JSON.stringify({ installed, skipped, platform: os2 }, null, 2)}
2629
+ `);
2630
+ }
2631
+ }
2632
+ async function runTerminalListCommand(options) {
2633
+ await runTerminalInstallCommand({ list: true, json: options.json });
2634
+ }
2635
+
1981
2636
  // src/cli.ts
1982
- var logger12 = createLogger12({ prefix: "CLI" });
2637
+ var logger14 = createLogger14({ prefix: "CLI" });
1983
2638
  function configureCreateCommand(command, legacyName) {
1984
2639
  command.description("Create a new RevealUI project").argument("[project-name]", "Name of the project").option("-t, --template <name>", "Template to use (basic-blog, e-commerce, portfolio)").option("--skip-git", "Skip git initialization", false).option("--skip-install", "Skip dependency installation", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (projectName, options) => {
1985
- logger12.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
2640
+ logger14.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
1986
2641
  await runCreateFlow(projectName, options);
1987
2642
  });
1988
2643
  return command;
@@ -2077,6 +2732,23 @@ function createCli() {
2077
2732
  devProfile.command("show").description("Show the configured default dev profile").option("--json", "Output machine-readable JSON", false).action(async (options) => {
2078
2733
  await runDevProfileShowCommand(options);
2079
2734
  });
2735
+ const auth = program.command("auth").description("Manage npm registry authentication and publish tokens");
2736
+ auth.command("status").description("Show current npm authentication state").option("--json", "Output machine-readable JSON", false).action(async (options) => {
2737
+ await runAuthStatusCommand(options);
2738
+ });
2739
+ auth.command("set-token").description("Store an npm token via RevVault and configure .npmrc").option("--token <value>", "npm token (or set NPM_TOKEN env var)").option("--skip-vault", "Skip RevVault storage", false).option("--skip-npmrc", "Skip .npmrc modification", false).action(async (options) => {
2740
+ await runAuthSetTokenCommand(options);
2741
+ });
2742
+ auth.command("verify").description("Verify the full npm auth chain (env \u2192 RevVault \u2192 .npmrc \u2192 registry)").option("--json", "Output machine-readable JSON", false).action(async (options) => {
2743
+ await runAuthVerifyCommand(options);
2744
+ });
2745
+ const terminal = program.command("terminal").description("Install RevealUI terminal profiles for your terminal emulator");
2746
+ terminal.command("install").description("Detect and install terminal profiles for supported emulators").option("--terminal <name>", "Install for a specific terminal (e.g. iTerm2, Alacritty, Kitty)").option("--force", "Overwrite existing profile files", false).option("--json", "Output machine-readable JSON", false).action(async (options) => {
2747
+ await runTerminalInstallCommand(options);
2748
+ });
2749
+ terminal.command("list").description("List available terminal profiles and detected emulators").option("--json", "Output machine-readable JSON", false).action(async (options) => {
2750
+ await runTerminalListCommand(options);
2751
+ });
2080
2752
  program.command("shell").description("Deprecated alias for `revealui dev shell`").option("--ensure", "Initialize/start the local DB when possible", false).option("--json", "Output machine-readable JSON", false).option("--inside", "Internal flag used after re-entering nix develop", false).action(async (options) => {
2081
2753
  await runDevUpCommand({
2082
2754
  ensure: options.ensure,