Skip to content

Commit 4bd3ce3

Browse files
committed
Add VHS session recording commands
1 parent c2a8251 commit 4bd3ce3

13 files changed

Lines changed: 1932 additions & 90 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ agent-tui wait "Loading complete"
7676
agent-tui kill
7777
```
7878

79+
### Session Recording (VHS)
80+
81+
```bash
82+
# Record active session (background)
83+
agent-tui sessions record
84+
85+
# Record active session to a directory
86+
agent-tui sessions record -o docs/recordings
87+
88+
# Stop recording for a specific session
89+
agent-tui --session <id> sessions record stop
90+
```
91+
7992
## CLI Reference
8093

8194
For the full CLI reference (auto-generated from clap), see `docs/cli/agent-tui.md`.
@@ -128,6 +141,8 @@ agent-tui screenshot --json
128141
| `AGENT_TUI_API_LISTEN` / `AGENT_TUI_API_ALLOW_REMOTE` / `AGENT_TUI_API_STATE` | Deprecated aliases for WS settings | - |
129142
| `AGENT_TUI_API_TOKEN` | Deprecated and ignored | - |
130143
| `AGENT_TUI_SESSION_STORE` | Session metadata log path | `~/.agent-tui/sessions.jsonl` |
144+
| `AGENT_TUI_RECORD_STATE` | Recording state file path | `~/.agent-tui/recordings.json` |
145+
| `AGENT_TUI_RECORDINGS_DIR` | Default recordings output directory | current working directory |
131146
| `AGENT_TUI_UI_URL` | External UI URL | - |
132147
| `AGENT_TUI_DETACH_KEYS` | Attach detach key sequence | `Ctrl+]` |
133148
| `AGENT_TUI_LOG` | Log file path (optional) | - |

cli/crates/agent-tui/src/app/commands.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ CONFIGURATION:
4545
AGENT_TUI_API_LISTEN / AGENT_TUI_API_ALLOW_REMOTE / AGENT_TUI_API_STATE
4646
Deprecated aliases for WS settings
4747
AGENT_TUI_SESSION_STORE Session metadata log path (default: ~/.agent-tui/sessions.jsonl)
48+
AGENT_TUI_RECORD_STATE Recording state file path (default: ~/.agent-tui/recordings.json)
49+
AGENT_TUI_RECORDINGS_DIR Default recordings output directory (default: current directory)
4850
AGENT_TUI_LOG Log file path (optional)
4951
AGENT_TUI_LOG_FORMAT Log format (text or json; default: text)
5052
AGENT_TUI_LOG_STREAM Log output stream (stderr or stdout; default: stderr)
@@ -302,6 +304,7 @@ MODES:
302304
list List active sessions (default)
303305
show <id> Show details for a session
304306
attach Attach with TTY (defaults to --session or active)
307+
record Record session activity to VHS artifacts
305308
switch <id> Set the active session
306309
cleanup [--all] Remove dead/orphaned sessions")]
307310
#[command(after_long_help = "\
@@ -313,6 +316,10 @@ EXAMPLES:
313316
agent-tui -s abc123 sessions attach # Attach to session by id (TTY)
314317
agent-tui sessions switch abc123 # Set active session
315318
agent-tui -s abc123 sessions attach -T # Attach without TTY (stream output only)
319+
agent-tui sessions record # Record active session in background
320+
agent-tui sessions record --foreground
321+
agent-tui sessions record -o docs/recordings
322+
agent-tui -s abc123 sessions record stop
316323
agent-tui sessions attach --detach-keys 'ctrl-]' # Custom detach sequence
317324
agent-tui sessions cleanup # Remove dead sessions
318325
agent-tui sessions cleanup --all # Remove all sessions")]
@@ -441,6 +448,45 @@ pub enum SessionsCommand {
441448
detach_keys: Option<DetachKeys>,
442449
},
443450

451+
/// Record a running session to VHS artifacts (.gif + .tape)
452+
#[command(long_about = "\
453+
Record a running session to VHS artifacts.
454+
455+
By default recording starts in background and returns immediately.
456+
Use --foreground to wait until recording exits.
457+
458+
OUTPUT PATH RULES:
459+
-o/--output-file omitted Uses AGENT_TUI_RECORDINGS_DIR or current directory
460+
Existing directory Creates timestamped <session>-<time>.gif/.tape
461+
Existing file Uses file stem for .gif/.tape pair
462+
Non-existing path w/ ext Treated as file path
463+
Non-existing path no ext Treated as directory")]
464+
#[command(after_long_help = "\
465+
EXAMPLES:
466+
agent-tui sessions record
467+
agent-tui sessions record --foreground
468+
agent-tui sessions record -o docs/recordings
469+
agent-tui sessions record -o docs/recordings/demo.gif
470+
agent-tui sessions record stop")]
471+
#[command(args_conflicts_with_subcommands = true)]
472+
Record {
473+
/// Output file or directory for recording artifacts
474+
#[arg(
475+
short = 'o',
476+
long = "output-file",
477+
value_name = "PATH",
478+
value_hint = ValueHint::AnyPath
479+
)]
480+
output_file: Option<PathBuf>,
481+
482+
/// Run recorder in foreground (wait until recording exits)
483+
#[arg(long)]
484+
foreground: bool,
485+
486+
#[command(subcommand)]
487+
command: Option<RecordCommand>,
488+
},
489+
444490
/// Set the active session without attaching
445491
#[command(alias = "select")]
446492
Switch {
@@ -456,6 +502,12 @@ pub enum SessionsCommand {
456502
},
457503
}
458504

505+
#[derive(Debug, Subcommand)]
506+
pub enum RecordCommand {
507+
/// Stop recording for the selected or active session
508+
Stop,
509+
}
510+
459511
#[derive(Debug, Subcommand)]
460512
pub enum LiveCommand {
461513
/// Show the live preview API details
@@ -874,6 +926,69 @@ mod tests {
874926
));
875927
}
876928

929+
#[test]
930+
fn test_sessions_record_defaults() {
931+
let cli = Cli::parse_from(["agent-tui", "sessions", "record"]);
932+
let Commands::Sessions { command } = cli.command else {
933+
panic!("Expected Sessions command, got {:?}", cli.command);
934+
};
935+
assert!(matches!(
936+
command,
937+
Some(SessionsCommand::Record {
938+
output_file: None,
939+
foreground: false,
940+
command: None
941+
})
942+
));
943+
}
944+
945+
#[test]
946+
fn test_sessions_record_foreground() {
947+
let cli = Cli::parse_from(["agent-tui", "sessions", "record", "--foreground"]);
948+
let Commands::Sessions { command } = cli.command else {
949+
panic!("Expected Sessions command, got {:?}", cli.command);
950+
};
951+
assert!(matches!(
952+
command,
953+
Some(SessionsCommand::Record {
954+
foreground: true,
955+
command: None,
956+
..
957+
})
958+
));
959+
}
960+
961+
#[test]
962+
fn test_sessions_record_output_file() {
963+
let cli = Cli::parse_from(["agent-tui", "sessions", "record", "-o", "out.gif"]);
964+
let Commands::Sessions { command } = cli.command else {
965+
panic!("Expected Sessions command, got {:?}", cli.command);
966+
};
967+
assert!(matches!(
968+
command,
969+
Some(SessionsCommand::Record {
970+
output_file: Some(_),
971+
foreground: false,
972+
command: None
973+
})
974+
));
975+
}
976+
977+
#[test]
978+
fn test_sessions_record_stop() {
979+
let cli = Cli::parse_from(["agent-tui", "sessions", "record", "stop"]);
980+
let Commands::Sessions { command } = cli.command else {
981+
panic!("Expected Sessions command, got {:?}", cli.command);
982+
};
983+
assert!(matches!(
984+
command,
985+
Some(SessionsCommand::Record {
986+
command: Some(RecordCommand::Stop),
987+
..
988+
})
989+
));
990+
}
991+
877992
#[test]
878993
fn test_sessions_cleanup() {
879994
let cli = Cli::parse_from(["agent-tui", "sessions", "cleanup"]);

0 commit comments

Comments
 (0)