Custom Commands in gastownhall/gastown: How to Extend the gt CLI

The gastown repository implements a pluggable command architecture where each Go file in internal/cmd auto-registers with the Cobra CLI via init() functions, enabling developers to add custom subcommands to the gt tool without modifying existing source code.

The Gastown project provides a multi-agent workflow platform centered around the gt CLI. Rather than using hard-coded command strings, the codebase employs a dynamic registration pattern that treats every file in the internal/cmd directory as a potential custom command extension point.

How Custom Commands Work in Gastown

The architecture follows a simple but powerful pattern: individual command files declare their own initialization logic, and the root CLI discovers them at runtime. This eliminates the need for a central registry that must be updated manually.

CLI Entry Point in cmd/gt/main.go

The journey begins at cmd/gt/main.go, which serves as the entry point for the gt binary. This file creates the Cobra root command and immediately delegates to cmd.Execute(). According to the gastownhall/gastown source code, this execution path triggers the registration of all subcommands found in the internal/cmd package.

Command Registration Pattern

Each custom command follows a consistent registration contract. Files in internal/cmd expose an init() function that calls RootCmd.AddCommand() with a constructor function. For example, internal/cmd/wl.go registers the work-list command using this exact pattern:

func init() {
    RootCmd.AddCommand(NewWLCmd())
}

// NewWLCmd builds the cobra.Command for `gt wl`.
func NewWLCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "wl",
        Short: "Show the work‑list for the current session",
        RunE:  runWLCmd,
    }
}

When the binary starts, Go automatically executes all init() functions, making the command available immediately.

Existing Custom Command Examples

The cmd directory contains several production commands that demonstrate this extensibility pattern. These files illustrate how the platform implements complex functionality through the same custom command interface:

Each file follows the identical structure: an init() hook calling RootCmd.AddCommand(NewXCommand()), followed by the command constructor and execution logic.

Adding a New Custom Command

Creating a custom command requires only a new Go file in internal/cmd. The following example demonstrates adding a gt hello command:

Create internal/cmd/hello.go:

package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

func init() {
    RootCmd.AddCommand(NewHelloCmd())
}

func NewHelloCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "hello",
        Short: "Print a friendly greeting",
        Run: func(cmd *cobra.Command, args []string) {
            fmt.Println("👋 Hello from Gastown!")
        },
    }
}

After recompiling the binary, the command becomes immediately available:

$ gt hello
👋 Hello from Gastown!

No changes to cmd/gt/main.go or existing files are required. The init() function automatically registers the new command with the root CLI at startup.

Summary

  • Auto-registration: Commands in internal/cmd register themselves via init() functions, eliminating central registry maintenance.
  • Cobra framework: The gt CLI uses the Cobra library, with each command returning a *cobra.Command struct from its constructor function.
  • Entry point: cmd/gt/main.go bootstraps the CLI but delegates command discovery to the internal/cmd package.
  • Extensibility: Adding custom commands requires only creating a new file with init() and NewCommand() functions following the established pattern.

Frequently Asked Questions

How do I add a custom command to the gastown CLI?

Create a new Go file in internal/cmd/ containing an init() function that calls RootCmd.AddCommand(NewMyCommand()), plus a constructor function returning a *cobra.Command. The Go runtime automatically registers the command when the binary starts, requiring no changes to existing code.

What is the role of the init() function in gastown commands?

The init() function serves as the registration hook. Each command file uses init() to add its command constructor to the root command. Go executes these functions automatically during package initialization, enabling the modular command architecture found in gastownhall/gastown.

Where are custom commands stored in the gastown repository?

Custom commands reside in the internal/cmd/ directory. While the entry point lives at cmd/gt/main.go, all subcommand implementations—including wl.go, witness.go, and start.go—are located in internal/cmd/.

Does gastown require manual registration of new commands?

No. The gastown CLI uses runtime discovery via Go's init() mechanism. Simply placing a properly structured Go file in internal/cmd/ automatically makes the command available to users after recompilation, without manual registry updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →