Move completed plan documents to docs/plans/completed/ with concise completion summaries alongside the originals. Archived: MCP integration, Go API design/plan, CLI meta-package design. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.4 KiB
CLI Meta-Package Restructure — Design
Goal: Transform core/cli from a 35K LOC monolith into a thin assembly repo that ships variant binaries. Domain repos own their commands. go/pkg/cli is the only import any domain package needs for CLI concerns.
Architecture: Commands register as framework services via cli.WithCommands(), passed to cli.Main(). Command code lives in the domain repos that own the business logic. The cli repo is a thin main.go that wires them together.
Tech Stack: go/pkg/cli (wraps cobra + charmbracelet), Core framework lifecycle, Taskfile
1. CLI SDK — The Single Import
forge.lthn.ai/core/go/pkg/cli is the only import domain packages use for CLI concerns. It wraps cobra, charmbracelet, and stdlib behind a stable API. If the underlying libraries change, only go/pkg/cli is touched — every domain repo is insulated.
Already done
- Cobra:
Commandtype alias,NewCommand(),NewGroup(),NewRun(), flag helpers (StringFlag,BoolFlag,IntFlag,StringSliceFlag), arg validators - Output:
Success(),Error(),Warn(),Info(),Table,Section(),Label(),Task(),Hint() - Prompts:
Confirm(),Question(),Choose(),ChooseMulti()with grammar-based action variants - Styles: 17 pre-built styles,
AnsiStylebuilder, Tailwind colour constants (47 hex values) - Glyphs:
:check:,:cross:,:warn:etc. with Unicode/Emoji/ASCII themes - Layout: HLCRF composite renderer (Header/Left/Content/Right/Footer)
- Errors:
Wrap(),WrapVerb(),ExitError,Is(),As() - Logging:
LogDebug(),LogInfo(),LogWarn(),LogError(),LogSecurity() - TUI primitives:
Spinner,ProgressBar,InteractiveList,TextInput,Viewport,RunTUI - Command registration:
WithCommands(name, fn)— registers commands as framework services
Stubbed for later (interface exists, returns simple fallback)
Form(fields []FormField) (map[string]string, error)— multi-field form (backed by huh later)FilePicker(opts ...FilePickerOption) (string, error)— file browserTabs(items []TabItem) error— tabbed content panes
Rule
Domain packages import forge.lthn.ai/core/go/pkg/cli and nothing else for CLI concerns. No cobra, no lipgloss, no bubbletea.
2. Command Registration — Framework Lifecycle
Commands register through the Core framework's service lifecycle, not through global state or init() functions.
The contract
Each domain repo exports an Add*Commands(root *cli.Command) function. The CLI binary wires it in via cli.WithCommands():
// go-ai/cmd/daemon/cmd.go
package daemon
import "forge.lthn.ai/core/go/pkg/cli"
// AddDaemonCommand adds the 'daemon' command group to the root.
func AddDaemonCommand(root *cli.Command) {
daemonCmd := cli.NewGroup("daemon", "Manage the core daemon", "")
root.AddCommand(daemonCmd)
// subcommands...
}
No init(). No blank imports. No cli.RegisterCommands().
How it works
cli.WithCommands(name, fn) wraps the registration function as a framework service implementing Startable. During Core.ServiceStartup(), the service's OnStartup() casts Core.App to *cobra.Command and calls the registration function. Core services (i18n, log, workspace) start first since they're registered before command services.
// cli/main.go
func main() {
cli.Main(
cli.WithCommands("config", config.AddConfigCommands),
cli.WithCommands("doctor", doctor.AddDoctorCommands),
// ...
)
}
Migration status (completed)
| Source | Destination | Status |
|---|---|---|
cmd/dev, setup, qa, docs, gitcmd, monitor |
go-devops/cmd/ |
Done |
cmd/lab |
go-ai/cmd/ |
Done |
cmd/workspace |
go-agentic/cmd/ |
Done |
cmd/go |
core/go/cmd/gocmd |
Done |
cmd/vanity-import, community |
go-devops/cmd/ |
Done |
cmd/updater |
go-update |
Done (own repo) |
cmd/daemon, mcpcmd, security |
go-ai/cmd/ |
Done |
cmd/crypt |
go-crypt/cmd/ |
Done |
cmd/rag |
go-rag/cmd/ |
Done |
cmd/unifi |
go-netops/cmd/ |
Done |
cmd/api |
go-api/cmd/ |
Done |
cmd/collect, forge, gitea |
go-scm/cmd/ |
Done |
cmd/deploy, prod, vm |
go-devops/cmd/ |
Done |
Stays in cli/ (meta/framework commands)
config, doctor, help, module, pkgcmd, plugin, session
3. Variant Binaries (future)
The cli/ repo can produce variant binaries by creating multiple main.go files that wire different sets of commands.
cli/
├── main.go # Current — meta commands only
├── cmd/core-full/main.go # Full CLI — all ecosystem commands
├── cmd/core-ci/main.go # CI agent dispatch + SCM
├── cmd/core-mlx/main.go # ML inference subprocess
└── cmd/core-ops/main.go # DevOps + infra management
Each variant calls cli.Main() with its specific cli.WithCommands() set. No blank imports needed.
Why variants matter
core-mlxships to the homelab as a ~10MB binary, not 50MB with devops/forge/netopscore-cideploys to agent machines without ML or CGO dependencies- Adding a new variant = one new
main.gowith the rightWithCommandscalls
4. Current State
cli/ has 7 meta packages, one main.go, and zero business logic. Everything else lives in the domain repos that own it. Total cli/ LOC is ~2K.