Orpheus Examples

May 24, 2026 · View on GitHub

Positional Arguments (v1.3.0)

Use ctx.Positional() / ctx.PositionalCount() / ctx.GetPositional(index) when you need the post-parse positional values without flag tokens.

// Before v1.3.0 — ctx.GetArg(0) returns "--output" when the user
// types: app convert --output report.pdf input.txt
// A check like "if ctx.ArgCount() < 1" silently accepts the call.

// v1.3.0 — use Positional* to express the intent correctly.
convertCmd := orpheus.NewCommand("convert", "Convert a file").
    AddFlag("output", "o", "", "Output file path").
    SetHandler(func(ctx *orpheus.Context) error {
        if ctx.PositionalCount() < 1 {
            return orpheus.ValidationError("convert", "input file required").
                WithUserMessage("Please provide the input file path")
        }
        input := ctx.GetPositional(0)  // "input.txt" — flag tokens excluded
        output := ctx.GetFlagString("output")
        fmt.Printf("Converting %s%s\n", input, output)
        return nil
    })

Context Cancellation (v1.4.0)

Run already provides Orpheus' default signal-aware context. Use RunContext when the application owns cancellation, deadlines, or a parent tracing context. Command handlers can read the active execution context through ctx.Context().

package main

import (
    "context"
    "log"
    "os"
    "os/signal"
    "syscall"

    "github.com/agilira/orpheus/pkg/orpheus"
)

func main() {
    runCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
    defer stop()

    app := orpheus.New("worker")
    app.Command("run", "Run the worker", func(ctx *orpheus.Context) error {
        select {
        case <-ctx.Context().Done():
            return ctx.Context().Err()
        default:
            return doWork(ctx.Context())
        }
    })

    if err := app.RunContext(runCtx, os.Args[1:]); err != nil {
        log.Fatal(err)
    }
}

Advanced Features

Custom Completion

deployCmd := orpheus.NewCommand("deploy", "Deploy to environment").
    SetHandler(func(ctx *orpheus.Context) error {
        env := ctx.GetArg(0)
        fmt.Printf("Deploying to %s...\n", env)
        return nil
    }).
    SetCompletionHandler(func(req *orpheus.CompletionRequest) *orpheus.CompletionResult {
        if req.Type == orpheus.CompletionArgs && req.Position == 0 {
            return &orpheus.CompletionResult{
                Suggestions: []string{"production", "staging", "development"},
            }
        }
        return &orpheus.CompletionResult{Suggestions: []string{}}
    })

app.AddCommand(deployCmd)

Shell Completion Setup

# Generate completion script
./myapp completion bash > /etc/bash_completion.d/myapp

# Or for zsh
./myapp completion zsh > "${fpath[1]}/_myapp"

# Or for fish
./myapp completion fish > ~/.config/fish/completions/myapp.fish

Advanced Use Cases

Subcommands (Git-style)

Orpheus now supports native subcommands with elegant parent-child relationships:

package main

import (
    "fmt"
    "log"
    "os"
    
    "github.com/agilira/orpheus/pkg/orpheus"
)

func main() {
    app := orpheus.New("gitlike").
        SetDescription("Git-style CLI with native subcommands").
        SetVersion("1.0.0")

    // Create parent commands
    remoteCmd := orpheus.NewCommand("remote", "Manage remote repositories")
    configCmd := orpheus.NewCommand("config", "Configuration management")
    branchCmd := orpheus.NewCommand("branch", "Branch operations")

    // Add subcommands using the native API
    remoteCmd.Subcommand("add", "Add a new remote", func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 2 {
            return orpheus.ValidationError("remote add", "requires name and URL").
                WithUserMessage("Please provide both remote name and URL").
                WithContext("expected_args", []string{"name", "url"}).
                WithContext("provided_args", ctx.ArgCount())
        }
        name, url := ctx.GetArg(0), ctx.GetArg(1)
        fmt.Printf("Added remote: %s -> %s\n", name, url)
        return nil
    })

    remoteCmd.Subcommand("remove", "Remove a remote", func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 1 {
            return orpheus.ValidationError("remote remove", "requires remote name").
                WithUserMessage("Please specify the name of the remote to remove")
        }
        name := ctx.GetArg(0)
        fmt.Printf("Removed remote: %s\n", name)
        return nil
    })

    remoteCmd.Subcommand("list", "List all remotes", func(ctx *orpheus.Context) error {
        fmt.Println("origin\thttps://github.com/user/repo.git")
        fmt.Println("upstream\thttps://github.com/upstream/repo.git")
        return nil
    })

    // Config subcommands with persistence
    configCmd.Subcommand("set", "Set configuration value", func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 2 {
            return orpheus.ValidationError("config set", "requires key and value")
        }
        key, value := ctx.GetArg(0), ctx.GetArg(1)
        // Save to config file
        fmt.Printf("Set: %s = %s\n", key, value)
        return nil
    })

    configCmd.Subcommand("get", "Get configuration value", func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 1 {
            return orpheus.ValidationError("config get", "requires key")
        }
        key := ctx.GetArg(0)
        // Load from config file
        fmt.Printf("user.name\n") // Example output
        return nil
    })

    configCmd.Subcommand("list", "List all configuration", func(ctx *orpheus.Context) error {
        fmt.Println("user.name=Developer")
        fmt.Println("user.email=dev@example.com")
        return nil
    })

    // Branch subcommands
    branchCmd.Subcommand("list", "List branches", func(ctx *orpheus.Context) error {
        fmt.Println("* main")
        fmt.Println("  develop")
        fmt.Println("  feature/new-ui")
        return nil
    })

    branchCmd.Subcommand("create", "Create branch", func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 1 {
            return orpheus.ValidationError("branch create", "requires branch name")
        }
        name := ctx.GetArg(0)
        fmt.Printf("Created branch '%s'\n", name)
        return nil
    })

    // Add commands to app
    app.AddCommand(remoteCmd)
    app.AddCommand(configCmd)
    app.AddCommand(branchCmd)

    // Usage examples:
    // ./gitlike remote add origin https://github.com/user/repo.git
    // ./gitlike config set user.name "Developer"
    // ./gitlike branch create feature/awesome
    // ./gitlike remote --help    (shows subcommands automatically)
    
    if err := app.Run(os.Args[1:]); err != nil {
        if orpheusErr, ok := err.(*orpheus.OrpheusError); ok {
            log.Printf("Error: %s", orpheusErr.UserMessage())
            if orpheusErr.IsRetryable() {
                log.Printf("This operation can be retried")
            }
            os.Exit(orpheusErr.ExitCode())
        }
        log.Fatal(err)
    }
}

Key advantages of subcommands:

  • Automatic help generation: ./app remote --help shows all subcommands
  • Clean command structure: Parent-child relationships are explicit
  • Path-aware context: Subcommands know their full command path
  • Elegant API: No manual argument parsing or switch statements
  • Completion support: Auto-completion works for nested commands

Working example: Check ./examples/gitlike/ for a complete implementation with JSON persistence.

Subcommand Aliases and Shortcuts

With native subcommands, you can create aliases at both command and subcommand levels:

func main() {
    app := orpheus.New("myapp").
        SetDescription("CLI with subcommand aliases").
        SetVersion("1.0.0")

    // Git-style remote command with aliases
    remoteCmd := orpheus.NewCommand("remote", "Manage remotes")
    
    // Add subcommands with aliases
    listHandler := func(ctx *orpheus.Context) error {
        fmt.Println("origin\thttps://github.com/user/repo.git")
        return nil
    }
    
    // Multiple aliases for the same subcommand
    remoteCmd.Subcommand("list", "List all remotes", listHandler)
    remoteCmd.Subcommand("ls", "List all remotes (alias)", listHandler)
    remoteCmd.Subcommand("l", "List all remotes (short)", listHandler)
    
    // Remove with aliases
    removeHandler := func(ctx *orpheus.Context) error {
        if ctx.ArgCount() < 1 {
            return orpheus.ValidationError("remote remove", "requires name")
        }
        fmt.Printf("Removed remote: %s\n", ctx.GetArg(0))
        return nil
    }
    
    remoteCmd.Subcommand("remove", "Remove a remote", removeHandler)
    remoteCmd.Subcommand("rm", "Remove a remote (alias)", removeHandler)
    remoteCmd.Subcommand("delete", "Remove a remote (alias)", removeHandler)
    
    app.AddCommand(remoteCmd)
    
    // Usage examples:
    // ./myapp remote list    -> lists remotes
    // ./myapp remote ls      -> same as list (alias)
    // ./myapp remote l       -> same as list (short alias)
    // ./myapp remote remove origin
    // ./myapp remote rm origin     -> same as remove (alias)
    // ./myapp remote delete origin -> same as remove (alias)
    
    return app.Run(os.Args[1:])
}

Advanced Subcommand Patterns

Modern patterns using native subcommands:

// Docker-style container management
func main() {
    app := orpheus.New("docker-like").SetVersion("1.0.0")
    
    containerCmd := orpheus.NewCommand("container", "Container operations")
    
    // Add flags to parent command that apply to all subcommands
    containerCmd.AddFlag("host", "H", "localhost", "Docker host")
    containerCmd.AddBoolFlag("tls", "", false, "Use TLS")
    
    // Subcommands inherit parent flags
    containerCmd.Subcommand("run", "Run a container", func(ctx *orpheus.Context) error {
        host := ctx.GetFlagString("host")
        image := ctx.GetArg(0)
        fmt.Printf("Running %s on %s\n", image, host)
        return nil
    })
    
    containerCmd.Subcommand("exec", "Execute in container", func(ctx *orpheus.Context) error {
        container := ctx.GetArg(0)
        command := ctx.GetArg(1)
        fmt.Printf("Executing '%s' in %s\n", command, container)
        return nil
    })
    
    containerCmd.Subcommand("logs", "Show container logs", func(ctx *orpheus.Context) error {
        container := ctx.GetArg(0)
        follow := ctx.GetFlagBool("follow")
        fmt.Printf("Showing logs for %s (follow: %v)\n", container, follow)
        return nil
    })
    
    // Add subcommand-specific flags
    logsCmd := orpheus.NewCommand("logs", "Show container logs").
        SetHandler(func(ctx *orpheus.Context) error {
            container := ctx.GetArg(0)
            follow := ctx.GetFlagBool("follow")
            lines := ctx.GetFlagInt("tail")
            fmt.Printf("Logs for %s (follow: %v, lines: %d)\n", container, follow, lines)
            return nil
        })
    logsCmd.AddBoolFlag("follow", "f", false, "Follow log output")
    logsCmd.AddIntFlag("tail", "t", 100, "Number of lines to show")
    
    containerCmd.AddCommand(logsCmd)
    app.AddCommand(containerCmd)
    
    // Usage:
    // ./app container --host remote.docker.com run nginx
    // ./app container logs --follow --tail 50 mycontainer
    
    return app.Run(os.Args[1:])
}

// Kubernetes-style resource management
func setupK8sStyle() *orpheus.App {
    app := orpheus.New("kubectl-like").SetVersion("1.0.0")
    
    // Global flags
    app.AddGlobalFlag("namespace", "n", "default", "Kubernetes namespace")
    app.AddGlobalFlag("kubeconfig", "", "", "Path to kubeconfig file")
    
    // Get command with resource types as subcommands
    getCmd := orpheus.NewCommand("get", "Display resources")
    getCmd.Subcommand("pods", "List pods", func(ctx *orpheus.Context) error {
        ns := ctx.GetGlobalFlagString("namespace")
        fmt.Printf("Listing pods in namespace: %s\n", ns)
        return nil
    })
    getCmd.Subcommand("services", "List services", func(ctx *orpheus.Context) error {
        ns := ctx.GetGlobalFlagString("namespace")
        fmt.Printf("Listing services in namespace: %s\n", ns)
        return nil
    })
    
    app.AddCommand(getCmd)
    return app
}

Orpheus • an AGILira library