@iamdangavin/claude-skill-vitepress-docs 3.0.2 → 3.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.
@@ -0,0 +1,288 @@
1
+ ---
2
+ name: vitedocs:capture
3
+ description: Capture screenshots and record interaction videos for VitePress docs. Uploads videos to GitHub Releases.
4
+ allowed-tools:
5
+ - Read
6
+ - Write
7
+ - Edit
8
+ - Bash
9
+ - Glob
10
+ - Grep
11
+ - AskUserQuestion
12
+ ---
13
+
14
+ # vitedocs:capture
15
+
16
+ This mode reads `.vitepress/docs-manifest.json`. Run `/vitedocs:generate` first if no manifest exists yet.
17
+
18
+ Handles two capture types from the manifest:
19
+ - **screenshot** (`"type": "screenshot"`) — static PNG via Playwright
20
+ - **video** (`"type": "video"`) — recorded interaction with step captions, uploaded to GitHub Releases
21
+
22
+ ---
23
+
24
+ ## Step 1 — Gather capture details
25
+
26
+ **Q1 — Source:**
27
+ - header: "Capture source"
28
+ - question: "Where should I capture from?"
29
+ - options:
30
+ - "Local dev server — I'll capture from a running server"
31
+ - "Deployed URL — give me the base URL"
32
+
33
+ If deployed: ask as plain text — "What is the base URL?"
34
+
35
+ **Q2 — Stack type** (only if local):
36
+ - header: "Project type"
37
+ - question: "What type of project is this?"
38
+ - options:
39
+ - "Node/npm — you can optionally start the server for me"
40
+ - "WordPress, PHP, or other non-Node stack"
41
+ - "Static files"
42
+
43
+ **Q3 — Server status** (only if local Node):
44
+ - header: "Dev server"
45
+ - question: "Is the dev server already running?"
46
+ - options:
47
+ - "Already running"
48
+ - "Not running — please start it for me"
49
+
50
+ If starting: ask as plain text — "What command starts it? And what URL/port does it run on?"
51
+
52
+ **Q4 — Authentication:**
53
+ - header: "Login required?"
54
+ - question: "Does reaching the target screens require login?"
55
+ - options:
56
+ - "No — all target screens are public"
57
+ - "Yes — I'll provide credentials"
58
+
59
+ 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."
60
+
61
+ **Q5 — Scope:**
62
+ - header: "Capture scope"
63
+ - question: "Which items should I capture?"
64
+ - options:
65
+ - "All placeholders from the manifest (screenshots + videos)"
66
+ - "Screenshots only"
67
+ - "Videos only"
68
+ - "Specific items — I'll tell you which"
69
+
70
+ If specific: ask as plain text — "Which pages or capture names?"
71
+
72
+ **Q6 — GitHub repo** (ask only if manifest contains any video entries in scope):
73
+ - header: "GitHub repo"
74
+ - question: "Videos are uploaded to GitHub Releases. What is the GitHub repo?"
75
+ - options:
76
+ - "Same repo as the docs"
77
+ - "Different repo — I'll type it"
78
+
79
+ If different: ask as plain text — "What is the repo? (full URL or `owner/repo`)"
80
+
81
+ Wait for all answers before proceeding.
82
+
83
+ ---
84
+
85
+ ## Step 2 — Find all pending captures
86
+
87
+ Read `.vitepress/docs-manifest.json`. Filter to:
88
+ - Screenshots where `"placeholder": true`
89
+ - Videos where `"videoUrl": ""`
90
+
91
+ Group by type. If no manifest exists, scan all markdown files for `VideoDemo` and image references in `public/screenshots/` or `public/videos/`.
92
+
93
+ Report what was found:
94
+ ```
95
+ Found 4 pending captures:
96
+ Screenshots (2): timer-main.png, settings-overview.png
97
+ Videos (2): timer-start.webm, form-submit.webm
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Step 3 — Capture screenshots
103
+
104
+ Run ALL screenshots as a single batched heredoc — one bash approval covers the entire set.
105
+
106
+ **⛔ Credential rule — NEVER write credentials to any file.** All scripts run as inline bash heredocs. Credentials are passed directly as inline values within the heredoc and exist only in memory.
107
+
108
+ **For pages requiring auth:** drive a login flow before navigating to target URLs. If no credentials were given, skip auth-gated items and list them in the summary.
109
+
110
+ Batch all screenshots into one heredoc:
111
+
112
+ ```bash
113
+ node --input-type=module << 'SCRIPT'
114
+ import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
115
+ const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
116
+
117
+ const captures = [
118
+ { url: 'BASE_URL/PATH_1', path: 'OUTPUT_PATH_1', settle: SETTLE_MS },
119
+ { url: 'BASE_URL/PATH_2', path: 'OUTPUT_PATH_2', settle: SETTLE_MS },
120
+ ];
121
+
122
+ for (const cap of captures) {
123
+ const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
124
+ await page.goto(cap.url, { waitUntil: 'load', timeout: 60000 });
125
+ await page.waitForTimeout(cap.settle);
126
+ await page.screenshot({ path: cap.path, fullPage: false });
127
+ await page.close();
128
+ console.log('Captured', cap.path);
129
+ }
130
+
131
+ await browser.close();
132
+ SCRIPT
133
+ ```
134
+
135
+ Adjust `settle` based on stack: WordPress/non-SPA → `500`, Next.js/SPA → `2000`–`4000`.
136
+
137
+ After the batch completes, display each captured image inline with the Read tool for verification.
138
+
139
+ ---
140
+
141
+ ## Step 4 — Capture videos
142
+
143
+ Capture each video one at a time (each needs its own heredoc due to the unique step sequence).
144
+
145
+ For each video entry, run a heredoc that:
146
+ 1. Records the screen with `recordVideo`
147
+ 2. Executes the steps from the manifest, tracking elapsed time for each
148
+ 3. Captures a poster PNG at the final step
149
+ 4. Moves the video from the temp dir to the final output path
150
+ 5. Generates the `.vtt` caption file (written to disk — goes in git)
151
+ 6. Prints the step timings as JSON
152
+
153
+ ```bash
154
+ node --input-type=module << 'SCRIPT'
155
+ import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
156
+ import { writeFileSync, mkdirSync, readdirSync, renameSync } from 'fs';
157
+ import { dirname } from 'path';
158
+
159
+ const VIDEO_TMP = 'DOCS_FOLDER/public/videos/.tmp';
160
+ const VIDEO_OUT = 'DOCS_FOLDER/public/videos/CAPTURE_NAME.webm';
161
+ const VTT_OUT = 'DOCS_FOLDER/public/videos/CAPTURE_NAME.vtt';
162
+ const POSTER_OUT = 'DOCS_FOLDER/public/screenshots/CAPTURE_NAME-poster.png';
163
+ const BASE_URL = 'BASE_URL';
164
+ const CAPTURE_URL = 'CAPTURE_PATH';
165
+
166
+ mkdirSync(VIDEO_TMP, { recursive: true });
167
+ mkdirSync(dirname(VIDEO_OUT), { recursive: true });
168
+ mkdirSync(dirname(POSTER_OUT), { recursive: true });
169
+
170
+ const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
171
+ const context = await browser.newContext({
172
+ viewport: { width: 1440, height: 900 },
173
+ recordVideo: { dir: VIDEO_TMP, size: { width: 1440, height: 900 } }
174
+ });
175
+ const page = await context.newPage();
176
+
177
+ const t0 = Date.now();
178
+ const elapsed = () => (Date.now() - t0) / 1000;
179
+ const stepTimings = [];
180
+
181
+ await page.goto(`${BASE_URL}${CAPTURE_URL}`, { waitUntil: 'load', timeout: 60000 });
182
+ await page.waitForTimeout(800);
183
+
184
+ // STEPS — substituted from manifest steps array:
185
+ // stepTimings.push({ caption: 'CAPTION', time: elapsed() }); await page.click('SELECTOR'); await page.waitForTimeout(MS);
186
+ // stepTimings.push({ caption: 'CAPTION', time: elapsed() }); await page.waitForTimeout(MS);
187
+
188
+ await page.screenshot({ path: POSTER_OUT });
189
+ const duration = elapsed();
190
+
191
+ await context.close();
192
+ await browser.close();
193
+
194
+ // Move recorded video to final path
195
+ const files = readdirSync(VIDEO_TMP).filter(f => f.endsWith('.webm'));
196
+ if (files.length) renameSync(`${VIDEO_TMP}/${files[0]}`, VIDEO_OUT);
197
+
198
+ // Generate VTT
199
+ const toVTT = s => {
200
+ const m = Math.floor(s / 60);
201
+ const sec = (s % 60).toFixed(3).padStart(6, '0');
202
+ return `${String(m).padStart(2,'0')}:${sec}`;
203
+ };
204
+ let vtt = 'WEBVTT\n\n';
205
+ stepTimings.forEach((step, i) => {
206
+ const end = i < stepTimings.length - 1 ? stepTimings[i + 1].time : duration;
207
+ vtt += `${toVTT(step.time)} --> ${toVTT(end)}\n${step.caption}\n\n`;
208
+ });
209
+ writeFileSync(VTT_OUT, vtt);
210
+
211
+ console.log(JSON.stringify({ stepTimings, duration }));
212
+ SCRIPT
213
+ ```
214
+
215
+ After each video runs:
216
+ 1. Display the poster image inline with the Read tool so the user can verify the final frame
217
+ 2. Update the manifest entry with the step timings (replace the action-based steps from generate with timed steps)
218
+ 3. The `.vtt` file is now on disk — it goes in git as-is
219
+
220
+ ---
221
+
222
+ ## Step 5 — GitHub Releases upload
223
+
224
+ After all videos are captured, provide upload instructions for each video file:
225
+
226
+ ```
227
+ Videos are ready for upload to GitHub Releases.
228
+
229
+ 1. Go to: https://github.com/OWNER/REPO/releases
230
+ 2. Create a release tagged `docs-media` (or open the existing one)
231
+ — This is a dedicated release just for doc assets, not tied to code versions.
232
+ 3. Attach these files:
233
+ public/videos/timer-start.webm
234
+ public/videos/form-submit.webm
235
+ 4. Once uploaded, each file will have a URL like:
236
+ https://github.com/OWNER/REPO/releases/download/docs-media/timer-start.webm
237
+ ```
238
+
239
+ Then ask:
240
+
241
+ - header: "Upload complete?"
242
+ - question: "Have you uploaded the videos to GitHub Releases?"
243
+ - options:
244
+ - "Yes — update the manifest and docs"
245
+ - "I'll do it later — skip for now"
246
+
247
+ If yes: update each video entry in the manifest with the correct `videoUrl`. Also update any `<VideoDemo src="...">` placeholders in the markdown files with the real URL.
248
+
249
+ If skip: leave `videoUrl: ""` in the manifest. The VideoDemo component will still render the step list and poster — it just won't have a playable video until the URL is filled in.
250
+
251
+ ---
252
+
253
+ ## Step 6 — Rewrite prose around captures
254
+
255
+ After all captures complete:
256
+ - For each screenshot: go back to the doc page and rewrite the surrounding paragraph to match what's actually in the screenshot
257
+ - For each video: verify the step captions in the doc match what was captured — update if anything looks off
258
+
259
+ Read each screenshot with the Read tool to inform the rewrite.
260
+
261
+ ---
262
+
263
+ ## Step 7 — Update manifest
264
+
265
+ For each successfully captured item:
266
+ - Screenshots: set `"placeholder": false`, record capture timestamp
267
+ - Videos: set step timings, `vtt` path, `poster` path, and `videoUrl` (once uploaded)
268
+
269
+ ---
270
+
271
+ ## Step 8 — Summary
272
+
273
+ ```
274
+ Captured X screenshots, Y videos.
275
+
276
+ Screenshots:
277
+ ✓ timer-main.png
278
+ ✓ settings-overview.png
279
+
280
+ Videos:
281
+ ✓ timer-start.webm — poster captured, VTT written, awaiting upload
282
+ ✓ form-submit.webm — poster captured, VTT written, awaiting upload
283
+
284
+ Upload to: https://github.com/OWNER/REPO/releases/tag/docs-media
285
+
286
+ Prose rewritten in X doc pages.
287
+ Run /vitedocs:sync after future code changes to keep docs current.
288
+ ```
@@ -40,10 +40,25 @@ Add this line if not already present:
40
40
  "images": [
41
41
  {
42
42
  "path": "docs/public/screenshots/timer-main.png",
43
+ "type": "screenshot",
43
44
  "placeholder": true,
44
45
  "caption": "Timer in active interval state",
45
46
  "captureUrl": "/workouts/123",
46
47
  "captureNote": "Timer should be running with intervals visible"
48
+ },
49
+ {
50
+ "path": "docs/public/videos/timer-start.webm",
51
+ "type": "video",
52
+ "poster": "docs/public/screenshots/timer-start-poster.png",
53
+ "vtt": "docs/public/videos/timer-start.vtt",
54
+ "videoUrl": "",
55
+ "caption": "Starting a timer interval",
56
+ "captureUrl": "/workouts/123",
57
+ "steps": [
58
+ { "caption": "Click Start to begin the interval", "selector": ".start-btn", "action": "click" },
59
+ { "caption": "Timer counts down in real time", "action": "wait", "ms": 1500 },
60
+ { "caption": "Pause freezes the current interval", "selector": ".pause-btn", "action": "click" }
61
+ ]
47
62
  }
48
63
  ],
49
64
  "lastSynced": "ISO-8601 timestamp",
@@ -280,9 +295,72 @@ Present the proposed outline as plain text, then use AskUserQuestion to confirm:
280
295
 
281
296
  If changes: ask as plain text — "What would you like to add, remove, or rename?" Then re-present the updated outline and ask again.
282
297
 
298
+ ### Step 3b — Identify home page features
299
+
300
+ Based on your codebase analysis, identify 3–6 meaningful capabilities or selling points of the project that would work well as `features` entries on the VitePress home page (`index.md`).
301
+
302
+ Good features are things a user or developer would care about — core functionality, key integrations, notable technical traits, or workflow benefits. Avoid generic filler like "Easy to use" unless it's specifically supported by something concrete in the code.
303
+
304
+ For each feature, derive:
305
+ - `icon` — a single emoji that represents it
306
+ - `title` — 2–4 words
307
+ - `details` — 1–2 sentences drawn from what you actually read in the codebase
308
+
309
+ Present the proposed features as plain text before writing anything:
310
+
311
+ ```
312
+ Home page features I identified:
313
+
314
+ ⚡️ Real-time Sync
315
+ Changes pushed from the dashboard are reflected in the app within 500ms via WebSockets.
316
+
317
+ 🔒 Role-Based Access
318
+ Three permission tiers (admin, editor, viewer) enforced at the API and UI layer.
319
+
320
+ 🧩 Block Builder
321
+ Drag-and-drop layout editor built on ACF Blocks — no PHP required for content authors.
322
+ ...
323
+ ```
324
+
325
+ Then ask:
326
+
327
+ - header: "Home page features"
328
+ - question: "Do these features look right for the home page?"
329
+ - options:
330
+ - "Yes — use these"
331
+ - "I want to change some"
332
+ - "Skip the home page features section"
333
+
334
+ If changes: ask as plain text — "What would you like to add, remove, or reword?"
335
+
336
+ Store the confirmed features — they are written into `index.md` in Step 4.
337
+
283
338
  ### Step 4 — Write pages
284
339
 
285
- Write pages one at a time. For each page:
340
+ Write `index.md` first, then remaining pages one at a time. For `index.md`, use the VitePress home page layout with the confirmed features:
341
+
342
+ ```md
343
+ ---
344
+ layout: home
345
+
346
+ hero:
347
+ name: "App Name"
348
+ text: "One-line tagline"
349
+ tagline: Slightly longer supporting line drawn from Q2 app description.
350
+ actions:
351
+ - theme: brand
352
+ text: Get Started
353
+ link: /FIRST_GUIDE_PAGE
354
+
355
+ features:
356
+ - icon: ⚡️
357
+ title: Feature Title
358
+ details: Feature details sentence.
359
+ # ... remaining features
360
+ ---
361
+ ```
362
+
363
+ Populate `name`, `text`, `tagline`, and the `link` in `actions` from what you know about the project. For each remaining page:
286
364
 
287
365
  1. Read the relevant source files
288
366
  2. Write the markdown based on what you can confidently determine
@@ -307,6 +385,27 @@ Write pages one at a time. For each page:
307
385
  ```
308
386
  Populate `method`, `path`, `auth`, `body`, and `response` from what you read in the source files. If a request body or response shape is unclear, leave it as a gap comment.
309
387
 
388
+ 6. For pages documenting interactions, flows, or button behaviors — anything where "what happens next" matters — suggest a video capture instead of a screenshot. Use `VideoDemo` like this:
389
+
390
+ ```md
391
+ <VideoDemo
392
+ src="https://github.com/OWNER/REPO/releases/download/docs-media/timer-start.webm"
393
+ poster="/screenshots/timer-start-poster.png"
394
+ vtt="/videos/timer-start.vtt"
395
+ caption="Starting a timer interval"
396
+ :steps="[
397
+ { caption: 'Click Start to begin the interval', time: 0 },
398
+ { caption: 'Timer counts down in real time', time: 1.5 },
399
+ { caption: 'Pause freezes the current interval', time: 4.2 }
400
+ ]"
401
+ />
402
+ ```
403
+
404
+ Leave `src` with a placeholder GitHub Releases URL — it will be filled in after `/vitedocs:capture` runs. Record the entry in the manifest with `"type": "video"` and `"videoUrl": ""`.
405
+
406
+ Use a screenshot (not video) for: static UI states, settings screens, empty states, error messages.
407
+ Use a video for: multi-step flows, button interactions, animations, form submissions, drag-and-drop.
408
+
310
409
  Do not stop to ask gap questions mid-page. Keep writing and accumulate all gaps.
311
410
 
312
411
  ### Step 5 — Gap review
@@ -484,6 +583,75 @@ const highlightedResponse = computed(() =>
484
583
  </style>
485
584
  ```
486
585
 
586
+ **`.vitepress/components/VideoDemo.vue`** — write this file exactly:
587
+
588
+ ```vue
589
+ <template>
590
+ <figure class="vd">
591
+ <div class="vd__player">
592
+ <video
593
+ ref="videoEl"
594
+ :src="src"
595
+ :poster="poster"
596
+ controls
597
+ preload="metadata"
598
+ @timeupdate="onTimeUpdate"
599
+ >
600
+ <track v-if="vtt" kind="subtitles" :src="vtt" default label="Steps" />
601
+ </video>
602
+ </div>
603
+ <ol v-if="steps && steps.length" class="vd__steps">
604
+ <li
605
+ v-for="(step, i) in steps"
606
+ :key="i"
607
+ class="vd__step"
608
+ :class="{ 'vd__step--active': activeStep === i }"
609
+ >
610
+ <span class="vd__num">{{ i + 1 }}</span>
611
+ {{ step.caption }}
612
+ </li>
613
+ </ol>
614
+ <figcaption v-if="caption" class="vd__caption">{{ caption }}</figcaption>
615
+ </figure>
616
+ </template>
617
+
618
+ <script setup>
619
+ import { ref } from 'vue'
620
+
621
+ const props = defineProps({
622
+ src: { type: String, required: true },
623
+ poster: { type: String, default: '' },
624
+ vtt: { type: String, default: '' },
625
+ caption: { type: String, default: '' },
626
+ steps: { type: Array, default: () => [] },
627
+ })
628
+
629
+ const videoEl = ref(null)
630
+ const activeStep = ref(-1)
631
+
632
+ const onTimeUpdate = () => {
633
+ if (!props.steps.length || !videoEl.value) return
634
+ const t = videoEl.value.currentTime
635
+ let active = -1
636
+ for (let i = 0; i < props.steps.length; i++) {
637
+ if (t >= props.steps[i].time) active = i
638
+ }
639
+ activeStep.value = active
640
+ }
641
+ </script>
642
+
643
+ <style scoped>
644
+ .vd { margin: 1.5rem 0 2.5rem; }
645
+ .vd__player { border-radius: 8px; overflow: hidden; border: 1px solid var(--vp-c-divider); background: #000; }
646
+ .vd__player video { width: 100%; display: block; max-height: 500px; }
647
+ .vd__steps { margin: 12px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 6px; }
648
+ .vd__step { display: flex; align-items: baseline; gap: 8px; font-size: 0.82rem; color: var(--vp-c-text-2); padding: 6px 10px; border-radius: 6px; border: 1px solid transparent; transition: all 0.2s; }
649
+ .vd__step--active { color: var(--vp-c-text-1); background: var(--vp-c-bg-soft); border-color: var(--vp-c-divider); }
650
+ .vd__num { font-size: 0.65rem; font-weight: 700; color: var(--vp-c-brand-1); background: var(--vp-c-brand-soft); width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
651
+ .vd__caption { font-size: 0.75rem; color: var(--vp-c-text-3); margin-top: 8px; text-align: center; }
652
+ </style>
653
+ ```
654
+
487
655
  **Install dependencies** in the docs folder:
488
656
 
489
657
  ```bash
@@ -511,6 +679,7 @@ import { useRoute } from 'vitepress'
511
679
  import mediumZoom from 'medium-zoom'
512
680
  import './index.css'
513
681
  import ApiEndpoint from '../components/ApiEndpoint.vue'
682
+ import VideoDemo from '../components/VideoDemo.vue'
514
683
 
515
684
  export default {
516
685
  extends: DefaultTheme,
@@ -522,6 +691,7 @@ export default {
522
691
  },
523
692
  enhanceApp({ app }) {
524
693
  app.component('ApiEndpoint', ApiEndpoint)
694
+ app.component('VideoDemo', VideoDemo)
525
695
  }
526
696
  }
527
697
  ```
@@ -551,7 +721,7 @@ Add `docs-manifest.json` to `.vitepress/` entry in `.gitignore`.
551
721
 
552
722
  ```
553
723
  Generated X pages (Y user-facing, Z developer).
554
- Created N placeholder screenshots — run /vitedocs:screenshot when ready to capture real images.
724
+ Created N placeholder screenshots — run /vitedocs:capture when ready to capture real images.
555
725
  Filled M of P gaps — X remaining gap comments left in files for manual review.
556
726
  ```
557
727
 
@@ -15,7 +15,7 @@ Use AskUserQuestion to ask which mode to run:
15
15
  - options:
16
16
  - "setup — Wire up VitePress + GitHub Actions for Pages deployment"
17
17
  - "generate — Analyze the codebase and write documentation pages"
18
- - "screenshot — Capture real screenshots and replace placeholders"
18
+ - "capture — Capture screenshots and record interaction videos"
19
19
  - "sync — Detect code drift and update docs that are out of date"
20
20
  - "brand — Configure colors, fonts, logo, and visual identity"
21
21
  - "update — Retrofit an existing docs setup with the latest components"
@@ -24,7 +24,8 @@ Scan the current working directory and any known focus paths for `.vitepress/con
24
24
  For each confirmed docs path, check what is already present:
25
25
 
26
26
  - `.vitepress/components/ApiEndpoint.vue` — exists?
27
- - `.vitepress/theme/index.js` — exists? includes `medium-zoom`? includes `ApiEndpoint`?
27
+ - `.vitepress/components/VideoDemo.vue` — exists?
28
+ - `.vitepress/theme/index.js` — exists? includes `medium-zoom`? includes `ApiEndpoint`? includes `VideoDemo`?
28
29
  - `.vitepress/theme/index.css` — exists? includes `.medium-zoom-overlay` z-index rules?
29
30
  - `package.json` — are `medium-zoom` and `highlight.js` listed in dependencies?
30
31
 
@@ -32,9 +33,10 @@ Report findings as a checklist:
32
33
  ```
33
34
  Path: docs/
34
35
  [x] ApiEndpoint.vue — found (needs update)
36
+ [ ] VideoDemo.vue — missing
35
37
  [ ] medium-zoom installed
36
38
  [x] highlight.js installed
37
- [x] theme/index.js — found (missing medium-zoom wiring)
39
+ [x] theme/index.js — found (missing medium-zoom wiring, missing VideoDemo)
38
40
  [x] theme/index.css — found (missing z-index rules)
39
41
  ```
40
42
 
@@ -71,6 +73,8 @@ Apply only the items selected (or all missing/outdated items if "Everything" was
71
73
 
72
74
  **`ApiEndpoint.vue`** — write the full component from the template in generate Step 6, substituting the correct tab list. If the file already exists, replace it entirely (it is a generated component, not hand-edited).
73
75
 
76
+ **`VideoDemo.vue`** — if missing, write the full component from the template in generate Step 6. If it already exists, replace it entirely (it is a generated component, not hand-edited).
77
+
74
78
  **`medium-zoom` + `highlight.js`** — if either is missing from `package.json` dependencies, run:
75
79
  ```bash
76
80
  cd DOCS_FOLDER && npm install medium-zoom highlight.js
@@ -101,6 +105,7 @@ import { useRoute } from 'vitepress'
101
105
  import mediumZoom from 'medium-zoom'
102
106
  import './index.css'
103
107
  import ApiEndpoint from '../components/ApiEndpoint.vue'
108
+ import VideoDemo from '../components/VideoDemo.vue'
104
109
 
105
110
  export default {
106
111
  extends: DefaultTheme,
@@ -112,6 +117,7 @@ export default {
112
117
  },
113
118
  enhanceApp({ app }) {
114
119
  app.component('ApiEndpoint', ApiEndpoint)
120
+ app.component('VideoDemo', VideoDemo)
115
121
  }
116
122
  }
117
123
  ```
package/install.js CHANGED
@@ -18,6 +18,10 @@ if (existsSync(oldSkill)) {
18
18
  const src = join(__dirname, 'commands', 'vitedocs');
19
19
  const dest = join(home, '.claude', 'commands', 'vitedocs');
20
20
 
21
+ // Clean destination before installing to remove any stale files
22
+ if (existsSync(dest)) {
23
+ rmSync(dest, { recursive: true });
24
+ }
21
25
  mkdirSync(dest, { recursive: true });
22
26
 
23
27
  const files = readdirSync(src).filter(f => f.endsWith('.md'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamdangavin/claude-skill-vitepress-docs",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Installs the vitedocs: Claude Code skill suite — VitePress docs setup, generate, screenshot, sync, brand, and update.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,126 +0,0 @@
1
- ---
2
- name: vitedocs:screenshot
3
- description: Capture real screenshots using Playwright and replace placeholders in docs.
4
- allowed-tools:
5
- - Read
6
- - Write
7
- - Edit
8
- - Bash
9
- - Glob
10
- - Grep
11
- - AskUserQuestion
12
- ---
13
-
14
- This mode reads `.vitepress/docs-manifest.json`. Run `/vitedocs:generate` first if no manifest exists yet.
15
-
16
- ---
17
-
18
- ## Mode: screenshot
19
-
20
- ### Step 1 — Gather capture details
21
-
22
- **Q1 — Source:**
23
- - header: "Capture source"
24
- - question: "Where should I capture screenshots from?"
25
- - options:
26
- - "Local dev server — I'll capture from a running server"
27
- - "Deployed URL — give me the base URL"
28
-
29
- If deployed: ask as plain text — "What is the base URL? (e.g. `https://docs.myapp.com`)"
30
-
31
- **Q2 — Stack type** (only if local):
32
- - header: "Project type"
33
- - question: "What type of project is this?"
34
- - options:
35
- - "Node/npm — you can optionally start the server for me"
36
- - "WordPress, PHP, or other non-Node stack"
37
- - "Static files"
38
-
39
- **Q3 — Server status** (only if local Node):
40
- - header: "Dev server"
41
- - question: "Is the dev server already running?"
42
- - options:
43
- - "Already running"
44
- - "Not running — please start it for me"
45
-
46
- If starting: ask as plain text — "What command starts it? And what URL/port does it run on?"
47
-
48
- **Q4 — Authentication:**
49
- - header: "Login required?"
50
- - question: "Does reaching the screens that need screenshots require login?"
51
- - options:
52
- - "No — all target screens are public"
53
- - "Yes — I'll provide credentials"
54
-
55
- 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."
56
-
57
- **Q5 — Scope:**
58
- - header: "Capture scope"
59
- - question: "Which screenshots should I capture?"
60
- - options:
61
- - "All placeholders from the manifest"
62
- - "Specific pages only — I'll tell you which"
63
-
64
- If specific: ask as plain text — "Which pages or screenshots?"
65
-
66
- Wait for all answers.
67
-
68
- ### Step 2 — Find all placeholders
69
-
70
- Read `.vitepress/docs-manifest.json`. Filter to all images where `"placeholder": true`. Group by the doc page they belong to.
71
-
72
- If no manifest exists, scan all markdown files in the docs folder for images referencing `public/screenshots/` — treat all of them as needing capture.
73
-
74
- ### Step 3 — Capture screenshots
75
-
76
- For each placeholder, refer to the manifest's `captureUrl` and `captureNote` fields to understand what state to capture.
77
-
78
- Use Playwright via Homebrew for all captures:
79
- ```js
80
- import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
81
- ```
82
-
83
- **⛔ Credential rule — NEVER write credentials to any file.** Do not write them to a script file, `.env`, the manifest, or anywhere on disk. All Playwright scripts run as inline bash heredocs — credentials are passed directly as inline values to `page.fill()` calls within the heredoc and exist only in memory for the duration of the command. 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.
84
-
85
- **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 `/vitedocs:screenshot` again with credentials.
86
-
87
- **Standard capture script template:**
88
-
89
- Run inline via bash heredoc — no file is ever written to disk:
90
-
91
- ```bash
92
- node --input-type=module << 'SCRIPT'
93
- import { chromium } from '/opt/homebrew/lib/node_modules/playwright/index.mjs';
94
- const browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
95
- const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
96
- await page.goto('BASE_URL + captureUrl', { waitUntil: 'load', timeout: 60000 });
97
- await page.waitForTimeout(SETTLE_MS);
98
- await page.screenshot({ path: 'OUTPUT_PATH', fullPage: false });
99
- await browser.close();
100
- SCRIPT
101
- ```
102
-
103
- Adjust `SETTLE_MS` based on stack type: WordPress/non-SPA → `500`, Next.js/SPA → `2000`–`4000`.
104
-
105
- Never write the script to a file — always use the heredoc form above.
106
-
107
- ### Step 4 — Rewrite prose around captured images
108
-
109
- 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.
110
-
111
- Read the screenshot with the Read tool to inform the rewrite.
112
-
113
- ### Step 5 — Update manifest
114
-
115
- For each successfully captured image, update `"placeholder": false` in the manifest and record the capture timestamp.
116
-
117
- ### Step 6 — Summary
118
-
119
- ```
120
- Captured X of Y screenshots.
121
- Skipped: [list any skipped with reason]
122
-
123
- Rewrote prose in X doc pages.
124
-
125
- Run /vitedocs:sync after future code changes to keep docs current.
126
- ```