Vectrum
Sign In
93 lines · 3.6 KB · Go
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: "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()
}