cli

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 47 Imported by: 0

Documentation

Overview

Package cli is PayCLI's command tree and the only place in the program that is allowed to see the process itself.

Everything ambient — argv, the environment, the three standard streams, the wall clock, the HTTP transport and the on-disk directory layout — enters through App and is carried to every command by Runtime. That is what makes whole-command golden tests possible in-process (§17.3): a test builds an App over bytes.Buffers and a fixture server's RoundTripper and asserts on the captured stdout, stderr and the returned exit code.

§3.1 makes this a CI-enforced rule: app.go is the only file outside cmd/ that may reference os.Stdout, os.Stderr, os.Stdin, os.Args, os.Getenv, os.Exit or time.Now.

Index

Constants

View Source
const (
	CheckOK      = "ok"
	CheckWarn    = "warn"
	CheckFail    = "fail"
	CheckUnknown = "unknown"
	CheckSkipped = "skipped"
)

Check statuses. `unknown` is a first-class answer: a fact PayCLI could not establish is never reported as a pass or a failure (§7.6).

View Source
const (
	// DetailCap is how many per-collection detail entries the default tier
	// emits. The SLUG LIST IS NEVER TRUNCATED — data.collection_slugs always
	// holds every slug, because it is the single most load-bearing thing this
	// command returns.
	DetailCap = 40
	// DefaultMaxBytes is --max-bytes' default: above it, --full refuses.
	DefaultMaxBytes = 400 << 10
)

§18's tiers.

View Source
const (
	GroupDiscovery = "discovery"
	GroupRead      = "read"
	GroupWrite     = "write"
	GroupFiles     = "files"
	GroupAdmin     = "admin"
	GroupOther     = "other"
)

Command groups, so `pay --help` reads as a map of the tool rather than an alphabetical wall. A command that sets no GroupID lands in "other".

View Source
const CompletionBudget = 100 * time.Millisecond

CompletionBudget is §9.9's hard limit. A shell that hangs on TAB is worse than one that suggests nothing, so every completion function checks the budget before and after the one cache read it is allowed to make.

Variables

BulkExitCodes adds §12.5's partial commit and §12's confirmation gate. 7 is the one an agent must never auto-retry.

DestructiveExitCodes adds §12's confirmation gate to a single-document write.

ReadExitCodes is what any command that reads from the server can return.

View Source
var WriteExitCodes = ReadExitCodes

WriteExitCodes is ReadExitCodes for a single-document write: same failures, and nothing bulk-specific.

Functions

func CompleteCollections

func CompleteCollections(rt *Runtime) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteCollections completes a collection slug from the cached index.

func CompleteEntities

func CompleteEntities(rt *Runtime) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteEntities completes a collection OR global slug, for `pay describe`.

func CompleteEnum

func CompleteEnum(values ...string) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteEnum completes a fixed value set, for --output and friends.

func CompleteFieldPaths

func CompleteFieldPaths(rt *Runtime, mode FieldMode) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteFieldPaths completes a field path for the collection already on the command line. It decodes at most the single shard for that collection (§9.9); a missing or generation-mismatched shard yields nothing.

func CompleteGlobals

func CompleteGlobals(rt *Runtime) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteGlobals completes a global slug from the cached index.

func CompleteProfiles

func CompleteProfiles(rt *Runtime) func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective)

CompleteProfiles completes --profile from the two config files. It reads no credentials and never touches the keychain.

func Handle

func Handle(rt *Runtime, name string, h Handler) func(*cobra.Command, []string) error

Handle adapts a Handler to cobra's RunE. `name` is the envelope's `command` field and the audit log's command name; it is the space-joined command path without the "pay" prefix ("auth login", "find").

Handle is the ONLY place a command's output reaches a stream. A Handler that returns (nil, nil) is a programming error and surfaces as exit 1.

func Main

func Main() int

Main is the entry point cmd/pay/main.go calls. It returns the exit status instead of taking it, so nothing below cmd/ ever calls os.Exit.

func Register

func Register(f factory)

Register adds a subcommand factory to the root tree.

Every command file calls this from an init(), so root.go never has to name the individual commands and several agents can add commands to the same package without editing a shared switch. Registration order does not matter: the tree is sorted by command name before assembly, which makes `pay --help` and the alias-resolution order deterministic.

func Run

func Run(ctx context.Context, app App) (code int)

Run executes one PayCLI invocation and returns its exit status. It never panics out to the caller: a panic in a command is converted to exit 1 with an `internal` envelope, because an agent parsing stdout must always get an envelope.

func SetHelp

func SetHelp(cmd *cobra.Command, h *Help)

SetHelp attaches a Help model to a command.

func UpdateHint

func UpdateHint(statePath, currentVersion string) string

UpdateHint is the one-line notice `pay version` and `pay doctor` print when a newer release was recorded by an earlier --check. It never performs I/O beyond reading the state file, so it is safe on every command path.

func WhereOperatorAliases

func WhereOperatorAliases() []string

WhereOperatorAliases is the flat alias list §10.4 puts on the --where flag.

Types

type App

type App struct {
	// Args is os.Args[1:] — the program name is not included.
	Args []string
	// Env is os.Environ()'s "NAME=value" form.
	Env []string

	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	// Now is the only clock the program has. Tests pin it.
	Now func() time.Time

	// HTTP replaces the transport internal/payload would otherwise build. A
	// test points it at an httptest.Server; production leaves it nil.
	HTTP http.RoundTripper

	// Dirs overrides the §4.1 directory layout wholesale. A zero value is
	// derived from Env.
	Dirs config.Paths

	// WorkDir is the directory project discovery (§4.3) walks up from.
	WorkDir string

	// Terminal facts. False is the safe answer: PayCLI never changes output
	// shape based on a TTY (§10), it only uses these for the interactive
	// confirmation prompt (§12.2) and the "you typed a key on a terminal"
	// warning (§5.1 step 3).
	StdinIsTTY  bool
	StdoutIsTTY bool
	StderrIsTTY bool

	// NoSignals disables the SIGINT/SIGTERM handler. Tests set it; the
	// in-process handler would otherwise leak across `go test`.
	NoSignals bool
}

App is the injected process surface. A zero value is unusable; Run substitutes inert defaults (io.Discard, a fixed clock) for anything left nil so that a malformed App can never panic mid-command.

type ArgSpec

type ArgSpec struct {
	Name       string   `json:"name"`
	Required   bool     `json:"required"`
	Type       string   `json:"type"`
	ValuesFrom string   `json:"values_from,omitempty"`
	Values     []string `json:"values,omitempty"`
	Example    string   `json:"example,omitempty"`
}

ArgSpec documents one positional argument (§10.4).

type Check

type Check struct {
	Name   string `json:"name"`
	Status string `json:"status"`
	Detail string `json:"detail"`
	Hint   string `json:"hint,omitempty"`
	Value  any    `json:"value,omitempty"`
}

Check is one diagnostic line.

type CommandSpec

type CommandSpec struct {
	Path        []string   `json:"path"`
	Short       string     `json:"short"`
	Args        []ArgSpec  `json:"args"`
	Flags       []FlagSpec `json:"flags"`
	Subcommands []string   `json:"subcommands,omitempty"`
	Output      OutputSpec `json:"output"`
	ExitCodes   []int      `json:"exit_codes"`
	Examples    []Example  `json:"examples"`
	Mistakes    []Mistake  `json:"common_mistakes,omitempty"`
	SeeAlso     []string   `json:"see_also,omitempty"`
}

CommandSpec is §10.4's machine-readable help.

type Deps

type Deps struct {
	// RT is the shared runtime.
	RT *Runtime
	// Client is the configured Payload REST client.
	Client *payload.Client
	// Manifest is the discovery index, or nil when discovery could not answer.
	// A nil manifest disables every client-side rejection rather than inventing
	// one, which is §9.3's tri-state rule applied at the top.
	Manifest *discovery.Manifest
	// FetchURL downloads a remote upload source (§13). It is a field so unit
	// tests never reach the network.
	FetchURL func(ctx context.Context, rawurl string) (body io.ReadCloser, contentType string, size int64, err error)
	// contains filtered or unexported fields
}

Deps is one data command's resolved dependencies.

type DiscoverOptions

type DiscoverOptions struct {
	Deep             bool
	AllowWriteProbes bool
	NoLabels         bool
	NoProbes         bool
}

DiscoverOptions are the knobs `pay discover` exposes. The zero value is what every other command uses.

type Example

type Example struct {
	Why string `json:"why"`
	Cmd string `json:"cmd"`
}

Example is one copy-pasteable invocation.

type FieldMode

type FieldMode string

FieldMode selects which field-path projection a completion offers.

const (
	// FieldQueryable completes --where / --or paths.
	FieldQueryable FieldMode = "queryable"
	// FieldSortable completes --sort.
	FieldSortable FieldMode = "sortable"
	// FieldSelectable completes --select / --select-exclude.
	FieldSelectable FieldMode = "selectable"
	// FieldDate completes --date-field.
	FieldDate FieldMode = "date"
)

type FlagInfo

type FlagInfo struct {
	Grammar    string   `json:"grammar,omitempty"`
	Operators  []string `json:"operators,omitempty"`
	Values     []string `json:"values,omitempty"`
	Repeatable bool     `json:"repeatable,omitempty"`
	Min        *int     `json:"min,omitempty"`
	Max        *int     `json:"max,omitempty"`
	// Note is printed in the FLAGS section, e.g. "no-op on this project:
	// localization is disabled".
	Note string `json:"note,omitempty"`
}

FlagInfo is the §10.4 detail for one flag.

type FlagSpec

type FlagSpec struct {
	Name       string   `json:"name"`
	Shorthand  string   `json:"shorthand,omitempty"`
	Type       string   `json:"type"`
	Repeatable bool     `json:"repeatable"`
	Default    any      `json:"default"`
	Usage      string   `json:"usage"`
	Grammar    string   `json:"grammar,omitempty"`
	Operators  []string `json:"operators,omitempty"`
	Values     []string `json:"values,omitempty"`
	Min        *int     `json:"min,omitempty"`
	Max        *int     `json:"max,omitempty"`
}

FlagSpec is one flag of a CommandSpec.

type Handler

type Handler func(ctx context.Context, rt *Runtime, args []string) (*output.Envelope, error)

Handler is the shape every PayCLI command implements. Returning an envelope and an error is deliberate: the envelope is rendered when err is nil, and the error is normalised into an error envelope otherwise, so no command has to know about exit codes, --path, --output or meta.

type Help

type Help struct {
	// Synopsis lines are printed verbatim under SYNOPSIS, metavariables and
	// all.
	Synopsis []string

	// Long is optional prose printed under the one-line Short.
	Long string

	// Discovered renders section 3 for this command. Returning nil falls back
	// to the shared collection inventory when Collections is true.
	Discovered func(rt *Runtime, m *discovery.Manifest) []string

	// Collections asks for the shared "collections in this project" block in
	// section 3.
	Collections bool
	// Globals asks for the globals inventory in section 3.
	Globals bool

	// Where and Sort switch on sections 4 and 5. They belong on any command
	// that accepts --where or --sort.
	Where bool
	Sort  bool

	// Args documents the positional arguments for §10.4.
	Args []ArgSpec
	// FlagInfo adds §10.4 detail (grammar, operators, bounds) that pflag
	// cannot express, keyed by flag name.
	FlagInfo map[string]FlagInfo

	Output    OutputSpec
	ExitCodes []int
	Examples  []Example
	Mistakes  []Mistake
	SeeAlso   []string

	// Notes are extra bullet lines printed after FLAGS. Use them for the
	// "this flag is a no-op on this project" callouts §10.5 item 6 requires.
	Notes []string
}

Help is the §10.5 model for one command's help text. Every command supplies the parts that cannot be derived from its cobra definition; the flag table, the command path and the subcommand list are read off cobra itself so the two can never drift.

A command with no Help still renders all eleven sections — it simply has no examples and no mistakes to show. That is a visible hole rather than a silent one, which is the point.

func HelpOf

func HelpOf(cmd *cobra.Command) *Help

HelpOf returns the Help model attached to a command, or an empty one.

type Mistake

type Mistake struct {
	Wrong string `json:"wrong"`
	Right string `json:"right"`
}

Mistake is one numbered anti-pattern and its correction. §10.5's hard rule: every "you cannot X" is followed by "do Y instead", which is why Right is not optional.

type OutputSpec

type OutputSpec struct {
	Kind      output.DataKind `json:"data_kind"`
	Paginated bool            `json:"paginated,omitempty"`
	Skeleton  string          `json:"skeleton,omitempty"`
}

OutputSpec is §10.4's output block.

type Runtime

type Runtime struct {
	App App

	// Env is the environment snapshot every layer reads from.
	Env config.Env
	// Paths is the resolved §4.1 layout.
	Paths config.Paths
	// Project is the detected Payload project (§4.3), never nil.
	Project *config.Project
	// UserFile and ProjectFile are the two config layers, never nil.
	UserFile    *config.File
	ProjectFile *config.File
	// Cfg is the resolved configuration. Nil until the persistent pre-run has
	// executed, which is why every command handler receives it already set.
	Cfg *config.Resolved

	// Command is the dotted command path of the command being run ("auth
	// login"), used for envelope.command and the audit log.
	Command string

	// Out renders envelopes. Commands must not write to App.Stdout directly.
	Out *output.Writer
	// Log is the structured logger; never nil.
	Log *slog.Logger

	// Flags is the parsed root flag state, exposed so a command can tell
	// "flag not given" from "flag given the default value".
	Flags *rootFlags
	// contains filtered or unexported fields
}

Runtime is the per-invocation world every command is handed. It owns the resolved configuration, the lazily built client, the cache session, the logger, the audit log and the renderer.

Everything expensive is lazy and memoised: `pay version` must work on a machine with no config, no network and no cache, so a Runtime that never resolves a credential never touches the keychain.

func (*Runtime) Audit

func (rt *Runtime) Audit() *audit.Logger

Audit returns the audit logger, building it on first use.

func (*Runtime) Cache

func (rt *Runtime) Cache() *cache.Store

Cache is the discovery cache store. It is permanently disabled (every read a miss, every write a no-op) under --no-cache.

func (*Runtime) CachedManifest

func (rt *Runtime) CachedManifest() (*discovery.Manifest, bool)

CachedManifest returns the discovery index from disk without any possibility of a network call. Help text (§10.5) and shell completion (§9.9) use this and nothing else: both must render instantly on a cold cache rather than block.

func (*Runtime) CachedShard

func (rt *Runtime) CachedShard(slug string, kind cache.EntityKind) (*discovery.Shard, bool)

CachedShard is the offline form used by completion and help, which may never trigger discovery.

func (*Runtime) Client

func (rt *Runtime) Client(ctx context.Context) (*payload.Client, error)

Client builds the Payload client for the resolved profile and credential.

It never hands out a client whose auth-collection slug is still §7.0's "auto" placeholder: in api-key mode the slug is part of the Authorization header, and a wrong slug answers HTTP 200 with the anonymous view rather than an error. resolveAuthCollection runs the §7.0 ladder (configured → cached → Stage -1) before the client escapes this function.

func (*Runtime) Confirmer

func (rt *Runtime) Confirmer() *safety.Confirmer

Confirmer builds §12.2's prompt over the injected streams.

func (*Runtime) Credential

func (rt *Runtime) Credential(ctx context.Context) (*secret.Credential, error)

Credential resolves §5.1's chain once per invocation.

func (*Runtime) Discovery

func (rt *Runtime) Discovery(ctx context.Context) (*discovery.Manifest, error)

Discovery returns the discovery index, running §7's pipeline when the cache cannot answer. It is memoised per invocation.

func (*Runtime) Elapsed

func (rt *Runtime) Elapsed() time.Duration

Elapsed is how long this invocation has been running.

func (*Runtime) Meta

func (rt *Runtime) Meta() output.Meta

Meta builds the §10.1 meta block from whatever the runtime currently knows.

func (*Runtime) NoRaw

func (rt *Runtime) NoRaw() bool

NoRaw reports whether --no-raw suppressed raw-body passthrough.

func (*Runtime) Now

func (rt *Runtime) Now() time.Time

Now is the injected clock. Nothing below internal/cli calls time.Now (§3.1).

func (*Runtime) RunDiscovery

func (rt *Runtime) RunDiscovery(ctx context.Context, opts DiscoverOptions) (*discovery.Manifest, error)

RunDiscovery executes §7's pipeline and persists the result (§8.7).

func (*Runtime) Scope

func (rt *Runtime) Scope() (cache.Scope, error)

Scope is the §8.1 cache scope for the resolved connection and credential.

func (*Runtime) SecretStore

func (rt *Runtime) SecretStore() *secret.Store

SecretStore is the credentials.json + keychain store for this profile set.

func (*Runtime) Secrets

func (rt *Runtime) Secrets() []string

Secrets returns the literal credential values that must be scrubbed from every log line and every rendered byte (§5.3). It never resolves a credential on its own.

func (*Runtime) Session

func (rt *Runtime) Session() *cache.Session

Session is the per-invocation cache session (§8.6).

func (*Runtime) SetDataBytes

func (rt *Runtime) SetDataBytes(n int64)

SetDataBytes makes meta.bytes report the size of the rendered payload rather than the bytes read from the network. `pay explain` is the case §18 names.

func (*Runtime) Shard

func (rt *Runtime) Shard(ctx context.Context, slug string, kind cache.EntityKind) (*discovery.Shard, bool)

Shard returns one entity's field schema, decoding it from the cache when the current invocation did not produce it.

func (*Runtime) Stderr

func (rt *Runtime) Stderr() io.Writer

Stderr is where human-facing notices and prompts go. Never the envelope.

func (*Runtime) Stdin

func (rt *Runtime) Stdin() io.Reader

Stdin is the command's input stream.

func (*Runtime) Warn

func (rt *Runtime) Warn(w ...output.Warning)

Warn records an envelope warning. Duplicates (same code and message) are collapsed so a fan-out cannot emit the same advice a hundred times.

func (*Runtime) Warnf

func (rt *Runtime) Warnf(code, format string, args ...any)

Warnf records a warning built from a code and a formatted message.

func (*Runtime) Warnings

func (rt *Runtime) Warnings() []output.Warning

Warnings returns the warnings recorded so far.

Jump to

Keyboard shortcuts

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