@mdocui/cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,15 +18,18 @@ pnpm add -g @mdocui/cli
18
18
 
19
19
  ### `init`
20
20
 
21
- Scaffold a new mdocUI config file with starter components.
21
+ Scaffold a complete mdocUI integration. Detects your framework (Next.js, Vite, Remix) and generates all required files.
22
22
 
23
23
  ```bash
24
24
  npx @mdocui/cli init
25
25
  ```
26
26
 
27
27
  Creates:
28
- - `mdocui.config.ts` — component definitions and config
29
- - `generated/`output directory for system prompts
28
+ - `mdocui.config.ts` — config with all 24 component definitions
29
+ - `src/lib/mdoc-registry.ts`component registry setup
30
+ - `src/components/mdoc-message.tsx` — streaming message component using `useRenderer`
31
+ - `.env.example` — API key placeholders
32
+ - `src/app/api/chat/route.ts` — streaming API route (Next.js only)
30
33
 
31
34
  ### `generate`
32
35
 
@@ -40,12 +43,15 @@ Reads `mdocui.config.ts` and writes the prompt to the configured `output` path (
40
43
 
41
44
  ### `preview`
42
45
 
43
- Print the generated system prompt to stdout for review.
46
+ Start a local dev server with a split-pane editor for live markup rendering and parse validation.
44
47
 
45
48
  ```bash
46
49
  npx @mdocui/cli preview
50
+ npx @mdocui/cli preview --port 3000
47
51
  ```
48
52
 
53
+ Opens a browser UI where you type mdocUI markup on the left and see it rendered on the right in real-time. Great for prompt iteration without burning API calls.
54
+
49
55
  ## Config File
50
56
 
51
57
  ```typescript
package/dist/index.js CHANGED
@@ -49,79 +49,911 @@ async function generate(cwd2) {
49
49
  // src/commands/init.ts
50
50
  import fs3 from "fs";
51
51
  import path3 from "path";
52
- var CONFIG_TEMPLATE = `import { defineComponent } from '@mdocui/core'
53
- import { z } from 'zod'
54
-
55
- const button = defineComponent({
56
- name: 'button',
57
- description: 'Clickable action button',
58
- props: z.object({
59
- action: z.string().describe('Action to perform'),
60
- label: z.string().describe('Button text'),
61
- }),
62
- children: 'none',
63
- })
52
+ function detectFramework(cwd2) {
53
+ const pkgPath = path3.resolve(cwd2, "package.json");
54
+ const hasSrc = fs3.existsSync(path3.resolve(cwd2, "src"));
55
+ const hasApp = fs3.existsSync(path3.resolve(cwd2, "src/app")) || fs3.existsSync(path3.resolve(cwd2, "app"));
56
+ const hasPages = fs3.existsSync(path3.resolve(cwd2, "src/pages")) || fs3.existsSync(path3.resolve(cwd2, "pages"));
57
+ if (!fs3.existsSync(pkgPath)) {
58
+ return { framework: "unknown", hasSrc, hasApp, hasPages };
59
+ }
60
+ const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
61
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
62
+ if (allDeps.next) return { framework: "nextjs", hasSrc, hasApp, hasPages };
63
+ if (allDeps["@remix-run/react"] || allDeps.remix)
64
+ return { framework: "remix", hasSrc, hasApp, hasPages };
65
+ if (allDeps.vite) return { framework: "vite", hasSrc, hasApp, hasPages };
66
+ return { framework: "unknown", hasSrc, hasApp, hasPages };
67
+ }
68
+ function registryTemplate() {
69
+ return `import { createDefaultRegistry } from '@mdocui/react'
70
+
71
+ // Pre-built registry with all 24 built-in mdocUI components:
72
+ // Layout: stack, grid, card, divider, accordion, tabs, tab
73
+ // Interactive: button, button-group, input, textarea, select, checkbox, toggle, form
74
+ // Data: chart, table, stat, progress
75
+ // Content: callout, badge, image, code-block, link
76
+ //
77
+ // To add custom components, use the ComponentRegistry API directly:
78
+ // import { ComponentRegistry } from '@mdocui/core'
79
+ // import { allDefinitions } from '@mdocui/react'
80
+
81
+ export const registry = createDefaultRegistry()
82
+ `;
83
+ }
84
+ function mdocMessageTemplate() {
85
+ return `'use client'
86
+
87
+ import { useCallback, useRef } from 'react'
88
+ import { Renderer, useRenderer, defaultComponents } from '@mdocui/react'
89
+ import type { ActionHandler } from '@mdocui/react'
90
+ import { registry } from '@/lib/mdoc-registry'
91
+
92
+ interface MdocMessageProps {
93
+ /** Called when the user clicks a button or submits a form */
94
+ onAction?: ActionHandler
95
+ /** Called when a "continue" action fires \u2014 typically sends the label as a new user message */
96
+ onSend?: (message: string) => void
97
+ /** Custom prose renderer, e.g. for markdown-to-HTML */
98
+ renderProse?: (content: string, key: string) => React.ReactNode
99
+ /** CSS class overrides per component name */
100
+ classNames?: Record<string, string>
101
+ }
102
+
103
+ export function MdocMessage({ onAction, onSend, renderProse, classNames }: MdocMessageProps) {
104
+ const { nodes, isStreaming, push, done, reset } = useRenderer({ registry })
105
+ const abortRef = useRef<AbortController | null>(null)
106
+
107
+ const handleAction: ActionHandler = useCallback(
108
+ (action, params) => {
109
+ if (action === 'continue' && onSend) {
110
+ onSend(params?.label ?? action)
111
+ return
112
+ }
113
+ onAction?.(action, params)
114
+ },
115
+ [onAction, onSend],
116
+ )
117
+
118
+ /** Stream a response from your API endpoint */
119
+ const stream = useCallback(
120
+ async (url: string, body: Record<string, unknown>) => {
121
+ abortRef.current?.abort()
122
+ reset()
123
+
124
+ const controller = new AbortController()
125
+ abortRef.current = controller
126
+
127
+ const res = await fetch(url, {
128
+ method: 'POST',
129
+ headers: { 'Content-Type': 'application/json' },
130
+ body: JSON.stringify(body),
131
+ signal: controller.signal,
132
+ })
133
+
134
+ if (!res.ok || !res.body) {
135
+ throw new Error(\`Stream request failed: \${res.status}\`)
136
+ }
137
+
138
+ const reader = res.body.getReader()
139
+ const decoder = new TextDecoder()
140
+
141
+ while (true) {
142
+ const { done: readerDone, value } = await reader.read()
143
+ if (readerDone) break
144
+ push(decoder.decode(value, { stream: true }))
145
+ }
146
+
147
+ done()
148
+ },
149
+ [push, done, reset],
150
+ )
151
+
152
+ return (
153
+ <Renderer
154
+ nodes={nodes}
155
+ components={defaultComponents}
156
+ onAction={handleAction}
157
+ isStreaming={isStreaming}
158
+ renderProse={renderProse}
159
+ classNames={classNames}
160
+ />
161
+ )
162
+ }
163
+
164
+ // Export the stream helper so parent components can drive it
165
+ export type { MdocMessageProps }
166
+ export { MdocMessage as default }
167
+ `;
168
+ }
169
+ function envExampleTemplate() {
170
+ return `# mdocUI \u2014 LLM API keys
171
+ # Copy this file to .env.local and fill in your keys.
64
172
 
65
- const callout = defineComponent({
66
- name: 'callout',
67
- description: 'Highlighted message block',
68
- props: z.object({
69
- type: z.enum(['info', 'warning', 'error', 'success']).describe('Severity'),
70
- title: z.string().optional().describe('Heading'),
71
- }),
72
- children: 'any',
173
+ # Anthropic (Claude)
174
+ ANTHROPIC_API_KEY=
175
+
176
+ # OpenAI
177
+ OPENAI_API_KEY=
178
+ `;
179
+ }
180
+ function nextjsRouteTemplate() {
181
+ return `import { type NextRequest, NextResponse } from 'next/server'
182
+ import { generatePrompt } from '@mdocui/core'
183
+ import { allDefinitions, defaultGroups } from '@mdocui/react'
184
+
185
+ // Build the system prompt once at module level
186
+ const systemPrompt = generatePrompt({
187
+ components: allDefinitions,
188
+ groups: defaultGroups,
189
+ preamble: 'You are a helpful assistant. Use mdocUI components to create rich, interactive responses.',
73
190
  })
74
191
 
192
+ export async function POST(req: NextRequest) {
193
+ const { messages, provider = 'anthropic' } = await req.json()
194
+
195
+ if (provider === 'openai') {
196
+ return streamOpenAI(messages)
197
+ }
198
+
199
+ return streamAnthropic(messages)
200
+ }
201
+
202
+ // \u2500\u2500 Anthropic (Claude) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
203
+
204
+ async function streamAnthropic(messages: Array<{ role: string; content: string }>) {
205
+ const apiKey = process.env.ANTHROPIC_API_KEY
206
+ if (!apiKey) {
207
+ return NextResponse.json({ error: 'ANTHROPIC_API_KEY not set' }, { status: 500 })
208
+ }
209
+
210
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
211
+ method: 'POST',
212
+ headers: {
213
+ 'Content-Type': 'application/json',
214
+ 'x-api-key': apiKey,
215
+ 'anthropic-version': '2023-06-01',
216
+ },
217
+ body: JSON.stringify({
218
+ model: 'claude-sonnet-4-20250514',
219
+ max_tokens: 4096,
220
+ system: systemPrompt,
221
+ messages,
222
+ stream: true,
223
+ }),
224
+ })
225
+
226
+ if (!res.ok || !res.body) {
227
+ const text = await res.text()
228
+ return NextResponse.json({ error: text }, { status: res.status })
229
+ }
230
+
231
+ const encoder = new TextEncoder()
232
+ const stream = new ReadableStream({
233
+ async start(controller) {
234
+ const reader = res.body!.getReader()
235
+ const decoder = new TextDecoder()
236
+ let buffer = ''
237
+
238
+ while (true) {
239
+ const { done, value } = await reader.read()
240
+ if (done) break
241
+ buffer += decoder.decode(value, { stream: true })
242
+
243
+ const lines = buffer.split('\\n')
244
+ buffer = lines.pop() ?? ''
245
+
246
+ for (const line of lines) {
247
+ if (!line.startsWith('data: ')) continue
248
+ const data = line.slice(6)
249
+ if (data === '[DONE]') continue
250
+
251
+ try {
252
+ const event = JSON.parse(data)
253
+ if (event.type === 'content_block_delta' && event.delta?.text) {
254
+ controller.enqueue(encoder.encode(event.delta.text))
255
+ }
256
+ } catch {
257
+ // skip malformed events
258
+ }
259
+ }
260
+ }
261
+
262
+ controller.close()
263
+ },
264
+ })
265
+
266
+ return new Response(stream, {
267
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
268
+ })
269
+ }
270
+
271
+ // \u2500\u2500 OpenAI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
272
+
273
+ async function streamOpenAI(messages: Array<{ role: string; content: string }>) {
274
+ const apiKey = process.env.OPENAI_API_KEY
275
+ if (!apiKey) {
276
+ return NextResponse.json({ error: 'OPENAI_API_KEY not set' }, { status: 500 })
277
+ }
278
+
279
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
280
+ method: 'POST',
281
+ headers: {
282
+ 'Content-Type': 'application/json',
283
+ Authorization: \`Bearer \${apiKey}\`,
284
+ },
285
+ body: JSON.stringify({
286
+ model: 'gpt-4o',
287
+ messages: [{ role: 'system', content: systemPrompt }, ...messages],
288
+ stream: true,
289
+ }),
290
+ })
291
+
292
+ if (!res.ok || !res.body) {
293
+ const text = await res.text()
294
+ return NextResponse.json({ error: text }, { status: res.status })
295
+ }
296
+
297
+ const encoder = new TextEncoder()
298
+ const stream = new ReadableStream({
299
+ async start(controller) {
300
+ const reader = res.body!.getReader()
301
+ const decoder = new TextDecoder()
302
+ let buffer = ''
303
+
304
+ while (true) {
305
+ const { done, value } = await reader.read()
306
+ if (done) break
307
+ buffer += decoder.decode(value, { stream: true })
308
+
309
+ const lines = buffer.split('\\n')
310
+ buffer = lines.pop() ?? ''
311
+
312
+ for (const line of lines) {
313
+ if (!line.startsWith('data: ')) continue
314
+ const data = line.slice(6)
315
+ if (data === '[DONE]') continue
316
+
317
+ try {
318
+ const event = JSON.parse(data)
319
+ const content = event.choices?.[0]?.delta?.content
320
+ if (content) {
321
+ controller.enqueue(encoder.encode(content))
322
+ }
323
+ } catch {
324
+ // skip malformed events
325
+ }
326
+ }
327
+ }
328
+
329
+ controller.close()
330
+ },
331
+ })
332
+
333
+ return new Response(stream, {
334
+ headers: { 'Content-Type': 'text/plain; charset=utf-8' },
335
+ })
336
+ }
337
+ `;
338
+ }
339
+ function configTemplate() {
340
+ return `import { allDefinitions, defaultGroups } from '@mdocui/react'
341
+
75
342
  export default {
76
- components: [button, callout],
343
+ components: allDefinitions,
344
+ groups: defaultGroups,
77
345
  output: './generated/system-prompt.txt',
78
- preamble: 'You are a helpful assistant.',
79
- additionalRules: [
80
- 'End responses with follow-up buttons using action="continue"',
81
- ],
346
+ preamble: 'You are a helpful assistant. Use mdocUI components to create rich, interactive responses.',
82
347
  }
83
348
  `;
349
+ }
350
+ function writeIfMissing(cwd2, file) {
351
+ const abs = path3.resolve(cwd2, file.relativePath);
352
+ if (fs3.existsSync(abs)) {
353
+ console.log(` skip ${file.relativePath} (already exists)`);
354
+ return false;
355
+ }
356
+ fs3.mkdirSync(path3.dirname(abs), { recursive: true });
357
+ fs3.writeFileSync(abs, file.content, "utf-8");
358
+ return true;
359
+ }
84
360
  async function init(cwd2) {
85
- const configPath = path3.resolve(cwd2, "mdocui.config.ts");
361
+ const project = detectFramework(cwd2);
362
+ console.log("");
363
+ console.log(` Detected: ${frameworkLabel(project.framework)}`);
364
+ console.log("");
365
+ const files = [];
366
+ files.push({ relativePath: "src/lib/mdoc-registry.ts", content: registryTemplate() });
367
+ files.push({ relativePath: "src/components/mdoc-message.tsx", content: mdocMessageTemplate() });
368
+ files.push({ relativePath: ".env.example", content: envExampleTemplate() });
369
+ files.push({ relativePath: "mdocui.config.ts", content: configTemplate() });
370
+ if (project.framework === "nextjs") {
371
+ const routeDir = project.hasSrc ? "src/app/api/chat" : "app/api/chat";
372
+ files.push({ relativePath: `${routeDir}/route.ts`, content: nextjsRouteTemplate() });
373
+ }
374
+ const created = [];
375
+ const skipped = [];
376
+ for (const file of files) {
377
+ if (writeIfMissing(cwd2, file)) {
378
+ created.push(file.relativePath);
379
+ } else {
380
+ skipped.push(file.relativePath);
381
+ }
382
+ }
86
383
  const generatedDir = path3.resolve(cwd2, "generated");
87
- if (fs3.existsSync(configPath)) {
88
- console.log("mdocui.config.ts already exists \u2014 skipping");
89
- return;
90
- }
91
- fs3.writeFileSync(configPath, CONFIG_TEMPLATE, "utf-8");
92
- fs3.mkdirSync(generatedDir, { recursive: true });
93
- fs3.writeFileSync(path3.resolve(generatedDir, ".gitkeep"), "", "utf-8");
94
- console.log("Created:");
95
- console.log(" mdocui.config.ts \u2014 component definitions + config");
96
- console.log(" generated/ \u2014 output directory for system prompts");
384
+ if (!fs3.existsSync(generatedDir)) {
385
+ fs3.mkdirSync(generatedDir, { recursive: true });
386
+ fs3.writeFileSync(path3.resolve(generatedDir, ".gitkeep"), "", "utf-8");
387
+ created.push("generated/");
388
+ }
97
389
  console.log("");
98
- console.log("Next steps:");
99
- console.log(" 1. Edit mdocui.config.ts \u2014 add your components");
100
- console.log(" 2. Run: npx @mdocui/cli generate");
390
+ if (created.length > 0) {
391
+ console.log(" Created:");
392
+ for (const f of created) {
393
+ console.log(` + ${f}`);
394
+ }
395
+ }
396
+ if (skipped.length > 0) {
397
+ console.log(" Skipped (already exist):");
398
+ for (const f of skipped) {
399
+ console.log(` - ${f}`);
400
+ }
401
+ }
402
+ console.log("");
403
+ console.log(" Next steps:");
404
+ console.log(" 1. Copy .env.example to .env.local and add your API keys");
405
+ console.log(" 2. Install dependencies:");
406
+ console.log(" npm install @mdocui/core @mdocui/react");
407
+ console.log(" 3. Generate the system prompt:");
408
+ console.log(" npx @mdocui/cli generate");
409
+ if (project.framework === "nextjs") {
410
+ console.log(" 4. Import <MdocMessage /> in your chat page");
411
+ console.log(' and call stream("/api/chat", { messages })');
412
+ } else {
413
+ console.log(" 4. Import <MdocMessage /> in your chat component");
414
+ console.log(" and wire up streaming from your backend");
415
+ }
416
+ console.log("");
417
+ }
418
+ function frameworkLabel(fw) {
419
+ switch (fw) {
420
+ case "nextjs":
421
+ return "Next.js";
422
+ case "vite":
423
+ return "Vite";
424
+ case "remix":
425
+ return "Remix";
426
+ case "unknown":
427
+ return "Unknown framework (generating generic files)";
428
+ }
101
429
  }
102
430
 
103
431
  // src/commands/preview.ts
104
- import { ComponentRegistry as ComponentRegistry2, generatePrompt as generatePrompt2 } from "@mdocui/core";
105
- async function preview(cwd2) {
106
- const config = await loadConfig(cwd2);
107
- const registry = new ComponentRegistry2();
108
- registry.registerAll(config.components);
109
- const prompt = generatePrompt2(registry, toPromptOptions(config));
110
- console.log(prompt);
432
+ import http from "http";
433
+ import { StreamingParser } from "@mdocui/core";
434
+ async function preview(cwd2, options) {
435
+ const port = options?.port ?? 4321;
436
+ let knownTags;
437
+ try {
438
+ const config = await loadConfig(cwd2);
439
+ knownTags = new Set(config.components.map((c) => c.name));
440
+ console.log(`Loaded config with ${knownTags.size} components: ${[...knownTags].join(", ")}`);
441
+ } catch {
442
+ console.log("No config found \u2014 all tags treated as known");
443
+ }
444
+ const server = http.createServer((req, res) => {
445
+ if (req.method === "POST" && req.url === "/api/parse") {
446
+ let body = "";
447
+ req.on("data", (chunk) => {
448
+ body += chunk.toString();
449
+ });
450
+ req.on("end", () => {
451
+ try {
452
+ const { content } = JSON.parse(body);
453
+ const parser = new StreamingParser(
454
+ knownTags ? { knownTags, dropUnknown: false } : void 0
455
+ );
456
+ parser.write(content);
457
+ parser.flush();
458
+ const nodes = parser.getNodes();
459
+ const meta = parser.getMeta();
460
+ res.writeHead(200, { "Content-Type": "application/json" });
461
+ res.end(JSON.stringify({ nodes, meta }));
462
+ } catch (err) {
463
+ res.writeHead(400, { "Content-Type": "application/json" });
464
+ res.end(JSON.stringify({ error: err.message }));
465
+ }
466
+ });
467
+ return;
468
+ }
469
+ if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
470
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
471
+ res.end(buildHTML(knownTags));
472
+ return;
473
+ }
474
+ res.writeHead(404, { "Content-Type": "text/plain" });
475
+ res.end("Not found");
476
+ });
477
+ server.listen(port, () => {
478
+ console.log(`
479
+ mdocui preview server running at:
480
+ `);
481
+ console.log(` http://localhost:${port}
482
+ `);
483
+ console.log("Press Ctrl+C to stop.\n");
484
+ });
485
+ }
486
+ function buildHTML(knownTags) {
487
+ const tagList = knownTags ? JSON.stringify([...knownTags]) : "[]";
488
+ return (
489
+ /* html */
490
+ `<!DOCTYPE html>
491
+ <html lang="en">
492
+ <head>
493
+ <meta charset="utf-8" />
494
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
495
+ <title>mdocui preview</title>
496
+ <style>
497
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
498
+
499
+ :root {
500
+ --bg: #0a0a0a;
501
+ --bg-surface: #141414;
502
+ --bg-surface-2: #1e1e1e;
503
+ --border: #2a2a2a;
504
+ --text: #e4e4e4;
505
+ --text-dim: #888;
506
+ --accent: #7c6bff;
507
+ --accent-dim: #5a4fbb;
508
+ --error: #f06;
509
+ --tag-bg: #1a1a2e;
510
+ --tag-border: #2a2a4e;
511
+ --prose-bg: #0f1a0f;
512
+ --prose-border: #1a3a1a;
513
+ --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', Menlo, monospace;
514
+ --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
515
+ }
516
+
517
+ html, body { height: 100%; background: var(--bg); color: var(--text); font-family: var(--font-sans); }
518
+
519
+ .app {
520
+ display: flex;
521
+ flex-direction: column;
522
+ height: 100vh;
523
+ }
524
+
525
+ header {
526
+ display: flex;
527
+ align-items: center;
528
+ justify-content: space-between;
529
+ padding: 10px 20px;
530
+ border-bottom: 1px solid var(--border);
531
+ background: var(--bg-surface);
532
+ }
533
+
534
+ header h1 {
535
+ font-size: 14px;
536
+ font-weight: 600;
537
+ letter-spacing: 0.5px;
538
+ color: var(--accent);
539
+ }
540
+
541
+ header .meta {
542
+ font-size: 12px;
543
+ color: var(--text-dim);
544
+ }
545
+
546
+ .panels {
547
+ display: flex;
548
+ flex: 1;
549
+ min-height: 0;
550
+ }
551
+
552
+ .panel {
553
+ flex: 1;
554
+ display: flex;
555
+ flex-direction: column;
556
+ min-width: 0;
557
+ }
558
+
559
+ .panel + .panel {
560
+ border-left: 1px solid var(--border);
561
+ }
562
+
563
+ .panel-header {
564
+ padding: 8px 16px;
565
+ font-size: 11px;
566
+ font-weight: 600;
567
+ text-transform: uppercase;
568
+ letter-spacing: 1px;
569
+ color: var(--text-dim);
570
+ background: var(--bg-surface);
571
+ border-bottom: 1px solid var(--border);
572
+ }
573
+
574
+ textarea {
575
+ flex: 1;
576
+ width: 100%;
577
+ padding: 16px;
578
+ background: var(--bg);
579
+ color: var(--text);
580
+ border: none;
581
+ resize: none;
582
+ font-family: var(--font-mono);
583
+ font-size: 13px;
584
+ line-height: 1.6;
585
+ outline: none;
586
+ tab-size: 2;
587
+ }
588
+
589
+ textarea::placeholder { color: var(--text-dim); }
590
+
591
+ .preview {
592
+ flex: 1;
593
+ overflow-y: auto;
594
+ padding: 16px;
595
+ background: var(--bg);
596
+ }
597
+
598
+ /* AST node rendering */
599
+
600
+ .node-prose {
601
+ background: var(--prose-bg);
602
+ border: 1px solid var(--prose-border);
603
+ border-radius: 6px;
604
+ padding: 12px 16px;
605
+ margin-bottom: 8px;
606
+ font-size: 14px;
607
+ line-height: 1.7;
608
+ }
609
+
610
+ .node-prose p { margin-bottom: 0.5em; }
611
+ .node-prose p:last-child { margin-bottom: 0; }
612
+ .node-prose strong { color: #fff; }
613
+ .node-prose em { color: #ccc; font-style: italic; }
614
+ .node-prose code {
615
+ background: var(--bg-surface-2);
616
+ padding: 2px 6px;
617
+ border-radius: 3px;
618
+ font-family: var(--font-mono);
619
+ font-size: 12px;
620
+ }
621
+ .node-prose a { color: var(--accent); text-decoration: underline; }
622
+ .node-prose ul, .node-prose ol { padding-left: 1.5em; margin-bottom: 0.5em; }
623
+ .node-prose h1, .node-prose h2, .node-prose h3 {
624
+ color: #fff;
625
+ margin-bottom: 0.4em;
626
+ }
627
+ .node-prose h1 { font-size: 1.4em; }
628
+ .node-prose h2 { font-size: 1.2em; }
629
+ .node-prose h3 { font-size: 1.05em; }
630
+
631
+ .node-component {
632
+ background: var(--tag-bg);
633
+ border: 1px solid var(--tag-border);
634
+ border-radius: 6px;
635
+ margin-bottom: 8px;
636
+ overflow: hidden;
637
+ }
638
+
639
+ .node-component-header {
640
+ display: flex;
641
+ align-items: center;
642
+ gap: 8px;
643
+ padding: 8px 12px;
644
+ background: rgba(124, 107, 255, 0.08);
645
+ border-bottom: 1px solid var(--tag-border);
646
+ }
647
+
648
+ .node-component-name {
649
+ font-family: var(--font-mono);
650
+ font-size: 13px;
651
+ font-weight: 600;
652
+ color: var(--accent);
653
+ }
654
+
655
+ .node-component-badge {
656
+ font-size: 10px;
657
+ padding: 2px 6px;
658
+ border-radius: 3px;
659
+ background: var(--accent-dim);
660
+ color: #fff;
661
+ }
662
+
663
+ .node-component-props {
664
+ padding: 8px 12px;
665
+ }
666
+
667
+ .prop-row {
668
+ display: flex;
669
+ gap: 8px;
670
+ font-size: 12px;
671
+ font-family: var(--font-mono);
672
+ padding: 2px 0;
673
+ }
674
+
675
+ .prop-key { color: #82aaff; }
676
+ .prop-eq { color: var(--text-dim); }
677
+ .prop-val { color: #c3e88d; }
678
+
679
+ .node-component-children {
680
+ padding: 8px 12px;
681
+ border-top: 1px solid var(--tag-border);
682
+ }
683
+
684
+ .node-component-children-label {
685
+ font-size: 10px;
686
+ text-transform: uppercase;
687
+ letter-spacing: 0.5px;
688
+ color: var(--text-dim);
689
+ margin-bottom: 6px;
690
+ }
691
+
692
+ .errors {
693
+ padding: 8px 16px;
694
+ background: rgba(255, 0, 102, 0.08);
695
+ border-top: 1px solid rgba(255, 0, 102, 0.2);
696
+ }
697
+
698
+ .error-item {
699
+ font-size: 12px;
700
+ color: var(--error);
701
+ font-family: var(--font-mono);
702
+ padding: 2px 0;
703
+ }
704
+
705
+ .error-item::before { content: '\\26A0 '; }
706
+
707
+ .status-bar {
708
+ display: flex;
709
+ align-items: center;
710
+ justify-content: space-between;
711
+ padding: 4px 16px;
712
+ font-size: 11px;
713
+ color: var(--text-dim);
714
+ border-top: 1px solid var(--border);
715
+ background: var(--bg-surface);
716
+ }
717
+
718
+ .empty-state {
719
+ display: flex;
720
+ align-items: center;
721
+ justify-content: center;
722
+ height: 100%;
723
+ color: var(--text-dim);
724
+ font-size: 13px;
725
+ text-align: center;
726
+ line-height: 1.8;
727
+ }
728
+ </style>
729
+ </head>
730
+ <body>
731
+ <div class="app">
732
+ <header>
733
+ <h1>mdocui preview</h1>
734
+ <span class="meta" id="tag-count"></span>
735
+ </header>
736
+ <div class="panels">
737
+ <div class="panel">
738
+ <div class="panel-header">Input</div>
739
+ <textarea id="editor" spellcheck="false" placeholder="Type mdocUI markup here...
740
+
741
+ Example:
742
+ Here is a **weather card**:
743
+
744
+ {% WeatherCard city=&quot;San Francisco&quot; temp=72 unit=&quot;F&quot; /%}
745
+
746
+ Or with body content:
747
+
748
+ {% Alert type=&quot;info&quot; %}
749
+ This is an informational alert with **markdown** inside.
750
+ {% /Alert %}"></textarea>
751
+ </div>
752
+ <div class="panel">
753
+ <div class="panel-header">Preview</div>
754
+ <div class="preview" id="preview">
755
+ <div class="empty-state">Start typing mdocUI markup<br/>to see the live preview</div>
756
+ </div>
757
+ <div class="errors" id="errors" style="display:none"></div>
758
+ </div>
759
+ </div>
760
+ <div class="status-bar">
761
+ <span id="status">Ready</span>
762
+ <span id="node-count"></span>
763
+ </div>
764
+ </div>
765
+
766
+ <script>
767
+ (function() {
768
+ const knownTags = ${tagList};
769
+ const editor = document.getElementById('editor');
770
+ const preview = document.getElementById('preview');
771
+ const errors = document.getElementById('errors');
772
+ const status = document.getElementById('status');
773
+ const nodeCount = document.getElementById('node-count');
774
+ const tagCountEl = document.getElementById('tag-count');
775
+
776
+ if (knownTags.length > 0) {
777
+ tagCountEl.textContent = knownTags.length + ' registered component' + (knownTags.length === 1 ? '' : 's');
778
+ } else {
779
+ tagCountEl.textContent = 'no config \u2014 all tags accepted';
780
+ }
781
+
782
+ let timer = null;
783
+
784
+ editor.addEventListener('input', function() {
785
+ clearTimeout(timer);
786
+ timer = setTimeout(parse, 150);
787
+ });
788
+
789
+ // also handle paste
790
+ editor.addEventListener('paste', function() {
791
+ clearTimeout(timer);
792
+ timer = setTimeout(parse, 50);
793
+ });
794
+
795
+ async function parse() {
796
+ const content = editor.value;
797
+ if (!content.trim()) {
798
+ preview.innerHTML = '<div class="empty-state">Start typing mdocUI markup<br/>to see the live preview</div>';
799
+ errors.style.display = 'none';
800
+ status.textContent = 'Ready';
801
+ nodeCount.textContent = '';
802
+ return;
803
+ }
804
+
805
+ status.textContent = 'Parsing...';
806
+
807
+ try {
808
+ const res = await fetch('/api/parse', {
809
+ method: 'POST',
810
+ headers: { 'Content-Type': 'application/json' },
811
+ body: JSON.stringify({ content }),
812
+ });
813
+
814
+ const data = await res.json();
815
+
816
+ if (data.error) {
817
+ status.textContent = 'Error: ' + data.error;
818
+ return;
819
+ }
820
+
821
+ preview.innerHTML = renderNodes(data.nodes);
822
+ nodeCount.textContent = data.meta.nodeCount + ' node' + (data.meta.nodeCount === 1 ? '' : 's');
823
+
824
+ if (data.meta.errors && data.meta.errors.length > 0) {
825
+ errors.style.display = 'block';
826
+ errors.innerHTML = data.meta.errors
827
+ .map(function(e) { return '<div class="error-item">' + escapeHtml(e.message) + '</div>'; })
828
+ .join('');
829
+ status.textContent = 'Parsed with ' + data.meta.errors.length + ' warning(s)';
830
+ } else {
831
+ errors.style.display = 'none';
832
+ status.textContent = 'OK';
833
+ }
834
+ } catch (err) {
835
+ status.textContent = 'Fetch error: ' + err.message;
836
+ }
837
+ }
838
+
839
+ function renderNodes(nodes) {
840
+ if (!nodes || nodes.length === 0) return '<div class="empty-state">No nodes parsed</div>';
841
+ return nodes.map(renderNode).join('');
842
+ }
843
+
844
+ function renderNode(node) {
845
+ if (node.type === 'prose') {
846
+ return '<div class="node-prose">' + markdownToHtml(node.content) + '</div>';
847
+ }
848
+
849
+ if (node.type === 'component') {
850
+ var html = '<div class="node-component">';
851
+ html += '<div class="node-component-header">';
852
+ html += '<span class="node-component-name">' + escapeHtml(node.name) + '</span>';
853
+ if (node.selfClosing) {
854
+ html += '<span class="node-component-badge">self-closing</span>';
855
+ }
856
+ html += '</div>';
857
+
858
+ var propKeys = Object.keys(node.props || {});
859
+ if (propKeys.length > 0) {
860
+ html += '<div class="node-component-props">';
861
+ propKeys.forEach(function(key) {
862
+ var val = node.props[key];
863
+ var display = typeof val === 'string' ? '"' + escapeHtml(val) + '"' : String(val);
864
+ html += '<div class="prop-row">';
865
+ html += '<span class="prop-key">' + escapeHtml(key) + '</span>';
866
+ html += '<span class="prop-eq">=</span>';
867
+ html += '<span class="prop-val">' + display + '</span>';
868
+ html += '</div>';
869
+ });
870
+ html += '</div>';
871
+ }
872
+
873
+ if (node.children && node.children.length > 0) {
874
+ html += '<div class="node-component-children">';
875
+ html += '<div class="node-component-children-label">Children</div>';
876
+ html += renderNodes(node.children);
877
+ html += '</div>';
878
+ }
879
+
880
+ html += '</div>';
881
+ return html;
882
+ }
883
+
884
+ return '';
885
+ }
886
+
887
+ /** Minimal markdown-to-HTML for prose content */
888
+ function markdownToHtml(text) {
889
+ var html = escapeHtml(text);
890
+
891
+ // headings
892
+ html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
893
+ html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
894
+ html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
895
+
896
+ // bold + italic
897
+ html = html.replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>');
898
+ html = html.replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>');
899
+ html = html.replace(/\\*(.+?)\\*/g, '<em>$1</em>');
900
+
901
+ // inline code
902
+ html = html.replace(/\`([^\`]+)\`/g, '<code>$1</code>');
903
+
904
+ // links
905
+ html = html.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2" target="_blank">$1</a>');
906
+
907
+ // unordered lists
908
+ html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
909
+ html = html.replace(/(<li>.*<\\/li>\\n?)+/g, function(m) { return '<ul>' + m + '</ul>'; });
910
+
911
+ // paragraphs: split on double newlines
912
+ html = html.split(/\\n\\n+/).map(function(block) {
913
+ block = block.trim();
914
+ if (!block) return '';
915
+ if (/^<(h[1-3]|ul|ol|li)/.test(block)) return block;
916
+ return '<p>' + block.replace(/\\n/g, '<br/>') + '</p>';
917
+ }).join('');
918
+
919
+ return html;
920
+ }
921
+
922
+ function escapeHtml(str) {
923
+ return String(str)
924
+ .replace(/&/g, '&amp;')
925
+ .replace(/</g, '&lt;')
926
+ .replace(/>/g, '&gt;')
927
+ .replace(/"/g, '&quot;');
928
+ }
929
+ })();
930
+ </script>
931
+ </body>
932
+ </html>`
933
+ );
111
934
  }
112
935
 
113
936
  // src/index.ts
114
937
  var args = process.argv.slice(2);
115
938
  var command = args[0];
116
939
  var cwd = process.cwd();
940
+ function parseFlag(flag) {
941
+ const idx = args.indexOf(flag);
942
+ if (idx !== -1 && idx + 1 < args.length) return args[idx + 1];
943
+ const eq = args.find((a) => a.startsWith(`${flag}=`));
944
+ if (eq) return eq.split("=")[1];
945
+ return void 0;
946
+ }
117
947
  async function main() {
118
948
  switch (command) {
119
949
  case "generate":
120
950
  await generate(cwd);
121
951
  break;
122
- case "preview":
123
- await preview(cwd);
952
+ case "preview": {
953
+ const portStr = parseFlag("--port");
954
+ await preview(cwd, portStr ? { port: Number(portStr) } : void 0);
124
955
  break;
956
+ }
125
957
  case "init":
126
958
  await init(cwd);
127
959
  break;
@@ -141,14 +973,18 @@ function printHelp() {
141
973
  mdocui \u2014 CLI for mdocUI generative UI library
142
974
 
143
975
  Commands:
144
- init Scaffold a new mdocui config file
976
+ init Scaffold mdocUI integration (registry, components, API route)
145
977
  generate Generate system prompt from component registry
146
- preview Print generated system prompt to stdout
978
+ preview Start local dev server with live mdocUI markup preview
979
+
980
+ Options:
981
+ --port <n> Port for preview server (default: 4321)
147
982
 
148
983
  Usage:
149
984
  npx @mdocui/cli init
150
985
  npx @mdocui/cli generate
151
986
  npx @mdocui/cli preview
987
+ npx @mdocui/cli preview --port 3000
152
988
  `);
153
989
  }
154
990
  main().catch((err) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/commands/generate.ts","../src/config.ts","../src/commands/init.ts","../src/commands/preview.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport { ComponentRegistry, generatePrompt } from '@mdocui/core'\nimport { loadConfig, toPromptOptions } from '../config'\n\nexport async function generate(cwd: string) {\n\tconst config = await loadConfig(cwd)\n\tconst registry = new ComponentRegistry()\n\tregistry.registerAll(config.components)\n\n\tconst prompt = generatePrompt(registry, toPromptOptions(config))\n\tconst outputPath = path.resolve(cwd, config.output ?? 'system-prompt.txt')\n\n\tfs.mkdirSync(path.dirname(outputPath), { recursive: true })\n\tfs.writeFileSync(outputPath, prompt, 'utf-8')\n\n\tconsole.log(`Generated system prompt → ${path.relative(cwd, outputPath)}`)\n\tconsole.log(`${registry.names().length} components, ${prompt.length} chars`)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ComponentDefinition, ComponentGroup, PromptOptions } from '@mdocui/core'\n\nexport interface MdocUIConfig {\n\tcomponents: ComponentDefinition[]\n\toutput?: string\n\tpreamble?: string\n\tadditionalRules?: string[]\n\texamples?: string[]\n\tgroups?: ComponentGroup[]\n}\n\nconst CONFIG_FILES = ['mdocui.config.ts', 'mdocui.config.js', 'mdocui.config.mjs']\n\nexport async function loadConfig(cwd: string): Promise<MdocUIConfig> {\n\tfor (const name of CONFIG_FILES) {\n\t\tconst configPath = path.resolve(cwd, name)\n\t\tif (!fs.existsSync(configPath)) continue\n\n\t\tconst url = pathToFileURL(configPath).href\n\t\tconst mod = await import(url)\n\t\tconst config = mod.default ?? mod\n\n\t\tif (!config.components || !Array.isArray(config.components)) {\n\t\t\tthrow new Error(`${name}: \"components\" must be an array of ComponentDefinition`)\n\t\t}\n\n\t\treturn config as MdocUIConfig\n\t}\n\n\tthrow new Error(`No config file found. Create one of: ${CONFIG_FILES.join(', ')}`)\n}\n\nexport function toPromptOptions(config: MdocUIConfig): PromptOptions {\n\treturn {\n\t\tpreamble: config.preamble,\n\t\tadditionalRules: config.additionalRules,\n\t\texamples: config.examples,\n\t\tgroups: config.groups,\n\t}\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\nconst CONFIG_TEMPLATE = `import { defineComponent } from '@mdocui/core'\nimport { z } from 'zod'\n\nconst button = defineComponent({\n name: 'button',\n description: 'Clickable action button',\n props: z.object({\n action: z.string().describe('Action to perform'),\n label: z.string().describe('Button text'),\n }),\n children: 'none',\n})\n\nconst callout = defineComponent({\n name: 'callout',\n description: 'Highlighted message block',\n props: z.object({\n type: z.enum(['info', 'warning', 'error', 'success']).describe('Severity'),\n title: z.string().optional().describe('Heading'),\n }),\n children: 'any',\n})\n\nexport default {\n components: [button, callout],\n output: './generated/system-prompt.txt',\n preamble: 'You are a helpful assistant.',\n additionalRules: [\n 'End responses with follow-up buttons using action=\"continue\"',\n ],\n}\n`\n\nexport async function init(cwd: string) {\n\tconst configPath = path.resolve(cwd, 'mdocui.config.ts')\n\tconst generatedDir = path.resolve(cwd, 'generated')\n\n\tif (fs.existsSync(configPath)) {\n\t\tconsole.log('mdocui.config.ts already exists — skipping')\n\t\treturn\n\t}\n\n\tfs.writeFileSync(configPath, CONFIG_TEMPLATE, 'utf-8')\n\tfs.mkdirSync(generatedDir, { recursive: true })\n\tfs.writeFileSync(path.resolve(generatedDir, '.gitkeep'), '', 'utf-8')\n\n\tconsole.log('Created:')\n\tconsole.log(' mdocui.config.ts — component definitions + config')\n\tconsole.log(' generated/ — output directory for system prompts')\n\tconsole.log('')\n\tconsole.log('Next steps:')\n\tconsole.log(' 1. Edit mdocui.config.ts — add your components')\n\tconsole.log(' 2. Run: npx @mdocui/cli generate')\n}\n","import { ComponentRegistry, generatePrompt } from '@mdocui/core'\nimport { loadConfig, toPromptOptions } from '../config'\n\nexport async function preview(cwd: string) {\n\tconst config = await loadConfig(cwd)\n\tconst registry = new ComponentRegistry()\n\tregistry.registerAll(config.components)\n\n\tconst prompt = generatePrompt(registry, toPromptOptions(config))\n\tconsole.log(prompt)\n}\n","import { generate } from './commands/generate'\nimport { init } from './commands/init'\nimport { preview } from './commands/preview'\n\nconst args = process.argv.slice(2)\nconst command = args[0]\nconst cwd = process.cwd()\n\nasync function main() {\n\tswitch (command) {\n\t\tcase 'generate':\n\t\t\tawait generate(cwd)\n\t\t\tbreak\n\t\tcase 'preview':\n\t\t\tawait preview(cwd)\n\t\t\tbreak\n\t\tcase 'init':\n\t\t\tawait init(cwd)\n\t\t\tbreak\n\t\tcase '--help':\n\t\tcase '-h':\n\t\tcase undefined:\n\t\t\tprintHelp()\n\t\t\tbreak\n\t\tdefault:\n\t\t\tconsole.error(`Unknown command: ${command}`)\n\t\t\tprintHelp()\n\t\t\tprocess.exit(1)\n\t}\n}\n\nfunction printHelp() {\n\tconsole.log(`\nmdocui — CLI for mdocUI generative UI library\n\nCommands:\n init Scaffold a new mdocui config file\n generate Generate system prompt from component registry\n preview Print generated system prompt to stdout\n\nUsage:\n npx @mdocui/cli init\n npx @mdocui/cli generate\n npx @mdocui/cli preview\n`)\n}\n\nmain().catch((err) => {\n\tconsole.error(err.message)\n\tprocess.exit(1)\n})\n"],"mappings":";;;AAAA,OAAOA,SAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,mBAAmB,sBAAsB;;;ACFlD,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAY9B,IAAM,eAAe,CAAC,oBAAoB,oBAAoB,mBAAmB;AAEjF,eAAsB,WAAWC,MAAoC;AACpE,aAAW,QAAQ,cAAc;AAChC,UAAM,aAAa,KAAK,QAAQA,MAAK,IAAI;AACzC,QAAI,CAAC,GAAG,WAAW,UAAU,EAAG;AAEhC,UAAM,MAAM,cAAc,UAAU,EAAE;AACtC,UAAM,MAAM,MAAM,OAAO;AACzB,UAAM,SAAS,IAAI,WAAW;AAE9B,QAAI,CAAC,OAAO,cAAc,CAAC,MAAM,QAAQ,OAAO,UAAU,GAAG;AAC5D,YAAM,IAAI,MAAM,GAAG,IAAI,wDAAwD;AAAA,IAChF;AAEA,WAAO;AAAA,EACR;AAEA,QAAM,IAAI,MAAM,wCAAwC,aAAa,KAAK,IAAI,CAAC,EAAE;AAClF;AAEO,SAAS,gBAAgB,QAAqC;AACpE,SAAO;AAAA,IACN,UAAU,OAAO;AAAA,IACjB,iBAAiB,OAAO;AAAA,IACxB,UAAU,OAAO;AAAA,IACjB,QAAQ,OAAO;AAAA,EAChB;AACD;;;ADrCA,eAAsB,SAASC,MAAa;AAC3C,QAAM,SAAS,MAAM,WAAWA,IAAG;AACnC,QAAM,WAAW,IAAI,kBAAkB;AACvC,WAAS,YAAY,OAAO,UAAU;AAEtC,QAAM,SAAS,eAAe,UAAU,gBAAgB,MAAM,CAAC;AAC/D,QAAM,aAAaC,MAAK,QAAQD,MAAK,OAAO,UAAU,mBAAmB;AAEzE,EAAAE,IAAG,UAAUD,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,EAAAC,IAAG,cAAc,YAAY,QAAQ,OAAO;AAE5C,UAAQ,IAAI,kCAA6BD,MAAK,SAASD,MAAK,UAAU,CAAC,EAAE;AACzE,UAAQ,IAAI,GAAG,SAAS,MAAM,EAAE,MAAM,gBAAgB,OAAO,MAAM,QAAQ;AAC5E;;;AElBA,OAAOG,SAAQ;AACf,OAAOC,WAAU;AAEjB,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiCxB,eAAsB,KAAKC,MAAa;AACvC,QAAM,aAAaD,MAAK,QAAQC,MAAK,kBAAkB;AACvD,QAAM,eAAeD,MAAK,QAAQC,MAAK,WAAW;AAElD,MAAIF,IAAG,WAAW,UAAU,GAAG;AAC9B,YAAQ,IAAI,iDAA4C;AACxD;AAAA,EACD;AAEA,EAAAA,IAAG,cAAc,YAAY,iBAAiB,OAAO;AACrD,EAAAA,IAAG,UAAU,cAAc,EAAE,WAAW,KAAK,CAAC;AAC9C,EAAAA,IAAG,cAAcC,MAAK,QAAQ,cAAc,UAAU,GAAG,IAAI,OAAO;AAEpE,UAAQ,IAAI,UAAU;AACtB,UAAQ,IAAI,6DAAwD;AACpE,UAAQ,IAAI,kEAA6D;AACzE,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,aAAa;AACzB,UAAQ,IAAI,uDAAkD;AAC9D,UAAQ,IAAI,oCAAoC;AACjD;;;ACxDA,SAAS,qBAAAE,oBAAmB,kBAAAC,uBAAsB;AAGlD,eAAsB,QAAQC,MAAa;AAC1C,QAAM,SAAS,MAAM,WAAWA,IAAG;AACnC,QAAM,WAAW,IAAIC,mBAAkB;AACvC,WAAS,YAAY,OAAO,UAAU;AAEtC,QAAM,SAASC,gBAAe,UAAU,gBAAgB,MAAM,CAAC;AAC/D,UAAQ,IAAI,MAAM;AACnB;;;ACNA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAM,UAAU,KAAK,CAAC;AACtB,IAAM,MAAM,QAAQ,IAAI;AAExB,eAAe,OAAO;AACrB,UAAQ,SAAS;AAAA,IAChB,KAAK;AACJ,YAAM,SAAS,GAAG;AAClB;AAAA,IACD,KAAK;AACJ,YAAM,QAAQ,GAAG;AACjB;AAAA,IACD,KAAK;AACJ,YAAM,KAAK,GAAG;AACd;AAAA,IACD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,gBAAU;AACV;AAAA,IACD;AACC,cAAQ,MAAM,oBAAoB,OAAO,EAAE;AAC3C,gBAAU;AACV,cAAQ,KAAK,CAAC;AAAA,EAChB;AACD;AAEA,SAAS,YAAY;AACpB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAYZ;AACD;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACrB,UAAQ,MAAM,IAAI,OAAO;AACzB,UAAQ,KAAK,CAAC;AACf,CAAC;","names":["fs","path","cwd","cwd","path","fs","fs","path","cwd","ComponentRegistry","generatePrompt","cwd","ComponentRegistry","generatePrompt"]}
1
+ {"version":3,"sources":["../src/commands/generate.ts","../src/config.ts","../src/commands/init.ts","../src/commands/preview.ts","../src/index.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport { ComponentRegistry, generatePrompt } from '@mdocui/core'\nimport { loadConfig, toPromptOptions } from '../config'\n\nexport async function generate(cwd: string) {\n\tconst config = await loadConfig(cwd)\n\tconst registry = new ComponentRegistry()\n\tregistry.registerAll(config.components)\n\n\tconst prompt = generatePrompt(registry, toPromptOptions(config))\n\tconst outputPath = path.resolve(cwd, config.output ?? 'system-prompt.txt')\n\n\tfs.mkdirSync(path.dirname(outputPath), { recursive: true })\n\tfs.writeFileSync(outputPath, prompt, 'utf-8')\n\n\tconsole.log(`Generated system prompt → ${path.relative(cwd, outputPath)}`)\n\tconsole.log(`${registry.names().length} components, ${prompt.length} chars`)\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { ComponentDefinition, ComponentGroup, PromptOptions } from '@mdocui/core'\n\nexport interface MdocUIConfig {\n\tcomponents: ComponentDefinition[]\n\toutput?: string\n\tpreamble?: string\n\tadditionalRules?: string[]\n\texamples?: string[]\n\tgroups?: ComponentGroup[]\n}\n\nconst CONFIG_FILES = ['mdocui.config.ts', 'mdocui.config.js', 'mdocui.config.mjs']\n\nexport async function loadConfig(cwd: string): Promise<MdocUIConfig> {\n\tfor (const name of CONFIG_FILES) {\n\t\tconst configPath = path.resolve(cwd, name)\n\t\tif (!fs.existsSync(configPath)) continue\n\n\t\tconst url = pathToFileURL(configPath).href\n\t\tconst mod = await import(url)\n\t\tconst config = mod.default ?? mod\n\n\t\tif (!config.components || !Array.isArray(config.components)) {\n\t\t\tthrow new Error(`${name}: \"components\" must be an array of ComponentDefinition`)\n\t\t}\n\n\t\treturn config as MdocUIConfig\n\t}\n\n\tthrow new Error(`No config file found. Create one of: ${CONFIG_FILES.join(', ')}`)\n}\n\nexport function toPromptOptions(config: MdocUIConfig): PromptOptions {\n\treturn {\n\t\tpreamble: config.preamble,\n\t\tadditionalRules: config.additionalRules,\n\t\texamples: config.examples,\n\t\tgroups: config.groups,\n\t}\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\ntype Framework = 'nextjs' | 'vite' | 'remix' | 'unknown'\n\ninterface DetectedProject {\n\tframework: Framework\n\thasSrc: boolean\n\thasApp: boolean // Next.js app router\n\thasPages: boolean // Next.js pages router\n}\n\nfunction detectFramework(cwd: string): DetectedProject {\n\tconst pkgPath = path.resolve(cwd, 'package.json')\n\tconst hasSrc = fs.existsSync(path.resolve(cwd, 'src'))\n\tconst hasApp =\n\t\tfs.existsSync(path.resolve(cwd, 'src/app')) || fs.existsSync(path.resolve(cwd, 'app'))\n\tconst hasPages =\n\t\tfs.existsSync(path.resolve(cwd, 'src/pages')) || fs.existsSync(path.resolve(cwd, 'pages'))\n\n\tif (!fs.existsSync(pkgPath)) {\n\t\treturn { framework: 'unknown', hasSrc, hasApp, hasPages }\n\t}\n\n\tconst pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))\n\tconst allDeps = { ...pkg.dependencies, ...pkg.devDependencies }\n\n\tif (allDeps.next) return { framework: 'nextjs', hasSrc, hasApp, hasPages }\n\tif (allDeps['@remix-run/react'] || allDeps.remix)\n\t\treturn { framework: 'remix', hasSrc, hasApp, hasPages }\n\tif (allDeps.vite) return { framework: 'vite', hasSrc, hasApp, hasPages }\n\n\treturn { framework: 'unknown', hasSrc, hasApp, hasPages }\n}\n\n// ── File templates ─────────────────────────────────────────\n\nfunction registryTemplate(): string {\n\treturn `import { createDefaultRegistry } from '@mdocui/react'\n\n// Pre-built registry with all 24 built-in mdocUI components:\n// Layout: stack, grid, card, divider, accordion, tabs, tab\n// Interactive: button, button-group, input, textarea, select, checkbox, toggle, form\n// Data: chart, table, stat, progress\n// Content: callout, badge, image, code-block, link\n//\n// To add custom components, use the ComponentRegistry API directly:\n// import { ComponentRegistry } from '@mdocui/core'\n// import { allDefinitions } from '@mdocui/react'\n\nexport const registry = createDefaultRegistry()\n`\n}\n\nfunction mdocMessageTemplate(): string {\n\treturn `'use client'\n\nimport { useCallback, useRef } from 'react'\nimport { Renderer, useRenderer, defaultComponents } from '@mdocui/react'\nimport type { ActionHandler } from '@mdocui/react'\nimport { registry } from '@/lib/mdoc-registry'\n\ninterface MdocMessageProps {\n /** Called when the user clicks a button or submits a form */\n onAction?: ActionHandler\n /** Called when a \"continue\" action fires — typically sends the label as a new user message */\n onSend?: (message: string) => void\n /** Custom prose renderer, e.g. for markdown-to-HTML */\n renderProse?: (content: string, key: string) => React.ReactNode\n /** CSS class overrides per component name */\n classNames?: Record<string, string>\n}\n\nexport function MdocMessage({ onAction, onSend, renderProse, classNames }: MdocMessageProps) {\n const { nodes, isStreaming, push, done, reset } = useRenderer({ registry })\n const abortRef = useRef<AbortController | null>(null)\n\n const handleAction: ActionHandler = useCallback(\n (action, params) => {\n if (action === 'continue' && onSend) {\n onSend(params?.label ?? action)\n return\n }\n onAction?.(action, params)\n },\n [onAction, onSend],\n )\n\n /** Stream a response from your API endpoint */\n const stream = useCallback(\n async (url: string, body: Record<string, unknown>) => {\n abortRef.current?.abort()\n reset()\n\n const controller = new AbortController()\n abortRef.current = controller\n\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n\n if (!res.ok || !res.body) {\n throw new Error(\\`Stream request failed: \\${res.status}\\`)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n\n while (true) {\n const { done: readerDone, value } = await reader.read()\n if (readerDone) break\n push(decoder.decode(value, { stream: true }))\n }\n\n done()\n },\n [push, done, reset],\n )\n\n return (\n <Renderer\n nodes={nodes}\n components={defaultComponents}\n onAction={handleAction}\n isStreaming={isStreaming}\n renderProse={renderProse}\n classNames={classNames}\n />\n )\n}\n\n// Export the stream helper so parent components can drive it\nexport type { MdocMessageProps }\nexport { MdocMessage as default }\n`\n}\n\nfunction envExampleTemplate(): string {\n\treturn `# mdocUI — LLM API keys\n# Copy this file to .env.local and fill in your keys.\n\n# Anthropic (Claude)\nANTHROPIC_API_KEY=\n\n# OpenAI\nOPENAI_API_KEY=\n`\n}\n\nfunction nextjsRouteTemplate(): string {\n\treturn `import { type NextRequest, NextResponse } from 'next/server'\nimport { generatePrompt } from '@mdocui/core'\nimport { allDefinitions, defaultGroups } from '@mdocui/react'\n\n// Build the system prompt once at module level\nconst systemPrompt = generatePrompt({\n components: allDefinitions,\n groups: defaultGroups,\n preamble: 'You are a helpful assistant. Use mdocUI components to create rich, interactive responses.',\n})\n\nexport async function POST(req: NextRequest) {\n const { messages, provider = 'anthropic' } = await req.json()\n\n if (provider === 'openai') {\n return streamOpenAI(messages)\n }\n\n return streamAnthropic(messages)\n}\n\n// ── Anthropic (Claude) ─────────────────────────────────────\n\nasync function streamAnthropic(messages: Array<{ role: string; content: string }>) {\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) {\n return NextResponse.json({ error: 'ANTHROPIC_API_KEY not set' }, { status: 500 })\n }\n\n const res = await fetch('https://api.anthropic.com/v1/messages', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 4096,\n system: systemPrompt,\n messages,\n stream: true,\n }),\n })\n\n if (!res.ok || !res.body) {\n const text = await res.text()\n return NextResponse.json({ error: text }, { status: res.status })\n }\n\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n async start(controller) {\n const reader = res.body!.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n\n const lines = buffer.split('\\\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6)\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data)\n if (event.type === 'content_block_delta' && event.delta?.text) {\n controller.enqueue(encoder.encode(event.delta.text))\n }\n } catch {\n // skip malformed events\n }\n }\n }\n\n controller.close()\n },\n })\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n })\n}\n\n// ── OpenAI ─────────────────────────────────────────────────\n\nasync function streamOpenAI(messages: Array<{ role: string; content: string }>) {\n const apiKey = process.env.OPENAI_API_KEY\n if (!apiKey) {\n return NextResponse.json({ error: 'OPENAI_API_KEY not set' }, { status: 500 })\n }\n\n const res = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: \\`Bearer \\${apiKey}\\`,\n },\n body: JSON.stringify({\n model: 'gpt-4o',\n messages: [{ role: 'system', content: systemPrompt }, ...messages],\n stream: true,\n }),\n })\n\n if (!res.ok || !res.body) {\n const text = await res.text()\n return NextResponse.json({ error: text }, { status: res.status })\n }\n\n const encoder = new TextEncoder()\n const stream = new ReadableStream({\n async start(controller) {\n const reader = res.body!.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n\n const lines = buffer.split('\\\\n')\n buffer = lines.pop() ?? ''\n\n for (const line of lines) {\n if (!line.startsWith('data: ')) continue\n const data = line.slice(6)\n if (data === '[DONE]') continue\n\n try {\n const event = JSON.parse(data)\n const content = event.choices?.[0]?.delta?.content\n if (content) {\n controller.enqueue(encoder.encode(content))\n }\n } catch {\n // skip malformed events\n }\n }\n }\n\n controller.close()\n },\n })\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n })\n}\n`\n}\n\nfunction configTemplate(): string {\n\treturn `import { allDefinitions, defaultGroups } from '@mdocui/react'\n\nexport default {\n components: allDefinitions,\n groups: defaultGroups,\n output: './generated/system-prompt.txt',\n preamble: 'You are a helpful assistant. Use mdocUI components to create rich, interactive responses.',\n}\n`\n}\n\n// ── Scaffolder ─────────────────────────────────────────────\n\ninterface ScaffoldedFile {\n\trelativePath: string\n\tcontent: string\n}\n\nfunction writeIfMissing(cwd: string, file: ScaffoldedFile): boolean {\n\tconst abs = path.resolve(cwd, file.relativePath)\n\tif (fs.existsSync(abs)) {\n\t\tconsole.log(` skip ${file.relativePath} (already exists)`)\n\t\treturn false\n\t}\n\tfs.mkdirSync(path.dirname(abs), { recursive: true })\n\tfs.writeFileSync(abs, file.content, 'utf-8')\n\treturn true\n}\n\nexport async function init(cwd: string) {\n\tconst project = detectFramework(cwd)\n\n\tconsole.log('')\n\tconsole.log(` Detected: ${frameworkLabel(project.framework)}`)\n\tconsole.log('')\n\n\tconst files: ScaffoldedFile[] = []\n\n\t// Always generate these\n\tfiles.push({ relativePath: 'src/lib/mdoc-registry.ts', content: registryTemplate() })\n\tfiles.push({ relativePath: 'src/components/mdoc-message.tsx', content: mdocMessageTemplate() })\n\tfiles.push({ relativePath: '.env.example', content: envExampleTemplate() })\n\tfiles.push({ relativePath: 'mdocui.config.ts', content: configTemplate() })\n\n\t// Framework-specific files\n\tif (project.framework === 'nextjs') {\n\t\tconst routeDir = project.hasSrc ? 'src/app/api/chat' : 'app/api/chat'\n\t\tfiles.push({ relativePath: `${routeDir}/route.ts`, content: nextjsRouteTemplate() })\n\t}\n\n\t// Write files\n\tconst created: string[] = []\n\tconst skipped: string[] = []\n\n\tfor (const file of files) {\n\t\tif (writeIfMissing(cwd, file)) {\n\t\t\tcreated.push(file.relativePath)\n\t\t} else {\n\t\t\tskipped.push(file.relativePath)\n\t\t}\n\t}\n\n\t// Ensure generated/ dir\n\tconst generatedDir = path.resolve(cwd, 'generated')\n\tif (!fs.existsSync(generatedDir)) {\n\t\tfs.mkdirSync(generatedDir, { recursive: true })\n\t\tfs.writeFileSync(path.resolve(generatedDir, '.gitkeep'), '', 'utf-8')\n\t\tcreated.push('generated/')\n\t}\n\n\t// Summary\n\tconsole.log('')\n\tif (created.length > 0) {\n\t\tconsole.log(' Created:')\n\t\tfor (const f of created) {\n\t\t\tconsole.log(` + ${f}`)\n\t\t}\n\t}\n\tif (skipped.length > 0) {\n\t\tconsole.log(' Skipped (already exist):')\n\t\tfor (const f of skipped) {\n\t\t\tconsole.log(` - ${f}`)\n\t\t}\n\t}\n\n\tconsole.log('')\n\tconsole.log(' Next steps:')\n\tconsole.log(' 1. Copy .env.example to .env.local and add your API keys')\n\tconsole.log(' 2. Install dependencies:')\n\tconsole.log(' npm install @mdocui/core @mdocui/react')\n\tconsole.log(' 3. Generate the system prompt:')\n\tconsole.log(' npx @mdocui/cli generate')\n\tif (project.framework === 'nextjs') {\n\t\tconsole.log(' 4. Import <MdocMessage /> in your chat page')\n\t\tconsole.log(' and call stream(\"/api/chat\", { messages })')\n\t} else {\n\t\tconsole.log(' 4. Import <MdocMessage /> in your chat component')\n\t\tconsole.log(' and wire up streaming from your backend')\n\t}\n\tconsole.log('')\n}\n\nfunction frameworkLabel(fw: Framework): string {\n\tswitch (fw) {\n\t\tcase 'nextjs':\n\t\t\treturn 'Next.js'\n\t\tcase 'vite':\n\t\t\treturn 'Vite'\n\t\tcase 'remix':\n\t\t\treturn 'Remix'\n\t\tcase 'unknown':\n\t\t\treturn 'Unknown framework (generating generic files)'\n\t}\n}\n","import http from 'node:http'\nimport type { ASTNode } from '@mdocui/core'\nimport { StreamingParser } from '@mdocui/core'\nimport { loadConfig } from '../config'\n\nexport interface PreviewOptions {\n\tport?: number\n}\n\nexport async function preview(cwd: string, options?: PreviewOptions) {\n\tconst port = options?.port ?? 4321\n\tlet knownTags: Set<string> | undefined\n\n\ttry {\n\t\tconst config = await loadConfig(cwd)\n\t\tknownTags = new Set(config.components.map((c) => c.name))\n\t\tconsole.log(`Loaded config with ${knownTags.size} components: ${[...knownTags].join(', ')}`)\n\t} catch {\n\t\tconsole.log('No config found — all tags treated as known')\n\t}\n\n\tconst server = http.createServer((req, res) => {\n\t\tif (req.method === 'POST' && req.url === '/api/parse') {\n\t\t\tlet body = ''\n\t\t\treq.on('data', (chunk: Buffer) => {\n\t\t\t\tbody += chunk.toString()\n\t\t\t})\n\t\t\treq.on('end', () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst { content } = JSON.parse(body) as { content: string }\n\t\t\t\t\tconst parser = new StreamingParser(\n\t\t\t\t\t\tknownTags ? { knownTags, dropUnknown: false } : undefined,\n\t\t\t\t\t)\n\t\t\t\t\tparser.write(content)\n\t\t\t\t\tparser.flush()\n\t\t\t\t\tconst nodes: ASTNode[] = parser.getNodes()\n\t\t\t\t\tconst meta = parser.getMeta()\n\n\t\t\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' })\n\t\t\t\t\tres.end(JSON.stringify({ nodes, meta }))\n\t\t\t\t} catch (err) {\n\t\t\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' })\n\t\t\t\t\tres.end(JSON.stringify({ error: (err as Error).message }))\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tif (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {\n\t\t\tres.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })\n\t\t\tres.end(buildHTML(knownTags))\n\t\t\treturn\n\t\t}\n\n\t\tres.writeHead(404, { 'Content-Type': 'text/plain' })\n\t\tres.end('Not found')\n\t})\n\n\tserver.listen(port, () => {\n\t\tconsole.log(`\\nmdocui preview server running at:\\n`)\n\t\tconsole.log(` http://localhost:${port}\\n`)\n\t\tconsole.log('Press Ctrl+C to stop.\\n')\n\t})\n}\n\nfunction buildHTML(knownTags?: Set<string>): string {\n\tconst tagList = knownTags ? JSON.stringify([...knownTags]) : '[]'\n\n\treturn /* html */ `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>mdocui preview</title>\n<style>\n*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n:root {\n --bg: #0a0a0a;\n --bg-surface: #141414;\n --bg-surface-2: #1e1e1e;\n --border: #2a2a2a;\n --text: #e4e4e4;\n --text-dim: #888;\n --accent: #7c6bff;\n --accent-dim: #5a4fbb;\n --error: #f06;\n --tag-bg: #1a1a2e;\n --tag-border: #2a2a4e;\n --prose-bg: #0f1a0f;\n --prose-border: #1a3a1a;\n --font-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', Menlo, monospace;\n --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\nhtml, body { height: 100%; background: var(--bg); color: var(--text); font-family: var(--font-sans); }\n\n.app {\n display: flex;\n flex-direction: column;\n height: 100vh;\n}\n\nheader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--border);\n background: var(--bg-surface);\n}\n\nheader h1 {\n font-size: 14px;\n font-weight: 600;\n letter-spacing: 0.5px;\n color: var(--accent);\n}\n\nheader .meta {\n font-size: 12px;\n color: var(--text-dim);\n}\n\n.panels {\n display: flex;\n flex: 1;\n min-height: 0;\n}\n\n.panel {\n flex: 1;\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n\n.panel + .panel {\n border-left: 1px solid var(--border);\n}\n\n.panel-header {\n padding: 8px 16px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 1px;\n color: var(--text-dim);\n background: var(--bg-surface);\n border-bottom: 1px solid var(--border);\n}\n\ntextarea {\n flex: 1;\n width: 100%;\n padding: 16px;\n background: var(--bg);\n color: var(--text);\n border: none;\n resize: none;\n font-family: var(--font-mono);\n font-size: 13px;\n line-height: 1.6;\n outline: none;\n tab-size: 2;\n}\n\ntextarea::placeholder { color: var(--text-dim); }\n\n.preview {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n background: var(--bg);\n}\n\n/* AST node rendering */\n\n.node-prose {\n background: var(--prose-bg);\n border: 1px solid var(--prose-border);\n border-radius: 6px;\n padding: 12px 16px;\n margin-bottom: 8px;\n font-size: 14px;\n line-height: 1.7;\n}\n\n.node-prose p { margin-bottom: 0.5em; }\n.node-prose p:last-child { margin-bottom: 0; }\n.node-prose strong { color: #fff; }\n.node-prose em { color: #ccc; font-style: italic; }\n.node-prose code {\n background: var(--bg-surface-2);\n padding: 2px 6px;\n border-radius: 3px;\n font-family: var(--font-mono);\n font-size: 12px;\n}\n.node-prose a { color: var(--accent); text-decoration: underline; }\n.node-prose ul, .node-prose ol { padding-left: 1.5em; margin-bottom: 0.5em; }\n.node-prose h1, .node-prose h2, .node-prose h3 {\n color: #fff;\n margin-bottom: 0.4em;\n}\n.node-prose h1 { font-size: 1.4em; }\n.node-prose h2 { font-size: 1.2em; }\n.node-prose h3 { font-size: 1.05em; }\n\n.node-component {\n background: var(--tag-bg);\n border: 1px solid var(--tag-border);\n border-radius: 6px;\n margin-bottom: 8px;\n overflow: hidden;\n}\n\n.node-component-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: rgba(124, 107, 255, 0.08);\n border-bottom: 1px solid var(--tag-border);\n}\n\n.node-component-name {\n font-family: var(--font-mono);\n font-size: 13px;\n font-weight: 600;\n color: var(--accent);\n}\n\n.node-component-badge {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 3px;\n background: var(--accent-dim);\n color: #fff;\n}\n\n.node-component-props {\n padding: 8px 12px;\n}\n\n.prop-row {\n display: flex;\n gap: 8px;\n font-size: 12px;\n font-family: var(--font-mono);\n padding: 2px 0;\n}\n\n.prop-key { color: #82aaff; }\n.prop-eq { color: var(--text-dim); }\n.prop-val { color: #c3e88d; }\n\n.node-component-children {\n padding: 8px 12px;\n border-top: 1px solid var(--tag-border);\n}\n\n.node-component-children-label {\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--text-dim);\n margin-bottom: 6px;\n}\n\n.errors {\n padding: 8px 16px;\n background: rgba(255, 0, 102, 0.08);\n border-top: 1px solid rgba(255, 0, 102, 0.2);\n}\n\n.error-item {\n font-size: 12px;\n color: var(--error);\n font-family: var(--font-mono);\n padding: 2px 0;\n}\n\n.error-item::before { content: '\\\\26A0 '; }\n\n.status-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 4px 16px;\n font-size: 11px;\n color: var(--text-dim);\n border-top: 1px solid var(--border);\n background: var(--bg-surface);\n}\n\n.empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-dim);\n font-size: 13px;\n text-align: center;\n line-height: 1.8;\n}\n</style>\n</head>\n<body>\n<div class=\"app\">\n <header>\n <h1>mdocui preview</h1>\n <span class=\"meta\" id=\"tag-count\"></span>\n </header>\n <div class=\"panels\">\n <div class=\"panel\">\n <div class=\"panel-header\">Input</div>\n <textarea id=\"editor\" spellcheck=\"false\" placeholder=\"Type mdocUI markup here...\n\nExample:\nHere is a **weather card**:\n\n{% WeatherCard city=&quot;San Francisco&quot; temp=72 unit=&quot;F&quot; /%}\n\nOr with body content:\n\n{% Alert type=&quot;info&quot; %}\nThis is an informational alert with **markdown** inside.\n{% /Alert %}\"></textarea>\n </div>\n <div class=\"panel\">\n <div class=\"panel-header\">Preview</div>\n <div class=\"preview\" id=\"preview\">\n <div class=\"empty-state\">Start typing mdocUI markup<br/>to see the live preview</div>\n </div>\n <div class=\"errors\" id=\"errors\" style=\"display:none\"></div>\n </div>\n </div>\n <div class=\"status-bar\">\n <span id=\"status\">Ready</span>\n <span id=\"node-count\"></span>\n </div>\n</div>\n\n<script>\n(function() {\n const knownTags = ${tagList};\n const editor = document.getElementById('editor');\n const preview = document.getElementById('preview');\n const errors = document.getElementById('errors');\n const status = document.getElementById('status');\n const nodeCount = document.getElementById('node-count');\n const tagCountEl = document.getElementById('tag-count');\n\n if (knownTags.length > 0) {\n tagCountEl.textContent = knownTags.length + ' registered component' + (knownTags.length === 1 ? '' : 's');\n } else {\n tagCountEl.textContent = 'no config — all tags accepted';\n }\n\n let timer = null;\n\n editor.addEventListener('input', function() {\n clearTimeout(timer);\n timer = setTimeout(parse, 150);\n });\n\n // also handle paste\n editor.addEventListener('paste', function() {\n clearTimeout(timer);\n timer = setTimeout(parse, 50);\n });\n\n async function parse() {\n const content = editor.value;\n if (!content.trim()) {\n preview.innerHTML = '<div class=\"empty-state\">Start typing mdocUI markup<br/>to see the live preview</div>';\n errors.style.display = 'none';\n status.textContent = 'Ready';\n nodeCount.textContent = '';\n return;\n }\n\n status.textContent = 'Parsing...';\n\n try {\n const res = await fetch('/api/parse', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ content }),\n });\n\n const data = await res.json();\n\n if (data.error) {\n status.textContent = 'Error: ' + data.error;\n return;\n }\n\n preview.innerHTML = renderNodes(data.nodes);\n nodeCount.textContent = data.meta.nodeCount + ' node' + (data.meta.nodeCount === 1 ? '' : 's');\n\n if (data.meta.errors && data.meta.errors.length > 0) {\n errors.style.display = 'block';\n errors.innerHTML = data.meta.errors\n .map(function(e) { return '<div class=\"error-item\">' + escapeHtml(e.message) + '</div>'; })\n .join('');\n status.textContent = 'Parsed with ' + data.meta.errors.length + ' warning(s)';\n } else {\n errors.style.display = 'none';\n status.textContent = 'OK';\n }\n } catch (err) {\n status.textContent = 'Fetch error: ' + err.message;\n }\n }\n\n function renderNodes(nodes) {\n if (!nodes || nodes.length === 0) return '<div class=\"empty-state\">No nodes parsed</div>';\n return nodes.map(renderNode).join('');\n }\n\n function renderNode(node) {\n if (node.type === 'prose') {\n return '<div class=\"node-prose\">' + markdownToHtml(node.content) + '</div>';\n }\n\n if (node.type === 'component') {\n var html = '<div class=\"node-component\">';\n html += '<div class=\"node-component-header\">';\n html += '<span class=\"node-component-name\">' + escapeHtml(node.name) + '</span>';\n if (node.selfClosing) {\n html += '<span class=\"node-component-badge\">self-closing</span>';\n }\n html += '</div>';\n\n var propKeys = Object.keys(node.props || {});\n if (propKeys.length > 0) {\n html += '<div class=\"node-component-props\">';\n propKeys.forEach(function(key) {\n var val = node.props[key];\n var display = typeof val === 'string' ? '\"' + escapeHtml(val) + '\"' : String(val);\n html += '<div class=\"prop-row\">';\n html += '<span class=\"prop-key\">' + escapeHtml(key) + '</span>';\n html += '<span class=\"prop-eq\">=</span>';\n html += '<span class=\"prop-val\">' + display + '</span>';\n html += '</div>';\n });\n html += '</div>';\n }\n\n if (node.children && node.children.length > 0) {\n html += '<div class=\"node-component-children\">';\n html += '<div class=\"node-component-children-label\">Children</div>';\n html += renderNodes(node.children);\n html += '</div>';\n }\n\n html += '</div>';\n return html;\n }\n\n return '';\n }\n\n /** Minimal markdown-to-HTML for prose content */\n function markdownToHtml(text) {\n var html = escapeHtml(text);\n\n // headings\n html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');\n html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');\n html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // bold + italic\n html = html.replace(/\\\\*\\\\*\\\\*(.+?)\\\\*\\\\*\\\\*/g, '<strong><em>$1</em></strong>');\n html = html.replace(/\\\\*\\\\*(.+?)\\\\*\\\\*/g, '<strong>$1</strong>');\n html = html.replace(/\\\\*(.+?)\\\\*/g, '<em>$1</em>');\n\n // inline code\n html = html.replace(/\\`([^\\`]+)\\`/g, '<code>$1</code>');\n\n // links\n html = html.replace(/\\\\[([^\\\\]]+)\\\\]\\\\(([^)]+)\\\\)/g, '<a href=\"$2\" target=\"_blank\">$1</a>');\n\n // unordered lists\n html = html.replace(/^- (.+)$/gm, '<li>$1</li>');\n html = html.replace(/(<li>.*<\\\\/li>\\\\n?)+/g, function(m) { return '<ul>' + m + '</ul>'; });\n\n // paragraphs: split on double newlines\n html = html.split(/\\\\n\\\\n+/).map(function(block) {\n block = block.trim();\n if (!block) return '';\n if (/^<(h[1-3]|ul|ol|li)/.test(block)) return block;\n return '<p>' + block.replace(/\\\\n/g, '<br/>') + '</p>';\n }).join('');\n\n return html;\n }\n\n function escapeHtml(str) {\n return String(str)\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n }\n})();\n</script>\n</body>\n</html>`\n}\n","import { generate } from './commands/generate'\nimport { init } from './commands/init'\nimport { preview } from './commands/preview'\n\nconst args = process.argv.slice(2)\nconst command = args[0]\nconst cwd = process.cwd()\n\nfunction parseFlag(flag: string): string | undefined {\n\tconst idx = args.indexOf(flag)\n\tif (idx !== -1 && idx + 1 < args.length) return args[idx + 1]\n\tconst eq = args.find((a) => a.startsWith(`${flag}=`))\n\tif (eq) return eq.split('=')[1]\n\treturn undefined\n}\n\nasync function main() {\n\tswitch (command) {\n\t\tcase 'generate':\n\t\t\tawait generate(cwd)\n\t\t\tbreak\n\t\tcase 'preview': {\n\t\t\tconst portStr = parseFlag('--port')\n\t\t\tawait preview(cwd, portStr ? { port: Number(portStr) } : undefined)\n\t\t\tbreak\n\t\t}\n\t\tcase 'init':\n\t\t\tawait init(cwd)\n\t\t\tbreak\n\t\tcase '--help':\n\t\tcase '-h':\n\t\tcase undefined:\n\t\t\tprintHelp()\n\t\t\tbreak\n\t\tdefault:\n\t\t\tconsole.error(`Unknown command: ${command}`)\n\t\t\tprintHelp()\n\t\t\tprocess.exit(1)\n\t}\n}\n\nfunction printHelp() {\n\tconsole.log(`\nmdocui — CLI for mdocUI generative UI library\n\nCommands:\n init Scaffold mdocUI integration (registry, components, API route)\n generate Generate system prompt from component registry\n preview Start local dev server with live mdocUI markup preview\n\nOptions:\n --port <n> Port for preview server (default: 4321)\n\nUsage:\n npx @mdocui/cli init\n npx @mdocui/cli generate\n npx @mdocui/cli preview\n npx @mdocui/cli preview --port 3000\n`)\n}\n\nmain().catch((err) => {\n\tconsole.error(err.message)\n\tprocess.exit(1)\n})\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAsRG,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqK7B;;;AC3fA,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,IAAM,UAAU,KAAK,CAAC;AACtB,IAAM,MAAM,QAAQ,IAAI;AAExB,SAAS,UAAU,MAAkC;AACpD,QAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,QAAQ,MAAM,MAAM,IAAI,KAAK,OAAQ,QAAO,KAAK,MAAM,CAAC;AAC5D,QAAM,KAAK,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,CAAC;AACpD,MAAI,GAAI,QAAO,GAAG,MAAM,GAAG,EAAE,CAAC;AAC9B,SAAO;AACR;AAEA,eAAe,OAAO;AACrB,UAAQ,SAAS;AAAA,IAChB,KAAK;AACJ,YAAM,SAAS,GAAG;AAClB;AAAA,IACD,KAAK,WAAW;AACf,YAAM,UAAU,UAAU,QAAQ;AAClC,YAAM,QAAQ,KAAK,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE,IAAI,MAAS;AAClE;AAAA,IACD;AAAA,IACA,KAAK;AACJ,YAAM,KAAK,GAAG;AACd;AAAA,IACD,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,gBAAU;AACV;AAAA,IACD;AACC,cAAQ,MAAM,oBAAoB,OAAO,EAAE;AAC3C,gBAAU;AACV,cAAQ,KAAK,CAAC;AAAA,EAChB;AACD;AAEA,SAAS,YAAY;AACpB,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAgBZ;AACD;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACrB,UAAQ,MAAM,IAAI,OAAO;AACzB,UAAQ,KAAK,CAAC;AACf,CAAC;","names":["fs","path","cwd","cwd","path","fs","fs","path","cwd","cwd"]}
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "@mdocui/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for mdocUI — scaffold, generate system prompts, preview",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "mdocui": "./dist/index.js"
8
8
  },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
9
15
  "main": "./dist/index.js",
10
16
  "types": "./dist/index.d.ts",
11
17
  "files": [
@@ -13,13 +19,14 @@
13
19
  ],
14
20
  "dependencies": {
15
21
  "zod": "^4.3.0",
16
- "@mdocui/core": "0.5.0"
22
+ "@mdocui/core": "0.6.0"
17
23
  },
18
24
  "devDependencies": {
19
25
  "@types/node": "^25.5.0",
20
26
  "tsup": "^8.5.1",
21
27
  "vitest": "^4.1.0"
22
28
  },
29
+ "homepage": "https://mdocui.github.io",
23
30
  "license": "MIT",
24
31
  "repository": {
25
32
  "type": "git",