osal.tmux #
TMUX
TMUX is a very capable process manager.
TODO: TTYD, need to integrate with TMUX for exposing TMUX over http
Concepts
- tmux = is the factory, it represents the tmux process manager, linked to a node
- session = is a set of windows, it has a name and groups windows
- window = is typically one process running (you can have panes but in our implementation we skip this)
structure
tmux library provides functions for managing tmux sessions
- session is the top one
- then windows (is where you see the app running)
- then panes in windows (we don't support yet)
to attach to a tmux session
HeroScript Programming Paradigms
The tmux module supports both Imperative and Declarative programming paradigms through heroscript, allowing you to choose the approach that best fits your use case.
Imperative vs Declarative
Imperative Approach:
- Explicit step-by-step actions
- Order matters
- More control over exact process
- Can fail if intermediate steps fail
- Good for one-time setup scripts
Declarative Approach:
- Describes desired end state
- Idempotent (can run multiple times safely)
- Automatically handles missing dependencies
- More resilient to partial failures
- Good for configuration management
Both paradigms can be mixed in the same script as needed!
Running HeroScript
hero run -p <heroscript_file>
Imperative Actions (Traditional)
Session Management
// Create a new session
!!tmux.session_create
name:'mysession'
reset:true // Optional: delete existing session first
// Delete a session
!!tmux.session_delete
name:'mysession'
Window Management
// Create a new window
!!tmux.window_create
name:'mysession|mywindow' // Format: session|window
cmd:'htop' // Optional: command to run
env:'VAR1=value1,VAR2=value2' // Optional: environment variables
reset:true // Optional: recreate if exists
// Delete a window
!!tmux.window_delete
name:'mysession|mywindow'
Pane Management
// Execute command in a pane
!!tmux.pane_execute
name:'mysession|mywindow|mypane' // Format: session|window|pane
cmd:'ls -la'
// Kill a pane
!!tmux.pane_kill
name:'mysession|mywindow|mypane'
Pane Splitting
// Split a pane horizontally or vertically
!!tmux.pane_split
name:'mysession|mywindow'
cmd:'htop'
horizontal:true // true for horizontal, false for vertical
env:'VAR1=value1'
Ttyd Management
// Start ttyd for session access
!!tmux.session_ttyd
name:'mysession'
port:8080
editable:true // Optional: allows write access
// Start ttyd for window access
!!tmux.window_ttyd
name:'mysession|mywindow'
port:8081
editable:false // Optional: read-only access
// Stop ttyd for session
!!tmux.session_ttyd_stop
name:'mysession'
port:8080
// Stop ttyd for window
!!tmux.window_ttyd_stop
name:'mysession|mywindow'
port:8081
// Stop all ttyd processes
!!tmux.ttyd_stop_all
Declarative Actions (State-Based)
Session Ensure
// Ensure session exists (idempotent)
!!tmux.session_ensure
name:'mysession'
Window Ensure with Pane Layouts
// Ensure window exists with specific pane layout
!!tmux.window_ensure
name:'mysession|mywindow'
cat:'4pane' // Supported: 16pane, 12pane, 8pane, 6pane, 4pane, 2pane, 1pane
cmd:'bash' // Optional: default command for panes
env:'VAR1=value1,VAR2=value2' // Optional: environment variables
Pane Ensure
// Ensure specific pane exists with command
!!tmux.pane_ensure
name:'mysession|mywindow|1' // Pane number (1-based)
label:'editor' // Optional: descriptive label
cmd:'vim' // Optional: command to run
env:'EDITOR=vim' // Optional: environment variables
// Multi-line commands are supported using proper heroscript syntax
!!tmux.pane_ensure
name:'mysession|mywindow|2'
label:'setup'
cmd:'
echo "Starting setup..."
mkdir -p /tmp/workspace
cd /tmp/workspace
echo "Setup complete"
'
Multi-line Commands
The tmux module supports multi-line commands in heroscripts using proper multi-line parameter syntax. Multi-line commands are automatically converted to temporary shell scripts for execution.
Syntax
Use the multi-line parameter format with quotes:
!!tmux.pane_ensure
name:'session|window|pane'
cmd:'
command1
command2
command3
'
Features
- Automatic Script Generation: Multi-line commands are converted to temporary shell scripts
- Sequential Execution: All commands execute in order within the same shell context
- Error Handling: Scripts include proper bash shebang and error handling
- Temporary Files: Scripts are stored in
/tmp/tmux/{session}/pane_{id}_script.sh
Example
!!tmux.pane_ensure
name:'dev|workspace|1'
label:'setup'
cmd:'
echo "Setting up development environment..."
mkdir -p /tmp/dev_workspace
cd /tmp/dev_workspace
git clone https://github.com/example/repo.git
cd repo
npm install
echo "Development environment ready!"
'
Pane Layout Categories
The declarative window_ensure
action supports predefined pane layouts:
- 1pane: Single pane (default)
- 2pane: Two panes side by side
- 4pane: Four panes in a 2x2 grid
- 6pane: Six panes in a 2x3 layout
- 8pane: Eight panes in a 2x4 layout
- 12pane: Twelve panes in a 3x4 layout
- 16pane: Sixteen panes in a 4x4 layout
Complete Imperative Example
#!/usr/bin/env hero
// Create development environment
!!tmux.session_create
name:'dev'
reset:true
!!tmux.window_create
name:"dev|editor"
cmd:'vim'
reset:true
!!tmux.window_create
name:"dev|server"
cmd:'python3 -m http.server 8000'
env:'PORT=8000,DEBUG=true'
reset:true
!!tmux.pane_execute
name:"dev|editor|main"
cmd:'echo "Welcome to development!"'
Naming Convention
- Sessions: Simple names like
dev
,monitoring
,main
- Windows: Use pipe separator:
session|window
(e.g.,dev|editor
) - Panes: Use pipe separator:
session|window|pane
(e.g.,dev|editor|main
)
Names are automatically normalized using texttools.name_fix()
for consistency.
Complete Declarative Example
#!/usr/bin/env hero
// Ensure sessions exist
!!tmux.session_ensure
name:'dev'
// Ensure 4-pane development workspace
!!tmux.window_ensure
name:"dev|workspace"
cat:"4pane"
// Configure each pane with specific commands
!!tmux.pane_ensure
name:"dev|workspace|1"
label:'editor'
cmd:'vim'
!!tmux.pane_ensure
name:"dev|workspace|2"
label:'server'
cmd:'python3 -m http.server 8000'
env:'PORT=8000'
!!tmux.pane_ensure
name:"dev|workspace|3"
label:'logs'
cmd:'tail -f /var/log/system.log'
!!tmux.pane_ensure
name:"dev|workspace|4"
label:'terminal'
cmd:'echo "Ready for commands"'
Example Usage
Example Scripts
Several example heroscripts are provided to demonstrate both paradigms:
1. Declarative Example (declarative_example.heroscript
)
Pure declarative approach showing state-based configuration:
hero run examples/tmux/declarative_example.heroscript
2. Paradigm Comparison (imperative_vs_declarative.heroscript
)
Side-by-side comparison of both approaches:
hero run examples/tmux/imperative_vs_declarative.heroscript
3. Setup and Cleanup Scripts
Traditional imperative scripts for environment management:
hero run examples/tmux/tmux_setup.heroscript ##hero run examples/tmux/tmux_cleanup.heroscript ##
fn new #
fn new(args TmuxNewArgs) !Tmux
return tmux instance
fn normalize_and_hash_command #
fn normalize_and_hash_command(cmd string) string
Generate MD5 hash for a command (normalized)
fn play #
fn play(mut plbook PlayBook) !
fn stop_all_ttyd #
fn stop_all_ttyd() !
Stop all ttyd processes (kills all ttyd processes system-wide)
fn (Session) cleanup_stale_command_states #
fn (mut s Session) cleanup_stale_command_states() !
Clean up stale command states (for maintenance)
fn (Session) create #
fn (mut s Session) create() !
fn (Session) get_all_command_states #
fn (mut s Session) get_all_command_states() !map[string]CommandState
Get all command states for a session (useful for debugging/monitoring)
fn (Session) kill_all_processes #
fn (mut s Session) kill_all_processes() !
Kill all processes in all windows and panes of this session
fn (Session) restart #
fn (mut s Session) restart() !
fn (Session) run_ttyd #
fn (mut s Session) run_ttyd(args TtydArgs) !
Run ttyd for this session so it can be accessed in the browser
fn (Session) run_ttyd_readonly #
fn (mut s Session) run_ttyd_readonly(port int) !
Backward compatibility method - runs ttyd in read-only mode
fn (Session) scan #
fn (mut s Session) scan() !
load info from reality
fn (Session) stats #
fn (mut s Session) stats() !ProcessStats
fn (Session) stop #
fn (mut s Session) stop() !
fn (Session) stop_ttyd #
fn (mut s Session) stop_ttyd(port int) !
Stop ttyd for this session by killing the process on the specified port
fn (Session) str #
fn (mut s Session) str() string
fn (Session) window_delete #
fn (mut s Session) window_delete(args_ WindowGetArgs) !
fn (Session) window_get #
fn (mut s Session) window_get(args_ WindowGetArgs) !&Window
fn (Session) window_list #
fn (mut s Session) window_list() []&Window
List windows in a session
fn (Session) window_names #
fn (mut s Session) window_names() []string
fn (Session) window_new #
fn (mut s Session) window_new(args WindowArgs) !&Window
window_name is the name of the window in session main (will always be called session main) cmd to execute e.g. bash file environment arguments to use reset, if reset it will create window even if it does already exist, will destroy it
struct WindowArgs {
pub mut:
name string
cmd string
env map[string]string
reset bool
}
fn (Session) windows_get #
fn (mut s Session) windows_get() []&Window
get all windows as found in a session
fn (Window) scan #
fn (mut w Window) scan() !
fn (Window) stop #
fn (mut w Window) stop() !
fn (Window) create #
fn (mut w Window) create(cmd_ string) !
helper function TODO env variables are not inserted in pane
fn (Window) kill #
fn (mut w Window) kill() !
stop the window with comprehensive process cleanup
fn (Window) kill_all_processes #
fn (mut w Window) kill_all_processes() !
Kill all processes in all panes of this window
fn (Window) str #
fn (window Window) str() string
fn (Window) stats #
fn (mut w Window) stats() !ProcessStats
fn (Window) pane_list #
fn (mut w Window) pane_list() []&Pane
List panes in a window
fn (Window) pane_active #
fn (mut w Window) pane_active() ?&Pane
Get active pane in window
fn (Window) pane_split #
fn (mut w Window) pane_split(args PaneSplitArgs) !&Pane
Split the active pane horizontally or vertically
fn (Window) pane_split_horizontal #
fn (mut w Window) pane_split_horizontal(cmd string) !&Pane
Split pane horizontally (side by side)
fn (Window) pane_split_vertical #
fn (mut w Window) pane_split_vertical(cmd string) !&Pane
Split pane vertically (top and bottom)
fn (Window) resize_panes_equal #
fn (mut w Window) resize_panes_equal() !
Resize panes to equal dimensions dynamically based on pane count
fn (Window) run_ttyd #
fn (mut w Window) run_ttyd(args TtydArgs) !
Run ttyd for this window so it can be accessed in the browser
fn (Window) run_ttyd_readonly #
fn (mut w Window) run_ttyd_readonly(port int) !
Backward compatibility method - runs ttyd in read-only mode
fn (Window) stop_ttyd #
fn (mut w Window) stop_ttyd(port int) !
Stop ttyd for this window by killing the process on the specified port
fn (Window) pane_get #
fn (mut w Window) pane_get(id int) !&Pane
Get a pane by its ID
fn (Window) pane_new #
fn (mut w Window) pane_new() !&Pane
Create a new pane (just a split with default shell)
struct CommandState #
struct CommandState {
pub mut:
cmd_md5 string // MD5 hash of the command
cmd_text string // Original command text
status string // running|finished|failed|unknown
pid int // Process ID of the command
started_at string // Timestamp when command started
last_check string // Last time status was checked
pane_id int // Pane ID for reference
}
Command state structure for Redis storage
struct LogsGetArgs #
struct LogsGetArgs {
pub mut:
reset bool
}
struct Pane #
struct Pane {
pub mut:
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
// Logging fields
log_enabled bool // whether logging is enabled for this pane
log_path string // path where logs are stored
logger_pid int // process id of the logger process
}
fn (Pane) clear #
fn (mut p Pane) clear() !
fn (Pane) clear_command_state #
fn (mut p Pane) clear_command_state() !
Clear command state from Redis (when pane is reset or command is removed)
fn (Pane) ensure_bash_parent #
fn (mut p Pane) ensure_bash_parent() !
Ensure bash is the first process in the pane
fn (Pane) exit_status #
fn (mut p Pane) exit_status() !ProcessStatus
fn (Pane) get_child_processes #
fn (mut p Pane) get_child_processes() ![]osal.ProcessInfo
Get all child processes of this pane's main process
fn (Pane) get_command_state #
fn (mut p Pane) get_command_state() ?CommandState
Retrieve command state from Redis
fn (Pane) get_height #
fn (p Pane) get_height() !int
Get current pane height
fn (Pane) get_state_key #
fn (p &Pane) get_state_key() string
Generate Redis key for command state tracking Pattern: herotmux:${session}:${window}|${pane}
fn (Pane) get_width #
fn (p Pane) get_width() !int
Get current pane width
fn (Pane) has_command_changed #
fn (mut p Pane) has_command_changed(new_cmd string) bool
Check if command has changed by comparing MD5 hashes
fn (Pane) is_at_clean_prompt #
fn (mut p Pane) is_at_clean_prompt() !bool
Check if pane is at a clean shell prompt
fn (Pane) is_pane_empty #
fn (mut p Pane) is_pane_empty() !bool
Check if pane is completely empty
fn (Pane) is_stored_command_running #
fn (mut p Pane) is_stored_command_running() bool
Check if stored command is currently running by verifying the PID
fn (Pane) kill #
fn (mut p Pane) kill() !
Kill this specific pane with comprehensive process cleanup
fn (Pane) kill_processes #
fn (mut p Pane) kill_processes() !
Kill all processes associated with this pane (main process and all children)
fn (Pane) kill_running_command #
fn (mut p Pane) kill_running_command() !
Kill the currently running command in this pane
fn (Pane) logging_disable #
fn (mut p Pane) logging_disable() !
Disable logging for this pane
fn (Pane) logging_enable #
fn (mut p Pane) logging_enable(args PaneLoggingEnableArgs) !
Enable logging for this pane
fn (Pane) logging_status #
fn (p Pane) logging_status() string
Get logging status for this pane
fn (Pane) logs_all #
fn (mut p Pane) logs_all() !string
fn (Pane) logs_get_new #
fn (mut p Pane) logs_get_new(args LogsGetArgs) ![]TMuxLogEntry
get new logs since last call
fn (Pane) output_wait #
fn (mut p Pane) output_wait(c_ string, timeoutsec int) !
Fix the output_wait method to use correct method name
fn (Pane) processinfo #
fn (mut p Pane) processinfo() !osal.ProcessMap
Get process information for this pane and all its children
fn (Pane) processinfo_main #
fn (mut p Pane) processinfo_main() !osal.ProcessInfo
Get process information for just this pane's main process
fn (Pane) reset_if_needed #
fn (mut p Pane) reset_if_needed() !
Reset pane if it appears empty or needs cleanup
fn (Pane) resize #
fn (mut p Pane) resize(args PaneResizeArgs) !
Resize this pane
fn (Pane) resize_down #
fn (mut p Pane) resize_down(cells int) !
fn (Pane) resize_left #
fn (mut p Pane) resize_left(cells int) !
fn (Pane) resize_right #
fn (mut p Pane) resize_right(cells int) !
fn (Pane) resize_up #
fn (mut p Pane) resize_up(cells int) !
Convenience methods for resizing
fn (Pane) select #
fn (mut p Pane) select() !
Select/activate this pane
fn (Pane) send_command #
fn (mut p Pane) send_command(command string) !
Send a command to this pane Supports both single-line and multi-line commands
fn (Pane) send_command_declarative #
fn (mut p Pane) send_command_declarative(command string) !
Send command with declarative mode logic (intelligent state management) This method implements the full declarative logic:1. Check if pane has previous command (Redis lookup)2. If previous command exists:a. Check if still running (process verification) b. Compare MD5 hashes c. If different command OR not running: proceed d. If same command AND running: skip3. If proceeding: kill existing processes, then start new command
fn (Pane) send_keys #
fn (mut p Pane) send_keys(keys string) !
Send raw keys to this pane (without Enter)
fn (Pane) send_reset #
fn (mut p Pane) send_reset() !
Send reset command to pane
fn (Pane) stats #
fn (mut p Pane) stats() !ProcessStats
fn (Pane) store_command_state #
fn (mut p Pane) store_command_state(cmd string, status string, pid int) !
Store command state in Redis
fn (Pane) update_command_status #
fn (mut p Pane) update_command_status(status string) !
Update command status in Redis
fn (Pane) verify_bash_parent #
fn (mut p Pane) verify_bash_parent() !bool
Verify that bash is the first process in this pane
fn (Pane) verify_command_hierarchy #
fn (mut p Pane) verify_command_hierarchy() !bool
Check if commands are running as children of bash
struct PaneLoggingEnableArgs #
struct PaneLoggingEnableArgs {
pub mut:
logpath string // custom log path, if empty uses default
logreset bool // whether to reset/clear existing logs
}
struct PaneNewArgs #
struct PaneNewArgs {
pub mut:
name string
reset bool // means we reset the pane if it already exists
cmd string
env map[string]string
}
struct PaneResizeArgs #
struct PaneResizeArgs {
pub mut:
direction string = 'right' // 'up', 'down', 'left', 'right'
cells int = 5 // number of cells to resize by
}
struct PaneSplitArgs #
struct PaneSplitArgs {
pub mut:
cmd string // command to run in new pane
horizontal bool // true for horizontal split, false for vertical
env map[string]string // environment variables
// Logging parameters
log bool // enable logging for this pane
logreset bool // reset/clear existing logs when enabling
logpath string // custom log path, if empty uses default
}
struct ProcessStats #
struct ProcessStats {
pub mut:
cpu_percent f64
memory_bytes u64
memory_percent f64
}
struct SessionCreateArgs #
struct SessionCreateArgs {
pub mut:
name string @[required]
reset bool
}
struct TMuxLogEntry #
struct TMuxLogEntry {
pub mut:
content string
timestamp time.Time
offset int
}
struct Tmux #
struct Tmux {
pub mut:
sessions []&Session
sessionid string // unique link to job
redis &redisclient.Redis @[skip] // Redis client for command state tracking
}
fn (Tmux) is_running #
fn (mut t Tmux) is_running() !bool
checks whether tmux server is running
fn (Tmux) list_print #
fn (mut t Tmux) list_print()
print list of tmux sessions
fn (Tmux) scan #
fn (mut t Tmux) scan() !
scan the system to detect sessions . TODO needs to be done differently, here only find the sessions, then per session call the scan() which will find the windows, call scan() there as well ...
fn (Tmux) session_create #
fn (mut t Tmux) session_create(args SessionCreateArgs) !&Session
create session, if reset will re-create
fn (Tmux) session_delete #
fn (mut t Tmux) session_delete(name_ string) !
fn (Tmux) session_exist #
fn (mut t Tmux) session_exist(name_ string) bool
fn (Tmux) session_get #
fn (mut t Tmux) session_get(name_ string) !&Session
get session (session has windows) . returns none if not found
fn (Tmux) start #
fn (mut t Tmux) start() !
fn (Tmux) stop #
fn (mut t Tmux) stop() !
fn (Tmux) str #
fn (mut t Tmux) str() string
fn (Tmux) window_new #
fn (mut t Tmux) window_new(args WindowNewArgs) !&Window
fn (Tmux) windows_get #
fn (mut t Tmux) windows_get() []&Window
get all windows as found in all sessions
struct TmuxNewArgs #
struct TmuxNewArgs {
pub:
sessionid string
}
struct TtydArgs #
struct TtydArgs {
pub mut:
port int
editable bool // if true, allows write access to the terminal
}
struct WindowArgs #
struct WindowArgs {
pub mut:
name string
cmd string
env map[string]string
reset bool
}
struct WindowGetArgs #
struct WindowGetArgs {
pub mut:
name string
id int
}
struct WindowNewArgs #
struct WindowNewArgs {
pub mut:
session_name string = 'main'
name string
cmd string
env map[string]string
reset bool
}
- README
- fn new
- fn normalize_and_hash_command
- fn play
- fn stop_all_ttyd
- type Session
- type Window
- struct CommandState
- struct LogsGetArgs
- struct Pane
- fn clear
- fn clear_command_state
- fn ensure_bash_parent
- fn exit_status
- fn get_child_processes
- fn get_command_state
- fn get_height
- fn get_state_key
- fn get_width
- fn has_command_changed
- fn is_at_clean_prompt
- fn is_pane_empty
- fn is_stored_command_running
- fn kill
- fn kill_processes
- fn kill_running_command
- fn logging_disable
- fn logging_enable
- fn logging_status
- fn logs_all
- fn logs_get_new
- fn output_wait
- fn processinfo
- fn processinfo_main
- fn reset_if_needed
- fn resize
- fn resize_down
- fn resize_left
- fn resize_right
- fn resize_up
- fn select
- fn send_command
- fn send_command_declarative
- fn send_keys
- fn send_reset
- fn stats
- fn store_command_state
- fn update_command_status
- fn verify_bash_parent
- fn verify_command_hierarchy
- struct PaneLoggingEnableArgs
- struct PaneNewArgs
- struct PaneResizeArgs
- struct PaneSplitArgs
- struct ProcessStats
- struct SessionCreateArgs
- struct TMuxLogEntry
- struct Tmux
- struct TmuxNewArgs
- struct TtydArgs
- struct WindowArgs
- struct WindowGetArgs
- struct WindowNewArgs