Typed records
ls returns fs.entry records with a filesize and a
date, ps returns proc records. where size > 10mb
needs no parsing, and ps | where cpu > 50 | kill needs no xargs.
Unix pipes move text, and every pipeline hides a small parser that breaks silently. meersh passes typed records instead, makes language models ordinary pipeline stages, and checks every action against who wrote it, what it touches and whether it can be undone. Written in Rust, for Linux, local models first.
A narrated tour of the real shell in a made-up project. Watch records flow through pipes, a question mark turn plain words into a pipeline, and a model plan a deletion that waits for your approval and can be undone. Then see the permission model decide what you, a model and a coding agent may do, and the kernel hold a program to the folder you allowed.
PowerShell showed that a shell can pass objects. meersh does it on Linux, with plain data that survives a pipe, a file or a language model.
ls returns fs.entry records with a filesize and a
date, ps returns proc records. where size > 10mb
needs no parsing, and ps | where cpu > 50 | kill needs no xargs.
$f.delete(), $f.hash(), $p.ports(). Every
object carries its identity, so a stale listing can never hit a different file
or a reused pid. help fs.entry lists fields and methods.
Adapters turn ip, ss, systemctl,
journalctl, git and more into tables when a builtin reads
them. Rust tools can send real objects across the process boundary.
〉ls | where size > 1kb | sort-by size -r ╭───┬───────────────┬──────┬─────────┬─────────────────────╮ │ # │ name │ type │ size │ modified │ ├───┼───────────────┼──────┼─────────┼─────────────────────┤ │ 0 │ build-old.bin │ file │ 6.7 MiB │ 2026-10-29 23:00:00 │ │ 1 │ dump.sql │ file │ 2.0 MiB │ 2026-10-29 23:00:00 │ │ 2 │ README.md │ file │ 1.2 KiB │ 2026-10-29 23:00:00 │ ╰───┴───────────────┴──────┴─────────┴─────────────────────╯
A model can write a pipeline, work on data, or act on objects. It never gets more than the role needs, and local models through Ollama are the default.
llm map, llm where, llm parse,
llm rank, llm group and llm join take a meersh
type, turn it into a JSON Schema and return typed values. Rows go in batches,
answers are cached, and unsure rows can cascade to a stronger model.
ls | llm "delete all files larger than 5mb" shows the model only
the type and its methods, never the files. It writes a pipeline over the input,
meersh checks that it can only touch what was piped in, and plans it.
? the three biggest files here turns a request into a pipeline. The
planner sees the request and the types, not the data, so text inside your files
cannot steer it. This follows the CaMeL design against prompt injection.
〉ls | llm "delete all files that are larger than 5mb" where size > 5mb and type == file | each { $it.delete() } # actor wrote this. The instruction asks to delete all files larger than 5mb. The pipeline first filters the table for rows where the size is greater than 5mb and the type is a file. Then, it applies the delete operation to each matching file. ╭───┬───────────┬────────┬───────────────┬────────┬─────────┬────────────┬──────────┬──────────────────╮ │ # │ principal │ action │ target │ detail │ size │ reversible │ decision │ because │ ├───┼───────────┼────────┼───────────────┼────────┼─────────┼────────────┼──────────┼──────────────────┤ │ 0 │ actor │ trash │ build-old.bin │ │ 6.7 MiB │ true │ ask │ not created by … │ ╰───┴───────────┴────────┴───────────────┴────────┴─────────┴────────────┴──────────┴──────────────────╯ 1 actions, 6.7 MiB, all reversible. 1 need your approval, 0 refused. not applied. Run interactively, or add --approve or --approve-only <rows>. error: nothing was changed
A real run with meersh -c. The model saw table<fs.entry>
and its methods, nothing else. Without a terminal to ask, meersh applies nothing. In the
shell the same plan waits for your answer, for all rows or only some.
Unix asks only which user runs a program, and every program of that user may delete every file of that user. meersh also asks who wrote the command. A line you typed, a script, the assistant, a model and a coding agent each run in a role of their own, and each role has its own rights.
user
A line you type may do everything Unix allows. Protected paths such as
/etc, ~/.ssh and .git ask first. So does
a target that a model picked in your pipeline, as in
ls | llm where "junk" | rm.
script:<path>A script file you run has your rights. The journal records each of its actions under the script's name, so you can see what it did and take it back.
plan
? request has a model write a pipeline. You see it with an
explanation, and nothing runs before you say yes.
actor
In ls | llm "delete ..." the model may only filter the objects it
was handed and call their methods. You approve the rows, at most 50 per plan.
agent:<name>
Claude Code and other agents work through meersh mcp. Each keeps
what it created across sessions and leases the files it edits. Whatever needs
you comes back unapplied.
| Action | You and your scripts | Assistant, models and agents |
|---|---|---|
| Create a new file in the project | allow | allow |
| Change, move or delete what it created itself | allow | allow |
| Change or delete any other file in the project | allow | askThe old content goes to a snapshot or the trash. |
| Delete someone else's file for good, when neither the trash nor git can bring it back | allow | deny |
| Change or delete files outside the project | allow | deny |
Touch a protected path. These are /, your home directory, .git, ~/.ssh and the system directories. |
ask | deny |
| Stop a process | allow | allowIf it started the process. Otherwise it asks. |
| Run an external program | allow | askThe program runs in an overlay that shows its changes before they are kept. |
| Send data to a cloud model | allow | askA local model is allowed. |
| Change policies, undo actions or switch the model backend | allow | never |
These are the defaults. Rules such as policy allow actor fs.delete owned
change them, and only you can write rules. Everything a model does runs as a plan that
you approve as a whole, row by row or as a narrow rule. policy explain
shows which rule or default decides.
〉[user plan actor agent:claude] | each { policy explain fs.delete dump.sql -p $it } | select principal decision because ╭───┬──────────────┬──────────┬─────────────────────────────────────────────────────────────╮ │ # │ principal │ decision │ because │ ├───┼──────────────┼──────────┼─────────────────────────────────────────────────────────────┤ │ 0 │ user │ allow │ │ │ 1 │ plan │ ask │ not created by this session, can be restored from the trash │ │ 2 │ actor │ ask │ not created by this session, can be restored from the trash │ │ 3 │ agent:claude │ ask │ not created by this session, can be restored from the trash │ ╰───┴──────────────┴──────────┴─────────────────────────────────────────────────────────────╯ 〉[README.md /etc/hostname] | each { policy explain fs.write $it -p agent:claude } | select target decision because ╭───┬───────────────┬──────────┬────────────────────────────────────────────────────────────────────╮ │ # │ target │ decision │ because │ ├───┼───────────────┼──────────┼────────────────────────────────────────────────────────────────────┤ │ 0 │ README.md │ ask │ not created by this session, a snapshot of the old content is kept │ │ 1 │ /etc/hostname │ deny │ protected, a system directory (/etc) │ ╰───┴───────────────┴──────────┴────────────────────────────────────────────────────────────────────╯
meersh mcp gives Claude Code and other agents typed tools instead of a bash
prompt. Each agent is its own principal with its own ledger and leases, so several
agents in one project cannot delete each other's work. Programs your policy allows
run under Landlock, confined to the project. Everything else comes back to the agent
as a plan for you to approve.
claude mcp add --scope user meersh -- ~/.local/bin/meersh mcp --agent claude --unique
The repository has the CLAUDE.md section, the Claude Code permissions and the policy that make an agent use meersh instead of bash, in integrations/claude.
One command installs meersh on any Linux. For the model features, a local Ollama or a logged in Claude Code is enough.
One binary for the shell and one multi call binary for the tools, so they serve
bash users with text too. Both are linked statically and run on every distribution
on x86_64 and ARM64. The script puts them in ~/.local/bin, or in
/usr/local/bin as root. Running it again updates meersh.
curl -fsSL https://github.com/ribalba/meersh/releases/latest/download/install.sh | sh
| Debian, Ubuntu | .deb x86_64 | .deb ARM64 |
|---|---|---|
| Fedora, openSUSE | .rpm x86_64 | .rpm ARM64 |
| Any Linux | .tar.gz x86_64 | .tar.gz ARM64 |
Every version and the checksums are on the
releases page.
To build from source you need a Rust toolchain. Run make install PREFIX=~/.local
in a clone of the repository.
Without a config meersh uses Ollama on this machine. Claude Code and any OpenAI
compatible server are one section of config.toml each. The installer
puts an example in ~/.local/share/doc/meersh, the packages in
/usr/share/doc/meersh.
cd ~/.local/share/doc/meersh cp config.example.toml \ ~/.config/meersh/config.toml llm backends
Tab completes commands, flags and the columns of the data flowing in.
help is a table like everything else.
meersh ps | sort-by cpu -r | first 5 help | where effect == destructive
CONCEPT.md explains the object model and what meersh takes from
Nushell, PowerShell and uutils. AUTHORITY.md explains principals,
ownership, plans and models that act on objects.