Documentation
¶
Overview ¶
Package entity is the canonical home for clicky's entity + operation model: the entity registry and CLI generation (Entity, EntityBuilder, RegisterEntity, GenerateCLI), the command-function registries (AddCommand, the cobra annotation metadata), the RPC operation model (RPCOperation, Schema, …), and named, reusable filter/lookup definitions that work across both static (Go-struct) and dynamic (JSON-Schema) entities.
A filter is defined once — in Go via RegisterFilter, or declaratively via RegisterFilterSpec — and reused by name from any number of entities. The core abstraction (FilterSource + FilterContext) never sees a typed ListOpts, so the same definition serves a compile-time Go entity and a schema-driven dynamic entity identically.
On a static entity, attach a named filter with the typed adapter:
clicky.NewEntity[Task, TaskOpts, Task]("tasks").
Filters(entity.Use[TaskOpts]("users").As("owner")).
List(listTasks).
Register()
The dependency runs one way: this package depends only on clicky subpackages (api, flags, formatters, task) plus cobra/pflag — never on the root clicky package. The root clicky package imports this one and re-exports the model via type aliases and thin wrappers (see entity_aliases.go), so callers keep using clicky.NewEntity, clicky.Entity, clicky.RegisterEntity, etc. unchanged. Host-owned globals (the CLI format flags, HTTPie argument parsing) are injected through the RenderResult / ParseArgs hooks in render.go.
Index ¶
- Constants
- Variables
- func AcceptedFormat(accept string) (string, error)
- func AddCommand[T any, R any](parent *cobra.Command, opts T, fn func(opts T) (R, error)) *cobra.Command
- func AddCommandWithContext[T any, R any](parent *cobra.Command, opts T, fn func(ctx context.Context, opts T) (R, error)) *cobra.Command
- func AddNamedCommand[T any, R any](name string, parent *cobra.Command, opts T, fn func(opts T) (R, error)) *cobra.Command
- func AddNamedCommandWithContext[T any, R any](name string, parent *cobra.Command, opts T, ...) *cobra.Command
- func AnnotateTool(cmd *cobra.Command, hints MCPToolHints)
- func ContentDisposition(filename string) string
- func DeclaresTruncatedTrailer(req PageRequest, res PageResponse) bool
- func ExportContentType(format string) string
- func ExportExtension(format string) string
- func ExportHeaderNames() []string
- func ExportResponseHeaders() map[string]ExportHeaderDoc
- func GenerateCLI(parent *cobra.Command)
- func GetDataFunc(cmd *cobra.Command) func(flags map[string]string, args []string) (any, error)
- func GetLookupFunc(cmd *cobra.Command) func(flags map[string]string, args []string) (any, error)
- func IsLocalOnly(cmd *cobra.Command) bool
- func IsTabularExport(format string) bool
- func JoinPath(segments []string) string
- func MarkLocalOnly(cmd *cobra.Command)
- func RegisterDynamicEntity(spec DynamicEntitySpec)
- func RegisterDynamicEntityFamily(family DynamicEntityFamily)
- func RegisterEntity[T EntityItem, ListOpts any, R any](e Entity[T, ListOpts, R])
- func RegisterFilter(f NamedFilter)
- func RegisterFilterSpec(s FilterSpec)
- func RegisterSubCommand(parentName string, cmd *cobra.Command)
- func RegisterSubCommandFn(parentName string, build func(parent *cobra.Command))
- func RequestedFormat(r *http.Request) (string, error)
- func ResolveDynamicLookup(ctx context.Context, spec DynamicEntitySpec, flags map[string]string) (any, error)
- func SanitizeExportFilename(filename string) string
- func SetCORSHeaders(w http.ResponseWriter)
- func SetCommandResponseMeta(cmd *cobra.Command, meta ResponseOpenAPIMeta)
- func SetExportAllowedOrigin(origin string)
- func SetExportHeaders(w http.ResponseWriter, name string, req PageRequest, res PageResponse)
- func SplitPath(name, delimiters string) []string
- func SupportedExportFormat(format string) bool
- func UnregisterDynamicEntityFamily(name string) bool
- func Use[ListOpts any](filterName string) *attachedFilter[ListOpts]
- func WriteError(w http.ResponseWriter, err error)
- type ActionFlags
- type ActionInfo
- type ActionSpec
- func Action[R any](name string, fn func(id string, flags map[string]string) (R, error)) *ActionSpec[R]
- func ActionWithContext[R any](name string, ...) *ActionSpec[R]
- func ActionWithFlags[R any](name string, flags ActionFlags, ...) *ActionSpec[R]
- func ActionWithFlagsAndContext[R any](name string, flags ActionFlags, ...) *ActionSpec[R]
- func PrimaryActionWithContext[Opts ActionFlags, R any](opts Opts, fn func(context.Context, Opts) (R, error)) *ActionSpec[R]
- func (a *ActionSpec[R]) WithFlags(flags ActionFlags) *ActionSpec[R]
- func (a *ActionSpec[R]) WithMethod(method string) *ActionSpec[R]
- func (a *ActionSpec[R]) WithOptionalID() *ActionSpec[R]
- func (a *ActionSpec[R]) WithShort(short string) *ActionSpec[R]
- func (a *ActionSpec[R]) WithToolGroup(group string) *ActionSpec[R]
- func (a *ActionSpec[R]) WithToolHints(hints MCPToolHints) *ActionSpec[R]
- func (a *ActionSpec[R]) WithToolPermission(permission ToolPermission) *ActionSpec[R]
- type BulkActionInfo
- type BulkActionSpec
- func BulkAction[R any](name string, fn func(ids []string, flags map[string]string) (R, error)) *BulkActionSpec[R]
- func BulkActionWithFilter[ListOpts any, R any](name string, run func(ids []string, flags map[string]string) (R, error), ...) *BulkActionSpec[R]
- func BulkFilterAction[ListOpts any, R any](name string, fn func(opts ListOpts, flags map[string]string) (R, error)) *BulkActionSpec[R]
- type ClickyOperationMeta
- type ClickySpecMeta
- type ClickySurface
- type CommandOpenAPIMeta
- type Config
- type ContextDataFunc
- type ContextFilter
- type ContextLookupFunc
- type ContextSearchableFilter
- type DataFunc
- type DynamicEntityBuilder
- type DynamicEntityFamily
- type DynamicEntitySpec
- type DynamicFilter
- type DynamicGetFunc
- type DynamicListFunc
- type Entity
- type EntityAction
- type EntityBuilder
- func (b *EntityBuilder[T, ListOpts, R]) Admin(admin Entity[T, ListOpts, R]) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Aliases(aliases ...string) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Build() Entity[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Create(fn func(map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) CreateWithContext(fn func(context.Context, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Delete(fn func(string) error) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) DeleteWithContext(fn func(context.Context, string) error) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Filters(filters ...Filter[ListOpts]) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Get(fn func(string) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) GetWithContext(fn func(context.Context, string) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) GetWithFlags(flags ActionFlags, fn func(string, map[string]string) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) GetWithFlagsAndContext(flags ActionFlags, ...) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) List(fn func(ListOpts) ([]T, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ListPaged(fn func(ListOpts) (PagedResult[T], error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ListPagedWithContext(fn func(context.Context, ListOpts) (PagedResult[T], error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ListWithContext(fn func(context.Context, ListOpts) ([]T, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Parent(parent string) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Path(segments ...string) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Register()
- func (b *EntityBuilder[T, ListOpts, R]) ToolGroup(group string) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ToolHints(hints MCPToolHints) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ToolPermission(permission ToolPermission) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) Update(fn func(string, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) UpdateWithContext(fn func(context.Context, string, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) ValidArgs(fn func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) WithAction(action EntityAction) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) WithBulkAction(action EntityBulkAction) *EntityBuilder[T, ListOpts, R]
- func (b *EntityBuilder[T, ListOpts, R]) WithPrimaryAction(action EntityAction) *EntityBuilder[T, ListOpts, R]
- type EntityBulkAction
- type EntityInfo
- type EntityItem
- type EntityOperation
- type ExecutableCommand
- type ExecuteOptions
- type ExportHeaderDoc
- type ExportMeta
- type Filter
- type FilterContext
- type FilterSource
- type FilterSourceSpec
- type FilterSpec
- type Filterable
- type Help
- type LimitedFilter
- type MCPToolHints
- type MultiFilter
- type Name
- type NamedFilter
- type PageInfo
- type PageLimits
- type PageRequest
- type PageResponse
- type Paged
- type PagedFunc
- type PagedResult
- type Property
- type RPCOperation
- type RPCParameter
- type RPCService
- type ResponseOpenAPIMeta
- type Schema
- type SearchableFilter
- type StatusError
- type ToolPermission
- type Total
- type TypedFilter
Constants ¶
const ( AnnotationClickyToolGroup = annotationClickyToolGroup AnnotationClickyToolTitle = annotationClickyToolTitle AnnotationClickyToolIcon = annotationClickyToolIcon AnnotationClickyToolParent = annotationClickyToolParent AnnotationClickyToolReadOnlyHint = annotationClickyToolReadOnlyHint AnnotationClickyToolDestructiveHint = annotationClickyToolDestructive AnnotationClickyToolIdempotentHint = annotationClickyToolIdempotent AnnotationClickyToolOpenWorldHint = annotationClickyToolOpenWorld AnnotationClickyToolDefaultPermission = annotationClickyToolPermission AnnotationClickyToolStrict = annotationClickyToolStrict )
const ( ScopePage = "page" ScopeAll = "all" )
Export scopes. A request either asks for one page or for everything up to the operation's ceiling; there is no third answer, so anything else is refused rather than quietly treated as a page.
const ( ModePage = "page" ModeBuffered = "buffered" ModeStreaming = "streaming" )
Export modes describe how the rows were produced, which is orthogonal to the scope that was asked for: a scope=page request against an operation that has to run its whole pipeline before any row is correct reports ModeBuffered.
const ( // DefaultPageSize is the page an operation serves when the caller names none. DefaultPageSize = 100 // DefaultMaxPageSize is the largest single page a caller may ask for. Past // this, the answer is scope=all rather than a bigger page. DefaultMaxPageSize = 1000 )
These are the library's own fallbacks, applied only when a caller states no PageLimits. An application with a view on how much its backends can serve is expected to pass its own; nothing here is claimed to match any consumer's.
const ( SourceStatic = "static" SourceEntity = "entity" SourceFunc = "func" // Go-only; not constructible from a spec. SourceCustom = "custom" )
Source kinds for the declarative FilterSourceSpec.
const DefaultExportFormat = "json"
DefaultExportFormat is what a caller that expressed no preference gets.
const InternalErrorMessage = "internal server error"
InternalErrorMessage is the body message every unclassified failure is reported with, so an internal detail can never reach a client by accident.
const MaxLookupOptions = lookupOptionsLimit
MaxLookupOptions is lookupOptionsLimit exported, so a caller declaring a per-filter Limit can validate it against the ceiling rather than discovering at request time that it was silently reduced.
const PathSeparator = "/"
PathSeparator is the wire delimiter for ClickySurface.Path. Producers split their own naming convention (dots, slashes, colons — whatever the domain uses) with SplitPath and emit the result joined by this, so a frontend never has to know, or guess, the producer's convention.
Variables ¶
var ParseArgs = func(args []string) (map[string]any, error) { return nil, fmt.Errorf("entity.ParseArgs not configured: the root clicky package must wire it") }
ParseArgs parses HTTPie-style positional arguments (key=value, key:=json, …) into a body map for create/update commands. Root clicky wires it in init() to its ParseArgumentsAsMap; the default fails loudly so a missing wiring surfaces rather than silently dropping arguments.
var RenderResult = func(o any) error { return nil }
RenderResult renders a command result through the host application's format pipeline — the global --format/--output flags and any configured sinks. Root clicky wires it in init() (to ParseFormatSpec + PrintAndWriteSinks against its global Flags); the default no-op lets the entity package's own unit tests run without a renderer.
It is a package-level hook rather than a direct dependency so the entity package stays free of the root clicky package (which owns the global flag state) — preserving the one-way entity → clicky-subpackages dependency.
Functions ¶
func AcceptedFormat ¶ added in v1.21.55
AcceptedFormat picks the format an Accept header asks for.
Accept is ranked, not ordered: a caller listing text/html first at q=0.1 and the clicky envelope at q=0.9 is asking for the envelope. Reading the first recognised entry answers with the one it weighted lowest. Ties keep the earlier entry, which is the order the caller wrote them in.
A q=0 is a refusal, not a low preference, and a refusal has to be able to leave nothing acceptable behind. Seeding the search with the default format would make `Accept: application/json;q=0` answer with the very thing it just refused, so refusals are tracked and the fallback is withheld when the default is among them — the honest answer there is 406. A wildcard resolves to the default for exactly this reason, so `*/*;q=0` refuses everything rather than silently accepting the default.
A refusal only decides the answer when nothing else was acceptable, which is what lets a named range override a blanket one: `*/*;q=0, text/csv` is a caller that wants csv and nothing else.
func AddCommand ¶
func AddCommand[T any, R any](parent *cobra.Command, opts T, fn func(opts T) (R, error)) *cobra.Command
AddCommand creates a Cobra command with automatic flag parsing from struct tags, execution, and result formatting.
Type parameter T must be a struct with tags defining flags:
- flag:"name" - flag name (required)
- help:"description" - flag help text
- default:"value" - default value
- short:"x" - short flag variant
- required:"true" - mark flag as required
- stdin:"true" - mark field as default for stdin input (only one field)
Supports @ prefix for file/URL loading:
- @file.txt - read from file
- @https://... - fetch from URL
- For slices: one value per line (skip empty lines and # comments)
- For slices: @file.csv:ColumnName reads a named column from a CSV (first row is the header, empty cells are skipped)
- For slices: @file.xlsx:ColumnName / @file.xls:ColumnName reads a named column from the first worksheet (case-insensitive header match)
Supported types:
- string, int, bool
- duration.Duration (supports "30d", "2w", etc.)
- time.Time (supports datamath like "now-7d", "now/d", "now-1M/M")
- []string, []int (slices)
Datamath expressions for time.Time fields (Elasticsearch compatible):
- now : current time
- now-7d : 7 days ago
- now+2h : 2 hours from now
- now/d : start of current day
- now-7d/d : start of day 7 days ago
- now/w : start of this week
- now-1M/M : start of last month
- now/y : start of this year
- Units: y (year), M (month), w (week), d (day), h (hour), m (minute), s (second)
Example:
type ListOptions struct {
Role string `flag:"role" help:"Filter by role" short:"r"`
Limit int `flag:"limit" help:"Max results" default:"50"`
Since time.Time `flag:"since" help:"Created since" default:"now-30d"`
Tags []string `flag:"tags" help:"Filter tags" stdin:"true"`
MaxAge duration.Duration `flag:"max-age" help:"Max age" default:"365d"`
}
cmd := &cobra.Command{Use: "list", Short: "List users"}
clicky.AddCommand(cmd, ListOptions{}, func(opts ListOptions) (any, error) {
return fetchUsers(opts)
})
Usage examples:
myapp list --tags @tags.txt # load from file myapp list --since now-30d # datamath echo -e "tag1\ntag2" | myapp list # stdin myapp list --max-age 60d --json # with formatting
func AddCommandWithContext ¶
func AddCommandWithContext[T any, R any](parent *cobra.Command, opts T, fn func(ctx context.Context, opts T) (R, error)) *cobra.Command
AddCommandWithContext is AddCommand whose closure receives the request-scoped context (cmd.Context() on the CLI, r.Context() over HTTP). It derives the command name from the opts struct exactly like AddCommand, then delegates to AddNamedCommandWithContext so the subcommand can resolve a per-request database/config instead of a process global.
func AddNamedCommand ¶
func AddNamedCommandWithContext ¶
func AddNamedCommandWithContext[T any, R any](name string, parent *cobra.Command, opts T, fn func(ctx context.Context, opts T) (R, error)) *cobra.Command
AddNamedCommandWithContext is AddNamedCommand whose closure receives the request-scoped context (cmd.Context() on the CLI, r.Context() over HTTP), so the subcommand can resolve a per-request database/config instead of a process global. Same flag/arg binding and rendering as AddNamedCommand.
func AnnotateTool ¶ added in v1.21.38
func AnnotateTool(cmd *cobra.Command, hints MCPToolHints)
AnnotateTool attaches MCP-facing tool hints to cmd. It is the public helper for commands that are not generated from an EntityBuilder.
func ContentDisposition ¶ added in v1.21.55
ContentDisposition renders an attachment header for a filename.
RFC 6266's plain filename= is a quoted-string, which is ISO-8859-1: a UTF-8 name written there travels as bytes the client is not obliged to decode, and Go's own quoting is not the escaping that header defines either. filename* names its charset, so it is the form that carries a non-ASCII name intact. Both are emitted — a client that understands filename* is required to prefer it, and one that does not still gets a usable, if flattened, name.
func DeclaresTruncatedTrailer ¶ added in v1.21.55
func DeclaresTruncatedTrailer(req PageRequest, res PageResponse) bool
DeclaresTruncatedTrailer reports whether this response has to declare Trailer: X-Truncated, and so whether it is a response that ends with trailers at all.
Only a ceiling that might still bite needs one. A buffered export has none to hit, and one already known to have been cut has its answer in the headers — declaring a trailer for either costs the response its Content-Length, forces chunked encoding, and promises an answer that never comes.
A trailer is a poor last resort: browsers do not expose them, so this reaches CLI and library callers only. It is kept for the one case that is genuinely unknowable up front — a stream whose backend states no total — rather than as the primary channel.
It is exported because the transport has to know the same thing to send the trailer it declares, and to know whether anything else may ride along in the trailer section it has already paid for. Two copies of this condition is the worst outcome available: a declared trailer that never arrives leaves a client waiting, and an undeclared one is dropped on the floor.
func ExportContentType ¶ added in v1.21.55
func ExportExtension ¶ added in v1.21.55
func ExportHeaderNames ¶ added in v1.21.55
func ExportHeaderNames() []string
ExportHeaderNames are the header names, for Access-Control-Expose-Headers.
func ExportResponseHeaders ¶ added in v1.21.55
func ExportResponseHeaders() map[string]ExportHeaderDoc
ExportResponseHeaders documents the same headers for the OpenAPI response.
func GenerateCLI ¶
GenerateCLI creates cobra subcommands for all registered entities under parent. Admin entities are nested under a shared "admin" parent command. Entities with a Parent set are nested under a shared parent cobra command, created lazily if it does not already exist.
func GetDataFunc ¶
GetDataFunc returns the direct data function registered for a command, if any. Used by the RPC converter to wire DataFunc on RPCOperation.
func GetLookupFunc ¶
GetLookupFunc returns the direct lookup function registered for a command, if any.
func IsLocalOnly ¶ added in v1.21.55
IsLocalOnly reports whether a command, or any command it is nested under, is marked local-only.
func IsTabularExport ¶ added in v1.21.55
IsTabularExport reports whether a format renders rows as a table, and so has to be written through formatters.WriteTableStream rather than serialized.
func JoinPath ¶ added in v1.21.55
JoinPath renders segments as a ClickySurface.Path. Empty segments are dropped so the output always round-trips through SplitPath(_, PathSeparator).
func MarkLocalOnly ¶ added in v1.21.55
MarkLocalOnly keeps a command off the generated HTTP surface.
Publishing a CLI as an API publishes every runnable command in the tree, which is wrong for the ones that administer the process rather than serve a resource: `serve` would start a nested server, and `migrate` would let an unauthenticated request alter the schema. Such commands stay in `--help` and on the CLI — they are simply not routes.
It marks the whole subtree: marking a parent covers every subcommand under it.
func RegisterDynamicEntity ¶
func RegisterDynamicEntity(spec DynamicEntitySpec)
RegisterDynamicEntity registers a schema-driven entity assembled at runtime (no compile-time Go struct). It is the single root entrypoint the entity package's dynamic builder targets; everything downstream — GenerateCLI, RPC, OpenAPI, the lookup endpoint — consumes it through the normal entity registry.
func RegisterDynamicEntityFamily ¶ added in v1.21.55
func RegisterDynamicEntityFamily(family DynamicEntityFamily)
RegisterDynamicEntityFamily registers a family, replacing any family already registered under the same Name.
Replacing rather than appending is deliberate: a family is a route, not an instance, and two families claiming one path segment is a wiring bug that would otherwise surface as whichever registration happened to be found first.
Register every family before the server builds its mux (rpc.RegisterRoutes). The mux pattern for a family is written once, from the families registered at that moment: a family registered afterwards is described by the OpenAPI document — which is resolved per request — but has no pattern, so the mux answers 404 before the family dispatch is ever reached. Unregistering has the mirror shape: the pattern stays, and the path falls through to ordinary operation lookup and 404s there.
Name must not collide with the first path segment of a registered operation. The dispatch resolves a registered operation first, so a colliding family is simply never reached for the paths the operation owns.
func RegisterEntity ¶
func RegisterEntity[T EntityItem, ListOpts any, R any](e Entity[T, ListOpts, R])
RegisterEntity registers a CRUD entity. Call during init(). CLI commands and HTTP routes are generated later via GenerateCLI/GenerateRoutes.
See the github.com/flanksource/clicky/entity package for the authoring guide and the best practices for operations that work well across CLI and HTTP.
func RegisterFilter ¶
func RegisterFilter(f NamedFilter)
RegisterFilter registers a named filter for reuse across entities. It panics on a duplicate name or an incomplete definition — registration happens at init/setup time, so a collision is a programming error that must fail loudly.
func RegisterFilterSpec ¶
func RegisterFilterSpec(s FilterSpec)
RegisterFilterSpec registers a declaratively-defined named filter. Like RegisterFilter it is a setup-time call and panics on an invalid spec.
func RegisterSubCommand ¶
RegisterSubCommand defers attaching cmd as a child of a parent cobra command identified by parentName. The attachment is performed by GenerateCLI after all entity parents have been created. If no command at parentName exists after entity generation, a thin parent command is created to host cmd.
parentName may be a bare command name ("policy") or a slash-delimited path ("billing/policy"). A path is resolved one segment at a time beneath root; missing intermediates are created as thin grouping commands, and existing nodes (including entity-generated parents) are reused.
Use this for non-entity cobra commands that should live under an entity-generated parent (or under an arbitrary grouping command).
func RegisterSubCommandFn ¶
RegisterSubCommandFn defers running build against a parent cobra command identified by parentName. Use this when the subcommand needs to be constructed lazily — e.g. AddNamedCommand which both builds and attaches in one call.
parentName accepts the same bare-name-or-slash-path form as RegisterSubCommand.
Example:
clicky.RegisterSubCommandFn("correspondence", func(parent *cobra.Command) {
clicky.AddNamedCommand("validate", parent, ValidateOptions{}, runValidate)
})
func RequestedFormat ¶ added in v1.21.55
RequestedFormat resolves ?format, falling back to content negotiation.
func ResolveDynamicLookup ¶ added in v1.21.55
func ResolveDynamicLookup(ctx context.Context, spec DynamicEntitySpec, flags map[string]string) (any, error)
ResolveDynamicLookup answers a filter-metadata lookup from a resolved spec.
A registered entity reaches this resolution through the LookupFunc the registry built for it. A family instance never enters the registry, so the transport has to enter the same resolution from the spec it just resolved — otherwise a runtime entity would be the one kind that cannot describe its own filters.
func SanitizeExportFilename ¶ added in v1.21.55
SanitizeExportFilename reduces a caller-supplied name to something that can only ever be a filename: no path to escape with, and nothing that could close the header parameter it is about to be written into.
func SetCORSHeaders ¶ added in v1.21.55
func SetCORSHeaders(w http.ResponseWriter)
SetCORSHeaders permits a cross-origin caller to read this response, whatever it turns out to be.
It is deliberately independent of the response: none of it depends on the rows, so it is set before the first thing that can fail. Setting it only on success is what makes an error body unreadable in a browser — and an error nobody can read is worse than the one it describes.
func SetCommandResponseMeta ¶
func SetCommandResponseMeta(cmd *cobra.Command, meta ResponseOpenAPIMeta)
SetCommandResponseMeta attaches static response metadata to a command.
func SetExportAllowedOrigin ¶ added in v1.21.55
func SetExportAllowedOrigin(origin string)
SetExportAllowedOrigin narrows Access-Control-Allow-Origin on every export response to a single origin. A consumer that serves its frontend from a known origin should set it at startup; leaving it unset (or passing "") keeps the permissive "*" a library with no knowledge of its host has to default to.
func SetExportHeaders ¶ added in v1.21.55
func SetExportHeaders(w http.ResponseWriter, name string, req PageRequest, res PageResponse)
SetExportHeaders writes every paging fact this response carries. It must be called before the first byte of the body.
func SplitPath ¶ added in v1.21.55
SplitPath breaks name into hierarchy segments on any rune in delimiters, dropping empty segments so repeated or leading separators are harmless. Runes outside delimiters are never separators — SplitPath("a-b.c", ".") yields ["a-b", "c"], not three segments.
PathSeparator is always a separator, whatever delimiters says (including when it is empty): a segment containing it would be re-split by any consumer of the JoinPath output, so SplitPath("jms/api.incoming", ".") yields ["jms", "api", "incoming"] and the round-trip through JoinPath is stable.
A name with no delimiter yields a single segment, i.e. a root-level leaf.
func SupportedExportFormat ¶ added in v1.21.55
func UnregisterDynamicEntityFamily ¶ added in v1.21.55
UnregisterDynamicEntityFamily removes a family by name, reporting whether one was registered. A family whose backing store is gone — a tenant disconnected, a plugin unloaded — has to be able to take its route with it.
func Use ¶
Use attaches a registered named filter to a typed (static) entity. The returned value implements clicky.Filter[ListOpts] (plus the searchable/context/typed extensions) so it drops straight into Entity.Filters. By default it binds to the flag/field named after the filter; override with As.
Filters(entity.Use[TaskOpts]("users").As("owner"))
Use resolves the filter eagerly and panics if the name is unregistered, so a wiring mistake fails at setup rather than at request time.
func WriteError ¶ added in v1.21.55
func WriteError(w http.ResponseWriter, err error)
WriteError writes any error in the same shape, so a caller never has to choose between two error formats depending on where the failure came from.
An error that did not state a status is a bug rather than a refusal, so it becomes a 500 with an opaque code and a fixed message. Its own text is not forwarded: an unclassified error is whatever the failing dependency said, and that routinely names hosts, DSNs, file paths, or credentials. The detail stays server-side in the log; a handler that wants the client to see a reason must classify the failure with a StatusError.
Types ¶
type ActionFlags ¶
type ActionFlags interface {
ClickyActionFlags()
}
ActionFlags is a marker interface implemented by options structs that declare typed cobra flags for a clicky Action. When an Action's Flags field is non-nil, the concrete type of the value is reflected for its `flag:"..."` struct tags and those flags are bound to the generated action cobra command.
Implementers attach a no-op `ClickyActionFlags()` method to an options struct, then pass a zero value of that struct in `Action.Flags`.
type ActionInfo ¶
type ActionInfo struct {
Name string
Short string
Method string
DataFunc func(flags map[string]string, args []string) (any, error)
// ContextDataFunc, when set, is preferred over DataFunc by both the CLI run
// path (fed cmd.Context()) and the RPC executor (fed r.Context()), mirroring
// the entity CRUD seam. Lets an action resolve request-scoped state instead
// of process globals.
ContextDataFunc ContextDataFunc
// FlagsType, if non-nil, is the struct type whose `flag:"..."` tagged
// fields are registered as cobra flags on the generated action command
// and populated into the flag map passed to DataFunc.
FlagsType reflect.Type
ResponseType reflect.Type
// OptionalID, when true, makes the positional <id> argument optional on
// the generated action command — the action is invokable with no
// argument (and the `id` passed to the run func is empty). Use for
// actions whose target is supplied entirely through flags.
OptionalID bool
// ToolGroup overrides the entity's tool group for this action only. Empty
// means the action inherits the entity's group.
ToolGroup string
ToolHints MCPToolHints
}
ActionInfo is the type-erased representation of a single-entity action.
type ActionSpec ¶
type ActionSpec[R any] struct { // contains filtered or unexported fields }
func Action ¶
func Action[R any](name string, fn func(id string, flags map[string]string) (R, error)) *ActionSpec[R]
Action creates a typed custom operation on a single entity by ID.
func ActionWithContext ¶
func ActionWithContext[R any](name string, fn func(ctx context.Context, id string, flags map[string]string) (R, error)) *ActionSpec[R]
ActionWithContext creates a typed action whose run closure receives the request-scoped context (cmd.Context() on the CLI, r.Context() over HTTP), so it can resolve a per-request database/config instead of a process global.
func ActionWithFlags ¶
func ActionWithFlags[R any](name string, flags ActionFlags, fn func(id string, flags map[string]string) (R, error)) *ActionSpec[R]
ActionWithFlags creates a typed custom operation with typed action flags.
func ActionWithFlagsAndContext ¶
func ActionWithFlagsAndContext[R any](name string, flags ActionFlags, fn func(ctx context.Context, id string, flags map[string]string) (R, error)) *ActionSpec[R]
ActionWithFlagsAndContext is ActionWithContext with typed action flags.
func PrimaryActionWithContext ¶ added in v1.21.55
func PrimaryActionWithContext[Opts ActionFlags, R any](opts Opts, fn func(context.Context, Opts) (R, error)) *ActionSpec[R]
PrimaryActionWithContext creates the typed collection action that runs when the bare entity command is invoked. Its POST route shares the entity's collection path with the list operation's GET route.
func (*ActionSpec[R]) WithFlags ¶
func (a *ActionSpec[R]) WithFlags(flags ActionFlags) *ActionSpec[R]
func (*ActionSpec[R]) WithMethod ¶
func (a *ActionSpec[R]) WithMethod(method string) *ActionSpec[R]
WithMethod overrides the inferred HTTP method for the generated RPC/OpenAPI action route. Leave empty to keep the default inference behavior.
func (*ActionSpec[R]) WithOptionalID ¶
func (a *ActionSpec[R]) WithOptionalID() *ActionSpec[R]
WithOptionalID makes the positional <id> argument optional on the generated action command. Use for actions whose target is supplied entirely through flags; the `id` passed to the run func is then empty.
func (*ActionSpec[R]) WithShort ¶
func (a *ActionSpec[R]) WithShort(short string) *ActionSpec[R]
func (*ActionSpec[R]) WithToolGroup ¶
func (a *ActionSpec[R]) WithToolGroup(group string) *ActionSpec[R]
WithToolGroup overrides the entity's tool group for this action only. Empty (the default) means the action inherits the entity's group.
func (*ActionSpec[R]) WithToolHints ¶ added in v1.21.38
func (a *ActionSpec[R]) WithToolHints(hints MCPToolHints) *ActionSpec[R]
WithToolHints overrides this action's inherited MCP tool hints.
func (*ActionSpec[R]) WithToolPermission ¶ added in v1.21.38
func (a *ActionSpec[R]) WithToolPermission(permission ToolPermission) *ActionSpec[R]
WithToolPermission sets this action's default approval mode. Actions carry no verb default: unset defers to the entity's ToolPermission or, failing that, the app's approval policy.
type BulkActionInfo ¶
type BulkActionInfo struct {
Name string
Short string
DataFunc func(flags map[string]string, args []string) (any, error)
FilterFunc func(flags map[string]string, args []string) (any, error)
LookupFunc func(flags map[string]string, args []string) (any, error)
ContextLookupFunc ContextLookupFunc
ListType reflect.Type
BindCompletions func(cmd *cobra.Command)
ResponseType reflect.Type
}
BulkActionInfo is the type-erased representation of a bulk action.
type BulkActionSpec ¶
type BulkActionSpec[R any] struct { // contains filtered or unexported fields }
func BulkAction ¶
func BulkAction[R any](name string, fn func(ids []string, flags map[string]string) (R, error)) *BulkActionSpec[R]
BulkAction creates a typed custom operation on multiple entity IDs.
func BulkActionWithFilter ¶
func BulkActionWithFilter[ListOpts any, R any]( name string, run func(ids []string, flags map[string]string) (R, error), runFilter func(opts ListOpts, flags map[string]string) (R, error), ) *BulkActionSpec[R]
BulkActionWithFilter creates a typed bulk action that supports both explicit IDs and filtered selections.
func BulkFilterAction ¶
func BulkFilterAction[ListOpts any, R any](name string, fn func(opts ListOpts, flags map[string]string) (R, error)) *BulkActionSpec[R]
BulkFilterAction creates a typed custom operation that runs against a typed filtered list selection instead of explicit IDs.
func (*BulkActionSpec[R]) WithShort ¶
func (b *BulkActionSpec[R]) WithShort(short string) *BulkActionSpec[R]
type ClickyOperationMeta ¶
type ClickyOperationMeta struct {
SurfaceID string `json:"-"`
Command string `json:"command,omitempty"`
Surface string `json:"surface,omitempty"`
Entity string `json:"-"`
Parent string `json:"-"`
Aliases []string `json:"-"`
Admin bool `json:"-"`
Icon string `json:"-"`
Path string `json:"-"`
Title string `json:"-"`
Verb string `json:"verb,omitempty"`
Scope string `json:"scope,omitempty"`
ActionName string `json:"actionName,omitempty"`
IDParam string `json:"idParam,omitempty"`
SupportsLookup bool `json:"supportsLookup,omitempty"`
SupportsFilterMode bool `json:"supportsFilterMode,omitempty"`
Group string `json:"group,omitempty"`
ToolHints MCPToolHints `json:"-"`
OpenAPIToolHints *MCPToolHints `json:"toolHints,omitempty"`
Export *ExportMeta `json:"export,omitempty"`
Order int `json:"-"`
}
ClickyOperationMeta is emitted as the OpenAPI operation-level x-clicky extension. SurfaceID/Aliases are internal-only fields used while building the final surface list.
type ClickySpecMeta ¶
type ClickySpecMeta struct {
Surfaces []ClickySurface `json:"surfaces,omitempty"`
}
ClickySpecMeta is emitted as the OpenAPI-level x-clicky extension.
type ClickySurface ¶
type ClickySurface struct {
Key string `json:"key"`
Entity string `json:"entity"`
Title string `json:"title"`
Parent string `json:"parent,omitempty"`
Admin bool `json:"admin,omitempty"`
Description string `json:"description,omitempty"`
// Icon is an opaque UI icon name (e.g. "database") the frontend resolves to
// a glyph. Emitted verbatim from the entity's x-clicky-icon annotation.
Icon string `json:"icon,omitempty"`
// Path is this surface's position within its Parent group, always separated
// by PathSeparator (e.g. "jms/incoming/disbursements"). Producers split
// their own naming convention with SplitPath before emitting it, so the
// frontend never guesses a delimiter. Empty — the default for every surface
// that does not opt in — means the group renders as a flat list.
Path string `json:"path,omitempty"`
Order int `json:"-"`
}
ClickySurface describes a UI surface resolved from clicky entity metadata.
type CommandOpenAPIMeta ¶
type CommandOpenAPIMeta struct {
Entity string
Parent string
Aliases []string
Admin bool
// Icon is an opaque UI icon name (e.g. "database"); clicky never interprets
// it — it is emitted verbatim on the surface for the frontend to resolve.
Icon string
// Path is the entity's hierarchy position within Parent, PathSeparator-joined.
Path string
// Title overrides the auto-generated surface title when non-empty.
Title string
Verb string
Method string
Scope string
ActionName string
IDParam string
SupportsLookup bool
SupportsFilterMode bool
// OptionalID is true when the operation's positional id is optional
// (WithOptionalID). Such operations are invocable without an id, so the
// generated REST path must NOT carry an {id} segment — otherwise the no-id
// call collides with the entity's get-by-id route.
OptionalID bool
// ToolGroup names the group an operation belongs to. It is set on the entity
// and inherited by every operation (a per-action override may replace it).
// AI tool-preference layers use it to enable/disable a set of related tools
// as one unit.
ToolGroup string
ToolHints MCPToolHints
}
CommandOpenAPIMeta is the clicky-specific metadata attached to generated Cobra commands so the RPC/OpenAPI layer can expose entity semantics without guessing from paths or operationIds.
func GetCommandOpenAPIMeta ¶
func GetCommandOpenAPIMeta(cmd *cobra.Command) *CommandOpenAPIMeta
type Config ¶
type Config struct {
// DefaultMethod is the default HTTP method for operations.
DefaultMethod string
// PathPrefix is the path prefix for REST APIs.
PathPrefix string
// AutoGeneratePaths controls whether REST paths are generated from the
// command hierarchy.
AutoGeneratePaths bool
// DefaultTags are applied to all operations.
DefaultTags []string
}
Config holds configuration for RPC conversion.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns a sensible default configuration.
type ContextDataFunc ¶
type ContextDataFunc = func(ctx context.Context, flags map[string]string, args []string) (any, error)
ContextDataFunc is the context-aware data closure stored for a command. Its signature matches rpc.ContextDataFunc structurally; the RPC converter converts it when wiring RPCOperation.ContextDataFunc. Defined here (the root package) to avoid a clicky -> clicky/rpc import cycle.
func GetContextDataFunc ¶
func GetContextDataFunc(cmd *cobra.Command) ContextDataFunc
GetContextDataFunc returns the context-aware data function registered for a command, if any. Used by the RPC converter to wire ContextDataFunc.
type ContextFilter ¶
type ContextFilter[ListOpts any] interface { OptionsWithContext(ctx context.Context, opts ListOpts) map[string]api.Textable }
ContextFilter is an OPTIONAL extension a Filter may implement when its option set is resolved from request-scoped state (e.g. a per-request database handle) rather than a process global. When the lookup handler runs with a context (the RPC path feeds r.Context(), the CLI feeds cmd.Context()) it prefers OptionsWithContext over Options; filters that don't implement it fall back to the plain Options() path unchanged.
type ContextLookupFunc ¶
type ContextLookupFunc = func(ctx context.Context, flags map[string]string, args []string) (any, error)
ContextLookupFunc is the context-aware variant of a filter lookup closure. When present, the RPC executor prefers it over LookupFunc and feeds the request's context.Context (r.Context() over HTTP, cmd.Context() on the CLI) so a filter's Options/OptionsWithQuery can resolve request-scoped state (e.g. the per-request database handle) instead of a process global.
func GetContextLookupFunc ¶
func GetContextLookupFunc(cmd *cobra.Command) ContextLookupFunc
GetContextLookupFunc returns the context-aware lookup function registered for a command, if any. Used by the RPC converter to wire ContextLookupFunc.
type ContextSearchableFilter ¶
type ContextSearchableFilter[ListOpts any] interface { OptionsWithQueryAndContext(ctx context.Context, opts ListOpts, query string, limit int) (options map[string]api.Textable, total int) }
ContextSearchableFilter is the context-aware counterpart of SearchableFilter: a large/searchable filter whose head and server-side search both need request-scoped state. The lookup handler prefers it over OptionsWithQuery when a context is available.
type DataFunc ¶
DataFunc is a function that returns structured data directly, bypassing stdout capture. Used by commands registered via AddCommand to provide data to the HTTP handler. (ContextDataFunc / ContextLookupFunc — the context-aware variants — are defined alongside the command registry in command.go.)
type DynamicEntityBuilder ¶
type DynamicEntityBuilder struct {
// contains filtered or unexported fields
}
DynamicEntityBuilder assembles a schema-driven entity and registers it through RegisterDynamicEntity. Filters referenced by x-clicky-filter must be registered (RegisterFilter / RegisterFilterSpec) before Register is called.
func NewDynamicEntity ¶
func NewDynamicEntity(name string, schema []byte) *DynamicEntityBuilder
NewDynamicEntity starts building a dynamic entity from a JSON schema.
func (*DynamicEntityBuilder) Filter ¶ added in v1.21.54
func (b *DynamicEntityBuilder) Filter(key, filterName string) *DynamicEntityBuilder
Filter offers the registered filter filterName under key, for an input the rows do not carry — a query parameter, say — and so has no schema property to hang x-clicky-filter on. Schema-declared filters need no such call.
The key is what the request sends and what the lookup response is keyed by. A value supplied for it reaches the filter's source as sibling context even though it is not a declared operation parameter.
func (*DynamicEntityBuilder) Get ¶
func (b *DynamicEntityBuilder) Get(fn DynamicGetFunc) *DynamicEntityBuilder
func (*DynamicEntityBuilder) List ¶
func (b *DynamicEntityBuilder) List(fn DynamicListFunc) *DynamicEntityBuilder
func (*DynamicEntityBuilder) Register ¶
func (b *DynamicEntityBuilder) Register()
Register parses the schema, generates the runtime types, wires the named filters, and registers the entity. It panics on an invalid schema or an unregistered filter reference — both are setup-time wiring errors.
type DynamicEntityFamily ¶ added in v1.21.55
type DynamicEntityFamily struct {
// Name is the family's path segment: "profile" serves one route,
// {prefix}/profile/{name}, for every profile that will ever exist.
Name string
// Parent groups the family's surfaces, as EntityInfo.Parent does.
Parent string
// Resolve returns the spec for one instance. It runs in front of every
// request to the family, so it has to be cheap enough to sit there. A name
// that does not exist is UnknownDynamicEntity rather than a zero spec: "no
// such profile" and "a profile that does nothing" are different answers and a
// caller has to be able to tell them apart.
Resolve func(ctx context.Context, name string) (DynamicEntitySpec, error)
// List enumerates the instances that exist right now, so the OpenAPI document
// can describe them. It is called per request for the same reason Resolve is:
// a document rendered once at startup would describe a set that has since
// changed.
List func(ctx context.Context) ([]DynamicEntitySpec, error)
// Paged serves one page or one export of a resolved instance, putting a
// family instance on the same export contract as RPCOperation.PagedFunc.
//
// It hangs off the family rather than off DynamicEntitySpec because the spec
// says what the entity is and this says how the transport reads it, and
// because one implementation serves every instance. Nil falls back to the
// instance's List, served the way every other operation's data is.
Paged func(ctx context.Context, spec DynamicEntitySpec, req PageRequest, flags map[string]string) (PageResponse, error)
}
DynamicEntityFamily is a route for entities that do not exist yet.
The registry pipeline — entityRegistry, GenerateCLI, Cobra, NewSwaggerServer, RegisterRoutes — is one snapshot taken at startup, so an entity that comes into being while the server runs has no route and no way to acquire one. A consumer whose entities are database rows is locked out of it entirely.
A family registers one route for the whole shape — {prefix}/{Name}/{name} — and resolves the instance per request. Nothing is cached, so an entity created a moment ago is immediately reachable and none of the startup pipeline has to be re-run or invalidated.
func GetDynamicEntityFamilies ¶ added in v1.21.55
func GetDynamicEntityFamilies() []DynamicEntityFamily
GetDynamicEntityFamilies returns the registered families.
Nil when none are registered, rather than an empty slice: this is consulted on the way into every executor request, and a consumer that registers no family must not pay an allocation for the feature it does not use.
type DynamicEntitySpec ¶
type DynamicEntitySpec struct {
Name string
Parent string
Aliases []string
// Icon is an opaque UI icon name emitted on the surface (x-clicky.surfaces[].icon).
Icon string
// Path is the hierarchy position emitted on the surface (x-clicky.surfaces[].path).
Path string
// Title overrides the auto-generated surface title when non-empty.
Title string
ListType reflect.Type // generated struct type: flag binding + OpenAPI params
ItemType reflect.Type // generated item struct type for the response schema
List ContextDataFunc
Get ContextDataFunc
Filters []DynamicFilter
// FilterRefs maps a filter key to the reusable named filter backing it, so
// the OpenAPI layer can emit an x-clicky-lookup $ref (see EntityInfo.FilterRefs).
FilterRefs map[string]string
}
DynamicEntitySpec is the type-erased description of a schema-driven entity, assembled by the entity package's NewDynamicEntity builder. It reuses the existing pipeline: ListType drives CLI flag binding and OpenAPI parameters, ItemType drives the response schema, and Filters feed the shared lookup engine.
type DynamicFilter ¶
type DynamicFilter struct {
Key string
Label string
Type string
Multi bool
Searchable bool
// Limit caps the option set this filter enumerates in one shot. Zero takes
// MaxLookupOptions, which is also the ceiling.
Limit int
// Options returns the head set (query == "") or search matches, plus the true
// total behind the head. Required.
Options func(ctx context.Context, flags map[string]string, query string, limit int) (map[string]api.Textable, int, error)
// Selected labels the currently-selected value(s) of this filter's key.
// Optional; nil means "no selection rendering".
Selected func(ctx context.Context, flags map[string]string) (map[string]api.Textable, error)
}
DynamicFilter describes one filter on a dynamic (schema-driven) entity. The Options/Selected closures resolve from the request flag map — they are built by the entity package from a reusable named filter — so the type-agnostic filter logic stays out of this package while reusing the shared lookup builder.
type DynamicGetFunc ¶
DynamicGetFunc fetches a single dynamic-entity row by id.
type DynamicListFunc ¶
DynamicListFunc lists rows of a dynamic entity for the given flag map.
type Entity ¶
type Entity[T EntityItem, ListOpts any, R any] struct { Name string // Parent, when set, nests the entity's command under a parent cobra command // with that name. The parent command is created lazily by GenerateCLI if it // does not already exist. Parent string // Aliases applied to the generated entity cobra command. Aliases []string // Path is the entity's hierarchy position within Parent, PathSeparator-joined // (x-clicky.surfaces[].path). Build it with JoinPath/SplitPath so the // separator stays the wire convention. Empty declares no hierarchy. Path string List func(opts ListOpts) ([]T, error) Get func(id string) (R, error) // ListWithContext / GetWithContext / GetWithFlagsAndContext are context-aware // variants of List / Get / GetWithFlags. When set they take precedence over // their non-context counterparts and receive the request-scoped // context.Context (r.Context() over HTTP, cmd.Context() on the CLI). Use them // to resolve per-request state (e.g. a database/config bundle) instead of // reaching for process globals. ListWithContext func(ctx context.Context, opts ListOpts) ([]T, error) ListPaged func(opts ListOpts) (PagedResult[T], error) ListPagedWithContext func(ctx context.Context, opts ListOpts) (PagedResult[T], error) GetWithContext func(ctx context.Context, id string) (R, error) GetWithFlagsAndContext func(ctx context.Context, id string, flags map[string]string) (R, error) // GetFlags, when non-nil, is a zero-value struct value implementing // ActionFlags whose `flag:"..."` tagged fields are registered as // CLI flags on the generated `get` subcommand. The parsed values are // passed into GetWithFlags. // // GetWithFlags is mutually exclusive with Get: when both are set, // GetWithFlags wins. Set GetFlags to declare the flag schema and // GetWithFlags to receive the typed values. GetFlags ActionFlags GetWithFlags func(id string, flags map[string]string) (R, error) Create func(body map[string]any) (R, error) Update func(id string, body map[string]any) (R, error) Delete func(id string) error // CreateWithContext / UpdateWithContext / DeleteWithContext are context-aware // variants of Create / Update / Delete, mirroring the read-side seam above. // When set they take precedence and receive the request-scoped context, so // writes target the request's environment rather than a process global. CreateWithContext func(ctx context.Context, body map[string]any) (R, error) UpdateWithContext func(ctx context.Context, id string, body map[string]any) (R, error) DeleteWithContext func(ctx context.Context, id string) error Filters []Filter[ListOpts] PrimaryAction EntityAction Actions []EntityAction BulkActions []EntityBulkAction // Admin groups admin-only operations (inspect, configure, etc.) // that produce different views/columns from the main CRUD operations. // Generates subcommands under an "admin" subgroup and routes like /entity/admin/{verb}. Admin *Entity[T, ListOpts, R] // ValidArgs provides shell completion for the ID argument. ValidArgs func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) // ToolGroup names the group all of this entity's operations belong to. It is // inherited by every generated operation; an action may override it via // WithToolGroup. AI tool-preference layers use it to toggle related tools // together. ToolGroup string ToolHints MCPToolHints }
Entity configures a CRUD resource. All function fields are optional. T is the type returned by List and must implement EntityItem. R is the type returned by Get/Create/Update.
type EntityAction ¶
type EntityAction interface {
// contains filtered or unexported methods
}
EntityAction is the type-erased registration surface for custom entity actions. Use Action or ActionWithFlags to construct values.
type EntityBuilder ¶
type EntityBuilder[T EntityItem, ListOpts any, R any] struct { // contains filtered or unexported fields }
EntityBuilder provides a fluent API for registering typed entities.
func NewEntity ¶
func NewEntity[T EntityItem, ListOpts any, R any](name string) *EntityBuilder[T, ListOpts, R]
NewEntity starts a typed entity registration. The resulting entity is served identically on the CLI and over HTTP/RPC. See the package github.com/flanksource/clicky/entity for an authoring guide, the best practices for operations that behave well on both surfaces, and runnable examples.
func (*EntityBuilder[T, ListOpts, R]) Admin ¶
func (b *EntityBuilder[T, ListOpts, R]) Admin(admin Entity[T, ListOpts, R]) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Aliases ¶
func (b *EntityBuilder[T, ListOpts, R]) Aliases(aliases ...string) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Build ¶
func (b *EntityBuilder[T, ListOpts, R]) Build() Entity[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Create ¶
func (b *EntityBuilder[T, ListOpts, R]) Create(fn func(map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) CreateWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) CreateWithContext(fn func(context.Context, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
CreateWithContext sets the context-aware create handler. The context carries request-scoped state (e.g. the originating *http.Request via rpc.RequestFromContext), letting handlers read the raw nested JSON body the executor would otherwise flatten to string flags.
func (*EntityBuilder[T, ListOpts, R]) Delete ¶
func (b *EntityBuilder[T, ListOpts, R]) Delete(fn func(string) error) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) DeleteWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) DeleteWithContext(fn func(context.Context, string) error) *EntityBuilder[T, ListOpts, R]
DeleteWithContext sets the context-aware delete handler. See CreateWithContext.
func (*EntityBuilder[T, ListOpts, R]) Filters ¶
func (b *EntityBuilder[T, ListOpts, R]) Filters(filters ...Filter[ListOpts]) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Get ¶
func (b *EntityBuilder[T, ListOpts, R]) Get(fn func(string) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) GetWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) GetWithContext(fn func(context.Context, string) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) GetWithFlags ¶
func (b *EntityBuilder[T, ListOpts, R]) GetWithFlags(flags ActionFlags, fn func(string, map[string]string) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) GetWithFlagsAndContext ¶ added in v1.21.52
func (b *EntityBuilder[T, ListOpts, R]) GetWithFlagsAndContext(flags ActionFlags, fn func(context.Context, string, map[string]string) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) List ¶
func (b *EntityBuilder[T, ListOpts, R]) List(fn func(ListOpts) ([]T, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) ListPaged ¶
func (b *EntityBuilder[T, ListOpts, R]) ListPaged(fn func(ListOpts) (PagedResult[T], error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) ListPagedWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) ListPagedWithContext(fn func(context.Context, ListOpts) (PagedResult[T], error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) ListWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) ListWithContext(fn func(context.Context, ListOpts) ([]T, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Parent ¶
func (b *EntityBuilder[T, ListOpts, R]) Parent(parent string) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) Path ¶ added in v1.21.55
func (b *EntityBuilder[T, ListOpts, R]) Path(segments ...string) *EntityBuilder[T, ListOpts, R]
Path sets the entity's hierarchy position within its parent, joined by PathSeparator, and surfaced as x-clicky.surfaces[].path. Pass segments through JoinPath rather than concatenating them by hand.
func (*EntityBuilder[T, ListOpts, R]) Register ¶
func (b *EntityBuilder[T, ListOpts, R]) Register()
func (*EntityBuilder[T, ListOpts, R]) ToolGroup ¶
func (b *EntityBuilder[T, ListOpts, R]) ToolGroup(group string) *EntityBuilder[T, ListOpts, R]
ToolGroup names the group this entity's operations belong to. Every generated operation inherits it (an action can override it via WithToolGroup). AI tool-preference layers use the group to enable/disable related tools together.
func (*EntityBuilder[T, ListOpts, R]) ToolHints ¶ added in v1.21.38
func (b *EntityBuilder[T, ListOpts, R]) ToolHints(hints MCPToolHints) *EntityBuilder[T, ListOpts, R]
ToolHints sets MCP-facing annotations and Clicky UI metadata inherited by all generated operations. Per-action hints can override these values.
func (*EntityBuilder[T, ListOpts, R]) ToolPermission ¶ added in v1.21.38
func (b *EntityBuilder[T, ListOpts, R]) ToolPermission(permission ToolPermission) *EntityBuilder[T, ListOpts, R]
ToolPermission sets the default approval mode inherited by every generated operation, replacing the standard verb defaults (list/get auto-run, create/update/delete ask). A per-action WithToolPermission overrides it.
func (*EntityBuilder[T, ListOpts, R]) Update ¶
func (b *EntityBuilder[T, ListOpts, R]) Update(fn func(string, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) UpdateWithContext ¶
func (b *EntityBuilder[T, ListOpts, R]) UpdateWithContext(fn func(context.Context, string, map[string]any) (R, error)) *EntityBuilder[T, ListOpts, R]
UpdateWithContext sets the context-aware update handler. See CreateWithContext.
func (*EntityBuilder[T, ListOpts, R]) ValidArgs ¶
func (b *EntityBuilder[T, ListOpts, R]) ValidArgs(fn func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) WithAction ¶
func (b *EntityBuilder[T, ListOpts, R]) WithAction(action EntityAction) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) WithBulkAction ¶
func (b *EntityBuilder[T, ListOpts, R]) WithBulkAction(action EntityBulkAction) *EntityBuilder[T, ListOpts, R]
func (*EntityBuilder[T, ListOpts, R]) WithPrimaryAction ¶ added in v1.21.55
func (b *EntityBuilder[T, ListOpts, R]) WithPrimaryAction(action EntityAction) *EntityBuilder[T, ListOpts, R]
type EntityBulkAction ¶
type EntityBulkAction interface {
// contains filtered or unexported methods
}
EntityBulkAction is the type-erased registration surface for custom bulk actions. Use BulkAction or BulkFilterAction to construct values.
type EntityInfo ¶
type EntityInfo struct {
Name string
Parent string
Aliases []string
// Icon is an opaque UI icon name carried through to the OpenAPI surface
// (x-clicky.surfaces[].icon). Empty for entities that declare no icon.
Icon string
// Path is the entity's hierarchy position within Parent, PathSeparator-joined
// (x-clicky.surfaces[].path). Empty for entities that declare no hierarchy.
Path string
// Title overrides the auto-generated surface title when non-empty.
Title string
Type reflect.Type
ListType reflect.Type
Operations []EntityOperation
PrimaryAction *ActionInfo
Actions []ActionInfo
BulkActions []BulkActionInfo
ValidArgs func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective)
IsAdmin bool
// FilterRefs maps a list filter's flag key to the name of the reusable named
// filter backing it (when the filter was attached via the entity package's
// Use/As helper). The OpenAPI layer uses it to emit an x-clicky-lookup $ref
// to the shared filter definition. Nil when no filters reference a named one.
FilterRefs map[string]string
// ToolGroup names the group all of this entity's operations belong to. It is
// inherited by every generated operation (an action may override it) and is
// used by AI tool-preference layers to toggle related tools together.
ToolGroup string
ToolHints MCPToolHints
}
EntityInfo is the type-erased representation stored in the registry.
func GetEntity ¶
func GetEntity(name string) (EntityInfo, bool)
GetEntity returns the registered entity with the given name (or alias) and whether it was found.
type EntityItem ¶
EntityItem is the interface that all entity types must implement.
type EntityOperation ¶
type EntityOperation struct {
Verb string // "list", "get", "create", "update", "delete"
Method string // Optional explicit HTTP method for generated RPC/OpenAPI routes.
DataFunc func(flags map[string]string, args []string) (any, error)
// ContextDataFunc, when set, is preferred over DataFunc by both the CLI run
// path (fed cmd.Context()) and the RPC executor (fed r.Context()). Lets the
// operation resolve request-scoped state instead of process globals.
ContextDataFunc ContextDataFunc
// FlagsType, when non-nil, binds typed flags from the given struct type
// onto the generated cobra command and collects their values into the
// flag map passed to DataFunc. Used by actions that implement
// ActionFlags; ignored for the built-in CRUD verbs.
FlagsType reflect.Type
LookupFunc func(flags map[string]string, args []string) (any, error)
// ContextLookupFunc, when set, is the context-aware filter lookup closure.
// The RPC converter wires it onto RPCOperation.ContextLookupFunc and the
// executor prefers it over LookupFunc, feeding the request context.
ContextLookupFunc ContextLookupFunc
BindCompletions func(cmd *cobra.Command)
ResponseType reflect.Type
ResponseArray bool
ResponsePaged bool
ResponseEntityID bool
}
EntityOperation represents a single CRUD operation.
type ExecutableCommand ¶
type ExecutableCommand interface {
// Execute runs the command with the given options, capturing everything
// written to os.Stdout / os.Stderr (including direct writes, not just the
// cobra writers). It is safe for concurrent callers: implementations that
// swap process-global file descriptors serialize internally.
Execute(ctx context.Context, opts ExecuteOptions) (stdout, stderr string, err error)
// Path is the full space-joined command path ("user create").
Path() string
// Name is the leaf command name ("create").
Name() string
// RootName is the top-level application command name ("xero-cli"), used for
// the MCP tool title.
RootName() string
// Runnable reports whether the command has a Run/RunE (not a pure group).
Runnable() bool
// Hidden reports whether the command is hidden from help/listing.
Hidden() bool
// IsBoolFlag reports whether the named flag is a boolean flag. Used by the
// CLI-string reconstruction to render "--flag" vs "--flag=value".
IsBoolFlag(name string) bool
}
ExecutableCommand is the transport-neutral handle an RPCOperation holds onto in place of a concrete *cobra.Command. It encapsulates everything the execution path (RPC executor, MCP server) and the tool-surfacing layers (aichat) need, so those packages no longer reference cobra directly.
The cobra-specific machinery — the global os.Stdout/os.Stderr pipe swap, flag copying, state reset — lives entirely inside the CobraExecutableCommand implementation (rpc package).
type ExecuteOptions ¶
type ExecuteOptions struct {
// Args are the positional arguments.
Args []string
// Flags maps flag name -> string value. A flag absent on the command is
// silently skipped (path params and other non-flag keys may appear here);
// a present flag whose value fails to parse returns an error.
Flags map[string]string
// FormatOverrides are best-effort flag values applied before execution
// (e.g. {"format":"markdown","no-color":"true"}). A flag absent on the
// command is silently skipped. Used by the MCP server's format forcing.
FormatOverrides map[string]string
}
ExecuteOptions controls how an ExecutableCommand runs one invocation. It is the transport-neutral subset of what the RPC executor and MCP server previously poked directly onto a *cobra.Command.
type ExportHeaderDoc ¶ added in v1.21.55
ExportHeaderDoc documents one response header for the OpenAPI spec. It is deliberately not the rpc package's own header type: rpc imports entity, so the description travels as plain data and rpc adapts it.
type ExportMeta ¶ added in v1.21.38
type ExportMeta struct {
Formats []string `json:"formats,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AllRowsMode string `json:"allRowsMode,omitempty"`
FormatMaxRows map[string]int `json:"formatMaxRows,omitempty"`
}
ExportMeta advertises the representations and scopes an operation can download. UI consumers preserve legacy download behavior when this metadata is absent.
type Filter ¶
type Filter[ListOpts any] interface { Key() string Label() string Lookup(opts *ListOpts) (map[string]api.Textable, error) Options(opts ListOpts) map[string]api.Textable }
Filter resolves raw entity option values into the backend-specific shape and exposes UI metadata for the currently selected and available values.
func LiftFilters ¶
func LiftFilters[Outer any, Inner any]( filters []Filter[Inner], project func(*Outer) *Inner, ) []Filter[Outer]
LiftFilters adapts a Filter[Inner] slice so the same picker definitions work against an outer struct that embeds (or otherwise reaches) the inner filter struct. The project func extracts a *Inner from any *Outer the filter system hands us; lookup keys, labels, options, and selected-value reads/writes flow through unchanged. Use this to keep picker constructors scoped to the inner filter type while letting subcommand options structs — which also carry positional args, behaviour flags, etc. — satisfy Filterable[Outer].
type FilterContext ¶
type FilterContext struct {
// Context is the request context (HTTP request or CLI command). It may be
// nil when a source is invoked outside a request (e.g. shell completion).
Context context.Context
// Key is the bound flag/field name this filter resolves on the current
// entity (e.g. "owner" when a "users" filter is attached via .As("owner")).
Key string
// Params holds the current values of every flag/filter on the entity, so a
// source can narrow its options against sibling selections (cascading).
Params map[string]string
}
FilterContext carries the request-scoped state a FilterSource needs to resolve its options. It is the single representation both static and dynamic entities can produce: a static entity reflects its typed ListOpts into Params, a dynamic entity already holds the flag map.
func (FilterContext) Ctx ¶
func (fc FilterContext) Ctx() context.Context
Ctx returns the context, defaulting to context.Background when none was set.
type FilterSource ¶
type FilterSource interface {
// Options returns the available options, optionally narrowed by query, and
// the true total count behind the (possibly capped) returned set. An empty
// query means "the head set". A non-positive limit means "no cap".
Options(fc FilterContext, query string, limit int) (options map[string]api.Textable, total int, err error)
// Resolve labels the currently-selected raw value(s) for display. A value
// with no known label is echoed back as plain text.
Resolve(fc FilterContext, values []string) (map[string]api.Textable, error)
}
FilterSource produces the options for a NamedFilter. Implementations are type-agnostic — they read FilterContext.Params rather than a typed struct — so one source serves any entity that references the filter.
func EntityOptions ¶
func EntityOptions(entityName string) FilterSource
EntityOptions is a FilterSource that resolves its options from another registered entity: value = GetID(), label = PrettyShort()/GetName(). Search is an in-memory substring match over the source entity's list.
func FuncOptions ¶
func FuncOptions(fn func(fc FilterContext, query string, limit int) (map[string]api.Textable, int, error)) FilterSource
FuncOptions is a FilterSource backed by a user-supplied function — the general escape hatch for DB-backed or computed options with server-side search. The function owns query matching and the limit cap.
func StaticOptions ¶
func StaticOptions(options map[string]api.Textable) FilterSource
StaticOptions is a FilterSource backed by a fixed set of options, narrowed by case-insensitive substring match on the option key or its rendered label.
type FilterSourceSpec ¶
type FilterSourceSpec struct {
Kind string `json:"kind" yaml:"kind"`
// Options is the value→label map for a static source.
Options map[string]string `json:"options,omitempty" yaml:"options,omitempty"`
// Entity is the source entity name for an entity source.
Entity string `json:"entity,omitempty" yaml:"entity,omitempty"`
}
FilterSourceSpec is the declarative description of where a filter's options come from. "static" and "entity" are constructible from a spec; "func" is Go-only and only ever appears on the emit side (NamedFilter.Spec).
type FilterSpec ¶
type FilterSpec struct {
Name string `json:"name" yaml:"name"`
Label string `json:"label,omitempty" yaml:"label,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
Multi bool `json:"multi,omitempty" yaml:"multi,omitempty"`
Source FilterSourceSpec `json:"source" yaml:"source"`
}
FilterSpec is the JSON/YAML-authorable form of a NamedFilter. It is also the shape emitted for OpenAPI (NamedFilter.Spec), so a Go-defined filter and a declaratively-defined one are indistinguishable to clients.
type Filterable ¶
Filterable is implemented by AddNamedCommand options structs that want to expose typed filter lookups (dropdowns/typeaheads on the web UI, shell completions on the CLI) on the subcommand itself rather than inheriting only the parent entity's Filters slice. The same Filter[T] values that back an Entity's Filters slice work here; AddNamedCommand stores a LookupFunc in the lookupFuncRegistry and binds completions whenever an opts struct satisfies this interface.
Use this whenever a subcommand's flag surface diverges from its parent list view — typically when a positional argument pins one or more identifier filters and the subcommand only exposes the orthogonal (status, type, date range) lenses.
type Help ¶
Help is implemented by options structs that supply their own long-form command help. The generated command sets cmd.Long from Help().ANSI().
type LimitedFilter ¶ added in v1.21.55
type LimitedFilter interface {
// LookupLimit returns the head/search cap, or zero to take the default.
LookupLimit() int
}
LimitedFilter is an OPTIONAL extension a Filter may implement to cap its own option set below the package ceiling. It is unparameterized because the cap is a property of the filter's cardinality, not of the entity it is attached to.
type MCPToolHints ¶ added in v1.21.38
type MCPToolHints struct {
Title string `json:"title,omitempty"`
ReadOnlyHint *bool `json:"readOnlyHint,omitempty"`
DestructiveHint *bool `json:"destructiveHint,omitempty"`
IdempotentHint *bool `json:"idempotentHint,omitempty"`
OpenWorldHint *bool `json:"openWorldHint,omitempty"`
Icon string `json:"icon,omitempty"`
Group string `json:"group,omitempty"`
Parent string `json:"parent,omitempty"`
DefaultPermission ToolPermission `json:"defaultPermission,omitempty"`
Strict *bool `json:"strict,omitempty"`
}
MCPToolHints describes MCP tool annotations plus Clicky-specific UI and approval metadata. Boolean fields are pointers because both true and false are meaningful MCP hint values.
func (MCPToolHints) IsZero ¶ added in v1.21.38
func (h MCPToolHints) IsZero() bool
IsZero reports whether no tool metadata or MCP annotations are set.
type MultiFilter ¶
type MultiFilter []string
MultiFilter is a string-list flag whose values are interpreted with collections.MatchItems semantics: plain values include, !value excludes.
type NamedFilter ¶
type NamedFilter struct {
// Name is the unique registry key.
Name string
// Label is the human-facing control label. Defaults to Name when empty.
Label string
// Type is the UI control type: "select" (default), "multi-select", "date",
// "from", or "to".
Type string
// Multi reports whether the control accepts multiple selections.
Multi bool
// Limit caps the option set this filter enumerates in one shot. Zero takes
// MaxLookupOptions, which is also the ceiling. Declare a smaller one for a
// field whose cardinality makes a full head set useless to pick from: what
// is past it is reached by typing, not by scrolling.
Limit int
// Source resolves the filter's options.
Source FilterSource
}
NamedFilter is a reusable filter definition registered under a unique name and attached to entities by reference. Authored in Go (RegisterFilter) or declaratively (RegisterFilterSpec), both normalize to this type.
func FilterFromSpec ¶
func FilterFromSpec(s FilterSpec) (NamedFilter, error)
FilterFromSpec builds (but does not register) a NamedFilter from a declarative spec, validating the source kind.
func GetFilter ¶
func GetFilter(name string) (NamedFilter, bool)
GetFilter returns the named filter and whether it was found.
func MustGetFilter ¶
func MustGetFilter(name string) NamedFilter
MustGetFilter returns the named filter, panicking if it is not registered. Used at attach time (Use/.As), where an unknown reference is a wiring bug.
func (NamedFilter) Spec ¶
func (f NamedFilter) Spec() FilterSpec
Spec emits the declarative form of a NamedFilter, used for OpenAPI generation and round-tripping. A func/custom source emits its kind without detail (its options are resolved server-side via the lookup endpoint).
type PageInfo ¶
type PageInfo struct {
Limit int `json:"limit"`
Offset int `json:"offset"`
Total int64 `json:"total"`
}
PageInfo carries the effective paging window for a list response.
type PageLimits ¶ added in v1.21.55
PageLimits bound what one request may ask for. A zero value resolves to the defaults, so a caller with no opinion does not have to state one.
type PageRequest ¶ added in v1.21.55
type PageRequest struct {
// Scope is ScopePage or ScopeAll.
Scope string
// Format is the resolved export format, after ?format and Accept have been
// reconciled — the operation never has to look at either again.
Format string
// Cursor is an opaque position handed back by a previous page.
Cursor string
// Filename overrides the download name. Empty means derive one.
Filename string
Limit int
Offset int
// Download asks for a Content-Disposition even when no filename was named.
Download bool
// SkipTotal waives the exact total. A backend asked for the size of the whole
// result has to produce the whole result first, which is the difference
// between an export that streams and one that hangs until the last row is
// found — so an all-rows export declines to ask.
SkipTotal bool
}
PageRequest is one export: which rows, in what shape, and how far.
func ParsePageRequest ¶ added in v1.21.55
func ParsePageRequest(r *http.Request, limits PageLimits) (PageRequest, error)
ParsePageRequest reads the transport half of a request: which rows, in what shape, and how far. Every failure is a StatusError so the caller writes one error shape rather than choosing between two.
type PageResponse ¶ added in v1.21.55
type PageResponse struct {
Rows formatters.RowIterator
// Mode is ModePage, ModeBuffered or ModeStreaming.
Mode string
// Total is nil when the backend states no size for the whole result.
Total *Total
HasMore bool
// Next is the opaque position resuming after this page, when the provider
// issued one.
Next string
// Ceiling stops an all-rows export. Zero means the rows are already bounded,
// and therefore that nothing is left to discover by reading to the end.
Ceiling int
// MaxRows is the ceiling this export was bounded by, reported so a caller
// sees the same number the server enforced.
MaxRows int
// Truncated reports a cut this response already knows about — the operation's
// own size, or a cap the backend applied. A ceiling is not known to have
// bitten until the rows have been read, so that case is reported as a trailer.
Truncated bool
// Pageable reports whether the operation can serve a position past its first
// page. A response that cannot must not report one as available.
Pageable bool
}
PageResponse is everything the transport reports about the rows it is about to write. Every field is resolved before the first byte of the body, because a header cannot be corrected once the body has begun.
It is unrelated to PagedResult[T], the buffered JSON envelope: this one is what clicky turns into an HTTP response, and its rows are still unread.
type PagedFunc ¶ added in v1.21.55
type PagedFunc func(ctx context.Context, req PageRequest, flags map[string]string) (PageResponse, error)
PagedFunc supplies one page or one export of rows.
It is preferred over ContextDataFunc when set, and the division is the point: the operation answers only what the rows are and what is true about them — how many exist, whether more can be asked for, where to resume — while clicky owns the response. Content negotiation, every header below, streaming and the download name are therefore identical across every operation that opts in, rather than re-derived per command.
type PagedResult ¶
PagedResult is a typed list response with paging metadata.
func NewPagedResult ¶
func NewPagedResult[T any](rows []T, limit, offset int, total int64) PagedResult[T]
NewPagedResult returns a paged result with a stable non-nil data array.
func (PagedResult[T]) PageMetadata ¶
func (p PagedResult[T]) PageMetadata() PageInfo
func (PagedResult[T]) PageRows ¶
func (p PagedResult[T]) PageRows() any
type Property ¶
type Property struct {
Type string `json:"type"`
Description string `json:"description"`
Enum []string `json:"enum,omitempty"`
Default interface{} `json:"default,omitempty"`
}
Property represents a JSON schema property.
type RPCOperation ¶
type RPCOperation struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters []RPCParameter `json:"parameters"`
Schema Schema `json:"schema"`
Command ExecutableCommand `json:"-"` // Transport-neutral handle to the original command
Path string `json:"path,omitempty"` // For REST APIs
Method string `json:"method,omitempty"` // HTTP method
Tags []string `json:"tags,omitempty"` // OpenAPI tags (auto-derived from the parent command)
Group string `json:"group,omitempty"` // tool-group this operation belongs to (clicky/tool-group)
ToolHints MCPToolHints `json:"-"` // MCP annotations and Clicky-specific tool metadata
DataFunc DataFunc `json:"-"` // Direct data provider, bypasses stdout capture
ContextDataFunc ContextDataFunc `json:"-"` // Context-aware data provider; preferred over DataFunc when set
PagedFunc PagedFunc `json:"-"` // Paging/export data provider returning a PageResponse; preferred over ContextDataFunc when set. The operation supplies rows and the paging facts about them, and clicky owns the response: headers, content negotiation, streaming and the download name.
LookupFunc DataFunc `json:"-"` // Direct filter metadata provider
ContextLookupFunc ContextLookupFunc `json:"-"` // Context-aware filter metadata provider; preferred over LookupFunc when set
Clicky *ClickyOperationMeta `json:"-"` // Entity semantics used for OpenAPI extensions
ResponseType reflect.Type `json:"-"` // Static response type for OpenAPI generation
ResponseArray bool `json:"-"` // Response body is an array of ResponseType
ResponsePaged bool `json:"-"` // Response body is a paged envelope around ResponseType rows
ResponseEntityID bool `json:"-"` // Array item schema should include Clicky _id metadata
}
RPCOperation represents a generic RPC operation that can be converted to various formats (OpenAPI, MCP tools, …).
type RPCParameter ¶
type RPCParameter struct {
Name string `json:"name"`
Type string `json:"type"`
Description string `json:"description"`
Required bool `json:"required"`
Default interface{} `json:"default,omitempty"`
In string `json:"in,omitempty"` // "query", "path", "header" for REST
}
RPCParameter represents a parameter in an RPC operation.
type RPCService ¶
type RPCService struct {
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Operations []RPCOperation `json:"operations"`
}
RPCService represents a collection of RPC operations.
type ResponseOpenAPIMeta ¶
ResponseOpenAPIMeta describes the static response type for a generated command. It is consumed by the RPC/OpenAPI layer without executing handlers.
func GetCommandResponseMeta ¶
func GetCommandResponseMeta(cmd *cobra.Command) *ResponseOpenAPIMeta
GetCommandResponseMeta returns static response metadata attached to a command.
type Schema ¶
type Schema struct {
Type string `json:"type"`
Properties map[string]Property `json:"properties"`
Required []string `json:"required"`
}
Schema represents a JSON schema for operation input/output.
type SearchableFilter ¶
type SearchableFilter[ListOpts any] interface { OptionsWithQuery(opts ListOpts, query string, limit int) (options map[string]api.Textable, total int) }
SearchableFilter is an OPTIONAL extension a Filter may implement when its option set is too large to enumerate in one shot (e.g. a SQL DISTINCT column with thousands of values). The lookup handler type-asserts to it; filters that don't implement it keep the plain Options() behaviour unchanged.
OptionsWithQuery returns at most limit options. An empty query means "the head set": the first limit options plus total = the true distinct count, so the UI can show "… and N more". A non-empty query returns up to limit options matching it (server-side search), letting the UI surface values that sort beyond the head. total is only meaningful for the head (query == "") call.
type StatusError ¶ added in v1.21.55
type StatusError struct {
// Status is the HTTP status this error is written with. It never appears in
// the body: the status line already carries it, and repeating it invites the
// two to disagree.
Status int `json:"-"`
Code string `json:"code"`
Message string `json:"message"`
}
StatusError is the body every failed HTTP operation returns.
The 409 that asks for a connection has always been structured, and a client that can branch on one code but has to string-match the rest is a client that breaks when a message is reworded. Code is the stable part; Message is for a person to read. It replaces http.Error, whose flat text body forces exactly the string-matching this exists to avoid.
func NewStatusError ¶ added in v1.21.55
func NewStatusError(status int, code, message string) *StatusError
NewStatusError builds an error a handler can return and the transport can write verbatim.
func NewStatusErrorf ¶ added in v1.21.55
func NewStatusErrorf(status int, code, format string, args ...any) *StatusError
NewStatusErrorf is NewStatusError with a formatted message.
func UnknownDynamicEntity ¶ added in v1.21.55
func UnknownDynamicEntity(family, name string) *StatusError
UnknownDynamicEntity is the answer to a name a family cannot resolve.
func (*StatusError) Error ¶ added in v1.21.55
func (e *StatusError) Error() string
func (*StatusError) StatusCode ¶ added in v1.21.55
func (e *StatusError) StatusCode() int
StatusCode reports the status this error should be written with, defaulting to 500 so a zero Status can never produce an invalid status line.
func (*StatusError) Write ¶ added in v1.21.55
func (e *StatusError) Write(w http.ResponseWriter)
Write renders the error as its JSON body at its status.
type ToolPermission ¶ added in v1.21.38
type ToolPermission string
ToolPermission is the default approval mode a tool advertises to clients.
const ( ToolPermissionAsk ToolPermission = "ask" ToolPermissionOff ToolPermission = "off" ToolPermissionOn ToolPermission = "on" ToolPermissionAuto ToolPermission = "auto" )
type Total ¶ added in v1.21.55
Total is a result-set size together with how much the backend was willing to promise about it.
func (*Total) Relation ¶ added in v1.21.55
Relation reports how X-Total-Count should be read.
A nil Total is the third answer and the reason this is a pointer method: a backend that states no total at all is different from one that reports zero, and without a relation those two are the same absent header.
type TypedFilter ¶
TypedFilter is an OPTIONAL extension a Filter may implement when the underlying flag type is intentionally broader than the UI control type. For example, date-math flags often remain strings at the CLI layer while the web lookup response should still advertise a from/to date-range control.
Source Files
¶
- annotations.go
- attach.go
- builder.go
- command.go
- doc.go
- dynamic.go
- dynamic_family.go
- dynamic_spec.go
- entity.go
- errors.go
- filter.go
- lookup_response.go
- multi_filter.go
- operation.go
- pagination.go
- paging.go
- paging_formats.go
- path.go
- primary_action.go
- registry.go
- render.go
- response_meta.go
- schema.go
- sources.go
- spec.go
- sub_command.go
- tool_hints.go
- types.go