tail.surf (tsf)
tail.surf is a streaming gist for live work and agent conversations.
Each stream gets a stable URL that can be concurrently written to, read from anywhere, and tailed in real-time.
Free to start with no sign-up required.
Use it to stream sandbox output, build output, deploy logs, sandbox sessions, or agent-to-agent messages. For example,
- Share long-running command output without keeping an SSH session, sandbox, or terminal attached.
- Give agents a reliable async channel with durable catch-up.
- Turn build, test, deploy, and debugging output into a permalink that can be inspected in real-time or after the fact.
tsf has an API and 2 first-class clients:
- Full-featured CLI
- Gist-style scrubbable live transcript with web UI
Development
This repo is a Rust workspace with two crates:
tailsurf: SDK/common crate with shared API types, stream URL parsing, permissions, IDs, and binary frame encoding.tailsurf-cli: CLI shell for stream workflows and URL validation. Its binary is namedtsf.
Language-neutral TSF v1 frame vectors live in tailsurf/fixtures/v1.json. They are packaged with the SDK and exercised by both the Rust and TypeScript implementations.
Install
From this workspace:
For local development against a running API:
TSF_API_URL is the API origin. The SDK appends the versioned /api/v1 namespace.
SDK readers and producers retry bounded transient WebSocket interruptions, including service shutdown and restart closes, while preserving read positions and unacknowledged writer sequence numbers. Protocol and policy closes such as 1002 and 1008 fail immediately instead of reconnecting with a request that cannot succeed.
SDK quickstart
The create_write_read_delete example creates a private stream, writes one durable record through the reconnecting producer, reads it back, and deletes the stream with its owner token:
Set TSF_API_URL to run the same example against a local or staging API. Applications normally use TsfProducer and TsfReadSession; TsfAppendSession is the lower-level frame/ack API.
CLI quickstart
Create a private stream:
Create a public stream:
tsf new prints the stream retention in seconds along with the generated URLs.
Stream command output into a new URL:
|
Run a command through tsf when you want tsf to propagate the command exit status:
By default, tsf write makes line boundaries transcript record boundaries and marks records as transcript-oriented:
|
Use raw mode when you want to send stdin as byte records instead of line-framed transcript records. Raw mode flushes at the physical record size limit, after a short linger, and at EOF:
|
Tail or replay a URL:
Owner URLs contain #o= and can manage the stream:
Run the checks:
RUSTDOCFLAGS='-D warnings'
Package and publish order:
# Wait for tailsurf 0.1.0 to appear in the crates.io index before packaging/publishing the CLI.
Use cargo package --workspace as the clean-tree preflight because it verifies both crates from the workspace source. After publishing tailsurf, wait for tailsurf 0.1.0 to appear in the crates.io index before packaging or publishing tailsurf-cli by itself, because the packaged CLI resolves its SDK dependency from crates.io rather than the local workspace path.
After tailsurf-cli is visible in the crates.io index, run the install smoke against staging:
TSF_CLI_VERSION=0.1.0 TSF_API_URL=https://staging.tail.surf TSF_WEB_URL=https://staging.tail.surf
Try the CLI URL parser:
License
The Rust SDK and CLI are MIT licensed. See LICENSE.