@iamdangavin/claude-skill-vitepress-docs 1.0.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 (3) hide show
  1. package/install.js +17 -0
  2. package/package.json +21 -0
  3. package/skill/SKILL.md +759 -0
package/install.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, copyFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { homedir } from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ const skillName = 'vitepress-docs';
10
+ const dest = join(homedir(), '.claude', 'skills', skillName);
11
+
12
+ mkdirSync(dest, { recursive: true });
13
+ copyFileSync(join(__dirname, 'skill', 'SKILL.md'), join(dest, 'SKILL.md'));
14
+
15
+ console.log(`✓ Installed @iamdangavin/claude-skill-vitepress-docs`);
16
+ console.log(` → ${dest}/SKILL.md`);
17
+ console.log(` Invoke with: /vitepress-docs in Claude Code`);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@iamdangavin/claude-skill-vitepress-docs",
3
+ "version": "1.0.0",
4
+ "description": "Installs the vitepress:docs Claude Code skill — VitePress docs setup, generation, screenshots, and sync.",
5
+ "type": "module",
6
+ "bin": {
7
+ "claude-skill-vitepress-docs": "./install.js"
8
+ },
9
+ "files": [
10
+ "install.js",
11
+ "skill/"
12
+ ],
13
+ "keywords": [
14
+ "claude",
15
+ "claude-code",
16
+ "claude-skill",
17
+ "vitepress",
18
+ "docs"
19
+ ],
20
+ "license": "MIT"
21
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,759 @@
1
+ ---
2
+ name: vitepress:docs
3
+ description: VitePress documentation suite — setup GitHub Pages deployment, generate docs from a codebase, capture screenshots of any running app, and sync docs as code changes.
4
+ argument-hint: [mode: setup|generate|screenshot|sync] or omit to be prompted
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Glob
11
+ - Grep
12
+ - AskUserQuestion
13
+ ---
14
+
15
+ # Skill: vitepress:docs
16
+
17
+ Four modes for full VitePress documentation lifecycle. If no mode is given as an argument, use AskUserQuestion to prompt for one:
18
+
19
+ - header: "vitepress:docs"
20
+ - question: "Which mode do you need?"
21
+ - options:
22
+ - "setup — Wire up VitePress + GitHub Actions for Pages deployment"
23
+ - "generate — Analyze the codebase and write documentation pages"
24
+ - "screenshot — Capture real screenshots and replace placeholders"
25
+ - "sync — Detect code drift and update docs that are out of date"
26
+
27
+ ---
28
+
29
+ ## Manifest
30
+
31
+ All modes read and write a manifest at `.vitepress/docs-manifest.json`. This file is **local-only** — always add it to `.gitignore` (under the docs folder's nearest `.gitignore`) so it is never committed or distributed.
32
+
33
+ Add this line if not already present:
34
+ ```
35
+ .vitepress/docs-manifest.json
36
+ ```
37
+
38
+ ### Manifest schema
39
+
40
+ ```json
41
+ {
42
+ "generated": "ISO-8601 timestamp",
43
+ "project": {
44
+ "type": "user-facing | developer | both",
45
+ "baseUrl": "http://localhost:3000",
46
+ "serverType": "node | wordpress | static | other",
47
+ "startCommand": "npm run dev"
48
+ },
49
+ "pages": [
50
+ {
51
+ "file": "docs/guides/timer.md",
52
+ "title": "Running a Timer",
53
+ "docType": "user-facing",
54
+ "sources": ["src/state/timer.js", "src/components/layouts/workouts.jsx"],
55
+ "images": [
56
+ {
57
+ "path": "docs/public/screenshots/timer-main.png",
58
+ "placeholder": true,
59
+ "caption": "Timer in active interval state",
60
+ "captureUrl": "/workouts/123",
61
+ "captureNote": "Timer should be running with intervals visible"
62
+ }
63
+ ],
64
+ "lastSynced": "ISO-8601 timestamp",
65
+ "syncHash": "sha of source file contents at last sync"
66
+ }
67
+ ]
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Mode: setup
74
+
75
+ ### Step 1 — Gather project details
76
+
77
+ Use AskUserQuestion for each choice below. For freeform text inputs (repo URL, domain name, docs path) ask as plain text immediately after the relevant choice is made.
78
+
79
+ **Q1 — VitePress status:**
80
+ - header: "VitePress"
81
+ - question: "Is VitePress already installed in this project?"
82
+ - options:
83
+ - "Already installed"
84
+ - "Starting from scratch"
85
+
86
+ **Q2 — Docs folder** (plain text): Ask — "Where do the docs live (or where should they go)? e.g. `docs/` at project root, or the repo root itself."
87
+
88
+ **Q3 — GitHub repo** (plain text): Ask — "What is the GitHub repo? (full URL or `owner/repo`)"
89
+
90
+ **Q4 — Domain type:**
91
+ - header: "Hosting"
92
+ - question: "Where will the docs be served?"
93
+ - options:
94
+ - "Custom domain I own (e.g. docs.myapp.com)"
95
+ - "GitHub Pages subdomain (e.g. owner.github.io/repo-name)"
96
+
97
+ If custom domain: ask as plain text — "What is the domain? (e.g. `docs.myapp.com`)"
98
+
99
+ **Q5 — Deploy trigger:**
100
+ - header: "Deploy trigger"
101
+ - question: "When should docs deploy?"
102
+ - options:
103
+ - "Every push to main"
104
+ - "Every push to a specific branch — I'll tell you which"
105
+ - "Only when a version tag is pushed (v*)"
106
+ - "Manual only (workflow_dispatch)"
107
+
108
+ If specific branch: ask as plain text — "Which branch?"
109
+
110
+ **Q6 — Docs on GitHub:**
111
+ - header: "Repo state"
112
+ - question: "Are the docs already committed and pushed to GitHub?"
113
+ - options:
114
+ - "Yes, already pushed"
115
+ - "No, this is all new"
116
+
117
+ Wait for all answers before proceeding.
118
+
119
+ ### Step 2 — Prerequisites checklist
120
+
121
+ Present the relevant checklist as plain text, then use AskUserQuestion to confirm before writing files:
122
+
123
+ - header: "Ready to proceed?"
124
+ - question: "Have you completed the steps above?"
125
+ - options:
126
+ - "Yes, all done — generate the files"
127
+ - "Not yet — I need more time"
128
+ - "I have a question"
129
+
130
+ If "Not yet": tell the user to run `/vitepress:docs setup` again when ready and exit.
131
+ If "I have a question": answer it, then re-ask this question.
132
+
133
+ The checklist to present (tailor to their answers):
134
+
135
+ **Always required:**
136
+ - [ ] GitHub repo exists with push access
137
+ - [ ] In repo **Settings → Pages**, set Source to **"GitHub Actions"**
138
+ _(You do not need to configure anything else here — the workflow handles the entire deployment. Just flip the source and leave everything else alone.)_
139
+
140
+ **If custom domain:**
141
+ - [ ] CNAME DNS record: `docs.yourdomain.com` → `<owner>.github.io`
142
+ - [ ] In repo **Settings → Pages → Custom domain**, enter the domain after DNS propagates
143
+ - [ ] TLS cert will provision automatically after first deploy
144
+
145
+ **If VitePress not installed:**
146
+ - [ ] Node.js 18+ installed
147
+
148
+ Before proceeding to Step 3, ask permission to handle the docs scaffold automatically:
149
+
150
+ - header: "Docs scaffold"
151
+ - question: "VitePress isn't installed yet. Can I create the docs folder and run the install for you?"
152
+ - options:
153
+ - "Yes — set it up for me"
154
+ - "No — I'll do it myself"
155
+
156
+ If yes, run:
157
+ ```bash
158
+ mkdir -p DOCS_FOLDER
159
+ cd DOCS_FOLDER && npm init -y && npm install -D vitepress
160
+ ```
161
+ Then confirm it succeeded before continuing.
162
+ If no, tell the user to run those two commands in the docs folder and come back when done.
163
+
164
+ **Playwright (required for screenshot mode):**
165
+
166
+ Check if it's already installed:
167
+ ```bash
168
+ ls /opt/homebrew/lib/node_modules/playwright/index.mjs 2>/dev/null && echo "Found" || echo "Not found"
169
+ ```
170
+
171
+ If not found, install it:
172
+ ```bash
173
+ brew install playwright
174
+ playwright install chrome
175
+ ```
176
+
177
+ If the user is **not on macOS / Homebrew**, note that the screenshot mode hardcodes the Homebrew path. They'll need to find their global Playwright path (`npm root -g`) and will have to update the import line in any capture scripts the skill generates. Offer to help with that when screenshot mode is first used.
178
+
179
+ ### Step 3 — Generate files
180
+
181
+ **`.github/workflows/docs.yml`** (at repo root, not inside docs folder):
182
+
183
+ ```yaml
184
+ name: Deploy Docs
185
+
186
+ on:
187
+ push:
188
+ branches: [BRANCH] # from answer 5
189
+ workflow_dispatch:
190
+
191
+ permissions:
192
+ contents: read
193
+ pages: write
194
+ id-token: write
195
+
196
+ concurrency:
197
+ group: pages
198
+ cancel-in-progress: false
199
+
200
+ jobs:
201
+ build:
202
+ runs-on: ubuntu-latest
203
+ steps:
204
+ - uses: actions/checkout@v4
205
+ with:
206
+ fetch-depth: 0
207
+ - uses: actions/setup-node@v4
208
+ with:
209
+ node-version: 20
210
+ cache: npm
211
+ cache-dependency-path: DOCS_FOLDER/package-lock.json
212
+ - run: npm ci
213
+ working-directory: DOCS_FOLDER
214
+ - run: npm run build
215
+ working-directory: DOCS_FOLDER
216
+ - uses: actions/upload-pages-artifact@v3
217
+ with:
218
+ path: DOCS_FOLDER/.vitepress/dist
219
+
220
+ deploy:
221
+ environment:
222
+ name: github-pages
223
+ url: ${{ steps.deployment.outputs.page_url }}
224
+ needs: build
225
+ runs-on: ubuntu-latest
226
+ steps:
227
+ - uses: actions/deploy-pages@v4
228
+ id: deployment
229
+ ```
230
+
231
+ Fill `BRANCH` and `DOCS_FOLDER` from answers. For tag-based triggers replace `branches` with `tags: ['v*']`. For manual-only, remove the `push:` block.
232
+
233
+ **VitePress config updates:**
234
+
235
+ Read the existing config before editing. Make only these targeted changes:
236
+
237
+ 1. Set `base`:
238
+ - Custom domain → `base: '/'`
239
+ - GitHub subdomain → `base: '/repo-name/'`
240
+
241
+ 2. Add PostCSS override if not already present. This prevents VitePress from inheriting a PostCSS config from a parent project (e.g. a Next.js or Laravel root that uses Tailwind), which would cause the Actions build to fail with a "Cannot find module" error:
242
+ ```js
243
+ vite: {
244
+ css: {
245
+ postcss: {},
246
+ },
247
+ },
248
+ ```
249
+ Always add this — it is harmless when there is no parent PostCSS config and critical when there is.
250
+
251
+ **`DOCS_FOLDER/public/CNAME`** (custom domain only):
252
+ ```
253
+ docs.yourdomain.com
254
+ ```
255
+
256
+ **`.gitignore` check:** ensure `node_modules` and `.vitepress/cache` are present.
257
+
258
+ ### Step 4 — Summary
259
+
260
+ List files written and give the user a next-steps checklist with the expected deploy URL.
261
+
262
+ ---
263
+
264
+ ## Mode: generate
265
+
266
+ ### Step 0 — Detect tech stack
267
+
268
+ Before asking any questions, scan the codebase root for stack markers. Use Glob and Grep — do not ask the user what their stack is until you've made an attempt to detect it yourself.
269
+
270
+ **WordPress indicators** — check for any of these:
271
+ ```
272
+ wp-config.php
273
+ wp-content/
274
+ wp-includes/
275
+ functions.php (in a theme directory)
276
+ style.css with "Theme Name:" header
277
+ composer.json with "johnpbloch/wordpress" or "roots/wordpress"
278
+ ```
279
+
280
+ **Node/Next indicators:**
281
+ ```
282
+ next.config.js / next.config.ts / next.config.mjs
283
+ package.json with "next" dependency
284
+ .next/ directory
285
+ ```
286
+
287
+ **Other Node indicators:** `package.json`, `vite.config.*`, `astro.config.*`, `nuxt.config.*`
288
+
289
+ **Static:** no package.json, no wp-config.php, only HTML/CSS/JS files.
290
+
291
+ After scanning, present your finding and confirm with the user:
292
+
293
+ - header: "Tech stack detected"
294
+ - question: "I detected this as a [DETECTED_STACK] project — is that right?"
295
+ - options:
296
+ - "Yes, that's correct"
297
+ - "No — let me tell you what it actually is"
298
+
299
+ If the user corrects you, accept their answer and proceed with the corrected stack.
300
+
301
+ ---
302
+
303
+ **If WordPress is detected**, ask the following WP-specific questions before running the git root check:
304
+
305
+ **WP-Q1 — WordPress project type:**
306
+ - header: "WordPress setup"
307
+ - question: "What kind of WordPress project is this?"
308
+ - options:
309
+ - "Traditional theme (PHP templates, The Loop, etc.)"
310
+ - "Block theme (Full Site Editing / Gutenberg blocks)"
311
+ - "Headless WordPress (REST API or WPGraphQL feeding a front-end)"
312
+ - "Plugin (documenting a plugin, not a theme)"
313
+ - "Full site — theme + plugins together"
314
+
315
+ **WP-Q2 — Local dev environment** (plain text): Ask — "What URL is the WordPress site running on locally? (e.g. `http://localhost:8888`, `http://mysite.local`)"
316
+
317
+ **WP-Q3 — Page builder / block editor:**
318
+ - header: "Page builder"
319
+ - question: "Is this site built with a page builder or block toolkit?"
320
+ - options:
321
+ - "Standard Gutenberg / block editor only"
322
+ - "Elementor"
323
+ - "Divi"
324
+ - "ACF Blocks (Advanced Custom Fields)"
325
+ - "Other — I'll tell you"
326
+ - "No page builder — mostly PHP templates"
327
+
328
+ **WP-Q4 — Key plugins to document** (plain text): Ask — "Are there any plugins that are central to how the site works and should be included in the docs? (e.g. WooCommerce, ACF, Yoast — or 'none')"
329
+
330
+ **WP-Q5 — Headless front-end** (only if headless was selected in WP-Q1):
331
+ - header: "Front-end"
332
+ - question: "What is the headless front-end stack?"
333
+ - options:
334
+ - "Next.js"
335
+ - "Nuxt"
336
+ - "SvelteKit"
337
+ - "Astro"
338
+ - "Other — I'll tell you"
339
+
340
+ **WP-Q6 — Theme / plugin folder** (plain text): Scan `wp-content/themes/` and `wp-content/plugins/` for likely candidates first — present your best guess and let the user confirm or correct it. Ask — "Which theme or plugin should I focus on? (e.g. `wp-content/themes/my-theme` or `wp-content/plugins/my-plugin`)"
341
+
342
+ Once WP-Q6 is answered, the **focus path** is now the theme/plugin folder — not the WP install root. All subsequent analysis, docs placement, and git root detection use this path as the base.
343
+
344
+ Store all WP answers and carry them through the rest of generate mode. They affect codebase analysis (Step 2) and the doc structure proposal (Step 3).
345
+
346
+ ---
347
+
348
+ #### Git root detection (all stacks)
349
+
350
+ **For WordPress:** run this check against the theme/plugin path confirmed in WP-Q6.
351
+ **For all other stacks:** run this check against the detected codebase root (current working directory unless corrected above).
352
+
353
+ Check for a `.git` directory at **that exact path**:
354
+
355
+ ```bash
356
+ ls FOCUS_PATH/.git
357
+ ```
358
+
359
+ Do not check parent folders — the `.git` must be in the specific folder being documented. For WordPress this means checking inside `wp-content/themes/my-theme/` or `wp-content/plugins/my-plugin/`, not in `wp-content/themes/`, `wp-content/`, or the WP install root.
360
+
361
+ **If `.git` is found at the focus path:** no question needed — the repo root and focus path are the same. Proceed.
362
+
363
+ **If `.git` is not found:** ask:
364
+
365
+ - header: "Git root"
366
+ - question: "I didn't find a `.git` folder at [FOCUS_PATH]. Where is the git root for this project?"
367
+ - options:
368
+ - "It's at the WP install root (the folder I was invoked from)"
369
+ - "It's somewhere else — I'll tell you the path"
370
+ - "There's no git repo yet"
371
+
372
+ Then ask as plain text — "Where inside the git repo should the docs folder and VitePress config live? (Default: `docs/` inside [FOCUS_PATH].)"
373
+
374
+ This matters because the GitHub Actions workflow file must go in `.github/workflows/` at the **git root**, even if the VitePress config and docs folder live deeper inside it (e.g. inside the theme or plugin subfolder).
375
+
376
+ ---
377
+
378
+ ### Step 1 — Gather project details
379
+
380
+ **Q1 — Doc type:**
381
+ - header: "Documentation type"
382
+ - question: "What type of docs should I generate?"
383
+ - options:
384
+ - "User-facing guides — how to use the app"
385
+ - "Developer reference — architecture, API, data layer"
386
+ - "Both"
387
+
388
+ **Q2 — App description** (plain text): Ask — "What is this app called, and what does it do in one sentence? (Used for headings and introductions.)"
389
+
390
+ **Q3 — Codebase root** (plain text): Ask — "Where is the codebase root? (Press enter to use the current directory.)" — **Skip for WordPress; already established by WP-Q6.**
391
+
392
+ **Q4 — Docs output** (plain text): Ask — "Where should docs be written? (Default: `docs/` inside [FOCUS_PATH].)" — For WordPress, default to `docs/` inside the theme/plugin folder from WP-Q6, not the WP install root.
393
+
394
+ **Q5 — Skip anything:**
395
+ - header: "Exclusions"
396
+ - question: "Anything to exclude from analysis? (node_modules, dist, .git, .next, coverage are always skipped.)"
397
+ - options:
398
+ - "No, defaults are fine"
399
+ - "Yes — I'll tell you what to skip"
400
+
401
+ If yes: ask as plain text — "Which folders or files should I skip?"
402
+
403
+ Wait for all answers.
404
+
405
+ ### Step 2 — Analyze the codebase
406
+
407
+ Scan the codebase systematically. Build a mental map before writing anything. Look for:
408
+
409
+ **For user-facing docs:**
410
+ - App routes / pages (what screens exist)
411
+ - Key user flows (what can a user do)
412
+ - Forms and interactive features
413
+ - Settings and configuration surfaces
414
+
415
+ **For developer docs:**
416
+ - Entry points and routing structure
417
+ - Data layer (API routes, DB queries, service modules)
418
+ - State management
419
+ - Auth and middleware
420
+ - Reusable utilities and lib functions
421
+ - Environment variable requirements
422
+
423
+ Use Glob and Grep to find these. Do not read every file — read enough to understand what each area does.
424
+
425
+ ### Step 3 — Plan the doc structure
426
+
427
+ Before writing any pages, output a proposed outline:
428
+
429
+ Present the proposed outline as plain text, then use AskUserQuestion to confirm:
430
+
431
+ - header: "Doc structure"
432
+ - question: "Does this outline look right?"
433
+ - options:
434
+ - "Looks good — start writing"
435
+ - "I want to make changes"
436
+
437
+ If changes: ask as plain text — "What would you like to add, remove, or rename?" Then re-present the updated outline and ask again.
438
+
439
+ ### Step 4 — Write pages
440
+
441
+ Write pages one at a time. For each page:
442
+
443
+ 1. Read the relevant source files
444
+ 2. Write the markdown based on what you can confidently determine
445
+ 3. Where information is unclear or missing, insert a gap comment and continue:
446
+ ```md
447
+ <!-- GAP: What are the valid values for rotation frequency? -->
448
+ ```
449
+ 4. Where a screenshot would help understanding, insert a placeholder image and record it in the manifest:
450
+ ```md
451
+ ![Timer in active interval state](../public/screenshots/timer-main.png)
452
+ ```
453
+ Then generate the placeholder PNG (see **Placeholder generation** below).
454
+
455
+ Do not stop to ask gap questions mid-page. Keep writing and accumulate all gaps.
456
+
457
+ ### Step 5 — Gap review
458
+
459
+ After all pages are written, present all gaps in one batch:
460
+
461
+ ```
462
+ I've written all X pages. I hit N gaps where I couldn't confidently determine
463
+ the correct information. Can you fill these in?
464
+
465
+ 1. [guides/timer.md] What are the valid values for rotation frequency?
466
+ 2. [developer/auth.md] Is the OAuth callback scoped to a single provider or does it support multiple?
467
+ ...
468
+ ```
469
+
470
+ Go back and fill in the answers the user provides.
471
+
472
+ ### Step 6 — Update VitePress config sidebar
473
+
474
+ Read the existing `.vitepress/config.mjs` (or `.ts`) and update the `sidebar` and `nav` to include all newly generated pages. Edit the config in place — do not rewrite unrelated sections.
475
+
476
+ ### Step 7 — Write the manifest
477
+
478
+ Write `.vitepress/docs-manifest.json` capturing all pages, their source file mappings, image placeholder status, and a `syncHash` (SHA of source file contents at generation time — use a simple string hash if needed).
479
+
480
+ Add `docs-manifest.json` to `.vitepress/` entry in `.gitignore`.
481
+
482
+ ### Step 8 — Summary
483
+
484
+ ```
485
+ Generated X pages (Y user-facing, Z developer).
486
+ Created N placeholder screenshots — run /vitepress:docs screenshot when ready to capture real images.
487
+ Filled M of P gaps — X remaining gap comments left in files for manual review.
488
+ ```
489
+
490
+ ---
491
+
492
+ ## Placeholder generation
493
+
494
+ When a page needs a screenshot, generate a 1200×630 grey PNG at the specified path before moving on.
495
+
496
+ Try in order:
497
+
498
+ **Option A — ImageMagick:**
499
+ ```bash
500
+ convert -size 1200x630 xc:'#888888' path/to/placeholder.png
501
+ ```
502
+
503
+ **Option B — Node.js (no external deps):**
504
+
505
+ Write this to `/tmp/make-placeholder.mjs`, run it, then delete it:
506
+
507
+ ```js
508
+ import zlib from 'zlib';
509
+ import { writeFileSync, mkdirSync } from 'fs';
510
+ import { dirname } from 'path';
511
+
512
+ const W = 1200, H = 630;
513
+ const OUTPUT = 'FILL_IN_OUTPUT_PATH';
514
+
515
+ const scanlines = [];
516
+ for (let y = 0; y < H; y++) {
517
+ const row = Buffer.alloc(1 + W * 3);
518
+ row[0] = 0; // filter type: None
519
+ row.fill(0x88, 1); // grey RGB pixels
520
+ scanlines.push(row);
521
+ }
522
+ const raw = Buffer.concat(scanlines);
523
+ const compressed = zlib.deflateSync(raw, { level: 9 });
524
+
525
+ const crcTable = new Uint32Array(256);
526
+ for (let i = 0; i < 256; i++) {
527
+ let c = i;
528
+ for (let j = 0; j < 8; j++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
529
+ crcTable[i] = c;
530
+ }
531
+ function crc32(buf) {
532
+ let c = 0xFFFFFFFF;
533
+ for (const b of buf) c = crcTable[(c ^ b) & 0xFF] ^ (c >>> 8);
534
+ return (c ^ 0xFFFFFFFF) >>> 0;
535
+ }
536
+ function makeChunk(type, data) {
537
+ const t = Buffer.from(type, 'ascii');
538
+ const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
539
+ const crcVal = Buffer.alloc(4); crcVal.writeUInt32BE(crc32(Buffer.concat([t, data])));
540
+ return Buffer.concat([len, t, data, crcVal]);
541
+ }
542
+
543
+ const sig = Buffer.from([137,80,78,71,13,10,26,10]);
544
+ const ihdr = Buffer.alloc(13);
545
+ ihdr.writeUInt32BE(W, 0); ihdr.writeUInt32BE(H, 4);
546
+ ihdr[8] = 8; ihdr[9] = 2; // 8-bit RGB
547
+
548
+ mkdirSync(dirname(OUTPUT), { recursive: true });
549
+ writeFileSync(OUTPUT, Buffer.concat([
550
+ sig,
551
+ makeChunk('IHDR', ihdr),
552
+ makeChunk('IDAT', compressed),
553
+ makeChunk('IEND', Buffer.alloc(0))
554
+ ]));
555
+ console.log('Created', OUTPUT);
556
+ ```
557
+
558
+ If both options fail, tell the user and skip the placeholder for that image — leave the `![...](path)` reference in the markdown so it renders as a broken image, making it obvious during preview.
559
+
560
+ ---
561
+
562
+ ## Mode: screenshot
563
+
564
+ ### Step 1 — Gather capture details
565
+
566
+ **Q1 — Source:**
567
+ - header: "Capture source"
568
+ - question: "Where should I capture screenshots from?"
569
+ - options:
570
+ - "Local dev server — I'll capture from a running server"
571
+ - "Deployed URL — give me the base URL"
572
+
573
+ If deployed: ask as plain text — "What is the base URL? (e.g. `https://docs.myapp.com`)"
574
+
575
+ **Q2 — Stack type** (only if local):
576
+ - header: "Project type"
577
+ - question: "What type of project is this?"
578
+ - options:
579
+ - "Node/npm — you can optionally start the server for me"
580
+ - "WordPress, PHP, or other non-Node stack"
581
+ - "Static files"
582
+
583
+ **Q3 — Server status** (only if local Node):
584
+ - header: "Dev server"
585
+ - question: "Is the dev server already running?"
586
+ - options:
587
+ - "Already running"
588
+ - "Not running — please start it for me"
589
+
590
+ If starting: ask as plain text — "What command starts it? And what URL/port does it run on?"
591
+
592
+ **Q4 — Authentication:**
593
+ - header: "Login required?"
594
+ - question: "Does reaching the screens that need screenshots require login?"
595
+ - options:
596
+ - "No — all target screens are public"
597
+ - "Yes — I'll provide credentials"
598
+
599
+ If yes: ask as plain text — "Please provide the username and password. ⚠️ Credentials are used only to drive the browser in this session — they will NEVER be written to any file, the manifest, or anywhere outside this conversation."
600
+
601
+ **Q5 — Scope:**
602
+ - header: "Capture scope"
603
+ - question: "Which screenshots should I capture?"
604
+ - options:
605
+ - "All placeholders from the manifest"
606
+ - "Specific pages only — I'll tell you which"
607
+
608
+ If specific: ask as plain text — "Which pages or screenshots?"
609
+
610
+ Wait for all answers.
611
+
612
+ ### Step 2 — Find all placeholders
613
+
614
+ Read `.vitepress/docs-manifest.json`. Filter to all images where `"placeholder": true`. Group by the doc page they belong to.
615
+
616
+ If no manifest exists, scan all markdown files in the docs folder for images referencing `public/screenshots/` — treat all of them as needing capture.
617
+
618
+ ### Step 3 — Capture screenshots
619
+
620
+ For each placeholder, refer to the manifest's `captureUrl` and `captureNote` fields to understand what state to capture.
621
+
622
+ Use Playwright via Homebrew for all captures:
623
+ ```js
624
+ import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
625
+ ```
626
+
627
+ **⛔ Credential rule — NEVER write credentials to any file.** Do not write them to the capture script, a `.env` file, the manifest, a temp file, or anywhere else. Use them only as inline values passed directly to `page.fill()` calls in the script held in memory. Delete the script immediately after running it (`rm /tmp/screenshot.mjs`). If Playwright needs credentials in a reusable way, use a saved storage state file — but prompt the user for credentials fresh each time rather than caching them.
628
+
629
+ **For pages requiring auth:** if credentials were provided, drive a login flow before navigating to the target URL. If no credentials were given, skip auth-gated pages and list them in the final summary so the user can run screenshot mode again with credentials.
630
+
631
+ **Standard capture script template:**
632
+ ```js
633
+ import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
634
+ const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
635
+ const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
636
+ await page.goto('BASE_URL + captureUrl', { waitUntil: 'load', timeout: 60000 });
637
+ await page.waitForTimeout(2000); // settle time for SPAs
638
+ await page.screenshot({ path: 'OUTPUT_PATH', fullPage: false });
639
+ await browser.close();
640
+ ```
641
+
642
+ Adjust settle time based on stack type. For WordPress/non-SPA sites use 500ms. For Next.js/SPA use 2000–4000ms.
643
+
644
+ After each capture, display the image inline with the Read tool so the user can verify it before moving on.
645
+
646
+ ### Step 4 — Rewrite prose around captured images
647
+
648
+ After capturing an image, go back to the doc page that references it and rewrite the paragraph or section surrounding that image. The placeholder was written with generic framing — now that you can see the actual screenshot, make the description specific and accurate.
649
+
650
+ Read the screenshot with the Read tool to inform the rewrite.
651
+
652
+ ### Step 5 — Update manifest
653
+
654
+ For each successfully captured image, update `"placeholder": false` in the manifest and record the capture timestamp.
655
+
656
+ ### Step 6 — Summary
657
+
658
+ ```
659
+ Captured X of Y screenshots.
660
+ Skipped: [list any skipped with reason]
661
+
662
+ Rewrote prose in X doc pages.
663
+
664
+ Run /vitepress:docs sync after future code changes to keep docs current.
665
+ ```
666
+
667
+ ---
668
+
669
+ ## Mode: sync
670
+
671
+ ### Step 1 — Gather sync preferences
672
+
673
+ **Q1 — Drift detection method:**
674
+ - header: "Sync method"
675
+ - question: "How should I check for drift?"
676
+ - options:
677
+ - "Git diff — compare code changes since last sync (fast)"
678
+ - "Full re-analysis — re-read source files and compare to docs (thorough)"
679
+ - "Both — git diff first, then deep re-analysis on flagged pages"
680
+
681
+ **Q2 — Update style:**
682
+ - header: "Update approach"
683
+ - question: "How should I handle pages that need updating?"
684
+ - options:
685
+ - "Update all automatically, then show me a summary"
686
+ - "Show me each changed page and confirm before updating"
687
+
688
+ Wait for answers.
689
+
690
+ ### Step 2 — Detect drift
691
+
692
+ **Git diff approach:**
693
+ ```bash
694
+ git diff --name-only LAST_SYNC_COMMIT HEAD
695
+ ```
696
+ Get `LAST_SYNC_COMMIT` from the manifest's `generated` timestamp — find the nearest commit at or before that time with `git log --before="TIMESTAMP" -1 --format="%H"`.
697
+
698
+ Cross-reference changed files against the manifest's `sources` arrays to find which doc pages are affected.
699
+
700
+ **Full re-analysis approach:**
701
+ For each page in the manifest, re-read the source files listed in `sources`. Compute a hash of their current contents and compare to `syncHash`. Flag any mismatch as drifted.
702
+
703
+ **Both:** run git diff first for speed, then re-analyze only the flagged pages for depth.
704
+
705
+ ### Step 3 — Report drift before changing anything
706
+
707
+ ```
708
+ Found N pages with drift:
709
+
710
+ guides/timer.md — src/state/timer.js changed (interval logic updated)
711
+ developer/auth.md — src/proxy.js changed (new middleware added)
712
+ developer/api/users.md — src/app/api/users/route.js changed (new endpoint)
713
+
714
+ Images that may need recapture:
715
+ public/screenshots/timer-main.png — source page changed
716
+ ```
717
+
718
+ If the user chose "confirm before each", use AskUserQuestion per page:
719
+ - header: "[filename]"
720
+ - question: "[source file] changed — [brief description of what changed]. Update this page?"
721
+ - options:
722
+ - "Yes — update it"
723
+ - "Skip this one"
724
+ - "Update all remaining without asking"
725
+
726
+ Otherwise update all and summarize at the end.
727
+
728
+ ### Step 4 — Update affected pages
729
+
730
+ For each drifted page:
731
+ 1. Re-read the source files
732
+ 2. Update the relevant sections in the doc
733
+ 3. Add a `<!-- GAP: ... -->` comment if something is now unclear
734
+ 4. If images on the page likely show outdated UI, mark them as `"placeholder": true` again in the manifest and note that they need recapture
735
+
736
+ Do not touch pages that have not drifted.
737
+
738
+ ### Step 5 — Update manifest
739
+
740
+ Update `lastSynced` and `syncHash` for all updated pages. Update the top-level `generated` timestamp.
741
+
742
+ ### Step 6 — Summary
743
+
744
+ ```
745
+ Updated X of N drifted pages.
746
+ X gap comments added for manual review.
747
+ Y screenshots flagged for recapture — run /vitepress:docs screenshot when ready.
748
+ ```
749
+
750
+ ---
751
+
752
+ ## Notes
753
+
754
+ - Never write the GitHub Actions workflow inside the docs folder — it goes in `.github/workflows/` at the repo root.
755
+ - Always read an existing config before editing it — never rewrite the whole file.
756
+ - For non-Node stacks in screenshot mode (WordPress, PHP, static): do not attempt to start a server. Just ask for the URL.
757
+ - `workflow_dispatch` should always be present in the workflow so manual deploys are possible.
758
+ - Do not use `peaceiris/actions-gh-pages` — use `actions/upload-pages-artifact` + `actions/deploy-pages`.
759
+ - The manifest is intentionally not committed. Each environment generates its own. Don't suggest committing it.