Documentation
¶
Overview ¶
Package agentinstall registers Grafo's MCP server with supported MCP clients.
The package is built from three parts: a declarative client registry (registry.go), adapters that either drive a client's own CLI or edit a documented user-level configuration file (adapters.go), and the injected Environment seam (env.go). Detection and dry-run planning only ever receive the read-only half of that seam, so neither can mutate anything.
Index ¶
- Constants
- func CheckUserConfigRoot(reader Reader, path string) error
- func ConfigHomePath(reader Reader, elements ...string) (string, error)
- func Digest(contents string) string
- func ForgetOwnedFile(env Environment, owner, kind string) error
- func HomeDirPath(reader Reader, elements ...string) (string, error)
- func HookCommand(goos, executable, phase string) string
- func JoinPath(goos string, elements ...string) string
- func ParentPath(goos, path string) string
- func ProvesContents(receipt Receipt, contents string) bool
- func ProvesFile(reader Reader, receipt Receipt, path, contents string) bool
- func RecordOwnedFile(env Environment, owner, kind, target, resolvedTarget, contents string) error
- func ResolvePath(reader Reader, path string) (string, error)
- func ResolveUserConfigPath(reader Reader, path string) (string, error)
- func RevalidateUserConfigPath(reader Reader, path, expected string) error
- type Action
- type Client
- type Environment
- type ExecRunner
- type OSEnvironment
- func (OSEnvironment) GOOS() string
- func (OSEnvironment) Getenv(key string) string
- func (OSEnvironment) HomeDir() (string, error)
- func (OSEnvironment) Lstat(name string) (fs.FileInfo, error)
- func (OSEnvironment) MkdirAll(path string, perm fs.FileMode) error
- func (OSEnvironment) ReadFile(name string) ([]byte, error)
- func (OSEnvironment) Readlink(name string) (string, error)
- func (OSEnvironment) Remove(path string) error
- func (OSEnvironment) Stat(name string) (fs.FileInfo, error)
- func (OSEnvironment) TempDir() string
- func (OSEnvironment) WriteFileAtomic(path string, data []byte, perm fs.FileMode) error
- type Options
- type Reader
- type Receipt
- type Runner
- type Status
- type Writer
Constants ¶
const ( KindMCP = "mcp" KindSkill = "skill" KindSetupSkill = "setup-skill" KindInstructions = "instructions" KindHooks = "hooks" )
Artifact kinds. Every Action carries one so callers can tell an MCP registration apart from installed guidance.
const ServiceOwner = "grafo-service"
ServiceOwner labels the background-service artifacts Grafo installs in the shared receipt ledger. Service definitions are recorded beside client registrations so there is exactly one ownership ledger on disk, and one place that can prove Grafo wrote a file before it is rewritten or removed.
Variables ¶
This section is empty.
Functions ¶
func CheckUserConfigRoot ¶
CheckUserConfigRoot refuses paths that escape the user's configuration roots. It is the single containment boundary for everything Grafo writes outside a repository, shared by guidance installation and by service definitions.
func ConfigHomePath ¶
ConfigHomePath resolves a path inside the XDG-style configuration directory ($XDG_CONFIG_HOME or ~/.config, %APPDATA% on Windows). Callers outside this package use it so the service registry lands in the same configuration root the installer already writes to.
func ForgetOwnedFile ¶
func ForgetOwnedFile(env Environment, owner, kind string) error
ForgetOwnedFile drops the receipt for an artifact Grafo has just removed, or for one the user deleted, so stale evidence can never authorize a later write.
func HomeDirPath ¶
HomeDirPath resolves a path relative to the user's home directory.
func HookCommand ¶
HookCommand renders the advisory hook command line for one phase. The executable is shell-quoted for the target platform, because clients run hook commands through a shell and Grafo's absolute path may contain spaces.
func ParentPath ¶
ParentPath returns the directory containing path, or "" when there is none.
func ProvesContents ¶
ProvesContents reports whether a receipt's digest proves Grafo wrote exactly these bytes. It says nothing about location: pair it with OwnedFileAt, which binds a receipt to one resolved path.
func ProvesFile ¶
ProvesFile reports whether a receipt proves Grafo wrote exactly these bytes at exactly this resolved path.
func RecordOwnedFile ¶
func RecordOwnedFile(env Environment, owner, kind, target, resolvedTarget, contents string) error
RecordOwnedFile records a non-client artifact Grafo has just written. The resolved target must be the identity captured and checked before that write; it is revalidated here so a later parent retarget cannot be recorded as the destination of an earlier mutation.
func ResolvePath ¶
ResolvePath resolves every symlinked parent of path and returns the real location, leaving the final element unresolved.
func ResolveUserConfigPath ¶
ResolveUserConfigPath returns the physical direct-write target after proving it remains inside the configured user roots. The leaf stays unresolved.
func RevalidateUserConfigPath ¶
RevalidateUserConfigPath checks that a direct-write target still resolves to the physical path captured during planning and that its leaf is not a symlink.
Types ¶
type Action ¶
type Action struct {
Client Client `json:"client"`
Kind string `json:"kind"` // KindMCP, KindSkill, KindSetupSkill, KindInstructions, KindHooks
Scope string `json:"scope"`
Target string `json:"target"` // config path, artifact path, or client executable
Change string `json:"change"` // "installed", "updated", "removed", "unchanged", "skipped"
DryRun bool `json:"dry_run,omitempty"`
Detail string `json:"detail,omitempty"`
}
Action is what Install/Uninstall did (or, in dry-run, would do) for one artifact of one client.
func Install ¶
func Install(ctx context.Context, env Environment, executable string, options Options) ([]Action, error)
Install registers executable as the Grafo MCP server for the selected clients and installs Grafo's guidance surfaces unless Options.MCPOnly is set. With no explicit targets (or Options.All) every supported client is considered and genuinely uninstalled ones are reported as skipped.
type Client ¶
type Client struct {
Name string `json:"name"` // stable lowercase identity, e.g. "claude"
Display string `json:"display"` // e.g. "Claude Code"
Scope string `json:"scope"` // e.g. "user"
Method string `json:"method"` // "cli" or "config"
}
Client is one supported MCP client.
type Environment ¶
Environment bundles every outside-world operation the installer needs.
type ExecRunner ¶
type ExecRunner struct{}
ExecRunner configures clients through their installed command-line tools.
type OSEnvironment ¶
type OSEnvironment struct {
ExecRunner
}
OSEnvironment is the production Environment: real processes, real files.
func NewOSEnvironment ¶
func NewOSEnvironment() OSEnvironment
NewOSEnvironment returns the Environment backed by the host operating system.
func (OSEnvironment) GOOS ¶
func (OSEnvironment) GOOS() string
func (OSEnvironment) Getenv ¶
func (OSEnvironment) Getenv(key string) string
func (OSEnvironment) HomeDir ¶
func (OSEnvironment) HomeDir() (string, error)
func (OSEnvironment) Remove ¶
func (OSEnvironment) Remove(path string) error
func (OSEnvironment) TempDir ¶
func (OSEnvironment) TempDir() string
func (OSEnvironment) WriteFileAtomic ¶
WriteFileAtomic writes to a temporary file in path's directory and renames it over path, so a failure leaves the original configuration untouched.
type Options ¶
type Options struct {
Targets []string // explicit client names; empty or ["all"] means every supported client
All bool
DryRun bool
// MCPOnly installs or removes only the MCP registration, never guidance.
MCPOnly bool
// Hooks opts in to the advisory, fail-open hooks of clients that document a
// safe hook API. Uninstall always removes Grafo's hooks regardless.
Hooks bool
// Refresh updates only artifacts that are already present, so it never adds
// a registration, skill, block, or hook that the user has not installed.
Refresh bool
// Announce receives the full planned action list before any mutation runs.
Announce func(planned []Action)
// contains filtered or unexported fields
}
Options selects clients, selects artifact kinds, and controls mutation.
type Reader ¶
type Reader interface {
// LookPath resolves an executable on PATH.
LookPath(file string) (string, error)
// Output runs a read-only informational command (`--help`, `mcp list`).
Output(ctx context.Context, name string, arguments ...string) ([]byte, error)
// ReadFile reads a configuration file.
ReadFile(name string) ([]byte, error)
// Stat reports whether a path exists, following symlinks.
Stat(name string) (fs.FileInfo, error)
// Lstat reports on a path without following symlinks, so the installer can
// refuse to write through one.
Lstat(name string) (fs.FileInfo, error)
// Readlink reads a symbolic link's target, so the installer can resolve a
// path completely before deciding whether it stays inside the user's
// configuration tree.
Readlink(name string) (string, error)
// GOOS reports the target operating system ("linux", "darwin", "windows").
GOOS() string
// HomeDir reports the current user's home directory.
HomeDir() (string, error)
// Getenv reads an environment variable.
Getenv(key string) string
// TempDir reports the directory used for temporary files.
TempDir() string
}
Reader is the read-only view of the outside world. Detection and dry-run planning are restricted to this interface so that neither can mutate state: there is simply no write operation reachable from here.
type Receipt ¶
type Receipt struct {
Client string `json:"client"`
Kind string `json:"kind"`
Target string `json:"target"`
ResolvedTarget string `json:"resolved_target,omitempty"`
Digest string `json:"digest,omitempty"`
// Commands are the exact command lines Grafo installed for a hook artifact,
// in hookPhases order. Removal matches them exactly, so a user-authored
// entry that merely resembles one is never claimed.
Commands []string `json:"commands,omitempty"`
Marker string `json:"marker,omitempty"`
Guidance string `json:"guidance_version,omitempty"`
Grafo string `json:"grafo_version"`
Updated string `json:"updated_at"`
}
Receipt records one artifact Grafo installed, so a later uninstall or upgrade can prove ownership instead of guessing from file contents alone.
func OwnedFile ¶
OwnedFile reports the receipt Grafo holds for one non-client artifact, such as a generated service definition. The boolean reports whether a receipt exists at all; a missing receipt is not an error.
func OwnedFileAt ¶
OwnedFileAt reports the receipt Grafo holds for one non-client artifact, but only when that receipt covers this exact location. Targets are compared after resolving every symlinked parent, so a receipt written for one path can never authorize a mutation at a different path that merely resolves nearby, and a path spelled differently from the recorded target still matches when both name the same file.
Keying ownership to the artifact kind alone is the defect PR #53 fixed for hook receipts; this is the same contract for generated service definitions.
type Runner ¶
type Runner interface {
LookPath(file string) (string, error)
Output(ctx context.Context, name string, arguments ...string) ([]byte, error)
Run(ctx context.Context, name string, arguments ...string) ([]byte, error)
}
Runner provides the process operations needed to configure a client. It is retained for compatibility with callers written against the original process-only seam; Environment embeds the same three operations.
type Status ¶
type Status struct {
Client Client `json:"client"`
Installed bool `json:"installed"` // the client itself was found
Path string `json:"path,omitempty"` // client executable or config file
Registered bool `json:"registered"` // grafo is already registered
Detail string `json:"detail,omitempty"`
}
Status is one row of detection output. Detect never mutates anything.
type Writer ¶
type Writer interface {
// Run executes a client command that changes configuration.
Run(ctx context.Context, name string, arguments ...string) ([]byte, error)
// MkdirAll creates a configuration directory and its parents.
MkdirAll(path string, perm fs.FileMode) error
// WriteFileAtomic replaces path's contents by writing a temporary file in
// the same directory and renaming it over path.
WriteFileAtomic(path string, data []byte, perm fs.FileMode) error
// Remove deletes one file or empty directory Grafo owns.
Remove(path string) error
}
Writer holds the mutating operations. Only Install and Uninstall outside of dry-run mode ever receive a value that satisfies it.