@iamdangavin/claude-skill-vitepress-docs 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skill/SKILL.md +218 -3
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -76,6 +76,8 @@ Add this line if not already present:
|
|
|
76
76
|
|
|
77
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
78
|
|
|
79
|
+
**Multiple focus paths:** if more than one path was established in Step 0, ask Q2 through Q6 once per path — fully completing all questions for the first path before starting the second. Never present questions for two paths at the same time.
|
|
80
|
+
|
|
79
81
|
**Q1 — VitePress status:**
|
|
80
82
|
- header: "VitePress"
|
|
81
83
|
- question: "Is VitePress already installed in this project?"
|
|
@@ -83,7 +85,14 @@ Use AskUserQuestion for each choice below. For freeform text inputs (repo URL, d
|
|
|
83
85
|
- "Already installed"
|
|
84
86
|
- "Starting from scratch"
|
|
85
87
|
|
|
86
|
-
**Q2 — Docs folder
|
|
88
|
+
**Q2 — Docs folder:**
|
|
89
|
+
- header: "Docs location"
|
|
90
|
+
- question: "Where should the docs folder be created? I found your repo at `[GIT_ROOT_PATH]`."
|
|
91
|
+
- options:
|
|
92
|
+
- "Here → `[GIT_ROOT_PATH]/docs/`"
|
|
93
|
+
- "Somewhere else — I'll type the path"
|
|
94
|
+
|
|
95
|
+
If "Somewhere else": ask as plain text — "What path should I use? (Full path or relative to `[GIT_ROOT_PATH]`)"
|
|
87
96
|
|
|
88
97
|
**Q3 — GitHub repo** (plain text): Ask — "What is the GitHub repo? (full URL or `owner/repo`)"
|
|
89
98
|
|
|
@@ -385,7 +394,53 @@ This matters because the GitHub Actions workflow file must go in `.github/workfl
|
|
|
385
394
|
|
|
386
395
|
**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.**
|
|
387
396
|
|
|
388
|
-
**Q4 — Docs output
|
|
397
|
+
**Q4 — Docs output:** Before asking, check whether `setup` has already been run by looking for `.vitepress/config.mjs` anywhere inside the focus path:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
find FOCUS_PATH -name "config.mjs" -path "*/.vitepress/*" 2>/dev/null
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**If found:** docs location is already established — skip this question, infer the docs folder from the found path, and tell the user: "I found an existing VitePress config at `[PATH]` — using that as the docs folder."
|
|
404
|
+
|
|
405
|
+
**If not found:** ask:
|
|
406
|
+
|
|
407
|
+
- header: "Docs location"
|
|
408
|
+
- question: "Where should the docs folder be created? I found your repo at `[GIT_ROOT_PATH]`."
|
|
409
|
+
- options:
|
|
410
|
+
- "Here → `[GIT_ROOT_PATH]/docs/`"
|
|
411
|
+
- "Somewhere else — I'll type the path"
|
|
412
|
+
|
|
413
|
+
If "Somewhere else": ask as plain text — "What path should I use? (Full path or relative to `[GIT_ROOT_PATH]`)"
|
|
414
|
+
|
|
415
|
+
Always show the full resolved path — never use abstract terms like "project root."
|
|
416
|
+
|
|
417
|
+
**Q1b — API base URL** (only if developer or both selected): Scan for API endpoints first — look for REST API routes, WP REST API extensions, Next.js API routes, Express routers, etc. If any are found, ask:
|
|
418
|
+
|
|
419
|
+
- header: "API base URL"
|
|
420
|
+
- question: "I found API endpoints in the codebase. What is the base URL for your API? This is used to generate live code samples in the docs."
|
|
421
|
+
- options:
|
|
422
|
+
- "Same as the local dev URL already provided"
|
|
423
|
+
- "Different URL — I'll type it"
|
|
424
|
+
- "Skip — I don't want code samples"
|
|
425
|
+
|
|
426
|
+
If different: ask as plain text — "What is the API base URL? (e.g. `http://localhost:8888/wp-json` or `https://api.mysite.com`)"
|
|
427
|
+
|
|
428
|
+
Then ask:
|
|
429
|
+
|
|
430
|
+
- header: "Code sample tabs"
|
|
431
|
+
- question: "Which code sample types should the API endpoint component show?"
|
|
432
|
+
- options:
|
|
433
|
+
- "Fetch + cURL (default)"
|
|
434
|
+
- "Fetch only"
|
|
435
|
+
- "cURL only"
|
|
436
|
+
- "Fetch + cURL + PHP"
|
|
437
|
+
- "Custom — I'll tell you which"
|
|
438
|
+
|
|
439
|
+
If custom: ask as plain text — "Which languages or methods? (e.g. Fetch, cURL, PHP, Python, Axios)"
|
|
440
|
+
|
|
441
|
+
Store the selected tabs — they determine which tab options are rendered in the `ApiEndpoint` component and which code samples are generated per endpoint.
|
|
442
|
+
|
|
443
|
+
Store `apiBase` and `tabs` — both used in VitePress config and the `ApiEndpoint` component.
|
|
389
444
|
|
|
390
445
|
**Q5 — Skip anything:**
|
|
391
446
|
- header: "Exclusions"
|
|
@@ -456,6 +511,17 @@ Write pages one at a time. For each page:
|
|
|
456
511
|
```
|
|
457
512
|
Then generate the placeholder PNG (see **Placeholder generation** below).
|
|
458
513
|
|
|
514
|
+
5. For any page documenting API endpoints (REST routes, WP REST API extensions, etc.), use the `ApiEndpoint` component instead of plain code blocks — but only if `apiBase` was collected in Q1b. Use it like this:
|
|
515
|
+
```md
|
|
516
|
+
<ApiEndpoint
|
|
517
|
+
method="GET"
|
|
518
|
+
path="/wp-json/alabama-news/v1/sources"
|
|
519
|
+
auth="none"
|
|
520
|
+
:response="{ sources: ['AP', 'Reuters'] }"
|
|
521
|
+
/>
|
|
522
|
+
```
|
|
523
|
+
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.
|
|
524
|
+
|
|
459
525
|
Do not stop to ask gap questions mid-page. Keep writing and accumulate all gaps.
|
|
460
526
|
|
|
461
527
|
### Step 5 — Gap review
|
|
@@ -473,10 +539,159 @@ the correct information. Can you fill these in?
|
|
|
473
539
|
|
|
474
540
|
Go back and fill in the answers the user provides.
|
|
475
541
|
|
|
476
|
-
### Step 6 —
|
|
542
|
+
### Step 6 — Install VitePress components
|
|
543
|
+
|
|
544
|
+
If `apiBase` was collected in Q1b, write the following files before updating the config.
|
|
545
|
+
|
|
546
|
+
**`.vitepress/components/ApiEndpoint.vue`** — write this file exactly, substituting `TABS` with the selected tab labels (e.g. `['Fetch', 'cURL']`) and adding sample generators for any additional tabs beyond Fetch and cURL:
|
|
547
|
+
|
|
548
|
+
```vue
|
|
549
|
+
<template>
|
|
550
|
+
<div class="api-ep">
|
|
551
|
+
<div class="api-ep__header">
|
|
552
|
+
<div class="api-ep__badge">
|
|
553
|
+
<span class="api-ep__method" :class="`api-ep__method--${method.toLowerCase()}`">{{ method }}</span>
|
|
554
|
+
<code class="api-ep__path">{{ path }}</code>
|
|
555
|
+
</div>
|
|
556
|
+
<span class="api-ep__auth">{{ authLabel }}</span>
|
|
557
|
+
</div>
|
|
558
|
+
<div class="api-ep__body">
|
|
559
|
+
<div class="api-ep__tabs">
|
|
560
|
+
<button
|
|
561
|
+
v-for="tab in tabs"
|
|
562
|
+
:key="tab"
|
|
563
|
+
class="api-ep__tab"
|
|
564
|
+
:class="{ 'api-ep__tab--active': activeTab === tab }"
|
|
565
|
+
@click="activeTab = tab"
|
|
566
|
+
>{{ tab }}</button>
|
|
567
|
+
</div>
|
|
568
|
+
<pre class="api-ep__code"><code>{{ currentSample }}</code></pre>
|
|
569
|
+
<p v-if="activeTab === 'Fetch'" class="api-ep__note">
|
|
570
|
+
Must be called from an authenticated browser session — cookies are sent automatically.
|
|
571
|
+
</p>
|
|
572
|
+
<template v-if="response">
|
|
573
|
+
<div class="api-ep__section-label">Sample Response</div>
|
|
574
|
+
<pre class="api-ep__code"><code>{{ formattedResponse }}</code></pre>
|
|
575
|
+
</template>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
</template>
|
|
579
|
+
|
|
580
|
+
<script setup>
|
|
581
|
+
import { ref, computed } from 'vue'
|
|
582
|
+
import { useData } from 'vitepress'
|
|
583
|
+
|
|
584
|
+
const props = defineProps({
|
|
585
|
+
method: { type: String, required: true },
|
|
586
|
+
path: { type: String, required: true },
|
|
587
|
+
auth: { type: String, default: 'authenticated' },
|
|
588
|
+
body: { type: Object, default: null },
|
|
589
|
+
response: { type: Object, default: null },
|
|
590
|
+
formData: { type: Boolean, default: false },
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
const { theme } = useData()
|
|
594
|
+
const baseUrl = computed(() => theme.value.apiBase ?? '')
|
|
595
|
+
|
|
596
|
+
const authLabel = computed(() => ({
|
|
597
|
+
'authenticated': 'Auth required',
|
|
598
|
+
'admin:manage': 'Admin only',
|
|
599
|
+
'self': 'Authenticated (self)',
|
|
600
|
+
'none': 'Unauthenticated',
|
|
601
|
+
}[props.auth] ?? props.auth))
|
|
602
|
+
|
|
603
|
+
const tabs = TABS // substituted from Q1b answer
|
|
604
|
+
const activeTab = ref(tabs[0])
|
|
605
|
+
|
|
606
|
+
const fetchSample = computed(() => {
|
|
607
|
+
const url = `${baseUrl.value}${props.path}`
|
|
608
|
+
if (props.method === 'GET') {
|
|
609
|
+
return `const res = await fetch('${url}', {\n credentials: 'include'\n})\nconst data = await res.json()`
|
|
610
|
+
}
|
|
611
|
+
if (props.formData) {
|
|
612
|
+
return `const form = new FormData()\n// append fields to form...\n\nconst res = await fetch('${url}', {\n method: 'POST',\n credentials: 'include',\n body: form\n})\nconst data = await res.json()`
|
|
613
|
+
}
|
|
614
|
+
const bodyStr = props.body ? JSON.stringify(props.body, null, 2) : '{}'
|
|
615
|
+
return `const res = await fetch('${url}', {\n method: '${props.method}',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(${bodyStr})\n})\nconst data = await res.json()`
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
const curlSample = computed(() => {
|
|
619
|
+
const url = `${baseUrl.value}${props.path}`
|
|
620
|
+
if (props.method === 'GET') {
|
|
621
|
+
return `curl '${url}' \\\n -H 'Cookie: <session-cookie>'`
|
|
622
|
+
}
|
|
623
|
+
if (props.formData) {
|
|
624
|
+
return `curl -X POST '${url}' \\\n -H 'Cookie: <session-cookie>' \\\n -F 'field=value'`
|
|
625
|
+
}
|
|
626
|
+
const bodyStr = props.body ? JSON.stringify(props.body, null, 2) : '{}'
|
|
627
|
+
return `curl -X ${props.method} '${url}' \\\n -H 'Content-Type: application/json' \\\n -H 'Cookie: <session-cookie>' \\\n -d '${bodyStr}'`
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
// Add additional sample computed properties here for PHP, Python, Axios etc if selected in Q1b
|
|
631
|
+
|
|
632
|
+
const currentSample = computed(() => {
|
|
633
|
+
if (activeTab.value === 'Fetch') return fetchSample.value
|
|
634
|
+
if (activeTab.value === 'cURL') return curlSample.value
|
|
635
|
+
return ''
|
|
636
|
+
})
|
|
637
|
+
|
|
638
|
+
const formattedResponse = computed(() =>
|
|
639
|
+
props.response ? JSON.stringify(props.response, null, 2) : ''
|
|
640
|
+
)
|
|
641
|
+
</script>
|
|
642
|
+
|
|
643
|
+
<style scoped>
|
|
644
|
+
.api-ep { border: 1px solid var(--vp-c-divider); border-radius: 8px; margin: 1.5rem 0 2.5rem; overflow: hidden; }
|
|
645
|
+
.api-ep__header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; background: var(--vp-c-bg-soft); border-bottom: 1px solid var(--vp-c-divider); }
|
|
646
|
+
.api-ep__badge { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
647
|
+
.api-ep__method { display: inline-flex; align-items: center; font-size: 0.65rem; font-weight: 700; letter-spacing: 0.06em; padding: 2px 7px; border-radius: 4px; flex-shrink: 0; }
|
|
648
|
+
.api-ep__method--get { background: var(--vp-c-green-soft); color: var(--vp-c-green-1); }
|
|
649
|
+
.api-ep__method--post { background: var(--vp-c-brand-soft); color: var(--vp-c-brand-1); }
|
|
650
|
+
.api-ep__method--put { background: var(--vp-c-yellow-soft); color: var(--vp-c-yellow-1); }
|
|
651
|
+
.api-ep__method--delete { background: var(--vp-c-danger-soft); color: var(--vp-c-danger-1); }
|
|
652
|
+
.api-ep__path { font-family: var(--vp-font-family-mono); font-size: 0.85rem; color: var(--vp-c-text-1); background: none; padding: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
653
|
+
.api-ep__auth { font-size: 0.7rem; font-weight: 500; color: var(--vp-c-text-3); flex-shrink: 0; white-space: nowrap; }
|
|
654
|
+
.api-ep__body { padding: 16px; background: var(--vp-c-bg-soft); display: flex; flex-direction: column; gap: 8px; }
|
|
655
|
+
.api-ep__tabs { display: flex; gap: 4px; margin-bottom: 2px; }
|
|
656
|
+
.api-ep__tab { font-size: 0.75rem; font-weight: 500; padding: 3px 10px; border-radius: 4px; border: 1px solid transparent; background: transparent; color: var(--vp-c-text-2); cursor: pointer; transition: all 0.15s; }
|
|
657
|
+
.api-ep__tab:hover { color: var(--vp-c-text-1); background: var(--vp-c-default-soft); }
|
|
658
|
+
.api-ep__tab--active { border-color: var(--vp-c-divider); background: var(--vp-c-bg); color: var(--vp-c-text-1); }
|
|
659
|
+
.api-ep__code { margin: 0; padding: 12px; background: var(--vp-c-bg); border-radius: 6px; overflow-x: auto; font-family: var(--vp-font-family-mono); font-size: 0.72rem; line-height: 1.65; color: var(--vp-c-text-1); }
|
|
660
|
+
.api-ep__code code { background: none; padding: 0; font-size: inherit; color: inherit; }
|
|
661
|
+
.api-ep__note { font-size: 0.7rem; color: var(--vp-c-text-3); margin: 0; line-height: 1.5; }
|
|
662
|
+
.api-ep__section-label { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--vp-c-text-3); margin-top: 4px; }
|
|
663
|
+
</style>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**`.vitepress/theme/index.js`** — create if it doesn't exist, otherwise merge into existing:
|
|
667
|
+
|
|
668
|
+
```js
|
|
669
|
+
import DefaultTheme from 'vitepress/theme'
|
|
670
|
+
import ApiEndpoint from '../components/ApiEndpoint.vue'
|
|
671
|
+
|
|
672
|
+
export default {
|
|
673
|
+
extends: DefaultTheme,
|
|
674
|
+
enhanceApp({ app }) {
|
|
675
|
+
app.component('ApiEndpoint', ApiEndpoint)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
If the file already exists, add only the import and `app.component()` line — do not overwrite other registrations.
|
|
681
|
+
|
|
682
|
+
### Step 6b — Update VitePress config sidebar
|
|
477
683
|
|
|
478
684
|
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.
|
|
479
685
|
|
|
686
|
+
If `apiBase` was collected, also add it to `themeConfig`:
|
|
687
|
+
|
|
688
|
+
```js
|
|
689
|
+
themeConfig: {
|
|
690
|
+
apiBase: 'API_BASE_URL',
|
|
691
|
+
// ...existing themeConfig
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
480
695
|
### Step 7 — Write the manifest
|
|
481
696
|
|
|
482
697
|
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).
|