claude-code-autoconfig 1.0.134 → 1.0.136
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/.claude/agents/docs-refresh.md +21 -29
- package/.claude/commands/show-docs.md +9 -29
- package/.claude/docs/autoconfig.docs.html +565 -341
- package/.claude/scripts/sync-docs.js +556 -0
- package/.claude/settings.json +81 -77
- package/bin/cli.js +35 -4
- package/package.json +1 -1
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* sync-docs.js — Scans .claude/ and updates the interactive docs HTML.
|
|
5
|
+
*
|
|
6
|
+
* Updates three sections in autoconfig.docs.html:
|
|
7
|
+
* 1. File tree (HTML divs)
|
|
8
|
+
* 2. treeInfo (JS object — title, desc, trigger per key)
|
|
9
|
+
* 3. fileContents (JS object — filename + content preview per key)
|
|
10
|
+
*
|
|
11
|
+
* Run: node .claude/scripts/sync-docs.js
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const cwd = process.cwd();
|
|
18
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
19
|
+
const docsPath = path.join(claudeDir, 'docs', 'autoconfig.docs.html');
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(docsPath)) {
|
|
22
|
+
// No docs file — nothing to sync
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Directories/files to skip entirely
|
|
27
|
+
const SKIP = new Set([
|
|
28
|
+
'docs', 'plans', 'migration', 'retro', 'scripts',
|
|
29
|
+
'settings.local.json'
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// Folders we scan for files
|
|
33
|
+
const SCAN_FOLDERS = ['commands', 'agents', 'hooks', 'feedback'];
|
|
34
|
+
|
|
35
|
+
// Structural keys that are not file-backed (always preserved, never generated)
|
|
36
|
+
const STRUCTURAL_KEYS = new Set([
|
|
37
|
+
'memory-md', 'root', 'claude-md', 'claude-dir'
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// Hardcoded entries that have special handling
|
|
41
|
+
const STATIC_ENTRIES = {
|
|
42
|
+
'settings': {
|
|
43
|
+
file: 'settings.json',
|
|
44
|
+
parent: 'claude-dir',
|
|
45
|
+
icon: '⚙️',
|
|
46
|
+
indent: 2
|
|
47
|
+
},
|
|
48
|
+
'mcp': {
|
|
49
|
+
file: '.mcp.json',
|
|
50
|
+
parent: 'claude-dir',
|
|
51
|
+
icon: '🔌',
|
|
52
|
+
indent: 2
|
|
53
|
+
},
|
|
54
|
+
'docs': {
|
|
55
|
+
file: 'autoconfig.docs.html',
|
|
56
|
+
parent: 'docs-folder',
|
|
57
|
+
icon: '🌐',
|
|
58
|
+
indent: 3,
|
|
59
|
+
folder: {
|
|
60
|
+
key: 'docs',
|
|
61
|
+
name: 'docs',
|
|
62
|
+
dataFolder: 'docs-folder',
|
|
63
|
+
parent: 'claude-dir'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Extract @description from a file's content.
|
|
70
|
+
*/
|
|
71
|
+
function extractDescription(content, ext) {
|
|
72
|
+
if (ext === '.md') {
|
|
73
|
+
const match = content.match(/<!--\s*@description\s+(.+?)\s*-->/);
|
|
74
|
+
return match ? match[1] : null;
|
|
75
|
+
}
|
|
76
|
+
if (ext === '.js') {
|
|
77
|
+
// Look for @description in JSDoc or comment block
|
|
78
|
+
const match = content.match(/@description\s+(.+?)(?:\n|\*\/)/);
|
|
79
|
+
return match ? match[1].replace(/\s*\*\s*$/, '').trim() : null;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extract @trigger from a JS file's JSDoc.
|
|
86
|
+
*/
|
|
87
|
+
function extractTrigger(content) {
|
|
88
|
+
const match = content.match(/@trigger\s+(.+?)(?:\n|\*\/)/);
|
|
89
|
+
return match ? match[1].replace(/\s*\*\s*$/, '').trim() : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Derive a unique key for a file.
|
|
94
|
+
*/
|
|
95
|
+
function deriveKey(folder, filename) {
|
|
96
|
+
const stem = filename.replace(/\.[^.]+$/, '');
|
|
97
|
+
if (folder === 'commands') return stem;
|
|
98
|
+
if (folder === 'hooks') return stem + '-hook';
|
|
99
|
+
if (folder === 'agents') return stem + '-agent';
|
|
100
|
+
if (folder === 'feedback') return 'feedback-' + stem.toLowerCase();
|
|
101
|
+
if (folder === 'updates') return 'update-' + stem;
|
|
102
|
+
return stem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Derive trigger text for a file.
|
|
107
|
+
*/
|
|
108
|
+
function deriveTrigger(folder, filename, content) {
|
|
109
|
+
if (folder === 'commands') {
|
|
110
|
+
const stem = filename.replace(/\.md$/, '');
|
|
111
|
+
return '/' + stem;
|
|
112
|
+
}
|
|
113
|
+
if (folder === 'hooks') {
|
|
114
|
+
const trigger = extractTrigger(content);
|
|
115
|
+
return trigger || 'PostToolUse hook';
|
|
116
|
+
}
|
|
117
|
+
if (folder === 'agents') return 'Background agent';
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Generate a content preview (first ~30 meaningful lines).
|
|
123
|
+
*/
|
|
124
|
+
function generatePreview(content, ext) {
|
|
125
|
+
const lines = content.split(/\r?\n/);
|
|
126
|
+
// Skip metadata comments at the top
|
|
127
|
+
let start = 0;
|
|
128
|
+
while (start < lines.length && /^<!--/.test(lines[start].trim())) {
|
|
129
|
+
// Find the closing -->
|
|
130
|
+
while (start < lines.length && !lines[start].includes('-->')) start++;
|
|
131
|
+
start++;
|
|
132
|
+
}
|
|
133
|
+
// Skip leading blank lines
|
|
134
|
+
while (start < lines.length && lines[start].trim() === '') start++;
|
|
135
|
+
|
|
136
|
+
const preview = lines.slice(start, start + 30).join('\n').trim();
|
|
137
|
+
return preview || content.slice(0, 500).trim();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Escape a string for use inside a JS template literal.
|
|
142
|
+
*/
|
|
143
|
+
function escapeTemplateLiteral(str) {
|
|
144
|
+
return str
|
|
145
|
+
.replace(/\\/g, '\\\\')
|
|
146
|
+
.replace(/`/g, '\\`')
|
|
147
|
+
.replace(/\$\{/g, '\\${');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Scan .claude/ and collect file entries.
|
|
152
|
+
*/
|
|
153
|
+
function scanFiles() {
|
|
154
|
+
const entries = [];
|
|
155
|
+
|
|
156
|
+
for (const folder of SCAN_FOLDERS) {
|
|
157
|
+
const folderPath = path.join(claudeDir, folder);
|
|
158
|
+
if (!fs.existsSync(folderPath)) continue;
|
|
159
|
+
|
|
160
|
+
const files = fs.readdirSync(folderPath).filter(f => {
|
|
161
|
+
const stat = fs.statSync(path.join(folderPath, f));
|
|
162
|
+
return stat.isFile();
|
|
163
|
+
}).sort();
|
|
164
|
+
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
const ext = path.extname(file);
|
|
167
|
+
const filePath = path.join(folderPath, file);
|
|
168
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
169
|
+
const key = deriveKey(folder, file);
|
|
170
|
+
const desc = extractDescription(content, ext) || `${file} in ${folder}/`;
|
|
171
|
+
const trigger = deriveTrigger(folder, file, content);
|
|
172
|
+
const preview = generatePreview(content, ext);
|
|
173
|
+
|
|
174
|
+
entries.push({ key, folder, file, desc, trigger, preview });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Check for rules/ directory
|
|
179
|
+
const rulesPath = path.join(claudeDir, 'rules');
|
|
180
|
+
if (fs.existsSync(rulesPath)) {
|
|
181
|
+
entries.push({
|
|
182
|
+
key: 'rules',
|
|
183
|
+
folder: null,
|
|
184
|
+
file: null,
|
|
185
|
+
desc: 'Path-scoped context that loads when Claude works on matching files.',
|
|
186
|
+
trigger: null,
|
|
187
|
+
preview: null,
|
|
188
|
+
isEmptyFolder: true,
|
|
189
|
+
emptyMessage: 'Add .md files here to define rules for specific paths in your codebase.'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return entries;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generate the tree HTML for file-backed entries.
|
|
198
|
+
*/
|
|
199
|
+
function generateTreeHtml(entries) {
|
|
200
|
+
const lines = [];
|
|
201
|
+
const folders = new Map(); // folder name -> entries
|
|
202
|
+
|
|
203
|
+
// Group entries by folder
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
if (entry.isEmptyFolder) continue;
|
|
206
|
+
if (!folders.has(entry.folder)) folders.set(entry.folder, []);
|
|
207
|
+
folders.get(entry.folder).push(entry);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Emit folder groups in a consistent order
|
|
211
|
+
const folderOrder = ['commands', 'agents', 'feedback', 'hooks'];
|
|
212
|
+
for (const folderName of folderOrder) {
|
|
213
|
+
const folderEntries = folders.get(folderName);
|
|
214
|
+
if (!folderEntries || folderEntries.length === 0) continue;
|
|
215
|
+
|
|
216
|
+
// Folder row
|
|
217
|
+
lines.push(` <div class="tree-item indent-2 folder-row hidden collapsed" data-info="${folderName}" data-folder="${folderName}" data-parent="claude-dir">`);
|
|
218
|
+
lines.push(` <span class="tree-chevron">›</span>`);
|
|
219
|
+
lines.push(` <span class="tree-folder-icon">📁</span>`);
|
|
220
|
+
lines.push(` <span class="folder">${folderName}</span>`);
|
|
221
|
+
lines.push(` </div>`);
|
|
222
|
+
|
|
223
|
+
// File rows
|
|
224
|
+
for (const entry of folderEntries) {
|
|
225
|
+
lines.push(` <div class="tree-item indent-3 hidden" data-info="${entry.key}" data-parent="${folderName}">`);
|
|
226
|
+
lines.push(` <span class="tree-spacer"></span>`);
|
|
227
|
+
lines.push(` <span class="tree-file-icon">📄</span>`);
|
|
228
|
+
lines.push(` <span class="file">${entry.file}</span>`);
|
|
229
|
+
lines.push(` </div>`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Docs folder (static)
|
|
234
|
+
lines.push(` <div class="tree-item indent-2 folder-row hidden collapsed" data-info="docs" data-folder="docs-folder" data-parent="claude-dir">`);
|
|
235
|
+
lines.push(` <span class="tree-chevron">›</span>`);
|
|
236
|
+
lines.push(` <span class="tree-folder-icon">📁</span>`);
|
|
237
|
+
lines.push(` <span class="folder">docs</span>`);
|
|
238
|
+
lines.push(` </div>`);
|
|
239
|
+
lines.push(` <div class="tree-item indent-3 hidden" data-info="docs" data-parent="docs-folder">`);
|
|
240
|
+
lines.push(` <span class="tree-spacer"></span>`);
|
|
241
|
+
lines.push(` <span class="tree-file-icon">🌐</span>`);
|
|
242
|
+
lines.push(` <span class="file">autoconfig.docs.html</span>`);
|
|
243
|
+
lines.push(` </div>`);
|
|
244
|
+
|
|
245
|
+
// Rules folder
|
|
246
|
+
const hasRules = entries.some(e => e.key === 'rules');
|
|
247
|
+
if (hasRules) {
|
|
248
|
+
lines.push(` <div class="tree-item indent-2 hidden" data-info="rules" data-parent="claude-dir">`);
|
|
249
|
+
lines.push(` <span class="tree-spacer"></span>`);
|
|
250
|
+
lines.push(` <span class="tree-folder-icon">📁</span>`);
|
|
251
|
+
lines.push(` <span class="folder">rules</span>`);
|
|
252
|
+
lines.push(` </div>`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// .mcp.json
|
|
256
|
+
if (fs.existsSync(path.join(claudeDir, '.mcp.json'))) {
|
|
257
|
+
lines.push(` <div class="tree-item indent-2 hidden" data-info="mcp" data-parent="claude-dir">`);
|
|
258
|
+
lines.push(` <span class="tree-spacer"></span>`);
|
|
259
|
+
lines.push(` <span class="tree-file-icon">🔌</span>`);
|
|
260
|
+
lines.push(` <span class="file">.mcp.json</span>`);
|
|
261
|
+
lines.push(` </div>`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// settings.json
|
|
265
|
+
if (fs.existsSync(path.join(claudeDir, 'settings.json'))) {
|
|
266
|
+
lines.push(` <div class="tree-item indent-2 hidden" data-info="settings" data-parent="claude-dir">`);
|
|
267
|
+
lines.push(` <span class="tree-spacer"></span>`);
|
|
268
|
+
lines.push(` <span class="tree-file-icon">⚙️</span>`);
|
|
269
|
+
lines.push(` <span class="file">settings.json</span>`);
|
|
270
|
+
lines.push(` </div>`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return lines.join('\n');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generate treeInfo JS entries for file-backed items.
|
|
278
|
+
*/
|
|
279
|
+
function generateTreeInfo(entries) {
|
|
280
|
+
const lines = [];
|
|
281
|
+
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
if (entry.isEmptyFolder) {
|
|
284
|
+
lines.push(` '${entry.key}': {`);
|
|
285
|
+
lines.push(` title: '${entry.key}/',`);
|
|
286
|
+
lines.push(` desc: '${entry.desc.replace(/'/g, "\\'")}'`);
|
|
287
|
+
lines.push(` },`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Folder-level info card
|
|
292
|
+
// (we emit these once per folder)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Emit folder info cards
|
|
296
|
+
const folderDescs = {
|
|
297
|
+
'commands': 'On-demand workflows you trigger with <code>/name</code>. Each .md file becomes a <a href="https://docs.anthropic.com/en/docs/claude-code/slash-commands" target="_blank" style="color: var(--accent-cyan);">slash command</a>.',
|
|
298
|
+
'agents': 'Reusable agent definitions that Claude can invoke for specialized tasks.',
|
|
299
|
+
'feedback': 'Team-maintained corrections and guidance for Claude. Add notes here when Claude does something wrong — it learns for next time. This directory persists across <code>/autoconfig</code> runs.',
|
|
300
|
+
'hooks': 'Executable hook scripts that trigger on Claude Code events like PostToolUse.'
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const seenFolders = new Set();
|
|
304
|
+
for (const entry of entries) {
|
|
305
|
+
if (entry.isEmptyFolder) continue;
|
|
306
|
+
if (!seenFolders.has(entry.folder)) {
|
|
307
|
+
seenFolders.add(entry.folder);
|
|
308
|
+
const desc = folderDescs[entry.folder] || `Files in ${entry.folder}/`;
|
|
309
|
+
lines.push(` '${entry.folder}': {`);
|
|
310
|
+
lines.push(` title: '${entry.folder}/',`);
|
|
311
|
+
lines.push(` desc: '${desc.replace(/'/g, "\\'")}'`);
|
|
312
|
+
lines.push(` },`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Emit file info cards
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (entry.isEmptyFolder) continue;
|
|
319
|
+
|
|
320
|
+
const escapedDesc = entry.desc.replace(/'/g, "\\'");
|
|
321
|
+
lines.push(` '${entry.key}': {`);
|
|
322
|
+
lines.push(` title: '${entry.file}',`);
|
|
323
|
+
lines.push(` desc: '${escapedDesc}'${entry.trigger ? ',' : ''}`);
|
|
324
|
+
if (entry.trigger) {
|
|
325
|
+
lines.push(` trigger: '${entry.trigger.replace(/'/g, "\\'")}'`);
|
|
326
|
+
}
|
|
327
|
+
lines.push(` },`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Static entries: docs, rules, mcp, settings
|
|
331
|
+
lines.push(` 'docs': {`);
|
|
332
|
+
lines.push(` title: 'docs/autoconfig.docs.html',`);
|
|
333
|
+
lines.push(` desc: 'This interactive docs. Open it anytime to review what each file does.',`);
|
|
334
|
+
lines.push(` trigger: '/show-docs'`);
|
|
335
|
+
lines.push(` },`);
|
|
336
|
+
|
|
337
|
+
if (entries.some(e => e.key === 'rules')) {
|
|
338
|
+
lines.push(` 'rules': {`);
|
|
339
|
+
lines.push(` title: 'rules/',`);
|
|
340
|
+
lines.push(` desc: 'Path-scoped context that loads when Claude works on matching files. Optimized rules are based on your project\\'s needs, patterns and practices.<br><br><div style="background: var(--bg-elevated); border: 1px solid var(--accent-cyan); border-radius: 8px; padding: 16px; margin-top: 8px;"><strong style="color: var(--accent-orange);">Want optimized rules for your project?</strong><br>Reach out: <a href="mailto:info@adac1001.com" style="color: var(--accent-cyan);">info@adac1001.com</a></div>'`);
|
|
341
|
+
lines.push(` },`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (fs.existsSync(path.join(claudeDir, '.mcp.json'))) {
|
|
345
|
+
lines.push(` 'mcp': {`);
|
|
346
|
+
lines.push(` title: '.mcp.json',`);
|
|
347
|
+
lines.push(` desc: 'MCP (Model Context Protocol) server configuration. Add your MCP servers here.'`);
|
|
348
|
+
lines.push(` },`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (fs.existsSync(path.join(claudeDir, 'settings.json'))) {
|
|
352
|
+
lines.push(` 'settings': {`);
|
|
353
|
+
lines.push(` title: 'settings.json',`);
|
|
354
|
+
lines.push(` desc: 'Permissions and security settings. Controls what Claude can auto-approve (allow) and what is always blocked (deny).'`);
|
|
355
|
+
lines.push(` }`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return lines.join('\n');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Generate fileContents JS entries.
|
|
363
|
+
*/
|
|
364
|
+
function generateFileContents(entries) {
|
|
365
|
+
const lines = [];
|
|
366
|
+
|
|
367
|
+
for (const entry of entries) {
|
|
368
|
+
if (entry.isEmptyFolder) {
|
|
369
|
+
lines.push(` '${entry.key}': {`);
|
|
370
|
+
lines.push(` filename: '${entry.key}/',`);
|
|
371
|
+
lines.push(` content: null,`);
|
|
372
|
+
lines.push(` empty: true,`);
|
|
373
|
+
lines.push(` emptyMessage: '${entry.emptyMessage.replace(/'/g, "\\'")}'`);
|
|
374
|
+
lines.push(` },`);
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const escaped = escapeTemplateLiteral(entry.preview);
|
|
379
|
+
lines.push(` '${entry.key}': {`);
|
|
380
|
+
lines.push(` filename: '${entry.file}',`);
|
|
381
|
+
lines.push(' content: `' + escaped + '`');
|
|
382
|
+
lines.push(` },`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Folder-level preview entries for hooks
|
|
386
|
+
lines.push(` 'hooks': {`);
|
|
387
|
+
lines.push(` filename: 'hooks/',`);
|
|
388
|
+
lines.push(` content: null,`);
|
|
389
|
+
lines.push(` empty: true,`);
|
|
390
|
+
lines.push(` emptyMessage: 'Contains executable hook scripts that trigger on Claude Code events.'`);
|
|
391
|
+
lines.push(` },`);
|
|
392
|
+
|
|
393
|
+
// Static entries
|
|
394
|
+
lines.push(` 'docs': {`);
|
|
395
|
+
lines.push(` filename: 'autoconfig.docs.html',`);
|
|
396
|
+
lines.push(` content: null,`);
|
|
397
|
+
lines.push(` empty: true,`);
|
|
398
|
+
lines.push(` emptyMessage: "You\\\'re looking at it! 👀"`);
|
|
399
|
+
lines.push(` },`);
|
|
400
|
+
|
|
401
|
+
if (entries.some(e => e.key === 'rules')) {
|
|
402
|
+
lines.push(` 'rules': {`);
|
|
403
|
+
lines.push(` filename: 'rules/',`);
|
|
404
|
+
lines.push(` content: null,`);
|
|
405
|
+
lines.push(` empty: true,`);
|
|
406
|
+
lines.push(` emptyMessage: 'This directory is empty.\\nAdd .md files here to define rules for specific paths in your codebase.'`);
|
|
407
|
+
lines.push(` },`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// settings.json — read actual content
|
|
411
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
412
|
+
if (fs.existsSync(settingsPath)) {
|
|
413
|
+
const content = fs.readFileSync(settingsPath, 'utf8').trim();
|
|
414
|
+
lines.push(` 'settings': {`);
|
|
415
|
+
lines.push(` filename: 'settings.json',`);
|
|
416
|
+
lines.push(' content: `' + escapeTemplateLiteral(content) + '`');
|
|
417
|
+
lines.push(` },`);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// .mcp.json — read actual content
|
|
421
|
+
const mcpPath = path.join(claudeDir, '.mcp.json');
|
|
422
|
+
if (fs.existsSync(mcpPath)) {
|
|
423
|
+
const content = fs.readFileSync(mcpPath, 'utf8').trim();
|
|
424
|
+
lines.push(` 'mcp': {`);
|
|
425
|
+
lines.push(` filename: '.mcp.json',`);
|
|
426
|
+
lines.push(' content: `' + escapeTemplateLiteral(content) + '`');
|
|
427
|
+
lines.push(` }`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return lines.join('\n');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// =============================================================================
|
|
434
|
+
// Main
|
|
435
|
+
// =============================================================================
|
|
436
|
+
|
|
437
|
+
const entries = scanFiles();
|
|
438
|
+
let html = fs.readFileSync(docsPath, 'utf8');
|
|
439
|
+
|
|
440
|
+
// 1. Replace the file tree (between claude-dir folder row and settings.json closing div)
|
|
441
|
+
// We find the marker after the claude-dir folder and replace up to the settings div
|
|
442
|
+
const treeStartMarker = '<span class="folder">.claude</span>';
|
|
443
|
+
const treeStartIdx = html.indexOf(treeStartMarker);
|
|
444
|
+
if (treeStartIdx === -1) {
|
|
445
|
+
console.error('Could not find .claude folder marker in docs HTML');
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
// Find the closing </div> of the claude-dir folder row
|
|
449
|
+
const claudeDirClose = html.indexOf('</div>', treeStartIdx);
|
|
450
|
+
const treeContentStart = claudeDirClose + '</div>'.length;
|
|
451
|
+
|
|
452
|
+
// Find the end of the tree: look for the closing of tree-side/tree-content after settings.json
|
|
453
|
+
const treeEndMarker = '</div>\n </div>\n <div class="info-side">';
|
|
454
|
+
const treeEndIdx = html.indexOf(treeEndMarker, treeContentStart);
|
|
455
|
+
if (treeEndIdx === -1) {
|
|
456
|
+
console.error('Could not find tree end marker in docs HTML');
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const newTreeHtml = generateTreeHtml(entries);
|
|
461
|
+
html = html.slice(0, treeContentStart) + '\n' + newTreeHtml + '\n ' + html.slice(treeEndIdx);
|
|
462
|
+
|
|
463
|
+
// 2. Replace treeInfo (between structural entries and closing })
|
|
464
|
+
// We keep memory-md, root, claude-md, claude-dir and replace everything after
|
|
465
|
+
const treeInfoStartMarker = "// Tree panel info data";
|
|
466
|
+
let treeInfoIdx = html.indexOf(treeInfoStartMarker);
|
|
467
|
+
if (treeInfoIdx === -1) {
|
|
468
|
+
// Try alternate marker
|
|
469
|
+
treeInfoIdx = html.indexOf("const treeInfo = {");
|
|
470
|
+
}
|
|
471
|
+
if (treeInfoIdx === -1) {
|
|
472
|
+
console.error('Could not find treeInfo in docs HTML');
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Find the claude-dir entry end (last structural entry)
|
|
477
|
+
const claudeDirInfoMarker = "'claude-dir': {";
|
|
478
|
+
const claudeDirInfoIdx = html.indexOf(claudeDirInfoMarker, treeInfoIdx);
|
|
479
|
+
if (claudeDirInfoIdx === -1) {
|
|
480
|
+
console.error('Could not find claude-dir info entry');
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
// Find the closing }, of the claude-dir entry
|
|
484
|
+
let braceDepth = 0;
|
|
485
|
+
let i = claudeDirInfoIdx;
|
|
486
|
+
while (i < html.length) {
|
|
487
|
+
if (html[i] === '{') braceDepth++;
|
|
488
|
+
if (html[i] === '}') {
|
|
489
|
+
braceDepth--;
|
|
490
|
+
if (braceDepth === 0) break;
|
|
491
|
+
}
|
|
492
|
+
i++;
|
|
493
|
+
}
|
|
494
|
+
// Skip past the closing },
|
|
495
|
+
const afterClaudeDir = i + 1;
|
|
496
|
+
// Skip whitespace/comma
|
|
497
|
+
let treeInfoInsertPoint = afterClaudeDir;
|
|
498
|
+
while (treeInfoInsertPoint < html.length && /[\s,]/.test(html[treeInfoInsertPoint])) {
|
|
499
|
+
treeInfoInsertPoint++;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Find the closing }; of treeInfo
|
|
503
|
+
const treeInfoEnd = html.indexOf('};', treeInfoInsertPoint);
|
|
504
|
+
if (treeInfoEnd === -1) {
|
|
505
|
+
console.error('Could not find treeInfo closing');
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const newTreeInfo = generateTreeInfo(entries);
|
|
510
|
+
html = html.slice(0, treeInfoInsertPoint) + newTreeInfo + '\n ' + html.slice(treeInfoEnd);
|
|
511
|
+
|
|
512
|
+
// 3. Replace fileContents
|
|
513
|
+
// Keep memory-md and claude-md (structural), replace the rest
|
|
514
|
+
const fileContentsMarker = "const fileContents = {";
|
|
515
|
+
const fcIdx = html.indexOf(fileContentsMarker);
|
|
516
|
+
if (fcIdx === -1) {
|
|
517
|
+
console.error('Could not find fileContents in docs HTML');
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Find claude-md entry end (last structural fileContents entry)
|
|
522
|
+
const claudeMdFcMarker = "'claude-md': {";
|
|
523
|
+
const claudeMdFcIdx = html.indexOf(claudeMdFcMarker, fcIdx);
|
|
524
|
+
if (claudeMdFcIdx === -1) {
|
|
525
|
+
console.error('Could not find claude-md fileContents entry');
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
braceDepth = 0;
|
|
529
|
+
i = claudeMdFcIdx;
|
|
530
|
+
while (i < html.length) {
|
|
531
|
+
if (html[i] === '{') braceDepth++;
|
|
532
|
+
if (html[i] === '}') {
|
|
533
|
+
braceDepth--;
|
|
534
|
+
if (braceDepth === 0) break;
|
|
535
|
+
}
|
|
536
|
+
i++;
|
|
537
|
+
}
|
|
538
|
+
const afterClaudeMdFc = i + 1;
|
|
539
|
+
let fcInsertPoint = afterClaudeMdFc;
|
|
540
|
+
while (fcInsertPoint < html.length && /[\s,]/.test(html[fcInsertPoint])) {
|
|
541
|
+
fcInsertPoint++;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const fcEnd = html.indexOf('};', fcInsertPoint);
|
|
545
|
+
if (fcEnd === -1) {
|
|
546
|
+
console.error('Could not find fileContents closing');
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const newFileContents = generateFileContents(entries);
|
|
551
|
+
html = html.slice(0, fcInsertPoint) + newFileContents + '\n ' + html.slice(fcEnd);
|
|
552
|
+
|
|
553
|
+
fs.writeFileSync(docsPath, html);
|
|
554
|
+
|
|
555
|
+
const fileCount = entries.filter(e => !e.isEmptyFolder).length;
|
|
556
|
+
console.log(`Synced ${fileCount} files to docs.`);
|