Blog & Press Releases
Go Cobra Tutorial

Build Your Own CLI Using Go and Cobra

BootLabs Engineering July 2022 4 min read
Building a command-line tool in Go with Cobra

Go is expressive, concise, clean and efficient. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted one — which makes it a superb choice for building command-line tools. In this tutorial we'll use the Cobra library to build a small git CLI that lists all the repositories on a GitHub account.

Along the way you'll see how a Go program is structured, how Cobra wires up commands and flags, and how to talk to the GitHub REST API from Go.

Goals

By the end of this tutorial you will:

  • Know how to use Cobra to build a CLI — we'll create a git command that lists all repositories on an account.
  • Understand how a Go application is structured.
  • Understand how to interact with the GitHub REST API from Go.

Prerequisites

You'll need Go installed on your machine. We'll use a single external package:

github.com/spf13/cobra

Why Cobra?

Cobra is a library for creating powerful, modern CLI applications, offering a simple interface for interfaces that feel like git and go themselves. It powers many well-known Go projects — Kubernetes, Hugo, and the GitHub CLI, to name a few. It provides:

  • Easy subcommand-based CLIs — app server, app fetch, etc.
  • Fully POSIX-compliant flags (short & long versions).
  • Nested subcommands, plus global, local and cascading flags.
  • Intelligent suggestions (app srver… did you mean app server?).
  • Automatic help generation for commands and flags, and recognition of -h / --help.
  • Automatically generated shell autocomplete (bash, zsh, fish, powershell) and man pages.
  • Command aliases, so you can rename things without breaking users.
  • The flexibility to define your own help and usage output.
  • Optional seamless integration with Viper for 12-factor apps.

Let's get started.

The Entry Point

Our main.go does one thing — hand control to the cmd package:

main.go
package main

import (
   "go-cli-for-git/cmd"
)

func main() {
   cmd.Execute()
}

The Root Command

Next we initialise the Cobra root command in a rootCmd variable with a short description, and expose an Execute() function that runs it:

cmd/execute.go
package cmd

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

var rootCmd = &cobra.Command{
   Use:   "cli",
   Short: "git cli using cobra to list all repositories and their clone URLs",
}

func Execute() {
   if err := rootCmd.Execute(); err != nil {
      fmt.Println(err)
      os.Exit(1)
   }
}

The get Command

Now the interesting part. We register a get subcommand on rootCmd and define the flags it needs. The command reads the username and access token, calls the GitHub API, and prints each repository's name, visibility, and clone URL.

cmd/base.go
package cmd

import (
   b64 "encoding/base64"
   "encoding/json"
   "fmt"
   "github.com/spf13/cobra"
   "io/ioutil"
   "net/http"
)

// addCmd represents the "get" command
var addCmd = &cobra.Command{
   Use:   "get",
   Short: "get repo details",
   Long:  `Get repo information using the Cobra command`,
   Run: func(cmd *cobra.Command, args []string) {
      username, _ := rootCmd.Flags().GetString("username")
      password, _ := rootCmd.Flags().GetString("password")
      auth := fmt.Sprintf("%s:%s", username, password)
      authEncode := b64.StdEncoding.EncodeToString([]byte(auth))

      url := "https://api.github.com/user/repos"
      method := "GET"

      client := &http.Client{}
      req, err := http.NewRequest(method, url, nil)
      if err != nil {
         fmt.Println(err)
         return
      }
      req.Header.Add("Authorization", fmt.Sprintf("Basic %s", authEncode))

      res, err := client.Do(req)
      if err != nil {
         fmt.Println(err)
         return
      }
      defer res.Body.Close()

      var response []interface{}
      body, err := ioutil.ReadAll(res.Body)
      if err != nil {
         fmt.Println(err)
         return
      }

      err = json.Unmarshal(body, &response)

      for _, repoDetails := range response {
         repo := repoDetails.(map[string]interface{})
         fmt.Println(" name :", repo["name"], " private :", repo["private"], " clone_url :", repo["clone_url"])
      }
   },
}

func init() {
   rootCmd.AddCommand(addCmd)
   rootCmd.PersistentFlags().StringP("username", "u", "", "the username of git")
   rootCmd.PersistentFlags().StringP("password", "p", "", "the access token of git")
}

A few things worth calling out:

  • We register addCmd on rootCmd and define two flags, -u and -p, for the GitHub username and access token.
  • rootCmd.PersistentFlags().StringP("username", "u", "", "…") is how you declare a persistent flag on the command.
  • rootCmd.Flags().GetString("username") is how you read that flag's value while the command runs.
  • We hit the GitHub REST API to list all repositories for the account. The GitHub REST API docs cover the many other endpoints available.
A note on auth

This example uses HTTP Basic auth with a personal access token for clarity. GitHub now recommends sending the token as a Bearer / token header, and ioutil.ReadAll is deprecated in favour of io.ReadAll on modern Go — worth updating for production use.

Build and Run

Compile the binary with go build:

go build -o git-cli

Perfect — we're all set. Run the project, passing your GitHub username and a personal access token:

./git-cli get -u <username> -p <access_token>

# or, using the long flags:
./git-cli get --username <username> --password <access_token>

Success! The Cobra command executes and your GitHub repositories are listed in the terminal — each with its name, visibility and clone URL.

Wrapping Up

In just three small files you've built a real, extensible CLI: a root command, a subcommand, flags, and a live API call. From here you can add more subcommands, richer output formatting, or Viper-backed configuration — Cobra scales cleanly as your tool grows.

If you get stuck following along, clone the reference repository and run it:

git clone https://github.com/venkateshsuresh/go-cli-for-git.git

Thanks for reading — and stay tuned for more.

BootLabs builds cloud automation tooling and platform engineering solutions in Go and beyond. If you're building internal CLIs or developer platforms, our engineering team can help.

Build developer tooling that scales.

Talk to our engineering team — we build CLIs, internal developer platforms, and cloud automation in Go and cloud-native stacks.