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
- Variables
- func AccountDir(n int) string
- func AccountDirName(n int) string
- func AccountsDir() string
- func ClaudeDir() string
- func DBPath() string
- func EnsureAccountsDir() error
- func EnsureStateDir() error
- func Home() (string, error)
- func LogPath() string
- func SocketPath() string
- func StateDir() string
- type CredentialStore
- type InitResult
- type Manager
- func (m *Manager) AbandonAdd(p *PendingAdd) error
- func (m *Manager) AdoptRotatedToken(a store.Account) error
- func (m *Manager) Close() error
- func (m *Manager) EnsureFreshToken(ctx context.Context, a store.Account, within time.Duration, allowRefresh bool) (*keychain.Credential, bool, error)
- func (m *Manager) FinalizeAdd(ctx context.Context, p *PendingAdd, label string) (*store.Account, error)
- func (m *Manager) Init(ctx context.Context) (*InitResult, error)
- func (m *Manager) Initialized() (bool, error)
- func (m *Manager) PreflightRefresh(ctx context.Context, a store.Account) error
- func (m *Manager) PrepareAdd() (*PendingAdd, error)
- func (m *Manager) RecordSticky(cwd string, accountID int, now time.Time) error
- func (m *Manager) Remove(id int, deleteCredential bool) error
- func (m *Manager) SampleUsage(ctx context.Context, a store.Account, allowRefresh bool) (*oauth.Usage, bool, error)
- func (m *Manager) Select(ctx context.Context, opts SelectOptions) (*SelectResult, error)
- func (m *Manager) Snapshots(ctx context.Context, live bool, fresh time.Duration) ([]Snapshot, error)
- func (m *Manager) StickyPick(cwd string, ranked []score.Result, now time.Time) (score.Result, bool)
- func (m *Manager) SyncOverlay(a store.Account) error
- type PendingAdd
- type Refresher
- type SelectOptions
- type SelectResult
- type Snapshot
Constants ¶
const AcctZero = 0
AcctZero is the account index of ~/.claude itself.
const DefaultFreshFor = 60 * time.Second
DefaultFreshFor is the default cache window for live selection.
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 ¶
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.
var ErrNoAccounts = errors.New("no accounts in the pool")
ErrNoAccounts means the pool is empty (run `clp init`/`clp add`).
var ErrNoneAvailable = errors.New("no account is currently available (all rate-limited)")
ErrNoneAvailable means every account is currently rate-limited.
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.
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`.
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 ¶
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 ¶
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 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.
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 ¶
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 (*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 ¶
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) 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 ¶
Initialized reports whether `clp init` has registered acct-00 yet.
func (*Manager) PreflightRefresh ¶
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
RecordSticky upserts the sticky record for cwd, refreshing the sliding TTL. An empty cwd is a no-op.
func (*Manager) Remove ¶
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
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 ¶
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.