@avodado/cli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js ADDED
@@ -0,0 +1,845 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import pc3 from 'picocolors';
4
+ import { useState } from 'react';
5
+ import { render, Text, useApp, Box } from 'ink';
6
+ import { existsSync, statSync, readFileSync } from 'fs';
7
+ import { resolve, join, dirname, parse, relative, basename, extname, sep } from 'path';
8
+ import { parse as parse$1 } from 'yaml';
9
+ import { createJiti } from 'jiti';
10
+ import { BLOCK_TYPES, parseDocument, validateDocument, resolveRefs, helpUrl } from '@avodado/core';
11
+ import { mkdir, cp, writeFile, readFile } from 'fs/promises';
12
+ import fg from 'fast-glob';
13
+ import { renderDocument } from '@avodado/render';
14
+ import { toHtml, toPdf } from '@avodado/export';
15
+ import { createHash } from 'crypto';
16
+ import { tmpdir } from 'os';
17
+ import open from 'open';
18
+ import { fileURLToPath } from 'url';
19
+ import { parseOpenApi, openapiToMarkdown } from '@avodado/sync';
20
+ import SelectInput from 'ink-select-input';
21
+ import { jsx, jsxs } from 'react/jsx-runtime';
22
+
23
+ var DEFAULTS = { docsDir: "docs", outDir: "dist" };
24
+ var CONFIG_FILES = [
25
+ "avodado.config.ts",
26
+ "avodado.config.js",
27
+ "avodado.config.mjs",
28
+ "avodado.config.json",
29
+ "avodado.config.yml",
30
+ "avodado.config.yaml"
31
+ ];
32
+ async function loadConfig(cwd) {
33
+ for (const name of CONFIG_FILES) {
34
+ const path = resolve(cwd, name);
35
+ if (!existsSync(path)) continue;
36
+ const raw = await readConfig(path);
37
+ return mergeWithDefaults(raw);
38
+ }
39
+ return DEFAULTS;
40
+ }
41
+ async function readConfig(path) {
42
+ if (path.endsWith(".json")) {
43
+ return JSON.parse(readFileSync(path, "utf8"));
44
+ }
45
+ if (path.endsWith(".yml") || path.endsWith(".yaml")) {
46
+ return parse$1(readFileSync(path, "utf8"));
47
+ }
48
+ const jiti = createJiti(import.meta.url);
49
+ const mod = await jiti.import(path);
50
+ return mod.default ?? mod;
51
+ }
52
+ function mergeWithDefaults(raw) {
53
+ if (raw === null || typeof raw !== "object") return DEFAULTS;
54
+ const r = raw;
55
+ return {
56
+ docsDir: typeof r.docsDir === "string" ? r.docsDir : DEFAULTS.docsDir,
57
+ outDir: typeof r.outDir === "string" ? r.outDir : DEFAULTS.outDir
58
+ };
59
+ }
60
+ async function loadDocs(patterns, cwd, docsRoot) {
61
+ const matches = await fg(patterns, {
62
+ cwd,
63
+ absolute: true,
64
+ onlyFiles: true,
65
+ dot: false
66
+ });
67
+ const docsRootAbs = resolve(cwd, docsRoot);
68
+ const files = await Promise.all(
69
+ matches.map(async (absolute) => {
70
+ const file = relative(cwd, absolute);
71
+ const slug = deriveSlug(absolute, docsRootAbs);
72
+ const source = await readFile(absolute, "utf8");
73
+ return { absolute, file, slug, source };
74
+ })
75
+ );
76
+ files.sort((a, b) => a.file.localeCompare(b.file));
77
+ return files;
78
+ }
79
+ function deriveSlug(absolute, docsRootAbs) {
80
+ const rel = relative(docsRootAbs, absolute);
81
+ const inside = !rel.startsWith("..") && !rel.startsWith(sep) && rel.length > 0;
82
+ const path = inside ? rel : basename(absolute);
83
+ return path.replace(/\\/g, "/").replace(/\.md$/i, "");
84
+ }
85
+
86
+ // src/commands/check.tsx
87
+ async function runCheck(opts) {
88
+ const docs = await loadDocs(opts.patterns, opts.cwd, opts.docsRoot);
89
+ const parsed = docs.map((d) => ({
90
+ doc: parseDocument(d.source, d.slug),
91
+ file: d.file
92
+ }));
93
+ const diagnostics = [];
94
+ for (const { doc, file } of parsed) {
95
+ diagnostics.push(...validateDocument(doc, file));
96
+ }
97
+ const resolved = resolveRefs(parsed);
98
+ diagnostics.push(...resolved.diagnostics);
99
+ diagnostics.sort((a, b) => {
100
+ const f = a.file.localeCompare(b.file);
101
+ if (f !== 0) return f;
102
+ return (a.line ?? 0) - (b.line ?? 0);
103
+ });
104
+ const exitCode = diagnostics.some((d) => d.level === "error") ? 1 : 0;
105
+ const sources = /* @__PURE__ */ new Map();
106
+ for (const d of docs) sources.set(d.file, d.source.split(/\r\n|\r|\n/));
107
+ return {
108
+ diagnostics,
109
+ files: docs.map((d) => d.file),
110
+ sources,
111
+ exitCode
112
+ };
113
+ }
114
+ var BASE_THEMES = /* @__PURE__ */ new Set(["navy", "teal", "plum", "slate", "dark", "soft"]);
115
+ var COLOR_TO_VAR = {
116
+ primary: "--navy",
117
+ // headings, primary nodes, links, section numbers
118
+ secondary: "--blue",
119
+ // secondary accents, CDN/consumer nodes
120
+ accent: "--highlight",
121
+ // highlight pills, FK keys, "current" markers
122
+ positive: "--positive",
123
+ // success / service nodes / done
124
+ negative: "--negative",
125
+ // errors / forbidden edges / danger
126
+ purple: "--purple",
127
+ // data / context / provider nodes
128
+ teal: "--teal",
129
+ // queues / topics / caches
130
+ ink: "--charcoal",
131
+ // body text
132
+ muted: "--gray",
133
+ // captions, dim text, external nodes
134
+ rule: "--rule",
135
+ // hairlines, borders, dividers
136
+ paper: "--white"
137
+ // surfaces / card fills
138
+ };
139
+ var FONT_TO_VAR = {
140
+ display: "--font-display",
141
+ // titles / headings
142
+ body: "--font-body",
143
+ // paragraphs / labels
144
+ mono: "--font-mono"
145
+ // code / technical labels
146
+ };
147
+ var THEME_FILES = ["avodado.theme.json", "avodado.theme.jsonc"];
148
+ function loadTheme(cwd) {
149
+ for (const name of THEME_FILES) {
150
+ const path = resolve(cwd, name);
151
+ if (!existsSync(path)) continue;
152
+ let raw;
153
+ try {
154
+ raw = JSON.parse(stripComments(readFileSync(path, "utf8")));
155
+ } catch {
156
+ return {};
157
+ }
158
+ const file = raw !== null && typeof raw === "object" ? raw : {};
159
+ const out = {};
160
+ if (typeof file.theme === "string" && BASE_THEMES.has(file.theme)) out.theme = file.theme;
161
+ const vars = toVars(raw);
162
+ if (vars !== void 0) out.themeVars = vars;
163
+ return out;
164
+ }
165
+ return {};
166
+ }
167
+ function toVars(raw) {
168
+ if (raw === null || typeof raw !== "object") return void 0;
169
+ const file = raw;
170
+ const vars = {};
171
+ if (file.colors !== void 0) {
172
+ for (const key of Object.keys(file.colors)) {
173
+ const cssVar = COLOR_TO_VAR[key.toLowerCase()];
174
+ const value = file.colors[key];
175
+ if (cssVar !== void 0 && typeof value === "string") vars[cssVar] = value;
176
+ }
177
+ }
178
+ if (file.fonts !== void 0) {
179
+ for (const key of Object.keys(file.fonts)) {
180
+ const cssVar = FONT_TO_VAR[key.toLowerCase()];
181
+ const value = file.fonts[key];
182
+ if (cssVar !== void 0 && typeof value === "string") vars[cssVar] = value;
183
+ }
184
+ }
185
+ return Object.keys(vars).length > 0 ? vars : void 0;
186
+ }
187
+ function stripComments(src) {
188
+ return src.replace(/^\s*\/\/.*$/gm, "");
189
+ }
190
+
191
+ // src/commands/render.ts
192
+ async function runRender(opts) {
193
+ const inputAbs = resolve(opts.cwd, opts.input);
194
+ const source = await readFile(inputAbs, "utf8");
195
+ const slug = parse(inputAbs).name;
196
+ const doc = parseDocument(source, slug);
197
+ const { theme, themeVars } = loadTheme(opts.cwd);
198
+ const html = renderDocument(doc, {
199
+ ...theme !== void 0 ? { theme } : {},
200
+ ...themeVars !== void 0 ? { themeVars } : {}
201
+ });
202
+ const outputAbs = opts.output !== void 0 ? resolve(opts.cwd, opts.output) : inputAbs.replace(/\.md$/i, ".html");
203
+ await mkdir(dirname(outputAbs), { recursive: true });
204
+ await writeFile(outputAbs, html, "utf8");
205
+ return { input: opts.input, output: outputAbs, bytes: html.length };
206
+ }
207
+ async function runExport(opts) {
208
+ const docs = await loadDocs(opts.patterns, opts.cwd, opts.docsRoot);
209
+ const outRoot = resolve(opts.cwd, opts.outDir);
210
+ await mkdir(outRoot, { recursive: true });
211
+ const items = [];
212
+ for (const d of docs) {
213
+ const doc = parseDocument(d.source, d.slug);
214
+ const outputs = [];
215
+ for (const fmt of opts.formats) {
216
+ const outPath = join(outRoot, `${d.slug}.${fmt}`);
217
+ await mkdir(resolve(outPath, ".."), { recursive: true });
218
+ if (fmt === "html") {
219
+ const html = toHtml(doc);
220
+ await writeFile(outPath, html, "utf8");
221
+ outputs.push({ format: fmt, path: outPath, bytes: html.length });
222
+ } else {
223
+ const pdf = await toPdf(doc);
224
+ await writeFile(outPath, pdf);
225
+ outputs.push({ format: fmt, path: outPath, bytes: pdf.byteLength });
226
+ }
227
+ }
228
+ items.push({ file: d.file, outputs });
229
+ }
230
+ return { items };
231
+ }
232
+ async function runPreview(opts) {
233
+ const inputAbs = resolve(opts.cwd, opts.input);
234
+ const source = await readFile(inputAbs, "utf8");
235
+ const slug = parse(inputAbs).name;
236
+ const doc = parseDocument(source, slug);
237
+ const html = renderDocument(doc);
238
+ const hash = createHash("sha1").update(inputAbs).digest("hex").slice(0, 8);
239
+ const dir = join(tmpdir(), "avodado-preview");
240
+ await mkdir(dir, { recursive: true });
241
+ const out = join(dir, `${slug}-${hash}.html`);
242
+ await writeFile(out, html, "utf8");
243
+ await open(out);
244
+ return { file: out };
245
+ }
246
+ function templatesDir() {
247
+ let dir = dirname(fileURLToPath(import.meta.url));
248
+ for (let i = 0; i < 6; i++) {
249
+ const candidate = join(dir, "templates");
250
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) return candidate;
251
+ const parent = dirname(dir);
252
+ if (parent === dir) break;
253
+ dir = parent;
254
+ }
255
+ throw new Error(`Could not locate avodado/cli templates directory near ${import.meta.url}`);
256
+ }
257
+ var FILES = [
258
+ "avodado.config.json",
259
+ "docs/getting-started.md",
260
+ "CLAUDE.md",
261
+ ".cursor/rules/avodado.mdc",
262
+ ".avodado/skill/SKILL.md"
263
+ ];
264
+ async function runInit(opts) {
265
+ const srcRoot = templatesDir();
266
+ const created = [];
267
+ const skipped = [];
268
+ for (const rel of FILES) {
269
+ const src = resolve(srcRoot, rel);
270
+ const dst = join(opts.cwd, rel);
271
+ if (existsSync(dst) && opts.force !== true) {
272
+ skipped.push(rel);
273
+ continue;
274
+ }
275
+ await mkdir(dirname(dst), { recursive: true });
276
+ await cp(src, dst);
277
+ created.push(rel);
278
+ }
279
+ return { created, skipped };
280
+ }
281
+ function slugFromPath(path) {
282
+ const base = basename(path, extname(path));
283
+ return base.length > 0 ? base : "api";
284
+ }
285
+ async function runSyncOpenApi(opts) {
286
+ if (opts.out === void 0 && opts.check === void 0) {
287
+ return { exitCode: 2, message: "avo sync openapi: must specify --out <path> or --check <path>" };
288
+ }
289
+ if (opts.out !== void 0 && opts.check !== void 0) {
290
+ return { exitCode: 2, message: "avo sync openapi: --out and --check are mutually exclusive" };
291
+ }
292
+ const specAbs = resolve(opts.cwd, opts.spec);
293
+ if (!existsSync(specAbs)) {
294
+ return { exitCode: 2, message: `Spec not found: ${specAbs}` };
295
+ }
296
+ const source = await readFile(specAbs, "utf8");
297
+ let spec;
298
+ try {
299
+ spec = parseOpenApi(source);
300
+ } catch (err) {
301
+ return { exitCode: 1, message: `Failed to parse spec: ${err.message}` };
302
+ }
303
+ const targetPath = opts.out ?? opts.check;
304
+ if (targetPath === void 0) {
305
+ return { exitCode: 2, message: "no target path" };
306
+ }
307
+ const slug = opts.slug ?? slugFromPath(targetPath);
308
+ const generated = openapiToMarkdown(spec, { slug });
309
+ if (opts.out !== void 0) {
310
+ const outAbs = resolve(opts.cwd, opts.out);
311
+ await mkdir(dirname(outAbs), { recursive: true });
312
+ await writeFile(outAbs, generated, "utf8");
313
+ return {
314
+ exitCode: 0,
315
+ message: `Wrote ${outAbs} (${generated.length} bytes)`
316
+ };
317
+ }
318
+ const checkAbs = resolve(opts.cwd, opts.check ?? "");
319
+ if (!existsSync(checkAbs)) {
320
+ return {
321
+ exitCode: 1,
322
+ message: `Drift: ${checkAbs} does not exist. Run with --out ${opts.check} to generate it.`
323
+ };
324
+ }
325
+ const existing = await readFile(checkAbs, "utf8");
326
+ if (existing === generated) {
327
+ return {
328
+ exitCode: 0,
329
+ message: `OK: ${checkAbs} matches ${specAbs} (${generated.length} bytes)`
330
+ };
331
+ }
332
+ return {
333
+ exitCode: 1,
334
+ message: `Drift: ${checkAbs} differs from what ${specAbs} would generate.`,
335
+ diff: simpleDiff(existing, generated)
336
+ };
337
+ }
338
+ function simpleDiff(a, b) {
339
+ const aLines = a.split("\n");
340
+ const bLines = b.split("\n");
341
+ const out = [];
342
+ const max = Math.max(aLines.length, bLines.length);
343
+ for (let i = 0; i < max; i++) {
344
+ const av = aLines[i];
345
+ const bv = bLines[i];
346
+ if (av === bv) continue;
347
+ if (av !== void 0) out.push(`- ${av}`);
348
+ if (bv !== void 0) out.push(`+ ${bv}`);
349
+ if (out.length >= 40) {
350
+ out.push(`\u2026 (truncated; ${max - i - 1} more lines)`);
351
+ break;
352
+ }
353
+ }
354
+ return out.join("\n");
355
+ }
356
+ var TEMPLATES = {
357
+ meta: "```meta\ntitle: New document\nsubtitle: One-line description.\ntag: DRAFT\n```\n",
358
+ callout: "```callout\ntone: note\ntitle: Heads up\nbody: A short note that the reader should not miss.\n```\n",
359
+ table: "```table\ncolumns: [Field, Description]\nrows:\n - [name, Display name]\n - [id, Stable identifier]\n```\n",
360
+ sequence: "```sequence\nid: seq-example\nactors:\n - { id: Client, name: Client }\n - { id: Server, name: Server }\nmessages:\n - { from: Client, to: Server, label: request, kind: sync }\n - { from: Server, to: Client, label: response, kind: response }\n```\n",
361
+ erd: "```erd\nid: erd-example\nentities:\n - name: orders\n columns:\n - { name: id, type: uuid, pk: true }\n - { name: user_id, type: uuid, fk: true }\nrelations: []\n```\n",
362
+ userstory: "```userstory\nid: US-001\nrole: user\nwant: do the thing\nsoThat: I get the outcome\ncriteria:\n - { given: a precondition, when: I act, then: the outcome }\n```\n",
363
+ timeline: "```timeline\nitems:\n - label: Phase 1\n date: now\n status: current\n desc: What is happening now\n - label: Phase 2\n date: next\n status: next\n desc: What is next\n```\n",
364
+ kanban: "```kanban\ncolumns:\n - label: Now\n cards:\n - { title: Current task }\n - label: Next\n cards:\n - { title: Upcoming task }\n - label: Later\n cards:\n - { title: Eventually }\n```\n",
365
+ tracker: "```tracker\nitems:\n - { task: First task, status: doing, priority: high }\n - { task: Second task, status: todo, priority: med }\n```\n",
366
+ prose: "```prose\ntitle: Overview\nblocks:\n - { type: h, text: Background }\n - { type: p, text: A paragraph explaining the context. }\n - { type: ul, items: [Idea one, Idea two, Idea three] }\n```\n",
367
+ glossary: "```glossary\nterms:\n - { term: Idempotency, def: Doing a thing twice has the same effect as doing it once. }\n - { term: SLO, def: Service-level objective the team commits to. }\n```\n",
368
+ proscons: "```proscons\ntitle: Synchronous vs async\nprosLabel: Synchronous\nconsLabel: Asynchronous\npros:\n - Easy to reason about\n - One transaction\ncons:\n - Latency-bound\n - Single point of failure\n```\n",
369
+ cvt: "```cvt\ntitle: Migration plan\ncurrent:\n label: Today\n items: [Single monolith, Shared database, Manual deploys]\ntarget:\n label: Target\n items: [Modular services, Per-service stores, Automated deploys]\nnote: Migrate one service per quarter.\n```\n",
370
+ stats: '```stats\ntitle: This quarter\nstats:\n - { value: 12.4k, label: Active users, delta: "+18%", trend: up }\n - { value: 99.95%, label: Uptime, delta: "0", trend: flat }\n - { value: 142ms, label: p95 latency, delta: "-22ms", trend: up }\n```\n',
371
+ code: "```code\ntitle: Reference\nblocks:\n - title: index.ts\n lang: TypeScript\n code: |\n export function add(a: number, b: number): number {\n return a + b;\n }\n```\n",
372
+ agenda: '```agenda\nitems:\n - { time: "09:00", duration: 30m, title: Intros, owner: Host }\n - { time: "09:30", duration: 45m, title: Status updates, desc: Each team for 5 min }\n - { time: "10:15", duration: 15m, title: Wrap-up }\n```\n',
373
+ tree: "```tree\nnodes:\n - { id: src, label: src }\n - { id: components, parent: src, label: components }\n - { id: hooks, parent: src, label: hooks }\n - { id: index, parent: src, label: index.ts, note: entry point }\n```\n",
374
+ pyramid: "```pyramid\nlevels:\n - { label: Vision, desc: Long-term direction }\n - { label: Strategy, desc: How we get there }\n - { label: Tactics, desc: This quarter }\n - { label: Tasks, desc: This week }\n```\n",
375
+ funnel: "```funnel\nstages:\n - { label: Visited, value: 10000 }\n - { label: Signed up, value: 2400 }\n - { label: Activated, value: 1100 }\n - { label: Paying, value: 320 }\n```\n",
376
+ flow: '```flow\ntitle: Decision flow\nnodes:\n - { id: start, col: 1, row: 1, kind: start, label: Start }\n - { id: check, col: 2, row: 1, kind: decision, label: Is valid? }\n - { id: yes, col: 3, row: 1, kind: process, label: Process }\n - { id: no, col: 2, row: 2, kind: end, label: Reject }\n - { id: done, col: 3, row: 2, kind: end, label: Done }\nedges:\n - { from: start, to: check }\n - { from: check, to: yes, label: "yes" }\n - { from: check, to: no, label: "no", kind: error }\n - { from: yes, to: done }\n```\n',
377
+ state: "```state\ntitle: Order lifecycle\nstates:\n - { id: s0, col: 1, row: 1, kind: start }\n - { id: pending, col: 2, row: 1, kind: wait, name: PENDING }\n - { id: confirmed, col: 3, row: 1, kind: active, name: CONFIRMED }\n - { id: end, col: 4, row: 1, kind: terminal }\ntransitions:\n - { from: s0, to: pending, event: create }\n - { from: pending, to: confirmed, event: pay }\n - { from: confirmed, to: end, event: ship }\n```\n",
378
+ dfd: "```dfd\ntitle: Data flow\nnodes:\n - { id: ext, col: 1, row: 1, kind: external, name: Client }\n - { id: proc, col: 2, row: 1, kind: process, name: Process, num: 1 }\n - { id: store, col: 3, row: 1, kind: store, name: Orders }\nedges:\n - { from: ext, to: proc, label: request }\n - { from: proc, to: store, label: write }\n```\n",
379
+ journey: "```journey\ntitle: Onboarding journey\nstages:\n - { label: Discover }\n - { label: Sign up }\n - { label: Activate }\n - { label: Pay }\nrows:\n - { label: Touchpoint, cells: [Landing, Form, Email, Checkout] }\n - { label: Friction, cells: [Low, Med, Low, Med] }\nemotion: [0.7, 0.4, 0.6, 0.8]\n```\n",
380
+ gantt: "```gantt\ntitle: Roadmap\nperiods: [Q1, Q2, Q3, Q4]\ntasks:\n - { label: Discovery, start: 0, span: 1, kind: done }\n - { label: Build, start: 1, span: 2, kind: active }\n - { label: Ship, start: 3, span: 1 }\n - { label: GA, start: 3, span: 1, kind: milestone }\n```\n",
381
+ graph: "```graph\ntitle: Dependency graph\nnodes:\n - { id: a, col: 1, row: 1, label: Module A, group: 0 }\n - { id: b, col: 2, row: 1, label: Module B, group: 1 }\n - { id: c, col: 3, row: 1, label: Module C, group: 2 }\n - { id: d, col: 2, row: 2, label: Shared, group: 3 }\nedges:\n - { from: a, to: b }\n - { from: b, to: c }\n - { from: a, to: d }\n - { from: b, to: d, dir: undirected }\n```\n",
382
+ quadrant: "```quadrant\ntitle: Effort vs impact\nxAxis: { label: Effort, low: Low, high: High }\nyAxis: { label: Impact, low: Low, high: High }\nitems:\n - { x: 0.2, y: 0.8, label: Quick win }\n - { x: 0.8, y: 0.8, label: Big bet }\n - { x: 0.2, y: 0.2, label: Fill-in }\n - { x: 0.8, y: 0.2, label: Thankless }\n```\n",
383
+ swimlane: "```swimlane\ntitle: Cross-functional flow\nlanes:\n - { label: Customer }\n - { label: Sales }\n - { label: Ops }\nsteps:\n - { id: req, col: 1, lane: 0, kind: start, label: Submit request }\n - { id: qual, col: 2, lane: 1, kind: decision, label: Qualify }\n - { id: fulfill, col: 3, lane: 2, label: Fulfill }\n - { id: done, col: 4, lane: 0, kind: end, label: Receive }\nlinks:\n - { from: req, to: qual }\n - { from: qual, to: fulfill }\n - { from: fulfill, to: done }\n```\n",
384
+ c4: "```c4\ntitle: System context\nlevel: context\nnodes:\n - { id: user, col: 1, row: 1, kind: person, name: Shopper, desc: A customer placing an order. }\n - { id: app, col: 2, row: 1, kind: system, name: ShopCo, desc: The retail platform. }\n - { id: pay, col: 3, row: 1, kind: external, name: Payment GW, desc: Stripe authorisation. }\nedges:\n - { from: user, to: app, label: places order }\n - { from: app, to: pay, label: authorises }\n```\n",
385
+ uml: '```uml\ntitle: Class model\nclasses:\n - { id: order, col: 1, row: 1, name: Order, attrs: ["id: UUID", "status: Status", "total: Money"], methods: ["place()", "cancel()"] }\n - { id: item, col: 2, row: 1, name: OrderItem, attrs: ["id: UUID", "sku: String", "qty: int"] }\n - { id: status, col: 1, row: 2, name: Status, stereotype: enumeration, attrs: ["PENDING", "CONFIRMED", "CANCELLED"] }\nrels:\n - { from: order, to: item, kind: composition }\n - { from: order, to: status, kind: association, label: has }\n```\n',
386
+ mece: "```mece\ntitle: Why are conversions down?\nnodes:\n - { id: root, label: Lower conversion }\n - { id: traffic, parent: root, label: Traffic }\n - { id: friction, parent: root, label: Friction }\n - { id: t1, parent: traffic, label: Lower quality, note: paid ads }\n - { id: t2, parent: traffic, label: Wrong audience }\n - { id: f1, parent: friction, label: Slow checkout }\n - { id: f2, parent: friction, label: Mobile bugs }\n```\n",
387
+ frontend: "```frontend\ntitle: React component tree\nnodes:\n - { id: app, kind: root, name: App }\n - { id: layout, parent: app, kind: layout, name: Layout }\n - { id: home, parent: layout, kind: page, name: Home }\n - { id: orders, parent: layout, kind: page, name: Orders }\n - { id: card, parent: orders, kind: component, name: OrderCard }\n - { id: useOrder, parent: orders, kind: hook, name: useOrder }\n - { id: store, parent: app, kind: store, name: cart, note: Zustand }\n```\n",
388
+ cluster: "```cluster\ntitle: Production cluster\nclusters:\n - { id: api, label: api namespace, kind: namespace }\n - { id: data, label: data namespace, kind: namespace }\nservices:\n - { id: web, cluster: api, label: web, kind: service, tech: Next.js, replicas: 3 }\n - { id: orders, cluster: api, label: orders, kind: service, tech: Go, replicas: 4 }\n - { id: pg, cluster: data, label: postgres, kind: store, tech: Postgres 16, replicas: 1 }\n - { id: redis, cluster: data, label: redis, kind: cache, tech: Redis 7, replicas: 2 }\nedges:\n - { from: web, to: orders }\n - { from: orders, to: pg }\n - { from: orders, to: redis }\n```\n",
389
+ block: '```block\ntitle: System architecture\ngroups:\n - { col: 1, row: 1, cols: 1, rows: 2, label: Edge, color: "#0e54a1" }\n - { col: 2, row: 1, cols: 2, rows: 2, label: Services, color: "#1f9747" }\nnodes:\n - { id: cdn, col: 1, row: 1, kind: cdn, name: CDN, tech: Cloudflare }\n - { id: gw, col: 1, row: 2, kind: gateway, name: Gateway, tech: Envoy }\n - { id: api, col: 2, row: 1, kind: service, name: API, tech: Go }\n - { id: worker, col: 3, row: 1, kind: service, name: Worker, tech: Go }\n - { id: pg, col: 2, row: 2, kind: store, name: Postgres, tech: "16" }\n - { id: q, col: 3, row: 2, kind: queue, name: Events, tech: NATS }\nedges:\n - { from: cdn, to: gw }\n - { from: gw, to: api }\n - { from: api, to: pg }\n - { from: api, to: q }\n - { from: q, to: worker, kind: dashed }\n```\n',
390
+ infra: "```infra\ntitle: AWS topology\nsystemLabel: ShopCo \xB7 us-east-1\nlayers:\n - { label: Edge }\n - { label: Compute }\n - { label: Data }\nnodes:\n - { id: cf, layer: 0, kind: cdn, name: CloudFront, tech: CDN }\n - { id: alb, layer: 0, kind: gateway, name: ALB, tech: Application LB }\n - { id: api, layer: 1, kind: service, name: API, tech: ECS Fargate }\n - { id: worker, layer: 1, kind: service, name: Worker, tech: ECS Fargate }\n - { id: pg, layer: 2, kind: store, name: orders-db, tech: RDS Postgres }\n - { id: cache, layer: 2, kind: cache, name: cache, tech: ElastiCache }\nedges:\n - { from: cf, to: alb }\n - { from: alb, to: api }\n - { from: api, to: pg }\n - { from: api, to: cache }\n - { from: api, to: worker, kind: dashed }\n```\n",
391
+ event: "```event\ntitle: Pub/sub choreography\nnodes:\n - { id: orders, col: 1, row: 1, kind: producer, name: orders }\n - { id: bus, col: 2, row: 1, kind: topic, name: order.events }\n - { id: ship, col: 3, row: 1, kind: consumer, name: shipping }\n - { id: bill, col: 3, row: 2, kind: consumer, name: billing }\nedges:\n - { from: orders, to: bus }\n - { from: bus, to: ship }\n - { from: bus, to: bill }\n```\n",
392
+ ddd: "```ddd\ntitle: Bounded contexts\nnodes:\n - { id: cat, col: 1, row: 1, kind: context, name: Catalog }\n - { id: order, col: 2, row: 1, kind: context, name: Orders }\n - { id: pay, col: 3, row: 1, kind: context, name: Payments }\n - { id: ship, col: 2, row: 2, kind: context, name: Shipping }\nedges:\n - { from: order, to: cat, label: reads, kind: dashed }\n - { from: order, to: pay }\n - { from: order, to: ship }\n```\n",
393
+ network: "```network\ntitle: Security zones\nnodes:\n - { id: edge, col: 1, row: 1, kind: gateway, name: Edge / WAF }\n - { id: fw, col: 2, row: 1, kind: firewall, name: Perimeter FW }\n - { id: api, col: 3, row: 1, kind: service, name: API }\n - { id: db, col: 3, row: 2, kind: store, name: DB (private) }\nedges:\n - { from: edge, to: fw }\n - { from: fw, to: api }\n - { from: api, to: db }\n```\n",
394
+ felogic: "```felogic\ntitle: Frontend modules\nnodes:\n - { id: hook, col: 1, row: 1, kind: hook, name: useOrders }\n - { id: svc, col: 2, row: 1, kind: service, name: ordersService }\n - { id: iface, col: 3, row: 1, kind: interface, name: OrdersClient }\n - { id: impl, col: 4, row: 1, kind: strategy, name: HttpOrdersClient }\n - { id: api, col: 5, row: 1, kind: external, name: Orders API }\nedges:\n - { from: hook, to: svc }\n - { from: svc, to: iface, kind: uses }\n - { from: impl, to: iface, kind: implements }\n - { from: impl, to: api, kind: egress, label: HTTPS }\n```\n",
395
+ belogic: "```belogic\ntitle: Backend modules\nnodes:\n - { id: ctrl, col: 1, row: 1, kind: controller, name: OrdersController }\n - { id: svc, col: 2, row: 1, kind: service, name: PlaceOrder }\n - { id: repo, col: 3, row: 1, kind: repository, name: OrdersRepo }\n - { id: db, col: 4, row: 1, kind: db, name: postgres }\n - { id: pay, col: 3, row: 2, kind: external, name: Stripe }\nedges:\n - { from: ctrl, to: svc }\n - { from: svc, to: repo }\n - { from: repo, to: db, kind: reads }\n - { from: svc, to: pay, kind: egress, label: authorise }\n```\n",
396
+ dag: "```dag\ntitle: Build pipeline\nnodes:\n - { id: src, col: 1, row: 1, kind: start, label: Source }\n - { id: lint, col: 2, row: 1, kind: process, label: Lint }\n - { id: test, col: 3, row: 1, kind: process, label: Test }\n - { id: build, col: 4, row: 1, kind: process, label: Build }\n - { id: deploy, col: 5, row: 1, kind: end, label: Deploy }\nedges:\n - { from: src, to: lint }\n - { from: lint, to: test }\n - { from: test, to: build }\n - { from: build, to: deploy }\n```\n",
397
+ wireframe: '```wireframe\ntitle: What the user sees\nscreens:\n - device: browser\n title: Dashboard\n url: app.example.com\n label: Desktop\n elements:\n - { type: nav, label: "Home, Inbox, Settings" }\n - { type: header, label: Notifications }\n - { type: list, rows: 4 }\n - { type: button, label: Mark all as read }\n - device: phone\n title: "9:41"\n label: iPhone\n elements:\n - { type: header, label: Alerts }\n - { type: card, rows: 3 }\n - { type: tabs, label: "Home, Search, Bell, You" }\n```\n'
398
+ };
399
+ function templateFor(type) {
400
+ return `\`\`\`meta
401
+ title: New document
402
+ tag: DRAFT
403
+ \`\`\`
404
+
405
+ ## ${type}
406
+
407
+ ${TEMPLATES[type]}`;
408
+ }
409
+ var ADR_TEMPLATE = [
410
+ "```meta",
411
+ "title: ADR-NNN \u2014 decision title",
412
+ "subtitle: One line on what we decided and why it matters.",
413
+ "tag: ADR \xB7 Proposed \xB7 YYYY-MM-DD",
414
+ "```",
415
+ "",
416
+ "## Status",
417
+ "",
418
+ "**Proposed** \u2014 YYYY-MM-DD. (Proposed \u2192 Accepted \u2192 Superseded.)",
419
+ "",
420
+ "## Context",
421
+ "",
422
+ "What forces a decision here? The constraints, requirements, and the problem",
423
+ "being solved. Two to four sentences of plain prose \u2014 no block needed.",
424
+ "",
425
+ "```callout",
426
+ "tone: note",
427
+ "title: Decision",
428
+ 'body: "State the decision in one or two sentences \u2014 what we will do, and the single most important reason why."',
429
+ "```",
430
+ "",
431
+ "## Options considered",
432
+ "",
433
+ "```proscons",
434
+ "id: the-choice",
435
+ "title: Chosen option vs the alternative",
436
+ "prosLabel: Chosen option",
437
+ "consLabel: The alternative",
438
+ "pros:",
439
+ " - A concrete reason the chosen option wins",
440
+ " - Another benefit worth the trade-off",
441
+ "cons:",
442
+ " - A real cost of the alternative",
443
+ " - Another drawback we are accepting against",
444
+ "```",
445
+ "",
446
+ "## Architecture",
447
+ "",
448
+ "```block",
449
+ "id: architecture",
450
+ "title: How it fits together",
451
+ "nodes:",
452
+ " - { id: client, col: 1, row: 1, kind: client, name: Client }",
453
+ " - { id: svc, col: 2, row: 1, kind: service, name: Service, tech: your stack }",
454
+ " - { id: db, col: 3, row: 1, kind: store, name: Database }",
455
+ "edges:",
456
+ " - { from: client, to: svc }",
457
+ " - { from: svc, to: db }",
458
+ "```",
459
+ "",
460
+ "## Consequences",
461
+ "",
462
+ "```tracker",
463
+ "id: consequences",
464
+ "title: Consequences & follow-ups",
465
+ "items:",
466
+ ' - { task: "Something that becomes easier or newly required", status: todo, priority: high }',
467
+ ' - { task: "A migration or new responsibility this creates", status: todo, priority: med }',
468
+ ' - { task: "A risk to watch as we roll this out", status: todo, priority: low }',
469
+ "```",
470
+ ""
471
+ ].join("\n");
472
+ var DOC_TEMPLATES = {
473
+ adr: ADR_TEMPLATE
474
+ };
475
+ function isDocTemplate(name) {
476
+ return Object.prototype.hasOwnProperty.call(DOC_TEMPLATES, name);
477
+ }
478
+ async function writeNewDoc(opts) {
479
+ const outAbs = resolve(opts.cwd, opts.out);
480
+ await mkdir(dirname(outAbs), { recursive: true });
481
+ const content = isDocTemplate(opts.type) ? DOC_TEMPLATES[opts.type] : templateFor(opts.type);
482
+ await writeFile(outAbs, content, "utf8");
483
+ return outAbs;
484
+ }
485
+ function NewPicker({ onPick }) {
486
+ const items = BLOCK_TYPES.map((t) => ({ label: t, value: t }));
487
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
488
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Pick a block type to scaffold:" }),
489
+ /* @__PURE__ */ jsx(
490
+ SelectInput,
491
+ {
492
+ items,
493
+ onSelect: (item) => onPick(item.value)
494
+ }
495
+ )
496
+ ] });
497
+ }
498
+ function NewApp({ cwd, out }) {
499
+ const [picked, setPicked] = useState(null);
500
+ const [writtenPath, setWrittenPath] = useState(null);
501
+ const { exit } = useApp();
502
+ const handlePick = (type) => {
503
+ setPicked(type);
504
+ void writeNewDoc({ cwd, type, out }).then((path) => {
505
+ setWrittenPath(path);
506
+ setTimeout(exit, 50);
507
+ });
508
+ };
509
+ if (writtenPath !== null) {
510
+ return /* @__PURE__ */ jsxs(Box, { children: [
511
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" }),
512
+ /* @__PURE__ */ jsx(Text, { children: " Wrote " }),
513
+ /* @__PURE__ */ jsx(Text, { bold: true, children: writtenPath })
514
+ ] });
515
+ }
516
+ if (picked !== null) {
517
+ return /* @__PURE__ */ jsxs(Text, { children: [
518
+ "Writing ",
519
+ out,
520
+ "\u2026"
521
+ ] });
522
+ }
523
+ return /* @__PURE__ */ jsx(NewPicker, { onPick: handlePick });
524
+ }
525
+ function renderCodeFrame(input) {
526
+ const { lines, line } = input;
527
+ if (line < 1 || line > lines.length) return "";
528
+ const contextBefore = input.contextBefore ?? 1;
529
+ const start = Math.max(1, line - contextBefore);
530
+ const gutterWidth = String(line).length + 1;
531
+ const tint = input.level === "warn" ? pc3.yellow : pc3.red;
532
+ const out = [];
533
+ for (let n = start; n <= line; n++) {
534
+ const text = lines[n - 1] ?? "";
535
+ const gutter = pc3.dim(`${String(n).padStart(gutterWidth)} | `);
536
+ out.push(gutter + text);
537
+ }
538
+ if (input.column !== void 0 && input.column >= 1) {
539
+ const col = input.column;
540
+ const span = input.endColumn !== void 0 ? Math.max(1, input.endColumn - col) : 1;
541
+ const pad = " ".repeat(gutterWidth) + pc3.dim(" | ") + " ".repeat(col - 1);
542
+ out.push(pad + tint("^".repeat(span)));
543
+ }
544
+ return out.join("\n");
545
+ }
546
+ var COLOR = {
547
+ error: "red",
548
+ warn: "yellow"
549
+ };
550
+ function frameFor(d, sources) {
551
+ if (d.line === void 0) return "";
552
+ const lines = sources.get(d.file);
553
+ if (lines === void 0) return "";
554
+ return renderCodeFrame({
555
+ lines,
556
+ line: d.line,
557
+ ...d.column !== void 0 ? { column: d.column } : {},
558
+ ...d.endColumn !== void 0 ? { endColumn: d.endColumn } : {},
559
+ level: d.level
560
+ });
561
+ }
562
+ function DiagnosticsTable({ diagnostics, fileCount, sources }) {
563
+ const errorCount = diagnostics.filter((d) => d.level === "error").length;
564
+ const warnCount = diagnostics.filter((d) => d.level === "warn").length;
565
+ if (diagnostics.length === 0) {
566
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsxs(Text, { color: "green", children: [
567
+ "\u2713 ",
568
+ fileCount,
569
+ " ",
570
+ fileCount === 1 ? "file" : "files",
571
+ " checked \u2014 no diagnostics"
572
+ ] }) });
573
+ }
574
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
575
+ diagnostics.map((d, i) => {
576
+ const frame = frameFor(d, sources);
577
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
578
+ /* @__PURE__ */ jsxs(Box, { children: [
579
+ /* @__PURE__ */ jsxs(Text, { color: COLOR[d.level], bold: true, children: [
580
+ d.level === "error" ? "\u2716" : "\u26A0",
581
+ " "
582
+ ] }),
583
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
584
+ d.file,
585
+ d.line !== void 0 ? `:${d.line}` : "",
586
+ d.column !== void 0 ? `:${d.column}` : "",
587
+ " "
588
+ ] }),
589
+ /* @__PURE__ */ jsxs(Text, { color: COLOR[d.level], children: [
590
+ d.code,
591
+ " "
592
+ ] }),
593
+ /* @__PURE__ */ jsx(Text, { children: d.message })
594
+ ] }),
595
+ frame.length > 0 ? /* @__PURE__ */ jsx(Text, { children: frame }) : null,
596
+ d.hint !== void 0 ? /* @__PURE__ */ jsxs(Text, { children: [
597
+ " ",
598
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "hint:" }),
599
+ " ",
600
+ d.hint
601
+ ] }) : null,
602
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
603
+ " ",
604
+ helpUrl(d.code)
605
+ ] })
606
+ ] }, `${d.file}:${d.line ?? "-"}:${i}`);
607
+ }),
608
+ /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { bold: true, color: errorCount > 0 ? "red" : warnCount > 0 ? "yellow" : "green", children: [
609
+ errorCount,
610
+ " ",
611
+ errorCount === 1 ? "error" : "errors",
612
+ ", ",
613
+ warnCount,
614
+ " ",
615
+ warnCount === 1 ? "warning" : "warnings",
616
+ " across ",
617
+ fileCount,
618
+ " ",
619
+ fileCount === 1 ? "file" : "files"
620
+ ] }) })
621
+ ] });
622
+ }
623
+ function formatDiagnosticsPlain(diagnostics, fileCount, sources) {
624
+ if (diagnostics.length === 0) {
625
+ return `OK: ${fileCount} ${fileCount === 1 ? "file" : "files"} checked, no diagnostics
626
+ `;
627
+ }
628
+ const blocks = diagnostics.map((d) => {
629
+ const loc = d.line !== void 0 ? `${d.file}:${d.line}${d.column !== void 0 ? `:${d.column}` : ""}` : d.file;
630
+ const parts = [`${loc} ${d.level} ${d.code} ${d.message}`];
631
+ const frame = frameFor(d, sources);
632
+ if (frame.length > 0) parts.push(frame);
633
+ if (d.hint !== void 0) parts.push(` hint: ${d.hint}`);
634
+ return parts.join("\n");
635
+ });
636
+ const errors = diagnostics.filter((d) => d.level === "error").length;
637
+ const warns = diagnostics.filter((d) => d.level === "warn").length;
638
+ blocks.push(`${errors} error(s), ${warns} warning(s) across ${fileCount} file(s)`);
639
+ return blocks.join("\n\n") + "\n";
640
+ }
641
+ function banner(version = "0.0.1") {
642
+ const g = pc3.green;
643
+ const pit = pc3.yellow("\u25CF");
644
+ const name = pc3.green(pc3.bold("avodado"));
645
+ const ver = pc3.dim(`v${version}`);
646
+ const tag = pc3.dim("Documentation-as-code \u2014 Markdown with typed, fenced YAML blocks.");
647
+ return [
648
+ "",
649
+ ` ${g("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}`,
650
+ ` ${g("\u2502 ")}${pit}${g(" ")}${pit}${g(" \u2502")} ${name} ${ver}`,
651
+ ` ${g("\u2502 ")}${pc3.yellow("\u25E1")}${g(" \u2502")} ${tag}`,
652
+ ` ${g("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}`,
653
+ ""
654
+ ].join("\n");
655
+ }
656
+ function examples() {
657
+ const step = (n, cmd, note) => ` ${pc3.dim(n)} ${pc3.cyan(cmd.padEnd(26))}${pc3.dim(note)}`;
658
+ return [
659
+ "",
660
+ pc3.bold("Workflow:"),
661
+ step("1", "avo init", "scaffold docs/, config, and the agent skill"),
662
+ step("2", "edit docs/*.md", "prose + typed blocks (grammar in .avodado/skill)"),
663
+ step("3", "avo check", "validate \u2014 exits non-zero on errors (use in CI)"),
664
+ step("4", "avo preview docs/x.md", "render and open it in your browser"),
665
+ "",
666
+ ` ${pc3.dim("Per-command help:")} avo ${pc3.cyan("<command>")} --help`,
667
+ ` ${pc3.dim("Docs:")} https://github.com/jdiejim/avodado`,
668
+ ""
669
+ ].join("\n");
670
+ }
671
+
672
+ // src/tty.ts
673
+ var isInteractive = process.stdout.isTTY === true && process.env["CI"] !== "true" && process.env["AVO_PLAIN"] !== "1";
674
+ async function main(argv) {
675
+ const program = new Command();
676
+ program.name("avo").description("Author, validate, render, and export Avodado documentation.").version("0.0.1").addHelpText("beforeAll", (ctx) => ctx.command.name() === "avo" ? banner() : "").addHelpText("after", (ctx) => ctx.command.name() === "avo" ? examples() : "").exitOverride();
677
+ let exitCode = 0;
678
+ program.command("init").description("Scaffold a new Avodado project in the current directory").option("--force", "overwrite existing files").action(async (opts) => {
679
+ const cwd = process.cwd();
680
+ const result = await runInit({ cwd, ...opts.force ? { force: true } : {} });
681
+ for (const f of result.created) console.log(pc3.green("+ ") + f);
682
+ for (const f of result.skipped) console.log(pc3.dim(" skip ") + f + pc3.dim(" (exists)"));
683
+ console.log(
684
+ pc3.bold(
685
+ `
686
+ Created ${result.created.length} file(s), skipped ${result.skipped.length}.
687
+ Next: avo check`
688
+ )
689
+ );
690
+ });
691
+ program.command("new").description("Scaffold a new doc from a block template").option(
692
+ "--type <kind>",
693
+ "doc template (" + Object.keys(DOC_TEMPLATES).join(", ") + ") or block type (" + BLOCK_TYPES.join(", ") + ")"
694
+ ).option("--out <path>", "output file path").action(async (opts) => {
695
+ const cwd = process.cwd();
696
+ const type = opts.type;
697
+ if (type !== void 0 && !BLOCK_TYPES.includes(type) && !isDocTemplate(type)) {
698
+ console.error(pc3.red(`Unknown type: ${opts.type ?? ""}`));
699
+ exitCode = 2;
700
+ return;
701
+ }
702
+ if (!isInteractive) {
703
+ if (type === void 0 || opts.out === void 0) {
704
+ console.error(pc3.red("In non-interactive mode, both --type and --out are required."));
705
+ exitCode = 2;
706
+ return;
707
+ }
708
+ const path = await writeNewDoc({ cwd, type, out: opts.out });
709
+ console.log(pc3.green("\u2713 ") + "Wrote " + path);
710
+ return;
711
+ }
712
+ if (type !== void 0 && opts.out !== void 0) {
713
+ const path = await writeNewDoc({ cwd, type, out: opts.out });
714
+ console.log(pc3.green("\u2713 ") + "Wrote " + path);
715
+ return;
716
+ }
717
+ const out = opts.out ?? "./docs/new-doc.md";
718
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx(NewApp, { cwd, out }));
719
+ await waitUntilExit();
720
+ });
721
+ program.command("check [globs...]").description("Validate documents (default: docs/**/*.md)").option("--json", "emit machine-readable JSON").action(async (globs, opts) => {
722
+ const cwd = process.cwd();
723
+ const config = await loadConfig(cwd);
724
+ const patterns = globs.length > 0 ? globs : [`${config.docsDir}/**/*.md`];
725
+ const result = await runCheck({ patterns, cwd, docsRoot: config.docsDir });
726
+ if (opts.json === true) {
727
+ process.stdout.write(
728
+ JSON.stringify({ diagnostics: result.diagnostics, files: result.files }, null, 2) + "\n"
729
+ );
730
+ } else if (isInteractive) {
731
+ const { waitUntilExit } = render(
732
+ /* @__PURE__ */ jsx(
733
+ DiagnosticsTable,
734
+ {
735
+ diagnostics: result.diagnostics,
736
+ fileCount: result.files.length,
737
+ sources: result.sources
738
+ }
739
+ )
740
+ );
741
+ await waitUntilExit();
742
+ } else {
743
+ process.stdout.write(
744
+ formatDiagnosticsPlain(result.diagnostics, result.files.length, result.sources)
745
+ );
746
+ }
747
+ exitCode = result.exitCode;
748
+ });
749
+ program.command("render <input>").description("Render one document to a standalone HTML file").option("-o, --output <path>", "output file path (defaults to <input>.html)").action(async (input, opts) => {
750
+ const cwd = process.cwd();
751
+ const result = await runRender({
752
+ cwd,
753
+ input,
754
+ ...opts.output !== void 0 ? { output: opts.output } : {}
755
+ });
756
+ if (isInteractive) {
757
+ const { waitUntilExit } = render(
758
+ /* @__PURE__ */ jsxs(Text, { children: [
759
+ /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 " }),
760
+ "Wrote ",
761
+ /* @__PURE__ */ jsx(Text, { bold: true, children: result.output }),
762
+ " ",
763
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
764
+ "(",
765
+ result.bytes,
766
+ " bytes)"
767
+ ] })
768
+ ] })
769
+ );
770
+ await waitUntilExit();
771
+ } else {
772
+ console.log(`${pc3.green("\u2713")} rendered ${result.output} ${pc3.dim(`(${result.bytes} bytes)`)}`);
773
+ }
774
+ });
775
+ program.command("export [globs...]").description("Batch-export documents to HTML and/or PDF").option("--format <list>", "comma-separated formats: html,pdf", "html").option("--out <dir>", "output directory (defaults to config outDir or dist)").action(async (globs, opts) => {
776
+ const cwd = process.cwd();
777
+ const config = await loadConfig(cwd);
778
+ const patterns = globs.length > 0 ? globs : [`${config.docsDir}/**/*.md`];
779
+ const formats = opts.format.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s === "html" || s === "pdf");
780
+ if (formats.length === 0) {
781
+ console.error(pc3.red("No valid --format specified (expected html, pdf, or both)."));
782
+ exitCode = 2;
783
+ return;
784
+ }
785
+ const result = await runExport({
786
+ cwd,
787
+ patterns,
788
+ docsRoot: config.docsDir,
789
+ outDir: opts.out ?? config.outDir,
790
+ formats
791
+ });
792
+ for (const item of result.items) {
793
+ for (const o of item.outputs) {
794
+ console.log(`${pc3.green("\u2713")} ${o.path} ${pc3.dim(`(${o.bytes} bytes)`)}`);
795
+ }
796
+ }
797
+ console.log(pc3.bold(`
798
+ ${result.items.length} document(s) exported.`));
799
+ });
800
+ program.command("preview <input>").description("Render a document to a temp HTML file and open it").action(async (input) => {
801
+ const cwd = process.cwd();
802
+ const result = await runPreview({ cwd, input });
803
+ console.log(pc3.green("\u2713 ") + "Opened " + result.file);
804
+ });
805
+ const syncCmd = program.command("sync").description("Generate Avodado docs from external sources (OpenAPI)");
806
+ syncCmd.command("openapi <spec>").description("Generate (or drift-check) a doc from an OpenAPI 3.x spec").option("-o, --out <path>", "write generated markdown to this path").option("--check <path>", "compare against an existing doc and fail on drift").option("--slug <slug>", "block-id namespace (defaults to the output basename)").action(
807
+ async (spec, opts) => {
808
+ const result = await runSyncOpenApi({
809
+ cwd: process.cwd(),
810
+ spec,
811
+ ...opts.out !== void 0 ? { out: opts.out } : {},
812
+ ...opts.check !== void 0 ? { check: opts.check } : {},
813
+ ...opts.slug !== void 0 ? { slug: opts.slug } : {}
814
+ });
815
+ if (result.exitCode === 0) {
816
+ console.log(pc3.green("\u2713 ") + result.message);
817
+ } else {
818
+ console.error(pc3.red(result.message));
819
+ if (result.diff !== void 0) console.error(result.diff);
820
+ }
821
+ exitCode = result.exitCode;
822
+ }
823
+ );
824
+ try {
825
+ await program.parseAsync(argv, { from: "node" });
826
+ } catch (err) {
827
+ const e = err;
828
+ if (e.code === "commander.helpDisplayed" || e.code === "commander.version") {
829
+ return 0;
830
+ }
831
+ if (e.code === "commander.help") return 0;
832
+ if (typeof e.exitCode === "number" && e.code?.startsWith("commander.")) {
833
+ return e.exitCode;
834
+ }
835
+ console.error(pc3.red(e.message ?? String(err)));
836
+ return 1;
837
+ }
838
+ return exitCode;
839
+ }
840
+
841
+ // src/bin.ts
842
+ var code = await main(process.argv);
843
+ process.exit(code);
844
+ //# sourceMappingURL=bin.js.map
845
+ //# sourceMappingURL=bin.js.map