Generating a CLI Skeleton¶
The journey of a thousand miles begins with a single stepβand for your new tool, that step is generate skeleton. π οΈ
Scaffolding a project from scratch can be tedious. generate skeleton fast-tracks this process by setting up a robust, industry-standard project structure that's ready for high-scale development.
What's included in the box?¶
When you run generate skeleton, we set up a complete, working CLI project:
- Project Core
- A clean
main.goand arootcommand inpkg/cmd/root. - Modern Tooling
- A
go.modfile using the latest Go 1.24+ tool directives, keeping your dependencies clean and isolated. - CI/CD Readiness
- GitHub Actions workflows for testing, linting, releases, and documentation.
- Standard Layout
- A
pkg/directory for your logic and adocs/directory for your users. - The Manifest
- A
.gtb/manifest.yamlfile that acts as the brain of your project, tracking your command hierarchy.
Project Structure Summary¶
my-awesome-tool/
βββ .github/workflows/ # CI/CD: Automated testing, linting, and release
βββ .gtb/manifest.yaml # The Brain: Tracks your command hierarchy
βββ cmd/my-awesome-tool/main.go # Entry Point: The main function of your tool
βββ pkg/cmd/root/cmd.go # The Root: Setup and registration of all commands
βββ docs/ # Documentation: Your project's documentation site
βββ go.mod # Dependencies: Uses Go 1.24+ tool directives
βββ README.md # Welcome: Basic info about your new tool
The Generated Root Command¶
The pkg/cmd/root/cmd.go file initializes the Props container, configures logging, and returns both the root command and the props for use by the Execute() wrapper.
Annotated Example: pkg/cmd/root/cmd.go¶
package root
import (
"embed"
"os"
gtbRoot "github.com/phpboyscout/gtb/pkg/cmd/root"
"github.com/phpboyscout/gtb/pkg/errorhandling"
"github.com/phpboyscout/gtb/pkg/props"
"github.com/phpboyscout/gtb/pkg/version"
log "github.com/charmbracelet/log"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
//go:embed assets/*
var assets embed.FS
// NewCmdRoot constructs the root command and props for this tool.
// It returns both so that main.go can pass them to pkgRoot.Execute().
func NewCmdRoot(v version.Info) (*cobra.Command, *props.Props) {
logger := log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: false,
ReportTimestamp: true,
Level: log.InfoLevel,
})
p := &props.Props{
Assets: props.NewAssets(props.AssetMap{"root": &assets}),
FS: afero.NewOsFs(),
Logger: logger,
Tool: props.Tool{
Name: "my-tool",
Summary: "A summary of my tool",
ReleaseSource: props.ReleaseSource{
Type: "github",
Owner: "my-org",
Repo: "my-repo",
},
},
Version: v,
}
// Optionally configure a help/support channel shown in error messages:
// p.Tool.Help = errorhandling.SlackHelp{Team: "My Team", Channel: "#support"}
// p.Tool.Help = errorhandling.TeamsHelp{Team: "My Team", Channel: "Support"}
p.ErrorHandler = errorhandling.New(logger, p.Tool.Help)
rootCmd := gtbRoot.NewCmdRoot(p)
// Subcommands are registered here by the generator:
// rootCmd.AddCommand(mysubcmd.NewCmdMySub(p))
return rootCmd, p
}
The Tool Entry Point¶
The cmd/my-awesome-tool/main.go uses pkgRoot.Execute to run the command and route all errors through ErrorHandler:
package main
import (
"my-awesome-tool/internal/version"
"my-awesome-tool/pkg/cmd/root"
pkgRoot "github.com/phpboyscout/gtb/pkg/cmd/root"
)
func main() {
rootCmd, p := root.NewCmdRoot(version.Get())
pkgRoot.Execute(rootCmd, p)
}
pkgRoot.Execute silences Cobra's own error printing and routes any error returned from RunE through ErrorHandler.Check at fatal level. There is no need for an os.Exit call in main.go.
How to run it¶
Navigate to the directory where you want your project to live and run:
gtb generate cli \
--name "my-awesome-tool" \
--repo "my-github-org/my-awesome-tool-repo" \
--git-backend github \
--help-type slack \
--slack-channel "#help" \
--slack-team "My Team"
Interactive Multi-Stage Form¶
You don't have to remember all the flags! If you run it without --name and --repo, the CLI will guide you through a three-stage interactive form:
- Stage 1 β Project Setup
- Name, Description, Destination Path, Features, Git Backend (GitHub/GitLab), Help Channel (Slack/Teams/None).
- Stage 2 β Git Repository
- Git Host (pre-filled from your backend selection, editable for self-hosted instances) and Repository in
org/repoformat. - Stage 3 β Help Channel (skipped if None selected)
- Slack Channel + Slack Team, or Teams Channel + Teams Team, depending on your Stage 1 selection.
Press Escape at any stage to go back to the previous one. Press Ctrl+C to cancel.
Available Flags¶
| Flag | Short | Description | Default |
|---|---|---|---|
--name |
-n |
Name of your CLI tool | β |
--repo |
-r |
Repository in org/repo format |
β |
--git-backend |
Git backend (github or gitlab) |
github |
|
--host |
Git host (overrides backend default, for self-hosted instances) | β | |
--description |
-d |
Short description of the tool | A tool built with gtb |
--path |
-p |
Destination path for the generated project | . |
--features |
-f |
Features to enable (init, update, mcp, docs) |
all four |
--help-type |
Help channel type (slack, teams, or none) |
none |
|
--slack-channel |
Slack channel (e.g. #my-team-help) |
β | |
--slack-team |
Slack team name (e.g. My Team) |
β | |
--teams-channel |
Microsoft Teams channel | β | |
--teams-team |
Microsoft Teams team name | β |
Tip
The --host flag is only needed when using a self-hosted GitHub Enterprise or GitLab instance. For public github.com or gitlab.com, the correct host is set automatically from --git-backend.
Help Channel Configuration¶
The skeleton generator supports two built-in help channel types, which populate the Tool.Help field in the generated root command:
Slack β users are directed to a Slack channel in error messages:
Microsoft Teams β users are directed to a Teams channel:
Both use the errorhandling.HelpConfig interface, so you can also provide a custom implementation.
Next Steps¶
Once your skeleton is generated, your project is ready to grow! Head over to the Command Generation guide to see how to add functionality.