The page-view suite now reads Reddit through the Atom feeds, so the
section carries the 2026-09-04 numbers on oc 0.5.3: real content on all
fifteen pages, 118x fewer tokens than raw HTML across the fourteen both
could read, and the two feeds at 459 and 493 tokens. The access paragraph
describes Reddit's metering the way the rerun measured it.
Reddit's edge answers the chrome fingerprint with a 403 or a 429 while
letting firefox through, and it allows anonymous readers about ten
requests a minute per address, so the wasted chrome attempt on every
read was costing a real share of that budget. identityOrder() picks the
order per host: firefox then chrome for reddit.com and its subdomains,
chrome then firefox everywhere else, with the same downgrade chain and
plain fetch fallback as before. Refs #52.
The README access paragraph, llms.txt, and the skill now say that oc reddit
reads the www.reddit.com Atom feeds, that a reddit.com page URL given to
oc open still meets the login wall, and that anonymous reddit.com allows
about ten requests a minute. The Benchmarks section notes that the suite
still measures the old.reddit.com page URLs, not the feed route.
Version 0.5.2 in package.json, the lockfile, the plugin manifest, the
marketplace entry, and the npx pins inside the skill. CHANGELOG covers one
change: oc reddit reads the Atom feeds on www.reddit.com now that old.reddit.com
is behind a login and the www .json views answer 403 (#52, #53).
Since 30 June 2026 every logged-out request to old.reddit.com, HTML, .json,
and .rss alike, lands on /login/?reason=lor2, so every oc reddit verb rendered
an empty login page. The www.reddit.com .json views closed a month earlier and
answer 403 to anything without an OAuth token, whatever the User-Agent or TLS
fingerprint, so they are not a way back in either.
The Atom feeds on www.reddit.com still answer without a login, and the feed
converter already turns one into a page: a subreddit renders in about 480
tokens from a 21,500 token payload, a thread with 22 comments in about 1,000.
Every verb now points at a feed, and new <name> and top <name> join them,
since the feeds offer those sorts for free. The feeds carry no scores or
comment counts, and anonymous reddit.com allows roughly ten requests a minute
per address, which the changelog says plainly.
The fixture is a three-entry post feed in Reddit's shape, one entry for the
post and one per comment, so the thread rendering is covered offline.
Closes#52
only-cli/benchmarks now runs twelve graded lookups on the pages around
a dependency: GitHub, npm, PyPI, RubyGems, crates.io, Docker Hub, Stack
Overflow, an RFC. oc 12/12 at $0.63 against WebFetch 10/12 at $0.70 and
WebSearch 12/12 at $1.22 on Claude Code, with the hard rows called out.
Page view and Claude agent suites rerun on the published 0.5.1 on
2026-09-02. Reddit now sends logged-out readers to a login wall, so oc
reads 13 of 15 pages and says so on the other two; Yahoo Finance refuses
plain fetch. The Codex figures stay from the 0.4.0 and 0.5.0 runs and
are labelled that way.
Version 0.5.1 in package.json, the lockfile, the plugin manifest, the
marketplace entry, and the npx pins inside the skill. CHANGELOG covers the
fingerprint fallback (#40), the footer no longer offering planned commands
(#44), and find in every footer (#46). The README install note and llms.txt
now describe the full transport chain: Chrome, then Firefox, then native
fetch.
cache.js had no test importing it, and cli.js was tested only through its
auth commands. Both now have a file of their own.
tests/cache.test.js proves the policy the module's comment states: a miss
fetches, parses, and writes under host and extension; a fresh copy is served
from disk without touching the network; a copy older than a day is refetched
and replaced; a body the parser rejects is never written, and a stale good
copy survives such a refetch; a cache directory that cannot be created costs
only the refetch. The network is a local proxy that records what it sees, so
"never asked" is asserted, not assumed.
tests/cli.test.js spawns the real binary against a throwaway OC_HOME with a
page seeded straight into a session file, so read, next, find, and do on
text all have something to answer with and nothing fetches. It covers help
on no command and both help flags, the help text naming every dispatchable
command once, unknown first words, a site name reaching the shortcut
resolver before being called unknown, budget and session name validation
running before any command, the nothing-open message from every session
command, the usage lines of open, raw, read, find, and do, do on a text
number reading in place with a recording proxy proving no request left, the
four planned commands failing with one line that names themselves, sites,
and flags placed before or after the command.
Each test was checked against a deliberate break in the code it guards.
find is the cheapest way to go deeper on a rendered page: one command lands
on the block that matters, where read needs the right number first and next
pages toward it. SKILL.md already lists it first under "going further,
cheapest first", but no footer offered it, and the footer is what an agent
actually reads, so callers were steered toward read, next, and raw instead.
The entry now appears in all three footers, after do and before read, so the
order matches the skill. On find's own output it turns the "narrow the
query" hint into an action.
Cost on the fixtures is 3 or 4 tokens per render (news 127 to 131, login 30
to 33, forum 695 to 699). Skipping one next on a long page pays for a
hundred of those.
The test checks the same three footer sites as the stub probe: find is
present and listed ahead of read. It fails on main.
Closes#46.
The footer is the line an agent reads to pick its next command, so a name
in it that cannot run costs a turn and returns nothing. fill and submit are
still stubs, so every page with an input was offering two commands that
throw NotImplemented. Removing them also takes 7 tokens off the rendered
news fixture and 6 off login.
The test probes act.js for handlers that throw NotImplemented rather than
listing them by name, so the next stub to land is covered without anyone
remembering to come back here. It checks all three footers: the render, and
both of find's paths.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
impers resolves the chrome alias to its newest fingerprint (chrome150 as
of impers 0.1.1), but the native library it loads can be an older system
copy of libcurl-impersonate found before its own pinned download. Such a
copy refuses the fingerprint with an ImpersonateError before any request
leaves, and oc died on the spot with "Impersonating chrome150 is not
supported" (#40).
A refused identity now downgrades the same way a 403 already did: chrome
falls back to firefox, and when both are refused the plain fetch
transport still gets the page. Any other impers failure propagates
unchanged.
CONTRIBUTING described a command as a URL template only, so a contributor
adding a docs site had no way to learn that a search verb can also name a
static index (sphinx, rdoc, nodedoc), a JSON endpoint (api), or fall back
to DuckDuckGo with a site: filter, or how to pick between them. It also
still counted six files in src/.
The rust, java, ts, and php search verbs had no test; only cpp's did. #25
asked for both verbs per language to be covered offline.
Dependabot opened separate PRs for init (#35) and analyze (#34). Each
failed because the two steps must run the same version of the action.
Bump init, analyze and upload-sarif together and add a Dependabot group
so future codeql-action bumps arrive as one PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>