Documentation
¶
Overview ¶
Package platform adapts brainjar's composed prompts to agent-specific config files on disk. Each adapter knows where its platform reads config from, how to merge brainjar-owned content with user-authored content, and how to install the hook + MCP plumbing that keeps the file current as state changes.
Adapters register themselves at init time via Register and are resolved by name through Get. Concrete adapters live in sibling subpackages (e.g. internal/platform/claude) and are bundled into the CLI by blank-importing each subpackage from internal/cli.
Index ¶
- Constants
- Variables
- func MapWithCatalog(cat ModelCatalog, prefs *models.ModelPrefs) map[string]any
- func Names() []string
- func Register(p Platform)
- func ResolveComposeModel(cat ModelCatalog, resp *models.ComposeResponse)
- type Capabilities
- type HookScope
- type HookStatus
- type MCPStatus
- type ModelCatalog
- type ModelInfo
- type Platform
- type ScopeSet
- type SkillEmit
- type SkillEmitResult
- type SkillFile
- type SkillOnDisk
- type SkillScope
- type SkillScopeResult
- type SkillsStatus
- type SliceCatalog
Constants ¶
const DefaultName = "claude"
DefaultName is the platform used when a workspace is initialized without an explicit choice.
Variables ¶
var ErrUnsupportedByPlatform = errors.New("platform: operation not supported")
ErrUnsupportedByPlatform signals that an operation is not implemented by the active adapter (e.g. Hooks on a platform with no hook surface). The CLI maps it to a usage error (exit 2).
var ErrUnsupportedScope = errors.New("platform: scope not supported")
ErrUnsupportedScope signals that an operation is implemented but the requested scope isn't (e.g. HookScopeLocal on a platform that has no gitignored-override file). Like ErrUnsupportedByPlatform, this is a usage error, not a system error.
Functions ¶
func MapWithCatalog ¶ added in v0.6.0
func MapWithCatalog(cat ModelCatalog, prefs *models.ModelPrefs) map[string]any
MapWithCatalog is the canonical MapModelPrefs body. Adapters delegate to it. When prefs.Model resolves through cat, the canonical ID is emitted; unknown names pass through verbatim so the runtime sees what the user wrote (callers warn separately at brain save time).
func Names ¶
func Names() []string
Names returns the sorted slugs of every registered adapter. Used by CLI surfaces that need to list what's available.
func Register ¶
func Register(p Platform)
Register adds p to the registry under its Name(). Intended to be called from an adapter's init(). Panics on duplicate registration — duplicate adapter slugs are a programming error, not a runtime one.
func ResolveComposeModel ¶ added in v0.6.0
func ResolveComposeModel(cat ModelCatalog, resp *models.ComposeResponse)
ResolveComposeModel rewrites resp.ModelPrefs.Model to the canonical platform ID when cat recognizes the alias. Idempotent — a literal ID resolves to itself. Pass-through when cat is nil, prefs are nil, the model field is empty, or the name is unknown. Mutates resp in place so callers can chain naturally.
Used at the CLI/MCP compose boundary so a brain expressing an alias ("opus", "flagship") returns to subagent orchestrators with the runtime-canonical ID, not the alias.
Types ¶
type Capabilities ¶
type Capabilities struct {
Sync bool `json:"sync"`
Hooks bool `json:"hooks"`
MCP bool `json:"mcp"`
Spawn bool `json:"spawn"`
Skills bool `json:"skills"`
Scopes ScopeSet `json:"scopes"`
}
Capabilities enumerates what an adapter can do. The zero value means "supports nothing" — every adapter must implement Capabilities() and set the fields explicitly.
type HookScope ¶
type HookScope int
HookScope selects which settings file a feature-group operation (hooks or MCP) targets. The three scopes mirror the tiers every supported platform exposes:
- Project scope writes to the platform's committed per-repository settings (e.g. <projectRoot>/.claude/settings.json). Everyone who clones the repo inherits the entry.
- Local scope writes to the platform's per-repository override file that is conventionally gitignored (e.g. <projectRoot>/.claude/settings.local.json). The entry fires only for this checkout and is not shared with collaborators.
- User scope writes to the platform's global user settings (e.g. ~/.claude/settings.json), so the entry applies in every project.
func ParseHookScope ¶
ParseHookScope maps a CLI slug to a HookScope. Empty is treated as project to keep the default easy.
func (HookScope) NeedsProjectRoot ¶
NeedsProjectRoot reports whether the scope requires a project root. User scope is filesystem-rooted at the user's home and ignores the projectRoot argument; project and local scope both need one.
type HookStatus ¶
type HookStatus struct {
Installed bool `json:"installed"`
UpToDate bool `json:"up_to_date"`
Command string `json:"command,omitempty"`
}
HookStatus is the result of Platform.HooksStatus. Installed reports whether any brainjar-owned hook is present; UpToDate reports whether the installed command matches what InstallHooks would write now.
type MCPStatus ¶
type MCPStatus struct {
Installed bool `json:"installed"`
UpToDate bool `json:"up_to_date"`
Approved *bool `json:"approved,omitempty"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
}
MCPStatus is the result of Platform.MCPStatus. Installed reports whether brainjar is registered; UpToDate reports whether the stored command + args match what InstallMCP would write now. Approved is populated only for platforms that have a separate approval step (nil means "not applicable").
type ModelCatalog ¶ added in v0.6.0
type ModelCatalog interface {
// List returns every model the adapter knows about. Static adapters
// ignore ctx; a future dynamic-fetch adapter can use it.
List(ctx context.Context) ([]ModelInfo, error)
// Resolve maps a user-supplied name to a ModelInfo. The name may be
// an alias ("opus", "opus-4", "flagship") or a literal ID
// ("claude-opus-4-6"). Unknown names return (zero, false) — never
// an error. Callers warn but don't fail.
Resolve(name string) (ModelInfo, bool)
}
ModelCatalog is the model-knowledge surface on a Platform. Adapters with no model namespace (e.g. Cursor) ship an empty catalog whose List returns nothing and whose Resolve always returns (zero, false).
type ModelInfo ¶ added in v0.6.0
type ModelInfo struct {
ID string `json:"id"`
Aliases []string `json:"aliases,omitempty"`
Family string `json:"family,omitempty"`
}
ModelInfo describes one model in an adapter's catalog. ID is the canonical wire identifier the runtime expects. Aliases are short human-friendly handles, layered by version specificity:
- bare family name ("opus") → newest model in that family
- major version ("opus-4") → newest minor in that major
- specific version ("opus-4-6") → exactly this entry
Cross-platform handles ("flagship", "balanced", "fast") may appear as ordinary aliases on whichever entry the adapter advertises as such. They get no special treatment in resolution — they're just strings.
type Platform ¶
type Platform interface {
// Name returns the canonical slug used to look this adapter up in
// the registry and in user-facing config (e.g. "claude").
Name() string
// Capabilities reports which features and scopes the adapter
// supports. The CLI uses it to render precise error messages and
// to power `brainjar platform list`.
Capabilities() Capabilities
// Sync writes prompt into the platform's managed section inside
// projectRoot. Idempotent: if the managed section already matches,
// Sync makes no filesystem change. Content outside the managed
// section is preserved byte-for-byte.
Sync(ctx context.Context, prompt string, projectRoot string) error
// SyncUser writes prompt into the platform's user-scope managed
// section (e.g. ~/.claude/CLAUDE.md, $CODEX_HOME/AGENTS.md).
// Idempotent. Adapters whose Capabilities.Scopes.User is false
// return ErrUnsupportedByPlatform.
SyncUser(ctx context.Context, prompt string) error
// SyncRemove strips brainjar's managed section from the project's
// sync target, preserving content outside the markers. If the file
// becomes empty after removal, the file is deleted. Used when a
// project has no project-scope override — leaving stale brainjar
// content behind would be misleading.
SyncRemove(ctx context.Context, projectRoot string) error
// InstallHooks registers brainjar-owned hook entries in the
// platform's settings file at the scope requested. User-authored
// hooks and unrelated settings are preserved. Re-invoking with the
// same binary is a no-op; re-invoking after the binary path
// changes upserts. projectRoot is used when scope is
// HookScopeProject or HookScopeLocal and ignored when scope is
// HookScopeUser.
InstallHooks(ctx context.Context, scope HookScope, projectRoot string) error
// RemoveHooks strips only brainjar-owned entries from the
// platform's settings file at the requested scope. User hooks and
// other settings survive.
RemoveHooks(ctx context.Context, scope HookScope, projectRoot string) error
// HooksStatus reports whether brainjar hooks are installed at the
// requested scope and whether the installed command matches what
// InstallHooks would write now.
HooksStatus(ctx context.Context, scope HookScope, projectRoot string) (HookStatus, error)
// InstallMCP registers brainjar as an MCP server in the platform's
// config at the given scope. projectRoot is used when scope needs
// it; home is the resolved --home value baked into the registered
// command args so the server targets the right brainjar workspace.
InstallMCP(ctx context.Context, scope HookScope, projectRoot, home string) error
// RemoveMCP strips only brainjar-owned MCP entries at the given
// scope. Other entries are preserved.
RemoveMCP(ctx context.Context, scope HookScope, projectRoot string) error
// MCPStatus reports whether brainjar is registered at the given
// scope and whether the installed command + args match what
// InstallMCP would write now for the given home. Pass the same
// home that would be used for a re-install so UpToDate reflects
// real drift (e.g. the user changed --home since install).
MCPStatus(ctx context.Context, scope HookScope, projectRoot, home string) (MCPStatus, error)
// Spawn returns an *exec.Cmd ready to start the platform's agent
// binary with prompt injected as its session system prompt. The
// caller runs the Cmd, wires stdio, forwards signals, and
// propagates the exit code.
//
// Prompt injection is platform-specific (--append-system-prompt for
// Claude, --system-prompt for Codex, …). Adapters choose the flag,
// the binary name, and any mandatory positional args.
//
// extraArgs are appended after the platform's injection flag so the
// user can still pass agent-native flags (--model, --add-dir, …).
//
// Adapters that have no CLI injection surface return
// ErrUnsupportedByPlatform. The CLI maps that to exit 2 with a
// user-facing hint.
//
// Spawn does not touch the filesystem and does not preconfigure
// stdio — the caller wires Stdin/Stdout/Stderr. exec.LookPath runs
// when the caller Start()s the Cmd, so an absent binary surfaces
// as exec.ErrNotFound at that point.
Spawn(ctx context.Context, prompt string, extraArgs []string) (*exec.Cmd, error)
// EmitSkills writes each Skill in skills to the platform's skill
// directory at the applicable scope. Skills carry their own
// SkillScope (project vs user) — adapters route on it. Adapters
// always prune brainjar-owned skill dirs not present in skills;
// user-authored skill dirs (no .brainjar-managed marker) survive
// untouched.
//
// projectRoot is required when any skill in skills has
// SkillScopeProject. Adapters return a SkillEmitResult with
// per-scope wrote / unchanged / pruned slug lists so the CLI can
// report churn to the user.
//
// Adapters that return Capabilities.Skills == false return
// ErrUnsupportedByPlatform from EmitSkills and SkillsStatus. The
// CLI converts that to a one-line warning, not a hard failure —
// sync should still write CLAUDE.md / AGENTS.md.
EmitSkills(ctx context.Context, projectRoot string, skills []SkillEmit) (SkillEmitResult, error)
// SkillsStatus reports brainjar-owned skills currently on disk
// at each applicable scope. Used by `brainjar status --skills`
// and by integration tests.
SkillsStatus(ctx context.Context, projectRoot string) (SkillsStatus, error)
// Catalog returns this adapter's model catalog. Always non-nil — an
// adapter with no model namespace returns an empty catalog whose
// List is empty and whose Resolve always returns (zero, false).
Catalog() ModelCatalog
// MapModelPrefs translates a generic ModelPrefs into a params map
// the platform's runtime understands (e.g. Claude model IDs). The
// canonical implementation is MapWithCatalog(c.Catalog(), prefs).
MapModelPrefs(prefs *models.ModelPrefs) map[string]any
// ModelArgs renders ModelPrefs as argv elements ready to prepend to
// a Spawn invocation. Adapters whose runtime accepts model
// selection on the CLI (Claude, Codex) emit "--model <id>" and
// any related flags. Adapters where model selection is UI-bound
// (Cursor) return nil. Empty prefs always produce nil.
//
// Callers prepend the result to user-supplied extra args so an
// explicit `-- --model X` from the user wins over the brain's
// stored preference.
ModelArgs(prefs *models.ModelPrefs) []string
}
Platform is a filesystem adapter for one agent runtime (Claude, Codex, Cursor, …). Every method is local-only — no network, no daemon.
Methods are grouped by concern: identity, prompt sync, hook plumbing, MCP registration, and a model-preference translator. Feature groups follow a uniform Install / Remove / Status triad so the CLI surface stays consistent across adapters and scopes.
type ScopeSet ¶
type ScopeSet struct {
Project bool `json:"project"`
Local bool `json:"local"`
User bool `json:"user"`
}
ScopeSet records which scopes an adapter accepts. A given method may still refuse individual scopes (e.g. Cursor's Sync has no User path), but the ScopeSet is the union across the adapter's operations.
type SkillEmit ¶ added in v0.6.0
type SkillEmit struct {
Slug string
Description string
Body string
Triggers []string
Version int
Scope SkillScope
Files []SkillFile
// Raw, when non-empty, is the byte-for-byte SKILL.md content the
// adapter should write instead of rendering frontmatter+body.
// Brainjar's embedded guides set this so their on-disk bytes match
// the source committed to the repo. User-content skills leave it
// nil and let the adapter render through skillemit.Render.
Raw []byte
}
SkillEmit is the platform-agnostic carrier passed to EmitSkills. It mirrors models.Skill but stays free of the apps/store types so the platform package never imports apps.
Files is reserved for bundled-files skill emission (not yet populated). Today's callers always pass Files == nil; adapters must accept that without panicking.
type SkillEmitResult ¶ added in v0.6.0
type SkillEmitResult struct {
Project SkillScopeResult `json:"project"`
User SkillScopeResult `json:"user"`
}
SkillEmitResult reports per-scope churn from EmitSkills.
type SkillFile ¶ added in v0.6.0
SkillFile is a single bundled file shipped alongside a SKILL.md. Reserved for future bundled-files emission — never populated today.
type SkillOnDisk ¶ added in v0.6.0
type SkillOnDisk struct {
Slug string `json:"slug"`
Version int `json:"version"`
Path string `json:"path"`
}
SkillOnDisk identifies one brainjar-managed skill that survived a status walk: its slug, the version recorded in the marker / frontmatter, and the absolute path to its SKILL.md.
type SkillScope ¶ added in v0.6.0
type SkillScope string
SkillScope mirrors models.SkillScope at the platform layer so the platform package stays free of internal/models imports. Callers translate at the boundary.
const ( SkillScopeProject SkillScope = "project" SkillScopeUser SkillScope = "user" )
type SkillScopeResult ¶ added in v0.6.0
type SkillScopeResult struct {
Dir string `json:"dir,omitempty"`
Wrote []string `json:"wrote"`
Unchanged []string `json:"unchanged"`
Pruned []string `json:"pruned"`
}
SkillScopeResult captures one scope's emit outcome — same shape as guides.ScopeResult, deliberately mirrored so the CLI can summarize either with the same code path.
type SkillsStatus ¶ added in v0.6.0
type SkillsStatus struct {
Project []SkillOnDisk `json:"project"`
User []SkillOnDisk `json:"user"`
}
SkillsStatus is the result of Platform.SkillsStatus — the brainjar-owned skills currently on disk at each scope.
type SliceCatalog ¶ added in v0.6.0
type SliceCatalog struct {
Entries []ModelInfo
}
SliceCatalog is the standard ModelCatalog backed by a static slice. Adapters declare their entries once and SliceCatalog handles List and Resolve. Lookup is O(n) — fine for catalogs of this size (single digits per platform).
Resolution is unambiguous by construction when the alias-uniqueness invariants hold (no two entries share an alias, no alias collides with another entry's literal ID). The shared CatalogContract test enforces this so a release that forgets to demote an old entry's bare alias fails CI.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package adapterkit holds the helpers shared by every brainjar Platform adapter: marker constants, brainjar brand strings, atomic file / JSON I/O, hook-entry identity, and the managed-section merge algorithm.
|
Package adapterkit holds the helpers shared by every brainjar Platform adapter: marker constants, brainjar brand strings, atomic file / JSON I/O, hook-entry identity, and the managed-section merge algorithm. |
|
Package claude is the brainjar Platform adapter for Claude Code.
|
Package claude is the brainjar Platform adapter for Claude Code. |
|
Package codex is the brainjar Platform adapter for OpenAI Codex CLI.
|
Package codex is the brainjar Platform adapter for OpenAI Codex CLI. |
|
Package cursor is the brainjar Platform adapter for the Cursor IDE (cursor.com).
|
Package cursor is the brainjar Platform adapter for the Cursor IDE (cursor.com). |
|
Package skillemit owns the per-scope skill writer + pruner shared between the embedded guides emit step and every platform adapter that knows how to land workspace skills on disk (Claude today, more later).
|
Package skillemit owns the per-scope skill writer + pruner shared between the embedded guides emit step and every platform adapter that knows how to land workspace skills on disk (Claude today, more later). |
|
Package testutil provides shared test contracts that every platform adapter runs against.
|
Package testutil provides shared test contracts that every platform adapter runs against. |