contracts

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: May 26, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Overview

Package contracts defines the shared types and interfaces used across all ccx internal packages. Every other internal/* package imports from this package only — never from sibling packages. This isolation is what allows Phase 1 development to run in parallel git worktrees.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrProfileNotFound is returned when the requested profile name has no
	// entry in the registry.
	ErrProfileNotFound = errors.New("profile not found")

	// ErrProfileAlreadyExists is returned by profile-add when a profile with
	// the requested name is already registered.
	ErrProfileAlreadyExists = errors.New("profile already exists")

	// ErrInvalidConfigDir is returned when a path is not a valid Claude Code
	// config directory (e.g., not a directory, or unreadable).
	ErrInvalidConfigDir = errors.New("invalid config directory")

	// ErrConfigDirConflict is returned when two profiles would point at the
	// same config directory.
	ErrConfigDirConflict = errors.New("config directory already used by another profile")

	// ErrUnknownShell is returned when a shell name is not recognized by the
	// shell package (see ParseShell).
	ErrUnknownShell = errors.New("unknown shell")

	// ErrNoActiveProfile is returned when an operation requires an active
	// profile but neither CCX_ACTIVE_PROFILE nor CLAUDE_CONFIG_DIR is set.
	ErrNoActiveProfile = errors.New("no active profile")
)

Sentinel errors. All ccx packages return one of these (wrapped with %w for context) for known, expected failure modes. Tests and callers use errors.Is to detect them.

Functions

This section is empty.

Types

type DaemonStatus

type DaemonStatus struct {
	PID             int       `json:"pid"`
	Version         string    `json:"version"`
	StartedAt       time.Time `json:"started_at"`
	Port            int       `json:"port"`
	URL             string    `json:"url"`
	DBPath          string    `json:"db_path"`
	LogPath         string    `json:"log_path"`
	ExecutablePath  string    `json:"executable_path,omitempty"`
	StartToken      string    `json:"start_token,omitempty"`
	ProcessIdentity string    `json:"process_identity,omitempty"`
	ProfilesWatched int       `json:"profiles_watched"`
	Running         bool      `json:"running"`
}

DaemonStatus is the daemon's externally visible runtime state.

type Doctor

type Doctor interface {
	Run(ctx context.Context) ([]DoctorCheck, error)
}

Doctor runs diagnostic checks and reports them as a structured slice.

type DoctorCheck

type DoctorCheck struct {
	Name        string
	Status      string
	Detail      string
	Remediation string
}

DoctorCheck is one diagnostic finding. Status is "ok", "warn", or "fail".

type Event

type Event struct {
	UUID      string    `json:"uuid"`
	SessionID string    `json:"session_id"`
	Timestamp time.Time `json:"timestamp"`
	Type      string    `json:"type"`
	Project   string    `json:"project"`
	Model     string    `json:"model,omitempty"`
	Usage     *Usage    `json:"usage,omitempty"`
}

Event is a single parsed JSONL line from a Claude Code session file. The Usage field is non-nil only for assistant events that carry token counts.

type HeadroomRecommendation

type HeadroomRecommendation struct {
	Profile         string    `json:"profile"`
	Score           float64   `json:"score"`
	HeadroomPercent float64   `json:"headroom_percent"`
	Available       bool      `json:"available"`
	Reason          string    `json:"reason"`
	CooldownUntil   time.Time `json:"cooldown_until"`
	AuthStatus      string    `json:"auth_status"`
}

HeadroomRecommendation is an advisory ranking for profile selection.

type HookEvent

type HookEvent struct {
	Profile      string    `json:"profile"`
	Session      string    `json:"session"`
	Event        string    `json:"event"`
	Timestamp    time.Time `json:"timestamp"`
	Transcript   string    `json:"transcript"`
	CWD          string    `json:"cwd"`
	Model        string    `json:"model"`
	Source       string    `json:"source"`
	Permission   string    `json:"permission"`
	Reason       string    `json:"reason"`
	Error        string    `json:"error"`
	ErrorDetails string    `json:"error_details"`
	Trigger      string    `json:"trigger"`
}

HookEvent captures one daemon-facing hook event emitted by Claude Code.

type PricingTable

type PricingTable interface {
	Cost(model string, ts time.Time, usage Usage) (float64, error)
	LastUpdated() time.Time
}

PricingTable returns estimated USD cost for a given model + usage at a given timestamp. Implementations consult an embedded pricing YAML; users may override via ~/.ccx/pricing.yaml.

type Profile

type Profile struct {
	Name       string        `json:"name"         toml:"name"`
	ConfigDir  string        `json:"config_dir"   toml:"config_dir"`
	Label      string        `json:"label"        toml:"label,omitempty"`
	Color      string        `json:"color"        toml:"color,omitempty"`
	CreatedAt  time.Time     `json:"created_at"   toml:"created_at"`
	LastUsedAt time.Time     `json:"last_used_at"  toml:"last_used_at"`
	Limits     ProfileLimits `json:"limits"       toml:"limits,omitempty"`
}

Profile identifies a Claude Code account by its config directory. The ConfigDir field is the only thing that determines identity — setting CLAUDE_CONFIG_DIR to this value is what isolates the account.

type ProfileHealth

type ProfileHealth struct {
	Profile    string    `json:"profile"`
	CheckedAt  time.Time `json:"checked_at"`
	AuthStatus string    `json:"auth_status"`
	AuthDetail string    `json:"auth_detail"`
}

ProfileHealth records the latest authentication health check for a profile.

type ProfileLimits

type ProfileLimits struct {
	DailyTokenBudget  int     `json:"daily_token_budget"  toml:"daily_token_budget,omitempty"`
	WeeklyTokenBudget int     `json:"weekly_token_budget" toml:"weekly_token_budget,omitempty"`
	MonthlyUSDBudget  float64 `json:"monthly_usd_budget"  toml:"monthly_usd_budget,omitempty"`
	Priority          int     `json:"priority"            toml:"priority,omitempty"`
	SuggestEnabled    *bool   `json:"suggest_enabled"     toml:"suggest_enabled,omitempty"`
	RateLimitCooldown string  `json:"rate_limit_cooldown" toml:"rate_limit_cooldown,omitempty"`

	// PlanTier identifies the Anthropic subscription tier this profile uses.
	// One of "pro", "max5", "max20", "api". Empty disables plan-aware quota
	// tracking for this profile.
	PlanTier string `json:"plan_tier,omitempty" toml:"plan_tier,omitempty"`

	// WeeklyAnchor controls how the weekly quota window is computed. "rolling"
	// (default) counts the trailing 7 days. A weekday name ("monday".."sunday")
	// anchors the window to the most recent occurrence of that weekday at
	// 00:00 UTC.
	WeeklyAnchor string `json:"weekly_anchor,omitempty" toml:"weekly_anchor,omitempty"`

	// Caps5hTurns overrides the shipped default 5-hour-window turn cap. Zero
	// means "use the shipped default for PlanTier".
	Caps5hTurns int `json:"caps_5h_turns,omitempty" toml:"caps_5h_turns,omitempty"`

	// CapsWeeklyTurns overrides the shipped default weekly turn cap. Zero
	// means "use the shipped default for PlanTier".
	CapsWeeklyTurns int `json:"caps_weekly_turns,omitempty" toml:"caps_weekly_turns,omitempty"`
}

ProfileLimits configures optional per-profile budget and headroom behavior. Zero values mean no explicit limit is configured.

type ProfileQuota

type ProfileQuota struct {
	Profile      string      `json:"profile"`
	PlanTier     string      `json:"plan_tier"`
	Window5h     QuotaWindow `json:"window_5h"`
	WindowWeekly QuotaWindow `json:"window_weekly"`
}

ProfileQuota is the per-profile response shape returned by GET /api/quota. Both windows are always present; their Cap field is zero when no plan tier is configured for the profile.

type QuotaWindow

type QuotaWindow struct {
	Used     int       `json:"used"`
	Cap      int       `json:"cap"`
	Pct      float64   `json:"pct"`
	ResetsAt time.Time `json:"resets_at"`
}

QuotaWindow describes turn usage within a single quota window (rolling 5h or weekly). Cap is zero when the owning profile has no PlanTier configured.

type RecommendationEvent

type RecommendationEvent struct {
	Profile        string              `json:"profile"`
	Level          RecommendationLevel `json:"level"`
	Reason         string              `json:"reason"`
	Suggested      string              `json:"suggested,omitempty"`
	Quota5hPct     float64             `json:"quota_5h_pct"`
	QuotaWeeklyPct float64             `json:"quota_weekly_pct"`
	Timestamp      time.Time           `json:"timestamp"`
}

RecommendationEvent is the payload emitted over /api/recommendations/live when the daemon detects a profile crossing a pressure threshold and a switch may be warranted. Suggested is empty when no sibling has more headroom than the crossed profile.

type RecommendationLevel

type RecommendationLevel string

RecommendationLevel categorizes the urgency of a streamed pressure-driven recommendation. Thresholds (warn/soft/hard) are evaluator-defined.

const (
	// RecommendationWarn signals a profile crossed the early-warning threshold.
	RecommendationWarn RecommendationLevel = "warn"
	// RecommendationSoft signals a profile crossed the soft-penalty threshold.
	RecommendationSoft RecommendationLevel = "soft"
	// RecommendationHard signals a profile is at or above its hard cap.
	RecommendationHard RecommendationLevel = "hard"
)

type Scanner

type Scanner interface {
	// Scan walks all JSONL files under profile.ConfigDir/projects/ and emits
	// Events on the returned channel. The channel is closed when scanning
	// completes or when ctx is cancelled. Errors are logged but do not abort
	// the scan; truly fatal errors return early via the error channel.
	Scan(ctx context.Context, profile Profile) (<-chan Event, <-chan error)
}

Scanner walks a profile's JSONL files and emits parsed Events. Implementations are expected to use the Store's scan-cursor API (via the host package wiring) to avoid re-reading already-consumed bytes.

type SessionQuery

type SessionQuery struct {
	Profile string    `json:"profile"`
	Status  string    `json:"status"`
	Since   time.Time `json:"since"`
	Limit   int       `json:"limit"`
}

SessionQuery filters session telemetry rows.

type SessionTelemetry

type SessionTelemetry struct {
	Profile        string    `json:"profile"`
	Session        string    `json:"session"`
	Transcript     string    `json:"transcript"`
	CWD            string    `json:"cwd"`
	Model          string    `json:"model"`
	Source         string    `json:"source"`
	Permission     string    `json:"permission"`
	StartedAt      time.Time `json:"started_at"`
	EndedAt        time.Time `json:"ended_at"`
	LastSeenAt     time.Time `json:"last_seen_at"`
	Status         string    `json:"status"`
	EndReason      string    `json:"end_reason"`
	FailureError   string    `json:"failure_error"`
	FailureDetails string    `json:"failure_details"`
	CompactCount   int       `json:"compact_count"`
}

SessionTelemetry is the current aggregate state for one Claude Code session.

type Shell

type Shell int

Shell identifies a shell flavor for the purpose of emitting init scripts and `ccx use` shell-eval output.

const (
	// ShellUnknown represents an unrecognized shell.
	ShellUnknown Shell = iota
	// ShellZsh represents zsh.
	ShellZsh
	// ShellBash represents bash.
	ShellBash
	// ShellFish represents fish.
	ShellFish
	// ShellPowerShell represents PowerShell.
	ShellPowerShell
)

func ParseShell

func ParseShell(s string) (Shell, bool)

ParseShell parses a shell name. Accepts "zsh", "bash", "fish", "pwsh", "powershell". Returns (ShellUnknown, false) for unknown input.

func (Shell) String

func (s Shell) String() string

String returns the canonical name of the shell.

type ShellEmitter

type ShellEmitter interface {
	// EmitUseScript returns the script that, when eval'd by the user's shell,
	// activates the given profile. The script sets CLAUDE_CONFIG_DIR and
	// CCX_ACTIVE_PROFILE.
	EmitUseScript(profile Profile, shell Shell) (string, error)

	// EmitInitScript returns the rc-file snippet the user pastes into their
	// shell config once. The snippet defines a wrapper function so `ccx use foo`
	// works without `eval`.
	EmitInitScript(shell Shell) (string, error)
}

ShellEmitter generates shell-specific snippets for `ccx use` and `ccx init`.

type Store

type Store interface {
	// Profile CRUD
	SaveProfile(ctx context.Context, p Profile) error
	GetProfile(ctx context.Context, name string) (Profile, error)
	ListProfiles(ctx context.Context) ([]Profile, error)
	DeleteProfile(ctx context.Context, name string) error

	// Event ingestion. Events come from raw JSONL and do not carry profile
	// context, so callers must pass the profile name explicitly.
	InsertEvents(ctx context.Context, profileName string, events []Event) error

	// Usage queries
	QueryUsage(ctx context.Context, q UsageQuery) ([]UsageRow, error)

	// Hook telemetry and session state
	InsertHookEvent(ctx context.Context, profileName string, event HookEvent) error
	UpsertSessionTelemetry(ctx context.Context, profileName string, event HookEvent) error
	QuerySessions(ctx context.Context, q SessionQuery) ([]SessionTelemetry, error)
	QueryRecentFailures(ctx context.Context, profileName string, since time.Time) ([]HookEvent, error)
	SaveProfileHealth(ctx context.Context, health ProfileHealth) error
	GetProfileHealth(ctx context.Context, profileName string) (ProfileHealth, error)

	// Scan cursors (for incremental scanning)
	GetCursor(ctx context.Context, profileName, filePath string) (offset int64, inode uint64, err error)
	SetCursor(ctx context.Context, profileName, filePath string, offset int64, inode uint64) error

	// Lifecycle
	Migrate(ctx context.Context) error
	Close() error
}

Store persists profiles and events. SQLite is the v0.1 implementation, but the interface is deliberately storage-agnostic.

type TimeRange

type TimeRange struct {
	Start time.Time
	End   time.Time
}

TimeRange is a closed interval [Start, End] used for usage queries.

func (TimeRange) Contains

func (r TimeRange) Contains(t time.Time) bool

Contains reports whether t falls within the closed interval [Start, End].

type Usage

type Usage struct {
	InputTokens       int `json:"input_tokens"`
	OutputTokens      int `json:"output_tokens"`
	CacheReadTokens   int `json:"cache_read_tokens"`
	CacheCreateTokens int `json:"cache_create_tokens"`
}

Usage holds the token counts for a single Claude Code event. All fields are non-negative. Token counts come from the upstream JSONL `message.usage` block.

func (Usage) Add

func (u Usage) Add(v Usage) Usage

Add returns the element-wise sum of u and v.

func (Usage) TotalTokens

func (u Usage) TotalTokens() int

TotalTokens returns the sum of all four token counts. Useful for one-number usage displays, but cost calculations should use the per-bucket fields because each bucket has a different rate.

type UsageQuery

type UsageQuery struct {
	Profile string
	Project string
	Range   TimeRange
}

UsageQuery filters and groups events for the Store.QueryUsage method. An empty Profile means "all profiles." An empty Project means "all projects."

type UsageRow

type UsageRow struct {
	Profile      string    `json:"profile"`
	Project      string    `json:"project,omitempty"`
	Model        string    `json:"model,omitempty"`
	Day          time.Time `json:"day"` // truncated to start of day in UTC
	Usage        Usage     `json:"usage"`
	SessionCount int       `json:"session_count"`
	EstimatedUSD float64   `json:"estimated_usd"` // populated by the caller after pricing lookup
}

UsageRow is one aggregated row returned by Store.QueryUsage. Aggregation granularity (per-profile, per-day, per-project) is determined by the concrete Store implementation.

Jump to

Keyboard shortcuts

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