Documentation
¶
Overview ¶
Package config loads and saves ~/.gadak/config.json.
Credentials (email/token) share the file with the site settings, but they never reach the database, a log line, or a snapshot (constitution article 8). The file is written 0600.
Index ¶
- Constants
- Variables
- func ApplyAppearance(c *Config, a Appearance) error
- func ApplyConfluence(c *Config, enabled *bool, spaces []string) error
- func ApplyUIConfig(c *Config, next *UIConfig) error
- func ApplyUIConfigWithWarnings(c *Config, next *UIConfig) ([]tokencheck.Violation, error)
- func AttachmentDir() (string, error)
- func AttachmentDirFor(profile string) (string, error)
- func ClearStoredWorkspace() error
- func ConfigVersionOfDir(dir string) string
- func DBPath() (string, error)
- func DBPathFor(profile string) (string, error)
- func Dir() (string, error)
- func DirFor(profile string) (string, error)
- func Env(suffix string) string
- func FormatTokenTime(t time.Time) string
- func LooksLikeProjectKey(s string) bool
- func NormalizeFeatures(set map[string]bool) map[string]bool
- func NormalizeProfile(name string) string
- func NotConfiguredWith(addendum string) error
- func ParseActorShorthand(v string) (slug, name string)
- func ParseTokenExpiresAt(raw string) (time.Time, error)
- func Path() (string, error)
- func Profile() string
- func Profiles() ([]string, error)
- func ReloadWorkspaceFromEnv()
- func RequireExistingProfile() error
- func SetProfile(name string)
- func SetStoredWorkspace(name string) error
- func SettingPaths() []string
- func UIDataColors(u *UIConfig) map[string]map[string]string
- func UIDimensionVars(u *UIConfig) (vars map[string]string, warns []tokencheck.Violation)
- func UITokenVars(u *UIConfig) (vars map[string]map[string]string, warns []tokencheck.Violation)
- func ValidateDataColors(dc map[string]map[string]string) error
- func ValidateDefaultIssueType(s string) (string, error)
- func ValidateDefaultIssueTypeID(s string) (string, error)
- func ValidateDefaultProject(s string) (string, error)
- func ValidateGroupQuery(q string) error
- func ValidateIntervals(syncSec, reconcileSec int) error
- func ValidateLocale(s string) (string, error)
- func ValidateProjectKeys(keys []string) ([]string, error)
- func ValidateTheme(s string) (string, error)
- func ValidateUIConfig(u *UIConfig) (warns []tokencheck.Violation, err error)
- func WorkspaceSource() (kind, envName string)
- type ActorConfig
- type Appearance
- type Config
- func (c *Config) ApplyTokenExpiry(userRaw, verifiedAt string) error
- func (c *Config) ApplyTokenExpiryIfNeeded(userRaw, verifiedAt string, tokenReplaced bool) error
- func (c *Config) ApplyVerifiedIdentity(accountID, displayName, verifiedAt string)
- func (c *Config) ClearTokenExpiry()
- func (c *Config) CustomFieldsStatus() map[string]any
- func (c *Config) Directory() string
- func (c *Config) EffectiveLocale() string
- func (c *Config) EffectiveReconcileIntervalSec() int
- func (c *Config) EffectiveSyncIntervalSec() int
- func (c *Config) EffectiveTheme() string
- func (c *Config) FieldSpecs() []FieldSpec
- func (c *Config) HasAtlassianCredential() bool
- func (c *Config) HasCredential() bool
- func (c *Config) HasLinearCredential() bool
- func (c *Config) IsStandalone() bool
- func (c *Config) NormalizeLegacyFields() (changed bool, shape string)
- func (c *Config) NotifyEnabled() bool
- func (c *Config) ProfileName() string
- func (c *Config) Save() error
- func (c *Config) SyncFrozen() bool
- func (c *Config) TokenExpiryAt(now time.Time) TokenExpiry
- func (c *Config) UpdateCheckEnabled() bool
- func (c *Config) WorkspaceKind() string
- type ConfluenceConfig
- type DimCatalogEntry
- type FieldSpec
- type GroupRule
- type LinearConfig
- type Member
- type Product
- type ResolvedActor
- type Setting
- type TokenExpiry
- type UIConfig
- type UITokens
Constants ¶
const ( ActorSourceEnv = "env" // GADAK_ACTOR ActorSourceConfig = "config" // config.json actor ActorSourceAuto = "auto" // Claude Code detection )
Actor sources, in ladder order. status reports which rung answered so an agent can see that its environment was (or was not) recognized.
const ( KindConnected = "connected" KindStandalone = "standalone" )
Workspace kinds. Empty Kind on disk is connected — existing configs keep working with no rewrite.
const ( DefaultSyncIntervalSec = 60 // 1 minute DefaultReconcileIntervalSec = 3600 // 1 hour MinSyncIntervalSec = 15 // seconds MinReconcileIntervalSec = 300 // 5 minutes )
Sync loop defaults and floors. Zero in the file means "use default". Floors reject values that would thrash Jira or busy-loop the local process.
const ( SourceFlag = "flag" SourceEnv = "env" SourceStored = "stored" SourceDefault = "default" )
Workspace selection source. WorkspaceSource reports one of these; when the kind is SourceEnv, the env variable name is returned alongside.
const ( // Name is the CLI binary, desktop app, and user-facing product name. Name = "gadak" // DirName is the directory under $HOME that holds the default profile. DirName = ".gadak" // DBFile is the SQLite filename inside a profile directory. DBFile = "gadak.db" // EnvPrefix is prepended to HOME, PROFILE, WORKSPACE, TOKEN, SITE, EMAIL, PROJECTS. EnvPrefix = "GADAK_" // Legacy names from the 2026-08 rename (scry → gadak). Still accepted so // an existing install keeps working until the user next launches gadak. LegacyName = "scry" LegacyDirName = ".scry" LegacyDBFile = "scry.db" LegacyEnvPrefix = "SCRY_" )
const ( TokenExpiryOK = "ok" TokenExpiryExpiring = "expiring" TokenExpiryExpired = "expired" TokenExpiryUnknown = "unknown" TokenExpirySourceUser = "user" TokenExpirySourceAssumed = "assumed" // TokenDefaultLifetimeDays is Atlassian's default API-token lifetime // when the user skipped the date from the create dialog. TokenDefaultLifetimeDays = 365 // TokenExpiryWarnDays is the first day the warning surfaces (inclusive). TokenExpiryWarnDays = 14 // TokenExpiryUrgentDays is the first day the warning is urgent (inclusive). TokenExpiryUrgentDays = 3 // TokenTimeFormat is ISOMilli so assumed dates line up with // tokenVerifiedAt and every other store timestamp. TokenTimeFormat = ISOMilli )
Token expiry assessment. One owner: AssessTokenExpiry. Surfaces (status, sync_health, the freshness chip) render what this returns; they do not re-derive state.
const ISOMilli = "2006-01-02T15:04:05.000Z"
ISOMilli is the millisecond-precision UTC ISO-8601 layout every timestamp gadak writes — store columns, token expiry, usage flush — and that the `delta` cursor contract depends on. Milliseconds are not decoration: a whole-second cursor would drop a row written in the same second the cursor was taken.
Variables ¶
var ErrNotConfigured = errors.New("not configured — run gadak init (Jira), gadak init --standalone (local), or gadak init --pairing-code (another machine's serve)")
ErrNotConfigured is the single owner of "this workspace has no origin yet" (GDK-454). CLI verbs that refuse because HasCredential is false print this — sync, writes, open, api, pairing list, status stderr, init's missing- values path. Paired workspaces are configured: HasCredential already counts remote-origin.json (GDK-442) and these verbs must not use this sentence for them. A Linear apiKey is a credential too: this sentence must not fire on a Linear-only workspace.
origin.errNeedCredential is a different error (Client/Wiki construction when a connected workspace lacks site/email/token) and is quoted by wikiPathStatus; it is not this sentence.
var FeatureNames = []string{"feed", "deploy", "qa", "teamGroups"}
FeatureNames is every optional-surface flag PUT / gadak config accept. Unknown keys in a features map are dropped.
Functions ¶
func ApplyAppearance ¶ added in v0.15.2
func ApplyAppearance(c *Config, a Appearance) error
ApplyAppearance writes a validated theme onto c. "system" and empty store as the zero value so the default is not persisted.
func ApplyConfluence ¶ added in v0.15.2
ApplyConfluence is the PUT settings/ confluence rule, shared with `gadak config set confluence*`. enabled:false turns the source off; enabled:true creates/replaces the block; spaces alone requires it on.
func ApplyUIConfig ¶ added in v0.17.2
ApplyUIConfig validates next and installs it on c. Warnings are printed to stderr here (LoadFor's migration notices set the precedent: the write succeeded and the warning is part of its output, not an API result).
func ApplyUIConfigWithWarnings ¶ added in v0.17.3
func ApplyUIConfigWithWarnings(c *Config, next *UIConfig) ([]tokencheck.Violation, error)
ApplyUIConfigWithWarnings is ApplyUIConfig with the write-time warnings returned as well: under warn+save the warning IS the interesting part of the answer — why the saved look will render the way it does — and the settings PUT carries them on its response (uiWarnings) so a writer that never sees the CLI's stderr still reads them. The stderr print stays; server logs keep the same record a CLI session leaves.
func AttachmentDir ¶
AttachmentDir is where attachment bytes are cached, next to the mirror it belongs to (so a profile keeps its own, and deleting a profile takes its cache with it).
func AttachmentDirFor ¶
AttachmentDirFor is where attachment bytes are cached for the named profile.
func ClearStoredWorkspace ¶ added in v0.17.0
func ClearStoredWorkspace() error
ClearStoredWorkspace removes the stored default so Profile() falls through to the root. Missing file is success.
func ConfigVersionOfDir ¶ added in v0.17.2
ConfigVersionOfDir is the disk identity of a profile's config.json, for a directory already in hand (webConfig gets the loaded Config's Directory()). Every writer (CLI `config set`, PUT settings/, LoadFor's legacy-field rewrite) goes through the same atomic temp+rename, so mtime+size changes exactly when the content does. The ui-focus poll carries it; the web refetches config.json when it moves. A monotonic counter was rejected because it needed a second owner (local_meta) and a CLI↔SQLite coupling on a path that already had a single owner: the file.
func DirFor ¶
DirFor is the config directory for a named profile. "" or "default" means the root (GADAK_HOME / ~/.gadak); any other name lives under profiles/<name>. Names that fail validProfileName return an error and no path.
func Env ¶
Env returns GADAK_<suffix>, then SCRY_<suffix> if the new name is unset or empty. An empty GADAK_* value is treated as unset so a blank export cannot hide a real SCRY_* fallback (decision 0007: read SCRY_* when GADAK_* is unset).
func FormatTokenTime ¶ added in v0.14.2
FormatTokenTime writes the on-disk / JSON form (UTC, millisecond).
func LooksLikeProjectKey ¶ added in v0.17.2
LooksLikeProjectKey reports that shape without case-folding. "Fix" (the create --help example) is a summary word; "FIX" / "GDK" look like keys.
func NormalizeFeatures ¶ added in v0.15.2
NormalizeFeatures projects the optional-surface flags. An explicit key wins; missing keys stay off except feed, which defaults on.
func NormalizeProfile ¶ added in v0.17.0
NormalizeProfile is the display name of a profile: the empty string and "default" name the same root profile (Profile() returns "" for it) and both serialize as "default". Single owner of that mapping (GDK-619) — serve's workspace list, settings' profile display, and sameProfile all call this instead of re-spelling the branch.
func NotConfiguredWith ¶ added in v0.17.0
NotConfiguredWith appends a verb-specific addendum to ErrNotConfigured.
func ParseActorShorthand ¶ added in v0.17.0
ResolveActor is the single owner of "who does this process write as" (GDK-586). Ladder, first match wins:
- env GADAK_ACTOR — "slug" or "slug|display name"
- the config.json actor block (the workspace default)
- Claude Code auto-detection: CLAUDECODE=1 derives "claude:<first 8 of CLAUDE_CODE_SESSION_ID>" (or bare "claude" with no session id), display name "Claude Code"
Nothing set means no actor: origins see the identity they always did (the in-process user / seed account) and no header is sent.
AI_AGENT is deliberately not a slug source: as measured it carries the harness version ("claude-code_2-1-239_agent"), so a slug built from it would mint a new agent identity per upgrade — the opposite of the stable-slug contract. ParseActorShorthand splits the one-line actor form shared by GADAK_ACTOR and `gadak config set actor`: "slug" or "slug|display name". An empty slug means unset.
func ParseTokenExpiresAt ¶ added in v0.14.2
ParseTokenExpiresAt accepts a calendar date (YYYY-MM-DD, from an HTML date input or Atlassian's create dialog) or an RFC3339 timestamp. Date-only values are midnight UTC that day.
func Profile ¶
func Profile() string
Profile is the single owner of workspace resolution: flag > env > stored default > root. "" is the root profile. "default" normalizes to "".
func ReloadWorkspaceFromEnv ¶ added in v0.17.0
func ReloadWorkspaceFromEnv()
ReloadWorkspaceFromEnv forgets a SetProfile override and reads GADAK_WORKSPACE, then GADAK_PROFILE, then SCRY_PROFILE. Empty values are unset. When none of those are set, Profile() still applies the stored default (then the root). Each process main must call this before flags (cmd/gadak does); importing this package does not. Tests call it after t.Setenv.
func RequireExistingProfile ¶ added in v0.13.0
func RequireExistingProfile() error
RequireExistingProfile is the single owner of "may this named profile be used without creating it?". The default profile (empty / "default") is always allowed so first-run can mint ~/.gadak. A named profile whose directory does not exist is an error; names that do exist are listed so a typo is obvious.
func SetProfile ¶
func SetProfile(name string)
SetProfile is called by the CLI's --workspace/--profile flag, which wins over the env var and the stored default. The source becomes SourceFlag even when name is empty.
func SetStoredWorkspace ¶ added in v0.17.0
SetStoredWorkspace writes the home-root stored default used by Profile() when no flag or env selected a workspace. Empty and "default" clear it. A name whose directory does not exist is refused with the same error RequireExistingProfile prints — Profile() must not be pointed at a missing workspace that then silently becomes the root.
func SettingPaths ¶ added in v0.15.2
func SettingPaths() []string
SettingPaths returns catalog paths in catalog order.
func UIDataColors ¶ added in v0.17.2
UIDataColors passes the stored data inks through the same value gate and returns what may render. Keys were validated at write time; this is the stale-schema defense for values that reached disk anyway.
func UIDimensionVars ¶ added in v0.17.3
func UIDimensionVars(u *UIConfig) (vars map[string]string, warns []tokencheck.Violation)
UIDimensionVars expands the stored dimension overrides into the single palette-agnostic CSS variable map the web consumes: cssVar ("--spacing-row") → value ("44px"). It is the sibling of UITokenVars with the palette axis removed — dimensions do not vary by theme. Mirrors its filter exactly: only names the dim catalog knows, tiers that may render, and values that parse are injected; everything else degrades to a load-time advisory (GDK-769 axis 3) so a config written by a newer build never blocks a boot. The map is overrides-only: base values keep coming from app.css and the JS layout constants.
func UITokenVars ¶ added in v0.17.2
UITokenVars expands the stored overrides into the final CSS variable maps the web consumes: palette → cssVar ("--color-accent") → hex. Only names the catalog knows and valid hex values are injected — anything else is reported as a load-time advisory so a config written by a newer build (renamed token) degrades to "override ignored" instead of an unbootable UI. Locked tiers RENDER (the write path warned and saved the value — filtering it here would turn that warning into a silent lie); the dimension sibling still filters its one locked token because a stored docked-min is genuinely dead (the runtime recomputes it).
func ValidateDataColors ¶ added in v0.17.2
ValidateDataColors enforces the family/key/value rules. The value rule is hex only — data inks are decorative (dots, chip text), not grounds, so no contrast floor applies; the token tiers carry the legibility contract.
func ValidateDefaultIssueType ¶ added in v0.16.0
ValidateDefaultIssueType stores an optional display label. Resolution never reads this value.
func ValidateDefaultIssueTypeID ¶ added in v0.16.0
ValidateDefaultIssueTypeID accepts empty (unset) or a Jira issue type id.
func ValidateDefaultProject ¶ added in v0.16.0
ValidateDefaultProject accepts empty (unset) or a project key with no whitespace. Membership in Projects is not checked here — that list can be empty ("every project") and can change after the default is set.
func ValidateGroupQuery ¶ added in v0.16.0
ValidateGroupQuery accepts empty (disabled) or a single SELECT/WITH. Writes, PRAGMA, ATTACH, and multi-statement payloads are refused here so a bad save fails before the derived view tries to run it.
func ValidateIntervals ¶ added in v0.15.2
ValidateIntervals is the PUT / gadak config rule for the two watch periods. 0 means "use the package default". A positive value below the floor is rejected so a typo cannot thrash Jira.
func ValidateLocale ¶ added in v0.17.0
ValidateLocale accepts "", en, ko, ja, de (GDK-597). Empty stores as the zero value so the default is not persisted.
func ValidateProjectKeys ¶ added in v0.17.2
ValidateProjectKeys is the single choke for the `projects` setting (GDK-809). Empty means "every project this account can see". Entries are trimmed and upper-cased (same as gadak init). Mixed-case values are refused as display names. Load does not call this — a file that already contains an unknown key keeps working until the next Set.
func ValidateTheme ¶ added in v0.15.2
ValidateTheme accepts empty/"system" (stored as "") and any lowercase identifier. "system", "light", and "dark" are always valid.
func ValidateUIConfig ¶ added in v0.17.2
func ValidateUIConfig(u *UIConfig) (warns []tokencheck.Violation, err error)
ValidateUIConfig is the write gate for the whole `ui` block. It returns the carry-along warnings (unknown names, unknown palettes) and an error that refuses the save. A nil config is valid and clears nothing.
func WorkspaceSource ¶ added in v0.17.0
func WorkspaceSource() (kind, envName string)
WorkspaceSource names what Profile() used: SourceFlag, SourceEnv, SourceStored, or SourceDefault. envName is the variable that supplied the value when kind is SourceEnv, otherwise "".
Types ¶
type ActorConfig ¶ added in v0.17.0
ActorConfig is the workspace-default acting identity for writes to an issuetap origin (standalone or paired, GDK-586): the slug becomes the origin accountId verbatim and names an agent account there; Name is an optional display name. Nil (or empty slug) means unset. Per-machine identity — never team-exported.
func ValidateActor ¶ added in v0.17.0
func ValidateActor(slug, name string) (*ActorConfig, error)
ValidateActor is the `gadak config set actor` rule: empty slug clears the block; a non-empty slug must be a stable identity, not a display name — no whitespace, within issuetap's cap.
type Appearance ¶ added in v0.15.2
type Appearance struct {
Theme string `json:"theme,omitempty"`
}
Appearance is the look block in config.json. Empty Theme means "system".
type Config ¶
type Config struct {
// Kind is the workspace kind. Empty (or any value other than
// KindStandalone) is a connected workspace — Jira-site bound, the
// default. Absent from existing configs; no migration.
// Do not store the word "local" here: gadak is already local-first.
Kind string `json:"kind,omitempty"`
// Frozen stops every request to this workspace's origin — pulls and
// writes alike (GDK-507). A scrubbed fixture with a live credential
// decontaminates itself the moment something opens it (GDK-181), and the
// same live credential could create real issues on the origin; both are
// refused at the client mint (origin.ErrWorkspaceFrozen). Demo, recording
// and screenshot workspaces set this. Mirror reads are unaffected.
Frozen bool `json:"frozen,omitempty"`
// The credential and what it connects to. Token is never copied out of this file.
// A standalone workspace leaves these empty.
Site string `json:"site,omitempty"` // https://your-site.atlassian.net
Email string `json:"email,omitempty"`
Token string `json:"token,omitempty"`
Projects []string `json:"projects,omitempty"`
// DevStatus turns on development-panel mirroring (GDK-496/497): sync
// fetches each changed issue's dev-status (pull requests) from the origin
// into dev_links. Off by default on a connected Cloud workspace — that
// path calls Jira's internal /rest/dev-status API (Atlassian marks it
// unstable) and adds per-issue requests. Standalone / issuetap always
// fetches regardless of this flag (GDK-536): the panel is local.
DevStatus bool `json:"devStatus,omitempty"`
// Locale is the display-name language of a standalone workspace's
// origin ("" | en | ko | ja | de; "" and "en" are both English,
// GDK-597). It rides EmbeddedConfig into the embedded issuetap, so
// status / issue-type / field-catalog names — and the agent alias
// dictionary (GDK-593) — follow it; priority names stay English, like
// a live Cloud site. A connected workspace ignores it: there the
// language is the Atlassian account's, not ours.
Locale string `json:"locale,omitempty"`
// DefaultProject is the project key used when create omits --project /
// project_key. Site-bound: never team-exported. Empty means unset.
DefaultProject string `json:"defaultProject,omitempty"`
// DefaultIssueTypeID is the Jira issue type id used when create omits
// --type / issue_type. Stored as id, never a localized display name —
// a Korean account's "Task" is "작업". Empty means unset. Display names
// are not a fallback; see DefaultIssueType.
DefaultIssueTypeID string `json:"defaultIssueTypeId,omitempty"`
// DefaultIssueType is an optional display label for DefaultIssueTypeID.
// Resolution never reads this field (names localize per account).
DefaultIssueType string `json:"defaultIssueType,omitempty"`
// Result of verifying the credential: when `PUT credential/` last confirmed it
// against /myself, and who owns it. Unlike the token itself, both may be
// returned in a response.
TokenVerifiedAt string `json:"tokenVerifiedAt,omitempty"`
TokenOwner string `json:"tokenOwner,omitempty"`
// TokenExpiresAt is when the stored API token stops working (RFC3339).
// Set on connect / replace: the date typed from Atlassian's create
// dialog, or verification time plus 365 days when that field was skipped.
// There is no Atlassian API for this; the token string is opaque.
// TokenExpirySource is "user" or "assumed" so a warning can hedge.
TokenExpiresAt string `json:"tokenExpiresAt,omitempty"`
TokenExpirySource string `json:"tokenExpirySource,omitempty"`
// AccountID is the Jira accountId returned by /myself. Used for feed
// relevance (assignee/reporter/mention) and self-action filtering. Empty
// when the credential was never verified against a live site.
AccountID string `json:"account_id,omitempty"`
// Sync field mapping (contracts/sync.md, "Field mapping").
// Fields is the sole on-disk truth. FieldMap/EditableFields exist only as
// unmarshal targets so LoadFor can migrate a pre-Fields config once.
Fields []FieldSpec `json:"fields,omitempty"`
// FieldsAppliedAt is when `gadak fields --apply` last succeeded (RFC3339,
// millisecond UTC). Mapping lives in Fields; the timestamp lives here too
// so a regenerated mirror does not lose "when discovery last ran".
FieldsAppliedAt string `json:"fieldsAppliedAt,omitempty"`
// FieldMap is a migration-only unmarshal target (alias → customfield id).
// LoadFor converts it into Fields and clears it; new writes must not set it.
FieldMap map[string]string `json:"fieldMap,omitempty"`
BodyFields []string `json:"bodyFields,omitempty"` // ADF custom-field ids to fold into FTS
// EditableFields is a migration-only unmarshal target (alias → field id).
// LoadFor overlays it onto Fields (legacy wins per alias) and clears it.
EditableFields map[string]string `json:"editableFields,omitempty"`
// Optional surfaces carried over from the tool this was extracted from.
Members []Member `json:"members,omitempty"`
GroupRules []GroupRule `json:"groupRules,omitempty"`
// GroupQuery is an optional read-only SELECT/WITH run when the derived
// view is rebuilt (config or sync version change), never on a keystroke.
// It must return two columns: issue key, group. An empty group string
// leaves the issue unclassified; NULL (or a missing key) falls through
// to groupRules and then the assignee's member group. Installation
// logic belongs in this string, not in gadak source.
GroupQuery string `json:"groupQuery,omitempty"`
GroupLabels map[string]string `json:"groupLabels,omitempty"`
GroupColors map[string]string `json:"groupColors,omitempty"`
ProductByGroup map[string]Product `json:"productByGroup,omitempty"`
Features map[string]bool `json:"features,omitempty"` // feed/deploy/qa/teamGroups
QaDashboardURL string `json:"qaDashboardUrl,omitempty"`
StaleThresholdHours int `json:"staleThresholdHours,omitempty"` // 0 = the client default (72)
// AttachmentCacheMB caps the on-disk attachment byte cache. 0 = package
// default (512 MB); a negative value is treated as 0.
AttachmentCacheMB int `json:"attachmentCacheMB,omitempty"`
// Sync periods in seconds. 0 means use DefaultSyncIntervalSec /
// DefaultReconcileIntervalSec. Watch re-reads config on each cycle when
// opts.Reload is set (serve, desktop, and workspace all pass config.Load),
// so a change applies on the next tick without restarting the process.
SyncIntervalSec int `json:"syncIntervalSec,omitempty"`
ReconcileIntervalSec int `json:"reconcileIntervalSec,omitempty"`
// Notify enables OS desktop notifications from the sync watch loop after
// new personal-feed events. Default true when absent; set false to opt out.
// Pointer so omitempty can distinguish "unset" from explicit false.
Notify *bool `json:"notify,omitempty"`
// UpdateCheck enables the once-per-day GitHub release lookup that surfaces
// a newer version on sync/status/serve bootstrap. Default true when absent;
// set false to opt out (restores the prior "outbound is only Jira" model).
UpdateCheck *bool `json:"updateCheck,omitempty"`
// Appearance is the look of the web/desktop UI. Nil (or empty Theme) means
// "system" and is not written — the default is not persisted. A pointer so
// encoding/json omitempty can drop the block; a zero struct would write {}.
Appearance *Appearance `json:"appearance,omitempty"`
// UI is the user color-override block (GDK-786/791): token overrides,
// per-palette overlays, and per-data-key inks. Nil means nothing is
// overridden and is not written. See uitokens.go for the write contract.
UI *UIConfig `json:"ui,omitempty"`
// Actor is the workspace-default acting identity for agent writes to an
// issuetap origin (GDK-586): the X-Issuetap-Actor slug (the origin
// accountId, verbatim) plus an optional display name. Nil or empty slug
// means unset; GADAK_ACTOR wins over it and Claude Code is auto-detected
// when neither is set (internal/config/actor.go ResolveActor). Nil blocks
// are not written.
Actor *ActorConfig `json:"actor,omitempty"`
// Confluence, when non-nil, enables the wiki-page mirror (second source).
// Spaces empty means every *global* space — not every space the account can
// see, which is what this comment used to claim and what a warning written
// from it went on to tell users. Cloud gives each person a personal space,
// so an unfiltered listing is mostly noise; personal spaces are mirrored
// only when named here. The rule itself lives in internal/sync/confluence.go.
Confluence *ConfluenceConfig `json:"confluence,omitempty"`
// Linear, when non-nil, enables the Linear issue mirror (third source,
// read-only — GDK-263). Unlike Confluence it carries its own credential:
// APIKey is a Linear personal API key and gets the same article-8
// treatment as Token — never a log line, a snapshot, or a team export
// (teamconfig classifies the whole block never-export).
Linear *LinearConfig `json:"linear,omitempty"`
// contains filtered or unexported fields
}
Config is the on-disk profile document (~/.gadak/config.json, or ~/.gadak/profiles/<name>/config.json). Credentials share this file with site settings but never reach the database, a log, or a snapshot; the file is written 0600.
func LoadFor ¶
LoadFor reads config.json for the named profile. Missing file returns an empty Config with dir set (not an error), matching Load's convention.
LoadFor is the single owner of the field-mapping rewrite rule: leftover fieldMap/editableFields are always normalized in memory so callers never see the legacy shape. A failed Save is a stderr warning, not a load error — a read-only home must still serve (the rewrite is a convenience, not a precondition). Callers (serve, desktop, MCP, status) must not re-implement this tolerate-the-write-failure rule.
func (*Config) ApplyTokenExpiry ¶ added in v0.14.2
ApplyTokenExpiry writes TokenExpiresAt and TokenExpirySource. A non-empty userRaw is source "user". An empty userRaw with a parseable verifiedAt assumes verifiedAt + 365 days. Empty userRaw and no verifiedAt leaves the fields untouched (offline init must not invent a date).
func (*Config) ApplyTokenExpiryIfNeeded ¶ added in v0.14.2
ApplyTokenExpiryIfNeeded is the init path: do not reset an existing date when the token was kept and the user did not supply a new one. Connect and replace-token always call ApplyTokenExpiry (they always store a token).
func (*Config) ApplyVerifiedIdentity ¶ added in v0.13.0
ApplyVerifiedIdentity stamps the three fields a successful Jira /myself call produces. CLI init uses this; the server onboarding path writes the same keys inline (internal/server/onboarding.go, write.go) — that package is outside this change's file boundary.
func (*Config) ClearTokenExpiry ¶ added in v0.14.2
func (c *Config) ClearTokenExpiry()
ClearTokenExpiry drops the stored date. Called when the credential is deleted.
func (*Config) CustomFieldsStatus ¶ added in v0.17.0
CustomFieldsStatus is the object `gadak status --json` and MCP gadak_status emit for field-mapping visibility (GDK-522). mapped is the effective spec count (FieldSpecs, so a leftover fieldMap still counts). applied_at is omitted when discovery has never been recorded. Does not scan the mirror.
func (*Config) Directory ¶ added in v0.16.0
Directory is the profile directory this Config was loaded from (LoadFor). Empty on a Config that was never loaded.
func (*Config) EffectiveLocale ¶ added in v0.17.0
EffectiveLocale is the origin's display-name language (GDK-597). Empty on disk means English. Only a standalone workspace consumes it; see Locale.
func (*Config) EffectiveReconcileIntervalSec ¶
EffectiveReconcileIntervalSec returns the reconcile interval Watch should use.
func (*Config) EffectiveSyncIntervalSec ¶
EffectiveSyncIntervalSec returns the interval Watch should use.
func (*Config) EffectiveTheme ¶ added in v0.15.2
EffectiveTheme is the UI theme id. Empty on disk means "system".
func (*Config) FieldSpecs ¶
FieldSpecs returns the effective field specs. After LoadFor, this is Fields. In-memory configs that never went through LoadFor (tests, settings PUT) may still carry only FieldMap; synthesize the same Label/Role defaults the migration writes so those callers keep working until they switch.
func (*Config) HasAtlassianCredential ¶ added in v0.17.0
HasAtlassianCredential reports whether Jira-family origin writes are possible: standalone, site+email+token, or a pairing remote-origin.json. Linear's key is a different origin and is not counted here — callers that mean "can I talk to Jira / Confluence / issuetap" use this, not HasCredential.
func (*Config) HasCredential ¶
HasCredential reports whether writes and the attachment proxy are possible. A standalone workspace has no site token; writes still go through the in-process origin, so it reports true. A connected workspace still requires site+email+token — that gate is not weakened. A Linear API key also counts: Linear writes are possible, so the workspace is configured. Jira-only callers that must not treat a Linear key as a site token use HasAtlassianCredential.
func (*Config) HasLinearCredential ¶ added in v0.17.0
HasLinearCredential reports whether Linear origin writes are possible: a linear block with an API key. Presence of an empty block is not a credential — origin.Linear still refuses a missing key.
func (*Config) IsStandalone ¶ added in v0.16.0
IsStandalone reports a workspace whose origin is the in-process issuetap snapshot, not a Jira site.
func (*Config) NormalizeLegacyFields ¶ added in v0.15.0
NormalizeLegacyFields converts leftover FieldMap/EditableFields into Fields using FieldSpecs() synthesis plus the EditableFields overlay (legacy wins per alias) and clears the legacy maps. No disk write — LoadFor persists. shape names the keys that were present, for the rewrite log line.
func (*Config) NotifyEnabled ¶
NotifyEnabled is true unless the user set notify: false. Absent means on.
func (*Config) ProfileName ¶ added in v0.17.0
ProfileName is the profile this Config was loaded for (LoadFor); "" is the root profile. A Config that was never loaded also answers "" — callers that need the active profile as a fallback must check Directory() first, the way origin's profileDir does. This exists so a Config loaded for a mounted workspace carries its own identity: comparing a probe answer against the process-global Profile() routed a mounted workspace's origin calls to the primary profile's route (GDK-677).
func (*Config) Save ¶
Save writes the file atomically with mode 0600. When c.dir is set (LoadFor), the write goes to that profile's config.json; otherwise the active Path(). The profile directory (and ~/.gadak / GADAK_HOME when writing the default profile) is created and owner-writable dirs are tightened to 0700 (fsperm.EnsurePrivateDir); chmod failures are logged only.
func (*Config) SyncFrozen ¶ added in v0.17.0
SyncFrozen is the only question the sync gate asks. Deliberately separate from HasCredential: a standalone workspace has a credential by definition (see HasCredential), and writes must keep working here.
func (*Config) TokenExpiryAt ¶ added in v0.14.2
func (c *Config) TokenExpiryAt(now time.Time) TokenExpiry
TokenExpiryAt is AssessTokenExpiry against this config and now.
func (*Config) UpdateCheckEnabled ¶
UpdateCheckEnabled is true unless the user set updateCheck: false. Absent means on.
func (*Config) WorkspaceKind ¶ added in v0.16.0
WorkspaceKind is KindStandalone or KindConnected. Empty/unknown Kind is connected so an existing config.json is unchanged.
type ConfluenceConfig ¶
type ConfluenceConfig struct {
Spaces []string `json:"spaces,omitempty"`
}
ConfluenceConfig is the optional wiki-page source. Presence (non-nil) is the on switch; same site/email/token as Jira, REST base under /wiki.
type DimCatalogEntry ¶ added in v0.17.3
type DimCatalogEntry struct {
Axis string `json:"axis"`
Name string `json:"name"`
CSSVar string `json:"cssVar"`
Tier string `json:"tier"`
Unit string `json:"unit"`
Default string `json:"default"`
Min *float64 `json:"min"`
Max *float64 `json:"max"`
Relations []string `json:"relations"`
}
DimCatalogEntry is one row of the read-only dimension-token discovery catalog — the sibling of tokencheck.CatalogToken on the color side. Min/Max read null where a relation owns the bound (row-excerpt, detail-max) or the token is locked, mirroring the embedded DimToken; Relations is always present, [] when the token stands alone, like the color catalog's rules field. Locked tokens (docked-min) stay in the same list, tier-marked — the color catalog's locked notation.
type FieldSpec ¶
type FieldSpec struct {
Alias string `json:"alias"` // stable key: ascii slug of the name, else cf_<id>
Label string `json:"label"` // Jira display name, in the account's language
IDs []string `json:"ids"` // all field ids sharing the name, most-filled first
Role string `json:"role"` // body | facet | user | plain
Kind string `json:"kind,omitempty"` // editor: option | multi_option | user | version_array | ""
Auto bool `json:"auto,omitempty"` // discovery-owned; regenerated on re-apply
}
FieldSpec is one logical custom field. Jira creates a separate field id per board template for the same concept, so one spec can carry several ids; the sync coalesces the first filled value (measured fact: 57 of 353 custom field names on one large site map to 2+ ids).
type GroupRule ¶
type GroupRule struct {
Group string `json:"group"`
Projects []string `json:"projects,omitempty"`
Labels []string `json:"labels,omitempty"`
Components []string `json:"components,omitempty"`
}
GroupRule classifies an issue into a group. Rules are read top-down and the first match wins. Conditions AND together; the list inside one condition ORs. An empty condition is always true. For classification that does not fit these three lists, set GroupQuery instead of growing this struct.
type LinearConfig ¶ added in v0.16.1
type LinearConfig struct {
APIKey string `json:"apiKey,omitempty"`
TeamIDs []string `json:"teamIds,omitempty"`
}
LinearConfig is the optional Linear issue source. Presence (non-nil) is the on switch, matching ConfluenceConfig. TeamIDs are Linear team uuids (never display keys — the same localization/rename hazard as Jira names); empty means every team the key can see.
type Member ¶
type Member struct {
Email string `json:"email"`
Name string `json:"name,omitempty"`
DisplayName string `json:"display_name,omitempty"`
Group string `json:"group,omitempty"`
Department string `json:"department,omitempty"`
JobRole string `json:"job_role,omitempty"`
JiraAccountID string `json:"jira_account_id,omitempty"`
AvatarURL string `json:"avatar_url,omitempty"`
}
Member is one entry of the static member directory injected through settings. It merges into bootstrap's members[], which is what gives an avatar its ring, tooltip, and team preset.
type ResolvedActor ¶ added in v0.17.0
type ResolvedActor struct {
Slug string `json:"slug"`
Name string `json:"name,omitempty"`
Source string `json:"source"`
}
ResolvedActor is the ladder's answer: Slug is the X-Issuetap-Actor value, Name the optional X-Issuetap-Actor-Name, Source which rung produced it.
func ResolveActor ¶ added in v0.17.0
func ResolveActor(cfg *Config) (ResolvedActor, bool)
type Setting ¶ added in v0.15.2
type Setting struct {
Path string
Root string
Description string
Get func(*Config) any
Set func(*Config, json.RawMessage) error
}
Setting is one dotted path `gadak config` may get or set. Root is the matching PUT /api/settings JSON key so a coverage test can keep the two surfaces on one schema.
func SettingByPath ¶ added in v0.15.2
SettingByPath looks up one catalog entry. Concrete ui.tokens.<axis>.<name> leaves are not enumerated in the catalog listing (that would add one row per token and bury the rest of the table); they resolve here from the four ui.tokens.<axis>.<name> templates.
type TokenExpiry ¶ added in v0.14.2
type TokenExpiry struct {
State string `json:"state"`
DaysLeft *int `json:"days_left,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"`
Source string `json:"source,omitempty"`
Urgent bool `json:"urgent,omitempty"`
// Message is the one English warning line. Empty when there is nothing
// to say (ok / unknown).
Message string `json:"message,omitempty"`
}
TokenExpiry is the computed warning state for a stored API token. DaysLeft is nil when State is unknown.
func AssessTokenExpiry ¶ added in v0.14.2
func AssessTokenExpiry(now time.Time, expiresAt, source string) TokenExpiry
AssessTokenExpiry maps (now, stored expiry, source) onto a warning state. Missing or unparseable dates are unknown — there is nothing to warn from.
DaysLeft is remaining/elapsed time in whole 24h periods, truncated toward zero. now >= expiresAt is expired (including a 0-day remaining of exactly now). 15 days is still ok; 14 days is the first warning; 3 days is urgent.
func (TokenExpiry) WarningLine ¶ added in v0.14.2
func (e TokenExpiry) WarningLine() string
WarningLine is the English sentence status and sync_health surface. Empty when there is nothing to warn about.
type UIConfig ¶ added in v0.17.2
type UIConfig struct {
Tokens *UITokens `json:"tokens,omitempty"`
// TokensByTheme overlays Tokens for one palette. Keys are palette ids;
// ids outside today's catalog are carried with a warning (theme ids are
// an open set by design — ValidateTheme accepts any [a-z0-9-]+).
TokensByTheme map[string]*UITokens `json:"tokensByTheme,omitempty"`
// DataColors is family → key → hex. Families: label, type, status.
DataColors map[string]map[string]string `json:"dataColors,omitempty"`
}
UIConfig is the `ui` block of config.json. Nil means "nothing overridden" and is not written.
type UITokens ¶ added in v0.17.2
type UITokens struct {
Colors map[string]string `json:"colors,omitempty"`
Spacing map[string]string `json:"spacing,omitempty"`
Layout map[string]string `json:"layout,omitempty"`
Type map[string]string `json:"type,omitempty"`
}
UITokens is one token override set. Colors are palette-scoped hex overrides; the dimension axes (spacing, layout, type) are palette-agnostic CSS lengths that ride the same wrapper but never the per-theme overlay. Values are bare token names ("accent", "row") or CSS variable names ("--color-accent", "--spacing-row") — tokencheck normalizes both.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Dimension-token validation (dim-token chunk 1) — the sibling of the color side of this package.
|
Dimension-token validation (dim-token chunk 1) — the sibling of the color side of this package. |