Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
Notable changes per release. Releases before 0.4.0 are listed at
[github.com/only-cli/oc/releases](https://github.com/only-cli/oc/releases).

## Unreleased

### Added

- Language documentation shortcuts: `py`, `mdn`, `node`, `ruby`, `go`, `rust`,
`java`, `php`, `cpp`, and `ts`, plus a `dotnet` verb on `learn` for the .NET
API browser. `search` on `py`, `node`, and `ruby` ranks the docs' own search
index locally and on `mdn` asks the site's API; the sites that only render
docs search client-side go through DuckDuckGo with a baked-in `site:` filter
instead. (#25)

## 0.4.0

### Added
Expand Down
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,23 +146,33 @@ Works on any mostly-static site with no per-site setup: news sites, blogs, docum
| Wikipedia | `oc wiki` (via `action=render`) | `article <title>`, `search <query>`, `lang <code> <title>` |
| AWS docs | `oc aws` (search via DuckDuckGo) | `guide <service> <page>`, `page <service> <guide> <page>`, `cli <command>`, `search <query>` |
| Google Cloud docs | `oc gcp` (via docs.cloud.google.com, search via DuckDuckGo) | `docs <product>`, `page <product> <page>`, `gcloud <command>`, `search <query>` |
| Microsoft Learn | `oc learn` (search via its RSS API) | `azure <page>`, `doc <path>`, `cli <command>`, `search <query>` |
| Microsoft Learn | `oc learn` (search via its RSS API, covers .NET) | `azure <page>`, `doc <path>`, `dotnet <api>`, `cli <command>`, `search <query>` |
| Python docs | `oc py` (search via the docs' own index) | `library <module>`, `doc <path>`, `search <query>` |
| MDN | `oc mdn` (search via the site's own API) | `js <page>`, `css <page>`, `doc <path>`, `search <query>` |
| Node.js docs | `oc node` (search via the docs' own reference) | `api <module>`, `search <query>` |
| Ruby docs | `oc ruby` (search via the docs' own index) | `class <class>`, `search <query>` |
| Go packages | `oc go` (pkg.go.dev, server-rendered search) | `pkg <path>`, `search <query>` |
| PHP manual | `oc php` (an exact `fn` name lands on its page, search via DuckDuckGo) | `fn <name>`, `doc <path>`, `search <query>` |
| Rust docs | `oc rust` (search via DuckDuckGo) | `std <path>`, `doc <path>`, `search <query>` |
| Java docs | `oc java` (Javadoc for the current JDK, search via DuckDuckGo) | `api <path>`, `search <query>` |
| C and C++ | `oc cpp` (cppreference.com, search via DuckDuckGo) | `cpp <path>`, `c <path>`, `search <query>` |
| TypeScript | `oc ts` (search via DuckDuckGo) | `handbook <page>`, `search <query>` |

A shortcut only ever resolves to a URL and then takes the same path `oc open` does, so it changes nothing about what a page costs or how it reads. The last argument takes every word after it, so `oc ddg search claude code cli` and `oc aws search s3 lifecycle rules` need no quoting, and a path argument keeps its slashes, so `oc learn doc azure/aks/what-is-aks` reaches that page.

A few of these (X, Stack Overflow, YouTube, Microsoft Learn search) read pages that look login-gated or JS-only from the outside, by finding the server-rendered HTML, feed, inline data, or public API the page already ships without a login. Stack Overflow search goes through the Stack Exchange API, and each result prints its `question_id`: read one with the `question <id>` feed rather than following its link, since the question page itself answers a bot challenge instead of the question. AWS and Google Cloud render docs search purely client-side with no feed, so their `search` goes through DuckDuckGo with a baked-in `site:` filter instead. Not supported yet: pages that only render with JavaScript and sites with hard bot challenges that expose no feed. Sites that genuinely require your account can be reached with `oc login` (bring your own cookies).
A few of these (X, Stack Overflow, YouTube, Microsoft Learn search) read pages that look login-gated or JS-only from the outside, by finding the server-rendered HTML, feed, inline data, or public API the page already ships without a login. Stack Overflow search goes through the Stack Exchange API, and each result prints its `question_id`: read one with the `question <id>` feed rather than following its link, since the question page itself answers a bot challenge instead of the question. AWS, Google Cloud, Rust, Java, TypeScript, PHP, and cppreference render docs search client-side, or as a page too bare for oc to read, so their `search` goes through DuckDuckGo with a baked-in `site:` filter instead; Go needs no such fallback, because pkg.go.dev renders its search results on the server and `oc go search` simply opens them. Python's docs are built with Sphinx, which publishes the site's full-text search index as one static file, so `oc py search` fetches that index (cached on disk for a day), ranks it locally, and prints a numbered result list; a query that names a symbol exactly, like `json.dumps`, links straight to its anchor. The same backend will work for any Sphinx site, including most Read the Docs projects. MDN also renders its search client-side, but the page gets its results from a public JSON endpoint, so `oc mdn search` asks that endpoint directly and prints the site's own ranking; that `api` shape in a site definition works for any site whose search answers as JSON. Node.js ships no search endpoint at all, but publishes its whole API reference as one static JSON file, so `oc node search` ranks that file locally the same way the Sphinx backend does, under the same day cache, and every module, class, method, property, and event heading links to its own anchor. Ruby's docs are built with RDoc, which also ships its search index as one static file, so `oc ruby search` ranks every class, method, and guide page locally the same way. PHP's manual has a lookup endpoint that sends an exact function name straight to its page, which is what `oc php fn` rides. Not supported yet: pages that only render with JavaScript and sites with hard bot challenges that expose no feed. Sites that genuinely require your account can be reached with `oc login` (bring your own cookies).

Want a website on that list? Open a pull request, or an issue naming the site; see [CONTRIBUTING.md](CONTRIBUTING.md).

## Benchmarks

Full methodology, per-task numbers, and other agents/models live in [only-cli/benchmarks](https://github.com/only-cli/benchmarks). The short version, measured with oc 0.4.0 on 2026-08-24 against live sites across a news front page, a Reddit discussion, a search results page, a stock quote, three cloud CLI reference pages, and more:
Full methodology, per-task numbers, and other agents/models live in [only-cli/benchmarks](https://github.com/only-cli/benchmarks). The short version, measured with oc 0.4.0 on 2026-08-24 against live sites across a news front page, a Reddit discussion, a search results page, a stock quote, three cloud CLI reference pages, the Python, MDN, and Node.js references, and more:

| method | tokens for 12 real pages | notes |
| method | tokens for 15 real pages | notes |
| --- | ---: | --- |
| `oc open` | 9,487 | only method that returned real content on every page |
| Jina Reader | 90,929 | blocked on both Reddit pages, failed the stock quote page |
| raw HTML fetch | 1,183,149 | the stock quote page alone is 371,597 tokens |
| `oc open` | 10,973 | only method that returned real content on every page |
| Jina Reader | 170,505 | blocked on both Reddit pages, failed the LinkedIn page |
| raw HTML fetch | 1,535,791 | the stock quote page alone is 375,721 tokens |

Read cost is one thing, but what an agent actually spends is another, so a
second suite runs whole tasks end to end in Claude Code and compares `oc`
Expand Down
9 changes: 9 additions & 0 deletions clis/developer.mozilla.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"domain": "developer.mozilla.org",
"commands": {
"js": { "open": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/{page}", "args": ["page"] },
"css": { "open": "https://developer.mozilla.org/en-US/docs/Web/CSS/{page}", "args": ["page"] },
"doc": { "open": "https://developer.mozilla.org/en-US/docs/{path}", "args": ["path"] },
"search": { "api": "https://developer.mozilla.org/api/v1/search?q={query}&locale=en-US", "page": "https://developer.mozilla.org/en-US/search?q={query}", "results": "documents", "fields": { "title": "title", "url": "mdn_url", "text": "summary" }, "total": "metadata.total.value", "args": ["query"] }
}
}
8 changes: 8 additions & 0 deletions clis/doc.rust-lang.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "doc.rust-lang.org",
"commands": {
"std": { "open": "https://doc.rust-lang.org/std/{path}.html", "args": ["path"] },
"doc": { "open": "https://doc.rust-lang.org/{path}.html", "args": ["path"] },
"search": { "open": "https://html.duckduckgo.com/html/?q=site%3Adoc.rust-lang.org+{query}", "args": ["query"] }
}
}
7 changes: 7 additions & 0 deletions clis/docs.oracle.com.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"domain": "docs.oracle.com",
"commands": {
"api": { "open": "https://docs.oracle.com/en/java/javase/26/docs/api/{path}.html", "args": ["path"] },
"search": { "open": "https://html.duckduckgo.com/html/?q=site%3Adocs.oracle.com+javase+{query}", "args": ["query"] }
}
}
8 changes: 8 additions & 0 deletions clis/docs.python.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "docs.python.org",
"commands": {
"library": { "open": "https://docs.python.org/3/library/{module}.html", "args": ["module"] },
"doc": { "open": "https://docs.python.org/3/{path}.html", "args": ["path"] },
"search": { "sphinx": "https://docs.python.org/3/", "args": ["query"] }
}
}
7 changes: 7 additions & 0 deletions clis/docs.ruby-lang.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"domain": "docs.ruby-lang.org",
"commands": {
"class": { "open": "https://docs.ruby-lang.org/en/3.4/{class}.html", "args": ["class"] },
"search": { "rdoc": "https://docs.ruby-lang.org/en/3.4/", "args": ["query"] }
}
}
8 changes: 8 additions & 0 deletions clis/en.cppreference.com.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "en.cppreference.com",
"commands": {
"cpp": { "open": "https://en.cppreference.com/cpp/{path}", "args": ["path"] },
"c": { "open": "https://en.cppreference.com/c/{path}", "args": ["path"] },
"search": { "open": "https://html.duckduckgo.com/html/?q=site%3Aen.cppreference.com+{query}", "args": ["query"] }
}
}
1 change: 1 addition & 0 deletions clis/learn.microsoft.com.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"azure": { "open": "https://learn.microsoft.com/en-us/azure/{page}", "args": ["page"] },
"doc": { "open": "https://learn.microsoft.com/en-us/{path}", "args": ["path"] },
"cli": { "open": "https://learn.microsoft.com/en-us/cli/azure/{command}", "args": ["command"] },
"dotnet": { "open": "https://learn.microsoft.com/en-us/dotnet/api/{api}", "args": ["api"] },
"search": { "open": "https://learn.microsoft.com/api/search/rss?search={query}&locale=en-us", "args": ["query"] }
}
}
7 changes: 7 additions & 0 deletions clis/nodejs.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"domain": "nodejs.org",
"commands": {
"api": { "open": "https://nodejs.org/api/{module}.html", "args": ["module"] },
"search": { "nodedoc": "https://nodejs.org/api/", "args": ["query"] }
}
}
8 changes: 8 additions & 0 deletions clis/php.net.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"domain": "php.net",
"commands": {
"fn": { "open": "https://www.php.net/manual-lookup.php?pattern={name}", "args": ["name"] },
"doc": { "open": "https://www.php.net/manual/en/{path}.php", "args": ["path"] },
"search": { "open": "https://html.duckduckgo.com/html/?q=site%3Aphp.net+{query}", "args": ["query"] }
}
}
7 changes: 7 additions & 0 deletions clis/pkg.go.dev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"domain": "pkg.go.dev",
"commands": {
"pkg": { "open": "https://pkg.go.dev/{path}", "args": ["path"] },
"search": { "open": "https://pkg.go.dev/search?q={query}", "args": ["query"] }
}
}
7 changes: 7 additions & 0 deletions clis/typescriptlang.org.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"domain": "typescriptlang.org",
"commands": {
"handbook": { "open": "https://www.typescriptlang.org/docs/handbook/{page}.html", "args": ["page"] },
"search": { "open": "https://html.duckduckgo.com/html/?q=site%3Atypescriptlang.org+{query}", "args": ["query"] }
}
}
4 changes: 2 additions & 2 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ Key facts:
- The budget is a target rather than a hard cap: a page that would finish within about four times it is printed whole, because a second command costs the agent far more than the lines the cut would have saved
- The render leads with the page's main content and puts navigation, sidebar, and footer after it, so the budget is spent on what was asked for rather than on menus
- Benchmarked at roughly 45x fewer tokens than reading raw HTML, with per-task numbers at https://github.com/only-cli/benchmarks
- Works on any mostly-static website; tuned shortcuts ship for Hacker News, Reddit, GitHub, X, LinkedIn (public guest views), DuckDuckGo, Bing, Stack Overflow (via its Atom feeds and the Stack Exchange API), Yahoo Finance (quotes, history, markets), Wikipedia (articles, search, and other language editions), and the AWS, Google Cloud, and Microsoft Learn documentation sites (guides, CLI reference, and search)
- Works on any mostly-static website; tuned shortcuts ship for Hacker News, Reddit, GitHub, X, LinkedIn (public guest views), DuckDuckGo, Bing, Stack Overflow (via its Atom feeds and the Stack Exchange API), Yahoo Finance (quotes, history, markets), Wikipedia (articles, search, and other language editions), the AWS, Google Cloud, and Microsoft Learn documentation sites (guides, CLI reference, and search), and the language documentation for Python, JavaScript (MDN), Node.js, Ruby, Go, Rust, Java, PHP, TypeScript, C and C++ (cppreference), and .NET (the Microsoft Learn API browser)
- JSON APIs render like pages: an endpoint that answers with JSON becomes one numbered item per record, with the fields that differ between items kept and the ones every item shares stated once, so a search endpoint reads like a results page for a few hundred tokens
- A page that comes back with no readable text (JavaScript-only, a consent wall, a bot challenge) prints one line on stderr and exits 2, rather than reporting an empty render as a success. `--json` carries the same verdict as an `empty` field, so a caller can tell "nothing on this page" from "oc could not read this page" and fall back to a browser only when it is worth it
- A shortcut is `oc <site> <verb> [args]`: `oc hn top`, `oc reddit sub ClaudeAI`, `oc gh repo only-cli oc`, `oc ddg search claude code cli`, `oc learn doc azure/aks/what-is-aks`. Name the site by its short name, bare name, or domain (`oc hn`, `oc ycombinator`, `oc news.ycombinator.com`), the last argument takes every word after it so a query needs no quoting, and `oc sites` lists every site with its verbs. A shortcut resolves to a URL and then behaves exactly like `oc open <url>`
- A shortcut is `oc <site> <verb> [args]`: `oc hn top`, `oc reddit sub ClaudeAI`, `oc gh repo only-cli oc`, `oc ddg search claude code cli`, `oc learn doc azure/aks/what-is-aks`, `oc py library json`. Name the site by its short name, bare name, or domain (`oc hn`, `oc ycombinator`, `oc news.ycombinator.com`), the last argument takes every word after it so a query needs no quoting, and `oc sites` lists every site with its verbs. A shortcut resolves to a URL and then behaves exactly like `oc open <url>`
- X profiles and individual posts read without a login (about 390 and 260 tokens); X search, explore, and hashtag pages do not, and oc reports the block instead of guessing
- Outbound fetches honor `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` (and their lowercase forms), so oc works in a sandbox whose only route to the network is a proxy. An https target is tunneled with CONNECT and its certificate is still verified, credentials in the proxy URL reach the proxy and nothing else, and private or locally unresolvable targets stay refused. `ALL_PROXY` is not read
- Requests impersonate Chrome, so pages that block plain scripts often still work
Expand Down
5 changes: 3 additions & 2 deletions skills/web-browsing-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ None of these except `open`/`do`/`raw <url>` fetch anything; they replay the pag

## Site shortcuts

`oc <site> <verb> [args]` resolves to a URL and then behaves exactly like `open` on it, so it costs the same and reads the same. It saves guessing a URL shape and, on a few sites, points at the feed or public API that answers without a login.
`oc <site> <verb> [args]` resolves to a URL and then behaves exactly like `open` on it, so it costs the same and reads the same. It saves guessing a URL shape and, on a few sites, points at the feed or public API that answers without a login. `search` on `py`, `node`, and `ruby` ranks the docs' own index locally, and on `mdn` asks the site's API; each prints a normal numbered result page.

```
oc hn top oc reddit sub ClaudeAI oc gh repo only-cli oc
oc wiki article Eiffel Tower oc wiki search anthropic oc wiki lang de Berlin
oc ddg search claude code oc so question 231767 oc learn doc azure/aks/what-is-aks
oc py library json oc mdn js Array/map oc node api fs
```

Sites: `hn`, `reddit`, `gh`, `x`, `linkedin`, `ddg`, `bing`, `so`, `finance`, `yt`, `aws`, `gcp`, `learn`, `wiki`. Name one by short name, bare name, or domain (`oc hn`, `oc ycombinator`, `oc news.ycombinator.com`). The last argument takes every word after it, so a query or title needs no quoting. `oc sites` lists every site with its verbs, which is cheaper than guessing one.
Sites: `hn`, `reddit`, `gh`, `x`, `linkedin`, `ddg`, `bing`, `so`, `finance`, `yt`, `aws`, `gcp`, `learn`, `wiki`, `py`, `mdn`, `node`, `ruby`, `go`, `rust`, `java`, `php`, `cpp`, `ts`. Name one by short name, bare name, or domain (`oc hn`, `oc ycombinator`, `oc news.ycombinator.com`). The last argument takes every word after it, so a query or title needs no quoting. `oc sites` lists every site with its verbs, which is cheaper than guessing one.

Prefer a shortcut over a hand-built URL when one exists for the site, and prefer `oc wiki article <title>` over a search when you already know the article's name.

Expand Down
Loading
Loading