# Terminal

> The twenty-three commands you and the agent share in code.extension.dev, the verification ladder, and what the terminal does not do.

Canonical: https://docs.extension.dev/code/terminal
This page as Markdown: https://docs.extension.dev/code/terminal.md
Section: Code
Siblings: [AI generation](https://docs.extension.dev/code.md) · [Chat mode](https://docs.extension.dev/code/chat-mode.md) · [Starters and templates](https://docs.extension.dev/code/starters.md) · [Live preview](https://docs.extension.dev/code/preview.md) · [Select to edit](https://docs.extension.dev/code/select-to-edit.md) · [The Code tab](https://docs.extension.dev/code/code-tab.md) · [Build console](https://docs.extension.dev/code/build-console.md) · [Checkpoints](https://docs.extension.dev/code/checkpoints.md) · [Drafts](https://docs.extension.dev/code/drafts.md) · [Firefox engine](https://docs.extension.dev/code/firefox-engine.md) · [Download and run locally](https://docs.extension.dev/code/download-and-run.md) · [Share links](https://docs.extension.dev/code/share.md) · [Publish and import](https://docs.extension.dev/code/publish.md) · [Generation allowance](https://docs.extension.dev/code/allowance.md) · [Settings and sign-in](https://docs.extension.dev/code/settings.md)
Map of every page: https://docs.extension.dev/llms.txt · Whole corpus: https://docs.extension.dev/llms-full.txt

---

# Terminal

The terminal is the left column of
[code.extension.dev](https://code.extension.dev), where a coding agent reads
the project, writes files, compiles, and reads the preview back. Reach for it
when you want to run the same commands the agent runs, with no model call.

## Quick start

```text
build
preview-errors
preview-open popup
preview-describe
```

The four commands compile the workspace, read the runtime errors, open the
popup, and print an accessibility snapshot of it. Publishing and sharing stay
on the header's controls.


## How it works

### Nothing compiles on a server

The agent edits files, `build` compiles them inside the page with a
WebAssembly toolchain, and an emulated Chrome runs the result next to the
terminal. The loop stays on your machine.

Chat mode is one switch away in **Settings**. The terminal needs WebAssembly
JSPI, which Chrome and Edge 137 and Firefox 153 or newer ship. Safari does
not yet. A browser without it opens chat mode and says so.

### The twenty-three commands

The agent has a fixed toolchain of 23 commands, and anything else you type
is a prompt. They fall into three groups, listed under Reference.

Seventeen run here, in the workspace, with no model call when you type
them: `build`, `changes`, `checkpoint`, `template`, `usage` and the twelve
`preview-*` commands. Their output lands above the terminal.

Four are the agent's: `publish`, `share`, `download` and `run-in-chrome`.
Each has a header control with its own confirmation, so the bare word prints
a notice such as `publish is the agent's command. Use Publish in the header,
or ask: "publish this extension".` Asked for, the agent runs them.

Two go to the agent as prompts: `loop-trace` and `preview-verify`. The agent
runs them and reports.

Quote a selector that starts with `#`. A `#` opens a shell comment, so
`preview-click "#save"` works and `preview-click #save` does not. The same
holds for `preview-type` and `preview-text`.

### Read the service worker console

`preview-logs` is the DevTools console for the background service worker and
the surfaces, read from the terminal. Every row carries its realm, its level,
and its time, so a background alarm and a popup warning read in the order they
happened:

```text
Background worker: ready.
Console (2 of 2):
  [background] log 10:00:01.000 alarm fired
  [popup] warn 10:00:02.000 popup warned
```

`--realm background` keeps one realm, and `--tail N` keeps the last N rows,
forty by default. An extension that declares no background service worker is
told so instead of reading silence. Content scripts do not forward their
console. Their errors reach `preview-errors`. A row tagged `[unattributed]`
came from a window the preview could not identify, and the command says not
to read it as the extension's own output.

### Read the chrome.* trace

`preview-network` says which `chrome.*` names were called, without their
arguments and without a content script's own `fetch`. `preview-trace` says
how each call went, one line per call, oldest first:

```text
Trace (3 of 3; 1 refused):
  t+0ms guest chrome.storage.local.get lane=emulated refused=no dur=3ms
  t+10ms background chrome.tabs.query lane=real refused=no downgraded=yes dur=12ms
  t+25ms surface chrome.storage.sync.set lane=emulated refused=yes dur=1ms error="denied"
t+ counts from the first traced call; dur is the bridge's own answer time.
```

`t+` is the time since the first traced call. The realm is who asked:
`background` for the service worker, `guest` for a content script in the
sample host page, `surface` for an open popup, options page, or side panel.
The `lane` field is who answered: `emulated` is the preview's own answer,
`real` is a real browser's. A refused call carries its error, and
`downgraded=yes` means the call asked for one answerer and another answered.

`--tail N` keeps the last N rows, forty by default, and `--realm` and
`--lane` keep one realm or one answerer. The preview keeps the last five
hundred calls, so a busy extension cannot flood a turn. Before the first call,
the command says so and tells you to run the action that should call
`chrome.*`.

### Read chrome.storage

`preview-storage` prints what the extension wrote to `chrome.storage`, all
three areas at once, one area, or one key in one area:

```text
local (2 keys):
  greeting: "hello"
  count: 3
sync: empty
session (1 key):
  token: "abc"
```

Together with `preview-logs` this is how a background action is proven without
opening a surface. The worker logged what it did, and the storage holds what
it wrote.

### Fixture pages

The sample host page is where content scripts run, and by default it is one
generic page. `preview-navigate list` prints six fixture pages the preview can
show instead, each built for a shape real extensions meet: `article`
(headline, byline, sections), `product` (price, options, an add-to-cart
form), `search` (a query form and ten results), `frames` (two same-origin
iframes), `live` (a table and a feed that change on a timer), and `login` (a
form that stays on the page).

`preview-navigate product` moves the page and prints what it contains. A
content script whose `matches` name `example.com` runs on every fixture. The
fixtures are served by the preview itself. Nothing is fetched from the
internet, and a name outside the list is refused with the list.

### Inside a surface

A popup, options page, side panel, devtools panel, new tab page, or a
history or bookmarks override is a document of its own. Once `preview-open`
has opened one, the click, type, and read commands act inside it rather than
on the sample host page. The open line says so:

```text
Opened popup. preview-click, preview-type, preview-text and preview-describe now act inside it (add --host to reach the sample host page instead); verify it with preview-errors and preview-network.
```

From there `preview-click "#inc"` answers `Clicked 1 element(s) inside the
popup.`, `preview-type "#name" Ada` answers `Typed into 1 element(s) inside
the popup.`, and `preview-text "#count"` prints the text it finds.

`preview-describe` prints `Inside the popup:` followed by the document's
title, a viewport line with the scroll position, and an accessibility snapshot
of the page. The snapshot is one node per line, indented by depth, in the
shape `[uid] role "name" state`. For a popup with a heading, a counter, and
one button it reads:

```text
[1_1] RootWebArea "Counter"
  [1_2] heading "Counter" level=1
  [1_4] paragraph
    [1_5] StaticText "0"
  [1_6] button "Add one"
```

A heading carries its `level`, an input carries its `value`, and a control
carries its state: `checked`, `disabled`, `expanded` or `collapsed`,
`selected`, `pressed`, `focused`, `required`, `readonly`. The snapshot stops
at eighty nodes and says `+K more`. A password field shows bullets in place of
its value, so a secret typed into a form never reaches the terminal or the
model.

The uid is a handle into that snapshot, and `preview-click` and
`preview-type` take one in place of a selector. `preview-click 1_6` clicks the
button above, the same as `preview-click "#inc"`. Every `preview-describe`
takes a new snapshot and the uids change with it, so `1_6` becomes `2_6` on
the next call. A uid from an older snapshot is refused by name:

```text
No node with uid 1_6 is in the current snapshot; run describe again and use a uid it prints.
```

`--host` on any of the four points it back at the sample host page while a
surface stays open, and the output says which page it acted on: `Sample host
page, not the open popup; drop --host to act inside it.`

Hands act inside a surface the way a user would. `preview-errors` and
`preview-network` remain the way to verify what the extension did about it.

### The edit loop

Every write compiles on its own after a short wait. The workbench waits 150 ms
after a lone write and 500 ms after a burst. A second write landing inside the
open wait makes it a burst and resets the wait to 500 ms, so a chain of
heredocs lands as one cycle. `build` skips the wait and compiles now.

An edit reloads only what it changed, in one of four classes: frame,
content, worker, or full. `preview-errors` names the class that ran on a
`Reload:` line. `loop-trace` prints the timing of the last cycle, span by
span. [Live preview](/code/preview) explains the classes and
[Build console](/code/build-console) the trace.

### Going back in time

Every applied edit records a checkpoint, and the header control that shows
your extension's name lists them. A checkpoint written by a terminal turn
carries a chip with the turn that produced it, so the list reads as the
history of the conversation and not only of the files.

That chip is what makes a rewind possible. On the first checkpoint of each
turn the row offers **Resume fx here**. Take it and the files go back to that
checkpoint and the conversation goes back to the turn that wrote them. The
turns you leave are parked as a branch, three per project, and **Resume this
branch** swaps back. After a rewind the agent does not remember the turns
past the checkpoint and is told not to cite them. It still reads the files in
front of it. See [Checkpoints](/code/checkpoints).

### The verification ladder

The agent climbs five rungs in order, and so should you. Fix what a rung
reports before you climb to the next. A green rung above a broken one is a
guess.

1. **Build.** Run `build` to compile and read the diagnostics. Clear every
   diagnostic first.
2. **Errors and state.** Run `preview-errors` to read runtime errors from
   the live preview, `preview-logs` to read the background service worker's
   console, and `preview-storage` to read what the extension wrote to
   `chrome.storage`. Clear the errors first. The logs and storage are how a
   background action is proven without opening a surface.
3. **Eyes.** Run `preview-describe` and `preview-text` to read the page in
   front of you: the open surface once `preview-open` succeeded, otherwise
   the sample host page your content scripts run in. They take a CSS
   selector or a uid from the last snapshot, never a surface name. `--host`
   is the one flag, and it points them at the sample host page while a
   surface is open.
4. **Hands.** Run `preview-open <surface>`, then `preview-click` and
   `preview-type` act inside that surface the way a user would. Without an
   open surface they drive the sample host page. Prove the flow, then read
   the state with `preview-errors`, `preview-logs`, and `preview-storage`. A
   refusal naming `surface-not-open` means run `preview-open` first. One
   naming `surface-unreachable` is final: stop after one, report it word for
   word, and prove that surface from `preview-errors` and `preview-network`.
5. **Handoff.** Run `run-in-chrome` to hand the extension to your own Chrome.
   This is the last rung, and you ask for it.

`preview-verify [surface|content]` climbs all five in one call and prints a
verdict whose last line is `PASS` or `FAIL`. Every refusal is a non-zero
exit with its reason on the error stream. Trust it over a guess.

### Real Firefox beside the emulator

The emulator next to the terminal is Chromium shaped. A real Gecko can run
the build in its place: **Switch to Firefox** in the preview's actions bar
adds a `/firefox` suffix to the address and boots the engine in the page.
The switch is drawn on desktop browsers with JSPI, and the engine admits only
logins on its own allowlist.

`preview-firefox` prints whether the engine is alive, which build it judged,
and Gecko's verdict in one call. When Gecko refuses an install, the refusal
sentence appears word for word above the terminal and in `preview-errors`,
and the agent reads the same sentence. Firefox refuses
`background.service_worker`, so the preview adapts the manifest on its way
to Gecko. See [Firefox engine](/code/firefox-engine).

### What the terminal does not do

These limits are how the terminal stays a browser loop with no server behind
it.

- **No npm, node, or web-ext.** No package manager lives here, and none is
  needed. `build` is the whole toolchain, and it handles TypeScript, React,
  and CSS on its own. Do not add build tooling.
- **No web access.** The agent never fetches a URL or reads a page off the
  internet. Everything it reads is in the project or the preview. The fixture
  pages behind `preview-navigate` are drawn by the preview, not fetched.
- **Hands reach the open surface and the sample host page, nothing else.**
  The click, type, and read commands act inside the surface `preview-open`
  opened, or on the sample host page. They never reach a real browser tab or
  a page the preview did not draw.
- **No real pixels.** The preview has eyes, not a camera. `preview-describe`
  reports the accessibility tree and `preview-text` the text, never an image.
  Where another tool would take a screenshot, the snapshot is the honest
  substitute. It tells you what the page says, not what it looks like.
- **`preview-network` reports names, not arguments.** It lists the `chrome.*`
  calls the extension made, so you can confirm a storage write or a tabs
  query fired. It does not capture the call arguments, and it does not see a
  content script's own `fetch`. The command says so when you run it.
- **No images, paste, or clipboard.** You cannot see a screenshot, and a
  command never copies a link over what you had on your clipboard. `share`
  prints its link instead.
- **No agent undo.** `/undo` tracks an edit tool this terminal does not use,
  so it has nothing to undo. To roll the extension back, restore a checkpoint
  from the header. Every applied edit records one, and a rewind takes the
  conversation back with the files.

### The Code tab and the terminal

The Code tab and the terminal edit the same files, and each side sees the
other's work. Edit a file in the Code tab between turns and the agent's next
turn finds `CHANGES.md` at the root of the workspace, a unified diff of what
you changed since it last looked, with the instruction to read it before
writing to the same paths. `changes` prints the same diff and marks it read,
or says `No edits outside the terminal since your last turn.` The file is a
message, not a project file. It never lands in your project.

In the other direction, every file the agent writes is listed above the editor
under **Changed by the agent** until you open it, and the Code tab carries the
count of files you have not looked at yet. **Review changes** opens the diff
view. See [The Code tab](/code/code-tab).

### The generation allowance

Every model call spends one unit of your generation allowance. `usage` prints
what is left, and **Settings** shows the used count, the limit, and the reset
before a refusal does. The allowance has a daily ceiling and a monthly one,
and both reset on the UTC calendar. When either is spent, only generation
locks. The workspace, the preview, and `download` keep working. The refusal
card links to the pricing page and its main button is **Notify me**.

A second, separate ceiling bounds the size of what the terminal generates per
UTC day, 2,000,000 tokens, and a call over it is refused as `TOKEN_CEILING`
without spending a unit, until the next UTC midnight. One turn can make at
most 40 model calls, and the 41st is refused as `TURN_CALL_CAP` with no unit
charged. See [Generation allowance](/code/allowance).

### Your account

code.extension.dev needs a sign-in, so every command runs on your account.
`publish` is the step that puts the extension on your account, and `share`
mints a link anyone can open. The agent's guide tells it to run them only
when you ask, `publish` most of all, and the bare words refuse to run from
your own keyboard. If your session ended, `publish` and `share` refuse out
loud, and the refusal carries the sign-in link. `download` writes to `.out/`
and needs no session.

The conversation is saved with the draft. Open the same draft on another
browser and the terminal resumes where it stopped. `/new` starts a fresh one.

## Reference

Run here, with no model call:

| Command | What it does |
| --- | --- |
| `build` | Compiles the workspace now and prints the diagnostics. Run it after every edit. |
| `changes` | Prints the diff of the files you edited in the Code tab since the agent's last turn, and marks it read. The same diff waits in `CHANGES.md` while it exists. |
| `checkpoint <message>` | Records a restore point you can roll back to from the header. |
| `template <slug\|words\|list>` | Seeds an empty workspace from a catalog template, by slug or by a few words. `template list` prints the slugs. Words with no close match print the three closest slugs instead, and a workspace that already holds a claimed project is refused. |
| `usage` | Prints how much of your generation allowance is left. |
| `preview-errors` | Reads runtime errors and surface status from the live preview, the way you read a browser console. |
| `preview-firefox` | Reads the Firefox engine's verdict on the latest build: liveness, applied or refused, and Gecko's refusal sentence. |
| `preview-open <surface>` | Opens a declared surface: popup, options, side-panel, devtools, newtab, history, or bookmarks. The click, type, and read commands then act inside it. |
| `preview-click [--host] <selector\|uid>` | Clicks an element inside the open surface, or in the sample host page when nothing is open or `--host` is given. The target is a CSS selector or a uid from the last `preview-describe`. |
| `preview-type [--host] <selector\|uid> <text>` | Types text into an element inside the open surface, or in the sample host page when nothing is open or `--host` is given. |
| `preview-text [--host] [selector]` | Reads text from inside the open surface, or from the sample host page when nothing is open or `--host` is given. The body by default. |
| `preview-describe [--host]` | Prints the preview state, then an accessibility snapshot of the open surface, one `[uid] role "name" state` line per node. The sample host page when nothing is open or `--host` is given. |
| `preview-network` | Lists the `chrome.*` API calls the extension has made. |
| `preview-logs [--realm background] [--tail N]` | Reads the background service worker's console and the surfaces' console, in time order. |
| `preview-trace [--tail N] [--realm background\|guest\|surface] [--lane emulated\|real]` | Prints every `chrome.*` call the extension made, with who asked, who answered, whether it was refused, and how long it took, newest last. |
| `preview-storage [local\|sync\|session] [key]` | Reads `chrome.storage` as the extension sees it, all three areas. |
| `preview-navigate <fixture\|list>` | Moves the sample host page to a fixture page so a content script can be proven on a real shape. |

The agent's, with a header control of their own:

| Command | What it does |
| --- | --- |
| `publish` | Puts the extension on your account and prints the import link. |
| `share` | Mints a preview link from the last build and prints it. |
| `download [dist\|source]` | Saves a zip of the build or the source into `.out/` and hands it to the browser. |
| `run-in-chrome` | Prints the handoff that runs the extension in your own Chrome. |

Sent to the agent as a prompt:

| Command | What it does |
| --- | --- |
| `preview-verify [surface\|content]` | Climbs the ladder in one call and prints a verdict: the build status, the runtime errors, the surface it opened when you name one, what a content script reads from the sample host page, and what the open surface says. The last line is `PASS` or `FAIL`, and `FAIL` exits non-zero. |
| `loop-trace [--last N] [--json]` | Prints the timing of the last edit-to-paint cycle, span by span: the debounce wait, the compile, the reload, and the total. |

Limits:

| Limit | Value |
| --- | --- |
| Commands in the toolchain | 23, of which 17 run with no model call |
| Debounce after a lone write | 150 ms |
| Debounce after a burst of writes | 500 ms |
| Accessibility snapshot | 80 nodes, then `+K more` |
| Trace rows kept by the preview | 500 |
| Trace and log rows printed by default | 40 |
| Time a surface has to answer a hand | 4 seconds |
| Parked branches per project | 3 |
| Model calls per turn | 40 |
| Tokens generated per UTC day | 2,000,000 |

## When it fails

- **The terminal is missing and the page opened in chat mode.** The browser
  has no WebAssembly JSPI. Use Chrome or Edge 137 or newer, or Firefox 153 or
  newer. Safari does not ship it yet.
- **A command answers `surface-not-open`.** No surface is open. Run
  `preview-open popup`, `preview-open options` or `preview-open side-panel`,
  then drive it.
- **A command answers `surface-not-responding`.** The surface did not answer
  within four seconds, for example a popup whose script threw before it could
  listen. Read `preview-errors` for the reason.
- **A uid is refused by name.** The snapshot it came from is stale. Run
  `preview-describe` again and use a uid it prints.
- **`preview-click #save` does nothing.** The `#` opened a shell comment.
  Quote the selector: `preview-click "#save"`.
- **`publish`, `share` or `download` only print a notice.** The bare word is
  the agent's command. Use the header control, or ask the agent in a
  sentence.
- **A call is refused as `TOKEN_CEILING`.** The day's generated-token ceiling
  is spent. No unit was charged. It resets at the next UTC midnight.
- **A turn stops with `TURN_CALL_CAP`.** The turn made 40 model calls. Send
  a new prompt to keep going.
- **`publish` or `share` refuse with a sign-in link.** Your session ended.
  Sign in and ask again. `download` needs no session.
- **Resume fx here is refused on an old turn.** The conversation has already
  compacted past that turn, so there is nothing to resume from. Pick a later
  checkpoint.
- **`loop-trace` says there is no cycle.** Nothing has been written yet. Make
  an edit or run `build`, then read it again.

## Next

- [AI generation](/code/) for the whole workbench around the terminal
- [Live preview](/code/preview) for what the `preview-*` commands read and
  drive
- [Checkpoints](/code/checkpoints) for the rewind a turn chip offers
- [Share links](/code/share) for the link `share` mints
