portable_pty

CI pub package License: MIT

Cross-platform pseudo-terminal (PTY) for Dart. Spawn shell subprocesses on Linux, macOS, and Windows with full terminal I/O — or connect to a remote PTY server on the web via WebSocket / WebTransport.

Features

  • Native PTYforkpty / openpty on Unix, ConPTY on Windows, via a Rust native library (portable-pty).
  • Web transport — plug in WebSocket, WebTransport, or any custom PortablePtyTransport to talk to a server-side PTY from the browser.
  • Unified API — same PortablePty / PortablePtyController interface regardless of platform.
  • Synchronous read/write for low-latency integrations.
  • Window resize, mode queries, and explicit lifecycle management.

Platform support

Platform Backend Build toolchain
Linux Native PTY (Rust) Rust (or prebuilt)
macOS Native PTY (Rust) Rust (or prebuilt)
Windows ConPTY (Rust) Rust (or prebuilt)
Android Native PTY (Rust) Rust + cross (or prebuilt)
iOS Static lib (Rust) Rust (or prebuilt)
Web WebSocket / WebTransport None (pure Dart)

Installation

dependencies:
  portable_pty: ^0.0.2

The Rust native library is compiled automatically by a Dart build hook using native_toolchain_rust. You need Rust ≥ 1.92 installed.

Tip: If you don't want to install Rust, download a prebuilt library instead.

Quick start

import 'package:portable_pty/portable_pty.dart';

void main() {
  final pty = PortablePty.open(rows: 24, cols: 80);
  pty.spawn('/bin/sh', args: ['-c', 'echo hello from portable_pty']);

  final out = pty.readSync(4096);
  print(String.fromCharCodes(out));

  print('pid: ${pty.childPid}');
  print('mode: ${pty.getMode()}');

  pty.close();
}

API overview

Opening a PTY

final pty = PortablePty.open(
  rows: 24,
  cols: 80,
  // Web-only options:
  webSocketUrl: 'ws://localhost:8080/pty',
  // webTransportUrl: 'https://localhost:4433/pty',
  // transport: MyCustomTransport(),
);

Spawning a process

pty.spawn('/bin/bash', args: ['-l']);
pty.spawn('cmd.exe');  // Windows

Reading & writing

// Synchronous
final bytes = pty.readSync(4096);
pty.writeString('ls -la\n');
pty.writeBytes(Uint8List.fromList([0x03]));  // Ctrl+C

// Written byte count
final n = pty.writeString('echo hello\n');
print('wrote $n bytes');

Window resize

pty.resize(rows: 40, cols: 120);

PTY mode

final mode = pty.getMode();
print('canonical: ${mode.canonical}, echo: ${mode.echo}');

Process lifecycle

print('running: ${pty.childPid}');

final exitCode = pty.tryWait();   // non-blocking, returns null if still running
print('exited: $exitCode');

pty.kill();    // SIGKILL
pty.close();   // close file descriptors

Controller (with buffered output)

final controller = PortablePtyController(
  rows: 24,
  cols: 80,
  defaultShell: '/bin/bash',
);

await controller.start();
controller.write('ls\n');
final output = controller.readOutput();
print(output);

await controller.stop();
controller.dispose();

Prebuilt libraries

Prebuilt binaries for every platform are attached to each GitHub release.

The easiest way to get them is the built-in setup command:

dart run portable_pty:setup

This downloads the correct library for your host platform into .prebuilt/<platform>/ at your project root. The build hook will find it automatically — no Rust install required.

You can also specify a release tag or target platform:

dart run portable_pty:setup --tag v0.0.2 --platform macos-arm64

Monorepo users can download all prebuilt libs at once:

dart run tool/prebuilt.dart --tag v0.0.2 --lib pty

You can also set the PORTABLE_PTY_PREBUILT environment variable to point directly at a prebuilt library file.

Tip: Add .prebuilt/ to your .gitignore.

Web usage

Browsers cannot spawn OS processes. On web, portable_pty connects to a remote PTY server over WebSocket or WebTransport.

┌──────────────┐       ws / webtransport       ┌──────────────┐
│  Browser app │  ◄──────────────────────────►  │  PTY server  │
│  (Dart web)  │                                │  (any lang)  │
└──────────────┘                                └──────────────┘
final pty = PortablePty.open(
  rows: 24,
  cols: 80,
  webSocketUrl: 'ws://localhost:8080/pty',
);
pty.spawn('ws://localhost:8080/pty');
pty.writeString('echo hello\n');

Custom transport

Implement PortablePtyTransport for any protocol (SSH, proprietary, etc.):

class MySshTransport implements PortablePtyTransport {
  @override
  void write(Uint8List data) { /* ... */ }

  @override
  Uint8List readSync(int maxBytes) { /* ... */ }

  // ...
}

final pty = PortablePty.open(transport: MySshTransport());
Package Description
portable_pty_flutter Flutter ChangeNotifier controller for PTY sessions
ghostty_vte Terminal VT engine — paste safety, OSC/SGR parsing, key encoding
ghostty_vte_flutter Flutter terminal widgets powered by Ghostty

License

MIT — see LICENSE.

Libraries

portable_pty
Cross-platform pseudo-terminal (PTY) for Dart.
portable_pty_bindings_generated