From b5fafb603b907536d7125c04df38fed12bf443c6 Mon Sep 17 00:00:00 2001 From: brandonkachen Date: Thu, 8 Jan 2026 18:25:58 -0800 Subject: [PATCH 01/14] Add CLI tmux tester agent with helper scripts - Create .agents/cli-tmux-tester.ts for testing CLI functionality via tmux - Add scripts/tmux/ with helper scripts (start, send, capture, stop, cli) - Include automatic session logging to debug/tmux-sessions/ - Add structured output schema for reporting test results and script issues - Include commands-log.txt for tracking inputs sent to CLI --- .agents/cli-tmux-tester.ts | 258 +++++++++++++++++++++++++++++++++++ scripts/tmux/README.md | 222 ++++++++++++++++++++++++++++++ scripts/tmux/tmux-capture.sh | 185 +++++++++++++++++++++++++ scripts/tmux/tmux-cli.sh | 120 ++++++++++++++++ scripts/tmux/tmux-send.sh | 153 +++++++++++++++++++++ scripts/tmux/tmux-start.sh | 125 +++++++++++++++++ scripts/tmux/tmux-stop.sh | 137 +++++++++++++++++++ 7 files changed, 1200 insertions(+) create mode 100644 .agents/cli-tmux-tester.ts create mode 100644 scripts/tmux/README.md create mode 100755 scripts/tmux/tmux-capture.sh create mode 100755 scripts/tmux/tmux-cli.sh create mode 100755 scripts/tmux/tmux-send.sh create mode 100755 scripts/tmux/tmux-start.sh create mode 100755 scripts/tmux/tmux-stop.sh diff --git a/.agents/cli-tmux-tester.ts b/.agents/cli-tmux-tester.ts new file mode 100644 index 000000000..4650d991d --- /dev/null +++ b/.agents/cli-tmux-tester.ts @@ -0,0 +1,258 @@ +import type { AgentDefinition } from './types/agent-definition' + +const definition: AgentDefinition = { + id: 'cli-tmux-tester', + displayName: 'CLI Tmux Tester', + model: 'anthropic/claude-opus-4.5', + + spawnerPrompt: `Expert at testing Codebuff CLI functionality using tmux. + +**What it does:** Spawns tmux sessions, sends input to the CLI, captures output, and validates behavior. + +**What you get back (structured output):** +- \`overallStatus\`: "success" | "failure" | "partial" +- \`summary\`: What was tested and the outcome +- \`testResults\`: Array of {testName, passed, details, capturedOutput} +- \`scriptIssues\`: Array of {script, issue, errorOutput, suggestedFix} - **YOU should fix these!** +- \`captures\`: Array of {path, label} - file paths to terminal captures you can read + +**Paper trail:** Session logs are saved to \`debug/tmux-sessions/{session}/\`. Use \`read_files\` to view them. + +**Your responsibilities as the parent agent:** +1. If \`scriptIssues\` is not empty, fix the scripts in \`scripts/tmux/\` based on the suggested fixes +2. Use \`read_files\` on the capture paths to see what the CLI displayed +3. Re-run the test after fixing any script issues`, + + inputSchema: { + prompt: { + type: 'string', + description: + 'Description of what CLI functionality to test (e.g., "test that the help command displays correctly", "verify authentication flow works")', + }, + }, + + outputMode: 'structured_output', + outputSchema: { + type: 'object', + properties: { + overallStatus: { + type: 'string', + enum: ['success', 'failure', 'partial'], + description: 'Overall test outcome', + }, + summary: { + type: 'string', + description: 'Brief summary of what was tested and the outcome', + }, + testResults: { + type: 'array', + items: { + type: 'object', + properties: { + testName: { type: 'string', description: 'Name/description of the test' }, + passed: { type: 'boolean', description: 'Whether the test passed' }, + details: { type: 'string', description: 'Details about what happened' }, + capturedOutput: { type: 'string', description: 'Relevant output captured from the CLI' }, + }, + required: ['testName', 'passed'], + }, + description: 'Array of individual test results', + }, + scriptIssues: { + type: 'array', + items: { + type: 'object', + properties: { + script: { + type: 'string', + description: 'Which script had the issue (e.g., "tmux-start.sh", "tmux-send.sh")', + }, + issue: { + type: 'string', + description: 'What went wrong when using the script', + }, + errorOutput: { + type: 'string', + description: 'The actual error message or unexpected output', + }, + suggestedFix: { + type: 'string', + description: 'Suggested fix or improvement for the parent agent to implement', + }, + }, + required: ['script', 'issue', 'suggestedFix'], + }, + description: 'Issues encountered with the helper scripts that the parent agent should fix', + }, + captures: { + type: 'array', + items: { + type: 'object', + properties: { + path: { + type: 'string', + description: 'Path to the capture file (relative to project root)', + }, + label: { + type: 'string', + description: 'What this capture shows (e.g., "initial-cli-state", "after-help-command")', + }, + timestamp: { + type: 'string', + description: 'When the capture was taken', + }, + }, + required: ['path', 'label'], + }, + description: 'Paths to saved terminal captures for debugging - check debug/tmux-sessions/{session}/', + }, + }, + required: ['overallStatus', 'summary', 'testResults', 'scriptIssues', 'captures'], + }, + includeMessageHistory: false, + + toolNames: ['run_terminal_command', 'read_files', 'code_search', 'set_output'], + + systemPrompt: `You are an expert at testing the Codebuff CLI using tmux. You have access to helper scripts that handle the complexities of tmux communication with the CLI. + +## Helper Scripts + +Use these scripts in \`scripts/tmux/\` for reliable CLI testing: + +### Unified Script (Recommended) + +\`\`\`bash +# Start a test session (returns session name) +SESSION=$(./scripts/tmux/tmux-cli.sh start) + +# Send input to the CLI +./scripts/tmux/tmux-cli.sh send "$SESSION" "/help" + +# Capture output (optionally wait first) +./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 3 + +# Stop the session when done +./scripts/tmux/tmux-cli.sh stop "$SESSION" + +# Stop all test sessions +./scripts/tmux/tmux-cli.sh stop --all +\`\`\` + +### Individual Scripts (More Options) + +\`\`\`bash +# Start with custom settings +./scripts/tmux/tmux-start.sh --name my-test --width 160 --height 40 + +# Send text (auto-presses Enter) +./scripts/tmux/tmux-send.sh my-test "your prompt here" + +# Send without pressing Enter +./scripts/tmux/tmux-send.sh my-test "partial" --no-enter + +# Send special keys +./scripts/tmux/tmux-send.sh my-test --key Escape +./scripts/tmux/tmux-send.sh my-test --key C-c + +# Capture with colors +./scripts/tmux/tmux-capture.sh my-test --colors + +# Save capture to file +./scripts/tmux/tmux-capture.sh my-test -o output.txt +\`\`\` + +## Why These Scripts? + +The scripts handle **bracketed paste mode** automatically. Standard \`tmux send-keys\` drops characters with the Codebuff CLI due to how OpenTUI processes keyboard input. The helper scripts wrap input in escape sequences (\`\\e[200~...\\e[201~\`) so you don't have to. + +## Typical Test Workflow + +\`\`\`bash +# 1. Start a session +SESSION=$(./scripts/tmux/tmux-cli.sh start) +echo "Testing in session: $SESSION" + +# 2. Verify CLI started +./scripts/tmux/tmux-cli.sh capture "$SESSION" + +# 3. Run your test +./scripts/tmux/tmux-cli.sh send "$SESSION" "/help" +sleep 2 +./scripts/tmux/tmux-cli.sh capture "$SESSION" + +# 4. Clean up +./scripts/tmux/tmux-cli.sh stop "$SESSION" +\`\`\` + +## Session Logs (Paper Trail) + +Captures are **automatically saved** to \`debug/tmux-sessions/{session-name}/\` whenever you capture output. + +\`\`\`bash +# Capture with a descriptive label (recommended) +./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "after-help-command" --wait 2 + +# Capture saved to: debug/tmux-sessions/{session}/capture-{timestamp}-after-help-command.txt +\`\`\` + +The capture path is printed to stderr. Both you and the parent agent can read these files to see exactly what the CLI displayed. + +## Debugging Tips + +- **Attach interactively**: \`tmux attach -t SESSION_NAME\` +- **List sessions**: \`./scripts/tmux/tmux-cli.sh list\` +- **View session logs**: \`ls debug/tmux-sessions/{session-name}/\` +- **Get help**: \`./scripts/tmux/tmux-cli.sh help\` or \`./scripts/tmux/tmux-start.sh --help\``, + + instructionsPrompt: `Instructions: + +1. **Use the helper scripts** in \`scripts/tmux/\` - they handle bracketed paste mode automatically + +2. **Start a test session**: + \`\`\`bash + SESSION=$(./scripts/tmux/tmux-cli.sh start) + \`\`\` + +3. **Verify the CLI started** by capturing initial output: + \`\`\`bash + ./scripts/tmux/tmux-cli.sh capture "$SESSION" + \`\`\` + +4. **Send commands** and capture responses: + \`\`\`bash + ./scripts/tmux/tmux-cli.sh send "$SESSION" "your command here" + ./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 3 + \`\`\` + +5. **Always clean up** when done: + \`\`\`bash + ./scripts/tmux/tmux-cli.sh stop "$SESSION" + \`\`\` + +6. **Use labels when capturing** to create a clear paper trail: + \`\`\`bash + ./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "initial-state" + ./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "after-help-command" --wait 2 + \`\`\` + +7. **Report results using set_output** - You MUST call set_output with structured results: + - \`overallStatus\`: "success", "failure", or "partial" + - \`summary\`: Brief description of what was tested + - \`testResults\`: Array of test outcomes with testName, passed (boolean), details, capturedOutput + - \`scriptIssues\`: Array of any problems with the helper scripts (IMPORTANT for the parent agent!) + - \`captures\`: Array of capture paths with labels (e.g., {path: "debug/tmux-sessions/cli-test-123/capture-...", label: "after-help"}) + +8. **If a helper script doesn't work correctly**, report it in \`scriptIssues\` with: + - \`script\`: Which script failed (e.g., "tmux-send.sh") + - \`issue\`: What went wrong + - \`errorOutput\`: The actual error message + - \`suggestedFix\`: How the parent agent should fix the script + + The parent agent CAN edit the scripts - you cannot. Your job is to identify issues clearly. + +9. **Always include captures** in your output so the parent agent can see what you saw. + +For advanced options, run \`./scripts/tmux/tmux-cli.sh help\` or check individual scripts with \`--help\`.`, +} + +export default definition diff --git a/scripts/tmux/README.md b/scripts/tmux/README.md new file mode 100644 index 000000000..4210ce236 --- /dev/null +++ b/scripts/tmux/README.md @@ -0,0 +1,222 @@ +# tmux CLI Testing Scripts + +Helper scripts for testing the Codebuff CLI using tmux. These scripts abstract away the complexity of tmux communication, particularly the **bracketed paste mode** requirement. + +## Features + +- **Automatic bracketed paste mode** - Input is wrapped in escape sequences so characters don't get dropped +- **Automatic session logs** - Every capture is saved to `debug/tmux-sessions/{session}/` for debugging +- **Paper trail** - Both the subagent and parent agent can review what the CLI displayed + +## Why These Scripts? + +The Codebuff CLI uses OpenTUI for rendering, which processes keyboard input character-by-character. When tmux sends characters rapidly via `send-keys`, they get dropped or garbled. These scripts automatically wrap input in bracketed paste escape sequences (`\e[200~...\e[201~`), which tells the terminal to process the input atomically. + +**Without these scripts:** +```bash +# ❌ Characters get dropped! +tmux send-keys -t session "hello world" +# Result: Only "d" appears in the input! +``` + +**With these scripts:** +```bash +# ✅ Works correctly +./scripts/tmux/tmux-send.sh session "hello world" +# Result: "hello world" appears correctly +``` + +## Quick Start + +```bash +# Start a test session +SESSION=$(./scripts/tmux/tmux-cli.sh start) +echo "Started session: $SESSION" + +# Send a command +./scripts/tmux/tmux-cli.sh send "$SESSION" "/help" + +# Wait and capture output +./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 2 + +# Clean up +./scripts/tmux/tmux-cli.sh stop "$SESSION" +``` + +## Scripts + +### `tmux-cli.sh` (Unified Interface) + +The main entry point with subcommands: + +```bash +./scripts/tmux/tmux-cli.sh start # Start a session +./scripts/tmux/tmux-cli.sh send # Send input +./scripts/tmux/tmux-cli.sh capture # Capture output +./scripts/tmux/tmux-cli.sh stop # Stop a session +./scripts/tmux/tmux-cli.sh list # List sessions +./scripts/tmux/tmux-cli.sh help # Show help +``` + +### `tmux-start.sh` + +Start a new tmux session with the CLI. + +```bash +# Default settings +./scripts/tmux/tmux-start.sh +# Output: cli-test-1234567890 + +# Custom session name +./scripts/tmux/tmux-start.sh --name my-test + +# Custom dimensions +./scripts/tmux/tmux-start.sh -w 160 -h 40 + +# Custom wait time for CLI initialization +./scripts/tmux/tmux-start.sh --wait 6 +``` + +### `tmux-send.sh` + +Send input to a running session. + +```bash +# Send text (auto-presses Enter) +./scripts/tmux/tmux-send.sh SESSION "your prompt" + +# Send without pressing Enter +./scripts/tmux/tmux-send.sh SESSION "partial" --no-enter + +# Send special keys +./scripts/tmux/tmux-send.sh SESSION --key Escape +./scripts/tmux/tmux-send.sh SESSION --key C-c +./scripts/tmux/tmux-send.sh SESSION --key Enter +``` + +### `tmux-capture.sh` + +Capture output from a session. **Automatically saves captures** to `debug/tmux-sessions/{session}/`. + +```bash +# Basic capture (auto-saves to session logs) +./scripts/tmux/tmux-capture.sh SESSION + +# Capture with a descriptive label (recommended) +./scripts/tmux/tmux-capture.sh SESSION --label "after-help-command" +# Saves to: debug/tmux-sessions/SESSION/capture-{timestamp}-after-help-command.txt + +# Wait before capturing +./scripts/tmux/tmux-capture.sh SESSION --wait 3 + +# Capture without auto-saving to session logs +./scripts/tmux/tmux-capture.sh SESSION --no-save + +# Preserve colors +./scripts/tmux/tmux-capture.sh SESSION --colors + +# Save to specific file (disables auto-save) +./scripts/tmux/tmux-capture.sh SESSION -o output.txt +``` + +### `tmux-stop.sh` + +Stop/kill sessions. + +```bash +# Stop specific session +./scripts/tmux/tmux-stop.sh SESSION + +# Stop all test sessions +./scripts/tmux/tmux-stop.sh --all + +# List sessions first, then stop +./scripts/tmux/tmux-stop.sh --list SESSION +``` + +## Example: Full Test Workflow + +```bash +#!/bin/bash + +# Start session +SESSION=$(./scripts/tmux/tmux-cli.sh start --name auth-test) +echo "Testing authentication in: $SESSION" + +# Verify CLI started (capture auto-saved with label) +./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "initial-state" + +# Send /status command +./scripts/tmux/tmux-cli.sh send "$SESSION" "/status" +./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 2 --label "after-status" + +# Test a prompt +./scripts/tmux/tmux-cli.sh send "$SESSION" "hello world" +./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 5 --label "after-prompt" + +# Clean up +./scripts/tmux/tmux-cli.sh stop "$SESSION" +echo "Test complete!" + +# View all session logs +ls -la debug/tmux-sessions/$SESSION/ +``` + +## Session Logs Directory Structure + +``` +debug/tmux-sessions/ +└── cli-test-1234567890/ + ├── session-info.txt # Session metadata (start time, dimensions) + ├── commands-log.txt # Log of all commands sent to the CLI + ├── capture-20250101-120000-initial-state.txt + ├── capture-20250101-120005-after-status.txt + └── capture-20250101-120010-after-prompt.txt +``` + +### Commands Log + +The `commands-log.txt` file records every input sent to the CLI: + +``` +[2025-01-01 12:00:05] SEND TEXT (+ Enter): /help +[2025-01-01 12:00:10] SEND TEXT (+ Enter): hello world +[2025-01-01 12:00:15] SEND KEY: Escape +[2025-01-01 12:00:20] SEND TEXT (no Enter): partial input +``` + +This provides a **paper trail** that both the testing agent and parent agent can review to understand what happened during testing. + +## Debugging + +### Attach to a Session Interactively + +```bash +tmux attach -t SESSION_NAME +# Detach with Ctrl+B, D +``` + +### List All Sessions + +```bash +./scripts/tmux/tmux-cli.sh list +# or +tmux list-sessions +``` + +### Check If Session Exists + +```bash +tmux has-session -t SESSION_NAME && echo "exists" || echo "not found" +``` + +## Prerequisites + +- **tmux** must be installed: + - macOS: `brew install tmux` + - Ubuntu: `sudo apt-get install tmux` + - Arch: `sudo pacman -S tmux` + +## Used By + +These scripts are used by the `@cli-tmux-tester` agent (`.agents/cli-tmux-tester.ts`) to automate CLI testing. diff --git a/scripts/tmux/tmux-capture.sh b/scripts/tmux/tmux-capture.sh new file mode 100755 index 000000000..8e0e37f37 --- /dev/null +++ b/scripts/tmux/tmux-capture.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env bash + +####################################################################### +# tmux-capture.sh - Capture output from a tmux session +####################################################################### +# +# DESCRIPTION: +# Captures the current terminal output from a tmux session. +# Automatically saves captures to debug/tmux-sessions/{session}/ +# Useful for verifying CLI behavior and debugging. +# +# USAGE: +# ./scripts/tmux/tmux-capture.sh SESSION_NAME [OPTIONS] +# +# ARGUMENTS: +# SESSION_NAME Name of the tmux session +# +# OPTIONS: +# -c, --colors Preserve ANSI color codes in output +# -s, --start LINE Start capture from this line (default: -) +# -e, --end LINE End capture at this line (default: -) +# -o, --output FILE Write output to file instead of stdout +# --wait SECONDS Wait this many seconds before capturing (default: 0) +# --no-save Don't auto-save to session logs directory +# -l, --label LABEL Add a label to the capture filename +# --help Show this help message +# +# SESSION LOGS: +# By default, captures are automatically saved to: +# debug/tmux-sessions/{session-name}/capture-{timestamp}.txt +# +# The capture path is printed to stderr so you can capture it: +# CAPTURE_PATH=$(./scripts/tmux/tmux-capture.sh session 2>&1 >/dev/null) +# +# EXAMPLES: +# # Capture current output (auto-saves to session logs) +# ./scripts/tmux/tmux-capture.sh cli-test-123 +# +# # Capture with a label for the log file +# ./scripts/tmux/tmux-capture.sh cli-test-123 --label "after-help-command" +# +# # Capture with colors preserved +# ./scripts/tmux/tmux-capture.sh cli-test-123 --colors +# +# # Wait 2 seconds then capture (for async responses) +# ./scripts/tmux/tmux-capture.sh cli-test-123 --wait 2 +# +# # Save to specific file (disables auto-save to session logs) +# ./scripts/tmux/tmux-capture.sh cli-test-123 -o output.txt +# +# # Capture without auto-saving to session logs +# ./scripts/tmux/tmux-capture.sh cli-test-123 --no-save +# +# EXIT CODES: +# 0 - Success (output printed to stdout or file) +# 1 - Error (session not found) +# +####################################################################### + +set -e + +# Defaults +COLORS=false +START_LINE="-" +END_LINE="-" +OUTPUT_FILE="" +WAIT_SECONDS=0 +AUTO_SAVE=true +LABEL="" + +# Check minimum arguments +if [[ $# -lt 1 ]]; then + echo "Usage: $0 SESSION_NAME [OPTIONS]" >&2 + echo "Run with --help for more information" >&2 + exit 1 +fi + +# First argument is session name +SESSION_NAME="$1" +shift + +# Handle --help first +if [[ "$SESSION_NAME" == "--help" ]]; then + head -n 60 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 +fi + +# Parse remaining arguments +while [[ $# -gt 0 ]]; do + case $1 in + -c|--colors) + COLORS=true + shift + ;; + -s|--start) + START_LINE="$2" + shift 2 + ;; + -e|--end) + END_LINE="$2" + shift 2 + ;; + -o|--output) + OUTPUT_FILE="$2" + AUTO_SAVE=false # Manual output disables auto-save + shift 2 + ;; + --wait) + WAIT_SECONDS="$2" + shift 2 + ;; + --no-save) + AUTO_SAVE=false + shift + ;; + -l|--label) + LABEL="$2" + shift 2 + ;; + --help) + head -n 60 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# Verify session exists +if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then + echo "❌ Session '$SESSION_NAME' not found" >&2 + echo " Run: tmux list-sessions" >&2 + exit 1 +fi + +# Wait if requested +if [[ "$WAIT_SECONDS" -gt 0 ]]; then + sleep "$WAIT_SECONDS" +fi + +# Build capture command +CAPTURE_CMD="tmux capture-pane -t \"$SESSION_NAME\" -p" + +if [[ "$COLORS" == true ]]; then + CAPTURE_CMD="$CAPTURE_CMD -e" +fi + +if [[ "$START_LINE" != "-" ]]; then + CAPTURE_CMD="$CAPTURE_CMD -S $START_LINE" +fi + +if [[ "$END_LINE" != "-" ]]; then + CAPTURE_CMD="$CAPTURE_CMD -E $END_LINE" +fi + +# Get project root for session logs directory +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +SESSION_DIR="$PROJECT_ROOT/debug/tmux-sessions/$SESSION_NAME" + +# Execute capture +if [[ -n "$OUTPUT_FILE" ]]; then + eval "$CAPTURE_CMD" > "$OUTPUT_FILE" +else + # Capture to variable first + CAPTURED_OUTPUT=$(eval "$CAPTURE_CMD") + + # Auto-save capture if enabled + if [[ "$AUTO_SAVE" == true ]]; then + mkdir -p "$SESSION_DIR" + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + if [[ -n "$LABEL" ]]; then + CAPTURE_FILE="$SESSION_DIR/capture-${TIMESTAMP}-${LABEL}.txt" + else + CAPTURE_FILE="$SESSION_DIR/capture-${TIMESTAMP}.txt" + fi + echo "$CAPTURED_OUTPUT" > "$CAPTURE_FILE" + # Print capture path to stderr so it can be captured separately + echo "$CAPTURE_FILE" >&2 + fi + + # Output to stdout + echo "$CAPTURED_OUTPUT" +fi diff --git a/scripts/tmux/tmux-cli.sh b/scripts/tmux/tmux-cli.sh new file mode 100755 index 000000000..9f201ca49 --- /dev/null +++ b/scripts/tmux/tmux-cli.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +####################################################################### +# tmux-cli.sh - Unified CLI testing helper using tmux +####################################################################### +# +# DESCRIPTION: +# A unified script for testing the Codebuff CLI using tmux. +# Provides subcommands for starting, sending input, capturing output, +# and stopping test sessions. +# +# USAGE: +# ./scripts/tmux/tmux-cli.sh [arguments] +# +# COMMANDS: +# start Start a new CLI test session +# send Send input to a session (uses bracketed paste) +# capture Capture output from a session +# stop Stop a session +# list List all active tmux sessions +# help Show this help message +# +# QUICK START: +# # Start a test session +# SESSION=$(./scripts/tmux/tmux-cli.sh start) +# echo "Started session: $SESSION" +# +# # Send a command +# ./scripts/tmux/tmux-cli.sh send "$SESSION" "/help" +# +# # Capture output with a label (auto-saves screenshot) +# ./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "after-help" --wait 2 +# +# # Clean up +# ./scripts/tmux/tmux-cli.sh stop "$SESSION" +# +# SESSION LOGS: +# Captures are automatically saved to debug/tmux-sessions/{session}/ +# Use --label to add descriptive names to capture files. +# +# EXAMPLES: +# # Full test workflow +# SESSION=$(./scripts/tmux/tmux-cli.sh start --name my-test) +# ./scripts/tmux/tmux-cli.sh send "$SESSION" "hello world" +# ./scripts/tmux/tmux-cli.sh capture "$SESSION" --wait 3 +# ./scripts/tmux/tmux-cli.sh stop "$SESSION" +# +# # Stop all test sessions +# ./scripts/tmux/tmux-cli.sh stop --all +# +# # Get help for a specific command +# ./scripts/tmux/tmux-cli.sh start --help +# +# INDIVIDUAL SCRIPTS: +# For more options, use the individual scripts directly: +# - scripts/tmux/tmux-start.sh +# - scripts/tmux/tmux-send.sh +# - scripts/tmux/tmux-capture.sh +# - scripts/tmux/tmux-stop.sh +# +####################################################################### + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +show_help() { + head -n 58 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' +} + +show_short_help() { + echo "Usage: $0 [arguments]" + echo "" + echo "Commands:" + echo " start Start a new CLI test session" + echo " send Send input to a session" + echo " capture Capture output from a session" + echo " stop Stop a session" + echo " list List all active tmux sessions" + echo " help Show full help message" + echo "" + echo "Run '$0 --help' for command-specific help" +} + +# Check for command +if [[ $# -lt 1 ]]; then + show_short_help + exit 1 +fi + +COMMAND="$1" +shift + +case "$COMMAND" in + start) + exec "$SCRIPT_DIR/tmux-start.sh" "$@" + ;; + send) + exec "$SCRIPT_DIR/tmux-send.sh" "$@" + ;; + capture) + exec "$SCRIPT_DIR/tmux-capture.sh" "$@" + ;; + stop) + exec "$SCRIPT_DIR/tmux-stop.sh" "$@" + ;; + list) + echo "Active tmux sessions:" + tmux list-sessions 2>/dev/null || echo " (none)" + ;; + help|--help|-h) + show_help + ;; + *) + echo "Unknown command: $COMMAND" >&2 + echo "" + show_short_help + exit 1 + ;; +esac diff --git a/scripts/tmux/tmux-send.sh b/scripts/tmux/tmux-send.sh new file mode 100755 index 000000000..a892ac74c --- /dev/null +++ b/scripts/tmux/tmux-send.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash + +####################################################################### +# tmux-send.sh - Send input to the Codebuff CLI in a tmux session +####################################################################### +# +# DESCRIPTION: +# Sends text input to a tmux session running the Codebuff CLI. +# Uses BRACKETED PASTE MODE which is REQUIRED for the CLI to receive +# input correctly. Standard tmux send-keys drops characters! +# +# IMPORTANT: +# This script handles the bracketed paste escape sequences automatically. +# You do NOT need to add escape sequences to your input. +# +# USAGE: +# ./scripts/tmux/tmux-send.sh SESSION_NAME "your text here" +# ./scripts/tmux/tmux-send.sh SESSION_NAME --key KEY +# +# ARGUMENTS: +# SESSION_NAME Name of the tmux session +# TEXT Text to send (will be wrapped in bracketed paste) +# +# OPTIONS: +# --key KEY Send a special key instead of text +# Supported: Enter, Escape, Up, Down, Left, Right, +# C-c, C-u, C-d, Tab +# --no-enter Don't automatically press Enter after text +# --help Show this help message +# +# EXAMPLES: +# # Send a command to the CLI +# ./scripts/tmux/tmux-send.sh cli-test-123 "/help" +# +# # Send text without pressing Enter +# ./scripts/tmux/tmux-send.sh cli-test-123 "partial text" --no-enter +# +# # Send a special key +# ./scripts/tmux/tmux-send.sh cli-test-123 --key Escape +# +# # Send Ctrl+C to interrupt +# ./scripts/tmux/tmux-send.sh cli-test-123 --key C-c +# +# WHY BRACKETED PASTE? +# The Codebuff CLI uses OpenTUI for rendering, which processes keyboard +# input character-by-character. When tmux sends characters rapidly, +# they get dropped or garbled. Bracketed paste mode (\e[200~...\e[201~) +# tells the terminal to treat the input as a paste operation, which is +# processed atomically. +# +# EXIT CODES: +# 0 - Success +# 1 - Error (missing arguments, session not found) +# +####################################################################### + +set -e + +# Get project root for logging +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +# Defaults +AUTO_ENTER=true +SPECIAL_KEY="" + +# Check minimum arguments +if [[ $# -lt 1 ]]; then + echo "Usage: $0 SESSION_NAME \"text\" [OPTIONS]" >&2 + echo " $0 SESSION_NAME --key KEY" >&2 + echo "Run with --help for more information" >&2 + exit 1 +fi + +# First argument is always session name +SESSION_NAME="$1" +shift + +# Handle --help first +if [[ "$SESSION_NAME" == "--help" ]]; then + head -n 55 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 +fi + +# Parse remaining arguments +TEXT="" +while [[ $# -gt 0 ]]; do + case $1 in + --key) + SPECIAL_KEY="$2" + shift 2 + ;; + --no-enter) + AUTO_ENTER=false + shift + ;; + --help) + head -n 55 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 + ;; + *) + TEXT="$1" + shift + ;; + esac +done + +# Verify session exists +if ! tmux has-session -t "$SESSION_NAME" 2>/dev/null; then + echo "❌ Session '$SESSION_NAME' not found" >&2 + echo " Run: tmux list-sessions" >&2 + exit 1 +fi + +# Send special key if specified +if [[ -n "$SPECIAL_KEY" ]]; then + tmux send-keys -t "$SESSION_NAME" "$SPECIAL_KEY" + + # Log the special key send + SESSION_DIR="$PROJECT_ROOT/debug/tmux-sessions/$SESSION_NAME" + if [[ -d "$SESSION_DIR" ]]; then + TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') + echo "[$TIMESTAMP] SEND KEY: $SPECIAL_KEY" >> "$SESSION_DIR/commands-log.txt" + fi + + exit 0 +fi + +# Check if text was provided +if [[ -z "$TEXT" ]]; then + echo "❌ No text or --key specified" >&2 + exit 1 +fi + +# Send text using bracketed paste mode +# \e[200~ = start bracketed paste +# \e[201~ = end bracketed paste +tmux send-keys -t "$SESSION_NAME" $'\e[200~'"$TEXT"$'\e[201~' + +# Optionally press Enter +if [[ "$AUTO_ENTER" == true ]]; then + tmux send-keys -t "$SESSION_NAME" Enter +fi + +# Log the text send +SESSION_DIR="$PROJECT_ROOT/debug/tmux-sessions/$SESSION_NAME" +if [[ -d "$SESSION_DIR" ]]; then + TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') + if [[ "$AUTO_ENTER" == true ]]; then + echo "[$TIMESTAMP] SEND TEXT (+ Enter): $TEXT" >> "$SESSION_DIR/commands-log.txt" + else + echo "[$TIMESTAMP] SEND TEXT (no Enter): $TEXT" >> "$SESSION_DIR/commands-log.txt" + fi +fi diff --git a/scripts/tmux/tmux-start.sh b/scripts/tmux/tmux-start.sh new file mode 100755 index 000000000..b46a121ad --- /dev/null +++ b/scripts/tmux/tmux-start.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +####################################################################### +# tmux-start.sh - Start a tmux session with the Codebuff CLI +####################################################################### +# +# DESCRIPTION: +# Creates a new detached tmux session running the Codebuff CLI. +# Returns the session name for use with other tmux helper scripts. +# Also creates a screenshots directory for capturing terminal output. +# +# USAGE: +# ./scripts/tmux/tmux-start.sh [OPTIONS] +# +# OPTIONS: +# -n, --name NAME Session name (default: cli-test-) +# -w, --width WIDTH Terminal width (default: 120) +# -h, --height HEIGHT Terminal height (default: 30) +# --wait SECONDS Seconds to wait for CLI to initialize (default: 4) +# --help Show this help message +# +# SESSION LOGS: +# Session logs are automatically saved to: +# debug/tmux-sessions/{session-name}/ +# +# Use tmux-capture.sh to save timestamped captures to this directory. +# +# EXAMPLES: +# # Start with default settings +# ./scripts/tmux/tmux-start.sh +# # Output: cli-test-1234567890 +# +# # Start with custom session name +# ./scripts/tmux/tmux-start.sh --name my-test-session +# +# # Start with custom dimensions +# ./scripts/tmux/tmux-start.sh -w 160 -h 40 +# +# EXIT CODES: +# 0 - Success (session name printed to stdout) +# 1 - Error (tmux not found or session creation failed) +# +####################################################################### + +set -e + +# Defaults +SESSION_NAME="" +WIDTH=120 +HEIGHT=30 +WAIT_SECONDS=4 + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + -n|--name) + SESSION_NAME="$2" + shift 2 + ;; + -w|--width) + WIDTH="$2" + shift 2 + ;; + -h|--height) + HEIGHT="$2" + shift 2 + ;; + --wait) + WAIT_SECONDS="$2" + shift 2 + ;; + --help) + head -n 40 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# Generate session name if not provided +if [[ -z "$SESSION_NAME" ]]; then + SESSION_NAME="cli-test-$(date +%s)" +fi + +# Check if tmux is available +if ! command -v tmux &> /dev/null; then + echo "❌ tmux not found" >&2 + echo "" >&2 + echo "📦 Installation:" >&2 + echo " macOS: brew install tmux" >&2 + echo " Ubuntu: sudo apt-get install tmux" >&2 + echo " Arch: sudo pacman -S tmux" >&2 + exit 1 +fi + +# Get project root (assuming script is in scripts/tmux/) +PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +# Create tmux session running CLI +if ! tmux new-session -d -s "$SESSION_NAME" \ + -x "$WIDTH" -y "$HEIGHT" \ + "cd '$PROJECT_ROOT' && bun --cwd=cli run dev 2>&1" 2>/dev/null; then + echo "❌ Failed to create tmux session" >&2 + exit 1 +fi + +# Create session logs directory +SESSION_DIR="$PROJECT_ROOT/debug/tmux-sessions/$SESSION_NAME" +mkdir -p "$SESSION_DIR" + +# Save session info to the logs directory +echo "Session: $SESSION_NAME" > "$SESSION_DIR/session-info.txt" +echo "Started: $(date)" >> "$SESSION_DIR/session-info.txt" +echo "Dimensions: ${WIDTH}x${HEIGHT}" >> "$SESSION_DIR/session-info.txt" + +# Wait for CLI to initialize +if [[ "$WAIT_SECONDS" -gt 0 ]]; then + sleep "$WAIT_SECONDS" +fi + +# Output session name for use by other scripts +echo "$SESSION_NAME" diff --git a/scripts/tmux/tmux-stop.sh b/scripts/tmux/tmux-stop.sh new file mode 100755 index 000000000..c35e6896f --- /dev/null +++ b/scripts/tmux/tmux-stop.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash + +####################################################################### +# tmux-stop.sh - Stop a tmux session +####################################################################### +# +# DESCRIPTION: +# Kills a tmux session. Use this to clean up after testing. +# Silently succeeds if session doesn't exist (idempotent). +# +# USAGE: +# ./scripts/tmux/tmux-stop.sh SESSION_NAME [OPTIONS] +# +# ARGUMENTS: +# SESSION_NAME Name of the tmux session to stop +# +# OPTIONS: +# --all Stop ALL cli-test-* sessions +# --list List all active tmux sessions first +# --help Show this help message +# +# EXAMPLES: +# # Stop a specific session +# ./scripts/tmux/tmux-stop.sh cli-test-123 +# +# # Stop all test sessions +# ./scripts/tmux/tmux-stop.sh --all +# +# # List sessions then stop one +# ./scripts/tmux/tmux-stop.sh --list cli-test-123 +# +# EXIT CODES: +# 0 - Success (session stopped or already doesn't exist) +# 1 - Error (invalid arguments) +# +####################################################################### + +set -e + +# Defaults +STOP_ALL=false +LIST_FIRST=false + +# Check minimum arguments +if [[ $# -lt 1 ]]; then + echo "Usage: $0 SESSION_NAME [OPTIONS]" >&2 + echo " $0 --all" >&2 + echo "Run with --help for more information" >&2 + exit 1 +fi + +# First argument handling +SESSION_NAME="" + +# Handle --help and --all as first arg +case "$1" in + --help) + head -n 40 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 + ;; + --all) + STOP_ALL=true + shift + ;; + --list) + LIST_FIRST=true + shift + if [[ $# -gt 0 && "$1" != "--"* ]]; then + SESSION_NAME="$1" + shift + fi + ;; + *) + SESSION_NAME="$1" + shift + ;; +esac + +# Parse remaining arguments +while [[ $# -gt 0 ]]; do + case $1 in + --all) + STOP_ALL=true + shift + ;; + --list) + LIST_FIRST=true + shift + ;; + --help) + head -n 40 "$0" | tail -n +2 | sed 's/^# //' | sed 's/^#//' + exit 0 + ;; + *) + if [[ -z "$SESSION_NAME" ]]; then + SESSION_NAME="$1" + fi + shift + ;; + esac +done + +# List sessions if requested +if [[ "$LIST_FIRST" == true ]]; then + echo "Active tmux sessions:" + tmux list-sessions 2>/dev/null || echo " (none)" + echo "" +fi + +# Stop all test sessions +if [[ "$STOP_ALL" == true ]]; then + # Get all cli-test-* sessions + SESSIONS=$(tmux list-sessions -F '#{session_name}' 2>/dev/null | grep '^cli-test-' || true) + + if [[ -z "$SESSIONS" ]]; then + echo "No cli-test-* sessions found" + exit 0 + fi + + COUNT=0 + while IFS= read -r session; do + tmux kill-session -t "$session" 2>/dev/null && ((COUNT++)) || true + done <<< "$SESSIONS" + + echo "Stopped $COUNT session(s)" + exit 0 +fi + +# Check if session name was provided +if [[ -z "$SESSION_NAME" ]]; then + echo "❌ No session name specified" >&2 + echo " Use --all to stop all test sessions" >&2 + exit 1 +fi + +# Stop the specific session (silently succeed if not found) +tmux kill-session -t "$SESSION_NAME" 2>/dev/null || true From 10ff4d78de9b70bbf0e09991d0f48fd6617b7cdb Mon Sep 17 00:00:00 2001 From: brandonkachen Date: Thu, 8 Jan 2026 20:14:45 -0800 Subject: [PATCH 02/14] Add YAML-based tmux session logging and OpenTUI viewer - Update tmux helper scripts to use YAML format: - session-info.yaml for session metadata - commands.yaml for command history - YAML front-matter on capture files - Create .agents/tmux-viewer/ OpenTUI package: - Interactive TUI for viewing session captures chronologically - --json flag for AI consumption (single source of truth) - --list flag to show available sessions - Timeline navigation with arrow keys - Update cli-tmux-tester.ts to reference new viewer tool - Update README.md with new YAML format documentation --- .agents/cli-tmux-tester.ts | 39 +- .agents/package.json | 3 +- .agents/tmux-viewer/README.md | 136 ++++++ .../tmux-viewer/components/session-viewer.tsx | 408 ++++++++++++++++++ .agents/tmux-viewer/components/theme.ts | 54 +++ .agents/tmux-viewer/index.tsx | 156 +++++++ .agents/tmux-viewer/package.json | 27 ++ .agents/tmux-viewer/session-loader.ts | 204 +++++++++ .agents/tmux-viewer/tsconfig.json | 9 + .agents/tmux-viewer/types.ts | 76 ++++ .agents/tsconfig.json | 2 +- bun.lock | 3 + package.json | 1 + scripts/tmux/README.md | 74 +++- scripts/tmux/tmux-capture.sh | 49 ++- scripts/tmux/tmux-send.sh | 29 +- scripts/tmux/tmux-start.sh | 14 +- 17 files changed, 1251 insertions(+), 33 deletions(-) create mode 100644 .agents/tmux-viewer/README.md create mode 100644 .agents/tmux-viewer/components/session-viewer.tsx create mode 100644 .agents/tmux-viewer/components/theme.ts create mode 100644 .agents/tmux-viewer/index.tsx create mode 100644 .agents/tmux-viewer/package.json create mode 100644 .agents/tmux-viewer/session-loader.ts create mode 100644 .agents/tmux-viewer/tsconfig.json create mode 100644 .agents/tmux-viewer/types.ts diff --git a/.agents/cli-tmux-tester.ts b/.agents/cli-tmux-tester.ts index 4650d991d..91560be89 100644 --- a/.agents/cli-tmux-tester.ts +++ b/.agents/cli-tmux-tester.ts @@ -186,17 +186,52 @@ sleep 2 ## Session Logs (Paper Trail) -Captures are **automatically saved** to \`debug/tmux-sessions/{session-name}/\` whenever you capture output. +All session data is stored in **YAML format** in \`debug/tmux-sessions/{session-name}/\`: + +- \`session-info.yaml\` - Session metadata (start time, dimensions, status) +- \`commands.yaml\` - YAML array of all commands sent with timestamps +- \`capture-{sequence}-{label}.txt\` - Captures with YAML front-matter \`\`\`bash # Capture with a descriptive label (recommended) ./scripts/tmux/tmux-cli.sh capture "$SESSION" --label "after-help-command" --wait 2 -# Capture saved to: debug/tmux-sessions/{session}/capture-{timestamp}-after-help-command.txt +# Capture saved to: debug/tmux-sessions/{session}/capture-001-after-help-command.txt +\`\`\` + +Each capture file has YAML front-matter with metadata: +\`\`\`yaml +--- +sequence: 1 +label: after-help-command +timestamp: 2025-01-01T12:00:30Z +after_command: "/help" +dimensions: + width: 120 + height: 30 +--- +[terminal content] \`\`\` The capture path is printed to stderr. Both you and the parent agent can read these files to see exactly what the CLI displayed. +## Viewing Session Data + +Use the **tmux-viewer** to inspect session data interactively or as JSON: + +\`\`\`bash +# Interactive TUI (for humans) +bun .agents/tmux-viewer/index.tsx "$SESSION" + +# JSON output (for AIs) - includes all captures, commands, and timeline +bun .agents/tmux-viewer/index.tsx "$SESSION" --json + +# List available sessions +bun .agents/tmux-viewer/index.tsx --list +\`\`\` + +The viewer parses all YAML data (session-info.yaml, commands.yaml, capture front-matter) and presents it in a unified format. + ## Debugging Tips - **Attach interactively**: \`tmux attach -t SESSION_NAME\` diff --git a/.agents/package.json b/.agents/package.json index e6dd6fc4e..b6b4bc4ac 100644 --- a/.agents/package.json +++ b/.agents/package.json @@ -6,6 +6,7 @@ "scripts": { "typecheck": "bun x tsc --noEmit -p tsconfig.json", "test": "bun test __tests__", - "test:e2e": "bun test e2e" + "test:e2e": "bun test e2e", + "view-session": "bun run tmux-viewer/index.tsx" } } diff --git a/.agents/tmux-viewer/README.md b/.agents/tmux-viewer/README.md new file mode 100644 index 000000000..d17043c70 --- /dev/null +++ b/.agents/tmux-viewer/README.md @@ -0,0 +1,136 @@ +# tmux-viewer + +Interactive TUI for viewing tmux session logs. Designed to work for **both humans and AIs**. + +## Usage + +```bash +# Interactive TUI (for humans) +bun .agents/tmux-viewer/index.tsx + +# JSON output (for AIs) +bun .agents/tmux-viewer/index.tsx --json + +# List available sessions +bun .agents/tmux-viewer/index.tsx --list + +# View most recent session (if no session specified) +bun .agents/tmux-viewer/index.tsx +``` + +Or using the npm script: + +```bash +cd .agents && bun run view-session +``` + +## Features + +### For Humans (Interactive TUI) +- **Timeline panel**: Navigate through captures with ↑↓ arrows +- **Capture panel**: View terminal output at each point in time +- **Metadata display**: Session info, dimensions, command count +- **Keyboard shortcuts**: + - `↑↓` or `jk`: Navigate captures + - `←→` or `hl`: Switch panels + - `q` or Ctrl+C: Quit + - Use the `--json` flag on the CLI entrypoint for JSON output + +### For AIs (JSON Output) +Use the `--json` flag to get structured output: + +```json +{ + "session": { + "session": "cli-test-1234567890", + "started": "2025-01-01T12:00:00Z", + "dimensions": { "width": 120, "height": 30 }, + "status": "active" + }, + "commands": [ + { "timestamp": "...", "type": "text", "input": "/help", "auto_enter": true } + ], + "captures": [ + { + "sequence": 1, + "label": "initial-state", + "timestamp": "...", + "after_command": null, + "dimensions": { "width": 120, "height": 30 }, + "path": "debug/tmux-sessions/.../capture-001-initial-state.txt", + "content": "[terminal output]" + } + ], + "timeline": [ + { "timestamp": "...", "type": "command", "data": {...} }, + { "timestamp": "...", "type": "capture", "data": {...} } + ] +} +``` + +## Data Format + +The viewer reads YAML-formatted session data from `debug/tmux-sessions/{session}/`: + +- `session-info.yaml` - Session metadata +- `commands.yaml` - Array of commands sent +- `capture-*.txt` - Capture files with YAML front-matter + +### Session Info (session-info.yaml) +```yaml +session: cli-test-1234567890 +started: 2025-01-01T12:00:00Z +started_local: Wed Jan 1 12:00:00 PST 2025 +dimensions: + width: 120 + height: 30 +status: active +``` + +### Commands (commands.yaml) +```yaml +- timestamp: 2025-01-01T12:00:05Z + type: text + input: "/help" + auto_enter: true +``` + +### Capture Files (capture-001-label.txt) +```yaml +--- +sequence: 1 +label: initial-state +timestamp: 2025-01-01T12:00:30Z +after_command: null +dimensions: + width: 120 + height: 30 +--- +[terminal content here] +``` + +## Integration with cli-tmux-tester + +The `@cli-tmux-tester` agent can use this viewer to inspect session data: + +```typescript +// In cli-tmux-tester output +{ + captures: [ + { path: "debug/tmux-sessions/cli-test-123/capture-001-initial.txt", label: "initial" } + ] +} + +// Parent agent can view the session +// bun .agents/tmux-viewer/index.tsx cli-test-123 --json +``` + +## Development + +```bash +# Typecheck +cd .agents && bun run typecheck + +# Run directly +bun .agents/tmux-viewer/index.tsx --list +``` diff --git a/.agents/tmux-viewer/components/session-viewer.tsx b/.agents/tmux-viewer/components/session-viewer.tsx new file mode 100644 index 000000000..061160178 --- /dev/null +++ b/.agents/tmux-viewer/components/session-viewer.tsx @@ -0,0 +1,408 @@ +/** + * SessionViewer - Interactive TUI for viewing tmux session data + * + * Designed to be simple and predictable for both humans and AIs: + * - Humans: navigate captures with arrow keys / vim keys + * - AIs: typically use the --json flag on the CLI entrypoint instead of the TUI + */ + +import { TextAttributes } from '@opentui/core' +import React, { useEffect, useState } from 'react' + +import { getTheme } from './theme' + +import type { SessionData, Capture } from '../types' +import type { ViewerTheme } from './theme' + +interface SessionViewerProps { + data: SessionData + onExit: () => void + /** + * Reserved for future use if we ever want a TUI hotkey to print JSON. + * For now, AIs should call the CLI with --json instead. + */ + onJsonOutput?: () => void +} + +export const SessionViewer: React.FC = ({ + data, + onExit, +}) => { + const theme = getTheme() + const captures = data.captures + + const [selectedIndex, setSelectedIndex] = useState(() => + captures.length > 0 ? 0 : -1, + ) + const [focusedPanel, setFocusedPanel] = useState<'timeline' | 'capture'>( + 'timeline', + ) + + // Keyboard input handling (q/Ctrl+C to quit, arrows + vim keys to navigate) + useEffect(() => { + const handleKey = (key: string) => { + // Quit: q or Ctrl+C + if (key === 'q' || key === '\x03') { + onExit() + return + } + + if (captures.length === 0) { + return + } + + // Up: arrow up or k + if (key === '\x1b[A' || key === 'k') { + setSelectedIndex((prev) => Math.max(0, prev - 1)) + return + } + + // Down: arrow down or j + if (key === '\x1b[B' || key === 'j') { + setSelectedIndex((prev) => + Math.min(captures.length - 1, Math.max(0, prev + 1)), + ) + return + } + + // Right: arrow right or l => focus capture panel + if (key === '\x1b[C' || key === 'l') { + setFocusedPanel('capture') + return + } + + // Left: arrow left or h => focus timeline panel + if (key === '\x1b[D' || key === 'h') { + setFocusedPanel('timeline') + } + } + + const stdin: NodeJS.ReadStream = process.stdin as any + const onData = (chunk: Buffer) => { + handleKey(chunk.toString()) + } + + stdin.setRawMode?.(true) + stdin.resume() + stdin.on('data', onData) + + return () => { + // Remove only this listener to avoid interfering with other handlers + if (typeof (stdin as any).off === 'function') { + ;(stdin as any).off('data', onData) + } else { + stdin.removeListener('data', onData as any) + } + } + }, [captures.length, onExit]) + + const selectedCapture: Capture | undefined = + selectedIndex >= 0 && selectedIndex < captures.length + ? captures[selectedIndex] + : undefined + + return ( + + {/* Header */} + + + {/* Main content area */} + + + + + + + {/* Footer / help text */} +