create-zudo-doc 0.1.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.
Files changed (212) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/bin/create-zudo-doc.js +2 -0
  4. package/dist/api.d.ts +20 -0
  5. package/dist/api.js +13 -0
  6. package/dist/claude-md-gen.d.ts +2 -0
  7. package/dist/claude-md-gen.js +113 -0
  8. package/dist/cli.d.ts +39 -0
  9. package/dist/cli.js +157 -0
  10. package/dist/compose.d.ts +95 -0
  11. package/dist/compose.js +206 -0
  12. package/dist/constants.d.ts +20 -0
  13. package/dist/constants.js +224 -0
  14. package/dist/features/body-foot-util.d.ts +10 -0
  15. package/dist/features/body-foot-util.js +12 -0
  16. package/dist/features/claude-resources.d.ts +2 -0
  17. package/dist/features/claude-resources.js +6 -0
  18. package/dist/features/design-token-panel.d.ts +14 -0
  19. package/dist/features/design-token-panel.js +27 -0
  20. package/dist/features/doc-history.d.ts +9 -0
  21. package/dist/features/doc-history.js +11 -0
  22. package/dist/features/doc-tags.d.ts +19 -0
  23. package/dist/features/doc-tags.js +33 -0
  24. package/dist/features/footer-taglist.d.ts +14 -0
  25. package/dist/features/footer-taglist.js +17 -0
  26. package/dist/features/footer.d.ts +8 -0
  27. package/dist/features/footer.js +10 -0
  28. package/dist/features/i18n.d.ts +22 -0
  29. package/dist/features/i18n.js +41 -0
  30. package/dist/features/image-enlarge.d.ts +11 -0
  31. package/dist/features/image-enlarge.js +13 -0
  32. package/dist/features/index.d.ts +15 -0
  33. package/dist/features/index.js +53 -0
  34. package/dist/features/llms-txt.d.ts +11 -0
  35. package/dist/features/llms-txt.js +13 -0
  36. package/dist/features/search.d.ts +9 -0
  37. package/dist/features/search.js +11 -0
  38. package/dist/features/sidebar-resizer.d.ts +14 -0
  39. package/dist/features/sidebar-resizer.js +16 -0
  40. package/dist/features/sidebar-toggle.d.ts +13 -0
  41. package/dist/features/sidebar-toggle.js +15 -0
  42. package/dist/features/tag-governance.d.ts +14 -0
  43. package/dist/features/tag-governance.js +16 -0
  44. package/dist/features/tauri-dev.d.ts +2 -0
  45. package/dist/features/tauri-dev.js +25 -0
  46. package/dist/features/tauri.d.ts +11 -0
  47. package/dist/features/tauri.js +52 -0
  48. package/dist/features/versioning.d.ts +27 -0
  49. package/dist/features/versioning.js +43 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.js +150 -0
  52. package/dist/preset.d.ts +37 -0
  53. package/dist/preset.js +156 -0
  54. package/dist/prompts.d.ts +32 -0
  55. package/dist/prompts.js +248 -0
  56. package/dist/scaffold.d.ts +4 -0
  57. package/dist/scaffold.js +344 -0
  58. package/dist/settings-gen.d.ts +2 -0
  59. package/dist/settings-gen.js +237 -0
  60. package/dist/utils.d.ts +8 -0
  61. package/dist/utils.js +34 -0
  62. package/dist/zfb-config-gen.d.ts +19 -0
  63. package/dist/zfb-config-gen.js +222 -0
  64. package/package.json +65 -0
  65. package/templates/base/.htmlvalidate.json +5 -0
  66. package/templates/base/.zfb/doc-history-meta.json +1 -0
  67. package/templates/base/pages/404.tsx +55 -0
  68. package/templates/base/pages/_data.ts +179 -0
  69. package/templates/base/pages/_mdx-components.ts +249 -0
  70. package/templates/base/pages/docs/[...slug].tsx +448 -0
  71. package/templates/base/pages/index.tsx +158 -0
  72. package/templates/base/pages/lib/_body-end-islands.tsx +201 -0
  73. package/templates/base/pages/lib/_category-nav.tsx +148 -0
  74. package/templates/base/pages/lib/_category-tree-nav.tsx +104 -0
  75. package/templates/base/pages/lib/_compose-meta-title.ts +29 -0
  76. package/templates/base/pages/lib/_details.tsx +30 -0
  77. package/templates/base/pages/lib/_doc-history-area.tsx +178 -0
  78. package/templates/base/pages/lib/_doc-metainfo-area.tsx +100 -0
  79. package/templates/base/pages/lib/_doc-tags-area.tsx +89 -0
  80. package/templates/base/pages/lib/_extract-headings.ts +81 -0
  81. package/templates/base/pages/lib/_footer-with-defaults.tsx +234 -0
  82. package/templates/base/pages/lib/_frontmatter-preview-data.ts +53 -0
  83. package/templates/base/pages/lib/_head-with-defaults.tsx +113 -0
  84. package/templates/base/pages/lib/_header-with-defaults.tsx +386 -0
  85. package/templates/base/pages/lib/_inline-version-switcher.tsx +84 -0
  86. package/templates/base/pages/lib/_math-block.tsx +63 -0
  87. package/templates/base/pages/lib/_nav-source-docs.ts +68 -0
  88. package/templates/base/pages/lib/_preset-generator.tsx +81 -0
  89. package/templates/base/pages/lib/_search-widget-script.ts +388 -0
  90. package/templates/base/pages/lib/_search-widget.tsx +196 -0
  91. package/templates/base/pages/lib/_sidebar-with-defaults.tsx +176 -0
  92. package/templates/base/pages/lib/_site-tree-nav.tsx +128 -0
  93. package/templates/base/pages/lib/locale-merge.ts +58 -0
  94. package/templates/base/pages/lib/route-enumerators.ts +302 -0
  95. package/templates/base/pages/sitemap.xml.tsx +51 -0
  96. package/templates/base/plugins/connect-adapter.mjs +144 -0
  97. package/templates/base/plugins/copy-public-plugin.mjs +50 -0
  98. package/templates/base/plugins/search-index-plugin.mjs +54 -0
  99. package/templates/base/scripts/run-b4push.sh +102 -0
  100. package/templates/base/src/components/ai-chat-modal.tsx +15 -0
  101. package/templates/base/src/components/client-router-bootstrap.tsx +14 -0
  102. package/templates/base/src/components/content/component-map.ts +25 -0
  103. package/templates/base/src/components/content/content-blockquote.tsx +16 -0
  104. package/templates/base/src/components/content/content-code.tsx +117 -0
  105. package/templates/base/src/components/content/content-link.tsx +83 -0
  106. package/templates/base/src/components/content/content-ol.tsx +19 -0
  107. package/templates/base/src/components/content/content-paragraph.tsx +10 -0
  108. package/templates/base/src/components/content/content-strong.tsx +16 -0
  109. package/templates/base/src/components/content/content-table.tsx +18 -0
  110. package/templates/base/src/components/content/content-ul.tsx +18 -0
  111. package/templates/base/src/components/content/heading-h2.tsx +26 -0
  112. package/templates/base/src/components/content/heading-h3.tsx +26 -0
  113. package/templates/base/src/components/content/heading-h4.tsx +26 -0
  114. package/templates/base/src/components/design-token-panel-bootstrap.tsx +15 -0
  115. package/templates/base/src/components/desktop-sidebar-toggle.tsx +15 -0
  116. package/templates/base/src/components/doc-history.tsx +18 -0
  117. package/templates/base/src/components/html-preview/highlighted-code.tsx +74 -0
  118. package/templates/base/src/components/html-preview/html-preview.tsx +108 -0
  119. package/templates/base/src/components/html-preview/preflight.ts +112 -0
  120. package/templates/base/src/components/html-preview/preview-base.tsx +159 -0
  121. package/templates/base/src/components/image-enlarge.tsx +19 -0
  122. package/templates/base/src/components/mobile-toc.tsx +94 -0
  123. package/templates/base/src/components/preset-generator.tsx +14 -0
  124. package/templates/base/src/components/sidebar-toggle.tsx +98 -0
  125. package/templates/base/src/components/sidebar-tree.tsx +543 -0
  126. package/templates/base/src/components/site-tree-nav.tsx +233 -0
  127. package/templates/base/src/components/theme-toggle.tsx +93 -0
  128. package/templates/base/src/components/toc.tsx +63 -0
  129. package/templates/base/src/components/tree-nav-shared.tsx +71 -0
  130. package/templates/base/src/config/color-scheme-utils.ts +182 -0
  131. package/templates/base/src/config/color-schemes.ts +128 -0
  132. package/templates/base/src/config/frontmatter-preview-defaults.ts +24 -0
  133. package/templates/base/src/config/frontmatter-preview-renderers.tsx +46 -0
  134. package/templates/base/src/config/i18n.ts +225 -0
  135. package/templates/base/src/config/settings-types.ts +162 -0
  136. package/templates/base/src/config/sidebars.ts +66 -0
  137. package/templates/base/src/config/tag-vocabulary-types.ts +39 -0
  138. package/templates/base/src/config/tag-vocabulary.ts +20 -0
  139. package/templates/base/src/hooks/use-active-heading.ts +133 -0
  140. package/templates/base/src/plugins/docs-source-map.ts +103 -0
  141. package/templates/base/src/plugins/hast-utils.ts +10 -0
  142. package/templates/base/src/plugins/rehype-code-title.ts +50 -0
  143. package/templates/base/src/plugins/rehype-heading-links.ts +53 -0
  144. package/templates/base/src/plugins/rehype-image-enlarge.ts +113 -0
  145. package/templates/base/src/plugins/rehype-mermaid.ts +41 -0
  146. package/templates/base/src/plugins/rehype-strip-md-extension.ts +58 -0
  147. package/templates/base/src/plugins/remark-admonitions.ts +99 -0
  148. package/templates/base/src/plugins/remark-resolve-markdown-links.ts +127 -0
  149. package/templates/base/src/plugins/url-utils.ts +4 -0
  150. package/templates/base/src/styles/global.css +1066 -0
  151. package/templates/base/src/types/docs-entry.ts +39 -0
  152. package/templates/base/src/types/heading.ts +5 -0
  153. package/templates/base/src/types/locale.ts +10 -0
  154. package/templates/base/src/utils/base.ts +139 -0
  155. package/templates/base/src/utils/content-files.ts +106 -0
  156. package/templates/base/src/utils/dedent.ts +24 -0
  157. package/templates/base/src/utils/docs.ts +335 -0
  158. package/templates/base/src/utils/git-info.ts +70 -0
  159. package/templates/base/src/utils/github.ts +19 -0
  160. package/templates/base/src/utils/header-right-items.ts +38 -0
  161. package/templates/base/src/utils/nav-scope.ts +63 -0
  162. package/templates/base/src/utils/sidebar.ts +104 -0
  163. package/templates/base/src/utils/slug.ts +10 -0
  164. package/templates/base/src/utils/smart-break.tsx +126 -0
  165. package/templates/base/src/utils/tags.ts +126 -0
  166. package/templates/base/tsconfig.json +36 -0
  167. package/templates/features/bodyFootUtil/files/src/utils/github.ts +19 -0
  168. package/templates/features/claudeResources/files/plugins/claude-resources-plugin.mjs +137 -0
  169. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/escape-for-mdx.test.ts +34 -0
  170. package/templates/features/claudeResources/files/src/integrations/claude-resources/__tests__/generate.test.ts +376 -0
  171. package/templates/features/claudeResources/files/src/integrations/claude-resources/escape-for-mdx.ts +93 -0
  172. package/templates/features/claudeResources/files/src/integrations/claude-resources/generate.ts +586 -0
  173. package/templates/features/designTokenPanel/files/src/components/design-token-panel-bootstrap.tsx +15 -0
  174. package/templates/features/designTokenPanel/files/src/config/design-token-panel-config.ts +99 -0
  175. package/templates/features/designTokenPanel/files/src/config/design-tokens-manifest.ts +177 -0
  176. package/templates/features/designTokenPanel/files/src/lib/design-token-panel-bootstrap.ts +50 -0
  177. package/templates/features/docHistory/files/plugins/doc-history-plugin.mjs +99 -0
  178. package/templates/features/docHistory/files/src/components/doc-history.tsx +598 -0
  179. package/templates/features/docHistory/files/src/types/doc-history.ts +23 -0
  180. package/templates/features/docHistory/files/src/utils/doc-history.ts +180 -0
  181. package/templates/features/docTags/files/pages/[locale]/docs/tags/[tag].tsx +116 -0
  182. package/templates/features/docTags/files/pages/[locale]/docs/tags/index.tsx +99 -0
  183. package/templates/features/docTags/files/pages/docs/tags/[tag].tsx +101 -0
  184. package/templates/features/docTags/files/pages/docs/tags/index.tsx +86 -0
  185. package/templates/features/i18n/files/pages/[locale]/docs/[...slug].tsx +467 -0
  186. package/templates/features/i18n/files/pages/[locale]/index.tsx +213 -0
  187. package/templates/features/imageEnlarge/files/src/components/image-enlarge.tsx +248 -0
  188. package/templates/features/llmsTxt/files/plugins/llms-txt-plugin.mjs +74 -0
  189. package/templates/features/sidebarResizer/files/src/scripts/sidebar-resizer.ts +185 -0
  190. package/templates/features/sidebarToggle/files/src/components/desktop-sidebar-toggle.tsx +126 -0
  191. package/templates/features/tagGovernance/files/scripts/tags-audit.ts +576 -0
  192. package/templates/features/tagGovernance/files/scripts/tags-suggest.ts +428 -0
  193. package/templates/features/tauri/files/src/components/find-bar.tsx +122 -0
  194. package/templates/features/tauri/files/src/components/find-in-page-init.tsx +53 -0
  195. package/templates/features/tauri/files/src/utils/find-in-page.ts +175 -0
  196. package/templates/features/tauri/files/src-tauri/Cargo.toml +14 -0
  197. package/templates/features/tauri/files/src-tauri/build.rs +3 -0
  198. package/templates/features/tauri/files/src-tauri/capabilities/default.json +11 -0
  199. package/templates/features/tauri/files/src-tauri/src/main.rs +250 -0
  200. package/templates/features/tauri/files/src-tauri/tauri.conf.json +25 -0
  201. package/templates/features/tauriDev/files/src-tauri-dev/Cargo.toml +15 -0
  202. package/templates/features/tauriDev/files/src-tauri-dev/build.rs +3 -0
  203. package/templates/features/tauriDev/files/src-tauri-dev/capabilities/default.json +7 -0
  204. package/templates/features/tauriDev/files/src-tauri-dev/frontend/index.html +187 -0
  205. package/templates/features/tauriDev/files/src-tauri-dev/icons/icon.png +0 -0
  206. package/templates/features/tauriDev/files/src-tauri-dev/src/main.rs +995 -0
  207. package/templates/features/tauriDev/files/src-tauri-dev/tauri.conf.json +22 -0
  208. package/templates/features/tauriDev/files/src-tauri-dev/test-launch.sh +65 -0
  209. package/templates/features/versioning/files/pages/[locale]/docs/versions.tsx +100 -0
  210. package/templates/features/versioning/files/pages/docs/versions.tsx +78 -0
  211. package/templates/features/versioning/files/pages/v/[version]/docs/[...slug].tsx +451 -0
  212. package/templates/features/versioning/files/pages/v/[version]/ja/docs/[...slug].tsx +490 -0
@@ -0,0 +1,586 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import matter from "gray-matter";
4
+ import { escapeForMdx } from "./escape-for-mdx";
5
+
6
+ export interface ClaudeResourcesConfig {
7
+ claudeDir: string;
8
+ projectRoot?: string;
9
+ docsDir: string;
10
+ }
11
+
12
+ interface ClaudeMdItem {
13
+ displayPath: string;
14
+ slug: string;
15
+ relPath: string;
16
+ }
17
+
18
+ interface CommandItem {
19
+ name: string;
20
+ description: string;
21
+ }
22
+
23
+ interface SkillReference {
24
+ name: string;
25
+ title: string;
26
+ content: string;
27
+ }
28
+
29
+ interface SkillItem {
30
+ name: string;
31
+ dir: string;
32
+ description: string;
33
+ references: SkillReference[];
34
+ }
35
+
36
+ interface AgentItem {
37
+ name: string;
38
+ file: string;
39
+ description: string;
40
+ model: string;
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Utility
45
+ // ---------------------------------------------------------------------------
46
+
47
+ function ensureDir(dir: string) {
48
+ if (!fs.existsSync(dir)) {
49
+ fs.mkdirSync(dir, { recursive: true });
50
+ }
51
+ }
52
+
53
+ function cleanDir(dir: string) {
54
+ if (!fs.existsSync(dir)) return;
55
+ fs.rmSync(dir, { recursive: true, force: true });
56
+ }
57
+
58
+ function parseFrontmatter(content: string) {
59
+ try {
60
+ return matter(content);
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function escapeTitle(s: string): string {
67
+ return s.replace(/"/g, '\\"');
68
+ }
69
+
70
+ function listFiles(dir: string): string[] {
71
+ if (!fs.existsSync(dir)) return [];
72
+ return fs
73
+ .readdirSync(dir, { withFileTypes: true })
74
+ .filter((d) => d.isFile())
75
+ .map((d) => d.name)
76
+ .sort();
77
+ }
78
+
79
+ function writeCategoryMeta(
80
+ outputDir: string,
81
+ label: string,
82
+ position: number,
83
+ description: string,
84
+ noPage = true,
85
+ ) {
86
+ const meta: Record<string, unknown> = { label, position, description };
87
+ if (noPage) meta.noPage = true;
88
+ fs.writeFileSync(
89
+ path.join(outputDir, "_category_.json"),
90
+ JSON.stringify(meta, null, 2) + "\n",
91
+ );
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // CLAUDE.md discovery
96
+ // ---------------------------------------------------------------------------
97
+
98
+ function findClaudeMdFiles(dir: string, excludeDirs: string[]): string[] {
99
+ const results: string[] = [];
100
+ if (!fs.existsSync(dir)) return results;
101
+
102
+ for (const item of fs.readdirSync(dir)) {
103
+ if (item === "node_modules") continue;
104
+ if (item.startsWith(".")) continue;
105
+ const itemPath = path.join(dir, item);
106
+ if (excludeDirs.some((d) => itemPath.startsWith(d))) continue;
107
+
108
+ // lstat (not stat) so symlinks aren't followed — a symlinked dir can point
109
+ // back into the project (e.g. e2e fixtures linking to packages/) or out to
110
+ // a slow mount (e.g. /mnt/c on WSL) and either turns the walk into a
111
+ // multi-minute hang.
112
+ let stat: fs.Stats;
113
+ try {
114
+ stat = fs.lstatSync(itemPath);
115
+ } catch {
116
+ continue;
117
+ }
118
+ if (stat.isDirectory()) {
119
+ results.push(...findClaudeMdFiles(itemPath, excludeDirs));
120
+ } else if (stat.isFile() && item === "CLAUDE.md") {
121
+ results.push(itemPath);
122
+ }
123
+ }
124
+ return results;
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // CLAUDE.md generation
129
+ // ---------------------------------------------------------------------------
130
+
131
+ function generateClaudemdDocs(
132
+ config: ClaudeResourcesConfig,
133
+ ): ClaudeMdItem[] {
134
+ const projectRoot = config.projectRoot ?? path.dirname(config.claudeDir);
135
+ const outputDir = path.join(config.docsDir, "claude-md");
136
+
137
+ cleanDir(outputDir);
138
+
139
+ const excludeDirs = [
140
+ path.join(projectRoot, ".git"),
141
+ path.join(projectRoot, "node_modules"),
142
+ path.join(projectRoot, "worktrees"),
143
+ path.join(projectRoot, "dist"),
144
+ path.join(projectRoot, "out"),
145
+ path.join(projectRoot, "public"),
146
+ path.join(projectRoot, "__inbox"),
147
+ path.join(projectRoot, "test-results"),
148
+ path.join(projectRoot, "e2e", "fixtures"),
149
+ path.join(config.docsDir),
150
+ ];
151
+
152
+ const files = findClaudeMdFiles(projectRoot, excludeDirs);
153
+ if (files.length === 0) return [];
154
+
155
+ ensureDir(outputDir);
156
+ const items: ClaudeMdItem[] = [];
157
+
158
+ for (const filePath of files) {
159
+ const content = fs.readFileSync(filePath, "utf8");
160
+ const relPath = path.relative(projectRoot, filePath);
161
+ const displayPath = `/${relPath}`;
162
+ const dirPart = path.dirname(relPath);
163
+ const slug = dirPart === "." ? "root" : dirPart.replace(/\//g, "--");
164
+
165
+ items.push({ displayPath, slug, relPath });
166
+
167
+ const pos = items.length + 1;
168
+ const mdx = `---
169
+ title: "${escapeTitle(displayPath)}"
170
+ description: "CLAUDE.md at ${escapeTitle(displayPath)}"
171
+ sidebar_position: ${pos}
172
+ sidebar_label: "${escapeTitle(relPath)}"
173
+ generated: true
174
+ ---
175
+
176
+ **Path:** \`${relPath}\`
177
+
178
+ ${escapeForMdx(content.trim())}
179
+ `;
180
+ fs.writeFileSync(path.join(outputDir, `${slug}.mdx`), mdx);
181
+ }
182
+
183
+ // Sort: root first, then alphabetically
184
+ items.sort((a, b) => {
185
+ if (a.slug === "root") return -1;
186
+ if (b.slug === "root") return 1;
187
+ return a.displayPath.localeCompare(b.displayPath);
188
+ });
189
+
190
+ writeCategoryMeta(outputDir, "CLAUDE.md", 900, "Project-specific instructions");
191
+ return items;
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Commands generation
196
+ // ---------------------------------------------------------------------------
197
+
198
+ function generateCommandsDocs(config: ClaudeResourcesConfig): CommandItem[] {
199
+ const commandsDir = path.join(config.claudeDir, "commands");
200
+ const outputDir = path.join(config.docsDir, "claude-commands");
201
+
202
+ cleanDir(outputDir);
203
+
204
+ if (!fs.existsSync(commandsDir)) return [];
205
+
206
+ const files = fs.readdirSync(commandsDir).filter((f) => f.endsWith(".md"));
207
+ if (files.length === 0) return [];
208
+
209
+ ensureDir(outputDir);
210
+ const items: CommandItem[] = [];
211
+
212
+ for (const file of files) {
213
+ const content = fs.readFileSync(path.join(commandsDir, file), "utf8");
214
+ const parsed = parseFrontmatter(content);
215
+ if (!parsed) continue;
216
+
217
+ const name = file.replace(/\.md$/, "");
218
+ const description = (parsed.data.description as string) || "";
219
+
220
+ items.push({ name, description });
221
+
222
+ const mdx = `---
223
+ title: "${escapeTitle(name)}"
224
+ description: "${escapeTitle(description)}"
225
+ sidebar_label: "${escapeTitle(name)}"
226
+ generated: true
227
+ ---
228
+
229
+ ${escapeForMdx(parsed.content.trim())}
230
+ `;
231
+ fs.writeFileSync(path.join(outputDir, `${name}.mdx`), mdx);
232
+ }
233
+
234
+ items.sort((a, b) => a.name.localeCompare(b.name));
235
+
236
+ writeCategoryMeta(outputDir, "Commands", 901, "Custom slash commands");
237
+ return items;
238
+ }
239
+
240
+ // ---------------------------------------------------------------------------
241
+ // Skills generation
242
+ // ---------------------------------------------------------------------------
243
+
244
+ type TreeEntry =
245
+ | { isDir: false; name: string }
246
+ | { isDir: true; name: string; children: string[] };
247
+
248
+ function getSkillFileTree(
249
+ skillDir: string,
250
+ subDirs: { name: string; files: string[] }[],
251
+ ): string {
252
+ const lines: string[] = [`${skillDir}/`];
253
+ const entries: TreeEntry[] = [{ isDir: false, name: "SKILL.md" }];
254
+
255
+ for (const sub of subDirs) {
256
+ entries.push({ isDir: true, name: sub.name, children: sub.files });
257
+ }
258
+
259
+ for (let i = 0; i < entries.length; i++) {
260
+ const entry = entries[i];
261
+ const isLast = i === entries.length - 1;
262
+ const prefix = isLast ? "└── " : "├── ";
263
+
264
+ if (!entry.isDir) {
265
+ lines.push(`${prefix}${entry.name}`);
266
+ } else {
267
+ lines.push(`${prefix}${entry.name}/`);
268
+ for (let j = 0; j < entry.children.length; j++) {
269
+ const child = entry.children[j];
270
+ const childIsLast = j === entry.children.length - 1;
271
+ const continuation = isLast ? " " : "│ ";
272
+ const childPrefix = childIsLast ? "└── " : "├── ";
273
+ lines.push(`${continuation}${childPrefix}${child}`);
274
+ }
275
+ }
276
+ }
277
+
278
+ return lines.join("\n");
279
+ }
280
+
281
+ function getScriptDescription(filePath: string): string {
282
+ try {
283
+ const topLines = fs.readFileSync(filePath, "utf8").split("\n", 2);
284
+ // Skip shebang, use second line if available
285
+ const commentLine = topLines[0].startsWith("#!")
286
+ ? topLines[1] || ""
287
+ : topLines[0];
288
+ // Match # comments (shell/python) or // comments (JS/TS)
289
+ const match = commentLine.match(/^(?:#|\/\/)\s*(.+)/);
290
+ return match ? ` — ${match[1]}` : "";
291
+ } catch {
292
+ return "";
293
+ }
294
+ }
295
+
296
+ function getSkillReferences(
297
+ skillsDir: string,
298
+ skillDir: string,
299
+ ): SkillReference[] {
300
+ const refsDir = path.join(skillsDir, skillDir, "references");
301
+ if (!fs.existsSync(refsDir)) return [];
302
+
303
+ return fs
304
+ .readdirSync(refsDir)
305
+ .filter((f) => f.endsWith(".md"))
306
+ .map((f) => {
307
+ const content = fs.readFileSync(path.join(refsDir, f), "utf8");
308
+ const name = f.replace(/\.md$/, "");
309
+ const h1Match = content.match(/^#\s+(.+)$/m);
310
+ const title = h1Match ? h1Match[1] : name;
311
+ return { name, title, content };
312
+ })
313
+ .sort((a, b) => a.name.localeCompare(b.name));
314
+ }
315
+
316
+ function generateSkillsDocs(config: ClaudeResourcesConfig): SkillItem[] {
317
+ const skillsDir = path.join(config.claudeDir, "skills");
318
+ const outputDir = path.join(config.docsDir, "claude-skills");
319
+
320
+ cleanDir(outputDir);
321
+
322
+ if (!fs.existsSync(skillsDir)) return [];
323
+
324
+ const dirs = fs.readdirSync(skillsDir).filter((d) => {
325
+ const skillPath = path.join(skillsDir, d);
326
+ return (
327
+ fs.statSync(skillPath).isDirectory() &&
328
+ fs.existsSync(path.join(skillPath, "SKILL.md"))
329
+ );
330
+ });
331
+
332
+ if (dirs.length === 0) return [];
333
+
334
+ ensureDir(outputDir);
335
+ const items: SkillItem[] = [];
336
+
337
+ for (const dir of dirs) {
338
+ const content = fs.readFileSync(
339
+ path.join(skillsDir, dir, "SKILL.md"),
340
+ "utf8",
341
+ );
342
+ const parsed = parseFrontmatter(content);
343
+ if (!parsed) continue;
344
+
345
+ const name = (parsed.data.name as string) || dir;
346
+ const description = (parsed.data.description as string) || "";
347
+ const references = getSkillReferences(skillsDir, dir);
348
+
349
+ items.push({ name, dir, description, references });
350
+
351
+ const scriptFiles = listFiles(path.join(skillsDir, dir, "scripts"));
352
+ const assetFiles = listFiles(path.join(skillsDir, dir, "assets"));
353
+ const refFiles = references.map((r) => `${r.name}.md`);
354
+
355
+ // Collect non-empty subdirectories for tree display
356
+ const subDirs: { name: string; files: string[] }[] = [];
357
+ if (scriptFiles.length > 0) subDirs.push({ name: "scripts", files: scriptFiles });
358
+ if (refFiles.length > 0) subDirs.push({ name: "references", files: refFiles });
359
+ if (assetFiles.length > 0) subDirs.push({ name: "assets", files: assetFiles });
360
+
361
+ // File tree + links to renderable .md sub-files
362
+ let fileStructureSection = "";
363
+ if (subDirs.length > 0) {
364
+ const tree = `\`\`\`\n${getSkillFileTree(dir, subDirs)}\n\`\`\``;
365
+
366
+ // Collect links to all .md sub-files that get pages
367
+ // Links use ./<subpage> which resolves correctly from the skill page URL
368
+ // (the page URL already includes the dir, e.g. /docs/claude-skills/<dir>/)
369
+ const links: string[] = [];
370
+ for (const ref of references) {
371
+ links.push(`- [references/${ref.name}.md](./ref-${ref.name})`);
372
+ }
373
+ for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
374
+ const slug = f.replace(/\.md$/, "");
375
+ links.push(`- [scripts/${f}](./script-${slug})`);
376
+ }
377
+ for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
378
+ const slug = f.replace(/\.md$/, "");
379
+ links.push(`- [assets/${f}](./asset-${slug})`);
380
+ }
381
+
382
+ const linkList = links.length > 0 ? `\n\n${links.join("\n")}` : "";
383
+ fileStructureSection = `## File Structure\n\n${tree}${linkList}`;
384
+ }
385
+
386
+ const shortDesc = description.length > 200
387
+ ? description.substring(0, 200) + "..."
388
+ : description;
389
+
390
+ // Rewrite references/scripts/assets links in skill body to match doc site URLs
391
+ let skillBody = parsed.content.trim();
392
+ skillBody = skillBody
393
+ .replace(/\]\(references\/([^)]+)\.md\)/g, "](./ref-$1)")
394
+ .replace(/\]\(scripts\/([^)]+)\.md\)/g, "](./script-$1)")
395
+ .replace(/\]\(assets\/([^)]+)\.md\)/g, "](./asset-$1)");
396
+
397
+ const body = [
398
+ fileStructureSection,
399
+ escapeForMdx(skillBody),
400
+ ]
401
+ .filter(Boolean)
402
+ .join("\n\n");
403
+
404
+ const mdx = `---
405
+ title: "${escapeTitle(name)}"
406
+ description: "${escapeTitle(shortDesc)}"
407
+ sidebar_label: "${escapeTitle(name)}"
408
+ generated: true
409
+ ---
410
+
411
+ ${body}`;
412
+
413
+ // Write skill page as flat file
414
+ fs.writeFileSync(path.join(outputDir, `${dir}.mdx`), mdx);
415
+
416
+ // Generate unlisted sub-pages (flat files with custom slug for nested breadcrumbs)
417
+ // File: <dir>--ref-<name>.mdx, slug: claude-skills/<dir>/ref-<name>
418
+ const skillSlugBase = `claude-skills/${dir}`;
419
+
420
+ for (const ref of references) {
421
+ const subSlug = `${skillSlugBase}/ref-${ref.name}`;
422
+ const refMdx = `---
423
+ title: "${escapeTitle(ref.title)}"
424
+ slug: "${subSlug}"
425
+ unlisted: true
426
+ generated: true
427
+ ---
428
+
429
+ ${escapeForMdx(ref.content.trim())}
430
+ `;
431
+ fs.writeFileSync(path.join(outputDir, `${dir}--ref-${ref.name}.mdx`), refMdx);
432
+ }
433
+
434
+ for (const f of scriptFiles.filter((s) => s.endsWith(".md"))) {
435
+ const slug = f.replace(/\.md$/, "");
436
+ const subSlug = `${skillSlugBase}/script-${slug}`;
437
+ const raw = fs.readFileSync(
438
+ path.join(skillsDir, dir, "scripts", f),
439
+ "utf8",
440
+ );
441
+ const h1Match = raw.match(/^#\s+(.+)$/m);
442
+ const title = h1Match ? h1Match[1] : slug;
443
+ fs.writeFileSync(
444
+ path.join(outputDir, `${dir}--script-${slug}.mdx`),
445
+ `---\ntitle: "${escapeTitle(title)}"\nslug: "${subSlug}"\nunlisted: true\ngenerated: true\n---\n\n${escapeForMdx(raw.trim())}\n`,
446
+ );
447
+ }
448
+
449
+ for (const f of assetFiles.filter((a) => a.endsWith(".md"))) {
450
+ const slug = f.replace(/\.md$/, "");
451
+ const subSlug = `${skillSlugBase}/asset-${slug}`;
452
+ const raw = fs.readFileSync(
453
+ path.join(skillsDir, dir, "assets", f),
454
+ "utf8",
455
+ );
456
+ const h1Match = raw.match(/^#\s+(.+)$/m);
457
+ const title = h1Match ? h1Match[1] : slug;
458
+ fs.writeFileSync(
459
+ path.join(outputDir, `${dir}--asset-${slug}.mdx`),
460
+ `---\ntitle: "${escapeTitle(title)}"\nslug: "${subSlug}"\nunlisted: true\ngenerated: true\n---\n\n${escapeForMdx(raw.trim())}\n`,
461
+ );
462
+ }
463
+ }
464
+
465
+ items.sort((a, b) => a.name.localeCompare(b.name));
466
+
467
+ writeCategoryMeta(outputDir, "Skills", 902, "Skill packages");
468
+ return items;
469
+ }
470
+
471
+ // ---------------------------------------------------------------------------
472
+ // Agents generation
473
+ // ---------------------------------------------------------------------------
474
+
475
+ function generateAgentsDocs(config: ClaudeResourcesConfig): AgentItem[] {
476
+ const agentsDir = path.join(config.claudeDir, "agents");
477
+ const outputDir = path.join(config.docsDir, "claude-agents");
478
+
479
+ cleanDir(outputDir);
480
+
481
+ if (!fs.existsSync(agentsDir)) return [];
482
+
483
+ const files = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
484
+ if (files.length === 0) return [];
485
+
486
+ ensureDir(outputDir);
487
+ const items: AgentItem[] = [];
488
+
489
+ for (const file of files) {
490
+ const content = fs.readFileSync(path.join(agentsDir, file), "utf8");
491
+ const parsed = parseFrontmatter(content);
492
+ if (!parsed) continue;
493
+
494
+ const name = (parsed.data.name as string) || file.replace(/\.md$/, "");
495
+ const description = (parsed.data.description as string) || "";
496
+ const model = (parsed.data.model as string) || "";
497
+ const fileSlug = file.replace(/\.md$/, "");
498
+
499
+ items.push({ name, file: fileSlug, description, model });
500
+
501
+ const modelBadge = model ? `**Model:** \`${model}\`\n` : "";
502
+
503
+ const mdx = `---
504
+ title: "${escapeTitle(name)}"
505
+ description: "${escapeTitle(description)}"
506
+ sidebar_label: "${escapeTitle(name)}"
507
+ generated: true
508
+ ---
509
+
510
+ ${modelBadge}
511
+ ${escapeForMdx(parsed.content.trim())}
512
+ `;
513
+ fs.writeFileSync(path.join(outputDir, `${fileSlug}.mdx`), mdx);
514
+ }
515
+
516
+ items.sort((a, b) => a.name.localeCompare(b.name));
517
+
518
+ writeCategoryMeta(outputDir, "Agents", 903, "Custom subagents");
519
+ return items;
520
+ }
521
+
522
+ // ---------------------------------------------------------------------------
523
+ // Main
524
+ // ---------------------------------------------------------------------------
525
+
526
+ function generateOverviewIndex(
527
+ config: ClaudeResourcesConfig,
528
+ {
529
+ hasCommands,
530
+ hasSkills,
531
+ hasAgents,
532
+ hasClaudemd,
533
+ }: { hasCommands: boolean; hasSkills: boolean; hasAgents: boolean; hasClaudemd: boolean },
534
+ ) {
535
+ const outputDir = path.join(config.docsDir, "claude");
536
+ cleanDir(outputDir);
537
+ ensureDir(outputDir);
538
+
539
+ // Build the explicit slug list from whichever sub-categories were generated.
540
+ // CategoryNav with `categories` renders cards for each slug by resolving
541
+ // the node in the nav tree (including noPage auto-index categories) and
542
+ // falling back to docsUrl(slug, locale) for the href when noPage=true.
543
+ const categorySlugs: string[] = [];
544
+ if (hasClaudemd) categorySlugs.push("claude-md");
545
+ if (hasSkills) categorySlugs.push("claude-skills");
546
+ if (hasAgents) categorySlugs.push("claude-agents");
547
+ if (hasCommands) categorySlugs.push("claude-commands");
548
+
549
+ const categoriesAttr = JSON.stringify(categorySlugs);
550
+
551
+ const index = `---
552
+ title: "Claude"
553
+ description: "Claude Code configuration reference."
554
+ sidebar_position: 899
555
+ generated: true
556
+ ---
557
+
558
+ Claude Code configuration reference.
559
+
560
+ ## Resources
561
+
562
+ <CategoryNav categories={${categoriesAttr}} />
563
+ `;
564
+ fs.writeFileSync(path.join(outputDir, "index.mdx"), index);
565
+ }
566
+
567
+ export function generateClaudeResourcesDocs(config: ClaudeResourcesConfig) {
568
+ const claudemds = generateClaudemdDocs(config);
569
+ const commands = generateCommandsDocs(config);
570
+ const skills = generateSkillsDocs(config);
571
+ const agents = generateAgentsDocs(config);
572
+
573
+ generateOverviewIndex(config, {
574
+ hasClaudemd: claudemds.length > 0,
575
+ hasCommands: commands.length > 0,
576
+ hasSkills: skills.length > 0,
577
+ hasAgents: agents.length > 0,
578
+ });
579
+
580
+ return {
581
+ claudemd: claudemds.length,
582
+ commands: commands.length,
583
+ skills: skills.length,
584
+ agents: agents.length,
585
+ };
586
+ }
@@ -0,0 +1,15 @@
1
+ "use client";
2
+
3
+ // Side-effect import — running this module in the browser triggers
4
+ // configurePanel(designTokenPanelConfig) which mounts the zdtp panel
5
+ // and registers the `toggle-design-token-panel` window listener.
6
+ import "@/lib/design-token-panel-bootstrap";
7
+
8
+ import type { JSX } from "preact";
9
+
10
+ function DesignTokenPanelBootstrap(): JSX.Element | null {
11
+ return null;
12
+ }
13
+ DesignTokenPanelBootstrap.displayName = "DesignTokenPanelBootstrap";
14
+
15
+ export default DesignTokenPanelBootstrap;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * zdtp (zudo-design-token-panel) PanelConfig for this project.
3
+ *
4
+ * This config object is the single source of truth passed to
5
+ * `configurePanel(designTokenPanelConfig)` in the bootstrap module.
6
+ *
7
+ * Type notes:
8
+ * - zdtp's `ColorScheme` requires a `shikiTheme: string` field that is not
9
+ * present in zudo-doc's local `ColorScheme` type or data. The cast below
10
+ * (`as unknown as Record<string, ZdtpColorScheme>`) is intentional: zdtp
11
+ * uses `shikiTheme` only for the code-block preview inside the panel; when
12
+ * absent at runtime it falls back to `colorCluster.defaultShikiTheme`. No
13
+ * user-visible regression results from the missing field.
14
+ */
15
+
16
+ import type {
17
+ PanelConfig,
18
+ ColorScheme as ZdtpColorScheme,
19
+ } from "@takazudo/zdtp";
20
+ import {
21
+ SPACING_TOKENS,
22
+ FONT_TOKENS,
23
+ SIZE_TOKENS,
24
+ COLOR_TOKENS,
25
+ } from "./design-tokens-manifest";
26
+ import { colorSchemes } from "./color-schemes";
27
+ import { SEMANTIC_DEFAULTS, SEMANTIC_CSS_NAMES } from "./color-scheme-utils";
28
+ import { settings } from "./settings";
29
+ import { DESIGN_TOKEN_SCHEMA } from "@takazudo/zudo-doc/theme";
30
+
31
+ /**
32
+ * Base-role fallback indices. Background defaults to palette index 0,
33
+ * foreground to 15, cursor to 6, selection to 0/15.
34
+ */
35
+ const BASE_DEFAULTS = {
36
+ background: 0,
37
+ foreground: 15,
38
+ cursor: 6,
39
+ selectionBg: 0,
40
+ selectionFg: 15,
41
+ } as const;
42
+
43
+ /**
44
+ * Fallback Shiki theme used when a color scheme's `shikiTheme` field is absent.
45
+ */
46
+ const DEFAULT_SHIKI_THEME = "github-dark";
47
+
48
+ export const designTokenPanelConfig: PanelConfig = {
49
+ // Customize these values to match your project name to avoid localStorage
50
+ // collisions when multiple zudo-doc projects run in the same browser.
51
+ storagePrefix: "my-doc-tweak",
52
+ consoleNamespace: "myDoc",
53
+ modalClassPrefix: "my-doc-design-token-panel-modal",
54
+ // Must match DESIGN_TOKEN_SCHEMA in @takazudo/zudo-doc/theme so that
55
+ // exported JSON files remain importable across panel versions.
56
+ schemaId: DESIGN_TOKEN_SCHEMA,
57
+ exportFilenameBase: "my-doc-design-tokens",
58
+ tokens: {
59
+ spacing: SPACING_TOKENS,
60
+ // TokenManifest uses "typography" (not "font") per zdtp's §3.1 contract.
61
+ typography: FONT_TOKENS,
62
+ size: SIZE_TOKENS,
63
+ // Empty — color is cluster-driven; zdtp reads palette via colorCluster.
64
+ color: COLOR_TOKENS,
65
+ },
66
+ colorCluster: {
67
+ id: "my-doc",
68
+ label: "My Doc",
69
+ paletteSize: 16,
70
+ // {n} placeholder replaced by resolvePaletteCssVar(cluster, i) at use time.
71
+ paletteCssVarTemplate: "--zd-{n}",
72
+ baseRoles: {
73
+ background: "--zd-bg",
74
+ foreground: "--zd-fg",
75
+ cursor: "--zd-cursor",
76
+ selectionBg: "--zd-sel-bg",
77
+ selectionFg: "--zd-sel-fg",
78
+ },
79
+ semanticDefaults: SEMANTIC_DEFAULTS,
80
+ semanticCssNames: SEMANTIC_CSS_NAMES,
81
+ baseDefaults: BASE_DEFAULTS,
82
+ defaultShikiTheme: DEFAULT_SHIKI_THEME,
83
+ // Local ColorScheme lacks shikiTheme (not in zudo-doc's type). Cast is safe:
84
+ // zdtp falls back to defaultShikiTheme when shikiTheme is absent at runtime.
85
+ colorSchemes: colorSchemes as unknown as Record<string, ZdtpColorScheme>,
86
+ panelSettings: {
87
+ colorScheme: settings.colorScheme,
88
+ // colorMode: strip off respectPrefersColorScheme (not in zdtp's shape).
89
+ colorMode: settings.colorMode
90
+ ? {
91
+ defaultMode: settings.colorMode.defaultMode,
92
+ lightScheme: settings.colorMode.lightScheme,
93
+ darkScheme: settings.colorMode.darkScheme,
94
+ }
95
+ : false,
96
+ },
97
+ },
98
+ colorPresets: {},
99
+ };