Documentation
¶
Overview ¶
Package config loads lazyshell's user configuration: a YAML file merged onto hardcoded defaults, so a missing or partial file is never an error.
Index ¶
- Constants
- Variables
- func AgentsDir() string
- func ControlSocketPath() string
- func DebugLogPath() string
- func IsTrusted(path string, content []byte) bool
- func Path() string
- func ProjectPath(flag string) string
- func RuntimeDir() string
- func RuntimeRoot() string
- func SaveState(cwd string, sessions []StateSession) error
- func StateDir() string
- func StatePath(cwd string) (string, error)
- func Trust(path string, content []byte) error
- func TrustPath() string
- type Clipboard
- type Config
- type Control
- type EnvTab
- type GroupSpec
- type Markers
- type Mouse
- type Notify
- type NumericBound
- type Perf
- type ProjectConfig
- type ResolvedSession
- type RestartPolicy
- type Scroll
- type SessionSpec
- type StateFile
- type StateSession
- type Theme
- type TrustStore
- type WatchSpec
- type WindowTitle
Constants ¶
const ( RestoreLayoutAsk = "ask" RestoreLayoutAlways = "always" RestoreLayoutNever = "never" )
RestoreLayoutAsk/Always/Never are Config.RestoreLayout's three valid values — see its doc comment.
Variables ¶
var Languages = map[string]bool{ "fr": true, "en": true, }
Languages are the values Config.Language accepts. The interface is written in French today and this field changes nothing yet — it exists now so that the i18n phase is a translation job and not also a configuration job, and so that a config file written today does not have to be edited then.
var NumericBounds = []NumericBound{
{"refresh_interval_ms", 10, 1000},
{"kill_timeout_ms", 100, 0},
{"scrollback_size", 0, 0},
{"sessions_panel_width", 5, 0},
{"sessions_panel_height", 5, 0},
{"agents_panel_height", 3, 0},
{"portrait_max_width", 0, 0},
{"portrait_min_height", 0, 0},
{"scroll.page_lines", 0, 0},
{"scroll.half_page_divisor", 1, 0},
{"mouse.wheel_lines", 1, 0},
}
NumericBounds is every int field Validate clamps, keyed by dotted config key, exported so the config-schema generator (cmd/gen-config-schema) can advertise the exact same bounds a running lazyshell would enforce, instead of a second hand-copied table that could quietly drift from this one.
Bounds are deliberately loose: the point is to catch a value that would break the UI (a zero-width panel, a redraw loop that pins a core), not to second-guess someone who wants a 400-column sessions list on an ultrawide.
var ProjectFileNames = []string{"lazyshell.yml", ".lazyshell.yml"}
ProjectFileNames are the file names looked up in the current directory, in this order, when no explicit path was given. No parent directory is ever searched: the file that gets executed must be the one sitting in the directory lazyshell was started from, with no ambiguity about which.
var RestartPolicyValues = []string{"never", string(RestartOnFailure), string(RestartAlways)}
RestartPolicyValues lists restart:'s valid *written* values — "never" rather than RestartNever's actual empty string, since that is what a project file author types and what the config-schema generator needs to offer as a closed choice. Enumerable the same way config.Languages already is, instead of re-deriving it from the constants above by hand.
var RestoreLayoutValues = []string{RestoreLayoutAsk, RestoreLayoutAlways, RestoreLayoutNever}
RestoreLayoutValues lists RestoreLayout's valid values, in the order they are documented in — enumerable the same way Languages already is, for anything that needs to offer a closed choice (validation error messages, the config-schema generator) rather than re-deriving it from the constants above by hand.
Functions ¶
func AgentsDir ¶
func AgentsDir() string
AgentsDir resolves the directory pkg/agent scans for user-supplied AI agent detection manifests that override or extend the built-in ones — $XDG_CONFIG_HOME/lazyshell/agents, else ~/.config/lazyshell/agents. Shares Path's XDG resolution rather than $LAZYSHELL_CONFIG, which names a single file, not a directory to derive one from.
func ControlSocketPath ¶
func ControlSocketPath() string
ControlSocketPath is the Unix socket the agent control API listens on when Control.Enabled is true — one per lazyshell process, not one per session: the session a verb applies to travels in the request, so per-session sockets would multiply RuntimeDir's path budget for nothing. Exported so pkg/gui can listen on it, pkg/session can inject it as $LAZYSHELL_CONTROL_SOCK, and `lazyshell ctl` can fall back to it when invoked outside a session.
func DebugLogPath ¶
func DebugLogPath() string
DebugLogPath resolves the file --debug appends to: $XDG_CONFIG_HOME/lazyshell/debug.log, else ~/.config/lazyshell/debug.log — next to config.yml, so there is a single lazyshell directory to know about. Shares AgentsDir's reasoning for ignoring $LAZYSHELL_CONFIG: that variable names one file, not a directory to derive others from. Empty when the home directory cannot be determined, which pkg/debug reports as an error.
func IsTrusted ¶
IsTrusted reports whether this exact content, at this exact path, has been approved. The hash is over the content, not the path alone, so any edit to the file asks again — which is the whole point: `git pull` can change what a project file launches.
func Path ¶
func Path() string
Path resolves the config file's location: $LAZYSHELL_CONFIG if set (mainly so tests never touch the real home directory), else $XDG_CONFIG_HOME/lazyshell/config.yml, else ~/.config/lazyshell/config.yml.
func ProjectPath ¶
ProjectPath resolves which project file to read: the --config-file flag, then $LAZYSHELL_PROJECT_CONFIG, then ./lazyshell.yml, then ./.lazyshell.yml. Returns "" when there is none, which means "behave exactly as before phase 6".
An explicitly requested path (flag or env) is returned even when it does not exist: asking for a specific file and getting silence instead of an error is worse than the error. The conventional names are only returned when present.
func RuntimeDir ¶
func RuntimeDir() string
RuntimeDir is where lazyshell's Unix sockets live: $XDG_RUNTIME_DIR/lazyshell/<pid>, falling back to os.TempDir()'s equivalent when $XDG_RUNTIME_DIR is unset — the same "always land somewhere, never fail silently" precedence Path uses for the config file. The pid segment is this lazyshell process's own, so two instances never collide: creation order alone ("session-1") is not unique across processes, and the control socket has no session in its name at all.
Callers must keep what they append short. A Unix socket path is capped at roughly 100 bytes depending on the platform, and a $TMPDIR-based fallback can already eat into that budget on its own.
func RuntimeRoot ¶
func RuntimeRoot() string
RuntimeRoot is RuntimeDir without the pid segment: the directory holding one subdirectory per lazyshell process on this machine. Exported for the one job that has to look across instances rather than inside its own — `lazyshell ctl` finding the lazyshell to talk to when it was not started from inside one of its sessions.
func SaveState ¶ added in v1.14.0
func SaveState(cwd string, sessions []StateSession) error
SaveState writes cwd's layout, overwriting whatever was there before. Written through a temporary file in the same directory then renamed, with mode 0600 before any content reaches it — the same atomic-write shape Trust uses for trust.yml, and for the same reason: a state file lists commands, and a half-written or world-readable one is worse than none.
func StateDir ¶ added in v1.14.0
func StateDir() string
StateDir is where saved layouts live: $XDG_CONFIG_HOME/lazyshell/state, else ~/.config/lazyshell/state — a subdirectory of configDir, so `~/.config/lazyshell` stays the one place to know about.
func StatePath ¶ added in v1.14.0
StatePath returns the file a given working directory's layout is saved to: StateDir joined with the sha256 hex of cwd's absolute, cleaned form. Two different directories never collide; the same directory always resolves to the same file regardless of a trailing slash or how it was reached.
Types ¶
type Clipboard ¶
type Clipboard struct {
FallbackCommand string `yaml:"fallback_command"`
}
Clipboard configures how copy-mode's yank leaves lazyshell. There is no reliable way to detect whether the host terminal actually accepted an OSC 52 sequence, so this is a manual switch rather than a fallback the code decides on its own: empty means "OSC 52 only", the choice that works through SSH and needs no binary installed; set means "run this command instead, with the yanked text on its stdin" — for a terminal that does not support OSC 52.
type Config ¶
type Config struct {
// Language is the UI language, "fr" or "en" — see pkg/i18n. Covers the
// interactive TUI (bindings, popups, status bar, footers, session
// messages); pkg/app's CLI output (`lazyshell config ...`) is unaffected
// and stays French, since it can run before a config file — and so a
// Language — has even been loaded.
Language string `yaml:"language"`
// Shell is the command started behind each new session's pty. Empty means
// "use $SHELL, falling back to /bin/bash" (resolved at use, not at load,
// so Default() does not need to touch the environment).
Shell string `yaml:"shell"`
// Term is the TERM value every session is started with. Lowering it below
// the bundled emulator's actual capabilities is the point of exposing it:
// some programs behave better when told less.
Term string `yaml:"term"`
// ScrollbackSize is the maximum number of lines a session's terminal
// emulator keeps once they scroll off-screen.
ScrollbackSize int `yaml:"scrollback_size"`
// SessionsPanelWidth is the sessions list's width in landscape mode, in
// columns — see pkg/gui/layout.go.
SessionsPanelWidth int `yaml:"sessions_panel_width"`
// SessionsPanelHeight is the sessions list's height in portrait mode, in
// rows.
SessionsPanelHeight int `yaml:"sessions_panel_height"`
// AgentsPanelHeight is the agents dashboard's height, in rows, under the
// sessions panel in landscape mode — see pkg/gui/layout.go. The panel
// itself is hidden automatically when no AI agent session is detected, or
// in portrait mode, so there is no separate enabled flag.
AgentsPanelHeight int `yaml:"agents_panel_height"`
// PortraitMaxWidth and PortraitMinHeight are the terminal geometry at which
// the layout switches to stacking the panels: portrait applies when the
// terminal is at most PortraitMaxWidth columns wide *and* more than
// PortraitMinHeight rows tall.
PortraitMaxWidth int `yaml:"portrait_max_width"`
PortraitMinHeight int `yaml:"portrait_min_height"`
// RefreshIntervalMs is how often, in milliseconds, the sessions list and
// the output panel are re-rendered. Lower is smoother and costs more CPU;
// an unchanged panel is never pushed, so idle cost stays near zero either
// way.
RefreshIntervalMs int `yaml:"refresh_interval_ms"`
// KillTimeoutMs is how long, in milliseconds, killing a session waits after
// SIGTERM before escalating to SIGKILL, and again after that before giving
// up.
KillTimeoutMs int `yaml:"kill_timeout_ms"`
// PrefixKey is the pass-through escape key, in gocui.Parse syntax
// ("Ctrl+A", "Ctrl+Space", ...). Overridable at runtime via
// $LAZYSHELL_PREFIX, which wins over this value.
PrefixKey string `yaml:"prefix_key"`
// Keybindings remaps actions (stable ids such as "new_session") to a
// gocui.Parse key spec. An action missing from this map keeps its
// built-in default.
Keybindings map[string]string `yaml:"keybindings"`
// Markers overrides the sessions list's gutter markers.
Markers Markers `yaml:"markers"`
// Scroll overrides the output panel's scrolling steps.
Scroll Scroll `yaml:"scroll"`
// Theme overrides the UI's colors. An empty field keeps its built-in
// default (see pkg/gui's Theme/defaultTheme).
Theme Theme `yaml:"theme"`
// Clipboard configures copy-mode's yank.
Clipboard Clipboard `yaml:"clipboard"`
// Notify configures the desktop notification sent when a detected AI
// agent session goes blocked or done (pkg/agent).
Notify Notify `yaml:"notify"`
// WindowTitle configures whether the host terminal's window/tab title
// tracks the focused session's name and live OSC title.
WindowTitle WindowTitle `yaml:"window_title"`
// Mouse configures clicking, wheel scrolling and drag selection.
Mouse Mouse `yaml:"mouse"`
// EnvTab configures the output panel's env tab.
EnvTab EnvTab `yaml:"env_tab"`
// Perf configures the output panel's perf tab.
Perf Perf `yaml:"perf"`
// Control configures the agent control API (pkg/control). Off by default,
// deliberately — see docs/adr/0006-api-de-controle-par-les-agents.md.
Control Control `yaml:"control"`
// AgentStatsCommand, when set, is run for the selected session — with
// $LAZYSHELL_SESSION_ID in its environment — and its first line of
// stdout is shown alongside the turn duration. Best-effort: lazyshell
// does not parse token/cost data itself, the same "external command
// whose output line is displayed" shape as Claude Code's own
// statusLine. Empty means no stats line.
AgentStatsCommand string `yaml:"agent_stats_command"`
// RestoreLayout controls the prompt offered at launch when no
// lazyshell.yml is present and a previous run's layout was saved for this
// directory (docs/adr/0013-persistance-de-la-disposition.md): "ask" (the
// default) shows a confirmation popup, "always" restores it with no
// prompt, "never" never offers it. A project file's declared sessions
// always take priority over a saved layout regardless of this setting —
// the two are never merged.
RestoreLayout string `yaml:"restore_layout"`
// Warnings lists the keys the file contained but this struct has no field
// for, so that a typo says why it does nothing instead of being silently
// dropped. Filled by Load; never read from the file itself.
Warnings []string `yaml:"-"`
}
Config is lazyshell's user-facing configuration. Every field has a meaningful default (see Default), so a config file only needs to mention the fields it wants to override.
Adding a field here is not enough to ship it: it must be wired to whatever used to hardcode it, validated in Validate, and documented in the README's reference table — doc_test.go fails the build otherwise.
func Default ¶
func Default() Config
Default returns the configuration lazyshell runs with when there is no config file, and what a partial file is merged onto.
func Load ¶
Load reads the YAML file at path and merges it onto Default(). A missing file is not an error — it just means "run with the defaults". Fields absent from the file are left untouched by yaml.Unmarshal, which is what makes the merge work: Unmarshal only sets the keys it actually finds.
Keys the file contains but Config has no field for end up in Warnings rather than being dropped in silence: `session_panel_width` (one letter short) is otherwise indistinguishable, from the user's side, from lazyshell ignoring the config file entirely.
func (Config) MergeProject ¶
func (c Config) MergeProject(p ProjectConfig) Config
MergeProject applies the project file on top of the user configuration. Shell is the only field it can touch — see ProjectConfig's doc comment for why the rest is off limits.
func (*Config) Validate ¶
Validate checks every numeric and enumerated field, replacing whatever is out of range with its built-in default and reporting what it did.
Same contract as ProjectConfig.Validate: a bad value never stops lazyshell from starting. A config file is hand-edited and read once at boot, and the terminal is about to be taken over — refusing to start over `refresh_interval_ms: 0` would leave the user with an unusable tool and no way to see why. Correcting the value and saying so out loud is the only behaviour that is both safe and honest.
Key specs (PrefixKey, Keybindings) and colors are *not* checked here: parsing them needs gocui, and this package deliberately has no such dependency. They are validated by pkg/gui's ValidateKeys and ValidateTheme, called from the same place in pkg/app.
type Control ¶
type Control struct {
// Enabled turns the whole API on: the socket is only created, and
// $LAZYSHELL_CONTROL_SOCK only injected into sessions, when it is true.
Enabled bool `yaml:"enabled"`
}
Control configures the agent control API: a Unix socket, one per lazyshell process, over which `lazyshell ctl` lists the sessions, reads their output, creates new ones, types into them, kills and renames them (pkg/control).
Unlike the hook channel of pkg/hook — which is inbound and declarative, an agent stating its own state and nothing else — this one carries verbs, and two of them (`new`, `send`) amount to running commands as you. There is no token: the socket's 0600 permissions are the only boundary, so enabling this means every process running under your account can drive lazyshell, not just the agents you started inside it. Hence off by default.
type EnvTab ¶
type EnvTab struct {
// MaskSecrets replaces the value of any variable whose *name* looks like a
// credential (TOKEN, SECRET, PASSWORD, AUTH, ..._KEY) with a fixed-width
// mask. On by default: the panel is as shareable as a screenshot of it,
// and an API key must not be what makes a screenshot dangerous. Set to
// false to see the real values.
MaskSecrets bool `yaml:"mask_secrets"`
}
EnvTab configures the output panel's env tab, which lists the environment a session's shell was launched with.
type GroupSpec ¶
type GroupSpec struct {
Name string `yaml:"name"`
}
GroupSpec is one declared group, as written in the file.
A name and nothing else, deliberately. A separate display label was considered and dropped: the name is already the string the panel shows, and a second field saying the same thing is exactly the speculative surface this file's whitelist exists to keep out.
type Markers ¶
type Markers struct {
// Bell flags a session that emitted a BEL since it was last looked at.
Bell string `yaml:"bell"`
// AltScreen flags a session with a full-screen application in control.
AltScreen string `yaml:"alt_screen"`
// Activity flags a session that produced output since it was last looked
// at, other than the one currently selected.
Activity string `yaml:"activity"`
// Broadcast flags a session marked to receive broadcast keystrokes.
Broadcast string `yaml:"broadcast"`
// AgentIdle/AgentWorking/AgentBlocked/AgentDone flag a detected AI agent
// session's state (pkg/agent) — idle, working, waiting on you, or done
// with its turn. Empty for a session that is not running a known agent.
AgentIdle string `yaml:"agent_idle"`
AgentWorking string `yaml:"agent_working"`
AgentBlocked string `yaml:"agent_blocked"`
AgentDone string `yaml:"agent_done"`
// AgentIdleColor/AgentWorkingColor/AgentBlockedColor/AgentDoneColor color
// the four markers above, in Theme's syntax (W3C name, ANSI alias, or
// "#rrggbb"). AgentWorkingColor also drives the working marker's pulse:
// it alternates between this color at full and dimmed brightness.
AgentIdleColor string `yaml:"agent_idle_color"`
AgentWorkingColor string `yaml:"agent_working_color"`
AgentBlockedColor string `yaml:"agent_blocked_color"`
AgentDoneColor string `yaml:"agent_done_color"`
// CommandFailed flags a non-agent session whose last command (per OSC 133
// shell integration) exited non-zero. Not part of the fixed gutter above:
// it is prepended to the detail column instead, alongside its exit code,
// which the gutter's fixed width has no room for.
CommandFailed string `yaml:"command_failed"`
// Restart flags a session (declared with a restart: policy) that has
// needed at least one automatic restart. Same treatment as CommandFailed:
// prepended to the detail column, next to its attempt count, not part of
// the fixed gutter.
Restart string `yaml:"restart"`
}
Markers is the four-column gutter every session line starts with — the only way to learn something about a session that is not the one on screen.
type Mouse ¶
type Mouse struct {
// Enabled turns the whole feature on or off, including gocui's own mouse
// reporting, which hands wheel and selection back to the host terminal.
Enabled bool `yaml:"enabled"`
// WheelLines is how many lines one wheel notch scrolls the output panel
// by. Zero falls back to the built-in default.
WheelLines int `yaml:"wheel_lines"`
// ForwardToApp lets a full-screen program running inside a session (vim
// with `set mouse=a`, htop) receive the mouse events itself, but only once
// it has explicitly asked for them with a DECSET 9/1000/1002/1003. A
// program that never asks — a shell, an AI agent CLI — never sees them,
// and the wheel keeps scrolling lazyshell's own scrollback.
ForwardToApp bool `yaml:"forward_to_app"`
}
Mouse configures the mouse support. It is on by default, and the switch exists because enabling it is not free: gocui gives mouse buttons and the Shift-Up/Shift-Down keys the very same values (MouseLeft is KeyShiftArrowDown, MouseRight is KeyShiftArrowUp), so the two cannot be told apart. lazyshell resolves the ambiguity in favour of the mouse and stops forwarding those two keys to the session — see docs/adr/0003-souris.md. Setting Enabled to false gives them back.
type Notify ¶
type Notify struct {
FallbackCommand string `yaml:"fallback_command"`
}
Notify configures the notification sent when a detected AI agent session goes blocked or done. Same shape and the same reasoning as Clipboard: OSC (9 and 777 here, both sent unconditionally — an unsupported one is simply ignored by the terminal, so sending both costs nothing and reaches more terminals than picking one) is a write with no acknowledgement, so the fallback is a manual switch rather than something auto-detected: empty means OSC only, set means this command runs instead, with the notification text on its stdin.
type NumericBound ¶ added in v1.16.0
type NumericBound struct {
// Key is the field's dotted config key, e.g. "scroll.half_page_divisor".
Key string
// Min and Max bound the field. Max == 0 means "no upper bound" — the same
// convention clamp itself uses.
Min, Max int
}
NumericBound is one entry of NumericBounds — see its doc comment.
type Perf ¶
type Perf struct {
// RefreshIntervalMs is how often the tab actually samples the process,
// independently of RefreshIntervalMs's redraw tick. Sampling is the one
// genuinely expensive thing this panel does — on macOS it spawns a `ps`,
// for want of a cgo-free alternative — so it is deliberately an order of
// magnitude slower than the redraw, and a CPU percentage is meaningless
// over a 30 ms window anyway.
RefreshIntervalMs int `yaml:"refresh_interval_ms"`
}
Perf configures the output panel's perf tab, which reports what a session's process is consuming.
type ProjectConfig ¶
type ProjectConfig struct {
// Shell overrides the user configuration's Shell for this project only.
Shell string `yaml:"shell"`
// Sessions are started at launch, in this order.
Sessions []SessionSpec `yaml:"sessions"`
// Groups declares the session groups this project uses, and — this is the
// part that matters — the order their headers appear in the sessions
// panel. Declaring a group is optional: a session may name a group that is
// not listed here, and it simply sorts after the declared ones.
//
// A group carries a name and a label, and deliberately nothing else. Per
// this struct's doc comment above, a project file says what exists; how
// the user's interface renders it is not a repository's business.
Groups []GroupSpec `yaml:"groups"`
// EnvFiles are .env-style files loaded, in order, for every session this
// project declares — before each session's own EnvFiles, and before its
// inline Env. Relative paths resolve against the project file's
// directory, like Cwd.
EnvFiles []string `yaml:"env_files"`
// NoDefaultEnv disables the automatic "<session cwd>/.env" lookup for
// every declared session, unless a session's own NoDefaultEnv overrides
// it back on.
NoDefaultEnv *bool `yaml:"no_default_env"`
// Path is the absolute path of the file this was read from. Relative cwds
// resolve against its directory, not against the process's.
Path string `yaml:"-"`
// Raw is the file's content as read, hashed by the trust store so that
// editing the file asks for approval again.
Raw []byte `yaml:"-"`
// Warnings lists the keys that were present but ignored, so that a `theme:`
// in a project file says why it does nothing instead of being silently
// dropped.
Warnings []string `yaml:"-"`
}
ProjectConfig is the subset of Config a lazyshell.yml may override, plus the sessions it declares. The whitelist is not a filter applied after the fact: it is this struct — a key with no field here has nowhere to land. That is deliberate. A lazyshell.yml comes from a repository, possibly someone else's, and must never be able to remap keybindings or the pass-through prefix under the user's fingers.
func LoadProject ¶
func LoadProject(path string) (ProjectConfig, error)
LoadProject reads and parses the project file at path. Unlike Load, a missing file *is* an error here: ProjectPath only returns a conventional name when it exists, so reaching this with a missing file means the user named it explicitly.
func ParseProject ¶
func ParseProject(path string, data []byte) (ProjectConfig, error)
ParseProject is LoadProject's pure half: everything but the file read, so the parsing and whitelist rules can be tested without a filesystem.
func (ProjectConfig) ResolvedGroups ¶
func (p ProjectConfig) ResolvedGroups() ([]string, []error)
ResolvedGroups returns the declared group names, trimmed, in declaration order — which is the order the sessions panel draws their headers in. Bad entries are dropped and reported, exactly like Validate's bad sessions: a hand-edited file must never cost the user the groups that were fine.
Kept separate from Validate rather than folded into it because the two answer different questions and have different consumers: Validate produces sessions to start, this produces a display order. Their errors are joined by the caller.
func (ProjectConfig) Validate ¶
func (p ProjectConfig) Validate() ([]ResolvedSession, []error)
Validate resolves and checks every declared session. Invalid entries are dropped and reported; the valid ones still start. A project file is edited by hand and read at startup — one bad entry must never cost the user the other sessions, and must never panic or fail silently.
type ResolvedSession ¶
type ResolvedSession struct {
Name string
// Group is the session's group, trimmed; "" for an ungrouped session.
Group string
Cwd string
Command string
Env map[string]string
// EnvFiles is the project's EnvFiles followed by the session's own,
// already resolved to absolute paths.
EnvFiles []string
// NoDefaultEnv is the session's NoDefaultEnv if set, else the project's.
// Nil means neither said anything — defer to the Manager's own setting.
NoDefaultEnv *bool
// Watch is the session's pattern watchers, already checked for a valid
// regexp — see Validate, which drops (and reports) any entry that fails
// to compile rather than letting a typo cost the whole session.
Watch []WatchSpec
// Restart is the session's automatic restart policy, already checked —
// see resolveRestartPolicy. An unrecognized value falls back to
// RestartNever with a warning, rather than dropping the session: unlike
// a bad watch pattern, a bad restart policy is a single scalar with a
// safe default, not a list of independent entries.
Restart RestartPolicy
// StopOnFailure is whether this session should be killed outright when
// its Command exits non-zero, instead of leaving the shell open
// underneath. Carried through unresolved — Validate never forces it
// false even when Command is empty, in which case it is simply inert
// (see warnIfStopOnFailureIsInert for why that case is still reported).
StopOnFailure bool
// Locked is whether the session starts locked, already resolved: the
// declared value if there was one, else "it has a Command". Never nil —
// SessionSpec.Locked's "not declared" state does not survive validation,
// so nothing downstream has to re-derive the heuristic.
Locked bool
}
ResolvedSession is a SessionSpec that passed validation, with its working directory made absolute. This — not SessionSpec — is what session creation consumes, so an unresolved relative path cannot reach a pty by accident.
type RestartPolicy ¶
type RestartPolicy string
RestartPolicy is a session's validated automatic restart policy. RestartNever is the empty string deliberately: it is Go's zero value, so every existing session.Options{} literal — every test, every session not declared in a project file — keeps meaning "never restart" with no explicit opt-out required.
const ( RestartNever RestartPolicy = "" RestartOnFailure RestartPolicy = "on-failure" RestartAlways RestartPolicy = "always" )
type Scroll ¶
type Scroll struct {
// PageLines is how many lines PgUp/PgDn move by. Zero means "one full
// panel height", which is what a page key normally does.
PageLines int `yaml:"page_lines"`
// HalfPageDivisor is what the panel height is divided by for Ctrl-U and
// Ctrl-D. The default of 2 is the half page the keys are named after; 4
// gives a quarter page.
HalfPageDivisor int `yaml:"half_page_divisor"`
}
Scroll is how far the output panel moves per scrolling keystroke.
type SessionSpec ¶
type SessionSpec struct {
Name string `yaml:"name"`
// Group is the group this session starts in, "" for none. It need not be
// declared in Groups; that block only fixes the display order.
Group string `yaml:"group"`
Cwd string `yaml:"cwd"`
Command string `yaml:"command"`
Env map[string]string `yaml:"env"`
// EnvFiles are .env-style files loaded for this session only, after the
// project's own EnvFiles — a later file, and this list, override a key
// set by anything earlier. Relative paths resolve against the project
// file's directory, like Cwd.
EnvFiles []string `yaml:"env_files"`
// NoDefaultEnv overrides the project's NoDefaultEnv for this session
// only. Nil means "use the project's setting".
NoDefaultEnv *bool `yaml:"no_default_env"`
// Watch declares this session's pattern watchers: a regex evaluated
// against each output line, and whether a match notifies. Same
// GroupSpec precedent as the rest of this file — what exists, not what
// the interface looks like.
Watch []WatchSpec `yaml:"watch"`
// Restart is this session's automatic restart policy, as written:
// "never" (or empty), "on-failure", or "always". See resolveRestartPolicy
// for what happens to anything else.
Restart string `yaml:"restart"`
// StopOnFailure, when true, kills the session outright as soon as its
// declared Command exits non-zero (detected via the shell's own OSC 133
// integration, pkg/screen), instead of leaving the shell open underneath
// as the default does. False (the zero value) leaves today's behaviour
// untouched. Declaring it without a Command is accepted but inert — see
// warnIfStopOnFailureIsInert.
StopOnFailure bool `yaml:"stop_on_failure"`
// Locked is whether this session starts in locked mode — the output panel
// showing it but not forwarding keystrokes to it. Nil means "not
// declared", and Validate then locks the session if, and only if, it
// declares a Command: a declared command is something you watch, and a
// stray keystroke landing in it can kill it.
//
// This is the one interface-shaped key the whitelist admits (see
// ProjectConfig's doc comment), because what it protects is the declared
// process itself, and the worst a hostile value can do is make the user
// press "i". It says nothing about colours, keys or layout.
Locked *bool `yaml:"locked"`
}
SessionSpec is one declared session, as written in the file.
func (SessionSpec) ResolveCwd ¶
func (s SessionSpec) ResolveCwd(configDir string) (string, error)
ResolveCwd turns the spec's Cwd into an absolute, existing directory. It is resolved against configDir — the directory holding the project file — not against the process's working directory, so that a `cwd: ./services/api` means the same thing however lazyshell was invoked. An empty Cwd is configDir itself.
type StateFile ¶ added in v1.14.0
type StateFile struct {
// Path is the cwd this layout was saved for, in clear — the filename is
// its hash, so this is what makes a directory listing legible.
Path string `yaml:"path"`
SavedAt time.Time `yaml:"saved_at"`
// Sessions are in the order they appeared in the sessions panel.
Sessions []StateSession `yaml:"sessions"`
}
StateFile is the on-disk record of one directory's session layout.
func LoadState ¶ added in v1.14.0
LoadState reads cwd's saved layout. A missing file is not an error — it returns (nil, nil), the same "nothing to report" idiom ProjectPath's "" return already establishes elsewhere in this package. A file whose permissions are wider than the 0600 SaveState writes is treated the same way: silently ignored rather than trusted, since anything on the machine could have altered it once it stopped being owner-only.
type StateSession ¶ added in v1.14.0
type StateSession struct {
Name string `yaml:"name"`
Group string `yaml:"group,omitempty"`
Cwd string `yaml:"cwd"`
Command string `yaml:"command,omitempty"`
}
StateSession is one session's saved recipe: exactly the fields the roadmap scoped this feature to. Deliberately narrower than SessionSpec — Env, EnvFiles, Watch, Restart and Locked are project-declaration concerns that go through the trust store, and this file never does.
type Theme ¶
type Theme struct {
ActiveBorderColor string `yaml:"active_border_color"`
InactiveBorderColor string `yaml:"inactive_border_color"`
SelectedBgColor string `yaml:"selected_bg_color"`
LockedBorderColor string `yaml:"locked_border_color"`
TabActiveColor string `yaml:"tab_active_color"`
}
Theme is the color part of Config, kept as plain strings (W3C color names or "#rrggbb", gocui.GetColor's syntax) so this package stays free of a gocui dependency — pkg/gui resolves them to actual gocui Attributes.
type TrustStore ¶
TrustStore maps a project file's absolute path to the sha256 of the content that was approved for it.
func LoadTrust ¶
func LoadTrust(path string) TrustStore
LoadTrust reads the trust store. A missing or unreadable store is not an error: it just means nothing is approved yet, which fails closed.
type WindowTitle ¶
type WindowTitle struct {
Enabled bool `yaml:"enabled"`
}
WindowTitle configures the OSC 0 sequence lazyshell writes to the host terminal so its window/tab title follows the focused session: its name, plus whatever OSC 0/2 title the program running inside that session's pty last set (pkg/screen's Screen.Title), when it has set one. Unlike Clipboard/Notify there is no fallback command — an unsupported OSC 0 is simply ignored by the terminal, so the only knob needed is on/off.