Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5e19739
feat: rename 'at' command to 'go' for session management and update r…
Mar 25, 2026
e197cc4
feat: remove 'commander' dependency and refactor command handling in CLI
Mar 25, 2026
f01f8d6
feat: rename CLI commands for consistency and clarity
Mar 25, 2026
a17e2f3
feat: implement dynamic cursor style handling via DECSCUSR sequences
Mar 25, 2026
50a83ae
feat: update cursor configuration to use cursorStyleBlink instead of …
Mar 25, 2026
adba3b5
Add create-live-spec skill and SPEC template
Mar 26, 2026
42ae47f
feat: add comprehensive guide on using the browser as a computing env…
Mar 26, 2026
bcb952d
feat: enhance browser recommendations and productivity tools in the g…
Mar 26, 2026
9399a83
feat: update terminal tool descriptions for clarity and accuracy
Mar 26, 2026
a76ff38
feat: refine browser guide content for clarity and completeness
Mar 26, 2026
7bb35d0
feat: enhance terminal tool descriptions with links and additional op…
Mar 26, 2026
9c49ab5
feat: update README and awesome-web guide for improved clarity and or…
Mar 26, 2026
a05943b
feat: add help command instructions for bunx and npx usage in README
Mar 26, 2026
3b3b761
feat: update 'go' command behavior to open main session by default
Mar 26, 2026
9e50473
feat: add CI badge and npm version badge to README
Mar 26, 2026
8feb5eb
feat: update CLI commands to replace 'at' with 'go' for session manag…
Mar 26, 2026
abad790
feat: update README for Windows usage and adjust webtty command in pa…
Mar 26, 2026
41f2337
fix: address copilot review comments
Mar 26, 2026
da7dab8
fix: center align social preview image in README
Mar 26, 2026
ca88e28
fix: bundle ghostty-web assets into dist for npx compatibility
Mar 26, 2026
375ecac
fix: update static test to match bundled dist path
Mar 26, 2026
d4686b7
test: restore 100% coverage for static.ts fallback path
Mar 26, 2026
3938b44
fix: ignore unknown DECSCUSR Ps values, fix awesome-web.md typo
Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 webtty contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
<img src="assets/social-preview.png" width="600">
<p align="center">
<img src="docs/assets/social-preview.png" width="600">
</p>

# webtty

Terminal UI in the browser. Run CLI/TUI applications in a browser tab, across platforms.
[![npm version](https://img.shields.io/npm/v/webtty)](https://www.npmjs.com/package/webtty)
[![CI](https://github.com/jesse23/webtty/actions/workflows/ci.yml/badge.svg)](https://github.com/jesse23/webtty/actions/workflows/ci.yml)

Terminal UI in the browser. Run CLI/TUI applications in a browser tab, across platforms. Powered by [ghostty-web](https://github.com/coder/ghostty-web).

- [Why webtty?](docs/awesome-web.md#terminal)

```sh
npx webtty # start server + open a terminal in the browser
npx webtty ls # list sessions
npx webtty help # show all commands
bunx webtty # open main session in the browser
bunx webtty go [id] # open a specific session by id
bunx webtty help # show all commands

# or with npx
npx webtty
npx webtty go [id]
npx webtty help
```

## Debugging
> **Windows**: use `npx` — `bunx` is not supported on Windows because `Bun.spawn({ terminal })` does not implement PTY on Windows yet.

## Development

Build emits source maps (`dist/**/*.js.map`), so you can debug against the built output directly — no minification, original TypeScript line numbers preserved.

```
```sh
bun run build
bun --inspect run dist/server/index.js
# or
Expand Down
3 changes: 0 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions docs/adrs/013.client.cursor-style.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ADR 013: Client — DECSCUSR cursor style via PTY intercept

**SPEC:** [client](../specs/client.md)
**Status:** Accepted
**Date:** 2026-03-25

---

## Context

ghostty-web does not implement DECSCUSR (CSI Ps SP q) — the standard escape sequence for cursor shape and blink control (ECMA-48 / DEC). Applications like vim, neovim, and fish emit DECSCUSR to switch the cursor between bar (insert mode), block (normal mode), and underline, with optional blinking.

The root cause is in ghostty-web's `GhosttyTerminal.getCursor()` (lib/ghostty.ts), which hardcodes `style: 'block'` with a TODO comment rather than reading the value from the WASM render state. The Ghostty WASM binary does process DECSCUSR correctly — `RenderState.Cursor.visual_style` is updated — but the JS wrapper never reads it back and never calls `renderer.setCursorStyle()` based on PTY output.

The consequence: `cursorStyle` in config sets the initial shape at startup, but apps cannot change it at runtime. With `cursorStyle: 'bar'` (the preferred default), vim's normal mode cursor stays a bar instead of switching to block.

## Decision

Intercept DECSCUSR sequences in `src/client/cursor.ts` before passing data to `term.write()`. On each WebSocket message, scan for the pattern `ESC [ Ps SP q`, decode `Ps`, and update `term.options.cursorStyle` and `term.options.cursorBlink` directly. ghostty-web's options proxy forwards these immediately to the renderer via `renderer.setCursorStyle()` and `renderer.setCursorBlink()`.

**DECSCUSR Ps mapping:**

| Ps | Style | Blink |
|----|-------|-------|
| 0 | block | yes (default reset) |
| 1 | block | yes |
| 2 | block | no |
| 3 | underline | yes |
| 4 | underline | no |
| 5 | bar | yes |
| 6 | bar | no |

**Config interaction:** `config.cursorStyle` and `config.cursorStyleBlink` set the initial values at Terminal construction. DECSCUSR overrides them at runtime. The two compose cleanly: config is the default, apps switch dynamically as needed.

The intercept lives in `src/client/cursor.ts`, isolated from the WebSocket and terminal wiring in `index.ts`. It is removed when ghostty-web implements DECSCUSR natively.

## Considered Options

**Option A: Patch ghostty-web**

ghostty-web would need to call `ghostty_render_state_get` with key `cursor_visual_style` (data key 10) after each `write()`, diff the result against the last known style, and call `renderer.setCursorStyle()` on change. This is the correct long-term fix but requires a PR to an external repo and a version bump. The client-side intercept is an equivalent workaround that can be removed once upstream ships it.

**Option B: Parse DECSCUSR in the server WebSocket handler**

Rejected — the server is a dumb pipe. Cursor state is a client rendering concern. Moving it to the server would couple rendering logic to the PTY transport.

**Option C: Leave cursor shape as static config only**

Rejected — vim in normal mode showing a bar cursor is confusing. The shell default and application overrides are a standard terminal UX expectation.

## Consequences

- vim, neovim, and fish normal mode show a block cursor; insert mode shows a bar. Blink state follows the app's preference.
- `config.cursorStyle` still works as the startup default — apps that don't emit DECSCUSR use whatever the user configured.
- The intercept adds one regex scan per WebSocket message. DECSCUSR sequences are rare (only on mode change), so the scan almost always yields zero matches and exits immediately.
- When ghostty-web adds native DECSCUSR support, `cursor.ts` and the `applyDecscusr` call in `index.ts` can be deleted with no other changes.

## Related Decisions

- [ADR 010 — Client UX polish](010.client.ux-polish.md): established the WebSocket message handling in `index.ts` that this intercept hooks into
- [ADR 008 — Config](008.webtty.config.md): established `cursorBlink` as a config key; `cursorStyle` and `cursorStyleBlink` are added alongside it
File renamed without changes
File renamed without changes
139 changes: 139 additions & 0 deletions docs/awesome-web.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Awesome Web

A personal guide to living in the browser.

## Why the Browser

The browser is where you already spend your time. One window, sync across devices, no install friction. The web platform caught up — most apps you need run well in it now.

**The principle**: if a web version exists and it's good enough, use it. Not because native is bad, but because staying in the browser means fewer windows, fewer context switches, and a setup that works the same everywhere — your main machine, a work laptop, a tablet, or a borrowed computer.

You don't need a native app for everything.

## Best Practices

### Browser Choice

Pick one, stick with it. Cross-device sync matters more than features.

| Browser | Why Pick It |
|---------|------------|
| **[Vivaldi](https://vivaldi.com)** | Most customizable — hide the address bar entirely for a minimal, distraction-free UI |
| **[Arc](https://arc.net)** | Minimal by default — no tab bar, no address bar, sidebar-first |
| **[Zen](https://zen-browser.app)** | Same minimal philosophy as Arc, open source |
| **[Edge](https://microsoft.com/edge)** | Enable vertical tab bar to collapse the top area to a single line |
| **[Chrome](https://google.com/chrome)** | Enable vertical tab bar to collapse the top area to a single line |

### Password Manager

**[KeeWeb](https://keeweb.info)** — KeePass-compatible, open source, works as an offline web app with no install. Syncs your `.kdbx` file via Dropbox, Google Drive, OneDrive, or your own server. Desktop apps available too if you want them.

### Productivity Suite

#### Google Workspace

Google was the first to push the browser-first model seriously. All web-native from the start, still the gold standard for real-time collaboration.

- [Gmail](https://mail.google.com)
- [Drive](https://drive.google.com)
- [Sheets](https://sheets.google.com)
- [Docs](https://docs.google.com)
- [Slides](https://slides.google.com)
- [Meet](https://meet.google.com)
- [Calendar](https://calendar.google.com)

If you're starting fresh or don't have org constraints, Google Workspace is the easiest path. Everything syncs, everything works offline, and sharing is built in.

#### Microsoft 365

Office Online has caught up. Word, Excel, PowerPoint in the browser are now good enough for most tasks. If your org is on M365, lean into it — everything works in the browser.

- [Outlook](https://outlook.live.com)
- [Teams](https://teams.microsoft.com)
- [OneDrive](https://onedrive.live.com)
- [Word](https://word.office.com)
- [Excel](https://excel.office.com)
- [PowerPoint](https://powerpoint.office.com)

#### AI Assistants

The major AI assistants all live in the browser — no install needed.

- [M365 Copilot](https://microsoft365.com/copilot)
- [Claude](https://claude.ai)
- [ChatGPT](https://chatgpt.com)
- [Gemini](https://gemini.google.com)
- [Grok](https://grok.com)

### IDE

VS Code has three browser modes — they're different products, often confused:

**[VS Code `serve-web`](https://code.visualstudio.com/docs/remote/vscode-server)** — Run `code serve-web` on your machine, open the URL in any browser. Fully self-hosted, no Microsoft infrastructure. Full VS Code with terminal, extensions, and debugger — the browser-first way to run your editor.

**[code-server](https://github.com/coder/code-server)** — Open source, self-hosted VS Code server by Coder. Same idea as `serve-web` but community-driven, more deployment options, and multi-user capable. Total control over your setup.

**[vscode.dev](https://vscode.dev)** — Runs entirely in your browser, no server needed. Zero setup, works on any device. Opens GitHub repos directly (`vscode.dev/github/<org>/<repo>`). No terminal, no debugger, and many extensions don't work because there's no backend to run them on.

| | `serve-web` | code-server | vscode.dev |
|--|-------------|-------------|------------|
| Terminal | ✅ | ✅ | ❌ |
| Self-hosted | ✅ | ✅ | ❌ |
| Extensions | ✅ full | ✅ full | ⚠️ limited |
| Setup | Easy | Medium | None |
| Best for | Local network | Self-hosted teams | Quick browsing |

### Terminal

Great native terminals exist — [Ghostty](https://ghostty.org), [Alacritty](https://alacritty.org), [WezTerm](https://wezfurlong.org/wezterm), [Windows Terminal](https://aka.ms/terminal) — but a browser terminal keeps you in one window, makes sessions just URLs, and removes the context switch between editor and terminal. On Windows especially, the native multiplexer story is weak — no tmux, limited Zellij support — and the browser fills that gap naturally.

Here's every known approach and how they compare:

| Tool | Sessions | Windows | Notes |
|------|----------|---------|-------|
| **[webtty](https://github.com/jesse23/webtty)** (current repo) | ✅ | ✅ | Lightweight, session-aware, cross-platform |
| **[VibeTunnel](https://github.com/amantus-ai/vibetunnel)** | ✅ | ❌ | macOS/Linux, built for AI agent monitoring, native menu bar app + `vt` command wrapper |
| **[ttyd](https://github.com/tsl0922/ttyd)** | ❌ | ✅ | One shell per URL; session terminates when the connection drops |
| **[GoTTY](https://github.com/yudai/gotty)** | ❌ | ❌ | Lightweight Go tool, abandoned since 2017 |
| **[Zellij](https://zellij.dev)** (web mode) | ✅ | ❌ | Full multiplexer with web mode, Linux/macOS only |

### Terminal Software Recommendations

Good pieces for a solid terminal workflow:

| Name | Type | Description |
|------|------|-------------|
| **[fish](https://fishshell.com)** | Shell | Sensible defaults, autosuggestions, no config required to be useful |
| **[starship](https://starship.rs)** | Shell | Fast, minimal shell prompt, works with any shell |
| **[Clink](https://chrisant996.github.io/clink)** | Shell (Windows) | Powerful Bash-style line editing and completions for Windows cmd.exe |
| **[MSYS2](https://www.msys2.org)** | Shell (Windows) | Unix-like shell environment on Windows with pacman package manager |
| **[Zellij](https://zellij.dev)** | Multiplexer | Terminal workspace with layouts; pairs well with webtty for multiple sessions |
| **[NvChad](https://nvchad.com)** (Neovim) | Editor | Full IDE feel in the terminal, built-in LSP and syntax highlighting. Note: has unresolved lagging issues |
| **[vim](https://www.vim.org)** | Editor | Self-customized vim is more efficient for vibe coding — no framework overhead |
| **[yazi](https://yazi-rs.github.io)** | File Manager | Fast terminal file manager with preview |
| **[gitui](https://github.com/extrawurst/gitui)** | Git | Terminal UI for git, better than memorizing flags |
| **[lazygit](https://github.com/jesseduffield/lazygit)** | Git | Alternative git TUI, more opinionated workflow |
| **[delta](https://github.com/dandavison/delta)** | Git | Syntax-highlighting pager for git diffs — configure as `core.pager` in gitconfig |
| **[fzf](https://github.com/junegunn/fzf)** | Search | Fuzzy finder for files, history, and anything else piped to it |
| **[fd](https://github.com/sharkdp/fd)** | Search | Fast, user-friendly alternative to `find` |
| **[ripgrep](https://github.com/BurntSushi/ripgrep)** | Search | Blazing fast grep — respects `.gitignore` by default |
| **[eza](https://eza.rocks)** | Utility | Modern `ls` replacement with icons, git status, and tree view |
| **[bottom](https://github.com/ClementTsang/bottom)** | Utility | Cross-platform system monitor with a TUI |
| **[glow](https://github.com/charmbracelet/glow)** | Utility | Render markdown in the terminal with style |
| **[yt-dlp](https://github.com/yt-dlp/yt-dlp)** | Utility | Download video/audio from YouTube and hundreds of other sites |

### Agentic CLI

CLI tools that go beyond code completion — they plan, execute commands, manage files, search the web, and work through multi-step tasks autonomously in your terminal.

| Name | Subscription | Description |
|------|-------------|-------------|
| **[OpenCode](https://github.com/sst/opencode)** | GitHub Copilot | Open-source terminal AI agent, provider-agnostic |
| **[Claude Code](https://docs.anthropic.com/claude-code)** | Claude Pro ($20/mo) or Max ($100/$200/mo) | Anthropic's terminal agent — strong at reasoning and long multi-step tasks |
| **[GitHub Copilot CLI](https://docs.github.com/en/copilot)** | Free ($0) / Pro ($10/mo) / Pro+ ($39/mo) | GitHub-native terminal agent with `/plan`, `/fleet` for parallel execution |
| **[Gemini CLI](https://github.com/google-gemini/gemini-cli)** | Free (1k req/day) / Google One AI Premium | Google's open-source terminal agent, generous free tier, 1M token context |
| **[Codex CLI](https://github.com/openai/codex)** | ChatGPT Plus/Pro/Team | OpenAI's terminal agent, lightweight, runs locally |

---

The browser is no longer a limitation. It's where the best tools live now.
8 changes: 4 additions & 4 deletions docs/specs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ The CLI communicates with the server exclusively over HTTP — no Unix sockets,

| Command | Description |
|---------|-------------|
| `webtty at [id]` | Start server if not running; attach to session (creates if new, reuses if exists); open in browser. Aliases: `a`, `attach` |
| `webtty go [id]` | Start server if not running; attach to session (creates if new, reuses if exists); open in browser. Aliases: `a`, `run`, `attach`, `open` |
| `webtty ls [id]` | `GET /api/sessions` — list sessions; if `[id]` given, filter by substring match. Alias: `list` |
| `webtty rm [id]` | `DELETE /api/sessions/:id` — destroy session and its PTY; stops server if last session. Alias: `remove` |
| `webtty mv [id] [new-id]` | `PATCH /api/sessions/:id` — rename a session. Aliases: `move`, `rename` |
| `webtty stop` | `POST /api/server/stop` — server cleans up and exits |
| `webtty start` | Fork server, wait for `GET /api/sessions` to respond |
| `webtty` | No-arg entry point — start server if not running, then delegate to `webtty at main` |
| `webtty` | No-arg entry point — start server if not running, then delegate to `webtty go main` |
| `webtty config` | Open `~/.config/webtty/config.json` in `$VISUAL` (falls back to `$EDITOR`, then `vi` on Unix / `notepad` on Windows) |
| `webtty help` | Show help — all commands |

Expand All @@ -30,7 +30,7 @@ The CLI communicates with the server exclusively over HTTP — no Unix sockets,
`webtty` with no arguments:

1. Start the server if not already running
2. Delegate to `webtty at main` — create or reuse the `main` session and open it in the browser
2. Delegate to `webtty go main` — create or reuse the `main` session and open it in the browser

This is the canonical quickstart: `npx webtty` or `bunx webtty` goes from zero to a browser terminal in one command.

Expand All @@ -55,7 +55,7 @@ The command exits when the editor exits.
| Feature | Description | ADR | Done? |
|---------|-------------|-----|-------|
| Server lifecycle | `webtty start` / `stop` — start and stop the server | [ADR 002](../adrs/002.cli.start-stop.md) | ✅ |
| Session management | `webtty at` / `ls` / `rm` / `mv` — attach, list, destroy, and rename sessions | [ADR 006](../adrs/006.cli.session-management.md) | ✅ |
| Session management | `webtty go` / `ls` / `rm` / `mv` — attach, list, destroy, and rename sessions | [ADR 006](../adrs/006.cli.session-management.md) | ✅ |
| No-arg entry point | `webtty` — start server and open `main` session in browser | [ADR 011](../adrs/011.cli.config-and-help.md) | ✅ |
| Help and config | `webtty help` — show all commands; `webtty config` — open config in `$VISUAL`/`$EDITOR`/`vi` | [ADR 011](../adrs/011.cli.config-and-help.md) | ✅ |
| Help formatting | Description first, all-caps headings, aligned params, frequency-ordered commands, annotated usage lines | [ADR 011](../adrs/011.cli.config-and-help.md) | ✅ |
Expand Down
Loading
Loading