Help us shape the future of Go! Take the 2026 Go Developer Survey

harness

package
v0.22.0 Latest Latest
Warning

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

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package harness detects and checks AI agent integration health.

Index

Constants

View Source
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
)
View Source
const AutoUpdateHint = "In Claude Code: /plugins → Marketplaces → 37signals → Enable auto-update"

AutoUpdateHint is the user-facing instruction for enabling plugin auto-update.

View Source
const ClaudeExpectedPluginKey = ClaudePluginName + "@" + ClaudeMarketplaceName

ClaudeExpectedPluginKey is the fully-qualified key for a correctly installed plugin.

View Source
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.

View Source
const ClaudeMarketplaceName = "37signals"

ClaudeMarketplaceName is the marketplace name as it appears in plugin keys.

View Source
const ClaudeMarketplaceSource = "basecamp/claude-plugins"

ClaudeMarketplaceSource is the marketplace repository for the Basecamp plugin. Migrating from basecamp/basecamp-cli → basecamp/claude-plugins.

View Source
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.

View Source
const LegacyPluginName = "basecamp"

LegacyPluginName is the plugin's pre-rename name.

View Source
const LegacyPluginUpdateHint = "Run: basecamp setup claude (reinstalls it as " + ClaudeExpectedPluginKey + "), then restart Claude Code"

LegacyPluginUpdateHint is the remedy for a plugin still installed under its pre-rename key: setup reinstalls it as basecamp-cli, at its newest.

Variables

View Source
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

func CodexLegacyReplaced(ctx context.Context) bool

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 PluginUpdateNotice added in v0.13.1

func PluginUpdateNotice(cli string) string

PluginUpdateNotice is the one-line notice shown after the CLI changes version, or "" when the Claude Code plugin matches it, isn't installed, or this is a dev build. A copy still under the pre-rename key is named first, whatever its version and whether or not the current key is there too: that copy is where a plugin old enough to lack the connect skill lives, updating it keeps the old name, and setup is what moves it.

func PluginVersionMismatchHint added in v0.13.1

func PluginVersionMismatchHint(installed, cli string) string

PluginVersionMismatchHint says which side of a plugin/CLI mismatch to bring forward. A plugin behind the CLI is missing skills the CLI's commands expect (basecamp-connect arrived in 0.12), and only takes effect after a restart, so the hint names the update and the restart. A CLI behind the plugin lacks commands the plugin's skills run, so the hint is the upgrade. Versions that don't parse get the auto-update advice.

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.

func SameVersion added in v0.13.1

func SameVersion(a, b string) bool

SameVersion reports whether two version strings name the same release, however they are spelled ("v0.13.0" and "0.13.0" are one release). Strings that don't parse as versions are compared as written.

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.

func FindAgent added in v0.3.0

func FindAgent(id string) *AgentInfo

FindAgent returns the agent with the given ID, or nil.

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() *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.

Jump to

Keyboard shortcuts

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