Terminal
The terminal is the left column of 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
build
preview-errors
preview-open popup
preview-describeThe 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:
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:
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:
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:
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:
[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:
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 explains the classes and 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.
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.
- Build. Run
buildto compile and read the diagnostics. Clear every diagnostic first. - Errors and state. Run
preview-errorsto read runtime errors from the live preview,preview-logsto read the background service worker's console, andpreview-storageto read what the extension wrote tochrome.storage. Clear the errors first. The logs and storage are how a background action is proven without opening a surface. - Eyes. Run
preview-describeandpreview-textto read the page in front of you: the open surface oncepreview-opensucceeded, 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.--hostis the one flag, and it points them at the sample host page while a surface is open. - Hands. Run
preview-open <surface>, thenpreview-clickandpreview-typeact 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 withpreview-errors,preview-logs, andpreview-storage. A refusal namingsurface-not-openmeans runpreview-openfirst. One namingsurface-unreachableis final: stop after one, report it word for word, and prove that surface frompreview-errorsandpreview-network. - Handoff. Run
run-in-chrometo 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.
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.
buildis 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-navigateare 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-openopened, 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-describereports the accessibility tree andpreview-textthe 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-networkreports names, not arguments. It lists thechrome.*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 ownfetch. 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.
shareprints its link instead. - No agent undo.
/undotracks 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.
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.
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. Runpreview-open popup,preview-open optionsorpreview-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. Readpreview-errorsfor the reason. - A uid is refused by name. The snapshot it came from is stale. Run
preview-describeagain and use a uid it prints. preview-click #savedoes nothing. The#opened a shell comment. Quote the selector:preview-click "#save".publish,shareordownloadonly 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. publishorsharerefuse with a sign-in link. Your session ended. Sign in and ask again.downloadneeds 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-tracesays there is no cycle. Nothing has been written yet. Make an edit or runbuild, then read it again.
Next
- AI generation for the whole workbench around the terminal
- Live preview for what the
preview-*commands read and drive - Checkpoints for the rewind a turn chip offers
- Share links for the link
sharemints