Documentation
¶
Overview ¶
Package harness detects and checks AI agent integration health.
Index ¶
- Constants
- Variables
- func AgentSkillPath() string
- func BaselineSkillInstalled() bool
- func CodexLegacyReplaced(ctx context.Context) bool
- func DetectClaude() bool
- func DetectCodex() bool
- func FindClaudeBinary() string
- func FindCodexBinary() string
- func InstalledPluginVersion() string
- func IsPluginNeeded() bool
- func RegisterAgent(info AgentInfo)
- type AgentInfo
- type SkillAgent
- type StalePlugin
- type StatusCheck
- func CheckClaudePlugin() *StatusCheck
- func CheckClaudePluginVersion() *StatusCheck
- func CheckClaudeSkillLink() *StatusCheck
- func CheckCodexPlugin() *StatusCheck
- func CheckCodexPluginContext(ctx context.Context) *StatusCheck
- func CheckCodexPluginDiagnosticsContext(ctx context.Context) []*StatusCheck
- func CheckCodexPluginVersion() *StatusCheck
- func CheckCodexPluginVersionContext(ctx context.Context) *StatusCheck
Constants ¶
const ( // CodexMarketplaceSource is the Git marketplace repository containing Basecamp. CodexMarketplaceSource = "basecamp/claude-plugins" // CodexPluginName is the plugin identifier to install. It was "basecamp" // until that name was set aside for the hosted-connector plugin; see // CodexLegacyPluginKey. CodexPluginName = "basecamp-cli" // CodexMarketplaceName is the marketplace name published by 37signals. CodexMarketplaceName = "37signals" // CodexExpectedPluginKey is the fully qualified Basecamp plugin ID. CodexExpectedPluginKey = CodexPluginName + "@" + CodexMarketplaceName // CodexLegacyPluginKey is the pre-rename plugin ID. During the migration // window the marketplace lists "basecamp" only as an alias of basecamp-cli, // with the same source, so an install under it is always this CLI's (see // ClaudeLegacyPluginKey). CodexLegacyPluginKey = "basecamp@" + CodexMarketplaceName )
const AutoUpdateHint = "In Claude Code: /plugins → Marketplaces → 37signals → Enable auto-update"
AutoUpdateHint is the user-facing instruction for enabling plugin auto-update.
const ClaudeExpectedPluginKey = ClaudePluginName + "@" + ClaudeMarketplaceName
ClaudeExpectedPluginKey is the fully-qualified key for a correctly installed plugin.
const ClaudeLegacyPluginKey = LegacyPluginName + "@" + ClaudeMarketplaceName
ClaudeLegacyPluginKey is the key this plugin was installed under before the rename. During the migration window the 37signals marketplace lists "basecamp" only as a deprecated alias of basecamp-cli with the same source, so an install under this key is always this CLI's pre-rename plugin and setup removes it by key, like any stale entry.
The marketplace may later list the hosted Basecamp connector as "basecamp" again. That step has to wait until this key is no longer treated as stale here (isStalePluginKey); otherwise setup would uninstall the connector.
const ClaudeMarketplaceName = "37signals"
ClaudeMarketplaceName is the marketplace name as it appears in plugin keys.
const ClaudeMarketplaceSource = "basecamp/claude-plugins"
ClaudeMarketplaceSource is the marketplace repository for the Basecamp plugin. Migrating from basecamp/basecamp-cli → basecamp/claude-plugins.
const ClaudePluginName = "basecamp-cli"
ClaudePluginName is the plugin identifier to install. It was "basecamp" until that name was set aside for the hosted-connector plugin; see ClaudeLegacyPluginKey.
const LegacyPluginName = "basecamp"
LegacyPluginName is the plugin's pre-rename name.
Variables ¶
var Grok = SkillAgent{Name: "Grok", ID: "grok", HomeEnv: "GROK_HOME", HomeDir: ".grok", Binary: "grok"}
Grok is xAI's Grok Build CLI. It reads user skills from ~/.grok/skills and from the cross-agent ~/.agents/skills, so the shared skill is all it needs.
Functions ¶
func AgentSkillPath ¶ added in v0.12.0
func AgentSkillPath() string
AgentSkillPath returns the shared skill's path, ~/.agents/skills/basecamp/SKILL.md, or "" when the home directory cannot be determined. Every agent that reads the cross-agent skills directory finds the skill here.
func BaselineSkillInstalled ¶ added in v0.12.0
func BaselineSkillInstalled() bool
BaselineSkillInstalled reports whether the shared skill is on disk. It is the one health predicate for the shared skill: setup, doctor and the shared-skill agents' checks all answer from it.
func CodexLegacyReplaced ¶ added in v0.13.0
CodexLegacyReplaced reports whether this CLI's plugin is still installed in Codex under CodexLegacyPluginKey while CodexExpectedPluginKey is installed and enabled, so removing the old ID leaves a working plugin behind. A disabled replacement (Codex keeps those installed) doesn't count.
func DetectClaude ¶
func DetectClaude() bool
DetectClaude returns true if Claude Code is installed. Checks ~/.claude/ directory first, then falls back to binary on PATH.
func DetectCodex ¶ added in v0.8.0
func DetectCodex() bool
DetectCodex returns true when Codex has a home directory or executable.
func FindClaudeBinary ¶ added in v0.3.0
func FindClaudeBinary() string
FindClaudeBinary returns the path to the claude binary, or "" if not found.
func FindCodexBinary ¶ added in v0.8.0
func FindCodexBinary() string
FindCodexBinary returns the Codex executable path, or an empty string.
func InstalledPluginVersion ¶ added in v0.7.0
func InstalledPluginVersion() string
InstalledPluginVersion reads the installed plugin version from ~/.claude/plugins/installed_plugins.json. Returns "" if unreadable.
func IsPluginNeeded ¶ added in v0.3.0
func IsPluginNeeded() bool
IsPluginNeeded returns true if Claude Code is installed but the plugin is not.
func RegisterAgent ¶ added in v0.3.0
func RegisterAgent(info AgentInfo)
RegisterAgent adds an agent to the global registry. Typically called from init() in agent-specific files. Panics on empty or duplicate IDs to keep registry state well-defined.
Types ¶
type AgentInfo ¶ added in v0.3.0
type AgentInfo struct {
Name string // "Claude Code"
ID string // "claude"
Detect func() bool // returns true if the agent is installed
Checks func() []*StatusCheck // cheap health checks gating setup wizard behavior
// FindBinary returns the agent's executable path, or "" when it is not on
// disk. Setup reads it to decide whether a "binary not found" remediation
// applies; nil means the agent has no executable to look for.
FindBinary func() string
// Diagnostics returns the full doctor check suite, including checks that
// are too slow or noisy for the wizard (e.g. version comparisons).
// When nil, doctor falls back to Checks.
Diagnostics func(ctx context.Context) []*StatusCheck
}
AgentInfo describes a coding agent integration.
func AllAgents ¶ added in v0.3.0
func AllAgents() []AgentInfo
AllAgents returns every registered agent.
func DetectedAgents ¶ added in v0.3.0
func DetectedAgents() []AgentInfo
DetectedAgents returns all agents whose Detect function returns true. Copies the registry under the lock before calling Detect callbacks to avoid holding the lock during potentially slow I/O.
type SkillAgent ¶ added in v0.12.0
type SkillAgent struct {
Name string // "Grok"
ID string // "grok"; the `basecamp setup <id>` subcommand and BASECAMP_SETUP_AGENT value
HomeEnv string // "GROK_HOME"; overrides HomeDir when set
HomeDir string // ".grok"; under the user's home directory
Binary string // "grok"; the executable's name
}
SkillAgent describes a coding agent whose whole Basecamp integration is the shared ~/.agents skill: basecamp-cli ships no plugin for it, so setup is confirming the shared skill and health is skill presence only. Each such agent differs only in the five fields below, which is why they are rows in a table rather than a file apiece.
Codex is not a row. It has a native plugin here (.codex-plugin, installed through the 37signals marketplace), so its detection, setup and health are its own — see codex.go. An agent leaves this table the day it grows one.
func SkillAgents ¶ added in v0.12.0
func SkillAgents() []SkillAgent
SkillAgents returns every shared-skill agent, in registration order.
func (SkillAgent) CheckSkill ¶ added in v0.12.0
func (a SkillAgent) CheckSkill() *StatusCheck
CheckSkill checks whether the shared Basecamp skill is installed for the agent. It answers from the same predicate as BaselineSkillInstalled; the extra states only say why it is false.
func (SkillAgent) Detect ¶ added in v0.12.0
func (a SkillAgent) Detect() bool
Detect reports whether the agent has a home directory or an executable.
func (SkillAgent) FindBinary ¶ added in v0.12.0
func (a SkillAgent) FindBinary() string
FindBinary returns the agent's executable path, or an empty string. It looks on PATH first, then where an installer puts the binary when the shell has not picked up the PATH change yet: ~/.local/bin, and the agent's own home's bin (Grok Build's installers write ~/.grok/bin/grok, or $GROK_HOME/bin/grok for the npm package). Each fallback goes through exec.LookPath too, so it is held to the PATH lookup's standard — an executable regular file on Unix, a PATHEXT extension such as .exe on Windows — and a stale directory or non-executable file of that name is not reported as the binary.
func (SkillAgent) Home ¶ added in v0.12.0
func (a SkillAgent) Home() string
Home returns the agent's home directory: $HomeEnv, or HomeDir under the user's home. Empty when neither can be determined.
type StalePlugin ¶ added in v0.7.0
type StalePlugin struct {
Key string
Scopes []string // empty = unknown, fall back to unscoped uninstall
}
StalePlugin carries a stale plugin key along with the scopes it was found in.
func StalePluginKeys ¶ added in v0.7.0
func StalePluginKeys() []StalePlugin
StalePluginKeys returns stale plugin entries from installed_plugins.json: entries from old/dead marketplaces, and this plugin still installed under its pre-rename key. Setup uninstalls each and reinstalls ClaudeExpectedPluginKey at the scopes it removed.
type StatusCheck ¶
type StatusCheck struct {
Name string `json:"name"`
Status string `json:"status"` // "pass", "warn", "fail"
Message string `json:"message"`
Hint string `json:"hint,omitempty"`
}
StatusCheck represents a single agent integration health check result.
func CheckClaudePlugin ¶
func CheckClaudePlugin() *StatusCheck
CheckClaudePlugin checks whether the basecamp plugin is installed in Claude Code.
func CheckClaudePluginVersion ¶ added in v0.7.0
func CheckClaudePluginVersion() *StatusCheck
CheckClaudePluginVersion compares the installed plugin version against the running CLI version. Returns a warn check when they differ.
func CheckClaudeSkillLink ¶ added in v0.3.0
func CheckClaudeSkillLink() *StatusCheck
CheckClaudeSkillLink checks whether ~/.claude/skills/basecamp contains a valid SKILL.md.
func CheckCodexPlugin ¶ added in v0.8.0
func CheckCodexPlugin() *StatusCheck
CheckCodexPlugin verifies that Basecamp is installed and enabled in Codex.
func CheckCodexPluginContext ¶ added in v0.8.0
func CheckCodexPluginContext(ctx context.Context) *StatusCheck
CheckCodexPluginContext verifies the plugin using the caller's context.
func CheckCodexPluginDiagnosticsContext ¶ added in v0.8.0
func CheckCodexPluginDiagnosticsContext(ctx context.Context) []*StatusCheck
CheckCodexPluginDiagnosticsContext checks plugin health and version from one Codex query.
func CheckCodexPluginVersion ¶ added in v0.8.0
func CheckCodexPluginVersion() *StatusCheck
CheckCodexPluginVersion compares the installed plugin and CLI versions.
func CheckCodexPluginVersionContext ¶ added in v0.8.0
func CheckCodexPluginVersionContext(ctx context.Context) *StatusCheck
CheckCodexPluginVersionContext compares plugin and CLI versions using the caller's context.