package tui
import (
"fmt"
"strings"
)
// command is one thing the prompt box accepts with a leading slash: either a
// control the TUI answers itself, or a skill the agent is asked to invoke.
type command struct {
name string // without the slash
args string // what may follow the name; "" when it takes none
desc string
tag string // "personal" or "project" for a skill; "" for a built-in
}
// builtins are the commands submit answers without going near the model. The
// order is the order the menu offers them in — most reached-for first, not
// alphabetical, because the first row is the one a person lands on.
//
// This list is also what /help prints and what the completion menu offers, so
// none of the three can drift away from the others.
var builtins = []command{
{name: "help", desc: "the commands and the keys"},
{name: "skills", desc: "the instruction sets found at startup"},
{name: "model", args: "id", desc: "switch model — lists what the endpoint serves"},
{name: "effort", args: "level", desc: "how hard the model thinks — opens a slider"},
{name: "setup", desc: "the full-screen wizard for the saved defaults"},
{name: "compact", desc: "fold the conversation into a summary now"},
{name: "clear", desc: "empty the transcript"},
{name: "mouse", desc: "hand the mouse back to the terminal, or take it again"},
{name: "allowed", desc: "what is permanently allowed this session"},
{name: "exit", desc: "leave the harness"},
}
// commands is the catalogue: the built-ins, then every discovered skill.
func (m *model) commands() []command {
out := make([]command, 0, len(builtins)+len(m.opts.Skills))
out = append(out, builtins...)
for _, s := range m.opts.Skills {
desc := s.Description
if desc == "" {
desc = "a skill with no description"
}
out = append(out, command{name: s.Name, args: "arguments", desc: desc, tag: s.Source})
}
return out
}
// matchCommands narrows the catalogue to what has been typed after the slash.
// A name that starts with it comes before one that merely contains it, so
// "/sk" offers /skills before a skill called "ask-the-docs".
func matchCommands(all []command, typed string) []command {
if typed == "" {
return append([]command(nil), all...)
}
var prefix, contains []command
for _, c := range all {
switch {
case strings.HasPrefix(c.name, typed):
prefix = append(prefix, c)
case strings.Contains(c.name, typed):
contains = append(contains, c)
}
}
return append(prefix, contains...)
}
// helpText is built from the catalogue rather than written out beside it.
func (m *model) helpText() string {
var b strings.Builder
b.WriteString("commands:")
for _, c := range builtins {
name := "/" + c.name
if c.args != "" {
name += " <" + c.args + ">"
}
fmt.Fprintf(&b, "\n %-18s %s", name, c.desc)
}
if n := len(m.opts.Skills); n > 0 {
fmt.Fprintf(&b, "\n %-18s %s", "/<skill>", plural(n, "discovered skill")+" — /skills lists them")
}
b.WriteString("\nmouse: wheel scrolls · drag selects and copies on release" +
"\n double-click a word · triple-click a line · shift+click extends" +
"\n click a menu row, a model, the slider or an approval to choose it" +
"\n /mouse hands the mouse back to the terminal for its own selection" +
"\nkeys: enter send · ctrl+j newline · shift+tab cycle approval mode" +
"\n ! runs a shell command · $ runs python — both on an empty prompt" +
"\n what they print goes to the agent too, so you can then say \"fix that\"" +
"\n / opens the command menu · ↑↓ move · tab or enter complete · esc dismiss" +
"\n ctrl+r toggle full tool output · pgup/pgdn scroll" +
"\n esc or ctrl+c interrupt a turn · ctrl+c again to exit")
return b.String()
}