mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ffba23d3d |
@@ -1,208 +0,0 @@
|
||||
---
|
||||
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/<api>/` 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/<concept>/` 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
|
||||
@@ -1,107 +0,0 @@
|
||||
# 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
|
||||
@@ -1,231 +0,0 @@
|
||||
# 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<string, string>
|
||||
|
||||
systemPrompt?: string
|
||||
tools?: AgentTool[]
|
||||
initialMessages?: AgentMessage[]
|
||||
toolPolicies?: Record<string, ToolPolicy>
|
||||
hooks?: Partial<AgentRuntimeHooks>
|
||||
plugins?: AgentPlugin[]
|
||||
}
|
||||
```
|
||||
|
||||
### With Pre-built Model
|
||||
|
||||
```typescript
|
||||
interface AgentRuntimeConfigWithModel {
|
||||
model: AgentModel // pre-built model from gateway
|
||||
|
||||
systemPrompt?: string
|
||||
tools?: AgentTool[]
|
||||
initialMessages?: AgentMessage[]
|
||||
toolPolicies?: Record<string, ToolPolicy>
|
||||
hooks?: Partial<AgentRuntimeHooks>
|
||||
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<string, unknown>
|
||||
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<void>
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
@@ -1,134 +0,0 @@
|
||||
# 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
|
||||
@@ -1,258 +0,0 @@
|
||||
# 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<string, Agent>()
|
||||
|
||||
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
|
||||
@@ -1,131 +0,0 @@
|
||||
# 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
|
||||
@@ -1,304 +0,0 @@
|
||||
# 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<string, ToolPolicy>
|
||||
automation?: boolean | ClineCoreAutomationOptions
|
||||
fetch?: typeof fetch
|
||||
}
|
||||
```
|
||||
|
||||
### RuntimeCapabilities
|
||||
|
||||
```typescript
|
||||
interface RuntimeCapabilities {
|
||||
requestToolApproval?: (request: ToolApprovalRequest) => Promise<ToolApprovalResult>
|
||||
// ... 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<string, unknown>
|
||||
initialMessages?: AgentMessage[]
|
||||
toolPolicies?: Record<string, ToolPolicy>
|
||||
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<AgentRuntimeHooks> // 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
|
||||
@@ -1,148 +0,0 @@
|
||||
# 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
|
||||
@@ -1,279 +0,0 @@
|
||||
# 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
|
||||
@@ -1,269 +0,0 @@
|
||||
# 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<string, unknown> }
|
||||
```
|
||||
|
||||
### 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
|
||||
@@ -1,157 +0,0 @@
|
||||
# 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
|
||||
@@ -1,649 +0,0 @@
|
||||
# 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<string, MyState>()
|
||||
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:
|
||||
|
||||
- `<workspace>/.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<void> {
|
||||
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/<kind>/` (user overrides).
|
||||
3. project -- files under `<workspace>/.cline/<kind>/` (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
|
||||
@@ -1,253 +0,0 @@
|
||||
# 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
|
||||
@@ -1,257 +0,0 @@
|
||||
# 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
|
||||
@@ -1,227 +0,0 @@
|
||||
# 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 <schedule-id>
|
||||
|
||||
# Pause/resume
|
||||
cline schedule pause <schedule-id>
|
||||
cline schedule resume <schedule-id>
|
||||
|
||||
# Delete
|
||||
cline schedule delete <schedule-id>
|
||||
|
||||
# View past executions
|
||||
cline schedule executions <schedule-id>
|
||||
```
|
||||
|
||||
## 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<string, unknown>
|
||||
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/<run-id>.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
|
||||
@@ -1,259 +0,0 @@
|
||||
# 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<string, unknown>
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
||||
@@ -1,211 +0,0 @@
|
||||
---
|
||||
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
|
||||
@@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix showing the ai core exisiting models when resource group field is empty (using the default resource group)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "restricted",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix issue on Account view where balance is fetched twice that cause janky UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixes an issue where thinking text from litellm was not being passed through to Cline thinking UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix Ollama connection issue to default endpoint at port 11434
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Optimized Cline for GPT-5 model family with an aligned system prompt
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
REfactoring Tool Executor
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add search functionality to API provider dropdown
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Remove disabled approve / reject buttons from UI.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: use correct base URL for Vertex AI global endpoint with Claude models
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add "Use custom prompt" option to Ollama provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix AutoApproveModal overflowing issue
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Dify.ai api integration
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
support orchestration mode for sap provider
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Improve Gemini Rate Limit handling
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix: Support Anthropic Caching when using LiteLLM
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Prompt changes for deep-planning in windows/powershell
|
||||
@@ -0,0 +1,26 @@
|
||||
changesDir: .changes
|
||||
unreleasedDir: unreleased
|
||||
headerPath: header.tpl.md
|
||||
changelogPath: CHANGELOG.md
|
||||
versionExt: md
|
||||
versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
|
||||
kindFormat: "### {{.Kind}}"
|
||||
changeFormat: "* {{.Body}}"
|
||||
kinds:
|
||||
- label: Added
|
||||
auto: minor
|
||||
- label: Changed
|
||||
auto: major
|
||||
- label: Deprecated
|
||||
auto: minor
|
||||
- label: Removed
|
||||
auto: major
|
||||
- label: Fixed
|
||||
auto: patch
|
||||
- label: Security
|
||||
auto: patch
|
||||
newlines:
|
||||
afterChangelogHeader: 1
|
||||
beforeChangelogVersion: 1
|
||||
endOfVersion: 1
|
||||
envPrefix: CHANGIE_
|
||||
@@ -1 +0,0 @@
|
||||
../../.clinerules/workflows/hotfix-release.md
|
||||
@@ -1 +0,0 @@
|
||||
../../.clinerules/workflows/release.md
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Only run in Claude Code remote environments
|
||||
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$CLAUDE_PROJECT_DIR"
|
||||
|
||||
echo "=== Claude Code for Web Setup ==="
|
||||
echo ""
|
||||
|
||||
# Install latest gh CLI tool
|
||||
echo "Installing GitHub CLI..."
|
||||
GH_VERSION=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//')
|
||||
curl -sL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tar.gz
|
||||
tar -xzf /tmp/gh.tar.gz -C /tmp
|
||||
sudo mv "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh
|
||||
rm -rf /tmp/gh.tar.gz /tmp/gh_${GH_VERSION}_linux_amd64
|
||||
echo "Installed gh version: $(gh --version | head -1)"
|
||||
echo ""
|
||||
|
||||
# Check if GITHUB_TOKEN is set and configure gh
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
echo "GITHUB_TOKEN is configured - gh CLI is ready to use"
|
||||
echo ""
|
||||
echo "You can use gh commands directly, for example:"
|
||||
echo " gh issue list --repo cline/cline --limit 5"
|
||||
echo " gh pr list --repo cline/cline --state open"
|
||||
echo " gh issue view 123 --repo cline/cline"
|
||||
echo ""
|
||||
else
|
||||
echo "GITHUB_TOKEN is not set - gh CLI will have limited functionality"
|
||||
echo ""
|
||||
echo "To enable full GitHub API access:"
|
||||
echo "1. Create a Fine-grained Personal Access Token at https://github.com/settings/tokens?type=beta"
|
||||
echo "2. Add it as GITHUB_TOKEN in your Claude Code environment settings"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Install project dependencies
|
||||
echo "Installing dependencies..."
|
||||
npm run install:all
|
||||
|
||||
# Generate gRPC/protobuf types (required for TypeScript)
|
||||
echo "Generating proto types..."
|
||||
npm run protos
|
||||
|
||||
echo ""
|
||||
echo "Session setup complete!"
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/claude-code-for-web-setup.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../.agents/skills/cline-sdk
|
||||
@@ -1,205 +0,0 @@
|
||||
This file is the secret sauce for working effectively in this codebase. It captures tribal knowledge—the nuanced, non-obvious patterns that make the difference between a quick fix and hours of back-and-forth & human intervention.
|
||||
|
||||
**When to add to this file:**
|
||||
- User had to intervene, correct, or hand-hold
|
||||
- Multiple back-and-forth attempts were needed to get something working
|
||||
- You discovered something that required reading many files to understand
|
||||
- A change touched files you wouldn't have guessed
|
||||
- Something worked differently than you expected
|
||||
- User explicitly asks to "add this to CLAUDE.md"
|
||||
|
||||
**Proactively suggest additions** when any of the above happen—don't wait to be asked.
|
||||
|
||||
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
|
||||
|
||||
## Miscellaneous
|
||||
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
|
||||
- When creating PRs, contributors should not create changelog-entry files. Maintainers handle release versioning and changelog curation during the release process.
|
||||
- When adding new feature flags, see this PR as a reference https://github.com/cline/cline/pull/7566
|
||||
- Additional instructions about making requests: @.clinerules/network.md
|
||||
|
||||
## gRPC/Protobuf Communication
|
||||
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
|
||||
|
||||
**Proto files live in `proto/`** (e.g., `proto/cline/task.proto`, `proto/cline/ui.proto`)
|
||||
- Each feature domain has its own `.proto` file
|
||||
- For simple data, use shared types in `proto/cline/common.proto` (`StringRequest`, `Empty`, `Int64Request`)
|
||||
- For complex data, define custom messages in the feature's `.proto` file
|
||||
- Naming: Services `PascalCaseService`, RPCs `camelCase`, Messages `PascalCase`
|
||||
- For streaming responses, use `stream` keyword (see `subscribeToAuthCallback` in `account.proto`)
|
||||
|
||||
**Run `npm run protos`** after any proto changes—generates types in:
|
||||
- `src/shared/proto/` - Shared type definitions
|
||||
- `src/generated/grpc-js/` - Service implementations
|
||||
- `src/generated/nice-grpc/` - Promise-based clients
|
||||
- `src/generated/hosts/` - Generated handlers
|
||||
|
||||
**Adding new enum values** (like a new `ClineSay` type) requires updating conversion mappings in `src/shared/proto-conversions/cline-message.ts`
|
||||
|
||||
**Adding new RPC methods** requires:
|
||||
- Handler in `src/core/controller/<domain>/`
|
||||
- Call from webview via generated client: `UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))`
|
||||
|
||||
**Example—the `explain-changes` feature touched:**
|
||||
- `proto/cline/task.proto` - Added `ExplainChangesRequest` message and `explainChanges` RPC
|
||||
- `proto/cline/ui.proto` - Added `GENERATE_EXPLANATION = 29` to `ClineSay` enum
|
||||
- `src/shared/ExtensionMessage.ts` - Added `ClineSayGenerateExplanation` type
|
||||
- `src/shared/proto-conversions/cline-message.ts` - Added mapping for new say type
|
||||
- `src/core/controller/task/explainChanges.ts` - Handler implementation
|
||||
- `webview-ui/src/components/chat/ChatRow.tsx` - UI rendering
|
||||
|
||||
## Adding a New API Provider
|
||||
When adding a new provider (e.g., "openai-codex"), you must update the proto conversion layer in THREE places or the provider will silently reset to Anthropic:
|
||||
|
||||
1. `proto/cline/models.proto` - Add to the `ApiProvider` enum (e.g., `OPENAI_CODEX = 40;`)
|
||||
2. `convertApiProviderToProto()` in `src/shared/proto-conversions/models/api-configuration-conversion.ts` - Add case mapping string to proto enum
|
||||
3. `convertProtoToApiProvider()` in the same file - Add case mapping proto enum back to string
|
||||
|
||||
**Why this matters:** Without these, the provider string hits the `default` case and returns `ANTHROPIC`. The webview, provider list, and handler all work fine, but the state silently resets when it round-trips through proto serialization. No error is thrown.
|
||||
|
||||
**Other files to update when adding a provider:**
|
||||
- `src/shared/api.ts` - Add to `ApiProvider` union type, define models
|
||||
- `src/shared/providers/providers.json` - Add to provider list for dropdown
|
||||
- `src/core/api/index.ts` - Register handler in `createHandlerForProvider()`
|
||||
- `webview-ui/src/components/settings/utils/providerUtils.ts` - Add cases in `getModelsForProvider()` and `normalizeApiConfiguration()`
|
||||
- `webview-ui/src/utils/validate.ts` - Add validation case
|
||||
- `webview-ui/src/components/settings/ApiOptions.tsx` - Render provider component
|
||||
|
||||
## Responses API Providers (OpenAI Codex, OpenAI Native)
|
||||
Providers using OpenAI's Responses API require native tool calling. XML tools don't work with the Responses API.
|
||||
|
||||
**Symptoms of broken native tool calling:**
|
||||
- Tools get called multiple times (e.g., `ask_followup_question` asks the same question twice)
|
||||
- Tool arguments get duplicated or malformed
|
||||
- The model responds but tools aren't recognized
|
||||
|
||||
**Root causes to check:**
|
||||
1. **Provider missing from `isNextGenModelProvider()`** in `src/utils/model-utils.ts`. The native variant matchers (e.g., `native-gpt-5/config.ts`) call this function. If your provider isn't in the list, the matcher returns false and falls back to XML tools.
|
||||
|
||||
2. **Model missing `apiFormat: ApiFormat.OPENAI_RESPONSES`** in its model info (`src/shared/api.ts`). This property signals that the model requires native tool calling. The task runner in `src/core/task/index.ts` checks this and forces `enableNativeToolCalls: true` regardless of user settings.
|
||||
|
||||
**When adding a new Responses API provider:**
|
||||
1. Add provider to `isNextGenModelProvider()` list in `src/utils/model-utils.ts`
|
||||
2. Set `apiFormat: ApiFormat.OPENAI_RESPONSES` on all models that use the Responses API
|
||||
3. The variant matcher and task runner will handle the rest automatically
|
||||
|
||||
## Adding Tools to System Prompt
|
||||
This is tricky—multiple prompt variants and configs. **Always search for existing similar tools first and follow their pattern.** Look at the full chain from prompt definition → variant configs → handler → UI before implementing.
|
||||
|
||||
1. **Add to `ClineDefaultTool` enum** in `src/shared/tools.ts`
|
||||
2. **Tool definition** in `src/core/prompts/system-prompt/tools/` (create file like `generate_explanation.ts`)
|
||||
- Define variants for each `ModelFamily` (generic, next-gen, xs, etc.)
|
||||
- Export variants array (e.g., `export const my_tool_variants = [GENERIC, NATIVE_NEXT_GEN, XS]`)
|
||||
- **Fallback behavior**: If a variant isn't defined for a model family, `ClineToolSet.getToolByNameWithFallback()` automatically falls back to GENERIC. So you only need to export `[GENERIC]` unless the tool needs model-specific behavior.
|
||||
3. **Register in `src/core/prompts/system-prompt/tools/init.ts`** - Import and spread into `allToolVariants`
|
||||
4. **Add to variant configs** - Each model family has its own config in `src/core/prompts/system-prompt/variants/*/config.ts`. Add your tool's enum to the `.tools()` list:
|
||||
- `generic/config.ts`, `next-gen/config.ts`, `gpt-5/config.ts`, `native-gpt-5/config.ts`, `native-gpt-5-1/config.ts`, `native-next-gen/config.ts`, `gemini-3/config.ts`, `glm/config.ts`, `hermes/config.ts`, `xs/config.ts`
|
||||
- **Important**: If you add to a variant's config, make sure the tool spec exports a variant for that ModelFamily (or relies on GENERIC fallback)
|
||||
5. **Create handler** in `src/core/task/tools/handlers/`
|
||||
6. **Wire up in `ToolExecutor.ts`** if needed for execution flow
|
||||
7. **Add to tool parsing** in `src/core/assistant-message/index.ts` if needed
|
||||
8. **If tool has UI feedback**: add `ClineSay` enum in proto, update `src/shared/ExtensionMessage.ts`, update `src/shared/proto-conversions/cline-message.ts`, update `webview-ui/src/components/chat/ChatRow.tsx`
|
||||
|
||||
## Modifying System Prompt
|
||||
**Read these first:** `src/core/prompts/system-prompt/README.md`, `tools/README.md`, `__tests__/README.md`
|
||||
|
||||
System prompt is modular: **components** (reusable sections) + **variants** (model-specific configs) + **templates** (with `{{PLACEHOLDER}}` resolution).
|
||||
|
||||
**Key directories:**
|
||||
- `components/` - Shared sections: `rules.ts`, `capabilities.ts`, `editing_files.ts`, etc.
|
||||
- `variants/` - Model-specific: `generic/`, `next-gen/`, `xs/`, `gpt-5/`, `gemini-3/`, `hermes/`, `glm/`, etc.
|
||||
- `templates/` - Template engine and placeholder definitions
|
||||
|
||||
**Variant tiers (ask user which to modify):**
|
||||
- **Next-gen** (Claude 4, GPT-5, Gemini 2.5): `next-gen/`, `native-next-gen/`, `native-gpt-5/`, `native-gpt-5-1/`, `gemini-3/`, `gpt-5/`
|
||||
- **Standard** (default fallback): `generic/`
|
||||
- **Local/small models**: `xs/`, `hermes/`, `glm/`
|
||||
|
||||
**How overrides work:** Variants can override components via `componentOverrides` in their `config.ts`, or provide a custom template in `template.ts` (e.g., `next-gen/template.ts` exports `rules_template`). If no override, the shared component from `components/` is used.
|
||||
|
||||
**Example: Adding a rule to RULES section**
|
||||
1. Check if variant overrides rules: look for `rules_template` in `variants/*/template.ts` or `componentOverrides.RULES` in `config.ts`
|
||||
2. If shared: modify `components/rules.ts`
|
||||
3. If overridden: modify that variant's template
|
||||
4. XS variant is special—has heavily condensed inline content in `template.ts`
|
||||
|
||||
**After any changes, regenerate snapshots:**
|
||||
```bash
|
||||
UPDATE_SNAPSHOTS=true npm run test:unit
|
||||
```
|
||||
Snapshots live in `__tests__/__snapshots__/`. Tests validate across model families and context variations (browser, MCP, focus chain).
|
||||
|
||||
## Modifying Default Slash Commands
|
||||
Three places need updates:
|
||||
- `src/core/slash-commands/index.ts` - Command definitions
|
||||
- `src/core/prompts/commands.ts` - System prompt integration
|
||||
- `webview-ui/src/utils/slash-commands.ts` - Webview autocomplete
|
||||
|
||||
## Adding New Global State Keys
|
||||
Adding a new key to global state requires updates in multiple places. Missing any step causes silent failures.
|
||||
|
||||
Required steps:
|
||||
1. Type definition in `src/shared/storage/state-keys.ts` - Add to `GlobalState` or `Settings` interface
|
||||
2. Read from globalState in `src/core/storage/utils/state-helpers.ts`:
|
||||
- Add `const myKey = context.globalState.get<GlobalStateAndSettings["myKey"]>("myKey")` in `readGlobalStateFromDisk()`
|
||||
- Add to the return object: `myKey: myKey ?? defaultValue,`
|
||||
3. StateManager handles read/write via `setGlobalState()`/`getGlobalStateKey()` after initialization
|
||||
|
||||
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
|
||||
|
||||
Settings plumbing gotcha: if a key is user-toggleable from settings, wire both controller update paths:
|
||||
- `src/core/controller/state/updateSettings.ts` for webview `updateSetting(...)`
|
||||
- `src/core/controller/state/updateSettingsCli.ts` for CLI/ACP settings updates
|
||||
Missing one path causes a toggle to appear to change in one surface while the backend state stays unchanged.
|
||||
|
||||
Webview toggle gotcha: settings changes must also round-trip back in state payloads.
|
||||
- Add the field to `UpdateSettingsRequest` in `proto/cline/state.proto` (for webview update requests), then run `npm run protos`
|
||||
- Include the key in `Controller.getStateToPostToWebview()` (`src/core/controller/index.ts`)
|
||||
- Ensure `ExtensionState` and webview defaults include the key (`src/shared/ExtensionMessage.ts`, `webview-ui/src/context/ExtensionStateContext.tsx`)
|
||||
If this round-trip wiring is missing, the backend value can update but the toggle in webview appears stuck or reverts.
|
||||
|
||||
## StateManager Cache vs Direct globalState Access
|
||||
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
|
||||
|
||||
Exception: State needed immediately at extension startup (before cache is ready)
|
||||
|
||||
When Window A sets state and immediately opens Window B, the new window's StateManager cache is populated from `context.globalState` during initialization. If you need to read state in Window B right at startup (e.g., in `common.ts` during `initialize()`), read directly from `context.globalState.get()` instead of StateManager's cache.
|
||||
|
||||
Example pattern (see `lastShownAnnouncementId` and `worktreeAutoOpenPath`):
|
||||
```typescript
|
||||
// Writing (normal pattern)
|
||||
controller.stateManager.setGlobalState("myKey", value)
|
||||
|
||||
// Reading at startup in common.ts (bypass cache)
|
||||
const value = context.globalState.get<string>("myKey")
|
||||
```
|
||||
|
||||
This is only needed for cross-window state read during the brief startup window before StateManager cache is fully usable. Normal state access after initialization should use StateManager.
|
||||
|
||||
## ChatRow Cancelled/Interrupted States
|
||||
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
|
||||
|
||||
**The pattern:**
|
||||
1. A message has a `status` field (e.g., `"generating"`, `"complete"`, `"error"`) stored in `message.text` as JSON
|
||||
2. When cancelled mid-operation, the status stays `"generating"` forever—no one updates it
|
||||
3. To detect cancellation, check TWO conditions:
|
||||
- `!isLast` — if this message is no longer the last message, something else happened after it (interrupted)
|
||||
- `lastModifiedMessage?.ask === "resume_task" || "resume_completed_task"` — task was just cancelled and is waiting to resume
|
||||
|
||||
**Example from `generate_explanation`:**
|
||||
```tsx
|
||||
const wasCancelled =
|
||||
explanationInfo.status === "generating" &&
|
||||
(!isLast ||
|
||||
lastModifiedMessage?.ask === "resume_task" ||
|
||||
lastModifiedMessage?.ask === "resume_completed_task")
|
||||
const isGenerating = explanationInfo.status === "generating" && !wasCancelled
|
||||
```
|
||||
|
||||
**Why both checks?**
|
||||
- `!isLast` catches: cancelled → resumed → did other stuff → this old message is stale
|
||||
- `lastModifiedMessage?.ask === "resume_task"` catches: just cancelled, hasn't resumed yet, this message is still technically "last"
|
||||
|
||||
**See also:** `BrowserSessionRow.tsx` uses similar pattern with `isLastApiReqInterrupted` and `isLastMessageResume`.
|
||||
|
||||
**Backend side:** When streaming is cancelled, clean up properly (close tabs, clear comments, etc.) by checking `taskState.abort` after the streaming function returns.
|
||||
@@ -1,423 +0,0 @@
|
||||
# Cline Hooks Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
|
||||
- **Global hooks directory**: `~/Documents/Cline/Hooks/` (applies to all workspaces)
|
||||
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
|
||||
|
||||
Hooks run automatically when enabled.
|
||||
|
||||
## Enabling Hooks
|
||||
|
||||
1. Open Cline settings in VSCode
|
||||
2. Navigate to the Feature Settings section
|
||||
3. Check the "Enable Hooks" checkbox
|
||||
4. Hooks must be executable files (on Unix/Linux/macOS use `chmod +x hookname`)
|
||||
|
||||
## Available Hooks
|
||||
|
||||
### TaskStart Hook
|
||||
- **When**: Runs when a NEW task is started (not when resuming)
|
||||
- **Purpose**: Initialize task context, validate task requirements, set up environment
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskStart`
|
||||
|
||||
### TaskResume Hook
|
||||
- **When**: Runs when an EXISTING task is resumed (after user clicks resume button)
|
||||
- **Purpose**: Validate resumed task state, restore context, check for changes since last run
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskResume`
|
||||
|
||||
### TaskCancel Hook
|
||||
- **When**: Runs when a task is cancelled or a hook is aborted by the user (only if there's actual active work or work was started)
|
||||
- **Purpose**: Clean up resources, log cancellation, save state
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
|
||||
- **Note**: This hook is NOT cancellable
|
||||
|
||||
### TaskComplete Hook (coming soon!)
|
||||
- **When**: Runs when a task is marked as complete
|
||||
- **Purpose**: Log completion status, perform final cleanup, generate reports
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
|
||||
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
|
||||
|
||||
### UserPromptSubmit Hook
|
||||
- **When**: Runs when the user submits a prompt/message (initial task, resume, or feedback)
|
||||
- **Purpose**: Validate user input, preprocess prompts, add context to user messages
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
|
||||
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
|
||||
|
||||
### PreToolUse Hook
|
||||
- **When**: Runs BEFORE a tool is executed
|
||||
- **Purpose**: Validate parameters, block execution, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
|
||||
|
||||
### PostToolUse Hook
|
||||
- **When**: Runs AFTER a tool completes
|
||||
- **Purpose**: Observe results, track patterns, or add context
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
|
||||
|
||||
### PreCompact Hook (coming soon!)
|
||||
- **When**: Runs BEFORE the conversation context is compacted/truncated
|
||||
- **Purpose**: Observe compaction events, log context management, track token usage
|
||||
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
|
||||
- **Workspace Location**: `.clinerules/hooks/PreCompact`
|
||||
|
||||
## Cross-Platform Hook Format
|
||||
|
||||
Cline uses a git-style approach for hooks that works consistently across all platforms:
|
||||
|
||||
### Hook Files (All Platforms)
|
||||
- **No file extensions**: Hooks are named exactly `PreToolUse` or `PostToolUse` (no `.bat`, `.cmd`, `.sh` etc.)
|
||||
- **Shebang required**: First line must be a shebang (e.g., `#!/usr/bin/env bash` or `#!/usr/bin/env node`)
|
||||
- **Executable on Unix**: On Unix/Linux/macOS, hooks must be executable: `chmod +x PreToolUse`
|
||||
- **Windows**: Not currently supported.
|
||||
|
||||
### How It Works
|
||||
|
||||
Like git hooks, Cline executes hook files through a shell that interprets the shebang line:
|
||||
- On Unix/Linux/macOS: Native shell execution with shebang support
|
||||
|
||||
This means:
|
||||
- ✅ Same hook script works on all platforms
|
||||
- ✅ Write once, run anywhere
|
||||
- ✅ Use any scripting language (bash, node, python, etc.)
|
||||
|
||||
### Creating Hooks
|
||||
|
||||
**On Unix/Linux/macOS:**
|
||||
```bash
|
||||
# Create hook file
|
||||
nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
|
||||
# Make executable
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
## Context Injection Timing
|
||||
|
||||
**IMPORTANT**: Context injected by hooks affects **FUTURE AI decisions**, not the current tool execution.
|
||||
|
||||
### Why This Matters
|
||||
|
||||
When a hook runs:
|
||||
1. The AI has already decided what tool to use and with what parameters
|
||||
2. The hook cannot modify those parameters
|
||||
3. Context from the hook is added to the conversation
|
||||
4. The AI sees this context in the **NEXT API request** and can adjust future decisions
|
||||
|
||||
### PreToolUse Hook Flow
|
||||
```
|
||||
1. AI decides: "I'll use write_to_file with these parameters"
|
||||
2. PreToolUse hook runs → can block or add context
|
||||
3. If allowed, tool executes with original parameters
|
||||
4. Context is added to conversation
|
||||
5. Next API request includes this context
|
||||
6. AI adjusts future decisions based on context
|
||||
```
|
||||
|
||||
### PostToolUse Hook Flow
|
||||
```
|
||||
1. Tool completes execution
|
||||
2. PostToolUse hook runs → observes results
|
||||
3. Hook adds context about the outcome
|
||||
4. Context is added to conversation
|
||||
5. Next API request includes this context
|
||||
6. AI can learn from the results
|
||||
```
|
||||
|
||||
## Hook Input/Output
|
||||
|
||||
### Input (via stdin as JSON)
|
||||
|
||||
All hooks receive:
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskStart" | "TaskResume" | "TaskCancel" | "TaskComplete" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PreCompact",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskStart": { // Only for TaskStart
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string",
|
||||
"initialTask": "string"
|
||||
}
|
||||
},
|
||||
"taskResume": { // Only for TaskResume
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
},
|
||||
"previousState": {
|
||||
"lastMessageTs": "string",
|
||||
"messageCount": "string",
|
||||
"conversationHistoryDeleted": "string"
|
||||
}
|
||||
},
|
||||
"taskCancel": { // Only for TaskCancel
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string",
|
||||
"completionStatus": "string"
|
||||
}
|
||||
},
|
||||
"taskComplete": { // Only for TaskComplete
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
}
|
||||
},
|
||||
"userPromptSubmit": { // Only for UserPromptSubmit
|
||||
"prompt": "string",
|
||||
"attachments": ["string"]
|
||||
},
|
||||
"preToolUse": { // Only for PreToolUse
|
||||
"toolName": "string",
|
||||
"parameters": {}
|
||||
},
|
||||
"postToolUse": { // Only for PostToolUse
|
||||
"toolName": "string",
|
||||
"parameters": {},
|
||||
"result": "string",
|
||||
"success": boolean,
|
||||
"executionTimeMs": number
|
||||
},
|
||||
"preCompact": { // Only for PreCompact
|
||||
"contextSize": number,
|
||||
"messagesToCompact": number,
|
||||
"compactionStrategy": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Output (via stdout as JSON)
|
||||
|
||||
All hooks must return:
|
||||
```json
|
||||
{
|
||||
"cancel": boolean, // Required: false to continue, true to block execution
|
||||
"contextModification": "string", // Optional: Context for future AI decisions
|
||||
"errorMessage": "string" // Optional: Error details if blocking
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `cancel` field works as follows:
|
||||
- `false` (or omitted): Allow execution to continue
|
||||
- `true`: Block execution and show error message to user
|
||||
|
||||
## Hook Execution Limits
|
||||
|
||||
- **Timeout**: Hooks must complete within 30 seconds (configurable via `HOOK_EXECUTION_TIMEOUT_MS`)
|
||||
- **Context Size**: Context modifications are limited to 50KB (configurable via `MAX_CONTEXT_MODIFICATION_SIZE`)
|
||||
- **Error Handling**: Expected errors (file not found, permission denied, not a directory) are handled silently; unexpected file system errors are propagated
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### 1. Validation - Block Invalid Operations
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
|
||||
|
||||
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": true,
|
||||
"errorMessage": "Cannot create .js files in TypeScript project",
|
||||
"contextModification": "Use .ts/.tsx extensions only"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
### 2. Context Building - Learn from Operations
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
success=$(echo "$input" | jq -r '.postToolUse.success')
|
||||
path=$(echo "$input" | jq -r '.postToolUse.parameters.path // ""')
|
||||
|
||||
if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
### 3. Performance Monitoring
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs')
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
|
||||
if [[ "$execution_time" -gt 5000 ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
### 4. Logging and Telemetry
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Log to file
|
||||
echo "$input" >> ~/.cline/hook-logs/tool-usage.jsonl
|
||||
|
||||
# Allow execution
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
## Global vs Workspace Hooks
|
||||
|
||||
Cline supports two levels of hooks:
|
||||
|
||||
### Global Hooks
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
|
||||
- **Scope**: Apply to ALL workspaces and projects
|
||||
- **Use Case**: Organization-wide policies, personal preferences, universal validations
|
||||
- **Priority**: Order not guaranteed when combined with workspace hooks
|
||||
|
||||
### Workspace Hooks
|
||||
- **Location**: `.clinerules/hooks/` in each workspace root
|
||||
- **Scope**: Apply only to the specific workspace
|
||||
- **Use Case**: Project-specific rules, team conventions, repository requirements
|
||||
- **Priority**: Order not guaranteed when combined with global hooks
|
||||
|
||||
### Hook Execution
|
||||
|
||||
When multiple hooks exist (global and/or workspace):
|
||||
- All hooks for a given step are executed **concurrently** using `Promise.all`
|
||||
- **Execution order is not guaranteed** - hooks run in parallel
|
||||
- If ALL hooks allow execution (`cancel: false`), the tool proceeds
|
||||
- If ANY hook blocks (`cancel: true`), execution is blocked
|
||||
|
||||
**Result Combination:**
|
||||
- `cancel`: If ANY hook returns `true`, execution is blocked
|
||||
- `contextModification`: All context strings are concatenated with double newlines (`\n\n`)
|
||||
- `errorMessage`: All error messages are concatenated with single newlines (`\n`)
|
||||
|
||||
### Setting Up Global Hooks
|
||||
|
||||
1. The global hooks directory is automatically created at:
|
||||
- macOS/Linux: `~/Documents/Cline/Hooks/`
|
||||
|
||||
2. Add your hook script:
|
||||
```bash
|
||||
# Unix/Linux/macOS
|
||||
nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
3. Enable hooks in Cline settings
|
||||
|
||||
### Example: Global + Workspace Hooks
|
||||
|
||||
**Global Hook** (applies to all projects):
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# ~/Documents/Cline/Hooks/PreToolUse
|
||||
# Universal rule: Never delete package.json
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
|
||||
|
||||
if [[ "$tool_name" == "write_to_file" && "$path" == *"package.json"* ]]; then
|
||||
echo '{"cancel": true, "errorMessage": "Global policy: Cannot modify package.json"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
**Workspace Hook** (applies to specific project):
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# .clinerules/hooks/PreToolUse
|
||||
# Project rule: Only TypeScript files
|
||||
input=$(cat)
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
|
||||
|
||||
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
|
||||
echo '{"cancel": true, "errorMessage": "Project rule: Use .ts files only"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
**All hooks must allow execution for the tool to proceed.** Hooks may execute concurrently.
|
||||
|
||||
## Multi-Root Workspaces
|
||||
|
||||
If you have multiple workspace roots, you can place hooks in each root's `.clinerules/hooks/` directory. All hooks (global and workspace) may execute concurrently. Their results will be combined:
|
||||
|
||||
- **cancel**: If ANY hook returns `true`, execution is blocked
|
||||
- **contextModification**: All context modifications are concatenated
|
||||
- **errorMessage**: All error messages are concatenated
|
||||
|
||||
**Note:** No execution order is guaranteed between hooks from different directories.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Running
|
||||
- Ensure the "Enable Hooks" setting is checked
|
||||
- Verify the hook file is executable (`chmod +x hookname`)
|
||||
- Check the hook file has no syntax errors
|
||||
- Look for errors in VSCode's Output panel (Cline channel)
|
||||
|
||||
### Hook Timing Out
|
||||
- Reduce complexity of the hook script
|
||||
- Avoid expensive operations (network calls, heavy computations)
|
||||
- Consider moving complex logic to a background process
|
||||
|
||||
### Context Not Affecting Behavior
|
||||
- Remember: context affects FUTURE decisions, not the current tool
|
||||
- Ensure context modifications are clear and actionable
|
||||
- Check that context isn't being truncated (50KB limit)
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Hooks run with the same permissions as VSCode
|
||||
- Be cautious with hooks from untrusted sources
|
||||
- Review hook scripts before enabling them
|
||||
- Consider using `.gitignore` to avoid committing sensitive hook logic
|
||||
- Hooks can access all workspace files and environment variables
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Keep hooks fast** - Aim for <100ms execution time
|
||||
2. **Make context actionable** - Be specific about what the AI should do
|
||||
3. **Use structured prefixes** - Help the AI categorize context
|
||||
4. **Handle errors gracefully** - Always return valid JSON
|
||||
5. **Log for debugging** - Keep logs of hook executions for troubleshooting
|
||||
6. **Test incrementally** - Start with simple hooks and add complexity
|
||||
7. **Document your hooks** - Add comments explaining the purpose and logic
|
||||
@@ -1,90 +0,0 @@
|
||||
# Networking & Proxy Support
|
||||
|
||||
To ensure Cline works correctly in all environments (VSCode, JetBrains, CLI) and with various network configurations (especially corporate proxies), strictly follow these guidelines for all network activity.
|
||||
|
||||
In extension code, do NOT use the global `fetch` or a default `axios` instance. (Note, `shared/net.ts` is exempt from these rules because it sets up the fetch wrappers.) In Webview code, you SHOULD use global `fetch`.
|
||||
|
||||
Global `fetch` and default `axios` do not automatically pick up proxy configurations in all environments (specifically JetBrains and CLI). You MUST use the provided utilities in `@/shared/net` which handle proxy agent configuration. In the webview, the browser/embedder handles proxies.
|
||||
|
||||
## Guidelines
|
||||
|
||||
### 1. Using `fetch`
|
||||
|
||||
Instead of `fetch(...)`, import the proxy-aware wrapper:
|
||||
|
||||
```typescript
|
||||
import { fetch } from '@/shared/net'
|
||||
|
||||
// Usage is identical to global fetch
|
||||
const response = await fetch('https://api.example.com/data')
|
||||
```
|
||||
|
||||
### 2. Using `axios`
|
||||
|
||||
When using `axios`, you must apply the settings from `getAxiosSettings()`:
|
||||
|
||||
```typescript
|
||||
import axios from 'axios'
|
||||
import { getAxiosSettings } from '@/shared/net'
|
||||
|
||||
const response = await axios.get('https://api.example.com/data', {
|
||||
headers: { 'Authorization': '...' },
|
||||
...getAxiosSettings() // <--- CRITICAL: Injects the proxy agent if needed
|
||||
})
|
||||
```
|
||||
|
||||
### 3. Third-Party Clients (OpenAI, Ollama, etc.)
|
||||
|
||||
Most API client libraries allow you to customize the `fetch` implementation. You **MUST** pass the proxy-aware `fetch` to these clients.
|
||||
|
||||
**Example (OpenAI):**
|
||||
```typescript
|
||||
import OpenAI from "openai"
|
||||
import { fetch } from "@/shared/net"
|
||||
|
||||
this.client = new OpenAI({
|
||||
apiKey: '...',
|
||||
fetch, // <--- CRITICAL: Pass our fetch wrapper
|
||||
})
|
||||
```
|
||||
|
||||
### 4. Tests
|
||||
|
||||
Use `mockFetchForTesting` to mock the underlying fetch implementation.
|
||||
|
||||
**Example (callback):**
|
||||
|
||||
```
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
...
|
||||
let mockFetch = ...
|
||||
mockFetchForTesting(mockFetch, () => {
|
||||
// This calls mockFetch
|
||||
fetch('https://foo.example').then(...)
|
||||
})
|
||||
// Original fetch is restored immediately when the call returns.
|
||||
```
|
||||
|
||||
**Example (Promise):**
|
||||
|
||||
```
|
||||
import { mockFetchForTesting } from "@/shared/net"
|
||||
|
||||
...
|
||||
let mockFetch = ...
|
||||
await mockFetchForTesting(mockFetch, async () => {
|
||||
await ...
|
||||
// This calls mockFetch
|
||||
await fetch('https://foo.example')
|
||||
...
|
||||
})
|
||||
// Original fetch is restored when the Promise from the callback settles
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
If you are adding a new network call or integration:
|
||||
1. Check `@/shared/net.ts` is imported.
|
||||
2. Ensure `fetch` or `getAxiosSettings` is being used.
|
||||
3. Verify that third-party clients are configured to use the custom fetch.
|
||||
@@ -1,64 +0,0 @@
|
||||
# Storage Architecture
|
||||
|
||||
Global settings, secrets and workspace state are stored in **file-backed JSON stores** under `~/.cline/data/`. This is the shared storage layer used by VSCode, CLI, and JetBrains.
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
### `StorageContext` (src/shared/storage/storage-context.ts)
|
||||
The entry point. Created via `createStorageContext()` and passed to `StateManager.initialize()`. Contains three `ClineFileStorage` instances:
|
||||
- `globalState` → `~/.cline/data/globalState.json`
|
||||
- `secrets` → `~/.cline/data/secrets.json` (mode 0o600)
|
||||
- `workspaceState` → `~/.cline/data/workspaces/<hash>/workspaceState.json`
|
||||
|
||||
### `ClineFileStorage` (src/shared/storage/ClineFileStorage.ts)
|
||||
Synchronous JSON key-value store backed by a single file. Supports `get()`, `set()`, `setBatch()`, `delete()`. Writes are atomic (write-then-rename).
|
||||
|
||||
### `StateManager` (src/core/storage/StateManager.ts)
|
||||
In-memory cache on top of `StorageContext`. All runtime reads hit the cache; writes update cache immediately and debounce-flush to disk.
|
||||
|
||||
## ⚠️ Do NOT Use VSCode's ExtensionContext for Storage
|
||||
|
||||
**Do not** read from or write to `context.globalState`, `context.workspaceState`, or `context.secrets` for persistent data. These are VSCode-specific and not available on CLI or JetBrains.
|
||||
|
||||
Instead, use:
|
||||
```typescript
|
||||
// Reading state
|
||||
StateManager.get().getGlobalStateKey("myKey")
|
||||
StateManager.get().getSecretKey("mySecretKey")
|
||||
StateManager.get().getWorkspaceStateKey("myWsKey")
|
||||
|
||||
// Writing state
|
||||
StateManager.get().setGlobalState("myKey", value)
|
||||
StateManager.get().setSecret("mySecretKey", value)
|
||||
StateManager.get().setWorkspaceState("myWsKey", value)
|
||||
```
|
||||
|
||||
Remember that your data may be read by a different client than the one that wrote it. For example, a value written by Cline in JetBrains may be read by Cline CLI.
|
||||
|
||||
## VSCode Migration (src/hosts/vscode/vscode-to-file-migration.ts)
|
||||
|
||||
On VSCode startup, a migration copies data from VSCode's `ExtensionContext` storage into the file-backed stores. This runs in `src/common.ts` before `StateManager.initialize()`.
|
||||
|
||||
- **Sentinel**: `__vscodeMigrationVersion` key in global state and workspace state — prevents re-migration.
|
||||
- **Merge strategy**: File store wins. Existing values are never overwritten.
|
||||
- **Safe downgrade**: VSCode storage is NOT cleared, so older extension versions still work.
|
||||
|
||||
## Adding New Storage Keys
|
||||
|
||||
1. Add to `src/shared/storage/state-keys.ts` (see existing patterns)
|
||||
2. Read/write via `StateManager` (NOT via `context.globalState`)
|
||||
3. If adding a secret, add to `SecretKeys` array in `state-keys.ts`
|
||||
|
||||
## File Layout
|
||||
|
||||
```
|
||||
~/.cline/
|
||||
data/
|
||||
globalState.json # Global settings & state
|
||||
secrets.json # API keys (mode 0o600)
|
||||
tasks/
|
||||
taskHistory.json # Task history (separate file)
|
||||
workspaces/
|
||||
<hash>/
|
||||
workspaceState.json # Per-workspace toggles
|
||||
```
|
||||
@@ -1,29 +0,0 @@
|
||||
# Address PR Comments
|
||||
|
||||
Review and address all comments on the current branch's PR.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Get the current branch name and find the associated PR:
|
||||
```bash
|
||||
gh pr view --json number,title,body
|
||||
```
|
||||
|
||||
2. Understand the PR context:
|
||||
- Get the full diff: `git diff origin/main...HEAD`
|
||||
- Read the changed files to understand what the PR is doing
|
||||
- Read related files if needed to understand the broader context
|
||||
- Understand the intent and spirit of the changes, not just the code
|
||||
|
||||
3. Fetch all PR comments:
|
||||
- Inline comments: `gh api repos/{owner}/{repo}/pulls/{pr_number}/comments`
|
||||
- General comments: `gh pr view {pr_number} --json comments,reviews`
|
||||
|
||||
4. Present a summary of all comments with your recommendation for each (apply, skip, or respond). Ignore bot noise (release automation, CI status, etc.).
|
||||
|
||||
5. **Wait for my approval** before proceeding.
|
||||
|
||||
6. After approval:
|
||||
- Apply code changes and commit
|
||||
- Reply to comments that were addressed or intentionally skipped
|
||||
- Push commits
|
||||
@@ -0,0 +1,549 @@
|
||||
The goal of this workflow is to take a changeset for a release of Cline, an autonomous coding agent extension that plugs right into your IDE, and write the updated announcement component, and the updated changelog.
|
||||
|
||||
|
||||
For reference, here are some examples of how we converted previous changesets to announcement components / changelogs.
|
||||
|
||||
|
||||
- 3.14
|
||||
<changeset>
|
||||
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
|
||||
|
||||
Releases
|
||||
claude-dev@3.14.0
|
||||
Minor Changes
|
||||
77c9863: create clinerules folder if its currently a file and creating new rule
|
||||
0ffb7dd: disabling shift hint for now & improving tooltip behavior
|
||||
79b76fd: Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile.
|
||||
eb6e481: Full support for LaTeX rendering
|
||||
df37f29: Add support for custom API request timeout. Previously, timeouts were hardcoded to 30 seconds for providers like Ollama or 15 seconds for OpenRouter and Cline. Now users can set a custom timeout value in milliseconds through the settings interface.
|
||||
e4d26be: allow cursorrules and windsurfrules
|
||||
c5de50f: Fix Handle @withRetry() SyntaxError when running extension locally issue
|
||||
61d2f42: enabled pricing calculation for gemini and vertex + more robust caching & cache tracking for gemini & vertex
|
||||
aed152b: add truncation notice when truncating manually
|
||||
2fe2405: Migrate Cline Tools Section to new docs
|
||||
19cc8bc: Add a timeout setting for the terminal connection, allowing users to adjust this if they are having timeout issues
|
||||
03d4410: Added copy button to code blocks.
|
||||
c78fe23: addressed race condition in terminal command usage
|
||||
91e222f: add checkpoints after more messages
|
||||
14230e7: add newrule slash command
|
||||
1c7d33a: Add remote config with posthog allowing for disabling new features until they're reading, making for a better developer experience.
|
||||
4196c14: add cache ui for open router and cline provider
|
||||
d97424f: showing expanded task by default
|
||||
5294e78: Refactor to not pass a message for showing the MCP View from the servers modal
|
||||
70cc437: Fix Windows path issue: Correct handling of import.meta.url to avoid leading slash in pathname
|
||||
4b697d8: Migrate the addRemoteServer to protobus
|
||||
Patch Changes
|
||||
c63d9a1: updated drag and drop text to say "drop" instead of "drag"
|
||||
459adf0: Add markdown copy to chat
|
||||
74ec823: Minor UX improvement to drag and drop ux
|
||||
b0961f4: Remove linear pull request action
|
||||
e9ce384: searchCommits protobus migration
|
||||
5802b68: createRuleFile protobus migration
|
||||
df7f9fc: Add dependsOn to more blocks in the tasks.json
|
||||
41ae732: Fix for git commit mentions in repos with no git commits
|
||||
7e78445: Adding args to allow Cursor to open workspaces (for checkpoint testing/development)
|
||||
bdfda6f: feat(bedrock): Introduce Amazon Nova Premier
|
||||
65243ad: Introduce UI library for future UI development
|
||||
4565e06: checkIsImageURL migrated to protobus
|
||||
5a8e9d8: protobus migration for openImage
|
||||
deeda6e: Lowering Gemini cache TTL time
|
||||
db0b022: Adding UI to show openrouter balance next to provider
|
||||
4650ffa: deleteRuleFile protobus migration
|
||||
d4bd755: fix cost calculation
|
||||
</changeset>
|
||||
|
||||
<changelog>
|
||||
## [3.14.0]
|
||||
|
||||
- Add UI to show openrouter balance next to provider
|
||||
- Add support for custom model ID in AWS Bedrock provider, enabling use of Application Inference Profile (Thanks @clicube!)
|
||||
- Add more robust caching & cache tracking for gemini & vertex providers
|
||||
- Add support for LaTeX rendering
|
||||
- Add support for custom API request timeout. Timeouts were 15-30s, but can now be configured via settings for OpenRouter/Cline & Ollama (Thanks @WingsDrafterwork!)
|
||||
- Add truncation notice when truncating manually
|
||||
- Add a timeout setting for the terminal connection, allowing users to set a time to wait for terminal startup
|
||||
- Add copy button to code blocks
|
||||
- Add copy button to markdown blocks (Thanks @weshoke!)
|
||||
- Add checkpoints to more messages
|
||||
- Add slash command to create a new rules file (/newrule)
|
||||
- Add cache ui for open router and cline provider
|
||||
- Add Amazon Nova Premier model to Bedrock (Thanks @watany!)
|
||||
- Add support for cursorrules and windsurfrules
|
||||
- Add support for batch history deletion (Thanks @danix800!)
|
||||
- Improve Drag & Drop experience
|
||||
- Create clinerules folder creating new rule if it's needed
|
||||
- Enable pricing calculation for gemini and vertex providers
|
||||
- Refactor message handling to not show the MCP View of the server modal
|
||||
- Migrate the addRemoteServer to protobus (Thanks @DaveFres!)
|
||||
- Update task header to be expanded by default
|
||||
- Update Gemini cache TTL time to 15 minutes
|
||||
- Fix race condition in terminal command usage
|
||||
- Fix to correctly handle `import.meta.url`, avoiding leading slash in pathname for Windows (Thanks @DaveFres!)
|
||||
- Fix @withRetry() decoration syntax error when running extension locally (Thanks @DaveFres!)
|
||||
- Fix for git commit mentions in repos with no git commits
|
||||
- Fix cost calculation (Thanks @BarreiroT!)
|
||||
</changelog>
|
||||
|
||||
|
||||
<announcement-component>
|
||||
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<VSCodeButton appearance="icon" onClick={hideAnnouncement} style={closeIconStyle}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h3 style={h3TitleStyle}>
|
||||
🎉{" "}New in v{minorVersion}
|
||||
</h3>
|
||||
<ul style={ulStyle}>
|
||||
<li>
|
||||
<b>Gemini prompt caching:</b> Gemini and Vertex providers now support prompt caching and price tracking for
|
||||
Gemini models.
|
||||
</li>
|
||||
<li>
|
||||
<b>Copy Buttons:</b> Buttons were added to Markdown and Code blocks that allow you to copy their contents
|
||||
easily.
|
||||
</li>
|
||||
<li>
|
||||
<b>/newrule command:</b> New slash command to have cline write your .clinerules for you based on your
|
||||
workflow.
|
||||
</li>
|
||||
<li>
|
||||
<b>Drag and drop improvements:</b> Don't forget to hold shift while dragging files!
|
||||
</li>
|
||||
<li>Added more checkpoints across the task, allowing you to restore from more than just file changes.</li>
|
||||
<li>Added support for rendering LaTeX in message responses. (Try asking Cline to show the quadratic formula)</li>
|
||||
</ul>
|
||||
<Accordion isCompact className="pl-0">
|
||||
<AccordionItem
|
||||
key="1"
|
||||
aria-label="Previous Updates"
|
||||
title="Previous Updates:"
|
||||
classNames={{
|
||||
trigger: "bg-transparent border-0 pl-0 pb-0 w-fit",
|
||||
title: "font-bold text-[var(--vscode-foreground)]",
|
||||
indicator:
|
||||
"text-[var(--vscode-foreground)] mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
|
||||
}}>
|
||||
<ul style={ulStyle}>
|
||||
<li>
|
||||
<b>Global Cline Rules:</b> store multiple rules files in Documents/Cline/Rules to share between
|
||||
projects.
|
||||
</li>
|
||||
<li>
|
||||
<b>Cline Rules Popup:</b> New button in the chat area to view workspace and global cline rules files
|
||||
to plug and play specific rules for the task
|
||||
</li>
|
||||
<li>
|
||||
<b>Slash Commands:</b> Type <code>/</code> in chat to see the list of quick actions, like starting a
|
||||
new task (more coming soon!)
|
||||
</li>
|
||||
<li>
|
||||
<b>Edit Messages:</b> You can now edit a message you sent previously by clicking on it. Optionally
|
||||
restore your project when the message was sent!
|
||||
</li>
|
||||
</ul>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
{/*
|
||||
// Leave this here for an example of how to structure the announcement
|
||||
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
|
||||
<li>
|
||||
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
|
||||
so I recommend trying them out.
|
||||
<br />
|
||||
{!apiConfiguration?.openRouterApiKey && (
|
||||
<VSCodeButtonLink
|
||||
href={getOpenRouterAuthUrl(vscodeUriScheme)}
|
||||
style={{
|
||||
transform: "scale(0.85)",
|
||||
transformOrigin: "left center",
|
||||
margin: "4px -30px 2px 0",
|
||||
}}>
|
||||
Get OpenRouter API Key
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
{apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
|
||||
<VSCodeButton
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "apiConfiguration",
|
||||
apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
transform: "scale(0.85)",
|
||||
transformOrigin: "left center",
|
||||
margin: "4px -30px 2px 0",
|
||||
}}>
|
||||
Switch to OpenRouter
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
<b>Edit Cline's changes before accepting!</b> When he creates or edits a file, you can modify his
|
||||
changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
|
||||
the center to undo "<code>{"// rest of code here"}</code>" shenanigans)
|
||||
</li>
|
||||
<li>
|
||||
New <code>search_files</code> tool that lets Cline perform regex searches in your project, letting
|
||||
him refactor code, address TODOs and FIXMEs, remove dead code, and more!
|
||||
</li>
|
||||
<li>
|
||||
When Cline runs commands, you can now type directly in the terminal (+ support for Python
|
||||
environments)
|
||||
</li>
|
||||
</ul>*/}
|
||||
<div style={hrStyle} />
|
||||
<p style={linkContainerStyle}>
|
||||
Join us on{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://x.com/cline">
|
||||
X,
|
||||
</VSCodeLink>{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://discord.gg/cline">
|
||||
discord,
|
||||
</VSCodeLink>{" "}
|
||||
or{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://www.reddit.com/r/cline/">
|
||||
r/cline
|
||||
</VSCodeLink>
|
||||
for more updates!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</announcement-component>
|
||||
|
||||
- 3.13
|
||||
|
||||
<changeset>
|
||||
Minor Changes
|
||||
2964388: Added copy button to MermaidBlock component
|
||||
75143a7: Add the ability to fetch from global cline rules files
|
||||
Patch Changes
|
||||
a0252e7: convert inline style to tailwind css of file SettingsView.tsx
|
||||
ab59bd9: Add stream options back to xai provider
|
||||
7276f50: Icons to indicate an action is occuring outside of the users workspace
|
||||
0b19ba6: update to NEW model
|
||||
</changeset>
|
||||
|
||||
<changelog>
|
||||
## [3.13.0]
|
||||
|
||||
- Add Cline rules popover under the chat field, allowing you to easily add, enable & disable workspace level or global rule files
|
||||
- Add new slash command menu letting you type “/“ to do quick actions like creating new tasks
|
||||
- Add ability to edit past messages, with options to restore your workspace back to that point
|
||||
- Allow sending a message when selecting an option provided by the question or plan tool
|
||||
- Add command to jump to Cline's chat input
|
||||
- Add support for OpenAI o3 & 4o-mini (Thanks @PeterDaveHello and @arafatkatze!)
|
||||
- Add baseURL option for Google Gemini provider (Thanks @owengo and @olivierhub!)
|
||||
- Add support for Azure's DeepSeek model. (Thanks @yt3trees!)
|
||||
- Add ability for models that support it to receive image responses from MCP servers (Thanks @rikaaa0928!)
|
||||
- Improve search and replace diff editing by making it more flexible with models that fail to follow structured output instructions. (Thanks @chi-cat!)
|
||||
- Add detection of Ctrl+C termination in terminal, improving output reading issues
|
||||
- Fix issue where some commands with large output would cause UI to freeze
|
||||
- Fix token usage tracking issues with vertex provider (Thanks @mzsima!)
|
||||
- Fix issue with xAI reasoning content not being parsed (Thanks @mrubens!)
|
||||
</changelog>
|
||||
|
||||
<announcement-component>
|
||||
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
const minorVersion = version.split(".").slice(0, 2).join(".") // 2.0.0 -> 2.0
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<VSCodeButton appearance="icon" onClick={hideAnnouncement} style={closeIconStyle}>
|
||||
<span className="codicon codicon-close"></span>
|
||||
</VSCodeButton>
|
||||
<h3 style={h3TitleStyle}>
|
||||
🎉{" "}New in v{minorVersion}
|
||||
</h3>
|
||||
<ul style={ulStyle}>
|
||||
<li>
|
||||
<b>Global Cline Rules:</b> store multiple rules files in Documents/Cline/Rules to share between projects.
|
||||
</li>
|
||||
<li>
|
||||
<b>Cline Rules Popup:</b> New button in the chat area to view workspace and global cline rules files to plug
|
||||
and play specific rules for the task
|
||||
</li>
|
||||
<li>
|
||||
<b>Slash Commands:</b> Type <code>/</code> in chat to see the list of quick actions, like starting a new task
|
||||
(more coming soon!)
|
||||
</li>
|
||||
<li>
|
||||
<b>Edit Messages:</b> You can now edit a message you sent previously by clicking on it. Optionally restore
|
||||
your project when the message was sent!
|
||||
</li>
|
||||
</ul>
|
||||
<h4 style={{ margin: "5px 0 5px" }}>Previous Updates:</h4>
|
||||
<ul style={ulStyle}>
|
||||
<li>
|
||||
<b>Model Favorites:</b> You can now mark your favorite models when using Cline & OpenRouter providers for
|
||||
quick access!
|
||||
</li>
|
||||
<li>
|
||||
<b>Faster Diff Editing:</b> Improved animation performance for large files, plus a new indicator in chat
|
||||
showing the number of edits Cline makes.
|
||||
</li>
|
||||
<li>
|
||||
<b>New Auto-Approve Options:</b> Turn off Cline's ability to read and edit files outside your workspace.
|
||||
</li>
|
||||
</ul>
|
||||
{/*
|
||||
// Leave this here for an example of how to structure the announcement
|
||||
<ul style={{ margin: "0 0 8px", paddingLeft: "12px" }}>
|
||||
<li>
|
||||
OpenRouter now supports prompt caching! They also have much higher rate limits than other providers,
|
||||
so I recommend trying them out.
|
||||
<br />
|
||||
{!apiConfiguration?.openRouterApiKey && (
|
||||
<VSCodeButtonLink
|
||||
href={getOpenRouterAuthUrl(vscodeUriScheme)}
|
||||
style={{
|
||||
transform: "scale(0.85)",
|
||||
transformOrigin: "left center",
|
||||
margin: "4px -30px 2px 0",
|
||||
}}>
|
||||
Get OpenRouter API Key
|
||||
</VSCodeButtonLink>
|
||||
)}
|
||||
{apiConfiguration?.openRouterApiKey && apiConfiguration?.apiProvider !== "openrouter" && (
|
||||
<VSCodeButton
|
||||
onClick={() => {
|
||||
vscode.postMessage({
|
||||
type: "apiConfiguration",
|
||||
apiConfiguration: { ...apiConfiguration, apiProvider: "openrouter" },
|
||||
})
|
||||
}}
|
||||
style={{
|
||||
transform: "scale(0.85)",
|
||||
transformOrigin: "left center",
|
||||
margin: "4px -30px 2px 0",
|
||||
}}>
|
||||
Switch to OpenRouter
|
||||
</VSCodeButton>
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
<b>Edit Cline's changes before accepting!</b> When he creates or edits a file, you can modify his
|
||||
changes directly in the right side of the diff view (+ hover over the 'Revert Block' arrow button in
|
||||
the center to undo "<code>{"// rest of code here"}</code>" shenanigans)
|
||||
</li>
|
||||
<li>
|
||||
New <code>search_files</code> tool that lets Cline perform regex searches in your project, letting
|
||||
him refactor code, address TODOs and FIXMEs, remove dead code, and more!
|
||||
</li>
|
||||
<li>
|
||||
When Cline runs commands, you can now type directly in the terminal (+ support for Python
|
||||
environments)
|
||||
</li>
|
||||
</ul>*/}
|
||||
<div style={hrStyle} />
|
||||
<p style={linkContainerStyle}>
|
||||
Join us on{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://x.com/cline">
|
||||
X,
|
||||
</VSCodeLink>{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://discord.gg/cline">
|
||||
discord,
|
||||
</VSCodeLink>{" "}
|
||||
or{" "}
|
||||
<VSCodeLink style={linkStyle} href="https://www.reddit.com/r/cline/">
|
||||
r/cline
|
||||
</VSCodeLink>
|
||||
for more updates!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</announcement-component>
|
||||
|
||||
|
||||
We have a changeset PR that automatically generated as new unreleased PRs are merged into main, the PR is always called "Changeset version bump" and the author is github-actions.
|
||||
|
||||
The Changeset PR description looks something like this:
|
||||
|
||||
<changeset-pr-description>
|
||||
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or [setup this action to publish automatically](https://github.com/changesets/action#with-publishing). If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
|
||||
|
||||
|
||||
# Releases
|
||||
## claude-dev@3.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- c6e8b04: Recent task list is now collapsible, allowing users to hide their recent tasks (e.g. when sharing their screen).
|
||||
- aabe4ae: Add detection for new users to display special components
|
||||
- 6c18d51: adds global endpoint for vertex ai users
|
||||
- 080ed7c: Add Tailwind CSS IntelliSense to the the recommended extensions list
|
||||
- 5147e28: new workflow feature
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c0b3c69: fix eternal loading states when the last message is a checkpoint
|
||||
- 570ece3: selectImages protos migration
|
||||
- 8d8452e: askResponse protobus migration
|
||||
- cd1ff2a: Finishing the migration of Vscode Advanced settings to Settings Webview
|
||||
</changeset-pr-description>
|
||||
|
||||
The changeset pr is ALWAYS on the following branch: `changeset-release/main`.
|
||||
|
||||
I have the `gh` command line tool set up and authenticated, so you have everything you need.
|
||||
|
||||
The first step is to get the full diff from the changeset PR to look at the changes that were automatically made to the `CHANGELOG.md` file. By default it will automatically add a new section to the changelog.md file with the new version. The problem with the automatically generated section is that it just takes the text that the developers threw into their changeset files for each corresponding PR, and they can be pretty vague and bad. Additionally there's some stuff that is totally irrelevant for the end user, like minor refactoring changes. So I manually typically go in and update this section to be a proper changelog that will show up in our patchnotes. You can look at how the rest of the file is done because those are all good examples of us updating this to use good language for the end user. We usually put new features up top (and the most exciting flagship features at the very top), and then bug fixes/improvements at the bottom. Having some basic organization to the ordering of the bullet points by content is nice. But use common sense.
|
||||
|
||||
To handle this process effectively, do the following:
|
||||
|
||||
For each of the automatically generated bullet points in the Changelog.md, you should
|
||||
1. Take the commit hash at the start of the bullet point, and use the `gh` command line tool find the PR that it was associated with.
|
||||
2. Use the `gh` command to get the PR title/description/discussion to understand the context surrounding the PR.
|
||||
3. Use the `gh` command line tool to get the full PR diff to fully understand the changes made in the code.
|
||||
4. Synthesize that knowledge to determine (a) whether or not this change is relevant to end users and (b) what the text & ordering of the line should be.
|
||||
5. Update the `CHANGELOG.md` accordingly
|
||||
|
||||
Do this for every single item in the list from the autogenerated bullet points. We want to be diligent and have a full understanding of every feature so we can make the best changelog ever!
|
||||
|
||||
Here are some principles for good changelogs from keepchangelog.com, a handy guide:
|
||||
|
||||
<keepachangelog-pinciples-for-good-changelogs>
|
||||
### Guiding Principles
|
||||
- Changelogs are for humans, not machines.
|
||||
- There should be an entry for every single version.
|
||||
- The same types of changes should be grouped.
|
||||
- The latest version comes first.
|
||||
|
||||
### Bullet points in the changelog should follow these principles:
|
||||
- Types of changes
|
||||
- Added for new features.
|
||||
- Changed for changes in existing functionality.
|
||||
- Deprecated for soon-to-be removed features.
|
||||
- Removed for now removed features.
|
||||
- Fixed for any bug fixes.
|
||||
- Security in case of vulnerabilities.
|
||||
</keepachangelog-pinciples-for-good-changelogs>
|
||||
|
||||
Lastly, when developers make a PR, they typically make a changeset. And they have 3 options when making the changeset:
|
||||
|
||||
1. Patch
|
||||
2. Minor
|
||||
3. Major
|
||||
|
||||
Sometimes they label something as minor when really it should just be a patch. Or vice versa. Because of this, the automatic version bump may be incorrect. So when starting out this workflow, you should use the <ask_followup_question> tool to confirm with me whether or not this should be a patch bump (show the old version number and what the proposed new version number would be) or a minor bump. Part of the release process is making sure the version in package.json that is automatically changed actually corresponds with what we decided the bump should actually be based on the features. ALL these modifications happen in the `changeset-release/main` branch btw.
|
||||
|
||||
<important_note>
|
||||
Before doing any of this, make sure you check out the `changeset-release/main` and pull the most recent up to date changes. Then perform all this work in that branch.
|
||||
|
||||
New announcement banners should ONLY be made for minor version bumps or higher. That's another reason why double checking if the changelog warrants the bump is important.
|
||||
|
||||
Also, SUPER important: For any external contributors that aren't part of the cline github organization, we always want to add a (Thanks @username!) at the end of the changelog to attribute them properly. We're an open source project and it's ethical to do this.
|
||||
</important_note>
|
||||
|
||||
Once the changelog looks good, and the version number looks good, we gotta double check that the version number in the changelog has the brackets around it. And as a final step, double check the package.json version number matches the latest number in the changelog. And as the ultimate final step we run `npm run install:all` to make sure the package version number permiates through the lock file.
|
||||
|
||||
|
||||
<detailed_sequence_of_steps>
|
||||
# Cline Release Process - Detailed Sequence of Steps
|
||||
|
||||
## Before Starting
|
||||
1. First, examine the changeset PR without checking it out:
|
||||
```bash
|
||||
gh pr view changeset-release/main
|
||||
```
|
||||
|
||||
2. View the PR diff to see the auto-generated CHANGELOG.md changes:
|
||||
```bash
|
||||
gh pr diff changeset-release/main > changeset-diff.txt
|
||||
cat changeset-diff.txt | grep -A 50 "CHANGELOG.md"
|
||||
```
|
||||
|
||||
## Initial Setup
|
||||
3. Once you're ready to start, checkout and update the changeset release branch:
|
||||
```bash
|
||||
git checkout changeset-release/main
|
||||
git pull origin changeset-release/main
|
||||
```
|
||||
|
||||
## Analyzing Each Change
|
||||
4. For each commit hash in the auto-generated changelog entries:
|
||||
|
||||
a. Find the PR number associated with a commit hash:
|
||||
```bash
|
||||
gh pr list --search "<commit-hash>" --state merged
|
||||
```
|
||||
|
||||
b. Get PR details for better context:
|
||||
```bash
|
||||
gh pr view <PR-number>
|
||||
```
|
||||
|
||||
c. Check if the contributor is external to determine if attribution is needed:
|
||||
```bash
|
||||
# Extract username from PR
|
||||
USERNAME=$(gh pr view <PR-number> --json author --jq .author.login)
|
||||
|
||||
# Check if user is a member of the Cline organization
|
||||
# this command is a bit finnicky, but it 100% works.
|
||||
# if you see a `Error executing command: The command ran successfully, but we couldn't capture its output. Please proceed accordingly.` error, just retry it until you actually get the output
|
||||
# don't make any assumptions, just retry the command to actually get the output and determine if they're external or not.
|
||||
# no output means they are an external contributor, otherwise if there is output they are an internal contributor (part of our github org)
|
||||
gh api "orgs/cline/members" --jq "map(.login)" | grep -i "pashpashpash"
|
||||
```
|
||||
|
||||
d. View the full PR diff to understand code changes:
|
||||
```bash
|
||||
gh pr diff <PR-number> > pr-diff-<PR-number>.txt
|
||||
cat pr-diff-<PR-number>.txt
|
||||
```
|
||||
|
||||
## Updating the Changelog
|
||||
5. Based on PR analysis, update the CHANGELOG.md with user-friendly descriptions:
|
||||
- Use the `<replace_in_file>` tool to edit the CHANGELOG.md file
|
||||
- Group by feature type (Added, Changed, Fixed)
|
||||
- Put most exciting features at the top
|
||||
- Move bug fixes and small improvements to the bottom
|
||||
- Use clear, end-user focused language
|
||||
- For external contributors, add attribution at the end of the relevant entry: `(Thanks @username!)`
|
||||
|
||||
## Version Number Verification
|
||||
6. Confirm the version bump is appropriate:
|
||||
- Check package.json to verify the auto-generated version number:
|
||||
```bash
|
||||
cat package.json | grep "\"version\""
|
||||
```
|
||||
- If the feature set doesn't warrant a minor bump, use the `<replace_in_file>` tool to modify package.json
|
||||
|
||||
7. Ensure the version in CHANGELOG.md has brackets around it:
|
||||
```
|
||||
## [3.16.0]
|
||||
```
|
||||
|
||||
## Creating the Announcement (for minor/major versions only)
|
||||
8. If this is a minor version bump, create/update the announcement component:
|
||||
- Use the `<replace_in_file>` tool to edit the src/views/components/announcement.tsx file
|
||||
- Update the highlights based on key features
|
||||
- Move previous version highlights to the "Previous Updates" section
|
||||
- Use the previous announcement components as reference for structure
|
||||
|
||||
## Finalizing the Release
|
||||
9. Update dependencies with the new version number:
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
|
||||
10. Commit your changes:
|
||||
```bash
|
||||
git add CHANGELOG.md package.json package-lock.json src/views/components/announcement.tsx
|
||||
git commit -m "Update CHANGELOG.md and announcement for version 3.16.0"
|
||||
```
|
||||
|
||||
11. Push your changes to the changeset branch:
|
||||
```bash
|
||||
git push origin changeset-release/main
|
||||
```
|
||||
|
||||
12. Check that your changes pushed successfully:
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
</detailed_sequence_of_steps>
|
||||
@@ -1,49 +0,0 @@
|
||||
# Find Best Reviewers for Current Branch
|
||||
|
||||
Analyze my current branch to find the best people to review my PR based on **domain expertise** and git history.
|
||||
|
||||
## Steps
|
||||
|
||||
1. Get the current branch name and verify it's not `main`
|
||||
2. Get the diff between the current branch and `origin/main`:
|
||||
- Use `git diff origin/main...HEAD --name-only` to get changed files
|
||||
- Use `git diff origin/main...HEAD` to understand the nature/spirit of the changes
|
||||
3. **Identify the domain/feature area** being changed:
|
||||
- Read the diff carefully to understand WHAT is being changed conceptually (e.g., "slash commands", "authentication", "API client", "UI components")
|
||||
- This semantic understanding is crucial for finding the right reviewers
|
||||
4. Find domain experts by searching for related files and their contributors:
|
||||
- Identify all files related to the feature/domain (not just the ones changed)
|
||||
- Example: if changing slash commands, find ALL slash-command related files across the codebase
|
||||
- Use `git log --format="%an <%ae>" -- <related-files-pattern>` to find who has expertise in that domain
|
||||
5. For additional context, also gather:
|
||||
- `git blame -L <start>,<end> origin/main -- <file-path>` for exact lines changed
|
||||
- Recent commit activity on related files
|
||||
6. Score and rank contributors by:
|
||||
- **Highest weight: Domain expertise** - who has the most commits to files in this feature area (even files not touched by this PR)
|
||||
- **Medium weight: Direct file expertise** - commits to the specific files being changed
|
||||
- **Lower weight: Line-level ownership** - authored the exact lines being modified
|
||||
7. Exclude myself (check against my git config user.email)
|
||||
8. Present the top 5 reviewers as an ordered list
|
||||
|
||||
## Output Format
|
||||
|
||||
Output an ordered list:
|
||||
|
||||
1. **Name** - Domain expert: 15 commits to slash-command related files, authored core parsing logic
|
||||
2. **Name** - 8 commits to affected files, recently added the feature being modified
|
||||
3. ...
|
||||
|
||||
## Commands Reference
|
||||
```bash
|
||||
git config user.email
|
||||
git diff origin/main...HEAD --name-only
|
||||
git diff origin/main...HEAD
|
||||
# Find related files for a domain (adjust pattern based on what you learn from the diff)
|
||||
find . -type f \( -name "*slash-command*" -o -name "*SlashCommand*" \) | head -20
|
||||
# Get contributors for related files
|
||||
find . -type f \( -name "*slash-command*" -o -name "*SlashCommand*" \) -print0 | xargs -0 git log --format="%an <%ae>" -- | sort | uniq -c | sort -rn
|
||||
git log --format="%an <%ae>" -- <file> | sort | uniq -c | sort -rn
|
||||
git blame -L 10,20 origin/main -- <file>
|
||||
```
|
||||
|
||||
Do NOT ask questions - analyze the changes, identify the domain, and output the reviewer list.
|
||||
@@ -1,187 +0,0 @@
|
||||
# Hotfix Release
|
||||
|
||||
Create a hotfix release by cherry-picking specific commits from main onto the latest release tag.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow helps you:
|
||||
1. Select specific commits from main to include in a hotfix
|
||||
2. Create a release notes commit on main (changelog + version bump)
|
||||
3. Cherry-pick everything onto the latest release tag
|
||||
4. Tag and push the new release
|
||||
|
||||
## Step 1: Setup and Gather Information
|
||||
|
||||
First, ensure we're on main and up to date:
|
||||
|
||||
```bash
|
||||
git checkout main && git pull origin main
|
||||
```
|
||||
|
||||
Get the latest release tag:
|
||||
|
||||
```bash
|
||||
git tag --sort=-v:refname | head -1
|
||||
```
|
||||
|
||||
## Step 2: Present Commits Since Last Release
|
||||
|
||||
Show all commits on main since the last release tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git log ${LAST_TAG}..HEAD --oneline --format="%h %s (%an)"
|
||||
```
|
||||
|
||||
Also get the commit messages already on the tag (to identify previously cherry-picked commits). Note: Run these as separate commands to avoid shell parsing issues with parentheses in author names:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
PREV_TAG=$(git tag --sort=-v:refname | head -2 | tail -1)
|
||||
```
|
||||
|
||||
```bash
|
||||
git log $PREV_TAG..$LAST_TAG --oneline --format="%s"
|
||||
```
|
||||
|
||||
**Present the list** to the user in a numbered format with commit hash, subject, and author. For any commits whose subject line already appears in the tag's history (previously cherry-picked in an earlier hotfix) or are "Release Notes" commits, add `(already in previous hotfix)` or `(release notes - skip)` after them so the user knows to skip those.
|
||||
|
||||
Ask which commits to include in the hotfix.
|
||||
|
||||
Use the ask_followup_question tool to let the user specify which commits they want (by number or hash).
|
||||
|
||||
## Step 3: Analyze Selected Commits
|
||||
|
||||
For each selected commit:
|
||||
1. Get the full commit message: `git show --no-patch --format="%B" <hash>`
|
||||
2. Get the diff to understand the change: `git show <hash> --stat`
|
||||
3. Find the associated PR if any: `gh pr list --search "<hash>" --state merged --json number,title --jq '.[0]'`
|
||||
|
||||
Build a mental model of what these changes do for the changelog.
|
||||
|
||||
## Step 4: Determine New Version Number
|
||||
|
||||
Parse the current version from package.json and the last tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
echo "Last release: $LAST_TAG"
|
||||
cat package.json | grep '"version"'
|
||||
```
|
||||
|
||||
Hotfixes always increment the patch version (e.g., 3.40.0 -> 3.40.1, or 3.40.1 -> 3.40.2).
|
||||
|
||||
**Ask the user to confirm the new version number.**
|
||||
|
||||
## Step 5: Create Release Notes Commit on Main
|
||||
|
||||
On the main branch, create a commit that updates:
|
||||
|
||||
1. **CHANGELOG.md** - Add a new section for the hotfix version at the top:
|
||||
```markdown
|
||||
## [3.40.1]
|
||||
|
||||
- Description of fix 1
|
||||
- Description of fix 2
|
||||
```
|
||||
|
||||
Write clear, user-friendly descriptions based on your analysis of the commits.
|
||||
|
||||
2. **package.json** - Update the version field to the new version
|
||||
|
||||
3. No changelog-entry file cleanup is needed. Contributors do not create changelog-entry files in this repo.
|
||||
|
||||
**Skip running `npm run install:all`** - release automation handles lockfile consistency as needed.
|
||||
|
||||
Commit with message format: `v{VERSION} Release Notes (hotfix)`
|
||||
|
||||
In the commit body, mention:
|
||||
- This is for a hotfix release
|
||||
- List the cherry-picked commits that will be included
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md package.json
|
||||
git commit -m "v3.40.1 Release Notes (hotfix)
|
||||
|
||||
Hotfix release including:
|
||||
- <commit1-hash>: <description>
|
||||
- <commit2-hash>: <description>
|
||||
"
|
||||
```
|
||||
|
||||
Push to main:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Step 6: Build the Hotfix on the Tag
|
||||
|
||||
Checkout the last release tag (detached HEAD):
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git checkout $LAST_TAG
|
||||
```
|
||||
|
||||
Cherry-pick the selected commits in order:
|
||||
|
||||
```bash
|
||||
git cherry-pick <commit1-hash>
|
||||
git cherry-pick <commit2-hash>
|
||||
# ... etc
|
||||
```
|
||||
|
||||
Finally, cherry-pick the release notes commit you just pushed to main:
|
||||
|
||||
```bash
|
||||
# Get the hash of the release notes commit (should be HEAD of main)
|
||||
RELEASE_NOTES_COMMIT=$(git rev-parse main)
|
||||
git cherry-pick $RELEASE_NOTES_COMMIT
|
||||
```
|
||||
|
||||
## Step 7: Tag and Push
|
||||
|
||||
After all cherry-picks are applied successfully:
|
||||
|
||||
```bash
|
||||
# Tag the new release
|
||||
git tag v{VERSION}
|
||||
|
||||
# Push the tag to remote
|
||||
git push origin v{VERSION}
|
||||
```
|
||||
|
||||
## Step 8: Return to Main and Summary
|
||||
|
||||
Return to main branch:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Copy a Slack announcement message to clipboard** with the version and PR links for each included fix:
|
||||
|
||||
```
|
||||
VS Code Hotfix v{VERSION} Published
|
||||
|
||||
- Description of fix 1 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
- Description of fix 2 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
```
|
||||
|
||||
Present a final summary:
|
||||
- New version: v{VERSION}
|
||||
- Tag pushed: yes
|
||||
- Commits included: (list them)
|
||||
- Slack message copied to clipboard: yes
|
||||
|
||||
Remind the user to:
|
||||
1. Manually trigger the publish release GitHub Action at: https://github.com/cline/cline/actions/workflows/ext-vscode-publish-stable.yml (paste `v{VERSION}` as the tag)
|
||||
2. Post the Slack message to announce the hotfix
|
||||
|
||||
## Important Notes
|
||||
|
||||
- This workflow does NOT create a release branch - only tags
|
||||
- The release notes commit goes to main first, then gets cherry-picked to the tag
|
||||
- This keeps main's history accurate while allowing hotfix releases from tags
|
||||
- If cherry-pick conflicts occur, resolve them before continuing
|
||||
@@ -347,6 +347,8 @@ A few notes:
|
||||
So until the settings page is update, and this is added to settings in a way that's clean and doesn't confuse new users, I don't think we can merge this. Please bear with us.
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Also, don't forget to add a changeset since this fixes a user-facing bug.
|
||||
|
||||
The architectural change is solid - moving the focus logic to the command handlers makes sense. Just don't want to introduce subtle timing issues by removing those timeouts.
|
||||
</request_changes_comment>
|
||||
</example_comments_that_i_have_written_before>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Release
|
||||
|
||||
Prepare and publish a release directly from `main`.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow helps you:
|
||||
1. Select/confirm the target version
|
||||
2. Curate `CHANGELOG.md` entries manually for end users
|
||||
3. Ensure `package.json` version matches the changelog
|
||||
4. Create and push a release commit + tag
|
||||
5. Trigger publish workflow
|
||||
6. Update GitHub release notes and share a summary
|
||||
|
||||
## Process
|
||||
|
||||
### 1) Sync and determine version
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
cat package.json | grep '"version"'
|
||||
```
|
||||
|
||||
Confirm the release version with the maintainer (patch/minor/major).
|
||||
|
||||
### 2) Curate changelog and version
|
||||
|
||||
- Edit `CHANGELOG.md` for the target version using human-friendly release notes.
|
||||
- Ensure version headers use bracket format, e.g. `## [3.66.1]`.
|
||||
- Update `package.json` version to the same value.
|
||||
|
||||
### 3) Commit and tag
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md package.json package-lock.json
|
||||
git commit -m "v<version> Release Notes"
|
||||
git push origin main
|
||||
git tag v<version>
|
||||
git push origin v<version>
|
||||
```
|
||||
|
||||
### 4) Trigger publish workflow
|
||||
|
||||
Tell the maintainer to run:
|
||||
https://github.com/cline/cline/actions/workflows/ext-vscode-publish-stable.yml
|
||||
|
||||
Use `v<version>` as the release tag.
|
||||
|
||||
### 5) Update GitHub release notes
|
||||
|
||||
After publish completes:
|
||||
|
||||
```bash
|
||||
gh release view v<version> --json body --jq '.body'
|
||||
gh release edit v<version> --notes "<final curated release notes>"
|
||||
```
|
||||
|
||||
### 6) Final summary
|
||||
|
||||
Provide:
|
||||
- Released version/tag
|
||||
- Link to release page
|
||||
- Summary of top end-user changes
|
||||
@@ -1,50 +0,0 @@
|
||||
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
|
||||
version = 1
|
||||
name = "cline"
|
||||
|
||||
[setup]
|
||||
script = '''
|
||||
if [ ! -d "node_modules" ]; then
|
||||
MAIN_WORKTREE="$(git worktree list | head -n1 | awk '{print $1}')"
|
||||
ln -s "$MAIN_WORKTREE/node_modules" node_modules
|
||||
ln -s "$MAIN_WORKTREE/webview-ui/node_modules" webview-ui/node_modules
|
||||
fi
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "VS Code"
|
||||
icon = "run"
|
||||
command = "chmod +x ./scripts/run-extension-host.sh && ./scripts/run-extension-host.sh production"
|
||||
|
||||
[[actions]]
|
||||
name = "CLI"
|
||||
icon = "run"
|
||||
command = '''
|
||||
cd sdk
|
||||
bun install
|
||||
bun run cli
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "npm install"
|
||||
icon = "tool"
|
||||
command = '''
|
||||
rm node_modules
|
||||
rm webview-ui/node_modules
|
||||
npm run install:all
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "pull main"
|
||||
icon = "tool"
|
||||
command = '''
|
||||
git fetch origin main
|
||||
|
||||
if ! git merge-base --is-ancestor main origin/main; then
|
||||
echo "Local main has commits not on origin/main. Aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git update-ref refs/heads/main refs/remotes/origin/main
|
||||
echo "main updated to $(git rev-parse --short main)"
|
||||
'''
|
||||
+3
-2
@@ -1,2 +1,3 @@
|
||||
/.github/ @saoudrizwan @arafatkatze @maxpaulus43 @dominiccooney
|
||||
/README.md @saoudrizwan @juanpflores
|
||||
/docs/
|
||||
/.github/ @saoudrizwan @dcbartlett
|
||||
/README.md @saoudrizwan @nickbaumann98
|
||||
@@ -1,76 +1,64 @@
|
||||
name: 🐛 Bug Report
|
||||
description: File a bug report
|
||||
labels: ['bug']
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Important:** All bug reports must be reproducible using Claude Sonnet 4.5. Cline uses complex prompts so less capable models may not work as expected.
|
||||
- type: dropdown
|
||||
id: plugin-type
|
||||
attributes:
|
||||
label: Plugin Type
|
||||
description: Which plugin are you reporting a bug for?
|
||||
options:
|
||||
- VSCode Extension
|
||||
- JetBrains Plugin
|
||||
- CLI
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: cline-version
|
||||
attributes:
|
||||
label: Cline Version
|
||||
description: What version of Cline are you using? (You can find this at the bottom of the Settings view)
|
||||
placeholder: 'e.g., 1.2.3'
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: beta
|
||||
attributes:
|
||||
label: Beta version
|
||||
options:
|
||||
- label: I am using a beta version of Cline
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: How do you trigger this bug? Please walk us through it step by step.
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: provider-model
|
||||
attributes:
|
||||
label: Provider/Model
|
||||
description: What provider and model were you using when the issue occurred?
|
||||
placeholder: 'e.g., cline:anthropic/claude-sonnet-4.5, gemini:gemini-2.5-pro-exp-03-25'
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Information
|
||||
description: What operating system and hardware are you using?
|
||||
placeholder: |
|
||||
Operating System: Windows 11, macOS Sonoma, Ubuntu 22.04, etc.
|
||||
Hardware: CPU, GPU, RAM specifications if relevant
|
||||
e.g.,
|
||||
OS: Windows 11
|
||||
CPU: Intel Core i7-11700K
|
||||
GPU: NVIDIA GeForce RTX 3070
|
||||
RAM: 32GB DDR4
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Important:** All bug reports must be reproducible using Claude 4 Sonnet. Cline uses complex prompts so less capable models may not work as expected.
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: How do you trigger this bug? Please walk us through it step by step.
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant API REQUEST output
|
||||
description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
- type: input
|
||||
id: provider-model
|
||||
attributes:
|
||||
label: Provider/Model
|
||||
description: What provider and model were you using when the issue occurred?
|
||||
placeholder: "e.g., cline:anthropic/claude-3.7-sonnet, gemini:gemini-2.5-pro-exp-03-25"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System Information
|
||||
description: What operating system and hardware are you using?
|
||||
placeholder: |
|
||||
Operating System: Windows 11, macOS Sonoma, Ubuntu 22.04, etc.
|
||||
Hardware: CPU, GPU, RAM specifications if relevant
|
||||
e.g.,
|
||||
OS: Windows 11
|
||||
CPU: Intel Core i7-11700K
|
||||
GPU: NVIDIA GeForce RTX 3070
|
||||
RAM: 32GB DDR4
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: cline-version
|
||||
attributes:
|
||||
label: Cline Version
|
||||
description: What version of Cline are you using? (You can find this at the bottom of the Settings view)
|
||||
placeholder: "e.g., 1.2.3"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# Copilot Instructions for Cline
|
||||
|
||||
This is a VS Code extension. Read `.clinerules/general.md` for tribal knowledge and nuanced patterns.
|
||||
|
||||
## Architecture
|
||||
- **Core** (`src/`): `extension.ts` → `WebviewProvider` → `Controller` (single source of truth) → `Task` (agent loop).
|
||||
- **Webview** (`webview-ui/`): React/Vite app. State via `ExtensionStateContext.tsx`, synced through message passing.
|
||||
- **Communication**: Protobuf-defined gRPC-like protocol over VS Code message passing. Schemas in `proto/`.
|
||||
- **MCP**: `src/services/mcp/McpHub.ts`.
|
||||
|
||||
## Build & Test (Critical — non-obvious commands)
|
||||
- **Build**: `npm run compile` — NOT `npm run build`.
|
||||
- **Watch**: `npm run watch` (extension + webview).
|
||||
- **Protos**: `npm run protos` — run **immediately** after any `.proto` change. Generates into `src/shared/proto/`, `src/generated/`.
|
||||
- **Tests**: `npm run test:unit`. After prompt/tool changes: `UPDATE_SNAPSHOTS=true npm run test:unit`.
|
||||
|
||||
## Protobuf RPC Workflow (4 steps)
|
||||
1. **Define** in `proto/cline/*.proto`. Naming: `PascalCaseService`, `camelCase` RPCs, `PascalCase` Messages. Use `common.proto` shared types for simple data.
|
||||
2. **Generate**: `npm run protos`.
|
||||
3. **Backend handler**: `src/core/controller/<domain>/`.
|
||||
4. **Frontend call**: `UiServiceClient.myMethod(Request.create({...}))`.
|
||||
- Adding enums (e.g. `ClineSay`) → also update `src/shared/proto-conversions/cline-message.ts`.
|
||||
|
||||
## Adding API Providers (silent failure risk)
|
||||
Three proto conversion updates are **required** or the provider silently resets to Anthropic:
|
||||
1. `proto/cline/models.proto` — add to `ApiProvider` enum.
|
||||
2. `convertApiProviderToProto()` in `src/shared/proto-conversions/models/api-configuration-conversion.ts`.
|
||||
3. `convertProtoToApiProvider()` in the same file.
|
||||
|
||||
Also update: `src/shared/api.ts`, `src/shared/providers/providers.json`, `src/core/api/index.ts`, `webview-ui/.../providerUtils.ts`, `webview-ui/.../validate.ts`, `webview-ui/.../ApiOptions.tsx`.
|
||||
|
||||
For Responses API providers: add to `isNextGenModelProvider()` in `src/utils/model-utils.ts` and set `apiFormat: ApiFormat.OPENAI_RESPONSES` on models.
|
||||
|
||||
## Adding Tools to System Prompt (5+ file chain)
|
||||
1. Add enum to `ClineDefaultTool` in `src/shared/tools.ts`.
|
||||
2. Create definition in `src/core/prompts/system-prompt/tools/` (export `[GENERIC]` minimum).
|
||||
3. Register in `src/core/prompts/system-prompt/tools/init.ts`.
|
||||
4. Whitelist in `src/core/prompts/system-prompt/variants/*/config.ts` for each model family.
|
||||
5. Handler in `src/core/task/tools/handlers/`, wire in `ToolExecutor.ts`.
|
||||
6. If tool has UI: add `ClineSay` enum in proto → `ExtensionMessage.ts` → `cline-message.ts` → `ChatRow.tsx`.
|
||||
7. Regenerate snapshots: `UPDATE_SNAPSHOTS=true npm run test:unit`.
|
||||
|
||||
## Modifying System Prompt
|
||||
Modular: `components/` (shared) + `variants/` (model-specific) + `templates/` (`{{PLACEHOLDER}}`). Variants override components via `componentOverrides` in `config.ts` or custom `template.ts`. XS variant is heavily condensed inline. Always regenerate snapshots after changes.
|
||||
|
||||
## Global State Keys (silent failure risk)
|
||||
Adding a key requires: type in `src/shared/storage/state-keys.ts`, read via `context.globalState.get()` in `src/core/storage/utils/state-helpers.ts` `readGlobalStateFromDisk()`, and add to return object. Missing the `.get()` call compiles fine but value is always `undefined`.
|
||||
|
||||
## Slash Commands (3 places)
|
||||
- `src/core/slash-commands/index.ts` — definitions.
|
||||
- `src/core/prompts/commands.ts` — system prompt integration.
|
||||
- `webview-ui/src/utils/slash-commands.ts` — webview autocomplete.
|
||||
|
||||
## Conventions
|
||||
- **Paths**: Always use `src/utils/path` helpers (`toPosixString`) for cross-platform compatibility.
|
||||
- **Logging**: `src/shared/services/Logger.ts`.
|
||||
- **Feature flags**: See PR #7566 as reference pattern.
|
||||
@@ -2,7 +2,7 @@ version: 2
|
||||
updates:
|
||||
# Main extension dependencies
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/vscode"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
# Group all updates into a single PR
|
||||
@@ -20,7 +20,7 @@ updates:
|
||||
|
||||
# Webview UI dependencies
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/apps/vscode/webview-ui"
|
||||
directory: "/webview-ui"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
groups:
|
||||
|
||||
@@ -60,6 +60,7 @@ We're not looking for exhaustive documentation - just evidence that you've thoug
|
||||
|
||||
- [ ] Changes are limited to a single feature, bugfix or chore (split larger changes into separate PRs)
|
||||
- [ ] Tests are passing (`npm test`) and code is formatted and linted (`npm run format && npm run lint`)
|
||||
- [ ] I have created a changeset using `npm run changeset` (required for user-facing changes)
|
||||
- [ ] I have reviewed [contributor guidelines](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)
|
||||
|
||||
### Screenshots
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
This script updates a specific version's release notes section in CHANGELOG.md with new content
|
||||
or reformats existing content.
|
||||
|
||||
The script:
|
||||
1. Takes a version number, changelog path, and optionally new content as input from environment variables
|
||||
2. Finds the section in the changelog for the specified version
|
||||
3. Either:
|
||||
a) Replaces the content with new content if provided, or
|
||||
b) Reformats existing content by:
|
||||
- Removing the first two lines of the changeset format
|
||||
- Ensuring version numbers are wrapped in square brackets
|
||||
4. Writes the updated changelog back to the file
|
||||
|
||||
Environment Variables:
|
||||
CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md')
|
||||
VERSION: The version number to update/format
|
||||
PREV_VERSION: The previous version number (used to locate section boundaries)
|
||||
NEW_CONTENT: Optional new content to insert for this version
|
||||
"""
|
||||
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
|
||||
CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md")
|
||||
VERSION = os.environ['VERSION']
|
||||
PREV_VERSION = os.environ.get("PREV_VERSION", "")
|
||||
NEW_CONTENT = os.environ.get("NEW_CONTENT", "")
|
||||
|
||||
def overwrite_changelog_section(changelog_text: str, new_content: str):
|
||||
# Find the section for the specified version
|
||||
version_pattern = f"## {VERSION}\n"
|
||||
unformmatted_prev_version_pattern = f"## {PREV_VERSION}\n"
|
||||
prev_version_pattern = f"## [{PREV_VERSION}]\n"
|
||||
print(f"latest version: {VERSION}")
|
||||
print(f"prev_version: {PREV_VERSION}")
|
||||
|
||||
notes_start_index = changelog_text.find(version_pattern) + len(version_pattern)
|
||||
notes_end_index = changelog_text.find(prev_version_pattern, notes_start_index) if PREV_VERSION and (prev_version_pattern in changelog_text or unformmatted_prev_version_pattern in changelog_text) else len(changelog_text)
|
||||
|
||||
if new_content:
|
||||
return changelog_text[:notes_start_index] + f"{new_content}\n" + changelog_text[notes_end_index:]
|
||||
else:
|
||||
changeset_lines = changelog_text[notes_start_index:notes_end_index].split("\n")
|
||||
filtered_lines = []
|
||||
for line in changeset_lines:
|
||||
# If the previous line is a changeset format
|
||||
if len(filtered_lines) > 1 and filtered_lines[-1].startswith("### "):
|
||||
# Remove the last two lines from the filted_lines
|
||||
filtered_lines.pop()
|
||||
filtered_lines.pop()
|
||||
else:
|
||||
filtered_lines.append(line.strip())
|
||||
|
||||
# Prepend a new line to the first line of filtered_lines
|
||||
if filtered_lines:
|
||||
filtered_lines[0] = "\n" + filtered_lines[0]
|
||||
|
||||
# Print filted_lines wiht a "\n" at the end of each line
|
||||
for line in filtered_lines:
|
||||
print(line.strip())
|
||||
|
||||
parsed_lines = "\n".join(line for line in filtered_lines)
|
||||
updated_changelog = changelog_text[:notes_start_index] + parsed_lines + changelog_text[notes_end_index:]
|
||||
return updated_changelog
|
||||
|
||||
with open(CHANGELOG_PATH, 'r') as f:
|
||||
changelog_content = f.read()
|
||||
|
||||
new_changelog = overwrite_changelog_section(changelog_content, NEW_CONTENT)
|
||||
# print("----------------------------------------------------------------------------------")
|
||||
# print(new_changelog)
|
||||
# print("----------------------------------------------------------------------------------")
|
||||
# Write back to CHANGELOG.md
|
||||
with open(CHANGELOG_PATH, 'w') as f:
|
||||
f.write(new_changelog)
|
||||
|
||||
print(f"{CHANGELOG_PATH} updated successfully!")
|
||||
@@ -0,0 +1,113 @@
|
||||
name: Changeset Converter
|
||||
run-name: Changeset Conversion
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
REPO_PATH: ${{ github.repository }}
|
||||
GIT_REF: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || 'main' }}
|
||||
NODE_VERSION: 20.18.1
|
||||
|
||||
jobs:
|
||||
# Job 1: Create version bump PR when changesets are merged to main
|
||||
changeset-pr-version-bump:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.merged == true &&
|
||||
github.event.pull_request.base.ref == 'main' &&
|
||||
github.actor != 'github-actions'
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check user for team affiliation
|
||||
id: team_check
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: morfien101/actions-authorized-user@4a3cfbf0bcb3cafe4a71710a278920c5d94bb38b
|
||||
with:
|
||||
username: ${{ github.actor }}
|
||||
org: ${{ github.repository_owner }}
|
||||
team: "deployer"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check if user is authorized
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
if [ "${{ steps.team_check.outputs.authorized }}" != "true" ]; then
|
||||
echo "User is not authorized to run this workflow."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Git Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ env.GIT_REF }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install changeset
|
||||
|
||||
# Check if there are any new changesets to process
|
||||
- name: Check for changesets
|
||||
id: check-changesets
|
||||
run: |
|
||||
NEW_CHANGESETS=$(find .changeset -name "*.md" ! -name "README.md" | wc -l | tr -d ' ')
|
||||
echo "Changesets diff with previous version: $NEW_CHANGESETS"
|
||||
echo "new_changesets=$NEW_CHANGESETS" >> $GITHUB_OUTPUT
|
||||
|
||||
# Create version bump PR using changesets/action if there are new changesets
|
||||
- name: Create Changeset Pull Request
|
||||
if: steps.check-changesets.outputs.new_changesets != '0'
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
commit: "changeset version bump"
|
||||
title: "Changeset version bump"
|
||||
version: npm run version-packages # This performs the changeset version bump
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Get current and previous versions to edit changelog entry
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(git show HEAD:package.json | jq -r '.version')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
PREV_VERSION=$(git show origin/main:package.json | jq -r '.version')
|
||||
echo "prev_version=$PREV_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version=$VERSION"
|
||||
echo "prev_version=$PREV_VERSION"
|
||||
|
||||
# Update CHANGELOG.md with proper format
|
||||
- name: Update Changelog Format
|
||||
env:
|
||||
VERSION: ${{ steps.get_version.outputs.version }}
|
||||
PREV_VERSION: ${{ steps.get_version.outputs.prev_version }}
|
||||
run: python .github/scripts/overwrite_changeset_changelog.py
|
||||
|
||||
# Commit and push changelog updates
|
||||
- name: Push Changelog updates to Pull Request
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email github-actions@github.com
|
||||
echo "Running git add and commit..."
|
||||
git add CHANGELOG.md
|
||||
git commit -m "Updating CHANGELOG.md format"
|
||||
git status
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
echo "Pushing to remote..."
|
||||
echo "--------------------------------------------------------------------------------"
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
git push origin $CURRENT_BRANCH
|
||||
@@ -1,435 +0,0 @@
|
||||
name: cli-publish
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 12 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish_target:
|
||||
description: "Which publish flow to run"
|
||||
required: true
|
||||
default: "main"
|
||||
type: choice
|
||||
options:
|
||||
- main
|
||||
- nightly
|
||||
git_tag:
|
||||
description: "Existing release tag to publish when publish_target=main, for example cli-v0.1.0"
|
||||
required: false
|
||||
type: string
|
||||
confirm_publish:
|
||||
description: 'Required when publish_target=main. Type "publish" to confirm release publish.'
|
||||
required: false
|
||||
type: string
|
||||
force_nightly_publish:
|
||||
description: "Force nightly publish even with no commits in last 24h"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: .
|
||||
|
||||
jobs:
|
||||
publish-main:
|
||||
name: Publish cline
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
if: |
|
||||
github.repository == 'cline/cline' &&
|
||||
github.ref == 'refs/heads/main' &&
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.publish_target == 'main' &&
|
||||
github.event.inputs.confirm_publish == 'publish' &&
|
||||
!endsWith(github.actor, '[bot]')
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.git_tag }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Verify publish tooling
|
||||
run: |
|
||||
NPM_VERSION=$(npm --version)
|
||||
echo "npm ${NPM_VERSION}"
|
||||
IFS=. read -r major minor patch <<EOF
|
||||
${NPM_VERSION}
|
||||
EOF
|
||||
if [ "$major" -lt 11 ] || { [ "$major" -eq 11 ] && [ "$minor" -lt 5 ]; } || { [ "$major" -eq 11 ] && [ "$minor" -eq 5 ] && [ "$patch" -lt 1 ]; }; then
|
||||
echo "npm 11.5.1 or newer is required for trusted publishing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Validate release tag
|
||||
id: version
|
||||
env:
|
||||
TAG: ${{ github.event.inputs.git_tag }}
|
||||
run: |
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "git_tag is required when publish_target=main"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf "%s\n" "$TAG" | grep -Eq '^cli-v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
||||
echo "git_tag must look like cli-vX.Y.Z, got: ${TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION="${TAG#cli-v}"
|
||||
PACKAGE_VERSION=$(node -p "require('./apps/cli/package.json').version")
|
||||
|
||||
if [ "$PACKAGE_VERSION" != "$VERSION" ]; then
|
||||
echo "apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! printf "%s\n" "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
||||
echo "apps/cli/package.json has invalid version: ${VERSION}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_COMMIT=$(git rev-parse "${TAG}^{commit}")
|
||||
HEAD_COMMIT=$(git rev-parse HEAD)
|
||||
if [ "$TAG_COMMIT" != "$HEAD_COMMIT" ]; then
|
||||
echo "${TAG} does not point at the checked out commit"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch origin +main:refs/remotes/origin/main
|
||||
if ! git merge-base --is-ancestor "$HEAD_COMMIT" origin/main; then
|
||||
echo "${TAG} is not reachable from origin/main"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build SDK packages
|
||||
run: bun run build:sdk
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
|
||||
- name: Run tests
|
||||
run: bun run test
|
||||
|
||||
- name: Build platform binaries
|
||||
run: bun script/build.ts --install-native-variants --skip-sdk-build
|
||||
working-directory: apps/cli
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
|
||||
- name: Verify build output
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
EXPECTED=(
|
||||
"@cline/cli-darwin-arm64"
|
||||
"@cline/cli-darwin-x64"
|
||||
"@cline/cli-linux-arm64"
|
||||
"@cline/cli-linux-x64"
|
||||
"@cline/cli-windows-arm64"
|
||||
"@cline/cli-windows-x64"
|
||||
)
|
||||
|
||||
for package_name in "${EXPECTED[@]}"; do
|
||||
dir="apps/cli/dist/${package_name#@cline/}"
|
||||
if [ ! -f "$dir/package.json" ]; then
|
||||
echo "Missing package manifest: $dir/package.json"
|
||||
exit 1
|
||||
fi
|
||||
actual_name=$(node -p "require('./$dir/package.json').name")
|
||||
actual_version=$(node -p "require('./$dir/package.json').version")
|
||||
if [ "$actual_name" != "$package_name" ]; then
|
||||
echo "Expected $package_name, got $actual_name"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$actual_version" != "$VERSION" ]; then
|
||||
echo "Expected $package_name@$VERSION, got $actual_version"
|
||||
exit 1
|
||||
fi
|
||||
ls -lh "$dir/bin/"
|
||||
done
|
||||
|
||||
- name: Publish to NPM with latest tag
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
run: bun script/publish-npm.ts --tag latest
|
||||
working-directory: apps/cli
|
||||
|
||||
- name: Get Previous CLI Tag
|
||||
id: prev_tag
|
||||
env:
|
||||
CURRENT_TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 --match 'cli-v*' "$CURRENT_TAG^" 2>/dev/null || echo "")
|
||||
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get Changelog Entry
|
||||
id: changelog
|
||||
run: |
|
||||
# Grab content between the first "## " header and the next one in apps/cli/CHANGELOG.md
|
||||
CONTENT=$(awk '/^## [0-9]/{if(found) exit; found=1; next} found{print}' apps/cli/CHANGELOG.md)
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CONTENT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "CLI v${{ steps.version.outputs.version }}"
|
||||
body: |
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
${{ steps.prev_tag.outputs.prev_tag != '' && format('**Full Changelog**: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Summary
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Published cline@${VERSION} to npm with dist-tag 'latest'"
|
||||
echo "Install with: npm install -g cline"
|
||||
|
||||
- name: Post release to Slack
|
||||
uses: slackapi/slack-github-action@v3.0.1
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: "C0APVKGGZFC"
|
||||
text: "Cline CLI v${{ steps.version.outputs.version }}"
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "Cline CLI v${{ steps.version.outputs.version }}"
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: ${{ toJSON(steps.changelog.outputs.content) }}
|
||||
- type: "context"
|
||||
elements:
|
||||
- type: "mrkdwn"
|
||||
text: "<https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}|View on npm>${{ steps.prev_tag.outputs.prev_tag != '' && format(' | Full Changelog: https://github.com/{0}/compare/{1}...{2}', github.repository, steps.prev_tag.outputs.prev_tag, steps.version.outputs.tag) || '' }}"
|
||||
|
||||
publish-nightly:
|
||||
name: Publish cline nightly
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
if: |
|
||||
github.repository == 'cline/cline' &&
|
||||
github.ref == 'refs/heads/main' &&
|
||||
(
|
||||
github.event_name == 'schedule' ||
|
||||
(
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.publish_target == 'nightly'
|
||||
)
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for recent commits
|
||||
id: check_commits
|
||||
env:
|
||||
FORCE_PUBLISH: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_nightly_publish == 'true' }}
|
||||
run: |
|
||||
if [ "$FORCE_PUBLISH" = "true" ]; then
|
||||
echo "force_nightly_publish enabled, proceeding with publish"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$(git rev-list --count HEAD --since='24 hours ago')" -eq 0 ]; then
|
||||
echo "No commits in last 24 hours, skipping publish"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Found recent commits, proceeding with publish"
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Setup Bun
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Verify publish tooling
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
NPM_VERSION=$(npm --version)
|
||||
echo "npm ${NPM_VERSION}"
|
||||
IFS=. read -r major minor patch <<EOF
|
||||
${NPM_VERSION}
|
||||
EOF
|
||||
if [ "$major" -lt 11 ] || { [ "$major" -eq 11 ] && [ "$minor" -lt 5 ]; } || { [ "$major" -eq 11 ] && [ "$minor" -eq 5 ] && [ "$patch" -lt 1 ]; }; then
|
||||
echo "npm 11.5.1 or newer is required for trusted publishing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun install
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build SDK packages
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun run build:sdk
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
|
||||
- name: Run tests
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun run test
|
||||
|
||||
- name: Generate nightly version
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
id: version
|
||||
run: |
|
||||
BASE_VERSION=$(node -p "require('./apps/cli/package.json').version")
|
||||
TIMESTAMP=$(date +%s)
|
||||
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
|
||||
|
||||
echo "Base version: ${BASE_VERSION}"
|
||||
echo "Generated nightly version: ${VERSION}"
|
||||
echo "base_version=${BASE_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update nightly package version
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
node -e '
|
||||
const fs = require("node:fs");
|
||||
const path = "apps/cli/package.json";
|
||||
const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||
pkg.version = process.env.VERSION;
|
||||
fs.writeFileSync(path, `${JSON.stringify(pkg, null, "\t")}\n`);
|
||||
'
|
||||
cat apps/cli/package.json | grep '"version"'
|
||||
|
||||
- name: Build platform binaries
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun script/build.ts --install-native-variants --skip-sdk-build
|
||||
working-directory: apps/cli
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
|
||||
- name: Verify build output
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
EXPECTED=(
|
||||
"@cline/cli-darwin-arm64"
|
||||
"@cline/cli-darwin-x64"
|
||||
"@cline/cli-linux-arm64"
|
||||
"@cline/cli-linux-x64"
|
||||
"@cline/cli-windows-arm64"
|
||||
"@cline/cli-windows-x64"
|
||||
)
|
||||
|
||||
for package_name in "${EXPECTED[@]}"; do
|
||||
dir="apps/cli/dist/${package_name#@cline/}"
|
||||
if [ ! -f "$dir/package.json" ]; then
|
||||
echo "Missing package manifest: $dir/package.json"
|
||||
exit 1
|
||||
fi
|
||||
actual_name=$(node -p "require('./$dir/package.json').name")
|
||||
actual_version=$(node -p "require('./$dir/package.json').version")
|
||||
if [ "$actual_name" != "$package_name" ]; then
|
||||
echo "Expected $package_name, got $actual_name"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$actual_version" != "$VERSION" ]; then
|
||||
echo "Expected $package_name@$VERSION, got $actual_version"
|
||||
exit 1
|
||||
fi
|
||||
ls -lh "$dir/bin/"
|
||||
done
|
||||
|
||||
- name: Publish to NPM with nightly tag
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
run: bun script/publish-npm.ts --tag nightly
|
||||
working-directory: apps/cli
|
||||
|
||||
- name: Summary
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Published cline@${VERSION} to npm with dist-tag 'nightly'"
|
||||
echo "Install with: npm install -g cline@nightly"
|
||||
@@ -0,0 +1,108 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- id: set-matrix
|
||||
run: |
|
||||
echo 'matrix=[{"runner":"ubuntu"},{"runner":"windows"},{"runner":"macos"}]' >> $GITHUB_OUTPUT
|
||||
|
||||
e2e:
|
||||
needs: matrix_prep
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.matrix_prep.outputs.matrix) }}
|
||||
runs-on: ${{ matrix.runner }}-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
# Cache VS Code installation
|
||||
- name: Cache VS Code
|
||||
uses: actions/cache@v4
|
||||
id: vscode-cache
|
||||
with:
|
||||
path: .vscode-test
|
||||
key: vscode-${{ runner.os }}-stable-${{ hashFiles('.vscode-test.mjs', 'package.json') }}
|
||||
restore-keys: |
|
||||
vscode-${{ runner.os }}-stable-
|
||||
|
||||
# Cache Playwright browsers
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
~/Library/Caches/ms-playwright
|
||||
~/AppData/Local/ms-playwright
|
||||
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
playwright-browsers-${{ runner.os }}-
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
|
||||
- name: Install xvfb on Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
# Run optimized E2E tests (eliminates redundant builds)
|
||||
- name: Run E2E tests - Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: xvfb-run -a npm run test:e2e:optimal
|
||||
|
||||
- name: Run E2E tests - Non-Linux
|
||||
if: matrix.runner != 'ubuntu'
|
||||
run: npm run test:e2e:optimal
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: playwright-recordings-${{ matrix.runner }}
|
||||
path: |
|
||||
test-results/playwright/
|
||||
@@ -1,100 +0,0 @@
|
||||
name: ext-jb-test-integration
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
concurrency:
|
||||
group: jetbrains-trigger-${{ github.event.pull_request.number || github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
trigger-integration-test:
|
||||
name: Run Tests
|
||||
runs-on: ubuntu-latest
|
||||
# Auto-run only for trusted PR authors. Anyone else needs a maintainer
|
||||
# to opt their PR in by commenting /test-jetbrains.
|
||||
if: |
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.pull_request.author_association)) ||
|
||||
(github.event_name == 'issue_comment' &&
|
||||
github.event.issue.pull_request &&
|
||||
contains(github.event.comment.body, '/test-jetbrains') &&
|
||||
contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association))
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ vars.CLINE_JETBRAINS_APP_ID }}
|
||||
private-key: ${{ secrets.CLINE_JETBRAINS_APP_KEY }}
|
||||
owner: cline
|
||||
repositories: intellij-plugin
|
||||
|
||||
- name: Get PR details (for issue_comment trigger)
|
||||
id: pr-details
|
||||
if: github.event_name == 'issue_comment'
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
|
||||
echo "head_ref=$(echo "$PR_DATA" | jq -r '.head.ref')" >> $GITHUB_OUTPUT
|
||||
echo "head_sha=$(echo "$PR_DATA" | jq -r '.head.sha')" >> $GITHUB_OUTPUT
|
||||
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> $GITHUB_OUTPUT
|
||||
echo "html_url=$(echo "$PR_DATA" | jq -r '.html_url')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Sanitize untrusted inputs
|
||||
id: sanitize
|
||||
env:
|
||||
RAW_BRANCH_NAME: ${{ github.event_name == 'pull_request_target' && github.head_ref || steps.pr-details.outputs.head_ref }}
|
||||
RAW_PR_TITLE: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.title || steps.pr-details.outputs.title }}
|
||||
run: |
|
||||
# Sanitize branch name for JSON
|
||||
BRANCH_NAME_JSON=$(jq -n --arg b "$RAW_BRANCH_NAME" '$b')
|
||||
echo "branch_name=$BRANCH_NAME_JSON" >> $GITHUB_OUTPUT
|
||||
|
||||
# Sanitize PR title for JSON
|
||||
PR_TITLE_JSON=$(jq -n --arg t "$RAW_PR_TITLE" '$t')
|
||||
echo "pr_title=$PR_TITLE_JSON" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Trigger IntelliJ Plugin Integration Test
|
||||
env:
|
||||
BRANCH_NAME: ${{ steps.sanitize.outputs.branch_name }}
|
||||
PR_TITLE: ${{ steps.sanitize.outputs.pr_title }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
|
||||
PR_URL: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.html_url || steps.pr-details.outputs.html_url }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "User-Agent: cline-pr-trigger" \
|
||||
-H "Content-Type: application/json" \
|
||||
https://api.github.com/repos/cline/intellij-plugin/dispatches \
|
||||
-d @- <<EOF
|
||||
{
|
||||
"event_type": "cline-pr-check",
|
||||
"client_payload": {
|
||||
"pr_number": "$PR_NUMBER",
|
||||
"branch_name": $BRANCH_NAME,
|
||||
"action": "${{ github.event.action }}",
|
||||
"sha": "$PR_SHA",
|
||||
"pr_title": $PR_TITLE,
|
||||
"pr_url": "$PR_URL"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Log trigger details
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
|
||||
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
|
||||
run: |
|
||||
echo "Triggered IntelliJ Plugin integration test for:"
|
||||
echo " PR #$PR_NUMBER"
|
||||
echo " Trigger: ${{ github.event_name }}"
|
||||
echo " Action: ${{ github.event.action }}"
|
||||
echo " SHA: $PR_SHA"
|
||||
@@ -1,104 +0,0 @@
|
||||
name: ext-vscode-publish-nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
run-name: "Publish Nightly from ${{ github.ref_name }} @ ${{ github.sha }}"
|
||||
|
||||
# Prevent concurrent publish runs on the same branch. The nightly publish script
|
||||
# generates the extension version from a seconds-resolution timestamp, so parallel
|
||||
# runs on the same ref can collide on the same version and cause publish failures
|
||||
# or inconsistent tagging. Runs on different branches proceed independently.
|
||||
concurrency:
|
||||
group: ext-vscode-publish-nightly-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
if: github.repository == 'cline/cline' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dpc/sdk-migration-simpler-login')
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
uses: ./.github/workflows/ext-vscode-test.yml
|
||||
|
||||
publish:
|
||||
needs: test
|
||||
permissions:
|
||||
contents: write
|
||||
name: Publish Cline (Nightly) Extension
|
||||
if: github.repository == 'cline/cline' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dpc/sdk-migration-simpler-login')
|
||||
runs-on: ubuntu-latest
|
||||
environment: PublishNightly
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/vscode
|
||||
|
||||
steps:
|
||||
- name: Checkout selected branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
lfs: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Show build source
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
echo "Building ref: $GITHUB_REF"
|
||||
echo "Building sha: $GITHUB_SHA"
|
||||
git --no-pager log -1 --oneline
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
# Keep publish environment aligned with test workflow/tooling lockfile expectations.
|
||||
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
|
||||
node-version: 22
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode ci --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui ci --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
- name: Publish Nightly Extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
# OpenTelemetry production defaults (can be overridden at runtime)
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
run: npm run publish:marketplace:nightly
|
||||
|
||||
- name: Tag published commit
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
SAFE_REF=$(echo "$GITHUB_REF_NAME" | tr '/[:upper:]' '-[:lower:]' | tr -cd 'a-z0-9._-')
|
||||
SHORT_SHA=$(git rev-parse --short=12 HEAD)
|
||||
TIMESTAMP=$(date -u +"%Y%m%d%H%M%S")
|
||||
TAG="nightly-${SAFE_REF}-${TIMESTAMP}-${SHORT_SHA}"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "$TAG" -m "Cline Nightly published from ${GITHUB_REF_NAME} at ${GITHUB_SHA}"
|
||||
# Use an explicit HTTPS remote with GH_TOKEN because checkout was run with
|
||||
# persist-credentials: false, so actions/checkout did not persist a git credential helper.
|
||||
git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "refs/tags/${TAG}"
|
||||
|
||||
echo "Tagged published commit: $TAG"
|
||||
@@ -1,229 +0,0 @@
|
||||
name: ext-vscode-publish-stable
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-type:
|
||||
description: "Choose release type (release or pre-release)"
|
||||
required: true
|
||||
default: "release"
|
||||
type: choice
|
||||
options:
|
||||
- pre-release
|
||||
- release
|
||||
auto_create_tag_from_main:
|
||||
description: "Auto-create and push the provided tag from the tested main commit (recommended)"
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
tag:
|
||||
description: "Tag to publish (required in both modes, e.g., v3.1.2)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/ext-vscode-test.yml
|
||||
|
||||
publish:
|
||||
needs: test
|
||||
name: Publish Extension
|
||||
runs-on: ubuntu-latest
|
||||
environment: publish
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/vscode
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
lfs: true
|
||||
|
||||
- name: Resolve Release Tag
|
||||
id: resolve_tag
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
TAG: ${{ github.event.inputs.tag }}
|
||||
AUTO_CREATE: ${{ github.event.inputs.auto_create_tag_from_main }}
|
||||
run: |
|
||||
TESTED_SHA="${{ github.sha }}"
|
||||
WORKFLOW_REF="${{ github.ref }}"
|
||||
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "Error: tag input is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$ ]]; then
|
||||
echo "Error: tag must match vX.Y.Z (optionally with -suffix or .suffix)"
|
||||
exit 1
|
||||
fi
|
||||
TAG_REF="refs/tags/$TAG"
|
||||
|
||||
git fetch origin main --tags
|
||||
|
||||
if [[ "$AUTO_CREATE" == "true" ]]; then
|
||||
if [[ "$WORKFLOW_REF" != "refs/heads/main" ]]; then
|
||||
echo "Error: auto-create mode requires dispatching from main (current ref: $WORKFLOW_REF)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Auto-create enabled. Using tested workflow SHA: $TESTED_SHA"
|
||||
|
||||
if ! git merge-base --is-ancestor "$TESTED_SHA" origin/main; then
|
||||
echo "Error: tested SHA $TESTED_SHA is not on origin/main"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if git show-ref --verify --quiet "$TAG_REF"; then
|
||||
TAG_SHA=$(git rev-list -n 1 "$TAG_REF^{commit}")
|
||||
if [[ "$TAG_SHA" != "$TESTED_SHA" ]]; then
|
||||
echo "Error: tag '$TAG' already exists at $TAG_SHA, not at tested SHA ($TESTED_SHA)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag '$TAG' already exists at tested SHA. Continuing."
|
||||
else
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag "$TAG" "$TESTED_SHA"
|
||||
git push origin "$TAG_REF"
|
||||
echo "Created and pushed tag '$TAG' from tested SHA $TESTED_SHA."
|
||||
fi
|
||||
else
|
||||
if ! git show-ref --verify --quiet "$TAG_REF"; then
|
||||
echo "Error: tag '$TAG' does not exist in the repository"
|
||||
exit 1
|
||||
fi
|
||||
echo "Using existing tag '$TAG'."
|
||||
fi
|
||||
|
||||
git checkout --detach "$TAG_REF^{commit}"
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "resolved_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode install --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui install --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Verify Tag Matches Package Version
|
||||
run: |
|
||||
TAG="${{ steps.resolve_tag.outputs.tag }}"
|
||||
VERSION="v${{ steps.get_version.outputs.version }}"
|
||||
if [[ "$TAG" != "$VERSION" ]]; then
|
||||
echo "Error: tag '$TAG' does not match package version '$VERSION'"
|
||||
exit 1
|
||||
fi
|
||||
echo "Tag and package version match: $TAG"
|
||||
|
||||
- name: Package and Publish Extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
# OpenTelemetry production defaults (can be overridden at runtime)
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
|
||||
run: |
|
||||
# Swap README.marketplace.md into README.md so both the GitHub
|
||||
# release artifact (vsce package below) and the marketplace
|
||||
# publish (npm run publish:marketplace below, which swaps
|
||||
# internally as an idempotent no-op) ship the same README.
|
||||
node scripts/marketplace-readme.mjs swap-in
|
||||
trap 'node scripts/marketplace-readme.mjs restore' EXIT
|
||||
|
||||
# Required to generate the .vsix
|
||||
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
if [ "$RELEASE_TYPE" = "pre-release" ]; then
|
||||
npm run publish:marketplace:prerelease
|
||||
echo "Successfully published pre-release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
else
|
||||
npm run publish:marketplace
|
||||
echo "Successfully published release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
fi
|
||||
|
||||
- name: Get Previous Tag
|
||||
id: prev_tag
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
CURRENT_TAG="${{ steps.resolve_tag.outputs.tag }}"
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG^" 2>/dev/null || echo "")
|
||||
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get Changelog Entry
|
||||
id: changelog
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
# Get content between first ## [ and second ## [
|
||||
CONTENT=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CONTENT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.resolve_tag.outputs.tag }}
|
||||
files: "apps/vscode/*.vsix"
|
||||
body: |
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.resolve_tag.outputs.tag }}
|
||||
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Post release to Slack
|
||||
uses: slackapi/slack-github-action@v3.0.1
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: "C0APVKGGZFC"
|
||||
text: "Cline ${{ steps.resolve_tag.outputs.tag }}"
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Cline ${{ steps.resolve_tag.outputs.tag }}*"
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: ${{ toJSON(steps.changelog.outputs.content) }}
|
||||
- type: "context"
|
||||
elements:
|
||||
- type: "mrkdwn"
|
||||
text: "Full Changelog: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.resolve_tag.outputs.tag }}"
|
||||
@@ -1,162 +0,0 @@
|
||||
name: ext-vscode-test-e2e
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
name: Detect Changes
|
||||
outputs:
|
||||
e2e: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.e2e == 'true' }}
|
||||
steps:
|
||||
- id: force
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
run: echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: steps.force.outputs.run_all != 'true'
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
if: steps.force.outputs.run_all != 'true'
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
e2e:
|
||||
- 'apps/vscode/src/**'
|
||||
- 'apps/vscode/webview-ui/**'
|
||||
- 'apps/vscode/proto/**'
|
||||
- 'apps/vscode/tests/**'
|
||||
- 'apps/vscode/scripts/**'
|
||||
- 'apps/vscode/standalone/**'
|
||||
- 'apps/vscode/assets/**'
|
||||
- 'apps/vscode/walkthrough/**'
|
||||
- 'apps/vscode/package.json'
|
||||
- 'apps/vscode/package-lock.json'
|
||||
- 'apps/vscode/buf.yaml'
|
||||
- 'apps/vscode/tsconfig*.json'
|
||||
- 'apps/vscode/biome.jsonc'
|
||||
- 'apps/vscode/esbuild.mjs'
|
||||
- 'apps/vscode/.mocharc.json'
|
||||
- 'apps/vscode/.vscode-test.mjs'
|
||||
- 'apps/vscode/.vscodeignore'
|
||||
- 'apps/vscode/playwright*.ts'
|
||||
- '.github/workflows/ext-vscode-test-e2e.yml'
|
||||
|
||||
matrix_prep:
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.e2e == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- id: set-matrix
|
||||
run: |
|
||||
echo 'matrix=[{"runner":"ubuntu"},{"runner":"windows"},{"runner":"macos"}]' >> $GITHUB_OUTPUT
|
||||
|
||||
e2e:
|
||||
needs: [detect-changes, matrix_prep]
|
||||
if: needs.detect-changes.outputs.e2e == 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJson(needs.matrix_prep.outputs.matrix) }}
|
||||
runs-on: ${{ matrix.runner }}-latest
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/vscode
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: apps/vscode/node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('apps/vscode/package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: apps/vscode/webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('apps/vscode/webview-ui/package-lock.json') }}
|
||||
|
||||
# Cache VS Code installation
|
||||
- name: Cache VS Code
|
||||
uses: actions/cache@v4
|
||||
id: vscode-cache
|
||||
with:
|
||||
path: apps/vscode/.vscode-test
|
||||
key: vscode-${{ runner.os }}-stable-${{ hashFiles('apps/vscode/.vscode-test.mjs', 'apps/vscode/package.json') }}
|
||||
restore-keys: |
|
||||
vscode-${{ runner.os }}-stable-
|
||||
|
||||
# Cache Playwright browsers
|
||||
- name: Cache Playwright browsers
|
||||
uses: actions/cache@v4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
~/Library/Caches/ms-playwright
|
||||
~/AppData/Local/ms-playwright
|
||||
key: playwright-browsers-${{ runner.os }}-${{ hashFiles('apps/vscode/package-lock.json') }}
|
||||
restore-keys: |
|
||||
playwright-browsers-${{ runner.os }}-
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui ci
|
||||
|
||||
- name: Install vsce
|
||||
run: npm install -g @vscode/vsce
|
||||
|
||||
- name: Install xvfb on Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
# Run optimized E2E tests (eliminates redundant builds)
|
||||
- name: Run E2E tests - Linux
|
||||
if: matrix.runner == 'ubuntu'
|
||||
run: xvfb-run -a npm run test:e2e:optimal
|
||||
|
||||
- name: Run E2E tests - Non-Linux
|
||||
if: matrix.runner != 'ubuntu'
|
||||
run: npm run test:e2e:optimal
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: playwright-recordings-${{ matrix.runner }}
|
||||
path: |
|
||||
test-results/playwright/
|
||||
@@ -1,365 +0,0 @@
|
||||
name: ext-vscode-test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
|
||||
# Set default permissions for all jobs
|
||||
permissions:
|
||||
contents: read # Needed to check out code
|
||||
pull-requests: read # Needed for changed-file detection on pull requests
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
runs-on: ubuntu-latest
|
||||
name: Detect Changes
|
||||
outputs:
|
||||
vscode: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.vscode == 'true' }}
|
||||
testing_platform: ${{ steps.force.outputs.run_all == 'true' || steps.filter.outputs.testing_platform == 'true' }}
|
||||
steps:
|
||||
- id: force
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call'
|
||||
run: echo "run_all=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: steps.force.outputs.run_all != 'true'
|
||||
|
||||
- uses: dorny/paths-filter@v3
|
||||
if: steps.force.outputs.run_all != 'true'
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
vscode:
|
||||
- 'apps/vscode/src/**'
|
||||
- 'apps/vscode/webview-ui/**'
|
||||
- 'apps/vscode/proto/**'
|
||||
- 'apps/vscode/tests/**'
|
||||
- 'apps/vscode/scripts/**'
|
||||
- 'apps/vscode/standalone/**'
|
||||
- 'apps/vscode/assets/**'
|
||||
- 'apps/vscode/walkthrough/**'
|
||||
- 'apps/vscode/package.json'
|
||||
- 'apps/vscode/package-lock.json'
|
||||
- 'apps/vscode/buf.yaml'
|
||||
- 'apps/vscode/tsconfig*.json'
|
||||
- 'apps/vscode/biome.jsonc'
|
||||
- 'apps/vscode/esbuild.mjs'
|
||||
- 'apps/vscode/.mocharc.json'
|
||||
- 'apps/vscode/.nycrc*.json'
|
||||
- 'apps/vscode/.vscode-test.mjs'
|
||||
- 'apps/vscode/test-setup.js'
|
||||
- '.github/workflows/ext-vscode-test.yml'
|
||||
testing_platform:
|
||||
- 'apps/vscode/src/**'
|
||||
- 'apps/vscode/proto/**'
|
||||
- 'apps/vscode/standalone/**'
|
||||
- 'apps/vscode/testing-platform/**'
|
||||
- 'apps/vscode/tests/specs/**'
|
||||
- 'apps/vscode/package.json'
|
||||
- 'apps/vscode/package-lock.json'
|
||||
- 'apps/vscode/buf.yaml'
|
||||
- 'apps/vscode/tsconfig*.json'
|
||||
- 'apps/vscode/esbuild.mjs'
|
||||
- 'apps/vscode/.vscodeignore'
|
||||
- 'apps/vscode/scripts/**'
|
||||
- '.github/workflows/ext-vscode-test.yml'
|
||||
|
||||
quality-checks:
|
||||
needs: detect-changes
|
||||
if: needs.detect-changes.outputs.vscode == 'true' || needs.detect-changes.outputs.testing_platform == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
name: Quality Checks
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/vscode
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
apps/vscode/package-lock.json
|
||||
apps/vscode/webview-ui/package-lock.json
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui ci
|
||||
|
||||
- name: Run Quality Checks (Parallel)
|
||||
run: npm run ci:check-all
|
||||
|
||||
vscode-test:
|
||||
needs: [detect-changes, quality-checks]
|
||||
if: needs.detect-changes.outputs.vscode == 'true'
|
||||
env:
|
||||
VSCODE_TEST_VERSION: 1.103.0
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os == 'ubuntu-latest' && 'vscode test' || format('vscode test ({0})', matrix.os) }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: apps/vscode
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
apps/vscode/package-lock.json
|
||||
apps/vscode/webview-ui/package-lock.json
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui ci
|
||||
|
||||
- name: Set up NPM on Windows
|
||||
if: runner.os == 'Windows'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
|
||||
- name: Cache VS Code test runtime
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .vscode-test
|
||||
key: vscode-test-runtime-${{ runner.os }}-${{ env.VSCODE_TEST_VERSION }}
|
||||
|
||||
# Build the extension and tests (without redundant checks)
|
||||
- name: Build Tests and Extension
|
||||
id: build_step
|
||||
run: npm run ci:build
|
||||
|
||||
- name: Unit Tests with coverage - Linux
|
||||
id: unit_tests_linux
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' && runner.os == 'Linux' }}
|
||||
run: |
|
||||
npx nyc --nycrc-path .nycrc.unit.json --reporter=lcov npm run test:unit
|
||||
|
||||
- name: Unit Tests - Non-Linux
|
||||
id: unit_tests_non_linux
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' && runner.os != 'Linux' }}
|
||||
run: |
|
||||
npm run test:unit
|
||||
|
||||
- name: Extension Integration Tests - Linux
|
||||
id: integration_tests_linux
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' && runner.os == 'Linux' }}
|
||||
run: xvfb-run -a npm run test:coverage
|
||||
|
||||
- name: Extension Integration Tests - Non-Linux
|
||||
id: integration_tests_non_linux
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' && runner.os != 'Linux' }}
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
echo "Running extension integration tests (attempt ${attempt}/3)"
|
||||
if npm run test:integration; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$attempt" -eq 3 ]; then
|
||||
echo "Extension integration tests failed after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Extension integration tests failed; retrying after short delay"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
- name: Webview Tests with Coverage
|
||||
id: webview_tests
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' }}
|
||||
run: |
|
||||
cd webview-ui
|
||||
npm run test:coverage
|
||||
|
||||
- name: Save Coverage Reports
|
||||
uses: actions/upload-artifact@v4
|
||||
# Only upload artifacts on Linux - We only need coverage from one OS
|
||||
if: runner.os == 'Linux'
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: |
|
||||
apps/vscode/coverage-unit/lcov.info
|
||||
apps/vscode/webview-ui/coverage/lcov.info
|
||||
|
||||
test-platform-integration:
|
||||
needs: [detect-changes, quality-checks]
|
||||
if: needs.detect-changes.outputs.testing_platform == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: apps/vscode
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
cache-dependency-path: |
|
||||
apps/vscode/package-lock.json
|
||||
apps/vscode/webview-ui/package-lock.json
|
||||
apps/vscode/testing-platform/package-lock.json
|
||||
|
||||
- name: Install extension dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/webview-ui ci
|
||||
|
||||
- name: Download ripgrep binaries
|
||||
run: npm run download-ripgrep
|
||||
|
||||
- name: Compile Standalone
|
||||
run: npm run compile-standalone
|
||||
|
||||
- name: Install testing platform dependencies
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: npm --prefix apps/vscode/testing-platform ci
|
||||
|
||||
- name: Running testing platform integration spec tests
|
||||
timeout-minutes: 7
|
||||
run: npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage
|
||||
|
||||
- name: Save Coverage Reports
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-platform-integration-core-coverage
|
||||
path: apps/vscode/coverage/**/lcov.info
|
||||
|
||||
# Keep the required "test" check as a tiny aggregate gate instead of the conditional
|
||||
# VS Code matrix. GitHub treats conditionally skipped jobs as successful required
|
||||
# checks, so the gate below preserves the old required check name while making sure
|
||||
# whichever filtered test jobs were selected actually passed.
|
||||
test:
|
||||
needs: [detect-changes, quality-checks, vscode-test, test-platform-integration]
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
name: test
|
||||
steps:
|
||||
- name: Verify selected test jobs
|
||||
env:
|
||||
DETECT_CHANGES_RESULT: ${{ needs.detect-changes.result }}
|
||||
QUALITY_CHECKS_RESULT: ${{ needs.quality-checks.result }}
|
||||
VSCODE_CHANGED: ${{ needs.detect-changes.outputs.vscode }}
|
||||
TESTING_PLATFORM_CHANGED: ${{ needs.detect-changes.outputs.testing_platform }}
|
||||
VSCODE_TEST_RESULT: ${{ needs.vscode-test.result }}
|
||||
TEST_PLATFORM_RESULT: ${{ needs.test-platform-integration.result }}
|
||||
run: |
|
||||
if [ "$DETECT_CHANGES_RESULT" != "success" ]; then
|
||||
echo "detect-changes did not succeed: $DETECT_CHANGES_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$VSCODE_CHANGED" != "true" ] && [ "$TESTING_PLATFORM_CHANGED" != "true" ]; then
|
||||
echo "No root test paths changed; skipping root test requirements."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$QUALITY_CHECKS_RESULT" != "success" ]; then
|
||||
echo "quality-checks did not succeed: $QUALITY_CHECKS_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$VSCODE_CHANGED" = "true" ] && [ "$VSCODE_TEST_RESULT" != "success" ]; then
|
||||
echo "vscode-test did not succeed: $VSCODE_TEST_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$TESTING_PLATFORM_CHANGED" = "true" ] && [ "$TEST_PLATFORM_RESULT" != "success" ]; then
|
||||
echo "test-platform-integration did not succeed: $TEST_PLATFORM_RESULT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Selected root test jobs passed."
|
||||
|
||||
qlty:
|
||||
needs: [detect-changes, quality-checks, vscode-test, test-platform-integration]
|
||||
if: ${{ !cancelled() && needs.quality-checks.result == 'success' && (needs.vscode-test.result == 'success' || needs.vscode-test.result == 'skipped') && (needs.test-platform-integration.result == 'success' || needs.test-platform-integration.result == 'skipped') && (needs.detect-changes.outputs.vscode == 'true' || needs.detect-changes.outputs.testing_platform == 'true') }}
|
||||
runs-on: ubuntu-latest
|
||||
# Run on PRs to main, pushes to main, and manual dispatches
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download unit tests coverage reports
|
||||
if: needs.detect-changes.outputs.vscode == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: apps/vscode
|
||||
|
||||
- name: Upload core unit tests coverage to Qlty
|
||||
if: needs.detect-changes.outputs.vscode == 'true'
|
||||
uses: qltysh/qlty-action/coverage@v2
|
||||
with:
|
||||
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
|
||||
# we can merge multiple files if necessary
|
||||
files: |
|
||||
apps/vscode/coverage-unit/lcov.info
|
||||
tag: unit:core
|
||||
|
||||
- name: Upload webview-ui unit tests coverage to Qlty
|
||||
if: needs.detect-changes.outputs.vscode == 'true'
|
||||
uses: qltysh/qlty-action/coverage@v2
|
||||
with:
|
||||
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
|
||||
# we can merge multiple files if necessary
|
||||
files: |
|
||||
apps/vscode/webview-ui/coverage/lcov.info
|
||||
tag: unit:webview-ui
|
||||
add-prefix: webview-ui/
|
||||
|
||||
- name: Download test platform integration core coverage artifact
|
||||
if: needs.detect-changes.outputs.testing_platform == 'true'
|
||||
uses: actions/download-artifact@v4
|
||||
continue-on-error: true
|
||||
id: download-integration-coverage
|
||||
with:
|
||||
name: test-platform-integration-core-coverage
|
||||
path: apps/vscode/integration-core-coverage-reports
|
||||
|
||||
- name: Upload core integration tests coverage to Qlty
|
||||
if: needs.detect-changes.outputs.testing_platform == 'true' && steps.download-integration-coverage.outcome == 'success'
|
||||
uses: qltysh/qlty-action/coverage@v2
|
||||
with:
|
||||
token: ${{ secrets.QLTY_COVERAGE_TOKEN }}
|
||||
files: apps/vscode/integration-core-coverage-reports/**/lcov.info
|
||||
tag: integration:core
|
||||
@@ -0,0 +1,128 @@
|
||||
name: "Publish Release"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-type:
|
||||
description: "Choose release type (release or pre-release)"
|
||||
required: true
|
||||
default: "release"
|
||||
type: choice
|
||||
options:
|
||||
- pre-release
|
||||
- release
|
||||
tag:
|
||||
description: "Enter existing tag to publish (e.g., v3.1.2)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
publish:
|
||||
needs: test
|
||||
name: Publish Extension
|
||||
runs-on: ubuntu-latest
|
||||
environment: publish
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
- name: Get Version
|
||||
id: get_version
|
||||
run: |
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Validate Tag
|
||||
id: validate_tag
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "Using existing tag: $TAG"
|
||||
|
||||
# Verify the tag exists
|
||||
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo "Error: Tag '$TAG' does not exist in the repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Tag '$TAG' validated successfully"
|
||||
|
||||
- name: Package and Publish Extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
run: |
|
||||
# Required to generate the .vsix
|
||||
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
if [ "${{ github.event.inputs.release-type }}" = "pre-release" ]; then
|
||||
npm run publish:marketplace:prerelease
|
||||
echo "Successfully published pre-release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
else
|
||||
npm run publish:marketplace
|
||||
echo "Successfully published release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
fi
|
||||
|
||||
# - name: Get Changelog Entry
|
||||
# id: changelog
|
||||
# uses: mindsers/changelog-reader-action@v2
|
||||
# with:
|
||||
# # This expects a standard Keep a Changelog format
|
||||
# # "latest" means it will read whichever is the most recent version
|
||||
# # set in "## [1.2.3] - 2025-01-28" style
|
||||
# version: latest
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.validate_tag.outputs.tag }}
|
||||
files: "*.vsix"
|
||||
# body: ${{ steps.changelog.outputs.content }}
|
||||
generate_release_notes: true
|
||||
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,65 +0,0 @@
|
||||
name: repo-label-issues
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.issue.body || '';
|
||||
const labels = context.payload.issue.labels.map(l => l.name);
|
||||
|
||||
// Check if JetBrains Plugin is selected
|
||||
if (body.match(/###\s*Plugin Type\s*\n+JetBrains Plugin/i)) {
|
||||
if (!labels.includes('JetBrains')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['JetBrains']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if VSCode Extension is selected
|
||||
if (body.match(/###\s*Plugin Type\s*\n+VSCode Extension/i)) {
|
||||
if (!labels.includes('VS Code')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['VS Code']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if CLI is selected
|
||||
if (body.match(/###\s*Plugin Type\s*\n+CLI/i)) {
|
||||
if (!labels.includes('CLI')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['CLI']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if beta version checkbox is checked
|
||||
if (body.includes('- [X] I am using a beta version of Cline') || body.includes('- [x] I am using a beta version of Cline')) {
|
||||
if (!labels.includes('beta')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['beta']
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# This workflow will only label and/or close 30 issues at a time in order to avoid exceeding a rate limit.
|
||||
# More info: https://docs.github.com/en/actions/use-cases-and-examples/project-management/closing-inactive-issues
|
||||
name: repo-stale-issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
exempt-issue-labels: "pinned,security"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,282 +0,0 @@
|
||||
name: sdk-publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
channel:
|
||||
description: "Publish channel"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- nightly
|
||||
- latest
|
||||
default: nightly
|
||||
force_publish:
|
||||
description: "Force publish even if there are no commits in the last 24 hours"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
confirm_publish:
|
||||
description: 'Required when channel=latest. Type "publish" to confirm release publish.'
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
# Run nightly at 2:00 AM UTC
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: .
|
||||
|
||||
jobs:
|
||||
test:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/sdk-test.yml
|
||||
|
||||
publish-sdk:
|
||||
needs: test
|
||||
name: Publish SDK Packages
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
if: |
|
||||
github.repository == 'cline/cline' &&
|
||||
github.ref == 'refs/heads/main' &&
|
||||
(
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
inputs.channel != 'latest' ||
|
||||
(
|
||||
inputs.confirm_publish == 'publish' &&
|
||||
!endsWith(github.actor, '[bot]')
|
||||
)
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Determine publish channel
|
||||
id: channel
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_CHANNEL: ${{ inputs.channel }}
|
||||
run: |
|
||||
# Default to nightly for scheduled runs
|
||||
if [ "$EVENT_NAME" = "schedule" ]; then
|
||||
echo "channel=nightly" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "channel=$INPUT_CHANNEL" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Check for recent commits
|
||||
id: check_commits
|
||||
env:
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
FORCE_PUBLISH: ${{ inputs.force_publish }}
|
||||
run: |
|
||||
# Always publish for latest (production) releases
|
||||
if [ "$CHANNEL" = "latest" ]; then
|
||||
echo "Production release requested, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$FORCE_PUBLISH" = "true" ]; then
|
||||
echo "force_publish enabled, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$(git rev-list --count HEAD --since="24 hours ago")" -eq 0 ]; then
|
||||
echo "No commits in last 24 hours, skipping publish"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Found recent commits, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Verify trusted publishing context
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
|
||||
echo "GitHub OIDC request environment is unavailable. Ensure this job has id-token: write for npm trusted publishing."
|
||||
exit 1
|
||||
fi
|
||||
echo "GitHub OIDC request environment is available for npm trusted publishing."
|
||||
|
||||
- name: Setup Bun
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Verify publish tooling
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
NPM_VERSION=$(npm --version)
|
||||
echo "npm ${NPM_VERSION}"
|
||||
IFS=. read -r major minor patch <<EOF
|
||||
${NPM_VERSION}
|
||||
EOF
|
||||
if [ "$major" -lt 11 ] || { [ "$major" -eq 11 ] && [ "$minor" -lt 5 ]; } || { [ "$major" -eq 11 ] && [ "$minor" -eq 5 ] && [ "$patch" -lt 1 ]; }; then
|
||||
echo "npm 11.5.1 or newer is required for trusted publishing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun install
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build SDK
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun run build:sdk
|
||||
|
||||
- name: Generate shared version
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
id: version
|
||||
env:
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
run: |
|
||||
BASE_VERSION=$(node -p "require('./sdk/packages/llms/package.json').version")
|
||||
|
||||
if [ "$CHANNEL" = "nightly" ]; then
|
||||
TIMESTAMP=$(date +%s)
|
||||
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
|
||||
else
|
||||
VERSION="$BASE_VERSION"
|
||||
fi
|
||||
|
||||
echo "Base version: $BASE_VERSION"
|
||||
echo "Channel: $CHANNEL"
|
||||
echo "Publish version: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update all package versions and lockfile
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: bun sdk/scripts/version.ts "$VERSION"
|
||||
|
||||
- name: Verify publishability
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: bun sdk/scripts/check-publish.ts
|
||||
|
||||
- name: Prepare package tarball directory
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: mkdir -p "$RUNNER_TEMP/sdk-npm-packs"
|
||||
|
||||
# Pack with Bun so workspace/catalog protocols are resolved in the tarball,
|
||||
# then publish that tarball with npm so npm trusted publishing can use GitHub OIDC.
|
||||
# Publish sequentially in dependency order: shared → llms → agents → core → sdk
|
||||
- name: Publish @cline/shared
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Publishing @cline/shared@${VERSION} with tag '${CHANNEL}'..."
|
||||
cd sdk/packages/shared
|
||||
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
|
||||
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
|
||||
|
||||
- name: Publish @cline/llms
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Publishing @cline/llms@${VERSION} with tag '${CHANNEL}'..."
|
||||
cd sdk/packages/llms
|
||||
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
|
||||
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
|
||||
|
||||
- name: Publish @cline/agents
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Publishing @cline/agents@${VERSION} with tag '${CHANNEL}'..."
|
||||
cd sdk/packages/agents
|
||||
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
|
||||
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
|
||||
|
||||
- name: Publish @cline/core
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Publishing @cline/core@${VERSION} with tag '${CHANNEL}'..."
|
||||
cd sdk/packages/core
|
||||
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
|
||||
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
|
||||
|
||||
- name: Publish @cline/sdk
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: "true"
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
echo "Publishing @cline/sdk@${VERSION} with tag '${CHANNEL}'..."
|
||||
cd sdk/packages/sdk
|
||||
TARBALL=$(bun pm pack --destination "$RUNNER_TEMP/sdk-npm-packs" --quiet)
|
||||
npm publish "$RUNNER_TEMP/sdk-npm-packs/$(basename "$TARBALL")" --tag "$CHANNEL" --access public
|
||||
|
||||
- name: Create package tags for production publish
|
||||
if: steps.check_commits.outputs.skip != 'true' && steps.channel.outputs.channel == 'latest'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
for PKG in shared llms agents core sdk; do
|
||||
TAG="sdk/${PKG}/v${VERSION}"
|
||||
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
echo "Tag already exists locally: ${TAG}"
|
||||
else
|
||||
git tag -a "${TAG}" -m "@cline/${PKG}@${VERSION}"
|
||||
echo "Created tag: ${TAG}"
|
||||
fi
|
||||
|
||||
# Ensure remote has the tag; this is idempotent if tag already exists remotely.
|
||||
git push origin "refs/tags/${TAG}"
|
||||
done
|
||||
|
||||
- name: Summary
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
CHANNEL: ${{ steps.channel.outputs.channel }}
|
||||
run: |
|
||||
echo "Published SDK packages with tag '${CHANNEL}':"
|
||||
echo " - @cline/shared@${VERSION}"
|
||||
echo " - @cline/llms@${VERSION}"
|
||||
echo " - @cline/agents@${VERSION}"
|
||||
echo " - @cline/core@${VERSION}"
|
||||
echo " - @cline/sdk@${VERSION}"
|
||||
if [ "$CHANNEL" = "latest" ]; then
|
||||
echo "Created git tags:"
|
||||
echo " - sdk/shared/v${VERSION}"
|
||||
echo " - sdk/llms/v${VERSION}"
|
||||
echo " - sdk/agents/v${VERSION}"
|
||||
echo " - sdk/core/v${VERSION}"
|
||||
echo " - sdk/sdk/v${VERSION}"
|
||||
fi
|
||||
@@ -1,112 +0,0 @@
|
||||
name: sdk-test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "sdk/**"
|
||||
- ".github/workflows/sdk-test.yml"
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "sdk/**"
|
||||
- ".github/workflows/sdk-test.yml"
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: .
|
||||
|
||||
jobs:
|
||||
quality-checks:
|
||||
runs-on: ubuntu-latest
|
||||
name: Quality Checks
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Typecheck
|
||||
run: |
|
||||
bun run build:sdk
|
||||
bun run -F @cline/cli build
|
||||
bun run types
|
||||
|
||||
- name: Lint & Format
|
||||
run: bun run lint
|
||||
|
||||
test:
|
||||
needs: quality-checks
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
node-version: "24.x"
|
||||
- os: windows-latest
|
||||
node-version: "24.x"
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Test (${{ matrix.os }}, Node ${{ matrix.node-version }})
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3.13"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build SDK
|
||||
id: build_sdk_step
|
||||
run: bun run build:sdk
|
||||
|
||||
- name: Build CLI
|
||||
id: build_cli_step
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' }}
|
||||
run: bun -F @cline/cli build
|
||||
|
||||
- name: Run Tests
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os != 'windows-latest' }}
|
||||
run: bun run test
|
||||
|
||||
- name: Run SDK Tests (Windows)
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'windows-latest' }}
|
||||
run: bun -F './sdk/packages/**' test
|
||||
|
||||
- name: Smoke test SQLite under Node
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && matrix.os != 'windows-latest' }}
|
||||
timeout-minutes: 10
|
||||
run: bun sdk/scripts/ci-node-smoke.ts
|
||||
|
||||
- name: Run TUI e2e tests
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x' }}
|
||||
run: bun -F @cline/cli test:e2e:cli:tui
|
||||
|
||||
- name: Verify packages are publishable
|
||||
if: ${{ !cancelled() && steps.build_sdk_step.outcome == 'success' && steps.build_cli_step.outcome == 'success' && matrix.os == 'ubuntu-latest' && matrix.node-version == '24.x' }}
|
||||
run: bun sdk/scripts/check-publish.ts
|
||||
@@ -0,0 +1,25 @@
|
||||
# This workflow will only label and/or close 30 issues at a time in order to avoid exceeding a rate limit.
|
||||
# More info: https://docs.github.com/en/actions/use-cases-and-examples/project-management/closing-inactive-issues
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
exempt-issue-labels: "pinned,security"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Test Stale Issues Workflow
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
days-before-stale:
|
||||
description: "Days before an issue becomes stale"
|
||||
required: true
|
||||
default: "1"
|
||||
days-before-close:
|
||||
description: "Days before a stale issue is closed"
|
||||
required: true
|
||||
default: "1"
|
||||
|
||||
jobs:
|
||||
test-stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@28ca103
|
||||
with:
|
||||
days-before-issue-stale: ${{ github.event.inputs.days-before-stale }}
|
||||
days-before-issue-close: ${{ github.event.inputs.days-before-close }}
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for ${{ github.event.inputs.days-before-stale }} days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for ${{ github.event.inputs.days-before-close }} days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
exempt-issue-labels: "pinned,security"
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
debug-only: true
|
||||
@@ -0,0 +1,221 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_call:
|
||||
|
||||
# Set default permissions for all jobs
|
||||
permissions:
|
||||
contents: read # Needed to check out code
|
||||
checks: write # Needed to report test results
|
||||
pull-requests: write # Needed to add comments/annotations to PRs
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os == 'ubuntu-latest' && 'test' || format('test ({0})', matrix.os) }}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
|
||||
- name: Install xvfb on Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y xvfb
|
||||
|
||||
- name: Set up NPM on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
|
||||
- name: Type Check
|
||||
run: npm run check-types
|
||||
|
||||
- name: Lint Check
|
||||
run: npm run lint
|
||||
|
||||
- name: Format Check
|
||||
run: npm run format
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Tests and Extension
|
||||
run: npm run pretest
|
||||
|
||||
- name: Unit Tests
|
||||
run: npm run test:unit
|
||||
|
||||
# Run extension tests with coverage
|
||||
- name: Extension Integration Tests with Coverage
|
||||
id: extension_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
node ./scripts/test-ci.js 2>&1 | tee extension_coverage.txt
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage extension_coverage.txt --type=extension --github-output --verbose
|
||||
|
||||
# Run webview tests with coverage
|
||||
- name: Webview Tests with Coverage
|
||||
id: webview_coverage
|
||||
continue-on-error: true
|
||||
run: |
|
||||
cd webview-ui
|
||||
# Ensure coverage dependency is installed
|
||||
npm install --no-save @vitest/coverage-v8
|
||||
npm run test:coverage 2>&1 | tee webview_coverage.txt
|
||||
cd ..
|
||||
# Default the encoding to UTF-8 - It's not the default on Windows
|
||||
PYTHONUTF8=1 PYTHONPATH=.github/scripts python -m coverage_check extract-coverage webview-ui/webview_coverage.txt --type=webview --github-output --verbose
|
||||
|
||||
# Save coverage reports as artifacts (workflow-scoped)
|
||||
- name: Save Coverage Reports
|
||||
uses: actions/upload-artifact@v4
|
||||
# Only upload artifacts on Linux - We only need coverage from one OS
|
||||
if: runner.os == 'Linux'
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: |
|
||||
extension_coverage.txt
|
||||
webview-ui/webview_coverage.txt
|
||||
|
||||
# Set the check as failed if any of the tests failed
|
||||
- name: Check for test failures
|
||||
run: |
|
||||
# Check if any of the test steps failed
|
||||
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#steps-context
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ]; then
|
||||
echo "Extension Integration Tests failed, see previous step for test output."
|
||||
fi
|
||||
if [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
echo "Webview Tests failed, see previous step for test output."
|
||||
fi
|
||||
if [ "${{ steps.extension_coverage.outcome }}" != "success" ] || [ "${{ steps.webview_coverage.outcome }}" != "success" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
coverage:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on PRs to main branch
|
||||
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for accurate comparison
|
||||
|
||||
# Setup Python for coverage script
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install requests
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci
|
||||
|
||||
# Build the extension before running tests
|
||||
- name: Build Extension
|
||||
run: npm run compile
|
||||
|
||||
# Download coverage artifacts from test job
|
||||
- name: Download Coverage Reports
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: pr-coverage-reports
|
||||
path: . # Download to root directory to match expected paths
|
||||
|
||||
# Process coverage workflow
|
||||
- name: Process coverage workflow
|
||||
id: coverage
|
||||
run: |
|
||||
# Extract PR number from GITHUB_REF
|
||||
PR_NUMBER=$(echo "$GITHUB_REF" | sed -e 's/refs\/pull\///' -e 's/\/merge//')
|
||||
|
||||
# Run the coverage workflow from root directory
|
||||
PYTHONPATH=.github/scripts python -m coverage_check process-workflow \
|
||||
--base-branch ${{ github.base_ref }} \
|
||||
--pr-number $PR_NUMBER \
|
||||
--repo $GITHUB_REPOSITORY \
|
||||
--token ${{ secrets.GITHUB_TOKEN }} \
|
||||
--verbose
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
+7
-53
@@ -8,76 +8,30 @@ tmp
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
.husky/_/
|
||||
|
||||
pnpm-lock.yaml
|
||||
|
||||
.clineignore
|
||||
.venv
|
||||
.actrc
|
||||
CLAUDE.local.md
|
||||
|
||||
apps/vscode/webview-ui/src/**/*.js
|
||||
apps/vscode/webview-ui/src/**/*.js.map
|
||||
webview-ui/src/**/*.js
|
||||
webview-ui/src/**/*.js.map
|
||||
|
||||
# Ignore coverage directories and files
|
||||
coverage
|
||||
coverage-unit
|
||||
.nyc_output
|
||||
# But don't ignore the coverage scripts in .github/scripts/
|
||||
!.github/scripts/coverage/
|
||||
|
||||
*evals.env
|
||||
.env
|
||||
.secrets
|
||||
.github/act/.secrets
|
||||
|
||||
.worktrees
|
||||
|
||||
## Generated files ##
|
||||
apps/vscode/src/generated/
|
||||
apps/vscode/src/shared/proto/
|
||||
apps/vscode/webview-ui/src/services/grpc-client.ts
|
||||
*.tsbuildinfo
|
||||
src/generated/
|
||||
src/shared/proto/
|
||||
webview-ui/src/services/grpc-client.ts
|
||||
|
||||
# E2E Tests
|
||||
test-results
|
||||
|
||||
/.github/act
|
||||
/pkg
|
||||
.secrets
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Smoke test results (generated)
|
||||
evals/smoke-tests/results/
|
||||
|
||||
.tui-test
|
||||
secrets.json
|
||||
tui-traces
|
||||
tests/**/cache
|
||||
|
||||
# Backup created by scripts/marketplace-readme.mjs while publishing.
|
||||
# Should never be committed: only exists if a publish aborts mid-swap.
|
||||
.README.github.bak
|
||||
|
||||
# Tauri generated code
|
||||
apps/*/src-tauri/gen
|
||||
apps/*/src-tauri/bin
|
||||
apps/examples/*/src-tauri/gen
|
||||
apps/examples/*/src-tauri/bin
|
||||
# Tauri UI test snapshots
|
||||
apps/*/src/tests/.tui-test
|
||||
apps/*/src/tests/tui-traces
|
||||
apps/vscode/webview-ui/src/**/*.js
|
||||
apps/vscode/webview-ui/src/**/*.js.map
|
||||
|
||||
|
||||
# SDK Session files / User data
|
||||
.cline/data
|
||||
.cline/tmp
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
.cline/**/managed.json
|
||||
.cline/**/bundle.json
|
||||
## CLI pre-release ##
|
||||
/cli
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
title = "Cline SDK secret scanning"
|
||||
|
||||
[extend]
|
||||
useDefault = true
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "evals/cline-bench"]
|
||||
path = evals/cline-bench
|
||||
url = https://github.com/cline/cline-bench.git
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"strictness": 2,
|
||||
"triggerOnUpdates": true,
|
||||
"statusCheck": true,
|
||||
"rules": [
|
||||
{
|
||||
"id": "sdk-tool-handler-telemetry",
|
||||
"rule": "Any new tool handler added to packages/agents/src or packages/core/src that performs a user-visible action (writes files, executes commands, modifies state, calls external APIs) must include a call to captureToolUsage() from packages/core/src/services/telemetry/core-events.ts, or emit a task.tool_used event via telemetry.capture(). Pure read-only helpers and getters are exempt. When in doubt, prefer instrumentation.",
|
||||
"scope": [
|
||||
"sdk/packages/agents/src/**",
|
||||
"sdk/packages/core/src/**"
|
||||
],
|
||||
"severity": "high"
|
||||
},
|
||||
{
|
||||
"id": "sdk-session-lifecycle-telemetry",
|
||||
"rule": "New session start, end, or state-transition code paths in packages/core/src must call the appropriate typed helper from packages/core/src/services/telemetry/core-events.ts (captureTaskCreated, captureTaskCompleted, captureConversationTurnEvent, captureTokenUsage, etc.). Do not inline raw telemetry.capture() calls for session lifecycle events — always use the typed helper, which guarantees a consistent payload shape.",
|
||||
"scope": [
|
||||
"sdk/packages/core/src/cline-core/**",
|
||||
"sdk/packages/core/src/runtime/**"
|
||||
],
|
||||
"severity": "high"
|
||||
},
|
||||
{
|
||||
"id": "sdk-no-raw-event-strings",
|
||||
"rule": "All telemetry event name strings must be sourced from CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts. If a PR introduces a string literal in a telemetry.capture(), telemetry.captureRequired(), or recordCounter()/recordHistogram()/recordGauge() call that does not reference CORE_TELEMETRY_EVENTS, flag it. New events must be added to CORE_TELEMETRY_EVENTS first, with a typed capture helper created alongside them.",
|
||||
"scope": [
|
||||
"sdk/packages/core/src/**",
|
||||
"sdk/packages/agents/src/**",
|
||||
"apps/cli/src/**",
|
||||
"apps/vscode/src/**"
|
||||
],
|
||||
"severity": "medium"
|
||||
},
|
||||
{
|
||||
"id": "sdk-auth-telemetry-completeness",
|
||||
"rule": "Any new OAuth or authentication provider added under packages/core/src/auth must emit all four lifecycle events using the typed helpers from core-events.ts: captureAuthStarted (at flow entry), captureAuthSucceeded + identifyAccount (on token success), captureAuthFailed (on error), and captureAuthLoggedOut (on token invalidation or explicit logout). Flag PRs that introduce a new auth flow file without all four. Cross-reference packages/core/src/auth/cline.ts and packages/core/src/auth/codex.ts as canonical examples.",
|
||||
"scope": [
|
||||
"sdk/packages/core/src/auth/**"
|
||||
],
|
||||
"severity": "high"
|
||||
},
|
||||
{
|
||||
"id": "sdk-telemetry-doc-update",
|
||||
"rule": "Any PR that adds new event constants to CORE_TELEMETRY_EVENTS in packages/core/src/services/telemetry/core-events.ts, adds new capture* helper functions, or changes the payload shape of an existing event must update the Event Catalog section in DOC.md. Flag PRs that modify core-events.ts without a corresponding change to DOC.md.",
|
||||
"scope": [
|
||||
"sdk/packages/core/src/services/telemetry/core-events.ts"
|
||||
],
|
||||
"severity": "medium"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"path": "sdk/packages/core/src/services/telemetry/core-events.ts",
|
||||
"description": "Single source of truth for all telemetry event names (CORE_TELEMETRY_EVENTS) and their typed capture helper functions. Every PR touching telemetry must be evaluated against this catalog. New events must be defined here first."
|
||||
},
|
||||
{
|
||||
"path": "sdk/packages/shared/src/services/telemetry.ts",
|
||||
"description": "ITelemetryService interface definition. Defines the contract all telemetry implementations must satisfy (capture, captureRequired, recordCounter, recordHistogram, recordGauge, flush, dispose)."
|
||||
},
|
||||
{
|
||||
"path": "sdk/packages/core/src/services/telemetry/TelemetryService.ts",
|
||||
"description": "Reference implementation of ITelemetryService used by all hosts. Multi-adapter fan-out service that forwards events to OpenTelemetry."
|
||||
},
|
||||
{
|
||||
"path": "sdk/packages/core/src/services/telemetry/OpenTelemetryProvider.ts",
|
||||
"description": "OpenTelemetry-backed provider that wires logs/metrics/traces exporters. Contains createConfiguredTelemetryService and createConfiguredTelemetryHandle, the canonical factories every host should use."
|
||||
},
|
||||
{
|
||||
"path": "DOC.md",
|
||||
"description": "Public API and event documentation. The Event Catalog and 'Activation funnel' sections must be kept in sync with core-events.ts. Host integration rules (CLI dir ordering, hub daemon metadata forwarding) are documented here."
|
||||
},
|
||||
{
|
||||
"path": "sdk/ARCHITECTURE.md",
|
||||
"description": "Architecture reference. Telemetry design decisions, completion semantics (submit_and_exit anchoring), and hub-daemon telemetry forwarding are documented here. Use as ground truth for design intent."
|
||||
},
|
||||
{
|
||||
"path": "sdk/AGENTS.md",
|
||||
"description": "Package boundary rules. Telemetry runtime services live in @cline/core; @cline/agents must not own stateful telemetry. Use to evaluate whether a telemetry change is being made in the correct package."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
# SDK Telemetry Standards
|
||||
|
||||
These rules supplement `config.json`. The structured rules describe **what** to enforce; this
|
||||
document explains **why**, so Greptile has the context to avoid false positives.
|
||||
|
||||
## Telemetry Stack
|
||||
|
||||
The SDK uses OpenTelemetry (OTEL) as its sole telemetry transport. Events flow through:
|
||||
|
||||
```
|
||||
core-events.ts (event catalog + typed helpers)
|
||||
↓
|
||||
ITelemetryService (sdk/packages/shared) ← interface contract
|
||||
↓
|
||||
TelemetryService (sdk/packages/core) ← multi-adapter fan-out
|
||||
↓
|
||||
OpenTelemetryAdapter → OpenTelemetryProvider ← OTLP transport
|
||||
↓
|
||||
OTLP endpoint (collector or vendor)
|
||||
```
|
||||
|
||||
The SDK does **not** depend on the original `cline/cline` repo for telemetry. The two have
|
||||
parallel-but-independent stacks; this `.greptile/` config covers only the SDK.
|
||||
|
||||
## The Single Source of Truth
|
||||
|
||||
`sdk/packages/core/src/services/telemetry/core-events.ts` is the single source of truth for all
|
||||
event names. It exports:
|
||||
|
||||
- `CORE_TELEMETRY_EVENTS` — a frozen const object grouped by family
|
||||
(`CLIENT`, `SESSION`, `USER`, `TASK`, `HOOKS`, `WORKSPACE`)
|
||||
- A typed `capture*()` helper for every event family
|
||||
(`captureExtensionActivated`, `captureTaskCreated`, `captureToolUsage`, etc.)
|
||||
|
||||
**Never use raw string literals for event names at call sites.** A new event always means:
|
||||
|
||||
1. Add the constant to `CORE_TELEMETRY_EVENTS`
|
||||
2. Add a typed `capture*()` helper alongside it (with a typed `properties` parameter)
|
||||
3. Update the Event Catalog section in `DOC.md`
|
||||
4. Add a unit test in `core-events.test.ts` asserting the event is dropped when telemetry is opted out
|
||||
|
||||
## The Activation Funnel
|
||||
|
||||
The canonical funnel that downstream analytics depends on:
|
||||
|
||||
```
|
||||
user.extension_activated
|
||||
→ workspace.initialized
|
||||
→ workspace.path_resolved (gated on multi-root)
|
||||
→ task.created
|
||||
→ task.conversation_turn (one per turn, source: "user" | "assistant")
|
||||
→ task.completed (source: "submit_and_exit" | "shutdown")
|
||||
```
|
||||
|
||||
Emission ownership:
|
||||
|
||||
- `user.extension_activated`: emitted **once per host process** by host-specific helpers
|
||||
(`captureCliExtensionActivated` for the CLI, `captureExtensionActivated` for VS Code).
|
||||
- `workspace.initialized` / `workspace.init_error`: emitted by a per-process de-duplicated
|
||||
emitter in `prepareLocalRuntimeBootstrap`. Hosts must NOT re-emit these.
|
||||
- `workspace.path_resolved`: emitted from default tool executors **only when**
|
||||
`WorkspaceManager` exposes more than one root.
|
||||
- `task.*`: emitted by core session lifecycle code in `sdk/packages/core/src/cline-core/` and
|
||||
`sdk/packages/core/src/runtime/`. Hosts must not duplicate this emission.
|
||||
|
||||
## `task.completed` Semantics
|
||||
|
||||
`task.completed` marks the moment the **assistant declared the task done**, not the moment
|
||||
the SDK session record was finalized. The local runtime emits it when it observes a successful
|
||||
`submit_and_exit` tool call (the SDK analog of original Cline's `attempt_completion`). For
|
||||
non-interactive runs that finish without invoking the explicit completion tool,
|
||||
`shutdownSession` emits it as a fallback with `source: "shutdown"`.
|
||||
|
||||
Each session is guaranteed at most one `task.completed` emission. The `source` field
|
||||
(`"submit_and_exit" | "shutdown"`) is required for analytics attribution.
|
||||
|
||||
## CLI Directory-Ordering Rule
|
||||
|
||||
The CLI accepts `--config <dir>`. The CLI **must** apply `setClineDir(...)` and
|
||||
`setHomeDir(...)` from `@cline/shared/storage` **before** calling
|
||||
`captureCliExtensionActivated()`. Otherwise the telemetry singleton's persisted distinct-id
|
||||
and any other on-disk telemetry state lands under `~/.cline` instead of the user's chosen
|
||||
config dir.
|
||||
|
||||
The canonical pattern is in `apps/cli/src/main.ts` (PR #357):
|
||||
|
||||
```ts
|
||||
if (configDir) setClineDir(configDir);
|
||||
setHomeDir(homedir());
|
||||
captureCliExtensionActivated(); // <-- after dir overrides
|
||||
```
|
||||
|
||||
## Hub Daemon Metadata Forwarding
|
||||
|
||||
Hosts that spawn a detached `@cline/core/hub/daemon-entry` process must forward telemetry
|
||||
metadata into the daemon argv so the daemon can reconstruct an equivalent
|
||||
`ITelemetryService`. The expected payload is base64-encoded JSON with snake_case keys:
|
||||
|
||||
```
|
||||
{ extension_version, cline_type, platform, platform_version, os_type, os_version, is_remote_workspace }
|
||||
```
|
||||
|
||||
The reference implementation is `apps/vscode/src/hub-daemon.ts` (PR #357). Without this
|
||||
forwarding, hub-backed sessions silently drop their lifecycle telemetry.
|
||||
|
||||
## Auth Lifecycle Completeness
|
||||
|
||||
Every authentication provider in `sdk/packages/core/src/auth/` must emit all four auth lifecycle
|
||||
events using the typed helpers:
|
||||
|
||||
| Phase | Helper | Where it fires |
|
||||
|---|---|---|
|
||||
| Flow entry | `captureAuthStarted(provider)` | Top of the OAuth flow function |
|
||||
| Token success | `captureAuthSucceeded(provider)` + `identifyAccount(...)` | After successful token exchange |
|
||||
| Token error | `captureAuthFailed(provider, errorMessage)` | In the catch block |
|
||||
| Token invalidation | `captureAuthLoggedOut(provider, reason)` | On invalid_grant or explicit logout |
|
||||
|
||||
Cross-reference `sdk/packages/core/src/auth/cline.ts` and `sdk/packages/core/src/auth/codex.ts` as
|
||||
canonical examples of all four phases.
|
||||
|
||||
## Single Telemetry Service Per Host
|
||||
|
||||
On VS Code, the telemetry handle is built **once** in `activate()`
|
||||
(`apps/vscode/src/telemetry.ts`) and the same instance is passed into the sidebar, panel
|
||||
command, and daemon spawn payload. Do not let individual controllers construct their own
|
||||
`ITelemetryService` — that fragments distinct-id state, opt-out tracking, and flush ownership.
|
||||
|
||||
The CLI follows the same pattern via the `getCliTelemetryService()` singleton in
|
||||
`apps/cli/src/utils/telemetry.ts`, which is memoized by the activation gate in
|
||||
`telemetry.activation-gate.ts`.
|
||||
|
||||
## Common False-Positive Adjustments
|
||||
|
||||
If Greptile flags one of the following, the rule is **not** violated:
|
||||
|
||||
- A telemetry call that is wrapped in a host-specific helper (e.g.
|
||||
`captureCliExtensionActivated` wrapping `captureExtensionActivated`) — the inner helper
|
||||
is the typed call.
|
||||
- `enterprise.*` events emitted from `apps/cli/src/utils/enterprise.ts` — these are
|
||||
enterprise-side events not yet in `CORE_TELEMETRY_EVENTS`; they are tracked separately.
|
||||
- A new test file that uses raw event name strings inside `expect(...)` assertions — tests
|
||||
may reference event names as strings to assert what was emitted.
|
||||
+1
-10
@@ -1,10 +1 @@
|
||||
if ! command -v gitleaks >/dev/null 2>&1; then
|
||||
echo "gitleaks is required for the pre-commit secret scan."
|
||||
echo "Install it with: brew install gitleaks"
|
||||
echo "Other install options: https://github.com/gitleaks/gitleaks#installing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gitleaks git --pre-commit --redact --staged --verbose || exit 1
|
||||
|
||||
lint-staged
|
||||
lint-staged --no-stash
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "Build & Link CLI",
|
||||
"command": "bun -F @cline/cli build && bun -F @cline/cli link",
|
||||
"icon": "play"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extension": [
|
||||
"ts"
|
||||
],
|
||||
"spec": [
|
||||
"src/**/__tests__/*.ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register",
|
||||
"source-map-support/register",
|
||||
"./src/test/requires.ts"
|
||||
],
|
||||
"recursive": true,
|
||||
"exit": true
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
bun 1.3.13
|
||||
node 22
|
||||
@@ -1,6 +1,5 @@
|
||||
import { defineConfig } from "@vscode/test-cli"
|
||||
import path from "path"
|
||||
const vscodeTestVersion = process.env.VSCODE_TEST_VERSION ?? "stable"
|
||||
|
||||
export default defineConfig({
|
||||
files: "{out/**/*.test.js,src/**/*.test.js,!src/test/e2e/**/*.test.js,!out/src/test/e2e/**/*.test.js}",
|
||||
@@ -13,7 +12,7 @@ export default defineConfig({
|
||||
require: ["./test-setup.js"],
|
||||
},
|
||||
workspaceFolder: "test-workspace",
|
||||
version: vscodeTestVersion,
|
||||
version: "stable",
|
||||
extensionDevelopmentPath: path.resolve("./"),
|
||||
launchArgs: ["--disable-extensions"],
|
||||
})
|
||||
Vendored
+1
-2
@@ -5,7 +5,6 @@
|
||||
"connor4312.esbuild-problem-matchers",
|
||||
"ms-vscode.extension-test-runner",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"biomejs.biome",
|
||||
"oven.bun-vscode"
|
||||
"biomejs.biome"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+27
-227
@@ -10,23 +10,17 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}/apps/vscode",
|
||||
"--disable-extensions"
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/apps/vscode/dist/**/*.js"
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
}
|
||||
},
|
||||
@@ -35,22 +29,17 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}/apps/vscode"
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/apps/vscode/dist/**/*.js"
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "staging"
|
||||
}
|
||||
},
|
||||
@@ -59,22 +48,17 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}/apps/vscode"
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/apps/vscode/dist/**/*.js"
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "${defaultBuildTask}",
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "local"
|
||||
}
|
||||
},
|
||||
@@ -84,234 +68,50 @@
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--user-data-dir=${workspaceFolder}/apps/vscode/dist/tmp/user",
|
||||
"--user-data-dir=${workspaceFolder}/dist/tmp/user",
|
||||
"--profile-temp",
|
||||
"--sync=off",
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"--extensionDevelopmentPath=${workspaceFolder}/apps/vscode",
|
||||
"${workspaceFolder}/apps/vscode"
|
||||
"--disable-extensions",
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/apps/vscode/dist/**/*.js"
|
||||
"${workspaceFolder}/dist/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "clean-tmp-user",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"postDebugTask": "stop",
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"TEMP_PROFILE": "true",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}/apps/vscode",
|
||||
"DEV_WORKSPACE_FOLDER": "${workspaceFolder}",
|
||||
"CLINE_ENVIRONMENT": "production"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Test Standalone Core Api Server (test:sca-server)",
|
||||
"name": "Run cline-core service",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/apps/vscode/**",
|
||||
"${workspaceFolder}/**",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"cwd": "${workspaceFolder}/dist-standalone",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/apps/vscode/dist/**/*.js",
|
||||
"${workspaceFolder}/apps/vscode/dist-standalone/**/*.js"
|
||||
"${workspaceFolder}/dist-standalone/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "compile-standalone",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": [
|
||||
"tsx"
|
||||
],
|
||||
"program": "scripts/test-standalone-core-api-server.ts",
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"PROTOBUS_PORT": "26040",
|
||||
"HOSTBRIDGE_PORT": "26041",
|
||||
"WORKSPACE_DIR": "${workspaceFolder}/apps/vscode",
|
||||
"E2E_TEST": "true",
|
||||
"CLINE_ENVIRONMENT": "local"
|
||||
// Turns on grpc debug log.
|
||||
//"GRPC_TRACE": "all",
|
||||
//"GRPC_VERBOSITY": "DEBUG",
|
||||
"NODE_PATH": "${workspaceFolder}/dist-standalone/node_modules"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Debug Current Test File",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/apps/vscode/**",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"runtimeExecutable": "npx",
|
||||
"runtimeArgs": [
|
||||
"mocha"
|
||||
],
|
||||
"args": [
|
||||
"--require",
|
||||
"ts-node/register",
|
||||
"--require",
|
||||
"source-map-support/register",
|
||||
"--require",
|
||||
"./src/test/requires.ts",
|
||||
"--exit",
|
||||
"${file}"
|
||||
],
|
||||
"envFile": "${workspaceFolder}/apps/vscode/.env",
|
||||
"env": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.unit-test.json",
|
||||
"NODE_ENV": "test",
|
||||
"IS_DEV": "true",
|
||||
"CLINE_ENVIRONMENT": "local"
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Open Storybook",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"storybook"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/apps/vscode/webview-ui",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"serverReadyAction": {
|
||||
"pattern": "Local:.*http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
},
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch Bun CLI (Prompt)",
|
||||
"type": "bun",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/sdk/apps/cli",
|
||||
"runtime": "bun",
|
||||
"runtimeArgs": [
|
||||
"--conditions=development"
|
||||
],
|
||||
"program": "${workspaceFolder}/sdk/apps/cli/src/index.ts",
|
||||
"args": [
|
||||
"${input:cliPrompt}"
|
||||
],
|
||||
"env": {
|
||||
"CLINE_BUILD_ENV": "development"
|
||||
},
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Launch RPC Server",
|
||||
"type": "bun",
|
||||
"request": "launch",
|
||||
"cwd": "${workspaceFolder}/sdk/apps/cli",
|
||||
"runtime": "bun",
|
||||
"runtimeArgs": [
|
||||
"--conditions=development"
|
||||
],
|
||||
"program": "${workspaceFolder}/sdk/apps/cli/src/index.ts",
|
||||
"args": [
|
||||
"rpc",
|
||||
"start"
|
||||
],
|
||||
"env": {
|
||||
"CLINE_BUILD_ENV": "development",
|
||||
"CLINE_DEBUG_PORT_BASE": "9230"
|
||||
},
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach RPC Runtime (9230)",
|
||||
"type": "bun",
|
||||
"request": "attach",
|
||||
"url": "ws://127.0.0.1:9230",
|
||||
"localRoot": "${workspaceFolder}/sdk",
|
||||
"remoteRoot": "${workspaceFolder}/sdk",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach Hook Worker (9231)",
|
||||
"type": "bun",
|
||||
"request": "attach",
|
||||
"url": "ws://127.0.0.1:9231",
|
||||
"localRoot": "${workspaceFolder}/sdk",
|
||||
"remoteRoot": "${workspaceFolder}/sdk",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach Plugin Sandbox (9232)",
|
||||
"type": "bun",
|
||||
"request": "attach",
|
||||
"url": "ws://127.0.0.1:9232",
|
||||
"localRoot": "${workspaceFolder}/sdk",
|
||||
"remoteRoot": "${workspaceFolder}/sdk",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach Connector Child (9233)",
|
||||
"type": "bun",
|
||||
"request": "attach",
|
||||
"url": "ws://127.0.0.1:9233",
|
||||
"localRoot": "${workspaceFolder}/sdk",
|
||||
"remoteRoot": "${workspaceFolder}/sdk",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Launch RPC Server Debugger",
|
||||
"configurations": [
|
||||
"Launch RPC Server",
|
||||
"Attach RPC Runtime (9230)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Launch CLI Debugger",
|
||||
"configurations": [
|
||||
"Launch Bun CLI (Prompt)",
|
||||
"Attach RPC Runtime (9230)",
|
||||
"Attach Hook Worker (9231)",
|
||||
"Attach Plugin Sandbox (9232)",
|
||||
"Attach Connector Child (9233)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
{
|
||||
"id": "cliPrompt",
|
||||
"type": "promptString",
|
||||
"description": "Prompt to send to the CLI",
|
||||
"default": "hey"
|
||||
"program": "cline-core.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+12
-10
@@ -1,6 +1,5 @@
|
||||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.exclude": {
|
||||
"out": false, // set this to true to hide the "out" folder with the compiled JS files
|
||||
"dist": false // set this to true to hide the "dist" folder with the compiled JS files
|
||||
@@ -17,18 +16,21 @@
|
||||
// Protobuf settings
|
||||
"protoc": {
|
||||
"options": [
|
||||
"--proto_path=apps/vscode/proto"
|
||||
"--proto_path=proto"
|
||||
]
|
||||
},
|
||||
// Enable Lint and format using Biome
|
||||
"biome.enabled": true,
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
"source.removeUnused.biome": "always",
|
||||
"source.removeUnusedImports": "always",
|
||||
"source.organizeImports.biome": "always"
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
// Disable auto-forwarding ports to prevent Simple Browser from opening the Vite dev server
|
||||
"remote.autoForwardPorts": false
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+20
-68
@@ -5,28 +5,24 @@
|
||||
"tasks": [
|
||||
{
|
||||
"label": "compile-standalone",
|
||||
"type": "shell",
|
||||
"command": "npm run compile-standalone",
|
||||
"type": "npm",
|
||||
"script": "compile-standalone",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "npm: protos",
|
||||
"type": "shell",
|
||||
"command": "npm run protos",
|
||||
"type": "npm",
|
||||
"script": "protos",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
@@ -64,8 +60,8 @@
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run build:webview",
|
||||
"type": "npm",
|
||||
"script": "build:webview",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
@@ -78,15 +74,14 @@
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run build:webview:test",
|
||||
"type": "npm",
|
||||
"script": "build:webview:test",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": true,
|
||||
@@ -99,7 +94,6 @@
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"IS_TEST": "true"
|
||||
@@ -107,8 +101,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run dev:webview",
|
||||
"type": "npm",
|
||||
"script": "dev:webview",
|
||||
"group": "build",
|
||||
"problemMatcher": [
|
||||
{
|
||||
@@ -137,15 +131,14 @@
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run watch:esbuild",
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
@@ -176,15 +169,14 @@
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run watch:esbuild:test",
|
||||
"type": "npm",
|
||||
"script": "watch:esbuild:test",
|
||||
"group": "build",
|
||||
"problemMatcher": {
|
||||
"pattern": [
|
||||
@@ -215,7 +207,6 @@
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true",
|
||||
"IS_TEST": "true"
|
||||
@@ -223,8 +214,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run watch:tsc",
|
||||
"type": "npm",
|
||||
"script": "watch:tsc",
|
||||
"group": "build",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
@@ -235,15 +226,11 @@
|
||||
"presentation": {
|
||||
"group": "watch",
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run watch-tests",
|
||||
"label": "npm: watch-tests",
|
||||
"type": "npm",
|
||||
"script": "watch-tests",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"dependsOn": [
|
||||
@@ -253,10 +240,7 @@
|
||||
"reveal": "always",
|
||||
"group": "watchers"
|
||||
},
|
||||
"group": "build",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode"
|
||||
}
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "tasks: watch-tests",
|
||||
@@ -278,39 +262,7 @@
|
||||
"dependsOn": [
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/apps/vscode/dist/tmp/user && mkdir -p ${workspaceFolder}/apps/vscode/dist/tmp/user"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "npm run storybook",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"label": "npm: storybook",
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/apps/vscode",
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "build-sdk",
|
||||
"type": "shell",
|
||||
"command": "bun run build:sdk",
|
||||
"problemMatcher": [
|
||||
"$tsc"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Default
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/
|
||||
dist-standalone/
|
||||
node_modules/
|
||||
src/**
|
||||
standalone/**
|
||||
.gitignore
|
||||
.yarnrc
|
||||
esbuild.js
|
||||
vsc-extension-quickstart.md
|
||||
tsconfig*.json
|
||||
**/.eslintrc.json
|
||||
**/*.map
|
||||
**/*.ts
|
||||
**/.vscode-test.*
|
||||
eslint-rules/**
|
||||
.github/**
|
||||
.husky/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
.gitattributes
|
||||
.prettierignore
|
||||
.husky/
|
||||
.github/
|
||||
eslint-rules/
|
||||
old_docs/
|
||||
evals/
|
||||
.changie.yaml
|
||||
.codespellrc
|
||||
.mocharc.json
|
||||
buf.yaml
|
||||
.changeset/
|
||||
.clinerules/
|
||||
|
||||
# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
|
||||
webview-ui/src/**
|
||||
webview-ui/public/**
|
||||
webview-ui/index.html
|
||||
webview-ui/README.md
|
||||
webview-ui/package.json
|
||||
webview-ui/package-lock.json
|
||||
webview-ui/node_modules/**
|
||||
**/.gitignore
|
||||
|
||||
# Ignore docs
|
||||
docs/**
|
||||
old_docs/**
|
||||
|
||||
# Fix issue where codicons don't get packaged (https://github.com/microsoft/vscode-extension-samples/issues/692)
|
||||
!node_modules/@vscode/codicons/dist/codicon.css
|
||||
!node_modules/@vscode/codicons/dist/codicon.ttf
|
||||
|
||||
# Include default themes JSON files used in getTheme
|
||||
!src/integrations/theme/default-themes/**
|
||||
|
||||
# Include icons
|
||||
!assets/icons/**
|
||||
|
||||
# Ignore E2E build files
|
||||
e2e-build.mjs
|
||||
e2e.vsix
|
||||
test-results/
|
||||
@@ -1 +0,0 @@
|
||||
.gitignore
|
||||
-1071
File diff suppressed because it is too large
Load Diff
+30
-46
@@ -42,27 +42,39 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
```bash
|
||||
code cline
|
||||
```
|
||||
3. Install [bun](https://bun.com)
|
||||
4. Install the necessary dependencies for the extension and webview-gui:
|
||||
3. Install the necessary dependencies for the extension and webview-gui:
|
||||
```bash
|
||||
cd apps/vscode && npm run install:all && cd ../..
|
||||
cd sdk && bun run build && cd ..
|
||||
npm run install:all
|
||||
```
|
||||
5. Generate Protocol Buffer files (required before first build):
|
||||
6. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
|
||||
|
||||
|
||||
|
||||
|
||||
### Creating a Pull Request
|
||||
|
||||
1. Commit your changes.
|
||||
1. Before creating a PR, generate a changeset entry:
|
||||
```bash
|
||||
npm run changeset
|
||||
```
|
||||
This will prompt you for:
|
||||
- Type of change (major, minor, patch)
|
||||
- `major` → breaking changes (1.0.0 → 2.0.0)
|
||||
- `minor` → new features (1.0.0 → 1.1.0)
|
||||
- `patch` → bug fixes (1.0.0 → 1.0.1)
|
||||
- Description of your changes
|
||||
|
||||
2. Push your branch and create a PR on GitHub. Our CI will:
|
||||
2. Commit your changes and the generated `.changeset` file
|
||||
|
||||
3. Push your branch and create a PR on GitHub. Our CI will:
|
||||
- Run tests and checks
|
||||
3. Testing
|
||||
- Run `cd apps/vscode && npm run test` to run tests locally.
|
||||
- Changesetbot will create a comment showing the version impact
|
||||
- When merged to main, changesetbot will create a Version Packages PR
|
||||
- When the Version Packages PR is merged, a new release will be published
|
||||
4. Testing
|
||||
- Run `npm run test` to run tests locally.
|
||||
- Before submitting PR, run `npm run format:fix` to format your code
|
||||
- Run `npm run test:ci` to run tests locally
|
||||
|
||||
### Extension
|
||||
|
||||
@@ -73,12 +85,9 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
- If you dismissed the prompts, you can install them manually from the Extensions panel
|
||||
|
||||
2. **Local Development**
|
||||
- cd into the vscode extension, `cd apps/vscode`
|
||||
- Run `npm run install:all` to install dependencies
|
||||
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
|
||||
- Run `npm run test` to run tests locally
|
||||
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
|
||||
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
|
||||
- Before submitting PR, run `npm run format:fix` to format your code
|
||||
|
||||
3. **Linux-specific Setup**
|
||||
@@ -148,40 +157,15 @@ Anyone can contribute code to Cline, but we ask that you follow these guidelines
|
||||
- Update existing tests if your changes affect them
|
||||
- Include both unit tests and integration tests where appropriate
|
||||
|
||||
**End-to-End (E2E) Testing**
|
||||
|
||||
Cline includes comprehensive E2E tests using Playwright that simulate real user interactions with the extension in VS Code:
|
||||
|
||||
- **Running E2E tests:**
|
||||
```bash
|
||||
npm run test:e2e # Build and run all E2E tests
|
||||
npm run e2e # Run tests without rebuilding
|
||||
npm run test:e2e -- --debug # Run with interactive debugger
|
||||
```
|
||||
|
||||
- **Writing E2E tests:**
|
||||
- Tests are located in `src/test/e2e/`
|
||||
- Use the `e2e` fixture for single-root workspace tests
|
||||
- Use `e2eMultiRoot` fixture for multi-root workspace tests
|
||||
- Follow existing patterns in `auth.test.ts`, `chat.test.ts`, `diff.test.ts`, and `editor.test.ts`
|
||||
- See `src/test/e2e/README.md` for detailed documentation
|
||||
|
||||
- **Debug mode features:**
|
||||
- Interactive Playwright Inspector for step-by-step debugging
|
||||
- Record new interactions and generate test code automatically
|
||||
- Visual VS Code instance for manual testing
|
||||
- Element inspection and selector validation
|
||||
|
||||
- **Test environment:**
|
||||
- Automated VS Code setup with Cline extension loaded
|
||||
- Mock API server for backend testing
|
||||
- Temporary workspaces with test fixtures
|
||||
- Video recording for failed tests
|
||||
4. **Version Management with Changesets**
|
||||
|
||||
4. **Versioning & Changelog Notes**
|
||||
|
||||
- Contributors do not need to create changelog-entry files as part of PRs.
|
||||
- Maintainers handle release versioning and changelog curation during the release process.
|
||||
- Create a changeset for any user-facing changes using `npm run changeset`
|
||||
- Choose the appropriate version bump:
|
||||
- `major` for breaking changes (1.0.0 → 2.0.0)
|
||||
- `minor` for new features (1.0.0 → 1.1.0)
|
||||
- `patch` for bug fixes (1.0.0 → 1.0.1)
|
||||
- Write clear, descriptive changeset messages that explain the impact
|
||||
- Documentation-only changes don't require changesets
|
||||
|
||||
5. **Commit Guidelines**
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 Cline Bot Inc.
|
||||
Copyright 2025 Cline Bot Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<p align="center">
|
||||
<img src="assets/icons/icon.png" width="80" alt="Cline" />
|
||||
</p>
|
||||
<div align="center"><sub>
|
||||
English | <a href="https://github.com/cline/cline/blob/main/locales/es/README.md" target="_blank">Español</a> | <a href="https://github.com/cline/cline/blob/main/locales/de/README.md" target="_blank">Deutsch</a> | <a href="https://github.com/cline/cline/blob/main/locales/ja/README.md" target="_blank">日本語</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-cn/README.md" target="_blank">简体中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-tw/README.md" target="_blank">繁體中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/ko/README.md" target="_blank">한국어</a>
|
||||
</sub></div>
|
||||
|
||||
<h1 align="center">Cline</h1>
|
||||
# Cline – \#1 on OpenRouter
|
||||
|
||||
<p align="center">
|
||||
The open source coding agent in your IDE and terminal.
|
||||
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
<td align="center">
|
||||
<a href="https://docs.cline.bot" target="_blank"><strong>Docs</strong></a>
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev" target="_blank"><strong>Download on VS Marketplace</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://discord.gg/cline" target="_blank"><strong>Discord</strong></a>
|
||||
@@ -26,214 +24,123 @@ The open source coding agent in your IDE and terminal.
|
||||
<a href="https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop" target="_blank"><strong>Feature Requests</strong></a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://cline.bot/join-us" target="_blank"><strong>Join us!</strong></a>
|
||||
<a href="https://docs.cline.bot/getting-started/for-new-coders" target="_blank"><strong>Getting Started</strong></a>
|
||||
</td>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
Meet Cline, an AI assistant that can use your **CLI** a**N**d **E**ditor.
|
||||
|
||||
<br>
|
||||
Thanks to [Claude Sonnet's agentic coding capabilities](https://www.anthropic.com/claude/sonnet), Cline can handle complex software development tasks step-by-step. With tools that let him create & edit files, explore large projects, use the browser, and execute terminal commands (after you grant permission), he can assist you in ways that go beyond code completion or tech support. Cline can even use the Model Context Protocol (MCP) to create new tools and extend his own capabilities. While autonomous AI scripts traditionally run in sandboxed environments, this extension provides a human-in-the-loop GUI to approve every file change and terminal command, providing a safe and accessible way to explore the potential of agentic AI.
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
1. Enter your task and add images to convert mockups into functional apps or fix bugs with screenshots.
|
||||
2. Cline starts by analyzing your file structure & source code ASTs, running regex searches, and reading relevant files to get up to speed in existing projects. By carefully managing what information is added to context, Cline can provide valuable assistance even for large, complex projects without overwhelming the context window.
|
||||
3. Once Cline has the information he needs, he can:
|
||||
- Create and edit files + monitor linter/compiler errors along the way, letting him proactively fix issues like missing imports and syntax errors on his own.
|
||||
- Execute commands directly in your terminal and monitor their output as he works, letting him e.g., react to dev server issues after editing a file.
|
||||
- For web development tasks, Cline can launch the site in a headless browser, click, type, scroll, and capture screenshots + console logs, allowing him to fix runtime errors and visual bugs.
|
||||
4. When a task is completed, Cline will present the result to you with a terminal command like `open -a "Google Chrome" index.html`, which you run with a click of a button.
|
||||
|
||||
### CLI
|
||||
|
||||
Run Cline in your terminal.
|
||||
Interactive chat or fully headless
|
||||
for CI/CD and scripting.
|
||||
|
||||
```
|
||||
npm i -g cline
|
||||
```
|
||||
|
||||
<a href="./apps/cli/README.md">Learn more</a>
|
||||
<br><br>
|
||||
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
|
||||
### Kanban
|
||||
|
||||
Run many agents in parallel from a
|
||||
web-based task board. Each card gets its own
|
||||
worktree, auto-commit, and dependency chains.
|
||||
|
||||
```
|
||||
npm i -g kanban
|
||||
```
|
||||
|
||||
<a href="https://github.com/cline/kanban">Learn more</a>
|
||||
<br><br>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="50%">
|
||||
|
||||
### VS Code Extension
|
||||
|
||||
AI coding assistant in your editor.
|
||||
Create files, run commands, browse the web,
|
||||
and use tools with human-in-the-loop approval.
|
||||
|
||||
<a href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev">Install from VS Marketplace</a>
|
||||
<br><br>
|
||||
|
||||
</td>
|
||||
<td align="center" width="50%">
|
||||
|
||||
### JetBrains Plugin
|
||||
|
||||
The same Cline experience in IntelliJ IDEA,
|
||||
PyCharm, WebStorm, GoLand, and the rest of
|
||||
the JetBrains family.
|
||||
|
||||
<a href="https://plugins.jetbrains.com/plugin/28247-cline">Install from JetBrains Marketplace</a>
|
||||
<br><br>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center">
|
||||
|
||||
### SDK
|
||||
|
||||
Build your own AI agents and integrations powered by the same engine that runs the CLI, Kanban, VS Code extension, and JetBrains plugin. Custom tools, multi-agent teams, connectors, scheduled automations, and more.
|
||||
|
||||
```
|
||||
npm install @cline/sdk
|
||||
```
|
||||
|
||||
<a href="https://docs.cline.bot/cline-sdk/overview">Documentation</a>
|
||||
<br><br>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
> [!TIP]
|
||||
> Use the `CMD/CTRL + Shift + P` shortcut to open the command palette and type "Cline: Open In New Tab" to open the extension as a tab in your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
|
||||
|
||||
---
|
||||
|
||||
## Index
|
||||
<img align="right" width="340" src="https://github.com/user-attachments/assets/3cf21e04-7ce9-4d22-a7b9-ba2c595e88a4">
|
||||
|
||||
| Product | Description | Location | CHANGELOG |
|
||||
|---------|------------|--------------|--------------|
|
||||
| **SDK** | Node.js programmatic agent API and extension exports. | [`sdk/`](https://github.com/cline/cline/tree/main/sdk) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/sdk/CHANGELOG.md) |
|
||||
| **CLI** | Terminal UI, headless mode, shell commands, and CLI-specific flows. | [`apps/cli/`](https://github.com/cline/cline/tree/main/apps/cli) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/apps/cli/CHANGELOG.md) |
|
||||
| **VS Code Extension** | The Marketplace extension and extension host integration. | [`/`](https://github.com/cline/cline/tree/main) (WIP migrating) | [CHANGELOG.md](https://github.com/cline/cline/blob/main/CHANGELOG.md) |
|
||||
| **JetBrains Plugin** | JetBrains-hosted client that talks to the shared agent core. | Currently we are not open-sourcing JetBrains plugins | - |
|
||||
| **Kanban** | Web-based multi-agent task board. | [`cline/kanban`](https://github.com/cline/kanban) | [CHANGELOG.md](https://github.com/cline/kanban/blob/main/CHANGELOG.md) |
|
||||
| **Docs site** | Public documentation pages. | [`docs/`](https://docs.cline.bot/) | - |
|
||||
### Use any API and Model
|
||||
|
||||
## Edits Code Across Your Project
|
||||
Cline supports API providers like OpenRouter, Anthropic, OpenAI, Google Gemini, AWS Bedrock, Azure, GCP Vertex, Cerebras and Groq. You can also configure any OpenAI compatible API, or use a local model through LM Studio/Ollama. If you're using OpenRouter, the extension fetches their latest model list, allowing you to use the newest models as soon as they're available.
|
||||
|
||||
Cline reads your project structure, understands the relationships between files, and makes coordinated changes across your codebase. It monitors linter and compiler errors as it works, fixing issues like missing imports, type mismatches, and syntax errors before you even see them. In VS Code and JetBrains, every edit shows up as a diff you can review, modify, or revert. All changes are tracked with checkpoints, so you can easily undo the agent's work.
|
||||
The extension also keeps track of total tokens and API usage cost for the entire task loop and individual requests, keeping you informed of spend every step of the way.
|
||||
|
||||
## Runs Bash Commands
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
Cline executes commands directly in your terminal and watches the output in real time. Install packages, run build scripts, execute tests, deploy applications, manage databases. For long-running processes like dev servers, Cline continues working in the background and reacts to new output as it appears, catching compile errors, test failures, and server crashes as they happen.
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
## Plan and Act
|
||||
<img align="left" width="370" src="https://github.com/user-attachments/assets/81be79a8-1fdb-4028-9129-5fe055e01e76">
|
||||
|
||||
Toggle between Plan mode and Act mode. In Plan mode, Cline explores your codebase, asks clarifying questions, and lays out a strategy. Once you're aligned, switch to Act mode and Cline executes the plan. Every file edit and terminal command requires your approval, so you stay in control of what actually changes. Or toggle auto-approve and let Cline run autonomously.
|
||||
### Run Commands in Terminal
|
||||
|
||||
## Rules and Skills
|
||||
Thanks to the new [shell integration updates in VSCode v1.93](https://code.visualstudio.com/updates/v1_93#_terminal-shell-integration-api), Cline can execute commands directly in your terminal and receive the output. This allows him to perform a wide range of tasks, from installing packages and running build scripts to deploying applications, managing databases, and executing tests, all while adapting to your dev environment & toolchain to get the job done right.
|
||||
|
||||
Define project-specific rules in `.clinerules` files that guide how Cline works in your codebase: coding standards, architecture conventions, deployment procedures, testing requirements. Rules are picked up automatically by the CLI, VS Code extension, and JetBrains plugin. Use skills to let the model load specific rules when needed.
|
||||
For long running processes like dev servers, use the "Proceed While Running" button to let Cline continue in the task while the command runs in the background. As Cline works he’ll be notified of any new terminal output along the way, letting him react to issues that may come up, such as compile-time errors when editing files.
|
||||
|
||||
## Works With Every Model
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
Cline is not locked to a single AI provider. Use whichever model fits your workflow:
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
| Provider | Models |
|
||||
|----------|--------|
|
||||
| Anthropic | Claude Opus, Sonnet, Haiku |
|
||||
| OpenAI | GPT series model |
|
||||
| Google | Gemini series model |
|
||||
| OpenRouter | 200+ models from any provider |
|
||||
| Vercel AI Gateway | Models through Vercel AI Gateway |
|
||||
| AWS Bedrock | Claude, Llama, and more |
|
||||
| Azure / GCP Vertex | All hosted models |
|
||||
| Cerebras / Groq | Fast inference models |
|
||||
| Ollama / LM Studio | Run local models on your machine |
|
||||
| Any OpenAI-compatible API | Self-hosted or third-party endpoints |
|
||||
<img align="right" width="400" src="https://github.com/user-attachments/assets/c5977833-d9b8-491e-90f9-05f9cd38c588">
|
||||
|
||||
## Extend With Plugins or MCP Servers
|
||||
### Create and Edit Files
|
||||
|
||||
Extend Cline's capabilities with plugins. Using the SDK, register tools and lifecycle hooks programmatically through the plugin system for logging, auditing, policy enforcement, or adding domain-specific capabilities. Simple plugin example below.
|
||||
Cline can create and edit files directly in your editor, presenting you a diff view of the changes. You can edit or revert Cline's changes directly in the diff view editor, or provide feedback in chat until you're satisfied with the result. Cline also monitors linter/compiler errors (missing imports, syntax errors, etc.) so he can fix issues that come up along the way on his own.
|
||||
|
||||
```typescript
|
||||
import { Agent, createTool } from "@cline/sdk"
|
||||
All changes made by Cline are recorded in your file's Timeline, providing an easy way to track and revert modifications if needed.
|
||||
|
||||
const deployTool = createTool({
|
||||
name: "deploy",
|
||||
description: "Deploy the current branch to staging.",
|
||||
inputSchema: { type: "object", properties: { env: { type: "string" } }, required: ["env"] },
|
||||
execute: async (input) => {
|
||||
// your deployment logic
|
||||
},
|
||||
})
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
const agent = new Agent({ tools: [deployTool], /* ... */ })
|
||||
```
|
||||
...or use [MCP servers](https://github.com/modelcontextprotocol) to connect to databases, query APIs, manage cloud infrastructure, and interact with external systems. Use [community-built servers](https://github.com/modelcontextprotocol/servers) or ask Cline to create custom tools on the fly. In the CLI, manage servers with `cline mcp`.
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
## Multi-Agent Teams
|
||||
<img align="left" width="370" src="https://github.com/user-attachments/assets/bc2e85ba-dfeb-4fe6-9942-7cfc4703cbe5">
|
||||
|
||||
Coordinate multiple agents working together on complex tasks. A coordinator agent breaks the work into subtasks and delegates to specialist agents, each with their own tools and context. Team state persists across sessions so you can pick up where you left off.
|
||||
### Use the Browser
|
||||
|
||||
```bash
|
||||
cline --team-name auth-sprint "Plan and implement user authentication with tests"
|
||||
```
|
||||
With Claude Sonnet's new [Computer Use](https://www.anthropic.com/news/3-5-models-and-computer-use) capability, Cline can launch a browser, click elements, type text, and scroll, capturing screenshots and console logs at each step. This allows for interactive debugging, end-to-end testing, and even general web use! This gives him autonomy to fixing visual bugs and runtime issues without you needing to handhold and copy-pasting error logs yourself.
|
||||
|
||||
## Scheduled Agents
|
||||
Try asking Cline to "test the app", and watch as he runs a command like `npm run dev`, launches your locally running dev server in a browser, and performs a series of tests to confirm that everything works. [See a demo here.](https://x.com/sdrzn/status/1850880547825823989)
|
||||
|
||||
Run agents on cron schedules for recurring automations. Daily PR summaries, weekly dependency checks, codebase health reports. Schedules persist across restarts and run independently of any terminal session.
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
```bash
|
||||
cline schedule create "PR summary" \
|
||||
--cron "0 9 * * MON-FRI" \
|
||||
--prompt "List all open PRs and their review status" \
|
||||
--workspace /path/to/repo
|
||||
```
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
## Connect to Slack, Telegram, Discord, and More
|
||||
<img align="right" width="350" src="https://github.com/user-attachments/assets/ac0efa14-5c1f-4c26-a42d-9d7c56f5fadd">
|
||||
|
||||
Chat with your agent from any messaging platform: Telegram, Slack, Discord, Google Chat, WhatsApp, and Linear. Each conversation thread maps to an agent session with full context. Set up access control to restrict who can interact with your agent.
|
||||
### "add a tool that..."
|
||||
|
||||
```bash
|
||||
# Connect to Telegram
|
||||
cline connect telegram -k $BOT_TOKEN
|
||||
# Connect to Slack through webhook
|
||||
cline connect slack --bot-token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
|
||||
# Connect to Slack using socket mode
|
||||
cline connect slack --bot-token $SLACK_TOKEN --app-token $SLACK_APP_TOKEN
|
||||
```
|
||||
Thanks to the [Model Context Protocol](https://github.com/modelcontextprotocol), Cline can extend his capabilities through custom tools. While you can use [community-made servers](https://github.com/modelcontextprotocol/servers), Cline can instead create and install tools tailored to your specific workflow. Just ask Cline to "add a tool" and he will handle everything, from creating a new MCP server to installing it into the extension. These custom tools then become part of Cline's toolkit, ready to use in future tasks.
|
||||
|
||||
## Headless CLI for CI/CD
|
||||
- "add a tool that fetches Jira tickets": Retrieve ticket ACs and put Cline to work
|
||||
- "add a tool that manages AWS EC2s": Check server metrics and scale instances up or down
|
||||
- "add a tool that pulls the latest PagerDuty incidents": Fetch details and ask Cline to fix bugs
|
||||
|
||||
Run Cline with zero interaction for scripting and automation. Pipe input, get JSON output, chain commands, integrate into CI/CD pipelines.
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
```bash
|
||||
cline "Run tests and fix any failures"
|
||||
git diff origin/main | cline "Review these changes for issues"
|
||||
cline --json "List all TODO comments" | jq -r 'select(.type == "agent_event" and .event.text) | .event.text'
|
||||
```
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="left" width="360" src="https://github.com/user-attachments/assets/7fdf41e6-281a-4b4b-ac19-020b838b6970">
|
||||
|
||||
### Add Context
|
||||
|
||||
**`@url`:** Paste in a URL for the extension to fetch and convert to markdown, useful when you want to give Cline the latest docs
|
||||
|
||||
**`@problems`:** Add workspace errors and warnings ('Problems' panel) for Cline to fix
|
||||
|
||||
**`@file`:** Adds a file's contents so you don't have to waste API requests approving read file (+ type to search files)
|
||||
|
||||
**`@folder`:** Adds folder's files all at once to speed up your workflow even more
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
<img align="right" width="350" src="https://github.com/user-attachments/assets/140c8606-d3bf-41b9-9a1f-4dbf0d4c90cb">
|
||||
|
||||
### Checkpoints: Compare and Restore
|
||||
|
||||
As Cline works through a task, the extension takes a snapshot of your workspace at each step. You can use the 'Compare' button to see a diff between the snapshot and your current workspace, and the 'Restore' button to roll back to that point.
|
||||
|
||||
For example, when working with a local web server, you can use 'Restore Workspace Only' to quickly test different versions of your app, then use 'Restore Task and Workspace' when you find the version you want to continue building from. This lets you safely explore different approaches without losing progress.
|
||||
|
||||
<!-- Transparent pixel to create line break after floating image -->
|
||||
|
||||
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
|
||||
|
||||
## Contributing
|
||||
|
||||
Start with the [Contributing Guide](CONTRIBUTING.md). Join our [Discord](https://discord.gg/cline) and head to the `#contributors` channel to connect with other contributors. Check our [careers page](https://cline.bot/join-us) for full-time roles.
|
||||
To contribute to the project, start with our [Contributing Guide](CONTRIBUTING.md) to learn the basics. You can also join our [Discord](https://discord.gg/cline) to chat with other contributors in the `#contributors` channel. If you're looking for full-time work, check out our open positions on our [careers page](https://cline.bot/join-us)!
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0 © 2026 Cline Bot Inc.](./LICENSE)
|
||||
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We actively patch only the most recent minor release of Cline. Older versions receive fixes at our discretion.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please submit your report through our [Bugcrowd Vulnerability Disclosure Program](https://bugcrowd.com/engagements/clinebot-vdp-ess). Bugcrowd will manage communication and triage on our behalf.
|
||||
|
||||
When reporting, please include:
|
||||
|
||||
- A short summary of the issue
|
||||
- Steps to reproduce or a proof of concept
|
||||
- Any logs, stack traces, or screenshots that might help us understand the problem
|
||||
|
||||
Please keep the details private until a resolution has been reached.
|
||||
|
||||
## Escalation
|
||||
|
||||
If you are unable to submit through Bugcrowd, you may send an email to security@cline.bot.
|
||||
|
||||
Thank you for helping us keep Cline users safe.
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.5/schema.json",
|
||||
"root": false,
|
||||
"extends": ["../sdk/biome.json"],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"a11y": {
|
||||
"noStaticElementInteractions": "warn"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
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/<framework>/` 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/<concept>/` 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/<category>.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 -> <framework>/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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user