cmdcore

package
v0.32.1 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

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

View Source
const APIKeyPrefix = "jak_"

APIKeyPrefix is the prefix the control-plane assigns to agent API keys.

View Source
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

View Source
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.

View Source
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

func APIKeyLabel(key string) string

APIKeyLabel masks a stored key, or reports its absence.

func AddGrouped

func AddGrouped(root *cobra.Command, groupID string, cmd *cobra.Command)

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 DotFail

func DotFail() string

DotFail is the status glyph for a failed item.

func DotOK

func DotOK() string

DotOK is the status glyph for a present/healthy item (filled).

func DotWarn

func DotWarn() string

DotWarn is the status glyph for a degraded/warning item (filled, amber).

func IdentityLabel

func IdentityLabel(me map[string]any) string

IdentityLabel picks the most descriptive field from a /me response.

func InvocationErrorMapper

func InvocationErrorMapper(root *cobra.Command, err error) error

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

func JSONOrPretty(cmd *cobra.Command, jsonFlag bool) bool

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

func MaskAPIKey(key string) string

MaskAPIKey renders a key as its prefix plus the last 4 chars, hiding the body.

func NewBaseRoot

func NewBaseRoot(app *App, binary string) *cobra.Command

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

func NewRegisterCmd(app *App) *cobra.Command

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

func RunRoot(build func(*App) *cobra.Command) int

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

func TokenStatus(st theme.Styles, t *auth.TokenSet) (label, dot string)

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 ValueOr

func ValueOr(v, fallback string) string

ValueOr returns v, or fallback when v is empty/whitespace.

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

func WantsInteractive(cmd *cobra.Command, yes bool, fieldFlags ...string) bool

WantsInteractive also requires a real terminal (so pipes/CI stay non-interactive).

func WriteJSON

func WriteJSON(w io.Writer, v any) error

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

func WriteList(w io.Writer, data any, nextCursor string, meta map[string]any) error

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

func (a *App) BrandHeader(ctx context.Context, baseURLFlag, cliVersion string) string

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

func (a *App) PollCadence() (initial, maxDelay, step time.Duration)

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

func (a *App) ResolveBaseURL(baseURLFlag string) (string, error)

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

func (a *App) SetPollCadence(initial, maxDelay, step time.Duration)

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.

Jump to

Keyboard shortcuts

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