Files
sherlock/internal/agent/agent.go
T
Alexandru Macocian 84958c7ef3
Release / release (push) Successful in 17s
Support for local agents
2026-06-15 19:03:56 +02:00

87 lines
2.8 KiB
Go

// Package agent dispatches `sherlock <name>` invocations to a small
// set of supported CLIs (Copilot, Claude Code, …). Each built-in agent
// is a concrete type in its own file that implements the Agent
// interface; they register themselves at init time. Adding a new
// built-in is a matter of dropping a new file in this package.
//
// Operators can also declare custom agents in config.toml's
// `[agents.<name>]` table — a wrapper command plus a backend type
// ("copilot" or "claude") that selects the MCP-config and env
// conventions. Those are added at runtime via RegisterCustom. The
// per-CLI quirks shared by built-in and custom agents live in
// backend.go.
//
// The reusable bits live in this package too (exec.go for env/exec
// helpers; the mcp sibling package for MCP-config rendering). Agent
// implementations are intentionally short — they wire the per-CLI
// quirks (flag names, env var names, MCP config schema) and delegate
// everything else.
package agent
import (
"fmt"
"sort"
"gitea.alexandru.macocian.me/amacocian/sherlock/internal/mcp"
)
// Context is everything the CLI hands an agent at spawn time. Kept
// deliberately small — Phase 2+ fields land here as MCPs are wired.
type Context struct {
// Servers is the set of MCP servers sherlock wants the agent to
// know about. The agent's Spawn method renders these into the
// per-agent MCP config file before exec.
Servers mcp.Servers
}
// Agent is the contract one concrete type implements per supported CLI.
type Agent interface {
// Name is the case-sensitive subcommand: `sherlock <Name>`.
Name() string
// Description shows up in `sherlock status`.
Description() string
// Spawn replaces the current process with the agent CLI. On
// success it does not return; any returned error is a setup
// failure (binary missing, MCP render failed, exec failed).
Spawn(ctx Context, args []string) error
}
var registry = map[string]Agent{}
// Register adds a to the package-level registry. Called from init()
// in each agent file. Duplicate names panic at package load — that's
// always a programmer error.
func Register(a Agent) {
if _, dup := registry[a.Name()]; dup {
panic(fmt.Sprintf("agent: %q registered twice", a.Name()))
}
registry[a.Name()] = a
}
// Get returns the agent for name and whether it exists.
func Get(name string) (Agent, bool) {
a, ok := registry[name]
return a, ok
}
// Names returns the registered agent names, sorted.
func Names() []string {
out := make([]string, 0, len(registry))
for n := range registry {
out = append(out, n)
}
sort.Strings(out)
return out
}
// All returns the registered agents, sorted by name. Useful for
// rendering descriptions in `sherlock status`.
func All() []Agent {
names := Names()
out := make([]Agent, len(names))
for i, n := range names {
out[i] = registry[n]
}
return out
}