xyz

package module
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 18 Imported by: 0

README

xyz-go — One definition, three interfaces

Go Version MCP Protocol Dependencies 中文

A command toolkit for Go: define a command once (argument struct + validation + per-interface details) and one binary automatically speaks three interfaces — CLI subcommands, an HTTP REST service (with an OpenAPI document), and an MCP tool server (official Go SDK). The library decides the running mode by itself.

import "github.com/ejfkdev/xyz-go" // package name is xyz; the import binds as xyz

func main() {
	xyz.Define("user.add", addUser).
		Summary("Add a user").
		CLI(xyz.CliHints{Usage: "add <name>", Fields: map[string]xyz.CliFieldHint{
			"age": {Shorthand: "a", Default: 20},
		}}).
		HTTP(xyz.HTTPHints{Method: "POST", Path: "/users/{name}"}).
		MCP(xyz.MCPHints{Annotations: []string{"write"}}).
		Also(xyz.Define("math.sum", sum).Summary("Sum two numbers")).
		Run() // register + dispatch + exit — that's the whole program
}
$ example user add bob -a 25          # CLI: subcommands + shorthand flags
id    1
name  bob
age   25

$ example user list                   # []struct renders as an aligned table
id  name   age
--  -----  ---
1   alice  18

$ curl -s -X POST localhost:8080/users/alice -d '{"age":9}'
{"id":2,"name":"alice","age":9}

$ example mcp stdio                   # MCP: every command becomes a tool (stdio/SSE/streamable HTTP)

Features

  • One definition, zero boilerplate: the whole main is a single xyz.Define(...)...Run() chain — no os.Exit, no explicit registry, no dispatch switch.
  • One pipeline across all three interfaces: CLI (strings), HTTP (JSON) and MCP arguments are normalized into one input shape and flow through the same decode → defaults → validate → handler path, so behavior never drifts.
  • Per-interface fine-tuning: shorthands, aliases, env fallbacks, binding locations, and interface-specific default values (two-tier layering: global tag default → per-interface override).
  • Envelope-free responses: primitives print bare, structs align as key/value, []struct becomes a table, --json flips to JSON; HTTP answers bare JSON; MCP returns both structuredContent and human textContent.
  • One error taxonomy: a single errs.New(errs.KindNotFound, ...) drives the CLI exit code, the HTTP status code and the MCP error code simultaneously.
  • Dependency hygiene: the core packages (spec / registry / errors / cli / httpapi / root) have zero third-party dependencies; the only third-party tree is the official MCP SDK, removable wholesale with -tags nomcp (smallest trimmed build ≈ 3.9M).
  • Protocol versions under control: MCP speaks the five spec revisions from 2024-11-05 to 2026-07-28; --versions pins the subset. Tools also carry a reflection-generated outputSchema (OpenAPI response schemas share the same source).
  • Production-friendly: SIGINT/SIGTERM graceful shutdown (context flows into handlers), /healthz probe, gzip, CLI help with inline (default …)/(env …)/(oneof …) hints, and completion bash|zsh|fish.

Install

go get github.com/ejfkdev/xyz-go

Requires Go ≥ 1.25 (dictated by the official MCP SDK's go directive). A complete runnable showcase lives in cmd/example (11 commands covering the full API surface); cmd/tour walks through the internals.

One definition: the argument struct

Tags on the argument struct form the shared contract across every interface (wire names, descriptions, defaults, required-ness, enums, validation, secrecy):

Tag Meaning Example
json:"name" Wire field name; - excludes it from binding & schema (still injectable via env/header by Go field name) json:"user_name"
desc:"..." Field description (CLI help and JSON Schema alike) desc:"username"
default:"..." Global default, parsed per field type, overridable per interface default:"18"
required:"true" Must be provided required:"true"
enum:"a,b" Allowed values (enforced at decode; written into schema) enum:"fast,slow"
validate:"..." Validation rules (built-in validator; see the supported set below) validate:"min=2,email"
secret:"true" Sensitive: redact in help/logs/echoes secret:"true"
cli:"..." CLI bindings: shorthand=a, positional, hidden, env=VAR, - cli:"shorthand=a,env=TOKEN"
http:"query" HTTP binding: query (the default when unset) / path / header / form / body http:"header"
httpName:"X-Key" HTTP wire-name override (typically a header name) httpName:"X-Api-Key"

validate supports: required, omitempty, min, max, len, gt, gte, lt, lte, oneof, email — a go-playground-compatible subset. Unsupported rules fail at registration time, never silently at runtime.

Type support: all scalars and named scalars (type Port int), []T, []byte, *T, nested structs, time.Time, time.Duration; maps, interfaces, anonymous embedding and recursive types are rejected at registration. All wired formats accept strings (CLI), JSON shapes (HTTP body) and raw JSON (MCP) with lossless conversion checks (3.7 never silently becomes int(3)).

Per-interface configuration & default layering

CLI()/HTTP()/MCP() on the Define chain configure command-level details and, via the Fields map, override tags per field (both layers merge; a zero hint field means "keep the tag"):

CLI(xyz.CliHints{
	Usage:   "add <name>",             // usage line in help
	Aliases: []string{"ua", "new"},    // aliases equal subcommand names
	Fields: map[string]xyz.CliFieldHint{
		"age":   {Shorthand: "a"},        // shorthand (also available as a tag)
		"mode":  {Default: "fast"},       // CLI-only default
		"token": {EnvVar: "APP_TOKEN"},   // env fallback
	},
})

Default precedence for one field (CLI as the example):

explicit flag > env fallback > interface default > global tag default (Invoke fills it) > zero value

Mechanism: each frontend injects its own overrides (Entry.CLIDefaults()/HTTPDefaults()/MCPDefaults()) before calling Invoke, which then applies global tag defaults — one pipeline, drift-free. MCP's overrides also replace default in inputSchema (the schema is MCP's contract).

Three modes

example [command] [args]          CLI: subcommand tree, shorthands/aliases/-h/-v/--json/positionals/env
example serve --addr :8080        HTTP: REST routes + /openapi.json + /mcp on the same port
example mcp stdio|sse|http        MCP: official SDK, three transports (--versions pins revisions)
example completion bash|zsh|fish  Built-in shell completion scripts

CLI (pure standard library): registry name user.add becomes the two-level subcommand user add; -h/--help prints per-command help (with inline (default …)/(env …)/(oneof …) hints), -v/--version prints the version (xyz.Version, injectable via -ldflags "-X github.com/ejfkdev/xyz-go.Version=v1.2.3").

HTTP (pure standard library): routes come straight from HTTPHints{Method, Path} ({name} is a path parameter); fields without an http: tag bind from the query string by default, a JSON body merges as the argument base; the error taxonomy maps to status codes (400/401/403/404/409/500) with {"error":"..."} bodies; GET /openapi.json serves an OpenAPI 3 document from the same InputSchema (response schemas included); GET /healthz probes liveness and Accept-Encoding: gzip is answered transparently. Commands without HTTP hints are not routed.

MCP (official SDK): commands become tools; tools/list serves the reflection-generated inputSchema and outputSchema; success returns dual content — structuredContent (bare JSON) + textContent (the CLI-style rendering); failures return isError: true with the classified message. Supported spec revisions: 2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, 2026-07-28 (the newest is the handshake-free server/discover era); built-in constraints: SSE serves ≤2025-11-25 only, streamable HTTP needs --stateless for 2026-07-28.

Response rendering (envelope-free)
Return type CLI --json / HTTP / MCP structuredContent
nil / nil pointer nothing JSON null
string / bool / numbers bare value, one line bare JSON value
time.Time RFC3339 RFC3339 string
[]scalars one per line JSON array
struct aligned key value columns JSON object
[]struct aligned table (header + rule) JSON array
map sorted key/value pairs JSON object
Per-channel output functions (custom rendering)

Every channel can override its rendering independently via the Output field on the channel hints — different shapes per channel, no core changes. Precedence on all three: machine mode (--json) > Output > §12.7 block envelope > default rendering. Error paths never pass through Output (status codes / exit codes stay as classified). v is the same result value the default renderer would see.

xyz.Define("user.add", addUser).Summary("Add a user").
	CLI(xyz.CliHints{
		Output: func(w io.Writer, v any) error { // rich text / colors / paging
			return lipsglossRender(w, v) // or write ANSI yourself
		},
	}).
	HTTP(xyz.HTTPHints{
		Method: "POST", Path: "/users/{name}",
		Output: func(w http.ResponseWriter, r *http.Request, e *spec.Entry, v any) error {
			w.Header().Set("Content-Type", "text/plain")
			w.WriteHeader(201)
			_, err := fmt.Fprintf(w, "created: %v", v)
			return err
		},
	}).
	MCP(xyz.MCPHints{
		Output: func(w io.Writer, v any) error { // markdown textContent
			_, err := fmt.Fprintf(w, "## result: %v", v)
			return err
		}, // structuredContent is still generated by the framework
	}).
	Run()

When the program is invoked by another program (piped, non---json), make your Output call the default cli.Render/plain-text path yourself — or better, use the machine mode conventions of each frontend (--json, HTTP body, MCP structuredContent) as the stable contract and treat the human projection as presentation-only.

Error taxonomy

Handlers return errs "github.com/ejfkdev/xyz-go/errors" and one classification drives the three interfaces:

Kind HTTP CLI exit JSON-RPC / MCP
invalid_input (auto for decode/validation) 400 2 -32602
unauthorized 401 1 -32010
forbidden 403 1 -32011
not_found 404 1 -32001
conflict 409 1 -32009
unavailable 503 1 -32603
unclassified (falls back to internal) 500 1 -32603

Configuration: mode words & capability switches

All dispatch configuration lives in xyz.Config; the zero value means defaults. Chain style uses .Configure(cfg), functional style uses MainConfig/RunConfig:

xyz.MainConfig(xyz.Config{
	// serve/mcp/help are reserved words; renaming releases the old words for use as commands
	Modes: xyz.ModeWords{Serve: "httpd", MCP: "protocol", Help: "assist"},
	// disabling a channel removes only its runtime path: mcp/serve/help/-v always survive
	Capabilities: xyz.Capabilities{NoCLI: true, NoMCP: true, NoHTTP: true},
})

With NoCLI, user subcommands disappear but mcp stdio, serve, help and -v keep working (the overview annotates "disabled" and hides the command table); the disabled channel's CLI()/HTTP()/MCP() configuration still compiles and runs — it simply stops being consumed by a frontend that is switched off. A registry with zero registered commands is a silent no-op (exit 0).

Built-in configuration (--xyz.* and Config fields)

The library's own settings live in xyz.Config fields and the --xyz.* command-line namespace; precedence: mode-local flag > global --xyz.* / code Config > library defaults. Inside serve/mcp the mode word is the namespace, so built-ins use bare names (--bearer, --addr, --cors, …), and the prefixed --xyz.* forms work anywhere on the command line. Renaming the mode words migrates the namespace with them.

Parameter Code field Meaning
--bearer=tok1,tok2 (or --bearer tok) Config.BearerTokens Bearer verification for serve REST and MCP http/sse: Authorization: Bearer <tok> must hit one of the tokens, else 401 + {"error":"unauthorized"}; empty = no auth. stdio is a local process and unaffected (a note is logged)
--addr=:8080 Config.Addr Default listen address for serve and mcp(http/sse)
--log-level=debug (or --xyz.log-level) Config.LogLevel Library diagnostics to stderr (xyz[level]: prefix): debug/info/warn/error, default info. Command results and usage errors are unaffected
--timeout=45s Config.Timeout serve read/write/idle timeouts; 0 keeps only the 10s header timeout
--tls-cert/--tls-key Config.CertFile/KeyFile serve switches to TLS when both are given
--cors=https://a,b (or *) Config.CORSOrigins CORS allowlist for serve and MCP http/sse; OPTIONS preflights answer before auth (browser preflights carry no credentials)
--session-timeout=30m (mcp only) mcp.Options.SessionTimeout Idle-session expiry for streamable HTTP (the SDK's SessionTimeout)
example serve --bearer=s3cret                        # REST/openapi/mcp all require credentials
example mcp http --addr :9000 --bearer a,b           # standalone MCP, same scheme
example mcp http --xyz.bearer a,b                    # prefixed form is equivalent

Interface language

The interface language of all built-in text (overview, help labels, usage errors, diagnostics) is picked in this order: --xyz.lang=en|zh-CN flag > Config.Lang > LANG/LC_ALL environment (a lowercase zh prefix picks Chinese) > English (default). Both languages ship in the library. Configure more multilingual content with Config.Translations (language → (message key → text)); key names and English wording are the xyz-spec §15.5 canonical catalog:

xyz.MainConfig(xyz.Config{
    Lang: "zh-CN", // or: LANG=zh_CN ./app; or: --xyz.lang=zh-CN
    Translations: map[string]map[string]string{
        "en": {"help.help_flag": "show this help"},
    },
})

Error taxonomy messages (§8) stay English; user content (summaries, descriptions, help blocks) is never translated.

Command channels, daemons & composable dispatch

  • Per-command channel switches: CliHints{Skip}, HTTPHints{Skip}, MCPHints{Skip} remove the command from the marked channel only (no subcommand node / no route / no tool). CLI Skip also drops aliases and completion words — stronger than Hidden.

  • Daemon commands: CliHints{Daemon: true} declares a long-running lifecycle — the handler blocks until ctx.Done(), the CLI exits 0 gracefully, the return value is not rendered and the command is implicitly CLI-only.

    spec.Define("watch", watch).CLI(spec.CliHints{Daemon: true})
    func watch(ctx context.Context, in *args) (*resp, error) { <-ctx.Done(); return nil, nil }
    
  • Channel defaults: --default k=v (repeatable; comma-pairs) — injected at serve/mcp startup, fills absent request/tool keys only; precedence: explicit > env > interface default > channel default > global default > zero. Any unrecognised --key value in serve/mcp is a shorthand for it: gs serve --index ./wiki equals --default index=./wiki. Code side: Config.ChannelDefaults.

  • Composable dispatch: code, handled := xyz.TryRun(reg, args) — full dispatch pipeline, but an unknown CLI top word returns (0, false) silently so the host can route its own commands; TryRunConfig takes a custom Config.

Custom help blocks

Free text blocks, raw multi-line, printed verbatim (trailing newlines normalized to one); empty = no-op:

// Overview top/bottom (Config) — app name, description, version, repo…
xyz.MainConfig(xyz.Config{
    HelpBefore: "udf v1.0.0 — inspect disk images\nhttps://github.com/example/udf",
    HelpAfter:  "More examples: https://github.com/example/udf#examples",
})
// Per-command -h (CliHints):
CLI(xyz.CliHints{Before: "extract — 解包镜像", After: "仓库: https://…"})

Block placement: Before at the very top of -h (ahead of the description), After at the very end (after Global Flags); leaf commands only. HelpBefore/HelpAfter surround the help overview (the after block prints even when the command table is hidden). No named block kinds are prescribed — examples, version lines, repository URLs and anything else are the user's own text.

Waiting list (kept out to stay minimal — each lands in one iteration when asked): log file rotation, basic rate limiting.

Embedding & multiple registries

Besides the singleton chain, the parameterized pure functions (which return the exit code without exiting) serve embedding, tests and multi-registry setups:

reg := registry.New()
spec.Define("user.add", addUser).Summary("...").Register(reg) // explicit path
os.Exit(xyz.Run(reg, os.Args[1:]))                            // for deferred cleanup in main

srv := mcp.Server(reg, mcp.Options{Versions: []string{mcp.ProtocolV2026_07_28}})
h, _ := httpapi.Handler(reg)      // mount all HTTP routes (healthz & openapi included)
addOne, _ := spec.Define(...).Register(reg) // 或 httpapi.HandlerFor(entry) 挂单条命令到任意路由器
app, _ := cli.NewWithOptions(reg, cli.Options{Out: w, ErrOut: ew}) // 或 cli.New(reg) + app.SetOutput / app.Use(中间件)

mcp.RunContext(ctx, reg, args)
cli.RunContext(ctx, reg, args)

Already on Cobra / Gin / Echo / chi? See the migration guide with runnable examples in examples/cobra and examples/gin — three coexistence levels (replace the frontend, mount per-command handlers, or reuse middleware pieces), all without touching the core.

Dependency policy & binary size

  • Zero third-party dependencies in the core packages: go.mod has exactly one direct dependency tree — the official MCP SDK (github.com/modelcontextprotocol/go-sdk). CLI and HTTP are pure standard library; no mimetype, no locale packs.
  • Trim any channel via build tags (any combination):
Build tags Channels Size (-s -w -trimpath, cmd/example measured)
(default) CLI + HTTP + MCP 8.3M
-tags nomcp CLI + HTTP 6.5M
-tags nocli HTTP + MCP 8.3M
-tags nohttp CLI + MCP 7.9M
-tags nomcp,nohttp CLI only 4.1M
-tags nocli,nomcp,nohttp embedding only 3.9M
go build -ldflags "-s -w" -trimpath -o example ./cmd/example
go build -tags nomcp,nohttp -ldflags "-s -w" -trimpath -o example ./cmd/example

Size breakdown (stripped): Go's runtime floor ≈1.1M (self-contained static linking — every Go binary pays this) + the library itself (fmt/json/reflection, plus the example's own code) ≈2.8M + HTTP (net/http, TLS, gzip) ≈2M + the MCP SDK ≈3–5M (grows with command count). A trimmed channel disappears as a block; invoking it answers a clear error and exits 1.

Package layout

Package Responsibility Dependencies
/ (root package xyz) fluent Builder, mode dispatch, capability switches, built-in parameters (--xyz.*), version stdlib
/spec generic definition, field reflection, decode pipeline, validation, JSON Schema stdlib
/registry command table: registration, conflict checks, the default singleton stdlib
/errors error taxonomy and three-interface mappings stdlib
/cli CLI frontend: command tree, flag parsing, help, completion stdlib
/httpapi HTTP frontend: routing, binding, middleware, openapi.json stdlib
/logx leveled diagnostics to stderr (xyz[level]: prefix) stdlib
/mcp MCP frontend: three transports, protocol versions official SDK
/cmd/example, /cmd/tour showcase & internal tour

Design principles

  1. Zero boilerplate by default, explicit paths always available: the singleton chain is the main entry; registry-parameterized pure functions are the backdoor for embedding and tests — both share one dispatch and invoke pipeline.
  2. Fail at registration: bad names/types/tags/route conflicts/unsupported validation rules surface at startup, never at runtime.
  3. Configuration is data; frontends are consumers: CLI()/HTTP()/MCP() only store metadata, so build tags and capability switches never break compilation.
  4. Envelope-free, natural forms: machines read JSON, humans read tables, from one return value.
  5. The shell is uncuttable: help/-v/mode words/completion work in every combination.
  6. The mode word is the namespace: built-ins in serve/mcp use bare names; --xyz.* works globally; library messages never hardcode mode words — renaming migrates everything.
  7. Cancellation flows everywhere: the dispatcher owns a signal context that reaches CLI/HTTP/MCP handlers; HTTP drains in-flight requests before exiting.

Development

go vet ./... && go test ./...                    # unit tests across all 8 library packages
go test -tags "nocli nomcp nohttp" ./...         # every build-tag variant also passes
go run ./cmd/example                             # full showcase
go run ./cmd/tour                                # internal-walkthrough tour

Output contract: command results go to stdout; errors and diagnostics go to stderr (diagnostics carry the xyz[level]: prefix, level via --log-level); under the stdio transport stdout is reserved for protocol frames. The context.Context passed to handlers is canceled on SIGINT/SIGTERM (the HTTP server drains in-flight requests first).

Release
git tag v0.1.0 && git push origin v0.1.0   # consumers: go get github.com/ejfkdev/xyz-go@v0.1.0

📄 Also available: 中文文档

Release process: see RELEASING.md — every tag carries a full changelog (the annotated message is the release note).

Documentation

Overview

Package xyz wires one registry to every frontend. Main reads the process arguments, decides the running mode by itself, and exits with the code the dispatch produced. The whole program can be a single define-chain:

func main() {
	xyz.Define("user.add", addUser).
		Summary("创建用户").
		CLI(xyz.CliHints{...}).
		MCP(xyz.MCPHints{...}).
		Also(xyz.Define("math.sum", sum).Summary("求和")).
		Run()
}

Run (and Main / MainConfig) dispatch the process-wide default registry and call os.Exit internally, so deferred cleanups written in main cannot run after them. When you need defer-based cleanup, a custom exit code, several registries, or want to embed the dispatcher, use Run / RunConfig with an explicit registry, which return the exit code instead:

func main() {
	reg := registry.New()
	// ... spec.Define(...).Register(reg) ...
	defer cleanup()
	os.Exit(xyz.Run(reg, os.Args[1:]))
}

A registry with no registered commands is a silent no-op: the dispatcher exits 0 without printing anything.

Mode detection:

<app> [命令] ...          -> CLI frontend (subcommands, flags, positionals, -h / -v)
<app> mcp stdio|sse|http  -> MCP frontend (official SDK; --versions pins protocol versions)
<app> serve [--addr ...]  -> HTTP frontend (REST + /openapi.json + /mcp)
<app> (no args) | help    -> overview listing modes and commands

The mode keywords default to "serve", "mcp" and "help" and are reserved top-level names; both the keywords and the reserved-name checks follow the Modes configuration in RunConfig, so they can be renamed. Dispatch lives in main.go, configuration types in config.go, built-in parameter parsing in builtins.go, overview rendering in overview.go and the fluent builder in builder.go.

Index

Constants

This section is empty.

Variables

View Source
var Version = "dev"

Version is the version reported by the -v/--version handling in Run / Main. Override it in code, or inject at build time with -ldflags "-X github.com/ejfkdev/xyz-go.Version=v1.2.3". (The cli frontend keeps its own Version for direct embedding via cli.Run.)

Functions

func Main

func Main(cmds ...Definable)

Main registers any fully-built command definitions passed to it (from xyz.Define), dispatches the process-wide default registry on the process arguments, and exits with the resulting exit code. Zero arguments means "definitions already registered via RegisterDefault, just dispatch". Use Run/RunConfig instead when you need the code yourself (embedding, testing, deferred cleanups) or want an explicit registry.

func MainConfig

func MainConfig(cfg Config)

MainConfig is Main with a custom configuration (e.g. renamed mode words).

func Run

func Run(reg *registry.Registry, args []string) int

Run is Main with explicit arguments and default configuration, returning the exit code without exiting the process.

func RunConfig

func RunConfig(reg *registry.Registry, args []string, cfg Config) int

RunConfig is Run with a custom configuration (renamed mode words, channel capabilities).

func TryRun added in v0.3.1

func TryRun(reg *registry.Registry, args []string) (int, bool)

TryRun 是 TryRunConfig 的默认配置形态。

func TryRunConfig added in v0.3.1

func TryRunConfig(reg *registry.Registry, args []string, cfg Config) (int, bool)

TryRunConfig 是可组合派发:与 RunConfig 同管线,但当参数进入 CLI 模式 且首段不是任何已注册命令段/别名(也不是 flag)时,不打印任何东西、 返回 (0, false),由宿主路由其余参数(handled=false 即「未命中」)。 其余路径(总览/版本/模式词/已知命令)行为与 RunConfig 完全一致。

Types

type Builder

type Builder[T, R any] struct {
	// contains filtered or unexported fields
}

Builder is the fluent main entry: one Define chain configures the whole program. Define opens it, Summary/Description/CLI/HTTP/MCP configure the current command, Also appends fully-built commands, and the terminal Run registers everything into the default registry, dispatches the process arguments, and exits with the resulting exit code.

xyz.Define("user.add", addUser).
	Summary("创建用户").
	CLI(xyz.CliHints{...}).
	MCP(xyz.MCPHints{...}).
	Also(
		xyz.Define("math.sum", sum).Summary("求和"),
		xyz.Define("time.now", now).Summary("当前 UTC 时间"),
	).
	Run()

Go has no generic methods, so the chain needs only this one convention: the first command is configured inline, every further command is a complete Define(...) chain handed to Also.

func Define

func Define[T, R any](name string, h Handler[T, R]) *Builder[T, R]

Define opens a chain on the default registry.

func (*Builder[T, R]) Also

func (b *Builder[T, R]) Also(cmds ...Definable) *Builder[T, R]

Also registers the current command and every command passed in, all into the same default registry, then keeps the chain going. Call it again to append more. Registration failures stop the chain: they surface at Run.

func (*Builder[T, R]) CLI

func (b *Builder[T, R]) CLI(h CliHints) *Builder[T, R]

CLI attaches command-level CLI options.

func (*Builder[T, R]) Configure

func (b *Builder[T, R]) Configure(cfg Config) *Builder[T, R]

Configure sets the dispatcher configuration used by Run / RunArgs (mode words, channel capabilities). Call it anywhere on the chain; RunConfig and RunArgsConfig take an explicit Config for that call instead.

func (*Builder[T, R]) Description

func (b *Builder[T, R]) Description(s string) *Builder[T, R]

Description sets the longer explanation of the command.

func (*Builder[T, R]) HTTP

func (b *Builder[T, R]) HTTP(h HTTPHints) *Builder[T, R]

HTTP attaches command-level HTTP options.

func (*Builder[T, R]) MCP

func (b *Builder[T, R]) MCP(h MCPHints) *Builder[T, R]

MCP attaches command-level MCP options.

func (*Builder[T, R]) Register

func (b *Builder[T, R]) Register(r spec.Registrar) (*spec.Entry, error)

Register implements Definable: it registers the underlying command into r.

func (*Builder[T, R]) Run

func (b *Builder[T, R]) Run()

Run registers the command (if not yet registered), dispatches the default registry on the process arguments, and exits with the resulting exit code. Everything after it is unreachable by design.

func (*Builder[T, R]) RunArgs

func (b *Builder[T, R]) RunArgs(args []string) int

RunArgs is the testable/embeddable form of Run: it registers, dispatches and returns the exit code without exiting the process. It uses the chain's Configured settings (zero value = every default).

func (*Builder[T, R]) RunArgsConfig

func (b *Builder[T, R]) RunArgsConfig(args []string, cfg Config) int

RunArgsConfig is RunArgs with a custom configuration.

func (*Builder[T, R]) RunConfig

func (b *Builder[T, R]) RunConfig(cfg Config)

RunConfig is Run with a custom configuration (e.g. renamed mode words).

func (*Builder[T, R]) Summary

func (b *Builder[T, R]) Summary(s string) *Builder[T, R]

Summary sets the one-line description of the command.

type Capabilities

type Capabilities struct {
	NoCLI  bool // 不在命令注册表上生成子命令(mcp/serve/help/-v 仍可用)
	NoMCP  bool // mcp 模式不可用(stdio/sse/http 都拒绝)
	NoHTTP bool // serve 模式不可用
}

Capabilities switches the channels on and off at runtime (independently of build tags). The zero value keeps every channel enabled. Disabling a channel only removes its own runtime path: the mode words (serve, mcp, help) and -v/--version keep working, and the disabled mode answers with a clear error. Disabled config methods still compile — they merely stop being consumed.

type CliFieldHint

type CliFieldHint = spec.CliFieldHint

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type CliHints

type CliHints = spec.CliHints

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type Config

type Config struct {
	Modes        ModeWords
	Capabilities Capabilities

	// Addr 是 serve 与 mcp(http/sse) 模式的默认监听地址(各模式自己的
	// --addr flag 优先)。
	Addr string
	// BearerTokens 开启 serve REST 与 MCP http/sse 传输的 Bearer 凭据校验,
	// 每个元素是一个可接受的 token;空表示不校验。命令行写法:
	// --xyz.bearer=tok1,tok2(stdio 传输为本地进程,不受影响)。
	BearerTokens []string

	// LogLevel 是库自身诊断的日志级别(logx 输出到 stderr)。
	// 零值(LevelUnset)保持默认 Info。命令行:--xyz.log-level=debug|info|warn|error。
	LogLevel logx.Level
	// Timeout 是 serve 模式的读/写/空闲超时;0 表示只保留 10s 的请求头超时。
	Timeout time.Duration
	// CertFile/KeyFile 同时给定则 serve 以 TLS 监听(--xyz.tls-cert/--xyz.tls-key)。
	CertFile string
	KeyFile  string
	// CORSOrigins 非空则开启 CORS:逐个 Origin 放行("*" 表示任意来源),
	// OPTIONS 预检在鉴权之前应答。命令行:--xyz.cors=origin1,origin2。
	CORSOrigins []string

	// Lang 覆盖界面语言:""=自动(--xyz.lang flag > 本字段 > LANG/LC_ALL
	// 环境检测 > 英文默认)。取值 "en" | "zh-CN"。
	Lang string
	// Translations 是用户的多语言内容覆盖表:语言 → (消息键 → 文本)。
	// 键名见 langx 目录(xyz-spec §15.8 的规范键表);只覆盖内置键亦可。
	Translations map[string]map[string]string

	// ChannelDefaults 是 serve/mcp 启动时注入的一批通道级默认参数
	// (字段线上名 → 字符串值):请求/调用未显式提供时自动补上,优先级
	// 高于全局 default tag、低于显式入参与接口默认。命令行:
	// --default k=v(可重复/逗号分隔对),代码侧写入本表。
	ChannelDefaults map[string]string

	// HelpBefore/HelpAfter 是 help 总览的自定义文本块:前者原样插在总览
	// 开头(程序名/描述/版本/仓库地址等自己拼),后者插在结尾(命令表之后,
	// 即使命令表被隐藏也打印)。空 = 不插入。
	HelpBefore string
	HelpAfter  string
}

Config adjusts the dispatcher. The zero value keeps every default.

type Definable

type Definable interface {
	Register(spec.Registrar) (*spec.Entry, error)
}

Definable is implemented by any fully-built command definition (spec.Command[T, R], or the Builder returned by Define), so heterogeneous commands can be collected into one chain or one Main call.

type HTTPFieldHint

type HTTPFieldHint = spec.HTTPFieldHint

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type HTTPHints

type HTTPHints = spec.HTTPHints

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type Handler

type Handler[T, R any] = spec.Handler[T, R]

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type MCPFieldHint

type MCPFieldHint = spec.MCPFieldHint

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type MCPHints

type MCPHints = spec.MCPHints

spec 公开类型在根包的原样别名:链式(单例)写法下用户只需要 import "github.com/ejfkdev/xyz-go"。

type ModeWords

type ModeWords struct {
	Serve string // 默认 "serve"
	MCP   string // 默认 "mcp"
	Help  string // 默认 "help"
}

ModeWords renames the built-in mode keywords. Fields left empty keep their defaults (serve, mcp, help).

Directories

Path Synopsis
Package block defines the reserved content-block envelope (xyz-spec §12.7): a result value whose JSON is an object with the single key "content", holding items of exactly the shapes
Package block defines the reserved content-block envelope (xyz-spec §12.7): a result value whose JSON is an object with the single key "content", holding items of exactly the shapes
Package cli is the CLI frontend: it consumes a registry's entries and turns their cli bindings (shorthands, positionals, env fallbacks, transport-specific defaults) into a command tree.
Package cli is the CLI frontend: it consumes a registry's entries and turns their cli bindings (shorthands, positionals, env fallbacks, transport-specific defaults) into a command tree.
cmd
example command
完整示例:一条 xyz.Define(...)...Run() 链 = 整个程序。
完整示例:一条 xyz.Define(...)...Run() 链 = 整个程序。
tour command
教学导览:展示三通道绑定、默认值分层和 schema 生成的内部视图。
教学导览:展示三通道绑定、默认值分层和 schema 生成的内部视图。
Package errors defines the error taxonomy shared by every frontend of the kit: one coded error drives the CLI exit code, the HTTP status code, and the MCP JSON-RPC error code alike, so transport implementations never need to interpret command-specific error strings.
Package errors defines the error taxonomy shared by every frontend of the kit: one coded error drives the CLI exit code, the HTTP status code, and the MCP JSON-RPC error code alike, so transport implementations never need to interpret command-specific error strings.
Package httpapi is the HTTP frontend, implemented on the standard library only (net/http with method-pattern routing).
Package httpapi is the HTTP frontend, implemented on the standard library only (net/http with method-pattern routing).
Package langx 是内置界面文本的 i18n 层:enum 语言 + 进程级目录 + 用户 覆盖。
Package langx 是内置界面文本的 i18n 层:enum 语言 + 进程级目录 + 用户 覆盖。
Package logx is the library's diagnostics sink: a leveled, zero-dependency logger writing to stderr.
Package logx is the library's diagnostics sink: a leveled, zero-dependency logger writing to stderr.
Package mcp is the MCP frontend, built on the official Model Context Protocol Go SDK (github.com/modelcontextprotocol/go-sdk).
Package mcp is the MCP frontend, built on the official Model Context Protocol Go SDK (github.com/modelcontextprotocol/go-sdk).
Package registry holds the type-erased entries built by spec.Define and hands them to the transport frontends.
Package registry holds the type-erased entries built by spec.Define and hands them to the transport frontends.
Package spec is the single source of truth for a command: one Go struct with tags is analyzed once and produces the metadata every frontend needs (CLI flags, HTTP bindings, MCP JSON Schema) plus an Invoke closure that decodes transport-shaped input (map[string]any) into the typed argument struct, applies defaults and validation, and runs the handler.
Package spec is the single source of truth for a command: one Go struct with tags is analyzed once and produces the metadata every frontend needs (CLI flags, HTTP bindings, MCP JSON Schema) plus an Invoke closure that decodes transport-shaped input (map[string]any) into the typed argument struct, applies defaults and validation, and runs the handler.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL