commands

package
v1.189.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

* ChatCLI - Slash command catalog (resolution + fingerprint cache) * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0

* ChatCLI - Slash command catalog (types) * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * A slash command is a reusable, parameterized PROMPT TEMPLATE stored as a * markdown file and invoked as "/<name> [args]". It differs from a skill on * purpose: a skill is knowledge injected as system-prompt guidance ("how we * do X here"), sticky and subject to aging; a command is an action ("do X * now with these args") whose body BECOMES the user turn, consumed once. * * Because expansion happens before the request is built, commands are * provider-agnostic by construction — no native tool calling, no * provider-specific API surface — and work identically on every surface * (REPL chat/coder, one-shot -p, gateway, ACP, MCP, scheduler).

* ChatCLI - Slash command execution mode * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * A command body is a prompt template, but WHERE that prompt should run is * not always the surface the user typed it on: a command whose body drives * tools is useless as a plain chat turn (chat mode is tool-less by design * and the model refuses). The execution mode captures the author's intent * so the dispatcher can route the invocation to the right engine.

* ChatCLI - Slash command file parser * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0

* ChatCLI - Slash command template expansion * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Pure text interpolation — no eval, no reflection, provider-agnostic by * construction. Placeholders cover every interop dialect so foreign files * work unchanged: * * $ARGUMENTS — the raw argument string, verbatim (Claude Code, Codex, * opencode) * {{args}} — Gemini CLI / Qwen Code alias for the same * $1 … $9 — positional arguments; KEY=value tokens are excluded * $KEY — Codex named arguments, passed as KEY=value / KEY="v v" * $$ — a literal '$' * * Execution surfaces, all delegated to the caller-supplied gated runner * (this package never touches os/exec): * * ! cmd — whole line replaced by the command's output * !{cmd} — Gemini inline form, substituted in place * !`cmd` — opencode inline form, substituted in place * * Fenced code blocks are never executed, on any of the three forms.

* ChatCLI - Minimal TOML subset parser for Gemini/Qwen command files * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Gemini CLI (and its Qwen Code fork) define custom commands as TOML files * whose useful surface is exactly two string keys: * * description = "one line" * prompt = """ * multi-line template * """ * * ChatCLI has no TOML dependency and the house rule is to avoid adding one * for a two-key format — this parser covers the string shapes those files * actually use (basic "..." with escapes, literal '...', and their * triple-quoted multi-line forms) and rejects anything it cannot parse * UNAMBIGUOUSLY: a malformed file surfaces in /config commands diagnostics * instead of loading with mangled content. Unknown keys with parseable * values are skipped; tables ([section]) end the scan — Gemini command * files do not use them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Expand

func Expand(cmd *Command, args string, runner ExecRunner) string

Expand interpolates args into the command body and resolves pre-execution lines through runner. A nil runner disables pre-execution: the lines are replaced by the denial marker (fail-safe for surfaces with no gate).

func PreExecLines

func PreExecLines(cmd *Command, args string) []string

PreExecLines returns the shell commands the template would run — whole "!" lines plus every inline !{…}/!`…` occurrence — after interpolation. Surfaced to the user before any approval prompt so they approve what will actually execute, not the template text.

Types

type Catalog

type Catalog struct {
	// contains filtered or unexported fields
}

Catalog loads and serves the slash-command set. Reads are served from an in-memory snapshot guarded by a stat-only fingerprint of the source dirs (same discipline as the session corpus cache): the walk+parse cost is paid only when a file actually changed, not per lookup — lookups happen on every REPL dispatch and every ACP/MCP listing.

func NewCatalog

func NewCatalog(projectDir, globalDir string, isReserved func(string) bool, logger *zap.Logger) *Catalog

NewCatalog builds a catalog. globalDir is ~/.chatcli/commands (created lazily); projectDir may be empty when no project root was detected. isReserved may be nil (no shadowing protection — tests only).

func (*Catalog) Dirs

func (c *Catalog) Dirs() []string

Dirs exposes the scanned directories (existing or not) for /config commands and the knowledge-graph fingerprint.

func (*Catalog) Get

func (c *Catalog) Get(token string) *Command

Get resolves one command by invocation token ("name" or "ns:name"). Returns nil when unknown.

func (*Catalog) Invalidate

func (c *Catalog) Invalidate()

Invalidate forces a re-scan on the next read (wired to /reload).

func (*Catalog) List

func (c *Catalog) List() []*Command

List returns every loaded command sorted by invocation name.

func (*Catalog) Refused

func (c *Catalog) Refused() map[string]string

Refused returns the name→path map of commands rejected for shadowing built-ins (diagnostics).

func (*Catalog) SetHomeDir

func (c *Catalog) SetHomeDir(dir string)

SetHomeDir re-roots the global interop directories (~/.codex/prompts, ~/.gemini/commands, …). Production uses the real home; hermetic tests point this at a temp dir so the developer's actual prompt libraries can never leak into assertions.

func (*Catalog) SetProjectDir

func (c *Catalog) SetProjectDir(dir string)

SetProjectDir re-roots the project-scoped directories (mirrors the skill loader's SetProjectDir; called when the CLI detects the project root).

func (*Catalog) Skipped

func (c *Catalog) Skipped() map[string]string

Skipped returns the path→reason map of files that failed to parse (diagnostics for /config commands).

type Command

type Command struct {
	// Name is the bare invocation token without slash or namespace
	// ("review-pr"). Derived from the filename, overridable by frontmatter.
	Name string
	// Namespace comes from the subdirectory ("frontend/deploy.md" →
	// namespace "frontend", invoked as /frontend:deploy). Empty for
	// top-level files.
	Namespace string
	// Description is shown in completers, palettes, ACP availableCommands
	// and MCP prompt listings.
	Description string
	// ArgumentHint documents the expected arguments ("<pr-number> [focus]").
	ArgumentHint string
	// Model / Effort optionally route the expanded turn, reusing the same
	// cross-provider hint plumbing manual skills use.
	Model  string
	Effort string
	// AllowedTools restricts which tools the model may call during the run
	// this command initiates (agent/coder surfaces). Enforced as an
	// ephemeral overlay on the security-gate check: a tool outside the
	// list escalates to an interactive ask, it is never silently allowed.
	AllowedTools []string
	// Mode is the raw `mode:` frontmatter value ("chat" | "coder", empty
	// when absent). It declares which surface the command is written for;
	// resolution — including the allowed-tools inference for files that
	// never declare a mode — lives in ResolvedMode.
	Mode string
	// Content is the template body (frontmatter stripped).
	Content string
	// Path is the absolute file path (diagnostics + /config listing).
	Path string
	// Source records which directory family won for this name.
	Source Source
}

Command is one loaded slash command.

func (*Command) InvocationName

func (c *Command) InvocationName() string

InvocationName is the full user-facing token: "name" or "ns:name".

func (*Command) ResolvedMode added in v1.185.0

func (c *Command) ResolvedMode() ExecutionMode

ResolvedMode decides where this command wants to run. An explicit `mode:` always wins; without one, declaring allowed-tools is taken as intent to run with tools (interop files from Claude Code and friends carry allowed-tools but have no mode key). Everything else stays chat.

type ExecRunner

type ExecRunner func(command string) (output string, ok bool)

ExecRunner runs one pre-execution shell line. ok=false means the command was denied (by policy or by the user) — the line is replaced by a denial marker so the model knows the output is missing rather than empty.

type ExecutionMode added in v1.185.0

type ExecutionMode string

ExecutionMode says which surface a command's expanded body targets.

const (
	// ExecModeChat runs the expanded body as a plain conversational turn.
	ExecModeChat ExecutionMode = "chat"
	// ExecModeCoder runs the expanded body through the coder ReAct loop
	// (one-shot when triggered from chat: execute, then return).
	ExecModeCoder ExecutionMode = "coder"
)

func ParseExecutionMode added in v1.185.0

func ParseExecutionMode(raw string) ExecutionMode

ParseExecutionMode maps a raw frontmatter value to a mode. Tolerant by the same contract as the rest of the frontmatter: an unknown or empty value yields "" (no opinion) so resolution falls back to inference — a bad `mode:` never invalidates the command file.

type Source

type Source string

Source identifies which directory family a command was loaded from. Kept as human-readable strings: they surface verbatim in /config commands.

const (
	// SourceProject is <project>/.chatcli/commands — versioned with the
	// repo, shared with the team. Highest precedence.
	SourceProject Source = "project"
	// SourceGlobal is ~/.chatcli/commands — the user's personal commands.
	SourceGlobal Source = "global"
	// SourceClaude is <project>/.claude/commands — Claude Code interop:
	// teams that already keep commands there get them in ChatCLI with zero
	// migration.
	SourceClaude Source = "claude-interop"
	// SourceDevin is <project>/.devin/workflows — Devin workflow interop.
	SourceDevin Source = "devin-interop"
	// SourceWindsurf is <project>/.windsurf/workflows — Windsurf Cascade
	// workflows (same family as Devin's since the Cognition merge).
	SourceWindsurf Source = "windsurf-interop"
	// SourceCursor is .cursor/commands (project) and ~/.cursor/commands
	// (personal library) — plain markdown commands.
	SourceCursor Source = "cursor-interop"
	// SourceOpencode is .opencode/commands (project) and
	// ~/.config/opencode/commands (global) — markdown with frontmatter.
	SourceOpencode Source = "opencode-interop"
	// SourceCodex is ~/.codex/prompts — OpenAI Codex custom prompts.
	// Global-only and top-level-only by that CLI's own contract.
	SourceCodex Source = "codex-interop"
	// SourceGemini is .gemini/commands (project) and ~/.gemini/commands
	// (global) — TOML files with prompt/description keys.
	SourceGemini Source = "gemini-interop"
	// SourceQwen is .qwen/commands / ~/.qwen/commands — Qwen Code, a
	// Gemini CLI fork with the same TOML shape.
	SourceQwen Source = "qwen-interop"
	// SourceCopilot is <project>/.github/prompts/*.prompt.md — GitHub
	// Copilot prompt files.
	SourceCopilot Source = "copilot-interop"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL