Documentation
¶
Overview ¶
Package workspace provides workspace/project management.
Package workspace provides workspace discovery functionality.
Discovery helpers for pkg/workspace - see workspace.go for the main package doc. the web UI can offer to register them. It is deliberately separate from pkg/workspace to avoid coupling registration concerns with filesystem scanning or network calls.
Two source types are supported:
- Local filesystem: walk a directory, emit every .git repo
- GitHub: list the authenticated user's repositories via the REST API
Each helper returns a slice of Candidate with a consistent shape so the caller (handlers/workspaces.go) can serialize to JSON without additional shaping.
Package workspace provides workspace/project management.
Package workspace provides workspace/project management.
Package workspace provides workspace and project management for bc.
A workspace represents a project directory containing bc configuration and agent state in .bc/settings.json.
Basic Usage ¶
Find the current workspace:
ws, err := workspace.Find(".")
if err != nil {
log.Fatal("not in a bc workspace")
}
fmt.Println("Workspace:", ws.Name())
Initialize a new workspace:
ws, err := workspace.Init("/path/to/project")
if err != nil {
log.Fatal(err)
}
Load an existing workspace:
ws, err := workspace.Load("/path/to/project")
if err != nil {
log.Fatal(err)
}
Index ¶
- Constants
- Variables
- func BCHome() (string, error)
- func Clone(ctx context.Context, url, target, name string) (string, error)
- func CloneURLFromRepo(repo Repo, preferSSH bool) string
- func ComputeWorkspaceID(path string) string
- func ConfigPath(rootDir string) string
- func DaemonAddrPath() (string, error)
- func DaemonLogPath() (string, error)
- func DaemonPidPath() (string, error)
- func DataDir(id string) (string, error)
- func DeleteGithubToken() error
- func DiscoverAndRegister(opts DiscoverOptions) (int, error)
- func EnsureBCHome() error
- func EnsureGlobalDir() (string, error)
- func FormatRoleFile(role *Role) (string, error)
- func GithubConnected() bool
- func GithubTokenPath() (string, error)
- func GlobalCostsDB() (string, error)
- func GlobalDir() string
- func GlobalMCPConfig() (string, error)
- func GlobalSecretsVault() (string, error)
- func GlobalStateDir(rootDir string) (string, error)
- func GlobalTemplatesDir() (string, error)
- func GlobalToolsConfig() (string, error)
- func IsWorkspace(dir string) bool
- func NormalizeNickname(nickname string) (string, error)
- func NormalizeRoleName(name string) string
- func ParseHTMLURL(raw string) (owner, name string)
- func ReadGithubToken() (string, error)
- func RegistryPath() string
- func SetGithubAPIBase(base string) func()
- func UserRCConfigPath() string
- func UserRCExists() bool
- func ValidateGithubToken(ctx context.Context, token string) (string, error)
- func ValidateNickname(nickname string) error
- func WriteGithubToken(token string) error
- type AliasConflictError
- type Candidate
- type Config
- func (c *Config) FillDefaults()
- func (c *Config) GetDefaultProvider() string
- func (c *Config) GetPreferredTool(rc *UserRCConfig) string
- func (c *Config) GetProvider(name string) *ProviderConfig
- func (c *Config) HasProviderDefined(name string) bool
- func (c *Config) ListProviders() []string
- func (c *Config) MergeWithUserRC(rc *UserRCConfig)
- func (c *Config) Save(path string) error
- func (c *Config) Validate() error
- type CronConfig
- type DiscordGatewayConfig
- type DiscoverOptions
- type DiscoveredWorkspace
- type DockerRuntimeConfig
- type GatewaysConfig
- type LogsConfig
- type ProviderConfig
- type ProvidersConfig
- type Registry
- func (r *Registry) Find(path string) *RegistryEntry
- func (r *Registry) FindByAlias(alias string) *RegistryEntry
- func (r *Registry) FindByID(id string) *RegistryEntry
- func (r *Registry) FindByNameOrAlias(identifier string) *RegistryEntry
- func (r *Registry) GetActive() *RegistryEntry
- func (r *Registry) List() []RegistryEntry
- func (r *Registry) Migrate() bool
- func (r *Registry) Prune() int
- func (r *Registry) PruneStalePaths() int
- func (r *Registry) Register(path, name string)
- func (r *Registry) RegisterWithAlias(path, name, alias string) error
- func (r *Registry) Resolve(identifier string) *RegistryEntry
- func (r *Registry) Save() error
- func (r *Registry) SetActive(identifier string) error
- func (r *Registry) SetAlias(path, alias string) error
- func (r *Registry) Touch(identifier string)
- func (r *Registry) Unregister(path string)
- type RegistryEntry
- type Repo
- type ResolvedRole
- type Role
- type RoleManager
- func (rm *RoleManager) AddMCPServer(roleName, server string) error
- func (rm *RoleManager) DeleteRole(name string) error
- func (rm *RoleManager) EnsureDefaultRoles() ([]string, error)
- func (rm *RoleManager) EnsureDefaultRoot() (bool, error)
- func (rm *RoleManager) EnsureRolesDir() error
- func (rm *RoleManager) GetMCPServers(roleName string) ([]string, error)
- func (rm *RoleManager) GetRole(name string) (*Role, bool)
- func (rm *RoleManager) HasRole(name string) bool
- func (rm *RoleManager) LoadAllRoles() (map[string]*Role, error)
- func (rm *RoleManager) LoadRole(name string) (*Role, error)
- func (rm *RoleManager) RemoveMCPServer(roleName, server string) error
- func (rm *RoleManager) ResolveRole(name string) (*ResolvedRole, error)
- func (rm *RoleManager) RolesDir() string
- func (rm *RoleManager) SetMCPServers(roleName string, servers []string) error
- func (rm *RoleManager) Store() *RoleStore
- func (rm *RoleManager) WriteRole(role *Role) error
- type RoleMetadata
- type RoleStore
- func (s *RoleStore) Close() error
- func (s *RoleStore) Delete(name string) error
- func (s *RoleStore) Has(name string) bool
- func (s *RoleStore) InitSchema() error
- func (s *RoleStore) Load(name string) (*Role, error)
- func (s *RoleStore) LoadAll() (map[string]*Role, error)
- func (s *RoleStore) MigrateDefaults() error
- func (s *RoleStore) MigrateFromFiles(rolesDir string) (int, error)
- func (s *RoleStore) Save(role *Role) error
- type RuntimeConfig
- type SQLiteStorageConfig
- type ScanOptions
- type ServerConfig
- type SlackGatewayConfig
- type StorageConfig
- type TelegramGatewayConfig
- type TimescaleStorageConfig
- type TmuxRuntimeConfig
- type UIConfig
- type UserConfig
- type UserRCConfig
- type UserRCDefaultsConfig
- type UserRCToolsConfig
- type UserRCUserConfig
- type Workspace
- func (w *Workspace) AgentsDir() string
- func (w *Workspace) ChannelsDir() string
- func (w *Workspace) DefaultProvider() string
- func (w *Workspace) DefaultProviderCommand() string
- func (w *Workspace) EnsureDirs() error
- func (w *Workspace) GetRole(name string) (*Role, error)
- func (w *Workspace) GetRolePrompt(name string) string
- func (w *Workspace) LogsDir() string
- func (w *Workspace) Name() string
- func (w *Workspace) RolesDir() string
- func (w *Workspace) Save() error
- func (w *Workspace) SettingsFile() string
- func (w *Workspace) StateDir() string
- type WorkspaceNotFoundError
Constants ¶
const ( // PreferencesFileName is the canonical workspace preferences filename (M11c+). PreferencesFileName = "preferences.json" // LegacySettingsFileName is the pre-M11c filename, still read for // backward compatibility. LegacySettingsFileName = "settings.json" )
Preferences / settings filename constants.
Before M11c: every workspace stored its config at <StateDir>/settings.json. From M11c onward the canonical filename is preferences.json; the legacy file is read as a fallback and promoted on first write.
const ConfigVersion = 2
ConfigVersion is the current config schema version.
const CurrentRegistryVersion = 2
CurrentRegistryVersion is the current registry schema version. v1 = legacy (no IDs). v2 = IDs + github_* + last_used_at.
const DefaultBaseRole = `` /* 2540-byte string literal not displayed */
DefaultBaseRole is the foundational role all other roles inherit from. It provides the bc MCP server so every agent can communicate with the workspace.
const DefaultRootRole = `` /* 619-byte string literal not displayed */
DefaultRootRole returns the default content for root.md.
const NameMaxLength = 30
User name limits.
Variables ¶
var ( ErrInvalidVersion = errors.New("version must be 2") ErrMissingDefaultProvider = errors.New("providers.default is required") ErrDefaultProviderNotFound = errors.New("providers.default references undefined provider") ErrInvalidTheme = errors.New("ui.theme must be one of: dark, light, matrix, synthwave, high-contrast") ErrInvalidThemeMode = errors.New("ui.mode must be one of: auto, dark, light") ErrNameTooLong = errors.New("user.name is too long") )
Validation errors.
var DefaultRoles = map[string]string{
"feature-dev": `---
name: feature-dev
description: Feature developer — implements tasks in isolated worktrees
parent_roles:
- base
mcp_servers:
- github
secrets:
- GITHUB_PERSONAL_ACCESS_TOKEN
prompt_start: |
Check #engineering channel for any new assignments or updates.
---
# Feature Developer
You implement features, fix bugs, and write tests in an isolated git worktree.
## Workflow
1. Read the assigned issue, create a feature branch (feat/<issue>-<slug>)
2. Implement with tests, run make check
3. Open a PR and post to #merge when ready for review
`,
"go-reviewer": `---
name: go-reviewer
description: Go code quality reviewer
parent_roles:
- feature-dev
---
# Go Reviewer
Review Go pull requests for correctness, security, and test coverage.
Use the github MCP to fetch PR diffs and leave inline review comments.
Block merges on security issues or broken tests; suggest rather than block on style.
`,
"web-reviewer": `---
name: web-reviewer
description: Web/TypeScript UI reviewer
parent_roles:
- feature-dev
---
# Web Reviewer
Review React/TypeScript pull requests for correctness and component patterns.
Use the github MCP to fetch PR diffs and leave inline review comments.
Hooks cannot be tested without a DOM in Ink — test exported helpers instead.
`,
"designer": `---
name: designer
description: Design system and Web UI specialist
parent_roles:
- feature-dev
---
# Designer
Maintain the design token system, build React/Ink TUI components, and implement
CSS/Tailwind changes for the web dashboard. Accessibility is non-negotiable.
`,
"product-manager": `---
name: product-manager
description: Product coordination and epic management
parent_roles:
- base
mcp_servers:
- github
secrets:
- GITHUB_PERSONAL_ACCESS_TOKEN
---
# Product Manager
Break down goals into epics and issues, assign work to agents, and review
completed work against acceptance criteria. Use GitHub issues for all tracking.
`,
"docs": `---
name: docs
description: Documentation writer
parent_roles:
- feature-dev
---
# Documentation Writer
Write and maintain README, CONTRIBUTING, API docs, and CLAUDE.md.
Update docs in the same PR as the feature. Use concrete examples.
`,
}
DefaultRoles contains the built-in role definitions for the bc agent team. These are written to .bc/roles/ if the files don't already exist.
var ErrGithubNotAuthenticated = errors.New("github not authenticated")
ErrGithubNotAuthenticated indicates no token is configured or it was rejected by the API.
var ValidModes = []string{"auto", "dark", "light"}
Valid theme modes.
var ValidThemes = []string{"dark", "light", "matrix", "synthwave", "high-contrast"}
Valid theme names.
Functions ¶
func BCHome ¶
BCHome returns the global bc home directory (~/.bc). Respects BC_HOME env var override.
func Clone ¶ added in v0.2.0
Clone runs `git clone <url> <target>/<name>` to materialize a repository. Returns the absolute path of the new checkout on success.
func CloneURLFromRepo ¶ added in v0.2.0
CloneURLFromRepo picks the best clone URL for a Repo: SSH if a credential helper appears configured, otherwise HTTPS. Returns the CloneURL field if no URL was provided.
func ComputeWorkspaceID ¶ added in v0.2.0
ComputeWorkspaceID returns the stable 12-char hex ID for an absolute path. It is the first registryIDLength hex chars of sha256(absPath). Empty path returns an empty string.
func ConfigPath ¶
ConfigPath returns the standard config file path for a workspace root. Checks the global state dir first (preferences.json then settings.json), then the legacy <rootDir>/.bc/ directory. When no file exists anywhere yet, returns the canonical preferences.json path under the global dir so callers writing a fresh config land in the right place.
func DaemonAddrPath ¶ added in v0.2.0
DaemonAddrPath returns the path to the user-global bcd address file (~/.bc/daemon.addr). `bc up` writes the currently-listening address (scheme + host:port, e.g. "http://127.0.0.1:8080") so the CLI and agents can locate the daemon without requiring BC_DAEMON_ADDR to be set when the daemon runs on a non-default port.
func DaemonLogPath ¶ added in v0.2.0
DaemonLogPath returns the path to the user-global bcd log file (~/.bc/daemon.log). Same rationale as DaemonPidPath: one bcd, one log.
func DaemonPidPath ¶ added in v0.2.0
DaemonPidPath returns the path to the user-global bcd pid file (~/.bc/daemon.pid). The bcd daemon is user-scoped — a single process serves every workspace — so its pid lives outside any per-workspace directory.
func DataDir ¶ added in v0.2.0
DataDir returns the per-workspace runtime directory for a given workspace ID — the M11 replacement for the legacy <project>/.bc/ sidecar.
Returns ~/.bc/workspaces/<id>/ (respecting BC_HOME). Pass the ID from ComputeWorkspaceID(absRootDir) or from a RegistryEntry.
This path holds every piece of runtime state for the workspace: preferences.json, state.db, cron.db, agents/, logs/ — nothing lives under the project directory anymore.
func DeleteGithubToken ¶ added in v0.2.0
func DeleteGithubToken() error
DeleteGithubToken removes the token file, ignoring "not found".
func DiscoverAndRegister ¶
func DiscoverAndRegister(opts DiscoverOptions) (int, error)
DiscoverAndRegister discovers workspaces and adds new ones to the registry. Returns the number of newly registered workspaces.
func EnsureBCHome ¶
func EnsureBCHome() error
EnsureBCHome creates the global ~/.bc directory structure if it doesn't exist.
func EnsureGlobalDir ¶ added in v0.2.0
EnsureGlobalDir makes sure ~/.bc/ exists with 0750 permissions. It is idempotent and safe to call from any process path that needs to write a global asset. Returns the resolved BCHome path for convenience.
func FormatRoleFile ¶
FormatRoleFile formats a role as a markdown file with YAML frontmatter.
func GithubConnected ¶ added in v0.2.0
func GithubConnected() bool
GithubConnected returns true if a non-empty token is on disk.
func GithubTokenPath ¶ added in v0.2.0
GithubTokenPath returns the filesystem location of the stored token. The directory is created as 0700 on first write.
func GlobalCostsDB ¶ added in v0.2.0
GlobalCostsDB returns the path to the user-global cost ledger (~/.bc/costs.db). Every cost record is tagged with a workspace_id so cross-workspace analytics work without data duplication.
func GlobalDir ¶
func GlobalDir() string
GlobalDir returns the path to ~/.bc/. Honors BC_HOME so test isolation (or power-user overrides) route every registry read/write through the same sandbox — previously this ignored BC_HOME and always read the host's real registry, which let tests corrupt production state.
func GlobalMCPConfig ¶ added in v0.2.0
GlobalMCPConfig returns the path to the user-global MCP trust config (~/.bc/mcps.json). Servers listed here are available to every workspace unless the workspace overrides them locally.
func GlobalSecretsVault ¶ added in v0.2.0
GlobalSecretsVault returns the path to the user-global secrets vault (~/.bc/secrets.vault). This is a SQLite database holding the user's encrypted key/value secrets shared across workspaces.
func GlobalStateDir ¶
GlobalStateDir returns the state directory for a workspace at ~/.bc/workspaces/<workspace-id>/, where the ID is the 12-char sha256 prefix produced by ComputeWorkspaceID. Matches RegistryEntry.DataDir so the migration and the registry agree on a single path. Respects BC_STATE_DIR env var override.
func GlobalTemplatesDir ¶ added in v0.2.0
GlobalTemplatesDir returns the user-global templates directory (~/.bc/templates/). Templates here apply across all workspaces; each workspace may override a template by placing a file with the same name under <ws>/.bc/templates/.
func GlobalToolsConfig ¶ added in v0.2.0
GlobalToolsConfig returns the path to the user-global CLI tools registry (~/.bc/tools.json). Tools here describe machine-level dependencies (claude, bun, docker helpers, etc.) — there is no per-workspace override for this file.
func IsWorkspace ¶
IsWorkspace checks if a directory is a workspace. Checks legacy .bc/ directory and global state dir (~/.bc/workspaces/<id>/).
func NormalizeNickname ¶
NormalizeNickname ensures a nickname has the @ prefix and is valid.
func NormalizeRoleName ¶
NormalizeRoleName canonicalises a role name by replacing underscores with hyphens and lower-casing, preventing duplicates like "product_manager" vs "product-manager".
func ParseHTMLURL ¶ added in v0.2.0
ParseHTMLURL extracts owner/name from a github.com HTTPS URL for display purposes. Returns ("", "") on non-github hostnames.
func ReadGithubToken ¶ added in v0.2.0
ReadGithubToken returns the stored PAT, or "" if none. Any read error other than ENOENT is returned so the caller can distinguish "not set" from "read failed".
func RegistryPath ¶
func RegistryPath() string
RegistryPath returns the path to ~/.bc/workspaces.json (BC_HOME-aware via GlobalDir).
func SetGithubAPIBase ¶ added in v0.2.0
func SetGithubAPIBase(base string) func()
SetGithubAPIBase is a test hook for retargeting the API base (e.g. at an httptest.Server). Not intended for production use.
func UserRCConfigPath ¶
func UserRCConfigPath() string
UserRCConfigPath returns the path to the user's .bcrc file. Default: ~/.bcrc
func ValidateGithubToken ¶ added in v0.2.0
ValidateGithubToken performs a lightweight /user call to verify the token works. Returns the GitHub login on success.
func ValidateNickname ¶
ValidateNickname validates a nickname and returns an error if invalid.
func WriteGithubToken ¶ added in v0.2.0
WriteGithubToken persists the PAT to ~/.bc/github-token with 0600 perms. Empty token clears the file.
Types ¶
type AliasConflictError ¶
AliasConflictError indicates the alias is already in use.
func (*AliasConflictError) Error ¶
func (e *AliasConflictError) Error() string
type Candidate ¶ added in v0.2.0
type Candidate struct {
Path string `json:"path"`
Name string `json:"name"`
GitRemote string `json:"git_remote,omitempty"`
GithubURL string `json:"github_url,omitempty"`
HasBC bool `json:"has_bc"`
AlreadyRegistered bool `json:"already_registered"`
}
Candidate describes one discovered repository/workspace.
func ScanLocal ¶ added in v0.2.0
func ScanLocal(ctx context.Context, opts ScanOptions) ([]Candidate, error)
ScanLocal walks opts.Root looking for Git repositories (directories containing a .git entry, file or dir) and returns one Candidate per hit. It is safe to call with an unresolvable or inaccessible Root — the error is returned without partial results.
type Config ¶
type Config struct {
User UserConfig `json:"user"`
Providers ProvidersConfig `json:"providers"`
Gateways GatewaysConfig `json:"gateways"`
Runtime RuntimeConfig `json:"runtime"`
Storage StorageConfig `json:"storage"`
Server ServerConfig `json:"server"`
Cron CronConfig `json:"cron"`
Logs LogsConfig `json:"logs"`
UI UIConfig `json:"ui"`
Version int `json:"version"`
}
Config represents the JSON-based workspace configuration for bc.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns sensible defaults for a new workspace.
func LoadConfig ¶
LoadConfig reads and parses a JSON config file.
If path is a directory, LoadConfig treats it as a state dir and looks for preferences.json first (M11c+), falling back to the legacy settings.json. When only the legacy file is present it is read in place; the caller is responsible for writing the promoted file via Save() or LoadConfig's higher-level wrappers.
If path points at a file, it is read directly.
func ParseConfig ¶
ParseConfig parses JSON data into a Config.
func (*Config) FillDefaults ¶
func (c *Config) FillDefaults()
FillDefaults fills zero-valued fields with defaults. Called after ParseConfig to handle configs from older versions.
func (*Config) GetDefaultProvider ¶
GetDefaultProvider returns the default AI provider name.
func (*Config) GetPreferredTool ¶
func (c *Config) GetPreferredTool(rc *UserRCConfig) string
GetPreferredTool returns the first available preferred tool from .bcrc.
func (*Config) GetProvider ¶
func (c *Config) GetProvider(name string) *ProviderConfig
GetProvider returns an AI provider's configuration by name.
func (*Config) HasProviderDefined ¶
HasProviderDefined checks if an AI provider is configured.
func (*Config) ListProviders ¶
ListProviders returns the names of all configured AI providers.
func (*Config) MergeWithUserRC ¶
func (c *Config) MergeWithUserRC(rc *UserRCConfig)
MergeWithUserRC merges user-level defaults from .bcrc into a workspace config.
type CronConfig ¶
type CronConfig struct {
PollIntervalSeconds int `json:"poll_interval_seconds"`
JobTimeoutSeconds int `json:"job_timeout_seconds"`
}
CronConfig configures the cron/job scheduler.
type DiscordGatewayConfig ¶
type DiscordGatewayConfig struct {
BotToken string `json:"bot_token"`
Enabled bool `json:"enabled"`
}
DiscordGatewayConfig configures the Discord gateway adapter.
type DiscoverOptions ¶
type DiscoverOptions struct {
// ScanPaths additional paths to scan for workspaces
ScanPaths []string
// MaxDepth maximum directory depth to scan (default 3)
MaxDepth int
// IncludeCached includes workspaces from the global registry (~/.bc/workspaces.json)
IncludeCached bool
// ScanHome scans ~/Projects and ~/Developer directories
ScanHome bool
}
DiscoverOptions configures workspace discovery.
func DefaultDiscoverOptions ¶
func DefaultDiscoverOptions() DiscoverOptions
DefaultDiscoverOptions returns sensible defaults for discovery.
type DiscoveredWorkspace ¶
type DiscoveredWorkspace struct {
Path string // Absolute path to workspace root
Name string // Workspace name (from config or directory name)
IsV2 bool // True if v2 (TOML) workspace
FromCache bool // True if found in registry (not disk scan)
}
DiscoveredWorkspace represents a workspace found during discovery.
func Discover ¶
func Discover(opts DiscoverOptions) ([]DiscoveredWorkspace, error)
Discover finds all bc workspaces on the machine. It checks the global registry and optionally scans common directories.
type DockerRuntimeConfig ¶
type DockerRuntimeConfig struct {
ExtraMounts []string `json:"extra_mounts"`
Image string `json:"image"`
Network string `json:"network"`
DockerSocketPath string `json:"docker_socket_path"`
MemoryMB int64 `json:"memory_mb"`
CPUs float64 `json:"cpus"`
}
DockerRuntimeConfig configures Docker container settings for agents.
type GatewaysConfig ¶
type GatewaysConfig struct {
// Telegram is the single default Telegram bot (key "telegram").
// Deprecated: prefer Telegrams map for multi-bot setups.
Telegram *TelegramGatewayConfig `json:"-"`
Discord *DiscordGatewayConfig `json:"discord,omitempty"`
Slack *SlackGatewayConfig `json:"slack,omitempty"`
// Telegrams holds zero or more Telegram bots keyed by label.
// A plain "telegram" key is stored under label "".
Telegrams map[string]*TelegramGatewayConfig `json:"-"`
}
GatewaysConfig configures external messaging platform integrations.
JSON keys follow a "platform" or "platform:label" convention. Plain "telegram" is a single Telegram bot (backward compat). Keys like "telegram:trade_research" register additional bots — parsed into the Telegrams map keyed by label.
func (GatewaysConfig) MarshalJSON ¶ added in v0.2.0
func (g GatewaysConfig) MarshalJSON() ([]byte, error)
MarshalJSON serializes the gateway config, emitting "telegram:label" keys for each entry in Telegrams.
func (*GatewaysConfig) UnmarshalJSON ¶ added in v0.2.0
func (g *GatewaysConfig) UnmarshalJSON(data []byte) error
UnmarshalJSON parses gateway config, routing "telegram:*" keys into the Telegrams map and keeping Discord/Slack on their typed fields.
type LogsConfig ¶
LogsConfig configures persistent session log streaming.
type ProviderConfig ¶
type ProviderConfig struct {
Command string `json:"command"`
}
ProviderConfig defines an AI provider's configuration.
type ProvidersConfig ¶
type ProvidersConfig struct {
Default string `json:"default"`
Providers map[string]ProviderConfig `json:"providers,omitempty"`
}
ProvidersConfig configures AI agent providers.
type Registry ¶
type Registry struct {
Active string `json:"active,omitempty"` // Path or alias of active workspace
Workspaces []RegistryEntry `json:"workspaces"`
Version int `json:"version,omitempty"`
// contains filtered or unexported fields
}
Registry manages the global list of workspaces at ~/.bc/workspaces.json. Issue #1218: Multi-workspace orchestration support.
func LoadRegistry ¶
LoadRegistry loads the global workspace registry. Returns an empty registry if the file doesn't exist. Always runs Migrate() so callers see a v2 in-memory shape.
func (*Registry) Find ¶
func (r *Registry) Find(path string) *RegistryEntry
Find returns the entry for a given path, or nil if not found.
func (*Registry) FindByAlias ¶
func (r *Registry) FindByAlias(alias string) *RegistryEntry
FindByAlias returns the entry for a given alias, or nil if not found. Issue #1218: Multi-workspace orchestration.
func (*Registry) FindByID ¶ added in v0.2.0
func (r *Registry) FindByID(id string) *RegistryEntry
FindByID returns the entry for a given workspace ID (v2+), or nil if not found. The ID is the 12-char hex hash of the absolute path, independent of user-editable fields like name or alias.
func (*Registry) FindByNameOrAlias ¶
func (r *Registry) FindByNameOrAlias(identifier string) *RegistryEntry
FindByNameOrAlias returns the entry matching name, alias, or path. Tries alias first, then name, then path.
func (*Registry) GetActive ¶
func (r *Registry) GetActive() *RegistryEntry
GetActive returns the active workspace entry, or nil if none set.
func (*Registry) List ¶
func (r *Registry) List() []RegistryEntry
List returns all registered workspaces.
func (*Registry) Migrate ¶ added in v0.2.0
Migrate bumps the registry to the current schema version.
- v1 → v2: populate ID on every entry (sha256(path)[:12]) and ensure LastUsedAt is set (falls back to LastAccessed or CreatedAt).
Returns true if any change was made. Safe to call multiple times.
func (*Registry) Prune ¶
Prune removes entries where the workspace no longer exists on disk. Checks for .bc/ dir in project root OR state dir in ~/.bc/workspaces/<id>/.
func (*Registry) PruneStalePaths ¶ added in v0.2.0
PruneStalePaths removes entries whose project directory no longer exists on disk — e.g. test tmpdirs that the caller created via t.TempDir() and that have since been cleaned up. Unlike Prune(), this does not look at .bc/ or the global state dir; it only checks that the root Path itself is still a directory. Returns the count removed.
Callers should invoke this before performing batch operations against the registry (like the M11 runtime migration) to avoid acting on phantom entries.
func (*Registry) RegisterWithAlias ¶
RegisterWithAlias adds or updates a workspace with an optional alias. Issue #1218: Multi-workspace orchestration.
func (*Registry) Resolve ¶ added in v0.2.0
func (r *Registry) Resolve(identifier string) *RegistryEntry
Resolve returns the entry matching an id, alias, name, or path. Returns nil if none match.
func (*Registry) Save ¶
Save persists the registry to disk atomically: write to a temp file then rename over the destination. This prevents corruption if the process is killed mid-write.
func (*Registry) Touch ¶
Touch updates the last-accessed time for a workspace. Accepts a path, id, alias, or name — for backwards compatibility, the single argument was historically a path.
func (*Registry) Unregister ¶
Unregister removes a workspace from the registry.
type RegistryEntry ¶
type RegistryEntry struct {
CreatedAt time.Time `json:"created_at"`
LastAccessed time.Time `json:"last_accessed,omitempty"` // legacy — mirrors LastUsedAt for backwards compat
LastUsedAt time.Time `json:"last_used_at,omitempty"` // canonical timestamp (v2+)
ID string `json:"id,omitempty"` // stable 12-char hash of abs path (v2+)
Path string `json:"path"` // project root (pristine git repo)
DataDir string `json:"data_dir,omitempty"` // runtime state dir (~/.bc/workspaces/<id>/); M11+
Name string `json:"name"`
Alias string `json:"alias,omitempty"` // Short alias for quick access (#1218)
GithubURL string `json:"github_url,omitempty"` // Optional GitHub remote URL (v2+)
GithubFullName string `json:"github_full_name,omitempty"` // Optional GitHub owner/repo (v2+)
}
RegistryEntry represents a registered workspace.
func (*RegistryEntry) GetDataDir ¶ added in v0.2.0
func (e *RegistryEntry) GetDataDir() string
GetDataDir returns the per-workspace runtime directory. When the registry field is empty (older entries), falls back to computing it from the workspace ID via DataDir(id). Returns "" if neither is available.
type Repo ¶ added in v0.2.0
type Repo struct {
FullName string `json:"full_name"`
Name string `json:"name"`
CloneURL string `json:"clone_url"`
SSHURL string `json:"ssh_url"`
HTMLURL string `json:"html_url"`
DefaultBranch string `json:"default_branch"`
Description string `json:"description,omitempty"`
Private bool `json:"private"`
}
Repo mirrors the subset of a GitHub repository record we expose to the frontend.
type ResolvedRole ¶
type ResolvedRole struct {
Settings map[string]any // Merged settings (child overrides parent)
Rules map[string]string // Merged rule files (child overrides parent)
Agents map[string]string // Merged agent templates
Skills map[string]string // Merged skill files
Commands map[string]string // Merged command files
PromptStart string // Lifecycle: sent on agent start/restart
Name string // Role name
PromptStop string // Lifecycle: sent on agent stop
PromptDelete string // Lifecycle: sent on agent delete
PromptCreate string // Lifecycle: sent on agent create
Prompt string // Merged prompt body (child + parent)
Review string // REVIEW.md content
Plugins []string // Unioned plugins from all ancestors
Secrets []string // Unioned secret names from all ancestors
MCPServers []string // Unioned MCP servers from all ancestors
CLITools []string // Unioned CLI tools from all ancestors
Description string // Human-readable role description
}
ResolvedRole contains the fully resolved role after BFS inheritance merge.
type Role ¶
type Role struct {
FilePath string // Path to the role file
Prompt string // Markdown body after frontmatter
Metadata RoleMetadata // Parsed YAML frontmatter
}
Role represents a parsed role file with metadata and prompt content.
func ParseRoleFile ¶
ParseRoleFile parses a role file with YAML frontmatter and markdown body. The frontmatter is delimited by --- on its own lines.
func (*Role) Description ¶
Description returns a brief description for the role. Uses Metadata.Description if set, otherwise extracts from the first heading in Prompt.
type RoleManager ¶
type RoleManager struct {
// contains filtered or unexported fields
}
RoleManager handles role operations for a workspace. All role data is stored in SQL (SQLite or Postgres) via the RoleStore. The rolesDir field is retained only for migration from legacy file-based storage.
func NewRoleManager ¶
func NewRoleManager(stateDir string) *RoleManager
NewRoleManager creates a new role manager for the given workspace state directory. It opens a SQLite-backed RoleStore at stateDir/bc.db automatically and performs a best-effort migration of any .md files in the roles directory. If the store cannot be opened, operations that require the store will return errors.
func NewRoleManagerWithStore ¶
func NewRoleManagerWithStore(stateDir string, store *RoleStore) *RoleManager
NewRoleManagerWithStore creates a role manager backed by a SQLite store. The filesystem rolesDir is still used for migration and backward compatibility.
func (*RoleManager) AddMCPServer ¶
func (rm *RoleManager) AddMCPServer(roleName, server string) error
AddMCPServer adds an MCP server association to a role if not already present.
func (*RoleManager) DeleteRole ¶
func (rm *RoleManager) DeleteRole(name string) error
DeleteRole removes a role by name from the SQL store.
func (*RoleManager) EnsureDefaultRoles ¶
func (rm *RoleManager) EnsureDefaultRoles() ([]string, error)
EnsureDefaultRoles writes built-in default roles to the store if they don't already exist. Returns the names of any roles that were created.
func (*RoleManager) EnsureDefaultRoot ¶
func (rm *RoleManager) EnsureDefaultRoot() (bool, error)
EnsureDefaultRoot ensures the base and root roles exist in the store. Returns true if the root role was created, false if it already existed.
func (*RoleManager) EnsureRolesDir ¶
func (rm *RoleManager) EnsureRolesDir() error
EnsureRolesDir creates the roles directory if it doesn't exist.
func (*RoleManager) GetMCPServers ¶
func (rm *RoleManager) GetMCPServers(roleName string) ([]string, error)
GetMCPServers returns the MCP server associations for a role.
func (*RoleManager) GetRole ¶
func (rm *RoleManager) GetRole(name string) (*Role, bool)
GetRole returns a cached role by name.
func (*RoleManager) HasRole ¶
func (rm *RoleManager) HasRole(name string) bool
HasRole checks if a role exists (cached or in store).
func (*RoleManager) LoadAllRoles ¶
func (rm *RoleManager) LoadAllRoles() (map[string]*Role, error)
LoadAllRoles loads all roles from the SQL store.
func (*RoleManager) LoadRole ¶
func (rm *RoleManager) LoadRole(name string) (*Role, error)
LoadRole loads and parses a single role from the SQL store.
func (*RoleManager) RemoveMCPServer ¶
func (rm *RoleManager) RemoveMCPServer(roleName, server string) error
RemoveMCPServer removes an MCP server association from a role.
func (*RoleManager) ResolveRole ¶
func (rm *RoleManager) ResolveRole(name string) (*ResolvedRole, error)
ResolveRole loads a role directly from the store. No inheritance — each role is self-contained with all its own MCP servers, secrets, plugins, etc.
func (*RoleManager) RolesDir ¶
func (rm *RoleManager) RolesDir() string
RolesDir returns the roles directory path.
func (*RoleManager) SetMCPServers ¶
func (rm *RoleManager) SetMCPServers(roleName string, servers []string) error
SetMCPServers replaces the MCP server list for a role.
func (*RoleManager) Store ¶
func (rm *RoleManager) Store() *RoleStore
Store returns the underlying RoleStore, or nil if filesystem-only.
func (*RoleManager) WriteRole ¶
func (rm *RoleManager) WriteRole(role *Role) error
WriteRole writes a role to the SQL store.
type RoleMetadata ¶
type RoleMetadata struct {
Settings map[string]any `yaml:"settings,omitempty"` // Claude settings overrides (e.g., model, permissions)
Rules map[string]string `yaml:"rules,omitempty"` // Rule files written to .claude/rules/*.md
Agents map[string]string `yaml:"agents,omitempty"` // Agent templates written to .claude/agents/*.md
Skills map[string]string `yaml:"skills,omitempty"` // Skill files written to .claude/skills/*.md
Commands map[string]string `yaml:"commands,omitempty"` // Command files written to .claude/commands/*.md
PromptStop string `yaml:"prompt_stop,omitempty"` // Sent when agent is stopped
PromptCreate string `yaml:"prompt_create,omitempty"` // Sent when agent is created
PromptStart string `yaml:"prompt_start,omitempty"` // Sent when agent is started/restarted
Name string `yaml:"name"` // Role name (e.g., "engineer", "manager")
PromptDelete string `yaml:"prompt_delete,omitempty"` // Sent when agent is deleted
Description string `yaml:"description,omitempty"` // Human-readable role description
Review string `yaml:"review,omitempty"` // REVIEW.md content for the role
Plugins []string `yaml:"plugins,omitempty"` // Claude Code plugins to install on agent start
Secrets []string `yaml:"secrets,omitempty"` // Secret names needed by MCP env vars
MCPServers []string `yaml:"mcp_servers,omitempty"` // MCP servers available to this role
ParentRoles []string `yaml:"parent_roles,omitempty"` // Roles to inherit from (capabilities, prompts)
CLITools []string `yaml:"cli_tools,omitempty"` // CLI tools expected in agent PATH (e.g., gh, aws, wrangler)
}
RoleMetadata contains the parsed frontmatter from a role file.
type RoleStore ¶
type RoleStore struct {
// contains filtered or unexported fields
}
RoleStore provides SQL-backed persistence for roles. It supports both SQLite and Postgres via the driver field.
func NewRoleStore ¶
NewRoleStore opens (or creates) a SQLite database at dbPath and ensures the roles table exists. The caller must call Close when done.
func NewRoleStoreFromDB ¶
NewRoleStoreFromDB creates a RoleStore from an existing *sql.DB connection. The caller retains ownership of the connection — Close on this store is a no-op.
func (*RoleStore) InitSchema ¶
InitSchema creates the roles table if it does not exist. Uses driver-appropriate column types.
func (*RoleStore) MigrateDefaults ¶
MigrateDefaults inserts the built-in default roles (base, root, and DefaultRoles map) into the database if they don't already exist.
func (*RoleStore) MigrateFromFiles ¶
MigrateFromFiles scans a roles directory for .md files and inserts them into the database. Existing roles in the DB are not overwritten. The source files are NOT deleted (kept as backup).
type RuntimeConfig ¶
type RuntimeConfig struct {
K8s json.RawMessage `json:"k8s,omitempty"` // future
Default string `json:"default"` // "tmux" or "docker"
Docker DockerRuntimeConfig `json:"docker"`
Tmux TmuxRuntimeConfig `json:"tmux"`
}
RuntimeConfig configures the agent session backend.
type SQLiteStorageConfig ¶
type SQLiteStorageConfig struct {
Path string `json:"path"`
}
SQLiteStorageConfig configures SQLite storage.
type ScanOptions ¶ added in v0.2.0
type ScanOptions struct {
// SkipDirs additional directory basenames to exclude, merged with the
// default SkipDirs list.
SkipDirs map[string]struct{}
// Root is the absolute directory to scan. Required.
Root string
// Depth bounds recursion relative to Root. Values <=0 use defaultDepth.
// Values > maxDepth are clamped.
Depth int
}
ScanOptions controls local discovery behavior.
type ServerConfig ¶
type ServerConfig struct {
Host string `json:"host"`
CORSOrigin string `json:"cors_origin"`
Port int `json:"port"`
}
ServerConfig configures the bcd HTTP server.
func (ServerConfig) Addr ¶
func (s ServerConfig) Addr() string
Addr returns the host:port string for the server.
type SlackGatewayConfig ¶
type SlackGatewayConfig struct {
BotToken string `json:"bot_token"`
AppToken string `json:"app_token"`
Mode string `json:"mode"`
Enabled bool `json:"enabled"`
}
SlackGatewayConfig configures the Slack gateway adapter.
type StorageConfig ¶
type StorageConfig struct {
Default string `json:"default"` // "sqlite" or "timescale"
SQLite SQLiteStorageConfig `json:"sqlite"`
Timescale TimescaleStorageConfig `json:"timescale"`
}
StorageConfig configures persistent storage.
type TelegramGatewayConfig ¶
type TelegramGatewayConfig struct {
BotToken string `json:"bot_token"`
Mode string `json:"mode"`
Enabled bool `json:"enabled"`
}
TelegramGatewayConfig configures the Telegram gateway adapter.
type TimescaleStorageConfig ¶
type TimescaleStorageConfig struct {
Host string `json:"host"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
Port int `json:"port"`
}
TimescaleStorageConfig configures TimescaleDB (Postgres) storage.
type TmuxRuntimeConfig ¶
type TmuxRuntimeConfig struct {
SessionPrefix string `json:"session_prefix"`
DefaultShell string `json:"default_shell"`
HistoryLimit int `json:"history_limit"`
}
TmuxRuntimeConfig configures tmux session settings.
type UIConfig ¶
type UIConfig struct {
Theme string `json:"theme"`
Mode string `json:"mode"`
DefaultView string `json:"default_view"`
}
UIConfig configures UI appearance.
type UserConfig ¶
type UserConfig struct {
Name string `json:"name"`
}
UserConfig holds user identity settings.
type UserRCConfig ¶
type UserRCConfig struct {
User UserRCUserConfig `json:"user"`
Defaults UserRCDefaultsConfig `json:"defaults"`
Tools UserRCToolsConfig `json:"tools"`
}
UserRCConfig represents user-level defaults stored in ~/.bcrc. These values are merged with workspace config when loading.
func DefaultUserRCConfig ¶
func DefaultUserRCConfig() UserRCConfig
DefaultUserRCConfig returns sensible defaults for a new .bcrc file.
func LoadUserRCConfig ¶
func LoadUserRCConfig() (*UserRCConfig, error)
LoadUserRCConfig loads the user's .bcrc file.
func ParseUserRCConfig ¶
func ParseUserRCConfig(data []byte) (*UserRCConfig, error)
ParseUserRCConfig parses JSON data into a UserRCConfig.
func (*UserRCConfig) Save ¶
func (c *UserRCConfig) Save() error
Save writes the user config to the .bcrc file.
type UserRCDefaultsConfig ¶
type UserRCDefaultsConfig struct {
DefaultRole string `json:"default_role,omitempty"`
AutoStartRoot bool `json:"auto_start_root,omitempty"`
}
UserRCDefaultsConfig holds default behavior settings.
type UserRCToolsConfig ¶
type UserRCToolsConfig struct {
Preferred []string `json:"preferred,omitempty"`
}
UserRCToolsConfig holds tool preferences.
type UserRCUserConfig ¶
type UserRCUserConfig struct {
Nickname string `json:"nickname,omitempty"`
}
UserRCUserConfig holds user identity settings.
type Workspace ¶
type Workspace struct {
Config *Config // JSON config
RoleManager *RoleManager // Role file manager
RootDir string // Project root directory (pristine git repo)
DataDir string // Runtime state dir (~/.bc/workspaces/<id>/); set for M11+ layouts
// contains filtered or unexported fields
}
Workspace represents an active workspace.
After M11 the Workspace maintains two independent directories:
- RootDir: the project (a pristine git repo bc points at but never writes runtime state into).
- DataDir: the per-workspace runtime directory (~/.bc/workspaces/<id>/) containing preferences.json, state.db, cron.db, agents/, logs/, etc.
StateDir() returns DataDir for new workspaces; for legacy workspaces that still keep state inside <RootDir>/.bc/ (pre-M11 migration), it returns that path instead so in-flight code keeps working until the migration runs.
func Find ¶
Find searches for a workspace starting from dir and going up. It checks the registry first (for .bc/-free workspaces), then falls back to the legacy .bc/ directory walk.
func (*Workspace) ChannelsDir ¶
ChannelsDir returns the channels directory path.
func (*Workspace) DefaultProvider ¶
DefaultProvider returns the default provider name for this workspace.
func (*Workspace) DefaultProviderCommand ¶
DefaultProviderCommand returns the command for the default provider.
func (*Workspace) EnsureDirs ¶
EnsureDirs creates all required directories.
func (*Workspace) GetRolePrompt ¶
GetRolePrompt returns the prompt content for a role.
func (*Workspace) Save ¶
Save saves the workspace configuration to preferences.json. A legacy settings.json on disk is left alone for the user to audit.
func (*Workspace) SettingsFile ¶ added in v0.2.0
SettingsFile returns the absolute path of the workspace preferences file. M11c renames the on-disk filename from settings.json to preferences.json; this accessor is the canonical way to find whichever file actually lives on disk.
Lookup order: <StateDir>/preferences.json (M11c+), then <StateDir>/settings.json (legacy). Returns the preferences.json path when neither exists so callers may safely write to it.
type WorkspaceNotFoundError ¶
type WorkspaceNotFoundError struct {
Identifier string
}
WorkspaceNotFoundError indicates the workspace was not found.
func (*WorkspaceNotFoundError) Error ¶
func (e *WorkspaceNotFoundError) Error() string