Pre-alpha · TypeScript · MIT

Terminal session sync
for the web stack

One session model. Pluggable transports. Factor out the plumbing every terminal UI rebuilds — and ship local PTY, IPC, and Tailscale mesh when you need them.

pnpm add @vibecook/avocado-sdk

Stop re-implementing terminal plumbing

Every terminal UI project ends up reinventing PTY spawning, output buffers, resize coordination, focus handoff, and multi-device sync. Avocado owns that layer.

One session model

A single IPTYSession + PTYSessionManager. Local PTYs, CLI-over-IPC, and remote peer sessions all look identical to your UI layer.

Pluggable transports

Ship only what you need — node-pty for local shells, UDS/Named Pipe for CLI bridges, Tailscale mesh via @vibecook/truffle for cross-device sharing.

Pluggable terminal engines

Drop-in React components and hooks. Default xterm.js engine, optional restty (libghostty-vt + WebGPU/WebGL2) via engine="restty".

Typed end-to-end

Strict TypeScript, source maps, and a small public surface — IPTYTransport, IPTYSession, ITerminalStoreSync. Optional peers only for the subpaths you import.

Subpath exports, clean boundaries

The SDK is carved into focused modules. Core stays free of heavy peers; transports and React opt in only when you need them.

@vibecook/avocado-sdk
Flagship entry — PTYSessionManager, TerminalService, shared types
/types
Interfaces & protocol constants — IPTYSession, IPTYTransport, wire types
/node-pty
Spawn local PTYs · peer: node-pty
/transport-ipc
Unix Domain Socket / Named Pipe · CLI ↔ host bridge
/transport-truffle
Tailscale mesh sync · peer: @vibecook/truffle ≥ 0.6
/react
Provider, grid, hooks, xterm + optional restty engines
Uniform abstraction. All transports implement IPTYTransport. The session manager routes by session source — local, IPC, and remote peers feel identical above that boundary.

Sessions, transports, terminals

Three layers, one mental model. Understand these and the rest of the API falls into place.

01

IPTYSession

A running PTY with id, dimensions, output stream, and lifecycle events — output, resized, exit, focusChanged. Every backend (local, IPC, proxy) implements the same interface.

02

PTYSessionManager + IPTYTransport

The manager owns sessions and registered transports. Each session has a source'local', 'ipc', or 'ws'. Transports implement a symmetric viewer/owner contract: input, resize, kill, focus, subscribe — plus owner-side output and lifecycle.

03

TerminalService

Tracks virtual terminals (UI surfaces attached to a session) and enforces one-active-terminal-per-session. Multiple passive viewers can share a shell; only one accepts input. Powers multi-window and multi-tab layouts.

Two packages, optional peers

Install the SDK for embedding, the CLI for shell wrapping — and only the peer deps for the subpaths you import.

@vibecook/avocado-sdk

The library. Session manager, transports, and React surface via subpath exports.

pnpm add @vibecook/avocado-sdk
@vibecook/avocado

CLI binary avo — wrap any command and sync to a host over IPC.

npm i -g @vibecook/avocado

Peer dependencies by subpath

Subpath Required peer(s)
@vibecook/avocado-sdk None — core session manager + types only
…/node-pty node-pty
…/transport-ipc None — built-in Node networking
…/transport-truffle @vibecook/truffle ^0.6
…/react react, xterm, @xterm/addon-fit · optional restty for Ghostty VT
example · local PTY + mesh
# Server-side app using local PTY + mesh sync
pnpm add @vibecook/avocado-sdk node-pty @vibecook/truffle

Runtimes: Node.js ≥ 18 · modern browsers (React subpath) · Electron (playground is Electron).

Local PTY → React grid in three steps

Spawn a session on the host, expose a backend, render with the grid. Mesh is one more block of wiring.

1

Host — spawn & register

Electron main or Node process: create the session manager, spawn a local PTY, register it.

main.ts
import {
  createPTYSessionManager,
  createTerminalService,
} from '@vibecook/avocado-sdk';
import { LocalPTYSession } from '@vibecook/avocado-sdk/node-pty';
import { spawn as ptySpawn } from 'node-pty';

const sessionManager = createPTYSessionManager();
const terminalService = createTerminalService(sessionManager);

const session = LocalPTYSession.spawn(
  (opts) => ptySpawn(opts.command, opts.args, opts),
  {
    command: process.env.SHELL ?? '/bin/bash',
    args: [],
    cwd: process.cwd(),
    cols: 120,
    rows: 32,
  },
  { command: 'bash', cwd: process.cwd() }
);
sessionManager.registerSession(session);
2

Renderer — provider + headless surface

Wrap your tree with AvocadoProvider and drop in a terminal surface — behavior from the SDK, every pixel of chrome yours.

App.tsx
import { AvocadoProvider, TerminalSurface } from '@vibecook/avocado-sdk/react';

export function App() {
  return (
    <AvocadoProvider backend={electronBackend}>
      <TerminalSurface sessionId={sid} terminalId={tid} engine="restty" isActive />
    </AvocadoProvider>
  );
}
3

Optional — mesh across devices

Add the truffle transport and the same session appears on every peer.

mesh.ts
import { createMeshNode } from '@vibecook/truffle';
import {
  PTYMeshBridge,
  PTYSyncStore,
  RemoteSessionService,
} from '@vibecook/avocado-sdk/transport-truffle';

const node = await createMeshNode({
  appId: 'my-app',
  deviceName: 'laptop',
});
const bridge = new PTYMeshBridge({ node, sessionManager });
const syncStore = new PTYSyncStore({ node });
const service = new RemoteSessionService({
  node, sessionManager, bridge, syncStore,
});

await bridge.initialize();
await service.enable();

Pick the path your app needs

Same session manager, different wires. Compose freely — local + IPC + mesh is a common Electron layout.

Live

Local PTY

Spawn shells in-process with LocalPTYSession. Ideal for Electron main and Node servers.

@vibecook/avocado-sdk/node-pty
Live

IPC

Unix Domain Socket (macOS/Linux) or Named Pipe (Windows). Bridge avo CLI sessions into a long-running host.

@vibecook/avocado-sdk/transport-ipc
Live

Mesh (Truffle)

Cross-device session sync over Tailscale. Peer-first routing, SyncedStore discovery, focus handoff.

@vibecook/avocado-sdk/transport-truffle
IPC host
import {
  createUDSServer,
  createPTYIPCBridge,
} from '@vibecook/avocado-sdk/transport-ipc';

const udsServer = createUDSServer();
udsServer.start({ socketPath: '/tmp/my-app.sock' });

const bridge = createPTYIPCBridge(sessionManager);
bridge.initialize(udsServer);
CLI
# Wrap your shell / any command
avo
avo claude
avo -- htop
avo -s /tmp/my-app.sock bash

# Standalone: buffers until host connects
avo --no-sync bash

Components that ship the hard parts

Provider wiring, grid layout, active-terminal switching, and two renderers — ready for production UIs.

Export Role
AvocadoProvider Root context — pass a TerminalBackend (Electron IPC, WebSocket, …)
TerminalSurface Headless terminal surface — behavior only, styled via [data-active]/[data-engine] attributes
useTerminalGrid / useResizeHandle Headless grid state (pluggable layout) and drag-to-resize prop getters
engine: 'xterm' Default TerminalView engine (xterm.js + FitAddon)
engine: 'restty' Optional Ghostty VT path via restty peer (WASM + WebGPU/WebGL2)
usePTYSessions, useTerminals, useTerminalSnapshot, … Live session & terminal state hooks

Full signatures and types → API reference · React

What ships in this repo

Path What it is
packages/sdk/ Source for @vibecook/avocado-sdk
packages/cli/ Source for @vibecook/avocado (avo)
apps/playground/ Electron showcase — local + IPC + mesh in one app
docs/ This site — static HTML on GitHub Pages
development
pnpm install
pnpm run typecheck
pnpm run build
pnpm run playground   # Electron demo

Build terminal UIs without the boilerplate

Open the API reference, clone the playground, or drop the SDK into your next Electron or Node app.