No description
Find a file
Jared Miller 31340fe0a8
Fix async terminal.write() causing garbled dashboard output
The terminal.write() method from @xterm/headless is async - the data
isn't in the buffer yet when we call serializeAsHTML() immediately
after. This caused empty or partially rendered output in the browser.

Now using the callback form of terminal.write(data, callback) to wait
for the write to complete before serializing and broadcasting to SSE
clients. This ensures the terminal buffer is fully updated before we
generate HTML from it.
2026-01-31 11:06:37 -05:00
data End all sessions on server startup 2026-01-30 09:12:40 -05:00
docs Add terminal emulation design doc 2026-01-31 09:40:31 -05:00
public Remove browser resize triggers 2026-01-31 10:58:22 -05:00
src Fix async terminal.write() causing garbled dashboard output 2026-01-31 11:06:37 -05:00
.dockerignore Speed up docker builds 2026-01-31 08:33:40 -05:00
.gitignore Rename project from claude-remote to clarc 2026-01-30 08:32:34 -05:00
biome.json Fix biome config formatting (self-heal) 2026-01-28 11:03:31 -05:00
bun.lock Add terminal.ts module for headless terminal emulation 2026-01-31 09:43:46 -05:00
Caddyfile.example Rename project from claude-remote to clarc 2026-01-30 08:32:34 -05:00
CLAUDE.md Update docs for terminal emulation 2026-01-31 10:04:29 -05:00
compose.yml Rename project from claude-remote to clarc 2026-01-30 08:32:34 -05:00
Dockerfile Speed up docker builds 2026-01-31 08:33:40 -05:00
justfile Rename project from claude-remote to clarc 2026-01-30 08:32:34 -05:00
package.json Add terminal.ts module for headless terminal emulation 2026-01-31 09:43:46 -05:00
README.md Update docs for terminal emulation 2026-01-31 10:04:29 -05:00
TODO.txt Update todo 2026-01-31 08:42:58 -05:00
tsconfig.json Add dynamic favicon state system 2026-01-31 08:42:58 -05:00

clarc

Self-hosted remote control for Claude Code. Wraps CLI in PTY, streams output to server, approve prompts from phone.

quick start

# 1. start the server
just dev

# 2. create a device (one-time setup)
just seed dev "My Phone"

# 3. build the binary + symlink to ~/bin
bun build --compile src/cli.ts --outfile clarc
ln -sf $(pwd)/clarc ~/bin/clarc

# 4. set your secret
export CLAUDE_REMOTE_SECRET=dev  # add to .bashrc/.zshrc

# 5. use it like claude
clarc
clarc -r
clarc --dangerously-skip-permissions

# 6. open browser/phone
open http://localhost:7200

usage

server

just dev     # development with hot reload
just start   # production
just check   # lint + typecheck + test

Server runs on port 7200 (configure via PORT env var).

create device

just seed <secret> <name>

Creates a device with the given secret and name. The secret is used to authenticate the CLI wrapper.

Default if you just run just seed:

  • secret: "dev"
  • name: "dev device"

run claude

With the binary installed and CLAUDE_REMOTE_SECRET set, just use clarc like claude:

clarc                    # interactive
clarc -r                 # resume
clarc --help             # shows help
clarc -p "explain this"  # print mode

All arguments pass through to claude.

configuration

Environment variables (add to .bashrc/.zshrc):

export CLAUDE_REMOTE_SECRET=dev                    # required
export CLAUDE_REMOTE_SERVER=ws://localhost:7200/ws # optional, this is the default

Or use flags to override:

clarc --secret other-device --server ws://remote:7200/ws

shell aliases

alias cr='clarc'
alias crr='clarc -r'
alias crd='clarc --dangerously-skip-permissions'
alias crdr='crd -r'

view output

Open http://localhost:7200 in browser or phone to:

  • see live terminal output
  • approve/reject permission prompts
  • answer questions

docker

just build  # build image
just up     # compose up
just down   # compose down
just logs   # compose logs

architecture

Pure Bun stack:

  • bun-pty for PTY wrapper
  • bun:sqlite for persistence
  • Bun.serve() for HTTP + WebSocket + SSE
  • @xterm/headless for terminal emulation and state tracking
  • plain text output (mobile-friendly)

Target: ~1000-1500 lines total.