Documentation
¶
Overview ¶
Package kit turns one declaration of an operation into three surfaces: a CLI subcommand, an HTTP route, and an MCP tool. A domain describes what its verbs do and how their inputs and outputs are shaped; kit builds the command tree, the flag parser, the JSON-over-HTTP server, and the MCP server from that one description.
The model ¶
An operation is a typed handler with surface-neutral metadata. The handler takes a context, a typed input struct, and an emit callback; it streams zero or more records to emit and returns an error. kit reflects the input struct once to learn its arguments and flags, and the output struct to learn the record's collection name and primary key. The same operation then renders as a CLI subcommand, answers an HTTP request, and exposes an MCP tool, with no per-surface code in the domain.
type searchIn struct {
Query string `kit:"arg" help:"URL or prefix to search"`
Limit int `kit:"flag" default:"20" help:"max captures"`
Client *crawl.Client `kit:"inject"`
}
type Capture struct {
URL string `json:"url" kit:"id"`
Status int `json:"status"`
}
kit.Handle(app, kit.OpMeta{
Name: "search",
Group: "read",
Summary: "Find captures for a URL or prefix",
}, func(ctx context.Context, in searchIn, emit func(Capture) error) error {
return in.Client.Search(ctx, in.Query, in.Limit, emit)
})
Input fields ¶
A field's kit tag says how kit fills it:
kit:"arg" a positional argument, in declaration order kit:"flag" a named flag / query parameter / tool argument (the default) kit:"inject" filled by kit with the domain client or record store
Options after the kind refine the binding:
name=foo override the derived snake_case name short=q single-letter CLI shorthand (-q) variadic an arg that takes the rest of the positionals; a slice flag inherit bind to a framework-global flag of the same name (e.g. limit)
Sibling tags describe the value: help for the one-line description, default for the unset value, and enum for a comma-separated set of allowed values.
An output field tagged kit:"id" (or named id) is the record's primary key, used to upsert into the record store when --db is set.
Building an app ¶
A domain builds one App, registers its operations, sets a client factory, and hands the App to Run:
func main() {
app := kit.New(kit.Identity{
Binary: "ccrawl",
Short: "A command line for Common Crawl",
Version: version,
})
app.SetClient(func(ctx context.Context, c kit.Config) (any, error) {
return crawl.New(c), nil
})
registerOps(app)
kit.Main(app)
}
New derives the baseline Config (XDG paths, env prefix, rate, retries, timeout, workers) from the binary name; WithDefaults overlays the domain's own values. SetClient registers the factory kit calls once per run; its result is injected into handler fields tagged kit:"inject" by assignability. GlobalFlags registers persistent flags that are not part of the baseline, and the domain reads them when building its client.
Escape hatches ¶
Some verbs do not fit the emit-records shape: a byte stream, an interactive shell, a bulk download, or a parent that only groups subcommands. A domain declares those as a Command, a small struct of metadata wired to named functions, without naming the underlying cobra and pflag types:
app.AddCommand(kit.Command{
Use: "get <url>",
Short: "Print the bytes Common Crawl captured",
Args: kit.ExactArgs(1),
Flags: c.flags,
Run: c.run,
})
Command.Flags binds flags through a FlagSet, whose method set mirrors pflag (StringVar, StringVarP, IntVar, BoolVar, and so on). Command.Args validates the positionals with one of ExactArgs, MinimumNArgs, MaximumNArgs, RangeArgs, or NoArgs. A run handler reaches the run's resolved config, client, and record store with MustClient (or Client when building can fail), the typed companions to FromContext and State.Client. Escape-hatch commands appear on the CLI only; the HTTP and MCP surfaces expose registered operations.
Surfaces ¶
The default invocation runs the CLI. The serve subcommand starts the HTTP server, mcp starts the MCP server over stdio, and each exposes the same operations. Run returns a process exit code derived from the error: the github.com/tamnd/any-cli/kit/errs taxonomy maps a usage error to 2, an empty result to 3, a missing credential to 4, a rate limit to 5, and so on, so the same failure yields the same code on every surface.
Index ¶
- func Client[T any](ctx context.Context) (T, error)
- func Handle[In, Out any](app *App, meta OpMeta, fn func(context.Context, In, func(Out) error) error)
- func Main(app *App)
- func MustClient[T any](ctx context.Context) T
- func NoArgs(args []string) error
- func Run(ctx context.Context, app *App) int
- func WithState(ctx context.Context, st *State) context.Context
- type App
- func (a *App) AddCommand(cmd Command)
- func (a *App) AddCommandUnder(parent string, cmd Command)
- func (a *App) CommandGroup(name, summary string)
- func (a *App) Config() Config
- func (a *App) Finalize(fn func(*Config))
- func (a *App) GlobalFlags(fn func(*FlagSet))
- func (a *App) Identity() Identity
- func (a *App) Ops() []Operation
- func (a *App) SetClient(fn func(context.Context, Config) (any, error))
- type Arg
- type Args
- type Command
- type Config
- type FlagSet
- func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string)
- func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string)
- func (f *FlagSet) CountVar(p *int, name, usage string)
- func (f *FlagSet) CountVarP(p *int, name, shorthand, usage string)
- func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string)
- func (f *FlagSet) DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string)
- func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string)
- func (f *FlagSet) Float64VarP(p *float64, name, shorthand string, value float64, usage string)
- func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string)
- func (f *FlagSet) Int64VarP(p *int64, name, shorthand string, value int64, usage string)
- func (f *FlagSet) IntVar(p *int, name string, value int, usage string)
- func (f *FlagSet) IntVarP(p *int, name, shorthand string, value int, usage string)
- func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string)
- func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string)
- func (f *FlagSet) StringVar(p *string, name, value, usage string)
- func (f *FlagSet) StringVarP(p *string, name, shorthand, value, usage string)
- type Globals
- type Identity
- type Input
- type OpMeta
- type Operation
- type Option
- type OutputOptions
- type ParamKind
- type ParamSpec
- type ParamType
- type RunContext
- type Sink
- type State
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Client ¶
Client returns the run's domain client typed as T. It is the typed companion to State.Client: an escape-hatch command that wants the concrete app it registered with SetClient calls Client[*MyApp](ctx) instead of fetching the state and asserting the any itself.
It returns an error when there is no run state on the context, when building the client failed, or when the client is not a T.
func Handle ¶
func Handle[In, Out any](app *App, meta OpMeta, fn func(context.Context, In, func(Out) error) error)
Handle registers a typed handler. It is the only registration call a domain makes. It reflects In to build the parameter set once, and Out to learn the record's collection name and primary key for the store tee.
func Main ¶
func Main(app *App)
Main is a convenience wrapper that runs the app with a cancellable context and calls os.Exit with the resulting code.
func MustClient ¶
MustClient is Client for clients that cannot fail to build. When a command's client factory is infallible (it only assembles config and shared handles), a missing or mistyped client is a wiring bug, not a runtime condition, so MustClient panics rather than making every command thread an impossible error. This mirrors template.Must and regexp.MustCompile.
func Run ¶
Run is the single entrypoint a generated main calls. It builds the CLI tree from the registry, wraps it in fang for help, completion, and error rendering, and executes. The default invocation is the CLI; the serve, mcp, and tui subcommands switch surfaces. Run returns the process exit code.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the registry every surface drives. A domain builds one with New, registers operations with Handle, sets its client factory, and hands the App to Run. The App holds no surface state of its own; each surface reads the operation list and the config to build itself.
func New ¶
New builds an App from its identity. It derives the baseline config from the binary name, then applies any options.
func (*App) AddCommand ¶
AddCommand is the escape hatch for an operation that does not fit the emit-records shape (an interactive shell, a DuckDB analytical console, a binary download). The command joins the CLI tree as-is. It is absent from the API and MCP surfaces, which expose only registered operations.
func (*App) AddCommandUnder ¶
AddCommandUnder attaches an escape-hatch command beneath a parent command, the same parent a nested operation declares with OpMeta.Parent. It lets a domain mix generated operations and hand-rolled commands under one group, so "crawls list" (an op) and "crawls info" (an escape hatch) sit side by side.
func (*App) CommandGroup ¶
CommandGroup sets the help summary for a parent command, whether that parent hosts operations, escape-hatch commands, or both. Without it a parent shows a generated one-line summary.
func (*App) Config ¶
Config returns the resolved baseline config, for a domain that needs to read a default while wiring its client factory.
func (*App) Finalize ¶
Finalize registers a hook kit calls after folding the framework globals onto the config, so a domain can apply its own global flags to the resolved Config (for instance copying a --data-dir-derived path into a domain field).
func (*App) GlobalFlags ¶
GlobalFlags lets a domain register its own persistent flags on the root, for settings that are not part of the framework baseline (for ccrawl: --crawl, --source, --library). The domain binds them to its own variables and reads those when building its client and its escape-hatch commands.
type Args ¶
Args validates a command's positional arguments. The constructors below cover the common cases; any function with this signature works, and returning an errs.Usage error keeps the exit code consistent with the rest of kit.
func MaximumNArgs ¶
MaximumNArgs allows at most n positional arguments.
func MinimumNArgs ¶
MinimumNArgs requires at least n positional arguments.
type Command ¶
type Command struct {
Use string // usage line, e.g. "paths <kind>"
Short string // one-line summary
Long string // multi-paragraph help
Aliases []string // alternate names
Group string // help group ID, matching an operation's Group
Args Args // positional-argument check; nil accepts any
Flags func(*FlagSet) // binds this command's flags; nil for none
Run func(context.Context, []string) error // handler; nil for a parent
Sub []Command // subcommands
Write bool // marks a mutating command (annotated for surfaces)
}
Command is a hand-written escape-hatch command, declared without naming the underlying cobra/pflag types. A domain reaches for it when a verb does not fit the emit-records shape of an operation: a byte stream, an interactive shell, a bulk download, or a parent that only groups subcommands.
The function-typed fields take named methods or functions, not closures, so a command reads as a small struct of metadata wired to package-level behaviour:
func newPathsCmd() kit.Command {
c := &pathsCmd{}
return kit.Command{
Use: "paths <kind>",
Short: "List the archive file paths for a crawl",
Args: kit.MaximumNArgs(1),
Flags: c.flags,
Run: c.run,
}
}
Escape-hatch commands appear on the CLI only; the HTTP and MCP surfaces expose registered operations.
type Config ¶
type Config struct {
Binary string // the binary name, e.g. "ccrawl"
EnvPrefix string // env var prefix, e.g. "CCRAWL"
DataDir string // root for cache, store, sessions
CacheDir string // on-disk content cache
ConfigDir string // config file directory
Profile string // selected profile name
Rate time.Duration // minimum delay between requests
Retries int // retry attempts on 429/5xx
Timeout time.Duration // per-request timeout
Workers int // bulk concurrency
NoCache bool // bypass caches
Color string // auto|always|never
UserAgent string // descriptive default UA
DB string // --db DSN for the record store (empty = no store)
Verbose int // verbosity level
Quiet bool // suppress progress
DryRun bool // print actions, do not perform them
Extra map[string]string
}
Config is the resolved runtime configuration kit hands a domain when it builds the client. The common fields live here so a domain does not re-derive XDG paths, rate, retries, and the like; a domain adds only its own extra settings (carried in Extra or read from its own env). Precedence is applied by the CLI surface: flags over env over the config file over these defaults.
func DefaultConfig ¶
DefaultConfig builds the baseline config for a binary, deriving the XDG paths and env prefix from the name. A domain overlays its own defaults on top.
type FlagSet ¶
type FlagSet struct {
// contains filtered or unexported fields
}
FlagSet binds a command's flags to a domain's own variables, the way a flag.FlagSet does, but without exposing the cobra/pflag types to the domain. The method set mirrors pflag one-for-one: each Var binds a long flag, and the matching VarP variant adds a single-letter shorthand. A domain that needs a flag type not listed here can request one; the set covers the common cases.
func (*FlagSet) CountVarP ¶
CountVarP binds a repeatable counting flag with a shorthand (e.g. -vvv).
func (*FlagSet) DurationVar ¶
DurationVar binds a time.Duration flag (e.g. --timeout 30s).
func (*FlagSet) DurationVarP ¶
func (f *FlagSet) DurationVarP(p *time.Duration, name, shorthand string, value time.Duration, usage string)
DurationVarP binds a time.Duration flag with a shorthand.
func (*FlagSet) Float64Var ¶
Float64Var binds a float64 flag.
func (*FlagSet) Float64VarP ¶
Float64VarP binds a float64 flag with a shorthand.
func (*FlagSet) StringSliceVar ¶
StringSliceVar binds a repeatable string flag, collected into a slice.
func (*FlagSet) StringSliceVarP ¶
func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []string, usage string)
StringSliceVarP binds a repeatable string flag with a shorthand.
func (*FlagSet) StringVarP ¶
StringVarP binds a string flag with a shorthand.
type Globals ¶
type Globals struct {
Limit int // --limit / -n; 0 means no limit
}
Globals holds the framework-wide flags shared by every operation. They are parsed once by the surface and threaded through to Invoke.
type Identity ¶
type Identity struct {
Binary string // binary and command name, e.g. "ccrawl"
Short string // one-line description
Long string // multi-paragraph description for help
Version string // semantic version, set by the build
Site string // the upstream site or service this CLI targets
Repo string // source repository URL
}
Identity is the fixed description of a CLI: its name, what it does, and where it lives. It seeds help text, the API title, the MCP server name, and the default env prefix.
type Input ¶
type Input struct {
Args []string // positional arguments in order
Flags map[string]any // named parameters, already typed where possible
Globals Globals // resolved framework globals
}
Input is the surface-neutral request a surface hands to Operation.Invoke. The CLI fills Args from positional tokens and Flags from parsed flags; the API fills them from the path and query; MCP fills Flags from the tool arguments object. Globals carries the resolved framework-global flags.
type OpMeta ¶
type OpMeta struct {
Name string // the verb: CLI subcommand, MCP tool suffix, route leaf
Parent string // optional parent command for a nested verb (e.g. "rank" for "rank domain")
Group string // grouping for help (e.g. "read", "data", "meta")
Summary string // one line, shown in help, OpenAPI summary, MCP description
Long string // optional multi-line help
Aliases []string // alternate names
Args []Arg // positional argument schema (names line up with In arg fields)
Write bool // a state-changing op; gated and annotated on every surface
Single bool // emits at most one record (no "no results" on empty stream)
}
OpMeta is the surface-neutral description of one operation. The same metadata drives the CLI subcommand, the HTTP route, and the MCP tool.
type Operation ¶
type Operation interface {
Meta() OpMeta
Params() []ParamSpec
InputSchema() map[string]any
OutputSchema() map[string]any
// Invoke binds the surface-neutral Input to the typed In, runs the handler,
// and routes each emitted record to the sink, applying the store tee and the
// limit. rt carries the injected handles.
Invoke(ctx context.Context, in Input, rt RunContext, sink Sink) error
}
Operation is what the registry stores and every surface drives. The concrete implementation is the generic op[In, Out] built by Handle.
type Option ¶
type Option func(*App)
Option customizes an App at construction.
func WithDefaults ¶
WithDefaults lets a domain overlay its own config defaults (rate, retries, workers, user agent) onto the framework baseline.
type OutputOptions ¶
type OutputOptions struct {
Format string
Fields []string
NoHeader bool
Template string
IsTTY bool
Width int
}
OutputOptions are the resolved rendering settings an escape-hatch command reuses so its structured output matches every operation's: same --output format, --fields projection, --no-header, and --template.
type ParamSpec ¶
type ParamSpec struct {
Name string
Kind ParamKind
Type ParamType
Short string
Help string
Default string
Enum []string
Variadic bool
Inherit bool // bind to a framework-global flag of the same name
}
ParamSpec is the external description of one bound parameter, used to build CLI flags, query params, and JSON Schema properties.
type RunContext ¶
RunContext carries the injected handles an operation may receive: the domain client, the optional record store, and the resolved limit. A handler reaches these only through KindInject fields, never directly.
type Sink ¶
Sink is where an operation's records go. Each surface supplies its own: the CLI sink renders to the terminal, the API sink streams an HTTP response, the MCP sink accumulates structured tool content. A handler only ever calls emit(record); swapping the sink swaps the surface with no change to domain code.
type State ¶
type State struct {
Config Config
Globals Globals
Output OutputOptions // resolved rendering settings, for escape-hatch commands
// contains filtered or unexported fields
}
State is the per-run context kit resolves once and shares with every command, whether a registered operation or an escape-hatch command added with AddCommand. It carries the resolved config, the lazily built domain client, and the optional record store. An escape-hatch command reaches it with FromContext(cmd.Context()).
func FromContext ¶
FromContext returns the run State stored on the context, or nil if absent. An escape-hatch command calls this to reach the resolved config, client, and store that kit built for the run.
func (*State) Client ¶
Client builds the domain client on first use and caches it (and any error) for the rest of the run. It returns nil when the app registered no client factory.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package errs defines the typed error taxonomy shared by every kit-based CLI and its one true mapping to CLI exit codes, HTTP status codes, and MCP error objects.
|
Package errs defines the typed error taxonomy shared by every kit-based CLI and its one true mapping to CLI exit codes, HTTP status codes, and MCP error objects. |
|
Package render turns a stream of record structs into one of the output formats the kit CLI supports: table, json, jsonl, csv, tsv, url, raw, and a Go text/template.
|
Package render turns a stream of record structs into one of the output formats the kit CLI supports: table, json, jsonl, csv, tsv, url, raw, and a Go text/template. |
|
Package store defines the pluggable record-store SPI that lets any kit read double as a crawl: with --db set, every emitted record is upserted into a local store before it reaches the output.
|
Package store defines the pluggable record-store SPI that lets any kit read double as a crawl: with --db set, every emitted record is upserted into a local store before it reaches the output. |
|
sqlitestore
Package sqlitestore is the built-in default store backend.
|
Package sqlitestore is the built-in default store backend. |