@revealui/cli 0.3.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,21 +1,549 @@
1
1
  // src/cli.ts
2
- import { createLogger as createLogger11 } from "@revealui/setup/utils";
2
+ import { createLogger as createLogger14 } from "@revealui/setup/utils";
3
3
  import { Command } from "commander";
4
4
 
5
+ // src/commands/agent.ts
6
+ import { createInterface } from "readline";
7
+ import { createLogger } from "@revealui/setup/utils";
8
+ var logger = createLogger({ prefix: "agent" });
9
+ async function runAgentStatusCommand() {
10
+ const { available, provider, model, projectRoot } = await detectProvider();
11
+ logger.info(`Provider: ${provider}`);
12
+ logger.info(`Model: ${model}`);
13
+ logger.info(`Project root: ${projectRoot}`);
14
+ logger.info(`Available: ${available ? "yes" : "no"}`);
15
+ if (!available) {
16
+ logger.warn("No LLM provider detected. Start BitNet, Ollama, or set GROQ_API_KEY.");
17
+ }
18
+ }
19
+ async function runAgentHeadlessCommand(prompt) {
20
+ const deps = await loadProDeps();
21
+ if (!deps) return;
22
+ const { StreamingAgentRuntime, createLLMClientFromEnv, createCodingTools } = deps;
23
+ const projectRoot = process.cwd();
24
+ const tools = createCodingTools({ projectRoot });
25
+ const llmClient = createLLMClientFromEnv();
26
+ const agent = {
27
+ id: "revealui-coding-agent",
28
+ name: "RevealUI Coding Agent",
29
+ instructions: buildMinimalInstructions(),
30
+ tools,
31
+ config: {},
32
+ getContext: () => ({ projectRoot, workingDirectory: projectRoot })
33
+ };
34
+ const task = {
35
+ id: `task-${Date.now()}`,
36
+ type: "headless-prompt",
37
+ description: prompt
38
+ };
39
+ const runtime = new StreamingAgentRuntime({
40
+ maxIterations: 10,
41
+ timeout: 12e4
42
+ });
43
+ try {
44
+ for await (const chunk of runtime.streamTask(agent, task, llmClient)) {
45
+ switch (chunk.type) {
46
+ case "text":
47
+ if (chunk.content) process.stdout.write(chunk.content);
48
+ break;
49
+ case "tool_call_start":
50
+ if (chunk.toolCall) {
51
+ process.stderr.write(`
52
+ [tool] ${chunk.toolCall.name}
53
+ `);
54
+ }
55
+ break;
56
+ case "tool_call_result":
57
+ if (chunk.toolResult?.content) {
58
+ process.stderr.write(` \u2192 ${chunk.toolResult.content}
59
+ `);
60
+ }
61
+ break;
62
+ case "error":
63
+ process.stderr.write(`
64
+ [error] ${chunk.error}
65
+ `);
66
+ break;
67
+ case "done":
68
+ process.stdout.write("\n");
69
+ break;
70
+ }
71
+ }
72
+ } finally {
73
+ await runtime.cleanup();
74
+ }
75
+ }
76
+ async function runAgentReplCommand() {
77
+ const deps = await loadProDeps();
78
+ if (!deps) return;
79
+ const { StreamingAgentRuntime, createLLMClientFromEnv, createCodingTools } = deps;
80
+ const projectRoot = process.cwd();
81
+ const tools = createCodingTools({ projectRoot });
82
+ const llmClient = createLLMClientFromEnv();
83
+ const agent = {
84
+ id: "revealui-coding-agent",
85
+ name: "RevealUI Coding Agent",
86
+ instructions: buildMinimalInstructions(),
87
+ tools,
88
+ config: {},
89
+ getContext: () => ({ projectRoot, workingDirectory: projectRoot })
90
+ };
91
+ logger.info('RevealUI Agent (type "exit" or Ctrl+C to quit)');
92
+ const rl = createInterface({
93
+ input: process.stdin,
94
+ output: process.stdout,
95
+ prompt: "\n> "
96
+ });
97
+ rl.prompt();
98
+ rl.on("line", async (line) => {
99
+ const input = line.trim();
100
+ if (!input) {
101
+ rl.prompt();
102
+ return;
103
+ }
104
+ if (input === "exit" || input === "quit") {
105
+ rl.close();
106
+ return;
107
+ }
108
+ const task = {
109
+ id: `task-${Date.now()}`,
110
+ type: "repl-prompt",
111
+ description: input
112
+ };
113
+ const runtime = new StreamingAgentRuntime({
114
+ maxIterations: 10,
115
+ timeout: 12e4
116
+ });
117
+ try {
118
+ for await (const chunk of runtime.streamTask(agent, task, llmClient)) {
119
+ switch (chunk.type) {
120
+ case "text":
121
+ if (chunk.content) process.stdout.write(chunk.content);
122
+ break;
123
+ case "tool_call_start":
124
+ if (chunk.toolCall) {
125
+ process.stderr.write(`
126
+ [tool] ${chunk.toolCall.name}
127
+ `);
128
+ }
129
+ break;
130
+ case "tool_call_result":
131
+ if (chunk.toolResult?.content) {
132
+ process.stderr.write(` \u2192 ${chunk.toolResult.content}
133
+ `);
134
+ }
135
+ break;
136
+ case "error":
137
+ process.stderr.write(`
138
+ [error] ${chunk.error}
139
+ `);
140
+ break;
141
+ case "done":
142
+ break;
143
+ }
144
+ }
145
+ } catch (err) {
146
+ logger.error(err instanceof Error ? err.message : String(err));
147
+ } finally {
148
+ await runtime.cleanup();
149
+ }
150
+ rl.prompt();
151
+ });
152
+ rl.on("close", () => {
153
+ process.exit(0);
154
+ });
155
+ }
156
+ async function loadProDeps() {
157
+ const aiRuntimePath = "@revealui/ai/orchestration/streaming-runtime";
158
+ const aiClientPath = "@revealui/ai/llm/client";
159
+ const aiToolsPath = "@revealui/ai/tools/coding";
160
+ try {
161
+ const [runtimeMod, clientMod, toolsMod] = await Promise.all([
162
+ import(aiRuntimePath),
163
+ import(aiClientPath),
164
+ import(aiToolsPath)
165
+ ]);
166
+ return {
167
+ StreamingAgentRuntime: runtimeMod.StreamingAgentRuntime,
168
+ createLLMClientFromEnv: clientMod.createLLMClientFromEnv,
169
+ createCodingTools: toolsMod.createCodingTools
170
+ };
171
+ } catch {
172
+ logger.error(
173
+ "Pro packages not found. Install @revealui/ai to use the agent.\n npm install @revealui/ai"
174
+ );
175
+ return null;
176
+ }
177
+ }
178
+ async function detectProvider() {
179
+ const projectRoot = process.cwd();
180
+ if (process.env.BITNET_BASE_URL) {
181
+ try {
182
+ const res = await fetch(`${process.env.BITNET_BASE_URL}/v1/models`, {
183
+ signal: AbortSignal.timeout(2e3)
184
+ });
185
+ if (res.ok) {
186
+ return { available: true, provider: "bitnet", model: "bitnet-b1.58-2B-4T", projectRoot };
187
+ }
188
+ } catch {
189
+ }
190
+ }
191
+ const ollamaUrl = process.env.OLLAMA_BASE_URL ?? "http://localhost:11434";
192
+ try {
193
+ const res = await fetch(`${ollamaUrl}/api/tags`, {
194
+ signal: AbortSignal.timeout(2e3)
195
+ });
196
+ if (res.ok) {
197
+ return { available: true, provider: "ollama", model: "llama3.2:3b", projectRoot };
198
+ }
199
+ } catch {
200
+ }
201
+ if (process.env.GROQ_API_KEY) {
202
+ return { available: true, provider: "groq", model: "llama-3.3-70b-versatile", projectRoot };
203
+ }
204
+ return { available: false, provider: "none", model: "none", projectRoot };
205
+ }
206
+ function buildMinimalInstructions() {
207
+ return [
208
+ "You are the RevealUI coding agent. You help with software development tasks in this project.",
209
+ "Use the available tools to read, write, edit, search, and execute commands.",
210
+ "Always read files before modifying them. Prefer surgical edits over full rewrites.",
211
+ "Follow project conventions discovered via the project_context tool.",
212
+ "Be concise and direct. Show your work through tool usage, not verbose explanations."
213
+ ].join("\n");
214
+ }
215
+
216
+ // src/commands/auth.ts
217
+ import fs from "fs/promises";
218
+ import os from "os";
219
+ import path from "path";
220
+ import { createLogger as createLogger2 } from "@revealui/setup/utils";
221
+ import { execa as execa2 } from "execa";
222
+
223
+ // src/utils/command.ts
224
+ import net from "net";
225
+ import { execa } from "execa";
226
+ async function commandExists(command) {
227
+ try {
228
+ await execa("bash", ["-lc", `command -v ${command}`], {
229
+ stdio: "pipe"
230
+ });
231
+ return true;
232
+ } catch {
233
+ return false;
234
+ }
235
+ }
236
+ async function isTcpReachable(host, port, timeoutMs = 1500) {
237
+ return await new Promise((resolve2) => {
238
+ const socket = new net.Socket();
239
+ const finalize = (value) => {
240
+ socket.removeAllListeners();
241
+ socket.destroy();
242
+ resolve2(value);
243
+ };
244
+ socket.setTimeout(timeoutMs);
245
+ socket.once("connect", () => finalize(true));
246
+ socket.once("timeout", () => finalize(false));
247
+ socket.once("error", () => finalize(false));
248
+ socket.connect(port, host);
249
+ });
250
+ }
251
+
252
+ // src/commands/auth.ts
253
+ var logger2 = createLogger2({ prefix: "Auth" });
254
+ var REVVAULT_NPM_PATH = "revealui/env/npm";
255
+ var NPM_TOKEN_INTERPOLATION = "${NPM_TOKEN}";
256
+ var NPMRC_AUTH_LINE = `//registry.npmjs.org/:_authToken=${NPM_TOKEN_INTERPOLATION}`;
257
+ function write(text5) {
258
+ process.stdout.write(text5);
259
+ }
260
+ function writeJson(data) {
261
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
262
+ `);
263
+ }
264
+ function getNpmrcPaths() {
265
+ const user = path.join(os.homedir(), ".npmrc");
266
+ let dir = process.cwd();
267
+ let project = null;
268
+ for (let i = 0; i < 10; i++) {
269
+ const candidate = path.join(dir, ".npmrc");
270
+ if (dir !== os.homedir()) {
271
+ project ??= candidate;
272
+ }
273
+ const parent = path.dirname(dir);
274
+ if (parent === dir) break;
275
+ dir = parent;
276
+ }
277
+ return { user, project };
278
+ }
279
+ async function fileContains(filePath, needle) {
280
+ try {
281
+ const content = await fs.readFile(filePath, "utf-8");
282
+ return content.includes(needle);
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+ async function getTokenFromEnv() {
288
+ return process.env.NPM_TOKEN || void 0;
289
+ }
290
+ async function getTokenFromNpmrc() {
291
+ const userRc = path.join(os.homedir(), ".npmrc");
292
+ try {
293
+ const content = await fs.readFile(userRc, "utf-8");
294
+ const match = content.match(/\/\/registry\.npmjs\.org\/:_authToken=(.+)/);
295
+ return match?.[1]?.trim() || void 0;
296
+ } catch {
297
+ return void 0;
298
+ }
299
+ }
300
+ function maskToken(token) {
301
+ if (token.length <= 8) return "***";
302
+ return `${token.slice(0, 4)}...${token.slice(-4)}`;
303
+ }
304
+ async function hasRevvault() {
305
+ return commandExists("revvault");
306
+ }
307
+ async function revvaultGet(secretPath) {
308
+ try {
309
+ const { stdout } = await execa2("revvault", ["get", secretPath], {
310
+ stdio: "pipe"
311
+ });
312
+ return stdout.trim() || void 0;
313
+ } catch {
314
+ return void 0;
315
+ }
316
+ }
317
+ async function revvaultSet(secretPath, value) {
318
+ try {
319
+ await execa2("revvault", ["set", secretPath], {
320
+ input: value,
321
+ stdio: ["pipe", "pipe", "pipe"]
322
+ });
323
+ return true;
324
+ } catch {
325
+ return false;
326
+ }
327
+ }
328
+ async function runAuthStatusCommand(options) {
329
+ const envToken = await getTokenFromEnv();
330
+ const npmrcToken = await getTokenFromNpmrc();
331
+ const hasVault = await hasRevvault();
332
+ const vaultValue = hasVault ? await revvaultGet(REVVAULT_NPM_PATH) : void 0;
333
+ const paths = getNpmrcPaths();
334
+ const projectUsesEnvVar = paths.project ? await fileContains(paths.project, NPM_TOKEN_INTERPOLATION) : false;
335
+ const effectiveToken = envToken || npmrcToken;
336
+ let npmUser;
337
+ try {
338
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
339
+ npmUser = stdout.trim();
340
+ } catch {
341
+ npmUser = void 0;
342
+ }
343
+ if (options.json) {
344
+ writeJson({
345
+ authenticated: !!npmUser,
346
+ user: npmUser ?? null,
347
+ tokenSource: envToken ? "env" : npmrcToken ? "npmrc" : null,
348
+ tokenMasked: effectiveToken ? maskToken(effectiveToken) : null,
349
+ revvaultConfigured: !!vaultValue,
350
+ projectNpmrcUsesEnvVar: projectUsesEnvVar,
351
+ paths: {
352
+ userNpmrc: paths.user,
353
+ projectNpmrc: paths.project
354
+ }
355
+ });
356
+ return;
357
+ }
358
+ logger2.header("npm Authentication Status");
359
+ if (npmUser) {
360
+ logger2.success(`Authenticated as: ${npmUser}`);
361
+ } else {
362
+ logger2.error("Not authenticated \u2014 npm whoami failed");
363
+ }
364
+ write("\n");
365
+ logger2.info(
366
+ `Token source: ${envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (file)" : "none"}`
367
+ );
368
+ if (effectiveToken) {
369
+ logger2.info(`Token: ${maskToken(effectiveToken)}`);
370
+ }
371
+ write("\n");
372
+ logger2.info(
373
+ `RevVault (${REVVAULT_NPM_PATH}): ${vaultValue ? "configured" : hasVault ? "not set" : "revvault not installed"}`
374
+ );
375
+ logger2.info(`Project .npmrc uses NPM_TOKEN: ${projectUsesEnvVar ? "yes" : "no"}`);
376
+ if (!projectUsesEnvVar && paths.project) {
377
+ logger2.warn(
378
+ "Project .npmrc does not reference NPM_TOKEN \u2014 direnv/RevVault tokens won't be used for publishing"
379
+ );
380
+ }
381
+ if (!vaultValue && hasVault && effectiveToken) {
382
+ logger2.warn("Token is not stored in RevVault. Run: revealui auth set-token to persist it.");
383
+ }
384
+ }
385
+ async function runAuthSetTokenCommand(options) {
386
+ logger2.header("Set npm Publish Token");
387
+ const token = options.token || process.env.NPM_TOKEN;
388
+ if (!token) {
389
+ logger2.error("No token provided. Pass --token <value> or set NPM_TOKEN in your environment.");
390
+ logger2.info("Create a Granular Access Token at: https://www.npmjs.com/settings/<user>/tokens");
391
+ logger2.info(" Type: Granular Access Token");
392
+ logger2.info(" Packages: All packages (read and write)");
393
+ logger2.info(" This bypasses 2FA \u2014 no OTP needed for publish");
394
+ process.exitCode = 1;
395
+ return;
396
+ }
397
+ if (!token.startsWith("npm_")) {
398
+ logger2.warn('Token does not start with "npm_" \u2014 this may not be a valid npm token');
399
+ }
400
+ if (!options.skipVault) {
401
+ const hasVault = await hasRevvault();
402
+ if (hasVault) {
403
+ const stored = await revvaultSet(REVVAULT_NPM_PATH, `NPM_TOKEN=${token}`);
404
+ if (stored) {
405
+ logger2.success(`Stored in RevVault (${REVVAULT_NPM_PATH})`);
406
+ } else {
407
+ logger2.error("Failed to store in RevVault");
408
+ }
409
+ } else {
410
+ logger2.warn("RevVault not installed \u2014 skipping vault storage");
411
+ }
412
+ }
413
+ if (!options.skipNpmrc) {
414
+ const paths = getNpmrcPaths();
415
+ if (paths.project) {
416
+ const hasInterpolation = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
417
+ if (!hasInterpolation) {
418
+ try {
419
+ const content = await fs.readFile(paths.project, "utf-8");
420
+ const updatedContent = `${content.trimEnd()}
421
+ ${NPMRC_AUTH_LINE}
422
+ `;
423
+ await fs.writeFile(paths.project, updatedContent, "utf-8");
424
+ logger2.success(`Added NPM_TOKEN interpolation to ${paths.project}`);
425
+ } catch (err) {
426
+ logger2.error(`Failed to update ${paths.project}: ${err}`);
427
+ }
428
+ } else {
429
+ logger2.info("Project .npmrc already uses NPM_TOKEN");
430
+ }
431
+ }
432
+ }
433
+ const userRc = path.join(os.homedir(), ".npmrc");
434
+ try {
435
+ const content = await fs.readFile(userRc, "utf-8");
436
+ const filtered = content.split("\n").filter((line) => !line.includes("//registry.npmjs.org/:_authToken=")).join("\n");
437
+ if (filtered !== content) {
438
+ await fs.writeFile(userRc, filtered, "utf-8");
439
+ logger2.success("Removed hardcoded token from ~/.npmrc (now managed via env var)");
440
+ }
441
+ } catch {
442
+ }
443
+ write("\n");
444
+ logger2.info("Verifying...");
445
+ process.env.NPM_TOKEN = token;
446
+ try {
447
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
448
+ logger2.success(`Authenticated as: ${stdout.trim()}`);
449
+ } catch {
450
+ logger2.error("npm whoami failed \u2014 token may be invalid or expired");
451
+ process.exitCode = 1;
452
+ return;
453
+ }
454
+ write("\n");
455
+ logger2.success("Token configured. Run `direnv reload` to load it in all terminals.");
456
+ }
457
+ async function runAuthVerifyCommand(options) {
458
+ const checks = [];
459
+ let npmUser;
460
+ try {
461
+ const { stdout } = await execa2("npm", ["whoami"], { stdio: "pipe" });
462
+ npmUser = stdout.trim();
463
+ checks.push({ name: "npm whoami", pass: true, detail: npmUser });
464
+ } catch {
465
+ checks.push({
466
+ name: "npm whoami",
467
+ pass: false,
468
+ detail: "Not authenticated"
469
+ });
470
+ }
471
+ const envToken = await getTokenFromEnv();
472
+ const npmrcToken = await getTokenFromNpmrc();
473
+ const source = envToken ? "$NPM_TOKEN (env)" : npmrcToken ? "~/.npmrc (hardcoded)" : "none";
474
+ checks.push({
475
+ name: "Token source",
476
+ pass: !!envToken,
477
+ detail: source + (npmrcToken && !envToken ? " \u2014 consider migrating to RevVault" : "")
478
+ });
479
+ const hasVault = await hasRevvault();
480
+ if (hasVault) {
481
+ const vaultValue = await revvaultGet(REVVAULT_NPM_PATH);
482
+ checks.push({
483
+ name: "RevVault",
484
+ pass: !!vaultValue,
485
+ detail: vaultValue ? `${REVVAULT_NPM_PATH} configured` : "Not stored in vault"
486
+ });
487
+ }
488
+ const paths = getNpmrcPaths();
489
+ if (paths.project) {
490
+ const usesEnvVar = await fileContains(paths.project, NPM_TOKEN_INTERPOLATION);
491
+ checks.push({
492
+ name: ".npmrc NPM_TOKEN",
493
+ pass: usesEnvVar,
494
+ detail: usesEnvVar ? "Project .npmrc uses env var" : "Missing \u2014 direnv token won't reach npm"
495
+ });
496
+ }
497
+ const envrcPath = path.join(process.cwd(), ".envrc");
498
+ const loadsNpm = await fileContains(envrcPath, REVVAULT_NPM_PATH);
499
+ checks.push({
500
+ name: ".envrc loads npm",
501
+ pass: loadsNpm,
502
+ detail: loadsNpm ? "revealui/env/npm in .envrc" : "Missing \u2014 direnv won't export NPM_TOKEN"
503
+ });
504
+ const hardcodedToken = await getTokenFromNpmrc();
505
+ checks.push({
506
+ name: "~/.npmrc clean",
507
+ pass: !hardcodedToken,
508
+ detail: hardcodedToken ? `Hardcoded token found (${maskToken(hardcodedToken)})` : "No hardcoded tokens"
509
+ });
510
+ if (options.json) {
511
+ const allPass2 = checks.every((c) => c.pass);
512
+ writeJson({ pass: allPass2, checks });
513
+ return;
514
+ }
515
+ logger2.header("npm Auth Verification");
516
+ for (const check of checks) {
517
+ if (check.pass) {
518
+ logger2.success(`${check.name}: ${check.detail}`);
519
+ } else {
520
+ logger2.error(`${check.name}: ${check.detail}`);
521
+ }
522
+ }
523
+ const allPass = checks.every((c) => c.pass);
524
+ write("\n");
525
+ if (allPass) {
526
+ logger2.success("All checks passed \u2014 ready to publish");
527
+ } else {
528
+ logger2.warn("Some checks failed. Run `revealui auth set-token` to fix.");
529
+ process.exitCode = 1;
530
+ }
531
+ }
532
+
5
533
  // src/commands/create-flow.ts
6
534
  import { readFileSync } from "fs";
7
535
  import { homedir } from "os";
8
536
  import { join } from "path";
9
- import { createLogger as createLogger6 } from "@revealui/setup/utils";
537
+ import { createLogger as createLogger8 } from "@revealui/setup/utils";
10
538
  import { importSPKI, jwtVerify } from "jose";
11
539
 
12
540
  // src/prompts/database.ts
13
- import inquirer from "inquirer";
541
+ import { isCancel, select, text } from "@clack/prompts";
14
542
 
15
543
  // src/validators/credentials.ts
16
- import { createLogger } from "@revealui/setup/utils";
17
- var logger = createLogger({ prefix: "Validator" });
18
- async function validateStripeKey(key) {
544
+ import { createLogger as createLogger3 } from "@revealui/setup/utils";
545
+ var logger3 = createLogger3({ prefix: "Validator" });
546
+ function validateStripeKey(key) {
19
547
  if (!(key.startsWith("sk_test_") || key.startsWith("sk_live_"))) {
20
548
  return {
21
549
  valid: false,
@@ -24,7 +552,7 @@ async function validateStripeKey(key) {
24
552
  }
25
553
  return { valid: true };
26
554
  }
27
- async function validateNeonUrl(url) {
555
+ function validateNeonUrl(url) {
28
556
  try {
29
557
  const parsed = new URL(url);
30
558
  if (!parsed.protocol.startsWith("postgres")) {
@@ -41,7 +569,7 @@ async function validateNeonUrl(url) {
41
569
  };
42
570
  }
43
571
  }
44
- async function validateVercelToken(token) {
572
+ function validateVercelToken(token) {
45
573
  if (!token || token.length < 20) {
46
574
  return {
47
575
  valid: false,
@@ -50,11 +578,11 @@ async function validateVercelToken(token) {
50
578
  }
51
579
  return { valid: true };
52
580
  }
53
- async function validateSupabaseUrl(url) {
581
+ function validateSupabaseUrl(url) {
54
582
  try {
55
583
  const parsed = new URL(url);
56
584
  if (!parsed.hostname.includes("supabase")) {
57
- logger.warn("URL does not appear to be a Supabase URL");
585
+ logger3.warn("URL does not appear to be a Supabase URL");
58
586
  }
59
587
  return { valid: true };
60
588
  } catch {
@@ -64,150 +592,152 @@ async function validateSupabaseUrl(url) {
64
592
  };
65
593
  }
66
594
  }
595
+ function validateNpmToken(token) {
596
+ if (!token.startsWith("npm_")) {
597
+ return {
598
+ valid: false,
599
+ message: "npm token must start with npm_"
600
+ };
601
+ }
602
+ if (token.length < 20) {
603
+ return {
604
+ valid: false,
605
+ message: "npm token appears invalid (too short)"
606
+ };
607
+ }
608
+ return { valid: true };
609
+ }
67
610
 
68
611
  // src/prompts/database.ts
69
612
  async function promptDatabaseConfig() {
70
- const { provider } = await inquirer.prompt([
71
- {
72
- type: "list",
73
- name: "provider",
74
- message: "Which database provider would you like to use?",
75
- choices: [
76
- {
77
- name: "NeonDB - Serverless PostgreSQL (recommended)",
78
- value: "neon"
79
- },
80
- {
81
- name: "Supabase - PostgreSQL with built-in features",
82
- value: "supabase"
83
- },
84
- {
85
- name: "Local PostgreSQL - Use existing local database",
86
- value: "local"
87
- },
88
- {
89
- name: "Skip - Configure later",
90
- value: "skip"
91
- }
92
- ],
93
- default: "neon"
94
- }
95
- ]);
613
+ const provider = await select({
614
+ message: "Which database provider would you like to use?",
615
+ options: [
616
+ { value: "neon", label: "NeonDB - Serverless PostgreSQL (recommended)" },
617
+ { value: "supabase", label: "Supabase - PostgreSQL with built-in features" },
618
+ { value: "local", label: "Local PostgreSQL - Use existing local database" },
619
+ { value: "skip", label: "Skip - Configure later" }
620
+ ],
621
+ initialValue: "neon"
622
+ });
623
+ if (isCancel(provider)) {
624
+ process.exit(0);
625
+ }
96
626
  if (provider === "skip") {
97
627
  return { provider: "skip" };
98
628
  }
99
629
  if (provider === "local") {
100
- const { postgresUrl: postgresUrl2 } = await inquirer.prompt([
101
- {
102
- type: "input",
103
- name: "postgresUrl",
104
- message: "Enter your PostgreSQL connection string:",
105
- default: "postgresql://postgres:postgres@localhost:5432/revealui",
106
- validate: async (input) => {
107
- const result = await validateNeonUrl(input);
108
- return result.valid ? true : result.message || "Invalid database URL";
109
- }
630
+ const postgresUrl2 = await text({
631
+ message: "Enter your PostgreSQL connection string:",
632
+ defaultValue: "postgresql://postgres:postgres@localhost:5432/revealui",
633
+ validate: (input) => {
634
+ if (!input) return void 0;
635
+ const result = validateNeonUrl(input);
636
+ return result.valid ? void 0 : result.message || "Invalid database URL";
110
637
  }
111
- ]);
638
+ });
639
+ if (isCancel(postgresUrl2)) {
640
+ process.exit(0);
641
+ }
112
642
  return { provider: "local", postgresUrl: postgresUrl2 };
113
643
  }
114
- const { postgresUrl } = await inquirer.prompt([
115
- {
116
- type: "input",
117
- name: "postgresUrl",
118
- message: `Enter your ${provider === "neon" ? "Neon" : "Supabase"} database connection string:`,
119
- validate: async (input) => {
120
- if (!input || input.trim() === "") {
121
- return "Database URL is required";
122
- }
123
- const result = await validateNeonUrl(input);
124
- return result.valid ? true : result.message || "Invalid database URL";
644
+ const label = provider === "neon" ? "Neon" : "Supabase";
645
+ const postgresUrl = await text({
646
+ message: `Enter your ${label} database connection string:`,
647
+ validate: (input) => {
648
+ if (!input || input.trim() === "") {
649
+ return "Database URL is required";
125
650
  }
651
+ const result = validateNeonUrl(input);
652
+ return result.valid ? void 0 : result.message || "Invalid database URL";
126
653
  }
127
- ]);
654
+ });
655
+ if (isCancel(postgresUrl)) {
656
+ process.exit(0);
657
+ }
128
658
  return { provider, postgresUrl };
129
659
  }
130
660
 
131
661
  // src/prompts/devenv.ts
132
- import inquirer2 from "inquirer";
662
+ import { confirm, isCancel as isCancel2 } from "@clack/prompts";
133
663
  async function promptDevEnvConfig() {
134
- const answers = await inquirer2.prompt([
135
- {
136
- type: "confirm",
137
- name: "createDevContainer",
138
- message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
139
- default: true
140
- },
141
- {
142
- type: "confirm",
143
- name: "createDevbox",
144
- message: "Create Devbox configuration for Nix-powered development?",
145
- default: true
146
- }
147
- ]);
148
- return answers;
664
+ const createDevContainer = await confirm({
665
+ message: "Create Dev Container configuration for VS Code / GitHub Codespaces?",
666
+ initialValue: true
667
+ });
668
+ if (isCancel2(createDevContainer)) {
669
+ process.exit(0);
670
+ }
671
+ const createDevbox = await confirm({
672
+ message: "Create Devbox configuration for Nix-powered development?",
673
+ initialValue: true
674
+ });
675
+ if (isCancel2(createDevbox)) {
676
+ process.exit(0);
677
+ }
678
+ return { createDevContainer, createDevbox };
149
679
  }
150
680
 
151
681
  // src/prompts/payments.ts
152
- import inquirer3 from "inquirer";
682
+ import { confirm as confirm2, isCancel as isCancel3, text as text2 } from "@clack/prompts";
153
683
  async function promptPaymentConfig() {
154
- const { enabled } = await inquirer3.prompt([
155
- {
156
- type: "confirm",
157
- name: "enabled",
158
- message: "Do you want to configure Stripe payments?",
159
- default: true
160
- }
161
- ]);
684
+ const enabled = await confirm2({
685
+ message: "Do you want to configure Stripe payments?",
686
+ initialValue: true
687
+ });
688
+ if (isCancel3(enabled)) {
689
+ process.exit(0);
690
+ }
162
691
  if (!enabled) {
163
692
  return { enabled: false };
164
693
  }
165
- const answers = await inquirer3.prompt([
166
- {
167
- type: "input",
168
- name: "stripeSecretKey",
169
- message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
170
- validate: async (input) => {
171
- if (!input || input.trim() === "") {
172
- return "Stripe secret key is required";
173
- }
174
- const result = await validateStripeKey(input);
175
- return result.valid ? true : result.message || "Invalid Stripe key";
694
+ const stripeSecretKey = await text2({
695
+ message: "Enter your Stripe secret key (sk_test_... or sk_live_...):",
696
+ validate: (input) => {
697
+ if (!input || input.trim() === "") {
698
+ return "Stripe secret key is required";
176
699
  }
177
- },
178
- {
179
- type: "input",
180
- name: "stripePublishableKey",
181
- message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
182
- validate: (input) => {
183
- if (!input || input.trim() === "") {
184
- return "Stripe publishable key is required";
185
- }
186
- if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
187
- return "Stripe publishable key must start with pk_test_ or pk_live_";
188
- }
189
- return true;
700
+ const result = validateStripeKey(input);
701
+ return result.valid ? void 0 : result.message || "Invalid Stripe key";
702
+ }
703
+ });
704
+ if (isCancel3(stripeSecretKey)) {
705
+ process.exit(0);
706
+ }
707
+ const stripePublishableKey = await text2({
708
+ message: "Enter your Stripe publishable key (pk_test_... or pk_live_...):",
709
+ validate: (input) => {
710
+ if (!input || input.trim() === "") {
711
+ return "Stripe publishable key is required";
190
712
  }
191
- },
192
- {
193
- type: "input",
194
- name: "stripeWebhookSecret",
195
- message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
196
- default: ""
713
+ if (!(input.startsWith("pk_test_") || input.startsWith("pk_live_"))) {
714
+ return "Stripe publishable key must start with pk_test_ or pk_live_";
715
+ }
716
+ return void 0;
197
717
  }
198
- ]);
718
+ });
719
+ if (isCancel3(stripePublishableKey)) {
720
+ process.exit(0);
721
+ }
722
+ const stripeWebhookSecret = await text2({
723
+ message: "Enter your Stripe webhook secret (whsec_..., optional - press Enter to skip):",
724
+ defaultValue: ""
725
+ });
726
+ if (isCancel3(stripeWebhookSecret)) {
727
+ process.exit(0);
728
+ }
199
729
  return {
200
730
  enabled: true,
201
- stripeSecretKey: answers.stripeSecretKey,
202
- stripePublishableKey: answers.stripePublishableKey,
203
- stripeWebhookSecret: answers.stripeWebhookSecret || void 0
731
+ stripeSecretKey,
732
+ stripePublishableKey,
733
+ stripeWebhookSecret: stripeWebhookSecret || void 0
204
734
  };
205
735
  }
206
736
 
207
737
  // src/prompts/project.ts
208
- import fs from "fs";
209
- import path from "path";
210
- import inquirer4 from "inquirer";
738
+ import fs2 from "fs";
739
+ import path2 from "path";
740
+ import { isCancel as isCancel4, select as select2, text as text3 } from "@clack/prompts";
211
741
  var VALID_TEMPLATES = ["basic-blog", "e-commerce", "portfolio"];
212
742
  async function promptProjectConfig(defaultName, templateArg, nonInteractive = false) {
213
743
  let projectName;
@@ -216,25 +746,31 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
216
746
  } else if (nonInteractive) {
217
747
  projectName = "my-revealui-project";
218
748
  } else {
219
- const answers = await inquirer4.prompt([
220
- {
221
- type: "input",
222
- name: "projectName",
223
- message: "What is your project name?",
224
- default: "my-revealui-project",
225
- validate: (input) => {
226
- if (!/^[a-z0-9-]+$/.test(input)) {
227
- return "Project name must contain only lowercase letters, numbers, and hyphens";
228
- }
229
- const projectPath = path.resolve(process.cwd(), input);
230
- if (fs.existsSync(projectPath)) {
231
- return `Directory "${input}" already exists`;
232
- }
233
- return true;
749
+ const name = await text3({
750
+ message: "What is your project name?",
751
+ defaultValue: "my-revealui-project",
752
+ validate: (input) => {
753
+ if (!input) return void 0;
754
+ const isValid = input.split("").every((ch) => {
755
+ const code = ch.charCodeAt(0);
756
+ return code >= 97 && code <= 122 || // a-z
757
+ code >= 48 && code <= 57 || // 0-9
758
+ code === 45;
759
+ });
760
+ if (!isValid) {
761
+ return "Project name must contain only lowercase letters, numbers, and hyphens";
234
762
  }
763
+ const projectPath = path2.resolve(process.cwd(), input);
764
+ if (fs2.existsSync(projectPath)) {
765
+ return `Directory "${input}" already exists`;
766
+ }
767
+ return void 0;
235
768
  }
236
- ]);
237
- projectName = answers.projectName;
769
+ });
770
+ if (isCancel4(name)) {
771
+ process.exit(0);
772
+ }
773
+ projectName = name;
238
774
  }
239
775
  let template;
240
776
  if (templateArg && VALID_TEMPLATES.includes(templateArg)) {
@@ -242,118 +778,106 @@ async function promptProjectConfig(defaultName, templateArg, nonInteractive = fa
242
778
  } else if (nonInteractive) {
243
779
  template = "basic-blog";
244
780
  } else {
245
- const answers = await inquirer4.prompt([
246
- {
247
- type: "list",
248
- name: "template",
249
- message: "Which template would you like to use?",
250
- choices: [
251
- { name: "Basic Blog - A simple blog with posts and pages", value: "basic-blog" },
252
- { name: "E-commerce - Product catalog with checkout", value: "e-commerce" },
253
- { name: "Portfolio - Personal portfolio site", value: "portfolio" }
254
- ],
255
- default: "basic-blog"
256
- }
257
- ]);
258
- template = answers.template;
781
+ const selected = await select2({
782
+ message: "Which template would you like to use?",
783
+ options: [
784
+ { value: "basic-blog", label: "Basic Blog - A simple blog with posts and pages" },
785
+ { value: "e-commerce", label: "E-commerce - Product catalog with checkout" },
786
+ { value: "portfolio", label: "Portfolio - Personal portfolio site" }
787
+ ],
788
+ initialValue: "basic-blog"
789
+ });
790
+ if (isCancel4(selected)) {
791
+ process.exit(0);
792
+ }
793
+ template = selected;
259
794
  }
260
795
  return {
261
796
  projectName,
262
- projectPath: path.resolve(process.cwd(), projectName),
797
+ projectPath: path2.resolve(process.cwd(), projectName),
263
798
  template
264
799
  };
265
800
  }
266
801
 
267
802
  // src/prompts/storage.ts
268
- import inquirer5 from "inquirer";
803
+ import { isCancel as isCancel5, select as select3, text as text4 } from "@clack/prompts";
269
804
  async function promptStorageConfig() {
270
- const { provider } = await inquirer5.prompt([
271
- {
272
- type: "list",
273
- name: "provider",
274
- message: "Which storage provider would you like to use?",
275
- choices: [
276
- {
277
- name: "Vercel Blob - Simple object storage (recommended)",
278
- value: "vercel-blob"
279
- },
280
- {
281
- name: "Supabase Storage - Integrated with Supabase",
282
- value: "supabase"
283
- },
284
- {
285
- name: "Skip - Configure later",
286
- value: "skip"
287
- }
288
- ],
289
- default: "vercel-blob"
290
- }
291
- ]);
805
+ const provider = await select3({
806
+ message: "Which storage provider would you like to use?",
807
+ options: [
808
+ { value: "vercel-blob", label: "Vercel Blob - Simple object storage (recommended)" },
809
+ { value: "supabase", label: "Supabase Storage - Integrated with Supabase" },
810
+ { value: "skip", label: "Skip - Configure later" }
811
+ ],
812
+ initialValue: "vercel-blob"
813
+ });
814
+ if (isCancel5(provider)) {
815
+ process.exit(0);
816
+ }
292
817
  if (provider === "skip") {
293
818
  return { provider: "skip" };
294
819
  }
295
820
  if (provider === "vercel-blob") {
296
- const { blobToken } = await inquirer5.prompt([
297
- {
298
- type: "input",
299
- name: "blobToken",
300
- message: "Enter your Vercel Blob read/write token:",
301
- validate: async (input) => {
302
- if (!input || input.trim() === "") {
303
- return "Blob token is required";
304
- }
305
- const result = await validateVercelToken(input);
306
- return result.valid ? true : result.message || "Invalid token";
821
+ const blobToken = await text4({
822
+ message: "Enter your Vercel Blob read/write token:",
823
+ validate: (input) => {
824
+ if (!input || input.trim() === "") {
825
+ return "Blob token is required";
307
826
  }
827
+ const result = validateVercelToken(input);
828
+ return result.valid ? void 0 : result.message || "Invalid token";
308
829
  }
309
- ]);
830
+ });
831
+ if (isCancel5(blobToken)) {
832
+ process.exit(0);
833
+ }
310
834
  return { provider: "vercel-blob", blobToken };
311
835
  }
312
- const answers = await inquirer5.prompt([
313
- {
314
- type: "input",
315
- name: "supabaseUrl",
316
- message: "Enter your Supabase project URL:",
317
- validate: async (input) => {
318
- if (!input || input.trim() === "") {
319
- return "Supabase URL is required";
320
- }
321
- const result = await validateSupabaseUrl(input);
322
- return result.valid ? true : result.message || "Invalid URL";
836
+ const supabaseUrl = await text4({
837
+ message: "Enter your Supabase project URL:",
838
+ validate: (input) => {
839
+ if (!input || input.trim() === "") {
840
+ return "Supabase URL is required";
323
841
  }
324
- },
325
- {
326
- type: "input",
327
- name: "supabasePublishableKey",
328
- message: "Enter your Supabase publishable key (sb_publishable_...):",
329
- validate: (input) => {
330
- if (!input || input.trim() === "") {
331
- return "Supabase publishable key is required";
332
- }
333
- return true;
842
+ const result = validateSupabaseUrl(input);
843
+ return result.valid ? void 0 : result.message || "Invalid URL";
844
+ }
845
+ });
846
+ if (isCancel5(supabaseUrl)) {
847
+ process.exit(0);
848
+ }
849
+ const supabasePublishableKey = await text4({
850
+ message: "Enter your Supabase publishable key (sb_publishable_...):",
851
+ validate: (input) => {
852
+ if (!input || input.trim() === "") {
853
+ return "Supabase publishable key is required";
334
854
  }
855
+ return void 0;
335
856
  }
336
- ]);
857
+ });
858
+ if (isCancel5(supabasePublishableKey)) {
859
+ process.exit(0);
860
+ }
337
861
  return {
338
862
  provider: "supabase",
339
- supabaseUrl: answers.supabaseUrl,
340
- supabasePublishableKey: answers.supabasePublishableKey
863
+ supabaseUrl,
864
+ supabasePublishableKey
341
865
  };
342
866
  }
343
867
 
344
868
  // src/validators/node-version.ts
345
- import { createLogger as createLogger2 } from "@revealui/setup/utils";
346
- var logger2 = createLogger2({ prefix: "Setup" });
869
+ import { createLogger as createLogger4 } from "@revealui/setup/utils";
870
+ var logger4 = createLogger4({ prefix: "Setup" });
347
871
  var REQUIRED_NODE_VERSION = "24.13.0";
348
872
  function validateNodeVersion() {
349
873
  const currentVersion = process.version.slice(1);
350
874
  const [currentMajor, currentMinor] = currentVersion.split(".").map(Number);
351
875
  const [requiredMajor, requiredMinor] = REQUIRED_NODE_VERSION.split(".").map(Number);
352
876
  if (currentMajor < requiredMajor || currentMajor === requiredMajor && currentMinor < requiredMinor) {
353
- logger2.error(
877
+ logger4.error(
354
878
  `Node.js ${REQUIRED_NODE_VERSION} or higher is required. You have ${currentVersion}.`
355
879
  );
356
- logger2.info("Please upgrade Node.js: https://nodejs.org/");
880
+ logger4.info("Please upgrade Node.js: https://nodejs.org/");
357
881
  return false;
358
882
  }
359
883
  return true;
@@ -362,15 +886,15 @@ function validateNodeVersion() {
362
886
  // src/commands/create.ts
363
887
  import crypto from "crypto";
364
888
  import { existsSync } from "fs";
365
- import fs5 from "fs/promises";
366
- import path5 from "path";
889
+ import fs6 from "fs/promises";
890
+ import path6 from "path";
367
891
  import { fileURLToPath } from "url";
368
- import { createLogger as createLogger5 } from "@revealui/setup/utils";
892
+ import { createLogger as createLogger7 } from "@revealui/setup/utils";
369
893
  import ora2 from "ora";
370
894
 
371
895
  // src/generators/devbox.ts
372
- import fs2 from "fs/promises";
373
- import path2 from "path";
896
+ import fs3 from "fs/promises";
897
+ import path3 from "path";
374
898
  async function generateDevbox(projectPath) {
375
899
  const devboxConfig = {
376
900
  packages: ["nodejs@24.13.0", "pnpm@10.28.2", "postgresql@16", "stripe-cli@latest"],
@@ -390,19 +914,19 @@ async function generateDevbox(projectPath) {
390
914
  NODE_ENV: "development"
391
915
  }
392
916
  };
393
- await fs2.writeFile(
394
- path2.join(projectPath, "devbox.json"),
917
+ await fs3.writeFile(
918
+ path3.join(projectPath, "devbox.json"),
395
919
  JSON.stringify(devboxConfig, null, 2),
396
920
  "utf-8"
397
921
  );
398
922
  }
399
923
 
400
924
  // src/generators/devcontainer.ts
401
- import fs3 from "fs/promises";
402
- import path3 from "path";
925
+ import fs4 from "fs/promises";
926
+ import path4 from "path";
403
927
  async function generateDevContainer(projectPath) {
404
- const devcontainerDir = path3.join(projectPath, ".devcontainer");
405
- await fs3.mkdir(devcontainerDir, { recursive: true });
928
+ const devcontainerDir = path4.join(projectPath, ".devcontainer");
929
+ await fs4.mkdir(devcontainerDir, { recursive: true });
406
930
  const devcontainerConfig = {
407
931
  name: "RevealUI Development",
408
932
  image: "mcr.microsoft.com/devcontainers/typescript-node:24",
@@ -449,8 +973,8 @@ async function generateDevContainer(projectPath) {
449
973
  },
450
974
  remoteUser: "node"
451
975
  };
452
- await fs3.writeFile(
453
- path3.join(devcontainerDir, "devcontainer.json"),
976
+ await fs4.writeFile(
977
+ path4.join(devcontainerDir, "devcontainer.json"),
454
978
  JSON.stringify(devcontainerConfig, null, 2),
455
979
  "utf-8"
456
980
  );
@@ -479,7 +1003,7 @@ services:
479
1003
  volumes:
480
1004
  postgres-data:
481
1005
  `;
482
- await fs3.writeFile(path3.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
1006
+ await fs4.writeFile(path4.join(devcontainerDir, "docker-compose.yml"), dockerCompose, "utf-8");
483
1007
  const dockerfile = `FROM mcr.microsoft.com/devcontainers/typescript-node:24
484
1008
 
485
1009
  # Install additional tools
@@ -489,7 +1013,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \\
489
1013
  # Enable pnpm
490
1014
  RUN corepack enable
491
1015
  `;
492
- await fs3.writeFile(path3.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
1016
+ await fs4.writeFile(path4.join(devcontainerDir, "Dockerfile"), dockerfile, "utf-8");
493
1017
  const readme = `# Dev Container Setup
494
1018
 
495
1019
  This directory contains the Dev Container configuration for RevealUI.
@@ -531,12 +1055,12 @@ For GitHub Codespaces, set secrets in your repository settings.
531
1055
  - 4000: CMS
532
1056
  - 5432: PostgreSQL database
533
1057
  `;
534
- await fs3.writeFile(path3.join(devcontainerDir, "README.md"), readme, "utf-8");
1058
+ await fs4.writeFile(path4.join(devcontainerDir, "README.md"), readme, "utf-8");
535
1059
  }
536
1060
 
537
1061
  // src/generators/readme.ts
538
- import fs4 from "fs/promises";
539
- import path4 from "path";
1062
+ import fs5 from "fs/promises";
1063
+ import path5 from "path";
540
1064
  async function generateReadme(projectPath, projectConfig) {
541
1065
  const readme = `# ${projectConfig.projectName}
542
1066
 
@@ -563,70 +1087,42 @@ Run the development server:
563
1087
  pnpm dev
564
1088
  \`\`\`
565
1089
 
566
- Open [http://localhost:4000](http://localhost:4000) with your browser to access the CMS.
567
-
568
- The web application runs on [http://localhost:3000](http://localhost:3000).
1090
+ Open [http://localhost:4000](http://localhost:4000) with your browser.
569
1091
 
570
- ## Development Environments
1092
+ ## Requirements
571
1093
 
572
- ### Standard Setup
573
-
574
- Requirements:
575
1094
  - Node.js 24.13.0 or higher
576
- - pnpm 10.28.2 or higher
577
- - PostgreSQL 16
578
-
579
- ### Dev Containers
580
-
581
- Open in VS Code and select "Reopen in Container", or use GitHub Codespaces.
582
-
583
- ### Devbox
584
-
585
- Install Devbox:
586
-
587
- \`\`\`bash
588
- curl -fsSL https://get.jetpack.io/devbox | bash
589
- \`\`\`
590
-
591
- Then start the Devbox shell:
592
-
593
- \`\`\`bash
594
- devbox shell
595
- pnpm dev
596
- \`\`\`
1095
+ - pnpm 10 or higher
1096
+ - PostgreSQL 16 (or use a hosted provider like [Neon](https://neon.tech))
597
1097
 
598
1098
  ## Project Structure
599
1099
 
600
1100
  \`\`\`
601
1101
  ${projectConfig.projectName}/
602
- \u251C\u2500\u2500 apps/
603
- \u2502 \u251C\u2500\u2500 cms/ # CMS application
604
- \u2502 \u2514\u2500\u2500 web/ # Frontend application
605
- \u251C\u2500\u2500 packages/
606
- \u2502 \u251C\u2500\u2500 auth/ # Authentication
607
- \u2502 \u251C\u2500\u2500 db/ # Database
608
- \u2502 \u2514\u2500\u2500 ... # Other shared packages
609
- \u251C\u2500\u2500 .devcontainer/ # Dev Container configuration
610
- \u251C\u2500\u2500 devbox.json # Devbox configuration
611
- \u2514\u2500\u2500 .env.development.local # Environment variables
1102
+ \u251C\u2500\u2500 src/
1103
+ \u2502 \u251C\u2500\u2500 app/ # Next.js App Router pages
1104
+ \u2502 \u251C\u2500\u2500 collections/ # RevealUI collection definitions
1105
+ \u2502 \u2514\u2500\u2500 seed.ts # Database seed script
1106
+ \u251C\u2500\u2500 revealui.config.ts # RevealUI configuration
1107
+ \u251C\u2500\u2500 next.config.mjs # Next.js configuration
1108
+ \u2514\u2500\u2500 .env.local # Environment variables (git-ignored)
612
1109
  \`\`\`
613
1110
 
614
1111
  ## Available Scripts
615
1112
 
616
- - \`pnpm dev\` - Start development servers
1113
+ - \`pnpm dev\` - Start the development server
617
1114
  - \`pnpm build\` - Build for production
618
1115
  - \`pnpm test\` - Run tests
619
- - \`pnpm lint\` - Run linters
1116
+ - \`pnpm lint\` - Lint with Biome
620
1117
  - \`pnpm typecheck\` - Type check
621
- - \`pnpm db:init\` - Initialize database
1118
+ - \`pnpm db:init\` - Initialize the database
622
1119
  - \`pnpm db:migrate\` - Run migrations
623
- - \`pnpm db:seed\` - Seed database
1120
+ - \`pnpm db:seed\` - Seed sample content
624
1121
 
625
1122
  ## Learn More
626
1123
 
627
- - [RevealUI Documentation](https://github.com/your-org/RevealUI)
1124
+ - [RevealUI Documentation](https://docs.revealui.com)
628
1125
  - [Next.js Documentation](https://nextjs.org/docs)
629
- - [Hono Documentation](https://hono.dev)
630
1126
 
631
1127
  ## Template
632
1128
 
@@ -636,31 +1132,31 @@ This project was created using the **${projectConfig.template}** template.
636
1132
 
637
1133
  MIT
638
1134
  `;
639
- await fs4.writeFile(path4.join(projectPath, "README.md"), readme, "utf-8");
1135
+ await fs5.writeFile(path5.join(projectPath, "README.md"), readme, "utf-8");
640
1136
  }
641
1137
 
642
1138
  // src/installers/dependencies.ts
643
- import { createLogger as createLogger3 } from "@revealui/setup/utils";
644
- import { execa } from "execa";
1139
+ import { createLogger as createLogger5 } from "@revealui/setup/utils";
1140
+ import { execa as execa3 } from "execa";
645
1141
  import ora from "ora";
646
- var logger3 = createLogger3({ prefix: "Install" });
1142
+ var logger5 = createLogger5({ prefix: "Install" });
647
1143
  async function installDependencies(projectPath) {
648
1144
  const spinner = ora("Installing dependencies with pnpm...").start();
649
1145
  try {
650
- await execa("pnpm", ["install"], {
1146
+ await execa3("pnpm", ["install"], {
651
1147
  cwd: projectPath,
652
1148
  stdio: "pipe"
653
1149
  });
654
1150
  spinner.succeed("Dependencies installed successfully");
655
1151
  } catch (error) {
656
1152
  spinner.fail("Failed to install dependencies");
657
- logger3.error('Please run "pnpm install" manually');
1153
+ logger5.error('Please run "pnpm install" manually');
658
1154
  throw error;
659
1155
  }
660
1156
  }
661
1157
  async function isPnpmInstalled() {
662
1158
  try {
663
- await execa("pnpm", ["--version"]);
1159
+ await execa3("pnpm", ["--version"]);
664
1160
  return true;
665
1161
  } catch {
666
1162
  return false;
@@ -668,31 +1164,31 @@ async function isPnpmInstalled() {
668
1164
  }
669
1165
 
670
1166
  // src/utils/git.ts
671
- import { createLogger as createLogger4 } from "@revealui/setup/utils";
672
- import { execa as execa2 } from "execa";
673
- var logger4 = createLogger4({ prefix: "Git" });
1167
+ import { createLogger as createLogger6 } from "@revealui/setup/utils";
1168
+ import { execa as execa4 } from "execa";
1169
+ var logger6 = createLogger6({ prefix: "Git" });
674
1170
  async function initializeGitRepo(projectPath) {
675
1171
  try {
676
- await execa2("git", ["init"], { cwd: projectPath });
677
- logger4.success("Initialized git repository");
1172
+ await execa4("git", ["init"], { cwd: projectPath });
1173
+ logger6.success("Initialized git repository");
678
1174
  } catch (error) {
679
- logger4.warn("Failed to initialize git repository");
1175
+ logger6.warn("Failed to initialize git repository");
680
1176
  throw error;
681
1177
  }
682
1178
  }
683
1179
  async function createInitialCommit(projectPath) {
684
1180
  try {
685
- await execa2("git", ["add", "."], { cwd: projectPath });
686
- await execa2("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
687
- logger4.success("Created initial commit");
1181
+ await execa4("git", ["add", "."], { cwd: projectPath });
1182
+ await execa4("git", ["commit", "-m", "Initial commit from @revealui/cli"], { cwd: projectPath });
1183
+ logger6.success("Created initial commit");
688
1184
  } catch (error) {
689
- logger4.warn("Failed to create initial commit");
1185
+ logger6.warn("Failed to create initial commit");
690
1186
  throw error;
691
1187
  }
692
1188
  }
693
1189
  async function isGitInstalled() {
694
1190
  try {
695
- await execa2("git", ["--version"]);
1191
+ await execa4("git", ["--version"]);
696
1192
  return true;
697
1193
  } catch {
698
1194
  return false;
@@ -700,10 +1196,10 @@ async function isGitInstalled() {
700
1196
  }
701
1197
 
702
1198
  // src/commands/create.ts
703
- var logger5 = createLogger5({ prefix: "Create" });
1199
+ var logger7 = createLogger7({ prefix: "Create" });
704
1200
  var __filename2 = fileURLToPath(import.meta.url);
705
- var __dirname2 = path5.dirname(__filename2);
706
- var TEMPLATES_DIR = existsSync(path5.resolve(__dirname2, "../../templates")) ? path5.resolve(__dirname2, "../../templates") : path5.resolve(__dirname2, "../templates");
1201
+ var __dirname2 = path6.dirname(__filename2);
1202
+ var TEMPLATES_DIR = existsSync(path6.resolve(__dirname2, "../../templates")) ? path6.resolve(__dirname2, "../../templates") : path6.resolve(__dirname2, "../templates");
707
1203
  function buildEnvLocal(cfg) {
708
1204
  const lines = [
709
1205
  "# Generated by @revealui/cli \u2014 fill in the remaining placeholders before running `pnpm dev`",
@@ -749,16 +1245,16 @@ function generateSecret() {
749
1245
  }
750
1246
  async function listAvailableTemplates() {
751
1247
  try {
752
- const entries = await fs5.readdir(TEMPLATES_DIR, { withFileTypes: true });
1248
+ const entries = await fs6.readdir(TEMPLATES_DIR, { withFileTypes: true });
753
1249
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
754
1250
  } catch {
755
1251
  return [];
756
1252
  }
757
1253
  }
758
1254
  async function copyTemplate(templateName, targetPath) {
759
- const templatePath = path5.join(TEMPLATES_DIR, templateName);
1255
+ const templatePath = path6.join(TEMPLATES_DIR, templateName);
760
1256
  try {
761
- await fs5.access(templatePath);
1257
+ await fs6.access(templatePath);
762
1258
  } catch {
763
1259
  const available = await listAvailableTemplates();
764
1260
  const listing = available.length > 0 ? `Available templates: ${available.join(", ")}` : `No templates found in ${TEMPLATES_DIR}`;
@@ -767,16 +1263,16 @@ async function copyTemplate(templateName, targetPath) {
767
1263
  await copyDir(templatePath, targetPath);
768
1264
  }
769
1265
  async function copyDir(src, dest) {
770
- await fs5.mkdir(dest, { recursive: true });
771
- const entries = await fs5.readdir(src, { withFileTypes: true });
1266
+ await fs6.mkdir(dest, { recursive: true });
1267
+ const entries = await fs6.readdir(src, { withFileTypes: true });
772
1268
  for (const entry of entries) {
773
- const srcPath = path5.join(src, entry.name);
1269
+ const srcPath = path6.join(src, entry.name);
774
1270
  const destName = entry.name === "_gitignore" ? ".gitignore" : entry.name;
775
- const destPath = path5.join(dest, destName);
1271
+ const destPath = path6.join(dest, destName);
776
1272
  if (entry.isDirectory()) {
777
1273
  await copyDir(srcPath, destPath);
778
1274
  } else {
779
- await fs5.copyFile(srcPath, destPath);
1275
+ await fs6.copyFile(srcPath, destPath);
780
1276
  }
781
1277
  }
782
1278
  }
@@ -788,6 +1284,66 @@ function resolveTemplateName(template) {
788
1284
  };
789
1285
  return map[template] ?? "starter";
790
1286
  }
1287
+ async function pullContentRules(projectPath) {
1288
+ const rawBaseUrl = process.env.REVEALUI_RULES_URL ?? "https://raw.githubusercontent.com/RevealUIStudio/editor-configs/main/harnesses";
1289
+ let baseUrl;
1290
+ try {
1291
+ const parsed = new URL(rawBaseUrl);
1292
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
1293
+ return;
1294
+ }
1295
+ baseUrl = rawBaseUrl;
1296
+ } catch {
1297
+ return;
1298
+ }
1299
+ const spinner = ora2("Pulling AI coding rules...").start();
1300
+ try {
1301
+ const manifestRes = await fetch(`${baseUrl}/manifest.json`, {
1302
+ signal: AbortSignal.timeout(1e4)
1303
+ });
1304
+ if (!manifestRes.ok) {
1305
+ spinner.info("AI rules not available \u2014 skipping");
1306
+ return;
1307
+ }
1308
+ const manifest = await manifestRes.json();
1309
+ const generatorId = "claude-code";
1310
+ const ossDefs = manifest.definitions.filter((d) => d.tier === "oss");
1311
+ let written = 0;
1312
+ const allowedExtensions = /* @__PURE__ */ new Set([".md", ".json", ".txt", ".yaml", ".yml", ".ts", ".js"]);
1313
+ const maxFileSize = 1048576;
1314
+ for (const def of ossDefs) {
1315
+ const paths = def.generatorPaths[generatorId] ?? [];
1316
+ for (const relPath of paths) {
1317
+ try {
1318
+ if (relPath.includes("..") || relPath.startsWith("/")) continue;
1319
+ const ext = path6.extname(relPath).toLowerCase();
1320
+ if (!allowedExtensions.has(ext)) continue;
1321
+ const absolutePath = path6.resolve(projectPath, relPath);
1322
+ if (!absolutePath.startsWith(path6.resolve(projectPath))) continue;
1323
+ const fileRes = await fetch(`${baseUrl}/generators/${generatorId}/${relPath}`, {
1324
+ signal: AbortSignal.timeout(5e3)
1325
+ });
1326
+ if (!fileRes.ok) continue;
1327
+ const contentLength = fileRes.headers.get("content-length");
1328
+ if (contentLength && Number.parseInt(contentLength, 10) > maxFileSize) continue;
1329
+ const content = await fileRes.text();
1330
+ if (content.length > maxFileSize) continue;
1331
+ await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
1332
+ await fs6.writeFile(absolutePath, content, "utf-8");
1333
+ written++;
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ }
1338
+ if (written > 0) {
1339
+ spinner.succeed(`Pulled ${written} AI coding rules`);
1340
+ } else {
1341
+ spinner.info("No AI rules pulled");
1342
+ }
1343
+ } catch {
1344
+ spinner.info("AI rules not available \u2014 skipping");
1345
+ }
1346
+ }
791
1347
  async function createProject(cfg) {
792
1348
  const { project, skipGit = false, skipInstall = false } = cfg;
793
1349
  const { projectPath, projectName, template } = project;
@@ -799,57 +1355,58 @@ async function createProject(cfg) {
799
1355
  spinner.fail("Failed to copy template files");
800
1356
  throw err;
801
1357
  }
802
- const pkgJsonPath = path5.join(projectPath, "package.json");
1358
+ const pkgJsonPath = path6.join(projectPath, "package.json");
803
1359
  try {
804
- const raw = await fs5.readFile(pkgJsonPath, "utf-8");
805
- await fs5.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
1360
+ const raw = await fs6.readFile(pkgJsonPath, "utf-8");
1361
+ await fs6.writeFile(pkgJsonPath, raw.replaceAll("{{PROJECT_NAME}}", projectName), "utf-8");
806
1362
  } catch {
807
1363
  }
808
1364
  const envSpinner = ora2("Writing .env.local...").start();
809
1365
  try {
810
- await fs5.writeFile(path5.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
1366
+ await fs6.writeFile(path6.join(projectPath, ".env.local"), buildEnvLocal(cfg), "utf-8");
811
1367
  envSpinner.succeed(".env.local written");
812
1368
  } catch (err) {
813
1369
  envSpinner.fail("Failed to write .env.local");
814
1370
  throw err;
815
1371
  }
816
1372
  await generateReadme(projectPath, project);
817
- logger5.success("README.md generated");
1373
+ logger7.success("README.md generated");
818
1374
  if (cfg.devenv.createDevContainer) {
819
1375
  await generateDevContainer(projectPath);
820
- logger5.success(".devcontainer/ generated");
1376
+ logger7.success(".devcontainer/ generated");
821
1377
  }
822
1378
  if (cfg.devenv.createDevbox) {
823
1379
  await generateDevbox(projectPath);
824
- logger5.success("devbox.json generated");
1380
+ logger7.success("devbox.json generated");
825
1381
  }
1382
+ await pullContentRules(projectPath);
826
1383
  if (!skipInstall) {
827
1384
  const pnpmOk = await isPnpmInstalled();
828
1385
  if (!pnpmOk) {
829
- logger5.warn(
1386
+ logger7.warn(
830
1387
  "pnpm not found \u2014 skipping dependency installation. Run `pnpm install` manually."
831
1388
  );
832
1389
  } else {
833
1390
  await installDependencies(projectPath);
834
1391
  }
835
1392
  } else {
836
- logger5.info("Skipping dependency installation (--skip-install)");
1393
+ logger7.info("Skipping dependency installation (--skip-install)");
837
1394
  }
838
1395
  if (!skipGit) {
839
1396
  const gitOk = await isGitInstalled();
840
1397
  if (!gitOk) {
841
- logger5.warn("git not found \u2014 skipping repository initialisation.");
1398
+ logger7.warn("git not found \u2014 skipping repository initialisation.");
842
1399
  } else {
843
1400
  await initializeGitRepo(projectPath);
844
1401
  await createInitialCommit(projectPath);
845
1402
  }
846
1403
  } else {
847
- logger5.info("Skipping git initialisation (--skip-git)");
1404
+ logger7.info("Skipping git initialisation (--skip-git)");
848
1405
  }
849
1406
  }
850
1407
 
851
1408
  // src/commands/create-flow.ts
852
- var logger6 = createLogger6({ prefix: "@revealui/cli" });
1409
+ var logger8 = createLogger8({ prefix: "@revealui/cli" });
853
1410
  var PRO_TEMPLATES = /* @__PURE__ */ new Set([]);
854
1411
  async function checkProLicense() {
855
1412
  let key = process.env.REVEALUI_LICENSE_KEY;
@@ -862,7 +1419,7 @@ async function checkProLicense() {
862
1419
  }
863
1420
  }
864
1421
  if (!key) return false;
865
- const publicKeyPem = process.env.REVEALUI_LICENSE_PUBLIC_KEY;
1422
+ const publicKeyPem = process.env.REVEALUI_LICENSE_PUBLIC_KEY?.replace(/\\n/g, "\n");
866
1423
  if (publicKeyPem) {
867
1424
  try {
868
1425
  const publicKey = await importSPKI(publicKeyPem, "RS256");
@@ -886,112 +1443,143 @@ async function checkProLicense() {
886
1443
  return false;
887
1444
  }
888
1445
  }
889
- async function runCreateFlow(projectName, options) {
890
- logger6.info("[1/8] Validating Node.js version...");
891
- if (!validateNodeVersion()) {
892
- process.exit(1);
893
- }
894
- logger6.success(`Node.js version: ${process.version}`);
895
- logger6.info("[2/8] Configure your project");
896
- const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
897
- logger6.success(`Project: ${projectConfig.projectName}`);
898
- logger6.success(`Template: ${projectConfig.template}`);
899
- if (PRO_TEMPLATES.has(projectConfig.template)) {
900
- if (!await checkProLicense()) {
901
- logger6.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
902
- logger6.info("Get Pro at https://revealui.com/pricing");
903
- logger6.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
904
- process.exit(2);
905
- }
906
- logger6.success("Pro license verified");
907
- }
908
- const nonInteractive = options.yes === true;
909
- logger6.info("[3/8] Configure database");
910
- const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
911
- if (databaseConfig.provider !== "skip") {
912
- logger6.success(`Database: ${databaseConfig.provider}`);
913
- } else {
914
- logger6.info("Database configuration skipped");
915
- }
916
- logger6.info("[4/8] Configure storage");
917
- const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
918
- if (storageConfig.provider !== "skip") {
919
- logger6.success(`Storage: ${storageConfig.provider}`);
920
- } else {
921
- logger6.info("Storage configuration skipped");
922
- }
923
- logger6.info("[5/8] Configure payments");
924
- const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
925
- if (paymentConfig.enabled) {
926
- logger6.success("Stripe configured");
1446
+ function printBanner() {
1447
+ logger8.divider();
1448
+ logger8.info(" RevealUI \u2014 The Agentic Business Runtime");
1449
+ logger8.info(" Build your business, not your boilerplate.");
1450
+ logger8.divider();
1451
+ logger8.info("");
1452
+ }
1453
+ function printPostCreateSummary(projectName) {
1454
+ logger8.divider();
1455
+ logger8.success("Your RevealUI project is ready!");
1456
+ logger8.info("");
1457
+ logger8.info(" Quick start:");
1458
+ logger8.info(` cd ${projectName}`);
1459
+ logger8.info(" pnpm install");
1460
+ logger8.info(" pnpm dev");
1461
+ logger8.info("");
1462
+ logger8.info(" What was created:");
1463
+ logger8.info(` ./${projectName}/ \u2014 project root`);
1464
+ logger8.info(` ./${projectName}/.env.local \u2014 environment variables (edit before pnpm dev)`);
1465
+ logger8.info(` ./${projectName}/README.md \u2014 getting started guide`);
1466
+ logger8.info("");
1467
+ logger8.info(" RevealUI ecosystem:");
1468
+ logger8.info(" Studio: Desktop companion for managing your dev environment");
1469
+ logger8.info(" Terminal: TUI client \u2014 run `revealui terminal install`");
1470
+ logger8.info(" CMS: Admin dashboard at your-domain.com/admin");
1471
+ logger8.info("");
1472
+ logger8.info(" Helpful links:");
1473
+ logger8.info(" Docs: https://docs.revealui.com");
1474
+ logger8.info(" GitHub: https://github.com/RevealUIStudio/revealui");
1475
+ logger8.info(" Support: support@revealui.com");
1476
+ logger8.divider();
1477
+ }
1478
+ function formatCreateError(err) {
1479
+ const message = err instanceof Error ? err.message : String(err);
1480
+ const code = err instanceof Error && "code" in err ? err.code : void 0;
1481
+ logger8.error("Project creation failed.");
1482
+ logger8.info("");
1483
+ if (code === "EACCES") {
1484
+ logger8.info(" Permission denied. Try:");
1485
+ logger8.info(" - Running from a directory you own");
1486
+ logger8.info(" - Checking folder permissions with `ls -la`");
1487
+ } else if (code === "ENOENT") {
1488
+ logger8.info(" A required file or directory was not found.");
1489
+ logger8.info(" - Check that you are in the correct directory");
1490
+ logger8.info(" - Try running `revealui doctor` to diagnose your environment");
1491
+ } else if (code === "ENOSPC") {
1492
+ logger8.info(" Disk full. Free up space and try again.");
1493
+ } else if (message.includes("fetch") || message.includes("ENOTFOUND") || message.includes("ETIMEDOUT")) {
1494
+ logger8.info(" Network error. Check your internet connection and try again.");
927
1495
  } else {
928
- logger6.info("Payments disabled");
1496
+ logger8.info(` ${message}`);
929
1497
  }
930
- logger6.info("[6/8] Configure development environment");
931
- const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
932
- logger6.success(
933
- `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
934
- );
935
- logger6.info("[7/8] Creating project...");
936
- await createProject({
937
- project: projectConfig,
938
- database: databaseConfig,
939
- storage: storageConfig,
940
- payment: paymentConfig,
941
- devenv: devEnvConfig,
942
- skipGit: options.skipGit,
943
- skipInstall: options.skipInstall
944
- });
945
- logger6.success("Project created successfully");
946
- logger6.info("[8/8] Next steps");
947
- logger6.divider();
948
- logger6.info(`cd ${projectConfig.projectName}`);
949
- logger6.info("pnpm install");
950
- logger6.info("pnpm dev");
951
- logger6.divider();
952
- logger6.success(`Project ${projectConfig.projectName} created successfully`);
1498
+ logger8.info("");
1499
+ logger8.info(" Troubleshooting:");
1500
+ logger8.info(" revealui doctor \u2014 diagnose your environment");
1501
+ logger8.info(" https://docs.revealui.com/docs/TROUBLESHOOTING");
1502
+ logger8.info(" support@revealui.com \u2014 we are here to help");
1503
+ logger8.divider();
953
1504
  }
954
-
955
- // src/commands/db.ts
956
- import fs8 from "fs/promises";
957
- import path8 from "path";
958
- import { createLogger as createLogger7 } from "@revealui/setup/utils";
959
- import { execa as execa4 } from "execa";
960
-
961
- // src/utils/command.ts
962
- import net from "net";
963
- import { execa as execa3 } from "execa";
964
- async function commandExists(command) {
1505
+ async function runCreateFlow(projectName, options) {
1506
+ printBanner();
965
1507
  try {
966
- await execa3("bash", ["-lc", `command -v ${command}`], {
967
- stdio: "pipe"
1508
+ logger8.info("[1/8] Validating Node.js version...");
1509
+ if (!validateNodeVersion()) {
1510
+ process.exit(1);
1511
+ }
1512
+ logger8.success(`Node.js version: ${process.version}`);
1513
+ logger8.info("[2/8] Configure your project");
1514
+ const projectConfig = await promptProjectConfig(projectName, options.template, options.yes);
1515
+ logger8.success(`Project: ${projectConfig.projectName}`);
1516
+ logger8.success(`Template: ${projectConfig.template}`);
1517
+ if (PRO_TEMPLATES.has(projectConfig.template)) {
1518
+ if (!await checkProLicense()) {
1519
+ logger8.error(`The "${projectConfig.template}" template requires a RevealUI Pro license.`);
1520
+ logger8.info("Get Pro at https://revealui.com/pricing");
1521
+ logger8.info("Set your license key: export REVEALUI_LICENSE_KEY=<your-key>");
1522
+ process.exit(2);
1523
+ }
1524
+ logger8.success("Pro license verified");
1525
+ }
1526
+ const nonInteractive = options.yes === true;
1527
+ logger8.info("[3/8] Configure database");
1528
+ const databaseConfig = nonInteractive ? { provider: "skip" } : await promptDatabaseConfig();
1529
+ if (databaseConfig.provider !== "skip") {
1530
+ logger8.success(`Database: ${databaseConfig.provider}`);
1531
+ } else {
1532
+ logger8.info("Database configuration skipped");
1533
+ }
1534
+ logger8.info("[4/8] Configure storage");
1535
+ const storageConfig = nonInteractive ? { provider: "skip" } : await promptStorageConfig();
1536
+ if (storageConfig.provider !== "skip") {
1537
+ logger8.success(`Storage: ${storageConfig.provider}`);
1538
+ } else {
1539
+ logger8.info("Storage configuration skipped");
1540
+ }
1541
+ logger8.info("[5/8] Configure payments");
1542
+ const paymentConfig = nonInteractive ? { enabled: false } : await promptPaymentConfig();
1543
+ if (paymentConfig.enabled) {
1544
+ logger8.success("Stripe configured");
1545
+ } else {
1546
+ logger8.info("Payments disabled");
1547
+ }
1548
+ logger8.info("[6/8] Configure development environment");
1549
+ const devEnvConfig = nonInteractive ? { createDevContainer: false, createDevbox: false } : await promptDevEnvConfig();
1550
+ logger8.success(
1551
+ `Dev Container: ${devEnvConfig.createDevContainer ? "Yes" : "No"}, Devbox: ${devEnvConfig.createDevbox ? "Yes" : "No"}`
1552
+ );
1553
+ logger8.info("[7/8] Creating project...");
1554
+ await createProject({
1555
+ project: projectConfig,
1556
+ database: databaseConfig,
1557
+ storage: storageConfig,
1558
+ payment: paymentConfig,
1559
+ devenv: devEnvConfig,
1560
+ skipGit: options.skipGit,
1561
+ skipInstall: options.skipInstall
968
1562
  });
969
- return true;
970
- } catch {
971
- return false;
1563
+ logger8.success("Project created successfully");
1564
+ logger8.info("[8/8] Done!");
1565
+ printPostCreateSummary(projectConfig.projectName);
1566
+ } catch (err) {
1567
+ formatCreateError(err);
1568
+ process.exit(1);
972
1569
  }
973
1570
  }
974
- async function isTcpReachable(host, port, timeoutMs = 1500) {
975
- return await new Promise((resolve) => {
976
- const socket = new net.Socket();
977
- const finalize = (value) => {
978
- socket.removeAllListeners();
979
- socket.destroy();
980
- resolve(value);
981
- };
982
- socket.setTimeout(timeoutMs);
983
- socket.once("connect", () => finalize(true));
984
- socket.once("timeout", () => finalize(false));
985
- socket.once("error", () => finalize(false));
986
- socket.connect(port, host);
987
- });
988
- }
1571
+
1572
+ // src/commands/db.ts
1573
+ import fs9 from "fs/promises";
1574
+ import path9 from "path";
1575
+ import { createLogger as createLogger9 } from "@revealui/setup/utils";
1576
+ import { execa as execa5 } from "execa";
989
1577
 
990
1578
  // src/utils/db.ts
991
- import fs6 from "fs/promises";
992
- import path6 from "path";
1579
+ import fs7 from "fs/promises";
1580
+ import path7 from "path";
993
1581
  function resolveLocalDbConfig(cwd = process.cwd(), env = process.env) {
994
- const pgdata = env.PGDATA || path6.join(cwd, ".pgdata");
1582
+ const pgdata = env.PGDATA || path7.join(cwd, ".pgdata");
995
1583
  const pghost = env.PGHOST || pgdata;
996
1584
  const pgdatabase = env.PGDATABASE || "postgres";
997
1585
  const pguser = env.PGUSER || "postgres";
@@ -1038,27 +1626,27 @@ host all all ::1/128 trust
1038
1626
  `.trimStart();
1039
1627
  }
1040
1628
  async function writeLocalDbConfigs(pgdata) {
1041
- await fs6.appendFile(path6.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1042
- await fs6.writeFile(path6.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1629
+ await fs7.appendFile(path7.join(pgdata, "postgresql.conf"), buildPostgresConfig(pgdata), "utf8");
1630
+ await fs7.writeFile(path7.join(pgdata, "pg_hba.conf"), buildPgHbaConfig(), "utf8");
1043
1631
  }
1044
1632
 
1045
1633
  // src/utils/workspace.ts
1046
- import fs7 from "fs";
1047
- import path7 from "path";
1634
+ import fs8 from "fs";
1635
+ import path8 from "path";
1048
1636
  function findWorkspaceRoot(startDir = process.cwd()) {
1049
- let current = path7.resolve(startDir);
1637
+ let current = path8.resolve(startDir);
1050
1638
  while (true) {
1051
- const packageJsonPath = path7.join(current, "package.json");
1052
- if (fs7.existsSync(packageJsonPath)) {
1639
+ const packageJsonPath = path8.join(current, "package.json");
1640
+ if (fs8.existsSync(packageJsonPath)) {
1053
1641
  try {
1054
- const pkg = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
1642
+ const pkg = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
1055
1643
  if (pkg.name === "revealui" && pkg.private === true) {
1056
1644
  return current;
1057
1645
  }
1058
1646
  } catch {
1059
1647
  }
1060
1648
  }
1061
- const parent = path7.dirname(current);
1649
+ const parent = path8.dirname(current);
1062
1650
  if (parent === current) {
1063
1651
  return null;
1064
1652
  }
@@ -1067,7 +1655,7 @@ function findWorkspaceRoot(startDir = process.cwd()) {
1067
1655
  }
1068
1656
 
1069
1657
  // src/commands/db.ts
1070
- var logger7 = createLogger7({ prefix: "DB" });
1658
+ var logger9 = createLogger9({ prefix: "DB" });
1071
1659
  function isPgCtlNotRunningError(error) {
1072
1660
  return typeof error === "object" && error !== null && "exitCode" in error && error.exitCode === 3;
1073
1661
  }
@@ -1108,18 +1696,18 @@ async function runDbInitCommand(options = {}) {
1108
1696
  const pgdata = getPgDataOrThrow(env);
1109
1697
  await requireCommand("initdb");
1110
1698
  try {
1111
- await fs8.access(pgdata);
1699
+ await fs9.access(pgdata);
1112
1700
  if (!options.force) {
1113
1701
  throw new Error(`PostgreSQL is already initialized at ${pgdata}. Use --force to reset it.`);
1114
1702
  }
1115
- await fs8.rm(pgdata, { recursive: true, force: true });
1703
+ await fs9.rm(pgdata, { recursive: true, force: true });
1116
1704
  } catch (error) {
1117
1705
  if (error instanceof Error && !error.message.includes("already initialized")) {
1118
1706
  } else if (error instanceof Error) {
1119
1707
  throw error;
1120
1708
  }
1121
1709
  }
1122
- await execa4(
1710
+ await execa5(
1123
1711
  "initdb",
1124
1712
  ["--locale=C.UTF-8", "--encoding=UTF8", "-D", pgdata, "--username=postgres"],
1125
1713
  {
@@ -1128,17 +1716,17 @@ async function runDbInitCommand(options = {}) {
1128
1716
  }
1129
1717
  );
1130
1718
  await writeLocalDbConfigs(pgdata);
1131
- logger7.success(`PostgreSQL initialized at ${pgdata}`);
1719
+ logger9.success(`PostgreSQL initialized at ${pgdata}`);
1132
1720
  }
1133
1721
  async function runDbStartCommand() {
1134
1722
  const root = getWorkspaceRootOrThrow();
1135
1723
  const env = buildDbEnv(root);
1136
1724
  const pgdata = getPgDataOrThrow(env);
1137
1725
  await requireCommand("pg_ctl");
1138
- await fs8.access(pgdata);
1139
- await execa4(
1726
+ await fs9.access(pgdata);
1727
+ await execa5(
1140
1728
  "pg_ctl",
1141
- ["start", "-D", pgdata, "-l", path8.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1729
+ ["start", "-D", pgdata, "-l", path9.join(pgdata, "logfile"), "-o", `-k ${pgdata}`],
1142
1730
  {
1143
1731
  env,
1144
1732
  stdio: "inherit"
@@ -1150,7 +1738,7 @@ async function runDbStopCommand() {
1150
1738
  const env = buildDbEnv(root);
1151
1739
  const pgdata = getPgDataOrThrow(env);
1152
1740
  await requireCommand("pg_ctl");
1153
- await execa4("pg_ctl", ["stop", "-D", pgdata], {
1741
+ await execa5("pg_ctl", ["stop", "-D", pgdata], {
1154
1742
  env,
1155
1743
  stdio: "inherit"
1156
1744
  });
@@ -1161,13 +1749,13 @@ async function runDbStatusCommand() {
1161
1749
  const pgdata = getPgDataOrThrow(env);
1162
1750
  await requireCommand("pg_ctl");
1163
1751
  try {
1164
- await execa4("pg_ctl", ["status", "-D", pgdata], {
1752
+ await execa5("pg_ctl", ["status", "-D", pgdata], {
1165
1753
  env,
1166
1754
  stdio: "inherit"
1167
1755
  });
1168
1756
  } catch (error) {
1169
1757
  if (isPgCtlNotRunningError(error)) {
1170
- logger7.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1758
+ logger9.warn(`PostgreSQL is not running (PGDATA: ${pgdata})`);
1171
1759
  return;
1172
1760
  }
1173
1761
  throw error;
@@ -1182,17 +1770,17 @@ async function runDbResetCommand(options = {}) {
1182
1770
  const pgdata = getPgDataOrThrow(env);
1183
1771
  if (await commandExists("pg_ctl")) {
1184
1772
  try {
1185
- await execa4("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1773
+ await execa5("pg_ctl", ["stop", "-D", pgdata], { env, stdio: "pipe" });
1186
1774
  } catch {
1187
1775
  }
1188
1776
  }
1189
- await fs8.rm(pgdata, { recursive: true, force: true });
1777
+ await fs9.rm(pgdata, { recursive: true, force: true });
1190
1778
  await runDbInitCommand({ force: false });
1191
1779
  }
1192
1780
  async function runDbMigrateCommand() {
1193
1781
  const root = getWorkspaceRootOrThrow();
1194
1782
  const env = buildDbEnv(root);
1195
- await execa4("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1783
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:migrate"], {
1196
1784
  cwd: root,
1197
1785
  env,
1198
1786
  stdio: "inherit"
@@ -1203,7 +1791,7 @@ async function runDbCleanupCommand(options = {}) {
1203
1791
  const env = { ...process.env };
1204
1792
  if (options.dryRun) env.DRY_RUN = "true";
1205
1793
  if (options.tables) env.TABLES = options.tables;
1206
- await execa4("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1794
+ await execa5("pnpm", ["--filter", "@revealui/db", "db:cleanup"], {
1207
1795
  cwd: root,
1208
1796
  env,
1209
1797
  stdio: "inherit"
@@ -1211,8 +1799,8 @@ async function runDbCleanupCommand(options = {}) {
1211
1799
  }
1212
1800
 
1213
1801
  // src/commands/dev.ts
1214
- import { createLogger as createLogger10 } from "@revealui/setup/utils";
1215
- import { execa as execa6 } from "execa";
1802
+ import { createLogger as createLogger12 } from "@revealui/setup/utils";
1803
+ import { execa as execa7 } from "execa";
1216
1804
 
1217
1805
  // src/runtime/doctor.ts
1218
1806
  function getMcpDetail(env) {
@@ -1231,6 +1819,139 @@ function getMcpDetail(env) {
1231
1819
  detail: `mcp credentials configured for ${configuredKeys.join(", ")}`
1232
1820
  };
1233
1821
  }
1822
+ var ENV_VAR_SPECS = [
1823
+ // Core
1824
+ {
1825
+ key: "REVEALUI_SECRET",
1826
+ label: "App secret",
1827
+ required: false
1828
+ },
1829
+ {
1830
+ key: "REVEALUI_ADMIN_EMAIL",
1831
+ label: "Admin email",
1832
+ required: false
1833
+ },
1834
+ // Database
1835
+ {
1836
+ key: "POSTGRES_URL",
1837
+ label: "PostgreSQL URL",
1838
+ required: true,
1839
+ validate: validateNeonUrl
1840
+ },
1841
+ // Stripe
1842
+ {
1843
+ key: "STRIPE_SECRET_KEY",
1844
+ label: "Stripe secret key",
1845
+ required: false,
1846
+ validate: validateStripeKey
1847
+ },
1848
+ {
1849
+ key: "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY",
1850
+ label: "Stripe publishable key",
1851
+ required: false,
1852
+ validate: (v) => ({
1853
+ valid: v.startsWith("pk_test_") || v.startsWith("pk_live_"),
1854
+ message: "Must start with pk_test_ or pk_live_"
1855
+ })
1856
+ },
1857
+ {
1858
+ key: "STRIPE_WEBHOOK_SECRET",
1859
+ label: "Stripe webhook secret",
1860
+ required: false,
1861
+ validate: (v) => ({
1862
+ valid: v.startsWith("whsec_"),
1863
+ message: "Must start with whsec_"
1864
+ })
1865
+ },
1866
+ // Supabase
1867
+ {
1868
+ key: "NEXT_PUBLIC_SUPABASE_URL",
1869
+ label: "Supabase URL",
1870
+ required: false,
1871
+ validate: validateSupabaseUrl
1872
+ },
1873
+ {
1874
+ key: "SUPABASE_SERVICE_ROLE_KEY",
1875
+ label: "Supabase service role key",
1876
+ required: false,
1877
+ validate: (v) => ({
1878
+ valid: v.startsWith("eyJ"),
1879
+ message: "Must be a JWT (starts with eyJ)"
1880
+ })
1881
+ },
1882
+ // Services
1883
+ {
1884
+ key: "RESEND_API_KEY",
1885
+ label: "Resend API key",
1886
+ required: false,
1887
+ validate: (v) => ({
1888
+ valid: v.startsWith("re_"),
1889
+ message: "Must start with re_"
1890
+ })
1891
+ },
1892
+ // npm
1893
+ {
1894
+ key: "NPM_TOKEN",
1895
+ label: "npm publish token",
1896
+ required: false,
1897
+ validate: validateNpmToken
1898
+ },
1899
+ // License
1900
+ {
1901
+ key: "REVEALUI_LICENSE_PRIVATE_KEY",
1902
+ label: "License signing key",
1903
+ required: false
1904
+ },
1905
+ // CRON
1906
+ {
1907
+ key: "REVEALUI_CRON_SECRET",
1908
+ label: "Cron secret",
1909
+ required: false
1910
+ },
1911
+ // AI
1912
+ {
1913
+ key: "GROQ_API_KEY",
1914
+ label: "Groq API key",
1915
+ required: false,
1916
+ validate: (v) => ({
1917
+ valid: v.startsWith("gsk_"),
1918
+ message: "Must start with gsk_"
1919
+ })
1920
+ }
1921
+ ];
1922
+ function maskValue(value) {
1923
+ if (value.length <= 8) return "***";
1924
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
1925
+ }
1926
+ function gatherCredentialChecks(env) {
1927
+ const checks = [];
1928
+ for (const spec of ENV_VAR_SPECS) {
1929
+ const value = env[spec.key];
1930
+ if (!value) {
1931
+ checks.push({
1932
+ id: `env:${spec.key}`,
1933
+ ok: !spec.required,
1934
+ detail: spec.required ? `${spec.label} \u2014 missing (required)` : `${spec.label} \u2014 not set`
1935
+ });
1936
+ continue;
1937
+ }
1938
+ if (spec.validate) {
1939
+ const result = spec.validate(value);
1940
+ checks.push({
1941
+ id: `env:${spec.key}`,
1942
+ ok: result.valid,
1943
+ detail: result.valid ? `${spec.label} \u2014 ${maskValue(value)}` : `${spec.label} \u2014 ${result.message} (got ${maskValue(value)})`
1944
+ });
1945
+ } else {
1946
+ checks.push({
1947
+ id: `env:${spec.key}`,
1948
+ ok: true,
1949
+ detail: `${spec.label} \u2014 set`
1950
+ });
1951
+ }
1952
+ }
1953
+ return checks;
1954
+ }
1234
1955
  async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1235
1956
  const workspaceRoot = findWorkspaceRoot(cwd);
1236
1957
  const dbConfig = resolveLocalDbConfig(workspaceRoot ?? cwd, env);
@@ -1244,6 +1965,7 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1244
1965
  const dockerOk = await commandExists("docker");
1245
1966
  const postgresReachable = dbTarget === "local" ? await isTcpReachable("127.0.0.1", 5432, 1e3) : false;
1246
1967
  const mcp = getMcpDetail(env);
1968
+ const credentials = gatherCredentialChecks(env);
1247
1969
  return {
1248
1970
  workspaceRoot,
1249
1971
  dbTarget,
@@ -1302,7 +2024,8 @@ async function gatherDoctorReport(cwd = process.cwd(), env = process.env) {
1302
2024
  id: "mcp",
1303
2025
  ok: mcp.ok,
1304
2026
  detail: mcp.detail
1305
- }
2027
+ },
2028
+ ...credentials
1306
2029
  ]
1307
2030
  };
1308
2031
  }
@@ -1315,22 +2038,22 @@ function formatDoctorReport(report) {
1315
2038
  }
1316
2039
 
1317
2040
  // src/utils/dev-config.ts
1318
- import fs9 from "fs";
1319
- import path9 from "path";
2041
+ import fs10 from "fs";
2042
+ import path10 from "path";
1320
2043
  function getDevConfigPath(startDir = process.cwd()) {
1321
2044
  const workspaceRoot = findWorkspaceRoot(startDir);
1322
2045
  if (!workspaceRoot) {
1323
2046
  return null;
1324
2047
  }
1325
- return path9.join(workspaceRoot, ".revealui", "dev.json");
2048
+ return path10.join(workspaceRoot, ".revealui", "dev.json");
1326
2049
  }
1327
2050
  function readDevConfig(startDir = process.cwd()) {
1328
2051
  const configPath = getDevConfigPath(startDir);
1329
- if (!(configPath && fs9.existsSync(configPath))) {
2052
+ if (!(configPath && fs10.existsSync(configPath))) {
1330
2053
  return {};
1331
2054
  }
1332
2055
  try {
1333
- return JSON.parse(fs9.readFileSync(configPath, "utf8"));
2056
+ return JSON.parse(fs10.readFileSync(configPath, "utf8"));
1334
2057
  } catch {
1335
2058
  return {};
1336
2059
  }
@@ -1340,15 +2063,15 @@ function writeDevConfig(config, startDir = process.cwd()) {
1340
2063
  if (!configPath) {
1341
2064
  throw new Error("RevealUI workspace root not found");
1342
2065
  }
1343
- fs9.mkdirSync(path9.dirname(configPath), { recursive: true });
1344
- 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)}
1345
2068
  `, "utf8");
1346
2069
  return configPath;
1347
2070
  }
1348
2071
 
1349
2072
  // src/commands/doctor.ts
1350
- import { createLogger as createLogger8 } from "@revealui/setup/utils";
1351
- var logger8 = createLogger8({ prefix: "Doctor" });
2073
+ import { createLogger as createLogger10 } from "@revealui/setup/utils";
2074
+ var logger10 = createLogger10({ prefix: "Doctor" });
1352
2075
  function getDoctorFixPlan(report) {
1353
2076
  const attempted = [];
1354
2077
  const skipped = [];
@@ -1406,7 +2129,7 @@ async function runDoctorCommand(options = {}) {
1406
2129
  `);
1407
2130
  if (options.fix) {
1408
2131
  if (fixPlan.attempted.length === 0) {
1409
- logger8.warn("No safe automatic fixes available");
2132
+ logger10.warn("No safe automatic fixes available");
1410
2133
  } else {
1411
2134
  await applyDoctorFixes(report);
1412
2135
  report = await gatherDoctorReport();
@@ -1415,7 +2138,7 @@ async function runDoctorCommand(options = {}) {
1415
2138
  }
1416
2139
  }
1417
2140
  if (report.checks.some((check) => !check.ok)) {
1418
- logger8.warn("Some checks failed");
2141
+ logger10.warn("Some checks failed");
1419
2142
  if (options.strict || options.json || process.env.CI) {
1420
2143
  process.exitCode = 1;
1421
2144
  }
@@ -1423,15 +2146,15 @@ async function runDoctorCommand(options = {}) {
1423
2146
  }
1424
2147
 
1425
2148
  // src/commands/shell.ts
1426
- import { createLogger as createLogger9 } from "@revealui/setup/utils";
1427
- import { execa as execa5 } from "execa";
1428
- var logger9 = createLogger9({ prefix: "Shell" });
2149
+ import { createLogger as createLogger11 } from "@revealui/setup/utils";
2150
+ import { execa as execa6 } from "execa";
2151
+ var logger11 = createLogger11({ prefix: "Shell" });
1429
2152
  async function runShellCommand(options = {}) {
1430
2153
  if (!(options.inside || process.env.IN_NIX_SHELL) && await commandExists("nix")) {
1431
2154
  const workspaceRoot = findWorkspaceRoot();
1432
2155
  if (workspaceRoot) {
1433
2156
  const reentryArgs = options.ensure ? ["dev", "up"] : ["dev", "status"];
1434
- await execa5(
2157
+ await execa6(
1435
2158
  "nix",
1436
2159
  [
1437
2160
  "develop",
@@ -1470,12 +2193,12 @@ async function runShellCommand(options = {}) {
1470
2193
  }
1471
2194
  process.stdout.write(`${formatDoctorReport(freshReport)}
1472
2195
  `);
1473
- logger9.info("Use `revealui doctor --json` for machine-readable status.");
2196
+ logger11.info("Use `revealui doctor --json` for machine-readable status.");
1474
2197
  return false;
1475
2198
  }
1476
2199
 
1477
2200
  // src/commands/dev.ts
1478
- var logger10 = createLogger10({ prefix: "Dev" });
2201
+ var logger12 = createLogger12({ prefix: "Dev" });
1479
2202
  var DEV_PROFILES = {
1480
2203
  local: {},
1481
2204
  agent: {
@@ -1601,21 +2324,21 @@ async function runDevUpCommand(options = {}) {
1601
2324
  process.stdout.write(`${formatDevActions(plan)}
1602
2325
  `);
1603
2326
  if (plan.dryRun) {
1604
- logger10.info("Dry run only; no migrations or services were started.");
2327
+ logger12.info("Dry run only; no migrations or services were started.");
1605
2328
  return;
1606
2329
  }
1607
2330
  if (options.fix) {
1608
2331
  await runDoctorCommand({ fix: true });
1609
2332
  }
1610
2333
  await runDbMigrateCommand();
1611
- logger10.success("Database migration complete");
2334
+ logger12.success("Database migration complete");
1612
2335
  if (shouldIncludeMcp(resolved.include)) {
1613
2336
  const workspaceRoot = findWorkspaceRoot();
1614
2337
  if (!workspaceRoot) {
1615
2338
  throw new Error("RevealUI workspace root not found");
1616
2339
  }
1617
- logger10.info("Validating MCP setup");
1618
- await execa6("pnpm", ["setup:mcp"], {
2340
+ logger12.info("Validating MCP setup");
2341
+ await execa7("pnpm", ["setup:mcp"], {
1619
2342
  cwd: workspaceRoot,
1620
2343
  stdio: "inherit"
1621
2344
  });
@@ -1625,8 +2348,8 @@ async function runDevUpCommand(options = {}) {
1625
2348
  if (!workspaceRoot) {
1626
2349
  throw new Error("RevealUI workspace root not found");
1627
2350
  }
1628
- logger10.info(`Starting dev script: ${resolved.script}`);
1629
- await execa6("pnpm", [resolved.script], {
2351
+ logger12.info(`Starting dev script: ${resolved.script}`);
2352
+ await execa7("pnpm", [resolved.script], {
1630
2353
  cwd: workspaceRoot,
1631
2354
  stdio: "inherit"
1632
2355
  });
@@ -1683,7 +2406,7 @@ async function runDevProfileSetCommand(profile) {
1683
2406
  );
1684
2407
  }
1685
2408
  const configPath = writeDevConfig({ defaultProfile: profile });
1686
- logger10.success(`Default dev profile set to "${profile}" in ${configPath}`);
2409
+ logger12.success(`Default dev profile set to "${profile}" in ${configPath}`);
1687
2410
  }
1688
2411
  async function runDevProfileShowCommand(options = {}) {
1689
2412
  const configuredProfile = getConfiguredProfile() ?? null;
@@ -1696,11 +2419,223 @@ async function runDevProfileShowCommand(options = {}) {
1696
2419
  `);
1697
2420
  }
1698
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
+
1699
2634
  // src/cli.ts
1700
- var logger11 = createLogger11({ prefix: "CLI" });
2635
+ var logger14 = createLogger14({ prefix: "CLI" });
1701
2636
  function configureCreateCommand(command, legacyName) {
1702
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) => {
1703
- logger11.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
2638
+ logger14.header(legacyName ? "Create RevealUI Project" : "RevealUI Create");
1704
2639
  await runCreateFlow(projectName, options);
1705
2640
  });
1706
2641
  return command;
@@ -1712,6 +2647,17 @@ function createCli() {
1712
2647
  program.command("doctor").description("Check RevealUI workspace and developer environment health").option("--json", "Output machine-readable JSON", false).option("--fix", "Apply safe automatic fixes when possible", false).option("--strict", "Exit nonzero when checks fail", false).action(async (options) => {
1713
2648
  await runDoctorCommand(options);
1714
2649
  });
2650
+ const agent = program.command("agent").description("RevealUI coding agent (powered by local or cloud LLMs)");
2651
+ agent.option("-p, --prompt <text>", "Run a single prompt in headless mode").action(async (options) => {
2652
+ if (options.prompt) {
2653
+ await runAgentHeadlessCommand(options.prompt);
2654
+ } else {
2655
+ await runAgentReplCommand();
2656
+ }
2657
+ });
2658
+ agent.command("status").description("Show agent status: model, provider, project root").action(async () => {
2659
+ await runAgentStatusCommand();
2660
+ });
1715
2661
  const db = program.command("db").description("Manage the local RevealUI database");
1716
2662
  db.command("init").description("Initialize the local PostgreSQL data directory").option("--force", "Delete and recreate the local data directory", false).action(async (options) => {
1717
2663
  await runDbInitCommand(options);
@@ -1784,6 +2730,23 @@ function createCli() {
1784
2730
  devProfile.command("show").description("Show the configured default dev profile").option("--json", "Output machine-readable JSON", false).action(async (options) => {
1785
2731
  await runDevProfileShowCommand(options);
1786
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
+ });
1787
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) => {
1788
2751
  await runDevUpCommand({
1789
2752
  ensure: options.ensure,