I built a CLI for agents: replace HTML scraping with one line of npx zhanglu-net
No MCP, no SDK. Just 9 static JSON endpoints and a 270-line CLI. Shipped in 1.5 hours.
UPDATE · 2026-07-24
What follows is an account of one day — 2026-06-09, zero to shipped — and the numbers are
from that day (9 endpoints, a 270-line CLI). The interface kept growing since:
/api/presentations.json was added, the site went bilingual
(a whole parallel set of English endpoints — prefix any path with /en),
and the CLI gained --lang.
For current state, always trust /en/agents and
/en/llms.txt.
The trigger
zhanglu.net went live yesterday. The very first version shipped with an index of 30 Claude Skills, 3 projects, and 1 WeChat article entry. Then the question came:
An AI agent wants to reference content on my site — how?
The options on the table are bad:
- Scrape HTML — what the agent gets is a layout meant for humans, which it has to parse. Wasted tokens, unstable fields.
- MCP server — Claude Code supports it, but Codex / Hermes / OpenClaw / the little agents I write myself may not.
- Don't bother — then why did I build this site.
I picked a fourth option: add a JSON API layer + a CLI to the site.
The two layers of the design
zhanglu.net (Cloudflare Pages, fully static)
│
┌──────────────┼──────────────┐
│ │ │
HTML pages /api/*.json /llms.txt
(for humans) (for agents) (self-discovery)
│
┌──────────┴──────────┐
│ │
curl / fetch npx zhanglu-net (CLI)
(any agent) (Node 18+, zero runtime deps) Layer one: 9 static JSON endpoints
/api/index.json manifest (counts + all endpoints)
/api/projects.json project list
/api/projects/{slug}.json single project (with body_md)
/api/articles.json WeChat article entries
/api/skills.json Claude Skill index
/api/skills/{slug}.json single skill (with body_md)
/api/about.json bio
/api/social.json public socials (email redacted)
/api/search.json flat corpus, searched by the CLI client Generated once as static files at build time, edge-cache friendly on CF Pages, Access-Control-Allow-Origin: *, so browser-side agents can use them directly too.
Layer two: the CLI
npx zhanglu-net list skills --featured
npx zhanglu-net get skill boss --md
npx zhanglu-net search "brand judgment" --type skill
npx zhanglu-net about --json The CLI is a thin wrapper over the JSON endpoints. --json for agents to pipe; human-readable by default, with ANSI colors (auto-disabled when the agent side isn't a TTY).
A few deliberate decisions not to do things
No MCP server. Agents beyond Claude Code may not support MCP yet. HTTP + JSON is the greatest common denominator. If MCP users grow in the future, add a CF Worker to wrap the existing endpoints into an MCP server — a manageable cost.
No server-side search. The corpus is just 30 skills + 3 projects + a few articles, under 100KB of full text. The CLI pulls one /api/search.json and does text.toLowerCase().includes(needle) locally — that's enough. Simple scoring: title hit +5, occurrences accumulate. Switch to MiniSearch when the corpus grows to a few hundred.
Zero runtime deps in the CLI. Node 18+'s built-in fetch replaces node-fetch, node:util's parseArgs replaces commander, hand-rolled ANSI escape codes replace chalk. npx zhanglu-net starts fast and doesn't stall when an agent calls it.
No endpoint for WeChat article bodies. WeChat URLs block scrapers; WebFetch can't reach them. The site only stores the entry (title / date / summary / url), and the body links out.
Self-discovery: /llms.txt
llmstxt.org is pushing a convention — put an llms.txt at the site root that tells AI agents "here's what I have and how to read it." I followed it:
# zhanglu.net
> Zhang Lu's personal site.
## For AI agents
- /api/index.json — manifest
- /api/skills.json — Claude Skills index
- ...
## CLI
npx zhanglu-net --help Any agent handed https://zhanglu.net just does GET /llms.txt first, and it knows where to go next.
How to use it
In Claude Code: I wrote a /zhanglu skill, with trigger phrases like "look up Zhang Lu's skill" and "X on zhanglu." The skill automatically calls npx zhanglu-net.
Other agents:
# List all my featured skills
curl -s https://zhanglu.net/api/skills.json \
| jq '.items[] | select(.featured)'
# Get the full text of the boss skill
curl -s https://zhanglu.net/api/skills/boss.json \
| jq -r .body_md
# Full-text search
curl -s https://zhanglu.net/api/search.json \
| jq '.items[] | select(.text | test("brand"; "i"))' Or just:
npx zhanglu-net list skills --featured --json
npx zhanglu-net get skill boss --md
npx zhanglu-net search "brand" Time to build
Endpoints (9 files): 25 min
CLI (single file, 270 lines): 30 min
llms.txt / robots.txt: 5 min
Skill (SKILL.md + sync): 10 min
Design doc + this post + draft: 20 min
AGENTS.md / README updates: 10 min
build + test + commit: 15 min
─────────────────────────────────
Total: ~1.5 hours Including drafting this very article. Leaving yourself enough time to write the docs matters more than the code itself.
Take it now
- Site integration guide: /agents
- Self-discovery entry: /llms.txt
- Manifest: /api/index.json
- CLI:
npx zhanglu-net --help - Source: github.com/zhanglunet/zhanglu.net
- Design doc: docs/agent-cli/design.md
If you also maintain a content site, I strongly recommend adding a JSON API layer + /llms.txt. No framework upgrade, no new service required. Astro / Next / Hugo can all generate static JSON at build time. Cloudflare Pages caches it automatically. Zero cost.
The next agent that comes to scrape your site won't get stuck on HTML parsing anymore.