205 lines
5.6 KiB
Go
205 lines
5.6 KiB
Go
package pluginpkg
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// InstalledNames returns installed plugin package names sorted for completion
|
|
// menus and lightweight management views.
|
|
func InstalledNames(reasonixHome string) ([]string, error) {
|
|
st, err := LoadState(reasonixHome)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, 0, len(st.Plugins))
|
|
for _, p := range st.Plugins {
|
|
names = append(names, p.Name)
|
|
}
|
|
sort.Strings(names)
|
|
return names, nil
|
|
}
|
|
|
|
// InstalledListText returns a compact session-facing view of installed plugins.
|
|
func InstalledListText(reasonixHome string) (string, error) {
|
|
st, err := LoadState(reasonixHome)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(st.Plugins) == 0 {
|
|
return "plugins: none installed\ninstall: reasonix plugin install <source> --yes, or use Settings -> Plugins", nil
|
|
}
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "plugins (%d):\n", len(st.Plugins))
|
|
for _, p := range st.Plugins {
|
|
state := "disabled"
|
|
if p.Enabled {
|
|
state = "enabled"
|
|
}
|
|
summary := p.Description
|
|
counts := pluginCapabilityText(reasonixHome, p)
|
|
if summary != "" && counts != "" {
|
|
summary = counts + " - " + oneLine(summary)
|
|
} else if counts != "" {
|
|
summary = counts
|
|
} else if summary != "" {
|
|
summary = oneLine(summary)
|
|
}
|
|
if summary != "" {
|
|
fmt.Fprintf(&b, " %s [%s] - %s\n", p.Name, state, summary)
|
|
} else {
|
|
fmt.Fprintf(&b, " %s [%s]\n", p.Name, state)
|
|
}
|
|
}
|
|
b.WriteString("show details: /plugins show <name>")
|
|
return strings.TrimRight(b.String(), "\n"), nil
|
|
}
|
|
|
|
// InstalledShowText returns the usage-oriented details for one installed plugin.
|
|
func InstalledShowText(reasonixHome, name string) (string, error) {
|
|
p, ok, err := FindInstalled(reasonixHome, name)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !ok {
|
|
return fmt.Sprintf("plugin %q is not installed", name), nil
|
|
}
|
|
root := ResolveRoot(reasonixHome, p.Root)
|
|
pkg, warnings, err := ParseDir(root)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
skills, commands, hooks, mcp := pkg.CapabilityCounts()
|
|
state := "disabled"
|
|
if p.Enabled {
|
|
state = "enabled"
|
|
}
|
|
version := p.Version
|
|
if version == "" {
|
|
version = "-"
|
|
}
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "plugin %s [%s]\n", p.Name, state)
|
|
fmt.Fprintf(&b, "version: %s\nkind: %s\nroot: %s\nsource: %s\ncapabilities: %d skills, %d commands, %d hooks, %d MCP servers\n", version, p.ManifestKind, filepath.Clean(root), p.Source, skills, commands, hooks, mcp)
|
|
if p.Enabled {
|
|
b.WriteString("usage: enabled plugins load into new sessions; use /skills, invoke /<plugin>:<skill> or /<plugin>:<command>, or ask naturally.\n")
|
|
} else {
|
|
b.WriteString("usage: enable this plugin before its skills, commands, hooks, or MCP servers participate in sessions.\n")
|
|
}
|
|
appendInventoryText(&b, p.Name, pkg.Inventory())
|
|
for _, warning := range warnings {
|
|
fmt.Fprintf(&b, "warning: %s\n", warning)
|
|
}
|
|
return strings.TrimRight(b.String(), "\n"), nil
|
|
}
|
|
|
|
func FindInstalled(reasonixHome, name string) (InstalledPlugin, bool, error) {
|
|
st, err := LoadState(reasonixHome)
|
|
if err != nil {
|
|
return InstalledPlugin{}, false, err
|
|
}
|
|
for _, p := range st.Plugins {
|
|
if p.Name == name {
|
|
return p, true, nil
|
|
}
|
|
}
|
|
return InstalledPlugin{}, false, nil
|
|
}
|
|
|
|
func pluginCapabilityText(reasonixHome string, p InstalledPlugin) string {
|
|
root := ResolveRoot(reasonixHome, p.Root)
|
|
pkg, _, err := ParseDir(root)
|
|
if err != nil {
|
|
return "invalid: " + err.Error()
|
|
}
|
|
skills, commands, hooks, mcp := pkg.CapabilityCounts()
|
|
parts := []string{}
|
|
if skills > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d skills", skills))
|
|
}
|
|
if commands > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d commands", commands))
|
|
}
|
|
if hooks > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d hooks", hooks))
|
|
}
|
|
if mcp > 0 {
|
|
parts = append(parts, fmt.Sprintf("%d MCP", mcp))
|
|
}
|
|
if len(parts) == 0 {
|
|
return "no exported capabilities"
|
|
}
|
|
return strings.Join(parts, " / ")
|
|
}
|
|
|
|
func appendInventoryText(b *strings.Builder, pluginName string, inv Inventory) {
|
|
if len(inv.Commands) > 0 {
|
|
b.WriteString("commands:\n")
|
|
for _, cmd := range inv.Commands {
|
|
desc := oneLine(cmd.Description)
|
|
if desc == "" {
|
|
desc = "(no description)"
|
|
}
|
|
invocation := "/" + pluginName + ":" + cmd.Name
|
|
if cmd.ArgHint != "" {
|
|
fmt.Fprintf(b, " %s %s - %s\n", invocation, cmd.ArgHint, desc)
|
|
} else {
|
|
fmt.Fprintf(b, " %s - %s\n", invocation, desc)
|
|
}
|
|
}
|
|
}
|
|
if len(inv.Skills) > 0 {
|
|
b.WriteString("skills:\n")
|
|
for _, sk := range inv.Skills {
|
|
desc := oneLine(sk.Description)
|
|
if desc == "" {
|
|
desc = "(no description)"
|
|
}
|
|
invocation := "/" + pluginName + ":" + sk.Name
|
|
if sk.RunAs != "" {
|
|
fmt.Fprintf(b, " %s [%s] - %s\n", invocation, sk.RunAs, desc)
|
|
} else {
|
|
fmt.Fprintf(b, " %s - %s\n", invocation, desc)
|
|
}
|
|
}
|
|
}
|
|
if len(inv.Hooks) > 0 {
|
|
b.WriteString("hooks:\n")
|
|
for _, hook := range inv.Hooks {
|
|
target := hook.Command
|
|
if target == "" {
|
|
target = hook.ContextFile
|
|
}
|
|
match := hook.Match
|
|
if match == "" {
|
|
match = "*"
|
|
}
|
|
desc := oneLine(hook.Description)
|
|
if desc != "" {
|
|
fmt.Fprintf(b, " %s match=%s - %s - %s\n", hook.Event, match, target, desc)
|
|
} else {
|
|
fmt.Fprintf(b, " %s match=%s - %s\n", hook.Event, match, target)
|
|
}
|
|
}
|
|
}
|
|
if len(inv.MCPServers) > 0 {
|
|
b.WriteString("mcpServers:\n")
|
|
for _, server := range inv.MCPServers {
|
|
target := server.Command
|
|
if target == "" {
|
|
target = server.URL
|
|
}
|
|
fmt.Fprintf(b, " %s [%s] - %s\n", server.Name, server.Transport, target)
|
|
}
|
|
}
|
|
if len(inv.Skills) == 0 && len(inv.Commands) == 0 && len(inv.Hooks) == 0 && len(inv.MCPServers) == 0 {
|
|
b.WriteString("capabilities: no detailed inventory available\n")
|
|
}
|
|
}
|
|
|
|
func oneLine(s string) string {
|
|
return strings.Join(strings.Fields(s), " ")
|
|
}
|