{"_id":"@combinatrix-ai/umux","name":"@combinatrix-ai/umux","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@combinatrix-ai/umux","version":"0.0.1","description":"Agent-ready terminal multiplexer","type":"module","bin":{"umux":"dist/cli/bin.js","umux-mcp":"dist/mcp/index.js"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"scripts":{"postinstall":"node scripts/postinstall.js","build:ghostty-vt-wasm":"./scripts/build-ghostty-vt-wasm.sh","build":"tsup","dev":"tsup --watch","bench:engine":"node ./scripts/bench/engine-vs-tmux.mjs","bench:logs":"node ./scripts/bench/logs-vs-tmux.mjs","bench:cat":"node ./scripts/bench/cat-throughput.mjs","test":"vitest run","test:local":"npm install && npm test","test:linux":"docker run --rm -v \"$PWD:/work\" -w /work node:20-bullseye bash -lc \"npm ci && npm test\"","test:watch":"vitest","test:integration":"vitest run tests/integration.test.ts","test:e2e":"vitest run tests/e2e.test.ts","test:scenarios":"vitest run tests/scenarios.test.ts --config vitest.scenarios.config.ts","test:all":"npm run test && npm run test:integration && npm run test:e2e && npm run test:scenarios","lint":"biome check","lint:fix":"biome check --write","format":"biome format --write","typecheck":"tsc --noEmit","clean":"rm -rf dist"},"dependencies":{"@hono/node-server":"^1.0.0","@modelcontextprotocol/sdk":"^1.0.0","@xterm/addon-serialize":"^0.14.0","@xterm/headless":"^5.5.0","commander":"^12.0.0","hono":"^4.0.0","nanoid":"^5.0.0","node-pty":"^1.0.0","picocolors":"^1.1.0","zod":"^3.23.0"},"devDependencies":{"@biomejs/biome":"^2.3.11","@types/node":"^22.0.0","tsup":"^8.0.0","typescript":"^5.7.0","vitest":"^2.0.0"},"engines":{"node":">=20.0.0"},"repository":{"type":"git","url":"git+https://github.com/combinatrix-ai/umux.git"},"keywords":["terminal","multiplexer","tmux","agent","cli","pty"],"license":"MIT","_id":"@combinatrix-ai/umux@0.0.1","gitHead":"fbecb59702b80cd24b294a1d967d1c87dbb583b1","bugs":{"url":"https://github.com/combinatrix-ai/umux/issues"},"homepage":"https://github.com/combinatrix-ai/umux#readme","_nodeVersion":"24.2.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-1BvwD4t3Okj6zAdQxJGs1LL/lQkzhH4ihioWEotd98ghUiyTrbjOb/o1Xd1FZbO0EtRqjQ1YOA1Kk+HPf5xLSg==","shasum":"4a5f3b183701e649004789b736cc41b1d41cb16b","tarball":"https://registry.npmjs.org/@combinatrix-ai/umux/-/umux-0.0.1.tgz","fileCount":29,"unpackedSize":1268405,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDOd6ydSPpmujvnAIaKHQkdZ5mgpt3wajVxOBabY73LLQIgecR4U2JvYk17lt7L7HrOZpreCD7S3JSW9AswLy0Zixk="}]},"_npmUser":{"name":"hmirin","email":"necktie.pulls37@icloud.com"},"directories":{},"maintainers":[{"name":"hmirin","email":"necktie.pulls37@icloud.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/umux_0.0.1_1770385450629_0.7311981627738686"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-06T13:44:10.536Z","0.0.1":"2026-02-06T13:44:10.791Z","modified":"2026-02-06T13:44:11.027Z"},"maintainers":[{"name":"hmirin","email":"necktie.pulls37@icloud.com"}],"description":"Agent-ready terminal multiplexer","homepage":"https://github.com/combinatrix-ai/umux#readme","keywords":["terminal","multiplexer","tmux","agent","cli","pty"],"repository":{"type":"git","url":"git+https://github.com/combinatrix-ai/umux.git"},"bugs":{"url":"https://github.com/combinatrix-ai/umux/issues"},"license":"MIT","readme":"# umux\n\n**Stateful shell sessions with an API — tmux, but for agents.**\n\n- Every blocking operation requires a timeout — no hanging forever\n- No polling loops or fragile timing hacks — describe what you're waiting for\n- No unbounded stdout — won't fill your context unexpectedly\n- No interference between human and agent — work separately\n\n**Example: Getting Claude Usage from TUI** 43% shorter, 16% faster — actually written by an agent\n<table>\n<tr>\n<th>❌ tmux: poll + sleep + guess</th>\n<th>✅ umux: declarative wait</th>\n</tr>\n<tr>\n<td>\n\n```bash\n# Wait for startup - polling loop\nfor i in {1..30}; do\n    sleep 1\n    if tmux capture-pane -p | grep -q \"Ready\"; then\n        break\n    fi\ndone\nsleep 2  # extra settle time (magic number)\n\n# Send command with autocomplete workaround\ntmux send-keys \"/status\"\nsleep 0.3\ntmux send-keys Escape  # close autocomplete menu\nsleep 0.2\ntmux send-keys Enter\n\n# Wait for UI - more polling\nfor i in {1..20}; do\n    sleep 0.3\n    if tmux capture-pane -p | grep -q \"Settings:\"; then\n        break\n    fi\ndone\n\n# After key presses, wait for UI to settle\n# (magic number)\ntmux send-keys Tab\nsleep 0.5\n```\n\n</td>\n<td>\n\n```bash\n# Wait for startup - one flag\numux spawn -n claude claude \\\n  --block-until-screen-match \"Welcome\" \\\n  --timeout 30000\n\n# Send command + wait for UI - one line\numux send \"/status\" --enter \\\n  --block-until-screen-match \"Settings:\" \\\n  --timeout 10000\n\n# After key presses, wait for UI to settle\n# (fast, deterministic)\numux send --key Tab \\\n  --block-until-idle 200 \\\n  --timeout 3000\n```\n\n</td>\n</tr>\n<tr>\n<td><strong>61 lines</strong></td>\n<td><strong>35 lines</strong></td>\n</tr>\n<tr>\n<td><strong>6.68s</strong> median runtime</td>\n<td><strong>5.58s</strong> median runtime</td>\n</tr>\n</table>\n\n→ [Full comparison](./examples/claude-usage/)\n\n## Testimonials\n\n> \"When I wrote a script to automate Claude CLI with tmux, I spent most of my time fighting timing issues — polling loops, fixed sleeps, autocomplete menu workarounds. The same script with umux was **35 lines** vs **61 lines** with tmux, and about **16% faster** (median of 10 runs). The `--block-until-screen-match` and `--block-until-idle` options let me express *what* I want to wait for, not *how* to poll for it.\"\n>\n> — Claude, after writing the code above\n\n---\n\n## The problem\n\n### The problem with exec()\n\nWhen agents run shell commands, raw `exec()` has issues:\n- **Can hang forever** — no timeout, blocks indefinitely\n- **Output can explode** — stdout fills memory\n- **No interactivity** — can't handle prompts, TUIs, REPLs\n\n### The problem with tmux\n\nSo agents use tmux. But tmux is built for humans:\n- **Keyboard capture** — `Ctrl-b` taken, input gets intercepted\n- **Shared UI state** — panes, windows, scroll position conflict with human\n- **Scrollback limits** — history truncated, designed for human eyes\n- **Poll to wait** — no \"wait until done\" API, must poll + sleep + guess\n\nWe humans love tmux but it seems difficult for agents to work smoothly with tmux\n\n### umux: shell sessions for agents\n\numux is a command execution environment designed for agents from the ground up:\n- **No keybindings** — all input goes straight to the shell\n- **No shared state** — observe sessions without interfering\n- **Queryable history** — searchable in-memory history + optional JSONL disk logs\n- **Declarative waiting** — `wait --block-until-*`, not poll loops\n- **Mandatory timeouts** — never hang forever\n\n\n### tmux vs umux\n\ntmux is a great TUI for humans. But Agents need an API.\n\n| | tmux | umux |\n|---|------|------|\n| **Interface** | TUI + keyboard shortcuts | CLI / API only |\n| **Complexity** | Panes, windows, layouts | 1 session = 1 shell |\n| **State** | Shared UI state (focus, scroll position) | Stateless observation |\n| **Keybindings** | `Ctrl-b` prefix captured | None — all input goes to shell |\n| **Waiting** | Poll `capture-pane` + sleep | `wait --block-until-ready`, `--block-until-match` |\n| **History access** | Poll `capture-pane` | `logs` API (search, tail, head) |\n| **Persistence** | None by default | Optional JSONL logs |\n| **Timeouts** | Can block forever | Always required |\n\nPlus: umux uses libghostty-vt (WASM) for terminal state and capture. In practice this is often faster than tmux, especially for long-running sessions and huge logs. See `scripts/bench/cat-throughput.mjs`.\n\n---\n\n## Installation\n\nRequirements\n- Node.js 20+\n- Linux, macOS, or Windows with WSL\n\n```bash\nnpm install -g @combinatrix-ai/umux\n```\n\nThen, ask your agent to \"use `umux` instead of tmux.\"\n\n---\n\n## Quick Start\n\n```bash\n# Spawn a shell\numux spawn bash\n\n# Run commands (state is preserved)\numux send \"cd /project && export FOO=bar\" --enter\numux wait --block-until-ready --timeout 5000\n\numux send \"npm install\" --enter\numux wait --block-until-ready --timeout 60000\n\numux send \"npm test\" --enter\numux wait --block-until-ready --timeout 60000\n\n# Check output\numux logs --tail 20\n\n# Clean up\numux rm\n```\n\n---\n\n## Key Features\n\n### Simple Model\n\nNo panes. No windows. No layouts. Just sessions.\n\n```bash\numux spawn bash      # Create a session\numux send \"ls\" --enter\numux logs            # See output\numux rm              # Done\n```\n\n### Persistent Shell State\n\nUnlike one-shot `exec()`, shell state is preserved across commands:\n\n```bash\numux send \"cd /project\" --enter\numux send \"export TOKEN=secret\" --enter\numux send \"source .env\" --enter\n# cwd, env vars, aliases — all preserved\n```\n\n### Declarative Waiting\n\nSay *what* you're waiting for, not *how* to poll for it:\n\n```bash\numux wait --block-until-ready --timeout 60000           # Wait for shell prompt\numux wait --block-until-match \"Success\" --timeout 5000  # Wait for output pattern\numux wait --block-until-screen-match \">\" --timeout 5000 # Wait for TUI state\numux wait --block-until-idle 500 --timeout 5000         # Wait for output to settle\n```\n\nAll waits require `--timeout` (or set via `UMUX_DEFAULT_TIMEOUT`). No infinite hangs.\n\n### Queryable History\n\nQuery history anytime. For long sessions, enable disk persistence via `UMUX_LOG_DIR`.\n\n```bash\numux logs                    # Last 100 lines (default)\numux logs --tail 50          # Last 50 lines\numux logs --all              # All retained history\numux logs --search \"error\"   # Search\numux logs --send-only        # Audit what agent sent\n```\n\nNote: `umux logs` defaults to the last 100 lines to avoid accidentally dumping huge output to stdout in scripts and CI.\n\n#### Optional: Persist I/O logs (JSONL)\n\nIf you want durable logs on disk, set `UMUX_LOG_DIR`. Each session appends JSONL (input + output) to:\n\n```\nYYYY-MM-DD_sess-XXXXXXXX.log.jsonl\n```\n\nNote: logs may contain sensitive data. Control input logging with `UMUX_LOG_INPUT=0`.\n\n### Screen Capture\n\nSnapshot the visible terminal (useful for TUIs):\n\n```bash\numux capture                 # Plain text\numux capture --format ansi   # With colors\n```\n\n---\n\n## CLI Reference\n\n| Command | Description |\n|---------|-------------|\n| `spawn [program]` | Start a session (default: `$SHELL`) |\n| `send [text]` | Send text/keys to session |\n| `wait` | Wait for a condition |\n| `guide` | Print bundled docs/examples |\n| `status` | Get session status |\n| `logs` | View output history |\n| `capture` | Snapshot current screen |\n| `ls` | List sessions |\n| `rm` | Remove session |\n| `kill` | Kill session process |\n| `resize` | Resize terminal |\n| `hook` | Manage event hooks |\n| `server` | Start the umux server (internal) |\n\nSee `umux <command> --help` for details, or [full CLI docs](./docs/cli.md).\n\n---\n\n## As a Library\n\n```typescript\nimport { Umux } from '@combinatrix-ai/umux';\nimport { createGhosttyTerminalEngine } from '@combinatrix-ai/umux';\n\nconst umux = new Umux();\nconst session = await umux.spawn('python3');\n\nawait umux.waitFor(session.id, { pattern: />>>/, timeout: 5000 });\n\numux.send(session.id, 'x = 42\\n');\nawait umux.waitFor(session.id, { ready: true, timeout: 5000 });\n\numux.send(session.id, 'print(x * 2)\\n');\nawait umux.waitFor(session.id, { ready: true, timeout: 5000 });\n\nconsole.log(session.history.tail(5));\n\numux.destroy();\n```\n\n### Using Ghostty VT (built-in)\n\numux uses Ghostty VT by default for fast `capture --format ansi` and robust screen state for TUIs.\n\nTo change engines:\n- `UMUX_TERMINAL_ENGINE=xterm` (force legacy xterm engine)\n- `UMUX_TERMINAL_ENGINE=ghostty` (default: Ghostty with xterm fallback)\n- `UMUX_TERMINAL_ENGINE=ghostty-strict` (force Ghostty; fail instead of falling back)\n\n```ts\nimport { Umux, createGhosttyTerminalEngine } from '@combinatrix-ai/umux';\n\nconst umux = new Umux({ terminalEngine: createGhosttyTerminalEngine });\n```\n\n#### Updating the bundled Wasm\n\n- Ghostty source is vendored as a pinned git submodule at `vendor/ghostty` (run `git submodule update --init --recursive` after cloning).\n- Rebuild the bundled Wasm with `npm run build:ghostty-vt-wasm` (writes `assets/umux-ghostty-vt.wasm`).\n\n### WaitCondition Options\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `pattern` | `RegExp \\| string` | Wait for output matching pattern |\n| `screenPattern` | `RegExp \\| string` | Wait for screen buffer matching pattern |\n| `idle` | `number` | Wait for N ms of no output |\n| `exit` | `boolean` | Wait for process exit |\n| `ready` | `boolean` | Wait for shell to be ready |\n| `timeout` | `number` | Timeout in milliseconds |\n| `not` | `RegExp \\| string` | Fail immediately if this pattern appears |\n\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-400dfe60c8918acf49ffd1b42ea6a4cd"}