Repository navigation
Codemode
An agent exploring a repo pays for every intermediate result: each read and each grep is a round-trip whose raw output lands in the transcript and is sent again on every later turn. With omit codemode the model writes a script, the reads happen inside it, and only what the script returns comes back.
Experimental, and read-only.
Codemode ships in omit 0.5.0 and later.
npm install -g @sriinnu/omit @earendil-works/pi-codemode
@earendil-works/pi-codemode is the sandbox, and it is an optional peer: omit installs without it and every other command works without it. Codemode needs Node 22.19 or newer.
With Homebrew there is nothing extra to install: the formula brings the sandbox with it.
brew install sriinnu/tap/omit
Nothing has to be registered. omit codemode run takes a script on stdin, or from a file, and prints the answer:
omit codemode run <<'EOF'
return (await tools.grep({ pattern: 'TODO' })).length
EOFQuote the delimiter ('EOF') so the shell leaves the script alone. Exit status 1 means the script failed or its result was withheld.
The omit-codemode skill teaches an agent when to reach for this and what a script can call. It is skills/omit-codemode/SKILL.md in the repo: copy it into your agent's skills directory, for example .claude/skills/omit-codemode/SKILL.md.
For a host without a shell, or one where you would rather approve a single read-only tool:
claude mcp add omit -- omit codemode # Claude Code
# Codex: ~/.codex/config.toml
[mcp_servers.omit]
command = "omit"
args = ["codemode"]The directory the server starts in is the workspace: every path is relative to it and confined to it.
A script is the body of an async function, so await and return work at the top level.
const libs = (await tools.files({ under: 'lib' })).filter((f) => f.endsWith('.mjs'))
const sources = await Promise.all(libs.map((path) => tools.read({ path })))
return Object.fromEntries(libs.map((f, i) => [f, (sources[i].match(/^export /gm) ?? []).length]))| Call | Resolves to | Notes |
|---|---|---|
tools.files({ under? }) |
string[] |
Tracked and untracked files, minus what git ignores. |
tools.read({ path }) |
string |
One file as text, up to 8 MiB. Past that, use grep. |
tools.grep({ pattern, under? }) |
{ path, line, text }[] |
Extended regular expression. No match is an empty array. |
text(value) and console.log add to the output; return adds the returned value. A call that fails rejects with an Error, so use Promise.allSettled to keep the results of the calls that worked.
There is nothing else in there: no file system, no network, no process, no timers.
- Whatever the script printed, then what it returned (non-strings as JSON).
- Past 20,000 characters the middle is cut, and the result says how much. A script that returns that much has not filtered anything: count, aggregate or filter instead.
- A result that matches a secret rule is withheld whole. A script may read a credentials file; it may not carry the key out.
- On failure, the partial output followed by
Script error (<kind>): …. A script has 60 seconds.
- Read-only. There are no write or edit tools yet.
-
filesandgrepneed a git repository. They read the tree through git, which is what knows what is ignored.readworks anywhere. -
One workspace per server. Paths cannot leave it: no
.., no absolute path, no symlink pointing out.