Documentation
¶
Overview ¶
Package plugin implements iterion's plugin ecosystem: declarative, out-of-process extensions described by a `plugin.yaml` manifest with typed contribution points. iterion cannot use Go's `plugin` package (it ships static CGO_ENABLED=0 binaries that are bind-mounted into sandbox containers), so a plugin never injects Go code — it declares WHAT to contribute and the runtime wires it into iterion's existing seams:
rewriters → command-output compressors (the rtk generalization) applied
on the three shell surfaces (claude_code hook, claw builtin,
tool node), composable as an ordered chain.
mcp_servers → MCP servers merged into the workflow MCP catalog (the natural
home for knowledge-graph explorers like repo-falcon).
skills → markdown skills mirrored into <workspace>/.claude/skills/.
lifecycle → index/refresh commands (e.g. build/refresh a code graph).
Plugins load from two sources: builtins embedded in the binary (rtk enabled by default; graphify + repo-falcon shipped disabled) and installed plugins under ~/.iterion/plugins/<name>/. Enable/disable state lives in ~/.iterion/plugins.yaml; the marketplace installs third-party plugins into the same directory.
Index ¶
- Constants
- Variables
- func Install(ctx context.Context, src string) (string, error)
- func InstallWith(ctx context.Context, opts InstallOptions) (string, error)
- func NormalizeName(src string) string
- func ReadReadme(dir string) (string, error)
- func RunLifecycle(ctx context.Context, reg *Registry, name, phase, workspace string, ...) error
- func Uninstall(name string) error
- func WriteManifest(dir string, m *Manifest) error
- type ConfigField
- type Contributes
- type Detail
- type ExpandContext
- type HookInfo
- type InspectInfo
- type InstallOptions
- type InvokeSpec
- type LifecycleInfo
- type LifecycleSpec
- type LocateSpec
- type MCPServerInfo
- type MCPServerSpec
- type Manifest
- type MirrorKind
- type ModeSpec
- type Plugin
- type Registry
- func (r *Registry) ApplyConfig(name string, submitted map[string]string) error
- func (r *Registry) CacheDir(name string) string
- func (r *Registry) DetailFor(name string) (Detail, error)
- func (r *Registry) EffectiveConfig(name string) map[string]string
- func (r *Registry) Enabled() []*Plugin
- func (r *Registry) EnabledRewriterSpecs() []RewriterSpec
- func (r *Registry) EnabledRewriters() []RewriterContribution
- func (r *Registry) ExpandContextFor(name, workspace string) ExpandContext
- func (r *Registry) Get(name string) (*Plugin, bool)
- func (r *Registry) InstallDir(name string) string
- func (r *Registry) IsEnabled(name string) bool
- func (r *Registry) Remove(name string) error
- func (r *Registry) SetConfig(name string, values map[string]string) error
- func (r *Registry) SetEnabled(name string, enabled bool) error
- func (r *Registry) StoredConfig(name string) map[string]string
- func (r *Registry) ViewFor(name string) (View, bool)
- func (r *Registry) Views() []View
- type RewriterContribution
- type RewriterInfo
- type RewriterSpec
- type SkillFile
- type View
Constants ¶
const CommandPlaceholder = "{{command}}"
CommandPlaceholder is the token in a rewriter argv replaced by the full shell command line at rewrite time.
const ManifestFile = "plugin.yaml"
ManifestFile is the manifest filename at a plugin's root.
const SchemaVersion = 1
SchemaVersion is the current plugin.yaml schema. Unknown future versions are rejected by ParseManifest so an old binary fails loudly rather than silently dropping contributions it cannot honour.
Variables ¶
var ErrNoManifest = errors.New("plugin: no plugin.yaml")
ErrNoManifest is returned by Inspect when the source directory has no plugin.yaml. Callers branch on it with errors.Is (e.g. the marketplace submit flow falls back to skill-library synthesis).
var MirrorKinds = []MirrorKind{
{Name: "skill", Dir: "skills"},
{Name: "command", Dir: "commands"},
{Name: "agent", Dir: "agents"},
}
MirrorKinds is the set of markdown contribution kinds, each mirrored into its own .claude/ subdir with the shared collision policy.
Functions ¶
func Install ¶
Install installs a plugin from a local directory or git URL into ~/.iterion/plugins/<name>/ and returns the installed plugin's name. It is InstallWith with only a source. Both the CLI (`iterion plugin install`) and the HTTP server (POST /api/v1/plugins/install) call this so the behaviour is identical on either surface.
func InstallWith ¶ added in v0.43.0
func InstallWith(ctx context.Context, opts InstallOptions) (string, error)
InstallWith installs a plugin per opts into ~/.iterion/plugins/<name>/ and returns the installed plugin's name. A git source is shallow-cloned (at opts.Ref when set); a local path's plugin.yaml is validated then copied. When the (sub)source has no plugin.yaml but ships bare skills (a public skill library), a skills-only manifest is synthesized and persisted into the install dir.
InstallWith only places files — it never executes plugin code; the cloned repo's .git metadata is stripped (see copyTree).
func NormalizeName ¶
NormalizeName derives a kebab-case plugin name from a directory path or git URL: it takes the last path segment, strips a trailing ".git", lowercases, and replaces any run of non-alphanumeric characters with a single dash.
func ReadReadme ¶ added in v0.43.0
ReadReadme returns the README body from dir, matching the filename case-insensitively (README.md / readme.md / Readme.md, …) and capping the content at 16 KiB. A directory with no README yields an empty string, not an error; an unreadable directory or file is an error.
func RunLifecycle ¶ added in v0.43.0
func RunLifecycle(ctx context.Context, reg *Registry, name, phase, workspace string, stdout, stderr io.Writer) error
RunLifecycle executes a plugin's lifecycle command ("index" or "refresh") in the given workspace (default: cwd), streaming subprocess output to stdout/stderr. Placeholders ({{workspace}}, {{plugin.dir}}, {{plugin.cache}}, {{config.<key>}}) are expanded before the command runs via `sh -c`; the plugin's cache directory is created so {{plugin.cache}} always resolves to an existing path. Shared by the CLI (`iterion plugin run`) and the HTTP server so both surfaces run lifecycles identically.
func Uninstall ¶
Uninstall removes an installed plugin. Builtins cannot be uninstalled (Registry.Remove rejects them — disable instead).
func WriteManifest ¶
WriteManifest marshals m to <dir>/plugin.yaml. Used by the install path to persist a synthesized manifest into an installed skill library.
Types ¶
type ConfigField ¶
type ConfigField struct {
// Key is the setting id, referenced as {{config.<key>}}.
Key string `yaml:"key" json:"key"`
// Label is the studio form label; defaults to Key when empty.
Label string `yaml:"label" json:"label,omitempty"`
// Type is one of string | bool | int | float | enum | secret (default string).
// secret renders as a password field and its value is never sent back to the
// studio (only whether it's set).
Type string `yaml:"type" json:"type,omitempty"`
// Description is shown under the field in the studio.
Description string `yaml:"description" json:"description,omitempty"`
// Default is the value used until the operator sets one.
Default string `yaml:"default" json:"default,omitempty"`
// Options are the allowed values for type: enum.
Options []string `yaml:"options" json:"options,omitempty"`
// Required marks the field as mandatory (advisory; surfaced in the studio).
Required bool `yaml:"required" json:"required,omitempty"`
}
ConfigField declares one user-configurable setting. Values are stored and expanded as strings (bool/int render as their natural input in the studio but persist as "true"/"30"), which is what {{config.<key>}} substitutes into a plugin's commands and process environment.
type Contributes ¶
type Contributes struct {
Rewriters []RewriterSpec `yaml:"rewriters"`
MCPServers []MCPServerSpec `yaml:"mcp_servers"`
Skills []string `yaml:"skills"`
Commands []string `yaml:"commands"`
Agents []string `yaml:"agents"`
// Hooks are paths to JSON settings fragments ({"hooks": {<Event>: [...]}}),
// idempotently merged into the workspace's .claude/settings.json so
// claude_code fires them (discovered via --setting-sources project). A
// command-type hook runs arbitrary shell on tool events — installed plugins
// are opt-in (disabled by default), so this is the operator's choice.
Hooks []string `yaml:"hooks"`
Lifecycle *LifecycleSpec `yaml:"lifecycle"`
}
Contributes is the set of typed contribution points.
Skills, Commands and Agents are markdown files mirrored into the workspace's .claude/<skills|commands|agents>/ directory at run start (claude_code discovers them via --setting-sources project; the claw backend reads the same dirs). They share one mirror mechanism and one collision policy.
type Detail ¶ added in v0.43.0
type Detail struct {
View View `json:"view"`
Readme string `json:"readme,omitempty"`
AutoIndex bool `json:"auto_index"`
Rewriters []RewriterInfo `json:"rewriters,omitempty"`
MCPServers []MCPServerInfo `json:"mcp_servers,omitempty"`
Skills []string `json:"skills,omitempty"`
Commands []string `json:"commands,omitempty"`
Agents []string `json:"agents,omitempty"`
Hooks []HookInfo `json:"hooks,omitempty"`
Lifecycle *LifecycleInfo `json:"lifecycle,omitempty"`
Dir string `json:"dir,omitempty"`
}
Detail is the studio-facing full projection of one plugin: the listing View plus README and every contribution spelled out (what a rewriter injects, which MCP servers start, which files mirror, what shell the hooks fire).
type ExpandContext ¶
type ExpandContext struct {
Workspace string
PluginDir string
CacheDir string
// Config is the plugin's effective config (defaults overlaid with operator
// values), exposed as {{config.<key>}} placeholders.
Config map[string]string
}
ExpandContext carries the values used to expand activation-time placeholders in mcp_servers args/env and lifecycle commands.
func (ExpandContext) Expand ¶
func (e ExpandContext) Expand(s string) string
Expand substitutes {{workspace}}, {{plugin.dir}}, {{plugin.cache}} and {{config.<key>}} in s.
type HookInfo ¶ added in v0.43.0
HookInfo surfaces one hook event contributed by a plugin: the claude_code event name (PreToolUse, Stop, …) and the raw shell command strings the hook fires — the studio shows these verbatim so the operator can vet what an opt-in plugin runs on tool events.
type InspectInfo ¶ added in v0.43.0
InspectInfo is what Inspect reads out of a plugin source directory before any install: the validated manifest plus the README body (empty when the source ships none).
func Inspect ¶ added in v0.43.0
func Inspect(ctx context.Context, srcDir string) (*InspectInfo, error)
Inspect reads and validates a plugin source directory without installing it: parses <srcDir>/plugin.yaml (a missing manifest reports ErrNoManifest) and reads the README via ReadReadme. The context is accepted for API symmetry with Install/InstallWith (a future git-source inspect will need it); local inspection does no blocking work beyond file reads.
type InstallOptions ¶ added in v0.43.0
InstallOptions parameterizes InstallWith. Source is a local directory or a git URL (required); Ref pins a branch or tag for a git source; Subpath selects a plugin directory inside the source (for monorepos shipping several plugins).
type InvokeSpec ¶
type InvokeSpec struct {
// Argv is the argument vector. Exactly one element must contain the
// "{{command}}" placeholder, substituted with the full shell command line.
Argv []string `yaml:"argv"`
// Env are extra environment variables set on every invocation (merged over
// the inherited process env, which wins on conflict for operator override).
Env map[string]string `yaml:"env"`
// TimeoutMs bounds a single invocation; 0 → DefaultRewriteTimeoutMs.
TimeoutMs int `yaml:"timeout_ms"`
// ApplyExitCodes are the exit codes whose stdout is taken as the rewrite.
// Empty defaults to {0}. (rtk uses {0,3}: Default verdict maps to Ask=3.)
ApplyExitCodes []int `yaml:"apply_exit_codes"`
// Modes maps a generic intensity level (on|ultra) to its transform. A mode
// absent here is still accepted (it simply applies no extra transform).
Modes map[string]ModeSpec `yaml:"modes"`
}
InvokeSpec is the subprocess contract for a rewriter.
type LifecycleInfo ¶ added in v0.43.0
type LifecycleInfo struct {
Index string `json:"index,omitempty"`
Refresh string `json:"refresh,omitempty"`
}
LifecycleInfo is the detail-facing projection of a LifecycleSpec.
type LifecycleSpec ¶
LifecycleSpec declares index/refresh commands for graph-building plugins. Commands are shell strings run via `sh -c` with placeholders expanded.
type LocateSpec ¶
type LocateSpec struct {
Env string `yaml:"env"`
Bin string `yaml:"bin"`
Paths []string `yaml:"paths"`
}
LocateSpec resolves a binary: env override first, then PATH (Bin), then the conventional install Paths. Empty fields are skipped.
type MCPServerInfo ¶ added in v0.43.0
type MCPServerInfo struct {
Name string `json:"name"`
Transport string `json:"transport"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
URL string `json:"url,omitempty"`
}
MCPServerInfo is the detail-facing projection of an MCPServerSpec: the server's name, transport, and how it is reached (command+args for stdio, url for http/sse).
type MCPServerSpec ¶
type MCPServerSpec struct {
Name string `yaml:"name"`
Transport string `yaml:"transport"`
Command string `yaml:"command"`
Args []string `yaml:"args"`
URL string `yaml:"url"`
Headers map[string]string `yaml:"headers"`
Env map[string]string `yaml:"env"`
}
MCPServerSpec declares an MCP server contribution. It mirrors the runtime mcp.ServerConfig shape; placeholders ({{workspace}}, {{plugin.dir}}, {{plugin.cache}}) are expanded at activation time.
type Manifest ¶
type Manifest struct {
// Name is the plugin's unique id (kebab-case). It is also the directory
// name under ~/.iterion/plugins/ and the enable/disable key.
Name string `yaml:"name"`
// Version is free-form (semver recommended). Surfaced in `plugin list`.
Version string `yaml:"version"`
// Description is the one-line summary shown in listings.
Description string `yaml:"description"`
// Author is free-form attribution.
Author string `yaml:"author"`
// SchemaVersion defaults to 1 when omitted.
SchemaVersion int `yaml:"schema_version"`
// DefaultEnabled is the enable state when the operator has expressed no
// preference in plugins.yaml. rtk ships true; KG explorers ship false.
DefaultEnabled bool `yaml:"default_enabled"`
// AutoIndex, when true, runs the lifecycle `index` command before a run if
// the plugin is enabled and contributes a lifecycle.
AutoIndex bool `yaml:"auto_index"`
// Contributes lists the typed extension points this plugin provides.
Contributes Contributes `yaml:"contributes"`
// Config declares user-configurable settings (like a Firefox add-on's
// preferences). The operator sets values in the studio; they are stored in
// plugins.yaml and substituted into the manifest's mcp env/args, rewriter
// env, and lifecycle commands via {{config.<key>}} placeholders.
Config []ConfigField `yaml:"config"`
}
Manifest is a parsed `plugin.yaml`.
func ParseManifest ¶
ParseManifest decodes and validates a plugin.yaml document.
func SynthesizeSkillsManifest ¶
SynthesizeSkillsManifest builds a skills-only Manifest for a directory that ships bare Claude-style skills but no plugin.yaml — the common shape of a public skill library. It collects markdown skills under <dir>/skills/ (recursively); if there is no skills/ directory it falls back to top-level *.md files. The returned manifest is disabled by default (an installed third-party skill pack should be opt-in). Returns an error when no skill files are found, so the caller can report that the repo is not installable.
type MirrorKind ¶
type MirrorKind struct {
Name string // "skill" | "command" | "agent"
Dir string // ".claude/<dir>" leaf: "skills" | "commands" | "agents"
}
MirrorKind names a markdown contribution kind mirrored into a workspace .claude/<dir>/ directory.
type ModeSpec ¶
type ModeSpec struct {
// InjectFlag, when set, is inserted right after the binary name in the
// produced rewrite (e.g. rtk "ultra" → insert "--ultra-compact").
InjectFlag string `yaml:"inject_flag"`
}
ModeSpec is the per-mode transform applied to a successful rewrite.
type Plugin ¶
type Plugin struct {
Manifest Manifest
// Builtin is true for plugins embedded in the binary.
Builtin bool
// Dir is the absolute install directory for an installed plugin; "" for a
// builtin (its files live in the embedded FS).
Dir string
// Enabled is the resolved enable state (operator state || default_enabled).
Enabled bool
// contains filtered or unexported fields
}
Plugin is a loaded plugin: its manifest plus where it came from and how to read its bundled files (skills).
func LoadDir ¶ added in v1.0.0
LoadDir reads a plugin from an arbitrary directory — a tree fetched from a git remote rather than installed under the iterion home.
It accepts both shapes an operator can host in a repository: a full plugin.yaml, or a bare skills/ library (no manifest), for which it synthesizes a skills-only manifest exactly like `iterion plugin install` does. name seeds the synthesized manifest and is ignored when the directory carries its own manifest.
Enabled is left to the caller: a PluginSource carries its own enable state, which is the authority for a git-hosted plugin (default_enabled belongs to the artifact, not to the operator's binding).
func (*Plugin) HookFragments ¶
HookFragments reads and JSON-decodes the plugin's contributed hook settings fragments. Each fragment is a settings.json shape: either {"hooks": {...}} or the bare {<Event>: [...]} map. The returned maps are the hooks map only.
func (*Plugin) MirrorFiles ¶
func (p *Plugin) MirrorFiles(kind MirrorKind) ([]SkillFile, error)
MirrorFiles reads the plugin's contributed files for a markdown kind.
func (*Plugin) SkillFiles ¶
SkillFiles reads the plugin's contributed skill files (back-compat shorthand).
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the loaded set of plugins (builtins + installed) with resolved enable state. It is read-mostly; SetEnabled / Remove mutate persisted state.
func Load ¶
Load builds a registry from the embedded builtins and the installed plugins under <iterion-home>/plugins/, applying the persisted enable state. A malformed installed plugin is skipped (logged by the caller via the returned error slice); a malformed builtin is a programming error and fails the load.
func (*Registry) ApplyConfig ¶
ApplyConfig merges submitted values over a plugin's stored config and persists. Only declared fields are accepted; a secret submitted blank keeps its prior value ("leave blank to keep"). Shared by the HTTP config handler and the `iterion plugin config` CLI so both behave identically.
func (*Registry) CacheDir ¶
CacheDir returns a per-plugin cache directory under the iterion home, used to expand the {{plugin.cache}} placeholder.
func (*Registry) DetailFor ¶ added in v0.43.0
DetailFor builds the full detail projection for one plugin. The README is read from the plugin's own file tree — the install dir for an installed plugin, the embedded FS for a builtin (builtins ship without READMEs today, which yields an empty string through the same lookup, not a skipped path).
func (*Registry) EffectiveConfig ¶
EffectiveConfig returns the named plugin's config as it is actually used: the manifest field defaults overlaid with the operator's stored values. This is the map fed to {{config.<key>}} expansion (it includes secret values, so the MCP/rewriter subprocess gets the real credential).
func (*Registry) Enabled ¶
Enabled returns the enabled plugins, sorted by name (stable chain order).
func (*Registry) EnabledRewriterSpecs ¶
func (r *Registry) EnabledRewriterSpecs() []RewriterSpec
EnabledRewriterSpecs returns the enabled plugins' rewriter specs with their {{config.<key>}} placeholders resolved from each plugin's effective config. {{command}} and {{workspace}}/{{plugin.*}} are left untouched for the rewrite/sandbox layers. This is how operator config reaches a rewriter's invoke env/argv — rewriters run per shell command and carry resolved env, unlike the mcp/lifecycle surfaces which expand via ExpandContext at run time.
func (*Registry) EnabledRewriters ¶
func (r *Registry) EnabledRewriters() []RewriterContribution
EnabledRewriters returns the rewriter contributions of all enabled plugins, in stable plugin-name order — this is the rewrite chain applied to commands.
func (*Registry) ExpandContextFor ¶
func (r *Registry) ExpandContextFor(name, workspace string) ExpandContext
ExpandContextFor builds an ExpandContext for the named plugin in a workspace.
func (*Registry) InstallDir ¶
InstallDir returns the directory an installed plugin lives in (or would).
func (*Registry) Remove ¶
Remove deletes an installed plugin's directory and clears its state. Builtin plugins cannot be removed (disable them instead).
func (*Registry) SetConfig ¶
SetConfig persists the operator config values for a plugin (replacing any prior values) and updates the in-memory registry. Empty values are dropped so the field falls back to its manifest default.
func (*Registry) SetEnabled ¶
SetEnabled persists an enable/disable decision for a plugin and updates the in-memory state. It errors if the plugin is unknown.
func (*Registry) StoredConfig ¶
StoredConfig returns a copy of the operator-set values for a plugin (no defaults), for the config handler's merge logic.
type RewriterContribution ¶
type RewriterContribution struct {
Plugin string
Spec RewriterSpec
}
RewriterContribution pairs a rewriter spec with its owning plugin name.
type RewriterInfo ¶ added in v0.43.0
type RewriterInfo struct {
ID string `json:"id"`
SandboxMount string `json:"sandbox_mount,omitempty"`
TimeoutMS int `json:"timeout_ms,omitempty"`
Argv []string `json:"argv,omitempty"`
}
RewriterInfo is the detail-facing projection of a RewriterSpec.
type RewriterSpec ¶
type RewriterSpec struct {
// ID is the rewriter's id within the chain (usually equals the plugin name
// for a single-rewriter plugin).
ID string `yaml:"id"`
// Locate resolves the binary path.
Locate LocateSpec `yaml:"locate"`
// Invoke describes the subprocess contract.
Invoke InvokeSpec `yaml:"invoke"`
// SandboxMount, when set, is the in-container path the host binary is
// bind-mounted to for sandboxed runs (e.g. /usr/local/bin/rtk).
SandboxMount string `yaml:"sandbox_mount"`
}
RewriterSpec declares a command-output rewriter backed by an external binary. It fully captures, declaratively, what was hardcoded for rtk: how to locate the binary, how to invoke it, which exit codes mean "apply the rewrite", and the per-mode transform of the produced rewrite.
func (*RewriterSpec) ApplyExitCodesOrDefault ¶
func (r *RewriterSpec) ApplyExitCodesOrDefault() []int
ApplyExitCodesOrDefault returns the configured apply exit codes, defaulting to {0} when none are declared.
type View ¶
type View struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Description string `json:"description,omitempty"`
Author string `json:"author,omitempty"`
Enabled bool `json:"enabled"`
Builtin bool `json:"builtin"`
Kinds []string `json:"kinds"`
// ConfigSchema is the plugin's declared config fields (empty when the plugin
// has no config block). ConfigValues carries the current values for the
// NON-secret fields; ConfigSecretSet names the secret fields that currently
// have a value (their value is never sent to the studio). The registry fills
// the value-bearing fields (Plugin.View only knows the schema).
ConfigSchema []ConfigField `json:"config_schema,omitempty"`
ConfigValues map[string]string `json:"config_values,omitempty"`
ConfigSecretSet []string `json:"config_secret_set,omitempty"`
}
View is the listing-facing projection of a plugin (name, enable state, and which contribution kinds it provides). Shared by the CLI and the HTTP server so both render plugins identically without an import cycle.