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
- Variables
- func CanonicalOrigin(raw string) (string, error)
- func CheckRevision(meta apigen.Meta, operationID string) error
- func CommandPath(args []string) []string
- func Help(w io.Writer, path []string) bool
- func HelpFromText(w io.Writer, text string, path []string) bool
- func HelpRequested(args []string) bool
- func IsHelpFlag(arg string) bool
- func NotifyUpdate(ctx context.Context, ios IO) bool
- func OpenBrowser(target string) error
- func ParseVerbosity(args []string) ([]string, int)
- func Render(w io.Writer, format Format, t Table) error
- func Report(stderr io.Writer, err error) int
- func Run(ctx context.Context, io IO, args []string) int
- func RunUpgrade(ctx context.Context, ios IO, args []string) error
- func SPKIFingerprint(cert *x509.Certificate) string
- func Usage(w io.Writer)
- func VerbosityHelp() string
- func WithVerbosityArguments(args []string, level int) []string
- type AuthArtifact
- type AuthKind
- type AuthKinds
- type AuthOperation
- type BinaryUpdater
- type Client
- type Context
- type CredentialRef
- type DeveloperCredentialArtifact
- type Dimension
- type Env
- type Error
- type Flags
- type Format
- type HumanSession
- type IO
- type MachineCredential
- type PinFile
- type RateLimitedError
- type Resolved
- type SessionArtifact
- type Source
- type State
- func (s *State) Contexts() (map[string]Context, error)
- func (s *State) DeleteContext(name string) error
- func (s *State) DeleteSession(instance string) error
- func (s *State) DeveloperCredentials() (map[string]DeveloperCredentialArtifact, error)
- func (s *State) Dir() string
- func (s *State) PutContext(c Context) error
- func (s *State) PutDeveloperCredential(c DeveloperCredentialArtifact) error
- func (s *State) PutSession(a SessionArtifact) error
- func (s *State) Sessions() (map[string]SessionArtifact, error)
- func (s *State) Trust() *TrustStore
- type TLSMode
- type Table
- type TenantScope
- type TenantScopeKind
- type TrustBundle
- type TrustEntry
- type TrustStore
Constants ¶
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 // 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.
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.
const ( TLSModeSPKI = "spki" TLSModeSystemCA = "system-ca" )
const PinFileName = ".hikyo.json"
PinFileName is the file the CLI looks for, walking up from the working directory.
Variables ¶
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.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
HelpFromText is Help over an arbitrary usage text laid out like usageText.
func HelpRequested ¶
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 ¶
IsHelpFlag reports whether one argument asks for help.
func NotifyUpdate ¶
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 ¶
OpenBrowser launches the platform browser for the CLI front channel.
func ParseVerbosity ¶
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 Report ¶
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 RunUpgrade ¶
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 ¶
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 ¶
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.
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) DoStatus ¶
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.
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 Env ¶
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 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.
type RateLimitedError ¶
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 ¶
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.
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 ¶
NewState resolves the state directory: $HIKYO_STATE_DIR, else $XDG_STATE_HOME/hikyo, else ~/.local/state/hikyo (%LocalAppData%\hikyo on Windows).
func (*State) DeleteContext ¶
DeleteContext removes a context.
func (*State) DeleteSession ¶
DeleteSession forgets an artifact.
func (*State) DeveloperCredentials ¶
func (s *State) DeveloperCredentials() (map[string]DeveloperCredentialArtifact, error)
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.
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 ¶
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) 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.
Source Files
¶
- access.go
- access_requests.go
- access_rules.go
- account_password.go
- adapters.go
- approval.go
- auth_artifact.go
- cli_reauth.go
- client.go
- clipboard_transaction.go
- clipboard_unsupported.go
- compose.go
- compose_doctor.go
- compose_render.go
- compose_run.go
- compose_stamp_state.go
- compose_sync.go
- definitions.go
- developer_credentials.go
- developer_state.go
- doctor.go
- doctor_evidence.go
- dynamic.go
- exec_unix.go
- exit.go
- federation.go
- file_sync.go
- file_targets.go
- fileowner_unix.go
- help.go
- hierarchy.go
- identities.go
- import_loop.go
- import_wizard.go
- importer.go
- keys.go
- login_socket.go
- login_socket_unix.go
- login_transport.go
- machine_export.go
- oauth2_provider.go
- output.go
- parameters.go
- pins.go
- pki.go
- project_capacity.go
- provider.go
- reencrypt.go
- registration.go
- remotes.go
- resolve.go
- retention.go
- reveal_window.go
- revisions.go
- rotation.go
- saml_sp_key.go
- scan.go
- scanlocal.go
- scim.go
- self_config.go
- ssh.go
- state.go
- transit.go
- trust.go
- trust_file_unix.go
- update.go
- values.go
- verbosity.go
- verbs.go