- serve prints a startup summary on a terminal (version, environment, URL, admin, database host and name, pending migrations, plugins, drivers) and logs it as a 'startup summary' record plus 'listening on <addr>' - on SIGINT/SIGTERM serve drains, prints and logs a shutdown report and returns nil (exit 0), or the drain errors (exit 1) - the request log is on by default under diary component http with request_id; the route pattern, never the path, is logged - lagoon.PendingMigrations, conga.Worker.Stats, diary.HideOnTTY, bonfire.IsTerminal and bonfire.Details back the summary and report
18 KiB
bonfire
Declarative console commands for the summer tool and application binaries, adapted to Cobra with typed input, prompts and styled output.
import "git.golem15.com/golem15/summercms/modules/bonfire"
Name: The evening fire where everyone gathers; the command line is where you sit down and fire off commands.
Overview
bonfire is the console layer of SummerCMS. Plugins and framework modules describe commands as plain bonfire.Command values (name, flags, arguments and a run function), and bonfire.NewRoot turns a slice of them into a Cobra root command. Commands never touch Cobra directly: they read arguments through bonfire.Input and write through bonfire.Output, which also provides tables, spinners, progress bars and interactive prompts. It is the counterpart of WinterCMS's artisan console commands (registerConsoleCommand and Laravel's Illuminate\Console\Command output helpers).
Role in the framework
A base layer for console commands; its only framework dependency is sunscreen, which scrubs credentials from error messages. pact uses it so plugins can contribute commands, and lagoon, conga, cabana, lighthouse, flare and surf build their own commands (migrate, workers, serve, admin tools) on it. Keeping it separate means every module describes commands the same way without depending on the CLI binary.
Use cases:
- Add a plugin command such as blog:import with typed arguments and options.
- Ask for confirmation or input interactively and print styled tables and progress.
- Ship operational commands (backfills, health checks) inside the same binary as the application.
Features
- Command values with a description, positional arguments (
bonfire.Arg) and string flags (bonfire.Flag), collected from plugins or the tool itself. - Command name validation: plugin commands must use the
namespace:verbform (for exampleblog:import);build,dev,new,serveandmigrateare the only bare names accepted. Invalid names makebonfire.NewRootfail withbonfire.ErrCommandName. - Usage strings and argument-count checks derived from the declared arguments (
<name>for required,[name]for optional). - Scalar flags, bare flags (
bonfire.Flag.Bare, so--forcealone storestrue) and ordered repeatable flags (bonfire.Flag.Repeatable, read back throughbonfire.Input.Flags). - Styled status lines:
bonfire.Output.Info,bonfire.Output.Success,bonfire.Output.Warningandbonfire.Output.Error(the last one writes to the error stream). - Widgets on the charm stack, ported from SSU's terminal UI: box-drawn tables (
bonfire.Output.Table, lipgloss), a spinner around a function (bonfire.Output.Spinner), a gradient progress bar (bonfire.Output.Progress, whosebonfire.Progressalso implementsbonfire.ItemProgress) and a multi-row process view for bulk loops (bonfire.Process), each a bubbletea program that keeps stdin and signals to the command. Log lines and output printed while a view runs appear above it (bonfire.PrintAbove). - Prompts:
bonfire.Output.Ask,bonfire.Output.Confirmandbonfire.Output.Secretuse huh fields on an interactive terminal, andbonfire.Output.Choicean arrow-key selector with a confirmation screen. Off a terminal they read lines from stdin as before (a piped password still works), return their defaults when input ends, andbonfire.Output.Confirmreturns its default without asking. A cancelled prompt returnsbonfire.ErrAborted.bonfire.ConfirmProceedasks before a destructive step only on an interactive terminal. - Degradation: every widget follows the terminal and the global options, and the
bonfire.Outputinterface is unchanged for plugins:Run Animation Decoration Data Prompts Terminal spinner, progress bar, process view colors, symbols, boxed tables printed huh fields and the selector Not a terminal none: [...] messageand a line per tenth of progresssymbols, no colors unless FORCE_COLORprinted, tables tab-separated read a line from stdin; Confirmreturns its default-qnone info, success and warning lines hidden; errors kept printed ( Println,Printf, tables)defaults, as with -n--no-ansinone symbols, no colors printed plain line prompts -nnone as on the terminal printed defaults; Secretreads piped stdin or fails with a hint - Command state for helpers:
bonfire.GlobalsOf,bonfire.VerbosityOf,bonfire.IsQuietandbonfire.Interactiveread the running command's global options from itsbonfire.Output. - Passthrough commands (
bonfire.Command.Passthrough) skip flag parsing, so every argument, including--help, reachesbonfire.Input.Argsunchanged; this suits commands that forward to another program.bonfire.Command.Longadds extended help text that the help command shows instead of the description. - Artisan-style command list: a bare binary (or
--help, orlist) prints a version header (bonfire.Version.Header, read from the build information bybonfire.BuildVersion), the usage line, the global options and the commands, top-level names first and then one block per namespace, with aligned descriptions. - Global options on every command:
-v/-vv/-vvv(--verbose),-q(--quiet, implies-n),--ansi/--no-ansi(last wins),-n(--no-interaction) and--env.bonfire.PeekGlobalsreads them from the raw arguments before the root exists, so the generated main can pick the configuration environment and the log verbosity before plugins boot;bonfire.OnGlobalshooks receive the parsedbonfire.Globalsbefore every command runs (roots built bybonfire.Callrun no hook). - Artisan-style help:
help <command>and<command> --helpprint Description, Usage, Arguments, Options with[default: ...]and "(multiple values allowed)", the global options, Help (bonfire.Command.Long) and Examples (bonfire.Command.Examples, printed afterbin/<name>orsummer). - Hidden commands (
bonfire.Command.Hidden) run but stay out of the list unlesslist --all. - JSON catalog:
list --format=jsonprints every command, hidden ones flagged, with arguments, options, defaults and examples as abonfire.Listing(bonfire.Describebuilds it from a command slice). - Documentation coverage:
bonfire.CheckDocsreports commands lacking a description, long help, examples, or argument and option descriptions. - Error block and exit codes:
bonfire.Executeruns the root through fang and returnsbonfire.ExitOK,bonfire.ExitFailure, orbonfire.ExitUsagefor an unknown command or flag and a wrong number of arguments (bonfire.UsageError). Errors print on the error stream as a block (bonfire.RenderError): the ERROR badge and the message, the explanation and Try lines ofbonfire.WithHint({bin}becomesbin/<name>orsummer), cobra's "did you mean" suggestions as Try lines, and "Run with -v for details." or, at-v, the wrapped error chain; every message passes throughsunscreen.Scrub. Errors wrapped bybonfire.Reportedprint nothing.bonfire.Failprints errors that happen before the root exists. - Name checks at boot: two commands with the same name, or a command named
listorhelp, makebonfire.NewRootfail withbonfire.ErrDuplicateCommand; a command flag that reuses a global option's name or shorthand fails withbonfire.ErrReservedFlaginstead of panicking. - Injectable streams (
bonfire.NewRootIO,bonfire.NewOutput) so commands can be tested against buffers. - In-process calls:
bonfire.Callruns a named command with arguments against any writer (LaravelArtisan::call), andbonfire.Catalogholds an application binary's final command list so code outside the Cobra root, such as the conga scheduler, can call any registered command.
Usage
package main
import (
"context"
"os"
"git.golem15.com/golem15/summercms/modules/bonfire"
)
func main() {
importPosts := bonfire.Command{
Name: "blog:import",
Description: "Import posts from a feed",
Args: []bonfire.Arg{{Name: "url", Description: "Feed URL", Required: true}},
Flags: []bonfire.Flag{
{Name: "dry-run", Description: "Report without writing", Bare: true},
{Name: "tag", Description: "Tag to apply (repeatable)", Repeatable: true},
},
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
url, _ := in.Argument("url")
_, dryRun := in.Flag("dry-run")
return out.Spinner("Importing "+url, func() error {
out.Table([]string{"Tag"}, [][]string{{"news"}})
if dryRun {
out.Warning("dry run: nothing written")
}
return nil
})
},
}
args := os.Args[1:]
g := bonfire.PeekGlobals(args)
root, err := bonfire.NewRoot("acme", []bonfire.Command{importPosts}, os.Stdout,
bonfire.WithVersion(bonfire.BuildVersion()))
if err != nil {
os.Exit(bonfire.Fail(os.Stderr, "acme", err, g))
}
os.Exit(bonfire.Execute(context.Background(), root, args))
}
Running a command in-process, with empty stdin so prompts take their defaults:
catalog := bonfire.NewCatalog(commands)
if catalog.Has("blog:import") {
err := catalog.Call(ctx, "blog:import", []string{"--dry-run", "https://example.com/feed"}, os.Stdout)
if errors.Is(err, bonfire.ErrUnknownCommand) {
// not registered in this binary
}
}
API reference
| Identifier | Description |
|---|---|
bonfire.Command |
A console command: name, description, optional extended help (bonfire.Command.Long), flags, arguments, the bonfire.Command.Passthrough switch that hands every argument to Run unparsed, the bonfire.Command.Run function, usage examples (bonfire.Command.Examples) and the bonfire.Command.Hidden switch. |
bonfire.Example |
One usage example: the command line without the binary (Command) and a short Description. |
bonfire.Flag |
A string flag; bonfire.Flag.Bare allows the flag without a value, bonfire.Flag.Repeatable makes it an ordered multi-value flag. |
bonfire.Arg |
A positional argument with a name, description and required marker. |
bonfire.Input |
Parsed view handed to bonfire.Command.Run: bonfire.Input.Args, bonfire.Input.Argument, bonfire.Input.Flag and bonfire.Input.Flags. |
bonfire.Output |
Injected console: printing, status lines, tables, spinner, progress bar and prompts. |
bonfire.Progress |
A progress bar advanced from inside bonfire.Output.Progress. |
bonfire.NewRoot |
Builds the Cobra root command for a binary from a slice of commands, using the process stdin, with the global options, the list command and the command list on a bare call; takes bonfire.RootOption values. When the output is the process stdout, the error block goes to stderr. |
bonfire.NewRootIO |
bonfire.NewRoot with injected stdin, stdout and stderr. |
bonfire.RootOption |
An option of bonfire.NewRoot and bonfire.NewRootIO. |
bonfire.WithVersion |
Sets the bonfire.Version shown in the list header; without it the root reads bonfire.BuildVersion. |
bonfire.OnGlobals |
Registers a function that receives the parsed bonfire.Globals before every command of the root runs. |
bonfire.Globals |
The global options: Verbosity (0 to 3), Quiet, Color (a bonfire.ColorMode), NoInteraction and Env. |
bonfire.PeekGlobals |
Reads bonfire.Globals from raw arguments without failing; stops at --. |
bonfire.Globals.Args |
Turns bonfire.Globals back into flags (-vv, -q, -n, --ansi or --no-ansi, --env=<value>) for forwarding. |
bonfire.Version |
Application and framework versions of a binary (App, Framework, Local). |
bonfire.BuildVersion |
Reads the bonfire.Version from the binary's build information. |
bonfire.Version.Header |
The list header: SummerCMS acme v1.4.0 (framework v0.3.2). |
bonfire.Execute |
Runs a root with arguments through fang and returns the exit code; prints the list when no command is given. |
bonfire.Fail |
Renders an error that happened before the root exists and returns bonfire.ExitFailure. |
bonfire.ExitOK, bonfire.ExitFailure, bonfire.ExitUsage |
The exit codes 0, 1 and 2. |
bonfire.RenderError |
Writes an error as the console error block. |
bonfire.UsageError |
Marks a usage mistake (exit code 2); bonfire.Usage wraps an error in it. |
bonfire.Usage |
Wraps an error as a bonfire.UsageError. |
bonfire.HintError |
An error with an Explanation and a Try text that the error block prints. |
bonfire.WithHint |
Wraps an error in a bonfire.HintError; {bin} in the Try text becomes bin/<name> or summer. |
bonfire.ErrReported |
Marks an error whose details the command already printed. |
bonfire.Reported |
Wraps an error so it matches bonfire.ErrReported: exit code 1, no error block. |
bonfire.Listing |
The JSON catalog of list --format=json: Application (bonfire.ApplicationInfo), GlobalOptions and Commands. |
bonfire.ApplicationInfo |
Name, version and framework version of a binary in a bonfire.Listing. |
bonfire.CommandInfo |
One command of a bonfire.Listing: name, description, long help, usage, hidden and passthrough switches, Arguments, Options and Examples. |
bonfire.ArgumentInfo |
One argument of a bonfire.CommandInfo. |
bonfire.OptionInfo |
One flag of a bonfire.CommandInfo, or a global option, with its default. |
bonfire.ExampleInfo |
One example of a bonfire.CommandInfo. |
bonfire.Describe |
Builds the bonfire.Listing of a binary from its commands and bonfire.Version. |
bonfire.CheckDocs |
Reports commands lacking a description, long help, examples or argument and option descriptions, as bonfire.DocProblem values. |
bonfire.DocProblem |
One command's documentation gaps; bonfire.DocProblem.String reads ": missing description, examples". |
bonfire.ItemProgress |
The optional side of a bonfire.Progress that names the current item (Current) and records failures (Fail); bonfire.Output.Progress hands one over. |
bonfire.Current |
Names the current item on a bonfire.Progress that is an bonfire.ItemProgress. |
bonfire.Failed |
Records a failed item on a bonfire.Progress that is an bonfire.ItemProgress. |
bonfire.Process |
Runs a function per item with the process view on a terminal, or one ✓/✗ line per item; returns the joined failures. |
bonfire.PrintAbove |
Prints a line above the running live view and reports whether one was running. |
bonfire.GlobalsOf |
The bonfire.Globals of the command an Output belongs to (zero for other Output implementations). |
bonfire.VerbosityOf |
The -v level of the command an Output belongs to. |
bonfire.IsQuiet |
Whether the command runs with -q. |
bonfire.IsTerminal |
Whether the Output writes to a terminal; long-running commands print a summary block for a person there and log records otherwise. |
bonfire.Details, bonfire.Detail |
Prints a title and aligned label/value lines in the palette (a Warn value in the warning color); nothing under -q. Used for startup summaries and shutdown reports. |
bonfire.Interactive |
Whether the Output can ask a person: stdin and stdout are terminals and neither -n nor -q is set. |
bonfire.ConfirmProceed |
Asks before a destructive step on an interactive terminal; returns true without asking otherwise. |
bonfire.ErrAborted |
Returned by a prompt the person cancelled. |
bonfire.ErrDuplicateCommand |
Returned by bonfire.NewRoot when two commands share a name or a command is named list or help. |
bonfire.ErrReservedFlag |
Returned by bonfire.NewRoot when a command flag reuses a global option's name or shorthand. |
bonfire.NewOutput |
Builds a bonfire.Output over the given streams, applying the terminal and color policy. |
bonfire.ErrCommandName |
Returned when a plugin command name is not in namespace:verb form. |
bonfire.Call |
Runs one command of a slice by exact name with arguments, writing output to a writer; stdin is empty. |
bonfire.ErrUnknownCommand |
Returned by bonfire.Call when no command has the name. |
bonfire.Catalog |
An immutable copy of a binary's command list; the generated app main publishes one on the app. |
bonfire.NewCatalog |
Builds a bonfire.Catalog from a command slice. |
bonfire.ColorMode |
How console output decides on color: bonfire.ColorAuto (terminal and environment), bonfire.ColorAlways or bonfire.ColorNever. |
bonfire.Palette |
The console styles (Success, Current, Warning, Error, ErrorBold, Muted, Header), shared with the diary console log lines. |
bonfire.DefaultPalette |
Returns the palette ported from SSU's status styles: green success, cyan current, yellow warning, red and bold red errors, gray muted text, bold headers. |
bonfire.DetectProfile |
Returns the color profile for a writer under a bonfire.ColorMode, honouring NO_COLOR, TERM=dumb and FORCE_COLOR; render palette styles through a colorprofile.Writer with it. |
Configuration
bonfire reads no config keys. Output color follows these environment variables:
| Variable | Effect |
|---|---|
NO_COLOR |
Any non-empty value disables color. |
TERM |
The value dumb disables color. |
FORCE_COLOR |
Any non-empty value enables color even when output is not a terminal (ignored when color is disabled by NO_COLOR or TERM). |
Without these variables, color is enabled only when stdout is a terminal. --ansi forces color and --no-ansi turns it off whatever the environment says.
Dependencies
- SummerCMS modules:
sunscreen(scrubs error messages). - Third-party:
github.com/spf13/cobra,github.com/spf13/pflag,golang.org/x/term,charm.land/lipgloss/v2(withlipgloss/v2/table),charm.land/bubbletea/v2,charm.land/bubbles/v2(spinner, progress, key),charm.land/huh/v2,charm.land/fang/v2andgithub.com/charmbracelet/colorprofile(decided in.planning/notes/2026-10-21-console-charm-stack.md). - Standard library:
bufio,context,encoding/json,errors,fmt,io,os,runtime/debug,slices,strconv,strings,sync,time,unicode/utf8.
Testing
go test ./modules/bonfire/...
The tests use in-memory streams and need no external services.