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