Compare commits

..

1 Commits

Author SHA1 Message Date
Eve Killaby c30e8dd828 Implemented local hooks testing infra redesign 2025-10-14 08:30:15 -07:00
1653 changed files with 63112 additions and 514123 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Added getCwdHash proto
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
add OpenTelemetry integration
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
removed multi-root feature flag
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add version information injection to CLI build script. The CLI binaries now include version, commit hash, build date, and builder information extracted from package.json and git, improving debugging and version tracking capabilities.
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": minor
---
Baseten Provider Model APIs Deprecation
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
allowing user to uncheck requesty base url
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Added updateApiConfigurationPartial with FieldMask to allow for partial ApiProvider updates
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add auto-retry with exponential backof for failed API requests
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Added subscribeToCheckpoints proto
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add UserAgent to Bedrock Client
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
auto-cleanup stale default instance config
+5
View File
@@ -0,0 +1,5 @@
---
"claude-dev": patch
---
Add interactive provider configuration wizard with add/list capabilities, support for 8 API providers (Anthropic, OpenAI, OpenAI Native, OpenRouter, X AI, AWS Bedrock, Google Gemini, Ollama), and UpdateSettings gRPC implementation for persisting configurations to Cline Core state.
-1
View File
@@ -1 +0,0 @@
../../.clinerules/workflows/hotfix-release.md
-1
View File
@@ -1 +0,0 @@
../../.clinerules/workflows/release.md
@@ -1,51 +0,0 @@
#!/bin/bash
set -euo pipefail
# Only run in Claude Code remote environments
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
exit 0
fi
cd "$CLAUDE_PROJECT_DIR"
echo "=== Claude Code for Web Setup ==="
echo ""
# Install latest gh CLI tool
echo "Installing GitHub CLI..."
GH_VERSION=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//')
curl -sL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tar.gz
tar -xzf /tmp/gh.tar.gz -C /tmp
sudo mv "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" /usr/local/bin/gh
rm -rf /tmp/gh.tar.gz /tmp/gh_${GH_VERSION}_linux_amd64
echo "Installed gh version: $(gh --version | head -1)"
echo ""
# Check if GITHUB_TOKEN is set and configure gh
if [ -n "${GITHUB_TOKEN:-}" ]; then
echo "GITHUB_TOKEN is configured - gh CLI is ready to use"
echo ""
echo "You can use gh commands directly, for example:"
echo " gh issue list --repo cline/cline --limit 5"
echo " gh pr list --repo cline/cline --state open"
echo " gh issue view 123 --repo cline/cline"
echo ""
else
echo "GITHUB_TOKEN is not set - gh CLI will have limited functionality"
echo ""
echo "To enable full GitHub API access:"
echo "1. Create a Fine-grained Personal Access Token at https://github.com/settings/tokens?type=beta"
echo "2. Add it as GITHUB_TOKEN in your Claude Code environment settings"
echo ""
fi
# Install project dependencies
echo "Installing dependencies..."
npm run install:all
# Generate gRPC/protobuf types (required for TypeScript)
echo "Generating proto types..."
npm run protos
echo ""
echo "Session setup complete!"
-14
View File
@@ -1,14 +0,0 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/claude-code-for-web-setup.sh"
}
]
}
]
}
}
-211
View File
@@ -1,211 +0,0 @@
---
name: create-pull-request
description: Create a GitHub pull request following project conventions. Use when the user asks to create a PR, submit changes for review, or open a pull request. Handles commit analysis, branch management, and PR creation using the gh CLI tool.
---
# Create Pull Request
This skill guides you through creating a well-structured GitHub pull request that follows project conventions and best practices.
## Prerequisites Check
Before proceeding, verify the following:
### 1. Check if `gh` CLI is installed
```bash
gh --version
```
If not installed, inform the user:
> The GitHub CLI (`gh`) is required but not installed. Please install it:
> - macOS: `brew install gh`
> - Other: https://cli.github.com/
### 2. Check if authenticated with GitHub
```bash
gh auth status
```
If not authenticated, guide the user to run `gh auth login`.
### 3. Verify clean working directory
```bash
git status
```
If there are uncommitted changes, ask the user whether to:
- Commit them as part of this PR
- Stash them temporarily
- Discard them (with caution)
## Gather Context
### 1. Identify the current branch
```bash
git branch --show-current
```
Ensure you're not on `main` or `master`. If so, ask the user to create or switch to a feature branch.
### 2. Find the base branch
```bash
git remote show origin | grep "HEAD branch"
```
This is typically `main` or `master`.
### 3. Analyze recent commits relevant to this PR
```bash
git log origin/main..HEAD --oneline --no-decorate
```
Review these commits to understand:
- What changes are being introduced
- The scope of the PR (single feature/fix or multiple changes)
- Whether commits should be squashed or reorganized
### 4. Review the diff
```bash
git diff origin/main..HEAD --stat
```
This shows which files changed and helps identify the type of change.
## Information Gathering
Before creating the PR, you need the following information. Check if it can be inferred from:
- Commit messages
- Branch name (e.g., `fix/issue-123`, `feature/new-login`)
- Changed files and their content
If any critical information is missing, use `ask_followup_question` to ask the user:
### Required Information
1. **Related Issue Number**: Look for patterns like `#123`, `fixes #123`, or `closes #123` in commit messages
2. **Description**: What problem does this solve? Why were these changes made?
3. **Type of Change**: Bug fix, new feature, breaking change, refactor, cosmetic, documentation, or workflow
4. **Test Procedure**: How was this tested? What could break?
### Example clarifying question
If the issue number is not found:
> I couldn't find a related issue number in the commit messages or branch name. What GitHub issue does this PR address? (Enter the issue number, e.g., "123" or "N/A" for small fixes)
## Git Best Practices
Before creating the PR, consider these best practices:
### Commit Hygiene
1. **Atomic commits**: Each commit should represent a single logical change
2. **Clear commit messages**: Follow conventional commit format when possible
3. **No merge commits**: Prefer rebasing over merging to keep history clean
### Branch Management
1. **Rebase on latest main** (if needed):
```bash
git fetch origin
git rebase origin/main
```
2. **Squash if appropriate**: If there are many small "WIP" commits, consider interactive rebase:
```bash
git rebase -i origin/main
```
Only suggest this if commits appear messy and the user is comfortable with rebasing.
### Push Changes
Ensure all commits are pushed:
```bash
git push origin HEAD
```
If the branch was rebased, you may need:
```bash
git push origin HEAD --force-with-lease
```
## Create the Pull Request
**IMPORTANT**: Read and use the PR template at `.github/pull_request_template.md`. The PR body format must **strictly match** the template structure. Do not deviate from the template format.
When filling out the template:
- Replace `#XXXX` with the actual issue number, or keep as `#XXXX` if no issue exists (for small fixes)
- Fill in all sections with relevant information gathered from commits and context
- Mark the appropriate "Type of Change" checkbox(es)
- Complete the "Pre-flight Checklist" items that apply
### Create PR with gh CLI
**Use a temporary file for the PR body** to avoid shell escaping issues, newline problems, and other command-line flakiness:
1. Write the PR body to a temporary file:
```
/tmp/pr-body.md
```
2. Create the PR using the file:
```bash
gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main
```
3. Clean up the temporary file:
```bash
rm /tmp/pr-body.md
```
For draft PRs:
```bash
gh pr create --title "PR_TITLE" --body-file /tmp/pr-body.md --base main --draft
```
**Why use a file?** Passing complex markdown with newlines, special characters, and checkboxes directly via `--body` is error-prone. The `--body-file` flag handles all content reliably.
## Post-Creation
After creating the PR:
1. **Display the PR URL** so the user can review it
2. **Remind about CI checks**: Tests and linting will run automatically
3. **Suggest next steps**:
- Add reviewers if needed: `gh pr edit --add-reviewer USERNAME`
- Add labels if needed: `gh pr edit --add-label "bug"`
## Error Handling
### Common Issues
1. **No commits ahead of main**: The branch has no changes to submit
- Ask if the user meant to work on a different branch
2. **Branch not pushed**: Remote doesn't have the branch
- Push the branch first: `git push -u origin HEAD`
3. **PR already exists**: A PR for this branch already exists
- Show the existing PR: `gh pr view`
- Ask if they want to update it instead
4. **Merge conflicts**: Branch conflicts with base
- Guide user through resolving conflicts or rebasing
## Summary Checklist
Before finalizing, ensure:
- [ ] `gh` CLI is installed and authenticated
- [ ] Working directory is clean
- [ ] All commits are pushed
- [ ] Branch is up-to-date with base branch
- [ ] Related issue number is identified, or placeholder is used
- [ ] PR description follows the template exactly
- [ ] Appropriate type of change is selected
- [ ] Pre-flight checklist items are addressed
-33
View File
@@ -1,33 +0,0 @@
# CLI Development
The CLI lives in `cli/` and uses React Ink for terminal UI.
- If needed, look at `cli/src/constants/colors.ts` for re-used terminal colors, e.g. `COLORS.primaryBlue` highlight color (selections, spinners, success states).
- Never use `dimColor` with gray (e.g. `<Text color="gray" dimColor>`) - it's too hard to read. Use `color="gray"` for secondary text and normal foreground (no color) for primary text.
- When thinking about how to handle state or messages from core, look at webview for how it communicates with the vs code extension.
- When updating the webview, consider and suggest to the user to update the CLI TUI since we want to provide a similar experience to our terminal users as we do our vs code extension users.
## Adding New API Providers
When adding a new API provider to the extension, you must also update the CLI:
1. **Update `cli/src/components/ModelPicker.tsx`**: Add the provider to the `providerModels` map so `getDefaultModelId()` returns the correct default model. Import the models and default ID from `@shared/api`:
```typescript
import { newProviderDefaultModelId, newProviderModels } from "@/shared/api"
export const providerModels = {
// ...existing providers
"new-provider": { models: newProviderModels, defaultId: newProviderDefaultModelId },
}
```
2. **Use `applyProviderConfig()` for auth flows**: When implementing OAuth or other auth flows for the provider, use the shared utility at `cli/src/utils/provider-config.ts`:
```typescript
import { applyProviderConfig } from "../utils/provider-config"
// After successful auth:
await applyProviderConfig({ providerId: "new-provider", controller })
```
This handles setting provider, default model, API key mapping, state persistence, and rebuilding the API handler.
3. **Provider-specific auth**: If the provider uses OAuth (like `openai-codex`), add handling in `SettingsPanelContent.tsx`'s `handleProviderSelect` callback. See the existing Codex OAuth flow as a reference.
-205
View File
@@ -1,205 +0,0 @@
This file is the secret sauce for working effectively in this codebase. It captures tribal knowledge—the nuanced, non-obvious patterns that make the difference between a quick fix and hours of back-and-forth & human intervention.
**When to add to this file:**
- User had to intervene, correct, or hand-hold
- Multiple back-and-forth attempts were needed to get something working
- You discovered something that required reading many files to understand
- A change touched files you wouldn't have guessed
- Something worked differently than you expected
- User explicitly asks to "add this to CLAUDE.md"
**Proactively suggest additions** when any of the above happen—don't wait to be asked.
**What NOT to add:** Stuff you can figure out from reading a few files, obvious patterns, or standard practices. This file should be high-signal, not comprehensive.
## Miscellaneous
- This is a VS Code extension—check `package.json` for available scripts before trying to verify builds (e.g., `npm run compile`, not `npm run build`).
- When creating PRs, if the change is user-facing and significant enough to warrant a changelog entry, run `npm run changeset` and create a patch changeset. Never create minor or major version bumps. Skip changesets for trivial fixes, internal refactors, or minor UI tweaks that users wouldn't notice.
- When adding new feature flags, see this PR as a reference https://github.com/cline/cline/pull/7566
- Additional instructions about making requests: @.clinerules/network.md
## gRPC/Protobuf Communication
The extension and webview communicate via gRPC-like protocol over VS Code message passing.
**Proto files live in `proto/`** (e.g., `proto/cline/task.proto`, `proto/cline/ui.proto`)
- Each feature domain has its own `.proto` file
- For simple data, use shared types in `proto/cline/common.proto` (`StringRequest`, `Empty`, `Int64Request`)
- For complex data, define custom messages in the feature's `.proto` file
- Naming: Services `PascalCaseService`, RPCs `camelCase`, Messages `PascalCase`
- For streaming responses, use `stream` keyword (see `subscribeToAuthCallback` in `account.proto`)
**Run `npm run protos`** after any proto changes—generates types in:
- `src/shared/proto/` - Shared type definitions
- `src/generated/grpc-js/` - Service implementations
- `src/generated/nice-grpc/` - Promise-based clients
- `src/generated/hosts/` - Generated handlers
**Adding new enum values** (like a new `ClineSay` type) requires updating conversion mappings in `src/shared/proto-conversions/cline-message.ts`
**Adding new RPC methods** requires:
- Handler in `src/core/controller/<domain>/`
- Call from webview via generated client: `UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))`
**Example—the `explain-changes` feature touched:**
- `proto/cline/task.proto` - Added `ExplainChangesRequest` message and `explainChanges` RPC
- `proto/cline/ui.proto` - Added `GENERATE_EXPLANATION = 29` to `ClineSay` enum
- `src/shared/ExtensionMessage.ts` - Added `ClineSayGenerateExplanation` type
- `src/shared/proto-conversions/cline-message.ts` - Added mapping for new say type
- `src/core/controller/task/explainChanges.ts` - Handler implementation
- `webview-ui/src/components/chat/ChatRow.tsx` - UI rendering
## Adding a New API Provider
When adding a new provider (e.g., "openai-codex"), you must update the proto conversion layer in THREE places or the provider will silently reset to Anthropic:
1. `proto/cline/models.proto` - Add to the `ApiProvider` enum (e.g., `OPENAI_CODEX = 40;`)
2. `convertApiProviderToProto()` in `src/shared/proto-conversions/models/api-configuration-conversion.ts` - Add case mapping string to proto enum
3. `convertProtoToApiProvider()` in the same file - Add case mapping proto enum back to string
**Why this matters:** Without these, the provider string hits the `default` case and returns `ANTHROPIC`. The webview, provider list, and handler all work fine, but the state silently resets when it round-trips through proto serialization. No error is thrown.
**Other files to update when adding a provider:**
- `src/shared/api.ts` - Add to `ApiProvider` union type, define models
- `src/shared/providers/providers.json` - Add to provider list for dropdown
- `src/core/api/index.ts` - Register handler in `createHandlerForProvider()`
- `webview-ui/src/components/settings/utils/providerUtils.ts` - Add cases in `getModelsForProvider()` and `normalizeApiConfiguration()`
- `webview-ui/src/utils/validate.ts` - Add validation case
- `webview-ui/src/components/settings/ApiOptions.tsx` - Render provider component
## Responses API Providers (OpenAI Codex, OpenAI Native)
Providers using OpenAI's Responses API require native tool calling. XML tools don't work with the Responses API.
**Symptoms of broken native tool calling:**
- Tools get called multiple times (e.g., `ask_followup_question` asks the same question twice)
- Tool arguments get duplicated or malformed
- The model responds but tools aren't recognized
**Root causes to check:**
1. **Provider missing from `isNextGenModelProvider()`** in `src/utils/model-utils.ts`. The native variant matchers (e.g., `native-gpt-5/config.ts`) call this function. If your provider isn't in the list, the matcher returns false and falls back to XML tools.
2. **Model missing `apiFormat: ApiFormat.OPENAI_RESPONSES`** in its model info (`src/shared/api.ts`). This property signals that the model requires native tool calling. The task runner in `src/core/task/index.ts` checks this and forces `enableNativeToolCalls: true` regardless of user settings.
**When adding a new Responses API provider:**
1. Add provider to `isNextGenModelProvider()` list in `src/utils/model-utils.ts`
2. Set `apiFormat: ApiFormat.OPENAI_RESPONSES` on all models that use the Responses API
3. The variant matcher and task runner will handle the rest automatically
## Adding Tools to System Prompt
This is tricky—multiple prompt variants and configs. **Always search for existing similar tools first and follow their pattern.** Look at the full chain from prompt definition → variant configs → handler → UI before implementing.
1. **Add to `ClineDefaultTool` enum** in `src/shared/tools.ts`
2. **Tool definition** in `src/core/prompts/system-prompt/tools/` (create file like `generate_explanation.ts`)
- Define variants for each `ModelFamily` (generic, next-gen, xs, etc.)
- Export variants array (e.g., `export const my_tool_variants = [GENERIC, NATIVE_NEXT_GEN, XS]`)
- **Fallback behavior**: If a variant isn't defined for a model family, `ClineToolSet.getToolByNameWithFallback()` automatically falls back to GENERIC. So you only need to export `[GENERIC]` unless the tool needs model-specific behavior.
3. **Register in `src/core/prompts/system-prompt/tools/init.ts`** - Import and spread into `allToolVariants`
4. **Add to variant configs** - Each model family has its own config in `src/core/prompts/system-prompt/variants/*/config.ts`. Add your tool's enum to the `.tools()` list:
- `generic/config.ts`, `next-gen/config.ts`, `gpt-5/config.ts`, `native-gpt-5/config.ts`, `native-gpt-5-1/config.ts`, `native-next-gen/config.ts`, `gemini-3/config.ts`, `glm/config.ts`, `hermes/config.ts`, `xs/config.ts`
- **Important**: If you add to a variant's config, make sure the tool spec exports a variant for that ModelFamily (or relies on GENERIC fallback)
5. **Create handler** in `src/core/task/tools/handlers/`
6. **Wire up in `ToolExecutor.ts`** if needed for execution flow
7. **Add to tool parsing** in `src/core/assistant-message/index.ts` if needed
8. **If tool has UI feedback**: add `ClineSay` enum in proto, update `src/shared/ExtensionMessage.ts`, update `src/shared/proto-conversions/cline-message.ts`, update `webview-ui/src/components/chat/ChatRow.tsx`
## Modifying System Prompt
**Read these first:** `src/core/prompts/system-prompt/README.md`, `tools/README.md`, `__tests__/README.md`
System prompt is modular: **components** (reusable sections) + **variants** (model-specific configs) + **templates** (with `{{PLACEHOLDER}}` resolution).
**Key directories:**
- `components/` - Shared sections: `rules.ts`, `capabilities.ts`, `editing_files.ts`, etc.
- `variants/` - Model-specific: `generic/`, `next-gen/`, `xs/`, `gpt-5/`, `gemini-3/`, `hermes/`, `glm/`, etc.
- `templates/` - Template engine and placeholder definitions
**Variant tiers (ask user which to modify):**
- **Next-gen** (Claude 4, GPT-5, Gemini 2.5): `next-gen/`, `native-next-gen/`, `native-gpt-5/`, `native-gpt-5-1/`, `gemini-3/`, `gpt-5/`
- **Standard** (default fallback): `generic/`
- **Local/small models**: `xs/`, `hermes/`, `glm/`
**How overrides work:** Variants can override components via `componentOverrides` in their `config.ts`, or provide a custom template in `template.ts` (e.g., `next-gen/template.ts` exports `rules_template`). If no override, the shared component from `components/` is used.
**Example: Adding a rule to RULES section**
1. Check if variant overrides rules: look for `rules_template` in `variants/*/template.ts` or `componentOverrides.RULES` in `config.ts`
2. If shared: modify `components/rules.ts`
3. If overridden: modify that variant's template
4. XS variant is special—has heavily condensed inline content in `template.ts`
**After any changes, regenerate snapshots:**
```bash
UPDATE_SNAPSHOTS=true npm run test:unit
```
Snapshots live in `__tests__/__snapshots__/`. Tests validate across model families and context variations (browser, MCP, focus chain).
## Modifying Default Slash Commands
Three places need updates:
- `src/core/slash-commands/index.ts` - Command definitions
- `src/core/prompts/commands.ts` - System prompt integration
- `webview-ui/src/utils/slash-commands.ts` - Webview autocomplete
## Adding New Global State Keys
Adding a new key to global state requires updates in multiple places. Missing any step causes silent failures.
Required steps:
1. Type definition in `src/shared/storage/state-keys.ts` - Add to `GlobalState` or `Settings` interface
2. Read from globalState in `src/core/storage/utils/state-helpers.ts`:
- Add `const myKey = context.globalState.get<GlobalStateAndSettings["myKey"]>("myKey")` in `readGlobalStateFromDisk()`
- Add to the return object: `myKey: myKey ?? defaultValue,`
3. StateManager handles read/write via `setGlobalState()`/`getGlobalStateKey()` after initialization
Common mistake: Adding only the return value without the `context.globalState.get()` call. This compiles but the value is always `undefined` on load.
Settings plumbing gotcha: if a key is user-toggleable from settings, wire both controller update paths:
- `src/core/controller/state/updateSettings.ts` for webview `updateSetting(...)`
- `src/core/controller/state/updateSettingsCli.ts` for CLI/ACP settings updates
Missing one path causes a toggle to appear to change in one surface while the backend state stays unchanged.
Webview toggle gotcha: settings changes must also round-trip back in state payloads.
- Add the field to `UpdateSettingsRequest` in `proto/cline/state.proto` (for webview update requests), then run `npm run protos`
- Include the key in `Controller.getStateToPostToWebview()` (`src/core/controller/index.ts`)
- Ensure `ExtensionState` and webview defaults include the key (`src/shared/ExtensionMessage.ts`, `webview-ui/src/context/ExtensionStateContext.tsx`)
If this round-trip wiring is missing, the backend value can update but the toggle in webview appears stuck or reverts.
## StateManager Cache vs Direct globalState Access
StateManager uses an in-memory cache populated during `StateManager.initialize(context)` in `common.ts`. For most state, use `controller.stateManager.setGlobalState()`/`getGlobalStateKey()`.
Exception: State needed immediately at extension startup (before cache is ready)
When Window A sets state and immediately opens Window B, the new window's StateManager cache is populated from `context.globalState` during initialization. If you need to read state in Window B right at startup (e.g., in `common.ts` during `initialize()`), read directly from `context.globalState.get()` instead of StateManager's cache.
Example pattern (see `lastShownAnnouncementId` and `worktreeAutoOpenPath`):
```typescript
// Writing (normal pattern)
controller.stateManager.setGlobalState("myKey", value)
// Reading at startup in common.ts (bypass cache)
const value = context.globalState.get<string>("myKey")
```
This is only needed for cross-window state read during the brief startup window before StateManager cache is fully usable. Normal state access after initialization should use StateManager.
## ChatRow Cancelled/Interrupted States
When a ChatRow displays a loading/in-progress state (spinner), you must handle what happens when the task is cancelled. This is non-obvious because cancellation doesn't update the message content—you have to infer it from context.
**The pattern:**
1. A message has a `status` field (e.g., `"generating"`, `"complete"`, `"error"`) stored in `message.text` as JSON
2. When cancelled mid-operation, the status stays `"generating"` forever—no one updates it
3. To detect cancellation, check TWO conditions:
- `!isLast` — if this message is no longer the last message, something else happened after it (interrupted)
- `lastModifiedMessage?.ask === "resume_task" || "resume_completed_task"` — task was just cancelled and is waiting to resume
**Example from `generate_explanation`:**
```tsx
const wasCancelled =
explanationInfo.status === "generating" &&
(!isLast ||
lastModifiedMessage?.ask === "resume_task" ||
lastModifiedMessage?.ask === "resume_completed_task")
const isGenerating = explanationInfo.status === "generating" && !wasCancelled
```
**Why both checks?**
- `!isLast` catches: cancelled → resumed → did other stuff → this old message is stale
- `lastModifiedMessage?.ask === "resume_task"` catches: just cancelled, hasn't resumed yet, this message is still technically "last"
**See also:** `BrowserSessionRow.tsx` uses similar pattern with `isLastApiReqInterrupted` and `isLastMessageResume`.
**Backend side:** When streaming is cancelled, clean up properly (close tabs, clear comments, etc.) by checking `taskState.abort` after the streaming function returns.
+45 -10
View File
@@ -1,19 +1,54 @@
#!/usr/bin/env bash
# PostToolUse Hook Example
#
# This hook runs AFTER a tool is executed. It can:
# 1. Observe tool results and outcomes
# 2. Add context for FUTURE tool uses via contextModification
# 3. Log or track tool usage patterns
#
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
# The tool has already completed when this hook runs.
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
# Read the hook input (JSON via stdin)
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
# Extract tool information
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName // "unknown"')
parameters=$(echo "$input" | jq -r '.postToolUse.parameters // {}')
result=$(echo "$input" | jq -r '.postToolUse.result // ""')
success=$(echo "$input" | jq -r '.postToolUse.success // false')
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs // 0')
# Example 1: Learning from file operations
# Track successful file creations to build context about project structure
# if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
# path=$(echo "$parameters" | jq -r '.path // ""')
# cat <<EOF
# {
# "shouldContinue": true,
# "contextModification": "FILE_OPERATIONS: Successfully created '$path'. Future operations should maintain consistency with this file's patterns and structure."
# }
# EOF
# exit 0
# fi
# Example 2: Performance monitoring
# Warn about slow operations
# if [[ "$execution_time" -gt 5000 ]]; then
# cat <<EOF
# {
# "shouldContinue": true,
# "contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms to complete. Consider optimizing future similar operations or breaking them into smaller steps."
# }
# EOF
# exit 0
# fi
# Example 3: Context injection for future tool uses
# The context will be available in the NEXT API request
cat <<EOF
{
"cancel": false,
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PostToolUse hook custom errorMessage"
"shouldContinue": true,
"contextModification": "TOOL_RESULT: The tool '$tool_name' completed with success=$success. Consider validating the results before proceeding to the next step."
}
EOF
+16
View File
@@ -0,0 +1,16 @@
@echo off
REM PostToolUse Hook Example - Windows Batch Version
REM
REM This hook runs AFTER a tool is executed. It can:
REM 1. Observe tool results and outcomes
REM 2. Add context for FUTURE tool uses via contextModification
REM 3. Log or track tool usage patterns
REM
REM IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
REM The tool has already completed when this hook runs.
REM Simple example: Always allow continuation
echo {"shouldContinue": true}
REM To add context based on results, use:
REM echo {"shouldContinue": true, "contextModification": "TOOL_RESULT: Operation completed successfully"}
@@ -0,0 +1,38 @@
@echo off
REM PreToolUse Hook - Advanced Example with Input Parsing
REM This version reads and parses the JSON input from stdin using PowerShell
setlocal enabledelayedexpansion
REM Read all input from stdin using PowerShell
for /f "usebackq delims=" %%i in (`powershell -NoProfile -Command "[Console]::In.ReadToEnd()"`) do set "INPUT=%%i"
REM Parse JSON and make decisions using PowerShell
REM Note: We use -replace to handle special characters in the input
powershell -NoProfile -Command ^
"$input = '%INPUT%' -replace \"'\", \"''\"; ^
try { ^
$json = $input | ConvertFrom-Json; ^
$toolName = $json.preToolUse.toolName; ^
$shouldBlock = $false; ^
$errorMsg = ''; ^
$context = ''; ^
if ($toolName -eq 'write_to_file') { ^
$path = $json.preToolUse.parameters.path; ^
if ($path -match '\\.js$') { ^
$shouldBlock = $true; ^
$errorMsg = 'Cannot create .js files in TypeScript project'; ^
$context = 'WORKSPACE_RULES: Use .ts/.tsx extensions only'; ^
} ^
} ^
$output = @{ ^
shouldContinue = -not $shouldBlock; ^
}; ^
if ($errorMsg) { $output.errorMessage = $errorMsg }; ^
if ($context) { $output.contextModification = $context }; ^
$output | ConvertTo-Json -Compress; ^
} catch { ^
@{ shouldContinue = $true } | ConvertTo-Json -Compress; ^
}"
endlocal
+33 -10
View File
@@ -1,19 +1,42 @@
#!/usr/bin/env bash
# PreToolUse Hook Example
#
# This hook runs BEFORE a tool is executed. It can:
# 1. Block execution by returning {"shouldContinue": false}
# 2. Add context for FUTURE tool uses via contextModification
# 3. Validate tool parameters
#
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
# The tool parameters are already determined when this hook runs.
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
# Read the hook input (JSON via stdin)
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
# Extract tool information
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName // "unknown"')
parameters=$(echo "$input" | jq -r '.preToolUse.parameters // {}')
# Example 1: Validation - Block invalid operations
# Uncomment to prevent creating .js files in a TypeScript project
# if [[ "$tool_name" == "write_to_file" ]]; then
# path=$(echo "$parameters" | jq -r '.path // ""')
# if [[ "$path" == *.js ]]; then
# cat <<EOF
# {
# "shouldContinue": false,
# "errorMessage": "VALIDATION FAILED: Cannot create .js files in TypeScript project. Please use .ts extension instead.",
# "contextModification": "WORKSPACE_RULES: This is a strict TypeScript project. All new files must use .ts or .tsx extensions."
# }
# EOF
# exit 0
# fi
# fi
# Example 2: Context injection for future tool uses
# The context will be available in the NEXT API request after this tool completes
cat <<EOF
{
"cancel": false,
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PreToolUse hook custom errorMessage"
"shouldContinue": true,
"contextModification": "WORKSPACE_RULES: [For future tool uses] This is a TypeScript React project. When creating files, use .ts/.tsx extensions and include detailed comments explaining the purpose and usage of each function."
}
EOF
+15
View File
@@ -0,0 +1,15 @@
@echo off
REM PreToolUse Hook Example - Windows Batch Version
REM
REM This hook runs BEFORE a tool is executed. It can:
REM 1. Block execution by returning {"shouldContinue": false}
REM 2. Add context for FUTURE tool uses via contextModification
REM 3. Validate tool parameters
REM
REM IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
REM Simple example: Always allow execution with workspace context
echo {"shouldContinue": true, "contextModification": "WORKSPACE_RULES: This is a TypeScript project. Use .ts/.tsx extensions for new files."}
REM To block execution, use:
REM echo {"shouldContinue": false, "errorMessage": "Operation not allowed"}
+82 -215
View File
@@ -2,11 +2,7 @@
## Overview
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
- **Global hooks directory**: `~/Documents/Cline/Hooks/` (applies to all workspaces)
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
Hooks run automatically when enabled.
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks are placed in the `.clinerules/hooks/` directory and run automatically when enabled.
## Enabling Hooks
@@ -17,86 +13,62 @@ Hooks run automatically when enabled.
## Available Hooks
### TaskStart Hook
- **When**: Runs when a NEW task is started (not when resuming)
- **Purpose**: Initialize task context, validate task requirements, set up environment
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
- **Workspace Location**: `.clinerules/hooks/TaskStart`
### TaskResume Hook
- **When**: Runs when an EXISTING task is resumed (after user clicks resume button)
- **Purpose**: Validate resumed task state, restore context, check for changes since last run
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
- **Workspace Location**: `.clinerules/hooks/TaskResume`
### TaskCancel Hook
- **When**: Runs when a task is cancelled or a hook is aborted by the user (only if there's actual active work or work was started)
- **Purpose**: Clean up resources, log cancellation, save state
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
- **Note**: This hook is NOT cancellable
### TaskComplete Hook (coming soon!)
- **When**: Runs when a task is marked as complete
- **Purpose**: Log completion status, perform final cleanup, generate reports
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
### UserPromptSubmit Hook
- **When**: Runs when the user submits a prompt/message (initial task, resume, or feedback)
- **Purpose**: Validate user input, preprocess prompts, add context to user messages
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
### PreToolUse Hook
- **When**: Runs BEFORE a tool is executed
- **Purpose**: Validate parameters, block execution, or add context
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
- **File**: `.clinerules/hooks/PreToolUse` (Unix/Linux/macOS) or `.clinerules/hooks/PreToolUse.bat/.cmd/.exe` (Windows)
### PostToolUse Hook
- **When**: Runs AFTER a tool completes
- **Purpose**: Observe results, track patterns, or add context
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
- **File**: `.clinerules/hooks/PostToolUse` (Unix/Linux/macOS) or `.clinerules/hooks/PostToolUse.bat/.cmd/.exe` (Windows)
### PreCompact Hook (coming soon!)
- **When**: Runs BEFORE the conversation context is compacted/truncated
- **Purpose**: Observe compaction events, log context management, track token usage
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
- **Workspace Location**: `.clinerules/hooks/PreCompact`
## Platform-Specific Guidance
## Cross-Platform Hook Format
### Windows Hooks
Cline uses a git-style approach for hooks that works consistently across all platforms:
Windows hooks use different file extensions and syntax than Unix hooks. Cline automatically searches for hooks using your system's `PATHEXT` environment variable (typically `.COM;.EXE;.BAT;.CMD;.VBS;.JS;.WSF;.MSC`).
### Hook Files (All Platforms)
- **No file extensions**: Hooks are named exactly `PreToolUse` or `PostToolUse` (no `.bat`, `.cmd`, `.sh` etc.)
- **Shebang required**: First line must be a shebang (e.g., `#!/usr/bin/env bash` or `#!/usr/bin/env node`)
- **Executable on Unix**: On Unix/Linux/macOS, hooks must be executable: `chmod +x PreToolUse`
- **Windows**: Not currently supported.
**Recommended approach for Windows:**
- Use `.cmd` or `.bat` batch files (most compatible)
- See `PreToolUse.example.cmd` and `PostToolUse.example.cmd` for simple examples
- See `PreToolUse.advanced.example.cmd` for PowerShell-based JSON parsing
### How It Works
Like git hooks, Cline executes hook files through a shell that interprets the shebang line:
- On Unix/Linux/macOS: Native shell execution with shebang support
This means:
- ✅ Same hook script works on all platforms
- ✅ Write once, run anywhere
- ✅ Use any scripting language (bash, node, python, etc.)
### Creating Hooks
**On Unix/Linux/macOS:**
```bash
# Create hook file
nano ~/Documents/Cline/Hooks/PreToolUse
# Make executable
chmod +x ~/Documents/Cline/Hooks/PreToolUse
**Simple Windows Hook Example:**
```batch
@echo off
REM Always allow execution with context
echo {"shouldContinue": true, "contextModification": "WORKSPACE_RULES: TypeScript project"}
```
**Advanced Windows Hook with Input Parsing:**
```batch
@echo off
setlocal enabledelayedexpansion
REM Read stdin using PowerShell
for /f "usebackq delims=" %%i in (`powershell -Command "[Console]::In.ReadToEnd()"`) do set "INPUT=%%i"
REM Parse and process JSON
powershell -Command ^
"$json = '%INPUT%' | ConvertFrom-Json; ^
$output = @{shouldContinue = $true}; ^
$output | ConvertTo-Json -Compress"
```
**Tips for Windows:**
- Batch files don't require `chmod +x` - they're executable by default
- Use `REM` for comments instead of `#`
- PowerShell is available on all modern Windows systems
- For complex logic, consider PowerShell scripts (`.ps1`) or compiled executables (`.exe`)
### Unix/Linux/macOS Hooks
Unix hooks are shell scripts without file extensions:
- Must be executable: `chmod +x PreToolUse`
- Must include shebang: `#!/usr/bin/env bash` or `#!/usr/bin/env node`
- See `PreToolUse.example` and `PostToolUse.example` for bash examples
## Context Injection Timing
**IMPORTANT**: Context injected by hooks affects **FUTURE AI decisions**, not the current tool execution.
@@ -137,46 +109,11 @@ All hooks receive:
```json
{
"clineVersion": "string",
"hookName": "TaskStart" | "TaskResume" | "TaskCancel" | "TaskComplete" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PreCompact",
"hookName": "PreToolUse" | "PostToolUse",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskStart": { // Only for TaskStart
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"initialTask": "string"
}
},
"taskResume": { // Only for TaskResume
"taskMetadata": {
"taskId": "string",
"ulid": "string"
},
"previousState": {
"lastMessageTs": "string",
"messageCount": "string",
"conversationHistoryDeleted": "string"
}
},
"taskCancel": { // Only for TaskCancel
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"completionStatus": "string"
}
},
"taskComplete": { // Only for TaskComplete
"taskMetadata": {
"taskId": "string",
"ulid": "string"
}
},
"userPromptSubmit": { // Only for UserPromptSubmit
"prompt": "string",
"attachments": ["string"]
},
"preToolUse": { // Only for PreToolUse
"toolName": "string",
"parameters": {}
@@ -187,11 +124,6 @@ All hooks receive:
"result": "string",
"success": boolean,
"executionTimeMs": number
},
"preCompact": { // Only for PreCompact
"contextSize": number,
"messagesToCompact": number,
"compactionStrategy": "string"
}
}
```
@@ -201,21 +133,38 @@ All hooks receive:
All hooks must return:
```json
{
"cancel": boolean, // Required: false to continue, true to block execution
"contextModification": "string", // Optional: Context for future AI decisions
"shouldContinue": boolean, // Required: Allow or block execution
"contextModification": "string", // Optional: Context for future tool uses
"errorMessage": "string" // Optional: Error details if blocking
}
```
**Note**: The `cancel` field works as follows:
- `false` (or omitted): Allow execution to continue
- `true`: Block execution and show error message to user
## Context Modification Format
Use structured prefixes to help the AI understand context type:
- `WORKSPACE_RULES:` - Project conventions and requirements
- `FILE_OPERATIONS:` - File creation/modification patterns
- `TOOL_RESULT:` - Outcomes of tool executions
- `PERFORMANCE:` - Performance concerns
- `VALIDATION:` - Validation results
- Custom prefixes as needed
Example:
```bash
cat <<EOF
{
"shouldContinue": true,
"contextModification": "WORKSPACE_RULES: This is a TypeScript project. All new files must use .ts or .tsx extensions."
}
EOF
```
## Hook Execution Limits
- **Timeout**: Hooks must complete within 30 seconds (configurable via `HOOK_EXECUTION_TIMEOUT_MS`)
- **Context Size**: Context modifications are limited to 50KB (configurable via `MAX_CONTEXT_MODIFICATION_SIZE`)
- **Error Handling**: Expected errors (file not found, permission denied, not a directory) are handled silently; unexpected file system errors are propagated
- **Timeout**: Hooks must complete within 30 seconds
- **Context Size**: Context modifications are limited to 50KB
- **Error Handling**: Unexpected file system errors are propagated; expected errors (file not found, permission denied) are handled silently
## Common Use Cases
@@ -230,15 +179,15 @@ path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
cat <<EOF
{
"cancel": true,
"shouldContinue": false,
"errorMessage": "Cannot create .js files in TypeScript project",
"contextModification": "Use .ts/.tsx extensions only"
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
}
EOF
exit 0
fi
echo '{"cancel": false}'
echo '{"shouldContinue": true}'
```
### 2. Context Building - Learn from Operations
@@ -253,12 +202,12 @@ path=$(echo "$input" | jq -r '.postToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
"shouldContinue": true,
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
}
EOF
else
echo '{"cancel": false}'
echo '{"shouldContinue": true}'
fi
```
@@ -273,12 +222,12 @@ tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
if [[ "$execution_time" -gt 5000 ]]; then
cat <<EOF
{
"cancel": false,
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
"shouldContinue": true,
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
}
EOF
else
echo '{"cancel": false}'
echo '{"shouldContinue": true}'
fi
```
@@ -292,100 +241,17 @@ input=$(cat)
echo "$input" >> ~/.cline/hook-logs/tool-usage.jsonl
# Allow execution
echo '{"cancel": false}'
echo '{"shouldContinue": true}'
```
## Global vs Workspace Hooks
Cline supports two levels of hooks:
### Global Hooks
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
- **Scope**: Apply to ALL workspaces and projects
- **Use Case**: Organization-wide policies, personal preferences, universal validations
- **Priority**: Order not guaranteed when combined with workspace hooks
### Workspace Hooks
- **Location**: `.clinerules/hooks/` in each workspace root
- **Scope**: Apply only to the specific workspace
- **Use Case**: Project-specific rules, team conventions, repository requirements
- **Priority**: Order not guaranteed when combined with global hooks
### Hook Execution
When multiple hooks exist (global and/or workspace):
- All hooks for a given step are executed **concurrently** using `Promise.all`
- **Execution order is not guaranteed** - hooks run in parallel
- If ALL hooks allow execution (`cancel: false`), the tool proceeds
- If ANY hook blocks (`cancel: true`), execution is blocked
**Result Combination:**
- `cancel`: If ANY hook returns `true`, execution is blocked
- `contextModification`: All context strings are concatenated with double newlines (`\n\n`)
- `errorMessage`: All error messages are concatenated with single newlines (`\n`)
### Setting Up Global Hooks
1. The global hooks directory is automatically created at:
- macOS/Linux: `~/Documents/Cline/Hooks/`
2. Add your hook script:
```bash
# Unix/Linux/macOS
nano ~/Documents/Cline/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
```
3. Enable hooks in Cline settings
### Example: Global + Workspace Hooks
**Global Hook** (applies to all projects):
```bash
#!/usr/bin/env bash
# ~/Documents/Cline/Hooks/PreToolUse
# Universal rule: Never delete package.json
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *"package.json"* ]]; then
echo '{"cancel": true, "errorMessage": "Global policy: Cannot modify package.json"}'
exit 0
fi
echo '{"cancel": false}'
```
**Workspace Hook** (applies to specific project):
```bash
#!/usr/bin/env bash
# .clinerules/hooks/PreToolUse
# Project rule: Only TypeScript files
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
echo '{"cancel": true, "errorMessage": "Project rule: Use .ts files only"}'
exit 0
fi
echo '{"cancel": false}'
```
**All hooks must allow execution for the tool to proceed.** Hooks may execute concurrently.
## Multi-Root Workspaces
If you have multiple workspace roots, you can place hooks in each root's `.clinerules/hooks/` directory. All hooks (global and workspace) may execute concurrently. Their results will be combined:
If you have multiple workspace roots, you can place hooks in each root's `.clinerules/hooks/` directory. All hooks will run and their results will be combined:
- **cancel**: If ANY hook returns `true`, execution is blocked
- **shouldContinue**: If ANY hook returns false, execution is blocked
- **contextModification**: All context modifications are concatenated
- **errorMessage**: All error messages are concatenated
**Note:** No execution order is guaranteed between hooks from different directories.
## Troubleshooting
### Hook Not Running
@@ -401,6 +267,7 @@ If you have multiple workspace roots, you can place hooks in each root's `.cline
### Context Not Affecting Behavior
- Remember: context affects FUTURE decisions, not the current tool
- Use PreToolUse for validation (blocking) if you need immediate effect
- Ensure context modifications are clear and actionable
- Check that context isn't being truncated (50KB limit)
-19
View File
@@ -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
-19
View File
@@ -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
-19
View File
@@ -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
-90
View File
@@ -1,90 +0,0 @@
# Networking & Proxy Support
To ensure Cline works correctly in all environments (VSCode, JetBrains, CLI) and with various network configurations (especially corporate proxies), strictly follow these guidelines for all network activity.
In extension code, do NOT use the global `fetch` or a default `axios` instance. (Note, `shared/net.ts` is exempt from these rules because it sets up the fetch wrappers.) In Webview code, you SHOULD use global `fetch`.
Global `fetch` and default `axios` do not automatically pick up proxy configurations in all environments (specifically JetBrains and CLI). You MUST use the provided utilities in `@/shared/net` which handle proxy agent configuration. In the webview, the browser/embedder handles proxies.
## Guidelines
### 1. Using `fetch`
Instead of `fetch(...)`, import the proxy-aware wrapper:
```typescript
import { fetch } from '@/shared/net'
// Usage is identical to global fetch
const response = await fetch('https://api.example.com/data')
```
### 2. Using `axios`
When using `axios`, you must apply the settings from `getAxiosSettings()`:
```typescript
import axios from 'axios'
import { getAxiosSettings } from '@/shared/net'
const response = await axios.get('https://api.example.com/data', {
headers: { 'Authorization': '...' },
...getAxiosSettings() // <--- CRITICAL: Injects the proxy agent if needed
})
```
### 3. Third-Party Clients (OpenAI, Ollama, etc.)
Most API client libraries allow you to customize the `fetch` implementation. You **MUST** pass the proxy-aware `fetch` to these clients.
**Example (OpenAI):**
```typescript
import OpenAI from "openai"
import { fetch } from "@/shared/net"
this.client = new OpenAI({
apiKey: '...',
fetch, // <--- CRITICAL: Pass our fetch wrapper
})
```
### 4. Tests
Use `mockFetchForTesting` to mock the underlying fetch implementation.
**Example (callback):**
```
import { mockFetchForTesting } from "@/shared/net"
...
let mockFetch = ...
mockFetchForTesting(mockFetch, () => {
// This calls mockFetch
fetch('https://foo.example').then(...)
})
// Original fetch is restored immediately when the call returns.
```
**Example (Promise):**
```
import { mockFetchForTesting } from "@/shared/net"
...
let mockFetch = ...
await mockFetchForTesting(mockFetch, async () => {
await ...
// This calls mockFetch
await fetch('https://foo.example')
...
})
// Original fetch is restored when the Promise from the callback settles
```
## Verification
If you are adding a new network call or integration:
1. Check `@/shared/net.ts` is imported.
2. Ensure `fetch` or `getAxiosSettings` is being used.
3. Verify that third-party clients are configured to use the custom fetch.
-64
View File
@@ -1,64 +0,0 @@
# Storage Architecture
Global settings, secrets and workspace state are stored in **file-backed JSON stores** under `~/.cline/data/`. This is the shared storage layer used by VSCode, CLI, and JetBrains.
## Key Abstractions
### `StorageContext` (src/shared/storage/storage-context.ts)
The entry point. Created via `createStorageContext()` and passed to `StateManager.initialize()`. Contains three `ClineFileStorage` instances:
- `globalState``~/.cline/data/globalState.json`
- `secrets``~/.cline/data/secrets.json` (mode 0o600)
- `workspaceState``~/.cline/data/workspaces/<hash>/workspaceState.json`
### `ClineFileStorage` (src/shared/storage/ClineFileStorage.ts)
Synchronous JSON key-value store backed by a single file. Supports `get()`, `set()`, `setBatch()`, `delete()`. Writes are atomic (write-then-rename).
### `StateManager` (src/core/storage/StateManager.ts)
In-memory cache on top of `StorageContext`. All runtime reads hit the cache; writes update cache immediately and debounce-flush to disk.
## ⚠️ Do NOT Use VSCode's ExtensionContext for Storage
**Do not** read from or write to `context.globalState`, `context.workspaceState`, or `context.secrets` for persistent data. These are VSCode-specific and not available on CLI or JetBrains.
Instead, use:
```typescript
// Reading state
StateManager.get().getGlobalStateKey("myKey")
StateManager.get().getSecretKey("mySecretKey")
StateManager.get().getWorkspaceStateKey("myWsKey")
// Writing state
StateManager.get().setGlobalState("myKey", value)
StateManager.get().setSecret("mySecretKey", value)
StateManager.get().setWorkspaceState("myWsKey", value)
```
Remember that your data may be read by a different client than the one that wrote it. For example, a value written by Cline in JetBrains may be read by Cline CLI.
## VSCode Migration (src/hosts/vscode/vscode-to-file-migration.ts)
On VSCode startup, a migration copies data from VSCode's `ExtensionContext` storage into the file-backed stores. This runs in `src/common.ts` before `StateManager.initialize()`.
- **Sentinel**: `__vscodeMigrationVersion` key in global state and workspace state — prevents re-migration.
- **Merge strategy**: File store wins. Existing values are never overwritten.
- **Safe downgrade**: VSCode storage is NOT cleared, so older extension versions still work.
## Adding New Storage Keys
1. Add to `src/shared/storage/state-keys.ts` (see existing patterns)
2. Read/write via `StateManager` (NOT via `context.globalState`)
3. If adding a secret, add to `SecretKeys` array in `state-keys.ts`
## File Layout
```
~/.cline/
data/
globalState.json # Global settings & state
secrets.json # API keys (mode 0o600)
tasks/
taskHistory.json # Task history (separate file)
workspaces/
<hash>/
workspaceState.json # Per-workspace toggles
```
@@ -1,29 +0,0 @@
# Address PR Comments
Review and address all comments on the current branch's PR.
## Steps
1. Get the current branch name and find the associated PR:
```bash
gh pr view --json number,title,body
```
2. Understand the PR context:
- Get the full diff: `git diff origin/main...HEAD`
- Read the changed files to understand what the PR is doing
- Read related files if needed to understand the broader context
- Understand the intent and spirit of the changes, not just the code
3. Fetch all PR comments:
- Inline comments: `gh api repos/{owner}/{repo}/pulls/{pr_number}/comments`
- General comments: `gh pr view {pr_number} --json comments,reviews`
4. Present a summary of all comments with your recommendation for each (apply, skip, or respond). Ignore bot noise (changeset-bot, CI status, etc.).
5. **Wait for my approval** before proceeding.
6. After approval:
- Apply code changes and commit
- Reply to comments that were addressed or intentionally skipped
- Push commits
+2 -2
View File
@@ -122,9 +122,9 @@ const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
title="Previous Updates:"
classNames={{
trigger: "bg-transparent border-0 pl-0 pb-0 w-fit",
title: "font-bold text-(--vscode-foreground)",
title: "font-bold text-[var(--vscode-foreground)]",
indicator:
"text-(--vscode-foreground) mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
"text-[var(--vscode-foreground)] mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
}}>
<ul style={ulStyle}>
<li>
@@ -1,49 +0,0 @@
# Find Best Reviewers for Current Branch
Analyze my current branch to find the best people to review my PR based on **domain expertise** and git history.
## Steps
1. Get the current branch name and verify it's not `main`
2. Get the diff between the current branch and `origin/main`:
- Use `git diff origin/main...HEAD --name-only` to get changed files
- Use `git diff origin/main...HEAD` to understand the nature/spirit of the changes
3. **Identify the domain/feature area** being changed:
- Read the diff carefully to understand WHAT is being changed conceptually (e.g., "slash commands", "authentication", "API client", "UI components")
- This semantic understanding is crucial for finding the right reviewers
4. Find domain experts by searching for related files and their contributors:
- Identify all files related to the feature/domain (not just the ones changed)
- Example: if changing slash commands, find ALL slash-command related files across the codebase
- Use `git log --format="%an <%ae>" -- <related-files-pattern>` to find who has expertise in that domain
5. For additional context, also gather:
- `git blame -L <start>,<end> origin/main -- <file-path>` for exact lines changed
- Recent commit activity on related files
6. Score and rank contributors by:
- **Highest weight: Domain expertise** - who has the most commits to files in this feature area (even files not touched by this PR)
- **Medium weight: Direct file expertise** - commits to the specific files being changed
- **Lower weight: Line-level ownership** - authored the exact lines being modified
7. Exclude myself (check against my git config user.email)
8. Present the top 5 reviewers as an ordered list
## Output Format
Output an ordered list:
1. **Name** - Domain expert: 15 commits to slash-command related files, authored core parsing logic
2. **Name** - 8 commits to affected files, recently added the feature being modified
3. ...
## Commands Reference
```bash
git config user.email
git diff origin/main...HEAD --name-only
git diff origin/main...HEAD
# Find related files for a domain (adjust pattern based on what you learn from the diff)
find . -type f \( -name "*slash-command*" -o -name "*SlashCommand*" \) | head -20
# Get contributors for related files
find . -type f \( -name "*slash-command*" -o -name "*SlashCommand*" \) -print0 | xargs -0 git log --format="%an <%ae>" -- | sort | uniq -c | sort -rn
git log --format="%an <%ae>" -- <file> | sort | uniq -c | sort -rn
git blame -L 10,20 origin/main -- <file>
```
Do NOT ask questions - analyze the changes, identify the domain, and output the reviewer list.
-194
View File
@@ -1,194 +0,0 @@
# Hotfix Release
Create a hotfix release by cherry-picking specific commits from main onto the latest release tag.
## Overview
This workflow helps you:
1. Select specific commits from main to include in a hotfix
2. Create a release notes commit on main (changelog + version bump)
3. Cherry-pick everything onto the latest release tag
4. Tag and push the new release
## Step 1: Setup and Gather Information
First, ensure we're on main and up to date:
```bash
git checkout main && git pull origin main
```
Get the latest release tag:
```bash
git tag --sort=-v:refname | head -1
```
## Step 2: Present Commits Since Last Release
Show all commits on main since the last release tag:
```bash
LAST_TAG=$(git tag --sort=-v:refname | head -1)
git log ${LAST_TAG}..HEAD --oneline --format="%h %s (%an)"
```
Also get the commit messages already on the tag (to identify previously cherry-picked commits). Note: Run these as separate commands to avoid shell parsing issues with parentheses in author names:
```bash
LAST_TAG=$(git tag --sort=-v:refname | head -1)
PREV_TAG=$(git tag --sort=-v:refname | head -2 | tail -1)
```
```bash
git log $PREV_TAG..$LAST_TAG --oneline --format="%s"
```
**Present the list** to the user in a numbered format with commit hash, subject, and author. For any commits whose subject line already appears in the tag's history (previously cherry-picked in an earlier hotfix) or are "Release Notes" commits, add `(already in previous hotfix)` or `(release notes - skip)` after them so the user knows to skip those.
Ask which commits to include in the hotfix.
Use the ask_followup_question tool to let the user specify which commits they want (by number or hash).
## Step 3: Analyze Selected Commits
For each selected commit:
1. Get the full commit message: `git show --no-patch --format="%B" <hash>`
2. Get the diff to understand the change: `git show <hash> --stat`
3. Find the associated PR if any: `gh pr list --search "<hash>" --state merged --json number,title --jq '.[0]'`
Build a mental model of what these changes do for the changelog.
## Step 4: Determine New Version Number
Parse the current version from package.json and the last tag:
```bash
LAST_TAG=$(git tag --sort=-v:refname | head -1)
echo "Last release: $LAST_TAG"
cat package.json | grep '"version"'
```
Hotfixes always increment the patch version (e.g., 3.40.0 -> 3.40.1, or 3.40.1 -> 3.40.2).
**Ask the user to confirm the new version number.**
## Step 5: Create Release Notes Commit on Main
On the main branch, create a commit that updates:
1. **CHANGELOG.md** - Add a new section for the hotfix version at the top:
```markdown
## [3.40.1]
- Description of fix 1
- Description of fix 2
```
Write clear, user-friendly descriptions based on your analysis of the commits.
2. **package.json** - Update the version field to the new version
3. **Delete changesets** for the commits being included in the hotfix. This prevents the changeset bot from including duplicate entries in the next regular release.
Find and delete the changeset files associated with the selected commits:
```bash
ls .changeset/
```
Each changeset file in `.changeset/` corresponds to a PR. Read them to identify which ones belong to the commits you're hotfixing, then delete those files.
**Skip running `npm run install:all`** - the automation handles outdated lockfiles.
Commit with message format: `v{VERSION} Release Notes (hotfix)`
In the commit body, mention:
- This is for a hotfix release
- List the cherry-picked commits that will be included
```bash
git add CHANGELOG.md package.json .changeset/
git commit -m "v3.40.1 Release Notes (hotfix)
Hotfix release including:
- <commit1-hash>: <description>
- <commit2-hash>: <description>
"
```
Push to main:
```bash
git push origin main
```
## Step 6: Build the Hotfix on the Tag
Checkout the last release tag (detached HEAD):
```bash
LAST_TAG=$(git tag --sort=-v:refname | head -1)
git checkout $LAST_TAG
```
Cherry-pick the selected commits in order:
```bash
git cherry-pick <commit1-hash>
git cherry-pick <commit2-hash>
# ... etc
```
Finally, cherry-pick the release notes commit you just pushed to main:
```bash
# Get the hash of the release notes commit (should be HEAD of main)
RELEASE_NOTES_COMMIT=$(git rev-parse main)
git cherry-pick $RELEASE_NOTES_COMMIT
```
## Step 7: Tag and Push
After all cherry-picks are applied successfully:
```bash
# Tag the new release
git tag v{VERSION}
# Push the tag to remote
git push origin v{VERSION}
```
## Step 8: Return to Main and Summary
Return to main branch:
```bash
git checkout main
```
**Copy a Slack announcement message to clipboard** with the version and PR links for each included fix:
```
VS Code Hotfix v{VERSION} Published
- Description of fix 1 https://github.com/cline/cline/pull/{PR_NUMBER}
- Description of fix 2 https://github.com/cline/cline/pull/{PR_NUMBER}
```
Present a final summary:
- New version: v{VERSION}
- Tag pushed: yes
- Commits included: (list them)
- Slack message copied to clipboard: yes
Remind the user to:
1. Manually trigger the publish release GitHub Action at: https://github.com/cline/cline/actions/workflows/publish.yml (paste `v{VERSION}` as the tag)
2. Post the Slack message to announce the hotfix
## Important Notes
- This workflow does NOT create a release branch - only tags
- The release notes commit goes to main first, then gets cherry-picked to the tag
- This keeps main's history accurate while allowing hotfix releases from tags
- If cherry-pick conflicts occur, resolve them before continuing
-232
View File
@@ -1,232 +0,0 @@
# Release
Prepare and publish a release from the open changeset PR.
## Overview
This workflow helps you:
1. Find and checkout the open changeset PR
2. Clean up the changelog (fix version format, wordsmith entries)
3. Push changes back to the PR branch
4. Merge with proper commit message format
5. Tag and push the release (after verifying the commit)
6. Trigger the publish workflow
7. Update GitHub release notes
8. Provide final summary with Slack announcement
## Step 1: Find the Changeset PR
Look for the open changeset PR:
```bash
gh pr list --search "Changeset version bump" --state open --json number,title,headRefName,url
```
If no PR is found, inform the user there's no changeset PR ready. They may need to:
- Merge PRs with changesets to main first
- Manually trigger the Changeset Converter workflow at: https://github.com/cline/cline/actions/workflows/changeset-converter.yml
## Step 2: Gather PR Information
Get the PR details:
```bash
PR_NUMBER=<number from step 1>
gh pr view $PR_NUMBER --json body,files,headRefName
```
Checkout the PR branch:
```bash
git fetch origin changeset-release/main
git checkout changeset-release/main
```
If the branch has diverged from remote, reset to the remote version:
```bash
git reset --hard origin/changeset-release/main
```
## Step 3: Analyze the Changes
Read the current CHANGELOG.md to see what the automation generated:
```bash
head -50 CHANGELOG.md
```
Get the version from package.json:
```bash
cat package.json | grep '"version"'
```
**Present to the user:**
- The version number that will be released
- The raw changelog entries from the changeset PR
- Whether this is a patch, minor, or major release
## Step 4: Clean Up the Changelog
The changelog needs these fixes:
1. **Add brackets to version number**: Change `## 3.44.1` to `## [3.44.1]`
2. **No category headers**: Don't use `### Added`, `### Fixed`, etc. Just a flat list of bullet points.
3. **Order entries from most important to least important**:
- Lead with major new features or significant fixes users care about
- End with minor fixes or internal changes
4. **Write user-friendly descriptions**:
- This is for end users, not developers—explain what changed in plain language
- Remove commit hashes from the beginning of lines (the automation adds these)
- Look at the actual commit diffs (`git show <hash>`) and PRs to understand what changed
- Write colorful descriptions that explain the value and impact, not just technical details
- Consolidate related changes into single entries when appropriate
**Ask the user** to review the proposed changelog changes before applying them. Show them:
- Current (raw) changelog section
- Proposed (cleaned) changelog section
Once approved, apply the changes to CHANGELOG.md.
## Step 5: Commit and Push Changes
After making changelog edits:
```bash
git add CHANGELOG.md
git commit -m "Clean up changelog formatting"
git push origin changeset-release/main
```
## Step 6: Merge the PR
**Ask the user to confirm** they're ready to merge.
Merge the PR with the proper commit message format:
```bash
VERSION=<version from package.json>
gh pr merge $PR_NUMBER --squash --subject "v${VERSION} Release Notes" --body ""
```
**If merge is blocked by branch protection:**
- Users with admin privileges can add the `--admin` flag to bypass
- Users without admin privileges need to get the PR approved through normal review first before merging
## Step 7: Tag the Release
After the merge completes, checkout main and pull:
```bash
git checkout main
git pull origin main
```
**IMPORTANT: Verify the latest commit is the release commit before tagging:**
```bash
git log -1 --oneline
```
Confirm the commit message matches `v{VERSION} Release Notes` (e.g., `v3.44.1 Release Notes`). Do NOT blindly tag HEAD without verification.
Once verified, tag and push:
```bash
VERSION=<version>
git tag v${VERSION}
git push origin v${VERSION}
```
## Step 8: Trigger Publish Workflow
**Copy the tag to clipboard** so the user can easily paste it into the GitHub Actions workflow:
```bash
echo -n "v{VERSION}" | pbcopy
```
**Tell the user to trigger the publish workflow:**
1. Go to: https://github.com/cline/cline/actions/workflows/publish.yml
2. Select **"release"** for release-type
3. Paste **`v{VERSION}`** as the tag (already in clipboard)
**Wait for the user** to confirm the publish workflow has completed before proceeding.
## Step 9: Update GitHub Release Notes
Once the user confirms the publish workflow is done, fetch the auto-generated release content:
```bash
VERSION=<version>
gh release view v${VERSION} --json body --jq '.body'
```
The auto-generated release has:
- `## What's Changed` - PR list (we'll replace this with our changelog)
- `## New Contributors` - First-time contributors (keep this if present)
- `**Full Changelog**` - Comparison link (keep this)
Build the new release body:
1. Start with `## What's Changed` header
2. Add our changelog content (from CHANGELOG.md for this version)
3. Keep the `## New Contributors` section if it exists
4. Keep the `**Full Changelog**` link
Update the release:
```bash
gh release edit v${VERSION} --notes "<new body content>"
```
Verify the release was updated:
```bash
gh release view v${VERSION}
```
## Step 10: Final Summary
**Copy a Slack announcement message to clipboard** (include the full changelog, not just highlights):
```bash
echo "VS Code v{VERSION} Released
- Changelog entry 1
- Changelog entry 2
- Changelog entry 3" | pbcopy
```
**Present a final summary:**
- Version released: v{VERSION}
- PR merged: #{PR_NUMBER}
- Tag pushed: v{VERSION}
- Release: https://github.com/cline/cline/releases/tag/v{VERSION}
- Slack message copied to clipboard
**Final reminder:**
Post the Slack message to announce the release
## Handling Edge Cases
### No changesets found
If the changeset PR body shows no changes, inform the user they need to merge PRs with changesets first.
### Merge conflicts
If there are conflicts on the changeset branch, help the user resolve them:
```bash
git fetch origin main
git rebase origin/main
# resolve conflicts
git push origin changeset-release/main --force-with-lease
```
### User wants to add more changes
If the user wants to include additional PRs before releasing:
1. Ask them to merge those PRs to main first
2. The changeset automation will update the PR automatically
3. Re-run this workflow after the PR is updated
-49
View File
@@ -1,49 +0,0 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "cline"
[setup]
script = '''
if [ ! -d "node_modules" ]; then
MAIN_WORKTREE="$(git worktree list | head -n1 | awk '{print $1}')"
ln -s "$MAIN_WORKTREE/node_modules" node_modules
ln -s "$MAIN_WORKTREE/webview-ui/node_modules" webview-ui/node_modules
fi
'''
[[actions]]
name = "VS Code"
icon = "run"
command = "chmod +x ./scripts/run-extension-host.sh && ./scripts/run-extension-host.sh production"
[[actions]]
name = "CLI"
icon = "run"
command = '''
npm run cli:build
npm run cli:run
'''
[[actions]]
name = "npm install"
icon = "tool"
command = '''
rm node_modules
rm webview-ui/node_modules
npm run install:all
'''
[[actions]]
name = "pull main"
icon = "tool"
command = '''
git fetch origin main
if ! git merge-base --is-ancestor main origin/main; then
echo "Local main has commits not on origin/main. Aborting..."
exit 1
fi
git update-ref refs/heads/main refs/remotes/origin/main
echo "main updated to $(git rev-parse --short main)"
'''
+10 -28
View File
@@ -16,6 +16,13 @@
TELEMETRY_SERVICE_API_KEY=your-posthog-telemetry-api-key
ERROR_SERVICE_API_KEY=your-posthog-error-tracking-api-key
# ============================================================================
# TELEMETRY PROVIDER CONTROL
# ============================================================================
# Control which telemetry providers are active
POSTHOG_TELEMETRY_ENABLED=true # Enable PostHog telemetry (default: true)
# Set to false to disable Telemetry completely
# ============================================================================
# OPENTELEMETRY (Optional - for advanced telemetry)
# ============================================================================
@@ -65,12 +72,12 @@ ERROR_SERVICE_API_KEY=your-posthog-error-tracking-api-key
# Example configurations:
#
# Console debugging (logs only):
# OTEL_TELEMETRY_ENABLED=true
# OTEL_TELEMETRY_ENABLED=1
# OTEL_LOGS_EXPORTER=console
# TEL_DEBUG_DIAGNOSTICS=true
#
# OTLP with gRPC (insecure, for local testing):
# OTEL_TELEMETRY_ENABLED=true
# OTEL_TELEMETRY_ENABLED=1
# OTEL_LOGS_EXPORTER=otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
@@ -78,37 +85,12 @@ ERROR_SERVICE_API_KEY=your-posthog-error-tracking-api-key
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer your-token
#
# OTLP with HTTP/JSON (production):
# OTEL_TELEMETRY_ENABLED=true
# OTEL_TELEMETRY_ENABLED=1
# OTEL_LOGS_EXPORTER=otlp
# OTEL_EXPORTER_OTLP_PROTOCOL=http/json
# OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com
# OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer your-token
# ============================================================================
# OBJECT STORE CONFIGURATION
# ============================================================================
# TO ENABLE S3 OR R2 STORAGE, UNCOMMENT AND FILL IN THE FOLLOWING:
# CLINE_STORAGE_ADAPTER="s3" # Options: "s3" or "r2"
# CLINE_STORAGE_BUCKET="cline"
# CLINE_STORAGE_ACCESS_KEY_ID="key"
# CLINE_STORAGE_SECRET_ACCESS_KEY="secrets"
#
# [OPTIONAL FIELDS FOR R2]
# CLINE_STORAGE_ACCOUNT_ID = "account-id"
# Default R2 endpoint (if not set): "https://<CLINE_STORAGE_ACCOUNT_ID>.r2.cloudflarestorage.com"
# CLINE_STORAGE_ENDPOINT = "http://localhost:8333"
#
# [OPTIONAL FIELDS FOR S3]
# CLINE_STORAGE_REGION = "us-west-1" # AWS Bucket Region (default: "us-east-1")
# Default S3 endpoint (if not set): "https://s3.<CLINE_STORAGE_REGION>.amazonaws.com"
# CLINE_STORAGE_ENDPOINT = "http://localhost:8333"
#
# [OPTIONAL FIELDS FOR ALL STORAGE TYPES]
# CLINE_STORAGE_SYNC_INTERVAL_MS = 30000 # Interval for sync worker in milliseconds
# CLINE_STORAGE_SYNC_MAX_RETRIES = 5 # Max retries for failed sync operations
# CLINE_STORAGE_SYNC_BATCH_SIZE = 10 # Number of files to sync in each batch
# CLINE_STORAGE_SYNC_BACKFILL_ENABLED = false # Enable backfill of existing data on startup
# ============================================================================
# OPTIONAL DEVELOPMENT SETTINGS
# ============================================================================
+4 -2
View File
@@ -1,2 +1,4 @@
/.github/ @saoudrizwan @arafatkatze @maxpaulus43 @candieduniverse
/README.md @saoudrizwan @juanpflores
/docs/
/.github/ @saoudrizwan @garoth @sjf
/README.md @saoudrizwan @nickbaumann98
/src/core/storage/ @celestial-vault
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
cache: "npm"
- name: Install Dependencies
run: npm ci
run: npm install changeset
# Check if there are any new changesets to process
- name: Check for changesets
@@ -1,70 +0,0 @@
name: Smoke Tests
on:
push:
branches: [main]
paths:
- 'src/core/**'
- 'src/shared/**'
- 'proto/**'
- 'evals/**'
- '.github/workflows/cline-evals-regression.yml'
pull_request:
paths:
- 'src/core/**'
- 'src/shared/**'
- 'proto/**'
- 'evals/**'
- '.github/workflows/cline-evals-regression.yml'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: smoke-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
smoke-tests:
name: Smoke Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build and install CLI
run: |
npm run protos
cd cli && npm install && npm run build && npm link
echo "$(npm config get prefix)/bin" >> $GITHUB_PATH
- name: Verify CLI
run: cline --version
- name: Run smoke tests
env:
CLINE_API_KEY: ${{ secrets.CLINE_API_KEY }}
run: |
cline auth -p cline -k "$CLINE_API_KEY" -m "anthropic/claude-sonnet-4.5"
npx tsx evals/smoke-tests/run-smoke-tests.ts --trials 1 --parallel
- name: Generate summary
if: always()
run: cat evals/smoke-tests/results/latest/summary.md >> $GITHUB_STEP_SUMMARY
- name: Upload results
uses: actions/upload-artifact@v4
if: always()
with:
name: smoke-test-results-${{ github.run_id }}
path: evals/smoke-tests/results/latest/
retention-days: 30
+2 -5
View File
@@ -80,16 +80,13 @@ jobs:
playwright-browsers-${{ runner.os }}-
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Install vsce
run: npm install -g @vscode/vsce
- name: Install xvfb on Linux
if: matrix.runner == 'ubuntu'
run: sudo apt-get update && sudo apt-get install -y xvfb
-93
View File
@@ -1,93 +0,0 @@
name: Publish NPM Release
on:
workflow_call:
inputs:
confirm_publish:
description: 'Type "publish" to confirm you want to publish to NPM'
required: true
type: string
permissions:
contents: write # Required for pushing tags
id-token: write # Required for npm trusted publishing (OIDC)
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
jobs:
test:
uses: ./.github/workflows/test.yml
publish-npm-release:
needs: test
name: Publish Cline CLI to NPM
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && inputs.confirm_publish == 'publish'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- name: Install root dependencies and CLI dependencies
run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field
- name: Generate Protos
run: npm run protos
- name: Read release version
id: version
run: |
# Read version from cli/package.json
VERSION=$(node -p "require('./cli/package.json').version")
echo "Release version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Build standalone NPM package
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
OTEL_TELEMETRY_ENABLED: "1"
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
run: node scripts/package-npm.mjs
- name: Verify build output
run: |
echo "Checking dist-standalone directory..."
ls -la dist-standalone/
echo "Verifying CLI binaries..."
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
echo "Checking package.json in dist-standalone..."
cat dist-standalone/package.json | grep version
- name: Publish to NPM with latest tag
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'latest'..."
cd dist-standalone
npm publish --tag latest --access public
- name: Tag release
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "v${{ steps.version.outputs.version }}-cli"
git push origin "v${{ steps.version.outputs.version }}-cli"
- name: Summary
run: |
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'latest'"
echo ""
echo "📦 Install with: npm install -g cline"
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
-134
View File
@@ -1,134 +0,0 @@
name: Publish NPM Nightly
on:
workflow_call:
inputs:
force_publish:
description: "Force publish even if there are no commits in the last 24 hours"
required: false
type: boolean
default: false
permissions:
contents: read
id-token: write # Required for npm trusted publishing (OIDC)
checks: write # Required by test workflow
pull-requests: write # Required by test workflow
jobs:
test:
uses: ./.github/workflows/test.yml
publish-npm-nightly:
needs: test
name: Publish Cline CLI (Nightly) to NPM
if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check for recent commits
id: check_commits
run: |
if [ "${{ inputs.force_publish }}" = "true" ]; then
echo "force_publish enabled, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then
echo "No commits in last 24 hours, skipping publish"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Found recent commits, proceeding with publish"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node.js
if: steps.check_commits.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- name: Install root dependencies and CLI dependencies
if: steps.check_commits.outputs.skip != 'true'
run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field
- name: Generate Protos
if: steps.check_commits.outputs.skip != 'true'
run: npm run protos
- name: Generate nightly version with timestamp
if: steps.check_commits.outputs.skip != 'true'
id: version
run: |
# Read base version from cli/package.json (e.g., "2.0.0")
BASE_VERSION=$(node -p "require('./cli/package.json').version")
# Generate timestamp (Unix epoch seconds)
TIMESTAMP=$(date +%s)
# Create unique nightly version: 1.0.9-nightly.1736365200
VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}"
echo "Base version: $BASE_VERSION"
echo "Generated nightly version: $VERSION"
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Update cli/package.json with nightly version
if: steps.check_commits.outputs.skip != 'true'
run: |
# Update version with timestamp-based nightly version
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('cli/package.json', 'utf8'));
pkg.version = '${{ steps.version.outputs.version }}';
fs.writeFileSync('cli/package.json', JSON.stringify(pkg, null, '\t'));
"
echo "Using version ${{ steps.version.outputs.version }} for build"
cat cli/package.json | grep '"version"'
- name: Build and package CLI
if: steps.check_commits.outputs.skip != 'true'
env:
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
OTEL_TELEMETRY_ENABLED: "1"
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
run: node scripts/package-npm.mjs
- name: Verify build output
if: steps.check_commits.outputs.skip != 'true'
run: |
echo "Checking dist-standalone directory..."
ls -la dist-standalone/
echo "Verifying CLI binaries..."
ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found"
echo "Checking package.json in dist-standalone..."
cat dist-standalone/package.json | grep version
- name: Publish to NPM with nightly tag
if: steps.check_commits.outputs.skip != 'true'
run: |
echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'nightly'..."
cd dist-standalone
npm publish --tag nightly --access public
- name: Summary
if: steps.check_commits.outputs.skip != 'true'
run: |
echo "✅ Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'nightly'"
echo ""
echo "📦 Install with: npm install -g cline@nightly"
echo "🔗 NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}"
-215
View File
@@ -1,215 +0,0 @@
# Build and Pack CLI
#
# Builds a CLI tarball from any branch/commit and publishes it as a GitHub Release.
# Requires write access to the repository (maintainers/collaborators only).
#
# Security: Split into two jobs to isolate untrusted build code from write tokens.
# The build job runs arbitrary ref code with zero permissions. The release job
# only runs trusted GitHub Actions with write scope.
#
# Usage (helper script, auto-detects current branch):
# ./scripts/build-cli-artifact.sh
# ./scripts/build-cli-artifact.sh feature/my-changes
# ./scripts/build-cli-artifact.sh feature/my-changes 1234 # comments on PR
#
# Usage (gh CLI directly):
# gh workflow run pack-cli.yml -f ref=main
# gh workflow run pack-cli.yml -f ref=abc123 -f pr_number=1234
#
# Install the built CLI (no auth required):
# npm install -g https://github.com/cline/cline/releases/download/cli-build-<sha>/cline-<ver>.tgz
#
# Find releases:
# gh release list --limit 10
name: Build and Pack CLI
permissions:
contents: read
on:
workflow_dispatch:
inputs:
ref:
description: 'Branch, tag, or commit SHA to build (leave empty for default branch)'
required: false
type: string
pr_number:
description: 'PR number to comment on with install instructions (optional)'
required: false
type: number
jobs:
# ── Build job: runs untrusted ref code with ZERO permissions ──
build:
name: Build CLI
runs-on: ubuntu-latest
permissions: {}
outputs:
commit_sha: ${{ steps.commit.outputs.sha }}
tarball: ${{ steps.pack.outputs.tarball }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
persist-credentials: false
- name: Get commit SHA
id: commit
run: |
COMMIT_SHA=$(git rev-parse --short HEAD)
echo "sha=$COMMIT_SHA" >> $GITHUB_OUTPUT
echo "Building from commit: $COMMIT_SHA"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Install dependencies
run: npm ci --include=optional
- name: Generate Protos
run: npm run protos
- name: Build standalone package
run: node scripts/package-npm.mjs
- name: Create Tarball
id: pack
run: |
cd dist-standalone
TARBALL=$(npm pack)
echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
echo "Created tarball: $TARBALL"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cli-tarball
path: dist-standalone/*.tgz
# ── Release job: only trusted Actions code, with write permissions ──
release:
name: Release CLI
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: cli-tarball
path: dist-standalone
- name: Create GitHub Release
id: create_release
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const commit = '${{ needs.build.outputs.commit_sha }}';
const tarball = '${{ needs.build.outputs.tarball }}';
// Delete existing release/tag if re-running for the same commit
const tagName = `cli-build-${commit}`;
try {
const existing = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag: tagName
});
await github.rest.repos.deleteRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: existing.data.id
});
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${tagName}`
});
core.info(`Deleted existing release for ${tagName}`);
} catch (e) {
// Release doesn't exist yet, that's fine
}
// Create a release
const release = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
name: `CLI Build (${commit})`,
body: `Automated CLI build from commit ${commit}\n\nInstall with:\n\`\`\`bash\nnpm install -g https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}\n\`\`\``,
draft: false,
prerelease: true
});
// Upload the tarball as a release asset
const tarballPath = path.join('dist-standalone', tarball);
const tarballData = fs.readFileSync(tarballPath);
await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.data.id,
name: tarball,
data: tarballData
});
const downloadUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}`;
core.setOutput('release_url', release.data.html_url);
core.setOutput('download_url', downloadUrl);
- name: Comment on PR with download instructions
if: inputs.pr_number != ''
uses: actions/github-script@v7
with:
script: |
const commit = '${{ needs.build.outputs.commit_sha }}';
const releaseUrl = '${{ steps.create_release.outputs.release_url }}';
const downloadUrl = '${{ steps.create_release.outputs.download_url }}';
const prNumber = ${{ inputs.pr_number || 0 }};
if (!prNumber) return;
const comment = `## 📦 CLI Build Ready
A CLI build has been created for commit \`${commit}\`.
### Install Directly from URL (No Authentication Required!)
\`\`\`bash
npm install -g ${downloadUrl}
\`\`\`
### Alternative: Download and Install
\`\`\`bash
curl -L ${downloadUrl} -o cline.tgz
npm install -g ./cline.tgz
\`\`\`
📦 [View Release](${releaseUrl})
`;
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
- name: Summary
run: |
echo "✅ CLI build complete!"
echo ""
echo "📦 Release: ${{ steps.create_release.outputs.release_url }}"
echo "🔗 Download URL: ${{ steps.create_release.outputs.download_url }}"
echo ""
echo "Install from anywhere (no authentication required):"
echo " npm install -g ${{ steps.create_release.outputs.download_url }}"
@@ -1,55 +0,0 @@
name: Publish CLI (Trusted)
on:
schedule:
- cron: "0 12 * * *" # 4 AM PST (UTC-8) = 12 UTC
workflow_dispatch:
inputs:
publish_target:
description: "Which publish flow to run"
required: true
default: "main"
type: choice
options:
- main
- nightly
confirm_publish:
description: 'Required when publish_target=main. Type "publish" to confirm release publish.'
required: false
type: string
force_nightly_publish:
description: "Force nightly publish even with no commits in last 24h"
required: false
type: boolean
default: false
permissions:
id-token: write # Required for npm trusted publishing (OIDC)
contents: write # Required because npm-main creates/pushes git tags
checks: write # Required by nested reusable test workflow
pull-requests: write # Required by nested reusable test workflow
jobs:
publish-main:
if: |
github.repository == 'cline/cline' && (
github.event_name == 'workflow_dispatch' &&
github.event.inputs.publish_target == 'main' &&
github.event.inputs.confirm_publish == 'publish' &&
!endsWith(github.actor, '[bot]')
)
uses: ./.github/workflows/npm-main.yaml
secrets: inherit
with:
confirm_publish: ${{ github.event.inputs.confirm_publish }}
publish-nightly:
if: |
github.repository == 'cline/cline' && (
github.event_name == 'schedule' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'nightly')
)
uses: ./.github/workflows/npm-nightly.yaml
secrets: inherit
with:
force_publish: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_nightly_publish == 'true' }}
+21 -2
View File
@@ -38,10 +38,28 @@ jobs:
with:
node-version: "lts/*"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci --include=optional
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci --include=optional
- name: Install Publishing Tools
@@ -56,9 +74,10 @@ jobs:
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_LOGS_EXPORTER: ${{ secrets.OTEL_LOGS_EXPORTER }}
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
run: npm run publish:marketplace:nightly
+33 -25
View File
@@ -36,19 +36,35 @@ jobs:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
fetch-depth: 0
fetch-tags: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
# Cache root dependencies - only reuse if package-lock.json exactly matches
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
# Cache webview-ui dependencies - only reuse if package-lock.json exactly matches
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
run: npm install --include=optional
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci --include=optional
- name: Install webview-ui dependencies
run: cd webview-ui && npm install --include=optional
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci --include=optional
- name: Install Publishing Tools
run: npm install -g @vscode/vsce ovsx
@@ -83,11 +99,12 @@ jobs:
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
# OpenTelemetry production defaults (can be overridden at runtime)
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: ${{ secrets.OTEL_LOGS_EXPORTER }}
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
run: |
# Required to generate the .vsix
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
@@ -100,31 +117,22 @@ jobs:
echo "Successfully published release version ${{ steps.get_version.outputs.version }} to VS Code Marketplace and Open VSX Registry"
fi
- name: Get Previous Tag
id: prev_tag
run: |
CURRENT_TAG="${{ steps.validate_tag.outputs.tag }}"
PREV_TAG=$(git describe --tags --abbrev=0 "$CURRENT_TAG^" 2>/dev/null || echo "")
echo "prev_tag=$PREV_TAG" >> $GITHUB_OUTPUT
- name: Get Changelog Entry
id: changelog
run: |
# Get content between first ## [ and second ## [
CONTENT=$(awk '/^## \[/{if(found) exit; found=1; next} found{print}' CHANGELOG.md)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# - name: Get Changelog Entry
# id: changelog
# uses: mindsers/changelog-reader-action@v2
# with:
# # This expects a standard Keep a Changelog format
# # "latest" means it will read whichever is the most recent version
# # set in "## [1.2.3] - 2025-01-28" style
# version: latest
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.validate_tag.outputs.tag }}
files: "*.vsix"
body: |
${{ steps.changelog.outputs.content }}
**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.prev_tag.outputs.prev_tag }}...${{ steps.validate_tag.outputs.tag }}
# body: ${{ steps.changelog.outputs.content }}
generate_release_notes: true
prerelease: ${{ github.event.inputs.release-type == 'pre-release' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+183
View File
@@ -0,0 +1,183 @@
name: Release Standalone CLI
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v3.32.6)'
required: true
type: string
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.platform }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: macos-13
platform: darwin-x64
arch: x64
- os: macos-14
platform: darwin-arm64
arch: arm64
- os: ubuntu-latest
platform: linux-x64
arch: x64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
- name: Install dependencies
run: npm ci
- name: Install webview dependencies
run: cd webview-ui && npm ci
- name: Download Node.js binaries
run: npm run download-node
- name: Download ripgrep binaries
run: npm run download-ripgrep
- name: Build CLI binaries
run: npm run compile-cli
- name: Build standalone CLI package
run: npm run compile-standalone-cli
env:
NODE_ENV: production
- name: Get version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Rename package
run: |
cd dist-standalone
mv standalone-cli.zip cline-${{ steps.version.outputs.version }}-${{ matrix.platform }}.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: cline-${{ matrix.platform }}
path: dist-standalone/cline-${{ steps.version.outputs.version }}-${{ matrix.platform }}.tar.gz
retention-days: 1
release:
name: Create Release
needs: build
runs-on: ubuntu-latest
environment: publish
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Get version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT
else
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
fi
- name: Display structure
run: ls -R artifacts/
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: Cline CLI ${{ steps.version.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
files: |
artifacts/cline-darwin-x64/cline-${{ steps.version.outputs.version }}-darwin-x64.tar.gz
artifacts/cline-darwin-arm64/cline-${{ steps.version.outputs.version }}-darwin-arm64.tar.gz
artifacts/cline-linux-x64/cline-${{ steps.version.outputs.version }}-linux-x64.tar.gz
body: |
## Installation
Install Cline CLI with a single command:
```bash
curl -fsSL https://raw.githubusercontent.com/cline/cline/main/scripts/install.sh | bash
```
### Platform-Specific Downloads
- **macOS (Intel)**: `cline-${{ steps.version.outputs.version }}-darwin-x64.tar.gz`
- **macOS (Apple Silicon)**: `cline-${{ steps.version.outputs.version }}-darwin-arm64.tar.gz`
- **Linux (x64)**: `cline-${{ steps.version.outputs.version }}-linux-x64.tar.gz`
### Manual Installation
1. Download the appropriate package for your platform
2. Extract: `tar -xzf cline-*.tar.gz`
3. Move to installation directory: `mv cline-* ~/.cline`
4. Add to PATH: `export PATH="$HOME/.cline/bin:$PATH"`
### What's Included
- ✅ Node.js v22.15.0 (bundled)
- ✅ Cline CLI binary
- ✅ Cline Host bridge
- ✅ Cline Core (TypeScript compiled)
- ✅ Ripgrep v14.1.1 (for file searching)
- ✅ All dependencies
### Getting Started
```bash
# Verify installation
cline version
# Sign in
cline auth login
# Get help
cline --help
```
### Documentation
- [Installation Guide](https://docs.cline.bot/getting-started/installing-cline)
- [CLI Documentation](https://docs.cline.bot/exploring-clines-tools/cline-tools-guide)
- [GitHub Repository](https://github.com/cline/cline)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }}
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
CLINE_ENVIRONMENT: production
+77 -27
View File
@@ -28,17 +28,27 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Run Quality Checks (Parallel)
@@ -63,17 +73,27 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Set up NPM on Windows
@@ -115,11 +135,6 @@ jobs:
cd webview-ui
npm run test:coverage
- name: CLI Tests
id: cli_tests
if: ${{ !cancelled() && steps.build_step.outcome == 'success' }}
run: cd cli && npm run test:run
- name: Save Coverage Reports
uses: actions/upload-artifact@v4
# Only upload artifacts on Linux - We only need coverage from one OS
@@ -141,32 +156,67 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
cache-dependency-path: |
package-lock.json
webview-ui/package-lock.json
testing-platform/package-lock.json
- name: Cache root dependencies
uses: actions/cache@v4
id: root-cache
with:
path: node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json') }}
- name: Cache webview-ui dependencies
uses: actions/cache@v4
id: webview-cache
with:
path: webview-ui/node_modules
key: ${{ runner.os }}-npm-webview-${{ hashFiles('webview-ui/package-lock.json') }}
# Cache testing-platform dependencies
- name: Cache testing-platform dependencies
uses: actions/cache@v4
id: testing-platform-cache
with:
path: testing-platform/node_modules
key: ${{ runner.os }}-npm-testing-platform-${{ hashFiles('testing-platform/package-lock.json') }}
- name: Install root dependencies
if: steps.root-cache.outputs.cache-hit != 'true'
run: npm ci
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install webview-ui dependencies
if: steps.webview-cache.outputs.cache-hit != 'true'
run: cd webview-ui && npm ci
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache-dependency-path: cli/go.sum
- name: Download Node.js binaries
run: npm run download-node
- name: Build CLI binaries
run: npm run compile-cli
- name: Download ripgrep binaries
run: npm run download-ripgrep
- name: Compile Standalone
run: npm run compile-standalone
- name: Compile standalone CLI
run: npm run compile-standalone-cli
- name: Install testing platform dependencies
if: steps.testing-platform-cache.outputs.cache-hit != 'true'
run: cd testing-platform && npm ci
- name: Running testing platform integration spec tests
continue-on-error: true
timeout-minutes: 7
run: npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage
# Temporarily wrapping the test command to always return a neutral exit code.
# This prevents the job from showing as failed and avoids distracting developers
# until the integration tests are ready to be enforced.
run: |
npm run test:tp-orchestrator -- tests/specs/ --count=1 --coverage || true
- name: Save Coverage Reports
uses: actions/upload-artifact@v4
+11 -55
View File
@@ -1,26 +1,17 @@
name: Trigger Jetbrains Plugin <-> Cline Tests
on:
pull_request_target:
types: [opened, reopened]
issue_comment:
types: [created]
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: read
concurrency:
group: jetbrains-trigger-${{ github.event.pull_request.number || github.event.issue.number }}
group: jetbrains-trigger-${{ github.event.number }}
cancel-in-progress: true
jobs:
trigger-integration-test:
name: Run Tests
runs-on: ubuntu-latest
# Run on PR open/reopen, or when someone comments /test-jetbrains on a PR
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/test-jetbrains'))
steps:
- name: Generate GitHub App Token
id: app-token
@@ -31,39 +22,7 @@ jobs:
owner: cline
repositories: intellij-plugin
- name: Get PR details (for issue_comment trigger)
id: pr-details
if: github.event_name == 'issue_comment'
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_DATA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }})
echo "head_ref=$(echo "$PR_DATA" | jq -r '.head.ref')" >> $GITHUB_OUTPUT
echo "head_sha=$(echo "$PR_DATA" | jq -r '.head.sha')" >> $GITHUB_OUTPUT
echo "title=$(echo "$PR_DATA" | jq -r '.title')" >> $GITHUB_OUTPUT
echo "html_url=$(echo "$PR_DATA" | jq -r '.html_url')" >> $GITHUB_OUTPUT
- name: Sanitize untrusted inputs
id: sanitize
env:
RAW_BRANCH_NAME: ${{ github.event_name == 'pull_request_target' && github.head_ref || steps.pr-details.outputs.head_ref }}
RAW_PR_TITLE: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.title || steps.pr-details.outputs.title }}
run: |
# Sanitize branch name for JSON
BRANCH_NAME_JSON=$(jq -n --arg b "$RAW_BRANCH_NAME" '$b')
echo "branch_name=$BRANCH_NAME_JSON" >> $GITHUB_OUTPUT
# Sanitize PR title for JSON
PR_TITLE_JSON=$(jq -n --arg t "$RAW_PR_TITLE" '$t')
echo "pr_title=$PR_TITLE_JSON" >> $GITHUB_OUTPUT
- name: Trigger IntelliJ Plugin Integration Test
env:
BRANCH_NAME: ${{ steps.sanitize.outputs.branch_name }}
PR_TITLE: ${{ steps.sanitize.outputs.pr_title }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
PR_URL: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.html_url || steps.pr-details.outputs.html_url }}
run: |
curl -X POST \
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
@@ -75,23 +34,20 @@ jobs:
{
"event_type": "cline-pr-check",
"client_payload": {
"pr_number": "$PR_NUMBER",
"branch_name": $BRANCH_NAME,
"pr_number": "${{ github.event.number }}",
"branch_name": "${{ github.head_ref }}",
"action": "${{ github.event.action }}",
"sha": "$PR_SHA",
"pr_title": $PR_TITLE,
"pr_url": "$PR_URL"
"sha": "${{ github.event.pull_request.head.sha }}",
"pr_title": ${{ toJSON(github.event.pull_request.title) }},
"pr_url": "${{ github.event.pull_request.html_url }}"
}
}
EOF
- name: Log trigger details
env:
PR_NUMBER: ${{ github.event.pull_request.number || github.event.issue.number }}
PR_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || steps.pr-details.outputs.head_sha }}
run: |
echo "Triggered IntelliJ Plugin integration test for:"
echo " PR #$PR_NUMBER"
echo " Trigger: ${{ github.event_name }}"
echo " PR #${{ github.event.number }}: ${{ github.event.pull_request.title }}"
echo " Branch: ${{ github.head_ref }}"
echo " Action: ${{ github.event.action }}"
echo " SHA: $PR_SHA"
echo " SHA: ${{ github.event.pull_request.head.sha }}"
-16
View File
@@ -8,14 +8,12 @@ tmp
.DS_Store
.idea
.husky/_/
pnpm-lock.yaml
.clineignore
.venv
.actrc
CLAUDE.local.md
webview-ui/src/**/*.js
webview-ui/src/**/*.js.map
@@ -29,25 +27,11 @@ coverage-unit
*evals.env
.env
.secrets
.github/act/.secrets
.worktrees
## Generated files ##
src/generated/
src/shared/proto/
webview-ui/src/services/grpc-client.ts
*.tsbuildinfo
# E2E Tests
test-results
/.github/act
/pkg
.secrets
*.tsbuildinfo
# Smoke test results (generated)
evals/smoke-tests/results/
-3
View File
@@ -1,3 +0,0 @@
[submodule "evals/cline-bench"]
path = evals/cline-bench
url = https://github.com/cline/cline-bench.git
+4 -37
View File
@@ -12,10 +12,7 @@
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"--disable-extensions", // Avoid conflicts with installed extensions
"${workspaceFolder}"
],
"outFiles": [
@@ -36,10 +33,7 @@
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"--disable-extensions", // Avoid conflicts with installed extensions
"${workspaceFolder}"
],
"outFiles": [
@@ -60,10 +54,7 @@
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--disable-workspace-trust",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"--disable-extensions", // Avoid conflicts with installed extensions
"${workspaceFolder}"
],
"outFiles": [
@@ -86,10 +77,7 @@
"--user-data-dir=${workspaceFolder}/dist/tmp/user",
"--profile-temp",
"--sync=off",
"--disable-extension",
"saoudrizwan.claude-dev", // Avoid conflicts with installed Cline
"--disable-extension",
"saoudrizwan.cline-nightly", // Avoid conflicts with installed Cline Nightly
"--disable-extensions", // Avoid conflicts with installed extensions
"--extensionDevelopmentPath=${workspaceFolder}",
"${workspaceFolder}"
],
@@ -177,27 +165,6 @@
},
"console": "integratedTerminal",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "Open Storybook",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run",
"storybook"
],
"cwd": "${workspaceFolder}/webview-ui",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"serverReadyAction": {
"pattern": "Local:.*http://localhost:([0-9]+)",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
},
"env": {
"IS_DEV": "true"
}
}
]
}
+1 -3
View File
@@ -27,7 +27,5 @@
"source.removeUnused.biome": "always",
"source.removeUnusedImports": "always",
"source.organizeImports.biome": "always"
},
// Disable auto-forwarding ports to prevent Simple Browser from opening the Vite dev server
"remote.autoForwardPorts": false
}
}
-20
View File
@@ -263,26 +263,6 @@
"watch"
],
"command": "rm -rf ${workspaceFolder}/dist/tmp/user && mkdir -p ${workspaceFolder}/dist/tmp/user"
},
{
"type": "npm",
"script": "storybook",
"group": "build",
"problemMatcher": [],
"isBackground": false,
"label": "npm: storybook",
"dependsOn": [
"npm: protos",
"npm: build:webview"
],
"presentation": {
"reveal": "always"
},
"options": {
"env": {
"IS_DEV": "true"
}
}
}
],
"inputs": [
+1 -6
View File
@@ -1,8 +1,6 @@
# Default
.vscode/**
.vscode-test/**
.worktrees/**
CLAUDE.local.md
out/
dist-standalone/
node_modules/
@@ -22,9 +20,6 @@ eslint-rules/**
.husky/**
.env
# cli
cli/**
# Custom
**/demo.gif
.nvmrc
@@ -75,4 +70,4 @@ test-results/
**/*.stories.tsx
*storybook.log
storybook-static
**/StorybookDecorator.tsx
**/StorybookDecorator.tsx
-1
View File
@@ -1 +0,0 @@
.gitignore
-554
View File
@@ -1,559 +1,5 @@
# Changelog
## [3.66.0]
### Added
- Gemini-3.1 Pro Preview
## [3.65.0]
### Added
- Add /skills slash command to CLI for viewing and managing installed skills
### Fixed
- Fix aggressive context compaction caused by accidental clicks on the context window progress bar silently setting a very low auto-condense threshold
- Fix infinite retry loop when write_to_file fails with missing content parameter.
- Fixed default claude model
## [3.64.0]
### Added
- Added sonnet 4.6
## [3.63.0]
### Added
- added zai GLM 5 Free promo
### Fixed
- Restore reasoning trace visibility in chat and improve the thinking row UX so reasoning is visible, then collapsible after completion.
## [3.62.0]
### Fixed
- Banners now display immediately when opening the extension instead of requiring user interaction first
- Resolved 17 security vulnerabilities including high-severity DoS issues in dependencies (body-parser, axios, qs, tar, and others)
## [3.61.0]
- UI/UX fixes with minimax model family
## [3.60.0]
- Fixes for Minimax model family
## [3.59.0]
- Added Minimax 2.5 Free Promo
- Fixed Response chaining for OpenAI's Responses API
## [3.58.0]
### Added
- Subagent: replace legacy subagents with the native `use_subagents` tool
- Bundle `endpoints.json` support so packaged distributions can ship required endpoints out-of-the-box
- Amazon Bedrock: support parallel tool calling
- New "double-check completion" experimental feature to verify work before marking tasks complete
- CLI: new task controls/flags including custom `--thinking` token budget and `--max-consecutive-mistakes` for yolo runs
- Remote config: new UI/options (including connection/test buttons) and support for syncing deletion of remotely configured MCP servers
- Vertex / Claude Code: add 1M context model options for Claude Opus 4.6
- ZAI/GLM: add GLM-5
### Fixed
- CLI: handle stdin redirection correctly in CI/headless environments
- CLI: preserve OAuth callback paths during auth redirects
- VS Code Web: generate auth callback URLs via `vscode.env.asExternalUri` (OAuth callback reliability)
- Terminal: surface command exit codes in results and improve long-running `execute_command` timeout behavior
- UI: add loading indicator and fix `api_req_started` rendering
- Task streaming: prevent duplicate streamed text rows after completion
- API: preserve selected Vercel model when model metadata is missing
- Telemetry: route PostHog networking through proxy-aware shared fetch and ensure telemetry flushes on shutdown
- CI: increase Windows E2E test timeout to reduce flakiness
### Changed
- Settings/model UX: move "reasoning effort" into model configuration and expose it in settings
- CLI provider selection: limit provider list to those remotely configured
- UI: consolidate ViewHeader component/styling across views
- Tools: add auto-approval support for `attempt_completion` commands
- Remotely configured MCP server schema now supports custom headers
## [3.57.1]
### Fixed
- Fixed Opus 4.6 for bedrock provider
## [3.57.0]
### Added
- Cline CLI 2.0 now available. Install with `npm install -g cline`
- Anthopic Opus 4.6
- Minimax-2.1 and Kimi-k2.5 now available for free for a limited time promo
- Codex-5.3 through ChatGPT subscription
### Fixed
- Fix read file tool to support reading large files
- Fix decimal input crash in OpenAI Compatible price fields (#8129)
- Fix build complete handlers when updating the api config
- Fixed missing provider from list
- Fixed Favorite Icon / Star from getting clipped in the task history view
### Changed
- Make skills always enabled and remove feature toggle setting
## [3.56.0]
### Added
- **CLI authentication:** Added Vercel AI Gateway and Cline API key provider support for headless CI/automation workflows
- **New model:** Added Kimi-K2.5 model to Moonshot provider (262K context, image support, prompt caching)
- **Prompt variant:** Added Trinity Large prompt variant for improved tool-calling support
- **OpenTelemetry:** Added support for custom headers on metrics and logs endpoints
- **Social links:** Added community icons (X, Discord, GitHub, Reddit, LinkedIn) to the What's New modal
### Fixed
- **LiteLLM:** Fixed thinking configuration not appearing for reasoning-capable models
- **OpenTelemetry:** Fixed endpoint path handling (no longer incorrectly appends `/v1/logs` or `/v1/metrics`) and ensured logs are sent regardless of VSCode telemetry settings
- **CLI auth:** Fixed `cline auth` displaying incorrect provider information after configuration
### Changed
- **Hooks:** Hook scripts now run from the workspace repository root instead of filesystem root
- **Default settings:** Enabled multi-root workspaces, parallel tool calling, and skills by default; disabled strict plan mode by default
- **Settings UI:** Refreshed feature settings section with collapsible design
## [3.55.0]
- Add new model: Arcee Trinity Large Preview
- Add new model: Moonshot Kimi K2.5
- Add MCP prompts support - prompts from connected MCP servers now appear in slash command autocomplete as `/mcp:<server>:<prompt>`
## [3.54.0]
### Added
- Native tool calls support for Ollama provider
- Sonnet 4.5 is now the default Amazon Bedrock model id
### Fixed
- Prevent infinite retry loops when replace_in_file fails repeatedly. The system now detects repeated failures and provides better guidance to break out of retry cycles.
- Skip diff error UI handling during streaming to prevent flickering. Error handling is deferred until streaming completes.
- Strip notebook cell outputs when extracting text content from Jupyter notebooks, significantly reducing context size sent to the LLM.
- Throttle diff view updates during streaming to reduce UI flickering and improve performance.
### Changed
- Removed Mistral's Devstral-2512 free from the free models list
- Removed deprecated zai-glm-4.6 model from Cerebras provider
## [3.53.1]
### Fixed
- Bug in responses API
## [3.53.0]
### Fixed
- Removed grok model from free tier
## [3.52.0]
### Added
- Users with ChatGPT Plus or Pro subscriptions can now use GPT-5 models directly through Cline without needing an API key. Authentication is handled via OAuth through OpenAI's authentication system.
- Grok models are now moving out of free tier and into paid plans.
- Introduces comprehensive Jupyter Notebook support for Cline, enabling AI-assisted editing of `.ipynb` files with full cell-level context awareness.
### Fixed
- Bugs in DiffViewProvider for file editing
- Ollama's recommended models to use correct identifiers
## [3.51.0]
### Added
- Adding OpenAI gpt-5.2-codex model to the model picker
## [3.50.0]
### Added
- Add gpt-5.2-codex OpenAI model support
- Add create-pull-request skill
### Fixed
- Fix the selection of remotely configured providers
- Fix act_mode_respond to prevent consecutive calls
- Fix invalid tool call IDs when switching between model formats
## [3.49.1]
### Added
- Add telemetry to track usage of skills feature
- Add version headers to Cline backend requests
- Phase in Responses API usage instead of defaulting for every supported model
### Fixed
- Fix workflow slash command search to be case-insensitive
- Fix model display in ModelPickerModal when using LiteLLM
- Fix LiteLLM model fetching with default base URL
- Fix crash when OpenAI-compatible APIs send usage chunks with empty or null choices arrays at end of streaming
- Fix model ID for Kat Coder Pro Free model
## [3.49.0]
- Enable configuring an OTEL collector at runtime
- Removing Minimax-2.1 from free model list as the free trial has ended
- Improved image display in MCP responses
- Auto-sync remote MCP servers from remote config to local settings
## [3.48.0]
### Added
- Add Skills system for reusable, on-demand agent instructions
- Add new websearch tooling in Cline provider
- Add zai-glm-4.7 to Cerebras model list
- Add model refresh and improve reasoning support for Vercel AI Gateway
### Fixed
- Revert #8341 due to regressions in diff view/document truncation (see #8423, #8429)
- Fixed extension crash when using context menu selector
## [3.47.0]
### Added
- Added experimental support for Background Edits (allows editing files in background without opening the diff view)
- Updated free model to MiniMax M2.1 (replacing MiniMax M2)
- Added support for Azure based identity authentication in OpenAI Compatible provider and Azure OpenAI
- Add `supportsReasoning` property to Baseten models
### Fixed
- Prevent expired token usage in authenticated requests
- Exclude binary files without extensions from diffs
- Preserve file endings and trailing newlines
- Fix Cerebras rate limiting
- Fix Auto Compact for Claude Code provider
- Make Workspace and Favorites history filters independent
- Fix remote MCP server connection failures (404 response handling)
- Disable native tool calling for Deepseek 3.2 speciale
- Show notification instead of opening sidebar on update
- Fix Baseten model selector
### Refactored
- Modify prompts for parallel tool usage in Claude and Gemini 3 models
## [3.46.1]
### Fixed
- Remove GLM 4.6 from free models
## [3.46.0]
### Added
- Added GLM 4.7 model
- Enhanced background terminal execution with command tracking, log file output, zombie process prevention (10-minute timeout), and clickable log paths in UI
- Apply Patch tool for GPT-5+ models (replacing current diff edit tools)
### Fixed
- Duplicate error messages during streaming for Diff Edit tool when Parallel Tool Calling is not enabled
- Banner carousel styling and dismiss functionality
- Typos in Gemini system prompt overrides
- Model picker favorites ordering, star toggle, and keyboard navigation for OpenRouter and Vercel AI Gateway providers
- Fetch remote config values from the cache
### Refactored
- Anthropic handler to use metadata for reasoning support
- Bedrock provider to use metadata for reasoning support
## [3.45.1]
- Fixed MCP settings race condition where toggling auto-approve or changing timeout settings would cause the UI to flash and revert
## [3.45.0]
- Added Gemini 3 Flash Preview model
## [3.44.2]
- Polished the model picker UI with checkmarks for selected models, tooltips on Plan/Act tabs, and consistent arrow pointers across all popup modals
- Improved WhatsNew modal responsiveness and cleaned up redundant UI elements
- Fixed GLM models outputting garbled text in thinking tags—reasoning is now properly disabled for these models
## [3.44.1]
- Fixed a critical bug where local MCP servers stopped connecting after v3.42.0—all user-configured stdio-based MCP servers should now work again
- Fixed remotely configured API keys not being extracted correctly for enterprise users
- Added support for dynamic tool instructions that adapt based on runtime context, laying groundwork for future context-aware features
## [3.44.0]
## Added
- Updating minor version to show a proper banner for the release
## [3.43.1]
### Patch Changes
- Fix GLM-4.6 Model reference id
## [3.43.0]
### Added
- GLM-4.6
- kat-coder-pro
- Add parsing of env variable patterns to the mcpconfig.json
### Fixed
- TLS Proxy support issues for VSCode
- Add supportsReasoning flag to OpenAI reasoning models
- Fix thinking not available for some models in the OpenAI provider
- Fix invalid signature field issues when switching between Gemini and Anthropic providers
- Extract OpenRouter model filtering into reusable utility and use it in different model pickers
- Fix a11y for auto approve checkbox
- Improve ModelPickerModal provider list layout
### Refactored
- Migrate WhatsNewModal to new shared dialogue component
## [3.42.0]
### Added
- Expose `getAvailableSlashCommands` rpc endpoint to UI clients
- Made slash command menu and context menu accessible and screenreader-friendly
- Made expanding/collapsing UI components accessible
### Fixed
- Devstral OpenRouter model ID and routing issues
- Incorrect pricing display for Devstral model in the extension
## [3.41.0]
### Added
- OpenAI GPT-5.2
- Devstral-2512 (formerly stealth model "Microwave")
- Improvements to chat modal model picker
- Amazon Nova 2 Lite
- DeepSeek 3.2 to native tool calling allow list
- Responses API support for Codex models in OpenAI provider (requires native tool calling)
- Xmas Special Santa Cline
- Welcome screen UI enhancements
### Fixed
- Initial checkpoint commit now non-blocking for improved responsiveness in large repositories
- Gemini Vertex models erroring when thinking parameters are not supported
- Restrictive file permissions for secrets.json
- Ollama streaming requests not aborting when task is cancelled
### Refactored
- OpenAI provider to centralize temperature configuration and include missing GPT-5 model settings
- OpenAI native handler to use metadata for model capabilities
- Vertex provider to use metadata for model capabilities
## [3.40.2]
- Fix logout on network errors during token refresh (e.g., opening laptop while offline)
## [3.40.1]
- Fix cost calculation display for Anthropic API requests
## [3.40.0]
- Fix highlighted text flashing when task header is collapsed
- Add X-Cerebras-3rd-Party-Integration header to Cerebras API requests
- Add microwave family system prompt configuration
- Remove tooltips from auto approve menu
- Fix Standalone, ensure cwd is the install dir to find resources reliably
- Fix a bug where terminal commands with double quotes are broken when "Terminal Execution Mode" is set to "Background Exec"
- Add support for slash commands anywhere in a message, not just at the beginning. This matches the behavior of @ mentions for a more flexible input experience.
- Add bottom padding to the last message to fix last response text getting cut off by auto approve settings bar.
- Add default thinking level for Gemini 3 Pro models in Gemini provider
## [3.39.2]
- Fix for microwave model and thinking settings
## [3.39.1]
- Fix Openrouter and Cline Provider model info
## [3.39.0]
- Add Explain Changes feature
- Add microwave Stealth model
- Add Tabbed Model Picker with Recommended and Free tabs
- Add support to View remote rules and workflows in the editor
- Enable NTC (Native Tool Calling) by default
- Bug fixes and improvements for LiteLLM provider
## [3.38.3]
- Task export feature now opens the task directory, allowing easy access to the full task files
- Add Grok 4.1 and Grok Code to XAI provider
- Enabled native tool calling for Baseten and Kimi K2 models
- Add thinking level to Gemini 3.0 Pro preview
- Expanded Hooks functionality
- Removed Task Timeline from Task Header
- Bug fix for slash commands
- Bug fixes for Vertex provider
- Bug fixes for thinking/reasoning issues across multiple providers when using native tool calling
- Bug fixes for terminal usage on Windows devices
## [3.38.2]
- Add Claude Opus 4.5
## [3.38.1]
### Fixed
- Fixed handling of 'signature' field in sanitizeAnthropicContentBlock to properly preserve it when thinking is enabled, as required by Anthropic's API.
## [3.38.0]
### Added
- Gemini 3 Pro Preview model
- AquaVoice Avalon model for voice-to-text dictation
### Fixed
- Automatic context truncation when AWS Bedrock token usage rate limits are exceeded
- Removed new_task tool from system prompts, updated slash command prompts, and added helper function for native tool calling validation
## [3.37.1]
- Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
- Add AGENTS.md support
- feat(models): Add free minimax/mimax-m2 model to the model picker
## [3.37.0]
### Added
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
- Nous Research provider with Hermes 4 model family and custom system prompts
- Switched to Aqua Voice's Avalon model in speech to text transcription
- Added Linux support for speech to text
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
- Expanded HTTP proxy support throughout the codebase
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
### Fixed
- Duplicate tool results prevention through existence checking
- XML entity escaping in model content processor
- Commit message generation in command palette
- OpenAI Compatible provider temperature parameter type conversion
## Documentation
- Added missing proto generation step in CONTRIBUTING.md
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
## [3.36.1]
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
- fix: disable native tool callings for grok code models
- Add MCP tool usage to GLM
- Removes reasoning_details content field from Anthropic providers
## [3.36.0]
- Add: Hooks allow you to inject custom logic into Cline's workflow
- Add: new provider AIhubmix
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
- Fix: Oca Token Refresh logic
- Fix: issues where assistant message with empty content is added to conversation history
- Fix: bug where the checkbox shows in the model selector dropdown
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
- Fix: support for `<think>` tags for better compatibility with open-source models
- Fix: refinements to the GLM-4.6 system prompt
## [3.35.1]
- Add: Hicap API integration as provider
- Fix: enable Add Header button in OpenAICompatibleProvider UI
- Fix: Remove orphaned tool_results after truncation and empty content field issues in native tool call
- Fix: render model description in markdown
## [3.35.0]
- Add native tool calling support with configurable setting.
- Auto-approve is now always-on with a redesigned expanding menu. Settings simplified and notifications moved to General Settings.
- added zai-glm-4.6 as a Cerebras model
- Created GPT5 family specific system prompt template
- Fix: show reasoning budget slider to models with valid thinking config
- Requesty base URL, and API key fixes
- Delete all Auth Tokens when logging out
- Support for <think> tags for models that prefer that over <thinking>
## [3.34.1]
- Added support for MiniMax provider with MiniMax-M2 model
- Remove Cline/code-supernova-1-million model
- Changes to allow users to manually enter model names (eg. presets) when using OpenRouter
## [3.34.0]
- Cline Teams is now free through 2025 for unlimited users. Includes Jetbrains, RBAC, centralized billing and more.
- Use the “exacto” versions of GLM-4.6, Kimi-K2, and Qwen3-Coder in the Cline provider for the best balance of cost, speed, accuracy and tool-calling.
## [3.33.1]
- Fix CLI installation copy text
## [3.33.0]
- Added Cline CLI (Preview)
- Added Subagent support (Experimental)
- Added Multi-Root Workspaces support (Enable in feature settings)
- Add auto-retry with exponential backof for failed API requests
## [3.32.8]
- Add Claude Haiku 4.5 support
## [3.32.7]
- Add JP and Global inference profile options to AWS Bedrock
-3
View File
@@ -1,3 +0,0 @@
@.clinerules/general.md
@.clinerules/network.md
@.clinerules/cli.md
+1 -7
View File
@@ -46,11 +46,7 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
```bash
npm run install:all
```
4. Generate Protocol Buffer files (required before first build):
```bash
npm run protos
```
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
@@ -89,10 +85,8 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
2. **Local Development**
- Run `npm run install:all` to install dependencies
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
- Run `npm run test` to run tests locally
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
- Before submitting PR, run `npm run format:fix` to format your code
3. **Linux-specific Setup**
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2026 Cline Bot Inc.
Copyright 2025 Cline Bot Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+3 -8
View File
@@ -2,7 +2,7 @@
English | <a href="https://github.com/cline/cline/blob/main/locales/es/README.md" target="_blank">Español</a> | <a href="https://github.com/cline/cline/blob/main/locales/de/README.md" target="_blank">Deutsch</a> | <a href="https://github.com/cline/cline/blob/main/locales/ja/README.md" target="_blank">日本語</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-cn/README.md" target="_blank">简体中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/zh-tw/README.md" target="_blank">繁體中文</a> | <a href="https://github.com/cline/cline/blob/main/locales/ko/README.md" target="_blank">한국어</a>
</sub></div>
# Cline
# Cline \#1 on OpenRouter
<p align="center">
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
@@ -43,7 +43,7 @@ Thanks to [Claude Sonnet's agentic coding capabilities](https://www.anthropic.c
4. When a task is completed, Cline will present the result to you with a terminal command like `open -a "Google Chrome" index.html`, which you run with a click of a button.
> [!TIP]
> Follow [this guide](https://docs.cline.bot/features/customization/opening-cline-in-sidebar) to open Cline on the right side of your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
> Use the `CMD/CTRL + Shift + P` shortcut to open the command palette and type "Cline: Open In New Tab" to open the extension as a tab in your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
---
@@ -141,11 +141,6 @@ For example, when working with a local web server, you can use 'Restore Workspac
To contribute to the project, start with our [Contributing Guide](CONTRIBUTING.md) to learn the basics. You can also join our [Discord](https://discord.gg/cline) to chat with other contributors in the `#contributors` channel. If you're looking for full-time work, check out our open positions on our [careers page](https://cline.bot/join-us)!
## Enterprise
Get the same Cline experience with enterprise-grade controls: SSO (SAML/OIDC), global policies and configuration, observability with audit trails, private networking (VPC/private link), and self-hosted or on-prem deployments, and enterprise support. Learn more at our [enterprise page](https://cline.bot/enterprise) or [talk to us](https://cline.bot/contact-sales).
## License
[Apache 2.0 © 2026 Cline Bot Inc.](./LICENSE)
[Apache 2.0 © 2025 Cline Bot Inc.](./LICENSE)
-27
View File
@@ -1,27 +0,0 @@
# Security Policy
## Supported Versions
We actively patch only the most recent minor release of Cline. Older versions receive fixes at our discretion.
## Reporting a Vulnerability
We appreciate your efforts to responsibly disclose your findings and will make every effort to acknowledge your contributions.
To report a security issue, please 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.
When reporting, please include:
- A short summary of the issue
- Steps to reproduce or a proof of concept
- Any logs, stack traces, or screenshots that might help us understand the problem
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.
## Escalation
If you do not receive an acknowledgement of your report within 5 business days, you may send an email to security@cline.bot.
Thank you for helping us keep Cline users safe.
-30
View File
@@ -1,30 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
<json>
<![CDATA[
{
"fontFamily": "cline-bot",
"majorVersion": 1,
"minorVersion": 0,
"fontURL": "https://cline.bot",
"designerURL": "https://cline.bot",
"licenseURL": "https://cline.bot",
"version": "Version 1.0",
"fontId": "cline-bot",
"psName": "cline-bot",
"subFamily": "Regular",
"fullName": "cline-bot",
"description": "Font generated by IcoMoon."
}
]]>
</json>
</metadata>
<defs>
<font id="cline-bot" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="cline" data-tags="cline" horiz-adv-x="977" d="M964.553 383.11l-60.285 121.406v69.495c0 115.545-92.939 209.321-207.647 209.321h-102.986c7.536 15.071 11.722 32.654 11.722 51.074 0 64.471-51.912 116.383-115.545 116.383s-115.545-51.912-115.545-116.383 4.186-35.166 11.722-51.074h-102.986c-114.708 0-207.647-93.776-207.647-209.321v-69.495l-61.959-121.406c-5.861-11.722-5.861-26.793 0-38.515l61.959-119.732v-69.495c0-115.545 92.939-209.321 207.647-209.321h415.294c114.708 0 207.647 93.776 207.647 209.321v69.495l60.285 119.732c5.861 11.722 5.861 25.956 0 38.515v0zM426.178 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132zM731.787 284.311c0-52.749-42.702-95.451-94.613-95.451s-94.613 42.702-94.613 95.451v169.132c0 52.749 42.702 95.451 94.613 95.451s94.613-42.702 94.613-95.451v-169.132z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.317 4.15557C18.7873 3.45369 17.147 2.93658 15.4319 2.6404C15.4007 2.63469 15.3695 2.64897 15.3534 2.67754C15.1424 3.05276 14.9087 3.54225 14.7451 3.927C12.9004 3.65083 11.0652 3.65083 9.25832 3.927C9.09465 3.5337 8.85248 3.05276 8.64057 2.67754C8.62449 2.64992 8.59328 2.63564 8.56205 2.6404C6.84791 2.93563 5.20756 3.45275 3.67693 4.15557C3.66368 4.16129 3.65233 4.17082 3.64479 4.18319C0.533392 8.83155 -0.31895 13.3657 0.0991801 17.8436C0.101072 17.8655 0.11337 17.8864 0.130398 17.8997C2.18321 19.4073 4.17171 20.3225 6.12328 20.9291C6.15451 20.9386 6.18761 20.9272 6.20748 20.9015C6.66913 20.2711 7.08064 19.6063 7.43348 18.9073C7.4543 18.8664 7.43442 18.8178 7.39186 18.8016C6.73913 18.554 6.1176 18.2521 5.51973 17.9093C5.47244 17.8816 5.46865 17.814 5.51216 17.7816C5.63797 17.6873 5.76382 17.5893 5.88396 17.4902C5.90569 17.4721 5.93598 17.4683 5.96153 17.4797C9.88928 19.273 14.1415 19.273 18.023 17.4797C18.0485 17.4674 18.0788 17.4712 18.1015 17.4893C18.2216 17.5883 18.3475 17.6873 18.4742 17.7816C18.5177 17.814 18.5149 17.8816 18.4676 17.9093C17.8697 18.2588 17.2482 18.554 16.5945 18.8006C16.552 18.8168 16.533 18.8664 16.5538 18.9073C16.9143 19.6054 17.3258 20.2701 17.7789 20.9005C17.7978 20.9272 17.8319 20.9386 17.8631 20.9291C19.8241 20.3225 21.8126 19.4073 23.8654 17.8997C23.8834 17.8864 23.8948 17.8664 23.8967 17.8445C24.3971 12.6676 23.0585 8.17064 20.3482 4.18414C20.3416 4.17082 20.3303 4.16129 20.317 4.15557ZM8.02002 15.117C6.8375 15.117 5.86313 14.0313 5.86313 12.6981C5.86313 11.3648 6.8186 10.2791 8.02002 10.2791C9.23087 10.2791 10.1958 11.3743 10.1769 12.6981C10.1769 14.0313 9.22141 15.117 8.02002 15.117ZM15.9947 15.117C14.8123 15.117 13.8379 14.0313 13.8379 12.6981C13.8379 11.3648 14.7933 10.2791 15.9947 10.2791C17.2056 10.2791 18.1705 11.3743 18.1516 12.6981C18.1516 14.0313 17.2056 15.117 15.9947 15.117Z" fill="#FAFAFA"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg viewBox="0 0 24 24" fill="black" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.477 2 2 6.477 2 12C2 16.418 4.865 20.166 8.84 21.49C9.34 21.58 9.52 21.27 9.52 21C9.52 20.77 9.51 20.14 9.51 19.31C6.73 19.91 6.14 17.97 6.14 17.97C5.68 16.81 5.03 16.5 5.03 16.5C4.12 15.88 5.1 15.9 5.1 15.9C6.1 15.97 6.63 16.93 6.63 16.93C7.5 18.45 8.97 18 9.54 17.76C9.63 17.11 9.89 16.67 10.17 16.42C7.95 16.17 5.62 15.31 5.62 11.5C5.62 10.39 6 9.5 6.65 8.79C6.55 8.54 6.2 7.5 6.75 6.15C6.75 6.15 7.59 5.88 9.5 7.17C10.29 6.95 11.15 6.84 12 6.84C12.85 6.84 13.71 6.95 14.5 7.17C16.41 5.88 17.25 6.15 17.25 6.15C17.8 7.5 17.45 8.54 17.35 8.79C18 9.5 18.38 10.39 18.38 11.5C18.38 15.32 16.04 16.16 13.81 16.41C14.17 16.72 14.5 17.33 14.5 18.26C14.5 19.6 14.49 20.68 14.49 21C14.49 21.27 14.67 21.59 15.17 21.49C19.14 20.16 22 16.42 22 12C22 6.477 17.523 2 12 2Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 902 B

-10
View File
@@ -1,10 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2001_1428)">
<path d="M22.2234 0H1.77187C0.792187 0 0 0.773438 0 1.72969V22.2656C0 23.2219 0.792187 24 1.77187 24H22.2234C23.2031 24 24 23.2219 24 22.2703V1.72969C24 0.773438 23.2031 0 22.2234 0ZM7.12031 20.4516H3.55781V8.99531H7.12031V20.4516ZM5.33906 7.43438C4.19531 7.43438 3.27188 6.51094 3.27188 5.37187C3.27188 4.23281 4.19531 3.30937 5.33906 3.30937C6.47813 3.30937 7.40156 4.23281 7.40156 5.37187C7.40156 6.50625 6.47813 7.43438 5.33906 7.43438ZM20.4516 20.4516H16.8937V14.8828C16.8937 13.5562 16.8703 11.8453 15.0422 11.8453C13.1906 11.8453 12.9094 13.2937 12.9094 14.7891V20.4516H9.35625V8.99531H12.7687V10.5609H12.8156C13.2891 9.66094 14.4516 8.70938 16.1813 8.70938C19.7859 8.70938 20.4516 11.0813 20.4516 14.1656V20.4516Z" fill="#FAFAFA"/>
</g>
<defs>
<clipPath id="clip0_2001_1428">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 989 B

-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.0512 4.07466C15.3113 5.17727 16.301 5.99866 17.4829 5.99866C18.8627 5.99866 19.9813 4.87965 19.9813 3.49933C19.9813 2.11902 18.8627 1 17.4829 1C16.2764 1 15.2703 1.85537 15.036 2.99314C13.0155 3.20991 11.4378 4.92417 11.4378 7.00167C11.4378 7.00636 11.4378 7.00988 11.4378 7.01456C9.24041 7.10713 7.23397 7.73284 5.641 8.72062C5.04949 8.26247 4.30688 7.98945 3.50102 7.98945C1.5672 7.98945 0 9.55725 0 11.4918C0 12.8955 0.824597 14.1048 2.01581 14.6637C2.13177 18.7297 6.56047 22 12.0082 22C17.4559 22 21.8905 18.7261 22.0006 14.6567C23.1824 14.0942 24 12.8885 24 11.493C24 9.55842 22.4328 7.99063 20.499 7.99063C19.6966 7.99063 18.9575 8.2613 18.3672 8.71594C16.7602 7.72113 14.7315 7.09541 12.5119 7.01222C12.5119 7.0087 12.5119 7.00636 12.5119 7.00285C12.5119 5.51473 13.6176 4.27971 15.0512 4.077V4.07466ZM5.50044 13.7146C5.559 12.4444 6.40234 11.4695 7.38272 11.4695C8.3631 11.4695 9.11274 12.4995 9.05417 13.7697C8.99561 15.0398 8.26354 15.5015 7.28199 15.5015C6.30044 15.5015 5.44187 14.9848 5.50044 13.7146ZM16.6348 11.4695C17.6164 11.4695 18.4597 12.4444 18.5171 13.7146C18.5757 14.9848 17.716 15.5015 16.7356 15.5015C15.7552 15.5015 15.022 15.041 14.9634 13.7697C14.9048 12.4995 15.6533 11.4695 16.6348 11.4695ZM15.4682 16.6533C15.6521 16.6721 15.7693 16.8631 15.6978 17.0341C15.0946 18.4766 13.6703 19.4901 12.0082 19.4901C10.3461 19.4901 8.92299 18.4766 8.31859 17.0341C8.24714 16.8631 8.36427 16.6721 8.54817 16.6533C9.62577 16.5444 10.7912 16.4846 12.0082 16.4846C13.2252 16.4846 14.3895 16.5444 15.4682 16.6533Z" fill="#FAFAFA"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.3263 1.90393H21.6998L14.3297 10.3274L23 21.7899H16.2112L10.894 14.838L4.80995 21.7899H1.43443L9.31743 12.78L1 1.90393H7.96111L12.7674 8.25826L18.3263 1.90393ZM17.1423 19.7707H19.0116L6.94539 3.81706H4.93946L17.1423 19.7707Z" fill="#FAFAFA"/>
</svg>

Before

Width:  |  Height:  |  Size: 358 B

+51 -88
View File
@@ -1,5 +1,5 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
@@ -28,62 +28,61 @@
"rules": {
"recommended": true,
"correctness": {
"useExhaustiveDependencies": "info",
"useExhaustiveDependencies": "off",
"noUndeclaredVariables": "off",
"noEmptyPattern": "info",
"noEmptyPattern": "off",
"useJsxKeyInIterable": "off",
"noInnerDeclarations": "off",
"useHookAtTopLevel": "info",
"useYield": "info",
"useHookAtTopLevel": "off",
"useYield": "off",
"noConstructorReturn": "off",
"noInvalidPositionAtImportRule": "off",
"noSwitchDeclarations": "off",
"noUnusedImports": "error"
},
"a11y": "info",
"a11y": "off",
"style": {
"useNodejsImportProtocol": "off",
"useImportType": "off",
"useBlockStatements": "off",
"useBlockStatements": "warn",
"useNamingConvention": "off",
"useThrowOnlyError": "info",
"useConsistentArrayType": "off",
"noParameterAssign": "off",
"useAsConstAssertion": "off",
"useDefaultParameterLast": "off",
"noNonNullAssertion": "info",
"noNonNullAssertion": "off",
"useEnumInitializers": "off",
"useSelfClosingElements": "info",
"useSelfClosingElements": "off",
"useSingleVarDeclarator": "off",
"useNumberNamespace": "info",
"noInferrableTypes": "info",
"useTemplate": "info",
"noUselessElse": "info"
"useNumberNamespace": "off",
"noInferrableTypes": "off",
"useTemplate": "off",
"noUselessElse": "off"
},
"suspicious": {
"noDoubleEquals": "warn",
"noImplicitAnyLet": "info",
"noThenProperty": "off",
"noAsyncPromiseExecutor": "info",
"noAsyncPromiseExecutor": "off",
"noImportAssign": "off",
"noExplicitAny": "info",
"noControlCharactersInRegex": "warn",
"noExplicitAny": "off",
"noControlCharactersInRegex": "off",
"noShadowRestrictedNames": "off",
"noArrayIndexKey": "info",
"noAssignInExpressions": "info",
"useIterableCallbackReturn": "info"
"noAssignInExpressions": "warn"
},
"complexity": {
"noUselessConstructor": "info",
"useOptionalChain": "info",
"noBannedTypes": "warn",
"useLiteralKeys": "info",
"noUselessCatch": "info",
"noUselessSwitchCase": "info",
"noStaticOnlyClass": "info"
"noUselessConstructor": "off",
"useOptionalChain": "off",
"noBannedTypes": "off",
"useLiteralKeys": "off",
"noUselessCatch": "off",
"noUselessSwitchCase": "off",
"noStaticOnlyClass": "off"
},
"security": {
"noDangerouslySetInnerHtml": "info"
"noDangerouslySetInnerHtml": "warn"
}
}
},
@@ -95,11 +94,6 @@
"lineEnding": "lf",
"formatWithErrors": true
},
"css": {
"parser": {
"tailwindDirectives": true
}
},
"javascript": {
"formatter": {
"semicolons": "asNeeded",
@@ -118,21 +112,19 @@
}
},
"files": {
"ignoreUnknown": true,
"includes": [
"**",
// explicitly force files to be ignored by the scanner with !!
"!!**/dist",
"!!**/dist-*",
"!!**/out",
"!!**/evals",
"!!**/playwright",
"!!**/test-results",
"!!**/node_modules",
"!!**/webview-ui/build",
"!!**/generated",
"!!**/proto",
"!!**/tests/specs"
"!**/dist/**",
"!**/dist-*/**",
"!**/out/**",
"!**/evals/**",
"!**/playwright/**",
"!**/test-results/**",
"!**/node_modules/**",
"!**/webview-ui/build/**",
"!**/generated/**",
"!**/proto/**",
"!**/tests/specs/**"
]
},
"plugins": [
@@ -142,58 +134,29 @@
{
"includes": [
"**",
"!!**/dist",
"!!**/hosts/vscode/**",
"!!**/test/**",
"!!**/*.test.ts",
"!!src/dev/**",
"!!src/extension.ts",
"!!src/integrations/git/commit-message-generator.ts",
"!!src/integrations/terminal/**",
"!!src/core/controller/ui/openWalkthrough.ts"
"!**/hosts/vscode/**",
"!**/test/**",
"!**/*.test.ts",
"!src/dev/**",
"!src/extension.ts",
"!src/integrations/git/commit-message-generator.ts",
"!src/integrations/terminal/**",
"!src/core/controller/ui/openWalkthrough.ts"
],
"plugins": [
"src/dev/grit/vscode-api.grit"
]
},
{
// Do not use console logging directly, use the Logger service instead.
"plugins": [
"src/dev/grit/console-log.grit"
],
"includes": [
"**",
"!!**/esbuild.*",
"!!**/*.mts",
"!!**/webview-ui/**",
"!!**/evals/**",
"!!**/standalone/**",
"!!**/cli/**",
"!!**/e2e/**",
"!!**/test/**",
"!!**/__tests__/**",
"!!**/*.test.ts",
"!!**/*.stories.ts",
"!!src/dev/**",
"!!**/*.mjs",
"!!**/*.js",
"!!**/scripts/**",
"!!**/*.tsx",
"!!**/testing-platform/**",
// ACP mode must redirect console to stderr - this is intentional
"!!cli/src/acp/index.ts"
]
},
{
"includes": [
"**",
"!!src/core/storage/state-migrations.ts",
"!!src/core/storage/FileContextTracker.ts",
"!!src/core/context/context-tracking/FileContextTracker.ts",
"!!src/common.ts",
"!!src/services/logging/distinctId.ts",
"!!src/core/storage/utils/state-helpers.ts",
"!!src/extension.ts"
"!src/core/storage/state-migrations.ts",
"!src/core/storage/FileContextTracker.ts",
"!src/core/context/context-tracking/FileContextTracker.ts",
"!src/common.ts",
"!src/services/logging/distinctId.ts",
"!src/core/storage/utils/state-helpers.ts",
"!src/extension.ts"
],
"plugins": [
"src/dev/grit/use-cache-service.grit"
+2
View File
@@ -0,0 +1,2 @@
cline-core-debug.log
bin/*
-105
View File
@@ -1,105 +0,0 @@
# cline
## 2.4.2
### Added
- Gemini-3.1 Pro Preview
### Patch Changes
- VSCode uses shared files for global, workspace and secret state.
## [2.4.1]
### Fixed
- Fix infinite retry loop when write_to_file fails with missing content parameter. Provides progressive guidance to the model, escalating from suggestions to hard stops, with context window awareness to break the loop.
## [2.4.0]
### Added
- Adding Anthropic Sonnet 4.6
- Allows users to enter custom aws region when selecting bedrock as a provider in CLI
- Keep reasoning rows visible when low-stakes tool groups start immediately after reasoning.
- Restore reasoning trace visibility in chat and improve the thinking row UX so streamed reasoning is visible, then collapsible after completion.
### Fixed
- Banners now display immediately when opening the extension instead of requiring user interaction first
- Resolved 17 security vulnerabilities including high-severity DoS issues in dependencies (body-parser, axios, qs, tar, and others)
## [2.2.2]
- Allows users to enter custom aws region when selecting bedrock as a provider
- Prevent Parent Container Scrolling In Dropdowns
## [2.2.1]
- Added Minimax 2.5 Free Promo
- Fixed Response chaining for OpenAI's Responses API
## [2.2.0]
### Added
- Subagent: replace legacy subagents with the native `use_subagents` tool
- Bundle `endpoints.json` support so packaged distributions can ship required endpoints out-of-the-box
- Amazon Bedrock: support parallel tool calling
- New "double-check completion" experimental feature to verify work before marking tasks complete
- CLI: new task controls/flags including custom `--thinking` token budget and `--max-consecutive-mistakes` for yolo runs
- Remote config: new UI/options (including connection/test buttons) and support for syncing deletion of remotely configured MCP servers
- Vertex / Claude Code: add 1M context model options for Claude Opus 4.6
- ZAI/GLM: add GLM-5
### Fixed
- CLI: handle stdin redirection correctly in CI/headless environments
- CLI: preserve OAuth callback paths during auth redirects
- VS Code Web: generate auth callback URLs via `vscode.env.asExternalUri` (OAuth callback reliability)
- Terminal: surface command exit codes in results and improve long-running `execute_command` timeout behavior
- UI: add loading indicator and fix `api_req_started` rendering
- Task streaming: prevent duplicate streamed text rows after completion
- API: preserve selected Vercel model when model metadata is missing
- Telemetry: route PostHog networking through proxy-aware shared fetch and ensure telemetry flushes on shutdown
- CI: increase Windows E2E test timeout to reduce flakiness
### Changed
- Settings/model UX: move "reasoning effort" into model configuration and expose it in settings
- CLI provider selection: limit provider list to those remotely configured
- UI: consolidate ViewHeader component/styling across views
- Tools: add auto-approval support for `attempt_completion` commands
- Remotely configured MCP server schema now supports custom headers
## [2.1.0]
### Minor Changes
- 42ce100: Add Generate API Key on Hicap Provider selection
### Patch Changes
- 195294f: Add support for bundled endpoints.json in enterprise distributions. Extensions can now include a pre-configured endpoints.json file that automatically switches Cline to self-hosted mode. Includes packaging scripts for VSIX, NPM, and JetBrains plugins.
- a1f2601: Replace the LiteLLM model list with a selector
- 739d75a: Add Claude Code provider support for Claude Opus 4.6 and Sonnet 4.5 1M variants via both full model names and aliases (`opus[1m]`, `sonnet[1m]`), and align the `opus` alias with Opus 4.6.
- 8440380: Add GitHub Actions workflow to build CLI from any commit for testing
- b1a8db2: fix(cli): prevent hang when spawned without TTY
- 7c87017: Add Claude Opus 4.6 model support
- d116ac5: Supports rendering markdown table in chat view.
- 6d8fb85: Fix CLI crashing in CI environments and with stdin redirection (e.g., `cline "prompt" < /dev/null`). Now checks both stdin and stdout TTY status before using Ink, and only errors on empty stdin when no prompt is provided.
- 70a9904: Fix JetBrains sign-in regression by adding fallback for openExternal RPC
- f440f3a: fix: use vscode.env.openExternal for auth in remote environments
Fixes OAuth authentication in VS Code Server and remote environments by routing browser URL opening through VS Code's native openExternal API instead of the npm 'open' package.
- 70a9904: fix: use vscode.env.asExternalUri for auth callback URLs only in VS Code Web
Fixes OAuth callback redirect in VS Code Web (`code serve-web`, Codespaces) by using `vscode.env.asExternalUri()` to resolve the callback URI. This is gated behind a `vscode.env.uiKind === UIKind.Web` check so regular desktop VS Code continues to use the `vscode://` URI directly. The `getCallbackUrl` API now accepts a `path` parameter so the full callback URI (including route) is resolved correctly, and callers pass their path directly instead of appending after.
- 5308ded: Updating script documentation and removing unnecessary continue on error
- b514f18: Prevent duplicate streamed text rows when a partial text update arrives after the same text was already finalized.
- 26391c9: Fix Bedrock model id
- d19a877: Unify ViewHeader Styles Across All Views
- 5dcaa8c: Add Vertex Claude Opus 4.6 1M model option and global endpoint support, and pass the 1M beta header for Vertex Claude requests.
-365
View File
@@ -1,365 +0,0 @@
# Cline CLI
The official CLI for Cline. Run Cline tasks directly from the terminal with the same underlying functionality as the VS Code extension.
## Features
- **Reuses Core Codebase**: Shares the same Controller, Task, and API handling as the VS Code extension
- **Terminal Output**: Displays Cline messages directly in your terminal with colored output
- **Task History**: Access your task history from the command line
- **Configurable**: Use custom configuration directories and working directories
- **Image Support**: Attach images to your prompts using file paths or inline references
## Prerequisites
- Node.js 20.x or later
- npm or yarn
- The parent Cline project dependencies installed
## Installation
From the repository root:
```bash
# Install all dependencies first
npm run install:all
# Ensure protos are generated
npm run protos
# Build and link the CLI globally
npm run cli:link
```
## Usage
### Interactive Mode (Default)
When you run `cline` without any command, it launches an interactive welcome prompt:
```bash
# Launch interactive mode
cline
# Or run a task directly
cline "Create a hello world function in Python"
# With options
cline -v --thinking "Analyze this codebase"
```
### Commands
#### `task` (alias: `t`)
Run a new task with a prompt.
```bash
cline task "Create a hello world function in Python"
cline t "Create a hello world function"
```
**Options:**
| Option | Description |
|--------|-------------|
| `-a, --act` | Run in act mode |
| `-p, --plan` | Run in plan mode |
| `-y, --yolo` | Enable yolo mode (auto-approve actions) |
| `-m, --model <model>` | Model to use for the task |
| `-i, --images <paths...>` | Image file paths to include with the task |
| `-v, --verbose` | Show verbose output including reasoning |
| `-c, --cwd <path>` | Working directory for the task |
| `--config <path>` | Path to Cline configuration directory |
| `-t, --thinking` | Enable extended thinking (1024 token budget) |
**Examples:**
```bash
# Run in plan mode with verbose output
cline task -p -v "Design a REST API"
# Use a specific model with yolo mode
cline task -m claude-sonnet-4-5-20250929 -y "Refactor this function"
# Include images with your prompt
cline task -i screenshot.png diagram.jpg "Fix the UI based on these images"
# Or use inline image references in the prompt
cline task "Fix the layout shown in @./screenshot.png"
# Enable extended thinking for complex tasks
cline task -t "Architect a microservices system"
# Specify working directory
cline task -c /path/to/project "Add unit tests"
```
#### `history` (alias: `h`)
List task history with pagination support.
```bash
cline history
cline h
```
**Options:**
| Option | Description |
|--------|-------------|
| `-n, --limit <number>` | Number of tasks to show (default: 10) |
| `-p, --page <number>` | Page number, 1-based (default: 1) |
| `--config <path>` | Path to Cline configuration directory |
**Examples:**
```bash
# Show last 10 tasks (default)
cline history
# Show 20 tasks
cline history -n 20
# Show page 2 with 5 tasks per page
cline history -n 5 -p 2
```
#### `config`
Show current configuration including global and workspace state.
```bash
cline config
```
**Options:**
| Option | Description |
|--------|-------------|
| `--config <path>` | Path to Cline configuration directory |
#### `auth`
Authenticate a provider and configure what model is used.
```bash
cline auth
```
**Options:**
| Option | Description |
|--------|-------------|
| `-p, --provider <id>` | Provider ID for quick setup (e.g., openai-native, anthropic) |
| `-k, --apikey <key>` | API key for the provider |
| `-m, --modelid <id>` | Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929) |
| `-b, --baseurl <url>` | Base URL (optional, only for openai provider) |
| `-v, --verbose` | Show verbose output |
| `-c, --cwd <path>` | Working directory for the task |
| `--config <path>` | Path to Cline configuration directory |
**Examples:**
```bash
# Interactive authentication
cline auth
# Quick setup with provider and API key
cline auth -p anthropic -k sk-ant-xxxxx
# Full quick setup with model
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
# OpenAI-compatible provider with custom base URL
cline auth -p openai -k your-api-key -b https://api.example.com/v1
```
### Global Options
These options are available for the default command (running a task directly):
| Option | Description |
|--------|-------------|
| `-v, --verbose` | Show verbose output |
| `-c, --cwd <path>` | Working directory |
| `--config <path>` | Configuration directory |
| `--thinking` | Enable extended thinking (1024 token budget) |
## Development
### Quick Start
```bash
# 1. Install all dependencies (root, webview-ui, cli)
npm run install:all
# 2. Build and link globally so you can run `cline` from anywhere
npm run cli:link
# 3. Test it
cline --help
```
### Scripts
Run these from the repository root:
| Script | Description |
|--------|-------------|
| `npm run install:all` | Install deps for root, webview-ui, and cli |
| `npm run cli:build` | Generate protos and build CLI |
| `npm run cli:build:production` | Production build (minified) |
| `npm run cli:link` | Build and `npm link` so you can run `cline` from anywhere |
| `npm run cli:unlink` | Remove the global `cline` symlink |
| `npm run cli:dev` | Link + watch mode for development |
| `npm run cli:watch` | Watch mode only (no initial build) |
| `npm run cli:test` | Run CLI tests |
### Development Workflow
1. Run `npm run cli:dev` - this links the CLI globally and starts watch mode
2. Make changes to files in `cli/src/`
3. The build automatically rebuilds on save
4. Test your changes by running `cline` in another terminal
5. When done, run `npm run cli:unlink` to clean up
### Proto Generation
The CLI uses proto-generated types for message passing (same as the VS Code extension). If you modify any `.proto` files, run:
```bash
npm run protos
```
This generates TypeScript types in `src/generated/` that both the CLI and extension use.
## Publish
#### 1. Publish to npm
```bash
npm publish
```
#### 2. Update the Homebrew formula
```bash
npm run update-brew-formula
```
#### 3. Test the formula locally
```bash
# Create a local tap
brew tap-new cline/local
cp ./cli/cline.rb "$(brew --repository)/Library/Taps/cline/homebrew-local/Formula/cline.rb"
# Build from Source
brew install --build-from-source cline/local/cline
# Install from your local tap
brew install cline/local/cline
# Clean up when done
brew untap cline/local
```
#### 4. If using a tap, commit and push
```bash
git add cline.rb
git commit -m "Update cline to v2.0.0"
git push
```
## Architecture
### How It Works
The CLI directly imports and reuses the core Cline TypeScript codebase (the same code that powers the VS Code extension). This means feature parity is easy to maintain - when core gets updated, the CLI automatically benefits.
```
┌─────────────────────────────────────────────────────────┐
│ CLI (cli/) │
│ - React Ink terminal UI │
│ - Command parsing (commander) │
│ - Terminal-specific adapters │
└─────────────────────────────────────────────────────────┘
│ direct imports
┌─────────────────────────────────────────────────────────┐
│ Core (src/core/) │
│ - Controller: task lifecycle, state management │
│ - Task: AI API calls, tool execution │
│ - StateManager: persistent storage │
│ - Proto types: message definitions │
└─────────────────────────────────────────────────────────┘
```
Unlike a client-server architecture, the CLI runs everything in a single Node.js process. The "host bridge" pattern provides terminal-appropriate implementations for things the VS Code extension would handle differently (clipboard, file dialogs, etc.).
### Key Files
| File | Purpose |
|------|---------|
| `src/index.ts` | Entry point, command definitions |
| `src/components/App.tsx` | Main React Ink app |
| `src/components/ChatView.tsx` | Task conversation UI |
| `src/controllers/CliWebviewProvider.ts` | Bridges core messages to terminal output |
| `src/vscode-context.ts` | Mock VS Code extension context for core compatibility |
| `src/vscode-shim.ts` | Shims for VS Code APIs that core depends on |
| `src/constants/colors.ts` | Terminal color definitions |
### React Ink
The CLI uses [React Ink](https://github.com/vadimdemedes/ink) for its terminal UI. This lets us build the interface with React components that render to the terminal. Key patterns:
- Components in `src/components/` render terminal UI
- Hooks in `src/hooks/` manage terminal-specific state (size, scrolling)
- The `useStateSubscriber` hook subscribes to core state changes
## Configuration
The CLI stores its data in `~/.cline/data/` by default:
- `globalState.json`: Global settings and state
- `secrets.json`: API keys and secrets
- `workspace/`: Workspace-specific state
- `tasks/`: Task history and conversation data
Override with the `--config` option or `CLINE_DIR` environment variable.
## Troubleshooting
### Build Errors
If you encounter build errors:
```bash
# Make sure all deps are installed
npm run install:all
# Regenerate proto types
npm run protos
# Then rebuild
npm run cli:build
```
### "command not found: cline"
The CLI isn't linked globally. Run:
```bash
npm run cli:link
```
### Changes Not Reflected
If your code changes aren't showing up:
1. Make sure watch mode is running (`npm run cli:dev`)
2. Check for TypeScript errors in the watch output
3. Try unlinking and relinking: `npm run cli:unlink && npm run cli:link`
### Import Errors from Core
The CLI imports from `@core/`, `@shared/`, etc. These paths are defined in the root `tsconfig.json`. If you see import errors, make sure you're building from the repo root, not from inside `cli/`.
-81
View File
@@ -1,81 +0,0 @@
# Cline
<p align="center">
<img src="https://github.com/user-attachments/assets/7123f9d1-afeb-48d5-93fa-e750dec0ebba" width="70%" />
</p>
<div align="center">
<table>
<tbody>
<td align="center">
<a href="https://www.npmjs.com/package/cline" target="_blank"><strong>NPM</strong></a>
</td>
<td align="center">
<a href="https://discord.gg/cline" target="_blank"><strong>Discord</strong></a>
</td>
<td align="center">
<a href="https://www.reddit.com/r/cline/" target="_blank"><strong>r/cline</strong></a>
</td>
<td align="center">
<a href="https://github.com/cline/cline/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+Requests%22+sort%3Atop" target="_blank"><strong>Feature Requests</strong></a>
</td>
<td align="center">
<a href="https://docs.cline.bot/getting-started/for-new-coders" target="_blank"><strong>Getting Started</strong></a>
</td>
</tbody>
</table>
</div>
Meet Cline, an AI assistant that lives in your terminal.
Cline can handle complex software development tasks step-by-step. With tools that let him create & edit files, explore large projects, use the browser, and execute terminal commands (after you grant permission), he can assist you in ways that go beyond code completion or tech support.
```bash
npm i -g cline
# cd into your project and run:
cline
```
> Move your mouse around under the Cline icon for a surprise!
---
<img align="right" width="340" src="https://github.com/user-attachments/assets/ceb74224-08aa-4b8b-a3e7-b438ac3d160a">
### Use any API and Model
Cline supports API providers like ChatGPT, Anthropic, OpenAI, Google Gemini, AWS Bedrock, Azure, GCP Vertex, Cerebras, Groq, and Moonshot. You can also configure any OpenAI compatible API, or use a local model through LM Studio/Ollama. If you're using a Cline Account, you'll always have access to the newest models as soon as they're available.
<!-- Transparent pixel to create line break after floating image -->
<img width="2000" height="0" src="https://github.com/user-attachments/assets/ee14e6f7-20b8-4391-9091-8e8e25561929"><br>
<img align="left" width="370" src="https://github.com/user-attachments/assets/cad091f6-6c0f-4e4b-97ea-a1ff67e39b9b">
### Stay in Control with Human-in-the-Loop
Cline asks for your approval before running commands, editing files, or taking any action. Review each step and approve or reject as you go—or enable auto-approve to let Cline work autonomously to completion.
<!-- Transparent pixel to create line break after floating image -->
<img width="2000" height="0" src="https://github.com/user-attachments/assets/cad091f6-6c0f-4e4b-97ea-a1ff67e39b9b"><br>
<img align="right" width="400" src="https://github.com/user-attachments/assets/4f264a0c-3802-49a7-8e5e-13d97beb659e">
### Plan & Act Modes
Toggle to Plan Mode to discuss implementation and architecture with Cline. He'll ask clarifying questions, explore your codebase, and present a plan for you to align on. Once you're satisfied, switch to Act Mode and let Cline execute the plan.
<!-- Transparent pixel to create line break after floating image -->
<img width="2000" height="0" src="https://github.com/user-attachments/assets/4f264a0c-3802-49a7-8e5e-13d97beb659e"><br>
## Enterprise
Get the same Cline experience with enterprise-grade controls: SSO (SAML/OIDC), global policies and configuration, observability with audit trails, private networking (VPC/private link), and self-hosted or on-prem deployments, and enterprise support. Learn more at our [enterprise page](https://cline.bot/enterprise) or [talk to us](https://cline.bot/contact-sales).
## License
[Apache 2.0 © 2026 Cline Bot Inc.](./LICENSE)
+6
View File
@@ -0,0 +1,6 @@
/_____/\ /_/\ /_______/\/__/\ /__/\ /_____/\
\:::__\/ \:\ \ \__.::._\/\::\_\\ \ \\::::_\/_
\:\ \ __\:\ \ \::\ \ \:. `-\ \ \\:\/___/\
\:\ \/_/\\:\ \____ _\::\ \__\:. _ \ \\::___\/_
\:\_\ \ \\:\/___/\/__\::\__/\\. \`-\ \ \\:\____/\
\_____\/ \_____\/\________\/ \__\/ \__\/ \_____\/
-21
View File
@@ -1,21 +0,0 @@
# IMPORTANT: `npm run postpublish` to update this file after publishing a new version of the package
class Cline < Formula
desc "Autonomous coding agent CLI - capable of creating/editing files, running commands, and more"
homepage "https://cline.bot"
url "https://registry.npmjs.org/cline/-/cline-2.0.0.tgz" # GET from https://registry.npmjs.org/cline/latest tarball URL
sha256 "65bae90401191aeeabfbbc0b315e816aea96742043ba85b90671bf5e19d0761e"
license "Apache-2.0"
depends_on "node@20"
depends_on "ripgrep"
def install
system "npm", "install", *std_npm_args(prefix: false)
bin.install_symlink Dir["#{libexec}/bin/*"]
end
test do
# Test that the binary exists and is executable
assert_match version.to_s, shell_output("#{bin}/cline --version")
end
end
+71
View File
@@ -0,0 +1,71 @@
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/spf13/cobra"
"github.com/cline/cli/pkg/hostbridge"
)
var (
port int
verbose bool
)
func main() {
rootCmd := &cobra.Command{
Use: "cline-host",
Short: "Cline Host Bridge Service",
Long: `A simple host bridge service that provides host operations for Cline Core.`,
RunE: runServer,
}
rootCmd.Flags().IntVarP(&port, "port", "p", 51052, "port to listen on")
rootCmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "verbose logging")
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func runServer(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Create gRPC hostbridge service
service := hostbridge.NewGrpcServer(port, verbose)
// Handle graceful shutdown
ctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
if verbose {
log.Println("Shutting down hostbridge server...")
}
cancel()
}()
// Start server
if verbose {
log.Printf("Starting Cline Host Bridge on port %d", port)
}
// Run the service
if err := service.Start(ctx); err != nil {
return fmt.Errorf("failed to run service: %w", err)
}
return nil
}
+228
View File
@@ -0,0 +1,228 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli"
"github.com/cline/cli/pkg/cli/auth"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/common"
"github.com/cline/grpc-go/cline"
"github.com/spf13/cobra"
)
var (
coreAddress string
verbose bool
outputFormat string
// Task creation flags (for root command)
images []string
files []string
workspaces []string
mode string
settings []string
yolo bool
)
func main() {
rootCmd := &cobra.Command{
Use: "cline [prompt]",
Short: "Cline CLI - AI-powered coding assistant",
Long: `A command-line interface for interacting with Cline AI coding assistant.
Start a new task by providing a prompt:
cline "Create a new Python script that prints hello world"
Or run with no arguments to enter interactive mode:
cline
This CLI also provides task management, configuration, and monitoring capabilities.`,
Args: cobra.ArbitraryArgs,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if outputFormat != "rich" && outputFormat != "json" && outputFormat != "plain" {
return fmt.Errorf("invalid output format '%s': must be one of 'rich', 'json', or 'plain'", outputFormat)
}
return global.InitializeGlobalConfig(&global.GlobalConfig{
Verbose: verbose,
OutputFormat: outputFormat,
CoreAddress: coreAddress,
})
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
var instanceAddress string
// If --address flag not provided, start instance BEFORE getting prompt
if !cmd.Flags().Changed("address") {
if global.Config.Verbose {
fmt.Println("Starting new Cline instance...")
}
instance, err := global.Clients.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start new instance: %w", err)
}
instanceAddress = instance.Address
if global.Config.Verbose {
fmt.Printf("Started instance at %s\n\n", instanceAddress)
}
// Set up cleanup on exit
defer func() {
if global.Config.Verbose {
fmt.Println("\nCleaning up instance...")
}
registry := global.Clients.GetRegistry()
if err := global.KillInstanceByAddress(context.Background(), registry, instanceAddress); err != nil {
if global.Config.Verbose {
fmt.Printf("Warning: Failed to clean up instance: %v\n", err)
}
}
}()
// Check if user has credentials configured
if !isUserReadyToUse(ctx, instanceAddress) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
markdown := "## hey there! looks like you're new here. let's get you set up"
rendered := renderer.RenderMarkdown(markdown)
fmt.Printf("\n%s\n\n", rendered)
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
return fmt.Errorf("auth setup failed: %w", err)
}
// Re-check after auth wizard
if !isUserReadyToUse(ctx, instanceAddress) {
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
markdown = "## ✓ setup complete, you can now use the cline cli"
rendered = renderer.RenderMarkdown(markdown)
fmt.Printf("\n%s\n\n", rendered)
}
} else {
// User specified --address flag, use that
instanceAddress = coreAddress
}
var prompt string
// If args provided, use as prompt
if len(args) > 0 {
prompt = strings.Join(args, " ")
} else {
// Show interactive input to get prompt
var err error
prompt, err = promptForInitialTask()
if err != nil {
return err
}
if prompt == "" {
return fmt.Errorf("prompt required")
}
}
return cli.CreateAndFollowTask(ctx, prompt, cli.TaskOptions{
Images: images,
Files: files,
Workspaces: workspaces,
Mode: mode,
Settings: settings,
Yolo: yolo,
Address: instanceAddress,
})
},
}
rootCmd.PersistentFlags().StringVar(&coreAddress, "address", fmt.Sprintf("localhost:%d", common.DEFAULT_CLINE_CORE_PORT), "Cline Core gRPC address")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "rich", "output format (rich|json|plain)")
// Task creation flags (only apply when using root command with prompt)
rootCmd.Flags().StringSliceVarP(&images, "image", "i", nil, "attach image files")
rootCmd.Flags().StringSliceVarP(&files, "file", "f", nil, "attach files")
rootCmd.Flags().StringSliceVarP(&workspaces, "workdir", "w", nil, "workdir directory paths")
rootCmd.Flags().StringVarP(&mode, "mode", "m", "plan", "mode (act|plan) - defaults to plan")
rootCmd.Flags().StringSliceVarP(&settings, "setting", "s", nil, "task settings (key=value format)")
rootCmd.Flags().BoolVarP(&yolo, "yolo", "y", false, "enable yolo mode (non-interactive)")
rootCmd.AddCommand(cli.NewTaskCommand())
rootCmd.AddCommand(cli.NewInstanceCommand())
rootCmd.AddCommand(cli.NewConfigCommand())
rootCmd.AddCommand(cli.NewVersionCommand())
rootCmd.AddCommand(cli.NewAuthCommand())
rootCmd.AddCommand(cli.NewTaskSendCommand())
rootCmd.AddCommand(cli.NewLogsCommand())
if err := rootCmd.ExecuteContext(context.Background()); err != nil {
os.Exit(1)
}
}
func promptForInitialTask() (string, error) {
var prompt string
form := huh.NewForm(
huh.NewGroup(
huh.NewText().
Title("Start a new Cline task").
Description("What would you like Cline to help you with?").
Placeholder("e.g., Create a REST API with authentication...").
Lines(5).
Value(&prompt),
),
)
err := form.Run()
if err != nil {
return "", err
}
return strings.TrimSpace(prompt), nil
}
// isUserReadyToUse checks if the user has completed initial setup
// Returns true if welcomeViewCompleted flag is set OR user is authenticated
// Matches extension logic: welcomeViewCompleted = Boolean(globalState.welcomeViewCompleted || user?.uid)
func isUserReadyToUse(ctx context.Context, instanceAddress string) bool {
manager, err := cli.NewTaskManagerForAddress(ctx, instanceAddress)
if err != nil {
return false
}
// Get state
state, err := manager.GetClient().State.GetLatestState(ctx, &cline.EmptyRequest{})
if err != nil {
return false
}
// Parse state JSON
stateMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(state.StateJson), &stateMap); err != nil {
return false
}
// Check 1: welcomeViewCompleted flag
if welcomeCompleted, ok := stateMap["welcomeViewCompleted"].(bool); ok && welcomeCompleted {
return true
}
// Check 2: Is user authenticated? (matches extension's || user?.uid check)
if userInfo, ok := stateMap["userInfo"].(map[string]interface{}); ok {
if uid, ok := userInfo["uid"].(string); ok && uid != "" {
return true
}
}
return false
}
+154
View File
@@ -0,0 +1,154 @@
package e2e
import (
"context"
"encoding/json"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/cline/cli/pkg/common"
)
// 2. Multi-instance start: default_instance remains the first started.
func TestMultiInstanceDefaultUnchanged(t *testing.T) {
_ = setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Start first instance and wait healthy
_ = mustRunCLI(ctx, t, "instance", "new")
out1 := listInstancesJSON(ctx, t)
if len(out1.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out1.CoreInstances))
}
firstAddr := out1.CoreInstances[0].Address
waitForAddressHealthy(t, firstAddr, defaultTimeout)
// Start second instance
_ = mustRunCLI(ctx, t, "instance", "new")
out2 := listInstancesJSON(ctx, t)
if len(out2.CoreInstances) < 2 {
t.Fatalf("expected at least 2 instances, got %d", len(out2.CoreInstances))
}
// Default should remain the first started address
if out2.DefaultInstance != firstAddr {
t.Fatalf("default changed; expected %s, got %s", firstAddr, out2.DefaultInstance)
}
}
// 6. Default.json update after removal of current default
func TestDefaultJsonUpdateAfterRemoval(t *testing.T) {
_ = setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Start two instances
_ = mustRunCLI(ctx, t, "instance", "new")
_ = mustRunCLI(ctx, t, "instance", "new")
out := listInstancesJSON(ctx, t)
if len(out.CoreInstances) < 2 {
t.Fatalf("expected at least 2 instances, got %d", len(out.CoreInstances))
}
// Choose second as new default
target := out.CoreInstances[1]
waitForAddressHealthy(t, target.Address, defaultTimeout)
// Set as default
_ = mustRunCLI(ctx, t, "instance", "use", target.Address)
// Verify default switched
out = listInstancesJSON(ctx, t)
if out.DefaultInstance != target.Address {
t.Fatalf("default_instance not updated to %s (got %s)", target.Address, out.DefaultInstance)
}
// Kill the default instance using runtime PID discovery
corePID := getCorePID(t, target.Address)
if corePID <= 0 {
t.Fatalf("could not find PID for core process at %s", target.Address)
}
t.Logf("Killing cline-core process PID %d for instance %s", corePID, target.Address)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
// Wait for removal
waitForAddressRemoved(t, target.Address, longTimeout)
// Clean up dangling host process (SIGKILL leaves these behind by design)
t.Logf("Cleaning up dangling host process on port %d", target.HostPort())
findAndKillHostProcess(t, target.HostPort())
// Ensure default_instance updated to another available instance (or removed if none remain)
out = listInstancesJSON(ctx, t)
// If there are instances left, default_instance must be one of them
if len(out.CoreInstances) > 0 {
found := false
for _, it := range out.CoreInstances {
if out.DefaultInstance == it.Address {
found = true
break
}
}
if !found {
t.Fatalf("default_instance %s not set to an existing instance after removal", out.DefaultInstance)
}
} else {
// No instances remain; cli-default-instance.json should be removed
clineDir := getClineDir(t)
defPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
if _, err := os.Stat(defPath); err == nil {
t.Fatalf("expected cli-default-instance.json removed when no instances remain")
}
}
// Also verify cli-default-instance.json on disk reflects the in-memory default (if any)
clineDir := getClineDir(t)
defPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
if len(out.CoreInstances) > 0 {
raw, err := os.ReadFile(defPath)
if err != nil {
t.Fatalf("read cli-default-instance.json: %v", err)
}
var tmp struct {
DefaultInstance string `json:"default_instance"`
}
if err := json.Unmarshal(raw, &tmp); err != nil {
t.Fatalf("unmarshal cli-default-instance.json: %v", err)
}
if tmp.DefaultInstance != out.DefaultInstance {
t.Fatalf("cli-default-instance.json mismatch: file=%s list=%s", tmp.DefaultInstance, out.DefaultInstance)
}
}
}
// 11. SQLite database missing (edge): list succeeds and returns empty set
func TestRegistryDirMissingEdge(t *testing.T) {
clineDir := setTempClineDir(t)
// Remove the settings directory entirely (which contains locks.db)
settingsDir := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER)
if err := os.RemoveAll(settingsDir); err != nil {
t.Fatalf("RemoveAll(%s): %v", common.SETTINGS_SUBFOLDER, err)
}
// Listing should succeed and return empty results
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
out := listInstancesJSON(ctx, t)
if len(out.CoreInstances) != 0 {
t.Fatalf("expected 0 instances after removing %s dir, got %d", common.SETTINGS_SUBFOLDER, len(out.CoreInstances))
}
// Ensure cli-default-instance.json not present
defPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
if _, err := os.Stat(defPath); err == nil {
t.Fatalf("expected no cli-default-instance.json after removing %s dir", common.SETTINGS_SUBFOLDER)
}
}
+378
View File
@@ -0,0 +1,378 @@
package e2e
import (
"context"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"testing"
"time"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/common"
"github.com/cline/grpc-go/cline"
)
const (
defaultTimeout = 30 * time.Second
longTimeout = 60 * time.Second
pollInterval = 250 * time.Millisecond
instancesBinRel = "../bin/cline"
)
func repoAwareBinPath(t *testing.T) string {
// Tests live in repoRoot/cli/e2e. Binary is at repoRoot/cli/bin/cline
t.Helper()
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Getwd error: %v", err)
}
// cli/e2e -> cli/bin/cline
p := filepath.Clean(filepath.Join(wd, instancesBinRel))
if _, err := os.Stat(p); err != nil {
t.Fatalf("CLI binary not found at %s; run `npm run compile-cli` first: %v", p, err)
}
return p
}
func setTempClineDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
clineDir := filepath.Join(dir, ".cline")
if err := os.MkdirAll(clineDir, 0o755); err != nil {
t.Fatalf("mkdir clineDir: %v", err)
}
t.Setenv("CLINE_DIR", clineDir)
return clineDir
}
func runCLI(ctx context.Context, t *testing.T, args ...string) (string, string, int) {
t.Helper()
bin := repoAwareBinPath(t)
// Ensure CLI uses the same CLINE_DIR as the tests by passing --config=<CLINE_DIR>
// (InitializeGlobalConfig uses ConfigPath as the base directory for registry.)
if clineDir := os.Getenv("CLINE_DIR"); clineDir != "" && !contains(args, "--config") {
// Prepend persistent flag so Cobra sees it regardless of subcommand position
args = append([]string{"--config", clineDir}, args...)
}
cmd := exec.CommandContext(ctx, bin, args...)
// Run CLI from repo root so relative paths inside CLI (./cli/bin/...) resolve
if wd, err := os.Getwd(); err == nil {
repoRoot := filepath.Clean(filepath.Join(wd, "..", ".."))
cmd.Dir = repoRoot
}
// propagate env including CLINE_DIR
cmd.Env = os.Environ()
outB, errB := &strings.Builder{}, &strings.Builder{}
cmd.Stdout = outB
cmd.Stderr = errB
err := cmd.Run()
exit := 0
if err != nil {
// Extract exit code if possible
if ee, ok := err.(*exec.ExitError); ok {
exit = ee.ExitCode()
} else {
exit = -1
}
}
return outB.String(), errB.String(), exit
}
func mustRunCLI(ctx context.Context, t *testing.T, args ...string) string {
t.Helper()
out, errOut, exit := runCLI(ctx, t, args...)
if exit != 0 {
t.Fatalf("cline %v failed (exit=%d)\nstdout:\n%s\nstderr:\n%s", args, exit, out, errOut)
}
return out
}
func listInstancesJSON(ctx context.Context, t *testing.T) common.InstancesOutput {
t.Helper()
// Trigger CLI to perform cleanup/health by invoking list (table output is ignored)
_ = mustRunCLI(ctx, t, "instance", "list")
// Read from SQLite locks database to build structured output
clineDir := getClineDir(t)
// Load default instance from settings file
defaultInstance := readDefaultInstanceFromSettings(t, clineDir)
// Load instances from SQLite
instances := readInstancesFromSQLite(t, clineDir)
return common.InstancesOutput{
DefaultInstance: defaultInstance,
CoreInstances: instances,
}
}
func hasAddress(in common.InstancesOutput, addr string) bool {
for _, it := range in.CoreInstances {
if it.Address == addr {
return true
}
}
return false
}
func getByAddress(in common.InstancesOutput, addr string) (common.CoreInstanceInfo, bool) {
for _, it := range in.CoreInstances {
if it.Address == addr {
return it, true
}
}
return common.CoreInstanceInfo{}, false
}
func waitFor(t *testing.T, timeout time.Duration, cond func() (bool, string)) {
t.Helper()
deadline := time.Now().Add(timeout)
for {
ok, msg := cond()
if ok {
return
}
if time.Now().After(deadline) {
t.Fatalf("waitFor timeout: %s", msg)
}
time.Sleep(pollInterval)
}
}
func waitForAddressHealthy(t *testing.T, addr string, timeout time.Duration) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
t.Logf("Waiting for gRPC health check on %s...", addr)
waitFor(t, timeout, func() (bool, string) {
if common.IsInstanceHealthy(ctx, addr) {
return true, ""
}
return false, fmt.Sprintf("gRPC health check failed for %s", addr)
})
t.Logf("gRPC health check passed for %s", addr)
}
func waitForAddressRemoved(t *testing.T, addr string, timeout time.Duration) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
waitFor(t, timeout, func() (bool, string) {
out := listInstancesJSON(ctx, t)
if hasAddress(out, addr) {
return false, fmt.Sprintf("address %s still present", addr)
}
return true, ""
})
}
func findFreePort(t *testing.T) int {
t.Helper()
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen 127.0.0.1:0: %v", err)
}
defer l.Close()
_, portStr, _ := net.SplitHostPort(l.Addr().String())
var port int
fmt.Sscanf(portStr, "%d", &port)
return port
}
func getClineDir(t *testing.T) string {
t.Helper()
clineDir := os.Getenv("CLINE_DIR")
if clineDir == "" {
t.Fatalf("CLINE_DIR not set")
}
return clineDir
}
// isPortInUse checks if a port is currently in use by any process
func isPortInUse(port int) bool {
conn, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return true // Port is in use
}
conn.Close()
return false // Port is free
}
// waitForPortClosed waits for a port to become free (no process listening)
func waitForPortClosed(t *testing.T, port int, timeout time.Duration) {
t.Helper()
waitFor(t, timeout, func() (bool, string) {
if isPortInUse(port) {
return false, fmt.Sprintf("port %d still in use", port)
}
return true, ""
})
}
// waitForPortsClosed waits for both core and host ports to become free
func waitForPortsClosed(t *testing.T, corePort, hostPort int, timeout time.Duration) {
t.Helper()
waitFor(t, timeout, func() (bool, string) {
if isPortInUse(corePort) {
return false, fmt.Sprintf("core port %d still in use", corePort)
}
if isPortInUse(hostPort) {
return false, fmt.Sprintf("host port %d still in use", hostPort)
}
return true, ""
})
}
// findAndKillHostProcess finds and kills any process listening on the host port
// This is used to clean up dangling host processes after SIGKILL tests
func findAndKillHostProcess(t *testing.T, hostPort int) {
t.Helper()
// Use lsof to find process listening on the host port
cmd := exec.Command("lsof", "-ti", fmt.Sprintf(":%d", hostPort))
output, err := cmd.Output()
if err != nil {
// No process found on port - that's fine
return
}
pidStr := strings.TrimSpace(string(output))
if pidStr == "" {
return
}
var pid int
if _, err := fmt.Sscanf(pidStr, "%d", &pid); err != nil {
t.Logf("Warning: could not parse PID from lsof output: %s", pidStr)
return
}
if pid > 0 {
t.Logf("Cleaning up dangling host process PID %d on port %d", pid, hostPort)
if err := syscall.Kill(pid, syscall.SIGKILL); err != nil {
t.Logf("Warning: failed to kill dangling host process %d: %v", pid, err)
}
}
}
// getPIDByPort returns the PID of the process listening on the specified port (fallback method)
func getPIDByPort(t *testing.T, port int) int {
t.Helper()
cmd := exec.Command("lsof", "-ti", fmt.Sprintf(":%d", port))
output, err := cmd.Output()
if err != nil {
return 0 // Process not found
}
pidStr := strings.TrimSpace(string(output))
if pidStr == "" {
return 0
}
pid, err := strconv.Atoi(pidStr)
if err != nil {
t.Logf("Warning: could not parse PID from lsof output: %s", pidStr)
return 0
}
return pid
}
// getCorePIDViaRPC returns the PID of the cline-core process using RPC (preferred method)
func getCorePIDViaRPC(t *testing.T, address string) int {
t.Helper()
// Initialize global config to access registry
clineDir := os.Getenv("CLINE_DIR")
if clineDir == "" {
t.Logf("Warning: CLINE_DIR not set, falling back to lsof")
return getCorePIDViaLsof(t, address)
}
cfg := &global.GlobalConfig{
ConfigPath: clineDir,
}
if err := global.InitializeGlobalConfig(cfg); err != nil {
t.Logf("Warning: failed to initialize global config, falling back to lsof: %v", err)
return getCorePIDViaLsof(t, address)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Get client for the address
client, err := global.Clients.GetRegistry().GetClient(ctx, address)
if err != nil {
t.Logf("Warning: failed to get client for %s, falling back to lsof: %v", address, err)
return getCorePIDViaLsof(t, address)
}
// Call GetProcessInfo RPC
processInfo, err := client.State.GetProcessInfo(ctx, &cline.EmptyRequest{})
if err != nil {
t.Logf("Warning: GetProcessInfo RPC failed for %s, falling back to lsof: %v", address, err)
return getCorePIDViaLsof(t, address)
}
return int(processInfo.ProcessId)
}
// getCorePIDViaLsof returns the PID using lsof (fallback method)
func getCorePIDViaLsof(t *testing.T, address string) int {
t.Helper()
_, portStr, err := net.SplitHostPort(address)
if err != nil {
t.Logf("Warning: invalid address format %s", address)
return 0
}
port, err := strconv.Atoi(portStr)
if err != nil {
t.Logf("Warning: invalid port in address %s", address)
return 0
}
return getPIDByPort(t, port)
}
// getCorePID returns the PID of the cline-core process for the given address
// Uses RPC first, falls back to lsof if RPC fails
func getCorePID(t *testing.T, address string) int {
t.Helper()
// Try RPC first (preferred method)
if pid := getCorePIDViaRPC(t, address); pid > 0 {
return pid
}
// Fall back to lsof if RPC fails
return getCorePIDViaLsof(t, address)
}
// getHostPID returns the PID of the cline-host process for the given host port
func getHostPID(t *testing.T, hostPort int) int {
t.Helper()
return getPIDByPort(t, hostPort)
}
// contains reports whether slice has the target string.
func contains(slice []string, target string) bool {
for _, s := range slice {
if s == target {
return true
}
}
return false
}
+47
View File
@@ -0,0 +1,47 @@
package e2e
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
// TestMain validates required artifacts exist before running E2E tests.
// It does NOT build artifacts. Build manually via:
//
// npm run compile-standalone
// npm run compile-cli
func TestMain(m *testing.M) {
// Determine repo root from cli/e2e
wd, err := os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "getwd: %v\n", err)
os.Exit(2)
}
repoRoot := filepath.Clean(filepath.Join(wd, "..", ".."))
cliBin := filepath.Join(repoRoot, "cli", "bin", "cline")
coreJS := filepath.Join(repoRoot, "dist-standalone", "cline-core.js")
missing := []string{}
if _, err := os.Stat(cliBin); err != nil {
missing = append(missing, cliBin)
}
if _, err := os.Stat(coreJS); err != nil {
missing = append(missing, coreJS)
}
if len(missing) > 0 {
if testing.Short() {
// Optional quality-of-life: allow skipping with -short when artifacts are absent
fmt.Fprintf(os.Stderr, "[e2e] skipping (-short) due to missing artifacts:\n %s\n", strings.Join(missing, "\n "))
os.Exit(0)
}
fmt.Fprintf(os.Stderr, "Missing required build artifacts for E2E tests:\n %s\n\nPlease build them first:\n npm run compile-standalone\n npm run compile-cli\n", strings.Join(missing, "\n "))
os.Exit(2)
}
os.Exit(m.Run())
}
+120
View File
@@ -0,0 +1,120 @@
package e2e
import (
"context"
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/cline/cli/pkg/common"
)
// 9. Mixed localhost vs 127.0.0.1 addresses coexist and are both healthy
func TestMixedLocalhostVs127Coexist(t *testing.T) {
clineDir := setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Start one instance
_ = mustRunCLI(ctx, t, "instance", "new")
// Get the running instance and its port/PID
out := listInstancesJSON(ctx, t)
if len(out.CoreInstances) == 0 {
t.Fatalf("expected at least 1 instance")
}
inst := out.CoreInstances[0]
waitForAddressHealthy(t, inst.Address, defaultTimeout)
// Manually add a SQLite entry for the same port but 127.0.0.1 host
addr127 := fmt.Sprintf("127.0.0.1:%d", inst.CorePort())
dbPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "locks.db")
if err := insertRemoteInstanceIntoSQLite(t, dbPath, addr127, inst.CorePort(), inst.HostPort()); err != nil {
t.Fatalf("insert 127 alias entry: %v", err)
}
// Verify both addresses appear and are healthy
waitForAddressHealthy(t, inst.Address, defaultTimeout)
waitForAddressHealthy(t, addr127, defaultTimeout)
out = listInstancesJSON(ctx, t)
if !hasAddress(out, inst.Address) || !hasAddress(out, addr127) {
t.Fatalf("expected both %s and %s present", inst.Address, addr127)
}
}
// 10. Start-stop stress: loop starting then killing instances; ensure no leftovers
func TestStartStopStress(t *testing.T) {
_ = setTempClineDir(t)
for i := 0; i < 3; i++ { // keep small for CI time
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Snapshot current addresses
before := listInstancesJSON(ctx, t)
beforeSet := map[string]struct{}{}
for _, it := range before.CoreInstances {
beforeSet[it.Address] = struct{}{}
}
// Start a new instance
_ = mustRunCLI(ctx, t, "instance", "new")
// Find the new instance address
var newAddr string
waitFor(t, defaultTimeout, func() (bool, string) {
after := listInstancesJSON(ctx, t)
for _, it := range after.CoreInstances {
if _, ok := beforeSet[it.Address]; !ok {
newAddr = it.Address
return true, ""
}
}
return false, "new instance address not detected yet"
})
// Wait healthy
waitForAddressHealthy(t, newAddr, defaultTimeout)
// Get PID using runtime discovery and kill it
after := listInstancesJSON(ctx, t)
info, ok := getByAddress(after, newAddr)
if !ok {
t.Fatalf("new instance %s missing", newAddr)
}
// Get PID using runtime discovery
corePID := getCorePID(t, info.Address)
if corePID <= 0 {
t.Fatalf("could not find PID for new instance at %s", info.Address)
}
t.Logf("Killing new instance %s (PID %d) for iteration %d", info.Address, corePID, i)
if err := syscall.Kill(corePID, syscall.SIGKILL); err != nil {
t.Fatalf("kill pid %d: %v", corePID, err)
}
// Wait removed from SQLite database
waitForAddressRemoved(t, newAddr, longTimeout)
// Verify instance is removed from SQLite database
clineDir := os.Getenv("CLINE_DIR")
if clineDir != "" {
dbPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "locks.db")
if verifyInstanceExistsInSQLite(t, dbPath, newAddr) {
t.Fatalf("expected instance removed from SQLite database: %s", newAddr)
}
}
// Clean up dangling host process (SIGKILL leaves these behind by design)
t.Logf("Cleaning up dangling host process on port %d for iteration %d", info.HostPort(), i)
findAndKillHostProcess(t, info.HostPort())
// Verify both ports are now free
waitForPortsClosed(t, info.CorePort(), info.HostPort(), defaultTimeout)
}
}
+161
View File
@@ -0,0 +1,161 @@
package e2e
import (
"database/sql"
"encoding/json"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"github.com/cline/cli/pkg/common"
_ "github.com/mattn/go-sqlite3"
"google.golang.org/grpc/health/grpc_health_v1"
)
// readInstancesFromSQLite reads instances directly from the SQLite database for testing
func readInstancesFromSQLite(t *testing.T, clineDir string) []common.CoreInstanceInfo {
t.Helper()
dbPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "locks.db")
// Check if database exists
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
return []common.CoreInstanceInfo{}
}
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
t.Logf("Warning: Failed to open SQLite database: %v", err)
return []common.CoreInstanceInfo{}
}
defer db.Close()
// Query instance locks
query := common.SelectInstanceLockHoldersAscSQL
rows, err := db.Query(query)
if err != nil {
t.Logf("Warning: Failed to query instance locks: %v", err)
return []common.CoreInstanceInfo{}
}
defer rows.Close()
var instances []common.CoreInstanceInfo
for rows.Next() {
var heldBy, lockTarget string
var lockedAt int64
err := rows.Scan(&heldBy, &lockTarget, &lockedAt)
if err != nil {
t.Logf("Warning: Failed to scan lock row: %v", err)
continue
}
// Create InstanceInfo
info := common.CoreInstanceInfo{
Address: heldBy,
HostServiceAddress: lockTarget,
Status: grpc_health_v1.HealthCheckResponse_UNKNOWN, // Will be updated by health check
LastSeen: time.Unix(lockedAt/1000, 0), // Convert from milliseconds
}
instances = append(instances, info)
}
return instances
}
// readDefaultInstanceFromSettings reads the default instance from the settings file
func readDefaultInstanceFromSettings(t *testing.T, clineDir string) string {
t.Helper()
settingsPath := filepath.Join(clineDir, common.SETTINGS_SUBFOLDER, "settings", "cli-default-instance.json")
data, err := os.ReadFile(settingsPath)
if err != nil {
if os.IsNotExist(err) {
return ""
}
t.Logf("Warning: Failed to read default instance file: %v", err)
return ""
}
var tmp struct {
DefaultInstance string `json:"default_instance"`
}
if err := json.Unmarshal(data, &tmp); err != nil {
t.Logf("Warning: Failed to parse default instance file: %v", err)
return ""
}
return tmp.DefaultInstance
}
// insertRemoteInstanceIntoSQLite inserts a remote instance entry directly into SQLite for testing
func insertRemoteInstanceIntoSQLite(t *testing.T, dbPath, address string, corePort, hostPort int) error {
t.Helper()
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
return err
}
defer db.Close()
// Initialize database schema for testing
createTableSQL := `
CREATE TABLE IF NOT EXISTS locks (
id INTEGER PRIMARY KEY,
held_by TEXT NOT NULL,
lock_type TEXT NOT NULL CHECK (lock_type IN ('file', 'instance', 'folder')),
lock_target TEXT NOT NULL,
locked_at INTEGER NOT NULL,
UNIQUE(lock_type, lock_target)
);
`
createIndexesSQL := `
CREATE INDEX IF NOT EXISTS idx_locks_held_by ON locks(held_by);
CREATE INDEX IF NOT EXISTS idx_locks_type ON locks(lock_type);
CREATE INDEX IF NOT EXISTS idx_locks_target ON locks(lock_target);
`
if _, err := db.Exec(createTableSQL); err != nil {
return err
}
if _, err := db.Exec(createIndexesSQL); err != nil {
return err
}
// Insert the remote instance
hostAddress := "remote.example.com:0"
if hostPort != 0 {
hostAddress = "remote.example.com:" + strconv.Itoa(hostPort)
}
insertSQL := `INSERT INTO locks (held_by, lock_type, lock_target, locked_at) VALUES (?, 'instance', ?, ?)`
_, err = db.Exec(insertSQL, address, hostAddress, time.Now().Unix()*1000)
return err
}
// verifyInstanceExistsInSQLite checks if an instance exists in the SQLite database
func verifyInstanceExistsInSQLite(t *testing.T, dbPath, address string) bool {
t.Helper()
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
t.Logf("Failed to open database: %v", err)
return false
}
defer db.Close()
query := `SELECT COUNT(*) FROM locks WHERE held_by = ? AND lock_type = 'instance'`
var count int
err = db.QueryRow(query, address).Scan(&count)
if err != nil {
t.Logf("Failed to query database: %v", err)
return false
}
return count > 0
}
+178
View File
@@ -0,0 +1,178 @@
package e2e
import (
"context"
"fmt"
"syscall"
"testing"
)
// TestStartAndList verifies self-registration and default.json semantics in a fresh CLINE_DIR.
func TestStartAndList(t *testing.T) {
clineDir := setTempClineDir(t)
t.Logf("Using temp CLINE_DIR: %s", clineDir)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
t.Logf("Starting new instance...")
// Start a new instance
startOutput := mustRunCLI(ctx, t, "instance", "new")
t.Logf("Instance start output: %s", startOutput)
t.Logf("Listing instances to check registration...")
// It should appear healthy in list JSON and be the default.
out := listInstancesJSON(ctx, t)
t.Logf("Found %d instances after start", len(out.CoreInstances))
if len(out.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
t.Logf("Instance address: %s, status: %s", addr, out.CoreInstances[0].Status)
t.Logf("Waiting for address %s to become healthy...", addr)
waitForAddressHealthy(t, addr, defaultTimeout)
t.Logf("Address %s is now healthy", addr)
t.Logf("Checking default instance configuration...")
// Default should be set to the new instance.
out = listInstancesJSON(ctx, t)
t.Logf("Default instance: %s", out.DefaultInstance)
if out.DefaultInstance == "" {
t.Fatalf("default_instance not set")
}
if out.DefaultInstance != out.CoreInstances[0].Address {
t.Fatalf("expected default_instance=%s, got %s", out.CoreInstances[0].Address, out.DefaultInstance)
}
t.Logf("TestStartAndList completed successfully")
}
// TestTaskNewDefault ensures tasks route to default instance.
func TestTaskNewDefault(t *testing.T) {
_ = setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Start one instance and wait for healthy
_ = mustRunCLI(ctx, t, "instance", "new")
out := listInstancesJSON(ctx, t)
if len(out.CoreInstances) != 1 {
t.Fatalf("expected 1 instance, got %d", len(out.CoreInstances))
}
addr := out.CoreInstances[0].Address
waitForAddressHealthy(t, addr, defaultTimeout)
// Create a new task at default (success is sufficient)
_ = mustRunCLI(ctx, t, "task", "new", "hello world")
}
// TestExplicitAddressAutoStart verifies that giving an explicit address auto-starts an instance and routes the task.
func TestExplicitAddressAutoStart(t *testing.T) {
_ = setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Find a free port and use explicit address. This should auto-start an instance.
port := findFreePort(t)
addr := "localhost:" + itoa(port)
// Run a task at explicit address (auto-start path)
_ = mustRunCLI(ctx, t, "task", "new", "--address", "localhost:"+itoa(port), "explicit address task")
// Verify the instance is present and healthy
waitForAddressHealthy(t, addr, defaultTimeout)
}
// TestCrashCleanup verifies that after SIGKILL of a local core, the cleanup removes the registry entry.
// Also tests graceful shutdown (SIGTERM) vs crash cleanup and ensures no dangling host processes.
func TestCrashCleanup(t *testing.T) {
_ = setTempClineDir(t)
ctx, cancel := context.WithTimeout(context.Background(), longTimeout)
defer cancel()
// Start two instances for testing both graceful and crash scenarios
_ = mustRunCLI(ctx, t, "instance", "new")
_ = mustRunCLI(ctx, t, "instance", "new")
out := listInstancesJSON(ctx, t)
if len(out.CoreInstances) < 2 {
t.Fatalf("expected at least 2 instances, got %d", len(out.CoreInstances))
}
// Test 1: Graceful shutdown (SIGTERM) - should clean up both processes
gracefulTarget := out.CoreInstances[0]
waitForAddressHealthy(t, gracefulTarget.Address, defaultTimeout)
// Get PID using runtime discovery
gracefulPID := getCorePID(t, gracefulTarget.Address)
if gracefulPID <= 0 {
t.Fatalf("could not find PID for graceful target at %s", gracefulTarget.Address)
}
t.Logf("Testing graceful shutdown (SIGTERM) for instance %s (PID %d)", gracefulTarget.Address, gracefulPID)
if err := syscall.Kill(gracefulPID, syscall.SIGTERM); err != nil {
t.Fatalf("kill SIGTERM pid %d: %v", gracefulPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, gracefulTarget.Address, longTimeout)
// Verify both core and host ports are freed (no dangling processes)
waitForPortsClosed(t, gracefulTarget.CorePort(), gracefulTarget.HostPort(), defaultTimeout)
// Verify the instance is removed from SQLite (no file to check anymore)
// The waitForAddressRemoved already confirms the instance is gone from the registry
// Test 2: Crash cleanup (SIGKILL) - creates dangling host process that we must clean up
crashTarget := out.CoreInstances[1]
waitForAddressHealthy(t, crashTarget.Address, defaultTimeout)
// Get PID using runtime discovery
crashPID := getCorePID(t, crashTarget.Address)
if crashPID <= 0 {
t.Fatalf("could not find PID for crash target at %s", crashTarget.Address)
}
t.Logf("Testing crash cleanup (SIGKILL) for instance %s (PID %d)", crashTarget.Address, crashPID)
if err := syscall.Kill(crashPID, syscall.SIGKILL); err != nil {
t.Fatalf("kill SIGKILL pid %d: %v", crashPID, err)
}
// Wait for registry cleanup
waitForAddressRemoved(t, crashTarget.Address, longTimeout)
// Verify the instance is removed from SQLite (no file to check anymore)
// The waitForAddressRemoved already confirms the instance is gone from the registry
// Clean up dangling host process (SIGKILL leaves these behind by design)
t.Logf("Cleaning up dangling host process %s", crashTarget.HostServiceAddress)
findAndKillHostProcess(t, crashTarget.HostPort())
// Verify both ports are now free
waitForPortsClosed(t, crashTarget.CorePort(), crashTarget.HostPort(), defaultTimeout)
}
// itoa is a small helper for readability
func itoa(i int) string {
return strconvItoa(i)
}
// minimal inline int->string to avoid extra imports in helpers
func strconvItoa(i int) string {
// simple fast path
return fmtInt(i)
}
func fmtInt(i int) string {
// allocate small buffer; ints here are short
return (func(n int) string {
return fmt.Sprintf("%d", n)
})(i)
}
-273
View File
@@ -1,273 +0,0 @@
import fs from "node:fs"
import path from "node:path"
import { fileURLToPath } from "node:url"
import dotenv from "dotenv"
import * as esbuild from "esbuild"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const rootDir = path.resolve(__dirname, "..")
// Load .env from repo root
dotenv.config({ path: path.join(rootDir, ".env") })
const production = process.argv.includes("--production")
const watch = process.argv.includes("--watch")
/**
* Plugin to resolve path aliases from the parent project
*/
const aliasResolverPlugin: esbuild.Plugin = {
name: "alias-resolver",
setup(build) {
const aliases = {
"@": path.resolve(rootDir, "src"),
"@core": path.resolve(rootDir, "src/core"),
"@integrations": path.resolve(rootDir, "src/integrations"),
"@services": path.resolve(rootDir, "src/services"),
"@shared": path.resolve(rootDir, "src/shared"),
"@utils": path.resolve(rootDir, "src/utils"),
"@packages": path.resolve(rootDir, "src/packages"),
"@hosts": path.resolve(rootDir, "src/hosts"),
"@generated": path.resolve(rootDir, "src/generated"),
"@api": path.resolve(rootDir, "src/core/api"),
}
// For each alias entry, create a resolver
Object.entries(aliases).forEach(([alias, aliasPath]) => {
const aliasRegex = new RegExp(`^${alias}($|/.*)`)
build.onResolve({ filter: aliasRegex }, (args) => {
const importPath = args.path.replace(alias, aliasPath)
// First, check if the path exists as is
if (fs.existsSync(importPath)) {
const stats = fs.statSync(importPath)
if (stats.isDirectory()) {
// If it's a directory, try to find index files
const extensions = [".ts", ".tsx", ".js", ".jsx"]
for (const ext of extensions) {
const indexFile = path.join(importPath, `index${ext}`)
if (fs.existsSync(indexFile)) {
return { path: indexFile }
}
}
} else {
// It's a file that exists, so return it
return { path: importPath }
}
}
// If the path doesn't exist, try appending extensions
const extensions = [".ts", ".tsx", ".js", ".jsx"]
for (const ext of extensions) {
const pathWithExtension = `${importPath}${ext}`
if (fs.existsSync(pathWithExtension)) {
return { path: pathWithExtension }
}
}
// Handle .js -> .ts extension mapping (common in ESM TypeScript projects)
if (importPath.endsWith(".js")) {
const tsPath = importPath.replace(/\.js$/, ".ts")
if (fs.existsSync(tsPath)) {
return { path: tsPath }
}
const tsxPath = importPath.replace(/\.js$/, ".tsx")
if (fs.existsSync(tsxPath)) {
return { path: tsxPath }
}
}
// If nothing worked, return the original path and let esbuild handle the error
return { path: importPath }
})
})
},
}
/**
* Plugin to redirect vscode imports to our shim
*/
const vscodeStubPlugin: esbuild.Plugin = {
name: "vscode-stub",
setup(build) {
// Redirect 'vscode' imports to our shim
build.onResolve({ filter: /^vscode$/ }, () => {
return { path: path.join(__dirname, "src", "vscode-shim.ts") }
})
},
}
const esbuildProblemMatcherPlugin: esbuild.Plugin = {
name: "esbuild-problem-matcher",
setup(build) {
build.onStart(() => {
console.log("[cli esbuild] Build started...")
})
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`)
if (location) {
console.error(` ${location.file}:${location.line}:${location.column}:`)
}
})
console.log("[cli esbuild] Build finished")
})
},
}
// Plugin to stub out optional devtools module
const stubOptionalModulesPlugin: esbuild.Plugin = {
name: "stub-optional-modules",
setup(build) {
build.onResolve({ filter: /^react-devtools-core$/ }, () => {
return { path: path.join(__dirname, "src", "stub-devtools.js"), external: false }
})
},
}
const copyWasmFiles: esbuild.Plugin = {
name: "copy-wasm-files",
setup(build) {
build.onEnd(() => {
const destDir = path.join(__dirname, "dist")
// Ensure dist directory exists
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true })
}
// tree sitter
const sourceDir = path.join(rootDir, "node_modules", "web-tree-sitter")
// Copy tree-sitter.wasm
const treeSitterWasm = path.join(sourceDir, "tree-sitter.wasm")
if (fs.existsSync(treeSitterWasm)) {
fs.copyFileSync(treeSitterWasm, path.join(destDir, "tree-sitter.wasm"))
}
// Copy language-specific WASM files
const languageWasmDir = path.join(rootDir, "node_modules", "tree-sitter-wasms", "out")
const languages = [
"typescript",
"tsx",
"python",
"rust",
"javascript",
"go",
"cpp",
"c",
"c_sharp",
"ruby",
"java",
"php",
"swift",
"kotlin",
]
if (fs.existsSync(languageWasmDir)) {
languages.forEach((lang) => {
const filename = `tree-sitter-${lang}.wasm`
const sourcePath = path.join(languageWasmDir, filename)
if (fs.existsSync(sourcePath)) {
fs.copyFileSync(sourcePath, path.join(destDir, filename))
}
})
}
})
},
}
const buildEnvVars: Record<string, string> = {
"process.env.IS_STANDALONE": JSON.stringify("true"),
"process.env.IS_CLI": JSON.stringify("true"),
}
const buildTimeEnvs = [
"TELEMETRY_SERVICE_API_KEY",
"ERROR_SERVICE_API_KEY",
"POSTHOG_TELEMETRY_ENABLED",
"OTEL_TELEMETRY_ENABLED",
"OTEL_LOGS_EXPORTER",
"OTEL_METRICS_EXPORTER",
"OTEL_EXPORTER_OTLP_PROTOCOL",
"OTEL_EXPORTER_OTLP_ENDPOINT",
"OTEL_EXPORTER_OTLP_HEADERS",
"OTEL_METRIC_EXPORT_INTERVAL",
"CLINE_ENVIRONMENT",
]
buildTimeEnvs.forEach((envVar) => {
if (process.env[envVar]) {
console.log(`[cli esbuild] ${envVar} env var is set`)
buildEnvVars[`process.env.${envVar}`] = JSON.stringify(process.env[envVar])
}
})
if (production) {
buildEnvVars["process.env.IS_DEV"] = "false"
}
const config: esbuild.BuildOptions = {
entryPoints: [path.join(__dirname, "src", "index.ts")],
bundle: true,
minify: production,
sourcemap: !production,
logLevel: "silent",
define: buildEnvVars,
tsconfig: path.join(__dirname, "tsconfig.json"),
plugins: [copyWasmFiles, aliasResolverPlugin, vscodeStubPlugin, stubOptionalModulesPlugin, esbuildProblemMatcherPlugin],
format: "esm",
sourcesContent: false,
platform: "node",
target: "node20",
outfile: path.join(__dirname, "dist", "cli.mjs"),
// These modules need to load files from the module directory at runtime
external: [
"@grpc/reflection",
"grpc-health-check",
"better-sqlite3",
"ink",
"ink-spinner",
"ink-picture",
"react",
"aws4fetch",
"pino",
"pino-roll",
"@vscode/ripgrep", // Uses __dirname to locate the binary
],
supported: { "top-level-await": true },
banner: {
js: `#!/usr/bin/env node
// Suppress all Node.js warnings (deprecation, experimental, etc.)
process.emitWarning = () => {};
import { createRequire as _createRequire } from 'module';
import { fileURLToPath as _fileURLToPath } from 'url';
import { dirname as _dirname } from 'path';
const require = _createRequire(import.meta.url);
const __filename = _fileURLToPath(import.meta.url);
const __dirname = _dirname(__filename);`,
},
}
async function main() {
const ctx = await esbuild.context(config)
if (watch) {
await ctx.watch()
console.log("[cli] Watching for changes...")
} else {
await ctx.rebuild()
await ctx.dispose()
// Make the output executable
const outfile = path.join(__dirname, "dist", "cli.mjs")
if (fs.existsSync(outfile)) {
fs.chmodSync(outfile, "755")
}
}
}
main().catch((e) => {
console.error(e)
process.exit(1)
})
+58
View File
@@ -0,0 +1,58 @@
module github.com/cline/cli
go 1.23.0
require (
github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532
github.com/cline/grpc-go v0.0.0
github.com/mattn/go-sqlite3 v1.14.24
github.com/spf13/cobra v1.8.0
golang.org/x/term v0.32.0
google.golang.org/grpc v1.75.0
google.golang.org/protobuf v1.36.6
)
replace github.com/cline/grpc-go => ../src/generated/grpc-go
require (
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.6 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/goldmark v1.7.8 // indirect
github.com/yuin/goldmark-emoji v1.0.5 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
)
+150
View File
@@ -0,0 +1,150 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532 h1:+xmbw70JXxmsOqvm1PEIAqFnqI/Hy2RYqrK7CtPmsNY=
github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-412
View File
@@ -1,412 +0,0 @@
.\" Automatically generated by Pandoc 3.8.3
.\"
.TH "CLINE" "1" "January 2026" "Cline CLI 2.0" "User Commands"
.SH NAME
cline \- AI coding assistant in your terminal
.SH SYNOPSIS
\f[B]cline\f[R] [\f[I]prompt\f[R]] [\f[I]options\f[R]]
.PP
\f[B]cline\f[R] \f[I]command\f[R] [\f[I]options\f[R]]
[\f[I]arguments\f[R]]
.SH DESCRIPTION
\f[B]cline\f[R] is a command\-line interface for the Cline AI coding
assistant.
It provides the same powerful AI capabilities as the VS Code extension,
directly in your terminal.
.PP
Cline is an autonomous AI agent that can read, write, and execute code
across your projects.
He can create and edit files, run terminal commands, use a headless
browser, and more\(emall while asking for your approval before taking
actions.
.PP
The CLI supports both interactive mode (with a rich terminal UI) and
plain text mode (for piped input and scripted workflows).
.SH MODES OF OPERATION
\f[B]Interactive Mode\f[R] : When you run \f[B]cline\f[R] without
arguments, it launches an interactive welcome prompt with a rich
terminal UI.
You can type your task, view conversation history, and interact with
Cline in real\-time.
.PP
\f[B]Task Mode\f[R] : Run \f[B]cline \(lqprompt\(rq\f[R] or \f[B]cline
task \(lqprompt\(rq\f[R] to immediately start a task.
If stdin is a TTY, you\(cqll see the interactive UI.
If stdin is piped or output is redirected, the CLI automatically
switches to plain text mode.
.PP
\f[B]Plain Text Mode\f[R] : Activated automatically when stdin is piped,
output is redirected, or \f[B]\-\-json\f[R]/\f[B]\-\-yolo\f[R] flags are
used.
Outputs clean text without the Ink UI, suitable for scripting and CI/CD
pipelines.
.SH AGENT BEHAVIOR
Cline operates in two primary modes:
.PP
\f[B]ACT MODE\f[R] : Cline actively uses tools to accomplish tasks.
He can read files, write code, execute commands, use a headless browser,
and more.
This is the default mode for task execution.
.PP
\f[B]PLAN MODE\f[R] : Cline gathers information and creates a detailed
plan before implementation.
He explores the codebase, asks clarifying questions, and presents a
strategy for user approval before switching to ACT MODE.
.SH COMMANDS
.SS task (alias: t)
Run a new task with a prompt.
.PP
\f[B]cline task\f[R] \f[I]prompt\f[R] [\f[I]options\f[R]]
.PP
\f[B]cline t\f[R] \f[I]prompt\f[R] [\f[I]options\f[R]] : Create and run
a new task.
Options:
.PP
\f[B]\-a\f[R], \f[B]\-\-act\f[R] : Run in act mode (default)
.PP
\f[B]\-p\f[R], \f[B]\-\-plan\f[R] : Run in plan mode
.PP
\f[B]\-y\f[R], \f[B]\-\-yolo\f[R] : Enable yolo/yes mode (auto\-approve
all actions, output in plain mode, exit process automatically when task
complete)
.PP
\f[B]\-m\f[R], \f[B]\-\-model\f[R] \f[I]model\f[R] : Model to use for
the task
.PP
\f[B]\-i\f[R], \f[B]\-\-images\f[R] \f[I]paths\&...\f[R] : Image file
paths to include with the task
.PP
\f[B]\-v\f[R], \f[B]\-\-verbose\f[R] : Show verbose output including
reasoning
.PP
\f[B]\-c\f[R], \f[B]\-\-cwd\f[R] \f[I]path\f[R] : Working directory for
the task
.PP
\f[B]\-\-config\f[R] \f[I]path\f[R] : Path to Cline configuration
directory
.PP
\f[B]\-\-thinking\f[R] : Enable extended thinking (1024 token budget)
.PP
\f[B]\-\-json\f[R] : Output messages as JSON instead of styled text
.PP
\f[B]\-T\f[R], \f[B]\-\-taskId\f[R] \f[I]id\f[R] : Resume an existing
task by ID.
The prompt argument becomes an optional follow\-up message.
.SS history (alias: h)
List task history with pagination.
.PP
\f[B]cline history\f[R] [\f[I]options\f[R]]
.PP
\f[B]cline h\f[R] [\f[I]options\f[R]] : Display previous tasks.
Options:
.PP
\f[B]\-n\f[R], \f[B]\-\-limit\f[R] \f[I]number\f[R] : Number of tasks to
show (default: 10)
.PP
\f[B]\-p\f[R], \f[B]\-\-page\f[R] \f[I]number\f[R] : Page number,
1\-based (default: 1)
.PP
\f[B]\-\-config\f[R] \f[I]path\f[R] : Path to Cline configuration
directory
.SS config
Show current configuration.
.PP
\f[B]cline config\f[R] [\f[I]options\f[R]] : Display global and
workspace state.
Options:
.PP
\f[B]\-\-config\f[R] \f[I]path\f[R] : Path to Cline configuration
directory
.SS auth
Authenticate a provider and configure the model.
.PP
\f[B]cline auth\f[R] [\f[I]options\f[R]] : Launch interactive
authentication wizard, or use quick setup flags.
Options:
.PP
\f[B]\-p\f[R], \f[B]\-\-provider\f[R] \f[I]id\f[R] : Provider ID for
quick setup (e.g., openai\-native, anthropic, openrouter, moonshot)
.PP
\f[B]\-k\f[R], \f[B]\-\-apikey\f[R] \f[I]key\f[R] : API key for the
provider
.PP
\f[B]\-m\f[R], \f[B]\-\-modelid\f[R] \f[I]id\f[R] : Model ID to
configure (e.g., gpt\-4o, claude\-sonnet\-4\-5\-20250929, kimi\-k2.5)
.PP
\f[B]\-b\f[R], \f[B]\-\-baseurl\f[R] \f[I]url\f[R] : Base URL (optional,
for OpenAI\-compatible providers)
.PP
\f[B]\-v\f[R], \f[B]\-\-verbose\f[R] : Show verbose output
.PP
\f[B]\-c\f[R], \f[B]\-\-cwd\f[R] \f[I]path\f[R] : Working directory
.PP
\f[B]\-\-config\f[R] \f[I]path\f[R] : Path to Cline configuration
directory
.SS update
Check for updates and install if available.
.PP
\f[B]cline update\f[R] [\f[I]options\f[R]] : Check npm for newer
versions.
Options:
.PP
\f[B]\-v\f[R], \f[B]\-\-verbose\f[R] : Show verbose output
.SS version
Show the CLI version number.
.PP
\f[B]cline version\f[R]
.SS dev
Developer tools and utilities.
.PP
\f[B]cline dev log\f[R] : Open the log file for debugging.
.SH DEFAULT COMMAND OPTIONS
When running \f[B]cline\f[R] with just a prompt (no subcommand), these
options are available:
.PP
\f[B]\-a\f[R], \f[B]\-\-act\f[R] : Run in act mode (default)
.PP
\f[B]\-p\f[R], \f[B]\-\-plan\f[R] : Run in plan mode
.PP
\f[B]\-y\f[R], \f[B]\-\-yolo\f[R] : Enable yolo mode (auto\-approve all
actions).
Also forces plain text output mode.
.PP
\f[B]\-m\f[R], \f[B]\-\-model\f[R] \f[I]model\f[R] : Model to use for
the task
.PP
\f[B]\-v\f[R], \f[B]\-\-verbose\f[R] : Show verbose output
.PP
\f[B]\-c\f[R], \f[B]\-\-cwd\f[R] \f[I]path\f[R] : Working directory
.PP
\f[B]\-\-config\f[R] \f[I]path\f[R] : Configuration directory
.PP
\f[B]\-\-thinking\f[R] : Enable extended thinking (1024 token budget)
.PP
\f[B]\-\-json\f[R] : Output messages as JSON instead of styled text.
Forces plain text mode.
.PP
\f[B]\-T\f[R], \f[B]\-\-taskId\f[R] \f[I]id\f[R] : Resume an existing
task by ID instead of starting a new one.
The prompt becomes an optional follow\-up message.
.SH JSON OUTPUT FORMAT
When using \f[B]\-\-json\f[R], each message is output as a JSON object
with these fields:
.PP
\f[B]Required fields:\f[R]
.IP \(bu 2
\f[B]type\f[R]: \(lqask\(rq or \(lqsay\(rq
.IP \(bu 2
\f[B]text\f[R]: message text
.IP \(bu 2
\f[B]ts\f[R]: Unix epoch timestamp in milliseconds
.PP
\f[B]Optional fields:\f[R]
.IP \(bu 2
\f[B]reasoning\f[R]: reasoning text
.IP \(bu 2
\f[B]say\f[R]: say subtype (when type is \(lqsay\(rq)
.IP \(bu 2
\f[B]ask\f[R]: ask subtype (when type is \(lqask\(rq)
.IP \(bu 2
\f[B]partial\f[R]: streaming flag
.IP \(bu 2
\f[B]images\f[R]: list of image URIs
.IP \(bu 2
\f[B]files\f[R]: list of file paths
.SH EXAMPLES
.SS Basic Usage
.IP
.EX
\f[I]# Launch interactive mode\f[R]
cline
\f[I]# Run a task directly\f[R]
cline \(dqCreate a hello world function in Python\(dq
\f[I]# Run with verbose output and extended thinking\f[R]
cline \-v \-\-thinking \(dqAnalyze this codebase architecture\(dq
.EE
.SS Mode Selection
.IP
.EX
\f[I]# Run in plan mode (gather info before acting)\f[R]
cline \-p \(dqDesign a REST API for user management\(dq
\f[I]# Run in act mode with auto\-approval (yolo)\f[R]
cline \-y \(dqFix the typo in README.md\(dq
.EE
.SS Using Specific Models
.IP
.EX
\f[I]# Use a specific model\f[R]
cline \-m claude\-sonnet\-4\-5\-20250929 \(dqRefactor this function\(dq
\f[I]# Quick auth setup with model\f[R]
cline auth \-p anthropic \-k sk\-ant\-xxxxx \-m claude\-sonnet\-4\-5\-20250929
\f[I]# Quick auth setup for Moonshot\f[R]
cline auth \-p moonshot \-k sk\-xxxxx \-m kimi\-k2.5
.EE
.SS Including Images
.IP
.EX
\f[I]# Include images with explicit flag\f[R]
cline task \-i screenshot.png diagram.jpg \(dqFix the UI based on these images\(dq
\f[I]# Or use inline image references in the prompt\f[R]
cline \(dqFix the layout shown in \(at./screenshot.png\(dq
.EE
.SS Piped Input
.IP
.EX
\f[I]# Pipe file contents to Cline\f[R]
cat README.md \f[B]|\f[R] cline \(dqSummarize this document\(dq
\f[I]# Pipe with additional prompt\f[R]
echo \(dqfunction add(a, b) { return a + b }\(dq \f[B]|\f[R] cline \(dqAdd TypeScript types to this\(dq
\f[I]# Combine piped input with a prompt\f[R]
git diff \f[B]|\f[R] cline \(dqReview these changes and suggest improvements\(dq
.EE
.SS Scripting and Automation
.IP
.EX
\f[I]# JSON output for parsing\f[R]
cline \-\-json \(dqWhat files are in this directory?\(dq \f[B]|\f[R] jq \(aq.text\(aq
\f[I]# Yolo mode for automated workflows (auto\-approves all actions), forces plain text output\f[R]
cline \-y \(dqRun the test suite and fix any failures\(dq
.EE
.SS Task History
.IP
.EX
\f[I]# List recent tasks\f[R]
cline history
\f[I]# Show more tasks with pagination\f[R]
cline history \-n 20 \-p 2
.EE
.SS Resuming Tasks
.IP
.EX
\f[I]# Resume a task by ID (get IDs from cline history)\f[R]
cline \-T abc123def
\f[I]# Resume a task with a follow\-up message\f[R]
cline \-T abc123def \(dqNow add unit tests for the changes\(dq
\f[I]# Resume in plan mode to review before continuing\f[R]
cline \-T abc123def \-p \(dqWhat\(aqs left to do?\(dq
\f[I]# Resume with yolo mode for automated continuation\f[R]
cline \-T abc123def \-y \(dqContinue with the implementation\(dq
.EE
.SS Authentication
.IP
.EX
\f[I]# Interactive authentication wizard\f[R]
cline auth
\f[I]# Quick setup for Anthropic\f[R]
cline auth \-p anthropic \-k sk\-ant\-api\-xxxxx
\f[I]# Quick setup for OpenAI\f[R]
cline auth \-p openai\-native \-k sk\-xxxxx \-m gpt\-4o
\f[I]# Quick setup for Moonshot\f[R]
cline auth \-p moonshot \-k sk\-xxxxx \-m kimi\-k2.5
\f[I]# OpenAI\-compatible provider with custom base URL\f[R]
cline auth \-p openai \-k your\-api\-key \-b https://api.example.com/v1
.EE
.SH ENVIRONMENT
\f[B]CLINE_DIR\f[R] : Override the default configuration directory.
When set, Cline stores all data in this directory instead of
\f[CR]\(ti/.cline/data/\f[R].
.PP
\f[B]CLINE_COMMAND_PERMISSIONS\f[R] : JSON configuration for restricting
which shell commands Cline can execute.
When set, commands are validated against allow/deny patternks before
execution.
When not set, all commands are allowed.
.PP
Format:
\f[CR]{\(dqallow\(dq: [\(dqpattern1\(dq, \(dqpattern2\(dq], \(dqdeny\(dq: [\(dqpattern3\(dq], \(dqallowRedirects\(dq: true}\f[R]
.PP
\f[B]Fields:\f[R]
.IP \(bu 2
\f[B]allow\f[R] (array of strings): Glob patterns for allowed commands.
If specified, only matching commands are permitted.
Uses \f[CR]*\f[R] to match any characters and \f[CR]?\f[R] to match a
single character.
Setting allow on anything will deny all others.
.IP \(bu 2
\f[B]deny\f[R] (array of strings): Glob patterns for denied commands.
Deny rules take precedence over allow rules.
.IP \(bu 2
\f[B]allowRedirects\f[R] (boolean): Whether to allow shell redirects
(\f[CR]>\f[R], \f[CR]>>\f[R], \f[CR]<\f[R], etc.).
Defaults to false.
.PP
\f[B]Rule evaluation:\f[R]
.IP "1." 3
Check for dangerous characters (backticks outside single quotes,
unquoted newlines)
.IP "2." 3
Parse command into segments split by operators (\f[CR]&&\f[R],
\f[CR]||\f[R], \f[CR]|\f[R], \f[CR];\f[R])
.IP "3." 3
If redirects detected and \f[CR]allowRedirects\f[R] is not true, command
is denied
.IP "4." 3
Each segment is validated against deny rules first, then allow rules
.IP "5." 3
Subshell contents (\f[CR]$(...)\f[R] and \f[CR](...)\f[R]) are
recursively validated
.IP "6." 3
All segments must pass for the command to be allowed
.PP
\f[B]Examples:\f[R]
.IP
.EX
\f[I]# Allow only npm and git commands.\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{\(dqallow\(dq: [\(dqnpm *\(dq, \(dqgit *\(dq]}\(aq
\f[I]# Allow development commands but deny dangerous ones. Deny not strictly required here since allow is set.\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{\(dqallow\(dq: [\(dqnpm *\(dq, \(dqgit *\(dq, \(dqnode *\(dq], \(dqdeny\(dq: [\(dqrm \-rf *\(dq, \(dqsudo *\(dq]}\(aq
\f[I]# Allow file operations with redirects\f[R]
export CLINE_COMMAND_PERMISSIONS=\(aq{\(dqallow\(dq: [\(dqcat *\(dq, \(dqecho *\(dq], \(dqallowRedirects\(dq: true}\(aq
.EE
.SH CONFIGURATION FILES
.IP
.EX
\(ti/.cline/
├── data/ # Default configuration directory
│ ├── globalState.json # Global settings and state
│ ├── secrets.json # API keys and secrets (stored securely)
│ ├── workspace/ # Workspace\-specific state
│ └── tasks/ # Task history and conversation data
└── log/ # Log files for debugging
.EE
.PP
View logs with \f[CR]cline dev log\f[R].
.SH BUGS
Report bugs at: \c
.UR https://github.com/cline/cline/issues
.UE \c
.PP
For real\-time help, join the Discord community at: \c
.UR https://discord.gg/cline
.UE \c
.SH SEE ALSO
Full documentation: \c
.UR https://docs.cline.bot
.UE \c
.PP
VS Code extension: \c
.UR https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev
.UE \c
.SH AUTHORS
Cline is developed by Cline Bot Inc.\ and the open source community.
.SH COPYRIGHT
Copyright © 2025 Cline Bot Inc.\ Licensed under the Apache License 2.0.
-364
View File
@@ -1,364 +0,0 @@
---
title: CLINE
section: 1
header: User Commands
footer: Cline CLI 2.0
date: January 2026
---
# NAME
cline - AI coding assistant in your terminal
# SYNOPSIS
**cline** [*prompt*] [*options*]
**cline** *command* [*options*] [*arguments*]
# DESCRIPTION
**cline** is a command-line interface for the Cline AI coding assistant. It provides the same powerful AI capabilities as the VS Code extension, directly in your terminal.
Cline is an autonomous AI agent that can read, write, and execute code across your projects. He can create and edit files, run terminal commands, use a headless browser, and more—all while asking for your approval before taking actions.
The CLI supports both interactive mode (with a rich terminal UI) and plain text mode (for piped input and scripted workflows).
# MODES OF OPERATION
**Interactive Mode** : When you run **cline** without arguments, it launches an interactive welcome prompt with a rich terminal UI. You can type your task, view conversation history, and interact with Cline in real-time.
**Task Mode** : Run **cline "prompt"** or **cline task "prompt"** to immediately start a task. If stdin is a TTY, you'll see the interactive UI. If stdin is piped or output is redirected, the CLI automatically switches to plain text mode.
**Plain Text Mode** : Activated automatically when stdin is piped, output is redirected, or **\--json**/**\--yolo** flags are used. Outputs clean text without the Ink UI, suitable for scripting and CI/CD pipelines.
# AGENT BEHAVIOR
Cline operates in two primary modes:
**ACT MODE** : Cline actively uses tools to accomplish tasks. He can read files, write code, execute commands, use a headless browser, and more. This is the default mode for task execution.
**PLAN MODE** : Cline gathers information and creates a detailed plan before implementation. He explores the codebase, asks clarifying questions, and presents a strategy for user approval before switching to ACT MODE.
# COMMANDS
## task (alias: t)
Run a new task with a prompt.
**cline task** *prompt* [*options*]
**cline t** *prompt* [*options*] : Create and run a new task. Options:
**-a**, **\--act** : Run in act mode (default)
**-p**, **\--plan** : Run in plan mode
**-y**, **\--yolo** : Enable yolo/yes mode (auto-approve all actions, output in plain mode, exit process automatically when task complete)
**-t**, **\--timeout** *seconds* : Optional timeout in seconds. Only applied when explicitly provided.
**-m**, **\--model** *model* : Model to use for the task
**-i**, **\--images** *paths...* : Image file paths to include with the task
**-v**, **\--verbose** : Show verbose output including reasoning
**-c**, **\--cwd** *path* : Working directory for the task
**\--config** *path* : Path to Cline configuration directory
**\--thinking** : Enable extended thinking (1024 token budget)
**\--json** : Output messages as JSON instead of styled text
**-T**, **\--taskId** *id* : Resume an existing task by ID. The prompt argument becomes an optional follow-up message.
## history (alias: h)
List task history with pagination.
**cline history** [*options*]
**cline h** [*options*] : Display previous tasks. Options:
**-n**, **\--limit** *number* : Number of tasks to show (default: 10)
**-p**, **\--page** *number* : Page number, 1-based (default: 1)
**\--config** *path* : Path to Cline configuration directory
## config
Show current configuration.
**cline config** [*options*] : Display global and workspace state. Options:
**\--config** *path* : Path to Cline configuration directory
## auth
Authenticate a provider and configure the model.
**cline auth** [*options*] : Launch interactive authentication wizard, or use quick setup flags. Options:
**-p**, **\--provider** *id* : Provider ID for quick setup (e.g., openai-native, anthropic, openrouter)
**-k**, **\--apikey** *key* : API key for the provider
**-m**, **\--modelid** *id* : Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929)
**-b**, **\--baseurl** *url* : Base URL (optional, for OpenAI-compatible providers)
**-v**, **\--verbose** : Show verbose output
**-c**, **\--cwd** *path* : Working directory
**\--config** *path* : Path to Cline configuration directory
## update
Check for updates and install if available.
**cline update** [*options*] : Check npm for newer versions. Options:
**-v**, **\--verbose** : Show verbose output
## version
Show the CLI version number.
**cline version**
## dev
Developer tools and utilities.
**cline dev log** : Open the log file for debugging.
# DEFAULT COMMAND OPTIONS
When running **cline** with just a prompt (no subcommand), these options are available:
**-a**, **\--act** : Run in act mode (default)
**-p**, **\--plan** : Run in plan mode
**-y**, **\--yolo** : Enable yolo mode (auto-approve all actions). Also forces plain text output mode.
**-t**, **\--timeout** *seconds* : Optional timeout in seconds. Only applied when explicitly provided.
**-m**, **\--model** *model* : Model to use for the task
**-v**, **\--verbose** : Show verbose output
**-c**, **\--cwd** *path* : Working directory
**\--config** *path* : Configuration directory
**\--thinking** : Enable extended thinking (1024 token budget)
**\--json** : Output messages as JSON instead of styled text. Forces plain text mode.
**-T**, **\--taskId** *id* : Resume an existing task by ID instead of starting a new one. The prompt becomes an optional follow-up message.
# JSON OUTPUT FORMAT
When using **\--json**, each message is output as a JSON object with these fields:
**Required fields:**
- **type**: "ask" or "say"
- **text**: message text
- **ts**: Unix epoch timestamp in milliseconds
**Optional fields:**
- **reasoning**: reasoning text
- **say**: say subtype (when type is "say")
- **ask**: ask subtype (when type is "ask")
- **partial**: streaming flag
- **images**: list of image URIs
- **files**: list of file paths
# EXAMPLES
## Basic Usage
```bash
# Launch interactive mode
cline
# Run a task directly
cline "Create a hello world function in Python"
# Run with verbose output and extended thinking
cline -v --thinking "Analyze this codebase architecture"
```
## Mode Selection
```bash
# Run in plan mode (gather info before acting)
cline -p "Design a REST API for user management"
# Run in act mode with auto-approval (yolo)
cline -y "Fix the typo in README.md"
```
## Using Specific Models
```bash
# Use a specific model
cline -m claude-sonnet-4-5-20250929 "Refactor this function"
# Quick auth setup with model
cline auth -p anthropic -k sk-ant-xxxxx -m claude-sonnet-4-5-20250929
```
## Including Images
```bash
# Include images with explicit flag
cline task -i screenshot.png diagram.jpg "Fix the UI based on these images"
# Or use inline image references in the prompt
cline "Fix the layout shown in @./screenshot.png"
```
## Piped Input
```bash
# Pipe file contents to Cline
cat README.md | cline "Summarize this document"
# Pipe with additional prompt
echo "function add(a, b) { return a + b }" | cline "Add TypeScript types to this"
# Combine piped input with a prompt
git diff | cline "Review these changes and suggest improvements"
```
## Scripting and Automation
```bash
# JSON output for parsing
cline --json "What files are in this directory?" | jq '.text'
# Yolo mode for automated workflows (auto-approves all actions), forces plain text output
cline -y "Run the test suite and fix any failures"
```
## Task History
```bash
# List recent tasks
cline history
# Show more tasks with pagination
cline history -n 20 -p 2
```
## Resuming Tasks
```bash
# Resume a task by ID (get IDs from cline history)
cline -T abc123def
# Resume a task with a follow-up message
cline -T abc123def "Now add unit tests for the changes"
# Resume in plan mode to review before continuing
cline -T abc123def -p "What's left to do?"
# Resume with yolo mode for automated continuation
cline -T abc123def -y "Continue with the implementation"
```
## Authentication
```bash
# Interactive authentication wizard
cline auth
# Quick setup for Anthropic
cline auth -p anthropic -k sk-ant-api-xxxxx
# Quick setup for OpenAI
cline auth -p openai-native -k sk-xxxxx -m gpt-4o
# OpenAI-compatible provider with custom base URL
cline auth -p openai -k your-api-key -b https://api.example.com/v1
```
# ENVIRONMENT
**CLINE_DIR** : Override the default configuration directory. When set, Cline stores all data in this directory instead of `~/.cline/data/`.
**CLINE_COMMAND_PERMISSIONS** : JSON configuration for restricting which shell commands Cline can execute. When set, commands are validated against allow/deny patternks before execution. When not set, all commands are allowed.
Format: `{"allow": ["pattern1", "pattern2"], "deny": ["pattern3"], "allowRedirects": true}`
**Fields:**
- **allow** (array of strings): Glob patterns for allowed commands. If specified, only matching commands are permitted. Uses `*` to match any characters and `?` to match a single character. Setting allow on anything will deny all others.
- **deny** (array of strings): Glob patterns for denied commands. Deny rules take precedence over allow rules.
- **allowRedirects** (boolean): Whether to allow shell redirects (`>`, `>>`, `<`, etc.). Defaults to false.
**Rule evaluation:**
1. Check for dangerous characters (backticks outside single quotes, unquoted newlines)
2. Parse command into segments split by operators (`&&`, `||`, `|`, `;`)
3. If redirects 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
**Examples:**
```bash
# Allow only npm and git commands.
export CLINE_COMMAND_PERMISSIONS='{"allow": ["npm *", "git *"]}'
# Allow development commands but deny dangerous ones. Deny not strictly required here since allow is set.
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}'
```
# CONFIGURATION FILES
```
~/.cline/
├── data/ # Default configuration directory
│ ├── globalState.json # Global settings and state
│ ├── secrets.json # API keys and secrets (stored securely)
│ ├── workspace/ # Workspace-specific state
│ └── tasks/ # Task history and conversation data
└── log/ # Log files for debugging
```
View logs with `cline dev log`.
# BUGS
Report bugs at: <https://github.com/cline/cline/issues>
For real-time help, join the Discord community at: <https://discord.gg/cline>
# SEE ALSO
Full documentation: <https://docs.cline.bot>
VS Code extension: <https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev>
# AUTHORS
Cline is developed by Cline Bot Inc. and the open source community.
# COPYRIGHT
Copyright © 2025 Cline Bot Inc. Licensed under the Apache License 2.0.
-91
View File
@@ -1,91 +0,0 @@
{
"name": "cline",
"version": "2.4.2",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "dist/cli.mjs",
"bin": {
"cline": "./dist/cli.mjs"
},
"os": [
"darwin",
"linux",
"win32"
],
"cpu": [
"x64",
"arm64"
],
"man": "./man/cline.1",
"type": "module",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"package:brew": "npx tsx ./scripts/update-brew-formula.mts",
"package": "npm pack --pack-destination ./dist",
"build": "npm run typecheck && npx tsx esbuild.mts",
"build:production": "npm run typecheck && npx tsx esbuild.mts --production",
"watch": "npx tsx esbuild.mts --watch",
"dev": "IS_DEV=true && npm run link && npm run watch ; npm run unlink",
"clean": "rimraf dist",
"typecheck": "npx tsc --noEmit",
"link": "npm run build && npm link",
"unlink": "npm unlink -g cline",
"test": "vitest",
"test:run": "vitest run"
},
"keywords": [
"cline",
"claude",
"dev",
"mcp",
"openrouter",
"coding",
"agent",
"autonomous",
"chatgpt",
"sonnet",
"ai",
"llama",
"cli"
],
"author": {
"name": "Cline Bot Inc."
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/cline/cline"
},
"homepage": "https://cline.bot",
"bugs": {
"url": "https://github.com/cline/cline/issues"
},
"devDependencies": {
"@types/node": "20.x",
"@types/prompts": "^2.4.9",
"@types/react": "^19.2.9",
"dotenv": "^16.4.5",
"esbuild": "^0.25.0",
"ink-testing-library": "^4.0.0",
"rimraf": "^6.0.1",
"typescript": "^5.4.5",
"vitest": "^4.0.17"
},
"dependencies": {
"@agentclientprotocol/sdk": "^0.13.1",
"@vscode/ripgrep": "^1.15.9",
"aws4fetch": "^1.0.20",
"chalk": "^5.3.0",
"commander": "^12.1.0",
"ink": "npm:@jrichman/ink@6.4.7",
"ink-picture": "^1.3.3",
"ink-spinner": "^5.0.0",
"ora": "^8.0.1",
"nanoid": "^5.1.6",
"pino": "^10.0.0",
"pino-roll": "^4.0.0",
"prompts": "^2.4.2",
"react": "^19.2.3"
}
}
+17
View File
@@ -0,0 +1,17 @@
package cli
import (
"github.com/cline/cli/pkg/cli/auth"
"github.com/spf13/cobra"
)
func NewAuthCommand() *cobra.Command {
return &cobra.Command{
Use: "auth",
Short: "Sign in to Cline",
Long: `Complete the authentication flow in browser to sign in to Cline.`,
RunE: func(cmd *cobra.Command, args []string) error {
return auth.RunAuthFlow(cmd.Context(), args)
},
}
}
+282
View File
@@ -0,0 +1,282 @@
package auth
import (
"context"
"fmt"
"time"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
)
var isSessionAuthenticated bool
// Cline provider specific code
func HandleClineAuth(ctx context.Context) error {
verboseLog("Authenticating with Cline...")
// Check if already authenticated
if IsAuthenticated(ctx) {
return signOutDialog(ctx)
}
// Perform sign in
if err := signIn(ctx); err != nil {
return err
}
fmt.Println()
verboseLog("✓ You are signed in!")
// Configure default Cline model after successful authentication
if err := configureDefaultClineModel(ctx); err != nil {
fmt.Printf("Warning: Could not configure default Cline model: %v\n", err)
fmt.Println("You can configure a model later with 'cline auth' and selecting 'Change Cline model'")
}
// Return to main auth menu after successful authentication
return HandleAuthMenuNoArgs(ctx)
}
func signOut(ctx context.Context) error {
client, err := global.GetDefaultClient(ctx)
if err != nil {
return err
}
if _, err = client.Account.AccountLogoutClicked(ctx, &cline.EmptyRequest{}); err != nil {
return err
}
isSessionAuthenticated = false
fmt.Println("You have been signed out of Cline.")
return nil
}
func signOutDialog(ctx context.Context) error {
var confirm bool
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("You are already signed in to Cline.").
Description("Would you like to sign out?").
Value(&confirm),
),
)
if err := form.Run(); err != nil {
return nil
}
if confirm {
if err := signOut(ctx); err != nil {
fmt.Printf("Failed to sign out: %v\n", err)
return err
}
}
return HandleAuthMenuNoArgs(ctx)
}
func signIn(ctx context.Context) error {
if IsAuthenticated(ctx) {
return nil
}
// Subscribe to auth updates before initiating login
verboseLog("Subscribing to auth status updates...")
listener, err := NewAuthStatusListener(ctx)
if err != nil {
verboseLog("Failed to subscribe to auth updates: %v", err)
return fmt.Errorf("failed to subscribe to auth updates: %w", err)
}
defer listener.Stop()
if err := listener.Start(); err != nil {
verboseLog("Failed to start auth listener: %v", err)
return fmt.Errorf("failed to start auth listener: %w", err)
}
// Initiate login (opens browser with callback URL from cline-core's AuthHandler)
verboseLog("Initiating login...")
client, err := global.GetDefaultClient(ctx)
if err != nil {
verboseLog("Failed to obtain client: %v", err)
return fmt.Errorf("failed to obtain client: %w", err)
}
_, err = client.Account.AccountLoginClicked(ctx, &cline.EmptyRequest{})
if err != nil {
verboseLog("Failed to initiate login: %v", err)
return fmt.Errorf("failed to initiate login: %w", err)
}
fmt.Println("\n Opening browser for authentication...")
fmt.Println(" Waiting for you to complete authentication in your browser...")
fmt.Println(" (This may take a few moments. Timeout: 5 minutes)")
// Wait for auth status update confirming success
verboseLog("Waiting for authentication to complete...")
if err := listener.WaitForAuthentication(5 * time.Minute); err != nil {
verboseLog("Authentication failed or timed out: %v", err)
fmt.Println("\n Authentication failed or timed out.")
fmt.Println(" Please try again with 'cline auth'")
return err
}
// Only NOW set the session flag after confirmed authentication
isSessionAuthenticated = true
verboseLog("Login successful")
return nil
}
func IsAuthenticated(ctx context.Context) bool {
if isSessionAuthenticated {
verboseLog("Session is already authenticated")
return true
}
verboseLog("Verifying authentication with server...")
client, err := global.GetDefaultClient(ctx)
if err != nil {
verboseLog("Failed to get client for auth check: %v", err)
return false
}
_, err = client.Account.GetUserCredits(ctx, &cline.EmptyRequest{})
if err == nil {
// Update session variable for future fast-path checks
verboseLog("Server verification successful, updating session flag")
isSessionAuthenticated = true
return true
}
verboseLog("Server verification failed: %v", err)
return false
}
// HandleChangeClineModel allows Cline-authenticated users to change their Cline model selection. Hidden when not authenticated.
func HandleChangeClineModel(ctx context.Context) error {
// Ensure user is authenticated
if !IsAuthenticated(ctx) {
return fmt.Errorf("you must be authenticated with Cline to change models. Run 'cline auth' to sign in")
}
// Get task manager
manager, err := createTaskManager(ctx)
if err != nil {
return fmt.Errorf("failed to create task manager: %w", err)
}
// Launch Cline model selection
return SelectClineModel(ctx, manager)
}
// configureDefaultClineModel configures the default Cline model after authentication
func configureDefaultClineModel(ctx context.Context) error {
verboseLog("Configuring default Cline model...")
// Create task manager
manager, err := task.NewManagerForDefault(ctx)
if err != nil {
return fmt.Errorf("failed to create task manager: %w", err)
}
// Set default Cline model
return SetDefaultClineModel(ctx, manager)
}
// HandleSelectOrganization allows Cline-authenticated users to select which organization to use
func HandleSelectOrganization(ctx context.Context) error {
// Ensure user is authenticated
if !IsAuthenticated(ctx) {
return fmt.Errorf("you must be authenticated with Cline to select an organization. Run 'cline auth' to sign in")
}
// Get client
client, err := global.GetDefaultClient(ctx)
if err != nil {
return fmt.Errorf("failed to get client: %w", err)
}
// Fetch user organizations
orgsResponse, err := client.Account.GetUserOrganizations(ctx, &cline.EmptyRequest{})
if err != nil {
return fmt.Errorf("failed to fetch organizations: %w", err)
}
organizations := orgsResponse.GetOrganizations()
if len(organizations) == 0 {
fmt.Println("You don't have any organizations yet.")
fmt.Println("Visit https://app.cline.bot/dashboard to create an organization.")
return HandleAuthMenuNoArgs(ctx)
}
// Build options list: Personal + Organizations
var options []huh.Option[string]
options = append(options, huh.NewOption("Personal", "personal"))
for _, org := range organizations {
displayName := org.Name
// Show active indicator
if org.Active {
displayName = fmt.Sprintf("%s (active)", displayName)
}
options = append(options, huh.NewOption(displayName, org.OrganizationId))
}
options = append(options, huh.NewOption("(Cancel)", "cancel"))
// Show selection menu
var selected string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Select which account to use").
Options(options...).
Value(&selected),
),
)
if err := form.Run(); err != nil {
return fmt.Errorf("failed to select organization: %w", err)
}
if selected == "cancel" {
return HandleAuthMenuNoArgs(ctx)
}
// Set the organization
var orgId *string
if selected != "personal" {
orgId = &selected
}
req := &cline.UserOrganizationUpdateRequest{
OrganizationId: orgId,
}
if _, err := client.Account.SetUserOrganization(ctx, req); err != nil {
return fmt.Errorf("failed to set organization: %w", err)
}
if selected == "personal" {
fmt.Println("✓ Switched to personal account")
} else {
// Find the org name to display
var orgName string
for _, org := range organizations {
if org.OrganizationId == selected {
orgName = org.Name
break
}
}
fmt.Printf("✓ Switched to organization: %s\n", orgName)
}
return HandleAuthMenuNoArgs(ctx)
}
+305
View File
@@ -0,0 +1,305 @@
package auth
import (
"context"
"fmt"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
)
// contextKey is a distinct type for context keys to avoid collisions
type contextKey string
const authInstanceAddressKey contextKey = "authInstanceAddress"
// AuthAction represents the type of authentication action
type AuthAction string
const (
AuthActionClineLogin AuthAction = "cline_login"
AuthActionBYOSetup AuthAction = "provider_setup"
AuthActionChangeClineModel AuthAction = "change_cline_model"
AuthActionSelectOrganization AuthAction = "select_organization"
AuthActionSelectProvider AuthAction = "select_provider"
AuthActionExit AuthAction = "exit_wizard"
)
// Cline Auth Menu
// Example Layout
//
// ┃ Cline Account: <authenticated/not authenticated>
// ┃ Active Provider: <provider name or none configured>
// ┃ Active Model: <model name or none configured>
// ┃
// ┃ What would you like to do?
// ┃ Change Cline model (only if authenticated) - hidden if not authenticated
// ┃ Authenticate with Cline account / Sign out of Cline - changes based on auth status
// ┃ Select active provider (Cline or BYO) - always shown. Used to switch between Cline and BYO providers
// ┃ Configure API provider - always shown. Launches provider setup wizard
// ┃ Exit authorization wizard - always shown. Exits the auth menu
// RunAuthFlow is the entry point for the entire auth flow with instance management
// It spawns a fresh instance for auth operations and cleans it up when done
func RunAuthFlow(ctx context.Context, args []string) error {
// Spawn a fresh instance for auth operations
instanceInfo, err := global.Clients.StartNewInstance(ctx)
if err != nil {
return fmt.Errorf("failed to start auth instance: %w", err)
}
// Cleanup when done (success, error, or panic)
defer func() {
verboseLog("Shutting down auth instance at %s", instanceInfo.Address)
if err := global.KillInstanceByAddress(context.Background(), global.Clients.GetRegistry(), instanceInfo.Address); err != nil {
verboseLog("Warning: Failed to kill auth instance: %v", err)
}
}()
// Store instance address in context for all auth handlers to use
authCtx := context.WithValue(ctx, authInstanceAddressKey, instanceInfo.Address)
// Route to existing auth flow
return HandleAuthCommand(authCtx, args)
}
// Main entry point for handling the `cline auth` command
// HandleAuthCommand routes the auth command based on the number of arguments
func HandleAuthCommand(ctx context.Context, args []string) error {
switch len(args) {
case 0:
// No args: Show menu (ShowAuthMenuNoArgs)
return HandleAuthMenuNoArgs(ctx)
case 1:
// One arg: Provider ID only, prompt for API key
return QuickAPISetup(args[0], "")
case 2:
// Two args: Provider ID and API key
return QuickAPISetup(args[0], args[1])
default:
return fmt.Errorf("quick BYO API setup is currently stubbed - not yet implemented")
}
}
// getAuthInstanceAddress retrieves the auth instance address from context
// Returns empty string if not found (falls back to default behavior)
func getAuthInstanceAddress(ctx context.Context) string {
if addr, ok := ctx.Value(authInstanceAddressKey).(string); ok {
return addr
}
return ""
}
// HandleAuthMenuNoArgs prepares the auth menu when no arguments are provided
func HandleAuthMenuNoArgs(ctx context.Context) error {
// Check if Cline is authenticated
isClineAuth := IsAuthenticated(ctx)
// Get current provider config for display
var currentProvider string
var currentModel string
if manager, err := createTaskManager(ctx); err == nil {
if providerList, err := GetProviderConfigurations(ctx, manager); err == nil {
if providerList.ActProvider != nil {
currentProvider = getProviderDisplayName(providerList.ActProvider.Provider)
currentModel = providerList.ActProvider.ModelID
}
}
}
// Fetch organizations if authenticated
var hasOrganizations bool
if isClineAuth {
if client, err := global.GetDefaultClient(ctx); err == nil {
if orgsResponse, err := client.Account.GetUserOrganizations(ctx, &cline.EmptyRequest{}); err == nil {
hasOrganizations = len(orgsResponse.GetOrganizations()) > 0
}
}
}
action, err := ShowAuthMenuWithStatus(isClineAuth, hasOrganizations, currentProvider, currentModel)
if err != nil {
return err
}
switch action {
case AuthActionClineLogin:
return HandleClineAuth(ctx)
case AuthActionBYOSetup:
return HandleAPIProviderSetup(ctx)
case AuthActionChangeClineModel:
return HandleChangeClineModel(ctx)
case AuthActionSelectOrganization:
return HandleSelectOrganization(ctx)
case AuthActionSelectProvider:
return HandleSelectProvider(ctx)
case AuthActionExit:
return nil
default:
return fmt.Errorf("invalid action")
}
}
// ShowAuthMenuWithStatus displays the main auth menu with Cline + provider status
func ShowAuthMenuWithStatus(isClineAuthenticated bool, hasOrganizations bool, currentProvider, currentModel string) (AuthAction, error) {
var action AuthAction
var options []huh.Option[AuthAction]
// Build menu options based on authentication status
if isClineAuthenticated {
options = []huh.Option[AuthAction]{
huh.NewOption("Change Cline model", AuthActionChangeClineModel),
}
// Add organization selection if user has organizations
if hasOrganizations {
options = append(options, huh.NewOption("Select organization", AuthActionSelectOrganization))
}
options = append(options,
huh.NewOption("Sign out of Cline", AuthActionClineLogin),
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
huh.NewOption("Configure API provider", AuthActionBYOSetup),
huh.NewOption("Exit authorization wizard", AuthActionExit),
)
} else {
options = []huh.Option[AuthAction]{
huh.NewOption("Authenticate with Cline account", AuthActionClineLogin),
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
huh.NewOption("Configure API provider", AuthActionBYOSetup),
huh.NewOption("Exit authorization wizard", AuthActionExit),
}
}
// Determine menu title based on status
var title string
// Always show Cline authentication status
if isClineAuthenticated {
title = "Cline Account: \033[32m✓\033[0m Authenticated\n"
} else {
title = "Cline Account: \033[31m✗\033[0m Not authenticated\n"
}
// Show active provider and model if configured (regardless of Cline auth status)
// ANSI color codes: Normal intensity = \033[22m, White = \033[37m, Reset = \033[0m
if currentProvider != "" && currentModel != "" {
title += fmt.Sprintf("Active Provider: \033[22m\033[37m%s\033[0m\nActive Model: \033[22m\033[37m%s\033[0m\n", currentProvider, currentModel)
}
// Always end with a huh?
title += "\nWhat would you like to do?"
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[AuthAction]().
Title(title).
Options(options...).
Value(&action),
),
)
if err := form.Run(); err != nil {
return "", fmt.Errorf("failed to get menu choice: %w", err)
}
return action, nil
}
// HandleAPIProviderSetup launches the API provider configuration wizard
func HandleAPIProviderSetup(ctx context.Context) error {
wizard, err := NewProviderWizard(ctx)
if err != nil {
return fmt.Errorf("failed to create provider wizard: %w", err)
}
return wizard.Run()
}
// HandleSelectProvider allows users to switch between Cline provider and BYO providers
func HandleSelectProvider(ctx context.Context) error {
// Get task manager
manager, err := createTaskManager(ctx)
if err != nil {
return fmt.Errorf("failed to create task manager: %w", err)
}
// Detect all providers with valid configurations (is an API key present)
availableProviders, err := DetectAllConfiguredProviders(ctx, manager)
if err != nil {
return fmt.Errorf("failed to detect configured providers: %w", err)
}
// Build list of available providers
var providerOptions []huh.Option[string]
var providerMapping = make(map[string]cline.ApiProvider)
// Add each configured provider to the selection menu
for _, provider := range availableProviders {
providerName := getProviderDisplayName(provider)
providerKey := fmt.Sprintf("provider_%d", provider)
providerOptions = append(providerOptions, huh.NewOption(providerName, providerKey))
providerMapping[providerKey] = provider
}
if len(providerOptions) == 0 {
fmt.Println("No providers available. Please configure a provider first.")
return HandleAuthMenuNoArgs(ctx)
}
if len(providerOptions) == 1 {
fmt.Println("Only one provider is configured. Configure another provider to switch between them.")
return HandleAuthMenuNoArgs(ctx)
}
providerOptions = append(providerOptions, huh.NewOption("(Cancel)", "cancel"))
// Show selection menu
var selected string
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Select which provider to use").
Options(providerOptions...).
Value(&selected),
),
)
if err := form.Run(); err != nil {
return fmt.Errorf("failed to select provider: %w", err)
}
if selected == "cancel" {
return HandleAuthMenuNoArgs(ctx)
}
// Get the selected provider
selectedProvider := providerMapping[selected]
// Apply the selected provider
if selectedProvider == cline.ApiProvider_CLINE {
// Configure Cline as the active provider
return SelectClineModel(ctx, manager)
} else {
// Switch to the selected BYO provider
return SwitchToBYOProvider(ctx, manager, selectedProvider)
}
}
// createTaskManager is a helper to create a task manager (avoids import cycles)
// Uses the auth instance address from context if available, otherwise falls back to default
func createTaskManager(ctx context.Context) (*task.Manager, error) {
authAddr := getAuthInstanceAddress(ctx)
if authAddr != "" {
return task.NewManagerForAddress(ctx, authAddr)
}
return task.NewManagerForDefault(ctx)
}
func verboseLog(format string, args ...interface{}) {
if global.Config != nil && global.Config.Verbose {
fmt.Printf("[VERBOSE] "+format+"\n", args...)
}
}
+130
View File
@@ -0,0 +1,130 @@
package auth
import (
"context"
"fmt"
"io"
"time"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/grpc-go/cline"
)
// AuthStatusListener manages subscription to auth status updates
type AuthStatusListener struct {
stream cline.AccountService_SubscribeToAuthStatusUpdateClient
updatesCh chan *cline.AuthState
errCh chan error
ctx context.Context
cancel context.CancelFunc
}
// NewAuthStatusListener creates a new auth status listener
func NewAuthStatusListener(parentCtx context.Context) (*AuthStatusListener, error) {
client, err := global.GetDefaultClient(parentCtx)
if err != nil {
return nil, fmt.Errorf("failed to get client: %w", err)
}
// Create cancellable context
ctx, cancel := context.WithCancel(parentCtx)
// Subscribe to auth status updates
stream, err := client.Account.SubscribeToAuthStatusUpdate(ctx, &cline.EmptyRequest{})
if err != nil {
cancel()
return nil, fmt.Errorf("failed to subscribe to auth updates: %w", err)
}
return &AuthStatusListener{
stream: stream,
updatesCh: make(chan *cline.AuthState, 10),
errCh: make(chan error, 1),
ctx: ctx,
cancel: cancel,
}, nil
}
// Start begins listening to the auth status update stream
func (l *AuthStatusListener) Start() error {
verboseLog("Starting auth status listener...")
go l.readStream()
return nil
}
// readStream reads from the gRPC stream and forwards messages to channels
func (l *AuthStatusListener) readStream() {
defer close(l.updatesCh)
defer close(l.errCh)
for {
select {
case <-l.ctx.Done():
verboseLog("Auth listener context cancelled")
return
default:
state, err := l.stream.Recv()
if err != nil {
if err == io.EOF {
verboseLog("Auth status stream closed")
return
}
verboseLog("Error reading from auth status stream: %v", err)
select {
case l.errCh <- err:
case <-l.ctx.Done():
}
return
}
verboseLog("Received auth state update: user=%v", state.User != nil)
select {
case l.updatesCh <- state:
case <-l.ctx.Done():
return
}
}
}
}
// WaitForAuthentication blocks until authentication succeeds or timeout occurs
func (l *AuthStatusListener) WaitForAuthentication(timeout time.Duration) error {
verboseLog("Waiting for authentication (timeout: %v)...", timeout)
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case <-timer.C:
return fmt.Errorf("authentication timeout after %v - please try again", timeout)
case <-l.ctx.Done():
return fmt.Errorf("authentication cancelled")
case err := <-l.errCh:
return fmt.Errorf("authentication stream error: %w", err)
case state := <-l.updatesCh:
if isAuthenticated(state) {
verboseLog("Authentication successful!")
return nil
}
verboseLog("Received auth update but not authenticated yet...")
}
}
}
// Stop closes the stream and cleans up resources
func (l *AuthStatusListener) Stop() {
verboseLog("Stopping auth status listener...")
l.cancel()
}
// isAuthenticated checks if AuthState indicates successful authentication
func isAuthenticated(state *cline.AuthState) bool {
return state != nil && state.User != nil
}
+13
View File
@@ -0,0 +1,13 @@
package auth
import "fmt"
// QuickAPISetup performs quick provider setup with provider ID and optional API key
func QuickAPISetup(providerID, apiKey string) error {
fmt.Println("Quick BYO API setup is currently stubbed - not yet implemented.")
fmt.Printf("Requested provider: %s\n", providerID)
if apiKey != "" {
fmt.Println("Provided API key:", "<jk redacted>")
}
return nil
}
+1
View File
@@ -0,0 +1 @@
package auth
+141
View File
@@ -0,0 +1,141 @@
package auth
import (
"context"
"fmt"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
)
// DefaultClineModelID is the default model ID for Cline provider.
// Cline uses OpenRouter-compatible model IDs.
const DefaultClineModelID = "anthropic/claude-sonnet-4.5"
// FetchClineModels fetches available Cline models from Cline Core.
// Note: Cline provider uses OpenRouter-compatible API and model format.
// The models are fetched using the same method as OpenRouter.
func FetchClineModels(ctx context.Context, manager *task.Manager) (map[string]*cline.OpenRouterModelInfo, error) {
if global.Config.Verbose {
fmt.Println("Fetching Cline models (using OpenRouter-compatible API)")
}
// Cline uses OpenRouter model fetching
models, err := FetchOpenRouterModels(ctx, manager)
if err != nil {
return nil, fmt.Errorf("failed to fetch Cline models: %w", err)
}
return models, nil
}
// GetClineModelInfo retrieves information for a specific Cline model.
func GetClineModelInfo(modelID string, models map[string]*cline.OpenRouterModelInfo) (*cline.OpenRouterModelInfo, error) {
modelInfo, exists := models[modelID]
if !exists {
return nil, fmt.Errorf("model %s not found", modelID)
}
return modelInfo, nil
}
// SetDefaultClineModel configures the default Cline model after authentication.
// This is called automatically after successful Cline sign-in.
func SetDefaultClineModel(ctx context.Context, manager *task.Manager) error {
// Fetch available models
models, err := FetchClineModels(ctx, manager)
if err != nil {
// If we can't fetch models, we'll use the default without model info
fmt.Printf("Warning: Could not fetch Cline models: %v\n", err)
fmt.Printf("Using default model: %s\n", DefaultClineModelID)
return applyDefaultClineModel(ctx, manager, nil)
}
// Check if default model is available
modelInfo, err := GetClineModelInfo(DefaultClineModelID, models)
if err != nil {
fmt.Printf("Warning: Default model not found: %v\n", err)
// Try to use any available model
for modelID := range models {
fmt.Printf("Using available model: %s\n", modelID)
return applyClineModelConfiguration(ctx, manager, modelID, models[modelID])
}
return fmt.Errorf("no usable Cline models found")
}
if err := applyClineModelConfiguration(ctx, manager, DefaultClineModelID, modelInfo); err != nil {
return err
}
if err := setWelcomeViewCompletedWithManager(ctx, manager); err != nil {
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
}
return nil
}
// SelectClineModel presents a menu to select a Cline model and applies the configuration.
func SelectClineModel(ctx context.Context, manager *task.Manager) error {
// Fetch models (uses OpenRouter-compatible format)
models, err := FetchClineModels(ctx, manager)
if err != nil {
return fmt.Errorf("failed to fetch Cline models: %w", err)
}
// Convert to interface map for generic utilities
modelMap := ConvertOpenRouterModelsToInterface(models)
// Get model IDs as a sorted list
modelIDs := ConvertModelsMapToSlice(modelMap)
// Display selection menu
selectedModelID, err := DisplayModelSelectionMenu(modelIDs, "Cline")
if err != nil {
return fmt.Errorf("model selection failed: %w", err)
}
// Get the selected model info
modelInfo := models[selectedModelID]
// Apply the configuration
if err := applyClineModelConfiguration(ctx, manager, selectedModelID, modelInfo); err != nil {
return err
}
fmt.Println()
// Return to main auth menu after model selection
return HandleAuthMenuNoArgs(ctx)
}
// applyClineModelConfiguration applies a Cline model configuration to both Act and Plan modes using UpdateProviderPartial.
// Cline uses OpenRouter-compatible model format.
func applyClineModelConfiguration(ctx context.Context, manager *task.Manager, modelID string, modelInfo *cline.OpenRouterModelInfo) error {
provider := cline.ApiProvider_CLINE
updates := ProviderUpdatesPartial{
ModelID: &modelID,
ModelInfo: modelInfo,
}
return UpdateProviderPartial(ctx, manager, provider, updates, true)
}
func applyDefaultClineModel(ctx context.Context, manager *task.Manager, modelInfo *cline.OpenRouterModelInfo) error {
if err := applyClineModelConfiguration(ctx, manager, DefaultClineModelID, modelInfo); err != nil {
return err
}
if err := setWelcomeViewCompletedWithManager(ctx, manager); err != nil {
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
}
return nil
}
func setWelcomeViewCompletedWithManager(ctx context.Context, manager *task.Manager) error {
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
return err
}

Some files were not shown because too many files have changed in this diff Show More