commit 9c06d70b3cc6f60ae906fbcace8510ac38d186de Author: wehub-resource-sync Date: Mon Jul 13 12:52:40 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.agents/skills/cline-sdk/SKILL.md b/.agents/skills/cline-sdk/SKILL.md new file mode 100644 index 0000000..7f7b061 --- /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 0000000..4fa40a7 --- /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 0000000..76a008c --- /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 0000000..8b4b945 --- /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 0000000..c0e43e2 --- /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 0000000..d1b9e01 --- /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 0000000..8abfce9 --- /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 0000000..240aab8 --- /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 0000000..379eff6 --- /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 0000000..76f273a --- /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 0000000..173cb1b --- /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 0000000..6bcc4bf --- /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 0000000..5be32e6 --- /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 0000000..1ad2dc7 --- /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 0000000..295e884 --- /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 0000000..3e3ac56 --- /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/.agents/skills/create-pull-request/SKILL.md b/.agents/skills/create-pull-request/SKILL.md new file mode 100644 index 0000000..9aa5add --- /dev/null +++ b/.agents/skills/create-pull-request/SKILL.md @@ -0,0 +1,211 @@ +--- +name: create-pull-request +description: Create a GitHub pull request following project conventions. Use when the user asks to create a PR, submit changes for review, or open a pull request. Handles commit analysis, branch management, PR template usage, and PR creation using the gh CLI tool. +--- + +# Create Pull Request + +This skill guides you through creating a well-structured GitHub pull request that follows project conventions and best practices. + +## Prerequisites Check + +Before proceeding, verify the following: + +### 1. Check if `gh` CLI is installed + +```bash +gh --version +``` + +If not installed, inform the user: +> The GitHub CLI (`gh`) is required but not installed. Please install it: +> - macOS: `brew install gh` +> - Other: https://cli.github.com/ + +### 2. Check if authenticated with GitHub + +```bash +gh auth status +``` + +If not authenticated, guide the user to run `gh auth login`. + +### 3. Verify clean working directory + +```bash +git status +``` + +If there are uncommitted changes, ask the user whether to: +- Commit them as part of this PR +- Stash them temporarily +- Discard them (with caution) + +## Gather Context + +### 1. Identify the current branch + +```bash +git branch --show-current +``` + +Ensure you're not on `main` or `master`. If so, ask the user to create or switch to a feature branch. + +### 2. Find the base branch + +```bash +git remote show origin | grep "HEAD branch" +``` + +This is typically `main` or `master`. + +### 3. Analyze recent commits relevant to this PR + +```bash +git log origin/main..HEAD --oneline --no-decorate +``` + +Review these commits to understand: +- What changes are being introduced +- The scope of the PR (single feature/fix or multiple changes) +- Whether commits should be squashed or reorganized + +### 4. Review the diff + +```bash +git diff origin/main..HEAD --stat +``` + +This shows which files changed and helps identify the type of change. + +## Information Gathering + +Before creating the PR, you need the following information. Check if it can be inferred from: +- Commit messages +- Branch name (e.g., `fix/issue-123`, `feature/new-login`) +- Changed files and their content + +If any critical information is missing, use `ask_followup_question` to ask the user: + +### Required Information + +1. **Related Issue Number**: Look for patterns like `#123`, `fixes #123`, or `closes #123` in commit messages +2. **Description**: What problem does this solve? Why were these changes made? +3. **Type of Change**: Bug fix, new feature, breaking change, refactor, cosmetic, documentation, or workflow +4. **Test Procedure**: How was this tested? What could break? + +### Example clarifying question + +If the issue number is not found: +> I couldn't find a related issue number in the commit messages or branch name. What GitHub issue does this PR address? (Enter the issue number, e.g., "123" or "N/A" for small fixes) + +## Git Best Practices + +Before creating the PR, consider these best practices: + +### Commit Hygiene + +1. **Atomic commits**: Each commit should represent a single logical change +2. **Clear commit messages**: Follow conventional commit format when possible +3. **No merge commits**: Prefer rebasing over merging to keep history clean + +### Branch Management + +1. **Rebase on latest main** (if needed): + ```bash + git fetch origin + git rebase origin/main + ``` + +2. **Squash if appropriate**: If there are many small "WIP" commits, consider interactive rebase: + ```bash + git rebase -i origin/main + ``` + Only suggest this if commits appear messy and the user is comfortable with rebasing. + +### Push Changes + +Ensure all commits are pushed: +```bash +git push origin HEAD +``` + +If the branch was rebased, you may need: +```bash +git push origin HEAD --force-with-lease +``` + +## Create the Pull Request + +**IMPORTANT**: Read and use the PR template at `.github/pull_request_template.md`. The PR body format must **strictly match** the template structure. Do not deviate from the template format. + +When filling out the template: +- Replace `#XXXX` with the actual issue number, or keep as `#XXXX` if no issue exists (for small fixes) +- Fill in all sections with relevant information gathered from commits and context +- Mark the appropriate "Type of Change" checkbox(es) +- Complete the "Pre-flight Checklist" items that apply + +### Create PR with gh CLI + +**Use a temporary file for the PR body** to avoid shell escaping issues, newline problems, and other command-line flakiness: + +1. Write the PR body to a temporary file: + ``` + /tmp/pr-body.md + ``` + +2. Create the PR using the file: + ```bash + gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main + ``` + +3. Clean up the temporary file: + ```bash + rm /tmp/pr-body.md + ``` + +For draft PRs: +```bash +gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main --draft +``` + +**Why use a file?** Passing complex markdown with newlines, special characters, and checkboxes directly via `--body` is error-prone. The `--body-file` flag handles all content reliably. + +## Post-Creation + +After creating the PR: + +1. **Display the PR URL** so the user can review it +2. **Remind about CI checks**: Tests and linting will run automatically +3. **Suggest next steps**: + - Add reviewers if needed: `gh pr edit --add-reviewer USERNAME` + - Add labels if needed: `gh pr edit --add-label "bug"` + +## Error Handling + +### Common Issues + +1. **No commits ahead of main**: The branch has no changes to submit + - Ask if the user meant to work on a different branch + +2. **Branch not pushed**: Remote doesn't have the branch + - Push the branch first: `git push -u origin HEAD` + +3. **PR already exists**: A PR for this branch already exists + - Show the existing PR: `gh pr view` + - Ask if they want to update it instead + +4. **Merge conflicts**: Branch conflicts with base + - Guide user through resolving conflicts or rebasing + +## Summary Checklist + +Before finalizing, ensure: +- [ ] `gh` CLI is installed and authenticated +- [ ] Working directory is clean +- [ ] All commits are pushed +- [ ] Branch is up-to-date with base branch +- [ ] Related issue number is identified, or placeholder is used +- [ ] PR description follows the template exactly +- [ ] Appropriate type of change is selected +- [ ] Pre-flight checklist items are addressed \ No newline at end of file diff --git a/.agents/skills/opentui/SKILL.md b/.agents/skills/opentui/SKILL.md new file mode 100644 index 0000000..ada49f5 --- /dev/null +++ b/.agents/skills/opentui/SKILL.md @@ -0,0 +1,200 @@ +--- +name: opentui +description: Comprehensive OpenTUI skill for building terminal user interfaces. Covers the core imperative API, React reconciler, and Solid reconciler. Use for any TUI development task including components, layout, keyboard handling, animations, and testing. +metadata: + references: core, react, solid +--- + +# OpenTUI Platform Skill + +Consolidated skill for building terminal user interfaces with OpenTUI. Use decision trees below to find the right framework and components, then load detailed references. + +## Critical Rules + +**Follow these rules in all OpenTUI code:** + +1. **Use `create-tui` for new projects.** See framework `REFERENCE.md` quick starts. +2. **`create-tui` options must come before arguments.** `bunx create-tui -t react my-app` works, `bunx create-tui my-app -t react` does NOT. +3. **Never call `process.exit()` directly.** Use `renderer.destroy()` (see `core/gotchas.md`). +4. **Text styling requires nested tags in React/Solid.** Use modifier elements, not props (see `components/text-display.md`). + +## How to Use This Skill + +### Reference File Structure + +Framework references follow a 5-file pattern. Cross-cutting concepts are single-file guides. + +Each framework in `./references//` contains: + +| File | Purpose | When to Read | +|------|---------|--------------| +| `REFERENCE.md` | Overview, when to use, quick start | **Always read first** | +| `api.md` | Runtime API, components, hooks | Writing code | +| `configuration.md` | Setup, tsconfig, bundling | Configuring a project | +| `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 framework +2. Then read additional files relevant to your task: + - Building components -> `api.md` + `components/.md` + - Setting up project -> `configuration.md` + - Layout/positioning -> `layout/REFERENCE.md` + - Keyboard/input handling -> `keyboard/REFERENCE.md` + - Animations -> `animation/REFERENCE.md` + - Troubleshooting -> `gotchas.md` + `testing/REFERENCE.md` + +### Example Paths + +``` +./references/react/REFERENCE.md # Start here for React +./references/react/api.md # React components and hooks +./references/solid/configuration.md # Solid project setup +./references/components/inputs.md # Input, Textarea, Select docs +./references/core/gotchas.md # Core debugging tips +``` + +### Runtime Notes + +OpenTUI runs on Bun and uses Zig for native builds. Read `./references/core/gotchas.md` for runtime requirements and build guidance. + +## Quick Decision Trees + +### "Which framework should I use?" + +``` +Which framework? +├─ I want full control, maximum performance, no framework overhead +│ └─ core/ (imperative API) +├─ I know React, want familiar component patterns +│ └─ react/ (React reconciler) +├─ I want fine-grained reactivity, optimal re-renders +│ └─ solid/ (Solid reconciler) +└─ I'm building a library/framework on top of OpenTUI + └─ core/ (imperative API) +``` + +### "I need to display content" + +``` +Display content? +├─ Plain or styled text -> components/text-display.md +├─ Container with borders/background -> components/containers.md +├─ Scrollable content area -> components/containers.md (scrollbox) +├─ ASCII art banner/title -> components/text-display.md (ascii-font) +├─ Data table with borders/wrapping -> components/code-diff.md (TextTable) +├─ Code with syntax highlighting -> components/code-diff.md +├─ Diff viewer (unified/split) -> components/code-diff.md +├─ Line numbers with diagnostics -> components/code-diff.md +└─ Markdown content (streaming) -> components/code-diff.md (markdown) +``` + +### "I need user input" + +``` +User input? +├─ Single-line text field -> components/inputs.md (input) +├─ Multi-line text editor -> components/inputs.md (textarea) +├─ Select from a list (vertical) -> components/inputs.md (select) +├─ Tab-based selection (horizontal) -> components/inputs.md (tab-select) +└─ Custom keyboard shortcuts -> keyboard/REFERENCE.md +``` + +### "I need layout/positioning" + +``` +Layout? +├─ Flexbox-style layouts (row, column, wrap) -> layout/REFERENCE.md +├─ Absolute positioning -> layout/patterns.md +├─ Responsive to terminal size -> layout/patterns.md +├─ Centering content -> layout/patterns.md +└─ Complex nested layouts -> layout/patterns.md +``` + +### "I need animations" + +``` +Animations? +├─ Timeline-based animations -> animation/REFERENCE.md +├─ Easing functions -> animation/REFERENCE.md +├─ Property transitions -> animation/REFERENCE.md +└─ Looping animations -> animation/REFERENCE.md +``` + +### "I need to handle input" + +``` +Input handling? +├─ Keyboard events (keypress, release) -> keyboard/REFERENCE.md +├─ Focus management -> keyboard/REFERENCE.md +├─ Paste events -> keyboard/REFERENCE.md +├─ Mouse events -> components/containers.md +├─ Text selection & copy-on-select -> keyboard/REFERENCE.md (selection) +└─ Clipboard (OSC 52) -> keyboard/REFERENCE.md (clipboard) +``` + +### "I need to test my TUI" + +``` +Testing? +├─ Snapshot testing -> testing/REFERENCE.md +├─ Interaction testing -> testing/REFERENCE.md +├─ Test renderer setup -> testing/REFERENCE.md +└─ Debugging tests -> testing/REFERENCE.md +``` + +### "I need to debug/troubleshoot" + +``` +Troubleshooting? +├─ Runtime errors, crashes -> /gotchas.md +├─ Layout issues -> layout/REFERENCE.md + layout/patterns.md +├─ Input/focus issues -> keyboard/REFERENCE.md +└─ Repro + regression tests -> testing/REFERENCE.md +``` + +### Troubleshooting Index + +- Terminal cleanup, crashes -> `core/gotchas.md` +- Text styling not applying -> `components/text-display.md` +- Input focus/shortcuts -> `keyboard/REFERENCE.md` +- Layout misalignment -> `layout/REFERENCE.md` +- Flaky snapshots -> `testing/REFERENCE.md` + +For component naming differences and text modifiers, see `components/REFERENCE.md`. + +## Product Index + +### Frameworks +| Framework | Entry File | Description | +|-----------|------------|-------------| +| Core | `./references/core/REFERENCE.md` | Imperative API, all primitives | +| React | `./references/react/REFERENCE.md` | React reconciler for declarative TUI | +| Solid | `./references/solid/REFERENCE.md` | SolidJS reconciler for declarative TUI | + +### Cross-Cutting Concepts +| Concept | Entry File | Description | +|---------|------------|-------------| +| Layout | `./references/layout/REFERENCE.md` | Yoga/Flexbox layout system | +| Components | `./references/components/REFERENCE.md` | Component reference by category | +| Keyboard | `./references/keyboard/REFERENCE.md` | Keyboard input handling | +| Animation | `./references/animation/REFERENCE.md` | Timeline-based animations | +| Testing | `./references/testing/REFERENCE.md` | Test renderer and snapshots | + +### Component Categories +| Category | Entry File | Components | +|----------|------------|------------| +| Text & Display | `./references/components/text-display.md` | text, ascii-font, styled text | +| Containers | `./references/components/containers.md` | box, scrollbox, borders | +| Inputs | `./references/components/inputs.md` | input, textarea, select, tab-select | +| Code & Diff | `./references/components/code-diff.md` | code, line-number, diff, markdown, text-table | + +## Resources + +**Repository**: https://github.com/anomalyco/opentui +**Core Docs**: https://github.com/anomalyco/opentui/tree/main/packages/core/docs +**Examples**: https://github.com/anomalyco/opentui/tree/main/packages/core/src/examples +**Awesome List**: https://github.com/msmps/awesome-opentui diff --git a/.agents/skills/opentui/references/animation/REFERENCE.md b/.agents/skills/opentui/references/animation/REFERENCE.md new file mode 100644 index 0000000..ff76b99 --- /dev/null +++ b/.agents/skills/opentui/references/animation/REFERENCE.md @@ -0,0 +1,431 @@ +# Animation System + +OpenTUI provides a timeline-based animation system for smooth property transitions. + +## Overview + +Animations in OpenTUI use: +- **Timeline**: Orchestrates multiple animations +- **Animation Engine**: Manages timelines and rendering +- **Easing Functions**: Control animation curves + +## When to Use + +Use this reference when you need timeline-driven animations, easing curves, or progressive transitions. + +## Basic Usage + +### React + +```tsx +import { useTimeline } from "@opentui/react" +import { useEffect, useState } from "react" + +function AnimatedBox() { + const [width, setWidth] = useState(0) + + const timeline = useTimeline({ + duration: 2000, + }) + + useEffect(() => { + timeline.add( + { width: 0 }, + { + width: 50, + duration: 2000, + ease: "easeOutQuad", + onUpdate: (anim) => { + setWidth(Math.round(anim.targets[0].width)) + }, + } + ) + }, []) + + return ( + + ) +} +``` + +### Solid + +```tsx +import { useTimeline } from "@opentui/solid" +import { createSignal, onMount } from "solid-js" + +function AnimatedBox() { + const [width, setWidth] = createSignal(0) + + const timeline = useTimeline({ + duration: 2000, + }) + + onMount(() => { + timeline.add( + { width: 0 }, + { + width: 50, + duration: 2000, + ease: "easeOutQuad", + onUpdate: (anim) => { + setWidth(Math.round(anim.targets[0].width)) + }, + } + ) + }) + + return ( + + ) +} +``` + +### Core + +```typescript +import { createCliRenderer, Timeline, engine } from "@opentui/core" + +const renderer = await createCliRenderer() +engine.attach(renderer) + +const timeline = new Timeline({ + duration: 2000, + autoplay: true, +}) + +timeline.add( + { x: 0 }, + { + x: 50, + duration: 2000, + ease: "easeOutQuad", + onUpdate: (anim) => { + box.setLeft(Math.round(anim.targets[0].x)) + }, + } +) + +engine.addTimeline(timeline) +``` + +## Timeline Options + +```typescript +const timeline = useTimeline({ + duration: 2000, // Total duration in ms + loop: false, // Loop the timeline + autoplay: true, // Start automatically + onComplete: () => {}, // Called when timeline completes + onPause: () => {}, // Called when timeline pauses +}) +``` + +## Timeline Methods + +```typescript +// Add animation +timeline.add(target, properties, startTime?) + +// Control playback +timeline.play() // Start/resume +timeline.pause() // Pause +timeline.restart() // Restart from beginning + +// State +timeline.progress // Current progress (0-1) +timeline.duration // Total duration +``` + +## Animation Properties + +```typescript +timeline.add( + { value: 0 }, // Target object with initial values + { + value: 100, // Final value + duration: 1000, // Animation duration in ms + ease: "linear", // Easing function + delay: 0, // Delay before starting + onUpdate: (anim) => { + // Called each frame + const current = anim.targets[0].value + }, + onComplete: () => { + // Called when this animation completes + }, + }, + 0 // Start time in timeline (optional) +) +``` + +## Easing Functions + +Available easing functions: + +### Linear + +| Name | Description | +|------|-------------| +| `linear` | Constant speed | + +### Quad (Power of 2) + +| Name | Description | +|------|-------------| +| `easeInQuad` | Slow start | +| `easeOutQuad` | Slow end | +| `easeInOutQuad` | Slow start and end | + +### Cubic (Power of 3) + +| Name | Description | +|------|-------------| +| `easeInCubic` | Slower start | +| `easeOutCubic` | Slower end | +| `easeInOutCubic` | Slower start and end | + +### Quart (Power of 4) + +| Name | Description | +|------|-------------| +| `easeInQuart` | Even slower start | +| `easeOutQuart` | Even slower end | +| `easeInOutQuart` | Even slower start and end | + +### Expo (Exponential) + +| Name | Description | +|------|-------------| +| `easeInExpo` | Exponential start | +| `easeOutExpo` | Exponential end | +| `easeInOutExpo` | Exponential start and end | + +### Back (Overshoot) + +| Name | Description | +|------|-------------| +| `easeInBack` | Pull back, then forward | +| `easeOutBack` | Overshoot, then settle | +| `easeInOutBack` | Both | + +### Elastic + +| Name | Description | +|------|-------------| +| `easeInElastic` | Elastic start | +| `easeOutElastic` | Elastic end (bouncy) | +| `easeInOutElastic` | Both | + +### Bounce + +| Name | Description | +|------|-------------| +| `easeInBounce` | Bounce at start | +| `easeOutBounce` | Bounce at end | +| `easeInOutBounce` | Both | + +## Patterns + +### Progress Bar + +```tsx +function ProgressBar({ progress }: { progress: number }) { + const [width, setWidth] = useState(0) + const maxWidth = 50 + + const timeline = useTimeline() + + useEffect(() => { + timeline.add( + { value: width }, + { + value: (progress / 100) * maxWidth, + duration: 300, + ease: "easeOutQuad", + onUpdate: (anim) => { + setWidth(Math.round(anim.targets[0].value)) + }, + } + ) + }, [progress]) + + return ( + + Progress: {progress}% + + + + + ) +} +``` + +### Fade In + +```tsx +function FadeIn({ children }) { + const [opacity, setOpacity] = useState(0) + + const timeline = useTimeline() + + useEffect(() => { + timeline.add( + { opacity: 0 }, + { + opacity: 1, + duration: 500, + ease: "easeOutQuad", + onUpdate: (anim) => { + setOpacity(anim.targets[0].opacity) + }, + } + ) + }, []) + + return ( + + {children} + + ) +} +``` + +### Looping Animation + +```tsx +function Spinner() { + const [frame, setFrame] = useState(0) + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] + + useEffect(() => { + const interval = setInterval(() => { + setFrame(f => (f + 1) % frames.length) + }, 80) + + return () => clearInterval(interval) + }, []) + + return {frames[frame]} Loading... +} +``` + +### Staggered Animation + +```tsx +function StaggeredList({ items }) { + const [visibleCount, setVisibleCount] = useState(0) + + useEffect(() => { + let count = 0 + const interval = setInterval(() => { + count++ + setVisibleCount(count) + if (count >= items.length) { + clearInterval(interval) + } + }, 100) + + return () => clearInterval(interval) + }, [items.length]) + + return ( + + {items.slice(0, visibleCount).map((item, i) => ( + {item} + ))} + + ) +} +``` + +### Slide In + +```tsx +function SlideIn({ children, from = "left" }) { + const [offset, setOffset] = useState(from === "left" ? -20 : 20) + + const timeline = useTimeline() + + useEffect(() => { + timeline.add( + { offset: from === "left" ? -20 : 20 }, + { + offset: 0, + duration: 300, + ease: "easeOutCubic", + onUpdate: (anim) => { + setOffset(Math.round(anim.targets[0].offset)) + }, + } + ) + }, []) + + return ( + + {children} + + ) +} +``` + +## Performance Tips + +### Batch Updates + +Timeline automatically batches updates within the render loop. + +### Use Integer Values + +Round animated values for character-based positioning: + +```typescript +onUpdate: (anim) => { + setX(Math.round(anim.targets[0].x)) +} +``` + +### Clean Up Timelines + +Hooks automatically clean up, but for core: + +```typescript +// When done with timeline +engine.removeTimeline(timeline) +``` + +## Gotchas + +### Terminal Refresh Rate + +Terminal UIs typically refresh at 60 FPS max. Very fast animations may appear choppy. + +### Character Grid + +Animations are constrained to character cells. Sub-pixel positioning isn't possible. + +### Cleanup in Effects + +Always clean up intervals and timelines: + +```tsx +useEffect(() => { + const interval = setInterval(...) + return () => clearInterval(interval) +}, []) +``` + +## See Also + +- [React API](../react/api.md) - `useTimeline` hook reference +- [Solid API](../solid/api.md) - `useTimeline` hook reference +- [Core API](../core/api.md) - `AnimationEngine` and `Timeline` classes +- [Layout Patterns](../layout/patterns.md) - Animated positioning and transitions diff --git a/.agents/skills/opentui/references/components/REFERENCE.md b/.agents/skills/opentui/references/components/REFERENCE.md new file mode 100644 index 0000000..a28ce8d --- /dev/null +++ b/.agents/skills/opentui/references/components/REFERENCE.md @@ -0,0 +1,144 @@ +# OpenTUI Components + +Reference for all OpenTUI components, organized by category. Components are available in all three frameworks (Core, React, Solid) with slight API differences. + +## When to Use + +Use this reference when you need to find the right component category or compare naming across Core, React, and Solid. + +## Component Categories + +| Category | Components | File | +|----------|------------|------| +| Text & Display | text, ascii-font, styled text | [text-display.md](./text-display.md) | +| Containers | box, scrollbox, borders | [containers.md](./containers.md) | +| Inputs | input, textarea, select, tab-select | [inputs.md](./inputs.md) | +| Code & Diff | code, line-number, diff, markdown, text-table | [code-diff.md](./code-diff.md) | + +## Component Chooser + +``` +Need a component? +├─ Styled text or ASCII art -> text-display.md +├─ Containers, borders, scrolling -> containers.md +├─ Forms or input controls -> inputs.md +└─ Code blocks, diffs, line numbers, markdown -> code-diff.md +``` + +## Component Naming + +Components have different names across frameworks: + +| Concept | Core (Class) | React (JSX) | Solid (JSX) | +|---------|--------------|-------------|-------------| +| Text | `TextRenderable` | `` | `` | +| Box | `BoxRenderable` | `` | `` | +| ScrollBox | `ScrollBoxRenderable` | `` | `` | +| Input | `InputRenderable` | `` | `` | +| Textarea | `TextareaRenderable` | `