Help us shape the future of Go! Take the 2026 Go Developer Survey

cli

package
v0.0.1-nightly.2026102... Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: MPL-2.0 Imports: 64 Imported by: 0

Documentation

Overview

Package cli is the client-side surface: the noun-verb verb table, context resolution, the local trust store, and the output grammar.

It holds no policy. Every authorization decision is the server's, evaluated at the chokepoint in the request's own transaction; the CLI adds transport and the client-local safety properties the api-cli-surface ADR fixes — which instance a credential may be presented to, where plaintext may go, and what a script may rely on.

Index

Constants

View Source
const (
	// ExitOK is success.
	ExitOK = 0
	// ExitInternal is an unexpected fault.
	ExitInternal = 1
	// ExitUsage is a malformed invocation: unknown verb, missing argument,
	// bad flag.
	ExitUsage = 2
	// ExitAuth is authentication required or failed.
	ExitAuth = 3
	// ExitRefused covers validation, policy, a declined ceremony, a
	// trust-store refusal and an output-grammar refusal — everything the
	// server or the client declined to do.
	ExitRefused = 4
	// ExitNotFound is not found, WHICH IS ALSO UNAUTHORIZED, indistinguishable
	// by design: unauthorized ≡ nonexistent holds on the CLI exactly as it
	// does on the wire.
	ExitNotFound = 5
	// ExitUnavailable is the server or transport being unreachable, or the
	// server failing (5xx).
	ExitUnavailable = 6
	// ExitRateLimited is the server throttling this caller (429): it is up and
	// answering, and asks the caller to come back later. The Retry-After
	// seconds, when the server sent them, are printed on stderr. Split from
	// ExitUnavailable by declared amendment (#806) so a script can tell
	// throttling from an outage without matching message text.
	ExitRateLimited = 7
)

The closed exit-code set (api-cli-surface ADR § Output grammar). It is scoped to Hikyo's own termination, and it is frozen from the first stable release: scripts branch on codes, never on parsing error prose.

View Source
const (
	ExitCommandNotExecutable = 126
	ExitCommandNotFound      = 127
)

126 and 127 are the ONLY exits outside the closed set above, and they are used by `hikyo run --` alone (verbs/compose.go). They are NOT hikyo's own codes: they are the child-side shell convention that `run` borrows because it stands in for the command it execs.

127  the command named after `--` was not found on PATH (exec.LookPath).
126  the command was found but could not be executed (Exec returned).

On a successful exec there is no hikyo process at all (unix syscall.Exec; windows spawn-wait-and-exit-with-the-child-code), so the child's own exit status and signals are the invocation's, untouched — hikyo never overwrites them with a code of its own.

View Source
const (
	TLSModeSPKI     = "spki"
	TLSModeSystemCA = "system-ca"
)
View Source
const PinFileName = ".hikyo.json"

PinFileName is the file the CLI looks for, walking up from the working directory.

Variables

View Source
var ErrUntrusted = errors.New("instance is not in the local trust store")

ErrUntrusted reports an instance reference that is not in the local store. The CLI names the missing provisioning step and stops; it never prompts-to-trust mid-command, because a prompt in the middle of a scripted run is a prompt nobody reads.

View Source
var Verbs = slices.Sorted(maps.Keys(verbHandlers))

Verbs is the closed set of client verbs this build serves. main dispatches on it, and the classification-totality invariant enumerates it against the wire registry: a verb here without a probe class fails the build. Derived from the dispatch table so the two can never disagree.

Functions

func CanonicalOrigin

func CanonicalOrigin(raw string) (string, error)

CanonicalOrigin reduces a URL to scheme://host[:port], rejecting anything that carries a path, query or credentials — an origin with a path is not an origin, and a URL with userinfo is a credential in a place credentials are never allowed.

func CheckRevision

func CheckRevision(meta apigen.Meta, operationID string) error

CheckRevision refuses an operation the server is too old to serve, NAMING the server version. A new client against an old server degrades loudly, not silently: the per-operation minimum revision is the mechanism, and a bare version string is not.

func CommandPath

func CommandPath(args []string) []string

CommandPath is the leading literal words of an invocation: everything up to the first flag or "--". `values set KEY --stdin --help` yields [values set KEY]; Help trims unmatched trailing words itself.

func Help

func Help(w io.Writer, path []string) bool

Help writes the slice of the client help that applies to path: every entry whose leading words match, grouped under its section, followed by that section's notes. Trailing words that match nothing are dropped one at a time so `values set KEY --help` shows `values set`. It returns false when not even the first word is a known command.

func HelpFromText

func HelpFromText(w io.Writer, text string, path []string) bool

HelpFromText is Help over an arbitrary usage text laid out like usageText.

func HelpRequested

func HelpRequested(args []string) bool

HelpRequested reports whether args ask for help anywhere before the "--" separator. Arguments after "--" belong to the child command of `hikyo run` and are never interpreted.

func IsHelpFlag

func IsHelpFlag(arg string) bool

IsHelpFlag reports whether one argument asks for help.

func NotifyUpdate

func NotifyUpdate(ctx context.Context, ios IO) bool

NotifyUpdate emits a best-effort interactive notice before command dispatch. It reports whether the binary changed so the caller can end this process and let the user restart into the new executable.

func OpenBrowser

func OpenBrowser(target string) error

OpenBrowser launches the platform browser for the CLI front channel.

func ParseVerbosity

func ParseVerbosity(args []string) ([]string, int)

ParseVerbosity removes repeatable -v/--verbose options and -vv/-vvv groups. Options may precede the command or follow command words. A non-verbosity option without '=' and its next argument remain untouched because the next argument may be a value, including a literal '-v'. Place verbosity before other options to avoid ambiguity after boolean flags. Nothing after '--' is interpreted, including the child command of hikyo run.

func Render

func Render(w io.Writer, format Format, t Table) error

Render writes the table in the requested format to stdout.

func Report

func Report(stderr io.Writer, err error) int

Report prints an error to stderr and returns its exit code. Diagnostics go to stderr without exception, so stdout carries only the requested payload and `-o json` output is parseable by construction.

func Run

func Run(ctx context.Context, io IO, args []string) int

Run dispatches one invocation and returns its exit code.

func RunUpgrade

func RunUpgrade(ctx context.Context, ios IO, args []string) error

RunUpgrade is `hikyo upgrade` where no server host is supported: it installs the newest verified release on this CLI's track over the running executable. Naming the command is the confirmation, so it does not prompt.

func SPKIFingerprint

func SPKIFingerprint(cert *x509.Certificate) string

SPKIFingerprint is base64(sha256(SubjectPublicKeyInfo)) — the same construction as HPKP and as `openssl … -pubkey | openssl dgst -sha256`, so an operator can compute the expected value without Hikyo's help.

The public key, not the certificate: pinning the certificate would break on every renewal, and an operator whose pin breaks quarterly learns to skip checking it.

func Usage

func Usage(w io.Writer)

Usage is the frozen help text. Its exact bytes are a committed golden snapshot: help output is part of the CLI's stable surface, and a diff to it is reviewed like a spec change.

func VerbosityHelp

func VerbosityHelp() string

VerbosityHelp describes global diagnostics for host and client help.

func WithVerbosityArguments

func WithVerbosityArguments(args []string, level int) []string

WithVerbosityArguments carries the diagnostic level across process handoffs. Leading options cannot be confused with values owned by the child command.

Types

type AuthArtifact

type AuthArtifact interface {
	Kind() AuthKind
	ArtifactOrigin() string
	// contains filtered or unexported methods
}

AuthArtifact is a sealed human-session | machine-credential sum. Credential plaintext deliberately lives only on Client, never in this aggregate.

type AuthKind

type AuthKind string

AuthKind is the closed set of authentication artifacts a CLI command may carry. Unauthenticated is included only in policy: it is never an AuthArtifact value.

const (
	AuthKindUnauthenticated     AuthKind = "unauthenticated"
	AuthKindHumanSession        AuthKind = "human-session"
	AuthKindMachineCredential   AuthKind = "machine-credential"
	AuthKindDeveloperCredential AuthKind = "developer-credential"
)

type AuthKinds

type AuthKinds uint8

func (AuthKinds) Allows

func (kinds AuthKinds) Allows(kind AuthKind) bool

type AuthOperation

type AuthOperation string

AuthOperation is the canonical leaf command name already owned by each handler's parseCommon call (for example, "values export").

type BinaryUpdater

type BinaryUpdater interface {
	Apply(context.Context, updatecheck.Status) error
	// CheckReplaceable refuses before any state is written when this process
	// could not replace the running executable.
	CheckReplaceable() error
}

BinaryUpdater applies one selected release to the current executable.

type Client

type Client struct {
	Entry TrustEntry
	HTTP  *http.Client
	// Bearer is the session artifact, presented only to Entry.Origin.
	Bearer string
	// UserAgent identifies the client class in the audit trail.
	UserAgent string
	// contains filtered or unexported fields
}

Client is the origin-bound HTTP client.

Two properties it enforces that a bare http.Client does not:

  • the established TLS policy is verified on connect: either the exact leaf SPKI pin, or system-CA chain, validity and hostname verification;
  • a redirect is NEVER followed with a credential attached. A redirect off the recorded origin is the exfiltration path the trust store exists to close, and following it would hand the bearer to whoever answered.

func NewClient

func NewClient(entry TrustEntry, bearer string) (*Client, error)

NewClient builds a client bound to a trust entry.

func (*Client) Do

func (c *Client) Do(ctx context.Context, method, path string, body, out any) error

func (*Client) DoStatus

func (c *Client) DoStatus(ctx context.Context, method, path string, body any) (int, []byte, error)

Do performs a request against the bound origin and decodes the result.

`out` may be nil for endpoints with no body. A non-2xx response is converted to an exit code through the contract's closed error enum, so the CLI's exit codes and the API's statuses cannot drift apart. DoStatus is Do for a caller that must branch on a 2xx status: the publish endpoint answers 200 with a publish result or 202 with a staged approval request (#151). It decodes the payload into out only when out is non-nil and returns the 2xx status so the caller can pick the right shape.

func (*Client) Meta

func (c *Client) Meta(ctx context.Context) (apigen.Meta, error)

Meta fetches the instance's capability advertisement. `login` needs it before any session exists, and every verb uses its API revision for the skew check below.

type Context

type Context struct {
	Name     string `json:"name"`
	Instance string `json:"instance"`
	Org      string `json:"org,omitempty"`
	Project  string `json:"project,omitempty"`
	Env      string `json:"env,omitempty"`
}

Context is a stored target tuple. It names an instance by REFERENCE, never by origin: a context, like a pin file, directs but never introduces trust.

type CredentialRef

type CredentialRef string

CredentialRef identifies the invocation channel without retaining its plaintext or a token-file path.

const (
	CredentialRefEnvironment CredentialRef = "HIKYO_TOKEN"
	CredentialRefTokenFile   CredentialRef = "--token-file"
)

func (CredentialRef) String

func (r CredentialRef) String() string

type DeveloperCredentialArtifact

type DeveloperCredentialArtifact struct {
	ID          string    `json:"id"`
	Origin      string    `json:"origin"`
	Org         string    `json:"org"`
	Project     string    `json:"project"`
	Environment string    `json:"environment"`
	Token       string    `json:"token"`
	ExpiresAt   time.Time `json:"expires_at"`
}

DeveloperCredentialArtifact is origin-bound private custody, never authority. It is deliberately separate from sessions and machine configuration.

type Dimension

type Dimension string

Dimension is one resolvable target dimension.

const (
	DimInstance Dimension = "instance"
	DimOrg      Dimension = "org"
	DimProject  Dimension = "project"
	DimEnv      Dimension = "env"
)

type Env

type Env struct {
	Getenv func(string) string
}

Env is the process environment a run sees. Injecting it (rather than reading os.Getenv inline) is what makes the resolution chain testable without mutating the test process.

type Error

type Error struct {
	Code int
	Err  error
}

Error carries an exit code out of a verb.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type Flags

type Flags struct {
	Context  string
	Instance string
	Org      string
	Project  string
	Env      string
}

Flags carries the per-dimension flag values.

type Format

type Format string

Format is the envelope selector for browse verbs.

const (
	FormatTable Format = "table"
	FormatJSON  Format = "json"
)

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat validates `-o`.

type HumanSession

type HumanSession struct{ SessionArtifact }

func (HumanSession) ArtifactOrigin

func (h HumanSession) ArtifactOrigin() string

func (HumanSession) Kind

func (HumanSession) Kind() AuthKind

type IO

type IO struct {
	Stdin   io.Reader
	Stdout  io.Writer
	Stderr  io.Writer
	Env     Env
	Workdir string
	// ReadPassword reads a secret from the controlling terminal with echo
	// off. Injected for tests; nil means the real terminal.
	ReadPassword func(prompt string) (string, error)
	// TerminalSession is the command-scoped controlling terminal. Platform
	// code constructs it once; Run closes it on every return path.
	TerminalSession *disclose.TerminalSession
	// TerminalError preserves a failed platform construction until a command
	// actually needs the terminal; non-interactive commands remain usable.
	TerminalError error
	// StderrIsTerminal reports whether Stderr is a TTY. `run
	// --use-human-session` requires it (api-cli-surface ADR: "stderr-is-a-TTY
	// (an additional refusal, never the control)"). Nil means false.
	StderrIsTerminal func() bool
	// OpenURL launches a browser without printing the opaque handoff state.
	// Nil uses the platform browser opener.
	OpenURL func(string) error
	// PrepareClipboard prepares a native protected clipboard destination before
	// disclosure. Nil uses the platform implementation; injected by tests.
	PrepareClipboard func() (func(context.Context, string) error, error)
	// Exec replaces the current process image with argv0 (unix syscall.Exec) or
	// spawns-waits-and-exits-with-the-child-code (windows). `hikyo run --` is the
	// only caller. Nil uses the real platform Exec (execRun, build-tagged). Tests
	// inject it to capture the child's argv/env without a real exec.
	Exec func(argv0 string, argv, env []string) error
	// Now is the clock the compose verbs read for snapshot expiry. Nil means
	// time.Now — there is deliberately no HIKYO_NOW env knob, because a clock a
	// hostile config file could set is the rollback surface the snapshot high-
	// water mark exists to bound.
	Now func() time.Time
	// Version is the linker-stamped Hikyo version used for update comparison.
	Version string
	// DefaultUpdateChannel is linker-stamped with the artifact's release track.
	// An empty value is fail-closed as off, matching a source build.
	DefaultUpdateChannel updatecheck.Channel
	// UpdateSource is injected by update tests. Nil uses Hikyo's pinned public
	// GitHub Releases source.
	UpdateSource updatecheck.Source
	// BinaryUpdater replaces the current executable after interactive consent.
	// Nil preserves notification-only behavior when no updater is configured.
	BinaryUpdater BinaryUpdater
}

IO is the run's streams and environment, injected so every verb is testable without touching the process.

type MachineCredential

type MachineCredential struct {
	Origin        string
	CredentialRef CredentialRef
}

func (MachineCredential) ArtifactOrigin

func (m MachineCredential) ArtifactOrigin() string

func (MachineCredential) Kind

func (MachineCredential) Kind() AuthKind

type PinFile

type PinFile struct {
	Instance string `json:"instance,omitempty"`
	Org      string `json:"org,omitempty"`
	Project  string `json:"project,omitempty"`
	Env      string `json:"env,omitempty"`
}

PinFile is the committable, non-secret project-dir file: the `.nvmrc` of Hikyo. It names an instance REFERENCE plus org/project/env — never credentials, never an origin. A hostile edit is bounded to retargeting within origins this box already trusts and grants the caller already holds; the credential-exfiltration variant is closed by construction, because a reference that is not in the trust store is a hard refusal.

func FindPinFile

func FindPinFile(dir string) (PinFile, string, error)

FindPinFile walks up from dir looking for a pin file, stopping at the filesystem root.

type RateLimitedError

type RateLimitedError struct {
	Message    string
	RetryAfter time.Duration
}

RateLimitedError is a throttled response. RetryAfter is the server's Retry-After, zero when the response carried no usable value.

func (*RateLimitedError) Error

func (e *RateLimitedError) Error() string

type Resolved

type Resolved struct {
	Values  map[Dimension]string
	Sources map[Dimension]Source
	// PinFilePath is the pin file that contributed, if any — named in the
	// echo so an operator can see what directed them.
	PinFilePath string
	// ContextName is the named context that contributed, if any.
	ContextName string
}

Resolved is the assembled target with provenance.

func Resolve

func Resolve(st *State, env Env, flags Flags, workdir string) (Resolved, error)

Resolve runs the per-dimension chain: flags, then environment, then the project-dir pin file, then the named context. First hit wins PER DIMENSION, which is what makes `--env staging` against a pinned project legitimate: the override re-resolves within the same chain rather than replacing it.

func (Resolved) Echo

func (r Resolved) Echo() string

Echo renders the fully resolved target for the disclosure echo. It goes to stderr, before acting, so the active target is visible exactly when it matters without polluting parseable stdout.

func (Resolved) Get

func (r Resolved) Get(d Dimension) string

Get returns a dimension's value.

func (Resolved) Require

func (r Resolved) Require(d Dimension) (string, error)

Require returns a dimension's value or a hard error naming what was missing and where the CLI looked. Ambiguity is never a default: no dimension is ever silently assumed.

type SessionArtifact

type SessionArtifact struct {
	Instance  string `json:"instance"`
	Origin    string `json:"origin"`
	Token     string `json:"token"`
	SessionID string `json:"session_id"`
	Principal string `json:"principal"`
	ExpiresAt string `json:"expires_at"`
}

SessionArtifact is a stored CLI session. The value is a replayable bearer credential, which is why the file is 0600 in a 0700 directory and why the stored record carries the origin it was established against: the CLI presents it only to that origin.

type Source

type Source string

Source names where a dimension's value came from, so the disclosure echo can say which precedence level supplied each one.

const (
	SourceFlag    Source = "flag"
	SourceEnv     Source = "environment"
	SourcePinFile Source = "pin file"
	SourceContext Source = "context"
	// SourceConfig is the hikyo-compose.yaml project file, which the compose
	// verbs fold into resolution AFTER the standard chain: it fills a dimension
	// the chain left unresolved and a disagreement with a resolved one is a hard
	// error naming both. The file-sync verb folds its own config the same way,
	// under the Source named by that file.
	SourceConfig Source = "hikyo-compose.yaml"
)

type State

type State struct {
	// contains filtered or unexported fields
}

State locates the client state directory.

func NewState

func NewState(env Env) (*State, error)

NewState resolves the state directory: $HIKYO_STATE_DIR, else $XDG_STATE_HOME/hikyo, else ~/.local/state/hikyo (%LocalAppData%\hikyo on Windows).

func (*State) Contexts

func (s *State) Contexts() (map[string]Context, error)

Contexts reads the stored contexts.

func (*State) DeleteContext

func (s *State) DeleteContext(name string) error

DeleteContext removes a context.

func (*State) DeleteSession

func (s *State) DeleteSession(instance string) error

DeleteSession forgets an artifact.

func (*State) DeveloperCredentials

func (s *State) DeveloperCredentials() (map[string]DeveloperCredentialArtifact, error)

func (*State) Dir

func (s *State) Dir() string

Dir is the resolved state directory.

func (*State) PutContext

func (s *State) PutContext(c Context) error

PutContext stores a context.

func (*State) PutDeveloperCredential

func (s *State) PutDeveloperCredential(c DeveloperCredentialArtifact) error

func (*State) PutSession

func (s *State) PutSession(a SessionArtifact) error

PutSession stores an artifact for an instance.

func (*State) Sessions

func (s *State) Sessions() (map[string]SessionArtifact, error)

Sessions reads the stored artifacts.

func (*State) Trust

func (s *State) Trust() *TrustStore

Trust returns the trust store.

type TLSMode

type TLSMode string

TLSMode is the explicit HTTPS verification choice. Its zero value is only for legacy entries whose JSON omits tls_mode, never an explicit JSON value.

func (*TLSMode) UnmarshalJSON

func (m *TLSMode) UnmarshalJSON(raw []byte) error

type Table

type Table struct {
	Columns []string
	Rows    [][]string
	// JSON is what `-o json` emits. It is the generated contract type, so the
	// machine shape is the API's shape rather than a hand-written echo of it.
	JSON any
}

Table is a rendered result: a header plus rows, or a JSON document. Both come from the same value so the two formats cannot describe different things.

type TenantScope

type TenantScope struct {
	// contains filtered or unexported fields
}

TenantScope is a validated contiguous tenant hierarchy. Its fields stay private so callers can obtain one only through NewTenantScope, which makes sparse shapes such as org+env impossible to route accidentally.

func NewTenantScope

func NewTenantScope(resolved Resolved) (TenantScope, error)

NewTenantScope closes independently resolved dimensions into exactly one hierarchy variant. Resolution may combine flag, environment, pin-file and context values; the resulting tuple must still contain every parent.

func (TenantScope) Get

func (s TenantScope) Get(d Dimension) string

Get returns the value at one tenant dimension. A successful constructor guarantees every parent of a returned value is present.

func (TenantScope) Kind

func (s TenantScope) Kind() TenantScopeKind

Kind returns the validated hierarchy depth.

type TenantScopeKind

type TenantScopeKind uint8

TenantScopeKind is the closed set of contiguous tenant hierarchy depths.

const (
	TenantScopeInstance TenantScopeKind = iota
	TenantScopeOrg
	TenantScopeProject
	TenantScopeEnvironment
)

type TrustBundle

type TrustBundle struct {
	Name      string  `json:"name"`
	Origin    string  `json:"origin"`
	SPKIPin   string  `json:"spki_pin,omitempty"`
	TLSMode   TLSMode `json:"tls_mode,omitempty"`
	CLISocket string  `json:"cli_socket,omitempty"`
}

TrustBundle is the provisioned-establishment file: the CI path's import format. It is deliberately the same shape as a store entry — trust material is not a secret, it is a binding.

type TrustEntry

type TrustEntry struct {
	// Name is the local reference a pin file or context names. A repository
	// file may name a reference; it can never introduce an origin.
	Name string `json:"name"`
	// Origin is scheme+host+port, canonicalised. A credential is presented
	// only to this origin, and a redirect off it is never followed with one.
	Origin string `json:"origin"`
	// SPKIPin is base64(sha256(SubjectPublicKeyInfo)) of the certificate
	// identity seen at establishment. Empty for explicit system-ca trust or
	// a loopback HTTP origin carried over the same-user Unix socket below.
	SPKIPin string `json:"spki_pin,omitempty"`
	// TLSMode selects HTTPS verification. Omission preserves legacy SPKI trust.
	TLSMode TLSMode `json:"tls_mode,omitempty"`
	// CLISocket carries loopback HTTP without putting credentials on an
	// identity-free TCP listener. Its directory and socket are euid-owned 0700
	// and 0600, and both peers verify the other's kernel-reported UID.
	CLISocket string `json:"cli_socket,omitempty"`
}

TrustEntry records the canonical origin and verification policy captured at establishment.

type TrustStore

type TrustStore struct {
	// contains filtered or unexported fields
}

TrustStore is the on-disk store under the XDG state directory.

func (*TrustStore) Delete

func (s *TrustStore) Delete(name string) error

Delete removes an entry.

func (*TrustStore) Load

func (s *TrustStore) Load() (map[string]TrustEntry, error)

Load reads the store, returning an empty one when it does not exist yet.

func (*TrustStore) Lookup

func (s *TrustStore) Lookup(name string) (TrustEntry, error)

Lookup resolves a reference. A missing reference is ErrUntrusted, never a silent trust-on-first-use.

func (*TrustStore) Put

func (s *TrustStore) Put(e TrustEntry) error

Put records an entry. Rewriting an existing origin or pin is refused: a silent re-pin is indistinguishable from the attack the pin exists to stop, so changing one is a delete-then-establish, done deliberately.

Jump to

Keyboard shortcuts

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