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

# Plugins

> How the Beige plugin system works — manifest, entry point, config, lifecycle

## Plugin Package Structure

```
plugins/my-plugin/
├── plugin.json    # Manifest: name, description, what it provides
├── package.json   # Optional: npm dependencies
├── index.ts       # Entry point: exports createPlugin()
├── README.md      # User/developer documentation
└── SKILL.md       # Agent usage guide (for tool-providing plugins)
```

## plugin.json — Manifest

```json theme={null}
{
  "name": "my-plugin",
  "description": "Does something useful",
  "commands": [
    "run <arg>  — Process input",
    "status     — Show current state"
  ],
  "provides": {
    "tools": ["my-plugin"],
    "channel": false,
    "hooks": [],
    "skills": []
  }
}
```

| Field          | Required | Description                                      |
| -------------- | -------- | ------------------------------------------------ |
| `name`         | Yes      | Plugin identifier — must be unique               |
| `description`  | Yes      | Short description                                |
| `commands`     | No       | Usage hints shown in agent system prompt         |
| `provides`     | No       | Declarative listing of what this plugin provides |
| `configSchema` | No       | JSON Schema for plugin config validation         |

<Note>
  Every plugin must have a `plugin.json` manifest in its directory.
</Note>

## index.ts — Entry Point

Every plugin exports a `createPlugin` function:

```typescript theme={null}
import type { PluginContext, PluginRegistrar, PluginInstance } from "@matthias-hausberger/beige";

export function createPlugin(
  config: Record<string, unknown>,
  ctx: PluginContext
): PluginInstance {
  return {
    register(reg: PluginRegistrar) {
      // Register tools, channels, hooks, skills here
      reg.tool({
        name: "my-plugin",
        description: "Does something useful",
        commands: ["run <arg>", "status"],
        handler: async (args, _, sessionContext) => {
          const [command, ...rest] = args;
          if (command === "run") {
            return { output: `Processed: ${rest.join(" ")}`, exitCode: 0 };
          }
          return { output: `Unknown command: ${command}`, exitCode: 1 };
        },
      });
    },

    // Optional: start background processes
    async start() {
      // Called after all plugins are registered
    },

    // Optional: clean up on shutdown
    async stop() {
      // Called in reverse order on gateway shutdown
    },
  };
}
```

## Config

Plugins are configured in `config.json5` under the `plugins` key:

```json5 theme={null}
{
  plugins: {
    "my-plugin": {
      path: "./plugins/my-plugin",    // or auto-resolved for installed plugins
      config: {                        // passed to createPlugin(config, ctx)
        apiKey: "${MY_API_KEY}",
        timeout: 30,
      },
    },
  },
}
```

### Per-Agent Config Overrides

Different agents can have different plugin configs via `pluginConfigs`:

```json5 theme={null}
{
  plugins: {
    git: {
      path: "./plugins/git",
      config: { allowForcePush: false },
    },
  },
  agents: {
    safe: {
      tools: ["git"],
      // Uses base config (allowForcePush: false)
    },
    power: {
      tools: ["git"],
      pluginConfigs: {
        git: { allowForcePush: true },  // deep-merged with base config
      },
    },
  },
}
```

## Tool Naming Rules

All tools registered by a plugin **must** start with the plugin name:

| Plugin     | Allowed tool names                                            |
| ---------- | ------------------------------------------------------------- |
| `git`      | `git`                                                         |
| `telegram` | `telegram`, `telegram.send_message`, `telegram.get_chat_info` |

This is enforced at registration time. Combined with unique plugin names, it guarantees no tool name collisions.

* **Single-tool plugins**: Register a tool with the plugin name (e.g. plugin `git` → tool `git`)
* **Multi-tool plugins**: Use dot notation (e.g. plugin `telegram` → tools `telegram.send_message`, `telegram.get_chat_info`)

## PluginContext

The `PluginContext` object is provided to every plugin and gives access to gateway capabilities:

```typescript theme={null}
interface PluginContext {
  // Send a prompt to an agent session
  prompt(sessionKey: string, agentName: string, message: string, opts?): Promise<string>;
  promptStreaming(sessionKey: string, agentName: string, message: string, onDelta, opts?): Promise<string>;
  newSession(sessionKey: string, agentName: string): Promise<void>;

  // Session settings
  getSessionSettings(sessionKey: string): SessionSettings;
  updateSessionSettings(sessionKey: string, update: Partial<SessionSettings>): void;
  setSessionMetadata(sessionKey: string, key: string, value: unknown): void;
  getSessionMetadata(sessionKey: string, key: string): unknown;

  // Cross-plugin tool invocation
  invokeTool(toolName: string, args: string[], sessionContext?): Promise<ToolResult>;

  // Read-only config and info
  config: Readonly<BeigeConfig>;
  agentNames: string[];

  // Channel registry
  getChannel(name: string): ChannelAdapter | undefined;
  getRegisteredTools(): string[];

  // Logging
  log: PluginLogger;
}
```

## Installing Plugins

```bash theme={null}
# From npm
beige plugins install npm:@matthias-hausberger/beige-toolkit

# From GitHub
beige plugins install github:matthias-hausberger/beige-toolkit

# From local directory
beige plugins install ./my-plugin

# List installed plugins
beige plugins list

# Update all plugins
beige plugins update

# Remove a plugin
beige plugins remove my-plugin
```

`beige plugins install` downloads the plugin and adds it to your config.json5 with its path and default config values. Review the config to fill in any required values (like API keys).

## Auto-installation on Gateway Start

The gateway automatically installs any plugin that has a `_source` in config but whose `path` doesn't exist on disk. This means you can copy a `config.json5` from another machine and just run `beige gateway start` — all plugins will be fetched and installed before the gateway comes up.

```json5 theme={null}
// config.json5 copied from another machine — no manual install needed
plugins: {
  telegram: {
    _source: "npm:@matthias-hausberger/beige-toolkit",
    // path is missing or points to the other machine — gateway will install it
    config: { token: "${TELEGRAM_BOT_TOKEN}" },
  },
}
```

If a plugin is already installed (its `path` exists), the gateway skips it. To upgrade to a newer version after changing `_source`, run `beige plugins update` explicitly — the gateway will not reinstall a plugin that is already present on disk.
