Quick start
Wire up a provider and render your first terminal.
Termkit has two seams you fill in: where the byte stream lives (connectionUrl)
and how to control sessions (transport). Everything else is handled for you.
1. Wrap your app in a provider
Define the config once (outside render, or memoised) and pass it to
<TermkitProvider> near your app root.
import { TermkitProvider, type TermkitConfig } from '@mp-lb/termkit';
import { trpc } from './trpc';
const config: TermkitConfig = {
// Where the WebSocket byte stream lives. `params` is forwarded to the daemon
// and drives its working-directory resolution.
connectionUrl: ({ sessionId, params }) =>
`ws://${location.host}/_terminal?` +
new URLSearchParams({ session: sessionId, ...params }),
// The control plane: list live sessions, kill one, clear a bell.
transport: {
list: () => trpc.terminal.list.query(),
kill: (sessionId) => trpc.terminal.kill.mutate({ sessionId }),
seen: (sessionId) => trpc.terminal.seen.mutate({ sessionId }),
},
};
export function App() {
return (
<TermkitProvider config={config}>
<Routes />
</TermkitProvider>
);
}The transport here forwards to a tRPC router, but it's just three
async functions — wire them to whatever backend you have.
2. Render a terminal
import { Terminal } from '@mp-lb/termkit';
export function TerminalPanel() {
return (
<div style={{ height: '100%' }}>
<Terminal session="global" autoFocus />
</div>
);
}The session id is stable and yours to choose. Render the same id again — even
after a reload — and termkit reattaches to the same live shell instead of spawning
a new one.
Give the terminal a sized container; it fills 100% of width and height and refits on resize.
3. Run the daemon
The browser connects to a daemon that owns the PTYs. The simplest option:
pnpm add -D @mp-lb/termkit-server
TERMKIT_PORT=5179 pnpm termkit-daemonPoint connectionUrl at it (directly, or through your dev server's proxy). For
working-directory resolution, embedding the daemon, and production setups, see
Server & daemon.