Skip to content

Cobra Interoperability

BOA is built on top of Cobra and provides full access to Cobra's primitives. This design allows you to:

  • Access the underlying *cobra.Command when you need low-level control
  • Mix BOA commands with existing Cobra commands in the same command tree
  • Migrate existing Cobra applications to BOA incrementally, one command at a time
  • Use existing Cobra plugins and ecosystem libraries

Exposed Cobra Types

BOA's CmdT struct directly exposes Cobra types in its API - no wrapping or abstraction:

type CmdT[Struct any] struct {
    // ...
    GroupID string              // Cobra's group ID for help categorization
    Groups  []*cobra.Group      // Cobra's Group type directly
    Args    cobra.PositionalArgs // Cobra's positional args validation
    SubCmds []*cobra.Command    // Cobra's Command type directly
    // ...
}

This means you can use Cobra types directly when configuring BOA commands:

boa.CmdT[Params]{
    Use:     "myapp",
    Groups:  []*cobra.Group{{ID: "admin", Title: "Admin Commands:"}},
    Args:    cobra.ExactArgs(2),
    SubCmds: []*cobra.Command{existingCobraCmd, anotherCobraCmd},
    // ...
}

Accessing the Underlying Cobra Command

BOA commands can be converted to Cobra commands using ToCobra():

boaCmd := boa.CmdT[Params]{
    Use:   "myapp",
    Short: "My application",
    RunFunc: func(params *Params, cmd *cobra.Command, args []string) {
        // ...
    },
}

// Get the underlying *cobra.Command
cobraCmd := boaCmd.ToCobra()

// Now you can use any Cobra API
cobraCmd.SetHelpTemplate("Custom help template...")
cobraCmd.SetUsageFunc(customUsageFunc)

Cobra Access in Run Functions

The *cobra.Command is available in run functions and lifecycle hooks:

boa.CmdT[Params]{
    Use: "myapp",
    RunFunc: func(params *Params, cmd *cobra.Command, args []string) {
        // cmd is the *cobra.Command
        fmt.Println("Command name:", cmd.Name())
        fmt.Println("Positional args:", args)

        // Access Cobra features
        cmd.Println("Using Cobra's output methods")
    },
}.Run()

Mixing BOA and Cobra Commands

The SubCmds field accepts []*cobra.Command, meaning you can freely mix BOA commands with pure Cobra commands:

Adding Cobra Commands to a BOA Parent

// Existing Cobra command (from your codebase or a library)
legacyCmd := &cobra.Command{
    Use:   "legacy",
    Short: "A legacy Cobra command",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("Running legacy command")
    },
}

// BOA root command with mixed subcommands
boa.CmdT[RootParams]{
    Use:   "myapp",
    Short: "Application with mixed commands",
    SubCmds: []*cobra.Command{
        legacyCmd,  // Pure Cobra command
        boa.CmdT[ServeParams]{
            Use:   "serve",
            Short: "Start the server",
            RunFunc: func(p *ServeParams, cmd *cobra.Command, args []string) { /* ... */ },
        }.ToCobra(),  // BOA command converted to Cobra
    },
}.Run()

Adding BOA Commands to a Cobra Parent

// Existing Cobra root command
rootCmd := &cobra.Command{
    Use:   "myapp",
    Short: "My application",
}

// Add BOA subcommands to Cobra parent
rootCmd.AddCommand(
    boa.CmdT[ServeParams]{
        Use:   "serve",
        Short: "Start the server",
        RunFunc: func(p *ServeParams, cmd *cobra.Command, args []string) { /* ... */ },
    }.ToCobra(),

    boa.CmdT[MigrateParams]{
        Use:   "migrate",
        Short: "Run migrations",
        RunFunc: func(p *MigrateParams, cmd *cobra.Command, args []string) { /* ... */ },
    }.ToCobra(),
)

rootCmd.Execute()

Incremental Migration Strategy

BOA's Cobra interoperability enables gradual migration of existing Cobra applications. You can migrate one command at a time without disrupting the entire codebase.

Step 1: Start with Your Existing Cobra Tree

// Your existing Cobra command structure
func main() {
    rootCmd := &cobra.Command{Use: "myapp"}

    rootCmd.AddCommand(serveCmd)   // Cobra command
    rootCmd.AddCommand(migrateCmd) // Cobra command
    rootCmd.AddCommand(configCmd)  // Cobra command

    rootCmd.Execute()
}

Step 2: Migrate One Command to BOA

func main() {
    rootCmd := &cobra.Command{Use: "myapp"}

    rootCmd.AddCommand(serveCmd)   // Still Cobra
    rootCmd.AddCommand(migrateCmd) // Still Cobra

    // Migrated to BOA - now with type-safe params!
    rootCmd.AddCommand(
        boa.CmdT[ConfigParams]{
            Use:   "config",
            Short: "Manage configuration",
            RunFunc: func(p *ConfigParams, cmd *cobra.Command, args []string) {
                // Type-safe access to parameters
            },
        }.ToCobra(),
    )

    rootCmd.Execute()
}

Step 3: Eventually Migrate the Root

func main() {
    // Root is now BOA, subcommands can be either
    boa.CmdT[RootParams]{
        Use: "myapp",
        SubCmds: []*cobra.Command{
            serveCmd,   // Legacy Cobra commands
            migrateCmd,
            boa.CmdT[ConfigParams]{
                Use: "config",
                RunFunc: func(p *ConfigParams, cmd *cobra.Command, args []string) { /* ... */ },
            }.ToCobra(),
        },
    }.Run()
}

Using Cobra's PositionalArgs

BOA supports Cobra's positional argument validation:

boa.CmdT[Params]{
    Use:  "greet [names...]",
    Args: cobra.MinimumNArgs(1), // Cobra's validation
    RunFunc: func(params *Params, cmd *cobra.Command, args []string) {
        for _, name := range args {
            fmt.Printf("Hello, %s!\n", name)
        }
    },
}.Run()

Using Cobra's Command Groups

Organize subcommands with Cobra's grouping feature:

boa.CmdT[boa.NoParams]{
    Use:   "myapp",
    Groups: []*cobra.Group{
        {ID: "core", Title: "Core Commands:"},
        {ID: "util", Title: "Utility Commands:"},
    },
    SubCmds: boa.SubCmds(
        boa.CmdT[ServeParams]{
            Use:     "serve",
            GroupID: "core",
            RunFunc: func(p *ServeParams, cmd *cobra.Command, args []string) { /* ... */ },
        },
        boa.CmdT[StatusParams]{
            Use:     "status",
            GroupID: "util",
            RunFunc: func(p *StatusParams, cmd *cobra.Command, args []string) { /* ... */ },
        },
    ),
}.Run()

Cobra Ecosystem Compatibility

Since BOA commands convert to standard *cobra.Command, you can use the entire Cobra ecosystem:

Shell Completion

Cobra's built-in completion generators work with BOA:

cmd := boa.CmdT[Params]{
    Use: "myapp",
    SubCmds: boa.SubCmds(/* ... */),
}.ToCobra()

// Add Cobra's completion command
cmd.AddCommand(completionCmd) // Your standard Cobra completion command

Documentation Generation

Use Cobra's doc generation packages:

import "github.com/spf13/cobra/doc"

cmd := boa.CmdT[Params]{Use: "myapp"}.ToCobra()

// Generate markdown docs
doc.GenMarkdownTree(cmd, "./docs")

// Generate man pages
doc.GenManTree(cmd, &doc.GenManHeader{Title: "MYAPP"}, "./man")

Interactive Help with Bubbletea

Libraries like elewis787/boa add interactive TUI help to Cobra (yes, we accidentally picked the same name - theirs adds Bubbletea-powered help to Cobra, ours adds declarative parameter handling):

import eboa "github.com/elewis787/boa"

boa.CmdT[Params]{
    Use: "myapp",
    PostCreateFunc: func(params *Params, cmd *cobra.Command) error {
        cmd.SetUsageFunc(eboa.UsageFunc)
        cmd.SetHelpFunc(eboa.HelpFunc)
        return nil
    },
}.Run()

Flag Constraints (Mutual Exclusion, Required Together)

Cobra supports flag relationship constraints. Use InitFunc to access these via the *cobra.Command:

type Params struct {
    JSON bool   `descr:"output as JSON" optional:"true"`
    YAML bool   `descr:"output as YAML" optional:"true"`
    Host string `descr:"server host"`
    Port int    `descr:"server port" default:"8080"`
}

boa.CmdT[Params]{
    Use: "serve",
    InitFunc: func(params *Params, cmd *cobra.Command) error {
        // --json and --yaml cannot both be set
        cmd.MarkFlagsMutuallyExclusive("json", "yaml")
        // --host and --port must be set together
        cmd.MarkFlagsRequiredTogether("host", "port")
        return nil
    },
    RunFunc: func(params *Params, cmd *cobra.Command, args []string) {
        // ...
    },
}.Run()

Available constraint methods on *cobra.Command: - MarkFlagsMutuallyExclusive(flags ...string) — at most one can be set - MarkFlagsOneRequired(flags ...string) — at least one must be set - MarkFlagsRequiredTogether(flags ...string) — all or none must be set

Summary

Task Method
Convert BOA -> Cobra boaCmd.ToCobra()
Add Cobra subcommands Set SubCmds field with []*cobra.Command
Add BOA subcommands Use boa.SubCmds() helper or call .ToCobra()
Access *cobra.Command in run Use RunFunc with full signature
Use Cobra arg validation Set Args field
Use Cobra groups Set Groups and GroupID fields
Use Cobra ecosystem libs Call ToCobra() then use standard Cobra APIs