From 71b0cf9490467fd290d2dde2fde1d4805e54ed1a Mon Sep 17 00:00:00 2001 From: vikingowl Date: Tue, 7 Apr 2026 02:18:12 +0200 Subject: [PATCH] =?UTF-8?q?feat(skill):=20TUI=20integration=20=E2=80=94=20?= =?UTF-8?q?/skillname=20invokes=20skills,=20/skills=20lists=20them?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tui/app.go | 55 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/internal/tui/app.go b/internal/tui/app.go index 3b0d812..a88002e 100644 --- a/internal/tui/app.go +++ b/internal/tui/app.go @@ -21,6 +21,7 @@ import ( "charm.land/lipgloss/v2" gnomacfg "somegit.dev/Owlibou/gnoma/internal/config" "somegit.dev/Owlibou/gnoma/internal/elf" + "somegit.dev/Owlibou/gnoma/internal/skill" "somegit.dev/Owlibou/gnoma/internal/engine" "somegit.dev/Owlibou/gnoma/internal/message" "somegit.dev/Owlibou/gnoma/internal/permission" @@ -63,6 +64,7 @@ type Config struct { ElfProgress <-chan elf.Progress // elf → TUI: structured progress updates SessionStore *session.SessionStore // nil = no persistence StartWithResumePicker bool // open session picker on launch + Skills *skill.Registry // nil = no skills loaded } type Model struct { @@ -811,10 +813,61 @@ func (m Model) handleCommand(cmd string) (tea.Model, tea.Cmd) { case "/help": m.messages = append(m.messages, chatMessage{role: "system", - content: "Commands:\n /init generate or update AGENTS.md project docs\n /clear, /new clear chat and start new conversation\n /config show current config\n /incognito toggle incognito (Ctrl+X)\n /model [name] list/switch models\n /permission [mode] set permission mode (Shift+Tab to cycle)\n /provider show current provider\n /resume [id] list or restore saved sessions\n /shell interactive shell (coming soon)\n /help show this help\n /quit exit gnoma"}) + content: "Commands:\n /init generate or update AGENTS.md project docs\n /clear, /new clear chat and start new conversation\n /config show current config\n /incognito toggle incognito (Ctrl+X)\n /model [name] list/switch models\n /permission [mode] set permission mode (Shift+Tab to cycle)\n /provider show current provider\n /resume [id] list or restore saved sessions\n /skills list loaded skills\n /shell interactive shell (coming soon)\n /help show this help\n /quit exit gnoma\n\nSkills (use / [args] to invoke):\n Add .md files with YAML front matter to .gnoma/skills/ or ~/.config/gnoma/skills/"}) + return m, nil + + case "/skills": + if m.config.Skills == nil || len(m.config.Skills.Names()) == 0 { + m.messages = append(m.messages, chatMessage{role: "system", content: "No skills loaded."}) + return m, nil + } + var b strings.Builder + b.WriteString("Loaded skills:\n") + for _, sk := range m.config.Skills.All() { + b.WriteString(fmt.Sprintf(" /%s", sk.Frontmatter.Name)) + if sk.Frontmatter.Description != "" { + b.WriteString(fmt.Sprintf(" — %s", sk.Frontmatter.Description)) + } + b.WriteString(fmt.Sprintf(" [%s]\n", sk.Source)) + } + m.messages = append(m.messages, chatMessage{role: "system", content: b.String()}) return m, nil default: + // Check skill registry before returning unknown command error. + if m.config.Skills != nil { + sk := m.config.Skills.Get(command[1:]) // strip leading / + if sk != nil { + args := strings.Join(parts[1:], " ") + rendered, err := sk.Render(skill.TemplateData{ + Args: args, + Cwd: m.cwd, + ProjectRoot: gnomacfg.ProjectRoot(), + }) + if err != nil { + m.messages = append(m.messages, chatMessage{role: "error", + content: fmt.Sprintf("skill %q: %v", sk.Frontmatter.Name, err)}) + return m, nil + } + // Display the invocation in chat, then submit the rendered prompt. + display := command + if args != "" { + display += " " + args + } + m.messages = append(m.messages, chatMessage{role: "user", content: display}) + m.streaming = true + m.currentRole = "assistant" + m.streamBuf.Reset() + m.thinkingBuf.Reset() + m.streamFilterClose = "" + if err := m.session.Send(rendered); err != nil { + m.messages = append(m.messages, chatMessage{role: "error", content: err.Error()}) + m.streaming = false + return m, nil + } + return m, m.listenForEvents() + } + } m.messages = append(m.messages, chatMessage{role: "error", content: fmt.Sprintf("unknown command: %s (try /help)", command)}) return m, nil