@infinitedusky/indusk-mcp 0.8.5 → 0.9.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/dist/bin/cli.js CHANGED
@@ -33,6 +33,13 @@ program
33
33
  const { update } = await import("./commands/update.js");
34
34
  await update(process.cwd());
35
35
  });
36
+ program
37
+ .command("init-docs")
38
+ .description("Scaffold a VitePress documentation site with Mermaid, llms.txt, and FullscreenDiagram")
39
+ .action(async () => {
40
+ const { initDocs } = await import("./commands/init-docs.js");
41
+ await initDocs(process.cwd());
42
+ });
36
43
  program
37
44
  .command("check-gates")
38
45
  .description("Validate plan execution gates — reports incomplete verification, context, and document items")
@@ -0,0 +1 @@
1
+ export declare function initDocs(projectRoot: string): Promise<void>;
@@ -0,0 +1,266 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { basename, join } from "node:path";
4
+ export async function initDocs(projectRoot) {
5
+ const projectName = basename(projectRoot);
6
+ const docsDir = join(projectRoot, `apps/${projectName}-docs`);
7
+ if (existsSync(docsDir)) {
8
+ console.info(`Docs app already exists at apps/${projectName}-docs/`);
9
+ console.info("Run 'update' to sync templates.");
10
+ return;
11
+ }
12
+ console.info(`Scaffolding docs site at apps/${projectName}-docs/\n`);
13
+ // Create directory structure
14
+ const dirs = [
15
+ "src/.vitepress/components",
16
+ "src/.vitepress/theme",
17
+ "src/guide",
18
+ "src/reference/skills",
19
+ "src/reference/tools",
20
+ "src/decisions",
21
+ "src/lessons",
22
+ ];
23
+ for (const dir of dirs) {
24
+ mkdirSync(join(docsDir, dir), { recursive: true });
25
+ }
26
+ // package.json
27
+ writeFileSync(join(docsDir, "package.json"), JSON.stringify({
28
+ name: `${projectName}-docs`,
29
+ version: "0.1.0",
30
+ private: true,
31
+ type: "module",
32
+ scripts: {
33
+ dev: "vitepress dev src --port 4173",
34
+ build: "vitepress build src",
35
+ preview: "vitepress preview src",
36
+ },
37
+ devDependencies: {
38
+ mermaid: "^10.2.2",
39
+ panzoom: "^9.4.3",
40
+ vitepress: "^1.6.3",
41
+ "vitepress-plugin-llms": "^1.12.0",
42
+ "vitepress-plugin-mermaid": "^2.0.10",
43
+ vue: "^3.4.15",
44
+ },
45
+ }, null, "\t") + "\n");
46
+ // .vitepress/config.ts
47
+ writeFileSync(join(docsDir, "src/.vitepress/config.ts"), `import { defineConfig } from "vitepress";
48
+ import llmstxt from "vitepress-plugin-llms";
49
+ import { withMermaid } from "vitepress-plugin-mermaid";
50
+
51
+ const config = defineConfig({
52
+ title: "${projectName}",
53
+ description: "Documentation for ${projectName}",
54
+ base: "/",
55
+ lastUpdated: true,
56
+ cleanUrls: true,
57
+ ignoreDeadLinks: true,
58
+
59
+ markdown: {
60
+ lineNumbers: true,
61
+ },
62
+
63
+ mermaid: {
64
+ theme: "default",
65
+ securityLevel: "strict",
66
+ maxTextSize: 50000,
67
+ flowchart: {
68
+ useMaxWidth: true,
69
+ htmlLabels: true,
70
+ },
71
+ sequence: {
72
+ actorFontWeight: "bold",
73
+ messageFontSize: 14,
74
+ actorFontSize: 14,
75
+ },
76
+ },
77
+
78
+ themeConfig: {
79
+ search: {
80
+ provider: "local",
81
+ },
82
+
83
+ nav: [
84
+ { text: "Guide", link: "/guide/" },
85
+ { text: "Reference", link: "/reference/" },
86
+ { text: "Decisions", link: "/decisions/" },
87
+ { text: "Lessons", link: "/lessons/" },
88
+ ],
89
+
90
+ sidebar: {
91
+ "/guide/": [
92
+ {
93
+ text: "Guide",
94
+ items: [
95
+ { text: "Overview", link: "/guide/" },
96
+ { text: "Getting Started", link: "/guide/getting-started" },
97
+ ],
98
+ },
99
+ ],
100
+ "/reference/": [
101
+ {
102
+ text: "Reference",
103
+ items: [
104
+ { text: "Overview", link: "/reference/" },
105
+ ],
106
+ },
107
+ ],
108
+ "/decisions/": [
109
+ {
110
+ text: "Architecture Decisions",
111
+ items: [{ text: "Overview", link: "/decisions/" }],
112
+ },
113
+ ],
114
+ "/lessons/": [
115
+ {
116
+ text: "Lessons Learned",
117
+ items: [{ text: "Overview", link: "/lessons/" }],
118
+ },
119
+ ],
120
+ },
121
+ },
122
+
123
+ vite: {
124
+ plugins: [llmstxt()],
125
+ server: {
126
+ allowedHosts: [".orb.local"],
127
+ },
128
+ optimizeDeps: {
129
+ include: ["mermaid"],
130
+ },
131
+ ssr: {
132
+ noExternal: ["mermaid"],
133
+ },
134
+ },
135
+ });
136
+
137
+ export default withMermaid(config);
138
+ `);
139
+ // .vitepress/theme/index.ts
140
+ writeFileSync(join(docsDir, "src/.vitepress/theme/index.ts"), `import type { Theme } from "vitepress";
141
+ import DefaultTheme from "vitepress/theme";
142
+ import FullscreenDiagram from "../components/FullscreenDiagram.vue";
143
+
144
+ export default {
145
+ extends: DefaultTheme,
146
+ enhanceApp({ app }) {
147
+ app.component("FullscreenDiagram", FullscreenDiagram);
148
+ },
149
+ } satisfies Theme;
150
+ `);
151
+ // FullscreenDiagram.vue — copy from templates
152
+ const fullscreenDiagramPath = join(docsDir, "src/.vitepress/components/FullscreenDiagram.vue");
153
+ const templateComponent = join(projectRoot, "apps/indusk-mcp/templates/FullscreenDiagram.vue");
154
+ // If we have the template in the package, use it; otherwise inline a minimal version
155
+ if (existsSync(templateComponent)) {
156
+ const content = readFileSync(templateComponent, "utf-8");
157
+ writeFileSync(fullscreenDiagramPath, content);
158
+ }
159
+ else {
160
+ // Use the bundled template from the package
161
+ const { dirname } = await import("node:path");
162
+ const { fileURLToPath } = await import("node:url");
163
+ const __dirname = dirname(fileURLToPath(import.meta.url));
164
+ const packageRoot = join(__dirname, "../../..");
165
+ const bundledTemplate = join(packageRoot, "templates/FullscreenDiagram.vue");
166
+ if (existsSync(bundledTemplate)) {
167
+ const content = readFileSync(bundledTemplate, "utf-8");
168
+ writeFileSync(fullscreenDiagramPath, content);
169
+ }
170
+ else {
171
+ writeFileSync(fullscreenDiagramPath, `<template>
172
+ <div class="diagram-container">
173
+ <div class="diagram"><slot></slot></div>
174
+ </div>
175
+ </template>
176
+
177
+ <style scoped>
178
+ .diagram-container { position: relative; width: 100%; }
179
+ .diagram { width: 100%; padding: 1rem; border-radius: 8px; border: 1px solid var(--vp-c-divider); background: var(--vp-c-bg-soft); }
180
+ </style>
181
+ `);
182
+ console.info(" warn: FullscreenDiagram created with minimal template (panzoom not included)");
183
+ }
184
+ }
185
+ // Starter pages
186
+ writeFileSync(join(docsDir, "src/index.md"), `---
187
+ layout: home
188
+ hero:
189
+ name: ${projectName}
190
+ tagline: Project documentation
191
+ actions:
192
+ - theme: brand
193
+ text: Get Started
194
+ link: /guide/getting-started
195
+ - theme: alt
196
+ text: Reference
197
+ link: /reference/
198
+ ---
199
+ `);
200
+ writeFileSync(join(docsDir, "src/guide/index.md"), `# Guide
201
+
202
+ How-to guides for working with ${projectName}.
203
+
204
+ - [Getting Started](/guide/getting-started) — set up the project and start developing
205
+ `);
206
+ writeFileSync(join(docsDir, "src/guide/getting-started.md"), `# Getting Started
207
+
208
+ ## Prerequisites
209
+
210
+ - Node.js 22+
211
+ - pnpm
212
+
213
+ ## Setup
214
+
215
+ \`\`\`bash
216
+ pnpm install
217
+ \`\`\`
218
+
219
+ ## Next Steps
220
+
221
+ Start building!
222
+ `);
223
+ writeFileSync(join(docsDir, "src/reference/index.md"), `# Reference
224
+
225
+ Reference documentation for ${projectName}.
226
+ `);
227
+ writeFileSync(join(docsDir, "src/decisions/index.md"), `# Architecture Decisions
228
+
229
+ Architecture decision records for ${projectName}. Each decision documents what was chosen, what was rejected, and why.
230
+ `);
231
+ writeFileSync(join(docsDir, "src/lessons/index.md"), `# Lessons Learned
232
+
233
+ Insights from building ${projectName}. Each lesson captures what we learned, what surprised us, and what we'd do differently.
234
+ `);
235
+ console.info(" created: package.json");
236
+ console.info(" created: .vitepress/config.ts (mermaid + llms plugin)");
237
+ console.info(" created: .vitepress/theme/index.ts");
238
+ console.info(" created: .vitepress/components/FullscreenDiagram.vue");
239
+ console.info(" created: starter pages (index, guide, reference, decisions, lessons)");
240
+ // Check if pnpm-workspace.yaml includes this app
241
+ const workspacePath = join(projectRoot, "pnpm-workspace.yaml");
242
+ if (existsSync(workspacePath)) {
243
+ const workspace = readFileSync(workspacePath, "utf-8");
244
+ if (!workspace.includes("apps/*") && !workspace.includes(`apps/${projectName}-docs`)) {
245
+ console.info(`\n note: add 'apps/${projectName}-docs' to pnpm-workspace.yaml if not covered by a glob`);
246
+ }
247
+ }
248
+ // Install dependencies
249
+ console.info("\n[Installing dependencies]");
250
+ try {
251
+ execSync("pnpm install", {
252
+ cwd: projectRoot,
253
+ stdio: "inherit",
254
+ timeout: 120000,
255
+ });
256
+ console.info(" done: dependencies installed");
257
+ }
258
+ catch {
259
+ console.info(" warn: pnpm install failed — run manually");
260
+ }
261
+ console.info(`\nDocs site ready at apps/${projectName}-docs/`);
262
+ console.info("\nNext steps:");
263
+ console.info(` 1. pnpm turbo dev --filter=${projectName}-docs`);
264
+ console.info(" 2. Edit src/guide/getting-started.md with your setup instructions");
265
+ console.info(" 3. Add reference pages as you build features");
266
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infinitedusky/indusk-mcp",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "description": "InDusk development system — skills, MCP tools, and CLI for structured AI-assisted development",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,279 @@
1
+ <template>
2
+ <div class="diagram-container">
3
+ <div class="diagram" ref="diagramRef">
4
+ <slot></slot>
5
+ </div>
6
+ <button class="expand-btn" @click="toggleExpand" :title="isExpanded ? 'Close' : 'Expand'">
7
+ <svg v-if="!isExpanded" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
8
+ <path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/>
9
+ </svg>
10
+ <svg v-else xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
11
+ <path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
12
+ </svg>
13
+ </button>
14
+
15
+ <Teleport to="body">
16
+ <div v-if="isExpanded" class="modal-overlay" @click.self="toggleExpand">
17
+ <div class="modal-content">
18
+ <div class="expanded-diagram">
19
+ <div ref="expandedDiagramRef" v-html="diagramHTML"></div>
20
+ </div>
21
+ <div class="zoom-controls">
22
+ <button @click="zoomOut" :disabled="zoom <= 0.05" title="Zoom Out">
23
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
24
+ <path fill="currentColor" d="M19 13H5v-2h14v2z"/>
25
+ </svg>
26
+ </button>
27
+ <span class="zoom-level">{{ Math.round(zoom * 100) }}%</span>
28
+ <button @click="zoomIn" :disabled="zoom >= 3" title="Zoom In">
29
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
30
+ <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
31
+ </svg>
32
+ </button>
33
+ <button @click="resetZoom" title="Reset Zoom">
34
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
35
+ <path fill="currentColor" d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
36
+ </svg>
37
+ </button>
38
+ </div>
39
+ <button class="close-btn" @click="toggleExpand">
40
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
41
+ <path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
42
+ </svg>
43
+ </button>
44
+ </div>
45
+ </div>
46
+ </Teleport>
47
+ </div>
48
+ </template>
49
+
50
+ <script setup>
51
+ import panzoom from "panzoom";
52
+ import { nextTick, ref, watch } from "vue";
53
+
54
+ const isExpanded = ref(false);
55
+ const diagramRef = ref(null);
56
+ const expandedDiagramRef = ref(null);
57
+ const zoom = ref(1);
58
+ const panzoomInstance = ref(null);
59
+ const diagramHTML = ref("");
60
+
61
+ const cloneDiagram = () => {
62
+ if (!diagramRef.value) return;
63
+ diagramHTML.value = diagramRef.value.innerHTML;
64
+ };
65
+
66
+ const toggleExpand = () => {
67
+ if (!isExpanded.value) {
68
+ cloneDiagram();
69
+ }
70
+ isExpanded.value = !isExpanded.value;
71
+ if (isExpanded.value) {
72
+ document.body.style.overflow = "hidden";
73
+ nextTick(() => {
74
+ if (expandedDiagramRef.value) {
75
+ const svgElement =
76
+ expandedDiagramRef.value.querySelector("svg") || expandedDiagramRef.value.firstChild;
77
+ if (svgElement) {
78
+ panzoomInstance.value = panzoom(svgElement, {
79
+ bounds: true,
80
+ boundsPadding: 0.1,
81
+ minZoom: 0.05,
82
+ maxZoom: 10,
83
+ });
84
+ panzoomInstance.value.on("zoom", () => {
85
+ zoom.value = panzoomInstance.value.getTransform().scale;
86
+ });
87
+ }
88
+ }
89
+ });
90
+ } else {
91
+ document.body.style.overflow = "";
92
+ if (panzoomInstance.value) {
93
+ panzoomInstance.value.dispose();
94
+ panzoomInstance.value = null;
95
+ }
96
+ resetZoom();
97
+ }
98
+ };
99
+
100
+ const zoomIn = () => {
101
+ if (panzoomInstance.value) {
102
+ const currentZoom = panzoomInstance.value.getTransform().scale;
103
+ if (currentZoom < 10) {
104
+ panzoomInstance.value.smoothZoom(0, 0, 1.1);
105
+ }
106
+ }
107
+ };
108
+
109
+ const zoomOut = () => {
110
+ if (panzoomInstance.value) {
111
+ const currentZoom = panzoomInstance.value.getTransform().scale;
112
+ if (currentZoom > 0.05) {
113
+ panzoomInstance.value.smoothZoom(0, 0, 0.9);
114
+ }
115
+ }
116
+ };
117
+
118
+ const resetZoom = () => {
119
+ if (panzoomInstance.value) {
120
+ panzoomInstance.value.zoomAbs(0, 0, 1);
121
+ panzoomInstance.value.moveTo(0, 0);
122
+ }
123
+ };
124
+
125
+ watch(isExpanded, (newValue) => {
126
+ if (!newValue) {
127
+ resetZoom();
128
+ }
129
+ });
130
+ </script>
131
+
132
+ <style scoped>
133
+ .diagram-container {
134
+ position: relative;
135
+ width: 100%;
136
+ }
137
+
138
+ .diagram {
139
+ width: 100%;
140
+ padding: 1rem;
141
+ border-radius: 8px;
142
+ border: 1px solid var(--vp-c-divider);
143
+ background: var(--vp-c-bg-soft);
144
+ }
145
+
146
+ .expand-btn {
147
+ position: absolute;
148
+ top: 1rem;
149
+ right: 1rem;
150
+ background: var(--vp-c-bg);
151
+ border: 1px solid var(--vp-c-divider);
152
+ border-radius: 4px;
153
+ padding: 8px;
154
+ cursor: pointer;
155
+ opacity: 0.9;
156
+ transition: opacity 0.2s;
157
+ z-index: 10;
158
+ color: var(--vp-c-text-1);
159
+ }
160
+
161
+ .expand-btn:hover {
162
+ opacity: 1;
163
+ background: var(--vp-c-bg-soft);
164
+ }
165
+
166
+ .modal-overlay {
167
+ position: fixed;
168
+ top: 0;
169
+ left: 0;
170
+ width: 100vw;
171
+ height: 100vh;
172
+ background: rgba(0, 0, 0, 0.85);
173
+ display: flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ z-index: 1000;
177
+ }
178
+
179
+ .modal-content {
180
+ position: relative;
181
+ background: var(--vp-c-bg);
182
+ padding: 2rem;
183
+ border-radius: 8px;
184
+ width: 95vw;
185
+ height: 95vh;
186
+ display: flex;
187
+ flex-direction: column;
188
+ }
189
+
190
+ .expanded-diagram {
191
+ flex: 1;
192
+ overflow: hidden;
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: center;
196
+ }
197
+
198
+ .expanded-diagram > div {
199
+ cursor: move;
200
+ }
201
+
202
+ .expanded-diagram :deep(svg) {
203
+ display: block;
204
+ width: auto;
205
+ height: auto;
206
+ min-width: 1000px;
207
+ max-width: 90%;
208
+ max-height: 90%;
209
+ margin: auto;
210
+ }
211
+
212
+ .expanded-diagram :deep(.mermaid) {
213
+ display: flex;
214
+ justify-content: center;
215
+ align-items: center;
216
+ width: 100%;
217
+ height: 100%;
218
+ }
219
+
220
+ .zoom-controls {
221
+ position: absolute;
222
+ bottom: 1rem;
223
+ left: 50%;
224
+ transform: translateX(-50%);
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 0.5rem;
228
+ background: var(--vp-c-bg);
229
+ padding: 0.5rem;
230
+ border-radius: 8px;
231
+ border: 1px solid var(--vp-c-divider);
232
+ }
233
+
234
+ .zoom-controls button {
235
+ background: var(--vp-c-bg-soft);
236
+ border: 1px solid var(--vp-c-divider);
237
+ border-radius: 4px;
238
+ padding: 4px;
239
+ cursor: pointer;
240
+ opacity: 0.7;
241
+ transition: opacity 0.2s;
242
+ color: var(--vp-c-text-1);
243
+ }
244
+
245
+ .zoom-controls button:hover {
246
+ opacity: 1;
247
+ }
248
+
249
+ .zoom-controls button:disabled {
250
+ opacity: 0.3;
251
+ cursor: not-allowed;
252
+ }
253
+
254
+ .zoom-level {
255
+ min-width: 4rem;
256
+ text-align: center;
257
+ font-size: 0.9rem;
258
+ color: var(--vp-c-text-2);
259
+ }
260
+
261
+ .close-btn {
262
+ position: absolute;
263
+ top: 1rem;
264
+ right: 1rem;
265
+ background: var(--vp-c-bg-soft);
266
+ border: 1px solid var(--vp-c-divider);
267
+ border-radius: 4px;
268
+ padding: 8px;
269
+ cursor: pointer;
270
+ opacity: 0.7;
271
+ transition: opacity 0.2s;
272
+ z-index: 10;
273
+ color: var(--vp-c-text-1);
274
+ }
275
+
276
+ .close-btn:hover {
277
+ opacity: 1;
278
+ }
279
+ </style>