@revealui/cli 0.4.0 → 0.6.2

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
@@ -15,7 +15,9 @@ async function runAgentStatusCommand() {
15
15
  logger.info(`Project root: ${projectRoot}`);
16
16
  logger.info(`Available: ${available ? "yes" : "no"}`);
17
17
  if (!available) {
18
- logger.warn("No LLM provider detected. Start BitNet, Ollama, or set GROQ_API_KEY.");
18
+ logger.warn(
19
+ "No LLM provider detected. Start BitNet, Ollama, or install an Ubuntu inference snap."
20
+ );
19
21
  }
20
22
  }
21
23
  async function runAgentHeadlessCommand(prompt) {
@@ -200,9 +202,6 @@ async function detectProvider() {
200
202
  }
201
203
  } catch {
202
204
  }
203
- if (process.env.GROQ_API_KEY) {
204
- return { available: true, provider: "groq", model: "llama-3.3-70b-versatile", projectRoot };
205
- }
206
205
  return { available: false, provider: "none", model: "none", projectRoot };
207
206
  }
208
207
  function buildMinimalInstructions() {
@@ -215,20 +214,337 @@ function buildMinimalInstructions() {
215
214
  ].join("\n");
216
215
  }
217
216
 
217
+ // src/commands/auth.ts
218
+ import fs from "fs/promises";
219
+ import os from "os";
220
+ import path from "path";
221
+ import { createLogger as createLogger2 } from "@revealui/setup/utils";
222
+ import { execa as execa2 } from "execa";
223
+
224
+ // src/utils/command.ts
225
+ import net from "net";
226
+ import { execa } from "execa";
227
+ async function commandExists(command) {
228
+ try {
229
+ await execa("bash", ["-lc", `command -v ${command}`], {
230
+ stdio: "pipe"
231
+ });
232
+ return true;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+ async function isTcpReachable(host, port, timeoutMs = 1500) {
238
+ return await new Promise((resolve2) => {
239
+ const socket = new net.Socket();
240
+ const finalize = (value) => {
241
+ socket.removeAllListeners();
242
+ socket.destroy();
243
+ resolve2(value);
244
+ };
245
+ socket.setTimeout(timeoutMs);
246
+ socket.once("connect", () => finalize(true));
247
+ socket.once("timeout", () => finalize(false));
248
+ socket.once("error", () => finalize(false));
249
+ socket.connect(port, host);
250
+ });
251
+ }
252
+
253
+ // src/commands/auth.ts
254
+ var logger2 = createLogger2({ prefix: "Auth" });
255
+ var REVVAULT_NPM_PATH = "revealui/env/npm";
256
+ var NPM_TOKEN_INTERPOLATION = "${NPM_TOKEN}";
257
+ var NPMRC_AUTH_LINE = `//registry.npmjs.org/:_authToken=${NPM_TOKEN_INTERPOLATION}`;
258
+ function write(text5) {
259
+ process.stdout.write(text5);
260
+ }
261
+ function writeJson(data) {
262
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
263
+ `);
264
+ }
265
+ function getNpmrcPaths() {
266
+ const user = path.join(os.homedir(), ".npmrc");
267
+ let dir = process.cwd();
268
+ let project = null;
269
+ for (let i = 0; i < 10; i++) {
270
+ const candidate = path.join(dir, ".npmrc");
271
+ if (dir !== os.homedir()) {
272
+ project ??= candidate;
273
+ }
274
+ const parent = path.dirname(dir);
275
+ if (parent === dir) break;
276
+ dir = parent;
277
+ }
278
+ return { user, project };
279
+ }
280
+ async function fileContains(filePath, needle) {
281
+ try {
282
+ const content = await fs.readFile(filePath, "utf-8");
283
+ return content.includes(needle);
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+ async function getTokenFromEnv() {
289
+ return process.env.NPM_TOKEN || void 0;
290
+ }
291
+ async function getTokenFromNpmrc() {
292
+ const userRc = path.join(os.homedir(), ".npmrc");
293
+ try {
294
+ const content = await fs.readFile(userRc, "utf-8");
295
+ const match = content.match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);
296
+ return match?.[1]?.trim() || void 0;
297
+ } catch {
298
+ return void 0;
299
+ }
300
+ }
301
+ function maskToken(token) {
302
+ if (token.length <= 8) return "***";
303
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
304
+ }
305
+ async function hasRevvault() {
306
+ return commandExists("revvault");
307
+ }
308
+ async function revvaultGet(secretPath) {
309
+ try {
310
+ const { stdout } = await execa2("revvault", ["get", secretPath], {
311
+ stdio: "pipe"
312
+ });
313
+ return stdout.trim() || void 0;
314
+ } catch {
315
+ return void 0;
316
+ }
317
+ }
318
+ async function revvaultSet(secretPath, value) {
319
+ try {
320
+ await execa2("revvault", ["set", secretPath], {
321
+ input: value,
322
+ stdio: ["pipe", "pipe", "pipe"]
323
+ });
324
+ return true;
325
+ } catch {
326
+ return false;
327
+ }
328
+ }
329
+ async function runAuthStatusCommand(options) {
330
+ const envToken = await getTokenFromEnv();
331
+ const npmrcToken = await getTokenFromNpmrc();
332
+ const hasVault = await hasRevvault();
333
+ const vaultValue = hasVault ? await revvaultGet(REVVAULT_NPM_PATH) : void 0;
334
+ const paths = getNpmrcPaths();
335
+ const projectUsesEnvVar = paths.project ? await fileContains(paths.project, NPM_TOKEN_INTERPOLATION) : false;
336
+ const effectiveToken = envToken || npmrcToken;
337
+ let npmUser;
338
+ try {
339
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
340
+ npmUser = stdout.trim();
341
+ } catch {
342
+ npmUser = void 0;
343
+ }
344
+ if (options.json) {
345
+ writeJson({
346
+ authenticated: !!npmUser,
347
+ user: npmUser ?? null,
348
+ tokenSource: envToken ? "env" : npmrcToken ? "npmrc" : null,
349
+ tokenMasked: effectiveToken ? maskToken(effectiveToken) : null,
350
+ revvaultConfigured: !!vaultValue,
351
+ projectNpmrcUsesEnvVar: projectUsesEnvVar,
352
+ paths: {
353
+ userNpmrc: paths.user,
354
+ projectNpmrc: paths.project
355
+ }
356
+ });
357
+ return;
358
+ }
359
+ logger2.header("npm Authentication Status");
360
+ if (npmUser) {
361
+ logger2.success(`Authenticated as: ${npmUser}`);
362
+ } else {
363
+ logger2.error("Not authenticated \u2014 npm whoami failed");
364
+ }
365
+ write("\n");
366
+ logger2.info(
367
+ `Token source: ${envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (file)" : "none"}`
368
+ );
369
+ if (effectiveToken) {
370
+ logger2.info(`Token: ${maskToken(effectiveToken)}`);
371
+ }
372
+ write("\n");
373
+ logger2.info(
374
+ `RevVault (${REVVAULT_NPM_PATH}): ${vaultValue ? "configured" : hasVault ? "not set" : "revvault not installed"}`
375
+ );
376
+ logger2.info(`Project .npmrc uses NPM_TOKEN: ${projectUsesEnvVar ? "yes" : "no"}`);
377
+ if (!projectUsesEnvVar && paths.project) {
378
+ logger2.warn(
379
+ "Project .npmrc does not reference NPM_TOKEN \u2014 direnv/RevVault tokens won't be used for publishing"
380
+ );
381
+ }
382
+ if (!vaultValue && hasVault && effectiveToken) {
383
+ logger2.warn("Token is not stored in RevVault. Run: revealui auth set-token to persist it.");
384
+ }
385
+ }
386
+ async function runAuthSetTokenCommand(options) {
387
+ logger2.header("Set npm Publish Token");
388
+ const token = options.token || process.env.NPM_TOKEN;
389
+ if (!token) {
390
+ logger2.error("No token provided. Pass --token <value> or set NPM_TOKEN in your environment.");
391
+ logger2.info("Create a Granular Access Token at: https://www.npmjs.com/settings/<user>/tokens");
392
+ logger2.info(" Type: Granular Access Token");
393
+ logger2.info(" Packages: All packages (read and write)");
394
+ logger2.info(" This bypasses 2FA \u2014 no OTP needed for publish");
395
+ process.exitCode = 1;
396
+ return;
397
+ }
398
+ if (!token.startsWith("npm_")) {
399
+ logger2.warn('Token does not start with "npm_" \u2014 this may not be a valid npm token');
400
+ }
401
+ if (!options.skipVault) {
402
+ const hasVault = await hasRevvault();
403
+ if (hasVault) {
404
+ const stored = await revvaultSet(REVVAULT_NPM_PATH, `NPM_TOKEN=${token}`);
405
+ if (stored) {
406
+ logger2.success(`Stored in RevVault (${REVVAULT_NPM_PATH})`);
407
+ } else {
408
+ logger2.error("Failed to store in RevVault");
409
+ }
410
+ } else {
411
+ logger2.warn("RevVault not installed \u2014 skipping vault storage");
412
+ }
413
+ }
414
+ if (!options.skipNpmrc) {
415
+ const paths = getNpmrcPaths();
416
+ if (paths.project) {
417
+ const hasInterpolation = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
418
+ if (!hasInterpolation) {
419
+ try {
420
+ const content = await fs.readFile(paths.project, "utf-8");
421
+ const updatedContent = `${content.trimEnd()}
422
+ ${NPMRC_AUTH_LINE}
423
+ `;
424
+ await fs.writeFile(paths.project, updatedContent, "utf-8");
425
+ logger2.success(`Added NPM_TOKEN interpolation to ${paths.project}`);
426
+ } catch (err) {
427
+ logger2.error(`Failed to update ${paths.project}: ${err}`);
428
+ }
429
+ } else {
430
+ logger2.info("Project .npmrc already uses NPM_TOKEN");
431
+ }
432
+ }
433
+ }
434
+ const userRc = path.join(os.homedir(), ".npmrc");
435
+ try {
436
+ const content = await fs.readFile(userRc, "utf-8");
437
+ const filtered = content.split("\n").filter((line) => !line.includes("//registry.npmjs.org/:_authToken=")).join("\n");
438
+ if (filtered !== content) {
439
+ await fs.writeFile(userRc, filtered, "utf-8");
440
+ logger2.success("Removed hardcoded token from ~/.npmrc (now managed via env var)");
441
+ }
442
+ } catch {
443
+ }
444
+ write("\n");
445
+ logger2.info("Verifying...");
446
+ process.env.NPM_TOKEN = token;
447
+ try {
448
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
449
+ logger2.success(`Authenticated as: ${stdout.trim()}`);
450
+ } catch {
451
+ logger2.error("npm whoami failed \u2014 token may be invalid or expired");
452
+ process.exitCode = 1;
453
+ return;
454
+ }
455
+ write("\n");
456
+ logger2.success("Token configured. Run `direnv reload` to load it in all terminals.");
457
+ }
458
+ async function runAuthVerifyCommand(options) {
459
+ const checks = [];
460
+ let npmUser;
461
+ try {
462
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
463
+ npmUser = stdout.trim();
464
+ checks.push({ name: "npm whoami", pass: true, detail: npmUser });
465
+ } catch {
466
+ checks.push({
467
+ name: "npm whoami",
468
+ pass: false,
469
+ detail: "Not authenticated"
470
+ });
471
+ }
472
+ const envToken = await getTokenFromEnv();
473
+ const npmrcToken = await getTokenFromNpmrc();
474
+ const source = envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (hardcoded)" : "none";
475
+ checks.push({
476
+ name: "Token source",
477
+ pass: !!envToken,
478
+ detail: source + (npmrcToken && !envToken ? " \u2014 consider migrating to RevVault" : "")
479
+ });
480
+ const hasVault = await hasRevvault();
481
+ if (hasVault) {
482
+ const vaultValue = await revvaultGet(REVVAULT_NPM_PATH);
483
+ checks.push({
484
+ name: "RevVault",
485
+ pass: !!vaultValue,
486
+ detail: vaultValue ? `${REVVAULT_NPM_PATH} configured` : "Not stored in vault"
487
+ });
488
+ }
489
+ const paths = getNpmrcPaths();
490
+ if (paths.project) {
491
+ const usesEnvVar = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
492
+ checks.push({
493
+ name: ".npmrc NPM_TOKEN",
494
+ pass: usesEnvVar,
495
+ detail: usesEnvVar ? "Project .npmrc uses env var" : "Missing \u2014 direnv token won't reach npm"
496
+ });
497
+ }
498
+ const envrcPath = path.join(process.cwd(), ".envrc");
499
+ const loadsNpm = await fileContains(envrcPath, REVVAULT_NPM_PATH);
500
+ checks.push({
501
+ name: ".envrc loads npm",
502
+ pass: loadsNpm,
503
+ detail: loadsNpm ? "revealui/env/npm in .envrc" : "Missing \u2014 direnv won't export NPM_TOKEN"
504
+ });
505
+ const hardcodedToken = await getTokenFromNpmrc();
506
+ checks.push({
507
+ name: "~/.npmrc clean",
508
+ pass: !hardcodedToken,
509
+ detail: hardcodedToken ? `Hardcoded token found (${maskToken(hardcodedToken)})` : "No hardcoded tokens"
510
+ });
511
+ if (options.json) {
512
+ const allPass2 = checks.every((c) => c.pass);
513
+ writeJson({ pass: allPass2, checks });
514
+ return;
515
+ }
516
+ logger2.header("npm Auth Verification");
517
+ for (const check of checks) {
518
+ if (check.pass) {
519
+ logger2.success(`${check.name}: ${check.detail}`);
520
+ } else {
521
+ logger2.error(`${check.name}: ${check.detail}`);
522
+ }
523
+ }
524
+ const allPass = checks.every((c) => c.pass);
525
+ write("\n");
526
+ if (allPass) {
527
+ logger2.success("All checks passed \u2014 ready to publish");
528
+ } else {
529
+ logger2.warn("Some checks failed. Run `revealui auth set-token` to fix.");
530
+ process.exitCode = 1;
531
+ }
532
+ }
533
+
218
534
  // src/commands/create-flow.ts
219
535
  import { readFileSync } from "fs";
220
536
  import { homedir } from "os";
221
537
  import { join } from "path";
222
- import { createLogger as createLogger7 } from "@revealui/setup/utils";
538
+ import { createLogger as createLogger8 } from "@revealui/setup/utils";
223
539
  import { importSPKI, jwtVerify } from "jose";
224
540
 
225
541
  // src/prompts/database.ts
226
- import inquirer from "inquirer";
542
+ import { isCancel, select, text } from "@clack/prompts";
227
543
 
228
544
  // src/validators/credentials.ts
229
- import { createLogger as createLogger2 } from "@revealui/setup/utils";
230
- var logger2 = createLogger2({ prefix: "Validator" });
231
- async function validateStripeKey(key) {
545
+ import { createLogger as createLogger3 } from "@revealui/setup/utils";
546
+ var logger3 = createLogger3({ prefix: "Validator" });
547
+ function validateStripeKey(key) {
232
548
  if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
233
549
  return {
234
550
  valid: false,
@@ -237,7 +553,7 @@ async function validateStripeKey(key) {
237
553
  }
238
554
  return { valid: true };
239
555
  }
240
- async function validateNeonUrl(url) {
556
+ function validateNeonUrl(url) {
241
557
  try {
242
558
  const parsed = new URL(url);
243
559
  if (!parsed.protocol.startsWith("postgres")) {
@@ -254,7 +570,7 @@ async function validateNeonUrl(url) {
254
570
  };
255
571
  }
256
572
  }
257
- async function validateVercelToken(token) {
573
+ function validateVercelToken(token) {
258
574
  if (!token || token.length < 20) {
259
575
  return {
260
576
  valid: false,
@@ -263,11 +579,11 @@ async function validateVercelToken(token) {
263
579
  }
264
580
  return { valid: true };
265
581
  }
266
- async function validateSupabaseUrl(url) {
582
+ function validateSupabaseUrl(url) {
267
583
  try {
268
584
  const parsed = new URL(url);
269
585
  if (!parsed.hostname.includes("supabase")) {
270
- logger2.warn("URL does not appear to be a Supabase URL");
586
+ logger3.warn("URL does not appear to be a Supabase URL");
271
587
  }
272
588
  return { valid: true };
273
589
  } catch {
@@ -277,150 +593,152 @@ async function validateSupabaseUrl(url) {
277
593
  };
278
594
  }
279
595
  }
596
+ function validateNpmToken(token) {
597
+ if (!token.startsWith("npm_")) {
598
+ return {
599
+ valid: false,
600
+ message: "npm token must start with npm_"
601
+ };
602
+ }
603
+ if (token.length < 20) {
604
+ return {
605
+ valid: false,
606
+ message: "npm token appears invalid (too short)"
607
+ };
608
+ }
609
+ return { valid: true };
610
+ }
280
611
 
281
612
  // src/prompts/database.ts
282
613
  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
- ]);
614
+ const provider = await select({
615
+ message: "Which database provider would you like to use?",
616
+ options: [
617
+ { value: "neon", label: "NeonDB - Serverless PostgreSQL (recommended)" },
618
+ { value: "supabase", label: "Supabase - PostgreSQL with built-in features" },
619
+ { value: "local", label: "Local PostgreSQL - Use existing local database" },
620
+ { value: "skip", label: "Skip - Configure later" }
621
+ ],
622
+ initialValue: "neon"
623
+ });
624
+ if (isCancel(provider)) {
625
+ process.exit(0);
626
+ }
309
627
  if (provider === "skip") {
310
628
  return { provider: "skip" };
311
629
  }
312
630
  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
- }
631
+ const postgresUrl2 = await text({
632
+ message: "Enter your PostgreSQL connection string:",
633
+ defaultValue: "postgresql://postgres:postgres@localhost:5432/revealui",
634
+ validate: (input) => {
635
+ if (!input) return void 0;
636
+ const result = validateNeonUrl(input);
637
+ return result.valid ? void 0 : result.message || "Invalid database URL";
323
638
  }
324
- ]);
639
+ });
640
+ if (isCancel(postgresUrl2)) {
641
+ process.exit(0);
642
+ }
325
643
  return { provider: "local", postgresUrl: postgresUrl2 };
326
644
  }
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";
645
+ const label = provider === "neon" ? "Neon" : "Supabase";
646
+ const postgresUrl = await text({
647
+ message: `Enter your ${label} database connection string:`,
648
+ validate: (input) => {
649
+ if (!input || input.trim() === "") {
650
+ return "Database URL is required";
338
651
  }
652
+ const result = validateNeonUrl(input);
653
+ return result.valid ? void 0 : result.message || "Invalid database URL";
339
654
  }
340
- ]);
655
+ });
656
+ if (isCancel(postgresUrl)) {
657
+ process.exit(0);
658
+ }
341
659
  return { provider, postgresUrl };
342
660
  }
343
661
 
344
662
  // src/prompts/devenv.ts
345
- import inquirer2 from "inquirer";
663
+ import { confirm, isCancel as isCancel2 } from "@clack/prompts";
346
664
  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;
665
+ const createDevContainer = await confirm({
666
+ message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
667
+ initialValue: true
668
+ });
669
+ if (isCancel2(createDevContainer)) {
670
+ process.exit(0);
671
+ }
672
+ const createDevbox = await confirm({
673
+ message: "Create Devbox configuration for Nix-powered development?",
674
+ initialValue: true
675
+ });
676
+ if (isCancel2(createDevbox)) {
677
+ process.exit(0);
678
+ }
679
+ return { createDevContainer, createDevbox };
362
680
  }
363
681
 
364
682
  // src/prompts/payments.ts
365
- import inquirer3 from "inquirer";
683
+ import { confirm as confirm2, isCancel as isCancel3, text as text2 } from "@clack/prompts";
366
684
  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
- ]);
685
+ const enabled = await confirm2({
686
+ message: "Do you want to configure Stripe payments?",
687
+ initialValue: true
688
+ });
689
+ if (isCancel3(enabled)) {
690
+ process.exit(0);
691
+ }
375
692
  if (!enabled) {
376
693
  return { enabled: false };
377
694
  }
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";
695
+ const stripeSecretKey = await text2({
696
+ message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
697
+ validate: (input) => {
698
+ if (!input || input.trim() === "") {
699
+ return "Stripe secret key is required";
389
700
  }
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;
701
+ const result = validateStripeKey(input);
702
+ return result.valid ? void 0 : result.message || "Invalid Stripe key";
703
+ }
704
+ });
705
+ if (isCancel3(stripeSecretKey)) {
706
+ process.exit(0);
707
+ }
708
+ const stripePublishableKey = await text2({
709
+ message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
710
+ validate: (input) => {
711
+ if (!input || input.trim() === "") {
712
+ return "Stripe publishable key is required";
403
713
  }
404
- },
405
- {
406
- type: "input",
407
- name: "stripeWebhookSecret",
408
- message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
409
- default: ""
714
+ if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
715
+ return "Stripe publishable key must start with pk_test_ or pk_live_";
716
+ }
717
+ return void 0;
410
718
  }
411
- ]);
719
+ });
720
+ if (isCancel3(stripePublishableKey)) {
721
+ process.exit(0);
722
+ }
723
+ const stripeWebhookSecret = await text2({
724
+ message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
725
+ defaultValue: ""
726
+ });
727
+ if (isCancel3(stripeWebhookSecret)) {
728
+ process.exit(0);
729
+ }
412
730
  return {
413
731
  enabled: true,
414
- stripeSecretKey: answers.stripeSecretKey,
415
- stripePublishableKey: answers.stripePublishableKey,
416
- stripeWebhookSecret: answers.stripeWebhookSecret || void 0
732
+ stripeSecretKey,
733
+ stripePublishableKey,
734
+ stripeWebhookSecret: stripeWebhookSecret || void 0
417
735
  };
418
736
  }
419
737
 
420
738
  // src/prompts/project.ts
421
- import fs from "fs";
422
- import path from "path";
423
- import inquirer4 from "inquirer";
739
+ import fs2 from "fs";
740
+ import path2 from "path";
741
+ import { isCancel as isCancel4, select as select2, text as text3 } from "@clack/prompts";
424
742
  var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
425
743
  async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
426
744
  let projectName;
@@ -429,25 +747,31 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
429
747
  } else if (nonInteractive) {
430
748
  projectName = "my-revealui-project";
431
749
  } 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;
750
+ const name = await text3({
751
+ message: "What is your project name?",
752
+ defaultValue: "my-revealui-project",
753
+ validate: (input) => {
754
+ if (!input) return void 0;
755
+ const isValid = input.split("").every((ch) => {
756
+ const code = ch.charCodeAt(0);
757
+ return code >= 97 && code <= 122 || // a-z
758
+ code >= 48 && code <= 57 || // 0-9
759
+ code === 45;
760
+ });
761
+ if (!isValid) {
762
+ return "Project name must contain only lowercase letters, numbers, and hyphens";
447
763
  }
764
+ const projectPath = path2.resolve(process.cwd(), input);
765
+ if (fs2.existsSync(projectPath)) {
766
+ return `Directory "${input}" already exists`;
767
+ }
768
+ return void 0;
448
769
  }
449
- ]);
450
- projectName = answers.projectName;
770
+ });
771
+ if (isCancel4(name)) {
772
+ process.exit(0);
773
+ }
774
+ projectName = name;
451
775
  }
452
776
  let template;
453
777
  if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
@@ -455,118 +779,106 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
455
779
  } else if (nonInteractive) {
456
780
  template = "basic-blog";
457
781
  } 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;
782
+ const selected = await select2({
783
+ message: "Which template would you like to use?",
784
+ options: [
785
+ { value: "basic-blog", label: "Basic Blog - A simple blog with posts and pages" },
786
+ { value: "e-commerce", label: "E-commerce - Product catalog with checkout" },
787
+ { value: "portfolio", label: "Portfolio - Personal portfolio site" }
788
+ ],
789
+ initialValue: "basic-blog"
790
+ });
791
+ if (isCancel4(selected)) {
792
+ process.exit(0);
793
+ }
794
+ template = selected;
472
795
  }
473
796
  return {
474
797
  projectName,
475
- projectPath: path.resolve(process.cwd(), projectName),
798
+ projectPath: path2.resolve(process.cwd(), projectName),
476
799
  template
477
800
  };
478
801
  }
479
802
 
480
803
  // src/prompts/storage.ts
481
- import inquirer5 from "inquirer";
804
+ import { isCancel as isCancel5, select as select3, text as text4 } from "@clack/prompts";
482
805
  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
- ]);
806
+ const provider = await select3({
807
+ message: "Which storage provider would you like to use?",
808
+ options: [
809
+ { value: "vercel-blob", label: "Vercel Blob - Simple object storage (recommended)" },
810
+ { value: "supabase", label: "Supabase Storage - Integrated with Supabase" },
811
+ { value: "skip", label: "Skip - Configure later" }
812
+ ],
813
+ initialValue: "vercel-blob"
814
+ });
815
+ if (isCancel5(provider)) {
816
+ process.exit(0);
817
+ }
505
818
  if (provider === "skip") {
506
819
  return { provider: "skip" };
507
820
  }
508
821
  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";
822
+ const blobToken = await text4({
823
+ message: "Enter your Vercel Blob read/write token:",
824
+ validate: (input) => {
825
+ if (!input || input.trim() === "") {
826
+ return "Blob token is required";
520
827
  }
828
+ const result = validateVercelToken(input);
829
+ return result.valid ? void 0 : result.message || "Invalid token";
521
830
  }
522
- ]);
831
+ });
832
+ if (isCancel5(blobToken)) {
833
+ process.exit(0);
834
+ }
523
835
  return { provider: "vercel-blob", blobToken };
524
836
  }
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";
837
+ const supabaseUrl = await text4({
838
+ message: "Enter your Supabase project URL:",
839
+ validate: (input) => {
840
+ if (!input || input.trim() === "") {
841
+ return "Supabase URL is required";
536
842
  }
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;
843
+ const result = validateSupabaseUrl(input);
844
+ return result.valid ? void 0 : result.message || "Invalid URL";
845
+ }
846
+ });
847
+ if (isCancel5(supabaseUrl)) {
848
+ process.exit(0);
849
+ }
850
+ const supabasePublishableKey = await text4({
851
+ message: "Enter your Supabase publishable key (sb_publishable_...):",
852
+ validate: (input) => {
853
+ if (!input || input.trim() === "") {
854
+ return "Supabase publishable key is required";
547
855
  }
856
+ return void 0;
548
857
  }
549
- ]);
858
+ });
859
+ if (isCancel5(supabasePublishableKey)) {
860
+ process.exit(0);
861
+ }
550
862
  return {
551
863
  provider: "supabase",
552
- supabaseUrl: answers.supabaseUrl,
553
- supabasePublishableKey: answers.supabasePublishableKey
864
+ supabaseUrl,
865
+ supabasePublishableKey
554
866
  };
555
867
  }
556
868
 
557
869
  // src/validators/node-version.ts
558
- import { createLogger as createLogger3 } from "@revealui/setup/utils";
559
- var logger3 = createLogger3({ prefix: "Setup" });
870
+ import { createLogger as createLogger4 } from "@revealui/setup/utils";
871
+ var logger4 = createLogger4({ prefix: "Setup" });
560
872
  var REQUIRED_NODE_VERSION = "24.13.0";
561
873
  function validateNodeVersion() {
562
874
  const currentVersion = process.version.slice(1);
563
875
  const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
564
876
  const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
565
877
  if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
566
- logger3.error(
878
+ logger4.error(
567
879
  `Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
568
880
  );
569
- logger3.info("Please upgrade Node.js: https://nodejs.org/");
881
+ logger4.info("Please upgrade Node.js: https://nodejs.org/");
570
882
  return false;
571
883
  }
572
884
  return true;
@@ -575,15 +887,15 @@ function validateNodeVersion() {
575
887
  // src/commands/create.ts
576
888
  import crypto from "crypto";
577
889
  import { existsSync } from "fs";
578
- import fs5 from "fs/promises";
579
- import path5 from "path";
890
+ import fs6 from "fs/promises";
891
+ import path6 from "path";
580
892
  import { fileURLToPath } from "url";
581
- import { createLogger as createLogger6 } from "@revealui/setup/utils";
893
+ import { createLogger as createLogger7 } from "@revealui/setup/utils";
582
894
  import ora2 from "ora";
583
895
 
584
896
  // src/generators/devbox.ts
585
- import fs2 from "fs/promises";
586
- import path2 from "path";
897
+ import fs3 from "fs/promises";
898
+ import path3 from "path";
587
899
  async function generateDevbox(projectPath) {
588
900
  const devboxConfig = {
589
901
  packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
@@ -603,19 +915,19 @@ async function generateDevbox(projectPath) {
603
915
  NODE_ENV: "development"
604
916
  }
605
917
  };
606
- await fs2.writeFile(
607
- path2.join(projectPath, "devbox.json"),
918
+ await fs3.writeFile(
919
+ path3.join(projectPath, "devbox.json"),
608
920
  JSON.stringify(devboxConfig, null, 2),
609
921
  "utf-8"
610
922
  );
611
923
  }
612
924
 
613
925
  // src/generators/devcontainer.ts
614
- import fs3 from "fs/promises";
615
- import path3 from "path";
926
+ import fs4 from "fs/promises";
927
+ import path4 from "path";
616
928
  async function generateDevContainer(projectPath) {
617
- const devcontainerDir = path3.join(projectPath, ".devcontainer");
618
- await fs3.mkdir(devcontainerDir, { recursive: true });
929
+ const devcontainerDir = path4.join(projectPath, ".devcontainer");
930
+ await fs4.mkdir(devcontainerDir, { recursive: true });
619
931
  const devcontainerConfig = {
620
932
  name: "RevealUI Development",
621
933
  image: "mcr.microsoft.com/devcontainers/typescript-node:24",
@@ -662,8 +974,8 @@ async function generateDevContainer(projectPath) {
662
974
  },
663
975
  remoteUser: "node"
664
976
  };
665
- await fs3.writeFile(
666
- path3.join(devcontainerDir, "devcontainer.json"),
977
+ await fs4.writeFile(
978
+ path4.join(devcontainerDir, "devcontainer.json"),
667
979
  JSON.stringify(devcontainerConfig, null, 2),
668
980
  "utf-8"
669
981
  );
@@ -692,7 +1004,7 @@ services:
692
1004
  volumes:
693
1005
  postgres-data:
694
1006
  `;
695
- await fs3.writeFile(path3.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
1007
+ await fs4.writeFile(path4.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
696
1008
  const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
697
1009
 
698
1010
  # Install additional tools
@@ -702,7 +1014,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
702
1014
  # Enable pnpm
703
1015
  RUN corepack enable
704
1016
  `;
705
- await fs3.writeFile(path3.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
1017
+ await fs4.writeFile(path4.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
706
1018
  const readme = `# Dev Container Setup
707
1019
 
708
1020
  This directory contains the Dev Container configuration for RevealUI.
@@ -744,12 +1056,12 @@ For GitHub Codespaces, set secrets in your repository settings.
744
1056
  - 4000: CMS
745
1057
  - 5432: PostgreSQL database
746
1058
  `;
747
- await fs3.writeFile(path3.join(devcontainerDir, "README.md"), readme, "utf-8");
1059
+ await fs4.writeFile(path4.join(devcontainerDir, "README.md"), readme, "utf-8");
748
1060
  }
749
1061
 
750
1062
  // src/generators/readme.ts
751
- import fs4 from "fs/promises";
752
- import path4 from "path";
1063
+ import fs5 from "fs/promises";
1064
+ import path5 from "path";
753
1065
  async function generateReadme(projectPath, projectConfig) {
754
1066
  const readme = `# ${projectConfig.projectName}
755
1067
 
@@ -821,31 +1133,31 @@ This project was created using the **${projectConfig.template}** template.
821
1133
 
822
1134
  MIT
823
1135
  `;
824
- await fs4.writeFile(path4.join(projectPath, "README.md"), readme, "utf-8");
1136
+ await fs5.writeFile(path5.join(projectPath, "README.md"), readme, "utf-8");
825
1137
  }
826
1138
 
827
1139
  // src/installers/dependencies.ts
828
- import { createLogger as createLogger4 } from "@revealui/setup/utils";
829
- import { execa } from "execa";
1140
+ import { createLogger as createLogger5 } from "@revealui/setup/utils";
1141
+ import { execa as execa3 } from "execa";
830
1142
  import ora from "ora";
831
- var logger4 = createLogger4({ prefix: "Install" });
1143
+ var logger5 = createLogger5({ prefix: "Install" });
832
1144
  async function installDependencies(projectPath) {
833
1145
  const spinner = ora("Installing dependencies with pnpm...").start();
834
1146
  try {
835
- await execa("pnpm", ["install"], {
1147
+ await execa3("pnpm", ["install"], {
836
1148
  cwd: projectPath,
837
1149
  stdio: "pipe"
838
1150
  });
839
1151
  spinner.succeed("Dependencies installed successfully");
840
1152
  } catch (error) {
841
1153
  spinner.fail("Failed to install dependencies");
842
- logger4.error('Please run "pnpm install" manually');
1154
+ logger5.error('Please run "pnpm install" manually');
843
1155
  throw error;
844
1156
  }
845
1157
  }
846
1158
  async function isPnpmInstalled() {
847
1159
  try {
848
- await execa("pnpm", ["--version"]);
1160
+ await execa3("pnpm", ["--version"]);
849
1161
  return true;
850
1162
  } catch {
851
1163
  return false;
@@ -853,31 +1165,31 @@ async function isPnpmInstalled() {
853
1165
  }
854
1166
 
855
1167
  // 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" });
1168
+ import { createLogger as createLogger6 } from "@revealui/setup/utils";
1169
+ import { execa as execa4 } from "execa";
1170
+ var logger6 = createLogger6({ prefix: "Git" });
859
1171
  async function initializeGitRepo(projectPath) {
860
1172
  try {
861
- await execa2("git", ["init"], { cwd: projectPath });
862
- logger5.success("Initialized git repository");
1173
+ await execa4("git", ["init"], { cwd: projectPath });
1174
+ logger6.success("Initialized git repository");
863
1175
  } catch (error) {
864
- logger5.warn("Failed to initialize git repository");
1176
+ logger6.warn("Failed to initialize git repository");
865
1177
  throw error;
866
1178
  }
867
1179
  }
868
1180
  async function createInitialCommit(projectPath) {
869
1181
  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");
1182
+ await execa4("git", ["add", "."], { cwd: projectPath });
1183
+ await execa4("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
1184
+ logger6.success("Created initial commit");
873
1185
  } catch (error) {
874
- logger5.warn("Failed to create initial commit");
1186
+ logger6.warn("Failed to create initial commit");
875
1187
  throw error;
876
1188
  }
877
1189
  }
878
1190
  async function isGitInstalled() {
879
1191
  try {
880
- await execa2("git", ["--version"]);
1192
+ await execa4("git", ["--version"]);
881
1193
  return true;
882
1194
  } catch {
883
1195
  return false;
@@ -885,10 +1197,10 @@ async function isGitInstalled() {
885
1197
  }
886
1198
 
887
1199
  // src/commands/create.ts
888
- var logger6 = createLogger6({ prefix: "Create" });
1200
+ var logger7 = createLogger7({ prefix: "Create" });
889
1201
  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");
1202
+ var __dirname2 = path6.dirname(__filename2);
1203
+ var TEMPLATES_DIR = existsSync(path6.resolve(__dirname2, "../../templates")) ? path6.resolve(__dirname2, "../../templates") : path6.resolve(__dirname2, "../templates");
892
1204
  function buildEnvLocal(cfg) {
893
1205
  const lines = [
894
1206
  "# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
@@ -934,16 +1246,16 @@ function generateSecret() {
934
1246
  }
935
1247
  async function listAvailableTemplates() {
936
1248
  try {
937
- const entries = await fs5.readdir(TEMPLATES_DIR, { withFileTypes: true });
1249
+ const entries = await fs6.readdir(TEMPLATES_DIR, { withFileTypes: true });
938
1250
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
939
1251
  } catch {
940
1252
  return [];
941
1253
  }
942
1254
  }
943
1255
  async function copyTemplate(templateName, targetPath) {
944
- const templatePath = path5.join(TEMPLATES_DIR, templateName);
1256
+ const templatePath = path6.join(TEMPLATES_DIR, templateName);
945
1257
  try {
946
- await fs5.access(templatePath);
1258
+ await fs6.access(templatePath);
947
1259
  } catch {
948
1260
  const available = await listAvailableTemplates();
949
1261
  const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
@@ -952,16 +1264,16 @@ async function copyTemplate(templateName, targetPath) {
952
1264
  await copyDir(templatePath, targetPath);
953
1265
  }
954
1266
  async function copyDir(src, dest) {
955
- await fs5.mkdir(dest, { recursive: true });
956
- const entries = await fs5.readdir(src, { withFileTypes: true });
1267
+ await fs6.mkdir(dest, { recursive: true });
1268
+ const entries = await fs6.readdir(src, { withFileTypes: true });
957
1269
  for (const entry of entries) {
958
- const srcPath = path5.join(src, entry.name);
1270
+ const srcPath = path6.join(src, entry.name);
959
1271
  const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
960
- const destPath = path5.join(dest, destName);
1272
+ const destPath = path6.join(dest, destName);
961
1273
  if (entry.isDirectory()) {
962
1274
  await copyDir(srcPath, destPath);
963
1275
  } else {
964
- await fs5.copyFile(srcPath, destPath);
1276
+ await fs6.copyFile(srcPath, destPath);
965
1277
  }
966
1278
  }
967
1279
  }
@@ -974,7 +1286,17 @@ function resolveTemplateName(template) {
974
1286
  return map[template] ?? "starter";
975
1287
  }
976
1288
  async function pullContentRules(projectPath) {
977
- const baseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
1289
+ const rawBaseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
1290
+ let baseUrl;
1291
+ try {
1292
+ const parsed = new URL(rawBaseUrl);
1293
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
1294
+ return;
1295
+ }
1296
+ baseUrl = rawBaseUrl;
1297
+ } catch {
1298
+ return;
1299
+ }
978
1300
  const spinner = ora2("Pulling AI coding rules...").start();
979
1301
  try {
980
1302
  const manifestRes = await fetch(`${baseUrl}/manifest.json`, {
@@ -988,18 +1310,27 @@ async function pullContentRules(projectPath) {
988
1310
  const generatorId = "claude-code";
989
1311
  const ossDefs = manifest.definitions.filter((d) => d.tier === "oss");
990
1312
  let written = 0;
1313
+ const allowedExtensions = /* @__PURE__ */ new Set([".md", ".json", ".txt", ".yaml", ".yml", ".ts", ".js"]);
1314
+ const maxFileSize = 1048576;
991
1315
  for (const def of ossDefs) {
992
1316
  const paths = def.generatorPaths[generatorId] ?? [];
993
1317
  for (const relPath of paths) {
994
1318
  try {
1319
+ if (relPath.includes("..") || relPath.startsWith("/")) continue;
1320
+ const ext = path6.extname(relPath).toLowerCase();
1321
+ if (!allowedExtensions.has(ext)) continue;
1322
+ const absolutePath = path6.resolve(projectPath, relPath);
1323
+ if (!absolutePath.startsWith(path6.resolve(projectPath))) continue;
995
1324
  const fileRes = await fetch(`${baseUrl}/generators/${generatorId}/${relPath}`, {
996
1325
  signal: AbortSignal.timeout(5e3)
997
1326
  });
998
1327
  if (!fileRes.ok) continue;
1328
+ const contentLength = fileRes.headers.get("content-length");
1329
+ if (contentLength && Number.parseInt(contentLength, 10) > maxFileSize) continue;
999
1330
  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");
1331
+ if (content.length > maxFileSize) continue;
1332
+ await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
1333
+ await fs6.writeFile(absolutePath, content, "utf-8");
1003
1334
  written++;
1004
1335
  } catch {
1005
1336
  }
@@ -1025,58 +1356,58 @@ async function createProject(cfg) {
1025
1356
  spinner.fail("Failed to copy template files");
1026
1357
  throw err;
1027
1358
  }
1028
- const pkgJsonPath = path5.join(projectPath, "package.json");
1359
+ const pkgJsonPath = path6.join(projectPath, "package.json");
1029
1360
  try {
1030
- const raw = await fs5.readFile(pkgJsonPath, "utf-8");
1031
- await fs5.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
1361
+ const raw = await fs6.readFile(pkgJsonPath, "utf-8");
1362
+ await fs6.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
1032
1363
  } catch {
1033
1364
  }
1034
1365
  const envSpinner = ora2("Writing .env.local...").start();
1035
1366
  try {
1036
- await fs5.writeFile(path5.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
1367
+ await fs6.writeFile(path6.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
1037
1368
  envSpinner.succeed(".env.local written");
1038
1369
  } catch (err) {
1039
1370
  envSpinner.fail("Failed to write .env.local");
1040
1371
  throw err;
1041
1372
  }
1042
1373
  await generateReadme(projectPath, project);
1043
- logger6.success("README.md generated");
1374
+ logger7.success("README.md generated");
1044
1375
  if (cfg.devenv.createDevContainer) {
1045
1376
  await generateDevContainer(projectPath);
1046
- logger6.success(".devcontainer/ generated");
1377
+ logger7.success(".devcontainer/ generated");
1047
1378
  }
1048
1379
  if (cfg.devenv.createDevbox) {
1049
1380
  await generateDevbox(projectPath);
1050
- logger6.success("devbox.json generated");
1381
+ logger7.success("devbox.json generated");
1051
1382
  }
1052
1383
  await pullContentRules(projectPath);
1053
1384
  if (!skipInstall) {
1054
1385
  const pnpmOk = await isPnpmInstalled();
1055
1386
  if (!pnpmOk) {
1056
- logger6.warn(
1387
+ logger7.warn(
1057
1388
  "pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
1058
1389
  );
1059
1390
  } else {
1060
1391
  await installDependencies(projectPath);
1061
1392
  }
1062
1393
  } else {
1063
- logger6.info("Skipping dependency installation (--skip-install)");
1394
+ logger7.info("Skipping dependency installation (--skip-install)");
1064
1395
  }
1065
1396
  if (!skipGit) {
1066
1397
  const gitOk = await isGitInstalled();
1067
1398
  if (!gitOk) {
1068
- logger6.warn("git not found \u2014 skipping repository initialisation.");
1399
+ logger7.warn("git not found \u2014 skipping repository initialisation.");
1069
1400
  } else {
1070
1401
  await initializeGitRepo(projectPath);
1071
1402
  await createInitialCommit(projectPath);
1072
1403
  }
1073
1404
  } else {
1074
- logger6.info("Skipping git initialisation (--skip-git)");
1405
+ logger7.info("Skipping git initialisation (--skip-git)");
1075
1406
  }
1076
1407
  }
1077
1408
 
1078
1409
  // src/commands/create-flow.ts
1079
- var logger7 = createLogger7({ prefix: "@revealui/cli" });
1410
+ var logger8 = createLogger8({ prefix: "@revealui/cli" });
1080
1411
  var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
1081
1412
  async function checkProLicense() {
1082
1413
  let key = process.env.REVEALUI_LICENSE_KEY;
@@ -1114,108 +1445,113 @@ async function checkProLicense() {
1114
1445
  }
1115
1446
  }
1116
1447
  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("");
1448
+ logger8.divider();
1449
+ logger8.info(" RevealUI \u2014 Agentic Business Runtime");
1450
+ logger8.info(" Build your business, not your boilerplate.");
1451
+ logger8.divider();
1452
+ logger8.info("");
1122
1453
  }
1123
1454
  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();
1455
+ logger8.divider();
1456
+ logger8.success("Your RevealUI project is ready!");
1457
+ logger8.info("");
1458
+ logger8.info(" Quick start:");
1459
+ logger8.info(` cd ${projectName}`);
1460
+ logger8.info(" pnpm install");
1461
+ logger8.info(" pnpm dev");
1462
+ logger8.info("");
1463
+ logger8.info(" What was created:");
1464
+ logger8.info(` ./${projectName}/ \u2014 project root`);
1465
+ logger8.info(` ./${projectName}/.env.local \u2014 environment variables (edit before pnpm dev)`);
1466
+ logger8.info(` ./${projectName}/README.md \u2014 getting started guide`);
1467
+ logger8.info("");
1468
+ logger8.info(" RevealUI ecosystem:");
1469
+ logger8.info(" Studio: Native AI experience \u2014 agent hub, local inference, dev environment");
1470
+ logger8.info(" Terminal: TUI client \u2014 run `revealui terminal install`");
1471
+ logger8.info(" CMS: Admin dashboard at your-domain.com/admin");
1472
+ logger8.info("");
1473
+ logger8.info(" Helpful links:");
1474
+ logger8.info(" Docs: https://docs.revealui.com");
1475
+ logger8.info(" GitHub: https://github.com/RevealUIStudio/revealui");
1476
+ logger8.info(" Support: support@revealui.com");
1477
+ logger8.divider();
1142
1478
  }
1143
1479
  function formatCreateError(err) {
1144
1480
  const message = err instanceof Error ? err.message : String(err);
1145
1481
  const code = err instanceof Error && "code" in err ? err.code : void 0;
1146
- logger7.error("Project creation failed.");
1147
- logger7.info("");
1482
+ logger8.error("Project creation failed.");
1483
+ logger8.info("");
1148
1484
  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`");
1485
+ logger8.info(" Permission denied. Try:");
1486
+ logger8.info(" - Running from a directory you own");
1487
+ logger8.info(" - Checking folder permissions with `ls -la`");
1152
1488
  } 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");
1489
+ logger8.info(" A required file or directory was not found.");
1490
+ logger8.info(" - Check that you are in the correct directory");
1491
+ logger8.info(" - Try running `revealui doctor` to diagnose your environment");
1156
1492
  } else if (code === "ENOSPC") {
1157
- logger7.info(" Disk full. Free up space and try again.");
1493
+ logger8.info(" Disk full. Free up space and try again.");
1158
1494
  } else if (message.includes("fetch") || message.includes("ENOTFOUND") || message.includes("ETIMEDOUT")) {
1159
- logger7.info(" Network error. Check your internet connection and try again.");
1495
+ logger8.info(" Network error. Check your internet connection and try again.");
1160
1496
  } else {
1161
- logger7.info(` ${message}`);
1497
+ logger8.info(` ${message}`);
1162
1498
  }
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();
1499
+ logger8.info("");
1500
+ logger8.info(" Troubleshooting:");
1501
+ logger8.info(" revealui doctor \u2014 diagnose your environment");
1502
+ logger8.info(" https://docs.revealui.com/docs/TROUBLESHOOTING");
1503
+ logger8.info(" support@revealui.com \u2014 we are here to help");
1504
+ logger8.divider();
1169
1505
  }
1170
1506
  async function runCreateFlow(projectName, options) {
1171
1507
  printBanner();
1172
1508
  try {
1173
- logger7.info("[1/8] Validating Node.js version...");
1509
+ logger8.info("[1/8] Validating Node.js version...");
1174
1510
  if (!validateNodeVersion()) {
1175
1511
  process.exit(1);
1176
1512
  }
1177
- logger7.success(`Node.js version: ${process.version}`);
1178
- logger7.info("[2/8] Configure your project");
1513
+ logger8.success(`Node.js version: ${process.version}`);
1514
+ logger8.info("[2/8] Configure your project");
1179
1515
  const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
1180
- logger7.success(`Project: ${projectConfig.projectName}`);
1181
- logger7.success(`Template: ${projectConfig.template}`);
1516
+ logger8.success(`Project: ${projectConfig.projectName}`);
1517
+ logger8.success(`Template: ${projectConfig.template}`);
1182
1518
  if (PRO_TEMPLATES.has(projectConfig.template)) {
1183
1519
  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>");
1520
+ logger8.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
1521
+ logger8.info("Get Pro at https://revealui.com/pricing");
1522
+ logger8.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
1187
1523
  process.exit(2);
1188
1524
  }
1189
- logger7.success("Pro license verified");
1525
+ logger8.success("Pro license verified");
1190
1526
  }
1191
1527
  const nonInteractive = options.yes === true;
1192
- logger7.info("[3/8] Configure database");
1528
+ logger8.info("[3/8] Configure database");
1193
1529
  const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
1194
1530
  if (databaseConfig.provider !== "skip") {
1195
- logger7.success(`Database: ${databaseConfig.provider}`);
1531
+ logger8.success(`Database: ${databaseConfig.provider}`);
1196
1532
  } else {
1197
- logger7.info("Database configuration skipped");
1533
+ logger8.info("Database configuration skipped");
1198
1534
  }
1199
- logger7.info("[4/8] Configure storage");
1535
+ logger8.info("[4/8] Configure storage");
1200
1536
  const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
1201
1537
  if (storageConfig.provider !== "skip") {
1202
- logger7.success(`Storage: ${storageConfig.provider}`);
1538
+ logger8.success(`Storage: ${storageConfig.provider}`);
1203
1539
  } else {
1204
- logger7.info("Storage configuration skipped");
1540
+ logger8.info("Storage configuration skipped");
1205
1541
  }
1206
- logger7.info("[5/8] Configure payments");
1542
+ logger8.info("[5/8] Configure payments");
1207
1543
  const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
1208
1544
  if (paymentConfig.enabled) {
1209
- logger7.success("Stripe configured");
1545
+ logger8.success("Stripe configured");
1210
1546
  } else {
1211
- logger7.info("Payments disabled");
1547
+ logger8.info("Payments disabled");
1212
1548
  }
1213
- logger7.info("[6/8] Configure development environment");
1549
+ logger8.info("[6/8] Configure development environment");
1214
1550
  const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
1215
- logger7.success(
1551
+ logger8.success(
1216
1552
  `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
1217
1553
  );
1218
- logger7.info("[7/8] Creating project...");
1554
+ logger8.info("[7/8] Creating project...");
1219
1555
  await createProject({
1220
1556
  project: projectConfig,
1221
1557
  database: databaseConfig,
@@ -1225,8 +1561,8 @@ async function runCreateFlow(projectName, options) {
1225
1561
  skipGit: options.skipGit,
1226
1562
  skipInstall: options.skipInstall
1227
1563
  });
1228
- logger7.success("Project created successfully");
1229
- logger7.info("[8/8] Done!");
1564
+ logger8.success("Project created successfully");
1565
+ logger8.info("[8/8] Done!");
1230
1566
  printPostCreateSummary(projectConfig.projectName);
1231
1567
  } catch (err) {
1232
1568
  formatCreateError(err);
@@ -1235,45 +1571,16 @@ async function runCreateFlow(projectName, options) {
1235
1571
  }
1236
1572
 
1237
1573
  // 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
- }
1574
+ import fs9 from "fs/promises";
1575
+ import path9 from "path";
1576
+ import { createLogger as createLogger9 } from "@revealui/setup/utils";
1577
+ import { execa as execa5 } from "execa";
1271
1578
 
1272
1579
  // src/utils/db.ts
1273
- import fs6 from "fs/promises";
1274
- import path6 from "path";
1580
+ import fs7 from "fs/promises";
1581
+ import path7 from "path";
1275
1582
  function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
1276
- const pgdata = env.PGDATA || path6.join(cwd, ".pgdata");
1583
+ const pgdata = env.PGDATA || path7.join(cwd, ".pgdata");
1277
1584
  const pghost = env.PGHOST || pgdata;
1278
1585
  const pgdatabase = env.PGDATABASE || "postgres";
1279
1586
  const pguser = env.PGUSER || "postgres";
@@ -1320,27 +1627,27 @@ host all all ::1/128 trust
1320
1627
  `.trimStart();
1321
1628
  }
1322
1629
  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");
1630
+ await fs7.appendFile(path7.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1631
+ await fs7.writeFile(path7.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1325
1632
  }
1326
1633
 
1327
1634
  // src/utils/workspace.ts
1328
- import fs7 from "fs";
1329
- import path7 from "path";
1635
+ import fs8 from "fs";
1636
+ import path8 from "path";
1330
1637
  function findWorkspaceRoot(startDir = process.cwd()) {
1331
- let current = path7.resolve(startDir);
1638
+ let current = path8.resolve(startDir);
1332
1639
  while (true) {
1333
- const packageJsonPath = path7.join(current, "package.json");
1334
- if (fs7.existsSync(packageJsonPath)) {
1640
+ const packageJsonPath = path8.join(current, "package.json");
1641
+ if (fs8.existsSync(packageJsonPath)) {
1335
1642
  try {
1336
- const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
1643
+ const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
1337
1644
  if (pkg.name === "revealui" && pkg.private === true) {
1338
1645
  return current;
1339
1646
  }
1340
1647
  } catch {
1341
1648
  }
1342
1649
  }
1343
- const parent = path7.dirname(current);
1650
+ const parent = path8.dirname(current);
1344
1651
  if (parent === current) {
1345
1652
  return null;
1346
1653
  }
@@ -1349,7 +1656,7 @@ function findWorkspaceRoot(startDir = process.cwd()) {
1349
1656
  }
1350
1657
 
1351
1658
  // src/commands/db.ts
1352
- var logger8 = createLogger8({ prefix: "DB" });
1659
+ var logger9 = createLogger9({ prefix: "DB" });
1353
1660
  function isPgCtlNotRunningError(error) {
1354
1661
  return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
1355
1662
  }
@@ -1390,18 +1697,18 @@ async function runDbInitCommand(options = {}) {
1390
1697
  const pgdata = getPgDataOrThrow(env);
1391
1698
  await requireCommand("initdb");
1392
1699
  try {
1393
- await fs8.access(pgdata);
1700
+ await fs9.access(pgdata);
1394
1701
  if (!options.force) {
1395
1702
  throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
1396
1703
  }
1397
- await fs8.rm(pgdata, { recursive: true, force: true });
1704
+ await fs9.rm(pgdata, { recursive: true, force: true });
1398
1705
  } catch (error) {
1399
1706
  if (error instanceof Error && !error.message.includes("already initialized")) {
1400
1707
  } else if (error instanceof Error) {
1401
1708
  throw error;
1402
1709
  }
1403
1710
  }
1404
- await execa4(
1711
+ await execa5(
1405
1712
  "initdb",
1406
1713
  ["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
1407
1714
  {
@@ -1410,17 +1717,17 @@ async function runDbInitCommand(options = {}) {
1410
1717
  }
1411
1718
  );
1412
1719
  await writeLocalDbConfigs(pgdata);
1413
- logger8.success(`PostgreSQL initialized at ${pgdata}`);
1720
+ logger9.success(`PostgreSQL initialized at ${pgdata}`);
1414
1721
  }
1415
1722
  async function runDbStartCommand() {
1416
1723
  const root = getWorkspaceRootOrThrow();
1417
1724
  const env = buildDbEnv(root);
1418
1725
  const pgdata = getPgDataOrThrow(env);
1419
1726
  await requireCommand("pg_ctl");
1420
- await fs8.access(pgdata);
1421
- await execa4(
1727
+ await fs9.access(pgdata);
1728
+ await execa5(
1422
1729
  "pg_ctl",
1423
- ["start", "-D", pgdata, "-l", path8.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1730
+ ["start", "-D", pgdata, "-l", path9.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1424
1731
  {
1425
1732
  env,
1426
1733
  stdio: "inherit"
@@ -1432,7 +1739,7 @@ async function runDbStopCommand() {
1432
1739
  const env = buildDbEnv(root);
1433
1740
  const pgdata = getPgDataOrThrow(env);
1434
1741
  await requireCommand("pg_ctl");
1435
- await execa4("pg_ctl", ["stop", "-D", pgdata], {
1742
+ await execa5("pg_ctl", ["stop", "-D", pgdata], {
1436
1743
  env,
1437
1744
  stdio: "inherit"
1438
1745
  });
@@ -1443,13 +1750,13 @@ async function runDbStatusCommand() {
1443
1750
  const pgdata = getPgDataOrThrow(env);
1444
1751
  await requireCommand("pg_ctl");
1445
1752
  try {
1446
- await execa4("pg_ctl", ["status", "-D", pgdata], {
1753
+ await execa5("pg_ctl", ["status", "-D", pgdata], {
1447
1754
  env,
1448
1755
  stdio: "inherit"
1449
1756
  });
1450
1757
  } catch (error) {
1451
1758
  if (isPgCtlNotRunningError(error)) {
1452
- logger8.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1759
+ logger9.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1453
1760
  return;
1454
1761
  }
1455
1762
  throw error;
@@ -1464,17 +1771,17 @@ async function runDbResetCommand(options = {}) {
1464
1771
  const pgdata = getPgDataOrThrow(env);
1465
1772
  if (await commandExists("pg_ctl")) {
1466
1773
  try {
1467
- await execa4("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1774
+ await execa5("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1468
1775
  } catch {
1469
1776
  }
1470
1777
  }
1471
- await fs8.rm(pgdata, { recursive: true, force: true });
1778
+ await fs9.rm(pgdata, { recursive: true, force: true });
1472
1779
  await runDbInitCommand({ force: false });
1473
1780
  }
1474
1781
  async function runDbMigrateCommand() {
1475
1782
  const root = getWorkspaceRootOrThrow();
1476
1783
  const env = buildDbEnv(root);
1477
- await execa4("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1784
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1478
1785
  cwd: root,
1479
1786
  env,
1480
1787
  stdio: "inherit"
@@ -1485,7 +1792,7 @@ async function runDbCleanupCommand(options = {}) {
1485
1792
  const env = { ...process.env };
1486
1793
  if (options.dryRun) env.DRY_RUN = "true";
1487
1794
  if (options.tables) env.TABLES = options.tables;
1488
- await execa4("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1795
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1489
1796
  cwd: root,
1490
1797
  env,
1491
1798
  stdio: "inherit"
@@ -1493,8 +1800,8 @@ async function runDbCleanupCommand(options = {}) {
1493
1800
  }
1494
1801
 
1495
1802
  // src/commands/dev.ts
1496
- import { createLogger as createLogger11 } from "@revealui/setup/utils";
1497
- import { execa as execa6 } from "execa";
1803
+ import { createLogger as createLogger12 } from "@revealui/setup/utils";
1804
+ import { execa as execa7 } from "execa";
1498
1805
 
1499
1806
  // src/runtime/doctor.ts
1500
1807
  function getMcpDetail(env) {
@@ -1513,6 +1820,140 @@ function getMcpDetail(env) {
1513
1820
  detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
1514
1821
  };
1515
1822
  }
1823
+ var ENV_VAR_SPECS = [
1824
+ // Core
1825
+ {
1826
+ key: "REVEALUI_SECRET",
1827
+ label: "App secret",
1828
+ required: false
1829
+ },
1830
+ {
1831
+ key: "REVEALUI_ADMIN_EMAIL",
1832
+ label: "Admin email",
1833
+ required: false
1834
+ },
1835
+ // Database
1836
+ {
1837
+ key: "POSTGRES_URL",
1838
+ label: "PostgreSQL URL",
1839
+ required: true,
1840
+ validate: validateNeonUrl
1841
+ },
1842
+ // Stripe
1843
+ {
1844
+ key: "STRIPE_SECRET_KEY",
1845
+ label: "Stripe secret key",
1846
+ required: false,
1847
+ validate: validateStripeKey
1848
+ },
1849
+ {
1850
+ key: "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
1851
+ label: "Stripe publishable key",
1852
+ required: false,
1853
+ validate: (v) => ({
1854
+ valid: v.startsWith("pk_test_") || v.startsWith("pk_live_"),
1855
+ message: "Must start with pk_test_ or pk_live_"
1856
+ })
1857
+ },
1858
+ {
1859
+ key: "STRIPE_WEBHOOK_SECRET",
1860
+ label: "Stripe webhook secret",
1861
+ required: false,
1862
+ validate: (v) => ({
1863
+ valid: v.startsWith("whsec_"),
1864
+ message: "Must start with whsec_"
1865
+ })
1866
+ },
1867
+ // Supabase
1868
+ {
1869
+ key: "NEXT_PUBLIC_SUPABASE_URL",
1870
+ label: "Supabase URL",
1871
+ required: false,
1872
+ validate: validateSupabaseUrl
1873
+ },
1874
+ {
1875
+ key: "SUPABASE_SERVICE_ROLE_KEY",
1876
+ label: "Supabase service role key",
1877
+ required: false,
1878
+ validate: (v) => ({
1879
+ valid: v.startsWith("eyJ"),
1880
+ message: "Must be a JWT (starts with eyJ)"
1881
+ })
1882
+ },
1883
+ // Services
1884
+ {
1885
+ key: "RESEND_API_KEY",
1886
+ label: "Resend API key",
1887
+ required: false,
1888
+ validate: (v) => ({
1889
+ valid: v.startsWith("re_"),
1890
+ message: "Must start with re_"
1891
+ })
1892
+ },
1893
+ // npm
1894
+ {
1895
+ key: "NPM_TOKEN",
1896
+ label: "npm publish token",
1897
+ required: false,
1898
+ validate: validateNpmToken
1899
+ },
1900
+ // License
1901
+ {
1902
+ key: "REVEALUI_LICENSE_PRIVATE_KEY",
1903
+ label: "License signing key",
1904
+ required: false
1905
+ },
1906
+ // CRON
1907
+ {
1908
+ key: "REVEALUI_CRON_SECRET",
1909
+ label: "Cron secret",
1910
+ required: false
1911
+ },
1912
+ // AI (open-model inference)
1913
+ {
1914
+ key: "BITNET_BASE_URL",
1915
+ label: "BitNet base URL",
1916
+ required: false
1917
+ },
1918
+ {
1919
+ key: "OLLAMA_BASE_URL",
1920
+ label: "Ollama base URL",
1921
+ required: false
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,