Skip to main content

From Assistant to Agent: A Paradigm Shift

Most AI tools for the command line work as assistants: you ask, they answer. ChatCLI goes further, transforming the AI into an autonomous agent that doesn’t just respond, but acts. The Plugin System and Agentic AI bring this vision to life:
  • You: Define the objective and provide the tools (plugins)
  • The Agent: Orchestrates execution, connecting perception, reasoning, and action to solve complex problems
This is not just a feature — it is the foundation for a new way of interacting with your development environment.

Plugin System Architecture

Automatic Discovery and Loading

ChatCLI uses an intelligent plugin manager that:
1

Monitors the directory

Monitors ~/.chatcli/plugins/ using fsnotify
2

Detects changes

Detects changes in real time (file creation, modification, removal)
3

Applies debounce

Applies a 500ms debounce to avoid multiple reloads
4

Validates the contract

Validates each plugin’s contract before loading it
5

Reloads automatically

Reloads automatically without needing to restart ChatCLI
// Events that trigger hot reload:
// - Write:  Existing file modified
// - Create: New plugin added
// - Remove: Plugin deleted
// - Rename: Plugin renamed

Remote Plugins (Server-Side)

When connected to a server via chatcli connect, the client automatically discovers plugins available on the server. These plugins appear in /plugin list with the [remote] tag and are executed on the server via gRPC — no need to install anything locally.
> /plugin list
Installed Plugins (4):
  @coder          - Complete engineering suite               [builtin]
  @hello          - Example plugin                          [local]
  @k8s-diagnose   - K8s cluster diagnostics                 [remote]
  @dockerhub      - Query Docker Hub tags                   [remote]
The agent can use remote plugins the same way as local plugins — execution is transparent. When disconnecting, remote plugins are automatically removed from the listing.

Builtin Plugins

Some essential plugins come embedded in the ChatCLI binary and appear with the [builtin] tag. The main one is @coder, which provides the engineering suite for the /coder mode. Builtin plugins require no installation and cannot be uninstalled. If you install a custom version in ~/.chatcli/plugins/ with the same name, it takes precedence over the builtin.

Flexible Plugin Lookup

The system accepts both invocation forms:
/agent @hello world
Internally, the manager normalizes automatically:
func (m *Manager) GetPlugin(name string) (Plugin, bool) {
    p, ok := m.plugins[name]
    if !ok {
        p, ok = m.plugins["@"+name]  // Automatic fallback
    }
    return p, ok
}

Agent Configuration

Environment Variables

Configure agent behavior through environment variables:
VariableTypeDefaultDescription
CHATCLI_AGENT_PLUGIN_MAX_TURNSinteger50Maximum number of ReAct cycle iterations. Prevents infinite loops (max: 200).
CHATCLI_AGENT_PLUGIN_TIMEOUTduration15mTime limit for each plugin execution. Accepts Go format (30s, 5m, 1h).
CHATCLI_AGENT_CMD_TIMEOUTduration10mTimeout for shell commands executed via @command (max: 1h).
CHATCLI_AGENT_DENYLISTstring-Regular expressions separated by ; to block dangerous commands.
CHATCLI_AGENT_ALLOW_SUDObooleanfalseAllows sudo commands without automatic blocking (use with caution).
CHATCLI_AGENT_PARALLEL_MODEbooleanfalseEnables multi-agent orchestration with parallel agents.
CHATCLI_AGENT_MAX_WORKERSinteger4Maximum workers (goroutines) running agents in parallel.
CHATCLI_AGENT_WORKER_MAX_TURNSinteger10Maximum turns of the mini ReAct loop for each worker agent.
CHATCLI_AGENT_WORKER_TIMEOUTduration5mTimeout per individual worker agent.
When CHATCLI_AGENT_PARALLEL_MODE=true, the orchestrator LLM can dispatch 12 specialist agents (FileAgent, CoderAgent, ShellAgent, GitAgent, SearchAgent, PlannerAgent, ReviewerAgent, TesterAgent, RefactorAgent, DiagnosticsAgent, FormatterAgent, DepsAgent) in parallel. See the full documentation.

The ReAct Cycle: Reasoning and Action

The AgentMode implements the ReAct (Reasoning and Acting) framework, a transparent iterative loop:
1

Reasoning (Thought)

The agent analyzes the objective and verbalizes its plan:
<pensamento>
O objetivo e analisar a performance de uma funcao Go.
Isso requer profiling. Olhando minhas ferramentas, vejo
@go-bench-gen e @go-bench-run. O primeiro passo logico
e gerar o arquivo de benchmark.
</pensamento>
2

Action (Tool Call)

The AI formalizes its decision in a structured call:
<tool_call name="@go-bench-gen" args="main.go MinhaFuncao" />
3

Execution (Plugin Invocation)

ChatCLI intercepts and executes the plugin:
Agent is using tool: @go-bench-gen main.go MinhaFuncao
   Configured timeout: 15m
   Directory: /home/user/projeto
4

Observation (Feedback)

The result is formatted and returned to the AI:
--- Tool Result ---
Generated file: main_bench_test.go
Benchmark created: BenchmarkMinhaFuncao
5

Reiteration

The cycle restarts until the objective is achieved or the turn limit is reached.

Plugin Management with /plugin

Available Commands

CommandDescription
/plugin listLists all installed plugins with metadata
/plugin install <url>Installs a plugin from a Git repository (compiled languages)
/plugin show <name>Displays description and usage syntax
/plugin inspect <name>Shows raw metadata, path, and permissions
/plugin uninstall <name>Removes a plugin from the system
/plugin reloadForces a manual reload (rarely needed)

Usage Example

> /plugin list
Installed Plugins (3):
  @go-bench-gen  - Generates Go benchmark files
  @go-bench-run  - Runs benchmarks and profiling
  @dockerhub     - Queries Docker Hub tags

Installing Plugins

> /plugin install https://github.com/user/chatcli-plugin-k8s.git
You are about to install third-party code that will be executed on your machine. Review the source code before proceeding.
Repository: https://github.com/user/chatcli-plugin-k8s.git
Confirm installation? (y/N): y

Cloning repository...
Go project detected, compiling...
Plugin @k8s installed successfully!

Creating Plugins: The Complete Guide

The Plugin Contract

Every plugin must follow these rules:
1

Be an Executable

  • Compiled binary (Go, Rust, C++) or
  • Script with shebang (#!/usr/bin/env python3, #!/bin/bash)
  • Located in ~/.chatcli/plugins/
  • Execute permission required (chmod +x)
ls -l ~/.chatcli/plugins/
-rwxr-xr-x  1 user  staff  2.3M  my-plugin   # Correct (x = executable)
-rw-r--r--  1 user  staff  1.8M  other       # Missing execute permission
2

Respond to the --metadata Contract (Required)

When invoked with --metadata, the plugin MUST print valid JSON to stdout:
{
  "name": "@my-plugin",
  "description": "Clear description of what the plugin does",
  "usage": "@my-plugin <arg1> [--flag]",
  "version": "1.0.0"
}
All fields are required:
  • name: Must start with @
  • description: Used by the AI to decide when to use the tool
  • usage: Invocation syntax
  • version: Semantic versioning
3

Implement --schema (Optional, but Recommended)

The schema helps the AI understand the plugin’s parameters:
{
  "parameters": [
    {
      "name": "cluster-name",
      "type": "string",
      "required": true,
      "description": "Kubernetes cluster name"
    },
    {
      "name": "namespace",
      "type": "string",
      "required": false,
      "default": "default",
      "description": "Target namespace"
    }
  ]
}
4

Communication via Standard I/O

ChannelUsageDescription
stdoutResultMain output sent to the AI
stderrLogs/ProgressStatus messages, warnings, and errors
stdinData inputLarge text blocks (e.g., generated code)
argsParametersCommand-line arguments
Golden Rule: stdout for the final result only, stderr for everything else (logs, progress, errors).

Complete Example: @hello Plugin in Go

This example demonstrates all best practices:
// ~/.chatcli/plugins-src/hello/main.go
package main

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

// Metadata defines the structure for --metadata
type Metadata struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    Usage       string `json:"usage"`
    Version     string `json:"version"`
}

// Schema defines the structure for --schema
type Schema struct {
    Parameters []Parameter `json:"parameters"`
}

type Parameter struct {
    Name        string `json:"name"`
    Type        string `json:"type"`
    Required    bool   `json:"required"`
    Description string `json:"description"`
    Default     string `json:"default,omitempty"`
}

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

func main() {
    // Discovery flags
    metadataFlag := flag.Bool("metadata", false, "Displays plugin metadata")
    schemaFlag := flag.Bool("schema", false, "Displays parameter schema")
    flag.Parse()

    // Respond to --metadata
    if *metadataFlag {
        meta := Metadata{
            Name:        "@hello",
            Description: "Example plugin that demonstrates the stdout/stderr flow",
            Usage:       "@hello [your-name]",
            Version:     "1.0.0",
        }
        jsonMeta, _ := json.Marshal(meta)
        fmt.Println(string(jsonMeta)) // stdout for ChatCLI
        return
    }

    // Respond to --schema
    if *schemaFlag {
        schema := Schema{
            Parameters: []Parameter{
                {
                    Name:        "name",
                    Type:        "string",
                    Required:    false,
                    Description: "Name of the person to greet",
                    Default:     "World",
                },
            },
        }
        jsonSchema, _ := json.Marshal(schema)
        fmt.Println(string(jsonSchema)) // stdout for ChatCLI
        return
    }

    // Main plugin logic
    logf("Plugin 'hello' started!\n") // stderr = visible progress

    time.Sleep(2 * time.Second) // Simulate work
    logf("   Performing a time-consuming task...\n")
    time.Sleep(2 * time.Second)

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

    logf("Task completed!\n") // stderr = progress feedback

    // Final result to stdout (will be sent to the AI)
    fmt.Printf("Hello, %s! The current time is %s.", name, time.Now().Format(time.RFC1123))
}

Compilation and Installation

1

Compile

cd ~/.chatcli/plugins-src/hello
go build -o hello main.go
2

Grant execute permission (CRITICAL!)

chmod +x hello
3

Move to the plugins directory

mv hello ~/.chatcli/plugins/
4

Verify installation

> /plugin list
Installed Plugins (1):
  @hello  - Example plugin that demonstrates the stdout/stderr flow

Testing the Plugin

# Inside ChatCLI
> /agent @hello Edilson
# Terminal output (stderr):
Plugin 'hello' started!
   Performing a time-consuming task...
Task completed!
The AI responds based on the plugin’s stdout. Example: “The plugin returned: Hello, Edilson! The current time is Mon, 02 Jan 2024 14:30:00 UTC.”

Debugging Plugins

Run /plugin list. If the plugin does not appear:
  1. Check permissions: ls -l ~/.chatcli/plugins/ — Must show -rwxr-xr-x (with ‘x’)
  2. Test the --metadata contract: ~/.chatcli/plugins/your-plugin --metadata — Must return valid JSON
  3. Enable debug logs in .env:
LOG_LEVEL=debug
ENV=dev
Before using in the agent, test directly:
  • Test metadata: ~/.chatcli/plugins/your-plugin --metadata
  • Test schema: ~/.chatcli/plugins/your-plugin --schema
  • Test execution: ~/.chatcli/plugins/your-plugin arg1 arg2
If the plugin is being interrupted:
  • Increase timeout globally: export CHATCLI_AGENT_PLUGIN_TIMEOUT=30m
  • Or in .env: CHATCLI_AGENT_PLUGIN_TIMEOUT=30m

Advanced Example: Docker Hub Plugin

This example demonstrates integration with an external API:
// chatcli-plugin-dockerhub/main.go
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
    "time"
)

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

type DockerHubResponse struct {
    Results []struct {
        Name string `json:"name"`
    } `json:"results"`
}

func main() {
    if len(os.Args) > 1 && os.Args[1] == "--metadata" {
        meta := Metadata{
            Name:        "@dockerhub",
            Description: "Queries available tags of an image on Docker Hub",
            Usage:       "@dockerhub <image>",
            Version:     "1.0.0",
        }
        jsonMeta, _ := json.Marshal(meta)
        fmt.Println(string(jsonMeta))
        return
    }

    if len(os.Args) < 2 {
        fmt.Fprintln(os.Stderr, "Error: Image name is required.")
        fmt.Fprintln(os.Stderr, "Usage: @dockerhub <image>")
        os.Exit(1)
    }

    imageName := os.Args[1]
    fmt.Fprintf(os.Stderr, "Querying tags for '%s'...\n", imageName)

    // Docker Hub API call
    url := fmt.Sprintf("https://hub.docker.com/v2/repositories/library/%s/tags?page_size=25", imageName)
    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(url)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Request error: %v\n", err)
        os.Exit(1)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    var apiResponse DockerHubResponse
    if err := json.Unmarshal(body, &apiResponse); err != nil {
        fmt.Fprintf(os.Stderr, "Response parsing error: %v\n", err)
        os.Exit(1)
    }

    // Extract tags
    var tags []string
    for _, result := range apiResponse.Results {
        tags = append(tags, result.Name)
    }

    fmt.Fprintf(os.Stderr, "%d tags found\n", len(tags))

    // Final result to stdout (for the AI)
    fmt.Println(strings.Join(tags, "\n"))
}

Use Case

> /agent deploy the latest alpine version of redis
The agent will:
  1. Use @dockerhub redis to list tags
  2. Filter tags with “alpine”
  3. Select the most recent version
  4. Run docker run redis:<alpine-tag>
  5. Validate that the container is running

Supported Languages

Any language that can create an executable, interact with standard I/O (stdin/stdout/stderr), and process command-line arguments.

Recommendations by Use Case

LanguageBest ForAdvantages
GoProduction pluginsStatic binaries, fast, portable
RustCritical performanceMemory safety, speed
PythonRapid prototypingRich ecosystem, easy debugging
BashSystem scriptsNative shell integration
Node.jsAPI integrationsNPM, async/await

Security and Best Practices

Input Validation

Always validate arguments before processing. Use os.Exit(1) to signal errors to ChatCLI.

Error Handling

A non-zero exit code signals an error to ChatCLI. Send error messages via stderr.

Internal Timeouts

Use context.WithTimeout to prevent external operations from blocking the plugin indefinitely.

Informative Logs

Send progress via stderr so the user can follow the plugin’s execution in real time.
// Input Validation
if len(os.Args) < 2 {
    fmt.Fprintln(os.Stderr, "Error: Insufficient arguments")
    os.Exit(1)
}

// Error Handling
if err != nil {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1) // Exit code != 0 signals error to ChatCLI
}

// Internal Timeouts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

Plugins in /coder Mode

The /coder mode is specialized in software engineering and uses the @coder plugin to execute its actions. @coder is a builtin plugin — it comes embedded in ChatCLI and works without installation. In /coder, the AI emits tool calls in a strict format:
  • First, it writes a short reasoning block (2 to 6 lines)
  • Then, it emits only one tool_call with JSON args
Examples of actual calls (that the AI emits in /coder):
<tool_call name="@coder" args='{"cmd":"tree","args":{"dir":"."}}'/>
<tool_call name="@coder" args='{"cmd":"read","args":{"file":"cli/agent_mode.go"}}'/>
<tool_call name="@coder" args='{"cmd":"test","args":{"dir":"."}}'/>
See more at Coder Mode and Plugin @coder.

Next Steps

Plugin Examples

Explore the example plugins in the repository

Create Your First Plugin

Follow the @hello template on this page to get started

Share with the Community

Publish plugins on GitHub for the ChatCLI ecosystem

Contribute

Contribute plugins to the ChatCLI ecosystem

The plugin system is your gateway to true automation. Start building your tools and transform your terminal into a teammate.