{
  "markdown": "# claude-webcache\n\n![npm](https://img.shields.io/npm/v/@theyahia/claude-webcache.svg)\n![npm downloads](https://img.shields.io/npm/dm/@theyahia/claude-webcache.svg)\n![license](https://img.shields.io/npm/l/@theyahia/claude-webcache.svg)\n![tests](https://github.com/theYahia/claude-webcache/actions/workflows/test.yml/badge.svg)\n\n**Persistent cross-session WebFetch cache for Claude Code. Cached reads in ~0.07ms — orders of magnitude faster than re-fetching.**\n\nClaude Code's built-in cache lasts 15 minutes, within one session. Every new session re-fetches from scratch. `claude-webcache` persists results across sessions in a local SQLite database — instant cache hits, zero network cost.\n\n```\nSession 1  →  WebFetch(\"docs.example.com\")  →  fetched, auto-cached ✓\nSession 2  →  cached_fetch(\"docs.example.com\")  →  instant hit, no network call\nSession 7  →  cached_fetch(\"docs.example.com\")  →  still instant, unlimited TTL\n```\n\n**v0.1.5+:** every `WebFetch` is automatically saved via `PostToolUse` hook — nothing to configure.\n\n![CACHE_MISS flow: WebFetch + cache_store in first session](docs/screenshots/cache-miss.png)\n![CACHE_HIT flow: instant hit, no WebFetch in second session](docs/screenshots/cache-hit.png)\n\n## Install\n\n```bash\nclaude plugin marketplace add theYahia/claude-webcache && claude plugin install claude-webcache@theyahia\n```\n\nWorks in: **Claude Code CLI · Desktop (Mac/Windows) · VS Code extension · JetBrains plugin** — same command everywhere.\n\nDone. Every `WebFetch` is auto-cached from now on.\n\nOptionally add the [usage pattern](#usage-pattern) to `~/.claude/CLAUDE.md` to also check the cache *before* fetching (saves the WebFetch call entirely on repeat URLs).\n\n> **Plugin TUI not working?** There's an open Claude Code bug ([#41653](https://github.com/anthropics/claude-code/issues/41653)) where `/plugin install` rejects third-party sources with \"source type not supported.\" Use the CLI command above — it bypasses the TUI and works fine.\n>\n> **Fallback (no marketplace):**\n> ```bash\n> git clone https://github.com/theYahia/claude-webcache && claude --plugin-dir ./claude-webcache/plugin\n> ```\n\n### Option 2 — npm global\n\n```bash\nnpm i -g @theyahia/claude-webcache\n```\n\nRequires Node.js **22.5+** (uses built-in `node:sqlite` — no native deps, no install step).\n\nThen register in `~/.claude/settings.json` (replace path with output of `npm root -g`):\n\n```json\n{\n  \"mcpServers\": {\n    \"claude-webcache\": {\n      \"command\": \"node\",\n      \"args\": [\"/path/from/npm-root-g/claude-webcache/scripts/mcp-server.cjs\"]\n    }\n  },\n  \"hooks\": {\n    \"SessionStart\": [\n      {\n        \"matcher\": \"startup|clear|compact\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"node /path/from/npm-root-g/claude-webcache/scripts/hook-stats.cjs\" }\n        ]\n      }\n    ]\n  }\n}\n```\n\n### Option 3 — clone (contributors)\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Usage pattern (optional — for pre-fetch cache checks)\n\n**v0.1.5+ auto-caches every WebFetch automatically.** The pattern below is optional: add it to `~/.claude/CLAUDE.md` to also check the cache *before* making a WebFetch — this saves the WebFetch call entirely on repeat URLs.\n\n**Auto-read (v0.5+): nothing to do.** A `PreToolUse` hook checks the cache before every `WebFetch`/`WebSearch`. On a hit it serves the cached copy and skips the network; on a miss the call runs normally and the `PostToolUse` hook stores the result. Same URL + same prompt (or same search query) in any future session = instant hit, zero network cost.\n\nManual lookup is still available if you want it: call `cached_fetch(url, prompt)` (or `cached_search(query)`) — returns the cached text, or `[CACHE_MISS] …` if absent. Disable auto-read with `WEBCACHE_AUTOREAD=0`.\n\n## ⚠ Security — authenticated URLs\n\nThe cache stores the URL alongside the response in `~/.webcache/cache.db`. **By default, claude-webcache strips obvious credentials from the stored URL** before write (`user:pass@host` and query params named `token`, `api_key`, `apikey`, `access_token`, `auth`, `secret`, `password`, `key`, `signature`, etc.).\n\n**That's display-level redaction, not key-level.** The cache key still hashes the original URL, so re-fetching the same authenticated URL hits the cache. If you want a stricter trade-off:\n\n```bash\nexport WEBCACHE_STRICT_REDACT=1\n```\n\nWith `WEBCACHE_STRICT_REDACT=1`, the cache key is computed from the redacted URL too — endpoints differing only in `?token=A` vs `?token=B` collide in one slot. Safe for pass-through auth (identical content), **unsafe for personalized endpoints** (different users see each other's cached data).\n\n**Bottom line:** prefer header-based auth (`Authorization:` headers) over URL-embedded tokens. Don't commit `~/.webcache/cache.db` to git.\n\n## Namespaces\n\nMultiple projects sharing one machine? Isolate per-project caches:\n\n```bash\nWEBCACHE_NAMESPACE=gosdelo  claude    # cache writes/reads scoped to ns \"gosdelo\"\nWEBCACHE_NAMESPACE=qsearch  claude    # separate ns, no cross-contamination\n```\n\nDefault namespace is the empty string `\"\"` (shared cache for v0.3 behavior). Inspect/manage per-namespace via CLI: `claude-webcache namespaces`, `claude-webcache --namespace gosdelo stats`.\n\n## Tools (MCP)\n\n| Tool | Args | Returns |\n|---|---|---|\n| `cached_fetch` | `url`, `prompt` | cached text, or `[CACHE_MISS] <url>` |\n| `cached_search` | `query` | cached WebSearch results, or `[CACHE_MISS] <query>` (websearch namespace, short TTL) |\n| `cache_store` | `url`, `prompt`, `output` | `stored` |\n| `cache_stats` | `global?` | `{ namespace, total, hits, misses, hit_rate, last, db_size_bytes, evicted, oversize_skipped, last_hook_error_at, top_urls, ... }` |\n| `cache_list` | `limit?`, `offset?`, `global?` | recent URLs (most recent first) |\n| `cache_invalidate` | `url` | `{ deleted: N }` — drops every entry for that URL in current namespace |\n| `cache_clear` | `older_than_days?`, `confirm?` | `{ deleted: N }` — partial wipe by age, or full wipe with `confirm:\"YES\"` |\n| `cache_warm` | `entries: [{url,prompt}]` or `urls[]+prompt` | `{ hits, misses, invalid }` — bulk pre-flight in one call |\n| `cache_refresh` | `url`, `prompt` | `[CACHE_MISS] <url>` — invalidates and signals re-fetch |\n\n## CLI\n\nThe npm package ships a `claude-webcache` binary for ad-hoc inspection and a local web dashboard:\n\n```bash\nclaude-webcache stats                            # JSON stats\nclaude-webcache stats --by-domain                # per-domain breakdown\nclaude-webcache list 20                          # 20 most-recent URLs\nclaude-webcache list 50 --offset 100             # pagination\nclaude-webcache invalidate https://news.com/123  # drop one URL\nclaude-webcache refresh https://news.com/123 --prompt \"extract title\"   # invalidate one (url,prompt) pair\nclaude-webcache warm urls.txt --prompt \"extract\" # bulk pre-flight check\nclaude-webcache clear --older-than-days 30       # partial wipe\nclaude-webcache clear --confirm YES              # full wipe (requires explicit confirm)\nclaude-webcache clear-logs                       # truncate ~/.webcache/hook.log\nclaude-webcache namespaces                       # list all namespaces present\nclaude-webcache export --out cache.json --all    # export metadata\nclaude-webcache dashboard                        # open http://localhost:37778\nclaude-webcache --namespace gosdelo stats        # scope command to namespace\n```\n\nThe dashboard renders top URLs by hits, top domains (with avg hits / last fetch / entry counts), full search-able paginated list with one-click invalidate + refresh buttons. Pure stdlib — no extra deps to install.\n\n## Configuration (env vars)\n\n| Variable | Default | Effect |\n|---|---|---|\n| `WEBCACHE_TTL_DAYS` | unlimited | Global TTL in days. `0` or unset = unlimited. |\n| `WEBCACHE_MAX_SIZE_MB` | unlimited | Above this size, LRU eviction drops ~20% of oldest-by-`last_hit_at` entries on next write (debounced every 100 writes). |\n| `WEBCACHE_DOMAIN_TTL` | none | Per-domain TTL JSON: `{\"news.com\":1,\"reuters.com\":1,\"arxiv.org\":0}`. Days; `0` = unlimited. Suffix-matches subdomains. Overrides global TTL when matched. |\n| `WEBCACHE_NAMESPACE` | `\"\"` (shared) | Isolate the cache per project. Different namespaces never see each other's entries. |\n| `WEBCACHE_MAX_OUTPUT_MB` | 10 | Reject WebFetch responses larger than N MB. Stats track `oversize_skipped` counter and `last_oversize_url`. |\n| `WEBCACHE_COMPRESS` | off | `1` enables gzip on responses ≥4 KB. Stored as base64 in TEXT column. Existing uncompressed rows read fine (BC). |\n| `WEBCACHE_STRICT_REDACT` | off | `1` makes the cache key use the redacted URL — collides per endpoint regardless of token value. See Security above. |\n| `WEBCACHE_QUIET` | off | `1` suppresses hook stderr output (file log at `~/.webcache/hook.log` still written). |\n| `WEBCACHE_DEBUG` | off | `1` enables verbose tracing in the auto-cache hook. |\n| `WEBCACHE_SEARCH_TTL_HOURS` | 6 | TTL for cached `WebSearch` results (the `websearch` namespace). Search rankings drift, so this is short by default. `0` = never expire. |\n| `WEBCACHE_AUTOREAD` | on | `0` disables the `PreToolUse` auto-read hooks (cache still fills via `PostToolUse`; you read it manually via `cached_fetch`/`cached_search`). |\n\n## SessionStart hook\n\nEvery new session injects a one-liner so Claude knows the cache exists:\n\n```\nwebcache [ns=gosdelo] 142 pages cached, 87% hit rate, last fetch 3h ago\n```\n\nNo output if cache is empty. `[ns=...]` is omitted when using the default namespace.\n\n## Storage\n\nSQLite at `~/.webcache/cache.db` (WAL mode, `synchronous=NORMAL`, `busy_timeout=5000`).\nCache key = `SHA256(namespace + \"|\" + canonical(url) + \"|\" + prompt)`. Default TTL: **unlimited** (set `WEBCACHE_TTL_DAYS=N` for N-day expiry).\n\n**URL canonicalization** (v0.4+): lowercase hostname, strip default ports (`:80`/`:443`), strip fragment, sort query parameters alphabetically. So `https://EXAMPLE.com/p?b=2&a=1#frag` and `https://example.com/p?a=1&b=2` produce the same cache key — no silent miss on URL formatting variance.\n\n| Field | Type |\n|---|---|\n| `key` | TEXT PRIMARY KEY |\n| `url` | TEXT (redacted) |\n| `prompt_hash` | TEXT |\n| `output` | TEXT (gzip+base64 when compressed=1) |\n| `cached_at` | INTEGER (ms epoch) |\n| `hit_count` | INTEGER |\n| `last_hit_at` | INTEGER |\n| `namespace` | TEXT (default `\"\"`) |\n| `compressed` | INTEGER (0/1) |\n\nConcurrent-safe via WAL + 5-second `busy_timeout` — multiple Claude Code sessions can read/write simultaneously without `SQLITE_BUSY` errors.\n\n## Limits\n\n- Cache key includes the prompt — use consistent prompts to maximize hit rate.\n- Output is whatever WebFetch returns (already summarized). No re-processing.\n- No semantic search. Exact `(namespace, canonical_url, prompt)` match only.\n\n## Benchmarks\n\nSingle-process latency on a populated DB (N=10000 entries, 1KB output each), measured via `npm run bench`:\n\n| Op | p50 | p95 | p99 | ops/sec |\n|---:|---:|---:|---:|---:|\n| `write` | 0.09ms | 0.15ms | 2.66ms | 5,800 |\n| `read_hit` | 0.07ms | 0.12ms | 0.23ms | 7,600 |\n| `read_miss` | 0.04ms | 0.07ms | 0.13ms | 17,600 |\n| `list_50` | 0.11ms | 0.16ms | 0.53ms | 7,400 |\n\nStorage overhead: ~2 KB per entry for a 1 KB payload (key + indexes + WAL + new v0.4 columns). With `WEBCACHE_COMPRESS=1` on text-heavy responses, expect 3-7× reduction.\n\nWebFetch over the network typically takes 1-5 seconds — a cached hit is **~15,000-70,000× faster**. Reproduce on your hardware: `npm run bench`. See [`bench/README.md`](bench/README.md) for methodology and full results metadata (CPU, RAM, OS, commit) saved per run.\n\n## Related\n\n- [claude-mem](https://github.com/thedotmack/claude-mem) — persistent memory across sessions (complements claude-webcache: memory vs. web cache)\n- [WWmcp](https://github.com/theYahia/WWmcp) — catalog of 46 MCP servers for non-Western APIs\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n\n---\n\nЧасть [WWmcp](https://github.com/theYahia/WWmcp) · Telegram: [@vhodvai](https://t.me/vhodvai)\n",
  "bytes": 11933,
  "sha": "4e0a8e4f118566681d3bb630052916a7ffb8f92bb97c38bbe39d5074126393d0",
  "repo_slug": "theyahia/claude-webcache",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_theyahia_claude_webcache_39c73729/readme"
}