config

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 10, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// EnvProjectRoot overrides project root discovery (walk-up from cwd).
	// Consumed by: ResolveProjectRootOverride()
	EnvProjectRoot = "OX_PROJECT_ROOT"

	// EnvSessionRecording overrides the session recording mode.
	// Consumed by: ResolveSessionRecording()
	EnvSessionRecording = "OX_SESSION_RECORDING"

	// EnvUserConfig overrides the user config file path.
	// Consumed by: LoadUserConfig()
	EnvUserConfig = "OX_USER_CONFIG"
)

Environment variable names for configuration overrides. Each variable is consumed in exactly one resolver function.

View Source
const (
	StalenessWeek  = 7 * 24 * time.Hour
	StalenessWeek2 = 14 * 24 * time.Hour
	StalenessWeek3 = 21 * 24 * time.Hour
	StalenessMonth = 30 * 24 * time.Hour

	// hint probabilities per staleness tier
	ProbabilityNone  = 0.0  // < 1 week: no hints
	ProbabilityWeek1 = 0.10 // 1 week: 10%
	ProbabilityWeek2 = 0.30 // 2 weeks: 30%
	ProbabilityWeek3 = 0.50 // 3 weeks: 50%
	ProbabilityMonth = 0.80 // 1 month+: 80%
)

Staleness thresholds and corresponding hint probabilities. Progressive nudging increases as time since last doctor run grows.

View Source
const (
	SessionRecordingDisabled = "disabled" // no recording
	SessionRecordingManual   = "manual"   // explicit start required
	SessionRecordingAuto     = "auto"     // automatic recording
)

SessionRecording constants: disabled -> manual -> auto

View Source
const (
	SessionPublishingAuto   = "auto"   // upload to ledger on session stop (default)
	SessionPublishingManual = "manual" // save locally, user uploads explicitly
)

SessionPublishing constants

View Source
const CurrentConfigVersion = "2"

CurrentConfigVersion is the latest config version supported by this ox binary Increment this when making breaking changes to .sageox config structure

View Source
const MaxDisplayNameLength = 40

MaxDisplayNameLength is the maximum allowed length (in runes) for a display name. Generous for real names/handles, constraining enough to prevent CLI layout breakage.

Variables

View Source
var ValidSessionPublishingModes = []string{SessionPublishingAuto, SessionPublishingManual}

ValidSessionPublishingModes lists all valid session publishing mode values.

ValidSessionRecordingModes lists all valid session recording mode values.

Functions

func AreSessionsEnabled

func AreSessionsEnabled() bool

AreSessionsEnabled loads user config and returns the sessions.enabled setting. This is a convenience function for use without loading the full config. Default: false

func CleanupOrphanedBackpointers

func CleanupOrphanedBackpointers(teamContextPath string) (int, error)

CleanupOrphanedBackpointers removes backpointers for workspaces that no longer exist.

func CreateLedgerMarker

func CreateLedgerMarker(checkoutPath, endpoint, repoID string) error

CreateLedgerMarker creates a checkout marker for a ledger repository.

func CreateOrUpdateProjectSymlink(projectRoot, rel, targetPath string) error

CreateOrUpdateProjectSymlink creates or updates a symlink inside the project root. The rel path is relative to projectRoot (e.g. ".sageox/ledger"). If the symlink already points to targetPath, this is a no-op.

func CreateProjectLedgerSymlink(projectRoot, repoID, ep string) error

CreateProjectLedgerSymlink creates .sageox/ledger -> user-dir ledger path.

func CreateProjectTeamSymlinks(projectRoot, teamID, ep string) error

CreateProjectTeamSymlinks creates .sageox/teams/<team_id> and .sageox/teams/primary.

Today we only support a single team per repo, but that is not a hard requirement. The "primary" symlink always points to the repo's team for convenient access.

func CreateTeamContextMarker

func CreateTeamContextMarker(checkoutPath, endpoint, teamID string) error

CreateTeamContextMarker creates a checkout marker for a team context repository.

func CreateTeamSymlink(repoName, projectRoot, teamID, ep string) error

CreateTeamSymlink creates a symlink from repo_sageox/<endpoint>/teams/<team_id> to the XDG team context directory (~/.sageox/data/<endpoint>/teams/<team_id>).

The symlink allows agents and tools working within the repository to access team context data through a path relative to the project, while the actual data lives in the centralized XDG location.

Returns nil on Windows (symlinks require Developer Mode). Returns error if symlink creation fails.

func DefaultLedgerPath

func DefaultLedgerPath(repoID, endpointURL string) string

DefaultLedgerPath returns the CANONICAL path for the ledger git checkout. Ledgers are stored in the user directory, shared across all worktrees of a repo.

Format: ~/.local/share/sageox/<endpoint_slug>/ledgers/<repo_id>/

IMPORTANT: This is the ONLY function that should determine the ledger checkout location. NEVER construct ledger paths manually. Changes to path locations require Ryan's review.

func DefaultPlanFooterAttribution

func DefaultPlanFooterAttribution() string

DefaultPlanFooterAttribution returns the canonical plan footer text. This is always-on (not config-gated) as a transparency requirement.

func DefaultSageoxSiblingDir

func DefaultSageoxSiblingDir(repoName, projectRoot string) string

DefaultSageoxSiblingDir returns the CANONICAL base sageox sibling directory for a project. This is a sibling directory to the project root that contains endpoint-namespaced ledger repos and team symlinks.

Format: <project_parent>/<repo_name>_sageox

Example: /path/to/myrepo -> /path/to/myrepo_sageox

IMPORTANT: This is the ONLY function that should determine the sibling directory location. NEVER construct sibling paths manually. Changes to path locations require Ryan's review.

func DefaultTeamContextPath

func DefaultTeamContextPath(teamID, ep string) string

DefaultTeamContextPath returns the default path for a team context repo.

For production endpoints (api.sageox.ai, app.sageox.ai, www.sageox.ai, sageox.ai):

~/.sageox/data/teams/<team_id>/

For non-production endpoints:

~/.sageox/data/<endpoint>/teams/<team_id>/

This centralized location allows all projects to share team contexts and enables daemons to discover team contexts at a known location.

Legacy format (for migration): <project_parent>/sageox_team_<team_id>_context Use LegacyTeamContextPath() to check for existing sibling-directory team contexts.

The projectRoot parameter is ignored in the new format but kept for API compatibility. The endpoint parameter determines the namespace; empty uses the current endpoint.

func DefaultTeamSymlinkPath

func DefaultTeamSymlinkPath(repoName, projectRoot, endpointURL, teamID string) string

DefaultTeamSymlinkPath returns the path where team context symlinks should be created. This is inside the sageox sibling directory, namespaced by endpoint.

Format: <project_parent>/<repo_name>_sageox/<endpoint_slug>/teams/<team_id>

Example: /path/to/myrepo with endpoint sageox.ai and team abc123 -> /path/to/myrepo_sageox/sageox.ai/teams/abc123

func DiscoverLegacyTeamContexts

func DiscoverLegacyTeamContexts(projectRoot string) ([]string, error)

DiscoverLegacyTeamContexts finds team contexts in the old sibling directory format. Searches parent directories for sageox_team_*_context directories.

func DiscoverTeamContexts

func DiscoverTeamContexts() ([]string, error)

DiscoverTeamContexts finds all team context directories across all endpoints. Scans ~/.sageox/data/<endpoint>/teams/ for each endpoint directory.

func FindProjectConfigPath

func FindProjectConfigPath() (string, error)

FindProjectConfigPath walks up from the current working directory looking for .sageox/config.json Returns the path to the config file if found, empty string if not found Stops at filesystem root

func FindProjectRoot

func FindProjectRoot() string

FindProjectRoot walks up from the current working directory looking for .sageox directory. Returns the project root if found, empty string if not found. This is useful for finding the project root without requiring a config file to exist.

OX_PROJECT_ROOT env var overrides discovery when set to a valid initialized project.

func GetConfiguredEndpoints

func GetConfiguredEndpoints(projectRoot string) []string

GetConfiguredEndpoints returns all unique endpoints configured for a project. Collects from: project config only (local config no longer has endpoint fields). Returns empty slice if no endpoints are configured.

func GetContextGitAutoCommit

func GetContextGitAutoCommit() bool

GetContextGitAutoCommit loads user config and returns the auto-commit setting. This is a convenience function for use without loading the full config. Default: true

func GetContextGitAutoPush

func GetContextGitAutoPush() bool

GetContextGitAutoPush loads user config and returns the auto-push setting. This is a convenience function for use without loading the full config. Default: false

func GetDisplayName

func GetDisplayName() string

GetDisplayName loads user config and returns the display_name setting. Returns "" if not set.

func GetRepoID

func GetRepoID(projectRoot string) string

GetRepoID returns the repo ID for the given project root. Returns empty string if the config doesn't exist or has no repo ID. The repo ID is a prefixed UUIDv7 (repo_01jfk3mab...) used for identifying the repo in context storage and API calls.

func GetSessionPublishing added in v0.3.0

func GetSessionPublishing(projectRoot string) string

GetSessionPublishing is a convenience function that returns just the publishing mode string.

func GetSessionRecording

func GetSessionRecording(projectRoot string) string

GetSessionRecording is a convenience function that returns just the mode string.

func GetUserConfigDir

func GetUserConfigDir() string

GetUserConfigDir returns the user config directory path.

Path Resolution (via internal/paths package):

Default:           ~/.sageox/config/
With OX_XDG_ENABLE: $XDG_CONFIG_HOME/sageox/ (default: ~/.config/sageox/)

The consolidated ~/.sageox/ structure provides a single discoverable location for all SageOx data. Users who prefer XDG standard locations can set OX_XDG_ENABLE=1 to use traditional XDG paths.

See internal/paths/doc.go for architecture rationale.

func IsInitialized

func IsInitialized(gitRoot string) bool

IsInitialized checks if SageOx is initialized in the given git root directory. Returns true if .sageox/config.json exists (the canonical file created by ox init).

func IsInitializedInCwd

func IsInitializedInCwd() bool

IsInitializedInCwd checks if SageOx is initialized by walking up from current directory. Returns true if .sageox/config.json is found in current dir or any parent.

func IsRepoTeamContext added in v0.2.0

func IsRepoTeamContext(projectRoot, teamID string) bool

IsRepoTeamContext returns true if the given team ID matches the repo's owning team.

func IsValidSessionPublishingMode added in v0.3.0

func IsValidSessionPublishingMode(mode string) bool

IsValidSessionPublishingMode returns true if the mode is a recognized value.

func IsValidSessionRecordingMode

func IsValidSessionRecordingMode(mode string) bool

IsValidSessionRecordingMode returns true if the mode is a recognized value.

func LegacyLedgerPath

func LegacyLedgerPath(repoName, projectRoot string) string

LegacyLedgerPath returns the old sibling-directory path for the ledger repo. This is used for migration detection and backward compatibility.

Format: <project_parent>/<repo_name>_sageox_ledger

func LegacyTeamContextPath

func LegacyTeamContextPath(teamID, projectRoot string) string

LegacyTeamContextPath returns the old sibling-directory path for a team context. This is used for migration detection and backward compatibility. Format: <project_parent>/sageox_team_<team_id>_context

func NormalizeSessionPublishing added in v0.3.0

func NormalizeSessionPublishing(mode string) string

NormalizeSessionPublishing normalizes session_publishing values. Returns "auto" for unrecognized or empty values (backward compatible default).

func NormalizeSessionRecording

func NormalizeSessionRecording(mode string) string

NormalizeSessionRecording normalizes session_recording values. Returns default (manual) for unrecognized values. Maps legacy "none" to "disabled" for backwards compatibility.

func RegisterWorkspace

func RegisterWorkspace(teamContextPath, workspaceID, projectPath string) error

RegisterWorkspace adds or updates a workspace backpointer in a team context. This should be called when a daemon starts watching a project that uses this team context.

func RemoveTeamSymlink(repoName, projectRoot, teamID, ep string) error

RemoveTeamSymlink removes the team symlink from the sibling directory. This only removes the symlink, not the actual team context data.

Returns nil on Windows. Returns nil if the symlink doesn't exist. Returns error if removal fails.

func ResolveProjectRootOverride added in v0.3.0

func ResolveProjectRootOverride() string

ResolveProjectRootOverride checks OX_PROJECT_ROOT env var for an explicit project root override. Returns the resolved path if valid, empty string otherwise. This is the single source of truth for the env var — all callers should use this.

func SanitizeDisplayName

func SanitizeDisplayName(name string) string

SanitizeDisplayName cleans a display name for safe storage and rendering. Replaces control characters with spaces (preserving word boundaries), trims whitespace, and collapses internal whitespace runs. Returns "" for whitespace-only input.

func SaveBackpointers

func SaveBackpointers(teamContextPath string, backpointers []WorkspaceBackpointer) error

SaveBackpointers writes workspace backpointers to a team context.

func SaveCheckoutMarker

func SaveCheckoutMarker(checkoutPath string, marker *CheckoutMarker) error

SaveCheckoutMarker writes the checkout marker to a ledger/team context directory. Creates the .sageox directory if it doesn't exist.

func SaveHealth

func SaveHealth(projectRoot string, h *Health) error

SaveHealth saves health data to .sageox/cache/health.json.

func SaveLocalConfig

func SaveLocalConfig(projectRoot string, cfg *LocalConfig) error

SaveLocalConfig saves the local configuration to .sageox/config.local.toml relative to projectRoot. Creates the .sageox directory if it does not exist.

func SaveProjectConfig

func SaveProjectConfig(gitRoot string, cfg *ProjectConfig) error

SaveProjectConfig saves the project configuration to .sageox/config.json relative to gitRoot

func SaveTeamConfig

func SaveTeamConfig(teamContextPath string, cfg *TeamConfig) error

SaveTeamConfig saves team configuration to <teamContextPath>/config.toml.

func SaveUserConfig

func SaveUserConfig(cfg *UserConfig) error

SaveUserConfig saves user configuration to the config directory. Uses atomic write (temp file + rename) to prevent corruption from concurrent writes or crashes mid-write.

func SetContextGitAutoCommit

func SetContextGitAutoCommit(value bool) error

SetContextGitAutoCommit loads user config, sets auto-commit, and saves. This is a convenience function for setting a single value.

func SetContextGitAutoPush

func SetContextGitAutoPush(value bool) error

SetContextGitAutoPush loads user config, sets auto-push, and saves. This is a convenience function for setting a single value.

func SetDisplayName

func SetDisplayName(name string) error

SetDisplayName loads user config, validates, sets display_name, and saves. Returns an error if the name exceeds MaxDisplayNameLength after sanitization.

func SetSessionsEnabled

func SetSessionsEnabled(value bool) error

SetSessionsEnabled loads user config, sets sessions.enabled, and saves. This is a convenience function for setting a single value.

func SiblingLedgerPath added in v0.2.0

func SiblingLedgerPath(repoName, projectRoot, endpointURL string) string

SiblingLedgerPath returns the DEPRECATED sibling-directory path for the ledger repo. This is used for migration detection when upgrading from the sibling to user-directory layout.

Format: <project_parent>/<repo_name>_sageox/<endpoint_slug>/ledger

func StringPtr

func StringPtr(s string) *string

StringPtr is a helper to create a pointer to a string value

func UpdateTeamSymlink(repoName, projectRoot, teamID, oldEndpoint, newEndpoint string) error

UpdateTeamSymlink updates an existing team symlink to point to a new target. This is useful when the endpoint changes and the symlink target needs updating.

Returns nil on Windows. Returns error if the symlink doesn't exist or update fails.

func UpdateWorkspaceActivity

func UpdateWorkspaceActivity(teamContextPath, workspaceID string) error

UpdateWorkspaceActivity updates the last active time for a workspace. Call this periodically while a daemon is active.

func ValidateDisplayName

func ValidateDisplayName(name string) error

ValidateDisplayName checks a display name after sanitization. Returns an error if the sanitized name exceeds MaxDisplayNameLength. Empty string is valid (means "use auto-derivation").

func ValidateLedgerMarker

func ValidateLedgerMarker(checkoutPath, currentEndpoint, currentRepoID string) error

ValidateLedgerMarker checks if the ledger checkout matches the current endpoint and repo. Returns nil if OK, CheckoutMarkerMismatch if mismatched.

func ValidateProjectConfig

func ValidateProjectConfig(cfg *ProjectConfig) []string

ValidateProjectConfig validates the project configuration and returns a list of validation errors

func ValidateTeamContextMarker

func ValidateTeamContextMarker(checkoutPath, currentEndpoint string) error

ValidateTeamContextMarker checks if the team context checkout matches the current endpoint. Returns nil if OK, CheckoutMarkerMismatch if mismatched.

Types

type Attribution

type Attribution struct {
	Plan    *string `yaml:"plan,omitempty" json:"plan,omitempty"`
	Commit  *string `yaml:"commit,omitempty" json:"commit,omitempty"`
	PR      *string `yaml:"pr,omitempty" json:"pr,omitempty"`
	Session *string `yaml:"session,omitempty" json:"session,omitempty"` // session URL trailer; nil=auto, ""=disabled
}

Attribution configures how ox-guided work is credited in plans, git commits, and PRs. Use pointer fields to distinguish between "not set" (nil) and "explicitly disabled" ("").

func DefaultAttribution

func DefaultAttribution() Attribution

DefaultAttribution returns the default attribution settings. These are used when no user or project config overrides are set.

func (*Attribution) GetCommit

func (a *Attribution) GetCommit() string

GetCommit returns the commit attribution value, or empty string if nil

func (*Attribution) GetPR

func (a *Attribution) GetPR() string

GetPR returns the PR attribution value, or empty string if nil

func (*Attribution) GetPlan

func (a *Attribution) GetPlan() string

GetPlan returns the plan attribution value, or empty string if nil

func (*Attribution) GetSession added in v0.3.0

func (a *Attribution) GetSession() string

GetSession returns the session attribution value, or empty string if nil

func (*Attribution) IsCommitSet

func (a *Attribution) IsCommitSet() bool

IsCommitSet returns true if commit attribution is explicitly set (including to empty string)

func (*Attribution) IsPRSet

func (a *Attribution) IsPRSet() bool

IsPRSet returns true if PR attribution is explicitly set (including to empty string)

func (*Attribution) IsPlanSet

func (a *Attribution) IsPlanSet() bool

IsPlanSet returns true if plan attribution is explicitly set (including to empty string)

func (*Attribution) IsSessionSet added in v0.3.0

func (a *Attribution) IsSessionSet() bool

IsSessionSet returns true if session attribution is explicitly set (including to empty string)

type BadgeConfig

type BadgeConfig struct {
	// SuggestionStatus tracks user response: "not_asked", "added", "declined"
	SuggestionStatus string `yaml:"suggestion_status,omitempty"`

	// LastDeclined timestamp if user chose "never" - we respect this permanently
	LastDeclined *string `yaml:"last_declined,omitempty"`
}

BadgeConfig tracks badge suggestion state across all projects.

type CheckoutMarker

type CheckoutMarker struct {
	// Type is "ledger" or "team-context"
	Type string `json:"type"`

	// Endpoint is the SageOx API endpoint this checkout is associated with
	Endpoint string `json:"endpoint"`

	// RepoID is the main repo's ID (from .repo_<id>) that this ledger belongs to.
	// Only set for ledger type, empty for team-context.
	RepoID string `json:"repo_id,omitempty"`

	// TeamID is the team ID for team-context checkouts.
	// Only set for team-context type, empty for ledger.
	TeamID string `json:"team_id,omitempty"`

	// CheckedOutAt is when the checkout was created
	CheckedOutAt time.Time `json:"checked_out_at"`

	// CheckedOutBy is the user who created the checkout (optional)
	CheckedOutBy string `json:"checked_out_by,omitempty"`
}

CheckoutMarker tracks metadata about a ledger or team context checkout. This file lives in <checkout-path>/.sageox/checkout.json and is gitignored.

func LoadCheckoutMarker

func LoadCheckoutMarker(checkoutPath string) (*CheckoutMarker, error)

LoadCheckoutMarker reads the checkout marker from a ledger/team context directory. Returns nil, nil if the marker doesn't exist.

type CheckoutMarkerMismatch

type CheckoutMarkerMismatch struct {
	CheckoutPath    string
	MarkerType      string
	MarkerEndpoint  string
	MarkerRepoID    string // for ledger
	CurrentEndpoint string
	CurrentRepoID   string // for ledger
}

CheckoutMarkerMismatch indicates a checkout is for a different endpoint or repo.

func (CheckoutMarkerMismatch) Error

func (e CheckoutMarkerMismatch) Error() string

type Config

type Config struct {
	Verbose       bool
	Quiet         bool
	JSON          bool
	Text          bool // human-readable text output (overrides JSON default)
	Review        bool // security audit mode: both human summary and machine output
	NoInteractive bool // disable spinners and TUI elements (auto-enabled in CI)
}

func Load

func Load() *Config

Load creates a Config from environment variables only. Runtime flags (--verbose, --json, etc.) are applied by the cobra layer in cli/context.go.

type ContextGitConfig

type ContextGitConfig struct {
	// AutoCommit controls whether to commit on session stop / session end.
	// Default: true
	AutoCommit *bool `yaml:"auto_commit,omitempty"`

	// AutoPush controls whether to push after commit.
	// Default: false
	AutoPush *bool `yaml:"auto_push,omitempty"`
}

ContextGitConfig holds settings for context git repo operations. These control automatic commit/push behavior during session operations.

func (*ContextGitConfig) IsAutoCommitEnabled

func (c *ContextGitConfig) IsAutoCommitEnabled() bool

IsAutoCommitEnabled returns true if auto-commit is enabled (default: true)

func (*ContextGitConfig) IsAutoPushEnabled

func (c *ContextGitConfig) IsAutoPushEnabled() bool

IsAutoPushEnabled returns true if auto-push is enabled (default: false)

type Health

type Health struct {
	LastDoctorAt    time.Time `json:"last_doctor_at"`
	LastDoctorFixAt time.Time `json:"last_doctor_fix_at"`
}

Health tracks diagnostic command execution times. Stored in .sageox/cache/health.json (machine-specific, not committed). Note: omitempty is not used on time.Time fields because encoding/json does not treat zero time as empty for nested structs.

func LoadHealth

func LoadHealth(projectRoot string) (*Health, error)

LoadHealth loads health data from .sageox/cache/health.json. Returns empty Health if file doesn't exist.

func (*Health) DoctorFixHintMessage

func (h *Health) DoctorFixHintMessage() string

DoctorFixHintMessage returns appropriate hint message based on staleness.

func (*Health) DoctorFixStaleness

func (h *Health) DoctorFixStaleness() time.Duration

DoctorFixStaleness returns duration since last doctor --fix run. Returns max duration if never run.

func (*Health) DoctorHintMessage

func (h *Health) DoctorHintMessage() string

DoctorHintMessage returns appropriate hint message based on staleness.

func (*Health) DoctorStaleness

func (h *Health) DoctorStaleness() time.Duration

DoctorStaleness returns duration since last doctor run. Returns max duration if never run.

func (*Health) RecordDoctorFixRun

func (h *Health) RecordDoctorFixRun()

RecordDoctorFixRun updates LastDoctorFixAt timestamp.

func (*Health) RecordDoctorRun

func (h *Health) RecordDoctorRun()

RecordDoctorRun updates LastDoctorAt timestamp.

func (*Health) ShouldHintDoctor

func (h *Health) ShouldHintDoctor() bool

ShouldHintDoctor returns true if a doctor hint should be shown. Uses probabilistic display based on staleness.

func (*Health) ShouldHintDoctorFix

func (h *Health) ShouldHintDoctorFix() bool

ShouldHintDoctorFix returns true if a doctor --fix hint should be shown. Uses probabilistic display based on staleness.

type LedgerConfig

type LedgerConfig struct {
	Path     string    `toml:"path"`
	LastSync time.Time `toml:"last_sync"`
}

LedgerConfig holds configuration for the local ledger git repo. Note: omitempty is intentionally not used on LastSync because go-toml v2 does not serialize time.Time fields with omitempty correctly.

func (*LedgerConfig) HasLastSync

func (c *LedgerConfig) HasLastSync() bool

HasLastSync returns true if LastSync has been set (is non-zero).

type LocalConfig

type LocalConfig struct {
	Ledger       *LedgerConfig `toml:"ledger,omitempty"`
	TeamContexts []TeamContext `toml:"team_contexts,omitempty"`
}

LocalConfig represents machine-specific config stored in .sageox/config.local.toml. This file tracks local paths for git repos (ledger and team-context) and is NOT committed to version control.

ARCHITECTURE DECISION: Why config.local.toml stores [[team_contexts]]

Team membership is inherently user-level data, but we store it per-repo because:

  • Each workspace runs its own daemon. Per-repo config means each daemon writes to its own file with no locking or coordination needed.
  • Different repos can point to different endpoints (e.g., sageox.ai vs self-hosted). Per-repo config naturally scopes team contexts to the correct endpoint.
  • A user-level file (~/) would require file locking across concurrent daemons, or namespace partitioning by endpoint — complexity that per-repo config avoids.

The daemon discovers all team contexts the user can access (via GET /api/v1/cli/repos) and records them here. This includes cross-team contexts (teams the user belongs to beyond the repo's own team). For the repo's own team, FindRepoTeamContext() has a fallback that computes the path from config.json's team_id without requiring this file.

FUTURE: Consider replacing this with a user-level file if we move to a single global daemon, which would eliminate the multi-writer locking problem.

func LoadLocalConfig

func LoadLocalConfig(projectRoot string) (*LocalConfig, error)

LoadLocalConfig loads the local configuration from .sageox/config.local.toml relative to projectRoot. Returns an empty config if the file does not exist.

func (*LocalConfig) GetLedgerPath

func (c *LocalConfig) GetLedgerPath(repoID, endpointURL string) string

GetLedgerPath returns the configured ledger path, or the default if not configured.

func (*LocalConfig) GetTeamContext

func (c *LocalConfig) GetTeamContext(teamID string) *TeamContext

GetTeamContext returns the team context config for the given team ID, or nil if not found.

func (*LocalConfig) GetTeamContextPath

func (c *LocalConfig) GetTeamContextPath(teamID, ep string) string

GetTeamContextPath returns the configured team context path, or the default if not configured. The endpoint parameter is used for the default path when no explicit path is configured.

func (*LocalConfig) RemoveTeamContext

func (c *LocalConfig) RemoveTeamContext(teamID string)

RemoveTeamContext removes a team context configuration by team ID.

func (*LocalConfig) SetLedgerPath

func (c *LocalConfig) SetLedgerPath(path string)

SetLedgerPath sets the ledger path in the config.

func (*LocalConfig) SetTeamContext

func (c *LocalConfig) SetTeamContext(teamID, teamName, path string)

SetTeamContext adds or updates a team context configuration.

func (*LocalConfig) UpdateLedgerLastSync

func (c *LocalConfig) UpdateLedgerLastSync()

UpdateLedgerLastSync updates the last sync time for the ledger. No-ops if Ledger is nil (consistent with UpdateTeamContextLastSync behavior).

func (*LocalConfig) UpdateTeamContextLastSync

func (c *LocalConfig) UpdateTeamContextLastSync(teamID string)

UpdateTeamContextLastSync updates the last sync time for a team context.

type ProjectConfig

type ProjectConfig struct {
	ConfigVersion    string   `json:"config_version,omitempty"` // tracks .sageox config version
	Org              string   `json:"org,omitempty"`
	Team             string   `json:"team,omitempty"`
	Project          string   `json:"project,omitempty"`
	ProjectID        string   `json:"project_id,omitempty"`         // API project ID (prj_xxx)
	WorkspaceID      string   `json:"workspace_id,omitempty"`       // API workspace ID (ws_xxx)
	RepoID           string   `json:"repo_id,omitempty"`            // prefixed UUIDv7 (repo_01jfk3mab...)
	RepoRemoteHashes []string `json:"repo_remote_hashes,omitempty"` // salted SHA256 hashes of remote URLs
	TeamID           string   `json:"team_id,omitempty"`            // team ID from server response
	TeamName         string   `json:"team_name,omitempty"`          // team display name from server response
	Endpoint         string   `json:"endpoint,omitempty"`           // SageOx endpoint URL (matches SAGEOX_ENDPOINT env var)

	// Legacy fields - kept for backward compatibility with old config files
	// TODO: Remove after 2026-01-31 - legacy field support
	APIBaseURL string `json:"api_base_url,omitempty"` // deprecated: use Endpoint
	WebBaseURL string `json:"web_base_url,omitempty"` // deprecated: use Endpoint

	UpdateFrequencyHours     int          `json:"update_frequency_hours"`
	LastUpdateCheckUTC       *string      `json:"last_update_check_utc,omitempty"`
	Attribution              *Attribution `json:"attribution,omitempty"`
	OfflineSnapshotStaleDays int          `json:"offline_snapshot_stale_days,omitempty"` // days until offline snapshot is considered stale (default: 7)
	// BadgeStatus tracks badge state for this specific project.
	// Values: "" (not asked), "added", "declined"
	// This enables per-project tracking of user's badge preference.
	BadgeStatus string `json:"badge_status,omitempty"`

	// SessionRecording controls automatic session recording behavior.
	// Values: "disabled" (no recording), "auto" (automatic recording), "manual" (explicit start required)
	// Empty string defaults to "auto".
	SessionRecording string `json:"session_recording,omitempty"`

	// SessionPublishing controls what happens when a session stops.
	// Values: "auto" (upload to ledger on stop), "manual" (save locally, user uploads explicitly)
	// Empty string defaults to "auto" for backward compatibility.
	SessionPublishing string `json:"session_publishing,omitempty"`
}

ProjectConfig represents the per-repository configuration stored in .sageox/config.json

func GetDefaultProjectConfig

func GetDefaultProjectConfig() *ProjectConfig

GetDefaultProjectConfig returns a ProjectConfig with default values

func GetProjectContext

func GetProjectContext() (*ProjectConfig, string, error)

GetProjectContext is a convenience function that finds and loads the project config Returns (config, configPath, error) If config is not found, returns (nil, "", nil) - not an error

func LoadProjectConfig

func LoadProjectConfig(gitRoot string) (*ProjectConfig, error)

LoadProjectConfig loads the project configuration from .sageox/config.json relative to gitRoot

func (*ProjectConfig) GetEndpoint

func (c *ProjectConfig) GetEndpoint() string

GetEndpoint returns the SageOx endpoint URL. Priority: config.Endpoint > endpoint.Get() (from SAGEOX_ENDPOINT env or default)

func (*ProjectConfig) GetOfflineSnapshotStaleThreshold

func (c *ProjectConfig) GetOfflineSnapshotStaleThreshold() time.Duration

GetOfflineSnapshotStaleThreshold returns the offline snapshot staleness threshold as a duration

func (*ProjectConfig) GitCredentials

func (c *ProjectConfig) GitCredentials() (*gitserver.GitCredentials, error)

GitCredentials returns the git credentials scoped to this project's endpoint.

func (*ProjectConfig) NeedsUpgrade

func (c *ProjectConfig) NeedsUpgrade() bool

NeedsUpgrade returns true if the config version is older than CurrentConfigVersion

func (*ProjectConfig) SetCurrentVersion

func (c *ProjectConfig) SetCurrentVersion()

SetCurrentVersion sets the config version to the current version

type ProjectContext

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

ProjectContext provides project configuration with guaranteed endpoint consistency. The endpoint is ALWAYS derived from ProjectConfig - never stored elsewhere.

Use LoadProjectContext() to create a ProjectContext for a project root, then access configuration and paths through the accessor methods.

func LoadProjectContext

func LoadProjectContext(projectRoot string) (*ProjectContext, error)

LoadProjectContext creates a ProjectContext for the given project root. Validates the root path and loads both project and local configurations.

Returns error if projectRoot is empty or configuration loading fails.

func (*ProjectContext) Config

func (p *ProjectContext) Config() *ProjectConfig

Config returns the project config.

func (*ProjectContext) DefaultLedgerPath

func (p *ProjectContext) DefaultLedgerPath() string

DefaultLedgerPath returns the default ledger path for this project. Uses the project's endpoint and repo ID for path resolution. Format: ~/.local/share/sageox/<endpoint_slug>/ledgers/<repo_id>/

func (*ProjectContext) Endpoint

func (p *ProjectContext) Endpoint() string

Endpoint returns the project's endpoint (single source of truth). Calls config.GetEndpoint() if config exists, otherwise returns empty string.

func (*ProjectContext) LedgerDir

func (p *ProjectContext) LedgerDir(repoID string) string

LedgerDir returns the path to the ledger directory for a repository. Uses the project's endpoint for path resolution.

func (*ProjectContext) LocalConfig

func (p *ProjectContext) LocalConfig() *LocalConfig

LocalConfig returns the local config (machine-specific).

func (*ProjectContext) RepoID

func (p *ProjectContext) RepoID() string

RepoID returns the repo ID from project config. Returns empty string if config doesn't exist or has no repo ID.

func (*ProjectContext) RepoName

func (p *ProjectContext) RepoName() string

RepoName returns the base name of the project root directory.

func (*ProjectContext) Root

func (p *ProjectContext) Root() string

Root returns the project root path.

func (*ProjectContext) SiblingDir

func (p *ProjectContext) SiblingDir() string

SiblingDir returns the sageox sibling directory for this project. Format: <project_parent>/<repo_name>_sageox

func (*ProjectContext) SiblingLedgerPath added in v0.2.0

func (p *ProjectContext) SiblingLedgerPath() string

SiblingLedgerPath returns the deprecated sibling-directory ledger path. Used for migration detection only.

func (*ProjectContext) TeamContextDir

func (p *ProjectContext) TeamContextDir(teamID string) string

TeamContextDir returns the path to a team context directory. Uses the project's endpoint for path resolution.

func (*ProjectContext) TeamsDataDir

func (p *ProjectContext) TeamsDataDir() string

TeamsDataDir returns the base directory for all team contexts. Uses the project's endpoint for path resolution.

type ResolvedAttribution

type ResolvedAttribution struct {
	Plan       string `json:"plan"`
	PlanFooter string `json:"plan_footer"` // exact footer text for plans ("Guided by SageOx")
	Commit     string `json:"commit"`
	PR         string `json:"pr"`
	Session    string `json:"session"` // "auto" = append when recording, "" = disabled
}

ResolvedAttribution holds the final resolved attribution values (non-pointer). Use this after merging configs for easier consumption.

func MergeAttribution

func MergeAttribution(project, user *Attribution) ResolvedAttribution

MergeAttribution merges project and user attribution with project taking precedence. Returns resolved values with defaults applied where not overridden.

Precedence (highest to lowest):

  1. Project config (repo-specific)
  2. User config (user preferences)
  3. Default values

Setting a field to empty string ("") explicitly disables that attribution type. Leaving a field unset (nil) means "use lower priority config or default".

type ResolvedSessionPublishing added in v0.3.0

type ResolvedSessionPublishing struct {
	Mode   string                 // effective mode: "auto" or "manual"
	Source SessionRecordingSource // where the setting came from (reuses same source type)
}

ResolvedSessionPublishing contains the effective publishing mode and its source.

func ResolveSessionPublishing added in v0.3.0

func ResolveSessionPublishing(projectRoot string) *ResolvedSessionPublishing

ResolveSessionPublishing determines the effective session publishing mode. Priority: project config > "auto" (default)

"auto" uploads to ledger on session stop (backward compatible default). "manual" saves locally without uploading.

type ResolvedSessionRecording

type ResolvedSessionRecording struct {
	Mode   string                 // effective mode: "disabled", "manual", or "auto"
	Source SessionRecordingSource // where the setting came from
}

ResolvedSessionRecording contains the effective mode and its source.

func ResolveSessionRecording

func ResolveSessionRecording(projectRoot string) *ResolvedSessionRecording

ResolveSessionRecording determines the effective session recording mode. Priority: OX_SESSION_RECORDING env > user config > project config > team config > "manual"

This priority ensures env vars (for pipelines) override everything, users can override team/repo settings, and teams can set defaults.

func (*ResolvedSessionRecording) IsAuto

func (r *ResolvedSessionRecording) IsAuto() bool

IsAuto returns true if recording is automatic.

func (*ResolvedSessionRecording) IsManual

func (r *ResolvedSessionRecording) IsManual() bool

IsManual returns true if recording requires explicit start.

func (*ResolvedSessionRecording) ShouldRecord

func (r *ResolvedSessionRecording) ShouldRecord() bool

ShouldRecord returns true if the mode enables any recording.

type SessionRecordingSource

type SessionRecordingSource string

SessionRecordingSource indicates where the session recording setting came from.

const (
	SessionRecordingSourceDefault SessionRecordingSource = "default" // no config, using default
	SessionRecordingSourceEnv     SessionRecordingSource = "env"     // from OX_SESSION_RECORDING env var
	SessionRecordingSourceUser    SessionRecordingSource = "user"    // from user config
	SessionRecordingSourceTeam    SessionRecordingSource = "team"    // from team defaults (future)
	SessionRecordingSourceRepo    SessionRecordingSource = "repo"    // from .sageox/config.json
)

type SessionsConfig

type SessionsConfig struct {
	// Enabled controls whether sessions are automatically recorded during agent sessions.
	// Deprecated: Use Mode instead. Kept for backward compatibility.
	// Default: false
	Enabled *bool `yaml:"enabled,omitempty"`

	// Mode controls the session recording level.
	// Values: "none", "infra", "all"
	// Default: "none" (or "all" if Enabled=true for backward compatibility)
	Mode string `yaml:"mode,omitempty"`
}

SessionsConfig holds settings for session recording.

func (*SessionsConfig) GetMode

func (c *SessionsConfig) GetMode() string

GetMode returns the effective session mode. Supports backward compatibility: if Mode is not set but Enabled=true, returns "all". Returns "none" if nothing is configured.

func (*SessionsConfig) IsEnabled

func (c *SessionsConfig) IsEnabled() bool

IsEnabled returns true if session recording is enabled (default: false) Deprecated: Use GetMode() instead

type TeamConfig

type TeamConfig struct {
	// SessionRecording controls the default session recording mode for the team.
	// Values: "disabled", "manual", "auto"
	// Empty string means no team default (fall back to system default).
	SessionRecording string `toml:"session_recording,omitempty"`

	// SessionNotification is the message shown to users when recording starts.
	// If empty, uses the default notification message.
	SessionNotification string `toml:"session_notification,omitempty"`
}

TeamConfig represents team-level configuration stored in the team context directory. Located at: <team_context_path>/config.toml

This allows teams to set default policies that apply to all team members, while still allowing individual users to override via their user config.

func LoadTeamConfig

func LoadTeamConfig(teamContextPath string) (*TeamConfig, error)

LoadTeamConfig loads team configuration from <teamContextPath>/config.toml. Returns nil, nil if the config file does not exist. Returns an error if the file exists but cannot be parsed.

type TeamContext

type TeamContext struct {
	TeamID   string    `toml:"team_id"`
	TeamName string    `toml:"team_name"`
	Slug     string    `toml:"slug,omitempty"`
	Path     string    `toml:"path"`
	LastSync time.Time `toml:"last_sync"`
}

TeamContext holds configuration for a team context git repo. Note: omitempty is intentionally not used on LastSync because go-toml v2 does not serialize time.Time fields with omitempty correctly.

func FindAllTeamContexts added in v0.3.0

func FindAllTeamContexts(projectRoot string) []TeamContext

FindAllTeamContexts returns all team contexts available to the user. Primary source is LocalConfig.TeamContexts (populated by daemon). Falls back to scanning paths.TeamsDataDir(endpoint) subdirectories. Returns empty slice (not nil) if none found.

func FindRepoTeamContext added in v0.2.0

func FindRepoTeamContext(projectRoot string) *TeamContext

FindRepoTeamContext returns the team context that belongs to this repo's team. Loads ProjectConfig.TeamID and matches it against LocalConfig.TeamContexts. Falls back to the first configured team context if no match is found.

If no team contexts are configured in config.local.toml (daemon hasn't synced yet), falls back to computing the expected path from config.json's team_id + endpoint. This ensures the repo's own team context is discoverable immediately after ox init, without waiting for the daemon to populate [[team_contexts]].

func (*TeamContext) HasLastSync

func (c *TeamContext) HasLastSync() bool

HasLastSync returns true if LastSync has been set (is non-zero).

type TeamContextHealth

type TeamContextHealth struct {
	TeamID        string
	Path          string
	Exists        bool
	IsGitRepo     bool
	LastSyncAge   time.Duration
	Workspaces    []WorkspaceBackpointer
	ActiveCount   int  // workspaces active in last 7 days
	StaleCount    int  // workspaces not active in 30+ days
	OrphanedCount int  // workspaces whose project paths no longer exist
	IsOrphaned    bool // no valid workspace references
	IsStale       bool // no activity in 30+ days
}

TeamContextHealth represents the health status of a team context directory.

func AnalyzeTeamContextHealth

func AnalyzeTeamContextHealth(teamID, teamPath string, lastSync time.Time) TeamContextHealth

AnalyzeTeamContextHealth checks the health of a team context directory.

type UserConfig

type UserConfig struct {
	DisplayName       string            `yaml:"display_name,omitempty"`
	TipsEnabled       *bool             `yaml:"tips_enabled,omitempty"`
	TelemetryEnabled  *bool             `yaml:"telemetry_enabled,omitempty"`
	SessionTermsShown *bool             `yaml:"session_terms_shown,omitempty"`
	Attribution       *Attribution      `yaml:"attribution,omitempty"`
	Badge             *BadgeConfig      `yaml:"badge,omitempty"`
	ContextGit        *ContextGitConfig `yaml:"context_git,omitempty"`
	Sessions          *SessionsConfig   `yaml:"sessions,omitempty"`
	ViewFormat        string            `yaml:"view_format,omitempty"` // "html", "text", "json" (default: "html")
}

UserConfig holds user-level configuration from config.yaml

func LoadUserConfig

func LoadUserConfig() (*UserConfig, error)

LoadUserConfig loads user configuration using standard path discovery. Checks OX_USER_CONFIG env var first, then XDG/default paths.

For tests that need to load from an explicit directory, use LoadUserConfigFrom.

func LoadUserConfigFrom added in v0.3.0

func LoadUserConfigFrom(configDir string) (*UserConfig, error)

LoadUserConfigFrom loads user configuration from the specified config directory. If configDir is empty, uses GetUserConfigDir() which respects XDG_CONFIG_HOME. This is primarily for tests that need to point at a temp directory.

func (*UserConfig) AreSessionsEnabled

func (c *UserConfig) AreSessionsEnabled() bool

AreSessionsEnabled returns whether session recording is enabled. Default: false

func (*UserConfig) AreTipsEnabled

func (c *UserConfig) AreTipsEnabled() bool

AreTipsEnabled returns true if tips are enabled (default: true)

func (*UserConfig) GetContextGitAutoCommit

func (c *UserConfig) GetContextGitAutoCommit() bool

GetContextGitAutoCommit returns whether auto-commit is enabled for context git. Default: true

func (*UserConfig) GetContextGitAutoPush

func (c *UserConfig) GetContextGitAutoPush() bool

GetContextGitAutoPush returns whether auto-push is enabled for context git. Default: false

func (*UserConfig) GetDisplayName

func (c *UserConfig) GetDisplayName() string

GetDisplayName returns the user's configured display name, or "" if not set.

func (*UserConfig) GetViewFormat

func (c *UserConfig) GetViewFormat() string

GetViewFormat returns the preferred session view format. Default: "web"

func (*UserConfig) HasSeenSessionTerms

func (c *UserConfig) HasSeenSessionTerms() bool

HasSeenSessionTerms returns true if the user has seen the session recording notice.

func (*UserConfig) IsTelemetryEnabled

func (c *UserConfig) IsTelemetryEnabled() bool

IsTelemetryEnabled returns true if telemetry is enabled (default: true)

func (*UserConfig) SetContextGitAutoCommit

func (c *UserConfig) SetContextGitAutoCommit(enabled bool)

SetContextGitAutoCommit sets the auto-commit preference for context git.

func (*UserConfig) SetContextGitAutoPush

func (c *UserConfig) SetContextGitAutoPush(enabled bool)

SetContextGitAutoPush sets the auto-push preference for context git.

func (*UserConfig) SetDisplayName

func (c *UserConfig) SetDisplayName(name string)

SetDisplayName sets the user's display name for privacy-aware rendering. Silently sanitizes the input (strips control chars, trims whitespace).

func (*UserConfig) SetSessionTermsShown

func (c *UserConfig) SetSessionTermsShown(shown bool)

SetSessionTermsShown records whether the user has seen the session recording notice.

func (*UserConfig) SetSessionsEnabled

func (c *UserConfig) SetSessionsEnabled(enabled bool)

SetSessionsEnabled sets the session recording preference.

func (*UserConfig) SetTelemetryEnabled

func (c *UserConfig) SetTelemetryEnabled(enabled bool)

SetTelemetryEnabled sets the telemetry preference

type Validator

type Validator struct{}

Validator provides reusable config validation functions

func NewValidator

func NewValidator() *Validator

NewValidator creates a new Validator instance

func (*Validator) ValidateAPIEndpoint

func (v *Validator) ValidateAPIEndpoint(endpoint string) error

ValidateAPIEndpoint validates an API endpoint URL

func (*Validator) ValidateAttribution

func (v *Validator) ValidateAttribution(attr *Attribution) []error

ValidateAttribution validates attribution configuration

func (*Validator) ValidateConfigVersion

func (v *Validator) ValidateConfigVersion(version string) error

ValidateConfigVersion validates a config version string

func (*Validator) ValidateID

func (v *Validator) ValidateID(id, prefix string) error

ValidateID validates a prefixed ID (e.g., prj_xxx, ws_xxx, repo_xxx)

func (*Validator) ValidateOrgSlug

func (v *Validator) ValidateOrgSlug(slug string) error

ValidateOrgSlug validates an organization slug slugs should be lowercase alphanumeric with hyphens (kebab-case)

func (*Validator) ValidateProjectConfig

func (v *Validator) ValidateProjectConfig(cfg *ProjectConfig) []error

ValidateProjectConfig validates a complete ProjectConfig object

func (*Validator) ValidateProjectID

func (v *Validator) ValidateProjectID(id string) error

ValidateProjectID validates a project ID (prj_xxx format)

func (*Validator) ValidateProjectSlug

func (v *Validator) ValidateProjectSlug(slug string) error

ValidateProjectSlug validates a project slug (same rules as org slug)

func (*Validator) ValidateRemoteHash

func (v *Validator) ValidateRemoteHash(hash string) error

ValidateRemoteHash validates a remote hash (SHA256 hex string)

func (*Validator) ValidateRepoID

func (v *Validator) ValidateRepoID(id string) error

ValidateRepoID validates a repo ID (repo_xxx format with UUIDv7)

func (*Validator) ValidateTeamID

func (v *Validator) ValidateTeamID(id string) error

ValidateTeamID validates a team ID

func (*Validator) ValidateTeamSlug

func (v *Validator) ValidateTeamSlug(slug string) error

ValidateTeamSlug validates a team slug (same rules as org slug)

func (*Validator) ValidateTimestamp

func (v *Validator) ValidateTimestamp(ts string) error

ValidateTimestamp validates an ISO 8601 timestamp string

func (*Validator) ValidateUpdateFrequency

func (v *Validator) ValidateUpdateFrequency(hours int) error

ValidateUpdateFrequency validates update frequency in hours

func (*Validator) ValidateUserConfig

func (v *Validator) ValidateUserConfig(cfg *UserConfig) []error

ValidateUserConfig validates a UserConfig object

func (*Validator) ValidateWorkspaceID

func (v *Validator) ValidateWorkspaceID(id string) error

ValidateWorkspaceID validates a workspace ID (ws_xxx format)

type WorkspaceBackpointer

type WorkspaceBackpointer struct {
	WorkspaceID string    `json:"workspace_id"`
	ProjectPath string    `json:"project_path"`
	LastActive  time.Time `json:"last_active"`
}

WorkspaceBackpointer tracks a workspace that references a team context. Stored as JSONL in <team_context>/.sageox/workspaces.jsonl

func LoadBackpointers

func LoadBackpointers(teamContextPath string) ([]WorkspaceBackpointer, error)

LoadBackpointers reads workspace backpointers from a team context.

Jump to

Keyboard shortcuts

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