Add Slash Commands
Your executable can add its own /commands to caretz’s interactive TUI. They
sit next to the built-in ones, show up in /help and autocomplete, and can
prompt the agent. This one turns /standup into a request for a summary of
the day’s work:
package main
import (
"context"
"os"
"github.com/makkalot/caretz/cli"
)
type standup struct{}
func (standup) Name() string { return "standup" }
func (standup) Description() string { return "Summarize today's changes for a standup" }
func (standup) Usage() string { return "/standup" }
func (standup) Aliases() []string { return nil }
func (standup) Run(ctx context.Context, session cli.CommandSession, _ string) (cli.SlashCommandResult, error) {
_, err := session.Call(ctx, "Summarize what changed in this workspace today in three bullets.")
return cli.SlashCommandResult{}, err
}
func main() {
os.Exit(cli.Run(context.Background(), os.Args[1:], os.Stdout, os.Stderr, cli.Options{
SlashCommands: []cli.CustomSlashCommand{standup{}},
}))
}
A command gets the session’s directories and channel ID, Call to prompt the
agent, and RunVerificationCommand to run a shell command in the session’s
sandbox without it showing up in the conversation. A name that clashes with a
built-in command stops startup with an error.
Give the agent tools for your command
When the agent has to answer back to your command, for example by leaving notes
that the command shows later, put those tools in a session integration and pass
its factory in cli.Options.SessionIntegrations. Caretz installs it on every
session the CLI opens and carries it over to the sessions that /clear starts.
The factory receives a sessiontools.SessionEventGateway. The gateway also has
SessionDir() and WorkspaceDir(), so the integration can share files with
your command. Integration tools follow the normal --tools and
--disable-tool policy. Their names must not clash with built-in or
cli.Options.Tools tools. Subagents and other child sessions never get
integration tools.
The *AgentSession behind the gateway also has GetContextMessages(),
AskPermission(ctx, request), ModelProvider(ctx, ref, reasoning) (another
configured model, or the current one for an empty ref) and LLMSessionID().
Assert to an interface with the methods you need. Before asking, check
sessiontools.ApprovalAvailable(ctx) so a request never waits for an answer
nobody can give.
Ask for approval your way
To let the user review what an integration is about to release, put it in
PermissionRequest.Preview and set NoSessionGrant when every request
differs. Then register a tui.PermissionPrompt for the request’s capability
in cli.Options.PermissionPrompts. Your prompt draws on the terminal in place
of the built-in Allow/Deny line. It can hand off to $EDITOR with
Terminal.RunExternal, and return an edited payload in
Metadata[eventing.PermissionMetadataPayload].
Render your messages
A tui.MessageRenderer in cli.Options.MessageRenderers gets each user and
tool message first and can draw content it recognises itself, for example a
tagged answer block your tool returns. It also gets the tool’s name: claim
only your own tools’ results, because a file the model reads or a page it
fetches can contain your tag too. A claimed tool message stays visible in
compact mode, which otherwise hides tool output. tui.NewMarkdownRenderer
renders Markdown in the active theme.
Take over the screen
In the built-in TUI a command can borrow the terminal for a full-screen view.
tui.ScreenRunnerFrom(ctx) returns nil in prompt mode, so check it before you
draw. tui.RunFullScreen drives a tui.Component on the alternate screen and
restores the conversation when it returns. tui.NewStyle colors text with the
active /theme. For code, github.com/makkalot/caretz/lib/syntax gives you
the highlighter caretz uses for diffs and fenced code.