Documentation
¶
Overview ¶
Package cli defines the agentctl command-line surface: the command tree with every subcommand, flag, default and help string, the duration grammar shared by the interval and timeout flags, and the exit codes that the credential-swap outcomes of `claude use` report.
The package parses the command line into typed option structs and dispatches through a Handlers value injected by the caller, so the surface stays reviewable in one place while the command implementations arrive independently.
Help strings contain no backticks, and that is this surface's one deliberate divergence from the published help text: the flag library consumes a backquoted span in a usage string as the flag's placeholder name instead of printing it, so a term the help would quote in backticks is stated plain.
Index ¶
- Constants
- Variables
- func LogLevel(value string) slog.Level
- func ParseDuration(input string) (time.Duration, error)
- func ParseWatchInterval(input string) (time.Duration, error)
- func SignalExitCode(sig os.Signal) int
- type CLI
- type ClaudeAccountsForgetOptions
- type ClaudeAccountsListOptions
- type ClaudeAccountsRelocateOptions
- type ClaudeAccountsRemoveOptions
- type ClaudeAccountsShowOptions
- type ClaudeAccountsUnforgetOptions
- type ClaudeDoctorOptions
- type ClaudeEnvOptions
- type ClaudeExecOptions
- type ClaudeImportOptions
- type ClaudeLoginOptions
- type ClaudeStatusOptions
- type ClaudeUseOptions
- type ClaudeWatchOptions
- type CodexAccountsForgetOptions
- type CodexAccountsListOptions
- type CodexAccountsRefreshOptions
- type CodexAccountsRemoveOptions
- type CodexAccountsSetOptions
- type CodexAccountsShowOptions
- type CodexAccountsUnforgetOptions
- type CodexDoctorOptions
- type CodexImportOptions
- type CodexImportSource
- type CodexLoginOptions
- type CodexStatusOptions
- type CodexWatchOptions
- type DurationParseError
- type DurationParseKind
- type Globals
- type Handlers
- type ImportSource
- type NotImplementedError
- type RefreshMode
- type Shell
Constants ¶
const ( // WatchFloor is the shortest polling interval `watch` accepts, so the // usage API is never polled more often than once every 60 seconds. WatchFloor = 60 * time.Second // WatchDefault is the polling interval `watch` uses when --interval is // not given. WatchDefault = 300 * time.Second // HTTPTimeoutDefault is the per-request HTTP timeout used when // --timeout is not given. HTTPTimeoutDefault = 10 * time.Second )
const ( // SwapExitRefusedA is refusal A: the lock held for the swap was // compromised — its mtime moved underneath the process, or the held // mtime could not be read — or acquisition failed for a reason other // than the store being unreachable, so nothing may be written. SwapExitRefusedA = 10 // SwapExitRefusedC is refusal C: CLAUDE_CODE_OAUTH_TOKEN holds a // non-empty value in the process's own environment, which // short-circuits every credential store. SwapExitRefusedC = 11 // SwapExitRefusedD is refusal D: the encoded credential line, // including its newline, does not fit the 4032-byte keychain stdin // bound. SwapExitRefusedD = 12 // SwapExitRefusedE is refusal E: CLAUDE_SECURESTORAGE_CONFIG_DIR // holds a non-empty value, so this shell names a namespace rather // than the live store — and the pass was asked for the live one. // Reachable only through an undo of a live-target swap, whose target // comes from the audit log and can disagree with the environment. SwapExitRefusedE = 13 // SwapExitRefusedF is refusal F: the outgoing credential cannot be // adopted safely, so the swap would lose it. SwapExitRefusedF = 14 // SwapExitPrecondition means the inherited // CLAUDE_SECURESTORAGE_CONFIG_DIR names no store this registry owns. // Not a lettered refusal: it is decided before the swap begins, so // the JSON report carries reason "not_owned" and no refusal member. SwapExitPrecondition = 15 // SwapExitBusy means another process holds the store's Claude Code // locks and they were not broken. SwapExitBusy = 16 // SwapExitDiscarded means the item changed or became unreadable under // the hold, or the remaining hold budget was too short to write, so // the refreshed credential was thrown away rather than written over a // newer one. SwapExitDiscarded = 17 // SwapExitUnknown means the write timed out and the verifying read // did not settle whether it landed. It means "re-run status", not // "failed". SwapExitUnknown = 18 // SwapExitWriteFailed means the write ran and failed definitely, for // a reason other than a timeout, and the item is demonstrably // untouched. The JSON report carries outcome "failed" and no refusal // member: an ordinary write failure is not a security signal. SwapExitWriteFailed = 19 // SwapExitCancelled means nobody agreed to the swap: the confirmation // was declined, or there was no terminal to ask at and --yes was not // given. --json does not imply --yes. The JSON report carries outcome // "cancelled" and no refusal member. SwapExitCancelled = 20 // SwapExitNeedsRefresh means the incoming account's credential has // expired and its store has migrated into the keychain, so the swap // will not refresh it: the refresh could not be saved back, and a // spent refresh token would strand the account. The JSON report // carries outcome "needs_refresh" and no refusal member. SwapExitNeedsRefresh = 21 // SwapExitAuditRefused means the append-only audit log cannot be // written, so a live-store swap is refused rather than performed // unrecorded. The JSON report carries outcome "refused" with reason // "audit_refused" and no refusal member. SwapExitAuditRefused = 22 // SwapExitLiveUnreachable means the live store could not be resolved: // nothing is at the path the environment names, or the symbolic link // there dangles. The JSON report carries reason "live_unreachable" // and no refusal member. SwapExitLiveUnreachable = 23 // SwapExitLiveItemAbsent means the live keychain item is absent, so // the live store has not migrated and its credential is still in a // plaintext file no swap may touch. Transient and self-healing; the // JSON report carries reason "live_item_absent" and no refusal // member. SwapExitLiveItemAbsent = 24 // SwapExitLiveUndoItemChanged means an undo found the live item // holding a third account's credential — neither the one the undo // would put back nor the one the swap being undone installed. The // JSON report carries reason "live_undo_foreign_login" and no refusal // member. SwapExitLiveUndoItemChanged = 27 // the live item holds: the profile request did not answer, or the // item's access token has expired without an owned audit attribution. // Decided before any lock, prompt or write, so nothing is written. SwapExitIdentityUnavailable = 29 // SwapExitRCNotDisconnected means Remote Control could not be // disconnected before a live swap: the preflight found an unsupported // platform, no TTY to attest at, or a session that stayed connected. // Nothing is written. SwapExitRCNotDisconnected = 30 )
Exit codes for the refusals and outcomes of a `claude use` credential swap. Each one gets its own message and its own exit code, so a script can act on one without parsing English.
The block starts at 10 because 0, 1 and 2 are taken: 0 is a clean run, 1 is a fatal failure, 2 is a run whose output holds at least one degraded row — and a command-line usage error also exits 2, so a swap code of 2 would be ambiguous between "the item changed under the hold" and "you misspelled a flag". Codes 25, 26 and 28 were assigned once, retired, and are never defined again: a script that learned one of them must not see it come back meaning something else.
Refusal B — a secure-storage backend is active or of unknown kind — has no code here on purpose: it is a warning line and exit 0, because no such backend exists in this build.
Variables ¶
var SwapExitCodes = [...]struct { Name string Code int }{ {Name: "refused_a", Code: SwapExitRefusedA}, {Name: "refused_c", Code: SwapExitRefusedC}, {Name: "refused_d", Code: SwapExitRefusedD}, {Name: "refused_e", Code: SwapExitRefusedE}, {Name: "refused_f", Code: SwapExitRefusedF}, {Name: "precondition", Code: SwapExitPrecondition}, {Name: "busy", Code: SwapExitBusy}, {Name: "discarded", Code: SwapExitDiscarded}, {Name: "unknown", Code: SwapExitUnknown}, {Name: "write_failed", Code: SwapExitWriteFailed}, {Name: "cancelled", Code: SwapExitCancelled}, {Name: "needs_refresh", Code: SwapExitNeedsRefresh}, {Name: "audit_refused", Code: SwapExitAuditRefused}, {Name: "live_unreachable", Code: SwapExitLiveUnreachable}, {Name: "live_item_absent", Code: SwapExitLiveItemAbsent}, {Name: "live_undo_item_changed", Code: SwapExitLiveUndoItemChanged}, {Name: "identity_unavailable", Code: SwapExitIdentityUnavailable}, {Name: "remote_control_not_disconnected", Code: SwapExitRCNotDisconnected}, }
SwapExitCodes pairs every swap exit name with its code, for the exhaustiveness and uniqueness checks; nothing in the shipped binary reads the table as a table.
Functions ¶
func LogLevel ¶
LogLevel maps an AGENTCTL_LOG value onto a slog level. The accepted values are exactly debug, info, warn and error; anything else, including an unset variable, falls back to warn so diagnostics stay quiet by default instead of failing the run over a logging knob.
func ParseDuration ¶
ParseDuration parses a duration written as `10s`, `5m`, `2h`, or a bare `300` meaning seconds, with outer whitespace trimmed.
The grammar is a single unsigned integer with at most one unit, so fractions (`1.5s`), negatives (`-5s`), sub-second units (`10ms`) and multi-unit forms (`2h30m`) are all rejected. All arithmetic is checked: a count that does not fit, either as a 64-bit unsigned second count or as a time.Duration, is an overflow error rather than a silently wrapped value.
func ParseWatchInterval ¶
ParseWatchInterval parses an interval argument with ParseDuration and additionally enforces the WatchFloor minimum, naming the floor in the error so the caller learns the limit, not merely that the value was refused.
func SignalExitCode ¶
SignalExitCode returns the process exit status for a run ended by sig: 128 plus the signal's number, the shell convention for a signal death, so TERM exits 143, HUP 129 and INT 130. A value that carries no POSIX signal number maps to 1, the fatal exit, because dying to an unnameable signal is a run that produced nothing useful.
Types ¶
type CLI ¶
type CLI struct {
// contains filtered or unexported fields
}
CLI is the assembled agentctl command tree.
func (*CLI) Dispatched ¶
Dispatched reports whether a subcommand's dispatch began. An error from an execution that never dispatched is a command-line usage error, which exits 2; an error after dispatch follows the application's own exit mapping.
type ClaudeAccountsForgetOptions ¶
type ClaudeAccountsForgetOptions struct {
Service string
}
ClaudeAccountsForgetOptions carries the parsed arguments of `claude accounts forget`.
type ClaudeAccountsListOptions ¶
type ClaudeAccountsListOptions struct {
All bool
}
ClaudeAccountsListOptions carries the parsed flags of `claude accounts list`.
type ClaudeAccountsRelocateOptions ¶
ClaudeAccountsRelocateOptions carries the parsed arguments of `claude accounts relocate`.
type ClaudeAccountsRemoveOptions ¶
ClaudeAccountsRemoveOptions carries the parsed arguments of `claude accounts remove`.
type ClaudeAccountsShowOptions ¶
type ClaudeAccountsShowOptions struct {
ID string
}
ClaudeAccountsShowOptions carries the parsed arguments of `claude accounts show`.
type ClaudeAccountsUnforgetOptions ¶
type ClaudeAccountsUnforgetOptions struct {
Service string
}
ClaudeAccountsUnforgetOptions carries the parsed arguments of `claude accounts unforget`.
type ClaudeDoctorOptions ¶
ClaudeDoctorOptions carries the parsed flags of `claude doctor`.
type ClaudeEnvOptions ¶
type ClaudeEnvOptions struct {
ID string
ClaudeConfigDir string
FreshContext bool
NoMCP bool
Shell Shell
}
ClaudeEnvOptions carries the parsed arguments of `claude env`.
type ClaudeExecOptions ¶
type ClaudeExecOptions struct {
ID string
ClaudeConfigDir string
FreshContext bool
NoMCP bool
Command []string
}
ClaudeExecOptions carries the parsed arguments of `claude exec`.
type ClaudeImportOptions ¶
type ClaudeImportOptions struct {
From ImportSource
ClaudeConfigDirs []string
DryRun bool
}
ClaudeImportOptions carries the parsed flags of `claude import`.
type ClaudeLoginOptions ¶
ClaudeLoginOptions carries the parsed flags of `claude login`.
type ClaudeStatusOptions ¶
type ClaudeStatusOptions struct {
JSON bool
Raw bool
Refresh bool
NoCache bool
All bool
ByIdentity bool
Accounts []string
Timeout time.Duration
}
ClaudeStatusOptions carries the parsed flags of `claude status`.
type ClaudeUseOptions ¶
type ClaudeUseOptions struct {
ID string
Live bool
RestartRemoteControl bool
NewOnly bool
Undo bool
Forget string
ClaudeConfigDir string
FreshContext bool
NoMCP bool
Yes bool
JSON bool
}
ClaudeUseOptions carries the parsed flags of `claude use`. Three shapes share this struct, distinguished by which fields are set: an isolated session for one account, an undo of the most recent live swap, and a forget of an earlier session directory.
type ClaudeWatchOptions ¶
ClaudeWatchOptions carries the parsed flags of `claude watch`.
type CodexAccountsForgetOptions ¶
type CodexAccountsForgetOptions struct {
ID string
}
CodexAccountsForgetOptions carries the parsed arguments of `codex accounts forget`.
type CodexAccountsListOptions ¶
type CodexAccountsListOptions struct {
All bool
}
CodexAccountsListOptions carries the parsed flags of `codex accounts list`.
type CodexAccountsRefreshOptions ¶
CodexAccountsRefreshOptions carries the parsed arguments of `codex accounts refresh`.
type CodexAccountsRemoveOptions ¶
CodexAccountsRemoveOptions carries the parsed arguments of `codex accounts remove`.
type CodexAccountsSetOptions ¶
type CodexAccountsSetOptions struct {
ID string
Refresh RefreshMode
}
CodexAccountsSetOptions carries the parsed arguments of `codex accounts set`.
type CodexAccountsShowOptions ¶
type CodexAccountsShowOptions struct {
ID string
}
CodexAccountsShowOptions carries the parsed arguments of `codex accounts show`.
type CodexAccountsUnforgetOptions ¶
type CodexAccountsUnforgetOptions struct {
ID string
}
CodexAccountsUnforgetOptions carries the parsed arguments of `codex accounts unforget`.
type CodexDoctorOptions ¶
type CodexDoctorOptions struct {
JSON bool
}
CodexDoctorOptions carries the parsed flags of `codex doctor`.
type CodexImportOptions ¶
type CodexImportOptions struct {
From CodexImportSource
CodexHome string
DryRun bool
}
CodexImportOptions carries the parsed flags of `codex import`.
type CodexImportSource ¶
type CodexImportSource string
CodexImportSource names where `codex import` reads accounts from.
const CodexImportSourceCodexHome CodexImportSource = "codex-home"
CodexImportSourceCodexHome reads a Codex home directory holding a credential file.
type CodexLoginOptions ¶
CodexLoginOptions carries the parsed flags of `codex login`.
type CodexStatusOptions ¶
type CodexStatusOptions struct {
JSON bool
Raw bool
Refresh bool
NoCache bool
All bool
Accounts []string
Timeout time.Duration
}
CodexStatusOptions carries the parsed flags of `codex status`.
type CodexWatchOptions ¶
CodexWatchOptions carries the parsed flags of `codex watch`.
type DurationParseError ¶
type DurationParseError struct {
// Kind classifies the failure.
Kind DurationParseKind
// Input is the trimmed argument, for context.
Input string
// Unit is the unrecognised suffix when Kind is DurationUnknownUnit.
Unit string
}
DurationParseError reports why a duration argument could not be parsed.
func (*DurationParseError) Error ¶
func (e *DurationParseError) Error() string
Error renders the failure with the input it refused, so the message can stand alone on stderr.
type DurationParseKind ¶
type DurationParseKind int
DurationParseKind classifies why a duration argument was rejected.
const ( // DurationEmpty means the argument was empty or entirely whitespace. DurationEmpty DurationParseKind = iota // DurationNoDigits means the argument did not start with a digit. DurationNoDigits // DurationOverflow means the numeric part did not fit, or the unit // conversion did not fit. DurationOverflow // DurationUnknownUnit means the unit suffix is not one the parser // knows. DurationUnknownUnit )
type Globals ¶
type Globals struct {
// ConfigDir is the configuration directory named by --config-dir, or
// by AGENTCTL_CONFIG_DIR when the flag is not given. Empty when
// neither is set, which lets the configuration package apply its own
// resolution order instead of baking a default in here.
ConfigDir string
}
Globals carries the options every subcommand shares.
type Handlers ¶
type Handlers struct {
ClaudeStatus func(ctx context.Context, globals Globals, opts ClaudeStatusOptions) error
ClaudeWatch func(ctx context.Context, globals Globals, opts ClaudeWatchOptions) error
ClaudeLogin func(ctx context.Context, globals Globals, opts ClaudeLoginOptions) error
ClaudeAccountsList func(ctx context.Context, globals Globals, opts ClaudeAccountsListOptions) error
ClaudeAccountsShow func(ctx context.Context, globals Globals, opts ClaudeAccountsShowOptions) error
ClaudeAccountsRemove func(ctx context.Context, globals Globals, opts ClaudeAccountsRemoveOptions) error
ClaudeAccountsRelocate func(ctx context.Context, globals Globals, opts ClaudeAccountsRelocateOptions) error
ClaudeAccountsForget func(ctx context.Context, globals Globals, opts ClaudeAccountsForgetOptions) error
ClaudeAccountsUnforget func(ctx context.Context, globals Globals, opts ClaudeAccountsUnforgetOptions) error
ClaudeImport func(ctx context.Context, globals Globals, opts ClaudeImportOptions) error
ClaudeDoctor func(ctx context.Context, globals Globals, opts ClaudeDoctorOptions) error
ClaudeUse func(ctx context.Context, globals Globals, opts ClaudeUseOptions) error
ClaudeExec func(ctx context.Context, globals Globals, opts ClaudeExecOptions) error
ClaudeEnv func(ctx context.Context, globals Globals, opts ClaudeEnvOptions) error
CodexStatus func(ctx context.Context, globals Globals, opts CodexStatusOptions) error
CodexWatch func(ctx context.Context, globals Globals, opts CodexWatchOptions) error
CodexLogin func(ctx context.Context, globals Globals, opts CodexLoginOptions) error
CodexAccountsList func(ctx context.Context, globals Globals, opts CodexAccountsListOptions) error
CodexAccountsShow func(ctx context.Context, globals Globals, opts CodexAccountsShowOptions) error
CodexAccountsRemove func(ctx context.Context, globals Globals, opts CodexAccountsRemoveOptions) error
CodexAccountsForget func(ctx context.Context, globals Globals, opts CodexAccountsForgetOptions) error
CodexAccountsUnforget func(ctx context.Context, globals Globals, opts CodexAccountsUnforgetOptions) error
CodexAccountsSet func(ctx context.Context, globals Globals, opts CodexAccountsSetOptions) error
CodexAccountsRefresh func(ctx context.Context, globals Globals, opts CodexAccountsRefreshOptions) error
CodexImport func(ctx context.Context, globals Globals, opts CodexImportOptions) error
CodexDoctor func(ctx context.Context, globals Globals, opts CodexDoctorOptions) error
}
Handlers holds one function per subcommand. The command tree parses the command line into the typed options struct and calls the matching field; a nil field makes the subcommand fail with a NotImplementedError.
type ImportSource ¶
type ImportSource string
ImportSource names where `claude import` reads accounts from. One source, still spelled as a value rather than as a bare flag: a command line that already says which source it read does not change shape when a second one arrives.
const ImportSourceKeychain ImportSource = "keychain"
ImportSourceKeychain reads Claude Code credential services discovered in the login keychain.
type NotImplementedError ¶
type NotImplementedError struct {
// Command is the space-joined command path, such as "claude status".
Command string
}
NotImplementedError reports a subcommand that parsed but has no handler wired in. A nil Handlers field yields this error on purpose: the command surface is reviewed as one piece while the implementations arrive independently, and an unwired command must fail loudly with exit status 1 rather than exit 0 having done nothing.
func (*NotImplementedError) Error ¶
func (e *NotImplementedError) Error() string
Error names the command so the caller's "agentctl: " prefix completes the sentence.
type RefreshMode ¶
type RefreshMode string
RefreshMode says whether agentctl refreshes an owned Codex account on its own. It is the command line's spelling of the registry's refresh policy, kept separate so the stored vocabulary and the flag's accepted values can change independently.
const ( // RefreshModeAuto refreshes when the access token is expired or // rejected. RefreshModeAuto RefreshMode = "auto" // RefreshModeNever never sends a refresh token. RefreshModeNever RefreshMode = "never" )