Skip to content

Commit 3eeb7b6

Browse files
committed
docs: add tmux bracketed paste workaround for CLI input
- Create cli/tmux.knowledge.md with comprehensive tmux documentation - Document that standard tmux send-keys drops characters - Solution: use bracketed paste mode (\e[200~...\e[201~) - Update cli/README.md, cli/knowledge.md, cli/src/__tests__/README.md - Add public troubleshooting docs in web/src/content/advanced/troubleshooting.mdx
1 parent 8b53bb1 commit 3eeb7b6

File tree

5 files changed

+301
-55
lines changed

5 files changed

+301
-55
lines changed

cli/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ Then run the proof-of-concept:
4646
bun run test:tmux-poc
4747
```
4848

49-
See [src/__tests__/README.md](src/__tests__/README.md) for comprehensive testing documentation.
49+
**Note:** When sending input to the CLI via tmux, you must use bracketed paste mode. Standard `send-keys` drops characters.
50+
51+
```bash
52+
# ❌ Broken: tmux send-keys -t session "hello"
53+
# ✅ Works: tmux send-keys -t session $'\e[200~hello\e[201~'
54+
```
55+
56+
See [tmux.knowledge.md](tmux.knowledge.md) for comprehensive tmux documentation and [src/__tests__/README.md](src/__tests__/README.md) for testing documentation.
5057

5158
## Build
5259

cli/knowledge.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ tmux new-session -d -s test-session 'cd /path/to/codebuff && bun --cwd=cli run d
7575
- Add `2>/dev/null` to `tmux kill-session` to suppress errors if session doesn't exist
7676
- Adjust sleep timings based on what you're testing (auth checks, network requests, etc.)
7777

78+
### Sending Input to the CLI via tmux
79+
80+
**See [`tmux.knowledge.md`](./tmux.knowledge.md) for comprehensive tmux documentation.**
81+
82+
**Key point:** Standard `tmux send-keys` does NOT work - you must use bracketed paste mode:
83+
84+
```bash
85+
# ❌ Broken: tmux send-keys -t session "hello"
86+
# ✅ Works: tmux send-keys -t session $'\e[200~hello\e[201~'
87+
```
88+
7889
## Migration from Custom OpenTUI Fork
7990

8091
**October 2024**: Migrated from custom `CodebuffAI/opentui#codebuff/custom` fork to official `@opentui/react@^0.1.27` and `@opentui/core@^0.1.27` packages. Updated to `^0.1.28` in February 2025.

cli/src/__tests__/README.md

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -162,68 +162,25 @@ if (isSDKBuilt()) {
162162
await sleep(1000)
163163
```
164164

165-
## tmux Testing Approach
165+
## tmux Testing
166166

167-
### Why tmux?
168-
169-
- ✅ Full terminal emulation with PTY support
170-
- ✅ No native compilation needed (Bun 1.3+ compatible)
171-
- ✅ Send keystrokes, capture output
172-
- ✅ Can attach to sessions for debugging
173-
- ✅ Cross-platform (macOS, Linux, WSL)
174-
175-
### Basic tmux Workflow
167+
**See [`../../tmux.knowledge.md`](../../tmux.knowledge.md) for comprehensive tmux documentation**, including:
168+
- Why standard `send-keys` doesn't work (must use bracketed paste mode)
169+
- Helper functions for Bash and TypeScript
170+
- Complete example scripts
171+
- Debugging and troubleshooting tips
176172

173+
**Quick reference:**
177174
```typescript
178-
// 1. Create tmux session
179-
await tmux(['new-session', '-d', '-s', sessionName, 'your-command'])
180-
181-
// 2. Send commands
182-
await tmux(['send-keys', '-t', sessionName, 'input text', 'Enter'])
183-
184-
// 3. Wait for output
185-
await sleep(1000)
186-
187-
// 4. Capture output
188-
const output = await tmux(['capture-pane', '-t', sessionName, '-p'])
189-
190-
// 5. Clean up
191-
await tmux(['kill-session', '-t', sessionName])
192-
```
193-
194-
### tmux Helper Function
175+
// ❌ Broken:
176+
await tmux(['send-keys', '-t', session, 'hello'])
195177

196-
```typescript
197-
function tmux(args: string[]): Promise<string> {
198-
return new Promise((resolve, reject) => {
199-
const proc = spawn('tmux', args, { stdio: 'pipe' })
200-
let stdout = ''
201-
202-
proc.stdout?.on('data', (data) => {
203-
stdout += data.toString()
204-
})
205-
206-
proc.on('close', (code) => {
207-
code === 0 ? resolve(stdout) : reject(new Error('tmux failed'))
208-
})
209-
})
210-
}
178+
// ✅ Works:
179+
await tmux(['send-keys', '-t', session, '-l', '\x1b[200~hello\x1b[201~'])
211180
```
212181

213182
## Debugging Tests
214183

215-
### Attach to tmux Session
216-
217-
For debugging, keep session alive and attach:
218-
219-
```typescript
220-
// Don't kill session immediately
221-
await tmux(['new-session', '-d', '-s', 'debug-session', 'cli-command'])
222-
223-
// In another terminal
224-
// tmux attach -t debug-session
225-
```
226-
227184
### View Test Output
228185

229186
```bash

cli/tmux.knowledge.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# tmux Knowledge for CLI Testing
2+
3+
This document covers essential knowledge for using tmux to test and automate the Codebuff CLI.
4+
5+
## Critical: Sending Input to the CLI
6+
7+
**Standard `tmux send-keys` does NOT work with the Codebuff CLI.** Characters are dropped or garbled due to how OpenTUI handles keyboard input.
8+
9+
### The Problem
10+
11+
```bash
12+
# ❌ BROKEN: Characters get dropped - only last character appears
13+
tmux send-keys -t session "hello world"
14+
tmux send-keys -t session Enter
15+
# Result: Only "d" appears in the input field!
16+
```
17+
18+
### The Solution: Bracketed Paste Mode
19+
20+
Always wrap input in bracketed paste escape sequences (`\e[200~...\e[201~`):
21+
22+
```bash
23+
# ✅ WORKS: Use bracketed paste escape sequences
24+
tmux send-keys -t session $'\e[200~hello world\e[201~'
25+
tmux send-keys -t session Enter
26+
# Result: "hello world" appears correctly!
27+
```
28+
29+
### Why This Happens
30+
31+
- OpenTUI's keyboard input handling processes individual keystrokes
32+
- When characters arrive rapidly via `send-keys`, they can be dropped or misinterpreted
33+
- Bracketed paste mode (`\e[200~...\e[201~`) signals that the input is a paste operation
34+
- Paste events are processed atomically, preserving all characters
35+
36+
### What Works Without Bracketed Paste
37+
38+
- Control keys: `Enter`, `C-c`, `C-u`, `C-d`, `Escape`
39+
- Arrow keys: `Up`, `Down`, `Left`, `Right`
40+
- Function keys and special keys
41+
- Single characters with long delays (200ms+) - but this is slow and unreliable
42+
43+
## Helper Functions
44+
45+
### Bash Helper
46+
47+
```bash
48+
# Send text to Codebuff CLI in tmux
49+
send_to_codebuff() {
50+
local session="$1"
51+
local text="$2"
52+
# Use bracketed paste mode for reliable input
53+
tmux send-keys -t "$session" $'\e[200~'"$text"$'\e[201~'
54+
}
55+
56+
# Usage:
57+
send_to_codebuff my-session "fix the bug in main.ts"
58+
tmux send-keys -t my-session Enter
59+
```
60+
61+
### TypeScript Helper
62+
63+
```typescript
64+
async function sendInput(sessionName: string, text: string): Promise<void> {
65+
// Use bracketed paste mode for reliable input
66+
await tmux(['send-keys', '-t', sessionName, '-l', `\x1b[200~${text}\x1b[201~`])
67+
}
68+
69+
// Usage:
70+
await sendInput(sessionName, 'fix the bug')
71+
await tmux(['send-keys', '-t', sessionName, 'Enter'])
72+
```
73+
74+
## Common tmux Patterns
75+
76+
### Starting the CLI in tmux
77+
78+
```bash
79+
# Create a detached session running the CLI
80+
tmux new-session -d -s codebuff-test -x 120 -y 30 'bun run src/index.tsx'
81+
82+
# Wait for CLI to initialize
83+
sleep 3
84+
```
85+
86+
### Sending Input and Capturing Output
87+
88+
```bash
89+
# Send input using bracketed paste
90+
tmux send-keys -t codebuff-test $'\e[200~what files are in this project?\e[201~'
91+
tmux send-keys -t codebuff-test Enter
92+
93+
# Wait for response
94+
sleep 5
95+
96+
# Capture output
97+
tmux capture-pane -t codebuff-test -p
98+
```
99+
100+
### Cleaning Up
101+
102+
```bash
103+
# Kill the session when done
104+
tmux kill-session -t codebuff-test 2>/dev/null
105+
```
106+
107+
## Complete Example Script
108+
109+
```bash
110+
#!/bin/bash
111+
SESSION="codebuff-test-$$"
112+
113+
# Start CLI
114+
tmux new-session -d -s "$SESSION" -x 120 -y 30 'bun --cwd=cli run dev'
115+
sleep 4
116+
117+
# Send a prompt
118+
tmux send-keys -t "$SESSION" $'\e[200~list the files in src/\e[201~'
119+
tmux send-keys -t "$SESSION" Enter
120+
sleep 3
121+
122+
# Capture and display output
123+
echo "=== CLI Output ==="
124+
tmux capture-pane -t "$SESSION" -p
125+
126+
# Cleanup
127+
tmux kill-session -t "$SESSION" 2>/dev/null
128+
```
129+
130+
## TypeScript Test Pattern
131+
132+
```typescript
133+
import { spawn } from 'child_process'
134+
135+
function tmux(args: string[]): Promise<string> {
136+
return new Promise((resolve, reject) => {
137+
const proc = spawn('tmux', args, { stdio: 'pipe' })
138+
let stdout = ''
139+
proc.stdout?.on('data', (data) => { stdout += data.toString() })
140+
proc.on('close', (code) => {
141+
code === 0 ? resolve(stdout) : reject(new Error('tmux failed'))
142+
})
143+
})
144+
}
145+
146+
async function sendInput(session: string, text: string): Promise<void> {
147+
await tmux(['send-keys', '-t', session, '-l', `\x1b[200~${text}\x1b[201~`])
148+
}
149+
150+
async function testCLI() {
151+
const session = `test-${Date.now()}`
152+
153+
try {
154+
// Start CLI
155+
await tmux(['new-session', '-d', '-s', session, '-x', '120', '-y', '30',
156+
'bun', 'run', 'src/index.tsx'])
157+
await sleep(4000)
158+
159+
// Send input
160+
await sendInput(session, 'hello world')
161+
await tmux(['send-keys', '-t', session, 'Enter'])
162+
await sleep(2000)
163+
164+
// Capture output
165+
const output = await tmux(['capture-pane', '-t', session, '-p'])
166+
console.log(output)
167+
168+
} finally {
169+
await tmux(['kill-session', '-t', session]).catch(() => {})
170+
}
171+
}
172+
```
173+
174+
## Debugging Tips
175+
176+
### Attach to a Session
177+
178+
To debug interactively, attach to the tmux session:
179+
180+
```bash
181+
tmux attach -t codebuff-test
182+
```
183+
184+
### Check Session Exists
185+
186+
```bash
187+
tmux has-session -t codebuff-test 2>/dev/null && echo "exists" || echo "not found"
188+
```
189+
190+
### List All Sessions
191+
192+
```bash
193+
tmux list-sessions
194+
```
195+
196+
### View Session Without Attaching
197+
198+
```bash
199+
# Capture current pane content
200+
tmux capture-pane -t codebuff-test -p
201+
202+
# Capture with ANSI colors preserved
203+
tmux capture-pane -t codebuff-test -p -e
204+
```
205+
206+
## Troubleshooting
207+
208+
### Input Not Appearing
209+
210+
1. **Forgot bracketed paste**: Always use `$'\e[200~text\e[201~'`
211+
2. **CLI not ready**: Increase sleep time after starting the session
212+
3. **Wrong session name**: Verify with `tmux list-sessions`
213+
214+
### Characters Garbled
215+
216+
1. **Not using `-l` flag in TypeScript**: Use `send-keys -l` for literal strings
217+
2. **Shell escaping issues**: Use `$'...'` syntax in bash for escape sequences
218+
219+
### Session Cleanup Issues
220+
221+
Always use `2>/dev/null` when killing sessions to suppress errors if already closed:
222+
223+
```bash
224+
tmux kill-session -t "$SESSION" 2>/dev/null || true
225+
```
226+
227+
## Integration with Bun Tests
228+
229+
The `.bin/bun` wrapper automatically detects tmux requirements for files matching:
230+
- `*integration*.test.ts`
231+
- `*e2e*.test.ts`
232+
233+
Name your test files accordingly to get automatic tmux availability checking.

web/src/content/advanced/troubleshooting.mdx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,44 @@ It means you have a directory named `.codebuff-SOMEHASH` in your node_modules di
114114
rm -rf /some/path/to/node_modules/.codebuff-SOMEHASH
115115
```
116116

117+
### Automating Codebuff with tmux
118+
119+
If you're trying to automate Codebuff using tmux (e.g., for scripting or testing), standard `tmux send-keys` won't work correctly. Characters will be dropped or garbled.
120+
121+
**The Problem:**
122+
```bash
123+
# ❌ Doesn't work - characters get dropped
124+
tmux send-keys -t codebuff "hello world"
125+
tmux send-keys -t codebuff Enter
126+
# Result: Only "d" appears in the input!
127+
```
128+
129+
**The Solution:**
130+
131+
Use bracketed paste mode by wrapping your input in escape sequences:
132+
133+
```bash
134+
# ✅ Works correctly
135+
tmux send-keys -t codebuff $'\e[200~hello world\e[201~'
136+
tmux send-keys -t codebuff Enter
137+
# Result: "hello world" appears correctly!
138+
```
139+
140+
This tells the terminal that the input is a paste operation, which Codebuff handles atomically.
141+
142+
**Helper script:**
143+
```bash
144+
send_to_codebuff() {
145+
local session="$1"
146+
local text="$2"
147+
tmux send-keys -t "$session" $'\e[200~'"$text"$'\e[201~'
148+
}
149+
150+
# Usage:
151+
send_to_codebuff my-session "fix the bug in main.ts"
152+
tmux send-keys -t my-session Enter
153+
```
154+
117155
### Need More Help?
118156

119157
If you're still experiencing issues:

0 commit comments

Comments
 (0)