mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
348 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85a7b7dbaf | |||
| 852f307268 | |||
| 4e3fe004f4 | |||
| 2b63eed85e | |||
| 2ffdc50ea1 | |||
| 74808431e5 | |||
| 7a523fbaf6 | |||
| c22ea39dc1 | |||
| c9f23076c2 | |||
| a5f6c1d732 | |||
| 3c37a160ac | |||
| 5c3294051f | |||
| 4c2f28f2af | |||
| 6e016298cb | |||
| 0cd7bebfba | |||
| 363aac61fb | |||
| eeb1cc7da8 | |||
| f760f13de5 | |||
| dd52a4a39c | |||
| 639edb5db6 | |||
| 3eac9b04de | |||
| 09692d7d3a | |||
| c81fa0a9d6 | |||
| 326c9c9f99 | |||
| a4518b90c2 | |||
| 79f4d938e6 | |||
| 37152329cd | |||
| 1332d1d70d | |||
| 6a0d92d683 | |||
| e761a8c252 | |||
| c26d0a076d | |||
| c037619b90 | |||
| a575a76e8f | |||
| b1d15d4fe7 | |||
| a0708e57ee | |||
| ab5796fa72 | |||
| b15c364a62 | |||
| 29dcc4e1e1 | |||
| 126d066893 | |||
| fa3e095a79 | |||
| 4033c83b51 | |||
| 0b7ea86e9b | |||
| 1d9a0b5986 | |||
| c47ffe2861 | |||
| e85d918816 | |||
| 49be10ead8 | |||
| aa9573fb0a | |||
| b913e47332 | |||
| 5be7a1b3cf | |||
| c2e91aa9c9 | |||
| 68b93fcbea | |||
| 0b0e8c36cb | |||
| b0bd0e3974 | |||
| 42b7a1e450 | |||
| 14ccf33d25 | |||
| d9a340523c | |||
| 87b3e79b90 | |||
| 60f2e85fc7 | |||
| 1826d98019 | |||
| af69b30a36 | |||
| e62fbf6b0c | |||
| 64254fc97a | |||
| c312c4aef6 | |||
| fab49e810b | |||
| 2a20523e16 | |||
| 06585821d1 | |||
| 9e802b11da | |||
| afb77c5a8d | |||
| 0a4811222f | |||
| b4ce378e4b | |||
| 2f60a898af | |||
| 8ffd82eda3 | |||
| 81276fdf85 | |||
| 164e11aae1 | |||
| 9792f174b1 | |||
| a590200c64 | |||
| 297a45d73a | |||
| e84de0ab3c | |||
| 515cb81439 | |||
| 550428eabd | |||
| 22c22a1cfc | |||
| 8202479cec | |||
| bcbaa4518d | |||
| 9d799643ba | |||
| 6271c5da37 | |||
| 89aeb3db3d | |||
| 0caeea1b37 | |||
| 852a7c9198 | |||
| c4ef472aeb | |||
| e22c457d19 | |||
| c15287ace0 | |||
| d30f54a89c | |||
| 56b913d951 | |||
| 55a30e0ffa | |||
| a017f3dfd3 | |||
| 41ebe7c9d1 | |||
| 4d11f0d2fa | |||
| 4baa2474eb | |||
| c94e2cf913 | |||
| dbedc6cfaa | |||
| 2ac568e649 | |||
| b13d0e75ea | |||
| 4d395deefd | |||
| 834a5b1df2 | |||
| 3089233298 | |||
| f2b7347a5c | |||
| 04bfef75cf | |||
| 716e8f236b | |||
| f2ddab71f1 | |||
| 0b56a45a65 | |||
| d6ebd2438a | |||
| 4c07c7e5c5 | |||
| ab66a5fd93 | |||
| bd1d6159fc | |||
| 7f2d28716f | |||
| 653727db2a | |||
| d3c2f1878d | |||
| ba92be9401 | |||
| 66eb5a62ba | |||
| 499ee22b3b | |||
| 7abeae5019 | |||
| accf47cb52 | |||
| 556d3e6f79 | |||
| 9e35048db2 | |||
| 9b0f2b82ef | |||
| c2c23054b9 | |||
| 3baaa5c8b4 | |||
| abafcc7290 | |||
| d82aa0add9 | |||
| abe4721a0b | |||
| 60d55b69a8 | |||
| d18e0271d3 | |||
| af71f9da90 | |||
| 2a1c8826aa | |||
| 9a54f2d246 | |||
| d928d58a40 | |||
| 31859b5fda | |||
| 0d5d89e8c7 | |||
| af34451eec | |||
| 027a4f6386 | |||
| 49642882c5 | |||
| 21ed6bc432 | |||
| da2689f885 | |||
| 5049326f02 | |||
| b02ce46a57 | |||
| de974737c8 | |||
| d072156e9a | |||
| 4939309a09 | |||
| 1a07ca7906 | |||
| c1eefbad3f | |||
| 1bfdce9b84 | |||
| b002cdacdb | |||
| cf4005b25e | |||
| 1494d145d5 | |||
| 1ab4b3cc24 | |||
| 535b653228 | |||
| 1335fa5452 | |||
| b2a4395f71 | |||
| ae34a3a8c5 | |||
| 0fb4a6c7e9 | |||
| 855db7d8d8 | |||
| bb375b78ca | |||
| 31af254f0a | |||
| 2745cdd54b | |||
| c94c6fd8f1 | |||
| a87664318e | |||
| ba70718c83 | |||
| cf8dd1c150 | |||
| e714c74ec4 | |||
| a6f8b33895 | |||
| 02abbcf045 | |||
| 8ca2706cca | |||
| e747d211e6 | |||
| 19aa81a1cc | |||
| f5f3654966 | |||
| 344b99988b | |||
| 7d3c39bcdb | |||
| 33c1692f8a | |||
| 3be3ffae4c | |||
| 87c6708fbe | |||
| b296706bc0 | |||
| 7fdc82ae23 | |||
| 246ed87350 | |||
| a28465151f | |||
| f1967bc3a1 | |||
| ccc83d6728 | |||
| c151d719c8 | |||
| cc25833963 | |||
| 2ef3b7cf0f | |||
| 19d7bd5198 | |||
| 121066f938 | |||
| f9b88318ea | |||
| 1d64f64f43 | |||
| 766e2e6a25 | |||
| 8bd7260350 | |||
| a1f4e8b9d4 | |||
| aa3e0860cd | |||
| 67bc9932e5 | |||
| ba98b44504 | |||
| fdeb639f75 | |||
| 76410b42b0 | |||
| e4e347a8a4 | |||
| 59e7c2b7b9 | |||
| 5371377b21 | |||
| 5a444dc30a | |||
| 34c48264c0 | |||
| daf14ef181 | |||
| f1bf9b3f90 | |||
| 6e20047c3e | |||
| 1d4fe88bec | |||
| 74dbbb9d6b | |||
| 3073cf40be | |||
| 50056768d6 | |||
| e7cd32b830 | |||
| d983b451e1 | |||
| bb32aac8e7 | |||
| e6d5f55fb2 | |||
| 93f60ebe5b | |||
| a3e000d5dd | |||
| 0076be531d | |||
| 9de4fc2a2f | |||
| 8a4b87a133 | |||
| 571480e3b6 | |||
| 5c2c264a1d | |||
| 04549cf1e3 | |||
| f2a93b6201 | |||
| 2f09b994dd | |||
| 8aac67bf75 | |||
| 10422dd3fc | |||
| 6191c681ce | |||
| 9ab7b41c24 | |||
| bddfb93b38 | |||
| a219eb8000 | |||
| 515b4e5d6a | |||
| aaa3846b82 | |||
| 8229dcfe88 | |||
| b4ea1a17a5 | |||
| e263c74325 | |||
| 08ca115c66 | |||
| 68ffe20b7d | |||
| 4517408537 | |||
| 42c947afd4 | |||
| d4736afce2 | |||
| b00053ff16 | |||
| 2ab8fcb54f | |||
| e697c12e94 | |||
| 64dcc3fc89 | |||
| 8215d3fdb9 | |||
| 59260cefab | |||
| a767f844b9 | |||
| bb7c3556d6 | |||
| 8b80c337ae | |||
| d0cac53d5b | |||
| 8aedc90214 | |||
| 0c118086df | |||
| 261a7dd505 | |||
| 9abf2f567a | |||
| 42de7c81b4 | |||
| 06b710f62b | |||
| ea9b8fe0b1 | |||
| 05213f2a71 | |||
| d5bad1357d | |||
| a4b1549dac | |||
| e015ce94c0 | |||
| be076cf407 | |||
| 580db36476 | |||
| 8f8b98bb58 | |||
| 36022438cb | |||
| c7afb61e28 | |||
| dfd113a6e5 | |||
| 3698d2356c | |||
| ff20c4addc | |||
| 8eeeabb966 | |||
| 9b1dc5bd92 | |||
| 1cfff0a45f | |||
| e7d00dec2d | |||
| 2c5748ccfd | |||
| 5196adce33 | |||
| d95d86f329 | |||
| f6eb3aa386 | |||
| 5c4b9e54c2 | |||
| 29a1d08685 | |||
| 9664ddd106 | |||
| 3c1327b115 | |||
| bb993e4a9a | |||
| 473b3d0204 | |||
| 7e68614631 | |||
| a19e9907d5 | |||
| 19f74cac01 | |||
| b877abc708 | |||
| c90e64e763 | |||
| e02e1eca7f | |||
| f8925fb8fd | |||
| ac81aeaf4e | |||
| f12b5a1573 | |||
| a66b57ef51 | |||
| 944ed41f4a | |||
| 970e941e57 | |||
| 45abe977e4 | |||
| 5755b30bce | |||
| 5fde21dffd | |||
| cf9f2a8630 | |||
| 5a3416ff09 | |||
| 826b2b1276 | |||
| 8da38b2a2e | |||
| cb121170cb | |||
| e9d2d344c6 | |||
| 69fb954a6c | |||
| e9e616e317 | |||
| 545ac29e07 | |||
| d38489aebc | |||
| 8d47026640 | |||
| 268cd5c527 | |||
| c7c4e43322 | |||
| aae9d432fd | |||
| 604dbd7bb0 | |||
| 062a32f93d | |||
| 535b29f465 | |||
| a8027dc570 | |||
| 978a8a0aa6 | |||
| 0cd462a414 | |||
| 65dbd85a92 | |||
| ee1bb2f788 | |||
| 6f69ffb16f | |||
| f91769bda7 | |||
| 7692adacf5 | |||
| a98faf5af4 | |||
| e3f4ce618f | |||
| dcf519d2f7 | |||
| 3ef4aea0f7 | |||
| a820026e0b | |||
| 29d1b0507c | |||
| 929d13a4dd | |||
| c729e8c7c6 | |||
| 737452b2b1 | |||
| ba6a72cf15 | |||
| 65a0c35163 | |||
| 0c8e02c6e4 | |||
| b636018ef5 | |||
| cfc6b0d7f5 | |||
| f116a6323d | |||
| 46d3b2a3ed | |||
| 9679917532 | |||
| 89bf81f7f6 | |||
| afe01df8b4 | |||
| eb1325686e | |||
| 7c7962ce0f | |||
| 0707df2205 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Add DeepSeek 3.2 to native tool calling allow list
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Prevent simultaneuos refreshes when restoring auth info
|
||||
@@ -1,54 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# PostToolUse Hook Example
|
||||
#
|
||||
# This hook runs AFTER a tool is executed. It can:
|
||||
# 1. Observe tool results and outcomes
|
||||
# 2. Add context for FUTURE tool uses via contextModification
|
||||
# 3. Log or track tool usage patterns
|
||||
#
|
||||
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
|
||||
# The tool has already completed when this hook runs.
|
||||
|
||||
# Read the hook input (JSON via stdin)
|
||||
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
# Extract tool information
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName // "unknown"')
|
||||
parameters=$(echo "$input" | jq -r '.postToolUse.parameters // {}')
|
||||
result=$(echo "$input" | jq -r '.postToolUse.result // ""')
|
||||
success=$(echo "$input" | jq -r '.postToolUse.success // false')
|
||||
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs // 0')
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
# Example 1: Learning from file operations
|
||||
# Track successful file creations to build context about project structure
|
||||
# if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
|
||||
# path=$(echo "$parameters" | jq -r '.path // ""')
|
||||
# cat <<EOF
|
||||
# {
|
||||
# "shouldContinue": true,
|
||||
# "contextModification": "FILE_OPERATIONS: Successfully created '$path'. Future operations should maintain consistency with this file's patterns and structure."
|
||||
# }
|
||||
# EOF
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
# Example 2: Performance monitoring
|
||||
# Warn about slow operations
|
||||
# if [[ "$execution_time" -gt 5000 ]]; then
|
||||
# cat <<EOF
|
||||
# {
|
||||
# "shouldContinue": true,
|
||||
# "contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms to complete. Consider optimizing future similar operations or breaking them into smaller steps."
|
||||
# }
|
||||
# EOF
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
# Example 3: Context injection for future tool uses
|
||||
# The context will be available in the NEXT API request
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": true,
|
||||
"contextModification": "TOOL_RESULT: The tool '$tool_name' completed with success=$success. Consider validating the results before proceeding to the next step."
|
||||
"cancel": false,
|
||||
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,42 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse Hook Example
|
||||
#
|
||||
# This hook runs BEFORE a tool is executed. It can:
|
||||
# 1. Block execution by returning {"shouldContinue": false}
|
||||
# 2. Add context for FUTURE tool uses via contextModification
|
||||
# 3. Validate tool parameters
|
||||
#
|
||||
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
|
||||
# The tool parameters are already determined when this hook runs.
|
||||
|
||||
# Read the hook input (JSON via stdin)
|
||||
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
# Extract tool information
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName // "unknown"')
|
||||
parameters=$(echo "$input" | jq -r '.preToolUse.parameters // {}')
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
# Example 1: Validation - Block invalid operations
|
||||
# Uncomment to prevent creating .js files in a TypeScript project
|
||||
# if [[ "$tool_name" == "write_to_file" ]]; then
|
||||
# path=$(echo "$parameters" | jq -r '.path // ""')
|
||||
# if [[ "$path" == *.js ]]; then
|
||||
# cat <<EOF
|
||||
# {
|
||||
# "shouldContinue": false,
|
||||
# "errorMessage": "VALIDATION FAILED: Cannot create .js files in TypeScript project. Please use .ts extension instead.",
|
||||
# "contextModification": "WORKSPACE_RULES: This is a strict TypeScript project. All new files must use .ts or .tsx extensions."
|
||||
# }
|
||||
# EOF
|
||||
# exit 0
|
||||
# fi
|
||||
# fi
|
||||
|
||||
# Example 2: Context injection for future tool uses
|
||||
# The context will be available in the NEXT API request after this tool completes
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": true,
|
||||
"contextModification": "WORKSPACE_RULES: [For future tool uses] This is a TypeScript React project. When creating files, use .ts/.tsx extensions and include detailed comments explaining the purpose and usage of each function."
|
||||
"cancel": false,
|
||||
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PreToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
|
||||
+124
-76
@@ -3,8 +3,8 @@
|
||||
## 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/Rules/Hooks/` (applies to all workspaces)
|
||||
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to specific workspace)
|
||||
- **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.
|
||||
|
||||
@@ -17,17 +17,54 @@ Hooks run automatically when enabled.
|
||||
|
||||
## 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/Rules/Hooks/PreToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PreToolUse` (all platforms)
|
||||
- **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/Rules/Hooks/PostToolUse` (all platforms)
|
||||
- **Workspace Location**: `.clinerules/hooks/PostToolUse` (all platforms)
|
||||
- **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
|
||||
|
||||
@@ -37,13 +74,12 @@ Cline uses a git-style approach for hooks that works consistently across all pla
|
||||
- **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**: No special permissions needed - hooks are executed through the shell
|
||||
- **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
|
||||
- On Windows: Shell execution handles shebang interpretation
|
||||
|
||||
This means:
|
||||
- ✅ Same hook script works on all platforms
|
||||
@@ -55,16 +91,10 @@ This means:
|
||||
**On Unix/Linux/macOS:**
|
||||
```bash
|
||||
# Create hook file
|
||||
nano ~/Documents/Cline/Rules/Hooks/PreToolUse
|
||||
nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
|
||||
# Make executable
|
||||
chmod +x ~/Documents/Cline/Rules/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
**On Windows:**
|
||||
```batch
|
||||
REM Create hook file (note: no file extension)
|
||||
notepad %USERPROFILE%\Documents\Cline\Rules\Hooks\PreToolUse
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
## Context Injection Timing
|
||||
@@ -107,11 +137,46 @@ All hooks receive:
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PreToolUse" | "PostToolUse",
|
||||
"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": {}
|
||||
@@ -122,6 +187,11 @@ All hooks receive:
|
||||
"result": "string",
|
||||
"success": boolean,
|
||||
"executionTimeMs": number
|
||||
},
|
||||
"preCompact": { // Only for PreCompact
|
||||
"contextSize": number,
|
||||
"messagesToCompact": number,
|
||||
"compactionStrategy": "string"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -131,38 +201,21 @@ All hooks receive:
|
||||
All hooks must return:
|
||||
```json
|
||||
{
|
||||
"shouldContinue": boolean, // Required: Allow or block execution
|
||||
"contextModification": "string", // Optional: Context for future tool uses
|
||||
"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
|
||||
}
|
||||
```
|
||||
|
||||
## Context Modification Format
|
||||
|
||||
Use structured prefixes to help the AI understand context type:
|
||||
|
||||
- `WORKSPACE_RULES:` - Project conventions and requirements
|
||||
- `FILE_OPERATIONS:` - File creation/modification patterns
|
||||
- `TOOL_RESULT:` - Outcomes of tool executions
|
||||
- `PERFORMANCE:` - Performance concerns
|
||||
- `VALIDATION:` - Validation results
|
||||
- Custom prefixes as needed
|
||||
|
||||
Example:
|
||||
```bash
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": true,
|
||||
"contextModification": "WORKSPACE_RULES: This is a TypeScript project. All new files must use .ts or .tsx extensions."
|
||||
}
|
||||
EOF
|
||||
```
|
||||
**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
|
||||
- **Context Size**: Context modifications are limited to 50KB
|
||||
- **Error Handling**: Unexpected file system errors are propagated; expected errors (file not found, permission denied) are handled silently
|
||||
- **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
|
||||
|
||||
@@ -177,15 +230,15 @@ path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
|
||||
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": false,
|
||||
"cancel": true,
|
||||
"errorMessage": "Cannot create .js files in TypeScript project",
|
||||
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
|
||||
"contextModification": "Use .ts/.tsx extensions only"
|
||||
}
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
### 2. Context Building - Learn from Operations
|
||||
@@ -200,12 +253,12 @@ path=$(echo "$input" | jq -r '.postToolUse.parameters.path // ""')
|
||||
if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": true,
|
||||
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
"cancel": false,
|
||||
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
@@ -220,12 +273,12 @@ tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
if [[ "$execution_time" -gt 5000 ]]; then
|
||||
cat <<EOF
|
||||
{
|
||||
"shouldContinue": true,
|
||||
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
"cancel": false,
|
||||
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
|
||||
}
|
||||
EOF
|
||||
else
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
@@ -239,7 +292,7 @@ input=$(cat)
|
||||
echo "$input" >> ~/.cline/hook-logs/tool-usage.jsonl
|
||||
|
||||
# Allow execution
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
## Global vs Workspace Hooks
|
||||
@@ -247,44 +300,40 @@ echo '{"shouldContinue": true}'
|
||||
Cline supports two levels of hooks:
|
||||
|
||||
### Global Hooks
|
||||
- **Location**: `~/Documents/Cline/Rules/Hooks/` (macOS/Linux) or `%USERPROFILE%\Documents\Cline\Rules\Hooks\` (Windows)
|
||||
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
|
||||
- **Scope**: Apply to ALL workspaces and projects
|
||||
- **Use Case**: Organization-wide policies, personal preferences, universal validations
|
||||
- **Priority**: Execute FIRST, before workspace hooks
|
||||
- **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**: Execute AFTER global hooks
|
||||
- **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 (PreToolUse or PostToolUse) are executed
|
||||
- **Execution order is not guaranteed** - hooks may run concurrently
|
||||
- If ALL hooks allow execution (`shouldContinue: true`), the tool proceeds
|
||||
- If ANY hook blocks (`shouldContinue: false`), execution is blocked
|
||||
- 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:**
|
||||
- `shouldContinue`: Must be `true` from ALL hooks for execution to proceed
|
||||
- `contextModification`: All context strings are concatenated
|
||||
- `errorMessage`: All error messages are concatenated
|
||||
- `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/Rules/Hooks/`
|
||||
- Windows: `%USERPROFILE%\Documents\Cline\Rules\Hooks\`
|
||||
- macOS/Linux: `~/Documents/Cline/Hooks/`
|
||||
|
||||
2. Add your hook script:
|
||||
```bash
|
||||
# Unix/Linux/macOS
|
||||
nano ~/Documents/Cline/Rules/Hooks/PreToolUse
|
||||
chmod +x ~/Documents/Cline/Rules/Hooks/PreToolUse
|
||||
|
||||
# Windows
|
||||
notepad %USERPROFILE%\Documents\Cline\Rules\Hooks\PreToolUse
|
||||
nano ~/Documents/Cline/Hooks/PreToolUse
|
||||
chmod +x ~/Documents/Cline/Hooks/PreToolUse
|
||||
```
|
||||
|
||||
3. Enable hooks in Cline settings
|
||||
@@ -294,18 +343,18 @@ When multiple hooks exist (global and/or workspace):
|
||||
**Global Hook** (applies to all projects):
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# ~/Documents/Cline/Rules/Hooks/PreToolUse
|
||||
# ~/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 '{"shouldContinue": false, "errorMessage": "Global policy: Cannot modify package.json"}'
|
||||
echo '{"cancel": true, "errorMessage": "Global policy: Cannot modify package.json"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
**Workspace Hook** (applies to specific project):
|
||||
@@ -318,11 +367,11 @@ 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 '{"shouldContinue": false, "errorMessage": "Project rule: Use .ts files only"}'
|
||||
echo '{"cancel": true, "errorMessage": "Project rule: Use .ts files only"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"shouldContinue": true}'
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
**All hooks must allow execution for the tool to proceed.** Hooks may execute concurrently.
|
||||
@@ -331,7 +380,7 @@ echo '{"shouldContinue": true}'
|
||||
|
||||
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:
|
||||
|
||||
- **shouldContinue**: If ANY hook returns false, execution is blocked
|
||||
- **cancel**: If ANY hook returns `true`, execution is blocked
|
||||
- **contextModification**: All context modifications are concatenated
|
||||
- **errorMessage**: All error messages are concatenated
|
||||
|
||||
@@ -352,7 +401,6 @@ If you have multiple workspace roots, you can place hooks in each root's `.cline
|
||||
|
||||
### Context Not Affecting Behavior
|
||||
- Remember: context affects FUTURE decisions, not the current tool
|
||||
- Use PreToolUse for validation (blocking) if you need immediate effect
|
||||
- Ensure context modifications are clear and actionable
|
||||
- Check that context isn't being truncated (50KB limit)
|
||||
|
||||
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -0,0 +1,90 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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 (changeset-bot, 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
|
||||
@@ -122,9 +122,9 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
|
||||
title="Previous Updates:"
|
||||
classNames={{
|
||||
trigger: "bg-transparent border-0 pl-0 pb-0 w-fit",
|
||||
title: "font-bold text-[var(--vscode-foreground)]",
|
||||
title: "font-bold text-(--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",
|
||||
"text-(--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>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Dependencies
|
||||
run: npm install changeset
|
||||
run: npm ci
|
||||
|
||||
# Check if there are any new changesets to process
|
||||
- name: Check for changesets
|
||||
|
||||
@@ -74,10 +74,9 @@ jobs:
|
||||
CLINE_ENVIRONMENT: production
|
||||
# OpenTelemetry production defaults (can be overridden at runtime)
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: ${{ secrets.OTEL_LOGS_EXPORTER }}
|
||||
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
|
||||
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 }}
|
||||
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
|
||||
run: npm run publish:marketplace:nightly
|
||||
|
||||
@@ -60,11 +60,11 @@ jobs:
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci --include=optional
|
||||
run: npm install --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
run: cd webview-ui && npm install --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
@@ -99,12 +99,11 @@ jobs:
|
||||
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: ${{ secrets.OTEL_LOGS_EXPORTER }}
|
||||
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
|
||||
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 }}
|
||||
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
|
||||
run: |
|
||||
# Required to generate the .vsix
|
||||
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
Vendored
+21
@@ -165,6 +165,27 @@
|
||||
},
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "Open Storybook",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"storybook"
|
||||
],
|
||||
"cwd": "${workspaceFolder}/webview-ui",
|
||||
"console": "integratedTerminal",
|
||||
"internalConsoleOptions": "neverOpen",
|
||||
"serverReadyAction": {
|
||||
"pattern": "Local:.*http://localhost:([0-9]+)",
|
||||
"uriFormat": "http://localhost:%s",
|
||||
"action": "openExternally"
|
||||
},
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+20
@@ -263,6 +263,26 @@
|
||||
"watch"
|
||||
],
|
||||
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "storybook",
|
||||
"group": "build",
|
||||
"problemMatcher": [],
|
||||
"isBackground": false,
|
||||
"label": "npm: storybook",
|
||||
"dependsOn": [
|
||||
"npm: protos",
|
||||
"npm: build:webview"
|
||||
],
|
||||
"presentation": {
|
||||
"reveal": "always"
|
||||
},
|
||||
"options": {
|
||||
"env": {
|
||||
"IS_DEV": "true"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
@@ -20,6 +20,9 @@ eslint-rules/**
|
||||
.husky/**
|
||||
.env
|
||||
|
||||
# cli
|
||||
cli/**
|
||||
|
||||
# Custom
|
||||
**/demo.gif
|
||||
.nvmrc
|
||||
|
||||
+143
-1
@@ -1,12 +1,154 @@
|
||||
# Changelog
|
||||
|
||||
## [3.40.0]
|
||||
|
||||
- Fix highlighted text flashing when task header is collapsed
|
||||
- Add X-Cerebras-3rd-Party-Integration header to Cerebras API requests
|
||||
- Add microwave family system prompt configuration
|
||||
- Remove tooltips from auto approve menu
|
||||
- Fix Standalone, ensure cwd is the install dir to find resources reliably
|
||||
- Fix a bug where terminal commands with double quotes are broken when "Terminal Execution Mode" is set to "Background Exec"
|
||||
- Add support for slash commands anywhere in a message, not just at the beginning. This matches the behavior of @ mentions for a more flexible input experience.
|
||||
- Add bottom padding to the last message to fix last response text getting cut off by auto approve settings bar.
|
||||
- Add default thinking level for Gemini 3 Pro models in Gemini provider
|
||||
|
||||
## [3.39.2]
|
||||
|
||||
- Fix for microwave model and thinking settings
|
||||
|
||||
## [3.39.1]
|
||||
|
||||
- Fix Openrouter and Cline Provider model info
|
||||
|
||||
## [3.39.0]
|
||||
|
||||
- Add Explain Changes feature
|
||||
- Add microwave Stealth model
|
||||
- Add Tabbed Model Picker with Recommended and Free tabs
|
||||
- Add support to View remote rules and workflows in the editor
|
||||
- Enable NTC (Native Tool Calling) by default
|
||||
- Bug fixes and improvements for LiteLLM provider
|
||||
|
||||
## [3.38.3]
|
||||
|
||||
- Task export feature now opens the task directory, allowing easy access to the full task files
|
||||
- Add Grok 4.1 and Grok Code to XAI provider
|
||||
- Enabled native tool calling for Baseten and Kimi K2 models
|
||||
- Add thinking level to Gemini 3.0 Pro preview
|
||||
- Expanded Hooks functionality
|
||||
- Removed Task Timeline from Task Header
|
||||
- Bug fix for slash commands
|
||||
- Bug fixes for Vertex provider
|
||||
- Bug fixes for thinking/reasoning issues across multiple providers when using native tool calling
|
||||
- Bug fixes for terminal usage on Windows devices
|
||||
|
||||
## [3.38.2]
|
||||
|
||||
- Add Claude Opus 4.5
|
||||
|
||||
## [3.38.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed handling of 'signature' field in sanitizeAnthropicContentBlock to properly preserve it when thinking is enabled, as required by Anthropic's API.
|
||||
|
||||
## [3.38.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Gemini 3 Pro Preview model
|
||||
- AquaVoice Avalon model for voice-to-text dictation
|
||||
|
||||
### Fixed
|
||||
|
||||
- Automatic context truncation when AWS Bedrock token usage rate limits are exceeded
|
||||
- Removed new_task tool from system prompts, updated slash command prompts, and added helper function for native tool calling validation
|
||||
|
||||
## [3.37.1]
|
||||
|
||||
- Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
|
||||
- Add AGENTS.md support
|
||||
- feat(models): Add free minimax/mimax-m2 model to the model picker
|
||||
|
||||
## [3.37.0]
|
||||
|
||||
### Added
|
||||
|
||||
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
|
||||
- Nous Research provider with Hermes 4 model family and custom system prompts
|
||||
- Switched to Aqua Voice's Avalon model in speech to text transcription
|
||||
- Added Linux support for speech to text
|
||||
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
|
||||
- Expanded HTTP proxy support throughout the codebase
|
||||
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Duplicate tool results prevention through existence checking
|
||||
- XML entity escaping in model content processor
|
||||
- Commit message generation in command palette
|
||||
- OpenAI Compatible provider temperature parameter type conversion
|
||||
|
||||
## Documentation
|
||||
|
||||
- Added missing proto generation step in CONTRIBUTING.md
|
||||
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
|
||||
|
||||
## [3.36.1]
|
||||
|
||||
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
|
||||
- fix: disable native tool callings for grok code models
|
||||
- Add MCP tool usage to GLM
|
||||
- Removes reasoning_details content field from Anthropic providers
|
||||
|
||||
## [3.36.0]
|
||||
|
||||
- Add: Hooks allow you to inject custom logic into Cline's workflow
|
||||
- Add: new provider AIhubmix
|
||||
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
|
||||
- Fix: Oca Token Refresh logic
|
||||
- Fix: issues where assistant message with empty content is added to conversation history
|
||||
- Fix: bug where the checkbox shows in the model selector dropdown
|
||||
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
|
||||
- Fix: support for `<think>` tags for better compatibility with open-source models
|
||||
- Fix: refinements to the GLM-4.6 system prompt
|
||||
|
||||
## [3.35.1]
|
||||
|
||||
- Add: Hicap API integration as provider
|
||||
- Fix: enable Add Header button in OpenAICompatibleProvider UI
|
||||
- Fix: Remove orphaned tool_results after truncation and empty content field issues in native tool call
|
||||
- Fix: render model description in markdown
|
||||
|
||||
## [3.35.0]
|
||||
|
||||
- Add native tool calling support with configurable setting.
|
||||
- Auto-approve is now always-on with a redesigned expanding menu. Settings simplified and notifications moved to General Settings.
|
||||
- added zai-glm-4.6 as a Cerebras model
|
||||
- Created GPT5 family specific system prompt template
|
||||
- Fix: show reasoning budget slider to models with valid thinking config
|
||||
- Requesty base URL, and API key fixes
|
||||
- Delete all Auth Tokens when logging out
|
||||
- Support for <think> tags for models that prefer that over <thinking>
|
||||
|
||||
## [3.34.1]
|
||||
|
||||
- Added support for MiniMax provider with MiniMax-M2 model
|
||||
- Remove Cline/code-supernova-1-million model
|
||||
- Changes to allow users to manually enter model names (eg. presets) when using OpenRouter
|
||||
|
||||
## [3.34.0]
|
||||
|
||||
- Cline Teams is now free through 2025 for unlimited users. Includes Jetbrains, RBAC, centralized billing and more.
|
||||
- Use the “exacto” versions of GLM-4.6, Kimi-K2, and Qwen3-Coder in the Cline provider for the best balance of cost, speed, accuracy and tool-calling.
|
||||
|
||||
## [3.33.1]
|
||||
|
||||
- Fix CLI installation copy text
|
||||
|
||||
## [3.33.0]
|
||||
|
||||
- Added Cline CLI (Preview)
|
||||
- Added Cline CLI (Preview)
|
||||
- Added Subagent support (Experimental)
|
||||
- Added Multi-Root Workspaces support (Enable in feature settings)
|
||||
- Add auto-retry with exponential backof for failed API requests
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
# CLAUDE.md
|
||||
|
||||
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.
|
||||
|
||||
## 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 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
|
||||
|
||||
## 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.
|
||||
+7
-1
@@ -46,7 +46,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
```bash
|
||||
npm run install:all
|
||||
```
|
||||
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.)
|
||||
4. Generate Protocol Buffer files (required before first build):
|
||||
```bash
|
||||
npm run protos
|
||||
```
|
||||
5. 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.)
|
||||
|
||||
|
||||
|
||||
@@ -85,8 +89,10 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
|
||||
|
||||
2. **Local Development**
|
||||
- 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**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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>
|
||||
|
||||
# Cline – \#1 on OpenRouter
|
||||
# Cline
|
||||
|
||||
<p align="center">
|
||||
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
|
||||
@@ -43,7 +43,7 @@ Thanks to [Claude Sonnet's agentic coding capabilities](https://www.anthropic.c
|
||||
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.
|
||||
|
||||
> [!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.
|
||||
> Follow [this guide](https://docs.cline.bot/features/customization/opening-cline-in-sidebar) to open Cline on the right side of your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
|
||||
|
||||
---
|
||||
|
||||
@@ -141,6 +141,11 @@ For example, when working with a local web server, you can use 'Restore Workspac
|
||||
|
||||
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)!
|
||||
|
||||
## Enterprise
|
||||
|
||||
Get the same Cline experience with enterprise-grade controls: SSO (SAML/OIDC), global policies and configuration, observability with audit trails, private networking (VPC/private link), and self-hosted or on-prem deployments, and enterprise support. Learn more at our [enterprise page](https://cline.bot/enterprise) or [talk to us](https://cline.bot/contact-sales).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>
|
||||
<json>
|
||||
<![CDATA[
|
||||
{
|
||||
"fontFamily": "cline-bot",
|
||||
"majorVersion": 1,
|
||||
"minorVersion": 0,
|
||||
"fontURL": "https://cline.bot",
|
||||
"designerURL": "https://cline.bot",
|
||||
"licenseURL": "https://cline.bot",
|
||||
"version": "Version 1.0",
|
||||
"fontId": "cline-bot",
|
||||
"psName": "cline-bot",
|
||||
"subFamily": "Regular",
|
||||
"fullName": "cline-bot",
|
||||
"description": "Font generated by IcoMoon."
|
||||
}
|
||||
]]>
|
||||
</json>
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="cline-bot" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="cline" data-tags="cline" horiz-adv-x="977" d="M964.553 383.11l-60.285 121.406v69.495c0 115.545-92.939 209.321-207.647 209.321h-102.986c7.536 15.071 11.722 32.654 11.722 51.074 0 64.471-51.912 116.383-115.545 116.383s-115.545-51.912-115.545-116.383 4.186-35.166 11.722-51.074h-102.986c-114.708 0-207.647-93.776-207.647-209.321v-69.495l-61.959-121.406c-5.861-11.722-5.861-26.793 0-38.515l61.959-119.732v-69.495c0-115.545 92.939-209.321 207.647-209.321h415.294c114.708 0 207.647 93.776 207.647 209.321v69.495l60.285 119.732c5.861 11.722 5.861 25.956 0 38.515v0zM426.178 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132zM731.787 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132z" />
|
||||
</font></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
Binary file not shown.
+13
-13
@@ -70,7 +70,7 @@
|
||||
"noControlCharactersInRegex": "off",
|
||||
"noShadowRestrictedNames": "off",
|
||||
"noArrayIndexKey": "info",
|
||||
"noAssignInExpressions": "warn"
|
||||
"noAssignInExpressions": "info"
|
||||
},
|
||||
"complexity": {
|
||||
"noUselessConstructor": "off",
|
||||
@@ -82,7 +82,7 @@
|
||||
"noStaticOnlyClass": "off"
|
||||
},
|
||||
"security": {
|
||||
"noDangerouslySetInnerHtml": "warn"
|
||||
"noDangerouslySetInnerHtml": "info"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -114,17 +114,17 @@
|
||||
"files": {
|
||||
"includes": [
|
||||
"**",
|
||||
"!**/dist/**",
|
||||
"!**/dist-*/**",
|
||||
"!**/out/**",
|
||||
"!**/evals/**",
|
||||
"!**/playwright/**",
|
||||
"!**/test-results/**",
|
||||
"!**/node_modules/**",
|
||||
"!**/webview-ui/build/**",
|
||||
"!**/generated/**",
|
||||
"!**/proto/**",
|
||||
"!**/tests/specs/**"
|
||||
"!**/dist",
|
||||
"!**/dist-*",
|
||||
"!**/out",
|
||||
"!**/evals",
|
||||
"!**/playwright",
|
||||
"!**/test-results",
|
||||
"!**/node_modules",
|
||||
"!**/webview-ui/build",
|
||||
"!**/generated",
|
||||
"!**/proto",
|
||||
"!**/tests/specs"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
|
||||
+16
-17
@@ -101,10 +101,7 @@ see the manual page: man cline`,
|
||||
if !isUserReadyToUse(ctx, instanceAddress) {
|
||||
// Create renderer for welcome messages
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
|
||||
markdown := "## hey there! looks like you're new here. let's get you set up"
|
||||
rendered := renderer.RenderMarkdown(markdown)
|
||||
fmt.Printf("\n%s\n\n", rendered)
|
||||
fmt.Printf("\n%s\n\n", renderer.Dim("Hey there! Looks like you're new here. Let's get you set up"))
|
||||
|
||||
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
|
||||
// Check if user cancelled - exit cleanly
|
||||
@@ -119,9 +116,7 @@ see the manual page: man cline`,
|
||||
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
|
||||
}
|
||||
|
||||
markdown = "## ✓ setup complete, you can now use the cline cli"
|
||||
rendered = renderer.RenderMarkdown(markdown)
|
||||
fmt.Printf("\n%s\n\n", rendered)
|
||||
fmt.Printf("\n%s\n\n", renderer.Dim("✓ Setup complete, you can now use the Cline CLI"))
|
||||
}
|
||||
} else {
|
||||
// User specified --address flag, use that
|
||||
@@ -187,6 +182,7 @@ see the manual page: man cline`,
|
||||
rootCmd.AddCommand(cli.NewVersionCommand())
|
||||
rootCmd.AddCommand(cli.NewAuthCommand())
|
||||
rootCmd.AddCommand(cli.NewLogsCommand())
|
||||
// rootCmd.AddCommand(cli.NewDoctorCommand()) // Disabled for now
|
||||
|
||||
if err := rootCmd.ExecuteContext(context.Background()); err != nil {
|
||||
os.Exit(1)
|
||||
@@ -331,17 +327,20 @@ func getContentFromStdinAndArgs(args []string) (string, error) {
|
||||
|
||||
// Check if data is being piped to stdin
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
stdinBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read from stdin: %w", err)
|
||||
}
|
||||
|
||||
stdinContent := strings.TrimSpace(string(stdinBytes))
|
||||
if stdinContent != "" {
|
||||
if content.Len() > 0 {
|
||||
content.WriteString(" ")
|
||||
// Only try to read if there's actually data available
|
||||
if stat.Size() > 0 {
|
||||
stdinBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read from stdin: %w", err)
|
||||
}
|
||||
|
||||
stdinContent := strings.TrimSpace(string(stdinBytes))
|
||||
if stdinContent != "" {
|
||||
if content.Len() > 0 {
|
||||
content.WriteString(" ")
|
||||
}
|
||||
content.WriteString(stdinContent)
|
||||
}
|
||||
content.WriteString(stdinContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -8,8 +8,10 @@ require (
|
||||
github.com/charmbracelet/bubbletea v1.3.6
|
||||
github.com/charmbracelet/glamour v0.10.0
|
||||
github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
|
||||
github.com/cline/grpc-go v0.0.0
|
||||
github.com/glebarez/go-sqlite v1.22.0
|
||||
github.com/muesli/termenv v0.16.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
golang.org/x/term v0.32.0
|
||||
google.golang.org/grpc v1.75.0
|
||||
@@ -24,7 +26,6 @@ require (
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
||||
@@ -45,7 +46,6 @@ require (
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cline",
|
||||
"version": "1.0.0-nightly.18",
|
||||
"version": "1.0.3",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "cline-core.js",
|
||||
"bin": {
|
||||
@@ -20,7 +20,7 @@
|
||||
"vscode-uri"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"cline",
|
||||
|
||||
+25
-5
@@ -6,18 +6,38 @@ import (
|
||||
)
|
||||
|
||||
func NewAuthCommand() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
cmd := &cobra.Command{
|
||||
Use: "auth",
|
||||
Short: "Authenticate a provider and configure model used",
|
||||
Long: `Authenticate a provider and configure model used
|
||||
Short: "Authenticate a provider and configure what model is used",
|
||||
Long: `Authenticate a provider and configure what model is used
|
||||
|
||||
This command opens an interactive menu where you can:
|
||||
Interactive Mode:
|
||||
Run without flags to open an interactive menu where you can:
|
||||
- Sign in to your Cline account
|
||||
- Configure other LLM providers (Anthropic, OpenAI, etc.)
|
||||
- Select and switch between AI models
|
||||
- Manage provider settings`,
|
||||
- Manage provider settings
|
||||
|
||||
Quick Setup Mode:
|
||||
Use flags to quickly configure a BYO provider non-interactively:
|
||||
|
||||
Examples:
|
||||
cline auth --provider openai-native --apikey sk-xxx --modelid gpt-5
|
||||
cline auth -p anthropic -k sk-ant-xxx -m claude-sonnet-4-5-20250929
|
||||
cline auth -p openai-compatible -k xxx -m gpt-4 -b https://api.example.com/v1
|
||||
|
||||
Supported providers: openai-native, openai, anthropic, gemini, openrouter, xai, cerebras, ollama
|
||||
Note: Bedrock provider requires interactive setup due to complex auth fields`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return auth.RunAuthFlow(cmd.Context(), args)
|
||||
},
|
||||
}
|
||||
|
||||
// Add flags for quick setup mode
|
||||
cmd.Flags().StringVarP(&auth.QuickProvider, "provider", "p", "", "Provider ID for quick setup (e.g., openai-native, anthropic)")
|
||||
cmd.Flags().StringVarP(&auth.QuickAPIKey, "apikey", "k", "", "API key for the provider")
|
||||
cmd.Flags().StringVarP(&auth.QuickModelID, "modelid", "m", "", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929)")
|
||||
cmd.Flags().StringVarP(&auth.QuickBaseURL, "baseurl", "b", "", "Base URL (optional, only for openai provider)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/cline/cli/pkg/cli/display"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
@@ -38,7 +39,7 @@ const (
|
||||
// ┃ Change Cline model (only if authenticated) - hidden if not authenticated
|
||||
// ┃ Authenticate with Cline account / Sign out of Cline - changes based on auth status
|
||||
// ┃ Select active provider (Cline or BYO) - always shown. Used to switch between Cline and BYO providers
|
||||
// ┃ Configure API provider - always shown. Launches provider setup wizard
|
||||
// ┃ Configure BYO API providers - always shown. Launches provider setup wizard
|
||||
// ┃ Exit authorization wizard - always shown. Exits the auth menu
|
||||
|
||||
// RunAuthFlow is the entry point for the entire auth flow with instance management
|
||||
@@ -68,18 +69,25 @@ func RunAuthFlow(ctx context.Context, args []string) error {
|
||||
// Main entry point for handling the `cline auth` command
|
||||
// HandleAuthCommand routes the auth command based on the number of arguments
|
||||
func HandleAuthCommand(ctx context.Context, args []string) error {
|
||||
|
||||
// Check if flags are provided for quick setup
|
||||
if QuickProvider != "" || QuickAPIKey != "" || QuickModelID != "" || QuickBaseURL != "" {
|
||||
if QuickProvider == "" || QuickAPIKey == "" || QuickModelID == "" {
|
||||
return fmt.Errorf("quick setup requires --provider, --apikey, and --modelid flags. Use 'cline auth --help' for more information")
|
||||
}
|
||||
return QuickSetupFromFlags(ctx, QuickProvider, QuickAPIKey, QuickModelID, QuickBaseURL)
|
||||
}
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
// No args: Show menu (ShowAuthMenuNoArgs)
|
||||
// No args: Show uth wizard
|
||||
return HandleAuthMenuNoArgs(ctx)
|
||||
case 1:
|
||||
// One arg: Provider ID only, prompt for API key
|
||||
return QuickAPISetup(args[0], "")
|
||||
case 2:
|
||||
// Two args: Provider ID and API key
|
||||
return QuickAPISetup(args[0], args[1])
|
||||
case 1, 2, 3, 4:
|
||||
fmt.Println("Invalid positional arguments. Correct usage:")
|
||||
fmt.Println(" cline auth --provider <provider> --apikey <key> --modelid <model> --baseurl <optional>")
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("quick BYO API setup is currently stubbed - not yet implemented")
|
||||
return fmt.Errorf("too many arguments. Use flags for quick setup: --provider, --apikey, --modelid --baseurl(optional)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,32 +173,34 @@ func ShowAuthMenuWithStatus(isClineAuthenticated bool, hasOrganizations bool, cu
|
||||
options = append(options,
|
||||
huh.NewOption("Sign out of Cline", AuthActionClineLogin),
|
||||
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
|
||||
huh.NewOption("Configure API provider", AuthActionBYOSetup),
|
||||
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
|
||||
huh.NewOption("Exit authorization wizard", AuthActionExit),
|
||||
)
|
||||
} else {
|
||||
options = []huh.Option[AuthAction]{
|
||||
huh.NewOption("Authenticate with Cline account", AuthActionClineLogin),
|
||||
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
|
||||
huh.NewOption("Configure API provider", AuthActionBYOSetup),
|
||||
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
|
||||
huh.NewOption("Exit authorization wizard", AuthActionExit),
|
||||
}
|
||||
}
|
||||
|
||||
// Determine menu title based on status
|
||||
var title string
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
|
||||
// Always show Cline authentication status
|
||||
if isClineAuthenticated {
|
||||
title = "Cline Account: \033[32m✓\033[0m Authenticated\n"
|
||||
title = fmt.Sprintf("Cline Account: %s Authenticated\n", renderer.Green("✓"))
|
||||
} else {
|
||||
title = "Cline Account: \033[31m✗\033[0m Not authenticated\n"
|
||||
title = fmt.Sprintf("Cline Account: %s Not authenticated\n", renderer.Red("✗"))
|
||||
}
|
||||
|
||||
// Show active provider and model if configured (regardless of Cline auth status)
|
||||
// ANSI color codes: Normal intensity = \033[22m, White = \033[37m, Reset = \033[0m
|
||||
if currentProvider != "" && currentModel != "" {
|
||||
title += fmt.Sprintf("Active Provider: \033[22m\033[37m%s\033[0m\nActive Model: \033[22m\033[37m%s\033[0m\n", currentProvider, currentModel)
|
||||
title += fmt.Sprintf("Active Provider: %s\nActive Model: %s\n",
|
||||
renderer.White(currentProvider),
|
||||
renderer.White(currentModel))
|
||||
}
|
||||
|
||||
// Always end with a huh?
|
||||
@@ -258,11 +268,6 @@ func HandleSelectProvider(ctx context.Context) error {
|
||||
return HandleAuthMenuNoArgs(ctx)
|
||||
}
|
||||
|
||||
if len(providerOptions) == 1 {
|
||||
fmt.Println("Only one provider is configured. Configure another provider to switch between them.")
|
||||
return HandleAuthMenuNoArgs(ctx)
|
||||
}
|
||||
|
||||
providerOptions = append(providerOptions, huh.NewOption("(Cancel)", "cancel"))
|
||||
|
||||
// Show selection menu
|
||||
|
||||
@@ -1,13 +1,247 @@
|
||||
package auth
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
// QuickAPISetup performs quick provider setup with provider ID and optional API key
|
||||
func QuickAPISetup(providerID, apiKey string) error {
|
||||
fmt.Println("Quick BYO API setup is currently stubbed - not yet implemented.")
|
||||
fmt.Printf("Requested provider: %s\n", providerID)
|
||||
if apiKey != "" {
|
||||
fmt.Println("Provided API key:", "<jk redacted>")
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
)
|
||||
|
||||
// Package-level variables for command-line flags
|
||||
var (
|
||||
QuickProvider string // Provider ID (e.g., "openai", "anthropic")
|
||||
QuickAPIKey string // API key for the provider
|
||||
QuickModelID string // Model ID to configure
|
||||
QuickBaseURL string // Base URL (optional, for openai compatible only)
|
||||
)
|
||||
|
||||
// QuickSetupFromFlags performs quick setup using command-line flags
|
||||
// Returns error if validation fails or configuration cannot be applied
|
||||
func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL string) error {
|
||||
// Validate all input parameters
|
||||
providerEnum, err := validateQuickSetupInputs(provider, apiKey, modelID, baseURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create task manager for state operations
|
||||
manager, err := createTaskManager(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create task manager: %w", err)
|
||||
}
|
||||
|
||||
// Validate and fetch model information if needed
|
||||
finalModelID, modelInfo, err := validateAndFetchModel(ctx, manager, providerEnum, modelID, apiKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("model validation failed: %w", err)
|
||||
}
|
||||
|
||||
// For Ollama, baseURL is stored in the API key field
|
||||
finalAPIKey := apiKey
|
||||
finalBaseURL := baseURL
|
||||
if providerEnum == cline.ApiProvider_OLLAMA {
|
||||
if baseURL != "" {
|
||||
finalAPIKey = baseURL
|
||||
finalBaseURL = ""
|
||||
} else if apiKey != "" {
|
||||
// User provided API key for Ollama - treat it as baseURL
|
||||
finalAPIKey = apiKey
|
||||
finalBaseURL = ""
|
||||
} else {
|
||||
// Use default Ollama baseURL
|
||||
finalAPIKey = "http://localhost:11434"
|
||||
finalBaseURL = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the provider using existing AddProviderPartial function
|
||||
if err := AddProviderPartial(ctx, manager, providerEnum, finalModelID, finalAPIKey, finalBaseURL, modelInfo); err != nil {
|
||||
return fmt.Errorf("failed to configure provider: %w", err)
|
||||
}
|
||||
|
||||
// Set the provider as active for both Plan and Act modes
|
||||
if err := UpdateProviderPartial(ctx, manager, providerEnum, ProviderUpdatesPartial{}, true); err != nil {
|
||||
return fmt.Errorf("failed to set provider as active: %w", err)
|
||||
}
|
||||
|
||||
// Mark welcome view as completed
|
||||
if err := markWelcomeViewCompleted(ctx, manager); err != nil {
|
||||
// Non-fatal error, just log it
|
||||
if global.Config.Verbose {
|
||||
fmt.Printf("[DEBUG] Warning: failed to mark welcome view as completed: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Flush pending state changes to disk immediately
|
||||
// This ensures all configuration changes are persisted before the instance terminates
|
||||
if _, err := manager.GetClient().State.FlushPendingState(ctx, &cline.EmptyRequest{}); err != nil {
|
||||
return fmt.Errorf("failed to flush pending state: %w", err)
|
||||
}
|
||||
|
||||
// Success message
|
||||
fmt.Printf("\n✓ Successfully configured %s provider\n", GetProviderDisplayName(providerEnum))
|
||||
fmt.Printf(" Model: %s\n", finalModelID)
|
||||
if providerEnum == cline.ApiProvider_OLLAMA {
|
||||
fmt.Printf(" Base URL: %s\n", finalAPIKey)
|
||||
} else {
|
||||
fmt.Println(" API Key: Configured")
|
||||
}
|
||||
if finalBaseURL != "" {
|
||||
fmt.Printf(" Custom Base URL: %s\n", finalBaseURL)
|
||||
}
|
||||
fmt.Println("\nYou can now use Cline with this provider.")
|
||||
fmt.Println("Run 'cline start' to begin a new task.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateQuickSetupInputs validates all input parameters for quick setup
|
||||
// Returns the validated provider enum or an error if validation fails
|
||||
func validateQuickSetupInputs(provider, apiKey, modelID, baseURL string) (cline.ApiProvider, error) {
|
||||
// Validate required parameters
|
||||
if provider == "" {
|
||||
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("provider is required. Use --provider or -p flag")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(apiKey) == "" && provider != "ollama" {
|
||||
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("API key is required for %s provider. Use --apikey or -k flag", provider)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(modelID) == "" {
|
||||
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("model ID is required. Use --modelid or -m flag")
|
||||
}
|
||||
|
||||
// Validate and map provider string to enum
|
||||
providerEnum, err := validateQuickSetupProvider(provider)
|
||||
if err != nil {
|
||||
return cline.ApiProvider_ANTHROPIC, err
|
||||
}
|
||||
|
||||
// Validate that baseURL is only provided for OpenAI-compatible providers
|
||||
if err := validateBaseURL(baseURL, providerEnum); err != nil {
|
||||
return cline.ApiProvider_ANTHROPIC, err
|
||||
}
|
||||
|
||||
return providerEnum, nil
|
||||
}
|
||||
|
||||
// validateBaseURL checks if the user's input includes a baseURL for a provider other than OpenAI (compatible)
|
||||
// Returns error if baseURL is provided for unsupported providers
|
||||
func validateBaseURL(baseURL string, providerEnum cline.ApiProvider) error {
|
||||
if providerEnum != cline.ApiProvider_OPENAI {
|
||||
if baseURL != "" {
|
||||
return fmt.Errorf("base URL is only supported for OpenAI and OpenAI-compatible providers")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// validateQuickSetupProvider validates the provider ID and returns the enum value
|
||||
// Returns error if provider is invalid or not supported for quick setup
|
||||
func validateQuickSetupProvider(providerID string) (cline.ApiProvider, error) {
|
||||
// Normalize provider ID (trim whitespace, lowercase)
|
||||
normalizedID := strings.TrimSpace(strings.ToLower(providerID))
|
||||
|
||||
// Explicitly block Bedrock
|
||||
if normalizedID == "bedrock" {
|
||||
return cline.ApiProvider_BEDROCK, fmt.Errorf("bedrock provider is not supported for quick setup due to complex authentication requirements. Please use interactive setup: cline auth")
|
||||
}
|
||||
|
||||
// Map provider string to enum using existing function
|
||||
provider, ok := mapProviderStringToEnum(normalizedID)
|
||||
if !ok {
|
||||
// Provider not found - provide helpful error message
|
||||
supportedProviders := []string{
|
||||
"openai-native", "openai", "anthropic", "gemini",
|
||||
"openrouter", "xai", "cerebras", "ollama",
|
||||
}
|
||||
return cline.ApiProvider_ANTHROPIC, fmt.Errorf(
|
||||
"invalid provider '%s'. Supported providers: %s",
|
||||
providerID,
|
||||
strings.Join(supportedProviders, ", "),
|
||||
)
|
||||
}
|
||||
|
||||
// Validate against supported quick setup providers
|
||||
supportedProviders := map[cline.ApiProvider]bool{
|
||||
cline.ApiProvider_OPENAI_NATIVE: true,
|
||||
cline.ApiProvider_OPENAI: true,
|
||||
cline.ApiProvider_ANTHROPIC: true,
|
||||
cline.ApiProvider_GEMINI: true,
|
||||
cline.ApiProvider_OPENROUTER: true,
|
||||
cline.ApiProvider_XAI: true,
|
||||
cline.ApiProvider_CEREBRAS: true,
|
||||
cline.ApiProvider_OLLAMA: true,
|
||||
cline.ApiProvider_NOUSRESEARCH: true,
|
||||
}
|
||||
|
||||
if !supportedProviders[provider] {
|
||||
return provider, fmt.Errorf(
|
||||
"provider '%s' is not supported for quick setup. Please use interactive setup: cline auth",
|
||||
providerID,
|
||||
)
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// validateAndFetchModel validates the model ID or fetches from provider if needed
|
||||
// Returns the final model ID and optional model info
|
||||
// For providers with static models, validates against the list
|
||||
// For providers with dynamic models, fetches the list if possible
|
||||
func validateAndFetchModel(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID, apiKey string) (string, interface{}, error) {
|
||||
// Normalize model ID
|
||||
modelID = strings.TrimSpace(modelID)
|
||||
if modelID == "" {
|
||||
return "", nil, fmt.Errorf("model ID cannot be empty")
|
||||
}
|
||||
|
||||
// For most providers, we trust the user's input since we can't easily validate without making API calls
|
||||
// The actual validation will happen when the model is used
|
||||
switch provider {
|
||||
case cline.ApiProvider_OPENROUTER:
|
||||
// OpenRouter supports model info fetching, but it requires an API call
|
||||
// For quick setup, we'll trust the user's input and return nil for model info
|
||||
// The actual model info will be fetched when needed
|
||||
if global.Config.Verbose {
|
||||
fmt.Printf("[DEBUG] OpenRouter model ID: %s (will be validated on first use)\n", modelID)
|
||||
}
|
||||
return modelID, nil, nil
|
||||
|
||||
case cline.ApiProvider_OLLAMA:
|
||||
// Ollama models can be validated by fetching the list, but this requires the server to be running
|
||||
// For quick setup, we'll trust the user's input
|
||||
if global.Config.Verbose {
|
||||
fmt.Printf("[DEBUG] Ollama model ID: %s (will be validated when server is accessible)\n", modelID)
|
||||
}
|
||||
return modelID, nil, nil
|
||||
|
||||
default:
|
||||
// For other providers (Anthropic, OpenAI, Gemini, XAI, Cerebras), trust user input
|
||||
// Model validation will occur when the model is actually used
|
||||
if global.Config.Verbose {
|
||||
fmt.Printf("[DEBUG] %s model ID: %s (will be validated on first use)\n", GetProviderDisplayName(provider), modelID)
|
||||
}
|
||||
return modelID, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// markWelcomeViewCompleted marks the welcome view as completed in the state
|
||||
// This prevents the welcome view from showing up after quick setup
|
||||
func markWelcomeViewCompleted(ctx context.Context, manager *task.Manager) error {
|
||||
// Use the State service to update the welcome view flag
|
||||
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mark welcome view as completed: %w", err)
|
||||
}
|
||||
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("[DEBUG] Marked welcome view as completed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package auth
|
||||
@@ -14,13 +14,33 @@ import (
|
||||
|
||||
// FetchOpenRouterModels fetches available OpenRouter models from Cline Core
|
||||
func FetchOpenRouterModels(ctx context.Context, manager *task.Manager) (map[string]*cline.OpenRouterModelInfo, error) {
|
||||
resp, err := manager.GetClient().Models.RefreshOpenRouterModels(ctx, &cline.EmptyRequest{})
|
||||
resp, err := manager.GetClient().Models.RefreshOpenRouterModelsRpc(ctx, &cline.EmptyRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch OpenRouter models: %w", err)
|
||||
}
|
||||
return resp.Models, nil
|
||||
}
|
||||
|
||||
// FetchOcaModels fetches available Oca models from Cline Core
|
||||
func FetchOcaModels(ctx context.Context, manager *task.Manager) (map[string]*cline.OcaModelInfo, error) {
|
||||
resp, err := manager.GetClient().Models.RefreshOcaModels(ctx, &cline.StringRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch Oca models: %w", err)
|
||||
}
|
||||
return resp.Models, nil
|
||||
}
|
||||
|
||||
// ConvertOpenRouterModelsToInterface converts OpenRouter model map to generic interface map.
|
||||
// This allows OpenRouter and Cline models to be used with the generic fetching utilities.
|
||||
func ConvertOpenRouterModelsToInterface(models map[string]*cline.OpenRouterModelInfo) map[string]interface{} {
|
||||
result := make(map[string]interface{}, len(models))
|
||||
for k, v := range models {
|
||||
result[k] = v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// FetchOpenAiModels fetches available OpenAI models from Cline Core
|
||||
// Takes the API key and returns a list of model IDs
|
||||
func FetchOpenAiModels(ctx context.Context, manager *task.Manager, baseURL, apiKey string) ([]string, error) {
|
||||
@@ -100,9 +120,9 @@ func ConvertModelsMapToSlice(models map[string]interface{}) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// ConvertOpenRouterModelsToInterface converts OpenRouter model map to generic interface map.
|
||||
// This allows OpenRouter and Cline models to be used with the generic fetching utilities.
|
||||
func ConvertOpenRouterModelsToInterface(models map[string]*cline.OpenRouterModelInfo) map[string]interface{} {
|
||||
// ConvertOcaModelsToInterface converts Oca model map to generic interface map.
|
||||
// This allows Oca and Cline models to be used with the generic fetching utilities.
|
||||
func ConvertOcaModelsToInterface(models map[string]*cline.OcaModelInfo) map[string]interface{} {
|
||||
result := make(map[string]interface{}, len(models))
|
||||
for k, v := range models {
|
||||
result[k] = v
|
||||
|
||||
@@ -18,14 +18,16 @@ type BYOProviderOption struct {
|
||||
func GetBYOProviderList() []BYOProviderOption {
|
||||
return []BYOProviderOption{
|
||||
{Name: "Anthropic", Provider: cline.ApiProvider_ANTHROPIC},
|
||||
{Name: "OpenAI", Provider: cline.ApiProvider_OPENAI},
|
||||
{Name: "OpenAI Native", Provider: cline.ApiProvider_OPENAI_NATIVE},
|
||||
{Name: "OpenAI Compatible", Provider: cline.ApiProvider_OPENAI},
|
||||
{Name: "OpenAI (Official)", Provider: cline.ApiProvider_OPENAI_NATIVE},
|
||||
{Name: "OpenRouter", Provider: cline.ApiProvider_OPENROUTER},
|
||||
{Name: "X AI (Grok)", Provider: cline.ApiProvider_XAI},
|
||||
{Name: "AWS Bedrock", Provider: cline.ApiProvider_BEDROCK},
|
||||
{Name: "Google Gemini", Provider: cline.ApiProvider_GEMINI},
|
||||
{Name: "Ollama", Provider: cline.ApiProvider_OLLAMA},
|
||||
{Name: "Cerebras", Provider: cline.ApiProvider_CEREBRAS},
|
||||
{Name: "NousResearch", Provider: cline.ApiProvider_NOUSRESEARCH},
|
||||
{Name: "Oracle Code Assist", Provider: cline.ApiProvider_OCA},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +73,8 @@ func SupportsBYOModelFetching(provider cline.ApiProvider) bool {
|
||||
return true
|
||||
case cline.ApiProvider_OLLAMA:
|
||||
return true
|
||||
case cline.ApiProvider_OCA:
|
||||
return true
|
||||
}
|
||||
|
||||
return SupportsStaticModelList(provider)
|
||||
@@ -82,9 +86,9 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
|
||||
case cline.ApiProvider_ANTHROPIC:
|
||||
return "e.g., claude-sonnet-4-5-20250929"
|
||||
case cline.ApiProvider_OPENAI:
|
||||
return "e.g., gpt-5-2025-08-07"
|
||||
case cline.ApiProvider_OPENAI_NATIVE:
|
||||
return "e.g., openai/gpt-oss-120b"
|
||||
case cline.ApiProvider_OPENAI_NATIVE:
|
||||
return "e.g., gpt-5-2025-08-07"
|
||||
case cline.ApiProvider_OPENROUTER:
|
||||
return "e.g., google/gemini-2.0-flash-exp:free"
|
||||
case cline.ApiProvider_XAI:
|
||||
@@ -97,6 +101,10 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
|
||||
return "e.g., qwen3-coder:30b"
|
||||
case cline.ApiProvider_CEREBRAS:
|
||||
return "e.g., gpt-oss-120b"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "e.g., Hermes-4-405B"
|
||||
case cline.ApiProvider_OCA:
|
||||
return "e.g., oca/llama4"
|
||||
default:
|
||||
return "Enter model ID"
|
||||
}
|
||||
@@ -127,8 +135,8 @@ func GetBYOAPIKeyFieldConfig(provider cline.ApiProvider) APIKeyFieldConfig {
|
||||
}
|
||||
|
||||
// PromptForAPIKey prompts the user to enter an API key (or base URL for Ollama).
|
||||
// For OpenAI Native provider, also prompts for an optional base URL.
|
||||
func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
|
||||
// For OpenAI (Compatible) provider, also prompts for an optional base URL.
|
||||
func PromptForAPIKey(provider cline.ApiProvider) (string, string, error) {
|
||||
var apiKey string
|
||||
config := GetBYOAPIKeyFieldConfig(provider)
|
||||
|
||||
@@ -149,11 +157,11 @@ func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
|
||||
form := huh.NewForm(huh.NewGroup(apiKeyField))
|
||||
|
||||
if err := form.Run(); err != nil {
|
||||
return "", fmt.Errorf("failed to get API key: %w", err)
|
||||
return "", "", fmt.Errorf("failed to get API key: %w", err)
|
||||
}
|
||||
|
||||
// For OpenAI Native provider, also prompt for base URL
|
||||
if provider == cline.ApiProvider_OPENAI_NATIVE {
|
||||
// For OpenAI (Compatible) provider, prompt for base URL
|
||||
if provider == cline.ApiProvider_OPENAI {
|
||||
var baseURL string
|
||||
baseURLForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
@@ -166,12 +174,11 @@ func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
|
||||
)
|
||||
|
||||
if err := baseURLForm.Run(); err != nil {
|
||||
return "", fmt.Errorf("failed to get base URL: %w", err)
|
||||
return "", "", fmt.Errorf("failed to get base URL: %w", err)
|
||||
}
|
||||
|
||||
// TODO - connect baseURL
|
||||
_ = baseURL
|
||||
return apiKey, baseURL, nil
|
||||
}
|
||||
|
||||
return apiKey, nil
|
||||
return apiKey, "", nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
@@ -110,6 +111,9 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
cline.ApiProvider_GEMINI,
|
||||
cline.ApiProvider_OLLAMA,
|
||||
cline.ApiProvider_CEREBRAS,
|
||||
cline.ApiProvider_NOUSRESEARCH,
|
||||
cline.ApiProvider_OCA,
|
||||
cline.ApiProvider_HICAP,
|
||||
}
|
||||
|
||||
// Check each provider to see if it's ready to use
|
||||
@@ -120,16 +124,23 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this provider has an API key
|
||||
hasAPIKey := checkAPIKeyExists(r.apiConfig, provider)
|
||||
if !hasAPIKey {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this provider has a model configured
|
||||
modelID := getProviderSpecificModelID(r.apiConfig, "plan", provider)
|
||||
if modelID == "" {
|
||||
continue
|
||||
|
||||
// Determine if credentials exist
|
||||
hasCreds := checkAPIKeyExists(r.apiConfig, provider)
|
||||
|
||||
// Determine readiness: OCA uses auth state presence; others need creds and model
|
||||
if provider == cline.ApiProvider_OCA {
|
||||
state, _ := GetLatestOCAState(context.Background(), 2 *time.Second)
|
||||
if state == nil || state.User == nil {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Provider is not ready unless it has credentials AND a model configured
|
||||
if !hasCreds || modelID == "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Get base URL for Ollama
|
||||
@@ -145,7 +156,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
Mode: "Ready",
|
||||
Provider: provider,
|
||||
ModelID: modelID,
|
||||
HasAPIKey: hasAPIKey,
|
||||
HasAPIKey: checkAPIKeyExists(r.apiConfig, provider),
|
||||
BaseURL: baseURL,
|
||||
})
|
||||
seenProviders[provider] = true
|
||||
@@ -203,13 +214,15 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
|
||||
// mapProviderStringToEnum converts provider string from state to ApiProvider enum
|
||||
// Returns (provider, ok) where ok is false if the provider is unknown
|
||||
func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
|
||||
normalizedStr := strings.ToLower(providerStr)
|
||||
|
||||
// Map string values to enum values
|
||||
switch providerStr {
|
||||
switch normalizedStr {
|
||||
case "anthropic":
|
||||
return cline.ApiProvider_ANTHROPIC, true
|
||||
case "openai":
|
||||
case "openai", "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
|
||||
return cline.ApiProvider_OPENAI, true
|
||||
case "openai-native":
|
||||
case "openai-native": // This is the native, official Open AI provider
|
||||
return cline.ApiProvider_OPENAI_NATIVE, true
|
||||
case "openrouter":
|
||||
return cline.ApiProvider_OPENROUTER, true
|
||||
@@ -225,6 +238,12 @@ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
|
||||
return cline.ApiProvider_CEREBRAS, true
|
||||
case "cline":
|
||||
return cline.ApiProvider_CLINE, true
|
||||
case "oca":
|
||||
return cline.ApiProvider_OCA, true
|
||||
case "hicap":
|
||||
return cline.ApiProvider_HICAP, true
|
||||
case "nousResearch":
|
||||
return cline.ApiProvider_NOUSRESEARCH, true
|
||||
default:
|
||||
return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
|
||||
}
|
||||
@@ -237,7 +256,7 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
|
||||
case cline.ApiProvider_ANTHROPIC:
|
||||
return "anthropic"
|
||||
case cline.ApiProvider_OPENAI:
|
||||
return "openai"
|
||||
return "openai-compatible"
|
||||
case cline.ApiProvider_OPENAI_NATIVE:
|
||||
return "openai-native"
|
||||
case cline.ApiProvider_OPENROUTER:
|
||||
@@ -254,6 +273,12 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
|
||||
return "cerebras"
|
||||
case cline.ApiProvider_CLINE:
|
||||
return "cline"
|
||||
case cline.ApiProvider_OCA:
|
||||
return "oca"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "nousResearch"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -312,9 +337,9 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
|
||||
case cline.ApiProvider_ANTHROPIC:
|
||||
return "Anthropic"
|
||||
case cline.ApiProvider_OPENAI:
|
||||
return "OpenAI"
|
||||
return "OpenAI Compatible"
|
||||
case cline.ApiProvider_OPENAI_NATIVE:
|
||||
return "OpenAI Native"
|
||||
return "OpenAI (Official)"
|
||||
case cline.ApiProvider_OPENROUTER:
|
||||
return "OpenRouter"
|
||||
case cline.ApiProvider_XAI:
|
||||
@@ -329,6 +354,12 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
|
||||
return "Cerebras"
|
||||
case cline.ApiProvider_CLINE:
|
||||
return "Cline (Official)"
|
||||
case cline.ApiProvider_OCA:
|
||||
return "Oracle Code Assist"
|
||||
case cline.ApiProvider_HICAP:
|
||||
return "Hicap"
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return "NousResearch"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
@@ -378,7 +409,7 @@ func FormatProviderList(result *ProviderListResult) string {
|
||||
} else {
|
||||
output.WriteString(" Base URL: (default)\n")
|
||||
}
|
||||
} else if display.Provider == cline.ApiProvider_CLINE {
|
||||
} else if display.Provider == cline.ApiProvider_CLINE || display.Provider == cline.ApiProvider_OCA {
|
||||
output.WriteString(" Status: Authenticated\n")
|
||||
} else {
|
||||
output.WriteString(" API Key: Configured\n")
|
||||
@@ -430,6 +461,12 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
verboseLog("[DEBUG] Cline provider is authenticated")
|
||||
}
|
||||
|
||||
// Check OCA provider via global auth subscription (state presence)
|
||||
if state, _ := GetLatestOCAState(context.Background(), 2*time.Second); state != nil && state.User != nil {
|
||||
configuredProviders = append(configuredProviders, cline.ApiProvider_OCA)
|
||||
verboseLog("[DEBUG] OCA provider has active auth state")
|
||||
}
|
||||
|
||||
// Check each BYO provider for API key presence
|
||||
providersToCheck := []struct {
|
||||
provider cline.ApiProvider
|
||||
@@ -444,6 +481,8 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
{cline.ApiProvider_GEMINI, "geminiApiKey"},
|
||||
{cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
|
||||
{cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
|
||||
{cline.ApiProvider_HICAP, "hicapApiKey"},
|
||||
{cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
|
||||
}
|
||||
|
||||
for _, providerCheck := range providersToCheck {
|
||||
@@ -459,6 +498,7 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
verboseLog("[DEBUG] Total configured providers: %d", len(configuredProviders))
|
||||
for _, p := range configuredProviders {
|
||||
verboseLog("[DEBUG] - %s", GetProviderDisplayName(p))
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
// updateApiConfigurationPartial is a helper that calls the gRPC method with optional verbose logging.
|
||||
// This replaces the Manager.UpdateApiConfigurationPartial method to keep auth-specific code in the auth package.
|
||||
// This replaces the Manager.updateApiConfigurationPartial method to keep auth-specific code in the auth package.
|
||||
func updateApiConfigurationPartial(ctx context.Context, manager *task.Manager, request *cline.UpdateApiConfigurationPartialRequest) error {
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("[DEBUG] Updating API configuration (partial)")
|
||||
@@ -46,6 +46,7 @@ func updateApiConfigurationPartial(ctx context.Context, manager *task.Manager, r
|
||||
// ProviderFields defines all the field names associated with a specific provider
|
||||
type ProviderFields struct {
|
||||
APIKeyField string // API key field name (e.g., "apiKey", "openAiApiKey")
|
||||
BaseURLField string // Base URL field name (optional, empty if not applicable)
|
||||
PlanModeModelIDField string // Plan mode model ID field (e.g., "planModeApiModelId")
|
||||
ActModeModelIDField string // Act mode model ID field (e.g., "actModeApiModelId")
|
||||
PlanModeModelInfoField string // Plan mode model info field (optional, empty if not applicable)
|
||||
@@ -68,6 +69,7 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
case cline.ApiProvider_OPENAI:
|
||||
return ProviderFields{
|
||||
APIKeyField: "openAiApiKey",
|
||||
BaseURLField: "openAiBaseUrl",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
PlanModeProviderSpecificModelIDField: "planModeOpenAiModelId",
|
||||
@@ -142,6 +144,34 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
ActModeProviderSpecificModelIDField: "actModeOpenRouterModelId",
|
||||
}, nil
|
||||
|
||||
case cline.ApiProvider_OCA:
|
||||
return ProviderFields{
|
||||
APIKeyField: "ocaApiKey",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
PlanModeModelInfoField: "planModeOcaModelInfo",
|
||||
ActModeModelInfoField: "actModeOcaModelInfo",
|
||||
PlanModeProviderSpecificModelIDField: "planModeOcaModelId",
|
||||
ActModeProviderSpecificModelIDField: "actModeOcaModelId",
|
||||
}, nil
|
||||
case cline.ApiProvider_HICAP:
|
||||
return ProviderFields{
|
||||
APIKeyField: "hicapApiKey",
|
||||
PlanModeModelInfoField: "planModeHicapModelInfo",
|
||||
ActModeModelInfoField: "actModeHicapModelInfo",
|
||||
PlanModeProviderSpecificModelIDField: "planModeHicapModelId",
|
||||
ActModeProviderSpecificModelIDField: "actModeHicapModelId",
|
||||
}, nil
|
||||
|
||||
case cline.ApiProvider_NOUSRESEARCH:
|
||||
return ProviderFields{
|
||||
APIKeyField: "nousResearchApiKey",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
|
||||
ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
|
||||
}
|
||||
@@ -150,9 +180,12 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
// ProviderUpdatesPartial defines optional fields for partial provider updates
|
||||
// Uses pointers to distinguish between "not provided" and "set to empty"
|
||||
type ProviderUpdatesPartial struct {
|
||||
ModelID *string // New model ID (optional)
|
||||
APIKey *string // New API key (optional)
|
||||
ModelInfo interface{} // New model info (optional, provider-specific)
|
||||
ModelID *string // New model ID (optional)
|
||||
APIKey *string // New API key (optional)
|
||||
ModelInfo interface{} // New model info (optional, provider-specific)
|
||||
BaseURL *string // New base URL (optional, e.g., for OCA, Ollama)
|
||||
RefreshToken *string // New refresh token (optional, e.g., for OCA)
|
||||
Mode *string // New mode (optional, e.g., "internal" or "external" for OCA)
|
||||
}
|
||||
|
||||
// GetModelIDFieldName returns the appropriate model ID field name for a provider and mode.
|
||||
@@ -182,7 +215,7 @@ func GetModelIDFieldName(provider cline.ApiProvider, mode string) (string, error
|
||||
// buildProviderFieldMask builds a list of camelCase field paths for the field mask.
|
||||
// When includeProviderEnums is true, the provider enum fields are included (for setting active provider).
|
||||
// When false, only the data fields are included (for configuring without activating).
|
||||
func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeModelID bool, includeModelInfo bool, includeProviderEnums bool) []string {
|
||||
func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeModelID bool, includeModelInfo bool, includeBaseURL bool, includeProviderEnums bool) []string {
|
||||
var fieldPaths []string
|
||||
|
||||
// Include provider enums if requested (used when setting active provider)
|
||||
@@ -199,6 +232,11 @@ func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeMo
|
||||
}
|
||||
}
|
||||
|
||||
// Add base URL field if requested and applicable
|
||||
if includeBaseURL && fields.BaseURLField != "" {
|
||||
fieldPaths = append(fieldPaths, fields.BaseURLField)
|
||||
}
|
||||
|
||||
// Add model ID fields if requested
|
||||
if includeModelID {
|
||||
// Only include provider-specific fields if they exist, otherwise use generic fields
|
||||
@@ -245,6 +283,12 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
|
||||
apiConfig.CerebrasApiKey = value
|
||||
case "clineApiKey":
|
||||
apiConfig.ClineApiKey = value
|
||||
case "ocaApiKey":
|
||||
apiConfig.OcaApiKey = value
|
||||
case "hicapApiKey":
|
||||
apiConfig.HicapApiKey = value
|
||||
case "nousResearchApiKey":
|
||||
apiConfig.NousResearchApiKey = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,11 +307,20 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
|
||||
case "planModeAwsBedrockCustomModelBaseId":
|
||||
apiConfig.PlanModeAwsBedrockCustomModelBaseId = value
|
||||
apiConfig.ActModeAwsBedrockCustomModelBaseId = value
|
||||
case "planModeOcaModelId":
|
||||
apiConfig.PlanModeOcaModelId = value
|
||||
apiConfig.ActModeOcaModelId = value
|
||||
case "planModeHicapModelId":
|
||||
apiConfig.PlanModeHicapModelId = value
|
||||
apiConfig.ActModeHicapModelId = value
|
||||
case "planModeNousResearchModelId":
|
||||
apiConfig.PlanModeNousResearchModelId = value
|
||||
apiConfig.ActModeNousResearchModelId = value
|
||||
}
|
||||
}
|
||||
|
||||
// AddProviderPartial configures a new provider with all necessary fields using partial updates.
|
||||
func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID string, apiKey string, modelInfo interface{}) error {
|
||||
func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID string, apiKey string, baseURL string, modelInfo interface{}) error {
|
||||
// Get field mapping for this provider
|
||||
fields, err := GetProviderFields(provider)
|
||||
if err != nil {
|
||||
@@ -282,6 +335,13 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
|
||||
setAPIKeyField(apiConfig, fields.APIKeyField, proto.String(apiKey))
|
||||
}
|
||||
|
||||
// Set base URL field if provided and applicable
|
||||
includeBaseURL := false
|
||||
if baseURL != "" && fields.BaseURLField != "" {
|
||||
setBaseURLField(apiConfig, fields.BaseURLField, proto.String(baseURL))
|
||||
includeBaseURL = true
|
||||
}
|
||||
|
||||
// Set model ID fields
|
||||
apiConfig.PlanModeApiModelId = proto.String(modelID)
|
||||
apiConfig.ActModeApiModelId = proto.String(modelID)
|
||||
@@ -301,7 +361,7 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
|
||||
|
||||
// Build field mask including all fields we're setting (without provider enums)
|
||||
includeModelInfo := fields.PlanModeModelInfoField != "" && modelInfo != nil
|
||||
fieldPaths := buildProviderFieldMask(fields, true, true, includeModelInfo, false)
|
||||
fieldPaths := buildProviderFieldMask(fields, true, true, includeModelInfo, includeBaseURL, false)
|
||||
|
||||
// Create field mask
|
||||
fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
|
||||
@@ -368,7 +428,7 @@ func UpdateProviderPartial(ctx context.Context, manager *task.Manager, provider
|
||||
}
|
||||
|
||||
// Build field mask for only the fields being updated
|
||||
fieldPaths := buildProviderFieldMask(fields, includeAPIKey, includeModelID, includeModelInfo, setAsActive)
|
||||
fieldPaths := buildProviderFieldMask(fields, includeAPIKey, includeModelID, includeModelInfo, false, setAsActive)
|
||||
|
||||
// Create field mask
|
||||
fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
|
||||
@@ -421,6 +481,46 @@ func RemoveProviderPartial(ctx context.Context, manager *task.Manager, provider
|
||||
return nil
|
||||
}
|
||||
|
||||
// setBaseURLField sets the appropriate base URL field in the config based on the field name
|
||||
func setBaseURLField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
|
||||
switch fieldName {
|
||||
case "ocaBaseUrl":
|
||||
apiConfig.OcaBaseUrl = value
|
||||
case "ollamaBaseUrl":
|
||||
apiConfig.OllamaBaseUrl = value
|
||||
case "openAiBaseUrl":
|
||||
apiConfig.OpenAiBaseUrl = value
|
||||
case "geminiBaseUrl":
|
||||
apiConfig.GeminiBaseUrl = value
|
||||
case "liteLlmBaseUrl":
|
||||
apiConfig.LiteLlmBaseUrl = value
|
||||
case "anthropicBaseUrl":
|
||||
apiConfig.AnthropicBaseUrl = value
|
||||
case "requestyBaseUrl":
|
||||
apiConfig.RequestyBaseUrl = value
|
||||
case "lmStudioBaseUrl":
|
||||
apiConfig.LmStudioBaseUrl = value
|
||||
case "oca":
|
||||
apiConfig.OcaBaseUrl = value
|
||||
}
|
||||
}
|
||||
|
||||
// setRefreshTokenField sets the appropriate refresh token field in the config
|
||||
func setRefreshTokenField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
|
||||
switch fieldName {
|
||||
case "ocaRefreshToken":
|
||||
apiConfig.OcaRefreshToken = value
|
||||
}
|
||||
}
|
||||
|
||||
// setModeField sets the appropriate mode field in the config
|
||||
func setModeField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
|
||||
switch fieldName {
|
||||
case "ocaMode":
|
||||
apiConfig.OcaMode = value
|
||||
}
|
||||
}
|
||||
|
||||
// BedrockOptionalFields holds optional configuration fields for AWS Bedrock
|
||||
type BedrockOptionalFields struct {
|
||||
SessionToken *string // Optional: AWS session token for temporary credentials
|
||||
@@ -434,6 +534,12 @@ type BedrockOptionalFields struct {
|
||||
Endpoint *string // Optional: Custom endpoint URL
|
||||
}
|
||||
|
||||
// OcaOptionalFields holds optional configuration fields for Oracle Code Assist
|
||||
type OcaOptionalFields struct {
|
||||
BaseURL *string // Optional: Base URL
|
||||
Mode *string // Optional: Mode ("internal" or "external")
|
||||
}
|
||||
|
||||
// setBedrockOptionalFields sets optional Bedrock-specific fields in the API configuration
|
||||
func setBedrockOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *BedrockOptionalFields) {
|
||||
if fields == nil {
|
||||
@@ -469,6 +575,20 @@ func setBedrockOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *B
|
||||
}
|
||||
}
|
||||
|
||||
// setOcaOptionalFields sets optional Oca-specific fields in the API configuration
|
||||
func setOcaOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *OcaOptionalFields) {
|
||||
if fields == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if fields.Mode != nil {
|
||||
apiConfig.OcaMode = fields.Mode
|
||||
}
|
||||
if fields.BaseURL != nil {
|
||||
apiConfig.OcaBaseUrl = fields.BaseURL
|
||||
}
|
||||
}
|
||||
|
||||
// buildBedrockOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
|
||||
func buildBedrockOptionalFieldMask(fields *BedrockOptionalFields) []string {
|
||||
if fields == nil {
|
||||
@@ -507,3 +627,21 @@ func buildBedrockOptionalFieldMask(fields *BedrockOptionalFields) []string {
|
||||
|
||||
return fieldPaths
|
||||
}
|
||||
|
||||
// buildOcaOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
|
||||
func buildOcaOptionalFieldMask(fields *OcaOptionalFields) []string {
|
||||
if fields == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fieldPaths []string
|
||||
|
||||
if fields.Mode != nil {
|
||||
fieldPaths = append(fieldPaths, "ocaMode")
|
||||
}
|
||||
if fields.BaseURL != nil {
|
||||
fieldPaths = append(fieldPaths, "ocaBaseUrl")
|
||||
}
|
||||
|
||||
return fieldPaths
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
@@ -40,7 +41,7 @@ func (pw *ProviderWizard) showMainMenu() (string, error) {
|
||||
huh.NewSelect[string]().
|
||||
Title("What would you like to do?").
|
||||
Options(
|
||||
huh.NewOption("Configure a new provider", "add"),
|
||||
huh.NewOption("Add or change an API provider", "add"),
|
||||
huh.NewOption("Change model for API provider", "change-model"),
|
||||
huh.NewOption("Remove a provider", "remove"),
|
||||
huh.NewOption("List configured providers", "list"),
|
||||
@@ -107,8 +108,13 @@ func (pw *ProviderWizard) handleAddProvider() error {
|
||||
return pw.handleAddBedrockProvider()
|
||||
}
|
||||
|
||||
// Step 2b: Special handling for OCA provider
|
||||
if provider == cline.ApiProvider_OCA {
|
||||
return pw.handleAddOcaProvider()
|
||||
}
|
||||
|
||||
// Step 3: Get API key first (for non-Bedrock providers)
|
||||
apiKey, err := PromptForAPIKey(provider)
|
||||
apiKey, baseURL, err := PromptForAPIKey(provider)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get API key: %w", err)
|
||||
}
|
||||
@@ -120,7 +126,7 @@ func (pw *ProviderWizard) handleAddProvider() error {
|
||||
}
|
||||
|
||||
// Step 5: Apply configuration using AddProviderPartial
|
||||
if err := AddProviderPartial(pw.ctx, pw.manager, provider, modelID, apiKey, modelInfo); err != nil {
|
||||
if err := AddProviderPartial(pw.ctx, pw.manager, provider, modelID, apiKey, baseURL, modelInfo); err != nil {
|
||||
return fmt.Errorf("failed to save configuration: %w", err)
|
||||
}
|
||||
|
||||
@@ -162,6 +168,51 @@ func (pw *ProviderWizard) handleAddBedrockProvider() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleAddOcaProvider handles adding Oracle Code Assist provider with optional settings and auth
|
||||
func (pw *ProviderWizard) handleAddOcaProvider() error {
|
||||
// Step 1: Get OCA configuration (base URL and mode)
|
||||
config, err := PromptForOcaConfig(pw.ctx, pw.manager)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "user aborted") || strings.Contains(err.Error(), "cancelled") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to get OCA configuration: %w", err)
|
||||
}
|
||||
|
||||
// Apply OCA configuration (base URL and mode)
|
||||
if err := ApplyOcaConfig(pw.ctx, pw.manager, config); err != nil {
|
||||
return fmt.Errorf("failed to save OCA configuration: %w", err)
|
||||
}
|
||||
|
||||
// Step 2: Ensure OCA authentication
|
||||
if err := ensureOcaAuthenticated(pw.ctx); err != nil {
|
||||
return fmt.Errorf("failed to authenticate with OCA: %w", err)
|
||||
}
|
||||
|
||||
// Step 3: Select model
|
||||
modelID, _, err := pw.selectModel(cline.ApiProvider_OCA, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("model selection failed: %w", err)
|
||||
}
|
||||
|
||||
// Step 4: Apply the OCA model configuration and set as active
|
||||
updates := ProviderUpdatesPartial{
|
||||
ModelID: &modelID,
|
||||
ModelInfo: nil,
|
||||
}
|
||||
|
||||
if err := UpdateProviderPartial(pw.ctx, pw.manager, cline.ApiProvider_OCA, updates, true); err != nil {
|
||||
return fmt.Errorf("failed to save OCA configuration: %w", err)
|
||||
}
|
||||
|
||||
if err := setWelcomeViewCompleted(pw.ctx, pw.manager); err != nil {
|
||||
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("✓ OCA provider configured successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleListProviders retrieves and displays configured providers
|
||||
func (pw *ProviderWizard) handleListProviders() error {
|
||||
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
|
||||
@@ -259,6 +310,15 @@ func (pw *ProviderWizard) fetchModelsForProvider(provider cline.ApiProvider, api
|
||||
}
|
||||
// Ollama returns just model IDs without additional info, so modelInfo map is nil
|
||||
return modelIDs, nil, nil
|
||||
|
||||
case cline.ApiProvider_OCA:
|
||||
// OCA supports dynamic model fetching
|
||||
models, err := FetchOcaModels(pw.ctx, pw.manager)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
interfaceMap := ConvertOcaModelsToInterface(models)
|
||||
return ConvertModelsMapToSlice(interfaceMap), interfaceMap, nil
|
||||
}
|
||||
|
||||
// Fall back to static models for providers that don't support dynamic fetching
|
||||
@@ -457,7 +517,7 @@ func (pw *ProviderWizard) applyModelChange(provider cline.ApiProvider, modelID s
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, false)
|
||||
return UpdateProviderPartial(pw.ctx, pw.manager, provider, updates, true)
|
||||
}
|
||||
|
||||
// SwitchToBYOProvider switches to a BYO provider that's already configured.
|
||||
@@ -525,8 +585,17 @@ func getProviderModelIDFromState(stateData map[string]interface{}, provider clin
|
||||
return ""
|
||||
}
|
||||
|
||||
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
|
||||
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
|
||||
func getProviderAPIKeyFromState(stateData map[string]interface{}, provider cline.ApiProvider) string {
|
||||
// OCA uses account authentication, not API keys. Consider it "present" if authenticated.
|
||||
if provider == cline.ApiProvider_OCA {
|
||||
if state, _ := GetLatestOCAState(context.TODO(), 2 * time.Second); state != nil && state.User != nil {
|
||||
// Return a sentinel non-empty string so upstream checks pass.
|
||||
return "OCA_AUTH_VERIFIED"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fields, err := GetProviderFields(provider)
|
||||
if err != nil {
|
||||
return ""
|
||||
@@ -656,7 +725,16 @@ func (pw *ProviderWizard) handleRemoveProvider() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Step 7: Clear the API key for the selected provider
|
||||
// Step 7: If removing OCA, sign out first
|
||||
if selectedProvider.Provider == cline.ApiProvider_OCA {
|
||||
if err := signOutOca(pw.ctx); err != nil {
|
||||
fmt.Printf("Warning: Failed to sign out of OCA: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Signed out of OCA.")
|
||||
}
|
||||
}
|
||||
|
||||
// Step 8: Clear the API key for the selected provider
|
||||
if err := pw.clearProviderAPIKey(selectedProvider.Provider); err != nil {
|
||||
return fmt.Errorf("failed to remove provider: %w", err)
|
||||
}
|
||||
@@ -670,6 +748,16 @@ func (pw *ProviderWizard) clearProviderAPIKey(provider cline.ApiProvider) error
|
||||
return RemoveProviderPartial(pw.ctx, pw.manager, provider)
|
||||
}
|
||||
|
||||
|
||||
func signOutOca(ctx context.Context) error {
|
||||
client, err := global.GetDefaultClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = client.Ocaaccount.OcaAccountLogoutClicked(ctx, &cline.EmptyRequest{})
|
||||
return err
|
||||
}
|
||||
|
||||
func setWelcomeViewCompleted(ctx context.Context, manager *task.Manager) error {
|
||||
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
// OcaConfig holds Oracle Code Assist (OCA) configuration fields
|
||||
type OcaConfig struct {
|
||||
BaseURL string
|
||||
Mode string
|
||||
}
|
||||
|
||||
// PromptForOcaConfig displays a form for OCA configuration (base URL and mode)
|
||||
func PromptForOcaConfig(ctx context.Context, manager *task.Manager) (*OcaConfig, error) {
|
||||
config := &OcaConfig{}
|
||||
var mode string
|
||||
|
||||
// Collect optional settings
|
||||
configForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Title("Base URL").
|
||||
Value(&config.BaseURL).
|
||||
Description("Leave empty to use default Base URL"),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Title("Choose OCA mode (used for authentication)").
|
||||
Description("Select 'Internal' to use Cline's internal OCA, or 'External' for your own OCA instance").
|
||||
Options(
|
||||
huh.NewOption("Internal", "internal"),
|
||||
huh.NewOption("External", "external"),
|
||||
).
|
||||
Value(&mode),
|
||||
),
|
||||
)
|
||||
|
||||
if err := configForm.Run(); err != nil {
|
||||
return nil, fmt.Errorf("failed to get OCA configuration: %w", err)
|
||||
}
|
||||
|
||||
// Trim whitespace from string fields
|
||||
config.BaseURL = strings.TrimSpace(config.BaseURL)
|
||||
config.Mode = strings.TrimSpace(mode)
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// ApplyOcaConfig applies OCA configuration using partial updates
|
||||
func ApplyOcaConfig(ctx context.Context, manager *task.Manager, config *OcaConfig) error {
|
||||
// Build the API configuration with all OCA fields
|
||||
apiConfig := &cline.ModelsApiConfiguration{}
|
||||
|
||||
// Set profile authentication fields (always required)
|
||||
optionalFields := &OcaOptionalFields{}
|
||||
|
||||
// Set profile name (can be empty for default profile)
|
||||
if config.BaseURL != "" {
|
||||
optionalFields.BaseURL = proto.String(config.BaseURL)
|
||||
}
|
||||
|
||||
// Set optional fields if provided
|
||||
if config.Mode != "" {
|
||||
optionalFields.Mode = proto.String(config.Mode)
|
||||
}
|
||||
|
||||
// Apply all fields to the config
|
||||
setOcaOptionalFields(apiConfig, optionalFields)
|
||||
|
||||
// Add profile authentication field paths
|
||||
optionalPaths := buildOcaOptionalFieldMask(optionalFields)
|
||||
|
||||
// Create field mask
|
||||
fieldMask := &fieldmaskpb.FieldMask{Paths: optionalPaths}
|
||||
|
||||
// Apply the partial update
|
||||
request := &cline.UpdateApiConfigurationPartialRequest{
|
||||
ApiConfiguration: apiConfig,
|
||||
UpdateMask: fieldMask,
|
||||
}
|
||||
|
||||
if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
|
||||
return fmt.Errorf("failed to apply OCA configuration: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// OCA Auth Listener Singleton
|
||||
// ===========================
|
||||
|
||||
type ocaAuthStream interface {
|
||||
Recv() (*cline.OcaAuthState, error)
|
||||
}
|
||||
|
||||
// OcaAuthStatusListener manages subscription to OCA auth status updates
|
||||
type OcaAuthStatusListener struct {
|
||||
stream ocaAuthStream
|
||||
updatesCh chan *cline.OcaAuthState
|
||||
errCh chan error
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mu sync.RWMutex
|
||||
lastState *cline.OcaAuthState
|
||||
firstEventCh chan struct{}
|
||||
firstEventOnce sync.Once
|
||||
}
|
||||
|
||||
// NewOcaAuthStatusListener creates a new OCA auth status listener
|
||||
func NewOcaAuthStatusListener(parentCtx context.Context) (*OcaAuthStatusListener, error) {
|
||||
client, err := global.GetDefaultClient(parentCtx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client: %w", err)
|
||||
}
|
||||
|
||||
// Keep the listener alive independently of short-lived caller contexts
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
// Subscribe to OCA auth status updates
|
||||
stream, err := client.Ocaaccount.OcaSubscribeToAuthStatusUpdate(ctx, &cline.EmptyRequest{})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed to subscribe to OCA auth updates: %w", err)
|
||||
}
|
||||
|
||||
return &OcaAuthStatusListener{
|
||||
stream: stream,
|
||||
updatesCh: make(chan *cline.OcaAuthState, 10),
|
||||
errCh: make(chan error, 1),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
firstEventCh: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start begins listening to the auth status update stream
|
||||
func (l *OcaAuthStatusListener) Start() error {
|
||||
go l.readStream()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *OcaAuthStatusListener) readStream() {
|
||||
defer close(l.updatesCh)
|
||||
defer close(l.errCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
default:
|
||||
state, err := l.stream.Recv()
|
||||
if err != nil {
|
||||
// Propagate error and exit
|
||||
if err == io.EOF {
|
||||
// Treat as error to notify waiters
|
||||
err = fmt.Errorf("OCA auth status stream closed")
|
||||
}
|
||||
select {
|
||||
case l.errCh <- err:
|
||||
case <-l.ctx.Done():
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
l.lastState = state
|
||||
l.mu.Unlock()
|
||||
|
||||
// Notify first event waiters
|
||||
l.firstEventOnce.Do(func() { close(l.firstEventCh) })
|
||||
|
||||
select {
|
||||
case l.updatesCh <- state:
|
||||
case <-l.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForFirstEvent blocks until the first event is received or timeout occurs
|
||||
func (l *OcaAuthStatusListener) WaitForFirstEvent(timeout time.Duration) error {
|
||||
// Fast-path if already have a state
|
||||
l.mu.RLock()
|
||||
ready := l.lastState != nil
|
||||
l.mu.RUnlock()
|
||||
if ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-l.firstEventCh:
|
||||
return nil
|
||||
case <-timer.C:
|
||||
return fmt.Errorf("timeout waiting for initial OCA auth event")
|
||||
case <-l.ctx.Done():
|
||||
return fmt.Errorf("OCA auth listener cancelled")
|
||||
}
|
||||
}
|
||||
|
||||
// IsAuthenticated returns true if the last known OCA auth state is authenticated
|
||||
func (l *OcaAuthStatusListener) IsAuthenticated() bool {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return isOCAStateAuthenticated(l.lastState)
|
||||
}
|
||||
|
||||
// WaitForAuthentication waits until OCA authentication succeeds or timeout occurs
|
||||
func (l *OcaAuthStatusListener) WaitForAuthentication(timeout time.Duration) error {
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
// If already authenticated, return immediately
|
||||
if l.IsAuthenticated() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timer.C:
|
||||
return fmt.Errorf("OCA authentication timeout after %v - please try again", timeout)
|
||||
case <-l.ctx.Done():
|
||||
return fmt.Errorf("OCA authentication cancelled")
|
||||
case err := <-l.errCh:
|
||||
return fmt.Errorf("OCA authentication stream error: %w", err)
|
||||
case state := <-l.updatesCh:
|
||||
if isOCAStateAuthenticated(state) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop closes the stream and cleans up resources
|
||||
func (l *OcaAuthStatusListener) Stop() {
|
||||
l.cancel()
|
||||
}
|
||||
|
||||
func isOCAStateAuthenticated(state *cline.OcaAuthState) bool {
|
||||
return state != nil && state.User != nil
|
||||
}
|
||||
|
||||
// Singleton holder
|
||||
var (
|
||||
ocaListener *OcaAuthStatusListener
|
||||
ocaListenerOnce sync.Once
|
||||
ocaListenerErr error
|
||||
)
|
||||
|
||||
// GetOcaAuthListener returns the OCA auth listener singleton
|
||||
func GetOcaAuthListener(ctx context.Context) (*OcaAuthStatusListener, error) {
|
||||
// Allow optional ctx: if nil, use context.TODO(). If already initialized, return singleton.
|
||||
if ctx == nil {
|
||||
ctx = context.TODO()
|
||||
}
|
||||
|
||||
ocaListenerOnce.Do(func() {
|
||||
l, err := NewOcaAuthStatusListener(ctx)
|
||||
if err != nil {
|
||||
ocaListenerErr = err
|
||||
return
|
||||
}
|
||||
if err := l.Start(); err != nil {
|
||||
ocaListenerErr = err
|
||||
return
|
||||
}
|
||||
ocaListener = l
|
||||
})
|
||||
return ocaListener, ocaListenerErr
|
||||
}
|
||||
|
||||
// IsOCAAuthenticated returns true if the global OCA auth status is authenticated.
|
||||
// It attempts a brief wait for the first event to avoid stale reads.
|
||||
func IsOCAAuthenticated(ctx context.Context) bool {
|
||||
l, err := GetOcaAuthListener(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_ = l.WaitForFirstEvent(1 * time.Second) // best-effort
|
||||
return l.IsAuthenticated()
|
||||
}
|
||||
|
||||
// LatestState returns the last received OCA auth state (may be nil)
|
||||
func (l *OcaAuthStatusListener) LatestState() *cline.OcaAuthState {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
return l.lastState
|
||||
}
|
||||
|
||||
// GetLatestOCAState returns the latest known OCA auth state, optionally waiting for the first event
|
||||
func GetLatestOCAState(ctx context.Context, timeout time.Duration) (*cline.OcaAuthState, error) {
|
||||
l, err := GetOcaAuthListener(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if timeout > 0 {
|
||||
if err := l.WaitForFirstEvent(timeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return l.LatestState(), nil
|
||||
}
|
||||
|
||||
// ensureOcaAuthenticated initiates OCA login (if needed) and waits for success using the singleton listener
|
||||
func ensureOcaAuthenticated(ctx context.Context) error {
|
||||
// Ensure listener exists
|
||||
listener, err := GetOcaAuthListener(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize OCA auth listener: %w", err)
|
||||
}
|
||||
|
||||
// Briefly wait for first event to know current state
|
||||
_ = listener.WaitForFirstEvent(1 * time.Second)
|
||||
|
||||
// If already authenticated, nothing to do
|
||||
if listener.IsAuthenticated() {
|
||||
fmt.Println("✓ OCA authentication already active.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create gRPC client for initiating login
|
||||
client, err := global.GetDefaultClient(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to obtain client: %w", err)
|
||||
}
|
||||
|
||||
// Start login and wait for authentication
|
||||
waitCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Initiate login (opens the browser with a callback URL from Cline Core)
|
||||
response, err := client.Ocaaccount.OcaAccountLoginClicked(waitCtx, &cline.EmptyRequest{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initiate OCA login: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("\nOpening browser for OCA authentication...")
|
||||
if response != nil && response.Value != "" {
|
||||
fmt.Printf("If the browser doesn't open automatically, visit this URL:\n%s\n\n", response.Value)
|
||||
}
|
||||
fmt.Println("Waiting for you to complete OCA authentication in your browser...")
|
||||
fmt.Println("(This may take a few moments. Timeout: 5 minutes)")
|
||||
|
||||
// Block until authenticated or timeout
|
||||
if err := listener.WaitForAuthentication(5 * time.Minute); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("✓ OCA authentication successful!")
|
||||
return nil
|
||||
}
|
||||
@@ -123,7 +123,10 @@ func setCommand() *cobra.Command {
|
||||
Use: "set <key=value> [key=value...]",
|
||||
Aliases: []string{"s"},
|
||||
Short: "Set configuration variables",
|
||||
Long: `Set one or more global configuration variables using key=value format.`,
|
||||
Long: `Set one or more global configuration variables using key=value format.
|
||||
|
||||
This command merges the provided settings with existing values, preserving
|
||||
unspecified fields. Only the fields you explicitly set will be updated.`,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
@@ -139,7 +142,7 @@ func setCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update settings
|
||||
// Update settings (server-side merge handles preserving existing values)
|
||||
return configManager.UpdateSettings(ctx, settings, secrets)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func renderAutoApprovalSettings(value interface{}, censor bool) error {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Print other fields normally (enabled, maxRequests, enableNotifications, favorites)
|
||||
// Print other fields normally (enabled, enableNotifications, favorites)
|
||||
fmt.Printf(" %s: %s\n", camelToKebab(key), formatValue(val, key, censor))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/output"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
@@ -14,6 +15,16 @@ type Renderer struct {
|
||||
typewriter *TypewriterPrinter
|
||||
mdRenderer *MarkdownRenderer
|
||||
outputFormat string
|
||||
|
||||
// Lipgloss styles that respect outputFormat
|
||||
dimStyle lipgloss.Style
|
||||
greenStyle lipgloss.Style
|
||||
redStyle lipgloss.Style
|
||||
yellowStyle lipgloss.Style
|
||||
blueStyle lipgloss.Style
|
||||
whiteStyle lipgloss.Style
|
||||
boldStyle lipgloss.Style
|
||||
successStyle lipgloss.Style
|
||||
}
|
||||
|
||||
func NewRenderer(outputFormat string) *Renderer {
|
||||
@@ -22,11 +33,23 @@ func NewRenderer(outputFormat string) *Renderer {
|
||||
mdRenderer = nil
|
||||
}
|
||||
|
||||
return &Renderer{
|
||||
r := &Renderer{
|
||||
typewriter: NewTypewriterPrinter(DefaultTypewriterConfig()),
|
||||
mdRenderer: mdRenderer,
|
||||
outputFormat: outputFormat,
|
||||
}
|
||||
|
||||
// Initialize lipgloss styles (will respect the global color profile)
|
||||
r.dimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
||||
r.greenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2"))
|
||||
r.redStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
|
||||
r.yellowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
|
||||
r.blueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39"))
|
||||
r.whiteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("7"))
|
||||
r.boldStyle = lipgloss.NewStyle().Bold(true)
|
||||
r.successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Renderer) RenderMessage(prefix, text string, newline bool) error {
|
||||
@@ -206,21 +229,76 @@ func (r *Renderer) GetMdRenderer() *MarkdownRenderer {
|
||||
|
||||
// RenderMarkdown renders markdown text to terminal format with ANSI codes
|
||||
// Falls back to plaintext if markdown rendering is unavailable or fails
|
||||
// Respects output format - skips rendering in plain mode
|
||||
// Respects output format - skips rendering in plain mode or non-TTY contexts
|
||||
func (r *Renderer) RenderMarkdown(markdown string) string {
|
||||
// Skip markdown rendering in plain mode
|
||||
if r.outputFormat == "plain" {
|
||||
// Skip markdown rendering if:
|
||||
// 1. Output format is explicitly "plain"
|
||||
// 2. Not in a TTY (piped output, file redirect, CI, etc.)
|
||||
if r.outputFormat == "plain" || !isTTY() {
|
||||
return markdown
|
||||
}
|
||||
|
||||
|
||||
if r.mdRenderer == nil {
|
||||
return markdown
|
||||
}
|
||||
|
||||
|
||||
rendered, err := r.mdRenderer.Render(markdown)
|
||||
if err != nil {
|
||||
return markdown
|
||||
}
|
||||
|
||||
|
||||
return rendered
|
||||
}
|
||||
|
||||
// Lipgloss-based color rendering methods
|
||||
// These automatically respect the output format via lipgloss color profile
|
||||
|
||||
// Dim renders text in dim gray (bright black)
|
||||
func (r *Renderer) Dim(text string) string {
|
||||
return r.dimStyle.Render(text)
|
||||
}
|
||||
|
||||
// Green renders text in green
|
||||
func (r *Renderer) Green(text string) string {
|
||||
return r.greenStyle.Render(text)
|
||||
}
|
||||
|
||||
// Red renders text in red
|
||||
func (r *Renderer) Red(text string) string {
|
||||
return r.redStyle.Render(text)
|
||||
}
|
||||
|
||||
// Yellow renders text in yellow
|
||||
func (r *Renderer) Yellow(text string) string {
|
||||
return r.yellowStyle.Render(text)
|
||||
}
|
||||
|
||||
// Blue renders text in 256-color blue (index 39)
|
||||
func (r *Renderer) Blue(text string) string {
|
||||
return r.blueStyle.Render(text)
|
||||
}
|
||||
|
||||
// White renders text in white
|
||||
func (r *Renderer) White(text string) string {
|
||||
return r.whiteStyle.Render(text)
|
||||
}
|
||||
|
||||
// Bold renders text in bold
|
||||
func (r *Renderer) Bold(text string) string {
|
||||
return r.boldStyle.Render(text)
|
||||
}
|
||||
|
||||
// Success renders text in green with bold
|
||||
func (r *Renderer) Success(text string) string {
|
||||
return r.successStyle.Render(text)
|
||||
}
|
||||
|
||||
// SuccessWithCheckmark renders text in green with bold and a checkmark prefix
|
||||
func (r *Renderer) SuccessWithCheckmark(text string) string {
|
||||
return r.Success("✓ " + text)
|
||||
}
|
||||
|
||||
// ErrorWithX renders text in red with an X prefix
|
||||
func (r *Renderer) ErrorWithX(text string) string {
|
||||
return r.Red("✗ " + text)
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ func NewStreamingSegment(sayType, prefix string, mdRenderer *MarkdownRenderer, s
|
||||
toolParser: NewToolResultParser(mdRenderer),
|
||||
}
|
||||
|
||||
// Render rich header immediately when creating segment (if in rich mode)
|
||||
if shouldMarkdown && outputFormat != "plain" {
|
||||
// Render rich header immediately when creating segment (if in rich mode and TTY)
|
||||
if shouldMarkdown && outputFormat != "plain" && isTTY() {
|
||||
header := ss.generateRichHeader()
|
||||
rendered, _ := mdRenderer.Render(header)
|
||||
output.Println("")
|
||||
@@ -113,8 +113,8 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
|
||||
} else if ss.sayType == string(types.SayTypeCommand) {
|
||||
// Command output
|
||||
bodyContent = "```shell\n" + currentBuffer + "\n```"
|
||||
// Render markdown
|
||||
if ss.shouldMarkdown && ss.outputFormat != "plain" {
|
||||
// Render markdown only in rich mode and TTY
|
||||
if ss.shouldMarkdown && ss.outputFormat != "plain" && isTTY() {
|
||||
rendered, err := ss.mdRenderer.Render(bodyContent)
|
||||
if err == nil {
|
||||
bodyContent = rendered
|
||||
@@ -122,7 +122,7 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
|
||||
}
|
||||
} else {
|
||||
// For other types (reasoning, text, etc.), render markdown as-is
|
||||
if ss.shouldMarkdown && ss.outputFormat != "plain" {
|
||||
if ss.shouldMarkdown && ss.outputFormat != "plain" && isTTY() {
|
||||
rendered, err := ss.mdRenderer.Render(currentBuffer)
|
||||
if err == nil {
|
||||
bodyContent = rendered
|
||||
|
||||
@@ -106,6 +106,14 @@ func (tr *ToolRenderer) generateToolHeader(tool *types.ToolMessage, verbTense st
|
||||
}
|
||||
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
|
||||
|
||||
case string(types.ToolTypeFileDeleted):
|
||||
if verbTense == "wants to" {
|
||||
action = "wants to delete"
|
||||
} else {
|
||||
action = "is deleting"
|
||||
}
|
||||
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
|
||||
|
||||
case string(types.ToolTypeListFilesTopLevel):
|
||||
if verbTense == "wants to" {
|
||||
action = "wants to list files in"
|
||||
@@ -199,7 +207,7 @@ func (tr *ToolRenderer) GenerateToolContentPreview(tool *types.ToolMessage) stri
|
||||
previewMd := fmt.Sprintf("```\n%s\n```", preview)
|
||||
return tr.renderMarkdown(previewMd)
|
||||
|
||||
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch):
|
||||
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch), string(types.ToolTypeFileDeleted):
|
||||
// No preview for read/fetch operations
|
||||
return ""
|
||||
|
||||
@@ -226,7 +234,8 @@ func (tr *ToolRenderer) GenerateToolContentBody(tool *types.ToolMessage) string
|
||||
toolParser := NewToolResultParser(tr.mdRenderer)
|
||||
|
||||
switch tool.Tool {
|
||||
case string(types.ToolTypeReadFile):
|
||||
case string(types.ToolTypeReadFile),
|
||||
string(types.ToolTypeFileDeleted):
|
||||
// readFile: show header only, no body
|
||||
return ""
|
||||
|
||||
@@ -339,9 +348,10 @@ func (tr *ToolRenderer) RenderUserResponse(approved bool, feedback string) strin
|
||||
return fmt.Sprintf("%s %s\n", symbol, status)
|
||||
}
|
||||
|
||||
// renderMarkdown renders markdown if not in plain mode
|
||||
// renderMarkdown renders markdown if not in plain mode and in a TTY
|
||||
func (tr *ToolRenderer) renderMarkdown(markdown string) string {
|
||||
if tr.outputFormat == "plain" {
|
||||
// Skip markdown rendering if plain mode or not in TTY
|
||||
if tr.outputFormat == "plain" || !isTTY() {
|
||||
return markdown
|
||||
}
|
||||
|
||||
|
||||
@@ -221,83 +221,7 @@ func (p *ToolResultParser) ParseCodeDefinitions(content string) string {
|
||||
|
||||
// ParseWebFetch formats webFetch tool results with content preview
|
||||
func (p *ToolResultParser) ParseWebFetch(content, url string) string {
|
||||
if content == "" {
|
||||
return fmt.Sprintf("*Fetched content from %s (empty response)*", url)
|
||||
}
|
||||
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var result strings.Builder
|
||||
|
||||
// Try to extract title
|
||||
var title string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "##") {
|
||||
title = strings.TrimSpace(strings.TrimPrefix(trimmed, "#"))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if title != "" {
|
||||
result.WriteString(fmt.Sprintf("**Title:** %s\n\n", title))
|
||||
}
|
||||
|
||||
// Show preview of content
|
||||
result.WriteString("**Preview:**\n")
|
||||
|
||||
charCount := 0
|
||||
maxChars := 500
|
||||
previewLines := []string{}
|
||||
|
||||
for _, line := range lines {
|
||||
// Skip markdown headers
|
||||
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if charCount+len(trimmed) > maxChars {
|
||||
break
|
||||
}
|
||||
|
||||
previewLines = append(previewLines, trimmed)
|
||||
charCount += len(trimmed)
|
||||
}
|
||||
|
||||
result.WriteString(strings.Join(previewLines, " "))
|
||||
result.WriteString("...\n\n")
|
||||
|
||||
// Extract sections
|
||||
sections := []string{}
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "##") {
|
||||
section := strings.TrimSpace(strings.TrimPrefix(trimmed, "##"))
|
||||
sections = append(sections, section)
|
||||
if len(sections) >= 5 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(sections) > 0 {
|
||||
result.WriteString("**Sections Found:**\n")
|
||||
for _, section := range sections {
|
||||
result.WriteString(fmt.Sprintf("- %s\n", section))
|
||||
}
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
// Word count estimate
|
||||
wordCount := len(strings.Fields(content))
|
||||
result.WriteString(fmt.Sprintf("*[Full content: ~%s]*", p.formatWordCount(wordCount)))
|
||||
|
||||
return result.String()
|
||||
return ""
|
||||
}
|
||||
|
||||
// detectLanguage returns syntax highlighting language based on file extension
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/display"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/terminal"
|
||||
"github.com/cline/cli/pkg/cli/updater"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDoctorCommand creates the doctor command
|
||||
func NewDoctorCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "doctor",
|
||||
Aliases: []string{"d"},
|
||||
Short: "Check system health and diagnose problems",
|
||||
Long: `Check the health of your Cline CLI installation and diagnose problems.
|
||||
|
||||
Currently this command performs the following checks and fixes:
|
||||
|
||||
Terminal Configuration:
|
||||
- Detects your terminal emulator (VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty)
|
||||
- Configures shift+enter to insert newlines in multiline input
|
||||
- Creates backups before modifying configuration files
|
||||
- Supported terminals: VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty
|
||||
- iTerm2 works by default, Terminal.app requires manual setup
|
||||
|
||||
CLI Updates:
|
||||
- Checks npm registry for the latest version
|
||||
- Automatically installs updates via npm if available
|
||||
- Respects NO_AUTO_UPDATE environment variable
|
||||
- Skipped in CI environments
|
||||
|
||||
Note: Future versions will include additional health checks for Node.js version,
|
||||
npm availability, Cline Core connectivity, database integrity, and more.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDoctorChecks()
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// runDoctorChecks performs all doctor diagnostics and configuration
|
||||
func runDoctorChecks() error {
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
|
||||
fmt.Printf("\n%s\n\n", renderer.Bold("Cline Doctor - System Health Check"))
|
||||
|
||||
// Configure terminal keybindings (terminal.go prints its own status)
|
||||
fmt.Printf("%s\n\n", renderer.Dim("━━━ Terminal Configuration ━━━"))
|
||||
terminal.SetupKeyboardSync()
|
||||
|
||||
// Check for updates (updater.go prints its own status)
|
||||
fmt.Printf("\n%s\n\n", renderer.Dim("━━━ CLI Updates ━━━"))
|
||||
updater.CheckAndUpdateSync(global.Config.Verbose, true)
|
||||
|
||||
// Summary
|
||||
fmt.Printf("\n%s\n", renderer.Dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"))
|
||||
fmt.Printf("\n%s\n\n", renderer.SuccessWithCheckmark("Health check complete"))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/common"
|
||||
"github.com/cline/grpc-go/client"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
type Port uint16
|
||||
@@ -47,6 +49,12 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
|
||||
return fmt.Errorf("failed to create config directory: %w", err)
|
||||
}
|
||||
|
||||
// Configure lipgloss color profile based on output format
|
||||
if cfg.OutputFormat == "plain" {
|
||||
lipgloss.SetColorProfile(termenv.Ascii) // NO COLOR mode
|
||||
}
|
||||
// Otherwise lipgloss auto-detects terminal capabilities (default behavior)
|
||||
|
||||
Config = cfg
|
||||
Clients = NewClineClients(cfg.ConfigPath)
|
||||
|
||||
|
||||
@@ -52,8 +52,6 @@ func (h *AskHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
return h.handleResumeCompletedTask(msg, dc)
|
||||
case string(types.AskTypeMistakeLimitReached):
|
||||
return h.handleMistakeLimitReached(msg, dc)
|
||||
case string(types.AskTypeAutoApprovalMaxReached):
|
||||
return h.handleAutoApprovalMaxReached(msg, dc)
|
||||
case string(types.AskTypeBrowserActionLaunch):
|
||||
return h.handleBrowserActionLaunch(msg, dc)
|
||||
case string(types.AskTypeUseMcpServer):
|
||||
@@ -128,8 +126,8 @@ func (h *AskHandler) handlePlanModeRespond(msg *types.ClineMessage, dc *DisplayC
|
||||
// showApprovalHint displays a hint in non-interactive mode about how to approve/deny
|
||||
func (h *AskHandler) showApprovalHint(dc *DisplayContext) {
|
||||
if !dc.IsInteractive {
|
||||
output.Printf("\n\033[90mCline is requesting approval to use this tool\033[0m\n")
|
||||
output.Printf("\033[90mUse \033[0mcline task send --approve\033[90m or \033[0m--deny\033[90m to respond\033[0m\n")
|
||||
output.Printf("\n%s\n", dc.Renderer.Dim("Cline is requesting approval to use this tool"))
|
||||
output.Printf("%s\n", dc.Renderer.Dim("Use cline task send --approve or --deny to respond"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,25 +253,6 @@ func (h *AskHandler) handleMistakeLimitReached(msg *types.ClineMessage, dc *Disp
|
||||
return dc.Renderer.RenderMessage("ERROR", fmt.Sprintf("Mistake Limit Reached: %s. Approval required.", msg.Text), true)
|
||||
}
|
||||
|
||||
// handleAutoApprovalMaxReached handles auto-approval max reached
|
||||
func (h *AskHandler) handleAutoApprovalMaxReached(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
if dc.SystemRenderer != nil {
|
||||
details := make(map[string]string)
|
||||
if msg.Text != "" {
|
||||
details["reason"] = msg.Text
|
||||
}
|
||||
dc.SystemRenderer.RenderError(
|
||||
"warning",
|
||||
"Auto-Approval Limit Reached",
|
||||
"The maximum number of auto-approved requests has been reached. Manual approval is now required.",
|
||||
details,
|
||||
)
|
||||
fmt.Printf("\n**Approval required to continue.**\n")
|
||||
return nil
|
||||
}
|
||||
return dc.Renderer.RenderMessage("WARNING", fmt.Sprintf("Auto-approval limit reached: %s. Approval required.", msg.Text), true)
|
||||
}
|
||||
|
||||
// handleBrowserActionLaunch handles browser action launch requests
|
||||
func (h *AskHandler) handleBrowserActionLaunch(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
url := strings.TrimSpace(msg.Text)
|
||||
|
||||
@@ -389,20 +389,21 @@ func newInstanceListCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
// Render the markdown table with terminal width for nice table layout
|
||||
renderer, err := display.NewMarkdownRendererForTerminal()
|
||||
mdRenderer, err := display.NewMarkdownRendererForTerminal()
|
||||
if err != nil {
|
||||
// Fallback to plain table if markdown renderer fails
|
||||
fmt.Println(markdown.String())
|
||||
} else {
|
||||
rendered, err := renderer.Render(markdown.String())
|
||||
rendered, err := mdRenderer.Render(markdown.String())
|
||||
if err != nil {
|
||||
fmt.Println(markdown.String())
|
||||
} else {
|
||||
// Post-process to colorize status values
|
||||
rendered = strings.ReplaceAll(rendered, "SERVING", "\033[32mSERVING\033[0m") // Green
|
||||
rendered = strings.ReplaceAll(rendered, "✓", "\033[32m✓\033[0m") // Green
|
||||
rendered = strings.ReplaceAll(rendered, "NOT_SERVING", "\033[31mNOT_SERVING\033[0m") // Red
|
||||
rendered = strings.ReplaceAll(rendered, "UNKNOWN", "\033[33mUNKNOWN\033[0m") // Yellow
|
||||
colorRenderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
rendered = strings.ReplaceAll(rendered, "SERVING", colorRenderer.Green("SERVING"))
|
||||
rendered = strings.ReplaceAll(rendered, "✓", colorRenderer.Green("✓"))
|
||||
rendered = strings.ReplaceAll(rendered, "NOT_SERVING", colorRenderer.Red("NOT_SERVING"))
|
||||
rendered = strings.ReplaceAll(rendered, "UNKNOWN", colorRenderer.Yellow("UNKNOWN"))
|
||||
|
||||
fmt.Print(strings.TrimLeft(rendered, "\n"))
|
||||
}
|
||||
|
||||
+6
-5
@@ -208,9 +208,9 @@ func listLogFiles(logsDir string) ([]logFileInfo, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by created time (newest first)
|
||||
// Sort by created time (oldest first)
|
||||
sort.Slice(logs, func(i, j int) bool {
|
||||
return logs[i].created.After(logs[j].created)
|
||||
return logs[i].created.Before(logs[j].created)
|
||||
})
|
||||
|
||||
return logs, nil
|
||||
@@ -340,6 +340,7 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
|
||||
}
|
||||
|
||||
// Use markdown table for rich output
|
||||
colorRenderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
var markdown strings.Builder
|
||||
markdown.WriteString("| **FILENAME** | **SIZE** | **CREATED** | **AGE** |\n")
|
||||
markdown.WriteString("|--------------|----------|-------------|---------|")
|
||||
@@ -352,9 +353,9 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
|
||||
row.age,
|
||||
)
|
||||
|
||||
// If marking for deletion, wrap in red ANSI codes
|
||||
// If marking for deletion, wrap in red
|
||||
if markForDeletion {
|
||||
line = "\033[31m" + line + "\033[0m"
|
||||
line = colorRenderer.Red(line)
|
||||
}
|
||||
|
||||
markdown.WriteString(line)
|
||||
@@ -378,4 +379,4 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
|
||||
fmt.Println()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
+46
-20
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/task"
|
||||
"github.com/cline/cli/pkg/cli/updater"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -393,7 +394,7 @@ func newTaskViewCommand() *cobra.Command {
|
||||
return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), false)
|
||||
} else if followComplete {
|
||||
// Follow until completion
|
||||
return taskManager.FollowConversationUntilCompletion(ctx)
|
||||
return taskManager.FollowConversationUntilCompletion(ctx, task.DefaultFollowOptions())
|
||||
} else {
|
||||
// Default: show snapshot
|
||||
return taskManager.ShowConversation(ctx)
|
||||
@@ -476,15 +477,34 @@ func newTaskOpenCommand() *cobra.Command {
|
||||
return fmt.Errorf("failed to parse settings: %w", err)
|
||||
}
|
||||
|
||||
// Create config manager to apply settings
|
||||
configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create config manager: %w", err)
|
||||
// Apply task-specific settings using UpdateTaskSettings RPC
|
||||
if parsedSettings != nil {
|
||||
_, err = taskManager.GetClient().State.UpdateTaskSettings(ctx, &cline.UpdateTaskSettingsRequest{
|
||||
Settings: parsedSettings,
|
||||
TaskId: &taskID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to apply task settings: %w", err)
|
||||
}
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("Task-specific settings applied successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the settings to the instance
|
||||
if err := configManager.UpdateSettings(ctx, parsedSettings, secrets); err != nil {
|
||||
return fmt.Errorf("failed to apply settings: %w", err)
|
||||
// Handle secrets separately if provided (they must go to global config)
|
||||
if secrets != nil {
|
||||
// Secrets are always global, not task-specific
|
||||
configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create config manager: %w", err)
|
||||
}
|
||||
|
||||
if err := configManager.UpdateSettings(ctx, nil, secrets); err != nil {
|
||||
return fmt.Errorf("failed to apply secrets: %w", err)
|
||||
}
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("Global secrets applied successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,17 +592,20 @@ func getContentFromStdinAndArgs(args []string) (string, error) {
|
||||
|
||||
// Check if data is being piped to stdin
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
stdinBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read from stdin: %w", err)
|
||||
}
|
||||
|
||||
stdinContent := strings.TrimSpace(string(stdinBytes))
|
||||
if stdinContent != "" {
|
||||
if content.Len() > 0 {
|
||||
content.WriteString(" ")
|
||||
// Only try to read if there's actually data available
|
||||
if stat.Size() > 0 {
|
||||
stdinBytes, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read from stdin: %w", err)
|
||||
}
|
||||
|
||||
stdinContent := strings.TrimSpace(string(stdinBytes))
|
||||
if stdinContent != "" {
|
||||
if content.Len() > 0 {
|
||||
content.WriteString(" ")
|
||||
}
|
||||
content.WriteString(stdinContent)
|
||||
}
|
||||
content.WriteString(stdinContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,8 +668,11 @@ func CreateAndFollowTask(ctx context.Context, prompt string, opts TaskOptions) e
|
||||
// If yolo mode is enabled, follow until completion (non-interactive)
|
||||
// Otherwise, follow in interactive mode
|
||||
if opts.Yolo {
|
||||
return taskManager.FollowConversationUntilCompletion(ctx)
|
||||
// Skip active task check since we just created the task
|
||||
return taskManager.FollowConversationUntilCompletion(ctx, task.FollowOptions{
|
||||
SkipActiveTaskCheck: true,
|
||||
})
|
||||
} else {
|
||||
return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package task
|
||||
|
||||
// FollowOptions contains options for following a conversation
|
||||
type FollowOptions struct {
|
||||
// SkipActiveTaskCheck skips the check for an active task
|
||||
// This is useful when following a task that was just created to avoid race conditions
|
||||
SkipActiveTaskCheck bool
|
||||
}
|
||||
|
||||
// DefaultFollowOptions returns the default options for following a conversation
|
||||
func DefaultFollowOptions() FollowOptions {
|
||||
return FollowOptions{
|
||||
SkipActiveTaskCheck: false,
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/output"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
@@ -164,6 +165,10 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
|
||||
// Check for mode switch commands first
|
||||
newMode, remainingMessage, isModeSwitch := ih.parseModeSwitch(message)
|
||||
if isModeSwitch {
|
||||
// Create styles for mode switch messages (respect global color profile)
|
||||
actStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
|
||||
planStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Bold(true)
|
||||
|
||||
if remainingMessage != "" {
|
||||
// Switching with a message - behavior differs by mode
|
||||
if newMode == "act" {
|
||||
@@ -172,16 +177,14 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
|
||||
output.Printf("\nError switching to act mode with message: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// 256-color index 39 for act mode (matches lipgloss color "39" in input form)
|
||||
output.Printf("\n\033[38;5;39m\033[1mSwitched to act mode\033[0m\n")
|
||||
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
|
||||
} else {
|
||||
// Plan mode: must switch first, then send message separately
|
||||
if err := ih.manager.SetMode(ctx, newMode, nil, nil, nil); err != nil {
|
||||
output.Printf("\nError switching to plan mode: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// Yellow color for plan mode (ANSI color 3)
|
||||
output.Printf("\n\033[33m\033[1mSwitched to plan mode\033[0m\n")
|
||||
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
|
||||
|
||||
// Now send the message separately
|
||||
time.Sleep(500 * time.Millisecond) // Give mode switch time to process
|
||||
@@ -198,9 +201,9 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
|
||||
}
|
||||
// Color based on mode
|
||||
if newMode == "act" {
|
||||
output.Printf("\n\033[38;5;39m\033[1mSwitched to act mode\033[0m\n")
|
||||
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
|
||||
} else {
|
||||
output.Printf("\n\033[33m\033[1mSwitched to plan mode\033[0m\n")
|
||||
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +256,8 @@ func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
|
||||
case types.ToolTypeEditedExistingFile,
|
||||
types.ToolTypeNewFileCreated:
|
||||
return "edit_files", nil
|
||||
case types.ToolTypeFileDeleted:
|
||||
return "apply_patch", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported tool type: %s", toolMsg.Tool)
|
||||
}
|
||||
|
||||
@@ -280,9 +280,8 @@ func (m *Manager) CheckSendEnabled(ctx context.Context) error {
|
||||
|
||||
// Error types which we allow sending on
|
||||
errorTypes := []string{
|
||||
string(types.AskTypeAPIReqFailed), // "api_req_failed"
|
||||
string(types.AskTypeMistakeLimitReached), // "mistake_limit_reached"
|
||||
string(types.AskTypeAutoApprovalMaxReached), // "auto_approval_max_req_reached"
|
||||
string(types.AskTypeAPIReqFailed), // "api_req_failed"
|
||||
string(types.AskTypeMistakeLimitReached), // "mistake_limit_reached"
|
||||
}
|
||||
|
||||
isError := false
|
||||
@@ -754,7 +753,21 @@ func (m *Manager) FollowConversation(ctx context.Context, instanceAddress string
|
||||
}
|
||||
|
||||
// FollowConversationUntilCompletion streams conversation updates until task completion
|
||||
func (m *Manager) FollowConversationUntilCompletion(ctx context.Context) error {
|
||||
func (m *Manager) FollowConversationUntilCompletion(ctx context.Context, opts FollowOptions) error {
|
||||
// Check if there's an active task before entering follow mode
|
||||
// Skip this check if we just created a task (to avoid race condition where task isn't active yet)
|
||||
if !opts.SkipActiveTaskCheck {
|
||||
err := m.CheckSendEnabled(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNoActiveTask) {
|
||||
fmt.Println("No task is currently running.")
|
||||
return nil
|
||||
}
|
||||
// For other errors (like task busy), we can still enter follow mode
|
||||
// as the user may want to observe the task
|
||||
}
|
||||
}
|
||||
|
||||
// Enable streaming mode
|
||||
m.mu.Lock()
|
||||
m.isStreamingMode = true
|
||||
@@ -1239,17 +1252,17 @@ func (m *Manager) updateMode(stateJson string) {
|
||||
|
||||
// UpdateTaskAutoApprovalAction enables a specific auto-approval action for the current task
|
||||
func (m *Manager) UpdateTaskAutoApprovalAction(ctx context.Context, actionKey string) error {
|
||||
boolPtr := func(b bool) *bool { return &b }
|
||||
|
||||
settings := &cline.Settings{
|
||||
AutoApprovalSettings: &cline.AutoApprovalSettings{
|
||||
Enabled: true,
|
||||
MaxRequests: 20, // Important: avoid maxRequests=0 bug
|
||||
Actions: &cline.AutoApprovalActions{},
|
||||
Actions: &cline.AutoApprovalActions{},
|
||||
},
|
||||
}
|
||||
|
||||
// Set the specific action to true based on actionKey
|
||||
truePtr := func() *bool { b := true; return &b }()
|
||||
|
||||
truePtr := boolPtr(true)
|
||||
|
||||
switch actionKey {
|
||||
case "read_files":
|
||||
settings.AutoApprovalSettings.Actions.ReadFiles = truePtr
|
||||
|
||||
@@ -180,8 +180,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
settings.PlanModeHuaweiCloudMaasModelId = strPtr(value)
|
||||
case "plan_mode_oca_model_id":
|
||||
settings.PlanModeOcaModelId = strPtr(value)
|
||||
case "plan_mode_vercel_ai_gateway_model_id":
|
||||
settings.PlanModeVercelAiGatewayModelId = strPtr(value)
|
||||
case "act_mode_api_model_id":
|
||||
settings.ActModeApiModelId = strPtr(value)
|
||||
case "act_mode_reasoning_effort":
|
||||
@@ -218,8 +216,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
settings.ActModeHuaweiCloudMaasModelId = strPtr(value)
|
||||
case "act_mode_oca_model_id":
|
||||
settings.ActModeOcaModelId = strPtr(value)
|
||||
case "act_mode_vercel_ai_gateway_model_id":
|
||||
settings.ActModeVercelAiGatewayModelId = strPtr(value)
|
||||
|
||||
// Boolean fields
|
||||
case "aws_use_cross_region_inference":
|
||||
@@ -416,24 +412,12 @@ func setNestedField(settings *cline.Settings, parentField string, childFields ma
|
||||
func setAutoApprovalSettings(settings *cline.AutoApprovalSettings, fields map[string]string) error {
|
||||
for key, value := range fields {
|
||||
switch key {
|
||||
case "enabled":
|
||||
val, err := parseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.Enabled = val
|
||||
case "max_requests":
|
||||
val, err := parseInt32(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.MaxRequests = val
|
||||
case "enable_notifications":
|
||||
val, err := parseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.EnableNotifications = val
|
||||
settings.EnableNotifications = boolPtr(val)
|
||||
case "actions":
|
||||
return fmt.Errorf("auto_approval_settings.actions requires nested dot notation (e.g., auto-approval-settings.actions.read-files=true)")
|
||||
default:
|
||||
@@ -672,6 +656,8 @@ func parseApiProvider(value string) (cline.ApiProvider, error) {
|
||||
return cline.ApiProvider_DIFY, nil
|
||||
case "oca":
|
||||
return cline.ApiProvider_OCA, nil
|
||||
case "minimax":
|
||||
return cline.ApiProvider_MINIMAX, nil
|
||||
default:
|
||||
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("invalid api_provider '%s'", value)
|
||||
}
|
||||
@@ -746,14 +732,14 @@ func setSecretField(secrets *cline.Secrets, key, value string) error {
|
||||
secrets.HuaweiCloudMaasApiKey = strPtr(value)
|
||||
case "baseten_api_key":
|
||||
secrets.BasetenApiKey = strPtr(value)
|
||||
case "vercel_ai_gateway_api_key":
|
||||
secrets.VercelAiGatewayApiKey = strPtr(value)
|
||||
case "dify_api_key":
|
||||
secrets.DifyApiKey = strPtr(value)
|
||||
case "oca_api_key":
|
||||
secrets.OcaApiKey = strPtr(value)
|
||||
case "oca_refresh_token":
|
||||
secrets.OcaRefreshToken = strPtr(value)
|
||||
case "hicap_api_key":
|
||||
secrets.HicapApiKey = strPtr(value)
|
||||
default:
|
||||
return fmt.Errorf("unsupported secret field '%s'", key)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,695 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/display"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
)
|
||||
|
||||
// KeyboardProtocol manages enhanced keyboard protocol support for detecting
|
||||
// modified keys like shift+enter across all major terminals.
|
||||
type KeyboardProtocol struct {
|
||||
enabled bool
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var globalProtocol = &KeyboardProtocol{}
|
||||
|
||||
// EnableEnhancedKeyboard enables enhanced keyboard protocols to support
|
||||
// shift+enter and other modified keys across all major terminals:
|
||||
// - VS Code integrated terminal
|
||||
// - iTerm2
|
||||
// - Terminal.app
|
||||
// - Ghostty
|
||||
// - Kitty
|
||||
// - WezTerm
|
||||
// - Alacritty
|
||||
// - foot
|
||||
// - xterm
|
||||
//
|
||||
// This function is safe to call multiple times and handles cleanup automatically.
|
||||
// It enables both modifyOtherKeys (xterm protocol) and Kitty keyboard protocol
|
||||
// for maximum compatibility.
|
||||
func EnableEnhancedKeyboard() {
|
||||
globalProtocol.mu.Lock()
|
||||
defer globalProtocol.mu.Unlock()
|
||||
|
||||
if globalProtocol.enabled {
|
||||
return // Already enabled
|
||||
}
|
||||
|
||||
// Check if we're in a TTY (not piped/redirected)
|
||||
if !isatty(os.Stdin.Fd()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Enable modifyOtherKeys mode 2
|
||||
// This tells xterm-compatible terminals (VS Code, iTerm2, Terminal.app, etc.)
|
||||
// to send escape sequences for modified keys including shift+enter
|
||||
// Format: CSI > 4 ; 2 m
|
||||
// - Mode 2 enables for ALL keys including well-known ones
|
||||
fmt.Print("\x1b[>4;2m")
|
||||
|
||||
// Also enable Kitty keyboard protocol for terminals that support it
|
||||
// This is a more modern protocol supported by Kitty, Ghostty, WezTerm, foot, etc.
|
||||
// Format: CSI = <flags> u where flags=1 means "disambiguate escape codes"
|
||||
// This makes shift+enter distinguishable from plain enter
|
||||
fmt.Print("\x1b[=1u")
|
||||
|
||||
globalProtocol.enabled = true
|
||||
}
|
||||
|
||||
// DisableEnhancedKeyboard restores the terminal to its default keyboard mode.
|
||||
// This should be called on program exit to be a good citizen.
|
||||
func DisableEnhancedKeyboard() {
|
||||
globalProtocol.mu.Lock()
|
||||
defer globalProtocol.mu.Unlock()
|
||||
|
||||
if !globalProtocol.enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Disable modifyOtherKeys (restore to mode 0)
|
||||
fmt.Print("\x1b[>4;0m")
|
||||
|
||||
// Disable Kitty keyboard protocol
|
||||
fmt.Print("\x1b[<u")
|
||||
|
||||
globalProtocol.enabled = false
|
||||
}
|
||||
|
||||
// isatty checks if a file descriptor is a terminal
|
||||
func isatty(fd uintptr) bool {
|
||||
// Use the standard library's terminal package
|
||||
// This works across all platforms (Unix, Windows, etc.)
|
||||
fileInfo, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (fileInfo.Mode() & os.ModeCharDevice) != 0
|
||||
}
|
||||
|
||||
// SetupKeyboard detects the current terminal and configures keybindings if needed.
|
||||
// Runs in background and doesn't block. Prints status when configs are modified.
|
||||
func SetupKeyboard() {
|
||||
go func() {
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
setupKeyboardInternal(renderer)
|
||||
}()
|
||||
}
|
||||
|
||||
// SetupKeyboardSync is the synchronous version used by doctor command.
|
||||
// Blocks until complete and prints status for all terminals.
|
||||
func SetupKeyboardSync() {
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
setupKeyboardInternal(renderer)
|
||||
}
|
||||
|
||||
func setupKeyboardInternal(renderer *display.Renderer) {
|
||||
terminalName := DetectTerminal()
|
||||
|
||||
switch terminalName {
|
||||
case "vscode":
|
||||
// VS Code and Cursor use the same TERM_PROGRAM value
|
||||
modified, path := SetupVSCodeKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s VS Code %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ VS Code shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
modified, path = SetupCursorKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s Cursor %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ Cursor shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
case "ghostty":
|
||||
modified, path := SetupGhosttyKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s Ghostty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
fmt.Printf("%s\n", renderer.Dim(" Fully restart Ghostty (quit all windows) for changes to take effect"))
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ Ghostty shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
case "wezterm":
|
||||
modified, path := SetupWezTermKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s WezTerm %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ WezTerm shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
case "alacritty":
|
||||
modified, path := SetupAlacrittyKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s Alacritty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ Alacritty shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
case "kitty":
|
||||
modified, path := SetupKittyKeybindings()
|
||||
if modified {
|
||||
fmt.Printf("%s Kitty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
} else if path != "" {
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ Kitty shift+enter already configured"))
|
||||
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
|
||||
}
|
||||
|
||||
case "iterm2":
|
||||
fmt.Printf("%s\n", renderer.Dim("✓ iTerm2 shift+enter works by default (maps to alt+enter)"))
|
||||
|
||||
case "terminal.app":
|
||||
fmt.Printf("%s\n", renderer.Dim("⚠ Terminal.app requires manual configuration"))
|
||||
fmt.Printf("%s\n", renderer.Dim(" See: Terminal → Preferences → Profiles → Keyboard"))
|
||||
|
||||
case "unknown":
|
||||
fmt.Printf("%s\n", renderer.Dim("ℹ Terminal not detected - use alt+enter or ctrl+j for newlines"))
|
||||
}
|
||||
}
|
||||
|
||||
// getVSCodeConfigPath returns the platform-specific path to VS Code's User directory
|
||||
func getVSCodeConfigPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(home, "Library", "Application Support", "Code", "User"), nil
|
||||
case "windows":
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData == "" {
|
||||
appData = filepath.Join(home, "AppData", "Roaming")
|
||||
}
|
||||
return filepath.Join(appData, "Code", "User"), nil
|
||||
default: // linux, freebsd, etc.
|
||||
return filepath.Join(home, ".config", "Code", "User"), nil
|
||||
}
|
||||
}
|
||||
|
||||
// getCursorConfigPath returns the platform-specific path to Cursor's User directory
|
||||
func getCursorConfigPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
return filepath.Join(home, "Library", "Application Support", "Cursor", "User"), nil
|
||||
case "windows":
|
||||
appData := os.Getenv("APPDATA")
|
||||
if appData == "" {
|
||||
appData = filepath.Join(home, "AppData", "Roaming")
|
||||
}
|
||||
return filepath.Join(appData, "Cursor", "User"), nil
|
||||
default: // linux, freebsd, etc.
|
||||
return filepath.Join(home, ".config", "Cursor", "User"), nil
|
||||
}
|
||||
}
|
||||
|
||||
// DetectTerminal identifies which terminal emulator is currently running
|
||||
func DetectTerminal() string {
|
||||
// Check TERM_PROGRAM (works for most terminals)
|
||||
termProgram := os.Getenv("TERM_PROGRAM")
|
||||
switch termProgram {
|
||||
case "vscode":
|
||||
return "vscode" // Also covers Cursor (uses same value)
|
||||
case "WezTerm":
|
||||
return "wezterm"
|
||||
case "ghostty":
|
||||
return "ghostty"
|
||||
case "iTerm.app":
|
||||
return "iterm2"
|
||||
case "Apple_Terminal":
|
||||
return "terminal.app"
|
||||
}
|
||||
|
||||
// Kitty doesn't set TERM_PROGRAM, check KITTY_WINDOW_ID
|
||||
if os.Getenv("KITTY_WINDOW_ID") != "" {
|
||||
return "kitty"
|
||||
}
|
||||
|
||||
// Alacritty doesn't set TERM_PROGRAM, check ALACRITTY_SOCKET
|
||||
if os.Getenv("ALACRITTY_SOCKET") != "" {
|
||||
return "alacritty"
|
||||
}
|
||||
|
||||
// Ghostty fallback (cross-platform - more reliable than TERM_PROGRAM)
|
||||
if os.Getenv("GHOSTTY_RESOURCES_DIR") != "" {
|
||||
return "ghostty"
|
||||
}
|
||||
|
||||
// Alacritty fallback
|
||||
if os.Getenv("ALACRITTY_LOG") != "" {
|
||||
return "alacritty"
|
||||
}
|
||||
|
||||
// Check TERM variable as last resort
|
||||
term := os.Getenv("TERM")
|
||||
if strings.Contains(term, "kitty") {
|
||||
return "kitty"
|
||||
}
|
||||
if term == "alacritty" {
|
||||
return "alacritty"
|
||||
}
|
||||
if term == "xterm-ghostty" {
|
||||
return "ghostty"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// VSCodeKeybinding represents a VS Code keyboard shortcut
|
||||
type VSCodeKeybinding struct {
|
||||
Key string `json:"key"`
|
||||
Command string `json:"command"`
|
||||
Args map[string]interface{} `json:"args,omitempty"`
|
||||
When string `json:"when,omitempty"`
|
||||
}
|
||||
|
||||
// SetupVSCodeKeybindings adds shift+enter support to VS Code's integrated terminal
|
||||
// by modifying the user's keybindings.json file.
|
||||
// Returns (wasModified, configPath) to allow caller to log the change.
|
||||
func SetupVSCodeKeybindings() (bool, string) {
|
||||
// Get platform-specific VS Code config path
|
||||
configDir, err := getVSCodeConfigPath()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
keybindingsPath := filepath.Join(configDir, "keybindings.json")
|
||||
|
||||
// Check if VS Code is installed (keybindings file or parent dir exists)
|
||||
if _, err := os.Stat(filepath.Dir(keybindingsPath)); os.IsNotExist(err) {
|
||||
// VS Code not installed, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing keybindings
|
||||
var keybindings []VSCodeKeybinding
|
||||
|
||||
data, err := os.ReadFile(keybindingsPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, ""
|
||||
}
|
||||
// File doesn't exist, start with empty array
|
||||
keybindings = []VSCodeKeybinding{}
|
||||
} else {
|
||||
// Parse existing keybindings
|
||||
if err := json.Unmarshal(data, &keybindings); err != nil {
|
||||
// If parse fails, don't modify the file
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
// Check if shift+enter binding already exists
|
||||
for _, kb := range keybindings {
|
||||
if kb.Key == "shift+enter" && kb.Command == "workbench.action.terminal.sendSequence" {
|
||||
// Already configured
|
||||
return false, keybindingsPath
|
||||
}
|
||||
}
|
||||
|
||||
// Add shift+enter keybinding
|
||||
newBinding := VSCodeKeybinding{
|
||||
Key: "shift+enter",
|
||||
Command: "workbench.action.terminal.sendSequence",
|
||||
Args: map[string]interface{}{
|
||||
"text": "\u001b\n", // ESC + newline (alt+enter sequence)
|
||||
},
|
||||
When: "terminalFocus",
|
||||
}
|
||||
|
||||
keybindings = append(keybindings, newBinding)
|
||||
|
||||
// Create backup
|
||||
if data != nil {
|
||||
backupPath := keybindingsPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, data, 0644)
|
||||
}
|
||||
|
||||
// Write updated keybindings
|
||||
updatedData, err := json.MarshalIndent(keybindings, "", " ")
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(keybindingsPath), 0755); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if err := os.WriteFile(keybindingsPath, updatedData, 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, keybindingsPath
|
||||
}
|
||||
|
||||
// SetupCursorKeybindings adds shift+enter support to Cursor's integrated terminal
|
||||
// by modifying the user's keybindings.json file.
|
||||
// Cursor is a fork of VS Code, so it uses the same keybinding format.
|
||||
// Returns (wasModified, configPath) to allow caller to log the change.
|
||||
func SetupCursorKeybindings() (bool, string) {
|
||||
// Get platform-specific Cursor config path
|
||||
configDir, err := getCursorConfigPath()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
keybindingsPath := filepath.Join(configDir, "keybindings.json")
|
||||
|
||||
// Check if Cursor is installed (keybindings file or parent dir exists)
|
||||
if _, err := os.Stat(filepath.Dir(keybindingsPath)); os.IsNotExist(err) {
|
||||
// Cursor not installed, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing keybindings
|
||||
var keybindings []VSCodeKeybinding
|
||||
|
||||
data, err := os.ReadFile(keybindingsPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, ""
|
||||
}
|
||||
// File doesn't exist, start with empty array
|
||||
keybindings = []VSCodeKeybinding{}
|
||||
} else {
|
||||
// Parse existing keybindings
|
||||
if err := json.Unmarshal(data, &keybindings); err != nil {
|
||||
// If parse fails, don't modify the file
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
// Check if shift+enter binding already exists
|
||||
for _, kb := range keybindings {
|
||||
if kb.Key == "shift+enter" && kb.Command == "workbench.action.terminal.sendSequence" {
|
||||
// Already configured
|
||||
return false, keybindingsPath
|
||||
}
|
||||
}
|
||||
|
||||
// Add shift+enter keybinding
|
||||
newBinding := VSCodeKeybinding{
|
||||
Key: "shift+enter",
|
||||
Command: "workbench.action.terminal.sendSequence",
|
||||
Args: map[string]interface{}{
|
||||
"text": "\u001b\n", // ESC + newline (alt+enter sequence)
|
||||
},
|
||||
When: "terminalFocus",
|
||||
}
|
||||
|
||||
keybindings = append(keybindings, newBinding)
|
||||
|
||||
// Create backup
|
||||
if data != nil {
|
||||
backupPath := keybindingsPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, data, 0644)
|
||||
}
|
||||
|
||||
// Write updated keybindings
|
||||
updatedData, err := json.MarshalIndent(keybindings, "", " ")
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
if err := os.MkdirAll(filepath.Dir(keybindingsPath), 0755); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if err := os.WriteFile(keybindingsPath, updatedData, 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, keybindingsPath
|
||||
}
|
||||
|
||||
// SetupGhosttyKeybindings adds shift+enter support to Ghostty terminal
|
||||
// by appending to the user's config file.
|
||||
// Returns (wasModified, configPath) to allow caller to log the change.
|
||||
func SetupGhosttyKeybindings() (bool, string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Ghostty config location: ~/.config/ghostty/config
|
||||
configPath := filepath.Join(home, ".config", "ghostty", "config")
|
||||
|
||||
// Check if config directory exists
|
||||
configDir := filepath.Dir(configPath)
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
// Ghostty not installed, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing config if it exists
|
||||
var existingContent []byte
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
existingContent = data
|
||||
// Check if shift+enter already configured
|
||||
if strings.Contains(string(data), "keybind = shift+enter") {
|
||||
return false, configPath
|
||||
}
|
||||
}
|
||||
|
||||
// Keybinding to add - send newline character (0x0a)
|
||||
// Ghostty requires \x0a hex escape syntax, verified working
|
||||
keybinding := "keybind = shift+enter=text:\\x0a\n"
|
||||
|
||||
// Append to config
|
||||
newContent := append(existingContent, []byte(keybinding)...)
|
||||
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Create backup if file exists
|
||||
if existingContent != nil {
|
||||
backupPath := configPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, existingContent, 0644)
|
||||
}
|
||||
|
||||
// Write updated config
|
||||
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, configPath
|
||||
}
|
||||
|
||||
// SetupWezTermKeybindings adds shift+enter support to WezTerm
|
||||
// by appending to the user's .wezterm.lua file.
|
||||
// Returns (wasModified, configPath)
|
||||
func SetupWezTermKeybindings() (bool, string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
configPath := filepath.Join(home, ".wezterm.lua")
|
||||
|
||||
// Check if WezTerm config exists
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
// WezTerm not configured, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing config
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Check if shift+enter already configured
|
||||
if strings.Contains(string(data), "key = 'Enter'") && strings.Contains(string(data), "mods = 'SHIFT'") {
|
||||
return false, configPath
|
||||
}
|
||||
|
||||
// Create backup
|
||||
backupPath := configPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, data, 0644)
|
||||
|
||||
// Keybinding to add (insert before final return statement)
|
||||
keybinding := `
|
||||
-- Shift+Enter for newlines (added by Cline CLI)
|
||||
config.keys = config.keys or {}
|
||||
table.insert(config.keys, {
|
||||
key = 'Enter',
|
||||
mods = 'SHIFT',
|
||||
action = wezterm.action.SendString '\x1b\n',
|
||||
})
|
||||
`
|
||||
|
||||
content := string(data)
|
||||
// Try to insert before the final return statement
|
||||
if strings.Contains(content, "return config") {
|
||||
content = strings.Replace(content, "return config", keybinding+"\nreturn config", 1)
|
||||
} else {
|
||||
// No return statement, append at end
|
||||
content += keybinding
|
||||
}
|
||||
|
||||
// Write updated config
|
||||
if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, configPath
|
||||
}
|
||||
|
||||
// SetupAlacrittyKeybindings adds shift+enter support to Alacritty
|
||||
// by appending to the user's alacritty.yml file.
|
||||
// Returns (wasModified, configPath)
|
||||
func SetupAlacrittyKeybindings() (bool, string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Try both possible locations
|
||||
configPaths := []string{
|
||||
filepath.Join(home, ".config", "alacritty", "alacritty.yml"),
|
||||
filepath.Join(home, ".config", "alacritty", "alacritty.toml"),
|
||||
filepath.Join(home, ".alacritty.yml"),
|
||||
}
|
||||
|
||||
var configPath string
|
||||
for _, path := range configPaths {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
configPath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if configPath == "" {
|
||||
// Alacritty not configured, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing config
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Check if shift+enter already configured
|
||||
if strings.Contains(string(data), "key: Return") && strings.Contains(string(data), "mods: Shift") {
|
||||
return false, configPath
|
||||
}
|
||||
|
||||
// Create backup
|
||||
backupPath := configPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, data, 0644)
|
||||
|
||||
// Keybinding to add
|
||||
var keybinding string
|
||||
if strings.HasSuffix(configPath, ".yml") || strings.HasSuffix(configPath, ".yaml") {
|
||||
keybinding = `
|
||||
# Shift+Enter for newlines (added by Cline CLI)
|
||||
key_bindings:
|
||||
- { key: Return, mods: Shift, chars: "\x1b\n" }
|
||||
`
|
||||
} else {
|
||||
// TOML format
|
||||
keybinding = `
|
||||
# Shift+Enter for newlines (added by Cline CLI)
|
||||
[[keyboard.bindings]]
|
||||
key = "Return"
|
||||
mods = "Shift"
|
||||
chars = "\x1b\n"
|
||||
`
|
||||
}
|
||||
|
||||
// Append to config
|
||||
newContent := append(data, []byte(keybinding)...)
|
||||
|
||||
// Write updated config
|
||||
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, configPath
|
||||
}
|
||||
|
||||
// SetupKittyKeybindings adds shift+enter support to Kitty terminal
|
||||
// by appending to the user's kitty.conf file.
|
||||
// Returns (wasModified, configPath)
|
||||
func SetupKittyKeybindings() (bool, string) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
configPath := filepath.Join(home, ".config", "kitty", "kitty.conf")
|
||||
|
||||
// Check if config directory exists
|
||||
configDir := filepath.Dir(configPath)
|
||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
||||
// Kitty not installed, skip silently
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Read existing config if it exists
|
||||
var existingContent []byte
|
||||
if data, err := os.ReadFile(configPath); err == nil {
|
||||
existingContent = data
|
||||
// Check if shift+enter already configured
|
||||
if strings.Contains(string(data), "map shift+enter") {
|
||||
return false, configPath
|
||||
}
|
||||
}
|
||||
|
||||
// Keybinding to add
|
||||
keybinding := "# Shift+Enter for newlines (added by Cline CLI)\nmap shift+enter send_text all \\x1b\\n\n"
|
||||
|
||||
// Append to config
|
||||
newContent := append(existingContent, []byte(keybinding)...)
|
||||
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Create backup if file exists
|
||||
if existingContent != nil {
|
||||
backupPath := configPath + ".backup"
|
||||
_ = os.WriteFile(backupPath, existingContent, 0644)
|
||||
}
|
||||
|
||||
// Write updated config
|
||||
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true, configPath
|
||||
}
|
||||
@@ -37,17 +37,16 @@ const (
|
||||
type AskType string
|
||||
|
||||
const (
|
||||
AskTypeFollowup AskType = "followup"
|
||||
AskTypePlanModeRespond AskType = "plan_mode_respond"
|
||||
AskTypeCommand AskType = "command"
|
||||
AskTypeCommandOutput AskType = "command_output"
|
||||
AskTypeCompletionResult AskType = "completion_result"
|
||||
AskTypeTool AskType = "tool"
|
||||
AskTypeAPIReqFailed AskType = "api_req_failed"
|
||||
AskTypeResumeTask AskType = "resume_task"
|
||||
AskTypeResumeCompletedTask AskType = "resume_completed_task"
|
||||
AskTypeMistakeLimitReached AskType = "mistake_limit_reached"
|
||||
AskTypeAutoApprovalMaxReached AskType = "auto_approval_max_req_reached"
|
||||
AskTypeFollowup AskType = "followup"
|
||||
AskTypePlanModeRespond AskType = "plan_mode_respond"
|
||||
AskTypeCommand AskType = "command"
|
||||
AskTypeCommandOutput AskType = "command_output"
|
||||
AskTypeCompletionResult AskType = "completion_result"
|
||||
AskTypeTool AskType = "tool"
|
||||
AskTypeAPIReqFailed AskType = "api_req_failed"
|
||||
AskTypeResumeTask AskType = "resume_task"
|
||||
AskTypeResumeCompletedTask AskType = "resume_completed_task"
|
||||
AskTypeMistakeLimitReached AskType = "mistake_limit_reached"
|
||||
AskTypeBrowserActionLaunch AskType = "browser_action_launch"
|
||||
AskTypeUseMcpServer AskType = "use_mcp_server"
|
||||
AskTypeNewTask AskType = "new_task"
|
||||
@@ -108,6 +107,7 @@ const (
|
||||
ToolTypeEditedExistingFile ToolType = "editedExistingFile"
|
||||
ToolTypeNewFileCreated ToolType = "newFileCreated"
|
||||
ToolTypeReadFile ToolType = "readFile"
|
||||
ToolTypeFileDeleted ToolType = "fileDeleted"
|
||||
ToolTypeListFilesTopLevel ToolType = "listFilesTopLevel"
|
||||
ToolTypeListFilesRecursive ToolType = "listFilesRecursive"
|
||||
ToolTypeListCodeDefinitionNames ToolType = "listCodeDefinitionNames"
|
||||
@@ -247,8 +247,6 @@ func convertProtoAskType(askType cline.ClineAsk) string {
|
||||
return string(AskTypeResumeCompletedTask)
|
||||
case cline.ClineAsk_MISTAKE_LIMIT_REACHED:
|
||||
return string(AskTypeMistakeLimitReached)
|
||||
case cline.ClineAsk_AUTO_APPROVAL_MAX_REQ_REACHED:
|
||||
return string(AskTypeAutoApprovalMaxReached)
|
||||
case cline.ClineAsk_BROWSER_ACTION_LAUNCH:
|
||||
return string(AskTypeBrowserActionLaunch)
|
||||
case cline.ClineAsk_USE_MCP_SERVER:
|
||||
|
||||
@@ -68,7 +68,7 @@ func CheckAndUpdate(isVerbose bool) {
|
||||
|
||||
// Run in background so we don't block CLI startup
|
||||
go func() {
|
||||
if err := checkAndUpdateSync(); err != nil {
|
||||
if err := checkAndUpdateInternal(false); err != nil {
|
||||
if verbose {
|
||||
output.Printf("[updater] Update check failed: %v\n", err)
|
||||
}
|
||||
@@ -76,15 +76,49 @@ func CheckAndUpdate(isVerbose bool) {
|
||||
}()
|
||||
}
|
||||
|
||||
func checkAndUpdateSync() error {
|
||||
// CheckAndUpdateSync performs a synchronous update check (blocks until complete).
|
||||
// If bypassCache is true, ignores the 24-hour cache and always checks npm registry.
|
||||
// This is used by the doctor command.
|
||||
func CheckAndUpdateSync(isVerbose bool, bypassCache bool) {
|
||||
verbose = isVerbose
|
||||
|
||||
// Skip in CI environments
|
||||
if os.Getenv("CI") != "" {
|
||||
if verbose {
|
||||
output.Printf("[updater] Skipping update check (CI environment)\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Skip if user disabled auto-updates
|
||||
if os.Getenv("NO_AUTO_UPDATE") != "" {
|
||||
if verbose {
|
||||
output.Printf("[updater] Skipping update check (NO_AUTO_UPDATE set)\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if verbose {
|
||||
output.Printf("[updater] Starting update check...\n")
|
||||
}
|
||||
|
||||
// Run synchronously
|
||||
if err := checkAndUpdateInternal(bypassCache); err != nil {
|
||||
if verbose {
|
||||
output.Printf("[updater] Update check failed: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkAndUpdateInternal(bypassCache bool) error {
|
||||
if verbose {
|
||||
output.Printf("[updater] Loading update cache...\n")
|
||||
}
|
||||
|
||||
// Load cache
|
||||
cache, err := loadCache()
|
||||
if err == nil && time.Since(cache.LastCheck) < checkInterval {
|
||||
// Checked recently, skip
|
||||
if !bypassCache && err == nil && time.Since(cache.LastCheck) < checkInterval {
|
||||
// Checked recently, skip (unless cache is bypassed)
|
||||
if verbose {
|
||||
output.Printf("[updater] Cache is fresh (last checked %v ago), skipping\n", time.Since(cache.LastCheck))
|
||||
}
|
||||
@@ -341,7 +375,7 @@ func showFailureMessage(channel string) {
|
||||
|
||||
func getCacheFilePath() string {
|
||||
configDir := filepath.Join(os.Getenv("HOME"), ".cline", "data")
|
||||
return filepath.Join(configDir, ".update-cache")
|
||||
return filepath.Join(configDir, "cli-update-cache")
|
||||
}
|
||||
|
||||
func loadCache() (cacheData, error) {
|
||||
@@ -372,4 +406,4 @@ func saveCache(cache cacheData) error {
|
||||
}
|
||||
|
||||
return os.WriteFile(cacheFile, data, 0644)
|
||||
}
|
||||
}
|
||||
+27
-1
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@@ -124,6 +126,16 @@ func NormalizeAddressForGRPC(address string) (string, error) {
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// GetNodeVersion returns the current Node.js version, or "unknown" if unable to detect
|
||||
func GetNodeVersion() string {
|
||||
cmd := exec.Command("node", "--version")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
}
|
||||
return strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
// RetryOperation performs an operation with retry logic
|
||||
func RetryOperation(maxRetries int, timeoutPerAttempt time.Duration, operation func() error) error {
|
||||
var lastErr error
|
||||
@@ -155,5 +167,19 @@ func RetryOperation(maxRetries int, timeoutPerAttempt time.Duration, operation f
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("operation failed after %d attempts: %w", maxRetries, lastErr)
|
||||
return fmt.Errorf(`operation failed to after %d attempts: %w
|
||||
|
||||
This is usually caused by an incompatible Node.js version
|
||||
|
||||
REQUIREMENTS:
|
||||
• Node.js version 20+ is required
|
||||
• Current Node.js version: %s
|
||||
|
||||
DEBUGGING STEPS:
|
||||
1. View recent logs: cline log list
|
||||
2. Logs are available in: ~/.cline/logs/
|
||||
3. The most recent cline-core log file is usually valuable
|
||||
|
||||
For additional help, visit: https://github.com/cline/cline/issues
|
||||
`, maxRetries, lastErr, GetNodeVersion())
|
||||
}
|
||||
|
||||
@@ -144,6 +144,8 @@ const (
|
||||
OPENAI_NATIVE = "openai-native"
|
||||
XAI = "xai"
|
||||
CEREBRAS = "cerebras"
|
||||
OCA = "oca"
|
||||
NOUSRESEARCH = "nousResearch"
|
||||
)
|
||||
|
||||
// AllProviders returns a slice of enabled provider IDs for the CLI build.
|
||||
@@ -159,6 +161,8 @@ var AllProviders = []string{
|
||||
"openai-native",
|
||||
"xai",
|
||||
"cerebras",
|
||||
"oca",
|
||||
"nousResearch",
|
||||
}
|
||||
|
||||
// ConfigField represents a configuration field requirement
|
||||
@@ -316,6 +320,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "nousResearchApiKey",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "nousResearch",
|
||||
"required": true,
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
{
|
||||
"name": "ulid",
|
||||
"type": "string",
|
||||
@@ -433,6 +446,15 @@ var rawConfigFields = ` [
|
||||
"fieldType": "url",
|
||||
"placeholder": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"name": "minimaxApiLine",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "general",
|
||||
"required": false,
|
||||
"fieldType": "string",
|
||||
"placeholder": ""
|
||||
},
|
||||
{
|
||||
"name": "ocaMode",
|
||||
"type": "string",
|
||||
@@ -441,7 +463,16 @@ var rawConfigFields = ` [
|
||||
"required": false,
|
||||
"fieldType": "string",
|
||||
"placeholder": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "hicapApiKey",
|
||||
"type": "string",
|
||||
"comment": "",
|
||||
"category": "general",
|
||||
"required": true,
|
||||
"fieldType": "password",
|
||||
"placeholder": "Enter your API key"
|
||||
},
|
||||
]`
|
||||
|
||||
// Raw model definitions data (parsed from TypeScript)
|
||||
@@ -467,6 +498,16 @@ var rawModelDefinitions = ` {
|
||||
"supportsImages": true,
|
||||
"supportsPromptCache": true
|
||||
},
|
||||
"claude-haiku-4-5-20251001": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 200000,
|
||||
"inputPrice": 1,
|
||||
"outputPrice": 5,
|
||||
"cacheWritesPrice": 1,
|
||||
"cacheReadsPrice": 0,
|
||||
"supportsImages": true,
|
||||
"supportsPromptCache": true
|
||||
},
|
||||
"claude-sonnet-4-20250514": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 200000,
|
||||
@@ -579,6 +620,16 @@ var rawModelDefinitions = ` {
|
||||
"supportsImages": true,
|
||||
"supportsPromptCache": true
|
||||
},
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 200000,
|
||||
"inputPrice": 1,
|
||||
"outputPrice": 5,
|
||||
"cacheWritesPrice": 1,
|
||||
"cacheReadsPrice": 0,
|
||||
"supportsImages": true,
|
||||
"supportsPromptCache": true
|
||||
},
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 200000,
|
||||
@@ -744,6 +795,24 @@ var rawModelDefinitions = ` {
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference."
|
||||
},
|
||||
"qwen.qwen3-coder-30b-a3b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 30B MoE model with 3.3B activated parameters, optimized for code generation and analysis with 256K context window."
|
||||
},
|
||||
"qwen.qwen3-coder-480b-a35b-v1:0": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 262144,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 1,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "Qwen3 Coder 480B flagship MoE model with 35B activated parameters, designed for complex coding tasks with advanced reasoning capabilities and 256K context window."
|
||||
}
|
||||
},
|
||||
"gemini": {
|
||||
@@ -1232,6 +1301,26 @@ var rawModelDefinitions = ` {
|
||||
"supportsPromptCache": false,
|
||||
"description": "SOTA performance with ~1500 tokens/s"
|
||||
}
|
||||
},
|
||||
"nousResearch": {
|
||||
"Hermes-4-405B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost."
|
||||
},
|
||||
"Hermes-4-70B": {
|
||||
"maxTokens": 8192,
|
||||
"contextWindow": 128000,
|
||||
"inputPrice": 0,
|
||||
"outputPrice": 0,
|
||||
"supportsImages": false,
|
||||
"supportsPromptCache": false,
|
||||
"description": "This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases."
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -1389,6 +1478,30 @@ func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Get your API key from https://cloud.cerebras.ai/`,
|
||||
}
|
||||
|
||||
// Oca
|
||||
definitions["oca"] = ProviderDefinition{
|
||||
ID: "oca",
|
||||
Name: "Oca",
|
||||
RequiredFields: getFieldsByProvider("oca", configFields, true),
|
||||
OptionalFields: getFieldsByProvider("oca", configFields, false),
|
||||
Models: modelDefinitions["oca"],
|
||||
DefaultModelID: "",
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure Oca API credentials`,
|
||||
}
|
||||
|
||||
// NousResearch
|
||||
definitions["nousResearch"] = ProviderDefinition{
|
||||
ID: "nousResearch",
|
||||
Name: "NousResearch",
|
||||
RequiredFields: getFieldsByProvider("nousResearch", configFields, true),
|
||||
OptionalFields: getFieldsByProvider("nousResearch", configFields, false),
|
||||
Models: modelDefinitions["nousResearch"],
|
||||
DefaultModelID: "Hermes-4-405B",
|
||||
HasDynamicModels: false,
|
||||
SetupInstructions: `Configure NousResearch API credentials`,
|
||||
}
|
||||
|
||||
return definitions, nil
|
||||
}
|
||||
@@ -1415,6 +1528,8 @@ func GetProviderDisplayName(providerID string) string {
|
||||
"openai-native": "OpenAI",
|
||||
"xai": "X AI (Grok)",
|
||||
"cerebras": "Cerebras",
|
||||
"oca": "Oca",
|
||||
"nousResearch": "NousResearch",
|
||||
}
|
||||
|
||||
if name, exists := displayNames[providerID]; exists {
|
||||
|
||||
@@ -77,7 +77,7 @@ func (s *EnvService) GetHostVersion(ctx context.Context, req *cline.EmptyRequest
|
||||
|
||||
return &host.GetHostVersionResponse{
|
||||
Platform: proto.String("Cline CLI"),
|
||||
Version: proto.String(""),
|
||||
Version: proto.String(global.CliVersion),
|
||||
ClineType: proto.String("CLI"),
|
||||
ClineVersion: proto.String(global.CliVersion),
|
||||
}, nil
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 187 KiB |
@@ -254,6 +254,19 @@ COMMANDS
|
||||
cline c l
|
||||
List all configuration variables and their values.
|
||||
|
||||
Context Window Configuration
|
||||
For local model providers, you can configure the context window size:
|
||||
|
||||
Ollama
|
||||
cline config s ollama-api-options-ctx-num=32768
|
||||
|
||||
LM Studio
|
||||
cline config s lm-studio-max-tokens=32768
|
||||
|
||||
For other providers (Anthropic, OpenRouter, etc.), the context window
|
||||
is defined per model in the model metadata and is not user-settable.
|
||||
Cline uses each model's built-in context limits automatically.
|
||||
|
||||
TASK SETTINGS
|
||||
Task settings are persisted in the ~/.cline/x/tasks directory. When
|
||||
resuming a task with cline task open, task settings are automatically
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
---
|
||||
title: "GitHub Actions Integration"
|
||||
description: "Automatically respond to GitHub issues by mentioning @cline in comments using Cline CLI in GitHub Actions."
|
||||
---
|
||||
|
||||
# GitHub Integration Sample
|
||||
|
||||
Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to trigger an autonomous investigation that reads files, analyzes code, and provides actionable insights - all running automatically in GitHub Actions.
|
||||
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](./github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
|
||||
</Note>
|
||||
|
||||
## The Workflow
|
||||
|
||||
Trigger Cline by mentioning `@cline` in any issue comment:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss0a-comment.png" alt="Issue comment with @cline mention" width="600" />
|
||||
</Frame>
|
||||
|
||||
Cline's automated analysis appears as a new comment, with insights drawn from your actual codebase:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss0b-final.png" alt="Automated analysis response from Cline" width="600" />
|
||||
</Frame>
|
||||
|
||||
The entire investigation runs autonomously in GitHub Actions - from file exploration to posting results.
|
||||
|
||||
Let's configure your repository.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and understand basic usage
|
||||
- **GitHub repository** - With admin access to configure Actions and secrets
|
||||
- **GitHub Actions familiarity** - Basic understanding of workflows and CI/CD
|
||||
- **API provider account** - OpenRouter, Anthropic, or similar with API key
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Copy the Workflow File
|
||||
|
||||
|
||||
|
||||
Copy the workflow file from this sample to your repository. The workflow file must be placed in the `.github/workflows/` directory in your repository root for GitHub Actions to detect and run it. In this case, we'll name it `cline-responder.yml`.
|
||||
|
||||
```bash
|
||||
# In your repository root
|
||||
mkdir -p .github/workflows
|
||||
curl -o .github/workflows/cline-responder.yml https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-integration/cline-responder.yml
|
||||
```
|
||||
|
||||
Alternatively, you can copy the full workflow file directly into `.github/workflows/cline-responder.yml`:
|
||||
|
||||
<Accordion title="Click to view the complete cline-responder.yml workflow">
|
||||
```yaml
|
||||
name: Cline Issue Assistant
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
respond:
|
||||
runs-on: ubuntu-latest
|
||||
environment: cline-actions
|
||||
steps:
|
||||
- name: Check for @cline mention
|
||||
id: detect
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const body = context.payload.comment?.body || "";
|
||||
const isPR = !!context.payload.issue?.pull_request;
|
||||
const hit = body.toLowerCase().includes("@cline");
|
||||
core.setOutput("hit", (!isPR && hit) ? "true" : "false");
|
||||
core.setOutput("issue_number", String(context.payload.issue?.number || ""));
|
||||
core.setOutput("issue_url", context.payload.issue?.html_url || "");
|
||||
core.setOutput("comment_body", body);
|
||||
|
||||
- name: Checkout repository
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Node v20 is needed for Cline CLI on GitHub Actions Linux
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Cline CLI
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
run: |
|
||||
# Install the Cline CLI
|
||||
sudo npm install -g cline
|
||||
|
||||
- name: Create Cline Instance
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
env:
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
CLINE_DIR: ${{ runner.temp }}/cline
|
||||
run: |
|
||||
# Create instance and capture output
|
||||
INSTANCE_OUTPUT=$(cline instance new 2>&1)
|
||||
|
||||
# Parse address from output (format: " Address: 127.0.0.1:36733")
|
||||
CLINE_ADDRESS=$(echo "$INSTANCE_OUTPUT" | grep "Address:" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+')
|
||||
echo "CLINE_ADDRESS=$CLINE_ADDRESS" >> $GITHUB_ENV
|
||||
|
||||
# Configure API key
|
||||
cline config set open-router-api-key=$OPENROUTER_API_KEY --address $CLINE_ADDRESS -v
|
||||
|
||||
- name: Download analyze script
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
run: |
|
||||
export GITORG="YOUR-GITHUB-ORG"
|
||||
export GITREPO="YOUR-GITHUB-REPO"
|
||||
|
||||
curl -L https://raw.githubusercontent.com/${GITORG}/${GITREPO}/refs/heads/main/git-scripts/analyze-issue.sh -o analyze-issue.sh
|
||||
chmod +x analyze-issue.sh
|
||||
|
||||
- name: Run analysis
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
id: analyze
|
||||
env:
|
||||
ISSUE_URL: ${{ steps.detect.outputs.issue_url }}
|
||||
COMMENT: ${{ steps.detect.outputs.comment_body }}
|
||||
CLINE_ADDRESS: ${{ env.CLINE_ADDRESS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}" "$CLINE_ADDRESS")
|
||||
|
||||
{
|
||||
echo 'result<<EOF'
|
||||
printf "%s\n" "$RESULT"
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Post response
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
ISSUE_NUMBER: ${{ steps.detect.outputs.issue_number }}
|
||||
RESULT: ${{ steps.analyze.outputs.result }}
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: Number(process.env.ISSUE_NUMBER),
|
||||
body: process.env.RESULT || "(no output)"
|
||||
});
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Warning>
|
||||
**You MUST edit the workflow file before committing!**
|
||||
|
||||
Open `.github/workflows/cline-responder.yml` and update the "Download analyze script" step within the workflow to specify your GitHub organization and repository where the analysis script is stored:
|
||||
|
||||
```yaml
|
||||
export GITORG="YOUR-GITHUB-ORG" # Change this!
|
||||
export GITREPO="YOUR-GITHUB-REPO" # Change this!
|
||||
```
|
||||
|
||||
**Example:** If your repository is `github.com/acme/myproject`, set:
|
||||
```yaml
|
||||
export GITORG="acme"
|
||||
export GITREPO="myproject"
|
||||
```
|
||||
|
||||
This tells the workflow where to download the analysis script from your repository after you commit it in step 3.
|
||||
</Warning>
|
||||
|
||||
The workflow will look for new or updated issues, check for `@cline` mentions, and then
|
||||
start up an instance of the Cline CLI to dig into the issue, providing feedback
|
||||
as a reply to the issue.
|
||||
|
||||
### 2. Configure API Keys
|
||||
|
||||
Add your AI provider API keys as repository secrets:
|
||||
|
||||
1. Go to your GitHub repository
|
||||
2. Navigate to **Settings** → **Environment** and Add a new environment.
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss01-environment.png" alt="Navigate to Actions secrets" width="600" />
|
||||
</Frame>
|
||||
|
||||
Make sure to name it "cline-actions" so that it matches the `environment`
|
||||
value at the top of the `cline-responder.yml` file.
|
||||
|
||||
3. Click **New repository secret**
|
||||
4. Add a secret for the `OPENROUTER_API_KEY` with a value of an API key from
|
||||
[openrouter.com](https://openrouter.com).
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss02-api-key.png" alt="Add API key secret" width="600" />
|
||||
</Frame>
|
||||
|
||||
5. Verify your secret is configured:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/ss03-ready.png" alt="API key configured" width="600" />
|
||||
</Frame>
|
||||
|
||||
Now you're ready to supply Cline with the credentials it needs in a GitHub Action.
|
||||
|
||||
### 3. Add Analysis Script
|
||||
|
||||
Add the analysis script from the `github-issue-rca` sample to your repository. **First, you'll need to create a `git-scripts` directory in your repository root where the script will be located.** Choose one of these options:
|
||||
|
||||
**Option A: Download directly (Recommended)**
|
||||
|
||||
```bash
|
||||
# In your repository root, create the directory and download the script
|
||||
mkdir -p git-scripts
|
||||
curl -o git-scripts/analyze-issue.sh https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
|
||||
chmod +x git-scripts/analyze-issue.sh
|
||||
```
|
||||
|
||||
**Option B: Manual copy-paste**
|
||||
|
||||
Create the directory and file manually, then paste the script content:
|
||||
|
||||
```bash
|
||||
# In your repository root
|
||||
mkdir -p git-scripts
|
||||
# Create and edit the file with your preferred editor
|
||||
nano git-scripts/analyze-issue.sh # or use vim, code, etc.
|
||||
```
|
||||
|
||||
<Accordion title="Click to view the complete analyze-issue.sh script">
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
After pasting the script content, make it executable:
|
||||
```bash
|
||||
chmod +x git-scripts/analyze-issue.sh
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
This analysis script calls Cline to execute a prompt on a GitHub issue,
|
||||
summarizing the output to populate the reply to the issue.
|
||||
|
||||
### 4. Commit and Push
|
||||
|
||||
```bash
|
||||
git add .github/workflows/cline-responder.yml
|
||||
git add git-scripts/analyze-issue.sh
|
||||
git commit -m "Add Cline issue assistant workflow"
|
||||
git push
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Once set up, simply mention `@cline` in any issue comment:
|
||||
|
||||
```
|
||||
@cline what's causing this error?
|
||||
|
||||
@cline analyze the root cause
|
||||
|
||||
@cline what are the security implications?
|
||||
```
|
||||
|
||||
GitHub Actions will:
|
||||
1. Detect the `@cline` mention
|
||||
2. Start a Cline CLI instance
|
||||
3. Download the analysis script
|
||||
4. Analyze the issue using act mode with yolo (fully autonomous)
|
||||
5. Post Cline's analysis as a new comment
|
||||
|
||||
**Note**: The workflow only triggers on issue comments, not pull request
|
||||
comments.
|
||||
|
||||
## How It Works
|
||||
|
||||
The workflow (`cline-responder.yml`):
|
||||
|
||||
1. **Triggers** on issue comments (created or edited)
|
||||
2. **Detects** `@cline` mentions (case-insensitive)
|
||||
3. **Installs** Cline CLI globally using npm
|
||||
4. **Creates** a Cline instance using `cline instance new`
|
||||
5. **Configures** authentication using `cline config set open-router-api-key=...
|
||||
--address ...`
|
||||
6. **Downloads** the reusable `analyze-issue.sh` script from the
|
||||
`github-issue-rca` sample
|
||||
7. **Runs** analysis with the instance address
|
||||
8. **Posts** the analysis result as a comment
|
||||
|
||||
## Related Samples
|
||||
|
||||
- **[github-issue-rca](./github-issue-rca)**: The reusable script that powers this integration
|
||||
@@ -0,0 +1,383 @@
|
||||
---
|
||||
title: "GitHub Issue RCA Sample"
|
||||
description: "Automated GitHub issue analysis using Cline CLI to identify root causes."
|
||||
---
|
||||
|
||||
# GitHub Root Cause Analysis
|
||||
|
||||
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
|
||||
</Note>
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/cli-rca.gif" alt="CLI Root Cause Analysis Demo" width="600" />
|
||||
</Frame>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This sample assumes you have already:
|
||||
|
||||
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
|
||||
- **At least one AI model provider** configured (e.g., OpenRouter, Anthropic, OpenAI)
|
||||
- **Basic familiarity** with Cline CLI commands
|
||||
|
||||
Additionally, you'll need:
|
||||
|
||||
- **GitHub CLI** (`gh`) installed and authenticated
|
||||
- **jq** installed for JSON parsing
|
||||
- **bash** shell (or compatible shell)
|
||||
|
||||
### Installation Instructions
|
||||
|
||||
#### macOS
|
||||
|
||||
<Note>
|
||||
These instructions require [Homebrew](https://brew.sh/) to be installed. If you don't have Homebrew, install it first by running:
|
||||
```bash
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
```
|
||||
</Note>
|
||||
|
||||
```bash
|
||||
# Install GitHub CLI
|
||||
brew install gh
|
||||
|
||||
# Install jq
|
||||
brew install jq
|
||||
|
||||
# Authenticate with GitHub
|
||||
gh auth login
|
||||
```
|
||||
|
||||
#### Linux
|
||||
|
||||
```bash
|
||||
# Install GitHub CLI (Debian/Ubuntu)
|
||||
sudo apt install gh
|
||||
|
||||
# Or for other Linux distributions, see: https://cli.github.com/manual/installation
|
||||
|
||||
# Install jq (Debian/Ubuntu)
|
||||
sudo apt install jq
|
||||
|
||||
# Authenticate with GitHub
|
||||
gh auth login
|
||||
```
|
||||
|
||||
## Getting the Script
|
||||
|
||||
**Option 1: Download directly with curl**
|
||||
```bash
|
||||
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
|
||||
```
|
||||
|
||||
**Option 2: Copy the full script**
|
||||
|
||||
<Accordion title="Click to view the complete analyze-issue.sh script">
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
**After downloading or creating the script**, make it executable by running:
|
||||
```bash
|
||||
chmod +x analyze-issue.sh
|
||||
```
|
||||
</Note>
|
||||
|
||||
## Quick Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Run this command in your terminal from the directory where you saved the script to analyze an issue with the default root cause prompt:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/123
|
||||
```
|
||||
|
||||
This will:
|
||||
- Fetch issue #123 from the repository
|
||||
- Analyze the issue to identify root causes
|
||||
- Provide detailed analysis with recommendations
|
||||
|
||||
### Custom Analysis Prompt
|
||||
|
||||
Ask specific questions about the issue:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/456 "What is the security impact?"
|
||||
```
|
||||
|
||||
### Using Specific Cline Instance
|
||||
|
||||
Target a particular Cline instance by address:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/123 \
|
||||
"What is the root cause of this issue?" \
|
||||
127.0.0.1:46529
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This is useful when:
|
||||
- Running multiple Cline instances
|
||||
- Using a remote Cline server
|
||||
- Testing with specific configurations
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The script will automatically handle everything: fetching the issue, analyzing it with Cline, and displaying the results. The analysis typically takes 30-60 seconds depending on the issue complexity.
|
||||
</Note>
|
||||
|
||||
## How It Works
|
||||
|
||||
Let's analyze each component of the script to understand how it works.
|
||||
|
||||
### Argument Validation
|
||||
|
||||
The script validates input and provides usage instructions:
|
||||
|
||||
```bash
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'Analyze security impact' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Points:**
|
||||
- Validates required GitHub issue URL
|
||||
- Shows clear usage examples
|
||||
- Supports optional custom prompt
|
||||
- Supports optional Cline instance address
|
||||
|
||||
### Argument Parsing
|
||||
|
||||
The script extracts and sets up the arguments:
|
||||
|
||||
```bash
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `ISSUE_URL="$1"` - First argument is always the issue URL
|
||||
- `PROMPT="${2:-...}"` - Second argument is optional, defaults to root cause analysis
|
||||
- `ADDRESS` - Third argument is optional, only set if provided
|
||||
|
||||
### The Core Analysis Pipeline
|
||||
|
||||
This is where the magic happens:
|
||||
|
||||
```bash
|
||||
# Ask Cline for his analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
<Accordion title="Pipeline Breakdown: Understanding Each Component">
|
||||
|
||||
**1. `cline -y "$PROMPT: $ISSUE_URL"`**
|
||||
- `-y` enables yolo mode (no user interaction)
|
||||
- Constructs prompt with issue URL
|
||||
|
||||
**2. `--mode act`**
|
||||
- Enables act mode for active investigation
|
||||
- Allows Cline to use tools (read files, run commands, etc.)
|
||||
|
||||
**3. `$ADDRESS`**
|
||||
- Optional address flag for specific instance
|
||||
- Expands to `--address <ip:port>` if set
|
||||
|
||||
**4. `-F json`**
|
||||
- Outputs in JSON format for parsing
|
||||
|
||||
**5. `sed -n '/^{/,$p'`**
|
||||
- Extracts JSON from output
|
||||
- Skips any non-JSON prefix lines
|
||||
|
||||
**6. `jq -r 'select(.say == "completion_result") | .text'`**
|
||||
- Filters for completion result messages
|
||||
- Extracts the text field
|
||||
- `-r` outputs raw strings (no JSON quotes)
|
||||
|
||||
**7. `sed 's/\\n/\n/g'`**
|
||||
- Converts escaped newlines to actual newlines
|
||||
- Makes output readable
|
||||
|
||||
</Accordion>
|
||||
|
||||
## Sample Output
|
||||
|
||||
Here's an example analyzing a real Flutter issue:
|
||||
|
||||
```bash
|
||||
$ ./analyze-issue.sh https://github.com/csells/flutter_counter/issues/2
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
```markdown
|
||||
**Root Cause Analysis of Issue #2: "setState isn't cutting it"**
|
||||
|
||||
After examining the GitHub issue and analyzing the Flutter counter codebase,
|
||||
I've identified the root cause of why setState() is insufficient for this
|
||||
project's needs:
|
||||
|
||||
## Current Implementation Problems
|
||||
|
||||
The current Flutter counter app uses setState() for state management, which
|
||||
has several limitations:
|
||||
|
||||
1. **Local State Only**: setState() only works within a single widget, making
|
||||
it difficult to share state across the app
|
||||
2. **Rebuild Overhead**: Every setState() call rebuilds the entire widget tree,
|
||||
causing performance issues with complex UIs
|
||||
3. **No State Persistence**: State is lost when the widget is disposed
|
||||
4. **Testing Challenges**: setState-based logic is tightly coupled to the UI,
|
||||
making unit testing difficult
|
||||
|
||||
## Why This Matters
|
||||
|
||||
As the app grows beyond a simple counter, these limitations become critical:
|
||||
- Multiple screens need to access the count
|
||||
- State needs to persist across navigation
|
||||
- Business logic should be testable independently
|
||||
- UI should only rebuild when necessary
|
||||
|
||||
## Recommended Solutions
|
||||
|
||||
The issue mentions "Provider or Bloc" - both are excellent alternatives:
|
||||
|
||||
1. **Provider**: Simple, lightweight state management using InheritedWidget
|
||||
- Easy migration path from setState
|
||||
- Good for small to medium apps
|
||||
- Official Flutter recommendation
|
||||
|
||||
2. **Bloc**: More structured approach with clear separation between events,
|
||||
states, and business logic
|
||||
- Better for complex apps
|
||||
- Excellent testability
|
||||
- Clear architectural patterns
|
||||
|
||||
3. **Riverpod**: Modern alternative to Provider with better performance and
|
||||
developer experience
|
||||
- Compile-time safety
|
||||
- Better testing support
|
||||
- More flexible than Provider
|
||||
|
||||
4. **GetX**: Full-featured solution with state management, routing, and
|
||||
dependency injection
|
||||
- Minimal boilerplate
|
||||
- Fast and lightweight
|
||||
- All-in-one solution
|
||||
|
||||
## Next Steps
|
||||
|
||||
The current codebase needs refactoring to implement proper state management
|
||||
architecture to handle more complex state scenarios effectively. Provider
|
||||
would be the easiest migration path while Bloc provides better long-term
|
||||
scalability.
|
||||
```
|
||||
|
||||
## When to Use This Pattern
|
||||
|
||||
This script pattern is ideal for various development scenarios where automated GitHub issue analysis can accelerate your workflow.
|
||||
|
||||
### Bug Investigation
|
||||
|
||||
Quickly analyze bug reports and identify root causes without manual code exploration:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/123 \
|
||||
"What is the root cause of this bug?"
|
||||
```
|
||||
|
||||
### Feature Request Analysis
|
||||
|
||||
Understand context and implications of feature requests:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/456 \
|
||||
"What are the implementation challenges?"
|
||||
```
|
||||
|
||||
### Security Audits
|
||||
|
||||
Assess security implications of reported issues:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/789 \
|
||||
"What are the security implications?"
|
||||
```
|
||||
|
||||
### Documentation Generation
|
||||
|
||||
Generate detailed technical documentation from issues:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/654 \
|
||||
"Provide detailed technical documentation for this issue"
|
||||
```
|
||||
|
||||
### Code Review Assistance
|
||||
|
||||
Get second opinions on proposed changes:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/project/repo/issues/987 \
|
||||
"Review the proposed solution approach"
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This sample demonstrates how to build an autonomous GitHub issue analysis tool using Cline CLI:
|
||||
|
||||
1. **Building autonomous CLI tools** using Cline's capabilities
|
||||
2. **Parsing structured JSON output** from Cline CLI
|
||||
3. **Creating flexible automation scripts** with custom prompting
|
||||
4. **Integrating with GitHub** for issue analysis
|
||||
5. **Handling command-line arguments** effectively
|
||||
|
||||
This pattern can be adapted for many other automation scenarios, from pull request reviews to documentation generation to code quality analysis.
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
|
||||
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
|
||||
- [Three Core Flows](https://docs.cline.bot/cline-cli/three-core-flows)
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "Samples Overview"
|
||||
description: Example implementations demonstrating Cline CLI capabilities
|
||||
---
|
||||
|
||||
This section provides sample implementations that demonstrate various Cline CLI features and capabilities. Each sample includes complete code, detailed explanations, and real-world usage examples.
|
||||
|
||||
## Available Samples
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card
|
||||
title="GitHub Root Cause Analysis"
|
||||
icon="magnifying-glass-chart"
|
||||
href="/cline-cli/samples/github-issue-rca"
|
||||
>
|
||||
A command-line script that uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues. Features JSON output parsing and non-interactive execution.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="GitHub Integration (Actions)"
|
||||
icon="github"
|
||||
href="/cline-cli/samples/github-integration"
|
||||
>
|
||||
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [CLI Installation Guide](/cline-cli/installation)
|
||||
- [CLI Reference Documentation](/cline-cli/cli-reference)
|
||||
- [Three Core Flows](/cline-cli/three-core-flows)
|
||||
@@ -113,6 +113,20 @@ cline instances kill -a
|
||||
Keep track of instance addresses returned by `cline instance new`. When scripting multiple agents, store these IDs and direct your tasks to the appropriate instance.
|
||||
</Tip>
|
||||
|
||||
## Configuring context window for local providers
|
||||
|
||||
For Ollama and LM Studio, you can configure the model context window via CLI:
|
||||
|
||||
```bash
|
||||
# For Ollama
|
||||
cline config s ollama-api-options-ctx-num=32768
|
||||
|
||||
# For LM Studio
|
||||
cline config s lm-studio-max-tokens=32768
|
||||
```
|
||||
|
||||
For other providers (Anthropic, OpenRouter, etc.), the context window is defined per model in the model metadata and is not user-configurable—Cline uses each model's built-in context limits automatically.
|
||||
|
||||
## Choosing the right flow
|
||||
|
||||
- **Interactive mode**: Best for exploring new problems, learning how Cline works, or when you want to review plans before execution
|
||||
@@ -138,7 +152,7 @@ For in-depth commands and flags, check out the [CLI reference](/cline-cli/cli-re
|
||||
Understand how YOLO mode works and when to use full automation versus manual approval.
|
||||
</Card>
|
||||
|
||||
<Card title="Task management" icon="clipboard-check" href="/getting-started/task-management">
|
||||
<Card title="Task management" icon="clipboard-check" href="/features/tasks/task-management">
|
||||
Learn how Cline tracks and manages tasks, including saving and restoring state from checkpoints.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
+80
-15
@@ -88,6 +88,14 @@
|
||||
"cline-cli/overview",
|
||||
"cline-cli/installation",
|
||||
"cline-cli/three-core-flows",
|
||||
{
|
||||
"group": "CLI Samples",
|
||||
"pages": [
|
||||
"cline-cli/samples/overview",
|
||||
"cline-cli/samples/github-issue-rca",
|
||||
"cline-cli/samples/github-integration"
|
||||
]
|
||||
},
|
||||
"cline-cli/cli-reference"
|
||||
]
|
||||
},
|
||||
@@ -129,7 +137,16 @@
|
||||
"features/dictation",
|
||||
"features/drag-and-drop",
|
||||
"features/editing-messages",
|
||||
"features/explain-changes",
|
||||
"features/focus-chain",
|
||||
{
|
||||
"group": "Hooks",
|
||||
"pages": [
|
||||
"features/hooks/index",
|
||||
"features/hooks/hook-reference",
|
||||
"features/hooks/samples"
|
||||
]
|
||||
},
|
||||
"features/multiroot-workspace",
|
||||
"features/plan-and-act",
|
||||
{
|
||||
@@ -137,12 +154,20 @@
|
||||
"pages": [
|
||||
"features/slash-commands/new-task",
|
||||
"features/slash-commands/new-rule",
|
||||
"features/slash-commands/explain-changes",
|
||||
"features/slash-commands/smol",
|
||||
"features/slash-commands/report-bug",
|
||||
"features/slash-commands/deep-planning"
|
||||
]
|
||||
},
|
||||
"features/slash-commands/workflows",
|
||||
{
|
||||
"group": "Workflows",
|
||||
"pages": [
|
||||
"features/slash-commands/workflows/index",
|
||||
"features/slash-commands/workflows/quickstart",
|
||||
"features/slash-commands/workflows/best-practices"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Task Management",
|
||||
"pages": [
|
||||
@@ -180,6 +205,7 @@
|
||||
"provider-config/fireworks",
|
||||
"provider-config/zai",
|
||||
"provider-config/gcp-vertex-ai",
|
||||
"provider-config/baseten",
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
@@ -206,8 +232,7 @@
|
||||
"provider-config/vscode-language-model-api",
|
||||
"provider-config/sap-aicore",
|
||||
"provider-config/vercel-ai-gateway",
|
||||
"provider-config/requesty",
|
||||
"provider-config/baseten"
|
||||
"provider-config/requesty"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -232,32 +257,48 @@
|
||||
"exploring-clines-tools/remote-browser-support"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Enterprise",
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/security-concerns"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"troubleshooting/networking-and-proxies",
|
||||
"troubleshooting/terminal-quick-fixes",
|
||||
"troubleshooting/terminal-integration-guide",
|
||||
"troubleshooting/task-history-recovery",
|
||||
"more-info/telemetry"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Enterprise",
|
||||
"icon": "building",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Enterprise Solutions",
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/onboarding",
|
||||
"enterprise-solutions/members/roles-and-permissions",
|
||||
{
|
||||
"group": "Provider Remote Configuration",
|
||||
"pages": [
|
||||
{
|
||||
"group": "AWS Bedrock",
|
||||
"pages": [
|
||||
"enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration",
|
||||
"enterprise-solutions/provider-remote-config/aws-bedrock/member-configuration"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Learn",
|
||||
"icon": "graduation-cap",
|
||||
"href": "https://cline.bot/learn"
|
||||
},
|
||||
{
|
||||
"tab": "Blog",
|
||||
"icon": "newspaper",
|
||||
"href": "https://cline.bot/blog"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -315,6 +356,30 @@
|
||||
{
|
||||
"source": "/getting-started/your-first-task",
|
||||
"destination": "/getting-started/your-first-project"
|
||||
},
|
||||
{
|
||||
"source": "/cline-cli/samples",
|
||||
"destination": "/cline-cli/samples/overview"
|
||||
},
|
||||
{
|
||||
"source": "/features/hooks/real-world-examples",
|
||||
"destination": "/features/hooks/samples"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-AWS-Bedrock-Admin",
|
||||
"destination": "/enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-AWS-Bedrock-Member",
|
||||
"destination": "/enterprise-solutions/provider-remote-config/aws-bedrock/member-configuration"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/configure-workOS-authkit",
|
||||
"destination": "/enterprise-solutions/onboarding"
|
||||
},
|
||||
{
|
||||
"source": "/enterprise-solutions/Onboarding your Organization",
|
||||
"destination": "/enterprise-solutions/onboarding"
|
||||
}
|
||||
],
|
||||
"search": {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Managing Members"
|
||||
sidebarTitle: "Managing Members"
|
||||
description: "A guide to adding, removing, and editing members in your enterprise organization."
|
||||
---
|
||||
|
||||
This guide covers the practical steps for adding, editing, and removing members from your enterprise dashboard. For a conceptual overview of roles and permissions, see the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions).
|
||||
|
||||
<Frame caption="The Members Dashboard provides a central place to manage your team.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/members-dash.png" alt="Members Dashboard" />
|
||||
</Frame>
|
||||
|
||||
## Adding Members
|
||||
|
||||
To invite someone to your organization, you must have an open seat available on your organization.
|
||||
|
||||
1. Navigate to the **Members** tab in your dashboard.
|
||||
2. Click the **Add Members** button.
|
||||
3. Enter one or more email addresses, separated by commas.
|
||||
4. Select a role for the new member(s). It's best practice to start with the "Member" role unless you know they need admin privileges.
|
||||
5. Click **Send Invitation**.
|
||||
|
||||
Invited users will receive an email with a link to join. You can cancel a pending invitation at any time by clicking the trash icon next to the user's email in the 'Pending Invites' section.
|
||||
|
||||
<Tip>
|
||||
**Managing Users at Scale**
|
||||
|
||||
When inviting a large number of users, you can paste a comma-separated list of emails directly into the invitation field. While role changes and removals are performed individually, this bulk invitation feature helps streamline the onboarding process for entire teams.
|
||||
</Tip>
|
||||
|
||||
<Frame caption="Adding members to your organization">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/adding-members.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Editing Member Roles
|
||||
|
||||
As your team's needs change, you can adjust member roles directly from the dashboard.
|
||||
|
||||
- Find the member in your list.
|
||||
- Under the "Role" column, click the dropdown menu.
|
||||
- Select their new role. The change takes effect immediately.
|
||||
|
||||
Refer to the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions) for a detailed breakdown of what each role can do.
|
||||
|
||||
## Removing Members
|
||||
|
||||
Removing a member immediately revokes their access to all organization-specific resources, including shared API keys and configurations.
|
||||
|
||||
1. Go to the **Members Dashboard**.
|
||||
2. Find the member in the list and click the red trash icon (<Icon icon="trash" iconType="solid" />).
|
||||
3. Confirm the removal when prompted.
|
||||
|
||||
<Frame caption="You will be asked to confirm before a member is permanently removed.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/remove-user.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Troubleshooting Invitations
|
||||
|
||||
If an invited user is having trouble joining, check these common issues:
|
||||
|
||||
- **Invitation Not Received**: Ask the user to check their spam or junk mail folder. If it's not there, cancel the pending invitation and try sending it again, verifying the email address is correct.
|
||||
|
||||
- **"Invalid Domain" Error**: The user's email address must belong to a domain that has been verified for your organization. Work with your IT administrator to ensure the necessary domains are configured.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "Managing Members"
|
||||
sidebarTitle: "Managing Members"
|
||||
description: "A guide to adding, removing, and editing members in your enterprise organization."
|
||||
---
|
||||
|
||||
This guide covers the practical steps for adding, editing, and removing members from your enterprise dashboard. For a conceptual overview of roles and permissions, see the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions).
|
||||
|
||||
<Frame caption="The Members Dashboard provides a central place to manage your team.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/members-dash.png" alt="Members Dashboard" />
|
||||
</Frame>
|
||||
|
||||
## Adding Members
|
||||
|
||||
To invite someone to your organization, you must have an open seat available on your organization.
|
||||
|
||||
1. Navigate to the **Members** tab in your dashboard.
|
||||
2. Click the **Add Members** button.
|
||||
3. Enter one or more email addresses, separated by commas.
|
||||
4. Select a role for the new member(s). It's best practice to start with the "Member" role unless you know they need admin privileges.
|
||||
5. Click **Send Invitation**.
|
||||
|
||||
Invited users will receive an email with a link to join. You can cancel a pending invitation at any time by clicking the trash icon next to the user's email in the 'Pending Invites' section.
|
||||
|
||||
<Tip>
|
||||
**Managing Users at Scale**
|
||||
|
||||
When inviting a large number of users, you can paste a comma-separated list of emails directly into the invitation field. While role changes and removals are performed individually, this bulk invitation feature helps streamline the onboarding process for entire teams.
|
||||
</Tip>
|
||||
|
||||
<Frame caption="Adding members to your organization">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/adding-members.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Editing Member Roles
|
||||
|
||||
As your team's needs change, you can adjust member roles directly from the dashboard.
|
||||
|
||||
- Find the member in your list.
|
||||
- Under the "Role" column, click the dropdown menu.
|
||||
- Select their new role. The change takes effect immediately.
|
||||
|
||||
Refer to the [Roles and Permissions](/enterprise-solutions/members/roles-and-permissions) for a detailed breakdown of what each role can do.
|
||||
|
||||
## Removing Members
|
||||
|
||||
Removing a member immediately revokes their access to all organization-specific resources, including shared API keys and configurations.
|
||||
|
||||
1. Go to the **Members Dashboard**.
|
||||
2. Find the member in the list and click the red trash icon (<Icon icon="trash" iconType="solid" />).
|
||||
3. Confirm the removal when prompted.
|
||||
|
||||
<Frame caption="You will be asked to confirm before a member is permanently removed.">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/remove-user.png" alt="Confirm Member Removal" />
|
||||
</Frame>
|
||||
|
||||
## Troubleshooting Invitations
|
||||
|
||||
If an invited user is having trouble joining, check these common issues:
|
||||
|
||||
- **Invitation Not Received**: Ask the user to check their spam or junk mail folder. If it's not there, cancel the pending invitation and try sending it again, verifying the email address is correct.
|
||||
|
||||
- **"Invalid Domain" Error**: The user's email address must belong to a domain that has been verified for your organization. Work with your IT administrator to ensure the necessary domains are configured.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Members Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "An overview of member management in your enterprise organization."
|
||||
---
|
||||
|
||||
This section provides a comprehensive guide to managing members in your enterprise organization. Here, you'll find everything you need to know about roles, permissions, and the practical steps for adding, editing, and removing members from your dashboard.
|
||||
|
||||
## Key Topics
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Roles and Permissions"
|
||||
icon="user-shield"
|
||||
href="/enterprise-solutions/members/roles-and-permissions"
|
||||
>
|
||||
A detailed breakdown of the available roles and their specific permissions.
|
||||
</Card>
|
||||
<Card
|
||||
title="Managing Members"
|
||||
icon="users-gear"
|
||||
href="/enterprise-solutions/members/managing-members"
|
||||
>
|
||||
A practical guide to adding, editing, and removing members from your
|
||||
dashboard.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: "Roles and Permissions"
|
||||
sidebarTitle: "Roles and Permissions"
|
||||
description: "An overview of member roles, permissions, and best practices for your enterprise organization."
|
||||
---
|
||||
|
||||
Choosing the right role for each member is crucial for maintaining security and ensuring your team can work effectively. This guide provides a detailed breakdown of the available roles, their specific permissions, and best practices for managing your organization.
|
||||
|
||||
## Role Definitions
|
||||
|
||||
Here’s a summary of the available roles and their intended use cases.
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="Owner" icon="user-crown">
|
||||
**Best for:** The primary account holder or a small number of designated leaders.
|
||||
|
||||
Owners have unrestricted access to all settings, including billing, member management, and security configurations. To maintain tight control over the organization, the number of Owners should be kept to a minimum.
|
||||
</Card>
|
||||
<Card title="Admin" icon="user-gear">
|
||||
**Best for:** Team leads or IT administrators who need to manage users and configurations.
|
||||
|
||||
Admins can invite, edit, and remove members, as well as manage provider configurations. They have broad access but cannot manage billing or change the Owner. This is a suitable role for trusted team managers.
|
||||
</Card>
|
||||
<Card title="Member" icon="user">
|
||||
**Best for:** Most developers and individual contributors.
|
||||
|
||||
Members can use Cline with the organization's shared resources but cannot change any settings or view other users' activity. This is the safest default role for new users.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Permissions Matrix
|
||||
|
||||
For a detailed comparison, this matrix outlines the specific capabilities of each role.
|
||||
|
||||
| Permission | Member | Admin | Owner |
|
||||
| --------------------------- | :----: | :----: | :----: |
|
||||
| **General Usage** | | | |
|
||||
| Use Cline | ✅ | ✅ | ✅ |
|
||||
| Access Shared API Providers | ✅ | ✅ | ✅ |
|
||||
| | | | |
|
||||
| **Member Management** | | | |
|
||||
| View Members | ❌ | ✅ | ✅ |
|
||||
| Invite New Members | ❌ | ✅ | ✅ |
|
||||
| Edit Member Roles | ❌ | ✅ | ✅ |
|
||||
| Remove Members | ❌ | ✅ | ✅ |
|
||||
| Remove Admins | ❌ | ❌ | ✅ |
|
||||
| | | | |
|
||||
| **Configuration** | | | |
|
||||
| Configure API Providers | ❌ | ✅ | ✅ |
|
||||
| Manage Security Settings | ❌ | ❌ | ✅ |
|
||||
| | | | |
|
||||
| **Billing & Ownership** | | | |
|
||||
| View Billing Information | ❌ | ❌ | ✅ |
|
||||
| Manage Subscription | ❌ | ❌ | ✅ |
|
||||
| Transfer Ownership | ❌ | ❌ | ✅ |
|
||||
|
||||
## Role Management Best Practices
|
||||
|
||||
Effective role management is fundamental to securing your organization.
|
||||
|
||||
- **Apply the Principle of Least Privilege**: Always assign the role with the minimum necessary permissions. Most users should be **Members**. Grant **Admin** rights only to those who are responsible for user management or technical configuration.
|
||||
|
||||
- **Limit the Number of Owners**: The **Owner** role should be reserved for one or two key individuals who control the account and billing. This centralization of power prevents accidental or malicious changes to critical settings.
|
||||
|
||||
- **Regularly Audit Roles**: Periodically review the list of Admins and Owners to ensure the assigned roles are still appropriate. When a team member's responsibilities change, adjust their role accordingly.
|
||||
|
||||
## Identity Providers and Domain Verification
|
||||
|
||||
For a user to successfully join and sign in to your organization, two conditions must be met:
|
||||
1. Their email must be managed by your organization's verified **Identity Provider (IDP)**, such as Microsoft Entra ID, Okta, or AWS.
|
||||
2. Your organization must have a **verified domain** with a provider like Google or Microsoft.
|
||||
|
||||
This ensures that only authenticated users from your company can access your Cline organization.
|
||||
|
||||
## Seat Management and Invitations
|
||||
|
||||
Each user in your organization, regardless of role, consumes one seat from your license.
|
||||
|
||||
- When an invitation is sent, a seat is considered "pending."
|
||||
- If an invited user does not accept, the invitation can be revoked to free up the seat.
|
||||
- Removing a member from the organization immediately frees up a seat.
|
||||
|
||||
Now that you understand the different roles and how to manage them, you can proceed to [configuring provider remote access](/enterprise-solutions/provider-remote-config/aws-bedrock/admin-configuration) for your organization.
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
title: "Onboarding"
|
||||
|
||||
description: "This guide explains how administrators configure SSO provisioning and user management in Cline Enterprise."
|
||||
---
|
||||
|
||||
## Overview
|
||||
Cline Enterprise integrates with your existing identity provider (IdP) via WorkOS to deliver secure SSO and zero-touch user lifecycle management. In this guide, you'll connect your IdP (Okta, Azure AD, Google Workspace, or any SAML/OIDC provider), enable just-in-time (JIT) provisioning so new users are created automatically on first sign-in, and configure role mapping so permissions stay aligned with your directory—no manual invites or seat reconciliations required.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Cline Enterprise License](https://cline.bot/enterprise)
|
||||
- Access to your identity provider (IdP) configuration (e.g., Okta, Azure AD, Google Workspace)
|
||||
- Knowledge of your organization's SSO requirements
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
### Step 1: Onboard to Cline Enterprise license
|
||||
|
||||
Your IdP administrator will receive an email with a link to register their organization with WorkOS during onboarding.
|
||||
|
||||
### Step 2: Configure Your Identity Provider
|
||||
|
||||
Connect your identity provider (IdP) to WorkOS:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
2. Click **Add Connection**
|
||||
3. Select your identity provider (e.g., Okta, Azure AD, Google Workspace, Generic SAML/OIDC)
|
||||
4. Follow the provider-specific setup instructions
|
||||
|
||||
Each identity provider (IdP) will have its own setup process and required fields. Be sure to follow the specific instructions in the WorkOS dashboard for your chosen provider.
|
||||
For more explicit instruction on connecting your IdP, refer to the [WorkOS SSO documentation](https://workos.com/docs/authkit/sso)
|
||||
|
||||
### Step 3: Configure User Provisioning
|
||||
|
||||
Cline Enterprise uses **just-in-time provisioning** that works automatically:
|
||||
|
||||
- **Organizations are created automatically**
|
||||
- **Users gain access automatically** on their first SSO sign-in, once their credentials have been configured by the IdP administrator.
|
||||
- **Roles sync automatically** from your IdP (Admin/Owner → Admin, Member → Member)
|
||||
- **No manual user invites or seat management** required
|
||||
|
||||
No additional configuration is needed. Users are provisioned automatically when they sign in through SSO.
|
||||
|
||||
### Step 4: Configure User Attributes Mapping
|
||||
|
||||
User roles are mapped automatically from your IdP:
|
||||
|
||||
- **Admin** in IdP → **Admin** role in Cline (Note: The first Owner of the org is created manually during onboarding)
|
||||
- **Member** in IdP → **Member** role in Cline
|
||||
|
||||
<Info>
|
||||
For what each role can access, see the [Roles and Permissions](./members/roles-and-permissions) page.
|
||||
</Info>
|
||||
|
||||
If needed, you can configure additional user attributes in the Cline Admin console:
|
||||
|
||||
1. Go to **Settings → Authentication → User Attributes**
|
||||
2. Map attributes such as email and name based on your IdP configuration
|
||||
|
||||
For information about available user attributes, see the [WorkOS User Object Documentation](https://workos.com/docs/authkit/user-management).
|
||||
|
||||
### Step 5: Test SSO Connection
|
||||
|
||||
Before allowing users to sign in, test the SSO flow to ensure everything is configured correctly.
|
||||
|
||||
**To test the connection:**
|
||||
|
||||
1. In the WorkOS dashboard (or Cline Admin console if available), locate and click **Test SSO Connection**
|
||||
2. You'll be redirected to your IdP's login page
|
||||
3. Enter valid credentials for a test user
|
||||
4. After successful authentication, you should be redirected back
|
||||
5. Confirm that the user's information (name, email, role) displays correctly
|
||||
|
||||
**Expected outcome:** The test user is authenticated, their account details are visible, and their role matches what's configured in your IdP.
|
||||
|
||||
**If the test fails:** Double-check your IdP configuration (redirect URIs, SAML certificates, attribute mappings). See the [WorkOS SSO documentation](https://workos.com/docs/authkit/sso) for troubleshooting guidance.
|
||||
|
||||
### User Access
|
||||
|
||||
Once SSO is configured, users in your IdP can access Cline automatically without manual invites or account setup.
|
||||
|
||||
**First-time sign-in flow:**
|
||||
|
||||
1. User navigates to Cline and clicks **Sign in with SSO**
|
||||
2. User authenticates via your organization's IdP
|
||||
3. Cline automatically creates their account in your Organization
|
||||
4. Role is assigned based on their IdP role (see [Step 4](#step-4-configure-user-attributes-mapping))
|
||||
5. User is redirected to Cline and can begin working
|
||||
|
||||
**What happens automatically:**
|
||||
- Account creation with correct organization assignment
|
||||
- Role and permission assignment
|
||||
- Basic profile information (name, email) populated from IdP
|
||||
|
||||
**No action required:** Users don't need to request access or wait for approval. Access is granted immediately upon successful IdP authentication.
|
||||
|
||||
### Managing Access
|
||||
|
||||
All access management and revocation of users is currently handled by your IdP:
|
||||
|
||||
- Add users → access granted automatically on first login
|
||||
- Change roles → updated on next login
|
||||
- Remove users → access revoked automatically
|
||||
|
||||
<Info>
|
||||
Role changes sync automatically on the user's next sign-in.
|
||||
</Info>
|
||||
|
||||
### Changing your IdP
|
||||
|
||||
In order to change to a different IdP, please contact support and we will guide you through this process.
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Steps to verify successful configuration:
|
||||
|
||||
1. **Test User Sign-In**: Have a test user sign in through the SSO flow (access is granted automatically on first login)
|
||||
2. **Verify User Provisioning**: Confirm that the user is automatically created and has appropriate role permissions
|
||||
3. **Check User Attributes**: Verify that user information (name, email, organization) is correctly populated
|
||||
4. **Test Role Changes**: Update a user's role in your IdP and verify it syncs on their next login
|
||||
5. **Test User Deprovisioning**: Remove a user from your IdP and verify they lose access to Cline on their next login attempt
|
||||
6. **Review Audit Logs**: Check WorkOS audit logs to ensure authentication events are being recorded
|
||||
|
||||
---
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
title: "Configure AWS Bedrock Provider (Admin)"
|
||||
sidebarTitle: "Configure AWS Bedrock (Admin)"
|
||||
description: "This guide explains how administrators configure AWS Bedrock as the organization-wide LLM provider for Cline."
|
||||
---
|
||||
|
||||
As an administrator, you can add AWS Bedrock as the organization-wide LLM provider for all Cline users. This centralized approach ensures consistent access to Amazon's AI models while maintaining your organization's security and compliance requirements through VPC endpoints, region controls, and prompt caching optimizations.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
To get started with setting up AWS Bedrock as your organization's LLM provider, you'll need a few items in place.
|
||||
|
||||
**Administrator access to the Cline Admin console**
|
||||
You need admin privileges to enforce provider settings across your organization. If you can navigate to **Settings → Cline Settings** in the admin console at [app.cline.bot](https://app.cline.bot), you have the right access level.
|
||||
|
||||
<Info>
|
||||
**Quick Check**: Try accessing the settings page now. If you can see the provider configuration options, you're good to go.
|
||||
</Info>
|
||||
|
||||
**AWS Bedrock account with the right permissions**
|
||||
Your AWS account needs specific Bedrock permissions to work with Cline.
|
||||
|
||||
<Note>
|
||||
If you don't have direct AWS access, coordinate with your cloud team to get these permissions set up before proceeding.
|
||||
</Note>
|
||||
|
||||
**Your preferred AWS region**
|
||||
Choose your primary AWS region carefully since this will be enforced for all users.
|
||||
|
||||
<Tip>
|
||||
Check which models are available in your region first. Some newer models might not be available in all regions yet.
|
||||
</Tip>
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/AWS%20Remote%20Config.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
<Steps>
|
||||
<Step title="Access Cline Settings">
|
||||
Navigate to [app.cline.bot](https://app.cline.bot) and sign in with your administrator account. Go to **Settings → Cline Settings**.
|
||||
|
||||
<Info>
|
||||
You should see the provider configuration options if you have the correct admin access level.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="Enable Remote Provider Configuration">
|
||||
Toggle on **Enable settings** to reveal the remote provider configuration options. This allows you to enforce provider settings across your organization.
|
||||
</Step>
|
||||
|
||||
<Step title="Select AWS Bedrock as the API Provider">
|
||||
Open the **API Provider** dropdown menu and select **Amazon Bedrock**. This will open the Bedrock configuration panel where you'll configure all your organization-wide settings.
|
||||
</Step>
|
||||
|
||||
<Step title="Configure Bedrock Settings">
|
||||
The configuration panel includes several settings that control how Bedrock works for your organization. Configure what you need:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Region (required)">
|
||||
Enter your preferred AWS region like `us-west-2` or `us-east-1`. This region will be enforced for all organization members.
|
||||
|
||||
[View AWS Global Infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
|
||||
|
||||
<Tip>
|
||||
For most organizations, `us-east-1` or `us-west-2` are recommended as they have the best model availability.
|
||||
</Tip>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Custom VPC Endpoint (optional)">
|
||||
If your organization uses a private VPC endpoint for Bedrock, specify it here to ensure all API calls go through your network infrastructure.
|
||||
|
||||
[Learn more about AWS PrivateLink](https://docs.aws.amazon.com/vpc/latest/userguide/endpoint-services-overview.html)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Cross-region Inference (optional)">
|
||||
Enable this to let Bedrock automatically route requests to other regions when your primary region has capacity constraints. Useful for maintaining availability during high-demand periods.
|
||||
|
||||
[Learn more about Inference Profiles](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Global Inference Profile (optional)">
|
||||
Turn this on to use AWS's global inference routing, which automatically directs requests to the optimal region based on availability and latency.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Prompt Caching (optional)">
|
||||
Enable prompt caching to reduce costs and latency. Bedrock caches portions of prompts that remain consistent across requests, making repeated interactions faster and cheaper.
|
||||
|
||||
[Learn more about Prompt Caching](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html)
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
</Step>
|
||||
|
||||
<Step title="Save Configuration">
|
||||
After configuring your settings, close the provider configuration panel and click **Save** on the settings page to persist your changes.
|
||||
|
||||
Once saved, all organization members signed into the Cline extension will automatically use AWS Bedrock with your configured settings. They won't be able to select other providers or switch to their personal Cline accounts.
|
||||
|
||||
<Warning>
|
||||
Members can't switch to personal Cline accounts or join other organizations once remote configuration is enabled. This ensures consistent provider usage across your team.
|
||||
</Warning>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Verification
|
||||
|
||||
To verify the configuration:
|
||||
|
||||
1. Check that the provider shows as "Amazon Bedrock" in the Enabled provider field
|
||||
2. Confirm the settings persist after refreshing the page
|
||||
3. Test with a member account to ensure they see only Bedrock as a provider
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Members don't see the configured provider**
|
||||
Ensure you clicked Save after closing the configuration panel. Verify the member account belongs to the correct organization.
|
||||
|
||||
**Configuration changes don't persist**
|
||||
Make sure to click the Save button on the main settings page, not just close the configuration panel.
|
||||
|
||||
**Need to change regions later**
|
||||
You can update the region at any time. Members will need to ensure their local AWS credentials have access to the new region. For more information, refer to the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html).
|
||||
|
||||
For further details, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your internal cloud team.
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
---
|
||||
title: "Configure AWS Bedrock in VS Code (Members)"
|
||||
sidebarTitle: "Configure AWS Bedrock (Member)"
|
||||
description: "Guide for engineers configuring AWS Bedrock credentials in VS Code after admin setup"
|
||||
---
|
||||
|
||||
As a team member, you can connect your local development environment to your organization's AWS Bedrock setup. This guide walks you through configuring your AWS credentials in VS Code so you can start using models through your organization's Bedrock infrastructure. Your administrator has already configured the provider settings—you just need to add your credentials to get started.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
To successfully connect to your organization's AWS Bedrock setup, you'll need a few things ready.
|
||||
|
||||
**Cline extension installed and configured**
|
||||
The Cline extension must be installed in VS Code and you need to be signed into your organization account. If you haven't installed Cline yet, follow our [installation guide](/getting-started/installing-cline).
|
||||
|
||||
<Info>
|
||||
**Quick Check**: Open the Cline panel in VS Code. If you see your organization name in the bottom left, you're signed in correctly.
|
||||
</Info>
|
||||
|
||||
**AWS credentials with Bedrock access**
|
||||
You need AWS credentials that have permission to access Bedrock in your organization's configured region.
|
||||
|
||||
<Note>
|
||||
If you don't have AWS credentials yet, reach out to your IT or cloud team to get access keys or AWS CLI profiles configured with the necessary Bedrock permissions.
|
||||
</Note>
|
||||
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
<Steps>
|
||||
<Step title="Open Cline Settings">
|
||||
Open VS Code and access the Cline settings panel using either of these methods:
|
||||
|
||||
- Click the settings icon (⚙️) in the Cline panel
|
||||
- Click on the API Provider dropdown located directly below the chat area (it will display as `bedrock.anthropic.claude-sonnet-4-20250514-v1:0` or similar)
|
||||
|
||||
</Step>
|
||||
|
||||
<Step title="Select Your Authentication Method">
|
||||
Choose one of the following credential methods to authenticate with AWS Bedrock:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="AWS Bedrock API Key">
|
||||
Use dedicated AWS access keys specifically for Bedrock access.
|
||||
|
||||
[Learn more about AWS Bedrock API Keys](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html)
|
||||
|
||||
1. Select the **API Key** radio button
|
||||
2. Enter your AWS Access Key ID and Secret Access Key
|
||||
3. These credentials are stored locally and used only by the VS Code extension
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Profile">
|
||||
Use an existing AWS CLI profile configured on your machine.
|
||||
|
||||
[Learn more about AWS CLI Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
|
||||
|
||||
1. Select the **AWS Profile** radio button
|
||||
2. Choose or enter the profile name from your `~/.aws/credentials` file
|
||||
3. Cline will use the credentials associated with that profile
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="AWS Credentials">
|
||||
Use your default AWS credential chain (environment variables, EC2 instance roles, etc.).
|
||||
|
||||
1. Select the **AWS Credentials** radio button
|
||||
2. Cline will automatically detect credentials from your environment using the standard AWS credential provider chain
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
<Note>
|
||||
The AWS Region is preconfigured by your administrator and does not need to be set in the extension.
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
<Step title="Verify Configuration">
|
||||
After selecting your authentication method, the extension will display checkmarks for enabled features:
|
||||
|
||||
- ✓ Supports images
|
||||
- ✓ Supports browser use
|
||||
- ✓ Supports prompt caching
|
||||
|
||||
Additional settings like cross-region inference and global inference profile will be locked (shown with a lock icon 🔒) as they're controlled by your administrator.
|
||||
</Step>
|
||||
|
||||
<Step title="Test the Connection">
|
||||
Send a test message in Cline to verify your credentials work correctly with the configured Bedrock region.
|
||||
|
||||
<Tip>
|
||||
**Testing Recommendation**
|
||||
|
||||
It is recommended to test the connection in plan mode to verify everything works correctly before using it for actual tasks.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Authentication errors ("Access Denied" or "Invalid Credentials")**
|
||||
Verify your chosen credential method has the necessary IAM permissions to call Bedrock in the configured region. Required permissions include `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream`. For more information, refer to [AWS Bedrock IAM Permissions](https://docs.aws.amazon.com/bedrock/latest/userguide/security-iam.html).
|
||||
|
||||
**Region-related errors or "model not available"**
|
||||
Ask your administrator to confirm which region is configured for your organization. Ensure your AWS credentials have access to Bedrock in that specific region. [View AWS Global Infrastructure](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/)
|
||||
|
||||
**Don't see AWS Bedrock as an option**
|
||||
Confirm you're signed into the correct Cline organization. Verify your administrator has saved the Bedrock configuration. Try signing out and back into the extension.
|
||||
|
||||
**AWS Credentials option not finding credentials**
|
||||
Verify AWS CLI is installed and configured with `aws configure` ([AWS CLI Installation Guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)). Check that credentials are present in `~/.aws/credentials`. For EC2/ECS environments, ensure IAM roles are properly attached. If using environment variables, set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
|
||||
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
When configuring your AWS credentials, follow these security guidelines:
|
||||
|
||||
- Use IAM roles with minimum required permissions ([AWS IAM Best Practices](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html))
|
||||
- Rotate access keys regularly if using the API Key method
|
||||
- Never store credentials in code or version control
|
||||
- Prefer AWS Profile method for better credential management
|
||||
- Consider using AWS SSO/federated roles for enhanced security
|
||||
|
||||
Your organization administrator controls which models are available. The extension will automatically display available models based on your region's Bedrock configuration. For more information about available models, refer to the [AWS Bedrock Model Access documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).
|
||||
|
||||
For further assistance, consult the [AWS Bedrock Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) and coordinate with your organization's cloud administrator.
|
||||
@@ -1,61 +0,0 @@
|
||||
---
|
||||
title: "Security Concerns"
|
||||
---
|
||||
|
||||
## Enterprise Security with Cline
|
||||
|
||||
Cline addresses enterprise security concerns through its unique client-side architecture that prioritizes data privacy, secure cloud integration, and transparent operations. Below is a comprehensive overview of how Cline maintains robust security measures for enterprise environments.
|
||||
|
||||
### Client-Side Architecture
|
||||
|
||||
Cline operates exclusively as a client-side VSCode extension with zero server-side components. This fundamental design choice ensures that your code and data remain within your secure environment at all times. Unlike traditional AI assistants that send data to external servers for processing, Cline connects directly to your chosen cloud provider's AI endpoints, keeping all sensitive information within your infrastructure boundaries.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/cline-arch.png"
|
||||
alt="Cline's relationship to local and remote assets"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Data Privacy Commitment
|
||||
|
||||
Cline implements a strict zero data retention policy, meaning your intellectual property never leaves your secure environment. The extension does not collect, store, or transmit your code to any central servers. This approach significantly reduces potential attack vectors that might otherwise be introduced through data transmission to third-party systems. Telemetry collection is optional and requires explicit consent.
|
||||
|
||||
### Cloud Provider Integration
|
||||
|
||||
Enterprise teams can access cutting-edge AI models through their existing cloud deployments. Cline supports seamless integration with:
|
||||
|
||||
- AWS Bedrock
|
||||
- Google Cloud Vertex AI
|
||||
- Microsoft Azure
|
||||
|
||||
These integrations utilize your organization's existing security credentials, including native IAM role assumption for AWS. This ensures that all AI processing occurs within your corporate cloud environment, maintaining compliance with your established security protocols.
|
||||
|
||||
### Open-Source Transparency
|
||||
|
||||
Cline's codebase is completely open-source, allowing for comprehensive security auditing by your internal teams. This transparency enables security professionals to verify exactly how the extension functions and confirm that it adheres to your organization's security requirements. Organizations can review the code to ensure it aligns with their security policies before deployment.
|
||||
|
||||
### Controlled Modifications
|
||||
|
||||
The extension implements safeguards against unauthorized changes to your codebase. Cline requires explicit user approval for all file modifications and terminal commands, preventing accidental or unwanted alterations. This approval-based workflow maintains the integrity of your projects while still providing AI assistance.
|
||||
|
||||
### Enterprise Deployment Support
|
||||
|
||||
For organizations with strict security review processes, Cline provides comprehensive documentation including detailed deployment diagrams, sequence diagrams illustrating all data flows, and complete security posture documentation. These materials facilitate thorough security reviews and help demonstrate compliance with enterprise data handling standards and regulations.
|
||||
|
||||
### Access Control
|
||||
|
||||
Enterprise editions of Cline (planned for Q2 2025) will include centralized administration features that allow organizations to:
|
||||
|
||||
- Manage user access with customizable permission levels
|
||||
- Provision accounts with corporate credentials
|
||||
- Immediately revoke access when needed
|
||||
- Control which AI providers and LLM endpoints can be used
|
||||
- Deploy standardized settings across the organization
|
||||
- Prevent unauthorized use of personal API keys
|
||||
|
||||
### Compliance and Governance
|
||||
|
||||
Cline's architecture supports compliance with data sovereignty requirements and enterprise data handling regulations. The planned Enterprise Complete edition will further enhance governance with detailed audit logging, compliance reporting, and automated policy enforcement mechanisms.
|
||||
|
||||
By combining client-side processing, direct cloud provider integration, and transparent operations, Cline offers enterprise teams a secure way to leverage AI assistance while maintaining strict control over their sensitive code and data.
|
||||
@@ -15,6 +15,30 @@ Cline creates a checkpoint after each tool use (file edits, commands, etc.). The
|
||||
|
||||
For example, if you're working on a feature and Cline makes multiple file changes, each change creates a checkpoint. This means you can review each modification and, if needed, roll back to any point without affecting your main Git repository.
|
||||
|
||||
## Enabling or Disabling Checkpoints
|
||||
|
||||
Checkpoints are enabled by default in Cline. To toggle this feature:
|
||||
|
||||
1. Open the Cline settings by clicking the gear icon in the Cline panel
|
||||
2. Go to "Feature Settings"
|
||||
3. Toggle the **"Enable Checkpoints"** checkbox on or off
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/checkpoints.gif"
|
||||
alt="Checkpoints toggle in settings"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### When to Disable Checkpoints
|
||||
|
||||
While checkpoints provide valuable safety nets, you might want to disable them in certain situations:
|
||||
|
||||
- **Large repositories**: If you're working with very large codebases, checkpoints may use additional storage space
|
||||
- **Performance concerns**: On systems with limited resources, disabling checkpoints can slightly improve performance
|
||||
- **Simple tasks**: For quick, low-risk operations where rollback isn't needed
|
||||
|
||||
|
||||
## Viewing Changes & Restoring
|
||||
|
||||
After each tool use, you can:
|
||||
|
||||
@@ -81,6 +81,20 @@ your-project/
|
||||
|
||||
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
|
||||
|
||||
### AGENTS.md Standard Support
|
||||
|
||||
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
|
||||
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
|
||||
your workspace root. This allows you to use the same rules file across different AI
|
||||
coding tools.
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── AGENTS.md
|
||||
├── src/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Tips for Writing Effective Cline Rules
|
||||
|
||||
- Be Clear and Concise: Use simple language and avoid ambiguity.
|
||||
|
||||
@@ -42,17 +42,17 @@ To open Cline in the right sidebar:
|
||||
4. Set the value to `vertical`
|
||||
5. Restart Cursor for the changes to take effect
|
||||
</Step>
|
||||
<Step title="Open Agent Panel">
|
||||
Click the Cursor cube icon button that opens Cursor's agent (right side view panel)
|
||||
<Step title="Open the AI Pane">
|
||||
Click the Cursor cube icon button (AI Pane) that opens Cursor's agent (right side view panel)
|
||||
</Step>
|
||||
<Step title="Drag to Three Dots">
|
||||
Drag the Cline icon directly onto the three dots button - it doesn't work if you just drag it to the top, it has to be the three dots
|
||||
<Step title="Drag Cline to the AI Pane Sidebar">
|
||||
Drag the Cline icon directly into the AI Pane sidebar.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/cursor-side-bar.gif"
|
||||
src="https://storage.googleapis.com/cline_public_images/Cursor-sidebar.gif"
|
||||
alt="Cursor Right Sidebar Setup"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
@@ -3,7 +3,7 @@ title: "Dictation"
|
||||
description: "Communicate with Cline using your voice for faster, more natural AI collaboration"
|
||||
---
|
||||
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about unlocking the kind of fluid collaboration that typing can't match.
|
||||
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about enabling fluid collaboration that typing can't match.
|
||||
|
||||
## Why Voice Changes Everything
|
||||
|
||||
@@ -35,11 +35,14 @@ Dictation works with any AI model you've configured. The transcription happens t
|
||||
|
||||
## System Requirements
|
||||
|
||||
<Note>
|
||||
Dictation is currently not available on Windows. Support for Windows is planned for a future release.
|
||||
</Note>
|
||||
|
||||
Dictation uses FFmpeg to capture your voice across all platforms:
|
||||
|
||||
- **macOS**: FFmpeg (via Homebrew: `brew install ffmpeg`)
|
||||
- **Linux**: FFmpeg (via apt: `sudo apt-get install ffmpeg`)
|
||||
- **Windows**: FFmpeg (via winget: `winget install Gyan.FFmpeg`)
|
||||
|
||||
If you don't have FFmpeg installed, Cline will automatically detect this and prompt you to install it with a single click.
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: "Explain Changes"
|
||||
sidebarTitle: "Explain Changes"
|
||||
---
|
||||
|
||||
Explain Changes is an AI-powered code review feature that adds inline explanations to your code changes. When Cline makes modifications to your codebase, you can click a button to get streaming, contextual explanations that appear directly in VS Code's diff view.
|
||||
|
||||
<Note>
|
||||
Explain Changes requires **[Checkpoints](/features/checkpoints)** to be enabled. Make sure to enable checkpoints in your Cline settings before using this feature.
|
||||
</Note>
|
||||
|
||||
<Frame>
|
||||
<video
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
src="https://storage.googleapis.com/cline_public_images/explain-code-button.mp4"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
After Cline completes a task that involves file changes, you'll see an "Explain Changes" button alongside the "View Changes" button in the completion message. Clicking this button:
|
||||
|
||||
1. Opens a multi-file diff view showing all changed files
|
||||
2. Streams AI-generated explanations as inline comments
|
||||
3. Places comments at relevant code locations to explain what changed and why
|
||||
|
||||
The AI uses the full conversation context to provide meaningful explanations, not just describing what code does, but explaining the reasoning behind the changes.
|
||||
|
||||
## Interactive Comment Threads
|
||||
|
||||
One of the most powerful aspects of Explain Changes is that the comments are fully interactive. You can have conversations directly within each comment thread.
|
||||
|
||||
### Asking Follow-up Questions
|
||||
|
||||
Each explanation comment has a reply input where you can ask questions about that specific piece of code:
|
||||
|
||||
- "Why did you use this approach instead of X?"
|
||||
- "Can you explain this pattern in more detail?"
|
||||
- "What would happen if we changed this to Y?"
|
||||
|
||||
The AI will respond with context-aware answers, understanding both the code being discussed and the original task context.
|
||||
|
||||
### Moving to Main Chat
|
||||
|
||||
If a conversation in a comment thread becomes complex or you want to continue working on that code, click the title area of the comment thread to move the entire conversation into Cline's main chat input. This lets you:
|
||||
|
||||
- Continue the discussion with full Cline capabilities
|
||||
- Have Cline make additional changes based on the discussion
|
||||
- Keep the context from your review conversation
|
||||
|
||||
## When to Use Explain Changes
|
||||
|
||||
### Learning and Onboarding
|
||||
|
||||
When you're new to a codebase or working with unfamiliar patterns, Explain Changes helps you understand not just what Cline did, but why. The explanations cover:
|
||||
|
||||
- Design decisions and trade-offs
|
||||
- Technical concepts and patterns used
|
||||
- Relationships between different changes
|
||||
|
||||
### Code Review
|
||||
|
||||
Use Explain Changes as part of your review process:
|
||||
|
||||
- Understand complex changes before committing
|
||||
- Verify the AI's reasoning matches your expectations
|
||||
- Catch potential issues by understanding the full context
|
||||
|
||||
### Knowledge Transfer
|
||||
|
||||
The explanations serve as documentation for your changes. When other team members review your code, they can see the reasoning behind each modification.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Ask specific questions**: The more specific your follow-up questions, the more useful the AI's responses will be.
|
||||
|
||||
2. **Use for complex changes**: Explain Changes is most valuable for multi-file changes or complex logic. For simple changes, the diff view alone may be sufficient.
|
||||
|
||||
3. **Move important discussions to chat**: If a comment thread reveals something that needs more work, move it to main chat to take action.
|
||||
|
||||
4. **Review before committing**: Use Explain Changes as a final check before committing changes to ensure you understand everything Cline did.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Checkpoints](/features/checkpoints) - Required for Explain Changes to work
|
||||
- [/explain-changes](/features/slash-commands/explain-changes) - Slash command to explain any git diff
|
||||
@@ -0,0 +1,437 @@
|
||||
---
|
||||
title: "Hook Reference"
|
||||
sidebarTitle: "Hook Reference"
|
||||
description: "Complete API reference for all Cline hook types, JSON schemas, and field documentation"
|
||||
---
|
||||
|
||||
This reference provides complete technical documentation for all hook types, their JSON schemas, input/output formats, and communication protocols.
|
||||
|
||||
## Hook Types
|
||||
|
||||
Cline provides multiple hook types that let you tap into different stages of the AI workflow. They're organized into categories based on their trigger points and use cases.
|
||||
|
||||
<Note>
|
||||
The hook names below are the exact file names you need to create. For example, to use the TaskStart hook, create a file named `TaskStart` (no file extension) in your hooks directory.
|
||||
</Note>
|
||||
|
||||
Each hook receives base fields in addition to its specific data: `clineVersion`, `hookName`, `timestamp`, `taskId`, `workspaceRoots`, `userId`.
|
||||
|
||||
### Tool Execution Hooks
|
||||
|
||||
These hooks intercept and validate tool operations before and after they execute. Use them to enforce policies, track changes, and learn from operations.
|
||||
|
||||
#### `PreToolUse`
|
||||
|
||||
Triggered immediately before Cline uses any tool (see the [Cline Tools Reference Guide](/cline-tools) for all available tools). Use it to block invalid operations, validate parameters, and enforce project policies before changes happen.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PreToolUse",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"preToolUse": {
|
||||
"toolName": "string",
|
||||
"parameters": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Usage:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Block creating .js files in TypeScript projects
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
if [[ "$tool_name" == "write_to_file" ]]; then
|
||||
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path')
|
||||
if [[ "$file_path" == *.js ]] && [[ -f "tsconfig.json" ]]; then
|
||||
echo '{"cancel": true, "errorMessage": "JavaScript files not allowed in TypeScript project"}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
#### `PostToolUse`
|
||||
|
||||
Triggered immediately after Cline uses any tool (see the [Cline Tools Reference Guide](/cline-tools) for all available tools). Use it to learn from results, track performance metrics, and build project knowledge based on operations performed.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "PostToolUse",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"postToolUse": {
|
||||
"toolName": "string",
|
||||
"parameters": {},
|
||||
"result": "string",
|
||||
"success": boolean,
|
||||
"executionTimeMs": number
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Usage:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Log slow operations for performance monitoring
|
||||
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs')
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
|
||||
if (( execution_time > 5000 )); then
|
||||
context="PERFORMANCE: Slow operation detected - $tool_name took ${execution_time}ms"
|
||||
echo "{\"cancel\": false, \"contextModification\": \"$context\"}"
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
### User Interaction Hooks
|
||||
|
||||
These hooks monitor and enhance user communication with Cline. Use them to validate input, inject context, and track interaction patterns.
|
||||
|
||||
#### `UserPromptSubmit`
|
||||
|
||||
Triggered when the user enters text into the prompt box and presses enter to start a new task, continue a completed task, or resume a cancelled task. Use it to validate input, inject context based on the prompt, and track interaction patterns.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "UserPromptSubmit",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"userPromptSubmit": {
|
||||
"prompt": "string",
|
||||
"attachments": ["string"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Usage:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Inject coding standards context for certain keywords
|
||||
prompt=$(echo "$input" | jq -r '.userPromptSubmit.prompt')
|
||||
context=""
|
||||
|
||||
if echo "$prompt" | grep -qi "component\|react"; then
|
||||
context="CODING_STANDARDS: Follow React functional component patterns with proper TypeScript types"
|
||||
elif echo "$prompt" | grep -qi "api\|endpoint"; then
|
||||
context="CODING_STANDARDS: Use consistent REST API patterns with proper error handling"
|
||||
fi
|
||||
|
||||
if [[ -n "$context" ]]; then
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
### Task Lifecycle Hooks
|
||||
|
||||
These hooks monitor and respond to task state changes from start to finish. Use them to track progress, restore state, and trigger workflows.
|
||||
|
||||
#### `TaskStart`
|
||||
|
||||
Triggered once at the beginning of a new task. Use it to detect project type, initialize tracking, and inject initial context that shapes how Cline approaches the work.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskStart",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskStart": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string",
|
||||
"initialTask": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Usage:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Detect project type and inject relevant context
|
||||
context=""
|
||||
|
||||
if [[ -f "package.json" ]]; then
|
||||
if grep -q "react" package.json; then
|
||||
context="PROJECT_TYPE: React application detected. Follow component-based architecture."
|
||||
elif grep -q "express" package.json; then
|
||||
context="PROJECT_TYPE: Express.js API detected. Follow RESTful patterns."
|
||||
else
|
||||
context="PROJECT_TYPE: Node.js project detected."
|
||||
fi
|
||||
elif [[ -f "requirements.txt" ]] || [[ -f "pyproject.toml" ]]; then
|
||||
context="PROJECT_TYPE: Python project detected. Follow PEP 8 standards."
|
||||
elif [[ -f "Cargo.toml" ]]; then
|
||||
context="PROJECT_TYPE: Rust project detected. Follow Rust conventions."
|
||||
fi
|
||||
|
||||
if [[ -n "$context" ]]; then
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
#### `TaskResume`
|
||||
|
||||
Triggered when the user resumes a task that has been cancelled or aborted. Use it to restore state, refresh context, and log resumption for analytics or external system notifications.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskResume",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskResume": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
},
|
||||
"previousState": {
|
||||
"lastMessageTs": "string",
|
||||
"messageCount": "string",
|
||||
"conversationHistoryDeleted": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `TaskCancel`
|
||||
|
||||
Triggered when the user cancels a task or aborts a hook execution. Use it to cleanup resources, log cancellation details, and notify external systems about interrupted work.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskCancel",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskCancel": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `TaskComplete`
|
||||
|
||||
Triggered when Cline finishes its work and successfully executes the `attempt_completion` tool to finalize the task output. Use it to track completion metrics, generate reports, log task outcomes, and trigger completion workflows.
|
||||
|
||||
**Input Fields:**
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "TaskComplete",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"taskComplete": {
|
||||
"taskMetadata": {
|
||||
"taskId": "string",
|
||||
"ulid": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example Usage:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
input=$(cat)
|
||||
|
||||
# Extract task metadata
|
||||
task_id=$(echo "$input" | jq -r '.taskComplete.taskMetadata.taskId // "unknown"')
|
||||
ulid=$(echo "$input" | jq -r '.taskComplete.taskMetadata.ulid // "unknown"')
|
||||
|
||||
# Log completion
|
||||
completion_log="$HOME/.cline_completions/$(date +%Y-%m-%d).log"
|
||||
mkdir -p "$(dirname "$completion_log")"
|
||||
|
||||
echo "$(date -Iseconds): Task $task_id completed (ULID: $ulid)" >> "$completion_log"
|
||||
|
||||
# Provide context about completion
|
||||
context="TASK_COMPLETED: Task $task_id finished successfully. Completion logged."
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
```
|
||||
|
||||
### System Events Hooks
|
||||
|
||||
These hooks monitor internal Cline operations and system-level events. Use them to track context usage, log system behavior, and analyze performance patterns.
|
||||
|
||||
## JSON Communication Protocol
|
||||
|
||||
Hooks receive JSON via stdin and return JSON via stdout.
|
||||
|
||||
### Input Format
|
||||
|
||||
All hooks receive a JSON object through stdin with this base structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"clineVersion": "string",
|
||||
"hookName": "string",
|
||||
"timestamp": "string",
|
||||
"taskId": "string",
|
||||
"workspaceRoots": ["string"],
|
||||
"userId": "string",
|
||||
"[hookSpecificField]": {
|
||||
// Hook-specific data structure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Output Format
|
||||
|
||||
Your hook script must output a JSON response as the final stdout content:
|
||||
|
||||
```json
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "WORKSPACE_RULES: Use TypeScript",
|
||||
"errorMessage": "Error details if blocking"
|
||||
}
|
||||
```
|
||||
|
||||
**Field Descriptions:**
|
||||
|
||||
- **`cancel`** (required): Boolean controlling whether execution continues
|
||||
- `true`: Block the current action
|
||||
- `false`: Allow the action to proceed
|
||||
|
||||
- **`contextModification`** (optional): String that gets injected into the conversation
|
||||
- Affects future AI decisions, not the current one
|
||||
- Use clear prefixes like `WORKSPACE_RULES:`, `PERFORMANCE:`, `SECURITY:` for categorization
|
||||
- Maximum length: 50KB
|
||||
|
||||
- **`errorMessage`** (optional): String shown to user when `cancel` is `true`
|
||||
- Only displayed when blocking an action
|
||||
- Should explain why the action was blocked
|
||||
|
||||
### Logging During Execution
|
||||
|
||||
Your hook script can output logging or diagnostic information to stdout during execution, as long as the JSON response is the last thing written:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
echo "Processing hook..." # This is fine
|
||||
echo "Tool: $tool_name" # This is also fine
|
||||
|
||||
# The JSON must be last:
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
Cline will parse only the final JSON object from stdout.
|
||||
|
||||
### Error Handling
|
||||
|
||||
Hook execution errors don't prevent task execution - only returning `"cancel": true` can halt a task. All other errors are treated as hook failures, not reasons to abort the task.
|
||||
|
||||
**Hook Status Display:**
|
||||
|
||||
- **Completed** (grey): Hook executed successfully, regardless of whether it returned `"cancel": false` or no JSON output
|
||||
- **Failed** (red): Hook exited with non-zero status, output invalid JSON, or timed out. The UI displays the error details (e.g., exit code number)
|
||||
- **Aborted** (red): Hook returned `"cancel": true`, halting the task. User must manually resume the task to continue
|
||||
|
||||
**Important:** Even when a hook fails (non-zero exit, invalid JSON, timeout), Cline continues with the task. Only `"cancel": true` stops execution.
|
||||
|
||||
### Context Modification Timing
|
||||
|
||||
Context injection affects future decisions, not current ones. When a hook runs:
|
||||
|
||||
1. The AI has already decided what to do
|
||||
2. The hook can block or allow it
|
||||
3. Any context gets added to the conversation
|
||||
4. The next AI request sees that context
|
||||
|
||||
This means:
|
||||
- **PreToolUse hooks**: Use for blocking bad actions + injecting context for next decision
|
||||
- **PostToolUse hooks**: Use for learning from completed actions
|
||||
|
||||
### Helpful Tip: String Escaping in JSON
|
||||
|
||||
When your hook needs to include strings containing unescaped quote characters (`"`) in JSON output, use jq's `--arg` flag for proper escaping:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# When $output contains unescaped quote characters (")...
|
||||
output='{"foo":"bar"}'
|
||||
|
||||
# Use the --arg flag for automatic string escaping
|
||||
jq -n --arg ctx "$output" '{cancel: false, contextModification: $ctx}'
|
||||
|
||||
# This will result in:
|
||||
# {
|
||||
# "cancel": false,
|
||||
# "contextModification": "{\"foo\":\"bar\"}"
|
||||
# }
|
||||
```
|
||||
|
||||
The `--arg` flag automatically escapes special characters, preventing JSON parsing errors when your context modification includes complex strings or nested JSON structures.
|
||||
|
||||
## Hook Execution Environment
|
||||
|
||||
### Execution Context
|
||||
|
||||
Hooks are executable scripts that run with the same permissions as VS Code. They have unrestricted access to:
|
||||
- The entire filesystem (any file the user can access)
|
||||
- All environment variables
|
||||
- System commands and tools
|
||||
- Network resources
|
||||
|
||||
Hooks can perform any operation the user could perform in a terminal, including reading and writing files outside the workspace, making network requests, and executing system commands.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
<Warning>
|
||||
Hooks run with the same permissions as VS Code. They can access all workspace files and environment variables. Review hooks from untrusted sources before enabling them.
|
||||
</Warning>
|
||||
|
||||
### Performance Guidelines
|
||||
|
||||
Hooks have a 30 second timeout. As long as your hook completes within this time, it can perform any operations needed, including network calls or heavy computations.
|
||||
|
||||
### Hook Discovery
|
||||
|
||||
Cline searches for hooks in this order:
|
||||
1. Project-specific: `.clinerules/hooks/` in workspace root
|
||||
2. User-global: `~/Documents/Cline/Rules/Hooks/`
|
||||
|
||||
Project-specific hooks override global hooks with the same name.
|
||||
@@ -0,0 +1,146 @@
|
||||
---
|
||||
title: "Hooks Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "Inject custom logic into Cline's workflow to validate operations, monitor tool usage, and shape AI decisions"
|
||||
---
|
||||
|
||||
Hooks let you inject custom logic into Cline's workflow at key moments. Think of them as automated checkpoints where you can validate operations before they execute, monitor tool usage as it happens, and shape how Cline makes decisions.
|
||||
|
||||
Hooks run automatically when specific events happen during development. They receive detailed information about each operation, can block problematic actions before they cause issues, and can inject context that guides future AI decisions.
|
||||
|
||||
The real power comes from combining these capabilities. You can:
|
||||
|
||||
- Stop operations before they cause problems (like creating `.js` files in a TypeScript project)
|
||||
- Learn from what's happening and build up project knowledge over time
|
||||
- Monitor performance and catch issues as they emerge
|
||||
- Track everything for analytics or compliance
|
||||
- Trigger external tools or services at the right moments
|
||||
|
||||
## Getting Started
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/hooks.gif" alt="Hooks in action" />
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
Hooks work across all platforms: Windows, macOS, and Linux. The bash examples in this documentation work with standard shells on all platforms (including Git Bash or WSL on Windows).
|
||||
</Note>
|
||||
|
||||
Setting up hooks in Cline is user-friendly with the built-in hooks management interface. Here's how to get started:
|
||||
|
||||
<Steps>
|
||||
<Step title="Access the Hooks Interface">
|
||||
Navigate to the Hooks management interface:
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/hooks/hooks-interface-with-dropdown.png" alt="Hooks management interface showing Global Hooks and project-specific hooks with dropdown menu" />
|
||||
</Frame>
|
||||
|
||||
1. Open Cline (ensure hooks are enabled in settings)
|
||||
2. Look for the **Hooks** tab at the top (alongside Rules and Workflows)
|
||||
3. Click on **Hooks** to open the hooks management panel
|
||||
|
||||
The interface shows you all available hook types and existing hooks organized by workspace.
|
||||
</Step>
|
||||
|
||||
<Step title="Understand Hook Locations">
|
||||
Hooks are automatically organized by location in the interface:
|
||||
|
||||
**Global Hooks** - Apply to all workspaces:
|
||||
- Stored in `~/Documents/Cline/Rules/Hooks/`
|
||||
- Perfect for personal coding standards and universal rules
|
||||
|
||||
**Project-Specific Hooks** - Apply only to current project:
|
||||
- Stored in `.clinerules/hooks/` within your repo
|
||||
- Great for project-specific validation and team workflows
|
||||
- Can be committed to version control for team sharing
|
||||
|
||||
Multi-root workspaces run hooks from all of the repos in your open workspace, making it easy to manage and run hooks across different repos within the same workspace.
|
||||
</Step>
|
||||
|
||||
<Step title="Create Your First Hook">
|
||||
Use the intuitive interface to create hooks:
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/hooks/hooks-empty-state.png" alt="Empty hooks interface showing New hook... dropdowns for both Global Hooks and project-specific hooks before any hooks are created" />
|
||||
</Frame>
|
||||
|
||||
1. **Choose your location**: Decide between Global Hooks or project-specific hooks
|
||||
2. **Select hook type**: Click the **"New hook..."** dropdown in your chosen location
|
||||
3. **Pick a hook type**: The dropdown shows all available hook types that haven't been created yet in this location. Only one of each hook type is allowed per hooks directory, so the dropdown automatically filters to show only the remaining available types.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/hooks/new-hook-dropdown.png" alt="Creating a new hook with the dropdown menu showing UserPromptSubmit selected with description" />
|
||||
</Frame>
|
||||
|
||||
4. **Review and edit the hook**: Click the pencil icon to review the hook's code and add your custom logic
|
||||
5. **Enable the hook**: Once you understand and approve of the hook's behavior, toggle the switch to activate it
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/hooks/hook-controls.png" alt="Hook management controls showing toggle, edit, and delete buttons for each hook" />
|
||||
</Frame>
|
||||
|
||||
<Warning>
|
||||
Always review a hook's code before enabling it. Hooks execute automatically during your workflow, so it's important to understand what they do before activation.
|
||||
</Warning>
|
||||
</Step>
|
||||
|
||||
<Step title="Test Your Hook">
|
||||
To develop and refine your hook, you'll need to trigger it multiple times during testing. Each hook type is triggered by different events in Cline's workflow. For example:
|
||||
|
||||
- **TaskStart** hooks trigger when you start a new task
|
||||
- **PreToolUse** hooks trigger before Cline executes tools like file editing
|
||||
- **PostToolUse** hooks trigger after tool execution completes
|
||||
- **UserPromptSubmit** hooks trigger when you submit a message to Cline
|
||||
|
||||
For complete details on when each hook type is triggered and how to test them effectively, see the [Hook Reference](/features/hooks/hook-reference) documentation. This includes the specific conditions that trigger each hook and examples of how to invoke them during development.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Tip>
|
||||
Start with a simple hook that just logs information before building complex validation logic. This helps you understand the data structure and timing.
|
||||
</Tip>
|
||||
|
||||
## What You Can Build
|
||||
|
||||
Once you understand the basics, hooks open up creative possibilities:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Intelligent Code Review" icon="code-branch">
|
||||
Run linters or custom validators before files get saved. Block commits that don't pass checks. Track code quality metrics over time.
|
||||
</Card>
|
||||
|
||||
<Card title="Security Enforcement" icon="shield-halved">
|
||||
Prevent operations that violate security policies. Detect when sensitive data might be exposed. Audit all file access for compliance.
|
||||
</Card>
|
||||
|
||||
<Card title="Development Analytics" icon="chart-line">
|
||||
Measure how long different operations take. Identify patterns in how the AI works. Generate productivity reports from hook data.
|
||||
</Card>
|
||||
|
||||
<Card title="Integration Hub" icon="plug">
|
||||
Connect to issue trackers when certain keywords appear. Update project management tools. Sync with external APIs at the right moments.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
The key is combining hooks with external tools. A hook can be the glue between Cline's workflow and the rest of your development ecosystem.
|
||||
|
||||
## Explore the Documentation
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Hook Reference" icon="book" href="/features/hooks/hook-reference">
|
||||
Complete API reference for all hook types, JSON schemas, and field documentation.
|
||||
</Card>
|
||||
|
||||
<Card title="Samples" icon="code" href="/features/hooks/samples">
|
||||
Practical examples and complete working scripts for common use cases.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Related Features
|
||||
|
||||
Hooks complement other Cline features:
|
||||
|
||||
- [Cline Rules](/features/cline-rules) define high-level guidance that hooks can enforce
|
||||
- [Checkpoints](/features/checkpoints) let you roll back changes if a hook didn't catch an issue
|
||||
- [Auto-Approve](/features/auto-approve) works well with hooks as safety nets for automated operations
|
||||
@@ -0,0 +1,755 @@
|
||||
---
|
||||
title: "Samples"
|
||||
sidebarTitle: "Samples"
|
||||
description: "Practical hook examples organized by complexity level - from beginner to advanced patterns"
|
||||
---
|
||||
|
||||
This page provides complete, production-ready hook examples organized by skill level. Each example includes full working code, detailed explanations, and guidance on when to use each pattern.
|
||||
|
||||
## How to Use These Samples
|
||||
|
||||
Each sample is designed to be:
|
||||
- **Copy-and-paste ready**: Use them directly or as starting points
|
||||
- **Educational**: Learn hook concepts through progressive complexity
|
||||
- **Practical**: Solve real development workflow challenges
|
||||
|
||||
Choose samples based on your experience level and gradually work up to more advanced patterns.
|
||||
|
||||
---
|
||||
|
||||
## Beginner Examples
|
||||
|
||||
Perfect for getting started with hooks. These examples demonstrate core concepts with straightforward logic.
|
||||
|
||||
### 1. Project Type Detection
|
||||
|
||||
**Hook:** `TaskStart`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Project Type Detection Hook
|
||||
#
|
||||
# Overview: Automatically detects project type at task start and injects relevant
|
||||
# coding standards and best practices into the AI context. This helps Cline understand
|
||||
# your project structure and apply appropriate conventions from the beginning.
|
||||
#
|
||||
# Demonstrates: Basic hook input/output, file system checks, conditional logic,
|
||||
# and context injection to guide AI behavior.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
# Read basic JSON structure and detect project type
|
||||
context=""
|
||||
|
||||
# Check for different project indicators
|
||||
if [[ -f "package.json" ]]; then
|
||||
if grep -q "react" package.json; then
|
||||
context="PROJECT_TYPE: React application detected. Follow component-based architecture and use functional components."
|
||||
elif grep -q "express" package.json; then
|
||||
context="PROJECT_TYPE: Express.js API detected. Follow RESTful patterns and proper middleware structure."
|
||||
else
|
||||
context="PROJECT_TYPE: Node.js project detected. Use proper npm scripts and dependency management."
|
||||
fi
|
||||
elif [[ -f "requirements.txt" ]] || [[ -f "pyproject.toml" ]]; then
|
||||
context="PROJECT_TYPE: Python project detected. Follow PEP 8 standards and use virtual environments."
|
||||
elif [[ -f "Cargo.toml" ]]; then
|
||||
context="PROJECT_TYPE: Rust project detected. Follow Rust conventions and use proper error handling."
|
||||
elif [[ -f "go.mod" ]]; then
|
||||
context="PROJECT_TYPE: Go project detected. Follow Go conventions and use proper package structure."
|
||||
fi
|
||||
|
||||
# Return the context to guide Cline's behavior
|
||||
if [[ -n "$context" ]]; then
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Reading hook input with `input=$(cat)`
|
||||
- Using file system checks to detect project type
|
||||
- Returning context to influence AI behavior
|
||||
- Basic JSON output with `jq`
|
||||
|
||||
### 2. File Extension Validator
|
||||
|
||||
**Hook:** `PreToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# File Extension Validator Hook
|
||||
#
|
||||
# Overview: Enforces TypeScript file extensions in TypeScript projects by blocking
|
||||
# creation of .js and .jsx files. This prevents common mistakes where developers
|
||||
# accidentally create JavaScript files when they should be using TypeScript.
|
||||
#
|
||||
# Demonstrates: PreToolUse blocking, parameter extraction, conditional validation,
|
||||
# and providing clear error messages to guide users toward correct file extensions.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
# Extract tool information
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
|
||||
# Only process file creation tools
|
||||
if [[ "$tool_name" != "write_to_file" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if this is a TypeScript project
|
||||
if [[ ! -f "tsconfig.json" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get the file path from tool parameters
|
||||
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')
|
||||
|
||||
if [[ -z "$file_path" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block .js files in TypeScript projects
|
||||
if [[ "$file_path" == *.js ]]; then
|
||||
echo '{"cancel": true, "errorMessage": "JavaScript files (.js) are not allowed in TypeScript projects. Use .ts extension instead."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block .jsx files, suggest .tsx
|
||||
if [[ "$file_path" == *.jsx ]]; then
|
||||
echo '{"cancel": true, "errorMessage": "JSX files (.jsx) are not allowed in TypeScript projects. Use .tsx extension instead."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Everything is OK
|
||||
echo '{"cancel": false}'
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Extracting tool name and parameters
|
||||
- Conditional logic based on project state
|
||||
- Blocking operations with `"cancel": true`
|
||||
- Providing helpful error messages
|
||||
|
||||
### 3. Basic Performance Monitor
|
||||
|
||||
**Hook:** `PostToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Basic Performance Monitor Hook
|
||||
#
|
||||
# Overview: Monitors tool execution times and logs operations that exceed a 3-second
|
||||
# threshold. This helps identify performance bottlenecks and provides feedback to
|
||||
# users about system resource issues that may be slowing down Cline's operations.
|
||||
#
|
||||
# Demonstrates: PostToolUse hook usage, arithmetic operations in bash, simple file
|
||||
# logging, and conditional context injection based on performance metrics.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
# Extract performance information
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs // 0')
|
||||
success=$(echo "$input" | jq -r '.postToolUse.success')
|
||||
|
||||
# Log slow operations (threshold: 3 seconds)
|
||||
if (( execution_time > 3000 )); then
|
||||
# Create simple log directory
|
||||
mkdir -p "$HOME/.cline_logs"
|
||||
|
||||
# Log the slow operation
|
||||
echo "$(date -Iseconds): SLOW OPERATION - $tool_name took ${execution_time}ms" >> "$HOME/.cline_logs/performance.log"
|
||||
|
||||
# Provide feedback to user
|
||||
context="PERFORMANCE: Operation $tool_name took ${execution_time}ms. Consider checking system resources if this happens frequently."
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Processing results after tool execution
|
||||
- Basic arithmetic operations in bash
|
||||
- Simple file logging
|
||||
- Conditional context injection
|
||||
|
||||
## Intermediate Examples
|
||||
|
||||
These examples demonstrate more advanced concepts including external tool integration, pattern matching, and structured logging.
|
||||
|
||||
### 4. Code Quality with Linting
|
||||
|
||||
**Hook:** `PreToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Code Quality Linting Hook
|
||||
#
|
||||
# Overview: Integrates ESLint and Flake8 to enforce code quality standards before
|
||||
# files are written. Blocks file creation if linting errors are detected, ensuring
|
||||
# all code meets quality standards. Supports TypeScript, JavaScript, and Python files.
|
||||
#
|
||||
# Demonstrates: External tool integration, temporary file handling, regex pattern
|
||||
# matching, and comprehensive error reporting with actionable feedback.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
|
||||
# Only lint file write operations
|
||||
if [[ "$tool_name" != "write_to_file" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')
|
||||
|
||||
# Skip non-code files
|
||||
if [[ ! "$file_path" =~ \.(ts|tsx|js|jsx|py|rs)$ ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get file content from the tool parameters
|
||||
content=$(echo "$input" | jq -r '.preToolUse.parameters.content // empty')
|
||||
|
||||
if [[ -z "$content" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create temporary file for linting
|
||||
temp_file=$(mktemp)
|
||||
echo "$content" > "$temp_file"
|
||||
|
||||
# Run appropriate linter based on file extension
|
||||
lint_errors=""
|
||||
if [[ "$file_path" =~ \.(ts|tsx)$ ]] && command -v eslint > /dev/null; then
|
||||
lint_output=$(eslint "$temp_file" --format=json 2>/dev/null || true)
|
||||
if [[ "$lint_output" != "[]" ]] && [[ -n "$lint_output" ]]; then
|
||||
error_count=$(echo "$lint_output" | jq '.[0].errorCount // 0')
|
||||
if (( error_count > 0 )); then
|
||||
messages=$(echo "$lint_output" | jq -r '.[0].messages[] | "\(.line):\(.column) \(.message)"')
|
||||
lint_errors="ESLint errors found:\n$messages"
|
||||
fi
|
||||
fi
|
||||
elif [[ "$file_path" =~ \.py$ ]] && command -v flake8 > /dev/null; then
|
||||
lint_output=$(flake8 "$temp_file" 2>/dev/null || true)
|
||||
if [[ -n "$lint_output" ]]; then
|
||||
lint_errors="Flake8 errors found:\n$lint_output"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Block if linting errors found
|
||||
if [[ -n "$lint_errors" ]]; then
|
||||
error_message="Code quality check failed. Please fix these issues:\n\n$lint_errors"
|
||||
jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Temporary file creation and cleanup
|
||||
- External tool integration (eslint, flake8)
|
||||
- Complex pattern matching with regex
|
||||
- Structured error reporting
|
||||
|
||||
### 5. Security Scanner
|
||||
|
||||
**Hook:** `PreToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Security Scanner Hook
|
||||
#
|
||||
# Overview: Scans file content for hardcoded secrets (API keys, tokens, passwords)
|
||||
# before files are written. Blocks creation of files containing secrets except in
|
||||
# safe locations like .env.example files or documentation, preventing credential leaks.
|
||||
#
|
||||
# Demonstrates: Pattern matching with regex arrays, file path exception handling,
|
||||
# security-focused validation, and clear user guidance in error messages.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
|
||||
|
||||
# Only check file operations
|
||||
if [[ "$tool_name" != "write_to_file" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
content=$(echo "$input" | jq -r '.preToolUse.parameters.content // empty')
|
||||
file_path=$(echo "$input" | jq -r '.preToolUse.parameters.path // empty')
|
||||
|
||||
# Skip if no content
|
||||
if [[ -z "$content" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Define secret patterns (simplified for readability)
|
||||
secrets_found=""
|
||||
|
||||
# Check for API keys
|
||||
if echo "$content" | grep -qi "api[_-]*key.*[=:].*['\"][a-z0-9_-]{10,}['\"]"; then
|
||||
secrets_found+="- API key pattern detected\n"
|
||||
fi
|
||||
|
||||
# Check for tokens
|
||||
if echo "$content" | grep -qi "token.*[=:].*['\"][a-z0-9_-]{10,}['\"]"; then
|
||||
secrets_found+="- Token pattern detected\n"
|
||||
fi
|
||||
|
||||
# Check for passwords
|
||||
if echo "$content" | grep -qi "password.*[=:].*['\"][^'\"]{8,}['\"]"; then
|
||||
secrets_found+="- Password pattern detected\n"
|
||||
fi
|
||||
|
||||
# Allow secrets in safe files
|
||||
safe_patterns=("\.env\.example$" "\.env\.template$" "/docs/" "\.md$")
|
||||
is_safe_file=false
|
||||
for safe_pattern in "${safe_patterns[@]}"; do
|
||||
if [[ "$file_path" =~ $safe_pattern ]]; then
|
||||
is_safe_file=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$secrets_found" ]] && [[ "$is_safe_file" == false ]]; then
|
||||
error_message="🔒 SECURITY ALERT: Potential secrets detected in $file_path
|
||||
|
||||
$secrets_found
|
||||
Please use environment variables or a secrets management service instead."
|
||||
|
||||
jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Pattern arrays and iteration
|
||||
- File path exception handling
|
||||
- Security-focused validation
|
||||
- Clear user guidance in error messages
|
||||
|
||||
### 6. Git Workflow Assistant
|
||||
|
||||
**Hook:** `PostToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Git Workflow Assistant Hook
|
||||
#
|
||||
# Overview: Analyzes file modifications and provides intelligent git workflow suggestions
|
||||
# based on file types and current branch. Encourages best practices like feature branches
|
||||
# for components and test branches for test files, with actionable git commands.
|
||||
#
|
||||
# Demonstrates: Git integration, branch analysis, file path pattern matching, and
|
||||
# contextual suggestions to guide users toward better git practices.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
success=$(echo "$input" | jq -r '.postToolUse.success')
|
||||
|
||||
# Only process successful file modifications
|
||||
if [[ "$success" != "true" ]] || [[ "$tool_name" != "write_to_file" && "$tool_name" != "replace_in_file" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if we're in a git repository
|
||||
if ! git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
file_path=$(echo "$input" | jq -r '.postToolUse.parameters.path // empty')
|
||||
current_branch=$(git branch --show-current 2>/dev/null || echo "main")
|
||||
|
||||
# Analyze file type and suggest appropriate branch naming
|
||||
context=""
|
||||
if [[ "$file_path" == *"component"* ]] && [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
|
||||
component_name=$(basename "$file_path" .tsx .ts .jsx .js)
|
||||
context="GIT_WORKFLOW: Consider creating a feature branch: git checkout -b feature/add-${component_name,,}-component"
|
||||
elif [[ "$file_path" == *"test"* ]] || [[ "$file_path" == *"spec"* ]]; then
|
||||
if [[ "$current_branch" == "main" || "$current_branch" == "master" ]]; then
|
||||
context="GIT_WORKFLOW: Consider creating a test branch: git checkout -b test/add-tests-$(basename "$(dirname "$file_path")")"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add staging guidance
|
||||
if [[ -n "$context" ]]; then
|
||||
context="$context After completing changes, use 'git add $file_path' to stage for commit."
|
||||
else
|
||||
context="GIT_WORKFLOW: File modified: $file_path. Use 'git add $file_path' when ready to commit."
|
||||
fi
|
||||
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Git repository detection
|
||||
- Branch analysis and suggestions
|
||||
- File path analysis for context
|
||||
- Actionable user guidance
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
These examples showcase sophisticated patterns including external integrations, asynchronous processing, and complex state management.
|
||||
|
||||
### 7. Comprehensive Task Lifecycle Manager
|
||||
|
||||
**Hook:** `TaskComplete`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Comprehensive Task Lifecycle Manager Hook
|
||||
#
|
||||
# Overview: Tracks task completions by generating detailed markdown reports with
|
||||
# workspace information and git state, and optionally sends webhook notifications
|
||||
# to external systems. Perfect for enterprise environments requiring audit trails.
|
||||
#
|
||||
# Demonstrates: Complex data extraction, structured report generation, markdown
|
||||
# heredocs, asynchronous webhook notifications, and robust error handling.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
# Extract task metadata using proper API field paths
|
||||
task_id=$(echo "$input" | jq -r '.taskId')
|
||||
ulid=$(echo "$input" | jq -r '.taskComplete.taskMetadata.ulid // "unknown"')
|
||||
completion_time=$(echo "$input" | jq -r '.timestamp')
|
||||
|
||||
# Create completion report directory with error handling
|
||||
reports_dir="$HOME/.cline_reports"
|
||||
if [[ ! -d "$(dirname "$reports_dir")" ]]; then
|
||||
echo '{"cancel": false, "errorMessage": "Cannot access home directory"}'
|
||||
exit 0
|
||||
fi
|
||||
mkdir -p "$reports_dir" || exit 0
|
||||
|
||||
# Generate safe, unique report filename
|
||||
safe_task_id=$(echo "$task_id" | tr -cd '[:alnum:]_-' | head -c 50)
|
||||
report_file="$reports_dir/completion_$(date +%Y%m%d_%H%M%S)_${safe_task_id}.md"
|
||||
|
||||
# Collect comprehensive workspace information
|
||||
git_branch=$(git branch --show-current 2>/dev/null || echo "No git repository")
|
||||
git_status_count=$(git status --porcelain 2>/dev/null | wc -l || echo "0")
|
||||
project_name=$(basename "$PWD")
|
||||
|
||||
# Generate detailed completion report
|
||||
cat > "$report_file" << EOF
|
||||
# Cline Task Completion Report
|
||||
|
||||
**Task ID:** $task_id
|
||||
**ULID:** $ulid
|
||||
**Completed:** $(date -Iseconds)
|
||||
**Completion Time:** $completion_time
|
||||
|
||||
## Workspace Information
|
||||
- **Project:** $project_name
|
||||
- **Git Branch:** $git_branch
|
||||
- **Modified Files:** $git_status_count
|
||||
|
||||
## Completion Status
|
||||
✅ Task completed successfully
|
||||
|
||||
## Next Steps
|
||||
- Review changes made during this task
|
||||
- Consider committing changes if appropriate
|
||||
- Run tests to verify functionality
|
||||
EOF
|
||||
|
||||
# Send webhook notification if configured
|
||||
webhook_url="${COMPLETION_WEBHOOK_URL:-}"
|
||||
if [[ -n "$webhook_url" ]]; then
|
||||
payload=$(jq -n \
|
||||
--arg task_id "$task_id" \
|
||||
--arg ulid "$ulid" \
|
||||
--arg workspace "$project_name" \
|
||||
--arg timestamp "$completion_time" \
|
||||
'{
|
||||
event: "task_completed",
|
||||
task_id: $task_id,
|
||||
ulid: $ulid,
|
||||
workspace: $workspace,
|
||||
timestamp: $timestamp
|
||||
}')
|
||||
|
||||
# Send notification in background with timeout
|
||||
(curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
"$webhook_url" \
|
||||
--max-time 5 \
|
||||
--silent > /dev/null 2>&1) &
|
||||
fi
|
||||
|
||||
context="TASK_COMPLETED: ✅ Task $task_id finished successfully. Report saved to: $(basename "$report_file")"
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Complex data extraction and validation
|
||||
- Structured report generation
|
||||
- Asynchronous webhook notifications
|
||||
- Error handling and resource management
|
||||
|
||||
### 8. Intelligent User Input Enhancer
|
||||
|
||||
**Hook:** `UserPromptSubmit`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Intelligent User Input Enhancer Hook
|
||||
#
|
||||
# Overview: Analyzes user prompts to detect potentially harmful commands, logs user
|
||||
# activity for analytics, and intelligently injects project and git context based on
|
||||
# prompt keywords. Provides safety guards while enhancing AI responses with relevant context.
|
||||
#
|
||||
# Demonstrates: UserPromptSubmit hook usage, multi-pattern safety validation, intelligent
|
||||
# context detection from prompts, structured JSON logging, and dynamic suggestion generation.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
user_prompt=$(echo "$input" | jq -r '.userPromptSubmit.prompt')
|
||||
task_id=$(echo "$input" | jq -r '.taskId')
|
||||
user_id=$(echo "$input" | jq -r '.userId')
|
||||
|
||||
# Log user activity for analytics
|
||||
activity_log="$HOME/.cline_user_activity/$(date +%Y-%m-%d).log"
|
||||
mkdir -p "$(dirname "$activity_log")"
|
||||
|
||||
activity_entry=$(jq -n \
|
||||
--arg timestamp "$(date -Iseconds)" \
|
||||
--arg task_id "$task_id" \
|
||||
--arg user_id "$user_id" \
|
||||
--arg prompt_length "${#user_prompt}" \
|
||||
'{
|
||||
timestamp: $timestamp,
|
||||
task_id: $task_id,
|
||||
user_id: $user_id,
|
||||
prompt_length: ($prompt_length | tonumber),
|
||||
workspace: env.PWD
|
||||
}')
|
||||
|
||||
echo "$activity_entry" >> "$activity_log"
|
||||
|
||||
context_modifications=""
|
||||
cancel_request=false
|
||||
|
||||
# Safety validation
|
||||
harmful_patterns=("rm -rf" "delete.*all" "format.*drive" "sudo.*passwd")
|
||||
for pattern in "${harmful_patterns[@]}"; do
|
||||
if echo "$user_prompt" | grep -qi "$pattern"; then
|
||||
cancel_request=true
|
||||
error_message="🚨 SAFETY ALERT: Potentially harmful command detected. Please review your request."
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Intelligent context enhancement
|
||||
if [[ "$cancel_request" == false ]]; then
|
||||
# Detect project context
|
||||
if echo "$user_prompt" | grep -qi "file\|directory\|folder"; then
|
||||
if [[ -f "package.json" ]]; then
|
||||
project_name=$(jq -r '.name // "unknown"' package.json 2>/dev/null)
|
||||
context_modifications+="PROJECT_CONTEXT: Working in Node.js project '$project_name'. "
|
||||
elif [[ -f "requirements.txt" ]]; then
|
||||
context_modifications+="PROJECT_CONTEXT: Working in Python project. "
|
||||
fi
|
||||
fi
|
||||
|
||||
# Git context enhancement
|
||||
if echo "$user_prompt" | grep -qi "git\|commit\|branch" && git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
current_branch=$(git branch --show-current 2>/dev/null)
|
||||
uncommitted=$(git status --porcelain | wc -l)
|
||||
context_modifications+="GIT_CONTEXT: On branch '$current_branch' with $uncommitted uncommitted changes. "
|
||||
fi
|
||||
|
||||
# Tool suggestions
|
||||
if echo "$user_prompt" | grep -qi "search.*code\|find.*function"; then
|
||||
context_modifications+="SUGGESTION: Consider using search_files tool for code exploration. "
|
||||
fi
|
||||
fi
|
||||
|
||||
# Return response
|
||||
if [[ "$cancel_request" == true ]]; then
|
||||
jq -n --arg msg "$error_message" '{"cancel": true, "errorMessage": $msg}'
|
||||
else
|
||||
if [[ -n "$context_modifications" ]]; then
|
||||
jq -n --arg ctx "$context_modifications" '{"cancel": false, "contextModification": $ctx}'
|
||||
else
|
||||
echo '{"cancel": false}'
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- User interaction analysis and logging
|
||||
- Multi-pattern safety validation
|
||||
- Intelligent context detection
|
||||
- Dynamic suggestion generation
|
||||
|
||||
### 9. Multi-Service Integration Hub
|
||||
|
||||
**Hook:** `PostToolUse`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Multi-Service Integration Hub Hook
|
||||
#
|
||||
# Overview: Detects file modifications by type (dependencies, CI/CD, frontend, backend, tests)
|
||||
# and sends asynchronous webhook notifications to multiple external services like Slack and
|
||||
# CI/CD systems. Enables seamless integration of Cline operations into enterprise workflows.
|
||||
#
|
||||
# Demonstrates: Advanced pattern matching with associative arrays, multi-service webhook
|
||||
# orchestration, asynchronous background processing, and enterprise notification patterns.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
|
||||
success=$(echo "$input" | jq -r '.postToolUse.success')
|
||||
file_path=$(echo "$input" | jq -r '.postToolUse.parameters.path // empty')
|
||||
|
||||
# Only process successful file operations
|
||||
if [[ "$success" != "true" ]] || [[ "$tool_name" != "write_to_file" && "$tool_name" != "replace_in_file" ]]; then
|
||||
echo '{"cancel": false}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Define workflow triggers
|
||||
declare -A triggers=(
|
||||
["package\\.json|yarn\\.lock"]="dependencies"
|
||||
["\\.github/workflows/"]="ci_cd"
|
||||
["src/.*component"]="frontend"
|
||||
["api/.*\\.(ts|js)"]="backend"
|
||||
[".*\\.(test|spec)\\."]="testing"
|
||||
)
|
||||
|
||||
# Determine triggered workflows
|
||||
triggered_workflows=""
|
||||
for pattern in "${!triggers[@]}"; do
|
||||
if [[ "$file_path" =~ $pattern ]]; then
|
||||
workflow_type="${triggers[$pattern]}"
|
||||
triggered_workflows+="$workflow_type "
|
||||
fi
|
||||
done
|
||||
|
||||
context="WORKFLOW: File modified: $file_path"
|
||||
|
||||
if [[ -n "$triggered_workflows" ]]; then
|
||||
# Slack notification (async)
|
||||
slack_webhook="${SLACK_WEBHOOK_URL:-}"
|
||||
if [[ -n "$slack_webhook" ]]; then
|
||||
slack_payload=$(jq -n \
|
||||
--arg file "$file_path" \
|
||||
--arg workflows "$triggered_workflows" \
|
||||
--arg workspace "$(basename "$PWD")" \
|
||||
'{
|
||||
text: ("🔧 Cline modified `" + $file + "` in " + $workspace),
|
||||
color: "good",
|
||||
fields: [{
|
||||
title: "Triggered Workflows",
|
||||
value: $workflows,
|
||||
short: true
|
||||
}]
|
||||
}')
|
||||
|
||||
(curl -X POST -H "Content-Type: application/json" -d "$slack_payload" "$slack_webhook" --max-time 5 --silent > /dev/null 2>&1) &
|
||||
fi
|
||||
|
||||
# CI/CD webhook (async)
|
||||
ci_webhook="${CI_WEBHOOK_URL:-}"
|
||||
if [[ -n "$ci_webhook" ]]; then
|
||||
ci_payload=$(jq -n \
|
||||
--arg file "$file_path" \
|
||||
--arg workflows "$triggered_workflows" \
|
||||
'{
|
||||
event: "file_modified",
|
||||
file_path: $file,
|
||||
workflows: ($workflows | split(" "))
|
||||
}')
|
||||
|
||||
(curl -X POST -H "Content-Type: application/json" -d "$ci_payload" "$ci_webhook" --max-time 5 --silent > /dev/null 2>&1) &
|
||||
fi
|
||||
|
||||
context+=" Triggered workflows: $triggered_workflows. Notifications sent to configured services."
|
||||
fi
|
||||
|
||||
jq -n --arg ctx "$context" '{"cancel": false, "contextModification": $ctx}'
|
||||
```
|
||||
|
||||
**Key Concepts:**
|
||||
- Multi-service integration patterns
|
||||
- Asynchronous webhook orchestration
|
||||
- Complex workflow detection
|
||||
- Enterprise notification systems
|
||||
|
||||
## Usage Tips
|
||||
|
||||
### Running Multiple Hooks
|
||||
|
||||
You can use multiple hooks together by creating separate files for each hook type:
|
||||
|
||||
```bash
|
||||
# Create hooks directory
|
||||
mkdir -p .clinerules/hooks
|
||||
|
||||
# Create multiple hooks
|
||||
touch .clinerules/hooks/PreToolUse
|
||||
touch .clinerules/hooks/PostToolUse
|
||||
touch .clinerules/hooks/TaskStart
|
||||
|
||||
# Make them executable
|
||||
chmod +x .clinerules/hooks/*
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Set up environment variables for external integrations:
|
||||
|
||||
```bash
|
||||
# Add to your .bashrc or .zshrc
|
||||
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
|
||||
export JIRA_URL="https://yourcompany.atlassian.net"
|
||||
export JIRA_USER="your-email@company.com"
|
||||
export JIRA_TOKEN="your-api-token"
|
||||
export CI_WEBHOOK_URL="https://your-ci-system.com/hooks/cline"
|
||||
```
|
||||
|
||||
### Testing Your Hooks
|
||||
|
||||
Test hooks manually by simulating their input:
|
||||
|
||||
```bash
|
||||
# Test a PreToolUse hook
|
||||
echo '{
|
||||
"clineVersion": "1.0.0",
|
||||
"hookName": "PreToolUse",
|
||||
"timestamp": "2024-01-01T12:00:00Z",
|
||||
"taskId": "test",
|
||||
"workspaceRoots": ["/path/to/workspace"],
|
||||
"userId": "test-user",
|
||||
"preToolUse": {
|
||||
"toolName": "write_to_file",
|
||||
"parameters": {
|
||||
"path": "test.js",
|
||||
"content": "console.log(\"test\");"
|
||||
}
|
||||
}
|
||||
}' | .clinerules/hooks/PreToolUse
|
||||
```
|
||||
|
||||
These examples provide a solid foundation for implementing hooks in your development workflow. Customize them based on your specific needs, tools, and integrations.
|
||||
@@ -0,0 +1,283 @@
|
||||
---
|
||||
title: "Explain Changes Command"
|
||||
sidebarTitle: "/explain-changes"
|
||||
---
|
||||
|
||||
`/explain-changes` is a slash command that generates AI-powered explanations for any git diff. Unlike the [Explain Changes button](/features/explain-changes) which explains changes from a completed task, this command lets you explain changes between any two git references - commits, branches, tags, PRs, staged changes, or your working directory.
|
||||
|
||||
|
||||
<video
|
||||
src="https://storage.googleapis.com/cline_public_images/slash-code-explain.mp4"
|
||||
autoPlay
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
/>
|
||||
|
||||
## Requirements
|
||||
|
||||
<Note>
|
||||
The `/explain-changes` command requires a **git repository**. Make sure you're working in a directory that has been initialized with git.
|
||||
</Note>
|
||||
|
||||
For PR explanations, you'll need the [GitHub CLI (gh)](https://cli.github.com/) installed and authenticated. For GitLab merge request explanations, you'll need the [GitLab CLI (glab)](https://gitlab.com/gitlab-org/cli) installed and authenticated.
|
||||
|
||||
Unlike the Explain Changes button, this command does **not** require checkpoints to be enabled since it uses git references directly.
|
||||
|
||||
|
||||
## Using the Command
|
||||
|
||||
Type `/explain-changes` in the chat input. Cline will:
|
||||
|
||||
1. Analyze your git history to understand what changes exist
|
||||
2. Gather context by reading relevant files
|
||||
3. Determine appropriate git references to compare
|
||||
4. Generate a diff view with streaming inline explanations
|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
When you use `/explain-changes`, Cline:
|
||||
|
||||
1. **Gathers context**: Runs git commands to understand your repository state
|
||||
2. **Identifies changes**: Determines which files changed between references
|
||||
3. **Reads relevant files**: Builds context for better explanations
|
||||
4. **Calls generate_explanation**: Creates the diff view and streams explanations
|
||||
5. **Displays results**: Opens a multi-file diff with inline comments
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Explain the Last Commit
|
||||
|
||||
The most common use case - understand what changed in the most recent commit:
|
||||
|
||||
```
|
||||
/explain-changes
|
||||
```
|
||||
|
||||
Cline will examine HEAD and compare it to HEAD~1, explaining all the changes in that commit.
|
||||
|
||||
**When to use:**
|
||||
- After pulling changes from a teammate
|
||||
- Reviewing your own work before pushing
|
||||
- Understanding what a merge commit brought in
|
||||
|
||||
### Explain Uncommitted Changes
|
||||
|
||||
Understand your work-in-progress changes before committing:
|
||||
|
||||
```
|
||||
/explain-changes for my uncommitted work
|
||||
```
|
||||
|
||||
Cline compares HEAD to your working directory, explaining all modified files.
|
||||
|
||||
**When to use:**
|
||||
- Before staging changes to ensure they're complete
|
||||
- After a long coding session to remember what you changed
|
||||
- To verify changes before creating a commit
|
||||
|
||||
### Explain Staged Changes
|
||||
|
||||
Review exactly what you're about to commit:
|
||||
|
||||
```
|
||||
/explain-changes for my staged changes
|
||||
```
|
||||
|
||||
Cline examines only the changes you've staged with `git add`.
|
||||
|
||||
**When to use:**
|
||||
- Final review before committing
|
||||
- When you've staged a subset of changes and want to verify
|
||||
- To ensure you haven't accidentally staged unintended files
|
||||
|
||||
### Explain a Specific Commit
|
||||
|
||||
Understand any commit in your history:
|
||||
|
||||
```
|
||||
/explain-changes for commit abc123
|
||||
```
|
||||
|
||||
Or by commit message:
|
||||
|
||||
```
|
||||
/explain-changes for the commit that added authentication
|
||||
```
|
||||
|
||||
Cline will find the commit and explain what it changed.
|
||||
|
||||
**When to use:**
|
||||
- Investigating when a bug was introduced
|
||||
- Understanding historical decisions
|
||||
- Learning how a feature was implemented
|
||||
|
||||
### Explain a Range of Commits
|
||||
|
||||
Understand multiple commits at once:
|
||||
|
||||
```
|
||||
/explain-changes for the last 3 commits
|
||||
```
|
||||
|
||||
Or a specific range:
|
||||
|
||||
```
|
||||
/explain-changes from v1.0.0 to v1.1.0
|
||||
```
|
||||
|
||||
Cline compares the endpoints and explains all changes between them.
|
||||
|
||||
**When to use:**
|
||||
- Understanding what changed in a release
|
||||
- Reviewing a series of related commits
|
||||
- Catching up after being away from the project
|
||||
|
||||
### Explain a Pull Request
|
||||
|
||||
Get AI explanations for any PR:
|
||||
|
||||
```
|
||||
/explain-changes for PR #42
|
||||
```
|
||||
|
||||
Cline uses the GitHub CLI to fetch PR details and explain the changes.
|
||||
|
||||
**When to use:**
|
||||
- Reviewing someone else's PR
|
||||
- Understanding a PR before approving
|
||||
- Learning from PRs in open source projects
|
||||
- Preparing to give PR feedback
|
||||
|
||||
### Explain Branch Differences
|
||||
|
||||
Compare any two branches:
|
||||
|
||||
```
|
||||
/explain-changes between main and feature-branch
|
||||
```
|
||||
|
||||
Or see what's changed on a feature branch:
|
||||
|
||||
```
|
||||
/explain-changes for everything on my-feature that's not in main
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Before merging a feature branch
|
||||
- Understanding divergence between branches
|
||||
- Planning a merge or rebase strategy
|
||||
- Reviewing what a colleague has been working on
|
||||
|
||||
### Explain Changes to Specific Files
|
||||
|
||||
Focus on particular files or directories:
|
||||
|
||||
```
|
||||
/explain-changes for src/auth in the last 5 commits
|
||||
```
|
||||
|
||||
Cline filters the diff to show only relevant changes.
|
||||
|
||||
**When to use:**
|
||||
- Understanding changes to a specific module
|
||||
- Tracking modifications to critical files
|
||||
- Learning how a particular feature evolved
|
||||
|
||||
### Explain Changes Since a Tag
|
||||
|
||||
Understand what's changed since a release:
|
||||
|
||||
```
|
||||
/explain-changes since v2.0.0
|
||||
```
|
||||
|
||||
Cline compares the tag to HEAD and explains all subsequent changes.
|
||||
|
||||
**When to use:**
|
||||
- Preparing release notes
|
||||
- Understanding what's new since a deployment
|
||||
- Identifying changes for a changelog
|
||||
|
||||
### Explain a Merge Commit
|
||||
|
||||
Understand what a merge brought in:
|
||||
|
||||
```
|
||||
/explain-changes for the merge from feature-x
|
||||
```
|
||||
|
||||
Cline explains all the changes that were merged.
|
||||
|
||||
**When to use:**
|
||||
- After merging a large feature branch
|
||||
- Understanding what a merge conflict resolution changed
|
||||
- Reviewing what others merged into main
|
||||
|
||||
### Explain Stashed Changes
|
||||
|
||||
Review what's in your stash:
|
||||
|
||||
```
|
||||
/explain-changes for my stashed changes
|
||||
```
|
||||
|
||||
Cline examines stash@{0} and explains its contents.
|
||||
|
||||
**When to use:**
|
||||
- Before applying a stash
|
||||
- Deciding whether to keep or drop a stash
|
||||
- Remembering what you stashed days ago
|
||||
|
||||
## Interactive Comments
|
||||
|
||||
Just like the [Explain Changes](/features/explain-changes) button, the generated comments are fully interactive:
|
||||
|
||||
### Reply to Comments
|
||||
|
||||
Ask follow-up questions directly in any comment thread:
|
||||
|
||||
- "Why was this function refactored?"
|
||||
- "What's the purpose of this new parameter?"
|
||||
- "Could this cause any breaking changes?"
|
||||
- "Is this change backwards compatible?"
|
||||
|
||||
The AI responds with context-aware explanations, understanding both the code and the broader changes.
|
||||
|
||||
### Move to Main Chat
|
||||
|
||||
Click the title area of any comment thread to move that conversation into Cline's main chat. This is useful when:
|
||||
|
||||
- You want Cline to make additional changes
|
||||
- The discussion reveals something that needs more investigation
|
||||
- You want to continue working with full Cline capabilities
|
||||
- A review comment sparks an idea for improvements
|
||||
|
||||
### The generate_explanation Tool
|
||||
|
||||
Under the hood, `/explain-changes` uses the `generate_explanation` tool with these parameters:
|
||||
|
||||
| Parameter | Description | Example |
|
||||
|-----------|-------------|---------|
|
||||
| `title` | Descriptive title for the diff view | "Changes in commit abc123" |
|
||||
| `from_ref` | Git reference for the "before" state | `HEAD~1`, `main`, `origin/main` |
|
||||
| `to_ref` | Git reference for the "after" state (optional) | `HEAD`, `develop` |
|
||||
|
||||
If `to_ref` is omitted, the tool compares against the working directory.
|
||||
|
||||
## Tips for Better Explanations
|
||||
|
||||
1. **Be specific**: Instead of just `/explain-changes`, tell Cline what you want explained. "Explain the authentication changes in PR #42" gives better context than just "explain PR #42".
|
||||
|
||||
2. **Ask about intent**: The AI can explain not just what changed but why. Ask follow-up questions like "What problem was this solving?"
|
||||
|
||||
3. **Chain with other commands**: Use `/explain-changes` after investigating an issue to understand potential fixes, then continue with Cline to implement improvements.
|
||||
|
||||
4. **Use for learning**: When onboarding to a new codebase, use `/explain-changes` on significant PRs or commits to understand how features were built.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Explain Changes](/features/explain-changes) - The button-based version for task completions
|
||||
- [Checkpoints](/features/checkpoints) - Enables the Explain Changes button
|
||||
- [@git mentions](/features/at-mentions/git-mentions) - Reference git diffs in your prompts
|
||||
@@ -1,445 +0,0 @@
|
||||
---
|
||||
title: "Workflows"
|
||||
sidebarTitle: "Workflows"
|
||||
---
|
||||
|
||||
Workflows allow you to define a series of steps to guide Cline through a repetitive set of tasks, such as deploying a service or submitting a PR.
|
||||
|
||||
To invoke a workflow, type `/[workflow-name.md]` in the chat.
|
||||
|
||||
## How to Create and Use Workflows
|
||||
|
||||
Workflows live alongside [Cline Rules](/features/cline-rules). Creating one is straightforward:
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/workflows.png" alt="Workflows tab in Cline" />
|
||||
</Frame>
|
||||
|
||||
1. Create a markdown file with clear instructions for the steps Cline should take
|
||||
2. Save it with a `.md` extension in your workflows directory
|
||||
3. To trigger a workflow, just type `/` followed by the workflow filename
|
||||
4. Provide any required parameters when prompted
|
||||
|
||||
The real power comes from how you structure your workflow files. You can:
|
||||
|
||||
- Leverage Cline's [built-in tools](/exploring-clines-tools/cline-tools-guide) like `ask_followup_question`, `read_file`, `search_files`, and `new_task`
|
||||
- Use command-line tools you already have installed like `gh` or `docker`
|
||||
- Reference external [MCP tool calls](/mcp/mcp-overview) like Slack or Whatsapp
|
||||
- Chain multiple actions together in a specific sequence
|
||||
|
||||
## Real-world Example
|
||||
|
||||
I created a PR Review workflow that's already saving me tons of time.
|
||||
|
||||
````md pr-review.md [expandable]
|
||||
You have access to the `gh` terminal command. I already authenticated it for you. Please review it to use the PR that I asked you to review. You're already in the `cline` repo.
|
||||
|
||||
<detailed_sequence_of_steps>
|
||||
|
||||
# GitHub PR Review Process - Detailed Sequence of Steps
|
||||
|
||||
## 1. Gather PR Information
|
||||
|
||||
1. Get the PR title, description, and comments:
|
||||
|
||||
```bash
|
||||
gh pr view <PR-number> --json title,body,comments
|
||||
```
|
||||
|
||||
2. Get the full diff of the PR:
|
||||
```bash
|
||||
gh pr diff <PR-number>
|
||||
```
|
||||
|
||||
## 2. Understand the Context
|
||||
|
||||
1. Identify which files were modified in the PR:
|
||||
|
||||
```bash
|
||||
gh pr view <PR-number> --json files
|
||||
```
|
||||
|
||||
2. Examine the original files in the main branch to understand the context:
|
||||
|
||||
```xml
|
||||
<read_file>
|
||||
<path>path/to/file</path>
|
||||
</read_file>
|
||||
```
|
||||
|
||||
3. For specific sections of a file, you can use search_files:
|
||||
```xml
|
||||
<search_files>
|
||||
<path>path/to/directory</path>
|
||||
<regex>search term</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
## 3. Analyze the Changes
|
||||
|
||||
1. For each modified file, understand:
|
||||
|
||||
- What was changed
|
||||
- Why it was changed (based on PR description)
|
||||
- How it affects the codebase
|
||||
- Potential side effects
|
||||
|
||||
2. Look for:
|
||||
- Code quality issues
|
||||
- Potential bugs
|
||||
- Performance implications
|
||||
- Security concerns
|
||||
- Test coverage
|
||||
|
||||
## 4. Ask for User Confirmation
|
||||
|
||||
1. Before making a decision, ask the user if you should approve the PR, providing your assessment and justification:
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Based on my review of PR #<PR-number>, I recommend [approving/requesting changes]. Here's my justification:
|
||||
|
||||
[Detailed justification with key points about the PR quality, implementation, and any concerns]
|
||||
|
||||
Would you like me to proceed with this recommendation?</question>
|
||||
<options>["Yes, approve the PR", "Yes, request changes", "No, I'd like to discuss further"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## 5. Ask if User Wants a Comment Drafted
|
||||
|
||||
1. After the user decides on approval/rejection, ask if they would like a comment drafted:
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Would you like me to draft a comment for this PR that you can copy and paste?</question>
|
||||
<options>["Yes, please draft a comment", "No, I'll handle the comment myself"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
2. If the user wants a comment drafted, provide a well-structured comment they can copy:
|
||||
|
||||
```
|
||||
Thank you for this PR! Here's my assessment:
|
||||
|
||||
[Detailed assessment with key points about the PR quality, implementation, and any suggestions]
|
||||
|
||||
[Include specific feedback on code quality, functionality, and testing]
|
||||
```
|
||||
|
||||
## 6. Make a Decision
|
||||
|
||||
1. Approve the PR if it meets quality standards:
|
||||
|
||||
```bash
|
||||
# For single-line comments:
|
||||
gh pr review <PR-number> --approve --body "Your approval message"
|
||||
|
||||
# For multi-line comments with proper whitespace formatting:
|
||||
cat << EOF | gh pr review <PR-number> --approve --body-file -
|
||||
Thanks @username for this PR! The implementation looks good.
|
||||
|
||||
I particularly like how you've handled X and Y.
|
||||
|
||||
Great work!
|
||||
EOF
|
||||
```
|
||||
|
||||
2. Request changes if improvements are needed:
|
||||
|
||||
```bash
|
||||
# For single-line comments:
|
||||
gh pr review <PR-number> --request-changes --body "Your feedback message"
|
||||
|
||||
# For multi-line comments with proper whitespace formatting:
|
||||
cat << EOF | gh pr review <PR-number> --request-changes --body-file -
|
||||
Thanks @username for this PR!
|
||||
|
||||
The implementation looks promising, but there are a few things to address:
|
||||
|
||||
1. Issue one
|
||||
2. Issue two
|
||||
|
||||
Please make these changes and we can merge this.
|
||||
EOF
|
||||
```
|
||||
|
||||
Note: The `cat << EOF | ... --body-file -` approach preserves all whitespace and formatting without requiring temporary files. The `-` parameter tells the command to read from standard input.
|
||||
</detailed_sequence_of_steps>
|
||||
|
||||
<example_review_process>
|
||||
|
||||
# Example PR Review Process
|
||||
|
||||
Let's walk through a real example of reviewing PR #3627 which fixes the thinking mode calculation for Claude 3.7 models.
|
||||
|
||||
## Step 1: Gather PR Information
|
||||
|
||||
```bash
|
||||
# Get PR details
|
||||
gh pr view 3627 --json title,body,comments
|
||||
|
||||
# Get the full diff
|
||||
gh pr diff 3627
|
||||
```
|
||||
|
||||
## Step 2: Understand the Context
|
||||
|
||||
```xml
|
||||
# Examine the original files to understand what's being changed
|
||||
<read_file>
|
||||
<path>src/shared/api.ts</path>
|
||||
</read_file>
|
||||
|
||||
# Look at the ThinkingBudgetSlider component implementation
|
||||
<read_file>
|
||||
<path>webview-ui/src/components/settings/ThinkingBudgetSlider.tsx</path>
|
||||
</read_file>
|
||||
|
||||
# Check how thinking mode is currently handled in API providers
|
||||
<search_files>
|
||||
<path>src/api/providers</path>
|
||||
<regex>reasoningOn</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
## Step 3: Analyze the Changes
|
||||
|
||||
From the PR information and code examination, we can see that:
|
||||
|
||||
1. The PR fixes an issue with the thinking mode budget calculation for Claude 3.7 models
|
||||
2. Currently, the thinking budget is incorrectly calculated as 80% of maxTokens (8192), resulting in 6553 tokens
|
||||
3. Claude 3.7 actually supports a much larger thinking budget (up to 64000 tokens)
|
||||
4. The PR adds a `thinkingConfig` property with `maxBudget: 64000` to model definitions
|
||||
5. It updates API handlers to use this value when reasoning mode is enabled
|
||||
6. It enhances the slider component to calculate max values based on model-specific percentages
|
||||
7. Comprehensive tests were added to verify the calculations
|
||||
|
||||
## Step 4: Ask for User Confirmation
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Based on my review of PR #3627, I recommend approving this PR. Here's my justification:
|
||||
|
||||
1. The PR correctly fixes the thinking mode budget calculation for Claude 3.7 models
|
||||
2. It properly implements the thinkingConfig.maxBudget property (64000) for Claude 3.7 models
|
||||
3. It applies the appropriate percentage (50%) for the slider calculation
|
||||
4. The changes are well-tested with comprehensive unit tests
|
||||
5. The implementation is clean and follows the project's coding standards
|
||||
6. The PR includes helpful screenshots demonstrating the fix
|
||||
|
||||
Would you like me to proceed with approving this PR?</question>
|
||||
<options>["Yes, approve the PR", "No, I'd like to discuss further", "Let me review it myself first"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## Step 5: Ask if User Wants a Comment Drafted
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Would you like me to draft a comment for this PR that you can copy and paste?</question>
|
||||
<options>["Yes, please draft a comment", "No, I'll handle the comment myself"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## Step 6: Make a Decision
|
||||
|
||||
```bash
|
||||
# Option 1: Simple one-line comment
|
||||
gh pr review 3627 --approve --body "This PR looks good! It correctly fixes the thinking mode budget calculation for Claude 3.7 models."
|
||||
|
||||
# Option 2: Multi-line comment with proper whitespace formatting
|
||||
cat << EOF | gh pr review 3627 --approve --body-file -
|
||||
This PR looks good! It correctly fixes the thinking mode budget calculation for Claude 3.7 models.
|
||||
|
||||
I particularly like:
|
||||
1. The proper implementation of thinkingConfig.maxBudget property (64000)
|
||||
2. The appropriate percentage (50%) for the slider calculation
|
||||
3. The comprehensive unit tests
|
||||
4. The clean implementation that follows project coding standards
|
||||
|
||||
Great work!
|
||||
EOF
|
||||
```
|
||||
|
||||
</example_review_process>
|
||||
|
||||
<common_gh_commands>
|
||||
|
||||
# Common GitHub CLI Commands for PR Review
|
||||
|
||||
## Basic PR Commands
|
||||
|
||||
```bash
|
||||
# List open PRs
|
||||
gh pr list
|
||||
|
||||
# View a specific PR
|
||||
gh pr view <PR-number>
|
||||
|
||||
# View PR with specific fields
|
||||
gh pr view <PR-number> --json title,body,comments,files,commits
|
||||
|
||||
# Check PR status
|
||||
gh pr status
|
||||
```
|
||||
|
||||
## Diff and File Commands
|
||||
|
||||
```bash
|
||||
# Get the full diff of a PR
|
||||
gh pr diff <PR-number>
|
||||
|
||||
# List files changed in a PR
|
||||
gh pr view <PR-number> --json files
|
||||
|
||||
# Check out a PR locally
|
||||
gh pr checkout <PR-number>
|
||||
```
|
||||
|
||||
## Review Commands
|
||||
|
||||
```bash
|
||||
# Approve a PR (single-line comment)
|
||||
gh pr review <PR-number> --approve --body "Your approval message"
|
||||
|
||||
# Approve a PR (multi-line comment with proper whitespace)
|
||||
cat << EOF | gh pr review <PR-number> --approve --body-file -
|
||||
Your multi-line
|
||||
approval message with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
|
||||
# Request changes on a PR (single-line comment)
|
||||
gh pr review <PR-number> --request-changes --body "Your feedback message"
|
||||
|
||||
# Request changes on a PR (multi-line comment with proper whitespace)
|
||||
cat << EOF | gh pr review <PR-number> --request-changes --body-file -
|
||||
Your multi-line
|
||||
change request with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
|
||||
# Add a comment review (without approval/rejection)
|
||||
gh pr review <PR-number> --comment --body "Your comment message"
|
||||
|
||||
# Add a comment review with proper whitespace
|
||||
cat << EOF | gh pr review <PR-number> --comment --body-file -
|
||||
Your multi-line
|
||||
comment with
|
||||
|
||||
proper whitespace formatting
|
||||
EOF
|
||||
```
|
||||
|
||||
## Additional Commands
|
||||
|
||||
```bash
|
||||
# View PR checks status
|
||||
gh pr checks <PR-number>
|
||||
|
||||
# View PR commits
|
||||
gh pr view <PR-number> --json commits
|
||||
|
||||
# Merge a PR (if you have permission)
|
||||
gh pr merge <PR-number> --merge
|
||||
```
|
||||
|
||||
</common_gh_commands>
|
||||
|
||||
<general_guidelines_for_commenting>
|
||||
When reviewing a PR, please talk normally and like a friendly reviwer. You should keep it short, and start out by thanking the author of the pr and @ mentioning them.
|
||||
|
||||
Whether or not you approve the PR, you should then give a quick summary of the changes without being too verbose or definitive, staying humble like that this is your understanding of the changes. Kind of how I'm talking to you right now.
|
||||
|
||||
If you have any suggestions, or things that need to be changed, request changes instead of approving the PR.
|
||||
|
||||
Leaving inline comments in code is good, but only do so if you have something specific to say about the code. And make sure you leave those comments first, and then request changes in the PR with a short comment explaining the overall theme of what you're asking them to change.
|
||||
</general_guidelines_for_commenting>
|
||||
|
||||
<example_comments_that_i_have_written_before>
|
||||
<brief_approve_comment>
|
||||
Looks good, though we should make this generic for all providers & models at some point
|
||||
</brief_approve_comment>
|
||||
<brief_approve_comment>
|
||||
Will this work for models that may not match across OR/Gemini? Like the thinking models?
|
||||
</brief_approve_comment>
|
||||
<approve_comment>
|
||||
This looks great! I like how you've handled the global endpoint support - adding it to the ModelInfo interface makes total sense since it's just another capability flag, similar to how we handle other model features.
|
||||
|
||||
The filtered model list approach is clean and will be easier to maintain than hardcoding which models work with global endpoints. And bumping the genai library was obviously needed for this to work.
|
||||
|
||||
Thanks for adding the docs about the limitations too - good for users to know they can't use context caches with global endpoints but might get fewer 429 errors.
|
||||
</approve_comment>
|
||||
<requesst_changes_comment>
|
||||
This is awesome. Thanks @scottsus.
|
||||
|
||||
My main concern though - does this work for all the possible VS Code themes? We struggled with this initially which is why it's not super styled currently. Please test and share screenshots with the different themes to make sure before we can merge
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Hey, the PR looks good overall but I'm concerned about removing those timeouts. Those were probably there for a reason - VSCode's UI can be finicky with timing.
|
||||
|
||||
Could you add back the timeouts after focusing the sidebar? Something like:
|
||||
|
||||
```typescript
|
||||
await vscode.commands.executeCommand("claude-dev.SidebarProvider.focus")
|
||||
await setTimeoutPromise(100) // Give UI time to update
|
||||
visibleWebview = WebviewProvider.getSidebarInstance()
|
||||
```
|
||||
|
||||
</request_changes_comment>
|
||||
<request_changes_comment>
|
||||
Heya @alejandropta thanks for working on this!
|
||||
|
||||
A few notes:
|
||||
1 - Adding additional info to the environment variables is fairly problematic because env variables get appended to **every single message**. I don't think this is justifiable for a somewhat niche use case.
|
||||
2 - Adding this option to settings to include that could be an option, but we want our options to be simple and straightforward for new users
|
||||
3 - We're working on revisualizing the way our settings page is displayed/organized, and this could potentially be reconciled once that is in and our settings page is more clearly delineated.
|
||||
|
||||
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>
|
||||
````
|
||||
|
||||
When I get a new PR to review, I used to manually gather context: checking the PR description, examining the diff, looking at surrounding files, and finally forming an opinion. Now I just:
|
||||
|
||||
1. Type `/pr-review.md` in chat
|
||||
2. Paste in the PR number
|
||||
3. Let Cline handle everything else
|
||||
|
||||
My workflow uses the `gh` command-line tool and Cline's built in `ask_followup_question` to:
|
||||
|
||||
- Pull the PR description and comments
|
||||
- Examine the diff
|
||||
- Check surrounding files for context
|
||||
- Analyze potential issues
|
||||
- Asks me if it's cool approve it if everything looks good, with justification for why it should be approved
|
||||
- If I say "yes," Cline automatically approves the PR with the `gh` command
|
||||
|
||||
This has taken my PR review process from a manual, multi-step operation to a single command that gives me everything I need to make an informed decision.
|
||||
|
||||
> This is just one example of a workflow file. You can find more in our [prompts repository](https://github.com/cline/prompts) for inspiration.
|
||||
|
||||
## Building Your Own Workflows
|
||||
|
||||
The beauty of workflows is they're completely customizable to your needs. You might create workflows for all kinds of repetitive tasks:
|
||||
|
||||
- For releases, you could have a workflow that grabs all merged PRs, builds a changelog, and handles version bumps.
|
||||
- Setting up new projects is perfect for workflows. Just run one command to create your folder structure, install dependencies, and set up configs.
|
||||
- Need to create a report? Create a workflow that grabs stats from different sources and formats them exactly how you like. You can even visualize them with a charting library and then make a presentation out of it with a library like [slidev](https://sli.dev/).
|
||||
- You can even use workflows to draft messages to your team using an MCP server like Slack or Whatsapp after you submit a PR.
|
||||
|
||||
With Workflows, your imagination is the limit. The true potential comes from spotting those annoying repetitive tasks you do all the time.
|
||||
|
||||
If you can describe something as "first I do X, then Y, then Z" - that's a perfect workflow candidate.
|
||||
|
||||
Start with something small that bugs you, turn it into a workflow, and keep refining it. You'll be shocked how much of your day can be automated this way.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: "Workflows Best Practices"
|
||||
sidebarTitle: "Best Practices"
|
||||
description: "Tips and strategies for creating effective and reliable Cline workflows."
|
||||
---
|
||||
|
||||
Creating effective workflows requires a balance of clear instructions, modular design, and intelligent tool usage. Follow these best practices to get the most out of Cline's automation capabilities.
|
||||
|
||||
## Use Cline to Build Workflows
|
||||
|
||||
We highly recommend using Cline to help you build your workflows. Since Cline understands your project's context and structure, it can be an invaluable partner in designing automation that fits your specific needs.
|
||||
|
||||
### Building your own workflows
|
||||
|
||||
Creating a workflow is simpler than you might think. There's actually a workflow for building workflows!
|
||||
|
||||
First, **save the [create-new-workflow.md](https://github.com/cline/prompts/blob/main/workflows/create-new-workflow.md) file to your workspace** (e.g., in `.clinerules/workflows/`).
|
||||
|
||||
Then, type `/create-new-workflow.md` and Cline guides you through it:
|
||||
|
||||
1. It asks for the purpose and a concise name.
|
||||
2. You describe the objective and expected outputs.
|
||||
3. You list the major steps (Cline can help determine details).
|
||||
4. It generates the properly structured workflow file.
|
||||
|
||||
<Tip>
|
||||
**Automate Your History:** The best workflows come from tasks you've already done. After completing something you'll need to repeat, tell Cline: "Create a workflow for the process I just completed." It analyzes the conversation, identifies the steps, and generates the workflow file. Your accumulated context becomes reusable automation.
|
||||
</Tip>
|
||||
|
||||
Workflows live in `.clinerules/workflows/` for project-specific ones or `~/Documents/Cline/Workflows/` for global ones you use across projects. Project workflows take precedence when names match.
|
||||
|
||||
## Workflow Design
|
||||
|
||||
<Tip>
|
||||
**Start Simple:** Begin with small, single-task workflows. As you get comfortable, you can combine them or create more complex sequences.
|
||||
</Tip>
|
||||
|
||||
### Be Modular
|
||||
Instead of creating one massive workflow file, break complex tasks into smaller, reusable workflows. This makes them easier to maintain and debug.
|
||||
|
||||
### Use Clear Comments
|
||||
Just like with code, commenting your workflow steps is crucial. Explain *why* a step is happening, not just *what* is happening. This helps both you (the future maintainer) and Cline understand the intent.
|
||||
|
||||
### Version Control
|
||||
Treat your workflows as part of your codebase. Store them in your Git repository (in `.clinerules/workflows/`) so they are versioned, reviewed, and shared with your team.
|
||||
|
||||
## Prompt Engineering for Cline
|
||||
|
||||
### Be Specific with Tool Use
|
||||
Don't just say "find the file." Be explicit about which tool Cline should use.
|
||||
|
||||
* **Bad:** "Find the user controller."
|
||||
* **Good:** "Use `search_files` to look for `UserController` in the `src/controllers` directory."
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
### Available Tools
|
||||
|
||||
Cline has a powerful set of tools you can use within your workflows. Here are the most common ones:
|
||||
|
||||
#### execute_command
|
||||
Executes a CLI command on your system. Use this for running tests, builds, git commands, or any other terminal operation.
|
||||
|
||||
```xml
|
||||
<execute_command>
|
||||
<command>npm run test</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
</execute_command>
|
||||
```
|
||||
|
||||
#### read_file
|
||||
Reads the contents of a file. Essential for analyzing code or configuration.
|
||||
|
||||
```xml
|
||||
<read_file>
|
||||
<path>src/config.json</path>
|
||||
</read_file>
|
||||
```
|
||||
|
||||
#### write_to_file
|
||||
Creates or overwrites a file. Use this to generate boilerplate, config files, or documentation.
|
||||
|
||||
```xml
|
||||
<write_to_file>
|
||||
<path>src/components/Button.tsx</path>
|
||||
<content>
|
||||
// File content goes here...
|
||||
</content>
|
||||
</write_to_file>
|
||||
```
|
||||
|
||||
#### search_files
|
||||
Searches for a regex pattern across files in a directory. Great for finding TODOs, usage examples, or specific code patterns.
|
||||
|
||||
```xml
|
||||
<search_files>
|
||||
<path>src</path>
|
||||
<regex>TODO</regex>
|
||||
<file_pattern>*.ts</file_pattern>
|
||||
</search_files>
|
||||
```
|
||||
|
||||
#### ask_followup_question
|
||||
Asks the user for input or confirmation. This makes your workflow interactive and allows for human-in-the-loop decision making.
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Do you want to deploy to production?</question>
|
||||
<options>["Yes", "No"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
#### browser_action
|
||||
Controls a built-in browser to interact with websites or local servers. Useful for testing web UIs or scraping data.
|
||||
|
||||
```xml
|
||||
<browser_action>
|
||||
<action>launch</action>
|
||||
<url>http://localhost:3000</url>
|
||||
</browser_action>
|
||||
```
|
||||
|
||||
### Leverage MCP Tools
|
||||
You can use Model Context Protocol (MCP) tools within your workflows to interact with external services like GitHub, Slack, or databases. This allows you to create powerful end-to-end automations.
|
||||
|
||||
### Manage Context Window
|
||||
Be mindful of Cline's context window. If a workflow is too long or processes too much data, it might exceed the token limit.
|
||||
* **Break it down:** Split long workflows into smaller parts.
|
||||
* **Be concise:** Keep instructions clear and to the point.
|
||||
|
||||
## Learn More
|
||||
|
||||
<Card title="Cline Learn" icon="lightbulb" href="https://cline.bot/learn">
|
||||
Dive deeper into general prompt engineering strategies to write even better instructions for Cline.
|
||||
</Card>
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: "Workflows Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "Learn what Cline workflows are, why they are useful, and how to structure them."
|
||||
---
|
||||
|
||||
Workflows in Cline are Markdown files that define a series of steps to guide Cline through repetitive or complex tasks. They are a powerful way to automate your development processes directly within your editor.
|
||||
|
||||
To invoke a workflow, you simply type `/` followed by the workflow's filename in the chat (e.g., `/deploy.md`).
|
||||
|
||||
## Why Use Cline Workflows?
|
||||
|
||||
* **Automation:** Automate repetitive tasks like setting up a new project, deploying a service, or running a specific test suite.
|
||||
* **Consistency:** Ensure that tasks are performed the same way every time, reducing errors.
|
||||
* **Reduced Cognitive Load:** Don't waste mental energy remembering complex sequences of commands or steps.
|
||||
* **Contextual:** Workflows run within your project's context, so Cline has access to your files and can use its tools to interact with them.
|
||||
|
||||
## How They Work
|
||||
|
||||
A workflow file is a standard Markdown file with a `.md` extension. Cline reads this file and interprets the instructions step-by-step. The real power comes from Cline's ability to use its built-in tools and other capabilities within these instructions:
|
||||
|
||||
* **Cline Tools:** Use tools like `read_file`, `write_to_file`, `execute_command`, and `ask_followup_question`.
|
||||
* **Command-Line Tools:** Instruct Cline to use any CLI tool installed on your machine (e.g., `git`, `gh`, `npm`, `docker`).
|
||||
* **MCP Tools:** Reference tools from connected Model Context Protocol (MCP) servers.
|
||||
|
||||
## Workflows vs. Rules
|
||||
|
||||
It's important to understand the difference between Cline Workflows and Cline Rules, as they serve different purposes:
|
||||
|
||||
| Feature | Purpose | When to Use |
|
||||
| :--- | :--- | :--- |
|
||||
| **Cline Rules** | Define *how* Cline should behave generally. They are always active (or contextually triggered) and set the "ground rules" for your project. | Enforcing coding standards, tech stack preferences, or project-specific constraints (e.g., "Always use TypeScript", "Never edit the `db` folder"). |
|
||||
| **Cline Workflows** | Define *what* specific task Cline should perform. They are sequences of steps invoked on-demand to automate a process. | Automating repetitive tasks like creating a component, running a release process, or generating a daily report. |
|
||||
|
||||
Think of **Rules** as the *environment* Cline works in, and **Workflows** as the *scripts* you give Cline to execute.
|
||||
|
||||
### Example: Automating a Release
|
||||
|
||||
Imagine you need to prepare a new release for your library.
|
||||
|
||||
**Without a workflow**, you might have to manually:
|
||||
1. Open `package.json` and bump the version number.
|
||||
2. Run your test suite to make sure everything is green.
|
||||
3. Update `CHANGELOG.md` with the latest commits.
|
||||
4. Run `git commit -am "v1.0.1"`.
|
||||
5. Run `git tag v1.0.1`.
|
||||
6. Run `git push origin main --tags`.
|
||||
|
||||
This is tedious and easy to mess up. You might forget to run the tests or format the changelog correctly.
|
||||
|
||||
**With a Cline workflow**, you define these steps once in a `release.md` file. Then, you just type:
|
||||
|
||||
```bash
|
||||
/release.md
|
||||
```
|
||||
|
||||
Cline will then meticulously follow your instructions: updating files, running tests, and executing git commands—pausing only if it encounters an error or needs your input.
|
||||
|
||||
## Where are Workflows Stored?
|
||||
|
||||
You can store workflows in two locations, depending on whether they are specific to a project or meant to be global.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Project-Specific Workflows">
|
||||
Store workflows that are specific to a single project in a `.clinerules/workflows/` directory in your project's root.
|
||||
|
||||
1. Create a `.clinerules` folder in your project's root directory (if it doesn't already exist).
|
||||
<Note>
|
||||
The `.clinerules` directory may be hidden by default on some systems. You might need to enable **Show Hidden Files** to see it.
|
||||
</Note>
|
||||
2. Inside `.clinerules`, create a `workflows` folder.
|
||||
3. Create your Markdown workflow files (e.g., `deploy.md`) in this folder.
|
||||
|
||||
These workflows will only be available when you have this specific project open.
|
||||
</Tab>
|
||||
<Tab title="Global Workflows">
|
||||
Store workflows that you want to use across all your projects in a global directory.
|
||||
|
||||
* **macOS/Linux:** `~/Documents/Cline/Workflows/`
|
||||
* **Windows:** `C:\Users\USERNAME\Documents\Cline\Workflows\`
|
||||
|
||||
Create your Markdown workflow files directly in this directory. They will be available in any project you open with Cline.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Manage Workflows
|
||||
|
||||
You can easily manage your workflows directly within the extension. This feature provides a unified interface to handle all your automation needs without leaving your editor or hunting through file directories. It consolidates both project-specific rules and global workflows into one view, giving you full control over your automation environment.
|
||||
|
||||
1. Click the **Manage Cline Rules and Workflows** button (<Icon icon="scale-balanced" />) at the bottom of the extension.
|
||||
2. This opens an interface where you can:
|
||||
* **View all available workflows:** See a comprehensive list of both project-specific and global workflows.
|
||||
* **Control automation:** Toggle individual workflows on and off as needed for your current task.
|
||||
* **Create and Edit:** Add new workflows or modify existing ones directly within the interface.
|
||||
* **Clean up:** Delete workflows you no longer need.
|
||||
|
||||
<Frame caption="Manage Workflows">
|
||||
<img src="https://storage.googleapis.com/cline_public_images/workflow-menu.gif" alt="Manage Cline Rules and Workflows Interface" />
|
||||
</Frame>
|
||||
|
||||
## Workflow Structure Example
|
||||
|
||||
Here is a simple example of a workflow file (`daily-changelog.md`) that helps you create a daily changelog.
|
||||
|
||||
````markdown daily-changelog.md
|
||||
# Daily Changelog Generator
|
||||
|
||||
This workflow helps you create a changelog for your daily work.
|
||||
|
||||
1. **Check your recent git commits:**
|
||||
I will run the following command to see your commits from today.
|
||||
```bash
|
||||
git log --author="$(git config user.name)" --since="yesterday" --oneline
|
||||
```
|
||||
|
||||
2. **Summarize your work:**
|
||||
I will present the commits to you and ask for a summary of your changes to be added to the `changelog.md` file.
|
||||
|
||||
3. **Create/Append to daily changelog:**
|
||||
I will append to the `changelog.md` file. The content will include a header with the current date, the list of commits, and your summary.
|
||||
````
|
||||
|
||||
### Breakdown of the Workflow
|
||||
|
||||
This workflow demonstrates that you don't always need to provide specific tool calls (like XML blocks). Cline is smart enough to interpret your high-level instructions.
|
||||
|
||||
1. **Step 1: Check recent git commits**
|
||||
* We give Cline a specific command to run. This ensures it gets exactly the data we want (today's commits).
|
||||
<Tip>
|
||||
After Cline shows the git commit history, you may need to click the **Proceed While Running** button to allow the workflow to continue.
|
||||
</Tip>
|
||||
|
||||
2. **Step 2: Summarize your work**
|
||||
* Instead of forcing a specific tool, we simply tell Cline what to do: "ask for a summary".
|
||||
* Cline knows it needs to use its capabilities to ask you a question.
|
||||
|
||||
3. **Step 3: Create/Append to daily changelog**
|
||||
* We describe the desired outcome: "append to the `changelog.md` file" with specific content.
|
||||
* Cline figures out how to format the file and use its file-writing tools to accomplish the task.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: "Workflows Quick Start"
|
||||
sidebarTitle: "Quick Start"
|
||||
description: "A step-by-step guide to creating your first Cline workflow."
|
||||
---
|
||||
|
||||
In this tutorial, you will create a powerful workflow that automates the process of reviewing a GitHub Pull Request. This example demonstrates how to combine CLI tools, file analysis, and user interaction into a seamless process.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* You have Cline installed.
|
||||
* You have the [GitHub CLI (`gh`)](https://cli.github.com/) installed and authenticated.
|
||||
* You have a Git repository open with a Pull Request you want to test this on.
|
||||
|
||||
## Creating a Pull Request Review Workflow
|
||||
|
||||
This workflow will automate the process of fetching PR details, analyzing the code changes for issues, and drafting a review comment.
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the Workflow File">
|
||||
First, create the directory structure for your project-specific workflows.
|
||||
|
||||
1. In the root of your project, create a new folder named `.clinerules`.
|
||||
2. Inside `.clinerules`, create another folder named `workflows`.
|
||||
3. Finally, create a new file named `pr-review.md` inside the `workflows` folder.
|
||||
</Step>
|
||||
|
||||
<Step title="Write the Workflow Content">
|
||||
Open the `pr-review.md` file and add the following content. This workflow will gather PR details, analyze the changes, and help you submit a review.
|
||||
|
||||
````markdown pr-review.md
|
||||
# Pull Request Reviewer
|
||||
|
||||
This workflow helps me review a pull request by analyzing the changes and drafting a review.
|
||||
|
||||
## 1. Gather PR Information
|
||||
First, I need to understand what this PR is about. I'll fetch the title, description, and list of changed files.
|
||||
|
||||
```bash
|
||||
gh pr view PR_NUMBER --json title,body,files
|
||||
```
|
||||
|
||||
## 2. Examine Modified Files
|
||||
Now I will examine the diff to understand the specific code changes.
|
||||
|
||||
```bash
|
||||
gh pr diff PR_NUMBER
|
||||
```
|
||||
|
||||
## 3. Analyze Changes
|
||||
I will analyze the code changes for:
|
||||
* **Bugs:** Logic errors or edge cases.
|
||||
* **Performance:** Inefficient loops or operations.
|
||||
* **Security:** Vulnerabilities or unsafe practices.
|
||||
|
||||
## 4. Confirm Assessment
|
||||
Based on my analysis, I will present my findings and ask how you want to proceed.
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>I've reviewed PR #PR_NUMBER. Here is my assessment:
|
||||
|
||||
[Insert Analysis Here]
|
||||
|
||||
Do you want me to approve this PR, request changes, or just leave a comment?</question>
|
||||
<options>["Approve", "Request Changes", "Comment", "Do nothing"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
## 5. Execute Review
|
||||
Finally, I will execute the review command based on your decision.
|
||||
|
||||
```bash
|
||||
# If approving:
|
||||
gh pr review PR_NUMBER --approve --body "Looks good to me! [Summary of analysis]"
|
||||
|
||||
# If requesting changes:
|
||||
gh pr review PR_NUMBER --request-changes --body "Please address the following: [Issues list]"
|
||||
|
||||
# If commenting:
|
||||
gh pr review PR_NUMBER --comment --body "[Comments]"
|
||||
```
|
||||
````
|
||||
|
||||
<Note>
|
||||
When you run this workflow, you will replace `PR_NUMBER` with the actual number of the pull request you want to review (e.g., `/pr-review.md 123`).
|
||||
</Note>
|
||||
</Step>
|
||||
|
||||
<Step title="Run the Workflow">
|
||||
Now you're ready to run your new workflow.
|
||||
|
||||
1. Open the Cline chat panel.
|
||||
2. Type `/pr-review.md` followed by the PR number (e.g., `/pr-review.md 42`) and press Enter.
|
||||
3. Cline will fetch the PR details, analyze the code, and present you with its findings before submitting the review.
|
||||
|
||||
<Tip>
|
||||
As Cline executes commands (like `gh pr view`), it may show you the output and pause. You will need to click the **Proceed While Running** button to allow Cline to analyze the content and continue with the workflow.
|
||||
</Tip>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Other Common Use Cases
|
||||
|
||||
This is just one example. You can create workflows for a wide variety of tasks, such as:
|
||||
|
||||
* **Creating Components:** Automate the boilerplate for new files (like React components or API endpoints).
|
||||
* **Running Tests:** Create a workflow that runs your test suite and summarizes the results.
|
||||
* **Deploying Your Application:** Automate your deployment pipeline using tools like `docker` and `kubectl`.
|
||||
* **Refactoring Code:** Guide Cline through a complex refactoring process step-by-step.
|
||||
|
||||
Explore Cline's capabilities and your own development processes to find repetitive tasks that can be turned into efficient workflows.
|
||||
@@ -361,10 +361,9 @@ description: "Get Cline up and running in your favorite IDE with these simple in
|
||||
<Info>
|
||||
You'll be redirected to [app.cline.bot](https://app.cline.bot) to authenticate. After signing in, you'll automatically return to your editor.
|
||||
</Info>
|
||||
<Frame>
|
||||
<img src="/assets/installation/login.png" alt="Cline sign up screen"
|
||||
/>
|
||||
</Frame>
|
||||
<Info>
|
||||
You'll be redirected to the Cline authentication page to sign in with your account.
|
||||
</Info>
|
||||
</Step>
|
||||
|
||||
<Step title="You're All Set!">
|
||||
@@ -403,7 +402,7 @@ description: "Get Cline up and running in your favorite IDE with these simple in
|
||||
Connect with our team and community for support, tips, and discussions.
|
||||
</Card>
|
||||
|
||||
<Card title="Read the Docs" icon="book-open" href="/getting-started/for-new-coders">
|
||||
Explore guides for new coders, model selection, and advanced features.
|
||||
<Card title="Read the Docs" icon="book-open" href="/getting-started/selecting-your-model">
|
||||
Explore model selection guides and advanced features to get the most out of Cline.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -35,7 +35,7 @@ Create a simple website in a single HTML file. It should have:
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/installation/chat-prompt.png" alt="Cline Chat Prompt"/>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/chat-prompt.png" alt="Cline Chat Prompt"/>
|
||||
</Frame>
|
||||
|
||||
Press Enter and watch Cline work!
|
||||
|
||||
@@ -29,11 +29,33 @@ The "Remote Servers" tab allows you to connect to any MCP server that's accessib
|
||||
2. Fill in the required information:
|
||||
- **Server Name**: Provide a unique, descriptive name for the server
|
||||
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
|
||||
- **Transport Type**: Select the connection protocol (Streamable HTTP is recommended for modern servers)
|
||||
3. Click "Add Server" to initiate the connection
|
||||
4. Cline will attempt to connect to the server and display the connection status
|
||||
|
||||
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
|
||||
|
||||
#### Transport Types
|
||||
|
||||
Cline supports two transport protocols for remote MCP servers:
|
||||
|
||||
- **Streamable HTTP (Recommended)**: The modern MCP transport protocol with better performance, reliability, and full OAuth 2.1 authentication support. Use this for most remote servers.
|
||||
- **SSE (Legacy)**: Server-Sent Events transport. Use this only if the server specifically requires SSE or doesn't support Streamable HTTP.
|
||||
|
||||
#### OAuth Authentication
|
||||
|
||||
Some MCP servers (like Vercel's MCP) require OAuth authentication to access your data securely. When connecting to an OAuth-enabled server:
|
||||
|
||||
1. Add the server as usual with its URL
|
||||
2. If the server requires authentication, you'll see an error message asking to authenticate.
|
||||
3. Click the **"Authenticate"** button that appears
|
||||
4. Your browser will open to the server's authorization page
|
||||
5. Sign in and grant permission
|
||||
6. You'll be redirected back to Cline automatically
|
||||
7. The server will connect and show a green status dot
|
||||
|
||||
Once authenticated, your credentials are securely stored and the server will reconnect automatically when you reload Cline. You won't need to authenticate again unless you delete the server or your credentials expire.
|
||||
|
||||
### Remote Server Discovery
|
||||
|
||||
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
|
||||
@@ -90,9 +112,20 @@ Toggle the switch next to each server to enable or disable it:
|
||||
If a server fails to connect:
|
||||
|
||||
1. An error message will be displayed with details about the failure
|
||||
2. Check that the server URL is correct and the server is running
|
||||
3. Use the "Restart Server" button to attempt reconnection
|
||||
4. If problems persist, you can delete the server and try adding it again
|
||||
2. **For OAuth errors**: Click the "Authenticate" button to complete the authorization flow
|
||||
3. Check that the server URL is correct and the server is running
|
||||
4. Try selecting a different transport type (Streamable HTTP vs SSE)
|
||||
5. Use the "Restart Server" button to attempt reconnection
|
||||
6. If problems persist, you can delete the server and try adding it again
|
||||
|
||||
#### OAuth-Specific Issues
|
||||
|
||||
If you're having trouble authenticating with an OAuth-enabled server:
|
||||
|
||||
- **"Authentication required" persists**: Make sure you completed the authorization flow in your browser and didn't cancel it
|
||||
- **Browser doesn't open**: Check your system's default browser settings and ensure external URLs can be opened
|
||||
- **Redirect errors**: Verify you're using the latest version of Cline - older versions may not support OAuth
|
||||
- **Reset authentication**: Delete the server and re-add it to start fresh with a new OAuth flow
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
@@ -105,10 +138,11 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
{
|
||||
"mcpServers": {
|
||||
"exampleServer": {
|
||||
"url": "https://example.com/mcp-sse",
|
||||
"url": "https://example.com/mcp-server",
|
||||
"type": "streamableHttp",
|
||||
"disabled": false,
|
||||
"autoApprove": ["tool1", "tool2"],
|
||||
"timeout": 30
|
||||
"timeout": 60
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,9 +151,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
|
||||
Key configuration options:
|
||||
|
||||
- **url**: The endpoint URL (for remote servers)
|
||||
- **type**: Transport protocol - `"streamableHttp"` (recommended) or `"sse"` (legacy)
|
||||
- **disabled**: Whether the server is currently enabled (true/false)
|
||||
- **autoApprove**: List of tool names that don't require confirmation
|
||||
- **timeout**: Maximum time in seconds to wait for server responses
|
||||
- **timeout**: Maximum time in seconds to wait for server responses (default: 60)
|
||||
|
||||
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
|
||||
|
||||
@@ -130,3 +165,20 @@ Once connected, Cline can use the tools and resources provided by the MCP server
|
||||
1. A tool approval prompt will appear (unless auto-approved)
|
||||
2. Review the tool details and parameters before approving
|
||||
3. The tool will execute and return results to Cline
|
||||
|
||||
### Example: Connecting to Vercel MCP
|
||||
|
||||
[Vercel MCP](https://vercel.com/docs/mcp/vercel-mcp) is an OAuth-enabled server that provides tools for managing your Vercel projects and deployments:
|
||||
|
||||
1. Click "Remote Servers" tab
|
||||
2. Enter:
|
||||
- **Server Name**: `vercel`
|
||||
- **Server URL**: `https://mcp.vercel.com`
|
||||
- **Transport Type**: Streamable HTTP (pre-selected)
|
||||
3. Click "Add Server"
|
||||
4. You'll see "Authentication required" - click the **"Authenticate"** button
|
||||
5. Sign in to Vercel in your browser and authorize Cline
|
||||
6. Return to Cline - the server will automatically connect
|
||||
7. Vercel's tools (deploy, logs, projects) are now available to Cline!
|
||||
|
||||
Your Vercel authentication persists across sessions, so you won't need to re-authenticate each time you use Cline.
|
||||
|
||||
Generated
+9
-37
@@ -5017,9 +5017,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
@@ -5146,28 +5146,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/gray-matter/node_modules/js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/has-bigints": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||
@@ -6490,9 +6468,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -10235,12 +10213,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/stack-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
|
||||
@@ -10609,9 +10581,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz",
|
||||
"integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz",
|
||||
"integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pump": "^3.0.0",
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"mintlify": "^4.2.23"
|
||||
},
|
||||
"overrides": {
|
||||
"tar-fs": "^3.1.1",
|
||||
"js-yaml": "^4.1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ description: "Learn how to configure and use Anthropic Claude models with Cline.
|
||||
Cline supports the following Anthropic Claude models:
|
||||
|
||||
- `claude-haiku-4-5-20251001`
|
||||
- `claude-opus-4-5-20251101`
|
||||
- `claude-opus-4-1-20250805`
|
||||
- `claude-opus-4-20250514`
|
||||
- `anthropic/claude-sonnet-4.5` (Recommended)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user