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

# Writing Plugins

> Build plugins that add tools, observe execution, and modify agent behavior.

Plugins are the primary way to package reusable agent capabilities. This guide walks through building a production-quality plugin from scratch.

## What You'll Build

A GitHub integration plugin that:

* Registers tools for interacting with GitHub (list issues, create PRs, post comments)
* Logs all tool calls for auditing
* Tracks token usage per session

## Step 1: Define the Plugin Structure

```typescript theme={"system"}
// github-plugin.ts
import { type AgentPlugin } from "@cline/sdk"
import { createTool } from "@cline/sdk"

interface GitHubConfig {
  token: string
  owner: string
  repo: string
}

export function createGitHubPlugin(config: GitHubConfig): AgentPlugin {
  let totalTokens = 0

  return {
    name: "github-integration",
    manifest: {
      capabilities: ["tools", "hooks"],
    },

    setup(api, ctx) {
      // Register tools in the setup phase
      api.registerTool(createListIssuesTool(config))
      api.registerTool(createCreateIssueTool(config))
      api.registerTool(createPostCommentTool(config))
    },

    hooks: {
      beforeRun() {
        console.log(`[github] Run started`)
      },

      beforeTool(context) {
        console.log(`[github] Tool: ${context.toolCall.name}(${JSON.stringify(context.input).slice(0, 100)})`)
      },

      afterRun(context) {
        const usage = context.result.usage
        totalTokens += (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
        console.log(`[github] Run complete. Session tokens so far: ${totalTokens}`)
      },
    },
  }
}
```

## Step 2: Create the Tools

```typescript theme={"system"}
function createListIssuesTool(config: GitHubConfig) {
  return createTool({
    name: "list_github_issues",
    description: `List open issues in ${config.owner}/${config.repo}. Returns issue numbers, titles, labels, and assignees.`,
    inputSchema: {
      type: "object",
      properties: {
        state: {
          type: "string",
          enum: ["open", "closed", "all"],
          description: "Issue state filter. Default: open.",
        },
        labels: {
          type: "string",
          description: "Comma-separated label names to filter by (e.g., 'bug,priority:high').",
        },
        limit: {
          type: "number",
          description: "Maximum issues to return. Default: 10, max: 100.",
        },
      },
    },
    execute: async (input) => {
      const params = new URLSearchParams({
        state: input.state ?? "open",
        per_page: String(Math.min(input.limit ?? 10, 100)),
      })
      if (input.labels) params.set("labels", input.labels)

      const response = await fetch(
        `https://api.github.com/repos/${config.owner}/${config.repo}/issues?${params}`,
        { headers: { Authorization: `token ${config.token}` } }
      )

      const issues = await response.json()
      return {
        issues: issues.map((i: Record<string, unknown>) => ({
          number: i.number,
          title: i.title,
          state: i.state,
          labels: (i.labels as Array<{ name: string }>).map((l) => l.name),
          assignee: (i.assignee as { login: string } | null)?.login,
          createdAt: i.created_at,
        })),
        total: issues.length,
      }
    },
  })
}

function createCreateIssueTool(config: GitHubConfig) {
  return createTool({
    name: "create_github_issue",
    description: `Create a new issue in ${config.owner}/${config.repo}.`,
    inputSchema: {
      type: "object",
      properties: {
        title: { type: "string", description: "Issue title" },
        body: { type: "string", description: "Issue body (Markdown supported)" },
        labels: {
          type: "array",
          items: { type: "string" },
          description: "Labels to apply",
        },
      },
      required: ["title"],
    },
    execute: async (input) => {
      const response = await fetch(
        `https://api.github.com/repos/${config.owner}/${config.repo}/issues`,
        {
          method: "POST",
          headers: {
            Authorization: `token ${config.token}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            title: input.title,
            body: input.body,
            labels: input.labels,
          }),
        }
      )

      const issue = await response.json()
      return { number: issue.number, url: issue.html_url }
    },
  })
}

function createPostCommentTool(config: GitHubConfig) {
  return createTool({
    name: "post_github_comment",
    description: `Post a comment on an issue or PR in ${config.owner}/${config.repo}.`,
    inputSchema: {
      type: "object",
      properties: {
        issueNumber: { type: "number", description: "Issue or PR number" },
        body: { type: "string", description: "Comment body (Markdown supported)" },
      },
      required: ["issueNumber", "body"],
    },
    execute: async (input) => {
      const response = await fetch(
        `https://api.github.com/repos/${config.owner}/${config.repo}/issues/${input.issueNumber}/comments`,
        {
          method: "POST",
          headers: {
            Authorization: `token ${config.token}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({ body: input.body }),
        }
      )

      const comment = await response.json()
      return { id: comment.id, url: comment.html_url }
    },
  })
}
```

## Step 3: Use the Plugin

```typescript theme={"system"}
import { Agent } from "@cline/sdk"
import { createGitHubPlugin } from "./github-plugin"

const agent = new Agent({
  providerId: "anthropic",
  modelId: "claude-sonnet-4-6",
  apiKey: process.env.ANTHROPIC_API_KEY,
  systemPrompt: "You are a project manager assistant with access to GitHub.",
  plugins: [
    createGitHubPlugin({
      token: process.env.GITHUB_TOKEN,
      owner: "my-org",
      repo: "my-project",
    }),
  ],
})

await agent.run("List all open bugs and create a summary issue with the count")
```

## Distributing as a File Plugin

To load this plugin from a file in ClineCore, pass its path in `pluginPaths`:

```typescript theme={"system"}
// /absolute/path/to/github.ts
import { type AgentPlugin, createTool } from "@cline/sdk"

const plugin: AgentPlugin = {
  name: "github",
  manifest: { capabilities: ["tools"] },
  setup(api, ctx) {
    api.registerTool(
      createTool({
        name: "list_github_issues",
        // ... tool definition
      })
    )
  },
}

export default plugin
```

Then include it in session config:

```typescript theme={"system"}
import { ClineCore } from "@cline/sdk"

const cline = await ClineCore.create({ clientName: "my-app" })

await cline.start({
  config: {
    systemPrompt: "Use the GitHub plugin",
    // ...model/runtime config
    pluginPaths: ["/absolute/path/to/github.ts"],
  },
})
```

## Distributing via CLI Install

Single-file plugins can be installed directly from a file URL:

```bash theme={"system"}
cline plugin install https://github.com/your-org/your-repo/blob/main/plugins/github-plugin.ts
```

Single-file plugins can only import Node builtins and `@cline/*`. As soon as you need an npm dependency (`zod`, an HTTP client, etc.) you must ship as a package: a directory with a `package.json` that declares a `cline` field for entry points and your runtime `dependencies`. Dependencies under the `@cline/` scope are provided by the host runtime -- the installer strips these and runs `npm install` for the rest, so declare any `@cline/*` package you import as an optional peer dependency:

```json theme={"system"}
{
  "cline": {
    "plugins": [{ "paths": ["./github-plugin.ts"], "capabilities": ["tools", "hooks"] }]
  },
  "dependencies": {
    "@octokit/rest": "^21.0.0"
  },
  "peerDependencies": {
    "@cline/sdk": "*"
  },
  "peerDependenciesMeta": {
    "@cline/sdk": {
      "optional": true
    }
  }
}
```

Users can also install package plugins from git, npm, or a local path:

```bash theme={"system"}
cline plugin install https://github.com/your-org/cline-github-plugin.git
```

## Bundling Skills

Package plugins can include skills by adding a top-level `skills/` directory next to `package.json`:

```txt theme={"system"}
cline-github-plugin/
  package.json
  github-plugin.ts
  skills/
    triage/
      SKILL.md
```

Each bundled skill follows the same directory format as any other [Cline skill](/customization/skills). When the plugin is installed or loaded through `pluginPaths`, Cline discovers those skills automatically.

See [Plugins](/customization/plugins) for the full manifest format, directory layout, and the [typescript-lsp-plugin](https://github.com/cline/typescript-lsp-plugin) for a complete working example.

## Plugin Design Guidelines

1. Use factory functions (like `createGitHubPlugin`) when the plugin needs configuration. Export the plugin object directly when it doesn't.

2. Keep `setup()` synchronous and fast. It runs before the first LLM call, so any async initialization delays the agent.

3. Register all tools in `setup()`, not in lifecycle hooks. Tools must be available before the first iteration.

4. Use lifecycle hooks for observation (logging, metrics, auditing), not for modifying agent behavior. If you need to modify behavior, consider using the `beforeRun` or `beforeModel` hooks to adjust the system prompt or context.

5. Handle errors gracefully in hooks. A thrown error in `beforeTool` will count as a tool failure. If your hook is purely observational, catch errors internally.
