> For the complete documentation index, see [llms.txt](https://www.opencodebook.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://www.opencodebook.xyz/en/chapter_14_skill_system/14.3_command_system.md).

# 14.3 Command System

> **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.

## 14.3.1 Custom Slash Command Mechanism

Slash Commands start with `/`. Users type commands like `/init` or `/review` in the TUI input box to trigger predefined operations.

The Command data model:

```typescript
// 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
})
```

### Template Variables

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.

```markdown
# Template example for the /review command
Review the changes in ${path}.
$1 defaults to uncommitted changes.
Focus on: $ARGUMENTS
```

The `hints()` function extracts all variables from the template, used to display parameter hints in the TUI:

```typescript
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
}
```

## 14.3.2 Built-in Commands

OpenCode includes two built-in commands:

```typescript
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's `AGENTS.md` file (project specification document).
* **`/review`**: Has the Agent review code changes. `subtask: true` means this command creates a sub-session for execution, without affecting the current session's context.

## 14.3.3 Three Command Sources

The Command system unifies commands from three different sources:

### User-Configured Commands

Defined through the `command` field in `opencode.json`:

```json
{
  "command": {
    "test": {
      "description": "Run and fix failing tests",
      "template": "Run `npm test` in ${path}. If any tests fail, fix them. $ARGUMENTS",
      "agent": "build"
    }
  }
}
```

### MCP Prompt Commands

Prompts exposed by MCP Servers are automatically registered as commands:

```typescript
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.

### Skill Commands

Each Skill is also registered as a command (unless a command with the same name already exists):

```typescript
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.

## 14.3.4 Command Priority

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.

## 14.3.5 Command Events

When a command is executed, an event is published:

```typescript
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.

## 14.3.6 Summary

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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://www.opencodebook.xyz/en/chapter_14_skill_system/14.3_command_system.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
