The connection
The conn object passed to every handler, covering id, terminal size, write, close, and key encoding
Every handler receives a conn: a single connected terminal session. It is how you read the terminal's size, write to the screen, and end the session. All of your app's users are multiplexed into one app instance, so each user gets their own conn.
interface Conn {
readonly id: string;
readonly cols: number;
readonly rows: number;
write(data: string): void;
close(): void;
}conn.id
readonly id: stringA unique identifier for this session. Use it as the key for per-connection state in a Map.
sessions.set(conn.id, { score: 0 });conn.cols and conn.rows
readonly cols: number // terminal width (default 80)
readonly rows: number // terminal height (default 24)The current terminal size in character cells. If the size is unknown they default to 80 by 24. They are updated automatically before onResize fires, so reading them in any handler always gives the live size.
const rule = '-'.repeat(conn.cols);conn.write(data)
write(data: string): voidWrites a string straight to the terminal. There is no buffering and no implicit newline. Non-string values are coerced with String().
conn.write('\x1b[2J\x1b[H'); // clear screen, cursor home
conn.write('\x1b[1;32mready\x1b[0m\r\n'); // bold green "ready", then a newline
conn.write('size: ' + conn.cols + 'x' + conn.rows + '\r\n');Use \r\n, not \n
write sends bytes verbatim, so you control every character including line endings. Use "\r\n" to start a new line, not "\n". A bare \n moves the cursor down but does not return it to column 0, so successive lines stair-step to the right. The \r (carriage return) is what returns the cursor to the start of the line.
Forgetting the \r is the most common terminal-output bug. If your output drifts diagonally across the screen, you are missing carriage returns.
conn.close()
close(): voidEnds the SSH session. Your onClose handler still fires afterwards, so closing is a good place to request teardown and let onClose do the cleanup.
.onKey((conn, data) => {
if (data === 'q' || data === '\x03') return conn.close();
})Key encoding
The data string passed to onKey is the decoded input. It is usually a single character, but special keys arrive as multi-character escape sequences and control keys as control codes.
| Input | data |
|---|---|
| An ordinary key | the character, for example "q" |
| Enter | "\r" |
| Ctrl-C | "\x03" |
| Ctrl-D | "\x04" |
| Up arrow | "\x1b[A" |
Escape sequences arrive whole, so you can match them with a single comparison. For richer decoding (named keys, modifiers), use the UI layer's useInput.
Terminal output notes
The sandbox is pure JavaScript with no DOM and no global fetch. A few things to keep in mind when writing to the terminal:
- Include ANSI escapes yourself for color and cursor control. For example,
"\x1b[2J\x1b[H"clears the screen and homes the cursor. console.loggoes to the server logs, not the user's terminal. Useconn.writefor anything the user should see, andconsole.logfor your own debugging.- There is no built-in network. Use the SDK
fetchfor HTTP requests; the globalfetchdoes not exist in the sandbox.
If you would rather build with components than hand-write escape sequences, use the React UI layer.
A handy ANSI cheat sheet
| Sequence | Effect |
|---|---|
\x1b[2J | Clear the entire screen |
\x1b[H | Move cursor to top-left (home) |
\x1b[<row>;<col>H | Move cursor to a position (1-based) |
\x1b[K | Clear from the cursor to end of line |
\x1b[?25l / \x1b[?25h | Hide / show the cursor |
\x1b[1m ... \x1b[0m | Bold ... reset all attributes |
\x1b[3<n>m | Foreground color (30 to 37) |
\x1b[38;2;<r>;<g>;<b>m | 24-bit truecolor foreground |