Adding CLI Commands
Runix uses cobra for CLI commands. This guide shows how to add a new command.
Step 1: Create the Command File
Create cmd/runix/<command>.go:
package main
import (
"fmt"
"os"
"github.com/runixio/runix/internal/daemon"
"github.com/spf13/cobra"
)
func newMyCommand() *cobra.Command {
var format string
cmd := &cobra.Command{
Use: "mycommand <id|name>",
Short: "Short description of my command",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
target := args[0]
// Support dry-run
if dryRun {
fmt.Fprintf(os.Stdout, "[Runix] Dry run: would mycommand %q\n", target)
return nil
}
// Try daemon IPC first
if daemonIsRunning() {
resp, err := sendIPC(daemon.ActionMyCommand, daemon.MyPayload{
Target: target,
})
if err != nil {
fmt.Fprintf(os.Stderr, "[Runix] Daemon IPC failed, using direct mode: %v\n", err)
} else if !resp.Success {
return fmt.Errorf("daemon error: %s", resp.Error)
} else {
fmt.Fprintf(os.Stdout, "[Runix] Done\n")
return nil
}
}
// Direct mode fallback
sup, err := getSupervisor()
if err != nil {
return err
}
// ... implement direct mode logic ...
return nil
},
}
cmd.Flags().StringVarP(&format, "format", "f", "text", "output format: text, json")
return cmd
}
Step 2: Register in root.go
Open cmd/runix/root.go and add the command to the init() function:
rootCmd.AddCommand(newMyCommand())
Step 3: Add Daemon IPC Action (if needed)
If the command should work through the daemon:
protocol.go — Add action constant and payload
const ActionMyCommand = "mycommand"
type MyPayload struct {
Target string `json:"target"`
}
server.go — Add handler
case daemon.ActionMyCommand:
// Handle the action
client.go — Add client method (if needed)
No changes needed — sendIPC() handles all actions generically.
Patterns to Follow
Dual-Mode Execution
Every command must follow the dual-mode pattern:
// 1. Try daemon IPC
if daemonIsRunning() {
resp, err := sendIPC(action, payload)
if err != nil {
// Log and fall through to direct mode
} else if !resp.Success {
return fmt.Errorf("daemon error: %s", resp.Error)
} else {
// Success via daemon
return nil
}
}
// 2. Direct mode (in-process supervisor)
sup, err := getSupervisor()
if err != nil {
return err
}
// ... execute logic ...
Output Formatting
Use the outputResult() helper for dual text/JSON output:
outputResult(format, resultData, func() {
fmt.Fprintf(os.Stdout, "[Runix] Human-readable message\n")
})
Target Resolution
Use sup.Get(target) which handles:
- Exact numeric ID match
- Exact name match
- Unique UUID prefix match
Global Flags
These are available automatically via root.go:
| Variable | Flag | Description |
|---|---|---|
dryRun | --dry-run | Print what would happen |
cfgFile | --config | Config file path |
verbose | --verbose | Verbose output |
What's Next
- Adding IPC Actions — Daemon-side implementation
- Contributing — Code conventions