Initialisers¶
This document provides a technical deep dive into the Initialiser interface, the lifecycle of an initialiser, and the specific implementation details of the built-in initialisers.
For a high-level conceptual overview of the Initialiser pattern, please see the Initialisers Concept Documentation.
Interface Definition¶
The setup.Initialiser interface is the core contract for all initialization logic. It is defined in pkg/setup/setup.go:
[!NOTE] See pkg.go.dev/gitlab.com/phpboyscout/go-tool-base/pkg/setup for the full API definition.
Key Considerations for Implementers¶
- Idempotency:
IsConfiguredmust be robust. It is called every timeinitis run. If it returnsfalseincorrectly, the user will be prompted unnecessarily. - Configuration Isolation: While
Configurereceives the fullconfig.Containable, an initialiser should ideally only modify keys relevant to its feature domain (e.g.,github.*orai.*). - Error Handling: Errors returned from
Configurewill halt the entire initialization process. Ensure critical failures are handled gracefully or returned with explanatory context.
Registration Lifecycle¶
Initialisers are registered via the setup.Register function, typically in a package's init() function.
func Register(
featureName string,
initialisers []InitialiserProvider,
subcommands []SubcommandProvider,
flags []FeatureFlag,
)
The Registration Flow¶
- Package Init: When the application starts, packages invoke
setup.Register. The setup package stores these providers in a global registry. - Command Construction:
- The Root Init Command iterates over the registry.
- It checks
props.Tool.IsEnabled(feature)to see if the feature is active. - If active, it adds any registered
FeatureFlags to the rootinitcommand flags.
- Command Execution:
- When
initruns, it callssetup.Initialise. setup.InitialiseinstantiatesInitialisers using the registeredInitialiserProviders.- It iterates through them, calling
IsConfigured. - If not configured (and not skipped via flag),
Configureis executed.
- When
Built-in Initialisers Implementation¶
1. GitHub Initialiser¶
Package: pkg/setup/github
The GitHub initialiser manages two distinct configuration areas: Authentication (OAuth token) and SSH Keys.
Configuration Keys¶
github.auth.value: The GitHub Personal Access Token (PAT).github.auth.env: (Optional) Name of the environment variable holding the token.github.ssh.key.path: Path to the private SSH key.github.ssh.key.type: Type of key (e.g.,rsa,ed25519) oragent.
Technical Workflow¶
- Auth Check: Checks for
GITHUB_TOKENenv var. If present, it validates it against the GitHub APIuserendpoint. If valid, it skips prompting. - Token Prompt: If no valid env var, it prompts the user to paste a token.
- SSH Scan: Scans
~/.sshfor files matching standard patterns (id_rsa,id_ed25519, etc.). - Key Selection: Uses
charmbracelet/huhto present a list of found keys + a "Generate New" option. - Agent Support: Can be configured to use
ssh-agentinstead of a direct key file.
2. AI Initialiser¶
Package: pkg/setup/ai
The AI initialiser abstracts over multiple LLM providers, normalizing their configuration into a common structure.
Configuration Keys¶
ai.provider: The selected provider identifier (openai,claude,gemini).ai.claude.key: Anthropic API key.ai.openai.key: OpenAI API key.ai.gemini.key: Google Gemini API key.
Technical Workflow¶
- Provider Selection: User selects a provider from a list.
- Key Input: User inputs the API key.
- Security Note: The input field is masked (echo mode password).
- Env Var Detection: The initialiser checks for standard environment variables (e.g.,
OPENAI_API_KEY) corresponding to the selected provider.- It displays a warning note in the UI if an env var is detected, informing the user that the env var will take precedence over the config file value they are about to set.
- Persistence: The provider choice and the specific key are written to the config file.
Security Features¶
Automatic .gitignore Generation¶
During init, if the config directory does not already contain a .gitignore file, one is automatically created to prevent accidental commit of sensitive files:
Existing .gitignore files are never overwritten.
API Key Detection Warning¶
After writing config files, the init process scans config files for common API key patterns (sk-, api_key, token, secret). If the config directory is inside a git repository, a warning is logged advising the user to ensure the config directory is gitignored. This provides defence in depth against accidental credential commits.
Creating Custom Initialisers¶
For a step-by-step guide on implementing your own initialiser, referring to the How-to Guide.
Conceptual Overview¶
Tool Initialisers¶
Initialisers are a core architectural pattern in GTB used to manage the configuration and bootstrapping of individual tool features in a decoupled, modular way.
Purpose: Configuration, Not Logic¶
It is important to distinguish between Configuration Initialisation and Functional Initialisation:
- Initialisers are exclusively for ensuring that the
config.yamlcontains the necessary values (tokens, paths, preferences) for a feature to operate. This often involves interactive prompts, environment variable checks, or asset mounting. - Functional Initialisation (the actual logic of how a feature behaves) remains firmly within your
NewCmd*constructor and thecobra.Commandexecution logic.
In short: Initialisers prepare the data so that your commands can run.
The Problem¶
Traditional CLI tools often have a monolithic init command that hardcodes every possible configuration step. This results in:
- Brittle Code: Adding a new feature requires modifying the core
initcommand logic. - Bloated Binaries: Features that aren't enabled for a specific project still carry their initialization logic.
- Complex UI: The
init --helpoutput becomes overwhelming with flags for features the user may not even be using.
The Initialiser Solution¶
GTB solves this through Self-Registering Initialisers. Instead of the init command knowing about features, the features "tell" the framework how they want to be initialised and what flags they need.
The Initialiser Interface¶
Any component that requires an interactive setup step (like a login or an API key input) implements the Initialiser interface:
[!NOTE] See pkg.go.dev/gitlab.com/phpboyscout/go-tool-base/pkg/setup for the full API definition.
Self-Registration¶
Features register themselves with the framework during package init(). This allows the main initialise command to discover them dynamically based on what's enabled in the local tool's props.
A feature can register three things:
- Initialisers: Logic to check and perform setup. These are executed by the main
initcommand if the feature is enabled and not yet configured. - Subcommands: Standalone
init <feature>commands. These are intended for forced reconfiguration. While the rootinitcommand will skip a feature if it's already configured, running the specific subcommand (e.g.,mytool init ai) will trigger the setup process regardless of the current state. - Flags: Feature-specific flags (like
--skip-ai) added to the maininitcommand.
graph TD
A[Main init Command] --> B{Registry Discovery}
B -->|AI Feature| C[AI Initialiser]
B -->|GitHub Feature| D[GitHub Initialiser]
B -->|Custom Feature| E[Custom Initialiser]
C --> F[Write config.yaml]
D --> F
E --> F
How it works at Runtime¶
- When you run
mytool init, the framework fetches all registered items from the Global Setup Registry. - It filters these items based on
props.Tool.IsEnabled(feature). - It dynamically attaches any registered Flags to the
initcommand. - During execution, it iterates through the Initialisers. If
IsConfigured()returns false (and the feature isn't explicitly skipped via a flag), it callsConfigure(). - Finally, it merges any default assets provided by the feature and writes the final configuration to disk.
Note
Initialisers are designed to be "aware" of the environment. For example, they can check if a specific environment variable override exists and skip interactive prompts automatically if a value is already provided.