Files
mesh-catalog/modules/claude-code/cmd/claude-code/terminal_text.go
T
jochen e139fb4dd9 claude-code: set shared agent text at the controller's terminal, for now
Instructions, output styles, skills, commands and subagents for every node
or some reach every session as the mesh's own word, and the tools cannot
tell an agent from the operator; the operator decided they are set at the
terminal until a surface of their own updates them (hq issue 340). They
are settings keys, each text a list of lines; the home scope stays free.
2026-10-09 09:41:52 +02:00

90 lines
3.1 KiB
Go

package main
// The shared text the operator sets at the controller's terminal (novox/hq issue 340, the operator's decision of
// 2026-10-09): instruction sections, output styles, skills, commands and subagents for every node or some. They are
// keys of this module's settings, which the controller takes at its terminal alone. A setting holds no line break
// (issue 339), so each text is a list of its lines; a one-line text may be a string.
import (
"encoding/json"
"errors"
"os"
"sort"
"strings"
)
// Lines is a text as a setting holds it: a list of its lines, or one line as a string.
type Lines string
// UnmarshalJSON takes a string or a list of strings, joined by line breaks.
func (l *Lines) UnmarshalJSON(raw []byte) error {
var one string
if json.Unmarshal(raw, &one) == nil {
*l = Lines(one)
return nil
}
var many []string
if err := json.Unmarshal(raw, &many); err != nil {
return errors.New("a text is a list of its lines, or one line")
}
*l = Lines(strings.Join(many, "\n") + "\n")
return nil
}
// SharedText is the text keys of this module's settings.
type SharedText struct {
Instructions map[string]Lines `json:"instructions"`
OutputStyles map[string]Lines `json:"output_styles"`
Agents map[string]Lines `json:"agents"`
Commands map[string]Lines `json:"commands"`
Skills map[string]map[string]Lines `json:"skills"`
}
// TerminalItems are the items the operator's settings hold, as the plugin and the instruction file take them, and
// what was left out and why: an item that is not well formed (a name, a path, a skill without its SKILL.md). What
// the text asks of a session is the operator's to say there, so it is not judged.
func TerminalItems(text SharedText) ([]Item, []string) {
var out []Item
var left []string
add := func(it Item) {
it.Scope, it.By = ScopeMesh, "the controller's terminal"
if problem := it.shapeProblem(); problem != "" {
left = append(left, it.Kind+" "+it.Name+": "+problem)
return
}
out = append(out, it)
}
for kind, texts := range map[string]map[string]Lines{KindInstructions: text.Instructions,
KindOutputStyle: text.OutputStyles, KindAgent: text.Agents, KindCommand: text.Commands} {
for name, body := range texts {
add(Item{Kind: kind, Name: name, Files: map[string]string{name + ".md": string(body)}})
}
}
for name, files := range text.Skills {
it := Item{Kind: KindSkill, Name: name, Files: map[string]string{}}
for rel, body := range files {
it.Files[rel] = string(body)
}
add(it)
}
sort.Slice(out, func(i, j int) bool {
if out[i].Kind != out[j].Kind {
return out[i].Kind < out[j].Kind
}
return out[i].Name < out[j].Name
})
sort.Strings(left)
return out, left
}
// terminalText reads the shared text from this module's settings file.
func terminalText(p Paths) ([]Item, []string) {
var text SharedText
if raw, err := os.ReadFile(p.Settings); err == nil {
if err := json.Unmarshal(raw, &text); err != nil {
return nil, []string{"the shared text in this module's settings could not be read: " + err.Error()}
}
}
return TerminalItems(text)
}