Documentation
¶
Index ¶
- Constants
- Variables
- func AppendWorkspace(path, name, apiKey string) error
- func ConfigPath(explicit string) string
- func EnvRefNames(s string) []string
- func ExpandEnvRefs(s string, lookup func(string) (string, bool)) (expanded string, missing []string)
- func ExpandHome(path string) string
- func LookupSecretRef(ryshDir, name string) (string, bool)
- func SetCompanyBlock(path string, company CompanyConfig) error
- func SetEmailProvider(path, provider string) error
- func SetProviderBlock(path, name, model, apiKeyRef, apiURL string) error
- func SetSessionName(path, name string) error
- func SetUIBlock(path, shell string, initialTabs, initialPanes int) error
- func SetUpstreamConnection(path, url, apiKey, workspaceID string) error
- func SetWorkspaceWorkingDir(path string, workspaceIdx int, workspaceName, dir string) error
- func WriteSecret(ryshDir, scope, name, value string) (string, error)
- type AgenticConfig
- type AutomationConfig
- type AutomationKindConfig
- type AutomationWebConfig
- type CompanyBinding
- type CompanyConfig
- type Config
- type EmailConfig
- type FailoverUpstream
- type ForgeAuthConfig
- type ForgeConfig
- type ForgeIntegrationConfig
- type ForgeSubscribeConfig
- type NATSConfig
- type PolicyConfig
- type ProfileConfig
- type ProxyConfig
- type ProxyFailoverConfig
- type ProxyModelRules
- type ProxyRateLimitConfig
- type ProxyTenantConfig
- type RateLimitRule
- type RegistryConfig
- type ReplayConfig
- type SNATConfig
- type SNATCustomDetector
- type TenantRule
- type UpstreamConfig
- type UsageConfig
- type UsagePriceOverride
- type VoiceConfig
- type VoiceControlConfig
- type WorkspaceConfig
Constants ¶
const DefaultAnthropicAPIURL = "https://api.anthropic.com"
DefaultAnthropicAPIURL is the api_url every config starts with, because the default provider is Claude. It is exported because consumers must be able to RECOGNIZE it: a non-Claude provider that inherits this value would send its requests to Anthropic's host and get a bare 404, so provider construction treats it as "no endpoint chosen for this family" rather than as a deliberate override (see internal/provider.NewOpenAIAgenticProvider).
const ProxyDummyKey = "rysh-proxy-dummy"
ProxyDummyKey is the placeholder written into a pane's provider env var when [proxy] inject_key is on. It must never be forwarded upstream or mistaken for a real key — the proxy swaps it for the real one server-side.
Variables ¶
var EnvRefPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`)
EnvRefPattern matches ${NAME} secret/environment references in config values. The shape mirrors the secret-name rules used by the ##secret command: a leading letter/underscore followed by letters, digits, or underscores.
Functions ¶
func AppendWorkspace ¶
AppendWorkspace adds a new workspace entry (name + working_directory "~/" + upstream{enabled, api_key}) to the YAML config at path, preserving existing comments and formatting. The entry is appended to the top-level `workspace:` sequence, which is created if absent. Used by the ##ws create command.
func ConfigPath ¶
ConfigPath returns the config file to read/write: the explicit path when non-empty, otherwise the first existing rysh.config.yaml (CWD, then CWD/.rysh — see resolveConfig), falling back to "rysh.config.yaml" in the CWD.
func EnvRefNames ¶
EnvRefNames returns the distinct ${NAME} reference names found in s, in order of first appearance.
func ExpandEnvRefs ¶
func ExpandEnvRefs(s string, lookup func(string) (string, bool)) (expanded string, missing []string)
ExpandEnvRefs replaces every ${NAME} reference in s using lookup. References that lookup cannot resolve are left untouched and reported in missing (each name once, in order of first appearance).
func ExpandHome ¶
ExpandHome replaces a leading "~" with the user's home directory. Exported for callers that build a WorkspaceConfig at runtime (e.g. ##ws create), since config loaded from disk is expanded during parsing but runtime-built configs are not.
func LookupSecretRef ¶
LookupSecretRef resolves a ${NAME} reference through the tiers available outside a running session: the persisted .rysh/secrets files under ryshDir (the "default" scope first, then any other scope, alphabetically), then the process environment. The session-KV tier only exists inside a daemon, so this is the config-level precedence `rysh onboard` / `rysh doctor` can see. Values are whitespace-trimmed like the runtime secret store's.
func SetCompanyBlock ¶ added in v0.2.11
func SetCompanyBlock(path string, company CompanyConfig) error
SetCompanyBlock writes the top-level `company:` block through the same yaml.Node round-trip SetUpstreamConnection uses, so every unrelated key and comment in the file survives.
func SetEmailProvider ¶ added in v0.2.11
SetEmailProvider persists only the transport selector and preserves every other config key/comment. Credentials are deliberately not accepted here.
func SetProviderBlock ¶
SetProviderBlock writes (or updates) the top-level `provider:` block of the YAML config at path via a structured yaml.Node round-trip, preserving every unrelated key and existing comments/formatting — never a string splice. It backs `rysh onboard` step 1 (design 004 §4.1).
apiKeyRef is expected to be a ${ENV} secret REFERENCE (e.g. "${ANTHROPIC_API_KEY}"), never a literal key — the caller stores literals in the .rysh/secrets tier (WriteSecret) or the environment. Empty model / apiKeyRef / apiURL leave the corresponding key untouched, so a re-run only writes deltas (the wizard's idempotency contract).
func SetSessionName ¶ added in v0.2.11
SetSessionName writes `rysh.session_name`. A company workspace names its session after the company slug so the desktop app and the terminal — which adopt daemons by (directory, session) — land on the same one.
func SetUIBlock ¶
SetUIBlock writes (or updates) the top-level `ui:` block of the YAML config at path via a structured yaml.Node round-trip, preserving every unrelated key and existing comments/formatting — never a string splice. It backs the terminal-preferences step of `rysh onboard` (design 008 TO2) and is the sibling of SetProviderBlock.
Zero/empty arguments leave the corresponding key untouched, so a re-run only writes deltas (the wizard's idempotency contract).
func SetUpstreamConnection ¶ added in v0.2.4
SetUpstreamConnection writes the connection half of the top-level `upstream:` block — enabled/url/api_key/workspace — to the YAML config at path via a structured yaml.Node round-trip, preserving every unrelated key and the user's comments and formatting. It is the sibling of SetUIBlock and SetProviderBlock, and it backs `##upstream connect <url> <api-key>`.
workspaceID MUST be the server workspace id (UUID), not the workspace name: the id is what namespaces NATS subjects upstream (ws.{id}.*). A name written here produces a session that connects and silently sees no shares. Callers resolve it from the api key via upstream.FetchWorkspaceInfo rather than asking the user to find it in a dashboard.
Deliberately NOT written: `governance`. It opts the daemon into reporting local spend to the server, which is a separate data-egress decision from connecting (see the UpstreamConfig.Governance comment). Whatever the user has — set or absent — is left exactly as it was.
func SetWorkspaceWorkingDir ¶
SetWorkspaceWorkingDir sets the working_directory of a workspace entry in the YAML config at path, preserving comments and formatting. Backs the persistence half of `##workspace cwd <path>`, so the chosen directory survives a restart.
The workspace is matched by INDEX (workspaceIdx) into the `workspace:` sequence: that order is preserved across the YAML, Config.Workspaces, ResolvedWorkspaces and the farm's children, and a synthesized name ("ws-N") has no `name` key in the file to match on. workspaceName is only a defensive cross-check / fallback.
- workspace: sequence present, index in range -> update that entry.
- sequence present but index out of range / name mismatch -> match by raw name, else append a {name, working_directory} entry (defensive).
- no workspace: section (a single synthesized default) -> set the inherited default rysh.working_directory instead of fabricating a section.
func WriteSecret ¶
WriteSecret persists a literal secret value into the .rysh/secrets tier: <ryshDir>/secrets/<scope>/<name> with restrictive permissions (0700 dirs, 0600 file) and a protective .gitignore at the secrets root, mirroring the ##secret command's persisted-secret layout so the runtime store resolves it. Returns the written path.
Types ¶
type AgenticConfig ¶
type AgenticConfig struct {
Enabled bool
MaxIterations int
MaxParallelTasks int
ContextWindowToks int
BashTimeout time.Duration
BlockedCommands []string
BlockedPaths []string
MaxFileSize int64
AutoApproveReads bool
AutoApproveBash bool
RequireApproveWrites bool
// Grounding selects the grounding protocol ("off" | "prompt" |
// "enforced"): read the codebase like a human (search → read → iterate)
// before acting, asking the user for the location when searching cannot
// find the target. Empty means the built-in defaults: panes run
// "enforced", agents/humanoids and sub-agents run "prompt" (advisory).
// RYSH_GROUNDING overrides.
Grounding string
}
AgenticConfig holds configuration for the agentic coding assistant.
func DefaultAgenticConfig ¶
func DefaultAgenticConfig() AgenticConfig
DefaultAgenticConfig returns sensible defaults for the agentic system.
func LoadAgenticConfig ¶
func LoadAgenticConfig() AgenticConfig
LoadAgenticConfig loads agentic configuration from environment variables. It starts from defaults and applies overrides.
type AutomationConfig ¶
type AutomationConfig struct {
Web AutomationKindConfig `yaml:"web"`
Task AutomationKindConfig `yaml:"task"`
Agent AutomationKindConfig `yaml:"agent"`
Humanoid AutomationKindConfig `yaml:"humanoid"`
Code AutomationKindConfig `yaml:"code"`
// Loop is the kind-INDEPENDENT loop defaults tier, one below the per-kind
// blocks: `automation.loop.do` seeds the internal-loop executor seat
// (model/effort of the do/step legs) and `automation.loop.while` seeds
// the until-judge seat, for every kind that doesn't declare its own.
// Built-ins below this tier: do → webauto.DefaultStepModel (Sonnet),
// while → webauto.DefaultJudgeModel (Opus 4.8).
//
// automation:
// loop:
// do: {model: claude-sonnet-5}
// while: {model: claude-opus-4-8}
Loop *webauto.LoopConfig `yaml:"loop"`
}
AutomationConfig maps the top-level `automation` section: config-level defaults for the ##auto command family, one child per subcommand (web/task/agent/humanoid/code), each carrying its own step defaults.
automation:
web:
step: {...}
task:
step: {...}
agent:
step: {...}
humanoid:
step: {...}
type AutomationKindConfig ¶
type AutomationKindConfig struct {
// Step is the config-level default for the per-pass budget (the recipe's
// `step` / `loop.do` block). Loop carries the full loop-engineering
// defaults ({do, while}); when Loop.Do is set it wins over Step, the same
// aliasing rule the recipe uses.
Step *webauto.StepConfig `yaml:"step"`
Loop *webauto.LoopConfig `yaml:"loop"`
// Record is the config-level default for run recording. Only the web kind
// reads it (the others have no browser to capture):
//
// automation:
// web:
// record:
// enabled: true
// interval: 500ms
// format: jpeg
// quality: 60
Record *webauto.RecordConfig `yaml:"record"`
}
AutomationKindConfig maps one `automation.<kind>` child: config-level defaults for that kind's recipes. Step mirrors the recipe `step` frontmatter block (interval, max_iterations, max_duration, auto_continue, auto_approve, budget: {size, watch: {takeover_when, takeover_prompt, floor}}). Per-field precedence: recipe frontmatter > these defaults > built-ins.
automation:
web:
step:
interval: 30
max_iterations: 300
max_duration: 7m
auto_continue: true
auto_approve: true
budget:
size: 3b
watch:
takeover_when: 90
floor:
trigger_iterations: 50
trigger_duration: 1m
trigger_size: 60p
iterations: 100
duration: 5m
size: 1b
type AutomationWebConfig ¶
type AutomationWebConfig = AutomationKindConfig
AutomationWebConfig is the former name of AutomationKindConfig, kept as an alias for compatibility (the web kind was the first ##auto child).
type CompanyBinding ¶ added in v0.2.11
type CompanyBinding struct {
URL string
APIKey string
WorkspaceID string
Company CompanyConfig
}
CompanyBinding is what `##config load <file>` reads out of a downloaded rysh.config.yaml (design 037, second cut): the upstream connection that carries the read-only key, and the company block that names what it sees. Everything else in the file is ignored — a downloaded config binds a session to a company; it does not replace the session's own settings.
func ReadCompanyBinding ¶ added in v0.2.11
func ReadCompanyBinding(path string) (*CompanyBinding, error)
ReadCompanyBinding parses and validates the file. The checks are the ones that would otherwise fail later and quietly: a workspace name where the id belongs (a session that connects and sees nothing), a key with the wrong shape, a company with no id.
type CompanyConfig ¶ added in v0.2.11
type CompanyConfig struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
Slug string `yaml:"slug"`
FQDN string `yaml:"fqdn"`
Server string `yaml:"server"`
}
CompanyConfig binds a session to one AI-native company on the upstream server (design 037, "Company Connect"). It is written by `ry connect` (and by the desktop app through the same command) next to the `upstream:` block that carries the read-only key; a session whose config has it opens on the company view instead of an empty shell.
Nothing here is secret: the key lives in `upstream.api_key`. These fields only name the company so the daemon knows which one to follow and the TUI can label it before the first fetch returns.
func (CompanyConfig) Enabled ¶ added in v0.2.11
func (c CompanyConfig) Enabled() bool
Enabled reports whether this session is bound to a company.
type Config ¶
type Config struct {
NATS NATSConfig
Upstream UpstreamConfig
// Company binds this session to one AI-native company on the upstream
// server (design 037); written by `rysh connect`.
Company CompanyConfig
SNAT SNATConfig
Profile ProfileConfig
Voice VoiceConfig
VoiceControl VoiceControlConfig
Automation AutomationConfig
Email EmailConfig
Usage UsageConfig
Proxy ProxyConfig
Replay ReplayConfig
Registry RegistryConfig
Policy PolicyConfig
Workspaces []WorkspaceConfig
ProviderName string
ClaudeCommand string
SystemPrompt string
// PairingDefault is the session-wide admission default for humanoid
// channels that declare neither an allowlist nor a pairing_policy:
// "open" (default, pre-WS3 behaviour) or "closed" (design 003 G5).
PairingDefault string
// AgentAutoresume brings back the agents `##claude` / `##codex` started,
// after a session stop/start has rebuilt the layout from KV (design 029).
// Only panes rysh launched an agent in are touched — a claude the user
// typed at their own shell stays theirs. Defaults to true; config
// `[rysh] agent_autoresume` or RYSH_AGENT_AUTORESUME.
AgentAutoresume bool
DefaultShell string
// InteractiveShell, when true, launches each pane's shell with the
// interactive flag (e.g. `bash -i`) so it sources the user's rc files
// (~/.bashrc, ~/.zshrc) and uses the user's real prompt (PS1). rysh then
// streams the shell's native prompt and command echo through verbatim
// instead of synthesizing a "$ <cmd>" line and stripping prompts. When
// false, the legacy minimal-prompt behavior is used. Defaults to true.
InteractiveShell bool
// ShellColorOutput, when true (the default), preserves ANSI color/style
// (SGR) sequences in shell-mode pane output so `ls --color`, git diffs,
// etc. keep their colors in the pane scrollback. Cursor-movement, OSC and
// other non-styling escapes are still removed, and carriage-return line
// rewrites are collapsed to their final state. Only effective together
// with InteractiveShell; when false the legacy strip-everything behavior
// is used. Config `[ui] shell_color_output` or RYSH_SHELL_COLOR.
ShellColorOutput bool
// ShellBufferBytes caps each pane's text output buffers (merged and
// per-mode) in bytes. <= 0 falls back to the built-in default (256 KiB).
// Config `[ui] shell_buffer_bytes` or RYSH_SHELL_BUFFER_BYTES.
ShellBufferBytes int
// ShellReadlineKeys, when true (the default), enables the bash-style
// extras rysh adds to shell mode: Ctrl+R incremental reverse-i-search
// over shell history, prefix history search (Up with a typed draft) and
// PS2 multi-line continuation. Ctrl+R is the ONLY Ctrl key this takes —
// every other Ctrl shortcut remains a rysh multiplexer chord (pane/tab/
// layout/... modes), in shell mode exactly like everywhere else.
// Config `[ui] shell_readline_keys` or RYSH_SHELL_READLINE.
ShellReadlineKeys bool
// ShellHistorySize bounds the persistent shell history file (and the
// number of entries seeded into a fresh pane) — bash HISTSIZE/HISTFILESIZE.
// <= 0 falls back to 1000. `[ui] shell_history_size` / RYSH_SHELL_HISTORY_SIZE.
ShellHistorySize int
// ShellHistoryIgnoreDups skips recording a shell command identical to the
// previous entry — bash HISTCONTROL=ignoredups. Default true.
// `[ui] shell_history_ignore_dups`.
ShellHistoryIgnoreDups bool
// ShellHistoryIgnoreSpace skips recording shell commands that start with a
// space — bash HISTCONTROL=ignorespace. Default false.
// `[ui] shell_history_ignore_space`.
ShellHistoryIgnoreSpace bool
// ShellBashCompletion, when true (the default), delegates Tab completion
// in argument position to a bash sidecar running the command's
// programmable completion spec (bash-completion) — git subcommands and
// branches, ssh hosts, docker/kubectl verbs, command flags. Falls back
// to built-in file completion when no spec exists or the sidecar fails.
// `[ui] shell_bash_completion` or RYSH_SHELL_BASH_COMPLETION.
ShellBashCompletion bool
// ShellPrompt is the template for the shell-mode input prompt. Supported
// placeholders: {dir} (basename of the shell's live cwd, "~" at home),
// {cwd} (full ~-abbreviated path), {session} (session name). The live cwd
// comes from OSC 7 reporting; when unknown the prompt falls back to "> ".
// Default "{dir} > ". `[ui] shell_prompt` or RYSH_SHELL_PROMPT.
ShellPrompt string
DefaultModel string
APIKey string
APIURL string
BraveAPIKey string
MaxTokens int
// Thinking enables extended (adaptive) thinking on the agentic provider.
// On Claude 4.6+ models the model decides when/how much to reason before
// answering; thinking blocks are recorded on assistant turns and replayed
// so tool-use loops keep working. Config `[provider] thinking = true` or
// RYSH_THINKING=1.
Thinking bool
SessionName string
SessionDir string
// SessionSource identifies the front-end that owns sessions this process
// creates/opens: "cli" (rysh command line, the default) or "app" (rysh
// desktop app, set via RYSH_SESSION_SOURCE=app by the app sidecar). The
// daemon stamps it onto the session record; the CLI refuses to open "app"
// sessions and the app refuses to open "cli" sessions.
SessionSource string
WorkingDirectory string // working directory assigned to newly created panes
// ConfigFile is the absolute path of the rysh.config.yaml that was loaded,
// or "" when no config file was found. RyshDir is the rysh directory
// implied by where that config was found (see resolveConfig): the
// project-local ".rysh" that lives in (or beside) the working directory.
// rysh state is ALWAYS project-local — when NO config file is found, RyshDir
// falls back to "<cwd>/.rysh" (defaultRyshDir). There is no global state root
// (no ~/.rysh, ~/.local/state/rysh, or ~/.config/rysh). Both are recorded so
// the running session is aware of where its configuration and rysh state
// live. RyshDir is always populated, and the session registry (SessionDir =
// <RyshDir>/sessions) and NATS JetStream data (NATS.DataDir =
// <RyshDir>/nats) are derived from it unless explicitly overridden.
ConfigFile string
RyshDir string
InitialTabs int
InitialPanes int
LogMessages bool
LogLevel string // "off", "debug", "info", "warn", "error" (default: "off" — logging disabled)
PrivateBufferSize int // max bytes for ##private pane output buffer (per pane)
PublicBufferSize int // max bytes for ##public pane output buffer (per pane)
// The [web] block is a PROJECT-WIDE DEFAULT for sessions that have never
// been configured, and the channel the desktop app's sidecar uses
// (RYSH_WEB_PORT / RYSH_WEB_AUTO_START land here). How a given session is
// actually served lives in .rysh/web/<session>.json — see
// session.WebSettings for why: this file is read by EVERY session rooted at
// this project, so one session's port became every session's port.
WebPort int // web UI server port (default 23232)
WebHost string // web UI bind IP (default "" = web.DefaultHost, 127.0.0.1; set 0.0.0.0 to expose on the network)
WebAutoStart bool // auto-start web server on daemon startup
// WebUsername/WebPassword are the web UI login as the CONFIG knows it:
// ${RYSH_WEB_USERNAME}/${RYSH_WEB_PASSWORD} references into the secrets
// tier (design 004 G4: references in YAML, never literals), resolved here
// at load. `##rysh web start` stores the login under exactly those secret
// names, so writing the references is optional. They exist so a restarted
// session can re-establish the SAME login without anyone retyping it —
// .rysh/web-auth.json stores only a bcrypt hash, which cannot be replayed
// into a new server.
WebUsername string
WebPassword string
// WebNgrok publishes the auto-started web server through an ngrok tunnel.
// WebNgrokDomain pins a reserved domain (without one the public URL changes
// on every restart); WebNgrokBinary and WebNgrokConfig override the
// executable and its config file — those two are genuinely machine-wide, so
// the config file is the right home for them.
WebNgrok bool
WebNgrokDomain string
WebNgrokBinary string
WebNgrokConfig string
// Prometheus metrics exporter (follow-up 3b). When MetricsEnabled, the
// daemon serves the agentic MetricsSink as Prometheus text on
// MetricsListen (/metrics). Disabled by default; the in-process sink
// (##agent metrics) is always available.
MetricsEnabled bool // expose Prometheus /metrics endpoint
MetricsListen string // listen address for the /metrics server (default 127.0.0.1:9095)
// workspace as soon as the initial tabs/panes are bootstrapped. Set by the
// global "--shared" CLI flag (not read from rysh.config.yaml). Requires a
// configured, enabled [upstream] block to take effect.
ShareFirstTab bool
// UpgradeOnAttach controls what happens when "rysh attach" (or the bare
// "rysh <session>") connects to a live daemon that was launched from a
// different rysh binary than the one now attaching (detected via the session
// record's BinHash/Version). Valid values:
// "off" — ignore the mismatch and attach to the old daemon as-is.
// "warn" — print a one-line notice, then attach to the old daemon (default).
// "prompt" — ask interactively whether to restart the daemon (upgrade).
// "auto" — restart the daemon from the current binary automatically.
// The explicit "--upgrade" CLI flag always forces a restart regardless of
// this setting. Set via [session] upgrade_on_attach or RYSH_UPGRADE_ON_ATTACH.
UpgradeOnAttach string
// Forge declares forge integrations to ingest/enable/share and remote forged
// Forge is the WORKSPACE-scoped forge config for this workspace. It is NOT a
// global/top-level YAML section — it is parsed from each `workspace:` entry's
// `forge:` block (WorkspaceConfig.Forge) and copied here per-workspace by the
// workspace farm. Applied a couple of seconds after start (applyForgeConfig).
Forge ForgeConfig
// Secrets are scope-namespaced named secrets declared in rysh.config.yaml
// under [secrets], nested by scope: secrets[<scope>][<NAME>] = value. A scope
// is a workspace name (the default storage scope) or a tab ID (for per-tab
// secrets). They are a resolution tier between the persisted .rysh/secrets
// files and the process environment, and fill ${NAME} placeholders when
// agent/humanoid skill files are loaded from a pane, resolving tab → workspace
// → environment.
Secrets map[string]map[string]string
// Variables mirror Secrets for plain environment variables, declared under
// [variables] and nested by scope: variables[<scope>][<NAME>] = value. Unlike
// secrets they are NOT SecretNAT-protected (the LLM may see them). They form
// the config tier between the persisted .rysh/variables files and the process
// environment, and fill ${NAME} placeholders alongside secrets when
// agent/humanoid skill files are loaded (secrets take precedence on a clash).
Variables map[string]map[string]string
}
Config is the main configuration for Rysh.
func Load ¶
func Load() Config
Load reads configuration from rysh.config.yaml, then applies environment variable overrides. The config file is searched in the current directory first, then <cwd>/.rysh/rysh.config.yaml — never any home-directory location (rysh has no global state root; see resolveConfig).
When the config file exists but cannot be parsed, a warning is written to stderr. This avoids the silent trap where a single malformed value (for example bad YAML indentation or a string where a number is expected) causes the entire configuration -- session name, provider, upstream, working_directory -- to be silently discarded in favor of defaults.
func LoadFrom ¶
LoadFrom reads configuration from an explicit file path, then applies environment variable overrides. When path is empty it behaves exactly like Load (searching the current directory, then <cwd>/.rysh/rysh.config.yaml). This backs the global "--config <path>" CLI flag.
func (Config) ProxyKeyFor ¶
ProxyKeyFor resolves the real upstream API key for a proxy dialect.
"" means "we hold no key for this dialect" — the caller's own credentials must then pass through untouched rather than being replaced by a key that belongs to a different provider.
func (Config) ResolvedWorkspaces ¶
func (c Config) ResolvedWorkspaces() []WorkspaceConfig
ResolvedWorkspaces returns the effective list of workspaces for the session. When rysh.config.yaml declares [[workspace]] entries, they are returned with any unset per-workspace fields filled in from the top-level defaults. Otherwise a single workspace is synthesized so existing single-workspace configs keep working unchanged. The result always contains at least one workspace.
type EmailConfig ¶ added in v0.2.11
type EmailConfig struct {
Provider string
Address string
IMAPHost string
IMAPPort int
SMTPHost string
SMTPPort int
Username string
Password string
MailgunDomain string
MailgunBaseURL string
MailgunAPIKey string
MailgunWebhookSigningKey string
// AttachmentDir is where the email client saves attachments on the
// daemon's machine (email.attachment_dir, ~ allowed). Empty ⇒ ~/Downloads,
// else home. Kept raw here; channels.AttachmentDir resolves and checks it.
AttachmentDir string
// Safety "read_only" turns on the opt-in read-only guard (founder ruling
// U-6/H-5; default off): IMAP EXAMINE + BODY.PEEK only, every mutating
// verb refused, only mail whose subject carries SubjectTag is fetched or
// sent, and every IMAP command is recorded in AuditLog. channels enforces it.
Safety string
SubjectTag string
AuditLog string
// AllowFrom is the guard's sender allow-list (root-0320): under read_only
// only mail whose From addr-spec is listed has a body fetched. Empty ⇒
// the account's username.
AllowFrom []string
}
EmailConfig selects the session-wide email transport. Secret-bearing fields should be ${NAME} references resolved from .rysh/secrets or the environment.
type FailoverUpstream ¶ added in v0.2.3
FailoverUpstream is one entry in a dialect's ordered upstream list. Key is the credential for THIS upstream; empty means reuse the dialect key. A corp gateway fallback normally needs a different credential from the vendor's own endpoint, and without a per-entry key the fallback would be sent a key that does not belong to it.
type ForgeAuthConfig ¶
type ForgeAuthConfig struct {
Type string `yaml:"type"` // static|jwt_login|oauth2_password|oauth2_client_credentials
TokenURL string `yaml:"token_url"` //
RefreshURL string `yaml:"refresh_url"` // defaults to token_url
ContentType string `yaml:"content_type"` // form|json
UsernameEnv string `yaml:"username_env"`
PasswordEnv string `yaml:"password_env"`
ClientIDEnv string `yaml:"client_id_env"`
ClientSecretEnv string `yaml:"client_secret_env"`
AccessTokenEnv string `yaml:"access_token_env"`
RefreshTokenEnv string `yaml:"refresh_token_env"`
Scope string `yaml:"scope"`
AccessField string `yaml:"access_token_field"`
RefreshField string `yaml:"refresh_token_field"`
ExpiresInField string `yaml:"expires_in_field"`
Header string `yaml:"header"`
Scheme string `yaml:"scheme"`
ExpiryMargin string `yaml:"expiry_margin"`
}
ForgeAuthConfig is the YAML surface for a forged-API token-auth flow (Phase A owner identity / Phase B subscriber identity). It mirrors runtime.AuthConfig and, like it, holds ONLY env-var names + URLs — never secret values.
type ForgeConfig ¶
type ForgeConfig struct {
Integrations []ForgeIntegrationConfig `yaml:"integrations"` // (source) add → enable → optionally share
Subscribe []ForgeSubscribeConfig `yaml:"subscribe"` // (subscriber) subscribe to remote shared APIs
}
ForgeConfig is the declarative startup config for forged-API sharing, declared per-workspace under `workspace[].forge`.
type ForgeIntegrationConfig ¶
type ForgeIntegrationConfig struct {
Name string `yaml:"name"` // integration name (tool prefix)
Spec string `yaml:"spec"` // path to the OpenAPI/GraphQL spec (~ expanded at apply time)
BaseURL string `yaml:"base_url"` // absolute base URL (required when the spec server is relative)
Source string `yaml:"source"` // "openapi" | "graphql" | "grpc" (inferred when empty)
GRPCProtocol string `yaml:"grpc_protocol"` // gRPC streaming wire protocol: "gateway" (default) | "connect" — same as `forge add --grpc-protocol`
CredentialEnv string `yaml:"credential_env"` // env var holding the API key (optional)
Auth *ForgeAuthConfig `yaml:"auth"` // JWT/OAuth2 token flow (owner identity, Model A)
EnableScope string `yaml:"enable_scope"` // pane|panegroup|lane|tab; empty ⇒ added but not enabled
}
ForgeIntegrationConfig declares one integration to make live on startup.
type ForgeSubscribeConfig ¶
type ForgeSubscribeConfig struct {
API string `yaml:"api"` // shared API name
Scope string `yaml:"scope"` // pane|panegroup|lane|tab (default tab)
Auth *ForgeAuthConfig `yaml:"auth"` // subscriber identity (Model B, delegated) — opt-in
}
ForgeSubscribeConfig declares one remote shared API to mount on startup.
type NATSConfig ¶
type NATSConfig struct {
Mode string `yaml:"mode"`
URL string `yaml:"url"`
DataDir string `yaml:"data_dir"`
Port int `yaml:"port"`
}
NATSConfig holds NATS-specific settings.
type PolicyConfig ¶
type PolicyConfig struct {
// OrgFile is the org policy file path ("~" expanded, made absolute at
// load). `[policy] org_file` or RYSH_ORG_POLICY (env wins).
OrgFile string
}
PolicyConfig wires policy-as-code (design 013) to an org-level policy file merged over .rysh/policy.yaml, strictest-wins (see internal/policy.Merge). Empty OrgFile means no org policy is configured — project policy only. A configured org file that is missing OR unparseable fails closed at load.
type ProfileConfig ¶
type ProfileConfig struct {
Name string `yaml:"name"` // display name shown as "Name: message" in chat
Color string `yaml:"color"` // ANSI color name for the name prefix (e.g. "green", "cyan")
}
ProfileConfig holds per-user identity settings used in collaborative chat.
type ProxyConfig ¶
type ProxyConfig struct {
// Enabled turns on env injection + the loopback proxy. Default false
// (safe: existing panes are untouched until opted in via config or ##proxy on).
Enabled bool
// InjectKey injects a dummy provider key into pane env; the real key is
// injected proxy-side from config.APIKey. Off ⇒ the pane's own key is used.
InjectKey bool
// AuditContent, when true, would persist redacted request bodies for
// debugging (never pre-redaction). Metadata-only by default.
AuditContent bool
// Upstreams overrides the default dialect → base-URL map (corp gateways).
Upstreams map[string]string
// Keys maps a dialect (anthropic|openai|gemini) to the REAL upstream API
// key the proxy injects server-side.
//
// The proxy is multi-dialect by design — a pane can run Claude Code and
// Codex side by side — so a single key cannot serve it. Before this
// existed, one key was applied to both the Anthropic and OpenAI dialects,
// which meant enabling the proxy sent an Anthropic key as the OpenAI
// bearer and broke every wrapped OpenAI CLI.
//
// A dialect with no key here is NOT rewritten: the caller's own
// credentials pass through untouched, so the pane keeps working (and is
// still redacted, metered and audited).
Keys map[string]string
// RateLimit bounds request RATE, independently of token spend
// (design 022 §4.2). Empty ⇒ unlimited.
RateLimit ProxyRateLimitConfig
// Failover routes to a secondary upstream when the primary is failing
// (design 022 §4.1). Disabled ⇒ exactly one attempt, as before.
Failover ProxyFailoverConfig
// Tenants carries per-customer caps on a shared upstream key
// (design 022 §4.3). Empty ⇒ untenanted, every control keys off the pane.
Tenants ProxyTenantConfig
// Adapters names the CLIs whose spawn environment rysh may rewrite to
// force them through the proxy (design 022 §4.4). Empty ⇒ detect and warn
// only.
//
// Opt-in because an adapter can change how the CLI authenticates: the codex
// adapter deliberately withholds a ChatGPT session, since that session is
// exactly what lets it egress around the proxy. Governing the pane and
// preserving an existing login are mutually exclusive there, and which one
// the user wants is not ours to assume.
Adapters []string
// Strict escalates the ungoverned-CLI warning (design 022 §4.4) to a block:
// a known agent CLI that runs past the grace window with no governed request
// is stopped instead of merely warned about.
//
// Off by default and opt-in, because the detection is NEGATIVE evidence: an
// idle CLI looks exactly like an escaped one, so strict mode can stop
// innocent work. `##proxy check <cli>` is the deterministic answer, and the
// warning stays the default posture (022 §8.2).
Strict bool
// Models bounds which models a governed pane may call (design 001 §4.7).
// Empty ⇒ every model is allowed, which is the default.
Models ProxyModelRules
}
ProxyConfig configures the Universal Agent Governance Proxy (design 001).
func (ProxyConfig) AdapterEnabled ¶ added in v0.2.3
func (c ProxyConfig) AdapterEnabled(binary string) bool
AdapterEnabled reports whether rysh may rewrite this CLI's spawn environment.
type ProxyFailoverConfig ¶ added in v0.2.3
type ProxyFailoverConfig struct {
// Enabled gates the whole feature. Off ⇒ one attempt against the primary,
// byte-for-byte the pre-022 behaviour.
Enabled bool
// MaxAttempts bounds total attempts INCLUDING the primary. <= 0 ⇒ try
// every configured upstream once.
MaxAttempts int
// HeaderTimeout bounds how long an upstream may take to return response
// HEADERS before it counts as failed.
//
// Default 0 (off), deliberately. A NON-streaming completion can legitimately
// send no bytes for minutes while the model generates, so any default here
// would eventually reclassify a slow-but-working request as a dead upstream
// and retry it — paying for the same generation twice. Timeout-based
// failover is therefore opt-in; transport errors and retryable statuses are
// unambiguous and need no such judgement.
HeaderTimeout time.Duration
// RetryStatus overrides which upstream status codes trigger the next
// upstream. Empty ⇒ 429, 500, 502, 503, 504.
RetryStatus []int
// Upstreams maps a dialect to its ordered FALLBACK list. The primary comes
// from Proxy.Upstreams (or the dialect default) and is always tried first.
Upstreams map[string][]FailoverUpstream
}
ProxyFailoverConfig configures upstream retry (design 022 §4.1).
type ProxyModelRules ¶ added in v0.2.3
ProxyModelRules is the model allowlist/denylist (design 001 §4.7).
Patterns are shell-style globs matched case-insensitively against the model name in the request body: "claude-*", "gpt-4o", "*-opus-*".
Deny is evaluated first and always wins, so a broad allow plus a narrow deny expresses "this family, except that one" without having to enumerate every permitted version — the version numbers change on the provider's schedule, not the operator's.
func (ProxyModelRules) Any ¶ added in v0.2.3
func (m ProxyModelRules) Any() bool
Any reports whether any model rule is configured; false ⇒ the check is skipped entirely rather than evaluated to "allow".
type ProxyRateLimitConfig ¶ added in v0.2.3
type ProxyRateLimitConfig struct {
PerPane RateLimitRule
PerDialect map[string]RateLimitRule
Global RateLimitRule
}
ProxyRateLimitConfig holds the request-rate rules, checked pane → dialect → session. A request must satisfy every rule that is configured.
func (ProxyRateLimitConfig) Any ¶ added in v0.2.3
func (c ProxyRateLimitConfig) Any() bool
Any reports whether any rule is configured. False ⇒ the proxy skips the limiter entirely rather than building buckets nothing will consult.
type ProxyTenantConfig ¶ added in v0.2.3
type ProxyTenantConfig struct {
// Header names the request header carrying the tenant. Empty ⇒ X-Rysh-Tenant.
Header string
// TrustHeader allows an unpinned pane to name its own tenant via the header.
//
// Default FALSE, and that default is the security property. Anything running
// inside a pane can set a header, so a trusted header lets a pane select a
// more generous tenant and turns every cap in the system into a suggestion.
// Enable it only where the caller is not the thing being capped.
TrustHeader bool
// Tenants maps a tenant name to its rule.
Tenants map[string]TenantRule
}
ProxyTenantConfig configures per-customer policy on a shared upstream key.
type RateLimitRule ¶ added in v0.2.3
RateLimitRule is one token bucket: Rate requests per second with a Burst allowance. Rate <= 0 disables the rule — the default, so a control that did not exist yesterday never silently throttles a session that used to work.
func (RateLimitRule) EffectiveBurst ¶ added in v0.2.3
func (r RateLimitRule) EffectiveBurst() int
EffectiveBurst is the configured burst, defaulted to one second's worth of requests (minimum 1) when unset. A bucket with burst 0 admits nothing, which is never what someone writing `rate: 5` and omitting `burst` meant.
func (RateLimitRule) Enabled ¶ added in v0.2.3
func (r RateLimitRule) Enabled() bool
Enabled reports whether this rule binds at all.
type RegistryConfig ¶
type RegistryConfig struct {
URL string
}
ReplayConfig configures session replay capture (design 006). RegistryConfig points `rysh install @ns/name` at a package index. Empty URL means the first-party default (registry.DefaultIndexURL); set it to host an internal or air-gapped mirror. The index format is open — see internal/registry/index.go.
type ReplayConfig ¶
type ReplayConfig struct {
// Enabled turns on in-session output capture for asciicast export.
Enabled bool
// Retention bounds the durable JetStream replay stream's age (design 006
// §3.1). Zero means the replay package default (7d).
Retention time.Duration
// MaxBytes caps the durable replay stream's size. Zero means the replay
// package default (1 GiB).
MaxBytes int64
}
type SNATConfig ¶
type SNATConfig struct {
// Enabled is the session-wide default (per-pane override via ##snat
// on|off). Defaults to true.
Enabled bool
// Mode selects token style: "semantic" (type-preserving, e.g.
// sk_live_SNAT000001 — default) or "private" (SECRET_TOKEN_001).
Mode string
// RestoreDisplay, when true, restores real values into DISPLAYED LLM
// output. Off by default: the pane display buffer is persisted to KV and
// forwarded to listeners/shares, so restoring would re-leak secrets.
RestoreDisplay bool
// MappingTTL expires idle per-conversation mappings (Go duration string);
// empty/0 = conversation lifetime.
MappingTTL string
// DisableDetectors removes built-in detectors by name (e.g. ["google"]).
DisableDetectors []string
// CustomDetectors adds user-defined regex detectors.
CustomDetectors []SNATCustomDetector
}
SNATConfig configures SecretNAT — alias ReSet (Reversible Secret Translation) — the reversible secret translation layer between rysh and the LLM provider. Outbound prompts/conversations/tool traffic have real secrets replaced with synthetic tokens; tokens are restored to real values only for local tool execution. Command surface: ##snat (alias ##rst).
type SNATCustomDetector ¶
type SNATCustomDetector struct {
Name string `yaml:"name"`
Pattern string `yaml:"pattern"`
Prefix string `yaml:"prefix"`
}
SNATCustomDetector is one user-defined regex detector for the snat section.
type TenantRule ¶ added in v0.2.3
type TenantRule struct {
// Panes PINS these panes to this tenant. A pin is authoritative and cannot
// be overridden by a request header — see ProxyTenantConfig.TrustHeader.
Panes []string
// CeilingTokens is the tenant's daily token ceiling (0 ⇒ none). It binds in
// ADDITION to the pane's own: the stricter of the two wins.
CeilingTokens int64
// RateLimit bounds this tenant's request rate across all its panes.
RateLimit RateLimitRule
// Keys maps a dialect to the upstream API key used for THIS tenant's
// traffic (design 022 §8.3). A dialect absent here falls back to
// ProxyConfig.Keys, so a tenant may override one provider and inherit the
// rest.
//
// This is what turns a tenant from "a cap on a shared key" into a
// credential boundary: a reseller can bill each customer on the customer's
// own provider account, and one customer's key is never used for another's
// request.
Keys map[string]string
}
TenantRule is one customer's caps (design 022 §4.3).
type UpstreamConfig ¶
type UpstreamConfig struct {
Enabled bool `yaml:"enabled"`
// Governance opts this daemon into the server-side control plane
// (design 023): usage batches, org-wide leases and central policy.
//
// OFF by default, and separately from `enabled`, because reporting local
// spend to a server is a data-egress decision — the same posture 013 §3
// takes for audit forwarding. With it off, nothing in design 023 activates
// and every control behaves exactly as it does today; the OSS client stays
// fully functional standalone.
Governance bool `yaml:"governance"`
// GovernanceOnPartition decides what happens when the server is unreachable
// AND the daemon's lease has expired (design 023 §4.3):
//
// local (default) fall back to the machine-local ceiling — which is
// exactly what exists today, so a partition can never be WORSE
// than not having the feature
// strict refuse
// open allow
//
// Never "unlimited" by accident: an unrecognised value reads as local.
GovernanceOnPartition string `yaml:"governance_on_partition"`
URL string `yaml:"url"`
APIKey string `yaml:"api_key"`
Workspace string `yaml:"workspace"` // upstream NATS namespace: MUST be the server workspace ID (UUID), shown in the dashboard. Workspace names are unique per user, so the globally unique id is the wire namespace (ws.{id}.*). All sessions using the same id see the same shares.
ReconnectInterval string `yaml:"reconnect_interval"`
MaxReconnectAttempts int `yaml:"max_reconnect_attempts"`
AllowedCommands []string `yaml:"allowed_commands"`
CommandApproval bool `yaml:"command_approval"`
CommandBlocklist []string `yaml:"command_blocklist"`
// ForgedAPIAllow / ForgedAPIBlock are glob lists governing which forge-origin
// operations a remote subscriber may invoke on a forged-API share (Task 2,
// phase 2b). The block list always wins; a non-empty allow list requires a
// match; an empty allow list passes reads but denies mutating ops
// (default-deny mutation). They apply ONLY to forge-generated operations —
// never built-in tools, which are never shared.
ForgedAPIAllow []string `yaml:"forged_api_allow"`
ForgedAPIBlock []string `yaml:"forged_api_block"`
// ForgedAPIDelegatedAuth opts this share into Model B (delegated subscriber
// identity, forged-API auth plan §4/§8): when true, a subscriber-supplied
// access token is injected as the bearer for that one invocation so the
// backend enforces the subscriber's authorization. Off by default — Model A
// (owner identity) is used unless explicitly enabled. Gated on the §8 review.
ForgedAPIDelegatedAuth bool `yaml:"forged_api_delegated_auth"`
// PredictiveEcho: Mosh-style local echo when typing in a control-mode mirror
// (shared) tab — typed characters render immediately from a local prediction
// overlay and are dropped as the source's authoritative echo confirms them, so
// typing no longer waits a full network round-trip. This is now ALWAYS enabled
// for control-mode mirrors (it only predicts printable chars and self-corrects
// against the authoritative stream), so this flag no longer needs to be set; it
// is retained for backward compatibility.
PredictiveEcho bool `yaml:"predictive_echo"`
}
UpstreamConfig holds settings for the remote upstream server connection.
func (UpstreamConfig) WorkspaceName ¶
func (u UpstreamConfig) WorkspaceName() string
WorkspaceName returns the upstream NATS namespace token, defaulting to "default". Despite the name, this is the server workspace ID (UUID), not the human workspace name: names are unique per user, so the globally unique id is what namespaces NATS subjects (ws.{id}.*) on the upstream server. Set [upstream] workspace to the workspace id from the dashboard.
type UsageConfig ¶
type UsageConfig struct {
// RetentionDays bounds how long day aggregates are kept (default 90).
RetentionDays int
// Pricing maps a lowercased model prefix to a USD-per-1M-token override
// (also the µUSD-per-token cost). Applied over the built-in table.
Pricing map[string]UsagePriceOverride
}
UsageConfig configures the cost-observability ledger (design 003).
type UsagePriceOverride ¶
UsagePriceOverride is a per-model pricing override (USD per 1M tokens).
type VoiceConfig ¶
type VoiceConfig struct {
Enabled bool `yaml:"enabled"` // master switch
Hotkey string `yaml:"hotkey"` // toggle record key (Bubble Tea key string), default "ctrl+r"
Recorder string `yaml:"recorder"` // "auto" | "sox" | "ffmpeg" | "afrecord" | "arecord"
RecorderCmd string `yaml:"recorder_cmd"` // optional full recorder command override (uses {output})
MaxSeconds int `yaml:"max_seconds"` // hard cap on a single recording (0 = no cap)
Language string `yaml:"language"` // optional BCP-47 language hint; empty = auto
}
VoiceConfig holds the recording / TUI behaviour for voice prompting (the [voice] section).
type VoiceControlConfig ¶
type VoiceControlConfig struct {
TTSProviderName string `yaml:"tts_provider_name"` // "deepgram" (default) | "whisper" (alias: "openai")
APIKey string `yaml:"api_key"` // API key for the selected provider
}
VoiceControlConfig holds the speech-to-text provider settings for voice prompting (the [voice_control] section).
type WorkspaceConfig ¶
type WorkspaceConfig struct {
Name string `yaml:"name"`
InitialTabs int `yaml:"initial_tabs"`
InitialPanes int `yaml:"initial_panes"`
WorkingDirectory string `yaml:"working_directory"`
// Upstream optionally overrides the session-wide [upstream] block for this
// workspace (parsed from a [workspace.upstream] sub-table). When nil, the
// workspace inherits the top-level [upstream]. See ResolveUpstream.
Upstream *UpstreamConfig `yaml:"upstream"`
// Forge declares this workspace's forge integrations (add/enable/share) and
// subscriptions to remote shared APIs. Forge config is WORKSPACE-scoped only —
// there is no global forge section above the workspace.
Forge ForgeConfig `yaml:"forge"`
}
WorkspaceConfig defines a single workspace within a session. A session may host multiple isolated workspaces; each one gets its own WorkspaceActor subtree under the WorkspaceFarmActor. When no [[workspace]] entries are declared in rysh.config.yaml, a single workspace is synthesized from the top-level settings (see Config.ResolvedWorkspaces).
func (WorkspaceConfig) ResolveUpstream ¶
func (wc WorkspaceConfig) ResolveUpstream(session UpstreamConfig) UpstreamConfig
ResolveUpstream computes the effective upstream config for this workspace, given the session-wide upstream block. A workspace that declares its own [workspace.upstream] uses it, inheriting connection details (url/api_key/ reconnect/share-mode) from the session block for any field it leaves empty. A workspace with no override inherits the session block wholesale.
The upstream namespace (workspace) is the server workspace ID (UUID), which is what namespaces NATS subjects on the upstream server (ws.{id}.*) — server workspace names are unique per user, not globally, so the id is the wire key. It MUST be set explicitly (via [upstream] workspace or [workspace.upstream] workspace) for sharing to work. As a last-resort fallback it defaults to the local workspace name when left unset, but that only matches an upstream share if the server workspace id happens to equal the local name (it normally does not), so always set the id from the dashboard.