vplan 0.1.0 → 0.2.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/core/index.ts CHANGED
@@ -56,6 +56,7 @@ export const calloutSchema = z.object({
56
56
  })
57
57
 
58
58
  export const questionsSchema = z.object({
59
+ title: z.string().default('Open questions'),
59
60
  items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),
60
61
  })
61
62
 
@@ -118,7 +119,7 @@ export const CATALOG: readonly CatalogEntry[] = [
118
119
  {
119
120
  name: 'Questions',
120
121
  summary:
121
- 'Open questions you (Claude) want the reader to weigh in on before building, as a highlighted panel.',
122
+ 'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to "Open questions"; override with title.',
122
123
  staticEnums: {},
123
124
  example:
124
125
  '<Questions items={["Should refresh tokens rotate on every use?", "Is a 15-minute access-token TTL acceptable?"]} />',
package/dist/index.js CHANGED
@@ -6,16 +6,23 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "vplan",
9
- version: "0.1.0",
10
- description: "Render Claude's plans as visual MDX pages instead of walls of text",
9
+ version: "0.2.0",
10
+ description: "Render an AI agent's plans as visual MDX pages instead of walls of text",
11
11
  author: "Brandon Burrus <brandon@burrus.io>",
12
12
  license: "MIT",
13
+ repository: {
14
+ type: "git",
15
+ url: "git+https://github.com/brandonburrus/visualplan.git",
16
+ directory: "packages/cli"
17
+ },
18
+ homepage: "https://github.com/brandonburrus/visualplan#readme",
19
+ bugs: "https://github.com/brandonburrus/visualplan/issues",
13
20
  type: "module",
14
21
  engines: {
15
22
  node: ">=20.0.0"
16
23
  },
17
24
  bin: {
18
- vplan: "./dist/index.js"
25
+ vplan: "dist/index.js"
19
26
  },
20
27
  files: [
21
28
  "dist",
@@ -51,10 +58,6 @@ var package_default = {
51
58
  vite: "^8.0.16",
52
59
  "vite-plugin-singlefile": "^2.3.3",
53
60
  zod: "^4.4.3"
54
- },
55
- devDependencies: {
56
- "@visualplan/core": "workspace:*",
57
- "@visualplan/runtime": "workspace:*"
58
61
  }
59
62
  };
60
63
 
@@ -109,6 +112,7 @@ var calloutSchema = z.object({
109
112
  type: z.enum(CALLOUT_TYPE_VALUES).default("note")
110
113
  });
111
114
  var questionsSchema = z.object({
115
+ title: z.string().default("Open questions"),
112
116
  items: z.array(z.string().min(1)).min(1, "questions needs at least one item")
113
117
  });
114
118
  var checklistSchema = z.object({
@@ -153,7 +157,7 @@ var CATALOG = [
153
157
  },
154
158
  {
155
159
  name: "Questions",
156
- summary: "Open questions you (Claude) want the reader to weigh in on before building, as a highlighted panel.",
160
+ summary: 'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to "Open questions"; override with title.',
157
161
  staticEnums: {},
158
162
  example: '<Questions items={["Should refresh tokens rotate on every use?", "Is a 15-minute access-token TTL acceptable?"]} />'
159
163
  },
@@ -189,11 +193,14 @@ async function checkPlan(mdxPath) {
189
193
  });
190
194
  } catch (error) {
191
195
  const vfileError = error;
196
+ const message = vfileError.reason ?? vfileError.message ?? "MDX failed to compile";
197
+ const placeStart = vfileError.place?.start ?? vfileError.place;
198
+ const fromMessage = message.match(/\((\d+):(\d+)/);
192
199
  return [
193
200
  {
194
- line: vfileError.line ?? 1,
195
- column: vfileError.column ?? 1,
196
- message: vfileError.reason ?? vfileError.message ?? "MDX failed to compile"
201
+ line: vfileError.line ?? placeStart?.line ?? (fromMessage ? Number(fromMessage[1]) : 1),
202
+ column: vfileError.column ?? placeStart?.column ?? (fromMessage ? Number(fromMessage[2]) : 1),
203
+ message
197
204
  }
198
205
  ];
199
206
  }
@@ -389,7 +396,20 @@ function baseConfig(paths, mdxPath) {
389
396
  root: paths.runtimeDir,
390
397
  configFile: false,
391
398
  logLevel: "silent",
392
- resolve: { alias: { "virtual:plan": mdxPath, "@visualplan/core": paths.coreEntry } },
399
+ resolve: {
400
+ alias: {
401
+ "virtual:plan": mdxPath,
402
+ "@visualplan/core": paths.coreEntry,
403
+ // The plan .mdx lives anywhere on disk, often outside any node project, but
404
+ // @mdx-js/rollup makes it import react/jsx-runtime and @mdx-js/react. Those
405
+ // are attributed to the plan's own directory, which usually has no
406
+ // node_modules, so resolve them from the CLI's install instead. Without this
407
+ // a plan in a bare directory fails with "failed to resolve react/jsx-runtime".
408
+ "react/jsx-runtime": require2.resolve("react/jsx-runtime"),
409
+ "react/jsx-dev-runtime": require2.resolve("react/jsx-dev-runtime"),
410
+ "@mdx-js/react": require2.resolve("@mdx-js/react")
411
+ }
412
+ },
393
413
  esbuild: { jsx: "automatic", jsxImportSource: "react" },
394
414
  plugins: [mdxPlugin()],
395
415
  // The runtime, core, and the user's plan span sibling dirs (and a hoisted
@@ -462,7 +482,7 @@ async function runRender(file, options) {
462
482
  }
463
483
 
464
484
  // src/index.ts
465
- var program = new Command("vplan").description("Render Claude plans as visual MDX pages instead of walls of text").version(package_default.version);
485
+ var program = new Command("vplan").description("Render an AI agent's plans as visual MDX pages instead of walls of text").version(package_default.version);
466
486
  program.command("render", { isDefault: true }).description("Compile a plan .mdx to a self-contained HTML page (default command)").argument("<file>", "the plan .mdx file to render").option("--watch", "start a hot-reloading dev server instead of writing a file").option("--out <path>", "output HTML path (defaults to <file>.plan.html)").option("--no-open", "do not open the result in a browser").action((file, options) => runRender(file, options));
467
487
  program.command("check").description("Validate a plan .mdx (compile + component checks) without rendering").argument("<file>", "the plan .mdx file to validate").action((file) => runCheck(file));
468
488
  program.command("components").description("Print the available plan components and their props").action(() => runComponents());
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../package.json","../src/commands/check.ts","../src/build/check.ts","../../core/src/index.ts","../src/commands/components.ts","../src/commands/render.ts","../src/build/compile.ts","../src/build/remark-mermaid.ts"],"sourcesContent":["import { Command } from 'commander'\nimport packageJson from '../package.json' with { type: 'json' }\nimport { runCheck } from './commands/check.js'\nimport { runComponents } from './commands/components.js'\nimport { runRender, type RenderOptions } from './commands/render.js'\n\nconst program = new Command('vplan')\n .description('Render Claude plans as visual MDX pages instead of walls of text')\n .version(packageJson.version)\n\nprogram\n .command('render', { isDefault: true })\n .description('Compile a plan .mdx to a self-contained HTML page (default command)')\n .argument('<file>', 'the plan .mdx file to render')\n .option('--watch', 'start a hot-reloading dev server instead of writing a file')\n .option('--out <path>', 'output HTML path (defaults to <file>.plan.html)')\n .option('--no-open', 'do not open the result in a browser')\n .action((file: string, options: RenderOptions) => runRender(file, options))\n\nprogram\n .command('check')\n .description('Validate a plan .mdx (compile + component checks) without rendering')\n .argument('<file>', 'the plan .mdx file to validate')\n .action((file: string) => runCheck(file))\n\nprogram\n .command('components')\n .description('Print the available plan components and their props')\n .action(() => runComponents())\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n})\n","{\n \"name\": \"vplan\",\n \"version\": \"0.1.0\",\n \"description\": \"Render Claude's plans as visual MDX pages instead of walls of text\",\n \"author\": \"Brandon Burrus <brandon@burrus.io>\",\n \"license\": \"MIT\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"bin\": {\n \"vplan\": \"./dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"runtime\",\n \"core\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/index.ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"vendor\": \"node scripts/vendor.mjs\",\n \"prepack\": \"node scripts/vendor.mjs && tsup\"\n },\n \"dependencies\": {\n \"@mdx-js/mdx\": \"^3.1.1\",\n \"@mdx-js/react\": \"^3.1.1\",\n \"@mdx-js/rollup\": \"^3.1.1\",\n \"@tabler/icons-react\": \"^3.44.0\",\n \"beautiful-mermaid\": \"^1.1.3\",\n \"commander\": \"^15.0.0\",\n \"open\": \"^11.0.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"recharts\": \"^3.8.1\",\n \"rehype-expressive-code\": \"^0.43.1\",\n \"remark-frontmatter\": \"^5.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-mdx\": \"^3.1.1\",\n \"remark-mdx-frontmatter\": \"^5.2.0\",\n \"remark-parse\": \"^11.0.0\",\n \"unified\": \"^11.0.5\",\n \"unist-util-visit\": \"^5.1.0\",\n \"vite\": \"^8.0.16\",\n \"vite-plugin-singlefile\": \"^2.3.3\",\n \"zod\": \"^4.4.3\"\n },\n \"devDependencies\": {\n \"@visualplan/core\": \"workspace:*\",\n \"@visualplan/runtime\": \"workspace:*\"\n }\n}\n","import { resolve } from 'node:path'\nimport { type CheckIssue, checkPlan } from '../build/check.js'\n\n/** Print check issues in an editor-clickable `file:line:column message` format. */\nexport function printIssues(file: string, issues: CheckIssue[]): void {\n for (const issue of issues) {\n process.stderr.write(`${file}:${issue.line}:${issue.column} ${issue.message}\\n`)\n }\n}\n\n/** `vplan check <file>` — validate a plan's MDX without rendering it. */\nexport async function runCheck(file: string): Promise<void> {\n const issues = await checkPlan(resolve(file))\n if (issues.length === 0) {\n process.stdout.write(`${file} is valid\\n`)\n return\n }\n printIssues(file, issues)\n process.stdout.write(`\\n${issues.length} issue(s) found\\n`)\n process.exitCode = 1\n}\n","import { readFile } from 'node:fs/promises'\nimport { compile } from '@mdx-js/mdx'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport remarkMdx from 'remark-mdx'\nimport remarkParse from 'remark-parse'\nimport { unified } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport { CATALOG } from '@visualplan/core'\n\nexport interface CheckIssue {\n line: number\n column: number\n message: string\n}\n\ninterface JsxAttribute {\n type: string\n name?: string\n value?: unknown\n}\n\ninterface JsxNode {\n type: string\n name?: string | null\n attributes?: JsxAttribute[]\n position?: { start: { line: number; column: number } }\n}\n\nconst COMPONENT_NAMES = CATALOG.filter(entry => /^[A-Z][A-Za-z0-9]*$/.test(entry.name)).map(\n entry => entry.name,\n)\n\nconst ENUMS_BY_COMPONENT = new Map(CATALOG.map(entry => [entry.name, entry.staticEnums]))\n\n/** Validate a plan's MDX: real compile errors plus static enum / unknown-component checks. */\nexport async function checkPlan(mdxPath: string): Promise<CheckIssue[]> {\n const source = await readFile(mdxPath, 'utf8')\n const issues: CheckIssue[] = []\n\n try {\n await compile(source, {\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n ],\n })\n } catch (error) {\n const vfileError = error as {\n line?: number\n column?: number\n reason?: string\n message?: string\n }\n return [\n {\n line: vfileError.line ?? 1,\n column: vfileError.column ?? 1,\n message: vfileError.reason ?? vfileError.message ?? 'MDX failed to compile',\n },\n ]\n }\n\n const tree = unified().use(remarkParse).use(remarkFrontmatter).use(remarkMdx).parse(source)\n\n visit(tree, node => {\n const element = node as unknown as JsxNode\n if (element.type !== 'mdxJsxFlowElement' && element.type !== 'mdxJsxTextElement') return\n const name = element.name\n if (!name) return\n const at = element.position?.start ?? { line: 1, column: 1 }\n\n if (!COMPONENT_NAMES.includes(name)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `Unknown component <${name}>. Valid components: ${COMPONENT_NAMES.join(', ')}.`,\n })\n return\n }\n\n const enums = ENUMS_BY_COMPONENT.get(name) ?? {}\n for (const [prop, allowed] of Object.entries(enums)) {\n const attribute = element.attributes?.find(\n candidate => candidate.type === 'mdxJsxAttribute' && candidate.name === prop,\n )\n if (attribute && typeof attribute.value === 'string' && !allowed.includes(attribute.value)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `<${name}> prop ${prop}=\"${attribute.value}\" is invalid. Valid: ${allowed.join(', ')}.`,\n })\n }\n }\n })\n\n return issues\n}\n","import { z } from 'zod'\n\n/**\n * Single source of truth for the VisualPlan component vocabulary.\n *\n * This module is imported by BOTH the browser runtime (for render-time zod\n * validation) and the Node CLI (for static `check` and the `components`\n * catalog printer), so it must stay free of any React, recharts, or mermaid\n * imports. Keep it isomorphic.\n */\n\nexport const STATUS_VALUES = ['planned', 'active', 'done'] as const\nexport const CHANGE_VALUES = ['add', 'modify', 'delete', 'move'] as const\nexport const CHART_TYPE_VALUES = ['bar', 'line', 'pie'] as const\nexport const CALLOUT_TYPE_VALUES = ['note', 'risk', 'decision', 'warn'] as const\n\nexport const phaseSchema = z.object({\n title: z.string().min(1, 'title is required'),\n status: z.enum(STATUS_VALUES).default('planned'),\n})\n\nexport const fileTreeSchema = z.object({\n files: z\n .array(\n z.object({\n path: z.string().min(1, 'each file needs a path'),\n change: z.enum(CHANGE_VALUES),\n }),\n )\n .min(1, 'files must list at least one entry'),\n})\n\nexport const chartSchema = z.object({\n type: z.enum(CHART_TYPE_VALUES),\n title: z.string().optional(),\n data: z\n .array(z.object({ label: z.string(), value: z.number() }))\n .min(1, 'data must have at least one point'),\n})\n\nexport const compareSchema = z.object({\n options: z\n .array(\n z.object({\n name: z.string().min(1, 'each option needs a name'),\n pros: z.array(z.string()).default([]),\n cons: z.array(z.string()).default([]),\n pick: z.boolean().optional(),\n }),\n )\n .min(2, 'compare needs at least two options'),\n})\n\nexport const calloutSchema = z.object({\n type: z.enum(CALLOUT_TYPE_VALUES).default('note'),\n})\n\nexport const questionsSchema = z.object({\n items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),\n})\n\nexport const checklistSchema = z.object({\n title: z.string().optional(),\n items: z\n .array(\n z.object({\n text: z.string().min(1, 'each item needs text'),\n done: z.boolean().default(false),\n }),\n )\n .min(1, 'checklist needs at least one item'),\n})\n\n/** Describes a component for the `components` printer and the static checker. */\nexport interface CatalogEntry {\n name: string\n summary: string\n /** Props the CLI can statically validate from MDX source (string-literal enums). */\n staticEnums: Record<string, readonly string[]>\n example: string\n}\n\nexport const CATALOG: readonly CatalogEntry[] = [\n {\n name: 'Phase',\n summary: 'A collapsible plan stage with a status badge. Wraps markdown.',\n staticEnums: { status: STATUS_VALUES },\n example: '<Phase title=\"Build the API\" status=\"active\">\\n 1. Define routes\\n</Phase>',\n },\n {\n name: 'FileTree',\n summary:\n 'A nested directory tree of file changes, built from the paths, with add/modify/delete/move markers.',\n staticEnums: {},\n example:\n '<FileTree files={[{ path: \"src/api/routes.ts\", change: \"add\" }, { path: \"src/api/db.ts\", change: \"modify\" }, { path: \"src/legacy.ts\", change: \"delete\" }]} />',\n },\n {\n name: 'Chart',\n summary: 'A bar/line/pie chart for estimates or metrics.',\n staticEnums: { type: CHART_TYPE_VALUES },\n example:\n '<Chart type=\"bar\" title=\"Effort (days)\" data={[{ label: \"API\", value: 3 }, { label: \"UI\", value: 2 }]} />',\n },\n {\n name: 'Compare',\n summary: 'Side-by-side option cards for weighing approaches.',\n staticEnums: {},\n example:\n '<Compare options={[{ name: \"Postgres\", pros: [\"ACID\"], cons: [\"ops\"], pick: true }, { name: \"SQLite\", pros: [\"simple\"], cons: [\"scale\"] }]} />',\n },\n {\n name: 'Callout',\n summary: 'A highlighted note/risk/decision/warning block. Wraps markdown.',\n staticEnums: { type: CALLOUT_TYPE_VALUES },\n example: '<Callout type=\"risk\">\\n Migration locks the table for ~2s.\\n</Callout>',\n },\n {\n name: 'Questions',\n summary:\n 'Open questions you (Claude) want the reader to weigh in on before building, as a highlighted panel.',\n staticEnums: {},\n example:\n '<Questions items={[\"Should refresh tokens rotate on every use?\", \"Is a 15-minute access-token TTL acceptable?\"]} />',\n },\n {\n name: 'Checklist',\n summary: 'Acceptance criteria / definition of done, with done and todo states.',\n staticEnums: {},\n example:\n '<Checklist title=\"Done when\" items={[{ text: \"Returns 429 over the limit\", done: true }, { text: \"Dashboards live\" }]} />',\n },\n {\n name: 'mermaid (code fence)',\n summary:\n 'A flowchart, sequence, state, class, ER, or XY-chart diagram. Write a ```mermaid fenced block. (gantt/pie are not supported by the renderer.)',\n staticEnums: {},\n example: '```mermaid\\nflowchart LR\\n A[Client] --> B[API] --> C[(DB)]\\n```',\n },\n]\n","import { CATALOG } from '@visualplan/core'\n\n/** `vplan components` — print the component vocabulary cheat-sheet. */\nexport function runComponents(): void {\n const lines: string[] = [\n 'VisualPlan components — use these directly in a plan .mdx (no imports):',\n '',\n ]\n for (const entry of CATALOG) {\n lines.push(`${entry.name}`)\n lines.push(` ${entry.summary}`)\n const enums = Object.entries(entry.staticEnums)\n for (const [prop, values] of enums) {\n lines.push(` ${prop}: ${values.join(' | ')}`)\n }\n lines.push(' example:')\n for (const exampleLine of entry.example.split('\\n')) {\n lines.push(` ${exampleLine}`)\n }\n lines.push('')\n }\n lines.push('Start the plan with a `# Title` heading. Do not use YAML frontmatter.')\n process.stdout.write(`${lines.join('\\n')}\\n`)\n}\n","import { basename, dirname, extname, join, resolve } from 'node:path'\nimport open from 'open'\nimport { checkPlan } from '../build/check.js'\nimport { renderToFile, startDevServer } from '../build/compile.js'\nimport { printIssues } from './check.js'\n\nexport interface RenderOptions {\n watch?: boolean\n out?: string\n open?: boolean\n}\n\nfunction defaultOutPath(absMdx: string): string {\n const stem = basename(absMdx, extname(absMdx))\n return join(dirname(absMdx), `${stem}.plan.html`)\n}\n\n/** `vplan render <file>` — validate, then build a static page or start a watch server. */\nexport async function runRender(file: string, options: RenderOptions): Promise<void> {\n const absMdx = resolve(file)\n\n const issues = await checkPlan(absMdx)\n if (issues.length > 0) {\n printIssues(file, issues)\n process.exitCode = 1\n return\n }\n\n if (options.watch) {\n const server = await startDevServer(absMdx)\n process.stdout.write(\n `VisualPlan watching ${file}\\n ${server.url}\\n (edit the file to hot-reload; Ctrl+C to stop)\\n`,\n )\n if (options.open !== false) await open(server.url)\n return\n }\n\n const out = options.out ? resolve(options.out) : defaultOutPath(absMdx)\n await renderToFile(absMdx, out)\n process.stdout.write(`Rendered ${out}\\n`)\n if (options.open !== false) await open(out)\n}\n","import { cp, mkdtemp, rm } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport mdx from '@mdx-js/rollup'\nimport rehypeExpressiveCode, { type RehypeExpressiveCodeOptions } from 'rehype-expressive-code'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport { build, createServer, type InlineConfig, type Plugin } from 'vite'\nimport { viteSingleFile } from 'vite-plugin-singlefile'\nimport { remarkMermaid } from './remark-mermaid.js'\n\nconst expressiveCodeOptions: RehypeExpressiveCodeOptions = {\n themes: ['github-dark', 'github-light'],\n useDarkModeMediaQuery: true,\n // The copy-button script does not execute reliably in our client-rendered SPA;\n // frames (titles) are CSS-only, so keep those and drop the interactive button.\n frames: { showCopyToClipboardButton: false },\n // Match the flat ink design: our borders/radius/surfaces/fonts, no shadow, no\n // colored tab accent. Values are CSS vars so the frame chrome tracks light/dark too.\n styleOverrides: {\n borderRadius: '10px',\n borderColor: 'var(--vp-border)',\n codeBackground: 'var(--vp-surface)',\n codeFontFamily: 'var(--vp-mono)',\n codeFontSize: '0.8rem',\n codeLineHeight: '1.6',\n codePaddingBlock: '0.9rem',\n codePaddingInline: '1rem',\n uiFontFamily: 'var(--vp-font)',\n uiFontSize: '0.78rem',\n frames: {\n frameBoxShadowCssValue: 'none',\n // A flat filename header on the same surface as the code, separated by one\n // border. No editor-tab metaphor, no colored indicator line.\n editorBackground: 'var(--vp-surface)',\n editorTabBarBackground: 'var(--vp-surface)',\n editorTabBarBorderBottomColor: 'var(--vp-border)',\n editorActiveTabBackground: 'var(--vp-surface)',\n editorActiveTabForeground: 'var(--vp-muted)',\n editorActiveTabBorderColor: 'transparent',\n editorActiveTabIndicatorTopColor: 'transparent',\n editorActiveTabIndicatorBottomColor: 'transparent',\n editorTabsMarginInlineStart: '0',\n terminalBackground: 'var(--vp-surface)',\n terminalTitlebarBackground: 'var(--vp-surface)',\n terminalTitlebarForeground: 'var(--vp-muted)',\n terminalTitlebarBorderBottomColor: 'var(--vp-border)',\n },\n },\n}\n\nconst require = createRequire(import.meta.url)\n\ninterface RuntimePaths {\n /** Directory Vite roots at: holds index.html plus the runtime source. */\n runtimeDir: string\n /** Core catalog source, aliased to `@visualplan/core` so the runtime import resolves. */\n coreEntry: string\n}\n\n/**\n * Locate the runtime source and the core catalog in both layouts:\n * - Published: both are vendored next to dist/ (`<pkg>/runtime`, `<pkg>/core`).\n * - Dev (workspace): they are sibling packages, resolved via node module resolution.\n * The core path is aliased to `@visualplan/core` in the Vite build, so the runtime's\n * import resolves identically whether the CLI is installed or run from the monorepo.\n */\nfunction findRuntimePaths(): RuntimePaths {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 6; depth++) {\n const runtimeDir = join(dir, 'runtime')\n const coreEntry = join(dir, 'core', 'index.ts')\n // Require BOTH vendored siblings: the monorepo also has a `runtime/index.html`\n // (under packages/), but its core lives at core/src/index.ts, so only the true\n // vendored layout has core/index.ts next to runtime/.\n if (existsSync(join(runtimeDir, 'index.html')) && existsSync(coreEntry)) {\n return { runtimeDir, coreEntry }\n }\n dir = dirname(dir)\n }\n const runtimeDir = dirname(require.resolve('@visualplan/runtime/package.json'))\n const coreDir = dirname(require.resolve('@visualplan/core/package.json'))\n return { runtimeDir, coreEntry: join(coreDir, 'src', 'index.ts') }\n}\n\nfunction mdxPlugin(): Plugin {\n return {\n enforce: 'pre',\n ...mdx({\n providerImportSource: '@mdx-js/react',\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n // Must run before rehype-expressive-code so mermaid never reaches the highlighter.\n remarkMermaid,\n ],\n rehypePlugins: [[rehypeExpressiveCode, expressiveCodeOptions]],\n }),\n }\n}\n\nfunction baseConfig(paths: RuntimePaths, mdxPath: string): InlineConfig {\n return {\n root: paths.runtimeDir,\n configFile: false,\n logLevel: 'silent',\n resolve: { alias: { 'virtual:plan': mdxPath, '@visualplan/core': paths.coreEntry } },\n esbuild: { jsx: 'automatic', jsxImportSource: 'react' },\n plugins: [mdxPlugin()],\n // The runtime, core, and the user's plan span sibling dirs (and a hoisted\n // node_modules) in the monorepo, so the dev server cannot use a single allow\n // root. This is a local tool rendering the user's own file, so fs is unrestricted.\n server: { fs: { strict: false }, open: false },\n }\n}\n\n/** Compile an MDX plan to a single self-contained HTML file at `outPath`. */\nexport async function renderToFile(mdxPath: string, outPath: string): Promise<void> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const outDir = await mkdtemp(join(tmpdir(), 'visualplan-build-'))\n try {\n const config = baseConfig(paths, absMdx)\n await build({\n ...config,\n plugins: [...(config.plugins ?? []), viteSingleFile()],\n build: {\n outDir,\n emptyOutDir: true,\n rollupOptions: { input: join(paths.runtimeDir, 'index.html') },\n },\n })\n await cp(join(outDir, 'index.html'), resolve(outPath))\n } finally {\n await rm(outDir, { recursive: true, force: true })\n }\n}\n\nexport interface DevServer {\n url: string\n close: () => Promise<void>\n}\n\n/** Start a hot-reloading dev server for an MDX plan and return its local URL. */\nexport async function startDevServer(mdxPath: string): Promise<DevServer> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const server = await createServer(baseConfig(paths, absMdx))\n await server.listen()\n const url =\n server.resolvedUrls?.local[0] ?? `http://localhost:${server.config.server.port ?? 5173}`\n return {\n url,\n close: () => server.close(),\n }\n}\n","import { visit } from 'unist-util-visit'\n\ninterface MdastCode {\n type: 'code'\n lang?: string | null\n value: string\n}\n\ninterface MdastParent {\n children: unknown[]\n}\n\n/**\n * Convert ```mermaid fenced code blocks into `<Mermaid chart=\"...\" />` MDX JSX\n * elements. This runs in the remark (mdast) stage, BEFORE rehype-expressive-code,\n * so the code highlighter never sees mermaid blocks and the diagram renders via the\n * Mermaid component instead.\n */\nexport function remarkMermaid() {\n return (tree: unknown) => {\n visit(\n tree as never,\n 'code',\n (node: MdastCode, index: number | undefined, parent: MdastParent | undefined) => {\n if (node.lang !== 'mermaid' || !parent || index === undefined) return\n parent.children[index] = {\n type: 'mdxJsxFlowElement',\n name: 'Mermaid',\n attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],\n children: [],\n }\n },\n )\n }\n}\n"],"mappings":"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","names":["dirname","join","resolve","resolve","remarkFrontmatter","remarkGfm","remarkMdxFrontmatter","visit","require","runtimeDir","remarkFrontmatter","remarkMdxFrontmatter","remarkGfm","resolve","join","dirname","resolve"]}
1
+ {"version":3,"sources":["../src/index.ts","../package.json","../src/commands/check.ts","../src/build/check.ts","../../core/src/index.ts","../src/commands/components.ts","../src/commands/render.ts","../src/build/compile.ts","../src/build/remark-mermaid.ts"],"sourcesContent":["import { Command } from 'commander'\nimport packageJson from '../package.json' with { type: 'json' }\nimport { runCheck } from './commands/check.js'\nimport { runComponents } from './commands/components.js'\nimport { runRender, type RenderOptions } from './commands/render.js'\n\nconst program = new Command('vplan')\n .description(\"Render an AI agent's plans as visual MDX pages instead of walls of text\")\n .version(packageJson.version)\n\nprogram\n .command('render', { isDefault: true })\n .description('Compile a plan .mdx to a self-contained HTML page (default command)')\n .argument('<file>', 'the plan .mdx file to render')\n .option('--watch', 'start a hot-reloading dev server instead of writing a file')\n .option('--out <path>', 'output HTML path (defaults to <file>.plan.html)')\n .option('--no-open', 'do not open the result in a browser')\n .action((file: string, options: RenderOptions) => runRender(file, options))\n\nprogram\n .command('check')\n .description('Validate a plan .mdx (compile + component checks) without rendering')\n .argument('<file>', 'the plan .mdx file to validate')\n .action((file: string) => runCheck(file))\n\nprogram\n .command('components')\n .description('Print the available plan components and their props')\n .action(() => runComponents())\n\nprogram.parseAsync(process.argv).catch((error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n process.exitCode = 1\n})\n","{\n \"name\": \"vplan\",\n \"version\": \"0.2.0\",\n \"description\": \"Render an AI agent's plans as visual MDX pages instead of walls of text\",\n \"author\": \"Brandon Burrus <brandon@burrus.io>\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/brandonburrus/visualplan.git\",\n \"directory\": \"packages/cli\"\n },\n \"homepage\": \"https://github.com/brandonburrus/visualplan#readme\",\n \"bugs\": \"https://github.com/brandonburrus/visualplan/issues\",\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=20.0.0\"\n },\n \"bin\": {\n \"vplan\": \"dist/index.js\"\n },\n \"files\": [\n \"dist\",\n \"runtime\",\n \"core\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsx src/index.ts\",\n \"typecheck\": \"tsc --noEmit\",\n \"vendor\": \"node scripts/vendor.mjs\",\n \"prepack\": \"node scripts/vendor.mjs && tsup\"\n },\n \"dependencies\": {\n \"@mdx-js/mdx\": \"^3.1.1\",\n \"@mdx-js/react\": \"^3.1.1\",\n \"@mdx-js/rollup\": \"^3.1.1\",\n \"@tabler/icons-react\": \"^3.44.0\",\n \"beautiful-mermaid\": \"^1.1.3\",\n \"commander\": \"^15.0.0\",\n \"open\": \"^11.0.0\",\n \"react\": \"^19.2.7\",\n \"react-dom\": \"^19.2.7\",\n \"recharts\": \"^3.8.1\",\n \"rehype-expressive-code\": \"^0.43.1\",\n \"remark-frontmatter\": \"^5.0.0\",\n \"remark-gfm\": \"^4.0.1\",\n \"remark-mdx\": \"^3.1.1\",\n \"remark-mdx-frontmatter\": \"^5.2.0\",\n \"remark-parse\": \"^11.0.0\",\n \"unified\": \"^11.0.5\",\n \"unist-util-visit\": \"^5.1.0\",\n \"vite\": \"^8.0.16\",\n \"vite-plugin-singlefile\": \"^2.3.3\",\n \"zod\": \"^4.4.3\"\n }\n}\n","import { resolve } from 'node:path'\nimport { type CheckIssue, checkPlan } from '../build/check.js'\n\n/** Print check issues in an editor-clickable `file:line:column message` format. */\nexport function printIssues(file: string, issues: CheckIssue[]): void {\n for (const issue of issues) {\n process.stderr.write(`${file}:${issue.line}:${issue.column} ${issue.message}\\n`)\n }\n}\n\n/** `vplan check <file>` — validate a plan's MDX without rendering it. */\nexport async function runCheck(file: string): Promise<void> {\n const issues = await checkPlan(resolve(file))\n if (issues.length === 0) {\n process.stdout.write(`${file} is valid\\n`)\n return\n }\n printIssues(file, issues)\n process.stdout.write(`\\n${issues.length} issue(s) found\\n`)\n process.exitCode = 1\n}\n","import { readFile } from 'node:fs/promises'\nimport { compile } from '@mdx-js/mdx'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport remarkMdx from 'remark-mdx'\nimport remarkParse from 'remark-parse'\nimport { unified } from 'unified'\nimport { visit } from 'unist-util-visit'\nimport { CATALOG } from '@visualplan/core'\n\nexport interface CheckIssue {\n line: number\n column: number\n message: string\n}\n\ninterface JsxAttribute {\n type: string\n name?: string\n value?: unknown\n}\n\ninterface JsxNode {\n type: string\n name?: string | null\n attributes?: JsxAttribute[]\n position?: { start: { line: number; column: number } }\n}\n\nconst COMPONENT_NAMES = CATALOG.filter(entry => /^[A-Z][A-Za-z0-9]*$/.test(entry.name)).map(\n entry => entry.name,\n)\n\nconst ENUMS_BY_COMPONENT = new Map(CATALOG.map(entry => [entry.name, entry.staticEnums]))\n\n/** Validate a plan's MDX: real compile errors plus static enum / unknown-component checks. */\nexport async function checkPlan(mdxPath: string): Promise<CheckIssue[]> {\n const source = await readFile(mdxPath, 'utf8')\n const issues: CheckIssue[] = []\n\n try {\n await compile(source, {\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n ],\n })\n } catch (error) {\n const vfileError = error as {\n line?: number\n column?: number\n place?: { line?: number; column?: number; start?: { line?: number; column?: number } }\n reason?: string\n message?: string\n }\n const message = vfileError.reason ?? vfileError.message ?? 'MDX failed to compile'\n // Some MDX errors (e.g. an unclosed JSX tag) carry no structured position; the\n // location is embedded in the message as \"(line:col-line:col)\". Fall back to the\n // `place` point, then to the message, so the file:line:col prefix is accurate.\n const placeStart = vfileError.place?.start ?? vfileError.place\n const fromMessage = message.match(/\\((\\d+):(\\d+)/)\n return [\n {\n line: vfileError.line ?? placeStart?.line ?? (fromMessage ? Number(fromMessage[1]) : 1),\n column:\n vfileError.column ?? placeStart?.column ?? (fromMessage ? Number(fromMessage[2]) : 1),\n message,\n },\n ]\n }\n\n const tree = unified().use(remarkParse).use(remarkFrontmatter).use(remarkMdx).parse(source)\n\n visit(tree, node => {\n const element = node as unknown as JsxNode\n if (element.type !== 'mdxJsxFlowElement' && element.type !== 'mdxJsxTextElement') return\n const name = element.name\n if (!name) return\n const at = element.position?.start ?? { line: 1, column: 1 }\n\n if (!COMPONENT_NAMES.includes(name)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `Unknown component <${name}>. Valid components: ${COMPONENT_NAMES.join(', ')}.`,\n })\n return\n }\n\n const enums = ENUMS_BY_COMPONENT.get(name) ?? {}\n for (const [prop, allowed] of Object.entries(enums)) {\n const attribute = element.attributes?.find(\n candidate => candidate.type === 'mdxJsxAttribute' && candidate.name === prop,\n )\n if (attribute && typeof attribute.value === 'string' && !allowed.includes(attribute.value)) {\n issues.push({\n line: at.line,\n column: at.column,\n message: `<${name}> prop ${prop}=\"${attribute.value}\" is invalid. Valid: ${allowed.join(', ')}.`,\n })\n }\n }\n })\n\n return issues\n}\n","import { z } from 'zod'\n\n/**\n * Single source of truth for the VisualPlan component vocabulary.\n *\n * This module is imported by BOTH the browser runtime (for render-time zod\n * validation) and the Node CLI (for static `check` and the `components`\n * catalog printer), so it must stay free of any React, recharts, or mermaid\n * imports. Keep it isomorphic.\n */\n\nexport const STATUS_VALUES = ['planned', 'active', 'done'] as const\nexport const CHANGE_VALUES = ['add', 'modify', 'delete', 'move'] as const\nexport const CHART_TYPE_VALUES = ['bar', 'line', 'pie'] as const\nexport const CALLOUT_TYPE_VALUES = ['note', 'risk', 'decision', 'warn'] as const\n\nexport const phaseSchema = z.object({\n title: z.string().min(1, 'title is required'),\n status: z.enum(STATUS_VALUES).default('planned'),\n})\n\nexport const fileTreeSchema = z.object({\n files: z\n .array(\n z.object({\n path: z.string().min(1, 'each file needs a path'),\n change: z.enum(CHANGE_VALUES),\n }),\n )\n .min(1, 'files must list at least one entry'),\n})\n\nexport const chartSchema = z.object({\n type: z.enum(CHART_TYPE_VALUES),\n title: z.string().optional(),\n data: z\n .array(z.object({ label: z.string(), value: z.number() }))\n .min(1, 'data must have at least one point'),\n})\n\nexport const compareSchema = z.object({\n options: z\n .array(\n z.object({\n name: z.string().min(1, 'each option needs a name'),\n pros: z.array(z.string()).default([]),\n cons: z.array(z.string()).default([]),\n pick: z.boolean().optional(),\n }),\n )\n .min(2, 'compare needs at least two options'),\n})\n\nexport const calloutSchema = z.object({\n type: z.enum(CALLOUT_TYPE_VALUES).default('note'),\n})\n\nexport const questionsSchema = z.object({\n title: z.string().default('Open questions'),\n items: z.array(z.string().min(1)).min(1, 'questions needs at least one item'),\n})\n\nexport const checklistSchema = z.object({\n title: z.string().optional(),\n items: z\n .array(\n z.object({\n text: z.string().min(1, 'each item needs text'),\n done: z.boolean().default(false),\n }),\n )\n .min(1, 'checklist needs at least one item'),\n})\n\n/** Describes a component for the `components` printer and the static checker. */\nexport interface CatalogEntry {\n name: string\n summary: string\n /** Props the CLI can statically validate from MDX source (string-literal enums). */\n staticEnums: Record<string, readonly string[]>\n example: string\n}\n\nexport const CATALOG: readonly CatalogEntry[] = [\n {\n name: 'Phase',\n summary: 'A collapsible plan stage with a status badge. Wraps markdown.',\n staticEnums: { status: STATUS_VALUES },\n example: '<Phase title=\"Build the API\" status=\"active\">\\n 1. Define routes\\n</Phase>',\n },\n {\n name: 'FileTree',\n summary:\n 'A nested directory tree of file changes, built from the paths, with add/modify/delete/move markers.',\n staticEnums: {},\n example:\n '<FileTree files={[{ path: \"src/api/routes.ts\", change: \"add\" }, { path: \"src/api/db.ts\", change: \"modify\" }, { path: \"src/legacy.ts\", change: \"delete\" }]} />',\n },\n {\n name: 'Chart',\n summary: 'A bar/line/pie chart for estimates or metrics.',\n staticEnums: { type: CHART_TYPE_VALUES },\n example:\n '<Chart type=\"bar\" title=\"Effort (days)\" data={[{ label: \"API\", value: 3 }, { label: \"UI\", value: 2 }]} />',\n },\n {\n name: 'Compare',\n summary: 'Side-by-side option cards for weighing approaches.',\n staticEnums: {},\n example:\n '<Compare options={[{ name: \"Postgres\", pros: [\"ACID\"], cons: [\"ops\"], pick: true }, { name: \"SQLite\", pros: [\"simple\"], cons: [\"scale\"] }]} />',\n },\n {\n name: 'Callout',\n summary: 'A highlighted note/risk/decision/warning block. Wraps markdown.',\n staticEnums: { type: CALLOUT_TYPE_VALUES },\n example: '<Callout type=\"risk\">\\n Migration locks the table for ~2s.\\n</Callout>',\n },\n {\n name: 'Questions',\n summary:\n 'Open questions you want the reader to weigh in on before building, as a highlighted panel. Title defaults to \"Open questions\"; override with title.',\n staticEnums: {},\n example:\n '<Questions items={[\"Should refresh tokens rotate on every use?\", \"Is a 15-minute access-token TTL acceptable?\"]} />',\n },\n {\n name: 'Checklist',\n summary: 'Acceptance criteria / definition of done, with done and todo states.',\n staticEnums: {},\n example:\n '<Checklist title=\"Done when\" items={[{ text: \"Returns 429 over the limit\", done: true }, { text: \"Dashboards live\" }]} />',\n },\n {\n name: 'mermaid (code fence)',\n summary:\n 'A flowchart, sequence, state, class, ER, or XY-chart diagram. Write a ```mermaid fenced block. (gantt/pie are not supported by the renderer.)',\n staticEnums: {},\n example: '```mermaid\\nflowchart LR\\n A[Client] --> B[API] --> C[(DB)]\\n```',\n },\n]\n","import { CATALOG } from '@visualplan/core'\n\n/** `vplan components` — print the component vocabulary cheat-sheet. */\nexport function runComponents(): void {\n const lines: string[] = [\n 'VisualPlan components — use these directly in a plan .mdx (no imports):',\n '',\n ]\n for (const entry of CATALOG) {\n lines.push(`${entry.name}`)\n lines.push(` ${entry.summary}`)\n const enums = Object.entries(entry.staticEnums)\n for (const [prop, values] of enums) {\n lines.push(` ${prop}: ${values.join(' | ')}`)\n }\n lines.push(' example:')\n for (const exampleLine of entry.example.split('\\n')) {\n lines.push(` ${exampleLine}`)\n }\n lines.push('')\n }\n lines.push('Start the plan with a `# Title` heading. Do not use YAML frontmatter.')\n process.stdout.write(`${lines.join('\\n')}\\n`)\n}\n","import { basename, dirname, extname, join, resolve } from 'node:path'\nimport open from 'open'\nimport { checkPlan } from '../build/check.js'\nimport { renderToFile, startDevServer } from '../build/compile.js'\nimport { printIssues } from './check.js'\n\nexport interface RenderOptions {\n watch?: boolean\n out?: string\n open?: boolean\n}\n\nfunction defaultOutPath(absMdx: string): string {\n const stem = basename(absMdx, extname(absMdx))\n return join(dirname(absMdx), `${stem}.plan.html`)\n}\n\n/** `vplan render <file>` — validate, then build a static page or start a watch server. */\nexport async function runRender(file: string, options: RenderOptions): Promise<void> {\n const absMdx = resolve(file)\n\n const issues = await checkPlan(absMdx)\n if (issues.length > 0) {\n printIssues(file, issues)\n process.exitCode = 1\n return\n }\n\n if (options.watch) {\n const server = await startDevServer(absMdx)\n process.stdout.write(\n `VisualPlan watching ${file}\\n ${server.url}\\n (edit the file to hot-reload; Ctrl+C to stop)\\n`,\n )\n if (options.open !== false) await open(server.url)\n return\n }\n\n const out = options.out ? resolve(options.out) : defaultOutPath(absMdx)\n await renderToFile(absMdx, out)\n process.stdout.write(`Rendered ${out}\\n`)\n if (options.open !== false) await open(out)\n}\n","import { cp, mkdtemp, rm } from 'node:fs/promises'\nimport { existsSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { tmpdir } from 'node:os'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport mdx from '@mdx-js/rollup'\nimport rehypeExpressiveCode, { type RehypeExpressiveCodeOptions } from 'rehype-expressive-code'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkMdxFrontmatter from 'remark-mdx-frontmatter'\nimport { build, createServer, type InlineConfig, type Plugin } from 'vite'\nimport { viteSingleFile } from 'vite-plugin-singlefile'\nimport { remarkMermaid } from './remark-mermaid.js'\n\nconst expressiveCodeOptions: RehypeExpressiveCodeOptions = {\n themes: ['github-dark', 'github-light'],\n useDarkModeMediaQuery: true,\n // The copy-button script does not execute reliably in our client-rendered SPA;\n // frames (titles) are CSS-only, so keep those and drop the interactive button.\n frames: { showCopyToClipboardButton: false },\n // Match the flat ink design: our borders/radius/surfaces/fonts, no shadow, no\n // colored tab accent. Values are CSS vars so the frame chrome tracks light/dark too.\n styleOverrides: {\n borderRadius: '10px',\n borderColor: 'var(--vp-border)',\n codeBackground: 'var(--vp-surface)',\n codeFontFamily: 'var(--vp-mono)',\n codeFontSize: '0.8rem',\n codeLineHeight: '1.6',\n codePaddingBlock: '0.9rem',\n codePaddingInline: '1rem',\n uiFontFamily: 'var(--vp-font)',\n uiFontSize: '0.78rem',\n frames: {\n frameBoxShadowCssValue: 'none',\n // A flat filename header on the same surface as the code, separated by one\n // border. No editor-tab metaphor, no colored indicator line.\n editorBackground: 'var(--vp-surface)',\n editorTabBarBackground: 'var(--vp-surface)',\n editorTabBarBorderBottomColor: 'var(--vp-border)',\n editorActiveTabBackground: 'var(--vp-surface)',\n editorActiveTabForeground: 'var(--vp-muted)',\n editorActiveTabBorderColor: 'transparent',\n editorActiveTabIndicatorTopColor: 'transparent',\n editorActiveTabIndicatorBottomColor: 'transparent',\n editorTabsMarginInlineStart: '0',\n terminalBackground: 'var(--vp-surface)',\n terminalTitlebarBackground: 'var(--vp-surface)',\n terminalTitlebarForeground: 'var(--vp-muted)',\n terminalTitlebarBorderBottomColor: 'var(--vp-border)',\n },\n },\n}\n\nconst require = createRequire(import.meta.url)\n\ninterface RuntimePaths {\n /** Directory Vite roots at: holds index.html plus the runtime source. */\n runtimeDir: string\n /** Core catalog source, aliased to `@visualplan/core` so the runtime import resolves. */\n coreEntry: string\n}\n\n/**\n * Locate the runtime source and the core catalog in both layouts:\n * - Published: both are vendored next to dist/ (`<pkg>/runtime`, `<pkg>/core`).\n * - Dev (workspace): they are sibling packages, resolved via node module resolution.\n * The core path is aliased to `@visualplan/core` in the Vite build, so the runtime's\n * import resolves identically whether the CLI is installed or run from the monorepo.\n */\nfunction findRuntimePaths(): RuntimePaths {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let depth = 0; depth < 6; depth++) {\n const runtimeDir = join(dir, 'runtime')\n const coreEntry = join(dir, 'core', 'index.ts')\n // Require BOTH vendored siblings: the monorepo also has a `runtime/index.html`\n // (under packages/), but its core lives at core/src/index.ts, so only the true\n // vendored layout has core/index.ts next to runtime/.\n if (existsSync(join(runtimeDir, 'index.html')) && existsSync(coreEntry)) {\n return { runtimeDir, coreEntry }\n }\n dir = dirname(dir)\n }\n const runtimeDir = dirname(require.resolve('@visualplan/runtime/package.json'))\n const coreDir = dirname(require.resolve('@visualplan/core/package.json'))\n return { runtimeDir, coreEntry: join(coreDir, 'src', 'index.ts') }\n}\n\nfunction mdxPlugin(): Plugin {\n return {\n enforce: 'pre',\n ...mdx({\n providerImportSource: '@mdx-js/react',\n remarkPlugins: [\n remarkFrontmatter,\n [remarkMdxFrontmatter, { name: 'frontmatter' }],\n remarkGfm,\n // Must run before rehype-expressive-code so mermaid never reaches the highlighter.\n remarkMermaid,\n ],\n rehypePlugins: [[rehypeExpressiveCode, expressiveCodeOptions]],\n }),\n }\n}\n\nfunction baseConfig(paths: RuntimePaths, mdxPath: string): InlineConfig {\n return {\n root: paths.runtimeDir,\n configFile: false,\n logLevel: 'silent',\n resolve: {\n alias: {\n 'virtual:plan': mdxPath,\n '@visualplan/core': paths.coreEntry,\n // The plan .mdx lives anywhere on disk, often outside any node project, but\n // @mdx-js/rollup makes it import react/jsx-runtime and @mdx-js/react. Those\n // are attributed to the plan's own directory, which usually has no\n // node_modules, so resolve them from the CLI's install instead. Without this\n // a plan in a bare directory fails with \"failed to resolve react/jsx-runtime\".\n 'react/jsx-runtime': require.resolve('react/jsx-runtime'),\n 'react/jsx-dev-runtime': require.resolve('react/jsx-dev-runtime'),\n '@mdx-js/react': require.resolve('@mdx-js/react'),\n },\n },\n esbuild: { jsx: 'automatic', jsxImportSource: 'react' },\n plugins: [mdxPlugin()],\n // The runtime, core, and the user's plan span sibling dirs (and a hoisted\n // node_modules) in the monorepo, so the dev server cannot use a single allow\n // root. This is a local tool rendering the user's own file, so fs is unrestricted.\n server: { fs: { strict: false }, open: false },\n }\n}\n\n/** Compile an MDX plan to a single self-contained HTML file at `outPath`. */\nexport async function renderToFile(mdxPath: string, outPath: string): Promise<void> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const outDir = await mkdtemp(join(tmpdir(), 'visualplan-build-'))\n try {\n const config = baseConfig(paths, absMdx)\n await build({\n ...config,\n plugins: [...(config.plugins ?? []), viteSingleFile()],\n build: {\n outDir,\n emptyOutDir: true,\n rollupOptions: { input: join(paths.runtimeDir, 'index.html') },\n },\n })\n await cp(join(outDir, 'index.html'), resolve(outPath))\n } finally {\n await rm(outDir, { recursive: true, force: true })\n }\n}\n\nexport interface DevServer {\n url: string\n close: () => Promise<void>\n}\n\n/** Start a hot-reloading dev server for an MDX plan and return its local URL. */\nexport async function startDevServer(mdxPath: string): Promise<DevServer> {\n const paths = findRuntimePaths()\n const absMdx = resolve(mdxPath)\n const server = await createServer(baseConfig(paths, absMdx))\n await server.listen()\n const url =\n server.resolvedUrls?.local[0] ?? `http://localhost:${server.config.server.port ?? 5173}`\n return {\n url,\n close: () => server.close(),\n }\n}\n","import { visit } from 'unist-util-visit'\n\ninterface MdastCode {\n type: 'code'\n lang?: string | null\n value: string\n}\n\ninterface MdastParent {\n children: unknown[]\n}\n\n/**\n * Convert ```mermaid fenced code blocks into `<Mermaid chart=\"...\" />` MDX JSX\n * elements. This runs in the remark (mdast) stage, BEFORE rehype-expressive-code,\n * so the code highlighter never sees mermaid blocks and the diagram renders via the\n * Mermaid component instead.\n */\nexport function remarkMermaid() {\n return (tree: unknown) => {\n visit(\n tree as never,\n 'code',\n (node: MdastCode, index: number | undefined, parent: MdastParent | undefined) => {\n if (node.lang !== 'mermaid' || !parent || index === undefined) return\n parent.children[index] = {\n type: 'mdxJsxFlowElement',\n name: 'Mermaid',\n attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],\n children: [],\n }\n },\n )\n }\n}\n"],"mappings":"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","names":["dirname","join","resolve","resolve","remarkFrontmatter","remarkGfm","remarkMdxFrontmatter","visit","require","runtimeDir","remarkFrontmatter","remarkMdxFrontmatter","remarkGfm","resolve","join","dirname","resolve"]}
package/package.json CHANGED
@@ -1,21 +1,35 @@
1
1
  {
2
2
  "name": "vplan",
3
- "version": "0.1.0",
4
- "description": "Render Claude's plans as visual MDX pages instead of walls of text",
3
+ "version": "0.2.0",
4
+ "description": "Render an AI agent's plans as visual MDX pages instead of walls of text",
5
5
  "author": "Brandon Burrus <brandon@burrus.io>",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/brandonburrus/visualplan.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/brandonburrus/visualplan#readme",
13
+ "bugs": "https://github.com/brandonburrus/visualplan/issues",
7
14
  "type": "module",
8
15
  "engines": {
9
16
  "node": ">=20.0.0"
10
17
  },
11
18
  "bin": {
12
- "vplan": "./dist/index.js"
19
+ "vplan": "dist/index.js"
13
20
  },
14
21
  "files": [
15
22
  "dist",
16
23
  "runtime",
17
24
  "core"
18
25
  ],
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsx src/index.ts",
29
+ "typecheck": "tsc --noEmit",
30
+ "vendor": "node scripts/vendor.mjs",
31
+ "prepack": "node scripts/vendor.mjs && tsup"
32
+ },
19
33
  "dependencies": {
20
34
  "@mdx-js/mdx": "^3.1.1",
21
35
  "@mdx-js/react": "^3.1.1",
@@ -38,15 +52,5 @@
38
52
  "vite": "^8.0.16",
39
53
  "vite-plugin-singlefile": "^2.3.3",
40
54
  "zod": "^4.4.3"
41
- },
42
- "devDependencies": {
43
- "@visualplan/core": "0.1.0",
44
- "@visualplan/runtime": "0.1.0"
45
- },
46
- "scripts": {
47
- "build": "tsup",
48
- "dev": "tsx src/index.ts",
49
- "typecheck": "tsc --noEmit",
50
- "vendor": "node scripts/vendor.mjs"
51
55
  }
52
- }
56
+ }
@@ -35,6 +35,14 @@ const COLORS = [
35
35
  ]
36
36
 
37
37
  const AXIS_TICK = { fill: 'var(--vp-muted)', fontSize: 12 }
38
+
39
+ /** Compact large axis numbers (100000 -> 100k) so y-axis ticks never clip. */
40
+ function formatTick(value: number): string {
41
+ const abs = Math.abs(value)
42
+ if (abs >= 1_000_000) return `${+(value / 1_000_000).toFixed(1)}M`
43
+ if (abs >= 1_000) return `${+(value / 1_000).toFixed(1)}k`
44
+ return `${value}`
45
+ }
38
46
  const TOOLTIP_STYLE = {
39
47
  background: 'var(--vp-surface)',
40
48
  border: '1px solid var(--vp-border)',
@@ -55,9 +63,14 @@ export function Chart(props: ChartProps) {
55
63
  <BarChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: -16 }}>
56
64
  <CartesianGrid strokeDasharray='3 3' stroke='var(--vp-border)' vertical={false} />
57
65
  <XAxis dataKey='label' tick={AXIS_TICK} stroke='var(--vp-border)' />
58
- <YAxis allowDecimals tick={AXIS_TICK} stroke='var(--vp-border)' />
66
+ <YAxis
67
+ allowDecimals
68
+ tick={AXIS_TICK}
69
+ stroke='var(--vp-border)'
70
+ tickFormatter={formatTick}
71
+ />
59
72
  <Tooltip cursor={{ fill: 'var(--vp-surface-2)' }} contentStyle={TOOLTIP_STYLE} />
60
- <Bar dataKey='value' radius={[4, 4, 0, 0]} maxBarSize={64}>
73
+ <Bar dataKey='value' radius={[4, 4, 0, 0]} maxBarSize={64} isAnimationActive={false}>
61
74
  {data.map((point, index) => (
62
75
  <Cell key={point.label} fill={COLORS[index % COLORS.length]} />
63
76
  ))}
@@ -67,7 +80,12 @@ export function Chart(props: ChartProps) {
67
80
  <LineChart data={data} margin={{ top: 4, right: 8, bottom: 0, left: -16 }}>
68
81
  <CartesianGrid strokeDasharray='3 3' stroke='var(--vp-border)' vertical={false} />
69
82
  <XAxis dataKey='label' tick={AXIS_TICK} stroke='var(--vp-border)' />
70
- <YAxis allowDecimals tick={AXIS_TICK} stroke='var(--vp-border)' />
83
+ <YAxis
84
+ allowDecimals
85
+ tick={AXIS_TICK}
86
+ stroke='var(--vp-border)'
87
+ tickFormatter={formatTick}
88
+ />
71
89
  <Tooltip contentStyle={TOOLTIP_STYLE} />
72
90
  <Line
73
91
  type='monotone'
@@ -75,12 +93,20 @@ export function Chart(props: ChartProps) {
75
93
  stroke={COLORS[0]}
76
94
  strokeWidth={2.5}
77
95
  dot={{ fill: COLORS[0], r: 3 }}
96
+ isAnimationActive={false}
78
97
  />
79
98
  </LineChart>
80
99
  ) : (
81
100
  <PieChart>
82
101
  <Tooltip contentStyle={TOOLTIP_STYLE} />
83
- <Pie data={data} dataKey='value' nameKey='label' outerRadius={88} stroke='none'>
102
+ <Pie
103
+ data={data}
104
+ dataKey='value'
105
+ nameKey='label'
106
+ outerRadius={88}
107
+ stroke='none'
108
+ isAnimationActive={false}
109
+ >
84
110
  {data.map((point, index) => (
85
111
  <Cell key={point.label} fill={COLORS[index % COLORS.length]} />
86
112
  ))}
@@ -1,20 +1,17 @@
1
- import { IconHelpCircle } from '@tabler/icons-react'
2
1
  import { questionsSchema } from '@visualplan/core'
3
2
  import { validateProps } from './validate.js'
4
3
 
5
4
  interface QuestionsProps {
5
+ title?: string
6
6
  items: string[]
7
7
  }
8
8
 
9
9
  /** Open questions for the reader to resolve before building, as a highlighted panel. */
10
10
  export function Questions(props: QuestionsProps) {
11
- const { items } = validateProps('Questions', questionsSchema, props)
11
+ const { title, items } = validateProps('Questions', questionsSchema, props)
12
12
  return (
13
13
  <section className='vp-questions'>
14
- <div className='vp-questions__head'>
15
- <IconHelpCircle size={16} stroke={2} className='vp-questions__icon' aria-hidden='true' />
16
- <span className='vp-questions__title'>Open questions</span>
17
- </div>
14
+ <div className='vp-questions__title'>{title}</div>
18
15
  <ol className='vp-questions__list'>
19
16
  {items.map((item, index) => (
20
17
  <li key={item} className='vp-questions__item'>
package/runtime/theme.css CHANGED
@@ -9,6 +9,7 @@
9
9
  --vp-border-strong: #d6d6d2;
10
10
  --vp-accent: #1a1a1c;
11
11
  --vp-on-accent: #fbfbfa;
12
+ --vp-link: #2f6fed;
12
13
  --vp-done: #15803d;
13
14
  --vp-modify: #b45309;
14
15
  --vp-risk: #b91c1c;
@@ -21,12 +22,12 @@
21
22
  --vp-question: #2f6fed;
22
23
  --vp-question-tint: #eef3fe;
23
24
  --vp-question-border: #d4e0fb;
24
- --vp-note: #7c3aed;
25
- --vp-note-tint: #f6f2fe;
26
- --vp-note-border: #e4d8fb;
27
- --vp-decision: #0d9488;
28
- --vp-decision-tint: #ecfbf7;
29
- --vp-decision-border: #c2ebe2;
25
+ --vp-note: #2f6fed;
26
+ --vp-note-tint: #eef3fe;
27
+ --vp-note-border: #d4e0fb;
28
+ --vp-decision: #7c3aed;
29
+ --vp-decision-tint: #f6f2fe;
30
+ --vp-decision-border: #e4d8fb;
30
31
  --vp-ease: cubic-bezier(0.25, 1, 0.5, 1);
31
32
  --vp-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
32
33
  --vp-mono: "SF Mono", ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
@@ -44,6 +45,7 @@
44
45
  --vp-border-strong: #3a3a3f;
45
46
  --vp-accent: #e9e9e6;
46
47
  --vp-on-accent: #161618;
48
+ --vp-link: #7aa2ff;
47
49
  --vp-done: #45c97a;
48
50
  --vp-modify: #d99a3a;
49
51
  --vp-risk: #ef8f8f;
@@ -56,12 +58,12 @@
56
58
  --vp-question: #7aa2ff;
57
59
  --vp-question-tint: #161d2e;
58
60
  --vp-question-border: #2b3a5e;
59
- --vp-note: #b794f6;
60
- --vp-note-tint: #1d1830;
61
- --vp-note-border: #362a52;
62
- --vp-decision: #2dd4bf;
63
- --vp-decision-tint: #0e2421;
64
- --vp-decision-border: #1d433c;
61
+ --vp-note: #7aa2ff;
62
+ --vp-note-tint: #161d2e;
63
+ --vp-note-border: #2b3a5e;
64
+ --vp-decision: #b794f6;
65
+ --vp-decision-tint: #1d1830;
66
+ --vp-decision-border: #362a52;
65
67
  }
66
68
  }
67
69
 
@@ -118,11 +120,54 @@ body {
118
120
  }
119
121
 
120
122
  .vp-main a {
121
- color: var(--vp-accent);
122
- text-decoration-color: var(--vp-border-strong);
123
+ color: var(--vp-link);
124
+ text-decoration-color: var(--vp-link);
125
+ text-decoration-thickness: 1px;
123
126
  text-underline-offset: 2px;
124
127
  }
125
128
 
129
+ /* GFM tables: row-line style (no outer box, no zebra), header in muted. */
130
+ .vp-main table {
131
+ width: 100%;
132
+ border-collapse: collapse;
133
+ margin: 1.25rem 0;
134
+ font-size: 0.9rem;
135
+ }
136
+
137
+ .vp-main th,
138
+ .vp-main td {
139
+ padding: 0.5rem 0.85rem;
140
+ text-align: left;
141
+ vertical-align: top;
142
+ border-bottom: 1px solid var(--vp-border);
143
+ }
144
+
145
+ .vp-main thead th {
146
+ font-weight: 600;
147
+ color: var(--vp-muted);
148
+ border-bottom-color: var(--vp-border-strong);
149
+ }
150
+
151
+ .vp-main tbody tr:last-child td {
152
+ border-bottom: none;
153
+ }
154
+
155
+ /* Blockquote: a neutral (non-semantic) quote, not a colored callout. */
156
+ .vp-main blockquote {
157
+ margin: 1.25rem 0;
158
+ padding: 0.1rem 0 0.1rem 1rem;
159
+ border-left: 2px solid var(--vp-border-strong);
160
+ color: var(--vp-muted);
161
+ }
162
+
163
+ .vp-main blockquote :first-child {
164
+ margin-top: 0;
165
+ }
166
+
167
+ .vp-main blockquote :last-child {
168
+ margin-bottom: 0;
169
+ }
170
+
126
171
  :focus-visible {
127
172
  outline: 2px solid var(--vp-accent);
128
173
  outline-offset: 2px;
@@ -233,9 +278,10 @@ body {
233
278
 
234
279
  .vp-phase__content {
235
280
  min-width: 0;
236
- padding-top: 0.15rem;
237
281
  }
238
282
 
283
+ /* The head matches the node height and centers its content, so the title sits on the
284
+ node's center line (both columns start at the row top). */
239
285
  .vp-phase__head {
240
286
  display: flex;
241
287
  align-items: center;
@@ -448,7 +494,7 @@ body {
448
494
  }
449
495
 
450
496
  /* Questions: open questions for the reader. Neutral card like the other list cards,
451
- with blue kept only as the accent on the icon, title, and numbers. */
497
+ with blue kept only as the accent on the item numbers. */
452
498
  .vp-questions {
453
499
  border: 1px solid var(--vp-border);
454
500
  background: var(--vp-surface);
@@ -457,28 +503,14 @@ body {
457
503
  margin: 1.4rem 0;
458
504
  }
459
505
 
460
- .vp-questions__head {
461
- display: flex;
462
- align-items: center;
463
- gap: 0.6rem;
464
- margin-bottom: 0.6rem;
465
- }
466
-
467
- /* The header icon shares a fixed-width column with the item numbers below, so the
468
- title and every question text line up on the same left edge. */
469
- .vp-questions__icon {
470
- flex: 0 0 auto;
471
- width: 1.15rem;
472
- display: inline-flex;
473
- align-items: center;
474
- justify-content: center;
475
- color: var(--vp-question);
476
- }
477
-
506
+ /* Small uppercase label, identical to the Checklist title. */
478
507
  .vp-questions__title {
508
+ font-size: 0.68rem;
479
509
  font-weight: 600;
480
- font-size: 0.9rem;
481
- color: var(--vp-text);
510
+ text-transform: uppercase;
511
+ letter-spacing: 0.05em;
512
+ color: var(--vp-muted);
513
+ margin-bottom: 0.55rem;
482
514
  }
483
515
 
484
516
  .vp-questions__list {
@@ -513,7 +545,10 @@ body {
513
545
  /* Compare */
514
546
  .vp-compare {
515
547
  display: grid;
516
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
548
+ /* Cards stack once the column is too narrow for two comfortably (narrow windows),
549
+ while keeping the desktop multi-column layout. min(100%, ...) avoids overflow
550
+ on ultra-narrow screens. */
551
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 15.5rem), 1fr));
517
552
  gap: 0.85rem;
518
553
  margin: 1.25rem 0;
519
554
  }
@@ -567,15 +602,19 @@ body {
567
602
  margin: 0.25rem 0;
568
603
  }
569
604
 
570
- .vp-compare__pro {
605
+ /* Center the 15px icon on the first text line (same technique as the Checklist),
606
+ so it stays aligned whether the item is one line or wraps to several. */
607
+ .vp-compare__pro,
608
+ .vp-compare__con {
571
609
  flex: 0 0 auto;
572
- margin-top: 0.12rem;
610
+ margin-top: calc((1lh - 15px) / 2);
611
+ }
612
+
613
+ .vp-compare__pro {
573
614
  color: var(--vp-done);
574
615
  }
575
616
 
576
617
  .vp-compare__con {
577
- flex: 0 0 auto;
578
- margin-top: 0.12rem;
579
618
  color: var(--vp-risk);
580
619
  }
581
620