lib

package
v0.12.0-beta Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: GPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

The lib package is the implementation of the core functionality of the raid CLI tool.

Index

Constants

View Source
const (
	ConfigDirName       = ".raid"
	ConfigFileName      = "config.toml"
	ConfigPathDefault   = "~" + sys.Sep + ConfigDirName + sys.Sep + ConfigFileName
	ConfigPathFlag      = "config"
	ConfigPathFlagShort = "c"
	ConfigPathFlagDesc  = "configuration file path (default is " + ConfigPathDefault + ")"
)
View Source
const (
	RecentStatusCompleted   = "completed"
	RecentStatusInterrupted = "interrupted"
)

Recent statuses surfaced via ReadRecent. "running" only ever appears on disk while a command is in flight; ReadRecent rewrites it to RecentStatusInterrupted so consumers always see one of these two terminal states.

View Source
const (
	RaidConfigFileName = "raid.yaml"
)

Variables

View Source
var CfgPath string
View Source
var LockPathOverride string

LockPathOverride redirects the mutation lock file. Tests set this so they don't contend with a real raid invocation running on the developer's machine. Empty in production.

View Source
var RecentPathOverride string

RecentPathOverride redirects the recent.json log path. Intended only for tests that exercise ExecuteCommand so they do not pollute the developer's real ~/.raid/recent.json.

Functions

func AcquireMutationLock

func AcquireMutationLock() (func(), error)

AcquireMutationLock blocks until this process holds the raid mutation lock, then returns a release function. Callers MUST defer the release.

This is a cross-process exclusive lock backed by flock(2) on POSIX and LockFileEx on Windows. Any raid invocation (CLI or MCP server) that calls AcquireMutationLock while another raid process holds it will wait until released. The kernel releases the lock automatically if the holder exits unexpectedly — no stale-lock recovery code needed.

Read-only operations (raid context, raid_list_*, raid doctor, etc.) don't need this lock; stale reads during a mutation are recoverable. Only paths that write ~/.raid state, .env files in repos, or run task sequences should acquire it.

func AddProfile

func AddProfile(profile Profile) error

AddProfile registers a profile in the config store.

func AddProfiles

func AddProfiles(profiles []Profile) error

AddProfiles registers multiple profiles in the config store.

func CloneRepository

func CloneRepository(repo Repo) error

CloneRepository clones a repository to its configured path. Skips if it already exists. Repos with no configured `url` (local-only) are never cloned — the path must already exist on disk; otherwise an error is returned so the user knows the local repo is missing rather than getting a confusing git error.

func ContainsEnv

func ContainsEnv(name string) bool

ContainsEnv reports whether an environment with the given name exists in the active profile.

func ContainsProfile

func ContainsProfile(name string) bool

ContainsProfile reports whether a profile with the given name is registered.

func CreateRepoConfigs

func CreateRepoConfigs(repos []RepoDraft)

CreateRepoConfigs writes a raid.yaml stub into each repository's local directory.

func ExecuteCommand

func ExecuteCommand(name string, args []string, named map[string]string) error

ExecuteCommand runs the tasks for the named command, applying any output configuration. Positional `args` are exposed as RAID_ARG_1, RAID_ARG_2, ... environment variables. When `named` is non-nil, each entry is also exported as a env var with the key uppercased — this is how cobra-parsed named arguments and flags reach task scripts. All bindings are unset after the command exits.

func ExecuteEnv

func ExecuteEnv(name string) error

ExecuteEnv writes environment variables to each repo's .env file and runs the environment's tasks.

func ExecuteRepoCommand

func ExecuteRepoCommand(repoName, cmdName string, args []string, named map[string]string) error

ExecuteRepoCommand runs a command defined in a specific repository's raid.yaml. See ExecuteCommand for how `args` and `named` are bound to env vars.

func ExecuteTask

func ExecuteTask(task Task) error

func ExecuteTasks

func ExecuteTasks(tasks []Task) error

func ForceLoad

func ForceLoad() error

ForceLoad rebuilds the context from the active profile, ignoring any cached state.

func GetEnv

func GetEnv() string

GetEnv returns the name of the currently active environment.

func InitConfig

func InitConfig() error

func Install

func Install(maxThreads int) error

Install clones all repositories in the active profile and runs install tasks.

func InstallRepo

func InstallRepo(name string) error

InstallRepo clones a single named repository and runs its install tasks. The profile-level install tasks are not run.

func ListEnvs

func ListEnvs() []string

ListEnvs returns the names of all environments in the active profile.

func Load

func Load() error

Load initializes the context from the active profile, using cached results if available.

func LoadEnv

func LoadEnv() error

LoadEnv loads .env files from all repositories in the active profile into the process environment.

func RecordRecentEnd

func RecordRecentEnd(command string, runErr error, startedAt time.Time)

RecordRecentEnd updates the placeholder entry written by RecordRecentStart with the command's exit code and total duration. If the matching entry has fallen off the cap, no update is recorded.

func RecordRecentStart

func RecordRecentStart(command string) time.Time

RecordRecentStart prepends a placeholder entry for command in the recent log and returns the moment recording began. Pass the same value to RecordRecentEnd once the command has finished. Errors writing the log are silenced — recording history must never break command execution.

The returned time keeps full nanosecond precision so it can be used as an unambiguous key by RecordRecentEnd: two commands started in the same second would alias if we truncated. Display formatters can re-truncate at render time when a coarser granularity is desired.

func RemoveProfile

func RemoveProfile(name string) error

RemoveProfile removes a registered profile by name.

func ResetContext

func ResetContext()

ResetContext clears the cached load context, forcing the next Load or ForceLoad to rebuild from the current viper configuration.

func Set

func Set(key string, value any) error

Set stores key in the viper config and persists it to disk.

func SetCommandOutput

func SetCommandOutput(stdout, stderr io.Writer) func()

SetCommandOutput swaps the writers used by task execution, repository cloning, and environment setup. The returned function restores the previous writers — call via defer to keep state clean even if the caller panics. Not safe for concurrent calls; serialise at the call site if multiple goroutines may run mutating operations.

func SetEnv

func SetEnv(name string) error

SetEnv sets the named environment as the active environment.

func SetProfile

func SetProfile(name string) error

SetProfile sets the named profile as the active profile.

func ValidateProfile

func ValidateProfile(path string) error

ValidateProfile validates the profile file at path against the profile JSON schema.

func ValidateRepo

func ValidateRepo(path string) error

ValidateRepo validates the repo config file at path against the repo JSON schema.

func ValidateSchema

func ValidateSchema(path string, schemaPath string) error

ValidateSchema validates the file at path against the JSON schema at schemaPath. schemaPath must be an absolute or CWD-relative path to a schema file on disk.

func WatchRaidVars

func WatchRaidVars(ctx stdctx.Context, onChange func()) error

WatchRaidVars watches the raid vars file (~/.raid/vars) for the lifetime of ctx and invokes onChange whenever the file is created, modified, or replaced. Events are debounced. The watcher is attached to the parent directory so atomic-rename writes (which swap the inode) keep firing — a watch on the file itself would silently go deaf after the first rename.

onChange is the caller's reload hook; lib does not assume what to reload, so the MCP server passes a closure that runs ForceLoad under the cross-process mutation lock.

func WithMutationLock

func WithMutationLock(fn func() error) error

WithMutationLock acquires the mutation lock, runs fn, and releases the lock. Use this when an entire user-visible operation should be atomic across processes — for example, the env-switch sequence (set + force load + execute) where any gap between steps could let another raid invocation interleave.

func Write

func Write() error

Write persists the current viper config to disk.

func WriteProfileFile

func WriteProfileFile(draft ProfileDraft, path string) error

WriteProfileFile serializes draft as YAML and writes it to path, creating parent directories as needed.

Types

type Arg

type Arg struct {
	Name     string `json:"name"`
	Usage    string `json:"usage,omitempty"`
	Required bool   `json:"required,omitempty"`
}

Arg declares a positional argument for a custom command. The supplied value is bound to the env var named after Name (uppercased) for the duration of the command, so tasks can reference it as `$NAME`. Required args without a matching positional value cause cobra to reject the invocation.

type Command

type Command struct {
	Name  string  `json:"name"`
	Usage string  `json:"usage"`
	Args  []Arg   `json:"args,omitempty"`
	Flags []Flag  `json:"flags,omitempty"`
	Tasks []Task  `json:"tasks"`
	Out   *Output `json:"out,omitempty"`
}

Command is a named, user-defined CLI command that can be invoked via 'raid <name>'.

func GetCommands

func GetCommands() []Command

GetCommands returns all commands available in the active profile.

func QuietLoad

func QuietLoad() []Command

QuietLoad attempts a best-effort, read-only profile load. It does not create config files, does not emit warnings, and returns nil if the config is absent or loading fails. Intended for info-command paths (--help, --version) where user-command registration is opportunistic and side effects are undesirable.

func (Command) IsZero

func (c Command) IsZero() bool

IsZero reports whether the command is uninitialized.

type Condition

type Condition struct {
	Platform string `json:"platform,omitempty"`
	Exists   string `json:"exists,omitempty"`
	Cmd      string `json:"cmd,omitempty"`
}

Condition guards a task — all specified fields must be satisfied for the task to run.

func (Condition) IsZero

func (c Condition) IsZero() bool

IsZero reports whether no condition fields are set.

type Context

type Context struct {
	Profile Profile
	Env     string
}

Context holds the active profile and environment for the current raid session.

type Env

type Env struct {
	Name      string   `json:"name"`
	Variables []EnvVar `json:"variables"`
	Tasks     []Task   `json:"tasks"`
}

Env represents a named environment with variables and tasks.

func (Env) IsZero

func (e Env) IsZero() bool

IsZero reports whether the environment is uninitialized.

type EnvVar

type EnvVar struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

EnvVar is a key/value pair written into a repository's .env file.

type Finding

type Finding struct {
	Severity   Severity
	Check      string
	Message    string
	Suggestion string // shown when non-empty
}

Finding represents the result of a single doctor check.

func RunDoctor

func RunDoctor() []Finding

RunDoctor performs all configuration checks and returns the findings.

type Flag

type Flag struct {
	Name     string `json:"name"`
	Short    string `json:"short,omitempty"`
	Usage    string `json:"usage,omitempty"`
	Type     string `json:"type,omitempty"`
	Required bool   `json:"required,omitempty"`
	Default  any    `json:"default,omitempty"`
}

Flag declares a long-form (--name) and optional short-form (-x) flag for a custom command. Type is one of "string" (default), "bool", or "int". Required flags are enforced by cobra. Default supplies the value when the flag is omitted; bool flags default to false unless overridden.

type OnInstall

type OnInstall struct {
	Tasks []Task `json:"tasks"`
}

OnInstall holds the tasks to run during profile installation.

type Output

type Output struct {
	Stdout bool   `json:"stdout"`
	Stderr bool   `json:"stderr"`
	File   string `json:"file,omitempty"`
}

Output configures how a command's task output is handled. Stdout and Stderr default to true when Out is nil. When Out is set, only streams explicitly set to true are shown.

type Profile

type Profile struct {
	Name         string            `json:"name"`
	Path         string            `json:"path"`
	Repositories []Repo            `json:"repositories"`
	Environments []Env             `json:"environments"`
	Install      OnInstall         `json:"install"`
	Groups       map[string][]Task `json:"task_groups" yaml:"task_groups"`
	Commands     []Command         `json:"commands"`
}

Profile represents a named collection of repositories, environments, and task groups.

func BuildSingleRepoProfile

func BuildSingleRepoProfile(path string) (Profile, error)

BuildSingleRepoProfile validates the raid.yaml at path and returns a synthetic profile whose only repository points at the raid.yaml's directory. The profile's Path is the raid.yaml itself, so IsSingleRepo reports true on subsequent loads. The repository's full configuration (commands, environments, install tasks) is merged in by buildRepo later in the load pipeline.

func ExtractProfile

func ExtractProfile(name, path string) (Profile, error)

ExtractProfile reads and returns a single named profile from the given file.

func ExtractProfiles

func ExtractProfiles(path string) ([]Profile, error)

ExtractProfiles reads all profiles from a YAML or JSON file.

func GetProfile

func GetProfile() Profile

GetProfile returns the currently active profile.

func ListProfiles

func ListProfiles() []Profile

ListProfiles returns all registered profiles.

func (Profile) IsSingleRepo

func (p Profile) IsSingleRepo() bool

IsSingleRepo reports whether the profile is backed by a raid.yaml (repo config) rather than a profile YAML. Detected by the registered path's basename: when `raid profile add ./raid.yaml` records a path pointing at a repo config, raid synthesizes a single-repo profile around it instead of requiring a wrapping profile file. The buildProfile / doctor paths branch on this check to switch schemas.

func (Profile) IsZero

func (p Profile) IsZero() bool

IsZero reports whether the profile is uninitialized.

type ProfileDraft

type ProfileDraft struct {
	Name         string      `yaml:"name"`
	Repositories []RepoDraft `yaml:"repositories,omitempty"`
}

ProfileDraft is the minimal structure written to a new profile file.

type RecentEntry

type RecentEntry struct {
	Command    string    `json:"command"`
	Status     string    `json:"status"`
	ExitCode   int       `json:"exitCode"`
	StartedAt  time.Time `json:"startedAt"`
	DurationMs int64     `json:"durationMs,omitempty"`
}

RecentEntry records a single `raid <command>` invocation. The log is intentionally per-command (not per-task) — agents asking "what did the developer just run?" want a high-level history, not every Shell step.

Lifecycle: an entry is first written on command start with Status="running", then updated to Status="completed" once the command exits normally. If the process is killed (SIGINT/SIGTERM/SIGKILL), the running entry survives on disk and ReadRecent reports it as Status="interrupted".

func ReadRecent

func ReadRecent() []RecentEntry

ReadRecent returns the most-recent-first list of recorded command runs. Returns nil if the log file does not exist or cannot be parsed; callers should treat absence as "no history yet".

Any entry still in the on-disk "running" state is rewritten to RecentStatusInterrupted before returning, so callers always see a terminal status. Concurrent in-flight invocations will be misreported, but that's an acceptable trade-off for a tool that is overwhelmingly run sequentially.

type Repo

type Repo struct {
	Name         string    `json:"name"`
	Path         string    `json:"path"`
	URL          string    `json:"url"`
	Branch       string    `json:"branch"`
	Environments []Env     `json:"environments"`
	Install      OnInstall `json:"install"`
	Commands     []Command `json:"commands"`
}

Repo represents a single repository entry in a profile.

func ExtractRepo

func ExtractRepo(path string) (Repo, error)

ExtractRepo reads and parses the raid.yaml from the given repository directory.

func GetRepos

func GetRepos() []Repo

GetRepos returns the repositories in the active profile.

func (Repo) IsLocalOnly

func (r Repo) IsLocalOnly() bool

IsLocalOnly reports whether the repo has no configured git remote. Local-only repos skip cloning; install tasks run directly against the existing path. The path must already exist on disk for install to work.

Whitespace-only URLs (e.g. `url: " "` from a stray edit) are treated as local-only — the trimmed value is what would be handed to `git clone`, and an empty string there produces a confusing error rather than a useful one.

func (Repo) IsZero

func (r Repo) IsZero() bool

IsZero reports whether the repo is uninitialized. URL is intentionally excluded — local-only repos with no git remote leave it empty.

type RepoDraft

type RepoDraft struct {
	Name   string `yaml:"name"`
	Path   string `yaml:"path"`
	URL    string `yaml:"url"`
	Branch string `yaml:"branch,omitempty"`
}

RepoDraft holds the fields collected for each repository during profile creation.

func CollectRepos

func CollectRepos(reader *bufio.Reader) []RepoDraft

CollectRepos runs an interactive prompt loop to collect repository details from reader.

type Severity

type Severity int

Severity indicates the importance of a doctor finding.

const (
	SeverityOK Severity = iota
	SeverityWarn
	SeverityError
)

type Task

type Task struct {
	TaskProps  `yaml:",inline"`
	Type       TaskType   `json:"type"`
	Concurrent bool       `json:"concurrent,omitempty"`
	Condition  *Condition `json:"condition,omitempty"`
	// Shell
	Cmd     string `json:"cmd,omitempty"`
	Literal bool   `json:"literal,omitempty"`
	Shell   string `json:"shell,omitempty"`
	// Script
	Path   string `json:"path,omitempty"`
	Runner string `json:"runner,omitempty"`
	// HTTP
	URL  string `json:"url,omitempty"`
	Dest string `json:"dest,omitempty"`
	// Wait
	Timeout string `json:"timeout,omitempty"`
	// Template
	Src string `json:"src,omitempty"`
	// Group
	Ref      string `json:"ref,omitempty"`
	Parallel bool   `json:"parallel,omitempty"`
	// Git
	Op     string `json:"op,omitempty"`
	Branch string `json:"branch,omitempty"`
	// Prompt / Confirm / Print
	Message string `json:"message,omitempty"`
	// Prompt / SetVar
	Var     string `json:"var,omitempty"`
	Default string `json:"default,omitempty"`
	// SetVar
	Value string `json:"value,omitempty"`
	// Print
	Color string `json:"color,omitempty"`
	// Retry
	Attempts int    `json:"attempts,omitempty"`
	Delay    string `json:"delay,omitempty"`
}

Task represents a single unit of work in a task sequence.

func (Task) Expand

func (t Task) Expand() Task

Expand returns a copy of the task with all string fields passed through environment variable expansion.

func (Task) IsZero

func (t Task) IsZero() bool

IsZero reports whether the task has no type set.

type TaskProps

type TaskProps struct {
	// Name is an optional human-readable label for the task, surfaced in logs
	// and agent-facing output. It does not affect execution.
	Name string `json:"name,omitempty" yaml:"name,omitempty"`
}

TaskProps holds properties shared by every task type. It is embedded into Task so the fields below appear at the top level of a task's YAML/JSON representation, and remain accessible via field promotion (e.g. `task.Name`).

type TaskType

type TaskType string

TaskType identifies which task executor to dispatch to.

const (
	Shell    TaskType = "shell"
	Script   TaskType = "script"
	HTTP     TaskType = "http"
	Wait     TaskType = "wait"
	Template TaskType = "template"
	Group    TaskType = "group"
	Git      TaskType = "git"
	Prompt   TaskType = "prompt"
	Confirm  TaskType = "confirm"
	Print    TaskType = "print"
	SetVar   TaskType = "set"
)

func (TaskType) ToLower

func (t TaskType) ToLower() TaskType

ToLower returns the task type normalized to lowercase for case-insensitive comparisons.

type Workspace

type Workspace struct {
	Profile  string             `json:"profile"`
	Env      string             `json:"env,omitempty"`
	Repos    []WorkspaceRepo    `json:"repos"`
	Commands []WorkspaceCommand `json:"commands"`
	Recent   []RecentEntry      `json:"recent,omitempty"`
	Vars     map[string]string  `json:"vars,omitempty"`
}

Workspace is the inline workspace state — what would be served via `resources/read` against a `raid://workspace/*` URI by an MCP server.

type WorkspaceCommand

type WorkspaceCommand struct {
	Name        string          `json:"name"`
	Description string          `json:"description,omitempty"`
	Steps       []WorkspaceStep `json:"steps,omitempty"`
}

WorkspaceCommand exposes a profile command's name and short description. Script bodies are intentionally excluded so the snapshot stays token-efficient and free of secrets. If any of the command's tasks have a `name` field set, they're surfaced in Steps as an outline of what the command does — also without script bodies.

type WorkspaceContext

type WorkspaceContext struct {
	Name        string          `json:"name"`
	Title       string          `json:"title,omitempty"`
	Version     string          `json:"version,omitempty"`
	WebsiteUrl  string          `json:"websiteUrl,omitempty"`
	GeneratedAt time.Time       `json:"generatedAt"`
	Tools       []WorkspaceTool `json:"tools,omitempty"`
	Workspace   Workspace       `json:"workspace"`
}

WorkspaceContext is a condensed snapshot of the active workspace, intended for agent / `raid context` consumption. It is derived from the loaded session context plus on-disk git state per repository.

The top-level Name / Version / WebsiteUrl / GeneratedAt fields identify the producer so an agent that picks up the snapshot in isolation can find the project on GitHub for further context and judge freshness. The Workspace sub-object holds the actual workspace state.

Field naming follows MCP (camelCase) so this shape can be lifted directly into the future `raid context serve` MCP server responses with no further translation.

func GetWorkspaceContext

func GetWorkspaceContext() WorkspaceContext

GetWorkspaceContext returns a snapshot of the active workspace. The session context must already be loaded (Load / ForceLoad). If no profile is active the returned WorkspaceContext has empty Workspace.Profile and empty Repos / Commands slices.

type WorkspaceRepo

type WorkspaceRepo struct {
	Name   string `json:"name"`
	Path   string `json:"path"`
	Cloned bool   `json:"cloned"`
	Branch string `json:"branch,omitempty"`
	Dirty  bool   `json:"dirty,omitempty"`
}

WorkspaceRepo describes a single repository in the active profile, as it exists on disk right now (not just as configured).

type WorkspaceStep

type WorkspaceStep struct {
	Name string `json:"name"`
}

WorkspaceStep describes one named task inside a command's task sequence. Only tasks with a populated TaskProps.Name appear here; unnamed tasks are omitted to keep the snapshot focused on user-meaningful labels.

type WorkspaceTool

type WorkspaceTool struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

WorkspaceTool describes a built-in `raid` subcommand the agent can invoke directly (e.g. `raid install`, `raid env`). User-defined commands live in Workspace.Commands and are not duplicated here.

Jump to

Keyboard shortcuts

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