15 KiB
name, description
| name | description |
|---|---|
| go-cli-builder | Build Go-based command-line tools following established patterns with Cobra CLI framework, Viper configuration, SQLite database, and automated GitHub Actions workflows for releases. Use when creating new Go CLI projects or adding features to existing ones that follow the Cobra/Viper/SQLite stack. |
Go CLI Builder
Overview
This skill provides templates, scripts, and patterns for building production-ready Go command-line tools. It follows established patterns from projects like feedspool-go, feed-to-mastodon, and linkding-to-opml.
The skill generates projects with:
- Cobra for CLI framework
- Viper for configuration management (YAML files with CLI overrides)
- SQLite with a naive migration system
- Logrus for structured logging
- Makefile for common tasks (lint, format, test, build)
- GitHub Actions workflows for CI, tagged releases, and rolling releases
- Strict code formatting with gofumpt and linting with golangci-lint
When to Use This Skill
Use this skill when:
- Creating a new Go CLI tool from scratch
- Adding commands to an existing Go CLI project that follows these patterns
- Needing reference material about Cobra/Viper integration
- Setting up GitHub Actions workflows for multi-platform Go releases
Example user requests:
- "Create a new Go CLI tool called feed-analyzer"
- "Scaffold a Go project for processing log files"
- "Add a new 'export' command to my Go CLI project"
- "Help me set up GitHub Actions for releasing my Go tool"
Quick Start
Creating a New Project
To scaffold a complete new project:
# With database support (default)
python scripts/scaffold_project.py my-cli-tool
# Without database support
python scripts/scaffold_project.py my-cli-tool --no-database
# With template support for generating output
python scripts/scaffold_project.py my-cli-tool --templates
# Combining options
python scripts/scaffold_project.py my-cli-tool --no-database --templates
Project Options:
-
Database Support (default: included)
- Includes SQLite with migrations system
- Use
--no-databaseto exclude if you don't need persistent storage - Examples: CLI tools that only fetch/transform data, API clients
-
Template Support (default: excluded)
- Includes embedded template system with init command
- Use
--templatesto include for tools that generate formatted output - Examples: Markdown generators, OPML exporters, report generators
What gets created:
Base structure (always):
- Entry point (
main.go) - Root command with Cobra/Viper integration (
cmd/root.go) - Version command (
cmd/version.go) - Configuration system (
internal/config/) - Makefile with standard targets
- GitHub Actions workflows (CI, release, rolling-release)
Optional additions:
- Database layer with migrations (
internal/database/) - if database enabled - Template system (
internal/templates/,cmd/init.go) - if templates enabled
Next steps after scaffolding:
- Update
go.modwith the actual module name - Customize the example config file
- If using database: Define initial schema in
internal/database/schema.sql - Run
make setupto install development tools - Run
go mod tidyto download dependencies
Adding Commands to Existing Projects
To add a new command to an existing project:
python scripts/add_command.py fetch
This creates cmd/fetch.go with:
- Command boilerplate
- Access to logger and config
- Flag binding examples
- TODO comments for implementation
Project Structure
Generated projects follow this structure:
my-cli-tool/
├── main.go # Entry point
├── go.mod # Dependencies
├── Makefile # Build automation
├── my-cli-tool.yaml.example # Example configuration
├── cmd/ # Command definitions
│ ├── root.go # Root command + Cobra/Viper setup
│ ├── version.go # Version command
│ ├── constants.go # Application constants
│ └── [command].go # Individual commands
├── internal/
│ ├── config/
│ │ └── config.go # Configuration struct
│ ├── database/
│ │ ├── database.go # Connection + initialization
│ │ ├── migrations.go # Migration system
│ │ └── schema.sql # Initial schema (embedded)
│ └── templates/ # Optional: For tools that generate output
│ ├── templates.go # Embedded template loader
│ └── default.md # Default template (embedded)
└── .github/workflows/
├── ci.yml # PR linting and testing
├── release.yml # Tagged releases
└── rolling-release.yml # Main branch rolling releases
Configuration System
Projects use a three-tier configuration hierarchy:
- Config file (
my-tool.yaml): Base configuration in YAML - Environment variables: Automatic via Viper
- CLI flags: Override everything
See references/cobra-viper-integration.md for detailed patterns on:
- Binding flags to Viper keys
- Adding new configuration options
- Command-specific vs. global configuration
- Environment variable mapping
Database Layer
The generated database layer includes:
- Initial schema (
internal/database/schema.sql): Embedded SQL for first-time setup - Migration tracking:
schema_migrationstable tracks applied versions - Migration execution: Automatic on database initialization
- Idempotent operations: Safe to run multiple times
To add a new migration:
- Edit
internal/database/migrations.go - Add to the
getMigrations()map with the next version number:func getMigrations() map[int]string { return map[int]string{ 2: `CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL );`, } } - Migrations run automatically on next database initialization
Init Command Pattern
For tools that generate output files (markdown, OPML, etc.), the init command pattern provides a great user experience by generating both configuration and customizable templates.
When to Use Init Command
Use the init command when your CLI tool:
- Generates formatted output (markdown, HTML, XML, etc.)
- Benefits from user-customizable templates
- Has configuration that users need to set up before first use
Init Command Components
Available templates:
init.go.template- Complete init command implementationtemplates.go.template- Template loader with embedded defaultdefault.md.template- Example embedded markdown template
The init command:
- Creates a YAML configuration file with all options documented
- Creates a customizable template file (using embedded default)
- Supports
--forceflag to overwrite existing files - Supports
--template-fileflag to specify custom template filename - Provides helpful next steps after initialization
Embedded Templates
Go's //go:embed directive allows embedding template files directly in the binary:
package templates
import (
_ "embed"
)
//go:embed default.md
var defaultTemplate string
func GetDefaultTemplate() (string, error) {
return defaultTemplate, nil
}
Benefits:
- Single binary distribution (no external template files needed)
- Users can still customize by running
initto get a copy - Template always available as fallback
Integration with Other Commands
Commands that generate output should support both:
- Built-in template (default) - uses embedded template
- Custom template (via
--templateflag or config) - loads from file
Example pattern:
templatePath := viper.GetString("command.template")
var generator *Generator
if templatePath != "" {
generator, err = NewGeneratorFromFile(templatePath)
} else {
generator, err = NewGenerator() // uses embedded default
}
Example Projects Using This Pattern
linkding-to-markdown- Fetches bookmarks and generates markdownmastodon-to-markdown- Exports Mastodon posts to markdown
Makefile Targets
All generated projects include these targets:
make setup: Install development tools (gofumpt, golangci-lint)make build: Build the binary with version informationmake run: Build and run the applicationmake lint: Run golangci-lintmake format: Format code with go fmt and gofumptmake test: Run tests with race detectionmake clean: Remove build artifacts
GitHub Actions Workflows
Three workflows are included:
1. CI (ci.yml)
- Triggers: Pull requests to main, manual workflow calls
- Actions: Lint with golangci-lint, test with race detection
- Skip: Commits starting with
[noci]
2. Release (release.yml)
- Triggers: Tags matching
v*(e.g.,v1.0.0) - Platforms: Linux (amd64, arm64), macOS (amd64, arm64), Windows (amd64)
- Outputs: Compressed binaries, checksums, GitHub release
- Docker: Optional (commented out by default)
3. Rolling Release (rolling-release.yml)
- Triggers: Pushes to main branch
- Actions: Same as Release but creates a "latest" prerelease
- Purpose: Testing builds from the latest commit
To customize:
- Update Docker Hub username in workflows if using Docker
- Adjust Go version if needed (default: 1.21)
- Modify build matrix to add/remove platforms
Typical Workflow
Starting a New Project
- Use this skill to scaffold the project
- Customize the initial schema in
internal/database/schema.sql - Update configuration struct in
internal/config/config.go - Add domain-specific packages in
internal/(seereferences/internal-organization.md) - Add commands using the add_command script
- Implement command logic, calling into
internal/packages
Adding a Feature
- Determine if it needs a new command or extends existing one
- If new command: use
add_command.pyscript - Add any required configuration to config struct and root flags
- Implement logic in
internal/packages - Update command to call the internal logic
- Add tests
- Run
make format && make lint && make test
Reference Documentation
For detailed patterns and guidelines, refer to:
-
references/cobra-viper-integration.md: Complete guide to configuration system- Flag binding patterns
- Adding new configuration options
- Environment variable mapping
- Best practices
-
references/internal-organization.md: Internal package structure- Package organization principles
- Dependency rules
- Common patterns (Option pattern, error wrapping)
- When to create new packages
-
references/template-patterns.md: Template-based output generation- When and how to use embedded templates
- Init command implementation
- Generator/renderer patterns
- Template functions and testing
- User workflow and best practices
Templates Available
All templates are in assets/templates/:
Core Files:
main.go: Minimal entry pointgo.mod.template: Pre-configured dependenciesMakefile.template: Standard build targetsgitignore.template: Go-specific ignoresconfig.yaml.example: Example configuration
Commands:
root.go.template: Cobra/Viper integrationversion.go.template: Version commandconstants.go.template: Application constantscommand.go.template: New command templateinit.go.template: Init command for config/template generation
Internal Packages:
config.go.template: Configuration structdatabase.go.template: Database layermigrations.go.template: Migration systemschema.sql.template: Initial schematemplates.go.template: Embedded template loaderdefault.md.template: Example embedded template
CI/CD:
ci.yml.template: CI workflowrelease.yml.template: Release workflowrolling-release.yml.template: Rolling release workflow
Best Practices
- Keep commands thin: Business logic belongs in
internal/packages - Use the config struct: Access configuration through
GetConfig()rather than calling Viper directly - Wrap errors: Always add context with
fmt.Errorf("context: %w", err) - Format before committing: Run
make format && make lint - Test with race detection:
go test -race ./... - Version your releases: Use semantic versioning tags (v1.0.0, v1.1.0, etc.)
- Document in .yaml.example: Keep example config updated
- Handle errors explicitly: Use
_ =for intentionally ignored errors (e.g.,_ = viper.BindPFlag(...)) - Defer cleanup safely: Use
defer func() { _ = tx.Rollback() }()instead ofdefer tx.Rollback()to avoid linter warnings
Common Customizations
After scaffolding, projects typically need:
- Module name update: Change
github.com/yourusername/projectingo.modto actual path - Additional dependencies: Add with
go getand rungo mod tidy - Custom schema: Define tables in
internal/database/schema.sql - Domain packages: Create packages in
internal/for business logic - Command implementations: Fill in the TODOs in command files
- Docker configuration: Uncomment Docker sections in workflows if needed
Recent Improvements
Linter Compliance (2025-11-11)
- Fixed viper.BindPFlag warnings: All
viper.BindPFlag()calls now use_ =prefix to explicitly ignore errors, satisfying theerrchecklinter - Fixed defer Rollback warnings: Database transaction cleanup now uses
defer func() { _ = tx.Rollback() }()pattern - Removed static linking: Removed
-linkmode external -extldflags "-static"flags from Makefile to eliminate getaddrinfo warnings when using CGO with SQLite - Updated templates: All templates now generate linter-clean code out of the box
These changes ensure that projects scaffolded with this skill pass golangci-lint without warnings.
Troubleshooting
"gofumpt not found" or "golangci-lint not found"
- Run
make setupto install development tools
"Failed to initialize schema"
- Check database file path and permissions
- Ensure directory exists or is creatable
"Missing migration for version N"
- Migrations must be sequential; add any missing versions
"getaddrinfo warning during build"
- This warning has been resolved in recent versions by removing static linking flags
- If you see this in an older project, remove the static linking lines from your Makefile (see Recent Improvements section)
GitHub Actions failing on cross-compilation
- Ensure CGO is enabled for SQLite
- Linux ARM64 builds require cross-compilation tools (handled in workflow)