diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index 013d1e3..0000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "claude-code-docs", - "version": "1.0.0", - "description": "Fetch Claude Code docs as markdown - rewrites code.claude.com/docs page URLs to their .md source before WebFetch runs", - "author": { - "name": "PleaseAI", - "url": "https://github.com/pleaseai" - }, - "homepage": "https://github.com/pleaseai/claude-code-docs", - "license": "MIT", - "keywords": [ - "claude-code", - "docs", - "markdown", - "webfetch", - "hooks" - ] -} diff --git a/README.md b/README.md index c097612..b8088af 100644 --- a/README.md +++ b/README.md @@ -54,12 +54,14 @@ csp is optional: without it on `PATH`, `ask search` prints the resolved checkout ## Claude Code plugin: fetch docs as markdown -This repo is also a Claude Code plugin. A `PreToolUse` hook rewrites `code.claude.com/docs` page URLs to their `.md` source before WebFetch runs, so Claude reads the clean markdown twin instead of the HTML app shell: +The `claude-code-docs` Claude Code plugin now lives in [`pleaseai/claude-code-plugins`](https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/claude-code-docs) (`plugins/claude-code-docs`). This repo is the docs mirror only. Moving the plugin out means installing it no longer clones this repo, which is large because of `docs/` and `llms-full.txt`. The install command is unchanged: ```bash /plugin install claude-code-docs@pleaseai ``` +A `PreToolUse` hook rewrites `code.claude.com/docs` page URLs to their `.md` source before WebFetch runs, so Claude reads the clean markdown twin instead of the HTML app shell: + | WebFetch input | Rewritten to | |---|---| | `https://code.claude.com/docs/en/agents` | `https://code.claude.com/docs/en/agents.md` | @@ -67,7 +69,7 @@ This repo is also a Claude Code plugin. A `PreToolUse` hook rewrites `code.claud Left untouched: URLs already ending in a file extension (`.md`, `llms.txt`, images), the docs root, and everything outside `code.claude.com/docs`. The hook returns [`updatedInput` with `permissionDecision: "allow"`](https://code.claude.com/docs/en/hooks#pretooluse-decision-control), so the rewritten fetch runs without an extra prompt. -Hook tests: `bash hooks/rewrite-docs-url.test.sh` +Source and hook tests: [`plugins/claude-code-docs`](https://github.com/pleaseai/claude-code-plugins/tree/main/plugins/claude-code-docs) in `pleaseai/claude-code-plugins`. ### Skill: `/claude-code-docs:claude-code-docs [question]` diff --git a/hooks/hooks.json b/hooks/hooks.json deleted file mode 100644 index fc0a342..0000000 --- a/hooks/hooks.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Rewrites code.claude.com/docs page URLs to their .md source so WebFetch gets clean markdown instead of the HTML app shell", - "hooks": { - "PreToolUse": [ - { - "matcher": "WebFetch", - "hooks": [ - { - "type": "command", - "command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/rewrite-docs-url.sh", - "timeout": 5 - } - ] - } - ] - } -} diff --git a/hooks/rewrite-docs-url.sh b/hooks/rewrite-docs-url.sh deleted file mode 100755 index 1f6e895..0000000 --- a/hooks/rewrite-docs-url.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# PreToolUse hook for WebFetch: rewrite Claude Code docs page URLs to their -# markdown source (https://code.claude.com/docs/en/agents -> .../agents.md). -# Every docs page serves a clean markdown twin at .md; fetching it skips -# the HTML app shell entirely. Silent exit 0 = no opinion, normal flow. - -input=$(cat) -url=$(jq -r '.tool_input.url // empty' <<<"$input") -[ -z "$url" ] && exit 0 - -case "$url" in - https://code.claude.com/docs/*) ;; - *) exit 0 ;; -esac - -# Drop fragment and query: hooks#pretooluse-decision-control -> hooks -path="${url%%#*}" -path="${path%%\?*}" -path="${path%/}" - -# Already a raw file (page .md, llms.txt, images, ...) - leave untouched. -# Check the last path segment only; the domain always contains dots. -case "${path##*/}" in - *.*) exit 0 ;; -esac - -# Only rewrite real pages under a locale segment (/docs//), -# not the docs root itself -rest="${path#https://code.claude.com/docs/}" -case "$rest" in - */*) ;; - *) exit 0 ;; -esac - -jq --arg url "${path}.md" '{ - hookSpecificOutput: { - hookEventName: "PreToolUse", - permissionDecision: "allow", - permissionDecisionReason: ("Rewrote Claude Code docs URL to markdown source: " + $url), - updatedInput: (.tool_input | .url = $url) - } -}' <<<"$input" diff --git a/hooks/rewrite-docs-url.test.sh b/hooks/rewrite-docs-url.test.sh deleted file mode 100755 index 3f78d41..0000000 --- a/hooks/rewrite-docs-url.test.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Tests for rewrite-docs-url.sh. Run: bash hooks/rewrite-docs-url.test.sh -set -u -cd "$(dirname "$0")" -pass=0 fail=0 - -run() { - echo "$1" | bash ./rewrite-docs-url.sh -} - -expect_rewrite() { # input-url expected-url - out=$(run "{\"tool_name\":\"WebFetch\",\"tool_input\":{\"url\":\"$1\",\"prompt\":\"p\"}}") - got=$(jq -r '.hookSpecificOutput.updatedInput.url // empty' <<<"$out") - if [ "$got" = "$2" ]; then pass=$((pass+1)); else fail=$((fail+1)); echo "FAIL: $1 -> '$got' (expected $2)"; fi -} - -expect_silent() { # input-url - out=$(run "{\"tool_name\":\"WebFetch\",\"tool_input\":{\"url\":\"$1\",\"prompt\":\"p\"}}") - if [ -z "$out" ]; then pass=$((pass+1)); else fail=$((fail+1)); echo "FAIL: $1 should pass through, got: $out"; fi -} - -expect_rewrite "https://code.claude.com/docs/en/agents" "https://code.claude.com/docs/en/agents.md" -expect_rewrite "https://code.claude.com/docs/en/agents/" "https://code.claude.com/docs/en/agents.md" -expect_rewrite "https://code.claude.com/docs/en/hooks#pretooluse-decision-control" "https://code.claude.com/docs/en/hooks.md" -expect_rewrite "https://code.claude.com/docs/en/agent-sdk/python" "https://code.claude.com/docs/en/agent-sdk/python.md" -expect_rewrite "https://code.claude.com/docs/en/hooks?foo=bar" "https://code.claude.com/docs/en/hooks.md" - -expect_silent "https://code.claude.com/docs/en/agents.md" -expect_silent "https://code.claude.com/docs/llms.txt" -expect_silent "https://code.claude.com/docs/llms-full.txt" -expect_silent "https://code.claude.com/docs/en" -expect_silent "https://code.claude.com/docs" -expect_silent "https://example.com/docs/en/agents" -expect_silent "https://docs.claude.com/en/api/overview" - -# prompt must survive updatedInput (it replaces the whole input object) -out=$(run '{"tool_name":"WebFetch","tool_input":{"url":"https://code.claude.com/docs/en/agents","prompt":"summarize"}}') -got=$(jq -r '.hookSpecificOutput.updatedInput.prompt' <<<"$out") -if [ "$got" = "summarize" ]; then pass=$((pass+1)); else fail=$((fail+1)); echo "FAIL: prompt lost: $got"; fi - -echo "passed: $pass, failed: $fail" -[ "$fail" -eq 0 ] diff --git a/skills/claude-code-docs/SKILL.md b/skills/claude-code-docs/SKILL.md deleted file mode 100644 index a5b33f1..0000000 --- a/skills/claude-code-docs/SKILL.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -name: claude-code-docs -description: Answer questions about Claude Code (features, hooks, plugins, skills, MCP, Agent SDK, settings, CLI) from the official docs. Uses the ask CLI to resolve a version-pinned local mirror and csp for semantic search, falling back to grep and WebFetch. Use when the user asks how Claude Code works, what a configuration field does, or whether Claude Code supports something. -argument-hint: [question] -arguments: [question] ---- - -Answer this question about Claude Code using the official documentation: - -**$question** - -## How to find the answer - -Work through these steps, falling back gracefully when a tool is missing. - -### 1. Resolve the docs mirror - -```bash -DOCS="$(ask src github:pleaseai/claude-code-docs)/docs" -``` - -`ask src` prints the root of a cached checkout of the docs mirror (clones on first use). If the `ask` CLI is not on PATH, run it via npx/bunx instead: - -```bash -DOCS="$(npx -y @pleaseai/ask src github:pleaseai/claude-code-docs)/docs" -``` - -Only fall back to step 4 (WebFetch) when neither works, such as offline sandboxes without npm registry access. - -### 2. Search for relevant pages - -Prefer semantic search with csp, scoped to the per-page markdown: - -```bash -csp search "$question" "$DOCS" --content docs --top-k 8 -``` - -Or equivalently via ask (requires csp on PATH): - -```bash -ask search github:pleaseai/claude-code-docs "$question" --content docs --top-k 8 -``` - -If csp is not on PATH, run it via npx/bunx: - -```bash -npx -y @pleaseai/csp search "$question" "$DOCS" --content docs --top-k 8 -``` - -If that fails too, fall back to grep over the mirror — search for 2-3 distinct keyword variants, not one long phrase: - -```bash -grep -ril "" "$DOCS" -``` - -The manifest at `$DOCS/../llms.txt` lists every page with a one-line summary; reading it is a fast way to shortlist pages when keyword search is noisy. - -### 3. Read and answer - -Read the top matching pages (whole pages — they are small). Ground every claim in what the pages actually say: - -- Answer in the user's language; keep code identifiers, field names, and flags verbatim. -- Cite each source page with its canonical URL: a mirror file `$DOCS/hooks.md` corresponds to `https://code.claude.com/docs/en/hooks`. -- Quote exact field names, JSON shapes, and version requirements (`min-version` markers) when they matter. -- If the docs do not answer the question, say so explicitly — do not fill gaps from memory. - -### 4. Fallback: WebFetch - -If the `ask` CLI is unavailable, fetch `https://code.claude.com/docs/llms.txt`, pick the relevant page URLs from the manifest, and fetch each as `.md` (this plugin's hook rewrites page URLs to `.md` automatically). Then answer as in step 3.