# Argos ⚡
**Command Execution & Parallel Task Orchestration Library for Elixir**
Argos is a powerful Elixir library for executing system commands with structured results, orchestrating parallel tasks, and providing enriched logging with formatted output. It's designed to be reliable, efficient, and easy to use.
## 🌟 Features
- **Command Execution**: Execute system commands with structured results and comprehensive error handling
- **Parallel Task Orchestration**: Run multiple workers concurrently with sequential tasks per worker
- **Circuit Breaker**: Built-in circuit breaker pattern to prevent cascading failures
- **ASDF Integration**: Automatic version management using `.tool-versions` files
- **Rich Logging**: Structured logging with Aurora integration for beautiful terminal output
- **Process Management**: Kill processes by name with validation and safety checks
- **CLI Tool**: Standalone executable for parallel command execution
- **Event Subscription**: Subscribe to individual worker events or aggregated monitor state
## 📦 Installation
Add Argos to your `mix.exs`:
```elixir
def deps do
[
{:argos, "~> 2.0.0"},
{:aurora, "~> 2.0.0"} # Required dependency
]
end
```
Then run:
```bash
mix deps.get
```
## 🚀 Quick Start
### Basic Command Execution
```elixir
# Execute a command and get structured result
result = Argos.Command.exec("ls -la")
if result.success? do
IO.puts("Success: #{result.output}")
else
IO.puts("Error: #{result.error}")
end
# Access result properties
IO.puts("Exit code: #{result.exit_code}")
IO.puts("Duration: #{result.duration}ms")
```
### Parallel Task Orchestration
```elixir
# Start the parallel system
Argos.Parallel.start_system()
# Create worker specifications
specs = [
Argos.Parallel.create_worker_spec(:worker1, [
fn ->
Process.sleep(1000)
{:ok, :task1_complete}
end,
fn ->
Process.sleep(500)
{:ok, :task2_complete}
end
])
]
# Start workers
Argos.Parallel.start_workers(specs)
# Subscribe to events
Argos.Parallel.subscribe()
# Receive events
receive do
{:parallel_event, _leader, event} ->
IO.inspect(event, label: "Worker event")
after
5000 -> :timeout
end
```
## 📚 API Reference
### Command Module
Execute system commands with various modes and options.
#### `exec/2`
Execute a command with structured result.
```elixir
result = Argos.Command.exec("ls -la")
result = Argos.Command.exec("risky_command", circuit_breaker: true)
```
**Options:**
- `circuit_breaker: true` - Enable circuit breaker for this command
- `halt: false` - Don't halt on error (default: true)
- `env: [{"KEY", "value"}]` - Environment variables
#### `exec_raw/2`
Execute command without additional logging.
```elixir
{output, exit_code} = Argos.Command.exec_raw("echo hello")
```
#### `exec_silent/2`
Execute silently, returning only exit code.
```elixir
exit_code = Argos.Command.exec_silent("ls -la")
```
#### `exec_interactive/2`
Execute command interactively (for commands requiring input).
```elixir
result = Argos.Command.exec_interactive("vim file.txt")
```
#### `exec_sudo/2`
Execute command with sudo.
```elixir
result = Argos.Command.exec_sudo("apt update")
```
#### `asdf_exec/3`
Execute command using ASDF with project-specific versions.
```elixir
# Execute using versions from .tool-versions in project directory
result = Argos.Command.asdf_exec(
"mix test",
"/path/to/project",
env: [{"MIX_ENV", "test"}]
)
```
**Features:**
- Automatically reads `.tool-versions` from project directory
- Uses `asdf exec` to run command with correct versions
- Configures PATH to include ASDF shims
- Falls back to normal execution if `.tool-versions` doesn't exist
#### `kill_process/1`
Kill a process by name.
```elixir
result = Argos.Command.kill_process("process_name")
```
#### `kill_processes_by_name/1`
Kill multiple processes by name.
```elixir
results = Argos.Command.kill_processes_by_name(["proc1", "proc2"])
```
### Parallel Module
Orchestrate parallel task execution with workers.
#### `start_system/1`
Start the parallel execution system.
```elixir
{:ok, pid} = Argos.Parallel.start_system()
```
#### `stop_system/0`
Stop the parallel execution system and clean up resources.
```elixir
:ok = Argos.Parallel.stop_system()
```
#### `create_worker_spec/3`
Create a worker specification.
```elixir
spec = Argos.Parallel.create_worker_spec(
:worker_id,
[fn -> :task1 end, fn -> :task2 end],
log: true # Enable logging for this worker
)
```
**Options:**
- `log: true` - Enable individual log file for this worker
- `timeout: 30_000` - Worker timeout in milliseconds
#### `start_workers/2`
Start multiple workers with given specifications.
```elixir
specs = [spec1, spec2, spec3]
results = Argos.Parallel.start_workers(specs)
```
#### `stop_worker/2`
Stop a specific worker by ID.
```elixir
:ok = Argos.Parallel.stop_worker(:worker_id)
```
#### `subscribe/1`
Subscribe to individual worker events from the Leader.
```elixir
:ok = Argos.Parallel.subscribe()
# Receive events
receive do
{:parallel_event, _leader, %{type: :result, worker_id: id, data: data}} ->
IO.puts("Worker #{id} completed task: #{inspect(data)}")
{:parallel_event, _leader, %{type: :progress, worker_id: id, data: data}} ->
IO.puts("Worker #{id} progress: #{data.percent}%")
{:parallel_event, _leader, %{type: :finished, worker_id: id}} ->
IO.puts("Worker #{id} finished")
{:parallel_event, _leader, %{type: :error, worker_id: id, data: data}} ->
IO.puts("Worker #{id} error: #{data.reason}")
end
```
#### `subscribe_monitor/1`
Subscribe to aggregated monitor state updates.
```elixir
:ok = Argos.Parallel.subscribe_monitor()
# Receive aggregated state
receive do
{:parallel_monitor_update, monitor_state} ->
IO.inspect(monitor_state.workers)
IO.inspect(monitor_state.stats)
end
```
#### `get_monitor_state/1`
Get current aggregated state from the monitor.
```elixir
state = Argos.Parallel.get_monitor_state()
IO.inspect(state.workers)
IO.inspect(state.stats)
```
#### `get_stats/1`
Get statistics about all workers.
```elixir
stats = Argos.Parallel.get_stats()
# %{
# progress_percentage: 75.5,
# total_workers: 4,
# status_counts: %{running: 2, finished: 2},
# completed_tasks: 10,
# total_tasks: 14
# }
```
#### `get_worker_state/2`
Get state of a specific worker.
```elixir
worker_state = Argos.Parallel.get_worker_state(:worker_id)
```
### Validation Module
Comprehensive validation functions for security and error prevention.
#### `validate_command/1`
Validate a command before execution.
```elixir
:ok = Argos.Validation.validate_command("ls -la")
{:error, reason} = Argos.Validation.validate_command("")
```
#### `validate_process_name/1`
Validate a process name.
```elixir
:ok = Argos.Validation.validate_process_name("my_process")
{:error, reason} = Argos.Validation.validate_process_name("")
```
#### `validate_worker_spec/1`
Validate a worker specification.
```elixir
spec = %{id: :worker1, tasks: [fn -> :ok end]}
:ok = Argos.Validation.validate_worker_spec(spec)
```
#### `escape_process_name/1`
Escape process name for safe shell usage.
```elixir
safe_name = Argos.Validation.escape_process_name("process'name")
```
### Circuit Breaker
Prevent cascading failures with circuit breaker pattern.
```elixir
# Circuit breaker is automatically used when circuit_breaker: true option is passed
result = Argos.Command.exec("risky_command", circuit_breaker: true)
# Manual circuit breaker usage
{:ok, pid} = Argos.Command.CircuitBreaker.start_link([])
result = Argos.Command.CircuitBreaker.call(pid, fn -> risky_operation() end)
```
**States:**
- `:closed` - Normal operation, commands execute
- `:open` - Circuit is open, commands are rejected
- `:half_open` - Testing if service recovered
### Logger Module
Structured logging with Aurora integration.
```elixir
Argos.log(:info, "Informative message")
Argos.log(:error, "Operation failed", module: __MODULE__, line: 123)
Argos.log(:warning, "Important warning")
```
## 🖥️ CLI Usage
### Building the Executable
```bash
mix escript.build
```
This generates the `./argos` executable.
### Basic Usage
```bash
# Execute a single command
./argos --command="echo 'Hello World'"
# Execute multiple commands in parallel
./argos --command="echo 'First'" --command="echo 'Second'"
# Short alias
./argos -c "ls -la"
```
### Advanced Usage
```bash
# Commands with ANSI codes (preserved)
./argos --command="echo \"$(aurora --text='Hello' --color=primary)\""
# Use shell aliases (loads .zshrc/.bashrc by default)
./argos --command="my_alias arguments"
# Don't load shell configuration
./argos --no-config --command="clean_command"
# Use ASDF versions from project's .tool-versions
./argos --asdf-local --command="cd /path/to/project && mix test"
```
### CLI Options
- `-c, --command="COMMAND"` - Command to execute (can be specified multiple times)
- `--load-config` - Explicitly load shell configuration file
- `--no-config` - Don't load shell configuration file
- `--asdf-local` - Use ASDF versions from project's `.tool-versions` file
- `-h, --help` - Show help
- `-e, --examples` - Show usage examples
- `-v, --version` - Show version
### CLI Features
- ✅ Commands execute in parallel using Elixir Tasks
- ✅ Output is shown in real-time preserving ANSI codes
- ✅ Shell configuration (`.zshrc`/`.bashrc`) is loaded by default for aliases
- ✅ Exit code is 0 if all commands succeed, 1 if any fails
- ✅ Help is shown automatically if no command is specified
## 📊 CommandResult Structure
```elixir
%Argos.CommandResult{
command: "ls -la", # Executed command
args: [], # Arguments
output: "total 48\n...", # Command output
exit_code: 0, # Exit code
success?: true, # Execution success
error: nil, # Error if occurred
duration: 15 # Duration in milliseconds
}
```
## ⚙️ Configuration
### Circuit Breaker
Configure circuit breaker in `config/config.exs`:
```elixir
config :argos, :circuit_breaker,
threshold: 5, # Number of failures before opening circuit
timeout: 60_000, # Time in ms before attempting half_open
enabled: true # Enable/disable circuit breaker
```
### Logger
Configure a custom logger:
```elixir
config :argos, :logger, MyCustomLogger
# Logger must implement Argos.Logger.Behaviour
defmodule MyCustomLogger do
@behaviour Argos.Logger.Behaviour
@impl Argos.Logger.Behaviour
def log(level, message, metadata) do
# Your implementation
end
end
```
### Shell
Configure default shell:
```elixir
config :argos, :shell, "/bin/zsh"
config :argos, :shell_timeout, 30_000 # Timeout in ms
```
## 🔧 Development
### Running Tests
```bash
mix test
```
### Code Quality
```bash
mix quality # Runs credo, dialyzer, and format check
```
### Building Documentation
```bash
mix docs
```
### Building Executable
```bash
mix escript.build
```
## 📐 Architecture
Argos follows a modular architecture:
- **Command Module**: System command execution with structured results
- **Parallel Module**: Worker orchestration with Leader, Monitor, and Supervisor
- **Validation Module**: Input validation and security checks
- **Circuit Breaker**: Failure prevention pattern
- **Logger Module**: Structured logging with Aurora integration
- **CLI Modules**: Parser, Executor, Formatter for command-line interface
### Parallel System Components
- **Leader**: Coordinates worker lifecycle and events
- **Monitor**: Aggregates state from all workers
- **Supervisor**: Supervises all parallel system processes
- **Worker**: Executes tasks sequentially within a worker
- **Logger**: Individual log files per worker (optional)
## 🤝 Contributing
Contributions are welcome! Please:
1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass (`mix test`)
5. Run code quality checks (`mix quality`)
6. Submit a pull request
## 📄 License
Apache 2.0 - See [LICENSE](LICENSE) for details.
## 🙏 Acknowledgments
Argos is part of the Ypsilon project ecosystem, designed to provide powerful, elegant tools for Elixir developers.
---
**Made with ⚡ for reliable command execution and parallel task orchestration**