Build Warp
KINDAreplaces $18/mosaves $216/yrback to the verdict
A block-based terminal whose hard problems are getting command boundaries from the shell instead of guessing at them, treating program output as hostile input to the renderer, and putting a tested redaction boundary in front of the one function that talks to a model.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server, the pty bridge and the build.
Get it nodejs.org or your version manager. open ↗
Verify
node --version prints v22 or higher - decidefree
Why The integration snippet is per shell, and supporting three badly is worse than supporting the one you actually use.
Get it Pick the shell you run daily and write the snippet for its prompt hooks.
Verify
The snippet emits the markers and block rows appear - have readyfree
Why Phase 2 involves piping deliberately hostile output through the renderer, and you want that happening somewhere you do not mind breaking.
Get it Any container runtime, or a virtual machine with a snapshot.
Verify
You can run the hostile fixture inside it and discard the result - API keypay per request; the ceiling in this build is what keeps it small
Why The assistant is the only paid and only networked part of the build, and it is optional by design.
Get it Create a key with your preferred provider and set a spend limit in their console as well.
Verify
One request returns a completion and writes an outbound row - have readyfree
Why The redactor is only as good as what you have attacked it with, and real credentials must never be the test data.
Get it Write fake keys, headers, private key blocks and connection strings by hand into the fixtures directory.
Verify
The suite runs them and reports zero unmasked matches
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- blocks(id, session_id, started_at, ended_at, command, cwd, exit_code, output_path, bytes, truncated) · one row per command, boundaries taken from the shell markers - sessions(id, shell, started_at, ended_at, tty, env_fingerprint) · env_fingerprint is a hash, never the variables themselves - rules(id, pattern_kind, pattern, label, enabled) · redaction rules, shipped with defaults and editable by hand - redactions(id, block_id, rule_id, span_start, span_end, label) · what was masked and by which rule, so a false positive is findable later - outbound(id, at, kind, block_ids_json, byte_count, sha256, model_id, cost_minor) · one row per request that left the machine - proposals(id, block_id, command_argv_json, rationale, accepted_at, rejected_at) · a suggested command is a row you can read, never a keystroke pushed into the pty - secrets_flagged(block_id, at, reason) · blocks excluded from the search index and from any selection offered to the model - No table here holds a model answer beside the raw output it read. The raw output stays in its block file and the answer is a proposal that points at it.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/terminal.db | Blocks, sessions, redactions and the outbound log. Back this up or delete it deliberately. |
OUTPUT_DIR | required | ./data/output | Block output bodies on disk. Expired on a schedule, rows retained. |
OUTPUT_RETENTION_DAYS | required | 14 | How long output bodies survive. Shorter is the sane default for a laptop. |
MAX_BLOCK_BYTES | required | 262144 | Per-block storage cap. Above it the block is truncated visibly. |
REDACTION_DENY_VARS | required | AWS_SECRET_ACCESS_KEY,DATABASE_URL,GITHUB_TOKEN | Variable names whose values are masked wherever they appear in output. |
MODEL_API_KEYsecret | optional | sk-...redacted | Absent by default. Without it the assistant panel is not shown. |
MODEL_ID | optional | a text model identifier | Recorded on every outbound row, because answers differ by model. |
MONTHLY_COST_CEILING | required | 5.00 | The assistant stops at this figure and the terminal carries on. |
The build, in order
A real pseudoterminal, and blocks the shell tells you about
Commands, working directories and exit codes come from the shell rather than from pattern matching on the screen.
A shell attached to a pipe disables its prompt, its job control and its colour.
Files
src/pty.tssrc/server.tsterminalnpm i node-pty @xterm/xterm ws
The shell knows where a command starts and the screen does not.
Files
shell/integration.shsrc/blocks.tstests/blocks.test.tsSilently guessing is worse than visibly not knowing.
Files
src/blocks.tssrc/ui/Banner.tsx
done when · tick each as it passeswatch out- Working out where a command started by scanning the screen for something that looks like a prompt breaks the first time somebody changes their prompt string, opens a full-screen editor, or prints a line containing a dollar sign, and from then on output is attributed to the wrong command in a store you will later hand to a model. Take the boundaries from the shell: the OSC 133 sequence marks prompt start, prompt end, pre-execution and the exit code, and if the snippet is not installed the terminal says block capture is off instead of guessing.
Program output is hostile input
Displaying a file somebody sent you cannot make the terminal answer back, set your clipboard or report your window title.
A list you can read beats whatever the parser happened to support.
Files
src/sequences.tsdocs/sequence-policy.mdAssert that nothing travels back up the pty.
Files
tests/hostile-output.test.tstests/fixtures/hostile.binThe clipboard is somebody else's text arriving at a shell prompt.
Files
src/paste.tstests/paste.test.ts
done when · tick each as it passeswatch out- Displaying a downloaded file is enough for that file to set your window title, ask the terminal to report it back as if you had typed it, or read your selection out as a base64 string, and xterm ships with allowWindowOps false precisely because of this, its disallowed list naming GetSelection, SetSelection, GetWinTitle, GetIconTitle, GetChecksum and SetXprop. Implement those as no-ops and write a test that pipes a crafted file through the renderer and asserts that not one byte travelled back up the pty.
- A pasted block of text containing a newline runs as soon as it lands unless paste is bracketed, and a pasted escape character can put the shell into a mode the user did not ask for, which is why xterm converts backspace, delete, enquiry, end-of-transmission, escape, null and the stty specials to spaces on paste by default. Bracket the paste and filter the same class of characters rather than trusting the clipboard.
One door out, with a filter welded to it
Text cannot reach the network without passing the redactor, and every request that left is recorded.
One door, and the lock is on the door rather than on the corridor.
Files
src/outbound.tstests/outbound.test.tsFabricated credentials only, and the suite runs them on every change.
Files
tests/fixtures/credentials/tests/redaction.test.tsSo the question of what left this machine has an answer.
Files
src/outbound.tssrc/db/outbound.ts
done when · tick each as it passeswatch out- The usual mistake is redacting for display and sending the original, because the highlight and the request get written a week apart by somebody who has forgotten the other exists. Put the filter inside the request function, have it refuse text without a receipt, and prove it by asserting in a test that a block containing a fabricated key never appears anywhere in the recorded request body.
An assistant that proposes and never types
A suggested command is inert text until a person deliberately runs it.
Key handling and paste. Everything else is a proposal.
Files
src/pty.tstests/no-injection.test.tsA pipe hidden in a suggestion is the whole attack.
Files
src/ui/Proposal.tsxThe default first run has no key and is a good terminal.
Files
src/config.tsREADME.md
done when · tick each as it passeswatch out- Pushing a suggested command into the input line so the user can edit it turns a poisoned line in a build log into a command sitting at your prompt, and the model read that log precisely because you asked it to explain the failure. Render proposals as inert rows, show the argument vector rather than a string with pipes in it, and keep a test asserting that the pty write path is reachable only from key handling and paste.
History you own, and a bill that stopsproduct builder
The archive is searchable, exportable, expiring, and the spend has a ceiling.
A secret you masked in transit is still on disk unless you say otherwise.
Files
src/search.tstests/search-exclusion.test.tsThe command history stays useful long after the output stops being.
Files
src/retention.tsWritten from data rather than from memory of what you built.
Files
src/reports/outbound.tsREADME.md
done when · tick each as it passeswatch out- A searchable index of every command you have run is the best thing in this build and the worst thing to leave in plaintext on a laptop that travels, and redaction in transit does nothing about redaction at rest. Exclude flagged blocks from the index entirely, expire output bodies on a schedule, and put the what-left-this-machine report on the first screen of the README rather than in a settings page.
That is the whole plan for Warp. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Team workflows and shared history.
- An agent that runs commands unattended.
- Remote session recording over SSH.
- A native GPU renderer.
- IDE-wide polish and latency
- enterprise policy, telemetry, and support
- frontier coding model quality
- large-context infrastructure
- Per-directory profiles, so a production checkout can force the assistant off entirely
- A second shell integration snippet, once the first one has survived a month of real use
- Encrypting the output directory at rest, which is the obvious next thing after the retention policy
Need the files? The project pack on the verdict page hands your agent the whole brief · more dev tools.