# How to Contribute New Commands to the Gastown CLI via the cmd Directory

> Learn to contribute new commands to the gastownhall/gastown project. Add your own Go commands to the cmd directory and register them with the root command easily.

- Repository: [Gas Town Hall/gastown](https://github.com/gastownhall/gastown)
- Tags: how-to-guide
- Published: 2026-07-07

---

**TLDR:** You contribute new commands by creating Go source files in the `internal/cmd/` directory that define a **Cobra** command struct, implement the logic in a `RunE` closure, and register the command to the **root command** via an `init()` function.

The **gastownhall/gastown** project provides a command-line interface built on the popular Cobra framework. To extend the CLI’s functionality, developers add self-contained command files to the `internal/cmd` package, following a consistent registration pattern that keeps the codebase modular and testable.

## Understanding the Gastown CLI Architecture

The CLI follows a clean separation between the entry point and command implementations, making it straightforward to add new functionality without touching core bootstrap logic.

### The Entry Point in cmd/gt/main.go

The binary starts at [`cmd/gt/main.go`](https://github.com/gastownhall/gastown/blob/main/cmd/gt/main.go), which serves as a minimal wrapper. According to the source code, this file simply imports the `internal/cmd` package and calls `cmd.Execute()` to bootstrap the application:

```go
// cmd/gt/main.go
package main

import (
    "os"
    "github.com/gastownhall/gastown/internal/cmd"
)

func main() {
    os.Exit(cmd.Execute())
}

```

### The internal/cmd Package Structure

The `internal/cmd` directory contains the **root command** definition and all sub-commands. The central bootstrap lives in the file defining `func Execute()` (typically [`root.go`](https://github.com/gastownhall/gastown/blob/main/root.go)), which initializes the global `rootCmd` variable. Every individual sub-command is a separate Go file that creates a `*cobra.Command` and attaches it to this root during package initialization.

## Step-by-Step Guide to Contributing a New Command

Follow these steps to add a command like `gt hello` to the gastownhall/gastown project.

### 1. Clone and Verify the Build

Start by cloning the repository and ensuring the existing test suite passes:

```bash
git clone https://github.com/gastownhall/gastown.git
cd gastown
go test ./...

```

### 2. Create the Command File

Add a new file at `internal/cmd/<name>.go`. For example, create [`internal/cmd/hello.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/hello.go) to implement a `gt hello` command.

### 3. Define the Cobra Command

Implement a constructor function that returns a `*cobra.Command` with the `Use`, `Short`, and `RunE` fields configured. The `RunE` closure contains your command’s business logic:

```go
// internal/cmd/hello.go
package cmd

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

// NewHelloCmd creates the `hello` sub-command.
func NewHelloCmd() *cobra.Command {
    return &cobra.Command{
        Use:   "hello",
        Short: "Print a friendly greeting",
        RunE: func(cmd *cobra.Command, args []string) error {
            cmd.Println("👋 Hello, Gastown!")
            return nil
        },
    }
}

```

### 4. Register with the Root Command

Add an `init()` function that calls `rootCmd.AddCommand()` to attach your command to the CLI tree:

```go
func init() {
    rootCmd.AddCommand(NewHelloCmd())
}

```

### 5. Write Unit Tests

Create [`internal/cmd/hello_test.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/hello_test.go) to verify execution. Capture stdout and assert on the output:

```go
// internal/cmd/hello_test.go
package cmd

import (
    "bytes"
    "testing"

    "github.com/spf13/cobra"
)

func TestHelloCommand(t *testing.T) {
    buf := new(bytes.Buffer)
    root := &cobra.Command{Use: "gt"}
    root.AddCommand(NewHelloCmd())
    root.SetOut(buf)

    root.SetArgs([]string{"hello"})
    if err := root.Execute(); err != nil {
        t.Fatalf("command failed: %v", err)
    }

    got := buf.String()
    want := "👋 Hello, Gastown!\n"
    if got != want {
        t.Fatalf("unexpected output: got %q, want %q", got, want)
    }
}

```

### 6. Validate and Submit

Run the full test suite to ensure no regressions, then commit your changes and open a pull request:

```bash
go test ./...

```

## Key Files and Conventions

When contributing to the gastownhall/gastown project, reference these critical files:

| File | Role |
|------|------|
| [`cmd/gt/main.go`](https://github.com/gastownhall/gastown/blob/main/cmd/gt/main.go) | Entry point that calls `cmd.Execute()` |
| [`internal/cmd/root.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/root.go) | Defines `func Execute()` and the global `rootCmd` variable |
| [`internal/cmd/status.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/status.go) | Existing command example showing the standard Cobra pattern |
| [`internal/cmd/hello.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/hello.go) | Your new command implementation (to be added) |
| [`internal/cmd/hello_test.go`](https://github.com/gastownhall/gastown/blob/main/internal/cmd/hello_test.go) | Corresponding unit tests (to be added) |

## Summary

- **Place new commands** in `internal/cmd/` as separate `.go` files to maintain modularity.
- **Use the Cobra framework** by creating a `*cobra.Command` with a `RunE` closure for error handling.
- **Register automatically** via an `init()` function that calls `rootCmd.AddCommand()`.
- **Never modify** [`cmd/gt/main.go`](https://github.com/gastownhall/gastown/blob/main/cmd/gt/main.go); the existing `Execute()` bootstrap handles all sub-commands dynamically.
- **Always include tests** that execute the command through Cobra’s API and verify output buffers.

## Frequently Asked Questions

### Where do I place new command files in the Gastown repository?

Create them in the `internal/cmd/` directory. The gastownhall/gastown project uses this package to house all CLI logic. Each command gets its own file (e.g., [`hello.go`](https://github.com/gastownhall/gastown/blob/main/hello.go)) alongside a corresponding test file ([`hello_test.go`](https://github.com/gastownhall/gastown/blob/main/hello_test.go)).

### Do I need to modify cmd/gt/main.go to add a new subcommand?

No. The entry point at [`cmd/gt/main.go`](https://github.com/gastownhall/gastown/blob/main/cmd/gt/main.go) only calls `cmd.Execute()`. Because Cobra commands register themselves via `init()` functions in the `internal/cmd` package, simply adding a new file to that directory automatically includes it in the CLI tree.

### How does the Cobra framework handle command registration in Gastown?

Registration happens at package initialization time. Each command file defines an `init()` function that calls `rootCmd.AddCommand()` with the command constructor (e.g., `NewHelloCmd()`). When Go loads the `internal/cmd` package, all `init()` functions execute, building the complete command tree before `Execute()` runs.

### What testing pattern should I follow for new CLI commands?

Create a test file in `internal/cmd/` that instantiates the root command, adds your new command via its constructor, and sets a `bytes.Buffer` to capture stdout. Use `root.SetArgs()` to specify command-line arguments, then call `root.Execute()` and assert on the captured output. This pattern validates both the command registration and the business logic without requiring a compiled binary.