doczero
v0.2.1
Published
Terminal-native documentation platform — browse docs in the CLI; same commands for humans and agents.
Maintainers
Readme
doc0
Terminal-native documentation: humans browse in a TUI; agents run the same CLI with --json / raw output, or talk to the MCP server.
doc0 is the primary command; d0 is installed as a short alias. Everything below works with either.
Install
npm install -g doczeroThe package name on npm is doczero (the shorter doc0 was blocked by npm's similarity filter). It installs the same two binaries — doc0 and d0 — so every command in these docs works as written.
Interactive browse (doc0 @scope/pkg, doc0 browse …) uses the Ink + React TUI only — Node 22+ and npm install are enough; there is no Rust or native binary.
Use doc0 browse --external <url> for live docs URLs when you want off-site links from llms.txt included in discovery.
Experimental secondary track: doc0 browse-opentui runs a parallel OpenTUI shell (requires Bun).
From source:
npm install
npm run build
node dist/index.js --helpAdd ~/.d0/bin to your PATH to use named bundle CLIs (e.g. stripe-docs) after doc0 add.
Quick start
# Point doc0 at any markdown folder — name + version inferred from package.json when present.
doc0 add ./my-docs
doc0 @local/my-docs
# Or install a packaged bundle dir that ships with d0.json.
doc0 add --local ./examples/example-lib
doc0 @example/lib
# TUI: j/k scroll, Enter open, / search, h back, l forward, q quit
# See which of your project's deps have built-in docs coverage.
doc0 scan
# Verify every registry entry (bundles installed, URLs serve llms.txt / llms-full.txt / sitemap).
doc0 doctor
doc0 @example/lib search webhooks --json
doc0 @example/lib read api/webhooks --raw
doc0 ls --jsonCommands
For most users, three commands are enough:
doc0 <id>— open docs in the TUI (doc0 stripe)doc0 mcp install— wire doc0 into your agent clientdoc0 add ./my-docs— install local markdown as a bundle
Everything else is available under doc0 contrib for maintainer/power-user workflows.
| Command | Description |
|--------|-------------|
| doc0 add <path> | Instant bundle. Point at any folder of markdown — doc0 scans .md / .mdx, infers name from package.json (or @local/<dirname>), bundles, and installs in one step. Pass --name @scope/x to override. |
| doc0 add --local <dir> | Install an existing bundle directory that already has d0.json (strict). |
| doc0 add <@scope/name> | Registry name (network registry not live yet). |
| doc0 remove <name> | Remove an installed bundle |
| doc0 ls | List installed bundles |
| doc0 <bundle> | Open Ink interactive browser (TTY) |
| doc0 <bundle> ls | List pages (slugs) |
| doc0 <bundle> read <slug> | Read one page |
| doc0 <bundle> search <query> | Full-text search |
| doc0 browse-opentui | Experimental secondary OpenTUI launcher (Bun required) |
| doc0 scan [dir] | Scan ./package.json deps and report which have doc0 registry coverage |
| doc0 ask <id> <question...> | Ask a question against a docs source; returns cited answer (--json for agents) |
| doc0 update [--check] | Self-update the CLI from npm. --check reports without installing. |
| doc0 mcp | MCP server on stdio. --installed-only hides built-in URL sources; only user-added entries + installed bundles are exposed. |
| doc0 mcp install | Add doc0 as an MCP server to a supported client. Interactive picker by default; use --cursor / --claude-code / --windsurf to skip the prompt. Merges into the target config, backing up any existing file. |
| doc0 contrib … | Maintainer workflows: bundle build/import/init, ingest, doctor, registry sync |
Flags: --json and --raw where documented; without a TTY, read defaults to raw markdown and search/ls default to JSON when outputFormat is auto in ~/.d0rc.
Bundle format
See examples/example-lib/d0.json — structure maps stable slugs to markdown paths. Bundle name must be scoped (@org/name).
MCP
Run the server on stdio:
doc0 mcpCursor: merge doc0 into Cursor's MCP config (global ~/.cursor/mcp.json by default):
doc0 mcp install --cursor # install into ~/.cursor/mcp.json
doc0 mcp install --cursor --project # use ./.cursor/mcp.json in this repo
doc0 mcp install --cursor --yes # replace existing mcpServers.d0
doc0 mcp install --cursor --dry-run # print JSON onlyClaude Code: merge doc0 into Claude Code's MCP config:
doc0 mcp install --claude-code # install into ~/.claude.json (user scope)
doc0 mcp install --claude-code --project # install into ./.mcp.json (project scope, team-shareable)Windsurf: merge doc0 into Windsurf's MCP config:
doc0 mcp install --windsurf # install into ~/.codeium/windsurf/mcp_config.jsonAntigravity: merge doc0 into Antigravity's MCP config:
doc0 mcp install --antigravity # install into ~/.gemini/antigravity/mcp_config.jsonZed: merge doc0 into Zed's settings (uses context_servers key):
doc0 mcp install --zed # install into ~/.config/zed/settings.json
doc0 mcp install --zed --project # install into ./.zed/settings.jsonOpenCode: merge doc0 into OpenCode's config (uses mcp key):
doc0 mcp install --opencode # install into ~/.config/opencode/opencode.json
doc0 mcp install --opencode --project # install into ./opencode.jsondoc0 mcp install # interactive picker
doc0 mcp install --list # show supported clientsRestart the client after install. The entry is registered under mcpServers.d0 (or context_servers.d0 for Zed, mcp.d0 for OpenCode).
Tools
Four tools, designed to minimize round-trips in an agent loop:
| Tool | Purpose |
|------|---------|
| find_docs(query) | Registry search. Returns matches + for the top match: root tree inline and whether /llms-full.txt is available. Usually one call is enough to start navigating. |
| read_docs(id, path?, full?) | Read docs by registry id. No path → root tree; dir path → subtree; page URL/slug → page markdown. full=true → whole /llms-full.txt markdown; full="heading substring" → a single matching chunk. Pages are cached on first read. |
| grep_docs(id, query) | Search within a source. Uses the local cache of pages you've read; for uncached URL docs falls back to bounded live search (cap via D0_MCP_SEARCH_MAX_FETCH). |
| list_docs() | List all registry entries. |
| ask_docs(id, question) | One-call Q&A with citations (uses your configured provider key). |
Tool-flow guidance for agents
Fast path when you just need an answer:
ask_docs("stripe", "how do I verify webhook signatures?")— single call, answer + citations.
Detailed path when you need full source context:
find_docs("stripe webhooks")— one call returns the id, the root tree, and anllms_full_availableflag.- If
llms_full_availableis true:read_docs("stripe", null, true)returns the entire docs site in one HTTP hit, orread_docs("stripe", null, "webhook")returns just the matching section. This is the fast path for most modern doc sites. - Otherwise navigate:
read_docs("stripe", "/api/webhooks"). Every page you read is cached under~/.d0/docs-store/<id>/so subsequentgrep_docscalls are local. - Use
grep_docsonce pages are cached, or for sites without/llms-full.txtwhen you need text search.
Registry
Registry entries resolve from, in order of precedence:
- User overrides:
~/.d0/docs-registry.json - Installed bundles (anything added via
doc0 add) - Community registry — a single JSON file on GitHub, fetched once a day and cached at
~/.d0/community-registry.json - Shipped seed —
registry.jsonbundled with the npm package (offline / first-run fallback)
Community registry
Every doc0 install points at the community registry by default:
https://raw.githubusercontent.com/doc0team/d0-registry/main/registry.jsonThat repo is a single JSON file. PRs are the curation UI — no servers, no accounts. See the template at examples/d0-registry-template/ for the README, contributing rules, and validation workflow that go in that repo.
The shipped seed (registry.json at the root of this package) is a point-in-time snapshot of the community file, refreshed before each publish via npm run sync-registry. It exists so doc0 works on a fresh install with no network and so first-run latency is zero. Community entries with the same id override seed entries, so stale seed data is fixed by a one-line PR to d0-registry.
Commands:
doc0 registry status # show configured URL + cache state
doc0 registry sync # force-refresh the cache right nowControl it from ~/.d0rc:
# Point at your own fork / private mirror
registryUrl: https://raw.githubusercontent.com/myorg/d0-registry/main/registry.json
# Or disable entirely (shipped seed only, no network call)
registryUrl: falseOr from the environment (wins over ~/.d0rc):
D0_REGISTRY_URL=off doc0 ls # disable for this run
D0_REGISTRY_URL=https://… doc0 stripe # override for this runFetch failures fall back to the last-known-good cache, then to the shipped seed, so doc0 keeps working offline.
Format of the JSON file (array or { "entries": [...] }):
{
"entries": [
{
"id": "stripe",
"aliases": ["stripe api", "stripe docs"],
"sourceType": "url",
"source": "https://docs.stripe.com",
"description": "Stripe API documentation"
}
]
}Bootstrapping a new d0-registry repo: copy registry.json from this package root, plus the files under examples/d0-registry-template/ (README, CONTRIBUTING, GitHub Actions workflow).
Local override
To add or override a single source without touching the community file, edit ~/.d0/docs-registry.json:
{
"entries": [
{ "id": "my-docs", "aliases": ["mydocs"], "sourceType": "url", "source": "https://example.com/docs" }
]
}There is still no doc0-hosted registry service. Everything resolves from the shipped seed + a GitHub-hosted JSON file + your local files.
URL docs completeness (env)
Large doc sites can return tens of thousands of URLs from sitemaps and llms.txt. doc0 caps work in layers so runs stay predictable; raise caps when you want maximum coverage (more time, disk, and HTTP load).
Multilingual sitemaps. Many sites (e.g. Starlight on Astro) list every locale in the sitemap (/en/…, /es/…, …). When doc0 detects a large, multi-locale URL set from the sitemap, it keeps a single language catalog so the TUI and discovery stay usable. It prefers, in order: D0_DOCS_LOCALE if set and present in the sitemap; else the locale implied by your browse URL’s first path segment; else en if present; else the locale with the most URLs. URLs whose first path segment does not look like a locale tag (two letters or xx-yy) are left unchanged. Set D0_DOCS_LOCALE=off or D0_DOCS_LOCALE=* to disable filtering and keep every locale from the sitemap.
| Variable | What it controls | Default |
|----------|------------------|---------|
| D0_DOCS_LOCALE | Force one locale for sitemap-derived pages (en, pt-br, …), or off / * to keep all languages | unset (auto when heuristics match) |
| D0_MAX_DISCOVERED_URLS | Max URLs kept after merging llms.txt, sitemaps, and nav discovery | 50000 |
| D0_MAX_SITEMAP_NESTED | Max nested sitemap index pages to follow | 200 |
| D0_SEARCH_MAX_FETCH | Live searchDocUrls (CLI/TUI): max pages to fetch and scan for a query (0 = all discovered up to D0_MAX_DISCOVERED_URLS) | 10000 |
| D0_MCP_SEARCH_MAX_FETCH | MCP search_nodes live URL search: max pages to consider (uses URL-ranking + early exit; avoids multi-hour scans on huge sites) | 80 |
| D0_SEARCH_FETCH_CONCURRENCY | Parallelism for that live search fetch pass | 8 |
| D0_INGEST_MAX_PAGES | ingestUrlToDocStore / CLI ingest: max pages after dedupe (0 = all discovered up to D0_MAX_DISCOVERED_URLS) | 50000 |
| D0_INGEST_FETCH_CONCURRENCY | Parallelism when writing ingested markdown pages | 8 |
| D0_LLMS_FULL_CHUNK_MAX_CHARS | Max characters per llms-full.txt chunk (paragraph-bounded; continuation chunks share the heading as (part N/M)) | 8000 |
| D0_MCP_INSTALLED_ONLY | Set to 1 to hide community + seed URL entries in MCP (equivalent to doc0 mcp --installed-only) | unset |
| D0_REGISTRY_URL | Override the community registry URL. Disable tokens: off / false / disabled / "". | default URL |
| D0_COMMUNITY_REGISTRY_TTL_MS | How long to trust ~/.d0/community-registry.json before re-fetching | 86400000 (24h) |
| D0_DEBUG | Set to 1 to surface community-registry fetch failures in MCP mode (stderr is otherwise silent there) | unset |
CLI: doc0 ingest url accepts --max-pages (same semantics: 0 means no extra cap beyond discovery).
License
MIT
