From 83dcd4c17847e0b2bae9eeb4d4bfddf78ff1e97a Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Thu, 14 May 2026 13:06:37 -0700 Subject: [PATCH] feat: add cline-sdk skill for Claude Code agents (#10766) * feat: add cline-sdk skill for Claude Code agents Add a comprehensive skill that teaches Claude Code how to build AI agents with the Cline SDK (@cline/sdk). The skill covers the Agent runtime, ClineCore sessions, custom tools, plugins, events, LLM providers, scheduling, multi-agent teams, and production deployment. Sourced from cline/sdk-skill, installed via the skills system. * feat: add symlink for cline-sdk skill in .claude/skills --- .agents/skills/cline-sdk/SKILL.md | 208 ++++++ .../cline-sdk/references/agent/REFERENCE.md | 107 +++ .../skills/cline-sdk/references/agent/api.md | 231 +++++++ .../cline-sdk/references/agent/gotchas.md | 134 ++++ .../cline-sdk/references/agent/patterns.md | 258 +++++++ .../references/clinecore/REFERENCE.md | 131 ++++ .../cline-sdk/references/clinecore/api.md | 304 ++++++++ .../cline-sdk/references/clinecore/gotchas.md | 148 ++++ .../references/clinecore/patterns.md | 279 ++++++++ .../cline-sdk/references/events/REFERENCE.md | 269 ++++++++ .../references/multi-agent/REFERENCE.md | 157 +++++ .../cline-sdk/references/plugins/REFERENCE.md | 649 ++++++++++++++++++ .../references/production/REFERENCE.md | 253 +++++++ .../references/providers/REFERENCE.md | 257 +++++++ .../references/scheduling/REFERENCE.md | 227 ++++++ .../cline-sdk/references/tools/REFERENCE.md | 259 +++++++ .claude/skills/cline-sdk | 1 + skills-lock.json | 11 + 18 files changed, 3883 insertions(+) create mode 100644 .agents/skills/cline-sdk/SKILL.md create mode 100644 .agents/skills/cline-sdk/references/agent/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/agent/api.md create mode 100644 .agents/skills/cline-sdk/references/agent/gotchas.md create mode 100644 .agents/skills/cline-sdk/references/agent/patterns.md create mode 100644 .agents/skills/cline-sdk/references/clinecore/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/clinecore/api.md create mode 100644 .agents/skills/cline-sdk/references/clinecore/gotchas.md create mode 100644 .agents/skills/cline-sdk/references/clinecore/patterns.md create mode 100644 .agents/skills/cline-sdk/references/events/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/multi-agent/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/plugins/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/production/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/providers/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/scheduling/REFERENCE.md create mode 100644 .agents/skills/cline-sdk/references/tools/REFERENCE.md create mode 120000 .claude/skills/cline-sdk create mode 100644 skills-lock.json diff --git a/.agents/skills/cline-sdk/SKILL.md b/.agents/skills/cline-sdk/SKILL.md new file mode 100644 index 0000000000..7f7b0612df --- /dev/null +++ b/.agents/skills/cline-sdk/SKILL.md @@ -0,0 +1,208 @@ +--- +name: cline-sdk +description: Comprehensive Cline SDK skill for building AI agents. Covers the Agent runtime, ClineCore sessions, custom tools, plugins, events, LLM providers, scheduling, multi-agent teams, and production deployment. Use for any task involving @cline/sdk or its sub-packages. +metadata: + references: agent, clinecore +--- + +# Cline SDK Skill + +Consolidated skill for building AI agents with the Cline SDK. Use the decision trees below to find the right entry point and API surface, then load detailed references. + +## Critical Rules + +Follow these rules in all Cline SDK code: + +1. Install with `npm install @cline/sdk`. The `@cline/sdk` package re-exports everything from `@cline/core`, `@cline/agents`, `@cline/llms`, and `@cline/shared`. +2. Requires Node.js 22 or later. +3. Use `createTool()` from `@cline/sdk` (or `@cline/shared`) to define tools. Tool names must be `snake_case`. +4. Return errors as structured data from tool `execute` functions. Throwing counts as a "mistake" against the agent's mistake limit. +5. Use `lifecycle: { completesRun: true }` on tools that should end the agent loop (e.g. a "submit answer" tool). +6. When using `ClineCore`, always call `dispose()` when done to clean up resources. +7. The standalone `Agent` and `ClineCore` have different event systems. For `Agent`: use `agent.subscribe()` to get `AgentRuntimeEvent` types (text streaming is `"assistant-text-delta"`, result text is `result.outputText`). For `ClineCore`: use `cline.subscribe()` to get `CoreSessionEvent` types (text streaming is `"chunk"` with `payload.type === "text"`, result text is `result.text`). There is no top-level `onEvent` field on `AgentRuntimeConfig` -- use `agent.subscribe()` or `hooks.onEvent` instead. Do not use event types like `"content_update"` or `"content_start"` with `agent.subscribe()` -- those are internal legacy types from the ClineCore adapter layer. + +## How to Use This Skill + +### Reference File Structure + +The two main API surfaces (`Agent` and `ClineCore`) follow a 4-file pattern. Cross-cutting concepts are single-file guides. + +Each main API surface in `./references//` contains: + +| File | Purpose | When to Read | +|------|---------|--------------| +| `REFERENCE.md` | Overview, when to use, quick start | Always read first | +| `api.md` | Full API: classes, methods, config, types | Writing code | +| `patterns.md` | Common patterns, best practices | Implementation guidance | +| `gotchas.md` | Pitfalls, limitations, debugging | Troubleshooting | + +Cross-cutting concepts in `./references//` have `REFERENCE.md` as the entry point. + +### Reading Order + +1. Start with `REFERENCE.md` for your chosen API surface +2. Then read additional files relevant to your task: + - Writing agent code -> `api.md` + - Common patterns -> `patterns.md` + - Creating tools -> `tools/REFERENCE.md` + - Adding plugins/hooks -> `plugins/REFERENCE.md` + - Configuring LLM providers -> `providers/REFERENCE.md` + - Streaming events -> `events/REFERENCE.md` + - Deploying to production -> `production/REFERENCE.md` + - Scheduling agents -> `scheduling/REFERENCE.md` + - Multi-agent orchestration -> `multi-agent/REFERENCE.md` + - Debugging -> `gotchas.md` + +### Example Paths + +``` +./references/agent/REFERENCE.md # Start here for lightweight agents +./references/clinecore/REFERENCE.md # Start here for full runtime +./references/agent/api.md # Agent class, config, methods +./references/tools/REFERENCE.md # Creating and using tools +./references/plugins/REFERENCE.md # Plugin system +./references/providers/REFERENCE.md # LLM provider configuration +``` + +## Quick Decision Trees + +### "Which API surface should I use?" + +``` +Which API? ++-- I want a simple, stateless agent with custom tools +| +-- agent/ (Agent class from @cline/agents) ++-- I need session persistence, built-in tools, config discovery +| +-- clinecore/ (ClineCore from @cline/core) ++-- I want built-in file/shell/search/web tools +| +-- clinecore/ (has built-in tools; Agent does not) ++-- I want scheduled or recurring agents +| +-- clinecore/ (automation API) ++-- I need multi-process or multi-client session sharing +| +-- clinecore/ (hub-backed runtime) ++-- I'm building a browser-compatible agent +| +-- agent/ (no Node.js dependencies) +``` + +### "I need to create tools" + +``` +Tools? ++-- Define a custom tool with schema -> tools/REFERENCE.md ++-- Use built-in tools (bash, editor, read_files) -> tools/REFERENCE.md (built-in section) ++-- Control tool approval/policies -> tools/REFERENCE.md (policies section) ++-- Tool that ends the agent loop -> tools/REFERENCE.md (completion tools) ++-- Package tools as a reusable plugin -> plugins/REFERENCE.md +``` + +### "I need to handle events" + +``` +Events? ++-- Stream text/reasoning in real time -> events/REFERENCE.md ++-- Track token usage and costs -> events/REFERENCE.md ++-- Watch tool calls -> events/REFERENCE.md ++-- Detect completion/errors -> events/REFERENCE.md ++-- Hook into lifecycle stages -> plugins/REFERENCE.md +``` + +### "I need to configure a model provider" + +``` +Providers? ++-- Anthropic (Claude) -> providers/REFERENCE.md ++-- OpenAI (GPT) -> providers/REFERENCE.md ++-- Google (Gemini/Vertex) -> providers/REFERENCE.md ++-- AWS Bedrock -> providers/REFERENCE.md ++-- Mistral -> providers/REFERENCE.md ++-- OpenAI-compatible (vLLM, Together, etc.) -> providers/REFERENCE.md ++-- Custom/self-hosted provider -> providers/REFERENCE.md +``` + +### "I need plugins or hooks" + +``` +Plugins? ++-- Package tools + hooks together -> plugins/REFERENCE.md ++-- Observe tool calls (logging, metrics) -> plugins/REFERENCE.md ++-- Intercept lifecycle events -> plugins/REFERENCE.md ++-- Add system prompt rules -> plugins/REFERENCE.md ++-- Distribute via npm/git -> plugins/REFERENCE.md +``` + +### "I need multi-agent coordination" + +``` +Multi-agent? ++-- Spawn one-off background agents -> multi-agent/REFERENCE.md (sub-agents) ++-- Persistent cross-session teams -> multi-agent/REFERENCE.md (teams) ++-- Parent-child delegation -> multi-agent/REFERENCE.md (sub-agents) ++-- Peer-to-peer task board -> multi-agent/REFERENCE.md (teams) +``` + +### "I need scheduling or automation" + +``` +Scheduling? ++-- Recurring cron jobs -> scheduling/REFERENCE.md ++-- One-off scheduled tasks -> scheduling/REFERENCE.md ++-- Event-driven triggers -> scheduling/REFERENCE.md ++-- CLI schedule management -> scheduling/REFERENCE.md +``` + +### "I need to go to production" + +``` +Production? ++-- Error handling and status checks -> production/REFERENCE.md ++-- Cost control and token limits -> production/REFERENCE.md ++-- Observability (OpenTelemetry) -> production/REFERENCE.md ++-- Security and sandboxing -> production/REFERENCE.md ++-- Deployment patterns -> production/REFERENCE.md +``` + +### Troubleshooting Index + +- Agent loop not stopping -> `tools/REFERENCE.md` (completion tools) +- Tool errors crashing the agent -> `agent/gotchas.md` or `clinecore/gotchas.md` +- Provider auth failures -> `providers/REFERENCE.md` +- Session not persisting -> `clinecore/gotchas.md` +- Token usage too high -> `production/REFERENCE.md` (cost control) +- Hub connection issues -> `clinecore/gotchas.md` +- Plugin not loading -> `plugins/REFERENCE.md` +- Events not firing -> `events/REFERENCE.md` + +## Product Index + +### API Surfaces +| API | Entry File | Description | +|-----|------------|-------------| +| Agent | `./references/agent/REFERENCE.md` | Lightweight stateless agent loop | +| ClineCore | `./references/clinecore/REFERENCE.md` | Full runtime with sessions, persistence, built-in tools | + +### Cross-Cutting Concepts +| Concept | Entry File | Description | +|---------|------------|-------------| +| Tools | `./references/tools/REFERENCE.md` | Built-in and custom tool creation | +| Plugins | `./references/plugins/REFERENCE.md` | Extension system with hooks | +| Events | `./references/events/REFERENCE.md` | Real-time streaming events | +| Providers | `./references/providers/REFERENCE.md` | LLM provider configuration | +| Production | `./references/production/REFERENCE.md` | Deployment, security, observability | +| Scheduling | `./references/scheduling/REFERENCE.md` | Cron jobs and automation | +| Multi-Agent | `./references/multi-agent/REFERENCE.md` | Teams and sub-agents | + +### Package Map +| Package | Purpose | +|---------|---------| +| `@cline/sdk` | Everything you need, install this one | +| `@cline/core` | Sessions, persistence, built-in tools, config, hub | +| `@cline/agents` | Stateless agent loop, tool orchestration, streaming | +| `@cline/llms` | LLM provider gateway | +| `@cline/shared` | Types, tool helpers, hook engine | + +## Resources + +Repository: https://github.com/cline/cline +SDK Source: https://github.com/cline/cline/tree/main/sdk +Documentation: https://docs.cline.bot/sdk/overview +Discord: https://discord.gg/cline diff --git a/.agents/skills/cline-sdk/references/agent/REFERENCE.md b/.agents/skills/cline-sdk/references/agent/REFERENCE.md new file mode 100644 index 0000000000..4fa40a7833 --- /dev/null +++ b/.agents/skills/cline-sdk/references/agent/REFERENCE.md @@ -0,0 +1,107 @@ +# Agent Runtime + +The `Agent` class (also exported as `AgentRuntime`) is the lightweight, stateless agent loop from `@cline/agents`. It handles the core iteration cycle: send messages to an LLM, execute tool calls, collect results, and repeat until the task is done. + +## When to Use Agent + +| Use Agent when... | Use ClineCore instead when... | +|---|---| +| You want a simple agent with custom tools | You need built-in tools (bash, editor, etc.) | +| You want minimal dependencies | You need session persistence | +| You need browser compatibility | You need config discovery from `.cline/` | +| You're building a stateless worker | You need multi-process session sharing | +| You want full control over the runtime | You want batteries-included setup | + +## Quick Start + +```typescript +import { Agent } from "@cline/sdk" + +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + systemPrompt: "You are a helpful assistant.", + tools: [], +}) + +const result = await agent.run("What is the capital of France?") +console.log(result.outputText) +``` + +## Core Concepts + +The Agent operates in a loop: +1. Accept user input (string, message, or array of messages) +2. Build turn context (system prompt, messages, tools) +3. Call the LLM provider +4. If the model returns tool calls, execute them and loop back to step 3 +5. If the model returns text without tool calls, the run completes +6. Emit events throughout for streaming + +The agent is stateless in the sense that it does not persist anything to disk. Conversation history is held in memory and can be accessed via `snapshot()`. + +## Key APIs + +- `new Agent(config)` or `createAgent(config)` - Create an agent +- `agent.run(input)` - Start a run with user input +- `agent.continue(input?)` - Continue an existing conversation +- `agent.abort(reason?)` - Cancel an active run +- `agent.subscribe(listener)` - Listen to streaming events +- `agent.snapshot()` - Get current runtime state +- `agent.restore(messages)` - Replace message history + +See `api.md` for full API details. + +## Multi-Turn Conversations + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "You are a helpful assistant.", + tools: [], +}) + +const first = await agent.run("What is 2 + 2?") +console.log(first.outputText) + +const second = await agent.continue("Now multiply that by 3") +console.log(second.outputText) +``` + +Use `agent.hasRun` to check if a run has already been executed, which determines whether to call `run()` or `continue()`. + +## Event Streaming + +Use `agent.subscribe()` to stream events in real time. Register the listener before calling `run()` to avoid missing early events. + +There is no top-level `onEvent` field on the Agent config. For an async alternative, use `hooks.onEvent` (see `api.md` and `gotchas.md`). + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "You are a helpful assistant.", + tools: [], +}) + +agent.subscribe((event) => { + if (event.type === "assistant-text-delta") { + process.stdout.write(event.text) + } +}) + +const result = await agent.run("What is the capital of France?") +``` + +See `events/REFERENCE.md` for the full event type catalog. + +## Next Steps + +- `api.md` - Full Agent API reference +- `patterns.md` - Common patterns and best practices +- `gotchas.md` - Pitfalls and debugging +- `../tools/REFERENCE.md` - Creating custom tools +- `../events/REFERENCE.md` - Event system details +- `../providers/REFERENCE.md` - Provider configuration diff --git a/.agents/skills/cline-sdk/references/agent/api.md b/.agents/skills/cline-sdk/references/agent/api.md new file mode 100644 index 0000000000..76a008cf4e --- /dev/null +++ b/.agents/skills/cline-sdk/references/agent/api.md @@ -0,0 +1,231 @@ +# Agent API Reference + +## Constructor + +```typescript +import { Agent } from "@cline/sdk" + +const agent = new Agent(config: AgentRuntimeConfig) +``` + +Also available via factory function: + +```typescript +import { createAgent } from "@cline/sdk" + +const agent = createAgent(config) +``` + +## AgentRuntimeConfig + +Two config forms exist as a discriminated union: + +### With Provider ID (recommended) + +```typescript +interface AgentRuntimeConfigWithProvider { + providerId: string // e.g. "anthropic", "openai", "gemini" + modelId: string // e.g. "claude-sonnet-4-6", "gpt-5.5" + apiKey?: string // provider API key + baseUrl?: string // custom endpoint + headers?: Record + + systemPrompt?: string + tools?: AgentTool[] + initialMessages?: AgentMessage[] + toolPolicies?: Record + hooks?: Partial + plugins?: AgentPlugin[] +} +``` + +### With Pre-built Model + +```typescript +interface AgentRuntimeConfigWithModel { + model: AgentModel // pre-built model from gateway + + systemPrompt?: string + tools?: AgentTool[] + initialMessages?: AgentMessage[] + toolPolicies?: Record + hooks?: Partial + plugins?: AgentPlugin[] +} +``` + +Note: there is no top-level `onEvent` field on `AgentRuntimeConfig`. For event streaming, use `agent.subscribe()` or `hooks.onEvent` (see AgentRuntimeHooks below). + +## Methods + +### run(input) + +Start the agent with user input. Returns when the agent loop completes. + +```typescript +const result: AgentRunResult = await agent.run("Build a REST API") +``` + +Input can be a string, an `AgentMessage`, or an array of `AgentMessage[]`. + +### continue(input?) + +Continue an existing conversation with optional new input. + +```typescript +const result = await agent.continue("Now add authentication") +``` + +### abort(reason?) + +Cancel the currently active run. + +```typescript +agent.abort("User cancelled") +``` + +### subscribe(listener) + +Register a listener for streaming events. + +```typescript +const unsubscribe = agent.subscribe((event: AgentRuntimeEvent) => { + // handle event +}) + +// Later: stop listening +unsubscribe() +``` + +### snapshot() + +Get the current runtime state including message history. + +```typescript +const state: AgentRuntimeStateSnapshot = agent.snapshot() +``` + +### restore(messages) + +Replace the agent's message history. + +```typescript +agent.restore(previousMessages) +``` + +### hasRun + +Boolean property indicating whether `run()` has been called at least once. + +```typescript +if (agent.hasRun) { + await agent.continue(input) +} else { + await agent.run(input) +} +``` + +## AgentRunResult + +Returned by `run()` and `continue()`. + +```typescript +interface AgentRunResult { + agentId: string + agentRole?: string + runId: string + status: "completed" | "aborted" | "failed" + iterations: number + outputText: string + messages: readonly AgentMessage[] + usage: AgentUsage + error?: Error +} +``` + +### Status Values + +- `"completed"` - Agent finished normally +- `"aborted"` - Cancelled via `abort()` +- `"failed"` - Unrecoverable error + +## AgentMessage + +```typescript +interface AgentMessage { + id: string + role: "user" | "assistant" | "tool" + content: AgentMessagePart[] + createdAt: number + metadata?: Record + modelInfo?: { id: string; provider: string; family?: string } + metrics?: { + inputTokens: number + outputTokens: number + cacheReadTokens?: number + cacheWriteTokens?: number + cost?: number + } +} +``` + +## AgentUsage + +```typescript +interface AgentUsage { + inputTokens: number + outputTokens: number + cacheReadTokens: number + cacheWriteTokens: number + totalInputTokens: number + totalOutputTokens: number + totalCost?: number +} +``` + +## AgentRuntimeHooks + +```typescript +interface AgentRuntimeHooks { + beforeRun?(context): AgentStopControl | undefined + afterRun?(context): void + beforeModel?(context): AgentBeforeModelResult | undefined + afterModel?(context): AgentStopControl | undefined + beforeTool?(context): AgentBeforeToolResult | undefined + afterTool?(context): AgentAfterToolResult | undefined + onEvent?(event: AgentRuntimeEvent): void | Promise +} +``` + +Hooks can intercept and modify behavior at each stage. Return a stop control from `beforeRun`, `afterModel`, or `beforeTool` to halt the agent loop. + +`hooks.onEvent` receives the same `AgentRuntimeEvent` types as `agent.subscribe()`, but hook callbacks are awaited (can be async), while `subscribe()` listeners are called synchronously. Use `subscribe()` for UI streaming and `hooks.onEvent` for async side effects like logging to an external service. + +## AgentRuntimeStateSnapshot + +```typescript +interface AgentRuntimeStateSnapshot { + messages: readonly AgentMessage[] + usage: AgentUsage + iterations: number + status: string +} +``` + +## Factory: createAgentRuntime + +Lower-level factory that returns the same `Agent` class: + +```typescript +import { createAgentRuntime } from "@cline/sdk" + +const runtime = createAgentRuntime(config) +``` + +## See Also + +- `REFERENCE.md` - Overview and quick start +- `patterns.md` - Common patterns +- `../tools/REFERENCE.md` - Tool creation +- `../events/REFERENCE.md` - Event types +- `../providers/REFERENCE.md` - Provider setup diff --git a/.agents/skills/cline-sdk/references/agent/gotchas.md b/.agents/skills/cline-sdk/references/agent/gotchas.md new file mode 100644 index 0000000000..8b4b945db4 --- /dev/null +++ b/.agents/skills/cline-sdk/references/agent/gotchas.md @@ -0,0 +1,134 @@ +# Agent Gotchas + +## Agent Loop Never Stops + +If the agent keeps iterating without completing: + +- Make sure at least one tool has `lifecycle: { completesRun: true }` if you want the agent to explicitly finish. +- Without any tools, the agent will complete after the model returns text without tool calls. +- If using tools, ensure the system prompt guides the model toward calling the completion tool when done. +- Check that `completesRun` tools return successfully (not throwing errors). + +## Tool Errors Count as Mistakes + +When a tool's `execute` function throws an exception, the SDK counts it as a "mistake." After too many mistakes, the agent stops with a `mistake_limit` finish reason. + +Instead, return errors as structured data: + +```typescript +// Bad: throwing +execute: async (input) => { + throw new Error("File not found") +} + +// Good: returning error data +execute: async (input) => { + return { error: "File not found", path: input.path } +} +``` + +## run() vs continue() + +- Call `run()` for the first interaction. It sets up the conversation. +- Call `continue()` for subsequent messages. It appends to the existing conversation. +- Calling `run()` a second time resets the conversation history. +- Use `agent.hasRun` to check which method to call. + +## Browser Compatibility + +`@cline/agents` (and by extension, the `Agent` class) is browser-safe with no Node.js dependencies. However, `@cline/core` and `ClineCore` require Node.js 22+. If you import from `@cline/sdk`, you get everything including the Node-only code. For browser usage, import directly from `@cline/agents`: + +```typescript +import { Agent } from "@cline/agents" +``` + +## No Top-Level onEvent on Agent Config + +`AgentRuntimeConfig` does not have a top-level `onEvent` field. Passing `onEvent` to `new Agent({ onEvent: ... })` has no effect. There are two ways to receive events: + +```typescript +// Option 1: subscribe() - synchronous, best for UI streaming +const agent = new Agent({ ...config }) +agent.subscribe((event) => { + if (event.type === "assistant-text-delta") { + process.stdout.write(event.text) + } +}) + +// Option 2: hooks.onEvent - awaited, best for async side effects +const agent = new Agent({ + ...config, + hooks: { + onEvent: async (event) => { + if (event.type === "assistant-text-delta") { + await logToService(event.text) + } + }, + }, +}) +``` + +Both receive the same `AgentRuntimeEvent` types. Prefer `subscribe()` for streaming UI. + +## Event Listener Timing + +Register event listeners via `subscribe()` before calling `run()`: + +```typescript +// Good: subscribe before run +agent.subscribe(handler) +const result = await agent.run(input) + +// Bad: subscribing after run starts loses early events +const promise = agent.run(input) +agent.subscribe(handler) // may miss events +``` + +## Tool Input Schema Matters + +The model uses the tool's `inputSchema` to decide what arguments to pass. A vague or missing schema leads to incorrect tool calls. + +- Use `z.enum()` for fixed value sets, not free-form strings +- Describe every property with `.describe()` in Zod or `description` in JSON Schema +- Include constraints (rate limits, max values) in the tool description + +## Memory and Long Conversations + +The Agent holds all messages in memory. For long-running conversations, memory usage grows with each turn. Consider: + +- Using `ClineCore` with compaction for long sessions +- Periodically creating a new agent with a summary of the conversation +- Monitoring `result.usage.totalInputTokens` to track context growth + +## Abort Signal Handling in Tools + +Long-running tools should respect the abort signal: + +```typescript +execute: async (input, context) => { + for (const item of items) { + if (context.abortSignal?.aborted) { + return { partial: results, aborted: true } + } + results.push(await process(item)) + } + return { results } +} +``` + +## Provider API Key + +If you get authentication errors, check: + +- `apiKey` is set in the config or via environment variables +- The key matches the `providerId` (e.g., Anthropic key for `providerId: "anthropic"`) +- For OpenAI-compatible providers, both `apiKey` and `baseUrl` are set + +See `../providers/REFERENCE.md` for provider-specific setup. + +## See Also + +- `api.md` - Full API reference +- `patterns.md` - Common patterns +- `../tools/REFERENCE.md` - Tool creation +- `../clinecore/REFERENCE.md` - Use ClineCore for persistence diff --git a/.agents/skills/cline-sdk/references/agent/patterns.md b/.agents/skills/cline-sdk/references/agent/patterns.md new file mode 100644 index 0000000000..c0e43e208f --- /dev/null +++ b/.agents/skills/cline-sdk/references/agent/patterns.md @@ -0,0 +1,258 @@ +# Agent Patterns + +## Interactive CLI Agent + +A multi-turn conversational agent in the terminal with streaming output: + +```typescript +import { Agent } from "@cline/sdk" +import * as readline from "node:readline" + +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + systemPrompt: "You are a helpful assistant. Keep responses concise.", + tools: [], +}) + +agent.subscribe((event) => { + if (event.type === "assistant-text-delta") { + process.stdout.write(event.text) + } +}) + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}) + +function prompt(): void { + rl.question("\nYou: ", async (input) => { + const trimmed = input.trim() + if (!trimmed || trimmed === "exit") { + rl.close() + return + } + + process.stdout.write("\nAssistant: ") + + if (agent.hasRun) { + await agent.continue(trimmed) + } else { + await agent.run(trimmed) + } + + process.stdout.write("\n") + prompt() + }) +} + +prompt() +``` + +## Conversational Agent (Slack Bot, Chat App) + +Maintain per-thread agents with conversation memory: + +```typescript +import { Agent } from "@cline/sdk" + +const agents = new Map() + +async function handleMessage(threadId: string, message: string) { + let agent = agents.get(threadId) + if (!agent) { + agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "You are a concise assistant.", + tools: [], + }) + agents.set(threadId, agent) + } + + const result = agent.hasRun + ? await agent.continue(message) + : await agent.run(message) + + return result.outputText +} +``` + +## Streaming UI + +Build a real-time UI by handling events via `subscribe()`: + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "You are a helpful assistant.", + tools: [myTool], +}) + +agent.subscribe((event) => { + switch (event.type) { + case "assistant-text-delta": + ui.appendText(event.text) + break + case "assistant-message": + ui.endText() + break + case "turn-started": + ui.startTurn(event.iteration) + break + case "turn-finished": + if (event.toolCallCount > 0) ui.showToolCount(event.toolCallCount) + break + case "usage-updated": + ui.updateUsage(event.usage.inputTokens, event.usage.outputTokens) + break + } +}) + +const result = await agent.run("Hello!") +``` + +## Structured Output via Completion Tool + +Use a tool with `completesRun: true` to extract structured data: + +```typescript +import { Agent, createTool } from "@cline/sdk" +import { z } from "zod" + +const submitReview = createTool({ + name: "submit_review", + description: "Submit the final code review with structured feedback.", + inputSchema: z.object({ + summary: z.string(), + issues: z.array(z.object({ + file: z.string(), + line: z.number(), + severity: z.enum(["error", "warning", "info"]), + message: z.string(), + })), + approved: z.boolean(), + }), + lifecycle: { completesRun: true }, + execute: async (input) => input, +}) + +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "Review the code diff and submit structured feedback.", + tools: [submitReview], +}) + +const result = await agent.run(diffContent) +const review = result.toolCalls.find(tc => tc.name === "submit_review") +console.log(review?.output) +``` + +## Agent with Abort/Timeout + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "Analyze this data.", + tools: [], +}) + +const timeout = setTimeout(() => agent.abort("Timeout"), 30_000) + +try { + const result = await agent.run(data) + if (result.status === "aborted") { + console.log("Agent was aborted") + } else { + console.log(result.outputText) + } +} finally { + clearTimeout(timeout) +} +``` + +## Agent with Plugins + +```typescript +import { Agent } from "@cline/sdk" +import type { AgentPlugin } from "@cline/sdk" + +const loggingPlugin: AgentPlugin = { + name: "logging", + manifest: { capabilities: ["hooks"] }, + setup() {}, + hooks: { + beforeTool({ toolCall }) { + console.log(`Calling tool: ${toolCall.toolName}`) + }, + afterRun({ result }) { + console.log(`Completed in ${result.iterations} iterations`) + }, + }, +} + +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + systemPrompt: "You are a helpful assistant.", + tools: [myTool], + plugins: [loggingPlugin], +}) +``` + +## Restoring State Across Sessions + +Save and restore agent state manually: + +```typescript +// Save state +const snapshot = agent.snapshot() +const serialized = JSON.stringify(snapshot.messages) + +// Later: restore +const agent2 = new Agent({ ...config }) +const messages = JSON.parse(serialized) +agent2.restore(messages) +const result = await agent2.continue("Continue where we left off") +``` + +For automatic persistence, use `ClineCore` instead. + +## Pre-Built Model via Gateway + +For advanced provider configuration: + +```typescript +import { Agent } from "@cline/sdk" +import { createGateway } from "@cline/llms" + +const gateway = createGateway({ + providerConfigs: [ + { providerId: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY }, + { providerId: "openai", apiKey: process.env.OPENAI_API_KEY }, + ], +}) + +const model = gateway.createAgentModel({ + providerId: "anthropic", + modelId: "claude-opus-4-7", +}) + +const agent = new Agent({ + model, + systemPrompt: "You are a helpful assistant.", + tools: [], +}) +``` + +## See Also + +- `api.md` - Full API reference +- `gotchas.md` - Common pitfalls +- `../tools/REFERENCE.md` - Creating tools +- `../plugins/REFERENCE.md` - Plugin system diff --git a/.agents/skills/cline-sdk/references/clinecore/REFERENCE.md b/.agents/skills/cline-sdk/references/clinecore/REFERENCE.md new file mode 100644 index 0000000000..d1b9e01c0f --- /dev/null +++ b/.agents/skills/cline-sdk/references/clinecore/REFERENCE.md @@ -0,0 +1,131 @@ +# ClineCore Runtime + +`ClineCore` is the full-featured runtime from `@cline/core`. It wraps the `Agent` loop with session persistence, built-in tools (bash, editor, file reading, search, web fetch), config discovery, plugin loading, and optional hub-backed multi-process support. + +## When to Use ClineCore + +| Use ClineCore when... | Use Agent instead when... | +|---|---| +| You need built-in tools (bash, editor, etc.) | You only need custom tools | +| You want session persistence to disk | Stateless is fine | +| You need config discovery from `.cline/` dirs | You handle config yourself | +| You want scheduled/automated agents | You don't need scheduling | +| You need multi-client session sharing | Single-process is fine | +| You're building a full application | You want minimal dependencies | + +## Quick Start + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create({ clientName: "my-app" }) + +const session = await cline.start({ + prompt: "Set up CI with GitHub Actions", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + cwd: "/path/to/project", + enableTools: true, + }, +}) + +console.log(session.result?.text) +await cline.dispose() +``` + +## Core Concepts + +### Sessions + +Every `cline.start()` call creates a session with a unique ID. Sessions persist their messages and metadata to SQLite. You can list, read, resume, and delete sessions. + +### Built-in Tools + +ClineCore provides these tools automatically when `enableTools: true`: + +| Tool | Description | +|------|-------------| +| `bash` | Execute shell commands | +| `editor` | Edit files | +| `read_files` | Read file contents | +| `apply_patch` | Apply unified diffs | +| `search` | Search file contents and structure | +| `fetch_web` | HTTP requests and web content | + +### Config Discovery + +ClineCore watches `.cline/` directories for: +- Rules (system prompt additions) +- Skills (domain knowledge) +- Workflows (multi-step procedures) +- Hooks (lifecycle logic) +- Plugins (tool + hook bundles) +- MCP servers (external tool providers) + +### Backend Modes + +| Mode | Description | +|------|-------------| +| `"auto"` (default) | Tries to connect to a local hub; falls back to in-process if unavailable | +| `"local"` | In-process execution, local SQLite storage, no hub | +| `"hub"` | Requires a compatible local WebSocket hub; fails if unavailable | +| `"remote"` | Connects to an explicit remote hub endpoint | + +The default mode is `"auto"`. For simple scripts and CLI tools, `"local"` avoids hub discovery overhead. Hub mode enables multi-client session sharing (e.g., a dashboard watching a running session from another process). + +## Key APIs + +- `ClineCore.create(options)` - Create and initialize +- `cline.start(input)` - Start a new session +- `cline.send({ sessionId, prompt })` - Send follow-up message +- `cline.subscribe(listener)` - Listen to session events +- `cline.list()` - List sessions +- `cline.get(sessionId)` - Get session metadata +- `cline.readMessages(sessionId)` - Read persisted messages +- `cline.getAccumulatedUsage(sessionId)` - Token/cost totals +- `cline.abort(sessionId)` - Abort a session +- `cline.delete(sessionId)` - Delete a session +- `cline.dispose()` - Clean up resources + +See `api.md` for full API details. + +## Event Streaming + +`cline.subscribe()` emits `CoreSessionEvent` types. These are different from the `AgentRuntimeEvent` types emitted by the standalone `Agent` class -- see `../events/REFERENCE.md` for the full comparison. + +```typescript +cline.subscribe((event) => { + switch (event.type) { + case "chunk": + if (event.payload.type === "text") { + process.stdout.write(event.payload.text) + } + break + case "ended": + console.log(`Session ended: ${event.payload.finishReason}`) + break + } +}) +``` + +ClineCore results use `AgentResult` with `.text` (not `.outputText` like the standalone Agent's `AgentRunResult`). + +## Session Persistence + +Sessions are stored at: +``` +~/.cline/data/sessions/ + sessions.db # SQLite database + [session-id].json # Message history +``` + +## Next Steps + +- `api.md` - Full ClineCore API reference +- `patterns.md` - Common patterns and best practices +- `gotchas.md` - Pitfalls and debugging +- `../tools/REFERENCE.md` - Custom tool creation +- `../plugins/REFERENCE.md` - Plugin system +- `../scheduling/REFERENCE.md` - Scheduled agents diff --git a/.agents/skills/cline-sdk/references/clinecore/api.md b/.agents/skills/cline-sdk/references/clinecore/api.md new file mode 100644 index 0000000000..8abfce998d --- /dev/null +++ b/.agents/skills/cline-sdk/references/clinecore/api.md @@ -0,0 +1,304 @@ +# ClineCore API Reference + +## Creating ClineCore + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create(options: ClineCoreOptions) +``` + +### ClineCoreOptions + +```typescript +interface ClineCoreOptions { + clientName: string // identifies your app + distinctId?: string // user/instance identifier + backendMode?: "auto" | "local" | "hub" | "remote" + hub?: HubOptions + remote?: RemoteOptions + capabilities?: RuntimeCapabilities + toolPolicies?: Record + automation?: boolean | ClineCoreAutomationOptions + fetch?: typeof fetch +} +``` + +### RuntimeCapabilities + +```typescript +interface RuntimeCapabilities { + requestToolApproval?: (request: ToolApprovalRequest) => Promise + // ... other capability callbacks +} +``` + +## Starting Sessions + +### start(input) + +```typescript +const session = await cline.start(input: ClineCoreStartInput) +``` + +Returns a `StartSessionResult`: + +```typescript +interface StartSessionResult { + sessionId: string + manifest: SessionManifest + manifestPath: string + messagesPath: string + result?: AgentResult +} +``` + +### ClineCoreStartInput + +```typescript +interface ClineCoreStartInput { + prompt: string + config: CoreSessionConfig + source?: string + interactive?: boolean + sessionMetadata?: Record + initialMessages?: AgentMessage[] + toolPolicies?: Record + capabilities?: RuntimeCapabilities +} +``` + +### CoreSessionConfig + +```typescript +interface CoreSessionConfig { + cwd?: string // working directory + providerId: string // LLM provider + modelId: string // model identifier + apiKey?: string // provider API key + systemPrompt?: string // custom system prompt + tools?: readonly AgentTool[] // additional custom tools + enableTools?: boolean // enable built-in tools + hooks?: Partial // runtime hooks + extensions?: AgentPlugin[] // plugins loaded inline + pluginPaths?: string[] // paths to plugin packages + extensionLoading?: "isolated" | "direct" + extensionContext?: { // context passed to plugin setup() + workspace?: { rootPath: string; cwd: string } + } + checkpointConfig?: CoreCheckpointConfig + compactionConfig?: CoreCompactionConfig + telemetry?: ITelemetryService + logger?: BasicLogger + enableSpawnAgent?: boolean // enable sub-agent spawning + enableAgentTeams?: boolean // enable team coordination + teamName?: string // team identifier +} +``` + +`extensions` passes plugin objects directly. `pluginPaths` points to directories with `package.json` containing a `cline.plugins` field. Set `extensionContext.workspace` so plugins receive `ctx.workspaceInfo` in their `setup()` call -- without it, `ctx.workspaceInfo` is undefined. + +## Follow-Up Messages + +### send({ sessionId, prompt }) + +Send a follow-up message to an existing session: + +```typescript +const result = await cline.send({ + sessionId: session.sessionId, + prompt: "Now add authentication", +}) +``` + +Returns `AgentResult | undefined`. + +## Event Subscription + +### subscribe(listener, options?) + +```typescript +const unsubscribe = cline.subscribe( + (event: CoreSessionEvent) => { + // handle events + }, + { sessionId: "optional-filter" } +) +``` + +### CoreSessionEvent + +```typescript +type CoreSessionEvent = + | { type: "chunk"; payload: SessionChunkEvent } + | { type: "agent_event"; payload: { sessionId: string, event: AgentEvent } } + | { type: "ended"; payload: SessionEndedEvent } + | { type: "team_progress"; payload: SessionTeamProgressEvent } + | { type: "status"; payload: { sessionId: string, status: string } } + | { type: "hook"; payload: SessionToolEvent } +``` + +## Session Management + +### list(limit?, options?) + +```typescript +const sessions: SessionRecord[] = await cline.list(50) +``` + +### get(sessionId) + +```typescript +const session: SessionRecord = await cline.get(sessionId) +``` + +### readMessages(sessionId) + +```typescript +const messages: AgentMessage[] = await cline.readMessages(sessionId) +``` + +### getAccumulatedUsage(sessionId) + +```typescript +const usage = await cline.getAccumulatedUsage(sessionId) +// usage.usage - root agent only +// usage.aggregateUsage - root + subagents/teammates +``` + +### update(sessionId, updates) + +```typescript +await cline.update(sessionId, { title: "New title" }) +``` + +### abort(sessionId, reason?) + +```typescript +await cline.abort(sessionId, "User cancelled") +``` + +### stop(sessionId) + +```typescript +await cline.stop(sessionId) +``` + +### delete(sessionId) + +```typescript +await cline.delete(sessionId) +``` + +### restore(input) + +Restore a session from a checkpoint: + +```typescript +await cline.restore({ sessionId, checkpointId }) +``` + +### dispose(reason?) + +Clean up all resources. Always call this when done: + +```typescript +await cline.dispose("Shutting down") +``` + +## AgentResult + +Returned by session operations: + +```typescript +interface AgentResult { + text: string + usage: LegacyAgentUsage + messages: MessageWithMetadata[] + toolCalls: ToolCallRecord[] + iterations: number + finishReason: "completed" | "max_iterations" | "aborted" | "mistake_limit" | "error" + model: { id: string; provider: string; info?: ModelInfo } + startedAt: Date + endedAt: Date + durationMs: number +} +``` + +## Tool Policies + +Control tool access at the session level: + +```typescript +const session = await cline.start({ + prompt: "Review the code", + config: { ... }, + toolPolicies: { + read_files: { autoApprove: true }, + bash: { autoApprove: false }, + editor: { enabled: false }, + }, +}) +``` + +### ToolPolicy + +```typescript +interface ToolPolicy { + enabled?: boolean // false = tool is hidden from the model + autoApprove?: boolean // false = requires approval callback +} +``` + +## Interactive Approval + +```typescript +const cline = await ClineCore.create({ + clientName: "my-app", + capabilities: { + requestToolApproval: async (request) => { + console.log(`Tool: ${request.toolName}, Input: ${JSON.stringify(request.input)}`) + const approved = await askUser(`Allow ${request.toolName}?`) + return { approved } + }, + }, +}) +``` + +## Automation API + +When `automation` is enabled in `ClineCore.create()`: + +```typescript +const cline = await ClineCore.create({ + clientName: "my-app", + automation: true, +}) + +// Access automation methods +cline.automation.start() +cline.automation.stop() +cline.automation.reconcile(specs) +cline.automation.ingestEvent(event) +cline.automation.listEvents() +cline.automation.listSpecs() +cline.automation.listRuns() +``` + +## Settings API + +```typescript +// Read settings +const settings = await cline.settings.list() + +// Toggle tools, plugins, MCP servers +await cline.settings.toggle({ type: "tool", name: "bash", enabled: true }) +``` + +## See Also + +- `REFERENCE.md` - Overview and quick start +- `patterns.md` - Common patterns +- `gotchas.md` - Pitfalls +- `../tools/REFERENCE.md` - Tool creation +- `../plugins/REFERENCE.md` - Plugin system diff --git a/.agents/skills/cline-sdk/references/clinecore/gotchas.md b/.agents/skills/cline-sdk/references/clinecore/gotchas.md new file mode 100644 index 0000000000..240aab8763 --- /dev/null +++ b/.agents/skills/cline-sdk/references/clinecore/gotchas.md @@ -0,0 +1,148 @@ +# ClineCore Gotchas + +## Always Call dispose() + +`ClineCore` holds resources (file watchers, database connections, hub connections). Failing to call `dispose()` can leave orphan processes and file locks. + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) +try { + // ... use cline +} finally { + await cline.dispose() +} +``` + +## Node.js 22 Required + +ClineCore and `@cline/core` require Node.js 22 or later. If you're on an older version, you'll get runtime errors. Check with `node --version`. + +## Session Config vs Global Config + +Tool policies can be set at two levels: +- Global: in `ClineCore.create({ toolPolicies })` -- applies to all sessions +- Per-session: in `cline.start({ toolPolicies })` -- overrides global for that session + +Per-session policies take precedence. + +## enableTools Must Be Explicit + +Built-in tools (bash, editor, read_files, etc.) are not available unless you set `enableTools: true` in the session config: + +```typescript +await cline.start({ + prompt: "Read package.json", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + enableTools: true, // required for built-in tools + }, +}) +``` + +Without this, the agent only has access to custom tools you provide via `config.tools`. + +## cwd Matters for Built-in Tools + +Built-in tools like `bash`, `editor`, and `read_files` operate relative to `config.cwd`. If not set, they use the process working directory. Always set it explicitly for predictable behavior: + +```typescript +config: { + cwd: "/absolute/path/to/project", + // ... +} +``` + +## Hub Startup Latency + +With `backendMode: "auto"`, the first session may be slow if a hub daemon needs to be spawned. For immediate responsiveness: +- Use `backendMode: "local"` for in-process execution (fastest startup) +- Pre-warm the hub with `cline hub ensure` CLI command +- Accept the one-time startup cost and let subsequent sessions reuse the hub + +## Session Storage Location + +Sessions are stored at `~/.cline/data/sessions/`. This includes: +- `sessions.db` - SQLite database with session metadata +- `[session-id].json` - Individual message history files + +If you're running in a container or ephemeral environment, these paths may not persist across restarts. + +## requestToolApproval Blocks Execution + +When a tool policy has `autoApprove: false` and you provide a `requestToolApproval` callback, the agent loop blocks until your callback resolves. If your callback never resolves (e.g., waiting for user input that never comes), the session hangs. + +For automated pipelines, either: +- Set all tools to `autoApprove: true` +- Implement a timeout in your approval callback + +## Plugin Discovery Paths + +ClineCore discovers plugins from: +- Global: `~/.cline/plugins/` +- Workspace: `.cline/plugins/` + +For SDK consumers, pass plugins via `extensions: [plugin]` or `pluginPaths: ["./path"]` in the session config. + +If a plugin isn't loading, verify: +- The file is in one of the discovery directories, or passed via `extensions`/`pluginPaths` +- The file exports a default plugin object with a non-empty `manifest.capabilities` array +- Every `api.register*` call in `setup()` has a matching capability declared +- If `hooks` is present on the plugin, `"hooks"` is in `capabilities` + +## extensionContext.workspace Is Required for Plugins + +If your plugins use `ctx.workspaceInfo` (e.g., to resolve workspace paths), you must set `extensionContext.workspace` in the session config. Without it, `ctx.workspaceInfo` is undefined: + +```typescript +await cline.start({ + config: { + extensions: [myPlugin], + extensionContext: { + workspace: { rootPath: process.cwd(), cwd: process.cwd() }, + }, + }, +}) +``` + +The CLI sets this automatically, but SDK consumers must set it explicitly. + +## send() Requires an Active Session + +`cline.send()` only works on sessions that are still active. If a session has already completed, `send()` may return `undefined` or fail. Check session status with `cline.get(sessionId)` first. + +## Result May Be Undefined + +`session.result` can be `undefined` if the session was started but hasn't completed yet (e.g., in a non-blocking hub mode). Check for this: + +```typescript +const session = await cline.start({ ... }) +if (session.result) { + console.log(session.result.text) +} else { + console.log("Session started but not yet complete") +} +``` + +## Compaction and Long Sessions + +For long-running sessions, message history grows and eventually exceeds the model's context window. ClineCore handles this via compaction, which summarizes older messages. Configure it via `compactionConfig`: + +```typescript +config: { + compactionConfig: { + strategy: "summarize", + // ... + }, +} +``` + +The default strategy works for most cases, but extremely long sessions may benefit from tuning. + +## See Also + +- `api.md` - Full API reference +- `patterns.md` - Common patterns +- `../agent/gotchas.md` - Agent-level gotchas +- `../tools/REFERENCE.md` - Tool troubleshooting +- `../providers/REFERENCE.md` - Provider troubleshooting diff --git a/.agents/skills/cline-sdk/references/clinecore/patterns.md b/.agents/skills/cline-sdk/references/clinecore/patterns.md new file mode 100644 index 0000000000..379eff6f3d --- /dev/null +++ b/.agents/skills/cline-sdk/references/clinecore/patterns.md @@ -0,0 +1,279 @@ +# ClineCore Patterns + +## Basic Session with Built-in Tools + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create({ clientName: "my-app" }) + +const session = await cline.start({ + prompt: "Read package.json and summarize the dependencies", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + cwd: process.cwd(), + enableTools: true, + }, +}) + +console.log(session.result?.text) +await cline.dispose() +``` + +## Streaming Session with UI Updates + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) + +cline.subscribe((event) => { + switch (event.type) { + case "chunk": + if (event.payload.type === "text") { + ui.appendText(event.payload.text) + } + break + case "ended": + ui.showComplete(event.payload.finishReason) + break + } +}) + +await cline.start({ + prompt: "Refactor the auth module", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + cwd: "/path/to/project", + enableTools: true, + }, +}) +``` + +## Multi-Turn Session + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) + +const session = await cline.start({ + prompt: "Create a new Express server", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + cwd: "/path/to/project", + enableTools: true, + }, +}) + +// Follow-up +const result = await cline.send({ + sessionId: session.sessionId, + prompt: "Now add a health check endpoint", +}) + +console.log(result?.text) +await cline.dispose() +``` + +## Tiered Permission Model + +Auto-approve reads, require approval for writes: + +```typescript +const cline = await ClineCore.create({ + clientName: "my-app", + toolPolicies: { + read_files: { autoApprove: true }, + search: { autoApprove: true }, + fetch_web: { autoApprove: true }, + bash: { autoApprove: false }, + editor: { autoApprove: false }, + apply_patch: { autoApprove: false }, + }, + capabilities: { + requestToolApproval: async (request) => { + const approved = await promptUser( + `Allow ${request.toolName}?\n${JSON.stringify(request.input, null, 2)}` + ) + return { approved } + }, + }, +}) +``` + +## Custom Tools Alongside Built-ins + +```typescript +import { ClineCore, createTool } from "@cline/sdk" +import { z } from "zod" + +const deployTool = createTool({ + name: "deploy", + description: "Deploy the application to the specified environment.", + inputSchema: z.object({ + environment: z.enum(["staging", "production"]), + }), + execute: async (input) => { + const result = await runDeployment(input.environment) + return { url: result.url, status: "deployed" } + }, +}) + +const cline = await ClineCore.create({ clientName: "my-app" }) + +await cline.start({ + prompt: "Deploy the app to staging", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + cwd: process.cwd(), + enableTools: true, + tools: [deployTool], + }, +}) +``` + +## Session with Plugins + +Load plugins inline with `extensions` and provide workspace context so plugins can access `ctx.workspaceInfo`: + +```typescript +import { ClineCore } from "@cline/sdk" +import myPlugin from "./my-plugin" + +const cline = await ClineCore.create({ + clientName: "my-app", + backendMode: "local", +}) + +await cline.start({ + prompt: "Do the thing my plugin enables", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + cwd: process.cwd(), + enableTools: true, + extensions: [myPlugin], + extensionContext: { + workspace: { rootPath: process.cwd(), cwd: process.cwd() }, + }, + }, +}) + +await cline.dispose() +``` + +For directory-based plugin packages, use `pluginPaths` instead: + +```typescript +config: { + pluginPaths: ["./my-cline-plugin"], + extensionContext: { + workspace: { rootPath: process.cwd(), cwd: process.cwd() }, + }, +} +``` + +See `../plugins/REFERENCE.md` for the full plugin authoring guide. + +## Session Listing and Replay + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) + +// List recent sessions +const sessions = await cline.list(10) +for (const session of sessions) { + console.log(`${session.id}: ${session.title}`) +} + +// Read messages from a past session +const messages = await cline.readMessages(sessions[0].id) +for (const msg of messages) { + console.log(`[${msg.role}] ${msg.content}`) +} + +// Check usage +const usage = await cline.getAccumulatedUsage(sessions[0].id) +console.log(`Total tokens: ${usage.aggregateUsage.totalInputTokens + usage.aggregateUsage.totalOutputTokens}`) +``` + +## Graceful Shutdown + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) + +process.on("SIGTERM", async () => { + await cline.dispose("SIGTERM received") + process.exit(0) +}) + +// Run sessions... +``` + +## Stateless Worker Pattern + +For request/response workloads (API endpoints, queue consumers): + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create({ + clientName: "worker", + backendMode: "local", +}) + +async function handleRequest(prompt: string, workspace: string) { + const session = await cline.start({ + prompt, + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + cwd: workspace, + enableTools: true, + }, + }) + + return { + text: session.result?.text, + usage: session.result?.usage, + sessionId: session.sessionId, + } +} +``` + +## Hub-Backed Multi-Client + +Multiple clients can attach to the same session: + +```typescript +// Process 1: start session +const cline = await ClineCore.create({ + clientName: "backend", + backendMode: "hub", +}) + +const session = await cline.start({ + prompt: "Long running refactor task", + config: { ... }, +}) + +// Process 2: attach and stream events +const viewer = await ClineCore.create({ + clientName: "dashboard", + backendMode: "hub", +}) + +viewer.subscribe((event) => { + dashboard.render(event) +}, { sessionId: session.sessionId }) +``` + +## See Also + +- `api.md` - Full API reference +- `gotchas.md` - Common pitfalls +- `../tools/REFERENCE.md` - Tool creation +- `../plugins/REFERENCE.md` - Plugin system +- `../scheduling/REFERENCE.md` - Scheduled agents diff --git a/.agents/skills/cline-sdk/references/events/REFERENCE.md b/.agents/skills/cline-sdk/references/events/REFERENCE.md new file mode 100644 index 0000000000..76f273a970 --- /dev/null +++ b/.agents/skills/cline-sdk/references/events/REFERENCE.md @@ -0,0 +1,269 @@ +# Events + +The Cline SDK has three event layers. Which one you use depends on whether you're working with the standalone `Agent` class or `ClineCore`. + +## Which Events Do I Get? + +| If you use... | You subscribe with... | You receive... | Text streaming event | +|---|---|---|---| +| Standalone `Agent` | `agent.subscribe()` | `AgentRuntimeEvent` | `assistant-text-delta` | +| `ClineCore` | `cline.subscribe()` | `CoreSessionEvent` | `chunk` (with `payload.type === "text"`) | + +These are different event types with different shapes. Do not mix them up. + +## Layer 1: AgentRuntimeEvent (Standalone Agent) + +Emitted by the `Agent` class via `agent.subscribe()`. This is what you get when using `new Agent(...)` directly. Every event includes a `snapshot` field with the current `AgentRuntimeStateSnapshot`. + +### Run Lifecycle + +```typescript +{ type: "run-started", snapshot } +{ type: "run-finished", snapshot, result: AgentRunResult } +{ type: "run-failed", snapshot, error: Error } +``` + +### Turns + +```typescript +{ type: "turn-started", snapshot, iteration: number } +{ type: "turn-finished", snapshot, iteration: number, toolCallCount: number } +``` + +### Text Streaming + +```typescript +// Streaming text delta (arrives as chunks during generation) +{ type: "assistant-text-delta", snapshot, iteration: number, text: string, accumulatedText: string } + +// Streaming reasoning delta (when model uses extended thinking) +{ type: "assistant-reasoning-delta", snapshot, iteration: number, text: string } + +// Complete assistant message after model finishes +{ type: "assistant-message", snapshot, iteration: number, message: AgentMessage, finishReason: string } +``` + +### Messages + +```typescript +// Fired when any message (user or assistant) is added to conversation history +{ type: "message-added", snapshot, message: AgentMessage } +``` + +### Tool Events + +```typescript +{ type: "tool-started", snapshot, toolCall: { toolName: string, toolCallId: string, input: unknown } } +{ type: "tool-updated", snapshot, toolCall: { toolName: string, toolCallId: string }, update: string } +{ type: "tool-finished", snapshot, toolCall: { toolName: string, toolCallId: string }, message: AgentMessage } +``` + +### Usage + +```typescript +{ + type: "usage-updated", + snapshot, + usage: { + inputTokens: number, + outputTokens: number, + cacheReadTokens?: number, + cacheWriteTokens?: number, + totalCost?: number, + }, +} +``` + +### Notices + +```typescript +{ type: "status-notice", snapshot, message: string, metadata?: Record } +``` + +### Subscribing + +Use `agent.subscribe()`. Register the listener before calling `run()` to avoid missing early events. + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + systemPrompt: "You are a helpful assistant.", + tools: [], +}) + +agent.subscribe((event) => { + switch (event.type) { + case "assistant-text-delta": + process.stdout.write(event.text) + break + case "tool-started": + console.log(`\nUsing tool: ${event.toolCall.toolName}`) + break + case "usage-updated": + console.log(`Cost: $${event.usage.totalCost?.toFixed(4)}`) + break + case "run-finished": + console.log(`\nDone: ${event.result.status}`) + break + } +}) + +const result = await agent.run("Hello!") +``` + +You can also receive events through hooks (these are awaited, so they can be async): + +```typescript +const agent = new Agent({ + ...config, + hooks: { + onEvent: async (event) => { + // Same AgentRuntimeEvent types as subscribe() + }, + }, +}) +``` + +## Layer 2: AgentEvent (ClineCore Internal) + +When using `ClineCore`, a `RuntimeEventAdapter` translates Layer 1 events into a legacy format called `AgentEvent`. You do not interact with this layer directly -- it is projected into `CoreSessionEvent` for subscribers. The key mappings: + +| AgentRuntimeEvent (Layer 1) | AgentEvent (Layer 2) | +|---|---| +| `turn-started` | `iteration_start` | +| `turn-finished` | `iteration_end` | +| `assistant-text-delta` | `content_start` (text) | +| `assistant-message` | `content_end` (text) | +| `tool-started` | `content_start` (tool) | +| `tool-updated` | `content_update` (tool) | +| `tool-finished` | `content_end` (tool) | +| `usage-updated` | `usage` (with computed deltas) | +| `run-finished` | `done` | +| `run-failed` | `error` | +| `run-started`, `message-added` | (suppressed, not emitted) | + +This layer exists for backwards compatibility. If you see event types like `content_update` or `iteration_start` in other documentation, they refer to this layer, not to what `agent.subscribe()` emits. + +## Layer 3: CoreSessionEvent (ClineCore Subscriber) + +Emitted by `ClineCore` via `cline.subscribe()`. These are higher-level session events. + +```typescript +type CoreSessionEvent = + | { type: "chunk"; payload: SessionChunkEvent } + | { type: "agent_event"; payload: { sessionId: string, event: AgentEvent } } + | { type: "ended"; payload: SessionEndedEvent } + | { type: "team_progress"; payload: SessionTeamProgressEvent } + | { type: "status"; payload: { sessionId: string, status: string } } + | { type: "hook"; payload: SessionToolEvent } +``` + +### SessionChunkEvent + +```typescript +interface SessionChunkEvent { + type: "text" | "reasoning" + text: string + sessionId: string +} +``` + +### SessionEndedEvent + +```typescript +interface SessionEndedEvent { + sessionId: string + finishReason: "completed" | "max_iterations" | "aborted" | "mistake_limit" | "error" + result?: AgentResult +} +``` + +### Subscribing + +```typescript +cline.subscribe((event) => { + switch (event.type) { + case "chunk": + if (event.payload.type === "text") { + process.stdout.write(event.payload.text) + } + break + case "ended": + console.log(`Finished: ${event.payload.finishReason}`) + break + } +}) +``` + +Filter by session: + +```typescript +cline.subscribe(handler, { sessionId: "specific-session-id" }) +``` + +## Hub Events (Layer 3b) + +When ClineCore runs in hub mode (via `backendMode: "hub"` or `"auto"` when a hub is available), events are projected over WebSocket using `HubEventName` types like `assistant.delta`, `iteration.started`, `tool.started`, etc. You do not interact with these directly -- `cline.subscribe()` still gives you `CoreSessionEvent` regardless of backend mode. + +## Result Type Differences + +The standalone Agent and ClineCore return different result types: + +| API | Result type | Text property | +|---|---|---| +| `agent.run()` | `AgentRunResult` | `result.outputText` | +| `cline.start()` / `cline.send()` | `AgentResult` | `result.text` | + +## Common Patterns + +### Streaming Text (Standalone Agent) + +```typescript +agent.subscribe((event) => { + if (event.type === "assistant-text-delta") { + process.stdout.write(event.text) + } +}) +``` + +### Streaming Text (ClineCore) + +```typescript +cline.subscribe((event) => { + if (event.type === "chunk" && event.payload.type === "text") { + process.stdout.write(event.payload.text) + } +}) +``` + +### Usage Tracking (Standalone Agent) + +```typescript +agent.subscribe((event) => { + if (event.type === "usage-updated" && event.usage.totalCost) { + console.log(`Running cost: $${event.usage.totalCost.toFixed(4)}`) + } +}) +``` + +### Tool Call Logging (Standalone Agent) + +```typescript +agent.subscribe((event) => { + if (event.type === "tool-started") { + console.log(`Tool started: ${event.toolCall.toolName}`) + } + if (event.type === "tool-finished") { + console.log(`Tool finished: ${event.toolCall.toolName}`) + } +}) +``` + +## See Also + +- `../agent/REFERENCE.md` - Agent runtime overview +- `../clinecore/REFERENCE.md` - ClineCore session management +- `../plugins/REFERENCE.md` - Plugin hooks for lifecycle events +- `../production/REFERENCE.md` - Observability in production diff --git a/.agents/skills/cline-sdk/references/multi-agent/REFERENCE.md b/.agents/skills/cline-sdk/references/multi-agent/REFERENCE.md new file mode 100644 index 0000000000..173cb1b24e --- /dev/null +++ b/.agents/skills/cline-sdk/references/multi-agent/REFERENCE.md @@ -0,0 +1,157 @@ +# Multi-Agent Coordination + +The Cline SDK supports two models for multi-agent work: sub-agents (parent-child) and teams (peer-to-peer). + +## Sub-Agents vs Teams + +| Feature | Sub-Agents | Teams | +|---------|-----------|-------| +| Enable with | `enableSpawnAgent: true` | `enableAgentTeams: true` | +| Persistence | Session-scoped only | Across sessions | +| Coordination | Parent-child hierarchy | Peer-to-peer | +| Shared state | None | Task board, mailbox, mission log | +| Best for | One-off delegation | Complex multi-session projects | + +## Sub-Agents + +Sub-agents are spawned by a parent agent during a run. They execute independently and report results back. + +### Enabling Sub-Agents + +```typescript +const cline = await ClineCore.create({ clientName: "my-app" }) + +await cline.start({ + prompt: "Refactor the auth module and update tests", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + enableSpawnAgent: true, + enableTools: true, + }, +}) +``` + +When `enableSpawnAgent` is true, the agent gets access to sub-agent tools: + +| Tool | Description | +|------|-------------| +| `start_subagent` | Spawn a background agent with a task | +| `message_subagent` | Send a message to a running sub-agent | +| `handoff_to_agent` | Delegate the current task entirely | +| `submit_and_exit` | Signal completion | + +### How Sub-Agents Work + +1. The parent agent decides a subtask can be delegated +2. It calls `start_subagent` with a role, task description, and optionally a preset +3. The sub-agent runs independently in the background +4. The parent can check status or send follow-up messages +5. Sub-agent results are available to the parent when complete + +## Teams + +Teams provide persistent, cross-session coordination between agents. + +### Enabling Teams + +```typescript +await cline.start({ + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + enableAgentTeams: true, + teamName: "auth-sprint", + enableTools: true, + }, +}) +``` + +### Team Tools + +When `enableAgentTeams` is true, the coordinator agent gets: + +| Tool | Description | +|------|-------------| +| `team_spawn_teammate` | Create a new agent with a role and task | +| `team_delegate_task` | Assign a task to an existing teammate | +| `team_check_status` | Check on a delegated task's progress | +| `team_get_result` | Get the completed result from a teammate | + +### Team Persistence + +Teams store shared state in: + +``` +~/.cline/data/teams/[team-name]/ + task-board.json # task assignments and status + mailbox.json # inter-agent messages + mission-log.json # coordination log +``` + +This state persists across sessions, so team members can pick up where they left off. + +### CLI Team Access + +```bash +cline --team-name auth-sprint "Continue the auth refactor" +``` + +## Choosing Between Sub-Agents and Teams + +Use sub-agents when: +- You need one-off parallel execution within a single session +- Tasks are independent and don't need to communicate with each other +- Results only matter to the parent agent + +Use teams when: +- Work spans multiple sessions over time +- Agents need to coordinate and share progress +- Tasks have dependencies between them +- You want a persistent record of multi-agent collaboration + +## Patterns + +### Parallel Research with Sub-Agents + +A parent agent spawns multiple sub-agents to research different topics simultaneously: + +```typescript +await cline.start({ + prompt: `Research these three topics in parallel: + 1. Current best practices for JWT auth + 2. OAuth 2.0 provider comparison + 3. Session management patterns + Spawn a sub-agent for each topic, then synthesize the results.`, + config: { + enableSpawnAgent: true, + enableTools: true, + // ... + }, +}) +``` + +### Team Sprint + +A coordinator manages a multi-session project: + +```typescript +await cline.start({ + prompt: `You are the coordinator for the auth-sprint team. + Review the task board and delegate the next highest-priority task + to a teammate. Check status on any in-progress tasks.`, + config: { + enableAgentTeams: true, + teamName: "auth-sprint", + enableTools: true, + // ... + }, +}) +``` + +## See Also + +- `../clinecore/REFERENCE.md` - ClineCore runtime +- `../clinecore/api.md` - Session config for teams +- `../tools/REFERENCE.md` - Tool system +- `../plugins/REFERENCE.md` - Plugin system diff --git a/.agents/skills/cline-sdk/references/plugins/REFERENCE.md b/.agents/skills/cline-sdk/references/plugins/REFERENCE.md new file mode 100644 index 0000000000..6bcc4bf1a7 --- /dev/null +++ b/.agents/skills/cline-sdk/references/plugins/REFERENCE.md @@ -0,0 +1,649 @@ +# Plugins + +A Cline plugin is a TypeScript module that extends any agent built on the Cline SDK. The same plugin runs in the Cline CLI, VS Code and JetBrains extensions, and any custom app built on `@cline/core`. + +A plugin can: + +- Register tools the model can call. +- Hook into the agent loop before/after runs, model calls, and tool calls. +- Rewrite provider messages before they hit the model (custom compaction, redaction, context shaping). +- Register slash commands, prompt rules, providers, and automation event types. + +A plugin ships in one of two shapes: + +1. Single-file plugin -- one `.ts` file that exports a default plugin object. Drop it in a discovery folder and it loads. +2. Plugin package -- a directory with `package.json`, npm dependencies, and optionally bundled assets. Installable via `cline plugin install`. + +Both shapes use the same plugin API. + +## The Mental Model + +When the host starts a session, it builds a registry of plugins and runs four phases: + +1. resolve -- collect the plugin objects. +2. validate -- check each plugin's `manifest`. Capabilities must be non-empty; declared hook stages must have matching handlers; if `hooks` is present, `"hooks"` must be in `capabilities`. +3. setup -- call each plugin's `setup(api, ctx)` once. This is where you `registerTool`, `registerCommand`, etc. +4. activate -- registry is frozen, the agent loop starts, and your hooks/tools are live. + +Two invariants the registry enforces: + +- Every contribution requires a matching capability. Calling `api.registerRule(...)` without `"rules"` in `manifest.capabilities` throws. +- Capabilities and handlers must agree. Declaring `"hooks"` without a `hooks` object, or vice versa, fails validation. + +After validation, registration is one-shot -- no dynamic register/unregister during the session. + +## The Smallest Working Plugin + +```typescript +import type { AgentPlugin } from "@cline/core" +import { createTool } from "@cline/core" + +const plugin: AgentPlugin = { + name: "hello-plugin", + manifest: { + capabilities: ["tools"], + }, + setup(api, ctx) { + api.registerTool( + createTool({ + name: "say_hello", + description: "Greet a person by name.", + inputSchema: { + type: "object", + properties: { name: { type: "string" } }, + required: ["name"], + }, + async execute({ name }: { name: string }) { + return { greeting: `Hello, ${name}!` } + }, + }), + ) + }, +} + +export default plugin +``` + +The agent will see `say_hello` as a callable tool. + +## The Manifest + +```typescript +manifest: { + capabilities: ["tools", "hooks"], // required, non-empty array + paths?: string[], // optional, multi-entry packages + providerIds?: string[], // optional, provider plugins + modelIds?: string[], // optional, model plugins +} +``` + +### The Complete Capability List + +| Capability | What It Unlocks in `api` | +|-----------|--------------------------| +| `"tools"` | `api.registerTool()` | +| `"commands"` | `api.registerCommand()` (slash commands in chat surfaces) | +| `"rules"` | `api.registerRule()` (string injected into the system prompt) | +| `"messageBuilders"` | `api.registerMessageBuilder()` (rewrites provider-bound messages) | +| `"providers"` | `api.registerProvider()` (custom model provider) | +| `"automationEvents"` | `api.registerAutomationEventType()` and `ctx.automation?.ingestEvent()` | +| `"hooks"` | The runtime `hooks` object on the plugin (lifecycle callbacks) | + +You declare any combination -- most real plugins need 1-3 capabilities. + +## setup(api, ctx) -- The Registration Phase + +`setup()` runs once per session before the agent loop starts. Everything you register here is frozen for the lifetime of the session. + +### The api Object + +Each `register*` method requires the matching capability in your manifest: + +```typescript +api.registerTool(tool) // requires "tools" +api.registerCommand({ name, description, handler }) // requires "commands" +api.registerRule({ id, content, source }) // requires "rules" +api.registerMessageBuilder({ name, build }) // requires "messageBuilders" +api.registerProvider({ name, description }) // requires "providers" +api.registerAutomationEventType({ eventType, source }) // requires "automationEvents" +``` + +### The ctx Object -- Host-Provided Session Context + +The second argument carries everything the host knows about the current session. All fields are optional, so feature-detect before using them -- the same plugin must work in hosts that supply less context (unit tests, sandboxed plugin processes). + +```typescript +ctx.session?.sessionId // string, stable core session id +ctx.client?.name // host: "cline-cli", "cline-vscode", etc. +ctx.user // authenticated user/org info, when available +ctx.workspaceInfo // { rootPath, hint, latestGitBranchName, + // latestGitCommitHash, associatedRemoteUrls } +ctx.automation?.ingestEvent // emit normalized automation events +ctx.logger?.log // structured logger scoped to this plugin +ctx.telemetry // ITelemetryService, only present in-process +``` + +Two rules about `ctx.workspaceInfo`: + +1. Always prefer `ctx.workspaceInfo?.rootPath` over `process.cwd()`. The CLI may have been launched with `--cwd` without calling `chdir`, and VS Code workspaces don't share a single CWD. `workspaceInfo` is sourced from the session config and is always correct. +2. Don't use `import.meta.url` tricks to find "the workspace". That gives you the plugin's own location, not the user's project. + +### Persisting State Across Hooks + +`setup()` runs first; hooks fire later. The simplest way to share state is module-level variables: + +```typescript +let sessionWorkspaceRoot: string | undefined +let sessionBranch: string | undefined + +const plugin: AgentPlugin = { + name: "metrics", + manifest: { capabilities: ["hooks"] }, + setup(api, ctx) { + sessionWorkspaceRoot = ctx.workspaceInfo?.rootPath + sessionBranch = ctx.workspaceInfo?.latestGitBranchName + }, + hooks: { + beforeTool({ toolCall, input }) { + if (sessionBranch === "main" && toolCall.toolName === "run_commands") { + // inspect input, optionally block + } + return undefined + }, + }, +} +``` + +A single Node process may host multiple sessions concurrently. If your plugin will run in a multi-session host, key your state by `ctx.session?.sessionId`: + +```typescript +const stateBySession = new Map() +setup(api, ctx) { + const id = ctx.session?.sessionId + if (id) stateBySession.set(id, /* ... */) +} +``` + +## Runtime Hooks + +Runtime hooks are typed in-process callbacks on the same hook layer the runtime uses internally. They run inside the agent loop with full type information -- no IPC, no JSON marshaling. + +Declare `"hooks"` in `manifest.capabilities`, then add a `hooks` property: + +```typescript +const plugin: AgentPlugin = { + name: "metrics", + manifest: { capabilities: ["hooks"] }, + hooks: { + beforeRun(ctx) { /* ... */ }, + beforeTool({ toolCall, input }) { /* ... */ }, + afterTool({ toolCall, result }) { /* ... */ }, + afterRun({ result }) { /* ... */ }, + onEvent(event) { /* ... */ }, + }, +} +``` + +### The Seven Hooks + +| Hook | Fires | Can Stop the Loop? | Common Uses | +|------|-------|--------------------|-------------| +| `beforeRun` | Before the runtime loop starts | Yes | Greet, log, attach session metadata | +| `afterRun` | After the runtime loop finishes (success, abort, or fail) | No | Notifications, metrics, persistent logs | +| `beforeModel` | Before each model request | Yes (mutate req) | Inject context, last-mile prompt edits | +| `afterModel` | After each model response, before tool execution | Yes | Block based on model output | +| `beforeTool` | Before each tool execution | Yes (`{ stop }`) | Audit, redact, block dangerous tools | +| `afterTool` | After each tool execution | Can replace result | Post-process, redact secrets in tool output | +| `onEvent` | On every `AgentRuntimeEvent` emitted by the runtime | No | Streaming UIs, telemetry pipes | + +### Stopping the Loop from a Hook + +Several hooks return an optional control object. The most common pattern is `beforeTool` blocking a destructive tool call: + +```typescript +beforeTool({ toolCall, input }) { + if (toolCall.toolName === "run_commands") { + const { commands } = input as { commands?: string[] } + if (sessionBranch === "main" && commands?.some(c => c.startsWith("git push"))) { + return { stop: true, reason: "Blocked git push on protected branch" } + } + } + return undefined // explicit "continue" +} +``` + +Returning `undefined` (or omitting `return`) lets execution continue normally. + +### afterRun Semantics + +`afterRun` fires for every terminal status -- `completed`, `aborted`, `failed`. If you only want to act on success: + +```typescript +afterRun({ result }) { + if (result.status !== "completed") return + // notify, log success metrics, etc. +} +``` + +### Plugin Hooks vs File Hooks + +The runtime supports two hook systems: + +- File hooks -- external scripts in `.cline/hooks/` invoked with serialized JSON. Right for user/workspace-specific scripts that don't ship with code. +- Plugin runtime hooks -- typed in-process callbacks. Right when the behavior belongs to a reusable extension and needs typed access to the runtime. + +Core adapts file hooks onto the runtime hook layer, so you don't need both. If you're shipping a plugin, write it as runtime hooks. + +## Message Builders + +Message builders rewrite the provider-bound message list before the model call. They run after runtime messages are converted into SDK message blocks but before core's built-in safety builder. + +Use them for: + +- Custom compaction policies (replace middle history with a summary). +- Redacting PII or secrets before they reach the provider. +- Reshaping context for a specific model's strengths. + +```typescript +api.registerMessageBuilder({ + name: "summarize-middle-history", + build(messages) { + if (estimateTokens(messages) < THRESHOLD) return messages + return [...prefix, summary, ...recent] + }, +}) +``` + +Multiple builders run in registration order; the output of one is the input of the next. + +When to use `beforeModel` instead: reach for the `beforeModel` hook only if you need the runtime snapshot or want to mutate the request object itself. Pure message rewrites belong in a builder. + +## Automation Events + +Plugins can declare normalized event types and emit them into Cline automation. Hosts that don't have automation enabled simply ignore both -- feature-detect `ctx.automation`. + +```typescript +manifest: { capabilities: ["automationEvents"] }, + +setup(api, ctx) { + api.registerAutomationEventType({ + eventType: "github.pull_request.opened", + source: "github", + description: "A new GitHub PR was opened", + attributesSchema: { /* JSON Schema for envelope.attributes */ }, + }) + + if (!ctx.automation) return // host has no automation + ctx.automation.ingestEvent({ + eventId: "pr-1234", + eventType: "github.pull_request.opened", + source: "github", + subject: "owner/repo#1234", + occurredAt: new Date().toISOString(), + attributes: { /* ... */ }, + }) +} +``` + +## Loading a Plugin + +There are three ways a plugin gets into a session: + +### Auto-Discovery (CLI) + +The CLI scans these directories on startup: + +- `/.cline/plugins/` -- project-scoped plugins. +- `~/.cline/plugins/` -- user-scoped plugins. + +Drop a `.ts` or `.js` file in, run `cline`, done: + +```bash +mkdir -p .cline/plugins +cp my-plugin.ts .cline/plugins/ +cline -i "do the thing my plugin enables" +``` + +### Explicit extensions in SDK Config + +When you build your own host with `ClineCore`, pass the plugin object directly: + +```typescript +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], + extensionContext: { + workspace: { rootPath: process.cwd(), cwd: process.cwd() }, + }, + }, + prompt: "...", + interactive: false, +}) +``` + +### pluginPaths for Directory-Based Plugins + +When the plugin is a directory with `package.json`, point `pluginPaths` at the directory: + +```typescript +config: { + pluginPaths: ["./path/to/my-plugin-package"], +} +``` + +Or install with the CLI: + +```bash +cline plugin install ./path/to/my-plugin-package +cline plugin install @scope/my-cline-plugin # from npm +cline plugin install --git github.com/owner/repo # from git +``` + +## Single-File Plugin Template + +Save as `my-plugin.ts`, drop in `.cline/plugins/`: + +```typescript +import { type AgentPlugin, ClineCore, createTool } from "@cline/core" + +let sessionRoot: string | undefined + +const plugin: AgentPlugin = { + name: "my-plugin", + manifest: { + capabilities: ["tools", "hooks"], + }, + + setup(api, ctx) { + sessionRoot = ctx.workspaceInfo?.rootPath + + api.registerTool( + createTool({ + name: "do_thing", + description: "Do the thing this plugin exists for.", + inputSchema: { + type: "object", + properties: { target: { type: "string" } }, + required: ["target"], + }, + async execute(input) { + const { target } = input as { target: string } + return { ok: true, target, root: sessionRoot } + }, + }), + ) + }, + + hooks: { + beforeRun() { + console.log("[my-plugin] run started") + }, + afterRun({ result }) { + if (result.status !== "completed") return + console.log(`[my-plugin] done in ${result.iterations} iteration(s)`) + }, + }, +} + +async function runDemo(): Promise { + const host = await ClineCore.create({ backendMode: "local" }) + try { + const result = 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. Use tools when needed.", + extensions: [plugin], + extensionContext: { + workspace: { rootPath: process.cwd(), cwd: process.cwd() }, + }, + }, + prompt: "Use do_thing on the target 'world'.", + interactive: false, + }) + console.log(result.result?.text ?? "") + } finally { + await host.dispose() + } +} + +if (import.meta.main) { + await runDemo() +} + +export { plugin, runDemo } +export default plugin +``` + +Copy it, rename the tool, swap in your logic. The `runDemo()` function lets you test with `ANTHROPIC_API_KEY=sk-... bun run my-plugin.ts`. + +## Plugin Package + +Use a plugin package when you need npm dependencies, multiple entry points, bundled assets, or npm/git distribution. + +### Layout + +``` +my-cline-plugin/ ++-- package.json ++-- tsconfig.json (optional, for local typechecking) ++-- index.ts (the plugin entry point) ++-- README.md ++-- assets/ (optional, bundled content) + +-- templates/ + +-- schemas/ +``` + +### package.json -- The Discovery Contract + +```json +{ + "name": "my-cline-plugin", + "version": "0.1.0", + "private": true, + "description": "What this plugin does, in one sentence.", + "type": "module", + "exports": { + ".": "./index.ts" + }, + "cline": { + "plugins": [ + { + "paths": ["./index.ts"], + "capabilities": ["tools", "hooks"] + } + ] + }, + "peerDependencies": { + "@cline/core": "*" + }, + "peerDependenciesMeta": { + "@cline/core": { "optional": true } + }, + "dependencies": { + "zod": "^4.1.5" + } +} +``` + +Key fields: + +- `type: "module"` -- required. Cline plugins are ES modules. +- `cline.plugins` -- the discovery contract. Array of entries, each with `paths` (entry files) and `capabilities` (pre-declared, validated before importing). +- `peerDependencies` for `@cline/core` -- the host already provides it. Marking it optional lets you typecheck in isolation. + +### Bundling Assets + +Resolve asset paths with `import.meta.url`, not `process.cwd()`: + +```typescript +import { dirname, join } from "node:path" +import { fileURLToPath } from "node:url" +import { readFileSync, existsSync } from "node:fs" + +const MODULE_DIR = dirname(fileURLToPath(import.meta.url)) +const TEMPLATES_DIR = join(MODULE_DIR, "assets", "templates") + +function loadTemplate(name: string): string | undefined { + const path = join(TEMPLATES_DIR, `${name}.md`) + return existsSync(path) ? readFileSync(path, "utf8") : undefined +} +``` + +This is the only place `import.meta.url` is appropriate in a plugin -- locating files inside the plugin package. For workspace paths, always use `ctx.workspaceInfo?.rootPath`. + +### The Override Pattern (Bundled / Global / Project) + +A package can ship default assets and let users override them. The convention is a three-tier lookup, last write wins by `name`: + +1. bundled -- files inside the plugin package (defaults shipped with the plugin). +2. global -- files under `~/.cline/data/settings//` (user overrides). +3. project -- files under `/.cline//` (project overrides). + +### Multiple Plugin Entries + +If your package exposes more than one plugin, list each in `cline.plugins`: + +```json +"cline": { + "plugins": [ + { "paths": ["./tools-plugin.ts"], "capabilities": ["tools"] }, + { "paths": ["./hooks-plugin.ts"], "capabilities": ["hooks"] } + ] +} +``` + +Each entry file should `export default` its own plugin object. + +## Testing Your Plugin + +### Unit Tests + +The plugin object is plain data. Drive `setup()` against a minimal context and exercise tools directly: + +```typescript +import plugin from "../my-plugin" + +const tools: unknown[] = [] +const api = { + registerTool: (t: unknown) => tools.push(t), + registerCommand: () => {}, + registerRule: () => {}, + registerMessageBuilder: () => {}, + registerProvider: () => {}, + registerAutomationEventType: () => {}, +} +await plugin.setup?.(api as never, { + workspaceInfo: { rootPath: "/tmp/fake-workspace" }, +}) + +// Now `tools` contains the registered tools -- call tool.execute(input, ctx) +``` + +### End-to-End with runDemo() + +Add a `runDemo()` in your plugin file (see the single-file template above) that boots a real `ClineCore` session: + +```bash +ANTHROPIC_API_KEY=sk-... bun run my-plugin.ts +``` + +### CLI Smoke Test + +```bash +mkdir -p .cline/plugins +cp my-plugin.ts .cline/plugins/ +cline -i "trigger something that exercises the plugin" +``` + +For packages: + +```bash +cline plugin install ./my-cline-plugin +cline -i "..." +``` + +If the plugin fails validation or setup, the CLI prints a clear error and continues without it. + +## Common Gotchas + +- "capabilities must be a non-empty array" -- you forgot `manifest.capabilities`, or it's `[]`. +- "registerRule requires the 'rules' capability" -- capability/handler drift. Add `"rules"` to capabilities, or stop calling `registerRule`. +- Tool not visible to the model -- check `enableTools: true` on the session config, and that you're declaring `"tools"` in capabilities. +- `ctx.workspaceInfo` is undefined in SDK tests -- the host didn't pass `extensionContext.workspace`. In SDK code, set it explicitly (see the ClineCore loading example above). +- State leaking across sessions -- module-level variables are shared across sessions in the same process. Key by `ctx.session?.sessionId` if your host runs multiple sessions concurrently. +- `afterRun` firing on aborts -- guard with `if (result.status !== "completed") return`. +- Heavy work in `setup()` -- `setup()` blocks session start. Defer expensive work into the first tool call or `beforeRun`. +- Importing host internals -- only import from `@cline/core`. Reaching into host-specific packages (e.g. CLI internals) will break in non-CLI hosts. +- Sandboxed plugins and `telemetry` -- telemetry is process-local. Feature-detect `ctx.telemetry` and expect it to be undefined in sandboxed plugin processes. +- Resolving bundled assets -- use `import.meta.url` + `fileURLToPath` to find files inside your package; never `process.cwd()`. For workspace paths, do the opposite: use `ctx.workspaceInfo?.rootPath`, never `import.meta.url`. +- Plugin name collisions -- `name` must be unique within a session. If two plugins share a name, validation fails. Namespace by package (`my-org-redactor`, not `redactor`). + +## Decision Guide -- Which Extension Point? + +| You want to... | Use | +|----------------|-----| +| Give the model a new capability | `registerTool` | +| Add a slash command in chat surfaces | `registerCommand` | +| Inject text into the system prompt | `registerRule` | +| Rewrite messages before they hit the provider | `registerMessageBuilder` | +| Add a custom model provider | `registerProvider` | +| Emit normalized cron/webhook events | `registerAutomationEventType` + `ctx.automation` | +| Observe or steer the agent loop | `hooks.*` | +| Block a dangerous tool call | `hooks.beforeTool` returning `{ stop: true }` | +| Notify on completion | `hooks.afterRun` (gate on `status === "completed"`) | +| Tweak each model request | `hooks.beforeModel` | +| Stream events to a UI | `hooks.onEvent` | +| Ship reusable templates with the plugin | Bundle assets next to `index.ts`, resolve via `import.meta.url` | +| Let users override defaults globally or per-project | Three-tier lookup: bundled / global / project | + +## Pre-Ship Checklist + +- `manifest.capabilities` is a non-empty array. +- Every `api.register*` call has a matching capability declared. +- If `hooks` is present, `"hooks"` is in `capabilities`. +- `ctx.workspaceInfo?.rootPath` is used for workspace paths (not `process.cwd()`). +- Optional `ctx` fields are feature-detected. +- Tool names are snake_case verbs; descriptions are written for the model. +- Tool inputs have JSON Schema with `required` set. +- `afterRun` handlers gate on `result.status === "completed"` if they only want successes. +- State that must not leak between concurrent sessions is keyed by `ctx.session?.sessionId`. +- (Package) `package.json` has `type: "module"`, `cline.plugins`, and `@cline/core` as an optional peer dep. +- (Package) Bundled assets resolved via `import.meta.url`, not `process.cwd()`. +- Smoke test: drop the plugin into `.cline/plugins/` (or `cline plugin install`), run `cline -i "..."`, watch it work. + +## Plugin Examples from SDK + +The SDK repo includes these example plugins: + +| Plugin | Description | +|--------|-------------| +| `weather-metrics.ts` | Tool registration + lifecycle metrics | +| `mac-notify.ts` | macOS Notification Center alerts | +| `custom-compaction.ts` | Custom message compaction via message builders | +| `background-terminal.ts` | Detached shell job management | +| `automation-events.ts` | Plugin-emitted automation events | +| `gitignore-read-files-guard.ts` | File access policy enforcement via beforeTool | +| `web-search.ts` | Web search via Exa API | +| `typescript-lsp/` | TypeScript Language Service tools (plugin package) | +| `agents-squad/` | Multi-agent team orchestration (plugin package) | + +## See Also + +- `../tools/REFERENCE.md` - Tool creation +- `../events/REFERENCE.md` - Event system +- `../agent/REFERENCE.md` - Using plugins with Agent +- `../clinecore/REFERENCE.md` - Using plugins with ClineCore diff --git a/.agents/skills/cline-sdk/references/production/REFERENCE.md b/.agents/skills/cline-sdk/references/production/REFERENCE.md new file mode 100644 index 0000000000..5be32e6d8b --- /dev/null +++ b/.agents/skills/cline-sdk/references/production/REFERENCE.md @@ -0,0 +1,253 @@ +# Going to Production + +Guidelines for deploying Cline SDK agents in production environments. + +## Error Handling + +Always check the result status: + +```typescript +const result = await agent.run(input) + +switch (result.status) { + case "completed": + console.log("Success:", result.outputText) + break + case "aborted": + console.log("Cancelled:", result.error?.message) + break + case "failed": + console.error("Failed:", result.error) + break +} +``` + +For ClineCore, check `finishReason`: + +```typescript +const session = await cline.start({ ... }) + +switch (session.result?.finishReason) { + case "completed": + // normal completion + break + case "max_iterations": + // agent hit iteration limit + break + case "aborted": + // manually cancelled + break + case "mistake_limit": + // too many tool errors + break + case "error": + // unrecoverable error + break +} +``` + +## Cost Control + +### Token Limits + +Set maximum tokens per turn and iteration limits: + +```typescript +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + maxTokensPerTurn: 4096, + maxIterations: 10, + tools: [...], +}) +``` + +### Model Selection + +Use cheaper models for simple tasks: + +```typescript +// Simple classification or formatting +{ providerId: "anthropic", modelId: "claude-haiku-4-5" } + +// Complex reasoning and code generation +{ providerId: "anthropic", modelId: "claude-sonnet-4-6" } + +// Hardest tasks requiring deep reasoning +{ providerId: "anthropic", modelId: "claude-opus-4-7" } +``` + +### Usage Tracking + +Monitor spending in real time: + +```typescript +agent.subscribe((event) => { + if (event.type === "usage-updated" && event.usage.totalCost) { + if (event.usage.totalCost > MAX_BUDGET) { + agent.abort("Budget exceeded") + } + } +}) +``` + +## Observability + +### OpenTelemetry Integration + +The SDK supports OpenTelemetry for traces, metrics, and logs: + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create({ + clientName: "my-app", + // OpenTelemetry config is picked up from environment + // OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, etc. +}) +``` + +### Structured Logging + +Use the `BasicLogger` interface for injectable logging: + +```typescript +import type { BasicLogger } from "@cline/sdk" + +const logger: BasicLogger = { + debug: (msg, meta) => console.debug(msg, meta), + log: (msg, meta) => console.log(msg, meta), + error: (msg, meta) => console.error(msg, meta), +} + +await cline.start({ + config: { + logger, + // ... + }, +}) +``` + +### Custom Metrics via Plugins + +```typescript +const metricsPlugin: AgentPlugin = { + name: "metrics", + manifest: { capabilities: ["hooks"] }, + setup() {}, + hooks: { + beforeRun() { + metrics.increment("agent.runs.started") + }, + afterRun({ result }) { + metrics.increment("agent.runs.completed") + metrics.histogram("agent.iterations", result.iterations) + metrics.histogram("agent.tokens.output", result.usage.outputTokens) + }, + beforeTool({ toolCall }) { + metrics.increment(`agent.tools.${toolCall.toolName}`) + }, + }, +} +``` + +## Security + +### Sandbox Tool Execution + +Validate tool inputs to prevent path traversal and injection: + +```typescript +execute: async (input) => { + const safePath = path.resolve(WORKSPACE_ROOT, input.path) + if (!safePath.startsWith(WORKSPACE_ROOT)) { + return { error: "Path traversal attempt blocked" } + } + return await readFile(safePath, "utf-8") +} +``` + +### API Key Management + +- Use environment variables, never hardcode keys +- Rotate keys regularly +- Use different keys for development and production + +```typescript +{ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, // never a literal string +} +``` + +### Tool Policy Hardening + +Disable tools you don't need and require approval for dangerous ones: + +```typescript +toolPolicies: { + read_files: { autoApprove: true }, + search: { autoApprove: true }, + bash: { autoApprove: false }, // require approval + editor: { autoApprove: false }, + apply_patch: { autoApprove: false }, + fetch_web: { enabled: false }, // disable entirely +} +``` + +## Deployment Patterns + +### Stateless Worker + +For request/response workloads (API endpoints, queue consumers): + +```typescript +const cline = await ClineCore.create({ + clientName: "worker", + backendMode: "local", +}) + +app.post("/agent", async (req, res) => { + const session = await cline.start({ + prompt: req.body.prompt, + config: { ... }, + }) + res.json({ text: session.result?.text, usage: session.result?.usage }) +}) +``` + +### Persistent Service + +For long-running services with session management: + +```typescript +const cline = await ClineCore.create({ + clientName: "service", + backendMode: "hub", +}) + +process.on("SIGTERM", async () => { + await cline.dispose("SIGTERM") + process.exit(0) +}) +``` + +### Scheduled Automation + +See `../scheduling/REFERENCE.md` for recurring agent tasks. + +## Retry and Resilience + +- Tool `execute` functions support `retryable: true` (default) and `maxRetries: 3` (default) +- Provider API calls are retried automatically on transient failures +- Use `timeoutMs` on tools to prevent hanging +- Monitor `mistake_limit` finish reason to detect systematic tool failures + +## See Also + +- `../agent/REFERENCE.md` - Agent overview +- `../clinecore/REFERENCE.md` - ClineCore overview +- `../tools/REFERENCE.md` - Tool configuration +- `../plugins/REFERENCE.md` - Metrics plugins +- `../scheduling/REFERENCE.md` - Scheduled agents diff --git a/.agents/skills/cline-sdk/references/providers/REFERENCE.md b/.agents/skills/cline-sdk/references/providers/REFERENCE.md new file mode 100644 index 0000000000..1ad2dc72c0 --- /dev/null +++ b/.agents/skills/cline-sdk/references/providers/REFERENCE.md @@ -0,0 +1,257 @@ +# Model Providers + +The Cline SDK supports every major LLM provider out of the box via `@cline/llms`. + +## Supported Providers + +| Provider ID | Models | +|-------------|--------| +| `"anthropic"` | Claude Opus 4.7, Sonnet 4.6, Haiku 4.5 | +| `"openai"` | GPT-5.5, GPT-5.3 Codex | +| `"gemini"` | Gemini 3.1 Pro Preview, Gemini 3 Flash Preview | +| `"vertex"` | Google models via Vertex AI | +| `"bedrock"` | Claude, Llama via AWS Bedrock | +| `"mistral"` | Mistral Large, Codestral | +| `"openai-compatible"` | vLLM, Together, Fireworks, Groq, etc. | + +## Basic Configuration + +### With Agent + +```typescript +import { Agent } from "@cline/sdk" + +const agent = new Agent({ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + systemPrompt: "You are a helpful assistant.", + tools: [], +}) +``` + +### With ClineCore + +```typescript +import { ClineCore } from "@cline/sdk" + +const cline = await ClineCore.create({ clientName: "my-app" }) + +await cline.start({ + prompt: "Hello", + config: { + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.ANTHROPIC_API_KEY, + }, +}) +``` + +## Provider-Specific Configuration + +### Anthropic + +```typescript +{ + providerId: "anthropic", + modelId: "claude-opus-4-7", // or "claude-sonnet-4-6", "claude-haiku-4-5" + apiKey: process.env.ANTHROPIC_API_KEY, +} +``` + +### OpenAI + +```typescript +{ + providerId: "openai", + modelId: "gpt-5.5", + apiKey: process.env.OPENAI_API_KEY, +} +``` + +### Google (Gemini) + +```typescript +{ + providerId: "gemini", + modelId: "gemini-3.1-pro-preview", + apiKey: process.env.GOOGLE_API_KEY, +} +``` + +### Google (Vertex AI) + +```typescript +{ + providerId: "vertex", + modelId: "gemini-3.1-pro-preview", + // Uses application default credentials or service account +} +``` + +### AWS Bedrock + +```typescript +{ + providerId: "bedrock", + modelId: "anthropic.claude-sonnet-4-6", + // Uses AWS credential chain (env vars, config file, IAM role) + // Set AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY +} +``` + +### Mistral + +```typescript +{ + providerId: "mistral", + modelId: "mistral-large-latest", + apiKey: process.env.MISTRAL_API_KEY, +} +``` + +### OpenAI-Compatible + +For any provider with an OpenAI-compatible API: + +```typescript +{ + providerId: "openai-compatible", + modelId: "my-model", + apiKey: process.env.API_KEY, + baseUrl: "https://api.together.xyz/v1", +} +``` + +Works with: vLLM, Together AI, Fireworks, Groq, Ollama, LiteLLM, etc. + +## Custom Base URL + +Override the API endpoint for any provider: + +```typescript +{ + providerId: "anthropic", + modelId: "claude-sonnet-4-6", + apiKey: process.env.API_KEY, + baseUrl: "https://my-proxy.example.com/v1", +} +``` + +## Custom Headers + +Pass additional headers to API requests: + +```typescript +{ + providerId: "openai", + modelId: "gpt-5.5", + apiKey: process.env.API_KEY, + headers: { + "X-Custom-Header": "value", + }, +} +``` + +## Gateway API + +For advanced multi-provider setups, use the Gateway directly: + +```typescript +import { createGateway, DefaultGateway } from "@cline/llms" + +const gateway = createGateway({ + providerConfigs: [ + { providerId: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY }, + { providerId: "openai", apiKey: process.env.OPENAI_API_KEY }, + ], +}) + +// Create a model for a specific provider +const model = gateway.createAgentModel({ + providerId: "anthropic", + modelId: "claude-opus-4-7", +}) + +// Use with Agent +const agent = new Agent({ model, systemPrompt: "...", tools: [] }) +``` + +### Gateway Methods + +```typescript +gateway.registerProvider(registration) // add a custom provider +gateway.configureProvider(config) // update provider settings +gateway.listProviders() // list available providers +gateway.listModels(providerId?) // list available models +gateway.createAgentModel(selection) // create model for agent +gateway.stream(request) // raw streaming (AsyncIterable) +``` + +## Provider Registry + +Query and register providers programmatically: + +```typescript +import { + getAllProviders, + getProviderIds, + getProvider, + getModelsForProvider, + registerProvider, + registerModel, + createHandler, +} from "@cline/llms" + +// List all registered providers +const providers = getAllProviders() + +// Get models for a provider +const models = getModelsForProvider("anthropic") + +// Register a custom provider +registerProvider({ + id: "my-provider", + name: "My Custom Provider", + handler: createHandler({ ... }), +}) +``` + +## Model Metadata + +Access model info (context window, pricing, capabilities): + +```typescript +import { getModelsForProvider } from "@cline/llms" + +const models = getModelsForProvider("anthropic") +for (const model of models) { + console.log(`${model.id}: context=${model.contextWindow}, input=$${model.inputPrice}/MTok`) +} +``` + +## Cost Tracking + +Track per-request and cumulative costs: + +```typescript +// Via events +agent.subscribe((event) => { + if (event.type === "usage-updated") { + console.log(`Cost: $${event.usage.totalCost?.toFixed(4)}`) + } +}) + +// Via result +const result = await agent.run("...") +console.log(`Total cost: $${result.usage.totalCost?.toFixed(4)}`) + +// Via ClineCore accumulated usage +const usage = await cline.getAccumulatedUsage(sessionId) +``` + +## See Also + +- `../agent/REFERENCE.md` - Using providers with Agent +- `../clinecore/REFERENCE.md` - Using providers with ClineCore +- `../production/REFERENCE.md` - Cost control in production diff --git a/.agents/skills/cline-sdk/references/scheduling/REFERENCE.md b/.agents/skills/cline-sdk/references/scheduling/REFERENCE.md new file mode 100644 index 0000000000..295e88452c --- /dev/null +++ b/.agents/skills/cline-sdk/references/scheduling/REFERENCE.md @@ -0,0 +1,227 @@ +# Scheduling and Automation + +The Cline SDK supports scheduled, one-off, and event-driven agent execution through the automation subsystem in `@cline/core`. + +## Overview + +Three trigger types: + +| Trigger | Description | +|---------|-------------| +| `schedule` | Recurring jobs via cron expressions | +| `one_off` | Single execution tasks | +| `event` | Triggered by external events (GitHub, Linear, custom) | + +## CLI Schedule Management + +```bash +# Create a recurring schedule +cline schedule create "Daily standup" \ + --cron "0 9 * * MON-FRI" \ + --prompt "Summarize open PRs and blockers" \ + --workspace /path/to/project \ + --model anthropic/claude-sonnet-4-6 + +# List schedules +cline schedule list + +# Trigger a schedule immediately +cline schedule trigger + +# Pause/resume +cline schedule pause +cline schedule resume + +# Delete +cline schedule delete + +# View past executions +cline schedule executions +``` + +## Cron Expressions + +| Expression | Meaning | +|-----------|---------| +| `0 9 * * MON-FRI` | 9 AM weekdays | +| `0 */6 * * *` | Every 6 hours | +| `0 8 * * MON` | Mondays at 8 AM | +| `*/30 * * * *` | Every 30 minutes | +| `0 0 1 * *` | First of every month | + +## File-Based Specs + +Create Markdown files in `~/.cline/cron/` (global) or `.cline/cron/` (workspace): + +### Recurring Schedule + +```markdown +--- +trigger: schedule +schedule: "0 9 * * MON-FRI" +timezone: America/New_York +mode: exclusive +prompt: "Check for dependency updates and create PRs for any outdated packages." +modelSelection: + providerId: anthropic + modelId: claude-sonnet-4-6 +tools: + enabled: true +--- + +Additional context or instructions for the agent go in the body. +``` + +### One-Off Task + +```markdown +--- +trigger: one_off +prompt: "Generate a comprehensive test coverage report." +modelSelection: + providerId: anthropic + modelId: claude-sonnet-4-6 +--- +``` + +### Event-Driven + +```markdown +--- +trigger: event +eventType: github.pull_request.opened +filters: + repository: myorg/myrepo +debounceMs: 5000 +cooldownMs: 60000 +prompt: "Review the PR for security issues and code quality." +modelSelection: + providerId: anthropic + modelId: claude-sonnet-4-6 +--- +``` + +## CronSpec Types + +```typescript +interface CronScheduleSpec { + trigger: "schedule" + schedule: string // cron expression + timezone?: string + mode?: "exclusive" | "concurrent" + prompt: string + modelSelection?: { providerId: string; modelId?: string } + extensionLoading?: "isolated" | "direct" + configExtensions?: RuntimeConfigExtensionKind[] + tools?: { enabled?: boolean; names?: string[] } +} + +interface CronOneOffSpec { + trigger: "one_off" + prompt: string + modelSelection?: { providerId: string; modelId?: string } +} + +interface CronEventSpec { + trigger: "event" + eventType: string // e.g., "github.pull_request.opened" + filters?: Record + debounceMs?: number + cooldownMs?: number + prompt: string + modelSelection?: { providerId: string; modelId?: string } +} +``` + +## Programmatic Automation API + +```typescript +const cline = await ClineCore.create({ + clientName: "my-app", + automation: true, +}) + +// Start automation service +cline.automation.start() + +// Ingest an external event +cline.automation.ingestEvent({ + eventId: "evt-123", + eventType: "github.pull_request.opened", + source: "github", + timestamp: Date.now(), + payload: { pr: { number: 42, title: "..." } }, +}) + +// List specs, runs, events +const specs = await cline.automation.listSpecs() +const runs = await cline.automation.listRuns() +const events = await cline.automation.listEvents() + +// Reconcile specs from directory +await cline.automation.reconcile(specDirectory) + +// Stop automation +cline.automation.stop() +``` + +## Event Ingestion from Plugins + +Plugins can declare and emit automation events: + +```typescript +const webhookPlugin: AgentPlugin = { + name: "webhook-events", + manifest: { capabilities: ["automationEvents"] }, + setup(api) { + api.registerAutomationEventType({ + type: "webhook.received", + description: "External webhook received", + }) + }, +} +``` + +Submit events via the plugin context: + +```typescript +ctx.automation.ingestEvent({ + eventId: "evt-456", + eventType: "webhook.received", + source: "custom", + timestamp: Date.now(), + payload: { ... }, +}) +``` + +## Concurrency Control + +| Mode | Behavior | +|------|----------| +| `"exclusive"` | Skip if previous run still active | +| `"concurrent"` | Allow overlapping runs | + +## Run Reports + +Each completed run writes a Markdown report to `.cline/cron/reports/.md` with: +- Run metadata (spec, trigger, timing) +- Summary of agent output +- Usage (tokens, cost) +- Tool calls made +- Trigger event context (for event-driven runs) + +## Use Cases + +- Daily standup summaries +- Automated dependency update checks +- PR review on open +- Codebase health reports +- Scheduled security scans +- Event-driven CI/CD workflows + +## See Also + +- `../clinecore/REFERENCE.md` - ClineCore runtime +- `../clinecore/api.md` - Automation API details +- `../plugins/REFERENCE.md` - Plugin events +- `../production/REFERENCE.md` - Production deployment diff --git a/.agents/skills/cline-sdk/references/tools/REFERENCE.md b/.agents/skills/cline-sdk/references/tools/REFERENCE.md new file mode 100644 index 0000000000..3e3ac56736 --- /dev/null +++ b/.agents/skills/cline-sdk/references/tools/REFERENCE.md @@ -0,0 +1,259 @@ +# Tools + +Tools are how agents interact with the world. The Cline SDK supports both built-in tools (via ClineCore) and custom tools you define yourself. + +## Creating Custom Tools + +Use `createTool()` from `@cline/sdk` (or `@cline/shared`): + +```typescript +import { createTool } from "@cline/sdk" + +const myTool = createTool({ + name: "search_issues", + description: "Search GitHub issues by query. Returns up to 10 results.", + inputSchema: { + type: "object", + properties: { + query: { type: "string", description: "Search query" }, + state: { type: "string", enum: ["open", "closed", "all"] }, + }, + required: ["query"], + }, + execute: async (input) => { + const issues = await github.searchIssues(input.query, input.state) + return { issues, count: issues.length } + }, +}) +``` + +### With Zod Schema + +```typescript +import { createTool } from "@cline/sdk" +import { z } from "zod" + +const deployTool = createTool({ + name: "deploy", + description: "Deploy the app to the specified environment.", + inputSchema: z.object({ + environment: z.enum(["staging", "production"]).describe("Target environment"), + version: z.string().optional().describe("Version tag, defaults to latest"), + }), + execute: async (input) => { + const result = await deploy(input.environment, input.version) + return { url: result.url, status: "deployed" } + }, +}) +``` + +### Tool Config Options + +```typescript +createTool({ + name: string, // snake_case, unique per agent + description: string, // what the tool does (model reads this) + inputSchema: JSONSchema | ZodSchema, // input validation + execute: async (input, context, onChange?) => output, + timeoutMs?: number, // default: 30000 + retryable?: boolean, // default: true + maxRetries?: number, // default: 3 + lifecycle?: { + completesRun?: boolean // true = ends agent loop on success + }, +}) +``` + +### AgentToolContext + +The second argument to `execute` provides runtime context: + +```typescript +interface AgentToolContext { + agentId: string + conversationId: string + iteration: number + abortSignal?: AbortSignal + metadata?: Record +} +``` + +## Tool Naming Rules + +- Names must be `snake_case` (e.g., `search_issues`, `deploy_app`) +- Names must be unique within a single agent's tool set +- Choose descriptive names since the model uses them to decide which tool to call + +## Tool Descriptions Matter + +The model reads the tool description to decide when and how to use it. Write clear, specific descriptions: + +```typescript +// Bad: vague +description: "Does deployment stuff" + +// Good: specific with constraints +description: "Deploy the application to staging or production. " + + "Staging deployments are immediate. Production requires a passing CI build. " + + "Returns the deployment URL and status." +``` + +Include constraints, rate limits, and expected behavior in the description. + +## Error Handling in Tools + +Return errors as structured data instead of throwing: + +```typescript +// Good: return error data +execute: async (input) => { + const file = await readFile(input.path).catch(() => null) + if (!file) { + return { error: "File not found", path: input.path } + } + return { content: file } +} +``` + +Thrown exceptions count as "mistakes" against the agent's mistake limit. Returned error data lets the agent adjust its approach. + +## Completion Tools + +Tools with `lifecycle: { completesRun: true }` end the agent loop when they execute successfully: + +```typescript +const submitAnswer = createTool({ + name: "submit_answer", + description: "Submit the final answer and end the task.", + inputSchema: z.object({ + answer: z.string(), + confidence: z.number().min(0).max(1), + }), + lifecycle: { completesRun: true }, + execute: async (input) => input, +}) +``` + +The model sees the tool result and the run ends. Access the output via `result.toolCalls`. + +## Built-in Tools (ClineCore Only) + +When using `ClineCore` with `enableTools: true`, these tools are available automatically: + +| Tool | Name | What It Does | +|------|------|-------------| +| Shell | `bash` | Execute shell commands in the session workspace | +| Editor | `editor` | Create and edit files | +| Read | `read_files` | Read file contents | +| Patch | `apply_patch` | Apply unified diffs to files | +| Search | `search` | Search file contents and directory structure | +| Web | `fetch_web` | Fetch web content via HTTP | + +Built-in tools respect the `cwd` setting in `CoreSessionConfig`. + +## Tool Policies + +Control which tools are available and whether they require approval: + +```typescript +// In Agent config +const agent = new Agent({ + tools: [toolA, toolB, toolC], + toolPolicies: { + tool_a: { autoApprove: true }, // runs without asking + tool_b: { autoApprove: false }, // requires approval + tool_c: { enabled: false }, // hidden from model + }, +}) + +// In ClineCore session +await cline.start({ + prompt: "...", + config: { ... }, + toolPolicies: { + bash: { autoApprove: true }, + editor: { autoApprove: false }, + }, +}) +``` + +### Policy Options + +| Policy | Effect | +|--------|--------| +| `{ autoApprove: true }` | Tool runs without approval | +| `{ autoApprove: false }` | Triggers approval callback before running | +| `{ enabled: false }` | Tool is hidden from the model entirely | +| No policy set | Defaults to enabled and auto-approved | + +## Abort Signal in Long-Running Tools + +Respect the abort signal for tools that take a long time: + +```typescript +execute: async (input, context) => { + const results = [] + for (const item of input.items) { + if (context.abortSignal?.aborted) { + return { results, aborted: true, processed: results.length } + } + results.push(await processItem(item)) + } + return { results, processed: results.length } +} +``` + +## Streaming Tool Output + +Use the `onChange` callback (third argument) to stream partial results: + +```typescript +execute: async (input, context, onChange) => { + let progress = 0 + for (const step of steps) { + progress++ + onChange?.(`Processing step ${progress}/${steps.length}...`) + await processStep(step) + } + return { completed: true } +} +``` + +## Testing Tools + +Tools are plain async functions, so they're straightforward to test: + +```typescript +import { describe, it, expect } from "vitest" + +describe("deploy tool", () => { + it("deploys to staging", async () => { + const context = { agentId: "test", conversationId: "test", iteration: 1 } + const result = await deployTool.execute({ environment: "staging" }, context) + expect(result.status).toBe("deployed") + }) +}) +``` + +## MCP Tool Integration + +ClineCore can connect to MCP (Model Context Protocol) servers for additional tools. Configure in `.cline/mcp-servers.json`: + +```json +{ + "servers": { + "my-server": { + "command": "node", + "args": ["./mcp-server.js"] + } + } +} +``` + +MCP tools appear alongside built-in and custom tools automatically. + +## See Also + +- `../agent/REFERENCE.md` - Using tools with Agent +- `../clinecore/REFERENCE.md` - Using tools with ClineCore +- `../plugins/REFERENCE.md` - Packaging tools as plugins diff --git a/.claude/skills/cline-sdk b/.claude/skills/cline-sdk new file mode 120000 index 0000000000..d6fa6e5a3a --- /dev/null +++ b/.claude/skills/cline-sdk @@ -0,0 +1 @@ +../../.agents/skills/cline-sdk \ No newline at end of file diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000000..f46e11fcb8 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "cline-sdk": { + "source": "cline/sdk-skill", + "sourceType": "github", + "skillPath": "skill/cline-sdk/SKILL.md", + "computedHash": "ce565d78d5bd1c40075248a3a925b2059c5c024bfcdbbf1f55bf3440cae34144" + } + } +}