pool

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package pool owns the canonical filesystem layout, account model, and per-account lifecycle helpers for cc-pool.

Two distinct trees exist and must not be confused:

  • ~/.claude The canonical Claude Code config dir. NEVER moved. It is the shared base AND acct-00, so plain `claude` keeps working untouched.
  • ~/.cc-pool/ cc-pool's OWN state (sqlite db, daemon socket, logs), plus accounts/ holding the pool account dirs (acct-01, acct-02, ...). Each account dir is a real, unique path so it gets its own Keychain item.

Index

Constants

View Source
const AcctZero = 0

AcctZero is the account index of ~/.claude itself.

View Source
const DefaultFreshFor = 60 * time.Second

DefaultFreshFor is the default cache window for live selection.

View Source
const RefreshLeadTime = 10 * time.Minute

RefreshLeadTime is how close to expiry an idle account's token is refreshed preemptively (the spec's "<10 min" pre-flight window).

Variables

View Source
var ErrNeedsLogin = errors.New("account needs re-login (refresh token missing or revoked)")

ErrNeedsLogin indicates the stored refresh token is gone/revoked and the account must be re-logged-in interactively.

View Source
var ErrNoAccounts = errors.New("no accounts in the pool")

ErrNoAccounts means the pool is empty (run `clp init`/`clp add`).

View Source
var ErrNoneAvailable = errors.New("no account is currently available (all rate-limited)")

ErrNoneAvailable means every account is currently rate-limited.

View Source
var ErrNotLoggedIn = errors.New("no Claude credential found — run `claude` and log in first")

ErrNotLoggedIn means plain `claude` has no stored credential, so there is nothing to register as acct-00.

View Source
var ErrRefuseAcctZeroRefresh = errors.New("refusing to POST-refresh acct-00 (shared single-use token owned by plain claude)")

ErrRefuseAcctZeroRefresh is returned by refresh when asked to POST-refresh acct-00, whose single-use refresh token is owned by plain `claude`.

View Source
var StickyTTL = time.Hour

StickyTTL is how long a cwd's last selection stays sticky. Claude's prompt cache expires well within an hour, so older records carry no continuity value. Var so tests can tune it.

Functions

func AccountDir

func AccountDir(n int) string

AccountDir returns the config-dir path for account index n.

  • n == 0 maps to ~/.claude (acct-00, canonical).
  • n >= 1 maps to ~/.cc-pool/accounts/acct-NN.

The returned path is exactly the string clp emits for CLAUDE_CONFIG_DIR and the string we hash for the per-dir Keychain service name; the two MUST stay byte-identical, so do not realpath or normalize divergently elsewhere.

func AccountDirName

func AccountDirName(n int) string

AccountDirName is the directory basename for account index n (n >= 1).

func AccountsDir

func AccountsDir() string

AccountsDir is the parent of all non-zero account dirs (~/.cc-pool/accounts).

func ClaudeDir

func ClaudeDir() string

ClaudeDir is the canonical Claude config dir (~/.claude). It is acct-00 and the shared base. It is returned as an absolute, symlink-resolved path so the value is stable and matches what we hash for the Keychain mirror item.

func DBPath

func DBPath() string

DBPath is the sqlite database path.

func EnsureAccountsDir

func EnsureAccountsDir() error

EnsureAccountsDir creates ~/.cc-pool/accounts with 0700 perms if missing.

func EnsureStateDir

func EnsureStateDir() error

EnsureStateDir creates ~/.cc-pool with 0700 perms if missing.

func Home

func Home() (string, error)

Home returns the current user's home directory.

func LogPath

func LogPath() string

LogPath is the daemon log path.

func SocketPath

func SocketPath() string

SocketPath is the daemon's unix socket path.

func StateDir

func StateDir() string

StateDir is cc-pool's own private state directory (~/.cc-pool).

Types

type CredentialStore added in v0.2.0

type CredentialStore interface {
	Read(service, account string) (*keychain.Credential, error)
	Write(service, account string, cred *keychain.Credential) error
}

CredentialStore is the slice of package keychain the Manager needs for credential reads and writes.

type InitResult

type InitResult struct {
	OverlayKind   overlay.Kind
	MirrorService string
	Account       store.Account
}

InitResult summarizes what `clp init` did.

type Manager

type Manager struct {
	Store      *store.Store
	OAuth      Refresher
	Keychain   CredentialStore
	DefaultDir string // ~/.claude (acct-00)
	// contains filtered or unexported fields
}

Manager is the high-level façade over the store, the OAuth client, and the Keychain/overlay machinery. CLI commands, the TUI wizards, and the daemon all go through it.

func Open

func Open() (*Manager, error)

Open ensures the state dir exists, opens the database, and returns a Manager.

func (*Manager) AbandonAdd

func (m *Manager) AbandonAdd(p *PendingAdd) error

AbandonAdd cleans up a prepared-but-not-finalized account dir. p must be a non-nil PendingAdd returned by PrepareAdd.

func (*Manager) AdoptRotatedToken

func (m *Manager) AdoptRotatedToken(a store.Account) error

AdoptRotatedToken re-reads an account's credential from the Keychain (where a live claude session may have rotated it) and propagates it across the account's services, keeping acct-00's mirror in lockstep with the canonical item. Used by the daemon on session check-in.

func (*Manager) Close

func (m *Manager) Close() error

Close releases resources.

func (*Manager) EnsureFreshToken

func (m *Manager) EnsureFreshToken(ctx context.Context, a store.Account, within time.Duration, allowRefresh bool) (*keychain.Credential, bool, error)

EnsureFreshToken returns the account's credential, refreshing it if the access token expires within `within` and allowRefresh is true. A successful refresh is written back to all of the account's services and logged. allowRefresh should be false for accounts with a live session (that session owns refresh).

func (*Manager) FinalizeAdd

func (m *Manager) FinalizeAdd(ctx context.Context, p *PendingAdd, label string) (*store.Account, error)

FinalizeAdd is called after the user completes the interactive login. It confirms the credential landed, re-asserts ACL ownership, validates with one usage call, and records the account. label is an optional human note.

func (*Manager) Init

func (m *Manager) Init(ctx context.Context) (*InitResult, error)

Init registers ~/.claude as acct-00 without moving it. It confirms a credential exists, mirrors that credential into a suffixed Keychain item so acct-00 is launchable via CLAUDE_CONFIG_DIR=~/.claude, picks an overlay provider, and records the account. Idempotent.

func (*Manager) Initialized

func (m *Manager) Initialized() (bool, error)

Initialized reports whether `clp init` has registered acct-00 yet.

func (*Manager) PreflightRefresh

func (m *Manager) PreflightRefresh(ctx context.Context, a store.Account) error

PreflightRefresh refreshes the chosen account's token if it expires within RefreshLeadTime and the account is idle, so the launched session starts with a healthy token. Errors are returned but non-fatal to the caller.

func (*Manager) PrepareAdd

func (m *Manager) PrepareAdd() (*PendingAdd, error)

PrepareAdd allocates the next account dir and establishes its overlay, then returns the login command the user must run. No account row or Keychain item is created yet — FinalizeAdd does that once the login lands.

func (*Manager) RecordSticky added in v0.2.0

func (m *Manager) RecordSticky(cwd string, accountID int, now time.Time) error

RecordSticky upserts the sticky record for cwd, refreshing the sliding TTL. An empty cwd is a no-op.

func (*Manager) Remove

func (m *Manager) Remove(id int, deleteCredential bool) error

Remove deletes an account from the pool: tears down its overlay, removes its suffixed Keychain item, and deletes its rows. acct-00's canonical ~/.claude and its default Keychain item are NEVER touched (only its mirror item).

func (*Manager) SampleUsage

func (m *Manager) SampleUsage(ctx context.Context, a store.Account, allowRefresh bool) (*oauth.Usage, bool, error)

SampleUsage fetches the account's usage windows, refreshing once on 401, and records a usage_sample. Returns the usage and whether the account is currently rate-limited.

func (*Manager) Select

func (m *Manager) Select(ctx context.Context, opts SelectOptions) (*SelectResult, error)

Select scores all accounts and returns the best available one.

func (*Manager) Snapshots

func (m *Manager) Snapshots(ctx context.Context, live bool, fresh time.Duration) ([]Snapshot, error)

Snapshots returns a scored view of every account. When live is true, stale usage is sampled synchronously first (the no-daemon path).

func (*Manager) StickyPick added in v0.2.0

func (m *Manager) StickyPick(cwd string, ranked []score.Result, now time.Time) (score.Result, bool)

StickyPick returns the ranked result for cwd's previously-selected account when stickiness should override ranking: the record is fresher than StickyTTL, the account still exists in ranked, and it is still usable (available with headroom). ok=false falls through to the normal pick. Best-effort by design: store errors read as a miss, never a failed select.

func (*Manager) SyncOverlay

func (m *Manager) SyncOverlay(a store.Account) error

SyncOverlay re-asserts an account's overlay so it reflects the current ~/.claude (the symlink provider links any new top-level entry; the fuse provider is a live mirror, so this just health-checks). acct-00 IS the base, so it is a no-op. Called at launch time and periodically by the daemon, which is why explicit `clp sync` is unnecessary.

type PendingAdd

type PendingAdd struct {
	Index           int
	ConfigDir       string
	KeychainService string
	OverlayKind     overlay.Kind
	LoginCommand    string
}

PendingAdd describes a half-created account awaiting interactive login.

type Refresher added in v0.2.0

type Refresher interface {
	Refresh(ctx context.Context, flightKey, refreshToken string) (*oauth.TokenResponse, error)
	Usage(ctx context.Context, accessToken string) (*oauth.Usage, error)
}

Refresher is the slice of *oauth.Client the Manager needs: token refresh and usage sampling. Consumer-defined so tests can fake provider behavior.

type SelectOptions

type SelectOptions struct {
	// Live samples usage synchronously for accounts whose latest sample is
	// older than FreshFor (the M2/no-daemon path). When false, only cached
	// samples are used (the daemon keeps them fresh).
	Live bool
	// FreshFor is the cache window for Live sampling.
	FreshFor time.Duration
	// Cwd is the caller's working directory, keying select stickiness.
	// Empty disables stickiness.
	Cwd string
}

SelectOptions tunes a selection.

type SelectResult

type SelectResult struct {
	Best   store.Account
	Result score.Result
	Ranked []score.Result
	Sticky bool // the pick honored a sticky record rather than the ranking
	// contains filtered or unexported fields
}

SelectResult is a ranked selection outcome.

func (*SelectResult) SoonestReset

func (sr *SelectResult) SoonestReset() (time.Time, bool)

SoonestReset returns the earliest 5h reset across the pool, for `--wait`.

type Snapshot

type Snapshot struct {
	Account        store.Account
	Score          float64
	HasUsage       bool
	Util5h         float64 // percent used 0..100
	Util7d         float64
	Remaining5h    float64
	Remaining7d    float64
	ActiveSessions int
	RateLimited    bool
	Stale          bool
	Resets5h       time.Time
	Resets7d       time.Time
	Burn5hPerHour  float64
	SampleAge      time.Duration
}

Snapshot is a fully-resolved per-account view for status/list rendering. It is provider- and transport-neutral so both the live CLI path and the daemon produce the same shape.

Jump to

Keyboard shortcuts

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