Documentation
¶
Index ¶
- func Dir() (string, error)
- func SavePreferences(prefs Preferences) error
- func SetLastSessionForCwd(cwd, sessionID string) error
- func UpdatePreferences(mutate func(*Preferences)) error
- type FlyMachinesPreference
- type FlySpritesPreference
- type ModelPreference
- type Preferences
- type Remote
- type RemoteConfig
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Dir ¶
Dir returns the path to the clank configuration directory (default ~/.clank). Can be overridden with the CLANK_DIR environment variable; useful for running multiple clankd instances on the same machine (e.g. laptop hub + remote hub for hub-to-hub sync development).
A leading "~" or "~/..." in CLANK_DIR is expanded to the user's home directory. Without this, a literal "~/.clank-cloud" gets created as a relative directory in the cwd when CLANK_DIR is set by something that doesn't perform shell-style tilde expansion (quoted shell values, a launchd/systemd unit, a docker `-e`).
func SavePreferences ¶
func SavePreferences(prefs Preferences) error
SavePreferences writes preferences to disk, creating the config directory if necessary.
func SetLastSessionForCwd ¶
SetLastSessionForCwd records the most recently active session for a cwd, consumed by the TUI's "reopen where I left off" restore on startup. No-op for empty cwd (would conflate unrelated launch contexts).
func UpdatePreferences ¶
func UpdatePreferences(mutate func(*Preferences)) error
UpdatePreferences serializes a load-modify-save against the preferences file. mutate is called with the most recently saved Preferences and may modify any subset of fields; the merged value is then written back. This is the safe way to change a single field from a goroutine — calling LoadPreferences/SavePreferences directly races other concurrent updaters.
Types ¶
type FlyMachinesPreference ¶
type FlyMachinesPreference struct {
APIToken string `json:"api_token,omitempty"`
OrgSlug string `json:"org_slug,omitempty"`
Region string `json:"region,omitempty"`
Image string `json:"image,omitempty"`
// GatewayNetwork is the Fly private network the per-app Flycast
// address is allocated on — where this daemon dials FROM. Empty
// means the org's default network.
GatewayNetwork string `json:"gateway_network,omitempty"`
// AppNamePrefix namespaces this deployment's per-user apps within
// the org (e.g. "clank-dev-u"). Empty defaults to "clank-u".
AppNamePrefix string `json:"app_name_prefix,omitempty"`
GuestCPUKind string `json:"guest_cpu_kind,omitempty"`
GuestCPUs int `json:"guest_cpus,omitempty"`
GuestMemoryMB int `json:"guest_memory_mb,omitempty"`
SwapSizeMB int `json:"swap_size_mb,omitempty"`
VolumeSizeGB int `json:"volume_size_gb,omitempty"`
}
FlyMachinesPreference configures the raw Fly Machines provisioner (pkg/provisioner/flymachines): one app+machine+volume per user. Required: api_token (org-scoped, app-create rights), org_slug, region, and image (the clank-host-fly OCI ref, ideally digest- pinned). Zero-valued sizing fields use the provisioner defaults.
type FlySpritesPreference ¶
type FlySpritesPreference struct {
APIToken string `json:"api_token,omitempty"`
OrganizationSlug string `json:"organization_slug,omitempty"`
Region string `json:"region,omitempty"`
// SpriteNamePrefix is prepended to the user identifier to form
// the sprite name. Empty defaults to "clank-host" (yielding e.g.
// "clank-host-local" in the single-user laptop daemon).
SpriteNamePrefix string `json:"sprite_name_prefix,omitempty"`
// Resource pins for the sprite. 0 uses Sprites' defaults.
RamMB int `json:"ram_mb,omitempty"`
CPUs int `json:"cpus,omitempty"`
StorageGB int `json:"storage_gb,omitempty"`
}
FlySpritesPreference configures the Fly.io Sprites host launcher. APIToken (a SPRITES_TOKEN) enables the launcher; everything else is optional with sensible defaults.
Sprites are persistent per-user — one sprite is created the first time EnsureHost runs and reused indefinitely. The sprite's public URL is set to "public" auth mode; clank-host's bearer-token middleware (see PR 2) is the only auth gate.
type ModelPreference ¶
type ModelPreference struct {
ModelID string `json:"model_id"`
ProviderID string `json:"provider_id"`
}
ModelPreference stores the user's preferred model selection.
func (ModelPreference) IsZero ¶
func (m ModelPreference) IsZero() bool
IsZero reports whether the preference is unset.
type Preferences ¶
type Preferences struct {
// Models holds per-backend model overrides, keyed by backend
// string (e.g. "opencode", "claude-code"). Per-backend rather
// than a single global pick because each backend has its own
// model catalog: a model valid for opencode (e.g. a github-copilot
// route) will not exist in claude-code's enum and would crash the
// CLI at spawn time. Use ModelFor/SetModelFor.
Models map[string]ModelPreference `json:"models,omitempty"`
// ColorScheme is the TUI color scheme name (e.g. "tokyo-night").
// Empty string means "use the default scheme".
ColorScheme string `json:"color_scheme,omitempty"`
// DefaultBackend is the user's preferred coding agent backend
// (e.g. "opencode", "claude-code"). Used when neither the CLI
// `--backend` flag nor an explicit TUI selection overrides it.
// Empty string means "use the built-in default" (agent.DefaultBackend).
//
// Stored as a plain string rather than agent.BackendType to avoid
// pulling internal/agent into the config package's dependency graph.
// Validate at the boundary via agent.ResolveBackendPreference.
DefaultBackend string `json:"default_backend,omitempty"`
// WebPreviewDictation is the dictation engine `clank preview`'s
// browser overlay uses: "local" (the clank-voice/exec engine on
// this machine) or "webspeech" (the browser's SpeechRecognition
// service, which typically uploads audio to the browser vendor).
// Empty means not chosen yet — the overlay asks on first dictation.
//
// Stored as a plain string (same reasoning as DefaultBackend);
// validated at the boundary via webpreview.ParseDictationEngine.
WebPreviewDictation string `json:"web_preview_dictation,omitempty"`
// SidebarWidthRatio is the sidebar width as a percentage of the terminal
// width, adjusted with +/- in the TUI. Zero means "use the built-in
// default" (defaultSidebarWidthRatio).
SidebarWidthRatio int `json:"sidebar_width_ratio,omitempty"`
// SidebarHidden persists the 'w' sidebar toggle across TUI launches.
// False (default) = visible. Width-based auto-collapse is not recorded
// here — only explicit toggles.
SidebarHidden bool `json:"sidebar_hidden,omitempty"`
// SidebarExpanded is the persisted per-row expand/collapse state for the
// IDE-style sidebar tree. Keys follow the scheme used by sidebarNode.Key
// (e.g. "wt:<LocalPath>", "older:wt", "older:s:<LocalPath>"). Absent or
// false entries are collapsed. Older buckets reset to collapsed at every
// launch regardless of what's stored here; the TUI is responsible for
// that override.
SidebarExpanded map[string]bool `json:"sidebar_expanded,omitempty"`
// LastSessionByCwd records the session that was most recently open
// when the TUI exited, keyed by the absolute cwd it was launched
// from. On startup the TUI looks up the entry for the current cwd
// and reopens that session — so "where I left off" survives across
// runs without dropping users into a session from an unrelated
// repo when they launch from elsewhere.
LastSessionByCwd map[string]string `json:"last_session_by_cwd,omitempty"`
// FlySprites configures the cloud-hub-side Fly.io Sprites launcher.
// Only effective on a TCP-listening hub. Empty = launcher
// disabled (sessions requesting launch_host.provider="flysprites"
// will 4xx).
FlySprites *FlySpritesPreference `json:"flysprites,omitempty"`
// FlyMachines configures the raw Fly Machines provisioner. Nil =
// disabled (sessions requesting launch_host.provider=
// "flymachines" will 4xx).
FlyMachines *FlyMachinesPreference `json:"flymachines,omitempty"`
// DefaultLaunchHostProvider, when set, is applied to every new
// session whose StartRequest omits LaunchHost. Use this on a
// cloud hub to make TUI-created sessions automatically spin up
// sandboxes (e.g. "flymachines") without each client having to know
// about launchers.
//
// Empty (default) = no auto-launch; sessions land on the hub's
// "local" host (the cloud-hub machine itself).
//
// Stored as a plain string to avoid importing internal/agent
// into the config package — the value is validated at the
// hub when a launcher is looked up.
DefaultLaunchHostProvider string `json:"default_launch_host_provider,omitempty"`
// Remote configures the user's named clank deployments. One or more
// remotes, each with its own gateway/auth endpoint and session, plus
// an Active selector pointing at the live one. Modeled on git
// remotes: same mental model, same `add/list/switch/remove` UX.
// The TUI auth panel and `clank remote` read the active remote via
// Preferences.ActiveRemote().
Remote *RemoteConfig `json:"remote,omitempty"`
}
Preferences stores user preferences that persist across sessions. All fields should be optional (omitempty) so the file can grow over time without breaking older installs.
func LoadPreferences ¶
func LoadPreferences() (Preferences, error)
LoadPreferences reads preferences from disk. Returns a zero Preferences (not an error) if the file doesn't exist yet.
func (*Preferences) ActiveRemote ¶
func (p *Preferences) ActiveRemote() *Remote
ActiveRemote is a Preferences-level convenience for the very common "what's the live remote" check. Returns nil if Remote or its Active entry is unset.
func (*Preferences) ActiveRemoteAndName ¶
func (p *Preferences) ActiveRemoteAndName() (*Remote, string)
ActiveRemoteAndName mirrors ActiveRemote but additionally returns the key the resolved profile lives under. Persistence-side callers (login, logout, refresh) must use this to avoid the (profile, "") mismatch when Active is empty/stale.
func (*Preferences) ModelFor ¶
func (p *Preferences) ModelFor(backend string) ModelPreference
ModelFor returns the persisted model preference for the given backend, or the zero value when none is set.
func (*Preferences) SetModelFor ¶
func (p *Preferences) SetModelFor(backend string, pref ModelPreference)
SetModelFor records the model preference for the given backend. A zero pref clears the entry instead of persisting empty strings.
func (*Preferences) UnmarshalJSON ¶
func (p *Preferences) UnmarshalJSON(data []byte) error
UnmarshalJSON on Preferences migrates two legacy shapes when the new keys are absent:
- top-level "cloud" → "remote"
- top-level "model" (single ModelPreference) → "models"[<backend>]
The next SavePreferences emits only the new keys, quietly upgrading the file. Migration is non-destructive: a populated new key wins over the legacy value.
type Remote ¶
type Remote struct {
// GatewayURL is the base URL of the cloud gateway (sessions + sync),
// e.g. "https://gateway.example.com". Required for push/pull and
// session proxying; also the discovery endpoint for OAuth via
// GET /auth-config.
GatewayURL string `json:"gateway_url,omitempty"`
AccessToken string `json:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
UserEmail string `json:"user_email,omitempty"`
UserID string `json:"user_id,omitempty"`
// ExpiresAt is unix-seconds. Zero when no session.
ExpiresAt int64 `json:"expires_at,omitempty"`
}
Remote holds one clank deployment's gateway URL + OAuth session. Mirrors a single entry in a git-remote-style config.
Provider-agnostic on purpose: the gateway exposes /auth-config and clank runs standards OAuth 2.0 + PKCE against the IdP it advertises. The deployment (hosted or self-hosted) owns the user-auth mechanism — Supabase OAuth Server, Auth0, Keycloak, whatever. clank only needs one URL: the gateway.
Session fields are populated after a successful OAuth grant and used for subsequent /me and sync calls. AccessToken expires; the user is prompted to sign in again on 401.
func (*Remote) IsStaticBearer ¶
IsStaticBearer returns true for self-hosted profiles that authenticate with a fixed CLANK_AUTH_TOKEN rather than an OAuth session. Recognised by the presence of an access_token alongside the absence of every OAuth session field — no refresh token, no expiry, no JWT sub.
type RemoteConfig ¶
type RemoteConfig struct {
// Active is the key in Profiles whose endpoints/session are used by
// push/pull/TUI right now. Empty falls back to "default".
Active string `json:"active,omitempty"`
// Profiles maps a user-chosen name to its configuration. At least
// one entry is expected when Remote is set at all; an Active that
// points at a missing entry renders ActiveRemote() nil.
Profiles map[string]*Remote `json:"profiles,omitempty"`
}
RemoteConfig holds one or more named clank deployments plus the Active selector. Lets the user switch between e.g. a dev docker stack, a managed cloud, and an enterprise self-hosted instance without rewriting preferences.
JSON marshalling auto-detects the legacy flat shape (single profile inline under "cloud" or "remote") and normalizes to the multi-profile shape on load — saves rewrite to the new shape on the next SavePreferences.
func (*RemoteConfig) ActiveProfile ¶
func (c *RemoteConfig) ActiveProfile() *Remote
ActiveProfile returns the active Remote or nil if none.
func (*RemoteConfig) ActiveProfileAndName ¶
func (c *RemoteConfig) ActiveProfileAndName() (*Remote, string)
ActiveProfileAndName returns the active Remote along with the key it lives under. Same fallback as ActiveProfile (when Active is empty or points at a missing profile, the alphabetically-first profile is selected). Callers that persist edits must use the returned name, not c.Active — the latter is the raw, possibly stale, on-disk value.
func (*RemoteConfig) UnmarshalJSON ¶
func (c *RemoteConfig) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts both the multi-profile shape and the legacy single-profile flat shape. Legacy gets normalized to a single "default" entry selected as Active.