Skip to content

Add language documentation shortcuts (#25): Python, MDN, Node.js, Ruby, Go, Rust, Java, PHP, TypeScript, C/C++, and .NET - #26

Merged
only-cli merged 13 commits into
mainfrom
feat/language-docs
Aug 25, 2026
Merged

only-cli merged 13 commits into
mainfrom
feat/language-docs

Conversation

@only-cli

Copy link
Copy Markdown
Owner

First three languages from #25: Python, MDN, and Node.js.

Each is one JSON definition in clis/, the same shape as the cloud docs shortcuts:

oc py library json              -> docs.python.org/3/library/json.html
oc py doc reference/datamodel   -> docs.python.org/3/reference/datamodel.html
oc mdn js Array/map             -> MDN's Array.prototype.map page
oc mdn css grid-template-columns
oc mdn doc Web/API/fetch        -> anything else under /en-US/docs
oc node api fs                  -> nodejs.org/api/fs.html

All three sites render fine through oc open today (checked live: the Python library page, the MDN reference pages, and the Node API pages all come back as real content under budget), so this is only about not making the agent guess URL shapes.

Search: Python renders search client-side with Sphinx, MDN serves its search page as a client-rendered shell (the HTML carries only the nav), and nodejs.org ships no search at all, so each search <query> goes through DuckDuckGo with a baked-in site: filter, the fallback the aws and gcp shortcuts already use.

Names: py, mdn, and node join the alias table; python, mozilla, nodejs, and the full domains already resolve through the generic rules. README table, llms.txt, and the skill's site list are updated, and the new definitions are covered in tests/sites.test.js alongside a check that a doc path keeps its slashes.

Settles the open question in #25 in favor of one CLI per language: it matches how clis/ works today and each file stays a screenful.

First three languages from #25. Each is one definition in clis/:

- oc py library json, oc py doc reference/datamodel (the .html is
  appended, matching how the aws shortcuts spell pages)
- oc mdn js Array/map, oc mdn css grid-template-columns, and a generic
  oc mdn doc <path> for everything else under /en-US/docs
- oc node api fs

Python renders search client-side (Sphinx), MDN serves its search page
as a client-rendered shell with only the nav in the HTML, and nodejs.org
has no search, so all three get the DuckDuckGo site: filter fallback the
aws and gcp shortcuts already use. Short names py, mdn, and node join
the alias table; python, mozilla, nodejs, and the full domains resolve
as before through the generic rules.
docs.python.org has no search server: its search page downloads the
whole Sphinx index (searchindex.js, ~900KB gzipped) and ranks matches
in the browser. oc now does the same ranking itself, so 'oc py search'
answers from the site's own index instead of DuckDuckGo.

The index is cached on disk for a day and never printed; what renders
is a numbered result list that rides the normal open path, so 'do <n>'
follows a result. A query that names a symbol exactly (json.dumps)
links straight to its anchor. A site definition opts in with a
'sphinx' field naming the docs root, so any Sphinx site can use the
backend later.
@only-cli

Copy link
Copy Markdown
Owner Author

Pushed a prototype of a Sphinx search backend. oc py search <query> now fetches docs.python.org's own searchindex.js (cached for a day under ~/.only-cli/sphinx/), ranks it locally, and prints a numbered result list, so no DuckDuckGo hop for Python. An exact symbol query like json.dumps returns a direct link to its anchor. The sphinx field in a site definition is generic, so Read the Docs projects can reuse it later. 113 tests pass.

Comment thread src/sphinx.js Fixed
CodeQL flagged the tag strip in plainTitle: requiring a closing '>' means
a title whose markup never closes keeps its '<script' fragment. The '>'
is optional now, so every '<' starts a strip and none reaches the results
page, and the output is trimmed. Both call sites already HTML-escape the
result; this hardens the flattening itself. Covered by a new test.
Comment thread src/sphinx.js Fixed
CodeQL cannot see that the optional '>' already keeps every bracket out
of the flattened title, and it re-flags each shape of the regex. The
character walk does the same thing in a form whose completeness is plain
to read: nothing between or including brackets survives, closed or not.
@only-cli

Copy link
Copy Markdown
Owner Author

CodeQL flagged the title flattening in the Sphinx backend (js/incomplete-multi-character-sanitization): the original regex required a closing '>', so a title whose markup never closes could keep a literal '<script' fragment. Both call sites already HTML-escape the flattened title, so nothing reached the results page unescaped, but the flattening itself was incomplete.

Fixed in d56ddb3 and e9d5ad4: titles are now flattened by a character walk that keeps only text outside angle brackets, so no '<' or '>' survives, closed tag or not, plus a regression test for the unterminated-tag case. All checks are green again and the alert is closed as fixed.

MDN renders its search results client-side, so 'oc mdn search' has gone
through DuckDuckGo with a site: filter. But the page it renders gets its
results from a public JSON endpoint, /api/v1/search, which returns the
site's own ranking with a summary per result in one ~11KB response. A new
'api' shortcut shape names such an endpoint and which response fields
hold the list, title, URL, and snippet; the response becomes the same
synthetic results page a Sphinx search produces and rides the normal
render path, so 'do <n>' follows a result. First view is ~520 tokens and
works for any site whose search answers as JSON.
@only-cli

Copy link
Copy Markdown
Owner Author

MDN's search no longer goes through DuckDuckGo. Its results page is client-rendered, but the page gets its results from a public JSON endpoint, /api/v1/search, so 5706aab adds a second search backend beside the Sphinx one: an 'api' shortcut shape that names a JSON search endpoint and which response fields hold the list, title, URL, and snippet. The response becomes the same numbered results page, so do/find/read/next work unchanged.

Live: 'oc mdn search array map' prints MDN's own ranking (2,696 matches, top 10 with one-line summaries) for ~520 tokens, and 'oc do 1' lands on Array.prototype.map(). The shape is declarative, so any site whose search answers as JSON can use it from its clis/ definition alone. Of the three language sites in this PR, only Node.js still searches via DuckDuckGo: nodejs.org ships no search index or endpoint at all.

@only-cli
only-cli marked this pull request as draft August 24, 2026 14:36
nodejs.org has no search results page: the site's search box is a
JavaScript modal asking a third-party service, so the search verb went
through DuckDuckGo. But the API docs publish their entire reference as
one static JSON file, all.json, much the way a Sphinx site publishes
its search index, so a new nodedoc backend ranks that file locally:
every module, class, method, property, and event heading becomes a
result linking to its own anchor, and oc node search prints them as a
normal numbered page.

The file is ~8MB (~1MB over the wire) and static, so the day cache the
Sphinx backend used moves to a shared cache module both backends call:
one directory per backend, one file per host, parsed before written so
a block page never poisons it.

A typical result list costs under 100 tokens and answers from disk in
under 100ms once cached.
@only-cli

Copy link
Copy Markdown
Owner Author

Node.js is off DuckDuckGo too, via a third shape: nodejs.org has no search results page at all (the search box is a JavaScript modal asking a third-party service), but the API docs publish their whole reference as one static JSON file, all.json, much the way a Sphinx site publishes its index. A new nodedoc backend fetches that file (~1MB over the wire, day-cached like the Sphinx index, shared cache module extracted for both), flattens every module, class, method, property, and event heading into a searchable list, and ranks it locally. Result links carry the same anchor the docs derive from each heading, so do <n> lands on the right section.

$ oc node search fs promises readfile
# nodejs.org search: fs promises readfile
1 heading match in the docs' own reference, ranked locally:
[1] fsPromises.readFile(path[, options])
method, in fs

~84 tokens per result list, ~80ms from cache. Of the three language references on this branch, none relies on DuckDuckGo now: Python ranks its Sphinx index, MDN asks its own search API, Node.js ranks its own docs corpus.

RDoc publishes a site's whole search index as one static JS file, the
same way Sphinx and the Node.js docs do, so 'oc ruby search' fetches
docs.ruby-lang.org's index (en/3.4, where the file exists), caches it
under the shared day cache, and ranks it with the same scorer the Node
backend uses. Class methods print as Class.name(params), instance
methods as Class#name(params), and every entry links straight to its
method anchor. 'oc ruby class Array' opens a class page directly.

cli.js now dispatches the local search backends through one map, and
the site-resolution guard keys on the query field, which only a search
shape ever carries.
The rest of the languages from #25, each probed for the cheapest shape
that works:

- go: pkg.go.dev renders search on the server, so 'search' is a plain
  URL and 'pkg <path>' opens a package page.
- php: manual-lookup.php sends an exact function name straight to its
  manual page, so 'fn array_map' lands on the function doc; fuzzy
  search goes through DuckDuckGo since the suggestions render behind
  the manual's own TOC.
- cpp: cppreference's new static site has clean /cpp/ and /c/ paths;
  its native search page is bare links oc refuses as unreadable, so
  'search' goes through DuckDuckGo.
- rust: std and doc page opens; rustdoc's search-index.js format is
  version-fragile, so 'search' goes through DuckDuckGo.
- java: Javadoc opens pinned to javase/26, the current GA (27
  redirects there); the typeSearchIndex file lacks the module names a
  URL needs, so 'search' goes through DuckDuckGo.
- ts: handbook page opens; the site searches via Algolia client-side,
  so DuckDuckGo again.
- learn gets a 'dotnet <api>' verb, since .NET API docs already live
  there.

README table and prose and the skill notes cover the new sites.
@only-cli

Copy link
Copy Markdown
Owner Author

Second wave from #25 is in: Go, Rust, Java, Ruby, PHP, TypeScript, C/C++, and a dotnet <api> verb on learn for .NET.

Ruby got a real local search backend: RDoc ships the whole search index as one static JS file, so oc ruby search ranks it locally under the same day cache and scorer the Node.js backend uses. Class methods print as Array.new(size, default), instance methods as Array#dig(*args), and every entry links to its method anchor.

The rest were probed for the cheapest shape that works:

  • go: pkg.go.dev renders search server-side, so it is a plain URL; pkg net/http opens a package page.
  • php: fn array_map rides manual-lookup.php, which redirects an exact name straight to its manual page. Fuzzy search goes through DuckDuckGo since the site buries its suggestions behind the manual TOC.
  • cpp: the new static cppreference has clean /cpp/ and /c/ paths; its native search page is bare links oc refuses as unreadable, so search goes through DuckDuckGo.
  • rust: std and doc page opens; rustdoc's search-index.js format is version-fragile, so DuckDuckGo for search.
  • java: Javadoc opens pinned to javase/26, the current GA (27 redirects there); typeSearchIndex.js lacks the module names a URL needs, so DuckDuckGo for search. A local backend joining it with the package index is a possible follow-up.
  • ts: handbook opens; the site searches via Algolia client-side, so DuckDuckGo.

137 tests pass, all verbs checked live, CI green. That covers every language on the #25 list.

@only-cli

Copy link
Copy Markdown
Owner Author

Docs are now in sync with the full branch across the repos:

  • b0d99cd here extends llms.txt's coverage line to all eleven language references and adds an Unreleased changelog entry for the feature. README and the skill were already updated in earlier commits.
  • only-cli/web got two commits: main now documents the shipped 0.4.0 surface (the site's llms.txt and homepage table were still listing only the four launch sites, and the roadmap notes named version numbers that have since shipped), and a feat/language-docs branch there stages the language shortcut rows, to be merged into main when the release that ships this PR goes out.
  • benchmarks needs no doc change: its browse-oc skill documents only the generic commands by design, and TASKS.md already describes the three language documentation pages accurately. Adding benchmark tasks that exercise the new shortcuts is possible follow-up work, not part of this PR.

@only-cli
only-cli marked this pull request as ready for review August 24, 2026 21:16
@only-cli only-cli changed the title Add Python, MDN, and Node.js documentation shortcuts Add language documentation shortcuts (#25): Python, MDN, Node.js, Ruby, Go, Rust, Java, PHP, TypeScript, C/C++, and .NET Aug 24, 2026
@only-cli

Copy link
Copy Markdown
Owner Author

The follow-up benchmark landed in only-cli/benchmarks (25ec3c1): a docs suite of eleven graded lookups, one per language shortcut in this PR, run end to end through both Claude Code headless and codex exec against the agents' built in web tools, with this branch shimmed onto PATH.

Headline numbers:

  • Claude Code: oc-docs got 11/11 correct at $0.57 for the suite, against WebFetch's 10/11 at $0.74 and WebSearch's 11/11 at $0.89. oc's fresh input stays flat at ~1,175 tokens per task while WebFetch pays page weight, up to 39,613 tokens on Ruby's Array page. WebFetch's one miss is cppreference answering it with 403 Forbidden, a page oc reads fine.
  • codex: both conditions 11/11, and codex's own web search was 16% cheaper than oc, answering canonical API facts straight from snippets. Recorded as is; the wiki suite shows where snippet trust breaks down.

Full tables in the benchmarks README and results/agent-latest-docs{,-codex}.md.

@only-cli
only-cli merged commit 4b3abf2 into main Aug 25, 2026
4 checks passed
@only-cli
only-cli deleted the feat/language-docs branch August 25, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants