{"id":45154447,"url":"https://github.com/icoretech/wootty","last_synced_at":"2026-03-04T04:06:00.449Z","repository":{"id":339413102,"uuid":"1161752242","full_name":"icoretech/wootty","owner":"icoretech","description":"🖥️ Flawless browser terminal for real operators","archived":false,"fork":false,"pushed_at":"2026-03-01T11:50:09.000Z","size":1022,"stargazers_count":10,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-01T14:58:45.921Z","etag":null,"topics":["browser-terminal","devops","go","pty","react","terminal","vite","web-terminal","websocket","xtermjs"],"latest_commit_sha":null,"homepage":"https://github.com/icoretech/wootty","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/icoretech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-19T13:30:40.000Z","updated_at":"2026-03-01T11:46:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/icoretech/wootty","commit_stats":null,"previous_names":["icoretech/wootty"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/icoretech/wootty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icoretech%2Fwootty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icoretech%2Fwootty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icoretech%2Fwootty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icoretech%2Fwootty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icoretech","download_url":"https://codeload.github.com/icoretech/wootty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icoretech%2Fwootty/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29991309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T01:47:34.672Z","status":"online","status_checked_at":"2026-03-02T02:00:07.342Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser-terminal","devops","go","pty","react","terminal","vite","web-terminal","websocket","xtermjs"],"created_at":"2026-02-20T04:01:07.675Z","updated_at":"2026-03-04T04:06:00.441Z","avatar_url":"https://github.com/icoretech.png","language":"TypeScript","readme":"# WooTTY\n\n[![CI](https://img.shields.io/github/actions/workflow/status/icoretech/wootty/ci.yml?branch=main\u0026label=CI)](https://github.com/icoretech/wootty/actions/workflows/ci.yml)\n[![GitHub Release](https://img.shields.io/github/v/release/icoretech/wootty)](https://github.com/icoretech/wootty/releases)\n[![License](https://img.shields.io/github/license/icoretech/wootty)](./LICENSE)\n\nWooTTY is a clean-slate browser terminal designed for one non-negotiable outcome: a terminal experience that stays reliable under real pressure (resize storms, reconnects, long output, and unstable networks).\n\n![WooTTY UI screenshot](docs/assets/wootty-ui-demo.gif)\n\n## Why WooTTY\n\n- Terminal-first UI: maximum viewport, compact status bar, floating controls.\n- Reconnect-safe sessions: resume by `sessionId`, replay buffered output.\n- Tab-safe defaults: each browser tab starts its own live session unless the operator explicitly resumes one.\n- Explicit multi-session actions: `Resume` for controllable sessions, `Watch` for sessions already controlled elsewhere (read-only).\n- Resize fidelity: client and PTY stay in sync during rapid window changes.\n- Operational defaults: high scrollback, keyboard-first controls, low-friction deployment.\n- Deployment flexibility: minimal image by default, plus `-openssh` image variant for SSH workflows.\n- Modern stack: Go 1.26+, Node 24+, React 19 + compiler, xterm.js.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Run with Docker](#run-with-docker)\n- [Kubernetes Deployments](#kubernetes-deployments)\n- [Run from Source](#run-from-source)\n- [Operator Controls](#operator-controls)\n- [Configuration](#configuration)\n- [Architecture](#architecture)\n- [Testing and Quality](#testing-and-quality)\n- [Contributing](#contributing)\n- [Security](#security)\n\n## Quick Start\n\n### Docker Quick Start\n\nStable image from GitHub Container Registry:\n\n```bash\ndocker run --rm -it -p 8080:8080 ghcr.io/icoretech/wootty:latest\n```\n\nThen open `http://127.0.0.1:8080`.\n\nFor image flavors, SSH usage, direct command args, compose profiles, and custom images, see [Run with Docker](#run-with-docker).\n\n### Run from Source\n\n```bash\npnpm install\npnpm dev\n```\n\nRun dev with SSH alias target:\n\n```bash\nWOOTTY_COMMAND=/usr/bin/ssh WOOTTY_COMMAND_ARGS=\"my-ssh-host-alias\" pnpm dev\n```\n\nRun WooTTY against `codex exec` (non-interactive Codex task in the terminal session):\n\n```bash\ncd apps/server\ngo run ./cmd/woottyd run --port 8080 \\\n  codex exec --skip-git-repo-check --ephemeral \"Reply with exactly: hello-from-codex\"\n```\n\nSame flow using environment variables:\n\n```bash\nWOOTTY_COMMAND=codex \\\nWOOTTY_COMMAND_ARGS='exec --skip-git-repo-check --ephemeral \"Reply with exactly: hello-from-codex\"' \\\npnpm dev\n```\n\n- Web: `http://localhost:5173`\n- Server: `http://127.0.0.1:8080` (auto-falls back to next free port if `8080` is busy)\n- Set `WOOTTY_PORT` explicitly to force a fixed dev port.\n\nProduction-like local run:\n\n```bash\npnpm build\ncd apps/server\ngo run ./cmd/woottyd run --port 8080 bash\n```\n\n## Run with Docker\n\nBuild locally:\n\n```bash\ndocker build -t wootty:dev .\ndocker run --rm -it -p 8080:8080 wootty:dev\n```\n\nRun profile-based examples from the root `docker-compose.yml`:\n\n```bash\n# default shell/runtime\ndocker compose up --build wootty\n\n# login shell\ndocker compose --profile bash up --build wootty-bash\n\n# ssh command mode (set your destination in docker-compose.yml)\ndocker compose --profile ssh up --build wootty-ssh\n\n# long-running detached session retention profile (72h TTL)\ndocker compose --profile retention up --build wootty-retention\n\n# deterministic fake PTY mode for tests/e2e\ndocker compose --profile test up --build wootty-fake-pty\n```\n\nThe `wootty-ssh` profile builds the `final-openssh` target so `/usr/bin/ssh` is available in that container.\n\nBuild your own image with custom binaries:\n\n```dockerfile\n# Dockerfile.custom\nFROM ghcr.io/icoretech/wootty:latest\n\n# Install additional runtime tools your command needs.\nRUN apt-get update \\\n  \u0026\u0026 apt-get install -y --no-install-recommends \\\n    ffmpeg \\\n    rsync \\\n    openssh-client \\\n  \u0026\u0026 rm -rf /var/lib/apt/lists/*\n\n# Optional: add your own binary/script\n# COPY ./bin/my-tool /usr/local/bin/my-tool\n# RUN chmod +x /usr/local/bin/my-tool\n```\n\n```bash\ndocker build -f Dockerfile.custom -t wootty:custom .\ndocker run --rm -it -p 8080:8080 \\\n  -e WOOTTY_COMMAND=/usr/bin/ssh \\\n  -e WOOTTY_COMMAND_ARGS=\"user@example.com\" \\\n  wootty:custom\n```\n\nEquivalent direct-args form:\n\n```bash\ndocker run --rm -it -p 8080:8080 \\\n  wootty:custom \\\n  run /usr/bin/ssh user@example.com\n```\n\nUse this pattern whenever `WOOTTY_COMMAND` depends on binaries not present in the default image.\n\nThe container serves:\n\n- backend API/websocket on `/api/*`\n- web UI bundled from `apps/web/src`\n\n## Kubernetes Deployments\n\n### Embed `wootty` Into Your App Image\n\n```dockerfile\n# Your existing runtime image\nFROM your-runtime-image:tag\n\n# Keep this pinned and automated (example Renovate comment)\n# renovate: datasource=docker depName=ghcr.io/icoretech/wootty versioning=semver\nCOPY --from=ghcr.io/icoretech/wootty:\u003cversion\u003e /usr/local/bin/wootty /usr/local/bin/wootty\n```\n\nThen start WooTTY as your container command (or entrypoint), pointing to the shell/binary you want:\n\n```bash\nwootty run bash\n```\n\n### Minimal Kubernetes Manifests (Official Image)\n\n```yaml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp-webcli\n  namespace: myapp\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: myapp-webcli\n  template:\n    metadata:\n      labels:\n        app: myapp-webcli\n    spec:\n      containers:\n        - name: webcli\n          # Or use ghcr.io/icoretech/wootty:latest-openssh or your own custom image.\n          image: ghcr.io/icoretech/wootty:latest\n          imagePullPolicy: IfNotPresent\n          command: [\"/bin/sh\", \"-lc\"]\n          args:\n            - |\n              exec wootty run bash\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: WOOTTY_AUTH_TOKEN\n              valueFrom:\n                secretKeyRef:\n                  name: wootty-auth\n                  key: token\n          readinessProbe:\n            httpGet:\n              path: /api/health\n              port: http\n          livenessProbe:\n            httpGet:\n              path: /api/health\n              port: http\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp-webcli\n  namespace: myapp\nspec:\n  selector:\n    app: myapp-webcli\n  ports:\n    - name: http\n      port: 8080\n      targetPort: http\n```\n\n### Deployment Ideas\n\n- Keep web CLI in a dedicated deployment (`myapp-webcli`) so scaling/restarts are independent from your main app.\n- Protect ingress with SSO, IP allowlists, or both; avoid exposing an unauthenticated terminal endpoint.\n- Set `WOOTTY_AUTH_TOKEN` from a Secret and rotate it like any other credential.\n- Use direct command args when you want a non-shell target, for example `wootty run /usr/bin/ssh user@example.com` or `wootty run /usr/local/bin/your-admin-tool --flag value`.\n\n## Operator Controls\n\nKeyboard shortcuts:\n\n- `Ctrl/Cmd+Shift+R`: reconnect\n- `Ctrl/Cmd+Shift+K`: clear viewport\n- `Ctrl/Cmd+Shift+=`: increase font size\n- `Ctrl/Cmd+Shift+-`: decrease font size\n- `Ctrl/Cmd+Shift+0`: reset font size\n- `Ctrl/Cmd+Shift+F`: fullscreen\n- `Ctrl/Cmd+Shift+B`: toggle controls\n\nStatus bar metrics:\n\n- connection status and latency\n- session id\n- attach mode (`Control` or `Read-only`)\n- reconnect count\n- buffered/dropped input size (humanized units)\n- output size (humanized units)\n\nSession controls:\n\n- click the `Session` badge in the status bar to open the session menu.\n- `New session`: start a fresh session in the current tab.\n- `Resume last`: reattach the last session id seen in this browser.\n- session menu separates `Live sessions` (running on server) from `Recent session ids` (browser memory only).\n- sessions already controlled in another tab/operator are shown as `Watch` (read-only attach).\n- resumable sessions are shown as `Resume` (full control attach).\n- recent ids that are not running are shown as unavailable.\n- tabs do not implicitly steal active sessions from each other.\n- terminal font starts at minimum (`11px`) by default and can be changed from controls/shortcuts.\n\n## Configuration\n\n| Variable | Default | Description |\n| --- | --- | --- |\n| `WOOTTY_HOST` | `0.0.0.0` | Bind address |\n| `WOOTTY_PORT` | `8080` | HTTP/WebSocket port |\n| `WOOTTY_DETACHED_TTL_MS` | `86400000` | Hard TTL for running detached sessions (24h). `0` disables this TTL |\n| `WOOTTY_HISTORY_BYTES` | `5242880` | Buffered output bytes for replay |\n| `WOOTTY_COMMAND` | `$SHELL` or `bash` | Executed command in the `woottyd` runtime environment (host or container) |\n| `WOOTTY_COMMAND_ARGS` | _empty_ | Shell-like command args string (supports quotes and escapes) |\n| `WOOTTY_CWD` | current directory | Process working directory |\n| `WOOTTY_STATIC_DIR` | auto-detected | Directory with built web assets |\n| `WOOTTY_AUTH_TOKEN` | _empty_ | Optional bearer token required by `/api/sessions` and `/api/terminal` when set |\n| `WOOTTY_ALLOWED_ORIGINS` | _empty_ | Optional comma-separated websocket origin allowlist |\n| `WOOTTY_FAKE_PTY` | `0` | Set to `1` for deterministic fake PTY mode |\n\n`WOOTTY_COMMAND_ARGS` examples:\n\n```bash\n# 1) Run a shell command with spaces\nWOOTTY_COMMAND=/bin/bash\nWOOTTY_COMMAND_ARGS='-lc \"echo hello world \u0026\u0026 whoami\"'\n\n# 2) SSH with multiple options\nWOOTTY_COMMAND=/usr/bin/ssh\nWOOTTY_COMMAND_ARGS='-o StrictHostKeyChecking=no -p 2222 user@example.com'\n\n# 3) Include escaped quotes inside an argument\nWOOTTY_COMMAND=/bin/bash\nWOOTTY_COMMAND_ARGS='-lc \"echo \\\"quoted text\\\" \u0026\u0026 date\"'\n\n# 4) Pass an explicit empty argument (\"\")\nWOOTTY_COMMAND=/bin/bash\nWOOTTY_COMMAND_ARGS='-lc \"printf \\\"[%s]\\\\n\\\" \\\"\\\" \\\"non-empty\\\"\"'\n```\n\nIf `WOOTTY_COMMAND_ARGS` has invalid quoting (for example an unterminated quote), WooTTY fails fast on startup with a config error.\n\nCLI equivalent is available for detached retention timing: `--detached-ttl-ms`.\n\nFor non-local deployments, set `WOOTTY_AUTH_TOKEN` (and optionally `WOOTTY_ALLOWED_ORIGINS`) to protect session and websocket endpoints.\n\n### Session Retention Model\n\n- Session metadata and PTY state are in-memory only.\n- If a terminal process exits, the session is removed immediately.\n- If a terminal process is still running and no controller/watcher is attached:\n  - `WOOTTY_DETACHED_TTL_MS \u003e 0`: session is cleaned up after that TTL.\n  - `WOOTTY_DETACHED_TTL_MS = 0`: timer cleanup is disabled; session remains available until process exit or server restart.\n- Server restart clears all sessions because there is no persistent session store.\n\nRecommended for long-running jobs with occasional reconnects:\n\n```bash\nWOOTTY_DETACHED_TTL_MS=259200000  # 72h\n```\n\nCompose example:\n\n```yaml\nservices:\n  wootty:\n    image: ghcr.io/icoretech/wootty:latest\n    ports:\n      - \"8080:8080\"\n    environment:\n      WOOTTY_DETACHED_TTL_MS: \"259200000\"\n```\n\n## Architecture\n\n```mermaid\nflowchart LR\n  B[\"Browser UI (React + xterm)\"] -- \"WebSocket (/api/terminal)\" --\u003e S[\"WooTTY Server (Go)\"]\n  B -- \"HTTP (/api/sessions)\" --\u003e S\n  S -- \"PTY attach/input/resize\" --\u003e P[\"Shell Process (creack/pty)\"]\n  P -- \"output stream\" --\u003e S\n  S -- \"output + status events\" --\u003e B\n  S -- \"session history buffer\" --\u003e H[\"In-memory replay buffer\"]\n```\n\nFrontend module ownership:\n\n\u003c!-- governance:module-ownership:start --\u003e\n- `apps/web/src/App.tsx`: composition entrypoint that mounts the terminal feature app.\n- `apps/web/src/features/terminal/app/TerminalApp.tsx`: terminal app entrypoint and top-level composition shell.\n- `apps/web/src/features/terminal/app/composition/*`: app-level composition boundaries (platform, domain, and controller wiring) that join environment adapters with feature hooks.\n- `apps/web/src/features/terminal/app/engine/*`: transport lifecycle, runtime boot/IO bridge, and connection state projection.\n- `apps/web/src/features/terminal/app/bindings/*`: browser/document/window/session bindings (shortcuts, refresh cadence wiring, resize/fullscreen wiring, title updates).\n- `apps/web/src/features/terminal/environment/*`: environment contracts shared by app bootstrap and controller layers.\n- `apps/web/src/features/terminal/commands/*`: terminal command contract + registry ownership (UI actions and shortcut mapping).\n- `apps/web/src/features/terminal/contracts/*`: shared terminal contracts (session + transport types and ready-state constants).\n- `apps/web/src/features/terminal/platform/*`: platform-facing utilities shared by app/engine bindings (for example scheduler abstractions).\n- `apps/web/src/features/terminal/components/*`: presentational controls, status bar, and session menu UI.\n- `apps/web/src/features/terminal/view/*`: UI-facing formatting and presenter mapping for menu/session copy.\n- `apps/web/src/features/terminal/commands/floating-controls/*`: floating-controls registry, metadata, and descriptor assembly.\n- `apps/web/src/features/terminal/notifications/*`: user-facing terminal notice mapping.\n- `apps/web/src/features/terminal/session/domain/*`: session candidate derivation and domain-level selection helpers.\n- `apps/web/src/features/terminal/session/protocol/*`: session payload parsing and refresh failure protocol ownership.\n- `apps/web/src/features/terminal/session/persistence/*`: storage adapters and storage key ownership.\n- `apps/web/src/features/terminal/lib/*`: terminal-only utility helpers (formatting, outbox buffering).\n- `apps/web/src/features/terminal/protocol/*`: protocol parsing owned by the terminal feature.\n- `apps/web/src/features/terminal/adapters/*`: transport adapters owned by the terminal feature.\n- `apps/web/src/features/terminal/runtime/*`: xterm runtime loading owned by the terminal feature.\n\u003c!-- governance:module-ownership:end --\u003e\n\n### Client Protocol Contract\n\n`apps/web/src/features/terminal/protocol/terminal-protocol.ts` is the client-side source of truth for websocket payload parsing.\n\n- Supported inbound message `type` values: `ready`, `output`, `exit`, `error`, `pong`.\n- Required fields:\n  - `ready`: `sessionId` (string), `readOnly` (boolean), `version` (must match `TERMINAL_WIRE_CONTRACT_VERSION`)\n  - `output`: `data` (string)\n  - `exit`: `code` (number), `signal` (number)\n  - `error`: `message` (string), optional `code` (known server code string). Unknown non-empty codes are surfaced as `rawCode`.\n  - `pong`: no additional fields\n- Compatibility policy:\n  - Additive fields are allowed and ignored by older clients.\n  - Unknown message `type` values are treated as unsupported and surfaced as a user notice.\n  - Invalid payload shapes are dropped by the parser and do not mutate terminal state.\n\n### Transport Lifecycle Contract\n\nTransport responsibilities are split by contract:\n\n- `apps/web/src/features/terminal/contracts/transport/transport.ts` defines the transport surface and ready-state constants used by app runtime and test doubles.\n- `apps/web/src/features/terminal/app/engine/transport/state/transport-policy.ts` defines heartbeat intervals, close codes, and reconnect delay policy.\n- `apps/web/src/features/terminal/adapters/transport-event-normalizer.ts` adapts browser runtime events into typed contract payloads.\n\n- Canonical ready states:\n  - `TRANSPORT_READY_STATE.CONNECTING` (`0`)\n  - `TRANSPORT_READY_STATE.OPEN` (`1`)\n  - `TRANSPORT_READY_STATE.CLOSING` (`2`)\n  - `TRANSPORT_READY_STATE.CLOSED` (`3`)\n- Heartbeat policy:\n  - Client sends `ping` every `12s` while connected.\n  - Missing `pong` for `12s` triggers close code `4103` (`pong timeout`) and reconnect flow.\n- Close/reconnect policy:\n  - Manual reconnect closes with `4101`.\n  - Starting a fresh session closes old transport with `4102`.\n  - Backoff uses `reconnectDelayMs(attempt)` (`300ms * 1.8^attempt`, capped at `5000ms`).\n\n## Testing and Quality\n\nStandard quality gates:\n\n```bash\npnpm lint\npnpm test\npnpm build\npnpm test:e2e\n```\n\nOne-shot local CI parity:\n\n```bash\npnpm ci\n```\n\nCross-browser browser matrix (Chromium + Firefox + WebKit):\n\n```bash\npnpm test:e2e:cross\n```\n\nNotes:\n\n- `pnpm lint` applies Biome fixes, runs `go fix` on the server module, and then runs typecheck.\n- CI enforces zero formatting drift (`git diff --exit-code`).\n- Test environment ownership:\n  - Browser test polyfills and setup wiring live under `apps/web/test/support/`.\n  - E2E URL/port defaults live under `apps/web/config/e2e/e2e-env.ts`.\n  - App integration harness composition lives in `apps/web/test/integration/app/harness/`.\n\n## Contributing\n\nRead [CONTRIBUTING.md](./CONTRIBUTING.md) before opening a PR.\n\n## Security\n\nReport vulnerabilities through [SECURITY.md](./SECURITY.md).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficoretech%2Fwootty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficoretech%2Fwootty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficoretech%2Fwootty/lists"}