@revealui/cli 0.0.0-canary-20260402004330

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