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>
The title and every heading are the page's to write, and both skipped
truncate(), so one hostile scalar could print unbounded output whatever
the budget said. The compact view now cuts titles, headings, and input
names at the same cap and marker every other block gets, and read cuts
even a first block bigger than its whole budget, since 'up to N tokens'
is a promise the page must not be able to break. The distilled page
keeps the full values: --json stays the machine-stable view, bounded by
the fetch cap, and machines cut for themselves.
Fixes#28
A syntax highlighter gives every token of a command its own element, so
`s3://bucket/` reached the walk as `s3`, `:`, `//`, `bucket`, `/`, and the
rule that reassembles text fragments only glues the ones sharing a parent.
The rest were space-joined. The AWS CLI reference handed over
aws s3 cp test . txt s3 : // amzn - s3 - demo - bucket / test2 . txt
and Node's fs docs handed over `console . log` and `fd ?. close ()`. Any
command or snippet an agent took from a docs page was wrong, and nothing in
the output said so. Highlighting is not an edge case: of 172 pre elements on
the AWS CLI reference, the Rust book, the Node API docs and the Python
library docs, 159 are split this way.
A pre or code subtree is now read as one string. That costs nothing to
follow, because not one of those 172 blocks contains a link, and it also
fixes inline code, which was inserting a space into `Byte ( u8 only)`.
Two things fall out of it.
Page furniture had to stop riding along. Node puts a language label beside a
copy button inside every code block, so the subtree's text ended in
`javascriptcopy`. A control is chrome, and so is the block-level element
holding it, which is how the label leaves with the button it sits beside.
The test stays on block-level wrappers because a highlighter's own elements
are inline, so a stray control can never take a line of code out with it.
Code blocks keep their lines. Collapsing them was survivable while the code
was already wrecked; once it reads correctly, `// read it back` in front of
the statements that followed it is worse, because the output now looks
trustworthy. Blank runs and the indentation the whole block shares carry no
meaning and still go. A cut lands on a line end for the same reason it
already lands on a sentence end, and a snippet stays one line, because that
is what find's index promises.
Measured over five real pages, the compact view moves by -15, -7, +208, -45
and 0 characters, about 35 tokens in total, all of the growth being Python's
pretty-printed output getting its indentation back.
A tool call inside an agent session costs 23,000 to 33,000 tokens of
overhead whatever it prints, so the page-view win only reaches the
session total if answering a task takes fewer commands. Three places
were charging a command to say what the next command should be, each
found by capturing the command stream of a real agent run rather than
by reading the code.
A search result title is a link. Every engine puts it in an anchor
filling an <h2>, and the walk took the heading's text and returned,
dropping the href, so `do` on the most obvious number on a results page
printed the title back. The agent then spent a second command finding
the number that navigates. The href now rides along when the anchor is
the whole heading, which is the test documentation fails on purpose:
every heading in the Rust book and on an AWS CLI reference page carries
a permalink to its own id, and following one would refetch the page the
agent is already reading.
`find` pointed at its answer. With a single match it printed the block
and a number, and the agent's next command was always the `read` on
that number, so it now prints the region. With several matches it
showed a 200 character snippet of each even when the budget had room
for them whole, so it spends that room, on the same terms `FINISH`
already documents for a page that nearly fits.
A truncated block ended mid sentence. Asked for the first sentence of a
page, an agent was handed it complete, followed by a marker saying 302
characters were cut, and spent a command on `read` to find out whether
the sentence went on. The cut now falls on the last sentence that
finished inside the cap, and measured across five real pages it costs
nothing: four came out within three characters of before.
The package-lock name field catches up with the scoped package name,
which npm rewrites on any install.
Three changes with one motive: a second command costs an agent more than
the lines it saves. 'oc do' on a heading or text block now prints the read
instead of refusing, since refusing spends a whole turn naming the command
that should have run. A page that would finish within about four times the
budget is printed whole rather than cut, because the cut moves tokens into
a second command instead of saving them. And social timelines stopped
rendering as one fused paragraph: linkedom splits text nodes around
apostrophes, so fragments are merged back by parent node and edge
whitespace, block elements now end lines, and repeated button labels are
trimmed sooner than links because a button label is never the content.
With that, x.com profiles and posts read without a login, so a six line
cli definition ships for the two x.com pages that work.
find searches the distilled page the session already holds, prints one line
per match with the number to read it by, and costs no fetch. It matches the
query as a phrase, case insensitive, and falls back to matching the words
separately when the phrase is not there.
On the reddit thread from the benchmark: 'oc find w3m' is 115 tokens against
9,670 for oc raw, and it lands on the numbers to read.
The compact view was all or nothing: an agent that needed more than the 500
token budget had only oc raw, ten to twenty times the price. Now open saves
the distilled page, next continues it where the view stopped, and read <n>
prints one region in full. Headings and text blocks long enough to be cut are
numbered so they can be addressed, and the marker prices what it left behind.
On one Reddit thread: open 475 tokens, next 455, read 88, raw 9,670.
The compact view hides link URLs because printing them is most of what
makes a page expensive, which left an agent re-fetching the same page as
--json or raw just to learn where [15] pointed. oc open now saves the
handles it numbered to a small JSON file per session under ~/.only-cli
(OC_HOME relocates it), and oc do <n> resolves one and renders the target
exactly as open would. Search engine tracking redirects are unwrapped so a
result link opens the destination instead of a script page.
Errors name the command that fixes them, since agents read them: an
out-of-range number reports the valid range, an input says to use fill, a
button says the page handles it itself.
Two hops on Hacker News cost about 3k characters this way, against roughly
23k for the re-fetch route.