Why the strongest coding agents live in the terminal

A CLI agent is a coding agent you talk to in text. It runs in a terminal on a machine you own, a Mac, a Linux box, or a server. There it inherits your permissions: read every file in the project, run any command, read the result back. Agents live in terminals because no other interface grants all three.

"CLI" still reads like a punishment: no mouse, no buttons, one blinking cursor. Yet Claude Code, Codex, Gemini CLI and OpenCode all live there. Their makers are not purists. The terminal grants two things no other interface grants together: full access to your files, and permission to run commands. That same permission is why it asks you before running anything.

What is a terminal, once you strip the buttons away?

Two programs are stacked here, and beginners blame the wrong one. The terminal is the display layer: it draws a grid of characters and forwards your keystrokes. Behind it sits the shell (zsh, bash, fish), which waits for a line of text, runs it, and prints what that program wrote.

That thinness is why an agent can drive a real project end to end. The commands it types are commands you would have typed, and the errors it reads are the errors you would have seen.

Three things you read off a terminal screen

Three parts of that screen carry the meaning. The rest is decoration.

yuki@mini ~/projects/auth (main)
$ npm test

 ✓ src/session/refresh.test.ts  (6 tests)
 ✗ src/session/expire.test.ts > drops a session after expiry

     Tests  1 failed | 41 passed (42)

$ echo $?
1

The prompt is that first line. It says who you are, which machine you are on, which directory, and which branch. That line stops being decoration once the machine is not in front of you. After an SSH login it is how you tell your laptop apart from a server you forgot you were still logged into.

Standard output (stdout) is the text the program wrote. The second stream, stderr, keeps its complaints separate so the real output stays readable.

The exit status. Every command leaves one integer behind, and the shell hands it to you as $?. Zero means the program claims success. An agent reads that number rather than the words "1 failed", and a 1 is its signal that the loop is not closed yet.

The interface decides what an agent can reach

A browser agent cannot see a single file on your computer. It lives inside a tab. An IDE plugin sees the files you have open but usually stops at suggestions: it cannot touch git, cannot run your tests, and cannot install packages.

A CLI agent holds the permissions you hold. It reads the whole repo, runs git commit, runs the test suite, and installs dependencies. Access arrives in three tiers, and the risk climbs with each one:

  • Read. Every file in your home directory, including the keys in .ssh.
  • Write. Inside a git project, a bad edit is one git checkout away. Outside one, nothing rolls it back for you.
  • Execute. Anything you could type, including a migration, a force-push, or an rm -rf pointed at the wrong path.

No vendor designed this gap to please anyone. It is the permission set that comes with the terminal, and every other interface gives away less of it.

Which commands can it run that a sandboxed agent cannot?

"Execution rights" is an abstraction. Here is the same thing as lines on a screen:

npm test
npx vitest run src/session/expire.test.ts

git status --short
git commit -m "fix: expire sessions on the dot"

npm install date-fns@3
npm run build

psql "$DATABASE_URL" -f db/schema.sql
curl -sf localhost:8000/health | jq .db

A hosted sandbox has nowhere to run those. That psql connects to a database that exists only on your machine, using a URL from a .env file that was never uploaded. That npm install pulls an internal package from a private registry named in your .npmrc. Those git commands run in a clone that carries your remote, your hooks, and your signing key.

A sandboxed agent can hand you a patch, but it cannot observe what that patch did. An agent that cannot see the result of its own edit cannot finish the task.

The loop that closes without you pressing anything

You send one line: "fix this bug." A dozen commands of that kind follow, and your hands stay off the keyboard:

  1. It greps for the failing message, reads three files, edits one.
  2. It runs npm test, reads the exit status, sees 1.
  3. It narrows to one case, edits again, reruns the whole suite, gets 0.
  4. It commits, applies the migration locally, and curls the endpoint that was broken.

Not one step in that chain needs a click. Put the same agent in a browser tab or an IDE plugin and it stalls at step two. It can produce the edit, but it cannot run the suite, so it waits for you to run the tests and tell it what happened. You have become the test runner. That is a permissions problem, not a model-quality one.

Why is a byte stream worth more than a window?

The program does not live inside the terminal window. It is attached to a pseudo-terminal, a kernel object with two ends. Bytes go in one end and come out the other, and your window is whoever happens to be holding the far end right now. Three things follow from that.

  • It can be saved. npm test > build.log writes that text to a file, which another program can read a year later. A GUI's canvas is a picture, and a picture is not a record.
  • It can be chained. git ls-files | grep .env feeds one program's output into the next program's input.
  • It can be moved. Nothing in the definition says the two ends have to be in the same place. tmux parks the session on the server, and your screen attaches, detaches, and attaches again from another device.

The third one is the point of this guide. A byte stream can be redirected, stored, replayed, and reattached from somewhere else. A rendered window cannot. Persistent sessions, SSH, and the same agent on your phone all follow from that one property.

What does full access cost you?

The permission that lets an agent migrate your database is the permission that lets it delete one. That is why an agent stops mid-task and asks: "Run this command?" The prompt is not timidity. An agent that ran everything without asking would be deciding about your .ssh directory and your production database on your behalf, from guesswork. The prompt is where you draw the line between tier two and tier three, and it can only be drawn at that moment: the command is known, and it has not run yet.

Two habits cover most of the risk. Read the command, not the summary: "I'll clean up the build cache" is a description, and only the line under it has consequences. Approve a category once: if it asks you to run tests forty times a day, write one rule that lets npm test through.

A prompt you were going to approve anyway costs ten seconds. One you approve by mistake, against a live database, costs a day and a restore. That ratio is why AI Permission Review exists: a blacklist blocks what is obviously dangerous, an allowlist passes what you already trust, and a model judges the rest.

When a GUI is the better tool

A terminal is not the right interface for everything you do to code. These are strictly worse in text:

  • A live debugger. Set a breakpoint, step through the loop, inspect a variable at frame four.
  • Anything visual. Layout, spacing, a colour that is almost right. Asking an agent to describe a screenshot is worse than looking at one.
  • Reading a large diff. Two thousand lines side by side is why you review an agent's work in a GUI, even when the agent wrote it.
  • Still learning. An editor underlines the exact token where you went wrong, which teaches faster than a wall of stack trace.

The line here is not skill against laziness. It is "you are here and driving" against "it is running and you are checking in". At a desk an IDE is often the better instrument, and the people who get the most out of a CLI agent keep both. That division of labour is its own article.

The terminal puts an agent on your machine, and its output is characters that can live anywhere. So that machine does not have to be the one you are sitting at. Getting there from somewhere else is SSH's job.