> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin System

> Extend ChatCLI with custom plugins. Create, install, and manage executables that add new commands.

ChatCLI supports a plugin system to extend its functionality. A plugin is an **executable** that follows a specific contract, allowing ChatCLI to discover, execute, and interact with it securely.

This lets you create custom commands (such as `@kind`, `@deploy`) that can orchestrate tools, interact with APIs, or perform any logic you can program.

***

## For Users: Managing Plugins

### List installed plugins

```bash theme={"system"}
/plugin list
```

Shows all available plugin commands, including `[builtin]` plugins (such as `@coder`) and `[remote]` plugins (from connected servers).

### Install a plugin

Install directly from a Git repository:

```bash theme={"system"}
/plugin install https://github.com/usuario/meu-plugin-chatcli.git
```

ChatCLI will clone, compile (if it's Go), and install the executable in `~/.chatcli/plugins/`.

<Warning>
  **Security:** Installing a plugin involves downloading and executing third-party code. Only install plugins from sources you trust.
</Warning>

### View plugin details

```bash theme={"system"}
/plugin show <plugin-name>
```

### Uninstall a plugin

```bash theme={"system"}
/plugin uninstall <plugin-name>
```

### Reload plugins

ChatCLI automatically monitors `~/.chatcli/plugins/` and **reloads when it detects changes** (creation, removal, modification). A 500ms debounce prevents multiple reloads.

To force a manual reload:

```bash theme={"system"}
/plugin reload
```

<Tip>
  Develop plugins iteratively: edit the code, recompile, send it to the plugins directory — ChatCLI will detect the change automatically.
</Tip>

***

## For Developers: Creating a Plugin

### The plugin contract

1. **Executable** — The plugin must be an executable file (any language)
2. **Location** — Placed in `~/.chatcli/plugins/`
3. **Command name** — The file name becomes the command. E.g., file `kind` = command `@kind`
4. **Metadata (`--metadata`)** — Required. The executable must respond to this flag with JSON:

```json theme={"system"}
{
  "name": "@meu-comando",
  "description": "A brief description of what the plugin does.",
  "usage": "@meu-comando <subcommand> [--flag value]",
  "version": "1.0.0"
}
```

5. **Schema (`--schema`)** — Optional. Describes the accepted parameters:

```json theme={"system"}
{
  "parameters": [
    {
      "name": "cluster-name",
      "type": "string",
      "required": true,
      "description": "Kubernetes cluster name"
    }
  ]
}
```

6. **Communication (stdout vs stderr)**:
   * **stdout** — Only the final result (returned to ChatCLI/AI)
   * **stderr** — Progress logs, status, and warnings (displayed in real time to the user)

***

### Example: "Hello World" Plugin in Go

```go theme={"system"}
package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "os"
    "time"
)

type Metadata struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    Usage       string `json:"usage"`
    Version     string `json:"version"`
}

// logf sends progress messages to the user (via stderr).
func logf(format string, v ...interface{}) {
    fmt.Fprintf(os.Stderr, format, v...)
}

func main() {
    metadataFlag := flag.Bool("metadata", false, "Exibe os metadados do plugin")
    schemaFlag := flag.Bool("schema", false, "Exibe o schema de parâmetros")
    flag.Parse()

    if *metadataFlag {
        meta := Metadata{
            Name:        "@hello",
            Description: "Plugin de exemplo que demonstra o fluxo stdout/stderr.",
            Usage:       "@hello [seu-nome]",
            Version:     "1.0.0",
        }
        jsonMeta, _ := json.Marshal(meta)
        fmt.Println(string(jsonMeta))
        return
    }

    if *schemaFlag {
        schema := map[string]interface{}{
            "parameters": []map[string]interface{}{
                {
                    "name":        "nome",
                    "type":        "string",
                    "required":    false,
                    "description": "Nome da pessoa a ser cumprimentada",
                    "default":     "Mundo",
                },
            },
        }
        jsonSchema, _ := json.Marshal(schema)
        fmt.Println(string(jsonSchema))
        return
    }

    // Lógica principal
    logf("Plugin 'hello' iniciado!\n")
    time.Sleep(2 * time.Second)
    logf("   - Realizando uma tarefa demorada...\n")
    time.Sleep(2 * time.Second)

    name := "Mundo"
    if len(flag.Args()) > 0 {
        name = flag.Args()[0]
    }

    logf("Tarefa concluída!\n")

    // Resultado final vai para stdout
    fmt.Printf("Olá, %s! A hora agora é %s.", name, time.Now().Format(time.RFC1123))
}
```

### Compilation and installation

```bash theme={"system"}
# 1. Compile
go build -o hello ./hello/main.go

# 2. Make it executable
chmod +x hello

# 3. Create the plugins directory (if it doesn't exist)
mkdir -p ~/.chatcli/plugins/

# 4. Move the executable
mv hello ~/.chatcli/plugins/

# 5. Use in ChatCLI
/agent Hello, my name is John
```

***

## Capability Interfaces (opt-in)

Plugins can expose capabilities through optional Go interfaces. Legacy plugins that don't implement them keep working unchanged — every interface is fail-closed (conservative default). When implemented, the plugin participates in orchestrator optimizations:

| Interface              | Method                                             | Purpose                                                                                                                    |
| ---------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `ReadOnlyAware`        | `IsReadOnly(args []string) bool`                   | Enables the capability security gate (auto-allow) and participation in parallel batches                                    |
| `ConcurrencySafeAware` | `IsConcurrencySafe(args []string) bool`            | Signals that multiple calls can run in parallel                                                                            |
| `DescriberWithInput`   | `DescribeCall(args []string) string`               | Dynamic spinner label ("Reading: main.go" instead of "RUNNING: @read")                                                     |
| `JSONSchemaAware`      | `JSONSchema() string`                              | draft-2020-12 schema validated before dispatch — invalid input becomes `ToolResult{IsError:true, ErrorCode:"InvalidArgs"}` |
| `TruncationAware`      | `MaxResultChars() int`                             | Per-tool output cap (versus the 30 000 global default)                                                                     |
| `Prompter`             | `Prompt(opts PromptOpts) (string, error)`          | Contextual system-prompt slice                                                                                             |
| `StreamingInputAware`  | `UpdateStreamingInput(field, value string)`        | Partial-input events as the LLM streams args                                                                               |
| `StructuredExecutor`   | `ExecuteStructured(...) (StructuredResult, error)` | Typed return in place of the legacy `(string, error)`                                                                      |

Example — a plugin that wants auto-allow + parallelization + custom label:

```go theme={"system"}
func (p *MyReadOnlyPlugin) IsReadOnly(_ []string) bool        { return true }
func (p *MyReadOnlyPlugin) IsConcurrencySafe(_ []string) bool { return true }
func (p *MyReadOnlyPlugin) DescribeCall(args []string) string {
    return i18n.T("plugins.myplugin.describe", extractTarget(args))
}
func (p *MyReadOnlyPlugin) JSONSchema() string {
    return `{"type":"object","properties":{"file":{"type":"string"}},"required":["file"]}`
}
```

The four atomic plugins (`@read`, `@search`, `@tree`, `@todo`) — see [Atomic Tools](/features/atomic-tools) — use every relevant capability as a reference implementation.

***

## Signature Verification

Starting with this version, plugins require **Ed25519 digital signatures** by default. This ensures that only plugins from trusted sources are loaded and executed.

### How It Works

Each plugin must have a corresponding `.sig` file in the same directory:

```text theme={"system"}
~/.chatcli/plugins/
  my-plugin          # plugin executable
  my-plugin.sig      # Ed25519 signature of the executable
```

ChatCLI verifies the signature against registered public keys before loading the plugin. If verification fails, the plugin is rejected.

### Managing Trusted Keys

Ed25519 public keys are stored in the `~/.chatcli/trusted-keys/` directory:

<Steps>
  <Step title="Generate a key pair">
    ```bash theme={"system"}
    # Generate private and public keys
    chatcli plugin keygen --name my-org
    # Creates: ~/.chatcli/trusted-keys/my-org.pub
    # Creates: ~/.chatcli/signing-keys/my-org.key (private - protect it!)
    ```
  </Step>

  <Step title="Sign a plugin">
    ```bash theme={"system"}
    chatcli plugin sign --key ~/.chatcli/signing-keys/my-org.key my-plugin
    # Creates: my-plugin.sig
    ```
  </Step>

  <Step title="Distribute the public key">
    Share the `.pub` file with users who should trust your plugins. They should place it in `~/.chatcli/trusted-keys/`.
  </Step>
</Steps>

### File Permissions

The plugins directory uses **0o700** permissions (owner-only read, write, and execute). ChatCLI checks permissions on startup and warns if they are more permissive.

### Development Mode

For local development, you can disable signature verification:

```bash theme={"system"}
export CHATCLI_ALLOW_UNSIGNED_PLUGINS=true
chatcli
```

<Warning>
  **Never** enable `CHATCLI_ALLOW_UNSIGNED_PLUGINS` in production. Unsigned plugins can execute arbitrary code with the ChatCLI process permissions.
</Warning>

***

## Remote Plugins

When connecting to a server via `chatcli connect`, server plugins are discovered automatically:

* They appear in `/plugin list` with the `[remote]` tag
* They are executed on the server (not downloaded locally by default)
* Local and remote plugins coexist without conflict
