Subpaths

Import only what you need. Peer dependencies are optional at the package level — install peers for the subpaths you use.

imports
import { createPTYSessionManager }     from '@vibecook/avocado-sdk';
import { LocalPTYSession }             from '@vibecook/avocado-sdk/node-pty';
import { createUDSServer }             from '@vibecook/avocado-sdk/transport-ipc';
import { PTYMeshBridge }               from '@vibecook/avocado-sdk/transport-truffle';
import { AvocadoProvider, TerminalSurface } from '@vibecook/avocado-sdk/react';

Core @vibecook/avocado-sdk

Flagship entry — re-exports the core session-management surface and shared types. No required peer dependencies.

PTYSessionManager

Owns a set of IPTYSessions and registered IPTYTransports. Routes I/O by session source so local, IPC, and remote sessions share one API.

function createPTYSessionManager()

Factory for a new session manager instance.

class PTYSessionManager

Register sessions, attach transports, subscribe to lifecycle events (sessionDiscovered, sessionLost, output, exit, resized, focusChanged).

TerminalService

Tracks virtual terminals attached to sessions and enforces one-active-terminal-per-session. Multiple passive viewers can share a shell; only one accepts input.

function createTerminalService(sessionManager)

Create a terminal service bound to a session manager.

class TerminalServiceImpl

Implementation of the TerminalService interface — create/list terminals, set active, resize, mode switching.

Terminal store sync

function createTerminalStoreSync(…)

Sync virtual-terminal layout state across processes or devices via a store backend.

class TerminalStoreSyncImpl

Implements ITerminalStoreSync — terminal entries, modes, and change events.

core imports
import {
  createPTYSessionManager,
  PTYSessionManager,
  createTerminalService,
  TerminalServiceImpl,
  createTerminalStoreSync,
  getOriginalId,
  WS_PTY_MESSAGE_TYPES,
} from '@vibecook/avocado-sdk';

Types @vibecook/avocado-sdk/types

Shared types, interfaces, and protocol constants. Largely a type surface with a few runtime utilities (IDs, buffers, message guards).

Core interfaces

interface IPTYSession

Running PTY contract: id, dimensions, write/resize/kill, and events — output, resized, exit, focusChanged. Implemented by local, IPC, and proxy sessions.

interface IPTYTransport

Symmetric viewer/owner contract. Viewer: sendInput, sendResize, sendKill, sendFocus, subscribe. Owner: sendOutput, sendResized, sendSessionEnded, sendFocusChanged.

class BasePTYSession

Abstract base with shared lifecycle and event-emitter logic. Constants: DEFAULT_COLS, DEFAULT_ROWS.

interface TerminalBackend

Abstract host API for the React layer — Electron IPC, WebSocket, or any shim that surfaces sessions and terminals to the renderer.

Session & transport types

TypeNotes
SessionSource'local' | 'ipc' | 'ws'
TransportTypeTransport discriminator
SessionMetadataBase / IPC / WS metadata union
PTYSessionStateLifecycle state of a session
RemoteSessionAnnounceShape published in mesh sync store
ConnectionState / HandshakeStateIPC / wire connection phases

Virtual terminal types

TypeNotes
TerminalInfoUI terminal record
TerminalModeActive vs passive input mode
TerminalTypeTerminal kind / renderer class
CreateSessionOptions / CreateTerminalOptionsCreation option bags

Protocol

const WS_PTY_MESSAGE_TYPES

Wire-format discriminators shared across IPC and mesh transports (pty:input, pty:output, pty:resize, …). Guards: isWSPTYMessageType, isWSOutputPayload, isWSInputPayload, etc.

Utilities

ExportRole
generateSessionId / generateLocalSessionId / generateCliSessionIdID helpers
createNamespacedId / parseNamespacedId / getOriginalIdNamespaced session IDs
getSocketDir / getSocketPathDefault IPC socket paths
CircularOutputBuffer / createOutputBufferOutput buffering when disconnected

Node PTY …/node-pty

Local PTY source. Peer dependency: node-pty.

class LocalPTYSession

Implements IPTYSession. Emits output, exit, resized into the session manager. Spawn via static LocalPTYSession.spawn(spawnFn, spawnConfig, options).

TypeNotes
PTYSpawnFunctionAdapter over node-pty’s spawn
PTYSpawnConfigcommand, args, cwd, cols, rows, env, …
LocalPTYSessionOptionsMetadata passed into the session
IPtyMinimal node-pty process surface
spawn local shell
import { LocalPTYSession, type PTYSpawnFunction } from '@vibecook/avocado-sdk/node-pty';
import { spawn as ptySpawn } from 'node-pty';

const spawnFn: PTYSpawnFunction = (opts) =>
  ptySpawn(opts.command, opts.args, opts);

const session = LocalPTYSession.spawn(
  spawnFn,
  {
    command: '/bin/bash',
    args: [],
    cwd: process.cwd(),
    cols: 120,
    rows: 32,
  },
  { command: 'bash', cwd: process.cwd() }
);
sessionManager.registerSession(session);

Transport IPC …/transport-ipc

Unix Domain Socket (macOS/Linux) or Named Pipe (Windows). Bridges a CLI process to a long-running host so sessions appear with source: 'ipc'. No peer deps — uses built-in Node networking. Messages are msgpack-encoded with the same WS_PTY_MESSAGE_TYPES discriminators as the mesh.

Exports

function createUDSServer() · UDSServer

Listen on a socket path; accept CLI connections and dispatch namespaced handlers.

function createPTYIPCBridge(sessionManager) · PTYIPCBridgeImpl

Wire the UDS server into the session manager — announce sessions, multiplex I/O, handle connection lifecycle.

class IPCPTYTransport · createIPCPTYTransport

Per-connection IPTYTransport over the message bus.

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

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

const bridge = createPTYIPCBridge(sessionManager);
bridge.initialize(udsServer);

// Windows named pipe example:
// socketPath: '\\\\.\\pipe\\my-app'
Wire format. Length-prefixed MessagePack. Same message type vocabulary as mesh, so the session-management layer does not change between transports.

Transport Truffle …/transport-truffle

Cross-device terminal session sync over Tailscale via @vibecook/truffle. Peer: @vibecook/truffle@^0.6.0 (Peer-first API, RFC 022). Live routing uses Peer handles; durable ULIDs are for SyncedStore discovery only.

Architecture

┌─────────────────────────┐ RemoteSessionService │ orchestrator └──────────┬──────────────┘ ┌─────────────────┼─────────────────┐ │ │ │ ┌────▼────────┐ ┌──────▼──────┐ ┌────────▼────────┐ │PTYMeshBridge│ │PTYSyncStore │ │ RelaySessionMgr │ └─────┬───────┘ └─────┬───────┘ └─────────────────┘ │ │ │ Peer handles │ SyncedStore (ULID slices) ┌─────▼───────────────────────────────────┐ │ @vibecook/truffle ≥ 0.6 │ │ MeshNode + Peer + SyncedStore │ └─────────────────────────────────────────┘

Key exports

class MeshPTYTransport · createMeshPTYTransport

One per peer; holds a Peer handle; implements IPTYTransport. Namespace: PTY_NAMESPACE.

class PTYMeshBridge · createPTYMeshBridge

Transport lifecycle for online peers (not gated on wsConnected). Primary key peer.ref; secondary index by durable ULID for store reconciliation.

class PTYSyncStore · createPTYSyncStore

Session discovery via truffle’s SyncedStore — per-device slices of { sessions, updatedAt }, keyed by durable ULID. Default store id: DEFAULT_PTY_STORE_ID.

class RelaySessionManager · RelayPTYSession

Owner-side forwarders that copy output / resized / exit from a local session to a viewer’s transport.

class RemoteSessionService · createRemoteSessionService

Orchestrator: ties bridge + sync store + relays together and dispatches owner-side PTY commands. Optional IPeerNotifier for UI side-channels.

minimal mesh wiring
import { createMeshNode } from '@vibecook/truffle';
import { createPTYSessionManager } from '@vibecook/avocado-sdk';
import {
  PTYMeshBridge,
  PTYSyncStore,
  RemoteSessionService,
  type IPeerNotifier,
} from '@vibecook/avocado-sdk/transport-truffle';

const node = await createMeshNode({
  appId: 'my-app',
  deviceName: 'my-device',
  onAuthRequired: (url) => { /* surface to UI */ },
});

const sessionManager = createPTYSessionManager();
const bridge = new PTYMeshBridge({ node, sessionManager });
const syncStore = new PTYSyncStore({ node });

const notifier: IPeerNotifier = {
  sessionFocusChanged: () => {},
  remoteSessionsChanged: () => {},
};

const service = new RemoteSessionService({
  node, sessionManager, bridge, syncStore, notifier,
});

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

Wire format

Every PTY message on the mesh shares:

mesh message shape
{
  type: 'pty:input' | 'pty:output' | 'pty:resize' | …,
  sessionId: string,
  // type-specific fields
}

JSON-serialized to a Buffer, sent via peer.send('pty', buf). On receive, truffle decodes at the NAPI boundary — msg.payload is already a plain object; msg.from is an interned Peer (WhoIs-verified Tailscale attribution).

Out of scope for v0.1. Remote session spawning (CREATE_SESSIONCREATE_FAILED). Primary device election / focus conflict resolution (cooperative last-write-wins).

React …/react

Components, hooks, and pluggable terminal engines. Peers: react, xterm, @xterm/addon-fit. Optional Ghostty VT path: restty.

Context

component <AvocadoProvider backend={…}>

Root provider. Pass a TerminalBackend that exposes the host process’s session/terminal APIs (Electron IPC, custom WebSocket, etc.). Hook: useAvocadoBackend().

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>
  );
}

Components

component <TerminalSurface>

Headless interactive terminal bound to a PTY session. Owns behavior only — engine lifecycle, PTY I/O, auto-fit, click-to-focus, active/passive resize gating. Zero visual opinion: style from your own CSS via data attributes ([data-active], [data-ready], [data-engine]). renderError overrides error UI, actionsRef exposes imperative focus/fit/write, children render as overlay chrome.

component <VirtualTerminal> deprecated

Legacy styled wrapper over TerminalSurface (baked-in dark chrome). Prefer TerminalSurface — styled demo UI (cards, grids) lives in apps/playground and apps/ghostty as copyable recipes.

type TerminalView · TerminalEngineId

Engine-neutral view contract. Engines: 'xterm' (default) and 'restty' (libghostty-vt). Factory: createTerminalView(engine, options). Wire via <TerminalSurface engine="restty" /> or useTerminalCore({ engine }).

Hooks

HookReturns
usePTYSessions()Live session list; optional SessionSourceFilter
useTerminals()Virtual terminal state
useTerminalGrid(options?)Headless grid selection/settings; pluggable layout fn, maxTerminals (default defaultGridLayout, 9)
useResizeHandle(options)Drag-to-resize prop getters (getHandleProps('e'|'s'|'se')) with pointer capture + min-size clamping
useTerminalSnapshot(options)Polled screen lines + cursor of a backend headless terminal
useTerminalAPI()Imperative terminal operations
useRemoteSessions()Mesh remote session offers
useTerminalCore()Low-level core state/actions for custom renderers

View / core types

TerminalEngineId, TerminalView, TerminalViewCreateOptions, TerminalViewFactory, TerminalCoreState, TerminalCoreActions, GridLayout, ResizeHandleDirection, TerminalSnapshot.

CLI @vibecook/avocado

General-purpose terminal session wrapper. Syncs to a host (e.g. the playground) over UDS / Named Pipe and can participate in mesh when the host has truffle enabled.

install
npm i -g @vibecook/avocado
avo --help

Usage

avo
avo                          # wrap $SHELL
avo claude                   # wrap claude CLI
avo -- htop                  # -- separates avo flags from command
avo --no-sync bash           # run without host connection
avo -s /path/to/sock bash    # custom socket path

On startup, avo prints a TUI banner with the wrapped command, session id, and sync status. Type exit or press Ctrl+D to leave.

How it works

PieceRole
PTYHostSpawns the command in a pseudo-terminal via node-pty
RouterMultiplexes I/O between PTY, stdin/stdout, and sync
SyncClientConnects to host: handshake, heartbeat, output buffer (up to 1MB when disconnected)
RealTerminalActive/passive mode for focus management
Standalone-friendly. If the host isn’t running, avo silently retries in the background and buffers output until a connection is established.
Stabilizing surface. Run avo --help for the authoritative flag list — the CLI is still evolving in pre-alpha.