Documentation
¶
Overview ¶
Package cmdcore holds the shared base of the Jentic CLI command trees: the App dependency container plus every helper and method used by BOTH the `jentic` (api) and `jenticctl` (ctl) trees. It deliberately never imports the installer/lifecycle packages (internal/install, internal/proc, internal/update) so the `jentic` binary — which links only cmdcore + internal/cli/api — stays free of them.
Index ¶
- Constants
- Variables
- func APIKeyLabel(key string) string
- func AddGrouped(root *cobra.Command, groupID string, cmd *cobra.Command)
- func DefaultContainer() *core.AppContainer
- func DotDown() string
- func DotFail() string
- func DotOK() string
- func DotWarn() string
- func IdentityLabel(me map[string]any) string
- func InvocationErrorMapper(root *cobra.Command, err error) error
- func JSONOrPretty(cmd *cobra.Command, jsonFlag bool) bool
- func MaskAPIKey(key string) string
- func NewBaseRoot(app *App, binary string) *cobra.Command
- func NewRegisterCmd(app *App) *cobra.Command
- func RunRoot(build func(*App) *cobra.Command) int
- func StdoutIsTerminal() bool
- func TokenStatus(st theme.Styles, t *auth.TokenSet) (label, dot string)
- func TreeBuilder(build func(*App) *cobra.Command) core.TreeBuilder
- func ValueOr(v, fallback string) string
- func Version() string
- func VersionMeta() (v, c, d string)
- func WantsInteractive(cmd *cobra.Command, yes bool, fieldFlags ...string) bool
- func WriteJSON(w io.Writer, v any) error
- func WriteList(w io.Writer, data any, nextCursor string, meta map[string]any) error
- type App
- func (a *App) BrandHeader(ctx context.Context, baseURLFlag, cliVersion string) string
- func (a *App) PollCadence() (initial, maxDelay, step time.Duration)
- func (a *App) RegisterActive(ctx context.Context, st *clictx.ActiveState, clientName, brokerURL string, ...) error
- func (a *App) RegisterSetup(ctx context.Context, vals SetupValues, timeout time.Duration, ...) (SetupValues, error)
- func (a *App) ResolveBaseURL(baseURLFlag string) (string, error)
- func (a *App) SetPollCadence(initial, maxDelay, step time.Duration)
- type ExitCodeError
- type IdentityOptions
- type SetupValues
Constants ¶
const APIKeyPrefix = "jak_"
APIKeyPrefix is the prefix the control-plane assigns to agent API keys.
const GateAnnotation = "migrate-gate"
GateAnnotation marks a ROOT command whose tree is subject to the migrate gate. Only the `jentic` (api) tree sets it: `jenticctl` is the operator lifecycle tool — its install/start/doctor surface owns ~/.jentic as the INSTALL root and must keep working on a machine that has never had (and will never have) an agent identity to migrate.
Variables ¶
var ErrOnboardCancelled = errors.New("onboarding cancelled")
ErrOnboardCancelled signals a user-aborted interactive onboarding (Esc in the form). Callers treat it as a clean no-op exit (matching the legacy arm's "Cancelled." + nil contract) — it exists so composed flows (setup) can stop their remaining steps without inventing a fake success.
var SetupFieldFlags = append(append([]string{}, registerFieldFlags...),
"operator", "all", "scope", "skip-skill")
SetupFieldFlags extend the register set with the skill-target and activation flags setup adds, so a flag-driven run (e.g. `--operator claude`) is not treated as interactive. Exported because setup now lives in the localagentcmd package (ARCH-1) and needs this set.
Functions ¶
func APIKeyLabel ¶
APIKeyLabel masks a stored key, or reports its absence.
func AddGrouped ¶
AddGrouped attaches cmd to root under the given group ID.
func DefaultContainer ¶
func DefaultContainer() *core.AppContainer
DefaultContainer builds the default injection container (no extra commands). A downstream package builds its own core.AppContainer{ExtraCommands: ...} and calls core.NewRootCmd directly from its own main.go.
func DotDown ¶
func DotDown() string
DotDown is the status glyph for an absent/offline item (hollow).
func DotWarn ¶
func DotWarn() string
DotWarn is the status glyph for a degraded/warning item (filled, amber).
func IdentityLabel ¶
IdentityLabel picks the most descriptive field from a /me response.
func InvocationErrorMapper ¶
InvocationErrorMapper is the pkg/core error mapper (AGT-20): it converts a cobra-native invocation error — unknown command/flag, bad positional-arg count, or a missing required flag — into a typed, already-reported *ux.CodedError so an agent gets a closed error_code + stderr envelope instead of the bare "error: …" text cobra hands back. RunRoot threads it into core.RunTree; the golden runner uses it too so the recorded contract matches the shipped path exactly.
Why here and not in decorateCodedErrors: those errors are produced by cobra's own arg/flag parsing and returned straight from Execute, so they never pass through a command's RunE (where decorateCodedErrors wraps). The mapper is the one hook that sees them. It renders through an Audience itself and marks the error reported, so pkg/core (which must not import ux) only ever sees an ExitCoder.
An error that is ALREADY an *ux.CodedError (e.g. our own SetFlagErrorFunc output, or a coded error returned by an Args validator such as exactNamedArgs) is passed through untouched — but if it has not yet been rendered (IsReported() is false), it is reported here first so its envelope reaches the user/agent exactly once. Args-validator errors take this path because they are returned from Execute without ever passing through a RunE wrapper.
func JSONOrPretty ¶
JSONOrPretty returns true when the caller should emit JSON output:
- --json was explicitly set, or
- the resolved mode is a fenced machine mode (agent/service-account), or
- mode is EXPLICITLY human → pretty, even piped (UX-5), or
- otherwise: stdout is not a TTY (agent friendly by default).
The machine-mode rung (AGT-2) exists because agent harnesses often run the CLI on a PTY: JENTIC_MODE=agent must force machine output even on a terminal, exactly as it forces fencing and no-color. Any non-human mode counts — an unknown mode fails closed to AgentUX at audience construction, so it fails closed to JSON here too.
The explicit-human rung (UX-5) is the inverse: --mode human (or JENTIC_MODE=human / a persisted human context) says "render for a person", so piping to `less`/`tee` keeps the pretty report — previously there was no way to force it in a pipe. Only DEFAULT human (nothing set anywhere) falls through to the TTY heuristic.
func MaskAPIKey ¶
MaskAPIKey renders a key as its prefix plus the last 4 chars, hiding the body.
func NewBaseRoot ¶
NewBaseRoot builds a root command with the shared wiring (banner, help renderer, version template, command-group ordering) for the given binary name. The two binary-specific builders (api.newAPIRootCmd, ctlcmd.newCtlRootCmd) add their own command sets and branding on top.
func NewRegisterCmd ¶
NewRegisterCmd builds the `register` command — the single onboarding front door (register.go): with an active context it registers that context's identity; on a fresh machine --url (or the interactive prompt) creates the environment + identity + context trio, activates it, and registers. Shared by both trees via cmdcore.
func RunRoot ¶
RunRoot builds the root command via the given tree builder and runs it through core.Run (shared signal-context + exit-code semantics). The tree packages call this from their Execute functions. It threads mapInvocationError so cobra-native parse errors (unknown command/flag, bad arg count) become coded envelopes (AGT-20) rather than raw "error: …" lines.
func StdoutIsTerminal ¶
func StdoutIsTerminal() bool
StdoutIsTerminal is the exported TTY probe for command trees outside cmdcore (e.g. jenticctl's doctor) that need the same pretty-vs-machine default without referencing os.Stdout directly (the 1F boundary confines that to the render layer and this file).
func TokenStatus ¶
TokenStatus summarizes a cached token as a human label plus a status dot, tinted from the resolved Styles.
func TreeBuilder ¶
func TreeBuilder(build func(*App) *cobra.Command) core.TreeBuilder
TreeBuilder adapts an internal (*App)-based command-tree builder to a core.TreeBuilder (which operates on the exported *core.AppContainer). Path resolution can fail; surface it as a command that FAILS CLOSED rather than threading an error out-of-band. This is the single definition shared by the built-in binaries (RunRoot) and the exported downstream builders (pkg/clitree).
The failure root uses PersistentPreRunE so the error also fires for any commands appended via AppContainer.ExtraCommands (which are attached to this root by core.NewRootCmd) — otherwise a downstream's extra command would run against an unresolved container and could silently succeed. SilenceUsage/ SilenceErrors mirror the real roots so the error prints once, without usage.
func Version ¶
func Version() string
Version returns the build-time version string (the -ldflags-stamped value, or "dev"). The tree packages read it through this accessor since the underlying var is unexported and lives only here.
func VersionMeta ¶
func VersionMeta() (v, c, d string)
VersionMeta returns the full build-time version metadata (version, commit, date). The tree packages alias these into local unexported vars so relocated code keeps referencing `version`/`commit`/`date` verbatim.
func WantsInteractive ¶
WantsInteractive also requires a real terminal (so pipes/CI stay non-interactive).
func WriteJSON ¶
WriteJSON encodes v as indented JSON to w, scrubbed by the byte-level redaction backstop (SEC-1). These legacy render paths write server-echoed payloads (execute envelopes, provider records, access responses) straight to stdout, OUTSIDE the ux.safeMarshal funnel — without this pass a secret embedded in an upstream response would reach a machine parser verbatim. The backstop deliberately does not re-shape the document (no key re-ordering), so golden output stays byte-stable except for redacted values. The strangler- fig cutover to Audience.Render (which applies the full three-layer funnel) retires these call sites over time.
func WriteList ¶
WriteList emits the canonical, versioned data-plane list envelope (AGT-1/ AGT-5): {schema_version, data, has_more, next_cursor[, meta]}. Every list command (search, catalog, apis, endpoints, access list, ...) routes through this so an agent sees ONE collection key (`data`), ONE pagination shape, and a stamped schema_version everywhere — replacing the ad-hoc `{data: …}` / `{endpoints: …}` maps that carried no version. data must be a non-nil slice so an empty result serialises as [] (never null). nextCursor is empty on the last/only page. meta carries command-specific summary fields (may be nil).
Types ¶
type App ¶
type App struct {
// Paths resolves every filesystem location the CLI owns.
Paths config.Paths
// Out and Err are the standard output streams (overridable in tests).
Out io.Writer
Err io.Writer
// DetectEnv overrides the skill operator-detection probe (tests only);
// nil means the real OS probe. Injected here rather than a package var so
// the command tree stays constructor-built with no global state.
DetectEnv func() (skillgen.DetectEnv, error)
// NudgeLatestTag and NewerVersionAvailable are the update-nudge seams. The
// ctl tree (which legitimately depends on internal/update) injects them so
// cmdcore itself never imports the installer/lifecycle packages — keeping the
// `jentic` (api) binary free of them. When either is nil (the api tree, and
// tests that don't opt in) the update nudge is a no-op. NudgeLatestTag
// resolves the latest release tag; NewerVersionAvailable reports whether
// latest is newer than installed.
NudgeLatestTag func(ctx context.Context, repo, token string) (string, error)
NewerVersionAvailable func(installed, latest string) bool
// NudgeCommand is the recovery command the update nudge tells the user to
// run. The ctl tree sets `jenticctl update`; the api (`jentic`) tree sets a
// jentic-appropriate command (a `jentic`-only user may not have jenticctl).
// Empty falls back to `jenticctl update` for backward compatibility.
NudgeCommand string
// ProbeServer overrides the interactive help-header server-version probe
// (QA-4). nil means the real serverinfo.Probe. It is a seam so a test can
// assert the header path never blocks — and so a caller could disable the
// probe entirely — without reaching the network. The real probe is already
// bounded by serverinfo.DefaultTimeout; this makes that bound testable and
// overridable rather than implicit.
ProbeServer func(baseURL string) serverinfo.Info
// contains filtered or unexported fields
}
App is the dependency container threaded into every command constructor. It holds the resolved filesystem paths and the output streams, so commands carry no package-global state and are constructible (and testable) in isolation.
App is the internal wiring derived from the exported core.AppContainer (see NewApp): the container carries the injectable seams a downstream package can override, while App carries the resolved paths every subcommand needs.
func NewApp ¶
func NewApp(deps *core.AppContainer) (*App, error)
NewApp derives the internal App (resolved paths + streams) from the injected container. Paths are resolved here — the exported core package stays free of the internal config package, keeping the dependency edge internal/cli/cmdcore → pkg/core one-directional.
func (*App) BrandHeader ¶
BrandHeader renders the gradient wordmark with a right-aligned version panel (CLI version + probed server version). The panel is only drawn for an interactive terminal — we need its width and want to avoid a network probe when output is piped — otherwise it falls back to the plain logo. baseURLFlag overrides the configured control-plane URL for the server probe. ctx carries the resolved theme so the wordmark and panel re-tint under --theme light.
func (*App) PollCadence ¶
PollCadence returns the App's approval-poll cadence, falling back to the production defaults for any field left zero. Exported so sibling command packages (api, ctlcmd) that embed App can share the exact same schedule.
func (*App) RegisterActive ¶
func (a *App) RegisterActive(ctx context.Context, st *clictx.ActiveState, clientName, brokerURL string, timeout time.Duration, force bool) error
RegisterActive registers the ACTIVE context's identity with its environment — the same store `jentic identity register` writes, plus the human-facing approval wait. A non-empty brokerURL is applied to the active environment first (fill-if-empty; a different existing broker refuses without force), so `jentic register --broker-url …` is the one-line way to complete a remote environment that was onboarded without one.
func (*App) RegisterSetup ¶
func (a *App) RegisterSetup(ctx context.Context, vals SetupValues, timeout time.Duration, force, interactive bool) (SetupValues, error)
RegisterSetup is the fresh-machine onboarding: create the environment + identity + context trio (idempotently — re-running reuses what exists), activate it, then fall into the shared register-and-wait flow. It returns the resolved values so composed flows (setup) can reuse them (e.g. the install URL for skill templating).
func (*App) ResolveBaseURL ¶
ResolveBaseURL loads the install config once and resolves the control-plane base URL, honouring an explicit flag value over the recorded install URL and the default. It reads the INSTALL record (~/.jentic/config.yaml base_url — where `jenticctl install` recorded the local deployment), not the identity store: the ctl tree's status/doctor probes target the local install, while identity resolution is the context's job.
func (*App) SetPollCadence ¶
SetPollCadence overrides the approval-poll schedule (tests use it to make the pending-path cases near-instant). Any zero argument keeps the default for that field.
type ExitCodeError ¶
type ExitCodeError struct{ Code int }
ExitCodeError carries a wrapped child's non-zero exit code up to Execute so the CLI mirrors it without printing an "error:" line.
func NewExitCodeError ¶
func NewExitCodeError(code int) *ExitCodeError
NewExitCodeError constructs an ExitCodeError for the given child exit code.
func (*ExitCodeError) Error ¶
func (e *ExitCodeError) Error() string
func (*ExitCodeError) ExitCode ¶
func (e *ExitCodeError) ExitCode() int
ExitCode satisfies core.ExitCoder so core.Run mirrors a wrapped child's exit code verbatim.
type IdentityOptions ¶
type IdentityOptions struct {
BaseURL string
}
IdentityOptions carries the --base-url override shared by the jenticctl install-facing commands (status/doctor): it points their server probes at a non-default control plane. Identity selection is NOT a flag anymore — the jentic tree acts on the active context, and the ctl tree only reads the context read-only for display.
func (*IdentityOptions) Bind ¶
func (o *IdentityOptions) Bind(cmd *cobra.Command)
Bind registers the --base-url flag onto cmd.
type SetupValues ¶
type SetupValues struct {
URL string // control-plane URL (becomes the environment's base_url)
Env string // environment name ("" -> derived from the URL host)
Name string // identity + client name ("" -> derived from the hostname)
BrokerURL string // broker URL ("" -> loopback seed, or unset for remote — never derived)
}
SetupValues are the things the fresh-machine arm must learn: where the install lives, what to call this agent, and (for a remote install) where the broker lives. Everything else is derived. Exported (with exported fields) because setup now lives in the localagentcmd package (ARCH-1) and constructs this to drive onboarding.