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

# 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

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

// 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

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

# 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)

# 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:

# 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

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:

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 - Rust API documentation

Building

# Debug build
cargo build

# Release build
cargo build --release

# Run tests
cargo test

License

Apache 2.0