Model: claude-opus-4-6 (anthropic/claude-opus-4-6) Generation Date: 2026-02-17
The Command system is another extension point for user interaction in OpenCode -- it allows users to define custom Slash Commands, turning frequently used prompts into templates with parameters for one-click triggering of complex operations.
Slash Commands start with /. Users type commands like /init or /review in the TUI input box to trigger predefined operations.
The Command data model:
// command/index.ts
export const Info = z.object({
name: z.string(), // Command name
description: z.string().optional(), // Description
agent: z.string().optional(), // Designated Agent
model: z.string().optional(), // Designated model
source: z.enum(["command", "mcp", "skill"]).optional(), // Source
template: z.promise(z.string()).or(z.string()), // Prompt template
subtask: z.boolean().optional(), // Whether to execute as a subtask
hints: z.array(z.string()), // Parameter hint list
})Command templates support two types of variables:
- Numbered variables (
$1,$2,$3...): Replaced positionally with user-provided arguments. $ARGUMENTS: Replaced with the user's complete argument string.
# Template example for the /review command
Review the changes in ${path}.
$1 defaults to uncommitted changes.
Focus on: $ARGUMENTSThe hints() function extracts all variables from the template, used to display parameter hints in the TUI:
export function hints(template: string): string[] {
const result: string[] = []
const numbered = template.match(/\$\d+/g)
if (numbered) {
for (const match of [...new Set(numbered)].sort()) result.push(match)
}
if (template.includes("$ARGUMENTS")) result.push("$ARGUMENTS")
return result
}OpenCode includes two built-in commands:
const Default = {
INIT: "init",
REVIEW: "review",
} as const
const result: Record<string, Info> = {
[Default.INIT]: {
name: "init",
description: "create/update AGENTS.md",
source: "command",
get template() {
return PROMPT_INITIALIZE.replace("${path}", Instance.worktree)
},
hints: hints(PROMPT_INITIALIZE),
},
[Default.REVIEW]: {
name: "review",
description: "review changes [commit|branch|pr], defaults to uncommitted",
source: "command",
get template() {
return PROMPT_REVIEW.replace("${path}", Instance.worktree)
},
subtask: true,
hints: hints(PROMPT_REVIEW),
},
}/init: Guides the Agent to create or update the project'sAGENTS.mdfile (project specification document)./review: Has the Agent review code changes.subtask: truemeans this command creates a sub-session for execution, without affecting the current session's context.
The Command system unifies commands from three different sources:
Defined through the command field in opencode.json:
{
"command": {
"test": {
"description": "Run and fix failing tests",
"template": "Run `npm test` in ${path}. If any tests fail, fix them. $ARGUMENTS",
"agent": "build"
}
}
}Prompts exposed by MCP Servers are automatically registered as commands:
for (const [name, prompt] of Object.entries(await MCP.prompts())) {
result[name] = {
name,
source: "mcp",
description: prompt.description,
get template() {
// Asynchronously fetch MCP Prompt content
return new Promise<string>(async (resolve, reject) => {
const template = await MCP.getPrompt(prompt.client, prompt.name, /* args */)
resolve(template?.messages.map(m => m.content.text).join("\n") || "")
})
},
hints: prompt.arguments?.map((_, i) => `$${i + 1}`) ?? [],
}
}Note that template uses a getter + Promise -- because MCP Prompt content needs to be fetched asynchronously from the MCP Server. The Zod Schema uses z.promise(z.string()).or(z.string()) to support both synchronous and asynchronous templates.
Each Skill is also registered as a command (unless a command with the same name already exists):
for (const skill of await Skill.all()) {
if (result[skill.name]) continue // Don't override existing commands
result[skill.name] = {
name: skill.name,
description: skill.description,
source: "skill",
get template() {
return skill.content
},
hints: [],
}
}When a user types /git-master, the complete content of the git-master Skill is sent to the Agent as a prompt. This provides a shortcut -- instead of the Agent deciding which Skill to load on its own, the user directly specifies it via a command.
When commands with the same name appear from the three sources, the priority is:
Built-in commands > User-configured commands > MCP Prompt commands > Skill commands
This priority ensures that built-in functionality cannot be accidentally overridden, while user configuration can override commands from external sources.
When a command is executed, an event is published:
export const Event = {
Executed: BusEvent.define("command.executed", z.object({
name: z.string(),
sessionID: Identifier.schema("session"),
arguments: z.string(),
messageID: Identifier.schema("message"),
})),
}This event is captured by the Plugin's command.execute.before hook, allowing Plugins to inject additional context before a command is executed.
The Command system unifies commands from three different sources (user configuration, MCP Prompts, Skills) under a single interface, providing a consistent user experience:
| Feature | User-Configured | MCP Prompt | Skill |
|---|---|---|---|
| Definition location | opencode.json |
MCP Server | SKILL.md |
| Template | Synchronous string | Asynchronous Promise | Synchronous string |
| Parameters | $1, $ARGUMENTS |
$1, $2 |
No parameters |
| Can specify Agent | Yes | No | No |
| Can specify Model | Yes | No | No |
| Subtask mode | Yes | No | No |
This unified design means users don't need to worry about where a command comes from -- whether it's a hand-written template, a Prompt provided by an MCP Server, or a Skill file, they can all be triggered with a single /command-name input.