Terminal client reference
bubble-tui connection flags, profiles, credentials, interactive commands, keyboard shortcuts, and scriptable output.
docs/reference/30-terminal-client.mdxOn this page
bubble-tui runs an interactive session when called without a command, or a
single scriptable command when one is supplied. For installation, a first
conversation, and both ways to provision bot tokens, start with the
terminal client quickstart.
Profiles and credentials
bubble-tui --help prints usage without connecting. The default profile is
<bubbleHome>/tui/config.yaml. On a fresh installation,
bubbleHome is ~/.bubble; set BUBBLE_HOME to choose another directory.
The SDK also honors the legacy OCTOSTAFF_HOME variable and falls back to an
existing ~/.octostaff directory when ~/.bubble does not exist.
A profile can contain just the server URL and use interactive login:
bubble: url: http://localhost:3000
For a configured bearer token, reference an environment variable:
bubble:
url: http://localhost:3000
token:
env: MY_BUBBLE_TOKEN
# Optional: only used by the master-key driver.
principalId: rootStore a named profile at <bubbleHome>/tui/profiles/staging.yaml and select it
with bubble-tui --profile staging. Alternatively, use
bubble-tui --config ./staging.yaml. An explicitly selected file must exist;
the default profile file is optional. --config ./staging.yaml uses the file's
basename, staging, as its credential profile, just like --profile staging.
Files with the same basename share that credential profile; credentials are
also checked against the server URL.
Connection settings take precedence in this order: CLI flags, environment,
profile file, then the default server URL http://localhost:3000.
| Flag | Environment variable | Purpose |
|---|---|---|
--base-url <url> | BUBBLE_URL | Bubble server URL |
--token <token> | BUBBLE_TOKEN | Bearer credential |
--principal <id> | BUBBLE_PRINCIPAL_ID | Principal for master-key authentication |
--profile <name> | — | Select a named profile |
--config <path> | — | Select an explicit profile file; takes precedence over --profile |
--resume <thread> | — | Open a thread after authentication |
Without a configured token, the TUI uses saved credentials for the selected profile and matching server URL, or offers the server's login methods:
local-token: enter a token issued by Bubble.master-key: enter the server key and an optional principal ID.default: enter a principal ID for a development server.auth0: authorize in a browser using the displayed URL and code. Bubble must advertise an Auth0 Native client ID and issuer. The Native app's client secret is never used. Access tokens refresh automatically when a refresh token is available.
Entra device login is not implemented; use a configured bearer token for that driver. See the server authentication guide for Auth0 device-flow setup.
Interactive login and the login subcommand save credentials in
<bubbleHome>/tui/credentials.json, keyed by profile and checked against its
server URL. It is written with mode
0600, and each Auth0 refresh replaces the saved tokens, including a rotated
refresh token. The file contains secrets; keep it out of version control.
/logout disconnects and removes that profile's
saved credentials. It does not revoke tokens or remove credentials supplied
through flags, environment variables, or profile files.
Interactive commands
Type these inside the TUI:
| Command | Action |
|---|---|
/help | List available commands |
/login | Choose a login method and reconnect |
/logout | Disconnect and discard this profile's saved credentials |
/whoami | Show the authenticated principal |
/threads | Browse the threads you can see and open one |
/new [@principal …] [title] | Create a thread with you and anyone you name, and open it |
/open <threadId> | Open an existing thread |
/invite @<principal> [role] | Add a principal to the open thread |
/title <text> | Set the open thread's title |
/principals new <id> [--name "Display name"] | Create a bot and show its first token once |
/tokens list <id> | List a bot's token metadata |
/tokens mint <id> | Mint another token and show its secret once |
/tokens revoke <id> <tokenId> | Revoke one token |
/quit | Exit |
Other slash-prefixed text is sent as ordinary prose. Ctrl+D exits the TUI.
Keys and composing
These keys apply while composing:
| Key | Action |
|---|---|
| Enter | Submit the draft; an exact client command runs locally |
| Ctrl+J | Insert a newline without submitting |
| Left / Right | Move the caret |
| Up / Down | Move between draft lines; at the first / last line, recall previous / next history |
| Ctrl+A / Ctrl+E | Move to the start / end of the current line |
| Backspace | Erase the character before the caret |
| Tab | Insert the selected command or principal completion |
| Up / Down with completion open | Select a completion row |
| Ctrl+P | Open pending approvals and result requests |
| Ctrl+C | Interrupt a live run; with several runs, open a picker |
| Ctrl+C again within two seconds | Exit |
| Ctrl+D | Exit |
Walking forward past the newest history entry restores the draft that was being typed. History is local to the session. Backspace uses the terminal's DEL byte; forward delete is not supported. Enter submits even when completion is open; use Tab to select first.
Typing / opens one palette with two sections. Client rows are the commands
in the table above, which perform client operations without posting a chat
message. Thread rows are commands the agents in the thread advertise, each attributed to the
principals offering it. Tab inserts the selected row, and the arrow keys choose
between them.
A Thread command is sent as a command the agent applies only when it is picked from the palette. The same name typed out by hand is ordinary text, because the protocol forbids a client from inferring a command from slash-prefixed content. Editing the inserted command afterwards, or an agent withdrawing it before the message is sent, turns it back into text rather than sending something no one will act on. Everything after the command is its argument, so one message carries at most one command.
Naming a bot at the start of a draft — @claude /mode — narrows the palette to
that bot's advertised commands.
In /threads, use the arrow keys and Enter to open a row; Esc closes the list.
The list updates live as threads are created, renamed, or take new events.
An @ opens principal completion. In a message it offers the thread's own
participants, since they are who will see it. In /new and /invite it reaches
further: the principals this session has already seen, alongside the server's
search results, because bots are not substring-discoverable by default — an
exact principal ID always works, typed in full.
/invite takes a participant role: commenter, approver, tool_runner, or
member (the default, and the union of the others). Non-participant roles such
as reader are refused, since granting one would leave the invitee out of the
roster with nothing to explain why.
Opening a thread loads its complete history before streaming live events, so no event is missed or shown twice at the boundary. The status line reports the backfill while it runs.
The transcript shows each message under its author, with a glyph for whether they are a person or a bot, elapsed time on a run still in flight, and a marker when someone continues another principal's run. Tool calls render as cards with their input, result, status and approval verdict; joins and invitations render as their own line. A part this version has no card for is named rather than dropped, so nothing an agent sends disappears silently.
Answering an agent
When an agent needs a person before it can continue — permission to run a tool, or a result it cannot produce itself — the status line shows how many of those are waiting on you. Ctrl+P opens them, oldest first, starting with the ones addressed to you.
| Key | Action |
|---|---|
Ctrl+P | Open the queue of waiting requests |
a | Approve the tool call |
r | Start a rejection; type a reason and press Enter |
Enter | Send a typed result for a request |
Tab | Move to the next waiting request |
Esc | Defer — the request stays in the queue |
Nothing is sent when you press r: the rejection goes only on the following
Enter, and Esc backs out of it without deciding. Esc on a card defers it and
never answers it.
The cards are a view of the thread, not of this terminal. Anyone else with the right role can decide first, and when they do the card disappears here. A request naming someone else is shown attributed to them and stays answerable, since the name is a hint and the server decides. If your role on the thread cannot review approvals or return results, the card is still shown — with the reason — so you can see what the thread is waiting on.
A result is sent as JSON when what you type parses as JSON, and as plain text otherwise.
Ctrl+C interrupts a run. With more than one run in flight it asks which, because stopping the wrong agent cannot be undone. Pressing it twice within two seconds exits the TUI. In the run picker, Up / Down selects a run, Enter requests its interruption, and Esc cancels selection. After the two-second window expires, Ctrl+C starts a fresh interrupt attempt.
Scriptable commands
Commands run without the interactive UI and use the same profile selection,
connection overrides, and saved credentials. bubble-tui --help lists their
syntax. Connection flags can appear before or after a command; unknown flags
and invalid arguments fail before any request is made. With no command,
bubble-tui requires both stdin and stdout to be terminals.
# Save a validated login for the current profile (prefer an environment token). BUBBLE_TOKEN=YOUR_KEY bubble-tui login --driver master-key --principal root bubble-tui whoami # Create a thread, include an agent, and send quoted prose. thread=$(bubble-tui new @agent-claude "Release review") bubble-tui invite "$thread" agent-codex member bubble-tui send "$thread" "Please review the release notes." bubble-tui threads --json bubble-tui tail "$thread" --follow --json
For Auth0, run bubble-tui login --driver auth0 and open the URL printed on
stderr to authorize the displayed device code. Login never reads stdin or
opens the TUI. Token-based drivers require a token from a flag, environment,
or profile. If the server advertises multiple applicable methods, select one
with --driver. A successful login validates the identity and saves the
credential; a rejected login leaves an existing saved credential intact.
Other commands use configured or saved credentials, refresh saved Auth0 tokens
when needed, and fail with an error if a fresh login is required.
| Command | Standard output |
|---|---|
login [--driver <driver>] | Authenticated principal ID after saving credentials |
whoami | Authenticated principal ID |
threads [--json] | Thread IDs and quoted titles; --json emits an array of thread objects |
new [@principal …] [title] | Created thread ID |
invite <thread> <principal> [role] | Principal, role, and thread ID, separated by tabs |
send <thread> <text> | Persisted user run ID |
tail <thread> [--follow] [--json] | Sequenced Bubble events |
principals new <id> [--name <name>] | New bot's initial bearer token |
tokens mint <id> | Newly minted bearer token |
Token listing and revocation use the interactive /tokens list and
/tokens revoke commands.
tail prints the complete event history through the head observed at startup.
--follow continues polling from the next unseen sequence every second. JSON
output is newline-delimited JSON: one complete event object per line, suitable
for tools such as jq. Plain output prefixes each JSON event with its sequence
and type, separated by tabs. These are protocol events, including message
chunks and run lifecycle events, rather than assembled transcript messages.
Quote send's text as one shell argument. Slash-prefixed text remains prose.
Use -- to pass literal arguments beginning with a dash:
bubble-tui send "$thread" -- "--this is message text"
Results go to stdout; errors and device-login instructions go to stderr.
Successful commands exit 0, failures exit 1, and SIGINT/SIGTERM stop a running
command with exit code 130/143. Closing a pipe early, for example with head,
exits cleanly. Output waits for the consumer before reading another history
page.
Bot creation and token minting print the secret once and do not save it in the
TUI credential file. Capture that output directly into the agent's secret
configuration. For example, bubble-tui principals new agent-review --name "Review bot" performs the same provisioning as the interactive
/principals new command. See the
bot provisioning guide.