Page:
Framework Integration
No results
1
Framework Integration
Virgil edited this page 2026-02-23 04:54:00 +00:00
Framework Integration
How Commands Register
Commands register through the Core framework lifecycle using WithCommands:
// In main.go
cli.Main(
cli.WithCommands("score", score.AddScoreCommands),
cli.WithCommands("gen", gen.AddGenCommands),
)
Internally, WithCommands wraps the registration function in a commandService that implements Startable.OnStartup(). During startup, it casts Core.App to *cobra.Command and calls the registration function.
Startup order:
- Core services start (i18n, log, crypt, workspace)
- Command services start (your
WithCommandsfunctions) Execute()runs the matched command
Registration Function Pattern
// score/commands.go
package score
import "forge.lthn.ai/core/cli/pkg/cli"
func AddScoreCommands(root *cli.Command) {
scoreCmd := cli.NewGroup("score", "Scoring commands", "")
grammarCmd := cli.NewCommand("grammar", "Grammar analysis", "", runGrammar)
cli.StringFlag(grammarCmd, &inputPath, "input", "i", "", "Input file")
scoreCmd.AddCommand(grammarCmd)
root.AddCommand(scoreCmd)
}
Accessing Core Services
func runMyCommand(cmd *cli.Command, args []string) error {
ctx := cli.Context() // Root context (cancelled on signal)
core := cli.Core() // Framework Core instance
root := cli.RootCmd() // Root cobra command
// Type-safe service retrieval
ws, err := framework.ServiceFor[*workspace.Service](core)
if err != nil {
return cli.WrapVerb(err, "get", "workspace service")
}
return nil
}
Built-in Services
| Service | Name | Purpose |
|---|---|---|
| i18n | i18n |
Internationalisation, grammar |
| log | log |
Structured logging (slog) |
| crypt | crypt |
OpenPGP encryption |
| workspace | workspace |
Project root detection |
| signal | signal |
SIGINT/SIGTERM/SIGHUP handling |
Main() Lifecycle
- Recovery defer (catches panics)
- Register core services (i18n, log, crypt, workspace)
- Append user command services
Init()— creates cobra root, signals, starts all services- Add completion command
Execute()— runs matched commandShutdown()— stops all services in reverse order
Legacy: RegisterCommands
For packages that need init()-time registration (not recommended):
func init() {
cli.RegisterCommands(func(root *cobra.Command) {
root.AddCommand(myCmd)
})
}
Prefer WithCommands — it's explicit and doesn't rely on import side effects.
Building a CLI Binary
// cmd/lem/main.go
package main
import (
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/lthn/lem/cmd/lemcmd"
)
func main() {
cli.WithAppName("lem")
cli.Main(lemcmd.Commands()...)
}
Where Commands() returns []framework.Option:
func Commands() []framework.Option {
return []framework.Option{
cli.WithCommands("score", addScoreCommands),
cli.WithCommands("gen", addGenCommands),
cli.WithCommands("data", addDataCommands),
}
}