zinit 0.2.1

A process supervisor with Rhai scripting support
Documentation
# Zinit

A lightweight process supervisor with Rhai scripting support.

Zinit manages services (long-running processes) with dependency ordering, health checks, automatic restarts, and a powerful scripting interface.

## Features

- **Rhai Scripting** - Control everything via scripts, not CLI flags
- **Interactive REPL** - Tab completion, syntax highlighting, command history
- **Real-time Output** - `print()` statements stream to client as they execute
- **Single Instance** - Only one zinit runs at a time via socket locking
- **Auto-start** - Running a script automatically starts zinit if needed
- **Service Dependencies** - Start services in order with `after()`
- **Health Checks** - Optional test commands to verify service health
- **Resource Stats** - CPU and memory usage per service
- **Name Normalization** - Service names are case-insensitive, `-` and `_` are equivalent

## Quick Start

```bash
# Build and install
cargo build --release
cp target/release/zinit ~/hero/bin/

# Run a script (auto-starts zinit)
zinit rhaiexamples/01_full_test.rhai

# Or use inline scripts
echo 'print("Hello from zinit!"); zinit_list_md()' | zinit -i
```

## Usage

```bash
zinit start                 # Start daemon (foreground)
zinit start -bg             # Start daemon (background)
zinit <script.rhai>         # Run script file
zinit <directory>           # Run all .rhai files in directory
zinit -i                    # Read script from stdin
zinit --ui                  # Interactive REPL with completion
zinit --help                # Show API documentation
```

## Interactive REPL

Start the interactive shell with `zinit --ui`:

```
╔═══════════════════════════════════════════════════════════╗
║           Zinit Interactive Shell (Rhai REPL)             ║
╠═══════════════════════════════════════════════════════════╣
║  Tab: completion | Up/Down: history | Ctrl+D: exit        ║
║  Type /help for commands, /functions for API              ║
╚═══════════════════════════════════════════════════════════╝

zinit> zinit_list_md()
| Service | State | PID | Target |
|---------|-------|-----|--------|
| webserver | Running | 1234 | Up |

zinit> 
```

### REPL Features

- **Tab Completion** - Type `zinit_` and press Tab to see all functions
- **Syntax Highlighting** - Keywords, functions, strings are colored
- **Command History** - Up/Down arrows, persisted to `~/.zinit_history`
- **Multi-line Input** - Use `{` `}` braces for multi-line scripts

### REPL Commands

| Command | Description |
|---------|-------------|
| `/help` | Show help |
| `/functions` | List all zinit functions with signatures |
| `/builder` | Show service builder pattern |
| `/load <file>` | Load and execute a .rhai script file |
| `/clear` | Clear screen |
| `/quit` | Exit (or Ctrl+D) |

### Example Session

```
zinit> /functions
Zinit Functions:

  Control:
    zinit_ping()       -> bool   Check if zinit is responding
    zinit_start()      -> bool   Start zinit daemon if not running
    ...

zinit> /load rhaiexamples/01_full_test.rhai
Loading: rhaiexamples/01_full_test.rhai
=== Zinit Full Test ===
Step 1: Creating webserver service...
...

zinit> zinit_stop("webserver")
true

zinit> /quit
Goodbye!
```

## Fixed Paths

Zinit uses fixed paths - no configuration needed:

| Path | Purpose |
|------|---------|
| `~/hero/cfg/zinit/` | Service YAML files |
| `~/hero/var/zinit.sock` | Unix socket for RPC |
| `~/hero/bin/zinit` | Recommended binary location |

## Example Script

```rhai
// Create and start a web server service
let webserver = zinit_service_new()
    .exec("python3 -m http.server 8080")
    .log("ring");

zinit_monitor("webserver", webserver);
zinit_start("webserver");

// Wait for it to be running
if zinit_wait_for("webserver", 5) {
    print("Web server is running!");
} else {
    print("Failed to start web server");
}

// Show status
print(zinit_status_md("webserver"));
```

## API Reference

### Service Management

| Function | Returns | Description |
|----------|---------|-------------|
| `zinit_service_new()` | ServiceBuilder | Create service builder |
| `zinit_monitor(name, builder)` | bool | Register a service |
| `zinit_start(name)` | bool | Start a service |
| `zinit_stop(name)` | bool | Stop a service |
| `zinit_restart(name)` | bool | Restart a service |
| `zinit_forget(name)` | bool | Unregister a stopped service |
| `zinit_kill(name, signal)` | bool | Send signal (e.g., "SIGTERM") |

### Service Builder

```rhai
zinit_service_new()
    .exec("command args")        // Required: command to run
    .test("health-check")        // Optional: health check command
    .oneshot(true)               // Optional: run once, don't restart
    .shutdown_timeout(10)        // Optional: seconds before SIGKILL
    .after("other-service")      // Optional: dependency
    .signal_stop("SIGTERM")      // Optional: stop signal
    .log("ring")                 // Optional: "ring", "stdout", "none"
    .env("KEY", "value")         // Optional: environment variable
    .dir("/path")                // Optional: working directory
```

### Query Functions

| Function | Returns | Description |
|----------|---------|-------------|
| `zinit_list()` | array | List service names |
| `zinit_status(name)` | map | Get service status |
| `zinit_stats(name)` | map | Get CPU/memory stats |
| `zinit_is_running(name)` | bool | Check if running |
| `zinit_wait_for(name, secs)` | bool | Wait for service |

### Markdown Output

| Function | Returns | Description |
|----------|---------|-------------|
| `zinit_list_md()` | string | Services as markdown table |
| `zinit_status_md(name)` | string | Status as markdown |
| `zinit_stats_md(name)` | string | Stats as markdown |

### Zinit Control

| Function | Returns | Description |
|----------|---------|-------------|
| `zinit_ping()` | bool | Check if zinit responds |
| `zinit_start()` | bool | Start zinit if not running |
| `zinit_exit()` | void | Graceful shutdown |
| `zinit_stop_all()` | int | Stop all services |
| `zinit_start_all()` | int | Start all services |

### Utilities

| Function | Description |
|----------|-------------|
| `print(msg)` | Print (streams to client) |
| `sleep(secs)` | Sleep seconds |
| `sleep_ms(ms)` | Sleep milliseconds |
| `get_env(key)` | Get environment variable |
| `set_env(key, val)` | Set environment variable |
| `file_exists(path)` | Check if path exists |

## Socket Protocol

Zinit uses a Unix socket at `~/hero/var/zinit.sock`. You can interact with it directly using netcat or socat.

### Protocol Format

**Send script:**
```
<rhai script lines>
===
```

**Receive response:**
```
ok
<output lines streamed in real-time>
=====
```

### Using Netcat

```bash
# Simple query
printf 'zinit_list_md()\n===\n' | nc -U ~/hero/var/zinit.sock

# Multi-line script
cat << 'EOF' | nc -U ~/hero/var/zinit.sock
print("Starting test...");
let services = zinit_list();
print("Found " + services.len() + " services");
for svc in services {
    print("  - " + svc);
}
print("Done!");
===
EOF
```

### Using Socat (Recommended for Interactive)

```bash
# Install socat (macOS)
brew install socat

# Simple query
printf 'zinit_list_md()\n===\n' | socat - UNIX-CONNECT:$HOME/hero/var/zinit.sock

# Interactive mode with readline support
socat READLINE UNIX-CONNECT:$HOME/hero/var/zinit.sock
# Then type your script and end with ===
# Example:
# zinit_list_md()
# ===

# Send a script file
(cat script.rhai; printf '\n===\n') | socat - UNIX-CONNECT:$HOME/hero/var/zinit.sock
```

### Bash Function for Easy Access

Add this to your `.bashrc` or `.zshrc`:

```bash
# Send Rhai script to zinit
zexec() {
    if [ -t 0 ]; then
        # Argument mode: zexec 'zinit_list_md()'
        printf '%s\n===\n' "$1" | nc -U ~/hero/var/zinit.sock
    else
        # Pipe mode: echo 'zinit_list_md()' | zexec
        (cat; printf '\n===\n') | nc -U ~/hero/var/zinit.sock
    fi
}

# Examples:
# zexec 'zinit_list_md()'
# zexec 'zinit_stop("myservice")'
# echo 'print("hello")' | zexec
```

### Python Example

```python
import socket

def send_script(script):
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect(f"{os.path.expanduser('~')}/hero/var/zinit.sock")
    
    # Send script with delimiter
    sock.sendall(f"{script}\n===\n".encode())
    
    # Read response
    response = b""
    while True:
        chunk = sock.recv(4096)
        if not chunk:
            break
        response += chunk
        if b"=====\n" in response:
            break
    
    sock.close()
    
    lines = response.decode().split('\n')
    status = lines[0]  # "ok" or "error"
    output = '\n'.join(lines[1:-2])  # Remove status and delimiter
    return status, output

# Usage
status, output = send_script('zinit_list_md()')
print(output)
```

## Real-Time Output

Output from `print()` streams to the client in real-time:

```rhai
print("Starting long operation...");
sleep(2);
print("Step 1 complete");
sleep(2);
print("Step 2 complete");
print("Done!");
```

Each line appears as it executes, not all at once when the script finishes.

## Service States

| State | Description |
|-------|-------------|
| `Spawned` | Process started, not yet confirmed running |
| `Running` | Process is running |
| `Success` | Oneshot completed successfully |
| `Error` | Process exited with error |
| `Blocked` | Waiting for dependencies |
| `Unknown` | Service registered but not monitored |

## Examples

See the `rhaiexamples/` directory:

- `01_full_test.rhai` - Complete workflow: create, start, query, stop, forget
- `02_stream_test.rhai` - Test real-time output streaming

## Documentation

- `zinit --help` - API documentation (also in `src/instructions.md`)
- `rhaiinstructions.md` - Technical implementation details
- [docs.rs/zinit]https://docs.rs/zinit - Rust API documentation

## Building

```bash
# Debug build
cargo build

# Release build
cargo build --release

# Run tests
cargo test
```

## License

Apache 2.0