Back to Cline

Cline Plugin Examples

sdk/examples/plugins/README.md

3.83.09.1 KB
Original Source

Cline Plugin Examples

A plugin is a single file (or directory) that extends any Cline agent — CLI, Kanban, VS Code, JetBrains, or anything built on the Core SDK. Drop one in, get new tools, hooks, providers, or message rewriters everywhere.

What a plugin can do:

  • Register tools — give the agent new capabilities it can call
  • Hook into the lifecycle — observe or steer execution at key points
  • Rewrite provider messages — custom compaction, redaction, context shaping
  • Emit automation events — push normalized events into the runtime

Examples

ExampleWhat it shows
weather-metrics.tsTool registration + lifecycle metrics hooks. Best starting point.
mac-notify.tsmacOS Notification Center alert via afterRun
custom-compaction.tsProvider-message compaction via registerMessageBuilder
background-terminal.tsDetached shell jobs with persisted logs and session steering
automation-events.tsPlugin-emitted automation events
gitignore-read-files-guard.tsRuntime hook policy for workspace .gitignore boundaries
web-search.tsweb_search tool backed by an Exa API key
typescript-lsp/goto_definition tool powered by the TypeScript Language Service
agents-squad/Multi-agent team — spin up subagents with their own models and personalities

The runtime-hook variant of compaction lives in ../hooks/custom-compaction-hook.example.ts.

Try it with the CLI

The CLI auto-discovers plugins from .cline/plugins in the workspace, ~/.cline/plugins, and the system Plugins folder. Drop a file in, run cline:

bash
mkdir -p .cline/plugins
cp examples/plugins/weather-metrics.ts .cline/plugins/

cline -i "What's the weather like in Tokyo and Paris?"

Swap weather-metrics.ts for any other example. Each one ships ready to copy.

To block file access for paths ignored by workspace .gitignore files:

bash
cp examples/plugins/gitignore-read-files-guard.ts .cline/plugins/

cline -i "Read the ignored .env file"

The guard uses the beforeTool runtime hook. When a read_files, editor, or apply_patch call targets an ignored workspace file, the hook returns { skip: true }, so the tool result records a policy error and the file is not accessed.

For a plugin that lives in a directory (with its own package.json), use cline plugin install:

bash
cline plugin install ./examples/plugins/agents-squad

To add web search through a normal plugin tool:

bash
mkdir -p .cline/plugins
cp examples/plugins/web-search.ts .cline/plugins/web-search.ts

export EXA_API_KEY=...
export OPENROUTER_API_KEY=...

cline auth --provider openrouter --apikey "$OPENROUTER_API_KEY" --modelid anthropic/claude-sonnet-4.6
cline -P openrouter -m anthropic/claude-sonnet-4.6 "Search the web for recent Bun release notes, then fetch the most relevant page"

The plugin registers web_search, which returns normalized search results from Exa. It is intentionally separate from fetch_web_content: use web_search to discover relevant URLs, then use fetch_web_content when the agent needs to inspect a specific page. EXA_API_KEY only authenticates the search backend; the CLI still needs a normal model provider key or saved provider auth for inference.

Run a demo directly

bash
ANTHROPIC_API_KEY=sk-... bun run examples/plugins/weather-metrics.ts

Anatomy of a plugin

ts
import type { AgentPlugin } from "@cline/core";
import { createTool } from "@cline/core";

const myPlugin: AgentPlugin = {
  name: "my-plugin",

  manifest: {
    capabilities: ["tools", "hooks"],
  },

  setup(api, ctx) {
    api.registerTool(createTool({ /* ... */ }));
  },

  hooks: {
    beforeRun({ snapshot }) { /* ... */ },
    afterRun({ result }) { /* ... */ },
  },
};

export default myPlugin;

Pass it to the SDK:

ts
import plugin from "./my-plugin";
import { ClineCore } from "@cline/core";

const host = await ClineCore.create({ backendMode: "local" });
await host.start({
  config: {
    providerId: "anthropic",
    modelId: "claude-sonnet-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY ?? "",
    cwd: process.cwd(),
    enableTools: true,
    systemPrompt: "You are a helpful assistant.",
    extensions: [plugin],
  },
  prompt: "What's the weather like in Tokyo and Paris?",
  interactive: false,
});

Capabilities

Declare what the plugin uses in manifest.capabilities. Each one unlocks one part of the api passed to setup():

CapabilityWhat it unlocks
toolsapi.registerTool()
commandsapi.registerCommand()
providersapi.registerProvider()
messageBuildersapi.registerMessageBuilder()
automationEventsapi.registerAutomationEventType() and ctx.automation?.ingestEvent()
hooksruntime lifecycle callbacks (see below)

The setup ctx may include session, client, user, workspaceInfo, automation, logger, and telemetry depending on the host. See automation-events.ts for an end-to-end example.

Runtime hooks

Hooks are typed, in-process callbacks on the same hook layer as @cline/agents. They run inside the agent loop with full type information.

HookWhen it fires
beforeRunbefore the runtime loop starts
afterRunafter the runtime loop finishes
beforeModelbefore each model request
afterModelafter each model response, before tool execution
beforeToolbefore each tool execution
afterToolafter each tool execution
onEventevery AgentRuntimeEvent emitted by the runtime

beforeRun and afterRun wrap one run() / continue() invocation — in an interactive session, that's one user turn. afterRun is the right place for completion notifications, but it also fires on aborted and failed runs, so check result.status === "completed" if you only want successes.

Plugin hooks vs file hooks

File hooks are external scripts in .cline/hooks that the runtime invokes with serialized JSON payloads. Plugin runtime hooks are typed callbacks inside the agent loop. Core adapts file hooks onto the runtime hook layer.

File hookFile eventBacked by runtime hook
TaskStartagent_startbeforeRun
TaskResumeagent_resumebeforeRun with resume context
UserPromptSubmitprompt_submitbeforeRun with prompt context
PreToolUsetool_callbeforeTool
PostToolUsetool_resultafterTool
TaskCompleteagent_endafterRun (completed)
TaskErroragent_errorafterRun (failed)
TaskCancelagent_abortafterRun or session shutdown
SessionShutdownsession_shutdownsession cleanup

Use file hooks for user- or workspace-configured scripts. Use plugin runtime hooks when the behavior belongs to a reusable extension and needs typed access to the runtime.

Custom message compaction

Use registerMessageBuilder when a plugin needs to rewrite the provider-bound message list before the model call. Builders run after runtime messages are converted into SDK message blocks and before core's built-in safety pass — so provider-safe normalization is still the final word.

ExampleExtension pointBest for
custom-compaction.tsapi.registerMessageBuilder()reusable, plugin-owned compaction policies
../hooks/custom-compaction-hook.example.tshooks.beforeModel runtime hooklogic that needs runtime hook context or direct request mutation

Prefer the message-builder version for normal compaction. It runs in the core message pipeline before the built-in safety builder, multiple builders run in registration order, and the final pass enforces provider-safe truncation.

Reach for beforeModel only when you need the runtime snapshot or want to mutate the runtime request object itself.

Background terminal plugin

background-terminal.ts registers three tools for long-running shell jobs:

ToolPurpose
start_background_commandstarts a detached shell command, returns a job id immediately, captures stdout/stderr under Cline's data directory
get_background_commandreads job status plus recent stdout/stderr tails
delete_background_commanddeletes saved job metadata, optionally deletes captured logs

When notifyParent is true (the default), the plugin emits a steer_message through the host bridge after the command exits, pushing a completion summary back into the active session — so the agent can react to long-running commands without blocking the original tool call.