Documentation
¶
Index ¶
- Constants
- Variables
- func AutoTheme(out *os.File) *clibtheme.Theme
- func CredentialIdentitiesDiffer(a, b SecretRef) bool
- func DefaultPath() string
- func DefaultQueriesPath() string
- func DefaultTheme() *clibtheme.Theme
- func GatewayBaseURL(cloudID string) string
- func IsAutoTheme(name string) bool
- func KeyChoices(key string) []string
- func LoadQueries(dir string) (map[string]Query, error)
- func NormalizeBaseURL(raw string) string
- func ReadSecret(raw string) (string, error)
- func RedactSecret(secret string) string
- func ResolveCredential(ctx context.Context, store CredentialStore, ref SecretRef) (string, error)
- func RevokeProfileCredential(ctx context.Context, store CredentialStore, ref SecretRef) (removed bool, note string, err error)
- func SanitizeCredentialError(err error) string
- func Save(path string, cfg *Config) error
- func StoreCredentialTransactionally(ctx context.Context, store CredentialStore, ref SecretRef, secret string, ...) error
- func StoredCredential(ctx context.Context, store CredentialStore, ref SecretRef) (string, error)
- func ThemeForName(name string) *clibtheme.Theme
- func ValidateBaseURL(raw string) error
- func ValidateCloudID(id string) error
- func ValidateProfileName(name string) error
- func ValidateThemeName(name string) error
- type AuthStatus
- type AuthType
- type CleanupFailure
- type CleanupNote
- type Config
- type CredentialError
- type CredentialMigration
- type CredentialStore
- type ErrorCode
- type ErrorContext
- type ErrorType
- type FileCredentialStore
- type KeyDesc
- type KeyringStore
- type MemoryCredentialStore
- type MigrationReport
- type OnePasswordStore
- type Option
- type Profile
- type ProfileIncompleteError
- type ProfileNotDefinedError
- type Query
- type SecretBackend
- type SecretRef
- type TUI
- type TUISection
- type Theme
- type UpstreamProvider
Constants ¶
const ( // DefaultRefreshIntervalSeconds is the per-profile polling default (watch // commands and the like). The dashboard's cadence is the separate // DefaultTUIRefreshSeconds below. DefaultRefreshIntervalSeconds = 30 // DefaultTUIRefreshSeconds is the dashboard's auto-refresh cadence; the // TUI refetches every section this often unless the user is mid-action. DefaultTUIRefreshSeconds = 60 DefaultTimeoutSeconds = 30 DefaultWorkdaySeconds = 28800 )
const AtlassianGatewayBaseURL = "https://api.atlassian.com"
AtlassianGatewayBaseURL is the Atlassian API gateway root. Scoped (granular) API tokens are rejected by the site host and only work when REST calls are addressed through this gateway with the site's cloudId in the path. Classic API tokens hit the site host directly and never touch the gateway.
const EnvThemeName = "JIRA_THEME"
EnvThemeName overrides the default theme process-wide. It matches the prefix set via clibtheme.SetEnvPrefix("JIRA") at help-renderer setup, so the same variable drives help, plain output, and the TUI.
const TestCredentialStoreDirEnv = "JIRA_TEST_CREDENTIAL_STORE_DIR"
TestCredentialStoreDirEnv enables a subprocess-visible credential store for tests. Production code never sets it; when present, command construction can use FileCredentialStore instead of probing the developer's OS keyring.
const ThemeNameAuto = "auto"
ThemeNameAuto selects clib's light or dark theme by detecting the terminal background, so hash-based entity colors stay readable on either surface. It is opt-in, never the default: background detection misfires silently under tmux, SSH, and container exec — all common developer environments — and a wrong guess is actively worse than the fixed palette, so the default stays dark until detection has a release cycle of field reports. A user whose terminal is misdetected pins "light" or "dark" instead.
Variables ¶
var ErrCredentialEmpty = errors.New("credential is empty")
ErrCredentialEmpty reports that an explicitly supplied credential was empty after delimiter trimming. An empty credential is rejected rather than stored, so a blank line never silently becomes a saved secret. ReadSecret returns a CredentialError wrapping this sentinel, so callers can recover either the typed error or this value.
var ErrCredentialNotFound = errors.New("credential not found")
var ErrProfileNotDefined = errors.New("profile is not defined")
ErrProfileNotDefined is returned when a requested profile, or the configured default profile, names no profile defined in the config. Callers test for it with errors.Is.
var ThemeNameValues = append([]string{ThemeNameAuto}, clibtheme.Names()...)
ThemeNameValues advertises the selectable theme names: the opt-in "auto" plus clib's built-in presets, sourced from clibtheme.Names so the list always matches the dependency. Names resolve straight through clib — there is no jira-cli-specific aliasing.
Functions ¶
func AutoTheme ¶ added in v0.4.0
AutoTheme resolves the opt-in "auto" theme: clib's light or dark theme chosen by the terminal background of out (the stream styled output goes to). The JIRA_THEME override still wins, matching every other resolution path. When there is nothing to detect — out is not a terminal, e.g. --color=always piped into a pager — it falls back to dark: dark matches the convention of every major ANSI CLI (git, ripgrep, bat) and the dark-terminal skew of users who deliberately force color through a pipe.
func CredentialIdentitiesDiffer ¶
CredentialIdentitiesDiffer reports whether two SecretRefs address different credential storage. It compares the secret backend, the keyring key (site host + profile) and, for the 1Password backend, the account, vault, and item. When an auth login re-points a profile — a new site, a backend switch, a different 1Password account, vault, or item — the re-derived identity differs and the credential under the old identity must be revoked.
func DefaultPath ¶
func DefaultPath() string
func DefaultQueriesPath ¶ added in v0.6.2
func DefaultQueriesPath() string
DefaultQueriesPath returns the default saved-queries directory, resolved under the same OS-native config root as DefaultPath so the two stay consistent across platforms (~/.config on Unix, %AppData% on Windows).
func DefaultTheme ¶ added in v0.4.0
DefaultTheme resolves the process default theme: the JIRA_THEME override when set and valid, otherwise the dark built-in. This restores clib v0.4.15's Default(), which the v0.5 upgrade removed, keeping the documented JIRA_THEME override working for plain output, help, and the TUI.
func GatewayBaseURL ¶ added in v0.6.2
GatewayBaseURL builds the REST base URL a scoped-token profile must target: https://api.atlassian.com/ex/jira/<cloudID>/ . The trailing slash is load bearing — the Jira client resolves its relative paths (rest/api/3/...) against this base, and a missing slash would drop the /ex/jira/<cloudID> segment.
func IsAutoTheme ¶ added in v0.4.0
IsAutoTheme reports whether name selects the background-detecting theme.
func KeyChoices ¶
KeyChoices returns the enum choices for a fully-qualified key, or nil if the key is freeform or unknown.
func NormalizeBaseURL ¶
NormalizeBaseURL expands shorthand Jira URLs into their canonical form.
- "" → "" (callers handle the empty case explicitly)
- "company" → "https://company.atlassian.net"
- "company.atlassian.net" → "https://company.atlassian.net"
- "company.example.com" → "https://company.example.com" (Server/DC)
- "http://localhost:8080" → unchanged (loopback)
- "https://x.example/" → "https://x.example" (trailing slash dropped)
The function is intentionally permissive — it only adds the scheme and the `.atlassian.net` suffix when both are missing. Callers must still run ValidateBaseURL on the result.
func ReadSecret ¶
ReadSecret is the canonical reader for a credential supplied to the CLI (stdin, an environment variable, or a 1Password CLI field). It strips only the trailing CLI record delimiter — a single trailing CR and/or LF — and preserves every other byte, including interior and leading whitespace, so a token whose own bytes are meaningful is never corrupted.
An input that is empty, or empty once the delimiter is removed, is rejected with a CredentialError carrying ErrorCodeCredentialEmpty: an explicitly blank credential is an error, not a stored value.
func RedactSecret ¶
func ResolveCredential ¶
ResolveCredential returns the effective credential for a profile: a JIRA_TOKEN_* environment override when set, otherwise the value stored in the backend. The env override is keyed by SecretRef.EnvTokenKey, which encodes the profile name bijectively so an override for one profile cannot bleed into a sibling whose name differs only by a hyphen/underscore swap.
Migration must NOT use this function: it would copy an env override rather than the configured secret. Use StoredCredential for a store-only read.
func RevokeProfileCredential ¶
func RevokeProfileCredential(ctx context.Context, store CredentialStore, ref SecretRef) (removed bool, note string, err error)
RevokeProfileCredential removes the credential for a profile from its backend. The keyring entry is keyed by site + profile and is jira-cli's own, so it is deleted outright. For the 1Password backend the store removes only jira-cli's managed credential field and always keeps the item itself — see OnePasswordStore.Delete.
It reports whether a credential was removed and, for the 1Password backend, an informational note that the item was kept. An absent credential is not an error: revocation is idempotent.
func SanitizeCredentialError ¶
SanitizeCredentialError returns a human-safe, secret-free description of a credential failure. It branches on typed CredentialError values rather than matching error message substrings, so a backend rewording its errors cannot change the classification or leak raw text.
func Save ¶
Save atomically writes cfg to path using a temp-file + rename idiom, so concurrent or interrupted writes never leave a half-truncated config file. The temp file is created in the same directory as path so that os.Rename is guaranteed to be atomic on the same filesystem.
func StoreCredentialTransactionally ¶
func StoreCredentialTransactionally(ctx context.Context, store CredentialStore, ref SecretRef, secret string, saveConfig func() error) error
StoreCredentialTransactionally writes a credential to a backend around a metadata save, so a save failure never leaves an orphaned secret. It mirrors the migration transaction: the destination is snapshotted, the new credential is written (staged), then saveConfig runs. On a save failure the write is rolled back — a pre-existing value is restored, or a freshly written one is removed — and the save error is returned. A rollback that itself fails is surfaced alongside the save error, never swallowed.
func StoredCredential ¶
StoredCredential reads the credential straight from the backend, ignoring any JIRA_TOKEN_* environment override. Migration uses this so a backend switch copies the configured secret, never a transient env value.
func ThemeForName ¶ added in v0.4.0
ThemeForName resolves an explicit theme name (e.g. config theme.name), falling back to DefaultTheme when the name is empty or unrecognized.
func ValidateBaseURL ¶
func ValidateCloudID ¶ added in v0.6.2
ValidateCloudID checks that a cloud_id is a plausible, URL-path-safe Atlassian cloudId (a UUID in practice). It is deliberately lenient about the exact shape — Atlassian has never promised the UUID format — but rejects blanks and anything that could break the gateway path (spaces, slashes, control bytes).
func ValidateProfileName ¶ added in v0.3.0
ValidateProfileName reports whether a profile name can be encoded into a credential key that round-trips uniquely. It is the input-time guard for the same namespace safety CredentialIdentity enforces at store time, so a bad name (uppercase, whitespace, a slash, anything outside lowercase letters, digits, hyphen, and underscore) is rejected when it is typed or passed — not late, after a token has already been sent to Jira. The returned error is a validation-class CredentialError.
func ValidateThemeName ¶
ValidateThemeName reports whether name is a theme clib accepts (or "auto"). It is the write-time gate for `config theme --name`; config load deliberately does not call it, so a theme name that clib later renames degrades to the dark fallback at render rather than blocking every command.
Types ¶
type AuthStatus ¶
type AuthStatus struct {
Profile string `json:"profile"`
Valid bool `json:"valid"`
Source string `json:"source"`
Redacted string `json:"redacted"`
Error string `json:"error,omitempty"`
}
func CredentialStatus ¶
func CredentialStatus(ctx context.Context, store CredentialStore, ref SecretRef) AuthStatus
type CleanupFailure ¶
CleanupFailure records a source secret that could not be deleted after the destination write and config save both succeeded. The migration is durable; the old secret is simply still present and must be removed by hand.
type CleanupNote ¶
CleanupNote records source storage that a successful migration deliberately left in place because jira-cli does not own it — a credential read via the shared legacy keyring key, or a 1Password item the profile named itself. The migration succeeded; the old credential simply still exists and the user may remove it by hand. A note is informational, not a failure.
type Config ¶
type Config struct {
DefaultProfile string `koanf:"default_profile" toml:"default_profile"`
Profiles []Profile `koanf:"profiles" toml:"profiles"`
Theme Theme `koanf:"theme" toml:"theme"`
TUI TUI `koanf:"tui" toml:"tui"`
QueriesPath string `koanf:"queries_path" toml:"queries_path"`
Aliases map[string]string `koanf:"aliases" toml:"aliases"`
// Editor is the global default external-editor command for rich-text
// edits. Per-profile Editor overrides this when set.
// Resolution chain: $JIRA_EDITOR → profile.editor → config.editor →
// $EDITOR → $VISUAL → "vi".
Editor string `koanf:"editor" toml:"editor"`
}
func Load ¶
Load reads configuration for read-only and resolution paths without ever creating a file. When the resolved path exists it is parsed; when an explicit --config path is missing it returns an error so a path typo cannot be masked; when the default path is missing it returns validated defaults without touching disk.
Commands that intend to persist config must use LoadOrInit, which bootstraps a default file when none exists.
func LoadOrInit ¶
LoadOrInit loads configuration, creating a default config file when none exists. Use this only for explicit init and config-write commands.
LoadOrInit returns the persisted, file-backed config: it deliberately does NOT apply JIRA_* env overlays. Read-modify-write commands Save the result, and persisting a transient env overlay into TOML would corrupt the user's stored config. Read-only callers that want the effective runtime view (file plus env) must use Load instead.
func (Config) Get ¶
Get returns the string form of a configuration value addressed by its user-facing dot-notation key. Profile-scoped keys take the form `profiles.<name>.<field>`. Returns ok=false for unknown keys or missing profiles.
func (Config) Profile ¶
Profile resolves a profile by name, falling back to the default profile when name is empty. A name that matches no defined profile yields a synthetic Profile carrying only that name.
This fabricating behavior is convenient for read-only display paths but unsafe for credential-admin or destructive commands. Those callers must use ResolveProfile, which refuses an unknown name instead of inventing one.
func (Config) ResolveProfile ¶
ResolveProfile resolves the profile a command should act on. An empty name resolves the configured default profile; a non-empty name must match a defined profile exactly. An unknown name, or an empty name with no configured default, returns a ProfileNotDefinedError wrapping ErrProfileNotDefined. It never fabricates a synthetic profile, so a typoed --profile cannot silently target the wrong credential namespace or cache.
type CredentialError ¶
type CredentialError struct {
// Type is the broad classification (auth or validation).
Type ErrorType
// ErrCode is the stable normalized code, the primary branch target.
ErrCode ErrorCode
// Message is a display message safe for humans and agents. It never
// contains a secret.
Message string
// HintMsg is a short next-action hint. It never contains a secret and
// never suggests a mutating command unless the failed command was itself
// mutating auth state.
HintMsg string
// IsRetryable reports whether retrying the same operation could succeed.
IsRetryable bool
// Context carries optional, fixed-shape context (flag, config key, etc.).
Context ErrorContext
// Upstream carries optional provider metadata when the backend exposes a
// structured code or status. Nil when none is available.
Upstream *UpstreamProvider
// Wrapped is the underlying error, preserved so provider-specific callers
// can still use errors.As against it.
Wrapped error
}
CredentialError is the concrete typed error for credential and auth failures in the config package. It exposes enough metadata for a later output layer to render a structured error contract without importing internal/cli into internal/config. Build it directly or via the classifier; recover it with errors.As and branch on Code, never on the message text.
func ClassifyCredentialError ¶
func ClassifyCredentialError(err error, backend string) *CredentialError
ClassifyCredentialError maps an error returned by a credential backend onto a typed CredentialError. It branches on typed errors — never on message substrings — so a backend changing its wording cannot reclassify a failure. A nil error returns nil.
The display message and hint are fixed strings: the upstream error is kept only as the wrapped cause, never copied into the message, so an upstream error that embeds a secret cannot leak through the classified error.
func (*CredentialError) Code ¶
func (e *CredentialError) Code() ErrorCode
Code returns the stable normalized failure code.
func (*CredentialError) Error ¶
func (e *CredentialError) Error() string
Error implements the error interface. It renders the type, the display message, and the hint so the next action survives when the error is shown as plain text; the wrapped cause is appended when present.
func (*CredentialError) Hint ¶
func (e *CredentialError) Hint() string
Hint returns the short next-action hint.
func (*CredentialError) Retryable ¶
func (e *CredentialError) Retryable() bool
Retryable reports whether retrying the operation could succeed.
func (*CredentialError) Unwrap ¶
func (e *CredentialError) Unwrap() error
Unwrap exposes the wrapped upstream error to errors.Is / errors.As.
type CredentialMigration ¶
type CredentialMigration struct {
Profile string
ProfileIndex int
Source CredentialStore
Destination CredentialStore
SourceRef SecretRef
DestRef SecretRef
}
CredentialMigration describes moving one profile's credential from a source backend to a destination backend. The refs are the fully namespaced identities produced by CredentialIdentity. ProfileIndex records the profile's position in the caller's profile slice so the migration and the profile it belongs to are a single ordered source of truth — no parallel index map has to be kept in lockstep.
type CredentialStore ¶
type CredentialStore interface {
Get(context.Context, SecretRef) (string, error)
Put(context.Context, SecretRef, string) error
Delete(context.Context, SecretRef) error
}
func FileCredentialStoreFromEnv ¶ added in v0.2.0
func FileCredentialStoreFromEnv() (CredentialStore, bool)
FileCredentialStoreFromEnv returns a test credential store when TestCredentialStoreDirEnv is set to a non-blank directory.
type ErrorCode ¶
type ErrorCode string
ErrorCode is the stable, normalized failure code for a credential or auth error. The Go identifier is MixedCaps; the underlying string value is the snake_case code that downstream layers serialize on the wire.
const ( // ErrorCodeCredentialSourceConflict: more than one credential input // source was supplied for one operation. ErrorCodeCredentialSourceConflict ErrorCode = "credential_source_conflict" // ErrorCodeCredentialMissing: no credential is stored or configured for // the profile. ErrorCodeCredentialMissing ErrorCode = "credential_missing" // ErrorCodeCredentialEmpty: a credential was supplied but is empty. ErrorCodeCredentialEmpty ErrorCode = "credential_empty" // be reached or failed to service the request. ErrorCodeCredentialBackendUnavailable ErrorCode = "credential_backend_unavailable" // ErrorCodeCredentialMigrationFailed: a backend-switch migration could // not complete; no metadata was changed. ErrorCodeCredentialMigrationFailed ErrorCode = "credential_migration_failed" // ErrorCodeCredentialCleanupFailed: a migration succeeded but the old // secret could not be removed from the source backend. ErrorCodeCredentialCleanupFailed ErrorCode = "credential_cleanup_failed" // ErrorCodeCredentialNamespaceCollision: a profile name cannot be encoded // into a credential namespace that round-trips uniquely. ErrorCodeCredentialNamespaceCollision ErrorCode = "credential_namespace_collision" // ErrorCodeOnePasswordItemAmbiguous: more than one 1Password item matched // the configured title. ErrorCodeOnePasswordItemAmbiguous ErrorCode = "onepassword_item_ambiguous" // reached or rejected the request. ErrorCodeOnePasswordUnavailable ErrorCode = "onepassword_unavailable" )
type ErrorContext ¶
type ErrorContext struct {
// Flag is the CLI flag involved, when the failure is flag-related.
Flag string
// ConfigKey is the config key involved, when the failure is config-related.
ConfigKey string
// Backend is the credential backend involved (keyring, 1password).
Backend string
// CredentialSource is the credential input source involved
// (stdin, env, prompt, json).
CredentialSource string
}
ErrorContext carries optional, fixed-shape context for a credential error. Every field is optional; an empty field is simply omitted by the consumer. Explicit fields are used rather than a map so the shape is checked at compile time and no unknown keys can leak.
type ErrorType ¶
type ErrorType string
ErrorType is the broad classification of a credential or auth failure. It maps onto the CLI's existing top-level error categories so the output layer can pick an exit code without inspecting the specific code.
const ( // ErrorTypeAuth marks a credential or backend failure: the credential is // missing, the backend is unreachable, or a provider rejected the request. ErrorTypeAuth ErrorType = "auth" // ErrorTypeValidation marks a caller-input failure: a conflicting flag // combination, an empty credential, or an unsafe profile name. ErrorTypeValidation ErrorType = "validation" )
type FileCredentialStore ¶ added in v0.2.0
type FileCredentialStore struct {
// contains filtered or unexported fields
}
FileCredentialStore stores credentials in one directory using hashed file names derived from the same SecretRef identity as the keyring backend.
func NewFileCredentialStore ¶ added in v0.2.0
func NewFileCredentialStore(dir string) FileCredentialStore
NewFileCredentialStore returns a file-backed credential store rooted at dir.
func (FileCredentialStore) Delete ¶ added in v0.2.0
func (s FileCredentialStore) Delete(_ context.Context, ref SecretRef) error
Delete removes a credential from the file store.
type KeyDesc ¶
type KeyDesc struct {
Name string
Description string
// Choices lists the valid values when the key has a closed enum;
// nil means freeform string/int.
Choices []string
}
KeyDesc describes a single configuration key visible to `jira config get/set`.
type KeyringStore ¶
type KeyringStore struct{}
KeyringStore stores credentials in the OS keyring under a readable "<site-host>/<profile>" entry name. A credential belongs to a site and a profile; the entry name is that identity verbatim, with no digest.
func (KeyringStore) Delete ¶
func (KeyringStore) Delete(_ context.Context, ref SecretRef) error
Delete removes the credential's "<host>/<profile>" keyring entry — the entry jira-cli itself created. A missing entry is normalized to ErrCredentialNotFound rather than surfaced as a backend failure, so logout is idempotent.
func (KeyringStore) Get ¶
Get reads the credential for ref from its "<host>/<profile>" keyring entry. A missing entry is reported as a typed credential-missing error (wrapping ErrCredentialNotFound) that names the profile and points at `auth login`. There is no legacy fallback: a credential stored by a pre-namespacing release under a bare profile name is not auto-resolved — the user logs in once after upgrading.
type MemoryCredentialStore ¶
type MemoryCredentialStore struct {
// contains filtered or unexported fields
}
func NewMemoryCredentialStore ¶
func NewMemoryCredentialStore() *MemoryCredentialStore
func (*MemoryCredentialStore) Delete ¶
func (s *MemoryCredentialStore) Delete(_ context.Context, ref SecretRef) error
type MigrationReport ¶
type MigrationReport struct {
CleanupFailures []CleanupFailure
CleanupNotes []CleanupNote
}
MigrationReport is the outcome of a MigrateCredentials batch. Cleanup failures and cleanup notes are reported, never hidden: the migration succeeded but a stale secret may remain in the source backend.
func MigrateCredentials ¶
func MigrateCredentials(ctx context.Context, migrations []CredentialMigration, saveConfig func() error) (MigrationReport, error)
MigrateCredentials moves a batch of credentials between backends transactionally around a single metadata save:
- Every destination secret is written first (staging). Before each destination write the destination is snapshotted so rollback can restore a pre-existing value. A write failure aborts the batch; any destination secrets already staged are rolled back and the source secrets are left untouched.
- saveConfig persists the new backend metadata. A save failure rolls back every staged destination secret and returns the save error; no source secret is deleted.
- Only after a durable save is each source credential cleaned up — but only storage jira-cli owns: a keyring entry, or a 1Password item jira-cli named by default. A 1Password item the profile named itself is left in place and recorded as a cleanup note. A delete failure does not fail the migration — it is recorded as a cleanup failure.
Rollback errors are not swallowed: if undoing a staged write fails, the returned error names the affected profiles, so the caller is never told a failed migration was cleanly rolled back when a destination write actually could not be undone.
The staged-write-before-save ordering means a crash between any two steps never leaves a profile whose config points at a backend with no secret.
func (MigrationReport) HasCleanupFailures ¶
func (r MigrationReport) HasCleanupFailures() bool
HasCleanupFailures reports whether any source secret could not be deleted.
type OnePasswordStore ¶
type OnePasswordStore struct {
// Client, when set, is used directly — tests inject an in-memory fake here.
Client onePasswordClient
// NewClient, when set, builds a client per request; it defaults to the
// real SDK client when neither Client nor NewClient is supplied.
NewClient onePasswordClientFactory
}
OnePasswordStore is the credential store for the 1password backend. It is SDK-only: it talks to 1Password through the official Go SDK, never the `op` CLI binary. Authentication comes from a service-account token in the environment or a desktop-app account pinned on the profile.
func (OnePasswordStore) Delete ¶
func (s OnePasswordStore) Delete(ctx context.Context, ref SecretRef) error
Delete revokes the credential from 1Password. It NEVER destroys a 1Password item — not even one jira-cli itself created. A 1Password item is a user-owned object, and an item's tags are user-editable metadata that can never prove who created it; so the only safe revocation is to remove jira-cli's own managed credential field — identified by its stable jira-cli-owned ID — and leave the item, and every other field and all its content, in place. An item jira-cli created solely to hold a credential simply ends up without that field; that is fine and expected.
A missing item normalizes to ErrCredentialNotFound so revocation is idempotent.
func (OnePasswordStore) Get ¶
Get resolves the credential field of the profile's 1Password item. It first confirms the item exists structurally — the SDK exposes no typed not-found error, so the existence check (the same ListItems path Put and Delete use) is the deterministic way to tell a missing item from a real backend failure. A missing item or vault normalizes to ErrCredentialNotFound.
func (OnePasswordStore) Put ¶
Put writes the credential to 1Password as a true upsert: an existing item is edited in place, otherwise the item is created. The in-place edit means a staged Put genuinely overwrites a prior value, so the migration rollback contract — "restore the prior value" — holds.
The credential always goes into the field with the stable jira-cli-owned ID; a user's own field merely titled "credential" with a different ID is left untouched. Editing a pre-existing item only writes that one field, so Put never disturbs anything else on an item jira-cli did not create.
type Profile ¶
type Profile struct {
Name string `koanf:"name" toml:"name"`
BaseURL string `koanf:"base_url" toml:"base_url"`
AuthType AuthType `koanf:"auth_type" toml:"auth_type"`
Email string `koanf:"email" toml:"email"`
DefaultProject string `koanf:"default_project" toml:"default_project"`
DefaultIssueType string `koanf:"default_issue_type" toml:"default_issue_type"`
// DefaultBoard is the profile-scoped default board NAME. When set and
// no `--board`/`--board-id` flag is supplied, the consuming command
// resolves it against the boards cache and applies the resulting
// scope. NOT validated at set time; the cache may not exist yet.
// Use-time validation lives in boardscope.FromFlags. An explicit
// `--board ""` suppresses any configured default.
DefaultBoard string `koanf:"default_board" toml:"default_board"`
RefreshInterval int `koanf:"refresh_interval" toml:"refresh_interval"`
TimeoutSeconds int `koanf:"timeout" toml:"timeout"`
WorkdaySeconds int `koanf:"workday_seconds" toml:"workday_seconds"`
SecretBackend SecretBackend `koanf:"secret_backend" toml:"secret_backend"`
// CloudID, when set, marks this profile as using a scoped (granular)
// Atlassian API token. The auth mechanism is unchanged — HTTP Basic with
// the account email and token — but REST calls must route through the
// Atlassian gateway (https://api.atlassian.com/ex/jira/<cloud_id>/...)
// because the site host rejects scoped tokens. BaseURL is still stored as
// the site URL for cloudId discovery, display, and browser links; the
// effective request base URL is derived by ClientBaseURL. Empty means a
// classic API token addressed directly at the site.
CloudID string `koanf:"cloud_id" toml:"cloud_id"`
OnePasswordAccount string `koanf:"onepassword_account" toml:"onepassword_account"`
Vault string `koanf:"vault" toml:"vault"`
Item string `koanf:"item" toml:"item"`
// TeamAccountIDs lists the account IDs of teammates whose issues count
// as "my team" in TUI filtering. Optional.
TeamAccountIDs []string `koanf:"team_account_ids" toml:"team_account_ids"`
// AccountID is the user's own Jira Cloud accountId. Used by `--assignee me`
// (CLI) and the "A" key (TUI) so assignments target the canonical user
// identifier. Optional; falls back to email when blank.
AccountID string `koanf:"account_id" toml:"account_id"`
// ReadOnly blocks every mutation command for this profile and returns a
// validation error (exit 3). Useful when handing credentials to an AI
// agent. The env var JIRA_READ_ONLY (truthy: 1/true/yes/on) layers on
// top — once set globally, every profile becomes read-only regardless
// of its own setting.
ReadOnly bool `koanf:"read_only" toml:"read_only"`
// Editor is the per-profile external-editor command override.
// When set, takes precedence over the global Config.Editor.
// Resolution chain: $JIRA_EDITOR → profile.editor → config.editor →
// $EDITOR → $VISUAL → "vi".
Editor string `koanf:"editor" toml:"editor"`
}
func (Profile) ClientBaseURL ¶ added in v0.6.2
ClientBaseURL is the REST base URL the Jira client must target for this profile. A classic API-token profile addresses the site directly; a scoped (granular) token profile carries a CloudID and must route through the Atlassian gateway, the only base URL that accepts scoped tokens. This is the single chokepoint that turns a cloud_id into gateway routing — every client constructor and credential-verification path funnels its base URL through here, so classic and scoped profiles diverge in exactly one place.
type ProfileIncompleteError ¶ added in v0.7.1
type ProfileIncompleteError struct {
Name string
}
ProfileIncompleteError is returned when a requested profile exists but cannot serve live commands because it has no base URL (and no cloud_id to derive one). Callers recover the name with errors.As.
func (ProfileIncompleteError) Error ¶ added in v0.7.1
func (e ProfileIncompleteError) Error() string
type ProfileNotDefinedError ¶
type ProfileNotDefinedError struct {
Name string
}
ProfileNotDefinedError carries the name of the profile that could not be resolved. It wraps ErrProfileNotDefined, so callers can use either errors.Is(err, ErrProfileNotDefined) or errors.As to recover the name.
func (ProfileNotDefinedError) Error ¶
func (e ProfileNotDefinedError) Error() string
func (ProfileNotDefinedError) Unwrap ¶
func (ProfileNotDefinedError) Unwrap() error
type SecretBackend ¶
type SecretBackend string
const ( SecretBackendKeyring SecretBackend = "keyring" SecretBackendOnePassword SecretBackend = "1password" )
type SecretRef ¶
type SecretRef struct {
Profile string
Backend SecretBackend
Account string
Vault string
Item string
// ItemIsDefault reports whether Item is the jira-cli-derived default name
// rather than a name the profile pinned itself. It records 1Password item
// ownership: jira-cli owns and may delete an item it named by default, but
// must never delete an item the user named.
ItemIsDefault bool
// Host is the site host of the profile's Jira base URL (e.g.
// "company.atlassian.net"). It scopes the credential to the site, so the
// same profile name pointing at two sites does not share an entry.
Host string
}
SecretRef identifies a credential in a backend. A credential belongs to a Jira site and a profile: Host is the site host (the host of the profile's base URL) and Profile is the profile name. Two profiles that share a name but point at different Jira sites address different credentials, because the site host is part of the identity. Account/Vault/Item are the backend-specific 1Password coordinates.
Construct a SecretRef with CredentialIdentity so the identity is derived consistently and unsafe profile names are rejected.
func CredentialIdentity ¶
CredentialIdentity derives the stable credential identity for a profile. The returned SecretRef carries the site host (derived from the profile's base URL) and profile name — the credential belongs to that site + profile pair — plus the 1Password coordinates (account/vault/item). For the 1Password backend the item title defaults to a site-scoped name ("jira-cli-<host>-<profile>") when the profile does not pin one, so two sites that share a profile name and a vault do not collide on one item.
A profile name that cannot be encoded into a safe keyring/env key is rejected with a CredentialError carrying ErrorCodeCredentialNamespaceCollision.
func (SecretRef) EnvTokenKey ¶
EnvTokenKey is the environment variable that overrides this profile's credential. The profile name is encoded bijectively: a literal underscore in the name is doubled and a hyphen maps to a single underscore, so "work-staging" (JIRA_TOKEN_WORK_STAGING) and "work_staging" (JIRA_TOKEN_WORK__STAGING) map to distinct keys and cannot bleed into each other. Letters are uppercased; namespace-safe profile names are lower-case only, so the uppercase transform is injective.
func (SecretRef) KeyringName ¶
KeyringName is the keyring entry name for this credential: the readable "<host>/<profile>" pair. There is no digest — the key is the credential's site + profile identity verbatim, so it is legible in any keyring browser.
type TUI ¶
type TUI struct {
RefreshInterval int `koanf:"refresh_interval" toml:"refresh_interval"`
DefaultTab string `koanf:"default_tab" toml:"default_tab"`
Tabs []string `koanf:"tabs" toml:"tabs"`
// Preview docks the issue sidebar: right, left, bottom, hidden, or auto
// (right on wide terminals, bottom on narrow). Applies on hot-reload too.
Preview string `koanf:"preview" toml:"preview"`
// Sections are user-defined dashboard tabs: each entry is a
// saved JQL query that becomes its own tab with its own result count.
Sections []TUISection `koanf:"sections" toml:"sections"`
// PreviewSize is the preview pane's share of the split as a percent
// (20–80; values outside clamp, 0/absent keeps the 50% default).
PreviewSize int `koanf:"preview_size" toml:"preview_size"`
// Keys rebinds TUI actions: action name → key list (the first key shows
// in help). Unknown action names surface as a footer error rather than
// failing the load. Applies on hot-reload; removing an entry restores
// the default binding.
Keys map[string][]string `koanf:"keys" toml:"keys"`
// Lenses replace the issues tab's built-in quick-filters (Mine/Sprint/
// Updated/Reported) with custom titled JQL queries. Absent or empty
// keeps the built-ins.
Lenses []TUISection `koanf:"lenses" toml:"lenses"`
// DefaultLens names the lens (by title, case-insensitive) the issues tab
// lands on. Absent or unmatched falls back to the first lens.
DefaultLens string `koanf:"default_lens" toml:"default_lens"`
}
type TUISection ¶ added in v0.6.0
type TUISection struct {
Title string `koanf:"title" toml:"title"`
JQL string `koanf:"jql" toml:"jql"`
}
TUISection is one configured dashboard tab: a title and the JQL it runs.
type UpstreamProvider ¶
type UpstreamProvider struct {
// Provider names the backend, e.g. "onepassword-sdk".
Provider string
// UpstreamCode is a bounded, redacted provider code when one can be parsed
// safely. Empty when the provider exposes none.
UpstreamCode string
// UpstreamStatus is a provider status or process exit code. Zero when the
// provider exposes none.
UpstreamStatus int
}
UpstreamProvider carries provider-specific failure metadata for a backend that exposes structured values. It is populated only when the provider reports a code or status; it is never derived by parsing a human message. Provider codes are kept separate from CredentialError.ErrCode so the normalized code stays the primary branch target.