config

package
v0.16.1 Latest Latest
Warning

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

Go to latest
Published: May 24, 2026 License: MIT Imports: 11 Imported by: 0

README

pkg/config

Configuration management for Confab's own config, Claude Code's settings file, and bundled skill file content.

Hook install/uninstall logic lives in pkg/hookconfig. This package owns the generic plumbing — atomic settings updates, settings struct, paths — and the bundled skill templates installed by provider clients.

Files

File Role
config.go ClaudeSettings struct + AtomicUpdateSettings (read/modify/write ~/.claude/settings.json with mtime-based optimistic locking). Generic accessor helpers: GetHooksMap, GetEventHooks, SetEventHooks. Tool-name constants used by pkg/hookconfig.
upload.go Confab config: read/write ~/.confab/config.json, validation, default redaction patterns, ParseLogLevel
paths.go Claude state-dir resolution (~/.claude) with CONFAB_CLAUDE_DIR override. ~/.confab paths use pkg/confabpath.
bundled_skills.go Shared bundled-skill registry and install/uninstall/check helpers for provider-local skills/<name>/SKILL.md layouts
skill_til.go /til templates for Claude Code and Codex plus legacy Claude helper wrappers
skill_retro.go /retro templates for Claude Code and Codex plus legacy Claude helper wrappers

Two Config Systems

Confab config (~/.confab/config.json)

Managed by upload.go. Contains backend URL, API key, log level, auto-update flag, and redaction settings. This is Confab's own config — we control the schema entirely.

Claude Code settings (~/.claude/settings.json)

Managed by config.go. Contains hooks that Claude Code reads to fire events. We install/uninstall hooks here, but Claude Code owns the file and other tools may write to it concurrently.

Bundled provider skills

Managed by bundled_skills.go, skill_til.go, and skill_retro.go (and future skill_*.go files). Skills are standalone SKILL.md files installed by provider clients into their local skill layouts: Claude uses ~/.claude/skills/<name>/SKILL.md; Codex uses ~/.codex/skills/<name>/SKILL.md. If an existing SKILL.md has been customized by the user, install backs it up to SKILL.md.bak before overwriting; if the backup write fails, the install aborts rather than silently overwrite.

Key Types

  • UploadConfig — Confab's configuration (backend URL, API key, redaction settings)
  • ParseLogLevel(string) — translates a config log_level value to logger.Level. Called from pkg/loginit at process startup.
  • ClaudeSettings — Wrapper around map[string]any for Claude Code settings, preserving unknown fields
  • ErrHooksTypeMismatch — Exported sentinel error returned when the "hooks" field in settings.json exists but is not a JSON object. Callers can check errors.Is(err, ErrHooksTypeMismatch) and surface a clear message asking users to fix the file manually.
  • RedactionConfig — Redaction enabled flag, use_default_patterns, custom pattern list
  • RedactionPattern — Individual redaction pattern (name, regex, type, capture group, field pattern)

How to Extend

Adding a new Confab config field
  1. Add the field to UploadConfig in upload.go
  2. Add validation in SaveUploadConfig() if needed
  3. Update the setup flow in cmd/setup.go to prompt for / set the field
Adding a new hook type

Hook install/uninstall lives in pkg/hookconfig — see that package's README. The wiring into cmd/ flows through pkg/provider's Provider interface: cmd/hooks.go and cmd/setup.go call p.InstallHooks(), which delegates to hookconfig per provider.

Adding a new bundled skill
  1. Add the provider-rendered template content in skill_<name>.go.
  2. Add the skill name to bundledSkillNames and route it in bundledSkillTemplate.
  3. Keep path/layout decisions in pkg/provider; pkg/config only receives a state directory and provider name.
  4. Add/update tests for Claude and Codex installs so both provider paths stay covered.

Invariants

  • Settings writes must use AtomicUpdateSettings(). This provides read-modify-write with mtime-based optimistic locking and exponential backoff retry (max 10 attempts). Never read + write separately — concurrent Claude Code sessions will clobber each other.
  • Config file permissions: 0600 for ~/.confab/config.json (contains API key), 0600 for ~/.claude/settings.json.
  • Directory permissions: 0700 for ~/.confab/ and ~/.claude/ directories created by Confab. Restrictive permissions prevent other users on shared systems from reading config or API keys.
  • GetDefaultRedactionPatterns() pattern order matters. More specific patterns (e.g., sk-ant-api03-...) must come before general ones (e.g., field-name-based patterns) to avoid partial matches.

Design Decisions

ClaudeSettings uses map[string]any instead of typed structs. Claude Code's settings schema evolves rapidly and includes fields we don't manage. A typed struct would silently drop unknown fields on round-trip. The raw map preserves everything.

Mtime-based optimistic locking instead of flock. AtomicUpdateSettings() checks that the file's mtime hasn't changed between read and write. If it has, it retries with backoff. This is simpler than file locking, works cross-platform, and is sufficient for the infrequent writes that hooks installation involves.

Bundled skills use provider-rendered templates. The shipped skills share a registry, but content can differ where the harnesses expose different session IDs or local transcript layouts. Claude /til uses CLAUDE_SESSION_ID; Codex /til uses CODEX_THREAD_ID.

Testing

go test ./pkg/config/...

Tests cover atomic settings updates under concurrency, field preservation across round-trips, config validation, and bundled skill install/uninstall behavior. Hook install/uninstall tests live in pkg/hookconfig.

Dependencies

Uses: pkg/confabpath (~/.confab path-builder for getConfigPath), pkg/logger (logging from config.go, skill_*.go). paths.go deliberately does not import pkg/provider even though it owns parallel constants — pkg/provider imports pkg/hookconfig, which imports pkg/config. The duplicated ClaudeStateDirEnv constant must stay in sync between the two packages.

Used by: cmd/ (setup, login, hooks, status), pkg/daemon/ (state dir), pkg/hookconfig/ (settings struct, atomic update, tool-name constants), pkg/http/ (upload config), pkg/loginit/ (GetUploadConfig, ParseLogLevel), pkg/provider/ (provider paths, skills install), pkg/redactor/ (redaction patterns), pkg/sync/ (upload config)

Documentation

Overview

ABOUTME: Manages the /retro bundled skill — install and uninstall. ABOUTME: The skill file lives at <provider>/skills/retro/SKILL.md and enables the /retro slash command.

ABOUTME: Manages the /til bundled skill — install and uninstall. ABOUTME: The skill file lives at <provider>/skills/til/SKILL.md and enables the /til slash command.

Index

Constants

View Source
const (
	SkillProviderClaude = "claude-code"
	SkillProviderCodex  = "codex"
)
View Source
const (
	ToolNameBash              = "Bash"
	ToolNameMCPGitHubCreatePR = "mcp__github__create_pull_request"
)

Tool names for PreToolUse/PostToolUse hook matching.

View Source
const ClaudeStateDirEnv = "CONFAB_CLAUDE_DIR"

ClaudeStateDirEnv is the environment variable to override the default Claude state directory. Mirrored from pkg/provider; the two must match.

View Source
const DisableLinkFromGitHubEnv = "CONFAB_DISABLE_LINK_FROM_GITHUB"

DisableLinkFromGitHubEnv is the environment variable to disable GitHub linking. When set to any non-empty value, GitHub linking (commits and PRs) is disabled.

Variables

View Source
var ErrHooksTypeMismatch = errors.New("settings.json: 'hooks' field exists but is not a JSON object — please fix manually")

ErrHooksTypeMismatch is returned when the "hooks" field in settings.json exists but is not a JSON object. This prevents silently overwriting user config.

Functions

func AtomicUpdateSettings

func AtomicUpdateSettings(updateFn func(*ClaudeSettings) error) error

AtomicUpdateSettings performs a read-modify-write with optimistic locking. It retries up to maxRetries times if the file is modified by another process. The updateFn receives the current settings and should modify them in-place.

Race condition limitation: The mtime check and rename are not truly atomic. There's a small window (<1ms) between os.Stat() and os.Rename() where another process could modify the file. The retry mechanism mitigates but does not eliminate this race. For most use cases (CLI hook installation, infrequent config changes), the retry logic provides sufficient reliability. If truly atomic updates are required, file locking (flock) would be needed.

func BundledSkillNames added in v0.16.0

func BundledSkillNames() []string

BundledSkillNames returns the shipped skill names in install order.

func EnsureDefaultRedaction

func EnsureDefaultRedaction() (bool, error)

EnsureDefaultRedaction ensures the config has a redaction section with defaults. If redaction config already exists (even if disabled), it's left unchanged. Returns true if defaults were added, false if config already had redaction settings.

func GetBinaryPath

func GetBinaryPath() (string, error)

GetBinaryPath returns the absolute path to the confab binary

func GetClaudeStateDir

func GetClaudeStateDir() (string, error)

GetClaudeStateDir returns the Claude state directory path. Defaults to ~/.claude but can be overridden with CONFAB_CLAUDE_DIR.

func GetSettingsPath

func GetSettingsPath() (string, error)

GetSettingsPath returns the path to the Claude settings file (defaults to ~/.claude/settings.json, can be overridden with CONFAB_CLAUDE_DIR).

func InstallBundledSkill added in v0.16.0

func InstallBundledSkill(stateDir, providerName, name string) error

InstallBundledSkill writes one shipped skill to stateDir, backing up a customized existing SKILL.md beside the file before overwriting it. If the backup write fails, the install aborts rather than overwriting user content.

func InstallBundledSkills added in v0.16.0

func InstallBundledSkills(stateDir, providerName string) error

InstallBundledSkills installs every shipped skill into stateDir.

func InstallRetroSkill added in v0.15.0

func InstallRetroSkill() error

InstallRetroSkill writes the /retro skill file to ~/.claude/skills/retro/SKILL.md. If an existing file differs from the template, it is backed up as SKILL.md.bak.

func InstallTilSkill added in v0.14.0

func InstallTilSkill() error

InstallTilSkill writes the /til skill file to ~/.claude/skills/til/SKILL.md. If an existing file differs from the template, it is backed up as SKILL.md.bak.

func IsBundledSkillInstalled added in v0.16.0

func IsBundledSkillInstalled(stateDir, name string) bool

func IsLinkFromGitHubDisabled

func IsLinkFromGitHubDisabled() bool

IsLinkFromGitHubDisabled returns true if GitHub linking is disabled via environment variable.

func IsRetroSkillInstalled added in v0.15.0

func IsRetroSkillInstalled() bool

IsRetroSkillInstalled returns true if the /retro skill file exists.

func IsTilSkillInstalled added in v0.14.0

func IsTilSkillInstalled() bool

IsTilSkillInstalled returns true if the /til skill file exists.

func ParseLogLevel

func ParseLogLevel(level string) (logger.Level, error)

ParseLogLevel parses a log level string and returns the corresponding logger.Level. Empty string defaults to INFO. Unknown values return INFO plus an error.

func SaveUploadConfig

func SaveUploadConfig(config *UploadConfig) error

SaveUploadConfig writes upload configuration to ~/.confab/config.json

func SkillPath added in v0.16.0

func SkillPath(stateDir, name string) string

SkillPath returns the provider-local SKILL.md path for name.

func UninstallBundledSkill added in v0.16.0

func UninstallBundledSkill(stateDir, name string) error

func UninstallBundledSkills added in v0.16.0

func UninstallBundledSkills(stateDir string) error

UninstallBundledSkills removes every shipped skill directory from stateDir.

func UninstallRetroSkill added in v0.15.0

func UninstallRetroSkill() error

UninstallRetroSkill removes the /retro skill directory (~/.claude/skills/retro/).

func UninstallTilSkill added in v0.14.0

func UninstallTilSkill() error

UninstallTilSkill removes the /til skill directory (~/.claude/skills/til/).

Types

type ClaudeSettings

type ClaudeSettings struct {
	// contains filtered or unexported fields
}

ClaudeSettings wraps the raw settings map to preserve all fields. This is similar to Python's json.load/json.dump pattern. We intentionally avoid typed structs for hooks since the schema is controlled by Claude Code and evolves rapidly.

func NewClaudeSettings added in v0.16.0

func NewClaudeSettings() *ClaudeSettings

NewClaudeSettings returns an empty ClaudeSettings. Useful for tests and for callers that want to build a settings object before writing.

func ReadSettings

func ReadSettings() (*ClaudeSettings, error)

ReadSettings reads the Claude settings file, preserving all fields

func (*ClaudeSettings) GetEventHooks added in v0.16.0

func (s *ClaudeSettings) GetEventHooks(eventName string) []any

GetEventHooks returns the array of matchers for an event, as []any. This is a read-only operation that does not create the hooks map if it doesn't exist.

func (*ClaudeSettings) GetHooksMap added in v0.16.0

func (s *ClaudeSettings) GetHooksMap() (map[string]any, error)

GetHooksMap returns the hooks map, creating it if it doesn't exist. Returns an error if the hooks field exists but has the wrong type, to prevent silently overwriting user configuration.

func (*ClaudeSettings) MarshalJSON added in v0.16.0

func (s *ClaudeSettings) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler so callers can inspect the serialized shape directly (used by tests).

func (*ClaudeSettings) SetEventHooks added in v0.16.0

func (s *ClaudeSettings) SetEventHooks(eventName string, matchers []any) error

SetEventHooks sets the array of matchers for an event. If matchers is nil or empty, the event key is removed. If the hooks map becomes empty, it is removed from settings.

type RedactionConfig

type RedactionConfig struct {
	Enabled            bool               `json:"enabled"`
	UseDefaultPatterns *bool              `json:"use_default_patterns,omitempty"` // defaults to true if nil
	Patterns           []RedactionPattern `json:"patterns,omitempty"`
}

RedactionConfig holds redaction settings

func (*RedactionConfig) ShouldUseDefaultPatterns

func (c *RedactionConfig) ShouldUseDefaultPatterns() bool

ShouldUseDefaultPatterns returns true if default patterns should be used. Defaults to true if UseDefaultPatterns is nil.

type RedactionPattern

type RedactionPattern struct {
	Name         string `json:"name"`
	Pattern      string `json:"pattern,omitempty"`
	Type         string `json:"type"`
	CaptureGroup int    `json:"capture_group,omitempty"`
	FieldPattern string `json:"field_pattern,omitempty"`
}

RedactionPattern represents a single redaction pattern

func GetDefaultRedactionPatterns

func GetDefaultRedactionPatterns() []RedactionPattern

GetDefaultRedactionPatterns returns the default high-precision redaction patterns

type UploadConfig

type UploadConfig struct {
	BackendURL string           `json:"backend_url"`
	APIKey     string           `json:"api_key"`
	LogLevel   string           `json:"log_level,omitempty"`   // debug, info, warn, error (default: info)
	AutoUpdate *bool            `json:"auto_update,omitempty"` // nil = enabled (default), false = disabled
	Redaction  *RedactionConfig `json:"redaction,omitempty"`
}

UploadConfig holds backend upload configuration

func EnsureAuthenticated

func EnsureAuthenticated() (*UploadConfig, error)

EnsureAuthenticated reads the config and verifies it has valid credentials Returns the config if authenticated, or an error if not configured

func GetUploadConfig

func GetUploadConfig() (*UploadConfig, error)

GetUploadConfig reads upload configuration from ~/.confab/config.json

func (*UploadConfig) IsAutoUpdateEnabled added in v0.10.2

func (c *UploadConfig) IsAutoUpdateEnabled() bool

IsAutoUpdateEnabled returns whether auto-update is enabled. Defaults to true when AutoUpdate is nil (not set in config).

func (*UploadConfig) Validate

func (c *UploadConfig) Validate() error

Validate checks if the upload config is valid

Jump to

Keyboard shortcuts

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