Simple network programming on top of Tailscale.
Truffle gives your apps familiar networking APIs — TCP, HTTP, UDP, WebSocket, QUIC — that run across devices on the same tailnet. Discover peers, open sockets, send messages, sync state, transfer files, and publish services. No central server. No new protocol to learn.
import { createMeshNode } from '@vibecook/truffle';
const mesh = await createMeshNode({
appId: 'field-tools',
deviceName: 'alice-laptop',
});
const bob = await mesh.peer('bob-laptop', { waitMs: 5000 });
if (bob) await bob.sendJson('chat', { text: 'hello' });
// Node-shaped transports over the private mesh
const server = mesh.net.createServer((socket) => socket.pipe(socket));
server.listen(7000);| Layer | Surface |
|---|---|
| Transports | mesh.net · mesh.http · mesh.dgram · mesh.ws · mesh.quic |
| Peers | Interned Peer handles — no id-string soup for dial/send |
| App APIs | Namespaced messaging, SHA-256 file transfer, device-owned synced stores |
| Publish | mesh.serve and mesh.http.createServer for HTTP on the tailnet |
| CLI / TUI | Interactive mesh view, truffle cp, JSONL for agents |
Tailscale owns tunnels, identity, ACLs, and crypto. Truffle owns discovery-aware peers, app isolation (appId), and the APIs your code calls.
Requires Tailscale installed and authenticated on the devices you want to connect.
pnpm add @vibecook/truffle
# optional — only if you use mesh.ws
pnpm add wsNode.js 18+. The package includes a prebuilt native addon and platform sidecar.
# macOS / Linux
curl -fsSL https://vibecook-dev.github.io/truffle/install.sh | sh
# Windows (PowerShell)
iwr -useb https://vibecook-dev.github.io/truffle/install.ps1 | iexSupports macOS (arm64/x64), Linux (x64/arm64), and Windows (x64).
import { createMeshNode } from '@vibecook/truffle';
const mesh = await createMeshNode({
appId: 'notes',
deviceName: 'alice-laptop',
onAuthRequired: (url) => console.log('Auth URL:', url),
});
// Discovery yields Peer handles (same object from events / messages)
for (const p of await mesh.getPeers()) {
console.log(p.displayName, p.online ? 'online' : 'offline');
}
mesh.onMessage('chat', async (msg) => {
if (typeof msg.from === 'string') return;
console.log(msg.from.displayName, msg.payload);
await msg.from.sendJson('chat', { text: 'ack' });
});
// Serve a local process or SPA to the whole tailnet
const h = await mesh.serve({
port: 443,
target: 'http://localhost:3000',
});
console.log(h.url); // https://….ts.nettruffle # interactive TUI
truffle up # start the background daemon
truffle ls # list peers
truffle send server "hello" # send a message
truffle cp report.pdf server:/tmp/ # send a file
truffle serve http://localhost:3000 --port 443 # publish a local service
truffle watch --json # stream mesh events as JSONL- Start a node —
createMeshNode({ appId })boots the native core + tsnet sidecar in-process. - Discover peers — Tailscale status is the source of truth; you get interned
Peerobjects. - Talk on purpose — messages, sockets, files, and HTTP are addressed with a Peer (or a query that resolves to one).
- Ship features — chat, agents, shared state, local dashboards — without operating a broker.
appId is the isolation boundary: devices with different app IDs do not see each other as Truffle peers, even on the same tailnet.
| Package | Role |
|---|---|
@vibecook/truffle |
Primary JS API (createMeshNode, transports, files, stores, serve) |
@vibecook/truffle-react |
React hooks (useMesh, useAuth, useSyncedStore) |
@vibecook/truffle-native |
NAPI-RS native addon |
truffle CLI |
Interactive CLI + TUI (truffle serve, cp, agents JSONL, …) |
truffle-core |
Rust core (network → transport → session → envelope → Node) |
truffle-tauri-plugin |
Tauri v2 desktop plugin |
sidecar-slim |
Go sidecar embedding tsnet |
| Truffle Swift | Apple-native SPM package (RFC 024) — wire-compatible mesh core + SwiftUI helpers |
Messaging prefers explicit wire types: peer.sendJson / peer.sendBytes (and mesh.broadcastJson / broadcastBytes) over the legacy content-sniffing send / broadcast.
See examples/ for runnable demos:
- Discovery, chat, shared state
- Netcat-style TCP, QUIC streams
- Express over the mesh, static SPA + API, expose a local dev server
- WebSocket chat over mesh
Swift: apple/Examples/MeshChatDemo — iOS SwiftUI chat over an in-process demo mesh (loopback backend today; live TailscaleKit backend still pending).
- Guide: vibecook-dev.github.io/truffle
- API reference: api.html
- HTTP serving guide: docs/guide/serving-http.md
- API stability: docs/API-STABILITY.md
- Testing: docs/TESTING.md
- Releasing: docs/RELEASING.md
- RFCs: docs/rfcs/ · Swift: RFC 024
pnpm install
cargo build --locked --workspace --exclude truffle-tauri-plugin --exclude truffle-napi
cargo test --locked --workspace --exclude truffle-tauri-plugin --exclude truffle-napi
pnpm run build
pnpm run testSwift package (macOS / Xcode toolchain):
cd apple && DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer swift testEnable the pre-commit hook once per clone:
git config core.hooksPath .githooks