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