Command line (bin/beam)
BEAM's terminal runs in the app. It also runs as a program on your machine —
same engine, same commands, same rules, no browser.
This is the read-only first cut. It is off by default, it does not sign in
to your account, and it is honest about both.
Status: experiment, default OFF, decide-by 2026-10-15. What it does
today is the §7.1 slice of the CLI surface spec (issue #1554).
Running it
BEAM_CLI=1 bin/beam
BEAM_CLI=1 is required. Without it the program prints why it will not start
and exits. The switch exists because a command line is the surface where
"just let it run unattended" is most tempting, and this cut is not ready to be
trusted with that.
Piped input works too, and behaves differently on purpose (see
Two modes):
echo 'today' | BEAM_CLI=1 bin/beam
BEAM_CLI=1 bin/beam --pipe < script.txt
Options
| Option | What it does |
|---|---|
--pipe |
Force the non-interactive posture even in a terminal. Proposals are refused instead of confirmed. |
--store <path> |
Load a plaintext export file (see Your data). Read only — nothing is written back to it. |
--no-color |
No colour. Also off automatically when output is not a terminal, or when NO_COLOR is set. |
-h, --help |
Usage. |
Environment
| Variable | Required | What it does |
|---|---|---|
BEAM_CLI |
yes | Must be 1. Nothing else starts it. |
BEAM_SUPABASE_URL |
no | Also read as VITE_SUPABASE_URL. Not needed for anything this cut does. |
BEAM_SUPABASE_ANON_KEY |
no | Also read as VITE_SUPABASE_ANON_KEY. Same. |
What works
Everything the in-app terminal routes through executeCommand — the same
~126 commands, the same handlers, the same receipts:
help, andhelp <command>for a single Reference entrytoday,queue,brief,standupsearch,decisionsadd,done,edit,project,list— the ordinary task and project verbs- Anything that proposes a change shows its receipt and waits for your answer,
in a terminal (see below)
ask works and runs under a read-only ceiling: it can search your workspace
and the web, and it cannot change anything. If the model proposes a change, you
get the answer it did produce, plus a plain sentence saying what did not happen
and where it can. This is the same enforced ceiling Spotlight runs under — not a
promise in the prompt.
ask honours settings ai-context off the same way the in-app terminal does,
because it runs the same loop: with the setting off it sends no workspace
content, its retrieval tools are not advertised and would refuse anyway, and it
prints "Answering without your workspace — ai-context is off" under the
answer. A generic answer here is always labelled as one.
ask reaches a model through the BEAM API at http://localhost:3001, the
built-in default. There is no CLI-side override in this cut, so ask needsnpm run server running locally. Without it you get a message saying so.
Type exit (or quit) to leave.
Two modes, and why they differ
In a terminal, a proposal is a real conversation. delete all prints its
receipt, and the next line you type is your answer:
beam> delete all
Delete 1 task? This cannot be undone.
Type y to confirm or n to cancel. (yes / no)
beam> y
For the five destructive tools — deleteTask, deleteList, deleteDoc,deleteProject, bulkMutate — a bare y is not enough. The receipt shows
a short code and you type it back (y a3f9). That is deliberate: those are
exactly the operations where a reflexive y is the failure, so they cost a
reply nobody types by accident.
Piped or scripted, there is nobody to ask, so nothing is confirmed:
$ printf 'add buy milk\ndelete all\ny\n' | BEAM_CLI=1 bin/beam --pipe
#1 Added: "buy milk" → Inbox (space: General)
Delete 1 task? This cannot be undone.
Type y to confirm or n to cancel. (yes / no)
That action needs your confirmation, and this run has no way to ask for it. It was NOT run and nothing was changed.
This input is piped, not typed: BEAM cannot show you the receipt and read your answer back, so it treats the approval as not given rather than assuming one. Approve it in BEAM's terminal, or run `bin/beam` from a terminal — an interactive session renders the real confirm and, for the destructive tools, the code you have to type back.
Nothing is waiting on that answer — the proposal above was refused, not armed, so there is no confirmation to give. It was ignored rather than sent to the model as a new question.
$ echo $?
1
A y in a heredoc was written before the proposal existed. Reading it as
consent would mean claiming a person approved something they never saw, so BEAM
does not. This is the same rule its MCP server already follows: a transport that
renders no approval gets no approval.
A piped run exits 1 if it printed an error or refused a proposal, and 0
otherwise — so a script can tell without reading the prose.
Your data
This cut does not sign in. There are two sources, and neither is your cloud
workspace:
- Nothing (the default) — a new empty workspace, held in memory. Commands
really run against it; it is gone when the process exits. --store <path>— a plaintext export file, the kind the in-appexport
command writes ({"version": 2, "exportedAt": …, "data": …}). It is loaded
into memory and never written back. A file that is not a version-2
envelope is refused by name, and a path that cannot be read is an error
rather than a silent empty workspace.
The startup banner says which of the two you are on, every time.
What signing in would take (deliberately not built here): a Supabase auth
session from a device with no browser redirect — a device-code or
paste-a-token flow, since the OAuth redirect this app uses has nowhere to land;
then the encryption unlock, which means prompting for the password without
echo, deriving the content key, pulling the encrypted envelope and decrypting
it in process. The unlock also reaches keyRewrap, which readswindow.localStorage directly — so a signed-in CLI needs a purpose-builtwindow object, and specifically not window = globalThis, which breaks the
store manager's browser check. That is the shape of the work, and it is a
separate change.
What does not work, and why
| Signing in / your real workspace | Not built in this cut. See above. |
Writes from ask |
Refused by the read-only ceiling, on purpose. Ask for the change in the in-app terminal. |
export |
Downloads a file through the browser. It fails here and says so. |
help quickstart, help concepts/… |
Those pages are bundled by the web build. help, help <command> and help reference/<family> all work. |
| Voice | Out of scope for a terminal surface. |
Rich receipts (brief cards, task boards, doc viewers) |
These are drawn by the app. The CLI prints a line naming the receipt rather than guessing at its contents. |
decide and other receipts with their own vocabulary |
They accept y/n here, but not their richer replies (o 0.7, import 1,3). Use the in-app terminal for those. |
council, debate, deep |
Routed normally, and not narrowed to the read-only ceiling. They are not part of this cut's read-only claim. |
What the CLI never claims
Every sentence about an outcome that this program prints came from the engine's
own receipt. The CLI's own text says what did not happen — what it refused,
what it cannot draw, what is held in memory. It does not tell you anything was
saved, synced or connected, because on this surface it has nothing to read that
back from.
That is checked, not promised: a test scans the CLI's source for outcome words
and fails the build if one appears.