mirror of
https://github.com/cline/cline.git
synced 2026-09-11 16:42:40 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71da25835a | ||
|
|
8f79c7a714 | ||
|
|
db4bd8b24c | ||
|
|
1d8b99f900 | ||
|
|
b6e6d7afad | ||
|
|
a616b4c5f8 | ||
|
|
16774c7cd2 | ||
|
|
d5a577389d | ||
|
|
bba90e9af4 | ||
|
|
d566a79929 | ||
|
|
2484d24f97 | ||
|
|
b8f40f4edc | ||
|
|
f936d53d25 | ||
|
|
7dfe88072e | ||
|
|
90c8112257 | ||
|
|
83a2824103 | ||
|
|
86f463496c | ||
|
|
5198db81f7 | ||
|
|
576d126208 | ||
|
|
c878c663eb | ||
|
|
72562ea74e | ||
|
|
562b636481 | ||
|
|
544e3aa240 | ||
|
|
beb3ad78dc | ||
|
|
82faedead3 | ||
|
|
770f3e0807 | ||
|
|
79c6381893 | ||
|
|
c9d051cc0b | ||
|
|
a5d6bcecea | ||
|
|
289ddd6922 | ||
|
|
65d93eedab | ||
|
|
2928f68fd0 | ||
|
|
c1e07f26a9 | ||
|
|
17018066a7 | ||
|
|
54fc8e2a7e | ||
|
|
af35bd28b2 | ||
|
|
bce75f9821 | ||
|
|
ee1d4b4dcf | ||
|
|
f2eda58c70 | ||
|
|
4afc973f7d | ||
|
|
5fe6c9a8ce | ||
|
|
901d1b5c97 | ||
|
|
c139f7a4d5 | ||
|
|
07593bb42a | ||
|
|
fd21c314c1 | ||
|
|
32ca1cad9a | ||
|
|
e272a8dfbb | ||
|
|
c516230809 | ||
|
|
50021c8c5a | ||
|
|
b953c3682a | ||
|
|
852c65b70c | ||
|
|
70f0e8d548 | ||
|
|
f3f5bdd902 | ||
|
|
1d91dbc894 | ||
|
|
5accd88d73 | ||
|
|
5b29be63b8 | ||
|
|
401abd9434 | ||
|
|
79e99eb526 | ||
|
|
f1d2569931 | ||
|
|
efbacbbf33 | ||
|
|
69e6ab9069 | ||
|
|
9dea336ced | ||
|
|
697f801937 | ||
|
|
6be35bfcea | ||
|
|
5a91800b6c | ||
|
|
dacadbaae0 | ||
|
|
cfc5abca02 | ||
|
|
4da5614863 | ||
|
|
cef0da35e2 | ||
|
|
62fa67d833 | ||
|
|
4a57450c07 | ||
|
|
8d020e89e6 | ||
|
|
c6dbc8bcb0 | ||
|
|
1862f15955 | ||
|
|
f6a9a02500 | ||
|
|
10af2439be | ||
|
|
9a8fbf9852 | ||
|
|
c5657a14bb | ||
|
|
955ae8df7a | ||
|
|
9405419efe | ||
|
|
f53dcb3096 | ||
|
|
afa32bf801 | ||
|
|
2d2d9d829a | ||
|
|
27a1b3da8a | ||
|
|
f5c8cd4384 | ||
|
|
fddabb6b8d | ||
|
|
071f32ec92 | ||
|
|
9bdb8a9362 | ||
|
|
2d994530fd | ||
|
|
e477f8fa04 | ||
|
|
36b0baec81 | ||
|
|
a0faf7c677 | ||
|
|
1dcf356f98 | ||
|
|
71d795eec8 | ||
|
|
792b9e89a1 | ||
|
|
10197b038d | ||
|
|
9390d3f933 | ||
|
|
5df470bf48 | ||
|
|
034c4342d1 | ||
|
|
349295ab2c | ||
|
|
841402c178 | ||
|
|
e52a052c81 | ||
|
|
7a91a9be2e | ||
|
|
93a494d009 | ||
|
|
2bd21f8a45 | ||
|
|
2f33f71ebd | ||
|
|
dec10aaec3 | ||
|
|
c09705a45f | ||
|
|
73058c871a | ||
|
|
03211f1364 | ||
|
|
65e9727c65 | ||
|
|
884fbfb21e | ||
|
|
0c880c7bb1 | ||
|
|
a915122c6d | ||
|
|
835ed94736 | ||
|
|
00ca3d13fa | ||
|
|
3f3a87aed9 | ||
|
|
7a0f11837e | ||
|
|
03d2d01eed | ||
|
|
db1b1c45bd | ||
|
|
a4131e57d8 | ||
|
|
683dd9a741 | ||
|
|
0d437f71b0 | ||
|
|
fc77c0faea | ||
|
|
12c85c4233 | ||
|
|
d6b7a1ab41 | ||
|
|
3926e7b404 | ||
|
|
5ba4314b9a | ||
|
|
9ccad7f764 | ||
|
|
3562f54dbf | ||
|
|
d427d5d76a | ||
|
|
072e2887b0 | ||
|
|
9c71a6f021 | ||
|
|
7627a382aa | ||
|
|
978c633c90 | ||
|
|
e807b520e0 | ||
|
|
f3a3f30db5 | ||
|
|
f1c7934064 | ||
|
|
1d42da5248 | ||
|
|
7f3974a827 | ||
|
|
d40ab56aff | ||
|
|
bbdf445db7 | ||
|
|
d992a3bf21 | ||
|
|
ace95988f8 | ||
|
|
54726f1677 | ||
|
|
6308fef0a9 | ||
|
|
b3fc79b8ce | ||
|
|
ff05ec3bbe | ||
|
|
bde7049c01 | ||
|
|
7cd06744ad | ||
|
|
c88d3238cf | ||
|
|
852ca2348f | ||
|
|
7091ccf2c7 | ||
|
|
ad6c33ac5b | ||
|
|
71e312e92a | ||
|
|
5903840f79 | ||
|
|
0c677b63db | ||
|
|
b741135f85 | ||
|
|
2eab216815 | ||
|
|
e57174eec8 | ||
|
|
ad4631c682 | ||
|
|
aae23e8685 | ||
|
|
0ff27591d5 | ||
|
|
23127f22de | ||
|
|
57ed14d1b1 | ||
|
|
a769585406 | ||
|
|
c44b29b002 | ||
|
|
bb4e397a51 | ||
|
|
9824d8d476 | ||
|
|
a46c5288ca | ||
|
|
91b947de69 | ||
|
|
7b25a21b26 | ||
|
|
1d1071dcf5 | ||
|
|
8d07b7d6cb | ||
|
|
aacc69a558 | ||
|
|
a27bedffb6 | ||
|
|
bbeecefe84 | ||
|
|
50b57f472f | ||
|
|
0e7e0099cd | ||
|
|
44cbfe9d9c | ||
|
|
4ecccf5105 | ||
|
|
6d29bc6551 | ||
|
|
820057308a | ||
|
|
7355f7e3b9 | ||
|
|
156f18f7b2 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"cline": patch
|
||||
---
|
||||
|
||||
Adds a new "Prompts Library" view
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PostToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "PreToolUse hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskCancel hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskResume hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "TaskStart hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
|
||||
|
||||
input=$(cat)
|
||||
echo $input | jq .
|
||||
|
||||
for i in {1..5}; do
|
||||
sleep 1
|
||||
echo "$i"
|
||||
done
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
|
||||
"errorMessage": "UserPromptSubmit hook custom errorMessage"
|
||||
}
|
||||
EOF
|
||||
@@ -1,4 +1,6 @@
|
||||
demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
assets/docs/demo.gif filter=lfs diff=lfs merge=lfs -text
|
||||
webview-ui/src/assets/cline_kanban_demo.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||
webview-ui/src/assets/cline_kanban_demo.webm filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
* text=auto eol=lf
|
||||
|
||||
@@ -26,6 +26,12 @@ body:
|
||||
placeholder: 'e.g., 1.2.3'
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: beta
|
||||
attributes:
|
||||
label: Beta version
|
||||
options:
|
||||
- label: I am using a beta version of Cline
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
name: CLI TUI Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cli-tui-tests:
|
||||
name: CLI TUI Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build CLI
|
||||
run: npm run cli:build
|
||||
|
||||
- name: Run TUI Tests
|
||||
id: tui_tests
|
||||
run: |
|
||||
npm run test:e2e:cli:tui 2>&1 | tee tui-test-output.log
|
||||
exit_code=${PIPESTATUS[0]}
|
||||
echo "tui_exit_code=$exit_code" >> $GITHUB_OUTPUT
|
||||
exit $exit_code
|
||||
|
||||
- name: Write failure summary
|
||||
if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped'
|
||||
run: |
|
||||
echo "## ❌ CLI TUI Tests Failed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Step outcome:** \`${{ steps.tui_tests.outcome }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Test Output" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
if [ -f tui-test-output.log ]; then
|
||||
cat tui-test-output.log >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "(no test output captured — process may have been killed before output was flushed)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Debugging" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **TUI traces** are attached as artifacts below — download and inspect them to see terminal state at the point of failure." >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **To view a trace replay/Run a TUI Trace: ** run \`npx tui-test show-trace path/to/trace/file\` in your terminal" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Full test log** is also attached as an artifact." >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Tests run with \`retries: 2\` so any failure shown is a consistent failure, not a flake." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload TUI traces
|
||||
if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tui-test-traces
|
||||
path: tests/e2e/cli/tui-traces/
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
|
||||
- name: Upload test log
|
||||
if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tui-test-log
|
||||
path: tui-test-output.log
|
||||
retention-days: 14
|
||||
if-no-files-found: warn
|
||||
@@ -51,3 +51,15 @@ jobs:
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check if beta version checkbox is checked
|
||||
if (body.includes('- [X] I am using a beta version of Cline') || body.includes('- [x] I am using a beta version of Cline')) {
|
||||
if (!labels.includes('beta')) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['beta']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,3 +91,21 @@ jobs:
|
||||
echo ""
|
||||
echo "📦 Install with: npm install -g cline"
|
||||
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
|
||||
|
||||
- name: Post release to Slack
|
||||
uses: slackapi/slack-github-action@v3.0.1
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: "C0APVKGGZFC"
|
||||
text: "Cline CLI v${{ steps.version.outputs.version }}"
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Cline CLI v${{ steps.version.outputs.version }}*"
|
||||
- type: "context"
|
||||
elements:
|
||||
- type: "mrkdwn"
|
||||
text: "<https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}|View on npm>"
|
||||
|
||||
@@ -31,8 +31,10 @@ jobs:
|
||||
|
||||
- name: Check for recent commits
|
||||
id: check_commits
|
||||
env:
|
||||
FORCE_PUBLISH: ${{ inputs.force_publish }}
|
||||
run: |
|
||||
if [ "${{ inputs.force_publish }}" = "true" ]; then
|
||||
if [ "$FORCE_PUBLISH" = "true" ]; then
|
||||
echo "force_publish enabled, proceeding with publish"
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
|
||||
@@ -30,7 +30,11 @@ permissions:
|
||||
pull-requests: write # Required by nested reusable test workflow
|
||||
|
||||
jobs:
|
||||
cli-tui-tests:
|
||||
uses: ./.github/workflows/cli-tui-tests.yml
|
||||
|
||||
publish-main:
|
||||
needs: cli-tui-tests
|
||||
if: |
|
||||
github.repository == 'cline/cline' && (
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
@@ -44,6 +48,7 @@ jobs:
|
||||
confirm_publish: ${{ github.event.inputs.confirm_publish }}
|
||||
|
||||
publish-nightly:
|
||||
needs: cli-tui-tests
|
||||
if: |
|
||||
github.repository == 'cline/cline' && (
|
||||
github.event_name == 'schedule' ||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
name: "Publish SDK Nightly Release"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # 4 AM PST (UTC-8) = 12 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
env:
|
||||
# Keep the publish source pinned to one reviewed branch instead of accepting arbitrary refs.
|
||||
SDK_NIGHTLY_REF: dpc/sdk-migration-simpler-login
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Cline (Nightly SDK) Extension
|
||||
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
environment: PublishNightly
|
||||
|
||||
steps:
|
||||
- name: Checkout trusted SDK nightly branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ env.SDK_NIGHTLY_REF }}
|
||||
lfs: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
# Keep publish environment aligned with test workflow/tooling lockfile expectations.
|
||||
# Newer LTS (Node 24 / npm 11) can make `npm list` fail with ELSPROBLEMS during vsce packaging.
|
||||
node-version: 22
|
||||
|
||||
- name: Install root dependencies
|
||||
run: npm ci --include=optional
|
||||
|
||||
- name: Install webview-ui dependencies
|
||||
run: cd webview-ui && npm ci --include=optional
|
||||
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
- name: Verify LFS media assets are resolved
|
||||
run: |
|
||||
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
|
||||
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
|
||||
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Publish SDK nightly extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
|
||||
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
|
||||
CLINE_ENVIRONMENT: production
|
||||
# OpenTelemetry production defaults (can be overridden at runtime)
|
||||
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
|
||||
OTEL_LOGS_EXPORTER: otlp
|
||||
OTEL_METRICS_EXPORTER: otlp
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
run: npm run publish:marketplace:nightly
|
||||
@@ -1,8 +1,6 @@
|
||||
name: "Publish Nightly Release"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *' # 4 AM PST (UTC-8) = 12 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -24,6 +22,8 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: true
|
||||
|
||||
- name: Check for recent commits
|
||||
run: |
|
||||
@@ -49,7 +49,16 @@ jobs:
|
||||
- name: Install Publishing Tools
|
||||
run: npm install -g @vscode/vsce ovsx
|
||||
|
||||
- name: Publish Extension as Pre-release
|
||||
- name: Verify LFS media assets are resolved
|
||||
run: |
|
||||
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
|
||||
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
|
||||
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Publish Nightly Extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
OVSX_PAT: ${{ secrets.OVSX_PAT }}
|
||||
|
||||
@@ -43,12 +43,14 @@ jobs:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
lfs: true
|
||||
|
||||
- name: Resolve Release Tag
|
||||
id: resolve_tag
|
||||
env:
|
||||
TAG: ${{ github.event.inputs.tag }}
|
||||
AUTO_CREATE: ${{ github.event.inputs.auto_create_tag_from_main }}
|
||||
run: |
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
AUTO_CREATE="${{ github.event.inputs.auto_create_tag_from_main }}"
|
||||
TESTED_SHA="${{ github.sha }}"
|
||||
WORKFLOW_REF="${{ github.ref }}"
|
||||
|
||||
@@ -133,6 +135,15 @@ jobs:
|
||||
fi
|
||||
echo "Tag and package version match: $TAG"
|
||||
|
||||
- name: Verify LFS media assets are resolved
|
||||
run: |
|
||||
for FILE in webview-ui/src/assets/cline_kanban_demo.mp4 webview-ui/src/assets/cline_kanban_demo.webm; do
|
||||
if grep -q "git-lfs.github.com/spec/v1" "$FILE"; then
|
||||
echo "Error: $FILE is still a Git LFS pointer in CI checkout"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Package and Publish Extension
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
@@ -147,11 +158,12 @@ jobs:
|
||||
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
|
||||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
|
||||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
|
||||
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
|
||||
run: |
|
||||
# Required to generate the .vsix
|
||||
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
|
||||
|
||||
if [ "${{ github.event.inputs.release-type }}" = "pre-release" ]; then
|
||||
if [ "$RELEASE_TYPE" = "pre-release" ]; then
|
||||
npm run publish:marketplace:prerelease
|
||||
echo "Successfully published pre-release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
|
||||
else
|
||||
@@ -187,3 +199,25 @@ jobs:
|
||||
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Post release to Slack
|
||||
uses: slackapi/slack-github-action@v3.0.1
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: "C0APVKGGZFC"
|
||||
text: "Cline ${{ steps.resolve_tag.outputs.tag }}"
|
||||
blocks:
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: "*Cline ${{ steps.resolve_tag.outputs.tag }}*"
|
||||
- type: "section"
|
||||
text:
|
||||
type: "mrkdwn"
|
||||
text: ${{ toJSON(steps.changelog.outputs.content) }}
|
||||
- type: "context"
|
||||
elements:
|
||||
- type: "mrkdwn"
|
||||
text: "Full Changelog: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.resolve_tag.outputs.tag }}"
|
||||
|
||||
@@ -46,6 +46,8 @@ jobs:
|
||||
|
||||
test:
|
||||
needs: quality-checks
|
||||
env:
|
||||
VSCODE_TEST_VERSION: 1.103.0
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -81,6 +83,13 @@ jobs:
|
||||
run: |
|
||||
npm config set script-shell "C:\\Program Files\\Git\\bin\\bash.exe"
|
||||
|
||||
- name: Cache VS Code test runtime
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .vscode-test
|
||||
key: vscode-test-runtime-${{ runner.os }}-${{ env.VSCODE_TEST_VERSION }}
|
||||
|
||||
# Build the extension and tests (without redundant checks)
|
||||
- name: Build Tests and Extension
|
||||
id: build_step
|
||||
@@ -106,7 +115,21 @@ jobs:
|
||||
- name: Extension Integration Tests - Non-Linux
|
||||
id: integration_tests_non_linux
|
||||
if: ${{ !cancelled() && steps.build_step.outcome == 'success' && runner.os != 'Linux' }}
|
||||
run: npm run test:integration
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
echo "Running extension integration tests (attempt ${attempt}/3)"
|
||||
if npm run test:integration; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$attempt" -eq 3 ]; then
|
||||
echo "Extension integration tests failed after 3 attempts"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Extension integration tests failed; retrying after short delay"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
- name: Webview Tests with Coverage
|
||||
id: webview_tests
|
||||
|
||||
@@ -51,3 +51,8 @@ test-results
|
||||
|
||||
# Smoke test results (generated)
|
||||
evals/smoke-tests/results/
|
||||
|
||||
.tui-test
|
||||
secrets.json
|
||||
tui-traces
|
||||
tests/**/cache
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@
|
||||
"ts"
|
||||
],
|
||||
"spec": [
|
||||
"src/**/__tests__/*.ts"
|
||||
"src/**/__tests__/*.ts",
|
||||
"src/test/services/**/*.test.ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register",
|
||||
|
||||
+3
-1
@@ -1,6 +1,8 @@
|
||||
import { defineConfig } from "@vscode/test-cli"
|
||||
import path from "path"
|
||||
|
||||
const vscodeTestVersion = process.env.VSCODE_TEST_VERSION ?? "stable"
|
||||
|
||||
export default defineConfig({
|
||||
files: "{out/**/*.test.js,src/**/*.test.js,!src/test/e2e/**/*.test.js,!out/src/test/e2e/**/*.test.js}",
|
||||
mocha: {
|
||||
@@ -12,7 +14,7 @@ export default defineConfig({
|
||||
require: ["./test-setup.js"],
|
||||
},
|
||||
workspaceFolder: "test-workspace",
|
||||
version: "stable",
|
||||
version: vscodeTestVersion,
|
||||
extensionDevelopmentPath: path.resolve("./"),
|
||||
launchArgs: ["--disable-extensions"],
|
||||
})
|
||||
|
||||
+174
@@ -1,5 +1,179 @@
|
||||
# Changelog
|
||||
|
||||
## [3.82.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Restore VS Code foreground terminal support and settings.
|
||||
- Add latest OpenAI, SAP AI Core, and Z AI models.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix hook template JSON escaping.
|
||||
- Improve ripgrep file search error handling.
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove hardcoded model lists from docs.
|
||||
|
||||
## [3.81.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add GPT-5.5 model support for OpenAI Codex subscription users.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Remove hardcoded "What’s New" fallback items in webview; only remote-configured welcome banners are shown.
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve cline-core memory diagnostics used by the extension runtime:
|
||||
- enable near-heap-limit heap snapshots
|
||||
- add periodic memory usage logging
|
||||
- log discovered heap snapshots on abnormal exits for easier OOM debugging
|
||||
|
||||
## [3.80.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Wire up remote `globalSkills` from enterprise remote config with full UI, toggle support, and system prompt integration — enterprise-managed skills now appear under a dedicated "Enterprise Skills" section and support `alwaysEnabled` enforcement
|
||||
- Onboarding flow now uses dynamically fetched recommended models instead of a hardcoded list, with a fallback to the welcome view on failure
|
||||
- Add dedicated "Quota Exceeded" error message in the chat error UI when Cline account spend caps are hit
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix OOM crashes during long conversations by setting `--max-old-space-size=8192` for the cline-core Node.js process (was defaulting to ~2 GB)
|
||||
- Show detailed error information in the chat error row instead of a generic caught error message
|
||||
- Update `axios` to 1.15.0 across all packages
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove foreground terminal mode — all task command execution now defaults to background mode, removing the VS Code integrated terminal dependency and related settings UI
|
||||
- Remove old hardcoded announcement banners
|
||||
|
||||
## [3.79.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add Claude Opus 4.7 model support
|
||||
- Add Azure Blob Storage as a storage provider
|
||||
- Add `globalSkills` to remote config
|
||||
- Inline value reuse in user-level remote-config discovery
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix cache reflection for Cline and Vercel API handlers
|
||||
- Fix stuck `command_output` ask when terminal command ends unexpectedly
|
||||
- Add `use_subagents` to system prompt for GLM, Hermes, and XS models
|
||||
- Fix action injection security risk
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove deprecated evals tool
|
||||
|
||||
## [3.78.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add a dedicated "Spend Limit Reached" error UI when spend caps are hit
|
||||
- Docs updates
|
||||
|
||||
### Fixed
|
||||
|
||||
- Show actual `read_file` line ranges in chat UI
|
||||
|
||||
## [3.77.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add "Lazy Teammate Mode" experimental toggle
|
||||
- `read_file` tool now supports chunked reading for targeted file access
|
||||
|
||||
### Fixed
|
||||
|
||||
- Exclude `new_task` tool from system prompt in yolo/headless mode
|
||||
- Fix Kanban demo video formatting
|
||||
|
||||
### Changed
|
||||
|
||||
- Polish `Notification` hook functionality
|
||||
|
||||
## [3.76.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add Cline Kanban launch modal in webview; CLI now launches Kanban by default with a migration view
|
||||
- Add toggle to disable feature tips in chat
|
||||
- Add repeated tool call loop detection to prevent infinite loops wasting tokens
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix CLI Kanban spawn on Windows by enabling shell mode for `npx.cmd`
|
||||
|
||||
## [3.75.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Latency improvements for remote workspaces
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stabilize flaky hooks tests
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove example hooks in favor of reading the docs
|
||||
|
||||
## [3.74.0]
|
||||
|
||||
### Added
|
||||
- Implement dynamic free model detection for Cline API
|
||||
- Add file read deduplication cache to prevent repeated reads
|
||||
- Add feature tips tooltip during thinking state
|
||||
|
||||
### Fixed
|
||||
- Replace error message when not logged in to Cline
|
||||
- Align ClineRulesToggleModal padding with ServersToggleModal
|
||||
- Skip WebP for GLM and Devstral models running through llama.cpp
|
||||
- Respect user-configured context window in LiteLLM getModel()
|
||||
- Honor explicit model IDs outside static catalog in W&B provider
|
||||
- Add missing Fireworks serverless models and pricing
|
||||
|
||||
## [3.73.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added W&B Inference by CoreWeave as a new API provider with 17 models
|
||||
- Improved parallel tool calling support for OpenRouter and Cline providers
|
||||
|
||||
### Fixed
|
||||
|
||||
- Claude Code Provider: handle rate limit events, empty content arrays, error results, and unknown content types without crashing
|
||||
- Tool handlers (`read_file`, `list_files`, `list_code_definition_names`, `search_files`) now return graceful errors instead of crashing
|
||||
|
||||
## [3.72.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added Anthropic Opus 4.6 fast mode variants
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resolved native tool placeholder interpolation in prompts
|
||||
- Gemini: capped Flash output tokens to 8192 across providers
|
||||
- Fixed Windows unit test path normalization
|
||||
- Fixed flaky hooks tests on Windows
|
||||
- Bedrock: handle thinking and redacted_thinking blocks correctly in message conversion and streaming
|
||||
- Prevent crash when `list_files` or `list_code_definition_names` receives a file path
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated Jupyter Notebook GIFs
|
||||
- Markdown image loading now requires user consent
|
||||
- Added `.github/copilot-instructions.md` for coding agents
|
||||
- Hooks: reintroduced feature toggle
|
||||
|
||||
## [3.71.0]
|
||||
|
||||
### Added
|
||||
|
||||
@@ -3,11 +3,6 @@ English | <a href="https://github.com/cline/cline/blob/main/locales/es/README.md
|
||||
</sub></div>
|
||||
|
||||
# Cline
|
||||
|
||||
<p align="center">
|
||||
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<table>
|
||||
<tbody>
|
||||
|
||||
+3
-5
@@ -8,9 +8,7 @@ We actively patch only the most recent minor release of Cline. Older versions re
|
||||
|
||||
We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your contributions.
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/cline/cline/security/advisories/new) tab.
|
||||
|
||||
The team will send a response indicating the next steps in handling your report. After the initial reply, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
|
||||
To report a security issue, please submit your report through our [Bugcrowd Vulnerability Disclosure Program](https://bugcrowd.com/engagements/clinebot-vdp-ess). Bugcrowd will manage communication and triage on our behalf.
|
||||
|
||||
When reporting, please include:
|
||||
|
||||
@@ -18,10 +16,10 @@ When reporting, please include:
|
||||
- Steps to reproduce or a proof of concept
|
||||
- Any logs, stack traces, or screenshots that might help us understand the problem
|
||||
|
||||
We acknowledge reports within 48 hours and aim to release a fix or mitigation within 30 days. While we work on a resolution, please keep the details private.
|
||||
Please keep the details private until a resolution has been reached.
|
||||
|
||||
## Escalation
|
||||
|
||||
If you do not receive an acknowledgement of your report within 5 business days, you may send an email to security@cline.bot.
|
||||
If you are unable to submit through Bugcrowd, you may send an email to security@cline.bot.
|
||||
|
||||
Thank you for helping us keep Cline users safe.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 535">
|
||||
<!-- Generator: Adobe Illustrator 29.8.5, SVG Export Plug-In . SVG Version: 2.1.1 Build 2) -->
|
||||
<defs>
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #fff;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<path class="st0" d="M500.6,300.5c-9-20.7-17.9-41.4-26.9-62.1-.7-2-.3-4.4-.3-6.4.4-9,1.1-18,1.4-27,2.8-28.4-6.5-58-25.2-79.6-15.1-18.1-36.6-30.7-59.6-35.5-8.1-1.8-16.6-1.6-25-2.1-10-.7-20-1-30-1.7,2-11.9,1-24.1-3.7-35.3-5.8-14.1-16.8-25.9-30.6-32.5-14.4-7-31.5-8.2-46.7-3.1-16,5.2-29.5,17-36.8,32.1-4.9,10-6.8,21.2-6.1,32.2-19.7-1-39.4-2.2-59.1-3.1-26.8.5-53,11.7-72,30.6-20.2,19.5-31.7,47-32.3,75-.5,9.3-1,18.7-1.5,28-.2,2.1,0,4.1-1.2,6-9.8,16.8-19.5,33.7-29.4,50.6-2.2,4.1-4.9,8-6.6,12.3-2,5.7-1.2,12.2,1.3,17.6,8.9,19.5,17.6,39.2,26.5,58.7.8,1.9,1.5,3.7,1.3,5.8-.6,10.3-1.1,20.7-1.7,31-1.5,21.2,3,42.6,13.5,61.1,8.8,15.8,21.6,29.4,37.1,38.9,13.9,8.7,29.7,13.9,46,15.4,72,3.9,144,7.7,216,11.5,20.1,1.8,40.8-2.8,58.5-12.5,18.8-10.1,34.2-26,44.1-44.9,6.5-12.6,10.5-26.4,11.7-40.5.7-12.4,1.2-24.7,2-37.1,0-3.3,1.9-5.5,3.3-8.2,6.6-11.8,13.5-23.4,20.1-35.2,3.7-6.9,8.1-13.4,11.6-20.4,3.2-6.1,3.2-13.5.3-19.7ZM218.5,316.5c-9.7,7.1-21.3,12.3-33.5,12.5-17.6,1-35.1-5.3-49-16-4.6-3.2-8.1-7.5-9.6-13,0-1.8-.7-3.6,1.7-3.8,4,1,7.9,2.6,12,3.5,22.8,5.6,47.6,5.9,71,4.8,6.5-.2,13-1.3,19.5-.9-2.7,5.6-7.1,9.2-12,12.9ZM276,449.7c-14,.5-28,.1-42-.2-2.1,0-4.3,0-6.4-.4-.9-2.1.6-3.2,1.7-4.8,4.8-5.9,11-11,18.7-12.4,8.4-1.6,16.5,1.2,23.5,5.5,4.7,3,9.2,6.3,12.6,10.8-2.6,1.1-5.3,1.4-8.1,1.4ZM390.4,319.4c-16.4,14.2-38.8,21.8-60.4,18.4-13.2-1.6-24.7-8.6-34.1-17.7-3-3-6.2-6.5-8.1-10.4.5-1,1.2-1.6,2.2-1.6,2.8-.2,5.7.7,8.5,1.1,16,2.9,32.3,4.9,48.5,5.5,14.3.4,28.2-.2,42.2-3.6,2.2-.6,3.7-.3,5.8.5-1.1,2.9-2.2,5.7-4.6,7.8Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,5 +1,149 @@
|
||||
# cline
|
||||
|
||||
## [2.18.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Restore foreground terminal support and settings.
|
||||
- Add latest OpenAI, SAP AI Core, and Z AI models.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix hook template JSON escaping.
|
||||
- Improve ripgrep file search error handling.
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove hardcoded model lists from docs.
|
||||
|
||||
## [2.17.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add GPT-5.5 model support for OpenAI Codex subscription users.
|
||||
|
||||
### Changed
|
||||
|
||||
- Improve `cline-core` runtime memory diagnostics used by CLI:
|
||||
- enable near-heap-limit heap snapshots
|
||||
- add periodic memory usage logging
|
||||
- log discovered heap snapshots on abnormal exits for easier OOM debugging
|
||||
|
||||
## [2.16.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Wire up remote `globalSkills` from enterprise remote config with full toggle support and system prompt integration — enterprise-managed skills now support `alwaysEnabled` enforcement
|
||||
- Add dedicated "Quota Exceeded" error message when Cline account spend caps are hit
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix OOM crashes during long conversations by setting `--max-old-space-size=8192` for the cline-core Node.js process (was defaulting to ~2 GB)
|
||||
- Show detailed error information instead of a generic caught error message
|
||||
- Update `axios` to 1.15.0 across all packages
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove dead ACP terminal setter stubs as part of foreground terminal mode removal
|
||||
|
||||
## [2.15.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Add Claude Opus 4.7 model support
|
||||
- Inline value reuse in user-level remote-config discovery
|
||||
- Add `globalSkills` to remote config
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stabilize Windows CI test path handling
|
||||
|
||||
## [2.14.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Simplify unified `cline update` flow for `cline` and `kanban`
|
||||
- Docs updates
|
||||
|
||||
### Fixed
|
||||
|
||||
- Update Kanban migration view copy
|
||||
|
||||
## [2.12.0]
|
||||
|
||||
### Added
|
||||
|
||||
- `read_file` tool now supports chunked reading for targeted file access
|
||||
|
||||
### Fixed
|
||||
|
||||
- Exclude `new_task` tool from system prompt in yolo/headless mode
|
||||
|
||||
### Changed
|
||||
|
||||
- Polish `Notification` hook functionality
|
||||
|
||||
## [2.9.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Latency improvements for remote workspaces
|
||||
|
||||
## [2.8.2]
|
||||
|
||||
### Fixed
|
||||
- Use `kanban@latest` in `cline kanban` to always fetch the newest version
|
||||
|
||||
## [2.8.1]
|
||||
|
||||
### Added
|
||||
- Implement dynamic free model detection for Cline API
|
||||
- Add file read deduplication cache to prevent repeated reads
|
||||
- Add feature tips tooltip during thinking state
|
||||
|
||||
### Fixed
|
||||
- Fix flaky CLI Enter-key handling across Windows/test environments
|
||||
- Replace error message when not logged in to Cline
|
||||
- Align ClineRulesToggleModal padding with ServersToggleModal
|
||||
- Skip WebP for GLM and Devstral models running through llama.cpp
|
||||
- Respect user-configured context window in LiteLLM getModel()
|
||||
- Honor explicit model IDs outside static catalog in W&B provider
|
||||
- Add missing Fireworks serverless models and pricing
|
||||
|
||||
## [2.8.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added W&B Inference by CoreWeave as a new API provider with 17 models including DeepSeek-V3.1, Llama 4, and Qwen3-Coder
|
||||
- Added CLI TUI end-to-end test suite
|
||||
|
||||
### Fixed
|
||||
|
||||
- Claude Code: handle rate limit events, empty content arrays, error results, and unknown content types without crashing
|
||||
- CLI: `/q` and `/exit` slash commands now execute immediately on Enter without requiring the slash menu to be visible
|
||||
- CLI: slash command filtering now prioritizes exact and prefix matches over fuzzy matches
|
||||
|
||||
## [2.7.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Added MCP add shortcuts for stdio and HTTP servers
|
||||
- Added `--continue` for the current directory
|
||||
- Added `--auto-condense` flag for AI-powered context compaction
|
||||
- Added `--hooks-dir` flag for runtime hook injection
|
||||
- Enabled error autocapture
|
||||
- Prompt rules now include test verification guidance and make `CLI_RULES` language-agnostic
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed remount behavior so TUI remounts only on width resize
|
||||
- Fixed startup prompt replay on resize remount
|
||||
- Fixed task flags so they are applied before the welcome TUI mounts
|
||||
|
||||
### Changed
|
||||
|
||||
- Hooks: reintroduced feature toggle
|
||||
|
||||
## [2.6.1]
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cline",
|
||||
"version": "2.6.1",
|
||||
"version": "2.18.0",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"main": "dist/lib.mjs",
|
||||
"types": "dist/lib.d.ts",
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Box, Text, useInput } from "ink"
|
||||
import React from "react"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
|
||||
interface ApiKeyInputProps {
|
||||
providerName: string
|
||||
@@ -39,7 +39,7 @@ export const ApiKeyInput: React.FC<ApiKeyInputProps> = ({
|
||||
onCancel()
|
||||
return
|
||||
}
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
onSubmit(value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { useTaskController } from "../context/TaskContext"
|
||||
import { useLastCompletedAskMessage } from "../hooks/useStateSubscriber"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
import { jsonParseSafe } from "../utils/parser"
|
||||
|
||||
interface AskPromptProps {
|
||||
@@ -136,7 +136,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
} else if (promptType === "options") {
|
||||
// Number selection for options, or free text input
|
||||
const parts = jsonParseSafe(text, { options: [] as string[] })
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
// Submit free text on Enter
|
||||
if (textInput.trim()) {
|
||||
sendResponse("messageResponse", textInput.trim())
|
||||
@@ -145,7 +145,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
setTextInput((prev) => prev.slice(0, -1))
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
// Check if it's a number for option selection (only when no text typed yet)
|
||||
const num = parseInt(input, 10)
|
||||
const num = Number.parseInt(input, 10)
|
||||
if (textInput === "" && !Number.isNaN(num) && num >= 1 && num <= parts.options.length) {
|
||||
const selectedOption = parts.options[num - 1]
|
||||
sendResponse("messageResponse", selectedOption)
|
||||
@@ -156,7 +156,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
}
|
||||
} else if (promptType === "text") {
|
||||
// Text input mode
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
// Submit on Enter
|
||||
if (textInput.trim()) {
|
||||
sendResponse("messageResponse", textInput.trim())
|
||||
@@ -169,7 +169,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
}
|
||||
} else if (promptType === "plan_mode_text") {
|
||||
// Plan mode text input - allows text response or toggle to Act mode
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
// Submit on Enter
|
||||
if (textInput.trim()) {
|
||||
sendResponse("messageResponse", textInput.trim())
|
||||
@@ -185,7 +185,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
}
|
||||
} else if (promptType === "completion") {
|
||||
// Task completed - allow follow-up question or exit
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
if (textInput.trim()) {
|
||||
// Send follow-up question
|
||||
sendResponse("messageResponse", textInput.trim())
|
||||
@@ -401,43 +401,42 @@ function getCliMessagePrefixIcon(message: ClineMessage): string {
|
||||
default:
|
||||
return "❔"
|
||||
}
|
||||
} else {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️"
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️"
|
||||
case "generate_explanation":
|
||||
return "📝"
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️"
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️"
|
||||
case "generate_explanation":
|
||||
return "📝"
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useClineFeaturedModels } from "../hooks/useClineFeaturedModels"
|
||||
import { useOcaAuth } from "../hooks/useOcaAuth"
|
||||
import { useScrollableList } from "../hooks/useScrollableList"
|
||||
import { type DetectedSources, detectImportSources, type ImportSource } from "../utils/import-configs"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
import { applyBedrockConfig, applyProviderConfig } from "../utils/provider-config"
|
||||
import { useValidProviders } from "../utils/providers"
|
||||
import { ApiKeyInput } from "./ApiKeyInput"
|
||||
@@ -79,12 +79,12 @@ const Select: React.FC<{
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
useInput(
|
||||
(_, key) => {
|
||||
(input, key) => {
|
||||
if (key.upArrow) {
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
onSelect(items[selectedIndex].value)
|
||||
}
|
||||
},
|
||||
@@ -130,7 +130,7 @@ const TextInput: React.FC<{
|
||||
return
|
||||
}
|
||||
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
onSubmit(value)
|
||||
} else if (key.backspace || key.delete) {
|
||||
onChange(value.slice(0, -1))
|
||||
@@ -853,7 +853,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setMenuIndex((prev) => (prev > 0 ? prev - 1 : mainMenuItems.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setMenuIndex((prev) => (prev < mainMenuItems.length - 1 ? prev + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
handleMainMenuSelect(mainMenuItems[menuIndex].value)
|
||||
}
|
||||
} else if (step === "provider") {
|
||||
@@ -861,7 +861,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setProviderIndex((prev) => (prev > 0 ? prev - 1 : providerItems.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setProviderIndex((prev) => (prev < providerItems.length - 1 ? prev + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
if (providerItems[providerIndex]) {
|
||||
handleProviderSelect(providerItems[providerIndex].value)
|
||||
}
|
||||
@@ -877,7 +877,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setClineModelIndex((prev) => (prev > 0 ? prev - 1 : maxIndex))
|
||||
} else if (key.downArrow) {
|
||||
setClineModelIndex((prev) => (prev < maxIndex ? prev + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
if (isBrowseAllSelected(clineModelIndex, featuredModels)) {
|
||||
setStep("modelid")
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Box, Text, useInput } from "ink"
|
||||
import React, { useCallback, useState } from "react"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
import { getModelList } from "./ModelPicker"
|
||||
import { SearchableList } from "./SearchableList"
|
||||
|
||||
@@ -43,7 +44,7 @@ export const BedrockCustomModelFlow: React.FC<BedrockCustomModelFlowProps> = ({
|
||||
if (step === "arn_input") {
|
||||
if (key.escape) {
|
||||
onCancel()
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
handleArnSubmit()
|
||||
} else if (key.backspace || key.delete) {
|
||||
setCustomArn((prev) => prev.slice(0, -1))
|
||||
|
||||
@@ -12,7 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { ChatView } from "./ChatView"
|
||||
|
||||
// Helper to wait for async state updates
|
||||
const delay = (ms: number = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
// Type for our exit mock function
|
||||
type ExitMockFn = ReturnType<typeof vi.fn> & (() => void)
|
||||
@@ -126,12 +126,16 @@ vi.mock("../utils/file-search", () => ({
|
||||
searchWorkspaceFiles: vi.fn(async () => []),
|
||||
}))
|
||||
|
||||
vi.mock("../utils/slash-commands", () => ({
|
||||
extractSlashQuery: vi.fn(() => ({ inSlashMode: false, query: "", slashIndex: -1 })),
|
||||
filterCommands: vi.fn(() => []),
|
||||
insertSlashCommand: vi.fn((text: string) => text),
|
||||
sortCommandsWorkflowsFirst: vi.fn((cmds: unknown[]) => cmds),
|
||||
}))
|
||||
vi.mock("../utils/slash-commands", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../utils/slash-commands")>()
|
||||
return {
|
||||
...actual,
|
||||
extractSlashQuery: vi.fn(() => ({ inSlashMode: false, query: "", slashIndex: -1 })),
|
||||
filterCommands: vi.fn(() => []),
|
||||
insertSlashCommand: vi.fn((text: string) => text),
|
||||
sortCommandsWorkflowsFirst: vi.fn((cmds: unknown[]) => cmds),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("../utils/input", () => ({
|
||||
isMouseEscapeSequence: vi.fn(() => false),
|
||||
|
||||
+100
-71
@@ -108,7 +108,6 @@ import type { ClineAsk, ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { getApiMetrics, getLastApiReqTotalTokens } from "@shared/getApiMetrics"
|
||||
import { EmptyRequest, StringRequest } from "@shared/proto/cline/common"
|
||||
import type { SlashCommandInfo } from "@shared/proto/cline/slash"
|
||||
import { CLI_ONLY_COMMANDS } from "@shared/slashCommands"
|
||||
import { getProviderDefaultModelId, getProviderModelIdKey } from "@shared/storage"
|
||||
import type { Mode } from "@shared/storage/types"
|
||||
import { execSync } from "child_process"
|
||||
@@ -137,7 +136,14 @@ import {
|
||||
} from "../utils/file-search"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { jsonParseSafe, parseImagesFromInput } from "../utils/parser"
|
||||
import { extractSlashQuery, filterCommands, insertSlashCommand, sortCommandsWorkflowsFirst } from "../utils/slash-commands"
|
||||
import {
|
||||
createCliOnlySlashCommands,
|
||||
extractSlashQuery,
|
||||
filterCommands,
|
||||
getStandaloneSlashCommandToExecute,
|
||||
insertSlashCommand,
|
||||
sortCommandsWorkflowsFirst,
|
||||
} from "../utils/slash-commands"
|
||||
import { waitFor } from "../utils/timeout"
|
||||
import { isFileEditTool, parseToolFromMessage } from "../utils/tools"
|
||||
import { shutdownEvent } from "../vscode-shim"
|
||||
@@ -403,7 +409,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
const PASTE_UPDATE_DEBOUNCE_MS = 50 // Debounce visual updates to avoid flicker
|
||||
|
||||
// Slash command state
|
||||
const [availableCommands, setAvailableCommands] = useState<SlashCommandInfo[]>([])
|
||||
const [availableCommands, setAvailableCommands] = useState<SlashCommandInfo[]>(() => createCliOnlySlashCommands())
|
||||
const [selectedSlashIndex, setSelectedSlashIndex] = useState(0)
|
||||
const [slashMenuDismissed, setSlashMenuDismissed] = useState(false)
|
||||
const lastSlashIndexRef = useRef<number>(-1)
|
||||
@@ -614,16 +620,15 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
try {
|
||||
const response = await getAvailableSlashCommands(ctrl, EmptyRequest.create())
|
||||
const cliCommands = response.commands.filter((cmd) => cmd.cliCompatible !== false)
|
||||
// Add CLI-only commands (like /settings) that are handled locally
|
||||
const cliOnlyCommands: SlashCommandInfo[] = CLI_ONLY_COMMANDS.map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description || "",
|
||||
section: cmd.section || "default",
|
||||
cliCompatible: true,
|
||||
}))
|
||||
// Add CLI-only commands (like /settings) that are handled locally.
|
||||
// Seed these synchronously on first render so locally handled commands like
|
||||
// /q and /exit are immediately available, even before the async command
|
||||
// fetch completes. This avoids a race that can make the quit command tests
|
||||
// flaky on slower Windows CI runners.
|
||||
const cliOnlyCommands = createCliOnlySlashCommands()
|
||||
setAvailableCommands([...cliOnlyCommands, ...sortCommandsWorkflowsFirst(cliCommands)])
|
||||
} catch {
|
||||
// Fallback: commands will be empty, menu won't show
|
||||
// Keep CLI-only commands available even if backend command loading fails.
|
||||
}
|
||||
}
|
||||
loadCommands()
|
||||
@@ -843,6 +848,77 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
}, 150)
|
||||
}, [inkExit, onExit])
|
||||
|
||||
const handleCliOnlySlashCommand = useCallback(
|
||||
(commandName: string): boolean => {
|
||||
if (commandName === "help") {
|
||||
setActivePanel({ type: "help" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "settings") {
|
||||
setActivePanel({ type: "settings" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "models") {
|
||||
const apiConfig = StateManager.get().getApiConfiguration()
|
||||
const provider =
|
||||
mode === "act"
|
||||
? apiConfig.actModeApiProvider || apiConfig.planModeApiProvider
|
||||
: apiConfig.planModeApiProvider || apiConfig.actModeApiProvider
|
||||
const initialMode = !provider ? undefined : provider === "cline" ? "featured-models" : "model-picker"
|
||||
const initialModelKey = mode === "act" ? "actModelId" : "planModelId"
|
||||
setActivePanel({ type: "settings", initialMode, initialModelKey })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "history") {
|
||||
setActivePanel({ type: "history" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "skills") {
|
||||
setActivePanel({ type: "skills" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "clear") {
|
||||
void clearViewAndResetTask()
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (commandName === "exit" || commandName === "q") {
|
||||
handleExit()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
[clearViewAndResetTask, handleExit, mode, setCursorPos, setTextInput],
|
||||
)
|
||||
|
||||
// Get button config based on the last message state
|
||||
const buttonConfig = useMemo(() => {
|
||||
const lastMsg = messages[messages.length - 1] as ClineMessage | undefined
|
||||
@@ -1102,6 +1178,17 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
|
||||
const inSlashMenu = slashInfo.inSlashMode && filteredCommands.length > 0 && !slashMenuDismissed
|
||||
const inFileMenu = mentionInfo.inMentionMode && fileResults.length > 0 && !inSlashMenu
|
||||
const standaloneSlashCommand = getStandaloneSlashCommandToExecute({
|
||||
prompt,
|
||||
inSlashMode: slashInfo.inSlashMode,
|
||||
hasSlashMenu: inSlashMenu,
|
||||
hasPendingAsk: !!pendingAsk,
|
||||
isSpinnerActive,
|
||||
})
|
||||
|
||||
if (key.return && standaloneSlashCommand && handleCliOnlySlashCommand(standaloneSlashCommand)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 5. Slash command menu navigation (takes priority over file menu)
|
||||
if (inSlashMenu) {
|
||||
@@ -1116,64 +1203,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
if (key.tab || key.return) {
|
||||
const cmd = filteredCommands[selectedSlashIndex]
|
||||
if (cmd) {
|
||||
// Handle CLI-only commands locally
|
||||
if (cmd.name === "help") {
|
||||
setActivePanel({ type: "help" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "settings") {
|
||||
setActivePanel({ type: "settings" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "models") {
|
||||
const apiConfig = StateManager.get().getApiConfiguration()
|
||||
// Use current mode's provider to determine picker type
|
||||
const provider =
|
||||
mode === "act"
|
||||
? apiConfig.actModeApiProvider || apiConfig.planModeApiProvider
|
||||
: apiConfig.planModeApiProvider || apiConfig.actModeApiProvider
|
||||
const initialMode = !provider ? undefined : provider === "cline" ? "featured-models" : "model-picker"
|
||||
// Set model for current mode (plan or act)
|
||||
const initialModelKey = mode === "act" ? "actModelId" : "planModelId"
|
||||
setActivePanel({ type: "settings", initialMode, initialModelKey })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "history") {
|
||||
setActivePanel({ type: "history" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "skills") {
|
||||
setActivePanel({ type: "skills" })
|
||||
setTextInput("")
|
||||
setCursorPos(0)
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "clear") {
|
||||
clearViewAndResetTask()
|
||||
setSelectedSlashIndex(0)
|
||||
setSlashMenuDismissed(true)
|
||||
return
|
||||
}
|
||||
if (cmd.name === "exit" || cmd.name === "q") {
|
||||
handleExit()
|
||||
if (handleCliOnlySlashCommand(cmd.name)) {
|
||||
return
|
||||
}
|
||||
const newText = insertSlashCommand(textInput, slashInfo.slashIndex, cmd.name)
|
||||
@@ -1462,13 +1492,12 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
if (item.type === "header") {
|
||||
// Show static robot frame in header (first frame, looking straight ahead)
|
||||
return (
|
||||
<Box flexDirection="column" key="header">
|
||||
<Box flexDirection="column" key="header" marginBottom={1}>
|
||||
<StaticRobotFrame />
|
||||
<Text> </Text>
|
||||
<Text bold color="white">
|
||||
{centerText("What can I do for you?")}
|
||||
</Text>
|
||||
<Text> </Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ClineMessage } from "@shared/ExtensionMessage"
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import React, { useState } from "react"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
|
||||
export type RestoreType = "task" | "workspace" | "taskAndWorkspace"
|
||||
|
||||
@@ -101,7 +102,7 @@ export const CheckpointMenu: React.FC<CheckpointMenuProps> = ({ messages, onSele
|
||||
setSelectedCheckpoint((i) => Math.max(0, i - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedCheckpoint((i) => Math.min(checkpoints.length - 1, i + 1))
|
||||
} else if (key.return && checkpoints.length > 0) {
|
||||
} else if (isEnterKey(input, key) && checkpoints.length > 0) {
|
||||
setStage("restoreType")
|
||||
}
|
||||
} else if (stage === "restoreType") {
|
||||
@@ -109,7 +110,7 @@ export const CheckpointMenu: React.FC<CheckpointMenuProps> = ({ messages, onSele
|
||||
setSelectedRestoreType((i) => Math.max(0, i - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedRestoreType((i) => Math.min(RESTORE_TYPE_OPTIONS.length - 1, i + 1))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
const checkpoint = checkpoints[selectedCheckpoint]
|
||||
const restoreType = RESTORE_TYPE_OPTIONS[selectedRestoreType]
|
||||
if (checkpoint && restoreType) {
|
||||
@@ -120,7 +121,7 @@ export const CheckpointMenu: React.FC<CheckpointMenuProps> = ({ messages, onSele
|
||||
|
||||
// Quick number selection for checkpoints
|
||||
if (stage === "checkpoint") {
|
||||
const num = parseInt(input, 10)
|
||||
const num = Number.parseInt(input, 10)
|
||||
if (!Number.isNaN(num) && num >= 1 && num <= checkpoints.length) {
|
||||
setSelectedCheckpoint(num - 1)
|
||||
setStage("restoreType")
|
||||
|
||||
@@ -56,7 +56,13 @@ export interface ObjectEditorState {
|
||||
editValue: string
|
||||
}
|
||||
|
||||
export const EXCLUDED_KEYS = new Set(["taskHistory", "primaryRootIndex", "welcomeViewCompleted", "isNewUser"])
|
||||
export const EXCLUDED_KEYS = new Set([
|
||||
"taskHistory",
|
||||
"primaryRootIndex",
|
||||
"welcomeViewCompleted",
|
||||
"isNewUser",
|
||||
"cliKanbanMigrationAnnouncementShown",
|
||||
])
|
||||
|
||||
export const EDITABLE_TYPES: Set<ValueType> = new Set(["string", "number", "boolean", "object"])
|
||||
export const MAX_VISIBLE = 12
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Rotating feature tips shown during thinking/acting phases.
|
||||
* Appears after a brief delay and cycles through tips to educate users
|
||||
* about Cline features while they wait.
|
||||
*/
|
||||
|
||||
import { Box, Text } from "ink"
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react"
|
||||
|
||||
interface FeatureTipItem {
|
||||
text: string
|
||||
}
|
||||
|
||||
const FEATURE_TIPS: FeatureTipItem[] = [
|
||||
{
|
||||
text: 'Enable "Double-Check Completion" in settings to have Cline verify its work before finishing a task.',
|
||||
},
|
||||
{
|
||||
text: "Add a .clinerules file to your project root to give Cline project-specific instructions.",
|
||||
},
|
||||
{
|
||||
text: "Press Tab to switch between Plan and Act mode — plan an approach before Cline takes action.",
|
||||
},
|
||||
{
|
||||
text: "Use @ in the chat input to add files, folders, or URLs as context for your task.",
|
||||
},
|
||||
{
|
||||
text: "Set up MCP Servers to give Cline access to external tools and APIs.",
|
||||
},
|
||||
{
|
||||
text: "Cline creates checkpoints after changes — you can always restore to a previous state.",
|
||||
},
|
||||
{
|
||||
text: "Use /compact to condense long conversations and free up context window space.",
|
||||
},
|
||||
{
|
||||
text: "Enable auto-approve for read-only tools like file reads to speed up exploration.",
|
||||
},
|
||||
{
|
||||
text: "Use /settings to configure your API provider and model without leaving the terminal.",
|
||||
},
|
||||
{
|
||||
text: "You can pass images with --images flag or paste image file paths in the chat.",
|
||||
},
|
||||
{
|
||||
text: "Cline can browse websites — ask it to test your local dev server in the browser.",
|
||||
},
|
||||
{
|
||||
text: "Use /reportbug to quickly file a GitHub issue with diagnostic context included.",
|
||||
},
|
||||
{
|
||||
text: "Try 'npm i -g cline' to manage tasks on a Kankan board — orchestrate coding agents across worktrees.",
|
||||
},
|
||||
{
|
||||
text: "Use Shift+Tab to toggle auto-approve all — let Cline work uninterrupted on trusted tasks.",
|
||||
},
|
||||
{
|
||||
text: "Press Up/Down arrows in an empty input to browse your previous task prompts.",
|
||||
},
|
||||
{
|
||||
text: "Type / to see all available commands — /history, /compact, /settings, and more.",
|
||||
},
|
||||
{
|
||||
text: "Use /skills to browse and attach reusable skill files that guide Cline's behavior.",
|
||||
},
|
||||
{
|
||||
text: 'You can disable these tips in /settings → Features → "Feature tips".',
|
||||
},
|
||||
]
|
||||
|
||||
const SHOW_DELAY_MS = 2000
|
||||
const CYCLE_INTERVAL_MS = 8000
|
||||
|
||||
/**
|
||||
* Shows rotating feature tips below the thinking indicator.
|
||||
* Appears after a brief delay and cycles through tips while Cline is thinking/acting.
|
||||
*/
|
||||
export const FeatureTip: React.FC = React.memo(() => {
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const [tipIndex, setTipIndex] = useState(Math.floor(Math.random() * FEATURE_TIPS.length))
|
||||
const cycleTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const showTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
const currentTip = FEATURE_TIPS[tipIndex]
|
||||
|
||||
const advanceTip = useCallback(() => {
|
||||
setTipIndex((prev) => (prev + 1) % FEATURE_TIPS.length)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
showTimerRef.current = setTimeout(() => {
|
||||
setIsVisible(true)
|
||||
cycleTimerRef.current = setInterval(advanceTip, CYCLE_INTERVAL_MS)
|
||||
}, SHOW_DELAY_MS)
|
||||
|
||||
return () => {
|
||||
if (showTimerRef.current) {
|
||||
clearTimeout(showTimerRef.current)
|
||||
}
|
||||
if (cycleTimerRef.current) {
|
||||
clearInterval(cycleTimerRef.current)
|
||||
}
|
||||
}
|
||||
}, [advanceTip])
|
||||
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Box paddingLeft={1}>
|
||||
<Text color="gray">
|
||||
💡 <Text bold>Tip:</Text> {currentTip.text}
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
})
|
||||
@@ -13,7 +13,7 @@ import { showTaskWithId } from "@/core/controller/task/showTaskWithId"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { useTerminalSize } from "../hooks/useTerminalSize"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
import { Panel } from "./Panel"
|
||||
|
||||
interface TaskHistoryItem {
|
||||
@@ -142,7 +142,7 @@ export const HistoryPanelContent: React.FC<HistoryPanelContentProps> = ({ onClos
|
||||
return
|
||||
}
|
||||
|
||||
if (key.return && items[selectedIndex]) {
|
||||
if (isEnterKey(input, key) && items[selectedIndex]) {
|
||||
handleSelect(items[selectedIndex])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { showTaskWithId } from "@/core/controller/task/showTaskWithId"
|
||||
import { StringRequest } from "@/shared/proto/cline/common"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { useTerminalSize } from "../hooks/useTerminalSize"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
|
||||
interface TaskHistoryItem {
|
||||
id: string
|
||||
@@ -40,7 +41,7 @@ interface HistoryViewProps {
|
||||
/**
|
||||
* Format separator
|
||||
*/
|
||||
function formatSeparator(char: string = "─", width: number = 80): string {
|
||||
function formatSeparator(char = "─", width = 80): string {
|
||||
return char.repeat(Math.max(width, 10))
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ export const HistoryView: React.FC<HistoryViewProps> = ({
|
||||
setSelectedIndex((prev) => Math.max(0, prev - 1))
|
||||
} else if (key.downArrow || input === "j") {
|
||||
setSelectedIndex((prev) => Math.min(pageItems.length - 1, prev + 1))
|
||||
} else if (key.return && pageItems[selectedIndex]) {
|
||||
} else if (isEnterKey(input, key) && pageItems[selectedIndex]) {
|
||||
onSelect(pageItems[selectedIndex])
|
||||
} else if (key.leftArrow && hasPrevPage) {
|
||||
handlePageChange(currentPage - 1)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
importFromCodex,
|
||||
importFromOpenCode,
|
||||
} from "../utils/import-configs"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
import { applyProviderConfig } from "../utils/provider-config"
|
||||
|
||||
type ImportStep = "select" | "confirm" | "saving" | "error"
|
||||
@@ -95,13 +96,13 @@ export const ImportView: React.FC<ImportViewProps> = ({ source, onComplete, onCa
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : keys.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedIndex((prev) => (prev < keys.length - 1 ? prev + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
setStep("confirm")
|
||||
}
|
||||
} else if (step === "confirm") {
|
||||
if (key.upArrow || key.downArrow) {
|
||||
setConfirmIndex((prev) => (prev === 0 ? 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(input, key)) {
|
||||
if (confirmIndex === 0) {
|
||||
handleConfirm()
|
||||
} else {
|
||||
@@ -109,7 +110,7 @@ export const ImportView: React.FC<ImportViewProps> = ({ source, onComplete, onCa
|
||||
}
|
||||
}
|
||||
} else if (step === "error") {
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { render } from "ink-testing-library"
|
||||
import { createElement } from "react"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import { KanbanMigrationView } from "./KanbanMigrationView"
|
||||
|
||||
describe("KanbanMigrationView", () => {
|
||||
it("renders the migration options", () => {
|
||||
const onSelect = vi.fn()
|
||||
const { lastFrame } = render(createElement(KanbanMigrationView, { isRawModeSupported: true, onSelect }))
|
||||
|
||||
expect(lastFrame()).toContain("Introducing Cline Kanban!")
|
||||
expect(lastFrame()).toContain("Open the new experience")
|
||||
expect(lastFrame()).toContain("Launch Cline Kanban and start there by default.")
|
||||
expect(lastFrame()).toContain("cline --tui")
|
||||
expect(lastFrame()).toContain("You can always run cline --tui for the terminal experience.")
|
||||
expect(lastFrame()).toContain("Exit")
|
||||
})
|
||||
|
||||
it("selects the highlighted option with Enter", () => {
|
||||
const onSelect = vi.fn()
|
||||
const { stdin } = render(createElement(KanbanMigrationView, { isRawModeSupported: true, onSelect }))
|
||||
|
||||
stdin.write("\r")
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith("kanban")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Box, Text, useApp, useInput } from "ink"
|
||||
import React, { useMemo, useState } from "react"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { StdinProvider, useStdinContext } from "../context/StdinContext"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
import { type KanbanMigrationAction } from "../utils/kanban"
|
||||
import { StaticRobotFrame } from "./AsciiMotionCli"
|
||||
import { ErrorBoundary } from "./ErrorBoundary"
|
||||
|
||||
interface KanbanMigrationViewProps {
|
||||
isRawModeSupported: boolean
|
||||
onSelect: (action: KanbanMigrationAction) => void
|
||||
}
|
||||
|
||||
interface MigrationMenuItem {
|
||||
label: string
|
||||
description: string
|
||||
value: KanbanMigrationAction
|
||||
}
|
||||
|
||||
const InternalKanbanMigrationView: React.FC<Pick<KanbanMigrationViewProps, "onSelect">> = ({ onSelect }) => {
|
||||
const { exit } = useApp()
|
||||
const { isRawModeSupported } = useStdinContext()
|
||||
const items = useMemo<MigrationMenuItem[]>(
|
||||
() => [
|
||||
{
|
||||
label: "Open the new experience",
|
||||
description: "Launch Cline Kanban and start there by default.",
|
||||
value: "kanban",
|
||||
},
|
||||
{
|
||||
label: "Exit",
|
||||
description: "You can always run cline --tui for the terminal experience.",
|
||||
value: "exit",
|
||||
},
|
||||
],
|
||||
[],
|
||||
)
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
if (key.escape) {
|
||||
onSelect("exit")
|
||||
exit()
|
||||
} else if (key.upArrow) {
|
||||
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0))
|
||||
} else if (isEnterKey(input, key)) {
|
||||
onSelect(items[selectedIndex].value)
|
||||
exit()
|
||||
}
|
||||
},
|
||||
{ isActive: isRawModeSupported },
|
||||
)
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
<StaticRobotFrame />
|
||||
<Text> </Text>
|
||||
<Text bold color="white">
|
||||
Introducing Cline Kanban!
|
||||
</Text>
|
||||
<Text color="gray">A board for orchestrating coding agents across worktrees, right from your browser.</Text>
|
||||
<Text> </Text>
|
||||
{items.map((item, index) => {
|
||||
const isSelected = index === selectedIndex
|
||||
return (
|
||||
<Box flexDirection="column" key={item.value} marginBottom={1}>
|
||||
<Text color={isSelected ? COLORS.primaryBlue : undefined}>
|
||||
{isSelected ? "❯ " : " "}
|
||||
{item.label}
|
||||
</Text>
|
||||
<Text color="gray"> {item.description}</Text>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
<Text> </Text>
|
||||
<Text color="gray">Use arrow keys to navigate, Enter to select, Esc or Ctrl+C to exit</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export const KanbanMigrationView: React.FC<KanbanMigrationViewProps> = ({ isRawModeSupported, onSelect }) => {
|
||||
const { exit } = useApp()
|
||||
|
||||
return (
|
||||
<ErrorBoundary exit={exit}>
|
||||
<StdinProvider isRawModeSupported={isRawModeSupported}>
|
||||
<InternalKanbanMigrationView onSelect={onSelect} />
|
||||
</StdinProvider>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
@@ -62,6 +62,8 @@ import {
|
||||
sapAiCoreModels,
|
||||
vertexDefaultModelId,
|
||||
vertexModels,
|
||||
wandbDefaultModelId,
|
||||
wandbModels,
|
||||
xaiDefaultModelId,
|
||||
xaiModels,
|
||||
} from "@/shared/api"
|
||||
@@ -101,6 +103,7 @@ export const providerModels: Record<string, { models: Record<string, unknown>; d
|
||||
sambanova: { models: sambanovaModels, defaultId: sambanovaDefaultModelId },
|
||||
sapaicore: { models: sapAiCoreModels, defaultId: sapAiCoreDefaultModelId },
|
||||
vertex: { models: vertexModels, defaultId: vertexDefaultModelId },
|
||||
wandb: { models: wandbModels, defaultId: wandbDefaultModelId },
|
||||
xai: { models: xaiModels, defaultId: xaiDefaultModelId },
|
||||
zai: { models: internationalZAiModels, defaultId: internationalZAiDefaultModelId },
|
||||
}
|
||||
|
||||
@@ -1,112 +1,38 @@
|
||||
import { render } from "ink-testing-library"
|
||||
// biome-ignore lint/correctness/noUnusedImports: React must be in scope for JSX in this test file.
|
||||
import React from "react"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { CLI_ONLY_COMMANDS } from "@shared/slashCommands"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { filterCommands, getStandaloneSlashCommandName, getStandaloneSlashCommandToExecute } from "../utils/slash-commands"
|
||||
|
||||
// Mock ink's useApp
|
||||
const mockExit = vi.fn()
|
||||
vi.mock("ink", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("ink")>()
|
||||
return {
|
||||
...actual,
|
||||
useApp: () => ({ exit: mockExit }),
|
||||
}
|
||||
})
|
||||
|
||||
// Mock child_process
|
||||
vi.mock("child_process", () => ({
|
||||
execSync: vi.fn().mockReturnValue(""),
|
||||
exec: vi.fn(),
|
||||
const cliOnlySlashCommands = CLI_ONLY_COMMANDS.map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description || "",
|
||||
section: cmd.section || "default",
|
||||
cliCompatible: true,
|
||||
}))
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("@/core/controller/slash/getAvailableSlashCommands", () => ({
|
||||
getAvailableSlashCommands: vi.fn().mockResolvedValue({ commands: [] }),
|
||||
}))
|
||||
|
||||
vi.mock("@/core/storage/StateManager", () => ({
|
||||
StateManager: {
|
||||
get: () => ({
|
||||
getGlobalSettingsKey: vi.fn().mockReturnValue("act"),
|
||||
getGlobalStateKey: vi.fn().mockReturnValue([]),
|
||||
getApiConfiguration: vi.fn().mockReturnValue({}),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@/services/telemetry", () => ({
|
||||
telemetryService: {
|
||||
captureHostEvent: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@shared/services/Session", () => ({
|
||||
Session: {
|
||||
get: () => ({
|
||||
getStats: vi.fn().mockReturnValue({}),
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("../context/TaskContext", () => ({
|
||||
useTaskContext: () => ({
|
||||
controller: {},
|
||||
clearState: vi.fn(),
|
||||
}),
|
||||
useTaskState: () => ({
|
||||
clineMessages: [],
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock("../hooks/useStateSubscriber", () => ({
|
||||
useIsSpinnerActive: () => ({ isActive: false, startTime: 0 }),
|
||||
}))
|
||||
|
||||
import { ChatView } from "./ChatView"
|
||||
|
||||
// Helper to wait for async state updates
|
||||
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
describe("Quit Command (/q and /exit)", () => {
|
||||
const mockOnExit = vi.fn()
|
||||
it("prioritizes /q as the selected slash command for an exact q query", () => {
|
||||
const result = filterCommands(cliOnlySlashCommands, "q")
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
expect(result[0]?.name).toBe("q")
|
||||
})
|
||||
|
||||
it("should exit the application when /q is selected from slash menu", async () => {
|
||||
const { stdin } = render(<ChatView onExit={mockOnExit} />)
|
||||
await delay()
|
||||
|
||||
// Type /q
|
||||
stdin.write("/q")
|
||||
await delay()
|
||||
|
||||
// Press Enter
|
||||
stdin.write("\r")
|
||||
|
||||
// handleExit has a 150ms timeout
|
||||
await delay(200)
|
||||
|
||||
expect(mockExit).toHaveBeenCalled()
|
||||
expect(mockOnExit).toHaveBeenCalled()
|
||||
it("detects /q as a standalone slash command", () => {
|
||||
expect(getStandaloneSlashCommandName("/q")).toBe("q")
|
||||
})
|
||||
|
||||
it("should exit the application when /exit is selected from slash menu", async () => {
|
||||
const { stdin } = render(<ChatView onExit={mockOnExit} />)
|
||||
await delay()
|
||||
it("detects /exit as a standalone slash command", () => {
|
||||
expect(getStandaloneSlashCommandName("/exit")).toBe("exit")
|
||||
})
|
||||
|
||||
// Type /exit
|
||||
stdin.write("/exit")
|
||||
await delay()
|
||||
|
||||
// Press Enter
|
||||
stdin.write("\r")
|
||||
|
||||
// handleExit has a 150ms timeout
|
||||
await delay(200)
|
||||
|
||||
expect(mockExit).toHaveBeenCalled()
|
||||
expect(mockOnExit).toHaveBeenCalled()
|
||||
it("resolves /q to direct execution when no slash menu is active", () => {
|
||||
expect(
|
||||
getStandaloneSlashCommandToExecute({
|
||||
prompt: "/q",
|
||||
inSlashMode: true,
|
||||
hasSlashMenu: false,
|
||||
hasPendingAsk: false,
|
||||
isSpinnerActive: false,
|
||||
}),
|
||||
).toBe("q")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Box, Text, useInput } from "ink"
|
||||
import React, { useState } from "react"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { isEnterKey } from "../utils/input"
|
||||
|
||||
export interface SelectListItem {
|
||||
id: string
|
||||
@@ -31,7 +32,7 @@ export function SelectList<T extends SelectListItem>({ items, onSelect, isActive
|
||||
setSelectedIndex((i) => (i > 0 ? i - 1 : items.length - 1))
|
||||
} else if (key.downArrow) {
|
||||
setSelectedIndex((i) => (i < items.length - 1 ? i + 1 : 0))
|
||||
} else if (key.return) {
|
||||
} else if (isEnterKey(_input, key)) {
|
||||
const item = items[selectedIndex]
|
||||
if (item) {
|
||||
onSelect(item)
|
||||
|
||||
@@ -129,6 +129,12 @@ const FEATURE_SETTINGS = {
|
||||
label: "Double-check completion",
|
||||
description: "Reject first completion attempt and require re-verification",
|
||||
},
|
||||
showFeatureTips: {
|
||||
stateKey: "showFeatureTips",
|
||||
default: true,
|
||||
label: "Feature tips",
|
||||
description: "Show tips during thinking phases",
|
||||
},
|
||||
} as const
|
||||
|
||||
type FeatureKey = keyof typeof FEATURE_SETTINGS
|
||||
|
||||
@@ -38,6 +38,46 @@ import { SkillsPanelContent } from "./SkillsPanelContent"
|
||||
// Helper to wait for async state updates
|
||||
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
type WaitForConditionOptions = {
|
||||
timeoutMs?: number
|
||||
intervalMs?: number
|
||||
errorMessage: string
|
||||
}
|
||||
|
||||
const waitForCondition = async (
|
||||
condition: () => boolean,
|
||||
{ timeoutMs = 1000, intervalMs = 25, errorMessage }: WaitForConditionOptions,
|
||||
) => {
|
||||
const start = Date.now()
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
if (condition()) {
|
||||
return
|
||||
}
|
||||
await delay(intervalMs)
|
||||
}
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
const waitForFrameToInclude = async (lastFrame: () => string | undefined, text: string) =>
|
||||
waitForCondition(() => (lastFrame() || "").includes(text), {
|
||||
errorMessage: `Expected frame to include: ${text}`,
|
||||
})
|
||||
|
||||
const waitForFrameToExclude = async (lastFrame: () => string | undefined, text: string) =>
|
||||
waitForCondition(() => !(lastFrame() || "").includes(text), {
|
||||
errorMessage: `Expected frame to exclude: ${text}`,
|
||||
})
|
||||
|
||||
const waitForMockToBeCalled = async (mockFn: { mock: { calls: unknown[] } }) =>
|
||||
waitForCondition(() => mockFn.mock.calls.length > 0, {
|
||||
errorMessage: "Expected mock to be called",
|
||||
})
|
||||
|
||||
const waitForSkillsPanelReady = async (lastFrame: () => string | undefined, expectedText: string) => {
|
||||
await waitForFrameToExclude(lastFrame, "Loading skills...")
|
||||
await waitForFrameToInclude(lastFrame, expectedText)
|
||||
}
|
||||
|
||||
describe("SkillsPanelContent", () => {
|
||||
const mockController = {} as any
|
||||
const mockOnClose = vi.fn()
|
||||
@@ -64,11 +104,11 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "No skills installed.")
|
||||
|
||||
stdin.write("\x1B") // Escape
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockOnClose)
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalled()
|
||||
})
|
||||
@@ -79,11 +119,11 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "test-skill")
|
||||
|
||||
stdin.write("\r") // Enter
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockOnUseSkill)
|
||||
|
||||
expect(mockOnUseSkill).toHaveBeenCalledWith("/test/path/SKILL.md")
|
||||
})
|
||||
@@ -94,11 +134,11 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "test-skill")
|
||||
|
||||
stdin.write(" ") // Space
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockToggleSkill)
|
||||
|
||||
expect(mockToggleSkill).toHaveBeenCalledWith(
|
||||
mockController,
|
||||
@@ -116,15 +156,17 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "skill")
|
||||
|
||||
// Navigate down to marketplace (past the one skill)
|
||||
stdin.write("\x1B[B") // Down arrow
|
||||
await delay()
|
||||
// Use vim-style navigation here because it's more deterministic in the
|
||||
// full suite than raw arrow escape sequences on Windows.
|
||||
stdin.write("j")
|
||||
await waitForFrameToInclude(lastFrame, "❯ Browse more skills at https://skills.sh/")
|
||||
|
||||
stdin.write("\r") // Enter
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockExec)
|
||||
|
||||
// Should have called exec with open command
|
||||
expect(mockExec).toHaveBeenCalled()
|
||||
@@ -141,16 +183,16 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "skill-1")
|
||||
|
||||
// Navigate down
|
||||
stdin.write("\x1B[B") // Down arrow
|
||||
await delay()
|
||||
await waitForFrameToInclude(lastFrame, "❯ ● skill-2")
|
||||
|
||||
// Press Enter - should use second skill
|
||||
stdin.write("\r")
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockOnUseSkill)
|
||||
|
||||
expect(mockOnUseSkill).toHaveBeenCalledWith("/path2")
|
||||
})
|
||||
@@ -164,16 +206,16 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "skill-1")
|
||||
|
||||
// Navigate down with j
|
||||
stdin.write("j")
|
||||
await delay()
|
||||
await waitForFrameToInclude(lastFrame, "❯ ● skill-2")
|
||||
|
||||
// Press Enter - should use second skill
|
||||
stdin.write("\r")
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockOnUseSkill)
|
||||
|
||||
expect(mockOnUseSkill).toHaveBeenCalledWith("/path2")
|
||||
})
|
||||
@@ -186,10 +228,11 @@ describe("SkillsPanelContent", () => {
|
||||
mockToggleSkill.mockRejectedValueOnce(new Error("toggle failed"))
|
||||
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
await waitForSkillsPanelReady(lastFrame, "test-skill")
|
||||
|
||||
stdin.write(" ") // Space to toggle
|
||||
await delay(100)
|
||||
await waitForMockToBeCalled(mockToggleSkill)
|
||||
await waitForFrameToInclude(lastFrame, "● test-skill")
|
||||
|
||||
// toggleSkill was called with enabled: false (toggled from true)
|
||||
expect(mockToggleSkill).toHaveBeenCalledWith(mockController, expect.objectContaining({ enabled: false }))
|
||||
@@ -204,15 +247,15 @@ describe("SkillsPanelContent", () => {
|
||||
localSkills: [],
|
||||
})
|
||||
|
||||
const { stdin } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { stdin, lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForSkillsPanelReady(lastFrame, "only-skill")
|
||||
|
||||
// Navigate up from first item (should wrap to last - marketplace)
|
||||
stdin.write("\x1B[A") // Up arrow
|
||||
await delay()
|
||||
await waitForFrameToInclude(lastFrame, "❯ Browse more skills at https://skills.sh/")
|
||||
|
||||
stdin.write("\r") // Enter
|
||||
await delay()
|
||||
await waitForMockToBeCalled(mockExec)
|
||||
|
||||
// Should have opened marketplace (wrapped to last item)
|
||||
expect(mockExec).toHaveBeenCalled()
|
||||
@@ -221,8 +264,9 @@ describe("SkillsPanelContent", () => {
|
||||
|
||||
describe("skill loading", () => {
|
||||
it("should call refreshSkills on mount", async () => {
|
||||
render(<SkillsPanelContent {...defaultProps} />)
|
||||
await delay()
|
||||
const { lastFrame } = render(<SkillsPanelContent {...defaultProps} />)
|
||||
await waitForMockToBeCalled(mockRefreshSkills)
|
||||
await waitForFrameToExclude(lastFrame, "Loading skills...")
|
||||
|
||||
expect(mockRefreshSkills).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
import { exec } from "node:child_process"
|
||||
import os from "node:os"
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import type { Controller } from "@/core/controller"
|
||||
import { refreshSkills } from "@/core/controller/file/refreshSkills"
|
||||
import { toggleSkill } from "@/core/controller/file/toggleSkill"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { isEnterKey, isMouseEscapeSequence } from "../utils/input"
|
||||
import { Panel } from "./Panel"
|
||||
|
||||
const SKILLS_MARKETPLACE_URL = "https://skills.sh/"
|
||||
@@ -38,6 +38,14 @@ export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controll
|
||||
const [localSkills, setLocalSkills] = useState<SkillInfo[]>([])
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const inputStateRef = useRef({
|
||||
isLoading: true,
|
||||
selectedIndex: 0,
|
||||
skillEntries: [] as Array<{ skill: SkillInfo; isGlobal: boolean }>,
|
||||
})
|
||||
const handleToggleRef = useRef<() => Promise<void>>(async () => {})
|
||||
const handleUseRef = useRef<() => void>(() => {})
|
||||
const openMarketplaceRef = useRef<() => void>(() => {})
|
||||
|
||||
// Load skills on mount
|
||||
useEffect(() => {
|
||||
@@ -58,8 +66,12 @@ export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controll
|
||||
// Build flat list of skills with source info (global first, then local, alphabetical within each)
|
||||
const skillEntries = useMemo(() => {
|
||||
const entries: { skill: SkillInfo; isGlobal: boolean }[] = []
|
||||
globalSkills.forEach((skill) => entries.push({ skill, isGlobal: true }))
|
||||
localSkills.forEach((skill) => entries.push({ skill, isGlobal: false }))
|
||||
globalSkills.forEach((skill) => {
|
||||
entries.push({ skill, isGlobal: true })
|
||||
})
|
||||
localSkills.forEach((skill) => {
|
||||
entries.push({ skill, isGlobal: false })
|
||||
})
|
||||
return entries.sort((a, b) => {
|
||||
if (a.isGlobal !== b.isGlobal) return a.isGlobal ? -1 : 1
|
||||
return a.skill.name.localeCompare(b.skill.name)
|
||||
@@ -117,6 +129,14 @@ export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controll
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
handleToggleRef.current = handleToggle
|
||||
handleUseRef.current = handleUse
|
||||
openMarketplaceRef.current = openMarketplace
|
||||
inputStateRef.current = {
|
||||
isLoading,
|
||||
selectedIndex,
|
||||
skillEntries,
|
||||
}
|
||||
|
||||
// Total items = skills + 1 for marketplace link
|
||||
const totalItems = skillEntries.length + 1
|
||||
@@ -132,6 +152,14 @@ export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controll
|
||||
return
|
||||
}
|
||||
|
||||
const { isLoading, selectedIndex, skillEntries } = inputStateRef.current
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalItems = skillEntries.length + 1
|
||||
const isMarketplaceSelected = selectedIndex === skillEntries.length
|
||||
|
||||
// Navigation
|
||||
if (key.upArrow || input === "k") {
|
||||
setSelectedIndex((i) => (i > 0 ? i - 1 : totalItems - 1))
|
||||
@@ -143,16 +171,16 @@ export const SkillsPanelContent: React.FC<SkillsPanelContentProps> = ({ controll
|
||||
}
|
||||
|
||||
// Actions
|
||||
if (key.return) {
|
||||
if (isEnterKey(input, key)) {
|
||||
if (isMarketplaceSelected) {
|
||||
openMarketplace()
|
||||
openMarketplaceRef.current()
|
||||
} else {
|
||||
handleUse()
|
||||
handleUseRef.current()
|
||||
}
|
||||
return
|
||||
}
|
||||
if (input === " " && !isMarketplaceSelected) {
|
||||
handleToggle()
|
||||
void handleToggleRef.current()
|
||||
return
|
||||
}
|
||||
},
|
||||
@@ -248,7 +276,7 @@ const SkillRow: React.FC<{ skill: SkillInfo; isSelected: boolean }> = ({ skill,
|
||||
{skill.description && (
|
||||
<Box marginLeft={4}>
|
||||
<Text color="gray">
|
||||
{skill.description.length > 60 ? skill.description.slice(0, 57) + "..." : skill.description}
|
||||
{skill.description.length > 60 ? `${skill.description.slice(0, 57)}...` : skill.description}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import React, { useEffect, useMemo, useState } from "react"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import { COLORS } from "../constants/colors"
|
||||
import { FeatureTip } from "./FeatureTip"
|
||||
|
||||
interface ThinkingIndicatorProps {
|
||||
mode?: "act" | "plan"
|
||||
@@ -52,6 +54,7 @@ const ShimmerText: React.FC<{ text: string; color: string; shimmerPos: number }>
|
||||
}
|
||||
|
||||
export const ThinkingIndicator: React.FC<ThinkingIndicatorProps> = ({ mode = "act", startTime, onCancel }) => {
|
||||
const showFeatureTips = StateManager.get().getGlobalSettingsKey("showFeatureTips") ?? true
|
||||
const message = mode === "plan" ? "Planning" : "Acting"
|
||||
const color = mode === "plan" ? "yellow" : COLORS.primaryBlue
|
||||
|
||||
@@ -118,9 +121,12 @@ export const ThinkingIndicator: React.FC<ThinkingIndicatorProps> = ({ mode = "ac
|
||||
}, [startTime, elapsedMs])
|
||||
|
||||
return (
|
||||
<Box paddingLeft={1}>
|
||||
<ShimmerText color={color} shimmerPos={shimmerPos} text={fullText} />
|
||||
{elapsedStr && <Text color="gray"> ({elapsedStr} · esc to interrupt)</Text>}
|
||||
<Box flexDirection="column">
|
||||
<Box paddingLeft={1}>
|
||||
<ShimmerText color={color} shimmerPos={shimmerPos} text={fullText} />
|
||||
{elapsedStr && <Text color="gray"> ({elapsedStr} · esc to interrupt)</Text>}
|
||||
</Box>
|
||||
{showFeatureTips && <FeatureTip />}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,333 +0,0 @@
|
||||
/**
|
||||
* Welcome view component
|
||||
* Shows an interactive prompt when user starts cline without a command
|
||||
* Supports file mentions with @
|
||||
*/
|
||||
|
||||
import { Box, Text, useInput } from "ink"
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import type { ApiProvider } from "@/shared/api"
|
||||
import { getProviderDefaultModelId, getProviderModelIdKey, Mode, SettingsKey } from "@/shared/storage"
|
||||
import { useStdinContext } from "../context/StdinContext"
|
||||
import {
|
||||
checkAndWarnRipgrepMissing,
|
||||
extractMentionQuery,
|
||||
type FileSearchResult,
|
||||
getRipgrepInstallInstructions,
|
||||
insertMention,
|
||||
searchWorkspaceFiles,
|
||||
} from "../utils/file-search"
|
||||
import { isMouseEscapeSequence } from "../utils/input"
|
||||
import { parseImagesFromInput } from "../utils/parser"
|
||||
import { AccountInfoView } from "./AccountInfoView"
|
||||
import { FileMentionMenu } from "./FileMentionMenu"
|
||||
|
||||
interface WelcomeViewProps {
|
||||
onSubmit: (prompt: string, imagePaths: string[]) => void
|
||||
onExit?: () => void
|
||||
controller?: any
|
||||
}
|
||||
|
||||
// ASCII art Cline logo
|
||||
const CLINE_LOGO = [
|
||||
" ::::::: ",
|
||||
" ::::::::: ",
|
||||
" ::::::::::::::::: ",
|
||||
" ::::::::::::::::::::::: ",
|
||||
" ::::::::::::::::::::::::: ",
|
||||
" ::::::::::::::::::::::::::: ",
|
||||
" ::::::: ::::::: ::::::: ",
|
||||
" ::::::: ::::: ::::::: ",
|
||||
":::::::: ::::: ::::::::",
|
||||
":::::::: ::::: ::::::::",
|
||||
" ::::::: ::::: ::::::: ",
|
||||
" ::::::: ::::::: ::::::: ",
|
||||
" ::::::::::::::::::::::::::: ",
|
||||
" ::::::::::::::::::::::::: ",
|
||||
" ::::::::::::::::::::::: ",
|
||||
" :::::::::::::::: ",
|
||||
]
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 150
|
||||
const RIPGREP_WARNING_DURATION_MS = 5000
|
||||
const MAX_SEARCH_RESULTS = 15
|
||||
|
||||
export const WelcomeView: React.FC<WelcomeViewProps> = ({ onSubmit, onExit, controller }) => {
|
||||
const { isRawModeSupported } = useStdinContext()
|
||||
const [textInput, setTextInput] = useState("")
|
||||
const [fileResults, setFileResults] = useState<FileSearchResult[]>([])
|
||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
const [showRipgrepWarning, setShowRipgrepWarning] = useState(false)
|
||||
const [escPressedOnce, setEscPressedOnce] = useState(false)
|
||||
const [mode, setMode] = useState<Mode>(() => {
|
||||
const stateManager = StateManager.get()
|
||||
return stateManager.getGlobalSettingsKey("mode") || "act"
|
||||
})
|
||||
|
||||
const provider = useMemo(() => {
|
||||
const stateManager = StateManager.get()
|
||||
const mode = stateManager.getGlobalSettingsKey("mode") as string
|
||||
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
|
||||
const currentProvider = stateManager.getGlobalSettingsKey(providerKey) as string
|
||||
return currentProvider || "cline"
|
||||
}, [controller])
|
||||
|
||||
// Get model ID based on current mode and provider
|
||||
// Different providers use different state keys (e.g., cline uses actModeOpenRouterModelId)
|
||||
const modelId = useMemo(() => {
|
||||
const stateManager = StateManager.get()
|
||||
const modelKey = getProviderModelIdKey(provider as ApiProvider, mode)
|
||||
return (
|
||||
(stateManager.getGlobalSettingsKey(modelKey as SettingsKey) as string) ||
|
||||
getProviderDefaultModelId(provider as ApiProvider)
|
||||
)
|
||||
}, [mode, provider])
|
||||
|
||||
const toggleMode = useCallback(() => {
|
||||
const newMode: Mode = mode === "act" ? "plan" : "act"
|
||||
setMode(newMode)
|
||||
const stateManager = StateManager.get()
|
||||
stateManager.setGlobalState("mode", newMode)
|
||||
}, [mode])
|
||||
|
||||
const refs = useRef({
|
||||
searchTimeout: null as NodeJS.Timeout | null,
|
||||
lastQuery: "",
|
||||
hasCheckedRipgrep: false,
|
||||
})
|
||||
|
||||
const { prompt, imagePaths } = parseImagesFromInput(textInput)
|
||||
|
||||
const mentionInfo = useMemo(() => extractMentionQuery(textInput), [textInput])
|
||||
|
||||
const workspacePath = useMemo(() => {
|
||||
try {
|
||||
const root = controller?.getWorkspaceManagerSync?.()?.getPrimaryRoot?.()
|
||||
if (root?.path) {
|
||||
return root.path
|
||||
}
|
||||
} catch {
|
||||
// Fallback to cwd
|
||||
}
|
||||
return process.cwd()
|
||||
}, [controller])
|
||||
|
||||
// Search for files when in mention mode
|
||||
useEffect(() => {
|
||||
const { current: r } = refs
|
||||
|
||||
if (!mentionInfo.inMentionMode) {
|
||||
setFileResults([])
|
||||
setSelectedIndex(0)
|
||||
if (r.searchTimeout) {
|
||||
clearTimeout(r.searchTimeout)
|
||||
r.searchTimeout = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check for ripgrep on first mention trigger
|
||||
if (!r.hasCheckedRipgrep) {
|
||||
r.hasCheckedRipgrep = true
|
||||
if (checkAndWarnRipgrepMissing()) {
|
||||
setShowRipgrepWarning(true)
|
||||
setTimeout(() => setShowRipgrepWarning(false), RIPGREP_WARNING_DURATION_MS)
|
||||
}
|
||||
}
|
||||
|
||||
const { query } = mentionInfo
|
||||
if (query === r.lastQuery) {
|
||||
return
|
||||
}
|
||||
r.lastQuery = query
|
||||
|
||||
if (r.searchTimeout) {
|
||||
clearTimeout(r.searchTimeout)
|
||||
}
|
||||
setIsSearching(true)
|
||||
|
||||
r.searchTimeout = setTimeout(async () => {
|
||||
try {
|
||||
const results = await searchWorkspaceFiles(query, workspacePath, MAX_SEARCH_RESULTS)
|
||||
setFileResults(results)
|
||||
setSelectedIndex(0)
|
||||
} catch {
|
||||
setFileResults([])
|
||||
} finally {
|
||||
setIsSearching(false)
|
||||
}
|
||||
}, SEARCH_DEBOUNCE_MS)
|
||||
|
||||
return () => {
|
||||
if (r.searchTimeout) {
|
||||
clearTimeout(r.searchTimeout)
|
||||
}
|
||||
}
|
||||
}, [mentionInfo.inMentionMode, mentionInfo.query, workspacePath])
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
// Filter out mouse escape sequences
|
||||
if (isMouseEscapeSequence(input)) {
|
||||
return
|
||||
}
|
||||
|
||||
const inMenu = mentionInfo.inMentionMode && fileResults.length > 0
|
||||
|
||||
// Menu navigation
|
||||
if (inMenu) {
|
||||
if (key.upArrow) {
|
||||
setSelectedIndex((i) => (i > 0 ? i - 1 : fileResults.length - 1))
|
||||
return
|
||||
}
|
||||
if (key.downArrow) {
|
||||
setSelectedIndex((i) => (i < fileResults.length - 1 ? i + 1 : 0))
|
||||
return
|
||||
}
|
||||
if (key.tab || key.return) {
|
||||
const file = fileResults[selectedIndex]
|
||||
if (file) {
|
||||
setTextInput(insertMention(textInput, mentionInfo.atIndex, file.path))
|
||||
setFileResults([])
|
||||
setSelectedIndex(0)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (key.escape) {
|
||||
setFileResults([])
|
||||
setSelectedIndex(0)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Normal input handling
|
||||
if (key.tab && !mentionInfo.inMentionMode) {
|
||||
toggleMode()
|
||||
return
|
||||
}
|
||||
if (key.return && !mentionInfo.inMentionMode) {
|
||||
if (prompt.trim() || imagePaths.length > 0) {
|
||||
onSubmit(prompt.trim(), imagePaths)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (key.escape && !mentionInfo.inMentionMode) {
|
||||
if (escPressedOnce) {
|
||||
onExit?.()
|
||||
} else {
|
||||
setEscPressedOnce(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (key.backspace || key.delete) {
|
||||
setTextInput((prev) => prev.slice(0, -1))
|
||||
setEscPressedOnce(false)
|
||||
return
|
||||
}
|
||||
if (input && !key.ctrl && !key.meta && !key.upArrow && !key.downArrow && !key.tab) {
|
||||
setTextInput((prev) => prev + input)
|
||||
setEscPressedOnce(false)
|
||||
}
|
||||
},
|
||||
{ isActive: isRawModeSupported },
|
||||
)
|
||||
|
||||
const borderColor = mode === "act" ? "blue" : "yellow"
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" width="100%">
|
||||
{/* Account/Provider info at top */}
|
||||
{controller && (
|
||||
<Box marginBottom={1}>
|
||||
<AccountInfoView controller={controller} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Cline logo - centered */}
|
||||
<Box alignItems="center" flexDirection="column">
|
||||
{CLINE_LOGO.map((line, idx) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: static array that never changes
|
||||
<Text color="white" key={idx}>
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Main prompt - centered, bold */}
|
||||
<Box justifyContent="center" marginTop={1}>
|
||||
<Text bold color="white">
|
||||
What can I do for you?
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Ripgrep warning if needed */}
|
||||
{showRipgrepWarning && (
|
||||
<Box marginTop={1}>
|
||||
<Text color="yellow">⚠ ripgrep not found - file search will be slower. </Text>
|
||||
<Text color="gray">Install: {getRipgrepInstallInstructions()}</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Input field with border */}
|
||||
<Box
|
||||
borderColor={borderColor}
|
||||
borderStyle="round"
|
||||
flexDirection="row"
|
||||
marginTop={1}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
width="100%">
|
||||
<Text>{textInput}</Text>
|
||||
<Text inverse> </Text>
|
||||
</Box>
|
||||
|
||||
{/* Model ID and Mode toggle row */}
|
||||
<Box justifyContent="space-between" width="100%">
|
||||
{/* Model ID on left */}
|
||||
<Text color="gray">{modelId}</Text>
|
||||
|
||||
{/* Mode toggle on right */}
|
||||
<Box gap={1}>
|
||||
<Box>
|
||||
<Text bold={mode === "plan"} color={mode === "plan" ? "yellow" : "gray"}>
|
||||
{mode === "plan" ? "●" : "○"} Plan
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text bold={mode === "act"} color={mode === "act" ? "blue" : "gray"}>
|
||||
{mode === "act" ? "●" : "○"} Act
|
||||
</Text>
|
||||
</Box>
|
||||
<Text color="gray">(Tab)</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* File mention menu - below input */}
|
||||
{mentionInfo.inMentionMode && (
|
||||
<FileMentionMenu
|
||||
isLoading={isSearching}
|
||||
query={mentionInfo.query}
|
||||
results={fileResults}
|
||||
selectedIndex={selectedIndex}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Attached images */}
|
||||
{imagePaths.length > 0 && (
|
||||
<Text color="magenta">
|
||||
📎 {imagePaths.length} image{imagePaths.length > 1 ? "s" : ""} attached
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* Help text */}
|
||||
<Box>
|
||||
<Text color="gray">Enter to submit · @ to mention files · </Text>
|
||||
<Text bold={escPressedOnce} color={escPressedOnce ? "white" : "gray"}>
|
||||
{escPressedOnce ? "Press Esc again to exit" : "Esc to exit"}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export class CliDiffServiceClient implements DiffServiceClientInterface {
|
||||
* CLI implementation of EnvService - handles environment operations
|
||||
*/
|
||||
export class CliEnvServiceClient implements EnvServiceClientInterface {
|
||||
private clipboardContent: string = ""
|
||||
private clipboardContent = ""
|
||||
|
||||
private getTelemetrySetting(): proto.host.Setting {
|
||||
// Read from StateManager - defaults to ENABLED if not set or "unset"
|
||||
@@ -102,6 +102,8 @@ export class CliEnvServiceClient implements EnvServiceClientInterface {
|
||||
version: CLI_VERSION,
|
||||
platform: "Cline CLI - Node.js",
|
||||
clineType: ClineClient.Cli,
|
||||
// remoteName is intentionally omitted — the CLI runs locally on the user's machine.
|
||||
// If CLI-in-container scenarios arise, populate this field to enable remote cadence tuning.
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+134
-31
@@ -11,6 +11,22 @@ import { captureUnhandledException } from "."
|
||||
describe("CLI Commands", () => {
|
||||
let program: Command
|
||||
|
||||
function getCommand(name: string): Command {
|
||||
const command = program.commands.find((candidate) => candidate.name() === name)
|
||||
if (!command) {
|
||||
throw new Error(`Missing command: ${name}`)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
function getSubcommand(commandName: string, subcommandName: string): Command {
|
||||
const subcommand = getCommand(commandName).commands.find((candidate) => candidate.name() === subcommandName)
|
||||
if (!subcommand) {
|
||||
throw new Error(`Missing subcommand: ${commandName} ${subcommandName}`)
|
||||
}
|
||||
return subcommand
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a fresh program instance for each test
|
||||
program = new Command()
|
||||
@@ -67,6 +83,28 @@ describe("CLI Commands", () => {
|
||||
.option("--config <path>", "Configuration directory")
|
||||
.action(() => {})
|
||||
|
||||
const mcpCommand = program.command("mcp").description("Manage MCP servers")
|
||||
mcpCommand
|
||||
.command("add")
|
||||
.description("Add an MCP server shortcut")
|
||||
.argument("<name>", "MCP server name")
|
||||
.argument("[targetOrCommand...]", "Command args for stdio, or URL for remote")
|
||||
.option("--type <type>", "Transport type", "stdio")
|
||||
.option("-c, --cwd <path>", "Working directory")
|
||||
.option("--config <path>", "Configuration directory")
|
||||
.action(() => {})
|
||||
|
||||
program
|
||||
.command("kanban")
|
||||
.description("Run kanban")
|
||||
.action(() => {})
|
||||
|
||||
program
|
||||
.command("update")
|
||||
.description("Check for updates and install if available")
|
||||
.option("-v, --verbose", "Show verbose output")
|
||||
.action(() => {})
|
||||
|
||||
// Default command for interactive mode
|
||||
program
|
||||
.argument("[prompt]", "Task prompt")
|
||||
@@ -81,6 +119,9 @@ describe("CLI Commands", () => {
|
||||
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
|
||||
.option("--hooks-dir <path>", "Additional hooks directory")
|
||||
.option("--auto-approve-all", "Enable auto-approve all")
|
||||
.option("--update", "Check for updates and install if available")
|
||||
.option("--kanban", "Run kanban")
|
||||
.option("--tui", "Open the legacy terminal UI instead of the kanban experience")
|
||||
.action(() => {})
|
||||
})
|
||||
|
||||
@@ -97,119 +138,119 @@ describe("CLI Commands", () => {
|
||||
})
|
||||
|
||||
it("should parse --act flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--act"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().act).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --plan flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--plan"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().plan).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --yolo flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--yolo"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().yolo).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --auto-approve-all flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--auto-approve-all"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().autoApproveAll).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --model option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--model", "claude-sonnet-4-20250514"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().model).toBe("claude-sonnet-4-20250514")
|
||||
})
|
||||
|
||||
it("should parse --images option with multiple paths", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--images", "/path/to/img1.png", "/path/to/img2.jpg"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().images).toEqual(["/path/to/img1.png", "/path/to/img2.jpg"])
|
||||
})
|
||||
|
||||
it("should parse --verbose flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--verbose"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().verbose).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --cwd option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--cwd", "/some/path"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().cwd).toBe("/some/path")
|
||||
})
|
||||
|
||||
it("should parse --config option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--config", "/custom/config"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().config).toBe("/custom/config")
|
||||
})
|
||||
|
||||
it("should parse --thinking flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--thinking"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().thinking).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --thinking with token budget", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--thinking", "8000"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().thinking).toBe("8000")
|
||||
})
|
||||
|
||||
it("should parse --reasoning-effort option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--reasoning-effort", "high"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().reasoningEffort).toBe("high")
|
||||
})
|
||||
|
||||
it("should parse --max-consecutive-mistakes option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--max-consecutive-mistakes", "999"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().maxConsecutiveMistakes).toBe("999")
|
||||
})
|
||||
|
||||
it("should parse --hooks-dir option", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--hooks-dir", "/tmp/hooks"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().hooksDir).toBe("/tmp/hooks")
|
||||
})
|
||||
|
||||
it("should parse --double-check-completion flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--double-check-completion"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().doubleCheckCompletion).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --auto-condense flag", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "--auto-condense"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().autoCondense).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse short flags", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const taskCmd = getCommand("task")
|
||||
const args = ["test prompt", "-a", "-v", "-m", "gpt-4"]
|
||||
taskCmd.parse(args, { from: "user" })
|
||||
expect(taskCmd.opts().act).toBe(true)
|
||||
@@ -220,26 +261,26 @@ describe("CLI Commands", () => {
|
||||
|
||||
describe("history command", () => {
|
||||
it("should have default limit of 10", () => {
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const historyCmd = getCommand("history")
|
||||
historyCmd.parse([], { from: "user" })
|
||||
expect(historyCmd.opts().limit).toBe("10")
|
||||
})
|
||||
|
||||
it("should have default page of 1", () => {
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const historyCmd = getCommand("history")
|
||||
historyCmd.parse([], { from: "user" })
|
||||
expect(historyCmd.opts().page).toBe("1")
|
||||
})
|
||||
|
||||
it("should parse --limit option", () => {
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const historyCmd = getCommand("history")
|
||||
const args = ["--limit", "20"]
|
||||
historyCmd.parse(args, { from: "user" })
|
||||
expect(historyCmd.opts().limit).toBe("20")
|
||||
})
|
||||
|
||||
it("should parse --page option", () => {
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const historyCmd = getCommand("history")
|
||||
const args = ["--page", "3"]
|
||||
historyCmd.parse(args, { from: "user" })
|
||||
expect(historyCmd.opts().page).toBe("3")
|
||||
@@ -252,7 +293,7 @@ describe("CLI Commands", () => {
|
||||
})
|
||||
|
||||
it("should parse short flags", () => {
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const historyCmd = getCommand("history")
|
||||
const args = ["-n", "5", "-p", "2"]
|
||||
historyCmd.parse(args, { from: "user" })
|
||||
expect(historyCmd.opts().limit).toBe("5")
|
||||
@@ -267,13 +308,34 @@ describe("CLI Commands", () => {
|
||||
})
|
||||
|
||||
it("should parse --config option", () => {
|
||||
const configCmd = program.commands.find((c) => c.name() === "config")!
|
||||
const configCmd = getCommand("config")
|
||||
const args = ["--config", "/custom/path"]
|
||||
configCmd.parse(args, { from: "user" })
|
||||
expect(configCmd.opts().config).toBe("/custom/path")
|
||||
})
|
||||
})
|
||||
|
||||
describe("kanban command", () => {
|
||||
it("should parse kanban command", () => {
|
||||
const args = ["node", "cli", "kanban"]
|
||||
program.parse(args)
|
||||
})
|
||||
})
|
||||
|
||||
describe("update command", () => {
|
||||
it("should parse update command", () => {
|
||||
const args = ["node", "cli", "update"]
|
||||
program.parse(args)
|
||||
})
|
||||
|
||||
it("should parse --verbose on update command", () => {
|
||||
const updateCmd = getCommand("update")
|
||||
const args = ["--verbose"]
|
||||
updateCmd.parse(args, { from: "user" })
|
||||
expect(updateCmd.opts().verbose).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("auth command", () => {
|
||||
it("should parse auth command", () => {
|
||||
const args = ["node", "cli", "auth"]
|
||||
@@ -281,35 +343,35 @@ describe("CLI Commands", () => {
|
||||
})
|
||||
|
||||
it("should parse --provider option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const authCmd = getCommand("auth")
|
||||
const args = ["--provider", "openai"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().provider).toBe("openai")
|
||||
})
|
||||
|
||||
it("should parse --apikey option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const authCmd = getCommand("auth")
|
||||
const args = ["--apikey", "sk-test-key"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().apikey).toBe("sk-test-key")
|
||||
})
|
||||
|
||||
it("should parse --modelid option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const authCmd = getCommand("auth")
|
||||
const args = ["--modelid", "gpt-4"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().modelid).toBe("gpt-4")
|
||||
})
|
||||
|
||||
it("should parse --baseurl option", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const authCmd = getCommand("auth")
|
||||
const args = ["--baseurl", "https://api.example.com"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().baseurl).toBe("https://api.example.com")
|
||||
})
|
||||
|
||||
it("should parse short flags", () => {
|
||||
const authCmd = program.commands.find((c) => c.name() === "auth")!
|
||||
const authCmd = getCommand("auth")
|
||||
const args = ["-p", "anthropic", "-k", "key123", "-m", "claude-sonnet-4-20250514"]
|
||||
authCmd.parse(args, { from: "user" })
|
||||
expect(authCmd.opts().provider).toBe("anthropic")
|
||||
@@ -318,6 +380,30 @@ describe("CLI Commands", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("mcp command", () => {
|
||||
it("should parse mcp add stdio syntax", () => {
|
||||
const args = ["node", "cli", "mcp", "add", "kanban", "--", "kanban", "mcp"]
|
||||
program.parse(args)
|
||||
})
|
||||
|
||||
it("should parse mcp add remote http syntax", () => {
|
||||
const args = ["node", "cli", "mcp", "add", "linear", "https://mcp.linear.app/mcp", "--type", "http"]
|
||||
program.parse(args)
|
||||
})
|
||||
|
||||
it("should default mcp add type to stdio", () => {
|
||||
const addCmd = getSubcommand("mcp", "add")
|
||||
addCmd.parse(["kanban", "--", "kanban", "mcp"], { from: "user" })
|
||||
expect(addCmd.opts().type).toBe("stdio")
|
||||
})
|
||||
|
||||
it("should parse mcp add type option", () => {
|
||||
const addCmd = getSubcommand("mcp", "add")
|
||||
addCmd.parse(["linear", "https://mcp.linear.app/mcp", "--type", "http"], { from: "user" })
|
||||
expect(addCmd.opts().type).toBe("http")
|
||||
})
|
||||
})
|
||||
|
||||
describe("default command (interactive mode)", () => {
|
||||
it("should parse optional prompt argument", () => {
|
||||
const args = ["node", "cli", "do something"]
|
||||
@@ -368,6 +454,21 @@ describe("CLI Commands", () => {
|
||||
program.parse(["node", "cli", "--auto-approve-all"])
|
||||
expect(program.opts().autoApproveAll).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --kanban flag", () => {
|
||||
program.parse(["node", "cli", "--kanban"])
|
||||
expect(program.opts().kanban).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --update flag", () => {
|
||||
program.parse(["node", "cli", "--update"])
|
||||
expect(program.opts().update).toBe(true)
|
||||
})
|
||||
|
||||
it("should parse --tui flag", () => {
|
||||
program.parse(["node", "cli", "--tui"])
|
||||
expect(program.opts().tui).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("command structure", () => {
|
||||
@@ -377,11 +478,14 @@ describe("CLI Commands", () => {
|
||||
expect(commandNames).toContain("history")
|
||||
expect(commandNames).toContain("config")
|
||||
expect(commandNames).toContain("auth")
|
||||
expect(commandNames).toContain("mcp")
|
||||
expect(commandNames).toContain("kanban")
|
||||
expect(commandNames).toContain("update")
|
||||
})
|
||||
|
||||
it("should have correct aliases", () => {
|
||||
const taskCmd = program.commands.find((c) => c.name() === "task")!
|
||||
const historyCmd = program.commands.find((c) => c.name() === "history")!
|
||||
const taskCmd = getCommand("task")
|
||||
const historyCmd = getCommand("history")
|
||||
expect(taskCmd.aliases()).toContain("t")
|
||||
expect(historyCmd.aliases()).toContain("h")
|
||||
})
|
||||
@@ -405,7 +509,6 @@ describe("getProviderModelIdKey", () => {
|
||||
litellm: "LiteLlmModelId",
|
||||
requesty: "RequestyModelId",
|
||||
together: "TogetherModelId",
|
||||
coreweave: "CoreweaveModelId",
|
||||
fireworks: "FireworksModelId",
|
||||
sapaicore: "SapAiCoreModelId",
|
||||
groq: "GroqModelId",
|
||||
|
||||
+268
-9
@@ -2,6 +2,7 @@
|
||||
* Cline CLI - TypeScript implementation with React Ink
|
||||
*/
|
||||
|
||||
import type { ChildProcess } from "node:child_process"
|
||||
import { exit } from "node:process"
|
||||
import type { ApiProvider } from "@shared/api"
|
||||
import { Command } from "commander"
|
||||
@@ -27,6 +28,7 @@ import { isOpenaiReasoningEffort, OPENAI_REASONING_EFFORT_OPTIONS, type OpenaiRe
|
||||
import { version as CLI_VERSION } from "../package.json"
|
||||
import { runAcpMode } from "./acp/index.js"
|
||||
import { App } from "./components/App"
|
||||
import { KanbanMigrationView } from "./components/KanbanMigrationView"
|
||||
import { checkRawModeSupport } from "./context/StdinContext"
|
||||
import { createCliHostBridgeProvider } from "./controllers"
|
||||
import { CliCommentReviewController } from "./controllers/CliCommentReviewController"
|
||||
@@ -34,6 +36,21 @@ import { CliWebviewProvider } from "./controllers/CliWebviewProvider"
|
||||
import { isAuthConfigured } from "./utils/auth"
|
||||
import { restoreConsole, suppressConsoleUnlessVerbose } from "./utils/console"
|
||||
import { printInfo, printWarning } from "./utils/display"
|
||||
import {
|
||||
forwardSignalToKanbanProcess,
|
||||
isKanbanCommandAvailable,
|
||||
KANBAN_LAUNCH_COMMAND,
|
||||
KANBAN_SHUTDOWN_TIMEOUT_MS,
|
||||
type KanbanMigrationAction,
|
||||
LEGACY_TUI_FLAG,
|
||||
markKanbanMigrationAnnouncementShown,
|
||||
resolveKanbanInstallCommand,
|
||||
shouldLaunchKanbanByDefault,
|
||||
shouldShowKanbanMigrationAnnouncementForCurrentUser,
|
||||
spawnKanbanInstallProcess,
|
||||
spawnKanbanProcess,
|
||||
} from "./utils/kanban"
|
||||
import { addMcpServerShortcut, type McpAddOptions } from "./utils/mcp"
|
||||
import { selectOutputMode } from "./utils/mode-selection"
|
||||
import { parseImagesFromInput, processImagePaths } from "./utils/parser"
|
||||
import { CLINE_CLI_DIR, getCliBinaryPath } from "./utils/path"
|
||||
@@ -56,6 +73,8 @@ suppressConsoleUnlessVerbose()
|
||||
interface TaskOptions {
|
||||
act?: boolean
|
||||
plan?: boolean
|
||||
kanban?: boolean
|
||||
tui?: boolean
|
||||
model?: string
|
||||
verbose?: boolean
|
||||
cwd?: string
|
||||
@@ -248,6 +267,83 @@ function getPlainTextModeReason(options: TaskOptions): string {
|
||||
return getModeSelection(options).reason
|
||||
}
|
||||
|
||||
function runKanbanAlias(spawnOptions?: Parameters<typeof spawnKanbanProcess>[0]): void {
|
||||
const launchKanban = () => {
|
||||
const child = spawnKanbanProcess(spawnOptions)
|
||||
activeKanbanProcess = child
|
||||
|
||||
child.on("error", (error) => {
|
||||
clearActiveKanbanProcess()
|
||||
const errorMessage = error instanceof Error ? ` ${error.message}` : ""
|
||||
printWarning(`Failed to run '${KANBAN_LAUNCH_COMMAND}'.${errorMessage}`)
|
||||
exit(1)
|
||||
})
|
||||
|
||||
child.on("close", (code, signal) => {
|
||||
clearActiveKanbanProcess()
|
||||
exit(resolveProcessExitCode(code, signal))
|
||||
})
|
||||
}
|
||||
|
||||
if (isKanbanCommandAvailable()) {
|
||||
launchKanban()
|
||||
return
|
||||
}
|
||||
|
||||
const installCommand = resolveKanbanInstallCommand()
|
||||
if (!installCommand) {
|
||||
printWarning(
|
||||
`'${KANBAN_LAUNCH_COMMAND}' not found and no supported package manager was detected in PATH (npm, pnpm, bun). Install Kanban globally and try again.`,
|
||||
)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
const installProcess = spawnKanbanInstallProcess(installCommand)
|
||||
|
||||
installProcess.on("error", (error) => {
|
||||
const errorMessage = error instanceof Error ? ` ${error.message}` : ""
|
||||
printWarning(`Failed to run '${installCommand.displayCommand}'.${errorMessage}`)
|
||||
exit(1)
|
||||
})
|
||||
|
||||
installProcess.on("close", (code, signal) => {
|
||||
const installExitCode = resolveProcessExitCode(code, signal)
|
||||
if (installExitCode !== 0) {
|
||||
printWarning(`Failed to install Kanban automatically. Please run '${installCommand.displayCommand}' manually.`)
|
||||
exit(installExitCode)
|
||||
}
|
||||
|
||||
launchKanban()
|
||||
})
|
||||
}
|
||||
|
||||
async function showKanbanMigrationView(): Promise<KanbanMigrationAction> {
|
||||
let selectedAction: KanbanMigrationAction = "exit"
|
||||
|
||||
await runInkApp(
|
||||
React.createElement(KanbanMigrationView, {
|
||||
isRawModeSupported: checkRawModeSupport(),
|
||||
onSelect: (action: KanbanMigrationAction) => {
|
||||
selectedAction = action
|
||||
},
|
||||
}),
|
||||
async () => {},
|
||||
)
|
||||
|
||||
return selectedAction
|
||||
}
|
||||
|
||||
async function addMcpServer(name: string, targetOrCommand: string[] = [], options: McpAddOptions): Promise<void> {
|
||||
try {
|
||||
const result = await addMcpServerShortcut(name, targetOrCommand, options)
|
||||
const transportLabel = result.transportType === "streamableHttp" ? "http" : result.transportType
|
||||
printInfo(`Added MCP server '${result.serverName}' (${transportLabel}) to ${result.settingsPath}`)
|
||||
} catch (error) {
|
||||
printWarning(error instanceof Error ? error.message : "Failed to add MCP server.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a task in plain text mode (no Ink UI).
|
||||
* Handles auth check, task execution, cleanup, and exit.
|
||||
@@ -314,6 +410,62 @@ let activeContext: CliContext | null = null
|
||||
let isShuttingDown = false
|
||||
// Track if we're in plain text mode (no Ink UI) - set by runTask when piped stdin detected
|
||||
let isPlainTextMode = false
|
||||
let activeKanbanProcess: ChildProcess | null = null
|
||||
let activeKanbanShutdownTimer: NodeJS.Timeout | null = null
|
||||
|
||||
function clearActiveKanbanProcess(): void {
|
||||
activeKanbanProcess = null
|
||||
if (activeKanbanShutdownTimer) {
|
||||
clearTimeout(activeKanbanShutdownTimer)
|
||||
activeKanbanShutdownTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function requestKanbanProcessShutdown(signal: NodeJS.Signals): void {
|
||||
if (!activeKanbanProcess) {
|
||||
return
|
||||
}
|
||||
|
||||
forwardSignalToKanbanProcess({
|
||||
child: activeKanbanProcess,
|
||||
signal,
|
||||
})
|
||||
|
||||
if (activeKanbanShutdownTimer) {
|
||||
clearTimeout(activeKanbanShutdownTimer)
|
||||
}
|
||||
|
||||
if (signal === "SIGKILL") {
|
||||
activeKanbanShutdownTimer = null
|
||||
return
|
||||
}
|
||||
|
||||
activeKanbanShutdownTimer = setTimeout(() => {
|
||||
if (!activeKanbanProcess) {
|
||||
return
|
||||
}
|
||||
forwardSignalToKanbanProcess({
|
||||
child: activeKanbanProcess,
|
||||
signal: "SIGKILL",
|
||||
})
|
||||
}, KANBAN_SHUTDOWN_TIMEOUT_MS)
|
||||
activeKanbanShutdownTimer.unref?.()
|
||||
}
|
||||
|
||||
function resolveProcessExitCode(code: number | null, signal: NodeJS.Signals | null): number {
|
||||
if (code !== null) {
|
||||
return code
|
||||
}
|
||||
|
||||
switch (signal) {
|
||||
case "SIGINT":
|
||||
return 130
|
||||
case "SIGTERM":
|
||||
return 143
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for stdout to fully drain before exiting.
|
||||
@@ -333,10 +485,19 @@ async function drainStdout(): Promise<void> {
|
||||
|
||||
export async function captureUnhandledException(reason: Error, context: string) {
|
||||
try {
|
||||
const errorService = ErrorService.get()
|
||||
await errorService.captureException(reason, { context })
|
||||
// dispose flushes any pending error captures to ensure they're sent before the process exits
|
||||
return errorService.dispose()
|
||||
// ErrorService may not be initialized yet (e.g., error occurred before initializeCli())
|
||||
// so we guard with a try/get pattern rather than letting ErrorService.get() throw
|
||||
let errorService: ErrorService | null = null
|
||||
try {
|
||||
errorService = ErrorService.get()
|
||||
} catch {
|
||||
// ErrorService not yet initialized; skip capture
|
||||
}
|
||||
if (errorService) {
|
||||
await errorService.captureException(reason, { context })
|
||||
// dispose flushes any pending error captures to ensure they're sent before the process exits
|
||||
return errorService.dispose()
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors during shutdown to avoid an infinite loop
|
||||
Logger.info("Error capturing unhandled exception. Proceeding with shutdown.")
|
||||
@@ -360,6 +521,17 @@ function onUnhandledException(reason: unknown, context: string) {
|
||||
|
||||
function setupSignalHandlers() {
|
||||
const shutdown = async (signal: string) => {
|
||||
if (activeKanbanProcess) {
|
||||
if (isShuttingDown) {
|
||||
requestKanbanProcessShutdown("SIGKILL")
|
||||
return
|
||||
}
|
||||
|
||||
isShuttingDown = true
|
||||
requestKanbanProcessShutdown(signal === "SIGTERM" ? "SIGTERM" : "SIGINT")
|
||||
return
|
||||
}
|
||||
|
||||
if (isShuttingDown) {
|
||||
// Force exit on second signal
|
||||
process.exit(1)
|
||||
@@ -395,7 +567,11 @@ function setupSignalHandlers() {
|
||||
} catch {
|
||||
// StateManager may not be initialized yet
|
||||
}
|
||||
await ErrorService.get().dispose()
|
||||
try {
|
||||
await ErrorService.get().dispose()
|
||||
} catch {
|
||||
// ErrorService may not be initialized yet
|
||||
}
|
||||
await disposeTelemetryServices()
|
||||
}
|
||||
} catch {
|
||||
@@ -553,6 +729,11 @@ async function runTask(prompt: string, options: TaskOptions & { images?: string[
|
||||
// Task without prompt starts in interactive mode
|
||||
telemetryService.captureHostEvent("task_command", prompt ? "task" : "interactive")
|
||||
|
||||
// Capture piped stdin telemetry now that HostProvider is initialized
|
||||
if (options.stdinWasPiped) {
|
||||
telemetryService.captureHostEvent("piped", "detached")
|
||||
}
|
||||
|
||||
// Apply shared task options (mode, model, thinking, yolo)
|
||||
applyTaskOptions(options)
|
||||
await StateManager.get().flushPendingState()
|
||||
@@ -825,6 +1006,18 @@ program
|
||||
.option("--config <path>", "Path to Cline configuration directory")
|
||||
.action(runAuth)
|
||||
|
||||
const mcpCommand = program.command("mcp").description("Manage MCP servers")
|
||||
|
||||
mcpCommand
|
||||
.command("add")
|
||||
.description("Add an MCP server shortcut to cline_mcp_settings.json")
|
||||
.argument("<name>", "MCP server name")
|
||||
.argument("[targetOrCommand...]", "For stdio: use -- <command> [args]. For http/sse: provide <url>.")
|
||||
.option("--type <type>", "Transport type: stdio (default), http, or sse", "stdio")
|
||||
.option("-c, --cwd <path>", "Working directory for config resolution")
|
||||
.option("--config <path>", "Path to Cline configuration directory")
|
||||
.action(addMcpServer)
|
||||
|
||||
program
|
||||
.command("version")
|
||||
.description("Show Cline CLI version number")
|
||||
@@ -834,7 +1027,12 @@ program
|
||||
.command("update")
|
||||
.description("Check for updates and install if available")
|
||||
.option("-v, --verbose", "Show verbose output")
|
||||
.action(() => checkForUpdates(CLI_VERSION))
|
||||
.action((options) => checkForUpdates(CLI_VERSION, { verbose: options.verbose, includeKanban: true }))
|
||||
|
||||
program
|
||||
.command("kanban")
|
||||
.description(`Run ${KANBAN_LAUNCH_COMMAND}`)
|
||||
.action(() => runKanbanAlias())
|
||||
|
||||
// Dev command with subcommands
|
||||
const devCommand = program.command("dev").description("Developer tools and utilities")
|
||||
@@ -874,6 +1072,11 @@ async function resumeTask(taskId: string, options: TaskOptions & { initialPrompt
|
||||
|
||||
telemetryService.captureHostEvent("resume_task_command", options.initialPrompt ? "with_prompt" : "interactive")
|
||||
|
||||
// Capture piped stdin telemetry now that HostProvider is initialized
|
||||
if (options.stdinWasPiped) {
|
||||
telemetryService.captureHostEvent("piped", "detached")
|
||||
}
|
||||
|
||||
// Apply shared task options (mode, model, thinking, yolo)
|
||||
applyTaskOptions(options)
|
||||
await StateManager.get().flushPendingState()
|
||||
@@ -980,9 +1183,37 @@ program
|
||||
.option("--auto-condense", "Enable AI-powered context compaction instead of mechanical truncation")
|
||||
.option("--hooks-dir <path>", "Path to additional hooks directory for runtime hook injection")
|
||||
.option("--acp", "Run in ACP (Agent Client Protocol) mode for editor integration")
|
||||
.option("--update", "Check for updates and install if available")
|
||||
.option("--kanban", `Run ${KANBAN_LAUNCH_COMMAND}`)
|
||||
.option("--tui", "Open the legacy terminal UI instead of the kanban experience")
|
||||
.option("-T, --taskId <id>", "Resume an existing task by ID")
|
||||
.option("--continue", "Resume the most recent task from the current working directory")
|
||||
.action(async (prompt, options) => {
|
||||
if (options.kanban && options.tui) {
|
||||
printWarning(`Use either --kanban or ${LEGACY_TUI_FLAG}, not both.`)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if (options.update) {
|
||||
if (prompt || options.taskId || options.continue || options.kanban || options.tui || options.acp) {
|
||||
printWarning("Use --update without a prompt or task flags.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
await checkForUpdates(CLI_VERSION, { verbose: options.verbose, includeKanban: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (options.kanban) {
|
||||
if (prompt) {
|
||||
printWarning("Use --kanban without a prompt.")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
runKanbanAlias({ cwd: options.cwd })
|
||||
return
|
||||
}
|
||||
|
||||
// Check for ACP mode first - this takes precedence over everything else
|
||||
if (options.acp) {
|
||||
await runAcpMode({
|
||||
@@ -1003,6 +1234,34 @@ program
|
||||
// stdinInput has content means stdin was piped with data
|
||||
const stdinWasPiped = stdinInput !== null
|
||||
|
||||
if (
|
||||
shouldLaunchKanbanByDefault({
|
||||
prompt,
|
||||
stdinWasPiped,
|
||||
taskId: options.taskId,
|
||||
continue: options.continue,
|
||||
tui: options.tui,
|
||||
})
|
||||
) {
|
||||
let migrationAction: "kanban" | "exit" = "kanban"
|
||||
const ctx = await initializeCli({ ...options, enableAuth: true })
|
||||
try {
|
||||
if (await shouldShowKanbanMigrationAnnouncementForCurrentUser()) {
|
||||
migrationAction = await showKanbanMigrationView()
|
||||
await markKanbanMigrationAnnouncementShown()
|
||||
}
|
||||
} finally {
|
||||
await disposeCliContext(ctx)
|
||||
}
|
||||
|
||||
if (migrationAction === "exit") {
|
||||
exit(0)
|
||||
}
|
||||
|
||||
runKanbanAlias({ cwd: options.cwd })
|
||||
return
|
||||
}
|
||||
|
||||
if (options.taskId && options.continue) {
|
||||
printWarning("Use either --taskId or --continue, not both.")
|
||||
exit(1)
|
||||
@@ -1042,8 +1301,6 @@ program
|
||||
effectivePrompt = stdinInput
|
||||
}
|
||||
|
||||
telemetryService.captureHostEvent("piped", "detached")
|
||||
|
||||
// Debug: show that we received piped input
|
||||
if (options.verbose) {
|
||||
process.stderr.write(`[debug] Received ${stdinInput.length} bytes from stdin\n`)
|
||||
@@ -1070,4 +1327,6 @@ program
|
||||
})
|
||||
|
||||
// Parse and run
|
||||
program.parse()
|
||||
if (process.env.VITEST !== "true") {
|
||||
program.parse()
|
||||
}
|
||||
|
||||
@@ -15,3 +15,13 @@ export function isMouseEscapeSequence(input: string): boolean {
|
||||
// They contain [< followed by numbers, semicolons, and end with M or m
|
||||
return input.includes("[<") && /\[<\d+;\d+;\d+[Mm]/.test(input)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ink's key metadata can be inconsistent across platforms/test environments for Enter.
|
||||
* In particular, some Windows CI/test runs surface Enter as raw "\r" input without
|
||||
* setting key.return. Treat either representation as Enter so keyboard handlers remain
|
||||
* stable in production and in tests across platforms.
|
||||
*/
|
||||
export function isEnterKey(input: string, key: { return?: boolean }): boolean {
|
||||
return key.return === true || input === "\r" || input === "\n"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
import { describe, expect, it, vi } from "vitest"
|
||||
import {
|
||||
buildKanbanInstallSpawnOptions,
|
||||
buildKanbanSpawnOptions,
|
||||
forwardSignalToKanbanProcess,
|
||||
hasUsedLegacyCli,
|
||||
isKanbanCommandAvailable,
|
||||
resolveKanbanInstallCommand,
|
||||
shouldDetachKanbanProcess,
|
||||
shouldLaunchKanbanByDefault,
|
||||
shouldShowKanbanMigrationAnnouncement,
|
||||
} from "./kanban"
|
||||
|
||||
describe("shouldLaunchKanbanByDefault", () => {
|
||||
it("launches kanban for a bare interactive run", () => {
|
||||
expect(
|
||||
shouldLaunchKanbanByDefault({
|
||||
stdinWasPiped: false,
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("does not launch kanban when a prompt is provided", () => {
|
||||
expect(
|
||||
shouldLaunchKanbanByDefault({
|
||||
prompt: "fix the tests",
|
||||
stdinWasPiped: false,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it("does not launch kanban when stdin is piped", () => {
|
||||
expect(
|
||||
shouldLaunchKanbanByDefault({
|
||||
stdinWasPiped: true,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it("does not launch kanban when the legacy tui is requested", () => {
|
||||
expect(
|
||||
shouldLaunchKanbanByDefault({
|
||||
stdinWasPiped: false,
|
||||
tui: true,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("hasUsedLegacyCli", () => {
|
||||
it("treats task history as legacy usage", () => {
|
||||
expect(
|
||||
hasUsedLegacyCli({
|
||||
taskHistoryCount: 1,
|
||||
isNewUser: true,
|
||||
welcomeViewCompleted: undefined,
|
||||
hasConfiguredAuth: false,
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("treats configured auth as legacy usage", () => {
|
||||
expect(
|
||||
hasUsedLegacyCli({
|
||||
taskHistoryCount: 0,
|
||||
isNewUser: true,
|
||||
welcomeViewCompleted: undefined,
|
||||
hasConfiguredAuth: true,
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("skips the announcement for fresh installs", () => {
|
||||
expect(
|
||||
hasUsedLegacyCli({
|
||||
taskHistoryCount: 0,
|
||||
isNewUser: true,
|
||||
welcomeViewCompleted: undefined,
|
||||
hasConfiguredAuth: false,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("kanban process launch", () => {
|
||||
it("detaches the kanban process on unix-like platforms", () => {
|
||||
expect(shouldDetachKanbanProcess("darwin")).toBe(true)
|
||||
expect(shouldDetachKanbanProcess("linux")).toBe(true)
|
||||
})
|
||||
|
||||
it("keeps the kanban process attached on windows", () => {
|
||||
expect(shouldDetachKanbanProcess("win32")).toBe(false)
|
||||
})
|
||||
|
||||
it("uses a detached process group by default on unix-like platforms", () => {
|
||||
expect(buildKanbanSpawnOptions({}, "darwin")).toMatchObject({
|
||||
stdio: "inherit",
|
||||
detached: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("does not detach the process on windows", () => {
|
||||
expect(buildKanbanSpawnOptions({}, "win32")).toMatchObject({
|
||||
stdio: "inherit",
|
||||
detached: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("enables shell mode on windows for command launches", () => {
|
||||
expect(buildKanbanSpawnOptions({}, "win32")).toMatchObject({
|
||||
shell: true,
|
||||
})
|
||||
})
|
||||
|
||||
it("does not set shell mode on unix-like platforms", () => {
|
||||
expect(buildKanbanSpawnOptions({}, "darwin")).not.toHaveProperty("shell")
|
||||
})
|
||||
})
|
||||
|
||||
describe("kanban command availability", () => {
|
||||
it("returns false when PATH is empty", () => {
|
||||
expect(isKanbanCommandAvailable({ PATH: "" }, "darwin")).toBe(false)
|
||||
})
|
||||
|
||||
it("detects the kanban command in PATH", () => {
|
||||
const tempDirectory = mkdtempSync(join(tmpdir(), "kanban-cli-test-"))
|
||||
const commandPath = join(tempDirectory, process.platform === "win32" ? "kanban.cmd" : "kanban")
|
||||
writeFileSync(commandPath, process.platform === "win32" ? "@echo off\r\necho ok\r\n" : "#!/bin/sh\necho ok\n")
|
||||
if (process.platform !== "win32") {
|
||||
chmodSync(commandPath, 0o755)
|
||||
}
|
||||
|
||||
try {
|
||||
expect(isKanbanCommandAvailable({ PATH: tempDirectory }, process.platform)).toBe(true)
|
||||
} finally {
|
||||
rmSync(tempDirectory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("kanban install process launch", () => {
|
||||
it("does not detach the install process on unix-like platforms", () => {
|
||||
expect(buildKanbanInstallSpawnOptions({}, "darwin")).toMatchObject({
|
||||
stdio: "inherit",
|
||||
detached: false,
|
||||
})
|
||||
})
|
||||
|
||||
it("enables shell mode on windows for npm.cmd launches", () => {
|
||||
expect(buildKanbanInstallSpawnOptions({}, "win32")).toMatchObject({
|
||||
shell: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("kanban installer resolution", () => {
|
||||
it("prefers npm when available", () => {
|
||||
const tempDirectory = mkdtempSync(join(tmpdir(), "kanban-installer-test-"))
|
||||
writeFileSync(join(tempDirectory, "npm"), "#!/bin/sh\necho npm\n")
|
||||
writeFileSync(join(tempDirectory, "pnpm"), "#!/bin/sh\necho pnpm\n")
|
||||
writeFileSync(join(tempDirectory, "bun"), "#!/bin/sh\necho bun\n")
|
||||
chmodSync(join(tempDirectory, "npm"), 0o755)
|
||||
chmodSync(join(tempDirectory, "pnpm"), 0o755)
|
||||
chmodSync(join(tempDirectory, "bun"), 0o755)
|
||||
|
||||
try {
|
||||
expect(resolveKanbanInstallCommand({ PATH: tempDirectory }, "darwin")?.packageManager).toBe("npm")
|
||||
} finally {
|
||||
rmSync(tempDirectory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("falls back to pnpm when npm is unavailable", () => {
|
||||
const tempDirectory = mkdtempSync(join(tmpdir(), "kanban-installer-test-"))
|
||||
writeFileSync(join(tempDirectory, "pnpm"), "#!/bin/sh\necho pnpm\n")
|
||||
chmodSync(join(tempDirectory, "pnpm"), 0o755)
|
||||
|
||||
try {
|
||||
const installer = resolveKanbanInstallCommand({ PATH: tempDirectory }, "darwin")
|
||||
expect(installer?.packageManager).toBe("pnpm")
|
||||
expect(installer?.displayCommand).toBe("pnpm add -g kanban@latest")
|
||||
} finally {
|
||||
rmSync(tempDirectory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("falls back to bun when npm and pnpm are unavailable", () => {
|
||||
const tempDirectory = mkdtempSync(join(tmpdir(), "kanban-installer-test-"))
|
||||
writeFileSync(join(tempDirectory, "bun"), "#!/bin/sh\necho bun\n")
|
||||
chmodSync(join(tempDirectory, "bun"), 0o755)
|
||||
|
||||
try {
|
||||
const installer = resolveKanbanInstallCommand({ PATH: tempDirectory }, "darwin")
|
||||
expect(installer?.packageManager).toBe("bun")
|
||||
expect(installer?.displayCommand).toBe("bun add -g kanban@latest")
|
||||
} finally {
|
||||
rmSync(tempDirectory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it("returns null when no supported package manager is available", () => {
|
||||
expect(resolveKanbanInstallCommand({ PATH: "" }, "darwin")).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("forwardSignalToKanbanProcess", () => {
|
||||
it("signals the detached kanban process group on unix-like platforms", () => {
|
||||
const killProcess = vi.fn()
|
||||
const child = {
|
||||
pid: 4321,
|
||||
kill: vi.fn(),
|
||||
}
|
||||
|
||||
forwardSignalToKanbanProcess({
|
||||
child,
|
||||
signal: "SIGINT",
|
||||
platform: "darwin",
|
||||
killProcess,
|
||||
})
|
||||
|
||||
expect(killProcess).toHaveBeenCalledWith(-4321, "SIGINT")
|
||||
expect(child.kill).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("signals the child process directly on windows", () => {
|
||||
const killProcess = vi.fn()
|
||||
const child = {
|
||||
pid: 4321,
|
||||
kill: vi.fn(),
|
||||
}
|
||||
|
||||
forwardSignalToKanbanProcess({
|
||||
child,
|
||||
signal: "SIGTERM",
|
||||
platform: "win32",
|
||||
killProcess,
|
||||
})
|
||||
|
||||
expect(killProcess).not.toHaveBeenCalled()
|
||||
expect(child.kill).toHaveBeenCalledWith("SIGTERM")
|
||||
})
|
||||
})
|
||||
|
||||
describe("shouldShowKanbanMigrationAnnouncement", () => {
|
||||
it("shows the announcement once for legacy users", () => {
|
||||
expect(
|
||||
shouldShowKanbanMigrationAnnouncement({
|
||||
announcementShown: false,
|
||||
hasUsedLegacyCli: true,
|
||||
}),
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it("does not show the announcement twice", () => {
|
||||
expect(
|
||||
shouldShowKanbanMigrationAnnouncement({
|
||||
announcementShown: true,
|
||||
hasUsedLegacyCli: true,
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,246 @@
|
||||
import { type ChildProcess, type SpawnOptions, spawn } from "node:child_process"
|
||||
import { accessSync, constants as fsConstants } from "node:fs"
|
||||
import { delimiter, extname, join } from "node:path"
|
||||
import { StateManager } from "@/core/storage/StateManager"
|
||||
import { checkAnyProviderConfigured } from "./auth"
|
||||
|
||||
export const KANBAN_LAUNCH_COMMAND = "kanban"
|
||||
export const KANBAN_SHUTDOWN_TIMEOUT_MS = 10_000
|
||||
export const LEGACY_TUI_FLAG = "--tui"
|
||||
export type KanbanMigrationAction = "kanban" | "exit"
|
||||
type KanbanInstaller = "npm" | "pnpm" | "bun"
|
||||
|
||||
interface KanbanInstallCommand {
|
||||
packageManager: KanbanInstaller
|
||||
command: string
|
||||
args: readonly string[]
|
||||
displayCommand: string
|
||||
}
|
||||
|
||||
interface SignalableKanbanProcess {
|
||||
pid?: number
|
||||
kill: (signal?: NodeJS.Signals | number) => boolean
|
||||
}
|
||||
|
||||
function getKanbanCommand(platform: NodeJS.Platform = process.platform): string {
|
||||
return platform === "win32" ? "kanban.cmd" : "kanban"
|
||||
}
|
||||
|
||||
function getPackageManagerCommand(packageManager: KanbanInstaller, platform: NodeJS.Platform = process.platform): string {
|
||||
if (platform !== "win32") {
|
||||
return packageManager
|
||||
}
|
||||
|
||||
return packageManager === "bun" ? "bun" : `${packageManager}.cmd`
|
||||
}
|
||||
|
||||
const KANBAN_INSTALL_COMMANDS: ReadonlyArray<Omit<KanbanInstallCommand, "displayCommand">> = [
|
||||
{
|
||||
packageManager: "npm",
|
||||
command: "npm",
|
||||
args: ["install", "-g", "kanban@latest"],
|
||||
},
|
||||
{
|
||||
packageManager: "pnpm",
|
||||
command: "pnpm",
|
||||
args: ["add", "-g", "kanban@latest"],
|
||||
},
|
||||
{
|
||||
packageManager: "bun",
|
||||
command: "bun",
|
||||
args: ["add", "-g", "kanban@latest"],
|
||||
},
|
||||
]
|
||||
|
||||
function toDisplayCommand(command: string, args: readonly string[]): string {
|
||||
return `${command} ${args.join(" ")}`
|
||||
}
|
||||
|
||||
export function shouldDetachKanbanProcess(platform: NodeJS.Platform = process.platform): boolean {
|
||||
return platform !== "win32"
|
||||
}
|
||||
|
||||
export function buildKanbanSpawnOptions(options: SpawnOptions = {}, platform: NodeJS.Platform = process.platform): SpawnOptions {
|
||||
return {
|
||||
stdio: "inherit",
|
||||
detached: shouldDetachKanbanProcess(platform),
|
||||
...(platform === "win32" ? { shell: true } : {}),
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildKanbanInstallSpawnOptions(
|
||||
options: SpawnOptions = {},
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): SpawnOptions {
|
||||
return {
|
||||
stdio: "inherit",
|
||||
detached: false,
|
||||
...(platform === "win32" ? { shell: true } : {}),
|
||||
...options,
|
||||
}
|
||||
}
|
||||
|
||||
export function spawnKanbanProcess(options: SpawnOptions = {}): ChildProcess {
|
||||
return spawn(getKanbanCommand(), [], buildKanbanSpawnOptions(options))
|
||||
}
|
||||
|
||||
export function spawnKanbanInstallProcess(installCommand: KanbanInstallCommand, options: SpawnOptions = {}): ChildProcess {
|
||||
return spawn(
|
||||
getPackageManagerCommand(installCommand.packageManager),
|
||||
[...installCommand.args],
|
||||
buildKanbanInstallSpawnOptions(options),
|
||||
)
|
||||
}
|
||||
|
||||
function getPathEntries(env: NodeJS.ProcessEnv): string[] {
|
||||
const pathValue = env.PATH ?? env.Path ?? env.path
|
||||
if (!pathValue) {
|
||||
return []
|
||||
}
|
||||
|
||||
return pathValue
|
||||
.split(delimiter)
|
||||
.map((entry) => entry.trim().replace(/^"(.*)"$/u, "$1"))
|
||||
.filter((entry) => entry.length > 0)
|
||||
}
|
||||
|
||||
function pathExists(candidatePath: string, platform: NodeJS.Platform): boolean {
|
||||
try {
|
||||
if (platform === "win32") {
|
||||
accessSync(candidatePath, fsConstants.F_OK)
|
||||
} else {
|
||||
accessSync(candidatePath, fsConstants.X_OK)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function isCommandAvailable(
|
||||
command: string,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): boolean {
|
||||
const commandHasExtension = extname(command).length > 0
|
||||
const pathExtensions =
|
||||
platform === "win32" ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter((ext) => ext.length > 0) : []
|
||||
|
||||
for (const pathEntry of getPathEntries(env)) {
|
||||
const commandPath = join(pathEntry, command)
|
||||
if (pathExists(commandPath, platform)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!commandHasExtension && platform === "win32") {
|
||||
for (const extension of pathExtensions) {
|
||||
if (pathExists(`${commandPath}${extension}`, platform)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function isKanbanCommandAvailable(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): boolean {
|
||||
return isCommandAvailable(getKanbanCommand(platform), env, platform)
|
||||
}
|
||||
|
||||
export function resolveKanbanInstallCommand(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): KanbanInstallCommand | null {
|
||||
for (const installCommand of KANBAN_INSTALL_COMMANDS) {
|
||||
if (isCommandAvailable(installCommand.command, env, platform)) {
|
||||
return {
|
||||
...installCommand,
|
||||
displayCommand: toDisplayCommand(installCommand.command, installCommand.args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function forwardSignalToKanbanProcess(options: {
|
||||
child: SignalableKanbanProcess
|
||||
signal: NodeJS.Signals
|
||||
platform?: NodeJS.Platform
|
||||
killProcess?: (pid: number, signal: NodeJS.Signals | number) => boolean
|
||||
}): void {
|
||||
if (options.child.pid == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (shouldDetachKanbanProcess(options.platform)) {
|
||||
try {
|
||||
;(options.killProcess ?? process.kill)(-options.child.pid, options.signal)
|
||||
return
|
||||
} catch (error) {
|
||||
if (error && typeof error === "object" && "code" in error && error.code === "ESRCH") {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options.child.kill(options.signal)
|
||||
}
|
||||
|
||||
export function shouldLaunchKanbanByDefault(options: {
|
||||
prompt?: string
|
||||
stdinWasPiped: boolean
|
||||
taskId?: string
|
||||
continue?: boolean
|
||||
tui?: boolean
|
||||
}): boolean {
|
||||
return !options.prompt && !options.stdinWasPiped && !options.taskId && !options.continue && !options.tui
|
||||
}
|
||||
|
||||
export function hasUsedLegacyCli(options: {
|
||||
taskHistoryCount: number
|
||||
isNewUser: boolean
|
||||
welcomeViewCompleted: boolean | undefined
|
||||
hasConfiguredAuth: boolean
|
||||
}): boolean {
|
||||
return (
|
||||
options.taskHistoryCount > 0 ||
|
||||
options.isNewUser === false ||
|
||||
options.welcomeViewCompleted !== undefined ||
|
||||
options.hasConfiguredAuth
|
||||
)
|
||||
}
|
||||
|
||||
export function shouldShowKanbanMigrationAnnouncement(options: {
|
||||
announcementShown: boolean
|
||||
hasUsedLegacyCli: boolean
|
||||
}): boolean {
|
||||
return !options.announcementShown && options.hasUsedLegacyCli
|
||||
}
|
||||
|
||||
export async function shouldShowKanbanMigrationAnnouncementForCurrentUser(): Promise<boolean> {
|
||||
const stateManager = StateManager.get()
|
||||
const hasConfiguredAuth = await checkAnyProviderConfigured()
|
||||
const hasUsedLegacy = hasUsedLegacyCli({
|
||||
taskHistoryCount: stateManager.getGlobalStateKey("taskHistory")?.length ?? 0,
|
||||
isNewUser: stateManager.getGlobalStateKey("isNewUser"),
|
||||
welcomeViewCompleted: stateManager.getGlobalStateKey("welcomeViewCompleted"),
|
||||
hasConfiguredAuth,
|
||||
})
|
||||
|
||||
return shouldShowKanbanMigrationAnnouncement({
|
||||
announcementShown: stateManager.getGlobalStateKey("cliKanbanMigrationAnnouncementShown"),
|
||||
hasUsedLegacyCli: hasUsedLegacy,
|
||||
})
|
||||
}
|
||||
|
||||
export async function markKanbanMigrationAnnouncementShown(): Promise<void> {
|
||||
const stateManager = StateManager.get()
|
||||
stateManager.setGlobalState("cliKanbanMigrationAnnouncementShown", true)
|
||||
await stateManager.flushPendingState()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import * as fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { afterEach, describe, expect, it } from "vitest"
|
||||
import { addMcpServerShortcut } from "./mcp"
|
||||
|
||||
const tempDirs: string[] = []
|
||||
|
||||
async function createTempConfigDir(): Promise<string> {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "cline-mcp-test-"))
|
||||
tempDirs.push(dir)
|
||||
return dir
|
||||
}
|
||||
|
||||
type McpSettingsFile = {
|
||||
mcpServers: Record<string, Record<string, unknown>>
|
||||
}
|
||||
|
||||
async function readMcpSettings(configDir: string): Promise<McpSettingsFile> {
|
||||
const settingsPath = path.join(configDir, "data", "settings", "cline_mcp_settings.json")
|
||||
return JSON.parse(await fs.readFile(settingsPath, "utf-8")) as McpSettingsFile
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
for (const dir of tempDirs.splice(0, tempDirs.length)) {
|
||||
await fs.rm(dir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe("addMcpServerShortcut", () => {
|
||||
it("writes stdio servers with type=stdio", async () => {
|
||||
const configDir = await createTempConfigDir()
|
||||
|
||||
await addMcpServerShortcut("kanban", ["kanban", "mcp"], { config: configDir })
|
||||
const settings = await readMcpSettings(configDir)
|
||||
|
||||
expect(settings.mcpServers.kanban).toEqual({
|
||||
command: "kanban",
|
||||
args: ["mcp"],
|
||||
type: "stdio",
|
||||
})
|
||||
})
|
||||
|
||||
it("maps --type http to streamableHttp", async () => {
|
||||
const configDir = await createTempConfigDir()
|
||||
|
||||
await addMcpServerShortcut("linear", ["https://mcp.linear.app/mcp"], { config: configDir, type: "http" })
|
||||
const settings = await readMcpSettings(configDir)
|
||||
|
||||
expect(settings.mcpServers.linear).toEqual({
|
||||
url: "https://mcp.linear.app/mcp",
|
||||
type: "streamableHttp",
|
||||
})
|
||||
})
|
||||
|
||||
it("errors when URL is provided without --type http", async () => {
|
||||
const configDir = await createTempConfigDir()
|
||||
|
||||
await expect(addMcpServerShortcut("linear", ["https://mcp.linear.app/mcp"], { config: configDir })).rejects.toThrow(
|
||||
"Use --type http",
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,159 @@
|
||||
import * as fs from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import { getMcpSettingsFilePath } from "@/core/storage/disk"
|
||||
import { ServerConfigSchema } from "@/services/mcp/schemas"
|
||||
import { initializeCliContext } from "../vscode-context"
|
||||
|
||||
export interface McpAddOptions {
|
||||
type?: string
|
||||
config?: string
|
||||
cwd?: string
|
||||
}
|
||||
|
||||
export type McpAddTransportType = "stdio" | "streamableHttp" | "sse"
|
||||
|
||||
export interface AddMcpServerResult {
|
||||
serverName: string
|
||||
transportType: McpAddTransportType
|
||||
settingsPath: string
|
||||
}
|
||||
|
||||
function normalizeMcpTransportType(value?: string): McpAddTransportType {
|
||||
const normalized = (value || "stdio").trim().toLowerCase()
|
||||
|
||||
switch (normalized) {
|
||||
case "stdio":
|
||||
return "stdio"
|
||||
case "http":
|
||||
case "streamable-http":
|
||||
case "streamablehttp":
|
||||
return "streamableHttp"
|
||||
case "sse":
|
||||
return "sse"
|
||||
default:
|
||||
throw new Error(`Invalid MCP transport type '${value}'. Valid values: stdio, http, sse.`)
|
||||
}
|
||||
}
|
||||
|
||||
function parseMcpSettings(content: string, settingsPath: string): Record<string, unknown> {
|
||||
const trimmedContent = content.trim()
|
||||
if (!trimmedContent) {
|
||||
return { mcpServers: {} }
|
||||
}
|
||||
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(content)
|
||||
} catch {
|
||||
throw new Error(`Invalid JSON in ${settingsPath}. Please fix the file and try again.`)
|
||||
}
|
||||
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
throw new Error(`Invalid MCP settings file at ${settingsPath}. Expected a JSON object.`)
|
||||
}
|
||||
|
||||
const settings = parsed as Record<string, unknown>
|
||||
if (settings.mcpServers === undefined) {
|
||||
settings.mcpServers = {}
|
||||
}
|
||||
|
||||
if (!settings.mcpServers || typeof settings.mcpServers !== "object" || Array.isArray(settings.mcpServers)) {
|
||||
throw new Error(`Invalid MCP settings file at ${settingsPath}. Expected 'mcpServers' to be an object.`)
|
||||
}
|
||||
|
||||
return settings
|
||||
}
|
||||
|
||||
function createMcpServerConfig(targetOrCommand: string[], transportType: McpAddTransportType): Record<string, unknown> {
|
||||
if (transportType === "stdio") {
|
||||
if (targetOrCommand.length < 1) {
|
||||
throw new Error("Missing stdio command. Example: cline mcp add kanban -- kanban mcp")
|
||||
}
|
||||
|
||||
// Guard against common mistake:
|
||||
// `cline mcp add <name> <url>` without `--type http`
|
||||
if (targetOrCommand.length === 1) {
|
||||
const [value] = targetOrCommand
|
||||
try {
|
||||
const parsedUrl = new URL(value)
|
||||
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
|
||||
throw new Error(
|
||||
`Looks like you provided a URL for '${value}'. Use --type http, for example: cline mcp add <name> ${value} --type http`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.startsWith("Looks like you provided a URL")) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [command, ...args] = targetOrCommand
|
||||
const config: Record<string, unknown> = {
|
||||
command,
|
||||
type: "stdio",
|
||||
}
|
||||
|
||||
if (args.length > 0) {
|
||||
config.args = args
|
||||
}
|
||||
|
||||
ServerConfigSchema.parse(config)
|
||||
return config
|
||||
}
|
||||
|
||||
if (targetOrCommand.length !== 1) {
|
||||
throw new Error(
|
||||
"HTTP/SSE MCP servers require exactly one URL. Example: cline mcp add linear https://mcp.linear.app/mcp --type http",
|
||||
)
|
||||
}
|
||||
|
||||
const config = {
|
||||
url: targetOrCommand[0],
|
||||
type: transportType,
|
||||
}
|
||||
|
||||
ServerConfigSchema.parse(config)
|
||||
return config
|
||||
}
|
||||
|
||||
export async function addMcpServerShortcut(
|
||||
name: string,
|
||||
targetOrCommand: string[] = [],
|
||||
options: McpAddOptions,
|
||||
): Promise<AddMcpServerResult> {
|
||||
const trimmedName = name.trim()
|
||||
if (!trimmedName) {
|
||||
throw new Error("Server name is required.")
|
||||
}
|
||||
|
||||
const transportType = normalizeMcpTransportType(options.type)
|
||||
|
||||
const { DATA_DIR } = initializeCliContext({
|
||||
clineDir: options.config,
|
||||
workspaceDir: options.cwd || process.cwd(),
|
||||
})
|
||||
|
||||
const settingsDirectoryPath = path.join(DATA_DIR, "settings")
|
||||
await fs.mkdir(settingsDirectoryPath, { recursive: true })
|
||||
const settingsPath = await getMcpSettingsFilePath(settingsDirectoryPath)
|
||||
|
||||
const content = await fs.readFile(settingsPath, "utf-8")
|
||||
const settings = parseMcpSettings(content, settingsPath)
|
||||
const mcpServers = settings.mcpServers as Record<string, unknown>
|
||||
|
||||
if (mcpServers[trimmedName]) {
|
||||
throw new Error(`An MCP server named '${trimmedName}' already exists.`)
|
||||
}
|
||||
|
||||
const serverConfig = createMcpServerConfig(targetOrCommand, transportType)
|
||||
mcpServers[trimmedName] = serverConfig
|
||||
|
||||
await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf-8")
|
||||
|
||||
return {
|
||||
serverName: trimmedName,
|
||||
transportType,
|
||||
settingsPath,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { SlashCommandInfo } from "@shared/proto/cline/slash"
|
||||
import { describe, expect, it } from "vitest"
|
||||
import { filterCommands, getStandaloneSlashCommandToExecute } from "./slash-commands"
|
||||
|
||||
const createCommand = (name: string): SlashCommandInfo => ({
|
||||
name,
|
||||
description: `${name} command`,
|
||||
section: "default",
|
||||
cliCompatible: true,
|
||||
})
|
||||
|
||||
describe("filterCommands", () => {
|
||||
it("prioritizes exact matches ahead of fuzzy matches", () => {
|
||||
const commands = [createCommand("help"), createCommand("history"), createCommand("q")]
|
||||
|
||||
const result = filterCommands(commands, "q")
|
||||
|
||||
expect(result.map((command) => command.name)[0]).toBe("q")
|
||||
})
|
||||
|
||||
it("prioritizes prefix matches ahead of fuzzy matches", () => {
|
||||
const commands = [createCommand("history"), createCommand("help"), createCommand("exit")]
|
||||
|
||||
const result = filterCommands(commands, "hi")
|
||||
|
||||
expect(result.map((command) => command.name)[0]).toBe("history")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getStandaloneSlashCommandToExecute", () => {
|
||||
it("ignores standalone execution when slash menu is visible", () => {
|
||||
expect(
|
||||
getStandaloneSlashCommandToExecute({
|
||||
prompt: "/q",
|
||||
inSlashMode: true,
|
||||
hasSlashMenu: true,
|
||||
hasPendingAsk: false,
|
||||
isSpinnerActive: false,
|
||||
}),
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it("returns standalone command when enter should execute it directly", () => {
|
||||
expect(
|
||||
getStandaloneSlashCommandToExecute({
|
||||
prompt: "/exit",
|
||||
inSlashMode: false,
|
||||
hasSlashMenu: false,
|
||||
hasPendingAsk: false,
|
||||
isSpinnerActive: false,
|
||||
}),
|
||||
).toBe("exit")
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { SlashCommandInfo } from "@shared/proto/cline/slash"
|
||||
import { CLI_ONLY_COMMANDS } from "@shared/slashCommands"
|
||||
import { fuzzyFilter } from "./fuzzy-search"
|
||||
|
||||
export interface SlashQueryInfo {
|
||||
@@ -17,12 +18,29 @@ export interface VisibleWindow<T> {
|
||||
startIndex: number
|
||||
}
|
||||
|
||||
export interface StandaloneSlashCommandExecutionInput {
|
||||
prompt: string
|
||||
inSlashMode: boolean
|
||||
hasSlashMenu: boolean
|
||||
hasPendingAsk: boolean
|
||||
isSpinnerActive: boolean
|
||||
}
|
||||
|
||||
export function createCliOnlySlashCommands(): SlashCommandInfo[] {
|
||||
return CLI_ONLY_COMMANDS.map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description || "",
|
||||
section: cmd.section || "default",
|
||||
cliCompatible: true,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate visible window for a scrollable list menu.
|
||||
* Centers the selected item in the visible window when possible.
|
||||
* Returns the visible items and the start index for selection tracking.
|
||||
*/
|
||||
export function getVisibleWindow<T>(items: T[], selectedIndex: number, maxVisible: number = 5): VisibleWindow<T> {
|
||||
export function getVisibleWindow<T>(items: T[], selectedIndex: number, maxVisible = 5): VisibleWindow<T> {
|
||||
if (items.length <= maxVisible) {
|
||||
return { items, startIndex: 0 }
|
||||
}
|
||||
@@ -91,6 +109,42 @@ export function extractSlashQuery(text: string, cursorPosition?: number): SlashQ
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect a standalone slash command (for example "/q" or "/exit")
|
||||
* that should be executed immediately when enter is pressed.
|
||||
*/
|
||||
export function getStandaloneSlashCommandName(text: string): string | null {
|
||||
const match = text.trim().match(/^\/([a-zA-Z0-9_.-]+)$/)
|
||||
return match?.[1] ?? null
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve whether pressing Enter should execute a standalone CLI slash command.
|
||||
* This keeps ChatView's key handling deterministic and easy to test.
|
||||
*/
|
||||
export function getStandaloneSlashCommandToExecute({
|
||||
prompt,
|
||||
inSlashMode,
|
||||
hasSlashMenu,
|
||||
hasPendingAsk,
|
||||
isSpinnerActive,
|
||||
}: StandaloneSlashCommandExecutionInput): string | null {
|
||||
const standaloneSlashCommand = getStandaloneSlashCommandName(prompt)
|
||||
if (!standaloneSlashCommand) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (hasPendingAsk || isSpinnerActive) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (inSlashMode && hasSlashMenu) {
|
||||
return null
|
||||
}
|
||||
|
||||
return standaloneSlashCommand
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter commands using fuzzy matching
|
||||
*/
|
||||
@@ -98,7 +152,26 @@ export function filterCommands(commands: SlashCommandInfo[], query: string): Sla
|
||||
if (!query) {
|
||||
return commands
|
||||
}
|
||||
return fuzzyFilter(commands, query, (cmd) => cmd.name)
|
||||
|
||||
const normalizedQuery = query.toLowerCase()
|
||||
const exactMatches: SlashCommandInfo[] = []
|
||||
const prefixMatches: SlashCommandInfo[] = []
|
||||
const remaining: SlashCommandInfo[] = []
|
||||
|
||||
for (const command of commands) {
|
||||
const normalizedName = command.name.toLowerCase()
|
||||
if (normalizedName === normalizedQuery) {
|
||||
exactMatches.push(command)
|
||||
continue
|
||||
}
|
||||
if (normalizedName.startsWith(normalizedQuery)) {
|
||||
prefixMatches.push(command)
|
||||
continue
|
||||
}
|
||||
remaining.push(command)
|
||||
}
|
||||
|
||||
return [...exactMatches, ...prefixMatches, ...fuzzyFilter(remaining, query, (cmd) => cmd.name)]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+198
-70
@@ -1,9 +1,10 @@
|
||||
import { spawn } from "node:child_process"
|
||||
import { type ChildProcess, spawn, spawnSync } from "node:child_process"
|
||||
import { realpathSync } from "node:fs"
|
||||
import { exit } from "node:process"
|
||||
import { ClineEndpoint } from "@/config"
|
||||
import { fetch } from "@/shared/net"
|
||||
import { printInfo, printWarning } from "./display"
|
||||
import { printInfo, printSuccess, printWarning } from "./display"
|
||||
import { resolveKanbanInstallCommand, spawnKanbanInstallProcess } from "./kanban"
|
||||
|
||||
export enum PackageManager {
|
||||
NPM = "npm",
|
||||
@@ -19,6 +20,11 @@ interface InstallationInfo {
|
||||
updateCommand?: string
|
||||
}
|
||||
|
||||
interface CheckForUpdatesOptions {
|
||||
verbose?: boolean
|
||||
includeKanban?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a version string is a nightly build.
|
||||
*/
|
||||
@@ -91,9 +97,12 @@ function getInstallationInfo(currentVersion: string): InstallationInfo {
|
||||
* Uses the appropriate tag based on whether the current version is nightly.
|
||||
*/
|
||||
async function getLatestVersion(currentVersion: string): Promise<string | null> {
|
||||
return getLatestPackageVersion("cline", getNpmTag(currentVersion))
|
||||
}
|
||||
|
||||
async function getLatestPackageVersion(packageName: string, tag = "latest"): Promise<string | null> {
|
||||
try {
|
||||
const tag = getNpmTag(currentVersion)
|
||||
const response = await fetch(`https://registry.npmjs.org/cline/${tag}`)
|
||||
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}/${tag}`)
|
||||
if (!response.ok) return null
|
||||
const data = (await response.json()) as { version: string }
|
||||
return data.version || null
|
||||
@@ -102,6 +111,29 @@ async function getLatestVersion(currentVersion: string): Promise<string | null>
|
||||
}
|
||||
}
|
||||
|
||||
async function getLatestKanbanVersion(): Promise<string | null> {
|
||||
return getLatestPackageVersion("kanban")
|
||||
}
|
||||
|
||||
function getInstalledKanbanVersion(): string | null {
|
||||
try {
|
||||
const command = process.platform === "win32" ? "kanban.cmd" : "kanban"
|
||||
const result = spawnSync(command, ["--version"], {
|
||||
encoding: "utf8",
|
||||
shell: process.platform === "win32",
|
||||
})
|
||||
if (result.status !== 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim()
|
||||
const versionMatch = output.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/)
|
||||
return versionMatch?.[0] ?? null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-update check that runs on CLI startup.
|
||||
* Checks for updates asynchronously (non-blocking), then spawns a detached
|
||||
@@ -157,85 +189,181 @@ async function checkAndUpdate(currentVersion: string, updateCommand: string): Pr
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForProcessExit(updateProcess: ChildProcess): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
updateProcess.once("close", (code) => {
|
||||
resolve(code ?? 1)
|
||||
})
|
||||
|
||||
updateProcess.once("error", (error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function runClineUpdate(updateCommand: string): Promise<number> {
|
||||
const updateProcess = spawn(updateCommand, {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
env: process.env,
|
||||
windowsHide: true,
|
||||
})
|
||||
|
||||
return waitForProcessExit(updateProcess)
|
||||
}
|
||||
|
||||
type KanbanInstallCommand = NonNullable<ReturnType<typeof resolveKanbanInstallCommand>>
|
||||
|
||||
async function runKanbanUpdate(installCommand: KanbanInstallCommand): Promise<number> {
|
||||
const updateProcess = spawnKanbanInstallProcess(installCommand, {
|
||||
env: process.env,
|
||||
windowsHide: true,
|
||||
})
|
||||
return waitForProcessExit(updateProcess)
|
||||
}
|
||||
|
||||
function formatUpdateSummaryTargets(targets: string[]): string {
|
||||
if (targets.length === 0) {
|
||||
return ""
|
||||
}
|
||||
if (targets.length === 1) {
|
||||
return targets[0]
|
||||
}
|
||||
if (targets.length === 2) {
|
||||
return `${targets[0]} and ${targets[1]}`
|
||||
}
|
||||
return `${targets.slice(0, -1).join(", ")}, and ${targets.at(-1)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates and install if available (manual command)
|
||||
*/
|
||||
export async function checkForUpdates(currentVersion: string, options?: { verbose?: boolean }) {
|
||||
printInfo("Checking for updates...")
|
||||
export async function checkForUpdates(currentVersion: string, options: CheckForUpdatesOptions = {}) {
|
||||
const includeKanban = options.includeKanban ?? true
|
||||
|
||||
printInfo("Checking for updates to cline and kanban packages...")
|
||||
|
||||
const { updateCommand, packageManager } = getInstallationInfo(currentVersion)
|
||||
|
||||
try {
|
||||
const latestVersion = await getLatestVersion(currentVersion)
|
||||
if (!latestVersion) {
|
||||
printWarning("Failed to check for updates: could not fetch latest version")
|
||||
exit(1)
|
||||
}
|
||||
const latestClineVersion = await getLatestVersion(currentVersion)
|
||||
const canCheckClineVersion = latestClineVersion !== null
|
||||
|
||||
if (options?.verbose) {
|
||||
printInfo(`Current version: ${currentVersion}`)
|
||||
printInfo(`Latest version: ${latestVersion}`)
|
||||
printInfo(`Package manager: ${packageManager}`)
|
||||
}
|
||||
|
||||
// Compare versions
|
||||
if (latestVersion === currentVersion) {
|
||||
printInfo(`You are already on the latest version (${currentVersion})`)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
// Check if current is newer (dev version)
|
||||
if (compareVersions(currentVersion, latestVersion) > 0) {
|
||||
printInfo(`You are already on a newer version ${currentVersion} (latest: ${latestVersion})`)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
printInfo(`New version available: ${latestVersion} (current: ${currentVersion})`)
|
||||
|
||||
if (!updateCommand) {
|
||||
printInfo("Unable to determine update command for your installation.")
|
||||
printInfo("Please update manually using your package manager.")
|
||||
exit(0)
|
||||
}
|
||||
|
||||
// Ask user to confirm update
|
||||
const userConfirmed = new Promise<boolean>((resolve) => {
|
||||
process.stdout.write("Do you want to update now? (y/N): ")
|
||||
process.stdin.setEncoding("utf-8")
|
||||
process.stdin.once("data", (dataBuff) => {
|
||||
const input = dataBuff.toString().trim().toLowerCase()
|
||||
resolve(input === "y" || input === "yes")
|
||||
})
|
||||
})
|
||||
|
||||
if (!(await userConfirmed)) {
|
||||
exit(0)
|
||||
}
|
||||
|
||||
printInfo(`Installing update via ${packageManager}...`)
|
||||
|
||||
const updateProcess = spawn(updateCommand, {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
env: process.env,
|
||||
windowsHide: true,
|
||||
})
|
||||
|
||||
updateProcess.on("close", (code) => {
|
||||
if (code === 0) {
|
||||
printInfo(`Successfully updated to version ${latestVersion}`)
|
||||
exit(0)
|
||||
} else {
|
||||
printWarning(`Update failed. Please try running: ${updateCommand}`)
|
||||
exit(1)
|
||||
if (canCheckClineVersion) {
|
||||
printInfo(`Latest version: ${latestClineVersion}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
updateProcess.on("error", (err) => {
|
||||
printWarning(`Failed to run update: ${err.message}`)
|
||||
printInfo(`Please try running manually: ${updateCommand}`)
|
||||
if (!canCheckClineVersion) {
|
||||
printWarning("Failed to check for Cline updates: could not fetch latest version")
|
||||
}
|
||||
|
||||
const clineComparison = latestClineVersion ? compareVersions(currentVersion, latestClineVersion) : null
|
||||
const clineUpdateAvailable = clineComparison !== null && clineComparison < 0
|
||||
const clineIsUpToDate = clineComparison !== null && clineComparison === 0
|
||||
const canUpdateCline = clineUpdateAvailable && Boolean(updateCommand)
|
||||
|
||||
if (clineUpdateAvailable && latestClineVersion) {
|
||||
printInfo(`New version available: ${latestClineVersion} (current: ${currentVersion})`)
|
||||
}
|
||||
|
||||
if (clineUpdateAvailable && !updateCommand) {
|
||||
printInfo("Unable to determine Cline update command for your installation.")
|
||||
printInfo("Please update Cline manually using your package manager.")
|
||||
}
|
||||
|
||||
const kanbanInstallCommand = includeKanban ? resolveKanbanInstallCommand() : null
|
||||
const kanbanInstallerAvailable = kanbanInstallCommand !== null
|
||||
if (includeKanban && !kanbanInstallerAvailable && options.verbose) {
|
||||
printWarning("Unable to determine Kanban update command (npm, pnpm, or bun not found in PATH).")
|
||||
}
|
||||
const latestKanbanVersion = kanbanInstallerAvailable ? await getLatestKanbanVersion() : null
|
||||
const installedKanbanVersion = includeKanban ? getInstalledKanbanVersion() : null
|
||||
const kanbanIsUpToDate =
|
||||
latestKanbanVersion !== null &&
|
||||
installedKanbanVersion !== null &&
|
||||
compareVersions(installedKanbanVersion, latestKanbanVersion) >= 0
|
||||
const shouldInstallKanban =
|
||||
kanbanInstallerAvailable &&
|
||||
latestKanbanVersion !== null &&
|
||||
(installedKanbanVersion === null || compareVersions(installedKanbanVersion, latestKanbanVersion) < 0)
|
||||
|
||||
if (!canCheckClineVersion && !shouldInstallKanban) {
|
||||
exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
if (!canUpdateCline && !shouldInstallKanban) {
|
||||
if (clineIsUpToDate && kanbanIsUpToDate && installedKanbanVersion) {
|
||||
printInfo(`You are already on the latest version cline@${currentVersion} and kanban@${installedKanbanVersion}`)
|
||||
} else if (clineIsUpToDate) {
|
||||
printInfo(`You are already on the latest version cline@${currentVersion}`)
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
let hadFailure = false
|
||||
const installedUpdates: string[] = []
|
||||
|
||||
if (canUpdateCline && updateCommand && latestClineVersion) {
|
||||
printInfo(`Installing cline@${latestClineVersion}...`)
|
||||
try {
|
||||
const clineUpdateCode = await runClineUpdate(updateCommand)
|
||||
if (clineUpdateCode === 0) {
|
||||
installedUpdates.push(`cline@${latestClineVersion}`)
|
||||
} else {
|
||||
printWarning(`Cline update failed. Please try running: ${updateCommand}`)
|
||||
hadFailure = true
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
printWarning(`Failed to run Cline update: ${message}`)
|
||||
printInfo(`Please try running manually: ${updateCommand}`)
|
||||
hadFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldInstallKanban && kanbanInstallCommand && latestKanbanVersion) {
|
||||
const kanbanTargetVersion = latestKanbanVersion ?? "latest"
|
||||
printInfo(`Installing kanban@${kanbanTargetVersion}...`)
|
||||
try {
|
||||
const kanbanUpdateCode = await runKanbanUpdate(kanbanInstallCommand)
|
||||
if (kanbanUpdateCode === 0) {
|
||||
installedUpdates.push(`kanban@${kanbanTargetVersion}`)
|
||||
} else {
|
||||
printWarning(`Kanban update failed. Please try running: ${kanbanInstallCommand.displayCommand}`)
|
||||
hadFailure = true
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
printWarning(`Failed to run Kanban update: ${message}`)
|
||||
if (kanbanInstallCommand) {
|
||||
printInfo(`Please try running manually: ${kanbanInstallCommand.displayCommand}`)
|
||||
}
|
||||
hadFailure = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hadFailure) {
|
||||
if (installedUpdates.length > 1) {
|
||||
printSuccess(`Installed updates for ${formatUpdateSummaryTargets(installedUpdates)}`)
|
||||
} else if (installedUpdates.length === 1) {
|
||||
printSuccess(`Installed update for ${installedUpdates[0]}`)
|
||||
} else {
|
||||
printInfo("No updates were installed.")
|
||||
}
|
||||
}
|
||||
|
||||
if (hadFailure) {
|
||||
exit(1)
|
||||
}
|
||||
|
||||
if (canUpdateCline || shouldInstallKanban) {
|
||||
exit(0)
|
||||
}
|
||||
exit(1)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
printWarning(`Error checking for updates: ${message}`)
|
||||
@@ -259,7 +387,7 @@ function parseVersion(version: string): ParsedVersion {
|
||||
return {
|
||||
base: nightlyMatch[1].split(".").map(Number),
|
||||
isNightly: true,
|
||||
timestamp: parseInt(nightlyMatch[2], 10),
|
||||
timestamp: Number.parseInt(nightlyMatch[2], 10),
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -69,7 +69,7 @@ cline auth
|
||||
cline auth -p cline -k "YOUR_API_KEY" -m anthropic/claude-sonnet-4-6
|
||||
```
|
||||
|
||||
See the [CLI Reference](/cline-cli/cli-reference#cline-auth) for all auth options.
|
||||
See the [CLI Reference](/cli/cli-reference#cline-auth) for all auth options.
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
|
||||
+4
-29
@@ -21,32 +21,7 @@ For example:
|
||||
|
||||
Pass this string as the `model` parameter in your [Chat Completions](/api/chat-completions) request.
|
||||
|
||||
## Popular Models
|
||||
|
||||
| Model ID | Provider | Context Window | Reasoning | Best For |
|
||||
|----------|----------|---------------|-----------|----------|
|
||||
| `anthropic/claude-sonnet-4-6` | Anthropic | 200K | Yes | General coding, analysis, complex tasks |
|
||||
| `anthropic/claude-sonnet-4-5` | Anthropic | 200K | Yes | Balanced performance and cost |
|
||||
| `openai/gpt-4o` | OpenAI | 128K | No | Multimodal tasks, fast responses |
|
||||
| `google/gemini-2.5-pro` | Google | 1M | Yes | Very long context, document analysis |
|
||||
| `deepseek/deepseek-chat` | DeepSeek | 64K | No | Cost-effective coding tasks |
|
||||
| `x-ai/grok-3` | xAI | 128K | Yes | Reasoning-heavy tasks |
|
||||
|
||||
<Note>
|
||||
Model availability and pricing change over time. Check [app.cline.bot](https://app.cline.bot) for the latest catalog.
|
||||
</Note>
|
||||
|
||||
## Free Models
|
||||
|
||||
These models are available at no cost. They are a good starting point for experimentation and lightweight tasks:
|
||||
|
||||
| Model ID | Provider | Context Window |
|
||||
|----------|----------|---------------|
|
||||
| `minimax/minimax-m2.5` | MiniMax | 1M |
|
||||
| `kwaipilot/kat-coder-pro` | Kwaipilot | 32K |
|
||||
| `z-ai/glm-5` | Z-AI | 128K |
|
||||
|
||||
Free models have the same API interface as paid models. Just use their model ID:
|
||||
Example:
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.cline.bot/api/v1/chat/completions \
|
||||
@@ -79,7 +54,7 @@ Models with reasoning support include most Claude, Gemini 2.5, and Grok 3 models
|
||||
| Multi-modal (text + images) | `openai/gpt-4o` or `anthropic/claude-sonnet-4-6` |
|
||||
| Complex reasoning | Any model with reasoning support |
|
||||
|
||||
For a deeper comparison of model capabilities and pricing, see the [Model Selection Guide](/core-features/model-selection-guide).
|
||||
For setup and account flow details, see the [Cline provider guide](/getting-started/cline-provider).
|
||||
|
||||
## Image Support
|
||||
|
||||
@@ -108,7 +83,7 @@ Not all models support images. Check the model's `supportsImages` capability bef
|
||||
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
|
||||
Use these models in your API requests.
|
||||
</Card>
|
||||
<Card title="Model Selection Guide" icon="scale-balanced" href="/core-features/model-selection-guide">
|
||||
In-depth comparison for choosing the right model.
|
||||
<Card title="Cline provider" icon="scale-balanced" href="/getting-started/cline-provider">
|
||||
Fastest setup path with built-in authentication and billing.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -24,7 +24,7 @@ Your App → Cline API (api.cline.bot) → Anthropic / OpenAI / Google / etc
|
||||
<Card title="Chat Completions" icon="message" href="/api/chat-completions">
|
||||
Full endpoint reference with request schemas, streaming, and tool calling.
|
||||
</Card>
|
||||
<Card title="SDK Examples" icon="code" href="/api/sdk-examples">
|
||||
<Card title="Code Examples" icon="code" href="/api/sdk-examples">
|
||||
Ready-to-copy examples for Python, Node.js, curl, and the Cline CLI.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
---
|
||||
title: "Cline API Reference"
|
||||
sidebarTitle: "API Reference"
|
||||
description: "Reference for the Cline Chat Completions API, an OpenAI-compatible endpoint for programmatic access."
|
||||
---
|
||||
|
||||
The Cline API provides an OpenAI-compatible Chat Completions endpoint. You can use it from the Cline extension, the CLI, or any HTTP client that speaks the OpenAI format.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
https://api.cline.bot/api/v1
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All requests require a Bearer token in the `Authorization` header. You can use either:
|
||||
|
||||
- **API key** created at [app.cline.bot](https://app.cline.bot) (Settings > API Keys)
|
||||
- **Account auth token** (used automatically by the Cline extension and CLI when you sign in)
|
||||
|
||||
```bash
|
||||
Authorization: Bearer YOUR_API_KEY
|
||||
```
|
||||
|
||||
### Getting an API Key
|
||||
|
||||
<Steps>
|
||||
<Step title="Go to app.cline.bot">
|
||||
Open [app.cline.bot](https://app.cline.bot) and sign in.
|
||||
</Step>
|
||||
<Step title="Open Settings > API Keys">
|
||||
Navigate to **Settings**, then **API Keys**.
|
||||
</Step>
|
||||
<Step title="Create and copy your key">
|
||||
Create a new key and copy it. Store it securely. You will not be able to see it again.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Chat Completions
|
||||
|
||||
Create a chat completion with streaming support. This endpoint follows the [OpenAI Chat Completions](https://platform.openai.com/docs/api-reference/chat/create) format.
|
||||
|
||||
### Request
|
||||
|
||||
```
|
||||
POST /chat/completions
|
||||
```
|
||||
|
||||
**Headers:**
|
||||
|
||||
| Header | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `Authorization` | Yes | `Bearer YOUR_API_KEY` |
|
||||
| `Content-Type` | Yes | `application/json` |
|
||||
| `HTTP-Referer` | No | Your application URL |
|
||||
| `X-Title` | No | Your application name |
|
||||
|
||||
**Body parameters:**
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `model` | string | Yes | Model ID in `provider/model` format (e.g., `anthropic/claude-sonnet-4-6`) |
|
||||
| `messages` | array | Yes | Array of message objects with `role` and `content` |
|
||||
| `stream` | boolean | No | Enable SSE streaming (default: `true`) |
|
||||
| `tools` | array | No | Tool definitions in OpenAI function calling format |
|
||||
| `temperature` | number | No | Sampling temperature |
|
||||
|
||||
### Example Request
|
||||
|
||||
```bash
|
||||
curl -X POST https://api.cline.bot/api/v1/chat/completions \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "anthropic/claude-sonnet-4-6",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "Explain what a context window is in 2 sentences."}
|
||||
],
|
||||
"stream": true
|
||||
}'
|
||||
```
|
||||
|
||||
### Response (Streaming)
|
||||
|
||||
When `stream: true`, the response is a series of [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-Sent_Events). Each event contains a JSON chunk:
|
||||
|
||||
```json
|
||||
data: {"id":"gen-abc123","choices":[{"delta":{"content":"A context"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
|
||||
|
||||
data: {"id":"gen-abc123","choices":[{"delta":{"content":" window is"},"index":0}],"model":"anthropic/claude-sonnet-4-6"}
|
||||
|
||||
data: [DONE]
|
||||
```
|
||||
|
||||
The final chunk includes a `usage` object with token counts and cost:
|
||||
|
||||
```json
|
||||
{
|
||||
"usage": {
|
||||
"prompt_tokens": 25,
|
||||
"completion_tokens": 42,
|
||||
"prompt_tokens_details": {
|
||||
"cached_tokens": 0
|
||||
},
|
||||
"cost": 0.000315
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Non-Streaming)
|
||||
|
||||
When `stream: false`, the response is a single JSON object:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "gen-abc123",
|
||||
"model": "anthropic/claude-sonnet-4-6",
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "A context window is the maximum amount of text..."
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 25,
|
||||
"completion_tokens": 42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Models
|
||||
|
||||
Model IDs use the `provider/model-name` format, the same format used by [OpenRouter](https://openrouter.ai). Some examples:
|
||||
|
||||
| Model ID | Description |
|
||||
|----------|-------------|
|
||||
| `anthropic/claude-sonnet-4-6` | Claude Sonnet 4.6 |
|
||||
| `anthropic/claude-sonnet-4-5` | Claude Sonnet 4.5 |
|
||||
| `google/gemini-2.5-pro` | Gemini 2.5 Pro |
|
||||
| `openai/gpt-4o` | GPT-4o |
|
||||
|
||||
### Free Models
|
||||
|
||||
The following models are available at no cost:
|
||||
|
||||
| Model ID | Provider |
|
||||
|----------|----------|
|
||||
| `minimax/minimax-m2.5` | MiniMax |
|
||||
| `kwaipilot/kat-coder-pro` | Kwaipilot |
|
||||
| `z-ai/glm-5` | Z-AI |
|
||||
|
||||
<Note>
|
||||
Model availability and pricing may change. Check [app.cline.bot](https://app.cline.bot) for the latest list.
|
||||
</Note>
|
||||
|
||||
## Error Handling
|
||||
|
||||
Errors follow the OpenAI error format:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": 401,
|
||||
"message": "Invalid API key",
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Common error codes:
|
||||
|
||||
| Code | Meaning |
|
||||
|------|---------|
|
||||
| `401` | Invalid or missing API key |
|
||||
| `402` | Insufficient credits |
|
||||
| `429` | Rate limit exceeded |
|
||||
| `500` | Server error |
|
||||
| `error` (finish_reason) | Mid-stream error from the upstream model provider |
|
||||
|
||||
## Using with Cline
|
||||
|
||||
The easiest way to use the Cline API is through the Cline extension or CLI, which handle authentication and streaming for you.
|
||||
|
||||
### VS Code / JetBrains
|
||||
|
||||
Select **Cline** as your provider in the model picker dropdown. Sign in with your Cline account and your API key is managed automatically.
|
||||
|
||||
### Cline CLI
|
||||
|
||||
Configure the CLI with your API key in one command:
|
||||
|
||||
```bash
|
||||
cline auth -p cline -k "YOUR_API_KEY" -m anthropic/claude-sonnet-4-6
|
||||
```
|
||||
|
||||
Then run tasks normally:
|
||||
|
||||
```bash
|
||||
cline "Write a one-line hello world in Python."
|
||||
```
|
||||
|
||||
See the [CLI Reference](/cline-cli/cli-reference) for all available commands and options.
|
||||
|
||||
## Using with Other Tools
|
||||
|
||||
Because the Cline API is OpenAI-compatible, you can use it with any library or tool that supports custom OpenAI endpoints.
|
||||
|
||||
### Python (OpenAI SDK)
|
||||
|
||||
```python
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI(
|
||||
base_url="https://api.cline.bot/api/v1",
|
||||
api_key="YOUR_API_KEY",
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="anthropic/claude-sonnet-4-6",
|
||||
messages=[{"role": "user", "content": "Hello!"}],
|
||||
)
|
||||
print(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
### Node.js (OpenAI SDK)
|
||||
|
||||
```typescript
|
||||
import OpenAI from "openai"
|
||||
|
||||
const client = new OpenAI({
|
||||
baseURL: "https://api.cline.bot/api/v1",
|
||||
apiKey: "YOUR_API_KEY",
|
||||
})
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
messages: [{ role: "user", content: "Hello!" }],
|
||||
})
|
||||
console.log(response.choices[0].message.content)
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
|
||||
Full command reference for the Cline CLI, including auth setup.
|
||||
</Card>
|
||||
<Card title="Enterprise API" icon="building" href="/enterprise-solutions/api-reference">
|
||||
Admin endpoints for user management, organizations, billing, and API keys.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "SDK Examples"
|
||||
sidebarTitle: "SDK Examples"
|
||||
title: "Code Examples"
|
||||
sidebarTitle: "Code Examples"
|
||||
description: "Use the Cline API from Python, Node.js, curl, the Cline CLI, and the VS Code extension."
|
||||
---
|
||||
|
||||
@@ -214,7 +214,7 @@ console.log(data.choices[0].message.content)
|
||||
|
||||
## Cline CLI
|
||||
|
||||
The [Cline CLI](/cline-cli/cli-reference) is the fastest way to use the Cline API from your terminal. It handles authentication, streaming, and tool execution for you.
|
||||
The [Cline CLI](/cli/cli-reference) is the fastest way to use the Cline API from your terminal. It handles authentication, streaming, and tool execution for you.
|
||||
|
||||
### Setup
|
||||
|
||||
@@ -242,7 +242,7 @@ cline -m google/gemini-2.5-pro "Analyze this codebase."
|
||||
cline -y "Run tests and fix failures."
|
||||
```
|
||||
|
||||
See the [CLI Reference](/cline-cli/cli-reference) for all commands and options.
|
||||
See the [CLI Reference](/cli/cli-reference) for all commands and options.
|
||||
|
||||
## VS Code / JetBrains
|
||||
|
||||
@@ -269,7 +269,7 @@ For setup instructions, see [Installing Cline](/getting-started/installing-cline
|
||||
<Card title="Models" icon="brain" href="/api/models">
|
||||
Browse available models.
|
||||
</Card>
|
||||
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
|
||||
<Card title="CLI Reference" icon="terminal" href="/cli/cli-reference">
|
||||
Complete Cline CLI command reference.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 MiB After Width: | Height: | Size: 8.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.2 MiB After Width: | Height: | Size: 6.1 MiB |
@@ -198,11 +198,11 @@ If Cline can't access files or run commands:
|
||||
## Learn More
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="CLI Overview" icon="terminal" href="/cline-cli/overview">
|
||||
<Card title="CLI Overview" icon="terminal" href="/usage/cli-overview">
|
||||
Learn about Cline CLI's core capabilities and use cases.
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
<Card title="Headless Mode" icon="robot" href="/usage/cli-overview#headless-mode">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "Agent Teams"
|
||||
sidebarTitle: "Agent Teams"
|
||||
description: "Coordinate multiple agents working together on complex tasks from the CLI."
|
||||
---
|
||||
<Warning>
|
||||
This feature currently only applies to Cline SDK, CLI, and Kanban. This feature is not applicable on VSCode and JetBrains Extension for now.
|
||||
</Warning>
|
||||
|
||||
|
||||
Agent teams let you break complex work across multiple agents that coordinate through a shared task board. One agent acts as the coordinator, delegating subtasks to specialist agents.
|
||||
|
||||
## Starting a Team
|
||||
|
||||
```bash
|
||||
cline --team-name auth-sprint "Plan and implement user authentication with tests"
|
||||
```
|
||||
|
||||
The `--team-name` flag enables team mode. The coordinator agent gets additional tools for spawning teammates and delegating tasks.
|
||||
|
||||
## Resuming Team Work
|
||||
|
||||
Team state persists across sessions. Resume where you left off:
|
||||
|
||||
```bash
|
||||
cline --team-name auth-sprint "Continue with incomplete tasks"
|
||||
```
|
||||
|
||||
## Interactive Mode
|
||||
|
||||
In interactive mode, use the `/team` slash command:
|
||||
|
||||
```
|
||||
/team Plan and implement a REST API with tests
|
||||
```
|
||||
|
||||
## Team State
|
||||
|
||||
Team state is stored at `~/.cline/data/teams/[team-name]/` and includes:
|
||||
|
||||
- Task board with current tasks and status
|
||||
- Inter-agent mailbox
|
||||
- Mission log with activity history
|
||||
|
||||
## Disabling Teams
|
||||
|
||||
Teams are enabled by default. Disable them with:
|
||||
|
||||
```bash
|
||||
cline --no-teams "your prompt"
|
||||
```
|
||||
|
||||
## Sub-Agents
|
||||
|
||||
For simpler delegation within a single session (no persistent state), use [sub-agents](/features/subagents). Sub-agents run in parallel for read-only research and return focused reports to the main agent.
|
||||
|
||||
See the [SDK Multi-Agent Teams guide](/sdk/guides/multi-agent-teams) for the programmatic API.
|
||||
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: "CLI Reference"
|
||||
description: "Complete command reference for Cline CLI including all commands, flags, and configuration options."
|
||||
---
|
||||
|
||||
```bash
|
||||
cline --help # Show all commands
|
||||
cline <command> --help # Show help for a specific command
|
||||
```
|
||||
|
||||
## Synopsis
|
||||
|
||||
```bash
|
||||
cline [options] [command] [prompt]
|
||||
```
|
||||
|
||||
## Help Menu (Source of Truth)
|
||||
|
||||
```text
|
||||
Usage: cline [options] [command] [prompt]
|
||||
|
||||
Cline CLI - AI coding assistant in your terminal
|
||||
|
||||
Arguments:
|
||||
prompt Your prompt. Default to start in act mode with auto-approve enabled.
|
||||
|
||||
Options:
|
||||
-V, --version Output the version number
|
||||
-p, --plan Run in plan mode
|
||||
--json Output messages as JSON instead of styled text
|
||||
--auto-approve <boolean> Set tool auto-approval for all tools (default: true)
|
||||
-t, --timeout <seconds> Optional timeout in seconds (default: 0 for no timeout)
|
||||
-m, --model <model-id> Model to use for the session with the selected provider
|
||||
-v, --verbose Show verbose output
|
||||
-c, --cwd <path> Working directory
|
||||
--config <path> Configuration directory (default: ~/.cline/data/settings)
|
||||
--data-dir <path> Use isolated local state at this directory path (default: ~/.cline)
|
||||
--thinking <level> Set reasoning effort level between none|low|medium|high|xhigh (default: medium)
|
||||
--retries <count> Maximum consecutive mistakes (retries) before halting
|
||||
--hooks-dir <path> Directory path to additional hooks for runtime hook injection (default: ~/.cline/hooks)
|
||||
--acp Run in Agent Client Protocol (ACP) mode for editor integration
|
||||
-i, --tui Open the terminal user interface (TUI) for interactive sessions
|
||||
--id <session-id> Resume an existing session by ID
|
||||
-k, --key <api-key> API key override for this run
|
||||
-P, --provider <id> Provider id (default: cline)
|
||||
-s, --system <system-prompt> Override the default system prompt
|
||||
-z, --zen Start a session that runs in the background hub
|
||||
-h, --help display help for command
|
||||
|
||||
Commands:
|
||||
auth [options] [provider] Authenticate a provider and configure what model is used
|
||||
config [options] Show current configuration
|
||||
connect [options] [adapter] Connect to an editor or IDE adapter
|
||||
mcp Manage MCP servers
|
||||
dev Developer tools and utilities
|
||||
doctor Diagnose and fix configuration issues
|
||||
history|h [options] List session history or manage saved sessions
|
||||
hook Handle a hook payload from stdin
|
||||
plugin Manage Cline Plugins
|
||||
schedule Manage scheduled tasks
|
||||
hub Manage the local hub daemon
|
||||
update [options] Check for updates and install if available
|
||||
version Show Cline CLI version number
|
||||
kanban Launch the kanban app and exit
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-V, --version` | Output the version number |
|
||||
| `-p, --plan` | Run in plan mode |
|
||||
| `--json` | Output messages as JSON instead of styled text |
|
||||
| `--auto-approve <boolean>` | Set tool auto-approval for all tools (default: `true`) |
|
||||
| `-t, --timeout <seconds>` | Optional timeout in seconds (default: `0` for no timeout) |
|
||||
| `-m, --model <model-id>` | Model to use for the session with the selected provider |
|
||||
| `-v, --verbose` | Show verbose output |
|
||||
| `-c, --cwd <path>` | Working directory |
|
||||
| `--config <path>` | Configuration directory (default: `~/.cline/data/settings`) |
|
||||
| `--data-dir <path>` | Use isolated local state at this directory path (default: `~/.cline`) |
|
||||
| `--thinking <level>` | Set reasoning effort: `none\|low\|medium\|high\|xhigh` (default `medium`) |
|
||||
| `--retries <count>` | Maximum consecutive mistakes (retries) before halting |
|
||||
| `--hooks-dir <path>` | Directory path to additional hooks for runtime hook injection (default: `~/.cline/hooks`) |
|
||||
| `--acp` | Run in Agent Client Protocol (ACP) mode for editor integration |
|
||||
| `-i, --tui` | Open the terminal user interface (TUI) for interactive sessions |
|
||||
| `--id <session-id>` | Resume an existing session by ID |
|
||||
| `-k, --key <api-key>` | API key override for this run |
|
||||
| `-P, --provider <id>` | Provider id (default: `cline`) |
|
||||
| `-s, --system <system-prompt>` | Override the default system prompt |
|
||||
| `-z, --zen` | Start a session that runs in the background hub |
|
||||
| `-h, --help` | Display help for command |
|
||||
|
||||
## Commands
|
||||
|
||||
### `cline` (default)
|
||||
|
||||
Start a task or enter interactive mode.
|
||||
|
||||
```bash
|
||||
cline
|
||||
cline "your prompt here"
|
||||
cline "Run tests and fix failures"
|
||||
echo "prompt" | cline
|
||||
```
|
||||
|
||||
### `auth [options] [provider]`
|
||||
|
||||
Configure authentication with an AI provider.
|
||||
|
||||
```bash
|
||||
cline auth
|
||||
```
|
||||
|
||||
### `config [options]`
|
||||
|
||||
Show current configuration.
|
||||
|
||||
```bash
|
||||
cline config
|
||||
```
|
||||
|
||||
### `connect [options] [adapter]`
|
||||
|
||||
Connect to messaging platforms. See [Connectors](/cli/connectors).
|
||||
|
||||
```bash
|
||||
cline connect
|
||||
cline connect [adapter]
|
||||
```
|
||||
|
||||
### `mcp`
|
||||
|
||||
Manage MCP servers. See [MCP](/mcp/mcp-overview).
|
||||
|
||||
```bash
|
||||
cline mcp
|
||||
```
|
||||
|
||||
### `dev`
|
||||
|
||||
Developer tools and utilities.
|
||||
|
||||
```bash
|
||||
cline dev
|
||||
```
|
||||
|
||||
### `doctor`
|
||||
|
||||
Diagnose and fix configuration issues.
|
||||
|
||||
```bash
|
||||
cline doctor
|
||||
```
|
||||
|
||||
### `history|h [options]`
|
||||
|
||||
List session history or manage saved sessions.
|
||||
|
||||
```bash
|
||||
cline history
|
||||
cline h
|
||||
```
|
||||
|
||||
### `hook`
|
||||
|
||||
Handle a hook payload from stdin.
|
||||
|
||||
```bash
|
||||
cat payload.json | cline hook
|
||||
```
|
||||
|
||||
### `plugin`
|
||||
|
||||
Manage Cline plugins. Install plugins from npm, git repositories, or local paths. See [Plugins](/customization/plugins) for full details and the plugin manifest format.
|
||||
|
||||
```bash
|
||||
cline plugin install <source> # Install a plugin
|
||||
cline plugin i <source> # Shorthand alias
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--npm` | Treat source as an npm package |
|
||||
| `--git` | Treat source as a git repository |
|
||||
| `--force` | Replace an existing install for the same source |
|
||||
| `--json` | Output result as JSON |
|
||||
| `--cwd <path>` | Install to `<path>/.cline/plugins` instead of the global directory |
|
||||
|
||||
Try it with the [TypeScript Navigation Plugin](https://github.com/cline/typescript-lsp-plugin):
|
||||
|
||||
```bash
|
||||
cline plugin install https://github.com/cline/typescript-lsp-plugin.git
|
||||
```
|
||||
|
||||
### `schedule`
|
||||
|
||||
Manage scheduled agents. See [Scheduling](/cli/scheduling).
|
||||
|
||||
```bash
|
||||
cline schedule
|
||||
```
|
||||
|
||||
### `hub`
|
||||
|
||||
Manage the local hub daemon.
|
||||
|
||||
```bash
|
||||
cline hub
|
||||
```
|
||||
|
||||
### `update [options]`
|
||||
|
||||
Check for updates and install if available.
|
||||
|
||||
```bash
|
||||
cline update
|
||||
```
|
||||
|
||||
### `version`
|
||||
|
||||
Show Cline CLI version number.
|
||||
|
||||
```bash
|
||||
cline version
|
||||
cline -V
|
||||
```
|
||||
|
||||
### `kanban`
|
||||
|
||||
Launch the kanban app and exit.
|
||||
|
||||
```bash
|
||||
cline kanban
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `CLINE_DATA_DIR` | Custom configuration directory (replaces `~/.cline/data/`) |
|
||||
| `CLINE_HUB_ADDRESS` | Override hub address (default: `127.0.0.1:25463`) |
|
||||
| `CLINE_SESSION_BACKEND_MODE` | Force backend mode (`local`, `hub`, `remote`, `auto`) |
|
||||
| `CLINE_SANDBOX_DATA_DIR` | Sandbox session storage directory |
|
||||
| `CLINE_SANDBOX` | Enable sandbox mode |
|
||||
| `CLINE_HOOKS_DIR` | Additional hooks directory |
|
||||
| `CLINE_BUILD_ENV` | Set to `development` for debug features |
|
||||
| `CLINE_DEBUG_PORT_BASE` | Base port for Node.js inspector |
|
||||
| `CLINE_COMMAND_PERMISSIONS` | JSON policy restricting shell commands (see below) |
|
||||
|
||||
### CLINE_COMMAND_PERMISSIONS
|
||||
|
||||
Restrict which shell commands the agent can execute:
|
||||
|
||||
```bash
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *", "sudo *"]}'
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `allow` | `string[]` | Glob patterns for allowed commands. If set, only matching commands are permitted. |
|
||||
| `deny` | `string[]` | Glob patterns for denied commands. Deny rules always take precedence. |
|
||||
| `allowRedirects` | `boolean` | Whether to allow shell redirects (`>`, `>>`, `<`). Default: `false`. |
|
||||
|
||||
## JSON Output Format
|
||||
|
||||
When using `--json`, each message is a JSON object on its own line:
|
||||
|
||||
```json
|
||||
{"type": "say", "text": "I'll create the file now.", "ts": 1760501486669, "say": "text"}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | `"ask"` or `"say"` | Message category |
|
||||
| `text` | `string` | Message content |
|
||||
| `ts` | `number` | Unix timestamp in milliseconds |
|
||||
| `say` | `string` | Subtype when `type` is `"say"` |
|
||||
| `ask` | `string` | Subtype when `type` is `"ask"` |
|
||||
| `reasoning` | `string` | Model reasoning (if available) |
|
||||
| `partial` | `boolean` | `true` while streaming |
|
||||
|
||||
## Configuration Files
|
||||
|
||||
```
|
||||
~/.cline/
|
||||
data/
|
||||
settings/
|
||||
providers.json # API keys and provider config
|
||||
rules/ # Global rules
|
||||
skills/ # Global skills
|
||||
teams/ # Team state
|
||||
sessions/ # Session database (SQLite)
|
||||
logs/
|
||||
hub-daemon.log # Hub logs
|
||||
plugins/ # Global plugins
|
||||
_installed/ # Managed by `cline plugin install`
|
||||
|
||||
.cline/ # Project root
|
||||
rules/ # Project rules
|
||||
skills/ # Project skills
|
||||
hooks/ # Lifecycle hooks
|
||||
plugins/ # Project plugins
|
||||
mcp.json # MCP server config
|
||||
agents.yaml # Agent definitions
|
||||
```
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: "Connectors"
|
||||
sidebarTitle: "Connectors"
|
||||
description: "Connect the CLI to Telegram, Slack, Discord, Google Chat, WhatsApp, etc."
|
||||
---
|
||||
<Warning>
|
||||
This feature currently only applies to Cline CLI.
|
||||
</Warning>
|
||||
|
||||
Connectors let you chat with your agent from messaging platforms. Each incoming message creates or continues an agent session, and the agent's response is sent back to the conversation.
|
||||
|
||||
## Setup Wizard
|
||||
|
||||
Run `cline connect` to open an interactive wizard that guides you through platform selection, credential entry, security configuration, and advanced options (provider, model, system prompt, agent mode).
|
||||
|
||||
```bash
|
||||
cline connect
|
||||
```
|
||||
|
||||
## Supported Platforms
|
||||
|
||||
| Platform | Direct Command | Required Credentials |
|
||||
|----------|---------------|---------------------|
|
||||
| Telegram | `cline connect telegram` | Bot username, bot token |
|
||||
| Slack | `cline connect slack` | Bot token, signing secret, base URL |
|
||||
| Discord | `cline connect discord` | Application ID, bot token, public key, base URL |
|
||||
| Google Chat | `cline connect gchat` | Service account credentials JSON, base URL |
|
||||
| WhatsApp | `cline connect whatsapp` | Phone number ID, access token, app secret, verify token, base URL |
|
||||
| Linear | `cline connect linear` | API key, webhook signing secret, base URL |
|
||||
|
||||
## Telegram
|
||||
|
||||
<Steps>
|
||||
<Step title="Create a Telegram bot">
|
||||
Open Telegram and start a chat with [@BotFather](https://t.me/BotFather). Send `/newbot` and follow the prompts:
|
||||
|
||||
1. Enter a display name (e.g., "Cline")
|
||||
2. Enter a username ending in `bot` (e.g., `cline_myname_bot`). Must be unique across Telegram.
|
||||
3. BotFather responds with your bot token (looks like `7123456789:AAH...`)
|
||||
</Step>
|
||||
|
||||
<Step title="Start the connector">
|
||||
```bash
|
||||
cline connect telegram -m <BOT-USERNAME> -k <BOT-TOKEN>
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Chat with your bot">
|
||||
Open Telegram, search for your bot's username, and send a message. The agent processes it and replies in the chat.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
### Security
|
||||
|
||||
By default, anyone who finds your bot can message it and it will execute tasks on your machine. Lock it down with the `--hook-command` flag.
|
||||
|
||||
<Steps>
|
||||
<Step title="Get your Telegram user ID">
|
||||
Message [@userinfobot](https://t.me/userinfobot) on Telegram. It replies with your user ID immediately.
|
||||
</Step>
|
||||
|
||||
<Step title="Start with access control">
|
||||
Replace `12345` with your actual Telegram user ID:
|
||||
|
||||
```bash
|
||||
cline connect telegram -m <BOT-USERNAME> -k <BOT-TOKEN> \
|
||||
--hook-command 'jq -r ".payload.actor.participantKey" | grep -q "telegram:id:12345" && echo "{\"action\":\"allow\"}" || echo "{\"action\":\"deny\",\"message\":\"unauthorized\"}"'
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
The `--hook-command` receives each incoming message with sender info via stdin. Your script returns `{"action": "allow"}` or `{"action": "deny", "message": "reason"}`. Without `--hook-command`, everything is auto-approved.
|
||||
|
||||
## Slack
|
||||
|
||||
Requires a bot token, signing secret, and public base URL.
|
||||
|
||||
```bash
|
||||
cline connect slack --token <BOT-TOKEN> --signing-secret <SECRET> --base-url <URL>
|
||||
```
|
||||
|
||||
Each Slack thread maps to an agent session, so the agent maintains conversation context within a thread.
|
||||
|
||||
## Discord
|
||||
|
||||
Requires an application ID, bot token, public key, and public base URL.
|
||||
|
||||
```bash
|
||||
cline connect discord --app-id <ID> --token <TOKEN> --public-key <KEY> --base-url <URL>
|
||||
```
|
||||
|
||||
## Google Chat
|
||||
|
||||
Requires a service account credentials JSON file and public base URL.
|
||||
|
||||
```bash
|
||||
cline connect gchat --credentials <JSON> --base-url <URL>
|
||||
```
|
||||
|
||||
## WhatsApp
|
||||
|
||||
Requires a phone number ID, access token, app secret, webhook verify token, and public base URL.
|
||||
|
||||
```bash
|
||||
cline connect whatsapp --phone-id <ID> --token <TOKEN> --app-secret <SECRET> --base-url <URL>
|
||||
```
|
||||
|
||||
## Linear
|
||||
|
||||
Requires an API key, webhook signing secret, and public base URL.
|
||||
|
||||
```bash
|
||||
cline connect linear --api-key <KEY> --signing-secret <SECRET> --base-url <URL>
|
||||
```
|
||||
|
||||
## Managing Connectors
|
||||
|
||||
```bash
|
||||
# Stop all connectors
|
||||
cline connect --stop
|
||||
|
||||
# Stop a specific connector
|
||||
cline connect telegram --stop
|
||||
```
|
||||
|
||||
## Hook Command Protocol
|
||||
|
||||
The `--hook-command` pattern works across all connectors. The script receives a JSON payload via stdin:
|
||||
|
||||
```json
|
||||
{
|
||||
"payload": {
|
||||
"actor": {
|
||||
"participantKey": "telegram:id:12345",
|
||||
"displayName": "User Name"
|
||||
},
|
||||
"message": "The incoming message text"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Return `{"action": "allow"}` or `{"action": "deny", "message": "reason"}`.
|
||||
|
||||
## Running Multiple Connectors
|
||||
|
||||
Multiple connectors can run simultaneously. They all share the same hub:
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
cline connect telegram -m my_bot -k $TELEGRAM_TOKEN
|
||||
|
||||
# Terminal 2
|
||||
cline connect slack --token $SLACK_TOKEN --signing-secret $SECRET --base-url $URL
|
||||
```
|
||||
|
||||
Connectors require the hub. Start it with `cline hub start` if it doesn't auto-start.
|
||||
+8
-11
@@ -7,7 +7,7 @@ Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to
|
||||
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](./github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
|
||||
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/getting-started/installing-cline). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](./github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
|
||||
</Note>
|
||||
|
||||
## The Workflow
|
||||
@@ -32,7 +32,7 @@ Let's configure your repository.
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and understand basic usage
|
||||
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/getting-started/installing-cline) and understand basic usage
|
||||
- **GitHub repository** - With admin access to configure Actions and secrets
|
||||
- **GitHub Actions familiarity** - Basic understanding of workflows and CI/CD
|
||||
- **API provider account** - OpenRouter, Anthropic, or similar with API key
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
|
||||
- name: Install Cline CLI
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
run: npm install -g cline
|
||||
run: npm install -g @cline/cli
|
||||
|
||||
- name: Configure Cline Authentication
|
||||
if: steps.detect.outputs.hit == 'true'
|
||||
@@ -120,7 +120,6 @@ jobs:
|
||||
env:
|
||||
ISSUE_URL: ${{ steps.detect.outputs.issue_url }}
|
||||
COMMENT: ${{ steps.detect.outputs.comment_body }}
|
||||
CLINE_ADDRESS: ${{ env.CLINE_ADDRESS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -231,10 +230,9 @@ nano git-scripts/analyze-issue.sh # or use vim, code, etc.
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Usage: $0 <github-issue-url> [prompt]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -243,9 +241,8 @@ ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
cline --auto-approve true --json "$PROMPT: $ISSUE_URL" | \
|
||||
jq -r 'select(.type == "agent_event" and .event.type == "done") | .event.text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
@@ -283,7 +280,7 @@ GitHub Actions will:
|
||||
1. Detect the `@cline` mention
|
||||
2. Start a Cline CLI instance
|
||||
3. Download the analysis script
|
||||
4. Analyze the issue using act mode with yolo (fully autonomous)
|
||||
4. Analyze the issue using Act mode with auto-approval enabled
|
||||
5. Post Cline's analysis as a new comment
|
||||
|
||||
**Note**: The workflow only triggers on issue comments, not pull request
|
||||
@@ -296,7 +293,7 @@ The workflow (`cline-responder.yml`):
|
||||
1. **Triggers** on issue comments (created or edited)
|
||||
2. **Detects** `@cline` mentions (case-insensitive)
|
||||
3. **Installs** Cline CLI globally using npm
|
||||
4. **Configures** authentication using `cline config set open-router-api-key=...`
|
||||
4. **Configures** authentication using `cline auth --provider openrouter --apikey ...`
|
||||
6. **Downloads** the reusable `analyze-issue.sh` script from the
|
||||
`github-issue-rca` sample
|
||||
7. **Runs** analysis in Cline CLI
|
||||
+21
-62
@@ -6,7 +6,7 @@ description: "Automated GitHub issue analysis using Cline CLI to identify root c
|
||||
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
|
||||
|
||||
<Note>
|
||||
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
|
||||
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/getting-started/installing-cline) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
|
||||
</Note>
|
||||
|
||||
<Frame>
|
||||
@@ -17,7 +17,7 @@ Automated GitHub issue analysis using Cline CLI. This script uses Cline's autono
|
||||
|
||||
This sample assumes you have already:
|
||||
|
||||
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
|
||||
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/getting-started/installing-cline))
|
||||
- **At least one AI model provider** configured (e.g., OpenRouter, Anthropic, OpenAI)
|
||||
- **Basic familiarity** with Cline CLI commands
|
||||
|
||||
@@ -80,24 +80,18 @@ curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/githu
|
||||
# Analyze a GitHub issue using Cline CLI
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Usage: $0 <github-issue-url> [prompt]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
cline --auto-approve true --json "$PROMPT: $ISSUE_URL" | \
|
||||
jq -r 'select(.type == "agent_event" and .event.type == "done") | .event.text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
@@ -133,23 +127,6 @@ Ask specific questions about the issue:
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/456 "What is the security impact?"
|
||||
```
|
||||
|
||||
### Using Specific Cline Instance
|
||||
|
||||
Target a particular Cline instance by address:
|
||||
|
||||
```bash
|
||||
./analyze-issue.sh https://github.com/owner/repo/issues/123 \
|
||||
"What is the root cause of this issue?" \
|
||||
127.0.0.1:46529
|
||||
```
|
||||
|
||||
<Warning>
|
||||
This is useful when:
|
||||
- Running multiple Cline instances
|
||||
- Using a remote Cline server
|
||||
- Testing with specific configurations
|
||||
</Warning>
|
||||
|
||||
<Note>
|
||||
The script will automatically handle everything: fetching the issue, analyzing it with Cline, and displaying the results. The analysis typically takes 30-60 seconds depending on the issue complexity.
|
||||
</Note>
|
||||
@@ -164,10 +141,9 @@ The script validates input and provides usage instructions:
|
||||
|
||||
```bash
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <github-issue-url> [prompt] [address]"
|
||||
echo "Usage: $0 <github-issue-url> [prompt]"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause?'"
|
||||
echo "Example: $0 https://github.com/owner/repo/issues/123 'Analyze security impact' 127.0.0.1:46529"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
@@ -176,7 +152,6 @@ fi
|
||||
- Validates required GitHub issue URL
|
||||
- Shows clear usage examples
|
||||
- Supports optional custom prompt
|
||||
- Supports optional Cline instance address
|
||||
|
||||
### Argument Parsing
|
||||
|
||||
@@ -186,15 +161,12 @@ The script extracts and sets up the arguments:
|
||||
# Gather the args
|
||||
ISSUE_URL="$1"
|
||||
PROMPT="${2:-What is the root cause of this issue?}"
|
||||
if [ -n "$3" ]; then
|
||||
ADDRESS="--address $3"
|
||||
fi
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `ISSUE_URL="$1"` - First argument is always the issue URL
|
||||
- `PROMPT="${2:-...}"` - Second argument is optional, defaults to root cause analysis
|
||||
- `ADDRESS` - Third argument is optional, only set if provided
|
||||
- The SDK CLI runs the task directly, so no address flag is required.
|
||||
|
||||
### The Core Analysis Pipeline
|
||||
|
||||
@@ -202,39 +174,26 @@ This is where the magic happens:
|
||||
|
||||
```bash
|
||||
# Ask Cline for its analysis, showing only the summary
|
||||
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
|
||||
sed -n '/^{/,$p' | \
|
||||
jq -r 'select(.say == "completion_result") | .text' | \
|
||||
cline --auto-approve true --json "$PROMPT: $ISSUE_URL" | \
|
||||
jq -r 'select(.type == "agent_event" and .event.type == "done") | .event.text' | \
|
||||
sed 's/\\n/\n/g'
|
||||
```
|
||||
|
||||
<Accordion title="Pipeline Breakdown: Understanding Each Component">
|
||||
|
||||
**1. `cline -y "$PROMPT: $ISSUE_URL"`**
|
||||
- `-y` enables yolo mode (no user interaction)
|
||||
**1. `cline --auto-approve true --json "$PROMPT: $ISSUE_URL"`**
|
||||
- `cline` is the Cline CLI binary
|
||||
- Act mode is the default for prompt runs
|
||||
- `--auto-approve true` allows tool use without interactive prompts
|
||||
- `--json` emits newline-delimited JSON for parsing
|
||||
- Constructs prompt with issue URL
|
||||
|
||||
**2. `--mode act`**
|
||||
- Enables act mode for active investigation
|
||||
- Allows Cline to use tools (read files, run commands, etc.)
|
||||
|
||||
**3. `$ADDRESS`**
|
||||
- Optional address flag for specific instance
|
||||
- Expands to `--address <ip:port>` if set
|
||||
|
||||
**4. `-F json`**
|
||||
- Outputs in JSON format for parsing
|
||||
|
||||
**5. `sed -n '/^{/,$p'`**
|
||||
- Extracts JSON from output
|
||||
- Skips any non-JSON prefix lines
|
||||
|
||||
**6. `jq -r 'select(.say == "completion_result") | .text'`**
|
||||
- Filters for completion result messages
|
||||
- Extracts the text field
|
||||
**2. `jq -r 'select(.type == "agent_event" and .event.type == "done") | .event.text'`**
|
||||
- Filters for the final agent `done` event
|
||||
- Extracts the final text field
|
||||
- `-r` outputs raw strings (no JSON quotes)
|
||||
|
||||
**7. `sed 's/\\n/\n/g'`**
|
||||
**3. `sed 's/\\n/\n/g'`**
|
||||
- Converts escaped newlines to actual newlines
|
||||
- Makes output readable
|
||||
|
||||
@@ -376,6 +335,6 @@ This pattern can be adapted for many other automation scenarios, from pull reque
|
||||
|
||||
## Related Resources
|
||||
|
||||
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
|
||||
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
|
||||
- [Headless Mode](https://docs.cline.bot/cline-cli/three-core-flows)
|
||||
- [CLI Installation Guide](https://docs.cline.bot/getting-started/installing-cline)
|
||||
- [CLI Reference Documentation](https://docs.cline.bot/cli/cli-reference)
|
||||
- [Headless Mode](https://docs.cline.bot/usage/cli-overview#headless-mode)
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
cache: "npm"
|
||||
|
||||
- name: Install Cline CLI
|
||||
run: npm install -g cline
|
||||
run: npm install -g @cline/cli
|
||||
|
||||
- name: Configure Cline Authentication
|
||||
# Replace 'anthropic' with your provider of choice (openai, openrouter, etc.)
|
||||
@@ -110,7 +110,7 @@ jobs:
|
||||
]
|
||||
}
|
||||
run: |
|
||||
cline --yolo 'You are a GitHub PR reviewer for this repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
|
||||
cline --auto-approve true 'You are a GitHub PR reviewer for this repository. Your goal is to give the PR author helpful feedback and give maintainers the context they need to review efficiently.
|
||||
|
||||
PR: #'"${PR_NUMBER}"'
|
||||
|
||||
@@ -175,11 +175,11 @@ cline auth --provider anthropic --apikey "..."
|
||||
```
|
||||
The `auth` command configures Cline in the CI environment without interactive prompts. You can switch providers (e.g., `openai`, `openrouter`) by changing the flags.
|
||||
|
||||
### Autonomous Mode (`--yolo`)
|
||||
### Autonomous Mode (`--auto-approve true`)
|
||||
```bash
|
||||
cline --yolo '...'
|
||||
cline --auto-approve true '...'
|
||||
```
|
||||
The `--yolo` flag tells Cline to run autonomously, executing commands without waiting for user approval. This is essential for CI/CD workflows.
|
||||
The `--auto-approve true` flag tells Cline to run autonomously, executing approved tools without waiting for interactive confirmation. Prompt runs start in Act mode by default, so CI/CD workflows can perform the requested work immediately.
|
||||
|
||||
### Command Permissions
|
||||
We explicitly restrict what commands Cline can run using `CLINE_COMMAND_PERMISSIONS`. This ensures Cline can only use `gh` and `git` commands relevant to reviewing, preventing any accidental or malicious system modifications.
|
||||
+27
-27
@@ -43,20 +43,20 @@ Use different models for different phases of work. Route simple tasks to cheap m
|
||||
ISSUE_CONTENT=$(gh issue view $(gh issue list -L 1 | awk '{print $1}'))
|
||||
|
||||
# Phase 1: Quick summary with cheap model
|
||||
SUMMARY=$(echo "$ISSUE_CONTENT" | cline -y --config ~/.cline-haiku \
|
||||
SUMMARY=$(echo "$ISSUE_CONTENT" | cline --auto-approve true --config ~/.cline-haiku \
|
||||
"summarize this issue in 2-3 sentences")
|
||||
|
||||
# Phase 2: Detailed plan with expensive model + thinking
|
||||
PLAN=$(echo "$SUMMARY" | cline -y --thinking --config ~/.cline-opus \
|
||||
PLAN=$(echo "$SUMMARY" | cline --auto-approve true --thinking high --config ~/.cline-opus \
|
||||
"create detailed implementation plan with edge cases")
|
||||
|
||||
# Phase 3: Execute with mid-tier model
|
||||
echo "$PLAN" | cline -y --config ~/.cline-sonnet \
|
||||
echo "$PLAN" | cline --auto-approve true --config ~/.cline-sonnet \
|
||||
"implement the plan from above"
|
||||
```
|
||||
|
||||
<Note>
|
||||
Each cline invocation needs to complete before passing output to the next phase. Use shell variables to store intermediate results rather than piping cline commands directly.
|
||||
Each `cline` invocation needs to complete before passing output to the next phase. Use shell variables to store intermediate results rather than piping `cline` commands directly.
|
||||
</Note>
|
||||
|
||||
**Cost impact:**
|
||||
@@ -102,19 +102,19 @@ Get multiple AI perspectives on the same change, then synthesize their feedback.
|
||||
DIFF=$(git show)
|
||||
|
||||
# Review 1: Gemini's perspective
|
||||
echo "$DIFF" | cline -y --config ~/.cline-gemini \
|
||||
echo "$DIFF" | cline --auto-approve true --config ~/.cline-gemini \
|
||||
"review this diff and write your analysis to gemini-review.md"
|
||||
|
||||
# Review 2: Codex's perspective
|
||||
echo "$DIFF" | cline -y --config ~/.cline-codex \
|
||||
echo "$DIFF" | cline --auto-approve true --config ~/.cline-codex \
|
||||
"review this diff and write your analysis to codex-review.md"
|
||||
|
||||
# Review 3: Opus's perspective
|
||||
echo "$DIFF" | cline -y --config ~/.cline-opus \
|
||||
echo "$DIFF" | cline --auto-approve true --config ~/.cline-opus \
|
||||
"review this diff and write your analysis to opus-review.md"
|
||||
|
||||
# Synthesize all reviews into a consensus
|
||||
cat gemini-review.md codex-review.md opus-review.md | cline -y \
|
||||
cat gemini-review.md codex-review.md opus-review.md | cline --auto-approve true \
|
||||
"summarize these 3 reviews and identify: 1) issues all models agree on, 2) issues only one model caught, 3) your final recommendation"
|
||||
```
|
||||
|
||||
@@ -130,19 +130,19 @@ Run reviews in parallel for faster feedback:
|
||||
|
||||
```bash
|
||||
# Run all reviews simultaneously
|
||||
git show | cline -y --config ~/.cline-gemini "review and save to gemini-review.md" &
|
||||
git show | cline -y --config ~/.cline-codex "review and save to codex-review.md" &
|
||||
git show | cline -y --config ~/.cline-opus "review and save to opus-review.md" &
|
||||
git show | cline --auto-approve true --config ~/.cline-gemini "review and save to gemini-review.md" &
|
||||
git show | cline --auto-approve true --config ~/.cline-codex "review and save to codex-review.md" &
|
||||
git show | cline --auto-approve true --config ~/.cline-opus "review and save to opus-review.md" &
|
||||
|
||||
# Wait for all to complete
|
||||
wait
|
||||
|
||||
# Synthesize
|
||||
cat *-review.md | cline -y "create consensus review"
|
||||
cat *-review.md | cline --auto-approve true "create consensus review"
|
||||
```
|
||||
|
||||
<Note>
|
||||
Parallel execution requires managing multiple Cline instances. See [Multi-instance workflows](/cline-cli/three-core-flows#3-multi-instance-run-parallel-agents) for details.
|
||||
Parallel execution requires managing multiple Cline instances. See [Multi-instance workflows](/usage/cli-overview#automation-patterns) for details.
|
||||
</Note>
|
||||
|
||||
## Extended Thinking for Complex Tasks
|
||||
@@ -151,14 +151,14 @@ Use the `--thinking` flag when Cline needs to analyze multiple approaches:
|
||||
|
||||
```bash
|
||||
# Without thinking: Fast but may miss nuances
|
||||
cline -y "refactor this codebase"
|
||||
cline --auto-approve true "refactor this codebase"
|
||||
|
||||
# With thinking: Slower but more thorough
|
||||
cline -y --thinking \
|
||||
cline --auto-approve true --thinking high \
|
||||
"refactor this codebase - consider: performance, maintainability, backward compatibility"
|
||||
```
|
||||
|
||||
The `--thinking` flag allocates 1024 tokens for internal reasoning before Cline responds. Best for:
|
||||
The `--thinking <level>` flag sets reasoning effort. Use `--thinking high` or `--thinking xhigh` when you want the model to spend more effort on complex tradeoffs. Best for:
|
||||
- Architectural decisions
|
||||
- Security analysis
|
||||
- Complex refactoring
|
||||
@@ -178,12 +178,12 @@ The `--thinking` flag allocates 1024 tokens for internal reasoning before Cline
|
||||
|
||||
```bash
|
||||
# Haiku: Quick summary and issue identification
|
||||
gh pr view $PR | cline -y --config ~/.cline-haiku \
|
||||
gh pr view $PR | cline --auto-approve true --config ~/.cline-haiku \
|
||||
"list all issues to fix, output as JSON"
|
||||
|
||||
# Opus with thinking: Deep analysis only if issues found
|
||||
if [ -s issues.json ]; then
|
||||
cline -y --thinking --config ~/.cline-opus \
|
||||
cline --auto-approve true --thinking high --config ~/.cline-opus \
|
||||
"analyze these issues and recommend fixes"
|
||||
fi
|
||||
```
|
||||
@@ -192,31 +192,31 @@ fi
|
||||
|
||||
```bash
|
||||
# Different models have different security perspectives
|
||||
git diff main | cline -y --config ~/.cline-gemini "security review" > gemini-sec.md &
|
||||
git diff main | cline -y --config ~/.cline-opus "security review" > opus-sec.md &
|
||||
git diff main | cline -y --config ~/.cline-codex "security review" > codex-sec.md &
|
||||
git diff main | cline --auto-approve true --config ~/.cline-gemini "security review" > gemini-sec.md &
|
||||
git diff main | cline --auto-approve true --config ~/.cline-opus "security review" > opus-sec.md &
|
||||
git diff main | cline --auto-approve true --config ~/.cline-codex "security review" > codex-sec.md &
|
||||
wait
|
||||
|
||||
# High-priority: Issues all 3 models found
|
||||
cat *-sec.md | cline -y "find security issues all 3 reviews mentioned"
|
||||
cat *-sec.md | cline --auto-approve true "find security issues all 3 reviews mentioned"
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
|
||||
<Card title="CLI Reference" icon="terminal" href="/cli/cli-reference">
|
||||
Complete documentation for --config and --thinking flags
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
<Card title="Headless Mode" icon="robot" href="/usage/cli-overview#headless-mode">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="Model Selection Guide" icon="brain" href="/core-features/model-selection-guide">
|
||||
Compare models and choose the right one for your needs
|
||||
<Card title="Cline provider" icon="brain" href="/getting-started/cline-provider">
|
||||
Fastest built-in model access setup and account workflow
|
||||
</Card>
|
||||
|
||||
<Card title="CI/CD Integration" icon="github" href="/cline-cli/samples/github-integration">
|
||||
<Card title="CI/CD Integration" icon="github" href="/cli/samples/github-integration">
|
||||
Automate GitHub workflows with Cline CLI
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
title: "Scheduling"
|
||||
sidebarTitle: "Scheduling"
|
||||
description: "Run agents on cron schedules for recurring automations like daily summaries and code reviews."
|
||||
---
|
||||
<Warning>
|
||||
This feature currently only applies to Cline SDK, CLI, and Kanban. This feature is not applicable on VSCode and JetBrains Extension for now.
|
||||
</Warning>
|
||||
|
||||
The CLI supports running agents on cron schedules through the hub. Scheduled agents persist across process restarts and run independently of any terminal session.
|
||||
|
||||
## Schedule Wizard
|
||||
|
||||
Run `cline schedule` to open an interactive menu for creating and managing schedules, browsing execution history, and viewing performance statistics.
|
||||
|
||||
```bash
|
||||
cline schedule
|
||||
```
|
||||
|
||||
The wizard provides:
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| Create new schedule | Set up a recurring task with cron timing and prompt |
|
||||
| List schedules | View all schedules with status and next run time |
|
||||
| Upcoming runs | Preview the next 10 scheduled executions |
|
||||
| Active executions | Show currently running tasks |
|
||||
| Trigger now | Immediately run a selected schedule |
|
||||
| Pause / Resume | Suspend or restart a schedule |
|
||||
| Execution history | View past runs with status, duration, tokens, and cost |
|
||||
| Statistics | Success rate, average duration, last failure |
|
||||
| Delete | Remove a schedule |
|
||||
|
||||
## Creating Schedules with Flags
|
||||
|
||||
```bash
|
||||
cline schedule create "PR summary" \
|
||||
--cron "0 9 * * MON-FRI" \
|
||||
--prompt "List all open PRs and their review status" \
|
||||
--workspace /path/to/repo \
|
||||
--model anthropic/claude-sonnet-4-6
|
||||
```
|
||||
|
||||
## Managing Schedules
|
||||
|
||||
```bash
|
||||
cline schedule list
|
||||
cline schedule trigger <schedule-id>
|
||||
cline schedule pause <schedule-id>
|
||||
cline schedule resume <schedule-id>
|
||||
cline schedule delete <schedule-id>
|
||||
cline schedule executions <schedule-id>
|
||||
```
|
||||
|
||||
## Cron Expression Reference
|
||||
|
||||
| Expression | Schedule |
|
||||
|-----------|----------|
|
||||
| `*/5 * * * *` | Every 5 minutes |
|
||||
| `*/15 * * * *` | Every 15 minutes |
|
||||
| `0 * * * *` | Every hour |
|
||||
| `0 */6 * * *` | Every 6 hours |
|
||||
| `0 0 * * *` | Daily at midnight |
|
||||
| `0 9 * * *` | Daily at 9am |
|
||||
| `0 9 * * 1-5` | Every weekday at 9am |
|
||||
| `0 9 * * 1` | Every Monday at 9am |
|
||||
| `0 0 1 * *` | First of every month |
|
||||
|
||||
## Examples
|
||||
|
||||
### Daily Standup Summary
|
||||
|
||||
```bash
|
||||
cline schedule create "Standup prep" \
|
||||
--cron "0 8 * * MON-FRI" \
|
||||
--prompt "Summarize: (1) PRs merged yesterday, (2) PRs currently in review, (3) open issues assigned to team members." \
|
||||
--workspace /path/to/repo
|
||||
```
|
||||
|
||||
### Weekly Dependency Check
|
||||
|
||||
```bash
|
||||
cline schedule create "Dependency check" \
|
||||
--cron "0 10 * * MON" \
|
||||
--prompt "Check for outdated npm dependencies. For any with security vulnerabilities, create a branch with the update and open a PR." \
|
||||
--workspace /path/to/project
|
||||
```
|
||||
|
||||
### Codebase Health Report
|
||||
|
||||
```bash
|
||||
cline schedule create "Code health" \
|
||||
--cron "0 6 * * MON" \
|
||||
--prompt "Analyze the codebase for: (1) files with no test coverage, (2) TODO/FIXME comments older than 30 days, (3) functions longer than 100 lines." \
|
||||
--workspace /path/to/project
|
||||
```
|
||||
|
||||
## Routing Results
|
||||
|
||||
Combine schedules with [connectors](/cli/connectors) to send results to messaging platforms:
|
||||
|
||||
```bash
|
||||
cline connect telegram -m my_bot -k $BOT_TOKEN
|
||||
|
||||
cline schedule create "Morning briefing" \
|
||||
--cron "0 8 * * *" \
|
||||
--prompt "Summarize overnight activity in the repo"
|
||||
```
|
||||
|
||||
Scheduling requires the hub. It starts automatically when you create a schedule.
|
||||
@@ -1,428 +0,0 @@
|
||||
---
|
||||
title: "CLI Reference"
|
||||
description: "Complete command reference for Cline CLI including all commands, flags, and configuration options"
|
||||
---
|
||||
|
||||
This page documents all available commands, flags, and configuration options for Cline CLI. For quick help in your terminal, use:
|
||||
|
||||
```bash
|
||||
cline --help # Show all commands
|
||||
cline task --help # Show task command options
|
||||
cline auth --help # Show auth command options
|
||||
man cline # View the full manual page (if installed)
|
||||
```
|
||||
|
||||
## Synopsis
|
||||
|
||||
```bash
|
||||
cline [prompt] [options]
|
||||
cline <command> [options] [arguments]
|
||||
```
|
||||
|
||||
## Global Options
|
||||
|
||||
These options work with any command:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--config <path>` | Use a custom configuration directory instead of `~/.cline/data/` |
|
||||
| `-c, --cwd <path>` | Set the working directory for the task |
|
||||
| `-v, --verbose` | Show detailed output including model reasoning |
|
||||
| `--help` | Show help for the command |
|
||||
|
||||
## Modes of Operation
|
||||
|
||||
Cline CLI automatically detects the best output mode based on how you invoke it:
|
||||
|
||||
| Mode | When Activated | Description |
|
||||
|------|----------------|-------------|
|
||||
| **Interactive** | `cline` with no args, TTY connected | Rich terminal UI with real-time streaming, keyboard shortcuts, and visual feedback. |
|
||||
| **Task** | `cline "prompt"` with TTY connected | Interactive UI starts immediately with your task. |
|
||||
| **Plain Text** | stdin piped, stdout redirected, or `--yolo`/`--json` flags | Clean text output without UI, suitable for scripting and CI/CD. |
|
||||
|
||||
## Agent Behavior
|
||||
|
||||
Cline operates in two primary modes that control how it approaches tasks:
|
||||
|
||||
| Mode | Description |
|
||||
|------|-------------|
|
||||
| **Act Mode** (default) | Cline actively uses tools to accomplish tasks. It can read files, write code, execute commands, use a headless browser, and more. |
|
||||
| **Plan Mode** | Cline gathers information and creates a detailed plan before implementation. It explores the codebase, asks clarifying questions, and presents a strategy for your approval before switching to Act Mode. |
|
||||
|
||||
Use `-a, --act` or `-p, --plan` flags to explicitly set the mode.
|
||||
|
||||
## Commands
|
||||
|
||||
### cline (default)
|
||||
|
||||
Run Cline without a subcommand to start a task or enter interactive mode.
|
||||
|
||||
```bash
|
||||
# Interactive mode (no arguments)
|
||||
cline
|
||||
|
||||
# Start a task directly
|
||||
cline "your prompt here"
|
||||
|
||||
# Resume the latest task for the current directory
|
||||
cline --continue
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-a, --act` | Start in Act mode (default). Cline executes actions directly. |
|
||||
| `-p, --plan` | Start in Plan mode. Cline analyzes and creates a strategy before acting. |
|
||||
| `-y, --yolo` | YOLO mode: auto-approve all actions, use plain text output, exit when complete. Ideal for CI/CD. |
|
||||
| `-m, --model <id>` | Use a specific model (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`). |
|
||||
| `-i, --images <paths...>` | Include image files with the prompt. |
|
||||
| `--thinking` | Enable extended thinking with a 1024 token budget. |
|
||||
| `--json` | Output messages as JSON (one object per line). Forces plain text mode. |
|
||||
| `--timeout <seconds>` | Maximum execution time before the task is stopped. |
|
||||
| `--continue` | Resume the most recent task from the current working directory. |
|
||||
|
||||
**Mode Behavior:**
|
||||
|
||||
| Invocation | Output Mode | Why |
|
||||
|------------|-------------|-----|
|
||||
| `cline` | Interactive UI | No arguments, TTY connected |
|
||||
| `cline "prompt"` | Interactive UI | TTY connected |
|
||||
| `cline -y "prompt"` | Plain text | YOLO flag forces plain text |
|
||||
| `cline --json "prompt"` | JSON | JSON flag forces plain text |
|
||||
| `cat file \| cline "prompt"` | Plain text | stdin is piped |
|
||||
| `cline "prompt" > out.txt` | Plain text | stdout is redirected |
|
||||
|
||||
---
|
||||
|
||||
### cline task (alias: t)
|
||||
|
||||
Run a task with a prompt. This is equivalent to `cline "prompt"`.
|
||||
|
||||
```bash
|
||||
cline task "Create a REST API endpoint"
|
||||
cline t "Fix the bug in utils.js"
|
||||
```
|
||||
|
||||
**Options:** Same as the default command above.
|
||||
|
||||
---
|
||||
|
||||
### cline auth
|
||||
|
||||
Configure authentication with an AI provider.
|
||||
|
||||
```bash
|
||||
# Interactive wizard
|
||||
cline auth
|
||||
|
||||
# Quick setup with flags
|
||||
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-p, --provider <id>` | Provider ID. See [Supported Providers](#supported-providers) below. |
|
||||
| `-k, --apikey <key>` | API key for the provider. |
|
||||
| `-m, --modelid <id>` | Model ID to use (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`). |
|
||||
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers. |
|
||||
|
||||
**Supported Providers:**
|
||||
|
||||
| Provider ID | Description |
|
||||
|-------------|-------------|
|
||||
| `anthropic` | Anthropic Claude (direct API) |
|
||||
| `openai-native` | OpenAI GPT models |
|
||||
| `openai-codex` | ChatGPT subscription via OAuth |
|
||||
| `openrouter` | OpenRouter (access multiple providers) |
|
||||
| `bedrock` | AWS Bedrock |
|
||||
| `gemini` | Google Gemini |
|
||||
| `xai` | X AI (Grok) |
|
||||
| `cerebras` | Cerebras (fast inference) |
|
||||
| `deepseek` | DeepSeek |
|
||||
| `ollama` | Ollama (local models) |
|
||||
| `lmstudio` | LM Studio (local models) |
|
||||
| `openai` | OpenAI-compatible API (custom base URL) |
|
||||
|
||||
---
|
||||
|
||||
### cline history (alias: h)
|
||||
|
||||
Browse task history with pagination.
|
||||
|
||||
```bash
|
||||
# Show recent tasks (default: 10)
|
||||
cline history
|
||||
|
||||
# Show more tasks
|
||||
cline history -n 20
|
||||
|
||||
# Paginate through history
|
||||
cline history -n 10 -p 2
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `-n, --limit <number>` | Number of tasks to show (default: 10) |
|
||||
| `-p, --page <number>` | Page number, 1-based (default: 1) |
|
||||
|
||||
---
|
||||
|
||||
### cline config
|
||||
|
||||
View and manage configuration settings.
|
||||
|
||||
```bash
|
||||
cline config
|
||||
```
|
||||
|
||||
Opens an interactive configuration view with tabs for:
|
||||
- **Settings** - Global and workspace-specific settings
|
||||
- **Rules** - `.clinerules` files and imported rules
|
||||
- **Workflows** - Available workflows (appear as slash commands)
|
||||
- **Hooks** - Configured hook scripts
|
||||
- **Skills** - Enabled skills
|
||||
|
||||
---
|
||||
|
||||
### cline update
|
||||
|
||||
Check for updates and install the latest version.
|
||||
|
||||
```bash
|
||||
cline update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### cline version
|
||||
|
||||
Show the installed CLI version.
|
||||
|
||||
```bash
|
||||
cline version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### cline dev
|
||||
|
||||
Developer tools for debugging.
|
||||
|
||||
```bash
|
||||
# Open the log file
|
||||
cline dev log
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### CLINE_DIR
|
||||
|
||||
Override the default configuration directory:
|
||||
|
||||
```bash
|
||||
export CLINE_DIR=/path/to/custom/config
|
||||
cline "your task"
|
||||
```
|
||||
|
||||
When set, all Cline data (settings, secrets, task history) is stored in this directory instead of `~/.cline/data/`.
|
||||
|
||||
**Use cases:**
|
||||
- Running isolated Cline instances with different settings
|
||||
- CI/CD environments with custom state directories
|
||||
- Testing configuration changes without affecting your main setup
|
||||
|
||||
### CLINE_COMMAND_PERMISSIONS
|
||||
|
||||
Restrict which shell commands Cline can execute:
|
||||
|
||||
```bash
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
|
||||
```
|
||||
|
||||
**Format:**
|
||||
|
||||
```json
|
||||
{
|
||||
"allow": ["pattern1", "pattern2"],
|
||||
"deny": ["pattern3"],
|
||||
"allowRedirects": true
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `allow` | `string[]` | Glob patterns for allowed commands. If set, **only** matching commands are permitted. |
|
||||
| `deny` | `string[]` | Glob patterns for denied commands. Deny rules **always take precedence** over allow rules. |
|
||||
| `allowRedirects` | `boolean` | Whether to allow shell redirects (`>`, `>>`, `<`). Default: `false`. |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Allow only npm and git commands (deny everything else)
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
|
||||
|
||||
# Allow dev commands but explicitly deny dangerous ones
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
|
||||
|
||||
# Allow file reading with redirects
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
|
||||
```
|
||||
|
||||
**How commands are evaluated:**
|
||||
|
||||
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
|
||||
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
|
||||
3. If redirects are detected and `allowRedirects` is not true, command is denied
|
||||
4. Each segment is validated against deny rules first, then allow rules
|
||||
5. Subshell contents (`$(...)` and `(...)`) are recursively validated
|
||||
6. All segments must pass for the command to be allowed
|
||||
|
||||
## JSON Output Format
|
||||
|
||||
When using `--json`, each message is output as a JSON object (one per line):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "say",
|
||||
"text": "I'll create the file now.",
|
||||
"ts": 1760501486669,
|
||||
"say": "text"
|
||||
}
|
||||
```
|
||||
|
||||
**Required fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | `"ask"` \| `"say"` | Message category |
|
||||
| `text` | `string` | Human-readable message content |
|
||||
| `ts` | `number` | Unix timestamp in milliseconds |
|
||||
|
||||
**Optional fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `say` | `string` | Subtype when `type` is `"say"` (e.g., `"text"`, `"tool"`) |
|
||||
| `ask` | `string` | Subtype when `type` is `"ask"` (e.g., `"tool"`, `"followup"`) |
|
||||
| `reasoning` | `string` | Model reasoning (omitted when empty) |
|
||||
| `partial` | `boolean` | `true` while streaming (omitted when complete) |
|
||||
| `images` | `string[]` | Image URIs (omitted when empty) |
|
||||
| `files` | `string[]` | File paths (omitted when empty) |
|
||||
|
||||
## Configuration Files
|
||||
|
||||
Cline stores all data in `~/.cline/` by default:
|
||||
|
||||
```text
|
||||
~/.cline/
|
||||
├── data/ # Configuration directory
|
||||
│ ├── globalState.json # Global settings
|
||||
│ ├── secrets.json # API keys (stored securely)
|
||||
│ ├── workspace/ # Workspace-specific state
|
||||
│ └── tasks/ # Task history and conversations
|
||||
└── log/ # Debug logs (view with cline dev log)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Interactive Development
|
||||
|
||||
```bash
|
||||
# Start interactive mode
|
||||
cline
|
||||
|
||||
# Start with a task and use interactive UI
|
||||
cline "Help me refactor this codebase"
|
||||
```
|
||||
|
||||
### Direct Task Execution
|
||||
|
||||
```bash
|
||||
# Run a task directly
|
||||
cline "Add error handling to utils.js"
|
||||
|
||||
# Start in Plan mode to review strategy first
|
||||
cline -p "Design a caching layer for the API"
|
||||
|
||||
# Use a specific model
|
||||
cline -m gpt-4o "Explain this code"
|
||||
```
|
||||
|
||||
### Piped Input
|
||||
|
||||
```bash
|
||||
# Pipe file contents
|
||||
cat README.md | cline "Summarize this document"
|
||||
|
||||
# Review git changes
|
||||
git diff | cline "Review these changes"
|
||||
|
||||
# Analyze test output
|
||||
npm test 2>&1 | cline "Fix any failing tests"
|
||||
```
|
||||
|
||||
### Automation and CI/CD
|
||||
|
||||
```bash
|
||||
# YOLO mode for automated workflows
|
||||
cline -y "Run tests and fix failures"
|
||||
|
||||
# JSON output for scripting
|
||||
cline --json "List all TODO comments" | jq '.text'
|
||||
|
||||
# With timeout
|
||||
cline -y --timeout 600 "Run the full test suite"
|
||||
|
||||
# Chain commands
|
||||
git diff | cline -y "explain" | cline -y "write a commit message"
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
```bash
|
||||
# Interactive wizard
|
||||
cline auth
|
||||
|
||||
# Quick setup: Anthropic
|
||||
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
|
||||
|
||||
# Quick setup: OpenAI
|
||||
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
|
||||
|
||||
# Quick setup: OpenRouter
|
||||
cline auth -p openrouter -k sk-or-xxxxx
|
||||
|
||||
# OpenAI-compatible with custom URL
|
||||
cline auth -p openai -k your-key -b https://api.example.com/v1
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
- **Report bugs:** https://github.com/cline/cline/issues
|
||||
- **Discord community:** https://discord.gg/cline
|
||||
- **Documentation:** https://docs.cline.bot
|
||||
|
||||
## See Also
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Installation & Setup" icon="download" href="/cline-cli/installation">
|
||||
Install Cline CLI and configure authentication.
|
||||
</Card>
|
||||
|
||||
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
|
||||
Keyboard shortcuts, slash commands, and file mentions.
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
|
||||
Environment variables and advanced settings.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,320 +0,0 @@
|
||||
---
|
||||
title: "Configuration"
|
||||
description: "Manage Cline CLI settings with cline config, environment variables, and configuration files"
|
||||
---
|
||||
|
||||
Cline CLI provides multiple ways to configure settings, from the interactive `cline config` command to environment variables for automation.
|
||||
|
||||
## The Config Command
|
||||
|
||||
Launch the configuration interface:
|
||||
|
||||
```bash
|
||||
cline config
|
||||
```
|
||||
|
||||
This opens an interactive view with tabs for different configuration categories.
|
||||
|
||||
## Configuration Tabs
|
||||
|
||||
Navigate between tabs using arrow keys.
|
||||
|
||||
### Settings Tab
|
||||
|
||||
View and edit global and workspace-specific settings:
|
||||
|
||||
- **Global State**: Settings that apply across all workspaces
|
||||
- **Workspace State**: Settings specific to the current directory
|
||||
|
||||
### Rules Tab
|
||||
|
||||
Manage Cline rules that guide AI behavior:
|
||||
|
||||
- **`.clinerules` files**: Project-specific rules in your workspace
|
||||
- **Cursor rules**: Import rules from Cursor editor format
|
||||
- **Windsurf rules**: Import rules from Windsurf editor format
|
||||
|
||||
Rules help Cline understand your project's conventions, coding standards, and preferences.
|
||||
|
||||
### Workflows Tab
|
||||
|
||||
View and manage [workflows](/customization/workflows):
|
||||
|
||||
- List available workflows
|
||||
- View workflow definitions
|
||||
- Workflows appear as slash commands in interactive mode
|
||||
|
||||
### Hooks Tab
|
||||
|
||||
Configure [hooks](/customization/hooks) for custom logic integration:
|
||||
|
||||
- Enable/disable hooks globally
|
||||
- View configured hook scripts
|
||||
- Hooks run at key points in Cline's workflow
|
||||
|
||||
<Note>
|
||||
Hooks must be enabled via settings. Use `cline config` to toggle `hooks-enabled`.
|
||||
</Note>
|
||||
|
||||
### Skills Tab
|
||||
|
||||
Manage [skills](/customization/skills) that extend Cline's capabilities:
|
||||
|
||||
- View available skills
|
||||
- Enable/disable specific skills
|
||||
- Skills provide specialized instructions for specific tasks
|
||||
|
||||
## Configuration Directory
|
||||
|
||||
Cline stores configuration in `~/.cline/data/`:
|
||||
|
||||
```text
|
||||
~/.cline/
|
||||
├── data/ # Configuration directory
|
||||
│ ├── globalState.json # Global settings
|
||||
│ ├── secrets.json # API keys (encrypted)
|
||||
│ ├── settings/ # Settings files
|
||||
│ │ └── cline_mcp_settings.json # MCP server configuration
|
||||
│ ├── workspace/ # Workspace-specific state
|
||||
│ └── tasks/ # Task history and data
|
||||
└── log/ # Log files
|
||||
```
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
For debugging, view the log file:
|
||||
|
||||
```bash
|
||||
cline dev log
|
||||
```
|
||||
|
||||
This opens the log file in your default editor.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### CLINE_DIR
|
||||
|
||||
Override the default configuration directory:
|
||||
|
||||
```bash
|
||||
export CLINE_DIR=/custom/path/to/cline
|
||||
cline "your task"
|
||||
```
|
||||
|
||||
When set, all Cline data is stored in this directory instead of `~/.cline/data/`.
|
||||
|
||||
**Use cases:**
|
||||
- Running multiple isolated Cline configurations
|
||||
- Team-shared configurations
|
||||
- CI/CD with custom state directories
|
||||
|
||||
### CLINE_COMMAND_PERMISSIONS
|
||||
|
||||
Restrict which shell commands Cline can execute:
|
||||
|
||||
```bash
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
|
||||
```
|
||||
|
||||
**Format:**
|
||||
|
||||
```json
|
||||
{
|
||||
"allow": ["pattern1", "pattern2"],
|
||||
"deny": ["pattern3"],
|
||||
"allowRedirects": true
|
||||
}
|
||||
```
|
||||
|
||||
**Fields:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `allow` | `string[]` | Glob patterns for allowed commands. If set, only matching commands are permitted. |
|
||||
| `deny` | `string[]` | Glob patterns for denied commands. Deny rules take precedence over allow. |
|
||||
| `allowRedirects` | `boolean` | Whether to allow shell redirects (`>`, `>>`, `<`). Default: `false` |
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Allow only npm and git commands
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
|
||||
|
||||
# Allow dev commands but deny dangerous ones
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *", "node *"], "deny": ["rm -rf *", "sudo *"]}'
|
||||
|
||||
# Allow file operations with redirects
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["cat *", "echo *"], "allowRedirects": true}'
|
||||
```
|
||||
|
||||
<Warning>
|
||||
When `allow` is set, all commands not matching the allow patterns are denied. Use this for security-sensitive environments.
|
||||
</Warning>
|
||||
|
||||
## Using --config Flag
|
||||
|
||||
Run Cline with a custom configuration directory:
|
||||
|
||||
```bash
|
||||
cline --config /path/to/custom/config "your task"
|
||||
```
|
||||
|
||||
This is useful for:
|
||||
- Running isolated Cline instances
|
||||
- Testing different configurations
|
||||
- Separating work and personal setups
|
||||
|
||||
**Example: Multiple configurations**
|
||||
|
||||
```bash
|
||||
# Work configuration
|
||||
cline --config ~/.cline-work "review this PR"
|
||||
|
||||
# Personal projects
|
||||
cline --config ~/.cline-personal "help me with this side project"
|
||||
```
|
||||
|
||||
## MCP Server Configuration
|
||||
|
||||
Cline CLI supports [MCP (Model Context Protocol)](/mcp/mcp-overview) servers, giving you access to external tools and data sources directly from the terminal. The CLI uses the same MCP configuration format as the VS Code extension.
|
||||
|
||||
### Setting Up MCP Servers
|
||||
|
||||
To configure MCP servers for the CLI, create or edit the settings file at:
|
||||
|
||||
```
|
||||
~/.cline/data/settings/cline_mcp_settings.json
|
||||
```
|
||||
|
||||
The file uses the same JSON format as the VS Code extension:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"my-server": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/server.js"],
|
||||
"env": {
|
||||
"API_KEY": "your_api_key"
|
||||
},
|
||||
"alwaysAllow": ["tool1", "tool2"],
|
||||
"disabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For the full configuration reference including STDIO and SSE transport types, see [Adding and Configuring MCP Servers](/mcp/adding-and-configuring-servers).
|
||||
|
||||
<Note>
|
||||
The CLI does not yet have a `/mcp` slash command for managing MCP servers interactively. For now, you'll need to edit the `cline_mcp_settings.json` file directly.
|
||||
</Note>
|
||||
|
||||
### Custom Config Directory
|
||||
|
||||
If you use the `CLINE_DIR` environment variable or `--config` flag, the MCP settings file will be located at `<your-config-dir>/data/settings/cline_mcp_settings.json` instead.
|
||||
|
||||
## Configuration for Local Providers
|
||||
|
||||
### Ollama
|
||||
|
||||
Configure context window size for Ollama:
|
||||
|
||||
```bash
|
||||
# In settings or via config
|
||||
cline config
|
||||
# Navigate to Settings tab, find ollama-api-options-ctx-num
|
||||
```
|
||||
|
||||
Or set via environment:
|
||||
|
||||
```bash
|
||||
# Set context window to 32K tokens
|
||||
cline -m ollama/llama3 "your task"
|
||||
```
|
||||
|
||||
### LM Studio
|
||||
|
||||
Configure max tokens for LM Studio:
|
||||
|
||||
```bash
|
||||
cline config
|
||||
# Navigate to Settings tab, find lm-studio-max-tokens
|
||||
```
|
||||
|
||||
## Importing Configuration
|
||||
|
||||
### From VS Code Extension
|
||||
|
||||
If you use the Cline VS Code extension, the CLI automatically detects and can share some settings. However, the CLI maintains its own configuration for terminal-specific features.
|
||||
|
||||
### From Other CLI Tools
|
||||
|
||||
See [Installation & Setup](/cline-cli/installation#option-3-import-from-existing-tools) for importing configurations from:
|
||||
- Codex CLI
|
||||
- OpenCode
|
||||
|
||||
## Configuration Best Practices
|
||||
|
||||
### For Development
|
||||
|
||||
Use the default configuration with workspace-specific rules:
|
||||
|
||||
```bash
|
||||
# Add project-specific rules
|
||||
echo "Use TypeScript strict mode" > .clinerules/typescript.md
|
||||
```
|
||||
|
||||
### For CI/CD
|
||||
|
||||
Use environment variables and `--yolo` mode:
|
||||
|
||||
```bash
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm test", "npm run build"]}'
|
||||
cline -y "run tests and fix any failures"
|
||||
```
|
||||
|
||||
### For Teams
|
||||
|
||||
Share configuration via version control:
|
||||
|
||||
```bash
|
||||
# Commit .clinerules/ to your repo
|
||||
git add .clinerules/
|
||||
git commit -m "Add Cline rules for team"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Configuration Not Persisting
|
||||
|
||||
1. Check write permissions on `~/.cline/data/`
|
||||
2. Ensure `CLINE_DIR` isn't set to a read-only location
|
||||
3. Verify the config directory exists
|
||||
|
||||
### Environment Variables Not Working
|
||||
|
||||
1. Ensure variables are exported: `export CLINE_DIR=/path`
|
||||
2. Check for typos in variable names
|
||||
3. Verify JSON syntax for `CLINE_COMMAND_PERMISSIONS`
|
||||
|
||||
### Reset Configuration
|
||||
|
||||
To start fresh, remove the configuration directory:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.cline/data/
|
||||
cline auth # Re-authenticate
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
|
||||
Complete command documentation with all flags and options.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,457 +0,0 @@
|
||||
---
|
||||
title: "Getting Started"
|
||||
description: "Run Cline AI coding agents directly in your terminal with an interactive CLI or automated workflows"
|
||||
---
|
||||
|
||||
## What is Cline CLI?
|
||||
|
||||
Cline CLI brings the full power of Cline to your terminal. Whether you prefer an interactive experience or automated workflows for CI/CD pipelines, the CLI adapts to your needs.
|
||||
|
||||
The CLI supports macOS, Linux, and Windows, and works with all the same AI providers as the VS Code extension.
|
||||
|
||||
## Two Ways to Use Cline CLI
|
||||
|
||||
The CLI operates in two distinct modes, automatically selecting the appropriate one based on how you invoke it:
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
Interactive mode is designed for **hands-on development sessions** where you want to collaborate with Cline in real-time. It provides a rich terminal interface that feels like chatting with an AI assistant.
|
||||
|
||||
**When it activates:** Running `cline` without arguments, or when stdin is a TTY (terminal).
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
|
||||
Key features:
|
||||
|
||||
- **Real-time conversation** - Type messages, see Cline's responses, and iterate on tasks
|
||||
- **Visual feedback** - Animated welcome screen, syntax-highlighted code, and progress indicators
|
||||
- **File mentions** with `@` - Reference workspace files with fuzzy search autocomplete
|
||||
- **Slash commands** with `/` - Quick access to `/settings`, `/history`, `/models`, and workflows
|
||||
- **Keyboard shortcuts** - `Tab` to toggle Plan/Act, `Shift+Tab` for auto-approve all
|
||||
- **Session summaries** - See tasks completed, files modified, and token usage on exit
|
||||
- **Settings panel** - Configure providers, models, and features without leaving the CLI
|
||||
|
||||
Interactive mode keeps you in control. You review Cline's plan, approve or modify actions, and guide the conversation.
|
||||
|
||||
[Learn more about interactive mode →](/cline-cli/interactive-mode)
|
||||
|
||||
### Headless Mode (Non-Interactive)
|
||||
|
||||
Headless mode is designed for **automation, scripting, and CI/CD pipelines** where human interaction isn't possible or desired.
|
||||
|
||||
**When it activates:** Using the `-y`/`--yolo` flag, `--json` flag, piping input/output, or when stdin is not a TTY.
|
||||
|
||||
```bash
|
||||
# Headless with auto-approval (YOLO mode)
|
||||
cline -y "Run tests and fix any failures"
|
||||
|
||||
# Headless with JSON output for parsing
|
||||
cline --json "List all TODO comments" | jq '.text'
|
||||
|
||||
# Headless via piped input
|
||||
cat README.md | cline "Summarize this document"
|
||||
|
||||
# Chain multiple headless commands
|
||||
git diff | cline -y "explain these changes" | cline -y "write a commit message"
|
||||
```
|
||||
|
||||
Key features:
|
||||
|
||||
- **No visual interface** - Clean text or JSON output suitable for scripting
|
||||
- **Automatic execution** - With `-y`, Cline approves all actions and runs autonomously
|
||||
- **Process control** - Exits automatically when the task completes
|
||||
- **Piped workflows** - Read from stdin, write to stdout, chain with other commands
|
||||
- **Machine-readable output** - Use `--json` to get structured output for parsing
|
||||
|
||||
<Warning>
|
||||
Headless mode with `-y` gives Cline full autonomy. Run on a clean git branch so you can easily revert changes if needed.
|
||||
</Warning>
|
||||
|
||||
### Mode Detection Summary
|
||||
|
||||
Cline automatically detects which mode to use based on your invocation. This table shows how different command patterns trigger each mode, helping you predict behavior in scripts and interactive sessions.
|
||||
|
||||
| Invocation | Mode | Reason |
|
||||
|------------|------|--------|
|
||||
| `cline` | Interactive | No arguments, TTY connected |
|
||||
| `cline "task"` | Interactive | TTY connected |
|
||||
| `cline -y "task"` | Headless | YOLO flag forces headless |
|
||||
| `cline --json "task"` | Headless | JSON flag forces headless |
|
||||
| `cat file \| cline "task"` | Headless | stdin is piped |
|
||||
| `cline "task" > output.txt` | Headless | stdout is redirected |
|
||||
|
||||
[Learn more about headless mode →](/cline-cli/three-core-flows)
|
||||
|
||||
## Supported Model Providers
|
||||
|
||||
Cline CLI supports all providers available in the VS Code extension:
|
||||
|
||||
- **Anthropic** (Claude)
|
||||
- **OpenAI** (GPT-4o, GPT-4)
|
||||
- **OpenAI Codex** (ChatGPT subscription)
|
||||
- **OpenRouter**
|
||||
- **AWS Bedrock**
|
||||
- **Google Gemini**
|
||||
- **X AI (Grok)**
|
||||
- **Cerebras**
|
||||
- **DeepSeek**
|
||||
- **Ollama** (local models)
|
||||
- **LM Studio** (local models)
|
||||
- **OpenAI Compatible** (any compatible API)
|
||||
|
||||
During setup, authenticate with `cline auth` to configure your preferred provider. [See authentication →](#authenticate)
|
||||
|
||||
## What You Can Build
|
||||
|
||||
### Automated Code Maintenance
|
||||
|
||||
Keep your codebase healthy with automated fixes. Cline scans for issues and applies corrections across multiple files.
|
||||
|
||||
```bash
|
||||
cline -y "Fix all ESLint errors in src/"
|
||||
```
|
||||
Finds and fixes linting violations throughout your source directory.
|
||||
|
||||
```bash
|
||||
cline -y "Update all deprecated React lifecycle methods"
|
||||
```
|
||||
Migrates legacy code patterns to modern equivalents (e.g., `componentWillMount` → `useEffect`).
|
||||
|
||||
```bash
|
||||
cline -y "Update dependencies with known vulnerabilities"
|
||||
```
|
||||
Identifies outdated packages with security issues and updates them to safe versions.
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
Integrate Cline into your continuous integration pipelines for automated code review and documentation.
|
||||
|
||||
```bash
|
||||
git diff origin/main | cline -y "Review these changes for issues"
|
||||
```
|
||||
Pipes your PR diff to Cline for automated code review, catching bugs and style issues before merge.
|
||||
|
||||
```bash
|
||||
git log --oneline v1.0..v1.1 | cline -y "Write release notes"
|
||||
```
|
||||
Generates human-readable release notes from your commit history between two tags.
|
||||
|
||||
```bash
|
||||
cline -y "Run tests and fix failures" --timeout 600
|
||||
```
|
||||
Executes your test suite, analyzes failures, and attempts fixes with a 10-minute timeout.
|
||||
|
||||
### Development Workflows
|
||||
|
||||
From quick edits to complex refactors, Cline adapts to your workflow.
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
Launches interactive mode for exploratory development and back-and-forth collaboration.
|
||||
|
||||
```bash
|
||||
cline "Refactor this function to use async/await"
|
||||
```
|
||||
Executes a focused task directly from the command line with approval prompts at key steps.
|
||||
|
||||
```bash
|
||||
cline "Based on @src/api.ts, add error handling to all endpoints"
|
||||
```
|
||||
Uses file mentions (`@`) to give Cline context about specific files in your workspace.
|
||||
|
||||
### Custom Shell Pipelines
|
||||
|
||||
Chain Cline with other CLI tools to build powerful automation workflows.
|
||||
|
||||
```bash
|
||||
gh pr diff 123 | cline -y "Review this PR"
|
||||
```
|
||||
Fetches a GitHub PR diff and pipes it directly to Cline for review.
|
||||
|
||||
```bash
|
||||
cline --json "List all TODO comments" | jq '.text'
|
||||
```
|
||||
Outputs structured JSON that you can process with tools like `jq` for scripting.
|
||||
|
||||
```bash
|
||||
git diff | cline -y "explain" | cline -y "write a haiku about these changes"
|
||||
```
|
||||
Chains multiple Cline invocations together for creative multi-step workflows.
|
||||
|
||||
## Features at a Glance
|
||||
|
||||
| Feature | Interactive Mode | Non-Interactive Mode |
|
||||
|---------|------------------|----------------------|
|
||||
| Interactive chat | ✓ | - |
|
||||
| File mentions (@) | ✓ | ✓ (inline) |
|
||||
| Slash commands (/) | ✓ | - |
|
||||
| Settings panel | ✓ | `cline config` |
|
||||
| Plan/Act toggle | ✓ (Tab) | `-p` / `-a` flags |
|
||||
| Auto-approve | ✓ (Shift+Tab) | `-y` flag |
|
||||
| Session summary | ✓ | - |
|
||||
| JSON output | - | `--json` |
|
||||
| Piped input | - | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Installation & Setup
|
||||
|
||||
In just a few minutes, you can install the CLI, authenticate with your preferred AI provider, and start running tasks from any directory on your machine.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Cline CLI requires **Node.js version 20 or higher**. We recommend Node.js 22 for the best experience.
|
||||
|
||||
Check your Node.js version:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
If you need to install or update Node.js, visit [nodejs.org](https://nodejs.org) or use a version manager like [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
### Install Cline CLI
|
||||
|
||||
Install globally via npm:
|
||||
|
||||
```bash
|
||||
npm install -g cline
|
||||
```
|
||||
|
||||
Verify the installation:
|
||||
|
||||
```bash
|
||||
cline version
|
||||
```
|
||||
|
||||
<Tip>
|
||||
To install a specific version, use `npm install -g cline@2.0.0`. Check [npm](https://www.npmjs.com/package/cline) for available versions.
|
||||
</Tip>
|
||||
|
||||
### Authenticate
|
||||
|
||||
After installation, run the authentication wizard:
|
||||
|
||||
```bash
|
||||
cline auth
|
||||
```
|
||||
|
||||
This launches an interactive wizard with multiple options. Choose the method that works best for your workflow.
|
||||
|
||||
#### Option 1: Sign in with Cline (Recommended)
|
||||
|
||||
Select **"Sign in with Cline"** to authenticate with your Cline account via OAuth. Your browser opens automatically to complete sign-in.
|
||||
|
||||
#### Option 2: Sign in with ChatGPT Subscription
|
||||
|
||||
If you have a ChatGPT Plus or Pro subscription, select **"Sign in with ChatGPT Subscription"**. This uses OpenAI's Codex OAuth to authenticate with your existing subscription.
|
||||
|
||||
#### Option 3: Import from Existing Tools
|
||||
|
||||
Already using another AI coding CLI? Cline can import your existing configuration:
|
||||
|
||||
- **Import from Codex CLI** - Imports credentials from `~/.codex/auth.json`
|
||||
- **Import from OpenCode** - Imports configuration from `~/.local/share/opencode/auth.json`
|
||||
|
||||
#### Option 4: Bring Your Own API Key
|
||||
|
||||
Select **"Bring your own API key"** to manually configure any supported provider. Or skip the wizard entirely with flags:
|
||||
|
||||
```bash
|
||||
# Anthropic (Claude)
|
||||
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
|
||||
|
||||
# OpenAI
|
||||
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
|
||||
|
||||
# OpenRouter
|
||||
cline auth -p openrouter -k sk-or-xxxxx -m anthropic/claude-sonnet-4-5-20250929
|
||||
|
||||
# OpenAI-compatible provider with custom base URL
|
||||
cline auth -p openai -k your-api-key -b https://api.example.com/v1
|
||||
```
|
||||
|
||||
**Quick Setup Flags:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-p, --provider <id>` | Provider ID (e.g., `anthropic`, `openai-native`, `openrouter`) |
|
||||
| `-k, --apikey <key>` | Your API key |
|
||||
| `-m, --modelid <id>` | Model ID (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`) |
|
||||
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers |
|
||||
|
||||
<Tip>
|
||||
Flags are especially useful for scripting, CI/CD environments, or setting up multiple machines.
|
||||
</Tip>
|
||||
|
||||
#### Supported Providers
|
||||
|
||||
| Provider | Provider ID | Notes |
|
||||
|----------|-------------|-------|
|
||||
| Anthropic | `anthropic` | Direct Claude API access |
|
||||
| OpenAI | `openai-native` | GPT-4o, GPT-4, etc. |
|
||||
| OpenAI Codex | `openai-codex` | ChatGPT subscription OAuth |
|
||||
| OpenRouter | `openrouter` | Access multiple providers |
|
||||
| AWS Bedrock | `bedrock` | Claude via AWS |
|
||||
| Google Gemini | `gemini` | Gemini Pro, etc. |
|
||||
| X AI (Grok) | `xai` | Grok models |
|
||||
| Cerebras | `cerebras` | Fast inference |
|
||||
| DeepSeek | `deepseek` | DeepSeek models |
|
||||
| Ollama | `ollama` | Local models |
|
||||
| LM Studio | `lmstudio` | Local models |
|
||||
| OpenAI Compatible | `openai` | Any OpenAI-compatible API |
|
||||
|
||||
### Verify Your Setup
|
||||
|
||||
Confirm everything is working with a simple test:
|
||||
|
||||
```bash
|
||||
cline "What is 2 + 2?"
|
||||
```
|
||||
|
||||
If Cline responds with an answer, your installation and authentication are complete.
|
||||
|
||||
Check your current configuration:
|
||||
|
||||
```bash
|
||||
cline config
|
||||
```
|
||||
|
||||
### Quick Start
|
||||
|
||||
Now you're ready to use Cline. Choose how you want to work:
|
||||
|
||||
#### Interactive Mode
|
||||
|
||||
Launch the interactive CLI for development:
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
|
||||
You'll see the Cline welcome screen. Type your task and press Enter. Use:
|
||||
- `Tab` to toggle between Plan and Act modes
|
||||
- `Shift+Tab` to enable auto-approve
|
||||
- `/help` for available commands
|
||||
|
||||
[Learn more about interactive mode →](/cline-cli/interactive-mode)
|
||||
|
||||
#### Direct Task Execution
|
||||
|
||||
Run a task directly from your shell:
|
||||
|
||||
```bash
|
||||
cline "Add error handling to utils.js"
|
||||
```
|
||||
|
||||
For non-interactive execution (perfect for scripts and CI/CD):
|
||||
|
||||
```bash
|
||||
cline -y "Run tests and fix any failures"
|
||||
```
|
||||
|
||||
[Learn more about headless mode →](/cline-cli/three-core-flows)
|
||||
|
||||
### Switching Providers
|
||||
|
||||
To change your configured provider at any time:
|
||||
|
||||
```bash
|
||||
cline auth
|
||||
```
|
||||
|
||||
You can also use the settings panel in interactive mode:
|
||||
|
||||
```bash
|
||||
cline
|
||||
# Then type: /settings
|
||||
# Navigate to the API tab
|
||||
```
|
||||
|
||||
### Updating
|
||||
|
||||
Check for updates and install the latest version:
|
||||
|
||||
```bash
|
||||
cline update
|
||||
```
|
||||
|
||||
Or update manually via npm:
|
||||
|
||||
```bash
|
||||
npm update -g cline
|
||||
```
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Command Not Found
|
||||
|
||||
If `cline` is not found after installation:
|
||||
|
||||
1. Ensure npm global bin is in your PATH:
|
||||
```bash
|
||||
npm bin -g
|
||||
```
|
||||
|
||||
2. Add the path to your shell configuration (`.bashrc`, `.zshrc`, etc.):
|
||||
```bash
|
||||
export PATH="$PATH:$(npm bin -g)"
|
||||
```
|
||||
|
||||
3. Restart your terminal or source your shell config.
|
||||
|
||||
#### Permission Errors
|
||||
|
||||
If you get permission errors during installation:
|
||||
|
||||
```bash
|
||||
# Option 1: Use a Node version manager (recommended)
|
||||
# nvm, fnm, or volta handle permissions automatically
|
||||
|
||||
# Option 2: Fix npm permissions
|
||||
# See: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally
|
||||
```
|
||||
|
||||
#### OAuth Flow Issues
|
||||
|
||||
If the browser doesn't open automatically during OAuth:
|
||||
1. Copy the URL from the terminal
|
||||
2. Paste it in your browser manually
|
||||
3. Complete the sign-in flow
|
||||
4. Return to the terminal
|
||||
|
||||
#### API Key Validation
|
||||
|
||||
If your API key is rejected:
|
||||
1. Verify the key is correct and hasn't expired
|
||||
2. Check that you've selected the correct provider
|
||||
3. Ensure your API account has the necessary permissions
|
||||
|
||||
**Provider-specific tips:**
|
||||
- **Anthropic**: Keys start with `sk-ant-`
|
||||
- **OpenAI**: Keys start with `sk-`
|
||||
- **AWS Bedrock**: Requires AWS credentials configured separately. See [AWS Bedrock documentation](/provider-config/aws-bedrock/api-key).
|
||||
|
||||
### Uninstallation
|
||||
|
||||
To remove Cline CLI:
|
||||
|
||||
```bash
|
||||
npm uninstall -g cline
|
||||
```
|
||||
|
||||
To also remove configuration data:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.cline
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[Interactive Mode](/cline-cli/interactive-mode)** - Master the interactive CLI with shortcuts and slash commands
|
||||
- **[Headless Mode](/cline-cli/three-core-flows)** - Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows
|
||||
- **[Configuration](/cline-cli/configuration)** - Configure settings, rules, workflows, and environment variables
|
||||
- **[CLI Reference](/cline-cli/cli-reference)** - Complete command documentation with all flags and options
|
||||
@@ -1,278 +0,0 @@
|
||||
---
|
||||
title: "Installation & Setup"
|
||||
description: "Install Cline CLI on macOS, Linux, or Windows and configure your AI provider"
|
||||
---
|
||||
|
||||
Cline CLI brings the full power of Cline to your terminal. In just a few minutes, you can install the CLI, authenticate with your preferred AI provider, and start running tasks from any directory on your machine.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Cline CLI requires **Node.js version 20 or higher**. We recommend Node.js 22 for the best experience.
|
||||
|
||||
Check your Node.js version:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
If you need to install or update Node.js, visit [nodejs.org](https://nodejs.org) or use a version manager like [nvm](https://github.com/nvm-sh/nvm).
|
||||
|
||||
## Install Cline CLI
|
||||
|
||||
Install globally via npm:
|
||||
|
||||
```bash
|
||||
npm install -g cline
|
||||
```
|
||||
|
||||
Verify the installation:
|
||||
|
||||
```bash
|
||||
cline version
|
||||
```
|
||||
|
||||
<Tip>
|
||||
To install a specific version, use `npm install -g cline@2.0.0`. Check [npm](https://www.npmjs.com/package/cline) for available versions.
|
||||
</Tip>
|
||||
|
||||
## Authenticate
|
||||
|
||||
After installation, run the authentication wizard:
|
||||
|
||||
```bash
|
||||
cline auth
|
||||
```
|
||||
|
||||
This launches an interactive wizard with multiple options. Choose the method that works best for your workflow.
|
||||
|
||||
### Option 1: Sign in with Cline (Recommended)
|
||||
|
||||
Select **"Sign in with Cline"** to authenticate with your Cline account via OAuth. Your browser opens automatically to complete sign-in.
|
||||
|
||||
### Option 2: Sign in with ChatGPT Subscription
|
||||
|
||||
If you have a ChatGPT Plus or Pro subscription, select **"Sign in with ChatGPT Subscription"**. This uses OpenAI's Codex OAuth to authenticate with your existing subscription.
|
||||
|
||||
### Option 3: Import from Existing Tools
|
||||
|
||||
Already using another AI coding CLI? Cline can import your existing configuration:
|
||||
|
||||
- **Import from Codex CLI** - Imports credentials from `~/.codex/auth.json`
|
||||
- **Import from OpenCode** - Imports configuration from `~/.local/share/opencode/auth.json`
|
||||
|
||||
### Option 4: Bring Your Own API Key
|
||||
|
||||
Select **"Bring your own API key"** to manually configure any supported provider. Or skip the wizard entirely with flags:
|
||||
|
||||
```bash
|
||||
# Anthropic (Claude)
|
||||
cline auth -p anthropic -k sk-ant-api-xxxxx -m claude-sonnet-4-5-20250929
|
||||
|
||||
# OpenAI
|
||||
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
|
||||
|
||||
# OpenRouter
|
||||
cline auth -p openrouter -k sk-or-xxxxx -m anthropic/claude-sonnet-4-5-20250929
|
||||
|
||||
# Moonshot
|
||||
cline auth -p moonshot -k sk-xxxxx -m kimi-k2.5
|
||||
|
||||
# OpenAI-compatible provider with custom base URL
|
||||
cline auth -p openai -k your-api-key -b https://api.example.com/v1
|
||||
```
|
||||
|
||||
**Quick Setup Flags:**
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `-p, --provider <id>` | Provider ID (e.g., `anthropic`, `openai-native`, `openrouter`, `moonshot`) |
|
||||
| `-k, --apikey <key>` | Your API key |
|
||||
| `-m, --modelid <id>` | Model ID (e.g., `claude-sonnet-4-5-20250929`, `gpt-4o`) |
|
||||
| `-b, --baseurl <url>` | Base URL for OpenAI-compatible providers |
|
||||
|
||||
<Tip>
|
||||
Flags are especially useful for scripting, CI/CD environments, or setting up multiple machines.
|
||||
</Tip>
|
||||
|
||||
### Supported Providers
|
||||
|
||||
| Provider | Provider ID | Notes |
|
||||
|----------|-------------|-------|
|
||||
| Anthropic | `anthropic` | Direct Claude API access |
|
||||
| OpenAI | `openai-native` | GPT-4o, GPT-4, etc. |
|
||||
| OpenAI Codex | `openai-codex` | ChatGPT subscription OAuth |
|
||||
| OpenRouter | `openrouter` | Access multiple providers |
|
||||
| AWS Bedrock | `bedrock` | Claude via AWS |
|
||||
| Google Gemini | `gemini` | Gemini Pro, etc. |
|
||||
| X AI (Grok) | `xai` | Grok models |
|
||||
| Cerebras | `cerebras` | Fast inference |
|
||||
| DeepSeek | `deepseek` | DeepSeek models |
|
||||
| Moonshot | `moonshot` | Kimi models via Moonshot AI |
|
||||
| Ollama | `ollama` | Local models |
|
||||
| LM Studio | `lmstudio` | Local models |
|
||||
| OpenAI Compatible | `openai` | Any OpenAI-compatible API |
|
||||
|
||||
## Verify Your Setup
|
||||
|
||||
Confirm everything is working with a simple test:
|
||||
|
||||
```bash
|
||||
cline "What is 2 + 2?"
|
||||
```
|
||||
|
||||
If Cline responds with an answer, your installation and authentication are complete.
|
||||
|
||||
Check your current configuration:
|
||||
|
||||
```bash
|
||||
cline config
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
Now you're ready to use Cline. Choose how you want to work:
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
Launch the interactive CLI for development:
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
|
||||
You'll see the Cline welcome screen. Type your task and press Enter. Use:
|
||||
- `Tab` to toggle between Plan and Act modes
|
||||
- `Shift+Tab` to enable auto-approve
|
||||
- `/help` for available commands
|
||||
|
||||
[Learn more about interactive mode →](/cline-cli/interactive-mode)
|
||||
|
||||
### Direct Task Execution
|
||||
|
||||
Run a task directly from your shell:
|
||||
|
||||
```bash
|
||||
cline "Add error handling to utils.js"
|
||||
```
|
||||
|
||||
For non-interactive execution (perfect for scripts and CI/CD):
|
||||
|
||||
```bash
|
||||
cline -y "Run tests and fix any failures"
|
||||
```
|
||||
|
||||
[Learn more about headless mode →](/cline-cli/three-core-flows)
|
||||
|
||||
## Switching Providers
|
||||
|
||||
To change your configured provider at any time:
|
||||
|
||||
```bash
|
||||
cline auth
|
||||
```
|
||||
|
||||
You can also use the settings panel in interactive mode:
|
||||
|
||||
```bash
|
||||
cline
|
||||
# Then type: /settings
|
||||
# Navigate to the API tab
|
||||
```
|
||||
|
||||
## Updating
|
||||
|
||||
Check for updates and install the latest version:
|
||||
|
||||
```bash
|
||||
cline update
|
||||
```
|
||||
|
||||
Or update manually via npm:
|
||||
|
||||
```bash
|
||||
npm update -g cline
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Command Not Found
|
||||
|
||||
If `cline` is not found after installation:
|
||||
|
||||
1. Ensure npm global bin is in your PATH:
|
||||
```bash
|
||||
npm bin -g
|
||||
```
|
||||
|
||||
2. Add the path to your shell configuration (`.bashrc`, `.zshrc`, etc.):
|
||||
```bash
|
||||
export PATH="$PATH:$(npm bin -g)"
|
||||
```
|
||||
|
||||
3. Restart your terminal or source your shell config.
|
||||
|
||||
### Permission Errors
|
||||
|
||||
If you get permission errors during installation:
|
||||
|
||||
```bash
|
||||
# Option 1: Use a Node version manager (recommended)
|
||||
# nvm, fnm, or volta handle permissions automatically
|
||||
|
||||
# Option 2: Fix npm permissions
|
||||
# See: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally
|
||||
```
|
||||
|
||||
### OAuth Flow Issues
|
||||
|
||||
If the browser doesn't open automatically during OAuth:
|
||||
1. Copy the URL from the terminal
|
||||
2. Paste it in your browser manually
|
||||
3. Complete the sign-in flow
|
||||
4. Return to the terminal
|
||||
|
||||
### API Key Validation
|
||||
|
||||
If your API key is rejected:
|
||||
1. Verify the key is correct and hasn't expired
|
||||
2. Check that you've selected the correct provider
|
||||
3. Ensure your API account has the necessary permissions
|
||||
|
||||
**Provider-specific tips:**
|
||||
- **Anthropic**: Keys start with `sk-ant-`
|
||||
- **OpenAI**: Keys start with `sk-`
|
||||
- **AWS Bedrock**: Requires AWS credentials configured separately. See [AWS Bedrock documentation](/provider-config/aws-bedrock/api-key).
|
||||
|
||||
## Uninstallation
|
||||
|
||||
To remove Cline CLI:
|
||||
|
||||
```bash
|
||||
npm uninstall -g cline
|
||||
```
|
||||
|
||||
To also remove configuration data:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.cline
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
|
||||
Master the interactive CLI with shortcuts and slash commands.
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
|
||||
Configure settings, rules, workflows, and environment variables.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Reference" icon="book" href="/cline-cli/cli-reference">
|
||||
Complete command documentation with all flags and options.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,252 +0,0 @@
|
||||
---
|
||||
title: "Interactive Mode"
|
||||
description: "Master the interactive CLI with keyboard shortcuts, slash commands, and file mentions"
|
||||
---
|
||||
|
||||
Interactive mode is the primary way to work with Cline CLI when you want a collaborative, conversational experience. Unlike headless mode (which runs a single task and exits), interactive mode keeps a session open where you can have back-and-forth conversations with Cline, refine your requests, and guide the AI as it works.
|
||||
|
||||
## Why Use Interactive Mode?
|
||||
|
||||
Interactive mode is ideal when you:
|
||||
|
||||
- **Don't know exactly what you need yet** - Explore a codebase, ask questions, and let Cline help you understand the architecture before making changes
|
||||
- **Want to review before acting** - Toggle Plan mode to see Cline's strategy, then switch to Act mode when you're ready
|
||||
- **Need iterative refinement** - Build on previous responses, ask follow-up questions, and guide Cline to the right solution
|
||||
- **Prefer human oversight** - Review each action, approve file changes, and maintain control over what Cline does
|
||||
- **Working on complex tasks** - Multi-step refactoring, debugging sessions, or feature development that requires judgment calls
|
||||
|
||||
For automated workflows, scripts, or CI/CD pipelines, see [headless mode](/cline-cli/overview#headless-mode-non-interactive) instead.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using interactive mode, you need to have Cline CLI installed and authenticated. If you haven't done this yet, follow the [Installation & Setup guide](/cline-cli/installation) first.
|
||||
|
||||
## Launching Interactive Mode
|
||||
|
||||
Start interactive mode by running `cline` without any arguments:
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
|
||||
You'll see an animated welcome screen with the Cline robot. Start typing your task in the input field at the bottom of the screen.
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
Keyboard shortcuts are the primary way to navigate and control the interactive CLI. Since there's no mouse interaction in the terminal, learning these shortcuts will help you work efficiently and switch between modes, manage input, and control your session without breaking your flow.
|
||||
|
||||
### Mode Controls
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Tab` | Toggle between Plan and Act mode |
|
||||
| `Shift+Tab` | Toggle auto-approve all actions |
|
||||
| `Esc` | Exit or cancel current operation |
|
||||
|
||||
### Input Controls
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Enter` | Submit your message |
|
||||
| `↑` / `↓` | Navigate message history |
|
||||
| `Home` / `End` | Move cursor to start/end of line |
|
||||
| `Ctrl+A` | Move cursor to beginning |
|
||||
| `Ctrl+E` | Move cursor to end |
|
||||
| `Ctrl+W` | Delete word before cursor |
|
||||
| `Ctrl+U` | Delete entire line |
|
||||
|
||||
### Session Controls
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| `Ctrl+C` | Exit with session summary |
|
||||
|
||||
## File Mentions with @
|
||||
|
||||
Reference files from your workspace by typing `@` followed by the filename:
|
||||
|
||||
```text
|
||||
@src/utils.ts can you add error handling to this file?
|
||||
```
|
||||
|
||||
As you type after `@`, Cline shows a fuzzy search dropdown of matching files. Use arrow keys to navigate and `Enter` to select.
|
||||
|
||||
<Tip>
|
||||
File search uses ripgrep for fast, fuzzy matching. You can type partial paths like `@utils` to find `src/utils/helpers.ts`.
|
||||
</Tip>
|
||||
|
||||
### Multiple File Mentions
|
||||
|
||||
Include multiple files in a single message:
|
||||
|
||||
```text
|
||||
Compare @src/old-api.ts with @src/new-api.ts and list the breaking changes
|
||||
```
|
||||
|
||||
## Slash Commands
|
||||
|
||||
Type `/` to see available commands. Slash commands provide quick access to settings, history, and workflows.
|
||||
|
||||
### Built-in Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `/settings` | Open the settings panel |
|
||||
| `/models` | Quick model switching |
|
||||
| `/history` | Browse and resume previous tasks |
|
||||
| `/clear` | Start a fresh task (clears current conversation) |
|
||||
| `/help` | Show help and available commands |
|
||||
| `/exit` | Exit the CLI |
|
||||
|
||||
### Workflow Commands
|
||||
|
||||
If you have [workflows](/customization/workflows) configured, they appear as additional slash commands. For example, if you have a workflow named `code-review`, you can invoke it with:
|
||||
|
||||
```text
|
||||
/code-review
|
||||
```
|
||||
|
||||
## Settings Panel
|
||||
|
||||
Access the settings panel with `/settings`. Navigate between tabs using arrow keys.
|
||||
|
||||
| Tab | Description | Settings |
|
||||
|-----|-------------|----------|
|
||||
| **API** | Configure your AI provider and model | Provider selection, model choice, extended thinking toggle, thinking budget |
|
||||
| **Auto-approve** | Control which actions Cline can perform without prompting | Read files, write files, execute commands, browser actions, MCP tools |
|
||||
| **Features** | Toggle Cline capabilities | Hooks, skills, auto-compact, sound notifications |
|
||||
| **Account** | Manage your Cline account | View account status, sign in/out, manage subscription |
|
||||
| **Other** | Additional preferences | Theme preferences, debug options |
|
||||
|
||||
## Plan and Act Modes
|
||||
|
||||
Cline operates in two modes, toggled with `Tab`. These modes work the same way in the CLI as they do in the VS Code extension. For a deeper explanation of how Plan and Act modes work, see the [Plan and Act documentation](/core-workflows/plan-and-act).
|
||||
|
||||
### Plan Mode
|
||||
|
||||
In Plan mode, Cline analyzes your request and creates a strategy before making changes. Use this when:
|
||||
- Exploring a new codebase
|
||||
- Working on complex refactoring
|
||||
- You want to review the approach first
|
||||
|
||||
### Act Mode
|
||||
|
||||
In Act mode, Cline executes tasks directly. Use this when:
|
||||
- You're confident in the task
|
||||
- Making straightforward changes
|
||||
- Running quick operations
|
||||
|
||||
<Tip>
|
||||
Press `Tab` anytime to switch modes. Starting in Plan mode and switching to Act after reviewing is a common workflow.
|
||||
</Tip>
|
||||
|
||||
## Auto-approve Toggle
|
||||
|
||||
Press `Shift+Tab` to toggle auto-approve for all actions. This removes the approval prompts that appear before each action, letting Cline work continuously without interruption.
|
||||
|
||||
### When to Enable Auto-approve
|
||||
|
||||
Auto-approve is useful when:
|
||||
- **You trust the task** - Well-defined tasks where you're confident in the outcome
|
||||
- **Speed matters** - Long-running tasks where constant approvals slow you down
|
||||
- **You're watching anyway** - You can see Cline's work in real-time and can interrupt if needed
|
||||
- **Iterating quickly** - Rapid prototyping where you want to see results fast
|
||||
|
||||
### What Gets Auto-approved
|
||||
|
||||
When enabled, these actions happen without prompting:
|
||||
- File reads
|
||||
- File writes
|
||||
- Command execution
|
||||
- Browser actions
|
||||
- MCP tool calls
|
||||
|
||||
You can also configure granular auto-approve settings (e.g., auto-approve reads but not writes) via `/settings` → Auto-approve tab, or see the [Auto-approve documentation](/features/auto-approve) for more details.
|
||||
|
||||
<Warning>
|
||||
Auto-approve gives Cline full autonomy. Use on a clean git branch so you can easily revert changes if needed. You can always press `Ctrl+C` to stop Cline immediately.
|
||||
</Warning>
|
||||
|
||||
## Session Summary
|
||||
|
||||
When you exit with `Ctrl+C`, Cline displays a session summary showing:
|
||||
- Tasks completed
|
||||
- Files modified
|
||||
- Commands executed
|
||||
- Token usage
|
||||
|
||||
This helps you track what was accomplished during your session.
|
||||
|
||||
## Running Multiple Instances
|
||||
|
||||
By default, all CLI instances share the same settings and state. However, you may want to run isolated instances with separate configurations for scenarios like:
|
||||
|
||||
- **Different models for different tasks** - Use a fast, cheap model for quick questions in one terminal and a more capable model for complex refactoring in another
|
||||
- **Separate work and personal projects** - Keep API keys, rules, and task history isolated between contexts
|
||||
- **Testing configuration changes** - Experiment with new settings without affecting your main setup
|
||||
- **Team vs. individual settings** - Use shared team configuration for work projects and personal preferences for side projects
|
||||
|
||||
To run isolated instances, use the `--config` flag with different directories:
|
||||
|
||||
```bash
|
||||
# Work instance with team configuration
|
||||
cline --config ~/.cline-work
|
||||
|
||||
# Personal instance with different model/provider
|
||||
cline --config ~/.cline-personal
|
||||
|
||||
# Experimental instance for testing new settings
|
||||
cline --config ~/.cline-test
|
||||
```
|
||||
|
||||
Each config directory maintains its own provider settings, API keys, task history, and preferences.
|
||||
|
||||
<Tip>
|
||||
Use terminal multiplexers like tmux or split terminals to run multiple Cline instances in parallel, each working on different parts of your project with different models or settings.
|
||||
</Tip>
|
||||
|
||||
## Tips for Effective Usage
|
||||
|
||||
### Start with Context
|
||||
|
||||
Give Cline context about what you're working on:
|
||||
|
||||
```text
|
||||
I'm building a REST API with Express. The routes are in @src/routes/ and models in @src/models/. Help me add user authentication.
|
||||
```
|
||||
|
||||
### Use Plan Mode for Exploration
|
||||
|
||||
When you're unsure about the best approach:
|
||||
|
||||
```text
|
||||
[Tab to Plan mode]
|
||||
How should I structure the database schema for a multi-tenant SaaS app?
|
||||
```
|
||||
|
||||
### Iterate with Follow-ups
|
||||
|
||||
The interactive CLI maintains conversation context. Build on previous messages:
|
||||
|
||||
```text
|
||||
> Add a login endpoint
|
||||
[Cline creates the endpoint]
|
||||
|
||||
> Now add rate limiting to it
|
||||
[Cline modifies the same endpoint]
|
||||
|
||||
> Add tests for both features
|
||||
[Cline creates test files]
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
|
||||
Explore `cline config` and advanced configuration options.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,246 +0,0 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "Run Cline AI coding agents directly in your terminal with an interactive CLI or automated workflows"
|
||||
---
|
||||
|
||||
## What is Cline CLI?
|
||||
|
||||
Cline CLI brings the full power of Cline to your terminal. Whether you prefer an interactive experience or automated workflows for CI/CD pipelines, the CLI adapts to your needs.
|
||||
|
||||
The CLI supports macOS, Linux, and Windows, and works with all the same AI providers as the VS Code extension.
|
||||
|
||||
<Tip>
|
||||
Ready to get started? Check out the [installation guide](/cline-cli/installation) to install Cline CLI and run your first task.
|
||||
</Tip>
|
||||
|
||||
## Two Ways to Use Cline CLI
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
|
||||
**For hands-on development.** Launch `cline` in your terminal and collaborate with Cline in real-time — chat, review plans, approve actions, and iterate on tasks with a rich visual interface.
|
||||
</Card>
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
**For automation & CI/CD.** Run `cline -y "task"` to let Cline work autonomously — no interaction needed. Pipe input/output, get JSON results, and chain commands in scripts and pipelines.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
||||
The CLI operates in two distinct modes, automatically selecting the appropriate one based on how you invoke it:
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
Interactive mode is designed for **hands-on development sessions** where you want to collaborate with Cline in real-time. It provides a rich terminal interface that feels like chatting with an AI assistant.
|
||||
|
||||
**When it activates:** Running `cline` without arguments, or when stdin is a TTY (terminal).
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
|
||||
Key features:
|
||||
|
||||
- **Real-time conversation** - Type messages, see Cline's responses, and iterate on tasks
|
||||
- **Visual feedback** - Animated welcome screen, syntax-highlighted code, and progress indicators
|
||||
- **File mentions** with `@` - Reference workspace files with fuzzy search autocomplete
|
||||
- **Slash commands** with `/` - Quick access to `/settings`, `/history`, `/models`, and workflows
|
||||
- **Keyboard shortcuts** - `Tab` to toggle Plan/Act, `Shift+Tab` for auto-approve all
|
||||
- **Session summaries** - See tasks completed, files modified, and token usage on exit
|
||||
- **Settings panel** - Configure providers, models, and features without leaving the CLI
|
||||
|
||||
Interactive mode keeps you in control. You review Cline's plan, approve or modify actions, and guide the conversation.
|
||||
|
||||
[Learn more about interactive mode →](/cline-cli/interactive-mode)
|
||||
|
||||
### Headless Mode (Non-Interactive)
|
||||
|
||||
Headless mode is designed for **automation, scripting, and CI/CD pipelines** where human interaction isn't possible or desired.
|
||||
|
||||
**When it activates:** Using the `-y`/`--yolo` flag, `--json` flag, piping input/output, or when stdin is not a TTY.
|
||||
|
||||
```bash
|
||||
# Headless with auto-approval (YOLO mode)
|
||||
cline -y "Run tests and fix any failures"
|
||||
|
||||
# Headless with JSON output for parsing
|
||||
cline --json "List all TODO comments" | jq '.text'
|
||||
|
||||
# Headless via piped input
|
||||
cat README.md | cline "Summarize this document"
|
||||
|
||||
# Chain multiple headless commands
|
||||
git diff | cline -y "explain these changes" | cline -y "write a commit message"
|
||||
```
|
||||
|
||||
Key features:
|
||||
|
||||
- **No visual interface** - Clean text or JSON output suitable for scripting
|
||||
- **Automatic execution** - With `-y`, Cline approves all actions and runs autonomously
|
||||
- **Process control** - Exits automatically when the task completes
|
||||
- **Piped workflows** - Read from stdin, write to stdout, chain with other commands
|
||||
- **Machine-readable output** - Use `--json` to get structured output for parsing
|
||||
|
||||
<Warning>
|
||||
Headless mode with `-y` gives Cline full autonomy. Run on a clean git branch so you can easily revert changes if needed.
|
||||
</Warning>
|
||||
|
||||
### Mode Detection Summary
|
||||
|
||||
Cline automatically detects which mode to use based on your invocation. This table shows how different command patterns trigger each mode, helping you predict behavior in scripts and interactive sessions.
|
||||
|
||||
| Invocation | Mode | Reason |
|
||||
|------------|------|--------|
|
||||
| `cline` | Interactive | No arguments, TTY connected |
|
||||
| `cline "task"` | Interactive | TTY connected |
|
||||
| `cline -y "task"` | Headless | YOLO flag forces headless |
|
||||
| `cline --json "task"` | Headless | JSON flag forces headless |
|
||||
| `cat file \| cline "task"` | Headless | stdin is piped |
|
||||
| `cline "task" > output.txt` | Headless | stdout is redirected |
|
||||
|
||||
[Learn more about headless mode →](/cline-cli/three-core-flows)
|
||||
|
||||
## Supported Model Providers
|
||||
|
||||
Cline CLI supports all providers available in the VS Code extension:
|
||||
|
||||
- **Anthropic** (Claude)
|
||||
- **OpenAI** (GPT-4o, GPT-4)
|
||||
- **OpenAI Codex** (ChatGPT subscription)
|
||||
- **OpenRouter**
|
||||
- **AWS Bedrock**
|
||||
- **Google Gemini**
|
||||
- **X AI (Grok)**
|
||||
- **Cerebras**
|
||||
- **DeepSeek**
|
||||
- **Ollama** (local models)
|
||||
- **LM Studio** (local models)
|
||||
- **OpenAI Compatible** (any compatible API)
|
||||
|
||||
During setup, authenticate with `cline auth` to configure your preferred provider. [See setup guide →](/cline-cli/installation#authenticate)
|
||||
|
||||
## What You Can Build
|
||||
|
||||
### Automated Code Maintenance
|
||||
|
||||
Keep your codebase healthy with automated fixes. Cline scans for issues and applies corrections across multiple files.
|
||||
|
||||
```bash
|
||||
cline -y "Fix all ESLint errors in src/"
|
||||
```
|
||||
Finds and fixes linting violations throughout your source directory.
|
||||
|
||||
```bash
|
||||
cline -y "Update all deprecated React lifecycle methods"
|
||||
```
|
||||
Migrates legacy code patterns to modern equivalents (e.g., `componentWillMount` → `useEffect`).
|
||||
|
||||
```bash
|
||||
cline -y "Update dependencies with known vulnerabilities"
|
||||
```
|
||||
Identifies outdated packages with security issues and updates them to safe versions.
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
Integrate Cline into your continuous integration pipelines for automated code review and documentation.
|
||||
|
||||
```bash
|
||||
git diff origin/main | cline -y "Review these changes for issues"
|
||||
```
|
||||
Pipes your PR diff to Cline for automated code review, catching bugs and style issues before merge.
|
||||
|
||||
```bash
|
||||
git log --oneline v1.0..v1.1 | cline -y "Write release notes"
|
||||
```
|
||||
Generates human-readable release notes from your commit history between two tags.
|
||||
|
||||
```bash
|
||||
cline -y "Run tests and fix failures" --timeout 600
|
||||
```
|
||||
Executes your test suite, analyzes failures, and attempts fixes with a 10-minute timeout.
|
||||
|
||||
### Development Workflows
|
||||
|
||||
From quick edits to complex refactors, Cline adapts to your workflow.
|
||||
|
||||
```bash
|
||||
cline
|
||||
```
|
||||
Launches interactive mode for exploratory development and back-and-forth collaboration.
|
||||
|
||||
```bash
|
||||
cline "Refactor this function to use async/await"
|
||||
```
|
||||
Executes a focused task directly from the command line with approval prompts at key steps.
|
||||
|
||||
```bash
|
||||
cline "Based on @src/api.ts, add error handling to all endpoints"
|
||||
```
|
||||
Uses file mentions (`@`) to give Cline context about specific files in your workspace.
|
||||
|
||||
### Custom Shell Pipelines
|
||||
|
||||
Chain Cline with other CLI tools to build powerful automation workflows.
|
||||
|
||||
```bash
|
||||
gh pr diff 123 | cline -y "Review this PR"
|
||||
```
|
||||
Fetches a GitHub PR diff and pipes it directly to Cline for review.
|
||||
|
||||
```bash
|
||||
cline --json "List all TODO comments" | jq '.text'
|
||||
```
|
||||
Outputs structured JSON that you can process with tools like `jq` for scripting.
|
||||
|
||||
```bash
|
||||
git diff | cline -y "explain" | cline -y "write a haiku about these changes"
|
||||
```
|
||||
Chains multiple Cline invocations together for creative multi-step workflows.
|
||||
|
||||
## Features at a Glance
|
||||
|
||||
| Feature | Interactive Mode | Non-Interactive Mode |
|
||||
|---------|------------------|----------------------|
|
||||
| Interactive chat | ✓ | - |
|
||||
| File mentions (@) | ✓ | ✓ (inline) |
|
||||
| Slash commands (/) | ✓ | - |
|
||||
| Settings panel | ✓ | `cline config` |
|
||||
| Plan/Act toggle | ✓ (Tab) | `-p` / `-a` flags |
|
||||
| Auto-approve | ✓ (Shift+Tab) | `-y` flag |
|
||||
| Session summary | ✓ | - |
|
||||
| JSON output | - | `--json` |
|
||||
| Piped input | - | ✓ |
|
||||
| [MCP servers](/cline-cli/configuration#mcp-server-configuration) | ✓ | ✓ |
|
||||
|
||||
## MCP Server Support
|
||||
|
||||
Cline CLI supports [MCP (Model Context Protocol)](/mcp/mcp-overview) servers, the same extensibility system available in the VS Code extension. MCP servers give Cline access to external tools and data sources, from databases and APIs to browser automation and project management.
|
||||
|
||||
To use MCP servers with the CLI, add your server configuration to `~/.cline/data/settings/cline_mcp_settings.json`. The format is identical to the VS Code extension.
|
||||
|
||||
[Configure MCP servers for the CLI →](/cline-cli/configuration#mcp-server-configuration)
|
||||
|
||||
## Learn More
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Installation & Setup" icon="download" href="/cline-cli/installation">
|
||||
Install Cline CLI and authenticate with your preferred provider.
|
||||
</Card>
|
||||
|
||||
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
|
||||
Master the interactive CLI with keyboard shortcuts and slash commands.
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
|
||||
Configure settings, rules, workflows, and environment variables.
|
||||
</Card>
|
||||
|
||||
<Card title="Use in Other Editors" icon="code" href="/cline-cli/acp-editor-integrations">
|
||||
Run Cline as an ACP agent in JetBrains, Neovim, Zed, and more.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Samples" icon="flask" href="/cline-cli/samples/overview">
|
||||
Real-world examples of headless workflows and automation patterns.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
title: "Samples Overview"
|
||||
description: Example implementations demonstrating Cline CLI capabilities
|
||||
---
|
||||
|
||||
This section provides sample implementations that demonstrate various Cline CLI features and capabilities. Each sample includes complete code, detailed explanations, and real-world usage examples.
|
||||
|
||||
## Available Samples
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card
|
||||
title="Model Orchestration"
|
||||
icon="layer-group"
|
||||
href="/cline-cli/samples/model-orchestration"
|
||||
>
|
||||
Use multiple AI models strategically with --config and --thinking flags. Optimize costs by routing simple tasks to cheap models and complex reasoning to premium models. Includes patterns for CI/CD code review, task phase optimization, and multi-model consensus.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Worktree Workflows"
|
||||
icon="code-branch"
|
||||
href="/cline-cli/samples/worktree-workflows"
|
||||
>
|
||||
Use Git worktrees with the --cwd flag to run parallel tasks, test different approaches, and pipe context between isolated environments. Includes patterns for parallel execution, cross-worktree piping, and combining with model orchestration.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="GitHub Root Cause Analysis"
|
||||
icon="magnifying-glass-chart"
|
||||
href="/cline-cli/samples/github-issue-rca"
|
||||
>
|
||||
A command-line script that uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues. Features JSON output parsing and non-interactive execution.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="GitHub Integration (Actions)"
|
||||
icon="github"
|
||||
href="/cline-cli/samples/github-integration"
|
||||
>
|
||||
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="GitHub PR Review (Actions)"
|
||||
icon="code-pull-request"
|
||||
href="/cline-cli/samples/github-pr-review"
|
||||
>
|
||||
Automatically review Pull Requests with AI. Configures Cline in GitHub Actions to analyze diffs, check for security issues, and post detailed reviews with inline code suggestions.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [CLI Installation Guide](/cline-cli/installation)
|
||||
- [CLI Reference Documentation](/cline-cli/cli-reference)
|
||||
- [Headless Mode](/cline-cli/three-core-flows)
|
||||
@@ -1,273 +0,0 @@
|
||||
---
|
||||
title: "Worktree Workflows"
|
||||
description: "Use Git worktrees with Cline CLI to run parallel tasks, test different approaches, and pipe context between isolated environments"
|
||||
---
|
||||
|
||||
Git worktrees let you have multiple branches checked out simultaneously in different folders. Combined with Cline CLI's `--cwd` flag, this enables powerful parallel development workflows and isolated experimentation.
|
||||
|
||||
<Tip>
|
||||
New to Git worktrees? See our comprehensive [Worktrees guide](/features/worktrees) for the full concept explanation, VS Code integration, and best practices.
|
||||
</Tip>
|
||||
|
||||
## Quick Worktree Setup
|
||||
|
||||
If you haven't used Git worktrees before, here's the essentials:
|
||||
|
||||
```bash
|
||||
# Create a new worktree in ~/worktree-a on branch feature-a
|
||||
git worktree add ~/worktree-a -b feature-a
|
||||
|
||||
# Create another worktree for a different feature
|
||||
git worktree add ~/worktree-b -b feature-b
|
||||
|
||||
# List all worktrees
|
||||
git worktree list
|
||||
|
||||
# Remove a worktree when done
|
||||
git worktree remove ~/worktree-a
|
||||
```
|
||||
|
||||
Each worktree is a separate folder with its own branch checked out. They all share the same Git history and `.git` directory, but have independent working directories.
|
||||
|
||||
## The `--cwd` Flag
|
||||
|
||||
The `-c, --cwd <path>` flag tells Cline to run in a specific directory without changing your current location:
|
||||
|
||||
```bash
|
||||
# Run Cline in a different directory
|
||||
cline --cwd ~/worktree-a -y "refactor the authentication code"
|
||||
|
||||
# Short form
|
||||
cline -c ~/worktree-b -y "add unit tests"
|
||||
```
|
||||
|
||||
This is the key to worktree workflows—you can run multiple Cline instances in different worktrees simultaneously from a single terminal.
|
||||
|
||||
## Pattern 1: Parallel Task Execution
|
||||
|
||||
Run different tasks in parallel across multiple worktrees. Each task works on a separate branch in complete isolation.
|
||||
|
||||
### Example: Parallel Feature Development
|
||||
|
||||
```bash
|
||||
# Terminal 1: Update docs in worktree-a
|
||||
cline --cwd ~/worktree-a -y "read the last 10 changes using git show and update our README with them" &
|
||||
|
||||
# Terminal 2: TypeScript migration in worktree-b
|
||||
cline --cwd ~/worktree-b -y "update the index.js to use typescript" &
|
||||
|
||||
# Terminal 3: Refactoring in worktree-c
|
||||
cline --cwd ~/worktree-c -y "refactor the cli/ folder to be more modular" &
|
||||
|
||||
# Wait for all to complete
|
||||
wait
|
||||
```
|
||||
|
||||
The `&` runs each command in the background, allowing all three to execute simultaneously.
|
||||
|
||||
### When to Use Parallel Execution
|
||||
|
||||
**Perfect for:**
|
||||
- Multiple independent features
|
||||
- Bulk refactoring across different modules
|
||||
- Running tests in one worktree while developing in another
|
||||
- Trying multiple approaches to the same problem
|
||||
|
||||
**Not ideal for:**
|
||||
- Tasks that modify the same files (merge conflicts likely)
|
||||
- Tasks that depend on each other's results
|
||||
- When you need to monitor progress closely
|
||||
|
||||
## Pattern 2: Cross-Worktree Context Piping
|
||||
|
||||
Pipe output from one worktree as input to another. Use when a task in one worktree needs context from attempts in another worktree.
|
||||
|
||||
### Example: Learning from Failures
|
||||
|
||||
```bash
|
||||
# Try approach A in worktree-a, capture only the failure summary
|
||||
cline --cwd ~/worktree-a -y \
|
||||
"edit the index.ts to be better and then npm run. if it fails, output ONLY the failure summary. nothing else but the failure summary" \
|
||||
| cline --cwd ~/worktree-b -y \
|
||||
"i've tried to edit the index.ts in a different worktree but it failed. use a different approach for this work tree"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
1. First Cline instance runs in `worktree-a`, attempts a change, tests it
|
||||
2. If it fails, outputs just the failure summary
|
||||
3. That summary is piped to a second Cline instance in `worktree-b`
|
||||
4. Second instance sees the failure and tries a different approach
|
||||
|
||||
### When to Use Context Piping
|
||||
|
||||
**Perfect for:**
|
||||
- A/B testing different solutions
|
||||
- Learning from failed attempts
|
||||
- Iterative refinement (try → analyze → try differently)
|
||||
- Comparing outputs across approaches
|
||||
|
||||
**Not ideal for:**
|
||||
- Simple tasks that don't need cross-context
|
||||
- When both worktrees would succeed independently
|
||||
- Real-time collaboration (use parallel execution instead)
|
||||
|
||||
## Combining with Other CLI Features
|
||||
|
||||
### Different Models Per Worktree
|
||||
|
||||
Use `--config` to run different models in different worktrees:
|
||||
|
||||
```bash
|
||||
# Cheap model for simple docs update
|
||||
cline --cwd ~/worktree-docs --config ~/.cline-haiku -y \
|
||||
"update README with latest changes"
|
||||
|
||||
# Expensive model for complex refactoring
|
||||
cline --cwd ~/worktree-refactor --config ~/.cline-opus --thinking -y \
|
||||
"refactor authentication system for better security"
|
||||
```
|
||||
|
||||
This optimizes costs while maintaining quality where it matters.
|
||||
|
||||
### Task Isolation
|
||||
|
||||
Keep long-running worktree sessions isolated by running each task against a different worktree path:
|
||||
|
||||
```bash
|
||||
# Run tasks in dedicated worktrees
|
||||
cline --cwd ~/worktree-a -y "long-running task"
|
||||
cline --cwd ~/worktree-b -y "another task"
|
||||
```
|
||||
|
||||
Each worktree has its own Git branch and working directory, so task history and changes stay separated without needing instance management.
|
||||
|
||||
### With YOLO Mode
|
||||
|
||||
The `-y` (YOLO) flag is essential for worktree workflows:
|
||||
|
||||
```bash
|
||||
# Without -y: Opens interactive chat (blocks other tasks)
|
||||
cline --cwd ~/worktree-a "refactor code"
|
||||
|
||||
# With -y: Runs autonomously (doesn't block)
|
||||
cline --cwd ~/worktree-a -y "refactor code" &
|
||||
```
|
||||
|
||||
For parallel execution, always use `-y` to avoid blocking on user approval.
|
||||
|
||||
## Real-World Workflow Example
|
||||
|
||||
Here's a complete workflow showing how these patterns work together:
|
||||
|
||||
```bash
|
||||
# Setup: Create three worktrees
|
||||
git worktree add ~/cline-worktrees/feature-auth -b feature/authentication
|
||||
git worktree add ~/cline-worktrees/feature-api -b feature/api-endpoints
|
||||
git worktree add ~/cline-worktrees/fix-tests -b fix/failing-tests
|
||||
|
||||
# Pattern 1: Run parallel independent tasks
|
||||
cline -c ~/cline-worktrees/feature-auth -y --config ~/.cline-sonnet \
|
||||
"implement JWT authentication" &
|
||||
|
||||
cline -c ~/cline-worktrees/feature-api -y --config ~/.cline-sonnet \
|
||||
"create REST API endpoints for user management" &
|
||||
|
||||
cline -c ~/cline-worktrees/fix-tests -y --config ~/.cline-haiku \
|
||||
"fix all failing unit tests" &
|
||||
|
||||
wait
|
||||
echo "All parallel tasks complete!"
|
||||
|
||||
# Pattern 2: Use piping for iterative refinement
|
||||
cline -c ~/cline-worktrees/feature-auth -y \
|
||||
"test the authentication with curl. output only errors if any" \
|
||||
| cline -c ~/cline-worktrees/feature-auth -y \
|
||||
"fix the authentication issues described in the input"
|
||||
|
||||
# Merge successful changes back
|
||||
cd ~/cline-worktrees/feature-auth
|
||||
git checkout main
|
||||
git merge feature/authentication
|
||||
|
||||
# Cleanup
|
||||
git worktree remove ~/cline-worktrees/feature-auth
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Worktree Organization">
|
||||
- **Use a dedicated folder**: Create `~/cline-worktrees/` for all worktrees
|
||||
- **Meaningful branch names**: Use `feature/`, `fix/`, `refactor/` prefixes
|
||||
- **Clean up regularly**: Remove worktrees after merging branches
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Task Isolation">
|
||||
- **Independent features only**: Don't parallelize tasks that touch the same files
|
||||
- **Test in isolation**: Each worktree should have its own test run
|
||||
- **Separate configs**: Use `.worktreeinclude` to copy `node_modules` and build artifacts
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Resource Management">
|
||||
- **Monitor disk space**: Each worktree is a full checkout
|
||||
- **Limit parallel tasks**: Running too many simultaneously can slow your system
|
||||
- **Use background jobs wisely**: Track with `jobs` command, kill with `kill %1`, etc.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Error Handling">
|
||||
- **Check exit codes**: Use `|| echo "Task failed"` to catch errors
|
||||
- **Log outputs**: Redirect to files for debugging: `> worktree-a.log 2>&1`
|
||||
- **Graceful cleanup**: Always remove worktrees after tasks complete
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title=""Branch already checked out" error">
|
||||
Git doesn't allow the same branch in multiple worktrees. Solutions:
|
||||
- Use different branch names for each worktree
|
||||
- Remove the existing worktree first: `git worktree remove <path>`
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Tasks not running in parallel">
|
||||
Make sure you're using:
|
||||
- `&` at the end of each command to background it
|
||||
- `-y` flag so Cline doesn't wait for approval
|
||||
- Different worktrees (not the same path)
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Pipe not working as expected">
|
||||
Verify:
|
||||
- First command outputs to stdout (not stderr)
|
||||
- Second command reads from stdin (use `--` separator if needed)
|
||||
- Both commands use correct `--cwd` paths
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Changes not appearing in worktree">
|
||||
Check:
|
||||
- You're in the right worktree: `git worktree list`
|
||||
- Files aren't gitignored
|
||||
- You committed/staged changes if needed
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Related Documentation
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Worktrees Overview" icon="code-branch" href="/features/worktrees">
|
||||
Complete guide to Git worktrees, VS Code integration, and .worktreeinclude
|
||||
</Card>
|
||||
|
||||
<Card title="Model Orchestration" icon="layer-group" href="/cline-cli/samples/model-orchestration">
|
||||
Use different models strategically with --config and --thinking flags
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Reference" icon="terminal" href="/cline-cli/cli-reference">
|
||||
Complete documentation for --cwd and all other CLI flags
|
||||
</Card>
|
||||
|
||||
<Card title="Headless Mode" icon="robot" href="/cline-cli/three-core-flows">
|
||||
Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -1,244 +0,0 @@
|
||||
---
|
||||
title: "Headless Mode"
|
||||
description: "Run Cline autonomously in scripts, CI/CD pipelines, and automated workflows"
|
||||
---
|
||||
|
||||
Headless mode runs Cline without an interactive interface — perfect for automation, scripting, and CI/CD pipelines where human interaction isn't possible or desired. Cline executes tasks, produces clean text or JSON output, and exits when complete.
|
||||
|
||||
For collaborative, conversational development, see [Interactive Mode](/cline-cli/interactive-mode) instead.
|
||||
|
||||
<Note>
|
||||
**Migrating from an older CLI version?** Instance commands (`cline instance new/list/kill`) have been removed in Cline CLI 2.0. The new architecture is simpler — just use `cline -y "task"` for headless execution.
|
||||
</Note>
|
||||
|
||||
## When Headless Mode Activates
|
||||
|
||||
Cline automatically enters headless mode when any of these conditions are met:
|
||||
|
||||
| Invocation | Reason |
|
||||
|------------|--------|
|
||||
| `cline -y "task"` | `-y`/`--yolo` flag forces headless |
|
||||
| `cline --json "task"` | `--json` flag forces headless |
|
||||
| `cat file \| cline "task"` | stdin is piped |
|
||||
| `cline "task" > output.txt` | stdout is redirected |
|
||||
|
||||
If none of these apply (e.g., running `cline` or `cline "task"` in a terminal), Cline launches in [interactive mode](/cline-cli/interactive-mode).
|
||||
|
||||
## YOLO Mode (Fully Autonomous)
|
||||
|
||||
The `-y` or `--yolo` flag enables fully autonomous operation — Cline approves all actions and runs without prompts:
|
||||
|
||||
```bash
|
||||
cline -y "Run the test suite and fix any failures"
|
||||
```
|
||||
|
||||
In YOLO mode:
|
||||
- All actions are auto-approved
|
||||
- Output is plain text (non-interactive)
|
||||
- Process exits automatically when complete
|
||||
- Perfect for CI/CD and scripts
|
||||
|
||||
<Warning>
|
||||
YOLO mode gives Cline full autonomy. Run on a clean git branch so you can easily revert changes if needed.
|
||||
</Warning>
|
||||
|
||||
### Mode Selection
|
||||
|
||||
Control whether Cline plans first or acts immediately:
|
||||
|
||||
```bash
|
||||
# Start in Plan mode (analyze before acting)
|
||||
cline -y -p "Design a REST API for user management"
|
||||
|
||||
# Start in Act mode (default)
|
||||
cline -y -a "Fix the typo in README.md"
|
||||
```
|
||||
|
||||
## Piping Context
|
||||
|
||||
Pipe file contents or command output into Cline to provide context:
|
||||
|
||||
```bash
|
||||
# Explain a file
|
||||
cat README.md | cline "Summarize this document"
|
||||
|
||||
# Review git changes
|
||||
git diff | cline "Review these changes and suggest improvements"
|
||||
|
||||
# Analyze command output
|
||||
npm test 2>&1 | cline "Analyze these test failures and fix them"
|
||||
|
||||
# Pipe a GitHub PR diff
|
||||
gh pr diff 123 | cline -y "Review this PR"
|
||||
```
|
||||
|
||||
When stdin is piped, Cline automatically enters headless mode — the piped content becomes part of the task context.
|
||||
|
||||
## Chaining Commands
|
||||
|
||||
Pipe Cline's output into another Cline instance for multi-step workflows:
|
||||
|
||||
```bash
|
||||
# Explain changes, then write a commit message
|
||||
git diff | cline -y "explain these changes" | cline -y "write a commit message for this"
|
||||
|
||||
# Generate code, then write tests
|
||||
cline -y "create a fibonacci function" | cline -y "write unit tests for this code"
|
||||
|
||||
# Fun: Generate a poem about your code
|
||||
git diff | cline -y "explain" | cline -y "write a haiku about this"
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
Use `--json` for machine-readable output that's easy to parse in scripts:
|
||||
|
||||
```bash
|
||||
cline --json "List all TODO comments in the codebase" | jq '.text'
|
||||
```
|
||||
|
||||
JSON output follows the same format as task files in `~/.cline/data/tasks/<id>/ui_messages.json`.
|
||||
|
||||
**JSON Message Schema:**
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `type` | `"ask"` or `"say"` | Message category |
|
||||
| `text` | `string` | Message content |
|
||||
| `ts` | `number` | Unix timestamp (ms) |
|
||||
| `reasoning` | `string` | (Optional) Model reasoning |
|
||||
| `partial` | `boolean` | (Optional) Streaming flag |
|
||||
|
||||
## Including Images
|
||||
|
||||
Attach images to your headless task:
|
||||
|
||||
```bash
|
||||
cline -y -i screenshot.png "Fix the layout issue shown in this screenshot"
|
||||
|
||||
# Or reference inline
|
||||
cline -y "Fix the UI shown in @./design-mockup.png"
|
||||
```
|
||||
|
||||
## Timeout Control
|
||||
|
||||
Set a maximum execution time to prevent runaway tasks:
|
||||
|
||||
```bash
|
||||
cline -y --timeout 600 "Run full test suite"
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Control Cline behavior via environment variables — useful for CI/CD where you can't use interactive configuration.
|
||||
|
||||
**CLINE_DIR** — Custom configuration directory:
|
||||
```bash
|
||||
export CLINE_DIR=/path/to/config
|
||||
cline -y "your task"
|
||||
```
|
||||
|
||||
**CLINE_COMMAND_PERMISSIONS** — Restrict allowed commands:
|
||||
```bash
|
||||
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"], "deny": ["rm -rf *"]}'
|
||||
cline -y "your task"
|
||||
```
|
||||
|
||||
See [Configuration](/cline-cli/configuration#environment-variables) for full documentation.
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
Automate PR reviews with Cline:
|
||||
|
||||
```yaml
|
||||
name: AI Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
- name: Install Cline
|
||||
run: npm install -g cline
|
||||
|
||||
- name: Configure Cline
|
||||
run: cline auth -p anthropic -k ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
||||
- name: Review PR
|
||||
run: |
|
||||
git diff origin/main...HEAD | cline -y "Review this PR for:
|
||||
- Potential bugs
|
||||
- Security issues
|
||||
- Performance concerns
|
||||
- Code style violations
|
||||
|
||||
Provide a summary of findings."
|
||||
```
|
||||
|
||||
### Shell Script Example
|
||||
|
||||
Create a reusable code review script:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# review.sh - AI-powered code review
|
||||
|
||||
set -e
|
||||
|
||||
# Get the diff
|
||||
DIFF=$(git diff HEAD~1)
|
||||
|
||||
if [ -z "$DIFF" ]; then
|
||||
echo "No changes to review"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run Cline review
|
||||
echo "$DIFF" | cline -y --json "Review this code diff for issues" | jq -r '.text'
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
| Use Case | Example |
|
||||
|----------|---------|
|
||||
| Code review | `git diff \| cline -y "Review these changes"` |
|
||||
| Fix test failures | `cline -y "Run tests and fix any failures"` |
|
||||
| Generate release notes | `git log --oneline v1.0..v1.1 \| cline -y "Write release notes"` |
|
||||
| Fix lint errors | `cline -y "Fix all ESLint errors in src/"` |
|
||||
| Update dependencies | `cline -y "Update dependencies with known vulnerabilities"` |
|
||||
| Migrate code patterns | `cline -y "Update all deprecated React lifecycle methods"` |
|
||||
| PR automation | `gh pr diff 123 \| cline -y "Review this PR"` |
|
||||
| Batch processing | `cline -y --json "List all TODO comments" \| jq '.text'` |
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Interactive Mode" icon="terminal" href="/cline-cli/interactive-mode">
|
||||
For hands-on development with keyboard shortcuts, slash commands, and file mentions.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Reference" icon="book" href="/cline-cli/cli-reference">
|
||||
Complete command documentation with all flags and options.
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" icon="gear" href="/cline-cli/configuration">
|
||||
Environment variables, rules, and advanced settings.
|
||||
</Card>
|
||||
|
||||
<Card title="CLI Samples" icon="flask" href="/cline-cli/samples/overview">
|
||||
Real-world examples of headless workflows and automation patterns.
|
||||
</Card>
|
||||
</Columns>
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: "Cline Overview"
|
||||
sidebarTitle: "Cline Overview"
|
||||
description: "Your AI-powered coding agent for complex work. Read files, write code, run commands, all with your approval."
|
||||
---
|
||||
|
||||
Welcome to the Cline documentation. Whether you're just getting started or looking to unlock advanced capabilities, you'll find everything you need here.
|
||||
|
||||
## What is Cline?
|
||||
|
||||
Cline is an AI coding agent that lives in your editor and your terminal. It can read and write files, run terminal commands, use a browser, and help you build features through natural conversation. Every action requires your explicit approval. You're always in control.
|
||||
### Agent Core (SDK)
|
||||
|
||||
The SDK is Cline's agent core—use it to build your own applications, automations, and integrations. See SDK section for detailed functionality and architectural design of the Cline Agent.
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="SDK" icon="cube" href="https://docs.cline.bot/sdk/overview">
|
||||
Build AI agents and integrations powered by the same core engine behind the CLI, Kanban, VS Code extension, and JetBrains plugin.
|
||||
|
||||
`npm install @cline/sdk`
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Applications
|
||||
|
||||
These are end-user applications built on top of Cline's agent core:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="CLI" icon="terminal" href="/usage/cli-overview">
|
||||
Run Cline in your terminal with interactive chat or fully headless automation for CI/CD and scripting.
|
||||
|
||||
`npm i -g cline`
|
||||
</Card>
|
||||
<Card title="Kanban" icon="table-columns" href="https://github.com/cline/kanban">
|
||||
Run many agents in parallel from a web-based task board with per-card worktrees, auto-commit, and dependency chains.
|
||||
|
||||
`npx kanban`
|
||||
</Card>
|
||||
<Card title="VS Code Extension" icon="code" href="https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev">
|
||||
AI coding assistant in your editor. Create files, run commands, browse the web, and use tools with human-in-the-loop approval.
|
||||
</Card>
|
||||
<Card title="JetBrains Plugin" icon="brain" href="https://plugins.jetbrains.com/plugin/27189-cline">
|
||||
The same Cline experience in IntelliJ IDEA, PyCharm, WebStorm, GoLand, and the rest of the JetBrains family.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
||||
## Other IDE Supports
|
||||
|
||||
Cline works across all major editors: **VS Code**, **Cursor**, **Windsurf**, **JetBrains** (IntelliJ, PyCharm, WebStorm), **Antigravity**, and **Zed**, **Neovim** via ACP mode.
|
||||
|
||||
|
||||
## Enterprise Solutions
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Security & Governance" icon="shield-halved" href="/enterprise-solutions/overview">
|
||||
SSO, role-based access control, model and tool controls per team, and remote configuration.
|
||||
</Card>
|
||||
<Card title="Observability" icon="chart-line" href="/enterprise-solutions/monitoring/overview">
|
||||
OpenTelemetry, Datadog, Grafana, Splunk integrations with real-time analytics.
|
||||
</Card>
|
||||
<Card title="Team Management" icon="users-gear" href="/enterprise-solutions/team-management/managing-members">
|
||||
Manage members, roles, and permissions across your organization.
|
||||
</Card>
|
||||
<Card title="API Reference" icon="code" href="/enterprise-solutions/api-reference">
|
||||
Programmatic access to Cline's enterprise features.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -1,675 +0,0 @@
|
||||
---
|
||||
title: "Cline SDK"
|
||||
description: "Embed Cline as a programmable coding agent in your Node.js applications using an ACP-compatible TypeScript API."
|
||||
---
|
||||
|
||||
# Cline SDK
|
||||
|
||||
The Cline SDK lets you embed Cline as a programmable coding agent in your Node.js applications. It exposes the same capabilities as the Cline CLI and VS Code extension — file editing, command execution, browser use, MCP servers — through a TypeScript API that conforms to the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/protocol/schema).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install cline
|
||||
```
|
||||
|
||||
If you want direct ACP type imports as well:
|
||||
|
||||
```bash
|
||||
npm install @agentclientprotocol/sdk
|
||||
```
|
||||
|
||||
Requires Node.js 20+.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { ClineAgent } from "cline";
|
||||
|
||||
const CLINE_DIR = "/Users/username/.cline";
|
||||
const agent = new ClineAgent({ clineDir: CLINE_DIR });
|
||||
|
||||
// 1. Initialize — negotiates capabilities
|
||||
const initializeResponse = await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
// these are the capabilities that the client (you) supports
|
||||
// The cline agent may or may not use them, but it needs to know about them to make informed decisions about what tools to use.
|
||||
clientCapabilities: {
|
||||
fs: { readTextFile: true, writeTextFile: true },
|
||||
terminal: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { agentInfo, authMethods } = initializeResponse;
|
||||
console.log("Agent info:", agentInfo); // contains things like agent name and version
|
||||
console.log("Auth methods:", authMethods); // contains a list of supported authentication methods. More auth methods coming soon
|
||||
|
||||
// 2. Authenticate if needed
|
||||
// If you skip this step, ClineAgent will look in CLINE_DIR for any existing credentials and authenticate with those
|
||||
await agent.authenticate({ methodId: "cline-oauth" });
|
||||
|
||||
// 3. Create a session.
|
||||
// A session represents a conversation or task with the agent. You can have multiple sessions for different tasks or conversations.
|
||||
const { sessionId } = await agent.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [], // mcpServers field not supported yet, but exposed here to maintain conformance with acp protocol
|
||||
});
|
||||
|
||||
// 4. Agent updates are sent via events. You can subscribe to these events to get real-time updates on the agent's progress, tool calls, and more.
|
||||
const emitter = agent.emitterForSession(sessionId);
|
||||
|
||||
emitter.on("agent_message_chunk", (payload) => {
|
||||
process.stdout.write(
|
||||
payload.content.type === "text"
|
||||
? payload.content.text
|
||||
: `[${payload.content.type}]`,
|
||||
);
|
||||
});
|
||||
emitter.on("agent_thought_chunk", (payload) => {
|
||||
process.stdout.write(
|
||||
payload.content.type === "text"
|
||||
? payload.content.text
|
||||
: `[${payload.content.type}]`,
|
||||
);
|
||||
});
|
||||
emitter.on("tool_call", (payload) => {
|
||||
console.log(`[tool] ${payload.title}`);
|
||||
});
|
||||
emitter.on("error", (err) => {
|
||||
console.error("[session error]", err);
|
||||
});
|
||||
|
||||
// 5. Send a prompt and wait for completion
|
||||
const { stopReason } = await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: "text", text: "Create a hello world Express server" }],
|
||||
});
|
||||
|
||||
console.log("Done:", stopReason);
|
||||
|
||||
// 6. Clean up
|
||||
await agent.shutdown();
|
||||
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Agent Lifecycle
|
||||
|
||||
The SDK follows the ACP lifecycle:
|
||||
|
||||
```
|
||||
initialize() → authenticate() → newSession() → prompt() ⇄ events → shutdown()
|
||||
```
|
||||
|
||||
| Step | Method | Purpose |
|
||||
|------|--------|---------|
|
||||
| Init | `initialize()` | Exchange protocol version and capabilities |
|
||||
| Auth | `authenticate()` | OAuth flow for Cline or OpenAI Codex accounts. Optional step if cline config directory already has credentials |
|
||||
| Session | `newSession()` | Create an isolated conversation context |
|
||||
| Prompt | `prompt()` | Send user messages; blocks until the turn ends |
|
||||
| Cancel | `cancel()` | Abort an in-progress prompt turn |
|
||||
| Mode | `setSessionMode()` | Switch between `"plan"` and `"act"` modes |
|
||||
| Model | `unstable_setSessionModel()` | Change the backing LLM (experimental) |
|
||||
| Shutdown | `shutdown()` | Abort all tasks, flush state, release resources |
|
||||
|
||||
### Sessions
|
||||
|
||||
A session is an independent conversation with its own task history and working directory. You can run multiple sessions concurrently.
|
||||
|
||||
```typescript
|
||||
const { sessionId, modes, models } = await agent.newSession({
|
||||
cwd: "/path/to/project",
|
||||
mcpServers: [], // mcpServers field not supported yet, but exposed here to maintain conformance with acp protocol
|
||||
})
|
||||
```
|
||||
|
||||
The response includes:
|
||||
- `sessionId` — use this in all subsequent calls
|
||||
- `modes` — available modes (`plan`, `act`) and the current mode
|
||||
- `models` — available models and the current model ID
|
||||
|
||||
Access session metadata via the read-only `sessions` map:
|
||||
|
||||
```typescript
|
||||
const session = agent.sessions.get(sessionId)
|
||||
// { sessionId, cwd, mode, mcpServers, createdAt, lastActivityAt, ... }
|
||||
```
|
||||
|
||||
### Prompting
|
||||
|
||||
`prompt()` sends a user message and blocks until the agent finishes its turn. While the prompt is processing, the agent streams output via session events.
|
||||
|
||||
```typescript
|
||||
const response = await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [
|
||||
{ type: "text", text: "Refactor the auth module to use JWT" },
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
The prompt array accepts multiple content blocks:
|
||||
|
||||
```typescript
|
||||
// Text + image + file context
|
||||
await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [
|
||||
{ type: "text", text: "What's in this screenshot?" },
|
||||
{ type: "image", data: base64ImageData, mimeType: "image/png" },
|
||||
{
|
||||
type: "resource",
|
||||
resource: {
|
||||
uri: "file:///path/to/relevant-file.ts",
|
||||
mimeType: "text/plain",
|
||||
text: fileContents,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
#### Content Block Types
|
||||
|
||||
| Type | Fields | Description |
|
||||
|------|--------|-------------|
|
||||
| `TextContent` | `{ type: "text", text: string }` | Plain text message |
|
||||
| `ImageContent` | `{ type: "image", mimeType: string, data: string }` | Base64-encoded image |
|
||||
| `EmbeddedResource` | `{ type: "resource", resource: { uri: string, mimeType?: string, text?: string, blob?: string } }` | File or resource context |
|
||||
|
||||
#### Stop Reasons
|
||||
|
||||
`prompt()` resolves with a `stopReason`:
|
||||
|
||||
| Value | Meaning |
|
||||
|-------|---------|
|
||||
| `"end_turn"` | Agent finished normally (completed task or waiting for user input) |
|
||||
| `"error"` | An error occurred |
|
||||
|
||||
### Streaming Events
|
||||
|
||||
Subscribe to real-time output via `ClineSessionEmitter`. Each session has its own emitter.
|
||||
|
||||
```typescript
|
||||
const emitter = agent.emitterForSession(sessionId)
|
||||
```
|
||||
|
||||
#### Event Types
|
||||
|
||||
All events correspond to [ACP `SessionUpdate` types](https://agentclientprotocol.com/protocol/schema#SessionUpdate):
|
||||
|
||||
| Event | Payload | Description |
|
||||
|-------|---------|-------------|
|
||||
| `agent_message_chunk` | `{ content: ContentBlock }` | Streamed text from the agent |
|
||||
| `agent_thought_chunk` | `{ content: ContentBlock }` | Internal reasoning / chain-of-thought |
|
||||
| `tool_call` | `ToolCall` | New tool invocation (file edit, command, etc.) |
|
||||
| `tool_call_update` | `ToolCallUpdate` | Progress/result update for an existing tool call |
|
||||
| `plan` | `{ entries: PlanEntry[] }` | Agent's execution plan |
|
||||
| `available_commands_update` | `{ availableCommands: AvailableCommand[] }` | Slash commands the agent supports |
|
||||
| `current_mode_update` | `{ currentModeId: string }` | Mode changed (plan/act) |
|
||||
| `user_message_chunk` | `{ content: ContentBlock }` | User message chunks (for multi-turn) |
|
||||
| `config_option_update` | `{ configOptions: SessionConfigOption[] }` | Configuration changed |
|
||||
| `session_info_update` | Session metadata | Session metadata changed |
|
||||
| `error` | `Error` | Session-level error (not an ACP update) |
|
||||
|
||||
```typescript
|
||||
emitter.on("agent_message_chunk", (payload) => {
|
||||
// payload.content is a ContentBlock — usually { type: "text", text: "..." }
|
||||
process.stdout.write(payload.content.text)
|
||||
})
|
||||
|
||||
emitter.on("agent_thought_chunk", (payload) => {
|
||||
console.log("[thinking]", payload.content.text)
|
||||
})
|
||||
|
||||
emitter.on("tool_call", (payload) => {
|
||||
console.log(`[${payload.kind}] ${payload.title} (${payload.status})`)
|
||||
})
|
||||
|
||||
emitter.on("tool_call_update", (payload) => {
|
||||
console.log(` → ${payload.toolCallId}: ${payload.status}`)
|
||||
})
|
||||
|
||||
emitter.on("error", (err) => {
|
||||
console.error("Session error:", err)
|
||||
})
|
||||
```
|
||||
|
||||
The emitter supports `on`, `once`, `off`, and `removeAllListeners`.
|
||||
|
||||
### Permission Handling
|
||||
|
||||
When the agent wants to execute a tool (edit a file, run a command, etc.), it requests permission. You **must** set a permission handler or all tool calls will be auto-rejected.
|
||||
|
||||
```typescript
|
||||
agent.setPermissionHandler(async (request) => {
|
||||
// request.toolCall — details about what the agent wants to do
|
||||
// request.options — available choices (allow_once, reject_once, etc.)
|
||||
|
||||
console.log(`Permission requested: ${request.toolCall.title}`)
|
||||
console.log("Options:", request.options.map(o => `${o.optionId} (${o.kind})`))
|
||||
|
||||
// Auto-approve everything:
|
||||
const allowOption = request.options.find(o => o.kind.includes("allow"))
|
||||
if (allowOption) {
|
||||
return { outcome: { outcome: "selected", optionId: allowOption.optionId } }
|
||||
} else {
|
||||
return { outcome: { outcome: "rejected" } }
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Permission Options
|
||||
|
||||
Each permission request includes an array of `PermissionOption` objects:
|
||||
|
||||
| `kind` | Meaning |
|
||||
|--------|---------|
|
||||
| `allow_once` | Approve this single operation |
|
||||
| `allow_always` | Approve and remember for future operations |
|
||||
| `reject_once` | Deny this single operation |
|
||||
| `reject_always` | Deny and remember for future operations |
|
||||
|
||||
**Important:** If no permission handler is set, all tool calls are rejected for safety.
|
||||
|
||||
### Modes
|
||||
|
||||
Cline supports two modes:
|
||||
|
||||
- **`plan`** — The agent gathers information and creates a plan without executing actions
|
||||
- **`act`** — The agent executes actions (file edits, commands, etc.)
|
||||
|
||||
```typescript
|
||||
// Switch to plan mode
|
||||
await agent.setSessionMode({ sessionId, modeId: "plan" })
|
||||
|
||||
// Switch back to act mode
|
||||
await agent.setSessionMode({ sessionId, modeId: "act" })
|
||||
```
|
||||
|
||||
The current mode is returned in `newSession()`
|
||||
|
||||
### Model Selection
|
||||
|
||||
Change the backing model with `unstable_setSessionModel()`. The model ID format is `"provider/modelId"`.
|
||||
|
||||
```typescript
|
||||
await agent.unstable_setSessionModel({
|
||||
sessionId,
|
||||
modelId: "anthropic/claude-sonnet-4-20250514",
|
||||
})
|
||||
```
|
||||
|
||||
This sets the model for both plan and act modes. Available providers include `anthropic`, `openai-native`, `gemini`, `bedrock`, `deepseek`, `mistral`, `groq`, `xai`, and others. Model Ids can be found in the NewSessionResponse object after calling `agent.newSession(..)`
|
||||
|
||||
> **Note:** This API is experimental and may change.
|
||||
|
||||
### Authentication
|
||||
|
||||
The SDK supports two OAuth flows:
|
||||
|
||||
```typescript
|
||||
// Cline account (uses browser OAuth)
|
||||
await agent.authenticate({ methodId: "cline-oauth" })
|
||||
|
||||
// OpenAI Codex / ChatGPT subscription
|
||||
await agent.authenticate({ methodId: "openai-codex-oauth" })
|
||||
```
|
||||
|
||||
Both methods open a browser window for the OAuth flow and block until authentication completes (5-minute timeout for Cline OAuth).
|
||||
|
||||
For BYO (bring-your-own) API key providers, configure the key through the cline config directory before creating a session. The `authenticate()` call is not needed for BYO providers. We plan to support more auth providers in the near future.
|
||||
|
||||
### Cancellation
|
||||
|
||||
Cancel an in-progress prompt turn:
|
||||
|
||||
```typescript
|
||||
await agent.cancel({ sessionId })
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
new ClineAgent(options: ClineAgentOptions)
|
||||
```
|
||||
|
||||
```typescript
|
||||
interface ClineAgentOptions {
|
||||
/** Enable debug logging (default: false) */
|
||||
debug?: boolean
|
||||
/** Custom Cline config directory (default: ~/.cline) */
|
||||
clineDir?: string
|
||||
}
|
||||
```
|
||||
|
||||
The `clineDir` option lets you isolate configuration and task history per-application:
|
||||
|
||||
```typescript
|
||||
const agent = new ClineAgent({
|
||||
clineDir: "/tmp/my-app-cline",
|
||||
})
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
#### `initialize(params): Promise<InitializeResponse>`
|
||||
|
||||
Initialize the agent and negotiate protocol capabilities.
|
||||
|
||||
```typescript
|
||||
const response = await agent.initialize({
|
||||
clientCapabilities: {},
|
||||
protocolVersion: 1,
|
||||
})
|
||||
|
||||
// Response includes:
|
||||
{
|
||||
protocolVersion: "0.9.0",
|
||||
agentCapabilities: {
|
||||
loadSession: true,
|
||||
promptCapabilities: { image: true, audio: false, embeddedContext: true },
|
||||
mcpCapabilities: { http: true, sse: false }
|
||||
},
|
||||
agentInfo: { name: "cline", version: "2.2.3" },
|
||||
authMethods: [
|
||||
{ id: "cline-oauth", name: "Sign in with Cline", description: "..." },
|
||||
{ id: "openai-codex-oauth", name: "Sign in with ChatGPT", description: "..." }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### `newSession(params): Promise<NewSessionResponse>`
|
||||
|
||||
Create a new conversation session.
|
||||
|
||||
```typescript
|
||||
const session = await agent.newSession({
|
||||
cwd: "/path/to/project",
|
||||
mcpServers: [
|
||||
{
|
||||
type: "stdio",
|
||||
name: "filesystem",
|
||||
command: "npx",
|
||||
args: ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
|
||||
env: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// Response includes:
|
||||
{
|
||||
sessionId: "uuid-string",
|
||||
modes: {
|
||||
availableModes: [
|
||||
{ id: "plan", name: "Plan", description: "Gather information and create a detailed plan" },
|
||||
{ id: "act", name: "Act", description: "Execute actions to accomplish the task" }
|
||||
],
|
||||
currentModeId: "act"
|
||||
},
|
||||
models: {
|
||||
currentModelId: "anthropic/claude-sonnet-4-5-20241022",
|
||||
availableModels: [{ modelId: "anthropic/claude-3-5-sonnet-20241022", name: "..." }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** `newSession()` may throw an auth-required error if credentials are not configured yet.
|
||||
|
||||
#### `prompt(params): Promise<PromptResponse>`
|
||||
|
||||
Send a user prompt to the agent. This is the main method for interacting with Cline. Blocks until the agent finishes its turn.
|
||||
|
||||
```typescript
|
||||
const response = await agent.prompt({
|
||||
sessionId: session.sessionId,
|
||||
prompt: [
|
||||
{ type: "text", text: "Create a function that adds two numbers" },
|
||||
],
|
||||
})
|
||||
|
||||
// Response: { stopReason: "end_turn" | "max_tokens" | "cancelled" | "error" }
|
||||
```
|
||||
|
||||
#### `cancel(params): Promise<void>`
|
||||
|
||||
Cancel an ongoing prompt operation.
|
||||
|
||||
```typescript
|
||||
await agent.cancel({ sessionId: session.sessionId })
|
||||
```
|
||||
|
||||
#### `setSessionMode(params): Promise<SetSessionModeResponse>`
|
||||
|
||||
Switch between plan and act modes.
|
||||
|
||||
```typescript
|
||||
await agent.setSessionMode({ sessionId, modeId: "plan" })
|
||||
```
|
||||
|
||||
#### `unstable_setSessionModel(params): Promise<SetSessionModelResponse>`
|
||||
|
||||
Change the model for the session. Model ID format depends on the inference provider. See NewSessionResponse object to get modelIds.
|
||||
|
||||
```typescript
|
||||
await agent.unstable_setSessionModel({
|
||||
sessionId,
|
||||
modelId: "anthropic/claude-sonnet-4-20250514",
|
||||
})
|
||||
```
|
||||
|
||||
#### `authenticate(params): Promise<AuthenticateResponse>`
|
||||
|
||||
Authenticate with a provider. Opens a browser window for OAuth flow.
|
||||
|
||||
```typescript
|
||||
await agent.authenticate({ methodId: "cline-oauth" })
|
||||
```
|
||||
|
||||
Current methodIds we support:
|
||||
|
||||
| methodId | Description |
|
||||
| -------------------- | ----------------------------- |
|
||||
| `cline-oauth` | use cline inference provider |
|
||||
| `openai-codex-oauth` | use your chatgpt subscription |
|
||||
| more coming soon!... | |
|
||||
|
||||
#### `shutdown(): Promise<void>`
|
||||
|
||||
Clean up all resources. Call this when done.
|
||||
|
||||
```typescript
|
||||
await agent.shutdown()
|
||||
```
|
||||
|
||||
#### `setPermissionHandler(handler)`
|
||||
|
||||
Set a callback to handle tool permission requests.
|
||||
|
||||
```typescript
|
||||
agent.setPermissionHandler((request, resolve) => {
|
||||
resolve({ outcome: { outcome: "selected", optionId: "allow_once" } })
|
||||
})
|
||||
```
|
||||
|
||||
#### `emitterForSession(sessionId): ClineSessionEmitter`
|
||||
|
||||
Get the typed event emitter for a session.
|
||||
|
||||
```typescript
|
||||
const emitter = agent.emitterForSession(session.sessionId)
|
||||
```
|
||||
|
||||
#### `sessions` (read-only Map)
|
||||
|
||||
Access active sessions:
|
||||
|
||||
```typescript
|
||||
for (const [sessionId, session] of agent.sessions) {
|
||||
console.log(sessionId, session.cwd, session.mode)
|
||||
}
|
||||
```
|
||||
|
||||
## Full Example: Auto-Approve Agent
|
||||
|
||||
```typescript
|
||||
import { ClineAgent } from "cline";
|
||||
|
||||
async function runTask(taskPrompt: string, cwd: string) {
|
||||
const agent = new ClineAgent({ clineDir: "/Users/maxpaulus/.cline" });
|
||||
|
||||
await agent.initialize({
|
||||
protocolVersion: 1,
|
||||
clientCapabilities: {},
|
||||
});
|
||||
|
||||
const { sessionId } = await agent.newSession({ cwd, mcpServers: [] });
|
||||
|
||||
// Auto-approve all tool calls
|
||||
agent.setPermissionHandler(async (request) => {
|
||||
const allow = request.options.find((o) => o.kind === "allow_once");
|
||||
return {
|
||||
outcome: allow
|
||||
? { outcome: "selected", optionId: allow.optionId }
|
||||
: { outcome: "cancelled" },
|
||||
};
|
||||
});
|
||||
|
||||
// Collect output
|
||||
const output: string[] = [];
|
||||
const emitter = agent.emitterForSession(sessionId);
|
||||
|
||||
emitter.on("agent_message_chunk", (p) => {
|
||||
if (p.content.type === "text") output.push(p.content.text);
|
||||
});
|
||||
|
||||
emitter.on("tool_call", (p) => {
|
||||
console.log(`[tool] ${p.title}`);
|
||||
});
|
||||
|
||||
const { stopReason } = await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: "text", text: taskPrompt }],
|
||||
});
|
||||
|
||||
console.log("\n--- Agent Output ---");
|
||||
console.log(output.join(""));
|
||||
console.log(`\nStop reason: ${stopReason}`);
|
||||
|
||||
await agent.shutdown();
|
||||
}
|
||||
|
||||
runTask("Create a README.md for this project", process.cwd());
|
||||
```
|
||||
|
||||
## Full Example: Interactive Permission Flow
|
||||
|
||||
```typescript
|
||||
import { ClineAgent, type PermissionHandler } from "cline";
|
||||
import * as readline from "readline";
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
const ask = (q: string) => new Promise<string>((res) => rl.question(q, res));
|
||||
|
||||
const interactivePermissions: PermissionHandler = async (request) => {
|
||||
console.log(`\n⚠️ Permission: ${request.toolCall.title}`);
|
||||
|
||||
for (const [i, opt] of request.options.entries()) {
|
||||
console.log(` ${i + 1}. [${opt.kind}] ${opt.name}`);
|
||||
}
|
||||
|
||||
const choice = await ask("Choose (number): ");
|
||||
const idx = parseInt(choice, 10) - 1;
|
||||
const selected = request.options[idx];
|
||||
|
||||
if (selected) {
|
||||
return {
|
||||
outcome: { outcome: "selected", optionId: selected.optionId },
|
||||
};
|
||||
} else {
|
||||
return { outcome: { outcome: "cancelled" } };
|
||||
}
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const agent = new ClineAgent({});
|
||||
await agent.initialize({ protocolVersion: 1, clientCapabilities: {} });
|
||||
|
||||
const { sessionId } = await agent.newSession({
|
||||
cwd: process.cwd(),
|
||||
mcpServers: [],
|
||||
});
|
||||
|
||||
agent.setPermissionHandler(interactivePermissions);
|
||||
|
||||
const emitter = agent.emitterForSession(sessionId);
|
||||
emitter.on("agent_message_chunk", (p) => {
|
||||
if (p.content.type === "text") process.stdout.write(p.content.text);
|
||||
});
|
||||
|
||||
// Multi-turn conversation
|
||||
while (true) {
|
||||
const userInput = await ask("\n> ");
|
||||
if (userInput === "exit") break;
|
||||
|
||||
const { stopReason } = await agent.prompt({
|
||||
sessionId,
|
||||
prompt: [{ type: "text", text: userInput }],
|
||||
});
|
||||
|
||||
console.log(`\n[${stopReason}]`);
|
||||
}
|
||||
|
||||
await agent.shutdown();
|
||||
rl.close();
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
```
|
||||
|
||||
## Exported Types
|
||||
|
||||
All types are re-exported from the `cline` package. Key types:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `ClineAgent` | Main agent class |
|
||||
| `ClineSessionEmitter` | Typed event emitter for session events |
|
||||
| `ClineAgentOptions` | Constructor options |
|
||||
| `ClineAcpSession` | Session metadata (read-only) |
|
||||
| `ClineSessionEvents` | Event name → handler signature map |
|
||||
| `PermissionHandler` | `(request, resolve) => void` callback |
|
||||
| `PermissionResolver` | `(response) => void` callback |
|
||||
| `SessionUpdate` | Union of all session update types |
|
||||
| `SessionUpdateType` | Discriminator values (`"agent_message_chunk"`, `"tool_call"`, etc.) |
|
||||
| `ToolCall` | Tool call details (id, title, kind, status, content) |
|
||||
| `ToolCallUpdate` | Partial update to an existing tool call |
|
||||
| `ToolCallStatus` | `"pending" \| "in_progress" \| "completed" \| "failed"` |
|
||||
| `ToolKind` | `"read" \| "edit" \| "delete" \| "execute" \| "search" \| ...` |
|
||||
| `StopReason` | `"end_turn" \| "cancelled" \| "error" \| "max_tokens" \| ...` |
|
||||
| `ContentBlock` | `TextContent \| ImageContent \| AudioContent \| ...` |
|
||||
| `McpServer` | MCP server configuration (stdio, http) |
|
||||
| `PromptRequest` / `PromptResponse` | Prompt call types |
|
||||
| `NewSessionRequest` / `NewSessionResponse` | Session creation types |
|
||||
| `InitializeRequest` / `InitializeResponse` | Initialization types |
|
||||
|
||||
See the [ACP Schema](https://agentclientprotocol.com/protocol/schema) for the full type definitions.
|
||||
|
||||
## Relationship to ACP
|
||||
|
||||
The Cline SDK implements the [Agent Client Protocol](https://agentclientprotocol.com) `Agent` interface. The key difference from a standard ACP stdio agent is that the SDK uses an **event emitter pattern** instead of a transport connection:
|
||||
|
||||
| ACP Stdio (via `AcpAgent`) | SDK (via `ClineAgent`) |
|
||||
|-----------------------------|------------------------|
|
||||
| Session updates sent over JSON-RPC stdio | Session updates emitted via `ClineSessionEmitter` |
|
||||
| Permissions requested via `connection.requestPermission()` | Permissions requested via `setPermissionHandler()` callback |
|
||||
| Single process, single connection | Embeddable, multiple concurrent sessions |
|
||||
|
||||
If you need stdio-based ACP communication (e.g., for IDE integration), use the `cline` CLI binary directly. The SDK is for embedding Cline in your own Node.js processes.
|
||||
@@ -1,316 +0,0 @@
|
||||
---
|
||||
title: "Documentation Templates"
|
||||
sidebarTitle: "Templates"
|
||||
description: "Templates for different types of Cline documentation"
|
||||
---
|
||||
|
||||
Use these templates as starting points for new documentation. Each template is designed for a specific purpose. Choose the one that best fits what you're documenting.
|
||||
|
||||
## Choosing a Template
|
||||
|
||||
| If you're documenting... | Use this template |
|
||||
|--------------------------|-------------------|
|
||||
| What a feature does and how to use it | Feature Doc |
|
||||
| How to accomplish a specific task | How-To Guide |
|
||||
| Technical specifications or API details | Reference Doc |
|
||||
| A complete project walkthrough | Tutorial |
|
||||
|
||||
## Feature Doc
|
||||
|
||||
Use this template when explaining a Cline feature. Focus on what it does, how to use it, and real examples.
|
||||
|
||||
````text
|
||||
---
|
||||
title: "Feature Name"
|
||||
sidebarTitle: "Feature Name"
|
||||
---
|
||||
|
||||
[One sentence explaining what this feature does.]
|
||||
|
||||
<Frame>
|
||||
<img src="..." alt="Feature in action" />
|
||||
</Frame>
|
||||
|
||||
[1-2 paragraphs explaining the feature in plain terms. What problem does it
|
||||
solve? Why would someone use it?]
|
||||
|
||||
## How It Works
|
||||
|
||||
[Explain the mechanics without jargon. What happens when you use this feature?]
|
||||
|
||||
## Using [Feature Name]
|
||||
|
||||
[Show how to access and use it. Include the exact UI path.]
|
||||
|
||||
### [Option or Variation 1]
|
||||
|
||||
[Details with examples]
|
||||
|
||||
### [Option or Variation 2]
|
||||
|
||||
[Details with examples]
|
||||
|
||||
## Inspiration
|
||||
|
||||
[Share how you personally use this feature. Use "I" voice. Give 2-3 real
|
||||
examples that spark imagination about what's possible.]
|
||||
|
||||
<Note>
|
||||
[Important caveat, limitation, or requirement]
|
||||
</Note>
|
||||
````
|
||||
|
||||
### Example: Checkpoints Feature
|
||||
|
||||
Here's how the [Checkpoints](/core-workflows/checkpoints) doc follows this pattern:
|
||||
|
||||
- Opens with one clear sentence about what checkpoints do
|
||||
- Shows a screenshot of the feature in action
|
||||
- Explains how checkpoints work under the hood
|
||||
- Shows exact steps to create and restore checkpoints
|
||||
- Includes real examples of when checkpoints save the day
|
||||
|
||||
## How-To Guide
|
||||
|
||||
Use this template when showing how to accomplish a specific task. Focus on clear steps and troubleshooting.
|
||||
|
||||
````text
|
||||
---
|
||||
title: "How to [Accomplish Task]"
|
||||
sidebarTitle: "[Short Title]"
|
||||
description: "[One sentence describing what the reader will learn]"
|
||||
---
|
||||
|
||||
[Brief intro explaining what problem this guide solves and what you'll end up
|
||||
with after following it.]
|
||||
|
||||
## Prerequisites
|
||||
|
||||
[What the reader needs before starting. Keep it short. Link to other docs
|
||||
rather than explaining setup here.]
|
||||
|
||||
- Cline installed and configured
|
||||
- [Other requirement]
|
||||
|
||||
## Steps
|
||||
|
||||
<Steps>
|
||||
<Step title="[First Action]">
|
||||
[Clear instructions. Show exactly what to click or type.]
|
||||
|
||||
```bash
|
||||
example command if needed
|
||||
```
|
||||
</Step>
|
||||
<Step title="[Second Action]">
|
||||
[Next step. Include screenshots for complex UI interactions.]
|
||||
|
||||
<Frame>
|
||||
<img src="..." alt="What you should see" />
|
||||
</Frame>
|
||||
</Step>
|
||||
<Step title="[Final Action]">
|
||||
[Complete the task. Show the expected result.]
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Common issues and how to fix them:
|
||||
|
||||
- **Problem description**: Solution in one or two sentences.
|
||||
- **Another problem**: Another solution.
|
||||
|
||||
## Next Steps
|
||||
|
||||
<Card title="Related Feature" icon="arrow-right" href="/path/to/related">
|
||||
Continue learning with this related guide.
|
||||
</Card>
|
||||
````
|
||||
|
||||
### Example: Your First Project
|
||||
|
||||
The [Your First Project](/getting-started/your-first-project) guide follows this pattern:
|
||||
|
||||
- Clear goal stated upfront
|
||||
- Prerequisites listed briefly
|
||||
- Step-by-step instructions with the Steps component
|
||||
- Troubleshooting section for common issues
|
||||
|
||||
## Reference Doc
|
||||
|
||||
Use this template for technical specifications, API documentation, or detailed configuration options.
|
||||
|
||||
````text
|
||||
---
|
||||
title: "[Component/API] Reference"
|
||||
sidebarTitle: "[Short Title]"
|
||||
description: "[What this reference covers]"
|
||||
---
|
||||
|
||||
[Brief description of what this reference documents and when you'd need it.]
|
||||
|
||||
## Overview
|
||||
|
||||
[High-level explanation. What is this component? What role does it play?]
|
||||
|
||||
## [Category 1]
|
||||
|
||||
### [Item Name]
|
||||
|
||||
[What it does in one sentence.]
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `propertyName` | `string` | `"default"` | What this property controls |
|
||||
| `anotherProp` | `boolean` | `false` | What this does |
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
// Show practical usage
|
||||
const example = {
|
||||
propertyName: "custom value",
|
||||
anotherProp: true
|
||||
}
|
||||
```
|
||||
|
||||
### [Another Item]
|
||||
|
||||
[Continue for each item in this category.]
|
||||
|
||||
## [Category 2]
|
||||
|
||||
[Continue with other categories as needed.]
|
||||
|
||||
## Examples
|
||||
|
||||
[Show 2-3 complete, practical examples that combine multiple concepts.]
|
||||
|
||||
### [Example 1 Title]
|
||||
|
||||
```typescript
|
||||
// Complete working example
|
||||
```
|
||||
|
||||
### [Example 2 Title]
|
||||
|
||||
```typescript
|
||||
// Another complete example
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Related Doc 1](/path/to/doc) - Brief description
|
||||
- [Related Doc 2](/path/to/doc) - Brief description
|
||||
````
|
||||
|
||||
### Example: Cline Tools Guide
|
||||
|
||||
The [Cline Tools Guide](/tools-reference/all-cline-tools) follows this pattern:
|
||||
|
||||
- Overview of the tool system
|
||||
- Each tool documented with parameters and examples
|
||||
- Practical examples showing tools in context
|
||||
|
||||
## Tutorial
|
||||
|
||||
Use this template for comprehensive project walkthroughs where users build something from start to finish.
|
||||
|
||||
````text
|
||||
---
|
||||
title: "[Build/Create X] Tutorial"
|
||||
sidebarTitle: "[Short Title]"
|
||||
description: "[What the reader will build]"
|
||||
---
|
||||
|
||||
In this tutorial, you'll build [specific outcome]. By the end, you'll have
|
||||
[tangible result you can see/use].
|
||||
|
||||
<Frame>
|
||||
<img src="..." alt="Preview of what you'll build" />
|
||||
</Frame>
|
||||
|
||||
## What You'll Learn
|
||||
|
||||
- [Skill or concept 1]
|
||||
- [Skill or concept 2]
|
||||
- [Skill or concept 3]
|
||||
|
||||
## Prerequisites
|
||||
|
||||
[Required setup. Link to installation guides rather than repeating them.]
|
||||
|
||||
- [Prerequisite 1]
|
||||
- [Prerequisite 2]
|
||||
|
||||
## Part 1: [First Major Section]
|
||||
|
||||
[Introduction to this section. What are we doing and why?]
|
||||
|
||||
### [Subsection]
|
||||
|
||||
[Detailed walkthrough with code blocks and explanations.]
|
||||
|
||||
```typescript
|
||||
// Code that the reader should write or understand
|
||||
```
|
||||
|
||||
[Explain what the code does and why.]
|
||||
|
||||
## Part 2: [Second Major Section]
|
||||
|
||||
[Continue building on Part 1.]
|
||||
|
||||
### [Subsection]
|
||||
|
||||
[More detailed walkthrough.]
|
||||
|
||||
## Part 3: [Final Section]
|
||||
|
||||
[Complete the project.]
|
||||
|
||||
## Summary
|
||||
|
||||
You built [what they built]. Along the way, you learned:
|
||||
|
||||
- [Key takeaway 1]
|
||||
- [Key takeaway 2]
|
||||
- [Key takeaway 3]
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Go Deeper" icon="book" href="/path/to/advanced">
|
||||
Learn more advanced techniques.
|
||||
</Card>
|
||||
<Card title="Related Tutorial" icon="code" href="/path/to/related">
|
||||
Build something else with similar concepts.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
````
|
||||
|
||||
### Example Structure
|
||||
|
||||
A good tutorial:
|
||||
- Shows the end result upfront so readers know what they're building
|
||||
- Breaks the work into logical parts
|
||||
- Explains the "why" alongside the "how"
|
||||
- Ends with clear next steps
|
||||
|
||||
## Quick Tips
|
||||
|
||||
When using these templates:
|
||||
|
||||
1. **Delete sections you don't need.** Templates are starting points, not rigid structures.
|
||||
|
||||
2. **Add sections that make sense.** If your doc needs something not in the template, add it.
|
||||
|
||||
3. **Keep the reader moving forward.** Every section should lead naturally to the next.
|
||||
|
||||
4. **Test your own instructions.** Follow your guide from scratch to catch missing steps.
|
||||
|
||||
<Tip>
|
||||
Use the `/write-docs` workflow to generate documentation from these templates automatically.
|
||||
Cline helps you fill in each section based on your project.
|
||||
</Tip>
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
title: "Documentation Guide"
|
||||
sidebarTitle: "Documentation Guide"
|
||||
description: "How to write and contribute to Cline documentation"
|
||||
---
|
||||
|
||||
Cline's documentation lives in the `docs/` directory and uses [Mintlify](https://mintlify.com) for rendering. This guide covers how to write docs that match Cline's established style.
|
||||
|
||||
## Using the Documentation Workflow
|
||||
|
||||
The fastest way to create documentation is using the `/write-docs` workflow. Type `/write-docs` in Cline and describe what you want to document. Cline guides you through a 4-step process:
|
||||
|
||||
1. **Research**: Examine existing docs structure and patterns
|
||||
2. **Scope**: Clarify audience, doc type, and key use cases
|
||||
3. **Outline**: Select a template and create structure
|
||||
4. **Write**: Generate documentation following style guidelines
|
||||
|
||||
The workflow file lives at `.clinerules/workflows/write-docs.md` and contains templates, style rules, and examples.
|
||||
|
||||
## Documentation Principles
|
||||
|
||||
### Write for Developers
|
||||
|
||||
Your audience is developers who value their time. Get to the point. Every sentence should either help them understand something or help them do something.
|
||||
|
||||
```markdown
|
||||
# Good
|
||||
Switch to bash in Cline Settings → Terminal → Default Terminal Profile.
|
||||
|
||||
# Bad
|
||||
Users who are experiencing issues may find it helpful to navigate to the
|
||||
Cline settings menu where they can locate the terminal configuration
|
||||
options and subsequently modify the default terminal profile setting.
|
||||
```
|
||||
|
||||
### Show Real Examples
|
||||
|
||||
Abstract descriptions don't help anyone. Show actual code, real file paths, and concrete implementations.
|
||||
|
||||
```markdown
|
||||
# Good
|
||||
I use `/deep-planning` whenever I'm building features that touch multiple
|
||||
parts of the codebase. For example, when adding authentication, Cline
|
||||
mapped every endpoint and created a migration plan that avoided breaking changes.
|
||||
|
||||
# Bad
|
||||
The deep planning feature can be utilized for various complex tasks
|
||||
that may require careful consideration and planning.
|
||||
```
|
||||
|
||||
### Use Active Voice
|
||||
|
||||
Cline does things. Files don't get created by Cline, Cline creates files.
|
||||
|
||||
```markdown
|
||||
# Good
|
||||
Cline reads your project files and builds context automatically.
|
||||
|
||||
# Bad
|
||||
Project files are read and context is built automatically.
|
||||
```
|
||||
|
||||
### Use Neutral Pronouns for Cline
|
||||
|
||||
Refer to Cline as "it" not "he". Cline is software, not a person.
|
||||
|
||||
```markdown
|
||||
# Good
|
||||
When Cline encounters an error, it suggests fixes.
|
||||
|
||||
# Bad
|
||||
When Cline encounters an error, he suggests fixes.
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
All documentation uses MDX format with YAML frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "Full Page Title"
|
||||
sidebarTitle: "Shorter Nav Title" # optional
|
||||
description: "One sentence for SEO" # optional but recommended
|
||||
---
|
||||
```
|
||||
|
||||
### Adding New Pages
|
||||
|
||||
After creating a new `.mdx` file, add it to `docs/docs.json` in the appropriate navigation group:
|
||||
|
||||
```json
|
||||
{
|
||||
"group": "Features",
|
||||
"pages": [
|
||||
"features/existing-page",
|
||||
"features/your-new-page"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Mintlify Components
|
||||
|
||||
Use these components appropriately throughout your docs.
|
||||
|
||||
### Frame
|
||||
|
||||
Wrap all images and videos:
|
||||
|
||||
```jsx
|
||||
<Frame>
|
||||
<img
|
||||
src="https://storage.googleapis.com/cline_public_images/docs/assets/filename.png"
|
||||
alt="Descriptive alt text"
|
||||
/>
|
||||
</Frame>
|
||||
```
|
||||
|
||||
### Callouts
|
||||
|
||||
Use sparingly and purposefully:
|
||||
|
||||
```jsx
|
||||
<Tip>Helpful suggestions that improve the experience.</Tip>
|
||||
<Note>Important information the reader needs to know.</Note>
|
||||
<Warning>Something that could cause problems if ignored.</Warning>
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
For sequential procedures:
|
||||
|
||||
```jsx
|
||||
<Steps>
|
||||
<Step title="Install the Extension">
|
||||
Search for "Cline" in the VS Code marketplace.
|
||||
</Step>
|
||||
<Step title="Configure Your Model">
|
||||
Open settings and add your API key.
|
||||
</Step>
|
||||
</Steps>
|
||||
```
|
||||
|
||||
### Cards
|
||||
|
||||
For navigation and feature overviews:
|
||||
|
||||
```jsx
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Getting Started" icon="rocket" href="/getting-started/installing-cline">
|
||||
Install Cline and set up your first project.
|
||||
</Card>
|
||||
<Card title="Features" icon="wand-magic-sparkles" href="/core-workflows/plan-and-act">
|
||||
Explore what Cline can do.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
```
|
||||
|
||||
## Style Rules
|
||||
|
||||
Quick reference for consistent documentation:
|
||||
|
||||
| Do | Don't |
|
||||
|---|---|
|
||||
| Use "use" | Use "utilize" |
|
||||
| Keep sentences under 25 words | Write run-on sentences |
|
||||
| Use bullet points for lists | Write walls of text |
|
||||
| Show where things are in the UI | Assume users can find features |
|
||||
| Cross-link related docs | Leave readers stranded |
|
||||
| Use code blocks with language tags | Use inline code for long snippets |
|
||||
|
||||
### Avoid These Patterns
|
||||
|
||||
- Em dashes and emojis
|
||||
- Starting with "This document explains..."
|
||||
- The **Bold Text**: description pattern
|
||||
- Explaining obvious things
|
||||
- Passive voice
|
||||
|
||||
## Previewing Changes
|
||||
|
||||
Run the docs locally to preview your changes:
|
||||
|
||||
```bash
|
||||
cd docs
|
||||
npm install # first time only
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open `http://localhost:3000` to see your changes in real time.
|
||||
|
||||
## Related Resources
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Documentation Templates" icon="file-lines" href="/contributing/doc-templates">
|
||||
Templates for different documentation types.
|
||||
</Card>
|
||||
<Card title="Workflows" icon="diagram-project" href="/customization/workflows">
|
||||
Learn about Cline's workflow system.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
title: "Model Selection Guide"
|
||||
description: "Choose the right AI model for your workflow based on reliability, speed, cost, and context window size."
|
||||
---
|
||||
|
||||
New models drop constantly, so this guide focuses on what's working well with Cline right now. We'll keep it updated as the landscape shifts.
|
||||
|
||||
<Callout type="tip">
|
||||
**New to model selection?** Start with [Module 2 of Cline's Learning Path](https://cline.bot/learn) for a comprehensive guide to choosing and configuring models.
|
||||
</Callout>
|
||||
|
||||
## What is an AI Model?
|
||||
|
||||
Think of an AI model as the "brain" that powers Cline. When you ask Cline to write code, fix bugs, or refactor your project, it's the model that actually understands your request and generates the response.
|
||||
|
||||
**Key points:**
|
||||
- **Models are trained AI systems** that understand natural language and code
|
||||
- **Different models have different strengths** some excel at complex reasoning, others prioritize speed or cost
|
||||
- **You choose which model Cline uses** like picking between different experts for different tasks
|
||||
- **Models are accessed via API providers** - companies like Anthropic, OpenAI, and OpenRouter host these models
|
||||
|
||||
**Why it matters:** The model you choose directly impacts Cline's capabilities, response quality, speed, and cost. A premium model might handle complex refactoring beautifully but cost more, while a budget model works great for routine tasks at a fraction of the price.
|
||||
|
||||
## How to Select a Model in Cline
|
||||
|
||||
Follow these 5 simple steps to get Cline up and running with your preferred AI model:
|
||||
|
||||
### Step 1: Open Cline Settings
|
||||
|
||||
First, you need to access Cline's configuration panel.
|
||||
|
||||
**Two ways to open settings:**
|
||||
- **Quick method**: Click the **gear icon (⚙️)** in the top-right corner of Cline's chat interface
|
||||
- **Command palette**: Press **Cmd/Ctrl + Shift + P** → type "Cline: Open Settings"
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/step1-config.png" alt="Cline Settings Panel" />
|
||||
</Frame>
|
||||
|
||||
The settings panel will open, showing configuration options with "API Provider" at the top.
|
||||
|
||||
<Note>
|
||||
The settings panel remembers your last configuration, so you'll only need to set this up once.
|
||||
</Note>
|
||||
|
||||
### Step 2: Select an API Provider
|
||||
|
||||
Choose your preferred AI provider from the dropdown menu.
|
||||
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/step2-provider.png" alt="Cline Settings Panel" />
|
||||
</Frame>
|
||||
|
||||
**Popular providers at a glance:**
|
||||
|
||||
| Provider | Best For | Notes |
|
||||
|----------|----------|-------|
|
||||
| **Cline** | Easiest setup | No API keys needed, access to multiple models including stealth models |
|
||||
| **OpenRouter** | Value seekers | Multiple models, competitive pricing |
|
||||
| **Anthropic** | Reliability | Claude models, most dependable tool usage |
|
||||
| **OpenAI** | Latest tech | GPT-5, o3, o4-mini models |
|
||||
| **OpenAI Codex** | ChatGPT subscribers | Use your ChatGPT subscription — no API key needed |
|
||||
| **Google Gemini** | Large context | Gemini 3/2.5 with up to 2M context |
|
||||
| **DeepSeek** | Budget reasoning | V3.2, R1 models at low cost |
|
||||
| **Alibaba Qwen** | Open source coding | Qwen3 Coder with 1M context |
|
||||
| **Moonshot** | Agentic coding | Kimi K2.5 with 262K context |
|
||||
| **Cerebras** | Speed | Up to 2,600 tokens/sec |
|
||||
| **AWS Bedrock** | Enterprise | Advanced features |
|
||||
| **Ollama** | Privacy | Run models locally |
|
||||
|
||||
See the [full provider list](/getting-started/authorizing-with-cline) for all 30+ supported providers including xAI Grok, Mistral, Groq, Fireworks, Together, Baseten, SambaNova, Nebius, Hugging Face, and more.
|
||||
|
||||
<Info>
|
||||
**Recommended for beginners:** Start with **Cline** as your provider - no API key management needed, instant access to multiple models, and occasional free inferencing through partner providers.
|
||||
</Info>
|
||||
|
||||
### Step 3: Add Your API Key (or Sign In)
|
||||
|
||||
The next step depends on which provider you selected.
|
||||
|
||||
#### If you selected **Cline** as your provider:
|
||||
|
||||
- **No API key needed!** Simply sign in with your Cline account
|
||||
- Click the **Sign In** button when prompted
|
||||
- You'll be redirected to [app.cline.bot](https://app.cline.bot) to authenticate
|
||||
- After signing in, return to your IDE
|
||||
|
||||
<Note>
|
||||
For detailed information about the Cline authentication flow, OAuth tokens, and troubleshooting, see [Authorizing with Cline](/getting-started/authorizing-with-cline).
|
||||
</Note>
|
||||
|
||||
#### If you selected **OpenAI Codex** as your provider:
|
||||
|
||||
- **No API key needed!** If you have a ChatGPT subscription (Plus, Pro, or Team), you can use it directly in Cline
|
||||
- Click **"Sign in with OpenAI"** to authenticate via your browser
|
||||
- Once authorized, all models available on your OpenAI plan will appear automatically
|
||||
- Usage is governed by your ChatGPT subscription — no separate API billing
|
||||
|
||||
See the full [OpenAI Codex setup guide](/provider-config/openai-codex) for details.
|
||||
|
||||
#### If you selected any other provider:
|
||||
|
||||
You'll need to get an API key from your chosen provider:
|
||||
|
||||
1. **Visit your provider's website to get an API key:**
|
||||
- **Anthropic**: [console.anthropic.com](https://console.anthropic.com/)
|
||||
- **OpenRouter**: [openrouter.ai/keys](https://openrouter.ai/keys)
|
||||
- **OpenAI**: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)
|
||||
- **Google**: [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
|
||||
- **Others**: See [Provider Setup Guide](/getting-started/authorizing-with-cline)
|
||||
|
||||
2. **Generate a new API key** on the provider's website
|
||||
|
||||
3. **Copy the API key** to your clipboard
|
||||
|
||||
4. **Paste your key** in the **"API Key"** field in Cline settings
|
||||
|
||||
5. **Save automatically** - Your key is stored securely in your editor's secrets storage
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/step3-API.png" alt="Cline API Selection" />
|
||||
</Frame>
|
||||
|
||||
<Warning>
|
||||
**Payment required for most providers**: Most providers need payment information before generating keys. You only pay for what you use (typically $0.01-$0.10 per coding task).
|
||||
</Warning>
|
||||
|
||||
### Step 4: Choose Your Model
|
||||
|
||||
Once your API key is added (or you've signed in), the **"Model"** dropdown becomes available.
|
||||
|
||||
<Frame>
|
||||
<img src="https://storage.googleapis.com/cline_public_images/step4-model.png" alt="Cline Model Selection" />
|
||||
</Frame>
|
||||
|
||||
**Quick model selection guide:**
|
||||
|
||||
| Your Priority | Choose This Model | Why |
|
||||
|---------------|-------------------|-----|
|
||||
| **Maximum reliability** | Claude Sonnet 4.5 | Most reliable tool usage, excellent at complex tasks |
|
||||
| **Best value** | DeepSeek V3 or Qwen3 Coder | Great performance at budget prices |
|
||||
| **Fastest speed** | Qwen3 Coder on Cerebras | Lightning-fast responses |
|
||||
| **Run locally** | Any Ollama model | Complete privacy, no internet needed |
|
||||
| **Latest features** | GPT-5 | OpenAI's newest capabilities |
|
||||
|
||||
Not sure which to pick? Start with **Claude Sonnet 4.5** for reliability or **DeepSeek V3** for value.
|
||||
|
||||
<Tip>
|
||||
You can switch models at any time without losing your conversation. Try different models to find what works best for your specific tasks.
|
||||
</Tip>
|
||||
|
||||
See the [model comparison tables](#current-top-models) below for detailed specifications and pricing.
|
||||
|
||||
### Step 5: Start Using Cline
|
||||
|
||||
**Congratulations! You're all set up.** Here's how to start coding with Cline:
|
||||
|
||||
1. **Type your request** in the Cline chat box
|
||||
- Example: "Create a React component for a login form"
|
||||
- Example: "Debug this TypeScript error"
|
||||
- Example: "Refactor this function to be more efficient"
|
||||
|
||||
2. **Press Enter** or click the send icon to submit
|
||||
|
||||
## Choosing the Right Model
|
||||
|
||||
Selecting the right model involves balancing several factors. Use this framework to find your ideal match:
|
||||
|
||||
<Note>
|
||||
**Pro tips**: Configure separate models for Plan Mode and Act Mode. Make the most out the each model's strengths. For example, use a budget model for planning discussions and a premium model for implementation.
|
||||
</Note>
|
||||
|
||||
### Key Selection Factors
|
||||
|
||||
| Factor | What to Consider | Recommendation |
|
||||
|--------|------------------|----------------|
|
||||
| **Task Complexity** | Simple fixes vs complex refactoring | Budget models for routine tasks; Premium models for complex work |
|
||||
| **Budget** | Monthly spending capacity | \$10-\$30: Budget, \$30-\$100: Mid-tier, \$100+: Premium |
|
||||
| **Context Window** | Project size and file count | Small: 32K-128K, Medium: 128K-200K, Large: 400K+ |
|
||||
| **Speed** | Response time requirements | Interactive: Fast models, Background: Reasoning models OK |
|
||||
| **Tool Reliability** | Complex operations | Claude excels at tool usage; Test others with your workflow |
|
||||
| **Provider** | Access and pricing needs | OpenRouter: Many options, Direct: Faster/reliable, Local: Privacy |
|
||||
|
||||
|
||||
|
||||
## Model Comparison Resources
|
||||
|
||||
For detailed model comparisons and performance metrics, see:
|
||||
- [**Context Window Guide**](/model-config/context-windows) - Understanding and optimizing context usage
|
||||
|
||||
## Open Source vs Closed Source
|
||||
|
||||
### Open Source Advantages
|
||||
- **Multiple providers** compete to host them
|
||||
- **Cheaper pricing** due to competition
|
||||
- **Provider choice** - switch if one goes down
|
||||
- **Faster innovation** cycles
|
||||
|
||||
### Open Source Models Available
|
||||
- **Qwen3 Coder** (Apache 2.0)
|
||||
- **Z AI GLM 4.5** (MIT)
|
||||
- **Kimi K2** (Open source)
|
||||
- **DeepSeek series** (Various licenses)
|
||||
|
||||
## Quick Decision Matrix
|
||||
|
||||
| If you want... | Use this |
|
||||
|----------------|----------|
|
||||
| Something that just works | Claude Sonnet 4.5 |
|
||||
| To save money | DeepSeek V3 or Qwen3 variants |
|
||||
| Huge context windows | Gemini 2.5 Pro or Claude Sonnet 4.5 |
|
||||
| Open source | Qwen3 Coder, Z AI GLM 4.5, or Kimi K2 |
|
||||
| Latest tech | GPT-5 |
|
||||
| To use your ChatGPT subscription | [OpenAI Codex](/provider-config/openai-codex) — sign in with your OpenAI account, no API key needed |
|
||||
| Speed | Qwen3 Coder on Cerebras (fastest available) |
|
||||
|
||||
## What Others Are Using
|
||||
|
||||
Check [Vercel's leaderboard](https://vercel.com/ai-gateway/leaderboards) to see real usage patterns from the community.
|
||||
@@ -89,7 +89,7 @@ For complex tasks that need thorough analysis, use the `/deep-planning` slash co
|
||||
3. Creates a detailed implementation plan
|
||||
4. Asks clarifying questions before proceeding
|
||||
|
||||
The deep planning prompt is optimized for each model family, so it adapts to the strengths of whatever model you're using. See the [Deep Planning docs](/features/deep-planning) for more details.
|
||||
The deep planning prompt is optimized for each model family, so it adapts to the strengths of whatever model you're using. See [/deep-planning](/core-workflows/using-commands#deep-planning) for more details.
|
||||
|
||||
## Choosing the Right Approach by Task Size
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: "Using Commands"
|
||||
sidebarTitle: "Using Commands"
|
||||
description: "Built-in slash commands to manage context, plan implementations, and create reusable workflows."
|
||||
description: "Built-in slash commands to manage context, plan implementations, and trigger reusable skills."
|
||||
---
|
||||
|
||||
Cline provides slash commands in chat that help you manage your conversation and plan complex implementations.
|
||||
@@ -37,7 +37,7 @@ Use `/smol` when you're deep into a debugging session or brainstorming and need
|
||||
|
||||
### /newrule
|
||||
|
||||
`/newrule` creates a rule file that teaches Cline your preferences. Cline will guide you through setting up guidelines for communication style, coding standards, project context, and workflows. The rule is saved to your `.clinerules` directory and automatically loaded for future conversations.
|
||||
`/newrule` creates a rule file that teaches Cline your preferences. Cline will guide you through setting up guidelines for communication style, coding standards, project context, and reusable practices. The rule is saved to your `.clinerules` directory and automatically loaded for future conversations.
|
||||
|
||||
Use `/newrule` when you find yourself repeating the same instructions across tasks. For more about rules, see [Cline Rules](/customization/cline-rules).
|
||||
|
||||
@@ -50,7 +50,7 @@ Transform Cline into a meticulous architect who investigates your codebase, asks
|
||||
3. **Plan Creation** - Generates `implementation_plan.md` with detailed specifications
|
||||
4. **Task Creation** - Creates a new task with trackable implementation steps
|
||||
|
||||
Use `/deep-planning` for features touching multiple parts of your codebase, architectural changes, or complex integrations. For detailed documentation, see [Deep Planning](/features/deep-planning).
|
||||
Use `/deep-planning` for features touching multiple parts of your codebase, architectural changes, or complex integrations.
|
||||
|
||||
### /explain-changes
|
||||
|
||||
@@ -68,8 +68,14 @@ Use `/explain-changes` when reviewing code, onboarding to a new codebase, or und
|
||||
|
||||
Use `/reportbug` when you encounter unexpected behavior, crashes, or bugs you want to report.
|
||||
|
||||
## Custom Workflows
|
||||
## Skills via Slash Commands
|
||||
|
||||
Beyond the built-in slash commands, you can create your own workflow files that work the same way. Store Markdown files in `.clinerules/workflows/` and invoke them with `/your-workflow.md`.
|
||||
In addition to built-in commands, you can trigger enabled skills directly from chat using slash commands.
|
||||
|
||||
For a complete guide on creating and managing custom workflows, see [Workflows](/customization/workflows).
|
||||
- Type `/` to open command suggestions.
|
||||
- Select a skill command (for example, `/aws-deploy`).
|
||||
- Cline loads that skill and applies its `SKILL.md` instructions for the task.
|
||||
|
||||
Any enabled skill can be triggered this way, which gives you a fast path to skill-specific guidance without rewriting the same instructions each time.
|
||||
|
||||
For setup and management details, see [Skills](/customization/skills#triggering-skills-with-slash-commands).
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
---
|
||||
title: "Adding Context"
|
||||
sidebarTitle: "Adding Context"
|
||||
description: "Use @ mentions and drag & drop to bring files, terminal output, errors, git changes, and web content into your conversations."
|
||||
description: "Use @ mentions and drag & drop to bring files into your conversations."
|
||||
---
|
||||
|
||||
Cline works best when it has the right context, not just more context. @ mentions let you pull in exactly the files, errors, terminal output, or documentation that matter for your task. No copying, no pasting, no context switching.
|
||||
Cline works best when it has the right context, not just more context. `@` mentions let you pull in the files and folders that matter for your task — no copying, no pasting, no context switching.
|
||||
|
||||
You can add context two ways:
|
||||
- Type `@` in the chat input and select what you want
|
||||
- Click the **+** button in the bottom left to browse files, images, or mentions
|
||||
|
||||
<Tip>
|
||||
**Want to learn more about managing context?** Watch [Adding Context with @ Mentions](https://youtu.be/7j6R75Dvj1Y) to see it in action.
|
||||
</Tip>
|
||||
- Type `@` in the chat input and select a file or folder
|
||||
- Click the **+** button in the bottom left to browse files or images
|
||||
|
||||
## Quick Reference
|
||||
|
||||
@@ -20,11 +16,8 @@ You can add context two ways:
|
||||
|---------------|--------|---------|
|
||||
| File content | `@/path/to/file` | `@/src/index.ts` |
|
||||
| Folder contents | `@/path/to/folder/` | `@/src/components/` |
|
||||
| Workspace errors | `@problems` | `@problems` |
|
||||
| Terminal output | `@terminal` | `@terminal` |
|
||||
| Uncommitted changes | `@git-changes` | `@git-changes` |
|
||||
| Specific commit | `@<commit-hash>` | `@a1b2c3d` |
|
||||
| Web page | `@<url>` | `@https://react.dev/learn` |
|
||||
|
||||
For other context — git history, web pages, terminal errors — just describe it. Cline will run `git log`, fetch the URL, or read the output itself.
|
||||
|
||||
## File Mentions
|
||||
|
||||
@@ -46,59 +39,6 @@ Explain how the components in @/src/components/auth/ work together.
|
||||
In multi-root workspaces, prefix paths with the workspace name: `@workspace-name:/path/to/file`
|
||||
</Note>
|
||||
|
||||
## Problem Mentions
|
||||
|
||||
Use `@problems` to share all errors and warnings from your workspace's Problems panel.
|
||||
|
||||
```text
|
||||
@problems Can you fix these TypeScript errors?
|
||||
```
|
||||
|
||||
## Terminal Mentions
|
||||
|
||||
Use `@terminal` to share recent terminal output. Perfect for debugging build errors or test failures.
|
||||
|
||||
```text
|
||||
@terminal The build is failing. What's wrong?
|
||||
```
|
||||
|
||||
## Git Mentions
|
||||
|
||||
Reference uncommitted changes with `@git-changes`:
|
||||
|
||||
```text
|
||||
@git-changes Review my changes before I commit.
|
||||
```
|
||||
|
||||
Reference specific commits with `@<commit-hash>` (7-40 character hex):
|
||||
|
||||
```text
|
||||
What did @a1b2c3d change?
|
||||
```
|
||||
|
||||
## URL Mentions
|
||||
|
||||
Reference web content with `@https://example.com`. Cline fetches the page content.
|
||||
|
||||
```text
|
||||
Implement the pattern described in @https://react.dev/learn/scaling-up-with-reducer-and-context
|
||||
```
|
||||
|
||||
## Combining Mentions
|
||||
|
||||
Combine multiple @ mentions for comprehensive context:
|
||||
|
||||
```text
|
||||
I'm getting these errors: @problems
|
||||
|
||||
Here's my component: @/src/components/Form.jsx
|
||||
And the API endpoint: @/src/api/users.js
|
||||
|
||||
The error happens when I submit: @terminal
|
||||
|
||||
I think this commit might have caused it: @a1b2c3d
|
||||
```
|
||||
|
||||
## Drag & Drop
|
||||
|
||||
Drag files directly into the chat input to add them to your conversation.
|
||||
|
||||
@@ -51,7 +51,7 @@ your-project/
|
||||
|
||||
Cline processes all `.md` and `.txt` files inside `.clinerules/`, combining them into a unified set of rules. Numeric prefixes (like `01-coding.md`) help organize files but are optional.
|
||||
|
||||
When both workspace and global rules exist, Cline combines them. Workspace rules take precedence when they conflict with global rules. See [Storage Locations](/customization/overview#storage-locations) for more guidance.
|
||||
When both workspace and global rules exist, Cline combines them. Workspace rules take precedence when they conflict with global rules. See [Storage Locations](/getting-started/config#storage-locations) for more guidance.
|
||||
|
||||
### Global Rules Directory
|
||||
|
||||
|
||||
@@ -107,4 +107,3 @@ You can still reference ignored files explicitly using [@ mentions](/core-workfl
|
||||
- [Cline Rules](/customization/cline-rules) - Define persistent instructions for Cline
|
||||
- [Task Management](/core-workflows/task-management#context-window) - Understand how context windows work
|
||||
- [Auto-Compact](/features/auto-compact) - Automatic context compression during long tasks
|
||||
- [Memory Bank](/features/memory-bank) - Structured documentation for cross-session context
|
||||
|
||||
@@ -1,509 +1,7 @@
|
||||
---
|
||||
title: "Hooks"
|
||||
sidebarTitle: "Hooks"
|
||||
description: "Inject custom logic into Cline's workflow to validate operations and shape Cline's decisions."
|
||||
description: "See details under SDK Hooks page."
|
||||
---
|
||||
|
||||
Hooks are scripts that run at key moments in Cline's workflow. Because they execute at known points with consistent inputs and outputs, hooks bring determinism to the non-deterministic nature of AI models by enforcing guardrails, validations, and context injection. You can validate operations before they execute, monitor tool usage, and shape how Cline makes decisions.
|
||||
|
||||
## What You Can Build
|
||||
|
||||
- Stop operations before they cause problems (like creating `.js` files in a TypeScript project)
|
||||
- Run linters or custom validators before files get saved
|
||||
- Prevent operations that violate security policies
|
||||
- Track everything for analytics or compliance
|
||||
- Trigger external tools or services at the right moments
|
||||
- Add context to the conversation based on what Cline is doing
|
||||
|
||||
## Hook Types
|
||||
|
||||
Cline supports 8 hook types that run at different points in the task lifecycle:
|
||||
|
||||
| Hook Type | When It Runs |
|
||||
|-----------|--------------|
|
||||
| TaskStart | When you start a new task |
|
||||
| TaskResume | When you resume an interrupted task |
|
||||
| TaskCancel | When you cancel a running task |
|
||||
| TaskComplete | When a task finishes successfully |
|
||||
| PreToolUse | Before Cline executes a tool (read_file, write_to_file, etc.) |
|
||||
| PostToolUse | After a tool execution completes |
|
||||
| UserPromptSubmit | When you submit a message to Cline |
|
||||
| PreCompact | Before Cline truncates conversation history to free up context |
|
||||
|
||||
## Hook Lifecycle
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% Styling
|
||||
classDef hook fill:#FFB74D,stroke:#E65100,stroke-width:2px,color:black,rx:5,ry:5;
|
||||
classDef state fill:#E1F5FE,stroke:#0277BD,stroke-width:2px,color:black;
|
||||
classDef action fill:#FFFFFF,stroke:#333,stroke-width:1px,color:black,stroke-dasharray: 5 5;
|
||||
|
||||
%% Entry Points
|
||||
Start((Start)) --> CheckType{New or<br/>Resume?}
|
||||
|
||||
%% Initialization Hooks
|
||||
CheckType -- New Task --> H_Start[TaskStart]:::hook
|
||||
CheckType -- Resume --> H_Resume[TaskResume]:::hook
|
||||
|
||||
%% Main Loop
|
||||
H_Start --> Loop(Task Active Loop):::state
|
||||
H_Resume --> Loop
|
||||
|
||||
subgraph Conversation Cycle
|
||||
direction TB
|
||||
Loop -- User sends message --> H_Submit[UserPromptSubmit]:::hook
|
||||
H_Submit --> Thinking[Cline Processes Context]:::state
|
||||
|
||||
%% Context Compaction Path
|
||||
Thinking -. Context Limit Reached .-> H_Compact[PreCompact]:::hook
|
||||
H_Compact -.-> Thinking
|
||||
|
||||
%% Tool Execution Path
|
||||
Thinking -- Decides to use tool --> H_PreTool[PreToolUse]:::hook
|
||||
H_PreTool -- Allowed --> ToolExec[Tool Executes]:::action
|
||||
H_PreTool -- Cancelled --> Thinking
|
||||
ToolExec --> H_PostTool[PostToolUse]:::hook
|
||||
H_PostTool --> Thinking
|
||||
end
|
||||
|
||||
%% Termination Paths
|
||||
Thinking -- Task Successfully Finished --> H_Complete[TaskComplete]:::hook
|
||||
Loop -- User Cancels Task --> H_Cancel[TaskCancel]:::hook
|
||||
|
||||
%% End
|
||||
H_Complete --> End((End))
|
||||
H_Cancel --> End
|
||||
```
|
||||
|
||||
The diagram shows the complete hook lifecycle:
|
||||
|
||||
1. **Entry**: When you start a task, either **TaskStart** (new task) or **TaskResume** (interrupted task) runs first
|
||||
2. **Conversation Cycle**: Each time you send a message, **UserPromptSubmit** runs, then Cline processes your request
|
||||
3. **Tool Execution**: When Cline decides to use a tool, **PreToolUse** runs first-if allowed, the tool executes, then **PostToolUse** runs
|
||||
4. **Context Management**: If the conversation approaches context limits, **PreCompact** runs before truncation
|
||||
5. **Exit**: The task ends with either **TaskComplete** (success) or **TaskCancel** (user cancellation)
|
||||
|
||||
Orange nodes represent hooks where you can inject custom logic. The cycle repeats as you continue the conversation.
|
||||
|
||||
## Hook Locations
|
||||
|
||||
Hooks can be stored globally or in a project workspace. See [Storage Locations](/customization/overview#storage-locations) for guidance on when to use each.
|
||||
|
||||
- **Global hooks**: `~/Documents/Cline/Hooks/`
|
||||
- **Project hooks**: `.clinerules/hooks/` in your repo (can be committed to version control)
|
||||
|
||||
When both global and workspace hooks exist for the same hook type, both run. Global hooks execute first, then workspace hooks. If either returns `cancel: true`, the operation stops.
|
||||
|
||||
## Creating a Hook
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the Hooks tab">
|
||||
Click the scale icon at the bottom of the Cline panel, to the left of the model selector. Switch to the Hooks tab.
|
||||
</Step>
|
||||
<Step title="Create a new hook">
|
||||
Click **"New hook..."** dropdown and select a hook type (e.g., PreToolUse, TaskStart).
|
||||
</Step>
|
||||
<Step title="Review the hook's code">
|
||||
Click the pencil icon to open and edit the hook script. Cline generates a template with examples.
|
||||
</Step>
|
||||
<Step title="Enable the hook">
|
||||
Toggle the switch to activate the hook once you understand what it does.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Warning>
|
||||
Always review a hook's code before enabling it. Hooks execute automatically during your workflow and can block operations or run shell commands.
|
||||
</Warning>
|
||||
|
||||
## Quick Start: Your First Hook
|
||||
|
||||
Let's create a simple hook that logs every file Cline reads or writes. You'll see results in seconds.
|
||||
|
||||
### The Hook
|
||||
|
||||
Create a file called `file-logger` in your hooks directory with this content:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Logs all file operations to ~/cline-activity.log
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.tool')
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // "N/A"')
|
||||
|
||||
# Log to file
|
||||
echo "$(date '+%H:%M:%S') - $TOOL: $FILE_PATH" >> ~/cline-activity.log
|
||||
|
||||
# Always allow the operation
|
||||
echo '{"cancel":false}'
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
<Steps>
|
||||
<Step title="Create the hook file">
|
||||
Save the script above as `~/Documents/Cline/Hooks/file-logger` or create it through the Hooks UI.
|
||||
</Step>
|
||||
<Step title="Make it executable">
|
||||
On macOS/Linux, run `chmod +x ~/Documents/Cline/Hooks/file-logger`.
|
||||
</Step>
|
||||
<Step title="Enable it (macOS/Linux only)">
|
||||
In Cline's Hooks tab, find "file-logger" under PreToolUse hooks and toggle it on.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Note>
|
||||
On Windows, hooks are executed with PowerShell and run whenever the hook file exists. In this
|
||||
foundation PR, hook enable/disable toggling is not yet supported on Windows.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Coming next: JSON-backed hook enabled/disabled state across platforms, so toggle behavior is
|
||||
consistent on Windows, macOS, and Linux.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Hook filenames are platform-specific:
|
||||
|
||||
- **Windows**: only `HookName.ps1` is supported (PowerShell script files)
|
||||
- **macOS/Linux**: only extensionless `HookName` is supported (executable files like bash scripts or binaries)
|
||||
|
||||
Wrong-platform naming is ignored by hook discovery.
|
||||
</Note>
|
||||
|
||||
### Test It
|
||||
|
||||
Ask Cline to read any file in your project: "What's in package.json?"
|
||||
|
||||
Then check the log:
|
||||
|
||||
```bash
|
||||
cat ~/cline-activity.log
|
||||
```
|
||||
|
||||
You'll see entries like:
|
||||
```text
|
||||
14:23:45 - read_file: /path/to/package.json
|
||||
14:23:47 - search_files: /path/to/src
|
||||
```
|
||||
|
||||
### Customize It
|
||||
|
||||
Try modifying the hook to:
|
||||
- Filter specific file types (only log `.ts` files)
|
||||
- Add the task ID to each log entry
|
||||
- Send notifications for write operations
|
||||
- Block operations on certain paths
|
||||
|
||||
The sections below explain how hooks receive input and return output, plus more examples.
|
||||
|
||||
## How Hooks Work
|
||||
|
||||
Hooks are executable scripts that receive JSON input via stdin and return JSON output via stdout.
|
||||
|
||||
### Input Structure
|
||||
|
||||
Every hook receives a JSON object with common fields plus hook-specific data:
|
||||
|
||||
```json
|
||||
{
|
||||
"taskId": "abc123",
|
||||
"hookName": "PreToolUse",
|
||||
"clineVersion": "3.17.0",
|
||||
"timestamp": "1736654400000",
|
||||
"workspaceRoots": ["/path/to/project"],
|
||||
"userId": "user_123",
|
||||
"model": {
|
||||
"provider": "openrouter",
|
||||
"slug": "anthropic/claude-sonnet-4.5"
|
||||
},
|
||||
|
||||
// Hook-specific field (name matches hook type in camelCase)
|
||||
"taskStart": {
|
||||
"task": "Add authentication to the API"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`model.provider` and `model.slug` are machine-stable identifiers for the active provider/model at hook execution time. If unavailable, Cline sends deterministic fallback values: `"unknown"`.
|
||||
|
||||
<Note>
|
||||
Migration note for existing hook scripts:
|
||||
|
||||
- `timestamp` is a string (milliseconds since epoch), not a number
|
||||
- `workspaceRoots` is an array of workspace root paths and replaces the old singular `workspacePath`
|
||||
|
||||
If your scripts previously read `.workspacePath`, switch to `.workspaceRoots[0]` (or iterate all roots).
|
||||
</Note>
|
||||
|
||||
The hook-specific field name matches the hook type:
|
||||
- `taskStart`, `taskResume`, `taskCancel`, `taskComplete` contain `{ task: string }`
|
||||
- `preToolUse` contains `{ tool: string, parameters: object }`
|
||||
- `postToolUse` contains `{ tool: string, parameters: object, result: string, success: boolean, durationMs: number }`
|
||||
- `userPromptSubmit` contains `{ prompt: string }`
|
||||
- `preCompact` contains `{ conversationLength: number, estimatedTokens: number }`
|
||||
|
||||
### Output Structure
|
||||
|
||||
Hooks return a JSON object to stdout:
|
||||
|
||||
```json
|
||||
{
|
||||
"cancel": false,
|
||||
"contextModification": "Optional text to add to the conversation",
|
||||
"errorMessage": ""
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `cancel` | boolean | If `true`, stops the operation (blocks the tool, cancels the task start, etc.) |
|
||||
| `contextModification` | string | Optional text that gets injected into the conversation as context for Cline |
|
||||
| `errorMessage` | string | Shown to the user if `cancel` is `true` |
|
||||
|
||||
### Context Modification
|
||||
|
||||
The `contextModification` field lets hooks inject information into the conversation. This is useful for:
|
||||
|
||||
- Adding project-specific context when a task starts
|
||||
- Providing validation results that Cline should consider
|
||||
- Injecting environment information before tool execution
|
||||
|
||||
For example, a PreToolUse hook could add: `"Note: This file is auto-generated. Edits may be overwritten."`
|
||||
|
||||
## Hook Reference
|
||||
|
||||
### Task Lifecycle Hooks
|
||||
|
||||
#### TaskStart
|
||||
|
||||
Runs when you start a new task. Use it to:
|
||||
- Log task start time for analytics
|
||||
- Add project context to the conversation
|
||||
- Check prerequisites before work begins
|
||||
- Notify external systems (Slack, issue trackers)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
INPUT=$(cat)
|
||||
TASK=$(echo "$INPUT" | jq -r '.taskStart.task')
|
||||
echo "[TaskStart] Starting: $TASK" >&2
|
||||
echo '{"cancel":false,"contextModification":"","errorMessage":""}'
|
||||
```
|
||||
|
||||
#### TaskResume
|
||||
|
||||
Runs when you resume an interrupted task (instead of TaskStart). Use it to:
|
||||
- Check for changes since the task was paused
|
||||
- Refresh context with latest project state
|
||||
- Notify that work is resuming
|
||||
|
||||
#### TaskCancel
|
||||
|
||||
Runs when you cancel a running task. Use it to:
|
||||
- Clean up temporary files or resources
|
||||
- Notify external systems about cancellation
|
||||
- Log cancellation for analytics
|
||||
|
||||
#### TaskComplete
|
||||
|
||||
Runs when a task completes successfully. Use it to:
|
||||
- Run tests or validation after changes
|
||||
- Generate reports or summaries
|
||||
- Notify stakeholders
|
||||
- Trigger CI/CD pipelines
|
||||
|
||||
### Tool Hooks
|
||||
|
||||
#### PreToolUse
|
||||
|
||||
Runs before any tool executes. This is the most powerful hook for validation and safety. Use it to:
|
||||
- Block dangerous operations
|
||||
- Validate parameters before execution
|
||||
- Add context about the file or resource being accessed
|
||||
- Log tool usage
|
||||
|
||||
The input includes the tool name and its parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"preToolUse": {
|
||||
"tool": "write_to_file",
|
||||
"parameters": {
|
||||
"path": "src/config.ts",
|
||||
"content": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example that blocks `.js` files in a TypeScript project:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.tool')
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // empty')
|
||||
|
||||
if [[ "$TOOL" == "write_to_file" && "$FILE_PATH" == *.js ]]; then
|
||||
echo '{"cancel":true,"errorMessage":"Use .ts files instead of .js in this TypeScript project"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"cancel":false}'
|
||||
```
|
||||
|
||||
#### PostToolUse
|
||||
|
||||
Runs after a tool completes (success or failure). Use it to:
|
||||
- Audit tool usage
|
||||
- Validate results
|
||||
- Trigger follow-up actions
|
||||
- Monitor performance
|
||||
|
||||
The input includes execution results:
|
||||
|
||||
```json
|
||||
{
|
||||
"postToolUse": {
|
||||
"tool": "execute_command",
|
||||
"parameters": { "command": "npm test" },
|
||||
"result": "All tests passed",
|
||||
"success": true,
|
||||
"durationMs": 3450
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
PostToolUse hooks can return `cancel: true` to stop the task, but they cannot undo the tool execution that already happened.
|
||||
</Note>
|
||||
|
||||
### Other Hooks
|
||||
|
||||
#### UserPromptSubmit
|
||||
|
||||
Runs when you send a message to Cline. Use it to:
|
||||
- Log prompts for analytics
|
||||
- Add context based on prompt content
|
||||
- Validate or sanitize prompts
|
||||
|
||||
#### PreCompact
|
||||
|
||||
Runs before Cline truncates conversation history to stay within context limits. Use it to:
|
||||
- Archive important conversation parts before they're removed
|
||||
- Log compaction events
|
||||
- Add a summary of what's being removed
|
||||
|
||||
The input includes context metrics:
|
||||
|
||||
```json
|
||||
{
|
||||
"preCompact": {
|
||||
"conversationLength": 45,
|
||||
"estimatedTokens": 125000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### TypeScript Enforcement
|
||||
|
||||
Block creation of `.js` files in a TypeScript project:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# PreToolUse hook
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.preToolUse.tool')
|
||||
FILE_PATH=$(echo "$INPUT" | jq -r '.preToolUse.parameters.path // empty')
|
||||
|
||||
if [[ "$TOOL" == "write_to_file" && "$FILE_PATH" == *.js ]]; then
|
||||
echo '{"cancel":true,"errorMessage":"Use .ts files instead of .js in this TypeScript project"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"cancel":false}'
|
||||
```
|
||||
|
||||
### Tool Usage Logging
|
||||
|
||||
Log all tool executions to a file:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# PostToolUse hook
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.postToolUse.tool')
|
||||
SUCCESS=$(echo "$INPUT" | jq -r '.postToolUse.success')
|
||||
DURATION=$(echo "$INPUT" | jq -r '.postToolUse.durationMs')
|
||||
|
||||
echo "$(date -Iseconds) | $TOOL | success=$SUCCESS | ${DURATION}ms" >> ~/.cline-tool-log.txt
|
||||
|
||||
echo '{"cancel":false}'
|
||||
```
|
||||
|
||||
### Add Project Context on Task Start
|
||||
|
||||
Inject project-specific information when a task begins:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# TaskStart hook
|
||||
|
||||
INPUT=$(cat)
|
||||
WORKSPACE=$(echo "$INPUT" | jq -r '.workspaceRoots[0] // empty')
|
||||
|
||||
# Read project info if available
|
||||
if [[ -f "$WORKSPACE/.project-context" ]]; then
|
||||
CONTEXT=$(cat "$WORKSPACE/.project-context")
|
||||
echo "{\"cancel\":false,\"contextModification\":\"Project context: $CONTEXT\"}"
|
||||
else
|
||||
echo '{"cancel":false}'
|
||||
fi
|
||||
```
|
||||
|
||||
## CLI Support
|
||||
|
||||
Hooks are available in the [Cline CLI](/cline-cli/getting-started):
|
||||
|
||||
```bash
|
||||
# Enable hooks for a task
|
||||
cline "What does this repo do?" -s hooks_enabled=true
|
||||
|
||||
# Configure hooks globally
|
||||
cline config set hooks-enabled=true
|
||||
```
|
||||
|
||||
<Note>
|
||||
Windows hooks require PowerShell (`powershell.exe`) available on your PATH.
|
||||
</Note>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Hook not running?**
|
||||
- On macOS/Linux, check that the file is executable (`chmod +x hookname`)
|
||||
- On Windows, ensure PowerShell is available (`powershell -NoProfile -Command "$PSVersionTable.PSVersion"`)
|
||||
- On Windows, ensure the hook file is named `<HookName>.ps1` (for example `PreToolUse.ps1`)
|
||||
- On macOS/Linux, ensure the hook file uses extensionless `<HookName>` naming (for example `PreToolUse`)
|
||||
- On macOS/Linux, verify the hook is enabled (toggle is on in the Hooks tab)
|
||||
- Check that Hooks are enabled globally in Settings
|
||||
|
||||
**Hook output not parsed?**
|
||||
- Ensure output is valid JSON on a single line to stdout
|
||||
- Use stderr (`>&2`) for debug logging, not stdout
|
||||
- Check for trailing characters or newlines before the JSON
|
||||
|
||||
**Hook blocking unexpectedly?**
|
||||
- Review the hook's logic and test with sample input
|
||||
- Check both global and workspace hooks (both run if they exist)
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Rules](/customization/cline-rules) define high-level guidance that hooks can enforce programmatically
|
||||
- [Checkpoints](/core-workflows/checkpoints) let you roll back if a hook didn't catch an issue
|
||||
- [Auto-Approve](/features/auto-approve) works well with hooks as safety nets
|
||||
See details under [SDK Plugins](/sdk/plugins).
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
title: "Overview"
|
||||
sidebarTitle: "Overview"
|
||||
description: "Understand how Rules, Skills, Workflows, Hooks, and .clineignore work together to customize Cline."
|
||||
---
|
||||
|
||||
Out of the box, Cline is a general-purpose AI assistant. Customizations transform it into an expert on your codebase, your team's conventions, and your workflows. Instead of repeating the same instructions every task, you define them once and Cline follows them automatically.
|
||||
|
||||
Cline offers five systems for this: Rules, Skills, Workflows, Hooks, and .clineignore. Each serves a different purpose and activates at different times.
|
||||
|
||||
## Quick Comparison
|
||||
|
||||
| Feature | Purpose | When Active | Best For |
|
||||
|---------|---------|-------------|----------|
|
||||
| **[Rules](/customization/cline-rules)** | Define how Cline behaves | Always (or contextually) | Coding standards, project constraints, team conventions |
|
||||
| **[Skills](/customization/skills)** | Domain expertise loaded on-demand | Triggered by matching requests | Specialized knowledge, complex procedures, institutional expertise |
|
||||
| **[Workflows](/customization/workflows)** | Step-by-step task automation | Invoked with `/workflow.md` | Repetitive processes, release procedures, setup scripts |
|
||||
| **[Hooks](/customization/hooks)** | Inject custom logic at key moments | Automatically on specific events | Validation, enforcement, monitoring, automation triggers |
|
||||
| **[.clineignore](/customization/clineignore)** | Control file access | Always | Excluding dependencies, build artifacts, large data files |
|
||||
|
||||
## Understanding Each Tool
|
||||
|
||||
**[Rules](/customization/cline-rules)** are always-on guidance. Use them when you want Cline to consistently follow certain patterns: coding standards, naming conventions, architectural constraints, or project-specific context. Rules shape *how* Cline works across all tasks. For example, a rule might say "always use TypeScript" or "follow the repository pattern for data access."
|
||||
|
||||
**[Skills](/customization/skills)** are domain expertise that loads only when relevant. Use them when you have extensive knowledge that would waste context if always active. Cline sees skill descriptions at startup and activates the full instructions only when your request matches. A data analysis skill might include pandas patterns, visualization preferences, and output formats that Cline only loads when you're working with data files.
|
||||
|
||||
**[Workflows](/customization/workflows)** are explicit task scripts you invoke on demand. Use them when you have a repeatable multi-step process that should run the same way every time. Type `/release.md` and Cline executes your release sequence: bump version, run tests, update changelog, commit, tag, push. Workflows define *what* to do, step by step.
|
||||
|
||||
**[Hooks](/customization/hooks)** are programmatic guardrails that run automatically at key moments. Use them when you need to validate, enforce, or extend Cline's behavior with custom code. A hook might block `.js` file creation in a TypeScript project, run linters before saves, or notify external services after deployments.
|
||||
|
||||
**[.clineignore](/customization/clineignore)** controls which files and directories Cline can access. Use it to exclude dependencies, build artifacts, generated files, and large data files from Cline's context. This reduces token usage, lowers costs, and keeps Cline focused on the code that matters. It works like `.gitignore`: add patterns to a `.clineignore` file in your project root and matching files are automatically excluded.
|
||||
|
||||
### Example: A Release Process
|
||||
|
||||
Consider how all five work together for releasing a new version:
|
||||
|
||||
1. **Rules** ensure Cline follows your team's commit message format and versioning policy
|
||||
2. **Skills** offer deep knowledge about your CI/CD system that Cline loads when deployment questions arise
|
||||
3. **Workflows** provide the explicit `/release.md` sequence: bump version, update changelog, tag, push
|
||||
4. **Hooks** validate that tests pass before allowing any commit or that the changelog was actually updated
|
||||
5. **.clineignore** keeps build artifacts, `node_modules/`, and generated files out of Cline's context so it stays focused
|
||||
|
||||
## Storage Locations
|
||||
|
||||
All five systems support both global and project-specific configurations:
|
||||
|
||||
| System | Global Location | Project Location |
|
||||
|--------|-----------------|------------------|
|
||||
| Rules | `~/Documents/Cline/Rules/` | `.clinerules/` |
|
||||
| Skills | `~/.cline/skills/` | `.cline/skills/` |
|
||||
| Workflows | `~/Documents/Cline/Workflows/` | `.clinerules/workflows/` |
|
||||
| Hooks | `~/Documents/Cline/Hooks/` | `.clinerules/hooks/` |
|
||||
| .clineignore | N/A | `.clineignore` |
|
||||
|
||||
### When to Use Each
|
||||
|
||||
**Start with project storage.** Most customizations belong in your project's directory because they're tied to that specific codebase. Team coding standards, deployment workflows, and architectural constraints all live with the code they describe. This also means your customizations travel with the repository, so collaborators get them automatically and changes can be reviewed in pull requests.
|
||||
|
||||
**Use global storage for personal preferences.** If you find yourself adding the same customization to every project, move it to global storage. Your preferred communication style, personal productivity workflows, and tools you use everywhere belong here. Global customizations apply to all projects but stay out of version control, so they won't affect your teammates.
|
||||
|
||||
When names conflict, project-specific configurations take precedence (except for Skills, where global takes precedence). This lets you override global defaults for specific projects when needed.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
<Warning>
|
||||
Always review customizations before adding them to your projects. Only use customizations from sources you trust.
|
||||
</Warning>
|
||||
|
||||
Customizations are powerful. They shape how Cline writes code, execute commands automatically, and influence every interaction. Treat customization files with the same scrutiny you'd give any code running in your environment.
|
||||
|
||||
### Best Practices
|
||||
|
||||
Review any customization file before adding it to your project or global configuration. Understand what it does and why.
|
||||
|
||||
When downloading customizations from GitHub repositories, community shares, or other external sources, verify the source:
|
||||
- Is the author reputable?
|
||||
- Has the community reviewed it?
|
||||
- Does the code do what it claims?
|
||||
|
||||
Look for dangerous commands:
|
||||
- Shell commands that delete files (`rm`, `del`)
|
||||
- Commands that transmit data (`curl`, `wget` with POST)
|
||||
- File operations outside your project directory
|
||||
- Commands that modify system configuration
|
||||
|
||||
Keep your customizations in version control so you can track changes, review diffs, and roll back if something goes wrong. When creating hooks, use the most restrictive event triggers necessary. Don't run hooks on every file save if you only need them before commits.
|
||||
@@ -0,0 +1,138 @@
|
||||
---
|
||||
title: "Plugins"
|
||||
sidebarTitle: "Plugins"
|
||||
description: "Install and manage plugins that extend Cline with custom tools, hooks, and capabilities."
|
||||
---
|
||||
<Warning>
|
||||
This feature currently only applies to Cline SDK, CLI, and Kanban. This feature is not applicable on VSCode and JetBrains Extension for now.
|
||||
</Warning>
|
||||
|
||||
Plugins extend Cline with custom tools, lifecycle hooks, slash commands, and more. They can be installed globally (available in all sessions) or per-project.
|
||||
|
||||
## Installing Plugins via CLI
|
||||
|
||||
The `cline plugin install` command installs plugins from three source types:
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Git Repository">
|
||||
```bash
|
||||
cline plugin install https://github.com/owner/repo.git
|
||||
cline plugin install git@github.com:owner/repo.git
|
||||
```
|
||||
|
||||
The installer clones the repository, installs production dependencies, and registers the plugin entry files.
|
||||
|
||||
To install a specific branch or tag, append `@ref`:
|
||||
|
||||
```bash
|
||||
cline plugin install https://github.com/owner/repo.git@v1.2.0
|
||||
cline plugin install https://github.com/owner/repo.git@main
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="npm Package">
|
||||
```bash
|
||||
cline plugin install npm:@scope/my-plugin
|
||||
cline plugin install --npm my-plugin
|
||||
```
|
||||
</Tab>
|
||||
<Tab title="Local Path">
|
||||
```bash
|
||||
cline plugin install ./my-plugin
|
||||
cline plugin install ~/plugins/my-tool
|
||||
cline plugin install /absolute/path/to/plugin.ts
|
||||
```
|
||||
|
||||
Local installs copy the file or directory into the plugin store. Both single `.ts`/`.js` files and directories with a `package.json` are supported.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
Additional flags:
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--force` | Replace an existing install for the same source |
|
||||
| `--json` | Output the result as JSON (useful for scripting) |
|
||||
| `--cwd <path>` | Install to `<path>/.cline/plugins` instead of the global directory |
|
||||
|
||||
After installation, confirm the plugin is loaded by running `cline config` and checking the plugin tab.
|
||||
|
||||
### Example: TypeScript Navigation Plugin
|
||||
|
||||
The [typescript-lsp-plugin](https://github.com/cline/typescript-lsp-plugin) is a good reference for how plugins work. It adds a `goto_definition` tool that uses the TypeScript Language Service API to resolve symbol definitions through imports, re-exports, and type aliases.
|
||||
|
||||
Install it with:
|
||||
|
||||
```bash
|
||||
cline plugin install https://github.com/cline/typescript-lsp-plugin.git
|
||||
```
|
||||
|
||||
Once installed, Cline can call `goto_definition` with a file path and line number to find where symbols are defined, which is much more precise than text search.
|
||||
|
||||
## Plugin Manifest Format
|
||||
|
||||
For a repository or npm package to be installable as a Cline plugin, its `package.json` should include a `cline` field that declares plugin entry points:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-cline-plugin",
|
||||
"version": "1.0.0",
|
||||
"cline": {
|
||||
"plugins": [
|
||||
{
|
||||
"paths": ["./index.ts"],
|
||||
"capabilities": ["tools", "hooks"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `cline.plugins` array accepts:
|
||||
|
||||
| Format | Example |
|
||||
|--------|---------|
|
||||
| Object with `paths` array | `{ "paths": ["./src/plugin.ts"], "capabilities": ["tools"] }` |
|
||||
| Plain string | `"./index.ts"` |
|
||||
|
||||
Each path should point to a `.ts` or `.js` file that exports an `AgentExtension` (either as the default export or a named export).
|
||||
|
||||
If no `cline.plugins` field is present, the installer falls back to auto-discovery: it looks for standard entry points, then recursively scans for `.ts` and `.js` files (skipping `node_modules` and `.git`).
|
||||
|
||||
### Host-Provided Dependencies
|
||||
|
||||
Dependencies under the `@cline/` scope (like `@cline/core`, `@cline/shared`) are provided by the host runtime. The installer automatically strips these from the plugin's dependency list before running `npm install`, so you should declare them as `peerDependencies`:
|
||||
|
||||
```json
|
||||
{
|
||||
"peerDependencies": {
|
||||
"@cline/core": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@cline/core": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Plugin Directory Structure
|
||||
|
||||
Plugins are stored in the `plugins` directory at two levels:
|
||||
|
||||
```
|
||||
~/.cline/
|
||||
plugins/ # Global plugins
|
||||
_installed/ # Managed by `cline plugin install`
|
||||
npm/ # npm-sourced plugins
|
||||
git/ # git-sourced plugins
|
||||
local/ # local-sourced plugins
|
||||
|
||||
.cline/ # Project root
|
||||
plugins/ # Project-scoped plugins
|
||||
```
|
||||
|
||||
Global plugins (`~/.cline/plugins/`) are available across all sessions. Project plugins (`.cline/plugins/` in your repo) are available only when working in that project.
|
||||
|
||||
## Writing Plugins
|
||||
|
||||
For a guide on building plugins with the SDK, see [Writing Plugins](/sdk/guides/writing-plugins). For the plugin API reference, see [SDK Plugins](/sdk/plugins).
|
||||
@@ -4,7 +4,7 @@ sidebarTitle: "Skills"
|
||||
description: "Modular instruction sets that extend Cline's capabilities for specific 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.
|
||||
Skills are modular instruction sets that extend Cline's capabilities for specific tasks. Each skill packages detailed guidance, processes, and optional resources that Cline loads only when relevant to your request.
|
||||
|
||||
Install multiple skills and Cline only loads what it needs. A deployment skill stays dormant until you ask about deploying. Unlike [rules](/customization/cline-rules) (which are always active), skills load on-demand so they don't consume context when you're working on something unrelated.
|
||||
|
||||
@@ -24,6 +24,16 @@ Skills use progressive loading to maximize efficiency:
|
||||
|
||||
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 from SKILL.md.
|
||||
|
||||
## Triggering Skills with Slash Commands
|
||||
|
||||
You can also invoke enabled skills explicitly from the chat input using slash commands.
|
||||
|
||||
1. Type `/` in chat to open command suggestions.
|
||||
2. Select the skill command you want to run (for example, `/aws-deploy`).
|
||||
3. Cline triggers that skill and loads its `SKILL.md` instructions.
|
||||
|
||||
This is useful when you want to force a specific skill immediately instead of waiting for auto-matching based on description.
|
||||
|
||||
## Skill Structure
|
||||
|
||||
Every skill is a directory containing a `SKILL.md` file with YAML frontmatter.
|
||||
@@ -138,7 +148,7 @@ Include real examples. Show what commands to run, what output to expect, and wha
|
||||
|
||||
## Where Skills Live
|
||||
|
||||
Skills can be stored globally or in a project workspace. See [Storage Locations](/customization/overview#storage-locations) for guidance on when to use each.
|
||||
Skills can be stored globally or in a project workspace. See [Storage Locations](/getting-started/config#storage-locations) for guidance on when to use each.
|
||||
|
||||
Project skills:
|
||||
- `.cline/skills/` (recommended)
|
||||
@@ -215,7 +225,7 @@ Cline reads documentation files using `read_file` when the instructions referenc
|
||||
| Use Scripts For | Use Instructions For |
|
||||
|-----------------|---------------------|
|
||||
| Deterministic operations (validation, formatting) | Flexible guidance that adapts to context |
|
||||
| Complex computations | Decision-making workflows |
|
||||
| Complex computations | Decision-making processes |
|
||||
| Operations that need reliability | Steps that might vary by situation |
|
||||
| Anything you'd rather not consume tokens explaining | Best practices and patterns |
|
||||
|
||||
@@ -231,7 +241,7 @@ description: Analyze data files and generate insights. Use when working with CSV
|
||||
|
||||
# Data Analysis
|
||||
|
||||
When analyzing data files, follow this workflow:
|
||||
When analyzing data files, follow this process:
|
||||
|
||||
## 1. Understand the Data
|
||||
- Read a sample of the file to understand its structure
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
---
|
||||
title: "Workflows"
|
||||
sidebarTitle: "Workflows"
|
||||
description: "Automate repetitive tasks with Markdown-based workflow files."
|
||||
---
|
||||
|
||||
Workflows are Markdown files that define a series of steps to guide Cline through repetitive or complex tasks. Type `/` followed by the workflow's filename to invoke it (e.g., `/deploy.md`).
|
||||
|
||||
Deploying, setting up a new project, running through a release checklist: these tasks often require remembering a dozen steps, running commands in the right order, and updating files manually. Mess up one step and you're debugging for an hour. Workflows turn those multi-step processes into one command. Type `/release.md` and Cline handles the version bump, runs tests, updates the changelog, commits, tags, and pushes. You just review and approve.
|
||||
|
||||
## Workflow Structure
|
||||
|
||||
A workflow is a markdown file with a title and steps. The filename becomes the command: `demo-workflow.md` is invoked with `/demo-workflow.md`.
|
||||
|
||||
````markdown title="demo-workflow.md"
|
||||
# Demo Workflow
|
||||
|
||||
Brief description of what this workflow accomplishes.
|
||||
|
||||
## Step 1: Check prerequisites
|
||||
Verify the environment is ready. Look for required tools and dependencies.
|
||||
|
||||
## Step 2: Run the build
|
||||
Execute the build command:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Step 3: Verify results
|
||||
Check that the build completed successfully and report any issues.
|
||||
````
|
||||
|
||||
Steps can be written at different levels of detail:
|
||||
|
||||
- **High-level**: "Run the test suite and fix any failures" lets Cline decide how to accomplish the goal
|
||||
- **Specific**: Use XML tool syntax or exact commands when you need precise control
|
||||
|
||||
## Creating Workflows
|
||||
|
||||
<Steps>
|
||||
<Step title="Open the Workflows menu">
|
||||
Click the scale icon at the bottom of the Cline panel, to the left of the model selector. Switch to the Workflows tab.
|
||||
</Step>
|
||||
<Step title="Create a new workflow file">
|
||||
Click "New workflow file..." and enter a filename (e.g., `deploy`). The file will be created with a `.md` extension.
|
||||
</Step>
|
||||
<Step title="Write your workflow">
|
||||
Add a title and numbered steps in markdown format. Describe what each step should accomplish.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Tip>
|
||||
**Create workflows from completed tasks.** After finishing something you'll need to repeat, tell Cline: "Create a workflow for the process I just completed." Cline analyzes the conversation, identifies the steps, and generates the workflow file. Your accumulated context becomes reusable automation.
|
||||
</Tip>
|
||||
|
||||
### Invoking Workflows
|
||||
|
||||
Type `/` in the chat input to see available workflows. Cline shows autocomplete suggestions as you type, so `/rel` would match `release-prep.md`. Select a workflow and press Enter to start it.
|
||||
|
||||
Cline executes each step in sequence, pausing for your approval when needed. You can stop a workflow at any point by rejecting a step.
|
||||
|
||||
### Toggling Workflows
|
||||
|
||||
Every workflow has a toggle to enable or disable it. This lets you control which workflows appear in the `/` menu without deleting the file.
|
||||
|
||||
## Where Workflows Live
|
||||
|
||||
Workflows can be stored in two locations: your project workspace or globally on your system.
|
||||
|
||||
**Workspace workflows** go in `.clinerules/workflows/` at your project root. Use these for project-specific automation like deployment scripts, release processes, or setup procedures that your team shares.
|
||||
|
||||
**Global workflows** go in your system's Cline Workflows directory. Use these for personal productivity workflows you use across all projects.
|
||||
|
||||
### Global Workflows Directory
|
||||
|
||||
| Operating System | Default Location |
|
||||
|------------------|------------------|
|
||||
| Windows | `Documents\Cline\Workflows` |
|
||||
| macOS | `~/Documents/Cline/Workflows` |
|
||||
| Linux/WSL | `~/Documents/Cline/Workflows` |
|
||||
|
||||
Workspace workflows take precedence when names match global workflows. See [Storage Locations](/customization/overview#storage-locations) for more guidance.
|
||||
|
||||
## What Workflows Can Use
|
||||
|
||||
Workflows can combine natural language instructions with specific tool calls. This flexibility lets you write workflows that are as simple or as precise as your task requires.
|
||||
|
||||
### Natural Language
|
||||
|
||||
Write steps as plain instructions. Cline interprets them and figures out which tools to use:
|
||||
|
||||
```markdown
|
||||
## Step 1: Check for uncommitted changes
|
||||
Look at the git status. If there are uncommitted changes, ask whether to continue or abort.
|
||||
|
||||
## Step 2: Run the test suite
|
||||
Execute all tests. If any fail, show the failures and stop.
|
||||
```
|
||||
|
||||
This approach works well when you want Cline to adapt to the situation rather than follow rigid steps.
|
||||
|
||||
### Cline Tools
|
||||
|
||||
For precise control, use Cline's built-in tools with XML syntax. This guarantees specific actions:
|
||||
|
||||
```xml
|
||||
<execute_command>
|
||||
<command>npm run test</command>
|
||||
<requires_approval>false</requires_approval>
|
||||
</execute_command>
|
||||
```
|
||||
|
||||
```xml
|
||||
<read_file>
|
||||
<path>src/config.json</path>
|
||||
</read_file>
|
||||
```
|
||||
|
||||
```xml
|
||||
<ask_followup_question>
|
||||
<question>Deploy to production or staging?</question>
|
||||
<options>["Production", "Staging", "Cancel"]</options>
|
||||
</ask_followup_question>
|
||||
```
|
||||
|
||||
See the full list in the [Cline Tools Reference](/tools-reference/all-cline-tools).
|
||||
|
||||
### CLI Tools
|
||||
|
||||
Reference any command-line tool installed on your machine. Git, npm, docker, gh, make, curl: whatever you have available.
|
||||
|
||||
```bash
|
||||
git log --author="$(git config user.name)" --since="yesterday" --oneline
|
||||
```
|
||||
|
||||
### MCP Tools
|
||||
|
||||
If you have [MCP servers](/mcp/mcp-overview) connected, use them in your workflows with the `use_mcp_tool` syntax. This lets you integrate with external services like GitHub, Slack, databases, or custom internal tools.
|
||||
|
||||
```xml
|
||||
<use_mcp_tool>
|
||||
<server_name>github-server</server_name>
|
||||
<tool_name>create_release</tool_name>
|
||||
<arguments>{"tag": "v1.2.0", "name": "Release v1.2.0", "body": "Changelog content here"}</arguments>
|
||||
</use_mcp_tool>
|
||||
```
|
||||
|
||||
Or describe the intent in natural language and let Cline figure out the tool call:
|
||||
|
||||
```markdown
|
||||
## Step 3: Create GitHub release
|
||||
Use the GitHub MCP server to create a release tagged with the version from package.json.
|
||||
Include the changelog as the release body.
|
||||
```
|
||||
|
||||
## Writing Effective Workflows
|
||||
|
||||
**Start simple.** Write natural language steps first. Only add XML tool calls when you need guaranteed behavior.
|
||||
|
||||
**Be specific about decisions.** If a step requires user input, make that explicit: "Ask whether to deploy to production or staging."
|
||||
|
||||
**Include failure handling.** Tell Cline what to do when something goes wrong: "If tests fail, show the failures and stop the workflow."
|
||||
|
||||
**Keep workflows focused.** A `deploy.md` should deploy. A `setup-db.md` should set up the database. Split complex processes into multiple workflows that can be run independently.
|
||||
|
||||
**Version control your workflows.** Store workflows in `.clinerules/workflows/` and commit them. Your team can share, review, and improve them together.
|
||||
|
||||
<Warning>
|
||||
Workflows execute with your permissions. Review workflows before running them, especially those from external sources.
|
||||
</Warning>
|
||||
|
||||
## Example: Release Preparation
|
||||
|
||||
This workflow automates the tedious pre-release checklist. It verifies your working directory is clean, runs tests and builds, prompts you for the version bump, and generates a changelog from recent commits.
|
||||
|
||||
The workflow demonstrates both approaches: XML tool syntax (`<execute_command>`, `<ask_followup_question>`) for steps that need precise control, and natural language for steps where Cline should adapt to the situation.
|
||||
|
||||
````markdown title="release-prep.md"
|
||||
# Release Preparation
|
||||
|
||||
Prepare a new release by running tests, building, and updating version info.
|
||||
|
||||
## Step 1: Check for clean working directory
|
||||
<execute_command>
|
||||
<command>git status --porcelain</command>
|
||||
</execute_command>
|
||||
|
||||
If there are uncommitted changes, ask whether to continue or stash them first.
|
||||
|
||||
## Step 2: Run the test suite
|
||||
<execute_command>
|
||||
<command>npm run test</command>
|
||||
</execute_command>
|
||||
|
||||
If any tests fail, stop the workflow and report the failures.
|
||||
|
||||
## Step 3: Build the project
|
||||
<execute_command>
|
||||
<command>npm run build</command>
|
||||
</execute_command>
|
||||
|
||||
Verify the build completes without errors.
|
||||
|
||||
## Step 4: Ask for new version
|
||||
<ask_followup_question>
|
||||
<question>What should the new version be?</question>
|
||||
<options>["Patch (x.x.X)", "Minor (x.X.0)", "Major (X.0.0)", "Custom"]</options>
|
||||
</ask_followup_question>
|
||||
|
||||
## Step 5: Update version
|
||||
Update the version in `package.json` to the new version specified by the user.
|
||||
|
||||
## Step 6: Generate changelog entry
|
||||
<execute_command>
|
||||
<command>git log --oneline $(git describe --tags --abbrev=0)..HEAD</command>
|
||||
</execute_command>
|
||||
|
||||
Use these commits to write a changelog entry for the new version.
|
||||
````
|
||||
|
||||
Invoke it with `/release-prep.md` and Cline walks through each step.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user