Design toolbelt/cmd — the shared cobra bootstrap surface #9

Closed
opened 2026-08-06 17:21:27 +00:00 by ginjiruu · 2 comments
Owner

Question

What does the absorbed toolbelt/cmd surface look like?

Research ticket "Research: can the cobra bootstrap boilerplate be reduced without losing cobra API power?" established absorb is viable (~86% of package cmd is absorbable; cobra is data-driven so a parent-command factory preserves the raw API). This ticket designs the concrete API.

Recommended shape to react to (Option B from the research, branch research/cobra-bootstrap, docs/research/cobra-bootstrap.md):

  • NewRootCommand(RootOptions) *cobra.Command — persistent flags (--config/-c, -v/-vv/-vvv ladder via CountVarP, --log-level), cobra.OnInitialize initConfig, optional Validate callback; returns raw *cobra.Command.
  • AddConfigCommand(root, SaveOptions) — the config list/describe/set group driven by the Fields() registry, with injectable KCL/YAML save strategy (gate KCL on .k suffix, matching go-template/cmd/config.go:322).
  • Pager() (io.Writer, func()).
  • Serve(ServeOptions, run) scaffold — signal ctx, logger build/sync, close/run.
  • DevSetup(prefix string, ...) in embeddedoidc (standardize on ConfigureEnv).
  • One shared BuildLogger(level, format) replacing the five copies.

Decisions this ticket resolves:

  1. The exact exported API of the new package (toolbelt/cmd?) and its option structs.
  2. The verbosity model — standardize on the int ladder; is a transition-period bool -v option needed?
  3. How KCL vs YAML save is injected without importing config/kcl in the toolbelt root (blank import seam vs callback).
  4. The per-app seams (app strings, New*App constructor, startup Debug summary, redirect URI) as options/callbacks.

Resolve with /prototype (sketch the API to react to) + /grilling + /domain-modeling. Per the research facts: derive binary name from cmd.CommandPath()/root.Use (not literals), read ConfigEnvPrefix/AppName at call time, keep the Debug summary per-app. On close, write the plan to docs/plans/ and reshape the example/cmd/ surface onto it.

Child of: "Toolbelt glue absorption — map: what the toolbelt absorbs from duplicated consumer glue" (#1).

## Question What does the absorbed `toolbelt/cmd` surface look like? Research ticket "Research: can the cobra bootstrap boilerplate be reduced without losing cobra API power?" established **absorb is viable** (~86% of `package cmd` is absorbable; cobra is data-driven so a parent-command factory preserves the raw API). This ticket designs the concrete API. Recommended shape to react to (Option B from the research, branch `research/cobra-bootstrap`, `docs/research/cobra-bootstrap.md`): - `NewRootCommand(RootOptions) *cobra.Command` — persistent flags (`--config/-c`, `-v/-vv/-vvv` ladder via `CountVarP`, `--log-level`), `cobra.OnInitialize` initConfig, optional `Validate` callback; returns raw `*cobra.Command`. - `AddConfigCommand(root, SaveOptions)` — the `config list/describe/set` group driven by the `Fields()` registry, with injectable KCL/YAML save strategy (gate KCL on `.k` suffix, matching `go-template/cmd/config.go:322`). - `Pager() (io.Writer, func())`. - `Serve(ServeOptions, run)` scaffold — signal ctx, logger build/sync, close/run. - `DevSetup(prefix string, ...)` in `embeddedoidc` (standardize on `ConfigureEnv`). - One shared `BuildLogger(level, format)` replacing the five copies. Decisions this ticket resolves: 1. The exact exported API of the new package (`toolbelt/cmd`?) and its option structs. 2. The verbosity model — standardize on the int ladder; is a transition-period bool `-v` option needed? 3. How KCL vs YAML save is injected without importing `config/kcl` in the toolbelt root (blank import seam vs callback). 4. The per-app seams (app strings, `New*App` constructor, startup Debug summary, redirect URI) as options/callbacks. Resolve with `/prototype` (sketch the API to react to) + `/grilling` + `/domain-modeling`. Per the research facts: derive binary name from `cmd.CommandPath()`/`root.Use` (not literals), read `ConfigEnvPrefix`/`AppName` at call time, keep the Debug summary per-app. On close, write the plan to `docs/plans/` and reshape the `example/cmd/` surface onto it. **Child of:** "Toolbelt glue absorption — map: what the toolbelt absorbs from duplicated consumer glue" (#1).
Author
Owner

Resolved via /prototype (throwaway branch prototype/cmd-bootstrap) + /grilling. The absorbed surface is research #6's Option B — a parent-command factory returning the raw *cobra.Command — designed against the #3/#4 instance shape.

The four decisions

  1. Exported API (designs against config.New(...)/app.New(ctx, cfg, opts)):
    • NewRootCommand(cfg *config.Config, RootOptions{Short, Long, PreInit}) *cobra.Command
    • AddConfigCommand(cfg *config.Config, root *cobra.Command) — full group, no options
    • Pager() (io.Writer, func())
    • Serve(cfg *config.Config, ServeOptions{Options app.Options}) — logger build/sync + startup summary absorbed into app.New (plan #4)
    • embeddedoidc.DevSetup(prefix, WithRedirectURI/WithStartupDelay/WithDevLogger)
      Per-app Execute() stays; factory owns flag storage; panics on nil cfg.
  2. Verbosity: int -v/-vv/-vvv ladder only, no transition bool. Modeled in KCL as the app.Log.level literal-union enum (source of truth); -v resolves into it via cfg.VerboseToLevel. ttrpg's bool -v is not a compat constraint (stale fork, out of build).
  3. KCL seam: blank-import config/kcl; gate the .k suffix by default (go-template pattern); no save callback. All apps define config in KCL (spec), end users configure via YAML/flags/env (runtime). Validation is structural — the generated config.App owns cfg.Validate(), no app can skip it.
  4. Per-app seams: RootOptions{Short, Long, PreInit} + ServeOptions{Options} + DevOptions — the only residue.

Deliverables

  • Prototype (primary source): branch prototype/cmd-bootstrap.
  • Plan: docs/plans/cmd-bootstrap.md.
  • Implementation lands in the same session as the #3/#4 reshapes (one composition-root surface).
  • Graduated follow-up: kcl-generate check/rule-block → Go validation codegen (#23).
Resolved via /prototype (throwaway branch `prototype/cmd-bootstrap`) + /grilling. The absorbed surface is research #6's Option B — a parent-command factory returning the raw `*cobra.Command` — designed against the #3/#4 instance shape. ## The four decisions 1. **Exported API** (designs against `config.New(...)`/`app.New(ctx, cfg, opts)`): - `NewRootCommand(cfg *config.Config, RootOptions{Short, Long, PreInit}) *cobra.Command` - `AddConfigCommand(cfg *config.Config, root *cobra.Command)` — full group, no options - `Pager() (io.Writer, func())` - `Serve(cfg *config.Config, ServeOptions{Options app.Options})` — logger build/sync + startup summary absorbed into `app.New` (plan #4) - `embeddedoidc.DevSetup(prefix, WithRedirectURI/WithStartupDelay/WithDevLogger)` Per-app `Execute()` stays; factory owns flag storage; panics on nil cfg. 2. **Verbosity**: int `-v/-vv/-vvv` ladder only, no transition bool. Modeled in KCL as the `app.Log.level` literal-union enum (source of truth); `-v` resolves into it via `cfg.VerboseToLevel`. ttrpg's bool `-v` is not a compat constraint (stale fork, out of build). 3. **KCL seam**: blank-import `config/kcl`; gate the `.k` suffix by default (go-template pattern); no save callback. All apps define config in KCL (spec), end users configure via YAML/flags/env (runtime). Validation is **structural** — the generated `config.App` owns `cfg.Validate()`, no app can skip it. 4. **Per-app seams**: `RootOptions{Short, Long, PreInit}` + `ServeOptions{Options}` + `DevOption`s — the only residue. ## Deliverables - Prototype (primary source): branch `prototype/cmd-bootstrap`. - Plan: `docs/plans/cmd-bootstrap.md`. - Implementation lands in the same session as the #3/#4 reshapes (one composition-root surface). - Graduated follow-up: kcl-generate `check`/`rule`-block → Go validation codegen ([#23](https://git.animeteamspeak.moe/ginjiruu/toolbelt/issues/23)).
Author
Owner

Closed: toolbelt/cmd surface designed (Option B factory, KCL-aware by default, structural validation, Serve absorbs app). Plan docs/plans/cmd-bootstrap.md; prototype on branch prototype/cmd-bootstrap; lands with #3/#4.

Closed: toolbelt/cmd surface designed (Option B factory, KCL-aware by default, structural validation, Serve absorbs app). Plan docs/plans/cmd-bootstrap.md; prototype on branch prototype/cmd-bootstrap; lands with #3/#4.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ginjiruu/toolbelt#9
No description provided.