0. Who am I?
π Hi, Iβm Youssef Ameachaq
π Iβm an engineering manager at Oracle.
π± I contribute to the htmx project.
π» I try to build some cool stuff with Go and JavaScript.
π« Reach me at youssefameachaq@gmail.com.
π¦ Follow me on Twitter: x.com/youssame0
π Connect with me on LinkedIn: linkedin.com/in/youssef-ameachaq/
π Iβm an engineering manager at Oracle.
π± I contribute to the htmx project.
π» I try to build some cool stuff with Go and JavaScript.
π« Reach me at youssefameachaq@gmail.com.
π¦ Follow me on Twitter: x.com/youssame0
π Connect with me on LinkedIn: linkedin.com/in/youssef-ameachaq/
2. Introduction: Why Build a CLI?
- Power of CLIs: Fast, direct interaction for automation and workflows.
- Goals:
- Solve real problems.
- Create a tool that users enjoy using (not just tolerate).
- Ensure reliability and ease of distribution.
2. Design Principles for a Great CLI
- Empathize with Users:
- Make commands predictable and intuitive.
- Support human-readable and machine-parsable output (e.g., JSON).
- Focus on Tasks:
- Combine multiple operations into meaningful, high-level commands.
- Simplify common workflows (e.g., one command instead of multiple steps).
- Command Grammar:
- Use
<verb> <noun>
structures for natural and memorable commands. - Follow ecosystem conventions (e.g., similar patterns to
kubectl
or Docker).
- Use
- Error Messages:
- Clear and actionable errors help debugging (e.g., suggest correct commands or flags).
3. Getting Started with Go and Cobra
Setting Up the CLI
- Initialize the project:
mkdir my-cli && cd my-cli go mod init github.com/username/my-cli go get -u github.com/spf13/cobra
- Create the project structure :
/my-cli /cmd /name # CLI commands main.go # CLI entry point /internal # Reusable code
Building the Root Command
- Define the main entry point (
main.go
):package main import ( "os" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "my-cli", Version: "0.1.0", Short: "A fast, reliable CLI built with Go", } func main() { err := rootCmd.Execute() if err != nil { os.Exit(1) } }
4. Adding Commands
- Create a new file (
get.go
) for a βgetβ command:package main import ( "fmt" "github.com/spf13/cobra" ) var getCmd = &cobra.Command{ Use: "get <resource>", Short: "Fetch details about a resource", Run: func(cmd *cobra.Command, args []string) { fmt.Println("Fetching details...") }, } func init() { rootCmd.AddCommand(getCmd) }
5. Enhancing Functionality
Reusable Components
- Use an
internal
package for shared logic:- API Calls:
package api import "net/http" func GetResourceDetails(name string) (*http.Response, error) { return http.Get("https://api.example.com/resource/" + name) }
- Validation:
package validation func IsValidName(name string) bool { return len(name) > 0 }
- API Calls:
Advanced CLI Features
- Add JSON output support for automation.
- Include flags for flexible command inputs:
var outputFormat string func init() { getCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text/json)") }
6. Testing and Improving
- Write unit tests for core logic:
func TestGetResource(t *testing.T) { result := api.GetResourceDetails("test-resource") if result == nil { t.Fatal("Expected non-nil result") } }
- Use mocks for API calls and input validation.
- Add CI pipelines to test and build on every push.
7. Packaging and Releasing
Automate Builds with GoReleaser
GoReleaser automates the process of building, packaging, and releasing your CLI across platforms.
- Install GoReleaser:
brew install goreleaser
- Create a
.goreleaser.yaml
file:cd cmd/drive-cli goreleaser init goreleaser release --snapshot --clean
Automate Releases with GitHub Actions
- Example workflow:
name: release on: push: tags: - '*' jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: goreleaser/goreleaser-action@v2 with: args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
8. Closing Thoughts
- CLIs are more than tools; theyβre user experiences.
- Build with empathy, keep iterating, and make the tool something people love to use.
- Encourage feedback and contributions to improve reliability and functionality.