mirror of
https://github.com/cline/cline.git
synced 2026-09-09 15:02:23 +08:00
Compare commits
230
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cf0a7f93c | ||
|
|
289824ba2b | ||
|
|
f6633f5d4e | ||
|
|
c01d9d9192 | ||
|
|
1fc8effe4e | ||
|
|
969190d2f8 | ||
|
|
703146182a | ||
|
|
c80ec4ce17 | ||
|
|
866968241a | ||
|
|
e6fd4d47d9 | ||
|
|
0be57bf9ef | ||
|
|
9603643b77 | ||
|
|
c6dce7fb17 | ||
|
|
cc0d4ae6cb | ||
|
|
a9365e30e9 | ||
|
|
efe468d9b1 | ||
|
|
1b6202604d | ||
|
|
ea1dbd8bea | ||
|
|
e43517519e | ||
|
|
520d08c5f2 | ||
|
|
31a55ac87e | ||
|
|
a442983742 | ||
|
|
7b71eff294 | ||
|
|
6d1890f8bb | ||
|
|
11d17fc17e | ||
|
|
42a3dc6150 | ||
|
|
4032e51e8d | ||
|
|
1bbc90487c | ||
|
|
d422ebbb27 | ||
|
|
7470d234ef | ||
|
|
21b81f1844 | ||
|
|
362429a317 | ||
|
|
09cb9ac9ac | ||
|
|
94160faeef | ||
|
|
f526f70e3a | ||
|
|
d9b47378c6 | ||
|
|
0671c59e6d | ||
|
|
bf87887501 | ||
|
|
66a81a6efa | ||
|
|
eee64c5204 | ||
|
|
748ba99c1c | ||
|
|
82b1a01644 | ||
|
|
1c6307e8ad | ||
|
|
58d9c0af18 | ||
|
|
bf213c24ea | ||
|
|
80cceaa3ae | ||
|
|
359e088eb6 | ||
|
|
47a464defe | ||
|
|
38f619cfd9 | ||
|
|
050773ac31 | ||
|
|
6d67ff0b94 | ||
|
|
18f4ef8b49 | ||
|
|
085e69d142 | ||
|
|
46aa66ed9d | ||
|
|
2ebbe954d9 | ||
|
|
067f5eea09 | ||
|
|
2a48bad28c | ||
|
|
c6f4584f7d | ||
|
|
a17b31070f | ||
|
|
cad82d518d | ||
|
|
b4d7ec187f | ||
|
|
42af8414e4 | ||
|
|
8f6b9e8362 | ||
|
|
f1430359db | ||
|
|
932695f70b | ||
|
|
489ee936c2 | ||
|
|
dff7f61175 | ||
|
|
aead42c6b8 | ||
|
|
db50a1c671 | ||
|
|
bb20f60f1d | ||
|
|
a7333b7177 | ||
|
|
f30837a850 | ||
|
|
5660b2513f | ||
|
|
64e7e5fa4c | ||
|
|
b34166e99a | ||
|
|
cd2d8f98a7 | ||
|
|
333468c9b6 | ||
|
|
0da6ddc001 | ||
|
|
612130366f | ||
|
|
85206cfded | ||
|
|
6f8ed7aa56 | ||
|
|
f2c130a69e | ||
|
|
c870bfb454 | ||
|
|
436bfdb535 | ||
|
|
e2a3652d26 | ||
|
|
632aca225c | ||
|
|
a1eaccdaef | ||
|
|
f7e76f9bee | ||
|
|
0d04205dc4 | ||
|
|
4b9dbf11a0 | ||
|
|
c5afbd743e | ||
|
|
6d1bfc3a1b | ||
|
|
d18486a199 | ||
|
|
7417b1b671 | ||
|
|
e4fbe331f4 | ||
|
|
d122683e91 | ||
|
|
fdc96d0545 | ||
|
|
c0241f4060 | ||
|
|
c3f523427b | ||
|
|
428e465d4d | ||
|
|
46ab08c9f8 | ||
|
|
0fd30e0d20 | ||
|
|
e87a765773 | ||
|
|
e80c3d3cd8 | ||
|
|
80b6ab6046 | ||
|
|
5155123b80 | ||
|
|
30af711d5e | ||
|
|
b218b25e0b | ||
|
|
bf27aa53ef | ||
|
|
4644a7159a | ||
|
|
63c63f38f5 | ||
|
|
f1d8ca423d | ||
|
|
16b8ce7e44 | ||
|
|
0bbb4299ba | ||
|
|
197b17b3bd | ||
|
|
591b840531 | ||
|
|
c35ba37402 | ||
|
|
850a6ab841 | ||
|
|
99c85710e9 | ||
|
|
9fc9785aa2 | ||
|
|
5c02f6eb13 | ||
|
|
ac01e69555 | ||
|
|
dbc18a8fce | ||
|
|
b77398390b | ||
|
|
c20052de00 | ||
|
|
1dac507b63 | ||
|
|
e5aa48c2b5 | ||
|
|
63efb4aa5d | ||
|
|
5fcf83b627 | ||
|
|
0b308d610e | ||
|
|
60d3048aa0 | ||
|
|
aed3ac6597 | ||
|
|
86e2a3e7ce | ||
|
|
47856c70d2 | ||
|
|
f1a84ddbde | ||
|
|
ebcc927cc7 | ||
|
|
1e108d87e0 | ||
|
|
7b62d7786e | ||
|
|
042f5c9823 | ||
|
|
2f8a4525a4 | ||
|
|
20774a4187 | ||
|
|
be5bda2740 | ||
|
|
261fd9036f | ||
|
|
2334d4d531 | ||
|
|
6390d854f7 | ||
|
|
7d5c56a55a | ||
|
|
0dc760ac03 | ||
|
|
8c1241c8cd | ||
|
|
17686ae3d9 | ||
|
|
97460d2952 | ||
|
|
e629ed0ef6 | ||
|
|
96788e9127 | ||
|
|
d7716a514d | ||
|
|
7aaa5966d6 | ||
|
|
bb1d068139 | ||
|
|
450945ae0e | ||
|
|
191e9635bd | ||
|
|
c13a7a80b3 | ||
|
|
d4a4adfa5f | ||
|
|
557e20224e | ||
|
|
0e9a326a6a | ||
|
|
f5ecb6db0c | ||
|
|
8f1405b881 | ||
|
|
8a9e03c8ff | ||
|
|
edba02b45e | ||
|
|
cc36c67fc9 | ||
|
|
d77032bc8a | ||
|
|
608dde94b3 | ||
|
|
47ff7c1620 | ||
|
|
1b7f971c34 | ||
|
|
12eadd3378 | ||
|
|
b3e0ef9ed7 | ||
|
|
fb94d8d3d4 | ||
|
|
d11bd15d60 | ||
|
|
31c48898a6 | ||
|
|
5c9901d68a | ||
|
|
45b79dc3d7 | ||
|
|
26b6c7bdb6 | ||
|
|
d0678a2ad1 | ||
|
|
09276ebf43 | ||
|
|
af8b51b189 | ||
|
|
3e6b3f252b | ||
|
|
f019c365a6 | ||
|
|
f6fe843cfb | ||
|
|
01f26c21ae | ||
|
|
031c2f5b05 | ||
|
|
c999e269db | ||
|
|
032c1bf792 | ||
|
|
fa73d60b7a | ||
|
|
841bb7f1d7 | ||
|
|
f01428884d | ||
|
|
2ce5548250 | ||
|
|
9f32cd247f | ||
|
|
ace48198cc | ||
|
|
9f0240dfd7 | ||
|
|
5530cfe375 | ||
|
|
af4d99e0bc | ||
|
|
cd011a0e4a | ||
|
|
d44184ab03 | ||
|
|
5d96704e92 | ||
|
|
49812eb332 | ||
|
|
57b72519ae | ||
|
|
1ed4d00a16 | ||
|
|
c090f5b1a7 | ||
|
|
3c917ec99d | ||
|
|
b315be397d | ||
|
|
dc9e7916de | ||
|
|
cb9d1e81b8 | ||
|
|
eb5a452c9c | ||
|
|
6a90294a2a | ||
|
|
2e1334c10a | ||
|
|
18b77ee5e5 | ||
|
|
4bf8feecac | ||
|
|
2f3450f667 | ||
|
|
3e89c28727 | ||
|
|
d065ac7b37 | ||
|
|
5a0a25601a | ||
|
|
48d822a030 | ||
|
|
417104505c | ||
|
|
e21d3ff1bb | ||
|
|
da5477f891 | ||
|
|
4903dfcb6e | ||
|
|
37b2f7fbc9 | ||
|
|
11fbe4b21d | ||
|
|
e0844ac6e2 | ||
|
|
f28d760675 | ||
|
|
8363d090e0 | ||
|
|
3409fa7442 | ||
|
|
a20685d289 | ||
|
|
926c5e189e |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Added telemetry to track usage of skills feature
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix workflow slash command search to be case-insensitive
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix context menu not closing when pressing Escape key by calling setShowContextMenu(false).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed issues with Oracle Code Assist modelInfo being saved in Cline CLI and removed extra logs
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix model display in the ModelPickerModal when using litellm
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fixed crash when OpenAI-compatible APIs send usage chunks with empty or null choices arrays at end of streaming
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
fix a11y for auto approve checkbox
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Update Claude 3.5 Haiku model to support image processing as per Anthropic API release notes (Feb 24, 2025).
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Send version headers when making requests to the Cline backend
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Prevent requests with an expired auth token
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Phasing in Responses API usage instead of defaulting to Responses for every model if it is supported
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Allow admins to opt out of remote config
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
Fix auth state loop
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
litellm: trigger model fetching with default base URL
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": patch
|
||||
---
|
||||
|
||||
add github workflow to have cline auto review PRs
|
||||
@@ -1,194 +0,0 @@
|
||||
# Hotfix Release
|
||||
|
||||
Create a hotfix release by cherry-picking specific commits from main onto the latest release tag.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow helps you:
|
||||
1. Select specific commits from main to include in a hotfix
|
||||
2. Create a release notes commit on main (changelog + version bump)
|
||||
3. Cherry-pick everything onto the latest release tag
|
||||
4. Tag and push the new release
|
||||
|
||||
## Step 1: Setup and Gather Information
|
||||
|
||||
First, ensure we're on main and up to date:
|
||||
|
||||
```bash
|
||||
git checkout main && git pull origin main
|
||||
```
|
||||
|
||||
Get the latest release tag:
|
||||
|
||||
```bash
|
||||
git tag --sort=-v:refname | head -1
|
||||
```
|
||||
|
||||
## Step 2: Present Commits Since Last Release
|
||||
|
||||
Show all commits on main since the last release tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git log ${LAST_TAG}..HEAD --oneline --format="%h %s (%an)"
|
||||
```
|
||||
|
||||
Also get the commit messages already on the tag (to identify previously cherry-picked commits). Note: Run these as separate commands to avoid shell parsing issues with parentheses in author names:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
PREV_TAG=$(git tag --sort=-v:refname | head -2 | tail -1)
|
||||
```
|
||||
|
||||
```bash
|
||||
git log $PREV_TAG..$LAST_TAG --oneline --format="%s"
|
||||
```
|
||||
|
||||
**Present the list** to the user in a numbered format with commit hash, subject, and author. For any commits whose subject line already appears in the tag's history (previously cherry-picked in an earlier hotfix) or are "Release Notes" commits, add `(already in previous hotfix)` or `(release notes - skip)` after them so the user knows to skip those.
|
||||
|
||||
Ask which commits to include in the hotfix.
|
||||
|
||||
Use the ask_followup_question tool to let the user specify which commits they want (by number or hash).
|
||||
|
||||
## Step 3: Analyze Selected Commits
|
||||
|
||||
For each selected commit:
|
||||
1. Get the full commit message: `git show --no-patch --format="%B" <hash>`
|
||||
2. Get the diff to understand the change: `git show <hash> --stat`
|
||||
3. Find the associated PR if any: `gh pr list --search "<hash>" --state merged --json number,title --jq '.[0]'`
|
||||
|
||||
Build a mental model of what these changes do for the changelog.
|
||||
|
||||
## Step 4: Determine New Version Number
|
||||
|
||||
Parse the current version from package.json and the last tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
echo "Last release: $LAST_TAG"
|
||||
cat package.json | grep '"version"'
|
||||
```
|
||||
|
||||
Hotfixes always increment the patch version (e.g., 3.40.0 -> 3.40.1, or 3.40.1 -> 3.40.2).
|
||||
|
||||
**Ask the user to confirm the new version number.**
|
||||
|
||||
## Step 5: Create Release Notes Commit on Main
|
||||
|
||||
On the main branch, create a commit that updates:
|
||||
|
||||
1. **CHANGELOG.md** - Add a new section for the hotfix version at the top:
|
||||
```markdown
|
||||
## [3.40.1]
|
||||
|
||||
- Description of fix 1
|
||||
- Description of fix 2
|
||||
```
|
||||
|
||||
Write clear, user-friendly descriptions based on your analysis of the commits.
|
||||
|
||||
2. **package.json** - Update the version field to the new version
|
||||
|
||||
3. **Delete changesets** for the commits being included in the hotfix. This prevents the changeset bot from including duplicate entries in the next regular release.
|
||||
|
||||
Find and delete the changeset files associated with the selected commits:
|
||||
```bash
|
||||
ls .changeset/
|
||||
```
|
||||
|
||||
Each changeset file in `.changeset/` corresponds to a PR. Read them to identify which ones belong to the commits you're hotfixing, then delete those files.
|
||||
|
||||
**Skip running `npm run install:all`** - the automation handles outdated lockfiles.
|
||||
|
||||
Commit with message format: `v{VERSION} Release Notes (hotfix)`
|
||||
|
||||
In the commit body, mention:
|
||||
- This is for a hotfix release
|
||||
- List the cherry-picked commits that will be included
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md package.json .changeset/
|
||||
git commit -m "v3.40.1 Release Notes (hotfix)
|
||||
|
||||
Hotfix release including:
|
||||
- <commit1-hash>: <description>
|
||||
- <commit2-hash>: <description>
|
||||
"
|
||||
```
|
||||
|
||||
Push to main:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Step 6: Build the Hotfix on the Tag
|
||||
|
||||
Checkout the last release tag (detached HEAD):
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git checkout $LAST_TAG
|
||||
```
|
||||
|
||||
Cherry-pick the selected commits in order:
|
||||
|
||||
```bash
|
||||
git cherry-pick <commit1-hash>
|
||||
git cherry-pick <commit2-hash>
|
||||
# ... etc
|
||||
```
|
||||
|
||||
Finally, cherry-pick the release notes commit you just pushed to main:
|
||||
|
||||
```bash
|
||||
# Get the hash of the release notes commit (should be HEAD of main)
|
||||
RELEASE_NOTES_COMMIT=$(git rev-parse main)
|
||||
git cherry-pick $RELEASE_NOTES_COMMIT
|
||||
```
|
||||
|
||||
## Step 7: Tag and Push
|
||||
|
||||
After all cherry-picks are applied successfully:
|
||||
|
||||
```bash
|
||||
# Tag the new release
|
||||
git tag v{VERSION}
|
||||
|
||||
# Push the tag to remote
|
||||
git push origin v{VERSION}
|
||||
```
|
||||
|
||||
## Step 8: Return to Main and Summary
|
||||
|
||||
Return to main branch:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Copy a Slack announcement message to clipboard** with the version and PR links for each included fix:
|
||||
|
||||
```
|
||||
VS Code Hotfix v{VERSION} Published
|
||||
|
||||
- Description of fix 1 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
- Description of fix 2 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
```
|
||||
|
||||
Present a final summary:
|
||||
- New version: v{VERSION}
|
||||
- Tag pushed: yes
|
||||
- Commits included: (list them)
|
||||
- Slack message copied to clipboard: yes
|
||||
|
||||
Remind the user to:
|
||||
1. Manually trigger the publish release GitHub Action at: https://github.com/cline/cline/actions/workflows/publish.yml (paste `v{VERSION}` as the tag)
|
||||
2. Post the Slack message to announce the hotfix
|
||||
|
||||
## Important Notes
|
||||
|
||||
- This workflow does NOT create a release branch - only tags
|
||||
- The release notes commit goes to main first, then gets cherry-picked to the tag
|
||||
- This keeps main's history accurate while allowing hotfix releases from tags
|
||||
- If cherry-pick conflicts occur, resolve them before continuing
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../.clinerules/workflows/hotfix-release.md
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../.clinerules/workflows/release.md
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Only run in Claude Code remote environments
|
||||
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$CLAUDE_PROJECT_DIR"
|
||||
|
||||
echo "=== Claude Code for Web Setup ==="
|
||||
echo ""
|
||||
|
||||
# Install latest gh CLI tool
|
||||
echo "Installing GitHub CLI..."
|
||||
GH_VERSION=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//')
|
||||
curl -sL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tar.gz
|
||||
tar -xzf /tmp/gh.tar.gz -C /tmp
|
||||
sudo mv "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh
|
||||
rm -rf /tmp/gh.tar.gz /tmp/gh_${GH_VERSION}_linux_amd64
|
||||
echo "Installed gh version: $(gh --version | head -1)"
|
||||
echo ""
|
||||
|
||||
# Check if GITHUB_TOKEN is set and configure gh
|
||||
if [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
echo "GITHUB_TOKEN is configured - gh CLI is ready to use"
|
||||
echo ""
|
||||
echo "You can use gh commands directly, for example:"
|
||||
echo " gh issue list --repo cline/cline --limit 5"
|
||||
echo " gh pr list --repo cline/cline --state open"
|
||||
echo " gh issue view 123 --repo cline/cline"
|
||||
echo ""
|
||||
else
|
||||
echo "GITHUB_TOKEN is not set - gh CLI will have limited functionality"
|
||||
echo ""
|
||||
echo "To enable full GitHub API access:"
|
||||
echo "1. Create a Fine-grained Personal Access Token at https://github.com/settings/tokens?type=beta"
|
||||
echo "2. Add it as GITHUB_TOKEN in your Claude Code environment settings"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Install project dependencies
|
||||
echo "Installing dependencies..."
|
||||
npm run install:all
|
||||
|
||||
# Generate gRPC/protobuf types (required for TypeScript)
|
||||
echo "Generating proto types..."
|
||||
npm run protos
|
||||
|
||||
echo ""
|
||||
echo "Session setup complete!"
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/claude-code-for-web-setup.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
../../.claude/commands/hotfix-release.md
|
||||
@@ -0,0 +1,194 @@
|
||||
# Hotfix Release
|
||||
|
||||
Create a hotfix release by cherry-picking specific commits from main onto the latest release tag.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow helps you:
|
||||
1. Select specific commits from main to include in a hotfix
|
||||
2. Create a release notes commit on main (changelog + version bump)
|
||||
3. Cherry-pick everything onto the latest release tag
|
||||
4. Tag and push the new release
|
||||
|
||||
## Step 1: Setup and Gather Information
|
||||
|
||||
First, ensure we're on main and up to date:
|
||||
|
||||
```bash
|
||||
git checkout main && git pull origin main
|
||||
```
|
||||
|
||||
Get the latest release tag:
|
||||
|
||||
```bash
|
||||
git tag --sort=-v:refname | head -1
|
||||
```
|
||||
|
||||
## Step 2: Present Commits Since Last Release
|
||||
|
||||
Show all commits on main since the last release tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git log ${LAST_TAG}..HEAD --oneline --format="%h %s (%an)"
|
||||
```
|
||||
|
||||
Also get the commit messages already on the tag (to identify previously cherry-picked commits). Note: Run these as separate commands to avoid shell parsing issues with parentheses in author names:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
PREV_TAG=$(git tag --sort=-v:refname | head -2 | tail -1)
|
||||
```
|
||||
|
||||
```bash
|
||||
git log $PREV_TAG..$LAST_TAG --oneline --format="%s"
|
||||
```
|
||||
|
||||
**Present the list** to the user in a numbered format with commit hash, subject, and author. For any commits whose subject line already appears in the tag's history (previously cherry-picked in an earlier hotfix) or are "Release Notes" commits, add `(already in previous hotfix)` or `(release notes - skip)` after them so the user knows to skip those.
|
||||
|
||||
Ask which commits to include in the hotfix.
|
||||
|
||||
Use the ask_followup_question tool to let the user specify which commits they want (by number or hash).
|
||||
|
||||
## Step 3: Analyze Selected Commits
|
||||
|
||||
For each selected commit:
|
||||
1. Get the full commit message: `git show --no-patch --format="%B" <hash>`
|
||||
2. Get the diff to understand the change: `git show <hash> --stat`
|
||||
3. Find the associated PR if any: `gh pr list --search "<hash>" --state merged --json number,title --jq '.[0]'`
|
||||
|
||||
Build a mental model of what these changes do for the changelog.
|
||||
|
||||
## Step 4: Determine New Version Number
|
||||
|
||||
Parse the current version from package.json and the last tag:
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
echo "Last release: $LAST_TAG"
|
||||
cat package.json | grep '"version"'
|
||||
```
|
||||
|
||||
Hotfixes always increment the patch version (e.g., 3.40.0 -> 3.40.1, or 3.40.1 -> 3.40.2).
|
||||
|
||||
**Ask the user to confirm the new version number.**
|
||||
|
||||
## Step 5: Create Release Notes Commit on Main
|
||||
|
||||
On the main branch, create a commit that updates:
|
||||
|
||||
1. **CHANGELOG.md** - Add a new section for the hotfix version at the top:
|
||||
```markdown
|
||||
## [3.40.1]
|
||||
|
||||
- Description of fix 1
|
||||
- Description of fix 2
|
||||
```
|
||||
|
||||
Write clear, user-friendly descriptions based on your analysis of the commits.
|
||||
|
||||
2. **package.json** - Update the version field to the new version
|
||||
|
||||
3. **Delete changesets** for the commits being included in the hotfix. This prevents the changeset bot from including duplicate entries in the next regular release.
|
||||
|
||||
Find and delete the changeset files associated with the selected commits:
|
||||
```bash
|
||||
ls .changeset/
|
||||
```
|
||||
|
||||
Each changeset file in `.changeset/` corresponds to a PR. Read them to identify which ones belong to the commits you're hotfixing, then delete those files.
|
||||
|
||||
**Skip running `npm run install:all`** - the automation handles outdated lockfiles.
|
||||
|
||||
Commit with message format: `v{VERSION} Release Notes (hotfix)`
|
||||
|
||||
In the commit body, mention:
|
||||
- This is for a hotfix release
|
||||
- List the cherry-picked commits that will be included
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md package.json .changeset/
|
||||
git commit -m "v3.40.1 Release Notes (hotfix)
|
||||
|
||||
Hotfix release including:
|
||||
- <commit1-hash>: <description>
|
||||
- <commit2-hash>: <description>
|
||||
"
|
||||
```
|
||||
|
||||
Push to main:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Step 6: Build the Hotfix on the Tag
|
||||
|
||||
Checkout the last release tag (detached HEAD):
|
||||
|
||||
```bash
|
||||
LAST_TAG=$(git tag --sort=-v:refname | head -1)
|
||||
git checkout $LAST_TAG
|
||||
```
|
||||
|
||||
Cherry-pick the selected commits in order:
|
||||
|
||||
```bash
|
||||
git cherry-pick <commit1-hash>
|
||||
git cherry-pick <commit2-hash>
|
||||
# ... etc
|
||||
```
|
||||
|
||||
Finally, cherry-pick the release notes commit you just pushed to main:
|
||||
|
||||
```bash
|
||||
# Get the hash of the release notes commit (should be HEAD of main)
|
||||
RELEASE_NOTES_COMMIT=$(git rev-parse main)
|
||||
git cherry-pick $RELEASE_NOTES_COMMIT
|
||||
```
|
||||
|
||||
## Step 7: Tag and Push
|
||||
|
||||
After all cherry-picks are applied successfully:
|
||||
|
||||
```bash
|
||||
# Tag the new release
|
||||
git tag v{VERSION}
|
||||
|
||||
# Push the tag to remote
|
||||
git push origin v{VERSION}
|
||||
```
|
||||
|
||||
## Step 8: Return to Main and Summary
|
||||
|
||||
Return to main branch:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
```
|
||||
|
||||
**Copy a Slack announcement message to clipboard** with the version and PR links for each included fix:
|
||||
|
||||
```
|
||||
VS Code Hotfix v{VERSION} Published
|
||||
|
||||
- Description of fix 1 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
- Description of fix 2 https://github.com/cline/cline/pull/{PR_NUMBER}
|
||||
```
|
||||
|
||||
Present a final summary:
|
||||
- New version: v{VERSION}
|
||||
- Tag pushed: yes
|
||||
- Commits included: (list them)
|
||||
- Slack message copied to clipboard: yes
|
||||
|
||||
Remind the user to:
|
||||
1. Manually trigger the publish release GitHub Action at: https://github.com/cline/cline/actions/workflows/publish.yml (paste `v{VERSION}` as the tag)
|
||||
2. Post the Slack message to announce the hotfix
|
||||
|
||||
## Important Notes
|
||||
|
||||
- This workflow does NOT create a release branch - only tags
|
||||
- The release notes commit goes to main first, then gets cherry-picked to the tag
|
||||
- This keeps main's history accurate while allowing hotfix releases from tags
|
||||
- If cherry-pick conflicts occur, resolve them before continuing
|
||||
@@ -0,0 +1,232 @@
|
||||
# Release
|
||||
|
||||
Prepare and publish a release from the open changeset PR.
|
||||
|
||||
## Overview
|
||||
|
||||
This workflow helps you:
|
||||
1. Find and checkout the open changeset PR
|
||||
2. Clean up the changelog (fix version format, wordsmith entries)
|
||||
3. Push changes back to the PR branch
|
||||
4. Merge with proper commit message format
|
||||
5. Tag and push the release (after verifying the commit)
|
||||
6. Trigger the publish workflow
|
||||
7. Update GitHub release notes
|
||||
8. Provide final summary with Slack announcement
|
||||
|
||||
## Step 1: Find the Changeset PR
|
||||
|
||||
Look for the open changeset PR:
|
||||
|
||||
```bash
|
||||
gh pr list --search "Changeset version bump" --state open --json number,title,headRefName,url
|
||||
```
|
||||
|
||||
If no PR is found, inform the user there's no changeset PR ready. They may need to:
|
||||
- Merge PRs with changesets to main first
|
||||
- Manually trigger the Changeset Converter workflow at: https://github.com/cline/cline/actions/workflows/changeset-converter.yml
|
||||
|
||||
## Step 2: Gather PR Information
|
||||
|
||||
Get the PR details:
|
||||
|
||||
```bash
|
||||
PR_NUMBER=<number from step 1>
|
||||
gh pr view $PR_NUMBER --json body,files,headRefName
|
||||
```
|
||||
|
||||
Checkout the PR branch:
|
||||
|
||||
```bash
|
||||
git fetch origin changeset-release/main
|
||||
git checkout changeset-release/main
|
||||
```
|
||||
|
||||
If the branch has diverged from remote, reset to the remote version:
|
||||
|
||||
```bash
|
||||
git reset --hard origin/changeset-release/main
|
||||
```
|
||||
|
||||
## Step 3: Analyze the Changes
|
||||
|
||||
Read the current CHANGELOG.md to see what the automation generated:
|
||||
|
||||
```bash
|
||||
head -50 CHANGELOG.md
|
||||
```
|
||||
|
||||
Get the version from package.json:
|
||||
|
||||
```bash
|
||||
cat package.json | grep '"version"'
|
||||
```
|
||||
|
||||
**Present to the user:**
|
||||
- The version number that will be released
|
||||
- The raw changelog entries from the changeset PR
|
||||
- Whether this is a patch, minor, or major release
|
||||
|
||||
## Step 4: Clean Up the Changelog
|
||||
|
||||
The changelog needs these fixes:
|
||||
|
||||
1. **Add brackets to version number**: Change `## 3.44.1` to `## [3.44.1]`
|
||||
|
||||
2. **No category headers**: Don't use `### Added`, `### Fixed`, etc. Just a flat list of bullet points.
|
||||
|
||||
3. **Order entries from most important to least important**:
|
||||
- Lead with major new features or significant fixes users care about
|
||||
- End with minor fixes or internal changes
|
||||
|
||||
4. **Write user-friendly descriptions**:
|
||||
- This is for end users, not developers—explain what changed in plain language
|
||||
- Remove commit hashes from the beginning of lines (the automation adds these)
|
||||
- Look at the actual commit diffs (`git show <hash>`) and PRs to understand what changed
|
||||
- Write colorful descriptions that explain the value and impact, not just technical details
|
||||
- Consolidate related changes into single entries when appropriate
|
||||
|
||||
**Ask the user** to review the proposed changelog changes before applying them. Show them:
|
||||
- Current (raw) changelog section
|
||||
- Proposed (cleaned) changelog section
|
||||
|
||||
Once approved, apply the changes to CHANGELOG.md.
|
||||
|
||||
## Step 5: Commit and Push Changes
|
||||
|
||||
After making changelog edits:
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md
|
||||
git commit -m "Clean up changelog formatting"
|
||||
git push origin changeset-release/main
|
||||
```
|
||||
|
||||
## Step 6: Merge the PR
|
||||
|
||||
**Ask the user to confirm** they're ready to merge.
|
||||
|
||||
Merge the PR with the proper commit message format:
|
||||
|
||||
```bash
|
||||
VERSION=<version from package.json>
|
||||
gh pr merge $PR_NUMBER --squash --subject "v${VERSION} Release Notes" --body ""
|
||||
```
|
||||
|
||||
**If merge is blocked by branch protection:**
|
||||
- Users with admin privileges can add the `--admin` flag to bypass
|
||||
- Users without admin privileges need to get the PR approved through normal review first before merging
|
||||
|
||||
## Step 7: Tag the Release
|
||||
|
||||
After the merge completes, checkout main and pull:
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
**IMPORTANT: Verify the latest commit is the release commit before tagging:**
|
||||
|
||||
```bash
|
||||
git log -1 --oneline
|
||||
```
|
||||
|
||||
Confirm the commit message matches `v{VERSION} Release Notes` (e.g., `v3.44.1 Release Notes`). Do NOT blindly tag HEAD without verification.
|
||||
|
||||
Once verified, tag and push:
|
||||
|
||||
```bash
|
||||
VERSION=<version>
|
||||
git tag v${VERSION}
|
||||
git push origin v${VERSION}
|
||||
```
|
||||
|
||||
## Step 8: Trigger Publish Workflow
|
||||
|
||||
**Copy the tag to clipboard** so the user can easily paste it into the GitHub Actions workflow:
|
||||
|
||||
```bash
|
||||
echo -n "v{VERSION}" | pbcopy
|
||||
```
|
||||
|
||||
**Tell the user to trigger the publish workflow:**
|
||||
1. Go to: https://github.com/cline/cline/actions/workflows/publish.yml
|
||||
2. Select **"release"** for release-type
|
||||
3. Paste **`v{VERSION}`** as the tag (already in clipboard)
|
||||
|
||||
**Wait for the user** to confirm the publish workflow has completed before proceeding.
|
||||
|
||||
## Step 9: Update GitHub Release Notes
|
||||
|
||||
Once the user confirms the publish workflow is done, fetch the auto-generated release content:
|
||||
|
||||
```bash
|
||||
VERSION=<version>
|
||||
gh release view v${VERSION} --json body --jq '.body'
|
||||
```
|
||||
|
||||
The auto-generated release has:
|
||||
- `## What's Changed` - PR list (we'll replace this with our changelog)
|
||||
- `## New Contributors` - First-time contributors (keep this if present)
|
||||
- `**Full Changelog**` - Comparison link (keep this)
|
||||
|
||||
Build the new release body:
|
||||
1. Start with `## What's Changed` header
|
||||
2. Add our changelog content (from CHANGELOG.md for this version)
|
||||
3. Keep the `## New Contributors` section if it exists
|
||||
4. Keep the `**Full Changelog**` link
|
||||
|
||||
Update the release:
|
||||
|
||||
```bash
|
||||
gh release edit v${VERSION} --notes "<new body content>"
|
||||
```
|
||||
|
||||
Verify the release was updated:
|
||||
|
||||
```bash
|
||||
gh release view v${VERSION}
|
||||
```
|
||||
|
||||
## Step 10: Final Summary
|
||||
|
||||
**Copy a Slack announcement message to clipboard** (include the full changelog, not just highlights):
|
||||
|
||||
```bash
|
||||
echo "VS Code v{VERSION} Released
|
||||
|
||||
- Changelog entry 1
|
||||
- Changelog entry 2
|
||||
- Changelog entry 3" | pbcopy
|
||||
```
|
||||
|
||||
**Present a final summary:**
|
||||
- Version released: v{VERSION}
|
||||
- PR merged: #{PR_NUMBER}
|
||||
- Tag pushed: v{VERSION}
|
||||
- Release: https://github.com/cline/cline/releases/tag/v{VERSION}
|
||||
- Slack message copied to clipboard
|
||||
|
||||
**Final reminder:**
|
||||
Post the Slack message to announce the release
|
||||
|
||||
## Handling Edge Cases
|
||||
|
||||
### No changesets found
|
||||
If the changeset PR body shows no changes, inform the user they need to merge PRs with changesets first.
|
||||
|
||||
### Merge conflicts
|
||||
If there are conflicts on the changeset branch, help the user resolve them:
|
||||
```bash
|
||||
git fetch origin main
|
||||
git rebase origin/main
|
||||
# resolve conflicts
|
||||
git push origin changeset-release/main --force-with-lease
|
||||
```
|
||||
|
||||
### User wants to add more changes
|
||||
If the user wants to include additional PRs before releasing:
|
||||
1. Ask them to merge those PRs to main first
|
||||
2. The changeset automation will update the PR automatically
|
||||
3. Re-run this workflow after the PR is updated
|
||||
+3
-3
@@ -72,12 +72,12 @@ POSTHOG_TELEMETRY_ENABLED=true # Enable PostHog telemetry (default: tru
|
||||
# Example configurations:
|
||||
#
|
||||
# Console debugging (logs only):
|
||||
# OTEL_TELEMETRY_ENABLED=1
|
||||
# OTEL_TELEMETRY_ENABLED=true
|
||||
# OTEL_LOGS_EXPORTER=console
|
||||
# TEL_DEBUG_DIAGNOSTICS=true
|
||||
#
|
||||
# OTLP with gRPC (insecure, for local testing):
|
||||
# OTEL_TELEMETRY_ENABLED=1
|
||||
# OTEL_TELEMETRY_ENABLED=true
|
||||
# OTEL_LOGS_EXPORTER=otlp
|
||||
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
# OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
|
||||
@@ -85,7 +85,7 @@ POSTHOG_TELEMETRY_ENABLED=true # Enable PostHog telemetry (default: tru
|
||||
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer your-token
|
||||
#
|
||||
# OTLP with HTTP/JSON (production):
|
||||
# OTEL_TELEMETRY_ENABLED=1
|
||||
# OTEL_TELEMETRY_ENABLED=true
|
||||
# OTEL_LOGS_EXPORTER=otlp
|
||||
# OTEL_EXPORTER_OTLP_PROTOCOL=http/json
|
||||
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/docs/
|
||||
/.github/ @saoudrizwan @garoth @sjf
|
||||
/README.md @saoudrizwan @nickbaumann98
|
||||
/src/core/storage/ @celestial-vault
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
name: Claude Issue Triage
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
# Manual trigger for backfilling existing issues. Run from terminal:
|
||||
# gh workflow run claude-issue-triage.yml -f issue_number=1234
|
||||
# Or batch process:
|
||||
# gh issue list --state open --limit 10 --json number --jq '.[].number' | while read num; do
|
||||
# gh workflow run claude-issue-triage.yml -f issue_number=$num
|
||||
# sleep 60
|
||||
# done
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
issue_number:
|
||||
description: 'Issue number to triage'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-issue-triage:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
# SECURITY: These permissions are intentionally restrictive.
|
||||
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
|
||||
# - issues: write -> Claude can comment and add labels (the only write access needed)
|
||||
# - pull-requests: read -> Claude can view PR context but CANNOT create PRs
|
||||
# This ensures that even if a malicious user attempts prompt injection via issue content,
|
||||
# Claude cannot modify repository code, create branches, or open PRs.
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Run Issue Response & Triage
|
||||
id: triage
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed_non_write_users: "*"
|
||||
# Allow all tools - security is enforced by GitHub permissions above (contents: read, issues: write)
|
||||
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
|
||||
prompt: |
|
||||
You're a GitHub issue first responder for the open source Cline repository.
|
||||
|
||||
**Issue:** #${{ github.event.issue.number || inputs.issue_number }}
|
||||
**Title:** ${{ github.event.issue.title || 'See issue details below' }}
|
||||
**Author:** @${{ github.event.issue.user.login || 'See issue details below' }}
|
||||
|
||||
## Your job
|
||||
|
||||
Investigate this issue thoroughly, then post a single helpful comment that helps the user and gives maintainers the context they need.
|
||||
|
||||
## Investigation
|
||||
|
||||
Start by reading the full issue:
|
||||
gh issue view ${{ github.event.issue.number || inputs.issue_number }}
|
||||
|
||||
### Search for duplicates and related issues
|
||||
|
||||
Search thoroughly for existing issues that match this one:
|
||||
gh issue list --search "<keywords from the issue>" --state all --limit 30
|
||||
gh issue list --search "<error messages>" --state all --limit 20
|
||||
gh issue list --search "<affected feature/component>" --state all --limit 20
|
||||
|
||||
For each relevant issue you find, read it including its comments:
|
||||
gh issue view <number> --comments
|
||||
|
||||
You're looking for:
|
||||
- **Duplicates**: Issues describing the same problem. Link to them and explain why you think they're duplicates. If closed, check how they were resolved - the solution might apply here.
|
||||
- **Related issues**: Similar problems or context that could help. Pull useful information from their comments (workarounds others found, debugging steps that helped, maintainer explanations). Link to them and explain the connection.
|
||||
|
||||
If there are closed issues with solutions, surface those solutions prominently - this might immediately solve the user's problem.
|
||||
|
||||
### Analyze recent changes (ALWAYS DO THIS)
|
||||
|
||||
Many issues are regressions from recent releases. **Always** check what changed recently:
|
||||
gh release list --limit 10
|
||||
gh pr list --state merged --limit 50 --json number,title,mergedAt,author,body
|
||||
|
||||
Look for PRs merged in the last few weeks that might correlate with the issue. If you find a likely connection:
|
||||
gh pr view <number>
|
||||
gh pr diff <number>
|
||||
git log --since="1 month ago" --oneline -- <relevant paths>
|
||||
git show <commit>
|
||||
|
||||
**Always include your findings in your comment:**
|
||||
- If you find a regression, call it out explicitly: which PR/commit likely caused it, who authored it, what changed, and suggest a fix direction if you can see one.
|
||||
- If you don't find anything related, still mention it: "I analyzed recent PRs and releases but didn't find any changes that seem related to this issue."
|
||||
|
||||
### Search the codebase
|
||||
|
||||
Find the relevant code:
|
||||
- Use grep/find to locate code related to the issue
|
||||
- Key areas: `src/api/` (providers/models), `src/core/prompts/` (tools/prompts), platform-specific code for VS Code vs JetBrains
|
||||
|
||||
### Find documentation
|
||||
|
||||
Cline docs are at **https://docs.cline.bot/** and built with Mintlify from the `docs/` directory.
|
||||
|
||||
The URL structure maps directly to the file structure:
|
||||
- `docs/getting-started/selecting-your-model.mdx` → https://docs.cline.bot/getting-started/selecting-your-model
|
||||
- `docs/troubleshooting.mdx` → https://docs.cline.bot/troubleshooting
|
||||
- Headings become anchors: `## Which Model` → `#which-model`
|
||||
|
||||
Search the `docs/` directory to find relevant documentation, then construct URLs to link users to:
|
||||
```bash
|
||||
ls docs/
|
||||
grep -r "keyword" docs/ --include="*.mdx" -l
|
||||
```
|
||||
|
||||
### Identify subject matter experts
|
||||
|
||||
For issues that clearly need engineering attention:
|
||||
git log --since="6 months ago" --format="%an" -- <relevant paths> | sort | uniq -c | sort -rn | head -5
|
||||
|
||||
Cross-reference with GitHub usernames. Include in your response (@mention, do NOT assign):
|
||||
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username1 | Authored PR #X which modified this area |
|
||||
| @username2 | Primary contributor to affected file |
|
||||
|
||||
## Weak model detection
|
||||
|
||||
Many issues are caused by users running small or non-frontier models that don't tool-call reliably. Signs include:
|
||||
- Model failing to use tools correctly
|
||||
- Nonsensical or malformed responses
|
||||
- User is running a small/local model or older model version
|
||||
|
||||
If this looks like a weak model issue, kindly suggest they try reproducing with Claude Sonnet and report back if it persists. Link to https://docs.cline.bot/getting-started/selecting-your-model if helpful. Still label and triage normally.
|
||||
|
||||
## Your comment
|
||||
|
||||
Write a single comment as a helpful community member. Be conversational, not robotic. Include what's relevant:
|
||||
|
||||
- **Helpful response** - Answer their question, suggest a fix, provide a workaround. If you found solutions in related closed issues, surface those prominently.
|
||||
- **Duplicates and related issues** - Link to any you found and explain why they're duplicates/related. Summarize useful context from their comments.
|
||||
- **Regression analysis** - If this looks like a regression, explain what change likely caused it, link to the PR/commit, and tag the author.
|
||||
- **Clarifying questions** - If you need more info, ask specific questions. Don't ask for things already provided.
|
||||
- **SME table** - Include the table above if this needs engineering attention. Don't tag people for questions with obvious answers or weak-model issues.
|
||||
- **Context for maintainers** - Relevant code paths, what you found. Keep it concise.
|
||||
- **Docs links** - If there's relevant documentation, link to it naturally in your response as a recommendation (e.g., "For more details, check out [the Ollama setup guide](url)"). Do NOT add a "Sources" section at the end - integrate doc links into your response where they're helpful.
|
||||
- **Possible Duplicates section** - ALWAYS include a "Possible Duplicates" section at the end of your comment listing issues that might be duplicates so maintainers can quickly close if appropriate. If none found, say "No obvious duplicates found."
|
||||
|
||||
## Labels
|
||||
First, retrieve all available labels and read their descriptions to understand what each is for:
|
||||
gh label list --json name,description --limit 100
|
||||
|
||||
Then apply the appropriate labels based on your analysis. Only use labels from the list above—do not create new labels.
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "label1,label2"
|
||||
|
||||
If your regression analysis found a likely culprit (a recent PR/commit that probably caused this issue), add the "Regression" label:
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Regression"
|
||||
|
||||
IMPORTANT: After posting your comment, add the "Bot Responded" label to indicate this issue has received an automated response:
|
||||
gh issue edit ${{ github.event.issue.number || inputs.issue_number }} --add-label "Bot Responded"
|
||||
|
||||
## Remember
|
||||
|
||||
- **This is a one-time automated response** - you will NOT see their reply or respond again. Never say things like "I can help you", "let me know", "once I have that info", or "I can give you more targeted help" - you won't be there to follow up. If you ask clarifying questions, frame them for the maintainers who will follow up, e.g., "If you can share X, that would help the maintainers diagnose this."
|
||||
- Don't be formulaic. Respond to what the issue actually needs.
|
||||
- Surface solutions from past issues - often the fastest path to helping.
|
||||
- Connecting regressions to specific changes is extremely valuable.
|
||||
- Link issues with #number so they're clickable.
|
||||
@@ -0,0 +1,272 @@
|
||||
name: Claude PR Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, ready_for_review]
|
||||
# Manual trigger for backfilling existing PRs. Run from terminal:
|
||||
# gh workflow run claude-pr-review.yml -f pr_number=1234
|
||||
# Or batch process open PRs:
|
||||
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
|
||||
# gh workflow run claude-pr-review.yml -f pr_number=$num
|
||||
# sleep 60
|
||||
# done
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to review'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
claude-pr-review:
|
||||
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
|
||||
if: |
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 120
|
||||
|
||||
# SECURITY: These permissions are intentionally restrictive.
|
||||
# - contents: read -> Claude can read the codebase but CANNOT write/push any code
|
||||
# - pull-requests: write -> Claude can post reviews and inline suggestions
|
||||
# - issues: read -> Claude can search for related issues
|
||||
# NOTE: Even with pull-requests: write, Claude CANNOT merge PRs because branch protection
|
||||
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get PR number
|
||||
id: pr
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run PR Review
|
||||
id: review
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
allowed_non_write_users: "*"
|
||||
claude_args: --model claude-opus-4-5-20251101 --allowedTools "Bash,Read,Write,Edit,Glob,Grep,WebFetch,WebSearch"
|
||||
prompt: |
|
||||
You're a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
|
||||
|
||||
PR: #${{ steps.pr.outputs.number }}
|
||||
|
||||
## Gather context
|
||||
|
||||
```bash
|
||||
# Get full PR details
|
||||
gh pr view ${{ steps.pr.outputs.number }} --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
|
||||
|
||||
# Get the diff
|
||||
gh pr diff ${{ steps.pr.outputs.number }}
|
||||
|
||||
# Check CI status
|
||||
gh pr checks ${{ steps.pr.outputs.number }}
|
||||
|
||||
# Get existing review comments (to understand context and your previous feedback)
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments --jq '.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'
|
||||
|
||||
# Get conversation comments
|
||||
gh pr view ${{ steps.pr.outputs.number }} --comments
|
||||
```
|
||||
|
||||
If this is a re-review (workflow_dispatch event):
|
||||
Read your previous comments carefully. Understand what you asked for before.
|
||||
Check if new commits or comments address your previous feedback.
|
||||
|
||||
## Check contributing guidelines
|
||||
|
||||
Flag (but don't block) if:
|
||||
- Missing changeset - For user-facing changes, check if there's a `.changeset/` file:
|
||||
```bash
|
||||
gh pr diff ${{ steps.pr.outputs.number }} --name-only | grep '.changeset/' || echo "No changeset found"
|
||||
```
|
||||
If missing, ask them to run `npm run changeset`
|
||||
- Missing tests - New features should have tests
|
||||
|
||||
## Find related issues and PRs
|
||||
|
||||
Search thoroughly for context that might help with the review:
|
||||
|
||||
```bash
|
||||
# Find related issues for context
|
||||
gh issue list --search "<keywords from the PR>" --state all --limit 30
|
||||
gh issue list --search "<error messages or feature names>" --state all --limit 20
|
||||
|
||||
# Find similar PRs for reference
|
||||
gh pr list --search "<keywords>" --state all --limit 30
|
||||
```
|
||||
|
||||
For each relevant issue or PR you find, read it including comments:
|
||||
```bash
|
||||
gh issue view <number> --comments
|
||||
gh pr view <number> --comments
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Open issues this PR might fix that weren't linked in the description
|
||||
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
|
||||
- Context from maintainer discussions that could inform your review
|
||||
|
||||
## Find subject matter experts
|
||||
|
||||
For files changed in this PR, find who knows the code best:
|
||||
```bash
|
||||
# Get files changed
|
||||
gh pr diff ${{ steps.pr.outputs.number }} --name-only
|
||||
|
||||
# For each relevant path, find contributors
|
||||
git log --since="6 months ago" --format="%an" -- <path> | sort | uniq -c | sort -rn | head -5
|
||||
```
|
||||
|
||||
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
|
||||
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username1 | Authored PR #X which modified this area |
|
||||
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
|
||||
| @username3 | Reviewed similar PR #Y with extensive feedback |
|
||||
|
||||
## Deep code review
|
||||
|
||||
This is the most important part. Don't just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
|
||||
|
||||
Step 1: Understand the intent
|
||||
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
|
||||
|
||||
Step 2: Form your own opinion first
|
||||
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
|
||||
|
||||
Step 3: Compare approaches
|
||||
Now look at their implementation. How does it compare to what you would have done?
|
||||
- Is their approach better in some ways? Note what they did well.
|
||||
- Is their approach missing something? Be specific about what and why.
|
||||
- Are there edge cases they haven't considered?
|
||||
- Does it follow the patterns established in similar parts of the codebase?
|
||||
|
||||
Step 4: Look at the bigger picture
|
||||
- What other files or systems does this change interact with?
|
||||
- Could this break anything else?
|
||||
- Is there additional work needed beyond this PR to complete the feature?
|
||||
- Does this fit well with the overall architecture?
|
||||
|
||||
Step 5: Find reference implementations
|
||||
Look for similar changes in the codebase:
|
||||
```bash
|
||||
git log --oneline --all --grep="<relevant keywords>" | head -20
|
||||
git log --oneline -- <similar files> | head -20
|
||||
```
|
||||
|
||||
If this is adding a new API provider, look at how other providers are implemented.
|
||||
If this is adding a new feature, look at how similar features were added.
|
||||
Note where their implementation aligns with or diverges from established patterns.
|
||||
|
||||
Step 6: Standard code review checks
|
||||
- DRY: Is there duplicated code that could be extracted?
|
||||
- Error handling: Are errors handled appropriately?
|
||||
- Security: Any injection risks, credential exposure, unsafe dependencies?
|
||||
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
|
||||
- Types: Is TypeScript used correctly? Any unsafe type assertions?
|
||||
- Naming: Are variables and functions named clearly?
|
||||
- Comments: Is complex logic explained? Are there outdated comments?
|
||||
|
||||
## Inline code suggestions
|
||||
|
||||
For specific code improvements, use GitHub's suggestion syntax via `gh api`.
|
||||
This creates suggestions the author can commit with one click.
|
||||
|
||||
Single-line suggestion:
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
|
||||
-f event="COMMENT" \
|
||||
-f body="" \
|
||||
-F comments='[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"line": 42,
|
||||
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
Multi-line suggestion (replacing lines 40-45):
|
||||
```bash
|
||||
gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view ${{ steps.pr.outputs.number }} --json headRefOid -q .headRefOid)" \
|
||||
-f event="COMMENT" \
|
||||
-f body="" \
|
||||
-F comments='[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"start_line": 40,
|
||||
"line": 45,
|
||||
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
|
||||
|
||||
## Post your review
|
||||
|
||||
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
|
||||
|
||||
Start with a warm thank you for their contribution. Be conversational, not robotic.
|
||||
|
||||
Include what's relevant:
|
||||
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author's intent, why they made the changes, how they implemented it, and what files/systems are affected. Don't just summarize - explain.
|
||||
- Related issues/PRs you found that provide useful context (link to them)
|
||||
- Your review findings (issues to address, suggestions, etc.)
|
||||
- Clear next steps for the author
|
||||
|
||||
Include a "For Maintainers" section with:
|
||||
- Anything else useful to help the maintainer resolve this PR
|
||||
- Related issues/PRs with context on why they're relevant
|
||||
- Open issues this PR might fix that weren't linked in the description
|
||||
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
|
||||
- SME table - who should review this and why
|
||||
|
||||
For the SME table:
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username | Primary contributor to affected files |
|
||||
|
||||
## Update labels
|
||||
|
||||
Add appropriate labels based on your analysis:
|
||||
```bash
|
||||
gh label list --json name,description --limit 100
|
||||
gh pr edit ${{ steps.pr.outputs.number }} --add-label "label1,label2"
|
||||
```
|
||||
|
||||
When done, add the reviewed label:
|
||||
```bash
|
||||
gh pr edit ${{ steps.pr.outputs.number }} --add-label "Bot Reviewed"
|
||||
```
|
||||
|
||||
## Remember
|
||||
|
||||
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like "let me know if you have questions", "I can help you with", or "feel free to ask" - you won't be there to follow up. Frame any questions for the maintainers who will follow up.
|
||||
- Be helpful and welcoming - Many contributors are new to the project
|
||||
- Be specific - Point to exact lines and suggest fixes, don't give vague feedback
|
||||
- Think deeply - Don't just surface-level review, understand the intent and evaluate the approach
|
||||
- Use inline suggestions - Make it easy for authors to accept changes
|
||||
- You're a first-pass reviewer - A human maintainer will do final approval
|
||||
@@ -0,0 +1,312 @@
|
||||
name: Cline PR Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
[opened, ready_for_review]
|
||||
# Manual trigger for backfilling existing PRs. Run from terminal:
|
||||
# gh workflow run cline-pr-review.yml -f pr_number=1234
|
||||
# Or batch process open PRs:
|
||||
# gh pr list --state open --limit 10 --json number --jq '.[].number' | while read num; do
|
||||
# gh workflow run cline-pr-review.yml -f pr_number=$num
|
||||
# sleep 60
|
||||
# done
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: "PR number to review"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: pr-review-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
cline-pr-review:
|
||||
# Runs on PR opened/ready_for_review (skips drafts) or manual trigger for backfilling
|
||||
if: |
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
# SECURITY: These permissions are intentionally restrictive.
|
||||
# - contents: read -> cline can read the codebase but CANNOT write/push any code
|
||||
# - pull-requests: write -> cline can post reviews and inline suggestions
|
||||
# - issues: read -> cline can search for related issues
|
||||
# NOTE: Even with pull-requests: write, cline CANNOT merge PRs because branch protection
|
||||
# requires 1 approval from a Code Owner. The GITHUB_TOKEN cannot bypass this.
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: read
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "npm"
|
||||
|
||||
- name: Install and Verify Cline CLI
|
||||
run: |
|
||||
npx cline version # verify installation
|
||||
|
||||
- name: Configure Cline with Anthropic
|
||||
run: |
|
||||
npx cline auth --provider anthropic \
|
||||
--apikey "${{ secrets.ANTHROPIC_API_KEY }}" \
|
||||
--modelid claude-opus-4-5-20251101
|
||||
|
||||
- name: Get PR number
|
||||
id: pr
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Review PR with Cline
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pr.outputs.number }}
|
||||
GITHUB_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
CLINE_COMMAND_PERMISSIONS: |
|
||||
{
|
||||
"allow": [
|
||||
"gh pr diff *",
|
||||
"gh pr view *",
|
||||
"gh pr checks *",
|
||||
"gh pr list *",
|
||||
"gh label list *",
|
||||
"gh issue list *",
|
||||
"gh issue view *",
|
||||
"git log *",
|
||||
"gh pr comment ${{ steps.pr.outputs.number }} *",
|
||||
"gh pr edit ${{ steps.pr.outputs.number }} *",
|
||||
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/comments *",
|
||||
"gh api repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.number }}/reviews *"
|
||||
]
|
||||
}
|
||||
run: |
|
||||
npx cline --yolo 'You'\''re a GitHub PR reviewer for the open source Cline repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
|
||||
|
||||
PR: #'"${PR_NUMBER}"'
|
||||
|
||||
## Gather context
|
||||
|
||||
```bash
|
||||
# Get full PR details
|
||||
gh pr view '"${PR_NUMBER}"' --json number,title,body,author,createdAt,updatedAt,isDraft,labels,commits,files,additions,deletions,changedFiles,baseRefName,headRefName,mergeable,reviewDecision
|
||||
|
||||
# Get the diff
|
||||
gh pr diff '"${PR_NUMBER}"'
|
||||
|
||||
# Check CI status
|
||||
gh pr checks '"${PR_NUMBER}"'
|
||||
|
||||
# Get existing review comments (to understand context and your previous feedback)
|
||||
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/comments --jq '\''.[] | {user: .user.login, body: .body, path: .path, created_at: .created_at}'\''
|
||||
|
||||
# Get conversation comments
|
||||
gh pr view '"${PR_NUMBER}"' --comments
|
||||
```
|
||||
|
||||
If this is a re-review (workflow_dispatch event):
|
||||
Read your previous comments carefully. Understand what you asked for before.
|
||||
Check if new commits or comments address your previous feedback.
|
||||
|
||||
## Check contributing guidelines
|
||||
|
||||
Flag (but don'\''t block) if:
|
||||
- Missing changeset - For user-facing changes, check if there'\''s a `.changeset/` file:
|
||||
```bash
|
||||
gh pr diff '"${PR_NUMBER}"' --name-only | grep '\''.changeset/'\'' || echo '\''No changeset found'\''
|
||||
```
|
||||
If missing, ask them to run `npm run changeset`
|
||||
- Missing tests - New features should have tests
|
||||
|
||||
## Find related issues and PRs
|
||||
|
||||
Search thoroughly for context that might help with the review:
|
||||
|
||||
```bash
|
||||
# Find related issues for context
|
||||
gh issue list --search '\''<keywords from the PR>'\'' --state all --limit 30
|
||||
gh issue list --search '\''<error messages or feature names>'\'' --state all --limit 20
|
||||
|
||||
# Find similar PRs for reference
|
||||
gh pr list --search '\''<keywords>'\'' --state all --limit 30
|
||||
```
|
||||
|
||||
For each relevant issue or PR you find, read it including comments:
|
||||
```bash
|
||||
gh issue view <number> --comments
|
||||
gh pr view <number> --comments
|
||||
```
|
||||
|
||||
Look for:
|
||||
- Open issues this PR might fix that weren'\''t linked in the description
|
||||
- Similar PRs that went through review - what feedback did they get? What patterns did they follow?
|
||||
- Context from maintainer discussions that could inform your review
|
||||
|
||||
## Find subject matter experts
|
||||
|
||||
For files changed in this PR, find who knows the code best:
|
||||
```bash
|
||||
# Get files changed
|
||||
gh pr diff '"${PR_NUMBER}"' --name-only
|
||||
|
||||
# For each relevant path, find contributors
|
||||
git log --since='\''6 months ago'\'' --format='\''%an'\'' -- <path> | sort | uniq -c | sort -rn | head -5
|
||||
```
|
||||
|
||||
Cross-reference git authors with GitHub usernames. Include an SME table in your response:
|
||||
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username1 | Authored PR #X which modified this area |
|
||||
| @username2 | Primary contributor to affected file (15 commits in 6 months) |
|
||||
| @username3 | Reviewed similar PR #Y with extensive feedback |
|
||||
|
||||
## Bash command usage
|
||||
|
||||
Don'\''t use operators like `|`, `&&`, or `;` - run each command separately and analyze the output.
|
||||
|
||||
When referencing command outputs, quote them properly to avoid formatting issues.
|
||||
|
||||
## Deep code review
|
||||
|
||||
This is the most important part. Don'\''t just look for syntax issues - understand what the PR is trying to achieve and whether the implementation is the right approach.
|
||||
|
||||
Step 1: Understand the intent
|
||||
Read the PR description and understand what the author is trying to accomplish. What problem are they solving? What feature are they adding?
|
||||
|
||||
Step 2: Form your own opinion first
|
||||
Before analyzing their code, think about how YOU would implement this feature or fix. What files would you touch? What patterns would you follow? What edge cases would you handle?
|
||||
|
||||
Step 3: Compare approaches
|
||||
Now look at their implementation. How does it compare to what you would have done?
|
||||
- Is their approach better in some ways? Note what they did well.
|
||||
- Is their approach missing something? Be specific about what and why.
|
||||
- Are there edge cases they haven'\''t considered?
|
||||
- Does it follow the patterns established in similar parts of the codebase?
|
||||
|
||||
Step 4: Look at the bigger picture
|
||||
- What other files or systems does this change interact with?
|
||||
- Could this break anything else?
|
||||
- Is there additional work needed beyond this PR to complete the feature?
|
||||
- Does this fit well with the overall architecture?
|
||||
|
||||
Step 5: Find reference implementations
|
||||
Look for similar changes in the codebase:
|
||||
```bash
|
||||
git log --oneline --all --grep='\''<relevant keywords>'\'' | head -20
|
||||
git log --oneline -- <similar files> | head -20
|
||||
```
|
||||
|
||||
If this is adding a new API provider, look at how other providers are implemented.
|
||||
If this is adding a new feature, look at how similar features were added.
|
||||
Note where their implementation aligns with or diverges from established patterns.
|
||||
|
||||
Step 6: Standard code review checks
|
||||
- DRY: Is there duplicated code that could be extracted?
|
||||
- Error handling: Are errors handled appropriately?
|
||||
- Security: Any injection risks, credential exposure, unsafe dependencies?
|
||||
- Performance: Any obvious inefficiencies, memory leaks, N+1 patterns?
|
||||
- Types: Is TypeScript used correctly? Any unsafe type assertions?
|
||||
- Naming: Are variables and functions named clearly?
|
||||
- Comments: Is complex logic explained? Are there outdated comments?
|
||||
|
||||
## Inline code suggestions
|
||||
|
||||
For specific code improvements, use GitHub'\''s suggestion syntax via `gh api`.
|
||||
This creates suggestions the author can commit with one click.
|
||||
|
||||
Single-line suggestion:
|
||||
```bash
|
||||
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
|
||||
-f event='\''COMMENT'\'' \
|
||||
-f body='\'''\'' \
|
||||
-F comments='\''[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"line": 42,
|
||||
"body": "Consider simplifying:\n\n```suggestion\nconst result = items.filter(Boolean);\n```"
|
||||
}
|
||||
]'\''
|
||||
```
|
||||
|
||||
Multi-line suggestion (replacing lines 40-45):
|
||||
```bash
|
||||
gh api repos/'"${GITHUB_REPO}"'/pulls/'"${PR_NUMBER}"'/reviews \
|
||||
-X POST \
|
||||
-f commit_id="$(gh pr view '"${PR_NUMBER}"' --json headRefOid -q .headRefOid)" \
|
||||
-f event='\''COMMENT'\'' \
|
||||
-f body='\'''\'' \
|
||||
-F comments='\''[
|
||||
{
|
||||
"path": "src/example.ts",
|
||||
"start_line": 40,
|
||||
"line": 45,
|
||||
"body": "This can be simplified:\n\n```suggestion\nconst simplified = doThing();\n```"
|
||||
}
|
||||
]'\''
|
||||
```
|
||||
|
||||
Use inline suggestions for concrete improvements. Use regular comments for questions or broader feedback.
|
||||
|
||||
## Post your review
|
||||
|
||||
After your investigation, post a single helpful comment that helps the author and gives maintainers context.
|
||||
|
||||
Start with a warm thank you for their contribution. Be conversational, not robotic.
|
||||
|
||||
Include what'\''s relevant:
|
||||
- In-depth explanation of what the PR does - Be comprehensive. A maintainer should be able to read this section and fully understand the author'\''s intent, why they made the changes, how they implemented it, and what files/systems are affected. Don'\''t just summarize - explain.
|
||||
- Related issues/PRs you found that provide useful context (link to them)
|
||||
- Your review findings (issues to address, suggestions, etc.)
|
||||
- Clear next steps for the author
|
||||
|
||||
Include a '\''For Maintainers'\'' section with:
|
||||
- Anything else useful to help the maintainer resolve this PR
|
||||
- Related issues/PRs with context on why they'\''re relevant
|
||||
- Open issues this PR might fix that weren'\''t linked in the description
|
||||
- Your recommendation: merge as-is, needs changes, needs discussion, close, etc.
|
||||
- SME table - who should review this and why
|
||||
|
||||
For the SME table:
|
||||
| SME | Reason |
|
||||
|-----|--------|
|
||||
| @username | Primary contributor to affected files |
|
||||
|
||||
## Update labels
|
||||
|
||||
Add appropriate labels based on your analysis:
|
||||
```bash
|
||||
gh label list --json name,description --limit 100
|
||||
gh pr edit '"${PR_NUMBER}"' --add-label '\''label1,label2'\''
|
||||
```
|
||||
|
||||
When done, add the reviewed label:
|
||||
```bash
|
||||
gh pr edit '"${PR_NUMBER}"' --add-label '\''Bot Reviewed'\''
|
||||
```
|
||||
|
||||
## Remember
|
||||
|
||||
- This is a one-time automated response - you will NOT see their reply or respond again. Never say things like '\''let me know if you have questions'\'', '\''I can help you with'\'', or '\''feel free to ask'\'' - you won'\''t be there to follow up. Frame any questions for the maintainers who will follow up.
|
||||
- Be helpful and welcoming - Many contributors are new to the project
|
||||
- Be specific - Point to exact lines and suggest fixes, don'\''t give vague feedback
|
||||
- Think deeply - Don'\''t just surface-level review, understand the intent and evaluate the approach
|
||||
- Use inline suggestions - Make it easy for authors to accept changes
|
||||
- You'\''re a first-pass reviewer - A human maintainer will do final approval'
|
||||
@@ -0,0 +1,130 @@
|
||||
name: Publish NPM Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
confirm_publish:
|
||||
description: 'Type "publish" to confirm you want to publish to NPM'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # Required by test workflow
|
||||
pull-requests: write # Required by test workflow
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
publish-npm-release:
|
||||
needs: test
|
||||
name: Publish Cline CLI to NPM
|
||||
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && github.event.inputs.confirm_publish == 'publish'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache-dependency-path: cli/go.sum
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
|
||||
- name: Read release version
|
||||
id: version
|
||||
run: |
|
||||
# Read version from cli/package.json (stable version)
|
||||
VERSION=$(node -p "require('./cli/package.json').version")
|
||||
echo "Release version: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download ripgrep binaries
|
||||
run: npm run download-ripgrep
|
||||
|
||||
- name: Clean previous builds
|
||||
run: rm -rf dist-standalone
|
||||
|
||||
- name: Generate Protos (First Pass)
|
||||
run: npm run protos && npm run protos-go
|
||||
|
||||
- name: Compile CLI
|
||||
run: npm run compile-cli
|
||||
|
||||
- name: Compile CLI for all platforms
|
||||
run: npm run compile-cli-all-platforms
|
||||
|
||||
- name: Build standalone NPM package
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
OTEL_TELEMETRY_ENABLED: "1"
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_LOGS_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 }}
|
||||
POSTHOG_TELEMETRY_ENABLED: "true"
|
||||
run: npm run compile-standalone-npm
|
||||
|
||||
- name: Generate Protos (Second Pass - Bug Workaround)
|
||||
run: npm run protos && npm run protos-go
|
||||
|
||||
- name: Verify build output
|
||||
run: |
|
||||
echo "Checking dist-standalone directory..."
|
||||
ls -la dist-standalone/
|
||||
|
||||
echo "Verifying CLI binaries..."
|
||||
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
|
||||
|
||||
echo "Checking package.json in dist-standalone..."
|
||||
cat dist-standalone/package.json | grep version
|
||||
|
||||
- name: Publish to NPM with latest tag
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'latest'..."
|
||||
cd dist-standalone
|
||||
npm publish --tag latest --access public
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'latest'"
|
||||
echo ""
|
||||
echo "📦 Install with: npm install -g cline"
|
||||
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
|
||||
@@ -0,0 +1,175 @@
|
||||
name: Publish NPM Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # 4 AM PST (UTC-8) = 12 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write # Required by test workflow
|
||||
pull-requests: write # Required by test workflow
|
||||
|
||||
jobs:
|
||||
test:
|
||||
uses: ./.github/workflows/test.yml
|
||||
|
||||
publish-npm-nightly:
|
||||
needs: test
|
||||
name: Publish Cline CLI (Nightly) to NPM
|
||||
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for recent commits
|
||||
id: check_commits
|
||||
run: |
|
||||
if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then
|
||||
echo "No commits in last 24 hours, skipping publish"
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Found recent commits, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Setup Go
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
cache-dependency-path: cli/go.sum
|
||||
|
||||
# Cache root dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache root dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/cache@v4
|
||||
id: root-cache
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
|
||||
- name: Cache webview-ui dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
uses: actions/cache@v4
|
||||
id: webview-cache
|
||||
with:
|
||||
path: webview-ui/node_modules
|
||||
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
|
||||
|
||||
- name: Install root dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true' && steps.root-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
if: steps.check_commits.outputs.skip != 'true' && steps.webview-cache.outputs.cache-hit != 'true'
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
|
||||
- name: Generate nightly version with timestamp
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
id: version
|
||||
run: |
|
||||
# Read base version from cli/package.json (e.g., "1.0.9")
|
||||
BASE_VERSION=$(node -p "require('./cli/package.json').version")
|
||||
|
||||
# Generate timestamp (Unix epoch seconds)
|
||||
TIMESTAMP=$(date +%s)
|
||||
|
||||
# Create unique nightly version: 1.0.9-nightly.1736365200
|
||||
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
|
||||
|
||||
echo "Base version: $BASE_VERSION"
|
||||
echo "Generated nightly version: $VERSION"
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update cli/package.json with nightly version
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
# Update version with timestamp-based nightly version
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = JSON.parse(fs.readFileSync('cli/package.json', 'utf8'));
|
||||
pkg.version = '${{ steps.version.outputs.version }}';
|
||||
fs.writeFileSync('cli/package.json', JSON.stringify(pkg, null, '\t'));
|
||||
"
|
||||
|
||||
echo "Using version ${{ steps.version.outputs.version }} for build"
|
||||
cat cli/package.json | grep '"version"'
|
||||
|
||||
- name: Download ripgrep binaries
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: npm run download-ripgrep
|
||||
|
||||
- name: Clean previous builds
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: rm -rf dist-standalone
|
||||
|
||||
- name: Generate Protos (First Pass)
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: npm run protos && npm run protos-go
|
||||
|
||||
- name: Compile CLI
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: npm run compile-cli
|
||||
|
||||
- name: Compile CLI for all platforms
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: npm run compile-cli-all-platforms
|
||||
|
||||
- name: Build standalone NPM package
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
OTEL_TELEMETRY_ENABLED: "1"
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_LOGS_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 }}
|
||||
POSTHOG_TELEMETRY_ENABLED: "true"
|
||||
run: npm run compile-standalone-npm
|
||||
|
||||
- name: Generate Protos (Second Pass - Bug Workaround)
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: npm run protos && npm run protos-go
|
||||
|
||||
- name: Verify build output
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
echo "Checking dist-standalone directory..."
|
||||
ls -la dist-standalone/
|
||||
|
||||
echo "Verifying CLI binaries..."
|
||||
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
|
||||
|
||||
echo "Checking package.json in dist-standalone..."
|
||||
cat dist-standalone/package.json | grep version
|
||||
|
||||
- name: Publish to NPM with nightly tag
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_RELEASE_TOKEN }}
|
||||
run: |
|
||||
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'nightly'..."
|
||||
cd dist-standalone
|
||||
npm publish --tag nightly --access public
|
||||
|
||||
- name: Summary
|
||||
if: steps.check_commits.outputs.skip != 'true'
|
||||
run: |
|
||||
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'nightly'"
|
||||
echo ""
|
||||
echo "📦 Install with: npm install -g cline@nightly"
|
||||
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
|
||||
@@ -36,6 +36,8 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
@@ -116,22 +118,31 @@ jobs:
|
||||
echo "Successfully published release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
fi
|
||||
|
||||
# - name: Get Changelog Entry
|
||||
# id: changelog
|
||||
# uses: mindsers/changelog-reader-action@v2
|
||||
# with:
|
||||
# # This expects a standard Keep a Changelog format
|
||||
# # "latest" means it will read whichever is the most recent version
|
||||
# # set in "## [1.2.3] - 2025-01-28" style
|
||||
# version: latest
|
||||
- name: Get Previous Tag
|
||||
id: prev_tag
|
||||
run: |
|
||||
CURRENT_TAG="${{ steps.validate_tag.outputs.tag }}"
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG^" 2>/dev/null || echo "")
|
||||
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get Changelog Entry
|
||||
id: changelog
|
||||
run: |
|
||||
# Get content between first ## [ and second ## [
|
||||
CONTENT=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CONTENT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.validate_tag.outputs.tag }}
|
||||
files: "*.vsix"
|
||||
# body: ${{ steps.changelog.outputs.content }}
|
||||
generate_release_notes: true
|
||||
body: |
|
||||
${{ steps.changelog.outputs.content }}
|
||||
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.validate_tag.outputs.tag }}
|
||||
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+11
@@ -8,12 +8,14 @@ tmp
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
.husky/_/
|
||||
|
||||
pnpm-lock.yaml
|
||||
|
||||
.clineignore
|
||||
.venv
|
||||
.actrc
|
||||
CLAUDE.local.md
|
||||
|
||||
webview-ui/src/**/*.js
|
||||
webview-ui/src/**/*.js.map
|
||||
@@ -27,6 +29,10 @@ coverage-unit
|
||||
|
||||
*evals.env
|
||||
.env
|
||||
.secrets
|
||||
.github/act/.secrets
|
||||
|
||||
.worktrees
|
||||
|
||||
## Generated files ##
|
||||
src/generated/
|
||||
@@ -35,3 +41,8 @@ webview-ui/src/services/grpc-client.ts
|
||||
|
||||
# E2E Tests
|
||||
test-results
|
||||
|
||||
/.github/act
|
||||
/pkg
|
||||
.secrets
|
||||
|
||||
|
||||
Vendored
+16
-4
@@ -12,7 +12,10 @@
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extensions", // Avoid conflicts with installed extensions
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
@@ -33,7 +36,10 @@
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extensions", // Avoid conflicts with installed extensions
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
@@ -54,7 +60,10 @@
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--disable-workspace-trust",
|
||||
"--disable-extensions", // Avoid conflicts with installed extensions
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
"outFiles": [
|
||||
@@ -77,7 +86,10 @@
|
||||
"--user-data-dir=${workspaceFolder}/dist/tmp/user",
|
||||
"--profile-temp",
|
||||
"--sync=off",
|
||||
"--disable-extensions", // Avoid conflicts with installed extensions
|
||||
"--disable-extension",
|
||||
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
|
||||
"--disable-extension",
|
||||
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}"
|
||||
],
|
||||
|
||||
Vendored
+3
-1
@@ -27,5 +27,7 @@
|
||||
"source.removeUnused.biome": "always",
|
||||
"source.removeUnusedImports": "always",
|
||||
"source.organizeImports.biome": "always"
|
||||
}
|
||||
},
|
||||
// Disable auto-forwarding ports to prevent Simple Browser from opening the Vite dev server
|
||||
"remote.autoForwardPorts": false
|
||||
}
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
+129
@@ -1,5 +1,134 @@
|
||||
# Changelog
|
||||
|
||||
## [3.49.0]
|
||||
|
||||
- Enable configuring an OTEL collector at runtime
|
||||
- Removing Minimax-2.1 from free model list as the free trial has ended
|
||||
- Improved image display in MCP responses
|
||||
- Auto-sync remote MCP servers from remote config to local settings
|
||||
|
||||
|
||||
## [3.48.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add Skills system for reusable, on-demand agent instructions
|
||||
- Add new websearch tooling in Cline provider
|
||||
- Add zai-glm-4.7 to Cerebras model list
|
||||
- Add model refresh and improve reasoning support for Vercel AI Gateway
|
||||
|
||||
### Fixed
|
||||
|
||||
- Revert #8341 due to regressions in diff view/document truncation (see #8423, #8429)
|
||||
- Fixed extension crash when using context menu selector
|
||||
|
||||
## [3.47.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added experimental support for Background Edits (allows editing files in background without opening the diff view)
|
||||
- Updated free model to MiniMax M2.1 (replacing MiniMax M2)
|
||||
- Added support for Azure based identity authentication in OpenAI Compatible provider and Azure OpenAI
|
||||
- Add `supportsReasoning` property to Baseten models
|
||||
|
||||
### Fixed
|
||||
|
||||
- Prevent expired token usage in authenticated requests
|
||||
- Exclude binary files without extensions from diffs
|
||||
- Preserve file endings and trailing newlines
|
||||
- Fix Cerebras rate limiting
|
||||
- Fix Auto Compact for Claude Code provider
|
||||
- Make Workspace and Favorites history filters independent
|
||||
- Fix remote MCP server connection failures (404 response handling)
|
||||
- Disable native tool calling for Deepseek 3.2 speciale
|
||||
- Show notification instead of opening sidebar on update
|
||||
- Fix Baseten model selector
|
||||
|
||||
### Refactored
|
||||
|
||||
- Modify prompts for parallel tool usage in Claude and Gemini 3 models
|
||||
|
||||
## [3.46.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove GLM 4.6 from free models
|
||||
|
||||
## [3.46.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added GLM 4.7 model
|
||||
- Enhanced background terminal execution with command tracking, log file output, zombie process prevention (10-minute timeout), and clickable log paths in UI
|
||||
- Apply Patch tool for GPT-5+ models (replacing current diff edit tools)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Duplicate error messages during streaming for Diff Edit tool when Parallel Tool Calling is not enabled
|
||||
- Banner carousel styling and dismiss functionality
|
||||
- Typos in Gemini system prompt overrides
|
||||
- Model picker favorites ordering, star toggle, and keyboard navigation for OpenRouter and Vercel AI Gateway providers
|
||||
- Fetch remote config values from the cache
|
||||
|
||||
### Refactored
|
||||
|
||||
- Anthropic handler to use metadata for reasoning support
|
||||
- Bedrock provider to use metadata for reasoning support
|
||||
|
||||
## [3.45.1]
|
||||
|
||||
- Fixed MCP settings race condition where toggling auto-approve or changing timeout settings would cause the UI to flash and revert
|
||||
|
||||
## [3.45.0]
|
||||
|
||||
- Added Gemini 3 Flash Preview model
|
||||
|
||||
## [3.44.2]
|
||||
|
||||
- Polished the model picker UI with checkmarks for selected models, tooltips on Plan/Act tabs, and consistent arrow pointers across all popup modals
|
||||
- Improved WhatsNew modal responsiveness and cleaned up redundant UI elements
|
||||
- Fixed GLM models outputting garbled text in thinking tags—reasoning is now properly disabled for these models
|
||||
|
||||
## [3.44.1]
|
||||
|
||||
- Fixed a critical bug where local MCP servers stopped connecting after v3.42.0—all user-configured stdio-based MCP servers should now work again
|
||||
- Fixed remotely configured API keys not being extracted correctly for enterprise users
|
||||
- Added support for dynamic tool instructions that adapt based on runtime context, laying groundwork for future context-aware features
|
||||
|
||||
## [3.44.0]
|
||||
|
||||
## Added
|
||||
|
||||
- Updating minor version to show a proper banner for the release
|
||||
|
||||
## [3.43.1]
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Fix GLM-4.6 Model reference id
|
||||
|
||||
## [3.43.0]
|
||||
|
||||
### Added
|
||||
|
||||
- GLM-4.6
|
||||
- kat-coder-pro
|
||||
- Add parsing of env variable patterns to the mcpconfig.json
|
||||
|
||||
### Fixed
|
||||
|
||||
- TLS Proxy support issues for VSCode
|
||||
- Add supportsReasoning flag to OpenAI reasoning models
|
||||
- Fix thinking not available for some models in the OpenAI provider
|
||||
- Fix invalid signature field issues when switching between Gemini and Anthropic providers
|
||||
- Extract OpenRouter model filtering into reusable utility and use it in different model pickers
|
||||
- Fix a11y for auto approve checkbox
|
||||
- Improve ModelPickerModal provider list layout
|
||||
|
||||
### Refactored
|
||||
|
||||
- Migrate WhatsNewModal to new shared dialogue component
|
||||
|
||||
## [3.42.0]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -14,6 +14,10 @@ This file is the secret sauce for working effectively in this codebase. It captu
|
||||
|
||||
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
|
||||
|
||||
## Miscellaneous
|
||||
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
|
||||
- When creating PRs, if the change is user-facing and significant enough to warrant a changelog entry, run `npm run changeset` and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.
|
||||
|
||||
## gRPC/Protobuf Communication
|
||||
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
|
||||
|
||||
|
||||
@@ -70,3 +70,4 @@ Apache-2.0 - see [LICENSE](https://github.com/cline/cline/blob/main/LICENSE) for
|
||||
- Report issues: [GitHub Issues](https://github.com/cline/cline/issues)
|
||||
- Community: [GitHub Discussions](https://github.com/cline/cline/discussions)
|
||||
- Documentation: [docs.cline.bot](https://docs.cline.bot)
|
||||
- Cline CLI Architecture: [architecture.md](./architecture.md)
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
# Cline CLI Architecture
|
||||
|
||||
The CLI is a **standalone terminal interface** for the Cline AI coding assistant, written in Go. It provides the same autonomous coding capabilities as the VS Code extension but runs entirely in the terminal.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ User Terminal │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ cline (Go binary) │
|
||||
│ cmd/cline/main.go │
|
||||
│ • Cobra CLI commands (task, auth, config, instance, etc.) │
|
||||
│ • Interactive input via Bubble Tea │
|
||||
│ • Streaming output with markdown rendering │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│ gRPC (50052) │ starts subprocess
|
||||
▼ ▼
|
||||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||||
│ cline-core │◄────────────────►│ cline-host │
|
||||
│ (Node.js) │ gRPC (51052) │ (Go binary) │
|
||||
│ │ │ cmd/cline-host/main.go│
|
||||
│ • AI/LLM orchestration │ │ │
|
||||
│ • Tool execution │ │ • Workspace paths │
|
||||
│ • Task state mgmt │ │ • File diff editing │
|
||||
│ • Message handling │ │ • Clipboard access │
|
||||
└─────────────────────────┘ │ • Environment info │
|
||||
│ └─────────────────────────┘
|
||||
│ SQLite (self-registration)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ ~/.cline/data/locks/locks.db │
|
||||
│ (Instance registry - core self-registers on startup) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Entry Points (`cmd/`)
|
||||
|
||||
### `cmd/cline/main.go` - Main CLI
|
||||
|
||||
Cobra-based CLI with commands:
|
||||
|
||||
- **Root**: `cline [prompt]` - Start a task directly
|
||||
- **task**: Create, send, view, list, pause, restore tasks
|
||||
- **auth**: Authentication setup and provider configuration
|
||||
- **config**: Read/write settings
|
||||
- **instance**: Manage running Cline instances
|
||||
- **logs**: View and clean log files
|
||||
- **doctor**: System health check
|
||||
|
||||
### `cmd/cline-host/main.go` - Host Bridge Service
|
||||
|
||||
Separate gRPC server providing host environment operations to cline-core:
|
||||
|
||||
- Workspace paths
|
||||
- File diff editing
|
||||
- Clipboard access
|
||||
- Shutdown coordination
|
||||
|
||||
---
|
||||
|
||||
## `pkg/cli/` Subsystems
|
||||
|
||||
### 1. `auth/` - Authentication System
|
||||
|
||||
Handles authentication with Cline service and BYO (Bring Your Own) API providers.
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------------- | ------------------------------------------------------------------------ |
|
||||
| `auth_cline_provider.go` | OAuth login flow - opens browser, subscribes to auth callback stream |
|
||||
| `auth_menu.go` | Interactive menu showing auth options based on current state |
|
||||
| `auth_subscription.go` | gRPC stream subscription for auth status updates |
|
||||
| `wizard_byo.go` | Interactive wizard for configuring BYO providers |
|
||||
| `wizard_byo_bedrock.go` | AWS Bedrock-specific credential setup |
|
||||
| `wizard_byo_oca.go` | Oracle Code Assist setup |
|
||||
| `providers_list.go` | Retrieves configured providers from core state |
|
||||
| `providers_byo.go` | Provider selection UI and field configuration |
|
||||
| `models_*.go` | Model listing (static lists + dynamic fetch from OpenRouter/OpenAI/Ollama) |
|
||||
|
||||
**Flow**: User runs `cline auth` → Menu shows options → For BYO: wizard guides through provider/key/model selection → Config saved via gRPC to core.
|
||||
|
||||
---
|
||||
|
||||
### 2. `clerror/` - Error Handling
|
||||
|
||||
Parses and classifies API errors from the Cline service.
|
||||
|
||||
**Error Types:**
|
||||
|
||||
- `ErrorTypeAuth` - 401, bad API key
|
||||
- `ErrorTypeBalance` - Insufficient credits
|
||||
- `ErrorTypeRateLimit` - 429, quota exceeded
|
||||
- `ErrorTypeNetwork` - Connection issues
|
||||
- `ErrorTypeUnknown` - Catch-all
|
||||
|
||||
Extracts billing details (balance, spent, buy credits URL) from error responses.
|
||||
|
||||
---
|
||||
|
||||
### 3. `config/` - Configuration Management
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | -------------------------------------------------------------------------- |
|
||||
| `manager.go` | gRPC interface for reading/writing settings via `UpdateSettingsCli` RPC |
|
||||
| `settings_renderer.go`| Pretty-prints config values, censors sensitive fields (keys, secrets) |
|
||||
|
||||
Supports dot-notation paths: `cline config get auto-approval-settings.actions.read-files`
|
||||
|
||||
---
|
||||
|
||||
### 4. `display/` - Terminal Display System
|
||||
|
||||
The most complex subsystem - handles all visual output.
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `renderer.go` | Central coordinator with lipgloss styles, color methods, markdown delegation |
|
||||
| `streaming.go` | Real-time streaming display with deduplication |
|
||||
| `segment_streamer.go` | Streaming segments (header + body) with context-aware headers |
|
||||
| `typewriter.go` | Character-by-character animation with variable delays |
|
||||
| `markdown_renderer.go` | Glamour wrapper for terminal markdown rendering |
|
||||
| `tool_renderer.go` | Tool operation formatting ("Cline is editing `file.ts`") |
|
||||
| `tool_result_parser.go` | Parses structured tool results (file lists, search results) |
|
||||
| `banner.go` | Session startup banner with version/model/workspace |
|
||||
| `deduplicator.go` | MD5-based deduplication with 2-second window |
|
||||
| `system_renderer.go` | Rich error/warning boxes for balance errors, auth failures |
|
||||
| `ansi.go` | TTY detection, line clearing with escape codes |
|
||||
|
||||
---
|
||||
|
||||
### 5. `global/` - Global State Management
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------ | -------------------------------------------------------------------------- |
|
||||
| `global.go` | Global config (paths, verbosity, output format), initialization |
|
||||
| `registry.go` | Instance discovery via SQLite, health checking, default instance management|
|
||||
| `cline-clients.go` | Starts cline-core + cline-host processes, port allocation, cleanup |
|
||||
|
||||
**Instance lifecycle:**
|
||||
|
||||
1. Find available port pair
|
||||
2. Start `cline-host` on port+1000
|
||||
3. Start `cline-core` on port
|
||||
4. Wait for core to self-register in SQLite
|
||||
5. Set as default if first instance
|
||||
|
||||
---
|
||||
|
||||
### 6. `handlers/` - Message Handlers
|
||||
|
||||
Routes incoming messages from cline-core to appropriate renderers.
|
||||
|
||||
| File | Purpose |
|
||||
| ------------------ | --------------------------------------------------------------------- |
|
||||
| `handler.go` | Handler registry with priority-based routing |
|
||||
| `ask_handlers.go` | Approval requests: tool, command, followup, api_req_failed, etc. |
|
||||
| `say_handlers.go` | Status messages: text, reasoning, command_output, tool, checkpoint, etc. |
|
||||
|
||||
Uses `DisplayContext` providing renderer access, state, and context flags (isLast, isPartial, isStreamingMode).
|
||||
|
||||
---
|
||||
|
||||
### 7. `output/` - Output Coordination
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | ----------------------------------------------------------------------- |
|
||||
| `coordinator.go` | Coordinates streaming output with interactive input (saves/restores input state) |
|
||||
| `input_model.go` | Bubble Tea model for rich input (message, approval, feedback types) |
|
||||
| `slash_completion.go` | Autocomplete dropdown for slash commands |
|
||||
|
||||
**Key pattern:** When output needs to print while input is visible, the coordinator saves input state, clears the form, prints, then restores input.
|
||||
|
||||
---
|
||||
|
||||
### 8. `slash/` - Slash Command Registry
|
||||
|
||||
Central registry for commands like `/plan`, `/act`, `/cancel`:
|
||||
|
||||
- **CLI-local commands**: Handled directly by CLI
|
||||
- **Backend commands**: Fetched from core via gRPC, filtered by `CliCompatible` flag
|
||||
|
||||
---
|
||||
|
||||
### 9. `sqlite/` - Instance Locking
|
||||
|
||||
Manages the distributed locking system:
|
||||
|
||||
- **Instance locks**: Track running Cline instances by address
|
||||
- **File locks**: Coordinate file access across instances
|
||||
- SQLite database created by cline-core, CLI reads/writes for discovery
|
||||
|
||||
---
|
||||
|
||||
### 10. `task/` - Task Management
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | -------------------------------------------------------------------- |
|
||||
| `manager.go` | Core orchestrator: create, cancel, resume, restore tasks; stream handling |
|
||||
| `stream_coordinator.go` | Deduplication and turn management for dual streams |
|
||||
| `input_handler.go` | Interactive input during follow mode (polling, approval detection) |
|
||||
| `history_handler.go` | Direct disk access to `taskHistory.json` |
|
||||
| `settings_parser.go` | Parse settings from CLI flags |
|
||||
| `follow_options.go` | Configuration for follow behavior |
|
||||
|
||||
**Streaming:** Task manager subscribes to two gRPC streams:
|
||||
|
||||
1. `SubscribeToState` - Full state updates
|
||||
2. `SubscribeToPartialMessage` - Streaming AI responses
|
||||
|
||||
---
|
||||
|
||||
### 11. `terminal/` - Terminal Handling
|
||||
|
||||
Enhanced keyboard protocol support and terminal configuration:
|
||||
|
||||
- Enables modifyOtherKeys and Kitty keyboard protocol
|
||||
- Detects terminal type (VS Code, iTerm, Ghostty, Kitty, etc.)
|
||||
- Auto-configures shift+enter keybindings for various terminals
|
||||
|
||||
---
|
||||
|
||||
### 12. `types/` - Type Definitions
|
||||
|
||||
| File | Purpose |
|
||||
| -------------- | ----------------------------------------------------------------- |
|
||||
| `messages.go` | `ClineMessage`, `AskType`, `SayType`, `ToolType` enums, proto conversion |
|
||||
| `state.go` | `ConversationState` with thread-safe message access |
|
||||
| `history.go` | `HistoryItem` matching taskHistory.json format |
|
||||
|
||||
---
|
||||
|
||||
### 13. `updater/` - Auto-Update
|
||||
|
||||
Background auto-update checking:
|
||||
|
||||
- 24-hour check interval (cached)
|
||||
- Queries npm registry for newer versions
|
||||
- Supports `latest` and `nightly` channels
|
||||
- Runs `npm install -g cline` to update
|
||||
|
||||
---
|
||||
|
||||
## `pkg/common/` - Shared Types
|
||||
|
||||
| File | Purpose |
|
||||
| --------------- | ------------------------------------------------------------ |
|
||||
| `constants.go` | `SETTINGS_SUBFOLDER`, `DEFAULT_CLINE_CORE_PORT` |
|
||||
| `schema.go` | SQL queries for instance/file locks |
|
||||
| `types.go` | `CoreInstanceInfo`, `LockRow`, `DefaultCoreInstance` |
|
||||
| `utils.go` | Port checking, health checks, address normalization, retry logic |
|
||||
|
||||
---
|
||||
|
||||
## `pkg/generated/` - Auto-Generated
|
||||
|
||||
| File | Purpose |
|
||||
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| `providers.go` | Provider definitions (Anthropic, OpenAI, Bedrock, etc.) with field metadata and model specs - generated from TypeScript sources |
|
||||
| `field_overrides.go` | Manual overrides for field filtering |
|
||||
|
||||
---
|
||||
|
||||
## `pkg/hostbridge/` - CLI-to-Core Bridge
|
||||
|
||||
This is the **reverse bridge** allowing cline-core to request host environment operations:
|
||||
|
||||
| File | Purpose |
|
||||
| ----------------------- | ---------------------------------------------------- |
|
||||
| `grpc_server.go` | Main server registering all services |
|
||||
| `simple_workspace.go` | Workspace service: returns CWD as workspace path |
|
||||
| `diff.go` | In-memory file diff editing with line-based operations |
|
||||
| `env.go` | Clipboard access, version info, shutdown coordination |
|
||||
| `window.go` | UI stubs (no-ops or console output) |
|
||||
|
||||
**Why this exists:** The same cline-core logic runs in VS Code and CLI. In VS Code, the "host" is the extension with editor APIs. In CLI, hostbridge emulates these capabilities with terminal-appropriate implementations.
|
||||
|
||||
---
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Two-process model:** `cline` CLI manages instances; `cline-core` is the actual AI engine (Node.js). This allows reusing the same core as the VS Code extension.
|
||||
|
||||
2. **Self-registration via SQLite:** `cline-core` registers itself in a SQLite database on startup. The CLI discovers instances by reading this database, enabling multi-instance support.
|
||||
|
||||
3. **Host bridge abstraction:** The `cline-host` process provides platform-specific operations (clipboard, workspace paths) via gRPC, allowing `cline-core` to remain host-agnostic.
|
||||
|
||||
4. **Streaming-first UI:** The CLI uses gRPC streaming to display AI responses in real-time with typewriter-style rendering.
|
||||
|
||||
5. **Dual stream handling:** Task manager subscribes to both state updates and partial messages, using deduplication to prevent duplicate rendering.
|
||||
@@ -14,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
port int
|
||||
verbose bool
|
||||
port int
|
||||
verbose bool
|
||||
workspaces []string
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -28,6 +29,7 @@ func main() {
|
||||
|
||||
rootCmd.Flags().IntVarP(&port, "port", "p", 51052, "port to listen on")
|
||||
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose logging")
|
||||
rootCmd.Flags().StringSliceVar(&workspaces, "workspace", nil, "workspace paths")
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
@@ -39,7 +41,7 @@ func runServer(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
// Create gRPC hostbridge service
|
||||
service := hostbridge.NewGrpcServer(port, verbose)
|
||||
service := hostbridge.NewGrpcServer(port, verbose, workspaces)
|
||||
|
||||
// Handle graceful shutdown
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
+69
-24
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
@@ -25,12 +26,13 @@ var (
|
||||
outputFormat string
|
||||
|
||||
// Task creation flags (for root command)
|
||||
images []string
|
||||
files []string
|
||||
mode string
|
||||
settings []string
|
||||
yolo bool
|
||||
oneshot bool
|
||||
images []string
|
||||
files []string
|
||||
mode string
|
||||
settings []string
|
||||
yolo bool
|
||||
oneshot bool
|
||||
workspaces []string
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -70,12 +72,23 @@ see the manual page: man cline`,
|
||||
|
||||
var instanceAddress string
|
||||
|
||||
// Validate workspace paths exist
|
||||
if err := common.ValidateDirsExist(workspaces); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Build the full workspace list: cwd first, then additional workspaces
|
||||
allWorkspaces, err := buildWorkspaceList(workspaces)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build workspace list: %w", err)
|
||||
}
|
||||
|
||||
// If --address flag not provided, start instance BEFORE getting prompt
|
||||
if !cmd.Flags().Changed("address") {
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("Starting new Cline instance...")
|
||||
}
|
||||
instance, err := global.Clients.StartNewInstance(ctx)
|
||||
instance, err := global.Clients.StartNewInstance(ctx, allWorkspaces...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start new instance: %w", err)
|
||||
}
|
||||
@@ -131,8 +144,8 @@ see the manual page: man cline`,
|
||||
|
||||
// If no prompt from args or stdin, show interactive input
|
||||
if prompt == "" {
|
||||
// Pass the mode flag to banner so it shows correct mode
|
||||
prompt, err = promptForInitialTask(ctx, instanceAddress, mode)
|
||||
// Pass the mode flag and workspaces to banner so it shows correct info
|
||||
prompt, err = promptForInitialTask(ctx, instanceAddress, mode, allWorkspaces)
|
||||
if err != nil {
|
||||
// Check if user cancelled - exit cleanly without error
|
||||
if err == huh.ErrUserAborted {
|
||||
@@ -152,13 +165,14 @@ see the manual page: man cline`,
|
||||
}
|
||||
|
||||
return cli.CreateAndFollowTask(ctx, prompt, cli.TaskOptions{
|
||||
Images: images,
|
||||
Files: files,
|
||||
Mode: mode,
|
||||
Settings: settings,
|
||||
Yolo: yolo,
|
||||
Address: instanceAddress,
|
||||
Verbose: verbose,
|
||||
Images: images,
|
||||
Files: files,
|
||||
Mode: mode,
|
||||
Settings: settings,
|
||||
Yolo: yolo,
|
||||
Address: instanceAddress,
|
||||
Verbose: verbose,
|
||||
Workspaces: allWorkspaces,
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -175,6 +189,7 @@ see the manual page: man cline`,
|
||||
rootCmd.Flags().BoolVarP(&yolo, "yolo", "y", false, "enable yolo mode (non-interactive)")
|
||||
rootCmd.Flags().BoolVar(&yolo, "no-interactive", false, "enable yolo mode (non-interactive)")
|
||||
rootCmd.Flags().BoolVarP(&oneshot, "oneshot", "o", false, "full autonomous mode")
|
||||
rootCmd.Flags().StringSliceVarP(&workspaces, "workspace", "w", nil, "additional workspace paths (can be specified multiple times)")
|
||||
|
||||
rootCmd.AddCommand(cli.NewTaskCommand())
|
||||
rootCmd.AddCommand(cli.NewInstanceCommand())
|
||||
@@ -189,9 +204,9 @@ see the manual page: man cline`,
|
||||
}
|
||||
}
|
||||
|
||||
func promptForInitialTask(ctx context.Context, instanceAddress, modeFlag string) (string, error) {
|
||||
func promptForInitialTask(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) (string, error) {
|
||||
// Show session banner before the initial input
|
||||
showSessionBanner(ctx, instanceAddress, modeFlag)
|
||||
showSessionBanner(ctx, instanceAddress, modeFlag, workspaces)
|
||||
|
||||
var prompt string
|
||||
|
||||
@@ -233,7 +248,7 @@ func promptForInitialTask(ctx context.Context, instanceAddress, modeFlag string)
|
||||
}
|
||||
|
||||
// showSessionBanner displays session info before initial prompt
|
||||
func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string) {
|
||||
func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string, workspaces []string) {
|
||||
bannerInfo := display.BannerInfo{
|
||||
Version: global.CliVersion,
|
||||
Mode: modeFlag, // Use the mode from command flag, not state
|
||||
@@ -244,10 +259,7 @@ func showSessionBanner(ctx context.Context, instanceAddress, modeFlag string) {
|
||||
bannerInfo.Mode = "plan"
|
||||
}
|
||||
|
||||
// Get current working directory (this is what Cline will use)
|
||||
if cwd, err := os.Getwd(); err == nil {
|
||||
bannerInfo.Workdir = cwd
|
||||
}
|
||||
bannerInfo.Workdirs = workspaces
|
||||
|
||||
// Get provider/model using auth functions (same logic as auth menu)
|
||||
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
|
||||
@@ -345,4 +357,37 @@ func getContentFromStdinAndArgs(args []string) (string, error) {
|
||||
}
|
||||
|
||||
return content.String(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// buildWorkspaceList builds the full workspace list with cwd as the first entry
|
||||
func buildWorkspaceList(additionalWorkspaces []string) ([]string, error) {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get current working directory: %w", err)
|
||||
}
|
||||
|
||||
// Start with cwd
|
||||
workspaces := []string{cwd}
|
||||
|
||||
// Add additional workspaces, avoiding duplicates
|
||||
for _, ws := range additionalWorkspaces {
|
||||
// Normalize the path
|
||||
absPath, err := common.AbsPath(ws)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve workspace path %s: %w", ws, err)
|
||||
}
|
||||
|
||||
// Skip if it's the same as cwd
|
||||
if absPath == cwd {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
isDuplicate := slices.Contains(workspaces, absPath)
|
||||
if !isDuplicate {
|
||||
workspaces = append(workspaces, absPath)
|
||||
}
|
||||
}
|
||||
|
||||
return workspaces, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
module github.com/cline/cli
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
|
||||
@@ -70,6 +70,10 @@ When using the instant task syntax **cline "prompt"** the following options are
|
||||
|
||||
: Starting mode. Options: **act** (default), **plan**
|
||||
|
||||
**-w**, **\--workspace** *path*
|
||||
|
||||
: Additional workspace paths. Can be specified multiple times to include multiple directories. The current working directory is always included as the first workspace. Example: cline -w /path/to/other/project "refactor shared code"
|
||||
|
||||
# GLOBAL OPTIONS
|
||||
|
||||
These options apply to all subcommands:
|
||||
@@ -78,6 +82,28 @@ These options apply to all subcommands:
|
||||
|
||||
: Output format. Options: **rich** (default), **json**, **plain**
|
||||
|
||||
When you use **-F json**, the CLI prints each client message as JSON.
|
||||
|
||||
Each message is a **ClineMessage** object.
|
||||
|
||||
Required fields:
|
||||
|
||||
- **type**: "ask" or "say"
|
||||
- **text**: message text
|
||||
- **ts**: Unix epoch timestamp in milliseconds
|
||||
|
||||
Optional fields (omitted when empty):
|
||||
|
||||
- **reasoning**: reasoning text
|
||||
- **say**: say subtype (present when type is "say")
|
||||
- **ask**: ask subtype (present when type is "ask")
|
||||
- **partial**: streaming flag
|
||||
- **images**: list of image URIs
|
||||
- **files**: list of file paths
|
||||
- **lastCheckpointHash**: git checkpoint hash
|
||||
- **isCheckpointCheckedOut**: checkpoint checkout flag
|
||||
- **isOperationOutsideWorkspace**: workspace safety flag
|
||||
|
||||
**-h**, **\--help**
|
||||
|
||||
: Display help information for the command.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cline",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.9",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "cline-core.js",
|
||||
"bin": {
|
||||
|
||||
@@ -47,7 +47,7 @@ func GetProviderConfigurations(ctx context.Context, manager *task.Manager) (*Pro
|
||||
}
|
||||
|
||||
// Parse state_json as map[string]interface{}
|
||||
var stateData map[string]interface{}
|
||||
var stateData map[string]any
|
||||
if err := json.Unmarshal([]byte(stateJSON), &stateData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse state JSON: %w", err)
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func GetProviderConfigurations(ctx context.Context, manager *task.Manager) (*Pro
|
||||
}
|
||||
|
||||
// Extract apiConfiguration object from state
|
||||
apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
|
||||
apiConfig, ok := stateData["apiConfiguration"].(map[string]any)
|
||||
if !ok {
|
||||
if global.Config.Verbose {
|
||||
fmt.Println("[DEBUG] No apiConfiguration found in state")
|
||||
@@ -128,11 +128,11 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
modelID := getProviderSpecificModelID(r.apiConfig, "plan", provider)
|
||||
|
||||
// Determine if credentials exist
|
||||
hasCreds := checkAPIKeyExists(r.apiConfig, provider)
|
||||
hasCreds := checkCredentialsExists(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)
|
||||
state, _ := GetLatestOCAState(context.Background(), 2*time.Second)
|
||||
if state == nil || state.User == nil {
|
||||
continue
|
||||
}
|
||||
@@ -156,7 +156,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
|
||||
Mode: "Ready",
|
||||
Provider: provider,
|
||||
ModelID: modelID,
|
||||
HasAPIKey: checkAPIKeyExists(r.apiConfig, provider),
|
||||
HasAPIKey: checkCredentialsExists(r.apiConfig, provider),
|
||||
BaseURL: baseURL,
|
||||
})
|
||||
seenProviders[provider] = true
|
||||
@@ -192,7 +192,7 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
|
||||
modelID := getProviderSpecificModelID(stateData, mode, provider)
|
||||
|
||||
// Check if API key exists
|
||||
hasAPIKey := checkAPIKeyExists(stateData, provider)
|
||||
hasCredentials := checkCredentialsExists(stateData, provider)
|
||||
|
||||
// Get base URL for Ollama (can be shown publicly)
|
||||
baseURL := ""
|
||||
@@ -206,7 +206,7 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
|
||||
Mode: capitalizeMode(mode),
|
||||
Provider: provider,
|
||||
ModelID: modelID,
|
||||
HasAPIKey: hasAPIKey,
|
||||
HasAPIKey: hasCredentials,
|
||||
BaseURL: baseURL,
|
||||
}
|
||||
}
|
||||
@@ -215,7 +215,7 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
|
||||
// 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 normalizedStr {
|
||||
case "anthropic":
|
||||
@@ -303,23 +303,27 @@ func getProviderSpecificModelID(stateData map[string]interface{}, mode string, p
|
||||
return modelID
|
||||
}
|
||||
|
||||
// checkAPIKeyExists checks if API key field exists in state (never retrieve actual key)
|
||||
func checkAPIKeyExists(stateData map[string]interface{}, provider cline.ApiProvider) bool {
|
||||
// checkCredentialsExists checks if API key field exists in state (never retrieve actual key)
|
||||
func checkCredentialsExists(stateData map[string]interface{}, provider cline.ApiProvider) bool {
|
||||
// Get field mapping from centralized function
|
||||
fields, err := GetProviderFields(provider)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
keyField := fields.APIKeyField
|
||||
|
||||
// Check if the key exists and is not empty
|
||||
if value, ok := stateData[keyField]; ok {
|
||||
if value, ok := stateData[fields.APIKeyField]; ok {
|
||||
if str, ok := value.(string); ok && str != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := stateData[fields.UseProfileField]; ok {
|
||||
if hasProfileField, ok := value.(bool); ok && hasProfileField {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -438,13 +442,13 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
stateJSON := state.StateJson
|
||||
|
||||
// Parse state_json as map[string]interface{}
|
||||
var stateData map[string]interface{}
|
||||
var stateData map[string]any
|
||||
if err := json.Unmarshal([]byte(stateJSON), &stateData); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse state JSON: %w", err)
|
||||
}
|
||||
|
||||
// Extract apiConfiguration object from state
|
||||
apiConfig, ok := stateData["apiConfiguration"].(map[string]interface{})
|
||||
apiConfig, ok := stateData["apiConfiguration"].(map[string]any)
|
||||
if !ok {
|
||||
verboseLog("[DEBUG] No apiConfiguration found in state")
|
||||
verboseLog("[DEBUG] Available keys in stateData: %v", getMapKeys(stateData))
|
||||
@@ -469,36 +473,38 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
|
||||
|
||||
// Check each BYO provider for API key presence
|
||||
providersToCheck := []struct {
|
||||
provider cline.ApiProvider
|
||||
keyField string
|
||||
provider cline.ApiProvider
|
||||
keyFields []string
|
||||
}{
|
||||
{cline.ApiProvider_ANTHROPIC, "apiKey"},
|
||||
{cline.ApiProvider_OPENAI, "openAiApiKey"},
|
||||
{cline.ApiProvider_OPENAI_NATIVE, "openAiNativeApiKey"},
|
||||
{cline.ApiProvider_OPENROUTER, "openRouterApiKey"},
|
||||
{cline.ApiProvider_XAI, "xaiApiKey"},
|
||||
{cline.ApiProvider_BEDROCK, "awsAccessKey"},
|
||||
{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"},
|
||||
{cline.ApiProvider_ANTHROPIC, []string{"apiKey"}},
|
||||
{cline.ApiProvider_OPENAI, []string{"openAiApiKey"}},
|
||||
{cline.ApiProvider_OPENAI_NATIVE, []string{"openAiNativeApiKey"}},
|
||||
{cline.ApiProvider_OPENROUTER, []string{"openRouterApiKey"}},
|
||||
{cline.ApiProvider_XAI, []string{"xaiApiKey"}},
|
||||
{cline.ApiProvider_BEDROCK, []string{"awsAccessKey", "awsUseProfile"}},
|
||||
{cline.ApiProvider_GEMINI, []string{"geminiApiKey"}},
|
||||
{cline.ApiProvider_OLLAMA, []string{"ollamaBaseUrl"}}, // Ollama uses baseUrl instead of API key
|
||||
{cline.ApiProvider_CEREBRAS, []string{"cerebrasApiKey"}},
|
||||
{cline.ApiProvider_HICAP, []string{"hicapApiKey"}},
|
||||
{cline.ApiProvider_NOUSRESEARCH, []string{"nousResearchApiKey"}},
|
||||
}
|
||||
|
||||
for _, providerCheck := range providersToCheck {
|
||||
verboseLog("[DEBUG] Checking for %s key: %s", GetProviderDisplayName(providerCheck.provider), providerCheck.keyField)
|
||||
if value, ok := apiConfig[providerCheck.keyField]; ok {
|
||||
verboseLog("[DEBUG] Found key, value type: %T, is empty: %v", value, value == "")
|
||||
if str, ok := value.(string); ok && str != "" {
|
||||
configuredProviders = append(configuredProviders, providerCheck.provider)
|
||||
verboseLog("[DEBUG] ✓ Provider %s is configured", GetProviderDisplayName(providerCheck.provider))
|
||||
verboseLog("[DEBUG] Checking for %s key: %s", GetProviderDisplayName(providerCheck.provider), providerCheck.keyFields)
|
||||
for _, keyField := range providerCheck.keyFields {
|
||||
if value, ok := apiConfig[keyField]; ok {
|
||||
verboseLog("[DEBUG] Found key, value type: %T, is empty: %v", value, value == "")
|
||||
if str, ok := value.(string); ok && str != "" {
|
||||
configuredProviders = append(configuredProviders, providerCheck.provider)
|
||||
verboseLog("[DEBUG] ✓ Provider %s is configured", GetProviderDisplayName(providerCheck.provider))
|
||||
break
|
||||
}
|
||||
} else {
|
||||
verboseLog("[DEBUG] Key %s not found", keyField)
|
||||
}
|
||||
} else {
|
||||
verboseLog("[DEBUG] Key %s not found", providerCheck.keyField)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
verboseLog("[DEBUG] Total configured providers: %d", len(configuredProviders))
|
||||
for _, p := range configuredProviders {
|
||||
verboseLog("[DEBUG] - %s", GetProviderDisplayName(p))
|
||||
|
||||
@@ -54,6 +54,7 @@ type ProviderFields struct {
|
||||
// Provider-specific additional model ID fields
|
||||
PlanModeProviderSpecificModelIDField string // e.g., "planModeOpenRouterModelId"
|
||||
ActModeProviderSpecificModelIDField string // e.g., "actModeOpenRouterModelId"
|
||||
UseProfileField string // e.g., "awsUseProfile" (for bedrock) (optional, empty if not applicable)
|
||||
}
|
||||
|
||||
// GetProviderFields returns the field mapping for a given provider
|
||||
@@ -96,6 +97,7 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
|
||||
|
||||
case cline.ApiProvider_BEDROCK:
|
||||
return ProviderFields{
|
||||
UseProfileField: "awsUseProfile",
|
||||
APIKeyField: "awsAccessKey",
|
||||
PlanModeModelIDField: "planModeApiModelId",
|
||||
ActModeModelIDField: "actModeApiModelId",
|
||||
@@ -356,6 +358,9 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
|
||||
if openRouterInfo, ok := modelInfo.(*cline.OpenRouterModelInfo); ok {
|
||||
apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
|
||||
apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
|
||||
} else if ocaInfo, ok := modelInfo.(*cline.OcaModelInfo); ok {
|
||||
apiConfig.PlanModeOcaModelInfo = ocaInfo
|
||||
apiConfig.ActModeOcaModelInfo = ocaInfo
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,6 +429,9 @@ func UpdateProviderPartial(ctx context.Context, manager *task.Manager, provider
|
||||
if openRouterInfo, ok := updates.ModelInfo.(*cline.OpenRouterModelInfo); ok {
|
||||
apiConfig.PlanModeOpenRouterModelInfo = openRouterInfo
|
||||
apiConfig.ActModeOpenRouterModelInfo = openRouterInfo
|
||||
} else if ocaInfo, ok := updates.ModelInfo.(*cline.OcaModelInfo); ok {
|
||||
apiConfig.PlanModeOcaModelInfo = ocaInfo
|
||||
apiConfig.ActModeOcaModelInfo = ocaInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,7 +190,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
|
||||
}
|
||||
|
||||
// Step 3: Select model
|
||||
modelID, _, err := pw.selectModel(cline.ApiProvider_OCA, "")
|
||||
modelID, modelInfo, err := pw.selectModel(cline.ApiProvider_OCA, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("model selection failed: %w", err)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func (pw *ProviderWizard) handleAddOcaProvider() error {
|
||||
// Step 4: Apply the OCA model configuration and set as active
|
||||
updates := ProviderUpdatesPartial{
|
||||
ModelID: &modelID,
|
||||
ModelInfo: nil,
|
||||
ModelInfo: modelInfo,
|
||||
}
|
||||
|
||||
if err := UpdateProviderPartial(pw.ctx, pw.manager, cline.ApiProvider_OCA, updates, true); err != nil {
|
||||
|
||||
@@ -15,23 +15,23 @@ import (
|
||||
// BedrockConfig holds all AWS Bedrock-specific configuration fields
|
||||
type BedrockConfig struct {
|
||||
// Profile authentication fields
|
||||
UseProfile bool // Always true for successful config
|
||||
Profile string // Optional: AWS profile name (empty = default)
|
||||
Region string // Required: AWS region
|
||||
Endpoint string // Optional: Custom VPC endpoint URL
|
||||
|
||||
UseProfile bool // Always true for successful config
|
||||
Profile string // Optional: AWS profile name (empty = default)
|
||||
Region string // Required: AWS region
|
||||
Endpoint string // Optional: Custom VPC endpoint URL
|
||||
|
||||
// Optional features
|
||||
UseCrossRegionInference bool // Optional: Enable cross-region inference
|
||||
UseGlobalInference bool // Optional: Use global inference endpoint
|
||||
UsePromptCache bool // Optional: Enable prompt caching
|
||||
|
||||
UseCrossRegionInference bool // Optional: Enable cross-region inference
|
||||
UseGlobalInference bool // Optional: Use global inference endpoint
|
||||
UsePromptCache bool // Optional: Enable prompt caching
|
||||
|
||||
// Authentication method (always "profile")
|
||||
Authentication string // Always set to "profile"
|
||||
|
||||
Authentication string // Always set to "profile"
|
||||
|
||||
// Legacy fields (no longer used in profile-only flow)
|
||||
AccessKey string // No longer used
|
||||
SecretKey string // No longer used
|
||||
SessionToken string // No longer used
|
||||
AccessKey string // No longer used
|
||||
SecretKey string // No longer used
|
||||
SessionToken string // No longer used
|
||||
}
|
||||
|
||||
// PromptForBedrockConfig displays a profile-first authentication form for Bedrock configuration
|
||||
@@ -130,7 +130,12 @@ func ApplyBedrockConfig(ctx context.Context, manager *task.Manager, config *Bedr
|
||||
// Build the API configuration with all Bedrock fields
|
||||
apiConfig := &cline.ModelsApiConfiguration{}
|
||||
|
||||
// Set model ID fields
|
||||
// Set provider for both Plan and Act modes
|
||||
bedrockProvider := cline.ApiProvider_BEDROCK
|
||||
apiConfig.PlanModeApiProvider = &bedrockProvider
|
||||
apiConfig.ActModeApiProvider = &bedrockProvider
|
||||
|
||||
// Set model ID field - this is the primary model ID used by Cline Core
|
||||
apiConfig.PlanModeApiModelId = proto.String(modelID)
|
||||
apiConfig.ActModeApiModelId = proto.String(modelID)
|
||||
apiConfig.PlanModeAwsBedrockCustomModelBaseId = proto.String(modelID)
|
||||
@@ -166,6 +171,8 @@ func ApplyBedrockConfig(ctx context.Context, manager *task.Manager, config *Bedr
|
||||
|
||||
// Build field mask including all fields we're setting (excluding access keys)
|
||||
fieldPaths := []string{
|
||||
"planModeApiProvider",
|
||||
"actModeApiProvider",
|
||||
"planModeApiModelId",
|
||||
"actModeApiModelId",
|
||||
"planModeAwsBedrockCustomModelBaseId",
|
||||
|
||||
+18
-130
@@ -1,22 +1,19 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/common"
|
||||
)
|
||||
|
||||
// BannerInfo contains information to display in the session banner
|
||||
type BannerInfo struct {
|
||||
Version string
|
||||
Provider string
|
||||
ModelID string
|
||||
Workdir string
|
||||
Mode string
|
||||
Version string
|
||||
Provider string
|
||||
ModelID string
|
||||
Workdirs []string // workspace directories
|
||||
Mode string
|
||||
}
|
||||
|
||||
// RenderSessionBanner renders a nice banner showing version, model, and workspace info
|
||||
@@ -81,131 +78,22 @@ func RenderSessionBanner(info BannerInfo) string {
|
||||
|
||||
// Model line - dim gray
|
||||
if info.Provider != "" && info.ModelID != "" {
|
||||
lines = append(lines, dimStyle.Render(info.Provider+"/"+shortenPath(info.ModelID, 30)))
|
||||
lines = append(lines, dimStyle.Render(info.Provider+"/"+common.ShortenPath(info.ModelID, 30)))
|
||||
}
|
||||
|
||||
// Workspace line - dim gray
|
||||
if info.Workdir != "" {
|
||||
lines = append(lines, dimStyle.Render(shortenPath(info.Workdir, 45)))
|
||||
for _, wd := range info.Workdirs {
|
||||
lines = append(lines, dimStyle.Render(common.ShortenPath(wd, 45)))
|
||||
}
|
||||
|
||||
// Checkpoint warning for multi-root workspaces
|
||||
if len(info.Workdirs) > 1 {
|
||||
warningStyle := lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("3")). // Yellow warning color
|
||||
Italic(true)
|
||||
lines = append(lines, "")
|
||||
lines = append(lines, warningStyle.Render("⚠ Checkpoints disabled for multi-root workspaces"))
|
||||
}
|
||||
|
||||
content := lipgloss.JoinVertical(lipgloss.Left, lines...)
|
||||
return boxStyle.Render(content)
|
||||
}
|
||||
|
||||
// shortenPath shortens a filesystem path to fit within maxLen
|
||||
func shortenPath(path string, maxLen int) string {
|
||||
// Try to replace home directory with ~ (cross-platform)
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
if strings.HasPrefix(path, homeDir) {
|
||||
shortened := "~" + path[len(homeDir):]
|
||||
// Always use ~ version if we can
|
||||
path = shortened
|
||||
}
|
||||
}
|
||||
|
||||
if len(path) <= maxLen {
|
||||
return path
|
||||
}
|
||||
|
||||
// If still too long, show last few path components
|
||||
if len(path) > maxLen {
|
||||
parts := strings.Split(path, string(filepath.Separator))
|
||||
if len(parts) > 2 {
|
||||
// Show last 2-3 components
|
||||
lastParts := parts[len(parts)-2:]
|
||||
shortened := "..." + string(filepath.Separator) + strings.Join(lastParts, string(filepath.Separator))
|
||||
if len(shortened) <= maxLen {
|
||||
return shortened
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: truncate with ellipsis
|
||||
if len(path) > maxLen {
|
||||
return "..." + path[len(path)-maxLen+3:]
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
// ExtractBannerInfoFromState extracts banner info from state JSON
|
||||
func ExtractBannerInfoFromState(stateJSON, version string) (BannerInfo, error) {
|
||||
var state map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(stateJSON), &state); err != nil {
|
||||
return BannerInfo{}, fmt.Errorf("failed to parse state JSON: %w", err)
|
||||
}
|
||||
|
||||
info := BannerInfo{
|
||||
Version: version,
|
||||
}
|
||||
|
||||
// Extract mode
|
||||
if mode, ok := state["mode"].(string); ok {
|
||||
info.Mode = mode
|
||||
}
|
||||
|
||||
// Extract workspace roots
|
||||
if workspaceRoots, ok := state["workspaceRoots"].([]interface{}); ok && len(workspaceRoots) > 0 {
|
||||
if root, ok := workspaceRoots[0].(map[string]interface{}); ok {
|
||||
if path, ok := root["path"].(string); ok {
|
||||
info.Workdir = path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract API configuration to get provider/model
|
||||
if apiConfig, ok := state["apiConfiguration"].(map[string]interface{}); ok {
|
||||
// Try common keys for provider and model (both camelCase and lowercase variants)
|
||||
providerKeys := []string{"apiProvider", "api_provider"}
|
||||
modelKeys := []string{"apiModelId", "api_model_id"}
|
||||
|
||||
// Try to extract provider
|
||||
for _, key := range providerKeys {
|
||||
if provider, ok := apiConfig[key].(string); ok && provider != "" {
|
||||
info.Provider = provider
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Try to extract model ID
|
||||
for _, key := range modelKeys {
|
||||
if modelID, ok := apiConfig[key].(string); ok && modelID != "" {
|
||||
info.ModelID = shortenModelID(modelID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// shortenModelID shortens long model IDs for display
|
||||
func shortenModelID(modelID string) string {
|
||||
// Remove date suffixes only if they're at the end (e.g., -20241022)
|
||||
// Check if the model ID ends with -YYYYMMDD pattern
|
||||
if len(modelID) > 9 {
|
||||
suffix := modelID[len(modelID)-9:] // Last 9 chars: -20241022
|
||||
if suffix[0] == '-' &&
|
||||
(strings.HasPrefix(suffix[1:], "202") || strings.HasPrefix(suffix[1:], "201")) {
|
||||
// Verify all remaining chars are digits
|
||||
allDigits := true
|
||||
for _, c := range suffix[1:] {
|
||||
if c < '0' || c > '9' {
|
||||
allDigits = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allDigits {
|
||||
return modelID[:len(modelID)-9]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still too long, show first 40 chars
|
||||
if len(modelID) > 40 {
|
||||
return modelID[:37] + "..."
|
||||
}
|
||||
|
||||
return modelID
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
)
|
||||
|
||||
// HookRenderer renders hook status messages in a CLI-native style.
|
||||
//
|
||||
// Goals:
|
||||
// - Match ToolRenderer’s markdown look
|
||||
// - Keep executions ungrouped
|
||||
// - Render status + high-signal metadata (script paths, error summary)
|
||||
//
|
||||
// Note: hook stdout/stderr currently arrives as separate `hook_output_stream` messages.
|
||||
// The CLI suppresses those by default and prints them only in --verbose mode.
|
||||
// Future work could group streamed output under the corresponding hook block.
|
||||
//
|
||||
// It returns markdown (or rendered markdown when enabled); callers should print the
|
||||
// returned string.
|
||||
|
||||
type HookRenderer struct {
|
||||
mdRenderer *MarkdownRenderer
|
||||
outputFormat string
|
||||
}
|
||||
|
||||
func NewHookRenderer(mdRenderer *MarkdownRenderer, outputFormat string) *HookRenderer {
|
||||
return &HookRenderer{mdRenderer: mdRenderer, outputFormat: outputFormat}
|
||||
}
|
||||
|
||||
func (hr *HookRenderer) RenderHookStatus(h types.HookMessage) string {
|
||||
statusText := strings.TrimSpace(h.Status)
|
||||
if statusText == "" {
|
||||
statusText = "unknown"
|
||||
}
|
||||
|
||||
// Header: aligned with ToolRenderer’s phrasing so transcripts scan consistently.
|
||||
// Example: "### Cline hook completed: PreToolUse (tool: read_file) (exit 0)"
|
||||
var headerBuilder strings.Builder
|
||||
headerBuilder.WriteString(fmt.Sprintf("### Cline hook %s: %s", statusText, h.HookName))
|
||||
if h.ToolName != "" {
|
||||
headerBuilder.WriteString(" ")
|
||||
headerBuilder.WriteString(fmt.Sprintf("(tool: %s)", h.ToolName))
|
||||
}
|
||||
if statusText == "failed" && h.ExitCode != 0 {
|
||||
headerBuilder.WriteString(" ")
|
||||
headerBuilder.WriteString(fmt.Sprintf("(exit %d)", h.ExitCode))
|
||||
}
|
||||
header := headerBuilder.String()
|
||||
|
||||
var lines []string
|
||||
lines = append(lines, header)
|
||||
|
||||
// Pending tool info (PreToolUse): show one high-signal line directly under the header.
|
||||
if h.PendingToolInfo != nil {
|
||||
if pending := hr.formatPendingToolInfo(h.PendingToolInfo); pending != "" {
|
||||
lines = append(lines, fmt.Sprintf("- Pending: %s", pending))
|
||||
}
|
||||
}
|
||||
|
||||
// Script paths: one per line.
|
||||
paths := make([]string, 0, len(h.ScriptPaths))
|
||||
for _, p := range h.ScriptPaths {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
paths = append(paths, p)
|
||||
}
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
// Fallback when no script paths are provided.
|
||||
lines = append(lines, "- *(no hook scripts found)*")
|
||||
} else {
|
||||
for _, p := range paths {
|
||||
lines = append(lines, fmt.Sprintf("- Running hook: `%s`", p))
|
||||
}
|
||||
}
|
||||
|
||||
// On failure, show a minimal summary (full stderr reserved for verbose).
|
||||
if statusText == "failed" && h.Error != nil {
|
||||
if msg := strings.TrimSpace(h.Error.Message); msg != "" {
|
||||
lines = append(lines, fmt.Sprintf("- Error: %s", msg))
|
||||
}
|
||||
// If we have a specific script path, include it as a hint.
|
||||
if sp := strings.TrimSpace(h.Error.ScriptPath); sp != "" {
|
||||
lines = append(lines, fmt.Sprintf("- Script: `%s`", sp))
|
||||
}
|
||||
}
|
||||
|
||||
markdown := strings.Join(lines, "\n")
|
||||
return hr.renderMarkdown(markdown)
|
||||
}
|
||||
|
||||
func (hr *HookRenderer) formatPendingToolInfo(info *types.ToolInfo) string {
|
||||
if info == nil {
|
||||
return ""
|
||||
}
|
||||
tool := strings.TrimSpace(info.Tool)
|
||||
if tool == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Keep this intentionally compact and readable.
|
||||
// Format: "<tool> <identifier>" where identifier is the most relevant param.
|
||||
var ident string
|
||||
switch {
|
||||
case strings.TrimSpace(info.Path) != "":
|
||||
ident = strings.TrimSpace(info.Path)
|
||||
case strings.TrimSpace(info.Command) != "":
|
||||
ident = strings.TrimSpace(info.Command)
|
||||
case strings.TrimSpace(info.Url) != "":
|
||||
ident = strings.TrimSpace(info.Url)
|
||||
case strings.TrimSpace(info.McpTool) != "" && strings.TrimSpace(info.McpServer) != "":
|
||||
ident = fmt.Sprintf("%s %s", strings.TrimSpace(info.McpServer), strings.TrimSpace(info.McpTool))
|
||||
case strings.TrimSpace(info.ResourceUri) != "":
|
||||
ident = strings.TrimSpace(info.ResourceUri)
|
||||
case strings.TrimSpace(info.Regex) != "":
|
||||
ident = strings.TrimSpace(info.Regex)
|
||||
default:
|
||||
ident = ""
|
||||
}
|
||||
|
||||
if ident != "" {
|
||||
return fmt.Sprintf("%s %s", tool, ident)
|
||||
}
|
||||
return tool
|
||||
}
|
||||
|
||||
func (hr *HookRenderer) renderMarkdown(markdown string) string {
|
||||
// Align with ToolRenderer: in plain mode or non-TTY, return markdown as-is.
|
||||
if hr.outputFormat == "plain" || !isTTY() {
|
||||
return markdown
|
||||
}
|
||||
if hr.mdRenderer == nil {
|
||||
return markdown
|
||||
}
|
||||
rendered, err := hr.mdRenderer.Render(markdown)
|
||||
if err != nil {
|
||||
return markdown
|
||||
}
|
||||
return rendered
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
)
|
||||
|
||||
func TestHookRenderer_RenderHookStatus_FailedShowsErrorAndScript(t *testing.T) {
|
||||
hr := NewHookRenderer(nil, "plain")
|
||||
|
||||
msg := hr.RenderHookStatus(types.HookMessage{
|
||||
HookName: "PreToolUse",
|
||||
ToolName: "execute_command",
|
||||
Status: "failed",
|
||||
ExitCode: 2,
|
||||
ScriptPaths: []string{"repo/.clinerules/hooks/PreToolUse"},
|
||||
Error: &types.HookError{
|
||||
Message: "boom",
|
||||
ScriptPath: "repo/.clinerules/hooks/PreToolUse",
|
||||
},
|
||||
})
|
||||
|
||||
if !strings.Contains(msg, "### Cline hook failed: PreToolUse") {
|
||||
t.Fatalf("expected header in rendered output, got: %q", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "- Error: boom") {
|
||||
t.Fatalf("expected error line in rendered output, got: %q", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "- Script: `repo/.clinerules/hooks/PreToolUse`") {
|
||||
t.Fatalf("expected script line in rendered output, got: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHookRenderer_RenderHookStatus_PendingToolInfoAppearsDirectlyUnderHeader(t *testing.T) {
|
||||
hr := NewHookRenderer(nil, "plain")
|
||||
|
||||
msg := hr.RenderHookStatus(types.HookMessage{
|
||||
HookName: "PreToolUse",
|
||||
ToolName: "write_to_file",
|
||||
Status: "running",
|
||||
PendingToolInfo: &types.ToolInfo{
|
||||
Tool: "write_to_file",
|
||||
Path: "src/foo.ts",
|
||||
},
|
||||
ScriptPaths: []string{"repo/.clinerules/hooks/PreToolUse"},
|
||||
})
|
||||
|
||||
header := "### Cline hook running: PreToolUse"
|
||||
pending := "- Pending: write_to_file src/foo.ts"
|
||||
runningHook := "- Running hook: `repo/.clinerules/hooks/PreToolUse`"
|
||||
|
||||
headerIdx := strings.Index(msg, header)
|
||||
if headerIdx == -1 {
|
||||
t.Fatalf("expected header %q in output, got: %q", header, msg)
|
||||
}
|
||||
pendingIdx := strings.Index(msg, pending)
|
||||
if pendingIdx == -1 {
|
||||
t.Fatalf("expected pending line %q in output, got: %q", pending, msg)
|
||||
}
|
||||
runningIdx := strings.Index(msg, runningHook)
|
||||
if runningIdx == -1 {
|
||||
t.Fatalf("expected running hook line %q in output, got: %q", runningHook, msg)
|
||||
}
|
||||
if !(headerIdx < pendingIdx && pendingIdx < runningIdx) {
|
||||
t.Fatalf("expected header < pending < runningHook ordering, got indexes header=%d pending=%d running=%d\nfull=%q", headerIdx, pendingIdx, runningIdx, msg)
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,12 @@ func NewStreamingSegment(sayType, prefix string, mdRenderer *MarkdownRenderer, s
|
||||
// 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("")
|
||||
output.Print(rendered)
|
||||
// Skip empty headers.
|
||||
if strings.TrimSpace(header) != "" {
|
||||
rendered, _ := mdRenderer.Render(header)
|
||||
output.Println("")
|
||||
output.Print(rendered)
|
||||
}
|
||||
}
|
||||
|
||||
return ss
|
||||
@@ -110,6 +113,9 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
|
||||
if err := json.Unmarshal([]byte(currentBuffer), &tool); err == nil {
|
||||
bodyContent = ss.toolRenderer.GenerateToolContentBody(&tool)
|
||||
}
|
||||
} else if ss.sayType == string(types.SayTypeHookStatus) {
|
||||
// Hooks are rendered via the state stream; nothing to render here.
|
||||
bodyContent = ""
|
||||
} else if ss.sayType == string(types.SayTypeCommand) {
|
||||
// Command output
|
||||
bodyContent = "```shell\n" + currentBuffer + "\n```"
|
||||
@@ -160,6 +166,10 @@ func (ss *StreamingSegment) generateRichHeader() string {
|
||||
|
||||
case string(types.SayTypeTool):
|
||||
return ss.generateToolHeader()
|
||||
|
||||
case string(types.SayTypeHookStatus):
|
||||
// Hooks are rendered from the state stream; don’t emit a partial-stream header.
|
||||
return ""
|
||||
|
||||
case "ask":
|
||||
// Check the specific ask type
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
)
|
||||
|
||||
func TestStreamingSegment_generateRichHeader_HookIsEmpty(t *testing.T) {
|
||||
ss := &StreamingSegment{
|
||||
sayType: string(types.SayTypeHookStatus),
|
||||
prefix: "HOOK",
|
||||
msg: &types.ClineMessage{},
|
||||
}
|
||||
|
||||
header := ss.generateRichHeader()
|
||||
if header != "" {
|
||||
t.Fatalf("expected empty header for hook segments to avoid double-render, got: %q", header)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,18 @@ func (s *StreamingDisplay) HandlePartialMessage(msg *types.ClineMessage) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
// Render hooks from the state stream only (not partial stream) to avoid duplicates.
|
||||
//
|
||||
// Rationale: hook status messages are often updated/reordered by the backend (e.g. PreToolUse
|
||||
// hooks are moved above the corresponding tool message). The state stream represents the
|
||||
// authoritative, “final” message ordering, while the partial stream is best-effort for
|
||||
// incremental display.
|
||||
//
|
||||
// Only suppress *partial* hook messages; complete ones still flow through dedupe.
|
||||
if msg.Partial && msg.Say == string(types.SayTypeHookStatus) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check for deduplication
|
||||
if s.dedupe.IsDuplicate(msg) {
|
||||
return nil
|
||||
@@ -91,7 +103,11 @@ func (s *StreamingDisplay) HandlePartialMessage(msg *types.ClineMessage) error {
|
||||
|
||||
func (s *StreamingDisplay) shouldRenderMarkdown(sayType string) bool {
|
||||
switch sayType {
|
||||
case string(types.SayTypeReasoning), string(types.SayTypeText), string(types.SayTypeCompletionResult), string(types.SayTypeTool), "ask":
|
||||
case string(types.SayTypeReasoning),
|
||||
string(types.SayTypeText),
|
||||
string(types.SayTypeCompletionResult),
|
||||
string(types.SayTypeTool),
|
||||
"ask":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -110,6 +126,8 @@ func (s *StreamingDisplay) getPrefix(sayType string) string {
|
||||
return "ASK"
|
||||
case string(types.SayTypeCommand):
|
||||
return "TERMINAL"
|
||||
case string(types.SayTypeHookStatus):
|
||||
return "HOOK"
|
||||
default:
|
||||
return strings.ToUpper(sayType)
|
||||
}
|
||||
|
||||
@@ -264,6 +264,6 @@ func (sr *SystemMessageRenderer) RenderInfo(title, message string) error {
|
||||
func (sr *SystemMessageRenderer) RenderCheckpoint(timestamp string, id int64) error {
|
||||
markdown := fmt.Sprintf("## [%s] Checkpoint created `%d`", timestamp, id)
|
||||
rendered := sr.renderer.RenderMarkdown(markdown)
|
||||
fmt.Printf(rendered)
|
||||
fmt.Print(rendered)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -339,6 +339,13 @@ func (tr *ToolRenderer) RenderCommandOutput(output string) string {
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func (tr *ToolRenderer) RenderCommandPermissionDenied(command string) string {
|
||||
command = strings.TrimSpace(command)
|
||||
rendered := tr.renderMarkdown("### Command was denied")
|
||||
message := fmt.Sprintf("Cline does not have permission to execute this command: `%s`", command)
|
||||
return fmt.Sprintf("\n%s\n\n%s\n", rendered, message)
|
||||
}
|
||||
|
||||
// RenderUserResponse renders user approval/rejection feedback
|
||||
func (tr *ToolRenderer) RenderUserResponse(approved bool, feedback string) string {
|
||||
var symbol, status string
|
||||
|
||||
@@ -36,7 +36,7 @@ func (c *ClineClients) Initialize(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// StartNewInstance starts a new Cline instance and waits for cline-core to self-register
|
||||
func (c *ClineClients) StartNewInstance(ctx context.Context) (*common.CoreInstanceInfo, error) {
|
||||
func (c *ClineClients) StartNewInstance(ctx context.Context, workspaces ...string) (*common.CoreInstanceInfo, error) {
|
||||
// Find available ports
|
||||
corePort, hostPort, err := common.FindAvailablePortPair()
|
||||
if err != nil {
|
||||
@@ -48,7 +48,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context) (*common.CoreInstan
|
||||
}
|
||||
|
||||
// Start cline-host first
|
||||
hostCmd, err := startClineHost(hostPort, corePort)
|
||||
hostCmd, err := startClineHost(hostPort, workspaces)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start cline-host: %w", err)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ func (c *ClineClients) StartNewInstance(ctx context.Context) (*common.CoreInstan
|
||||
}
|
||||
|
||||
// StartNewInstanceAtPort starts a new Cline instance at the specified port and waits for self-registration
|
||||
func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int) (*common.CoreInstanceInfo, error) {
|
||||
func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int, workspaces ...string) (*common.CoreInstanceInfo, error) {
|
||||
// Find available host port (core port + 1000)
|
||||
hostPort := corePort + 1000
|
||||
coreAddress := fmt.Sprintf("localhost:%d", corePort)
|
||||
@@ -135,7 +135,7 @@ func (c *ClineClients) StartNewInstanceAtPort(ctx context.Context, corePort int)
|
||||
}
|
||||
|
||||
// Start cline-host first
|
||||
hostCmd, err := startClineHost(hostPort, corePort)
|
||||
hostCmd, err := startClineHost(hostPort, workspaces)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to start cline-host: %w", err)
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func (c *ClineClients) EnsureInstanceAtAddress(ctx context.Context, address stri
|
||||
return fmt.Errorf("cannot start remote instance at %s", normalized)
|
||||
}
|
||||
|
||||
func startClineHost(hostPort, corePort int) (*exec.Cmd, error) {
|
||||
func startClineHost(hostPort int, workspaces []string) (*exec.Cmd, error) {
|
||||
if Config.Verbose {
|
||||
fmt.Printf("Starting cline-host on port %d\n", hostPort)
|
||||
}
|
||||
@@ -255,10 +255,18 @@ func startClineHost(hostPort, corePort int) (*exec.Cmd, error) {
|
||||
binDir := path.Dir(execPath)
|
||||
clineHostPath := path.Join(binDir, "cline-host")
|
||||
|
||||
// Start the cline-host process
|
||||
cmd := exec.Command(clineHostPath,
|
||||
// Build command arguments
|
||||
args := []string{
|
||||
"--verbose",
|
||||
"--port", fmt.Sprintf("%d", hostPort))
|
||||
"--port", fmt.Sprintf("%d", hostPort),
|
||||
}
|
||||
|
||||
for _, ws := range workspaces {
|
||||
args = append(args, "--workspace", ws)
|
||||
}
|
||||
|
||||
// Start the cline-host process
|
||||
cmd := exec.Command(clineHostPath, args...)
|
||||
|
||||
// Create logs directory in ~/.cline/logs
|
||||
logsDir := path.Join(Config.ConfigPath, "logs")
|
||||
@@ -333,7 +341,7 @@ func KillInstanceByAddress(ctx context.Context, registry *ClientRegistry, addres
|
||||
if Config.Verbose {
|
||||
fmt.Printf("Waiting for instance to clean up registry entry...\n")
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
for range 5 {
|
||||
time.Sleep(1 * time.Second)
|
||||
if !registry.HasInstanceAtAddress(address) {
|
||||
if Config.Verbose {
|
||||
@@ -408,15 +416,15 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) {
|
||||
// This handles the case where we're running from cli/bin/cline
|
||||
devClineCorePath := path.Join(binDir, "..", "..", "dist-standalone", "cline-core.js")
|
||||
devInstallDir := path.Join(binDir, "..", "..", "dist-standalone")
|
||||
|
||||
|
||||
if Config.Verbose {
|
||||
fmt.Printf("Primary location not found, trying development path: %s\n", devClineCorePath)
|
||||
}
|
||||
|
||||
|
||||
if _, err := os.Stat(devClineCorePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("cline-core.js not found at '%s' or '%s'. Please ensure you're running from the correct location or reinstall with 'npm install -g cline'", clineCorePath, devClineCorePath)
|
||||
}
|
||||
|
||||
|
||||
finalClineCorePath = devClineCorePath
|
||||
finalInstallDir = devInstallDir
|
||||
if Config.Verbose {
|
||||
@@ -475,7 +483,7 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) {
|
||||
realNodeModules := path.Join(finalInstallDir, "node_modules")
|
||||
fakeNodeModules := path.Join(finalInstallDir, "fake_node_modules")
|
||||
nodePath := fmt.Sprintf("%s%c%s", realNodeModules, os.PathListSeparator, fakeNodeModules)
|
||||
|
||||
|
||||
env = append(env,
|
||||
fmt.Sprintf("NODE_PATH=%s", nodePath),
|
||||
// These control gRPC debug logging
|
||||
@@ -484,7 +492,7 @@ func startClineCore(corePort, hostPort int) (*exec.Cmd, error) {
|
||||
"NODE_ENV=development",
|
||||
)
|
||||
cmd.Env = env
|
||||
|
||||
|
||||
if Config.Verbose {
|
||||
fmt.Printf("NODE_PATH set to: %s\n", nodePath)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ type DisplayContext struct {
|
||||
State *types.ConversationState
|
||||
Renderer *display.Renderer
|
||||
ToolRenderer *display.ToolRenderer
|
||||
HookRenderer *display.HookRenderer
|
||||
SystemRenderer *display.SystemMessageRenderer
|
||||
IsLast bool
|
||||
IsPartial bool
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/clerror"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
"github.com/cline/cli/pkg/cli/output"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
)
|
||||
|
||||
// SayHandler handles SAY type messages
|
||||
@@ -90,6 +90,12 @@ func (h *SayHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
return h.handleInfo(msg, dc)
|
||||
case string(types.SayTypeTaskProgress):
|
||||
return h.handleTaskProgress(msg, dc)
|
||||
case string(types.SayTypeHookStatus):
|
||||
return h.handleHookStatus(msg, dc)
|
||||
case string(types.SayTypeHookOutputStream):
|
||||
return h.handleHookOutputStream(msg, dc)
|
||||
case string(types.SayTypeCommandPermissionDenied):
|
||||
return h.handleCommandPermissionDenied(msg, dc)
|
||||
default:
|
||||
return h.handleDefault(msg, dc)
|
||||
}
|
||||
@@ -242,18 +248,17 @@ func (h *SayHandler) handleCompletionResult(msg *types.ClineMessage, dc *Display
|
||||
}
|
||||
|
||||
func formatUserMessage(text string) string {
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
// Wrap each line in backticks
|
||||
for i, line := range lines {
|
||||
if line != "" {
|
||||
lines[i] = fmt.Sprintf("`%s`", line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
// Wrap each line in backticks
|
||||
for i, line := range lines {
|
||||
if line != "" {
|
||||
lines[i] = fmt.Sprintf("`%s`", line)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// handleUserFeedback handles user feedback messages
|
||||
func (h *SayHandler) handleUserFeedback(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
@@ -343,6 +348,18 @@ func (h *SayHandler) handleCommandOutput(msg *types.ClineMessage, dc *DisplayCon
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SayHandler) handleCommandPermissionDenied(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
if msg.Text == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use unified ToolRenderer
|
||||
rendered := dc.ToolRenderer.RenderCommandPermissionDenied(msg.Text)
|
||||
output.Print(rendered)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SayHandler) handleTool(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
var tool types.ToolMessage
|
||||
if err := json.Unmarshal([]byte(msg.Text), &tool); err != nil {
|
||||
@@ -517,5 +534,16 @@ func (h *SayHandler) handleTaskProgress(msg *types.ClineMessage, dc *DisplayCont
|
||||
|
||||
// handleDefault handles unknown SAY message types
|
||||
func (h *SayHandler) handleDefault(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
// Debug: log unhandled say types to help identify missing cases using output.Printf for CLI consistency
|
||||
if dc.Verbose {
|
||||
output.Printf("[DEBUG] Unhandled SAY type: '%s' (text preview: %s)\n", msg.Say, truncateForDisplay(msg.Text, 50))
|
||||
}
|
||||
return dc.Renderer.RenderMessage("SAY", msg.Text, true)
|
||||
}
|
||||
|
||||
func truncateForDisplay(text string, maxLen int) string {
|
||||
if len(text) <= maxLen {
|
||||
return text
|
||||
}
|
||||
return text[:maxLen] + "..."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cline/cli/pkg/cli/output"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
)
|
||||
|
||||
// Hook-specific SAY handlers and helpers.
|
||||
// Kept in a separate file to keep say_handlers.go focused on routing.
|
||||
|
||||
// handleHookStatus handles hook execution status messages.
|
||||
func (h *SayHandler) handleHookStatus(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
hook, err := parseHookMessage(msg.Text)
|
||||
if err != nil {
|
||||
// Fallback to basic output if JSON parsing fails
|
||||
return dc.Renderer.RenderMessage("HOOK", msg.Text, true)
|
||||
}
|
||||
|
||||
logHookDebug(hook, dc)
|
||||
hook.ScriptPaths = formatHookPaths(hook.ScriptPaths)
|
||||
|
||||
return renderHookStatus(hook, dc)
|
||||
}
|
||||
|
||||
// handleHookOutputStream handles streaming output from hooks.
|
||||
//
|
||||
// Hook stdout/stderr currently arrives line-by-line from the backend as
|
||||
// `hook_output_stream` messages. The CLI intentionally suppresses these by default
|
||||
// to keep the transcript high-signal.
|
||||
//
|
||||
// In --verbose mode, we print each non-empty line prefixed with "HOOK>" for easy grepping.
|
||||
// Future work could associate these lines with a specific hook execution and render them
|
||||
// as a grouped section under the hook status header.
|
||||
func (h *SayHandler) handleHookOutputStream(msg *types.ClineMessage, dc *DisplayContext) error {
|
||||
if !dc.Verbose {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := strings.TrimRight(msg.Text, "\n")
|
||||
if strings.TrimSpace(line) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
output.Printf("HOOK> %s\n", line)
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseHookMessage(jsonText string) (types.HookMessage, error) {
|
||||
var hook types.HookMessage
|
||||
if err := json.Unmarshal([]byte(jsonText), &hook); err != nil {
|
||||
return types.HookMessage{}, err
|
||||
}
|
||||
return hook, nil
|
||||
}
|
||||
|
||||
func logHookDebug(hook types.HookMessage, dc *DisplayContext) {
|
||||
if dc.Verbose {
|
||||
output.Printf("[DEBUG] Hook parsed: name=%s, status=%s, toolName=%s, scriptPaths=%v\n",
|
||||
hook.HookName, hook.Status, hook.ToolName, hook.ScriptPaths)
|
||||
}
|
||||
}
|
||||
|
||||
func formatHookPaths(paths []string) []string {
|
||||
if len(paths) == 0 {
|
||||
return paths
|
||||
}
|
||||
formatted := make([]string, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
if strings.TrimSpace(p) == "" {
|
||||
continue
|
||||
}
|
||||
formatted = append(formatted, formatHookPath(p))
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
func renderHookStatus(hook types.HookMessage, dc *DisplayContext) error {
|
||||
if dc.HookRenderer != nil {
|
||||
rendered := dc.HookRenderer.RenderHookStatus(hook)
|
||||
// Match ToolRenderer’s spacing: one leading newline, one trailing newline.
|
||||
output.Print("\n")
|
||||
output.Print(rendered)
|
||||
output.Print("\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fallback: if HookRenderer not available
|
||||
return dc.Renderer.RenderMessage("HOOK", fmt.Sprintf("%s %s", hook.HookName, hook.Status), true)
|
||||
}
|
||||
|
||||
func formatHookPath(fullPath string) string {
|
||||
// Normalize for display and prefix checks. This is display-only; do not use for IO.
|
||||
normalized := normalizeSlashes(fullPath)
|
||||
|
||||
// If this is a repo-scoped hook script (i.e. lives under <repo>/.clinerules/hooks/),
|
||||
// always include the repo name for disambiguation even in single-repo workspaces.
|
||||
//
|
||||
// This intentionally runs before workspace-relative formatting, which would otherwise
|
||||
// collapse to ".clinerules/hooks/..." and lose the repo context.
|
||||
if p, ok := tryRepoScopedHooksPath(normalized); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
// Prefer workspace-relative paths first for readability, since most hook scripts
|
||||
// live inside the current project.
|
||||
if p, ok := tryWorkspaceRelativeHookPath(normalized); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
// Follow existing CLI pattern: resolve home via os.UserHomeDir.
|
||||
if p, ok := tryHomeTildePath(normalized); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
// Secondary heuristic: if hook lives under <repo>/.clinerules, collapse to repo-relative.
|
||||
if p, ok := tryRepoRelativeHookPath(normalized); ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return fallbackLastComponents(normalized, 3)
|
||||
}
|
||||
|
||||
func normalizeSlashes(p string) string {
|
||||
return filepath.ToSlash(p)
|
||||
}
|
||||
|
||||
func tryWorkspaceRelativeHookPath(normalizedPath string) (string, bool) {
|
||||
root, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// filepath.Rel expects OS-native paths, so we need to convert the normalized path
|
||||
// back to OS-native format before calling Rel, then normalize the result for display.
|
||||
targetOS := filepath.FromSlash(normalizedPath)
|
||||
rel, err := filepath.Rel(root, targetOS)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
// If it's not within the workspace, Rel will start with "..".
|
||||
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||
return "", false
|
||||
}
|
||||
return normalizeSlashes(rel), true
|
||||
}
|
||||
|
||||
func tryHomeTildePath(normalizedPath string) (string, bool) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil || strings.TrimSpace(homeDir) == "" {
|
||||
return "", false
|
||||
}
|
||||
homeDir = normalizeSlashes(homeDir)
|
||||
if !strings.HasPrefix(normalizedPath, homeDir) {
|
||||
return "", false
|
||||
}
|
||||
rel := strings.TrimPrefix(normalizedPath, homeDir)
|
||||
rel = strings.TrimPrefix(rel, "/")
|
||||
return "~/" + rel, true
|
||||
}
|
||||
|
||||
func tryRepoRelativeHookPath(normalizedPath string) (string, bool) {
|
||||
parts := strings.Split(normalizedPath, "/")
|
||||
for i, part := range parts {
|
||||
if part == ".clinerules" && i > 0 {
|
||||
repoName := parts[i-1]
|
||||
return repoName + "/" + strings.Join(parts[i:], "/"), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// tryRepoScopedHooksPath returns a repo-prefixed path like
|
||||
// "myrepo/.clinerules/hooks/PreToolUse" when the given path points to a hook script
|
||||
// under a repo's .clinerules/hooks directory.
|
||||
//
|
||||
// This is more specific than tryRepoRelativeHookPath and is used to ensure hook script
|
||||
// paths always include repo context.
|
||||
func tryRepoScopedHooksPath(normalizedPath string) (string, bool) {
|
||||
// Fast path check to avoid split work.
|
||||
if !strings.Contains(normalizedPath, "/.clinerules/hooks/") {
|
||||
return "", false
|
||||
}
|
||||
return tryRepoRelativeHookPath(normalizedPath)
|
||||
}
|
||||
|
||||
func fallbackLastComponents(normalizedPath string, n int) string {
|
||||
parts := strings.Split(normalizedPath, "/")
|
||||
if len(parts) >= n {
|
||||
return strings.Join(parts[len(parts)-n:], "/")
|
||||
}
|
||||
return normalizedPath
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatHookPath_PrefersWorkspaceRelative(t *testing.T) {
|
||||
// Create a stable workspace root (avoid TempDir's nested ".../001" patterns)
|
||||
// so that workspace-relative formatting is deterministic.
|
||||
root := filepath.Join(t.TempDir(), "workspace")
|
||||
if err := os.MkdirAll(root, 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll: %v", err)
|
||||
}
|
||||
oldWd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Getwd: %v", err)
|
||||
}
|
||||
defer func() { _ = os.Chdir(oldWd) }()
|
||||
if err := os.Chdir(root); err != nil {
|
||||
t.Fatalf("Chdir: %v", err)
|
||||
}
|
||||
|
||||
inside := filepath.Join(root, ".clinerules", "hooks", "pre.sh")
|
||||
got := formatHookPath(inside)
|
||||
// Repo-scoped hook scripts should always include the repo name (the directory
|
||||
// immediately containing .clinerules) even when running inside that repo.
|
||||
expected := "workspace/" + filepath.ToSlash(filepath.Join(".clinerules", "hooks", "pre.sh"))
|
||||
if got != expected {
|
||||
t.Fatalf("expected formatted path to be %q. got=%q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHookPath_FallsBackToLastComponents(t *testing.T) {
|
||||
// Use an obviously non-workspace path (relative, but not prefixed with cwd).
|
||||
got := formatHookPath("/var/tmp/foo/bar/baz.sh")
|
||||
if got != "foo/bar/baz.sh" {
|
||||
t.Fatalf("expected last 3 components fallback, got=%q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatHookPath_HomeDirToTilde(t *testing.T) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil || home == "" {
|
||||
t.Skip("home dir not available; skipping")
|
||||
}
|
||||
|
||||
got := formatHookPath(home + "/Documents/Cline/Hooks/TaskStart")
|
||||
want := "~/Documents/Cline/Hooks/TaskStart"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHookPath_WorkspaceRepoRelative(t *testing.T) {
|
||||
got := formatHookPath("/Users/alice/dev/repo-name/.clinerules/hooks/TaskStart")
|
||||
want := "repo-name/.clinerules/hooks/TaskStart"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatHookPath_FallbackLast3Components(t *testing.T) {
|
||||
got := formatHookPath("/a/b/c/d/e")
|
||||
want := "c/d/e"
|
||||
if got != want {
|
||||
t.Fatalf("expected %q, got %q", want, got)
|
||||
}
|
||||
}
|
||||
@@ -407,7 +407,7 @@ func newInstanceListCommand() *cobra.Command {
|
||||
|
||||
fmt.Print(strings.TrimLeft(rendered, "\n"))
|
||||
}
|
||||
fmt.Println("\n")
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,4 +503,4 @@ func newInstanceNewCommand() *cobra.Command {
|
||||
cmd.Flags().BoolVarP(&setDefault, "default", "d", false, "set as default instance")
|
||||
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
"github.com/charmbracelet/bubbles/textarea"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/cli/slash"
|
||||
)
|
||||
|
||||
// InputType represents the type of input being collected
|
||||
type InputType int
|
||||
|
||||
const INPUT_WIDTH = 46
|
||||
const INPUT_WIDTH = 46
|
||||
|
||||
const (
|
||||
InputTypeMessage InputType = iota
|
||||
@@ -24,11 +25,11 @@ const (
|
||||
|
||||
// InputSubmitMsg is sent when the user submits input
|
||||
type InputSubmitMsg struct {
|
||||
Value string
|
||||
InputType InputType
|
||||
Approved bool // For approval type
|
||||
NeedsFeedback bool // For approval type
|
||||
NoAskAgain bool // For approval type - indicates "don't ask again" was selected
|
||||
Value string
|
||||
InputType InputType
|
||||
Approved bool // For approval type
|
||||
NeedsFeedback bool // For approval type
|
||||
NoAskAgain bool // For approval type - indicates "don't ask again" was selected
|
||||
}
|
||||
|
||||
// InputCancelMsg is sent when the user cancels input (Ctrl+C)
|
||||
@@ -36,8 +37,8 @@ type InputCancelMsg struct{}
|
||||
|
||||
// ChangeInputTypeMsg changes the current input type
|
||||
type ChangeInputTypeMsg struct {
|
||||
InputType InputType
|
||||
Title string
|
||||
InputType InputType
|
||||
Title string
|
||||
Placeholder string
|
||||
}
|
||||
|
||||
@@ -57,7 +58,7 @@ type InputModel struct {
|
||||
placeholder string
|
||||
currentMode string // "plan" or "act"
|
||||
width int
|
||||
lastHeight int // Track height for cleanup on submit
|
||||
lastHeight int // Track height for cleanup on submit
|
||||
|
||||
// For approval type
|
||||
approvalOptions []string
|
||||
@@ -66,6 +67,9 @@ type InputModel struct {
|
||||
|
||||
// Styles (huh-inspired theme)
|
||||
styles fieldStyles
|
||||
|
||||
// Slash command autocomplete dropdown
|
||||
completion CompletionModel
|
||||
}
|
||||
|
||||
// fieldStyles holds the styling for the input field
|
||||
@@ -115,12 +119,17 @@ func newFieldStyles() fieldStyles {
|
||||
|
||||
// NewInputModel creates a new input model
|
||||
func NewInputModel(inputType InputType, title, placeholder, currentMode string) InputModel {
|
||||
return NewInputModelWithRegistry(inputType, title, placeholder, currentMode, nil)
|
||||
}
|
||||
|
||||
// NewInputModelWithRegistry creates a new input model with slash command autocomplete support
|
||||
func NewInputModelWithRegistry(inputType InputType, title, placeholder, currentMode string, registry *slash.Registry) InputModel {
|
||||
ta := textarea.New()
|
||||
ta.Placeholder = placeholder
|
||||
ta.Focus()
|
||||
ta.CharLimit = 0
|
||||
ta.ShowLineNumbers = false
|
||||
ta.Prompt = "" // Remove prompt prefix (this is what adds the inner border!)
|
||||
ta.Prompt = "" // Remove prompt prefix (this is what adds the inner border!)
|
||||
ta.SetHeight(5)
|
||||
// Don't set width here - let WindowSizeMsg handle it
|
||||
ta.SetWidth(INPUT_WIDTH)
|
||||
@@ -138,11 +147,11 @@ func NewInputModel(inputType InputType, title, placeholder, currentMode string)
|
||||
cursorColor = lipgloss.Color("39") // Blue for act
|
||||
}
|
||||
|
||||
ta.FocusedStyle.CursorLine = lipgloss.NewStyle() // No cursor line highlighting
|
||||
ta.FocusedStyle.EndOfBuffer = lipgloss.NewStyle() // No end-of-buffer styling
|
||||
ta.FocusedStyle.CursorLine = lipgloss.NewStyle() // No cursor line highlighting
|
||||
ta.FocusedStyle.EndOfBuffer = lipgloss.NewStyle() // No end-of-buffer styling
|
||||
ta.FocusedStyle.Placeholder = styles.placeholder
|
||||
ta.FocusedStyle.Text = styles.textArea
|
||||
ta.FocusedStyle.Prompt = lipgloss.NewStyle() // No prompt styling
|
||||
ta.FocusedStyle.Prompt = lipgloss.NewStyle() // No prompt styling
|
||||
ta.Cursor.Style = lipgloss.NewStyle().Foreground(cursorColor)
|
||||
ta.Cursor.TextStyle = styles.textArea
|
||||
|
||||
@@ -154,6 +163,7 @@ func NewInputModel(inputType InputType, title, placeholder, currentMode string)
|
||||
currentMode: currentMode,
|
||||
width: 0, // Will be set by first WindowSizeMsg
|
||||
styles: styles,
|
||||
completion: NewCompletionModel(registry),
|
||||
}
|
||||
|
||||
// For approval type, set up options
|
||||
@@ -217,13 +227,6 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
|
||||
default:
|
||||
// Forward all other messages to textarea (including blink ticks)
|
||||
if !m.suspended && (m.inputType == InputTypeMessage || m.inputType == InputTypeFeedback) {
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
case tea.KeyMsg:
|
||||
if m.suspended {
|
||||
return m, nil
|
||||
@@ -231,6 +234,31 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// Handle keys for text input types (Message/Feedback)
|
||||
if m.inputType == InputTypeMessage || m.inputType == InputTypeFeedback {
|
||||
// When completion menu is visible, let it handle navigation keys first
|
||||
if m.completion.Visible() {
|
||||
// ctrl+c always cancels, even with dropdown open
|
||||
if msg.String() == "ctrl+c" {
|
||||
return m, func() tea.Msg { return InputCancelMsg{} }
|
||||
}
|
||||
|
||||
var handled bool
|
||||
m.completion, cmd, handled = m.completion.Update(msg)
|
||||
if handled {
|
||||
// Check if a completion was selected
|
||||
if applied := m.completion.Apply(); applied != "" {
|
||||
m.textarea.SetValue(applied)
|
||||
m.textarea.CursorEnd()
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Key not handled by completion - pass to textarea and update completion
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
m.completion.CheckInput(m.textarea.Value())
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Normal key handling when completion menu is NOT visible
|
||||
switch msg.String() {
|
||||
case "ctrl+c":
|
||||
return m, func() tea.Msg { return InputCancelMsg{} }
|
||||
@@ -239,6 +267,11 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Open external editor (like huh does)
|
||||
return m, m.openEditor()
|
||||
|
||||
case "tab":
|
||||
// Tab without dropdown visible - do nothing special
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
return m, cmd
|
||||
|
||||
case "enter":
|
||||
// Intercept enter for submit (textarea handles alt+enter and ctrl+j for newlines)
|
||||
return m.handleSubmit()
|
||||
@@ -249,8 +282,9 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// Pass all other keys to textarea (including alt+enter, ctrl+j for newlines)
|
||||
// Pass all other keys to textarea, then check for slash completion
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
m.completion.CheckInput(m.textarea.Value())
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
@@ -276,6 +310,13 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// Forward all other messages to textarea (including blink ticks)
|
||||
if !m.suspended && (m.inputType == InputTypeMessage || m.inputType == InputTypeFeedback) {
|
||||
m.textarea, cmd = m.textarea.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
@@ -365,6 +406,11 @@ func (m *InputModel) View() string {
|
||||
case InputTypeMessage, InputTypeFeedback:
|
||||
parts = append(parts, m.textarea.View())
|
||||
|
||||
// Render completion dropdown if visible
|
||||
if m.completion.Visible() {
|
||||
parts = append(parts, m.completion.View())
|
||||
}
|
||||
|
||||
case InputTypeApproval:
|
||||
var options []string
|
||||
for i, option := range m.approvalOptions {
|
||||
@@ -411,7 +457,7 @@ func (m *InputModel) Clone() *InputModel {
|
||||
ta.ShowLineNumbers = false
|
||||
ta.Prompt = ""
|
||||
ta.SetHeight(5)
|
||||
ta.SetWidth(INPUT_WIDTH)
|
||||
ta.SetWidth(INPUT_WIDTH)
|
||||
ta.Focus()
|
||||
|
||||
// Configure keybindings
|
||||
@@ -446,6 +492,7 @@ func (m *InputModel) Clone() *InputModel {
|
||||
selectedOption: m.selectedOption,
|
||||
pendingApproval: m.pendingApproval, // Preserve approval decision
|
||||
styles: m.styles,
|
||||
completion: NewCompletionModel(m.completion.registry), // Preserve registry, start fresh state
|
||||
}
|
||||
|
||||
return clone
|
||||
@@ -495,3 +542,8 @@ func (m *InputModel) openEditor() tea.Cmd {
|
||||
return editorFinishedMsg{content: content, err: err}
|
||||
})
|
||||
}
|
||||
|
||||
// SetSlashRegistry sets the slash command registry for autocomplete
|
||||
func (m *InputModel) SetSlashRegistry(registry *slash.Registry) {
|
||||
m.completion.SetRegistry(registry)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/cline/cli/pkg/cli/slash"
|
||||
)
|
||||
|
||||
const maxVisibleCompletions = 7
|
||||
|
||||
// completionStyles holds the styling for the completion dropdown
|
||||
type completionStyles struct {
|
||||
menu lipgloss.Style
|
||||
selected lipgloss.Style
|
||||
normalName lipgloss.Style
|
||||
description lipgloss.Style
|
||||
scrollIndicator lipgloss.Style
|
||||
}
|
||||
|
||||
// newCompletionStyles creates the default styles for the completion dropdown
|
||||
func newCompletionStyles() completionStyles {
|
||||
return completionStyles{
|
||||
menu: lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("238")).
|
||||
Padding(0, 1),
|
||||
selected: lipgloss.NewStyle().
|
||||
Background(lipgloss.Color("62")).
|
||||
Foreground(lipgloss.Color("230")),
|
||||
normalName: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.AdaptiveColor{Light: "235", Dark: "252"}),
|
||||
description: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("243")),
|
||||
scrollIndicator: lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("243")),
|
||||
}
|
||||
}
|
||||
|
||||
// CompletionModel is a Bubbletea model for slash command autocomplete dropdown
|
||||
type CompletionModel struct {
|
||||
registry *slash.Registry
|
||||
visible bool
|
||||
matches []slash.Command
|
||||
index int // selected item (0-based)
|
||||
scroll int // scroll offset for long lists
|
||||
styles completionStyles
|
||||
|
||||
// pendingApply holds the command to apply after selection
|
||||
pendingApply string
|
||||
}
|
||||
|
||||
// NewCompletionModel creates a new completion model with the given registry
|
||||
func NewCompletionModel(registry *slash.Registry) CompletionModel {
|
||||
return CompletionModel{
|
||||
registry: registry,
|
||||
styles: newCompletionStyles(),
|
||||
}
|
||||
}
|
||||
|
||||
// SetRegistry sets the slash command registry
|
||||
func (m *CompletionModel) SetRegistry(registry *slash.Registry) {
|
||||
m.registry = registry
|
||||
}
|
||||
|
||||
// Visible returns whether the completion dropdown is currently visible
|
||||
func (m CompletionModel) Visible() bool {
|
||||
return m.visible
|
||||
}
|
||||
|
||||
// Update handles key messages for the completion dropdown.
|
||||
// Returns the updated model, any commands, and whether the key was handled.
|
||||
// If handled is true, the parent should NOT pass the key to the textarea.
|
||||
func (m CompletionModel) Update(msg tea.Msg) (CompletionModel, tea.Cmd, bool) {
|
||||
if !m.visible {
|
||||
return m, nil, false
|
||||
}
|
||||
|
||||
keyMsg, ok := msg.(tea.KeyMsg)
|
||||
if !ok {
|
||||
return m, nil, false
|
||||
}
|
||||
|
||||
switch keyMsg.String() {
|
||||
case "up":
|
||||
m.navigateUp()
|
||||
return m, nil, true
|
||||
|
||||
case "down":
|
||||
m.navigateDown()
|
||||
return m, nil, true
|
||||
|
||||
case "tab", "enter":
|
||||
// Select the current completion
|
||||
if len(m.matches) > 0 {
|
||||
selected := m.matches[m.index]
|
||||
m.pendingApply = "/" + selected.Name + " "
|
||||
}
|
||||
m.Hide()
|
||||
return m, nil, true
|
||||
|
||||
case "esc":
|
||||
m.Hide()
|
||||
return m, nil, true
|
||||
}
|
||||
|
||||
// Key not handled by completion - let parent process it
|
||||
return m, nil, false
|
||||
}
|
||||
|
||||
// CheckInput updates the completion state based on the current input value.
|
||||
// Call this after each input change to show/hide/update the dropdown.
|
||||
func (m *CompletionModel) CheckInput(value string) {
|
||||
if m.registry == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Only activate if input starts with "/" (first character requirement)
|
||||
if !strings.HasPrefix(value, "/") {
|
||||
m.Hide()
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the command being typed (everything after "/" until space/newline)
|
||||
rest := value[1:] // Everything after the "/"
|
||||
|
||||
// If there's whitespace, the command is complete - hide dropdown
|
||||
if idx := strings.IndexAny(rest, " \n\t"); idx != -1 {
|
||||
m.Hide()
|
||||
return
|
||||
}
|
||||
|
||||
// Update matches based on prefix
|
||||
m.updateMatches(rest)
|
||||
m.visible = len(m.matches) > 0
|
||||
}
|
||||
|
||||
// Apply returns the command string to insert (if any) and clears the pending state.
|
||||
// The parent should call this after Update returns handled=true for tab/enter.
|
||||
func (m *CompletionModel) Apply() string {
|
||||
result := m.pendingApply
|
||||
m.pendingApply = ""
|
||||
return result
|
||||
}
|
||||
|
||||
// Hide hides the completion dropdown and resets state
|
||||
func (m *CompletionModel) Hide() {
|
||||
m.visible = false
|
||||
m.matches = nil
|
||||
m.index = 0
|
||||
m.scroll = 0
|
||||
}
|
||||
|
||||
// View renders the completion dropdown
|
||||
func (m CompletionModel) View() string {
|
||||
if !m.visible || len(m.matches) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var lines []string
|
||||
|
||||
// Calculate visible range
|
||||
endIdx := min(m.scroll+maxVisibleCompletions, len(m.matches))
|
||||
|
||||
// Show scroll indicator if there are items above
|
||||
if m.scroll > 0 {
|
||||
lines = append(lines, m.styles.scrollIndicator.Render(" ↑ more"))
|
||||
}
|
||||
|
||||
// Find the longest command name for alignment
|
||||
maxNameLen := 0
|
||||
for _, cmd := range m.matches {
|
||||
nameLen := len(cmd.Name) + 1 // +1 for the "/"
|
||||
if nameLen > maxNameLen {
|
||||
maxNameLen = nameLen
|
||||
}
|
||||
}
|
||||
// Cap at reasonable width
|
||||
if maxNameLen > 15 {
|
||||
maxNameLen = 15
|
||||
}
|
||||
|
||||
// Render visible items
|
||||
for i := m.scroll; i < endIdx; i++ {
|
||||
cmd := m.matches[i]
|
||||
name := "/" + cmd.Name
|
||||
desc := cmd.Description
|
||||
|
||||
// Truncate description if too long
|
||||
maxDescLen := 35
|
||||
if len(desc) > maxDescLen {
|
||||
desc = desc[:maxDescLen-3] + "..."
|
||||
}
|
||||
|
||||
// Pad name for alignment
|
||||
paddedName := fmt.Sprintf("%-*s", maxNameLen, name)
|
||||
|
||||
if i == m.index {
|
||||
// Selected item - highlight the entire line
|
||||
line := fmt.Sprintf("> %s %s", paddedName, desc)
|
||||
lines = append(lines, m.styles.selected.Render(line))
|
||||
} else {
|
||||
// Normal item
|
||||
line := fmt.Sprintf(" %s %s", m.styles.normalName.Render(paddedName), m.styles.description.Render(desc))
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Show scroll indicator if there are items below
|
||||
if endIdx < len(m.matches) {
|
||||
lines = append(lines, m.styles.scrollIndicator.Render(" ↓ more"))
|
||||
}
|
||||
|
||||
return m.styles.menu.Render(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// updateMatches filters commands by prefix and updates the matches list
|
||||
func (m *CompletionModel) updateMatches(prefix string) {
|
||||
if m.registry == nil {
|
||||
m.matches = nil
|
||||
return
|
||||
}
|
||||
m.matches = m.registry.GetMatching(prefix)
|
||||
// Reset selection if out of bounds
|
||||
if m.index >= len(m.matches) {
|
||||
m.index = 0
|
||||
m.scroll = 0
|
||||
}
|
||||
m.adjustScroll()
|
||||
}
|
||||
|
||||
// navigateUp moves selection up in the dropdown
|
||||
func (m *CompletionModel) navigateUp() {
|
||||
if len(m.matches) == 0 {
|
||||
return
|
||||
}
|
||||
m.index--
|
||||
if m.index < 0 {
|
||||
m.index = len(m.matches) - 1
|
||||
}
|
||||
m.adjustScroll()
|
||||
}
|
||||
|
||||
// navigateDown moves selection down in the dropdown
|
||||
func (m *CompletionModel) navigateDown() {
|
||||
if len(m.matches) == 0 {
|
||||
return
|
||||
}
|
||||
m.index++
|
||||
if m.index >= len(m.matches) {
|
||||
m.index = 0
|
||||
}
|
||||
m.adjustScroll()
|
||||
}
|
||||
|
||||
// adjustScroll ensures the selected item is visible in the dropdown
|
||||
func (m *CompletionModel) adjustScroll() {
|
||||
if m.index < m.scroll {
|
||||
m.scroll = m.index
|
||||
} else if m.index >= m.scroll+maxVisibleCompletions {
|
||||
m.scroll = m.index - maxVisibleCompletions + 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package slash
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cline/grpc-go/client"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
)
|
||||
|
||||
// Command represents a slash command available for autocomplete
|
||||
type Command struct {
|
||||
Name string
|
||||
Description string
|
||||
Section string // "default", "custom", or "cli"
|
||||
CLICompatible bool
|
||||
}
|
||||
|
||||
// Registry holds available slash commands for autocomplete
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
commands []Command
|
||||
}
|
||||
|
||||
// CLI-local commands (handled by CLI, not sent to backend)
|
||||
var cliLocalCommands = []Command{
|
||||
{Name: "plan", Description: "Switch to plan mode", Section: "cli", CLICompatible: true},
|
||||
{Name: "act", Description: "Switch to act mode", Section: "cli", CLICompatible: true},
|
||||
{Name: "cancel", Description: "Cancel the current task", Section: "cli", CLICompatible: true},
|
||||
{Name: "exit", Description: "Exit follow mode", Section: "cli", CLICompatible: true},
|
||||
}
|
||||
|
||||
// NewRegistry creates a new slash command registry
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
commands: make([]Command, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// FetchFromBackend fetches available commands from cline-core backend
|
||||
func (r *Registry) FetchFromBackend(ctx context.Context, c *client.ClineClient) error {
|
||||
resp, err := c.Slash.GetAvailableSlashCommands(ctx, &cline.EmptyRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
// Start with CLI-local commands
|
||||
r.commands = append([]Command{}, cliLocalCommands...)
|
||||
|
||||
// Add backend commands (only CLI-compatible ones)
|
||||
for _, cmd := range resp.Commands {
|
||||
if cmd.CliCompatible {
|
||||
r.commands = append(r.commands, Command{
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
Section: cmd.Section,
|
||||
CLICompatible: cmd.CliCompatible,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCommands returns all available commands
|
||||
func (r *Registry) GetCommands() []Command {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
// Return a copy to avoid race conditions
|
||||
result := make([]Command, len(r.commands))
|
||||
copy(result, r.commands)
|
||||
return result
|
||||
}
|
||||
|
||||
// GetMatching returns commands that start with the given prefix (case-insensitive)
|
||||
func (r *Registry) GetMatching(prefix string) []Command {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
prefix = strings.ToLower(prefix)
|
||||
var matches []Command
|
||||
for _, cmd := range r.commands {
|
||||
if strings.HasPrefix(strings.ToLower(cmd.Name), prefix) {
|
||||
matches = append(matches, cmd)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
// IsValid checks if a command name is valid
|
||||
func (r *Registry) IsValid(name string) bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
name = strings.ToLower(name)
|
||||
for _, cmd := range r.commands {
|
||||
if strings.ToLower(cmd.Name) == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCLILocal checks if a command is handled locally by CLI (not sent to backend)
|
||||
func (r *Registry) IsCLILocal(name string) bool {
|
||||
name = strings.ToLower(name)
|
||||
for _, cmd := range cliLocalCommands {
|
||||
if strings.ToLower(cmd.Name) == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasCommands returns true if the registry has any commands loaded
|
||||
func (r *Registry) HasCommands() bool {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.commands) > 0
|
||||
}
|
||||
+8
-7
@@ -20,13 +20,14 @@ import (
|
||||
|
||||
// TaskOptions contains options for creating a task
|
||||
type TaskOptions struct {
|
||||
Images []string
|
||||
Files []string
|
||||
Mode string
|
||||
Settings []string
|
||||
Yolo bool
|
||||
Address string
|
||||
Verbose bool
|
||||
Images []string
|
||||
Files []string
|
||||
Mode string
|
||||
Settings []string
|
||||
Yolo bool
|
||||
Address string
|
||||
Verbose bool
|
||||
Workspaces []string
|
||||
}
|
||||
|
||||
func NewTaskCommand() *cobra.Command {
|
||||
|
||||
@@ -38,13 +38,13 @@ type InputHandler struct {
|
||||
// NewInputHandler creates a new input handler
|
||||
func NewInputHandler(manager *Manager, coordinator *StreamCoordinator, cancelFunc context.CancelFunc) *InputHandler {
|
||||
return &InputHandler{
|
||||
manager: manager,
|
||||
coordinator: coordinator,
|
||||
cancelFunc: cancelFunc,
|
||||
isRunning: false,
|
||||
pollTicker: time.NewTicker(500 * time.Millisecond),
|
||||
resultChan: make(chan output.InputSubmitMsg, 1),
|
||||
cancelChan: make(chan struct{}, 1),
|
||||
manager: manager,
|
||||
coordinator: coordinator,
|
||||
cancelFunc: cancelFunc,
|
||||
isRunning: false,
|
||||
pollTicker: time.NewTicker(500 * time.Millisecond),
|
||||
resultChan: make(chan output.InputSubmitMsg, 1),
|
||||
cancelChan: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,11 +281,12 @@ func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
|
||||
func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error) {
|
||||
currentMode := ih.manager.GetCurrentMode()
|
||||
|
||||
model := output.NewInputModel(
|
||||
model := output.NewInputModelWithRegistry(
|
||||
output.InputTypeMessage,
|
||||
"Cline is ready for your message...",
|
||||
"/plan or /act to switch modes\nctrl+e to open editor",
|
||||
"/plan or /act to switch modes\nctrl+e to open editor\ntab to autocomplete commands",
|
||||
currentMode,
|
||||
ih.manager.GetSlashRegistry(),
|
||||
)
|
||||
|
||||
return ih.runInputProgram(ctx, model)
|
||||
@@ -295,12 +296,13 @@ func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error
|
||||
func (ih *InputHandler) promptForApproval(ctx context.Context, msg *types.ClineMessage) (bool, string, error) {
|
||||
// Store the approval message for later use in determining auto-approval action
|
||||
ih.approvalMessage = msg
|
||||
|
||||
model := output.NewInputModel(
|
||||
|
||||
model := output.NewInputModelWithRegistry(
|
||||
output.InputTypeApproval,
|
||||
"Let Cline use this tool?",
|
||||
"",
|
||||
ih.manager.GetCurrentMode(),
|
||||
ih.manager.GetSlashRegistry(), // Pass registry for feedback input after approval
|
||||
)
|
||||
|
||||
message, shouldSend, err := ih.runInputProgram(ctx, model)
|
||||
@@ -395,7 +397,7 @@ func (ih *InputHandler) runInputProgram(ctx context.Context, model output.InputM
|
||||
// Need to collect feedback - will be handled by model state change
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
|
||||
// Check if NoAskAgain was selected
|
||||
if result.NoAskAgain && result.Approved && ih.approvalMessage != nil {
|
||||
// Determine which auto-approval action to enable
|
||||
@@ -411,7 +413,7 @@ func (ih *InputHandler) runInputProgram(ctx context.Context, model output.InputM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Store approval state for when feedback comes back
|
||||
ih.feedbackApproval = false
|
||||
ih.feedbackApproved = result.Approved
|
||||
@@ -441,6 +443,7 @@ func (w *inputProgramWrapper) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (w *inputProgramWrapper) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case output.InputSubmitMsg:
|
||||
// Handle input submission - clear the screen before quitting
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/cline/cli/pkg/cli/display"
|
||||
"github.com/cline/cli/pkg/cli/global"
|
||||
"github.com/cline/cli/pkg/cli/handlers"
|
||||
"github.com/cline/cli/pkg/cli/slash"
|
||||
"github.com/cline/cli/pkg/cli/types"
|
||||
"github.com/cline/grpc-go/client"
|
||||
"github.com/cline/grpc-go/cline"
|
||||
@@ -32,10 +33,12 @@ type Manager struct {
|
||||
clientAddress string
|
||||
state *types.ConversationState
|
||||
renderer *display.Renderer
|
||||
hookRenderer *display.HookRenderer
|
||||
toolRenderer *display.ToolRenderer
|
||||
systemRenderer *display.SystemMessageRenderer
|
||||
streamingDisplay *display.StreamingDisplay
|
||||
handlerRegistry *handlers.HandlerRegistry
|
||||
slashRegistry *slash.Registry
|
||||
isStreamingMode bool
|
||||
isInteractive bool
|
||||
currentMode string // "plan" or "act"
|
||||
@@ -46,6 +49,7 @@ func NewManager(client *client.ClineClient) *Manager {
|
||||
state := types.NewConversationState()
|
||||
renderer := display.NewRenderer(global.Config.OutputFormat)
|
||||
toolRenderer := display.NewToolRenderer(renderer.GetMdRenderer(), global.Config.OutputFormat)
|
||||
hookRenderer := display.NewHookRenderer(renderer.GetMdRenderer(), global.Config.OutputFormat)
|
||||
systemRenderer := display.NewSystemMessageRenderer(renderer, renderer.GetMdRenderer(), global.Config.OutputFormat)
|
||||
streamingDisplay := display.NewStreamingDisplay(state, renderer)
|
||||
|
||||
@@ -59,10 +63,12 @@ func NewManager(client *client.ClineClient) *Manager {
|
||||
clientAddress: "", // Will be set when client is provided
|
||||
state: state,
|
||||
renderer: renderer,
|
||||
hookRenderer: hookRenderer,
|
||||
toolRenderer: toolRenderer,
|
||||
systemRenderer: systemRenderer,
|
||||
streamingDisplay: streamingDisplay,
|
||||
handlerRegistry: registry,
|
||||
slashRegistry: slash.NewRegistry(),
|
||||
currentMode: "plan", // Default mode
|
||||
}
|
||||
}
|
||||
@@ -76,6 +82,10 @@ func NewManagerForAddress(ctx context.Context, address string) (*Manager, error)
|
||||
|
||||
manager := NewManager(client)
|
||||
manager.clientAddress = address
|
||||
|
||||
// Fetch slash commands from backend (non-blocking, errors are logged)
|
||||
manager.fetchSlashCommands(ctx)
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
@@ -93,9 +103,25 @@ func NewManagerForDefault(ctx context.Context) (*Manager, error) {
|
||||
manager.clientAddress = global.Clients.GetRegistry().GetDefaultInstance()
|
||||
}
|
||||
|
||||
// Fetch slash commands from backend (non-blocking, errors are logged)
|
||||
manager.fetchSlashCommands(ctx)
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
// fetchSlashCommands fetches available slash commands from the backend
|
||||
// This is non-blocking and errors are logged but don't prevent manager creation
|
||||
func (m *Manager) fetchSlashCommands(ctx context.Context) {
|
||||
if err := m.slashRegistry.FetchFromBackend(ctx, m.client); err != nil {
|
||||
if global.Config.Verbose {
|
||||
m.renderer.RenderDebug("Failed to fetch slash commands: %v", err)
|
||||
}
|
||||
// Non-fatal: CLI-local commands are still available
|
||||
} else if global.Config.Verbose {
|
||||
m.renderer.RenderDebug("Loaded %d slash commands", len(m.slashRegistry.GetCommands()))
|
||||
}
|
||||
}
|
||||
|
||||
// SwitchToInstance switches the manager to use a different Cline instance
|
||||
func (m *Manager) SwitchToInstance(ctx context.Context, address string) error {
|
||||
m.mu.Lock()
|
||||
@@ -966,6 +992,15 @@ func (m *Manager) processStateUpdate(stateUpdate *cline.State, coordinator *Stre
|
||||
coordinator.MarkProcessedInCurrentTurn(msgKey)
|
||||
}
|
||||
|
||||
case msg.Say == string(types.SayTypeCommandPermissionDenied):
|
||||
msgKey := fmt.Sprintf("%d", msg.Timestamp)
|
||||
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
|
||||
fmt.Println()
|
||||
m.displayMessage(msg, false, false, i)
|
||||
|
||||
coordinator.MarkProcessedInCurrentTurn(msgKey)
|
||||
}
|
||||
|
||||
case msg.Say == string(types.SayTypeBrowserActionLaunch):
|
||||
msgKey := fmt.Sprintf("%d", msg.Timestamp)
|
||||
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
|
||||
@@ -1020,6 +1055,26 @@ func (m *Manager) processStateUpdate(stateUpdate *cline.State, coordinator *Stre
|
||||
coordinator.MarkProcessedInCurrentTurn(msgKey)
|
||||
}
|
||||
|
||||
case msg.Say == string(types.SayTypeHookStatus):
|
||||
msgKey := fmt.Sprintf("%d", msg.Timestamp)
|
||||
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
|
||||
fmt.Println()
|
||||
m.displayMessage(msg, false, false, i)
|
||||
|
||||
coordinator.MarkProcessedInCurrentTurn(msgKey)
|
||||
}
|
||||
|
||||
case msg.Say == string(types.SayTypeHookOutputStream):
|
||||
// Hook stdout/stderr streaming arrives as hook_output_stream messages.
|
||||
// These are intentionally suppressed unless verbose (see SayHandler.handleHookOutputStream),
|
||||
// but we still need to route them through the normal handler pipeline in streaming/follow
|
||||
// mode so verbose users actually see `HOOK> ...` lines.
|
||||
msgKey := fmt.Sprintf("%d", msg.Timestamp)
|
||||
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
|
||||
m.displayMessage(msg, false, false, i)
|
||||
coordinator.MarkProcessedInCurrentTurn(msgKey)
|
||||
}
|
||||
|
||||
case msg.Say == string(types.SayTypeAPIReqStarted):
|
||||
msgKey := fmt.Sprintf("%d", msg.Timestamp)
|
||||
apiInfo := types.APIRequestInfo{Cost: -1}
|
||||
@@ -1151,12 +1206,14 @@ func (m *Manager) displayMessage(msg *types.ClineMessage, isLast, isPartial bool
|
||||
m.mu.RUnlock()
|
||||
|
||||
dc := &handlers.DisplayContext{
|
||||
State: m.state,
|
||||
Renderer: m.renderer,
|
||||
ToolRenderer: m.toolRenderer,
|
||||
SystemRenderer: m.systemRenderer,
|
||||
State: m.state,
|
||||
Renderer: m.renderer,
|
||||
ToolRenderer: m.toolRenderer,
|
||||
HookRenderer: m.hookRenderer,
|
||||
SystemRenderer: m.systemRenderer,
|
||||
IsLast: isLast,
|
||||
IsPartial: isPartial,
|
||||
Verbose: global.Config.Verbose,
|
||||
MessageIndex: messageIndex,
|
||||
IsStreamingMode: isStreaming,
|
||||
IsInteractive: isInteractive,
|
||||
@@ -1263,6 +1320,11 @@ func (m *Manager) GetCurrentMode() string {
|
||||
return m.currentMode
|
||||
}
|
||||
|
||||
// GetSlashRegistry returns the slash command registry
|
||||
func (m *Manager) GetSlashRegistry() *slash.Registry {
|
||||
return m.slashRegistry
|
||||
}
|
||||
|
||||
// extractModeFromState extracts the current mode from state JSON
|
||||
func (m *Manager) extractModeFromState(stateJson string) string {
|
||||
var rawState map[string]interface{}
|
||||
|
||||
@@ -296,6 +296,12 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
|
||||
return err
|
||||
}
|
||||
settings.HooksEnabled = boolPtr(val)
|
||||
case "azure_identity":
|
||||
val, err := parseBool(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
settings.AzureIdentity = boolPtr(val)
|
||||
|
||||
// Integer fields
|
||||
case "request_timeout_ms":
|
||||
|
||||
@@ -3,15 +3,16 @@ package types
|
||||
// HistoryItem represents a task history item from taskHistory.json
|
||||
// This struct matches the JSON format stored on disk
|
||||
type HistoryItem struct {
|
||||
Id string `json:"id"`
|
||||
Ulid string `json:"ulid,omitempty"`
|
||||
Ts int64 `json:"ts"`
|
||||
Task string `json:"task"`
|
||||
TokensIn int32 `json:"tokensIn"`
|
||||
TokensOut int32 `json:"tokensOut"`
|
||||
CacheWrites int32 `json:"cacheWrites,omitempty"`
|
||||
CacheReads int32 `json:"cacheReads,omitempty"`
|
||||
TotalCost float64 `json:"totalCost"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
IsFavorited bool `json:"isFavorited,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Ulid string `json:"ulid,omitempty"`
|
||||
Ts int64 `json:"ts"`
|
||||
Task string `json:"task"`
|
||||
TokensIn int32 `json:"tokensIn"`
|
||||
TokensOut int32 `json:"tokensOut"`
|
||||
CacheWrites int32 `json:"cacheWrites,omitempty"`
|
||||
CacheReads int32 `json:"cacheReads,omitempty"`
|
||||
TotalCost float64 `json:"totalCost"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
IsFavorited bool `json:"isFavorited,omitempty"`
|
||||
WorkspacePaths []string `json:"workspacePaths,omitempty"`
|
||||
}
|
||||
|
||||
@@ -47,11 +47,11 @@ const (
|
||||
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"
|
||||
AskTypeCondense AskType = "condense"
|
||||
AskTypeReportBug AskType = "report_bug"
|
||||
AskTypeBrowserActionLaunch AskType = "browser_action_launch"
|
||||
AskTypeUseMcpServer AskType = "use_mcp_server"
|
||||
AskTypeNewTask AskType = "new_task"
|
||||
AskTypeCondense AskType = "condense"
|
||||
AskTypeReportBug AskType = "report_bug"
|
||||
)
|
||||
|
||||
// SayType represents different types of SAY messages
|
||||
@@ -87,6 +87,11 @@ const (
|
||||
SayTypeLoadMcpDocumentation SayType = "load_mcp_documentation"
|
||||
SayTypeInfo SayType = "info"
|
||||
SayTypeTaskProgress SayType = "task_progress"
|
||||
// Hook status streaming from the backend.
|
||||
// These values must match the backend "say" strings emitted by the extension.
|
||||
SayTypeHookStatus SayType = "hook_status"
|
||||
SayTypeHookOutputStream SayType = "hook_output_stream"
|
||||
SayTypeCommandPermissionDenied SayType = "command_permission_denied"
|
||||
)
|
||||
|
||||
// ToolMessage represents a tool-related message
|
||||
@@ -145,6 +150,42 @@ type APIRequestRetryStatus struct {
|
||||
ErrorSnippet string `json:"errorSnippet,omitempty"`
|
||||
}
|
||||
|
||||
// HookMessage represents hook execution metadata sent from the backend
|
||||
type HookMessage struct {
|
||||
HookName string `json:"hookName"` // Type of hook (TaskStart, PreToolUse, etc.)
|
||||
ToolName string `json:"toolName,omitempty"` // Optional tool name for tool-specific hooks
|
||||
Status string `json:"status"` // "running", "completed", "cancelled", or "failed"
|
||||
ScriptPaths []string `json:"scriptPaths,omitempty"` // Full paths to hook script(s)
|
||||
PendingToolInfo *ToolInfo `json:"pendingToolInfo,omitempty"` // Metadata about the pending tool execution (PreToolUse)
|
||||
ExitCode int `json:"exitCode,omitempty"` // Exit code for completed/failed hooks
|
||||
HasJsonResponse bool `json:"hasJsonResponse,omitempty"` // Whether hook returned JSON
|
||||
Error *HookError `json:"error,omitempty"` // Error details if hook failed
|
||||
}
|
||||
|
||||
// ToolInfo represents a compact subset of tool parameters for UI display.
|
||||
// This mirrors the extension's pendingToolInfo shape and is used by the CLI to
|
||||
// show what tool the PreToolUse hook is gating.
|
||||
type ToolInfo struct {
|
||||
Tool string `json:"tool"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Diff string `json:"diff,omitempty"`
|
||||
Regex string `json:"regex,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
McpTool string `json:"mcpTool,omitempty"`
|
||||
McpServer string `json:"mcpServer,omitempty"`
|
||||
ResourceUri string `json:"resourceUri,omitempty"`
|
||||
}
|
||||
|
||||
// HookError represents structured error information from a failed hook
|
||||
type HookError struct {
|
||||
Type string `json:"type"` // Error type: "execution", "timeout", "validation", etc.
|
||||
Message string `json:"message"` // Human-readable error message
|
||||
Details string `json:"details,omitempty"` // Additional error details
|
||||
ScriptPath string `json:"scriptPath,omitempty"` // Path to script that failed
|
||||
}
|
||||
|
||||
// GetTimestamp returns a formatted timestamp string
|
||||
func (m *ClineMessage) GetTimestamp() string {
|
||||
return time.Unix(m.Timestamp/1000, 0).Format("15:04:05")
|
||||
@@ -324,6 +365,12 @@ func convertProtoSayType(sayType cline.ClineSay) string {
|
||||
return string(SayTypeInfo)
|
||||
case cline.ClineSay_TASK_PROGRESS:
|
||||
return string(SayTypeTaskProgress)
|
||||
case cline.ClineSay_HOOK_STATUS:
|
||||
return string(SayTypeHookStatus)
|
||||
case cline.ClineSay_HOOK_OUTPUT_STREAM:
|
||||
return string(SayTypeHookOutputStream)
|
||||
case cline.ClineSay_COMMAND_PERMISSION_DENIED:
|
||||
return string(SayTypeCommandPermissionDenied)
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -183,3 +185,72 @@ DEBUGGING STEPS:
|
||||
For additional help, visit: https://github.com/cline/cline/issues
|
||||
`, maxRetries, lastErr, GetNodeVersion())
|
||||
}
|
||||
|
||||
// validateDirsExist validates that all workspace paths exist on the filesystem
|
||||
func ValidateDirsExist(paths []string) error {
|
||||
for _, p := range paths {
|
||||
info, err := os.Stat(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("path does not exist: %s", p)
|
||||
}
|
||||
return fmt.Errorf("failed to access path %s: %w", p, err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return fmt.Errorf("path is not a directory: %s", p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// absPath returns the absolute path, resolving symlinks
|
||||
func AbsPath(path string) (string, error) {
|
||||
// First get absolute path
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Then resolve any symlinks
|
||||
resolved, err := filepath.EvalSymlinks(abs)
|
||||
if err != nil {
|
||||
// If symlink resolution fails, return the absolute path
|
||||
return abs, nil
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
// shortenPath shortens a filesystem path to fit within maxLen
|
||||
func ShortenPath(path string, maxLen int) string {
|
||||
// Try to replace home directory with ~ (cross-platform)
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
if strings.HasPrefix(path, homeDir) {
|
||||
shortened := "~" + path[len(homeDir):]
|
||||
// Always use ~ version if we can
|
||||
path = shortened
|
||||
}
|
||||
}
|
||||
|
||||
if len(path) <= maxLen {
|
||||
return path
|
||||
}
|
||||
|
||||
// If still too long, show last few path components
|
||||
if len(path) > maxLen {
|
||||
parts := strings.Split(path, string(filepath.Separator))
|
||||
if len(parts) > 2 {
|
||||
// Show last 2-3 components
|
||||
lastParts := parts[len(parts)-2:]
|
||||
shortened := "..." + string(filepath.Separator) + strings.Join(lastParts, string(filepath.Separator))
|
||||
if len(shortened) <= maxLen {
|
||||
return shortened
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: truncate with ellipsis
|
||||
if len(path) > maxLen {
|
||||
return "..." + path[len(path)-maxLen+3:]
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -16,15 +16,17 @@ import (
|
||||
type GrpcServer struct {
|
||||
port int
|
||||
verbose bool
|
||||
workspaces []string
|
||||
server *grpc.Server
|
||||
shutdownCh chan struct{}
|
||||
}
|
||||
|
||||
// NewGrpcServer creates a new GrpcServer
|
||||
func NewGrpcServer(port int, verbose bool) *GrpcServer {
|
||||
func NewGrpcServer(port int, verbose bool, workspaces []string) *GrpcServer {
|
||||
return &GrpcServer{
|
||||
port: port,
|
||||
verbose: verbose,
|
||||
workspaces: workspaces,
|
||||
shutdownCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@@ -50,7 +52,7 @@ func (s *GrpcServer) Start(ctx context.Context) error {
|
||||
grpc_health_v1.RegisterHealthServer(s.server, healthServer)
|
||||
|
||||
// Register services
|
||||
workspaceService := NewSimpleWorkspaceService(s.verbose)
|
||||
workspaceService := NewSimpleWorkspaceService(s.verbose, s.workspaces)
|
||||
host.RegisterWorkspaceServiceServer(s.server, workspaceService)
|
||||
|
||||
windowService := NewWindowService(s.verbose)
|
||||
|
||||
@@ -12,13 +12,15 @@ import (
|
||||
// SimpleWorkspaceService implements a basic workspace service without complex dependencies
|
||||
type SimpleWorkspaceService struct {
|
||||
host.UnimplementedWorkspaceServiceServer
|
||||
verbose bool
|
||||
verbose bool
|
||||
workspaces []string
|
||||
}
|
||||
|
||||
// NewSimpleWorkspaceService creates a new SimpleWorkspaceService
|
||||
func NewSimpleWorkspaceService(verbose bool) *SimpleWorkspaceService {
|
||||
func NewSimpleWorkspaceService(verbose bool, workspaces []string) *SimpleWorkspaceService {
|
||||
return &SimpleWorkspaceService{
|
||||
verbose: verbose,
|
||||
verbose: verbose,
|
||||
workspaces: workspaces,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,14 +30,24 @@ func (s *SimpleWorkspaceService) GetWorkspacePaths(ctx context.Context, req *hos
|
||||
log.Printf("GetWorkspacePaths called")
|
||||
}
|
||||
|
||||
// Get current working directory as the workspace
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
paths := []string{}
|
||||
|
||||
if len(s.workspaces) == 0 {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paths = append(paths, cwd)
|
||||
} else {
|
||||
paths = s.workspaces
|
||||
}
|
||||
|
||||
if s.verbose {
|
||||
log.Printf("Returning configured workspaces: %v", paths)
|
||||
}
|
||||
|
||||
return &host.GetWorkspacePathsResponse{
|
||||
Paths: []string{cwd},
|
||||
Paths: paths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -95,6 +95,12 @@ INSTANT TASK OPTIONS
|
||||
-m, --mode mode
|
||||
Starting mode. Options: act (default), plan
|
||||
|
||||
-w, --workspace path
|
||||
Additional workspace paths. Can be specified multiple times to
|
||||
include multiple directories. The current working directory is
|
||||
always included as the first workspace. Example: cline -w
|
||||
/path/to/other/project "refactor shared code"
|
||||
|
||||
GLOBAL OPTIONS
|
||||
These options apply to all subcommands:
|
||||
|
||||
@@ -278,6 +284,29 @@ TASK SETTINGS
|
||||
|
||||
mode Starting mode (act/plan)
|
||||
|
||||
hooks_enabled
|
||||
Enable or disable hooks for the task (true/false)
|
||||
|
||||
HOOKS INTEGRATION
|
||||
Hooks let you inject custom logic into Cline's workflow at key moments.
|
||||
They can validate operations before they execute, monitor tool usage,
|
||||
and shape AI decisions. This allows you to integrate hooks into
|
||||
automated workflows, CI/CD pipelines, and headless task execution.
|
||||
|
||||
Enable hooks for a task:
|
||||
|
||||
cline "prompt" -s hooks_enabled=true
|
||||
|
||||
Configure hooks globally:
|
||||
|
||||
cline config set hooks-enabled=true
|
||||
cline config get hooks-enabled
|
||||
|
||||
Note: Hooks in the CLI are only supported on macOS and Linux.
|
||||
|
||||
For complete hooks documentation, see:
|
||||
<https://docs.cline.bot/features/hooks/index>
|
||||
|
||||
NOTES & EXAMPLES
|
||||
The cline task send and cline task new commands support reading from
|
||||
stdin, enabling powerful pipeline compositions:
|
||||
@@ -348,6 +377,43 @@ COPYRIGHT
|
||||
Copyright © 2025 Cline Bot Inc. Licensed under the Apache License 2.0.
|
||||
```
|
||||
|
||||
## JSON output (-F json)
|
||||
|
||||
When you run a command with `-F json` (or `--output-format json`), Cline prints each client message as JSON.
|
||||
|
||||
### ClineMessage schema
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `type` | `"ask" or "say"` | Yes | Top-level message category. |
|
||||
| `text` | `string` | Yes | Human-readable message content. |
|
||||
| `ts` | `number` | Yes | Unix epoch timestamp in milliseconds. |
|
||||
| `reasoning` | `string` | No | Omitted when empty. |
|
||||
| `say` | `string` | No | Omitted when empty. Present when `type` is `"say"`. |
|
||||
| `ask` | `string` | No | Omitted when empty. Present when `type` is `"ask"`. |
|
||||
| `partial` | `boolean` | No | Omitted when false. `true` for streaming updates. |
|
||||
| `images` | `string[]` | No | Omitted when empty. Image URIs when included with a message. |
|
||||
| `files` | `string[]` | No | Omitted when empty. File paths when attached to a message. |
|
||||
| `lastCheckpointHash` | `string` | No | Omitted when empty. Git checkpoint hash when available. |
|
||||
| `isCheckpointCheckedOut` | `boolean` | No | Omitted when false. `true` if Cline checked out a checkpoint. |
|
||||
| `isOperationOutsideWorkspace` | `boolean` | No | Omitted when false. `true` if an operation happened outside the workspace. |
|
||||
|
||||
<Note>
|
||||
Most fields are optional and omitted when empty. If you parse this output, treat missing fields as “not present”, not as empty strings.
|
||||
</Note>
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "say",
|
||||
"text": "Cline is about to run a command.",
|
||||
"ts": 1760501486669,
|
||||
"say": "command",
|
||||
"partial": false
|
||||
}
|
||||
```
|
||||
|
||||
### Shell Completion
|
||||
|
||||
Generate autocompletion scripts for various shells:
|
||||
|
||||
@@ -57,6 +57,20 @@ During installation, you'll authenticate and configure your preferred provider u
|
||||
- Create GitLab pipelines that generate migration scripts from schema changes
|
||||
- Build Jenkins jobs that use Cline to analyze test failures and suggest fixes
|
||||
|
||||
## Hooks integration
|
||||
|
||||
[Hooks](/features/hooks/index) let you inject custom logic into Cline's workflow to validate operations and enforce policies. You can enable hooks when running tasks from the command line:
|
||||
|
||||
```bash
|
||||
# Enable hooks for a task
|
||||
cline "What does this repo do?" -s hooks_enabled=true
|
||||
|
||||
# Configure hooks globally via CLI
|
||||
cline config set hooks-enabled=true
|
||||
```
|
||||
|
||||
This allows you to integrate hooks into automated workflows, CI/CD pipelines, and headless task execution for consistent enforcement across all environments.
|
||||
|
||||
## Learn more
|
||||
|
||||
<Columns cols={2}>
|
||||
|
||||
+5
-1
@@ -115,6 +115,7 @@
|
||||
},
|
||||
"features/auto-approve",
|
||||
"features/auto-compact",
|
||||
"features/background-edit",
|
||||
"features/checkpoints",
|
||||
"features/cline-rules",
|
||||
{
|
||||
@@ -149,6 +150,7 @@
|
||||
},
|
||||
"features/multiroot-workspace",
|
||||
"features/plan-and-act",
|
||||
"features/skills",
|
||||
{
|
||||
"group": "Slash Commands",
|
||||
"pages": [
|
||||
@@ -278,6 +280,7 @@
|
||||
"pages": [
|
||||
"enterprise-solutions/overview",
|
||||
"enterprise-solutions/onboarding",
|
||||
"enterprise-solutions/sso-setup",
|
||||
"enterprise-solutions/team-management/managing-members",
|
||||
{
|
||||
"group": "SaaS Provider Configuration",
|
||||
@@ -317,7 +320,8 @@
|
||||
"pages": [
|
||||
"enterprise-solutions/monitoring/overview",
|
||||
"enterprise-solutions/monitoring/telemetry",
|
||||
"enterprise-solutions/monitoring/opentelemetry"
|
||||
"enterprise-solutions/monitoring/opentelemetry",
|
||||
"enterprise-solutions/monitoring/opentelemetry_override"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ Check which models are available in your region first. Some newer models might n
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/AWS%20Remote%20Config.gif"
|
||||
src="https://assets.int.cline.bot/assets/AWS%20Remote%20Config.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ If you don't have AWS credentials yet, reach out to your IT or cloud team to get
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline-static-assets-prod/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
src="https://assets.int.cline.bot/assets/VS%20Code%20Bedrock%20API%20Key.gif"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
|
||||
@@ -42,76 +42,45 @@ Cline supports three OTLP export protocols:
|
||||
- **HTTP/protobuf**
|
||||
- **HTTP/JSON**
|
||||
|
||||
### Export Destinations
|
||||
|
||||
You can export to:
|
||||
- **Console** (for testing)
|
||||
- **OTLP endpoint** (your own collector or observability platform)
|
||||
|
||||
## Configuration
|
||||
|
||||
OpenTelemetry is configured using environment variables before launching Cline.
|
||||
OpenTelemetry is configured using [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works) from the [dashboard](https://app.cline.bot/dashboard/organization?tab=settings).
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Enable OpenTelemetry and configure an OTLP endpoint:
|
||||
Enable OpenTelemetry, configure an OTLP endpoint and select a protocol:
|
||||
|
||||
```bash
|
||||
# Enable OpenTelemetry
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_main_options.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
# Configure metrics and logs export
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
If you're using gRPC, you can opt out of TLS.
|
||||
|
||||
# Set your OTLP endpoint
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
|
||||
Once the collector has been configured, you can enable logs and/or metrics collection. At least one of them needs to be enabled.
|
||||
|
||||
# Optional: Set protocol (default is grpc)
|
||||
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`1` or `true`) | Disabled |
|
||||
| `OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
|
||||
| `OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
|
||||
| `OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
|
||||
You only need to configure it further if you need an advanced configuration.
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
**Separate endpoints for metrics and logs:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
|
||||
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
|
||||
```
|
||||
You can add custom protocols and endpoints for both, logs and metrics. You can also configure the metrics export interval, and the logs batch size, batch timeout and max queue size.
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_metrics_and_logs.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
**Custom headers for authentication:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
|
||||
```
|
||||
|
||||
**Multiple exporters (console + OTLP):**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console,otlp
|
||||
export OTEL_LOGS_EXPORTER=console,otlp
|
||||
```
|
||||
Finally, if your collector needs authentication headers, you can add key value pairs in the headers section.
|
||||
|
||||
**Export intervals:**
|
||||
```bash
|
||||
# Metrics export interval in milliseconds (default: 60000)
|
||||
export OTEL_METRIC_EXPORT_INTERVAL=30000
|
||||
|
||||
# Logs batch size and timeout
|
||||
export OTEL_LOG_BATCH_SIZE=512
|
||||
export OTEL_LOG_BATCH_TIMEOUT=5000
|
||||
export OTEL_LOG_MAX_QUEUE_SIZE=2048
|
||||
```
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_headers.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Integration Examples
|
||||
|
||||
@@ -119,73 +88,46 @@ export OTEL_LOG_MAX_QUEUE_SIZE=2048
|
||||
|
||||
Export to Datadog using their OTLP endpoint:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
|
||||
```
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_datadog_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### New Relic
|
||||
|
||||
Export to New Relic:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
|
||||
```
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_relic_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
### Grafana Cloud
|
||||
|
||||
Export to Grafana Cloud:
|
||||
|
||||
```bash
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=otlp
|
||||
export OTEL_LOGS_EXPORTER=otlp
|
||||
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
|
||||
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
|
||||
```
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
src="https://assets.int.cline.bot/assets/open_telemetry_grafana_example.png"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
Test your configuration with console output before sending to a real endpoint:
|
||||
|
||||
```bash
|
||||
# Enable console output to see what data would be exported
|
||||
export OTEL_TELEMETRY_ENABLED=1
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
Then launch Cline and check the console output for metrics and logs.
|
||||
To test your configuration, log in to your account, perform some actions in a task, wait for the export interval, and verify that the data has arrived at your collector.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Data Being Exported
|
||||
If you aren’t getting any data in your collector, the easiest way to verify your integration is to enable the developer tools in your editor.
|
||||
|
||||
1. **Verify OpenTelemetry is enabled:**
|
||||
```bash
|
||||
echo $OTEL_TELEMETRY_ENABLED
|
||||
```
|
||||
Should output `1` or `true`
|
||||
To do this, open the [webview developer tools](https://code.visualstudio.com/api/extension-guides/webview#inspecting-and-debugging-webviews).
|
||||
|
||||
2. **Check exporters are configured:**
|
||||
```bash
|
||||
echo $OTEL_METRICS_EXPORTER
|
||||
echo $OTEL_LOGS_EXPORTER
|
||||
```
|
||||
Once you’ve done so, if you perform some actions that trigger metrics and/or logs (such as doing a task with Cline),
|
||||
you will see error logs if any error occurs when sending the data to your collector.
|
||||
|
||||
3. **Test with console exporter first:**
|
||||
```bash
|
||||
export OTEL_METRICS_EXPORTER=console
|
||||
export OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
If you don't see any logs, enable [debug mode](#debug-mode).
|
||||
|
||||
### Connection Errors
|
||||
|
||||
@@ -194,10 +136,7 @@ Then launch Cline and check the console output for metrics and logs.
|
||||
curl -v https://your-otlp-endpoint:4317
|
||||
```
|
||||
|
||||
2. **Check if insecure mode is needed:**
|
||||
```bash
|
||||
export OTEL_EXPORTER_OTLP_INSECURE=true
|
||||
```
|
||||
2. **Check if insecure mode is needed** by opting out of TLS
|
||||
|
||||
3. **Verify authentication headers:**
|
||||
Double-check your API keys and authentication headers are correct
|
||||
@@ -207,7 +146,7 @@ Then launch Cline and check the console output for metrics and logs.
|
||||
Enable debug logging to see detailed OpenTelemetry information:
|
||||
|
||||
```bash
|
||||
export TEL_DEBUG_DIAGNOSTICS=true
|
||||
TEL_DEBUG_DIAGNOSTICS=true code .
|
||||
```
|
||||
|
||||
This will output detailed information about:
|
||||
@@ -218,7 +157,7 @@ This will output detailed information about:
|
||||
|
||||
## What Gets Exported
|
||||
|
||||
When Opentelemetry is enabled, Cline exports:
|
||||
When OpenTelemetry is enabled, Cline exports:
|
||||
|
||||
### Metrics
|
||||
- Feature usage counts
|
||||
@@ -238,9 +177,9 @@ Exported data is already anonymous and doesn't include code content, file paths,
|
||||
## Limitations
|
||||
|
||||
Current OpenTelemetry support in Cline:
|
||||
- ✅ OTLP metrics export (console, gRPC, HTTP)
|
||||
- ✅ OTLP logs export (console, gRPC, HTTP)
|
||||
- ✅ Basic configuration via environment variables
|
||||
- ✅ OTLP metrics export (gRPC, HTTP)
|
||||
- ✅ OTLP logs export (gRPC, HTTP)
|
||||
- ✅ Basic configuration via [Remote Configuration](/enterprise-solutions/configuration/remote-configuration/overview#how-remote-configuration-works)
|
||||
- ❌ Distributed tracing (not yet implemented)
|
||||
- ❌ Custom instrumentation API (not yet exposed)
|
||||
- ❌ Sampling configuration (uses defaults)
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
title: "OpenTelemetry Integration Override"
|
||||
sidebarTitle: "OpenTelemetry Override"
|
||||
description: "Export Cline telemetry to your observability platform using OpenTelemetry Protocol (OTLP)"
|
||||
---
|
||||
|
||||
Cline includes opt-in OpenTelemetry support for exporting metrics and logs to your own observability infrastructure using the OpenTelemetry Protocol (OTLP).
|
||||
|
||||
<Note>
|
||||
OpenTelemetry integration is **optional** and intended for advanced users with existing observability infrastructure. Most users won't need this feature.
|
||||
</Note>
|
||||
|
||||
## What is OpenTelemetry?
|
||||
|
||||
[OpenTelemetry](https://opentelemetry.io/) is an industry-standard observability framework that provides a unified way to collect and export telemetry data (metrics, logs, and traces).
|
||||
|
||||
Cline's OpenTelemetry support allows you to:
|
||||
- Export telemetry to your own systems
|
||||
- Integrate with observability platforms like Datadog, New Relic, Grafana Cloud, etc.
|
||||
- Maintain full control over your monitoring data
|
||||
- Use your organization's existing monitoring infrastructure
|
||||
|
||||
## Supported Features
|
||||
|
||||
Cline supports OpenTelemetry's **OTLP (OpenTelemetry Protocol)** export with:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Metrics Export" icon="chart-bar">
|
||||
Export metrics about Cline usage, performance, and errors
|
||||
</Card>
|
||||
|
||||
<Card title="Logs Export" icon="file-lines">
|
||||
Export structured logs for debugging and analysis
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Export Formats
|
||||
|
||||
Cline supports three OTLP export protocols:
|
||||
|
||||
- **gRPC** (default, recommended)
|
||||
- **HTTP/protobuf**
|
||||
- **HTTP/JSON**
|
||||
|
||||
### Export Destinations
|
||||
|
||||
You can export to:
|
||||
- **Console** (for testing)
|
||||
- **OTLP endpoint** (your own collector or observability platform)
|
||||
|
||||
## Configuration
|
||||
|
||||
OpenTelemetry is configured using environment variables before launching Cline.
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Enable OpenTelemetry and configure an OTLP endpoint:
|
||||
|
||||
```bash
|
||||
# Enable OpenTelemetry
|
||||
export CLINE_OTEL_TELEMETRY_ENABLED=true
|
||||
|
||||
# Configure metrics and logs export
|
||||
export CLINE_OTEL_METRICS_EXPORTER=otlp
|
||||
export CLINE_OTEL_LOGS_EXPORTER=otlp
|
||||
|
||||
# Set your OTLP endpoint
|
||||
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://your-collector:4317
|
||||
|
||||
# Optional: Set protocol (default is grpc)
|
||||
export CLINE_OTEL_EXPORTER_OTLP_PROTOCOL=grpc
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `CLINE_OTEL_TELEMETRY_ENABLED` | Enable OpenTelemetry (`true`) | Disabled |
|
||||
| `CLINE_OTEL_METRICS_EXPORTER` | Metrics exporter type (`console`, `otlp`, or both) | None |
|
||||
| `CLINE_OTEL_LOGS_EXPORTER` | Logs exporter type (`console`, `otlp`, or both) | None |
|
||||
| `CLINE_OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint URL | None |
|
||||
| `CLINE_OTEL_EXPORTER_OTLP_PROTOCOL` | Protocol (`grpc`, `http/protobuf`, `http/json`) | `grpc` |
|
||||
| `CLINE_OTEL_EXPORTER_OTLP_INSECURE` | Allow insecure connections | `false` |
|
||||
| `CLINE_OTEL_EXPORTER_OTLP_HEADERS` | Custom headers (comma-separated `key=value` pairs) | None |
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
**Separate endpoints for metrics and logs:**
|
||||
```bash
|
||||
export CLINE_OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics-collector:4317
|
||||
export CLINE_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://logs-collector:4317
|
||||
```
|
||||
|
||||
**Custom headers for authentication:**
|
||||
```bash
|
||||
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key,x-custom-header=value"
|
||||
```
|
||||
|
||||
**Multiple exporters (console + OTLP):**
|
||||
```bash
|
||||
export CLINE_OTEL_METRICS_EXPORTER=console,otlp
|
||||
export CLINE_OTEL_LOGS_EXPORTER=console,otlp
|
||||
```
|
||||
|
||||
**Export intervals:**
|
||||
```bash
|
||||
# Metrics export interval in milliseconds (default: 60000)
|
||||
export CLINE_OTEL_METRIC_EXPORT_INTERVAL=30000
|
||||
|
||||
# Logs batch size and timeout
|
||||
export CLINE_OTEL_LOG_BATCH_SIZE=512
|
||||
export CLINE_OTEL_LOG_BATCH_TIMEOUT=5000
|
||||
export CLINE_OTEL_LOG_MAX_QUEUE_SIZE=2048
|
||||
```
|
||||
|
||||
## Integration Examples
|
||||
|
||||
### Datadog
|
||||
|
||||
Export to Datadog using their OTLP endpoint:
|
||||
|
||||
```bash
|
||||
export CLINE_OTEL_TELEMETRY_ENABLED=true
|
||||
export CLINE_OTEL_METRICS_EXPORTER=otlp
|
||||
export CLINE_OTEL_LOGS_EXPORTER=otlp
|
||||
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://api.datadoghq.com
|
||||
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="dd-api-key=YOUR_DD_API_KEY"
|
||||
```
|
||||
|
||||
### New Relic
|
||||
|
||||
Export to New Relic:
|
||||
|
||||
```bash
|
||||
export CLINE_OTEL_TELEMETRY_ENABLED=true
|
||||
export CLINE_OTEL_METRICS_EXPORTER=otlp
|
||||
export CLINE_OTEL_LOGS_EXPORTER=otlp
|
||||
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp.nr-data.net:4317
|
||||
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_NEW_RELIC_LICENSE_KEY"
|
||||
```
|
||||
|
||||
### Grafana Cloud
|
||||
|
||||
Export to Grafana Cloud:
|
||||
|
||||
```bash
|
||||
export CLINE_OTEL_TELEMETRY_ENABLED=true
|
||||
export CLINE_OTEL_METRICS_EXPORTER=otlp
|
||||
export CLINE_OTEL_LOGS_EXPORTER=otlp
|
||||
export CLINE_OTEL_EXPORTER_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
|
||||
export CLINE_OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic YOUR_BASE64_CREDENTIALS"
|
||||
```
|
||||
|
||||
|
||||
## Testing Configuration
|
||||
|
||||
Test your configuration with console output before sending to a real endpoint:
|
||||
|
||||
```bash
|
||||
# Enable console output to see what data would be exported
|
||||
export CLINE_OTEL_TELEMETRY_ENABLED=true
|
||||
export CLINE_OTEL_METRICS_EXPORTER=console
|
||||
export CLINE_OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
Then launch Cline and check the console output for metrics and logs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Data Being Exported
|
||||
|
||||
1. **Verify OpenTelemetry is enabled:**
|
||||
```bash
|
||||
echo $CLINE_OTEL_TELEMETRY_ENABLED
|
||||
```
|
||||
Should output `true`
|
||||
|
||||
2. **Check exporters are configured:**
|
||||
```bash
|
||||
echo $CLINE_OTEL_METRICS_EXPORTER
|
||||
echo $CLINE_OTEL_LOGS_EXPORTER
|
||||
```
|
||||
|
||||
3. **Test with console exporter first:**
|
||||
```bash
|
||||
export CLINE_OTEL_METRICS_EXPORTER=console
|
||||
export CLINE_OTEL_LOGS_EXPORTER=console
|
||||
```
|
||||
|
||||
### Connection Errors
|
||||
|
||||
1. **Verify endpoint is accessible:**
|
||||
```bash
|
||||
curl -v https://your-otlp-endpoint:4317
|
||||
```
|
||||
|
||||
2. **Check if insecure mode is needed:**
|
||||
```bash
|
||||
export CLINE_OTEL_EXPORTER_OTLP_INSECURE=true
|
||||
```
|
||||
|
||||
3. **Verify authentication headers:**
|
||||
Double-check your API keys and authentication headers are correct
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Enable debug logging to see detailed OpenTelemetry information:
|
||||
|
||||
```bash
|
||||
export TEL_DEBUG_DIAGNOSTICS=true
|
||||
```
|
||||
|
||||
This will output detailed information about:
|
||||
- Configuration being used
|
||||
- Exporters being created
|
||||
- Connection attempts
|
||||
- Export successes/failures
|
||||
|
||||
## What Gets Exported
|
||||
|
||||
When OpenTelemetry is enabled, Cline exports:
|
||||
|
||||
### Metrics
|
||||
- Feature usage counts
|
||||
- Task execution metrics
|
||||
- Error rates and types
|
||||
- Performance measurements
|
||||
|
||||
### Logs
|
||||
- System events
|
||||
- Error logs with context
|
||||
- Operational information
|
||||
|
||||
<Warning>
|
||||
Exported data is already anonymous and doesn't include code content, file paths, or sensitive information. However, you're responsible for securing the data once exported to your systems.
|
||||
</Warning>
|
||||
|
||||
## Limitations
|
||||
|
||||
Current OpenTelemetry support in Cline:
|
||||
- ✅ OTLP metrics export (console, gRPC, HTTP)
|
||||
- ✅ OTLP logs export (console, gRPC, HTTP)
|
||||
- ✅ Basic configuration via environment variables
|
||||
- ❌ Distributed tracing (not yet implemented)
|
||||
- ❌ Custom instrumentation API (not yet exposed)
|
||||
- ❌ Sampling configuration (uses defaults)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Test First**: Always test with console exporter before sending to production
|
||||
2. **Secure Credentials**: Never hardcode API keys; use secure environment variable management
|
||||
3. **Monitor Costs**: Be aware of data ingestion costs with your observability platform
|
||||
4. **Start Simple**: Begin with metrics only, add logs if needed
|
||||
5. **Use Compression**: OTLP supports compression; check if your endpoint requires it
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
|
||||
Configure simple built-in telemetry
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry Docs" icon="book" href="https://opentelemetry.io/docs/">
|
||||
Learn more about OpenTelemetry
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -8,14 +8,20 @@ Cline includes optional monitoring capabilities for organizations that want to t
|
||||
|
||||
## Monitoring Options
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
|
||||
Export metrics and logs to your own observability backends
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry Override" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry_override">
|
||||
Export to your own observability backends through environment variables (advanced)
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="Cline Telemetry" icon="chart-simple" href="/enterprise-solutions/monitoring/telemetry">
|
||||
Built-in anonymous usage tracking that helps improve Cline (opt-in)
|
||||
</Card>
|
||||
|
||||
<Card title="OpenTelemetry" icon="chart-line" href="/enterprise-solutions/monitoring/opentelemetry">
|
||||
Export metrics and logs to your own observability backends (advanced)
|
||||
</Card>
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Cline Telemetry
|
||||
|
||||
@@ -22,6 +22,10 @@ Your IdP administrator will receive an email with a link to register their organ
|
||||
|
||||
### Step 2: Configure Your Identity Provider
|
||||
|
||||
<Info>
|
||||
For a short overview of where SSO configuration lives (Cline dashboard vs WorkOS vs your IdP), see [SSO Setup](/enterprise-solutions/sso-setup).
|
||||
</Info>
|
||||
|
||||
Connect your identity provider (IdP) to WorkOS:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
title: "SSO Setup"
|
||||
sidebarTitle: "SSO Setup"
|
||||
description: "Configure Single Sign-On (SSO) for Cline Enterprise via WorkOS AuthKit."
|
||||
---
|
||||
|
||||
## Overview
|
||||
Cline Enterprise integrates with your identity provider (IdP) via **WorkOS AuthKit** for SSO.
|
||||
|
||||
This page describes, at a high level, how SSO is set up for Cline Enterprise using WorkOS AuthKit.
|
||||
|
||||
If you haven’t completed initial onboarding, start with [Onboarding](/enterprise-solutions/onboarding).
|
||||
|
||||
## Where setup happens
|
||||
SSO setup spans two places:
|
||||
|
||||
1) **Cline Dashboard (app.cline.bot)**
|
||||
- Where you sign in and verify SSO works for your organization.
|
||||
|
||||
2) **WorkOS dashboard**
|
||||
- Where your IdP connection is configured (AuthKit → Connections). Your designated admin receives access to this during enterprise onboarding.
|
||||
|
||||
## Using the Cline Dashboard
|
||||
Use the Cline Dashboard at https://app.cline.bot to:
|
||||
- complete sign-in and onboarding flows
|
||||
- verify users can authenticate via SSO
|
||||
|
||||
## Configure your IdP connection in WorkOS
|
||||
|
||||
During enterprise onboarding, your designated admin will receive an invitation email from WorkOS with a link to access your organization's WorkOS dashboard.
|
||||
|
||||
<Frame>
|
||||
<img src="/assets/workos-invite-email.png" alt="WorkOS invitation email example" />
|
||||
</Frame>
|
||||
|
||||
To connect your IdP to Cline Enterprise, configure your identity provider in **WorkOS AuthKit**:
|
||||
|
||||
1. In the WorkOS dashboard, go to **AuthKit → Connections**
|
||||
2. Click **Add Connection**
|
||||
3. Select your identity provider (e.g., Okta, Microsoft Entra ID/Azure AD, Google Workspace, Generic SAML/OIDC)
|
||||
4. Follow the provider-specific instructions in WorkOS
|
||||
|
||||
WorkOS’s UI and required fields vary by provider. For details, follow WorkOS documentation:
|
||||
- https://workos.com/docs/authkit/sso
|
||||
|
||||
## Keycloak note (IdP compatibility)
|
||||
Cline Enterprise’s default SSO integration is **via WorkOS**.
|
||||
|
||||
If you use **Keycloak** as your IdP, the supported path is to configure Keycloak in WorkOS as a **Generic SAML** or **Generic OIDC** provider (using the settings WorkOS requests for those provider types).
|
||||
|
||||
## Verification
|
||||
After configuring WorkOS:
|
||||
|
||||
1) Attempt an SSO sign-in from https://app.cline.bot.
|
||||
2) Confirm the sign-in completes (you are redirected back successfully).
|
||||
|
||||
## Troubleshooting
|
||||
- **Redirect URI mismatch**: confirm the redirect/callback URL configured in WorkOS matches what was provided during your Cline Enterprise onboarding.
|
||||
|
||||
For additional troubleshooting guidance, refer to WorkOS documentation:
|
||||
- https://workos.com/docs/authkit/sso
|
||||
@@ -13,46 +13,6 @@ Cline is your AI assistant that can:
|
||||
- Automate repetitive tasks
|
||||
- Integrate with external tools
|
||||
|
||||
## First Steps
|
||||
|
||||
1. **Start a Task**
|
||||
|
||||
- Type your request in the chat
|
||||
- Example: "Create a new React component called Header"
|
||||
|
||||
2. **Provide Context**
|
||||
|
||||
- Use @ mentions to add files, folders, or URLs
|
||||
- Example: "@file:src/components/App.tsx"
|
||||
|
||||
3. **Review Changes**
|
||||
- Cline will show diffs before making changes
|
||||
- You can edit or reject changes
|
||||
|
||||
## Key Features
|
||||
|
||||
1. **File Editing**
|
||||
|
||||
- Create new files
|
||||
- Modify existing code
|
||||
- Search and replace across files
|
||||
|
||||
2. **Terminal Commands**
|
||||
|
||||
- Run npm commands
|
||||
- Start development servers
|
||||
- Install dependencies
|
||||
|
||||
3. **Code Analysis**
|
||||
|
||||
- Find and fix errors
|
||||
- Refactor code
|
||||
- Add documentation
|
||||
|
||||
4. **Browser Integration**
|
||||
- Test web pages
|
||||
- Capture screenshots
|
||||
- Inspect console logs
|
||||
|
||||
## Available Tools
|
||||
|
||||
@@ -84,6 +44,7 @@ Cline has access to the following tools for various tasks:
|
||||
- `ask_followup_question`: Ask user for clarification
|
||||
- `attempt_completion`: Present final results
|
||||
|
||||
|
||||
Each tool has specific parameters and usage patterns. Here are some examples:
|
||||
|
||||
- Create a new file (write_to_file):
|
||||
|
||||
@@ -1,59 +1,104 @@
|
||||
The Auto Approve menu lets you set fine-grained permissions on what you allow Cline to do in an automated way.
|
||||
---
|
||||
title: "Auto Approve"
|
||||
sidebarTitle: "Auto Approve"
|
||||
description: "Let Cline take specific actions without asking for approval every time."
|
||||
---
|
||||
|
||||
Auto Approve lets you decide which actions Cline can take without prompting you each time. It keeps you out of approval popups during routine work, while still letting you keep tight control over high-risk actions.
|
||||
|
||||
If you find yourself repeatedly clicking approve for the same safe operations, Auto Approve is the setting that fixes that. The goal is fewer interruptions without losing the ability to review changes when it matters.
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/docs/assets/auto-approve.png" alt="Auto Approve" />
|
||||
<video
|
||||
style={{ width: "100%" }}
|
||||
src="https://storage.googleapis.com/cline_public_images/autoapprove.mp4"
|
||||
autoPlay
|
||||
controls
|
||||
playsInline
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## How it works
|
||||
|
||||
By default, Cline will ask for your permission before calling any tool, including reading or writing files.
|
||||
Auto Approve is evaluated per tool call. When Cline is about to read a file, edit a file, run a command, or use the browser, Cline checks your Auto Approve settings for that category.
|
||||
|
||||
If you want to allow Cline to do something without asking, you can set the Auto Approve permission for that tool.
|
||||
A few details matter in practice:
|
||||
|
||||
## Permission Options
|
||||
- **Workspace vs outside your workspace**: “Read all files” and “Edit all files” only extend the base toggle. If the base toggle is off, the “all files” option does nothing.
|
||||
|
||||
- **Read project files**
|
||||
- **Terminal commands**: Cline treats terminal commands as either safe or requiring approval. “Execute safe commands” covers the first category. “Execute all commands” extends this to commands flagged as requiring approval.
|
||||
|
||||
- Allows Cline to read files within your current workspace without asking
|
||||
- **Read all files**
|
||||
- Extends read permission to files outside your workspace (system files, config files, etc.)
|
||||
- **Notifications**: If enabled, Cline sends OS-level notifications when approval is required, and when an auto-approved terminal command has been running for 30 seconds and may need attention.
|
||||
|
||||
- **Edit project files**
|
||||
<Note>
|
||||
[YOLO mode](/features/yolo-mode) bypasses these granular approvals.
|
||||
</Note>
|
||||
|
||||
- Allows Cline to modify files within your current workspace without confirmation
|
||||
- **Edit all files**
|
||||
- Extends modification permission to files outside your workspace
|
||||
## Permissions
|
||||
|
||||
- **Execute safe commands**
|
||||
These labels match what you see in the Auto Approve menu.
|
||||
|
||||
- Allows execution of terminal commands that the model deems non-destructive
|
||||
- **Execute all commands**
|
||||
- Permits execution of any terminal command without asking
|
||||
| Setting | What it allows | Notes |
|
||||
|--------|-----------------|------|
|
||||
| Read project files | Read files, list files, search in your workspace | Good default for most tasks |
|
||||
| Read all files | Read files outside your workspace | Requires “Read project files” |
|
||||
| Edit project files | Create and edit files in your workspace | Consider using checkpoints |
|
||||
| Edit all files | Edit files outside your workspace | Requires “Edit project files” |
|
||||
| Execute safe commands | Run terminal commands marked safe | Can still run long |
|
||||
| Execute all commands | Run commands marked as requiring approval | Requires “Execute safe commands” |
|
||||
| Use the browser | Allows use of the browser tool for web fetching and searching | Proxy issues can apply |
|
||||
| Use MCP servers | Use MCP tools and access MCP resources | Some servers also have per-tool auto-approve |
|
||||
| Enable notifications | Notifies you about long-running auto-approved commands | Helpful for terminal work |
|
||||
|
||||
- **Use the browser**
|
||||
<Warning>
|
||||
“Read all files” and “Edit all files” only matter if their base toggle is enabled. They extend access outside your workspace.
|
||||
</Warning>
|
||||
|
||||
- Allows Cline to use the browser tool to fetch web content
|
||||
<Card title="Networking & proxies" icon="globe" href="/troubleshooting/networking-and-proxies">
|
||||
If browser-based tools fail in corporate networks, this page covers the common fixes.
|
||||
</Card>
|
||||
|
||||
- **Use MCP servers**
|
||||
## Safe vs approval-required command examples
|
||||
|
||||
- Permits connection to and usage of MCP servers for extended functionality
|
||||
Cline does not use a fixed allowlist of safe or unsafe commands. The model marks each command with a `requires_approval` flag based on the command and its arguments, and Auto Approve uses that flag.
|
||||
|
||||
- **Maximum requests**
|
||||
- Sets the number of consecutive automated actions Cline can take before requiring your input
|
||||
These are examples, not guarantees.
|
||||
|
||||
## Best Practices
|
||||
### Commonly treated as safe
|
||||
|
||||
Personally, I like to keep auto-editing disabled because it gives me a chance to review changes every step of the way.
|
||||
| Example | Why it is usually safe |
|
||||
|--------|-------------------------|
|
||||
| `npm run build` | Build output, no direct file deletions |
|
||||
| `npm test` | Runs tests |
|
||||
| `git status` | Read-only |
|
||||
| `ls -la` | Read-only |
|
||||
| `cat package.json` | Read-only |
|
||||
|
||||
For most serious development workflows, I recommend starting with:
|
||||
### Commonly requires approval
|
||||
|
||||
- Auto-approving read access to project files
|
||||
- Setting a reasonable maximum request limit (10-20)
|
||||
| Example | Why it often needs approval |
|
||||
|--------|------------------------------|
|
||||
| `npm install <pkg>` | Modifies dependencies and lockfiles |
|
||||
| `rm -rf <path>` | Deletes files |
|
||||
| `mv <a> <b>` | Moves files (can overwrite) |
|
||||
| `sed -i ...` | In-place file edits |
|
||||
| `curl https://...` | Downloads and executes remote code |
|
||||
|
||||
This gives Cline enough freedom to explore your codebase without constant interruptions, while still requiring permission for edits or potentially destructive actions.
|
||||
<Note>
|
||||
Whether a command is treated as safe depends on the exact command, flags, and the current task. When in doubt, keep command auto-approval off and approve commands manually.
|
||||
</Note>
|
||||
|
||||
As you build more trust in Cline's capabilities with your specific projects, you can gradually increase the permissions to match your comfort level.
|
||||
## Enable notifications
|
||||
|
||||
Remember that you can always adjust these settings as your needs change - tighten permissions for critical production work, or loosen them when prototyping and exploring.
|
||||
Auto-approved actions can run for a while, especially long terminal commands. If you enable notifications, Cline can notify you when an auto-approved command has been running for a while and may need attention.
|
||||
|
||||
You can even use the quick "star" actions to quickly toggle your auto-approved selections on and off as you go.
|
||||
## Recommendations
|
||||
|
||||
A good default setup is:
|
||||
|
||||
- Enable **Read project files**
|
||||
- Leave **Edit project files**, **Execute safe commands**, **Use the browser**, and **Use MCP servers** off until you have a specific reason to enable them
|
||||
|
||||
If you enable edits, use [Checkpoints](/features/checkpoints) so you can roll back quickly.
|
||||
|
||||
If you’re working in a sensitive environment (production credentials, personal files, corporate devices), keep external file access and command execution locked down and approve actions manually as you go.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "Background Edit"
|
||||
sidebarTitle: "Background Edit"
|
||||
---
|
||||
|
||||
Background Edit lets Cline make file changes without opening the diff editor, so you can keep writing code while Cline works on other files in the background.
|
||||
|
||||
<Note>
|
||||
This feature is marked as experimental.
|
||||
</Note>
|
||||
|
||||
## How It Works
|
||||
|
||||
By default, Cline opens a side-by-side diff editor tab for each file it modifies. With Background Edit enabled:
|
||||
|
||||
- Edits write directly to your files without opening new tabs
|
||||
- Changes appear as collapsible diff blocks in the chat panel
|
||||
- Your editor focus stays on whatever file you had open
|
||||
|
||||
## Enabling Background Edit
|
||||
|
||||
1. Click the settings icon (gear) in the top-right corner of the Cline panel
|
||||
2. Go to "**Feature Settings**"
|
||||
3. Toggle "**Enable Background Edit**" on
|
||||
|
||||
## Viewing Changes
|
||||
|
||||
File changes display directly in the chat panel with:
|
||||
|
||||
- **File action icons** showing whether the file was added, updated, or deleted
|
||||
- **Stats** showing additions (+) and deletions (-) at a glance
|
||||
- **Collapsible diffs** you can expand or collapse by clicking the file header
|
||||
- **Real-time streaming** as changes appear line-by-line
|
||||
|
||||
Green highlights additions, red highlights deletions.
|
||||
|
||||
## When to Use It
|
||||
|
||||
This feature works well when you:
|
||||
|
||||
- Use [auto-approve mode](/features/auto-approve) and prefer reviewing changes after the fact
|
||||
- Work on tasks with many small file changes
|
||||
- Want to stay focused on your current file
|
||||
|
||||
Stick with the default diff editor if you prefer reviewing each change before it saves, or need to make inline edits to Cline's proposed changes.
|
||||
|
||||
## Relationship with Other Features
|
||||
|
||||
- **Checkpoints**: Still created after each file operation
|
||||
- **Auto-approve**: Pairs well for uninterrupted workflows
|
||||
- **Message editing**: Restoring from a previous message works as expected
|
||||
@@ -3,94 +3,192 @@ title: "Keyboard Shortcuts"
|
||||
sidebarTitle: "Keyboard Shortcuts"
|
||||
---
|
||||
|
||||
Cline's keyboard shortcuts let you access AI assistance without taking your hands off the keyboard. Speed up your workflow by using hotkeys for common Cline actions.
|
||||
Speed up your workflow by accessing Cline's AI assistance without taking your hands off the keyboard.
|
||||
|
||||
## Default Keyboard Shortcuts
|
||||
<Tip>
|
||||
**The One Shortcut You Need:** `Ctrl+'` (Windows/Linux) or `Cmd+'` (macOS)
|
||||
|
||||
Cline comes with the following built-in keyboard shortcuts to streamline your workflow:
|
||||
This context-aware shortcut handles your most common needs:
|
||||
- **With text selected:** Adds code to Cline chat
|
||||
- **Without selection:** Focuses the chat input
|
||||
|
||||
| Action | Windows/Linux | macOS | Condition | Description |
|
||||
| ----------------------- | ------------- | ------- | ---------------------------- | ----------------------------------------- |
|
||||
| Add to Cline | `Ctrl+'` | `Cmd+'` | When text is selected | Adds selected code to Cline chat |
|
||||
| Focus Chat Input | `Ctrl+'` | `Cmd+'` | When no text is selected | Focuses the Cline chat input field |
|
||||
| Generate Commit Message | (unset) | (unset) | When Git is the SCM provider | Available through the Source Control view |
|
||||
Master this one shortcut, and you're 90% there.
|
||||
</Tip>
|
||||
|
||||
## Available Commands for Custom Shortcuts
|
||||
## Default Shortcuts
|
||||
|
||||
While Cline has only a few default keyboard shortcuts, you can assign your own shortcuts to any of these commands:
|
||||
Cline has minimal default shortcuts by design, so they won't conflict with your existing VSCode setup:
|
||||
|
||||
| Command ID | Description |
|
||||
| ---------------------------------------------------------------------------------------- | --------------------------------------------- |
|
||||
| [`cline.addToChat`](/features/commands-and-shortcuts/code-commands) | Adds selected code to Cline chat |
|
||||
| [`cline.addTerminalOutputToChat`](/features/commands-and-shortcuts/terminal-integration) | Adds terminal output to Cline |
|
||||
| `cline.focusChatInput` | Focuses the Cline chat input field |
|
||||
| [`cline.generateGitCommitMessage`](/features/commands-and-shortcuts/git-integration) | Generates a commit message for staged changes |
|
||||
| [`cline.explainCode`](/features/commands-and-shortcuts/code-commands) | Explains selected code |
|
||||
| [`cline.improveCode`](/features/commands-and-shortcuts/code-commands) | Suggests improvements for selected code |
|
||||
| [`cline.fixWithCline`](/features/commands-and-shortcuts/code-commands) | Fixes code with errors |
|
||||
| `claude-dev.SidebarProvider.focus` | Opens and focuses the Cline sidebar |
|
||||
| Shortcut | Windows/Linux | macOS | What It Does |
|
||||
| -------- | ------------- | ----- | ------------ |
|
||||
| **Add to Chat / Focus Input** | `Ctrl+'` | `Cmd+'` | Context-aware: adds selected code or focuses chat |
|
||||
|
||||
## Customizing Keyboard Shortcuts
|
||||
That's it! Everything else is available for you to customize.
|
||||
|
||||
You can customize Cline's keyboard shortcuts to match your preferences:
|
||||
## Quick Workflow Examples
|
||||
|
||||
1. Open the Keyboard Shortcuts editor in VSCode:
|
||||
Here's how keyboard shortcuts fit into real coding workflows:
|
||||
|
||||
- Press `Ctrl+K Ctrl+S` (Windows/Linux) or `Cmd+K Cmd+S` (macOS)
|
||||
- Or go to File > Preferences > Keyboard Shortcuts
|
||||
### Debug & Fix Workflow
|
||||
|
||||
2. Search for "Cline" to see all available commands
|
||||
1. **Find error in code** → VSCode highlights it
|
||||
2. **Select the problematic code** → `Shift+Arrow` or `Ctrl+L` / `Cmd+L`
|
||||
3. **Send to Cline** → `Ctrl+'` / `Cmd+'`
|
||||
4. **Ask for help** → Type your question, hit `Enter`
|
||||
|
||||
3. Click on the pencil icon next to any command to change its shortcut
|
||||
### Code Review Workflow
|
||||
|
||||
4. Press the keys you want to assign to that command
|
||||
1. **Review a function** → Select it with `Ctrl+L` / `Cmd+L`
|
||||
2. **Get AI review** → `Ctrl+'` / `Cmd+'` then ask "Review this"
|
||||
3. **Iterate** → Apply suggestions and repeat
|
||||
|
||||
5. Press Enter to save the new shortcut
|
||||
### Terminal Integration Workflow
|
||||
|
||||
## Suggested Custom Shortcuts
|
||||
1. **Open terminal** → Press `` Ctrl+` `` / `` Cmd+` ``
|
||||
2. **Run your command** → Execute in terminal
|
||||
3. **Capture output** → Press `Alt+T` (after assigning shortcut)
|
||||
4. **Get help** → Ask Cline to interpret errors or output
|
||||
|
||||
Here are some suggested shortcuts you might find useful:
|
||||
<Info>
|
||||
**Pro Tip:** Assign `Alt+T` to the `cline.addTerminalOutputToChat` command for quick terminal output capture. Without a shortcut, you can still right-click in the terminal and select "Add to Cline" - but the keyboard approach is much faster for frequent debugging workflows.
|
||||
</Info>
|
||||
|
||||
| Action | Suggested Shortcut | Command ID | Description |
|
||||
| --------------------- | ------------------------------ | ----------------------------------------- | ----------------------------- |
|
||||
| Open Cline Sidebar | `Ctrl+Shift+C` / `Cmd+Shift+C` | `claude-dev.SidebarProvider.focus` | Opens the Cline sidebar panel |
|
||||
| New Task | `Alt+N` | `cline.plusButtonClicked` | Starts a new Cline task |
|
||||
| Add Terminal to Cline | `Alt+T` | `cline.addTerminalOutputToChat` | Adds terminal output to Cline |
|
||||
| Clear Current Task | `Alt+C` | (Requires custom keybinding to UI action) | Clears the current task |
|
||||
## Customizing Shortcuts
|
||||
|
||||
## Keyboard-Only Workflow
|
||||
Want to assign shortcuts to more Cline commands? Here's how:
|
||||
|
||||
With the right shortcuts, you can use Cline without ever touching the mouse:
|
||||
**Step 1:** Open VSCode's Keyboard Shortcuts editor
|
||||
- Press `Ctrl+K Ctrl+S` (Windows/Linux) or `Cmd+K Cmd+S` (macOS)
|
||||
- Or: **File → Preferences → Keyboard Shortcuts**
|
||||
|
||||
1. Select code with keyboard navigation (`Shift+Arrow` keys)
|
||||
2. Send to Cline with `Ctrl+'` / `Cmd+'`
|
||||
3. Type your question and press Enter
|
||||
4. Review the response and apply suggestions
|
||||
**Step 2:** Search for "Cline"
|
||||
|
||||
## Editor Integration Shortcuts
|
||||
**Step 3:** Click the ✏️ icon next to any command
|
||||
|
||||
Cline's keyboard shortcuts integrate seamlessly with VSCode's built-in shortcuts:
|
||||
**Step 4:** Press your desired key combo, then `Enter`
|
||||
|
||||
- Use VSCode's selection shortcuts (`Ctrl+L` / `Cmd+L` to select line, etc.) before sending code to Cline
|
||||
- Combine with VSCode's split editor shortcuts to view code and Cline side by side
|
||||
- Use VSCode's terminal focus shortcut (`` Ctrl+` `` / `` Cmd+` ``) before capturing terminal output
|
||||
<Warning>
|
||||
**Avoid Conflicts:** Check that your shortcut doesn't override important VSCode commands. The shortcuts editor will warn you about conflicts.
|
||||
</Warning>
|
||||
|
||||
## Tips for Effective Use
|
||||
## Available Commands Reference
|
||||
|
||||
- **Learn the default shortcut first**: The `Ctrl+'` / `Cmd+'` shortcut is versatile - it adds selected code to chat when text is selected, or focuses the chat input when nothing is selected
|
||||
- **Create muscle memory**: Use keyboard shortcuts consistently to build habits
|
||||
- **Customize for your workflow**: Assign shortcuts to commands you use frequently
|
||||
- **Consider ergonomics**: Choose shortcuts that are comfortable for your keyboard layout
|
||||
<Accordion title="Task Management Commands">
|
||||
|
||||
Keyboard shortcuts may seem like a small optimization, but they can significantly speed up your workflow when using Cline regularly. By keeping your hands on the keyboard, you maintain your coding flow while still getting AI assistance exactly when you need it.
|
||||
These commands help you navigate and manage Cline tasks:
|
||||
|
||||
## How to Find All Available Commands
|
||||
| Command ID | Description | Suggested Shortcut |
|
||||
| ---------- | ----------- | ------------------ |
|
||||
| `cline.plusButtonClicked` | Start a new task | `Ctrl+Shift+N` / `Cmd+Shift+N` |
|
||||
| `cline.historyButtonClicked` | Open task history | `Ctrl+Shift+H` / `Cmd+Shift+H` |
|
||||
| `claude-dev.SidebarProvider.focus` | Open Cline sidebar | `Ctrl+Shift+L` / `Cmd+Shift+L` |
|
||||
|
||||
To see all Cline commands that can be assigned shortcuts:
|
||||
**Note:** `claude-dev` prefix is for historical reasons - it works with Cline.
|
||||
|
||||
1. Open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`)
|
||||
2. Type "Cline" to filter the list
|
||||
3. Browse the available commands
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Code Interaction Commands">
|
||||
|
||||
Work directly with your code:
|
||||
|
||||
| Command ID | Description | Suggested Shortcut |
|
||||
| ---------- | ----------- | ------------------ |
|
||||
| `cline.addToChat` | Add selected code to chat | `Ctrl+'` / `Cmd+'` ⭐ (default) |
|
||||
| `cline.focusChatInput` | Focus chat input | `Ctrl+'` / `Cmd+'` ⭐ (default) |
|
||||
| `cline.explainCode` | Explain selected code | `Ctrl+Shift+E` / `Cmd+Shift+E` |
|
||||
| `cline.improveCode` | Suggest code improvements | `Ctrl+Shift+I` / `Cmd+Shift+I` |
|
||||
|
||||
⭐ These share the same shortcut - it's context-aware!
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Terminal Integration Commands">
|
||||
|
||||
Connect Cline with your terminal:
|
||||
|
||||
| Command ID | Description | Suggested Shortcut |
|
||||
| ---------- | ----------- | ------------------ |
|
||||
| `cline.addTerminalOutputToChat` | Add terminal output to Cline | `Alt+T` |
|
||||
|
||||
**Tip:** Use this after running commands to get help interpreting output or fixing errors.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Git Integration Commands">
|
||||
|
||||
Generate commit messages with AI:
|
||||
|
||||
| Command ID | Description | Suggested Shortcut |
|
||||
| ---------- | ----------- | ------------------ |
|
||||
| `cline.generateGitCommitMessage` | Generate commit message | `Ctrl+Shift+G` / `Cmd+Shift+G` |
|
||||
| `cline.abortGitCommitMessage` | Stop generation | `Ctrl+Shift+Esc` / `Cmd+Shift+Esc` |
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Settings & Configuration Commands (Advanced)">
|
||||
|
||||
These commands open Cline's configuration panels. Most users access these via the sidebar buttons, but keyboard shortcuts can be useful for:
|
||||
|
||||
- **Frequent MCP server developers** who constantly adjust server configurations
|
||||
- **Demo/presentation scenarios** where you need quick, keyboard-only navigation
|
||||
- **Accessibility workflows** where mouse usage is minimized
|
||||
|
||||
| Command ID | Description | Suggested Shortcut |
|
||||
| ---------- | ----------- | ------------------ |
|
||||
| `cline.settingsButtonClicked` | Open Cline settings | `Ctrl+Alt+,` / `Cmd+Opt+,` |
|
||||
| `cline.mcpButtonClicked` | Open MCP servers config | `Ctrl+Alt+M` / `Cmd+Opt+M` |
|
||||
| `cline.accountButtonClicked` | Open account settings | `Ctrl+Alt+A` / `Cmd+Opt+A` |
|
||||
| `cline.openWalkthrough` | Open walkthrough guide | (not recommended) |
|
||||
|
||||
**Our take:** Unless you're constantly tweaking settings or building MCP servers, the sidebar buttons are more convenient. But if you find yourself opening these panels frequently, shortcuts can save time.
|
||||
|
||||
</Accordion>
|
||||
|
||||
## What About "Fix with Cline"?
|
||||
|
||||
<Warning>
|
||||
**You CAN'T assign a keyboard shortcut to "Fix with Cline"**
|
||||
|
||||
This command only appears in the **lightbulb menu** (💡) when VSCode detects errors in your code. It needs the error context to work, so it's not available as a standalone command.
|
||||
|
||||
**Workarounds:**
|
||||
- Click the 💡 lightbulb icon that appears next to errors
|
||||
- Or select code with errors and use `Ctrl+'` / `Cmd+'` to ask Cline to fix them
|
||||
- Or right-click and select "Add to Cline"
|
||||
</Warning>
|
||||
|
||||
Learn more about code actions in our [Code Commands documentation](/features/commands-and-shortcuts/code-commands).
|
||||
|
||||
## Best Practices
|
||||
|
||||
<Tip>
|
||||
**Start Simple**
|
||||
|
||||
Don't try to memorize 20 shortcuts on day one. Start with:
|
||||
1. `Ctrl+'` / `Cmd+'` (the essential one)
|
||||
2. Add 1-2 more based on your actual usage patterns
|
||||
3. Build muscle memory over time
|
||||
</Tip>
|
||||
|
||||
**Choose Shortcuts Wisely:**
|
||||
- **Be ergonomic:** Use comfortable key combinations
|
||||
- **Create patterns:** Group related commands (e.g., all Cline shortcuts use `Ctrl+Shift+...`)
|
||||
- **Avoid conflicts:** Don't override VSCode essentials like `Ctrl+C` or `Ctrl+S`
|
||||
- **Use modifiers:** Combine `Ctrl`/`Cmd` + `Shift` + `Alt` to reduce conflicts
|
||||
|
||||
**Build the Habit:**
|
||||
- Use shortcuts consistently for a week to build muscle memory
|
||||
- Keep a note of your custom shortcuts until they're automatic
|
||||
- Review monthly to see if your workflow has changed
|
||||
|
||||
## Discovering Commands
|
||||
|
||||
Not sure what commands are available? Use VSCode's Command Palette:
|
||||
|
||||
1. Press `Ctrl+Shift+P` / `Cmd+Shift+P`
|
||||
2. Type "Cline" to filter
|
||||
3. Browse all available commands
|
||||
4. Assign shortcuts to your favorites
|
||||
|
||||
<Frame>
|
||||
<img
|
||||
@@ -99,4 +197,8 @@ To see all Cline commands that can be assigned shortcuts:
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
This helps you discover features you might not have known about and assign shortcuts to the ones you use most frequently.
|
||||
---
|
||||
|
||||
<Info>
|
||||
**Remember:** The goal isn't to memorize every possible shortcut. Master `Ctrl+'` / `Cmd+'` first, then gradually add shortcuts for commands you use frequently. Quality over quantity!
|
||||
</Info>
|
||||
|
||||
@@ -3,11 +3,13 @@ title: "Explain Changes"
|
||||
sidebarTitle: "Explain Changes"
|
||||
---
|
||||
|
||||
<Note>
|
||||
This feature is only available in **VS Code**. The diff view with inline comments requires VS Code's native diff capabilities.
|
||||
</Note>
|
||||
|
||||
|
||||
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
|
||||
@@ -21,6 +23,9 @@ Explain Changes requires **[Checkpoints](/features/checkpoints)** to be enabled.
|
||||
|
||||
|
||||
## How It Works
|
||||
<Note>
|
||||
Explain Changes requires **[Checkpoints](/features/checkpoints)** to be enabled. Make sure to enable checkpoints in your Cline settings before using this feature.
|
||||
</Note>
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -137,10 +137,30 @@ The key is combining hooks with external tools. A hook can be the glue between C
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Related Features
|
||||
## CLI support
|
||||
|
||||
Hooks are also available in the [Cline CLI](/cline-cli/overview). You can enable or disable hooks when running tasks from the command line:
|
||||
|
||||
```bash
|
||||
# Enable hooks for a task
|
||||
cline "What does this repo do?" -s hooks_enabled=true
|
||||
|
||||
# Configure hooks globally via CLI
|
||||
cline config set hooks-enabled=true
|
||||
cline config get hooks-enabled
|
||||
```
|
||||
|
||||
This allows you to integrate hooks into automated workflows, CI/CD pipelines, and headless task execution.
|
||||
|
||||
<Note>
|
||||
Hooks in the CLI are only supported on macOS and Linux. Windows support is not yet available.
|
||||
</Note>
|
||||
|
||||
## 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
|
||||
- [Cline CLI](/cline-cli/overview) enables hooks in terminal-based and automated workflows
|
||||
|
||||
@@ -1,164 +1,260 @@
|
||||
---
|
||||
title: "Multiroot Workspace Support"
|
||||
sidebarTitle: "Multiroot Workspace"
|
||||
title: "Multi-Root Workspaces"
|
||||
sidebarTitle: "Multi-Root Workspaces"
|
||||
---
|
||||
|
||||
Cline's Multiroot feature works seamlessly with VSCode's multi-root workspaces, letting you manage multiple project folders in a single workspace.
|
||||
Cline works with VSCode's multi-root workspaces, letting you manage multiple project folders or repositories in a single window. Whether you're working with a monorepo or separate Git repositories, Cline can read files, write code, and run commands across all of them.
|
||||
|
||||
<Frame>
|
||||
<video
|
||||
src="https://storage.googleapis.com/cline_public_images/multiworkspace.mp4"
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
controls
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
<Warning>
|
||||
Multi-root workspaces have two limitations:
|
||||
- **Cline rules** only work in the primary workspace folder
|
||||
- **Checkpoints** are disabled (restored when you return to a single folder)
|
||||
|
||||
See [Current Limitations](#current-limitations) for details.
|
||||
</Warning>
|
||||
|
||||
## Understanding Multi-Root Workspaces
|
||||
|
||||
Before diving in, it helps to understand the two common patterns for organizing related projects.
|
||||
|
||||
### Why Use Multi-Root Workspaces?
|
||||
|
||||
Cline can complete tasks that span multiple projects or repositories:
|
||||
|
||||
- **Refactoring**: Update an API contract and fix all consumers across repos
|
||||
- **Feature development**: Implement a feature that touches frontend, backend, and shared code
|
||||
- **Dependency updates**: Coordinate version bumps across related projects
|
||||
- **Documentation**: Generate docs that reference code from multiple repositories
|
||||
|
||||
**Example prompt:**
|
||||
```
|
||||
Update the User type in the contracts repo, then update both the frontend
|
||||
and backend to use the new fields. Make sure the API validates the new
|
||||
required field.
|
||||
```
|
||||
## Setting Up a Multi-Root Workspace
|
||||
|
||||
### Monorepos vs Multiple Repositories
|
||||
|
||||
**Monorepo**: One Git repository containing multiple projects or packages. All code shares the same version history.
|
||||
|
||||
```
|
||||
my-company/ # Single Git repo
|
||||
├── .git/
|
||||
├── packages/
|
||||
│ ├── web/ # React frontend
|
||||
│ ├── api/ # Node.js backend
|
||||
│ └── shared/ # Common utilities
|
||||
└── package.json
|
||||
```
|
||||
|
||||
**Multiple Repositories**: Separate Git repositories, each with their own history, opened together in one VSCode workspace.
|
||||
|
||||
```
|
||||
~/projects/
|
||||
├── fullstack.code-workspace # Workspace config file
|
||||
├── frontend/ # git@github.com:acme/frontend.git
|
||||
│ └── .git/
|
||||
├── backend/ # git@github.com:acme/backend.git
|
||||
│ └── .git/
|
||||
└── contracts/ # git@github.com:acme/api-contracts.git
|
||||
└── .git/
|
||||
```
|
||||
|
||||
Cline supports both patterns, as well as hybrid setups where some folders are Git repositories and others are not. The key difference: with multiple repositories, each folder has its own `.git` directory and Cline tracks them independently.
|
||||
|
||||
### Adding Folders to Your Workspace
|
||||
|
||||
You can add folders to your workspace in several ways:
|
||||
|
||||
- **File menu**: Use `File > Add Folder to Workspace` in VSCode
|
||||
- **Drag and drop**: Drag folders directly into VSCode's file explorer
|
||||
- **Workspace file**: Create a `.code-workspace` file (recommended for teams)
|
||||
- **Command palette**: Run `Workspaces: Add Folder to Workspace`
|
||||
|
||||
For detailed instructions, see [Microsoft's multi-root workspace guide](https://code.visualstudio.com/docs/editor/multi-root-workspaces).
|
||||
|
||||
## Working with Multiple Repositories
|
||||
|
||||
When you open separate Git repositories in one workspace, Cline treats each as an independent project with its own version control.
|
||||
|
||||
### What Cline Tracks Per Repository
|
||||
|
||||
For each workspace folder, Cline detects:
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| **Path** | Absolute path to the folder |
|
||||
| **Name** | Derived from folder name or workspace file |
|
||||
| **VCS Type** | Git, Mercurial, or None |
|
||||
| **Commit Hash** | Current HEAD commit (for Git/Mercurial repos) |
|
||||
|
||||
This means Cline understands that your frontend and backend might be at different commits, on different branches, or even use different version control systems.
|
||||
|
||||
<Note>
|
||||
**Important:** Multi-root workspaces are currently an experimental feature and have the following limitations:
|
||||
- **Cline rules** only work in the first workspace folder
|
||||
- **Checkpoints** are automatically disabled with a warning message
|
||||
- Both features are restored when you return to a single-folder workspace
|
||||
While Cline detects VCS information for all workspace folders, certain features only use the **primary workspace** (the first folder): [Cline rules](/features/cline-rules), [workflows](/features/slash-commands/workflows/index), and [Git-related features](/features/at-mentions/git-mentions) like `@git` mentions.
|
||||
</Note>
|
||||
|
||||
## What is multiroot workspace support?
|
||||
## Referencing Files Across Workspaces
|
||||
|
||||
Instead of being limited to one project folder, Cline can read files, write code, and run commands across all folders in your VSCode workspace. This is helpful when working with monorepos, microservices, or when you're working on related projects simultaneously.
|
||||
### Natural Language References
|
||||
|
||||
### How it works
|
||||
|
||||
When you open multiple workspace folders in VSCode, Cline automatically:
|
||||
- Designates one folder as the **primary workspace** (typically the first folder added)
|
||||
- Tracks all workspace folders and their paths
|
||||
- Resolves file paths intelligently across workspaces
|
||||
- Displays workspace information in the environment details for each API request
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Setting Up Multi-Root Workspaces
|
||||
|
||||
1. **Add folders to your workspace:**
|
||||
- Use `File > Add Folder to Workspace` in VSCode
|
||||
- Or create a `.code-workspace` file with multiple folder paths
|
||||
- Drag and drop folders to the File Explorer
|
||||
- Select multiple folders when opening a new workspace
|
||||
|
||||
2. **Start using Cline** - Cline will automatically detect all your workspace folders and interact with them as needed.
|
||||
|
||||
For detailed instructions on setting up multi-root workspaces in VS Code, see [Microsoft's official guide](https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces).
|
||||
|
||||
### Technical behavior
|
||||
|
||||
**Workspace detection**
|
||||
- Cline detects all workspace folders when a task starts
|
||||
- The first workspace folder becomes the primary workspace by default
|
||||
- Each workspace can have its own VCS (Git, SVN, etc.)
|
||||
|
||||
**Path resolution**
|
||||
- Relative paths are resolved relative to the primary workspace
|
||||
- You can use workspace hints to target specific workspaces: `@workspaceName:path/to/file`
|
||||
- Cline attempts to intelligently determine which workspace a file belongs to
|
||||
|
||||
**Command execution**
|
||||
- Commands execute in the appropriate workspace context
|
||||
- The working directory is set based on where files are being accessed
|
||||
|
||||
## Working across workspaces
|
||||
|
||||
### Referencing specific workspaces
|
||||
|
||||
You can reference different workspaces naturally in your prompts:
|
||||
Cline understands natural references to your workspaces:
|
||||
|
||||
```
|
||||
"Read the package.json in my frontend folder and compare it with the backend dependencies"
|
||||
"Read the package.json in the frontend folder"
|
||||
```
|
||||
|
||||
```
|
||||
"Create a shared utility function and update both the client and server to use it"
|
||||
"Compare the user model in backend with the TypeScript types in contracts"
|
||||
```
|
||||
|
||||
```
|
||||
"Search for TODO comments across all my workspace folders"
|
||||
"Search for TODO comments across all workspaces"
|
||||
```
|
||||
|
||||
### Workspace hints
|
||||
### Workspace Hints Syntax
|
||||
|
||||
Use workspace hints to explicitly reference files in specific workspaces:
|
||||
For explicit references, use the `@workspace:path` syntax:
|
||||
|
||||
```
|
||||
@frontend:src/App.tsx
|
||||
@backend:server.ts
|
||||
```
|
||||
| Syntax | Description |
|
||||
|--------|-------------|
|
||||
| `@frontend:src/App.tsx` | File in the "frontend" workspace |
|
||||
| `@backend:server.ts` | File in the "backend" workspace |
|
||||
| `@contracts:types/` | Folder in the "contracts" workspace |
|
||||
|
||||
This syntax helps Cline resolve ambiguity when multiple workspaces contain similarly named files.
|
||||
This syntax is especially useful when:
|
||||
- Multiple workspaces have files with the same name
|
||||
- You want to be explicit about which project you mean
|
||||
- Cline needs to resolve ambiguity
|
||||
|
||||
### How Workspace Names Work
|
||||
|
||||
## Common use cases
|
||||
Workspace names are derived from:
|
||||
1. The `name` field in your `.code-workspace` file (if specified)
|
||||
2. The folder name (default)
|
||||
|
||||
If two folders have the same name, append numbers or use the workspace file to give them unique names.
|
||||
|
||||
## Common Configurations
|
||||
|
||||
### Monorepo Development
|
||||
|
||||
Perfect for when you have related projects in one repository:
|
||||
|
||||
```
|
||||
my-app.code-workspace
|
||||
~/projects/my-app/
|
||||
├── my-app.code-workspace # Workspace config file
|
||||
├── web/ (React frontend)
|
||||
├── api/ (Node.js backend)
|
||||
├── api/ (Node.js backend)
|
||||
├── mobile/ (React Native)
|
||||
└── shared/ (Common utilities)
|
||||
```
|
||||
|
||||
Ask Cline: *"Update the API endpoint in both web and mobile apps to match the new backend route"*
|
||||
All folders share one Git history. Changes across packages are atomic.
|
||||
|
||||
### Microservices Architecture
|
||||
**Example prompt:** *"Update the API endpoint in both web and mobile apps to match the new backend route"*
|
||||
|
||||
Manage multiple services from one workspace:
|
||||
### Microservices with Separate Repos
|
||||
|
||||
```
|
||||
services.code-workspace
|
||||
├── user-service/
|
||||
├── payment-service/
|
||||
├── notifications/
|
||||
└── infrastructure/
|
||||
~/projects/services/
|
||||
├── services.code-workspace # Workspace config file
|
||||
├── user-service/ (git: github.com/acme/user-service)
|
||||
├── payment-service/ (git: github.com/acme/payment-service)
|
||||
├── gateway/ (git: github.com/acme/api-gateway)
|
||||
└── proto/ (git: github.com/acme/service-protos)
|
||||
```
|
||||
|
||||
### Full-Stack Development
|
||||
Each service has its own repository. Cline can update the proto definitions and regenerate clients across all services.
|
||||
|
||||
Keep everything together while maintaining separation:
|
||||
**Example prompt:** *"Add a new field to the UserProfile message in proto, then update user-service and gateway to handle it"*
|
||||
|
||||
### Full-Stack with Shared Contracts
|
||||
|
||||
```
|
||||
fullstack.code-workspace
|
||||
├── client/ (Frontend)
|
||||
├── server/ (Backend API)
|
||||
├── docs/ (Documentation)
|
||||
└── deploy/ (Scripts & config)
|
||||
~/projects/fullstack/
|
||||
├── fullstack.code-workspace # Workspace config file
|
||||
├── client/ (git: github.com/acme/web-client)
|
||||
├── server/ (git: github.com/acme/api-server)
|
||||
└── types/ (git: github.com/acme/shared-types)
|
||||
```
|
||||
|
||||
The types repository defines interfaces used by both client and server. When you update a type, Cline can fix both consumers.
|
||||
|
||||
### Auto-Approve Integration
|
||||
### Hybrid Setup
|
||||
|
||||
Multiroot workspaces work with [Auto Approve](/features/auto-approve):
|
||||
```
|
||||
~/projects/project/
|
||||
├── project.code-workspace # Workspace config file
|
||||
├── main-app/ (git: github.com/acme/main-app)
|
||||
├── vendor/ (no VCS - vendored dependencies)
|
||||
└── scripts/ (no VCS - local automation)
|
||||
```
|
||||
|
||||
- Enable permissions for operations within workspace folders
|
||||
- Restrict auto-approve for files outside your workspace(s)
|
||||
- Configure different levels for different workspace folders
|
||||
Mix of repositories and plain folders. Cline adapts to each folder's configuration.
|
||||
|
||||
### Cross-Workspace Operations
|
||||
## Current Limitations
|
||||
|
||||
Cline can complete tasks spanning multiple workspaces:
|
||||
Two features have limitations in multi-root workspace mode:
|
||||
|
||||
- **Refactoring**: Update imports and references across projects
|
||||
- **Feature development**: Implement features requiring changes in multiple services
|
||||
- **Documentation**: Generate docs referencing code from multiple folders
|
||||
- **Testing**: Build & run tests across all workspaces and analyze results
|
||||
### Cline Rules
|
||||
|
||||
When working with large multiroot workspaces, start in [Plan mode](/features/plan-and-act) to let Cline understand your project structure before making changes.
|
||||
[Cline rules](/features/cline-rules) (`.clinerules/` directory) only work in the **primary workspace** (the first folder in your workspace). Rules in other workspace folders are ignored.
|
||||
|
||||
**Workaround:** Place shared rules in the primary workspace, or use global rules (`~/Documents/Cline/Rules/`) which apply everywhere.
|
||||
|
||||
### Checkpoints
|
||||
|
||||
[Checkpoints](/features/checkpoints) are disabled in multi-root workspace mode. Cline displays a warning when this happens.
|
||||
|
||||
**Why:** Checkpoints use a shadow Git repository to track changes. With multiple repositories, coordinating checkpoints across independent Git histories adds complexity that isn't yet supported.
|
||||
|
||||
**Workaround:** Use your normal Git workflow. Commit frequently, or create branches for experimental work.
|
||||
|
||||
Both limitations are restored when you return to a single-folder workspace.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Organizing Your Workspaces
|
||||
|
||||
1. **Group related projects** that often need coordinated changes
|
||||
2. **Use consistent folder structures** across workspaces when possible
|
||||
3. **Name folders clearly** so Cline can understand your project structure
|
||||
2. **Use a workspace file** for reproducible setups across your team
|
||||
3. **Name folders clearly** so workspace hints are intuitive
|
||||
4. **Consider the primary workspace** for Cline rules placement
|
||||
|
||||
### Effective Prompting & Tips
|
||||
### Effective Prompting
|
||||
|
||||
When working with multiroot workspaces, these approaches work best:
|
||||
- **Be specific** when it matters: *"Update the user model in the backend workspace"*
|
||||
- **Reference relationships**: *"The frontend uses types from the contracts workspace"*
|
||||
- **Describe cross-workspace changes**: *"This needs to update both web and mobile"*
|
||||
- **Scope searches** for large codebases: *"Search for 'TODO' only in the frontend workspace"*
|
||||
|
||||
- **Be specific** about which workspace when it matters: *"Update the user model in the backend workspace"*
|
||||
- **Reference relationships**: *"The frontend uses the API types from the shared workspace"*
|
||||
- **Describe cross-workspace operations**: *"This change needs to be reflected in both the web and mobile apps"*
|
||||
- **Scope your searches** when dealing with large codebases: *"Search for 'TODO' in just the frontend workspace"*
|
||||
- **Break down large tasks** into workspace-specific operations when possible
|
||||
- **Consider excluding large folders** like `node_modules` from your workspace search Scope
|
||||
### Working with Large Workspaces
|
||||
|
||||
- Break large tasks into workspace-specific operations when possible
|
||||
- Use [Plan mode](/features/plan-and-act) to let Cline understand structure first
|
||||
- Add a `.clineignore` file to reduce noise, speed up scanning, and keep Cline focused on source code:
|
||||
|
||||
```text
|
||||
# Dependencies
|
||||
**/node_modules/
|
||||
|
||||
# Build outputs
|
||||
**/dist/
|
||||
**/build/
|
||||
|
||||
# VCS metadata
|
||||
**/.git/
|
||||
```
|
||||
|
||||
For more patterns and gotchas, see the [.clineignore File Guide](/prompting/prompt-engineering-guide#clineignore-file-guide).
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
title: "Skills"
|
||||
sidebarTitle: "Skills"
|
||||
description: "Extend Cline with reusable, on-demand instruction sets for specialized tasks"
|
||||
---
|
||||
|
||||
Skills are modular instruction sets that extend Cline's capabilities for specific tasks. Each skill packages detailed guidance, workflows, and optional resources that Cline loads only when relevant to your request.
|
||||
|
||||
Unlike rules (which are always active), skills load on-demand. You can install dozens of skills without affecting context or performance because Cline only sees the skill name and description until it's actually needed.
|
||||
|
||||
<Note>
|
||||
Skills is an experimental feature. Enable it in Settings → Features → Enable Skills.
|
||||
</Note>
|
||||
|
||||
## Why Skills?
|
||||
|
||||
Consider how you'd onboard a new team member: you wouldn't dump every document on them at once. You'd give them a brief overview, then point them to detailed guides when they're working on specific tasks.
|
||||
|
||||
Skills work the same way:
|
||||
- **At startup**: Cline sees only a brief description of each skill
|
||||
- **When triggered**: Cline loads the full instructions for that specific skill
|
||||
- **As needed**: Skills can bundle additional files that Cline reads only when referenced
|
||||
|
||||
This progressive loading means you can package extensive domain knowledge without burning context tokens on information that isn't relevant to the current task.
|
||||
|
||||
## Creating a Skill
|
||||
|
||||
Every skill is a directory containing a `SKILL.md` file with YAML frontmatter:
|
||||
|
||||
```
|
||||
my-skill/
|
||||
├── SKILL.md # Required: main instructions
|
||||
├── docs/ # Optional: additional documentation
|
||||
│ └── advanced.md
|
||||
└── scripts/ # Optional: utility scripts
|
||||
└── helper.sh
|
||||
```
|
||||
|
||||
The `SKILL.md` file has two parts: metadata and instructions.
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: my-skill
|
||||
description: Brief description of what this skill does and when to use it.
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
Detailed instructions for Cline to follow when this skill is activated.
|
||||
|
||||
## Steps
|
||||
|
||||
1. First, do this
|
||||
2. Then do that
|
||||
3. For advanced usage, see [advanced.md](docs/advanced.md)
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
- `name`: Must exactly match the directory name
|
||||
- `description`: Tells Cline when to use this skill (max 1024 characters)
|
||||
|
||||
The description is critical because it's how Cline decides whether to activate a skill. Be specific about what the skill does and when it should be used.
|
||||
|
||||
## Where Skills Live
|
||||
|
||||
Skills can be stored in two locations:
|
||||
|
||||
**Global Skills** apply to all your projects:
|
||||
- **macOS/Linux:** `~/.cline/skills/`
|
||||
- **Windows:** `C:\Users\USERNAME\.cline\skills\`
|
||||
|
||||
**Project Skills** apply only to the current workspace:
|
||||
- `.cline/skills/` (recommended)
|
||||
- `.clinerules/skills/`
|
||||
- `.claude/skills/` (for Claude Code compatibility)
|
||||
|
||||
When a global skill and project skill have the same name, the global skill takes precedence. This lets you customize skills for your personal workflow while still using project defaults.
|
||||
|
||||
## Managing Skills
|
||||
|
||||
Click the scale icon below the chat input to open the rules and workflows panel. When skills are enabled, you'll see a Skills tab where you can:
|
||||
|
||||
- View all available skills (global and workspace)
|
||||
- Toggle individual skills on or off
|
||||
- Create new skills from a template
|
||||
- Delete skills you no longer need
|
||||
|
||||
Skills are enabled by default when discovered. Toggle them off if you want them available but not active for the current project.
|
||||
|
||||
## How Cline Uses Skills
|
||||
|
||||
When you send a message, Cline sees a list of available skills with their descriptions. If your request matches a skill's description, Cline activates it using the `use_skill` tool, which loads the full instructions.
|
||||
|
||||
For example, if you have a skill for deploying to AWS:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: aws-deploy
|
||||
description: Deploy applications to AWS using CDK. Use when deploying, updating infrastructure, or managing AWS resources.
|
||||
---
|
||||
```
|
||||
|
||||
Asking "deploy this to AWS" would trigger Cline to activate the skill, load its detailed instructions, and follow them to complete your request.
|
||||
|
||||
## Example: Data Analysis Skill
|
||||
|
||||
Here's a practical skill for data analysis tasks. Create a directory called `data-analysis/` with this `SKILL.md`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: data-analysis
|
||||
description: Analyze data files and generate insights. Use when working with CSV, Excel, or JSON data files that need exploration, cleaning, or visualization.
|
||||
---
|
||||
```
|
||||
|
||||
Then add the instructions in the body of the file:
|
||||
|
||||
````markdown
|
||||
# Data Analysis
|
||||
|
||||
When analyzing data files, follow this workflow:
|
||||
|
||||
## 1. Understand the Data
|
||||
|
||||
- Read a sample of the file to understand its structure
|
||||
- Identify column types and data quality issues
|
||||
- Note any missing values or anomalies
|
||||
|
||||
## 2. Ask Clarifying Questions
|
||||
|
||||
Before diving in, ask the user:
|
||||
- What specific insights are they looking for?
|
||||
- Are there any known data quality issues?
|
||||
- What format do they want for the output?
|
||||
|
||||
## 3. Perform Analysis
|
||||
|
||||
Use pandas for data manipulation:
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
# Load and explore
|
||||
df = pd.read_csv("data.csv")
|
||||
print(df.head())
|
||||
print(df.describe())
|
||||
print(df.info())
|
||||
```
|
||||
|
||||
For visualization, prefer matplotlib or seaborn depending on complexity.
|
||||
|
||||
## 4. Present Findings
|
||||
|
||||
- Start with a summary of key insights
|
||||
- Support findings with specific numbers
|
||||
- Include visualizations where they add clarity
|
||||
- End with recommendations or next steps
|
||||
````
|
||||
|
||||
## Bundling Supporting Files
|
||||
|
||||
Skills can include additional files that Cline accesses only when needed:
|
||||
|
||||
```
|
||||
complex-skill/
|
||||
├── SKILL.md
|
||||
├── docs/
|
||||
│ ├── setup.md
|
||||
│ └── troubleshooting.md
|
||||
├── templates/
|
||||
│ └── config.yaml
|
||||
└── scripts/
|
||||
└── validate.py
|
||||
```
|
||||
|
||||
Reference these in your instructions:
|
||||
|
||||
````markdown
|
||||
For initial setup, follow [setup.md](docs/setup.md).
|
||||
|
||||
Use the config template at `templates/config.yaml` as a starting point.
|
||||
|
||||
Run the validation script to check your configuration:
|
||||
```bash
|
||||
python scripts/validate.py
|
||||
```
|
||||
````
|
||||
|
||||
Cline reads these files using `read_file` when the instructions reference them. Scripts can be executed directly, with only the output entering the context (not the script code itself).
|
||||
|
||||
## Ideas for Skills
|
||||
|
||||
Skills shine when you have tasks that:
|
||||
- Require detailed, multi-step workflows
|
||||
- Need domain-specific knowledge or best practices
|
||||
- Would otherwise require repeating the same instructions across conversations
|
||||
|
||||
Some possibilities:
|
||||
|
||||
- **Release management**: Version bumping, changelog generation, git tagging, and publishing
|
||||
- **Code review**: Your team's specific review checklist and quality standards
|
||||
- **Database migrations**: Safely evolving schemas with rollback procedures
|
||||
- **API integration**: Connecting to specific third-party services with proper error handling
|
||||
- **Documentation**: Your preferred structure, style guide, and tooling
|
||||
- **Debugging workflows**: Systematic approaches to diagnosing specific types of issues
|
||||
- **Infrastructure**: Terraform/CDK patterns for your cloud setup
|
||||
|
||||
The best skills encode institutional knowledge that would otherwise live only in experienced developers' heads.
|
||||
|
||||
## Skills vs Rules vs Workflows
|
||||
|
||||
| Feature | Purpose | When Active |
|
||||
|---------|---------|-------------|
|
||||
| **Rules** | Define how Cline should behave | Always (or contextually) |
|
||||
| **Workflows** | Step-by-step task automation | Invoked with `/workflow.md` |
|
||||
| **Skills** | Domain expertise loaded on-demand | Triggered by matching requests |
|
||||
|
||||
**Rules** set constraints and preferences (like "always use TypeScript" or "follow this style guide").
|
||||
|
||||
**Workflows** are explicit sequences you invoke for specific tasks (like `/release.md` for a release process).
|
||||
|
||||
**Skills** are expertise that Cline activates automatically when relevant (like data analysis knowledge when you're working with CSV files).
|
||||
|
||||
Use rules for ongoing constraints, workflows for explicit automation, and skills for domain knowledge that should be available but not always active.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Cline Rules](/features/cline-rules) for always-active project guidance
|
||||
- [Workflows](/features/slash-commands/workflows/index) for explicit task automation
|
||||
- [Hooks](/features/hooks/index) for injecting custom logic at key moments
|
||||
|
||||
@@ -12,6 +12,19 @@ sidebarTitle: "/deep-planning"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
## Demo Video
|
||||
|
||||
Watch Deep Planning in action as Cline investigates a codebase, asks clarifying questions, and generates a comprehensive implementation plan:
|
||||
|
||||
<Frame>
|
||||
<video
|
||||
muted
|
||||
controls
|
||||
playsInline
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/Cline-Deep-Planning-Demo.mp4"
|
||||
/>
|
||||
</Frame>
|
||||
|
||||
When you use `/deep-planning`, Cline follows a four-step process that mirrors how senior developers approach complex features: thorough investigation, discussion & clarification of requirements, detailed planning, and structured task creation with progress tracking.
|
||||
|
||||
## The Four-Step Process
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
title: "Explain Changes Command"
|
||||
sidebarTitle: "/explain-changes"
|
||||
---
|
||||
<Note>
|
||||
This command is only available in **VS Code**. The diff view with inline comments requires VS Code's native diff capabilities.
|
||||
</Note>
|
||||
|
||||
`/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.
|
||||
|
||||
|
||||
@@ -120,8 +120,38 @@ Controls a built-in browser to interact with websites or local servers. Useful f
|
||||
</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.
|
||||
### Leveraging MCP Tools
|
||||
|
||||
MCP tools allow Cline to interact with external services like GitHub, Slack, or databases. You can reference them in your workflows using natural language or explicit XML tags for deterministic control.
|
||||
|
||||
#### Natural Language (Heuristic)
|
||||
|
||||
Most of the time, the simplest way to use an MCP tool is to describe the action you want Cline to take.
|
||||
|
||||
```markdown
|
||||
1. Fetch the latest issues from the github-repo MCP server.
|
||||
2. Summarize the critical bugs.
|
||||
3. Post the summary to the #engineering channel using the slack-notifications MCP.
|
||||
```
|
||||
|
||||
#### Explicit XML Tag (Deterministic)
|
||||
|
||||
For critical automation where you need exact control over parameters, use the `use_mcp_tool` tag.
|
||||
|
||||
```xml
|
||||
<use_mcp_tool>
|
||||
<server_name>github-repo-manager</server_name>
|
||||
<tool_name>create_issue</tool_name>
|
||||
<arguments>
|
||||
{
|
||||
"owner": "cline",
|
||||
"repo": "cline",
|
||||
"title": "Automated Bug Report",
|
||||
"body": "Found a regression in the latest build."
|
||||
}
|
||||
</arguments>
|
||||
</use_mcp_tool>
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "Web Tools"
|
||||
sidebarTitle: "Web Tools"
|
||||
description: "Search the web and fetch content from URLs directly within Cline"
|
||||
---
|
||||
|
||||
Web Tools give Cline the ability to search the internet and fetch content from specific URLs during your tasks. This is useful when you need up-to-date information, documentation lookups, or research that goes beyond your local codebase and the LLM's internal knowledge.
|
||||
|
||||
<Warning>
|
||||
Web Tools require the **Cline provider**. They are not available when using other providers like OpenRouter, Anthropic, AWS Bedrock, etc.
|
||||
</Warning>
|
||||
|
||||
## How Web Tools Work
|
||||
|
||||
Cline has two web tools:
|
||||
|
||||
- **web_search**: Searches the web and returns a list of relevant webpages based on your query
|
||||
- **web_fetch**: Fetches and analyzes content from a specific URL
|
||||
|
||||
When Cline determines that web information would help complete your task, it will use these tools automatically. The tools call Cline's backend API, which handles the search or fetch operation and returns the results.
|
||||
|
||||
## Enabling Web Tools
|
||||
|
||||
Web Tools are available when using the Cline provider. To use them:
|
||||
|
||||
1. Make sure you're signed in to Cline
|
||||
2. Ensure you're using the Cline provider
|
||||
3. Enable the Web Tools toggle in the Feature Settings menu
|
||||
|
||||
<Note>
|
||||
Web tools can be auto-approved using the "Use the browser" setting in [Auto Approve](/features/auto-approve).
|
||||
</Note>
|
||||
|
||||
## Use Cases
|
||||
|
||||
### Looking Up Documentation
|
||||
|
||||
When working with unfamiliar libraries or APIs:
|
||||
- Search for official documentation
|
||||
- Fetch specific API reference pages
|
||||
- Get examples and usage patterns
|
||||
|
||||
### Research Before Implementation
|
||||
|
||||
Before implementing a feature:
|
||||
- Search for best practices and common patterns
|
||||
- Find recent discussions about approaches
|
||||
- Look up known issues or limitations
|
||||
|
||||
### Checking Latest Information
|
||||
|
||||
For time-sensitive information:
|
||||
- Latest release notes and changelogs
|
||||
- Recent bug fixes or security updates
|
||||
- Current recommended versions
|
||||
@@ -29,7 +29,7 @@ Cline is an open source AI coding agent that brings frontier AI models directly
|
||||
Master Cline's powerful features and optimize your workflow
|
||||
</Card>
|
||||
|
||||
<Card title="Enterprise" icon="building" href="/enterprise-solutions/security-concerns">
|
||||
<Card title="Enterprise" icon="building" href="/enterprise-solutions/overview">
|
||||
Deploy Cline in your organization with confidence
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Generated
+2310
-1422
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user