feat: Add declarative tmux module functions

- Implement `tmux.session_ensure` for idempotent create
- Implement `tmux.window_ensure` with 1 to 16 pane layouts
- Implement `tmux.pane_ensure` to configure individual panes
- Add new declarative tmux example scripts
- Update docs for imperative and declarative paradigms
This commit is contained in:
Mahmoud-Emad
2025-08-31 11:59:23 +03:00
parent ccfe02b1ee
commit 4b0b5a26f8
10 changed files with 948 additions and 26 deletions

View File

@@ -19,7 +19,7 @@ The SSH agent functionality is built into the hero binary. After compiling hero:
./cli/compile.vsh
```
The hero binary will be available at `/Users/mahmoud/hero/bin/hero`
The hero binary will be available at `~/hero/bin/hero`
## Commands

View File

@@ -0,0 +1,218 @@
# Testing the Declarative TMUX Implementation
## Prerequisites
1. **Build the hero binary** with the new tmux functionality:
```bash
cd /Users/mahmoud/code/github/freeflowuniverse/herolib
./cli/compile.vsh
```
2. **Ensure tmux is installed** on your system:
```bash
# macOS
brew install tmux
# Ubuntu/Debian
sudo apt install tmux
```
## Test Scripts
### 1. Simple Declarative Test (Recommended First Test)
```bash
/Users/mahmoud/hero/bin/hero run -p examples/tmux/simple_declarative_test.heroscript
```
**Expected Result:**
- Creates a session named "test"
- Creates a window "demo" with 2 panes
- Each pane displays a welcome message
**Verification:**
```bash
# List tmux sessions
tmux list-sessions
# Attach to the test session
tmux attach-session -t test
# You should see 2 panes side by side with messages
```
### 2. Full Declarative Example
```bash
/Users/mahmoud/hero/bin/hero run -p examples/tmux/declarative_example.heroscript
```
**Expected Result:**
- Creates "dev" session with 4-pane workspace
- Creates "monitoring" session with 2-pane system view
- Starts ttyd web access on ports 8080 and 8081
**Verification:**
```bash
# Check sessions
tmux list-sessions
# Check web access (if ttyd is installed)
open http://localhost:8080 # Dev session
open http://localhost:8081 # Monitoring session
# Attach to sessions
tmux attach-session -t dev
tmux attach-session -t monitoring
```
### 3. Paradigm Comparison Test
```bash
/Users/mahmoud/hero/bin/hero run -p examples/tmux/imperative_vs_declarative.heroscript
```
**Expected Result:**
- Creates "imperative_demo" session using step-by-step commands
- Creates "declarative_demo" session using state-based configuration
- Demonstrates both approaches working together
## Testing Individual Functions
### Test Session Ensure (Idempotent)
Create a test file:
```heroscript
!!tmux.session_ensure
name:"test_session"
```
Run it multiple times - should not create duplicates:
```bash
/Users/mahmoud/hero/bin/hero run -p test_session.heroscript
/Users/mahmoud/hero/bin/hero run -p test_session.heroscript # Should be safe to run again
```
### Test Window Layouts
Test different pane layouts:
```heroscript
!!tmux.session_ensure
name:"layout_test"
!!tmux.window_ensure
name:"layout_test|test_1pane"
cat:"1pane"
!!tmux.window_ensure
name:"layout_test|test_2pane"
cat:"2pane"
!!tmux.window_ensure
name:"layout_test|test_4pane"
cat:"4pane"
```
### Test Pane Configuration
```heroscript
!!tmux.session_ensure
name:"pane_test"
!!tmux.window_ensure
name:"pane_test|demo"
cat:"2pane"
!!tmux.pane_ensure
name:"pane_test|demo|1"
label:"first"
cmd:"echo Hello from pane 1"
!!tmux.pane_ensure
name:"pane_test|demo|2"
label:"second"
cmd:"htop"
env:"TERM=xterm-256color"
```
## Troubleshooting
### Common Issues
1. **"tmux server not running"**
```bash
# Start tmux server
tmux new-session -d -s temp
tmux kill-session -t temp
```
2. **"Permission denied" for ttyd**
```bash
# Install ttyd if needed
brew install ttyd # macOS
```
3. **Syntax errors in heroscript**
- Ensure no nested quotes in command strings
- Use double quotes for all parameter values
- Avoid special characters like `!` in commands
### Verification Commands
```bash
# List all tmux sessions
tmux list-sessions
# List windows in a session
tmux list-windows -t session_name
# List panes in a window
tmux list-panes -t session_name:window_name
# Kill all tmux sessions (cleanup)
tmux kill-server
```
## Expected Behavior
### Declarative Functions Should
1. **Be Idempotent**: Running the same script multiple times should not create duplicates
2. **Handle Dependencies**: Automatically create sessions if windows are requested
3. **Respect Layouts**: Create the correct number of panes based on category
4. **Execute Commands**: Run specified commands in the correct panes
5. **Set Environment**: Apply environment variables to panes
### Success Indicators
- ✅ Scripts run without syntax errors
- ✅ Sessions and windows are created as specified
- ✅ Pane layouts match the requested categories
- ✅ Commands execute in the correct panes
- ✅ Re-running scripts doesn't create duplicates
- ✅ Environment variables are properly set
## Cleanup
After testing, clean up tmux sessions:
```bash
# Kill specific sessions
tmux kill-session -t test
tmux kill-session -t dev
tmux kill-session -t monitoring
# Or kill all sessions
tmux kill-server

View File

@@ -0,0 +1,16 @@
// Debug test - no ttyd, just basic declarative functionality
// Ensure a test session exists
!!tmux.session_ensure
name:"debug_test"
// Ensure a simple 2-pane window exists
!!tmux.window_ensure
name:"debug_test|demo"
cat:"2pane"
// Try to configure just one pane
!!tmux.pane_ensure
name:"debug_test|demo|1"
label:"test"
cmd:"echo Hello from pane 1"

View File

@@ -0,0 +1,64 @@
// Declarative TMUX Configuration Example
// This demonstrates the declarative paradigm where we define the desired state
// Ensure a development session exists
!!tmux.session_ensure
name:'dev'
// Ensure a monitoring session exists
!!tmux.session_ensure
name:'monitoring'
// Ensure a 4-pane development window exists
!!tmux.window_ensure
name:"dev|workspace"
cat:"4pane"
// Ensure a 2-pane monitoring window exists
!!tmux.window_ensure
name:"monitoring|system"
cat:"2pane"
// Ensure specific panes with commands
!!tmux.pane_ensure
name:"dev|workspace|1"
label:"editor"
cmd:"echo Starting editor... && sleep 1 && echo Editor ready"
!!tmux.pane_ensure
name:"dev|workspace|2"
label:"server"
cmd:"echo Starting development server... && sleep 2 && echo Server running on port 3000"
env:"PORT=3000,NODE_ENV=development"
!!tmux.pane_ensure
name:"dev|workspace|3"
label:"logs"
cmd:"echo Monitoring logs... && tail -f /dev/null"
!!tmux.pane_ensure
name:"dev|workspace|4"
label:"terminal"
cmd:"echo Terminal ready for commands"
!!tmux.pane_ensure
name:"monitoring|system|1"
label:"htop"
cmd:"htop"
!!tmux.pane_ensure
name:"monitoring|system|2"
label:"network"
cmd:"echo Network monitoring... && netstat -tuln"
// Start web access for the development session
!!tmux.session_ttyd
name:'dev'
port:8080
editable:true
// Start web access for the monitoring session
!!tmux.session_ttyd
name:'monitoring'
port:8081
editable:false

View File

@@ -0,0 +1,108 @@
// TMUX Configuration: Imperative vs Declarative Examples
// This file demonstrates both paradigms for comparison
// =============================================================================
// IMPERATIVE APPROACH - Action after action builds the output
// =============================================================================
// Create sessions step by step
!!tmux.session_create
name:'imperative_demo'
reset:true
// Create windows one by one
!!tmux.window_create
name:"imperative_demo|editor"
cmd:'vim'
reset:true
!!tmux.window_create
name:"imperative_demo|server"
cmd:'python3 -m http.server 8000'
env:'PORT=8000,DEBUG=true'
reset:true
// Split panes manually
!!tmux.pane_split
name:"imperative_demo|editor"
cmd:'htop'
horizontal:true
!!tmux.pane_split
name:"imperative_demo|editor"
cmd:'tail -f /var/log/system.log'
horizontal:false
// Execute commands in specific panes
!!tmux.pane_execute
name:"imperative_demo|editor|main"
cmd:"echo Imperative setup complete!"
// =============================================================================
// DECLARATIVE APPROACH - Describe the desired state
// =============================================================================
// Ensure sessions exist (idempotent)
!!tmux.session_ensure
name:'declarative_demo'
// Ensure windows with specific layouts exist
!!tmux.window_ensure
name:"declarative_demo|workspace"
cat:"4pane" // Automatically creates 4-pane layout
!!tmux.window_ensure
name:"declarative_demo|monitoring"
cat:"2pane" // Automatically creates 2-pane layout
// Ensure specific panes with their desired state
!!tmux.pane_ensure
name:"declarative_demo|workspace|1"
label:"editor"
cmd:"echo Editor pane ready && vim"
!!tmux.pane_ensure
name:"declarative_demo|workspace|2"
label:"server"
cmd:"echo Starting server... && python3 -m http.server 8000"
env:"PORT=8000,DEBUG=true"
!!tmux.pane_ensure
name:"declarative_demo|workspace|3"
label:"logs"
cmd:"echo Monitoring logs... && tail -f /var/log/system.log"
!!tmux.pane_ensure
name:"declarative_demo|workspace|4"
label:"terminal"
cmd:"echo Terminal ready for commands"
!!tmux.pane_ensure
name:"declarative_demo|monitoring|1"
label:"system"
cmd:"htop"
!!tmux.pane_ensure
name:"declarative_demo|monitoring|2"
label:"network"
cmd:"netstat -tuln"
// =============================================================================
// COMPARISON SUMMARY
// =============================================================================
// IMPERATIVE:
// - 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:
// - Describes desired end state
// - Idempotent (can run multiple times safely)
// - Automatically handles missing dependencies
// - More resilient to partial failures
// - Good for configuration management
// Both approaches can be mixed in the same script as needed!

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env hero
// Simple test of declarative tmux functionality
// Ensure a test session exists
!!tmux.session_ensure
name:"test"
// Ensure a simple 2-pane window exists
!!tmux.window_ensure
name:"test|demo"
cat:"2pane"
// Configure the panes
!!tmux.pane_ensure
name:"test|demo|1"
label:"first"
cmd:"echo First pane ready"
!!tmux.pane_ensure
name:"test|demo|2"
label:"second"
cmd:"echo Second pane ready"

View File

@@ -1,5 +1,3 @@
#!/usr/bin/env hero
// Tmux Cleanup Script - Tears down all tmux sessions, windows, and panes
// Run this after tmux_setup.heroscript to clean up everything

View File

@@ -17,6 +17,7 @@ pub fn play(mut plbook PlayBook) ! {
tmux_instance.start()!
}
// Imperative functions (action after action)
play_session_create(mut plbook, mut tmux_instance)!
play_session_delete(mut plbook, mut tmux_instance)!
play_window_create(mut plbook, mut tmux_instance)!
@@ -29,6 +30,11 @@ pub fn play(mut plbook PlayBook) ! {
play_session_ttyd_stop(mut plbook, mut tmux_instance)!
play_window_ttyd_stop(mut plbook, mut tmux_instance)!
play_ttyd_stop_all(mut plbook, mut tmux_instance)!
// Declarative functions (desired state)
play_session_ensure(mut plbook, mut tmux_instance)!
play_window_ensure(mut plbook, mut tmux_instance)!
play_pane_ensure(mut plbook, mut tmux_instance)!
}
struct ParsedWindowName {
@@ -337,3 +343,384 @@ fn play_ttyd_stop_all(mut plbook PlayBook, mut tmux_instance Tmux) ! {
action.done = true
}
}
// DECLARATIVE FUNCTIONS - Ensure desired state exists
// Ensure session exists (declarative)
fn play_session_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.session_ensure')!
for mut action in actions {
mut p := action.params
session_name := p.get('name')!
// Ensure session exists, create if it doesn't
if !tmux_instance.session_exist(session_name) {
tmux_instance.session_create(name: session_name)!
}
action.done = true
}
}
// Pane layout configurations for different categories
struct PaneLayout {
splits []PaneSplit
}
struct PaneSplit {
horizontal bool
target_pane int // which pane to split (0-based index)
}
// Get pane layout configuration based on category
fn get_pane_layout(category string) PaneLayout {
match category {
'1pane' {
return PaneLayout{
splits: []
}
}
'2pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
},
]
}
}
'4pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally first
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left pane vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right pane vertically
]
}
}
'6pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally
PaneSplit{
horizontal: true
target_pane: 1
}, // Split right pane horizontally
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left pane vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split middle pane vertically
PaneSplit{
horizontal: false
target_pane: 2
}, // Split right pane vertically
]
}
}
'8pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right vertically
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-right horizontally
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-right horizontally
]
}
}
'12pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally (2 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split right horizontally (3 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically (4 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split middle vertically (5 panes)
PaneSplit{
horizontal: false
target_pane: 2
}, // Split right vertically (6 panes)
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally (7 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally (8 panes)
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-middle horizontally (9 panes)
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-middle horizontally (10 panes)
PaneSplit{
horizontal: true
target_pane: 4
}, // Split top-right horizontally (11 panes)
PaneSplit{
horizontal: true
target_pane: 5
}, // Split bottom-right horizontally (12 panes)
]
}
}
'16pane' {
return PaneLayout{
splits: [
PaneSplit{
horizontal: true
target_pane: 0
}, // Split horizontally (2 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split left vertically (3 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split right vertically (4 panes)
PaneSplit{
horizontal: true
target_pane: 0
}, // Split top-left horizontally (5 panes)
PaneSplit{
horizontal: true
target_pane: 1
}, // Split bottom-left horizontally (6 panes)
PaneSplit{
horizontal: true
target_pane: 2
}, // Split top-right horizontally (7 panes)
PaneSplit{
horizontal: true
target_pane: 3
}, // Split bottom-right horizontally (8 panes)
PaneSplit{
horizontal: false
target_pane: 0
}, // Split first quarter vertically (9 panes)
PaneSplit{
horizontal: false
target_pane: 1
}, // Split second quarter vertically (10 panes)
PaneSplit{
horizontal: false
target_pane: 2
}, // Split third quarter vertically (11 panes)
PaneSplit{
horizontal: false
target_pane: 3
}, // Split fourth quarter vertically (12 panes)
PaneSplit{
horizontal: false
target_pane: 4
}, // Split fifth quarter vertically (13 panes)
PaneSplit{
horizontal: false
target_pane: 5
}, // Split sixth quarter vertically (14 panes)
PaneSplit{
horizontal: false
target_pane: 6
}, // Split seventh quarter vertically (15 panes)
PaneSplit{
horizontal: false
target_pane: 7
}, // Split eighth quarter vertically (16 panes)
]
}
}
else {
// Default to 1pane if unknown category
return PaneLayout{
splits: []
}
}
}
}
// Ensure window exists with specified pane layout (declarative)
fn play_window_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.window_ensure')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_window_name(name)!
category := p.get_default('cat', '1pane')!
cmd := p.get_default('cmd', '')!
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Ensure session exists
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
// Check if window already exists with correct pane layout
mut window_exists := session.window_exist(name: parsed.window)
mut window := if window_exists {
session.window_get(name: parsed.window)!
} else {
// Create new window
session.window_new(
name: parsed.window
cmd: cmd
env: env
)!
}
// Ensure correct pane layout
layout := get_pane_layout(category)
current_pane_count := window.panes.len
// If we need more panes, create them according to layout
if layout.splits.len + 1 > current_pane_count {
// We need to create the layout from scratch
// First, ensure we have at least one pane (the window should have one by default)
window.scan()! // Refresh pane information
// Apply splits according to layout
for split in layout.splits {
// For simplicity, we'll split the active pane
// In a more sophisticated implementation, we could track specific panes
window.pane_split(
cmd: cmd
horizontal: split.horizontal
env: env
)!
}
}
action.done = true
}
}
// Ensure specific pane exists with command and label (declarative)
fn play_pane_ensure(mut plbook PlayBook, mut tmux_instance Tmux) ! {
mut actions := plbook.find(filter: 'tmux.pane_ensure')!
for mut action in actions {
mut p := action.params
name := p.get('name')!
parsed := parse_pane_name(name)!
cmd := p.get_default('cmd', '')!
label := p.get_default('label', '')!
// Parse environment variables if provided
mut env := map[string]string{}
if env_str := p.get_default('env', '') {
env_pairs := env_str.split(',')
for pair in env_pairs {
kv := pair.split('=')
if kv.len == 2 {
env[kv[0].trim_space()] = kv[1].trim_space()
}
}
}
// Ensure session exists
mut session := if tmux_instance.session_exist(parsed.session) {
tmux_instance.session_get(parsed.session)!
} else {
tmux_instance.session_create(name: parsed.session)!
}
// Ensure window exists
mut window := if session.window_exist(name: parsed.window) {
session.window_get(name: parsed.window)!
} else {
session.window_new(name: parsed.window)!
}
// Refresh pane information
window.scan()!
// Check if we need to create more panes or execute command in existing pane
pane_number := parsed.pane.int()
// Ensure we have enough panes (create splits if needed)
for window.panes.len < pane_number {
window.pane_split(
cmd: '/bin/bash'
horizontal: window.panes.len % 2 == 0 // Alternate between horizontal and vertical
env: env
)!
}
// Execute command in the specified pane if provided
if cmd.len > 0 {
// Find the target pane (by index, since tmux pane IDs can vary)
if pane_number > 0 && pane_number <= window.panes.len {
mut target_pane := window.panes[pane_number - 1] // Convert to 0-based index
target_pane.send_command(cmd)!
}
}
action.done = true
}
}

View File

@@ -21,9 +21,29 @@ tmux library provides functions for managing tmux sessions
## to attach to a tmux session
>
## HeroScript Declarative Support
## HeroScript Programming Paradigms
The tmux module supports declarative configuration through heroscript, allowing you to define tmux sessions, windows, and panes in a structured way.
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
@@ -31,7 +51,7 @@ The tmux module supports declarative configuration through heroscript, allowing
hero run -p <heroscript_file>
```
### Supported Actions
### Imperative Actions (Traditional)
#### Session Management
@@ -74,6 +94,17 @@ hero run -p <heroscript_file>
name:"mysession|mywindow|mypane"
```
#### Pane Splitting
```heroscript
// 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
```heroscript
@@ -103,7 +134,51 @@ hero run -p <heroscript_file>
!!tmux.ttyd_stop_all
```
### Complete Example
### Declarative Actions (State-Based)
#### Session Ensure
```heroscript
// Ensure session exists (idempotent)
!!tmux.session_ensure
name:'mysession'
```
#### Window Ensure with Pane Layouts
```heroscript
// 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
```heroscript
// 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
```
### 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
```heroscript
#!/usr/bin/env hero
@@ -137,37 +212,70 @@ hero run -p <heroscript_file>
Names are automatically normalized using `texttools.name_fix()` for consistency.
### Complete Declarative Example
```heroscript
#!/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
### Setup and Cleanup Scripts
### Example Scripts
Two example heroscripts are provided to demonstrate complete tmux environment management:
Several example heroscripts are provided to demonstrate both paradigms:
#### 1. Setup Script (`tmux_setup.heroscript`)
#### 1. Declarative Example (`declarative_example.heroscript`)
Creates a complete development environment with multiple sessions and windows:
Pure declarative approach showing state-based configuration:
```bash
hero run examples/tmux/tmux_setup.heroscript
hero run examples/tmux/declarative_example.heroscript
```
This creates:
#### 2. Paradigm Comparison (`imperative_vs_declarative.heroscript`)
- **dev session** with editor, server, logs, and a 4-pane services window
- **monitoring session** with htop and network monitoring windows
- **Web access** via ttyd on ports 8080, 8081, and 7681
#### 2. Cleanup Script (`tmux_cleanup.heroscript`)
Tears down all created tmux resources:
Side-by-side comparison of both approaches:
```bash
hero run examples/tmux/tmux_cleanup.heroscript
hero run examples/tmux/imperative_vs_declarative.heroscript
```
This removes:
#### 3. Setup and Cleanup Scripts
- All windows from both sessions
- Both dev and monitoring sessions
- All associated panes
- All ttyd web processes (ports 8080, 8081, 7681)
Traditional imperative scripts for environment management:
```bash
hero run examples/tmux/tmux_setup.heroscript # Setup
hero run examples/tmux/tmux_cleanup.heroscript # Cleanup
```