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