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 ¶
- func Expand(cmd *Command, args string, runner ExecRunner) string
- func PreExecLines(cmd *Command, args string) []string
- type Catalog
- func (c *Catalog) Dirs() []string
- func (c *Catalog) Get(token string) *Command
- func (c *Catalog) Invalidate()
- func (c *Catalog) List() []*Command
- func (c *Catalog) Refused() map[string]string
- func (c *Catalog) SetHomeDir(dir string)
- func (c *Catalog) SetProjectDir(dir string)
- func (c *Catalog) Skipped() map[string]string
- type Command
- type ExecRunner
- type ExecutionMode
- type Source
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 ¶
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 ¶
Dirs exposes the scanned directories (existing or not) for /config commands and the knowledge-graph fingerprint.
func (*Catalog) Get ¶
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) Refused ¶
Refused returns the name→path map of commands rejected for shadowing built-ins (diagnostics).
func (*Catalog) SetHomeDir ¶
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 ¶
SetProjectDir re-roots the project-scoped directories (mirrors the skill loader's SetProjectDir; called when the CLI detects the project root).
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 ¶
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 ¶
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" )