Compare commits

...

208 Commits

Author SHA1 Message Date
celestial-vault a07554f191 rework the docker:shell script to reuse an existing container 2025-11-14 21:39:59 -08:00
celestial-vault 474c655240 update script documentation for next steps after docker build 2025-11-14 11:11:25 -08:00
celestial-vault b5157a2376 code comment 2025-11-14 11:02:53 -08:00
celestial-vault b14db72140 add docker setup for cli development 2025-11-13 21:31:54 -08:00
github-actions[bot] 0fb4a6c7e9 v3.37.1 Release Notes (#7451)
* changeset version bump

* Updating CHANGELOG.md format

* Update changelog for version 3.37.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-11-13 17:42:42 -08:00
Saoud Rizwan 855db7d8d8 feat(models): Add free minimax/mimax-m2 model to the model picker (#7453)
* feat(models): Add free minimax/mimax-m2 model to the model picker

* Add free minimax/mimax-m2 model to model picker
2025-11-13 17:41:16 -08:00
CandiedUniverse bb375b78ca fix(hooks): Prevent PreToolUse hook from running before attempt_completion tool (#7450) 2025-11-13 16:14:13 -08:00
CandiedUniverse 31af254f0a fix(hooks): Run PreToolUse only after approval (#7446)
* fix(hooks): Run PreToolUse only after approval; get it working for read_file first

* fix(hooks): Run PreToolUse only after approval; get it working for six more tools now

* fix(hooks): Run PreToolUse only after approval; get it working for the remaining six tools now

* fix(hooks): Implement HookExecution type to avoid using 'any'
2025-11-13 15:41:57 -08:00
github-actions[bot] 2745cdd54b Changeset version bump (#7358)
* v3.37.0 Release Notes

- 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
- 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)

- 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

- Added missing proto generation step in CONTRIBUTING.md
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)

* Adding image optimizations

* Adding image optimizations

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2025-11-13 11:47:42 -08:00
celestial-vault c94c6fd8f1 Fix CLI auth state persistence with explicit flush mechanism (#7445)
Fixes issue where 'cline auth' command would lose all configuration due to process termination race condition.

Root cause: StateManager uses 500ms debounced persistence, but CLI process terminated before setTimeout callback could fire.

Solution: Implemented explicit flushPendingState() mechanism:
- Added StateManager.flushPendingState() method for immediate persistence
- Refactored to extract shared persistence logic (DRY)
- Added flushPendingState gRPC endpoint
- CLI now calls flush instead of using 5s sleep workaround

Results:
- Deterministic persistence (no race condition)
- Faster auth flow (~2s vs 7s)
- Cleaner, more maintainable code
2025-11-13 11:34:17 -08:00
Ara a87664318e Adding Gpt-5.1 to model picker and onboarding list (#7444)
* feat(settings): add GPT-5.1 to Model picker featured models

* adding

* adding

* adding
2025-11-13 11:20:08 -08:00
Ara ba70718c83 Remove Qwen3 coder from Cerebras providers (#7404)
* Remove Qwen3 coder from Cerebras providers

* Update Cerebras Docs to include new model `zai-glm-4.6` and remove `qwen-3-coder-480b-free` and `qwen-3-coder-480b` entries.

---------

Co-authored-by: Juan Pablo <juan@cline.bot>
2025-11-13 11:13:25 -08:00
canvrno cf8dd1c150 Feat:Enhanced support for OpenAI GPT 5.1 (#7443)
* Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.

* Fix typos

* Fix more typos
2025-11-13 11:06:23 -08:00
Bee e714c74ec4 fix(anthropic): sanitize messages to SDK MessageParam shape (#7441)
- Use Anthropic SDK types and return Array<Messages.MessageParam>
- Replace removeReasoningDetails with removeUnknownParams
- Strip unsupported fields (e.g., reasoning_details) and normalize role
- Keep string content unchanged; map array content to known blocks
- Continue adding ephemeral cache_control to targeted user messages

This ensures only Anthropic-supported fields are sent, improving type safety and preventing API errors.
2025-11-13 10:28:40 -08:00
Sarah Fortune a6f8b33895 Change the label in the webview to say Enterprise Rules/Workflows instead of Remote Rules/Workflows (#7440) 2025-11-13 10:15:27 -08:00
Saoud Rizwan 02abbcf045 feat: add AGENTS.md support (#7437)
* feat: add AGENTS.md support

* Update webview-ui/src/components/cline-rules/RuleRow.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update webview-ui/src/components/cline-rules/ClineRulesToggleModal.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update font size for documentation link in ClineRulesToggleModal component

* docs: add support for AGENTS.md standard in Cline rules documentation

* fix: delete agents.md

* Add AGENTS.md support

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 09:42:24 -08:00
Bee 8ca2706cca chore: cleanup leftover code from anthropic provider (#7434)
Follow up from https://github.com/cline/cline/pull/7399

- Only add the redacted reasoning block when a reasoningSignature is present,
  instead of checking message length. This aligns with Claude extended thinking
  requirements and prevents unsignatured reasoning content from being sent.
- Remove leftover thinkingDeltaAccumulator from the Anthropic provider.
2025-11-12 18:33:13 -08:00
Bee e747d211e6 refactor: standardize reasoning yield types across providers [ENG-1226] (#7399)
* refactor(api): standardize reasoning yield types across providers

- Unify reasoning output format in Anthropic, Cline, and Minimax handlers
- Change "ant_thinking" and "reasoning_details" to "reasoning" type
- Add signature and redacted_data properties for consistency
- Wrap message_start cases in braces for scoping
- Consolidate yields to reduce redundancy and improve maintainability

* VercelAIGatewayHandler

* feat: add model information tracking to tasks and messages

Add modelId field to TaskResponse and TaskItem proto messages, and introduce ClineModelInfo message type to track provider and model IDs throughout the system. Update API transform functions to use ClineStorageMessage types and refactor message handling to support model information tracking.

This enables better tracking and auditing of which AI models are used for specific tasks and messages, improving observability and allowing for model-specific analytics.

* clean up protos

* remove console log

* minimax

* use new interface

* clean up
2025-11-12 15:33:48 -08:00
Bee 19aa81a1cc fix(dev): add back CLINE_ENVIRONMENT_OVERRIDE support (#7427)
CLINE_ENVIRONMENT_OVERRIDE was removed in https://github.com/cline/cline/pull/6621 accidentally. This PR adds it back:
Prioritize CLINE_ENVIRONMENT_OVERRIDE over CLINE_ENVIRONMENT when
setting the runtime environment at module load.
This enables temporary/test-specific environment selection without
modifying the base env var. Existing behavior is unchanged when the
override is not set. Updated inline comment for clarity.
2025-11-12 14:58:53 -08:00
Bee f5f3654966 refactor: Extract model info into method in WriteToFileToolHandler (#7420)
* refactor: Extract provider and model info into method in WriteToFileToolHandler

- Introduce private getModelInfo method to centralize extraction of providerId and modelId
- Replace duplicated telemetry extraction logic across multiple function calls
- Update telemetry service invocations to use the new method's return values for consistency and reduced code duplication/script>

* Update src/core/task/tools/handlers/WriteToFileToolHandler.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-12 14:40:51 -08:00
Bee 344b99988b feat: add native tool calls tracking to task events (#7429)
Add `isNativeToolCall` parameter to telemetry service to track when
assistant turns and provider API errors occur with native tool calls
enabled. This enables better analytics and debugging of native tool
call behavior across conversation turns.
2025-11-12 13:03:41 -08:00
Bee 7d3c39bcdb feat: make baseUrl dynamic by removing static field (#7428)
Remove the static _baseUrl field and replace all references with direct calls to ClineEnv.config().apiBaseUrl. This ensures the base URL updates correctly when configuration changes, fixing an issue where the URL remained static even after configuration updates.
2025-11-12 12:38:13 -08:00
celestial-vault 33c1692f8a add remotely configured rules and workflows (#7411)
* add remotely configured rules and workflows

* fix toggles state not being passes to webview on updates; minor format updates

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2025-11-12 10:33:19 -08:00
Bee 3be3ffae4c fix: prevent duplicate tool results by adding existence check (#7419)
* fix: prevent duplicate tool results by adding existence check

Add a check in ToolResultUtils to skip adding a tool result if one already exists for the same tool_use_id, logging a warning to avoid redundant entries in userMessageContent and maintain message integrity. Introduces Logger import for warning output.

* add changeset
2025-11-11 21:37:11 -08:00
murkvin 87c6708fbe fix: restore commit msg generation functionality to command palette (#7417)
Co-authored-by: Kevin Murphy <murkvin@amazon.com>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
2025-11-11 17:36:30 -08:00
Juan Pablo Flores b296706bc0 docs: add guidance for handling strings with quotes in JSON payloads for hooks (#7396)
* docs: add guidance for handling strings with quotes in JSON payloads in hooks documentation

- Introduced a new section on using jq's --arg flag for proper escaping of unescaped quote characters in JSON output.
- Provided a code example demonstrating how to handle complex strings or nested JSON structures safely.

This update enhances the documentation by clarifying how to manage JSON payloads effectively within hooks.

* docs: clarify context modification behavior in hooks documentation

- Updated the explanation regarding context modifications and their effect on AI decisions.
- Emphasized the need to return `cancel: true` in PreToolUse hooks to block actions effectively.

This change enhances the clarity of the documentation, ensuring users understand how to implement immediate effects in their hooks.

---------

Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
2025-11-11 15:24:52 -08:00
canvrno 7fdc82ae23 Renamed Nous proto config strings (#7416)
* Renamed Nous proto config strings

* Revert "feat: migrate terminal execution mode default to background" (#7412)

This reverts commit 0b7393f50e.

---------

Co-authored-by: Ara <arafat.da.khan@gmail.com>
2025-11-11 14:42:13 -08:00
Bee 246ed87350 fix: ensure temperature parameter is explicitly converted to number (#7397)
* fix(openai): ensure temperature parameter is explicitly converted to number

Changed temperature assignment to explicitly check for undefined and convert
the value to Number type before use. This prevents potential type issues when
the temperature configuration is provided as a string or other non-number type,
ensuring consistent numeric handling for the OpenAI API.

* set 0 tempature to undefined

* add changeset
2025-11-11 14:10:21 -08:00
Ara a28465151f Revert "feat: migrate terminal execution mode default to background" (#7412)
This reverts commit 0b7393f50e.
2025-11-11 12:53:18 -08:00
Saoud Rizwan f1967bc3a1 feat: Add OAuth 2.1 authentication support for remote MCP servers (#7376) 2025-11-11 12:35:11 -08:00
Bee ccc83d6728 feat: add feature flag to toggle between new and old onboarding view [ENG-1224] (#7410)
* feat: add feature flag to toggle between new and old onboarding view

Add a new feature flag `show_onboarding_flow` that controls whether users see the new onboarding experience or the legacy welcome view. This enables A/B testing and gradual rollout of the new onboarding flow, and gives me better control when model list has changed and the onboarding model list is outdated.

Changes:
- Add ONBOARDING feature flag (enabled by default)
- Add showOnboardingFlow to ExtensionState and proto definitions
- Add getOnboardingEnabled() method to FeatureFlagsService
- Update App component to conditionally render OnboardingView or WelcomeView based on flag
- Extend ExtensionStateContext with showOnboardingFlow state management

This allows for controlled rollout and easy rollback if issues arise with the new onboarding experience.

* default true

* default to false unless e2e test

* showOnboardingFlow default to off
2025-11-11 11:33:42 -08:00
Szymon Stasik c151d719c8 fix: Handle tool_use blocks in Claude Code backward compatibility path (#7394)
- Transform tool_use content blocks to tool_calls format
- Map Anthropic tool_use structure to ApiStreamToolCallsChunk
- Fixes 'tool_use is not supported yet' error
- Resolves 'Current ask promise was ignored' downstream errors
2025-11-11 11:13:22 -08:00
canvrno cc25833963 Add Nous Research provider (#7141)
* Added Nous Research provider

* Fix casing on import
2025-11-11 10:43:12 -08:00
canvrno 2ef3b7cf0f Nous Hermes-4 family system prompt (#7142)
* Hermes-4 model family system prompt

* Update src/core/prompts/system-prompt/variants/hermes/overrides.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-11 10:14:24 -08:00
Mike Mikula 19d7bd5198 fix: Fix XML entity escaping in model content processor (#7385)
* refactor(task): replace HTML escaping logic with model-specific content fixes

Updated ExecuteCommandToolHandler and WriteToFileToolHandler to utilize a new utility function, applyModelContentFixes, for handling model-specific quirks. This change centralizes the logic for fixing escaped characters and invalid characters, improving maintainability and consistency across command execution and file writing processes. Additionally, introduced ModelContentProcessor to encapsulate the new content processing logic.

* test(ModelContentProcessor): add comprehensive unit tests for applyModelContentFixes

Introduced a new test suite for the ModelContentProcessor, covering various scenarios for the applyModelContentFixes function. Tests include model ID detection, file type handling, orchestration logic, and integration with real-world use cases, ensuring robust validation of content processing for different model types and file formats.

* fix: add changeset for XML escaping bug fix

* refactor(ModelContentProcessor): make modelId parameter optional in applyModelContentFixes

Updated the applyModelContentFixes function to accept an optional modelId parameter, allowing for fixes to be applied even when the model ID is not provided. Adjusted related logic in ExecuteCommandToolHandler and added a test case to verify this new behavior.
2025-11-11 07:26:27 -08:00
Ara 121066f938 feat: migrate terminal execution mode default to background (#7368)
- Add one-time migration to set all users to backgroundExec mode
- Change default terminal execution mode from vscodeTerminal to backgroundExec
- Update UI labels to clarify background mode is recommended
- Add migration sentinel flag to prevent re-running

This migration improves the default user experience by using background
terminal execution, which provides a cleaner UI and better performance
compared to the interactive VSCode terminal mode. Existing users will be
automatically migrated once, while new users will get the improved default.
2025-11-10 14:06:32 -08:00
Juan Pablo Flores f9b88318ea docs(dictation): add notice on Windows support (#7059)
* docs(dictation): remove Windows support from system requirements

- Add note indicating dictation is currently unavailable on Windows
- Remove Windows FFmpeg installation instructions from the list
- Clarify that Windows support is planned for a future release

This update reflects the current state of the feature and sets proper user expectations about platform availability.

* Update docs/features/dictation.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-10 13:26:10 -08:00
Ara 1d64f64f43 Enable voice mode for linux also (#7369) 2025-11-10 12:19:41 -08:00
canvrno 766e2e6a25 Model-family specific variants for deep-planning slash command (#7337) 2025-11-10 11:23:40 -08:00
Ara 8bd7260350 Fix Standalone Terminal Background Transition Issues (#7390) 2025-11-10 11:11:16 -08:00
canvrno a1f4e8b9d4 Adjusted focus chain prompting - more guidance for models using native tool calling, consolidated prompting for other models (#7340) 2025-11-10 11:05:51 -08:00
Johnny Rice aa3e0860cd docs: add missing proto generation step in CONTRIBUTING.md (#7336)
- Add step 4 in Local Development Instructions to run npm run protos
- Add proto generation step in Extension section
- Create new npm run dev script that runs protos + watch
- Update Terminal Workflow documentation to mention npm run dev

Fixes #7335
2025-11-10 10:20:44 -08:00
Dominic Cooney 67bc9932e5 Add proxy configuration to more network fetches. (#7328) 2025-11-10 09:54:54 -08:00
Ara ba98b44504 feat(task): cancel active background command during task abort (#7363)
Add cleanup logic to cancel any active background command when aborting a task. This ensures background processes are properly terminated before the task abort completes, preventing orphaned background commands.
2025-11-07 19:13:31 -08:00
celestial-vault fdeb639f75 make field mask type a string array so that the kotlin/go protobuf libraries don't convert it to snake case (#7364) 2025-11-07 18:52:38 -08:00
Ara 76410b42b0 Removing deprecated Fireworks Models (#7268) 2025-11-07 14:34:29 -08:00
Tomás Barreiro e4e347a8a4 Add Remote Server MCPs to the remote config (#7357) 2025-11-07 23:33:07 +01:00
canvrno 59e7c2b7b9 Fix CLI quick auth issue in docker envs (#7256) 2025-11-07 14:26:39 -08:00
Ara 5371377b21 Removing Minimax M2 from free models list (#7359) 2025-11-07 14:13:19 -08:00
canvrno 5a444dc30a Hide context window usage from env details until usage reaches elevated state (ng models only) (#7345) 2025-11-07 13:46:36 -08:00
Bee 34c48264c0 fix(models): ensure thinking config has default maxBudget value (#7319)
* fix(models): ensure thinking config has default maxBudget value

- Add ANTHROPIC_MAX_THINKING_BUDGET import and use as fallback
- Check for both "include_reasoning" and "reasoning" parameters
- Set default maxBudget when thinking is supported but value not provided
- Ensures thinking models always have valid budget configuration

This prevents issues when OpenRouter API doesn't return thinking_config.maxBudget, ensuring all thinking-enabled models have a proper budget value set as it's used to determine if a model support thinking in some part of our code.

* update thinkingConfig placeholder
2025-11-07 12:52:14 -08:00
Tomás Barreiro daf14ef181 Add LiteLLM to the remote config (#7307)
* Add Google Vertex and LiteLLM to the remote config

* Add the LiteLLM models and export types
2025-11-07 20:24:27 +01:00
Bee f1bf9b3f90 feat: new onboarding flow [ENG-1128] (#7088)
* feat(auth): new onboarding UI

- Add optional `strict` parameter to `createAuthRequest()` to prevent opening new auth windows when already authenticated
- Update onboarding flow with new UI text and button labels ("Login to Cline", "I have my own key", "Ready")
- Add new onboarding data models and step configuration for improved user experience
- Update all e2e tests to reflect new button labels and authentication flow
- Remove obsolete `closeBanners` utility function
- Refactor authentication logic to support strict mode for better control over auth window behavior

This change improves the authentication UX by preventing duplicate auth windows and provides a more streamlined onboarding experience with clearer call-to-action buttons.

* Debug buttons

* Update model list

* Add search box

* improve model selection with names and improved search

- Add name field to ModelInfo interface for better model identification
- Populate model name from OpenRouter API response
- Improve model search filtering to exclude embedding models
- Add case-insensitive search for better UX
- Enhance UI with badges for model capabilities and pricing
- Update styling for better visual hierarchy and selected state
- Display model names in search results for clarity

This improves the onboarding experience by making model selection more informative and user-friendly with better search capabilities and visual feedback.

* clean up

* async onboarding

* Update langauge

* spacing

* search result info text

* update e2e test

* update badge

* style(onboarding): adjust max-width constraint placement

Move max-width constraint from parent container to content wrapper div to improve responsive layout behavior. This ensures the full width is utilized at the top level while constraining only the scrollable content area.

Changes:
- Added w-full to root container for proper width handling
- Removed max-w-lg from middle container
- Applied max-w-lg to content wrapper instead

* Update search box placeholder text

* remove unused description field

* clean up

* Fix search box reset state

* feat: add loading screen to onboarding flow

- Add loading state (step 2) during authentication process
- Simplify welcomeViewCompleted logic by removing auth service check
- Move welcomeViewCompleted state update to auth success handler
- Display loading spinner during sign-in to improve UX
- Ensure auth status update occurs in finally block for reliability

The loading screen provides visual feedback while authentication completes, preventing users from seeing incomplete UI states during the sign-in process.

* update AuthServiceMock

* capture onboarding events

* smaller badge radius

* radius-xs = 4px & add speed label

* update to MiniMax M2
2025-11-07 10:30:50 -08:00
Bee 6e20047c3e refactor(ui): simplify flexible SettingsView (#7352)
* refactor(ui): simplify SettingsView tab system

- Remove ResizeObserver-based compact mode detection and related state
- Replace dynamic CSS class constants with inline Tailwind classes using cn utility
- Remove debounce dependency and handleTabChange callback
- Simplify tab rendering logic by removing conditional compact/full mode rendering
- Clean up unused imports (debounce, useRef) and state management
- Streamline component architecture for better maintainability
- simplify ModelDescriptionMarkdown

This refactoring reduces complexity while maintaining the same visual functionality, making the code easier to understand and maintain.

* clean up
2025-11-07 10:01:14 -08:00
celestial-vault 1d4fe88bec simplify updateApiConfiguration calls (#7299)
* simplify calls to updateApiConfiguration by moving the logic around which mode to update to the RPC; change proto field name; use new calls in MoonshotProvider, and update calls in LiteLlmProvider

* remove console logs
2025-11-07 08:12:20 -08:00
Sarah Fortune 74dbbb9d6b Use staging endpoint for MCP marketplace (#7348) 2025-11-07 00:03:02 -08:00
github-actions[bot] 3073cf40be v3.36.1 Release Notes (#7317)
* changeset version bump

* Updating CHANGELOG.md format

* Update changelog format for version 3.36.1

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-11-06 18:54:30 -08:00
canvrno 50056768d6 Add MCP tool usage to GLM prompt (#7347)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-06 18:03:23 -08:00
Bee e7cd32b830 fix: function declarations tool converter for native tool calling (#7342)
* fix:  function declarations tool converter for native tool calling

- Replace crypto-based ID generation with nanoid in McpHub that is causing invalid function name issue with Gemini models
- Refactor Google tool parameter schema mapping with typed constant
- Improve parameter properties handling in toolSpecFunctionDeclarations
  - Add support for nested properties and enum values
  - Skip $schema property during parameter processing

This change fix type safety for Google Gemini function calling parameter definitions.

* disable native tool calling by default

* Always starts with a letter

* whoops

* reuse id

* fix run time package issue
2025-11-06 17:06:17 -08:00
Bee d983b451e1 fix: Remove Gemini and XAI from next-gen model providers (#7341)
Removes "gemini" and "xai" from the list of providers considered "next-gen" in `isNextGenModelProvider`.
This change prevents Cline from attempting to use native tool callings with these providers until existing issues are resolved.
2025-11-06 14:18:55 -08:00
Bee bb32aac8e7 fix: exclude grok-code from Grok-4 model family check (#7338)
Remove "grok-code" from isGrok4ModelFamily function to exclude it from the next-gen model list, disabling native tool calling for grok-code as it does not work well with the model.
2025-11-06 12:59:58 -08:00
chenxue e6d5f55fb2 fix bug (#7324) 2025-11-06 11:42:14 -08:00
Tomás Barreiro 93f60ebe5b Share provider information (#7331)
* Share provider information

* Move files

* Move files
2025-11-06 20:40:32 +01:00
Bee a3e000d5dd fix: refactor APPLY_PATCH tool [ENG-1174] (#7189)
* feat: refactor APPLY_PATCH tool

Replace separate file creation and editing tools with unified APPLY_PATCH tool for the native-gpt-5 model variant. This consolidation simplifies file operations through a single patch-based interface.

Changes:
- Replace FILE_NEW and FILE_EDIT tools with APPLY_PATCH in config for gpt-5 with native tool calling
- Disable EDITING_FILES system prompt section (no longer needed)
- Remove EDITING_FILES section from base template
- Refactor ApplyPatchHandler with improved architecture:
  - Extract patch parsing logic into PatchParser utility class
  - Extract file operations into FileProviderOperations utility
  - Extract path resolution into PathResolver utility
  - Add comprehensive error handling with DiffError types
  - Improve type safety with shared Patch types
- Add extensive test coverage for PatchParser including:
  - Edge cases (empty files, large files, unicode)
  - Error conditions (malformed patches, invalid operations)
  - Complex scenarios (multiple chunks, context matching)
- Export PATCH_MARKERS and BASH_WRAPPERS for reusability

This refactoring improves maintainability, testability, and provides a more robust patch application system for the GPT-5 model variant when native tool calling is enabled.

* Add diagnostic to result

* Update unit tests

* add feedback

* typo

* captureToolUsage

* fix test

* fix test

* Update ClineMessage

* update diff editor on stream

* revert content partial stream

* Disable Apply patch and add native gpt snap shot

* Remove delete action and make preserveEscaping conditional

* Add delete function to diff provider

* Update UI for file deletion
2025-11-05 20:10:26 -08:00
Bee 0076be531d fix: removes reasoning_details field from Anthropic and Gemini providers (#7282)
* fix: sanitize Anthropic message and removes reasoning_details field

- Extract duplicate Anthropic message formatting code into sanitizeAnthropicMessages function
- Remove code duplication between AnthropicHandler and VertexHandler
- Add support for claude-sonnet-4-5-20250929:1m model
- Remove unused MessageParam import from anthropic provider

* add changeset

* Create thinking content block for gemini
2025-11-05 20:10:09 -08:00
github-actions[bot] 9de4fc2a2f v3.36.0 Release Notes (#7271)
- 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, and refinements to the GLM-4.6 system prompt

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-05 18:10:09 -08:00
CandiedUniverse 8a4b87a133 feat(hooks): Improve examples in .clinerules/hooks/ directory (#7316) 2025-11-05 16:52:26 -08:00
Juan Pablo Flores 571480e3b6 Hooks docs (#7173)
* docs(features): add comprehensive hooks documentation

Add detailed documentation for the Hooks feature, including:
- Overview of hooks functionality and use cases
- Getting started guide with step-by-step setup instructions
- Hook types and lifecycle explanations
- JSON communication format and examples
- Validation and blocking capabilities
- Best practices and implementation patterns

Update docs navigation to include the new hooks documentation page
in the features section.

* docs(features): reorganize and clarify Hooks documentation

- add frontmatter description and insert demo GIF in Getting Started
- reorganize Hook Types into Task Lifecycle and Communication categories
- convert tables into dedicated sections per hook with explicit "Input Fields" examples
- add JSON input/output examples and clarify execution timing, limits, and context behavior
- remove embedded bash examples and Advanced Topics clutter; streamline troubleshooting and security warning

* Update docs/features/hooks.mdx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* docs: reorganize hooks documentation into four categories

Restructure the hooks documentation to improve clarity and organization:

- Change from two categories to four logical categories: Tool Execution,
  User Interaction, Task Lifecycle, and API Response
- Reorder hooks by category based on trigger points and use cases
- Add complete JSON examples with all base fields for each hook type
- Enhance descriptions with clearer use case explanations
- Improve consistency across hook documentation format

This reorganization makes it easier for developers to find the right hook
for their specific needs and understand the complete data structure they
will receive.

* docs: enhance TaskStart hook example with JSON payload handling

Replace basic "Hello World" example with practical demonstration of reading JSON input from stdin and using jq to inspect payload structure and field types. Added explanatory text to help developers understand hook input/output mechanics before building complex logic.

* Update docs/features/hooks.mdx

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* comments out preCompact and taskComplete

* fix: clarify description of hook types in documentation

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: CandiedUniverse <132302818+candieduniverse@users.noreply.github.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-11-05 16:50:22 -08:00
Chris Sells 5c2c264a1d initial CLI samples (#7040)
* initial CLI samples

* Add GitHub integration and root cause analysis samples to CLI documentation

* docs(cline-cli): restructure samples into nested documentation group

Convert the single samples page into a grouped navigation structure with three separate sample pages:
- Overview (main samples page)
- GitHub Issue RCA
- GitHub Integration

Add redirect from the old `/cline-cli/samples` path to the new `/cline-cli/samples/overview` location to maintain backward compatibility with existing links.

* docs: improve GitHub issue RCA sample docs with better structure and links

Reorganize documentation with source code links, move demo video to prominent position,
simplify explanations, and add tutorial references for better discoverability.

* docs: improve github-issue-rca documentation and add download instructions

- Remove redundant "Demo Video" heading from mdx documentation
- Add "Getting the Script" section to README with two options:
  - Option 1: Clone/view repository with direct link to script
  - Option 2: Direct download using curl with example commands

These changes make it easier for users to quickly access and download
the analyze-issue.sh script without needing to navigate the repository
structure, improving the overall user experience.

* docs: update GitHub Root Cause Analysis sample links and enhance README with installation instructions

* docs(samples): improve GitHub integration setup guide and add prerequisites

Enhanced the GitHub integration sample documentation with:
- Added prerequisites section listing required knowledge and resources
- Improved setup instructions with direct curl commands for downloading files
- Added prominent warning callout for editing GITORG and GITREPO variables
- Included beginner-friendly note recommending simpler sample first
- Updated documentation link to point directly to GitHub repository
- Provided two options for adding analysis script (download or copy)
- Enhanced formatting and clarity throughout the guide

These changes make the sample more accessible to new users and reduce
setup friction by providing copy-paste commands and clearer guidance.

* docs(samples): consolidate CLI samples documentation and enhance GitHub integration README with improved image formatting

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* removes bookmarks and creates padding for images

* Update image source in README for CLI sample

* Add GitHub CLI samples and update docs navigation

- Add GitHub Issue RCA sample (docs/cline-cli/samples/github-issue-rca.mdx)
- Add GitHub Actions integration sample (docs/cline-cli/samples/github-integration.mdx)
- Add samples overview page (docs/cline-cli/samples/overview.mdx)
- Update docs.json to include new CLI samples navigation entries

* docs: simplify "Getting the Script" section in GitHub Issue RCA sample

Reorder options so curl download is Option 1 and the full script view is Option 2.
Remove the clone/view-repo entry, move the chmod +x instruction into a Note
after the script accordion, and clarify the Basic Usage intro text.

* docs: tidy GitHub Integration sample — remove redundant workflow placement note, simplify script copy options, fix GitHub Action capitalization and related-samples link

* docs: remove GitHub Integration and GitHub Issue RCA sample READMEs

---------

Co-authored-by: Juan Pablo <juan@cline.bot>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 16:49:57 -08:00
CandiedUniverse 04549cf1e3 feat(hooks): Trim characters around JSON in hook output (#7314) 2025-11-05 15:59:56 -08:00
Bee f2a93b6201 fix(account): correct tooltip visibility logic for remote config lock (#7313)
Invert the boolean condition for TooltipContent's hidden prop to properly
show the tooltip when organization has remote configuration enabled.
Previously, the tooltip was hidden when it should be visible and vice versa.
2025-11-05 15:57:47 -08:00
CandiedUniverse 2f09b994dd feat(hooks): Fix contextModification injection into API Request (#7311) 2025-11-05 15:28:34 -08:00
Bee 8aac67bf75 feat: enable native tool call by default (#7304)
Change the default value of `nativeToolCallEnabled` from `false` to `true` in the global state initialization. This enables native tool calling functionality by default when no previous state exists, improving the out-of-box experience for users.
2025-11-05 15:23:02 -08:00
CandiedUniverse 10422dd3fc feat(hooks): Remove hooks feature flag (step 1), keeping hooks setting toggle (#7309) 2025-11-05 14:51:11 -08:00
CandiedUniverse 6191c681ce feat(hooks): Fix the missing Start New Task button after task completion (#7303) 2025-11-05 14:49:42 -08:00
CandiedUniverse 9ab7b41c24 feat(hooks): Remove debug logging to the console (#7306) 2025-11-05 14:10:19 -08:00
Saoud Rizwan bddfb93b38 fix: order of AIHubMix in API providers (#7302) 2025-11-05 12:51:18 -08:00
Bee a219eb8000 fix(settings): remove highlight function from provider search (#7300)
Remove redundant highlight import and simplify provider search results
mapping in ApiOptions component. Search results now directly map to
items without applying highlighting, reducing unnecessary dependencies
and simplifying the code.
2025-11-05 12:44:34 -08:00
Saoud Rizwan 515b4e5d6a Fix vercel gateway thinking and model list (#7196) 2025-11-05 12:43:44 -08:00
Sarah Fortune aaa3846b82 Add GCP Vertex provider to remote config schema (#7200)
* Add global rules and workflows to the remote config schema

Add a way for the admin to configure global cline rules and workflows for their users.

* Add GCP Vertex provider to remote config

Add config settings for the GCP Vertex provider
2025-11-05 12:28:08 -08:00
canvrno 8229dcfe88 Fix openai compatible auth issue in CLI (#7291) 2025-11-05 11:34:31 -08:00
Dominic Cooney b4ea1a17a5 WIP proxy patch (#7263)
Better documentation in shared/net

Better docs, wire up when running standalone.

Use esbuild to statically decide which proxying we are doing.

Add the changeset.

Add the ability to mock this fetch.
2025-11-05 11:26:12 -08:00
Nick Baumann e263c74325 Add model/provider attribution to telemetry events (#7230)
- Add provider parameter to diff edit failure events
- Add provider parameter to context summarization events
- Add mode parameter to conversation turn events
- Add model/provider parameters to focus chain completion events

Addresses review feedback by removing checkpoint telemetry changes.
Follows pattern from PR #7214 for consistent telemetry attribution.
2025-11-05 11:15:01 -08:00
nihar-oracle 08ca115c66 fix: Fixing token refresh logic for oca on startup (#7209)
* fix: Fixing token refresh logic for oca on startup

* fix: Fixing token refresh logic for oca on startup
2025-11-05 09:14:42 -08:00
Bee 68ffe20b7d feat(telemetry): add native tool call tracking (#7279) 2025-11-05 06:57:38 -08:00
TechBrewBoss 4517408537 Fixes Hicap model selector dropdown display (#7285)
* Fixes Hicap model selector dropdown display

Fixes a bug where the checkbox was showing in the model selector dropdown.

The z-index was being improperly applied to the VSCodeTextField, causing the dropdown to be obscured. This change moves the z-index styling to be applied directly to the style attribute of the VSCodeTextField component, resolving the display issue.

* Update .changeset/proud-humans-itch.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-04 23:56:12 -08:00
AJ Juaire 42c947afd4 Fix Bedrock user agent to report full SDK details (#7273)
* switch from defaultUserAgentProvider to customUserAgent for Bedrock

* update to userAgentAppId
2025-11-04 18:47:08 -08:00
Bee d4736afce2 fix(navbar): align navbar tooltip buttons with new shadcn component (#7283)
Switch Navbar actions to the shared Button component and tooptip to use the new shadcn tooltip component so the navbar render correctly and spacing matches the previous design.
2025-11-04 18:22:35 -08:00
Bee b00053ff16 fix(settings): restore Native Tool Call setting option (#7284)
Re-add the Native Tool Call checkbox setting to the FeatureSettingsSection
that was accidentally removed in PR #7126 due to merge conflicts.

The setting allows users to enable/disable native tool calling through the
API when the feature flag is enabled. The UI includes an experimental
warning and descriptive text explaining the functionality.

Changes:
- Add nativeToolCallSetting to useExtensionState destructuring
- Add conditional checkbox UI for Native Tool Call feature
- Include experimental warning and feature description
2025-11-04 18:17:49 -08:00
Bee 2ab8fcb54f fix(task): prevent empty assistant messages in conversation history (#7280)
* fix(task): prevent empty assistant messages in conversation history

Add conditional check to only append assistant content to API conversation
history when the content array is not empty. This prevents unnecessary empty
assistant messages from cluttering the conversation history and potentially
causing issues with the API.

Changes:
- Wrap addToApiConversationHistory call in length check
- Add explanatory comment for the conditional logic

* add changeset
2025-11-04 16:15:37 -08:00
celestial-vault e697c12e94 Remote config - add info banner for mcp marketplace lockdown state (#7278)
* filter mcp marketplace results based on remote config

* add info ui banner for mcp marketplace remote config state

---------

Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
2025-11-04 13:57:59 -08:00
celestial-vault 64dcc3fc89 filter mcp marketplace results based on remote config (#7277) 2025-11-04 13:50:50 -08:00
Bee 8215d3fdb9 fix: flaky e2e test for slash commands (#7206)
* fix: flaky e2e test for slash commands

* use click

* @problems

* newtask click

* exact: false

* exact: false

* revert unrelated change
2025-11-04 12:43:29 -08:00
canvrno 59260cefab Small timeout increaase in diff.test (#7270) 2025-11-04 11:04:07 -08:00
chenxue a767f844b9 feat: add new provider AIhubmix (#7259)
* feat: add AIhubmix provider integration

- Add AIhubmix as a new provider with full API integration
- Implement AIhubmixHandler for API interactions
- Add model fetching functionality via getAihubmixModels
- Create UI components for AIhubmix configuration
- Update proto definitions and API configuration
- Add changeset for version tracking

* docs: add AIhubmix integration documentation

* fix: add plan/act mode fields for AIhubmix and Hicap providers

- Add planModeAihubmixModelId and planModeAihubmixModelInfo
- Add actModeAihubmixModelId and actModeAihubmixModelInfo
- Add planModeHicapModelId and planModeHicapModelInfo
- Add actModeHicapModelId and actModeHicapModelInfo
- Ensures model selection works correctly in both plan and act modes

* merge

* Delete AIHUBMIX_INTEGRATION.md

* fix: bot

* Update providerUtils.ts
2025-11-04 10:51:45 -08:00
canvrno bb7c3556d6 Use context-aware task manager for quick auth (#7255) 2025-11-03 21:34:52 -08:00
github-actions[bot] 8b80c337ae v3.35.1 Release Notes (#7207)
- 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

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-11-03 20:08:34 -08:00
celestial-vault d0cac53d5b Add a field mask to the new updateApiConfiguration RPC (#7197)
* add a field mask to updateApiConfiguration and update LiteLLM provider component calls to use it

* remove console log
2025-11-03 19:43:45 -08:00
celestial-vault 8aedc90214 add configured providers to remote config to limit options (#7250) 2025-11-03 19:39:52 -08:00
celestial-vault 0c118086df fix: add explicit wait for slash commands menu in e2e test (#7063)
This fixes a flaky e2e test by adding an explicit wait for the slash
commands menu to be visible before attempting to interact with it.

Changes:
- Add data-testid to SlashCommandMenu component for test targeting
- Add waitFor() call in test to ensure menu is visible before Tab press
2025-11-03 18:10:42 -08:00
Bee 261a7dd505 fix: enable Add Header button in OpenAICompatibleProvider (#7246)
* fix: enable Add Header button in OpenAICompatibleProvider

Implement functionality for the Add Header button that was previously disabled. The button now:
- Enables when headers are not managed by remote configuration
- Creates new header entries with auto-incremented keys (header1, header2, etc.)
- Updates the API configuration state with the new header

This allows users to add custom headers to their OpenAI-compatible provider configuration unless restricted by organization settings.

* add changeset
2025-11-03 15:01:14 -08:00
canvrno 9abf2f567a Fix empty content field issue (#7247) 2025-11-03 14:57:49 -08:00
Marco Alejandro Chavez Santos 42de7c81b4 Hicap integration as new provider (#6988)
* Feat:
* add hicap as provider option
* add new variable to handle hicapApiKey
* add variable to handle hicapModelId in Plan and Act mode
* get hicap available model from Hicap Endpoint
* create hicap provider (ui)
* hicap handler

* rebase main into this branch and fixing errors

* add changeset

* fix typo with hicap api key

* resolve comments from cline team on PR

* revert some delete console logs

* HicapModelPicker changed styled components for tailwind format, remove unnecessary hicapModelId migration, refreshHicapModel use setGlobalState function

* rebase main branch
2025-11-03 14:57:23 -08:00
Saoud Rizwan 06b710f62b fix: context mentions are positioned incorrectly (#7248) 2025-11-03 14:43:52 -08:00
Ara ea9b8fe0b1 Enable Terminal Timeouts by defualt (#7171)
* Remove redundant test code that's very confusing

* feat: add vscodeTerminalExecutionMode to TaskConfig and apply timeout

Add vscodeTerminalExecutionMode parameter to TaskConfig and thread it through ToolExecutor and Task classes. Apply default 30-second timeout to commands executed in backgroundExec mode, similar to existing yolo mode behavior.

This change enables different execution strategies for VSCode terminal commands and ensures background executions have appropriate timeout protection to prevent hanging processes.
2025-11-03 11:33:10 -08:00
canvrno 05213f2a71 fix: Remove orphaned tool_results after truncation (#7225) 2025-11-03 10:25:16 -08:00
Ara d5bad1357d fix: Support interleaved thinking for miniMax provider (#7162)
* fix: interleaved thinking

* Adding native tool calling
2025-11-03 09:31:08 -08:00
Saoud Rizwan a4b1549dac Revise title in README.md
Updated the title of the README file and removed the subtitle.
2025-11-02 23:29:49 -08:00
Andrei Eternal e015ce94c0 cli polish ahead of release - disable doctor, better node errors (#7215)
Co-authored-by: Andrei Edell <andrei@nugbase.com>
2025-11-02 14:52:54 -08:00
Wahaj Ahmed Khan be076cf407 feat: Add support for Claude 4.5 Sonnet in SAP AI Core provider (#7217)
- Add anthropic--claude-4.5-sonnet model definition to sapAiCoreModels
- Update SAP AI Core provider to handle Claude 4.5 Sonnet in anthropicModels array
- Enable caching support for Claude 4.5 Sonnet using converse-stream endpoint
- Add Claude 4.5 Sonnet to streamCompletionSonnet37 method for proper response handling

Fixes #7216

Co-authored-by: Wahaj Ahmed Khan <“wahaj.khan@sap.com”>
2025-11-01 20:01:04 -07:00
Nick Baumann 580db36476 Add provider field to tool usage telemetry events (#7214)
- Add provider parameter to captureToolUsage() and captureDiffEditFailure() methods in TelemetryService
- Update all 11 tool handlers to extract and pass provider information
- Extract provider using plan/act mode differentiation from state manager
- Update UIHelpers.ts captureTelemetry wrapper to include provider
- Update test file to include provider parameter in test calls

This enables tracking which API provider (anthropic, openai, etc.) was used for each tool execution.
2025-10-31 18:31:45 -07:00
Sarah Fortune 8f8b98bb58 Add global rules and workflows to the remote config schema (#7198)
Add a way for the admin to configure global cline rules and workflows for their users.
2025-10-31 10:24:40 -07:00
CandiedUniverse 36022438cb 🪝Hooks: Exclude .clinerules/hooks/ files from Rules feature (#7202)
* Exclude .clinerules/hooks/ files from Rules feature

* Escape whitespace in paths correctly when discovering hooks/ directories
2025-10-31 10:18:51 -07:00
Bee c7afb61e28 fix: react-remark rendering in ModelDescriptionMarkdown (#7205)
* fix: react-remark rendering in ModelDescriptionMarkdown

- Add useRemark hook to properly parse and render markdown content that was removed in last git commit
- Extract props interface to ModelDescriptionMarkdownProps for better type safety
- Add useEffect to reactively update markdown when content changes
- Set fixed height (h-20) for collapsed state to improve layout consistency
- Replace raw markdown text display with processed reactContent

This change ensures markdown formatting (links, bold, italics, etc.) is correctly rendered in model descriptions instead of showing raw markdown syntax.

* changeset
2025-10-31 10:03:27 -07:00
CandiedUniverse dfd113a6e5 🪝Hooks: Gorgeous UI [ENG-994] (#7126)
* feat(hooks): Initial implementation of hooks UI using background terminal UI

* Hooks UI improvements

* Hook discovery improvements

* Separate hooks UI into separate files

* Fix failing tests

* Add missing docs

* Hooks hardening and handling edge cases

* Improvements to hooks error messaging

* Improve TaskCancel hook UI

* Simplify and improve console.log() output from hooks

* Changes from usability feedback

* test: verify linting fixes

* fix: resolve cancel functionality in PostToolUse hook using flag pattern

* fix: add cancel button support for TaskResume and UserPromptSubmit hooks

* Getting things working again

* Fixing cancel behaviors

* Trying another fix

* Demoed

* Add examples to .clinerules/hooks/ directory

* Refactored away loadTaskStateWithoutWorkflow

* Fix unsafe return in finally block

Refactored PostToolUse hook execution to avoid using return statement
in finally block, which the linter flags as unsafe. Instead of early
return, wrapped the hook logic in a conditional check for abort status.

This resolves the biome lint error:
lint/correctness/noUnsafeFinally - Unsafe usage of 'return' in finally block

* Remove unnecessary files from branch

* remove cancelHookExecution() in favor of cancelTask()

* Remove unnecessary polling pattern for hook cancellation

* Fixing cancel behavior

* Fixing cancel behavior

* Fix resume

* Sqaush commits to improve merge tool behavior

Plan out the work needed to resolve race conditions

Fix race conditions (1)

Fix race conditions (2)

Fix race conditions (3)

Fix race conditions (4)

Fix race conditions (5)

Fix unit tests

Updated implementation plan doc

Code quality improvement

Temporary logging to troubleshoot race conditions in hooks UI blocks

Fix streaming output for hooks

Remove in-progress planning file

Revert breakage

Remove unneeded arg output in PreToolUse hook UI

Fix race condition with PreToolUse and PostToolUse hooks

Fix PreToolUse attempt_completion use case

Fix ChatTextArea prompt input

Fixed part of the rersume bug

Clean-up before code review

Change cancel button to abort button

Fixed TaskCancel behavior

Fixing resume behavior (partially fixed)

Make notch arrow visible in hooks block up expand/contract toggle

Prevent cancel/abort from clearing unsent user message

Change 'Cancelled' to 'Aborted' in hook UI block

Use color styles consistent with background terminal

Fix npm compile issue

Remove unneeded hot-cold tracking from hooks

Remove unused constant as per PR feedback

Minor changes

Skip combineHookSequences() if hooks feature setting not enabled

Improve mutex pattern for general use and fix additional race condition

Refactor to reduce duplication (keep it DRY)

Refactor to reduce duplication (keep it DRY) part 2

Fix tests

Skip some tests on Windows (not yet supported)

Hooks unsupported on Windows

Fix broken unit tests

* Fix broken unit tests

* Bee and Eve fixing 'API Request...' test in e2e tests

* Add comment as per ellipsis-dev PR feedback

* Changes as per PR feedback from Evan

* Remove if that always resolves to true

* Don't use vscode API; use cross-compatible solutions instead

* Remove mention of obsolete clineAsk handler
2025-10-31 05:50:18 -07:00
github-actions[bot] 3698d2356c v3.35.0 Release Notes (#7127)
- 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>

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-30 18:41:23 -07:00
Bee ff20c4addc feat: add native tool call setting with feature flag support (#7193)
* feat: add native tool call setting with feature flag support

Add a new native tool call setting that allows users to enable/disable native tool calling functionality. The setting is controlled by both a user preference and a feature flag, providing flexibility for gradual rollout. Changes include:
- Added native_tool_call_enabled field to UpdateSettingsRequest proto
- Implemented state management for the setting in controller and storage
- Added UI toggle in feature settings section
- Updated task execution logic to check both user setting and feature flag
- Extended extension state interface to include the new setting

* Add changeset
2025-10-30 17:47:35 -07:00
Bee 8eeeabb966 fix: ensure hasThinkingBudget validates non-empty thinkingConfig object (#7192)
Previously, hasThinkingBudget would return true for empty objects ({}),
causing incorrect behavior. Now checks both for truthiness and that the
object contains at least one key.

Also updates thinkingConfig assignment logic to avoid setting empty
objects and converts imports to type-only imports for better tree-shaking.
2025-10-30 16:22:51 -07:00
Saoud Rizwan 9b1dc5bd92 Improve auto-approve menu design and remove unnecessary options (#7180) 2025-10-30 16:10:14 -07:00
CandiedUniverse 1cfff0a45f Hide hooks toggle from appearing in the the Feature Settings (#7191) 2025-10-30 15:44:31 -07:00
Bee e7d00dec2d refactor(ui): improve tooltip and task header responsiveness (#7186)
Refine TaskHeader controls so actions wrap naturally and the cost
label scales down on narrow viewports.
Update tooltip content to wrap flexibly, tweak padding,
add collision padding, and cap width on small screens.
Introduce xs and xxs breakpoint variables to support responsive styles.
2025-10-30 11:41:23 -07:00
Bee 2c5748ccfd fix: set free pricing for featured models in Cline provider (#7179)
* fix: set free pricing for featured models in Cline provider

Add isFree flag to featured models and ensure free models have $0 pricing when using Cline provider. This updates the OpenRouterModelPicker to automatically set input, output, and cache prices to zero for free featured models, preventing confusing info display to users.

* update

* fix
2025-10-30 11:41:14 -07:00
Bee 5196adce33 fix: adjust mode toggle hover and colors (#7177)
* fix: adjust mode toggle hover and colors

- add hover state to plan/act toggle buttons with toolbar hover color
- set inactive text color to text-foreground
- align toggle styling with previous design

* feat: toggle spacing fix and transparent color

* SwitchContainer to tailwind

* revert

---------

Co-authored-by: Jose R. Perez <trupix@gmail.com>
2025-10-30 10:20:30 -07:00
celestial-vault d95d86f329 updateApiConfiguration v2 (#7160)
* add new version of updateApiConfiguration that splits options and secrets

* remove console log

* remove unnecessary modelInfo conversion
2025-10-29 22:39:51 -06:00
Bee f6eb3aa386 update: GPT-5 native tools apply_patch to write_to_file (#7178)
- extend replace_in_file and write_to_file specs with a native GPT-5 variant to support the model
- swap native GPT-5 config to use write_to_file and replace_in_file tools instead of apply_patch tool
2025-10-29 19:56:29 -07:00
Bee 5c4b9e54c2 Bee/mcp server key (#7175)
* refactor: use hashed keys for MCP server names in native tool identifiers

- Replace server names with unique hashed keys in MCP tool names to avoid length issues and ensure uniqueness.
- Add McpHub methods to generate and retrieve server names from keys.
- Update McpServer type to include uid field.
- Modify tool-use-handler to resolve server names from keys during tool execution.

* Add server name to tool description

* Fix collision case

* clean up on error
2025-10-29 18:59:29 -07:00
Juan Pablo Flores 29a1d08685 docs: updates Cursor AI Panel drag instructions and updates Gif (#7176)
* docs: clarify Cursor AI Panel drag instructions and fix image path

* docs: replace "AI Panel" with "AI Pane" in Cursor right-sidebar instructions
2025-10-29 18:58:48 -07:00
Tomás Barreiro 9664ddd106 Ensure WorkOS Auth token deletion when logging out (#7174)
* Ensure WorkOS Auth token deletion when logging out

* Add changeset

* refactor
2025-10-30 02:19:33 +01:00
Daniel Steigman 3c1327b115 fix(cli): Fix config set overwriting all settings instead of merging (#6980)
* fix(cli): Fix config set overwriting all settings instead of merging

ENG-1115

This fix resolves the issue where 'cline config set' would overwrite all
settings instead of merging with existing values.

Changes:
1. CLI (cli/pkg/cli/config.go): Changed setCommand to use
   UpdateSettingsPartial() instead of UpdateSettings()

2. Server (src/core/controller/state/updateSettingsCli.ts): Added defensive
   checks for defaultTerminalProfile to prevent undefined errors

3. Added merge.go with proper settings merge logic

The fix follows git-style behavior where 'cline config set key=value'
merges with existing settings, preserving all other values.

Tested and verified:
- Setting max-requests to 999 works
- Setting edit-files-externally preserves max-requests
- Multiple successive config sets preserve all previous values

* fix: use optional proto bools to fix config set field overwrite

Root cause: Non-optional bool fields in AutoApprovalSettings proto were
transmitting zero values (false) even when not set by user, causing the
server-side merge to overwrite existing settings.

Solution: Made 'enabled' and 'enableNotifications' fields optional in proto,
matching the pattern used by AutoApprovalActions fields. This allows proper
server-side merge detection using 'field !== undefined' checks.

Changes:
- proto/cline/state.proto: Added optional keyword to two bool fields
- cli/pkg/cli/task/settings_parser.go: Use boolPtr() for optional fields
- cli/pkg/cli/task/manager.go: Fix UpdateTaskAutoApprovalAction to use boolPtr()
- Removed client-side merge logic (merge.go, UpdateSettingsPartial method)
- Simplified config.go to use UpdateSettings() directly

This approach is simpler and more maintainable than the previous client-side
merge solution, relying on the existing server-side merge logic that already
handles undefined values correctly.

* fix: Address celestial-vault feedback on proto optional fields

- Remove unnecessary error suppression in updateSettingsCli.ts
- Make max_requests optional in AutoApprovalSettings proto
- Update Go parser to use int32Ptr() for optional max_requests
- Remove hardcoded MaxRequests default in UpdateTaskAutoApprovalAction

This prevents the CLI from overwriting user-configured maxRequests values
and maintains consistency with other optional fields (enabled, enableNotifications).

* fix: Remove error suppression for missing terminal manager in updateSettingsCli

Addresses celestial-vault's feedback on ENG-1115 PR.

Previously, the check 'if (controller.task && controller.task.terminalManager)'
silently suppressed errors when a task existed but terminalManager was missing.

Now properly throws an error if task exists without terminalManager (error case),
while allowing terminal profile updates when no task is running (normal case).
2025-10-29 17:40:10 -07:00
Sarah Fortune bb993e4a9a Remove duplicated code for getting the MCP catalog from the server (#7163)
Combine almost identitical methods for getting the MCP catalog.

We are always using silent=true, so remove the param and silent=false code path.
2025-10-29 17:01:15 -07:00
Sarah Fortune 473b3d0204 Update remote config schema to use URL for MCP ID (#7168) 2025-10-29 17:01:03 -07:00
Bee 7e68614631 refactor(ui): update BrowserSessionRow URL bar spacing (#7158)
* refactor(ui): update BrowserSessionRow URL bar spacing

Replace inline styles with Tailwind CSS classes for the URL bar component in BrowserSessionRow. This change:
- Removes the `urlTextStyle` CSSProperties object
- Converts inline style objects to Tailwind utility classes using `cn()`
- Maintains the same visual appearance and conditional styling
- Improves code maintainability and consistency with the project's styling approach

* width

---------

Co-authored-by: Jose R. Perez <trupix@gmail.com>
2025-10-29 16:10:34 -07:00
Bee a19e9907d5 fix(ui): hide tooltip arrow for auto approve menu item (#7157)
* fix(ui): hide tooltip arrow for auto approve menu item

Remove the arrow from the tooltip in AutoApproveMenuItem by setting showArrow={false} on TooltipContent. This improves the visual appearance of the tooltip for auto-approve action descriptions.

* clean up

---------

Co-authored-by: Jose R. Perez <trupix@gmail.com>
2025-10-29 16:03:02 -07:00
canvrno 19f74cac01 Add domain to openai compatible telemetry (#7170)
* OpenAI compatible domain telemetry

* Updated tests for openAiCompatibleDomain inclusion in telemtry
2025-10-29 15:51:50 -07:00
Bee b877abc708 fix(ui): align toolbar buttons and improve modal visibility control (#7159)
* fix(ui): align toolbar buttons and improve modal visibility control

- Remove inconsistent top margin (mt-0.5, mt-1) from toolbar buttons for consistent vertical alignment
- Add explicit `open` prop to ServersToggleModal Popover for better visibility state control
- Replace inline style with Tailwind class (mb-2.5) for consistent spacing
- Restructure ClineRulesToggleModal layout with proper flex container hierarchy
- Standardize icon sizing and remove redundant flex classes
- Remove trailing whitespace in ServersToggleModal

These changes ensure toolbar buttons align properly and modal visibility states are managed consistently across the chat interface.

* remove space

---------

Co-authored-by: Jose R. Perez <trupix@gmail.com>
2025-10-29 15:12:48 -07:00
Thibault Jaigu c90e64e763 feat: add Requesty OAuth with custom base URL support (#6953)
* feat: add Requesty OAuth with custom base URL support

* fix: changing base url to undefined when user unselected base url checkbox

* chore: adding change set

* chore: minor style changes to requesty provider

---------

Co-authored-by: John Costa <john@requesty.ai>
2025-10-29 14:47:11 -07:00
Bee e02e1eca7f adjust see more styles (#7150) 2025-10-29 14:44:48 -07:00
Bee f8925fb8fd fix: improve CheckpointError layout and Alert component flex behavior (#7148)
* fix: improve CheckpointError layout and Alert component flex behavior

- Add explicit sizing and spacing classes to AlertTriangleIcon in CheckpointError
- Refactor Alert component to use flex layout instead of absolute positioning
- Reduce padding from p-4 to p-2 and adjust gap spacing for tighter layout
- Remove absolute positioning from dismiss button and icon elements
- Update AlertTitle to use grow class for proper flex behavior

This fixes layout issues with the CheckpointError component on flex containers by replacing absolute positioning with flexbox, resulting in more predictable and responsive behavior.

* alert styles

* title
2025-10-29 14:44:39 -07:00
Zhongying Qiao ac81aeaf4e feat: Add banner api calls and evaluate banner display rule sets locally (#7087)
* scaffold banner api calls

* feat: add basic json rules local evaludation

* use another way to fetch ide name and version

* refine rules as well as rules evaluations in BannerService

* refactor: refactor BannerService so it is unit testable

* feat: add tests to banner service

* refactor: use separate initialize and get for banner service

* refactor: use Logger.error to log errors

* refactor: rename personal to personal only

* fix: move import to the top

* fix: remove unnecessary  wrapper

* move banner initialization to common.ts

* fix test

* fix: move import statement to the top in common.ts

* fix: remove feature targeting from rulesets

* fix: remove this._controller check in BannerService

* fix: add getProviderName in AuthService

* fix: include the owner role, treat it same as admin in applying banner rules
2025-10-29 14:03:33 -07:00
Ara f12b5a1573 fix: Removing language selection from voice mode to support aqua voice (#7086)
* fix: Removing language selection from voice mode to support aqua voice

* fix: Removing language selection from voice mode to support aqua voice

* fix: Removing language selection from voice mode to support aqua voice

* fix: bug fixes

* fix: bug fixes
2025-10-29 13:29:22 -07:00
Tomás Barreiro a66b57ef51 Add MCP settings to the remote config (#7123)
* Add MCP settings to the remote config

* Refactor MCP server schema

* Add model list to the root remote config

* Add tests

* Add type
2025-10-29 12:20:29 -07:00
Tomás Barreiro 944ed41f4a Add headers to the OTEL remote settings (#7149) 2025-10-29 16:36:31 +01:00
canvrno 970e941e57 Remove <think> tags for models that prefer this tag over <thinking> (#7144) 2025-10-28 20:24:46 -07:00
Bee 45abe977e4 update: use diff edit tools for gpt-5 (#7147)
GPT-5 with native tool calling support has updated to use the new apply_patch tool. Before we can confirm the new tool works consistently, the old GPT-5 variant without native tool calling enabled should continue to use the current file edit tools.
2025-10-28 18:10:30 -07:00
Bee 5755b30bce refactor: migrate ModelDescriptionMarkdown to Tailwind CSS (#7145)
Replace styled-components with Tailwind CSS classes in ModelDescriptionMarkdown component. Remove react-remark dependency in favor of simpler rendering approach. Update component to use shadcn/ui Button component instead of VSCodeLink for "See more" action. Add displayName for better debugging. Remove duplicate and unused styled markdown implementation from OpenRouterModelPicker and RequestyModelPicker.

This change improves consistency with the project's UI component library and codebase clean up.

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-10-28 17:51:26 -07:00
Bee 5fde21dffd native tool calling (#6921)
* (WIP)feat(api): add tool calling support to openai API handlers

Add ChatCompletionTool parameter to createMessage interface and implement tool calling functionality across API providers. Includes new StreamingToolCallHandler for processing tool call deltas and utility functions for converting tool specifications to OpenAI format.

* Replace path with absolutePath

* Simplify tool spec

* Parse stream and use variant matcher

* Simplify StreamingToolCallHandler

* Add CLINE_NEXT_GEN

* Enable parallel_tool_calls

* ApplyPatchHandler

* Simplify ApplyPatchHandler

* store tool use chunk

* feat(anthropic): add streaming tool use support with OpenAI-compatible format

Add comprehensive support for streaming tool use blocks from Anthropic API:

- Add tool_use case handler in AnthropicHandler to convert streaming tool
  calls to OpenAI-compatible format during content block start events
- Implement ToolUseHandler class to accumulate and parse streaming tool use
  deltas with incremental JSON parsing support
- Add proper handling for undefined tool message content in openai-format
  converter to prevent errors
- Integrate ToolUseHandler into TaskState for managing streaming tool calls
  across the request lifecycle

This enables real-time processing of tool calls as they stream from Anthropic's
API while maintaining compatibility with OpenAI's tool calling format.

* feat(anthropic): add tool support with OpenAI tool conversion

Add support for passing tools to Anthropic API with automatic conversion from OpenAI tool format. Implements tool_choice configuration to force tool usage when tools are provided.

Changes:
- Add tools parameter to createMessage method in AnthropicHandler
- Implement openAIToolToAnthropic converter to transform OpenAI ChatCompletionTool format to Anthropic Tool format
- Set tool_choice to "any" when tools are provided to ensure Claude uses one of the available tools
- Fix tool result handling to avoid invalid tool_use_id "cline" by converting to text blocks for backward compatibility
- Add documentation for tool_choice options (none, auto, any)

This enables proper tool calling functionality with Anthropic models while maintaining compatibility with the existing OpenAI tool definitions.

* oops

* fix: remove .mjs extensions from import paths

Remove `.mjs` file extensions from OpenAI and Anthropic SDK import paths to ensure compatibility with current module resolution. This updates imports across multiple files:
- openai/resources/chat/completions.mjs → openai/resources/chat/completions
- @anthropic-ai/sdk/resources/index.mjs → @anthropic-ai/sdk/resources/index

* revert styles.css change from merge main

* Update config and fix tests

* gpt-5 variant for cline provider only

* Fix GPT-5 web fetch tool

* Add native next gen for supported openai providers

* add native tool calling support to more openai comp providers

* list supported providers for gpt-5

* clean up

* Update ApplyPatchHandler

* clean up

* Update ask_followup_question

* Add system prompt test for cline claude 4.5 sonnet

* noToolsUsed

* Add support to gemini api provider

* add native tool calling to more providers

* Skip XML parsing

* clean up

* add back TASK_PROGRESS_PARAMETER to native tools

* feature flag NATIVE_TOOL_CALLS_NEXT_GEN_MODELS

* add allowNativeToolCalls to system prompt context

* add grok-code

* feat(context): ensure tool_result blocks follow tool_use blocks

Add validation to ensure every tool_use block in assistant messages has a corresponding tool_result in the following user message. When tool_result blocks are missing, they are automatically added with "result missing" content to maintain proper message pairing required by the Anthropic API.

This prevents API errors caused by orphaned tool_use blocks and ensures conversation history integrity after context management operations.

* feat(task): refactor ApplyPatchHandler to use FileEditProvider with revert support

Refactor the ApplyPatchHandler to use FileEditProvider and DiffViewProvider
for file operations instead of direct filesystem access. This change adds:

- State tracking for applied commits to enable rollback functionality
- Automatic revert of changes when user denies approval or errors occur
- Enhanced response format that includes full file contents after patching
- Consistent file reading through DiffViewProvider for better editor integration
- Proper cleanup of applied state after successful operations

This improves the patch application workflow by ensuring changes can be
safely reverted and provides better visibility into the applied changes.
The refactoring also aligns file operations with the existing editor
integration patterns used elsewhere in the codebase.

* Finalizing...

* MCP tools converter

* remove ClineDefaultTool.MCP_USE

* Update snapshot

* comments

* use diffViewProvider

* Fix MCP tool use with images

* fix mcp tool converter with reserved keys

* Fix apply patch streaming issue

* add vercel-ai-gateway to supported provider & clean up

* simplfy apply patch handler

* Turn off thinking when native tools are enabled for anthropic provider

* clean up ToolUseHandler

* anthropic tool_choice logic

* improve tool result ordering

- Extract anthropic message mapping logic into a separate variable for better readability
- Add MessageParam import from Anthropic SDK
- Update ensureToolResultsFollowToolUse to maintain tool_result order matching tool_use blocks
- Improve documentation to clarify tool result ordering requirements

This refactoring improves code maintainability by separating the message transformation logic and ensures tool results are properly ordered to match their corresponding tool use blocks in the conversation flow.
<budget:token_budget>200000</budget:token_budget>

* refactor tool result validation for improved efficiency

Simplify ensureToolResultsFollowToolUse method by streamlining the logic
for validating and reordering tool_result blocks. Changes include:

- Use early returns to reduce nesting and improve readability
- Process tool results and other blocks in a single pass instead of multiple iterations
- Eliminate unnecessary deep cloning by mutating content array directly
- Add flag-based update detection to avoid unnecessary array rebuilding
- Simplify the reordering logic while maintaining the same validation rules

This refactoring maintains the same functional behavior (ensuring tool_result
blocks follow their corresponding tool_use blocks) while reducing complexity
and improving performance through more efficient array operations.
<budget:token_budget>200000</budget:token_budget>

* attempt_completion

* use helper function

* remove FreeModelIDs from merge conflicts

* clean up

* Add NATIVE_GPT_5 & exclude gpt-5-chat

* shouldAutoApproveToolWithPath for apply patch

* add openai-native to supported provider

* add lightweight incremental chunk extraction

* update apply patch handler

* revert version regex

* Fix browser action
2025-10-28 17:49:19 -07:00
canvrno cf9f2a8630 Package updates (#7001) 2025-10-28 17:45:19 -07:00
nihar-oracle 5a3416ff09 Feat/nturumel/proto-python (#7090)
* feat(proto-python): add script-generated Python gRPC stubs, Go-like client, docs, and PyPI publish workflow

- add scripts/build-python-proto.mjs

  - invokes python -m grpc_tools.protoc over proto/**/*.proto
  - outputs to src/generated/grpc-python
  - mirrors Go layout under client/: connection.py, cline_client.py, services/_client.py
  - supports PYTHON env override (use a venv interpreter easily)
  - generates src/generated/grpc-python/pyproject.toml so output can be pip installed (pip install -e src/generated/grpc-python)

- package.json

  - add protos-python script to run the generator

- docs

  - add docs/exploring-clines-tools/python-protos.mdx with venv setup, generation steps, and import examples
  - emphasize: everything in src/generated is produced by scripts (do not commit manual edits)

- CI: publish to PyPI only

  - add .github/workflows/publish-grpc-python.yml
  - workflow generates code via script, builds wheel/sdist from src/generated/grpc-python, and uploads to PyPI
  - requires repo secret: PYPI_API_TOKEN (TWINE_USERNAME=__token__)
  - optional version override input for workflow_dispatch

Notes:

- generation strictly produces all content under src/generated/grpc-python (including pyproject.toml)
- default package name in generated pyproject is cline-grpc-python (adjustable in the script if needed)
- recommended usage on macOS: PYTHON=/Users/nturumel/projects/oracle-github/cline/.venv-proto/bin/python npm run protos-python

* Delete .github/workflows/publish-grpc-python.yml

* Delete docs/exploring-clines-tools/python-protos.mdx

* Update tired-banks-show.md

---------

Co-authored-by: Andrei Eternal <206184+Garoth@users.noreply.github.com>
2025-10-28 17:43:05 -07:00
Bee 826b2b1276 refactor: replace HeroUI Alert with shadcn Alert component (#7137)
* refactor: replace HeroUI Alert with shadcn Alert component

Replace HeroUI Alert component with a new custom Alert component in CheckpointError. The new component provides better control over styling and behavior, removes the dismiss functionality from CheckpointError, and uses a cleaner structure with AlertTitle and AlertDescription. This change improves consistency with the UI design system and simplifies the error display logic.

* Add storybook for TaskHeader with CheckpointError

* clean up
2025-10-28 17:29:51 -07:00
Kevin Taylor 8da38b2a2e Add Cerebras GLM 4.6 model support and set as default (#7143)
* Add Cerebras GLM 4.6 model support and set as default

- Add zai-glm-4.6 model configuration with 128k context window
- Set GLM 4.6 as default Cerebras model (replacing qwen-3-coder-480b-free)
- Configure 40k max tokens and 2,000 tokens/s performance

* add changeset
2025-10-28 14:22:31 -07:00
Saoud Rizwan cb121170cb Revise tip for opening Cline on the right (#7122)
Updated the tip section to provide a link for opening Cline in the sidebar.
2025-10-28 13:55:23 -07:00
Bee e9d2d344c6 fix: stop event propagation on task header button clicks (#7139)
Prevent default actions and stop event propagation on button clicks to avoid unintended behavior.
2025-10-28 12:19:13 -07:00
Daniel Steigman 69fb954a6c feat: Set default OpenTelemetry exporters to console,otlp (#7129)
* feat(ci): configure OpenTelemetry exporters for production

Set default OpenTelemetry log and metric exporters to console,otlp in both nightly and release workflows to ensure proper telemetry collection in production environment.

* ci: remove OTEL_METRIC_EXPORT_INTERVAL from publish workflows

Removed the OTEL_METRIC_EXPORT_INTERVAL environment variable from both nightly and stable publish workflows as it's no longer needed for the publishing process.
2025-10-28 11:31:11 -07:00
Bee e9e616e317 Replace HeroUI Tooltip with shadcn (#6872)
* Set up Tailwind v4

npx @tailwindcss/upgrade                                                                                                                                   1 ↵
≈ tailwindcss v4.1.13

│ Searching for CSS files in the current directory and its subdirectories…

│ Migrating stylesheets…

│ ↳ Migrated stylesheet: `./src/index.css`

│ Updating dependencies…

│ ↳ Updated package: `tailwindcss`

│ ↳ Updated package: `@tailwindcss/vite`

│ Migrating templates…

│ ↳ Migrated templates for: `./src/index.css`

│ Verify the changes and commit them to your repository.

* Migrate HeroUITooltip to radix-ui shadcn components

* import main.css

* Update e2e test text

* clean up

* Update mode switch test

* Fix auto approve modal z-index number

* Unify styles with theme

* fix spacing and sizes

* update logo id

* Fix e2e test

* Clean up

* npm install tailwindcss @tailwindcss/vite

* npx @tailwindcss/upgrade
≈ tailwindcss v4.1.14

│ ↳ Upgrading from Tailwind CSS `v4.1.14`

│ Searching for CSS files in the current directory and its subdirectories…

│ Migrating stylesheets…

│ ↳ Migrated stylesheet: `./webview-ui/src/index.css`

│ Updating dependencies…

│ ↳ Updated package: `tailwindcss`

│ ↳ Updated package: `@tailwindcss/vite`

│ Migrating templates…

│ ↳ Migrated templates for: `./webview-ui/src/index.css`

│ Verify the changes and commit them to your repository.

* clean up

* clean up

* Remove DRY code and update descriptionForeground class name

* Update TaskHeader classnames

* unify font size

* clean up

* update test with clear test id

* size

* Fix tooltip trigger in settings

* Apply feedback - hide arrow for autoapprove menu

* Align chat toolbox icon stylings

* update data-testid

* set

* CheckpointError

* feat: arrow alignment issues

* text-wrap tooltip

* feat: mcp tooltip arrow fix

---------

Co-authored-by: Jose R. Perez <trupix@gmail.com>
2025-10-28 09:15:29 -07:00
canvrno 545ac29e07 GPT5 system prompt snapshots + small adjustment. (#7120)
* gpt5 system prompt adjustments

* changeset
2025-10-27 15:19:47 -07:00
canvrno d38489aebc glm-4.6 system prompt adjustments (#7121) 2025-10-27 14:35:43 -07:00
github-actions[bot] 8d47026640 v3.34.1 Release Notes (#7061)
- 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

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-27 13:49:59 -07:00
Ara 268cd5c527 feat(settings): allow Minimax models with :free suffix for Cline provider (#7119)
Update OpenRouterModelPicker to include Minimax M2 models even when they
have the :free suffix. Previously, all :free models were filtered out for
the Cline provider, but Minimax models should be available regardless of
their pricing tier to ensure users have access to these specific models.
2025-10-27 13:01:59 -07:00
Ara c7c4e43322 Remove old models (#7118)
* refactor: remove CodeSupernova model and related code

Remove the deprecated cline/code-supernova-1-million model from the codebase:
- Delete clineCodeSupernovaModelInfo export from shared API
- Remove CodeSupernova model entry from CLINE_STEALTH_MODELS
- Remove CodeSupernova announcement UI and related state management
- Clean up unused imports (EmptyRequest, AccountServiceClient)
- Update import formatting in refreshOpenRouterModels

This model is no longer supported and has been replaced by other offerings.

* hel

* fix: bug fixes
2025-10-27 12:26:09 -07:00
Maosghoul aae9d432fd Feat: Add MiniMax AI provider (#7094)
* feat: add minimax ai

* feat: api

* feat: mmx

* feat: name

* feat: model name

* feat: fix

* feat: fix

* feat: add model info

* feat: format code

* feat: format code

* feat: code

* fix: log

* feat: param

* feat: add m2

* feat: add m2

* feat: format code

* feat: info

---------

Co-authored-by: xiaose <xiaose@minmaxi.com>
2025-10-27 11:18:34 -07:00
Saoud Rizwan 604dbd7bb0 fix: error_retry message breaking browser session row flow (#7106) 2025-10-26 09:43:01 -07:00
Bee 062a32f93d fix(scripts): fix proto-lint script execution on Windows (#7089)
* fix(scripts): fix proto-lint script execution on Windows

On Windows, directly calling 'scripts/proto-lint.sh' fails because it's not recognized as an internal or external command. This change wraps the script in an npm run command to ensure cross-platform compatibility. Added a new 'lint:proto' script for better organization.

* Update lint:proto script path to use relative path

* bash
2025-10-24 19:57:12 -07:00
canvrno 535b29f465 Support OpenRouter presets entry (#7083) 2025-10-24 17:18:59 -07:00
nihar-oracle a8027dc570 feat: Adding oracle code assist to the cli (#7004)
wip:

wip:

wip:

fix: Adding oca auth state instead of using model id check

fix: Adding oca auth state instead of using model id check

chore: Undoing debug changes
2025-10-24 14:50:37 -07:00
Toshii 978a8a0aa6 update e2e evals to use cline cli (#6977)
* remove un-implemented tests and create foundation for running cline in cli for exercism

* running version for python language

* remove unused code and reorder benchmark adapter

* remove optional helper functions from BenchmarkAdapter

* unskipping tests for java and javascript

* updating db schema

* updating output to match schema

* functional tests for all languages

* clean up unused commit and stored result

* nits

* small changes to wording

* adding to the test outputs

* using stdin for cline task send

* adding results dir to gitignore

* updating readme

* small nits for readme
2025-10-24 09:21:21 -07:00
Sarah Fortune 0cd462a414 Add linter check for proto files and add autoformatting (#7066)
Add a linter check for proto files to avoid issues like https://github.com/cline/cline/pull/7054
Format the proto files while linting
2025-10-23 14:37:26 -07:00
canvrno 65dbd85a92 Updating trending model list (#7018)
* Updating trending model list

* exacto
2025-10-23 14:33:00 -07:00
Tomás Barreiro ee1bb2f788 Support Feature Flags default values (#7027)
* Support Feature Flags default values

* Update src/services/feature-flags/FeatureFlagsService.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

* Update FeatureFlag support for unknown values

* refactor isFeatureFlagEnabeld

---------

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
2025-10-23 23:30:09 +02:00
celestial-vault 6f69ffb16f Remove apiConfiguration conversion function from updateApiConfiguration (#7045)
* remove massive conversion function and only convert what's needed

* add modelinfo conversion for all providers
2025-10-23 10:47:59 -07:00
canvrno f91769bda7 Fixed proto name issue (#7054) 2025-10-23 09:05:05 -06:00
Juan Pablo Flores 7692adacf5 Claude docs update and fixing missing images (#7041)
* Remove Windows setup accordion and streamline instructions for finding Claude Code path

* fix: update image source for Cline chat prompt to use a public URL
2025-10-22 19:43:32 -07:00
Ara a98faf5af4 fix: Removing Eslint from package lock json (#7047) 2025-10-22 19:32:59 -07:00
github-actions[bot] e3f4ce618f Changeset version bump (#7037)
* v3.34.0 Release Notes

- 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.

* fix: Adding Fallbacks

* fix: Adding Fallbacks

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2025-10-22 17:35:54 -07:00
canvrno dcf519d2f7 GLM 4.6 prompt changes (#7046)
* GLM 4.6 prompt changes

* GLM MCP prompt tweaks

* Update src/core/prompts/system-prompt/__tests__/__snapshots__/zai_glm_4_6-no-browser.snap

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* snapshot update

* snapshot update again

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-10-22 16:42:45 -07:00
pashpashpash 3ef4aea0f7 added opinionated preferences for open source model providers (#7020)
* added opinionated preferences for open source model providers

* moving to apits instead of refreshopenroutermodels

* aras recommendations

* zai fix

* changed name and removed free models

* fix: Adding Fallbacks

---------

Co-authored-by: Arafatkatze <arafat.da.khan@gmail.com>
2025-10-22 16:24:54 -07:00
canvrno a820026e0b Multiple CLI auth wizard changes (#7005) 2025-10-22 13:06:23 -07:00
Tomás Barreiro 29d1b0507c Update stored WorkOS Auth Data after refreshing it (#7029)
* Update stored Auth Data after refreshing it

* Update src/services/auth/providers/ClineAuthProvider.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

---------

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
2025-10-22 19:17:26 +02:00
Ara 929d13a4dd Fix(task): use background terminal for subagent command execution (#7017)
* refactor(task): use background terminal for subagent command execution

Replace VSCode terminal with StandaloneTerminalManager for CLI subagent
commands to enable hidden background execution. Falls back to standard
TerminalManager if standalone module is unavailable.

This change allows subagent commands to run in a background terminal
instead of visible VSCode terminals, improving user experience by
reducing terminal clutter during subagent operations.

* fix: added links

* Update src/core/task/index.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

* Update src/core/task/index.ts

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>

---------

Co-authored-by: Bee <68532117+abeatrix@users.noreply.github.com>
2025-10-22 10:09:03 -07:00
Bee c729e8c7c6 feat: expandable long task header (#6966)
* fix: add Read More for long task in header

- Truncate task description to first 3 lines by default and add a
  Read More/Show Less toggle to expand/collapse the full text
- Compute highlighted text based on expansion state; introduce local
  isHighlightedTextExpanded state
- Increase task details container max height (max-h-20 -> max-h-80) for
  better readability when expanded
- Remove unused useAutoCondense from context destructuring

Improves UX by preventing long task text from overwhelming the UI while
giving users control to view more when needed. Also includes minor cleanup.

* update

* Set to 25vh instead

* highlightText

* feat(ui): task text expansion with click-outside collapse

- Replace "Read More/Show Less" button with click-to-expand interaction
- Add click-outside listener to automatically collapse expanded text
- Apply gradient mask to truncated text for better visual indication
- Optimize rendering by removing conditional text highlighting
- Refactor layout to use single container with dynamic height constraints

This improves UX by making text expansion more intuitive and reducing visual clutter from the toggle button.

* update changeset

---------

Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-10-22 10:05:24 -07:00
celestial-vault 737452b2b1 wire open command with -s flag to use tasksettings (#7016) 2025-10-22 09:32:41 -06:00
Sarah Fortune ba6a72cf15 Update the remote config when the user logs in (#7025)
* Update the remote config when the user logs in

Subscribe to changes in the auth state, and fetch the remote config when the user logs in.

Move the error handling into `fetchRemoteConfig` to remove duplication.

* Update src/core/storage/remote-config/fetch.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-10-22 09:31:15 -06:00
AJ Juaire 65a0c35163 Add Qwen 3 Coder models to Amazon Bedrock models (#7022)
* Add Qwen 3 Coder models to Amazon Bedrock models

* Update comments to reference qwen

* Update cost.ts to round to avoid flakey tests

* remove math.round
2025-10-21 20:10:34 -07:00
Tomás Barreiro 0c8e02c6e4 Fetch the complete user info when the user logs in using WorkOS (#7026)
* Fetch the complete user info when the user logs in using WorkOS

* Add changeset

* fallback to token data
2025-10-22 03:29:52 +02:00
pashpashpash b636018ef5 fixing no-tty / stdin + standardizing color codes in plain mode (#6992)
* terminal shift enter support

* not needed

* detecting windows

* removing enhancedkeyboard

* removing enhanced keyboard

* ghostty

* proper ghostty support

* docs for posterity

* better logging

* removing md

* doctor command

* adding arguments for sync/async for doctor and keyboard setup - and moved keyboard setup to doctor command

* doctor help

* cleaning up logging and making things more explicit

* language and positioning

* standardizing color codes in plain mode

* wow even more hidden rendering - removed

* fixing stdin for restrictive shell environments
2025-10-21 13:22:20 -07:00
Alex Ker cfc6b0d7f5 added ZAI GLM4.6 to static models list and set as default (#6989)
* added ZAI GLM4.6 to static models list and set as default

* changest

---------

Co-authored-by: AlexKer <AlexKer@users.noreply.github.com>
2025-10-21 13:06:11 -06:00
Bee f116a6323d refactor: simplify BedrockProvider UI code (#7015)
* refactor(settings): migrate BedrockProvider to Tailwind and extract constants

- Extract Claude models and AWS regions into reusable constants
- Add className prop support to DebouncedTextField component
- Replace inline styles with Tailwind CSS classes throughout BedrockProvider
- Improve code maintainability and consistency with modern styling approach

This refactoring improves code organization by moving hardcoded lists to
constants and standardizes the styling approach across the settings UI
components.

* lock icons
2025-10-21 11:32:28 -07:00
Toshii 46d3b2a3ed auto compact updates (#6909)
* update prompting around first task message and summarization prompt and add file read parsing

* replace first user message to handle issue of refousing on old task after condense

* add line about focusing on initial task for history

* adding prompting around our removing of context history and verbosity

* prompting changes

* fix spelling nit in prompting

* update displayPath,absolutePath logic to match read file tool handler

* increment the auto approval usage
2025-10-20 17:04:44 -07:00
pashpashpash 9679917532 cline doctor command: terminal shift enter support + auto updates (#6883)
* terminal shift enter support

* not needed

* detecting windows

* removing enhancedkeyboard

* removing enhanced keyboard

* ghostty

* proper ghostty support

* docs for posterity

* better logging

* removing md

* doctor command

* adding arguments for sync/async for doctor and keyboard setup - and moved keyboard setup to doctor command

* doctor help

* cleaning up logging and making things more explicit

* language and positioning
2025-10-20 16:47:07 -07:00
canvrno 89bf81f7f6 Package updates (#6991)
* tar-fs

* playwright

* mammoth
2025-10-20 13:42:51 -07:00
canvrno afe01df8b4 Added new AWS SE regions (#6990) 2025-10-20 11:26:47 -07:00
celestial-vault eb1325686e separate core and rpc wrappers for refresh models (#6981)
* separate core and rpc wrappers for cleaner calling on the extension

* tweak jsdoc strings

* fix function name error go code
2025-10-20 12:20:46 -06:00
Ara 7c7962ce0f fix: Solve the issue where the notch on the terminal doesn't have full visibility (#6972) 2025-10-20 09:46:34 -07:00
celestial-vault 0707df2205 add cline provider (#6927) 2025-10-19 19:22:14 -06:00
celestial-vault ca87c21b77 use setglobalstatebatch for refresh models instead of setapiconfiguration (#6971) 2025-10-18 11:05:41 -07:00
CandiedUniverse d6f736e8d5 feat(hooks): Implement TaskCancel hook (#6962) 2025-10-17 18:24:23 -07:00
CandiedUniverse 4336471d84 feat(hooks): Implement TaskResume hook (#6928) 2025-10-17 18:23:50 -07:00
Bee 3191e23c1d fix(dev): deauth user on env changed (#6969) 2025-10-17 16:47:04 -07:00
Ara c5f12b8dc6 Fixing banner to not show CLI release for windows users (#6942)
* Fixing banner to not show CLI release for windows users

* Fixing banner

* Fixing banner
2025-10-17 11:36:54 -07:00
celestial-vault b21ff1e44a remove unused ApiConfiguration proto message (#6941) 2025-10-17 11:11:30 -07:00
celestial-vault 2860ffe147 add auto approve option to interactive (#6937)
* add auto approve option to interactive

* removing redundant options

---------

Co-authored-by: pashpashpash <nik@nugbase.com>
2025-10-17 10:25:01 -07:00
Bee 2b25ef63b5 chore: remove unnecessary debug logs for cline env (#6960)
Remove unnecessary console.info debug statements from config methods and fix log message formatting. Add explicit "no-op" case to TelemetryProviderFactory for cleaner telemetry provider selection logic.
2025-10-17 09:59:20 -07:00
pashpashpash e70d60d5c4 fixing duplicate ask headers for tool approvals, and fixing ask statestream not waiting for partial=false (#6945) 2025-10-16 23:40:37 -07:00
Ara bddbea04ef fix: Disable subagents for jetbrains (#6933)
* fix: Disable subagents for jetbrains

* fix: Disable subagents for jetbrains
2025-10-16 21:15:40 -07:00
canvrno 176ccedb2a Updated banner to add that Linux is supported in Cline CLI (#6922)
* Updated banner to note Linux is supported in Cline CLI

* Updated settings for subagents to enable on Linux
2025-10-16 21:06:13 -07:00
pashpashpash 63276aba70 fixing bug where yolo mode didnt output body in plan mode (#6940)
Co-authored-by: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com>
2025-10-16 20:54:24 -07:00
CandiedUniverse ae0f2557fe Remove unneeded test that's slowing down the test suite (#6932) 2025-10-16 18:47:13 -07:00
celestial-vault 0d9909c80e partially update autoApprovalSettings (#6929)
* make autoApprovalSettings partially updatable

* typos

* make actions fields pointers since they are now optional
2025-10-16 15:17:53 -07:00
Juan Pablo Flores c0b4081a53 Enhance documentation: Add supported model providers and installation… (#6925)
* Enhance documentation: Add supported model providers and installation details for Cline CLI

* Refactor supported model providers list for clarity and consistency
2025-10-16 14:55:17 -07:00
637 changed files with 44038 additions and 9967 deletions
+10 -45
View File
@@ -1,54 +1,19 @@
#!/usr/bin/env bash
# PostToolUse Hook Example
#
# This hook runs AFTER a tool is executed. It can:
# 1. Observe tool results and outcomes
# 2. Add context for FUTURE tool uses via contextModification
# 3. Log or track tool usage patterns
#
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
# The tool has already completed when this hook runs.
# Read the hook input (JSON via stdin)
echo "PostToolUse running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
# Extract tool information
tool_name=$(echo "$input" | jq -r '.postToolUse.toolName // "unknown"')
parameters=$(echo "$input" | jq -r '.postToolUse.parameters // {}')
result=$(echo "$input" | jq -r '.postToolUse.result // ""')
success=$(echo "$input" | jq -r '.postToolUse.success // false')
execution_time=$(echo "$input" | jq -r '.postToolUse.executionTimeMs // 0')
for i in {1..5}; do
sleep 1
echo "$i"
done
# Example 1: Learning from file operations
# Track successful file creations to build context about project structure
# if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
# path=$(echo "$parameters" | jq -r '.path // ""')
# cat <<EOF
# {
# "shouldContinue": true,
# "contextModification": "FILE_OPERATIONS: Successfully created '$path'. Future operations should maintain consistency with this file's patterns and structure."
# }
# EOF
# exit 0
# fi
# Example 2: Performance monitoring
# Warn about slow operations
# if [[ "$execution_time" -gt 5000 ]]; then
# cat <<EOF
# {
# "shouldContinue": true,
# "contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms to complete. Consider optimizing future similar operations or breaking them into smaller steps."
# }
# EOF
# exit 0
# fi
# Example 3: Context injection for future tool uses
# The context will be available in the NEXT API request
cat <<EOF
{
"shouldContinue": true,
"contextModification": "TOOL_RESULT: The tool '$tool_name' completed with success=$success. Consider validating the results before proceeding to the next step."
"cancel": false,
"contextModification": "PostToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PostToolUse hook custom errorMessage"
}
EOF
+10 -33
View File
@@ -1,42 +1,19 @@
#!/usr/bin/env bash
# PreToolUse Hook Example
#
# This hook runs BEFORE a tool is executed. It can:
# 1. Block execution by returning {"shouldContinue": false}
# 2. Add context for FUTURE tool uses via contextModification
# 3. Validate tool parameters
#
# IMPORTANT: Context injection affects FUTURE AI decisions, not the current tool execution.
# The tool parameters are already determined when this hook runs.
# Read the hook input (JSON via stdin)
echo "PreToolUse running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
# Extract tool information
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName // "unknown"')
parameters=$(echo "$input" | jq -r '.preToolUse.parameters // {}')
for i in {1..5}; do
sleep 1
echo "$i"
done
# Example 1: Validation - Block invalid operations
# Uncomment to prevent creating .js files in a TypeScript project
# if [[ "$tool_name" == "write_to_file" ]]; then
# path=$(echo "$parameters" | jq -r '.path // ""')
# if [[ "$path" == *.js ]]; then
# cat <<EOF
# {
# "shouldContinue": false,
# "errorMessage": "VALIDATION FAILED: Cannot create .js files in TypeScript project. Please use .ts extension instead.",
# "contextModification": "WORKSPACE_RULES: This is a strict TypeScript project. All new files must use .ts or .tsx extensions."
# }
# EOF
# exit 0
# fi
# fi
# Example 2: Context injection for future tool uses
# The context will be available in the NEXT API request after this tool completes
cat <<EOF
{
"shouldContinue": true,
"contextModification": "WORKSPACE_RULES: [For future tool uses] This is a TypeScript React project. When creating files, use .ts/.tsx extensions and include detailed comments explaining the purpose and usage of each function."
"cancel": false,
"contextModification": "PreToolUse response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "PreToolUse hook custom errorMessage"
}
EOF
+124 -76
View File
@@ -3,8 +3,8 @@
## Overview
Cline hooks allow you to execute custom scripts at specific points in the agentic workflow. Hooks can be placed in either:
- **Global hooks directory**: `~/Documents/Cline/Rules/Hooks/` (applies to all workspaces)
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to specific workspace)
- **Global hooks directory**: `~/Documents/Cline/Hooks/` (applies to all workspaces)
- **Workspace hooks directory**: `.clinerules/hooks/` (applies to the workspace the repo is part of)
Hooks run automatically when enabled.
@@ -17,17 +17,54 @@ Hooks run automatically when enabled.
## Available Hooks
### TaskStart Hook
- **When**: Runs when a NEW task is started (not when resuming)
- **Purpose**: Initialize task context, validate task requirements, set up environment
- **Global Location**: `~/Documents/Cline/Hooks/TaskStart`
- **Workspace Location**: `.clinerules/hooks/TaskStart`
### TaskResume Hook
- **When**: Runs when an EXISTING task is resumed (after user clicks resume button)
- **Purpose**: Validate resumed task state, restore context, check for changes since last run
- **Global Location**: `~/Documents/Cline/Hooks/TaskResume`
- **Workspace Location**: `.clinerules/hooks/TaskResume`
### TaskCancel Hook
- **When**: Runs when a task is cancelled or a hook is aborted by the user (only if there's actual active work or work was started)
- **Purpose**: Clean up resources, log cancellation, save state
- **Global Location**: `~/Documents/Cline/Hooks/TaskCancel`
- **Workspace Location**: `.clinerules/hooks/TaskCancel`
- **Note**: This hook is NOT cancellable
### TaskComplete Hook (coming soon!)
- **When**: Runs when a task is marked as complete
- **Purpose**: Log completion status, perform final cleanup, generate reports
- **Global Location**: `~/Documents/Cline/Hooks/TaskComplete`
- **Workspace Location**: `.clinerules/hooks/TaskComplete`
### UserPromptSubmit Hook
- **When**: Runs when the user submits a prompt/message (initial task, resume, or feedback)
- **Purpose**: Validate user input, preprocess prompts, add context to user messages
- **Global Location**: `~/Documents/Cline/Hooks/UserPromptSubmit`
- **Workspace Location**: `.clinerules/hooks/UserPromptSubmit`
### PreToolUse Hook
- **When**: Runs BEFORE a tool is executed
- **Purpose**: Validate parameters, block execution, or add context
- **Global Location**: `~/Documents/Cline/Rules/Hooks/PreToolUse` (all platforms)
- **Workspace Location**: `.clinerules/hooks/PreToolUse` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/PreToolUse`
- **Workspace Location**: `.clinerules/hooks/PreToolUse`
### PostToolUse Hook
- **When**: Runs AFTER a tool completes
- **Purpose**: Observe results, track patterns, or add context
- **Global Location**: `~/Documents/Cline/Rules/Hooks/PostToolUse` (all platforms)
- **Workspace Location**: `.clinerules/hooks/PostToolUse` (all platforms)
- **Global Location**: `~/Documents/Cline/Hooks/PostToolUse`
- **Workspace Location**: `.clinerules/hooks/PostToolUse`
### PreCompact Hook (coming soon!)
- **When**: Runs BEFORE the conversation context is compacted/truncated
- **Purpose**: Observe compaction events, log context management, track token usage
- **Global Location**: `~/Documents/Cline/Hooks/PreCompact`
- **Workspace Location**: `.clinerules/hooks/PreCompact`
## Cross-Platform Hook Format
@@ -37,13 +74,12 @@ Cline uses a git-style approach for hooks that works consistently across all pla
- **No file extensions**: Hooks are named exactly `PreToolUse` or `PostToolUse` (no `.bat`, `.cmd`, `.sh` etc.)
- **Shebang required**: First line must be a shebang (e.g., `#!/usr/bin/env bash` or `#!/usr/bin/env node`)
- **Executable on Unix**: On Unix/Linux/macOS, hooks must be executable: `chmod +x PreToolUse`
- **Windows**: No special permissions needed - hooks are executed through the shell
- **Windows**: Not currently supported.
### How It Works
Like git hooks, Cline executes hook files through a shell that interprets the shebang line:
- On Unix/Linux/macOS: Native shell execution with shebang support
- On Windows: Shell execution handles shebang interpretation
This means:
- ✅ Same hook script works on all platforms
@@ -55,16 +91,10 @@ This means:
**On Unix/Linux/macOS:**
```bash
# Create hook file
nano ~/Documents/Cline/Rules/Hooks/PreToolUse
nano ~/Documents/Cline/Hooks/PreToolUse
# Make executable
chmod +x ~/Documents/Cline/Rules/Hooks/PreToolUse
```
**On Windows:**
```batch
REM Create hook file (note: no file extension)
notepad %USERPROFILE%\Documents\Cline\Rules\Hooks\PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
```
## Context Injection Timing
@@ -107,11 +137,46 @@ All hooks receive:
```json
{
"clineVersion": "string",
"hookName": "PreToolUse" | "PostToolUse",
"hookName": "TaskStart" | "TaskResume" | "TaskCancel" | "TaskComplete" | "UserPromptSubmit" | "PreToolUse" | "PostToolUse" | "PreCompact",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskStart": { // Only for TaskStart
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"initialTask": "string"
}
},
"taskResume": { // Only for TaskResume
"taskMetadata": {
"taskId": "string",
"ulid": "string"
},
"previousState": {
"lastMessageTs": "string",
"messageCount": "string",
"conversationHistoryDeleted": "string"
}
},
"taskCancel": { // Only for TaskCancel
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"completionStatus": "string"
}
},
"taskComplete": { // Only for TaskComplete
"taskMetadata": {
"taskId": "string",
"ulid": "string"
}
},
"userPromptSubmit": { // Only for UserPromptSubmit
"prompt": "string",
"attachments": ["string"]
},
"preToolUse": { // Only for PreToolUse
"toolName": "string",
"parameters": {}
@@ -122,6 +187,11 @@ All hooks receive:
"result": "string",
"success": boolean,
"executionTimeMs": number
},
"preCompact": { // Only for PreCompact
"contextSize": number,
"messagesToCompact": number,
"compactionStrategy": "string"
}
}
```
@@ -131,38 +201,21 @@ All hooks receive:
All hooks must return:
```json
{
"shouldContinue": boolean, // Required: Allow or block execution
"contextModification": "string", // Optional: Context for future tool uses
"cancel": boolean, // Required: false to continue, true to block execution
"contextModification": "string", // Optional: Context for future AI decisions
"errorMessage": "string" // Optional: Error details if blocking
}
```
## Context Modification Format
Use structured prefixes to help the AI understand context type:
- `WORKSPACE_RULES:` - Project conventions and requirements
- `FILE_OPERATIONS:` - File creation/modification patterns
- `TOOL_RESULT:` - Outcomes of tool executions
- `PERFORMANCE:` - Performance concerns
- `VALIDATION:` - Validation results
- Custom prefixes as needed
Example:
```bash
cat <<EOF
{
"shouldContinue": true,
"contextModification": "WORKSPACE_RULES: This is a TypeScript project. All new files must use .ts or .tsx extensions."
}
EOF
```
**Note**: The `cancel` field works as follows:
- `false` (or omitted): Allow execution to continue
- `true`: Block execution and show error message to user
## Hook Execution Limits
- **Timeout**: Hooks must complete within 30 seconds
- **Context Size**: Context modifications are limited to 50KB
- **Error Handling**: Unexpected file system errors are propagated; expected errors (file not found, permission denied) are handled silently
- **Timeout**: Hooks must complete within 30 seconds (configurable via `HOOK_EXECUTION_TIMEOUT_MS`)
- **Context Size**: Context modifications are limited to 50KB (configurable via `MAX_CONTEXT_MODIFICATION_SIZE`)
- **Error Handling**: Expected errors (file not found, permission denied, not a directory) are handled silently; unexpected file system errors are propagated
## Common Use Cases
@@ -177,15 +230,15 @@ path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
cat <<EOF
{
"shouldContinue": false,
"cancel": true,
"errorMessage": "Cannot create .js files in TypeScript project",
"contextModification": "WORKSPACE_RULES: Use .ts/.tsx extensions only"
"contextModification": "Use .ts/.tsx extensions only"
}
EOF
exit 0
fi
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
```
### 2. Context Building - Learn from Operations
@@ -200,12 +253,12 @@ path=$(echo "$input" | jq -r '.postToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$success" == "true" ]]; then
cat <<EOF
{
"shouldContinue": true,
"contextModification": "FILE_OPERATIONS: Created '$path'. Maintain consistency with this file's patterns in future operations."
"cancel": false,
"contextModification": "Created '$path'. Maintain consistency with this file's patterns in future operations."
}
EOF
else
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
fi
```
@@ -220,12 +273,12 @@ tool_name=$(echo "$input" | jq -r '.postToolUse.toolName')
if [[ "$execution_time" -gt 5000 ]]; then
cat <<EOF
{
"shouldContinue": true,
"contextModification": "PERFORMANCE: Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
"cancel": false,
"contextModification": "Tool '$tool_name' took ${execution_time}ms. Consider optimizing future similar operations."
}
EOF
else
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
fi
```
@@ -239,7 +292,7 @@ input=$(cat)
echo "$input" >> ~/.cline/hook-logs/tool-usage.jsonl
# Allow execution
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
```
## Global vs Workspace Hooks
@@ -247,44 +300,40 @@ echo '{"shouldContinue": true}'
Cline supports two levels of hooks:
### Global Hooks
- **Location**: `~/Documents/Cline/Rules/Hooks/` (macOS/Linux) or `%USERPROFILE%\Documents\Cline\Rules\Hooks\` (Windows)
- **Location**: `~/Documents/Cline/Hooks/` (macOS/Linux)
- **Scope**: Apply to ALL workspaces and projects
- **Use Case**: Organization-wide policies, personal preferences, universal validations
- **Priority**: Execute FIRST, before workspace hooks
- **Priority**: Order not guaranteed when combined with workspace hooks
### Workspace Hooks
- **Location**: `.clinerules/hooks/` in each workspace root
- **Scope**: Apply only to the specific workspace
- **Use Case**: Project-specific rules, team conventions, repository requirements
- **Priority**: Execute AFTER global hooks
- **Priority**: Order not guaranteed when combined with global hooks
### Hook Execution
When multiple hooks exist (global and/or workspace):
- All hooks for a given step (PreToolUse or PostToolUse) are executed
- **Execution order is not guaranteed** - hooks may run concurrently
- If ALL hooks allow execution (`shouldContinue: true`), the tool proceeds
- If ANY hook blocks (`shouldContinue: false`), execution is blocked
- All hooks for a given step are executed **concurrently** using `Promise.all`
- **Execution order is not guaranteed** - hooks run in parallel
- If ALL hooks allow execution (`cancel: false`), the tool proceeds
- If ANY hook blocks (`cancel: true`), execution is blocked
**Result Combination:**
- `shouldContinue`: Must be `true` from ALL hooks for execution to proceed
- `contextModification`: All context strings are concatenated
- `errorMessage`: All error messages are concatenated
- `cancel`: If ANY hook returns `true`, execution is blocked
- `contextModification`: All context strings are concatenated with double newlines (`\n\n`)
- `errorMessage`: All error messages are concatenated with single newlines (`\n`)
### Setting Up Global Hooks
1. The global hooks directory is automatically created at:
- macOS/Linux: `~/Documents/Cline/Rules/Hooks/`
- Windows: `%USERPROFILE%\Documents\Cline\Rules\Hooks\`
- macOS/Linux: `~/Documents/Cline/Hooks/`
2. Add your hook script:
```bash
# Unix/Linux/macOS
nano ~/Documents/Cline/Rules/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Rules/Hooks/PreToolUse
# Windows
notepad %USERPROFILE%\Documents\Cline\Rules\Hooks\PreToolUse
nano ~/Documents/Cline/Hooks/PreToolUse
chmod +x ~/Documents/Cline/Hooks/PreToolUse
```
3. Enable hooks in Cline settings
@@ -294,18 +343,18 @@ When multiple hooks exist (global and/or workspace):
**Global Hook** (applies to all projects):
```bash
#!/usr/bin/env bash
# ~/Documents/Cline/Rules/Hooks/PreToolUse
# ~/Documents/Cline/Hooks/PreToolUse
# Universal rule: Never delete package.json
input=$(cat)
tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *"package.json"* ]]; then
echo '{"shouldContinue": false, "errorMessage": "Global policy: Cannot modify package.json"}'
echo '{"cancel": true, "errorMessage": "Global policy: Cannot modify package.json"}'
exit 0
fi
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
```
**Workspace Hook** (applies to specific project):
@@ -318,11 +367,11 @@ tool_name=$(echo "$input" | jq -r '.preToolUse.toolName')
path=$(echo "$input" | jq -r '.preToolUse.parameters.path // ""')
if [[ "$tool_name" == "write_to_file" && "$path" == *.js ]]; then
echo '{"shouldContinue": false, "errorMessage": "Project rule: Use .ts files only"}'
echo '{"cancel": true, "errorMessage": "Project rule: Use .ts files only"}'
exit 0
fi
echo '{"shouldContinue": true}'
echo '{"cancel": false}'
```
**All hooks must allow execution for the tool to proceed.** Hooks may execute concurrently.
@@ -331,7 +380,7 @@ echo '{"shouldContinue": true}'
If you have multiple workspace roots, you can place hooks in each root's `.clinerules/hooks/` directory. All hooks (global and workspace) may execute concurrently. Their results will be combined:
- **shouldContinue**: If ANY hook returns false, execution is blocked
- **cancel**: If ANY hook returns `true`, execution is blocked
- **contextModification**: All context modifications are concatenated
- **errorMessage**: All error messages are concatenated
@@ -352,7 +401,6 @@ If you have multiple workspace roots, you can place hooks in each root's `.cline
### Context Not Affecting Behavior
- Remember: context affects FUTURE decisions, not the current tool
- Use PreToolUse for validation (blocking) if you need immediate effect
- Ensure context modifications are clear and actionable
- Check that context isn't being truncated (50KB limit)
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
echo "TaskCancel running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
cat <<EOF
{
"cancel": false,
"contextModification": "TaskCancel response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskCancel hook custom errorMessage"
}
EOF
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
echo "TaskResume running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
cat <<EOF
{
"cancel": false,
"contextModification": "TaskResume response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskResume hook custom errorMessage"
}
EOF
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
echo "TaskStart running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
cat <<EOF
{
"cancel": false,
"contextModification": "TaskStart response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "TaskStart hook custom errorMessage"
}
EOF
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
echo "UserPromptSubmit running inside local cline/.clinerules/hooks/ directory"
input=$(cat)
echo $input | jq .
for i in {1..5}; do
sleep 1
echo "$i"
done
cat <<EOF
{
"cancel": false,
"contextModification": "UserPromptSubmit response from the local cline/.clinerules/hooks/ directory.",
"errorMessage": "UserPromptSubmit hook custom errorMessage"
}
EOF
+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-[var(--vscode-foreground)]",
title: "font-bold text-(--vscode-foreground)",
indicator:
"text-[var(--vscode-foreground)] mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
"text-(--vscode-foreground) mb-0.5 -rotate-180 data-[open=true]:-rotate-90 rtl:rotate-0 rtl:data-[open=true]:-rotate-90",
}}>
<ul style={ulStyle}>
<li>
+2 -3
View File
@@ -74,10 +74,9 @@ jobs:
CLINE_ENVIRONMENT: production
# OpenTelemetry production defaults (can be overridden at runtime)
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: ${{ secrets.OTEL_LOGS_EXPORTER }}
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
OTEL_LOGS_EXPORTER: console,otlp
OTEL_METRICS_EXPORTER: console,otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
run: npm run publish:marketplace:nightly
+2 -3
View File
@@ -99,12 +99,11 @@ jobs:
ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }}
# OpenTelemetry production defaults (can be overridden at runtime)
OTEL_TELEMETRY_ENABLED: ${{ secrets.OTEL_TELEMETRY_ENABLED }}
OTEL_LOGS_EXPORTER: ${{ secrets.OTEL_LOGS_EXPORTER }}
OTEL_METRICS_EXPORTER: ${{ secrets.OTEL_METRICS_EXPORTER }}
OTEL_LOGS_EXPORTER: console,otlp
OTEL_METRICS_EXPORTER: console,otlp
OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }}
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }}
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
OTEL_METRIC_EXPORT_INTERVAL: ${{ secrets.OTEL_METRIC_EXPORT_INTERVAL }}
run: |
# Required to generate the .vsix
vsce package --allow-package-secrets sendgrid --out "cline-${{ steps.get_version.outputs.version }}.vsix"
+3
View File
@@ -20,6 +20,9 @@ eslint-rules/**
.husky/**
.env
# cli
cli/**
# Custom
**/demo.gif
.nvmrc
+79 -1
View File
@@ -1,12 +1,90 @@
# Changelog
## 3.37.1
- cf8dd1c: Comprehensive changes to better support GPT 5.1 - System prompt, tools, deep-planning, focus chain, etc.
- 02abbcf: Add AGENTS.md support
- 855db7d: feat(models): Add free minimax/mimax-m2 model to the model picker
## [3.37.0]
## Added
- GPT-5.1 with model-specific prompting: tailored system prompts, tool usage, focus chain, and deep-planning optimizations
- Nous Research provider with Hermes 4 model family and custom system prompts
- Switched to Aqua Voice's Avalon model in speech to text transcription
- Added Linux support for speech to text
- Model-family breakouts for deep-planning prompting, laying groundwork for enhanced slash commands
- Expanded HTTP proxy support throughout the codebase
- Improved focus chain prompting for frontier models (Anthropic, OpenAI, Gemini, xAI)
## Fixed
- Duplicate tool results prevention through existence checking
- XML entity escaping in model content processor
- Commit message generation in command palette
- OpenAI Compatible provider temperature parameter type conversion
## Documentation
- Added missing proto generation step in CONTRIBUTING.md
- New `npm run dev` script for streamlined terminal workflow (fixes #7335)
## [3.36.1]
- fix: remove native tool calling support from Gemini and XAI provider due to invalid tool names issues
- fix: disable native tool callings for grok code models
- Add MCP tool usage to GLM
- Removes reasoning_details content field from Anthropic providers
## [3.36.0]
- Add: Hooks allow you to inject custom logic into Cline's workflow
- Add: new provider AIhubmix
- Add: Use http_proxy, https_proxy and no_proxy in JetBrains
- Fix: Oca Token Refresh logic
- Fix: issues where assistant message with empty content is added to conversation history
- Fix: bug where the checkbox shows in the model selector dropdown
- Fix: Switch from defaultUserAgentProvider to customUserAgent for Bedrock
- Fix: support for `<think>` tags for better compatibility with open-source models
- Fix: refinements to the GLM-4.6 system prompt
## [3.35.1]
- Add: Hicap API integration as provider
- Fix: enable Add Header button in OpenAICompatibleProvider UI
- Fix: Remove orphaned tool_results after truncation and empty content field issues in native tool call
- Fix: render model description in markdown
## [3.35.0]
- Add native tool calling support with configurable setting.
- Auto-approve is now always-on with a redesigned expanding menu. Settings simplified and notifications moved to General Settings.
- added zai-glm-4.6 as a Cerebras model
- Created GPT5 family specific system prompt template
- Fix: show reasoning budget slider to models with valid thinking config
- Requesty base URL, and API key fixes
- Delete all Auth Tokens when logging out
- Support for <think> tags for models that prefer that over <thinking>
## [3.34.1]
- Added support for MiniMax provider with MiniMax-M2 model
- Remove Cline/code-supernova-1-million model
- Changes to allow users to manually enter model names (eg. presets) when using OpenRouter
## [3.34.0]
- Cline Teams is now free through 2025 for unlimited users. Includes Jetbrains, RBAC, centralized billing and more.
- Use the “exacto” versions of GLM-4.6, Kimi-K2, and Qwen3-Coder in the Cline provider for the best balance of cost, speed, accuracy and tool-calling.
## [3.33.1]
- Fix CLI installation copy text
## [3.33.0]
- Added Cline CLI (Preview)
- Added Cline CLI (Preview)
- Added Subagent support (Experimental)
- Added Multi-Root Workspaces support (Enable in feature settings)
- Add auto-retry with exponential backof for failed API requests
+7 -1
View File
@@ -46,7 +46,11 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
```bash
npm run install:all
```
4. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
4. Generate Protocol Buffer files (required before first build):
```bash
npm run protos
```
5. Launch by pressing `F5` (or `Run`->`Start Debugging`) to open a new VSCode window with the extension loaded. (You may need to install the [esbuild problem matchers extension](https://marketplace.visualstudio.com/items?itemName=connor4312.esbuild-problem-matchers) if you run into issues building the project.)
@@ -85,8 +89,10 @@ We also welcome contributions to our [documentation](https://github.com/cline/cl
2. **Local Development**
- Run `npm run install:all` to install dependencies
- Run `npm run protos` to generate Protocol Buffer files (required before first build)
- Run `npm run test` to run tests locally
- Run → Start Debugging or `>Debug: Select and Start Debugging` and wait for a new VS Code instance to open
- **Terminal Workflow**: Use `npm run dev` (generates protos + runs watch mode) or `npm run watch` (if protos already generated)
- Before submitting PR, run `npm run format:fix` to format your code
3. **Linux-specific Setup**
+2 -2
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 \#1 on OpenRouter
# Cline
<p align="center">
<img src="https://media.githubusercontent.com/media/cline/cline/main/assets/docs/demo.gif" width="100%" />
@@ -43,7 +43,7 @@ Thanks to [Claude Sonnet's agentic coding capabilities](https://www.anthropic.c
4. When a task is completed, Cline will present the result to you with a terminal command like `open -a "Google Chrome" index.html`, which you run with a click of a button.
> [!TIP]
> Use the `CMD/CTRL + Shift + P` shortcut to open the command palette and type "Cline: Open In New Tab" to open the extension as a tab in your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
> Follow [this guide](https://docs.cline.bot/features/customization/opening-cline-in-sidebar) to open Cline on the right side of your editor. This lets you use Cline side-by-side with your file explorer, and see how he changes your workspace more clearly.
---
+13 -13
View File
@@ -70,7 +70,7 @@
"noControlCharactersInRegex": "off",
"noShadowRestrictedNames": "off",
"noArrayIndexKey": "info",
"noAssignInExpressions": "warn"
"noAssignInExpressions": "info"
},
"complexity": {
"noUselessConstructor": "off",
@@ -82,7 +82,7 @@
"noStaticOnlyClass": "off"
},
"security": {
"noDangerouslySetInnerHtml": "warn"
"noDangerouslySetInnerHtml": "info"
}
}
},
@@ -114,17 +114,17 @@
"files": {
"includes": [
"**",
"!**/dist/**",
"!**/dist-*/**",
"!**/out/**",
"!**/evals/**",
"!**/playwright/**",
"!**/test-results/**",
"!**/node_modules/**",
"!**/webview-ui/build/**",
"!**/generated/**",
"!**/proto/**",
"!**/tests/specs/**"
"!**/dist",
"!**/dist-*",
"!**/out",
"!**/evals",
"!**/playwright",
"!**/test-results",
"!**/node_modules",
"!**/webview-ui/build",
"!**/generated",
"!**/proto",
"!**/tests/specs"
]
},
"plugins": [
+16 -17
View File
@@ -101,10 +101,7 @@ see the manual page: man cline`,
if !isUserReadyToUse(ctx, instanceAddress) {
// Create renderer for welcome messages
renderer := display.NewRenderer(global.Config.OutputFormat)
markdown := "## hey there! looks like you're new here. let's get you set up"
rendered := renderer.RenderMarkdown(markdown)
fmt.Printf("\n%s\n\n", rendered)
fmt.Printf("\n%s\n\n", renderer.Dim("Hey there! Looks like you're new here. Let's get you set up"))
if err := auth.HandleAuthMenuNoArgs(ctx); err != nil {
// Check if user cancelled - exit cleanly
@@ -119,9 +116,7 @@ see the manual page: man cline`,
return fmt.Errorf("credentials still not configured - please run 'cline auth' to complete setup")
}
markdown = "## setup complete, you can now use the cline cli"
rendered = renderer.RenderMarkdown(markdown)
fmt.Printf("\n%s\n\n", rendered)
fmt.Printf("\n%s\n\n", renderer.Dim("Setup complete, you can now use the Cline CLI"))
}
} else {
// User specified --address flag, use that
@@ -187,6 +182,7 @@ see the manual page: man cline`,
rootCmd.AddCommand(cli.NewVersionCommand())
rootCmd.AddCommand(cli.NewAuthCommand())
rootCmd.AddCommand(cli.NewLogsCommand())
// rootCmd.AddCommand(cli.NewDoctorCommand()) // Disabled for now
if err := rootCmd.ExecuteContext(context.Background()); err != nil {
os.Exit(1)
@@ -331,17 +327,20 @@ func getContentFromStdinAndArgs(args []string) (string, error) {
// Check if data is being piped to stdin
if (stat.Mode() & os.ModeCharDevice) == 0 {
stdinBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("failed to read from stdin: %w", err)
}
stdinContent := strings.TrimSpace(string(stdinBytes))
if stdinContent != "" {
if content.Len() > 0 {
content.WriteString(" ")
// Only try to read if there's actually data available
if stat.Size() > 0 {
stdinBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("failed to read from stdin: %w", err)
}
stdinContent := strings.TrimSpace(string(stdinBytes))
if stdinContent != "" {
if content.Len() > 0 {
content.WriteString(" ")
}
content.WriteString(stdinContent)
}
content.WriteString(stdinContent)
}
}
+2 -2
View File
@@ -8,8 +8,10 @@ require (
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/huh v0.7.1-0.20251005153135-a01a1e304532
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
github.com/cline/grpc-go v0.0.0
github.com/glebarez/go-sqlite v1.22.0
github.com/muesli/termenv v0.16.0
github.com/spf13/cobra v1.8.0
golang.org/x/term v0.32.0
google.golang.org/grpc v1.75.0
@@ -24,7 +26,6 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
@@ -45,7 +46,6 @@ require (
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "cline",
"version": "1.0.0-nightly.18",
"version": "1.0.3",
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
"main": "cline-core.js",
"bin": {
@@ -20,7 +20,7 @@
"vscode-uri"
],
"engines": {
"node": ">=18.0.0"
"node": ">=20.0.0"
},
"keywords": [
"cline",
+25 -5
View File
@@ -6,18 +6,38 @@ import (
)
func NewAuthCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "auth",
Short: "Authenticate a provider and configure model used",
Long: `Authenticate a provider and configure model used
Short: "Authenticate a provider and configure what model is used",
Long: `Authenticate a provider and configure what model is used
This command opens an interactive menu where you can:
Interactive Mode:
Run without flags to open an interactive menu where you can:
- Sign in to your Cline account
- Configure other LLM providers (Anthropic, OpenAI, etc.)
- Select and switch between AI models
- Manage provider settings`,
- Manage provider settings
Quick Setup Mode:
Use flags to quickly configure a BYO provider non-interactively:
Examples:
cline auth --provider openai-native --apikey sk-xxx --modelid gpt-5
cline auth -p anthropic -k sk-ant-xxx -m claude-sonnet-4-5-20250929
cline auth -p openai-compatible -k xxx -m gpt-4 -b https://api.example.com/v1
Supported providers: openai-native, openai, anthropic, gemini, openrouter, xai, cerebras, ollama
Note: Bedrock provider requires interactive setup due to complex auth fields`,
RunE: func(cmd *cobra.Command, args []string) error {
return auth.RunAuthFlow(cmd.Context(), args)
},
}
// Add flags for quick setup mode
cmd.Flags().StringVarP(&auth.QuickProvider, "provider", "p", "", "Provider ID for quick setup (e.g., openai-native, anthropic)")
cmd.Flags().StringVarP(&auth.QuickAPIKey, "apikey", "k", "", "API key for the provider")
cmd.Flags().StringVarP(&auth.QuickModelID, "modelid", "m", "", "Model ID to configure (e.g., gpt-4o, claude-sonnet-4-5-20250929)")
cmd.Flags().StringVarP(&auth.QuickBaseURL, "baseurl", "b", "", "Base URL (optional, only for openai provider)")
return cmd
}
+25 -20
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
@@ -38,7 +39,7 @@ const (
// ┃ Change Cline model (only if authenticated) - hidden if not authenticated
// ┃ Authenticate with Cline account / Sign out of Cline - changes based on auth status
// ┃ Select active provider (Cline or BYO) - always shown. Used to switch between Cline and BYO providers
// ┃ Configure API provider - always shown. Launches provider setup wizard
// ┃ Configure BYO API providers - always shown. Launches provider setup wizard
// ┃ Exit authorization wizard - always shown. Exits the auth menu
// RunAuthFlow is the entry point for the entire auth flow with instance management
@@ -68,18 +69,25 @@ func RunAuthFlow(ctx context.Context, args []string) error {
// Main entry point for handling the `cline auth` command
// HandleAuthCommand routes the auth command based on the number of arguments
func HandleAuthCommand(ctx context.Context, args []string) error {
// Check if flags are provided for quick setup
if QuickProvider != "" || QuickAPIKey != "" || QuickModelID != "" || QuickBaseURL != "" {
if QuickProvider == "" || QuickAPIKey == "" || QuickModelID == "" {
return fmt.Errorf("quick setup requires --provider, --apikey, and --modelid flags. Use 'cline auth --help' for more information")
}
return QuickSetupFromFlags(ctx, QuickProvider, QuickAPIKey, QuickModelID, QuickBaseURL)
}
switch len(args) {
case 0:
// No args: Show menu (ShowAuthMenuNoArgs)
// No args: Show uth wizard
return HandleAuthMenuNoArgs(ctx)
case 1:
// One arg: Provider ID only, prompt for API key
return QuickAPISetup(args[0], "")
case 2:
// Two args: Provider ID and API key
return QuickAPISetup(args[0], args[1])
case 1, 2, 3, 4:
fmt.Println("Invalid positional arguments. Correct usage:")
fmt.Println(" cline auth --provider <provider> --apikey <key> --modelid <model> --baseurl <optional>")
return nil
default:
return fmt.Errorf("quick BYO API setup is currently stubbed - not yet implemented")
return fmt.Errorf("too many arguments. Use flags for quick setup: --provider, --apikey, --modelid --baseurl(optional)")
}
}
@@ -165,32 +173,34 @@ func ShowAuthMenuWithStatus(isClineAuthenticated bool, hasOrganizations bool, cu
options = append(options,
huh.NewOption("Sign out of Cline", AuthActionClineLogin),
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
huh.NewOption("Configure API provider", AuthActionBYOSetup),
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
huh.NewOption("Exit authorization wizard", AuthActionExit),
)
} else {
options = []huh.Option[AuthAction]{
huh.NewOption("Authenticate with Cline account", AuthActionClineLogin),
huh.NewOption("Select active provider (Cline or BYO)", AuthActionSelectProvider),
huh.NewOption("Configure API provider", AuthActionBYOSetup),
huh.NewOption("Configure BYO API providers", AuthActionBYOSetup),
huh.NewOption("Exit authorization wizard", AuthActionExit),
}
}
// Determine menu title based on status
var title string
renderer := display.NewRenderer(global.Config.OutputFormat)
// Always show Cline authentication status
if isClineAuthenticated {
title = "Cline Account: \033[32m✓\033[0m Authenticated\n"
title = fmt.Sprintf("Cline Account: %s Authenticated\n", renderer.Green("✓"))
} else {
title = "Cline Account: \033[31m✗\033[0m Not authenticated\n"
title = fmt.Sprintf("Cline Account: %s Not authenticated\n", renderer.Red("✗"))
}
// Show active provider and model if configured (regardless of Cline auth status)
// ANSI color codes: Normal intensity = \033[22m, White = \033[37m, Reset = \033[0m
if currentProvider != "" && currentModel != "" {
title += fmt.Sprintf("Active Provider: \033[22m\033[37m%s\033[0m\nActive Model: \033[22m\033[37m%s\033[0m\n", currentProvider, currentModel)
title += fmt.Sprintf("Active Provider: %s\nActive Model: %s\n",
renderer.White(currentProvider),
renderer.White(currentModel))
}
// Always end with a huh?
@@ -258,11 +268,6 @@ func HandleSelectProvider(ctx context.Context) error {
return HandleAuthMenuNoArgs(ctx)
}
if len(providerOptions) == 1 {
fmt.Println("Only one provider is configured. Configure another provider to switch between them.")
return HandleAuthMenuNoArgs(ctx)
}
providerOptions = append(providerOptions, huh.NewOption("(Cancel)", "cancel"))
// Show selection menu
+241 -7
View File
@@ -1,13 +1,247 @@
package auth
import "fmt"
import (
"context"
"fmt"
"strings"
// QuickAPISetup performs quick provider setup with provider ID and optional API key
func QuickAPISetup(providerID, apiKey string) error {
fmt.Println("Quick BYO API setup is currently stubbed - not yet implemented.")
fmt.Printf("Requested provider: %s\n", providerID)
if apiKey != "" {
fmt.Println("Provided API key:", "<jk redacted>")
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
)
// Package-level variables for command-line flags
var (
QuickProvider string // Provider ID (e.g., "openai", "anthropic")
QuickAPIKey string // API key for the provider
QuickModelID string // Model ID to configure
QuickBaseURL string // Base URL (optional, for openai compatible only)
)
// QuickSetupFromFlags performs quick setup using command-line flags
// Returns error if validation fails or configuration cannot be applied
func QuickSetupFromFlags(ctx context.Context, provider, apiKey, modelID, baseURL string) error {
// Validate all input parameters
providerEnum, err := validateQuickSetupInputs(provider, apiKey, modelID, baseURL)
if err != nil {
return err
}
// Create task manager for state operations
manager, err := createTaskManager(ctx)
if err != nil {
return fmt.Errorf("failed to create task manager: %w", err)
}
// Validate and fetch model information if needed
finalModelID, modelInfo, err := validateAndFetchModel(ctx, manager, providerEnum, modelID, apiKey)
if err != nil {
return fmt.Errorf("model validation failed: %w", err)
}
// For Ollama, baseURL is stored in the API key field
finalAPIKey := apiKey
finalBaseURL := baseURL
if providerEnum == cline.ApiProvider_OLLAMA {
if baseURL != "" {
finalAPIKey = baseURL
finalBaseURL = ""
} else if apiKey != "" {
// User provided API key for Ollama - treat it as baseURL
finalAPIKey = apiKey
finalBaseURL = ""
} else {
// Use default Ollama baseURL
finalAPIKey = "http://localhost:11434"
finalBaseURL = ""
}
}
// Configure the provider using existing AddProviderPartial function
if err := AddProviderPartial(ctx, manager, providerEnum, finalModelID, finalAPIKey, finalBaseURL, modelInfo); err != nil {
return fmt.Errorf("failed to configure provider: %w", err)
}
// Set the provider as active for both Plan and Act modes
if err := UpdateProviderPartial(ctx, manager, providerEnum, ProviderUpdatesPartial{}, true); err != nil {
return fmt.Errorf("failed to set provider as active: %w", err)
}
// Mark welcome view as completed
if err := markWelcomeViewCompleted(ctx, manager); err != nil {
// Non-fatal error, just log it
if global.Config.Verbose {
fmt.Printf("[DEBUG] Warning: failed to mark welcome view as completed: %v\n", err)
}
}
// Flush pending state changes to disk immediately
// This ensures all configuration changes are persisted before the instance terminates
if _, err := manager.GetClient().State.FlushPendingState(ctx, &cline.EmptyRequest{}); err != nil {
return fmt.Errorf("failed to flush pending state: %w", err)
}
// Success message
fmt.Printf("\n✓ Successfully configured %s provider\n", GetProviderDisplayName(providerEnum))
fmt.Printf(" Model: %s\n", finalModelID)
if providerEnum == cline.ApiProvider_OLLAMA {
fmt.Printf(" Base URL: %s\n", finalAPIKey)
} else {
fmt.Println(" API Key: Configured")
}
if finalBaseURL != "" {
fmt.Printf(" Custom Base URL: %s\n", finalBaseURL)
}
fmt.Println("\nYou can now use Cline with this provider.")
fmt.Println("Run 'cline start' to begin a new task.")
return nil
}
// validateQuickSetupInputs validates all input parameters for quick setup
// Returns the validated provider enum or an error if validation fails
func validateQuickSetupInputs(provider, apiKey, modelID, baseURL string) (cline.ApiProvider, error) {
// Validate required parameters
if provider == "" {
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("provider is required. Use --provider or -p flag")
}
if strings.TrimSpace(apiKey) == "" && provider != "ollama" {
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("API key is required for %s provider. Use --apikey or -k flag", provider)
}
if strings.TrimSpace(modelID) == "" {
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("model ID is required. Use --modelid or -m flag")
}
// Validate and map provider string to enum
providerEnum, err := validateQuickSetupProvider(provider)
if err != nil {
return cline.ApiProvider_ANTHROPIC, err
}
// Validate that baseURL is only provided for OpenAI-compatible providers
if err := validateBaseURL(baseURL, providerEnum); err != nil {
return cline.ApiProvider_ANTHROPIC, err
}
return providerEnum, nil
}
// validateBaseURL checks if the user's input includes a baseURL for a provider other than OpenAI (compatible)
// Returns error if baseURL is provided for unsupported providers
func validateBaseURL(baseURL string, providerEnum cline.ApiProvider) error {
if providerEnum != cline.ApiProvider_OPENAI {
if baseURL != "" {
return fmt.Errorf("base URL is only supported for OpenAI and OpenAI-compatible providers")
}
}
return nil
}
// validateQuickSetupProvider validates the provider ID and returns the enum value
// Returns error if provider is invalid or not supported for quick setup
func validateQuickSetupProvider(providerID string) (cline.ApiProvider, error) {
// Normalize provider ID (trim whitespace, lowercase)
normalizedID := strings.TrimSpace(strings.ToLower(providerID))
// Explicitly block Bedrock
if normalizedID == "bedrock" {
return cline.ApiProvider_BEDROCK, fmt.Errorf("bedrock provider is not supported for quick setup due to complex authentication requirements. Please use interactive setup: cline auth")
}
// Map provider string to enum using existing function
provider, ok := mapProviderStringToEnum(normalizedID)
if !ok {
// Provider not found - provide helpful error message
supportedProviders := []string{
"openai-native", "openai", "anthropic", "gemini",
"openrouter", "xai", "cerebras", "ollama",
}
return cline.ApiProvider_ANTHROPIC, fmt.Errorf(
"invalid provider '%s'. Supported providers: %s",
providerID,
strings.Join(supportedProviders, ", "),
)
}
// Validate against supported quick setup providers
supportedProviders := map[cline.ApiProvider]bool{
cline.ApiProvider_OPENAI_NATIVE: true,
cline.ApiProvider_OPENAI: true,
cline.ApiProvider_ANTHROPIC: true,
cline.ApiProvider_GEMINI: true,
cline.ApiProvider_OPENROUTER: true,
cline.ApiProvider_XAI: true,
cline.ApiProvider_CEREBRAS: true,
cline.ApiProvider_OLLAMA: true,
cline.ApiProvider_NOUSRESEARCH: true,
}
if !supportedProviders[provider] {
return provider, fmt.Errorf(
"provider '%s' is not supported for quick setup. Please use interactive setup: cline auth",
providerID,
)
}
return provider, nil
}
// validateAndFetchModel validates the model ID or fetches from provider if needed
// Returns the final model ID and optional model info
// For providers with static models, validates against the list
// For providers with dynamic models, fetches the list if possible
func validateAndFetchModel(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID, apiKey string) (string, interface{}, error) {
// Normalize model ID
modelID = strings.TrimSpace(modelID)
if modelID == "" {
return "", nil, fmt.Errorf("model ID cannot be empty")
}
// For most providers, we trust the user's input since we can't easily validate without making API calls
// The actual validation will happen when the model is used
switch provider {
case cline.ApiProvider_OPENROUTER:
// OpenRouter supports model info fetching, but it requires an API call
// For quick setup, we'll trust the user's input and return nil for model info
// The actual model info will be fetched when needed
if global.Config.Verbose {
fmt.Printf("[DEBUG] OpenRouter model ID: %s (will be validated on first use)\n", modelID)
}
return modelID, nil, nil
case cline.ApiProvider_OLLAMA:
// Ollama models can be validated by fetching the list, but this requires the server to be running
// For quick setup, we'll trust the user's input
if global.Config.Verbose {
fmt.Printf("[DEBUG] Ollama model ID: %s (will be validated when server is accessible)\n", modelID)
}
return modelID, nil, nil
default:
// For other providers (Anthropic, OpenAI, Gemini, XAI, Cerebras), trust user input
// Model validation will occur when the model is actually used
if global.Config.Verbose {
fmt.Printf("[DEBUG] %s model ID: %s (will be validated on first use)\n", GetProviderDisplayName(provider), modelID)
}
return modelID, nil, nil
}
}
// markWelcomeViewCompleted marks the welcome view as completed in the state
// This prevents the welcome view from showing up after quick setup
func markWelcomeViewCompleted(ctx context.Context, manager *task.Manager) error {
// Use the State service to update the welcome view flag
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
if err != nil {
return fmt.Errorf("failed to mark welcome view as completed: %w", err)
}
if global.Config.Verbose {
fmt.Println("[DEBUG] Marked welcome view as completed")
}
return nil
}
-1
View File
@@ -1 +0,0 @@
package auth
+24 -4
View File
@@ -14,13 +14,33 @@ import (
// FetchOpenRouterModels fetches available OpenRouter models from Cline Core
func FetchOpenRouterModels(ctx context.Context, manager *task.Manager) (map[string]*cline.OpenRouterModelInfo, error) {
resp, err := manager.GetClient().Models.RefreshOpenRouterModels(ctx, &cline.EmptyRequest{})
resp, err := manager.GetClient().Models.RefreshOpenRouterModelsRpc(ctx, &cline.EmptyRequest{})
if err != nil {
return nil, fmt.Errorf("failed to fetch OpenRouter models: %w", err)
}
return resp.Models, nil
}
// FetchOcaModels fetches available Oca models from Cline Core
func FetchOcaModels(ctx context.Context, manager *task.Manager) (map[string]*cline.OcaModelInfo, error) {
resp, err := manager.GetClient().Models.RefreshOcaModels(ctx, &cline.StringRequest{})
if err != nil {
return nil, fmt.Errorf("failed to fetch Oca models: %w", err)
}
return resp.Models, nil
}
// ConvertOpenRouterModelsToInterface converts OpenRouter model map to generic interface map.
// This allows OpenRouter and Cline models to be used with the generic fetching utilities.
func ConvertOpenRouterModelsToInterface(models map[string]*cline.OpenRouterModelInfo) map[string]interface{} {
result := make(map[string]interface{}, len(models))
for k, v := range models {
result[k] = v
}
return result
}
// FetchOpenAiModels fetches available OpenAI models from Cline Core
// Takes the API key and returns a list of model IDs
func FetchOpenAiModels(ctx context.Context, manager *task.Manager, baseURL, apiKey string) ([]string, error) {
@@ -100,9 +120,9 @@ func ConvertModelsMapToSlice(models map[string]interface{}) []string {
return result
}
// ConvertOpenRouterModelsToInterface converts OpenRouter model map to generic interface map.
// This allows OpenRouter and Cline models to be used with the generic fetching utilities.
func ConvertOpenRouterModelsToInterface(models map[string]*cline.OpenRouterModelInfo) map[string]interface{} {
// ConvertOcaModelsToInterface converts Oca model map to generic interface map.
// This allows Oca and Cline models to be used with the generic fetching utilities.
func ConvertOcaModelsToInterface(models map[string]*cline.OcaModelInfo) map[string]interface{} {
result := make(map[string]interface{}, len(models))
for k, v := range models {
result[k] = v
+20 -13
View File
@@ -18,14 +18,16 @@ type BYOProviderOption struct {
func GetBYOProviderList() []BYOProviderOption {
return []BYOProviderOption{
{Name: "Anthropic", Provider: cline.ApiProvider_ANTHROPIC},
{Name: "OpenAI", Provider: cline.ApiProvider_OPENAI},
{Name: "OpenAI Native", Provider: cline.ApiProvider_OPENAI_NATIVE},
{Name: "OpenAI Compatible", Provider: cline.ApiProvider_OPENAI},
{Name: "OpenAI (Official)", Provider: cline.ApiProvider_OPENAI_NATIVE},
{Name: "OpenRouter", Provider: cline.ApiProvider_OPENROUTER},
{Name: "X AI (Grok)", Provider: cline.ApiProvider_XAI},
{Name: "AWS Bedrock", Provider: cline.ApiProvider_BEDROCK},
{Name: "Google Gemini", Provider: cline.ApiProvider_GEMINI},
{Name: "Ollama", Provider: cline.ApiProvider_OLLAMA},
{Name: "Cerebras", Provider: cline.ApiProvider_CEREBRAS},
{Name: "NousResearch", Provider: cline.ApiProvider_NOUSRESEARCH},
{Name: "Oracle Code Assist", Provider: cline.ApiProvider_OCA},
}
}
@@ -71,6 +73,8 @@ func SupportsBYOModelFetching(provider cline.ApiProvider) bool {
return true
case cline.ApiProvider_OLLAMA:
return true
case cline.ApiProvider_OCA:
return true
}
return SupportsStaticModelList(provider)
@@ -82,9 +86,9 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
case cline.ApiProvider_ANTHROPIC:
return "e.g., claude-sonnet-4-5-20250929"
case cline.ApiProvider_OPENAI:
return "e.g., gpt-5-2025-08-07"
case cline.ApiProvider_OPENAI_NATIVE:
return "e.g., openai/gpt-oss-120b"
case cline.ApiProvider_OPENAI_NATIVE:
return "e.g., gpt-5-2025-08-07"
case cline.ApiProvider_OPENROUTER:
return "e.g., google/gemini-2.0-flash-exp:free"
case cline.ApiProvider_XAI:
@@ -97,6 +101,10 @@ func GetBYOProviderPlaceholder(provider cline.ApiProvider) string {
return "e.g., qwen3-coder:30b"
case cline.ApiProvider_CEREBRAS:
return "e.g., gpt-oss-120b"
case cline.ApiProvider_NOUSRESEARCH:
return "e.g., Hermes-4-405B"
case cline.ApiProvider_OCA:
return "e.g., oca/llama4"
default:
return "Enter model ID"
}
@@ -127,8 +135,8 @@ func GetBYOAPIKeyFieldConfig(provider cline.ApiProvider) APIKeyFieldConfig {
}
// PromptForAPIKey prompts the user to enter an API key (or base URL for Ollama).
// For OpenAI Native provider, also prompts for an optional base URL.
func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
// For OpenAI (Compatible) provider, also prompts for an optional base URL.
func PromptForAPIKey(provider cline.ApiProvider) (string, string, error) {
var apiKey string
config := GetBYOAPIKeyFieldConfig(provider)
@@ -149,11 +157,11 @@ func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
form := huh.NewForm(huh.NewGroup(apiKeyField))
if err := form.Run(); err != nil {
return "", fmt.Errorf("failed to get API key: %w", err)
return "", "", fmt.Errorf("failed to get API key: %w", err)
}
// For OpenAI Native provider, also prompt for base URL
if provider == cline.ApiProvider_OPENAI_NATIVE {
// For OpenAI (Compatible) provider, prompt for base URL
if provider == cline.ApiProvider_OPENAI {
var baseURL string
baseURLForm := huh.NewForm(
huh.NewGroup(
@@ -166,12 +174,11 @@ func PromptForAPIKey(provider cline.ApiProvider) (string, error) {
)
if err := baseURLForm.Run(); err != nil {
return "", fmt.Errorf("failed to get base URL: %w", err)
return "", "", fmt.Errorf("failed to get base URL: %w", err)
}
// TODO - connect baseURL
_ = baseURL
return apiKey, baseURL, nil
}
return apiKey, nil
return apiKey, "", nil
}
+56 -16
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
@@ -110,6 +111,9 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
cline.ApiProvider_GEMINI,
cline.ApiProvider_OLLAMA,
cline.ApiProvider_CEREBRAS,
cline.ApiProvider_NOUSRESEARCH,
cline.ApiProvider_OCA,
cline.ApiProvider_HICAP,
}
// Check each provider to see if it's ready to use
@@ -120,16 +124,23 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
continue
}
// Check if this provider has an API key
hasAPIKey := checkAPIKeyExists(r.apiConfig, provider)
if !hasAPIKey {
continue
}
// Check if this provider has a model configured
modelID := getProviderSpecificModelID(r.apiConfig, "plan", provider)
if modelID == "" {
continue
// Determine if credentials exist
hasCreds := checkAPIKeyExists(r.apiConfig, provider)
// Determine readiness: OCA uses auth state presence; others need creds and model
if provider == cline.ApiProvider_OCA {
state, _ := GetLatestOCAState(context.Background(), 2 *time.Second)
if state == nil || state.User == nil {
continue
}
} else {
// Provider is not ready unless it has credentials AND a model configured
if !hasCreds || modelID == "" {
continue
}
}
// Get base URL for Ollama
@@ -145,7 +156,7 @@ func (r *ProviderListResult) GetAllReadyProviders() []*ProviderDisplay {
Mode: "Ready",
Provider: provider,
ModelID: modelID,
HasAPIKey: hasAPIKey,
HasAPIKey: checkAPIKeyExists(r.apiConfig, provider),
BaseURL: baseURL,
})
seenProviders[provider] = true
@@ -203,13 +214,15 @@ func extractProviderFromState(stateData map[string]interface{}, mode string) *Pr
// mapProviderStringToEnum converts provider string from state to ApiProvider enum
// Returns (provider, ok) where ok is false if the provider is unknown
func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
normalizedStr := strings.ToLower(providerStr)
// Map string values to enum values
switch providerStr {
switch normalizedStr {
case "anthropic":
return cline.ApiProvider_ANTHROPIC, true
case "openai":
case "openai", "openai-compatible": // internal name is 'openai', but this is actually the openai-compatible provider
return cline.ApiProvider_OPENAI, true
case "openai-native":
case "openai-native": // This is the native, official Open AI provider
return cline.ApiProvider_OPENAI_NATIVE, true
case "openrouter":
return cline.ApiProvider_OPENROUTER, true
@@ -225,6 +238,12 @@ func mapProviderStringToEnum(providerStr string) (cline.ApiProvider, bool) {
return cline.ApiProvider_CEREBRAS, true
case "cline":
return cline.ApiProvider_CLINE, true
case "oca":
return cline.ApiProvider_OCA, true
case "hicap":
return cline.ApiProvider_HICAP, true
case "nousResearch":
return cline.ApiProvider_NOUSRESEARCH, true
default:
return cline.ApiProvider_ANTHROPIC, false // Return 0 value with false
}
@@ -237,7 +256,7 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
case cline.ApiProvider_ANTHROPIC:
return "anthropic"
case cline.ApiProvider_OPENAI:
return "openai"
return "openai-compatible"
case cline.ApiProvider_OPENAI_NATIVE:
return "openai-native"
case cline.ApiProvider_OPENROUTER:
@@ -254,6 +273,12 @@ func GetProviderIDForEnum(provider cline.ApiProvider) string {
return "cerebras"
case cline.ApiProvider_CLINE:
return "cline"
case cline.ApiProvider_OCA:
return "oca"
case cline.ApiProvider_HICAP:
return "hicap"
case cline.ApiProvider_NOUSRESEARCH:
return "nousResearch"
default:
return ""
}
@@ -312,9 +337,9 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
case cline.ApiProvider_ANTHROPIC:
return "Anthropic"
case cline.ApiProvider_OPENAI:
return "OpenAI"
return "OpenAI Compatible"
case cline.ApiProvider_OPENAI_NATIVE:
return "OpenAI Native"
return "OpenAI (Official)"
case cline.ApiProvider_OPENROUTER:
return "OpenRouter"
case cline.ApiProvider_XAI:
@@ -329,6 +354,12 @@ func GetProviderDisplayName(provider cline.ApiProvider) string {
return "Cerebras"
case cline.ApiProvider_CLINE:
return "Cline (Official)"
case cline.ApiProvider_OCA:
return "Oracle Code Assist"
case cline.ApiProvider_HICAP:
return "Hicap"
case cline.ApiProvider_NOUSRESEARCH:
return "NousResearch"
default:
return "Unknown"
}
@@ -378,7 +409,7 @@ func FormatProviderList(result *ProviderListResult) string {
} else {
output.WriteString(" Base URL: (default)\n")
}
} else if display.Provider == cline.ApiProvider_CLINE {
} else if display.Provider == cline.ApiProvider_CLINE || display.Provider == cline.ApiProvider_OCA {
output.WriteString(" Status: Authenticated\n")
} else {
output.WriteString(" API Key: Configured\n")
@@ -430,6 +461,12 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
verboseLog("[DEBUG] Cline provider is authenticated")
}
// Check OCA provider via global auth subscription (state presence)
if state, _ := GetLatestOCAState(context.Background(), 2*time.Second); state != nil && state.User != nil {
configuredProviders = append(configuredProviders, cline.ApiProvider_OCA)
verboseLog("[DEBUG] OCA provider has active auth state")
}
// Check each BYO provider for API key presence
providersToCheck := []struct {
provider cline.ApiProvider
@@ -444,6 +481,8 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
{cline.ApiProvider_GEMINI, "geminiApiKey"},
{cline.ApiProvider_OLLAMA, "ollamaBaseUrl"}, // Ollama uses baseUrl instead of API key
{cline.ApiProvider_CEREBRAS, "cerebrasApiKey"},
{cline.ApiProvider_HICAP, "hicapApiKey"},
{cline.ApiProvider_NOUSRESEARCH, "nousResearchApiKey"},
}
for _, providerCheck := range providersToCheck {
@@ -459,6 +498,7 @@ func DetectAllConfiguredProviders(ctx context.Context, manager *task.Manager) ([
}
}
verboseLog("[DEBUG] Total configured providers: %d", len(configuredProviders))
for _, p := range configuredProviders {
verboseLog("[DEBUG] - %s", GetProviderDisplayName(p))
+146 -8
View File
@@ -12,7 +12,7 @@ import (
)
// updateApiConfigurationPartial is a helper that calls the gRPC method with optional verbose logging.
// This replaces the Manager.UpdateApiConfigurationPartial method to keep auth-specific code in the auth package.
// This replaces the Manager.updateApiConfigurationPartial method to keep auth-specific code in the auth package.
func updateApiConfigurationPartial(ctx context.Context, manager *task.Manager, request *cline.UpdateApiConfigurationPartialRequest) error {
if global.Config.Verbose {
fmt.Println("[DEBUG] Updating API configuration (partial)")
@@ -46,6 +46,7 @@ func updateApiConfigurationPartial(ctx context.Context, manager *task.Manager, r
// ProviderFields defines all the field names associated with a specific provider
type ProviderFields struct {
APIKeyField string // API key field name (e.g., "apiKey", "openAiApiKey")
BaseURLField string // Base URL field name (optional, empty if not applicable)
PlanModeModelIDField string // Plan mode model ID field (e.g., "planModeApiModelId")
ActModeModelIDField string // Act mode model ID field (e.g., "actModeApiModelId")
PlanModeModelInfoField string // Plan mode model info field (optional, empty if not applicable)
@@ -68,6 +69,7 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
case cline.ApiProvider_OPENAI:
return ProviderFields{
APIKeyField: "openAiApiKey",
BaseURLField: "openAiBaseUrl",
PlanModeModelIDField: "planModeApiModelId",
ActModeModelIDField: "actModeApiModelId",
PlanModeProviderSpecificModelIDField: "planModeOpenAiModelId",
@@ -142,6 +144,34 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
ActModeProviderSpecificModelIDField: "actModeOpenRouterModelId",
}, nil
case cline.ApiProvider_OCA:
return ProviderFields{
APIKeyField: "ocaApiKey",
PlanModeModelIDField: "planModeApiModelId",
ActModeModelIDField: "actModeApiModelId",
PlanModeModelInfoField: "planModeOcaModelInfo",
ActModeModelInfoField: "actModeOcaModelInfo",
PlanModeProviderSpecificModelIDField: "planModeOcaModelId",
ActModeProviderSpecificModelIDField: "actModeOcaModelId",
}, nil
case cline.ApiProvider_HICAP:
return ProviderFields{
APIKeyField: "hicapApiKey",
PlanModeModelInfoField: "planModeHicapModelInfo",
ActModeModelInfoField: "actModeHicapModelInfo",
PlanModeProviderSpecificModelIDField: "planModeHicapModelId",
ActModeProviderSpecificModelIDField: "actModeHicapModelId",
}, nil
case cline.ApiProvider_NOUSRESEARCH:
return ProviderFields{
APIKeyField: "nousResearchApiKey",
PlanModeModelIDField: "planModeApiModelId",
ActModeModelIDField: "actModeApiModelId",
PlanModeProviderSpecificModelIDField: "planModeNousResearchModelId",
ActModeProviderSpecificModelIDField: "actModeNousResearchModelId",
}, nil
default:
return ProviderFields{}, fmt.Errorf("unsupported provider: %v", provider)
}
@@ -150,9 +180,12 @@ func GetProviderFields(provider cline.ApiProvider) (ProviderFields, error) {
// ProviderUpdatesPartial defines optional fields for partial provider updates
// Uses pointers to distinguish between "not provided" and "set to empty"
type ProviderUpdatesPartial struct {
ModelID *string // New model ID (optional)
APIKey *string // New API key (optional)
ModelInfo interface{} // New model info (optional, provider-specific)
ModelID *string // New model ID (optional)
APIKey *string // New API key (optional)
ModelInfo interface{} // New model info (optional, provider-specific)
BaseURL *string // New base URL (optional, e.g., for OCA, Ollama)
RefreshToken *string // New refresh token (optional, e.g., for OCA)
Mode *string // New mode (optional, e.g., "internal" or "external" for OCA)
}
// GetModelIDFieldName returns the appropriate model ID field name for a provider and mode.
@@ -182,7 +215,7 @@ func GetModelIDFieldName(provider cline.ApiProvider, mode string) (string, error
// buildProviderFieldMask builds a list of camelCase field paths for the field mask.
// When includeProviderEnums is true, the provider enum fields are included (for setting active provider).
// When false, only the data fields are included (for configuring without activating).
func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeModelID bool, includeModelInfo bool, includeProviderEnums bool) []string {
func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeModelID bool, includeModelInfo bool, includeBaseURL bool, includeProviderEnums bool) []string {
var fieldPaths []string
// Include provider enums if requested (used when setting active provider)
@@ -199,6 +232,11 @@ func buildProviderFieldMask(fields ProviderFields, includeAPIKey bool, includeMo
}
}
// Add base URL field if requested and applicable
if includeBaseURL && fields.BaseURLField != "" {
fieldPaths = append(fieldPaths, fields.BaseURLField)
}
// Add model ID fields if requested
if includeModelID {
// Only include provider-specific fields if they exist, otherwise use generic fields
@@ -245,6 +283,12 @@ func setAPIKeyField(apiConfig *cline.ModelsApiConfiguration, fieldName string, v
apiConfig.CerebrasApiKey = value
case "clineApiKey":
apiConfig.ClineApiKey = value
case "ocaApiKey":
apiConfig.OcaApiKey = value
case "hicapApiKey":
apiConfig.HicapApiKey = value
case "nousResearchApiKey":
apiConfig.NousResearchApiKey = value
}
}
@@ -263,11 +307,20 @@ func setProviderSpecificModelID(apiConfig *cline.ModelsApiConfiguration, fieldNa
case "planModeAwsBedrockCustomModelBaseId":
apiConfig.PlanModeAwsBedrockCustomModelBaseId = value
apiConfig.ActModeAwsBedrockCustomModelBaseId = value
case "planModeOcaModelId":
apiConfig.PlanModeOcaModelId = value
apiConfig.ActModeOcaModelId = value
case "planModeHicapModelId":
apiConfig.PlanModeHicapModelId = value
apiConfig.ActModeHicapModelId = value
case "planModeNousResearchModelId":
apiConfig.PlanModeNousResearchModelId = value
apiConfig.ActModeNousResearchModelId = value
}
}
// AddProviderPartial configures a new provider with all necessary fields using partial updates.
func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID string, apiKey string, modelInfo interface{}) error {
func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cline.ApiProvider, modelID string, apiKey string, baseURL string, modelInfo interface{}) error {
// Get field mapping for this provider
fields, err := GetProviderFields(provider)
if err != nil {
@@ -282,6 +335,13 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
setAPIKeyField(apiConfig, fields.APIKeyField, proto.String(apiKey))
}
// Set base URL field if provided and applicable
includeBaseURL := false
if baseURL != "" && fields.BaseURLField != "" {
setBaseURLField(apiConfig, fields.BaseURLField, proto.String(baseURL))
includeBaseURL = true
}
// Set model ID fields
apiConfig.PlanModeApiModelId = proto.String(modelID)
apiConfig.ActModeApiModelId = proto.String(modelID)
@@ -301,7 +361,7 @@ func AddProviderPartial(ctx context.Context, manager *task.Manager, provider cli
// Build field mask including all fields we're setting (without provider enums)
includeModelInfo := fields.PlanModeModelInfoField != "" && modelInfo != nil
fieldPaths := buildProviderFieldMask(fields, true, true, includeModelInfo, false)
fieldPaths := buildProviderFieldMask(fields, true, true, includeModelInfo, includeBaseURL, false)
// Create field mask
fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
@@ -368,7 +428,7 @@ func UpdateProviderPartial(ctx context.Context, manager *task.Manager, provider
}
// Build field mask for only the fields being updated
fieldPaths := buildProviderFieldMask(fields, includeAPIKey, includeModelID, includeModelInfo, setAsActive)
fieldPaths := buildProviderFieldMask(fields, includeAPIKey, includeModelID, includeModelInfo, false, setAsActive)
// Create field mask
fieldMask := &fieldmaskpb.FieldMask{Paths: fieldPaths}
@@ -421,6 +481,46 @@ func RemoveProviderPartial(ctx context.Context, manager *task.Manager, provider
return nil
}
// setBaseURLField sets the appropriate base URL field in the config based on the field name
func setBaseURLField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
switch fieldName {
case "ocaBaseUrl":
apiConfig.OcaBaseUrl = value
case "ollamaBaseUrl":
apiConfig.OllamaBaseUrl = value
case "openAiBaseUrl":
apiConfig.OpenAiBaseUrl = value
case "geminiBaseUrl":
apiConfig.GeminiBaseUrl = value
case "liteLlmBaseUrl":
apiConfig.LiteLlmBaseUrl = value
case "anthropicBaseUrl":
apiConfig.AnthropicBaseUrl = value
case "requestyBaseUrl":
apiConfig.RequestyBaseUrl = value
case "lmStudioBaseUrl":
apiConfig.LmStudioBaseUrl = value
case "oca":
apiConfig.OcaBaseUrl = value
}
}
// setRefreshTokenField sets the appropriate refresh token field in the config
func setRefreshTokenField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
switch fieldName {
case "ocaRefreshToken":
apiConfig.OcaRefreshToken = value
}
}
// setModeField sets the appropriate mode field in the config
func setModeField(apiConfig *cline.ModelsApiConfiguration, fieldName string, value *string) {
switch fieldName {
case "ocaMode":
apiConfig.OcaMode = value
}
}
// BedrockOptionalFields holds optional configuration fields for AWS Bedrock
type BedrockOptionalFields struct {
SessionToken *string // Optional: AWS session token for temporary credentials
@@ -434,6 +534,12 @@ type BedrockOptionalFields struct {
Endpoint *string // Optional: Custom endpoint URL
}
// OcaOptionalFields holds optional configuration fields for Oracle Code Assist
type OcaOptionalFields struct {
BaseURL *string // Optional: Base URL
Mode *string // Optional: Mode ("internal" or "external")
}
// setBedrockOptionalFields sets optional Bedrock-specific fields in the API configuration
func setBedrockOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *BedrockOptionalFields) {
if fields == nil {
@@ -469,6 +575,20 @@ func setBedrockOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *B
}
}
// setOcaOptionalFields sets optional Oca-specific fields in the API configuration
func setOcaOptionalFields(apiConfig *cline.ModelsApiConfiguration, fields *OcaOptionalFields) {
if fields == nil {
return
}
if fields.Mode != nil {
apiConfig.OcaMode = fields.Mode
}
if fields.BaseURL != nil {
apiConfig.OcaBaseUrl = fields.BaseURL
}
}
// buildBedrockOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
func buildBedrockOptionalFieldMask(fields *BedrockOptionalFields) []string {
if fields == nil {
@@ -507,3 +627,21 @@ func buildBedrockOptionalFieldMask(fields *BedrockOptionalFields) []string {
return fieldPaths
}
// buildOcaOptionalFieldMask builds field mask paths for Bedrock optional fields that have values
func buildOcaOptionalFieldMask(fields *OcaOptionalFields) []string {
if fields == nil {
return nil
}
var fieldPaths []string
if fields.Mode != nil {
fieldPaths = append(fieldPaths, "ocaMode")
}
if fields.BaseURL != nil {
fieldPaths = append(fieldPaths, "ocaBaseUrl")
}
return fieldPaths
}
+93 -5
View File
@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli/global"
@@ -40,7 +41,7 @@ func (pw *ProviderWizard) showMainMenu() (string, error) {
huh.NewSelect[string]().
Title("What would you like to do?").
Options(
huh.NewOption("Configure a new provider", "add"),
huh.NewOption("Add or change an API provider", "add"),
huh.NewOption("Change model for API provider", "change-model"),
huh.NewOption("Remove a provider", "remove"),
huh.NewOption("List configured providers", "list"),
@@ -107,8 +108,13 @@ func (pw *ProviderWizard) handleAddProvider() error {
return pw.handleAddBedrockProvider()
}
// Step 2b: Special handling for OCA provider
if provider == cline.ApiProvider_OCA {
return pw.handleAddOcaProvider()
}
// Step 3: Get API key first (for non-Bedrock providers)
apiKey, err := PromptForAPIKey(provider)
apiKey, baseURL, err := PromptForAPIKey(provider)
if err != nil {
return fmt.Errorf("failed to get API key: %w", err)
}
@@ -120,7 +126,7 @@ func (pw *ProviderWizard) handleAddProvider() error {
}
// Step 5: Apply configuration using AddProviderPartial
if err := AddProviderPartial(pw.ctx, pw.manager, provider, modelID, apiKey, modelInfo); err != nil {
if err := AddProviderPartial(pw.ctx, pw.manager, provider, modelID, apiKey, baseURL, modelInfo); err != nil {
return fmt.Errorf("failed to save configuration: %w", err)
}
@@ -162,6 +168,51 @@ func (pw *ProviderWizard) handleAddBedrockProvider() error {
return nil
}
// handleAddOcaProvider handles adding Oracle Code Assist provider with optional settings and auth
func (pw *ProviderWizard) handleAddOcaProvider() error {
// Step 1: Get OCA configuration (base URL and mode)
config, err := PromptForOcaConfig(pw.ctx, pw.manager)
if err != nil {
if strings.Contains(err.Error(), "user aborted") || strings.Contains(err.Error(), "cancelled") {
return nil
}
return fmt.Errorf("failed to get OCA configuration: %w", err)
}
// Apply OCA configuration (base URL and mode)
if err := ApplyOcaConfig(pw.ctx, pw.manager, config); err != nil {
return fmt.Errorf("failed to save OCA configuration: %w", err)
}
// Step 2: Ensure OCA authentication
if err := ensureOcaAuthenticated(pw.ctx); err != nil {
return fmt.Errorf("failed to authenticate with OCA: %w", err)
}
// Step 3: Select model
modelID, _, err := pw.selectModel(cline.ApiProvider_OCA, "")
if err != nil {
return fmt.Errorf("model selection failed: %w", err)
}
// Step 4: Apply the OCA model configuration and set as active
updates := ProviderUpdatesPartial{
ModelID: &modelID,
ModelInfo: nil,
}
if err := UpdateProviderPartial(pw.ctx, pw.manager, cline.ApiProvider_OCA, updates, true); err != nil {
return fmt.Errorf("failed to save OCA configuration: %w", err)
}
if err := setWelcomeViewCompleted(pw.ctx, pw.manager); err != nil {
verboseLog("Warning: Failed to mark welcome view as completed: %v", err)
}
fmt.Println("✓ OCA provider configured successfully!")
return nil
}
// handleListProviders retrieves and displays configured providers
func (pw *ProviderWizard) handleListProviders() error {
result, err := GetProviderConfigurations(pw.ctx, pw.manager)
@@ -259,6 +310,15 @@ func (pw *ProviderWizard) fetchModelsForProvider(provider cline.ApiProvider, api
}
// Ollama returns just model IDs without additional info, so modelInfo map is nil
return modelIDs, nil, nil
case cline.ApiProvider_OCA:
// OCA supports dynamic model fetching
models, err := FetchOcaModels(pw.ctx, pw.manager)
if err != nil {
return nil, nil, err
}
interfaceMap := ConvertOcaModelsToInterface(models)
return ConvertModelsMapToSlice(interfaceMap), interfaceMap, nil
}
// Fall back to static models for providers that don't support dynamic fetching
@@ -525,8 +585,17 @@ func getProviderModelIDFromState(stateData map[string]interface{}, provider clin
return ""
}
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
// getProviderAPIKeyFromState retrieves the API key for a specific provider from state
func getProviderAPIKeyFromState(stateData map[string]interface{}, provider cline.ApiProvider) string {
// OCA uses account authentication, not API keys. Consider it "present" if authenticated.
if provider == cline.ApiProvider_OCA {
if state, _ := GetLatestOCAState(context.TODO(), 2 * time.Second); state != nil && state.User != nil {
// Return a sentinel non-empty string so upstream checks pass.
return "OCA_AUTH_VERIFIED"
}
return ""
}
fields, err := GetProviderFields(provider)
if err != nil {
return ""
@@ -656,7 +725,16 @@ func (pw *ProviderWizard) handleRemoveProvider() error {
return nil
}
// Step 7: Clear the API key for the selected provider
// Step 7: If removing OCA, sign out first
if selectedProvider.Provider == cline.ApiProvider_OCA {
if err := signOutOca(pw.ctx); err != nil {
fmt.Printf("Warning: Failed to sign out of OCA: %v\n", err)
} else {
fmt.Println("Signed out of OCA.")
}
}
// Step 8: Clear the API key for the selected provider
if err := pw.clearProviderAPIKey(selectedProvider.Provider); err != nil {
return fmt.Errorf("failed to remove provider: %w", err)
}
@@ -670,6 +748,16 @@ func (pw *ProviderWizard) clearProviderAPIKey(provider cline.ApiProvider) error
return RemoveProviderPartial(pw.ctx, pw.manager, provider)
}
func signOutOca(ctx context.Context) error {
client, err := global.GetDefaultClient(ctx)
if err != nil {
return err
}
_, err = client.Ocaaccount.OcaAccountLogoutClicked(ctx, &cline.EmptyRequest{})
return err
}
func setWelcomeViewCompleted(ctx context.Context, manager *task.Manager) error {
_, err := manager.GetClient().State.SetWelcomeViewCompleted(ctx, &cline.BooleanRequest{Value: true})
return err
+366
View File
@@ -0,0 +1,366 @@
package auth
import (
"context"
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/charmbracelet/huh"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/grpc-go/cline"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
// OcaConfig holds Oracle Code Assist (OCA) configuration fields
type OcaConfig struct {
BaseURL string
Mode string
}
// PromptForOcaConfig displays a form for OCA configuration (base URL and mode)
func PromptForOcaConfig(ctx context.Context, manager *task.Manager) (*OcaConfig, error) {
config := &OcaConfig{}
var mode string
// Collect optional settings
configForm := huh.NewForm(
huh.NewGroup(
huh.NewInput().
Title("Base URL").
Value(&config.BaseURL).
Description("Leave empty to use default Base URL"),
huh.NewSelect[string]().
Title("Choose OCA mode (used for authentication)").
Description("Select 'Internal' to use Cline's internal OCA, or 'External' for your own OCA instance").
Options(
huh.NewOption("Internal", "internal"),
huh.NewOption("External", "external"),
).
Value(&mode),
),
)
if err := configForm.Run(); err != nil {
return nil, fmt.Errorf("failed to get OCA configuration: %w", err)
}
// Trim whitespace from string fields
config.BaseURL = strings.TrimSpace(config.BaseURL)
config.Mode = strings.TrimSpace(mode)
return config, nil
}
// ApplyOcaConfig applies OCA configuration using partial updates
func ApplyOcaConfig(ctx context.Context, manager *task.Manager, config *OcaConfig) error {
// Build the API configuration with all OCA fields
apiConfig := &cline.ModelsApiConfiguration{}
// Set profile authentication fields (always required)
optionalFields := &OcaOptionalFields{}
// Set profile name (can be empty for default profile)
if config.BaseURL != "" {
optionalFields.BaseURL = proto.String(config.BaseURL)
}
// Set optional fields if provided
if config.Mode != "" {
optionalFields.Mode = proto.String(config.Mode)
}
// Apply all fields to the config
setOcaOptionalFields(apiConfig, optionalFields)
// Add profile authentication field paths
optionalPaths := buildOcaOptionalFieldMask(optionalFields)
// Create field mask
fieldMask := &fieldmaskpb.FieldMask{Paths: optionalPaths}
// Apply the partial update
request := &cline.UpdateApiConfigurationPartialRequest{
ApiConfiguration: apiConfig,
UpdateMask: fieldMask,
}
if err := updateApiConfigurationPartial(ctx, manager, request); err != nil {
return fmt.Errorf("failed to apply OCA configuration: %w", err)
}
return nil
}
// ===========================
// OCA Auth Listener Singleton
// ===========================
type ocaAuthStream interface {
Recv() (*cline.OcaAuthState, error)
}
// OcaAuthStatusListener manages subscription to OCA auth status updates
type OcaAuthStatusListener struct {
stream ocaAuthStream
updatesCh chan *cline.OcaAuthState
errCh chan error
ctx context.Context
cancel context.CancelFunc
mu sync.RWMutex
lastState *cline.OcaAuthState
firstEventCh chan struct{}
firstEventOnce sync.Once
}
// NewOcaAuthStatusListener creates a new OCA auth status listener
func NewOcaAuthStatusListener(parentCtx context.Context) (*OcaAuthStatusListener, error) {
client, err := global.GetDefaultClient(parentCtx)
if err != nil {
return nil, fmt.Errorf("failed to get client: %w", err)
}
// Keep the listener alive independently of short-lived caller contexts
ctx, cancel := context.WithCancel(context.Background())
// Subscribe to OCA auth status updates
stream, err := client.Ocaaccount.OcaSubscribeToAuthStatusUpdate(ctx, &cline.EmptyRequest{})
if err != nil {
cancel()
return nil, fmt.Errorf("failed to subscribe to OCA auth updates: %w", err)
}
return &OcaAuthStatusListener{
stream: stream,
updatesCh: make(chan *cline.OcaAuthState, 10),
errCh: make(chan error, 1),
ctx: ctx,
cancel: cancel,
firstEventCh: make(chan struct{}),
}, nil
}
// Start begins listening to the auth status update stream
func (l *OcaAuthStatusListener) Start() error {
go l.readStream()
return nil
}
func (l *OcaAuthStatusListener) readStream() {
defer close(l.updatesCh)
defer close(l.errCh)
for {
select {
case <-l.ctx.Done():
return
default:
state, err := l.stream.Recv()
if err != nil {
// Propagate error and exit
if err == io.EOF {
// Treat as error to notify waiters
err = fmt.Errorf("OCA auth status stream closed")
}
select {
case l.errCh <- err:
case <-l.ctx.Done():
}
return
}
l.mu.Lock()
l.lastState = state
l.mu.Unlock()
// Notify first event waiters
l.firstEventOnce.Do(func() { close(l.firstEventCh) })
select {
case l.updatesCh <- state:
case <-l.ctx.Done():
return
}
}
}
}
// WaitForFirstEvent blocks until the first event is received or timeout occurs
func (l *OcaAuthStatusListener) WaitForFirstEvent(timeout time.Duration) error {
// Fast-path if already have a state
l.mu.RLock()
ready := l.lastState != nil
l.mu.RUnlock()
if ready {
return nil
}
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case <-l.firstEventCh:
return nil
case <-timer.C:
return fmt.Errorf("timeout waiting for initial OCA auth event")
case <-l.ctx.Done():
return fmt.Errorf("OCA auth listener cancelled")
}
}
// IsAuthenticated returns true if the last known OCA auth state is authenticated
func (l *OcaAuthStatusListener) IsAuthenticated() bool {
l.mu.RLock()
defer l.mu.RUnlock()
return isOCAStateAuthenticated(l.lastState)
}
// WaitForAuthentication waits until OCA authentication succeeds or timeout occurs
func (l *OcaAuthStatusListener) WaitForAuthentication(timeout time.Duration) error {
timer := time.NewTimer(timeout)
defer timer.Stop()
// If already authenticated, return immediately
if l.IsAuthenticated() {
return nil
}
for {
select {
case <-timer.C:
return fmt.Errorf("OCA authentication timeout after %v - please try again", timeout)
case <-l.ctx.Done():
return fmt.Errorf("OCA authentication cancelled")
case err := <-l.errCh:
return fmt.Errorf("OCA authentication stream error: %w", err)
case state := <-l.updatesCh:
if isOCAStateAuthenticated(state) {
return nil
}
}
}
}
// Stop closes the stream and cleans up resources
func (l *OcaAuthStatusListener) Stop() {
l.cancel()
}
func isOCAStateAuthenticated(state *cline.OcaAuthState) bool {
return state != nil && state.User != nil
}
// Singleton holder
var (
ocaListener *OcaAuthStatusListener
ocaListenerOnce sync.Once
ocaListenerErr error
)
// GetOcaAuthListener returns the OCA auth listener singleton
func GetOcaAuthListener(ctx context.Context) (*OcaAuthStatusListener, error) {
// Allow optional ctx: if nil, use context.TODO(). If already initialized, return singleton.
if ctx == nil {
ctx = context.TODO()
}
ocaListenerOnce.Do(func() {
l, err := NewOcaAuthStatusListener(ctx)
if err != nil {
ocaListenerErr = err
return
}
if err := l.Start(); err != nil {
ocaListenerErr = err
return
}
ocaListener = l
})
return ocaListener, ocaListenerErr
}
// IsOCAAuthenticated returns true if the global OCA auth status is authenticated.
// It attempts a brief wait for the first event to avoid stale reads.
func IsOCAAuthenticated(ctx context.Context) bool {
l, err := GetOcaAuthListener(ctx)
if err != nil {
return false
}
_ = l.WaitForFirstEvent(1 * time.Second) // best-effort
return l.IsAuthenticated()
}
// LatestState returns the last received OCA auth state (may be nil)
func (l *OcaAuthStatusListener) LatestState() *cline.OcaAuthState {
l.mu.RLock()
defer l.mu.RUnlock()
return l.lastState
}
// GetLatestOCAState returns the latest known OCA auth state, optionally waiting for the first event
func GetLatestOCAState(ctx context.Context, timeout time.Duration) (*cline.OcaAuthState, error) {
l, err := GetOcaAuthListener(ctx)
if err != nil {
return nil, err
}
if timeout > 0 {
if err := l.WaitForFirstEvent(timeout); err != nil {
return nil, err
}
}
return l.LatestState(), nil
}
// ensureOcaAuthenticated initiates OCA login (if needed) and waits for success using the singleton listener
func ensureOcaAuthenticated(ctx context.Context) error {
// Ensure listener exists
listener, err := GetOcaAuthListener(ctx)
if err != nil {
return fmt.Errorf("failed to initialize OCA auth listener: %w", err)
}
// Briefly wait for first event to know current state
_ = listener.WaitForFirstEvent(1 * time.Second)
// If already authenticated, nothing to do
if listener.IsAuthenticated() {
fmt.Println("✓ OCA authentication already active.")
return nil
}
// Create gRPC client for initiating login
client, err := global.GetDefaultClient(ctx)
if err != nil {
return fmt.Errorf("failed to obtain client: %w", err)
}
// Start login and wait for authentication
waitCtx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
// Initiate login (opens the browser with a callback URL from Cline Core)
response, err := client.Ocaaccount.OcaAccountLoginClicked(waitCtx, &cline.EmptyRequest{})
if err != nil {
return fmt.Errorf("failed to initiate OCA login: %w", err)
}
fmt.Println("\nOpening browser for OCA authentication...")
if response != nil && response.Value != "" {
fmt.Printf("If the browser doesn't open automatically, visit this URL:\n%s\n\n", response.Value)
}
fmt.Println("Waiting for you to complete OCA authentication in your browser...")
fmt.Println("(This may take a few moments. Timeout: 5 minutes)")
// Block until authenticated or timeout
if err := listener.WaitForAuthentication(5 * time.Minute); err != nil {
return err
}
fmt.Println("✓ OCA authentication successful!")
return nil
}
+5 -2
View File
@@ -123,7 +123,10 @@ func setCommand() *cobra.Command {
Use: "set <key=value> [key=value...]",
Aliases: []string{"s"},
Short: "Set configuration variables",
Long: `Set one or more global configuration variables using key=value format.`,
Long: `Set one or more global configuration variables using key=value format.
This command merges the provided settings with existing values, preserving
unspecified fields. Only the fields you explicitly set will be updated.`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
@@ -139,7 +142,7 @@ func setCommand() *cobra.Command {
return err
}
// Update settings
// Update settings (server-side merge handles preserving existing values)
return configManager.UpdateSettings(ctx, settings, secrets)
},
}
+1 -1
View File
@@ -189,7 +189,7 @@ func renderAutoApprovalSettings(value interface{}, censor bool) error {
}
}
} else {
// Print other fields normally (enabled, maxRequests, enableNotifications, favorites)
// Print other fields normally (enabled, enableNotifications, favorites)
fmt.Printf(" %s: %s\n", camelToKebab(key), formatValue(val, key, censor))
}
}
+85 -7
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/types"
@@ -14,6 +15,16 @@ type Renderer struct {
typewriter *TypewriterPrinter
mdRenderer *MarkdownRenderer
outputFormat string
// Lipgloss styles that respect outputFormat
dimStyle lipgloss.Style
greenStyle lipgloss.Style
redStyle lipgloss.Style
yellowStyle lipgloss.Style
blueStyle lipgloss.Style
whiteStyle lipgloss.Style
boldStyle lipgloss.Style
successStyle lipgloss.Style
}
func NewRenderer(outputFormat string) *Renderer {
@@ -22,11 +33,23 @@ func NewRenderer(outputFormat string) *Renderer {
mdRenderer = nil
}
return &Renderer{
r := &Renderer{
typewriter: NewTypewriterPrinter(DefaultTypewriterConfig()),
mdRenderer: mdRenderer,
outputFormat: outputFormat,
}
// Initialize lipgloss styles (will respect the global color profile)
r.dimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
r.greenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2"))
r.redStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
r.yellowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("3"))
r.blueStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39"))
r.whiteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("7"))
r.boldStyle = lipgloss.NewStyle().Bold(true)
r.successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Bold(true)
return r
}
func (r *Renderer) RenderMessage(prefix, text string, newline bool) error {
@@ -206,21 +229,76 @@ func (r *Renderer) GetMdRenderer() *MarkdownRenderer {
// RenderMarkdown renders markdown text to terminal format with ANSI codes
// Falls back to plaintext if markdown rendering is unavailable or fails
// Respects output format - skips rendering in plain mode
// Respects output format - skips rendering in plain mode or non-TTY contexts
func (r *Renderer) RenderMarkdown(markdown string) string {
// Skip markdown rendering in plain mode
if r.outputFormat == "plain" {
// Skip markdown rendering if:
// 1. Output format is explicitly "plain"
// 2. Not in a TTY (piped output, file redirect, CI, etc.)
if r.outputFormat == "plain" || !isTTY() {
return markdown
}
if r.mdRenderer == nil {
return markdown
}
rendered, err := r.mdRenderer.Render(markdown)
if err != nil {
return markdown
}
return rendered
}
// Lipgloss-based color rendering methods
// These automatically respect the output format via lipgloss color profile
// Dim renders text in dim gray (bright black)
func (r *Renderer) Dim(text string) string {
return r.dimStyle.Render(text)
}
// Green renders text in green
func (r *Renderer) Green(text string) string {
return r.greenStyle.Render(text)
}
// Red renders text in red
func (r *Renderer) Red(text string) string {
return r.redStyle.Render(text)
}
// Yellow renders text in yellow
func (r *Renderer) Yellow(text string) string {
return r.yellowStyle.Render(text)
}
// Blue renders text in 256-color blue (index 39)
func (r *Renderer) Blue(text string) string {
return r.blueStyle.Render(text)
}
// White renders text in white
func (r *Renderer) White(text string) string {
return r.whiteStyle.Render(text)
}
// Bold renders text in bold
func (r *Renderer) Bold(text string) string {
return r.boldStyle.Render(text)
}
// Success renders text in green with bold
func (r *Renderer) Success(text string) string {
return r.successStyle.Render(text)
}
// SuccessWithCheckmark renders text in green with bold and a checkmark prefix
func (r *Renderer) SuccessWithCheckmark(text string) string {
return r.Success("✓ " + text)
}
// ErrorWithX renders text in red with an X prefix
func (r *Renderer) ErrorWithX(text string) string {
return r.Red("✗ " + text)
}
+5 -5
View File
@@ -36,8 +36,8 @@ func NewStreamingSegment(sayType, prefix string, mdRenderer *MarkdownRenderer, s
toolParser: NewToolResultParser(mdRenderer),
}
// Render rich header immediately when creating segment (if in rich mode)
if shouldMarkdown && outputFormat != "plain" {
// Render rich header immediately when creating segment (if in rich mode and TTY)
if shouldMarkdown && outputFormat != "plain" && isTTY() {
header := ss.generateRichHeader()
rendered, _ := mdRenderer.Render(header)
output.Println("")
@@ -113,8 +113,8 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
} else if ss.sayType == string(types.SayTypeCommand) {
// Command output
bodyContent = "```shell\n" + currentBuffer + "\n```"
// Render markdown
if ss.shouldMarkdown && ss.outputFormat != "plain" {
// Render markdown only in rich mode and TTY
if ss.shouldMarkdown && ss.outputFormat != "plain" && isTTY() {
rendered, err := ss.mdRenderer.Render(bodyContent)
if err == nil {
bodyContent = rendered
@@ -122,7 +122,7 @@ func (ss *StreamingSegment) renderFinal(currentBuffer string) {
}
} else {
// For other types (reasoning, text, etc.), render markdown as-is
if ss.shouldMarkdown && ss.outputFormat != "plain" {
if ss.shouldMarkdown && ss.outputFormat != "plain" && isTTY() {
rendered, err := ss.mdRenderer.Render(currentBuffer)
if err == nil {
bodyContent = rendered
+14 -4
View File
@@ -106,6 +106,14 @@ func (tr *ToolRenderer) generateToolHeader(tool *types.ToolMessage, verbTense st
}
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
case string(types.ToolTypeFileDeleted):
if verbTense == "wants to" {
action = "wants to delete"
} else {
action = "is deleting"
}
return fmt.Sprintf("### Cline %s `%s`", action, tool.Path)
case string(types.ToolTypeListFilesTopLevel):
if verbTense == "wants to" {
action = "wants to list files in"
@@ -199,7 +207,7 @@ func (tr *ToolRenderer) GenerateToolContentPreview(tool *types.ToolMessage) stri
previewMd := fmt.Sprintf("```\n%s\n```", preview)
return tr.renderMarkdown(previewMd)
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch):
case string(types.ToolTypeReadFile), string(types.ToolTypeWebFetch), string(types.ToolTypeFileDeleted):
// No preview for read/fetch operations
return ""
@@ -226,7 +234,8 @@ func (tr *ToolRenderer) GenerateToolContentBody(tool *types.ToolMessage) string
toolParser := NewToolResultParser(tr.mdRenderer)
switch tool.Tool {
case string(types.ToolTypeReadFile):
case string(types.ToolTypeReadFile),
string(types.ToolTypeFileDeleted):
// readFile: show header only, no body
return ""
@@ -339,9 +348,10 @@ func (tr *ToolRenderer) RenderUserResponse(approved bool, feedback string) strin
return fmt.Sprintf("%s %s\n", symbol, status)
}
// renderMarkdown renders markdown if not in plain mode
// renderMarkdown renders markdown if not in plain mode and in a TTY
func (tr *ToolRenderer) renderMarkdown(markdown string) string {
if tr.outputFormat == "plain" {
// Skip markdown rendering if plain mode or not in TTY
if tr.outputFormat == "plain" || !isTTY() {
return markdown
}
+65
View File
@@ -0,0 +1,65 @@
package cli
import (
"fmt"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/terminal"
"github.com/cline/cli/pkg/cli/updater"
"github.com/spf13/cobra"
)
// NewDoctorCommand creates the doctor command
func NewDoctorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "doctor",
Aliases: []string{"d"},
Short: "Check system health and diagnose problems",
Long: `Check the health of your Cline CLI installation and diagnose problems.
Currently this command performs the following checks and fixes:
Terminal Configuration:
- Detects your terminal emulator (VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty)
- Configures shift+enter to insert newlines in multiline input
- Creates backups before modifying configuration files
- Supported terminals: VS Code, Cursor, Ghostty, Kitty, WezTerm, Alacritty
- iTerm2 works by default, Terminal.app requires manual setup
CLI Updates:
- Checks npm registry for the latest version
- Automatically installs updates via npm if available
- Respects NO_AUTO_UPDATE environment variable
- Skipped in CI environments
Note: Future versions will include additional health checks for Node.js version,
npm availability, Cline Core connectivity, database integrity, and more.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runDoctorChecks()
},
}
return cmd
}
// runDoctorChecks performs all doctor diagnostics and configuration
func runDoctorChecks() error {
renderer := display.NewRenderer(global.Config.OutputFormat)
fmt.Printf("\n%s\n\n", renderer.Bold("Cline Doctor - System Health Check"))
// Configure terminal keybindings (terminal.go prints its own status)
fmt.Printf("%s\n\n", renderer.Dim("━━━ Terminal Configuration ━━━"))
terminal.SetupKeyboardSync()
// Check for updates (updater.go prints its own status)
fmt.Printf("\n%s\n\n", renderer.Dim("━━━ CLI Updates ━━━"))
updater.CheckAndUpdateSync(global.Config.Verbose, true)
// Summary
fmt.Printf("\n%s\n", renderer.Dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"))
fmt.Printf("\n%s\n\n", renderer.SuccessWithCheckmark("Health check complete"))
return nil
}
+8
View File
@@ -6,8 +6,10 @@ import (
"os"
"path/filepath"
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/common"
"github.com/cline/grpc-go/client"
"github.com/muesli/termenv"
)
type Port uint16
@@ -47,6 +49,12 @@ func InitializeGlobalConfig(cfg *GlobalConfig) error {
return fmt.Errorf("failed to create config directory: %w", err)
}
// Configure lipgloss color profile based on output format
if cfg.OutputFormat == "plain" {
lipgloss.SetColorProfile(termenv.Ascii) // NO COLOR mode
}
// Otherwise lipgloss auto-detects terminal capabilities (default behavior)
Config = cfg
Clients = NewClineClients(cfg.ConfigPath)
+28 -36
View File
@@ -52,8 +52,6 @@ func (h *AskHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
return h.handleResumeCompletedTask(msg, dc)
case string(types.AskTypeMistakeLimitReached):
return h.handleMistakeLimitReached(msg, dc)
case string(types.AskTypeAutoApprovalMaxReached):
return h.handleAutoApprovalMaxReached(msg, dc)
case string(types.AskTypeBrowserActionLaunch):
return h.handleBrowserActionLaunch(msg, dc)
case string(types.AskTypeUseMcpServer):
@@ -71,22 +69,25 @@ func (h *AskHandler) Handle(msg *types.ClineMessage, dc *DisplayContext) error {
// handleFollowup handles followup questions
func (h *AskHandler) handleFollowup(msg *types.ClineMessage, dc *DisplayContext) error {
// Use ToolRenderer for unified rendering
header := dc.ToolRenderer.GenerateAskFollowupHeader()
body := dc.ToolRenderer.GenerateAskFollowupBody(msg.Text)
if body == "" {
return nil
}
// Render header
rendered := dc.Renderer.RenderMarkdown(header)
output.Print("\n")
output.Print(rendered)
output.Print("\n")
// Render body
output.Print(body)
if dc.IsStreamingMode {
// In streaming mode, header was already shown by partial stream
// Just render the body content
output.Print(body)
} else {
// Non-streaming mode: render header + body together
header := dc.ToolRenderer.GenerateAskFollowupHeader()
rendered := dc.Renderer.RenderMarkdown(header)
output.Print("\n")
output.Print(rendered)
output.Print("\n")
output.Print(body)
}
return nil
}
@@ -125,8 +126,8 @@ func (h *AskHandler) handlePlanModeRespond(msg *types.ClineMessage, dc *DisplayC
// showApprovalHint displays a hint in non-interactive mode about how to approve/deny
func (h *AskHandler) showApprovalHint(dc *DisplayContext) {
if !dc.IsInteractive {
output.Printf("\n\033[90mCline is requesting approval to use this tool\033[0m\n")
output.Printf("\033[90mUse \033[0mcline task send --approve\033[90m or \033[0m--deny\033[90m to respond\033[0m\n")
output.Printf("\n%s\n", dc.Renderer.Dim("Cline is requesting approval to use this tool"))
output.Printf("%s\n", dc.Renderer.Dim("Use cline task send --approve or --deny to respond"))
}
}
@@ -177,9 +178,19 @@ func (h *AskHandler) handleTool(msg *types.ClineMessage, dc *DisplayContext) err
return dc.Renderer.RenderMessage("TOOL", msg.Text, true)
}
// Use unified ToolRenderer
rendered := dc.ToolRenderer.RenderToolApprovalRequest(&tool)
output.Print(rendered)
if dc.IsStreamingMode {
// In streaming mode, header was already shown by partial stream
// Just render the content preview
contentPreview := dc.ToolRenderer.GenerateToolContentPreview(&tool)
if contentPreview != "" {
output.Print("\n")
output.Print(contentPreview)
}
} else {
// Non-streaming mode: render full approval (header + preview)
rendered := dc.ToolRenderer.RenderToolApprovalRequest(&tool)
output.Print(rendered)
}
h.showApprovalHint(dc)
return nil
@@ -242,25 +253,6 @@ func (h *AskHandler) handleMistakeLimitReached(msg *types.ClineMessage, dc *Disp
return dc.Renderer.RenderMessage("ERROR", fmt.Sprintf("Mistake Limit Reached: %s. Approval required.", msg.Text), true)
}
// handleAutoApprovalMaxReached handles auto-approval max reached
func (h *AskHandler) handleAutoApprovalMaxReached(msg *types.ClineMessage, dc *DisplayContext) error {
if dc.SystemRenderer != nil {
details := make(map[string]string)
if msg.Text != "" {
details["reason"] = msg.Text
}
dc.SystemRenderer.RenderError(
"warning",
"Auto-Approval Limit Reached",
"The maximum number of auto-approved requests has been reached. Manual approval is now required.",
details,
)
fmt.Printf("\n**Approval required to continue.**\n")
return nil
}
return dc.Renderer.RenderMessage("WARNING", fmt.Sprintf("Auto-approval limit reached: %s. Approval required.", msg.Text), true)
}
// handleBrowserActionLaunch handles browser action launch requests
func (h *AskHandler) handleBrowserActionLaunch(msg *types.ClineMessage, dc *DisplayContext) error {
url := strings.TrimSpace(msg.Text)
+7 -6
View File
@@ -389,20 +389,21 @@ func newInstanceListCommand() *cobra.Command {
}
// Render the markdown table with terminal width for nice table layout
renderer, err := display.NewMarkdownRendererForTerminal()
mdRenderer, err := display.NewMarkdownRendererForTerminal()
if err != nil {
// Fallback to plain table if markdown renderer fails
fmt.Println(markdown.String())
} else {
rendered, err := renderer.Render(markdown.String())
rendered, err := mdRenderer.Render(markdown.String())
if err != nil {
fmt.Println(markdown.String())
} else {
// Post-process to colorize status values
rendered = strings.ReplaceAll(rendered, "SERVING", "\033[32mSERVING\033[0m") // Green
rendered = strings.ReplaceAll(rendered, "", "\033[32m✓\033[0m") // Green
rendered = strings.ReplaceAll(rendered, "NOT_SERVING", "\033[31mNOT_SERVING\033[0m") // Red
rendered = strings.ReplaceAll(rendered, "UNKNOWN", "\033[33mUNKNOWN\033[0m") // Yellow
colorRenderer := display.NewRenderer(global.Config.OutputFormat)
rendered = strings.ReplaceAll(rendered, "SERVING", colorRenderer.Green("SERVING"))
rendered = strings.ReplaceAll(rendered, "✓", colorRenderer.Green("✓"))
rendered = strings.ReplaceAll(rendered, "NOT_SERVING", colorRenderer.Red("NOT_SERVING"))
rendered = strings.ReplaceAll(rendered, "UNKNOWN", colorRenderer.Yellow("UNKNOWN"))
fmt.Print(strings.TrimLeft(rendered, "\n"))
}
+6 -5
View File
@@ -208,9 +208,9 @@ func listLogFiles(logsDir string) ([]logFileInfo, error) {
})
}
// Sort by created time (newest first)
// Sort by created time (oldest first)
sort.Slice(logs, func(i, j int) bool {
return logs[i].created.After(logs[j].created)
return logs[i].created.Before(logs[j].created)
})
return logs, nil
@@ -340,6 +340,7 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
}
// Use markdown table for rich output
colorRenderer := display.NewRenderer(global.Config.OutputFormat)
var markdown strings.Builder
markdown.WriteString("| **FILENAME** | **SIZE** | **CREATED** | **AGE** |\n")
markdown.WriteString("|--------------|----------|-------------|---------|")
@@ -352,9 +353,9 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
row.age,
)
// If marking for deletion, wrap in red ANSI codes
// If marking for deletion, wrap in red
if markForDeletion {
line = "\033[31m" + line + "\033[0m"
line = colorRenderer.Red(line)
}
markdown.WriteString(line)
@@ -378,4 +379,4 @@ func renderLogsTable(logs []logFileInfo, markForDeletion bool) error {
fmt.Println()
return nil
}
}
+9 -8
View File
@@ -24,10 +24,11 @@ const (
// InputSubmitMsg is sent when the user submits input
type InputSubmitMsg struct {
Value string
InputType InputType
Approved bool // For approval type
NeedsFeedback bool // For approval type
Value string
InputType InputType
Approved bool // For approval type
NeedsFeedback bool // For approval type
NoAskAgain bool // For approval type - indicates "don't ask again" was selected
}
// InputCancelMsg is sent when the user cancels input (Ctrl+C)
@@ -159,8 +160,7 @@ func NewInputModel(inputType InputType, title, placeholder, currentMode string)
if inputType == InputTypeApproval {
m.approvalOptions = []string{
"Yes",
"Yes, with feedback",
"No",
"Yes, and don't ask again for this task",
"No, with feedback",
}
m.selectedOption = 0
@@ -210,8 +210,7 @@ func (m *InputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.InputType == InputTypeApproval {
m.approvalOptions = []string{
"Yes",
"Yes, with feedback",
"No",
"Yes, and don't ask again for this task",
"No, with feedback",
}
m.selectedOption = 0
@@ -298,6 +297,7 @@ func (m *InputModel) handleSubmit() (tea.Model, tea.Cmd) {
selected := m.approvalOptions[m.selectedOption]
approved := strings.HasPrefix(selected, "Yes")
needsFeedback := strings.Contains(selected, "feedback")
noAskAgain := strings.Contains(selected, "don't ask again")
if needsFeedback {
// Store the approval decision before switching to feedback input
@@ -318,6 +318,7 @@ func (m *InputModel) handleSubmit() (tea.Model, tea.Cmd) {
InputType: InputTypeApproval,
Approved: approved,
NeedsFeedback: false,
NoAskAgain: noAskAgain,
}
}
+41 -18
View File
@@ -14,6 +14,7 @@ import (
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/task"
"github.com/cline/cli/pkg/cli/updater"
"github.com/cline/grpc-go/cline"
"github.com/spf13/cobra"
)
@@ -476,15 +477,34 @@ func newTaskOpenCommand() *cobra.Command {
return fmt.Errorf("failed to parse settings: %w", err)
}
// Create config manager to apply settings
configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
if err != nil {
return fmt.Errorf("failed to create config manager: %w", err)
// Apply task-specific settings using UpdateTaskSettings RPC
if parsedSettings != nil {
_, err = taskManager.GetClient().State.UpdateTaskSettings(ctx, &cline.UpdateTaskSettingsRequest{
Settings: parsedSettings,
TaskId: &taskID,
})
if err != nil {
return fmt.Errorf("failed to apply task settings: %w", err)
}
if global.Config.Verbose {
fmt.Println("Task-specific settings applied successfully")
}
}
// Apply the settings to the instance
if err := configManager.UpdateSettings(ctx, parsedSettings, secrets); err != nil {
return fmt.Errorf("failed to apply settings: %w", err)
// Handle secrets separately if provided (they must go to global config)
if secrets != nil {
// Secrets are always global, not task-specific
configManager, err := config.NewManager(ctx, taskManager.GetCurrentInstance())
if err != nil {
return fmt.Errorf("failed to create config manager: %w", err)
}
if err := configManager.UpdateSettings(ctx, nil, secrets); err != nil {
return fmt.Errorf("failed to apply secrets: %w", err)
}
if global.Config.Verbose {
fmt.Println("Global secrets applied successfully")
}
}
}
@@ -572,17 +592,20 @@ func getContentFromStdinAndArgs(args []string) (string, error) {
// Check if data is being piped to stdin
if (stat.Mode() & os.ModeCharDevice) == 0 {
stdinBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("failed to read from stdin: %w", err)
}
stdinContent := strings.TrimSpace(string(stdinBytes))
if stdinContent != "" {
if content.Len() > 0 {
content.WriteString(" ")
// Only try to read if there's actually data available
if stat.Size() > 0 {
stdinBytes, err := io.ReadAll(os.Stdin)
if err != nil {
return "", fmt.Errorf("failed to read from stdin: %w", err)
}
stdinContent := strings.TrimSpace(string(stdinBytes))
if stdinContent != "" {
if content.Len() > 0 {
content.WriteString(" ")
}
content.WriteString(stdinContent)
}
content.WriteString(stdinContent)
}
}
@@ -649,4 +672,4 @@ func CreateAndFollowTask(ctx context.Context, prompt string, opts TaskOptions) e
} else {
return taskManager.FollowConversation(ctx, taskManager.GetCurrentInstance(), true)
}
}
}
+87 -20
View File
@@ -2,6 +2,7 @@ package task
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
@@ -9,6 +10,7 @@ import (
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/cline/cli/pkg/cli/global"
"github.com/cline/cli/pkg/cli/output"
"github.com/cline/cli/pkg/cli/types"
@@ -16,20 +18,21 @@ import (
// InputHandler manages interactive user input during follow mode
type InputHandler struct {
manager *Manager
coordinator *StreamCoordinator
cancelFunc context.CancelFunc
mu sync.RWMutex
isRunning bool
pollTicker *time.Ticker
program *tea.Program
programRunning bool
programDoneChan chan struct{} // Signals when program actually exits
resultChan chan output.InputSubmitMsg
cancelChan chan struct{}
feedbackApproval bool // Track if we're in feedback after approval
feedbackApproved bool // Track the approval decision
ctx context.Context // Context for restart callback
manager *Manager
coordinator *StreamCoordinator
cancelFunc context.CancelFunc
mu sync.RWMutex
isRunning bool
pollTicker *time.Ticker
program *tea.Program
programRunning bool
programDoneChan chan struct{} // Signals when program actually exits
resultChan chan output.InputSubmitMsg
cancelChan chan struct{}
feedbackApproval bool // Track if we're in feedback after approval
feedbackApproved bool // Track the approval decision
approvalMessage *types.ClineMessage // Store the approval message for determining action
ctx context.Context // Context for restart callback
}
// NewInputHandler creates a new input handler
@@ -162,6 +165,10 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
// Check for mode switch commands first
newMode, remainingMessage, isModeSwitch := ih.parseModeSwitch(message)
if isModeSwitch {
// Create styles for mode switch messages (respect global color profile)
actStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
planStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Bold(true)
if remainingMessage != "" {
// Switching with a message - behavior differs by mode
if newMode == "act" {
@@ -170,16 +177,14 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
output.Printf("\nError switching to act mode with message: %v\n", err)
continue
}
// 256-color index 39 for act mode (matches lipgloss color "39" in input form)
output.Printf("\n\033[38;5;39m\033[1mSwitched to act mode\033[0m\n")
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
} else {
// Plan mode: must switch first, then send message separately
if err := ih.manager.SetMode(ctx, newMode, nil, nil, nil); err != nil {
output.Printf("\nError switching to plan mode: %v\n", err)
continue
}
// Yellow color for plan mode (ANSI color 3)
output.Printf("\n\033[33m\033[1mSwitched to plan mode\033[0m\n")
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
// Now send the message separately
time.Sleep(500 * time.Millisecond) // Give mode switch time to process
@@ -196,9 +201,9 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
}
// Color based on mode
if newMode == "act" {
output.Printf("\n\033[38;5;39m\033[1mSwitched to act mode\033[0m\n")
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
} else {
output.Printf("\n\033[33m\033[1mSwitched to plan mode\033[0m\n")
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
}
}
@@ -229,6 +234,48 @@ func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
}
}
// determineAutoApprovalAction determines which auto-approval action to enable based on the ask type
func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
switch types.AskType(msg.Ask) {
case types.AskTypeTool:
// Parse tool message to determine if it's a read or edit operation
var toolMsg types.ToolMessage
if err := json.Unmarshal([]byte(msg.Text), &toolMsg); err != nil {
return "", fmt.Errorf("failed to parse tool message: %w", err)
}
// Determine action based on tool type
switch types.ToolType(toolMsg.Tool) {
case types.ToolTypeReadFile,
types.ToolTypeListFilesTopLevel,
types.ToolTypeListFilesRecursive,
types.ToolTypeListCodeDefinitionNames,
types.ToolTypeSearchFiles,
types.ToolTypeWebFetch:
return "read_files", nil
case types.ToolTypeEditedExistingFile,
types.ToolTypeNewFileCreated:
return "edit_files", nil
case types.ToolTypeFileDeleted:
return "apply_patch", nil
default:
return "", fmt.Errorf("unsupported tool type: %s", toolMsg.Tool)
}
case types.AskTypeCommand:
return "execute_all_commands", nil
case types.AskTypeBrowserActionLaunch:
return "use_browser", nil
case types.AskTypeUseMcpServer:
return "use_mcp", nil
default:
return "", fmt.Errorf("unsupported ask type: %s", msg.Ask)
}
}
// promptForInput displays an interactive prompt and waits for user input
func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error) {
currentMode := ih.manager.GetCurrentMode()
@@ -245,6 +292,9 @@ func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error
// promptForApproval displays an approval prompt for tool/command requests
func (ih *InputHandler) promptForApproval(ctx context.Context, msg *types.ClineMessage) (bool, string, error) {
// Store the approval message for later use in determining auto-approval action
ih.approvalMessage = msg
model := output.NewInputModel(
output.InputTypeApproval,
"Let Cline use this tool?",
@@ -344,6 +394,23 @@ func (ih *InputHandler) runInputProgram(ctx context.Context, model output.InputM
// Need to collect feedback - will be handled by model state change
return "", false, nil
}
// Check if NoAskAgain was selected
if result.NoAskAgain && result.Approved && ih.approvalMessage != nil {
// Determine which auto-approval action to enable
action, err := determineAutoApprovalAction(ih.approvalMessage)
if err != nil {
output.Printf("\nWarning: Could not determine auto-approval action: %v\n", err)
} else {
// Enable the auto-approval action
if err := ih.manager.UpdateTaskAutoApprovalAction(ctx, action); err != nil {
output.Printf("\nWarning: Could not update auto-approval: %v\n", err)
} else {
output.Printf("\nAuto-approval enabled for %s\n", action)
}
}
}
// Store approval state for when feedback comes back
ih.feedbackApproval = false
ih.feedbackApproved = result.Approved
+44 -15
View File
@@ -282,7 +282,6 @@ func (m *Manager) CheckSendEnabled(ctx context.Context) error {
errorTypes := []string{
string(types.AskTypeAPIReqFailed), // "api_req_failed"
string(types.AskTypeMistakeLimitReached), // "mistake_limit_reached"
string(types.AskTypeAutoApprovalMaxReached), // "auto_approval_max_req_reached"
}
isError := false
@@ -1004,26 +1003,18 @@ func (m *Manager) processStateUpdate(stateUpdate *cline.State, coordinator *Stre
case msg.Ask == string(types.AskTypePlanModeRespond):
msgKey := fmt.Sprintf("%d", msg.Timestamp)
// In streaming mode, partial stream handles this message
// State stream should skip to avoid duplication
if m.isStreamingMode {
// Skip - partial stream already handled this
} else {
// Non-streaming mode: render normally when message is complete
if !msg.Partial && !coordinator.IsProcessedInCurrentTurn(msgKey) {
m.displayMessage(msg, false, false, i)
// Non-streaming mode: render normally when message is complete
if !msg.Partial && !coordinator.IsProcessedInCurrentTurn(msgKey) {
m.displayMessage(msg, false, false, i)
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
case msg.Type == types.MessageTypeAsk:
msgKey := fmt.Sprintf("%d", msg.Timestamp)
// Only render if not already handled by partial stream
if !coordinator.IsProcessedInCurrentTurn(msgKey) {
fmt.Println()
if !msg.Partial && !coordinator.IsProcessedInCurrentTurn(msgKey) {
m.displayMessage(msg, false, false, i)
coordinator.MarkProcessedInCurrentTurn(msgKey)
}
}
@@ -1245,10 +1236,48 @@ func (m *Manager) updateMode(stateJson string) {
m.mu.Unlock()
}
// UpdateTaskAutoApprovalAction enables a specific auto-approval action for the current task
func (m *Manager) UpdateTaskAutoApprovalAction(ctx context.Context, actionKey string) error {
boolPtr := func(b bool) *bool { return &b }
settings := &cline.Settings{
AutoApprovalSettings: &cline.AutoApprovalSettings{
Actions: &cline.AutoApprovalActions{},
},
}
// Set the specific action to true based on actionKey
truePtr := boolPtr(true)
switch actionKey {
case "read_files":
settings.AutoApprovalSettings.Actions.ReadFiles = truePtr
case "edit_files":
settings.AutoApprovalSettings.Actions.EditFiles = truePtr
case "execute_all_commands":
settings.AutoApprovalSettings.Actions.ExecuteAllCommands = truePtr
case "use_browser":
settings.AutoApprovalSettings.Actions.UseBrowser = truePtr
case "use_mcp":
settings.AutoApprovalSettings.Actions.UseMcp = truePtr
default:
return fmt.Errorf("unknown auto-approval action: %s", actionKey)
}
_, err := m.client.State.UpdateTaskSettings(ctx, &cline.UpdateTaskSettingsRequest{
Settings: settings,
})
if err != nil {
return fmt.Errorf("failed to update task settings: %w", err)
}
return nil
}
// Cleanup cleans up resources
func (m *Manager) Cleanup() {
// Clean up streaming display resources if needed
if m.streamingDisplay != nil {
m.streamingDisplay.Cleanup()
}
}
}
+13 -27
View File
@@ -180,8 +180,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
settings.PlanModeHuaweiCloudMaasModelId = strPtr(value)
case "plan_mode_oca_model_id":
settings.PlanModeOcaModelId = strPtr(value)
case "plan_mode_vercel_ai_gateway_model_id":
settings.PlanModeVercelAiGatewayModelId = strPtr(value)
case "act_mode_api_model_id":
settings.ActModeApiModelId = strPtr(value)
case "act_mode_reasoning_effort":
@@ -218,8 +216,6 @@ func setSimpleField(settings *cline.Settings, key, value string) error {
settings.ActModeHuaweiCloudMaasModelId = strPtr(value)
case "act_mode_oca_model_id":
settings.ActModeOcaModelId = strPtr(value)
case "act_mode_vercel_ai_gateway_model_id":
settings.ActModeVercelAiGatewayModelId = strPtr(value)
// Boolean fields
case "aws_use_cross_region_inference":
@@ -416,24 +412,12 @@ func setNestedField(settings *cline.Settings, parentField string, childFields ma
func setAutoApprovalSettings(settings *cline.AutoApprovalSettings, fields map[string]string) error {
for key, value := range fields {
switch key {
case "enabled":
val, err := parseBool(value)
if err != nil {
return err
}
settings.Enabled = val
case "max_requests":
val, err := parseInt32(value)
if err != nil {
return err
}
settings.MaxRequests = val
case "enable_notifications":
val, err := parseBool(value)
if err != nil {
return err
}
settings.EnableNotifications = val
settings.EnableNotifications = boolPtr(val)
case "actions":
return fmt.Errorf("auto_approval_settings.actions requires nested dot notation (e.g., auto-approval-settings.actions.read-files=true)")
default:
@@ -464,21 +448,21 @@ func setAutoApprovalAction(actions *cline.AutoApprovalActions, key, value string
switch key {
case "read_files":
actions.ReadFiles = val
actions.ReadFiles = boolPtr(val)
case "read_files_externally":
actions.ReadFilesExternally = val
actions.ReadFilesExternally = boolPtr(val)
case "edit_files":
actions.EditFiles = val
actions.EditFiles = boolPtr(val)
case "edit_files_externally":
actions.EditFilesExternally = val
actions.EditFilesExternally = boolPtr(val)
case "execute_safe_commands":
actions.ExecuteSafeCommands = val
actions.ExecuteSafeCommands = boolPtr(val)
case "execute_all_commands":
actions.ExecuteAllCommands = val
actions.ExecuteAllCommands = boolPtr(val)
case "use_browser":
actions.UseBrowser = val
actions.UseBrowser = boolPtr(val)
case "use_mcp":
actions.UseMcp = val
actions.UseMcp = boolPtr(val)
default:
return fmt.Errorf("unsupported auto_approval_actions field '%s'", key)
}
@@ -672,6 +656,8 @@ func parseApiProvider(value string) (cline.ApiProvider, error) {
return cline.ApiProvider_DIFY, nil
case "oca":
return cline.ApiProvider_OCA, nil
case "minimax":
return cline.ApiProvider_MINIMAX, nil
default:
return cline.ApiProvider_ANTHROPIC, fmt.Errorf("invalid api_provider '%s'", value)
}
@@ -746,14 +732,14 @@ func setSecretField(secrets *cline.Secrets, key, value string) error {
secrets.HuaweiCloudMaasApiKey = strPtr(value)
case "baseten_api_key":
secrets.BasetenApiKey = strPtr(value)
case "vercel_ai_gateway_api_key":
secrets.VercelAiGatewayApiKey = strPtr(value)
case "dify_api_key":
secrets.DifyApiKey = strPtr(value)
case "oca_api_key":
secrets.OcaApiKey = strPtr(value)
case "oca_refresh_token":
secrets.OcaRefreshToken = strPtr(value)
case "hicap_api_key":
secrets.HicapApiKey = strPtr(value)
default:
return fmt.Errorf("unsupported secret field '%s'", key)
}
+695
View File
@@ -0,0 +1,695 @@
package terminal
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/cline/cli/pkg/cli/display"
"github.com/cline/cli/pkg/cli/global"
)
// KeyboardProtocol manages enhanced keyboard protocol support for detecting
// modified keys like shift+enter across all major terminals.
type KeyboardProtocol struct {
enabled bool
mu sync.Mutex
}
var globalProtocol = &KeyboardProtocol{}
// EnableEnhancedKeyboard enables enhanced keyboard protocols to support
// shift+enter and other modified keys across all major terminals:
// - VS Code integrated terminal
// - iTerm2
// - Terminal.app
// - Ghostty
// - Kitty
// - WezTerm
// - Alacritty
// - foot
// - xterm
//
// This function is safe to call multiple times and handles cleanup automatically.
// It enables both modifyOtherKeys (xterm protocol) and Kitty keyboard protocol
// for maximum compatibility.
func EnableEnhancedKeyboard() {
globalProtocol.mu.Lock()
defer globalProtocol.mu.Unlock()
if globalProtocol.enabled {
return // Already enabled
}
// Check if we're in a TTY (not piped/redirected)
if !isatty(os.Stdin.Fd()) {
return
}
// Enable modifyOtherKeys mode 2
// This tells xterm-compatible terminals (VS Code, iTerm2, Terminal.app, etc.)
// to send escape sequences for modified keys including shift+enter
// Format: CSI > 4 ; 2 m
// - Mode 2 enables for ALL keys including well-known ones
fmt.Print("\x1b[>4;2m")
// Also enable Kitty keyboard protocol for terminals that support it
// This is a more modern protocol supported by Kitty, Ghostty, WezTerm, foot, etc.
// Format: CSI = <flags> u where flags=1 means "disambiguate escape codes"
// This makes shift+enter distinguishable from plain enter
fmt.Print("\x1b[=1u")
globalProtocol.enabled = true
}
// DisableEnhancedKeyboard restores the terminal to its default keyboard mode.
// This should be called on program exit to be a good citizen.
func DisableEnhancedKeyboard() {
globalProtocol.mu.Lock()
defer globalProtocol.mu.Unlock()
if !globalProtocol.enabled {
return
}
// Disable modifyOtherKeys (restore to mode 0)
fmt.Print("\x1b[>4;0m")
// Disable Kitty keyboard protocol
fmt.Print("\x1b[<u")
globalProtocol.enabled = false
}
// isatty checks if a file descriptor is a terminal
func isatty(fd uintptr) bool {
// Use the standard library's terminal package
// This works across all platforms (Unix, Windows, etc.)
fileInfo, err := os.Stdin.Stat()
if err != nil {
return false
}
return (fileInfo.Mode() & os.ModeCharDevice) != 0
}
// SetupKeyboard detects the current terminal and configures keybindings if needed.
// Runs in background and doesn't block. Prints status when configs are modified.
func SetupKeyboard() {
go func() {
renderer := display.NewRenderer(global.Config.OutputFormat)
setupKeyboardInternal(renderer)
}()
}
// SetupKeyboardSync is the synchronous version used by doctor command.
// Blocks until complete and prints status for all terminals.
func SetupKeyboardSync() {
renderer := display.NewRenderer(global.Config.OutputFormat)
setupKeyboardInternal(renderer)
}
func setupKeyboardInternal(renderer *display.Renderer) {
terminalName := DetectTerminal()
switch terminalName {
case "vscode":
// VS Code and Cursor use the same TERM_PROGRAM value
modified, path := SetupVSCodeKeybindings()
if modified {
fmt.Printf("%s VS Code %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ VS Code shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
modified, path = SetupCursorKeybindings()
if modified {
fmt.Printf("%s Cursor %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ Cursor shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
case "ghostty":
modified, path := SetupGhosttyKeybindings()
if modified {
fmt.Printf("%s Ghostty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
fmt.Printf("%s\n", renderer.Dim(" Fully restart Ghostty (quit all windows) for changes to take effect"))
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ Ghostty shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
case "wezterm":
modified, path := SetupWezTermKeybindings()
if modified {
fmt.Printf("%s WezTerm %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ WezTerm shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
case "alacritty":
modified, path := SetupAlacrittyKeybindings()
if modified {
fmt.Printf("%s Alacritty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ Alacritty shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
case "kitty":
modified, path := SetupKittyKeybindings()
if modified {
fmt.Printf("%s Kitty %s\n", renderer.Dim("Configured shift+enter for"), renderer.Dim("terminal"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
} else if path != "" {
fmt.Printf("%s\n", renderer.Dim("✓ Kitty shift+enter already configured"))
fmt.Printf("%s %s\n", renderer.Dim(" →"), path)
}
case "iterm2":
fmt.Printf("%s\n", renderer.Dim("✓ iTerm2 shift+enter works by default (maps to alt+enter)"))
case "terminal.app":
fmt.Printf("%s\n", renderer.Dim("⚠ Terminal.app requires manual configuration"))
fmt.Printf("%s\n", renderer.Dim(" See: Terminal → Preferences → Profiles → Keyboard"))
case "unknown":
fmt.Printf("%s\n", renderer.Dim(" Terminal not detected - use alt+enter or ctrl+j for newlines"))
}
}
// getVSCodeConfigPath returns the platform-specific path to VS Code's User directory
func getVSCodeConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
switch runtime.GOOS {
case "darwin":
return filepath.Join(home, "Library", "Application Support", "Code", "User"), nil
case "windows":
appData := os.Getenv("APPDATA")
if appData == "" {
appData = filepath.Join(home, "AppData", "Roaming")
}
return filepath.Join(appData, "Code", "User"), nil
default: // linux, freebsd, etc.
return filepath.Join(home, ".config", "Code", "User"), nil
}
}
// getCursorConfigPath returns the platform-specific path to Cursor's User directory
func getCursorConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
switch runtime.GOOS {
case "darwin":
return filepath.Join(home, "Library", "Application Support", "Cursor", "User"), nil
case "windows":
appData := os.Getenv("APPDATA")
if appData == "" {
appData = filepath.Join(home, "AppData", "Roaming")
}
return filepath.Join(appData, "Cursor", "User"), nil
default: // linux, freebsd, etc.
return filepath.Join(home, ".config", "Cursor", "User"), nil
}
}
// DetectTerminal identifies which terminal emulator is currently running
func DetectTerminal() string {
// Check TERM_PROGRAM (works for most terminals)
termProgram := os.Getenv("TERM_PROGRAM")
switch termProgram {
case "vscode":
return "vscode" // Also covers Cursor (uses same value)
case "WezTerm":
return "wezterm"
case "ghostty":
return "ghostty"
case "iTerm.app":
return "iterm2"
case "Apple_Terminal":
return "terminal.app"
}
// Kitty doesn't set TERM_PROGRAM, check KITTY_WINDOW_ID
if os.Getenv("KITTY_WINDOW_ID") != "" {
return "kitty"
}
// Alacritty doesn't set TERM_PROGRAM, check ALACRITTY_SOCKET
if os.Getenv("ALACRITTY_SOCKET") != "" {
return "alacritty"
}
// Ghostty fallback (cross-platform - more reliable than TERM_PROGRAM)
if os.Getenv("GHOSTTY_RESOURCES_DIR") != "" {
return "ghostty"
}
// Alacritty fallback
if os.Getenv("ALACRITTY_LOG") != "" {
return "alacritty"
}
// Check TERM variable as last resort
term := os.Getenv("TERM")
if strings.Contains(term, "kitty") {
return "kitty"
}
if term == "alacritty" {
return "alacritty"
}
if term == "xterm-ghostty" {
return "ghostty"
}
return "unknown"
}
// VSCodeKeybinding represents a VS Code keyboard shortcut
type VSCodeKeybinding struct {
Key string `json:"key"`
Command string `json:"command"`
Args map[string]interface{} `json:"args,omitempty"`
When string `json:"when,omitempty"`
}
// SetupVSCodeKeybindings adds shift+enter support to VS Code's integrated terminal
// by modifying the user's keybindings.json file.
// Returns (wasModified, configPath) to allow caller to log the change.
func SetupVSCodeKeybindings() (bool, string) {
// Get platform-specific VS Code config path
configDir, err := getVSCodeConfigPath()
if err != nil {
return false, ""
}
keybindingsPath := filepath.Join(configDir, "keybindings.json")
// Check if VS Code is installed (keybindings file or parent dir exists)
if _, err := os.Stat(filepath.Dir(keybindingsPath)); os.IsNotExist(err) {
// VS Code not installed, skip silently
return false, ""
}
// Read existing keybindings
var keybindings []VSCodeKeybinding
data, err := os.ReadFile(keybindingsPath)
if err != nil {
if !os.IsNotExist(err) {
return false, ""
}
// File doesn't exist, start with empty array
keybindings = []VSCodeKeybinding{}
} else {
// Parse existing keybindings
if err := json.Unmarshal(data, &keybindings); err != nil {
// If parse fails, don't modify the file
return false, ""
}
}
// Check if shift+enter binding already exists
for _, kb := range keybindings {
if kb.Key == "shift+enter" && kb.Command == "workbench.action.terminal.sendSequence" {
// Already configured
return false, keybindingsPath
}
}
// Add shift+enter keybinding
newBinding := VSCodeKeybinding{
Key: "shift+enter",
Command: "workbench.action.terminal.sendSequence",
Args: map[string]interface{}{
"text": "\u001b\n", // ESC + newline (alt+enter sequence)
},
When: "terminalFocus",
}
keybindings = append(keybindings, newBinding)
// Create backup
if data != nil {
backupPath := keybindingsPath + ".backup"
_ = os.WriteFile(backupPath, data, 0644)
}
// Write updated keybindings
updatedData, err := json.MarshalIndent(keybindings, "", " ")
if err != nil {
return false, ""
}
// Ensure parent directory exists
if err := os.MkdirAll(filepath.Dir(keybindingsPath), 0755); err != nil {
return false, ""
}
if err := os.WriteFile(keybindingsPath, updatedData, 0644); err != nil {
return false, ""
}
return true, keybindingsPath
}
// SetupCursorKeybindings adds shift+enter support to Cursor's integrated terminal
// by modifying the user's keybindings.json file.
// Cursor is a fork of VS Code, so it uses the same keybinding format.
// Returns (wasModified, configPath) to allow caller to log the change.
func SetupCursorKeybindings() (bool, string) {
// Get platform-specific Cursor config path
configDir, err := getCursorConfigPath()
if err != nil {
return false, ""
}
keybindingsPath := filepath.Join(configDir, "keybindings.json")
// Check if Cursor is installed (keybindings file or parent dir exists)
if _, err := os.Stat(filepath.Dir(keybindingsPath)); os.IsNotExist(err) {
// Cursor not installed, skip silently
return false, ""
}
// Read existing keybindings
var keybindings []VSCodeKeybinding
data, err := os.ReadFile(keybindingsPath)
if err != nil {
if !os.IsNotExist(err) {
return false, ""
}
// File doesn't exist, start with empty array
keybindings = []VSCodeKeybinding{}
} else {
// Parse existing keybindings
if err := json.Unmarshal(data, &keybindings); err != nil {
// If parse fails, don't modify the file
return false, ""
}
}
// Check if shift+enter binding already exists
for _, kb := range keybindings {
if kb.Key == "shift+enter" && kb.Command == "workbench.action.terminal.sendSequence" {
// Already configured
return false, keybindingsPath
}
}
// Add shift+enter keybinding
newBinding := VSCodeKeybinding{
Key: "shift+enter",
Command: "workbench.action.terminal.sendSequence",
Args: map[string]interface{}{
"text": "\u001b\n", // ESC + newline (alt+enter sequence)
},
When: "terminalFocus",
}
keybindings = append(keybindings, newBinding)
// Create backup
if data != nil {
backupPath := keybindingsPath + ".backup"
_ = os.WriteFile(backupPath, data, 0644)
}
// Write updated keybindings
updatedData, err := json.MarshalIndent(keybindings, "", " ")
if err != nil {
return false, ""
}
// Ensure parent directory exists
if err := os.MkdirAll(filepath.Dir(keybindingsPath), 0755); err != nil {
return false, ""
}
if err := os.WriteFile(keybindingsPath, updatedData, 0644); err != nil {
return false, ""
}
return true, keybindingsPath
}
// SetupGhosttyKeybindings adds shift+enter support to Ghostty terminal
// by appending to the user's config file.
// Returns (wasModified, configPath) to allow caller to log the change.
func SetupGhosttyKeybindings() (bool, string) {
home, err := os.UserHomeDir()
if err != nil {
return false, ""
}
// Ghostty config location: ~/.config/ghostty/config
configPath := filepath.Join(home, ".config", "ghostty", "config")
// Check if config directory exists
configDir := filepath.Dir(configPath)
if _, err := os.Stat(configDir); os.IsNotExist(err) {
// Ghostty not installed, skip silently
return false, ""
}
// Read existing config if it exists
var existingContent []byte
if data, err := os.ReadFile(configPath); err == nil {
existingContent = data
// Check if shift+enter already configured
if strings.Contains(string(data), "keybind = shift+enter") {
return false, configPath
}
}
// Keybinding to add - send newline character (0x0a)
// Ghostty requires \x0a hex escape syntax, verified working
keybinding := "keybind = shift+enter=text:\\x0a\n"
// Append to config
newContent := append(existingContent, []byte(keybinding)...)
// Ensure directory exists
if err := os.MkdirAll(configDir, 0755); err != nil {
return false, ""
}
// Create backup if file exists
if existingContent != nil {
backupPath := configPath + ".backup"
_ = os.WriteFile(backupPath, existingContent, 0644)
}
// Write updated config
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
return false, ""
}
return true, configPath
}
// SetupWezTermKeybindings adds shift+enter support to WezTerm
// by appending to the user's .wezterm.lua file.
// Returns (wasModified, configPath)
func SetupWezTermKeybindings() (bool, string) {
home, err := os.UserHomeDir()
if err != nil {
return false, ""
}
configPath := filepath.Join(home, ".wezterm.lua")
// Check if WezTerm config exists
if _, err := os.Stat(configPath); os.IsNotExist(err) {
// WezTerm not configured, skip silently
return false, ""
}
// Read existing config
data, err := os.ReadFile(configPath)
if err != nil {
return false, ""
}
// Check if shift+enter already configured
if strings.Contains(string(data), "key = 'Enter'") && strings.Contains(string(data), "mods = 'SHIFT'") {
return false, configPath
}
// Create backup
backupPath := configPath + ".backup"
_ = os.WriteFile(backupPath, data, 0644)
// Keybinding to add (insert before final return statement)
keybinding := `
-- Shift+Enter for newlines (added by Cline CLI)
config.keys = config.keys or {}
table.insert(config.keys, {
key = 'Enter',
mods = 'SHIFT',
action = wezterm.action.SendString '\x1b\n',
})
`
content := string(data)
// Try to insert before the final return statement
if strings.Contains(content, "return config") {
content = strings.Replace(content, "return config", keybinding+"\nreturn config", 1)
} else {
// No return statement, append at end
content += keybinding
}
// Write updated config
if err := os.WriteFile(configPath, []byte(content), 0644); err != nil {
return false, ""
}
return true, configPath
}
// SetupAlacrittyKeybindings adds shift+enter support to Alacritty
// by appending to the user's alacritty.yml file.
// Returns (wasModified, configPath)
func SetupAlacrittyKeybindings() (bool, string) {
home, err := os.UserHomeDir()
if err != nil {
return false, ""
}
// Try both possible locations
configPaths := []string{
filepath.Join(home, ".config", "alacritty", "alacritty.yml"),
filepath.Join(home, ".config", "alacritty", "alacritty.toml"),
filepath.Join(home, ".alacritty.yml"),
}
var configPath string
for _, path := range configPaths {
if _, err := os.Stat(path); err == nil {
configPath = path
break
}
}
if configPath == "" {
// Alacritty not configured, skip silently
return false, ""
}
// Read existing config
data, err := os.ReadFile(configPath)
if err != nil {
return false, ""
}
// Check if shift+enter already configured
if strings.Contains(string(data), "key: Return") && strings.Contains(string(data), "mods: Shift") {
return false, configPath
}
// Create backup
backupPath := configPath + ".backup"
_ = os.WriteFile(backupPath, data, 0644)
// Keybinding to add
var keybinding string
if strings.HasSuffix(configPath, ".yml") || strings.HasSuffix(configPath, ".yaml") {
keybinding = `
# Shift+Enter for newlines (added by Cline CLI)
key_bindings:
- { key: Return, mods: Shift, chars: "\x1b\n" }
`
} else {
// TOML format
keybinding = `
# Shift+Enter for newlines (added by Cline CLI)
[[keyboard.bindings]]
key = "Return"
mods = "Shift"
chars = "\x1b\n"
`
}
// Append to config
newContent := append(data, []byte(keybinding)...)
// Write updated config
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
return false, ""
}
return true, configPath
}
// SetupKittyKeybindings adds shift+enter support to Kitty terminal
// by appending to the user's kitty.conf file.
// Returns (wasModified, configPath)
func SetupKittyKeybindings() (bool, string) {
home, err := os.UserHomeDir()
if err != nil {
return false, ""
}
configPath := filepath.Join(home, ".config", "kitty", "kitty.conf")
// Check if config directory exists
configDir := filepath.Dir(configPath)
if _, err := os.Stat(configDir); os.IsNotExist(err) {
// Kitty not installed, skip silently
return false, ""
}
// Read existing config if it exists
var existingContent []byte
if data, err := os.ReadFile(configPath); err == nil {
existingContent = data
// Check if shift+enter already configured
if strings.Contains(string(data), "map shift+enter") {
return false, configPath
}
}
// Keybinding to add
keybinding := "# Shift+Enter for newlines (added by Cline CLI)\nmap shift+enter send_text all \\x1b\\n\n"
// Append to config
newContent := append(existingContent, []byte(keybinding)...)
// Ensure directory exists
if err := os.MkdirAll(configDir, 0755); err != nil {
return false, ""
}
// Create backup if file exists
if existingContent != nil {
backupPath := configPath + ".backup"
_ = os.WriteFile(backupPath, existingContent, 0644)
}
// Write updated config
if err := os.WriteFile(configPath, newContent, 0644); err != nil {
return false, ""
}
return true, configPath
}
+11 -13
View File
@@ -37,17 +37,16 @@ const (
type AskType string
const (
AskTypeFollowup AskType = "followup"
AskTypePlanModeRespond AskType = "plan_mode_respond"
AskTypeCommand AskType = "command"
AskTypeCommandOutput AskType = "command_output"
AskTypeCompletionResult AskType = "completion_result"
AskTypeTool AskType = "tool"
AskTypeAPIReqFailed AskType = "api_req_failed"
AskTypeResumeTask AskType = "resume_task"
AskTypeResumeCompletedTask AskType = "resume_completed_task"
AskTypeMistakeLimitReached AskType = "mistake_limit_reached"
AskTypeAutoApprovalMaxReached AskType = "auto_approval_max_req_reached"
AskTypeFollowup AskType = "followup"
AskTypePlanModeRespond AskType = "plan_mode_respond"
AskTypeCommand AskType = "command"
AskTypeCommandOutput AskType = "command_output"
AskTypeCompletionResult AskType = "completion_result"
AskTypeTool AskType = "tool"
AskTypeAPIReqFailed AskType = "api_req_failed"
AskTypeResumeTask AskType = "resume_task"
AskTypeResumeCompletedTask AskType = "resume_completed_task"
AskTypeMistakeLimitReached AskType = "mistake_limit_reached"
AskTypeBrowserActionLaunch AskType = "browser_action_launch"
AskTypeUseMcpServer AskType = "use_mcp_server"
AskTypeNewTask AskType = "new_task"
@@ -108,6 +107,7 @@ const (
ToolTypeEditedExistingFile ToolType = "editedExistingFile"
ToolTypeNewFileCreated ToolType = "newFileCreated"
ToolTypeReadFile ToolType = "readFile"
ToolTypeFileDeleted ToolType = "fileDeleted"
ToolTypeListFilesTopLevel ToolType = "listFilesTopLevel"
ToolTypeListFilesRecursive ToolType = "listFilesRecursive"
ToolTypeListCodeDefinitionNames ToolType = "listCodeDefinitionNames"
@@ -247,8 +247,6 @@ func convertProtoAskType(askType cline.ClineAsk) string {
return string(AskTypeResumeCompletedTask)
case cline.ClineAsk_MISTAKE_LIMIT_REACHED:
return string(AskTypeMistakeLimitReached)
case cline.ClineAsk_AUTO_APPROVAL_MAX_REQ_REACHED:
return string(AskTypeAutoApprovalMaxReached)
case cline.ClineAsk_BROWSER_ACTION_LAUNCH:
return string(AskTypeBrowserActionLaunch)
case cline.ClineAsk_USE_MCP_SERVER:
+40 -6
View File
@@ -68,7 +68,7 @@ func CheckAndUpdate(isVerbose bool) {
// Run in background so we don't block CLI startup
go func() {
if err := checkAndUpdateSync(); err != nil {
if err := checkAndUpdateInternal(false); err != nil {
if verbose {
output.Printf("[updater] Update check failed: %v\n", err)
}
@@ -76,15 +76,49 @@ func CheckAndUpdate(isVerbose bool) {
}()
}
func checkAndUpdateSync() error {
// CheckAndUpdateSync performs a synchronous update check (blocks until complete).
// If bypassCache is true, ignores the 24-hour cache and always checks npm registry.
// This is used by the doctor command.
func CheckAndUpdateSync(isVerbose bool, bypassCache bool) {
verbose = isVerbose
// Skip in CI environments
if os.Getenv("CI") != "" {
if verbose {
output.Printf("[updater] Skipping update check (CI environment)\n")
}
return
}
// Skip if user disabled auto-updates
if os.Getenv("NO_AUTO_UPDATE") != "" {
if verbose {
output.Printf("[updater] Skipping update check (NO_AUTO_UPDATE set)\n")
}
return
}
if verbose {
output.Printf("[updater] Starting update check...\n")
}
// Run synchronously
if err := checkAndUpdateInternal(bypassCache); err != nil {
if verbose {
output.Printf("[updater] Update check failed: %v\n", err)
}
}
}
func checkAndUpdateInternal(bypassCache bool) error {
if verbose {
output.Printf("[updater] Loading update cache...\n")
}
// Load cache
cache, err := loadCache()
if err == nil && time.Since(cache.LastCheck) < checkInterval {
// Checked recently, skip
if !bypassCache && err == nil && time.Since(cache.LastCheck) < checkInterval {
// Checked recently, skip (unless cache is bypassed)
if verbose {
output.Printf("[updater] Cache is fresh (last checked %v ago), skipping\n", time.Since(cache.LastCheck))
}
@@ -341,7 +375,7 @@ func showFailureMessage(channel string) {
func getCacheFilePath() string {
configDir := filepath.Join(os.Getenv("HOME"), ".cline", "data")
return filepath.Join(configDir, ".update-cache")
return filepath.Join(configDir, "cli-update-cache")
}
func loadCache() (cacheData, error) {
@@ -372,4 +406,4 @@ func saveCache(cache cacheData) error {
}
return os.WriteFile(cacheFile, data, 0644)
}
}
+27 -1
View File
@@ -4,7 +4,9 @@ import (
"context"
"fmt"
"net"
"os/exec"
"strconv"
"strings"
"time"
"google.golang.org/grpc"
@@ -124,6 +126,16 @@ func NormalizeAddressForGRPC(address string) (string, error) {
return address, nil
}
// GetNodeVersion returns the current Node.js version, or "unknown" if unable to detect
func GetNodeVersion() string {
cmd := exec.Command("node", "--version")
output, err := cmd.Output()
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(output))
}
// RetryOperation performs an operation with retry logic
func RetryOperation(maxRetries int, timeoutPerAttempt time.Duration, operation func() error) error {
var lastErr error
@@ -155,5 +167,19 @@ func RetryOperation(maxRetries int, timeoutPerAttempt time.Duration, operation f
}
}
return fmt.Errorf("operation failed after %d attempts: %w", maxRetries, lastErr)
return fmt.Errorf(`operation failed to after %d attempts: %w
This is usually caused by an incompatible Node.js version
REQUIREMENTS:
• Node.js version 20+ is required
• Current Node.js version: %s
DEBUGGING STEPS:
1. View recent logs: cline log list
2. Logs are available in: ~/.cline/logs/
3. The most recent cline-core log file is usually valuable
For additional help, visit: https://github.com/cline/cline/issues
`, maxRetries, lastErr, GetNodeVersion())
}
+116 -1
View File
@@ -144,6 +144,8 @@ const (
OPENAI_NATIVE = "openai-native"
XAI = "xai"
CEREBRAS = "cerebras"
OCA = "oca"
NOUSRESEARCH = "nousResearch"
)
// AllProviders returns a slice of enabled provider IDs for the CLI build.
@@ -159,6 +161,8 @@ var AllProviders = []string{
"openai-native",
"xai",
"cerebras",
"oca",
"nousResearch",
}
// ConfigField represents a configuration field requirement
@@ -316,6 +320,15 @@ var rawConfigFields = ` [
"fieldType": "password",
"placeholder": "Enter your API key"
},
{
"name": "nousResearchApiKey",
"type": "string",
"comment": "",
"category": "nousResearch",
"required": true,
"fieldType": "password",
"placeholder": "Enter your API key"
},
{
"name": "ulid",
"type": "string",
@@ -433,6 +446,15 @@ var rawConfigFields = ` [
"fieldType": "url",
"placeholder": "https://api.example.com"
},
{
"name": "minimaxApiLine",
"type": "string",
"comment": "",
"category": "general",
"required": false,
"fieldType": "string",
"placeholder": ""
},
{
"name": "ocaMode",
"type": "string",
@@ -441,7 +463,16 @@ var rawConfigFields = ` [
"required": false,
"fieldType": "string",
"placeholder": ""
}
},
{
"name": "hicapApiKey",
"type": "string",
"comment": "",
"category": "general",
"required": true,
"fieldType": "password",
"placeholder": "Enter your API key"
},
]`
// Raw model definitions data (parsed from TypeScript)
@@ -467,6 +498,16 @@ var rawModelDefinitions = ` {
"supportsImages": true,
"supportsPromptCache": true
},
"claude-haiku-4-5-20251001": {
"maxTokens": 8192,
"contextWindow": 200000,
"inputPrice": 1,
"outputPrice": 5,
"cacheWritesPrice": 1,
"cacheReadsPrice": 0,
"supportsImages": true,
"supportsPromptCache": true
},
"claude-sonnet-4-20250514": {
"maxTokens": 8192,
"contextWindow": 200000,
@@ -579,6 +620,16 @@ var rawModelDefinitions = ` {
"supportsImages": true,
"supportsPromptCache": true
},
"anthropic.claude-haiku-4-5-20251001-v1:0": {
"maxTokens": 8192,
"contextWindow": 200000,
"inputPrice": 1,
"outputPrice": 5,
"cacheWritesPrice": 1,
"cacheReadsPrice": 0,
"supportsImages": true,
"supportsPromptCache": true
},
"anthropic.claude-sonnet-4-20250514-v1:0": {
"maxTokens": 8192,
"contextWindow": 200000,
@@ -744,6 +795,24 @@ var rawModelDefinitions = ` {
"supportsImages": false,
"supportsPromptCache": false,
"description": "A compact 20B open-weight Mixture-of-Experts language model designed for strong reasoning and tool use, ideal for edge devices and local inference."
},
"qwen.qwen3-coder-30b-a3b-v1:0": {
"maxTokens": 8192,
"contextWindow": 262144,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "Qwen3 Coder 30B MoE model with 3.3B activated parameters, optimized for code generation and analysis with 256K context window."
},
"qwen.qwen3-coder-480b-a35b-v1:0": {
"maxTokens": 8192,
"contextWindow": 262144,
"inputPrice": 0,
"outputPrice": 1,
"supportsImages": false,
"supportsPromptCache": false,
"description": "Qwen3 Coder 480B flagship MoE model with 35B activated parameters, designed for complex coding tasks with advanced reasoning capabilities and 256K context window."
}
},
"gemini": {
@@ -1232,6 +1301,26 @@ var rawModelDefinitions = ` {
"supportsPromptCache": false,
"description": "SOTA performance with ~1500 tokens/s"
}
},
"nousResearch": {
"Hermes-4-405B": {
"maxTokens": 8192,
"contextWindow": 128000,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "This is the largest model in the Hermes 4 family, and it is the fullest expression of our design, focused on advanced reasoning and creative depth rather than optimizing inference speed or cost."
},
"Hermes-4-70B": {
"maxTokens": 8192,
"contextWindow": 128000,
"inputPrice": 0,
"outputPrice": 0,
"supportsImages": false,
"supportsPromptCache": false,
"description": "This incarnation of Hermes 4 balances scale and size. It handles complex reasoning tasks, while staying fast and cost effective. A versatile choice for many use cases."
}
}
}`
@@ -1389,6 +1478,30 @@ func GetProviderDefinitions() (map[string]ProviderDefinition, error) {
HasDynamicModels: false,
SetupInstructions: `Get your API key from https://cloud.cerebras.ai/`,
}
// Oca
definitions["oca"] = ProviderDefinition{
ID: "oca",
Name: "Oca",
RequiredFields: getFieldsByProvider("oca", configFields, true),
OptionalFields: getFieldsByProvider("oca", configFields, false),
Models: modelDefinitions["oca"],
DefaultModelID: "",
HasDynamicModels: false,
SetupInstructions: `Configure Oca API credentials`,
}
// NousResearch
definitions["nousResearch"] = ProviderDefinition{
ID: "nousResearch",
Name: "NousResearch",
RequiredFields: getFieldsByProvider("nousResearch", configFields, true),
OptionalFields: getFieldsByProvider("nousResearch", configFields, false),
Models: modelDefinitions["nousResearch"],
DefaultModelID: "Hermes-4-405B",
HasDynamicModels: false,
SetupInstructions: `Configure NousResearch API credentials`,
}
return definitions, nil
}
@@ -1415,6 +1528,8 @@ func GetProviderDisplayName(providerID string) string {
"openai-native": "OpenAI",
"xai": "X AI (Grok)",
"cerebras": "Cerebras",
"oca": "Oca",
"nousResearch": "NousResearch",
}
if name, exists := displayNames[providerID]; exists {
+48
View File
@@ -0,0 +1,48 @@
# Git
.git
.gitignore
.gitattributes
# Node modules
node_modules
npm-debug.log
# Build artifacts
dist
dist-standalone
build
*.log
# Generated code
src/generated
# CLI build artifacts
cli/bin
cli/dist
# Webview build artifacts
webview-ui/dist
webview-ui/build
# IDE
.vscode
.idea
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Documentation
*.md
!README.md
# Tests
tests
*.test.js
*.spec.js
# CI/CD
.github
.gitlab-ci.yml
+49
View File
@@ -0,0 +1,49 @@
FROM node:22-slim
# TARGETARCH enables multi-architecture support without emulation warnings:
# - Docker automatically sets TARGETARCH to the build platform's architecture
# - On arm64 machines (Apple Silicon): TARGETARCH=arm64, uses linux-arm64 binaries
# - On amd64 machines (Intel/AMD): TARGETARCH=amd64, uses linux-x64 binaries
# The corresponding platform-specific binaries and native modules (better-sqlite3)
# are pre-built by scripts/package-standalone.mjs during the build process.
ARG TARGETARCH
# Install only runtime dependencies
RUN apt-get update && apt-get install -y \
git curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/cline
# Copy the entire pre-built distribution
COPY dist-standalone/ ./
# Create symlink for Linux native modules
# Map Docker's TARGETARCH (arm64/amd64) to Node's platform naming (x64 for amd64)
RUN if [ "$TARGETARCH" = "amd64" ]; then \
ln -sf /opt/cline/binaries/linux-x64/node_modules/better-sqlite3 /opt/cline/node_modules/better-sqlite3; \
else \
ln -sf /opt/cline/binaries/linux-$TARGETARCH/node_modules/better-sqlite3 /opt/cline/node_modules/better-sqlite3; \
fi
# Set up CLI binaries
# The Linux binaries are already in /opt/cline/bin/ from dist-standalone
# Just need to create symlinks to the platform-specific ones
RUN cd /opt/cline/bin && \
ln -sf cline-linux-$TARGETARCH cline && \
ln -sf cline-host-linux-$TARGETARCH cline-host && \
chmod +x cline-linux-$TARGETARCH cline-host-linux-$TARGETARCH cline cline-host
# Add binaries to PATH
ENV PATH="/opt/cline/bin:${PATH}"
ENV NODE_ENV=production
ENV CLINE_HOME=/root/.cline
RUN mkdir -p $CLINE_HOME
WORKDIR /workspace
EXPOSE 8000
ENTRYPOINT ["/opt/cline/bin/cline"]
CMD ["--help"]
+14 -12
View File
@@ -3,19 +3,21 @@ title: "Installation"
description: "Install Cline CLI and authenticate with your account"
---
<Tabs>
<Tab title="NPM">
```bash
npm install -g cline
```
</Tab>
## Prerequisites
<Tab title="CURL">
```bash
curl -fsSL https://raw.githubusercontent.com/cline/cline/main/scripts/install.sh | bash
```
</Tab>
</Tabs>
Cline CLI requires Node.js version 20 or higher. We recommend using Node.js 22 for the best experience.
To check your Node.js version:
```bash
node --version
```
## Installation
```bash
npm install -g cline
```
After installation, authenticate with your Cline account:
+16
View File
@@ -19,6 +19,22 @@ The CLI tracks instances across your system and outputs in formats designed for
Ready to get started? Check out the [installation guide](/cline-cli/installation) to install Cline CLI and run your first task.
</Tip>
## Supported Model Providers
Cline CLI supports multiple AI model providers, giving you flexibility in choosing the best model for your needs:
- **Anthropic**
- **OpenAI**
- **OpenAI Compatible**
- **OpenRouter**
- **X AI (Grok)**
- **AWS Bedrock**
- **Google Gemini**
- **Ollama**
- **Cerebras**
During installation, you'll authenticate and configure your preferred provider using the `cline auth` command.
## What you can build with this
**Automated code maintenance**
@@ -0,0 +1,324 @@
---
title: "GitHub Actions Integration"
description: "Automatically respond to GitHub issues by mentioning @cline in comments using Cline CLI in GitHub Actions."
---
# GitHub Integration Sample
Automate GitHub issue analysis with AI. Mention `@cline` in any issue comment to trigger an autonomous investigation that reads files, analyzes code, and provides actionable insights - all running automatically in GitHub Actions.
<Note>
**New to Cline CLI?** This sample assumes you understand Cline CLI basics and have completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation). If you're new to Cline CLI, we recommend starting with the [GitHub RCA sample](../github-issue-rca) first, as it's simpler and will help you understand the fundamentals before setting up GitHub Actions.
</Note>
## The Workflow
Trigger Cline by mentioning `@cline` in any issue comment:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss0a-comment.png" alt="Issue comment with @cline mention" width="600" />
</Frame>
Cline's automated analysis appears as a new comment, with insights drawn from your actual codebase:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss0b-final.png" alt="Automated analysis response from Cline" width="600" />
</Frame>
The entire investigation runs autonomously in GitHub Actions - from file exploration to posting results.
Let's configure your repository.
## Prerequisites
Before you begin, you'll need:
- **Cline CLI knowledge** - Completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and understand basic usage
- **GitHub repository** - With admin access to configure Actions and secrets
- **GitHub Actions familiarity** - Basic understanding of workflows and CI/CD
- **API provider account** - OpenRouter, Anthropic, or similar with API key
## Setup
### 1. Copy the Workflow File
Copy the workflow file from this sample to your repository. The workflow file must be placed in the `.github/workflows/` directory in your repository root for GitHub Actions to detect and run it. In this case, we'll name it `cline-responder.yml`.
```bash
# In your repository root
mkdir -p .github/workflows
curl -o .github/workflows/cline-responder.yml https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-integration/cline-responder.yml
```
Alternatively, you can copy the full workflow file directly into `.github/workflows/cline-responder.yml`:
<Accordion title="Click to view the complete cline-responder.yml workflow">
```yaml
name: Cline Issue Assistant
on:
issue_comment:
types: [created, edited]
permissions:
issues: write
jobs:
respond:
runs-on: ubuntu-latest
environment: cline-actions
steps:
- name: Check for @cline mention
id: detect
uses: actions/github-script@v7
with:
script: |
const body = context.payload.comment?.body || "";
const isPR = !!context.payload.issue?.pull_request;
const hit = body.toLowerCase().includes("@cline");
core.setOutput("hit", (!isPR && hit) ? "true" : "false");
core.setOutput("issue_number", String(context.payload.issue?.number || ""));
core.setOutput("issue_url", context.payload.issue?.html_url || "");
core.setOutput("comment_body", body);
- name: Checkout repository
if: steps.detect.outputs.hit == 'true'
uses: actions/checkout@v4
# Node v20 is needed for Cline CLI on GitHub Actions Linux
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Cline CLI
if: steps.detect.outputs.hit == 'true'
run: |
# Install the Cline CLI
sudo npm install -g cline
- name: Create Cline Instance
if: steps.detect.outputs.hit == 'true'
env:
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
CLINE_DIR: ${{ runner.temp }}/cline
run: |
# Create instance and capture output
INSTANCE_OUTPUT=$(cline instance new 2>&1)
# Parse address from output (format: " Address: 127.0.0.1:36733")
CLINE_ADDRESS=$(echo "$INSTANCE_OUTPUT" | grep "Address:" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+')
echo "CLINE_ADDRESS=$CLINE_ADDRESS" >> $GITHUB_ENV
# Configure API key
cline config set open-router-api-key=$OPENROUTER_API_KEY --address $CLINE_ADDRESS -v
- name: Download analyze script
if: steps.detect.outputs.hit == 'true'
run: |
export GITORG="YOUR-GITHUB-ORG"
export GITREPO="YOUR-GITHUB-REPO"
curl -L https://raw.githubusercontent.com/${GITORG}/${GITREPO}/refs/heads/main/git-scripts/analyze-issue.sh -o analyze-issue.sh
chmod +x analyze-issue.sh
- name: Run analysis
if: steps.detect.outputs.hit == 'true'
id: analyze
env:
ISSUE_URL: ${{ steps.detect.outputs.issue_url }}
COMMENT: ${{ steps.detect.outputs.comment_body }}
CLINE_ADDRESS: ${{ env.CLINE_ADDRESS }}
run: |
set -euo pipefail
RESULT=$(./analyze-issue.sh "${ISSUE_URL}" "Analyze this issue. The user asked: ${COMMENT}" "$CLINE_ADDRESS")
{
echo 'result<<EOF'
printf "%s\n" "$RESULT"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Post response
if: steps.detect.outputs.hit == 'true'
uses: actions/github-script@v7
env:
ISSUE_NUMBER: ${{ steps.detect.outputs.issue_number }}
RESULT: ${{ steps.analyze.outputs.result }}
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.ISSUE_NUMBER),
body: process.env.RESULT || "(no output)"
});
```
</Accordion>
<Warning>
**You MUST edit the workflow file before committing!**
Open `.github/workflows/cline-responder.yml` and update the "Download analyze script" step within the workflow to specify your GitHub organization and repository where the analysis script is stored:
```yaml
export GITORG="YOUR-GITHUB-ORG" # Change this!
export GITREPO="YOUR-GITHUB-REPO" # Change this!
```
**Example:** If your repository is `github.com/acme/myproject`, set:
```yaml
export GITORG="acme"
export GITREPO="myproject"
```
This tells the workflow where to download the analysis script from your repository after you commit it in step 3.
</Warning>
The workflow will look for new or updated issues, check for `@cline` mentions, and then
start up an instance of the Cline CLI to dig into the issue, providing feedback
as a reply to the issue.
### 2. Configure API Keys
Add your AI provider API keys as repository secrets:
1. Go to your GitHub repository
2. Navigate to **Settings** → **Environment** and Add a new environment.
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss01-environment.png" alt="Navigate to Actions secrets" width="600" />
</Frame>
Make sure to name it "cline-actions" so that it matches the `environment`
value at the top of the `cline-responder.yml` file.
3. Click **New repository secret**
4. Add a secret for the `OPENROUTER_API_KEY` with a value of an API key from
[openrouter.com](https://openrouter.com).
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss02-api-key.png" alt="Add API key secret" width="600" />
</Frame>
5. Verify your secret is configured:
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/ss03-ready.png" alt="API key configured" width="600" />
</Frame>
Now you're ready to supply Cline with the credentials it needs in a GitHub Action.
### 3. Add Analysis Script
Add the analysis script from the `github-issue-rca` sample to your repository. **First, you'll need to create a `git-scripts` directory in your repository root where the script will be located.** Choose one of these options:
**Option A: Download directly (Recommended)**
```bash
# In your repository root, create the directory and download the script
mkdir -p git-scripts
curl -o git-scripts/analyze-issue.sh https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
chmod +x git-scripts/analyze-issue.sh
```
**Option B: Manual copy-paste**
Create the directory and file manually, then paste the script content:
```bash
# In your repository root
mkdir -p git-scripts
# Create and edit the file with your preferred editor
nano git-scripts/analyze-issue.sh # or use vim, code, etc.
```
<Accordion title="Click to view the complete analyze-issue.sh script">
```bash
#!/bin/bash
# Analyze a GitHub issue using Cline CLI
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
exit 1
fi
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
After pasting the script content, make it executable:
```bash
chmod +x git-scripts/analyze-issue.sh
```
</Accordion>
This analysis script calls Cline to execute a prompt on a GitHub issue,
summarizing the output to populate the reply to the issue.
### 4. Commit and Push
```bash
git add .github/workflows/cline-responder.yml
git add git-scripts/analyze-issue.sh
git commit -m "Add Cline issue assistant workflow"
git push
```
## Usage
Once set up, simply mention `@cline` in any issue comment:
```
@cline what's causing this error?
@cline analyze the root cause
@cline what are the security implications?
```
GitHub Actions will:
1. Detect the `@cline` mention
2. Start a Cline CLI instance
3. Download the analysis script
4. Analyze the issue using act mode with yolo (fully autonomous)
5. Post Cline's analysis as a new comment
**Note**: The workflow only triggers on issue comments, not pull request
comments.
## How It Works
The workflow (`cline-responder.yml`):
1. **Triggers** on issue comments (created or edited)
2. **Detects** `@cline` mentions (case-insensitive)
3. **Installs** Cline CLI globally using npm
4. **Creates** a Cline instance using `cline instance new`
5. **Configures** authentication using `cline config set open-router-api-key=...
--address ...`
6. **Downloads** the reusable `analyze-issue.sh` script from the
`github-issue-rca` sample
7. **Runs** analysis with the instance address
8. **Posts** the analysis result as a comment
## Related Samples
- **[github-issue-rca](./github-issue-rca)**: The reusable script that powers this integration
+383
View File
@@ -0,0 +1,383 @@
---
title: "GitHub Issue RCA Sample"
description: "Automated GitHub issue analysis using Cline CLI to identify root causes."
---
# GitHub Root Cause Analysis
Automated GitHub issue analysis using Cline CLI. This script uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues, outputting clean, parseable results that can be easily integrated into your development workflows.
<Note>
**New to Cline CLI?** This sample assumes you have already completed the [Installation Guide](https://docs.cline.bot/cline-cli/installation) and authenticated with `cline auth`. If you haven't set up Cline CLI yet, please start there first.
</Note>
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/cli-rca.gif" alt="CLI Root Cause Analysis Demo" width="600" />
</Frame>
## Prerequisites
This sample assumes you have already:
- **Cline CLI** installed and authenticated ([Installation Guide](https://docs.cline.bot/cline-cli/installation))
- **At least one AI model provider** configured (e.g., OpenRouter, Anthropic, OpenAI)
- **Basic familiarity** with Cline CLI commands
Additionally, you'll need:
- **GitHub CLI** (`gh`) installed and authenticated
- **jq** installed for JSON parsing
- **bash** shell (or compatible shell)
### Installation Instructions
#### macOS
<Note>
These instructions require [Homebrew](https://brew.sh/) to be installed. If you don't have Homebrew, install it first by running:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
</Note>
```bash
# Install GitHub CLI
brew install gh
# Install jq
brew install jq
# Authenticate with GitHub
gh auth login
```
#### Linux
```bash
# Install GitHub CLI (Debian/Ubuntu)
sudo apt install gh
# Or for other Linux distributions, see: https://cli.github.com/manual/installation
# Install jq (Debian/Ubuntu)
sudo apt install jq
# Authenticate with GitHub
gh auth login
```
## Getting the Script
**Option 1: Download directly with curl**
```bash
curl -O https://raw.githubusercontent.com/cline/cline/main/src/samples/cli/github-issue-rca/analyze-issue.sh
```
**Option 2: Copy the full script**
<Accordion title="Click to view the complete analyze-issue.sh script">
```bash
#!/bin/bash
# Analyze a GitHub issue using Cline CLI
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause of this issue?' 127.0.0.1:46529"
exit 1
fi
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
# Ask Cline for its analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
</Accordion>
<Note>
**After downloading or creating the script**, make it executable by running:
```bash
chmod +x analyze-issue.sh
```
</Note>
## Quick Usage Examples
### Basic Usage
Run this command in your terminal from the directory where you saved the script to analyze an issue with the default root cause prompt:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/123
```
This will:
- Fetch issue #123 from the repository
- Analyze the issue to identify root causes
- Provide detailed analysis with recommendations
### Custom Analysis Prompt
Ask specific questions about the issue:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/456 "What is the security impact?"
```
### Using Specific Cline Instance
Target a particular Cline instance by address:
```bash
./analyze-issue.sh https://github.com/owner/repo/issues/123 \
"What is the root cause of this issue?" \
127.0.0.1:46529
```
<Warning>
This is useful when:
- Running multiple Cline instances
- Using a remote Cline server
- Testing with specific configurations
</Warning>
<Note>
The script will automatically handle everything: fetching the issue, analyzing it with Cline, and displaying the results. The analysis typically takes 30-60 seconds depending on the issue complexity.
</Note>
## How It Works
Let's analyze each component of the script to understand how it works.
### Argument Validation
The script validates input and provides usage instructions:
```bash
if [ -z "$1" ]; then
echo "Usage: $0 <github-issue-url> [prompt] [address]"
echo "Example: $0 https://github.com/owner/repo/issues/123"
echo "Example: $0 https://github.com/owner/repo/issues/123 'What is the root cause?'"
echo "Example: $0 https://github.com/owner/repo/issues/123 'Analyze security impact' 127.0.0.1:46529"
exit 1
fi
```
**Key Points:**
- Validates required GitHub issue URL
- Shows clear usage examples
- Supports optional custom prompt
- Supports optional Cline instance address
### Argument Parsing
The script extracts and sets up the arguments:
```bash
# Gather the args
ISSUE_URL="$1"
PROMPT="${2:-What is the root cause of this issue?}"
if [ -n "$3" ]; then
ADDRESS="--address $3"
fi
```
**Explanation:**
- `ISSUE_URL="$1"` - First argument is always the issue URL
- `PROMPT="${2:-...}"` - Second argument is optional, defaults to root cause analysis
- `ADDRESS` - Third argument is optional, only set if provided
### The Core Analysis Pipeline
This is where the magic happens:
```bash
# Ask Cline for his analysis, showing only the summary
cline -y "$PROMPT: $ISSUE_URL" --mode act $ADDRESS -F json | \
sed -n '/^{/,$p' | \
jq -r 'select(.say == "completion_result") | .text' | \
sed 's/\\n/\n/g'
```
<Accordion title="Pipeline Breakdown: Understanding Each Component">
**1. `cline -y "$PROMPT: $ISSUE_URL"`**
- `-y` enables yolo mode (no user interaction)
- Constructs prompt with issue URL
**2. `--mode act`**
- Enables act mode for active investigation
- Allows Cline to use tools (read files, run commands, etc.)
**3. `$ADDRESS`**
- Optional address flag for specific instance
- Expands to `--address <ip:port>` if set
**4. `-F json`**
- Outputs in JSON format for parsing
**5. `sed -n '/^{/,$p'`**
- Extracts JSON from output
- Skips any non-JSON prefix lines
**6. `jq -r 'select(.say == "completion_result") | .text'`**
- Filters for completion result messages
- Extracts the text field
- `-r` outputs raw strings (no JSON quotes)
**7. `sed 's/\\n/\n/g'`**
- Converts escaped newlines to actual newlines
- Makes output readable
</Accordion>
## Sample Output
Here's an example analyzing a real Flutter issue:
```bash
$ ./analyze-issue.sh https://github.com/csells/flutter_counter/issues/2
```
**Output:**
```markdown
**Root Cause Analysis of Issue #2: "setState isn't cutting it"**
After examining the GitHub issue and analyzing the Flutter counter codebase,
I've identified the root cause of why setState() is insufficient for this
project's needs:
## Current Implementation Problems
The current Flutter counter app uses setState() for state management, which
has several limitations:
1. **Local State Only**: setState() only works within a single widget, making
it difficult to share state across the app
2. **Rebuild Overhead**: Every setState() call rebuilds the entire widget tree,
causing performance issues with complex UIs
3. **No State Persistence**: State is lost when the widget is disposed
4. **Testing Challenges**: setState-based logic is tightly coupled to the UI,
making unit testing difficult
## Why This Matters
As the app grows beyond a simple counter, these limitations become critical:
- Multiple screens need to access the count
- State needs to persist across navigation
- Business logic should be testable independently
- UI should only rebuild when necessary
## Recommended Solutions
The issue mentions "Provider or Bloc" - both are excellent alternatives:
1. **Provider**: Simple, lightweight state management using InheritedWidget
- Easy migration path from setState
- Good for small to medium apps
- Official Flutter recommendation
2. **Bloc**: More structured approach with clear separation between events,
states, and business logic
- Better for complex apps
- Excellent testability
- Clear architectural patterns
3. **Riverpod**: Modern alternative to Provider with better performance and
developer experience
- Compile-time safety
- Better testing support
- More flexible than Provider
4. **GetX**: Full-featured solution with state management, routing, and
dependency injection
- Minimal boilerplate
- Fast and lightweight
- All-in-one solution
## Next Steps
The current codebase needs refactoring to implement proper state management
architecture to handle more complex state scenarios effectively. Provider
would be the easiest migration path while Bloc provides better long-term
scalability.
```
## When to Use This Pattern
This script pattern is ideal for various development scenarios where automated GitHub issue analysis can accelerate your workflow.
### Bug Investigation
Quickly analyze bug reports and identify root causes without manual code exploration:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/123 \
"What is the root cause of this bug?"
```
### Feature Request Analysis
Understand context and implications of feature requests:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/456 \
"What are the implementation challenges?"
```
### Security Audits
Assess security implications of reported issues:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/789 \
"What are the security implications?"
```
### Documentation Generation
Generate detailed technical documentation from issues:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/654 \
"Provide detailed technical documentation for this issue"
```
### Code Review Assistance
Get second opinions on proposed changes:
```bash
./analyze-issue.sh https://github.com/project/repo/issues/987 \
"Review the proposed solution approach"
```
## Conclusion
This sample demonstrates how to build an autonomous GitHub issue analysis tool using Cline CLI:
1. **Building autonomous CLI tools** using Cline's capabilities
2. **Parsing structured JSON output** from Cline CLI
3. **Creating flexible automation scripts** with custom prompting
4. **Integrating with GitHub** for issue analysis
5. **Handling command-line arguments** effectively
This pattern can be adapted for many other automation scenarios, from pull request reviews to documentation generation to code quality analysis.
## Related Resources
- [CLI Installation Guide](https://docs.cline.bot/cline-cli/installation)
- [CLI Reference Documentation](https://docs.cline.bot/cline-cli/cli-reference)
- [Three Core Flows](https://docs.cline.bot/cline-cli/three-core-flows)
+32
View File
@@ -0,0 +1,32 @@
---
title: "Samples Overview"
description: Example implementations demonstrating Cline CLI capabilities
---
This section provides sample implementations that demonstrate various Cline CLI features and capabilities. Each sample includes complete code, detailed explanations, and real-world usage examples.
## Available Samples
<CardGroup cols={1}>
<Card
title="GitHub Root Cause Analysis"
icon="magnifying-glass-chart"
href="/cline-cli/samples/github-issue-rca"
>
A command-line script that uses Cline's autonomous AI capabilities to fetch, analyze, and identify root causes of GitHub issues. Features JSON output parsing and non-interactive execution.
</Card>
<Card
title="GitHub Integration (Actions)"
icon="github"
href="/cline-cli/samples/github-integration"
>
Automatically respond to GitHub issues by mentioning @cline in comments. Uses Cline CLI in GitHub Actions to create an AI-powered issue assistant that analyzes and responds autonomously.
</Card>
</CardGroup>
## Additional Resources
- [CLI Installation Guide](/cline-cli/installation)
- [CLI Reference Documentation](/cline-cli/cli-reference)
- [Three Core Flows](/cline-cli/three-core-flows)
+13
View File
@@ -88,6 +88,14 @@
"cline-cli/overview",
"cline-cli/installation",
"cline-cli/three-core-flows",
{
"group": "CLI Samples",
"pages": [
"cline-cli/samples/overview",
"cline-cli/samples/github-issue-rca",
"cline-cli/samples/github-integration"
]
},
"cline-cli/cli-reference"
]
},
@@ -130,6 +138,7 @@
"features/drag-and-drop",
"features/editing-messages",
"features/focus-chain",
"features/hooks",
"features/multiroot-workspace",
"features/plan-and-act",
{
@@ -315,6 +324,10 @@
{
"source": "/getting-started/your-first-task",
"destination": "/getting-started/your-first-project"
},
{
"source": "/cline-cli/samples",
"destination": "/cline-cli/samples/overview"
}
],
"search": {
+14
View File
@@ -81,6 +81,20 @@ your-project/
Cline's system prompt, on the other hand, is not user-editable ([here's where you can find it](https://github.com/cline/cline/blob/main/src/core/prompts/system.ts)). For a broader look at prompt engineering best practices, check out [this resource](https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview).
### AGENTS.md Standard Support
Cline also supports the [AGENTS.md](https://agents.md/) standard as a fallback
(in addition to Cline Rules) by automatically detecting `AGENTS.md` files in
your workspace root. This allows you to use the same rules file across different AI
coding tools.
```
your-project/
├── AGENTS.md
├── src/
└── ...
```
### Tips for Writing Effective Cline Rules
- Be Clear and Concise: Use simple language and avoid ambiguity.
@@ -42,17 +42,17 @@ To open Cline in the right sidebar:
4. Set the value to `vertical`
5. Restart Cursor for the changes to take effect
</Step>
<Step title="Open Agent Panel">
Click the Cursor cube icon button that opens Cursor's agent (right side view panel)
<Step title="Open the AI Pane">
Click the Cursor cube icon button (AI Pane) that opens Cursor's agent (right side view panel)
</Step>
<Step title="Drag to Three Dots">
Drag the Cline icon directly onto the three dots button - it doesn't work if you just drag it to the top, it has to be the three dots
<Step title="Drag Cline to the AI Pane Sidebar">
Drag the Cline icon directly into the AI Pane sidebar.
</Step>
</Steps>
<Frame>
<img
src="https://storage.googleapis.com/cline_public_images/cursor-side-bar.gif"
src="https://storage.googleapis.com/cline_public_images/Cursor-sidebar.gif"
alt="Cursor Right Sidebar Setup"
/>
</Frame>
+5 -2
View File
@@ -3,7 +3,7 @@ title: "Dictation"
description: "Communicate with Cline using your voice for faster, more natural AI collaboration"
---
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about unlocking the kind of fluid collaboration that typing can't match.
Dictation transforms how you work with AI. Instead of typing out complex thoughts, you speak naturally and share your complete intent. This isn't just about speed - though voice is faster - it's about enabling fluid collaboration that typing can't match.
## Why Voice Changes Everything
@@ -35,11 +35,14 @@ Dictation works with any AI model you've configured. The transcription happens t
## System Requirements
<Note>
Dictation is currently not available on Windows. Support for Windows is planned for a future release.
</Note>
Dictation uses FFmpeg to capture your voice across all platforms:
- **macOS**: FFmpeg (via Homebrew: `brew install ffmpeg`)
- **Linux**: FFmpeg (via apt: `sudo apt-get install ffmpeg`)
- **Windows**: FFmpeg (via winget: `winget install Gyan.FFmpeg`)
If you don't have FFmpeg installed, Cline will automatically detect this and prompt you to install it with a single click.
+419
View File
@@ -0,0 +1,419 @@
---
title: "Hooks"
sidebarTitle: "Hooks"
description: "Inject custom logic into Cline's workflow to validate operations, monitor tool usage, and shape AI decisions"
---
Hooks let you inject custom logic into Cline's workflow at key moments. Think of them as automated checkpoints where you can validate operations before they execute, monitor tool usage as it happens, and shape how Cline makes decisions.
Hooks run automatically when specific events happen during development. They receive detailed information about each operation, can block problematic actions before they cause issues, and can inject context that guides future AI decisions.
The real power comes from combining these capabilities. You can:
- Stop operations before they cause problems (like creating `.js` files in a TypeScript project)
- Learn from what's happening and build up project knowledge over time
- Monitor performance and catch issues as they emerge
- Track everything for analytics or compliance
- Trigger external tools or services at the right moments
<Warning>
Hooks are currently supported on macOS and Linux only. Windows support is not available.
</Warning>
## Getting Started
<Frame>
<img src="https://storage.googleapis.com/cline_public_images/hooks.gif" alt="Hooks in action" />
</Frame>
Enabling hooks in Cline is straightforward. Here's what you need to do:
<Steps>
<Step title="Enable Hooks in Settings">
Open Cline settings and check the **"Enable Hooks"** checkbox.
You can find this setting by:
1. Opening Cline
2. Click the "Settings" button on the top right corner
3. Click the "Feature" section in the left side navigation menu.
4. Scroll down until you see the "Enable Hooks" checkbox and check it.
</Step>
<Step title="Choose Your Hook Location">
Decide where to place your hooks:
**For personal or organization-wide hooks:**
- Create hooks in `~/Documents/Cline/Rules/Hooks/`
- These apply to all workspaces automatically
**For project-specific hooks:**
- Create hooks in `.clinerules/hooks/` in your project root
- These only apply to the specific workspace
- Commit them to version control so your team can use them too
</Step>
<Step title="Create Your First Hook">
Hook files must have exact names with no file extensions. For example, to create a TaskStart hook:
```bash
# Create the hook file
vim .clinerules/hooks/TaskStart
```
Add your script (must start with shebang)
``` bash
#!/usr/bin/env bash
# Store piped input into a variable
input=$(cat)
# Dump the entire JSON payload
echo "$input" | jq .
# Get the type of a field
echo "$input" | jq -r '.timestamp | type'
```
This example script demonstrates the key mechanics of hook input/output: reading the JSON payload from stdin with `input=$(cat)`, and using `jq` to inspect the data structure and field types that your hook receives. This helps you understand what data is available before building more complex hook logic.
**Make it executable**
```bash
chmod +x .clinerules/hooks/TaskStart
```
</Step>
<Step title="Test Your Hook">
Start a task in Cline and verify your hook executes.
</Step>
</Steps>
<Tip>
Start with a simple hook that just logs information before building complex validation logic. This helps you understand the data structure and timing.
</Tip>
## What You Can Build
Once you understand the basics, hooks open up creative possibilities:
<CardGroup cols={2}>
<Card title="Intelligent Code Review" icon="code-branch">
Run linters or custom validators before files get saved. Block commits that don't pass checks. Track code quality metrics over time.
</Card>
<Card title="Security Enforcement" icon="shield-halved">
Prevent operations that violate security policies. Detect when sensitive data might be exposed. Audit all file access for compliance.
</Card>
<Card title="Development Analytics" icon="chart-line">
Measure how long different operations take. Identify patterns in how the AI works. Generate productivity reports from hook data.
</Card>
<Card title="Integration Hub" icon="plug">
Connect to issue trackers when certain keywords appear. Update project management tools. Sync with external APIs at the right moments.
</Card>
</CardGroup>
The key is combining hooks with external tools. A hook can be the glue between Cline's workflow and the rest of your development ecosystem.
## Hook Types
Cline provides multiple hook types that let you tap into different stages of the AI workflow. They're organized into categories based on their trigger points and use cases.
<Note>
The hook names below are the exact file names you need to create. For example, to use the TaskStart hook, create a file named `TaskStart` (no file extension) in your hooks directory.
</Note>
Each hook receives base fields in addition to its specific data: `clineVersion`, `hookName`, `timestamp`, `taskId`, `workspaceRoots`, `userId`.
### Tool Execution
These hooks intercept and validate tool operations before and after they execute. Use them to enforce policies, track changes, and learn from operations.
#### PreToolUse
Runs before any tool executes. Use it to block invalid operations, validate parameters, and enforce project policies before changes happen.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PreToolUse",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"preToolUse": {
"toolName": "string",
"parameters": {}
}
}
```
#### PostToolUse
Runs after a tool completes. Use it to learn from results, track performance metrics, and build project knowledge based on operations performed.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PostToolUse",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"postToolUse": {
"toolName": "string",
"parameters": {},
"result": "string",
"success": boolean,
"executionTimeMs": number
}
}
```
### User Interaction
These hooks monitor and enhance user communication with Cline. Use them to validate input, inject context, and track interaction patterns.
#### UserPromptSubmit
Runs when a user sends a message to Cline. Use it to validate input, inject context based on the prompt, and track interaction patterns.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "UserPromptSubmit",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"userPromptSubmit": {
"prompt": "string",
"attachments": ["string"]
}
}
```
### Task Lifecycle
These hooks monitor and respond to task state changes from start to finish. Use them to track progress, restore state, and trigger workflows.
#### TaskStart
Runs when a new task begins. Use it to detect project type, initialize tracking, and inject initial context that shapes how Cline approaches the work.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskStart",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskStart": {
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"initialTask": "string"
}
}
}
```
#### TaskResume
Runs when a task resumes after interruption. Use it to restore state, refresh context, and log resumption for analytics or external system notifications.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskResume",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskResume": {
"taskMetadata": {
"taskId": "string",
"ulid": "string"
},
"previousState": {
"lastMessageTs": "string",
"messageCount": "string",
"conversationHistoryDeleted": "string"
}
}
}
```
#### TaskCancel
Runs when a task is cancelled. Use it to cleanup resources, log cancellation details, and notify external systems about interrupted work.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskCancel",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskCancel": {
"taskMetadata": {
"taskId": "string",
"ulid": "string",
"completionStatus": "string"
}
}
}
```
{/*
#### TaskComplete
Runs when a task finishes successfully. Use it for final cleanup, tracking metrics, generating reports, and triggering post-task workflows.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "TaskComplete",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"taskComplete": {
"taskMetadata": {
"taskId": "string",
"ulid": "string"
}
}
}
```
*/}
### System Events
These hooks monitor internal Cline operations and system-level events. Use them to track context usage, log system behavior, and analyze performance patterns.
{/*
#### PreCompact
Runs before conversation context is truncated to fit token limits. Use it to monitor compaction frequency, log events, and track context usage patterns.
**Input Fields:**
```json
{
"clineVersion": "string",
"hookName": "PreCompact",
"timestamp": "string",
"taskId": "string",
"workspaceRoots": ["string"],
"userId": "string",
"preCompact": {
"contextSize": number,
"messagesToCompact": number,
"compactionStrategy": "string"
}
}
```
*/}
### JSON Communication
Hooks receive JSON via stdin and return JSON via stdout.
**Output structure:**
```json
{
"cancel": false,
"contextModification": "WORKSPACE_RULES: Use TypeScript",
"errorMessage": "Error details if blocking"
}
```
Your hook script can output logging or diagnostic information to stdout during execution, as long as the JSON response is the last thing written. Cline will parse only the final JSON object from stdout.
For example:
```bash
#!/usr/bin/env bash
echo "Processing hook..." # This is fine
echo "Tool: $tool_name" # This is also fine
# The JSON must be last:
echo '{"cancel": false}'
```
The `cancel` field controls whether execution continues. Set it to `true` to block an action, `false` to allow it.
The `contextModification` field injects text into the conversation. This affects future AI decisions, not the current one. Use prefixes like `WORKSPACE_RULES:` or `PERFORMANCE:` to help categorize the context.
### Understanding Context Timing
Context injection affects future decisions, not current ones. When a hook runs:
1. The AI has already decided what to do
2. The hook can block or allow it
3. Any context gets added to the conversation
4. The next AI request sees that context
This means PreToolUse hooks are for blocking bad actions, while PostToolUse hooks are for learning from completed ones.
## Troubleshooting
### Hook Not Running
- Ensure the "Enable Hooks" setting is checked
- Verify the hook file is executable (`chmod +x hookname`)
- Check the hook file has no syntax errors
- Look for errors in VSCode's Output panel (Cline channel)
### Hook Timing Out
- Reduce complexity of the hook script
- Avoid expensive operations (network calls, heavy computations)
- Consider moving complex logic to a background process
### Context Not Affecting Behavior
Remember that context modifications affect future AI decisions, not the current operation. The AI's current behavior is based on the previous "API Request..." block, and your `contextModification` gets injected into the next "API Request..." block. This means if you need immediate effect, you should use PreToolUse hooks for validation and return `cancel: true` in your hook's JSON response to block Cline from continuing.
When adding context, ensure your modifications are clear and actionable so the AI can understand and apply them effectively. Also check that your context isn't being truncated due to the 50KB limit, as this could prevent important information from reaching the AI.
### Handling Strings with Quotes in JSON Payloads
When your hook needs to include strings containing unescaped quote characters (`"`) in JSON output, use jq's `--arg` flag for proper escaping:
```bash
#!/usr/bin/env bash
# When $output contains unescaped quote characters (")...
output='{"foo":"bar"}'
# Use the --arg flag for automatic string escaping
jq -n --arg ctx "$output" '{cancel: false, contextModification: $ctx}'
# This will result in:
# {
# "cancel": false,
# "contextModification": "{\"foo\":\"bar\"}"
# }
```
The `--arg` flag automatically escapes special characters, preventing JSON parsing errors when your context modification includes complex strings or nested JSON structures.
<Warning>
Hooks run with the same permissions as VS Code. They can access all workspace files and environment variables. Review hooks from untrusted sources before enabling them.
</Warning>
## Related Features
Hooks complement other Cline features:
- [Cline Rules](/features/cline-rules) define high-level guidance that hooks can enforce
- [Checkpoints](/features/checkpoints) let you roll back changes if a hook didn't catch an issue
- [Auto-Approve](/features/auto-approve) works well with hooks as safety nets for automated operations
+1 -1
View File
@@ -35,7 +35,7 @@ Create a simple website in a single HTML file. It should have:
```
<Frame>
<img src="/assets/installation/chat-prompt.png" alt="Cline Chat Prompt"/>
<img src="https://storage.googleapis.com/cline_public_images/chat-prompt.png" alt="Cline Chat Prompt"/>
</Frame>
Press Enter and watch Cline work!
+58 -6
View File
@@ -29,11 +29,33 @@ The "Remote Servers" tab allows you to connect to any MCP server that's accessib
2. Fill in the required information:
- **Server Name**: Provide a unique, descriptive name for the server
- **Server URL**: Enter the complete URL endpoint of the MCP server (e.g., `https://example.com/mcp-sse`)
- **Transport Type**: Select the connection protocol (Streamable HTTP is recommended for modern servers)
3. Click "Add Server" to initiate the connection
4. Cline will attempt to connect to the server and display the connection status
> **Note**: When connecting to a remote server, ensure you trust the source, as MCP servers can execute code in your environment.
#### Transport Types
Cline supports two transport protocols for remote MCP servers:
- **Streamable HTTP (Recommended)**: The modern MCP transport protocol with better performance, reliability, and full OAuth 2.1 authentication support. Use this for most remote servers.
- **SSE (Legacy)**: Server-Sent Events transport. Use this only if the server specifically requires SSE or doesn't support Streamable HTTP.
#### OAuth Authentication
Some MCP servers (like Vercel's MCP) require OAuth authentication to access your data securely. When connecting to an OAuth-enabled server:
1. Add the server as usual with its URL
2. If the server requires authentication, you'll see an error message asking to authenticate.
3. Click the **"Authenticate"** button that appears
4. Your browser will open to the server's authorization page
5. Sign in and grant permission
6. You'll be redirected back to Cline automatically
7. The server will connect and show a green status dot
Once authenticated, your credentials are securely stored and the server will reconnect automatically when you reload Cline. You won't need to authenticate again unless you delete the server or your credentials expire.
### Remote Server Discovery
If you're looking for MCP servers to connect to, several third-party marketplaces provide directories of available servers with various capabilities.
@@ -90,9 +112,20 @@ Toggle the switch next to each server to enable or disable it:
If a server fails to connect:
1. An error message will be displayed with details about the failure
2. Check that the server URL is correct and the server is running
3. Use the "Restart Server" button to attempt reconnection
4. If problems persist, you can delete the server and try adding it again
2. **For OAuth errors**: Click the "Authenticate" button to complete the authorization flow
3. Check that the server URL is correct and the server is running
4. Try selecting a different transport type (Streamable HTTP vs SSE)
5. Use the "Restart Server" button to attempt reconnection
6. If problems persist, you can delete the server and try adding it again
#### OAuth-Specific Issues
If you're having trouble authenticating with an OAuth-enabled server:
- **"Authentication required" persists**: Make sure you completed the authorization flow in your browser and didn't cancel it
- **Browser doesn't open**: Check your system's default browser settings and ensure external URLs can be opened
- **Redirect errors**: Verify you're using the latest version of Cline - older versions may not support OAuth
- **Reset authentication**: Delete the server and re-add it to start fresh with a new OAuth flow
### Advanced Configuration
@@ -105,10 +138,11 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
{
"mcpServers": {
"exampleServer": {
"url": "https://example.com/mcp-sse",
"url": "https://example.com/mcp-server",
"type": "streamableHttp",
"disabled": false,
"autoApprove": ["tool1", "tool2"],
"timeout": 30
"timeout": 60
}
}
}
@@ -117,9 +151,10 @@ For advanced users, Cline stores MCP server configurations in a JSON file that c
Key configuration options:
- **url**: The endpoint URL (for remote servers)
- **type**: Transport protocol - `"streamableHttp"` (recommended) or `"sse"` (legacy)
- **disabled**: Whether the server is currently enabled (true/false)
- **autoApprove**: List of tool names that don't require confirmation
- **timeout**: Maximum time in seconds to wait for server responses
- **timeout**: Maximum time in seconds to wait for server responses (default: 60)
For additional MCP settings, click the "Advanced MCP Settings" link to access VSCode settings.
@@ -130,3 +165,20 @@ Once connected, Cline can use the tools and resources provided by the MCP server
1. A tool approval prompt will appear (unless auto-approved)
2. Review the tool details and parameters before approving
3. The tool will execute and return results to Cline
### Example: Connecting to Vercel MCP
[Vercel MCP](https://vercel.com/docs/mcp/vercel-mcp) is an OAuth-enabled server that provides tools for managing your Vercel projects and deployments:
1. Click "Remote Servers" tab
2. Enter:
- **Server Name**: `vercel`
- **Server URL**: `https://mcp.vercel.com`
- **Transport Type**: Streamable HTTP (pre-selected)
3. Click "Add Server"
4. You'll see "Authentication required" - click the **"Authenticate"** button
5. Sign in to Vercel in your browser and authorize Cline
6. Return to Cline - the server will automatically connect
7. Vercel's tools (deploy, logs, projects) are now available to Cline!
Your Vercel authentication persists across sessions, so you won't need to re-authenticate each time you use Cline.
+5 -9
View File
@@ -21,20 +21,16 @@ For the most updated pricing, please visit: https://www.baseten.co/products/mode
Note: Kimi K2 0711, Llama 4 Maverick, and Llama 4 Scout Model APIs have been deprecated at 5pm PT on October 8th.
https://www.baseten.co/resources/changelog/model-api-deprecation-notice-kimi-k2-0711-scout-maverick/
**Reasoning Models:**
- `zai-org/GLM-4.6` (Z AI) - Frontier open model with advanced agentic, reasoning and coding capabilities by Z AI (200k context) \$0.60/\$2.20 per 1M tokens
- `moonshotai/Kimi-K2-Instruct-0905` (Moonshot AI) - September update with enhanced capabilities (262K context) - \$0.60/\$2.50 per 1M tokens
- `openai/gpt-oss-120b` (OpenAI) - 120B MoE with strong reasoning capabilities (128K context) - \$0.10/\$0.50 per 1M tokens
- `Qwen/Qwen3-Coder-480B-A35B-Instruct`- Advanced coding and reasoning (262K context) - \$0.38/\$1.53 per 1M tokens
- `Qwen/Qwen3-235B-A22B-Instruct-2507` - Math and reasoning expert (262K context) - \$0.22/\$0.80 per 1M tokens
- `deepseek-ai/DeepSeek-R1` - DeepSeek's first-generation reasoning model (163K context) - \$2.55/\$5.95 per 1M tokens
- `deepseek-ai/DeepSeek-R1-0528` - Latest revision of DeepSeek's reasoning model (163K context) - \$2.55/\$5.95 per 1M tokens
- `deepseek-ai/DeepSeek-V3.1` - Hybrid reasoning with advanced tool calling (163K context) - \$0.50/\$1.50 per 1M tokens
- `deepseek-ai/DeepSeek-V3-0324` - Fast general-purpose with enhanced reasoning (163K context) - \$0.77/\$0.77 per 1M tokens
**Flagship Models:**
- `openai/gpt-oss-120b` (OpenAI) - 120B MoE with strong reasoning capabilities (128K context) - \$0.10/\$0.50 per 1M tokens
- `moonshotai/Kimi-K2-Instruct-0905` (Moonshot AI) - September update with enhanced capabilities (262K context) - \$0.60/\$2.50 per 1M tokens
**Coding Specialists:**
- `Qwen/Qwen3-Coder-480B-A35B-Instruct`- Advanced coding and reasoning (262K context) - \$0.38/\$1.53 per 1M tokens
- `Qwen/Qwen3-235B-A22B-Instruct-2507` - Math and reasoning expert (262K context) - \$0.22/\$0.80 per 1M tokens
### Configuration in Cline
1. **Open Cline Settings:** Click the settings icon (⚙️) in the Cline panel.
+1 -2
View File
@@ -18,8 +18,7 @@ Cerebras delivers the world's fastest AI inference through their revolutionary w
Cline supports the following Cerebras models:
- `qwen-3-coder-480b-free` (Free tier) - High-performance coding model at no cost
- `qwen-3-coder-480b` - Flagship 480B parameter coding model
- `zai-glm-4.6` - Intelligent general purpose model with 1,500 tokens/s
- `qwen-3-235b-a22b-instruct-2507` - Advanced instruction-following model
- `qwen-3-235b-a22b-thinking-2507` - Reasoning model with step-by-step thinking
- `llama-3.3-70b` - Meta's Llama 3.3 model optimized for speed
+2 -10
View File
@@ -34,18 +34,10 @@ First, you'll need to install and authenticate Claude Code on your system:
<br />
<Accordion title="Windows Setup">
Anthropic introduced full support for Claude Code on Windows. Follow the [instructions on how to set up Claude Code
normally](#setup) and make sure you have the latest Claude Code and Cline versions.
</Accordion>
### Finding your Claude Code path
If you're not sure where Claude Code is installed:
- **macOS / Linux**: Run `which claude` in your terminal
- **Windows (Command Prompt)**: Run `where claude`
- **Windows (PowerShell)**: Run `Get-Command claude`
- **macOS / Linux / WSL / Git Bash**: `which claude`
- **Windows Command Prompt**: `where claude`
## Supported Models
+2 -1
View File
@@ -42,7 +42,8 @@ h5,
h6,
img {
opacity: 1 !important;
font-family: "Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
font-family:
"Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
}
/* Also apply to any h1 elements within content areas */
+5 -1
View File
@@ -123,7 +123,11 @@ const copyWasmFiles = {
},
}
const buildEnvVars = { "import.meta.url": "_importMetaUrl" }
const buildEnvVars = {
"import.meta.url": "_importMetaUrl",
"process.env.IS_STANDALONE": JSON.stringify(standalone),
}
if (production) {
// IS_DEV is always disable in production builds.
buildEnvVars["process.env.IS_DEV"] = "false"
+3 -3
View File
@@ -1,6 +1,6 @@
repositories
results/evals.db
temp-files
results
diff-edits/cases/
diff-edits/results/
@@ -21,4 +21,4 @@ diff_editing/test_outputs/
# Python bytecode cache
*__pycache__/
diff-edits/cases.zip
diff-edits/cases.zip
+32 -70
View File
@@ -15,48 +15,32 @@ The Cline Evaluation System allows you to:
The evaluation system consists of two main components:
1. **Test Server**: Enhanced HTTP server in `src/services/test/TestServer.ts` that provides detailed task results
2. **CLI Tool**: Command-line interface in `evals/cli/` for orchestrating evaluations
3. **Diff Edit Benchmark**: Separate command using the CLI tool that runs a comprehensive diff editing benchmark suite on real world cases, along with a streamlit dashboard displaying the results. For more details, see the [Diff Edit Benchmark README](./diff-edits/README.md). Make sure you add a `evals/diff-edits/cases` folder with all the conversation jsons.
1. **CLI Tool**: Command-line interface in `evals/cli/` for orchestrating evaluations
2. **Diff Edit Benchmark**: Separate command using the CLI tool that runs a comprehensive diff editing benchmark suite on real world cases, along with a streamlit dashboard displaying the results. For more details, see the Diff Edit Benchmark [README](./diff-edits/README.md). Make sure you add a `evals/diff-edits/cases` folder with all the conversation jsons.
## Directory Structure
```
cline-repo/
├── src/
── services/
├── test/
│ │ ├── TestServer.ts # Enhanced HTTP server for task execution
│ │ ├── GitHelper.ts # Git utilities for file tracking
│ └── ...
└── ...
│ └── ...
├── evals/ # Main directory for evaluation system
│ ├── cli/ # CLI tool for orchestrating evaluations
│ ├── src/
│ │ ├── index.ts # CLI entry point
│ │ ├── commands/ # CLI commands (setup, run, report)
│ │ │ ├── adapters/ # Benchmark adapters
│ │ ├── db/ # Database management
│ │ │ └── utils/ # Utility functions
│ ├── package.json
│ └── tsconfig.json
│ ├── diff-edits/ # Diff editing evaluation suite
│ │ ├── cases/ # Test case JSON files
│ │ ├── results/ # Evaluation results
│ │ ├── diff-apply/ # Diff application logic
│ │ ├── parsing/ # Assistant message parsing
│ │ └── prompts/ # System prompts
│ ├── repositories/ # Cloned benchmark repositories
│ │ ├── exercism/ # Modified Exercism (from pashpashpash/evals)
│ │ ├── swe-bench/ # SWE-Bench repository
│ │ ├── swelancer/ # SWELancer repository
│ │ └── multi-swe/ # Multi-SWE-Bench repository
│ ├── results/ # Evaluation results storage
│ │ ├── runs/ # Individual run results
│ │ └── reports/ # Generated reports
│ └── README.md # This file
└── ...
evals/ # Main directory for evaluation system
├── cli/ # CLI tool for orchestrating evaluations
── src/
├── index.ts # CLI entry point
├── commands/ # CLI commands (setup, run, report)
├── adapters/ # Benchmark adapters
├── db/ # Database management
└── utils/ # Utility functions
├── diff-edits/ # Diff editing evaluation suite
│ ├── cases/ # Test case JSON files
│ ├── results/ # Evaluation results
│ ├── diff-apply/ # Diff application logic
├── parsing/ # Assistant message parsing
└── prompts/ # System prompts
├── repositories/ # Cloned benchmark repositories
└── exercism/ # Exercism (Aider Polyglot)
├── results/ # Evaluation results storage
│ ├── runs/ # Individual run results
│ └── reports/ # Generated reports
└── README.md # This file
```
## Getting Started
@@ -67,25 +51,14 @@ cline-repo/
- VSCode with Cline extension installed
- Git
### Activation Mechanism
The evaluation system uses an `evals.env` file approach to activate test mode in the Cline extension. When an evaluation is run:
1. The CLI creates an `evals.env` file in the workspace directory
2. The Cline extension activates due to the `workspaceContains:evals.env` activation event
3. The extension detects this file and automatically enters test mode
4. After evaluation completes, the file is automatically removed
This approach eliminates the need for environment variables during the build process and allows for targeted activation only when needed for evaluations. The extension remains dormant during normal use, only activating when an evals.env file is present. For more details, see [Evals Env Activation](./docs/evals-env-activation.md).
### Installation
1. Build the CLI tool:
```bash
cd evals/cli
cd evals
npm install
npm run build
npm run build:cli
```
### Usage
@@ -106,13 +79,14 @@ node dist/index.js setup --benchmarks exercism
#### Running Evaluations
```bash
node dist/index.js run --model claude-3-opus-20240229 --benchmark exercism
node dist/index.js run --benchmark exercism --count 10
```
Options:
- `--model`: The model to evaluate (default: claude-3-opus-20240229)
- `--benchmark`: Specific benchmark to run (default: all)
- `--count`: Number of tasks to run (default: all)
- `--benchmark`: Specific benchmark to run (default: exercism)
- `--count`: Number of tasks to run (default: all available tasks)
**Note:** Model selection is currently configured through the Cline CLI itself, not through evaluation flags.
#### Generating Reports
@@ -124,24 +98,11 @@ Options:
- `--format`: Report format (json, markdown) (default: markdown)
- `--output`: Output path for the report
#### Managing Test Mode Activation
The CLI provides a command to manually manage the evals.env file for test mode activation:
```bash
node dist/index.js evals-env create # Create evals.env file in current directory
node dist/index.js evals-env remove # Remove evals.env file from current directory
node dist/index.js evals-env check # Check if evals.env file exists in current directory
```
Options:
- `--directory`: Specify a directory other than the current one
## Benchmarks
### Exercism
Modified Exercism exercises from the [pashpashpash/evals](https://github.com/pashpashpash/evals) repository. These are small, focused programming exercises in various languages.
Modified Exercism exercises from the [polyglot-benchmark](https://github.com/Aider-AI/polyglot-benchmark) repository. These are small, focused programming exercises in various languages.
### SWE-Bench (Coming Soon)
@@ -350,7 +311,8 @@ The evaluation system collects the following metrics:
- **Duration**: Time taken to complete tasks
- **Tool Usage**: Number of tool calls and failures
- **Success Rate**: Percentage of tasks completed successfully
- **Functional Correctness**: Percentage of tests passed
- **Test Success Rate**: Percentage of tests passed
- **Functional Correctness**: Ratio of tests passed to total tests
## Reports
+474 -43
View File
@@ -1,6 +1,7 @@
import * as path from "path"
import * as fs from "fs"
import chalk from "chalk"
import execa from "execa"
import * as fs from "fs"
import * as path from "path"
import { BenchmarkAdapter, Task, VerificationResult } from "./types"
const EVALS_DIR = path.resolve(__dirname, "../../../")
@@ -20,8 +21,12 @@ export class ExercismAdapter implements BenchmarkAdapter {
if (!fs.existsSync(exercismDir)) {
console.log(`Cloning Exercism repository to ${exercismDir}...`)
await execa("git", ["clone", "https://github.com/pashpashpash/evals.git", exercismDir])
await execa("git", ["clone", "https://github.com/Aider-AI/polyglot-benchmark.git", exercismDir])
console.log("Exercism repository cloned successfully")
// Unskip all JavaScript and Java tests after cloning
this.unskipAllJavaScriptTests(exercismDir)
this.unskipAllJavaTests(exercismDir)
} else {
console.log(`Exercism repository already exists at ${exercismDir}`)
@@ -29,6 +34,10 @@ export class ExercismAdapter implements BenchmarkAdapter {
console.log("Pulling latest changes...")
await execa("git", ["pull"], { cwd: exercismDir })
console.log("Repository updated successfully")
// Unskip tests again after pulling
this.unskipAllJavaScriptTests(exercismDir)
this.unskipAllJavaTests(exercismDir)
}
}
@@ -51,7 +60,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
.filter((dir) => !dir.startsWith(".") && !["node_modules", ".git"].includes(dir))
for (const language of languages) {
const languageDir = path.join(exercisesDir, language)
const languageDir = path.join(exercisesDir, language, "exercises", "practice")
// Read exercise directories
const exercises = fs.readdirSync(languageDir).filter((dir) => fs.statSync(path.join(languageDir, dir)).isDirectory())
@@ -61,7 +70,7 @@ export class ExercismAdapter implements BenchmarkAdapter {
// Read instructions
let description = ""
const instructionsPath = path.join(exerciseDir, "docs", "instructions.md")
const instructionsPath = path.join(exerciseDir, ".docs", "instructions.md")
if (fs.existsSync(instructionsPath)) {
description = fs.readFileSync(instructionsPath, "utf-8")
}
@@ -69,20 +78,23 @@ export class ExercismAdapter implements BenchmarkAdapter {
// Determine test commands based on language
let testCommands: string[] = []
switch (language) {
case "cpp":
testCommands = ["cmake -DEXERCISM_RUN_ALL_TESTS=1 .", "make"]
break
case "javascript":
testCommands = ["npm install", "npm test"]
testCommands = ["npm install", "npm test -- --testNamePattern=."]
break
case "python":
testCommands = ["python -m pytest -o markers=task *_test.py"]
testCommands = ["python3 -m pytest -o markers=task *_test.py"]
break
case "go":
testCommands = ["go test"]
testCommands = ["GOWORK=off go test -v"]
break
case "java":
testCommands = ["./gradlew test"]
break
case "rust":
testCommands = ["cargo test"]
testCommands = ["cargo test -- --include-ignored"]
break
default:
testCommands = []
@@ -118,53 +130,117 @@ export class ExercismAdapter implements BenchmarkAdapter {
throw new Error(`Task ${taskId} not found`)
}
// Check if Git repository is already initialized
const gitDirExists = fs.existsSync(path.join(task.workspacePath, ".git"))
// Create temp directory outside workspace for hiding files
const tempDir = path.join(EVALS_DIR, "temp-files", task.id)
fs.mkdirSync(tempDir, { recursive: true })
try {
// Initialize Git repository if needed
if (!gitDirExists) {
await execa("git", ["init"], { cwd: task.workspacePath })
}
// Read config.json to get solution and test files
const configPath = path.join(task.workspacePath, ".meta", "config.json")
let config: any = { files: { solution: [], test: [] } }
// Create a dummy file to ensure there's something to commit
const dummyFilePath = path.join(task.workspacePath, ".eval-timestamp")
fs.writeFileSync(dummyFilePath, new Date().toISOString())
// Add all files and commit
await execa("git", ["add", "."], { cwd: task.workspacePath })
try {
await execa("git", ["commit", "-m", "Initial commit"], { cwd: task.workspacePath })
} catch (error: any) {
// If commit fails because there are no changes, that's okay
if (!error.stderr?.includes("nothing to commit")) {
throw error
}
}
} catch (error: any) {
console.warn(`Warning: Git operations failed: ${error.message}`)
console.warn("Continuing without Git initialization")
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, "utf-8"))
}
return task
// Build enhanced description with instructions
let description = ""
const instructionsPath = path.join(task.workspacePath, ".docs", "instructions.md")
const appendPath = path.join(task.workspacePath, ".docs", "instructions.append.md")
if (fs.existsSync(instructionsPath)) {
description = fs.readFileSync(instructionsPath, "utf-8")
}
if (fs.existsSync(appendPath)) {
description += "\n\n" + fs.readFileSync(appendPath, "utf-8")
}
// Add solution files constraint to description
const solutionFiles = config.files.solution || []
const fileList = solutionFiles.join(", ")
description += `\n\nUse the above instructions to modify the supplied files: ${fileList}. Don't change the names of existing functions or classes, as they may be referenced from other code like unit tests, etc. Only use standard libraries, don't suggest installing any packages.`
description +=
" You should ignore all test or test related files in this directory. The final test file has been removed and will be used to evaluate your work after your implementation is complete. Think deeply about the problem prior to working on the implementation. Consider all edge cases and test your solution prior to finalizing."
// Move test files to temp directory
if (config.files.test) {
config.files.test.forEach((testFile: string) => {
const src = path.join(task.workspacePath, testFile)
if (fs.existsSync(src)) {
const dest = path.join(tempDir, testFile)
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.renameSync(src, dest)
}
})
}
// Move all dot directories (except .git) to temp directory
const items = fs.readdirSync(task.workspacePath)
items.forEach((item) => {
if (item.startsWith(".") && item !== ".git") {
const src = path.join(task.workspacePath, item)
const stat = fs.statSync(src)
if (stat.isDirectory()) {
const dest = path.join(tempDir, item)
fs.renameSync(src, dest)
}
}
})
return {
...task,
description,
metadata: {
...task.metadata,
solutionFiles,
tempDir,
config,
},
}
}
/**
* Verify the result of a task execution
* Cleanup after task execution (restores hidden files from temp directory)
* @param task The task that was executed
* @param result The result of the task execution
*/
async verifyResult(task: Task, result: any): Promise<VerificationResult> {
async cleanupTask(task: Task): Promise<void> {
const tempDir = path.join(EVALS_DIR, "temp-files", task.id)
if (fs.existsSync(tempDir)) {
const items = fs.readdirSync(tempDir)
items.forEach((item) => {
const src = path.join(tempDir, item)
const dest = path.join(task.workspacePath, item)
// Only move if destination doesn't exist (keeps newer test artifacts like .pytest_cache)
if (!fs.existsSync(dest)) {
fs.renameSync(src, dest)
}
})
// Clean up temp directory
fs.rmSync(tempDir, { recursive: true, force: true })
}
}
/**
* Verify the result of a task execution by running tests
* @param task The task that was executed
*/
async verifyResult(task: Task): Promise<VerificationResult> {
// Run verification commands
let success = true
let output = ""
for (const command of task.verificationCommands) {
try {
const [cmd, ...args] = command.split(" ")
const { stdout } = await execa(cmd, args, { cwd: task.workspacePath })
const { stdout, stderr } = await execa(command, {
cwd: task.workspacePath,
shell: true,
})
output += stdout + "\n"
if (stderr) {
output += stderr + "\n"
}
} catch (error: any) {
success = false
if (error.stdout) {
@@ -176,13 +252,92 @@ export class ExercismAdapter implements BenchmarkAdapter {
}
}
// Parse test results
const testsPassed = (output.match(/PASS/g) || []).length
const testsFailed = (output.match(/FAIL/g) || []).length
// Log the raw output
// console.log("\n=== TEST OUTPUT START ===")
// console.log(output)
// console.log("=== TEST OUTPUT END ===\n")
// Parse test results based on language
const language = task.metadata.language
let testsPassed = 0
let testsFailed = 0
switch (language) {
case "python":
const pyPassMatch = output.match(/(\d+) passed/)
const pyFailMatch = output.match(/(\d+) failed/)
testsPassed = pyPassMatch ? parseInt(pyPassMatch[1]) : 0
testsFailed = pyFailMatch ? parseInt(pyFailMatch[1]) : 0
break
case "javascript":
const jestMatch = output.match(/Tests:\s+(?:\d+ skipped,\s+)?(\d+) passed(?:,\s+(\d+) failed)?/)
if (jestMatch) {
testsPassed = parseInt(jestMatch[1])
testsFailed = jestMatch[2] ? parseInt(jestMatch[2]) : 0
} else {
// Fallback to counting test suites
testsPassed = (output.match(/PASS/g) || []).length
testsFailed = (output.match(/FAIL/g) || []).length
}
break
case "go":
// This incorrectly counts the parent, but minor and doesn't affect final boolean metric
testsPassed = (output.match(/--- PASS:/g) || []).length
testsFailed = (output.match(/--- FAIL:/g) || []).length
break
case "rust":
// Rust runs multiple test suites (unit, integration, doc tests)
// Sum results across all test result lines
const resultLines = output.match(/test result:.*?(\d+) passed; (\d+) failed/g)
if (resultLines) {
testsPassed = 0
testsFailed = 0
for (const line of resultLines) {
const match = line.match(/(\d+) passed; (\d+) failed/)
if (match) {
testsPassed += parseInt(match[1])
testsFailed += parseInt(match[2])
}
}
}
break
case "java":
testsPassed = (output.match(/PASSED/g) || []).length
testsFailed = (output.match(/FAILED/g) || []).length
break
case "cpp":
const cppAllPassedMatch = output.match(/All tests passed \(.*?(\d+) test cases?\)/)
const cppTestCasesMatch = output.match(/test cases?: (\d+) \| (\d+) passed/)
const cppFailedMatch = output.match(/(\d+) failed/)
if (cppAllPassedMatch) {
// All tests passed - extract total test cases
testsPassed = parseInt(cppAllPassedMatch[1])
testsFailed = 0
} else if (cppTestCasesMatch) {
// Mixed results - extract passed count and calculate failed
const totalTests = parseInt(cppTestCasesMatch[1])
testsPassed = parseInt(cppTestCasesMatch[2])
testsFailed = cppFailedMatch ? parseInt(cppFailedMatch[1]) : totalTests - testsPassed
}
break
default:
// Fallback to generic PASS/FAIL counting
testsPassed = (output.match(/PASS/g) || []).length
testsFailed = (output.match(/FAIL/g) || []).length
}
const testsTotal = testsPassed + testsFailed
return {
success,
rawOutput: output,
metrics: {
testsPassed,
testsFailed,
@@ -191,4 +346,280 @@ export class ExercismAdapter implements BenchmarkAdapter {
},
}
}
/**
* Hide test files by moving them to temp directory
* @param task The task to hide test files for
*/
private hideTestFiles(task: Task): void {
const tempDir = task.metadata.tempDir
const config = task.metadata.config
if (config?.files?.test) {
config.files.test.forEach((testFile: string) => {
const src = path.join(task.workspacePath, testFile)
if (fs.existsSync(src)) {
const dest = path.join(tempDir, testFile)
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.renameSync(src, dest)
}
})
}
// Hide dot directories again (except .git)
const items = fs.readdirSync(task.workspacePath)
items.forEach((item) => {
if (item.startsWith(".") && item !== ".git") {
const src = path.join(task.workspacePath, item)
if (fs.existsSync(src)) {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
const dest = path.join(tempDir, item)
if (!fs.existsSync(dest)) {
fs.renameSync(src, dest)
}
}
}
}
})
}
/**
* Restore test files by moving them from temp directory
* @param task The task to restore test files for
*/
private restoreTestFiles(task: Task): void {
const tempDir = task.metadata.tempDir
const config = task.metadata.config
if (config?.files?.test) {
config.files.test.forEach((testFile: string) => {
const src = path.join(tempDir, testFile)
if (fs.existsSync(src)) {
const dest = path.join(task.workspacePath, testFile)
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.renameSync(src, dest)
}
})
}
// Restore dot directories (except .git)
if (fs.existsSync(tempDir)) {
const items = fs.readdirSync(tempDir)
items.forEach((item) => {
if (item.startsWith(".") && item !== ".git") {
const src = path.join(tempDir, item)
const dest = path.join(task.workspacePath, item)
if (fs.existsSync(src) && !fs.existsSync(dest)) {
fs.renameSync(src, dest)
}
}
})
}
}
/**
* Builds retry message with test errors and fix instructions
* @param testOutput The raw test output showing errors
* @param solutionFiles List of solution files to fix
* @returns Formatted retry message
*/
private buildRetryMessage(testOutput: string, solutionFiles: string[]): string {
const fileList = solutionFiles.join(", ")
return `${testOutput}\n\nSee the testing errors above. The tests are correct, don't try and change them. Fix the code in ${fileList} to resolve the errors.`
}
/**
* Unskip all JavaScript tests in the repository by replacing xtest with test
* @param repoPath Path to the exercism repository
*/
private unskipAllJavaScriptTests(repoPath: string): void {
const jsDir = path.join(repoPath, "javascript", "exercises", "practice")
if (!fs.existsSync(jsDir)) {
console.log("JavaScript exercises directory not found, skipping test unskipping")
return
}
// Walk through all exercise directories
const exercises = fs.readdirSync(jsDir).filter((dir) => {
const fullPath = path.join(jsDir, dir)
return fs.statSync(fullPath).isDirectory()
})
let filesModified = 0
for (const exercise of exercises) {
const exerciseDir = path.join(jsDir, exercise)
// Find all .spec.js files
const files = fs.readdirSync(exerciseDir).filter((file) => file.endsWith(".spec.js"))
for (const file of files) {
const filePath = path.join(exerciseDir, file)
let content = fs.readFileSync(filePath, "utf-8")
const originalContent = content
// Replace xtest with test to unskip tests
content = content.replace(/xtest\(/g, "test(")
if (content !== originalContent) {
fs.writeFileSync(filePath, content)
filesModified++
}
}
}
console.log(`Unskipped tests in ${filesModified} JavaScript test files`)
}
/**
* Unskip all Java tests in the repository by removing @Disabled annotations
* @param repoPath Path to the exercism repository
*/
private unskipAllJavaTests(repoPath: string): void {
const javaDir = path.join(repoPath, "java", "exercises", "practice")
if (!fs.existsSync(javaDir)) {
console.log("Java exercises directory not found, skipping test unskipping")
return
}
// Walk through all exercise directories
const exercises = fs.readdirSync(javaDir).filter((dir) => {
const fullPath = path.join(javaDir, dir)
return fs.statSync(fullPath).isDirectory()
})
let filesModified = 0
for (const exercise of exercises) {
const testDir = path.join(javaDir, exercise, "src", "test", "java")
if (!fs.existsSync(testDir)) {
continue
}
// Find all .java test files
const files = fs.readdirSync(testDir).filter((file) => file.endsWith(".java"))
for (const file of files) {
const filePath = path.join(testDir, file)
let content = fs.readFileSync(filePath, "utf-8")
const originalContent = content
// Remove @Disabled("Remove to run test") annotations
content = content.replace(/@Disabled\("Remove to run test"\)\s*\n/g, "")
if (content !== originalContent) {
fs.writeFileSync(filePath, content)
filesModified++
}
}
}
console.log(`Unskipped tests in ${filesModified} Java test files`)
}
/**
* Runs a Cline task with automatic retry on test failure
* Creates a new Cline instance, runs the task, verifies with tests,
* and retries once if tests fail
* @param task The task to execute
* @returns The final verification result, or null
*/
async runTask(task: Task): Promise<VerificationResult | null> {
const startTime = Date.now()
let instanceAddress: string | null = null
let attempts = 0
let finalVerification: VerificationResult | null = null
try {
// Step 1: Start a new Cline instance in the working directory
const instanceResult = await execa("cline", ["instance", "new"], {
cwd: task.workspacePath,
stdin: "ignore",
})
// Step 2: Parse the instance address from output
const addressMatch = instanceResult.stdout.match(/Address:\s*([\d.]+:\d+)/)
if (!addressMatch) {
throw new Error("Failed to parse instance address from output")
}
instanceAddress = addressMatch[1]
// Step 3: Create the initial task on this specific instance
await execa("cline", ["task", "new", "--yolo", "--address", instanceAddress, task.description], {
cwd: task.workspacePath,
stdin: "ignore",
})
// Step 4: Wait for initial implementation to complete
console.log(chalk.blue(`Waiting for first attempt to complete...`))
await execa("cline", ["task", "view", "--follow-complete", "--address", instanceAddress], {
cwd: task.workspacePath,
stdin: "ignore",
})
// Step 5: Run first test attempt
console.log(chalk.blue(`Running tests (attempt 1)...`))
this.restoreTestFiles(task)
attempts = 1
const firstVerification = await this.verifyResult(task)
finalVerification = firstVerification
// Step 6: Retry if tests failed
if (!firstVerification.success) {
console.log(chalk.blue(`Tests failed on first attempt. Retrying...`))
// Hide test files again for retry
this.hideTestFiles(task)
attempts = 2
const solutionFiles = task.metadata.solutionFiles || []
const retryMessage = this.buildRetryMessage(firstVerification.rawOutput || "", solutionFiles)
// Send retry task message
await execa("cline", ["task", "send", "--yolo", "--address", instanceAddress], {
cwd: task.workspacePath,
input: retryMessage,
})
// Follow retry until complete
await execa("cline", ["task", "view", "--follow-complete", "--address", instanceAddress], {
cwd: task.workspacePath,
stdin: "ignore",
})
// Run second test attempt (final)
console.log(chalk.blue(`Running tests (attempt 2)...`))
this.restoreTestFiles(task)
const secondVerification = await this.verifyResult(task)
finalVerification = secondVerification
}
const duration = Date.now() - startTime
console.log(
chalk.green(
`Task completed in ${(duration / 1000).toFixed(1)}s after ${attempts} attempt${attempts > 1 ? "s" : ""}`,
),
)
return finalVerification
} catch (error: any) {
const duration = Date.now() - startTime
console.error(chalk.red(`Task failed after ${(duration / 1000).toFixed(1)}s: ${error.message}`))
return finalVerification
} finally {
// Step 7: Always clean up the instance, even if task failed
if (instanceAddress) {
try {
await execa("cline", ["instance", "kill", instanceAddress], {
stdin: "ignore",
})
} catch (cleanupError: any) {
console.error(chalk.yellow(`Warning: Failed to kill instance ${instanceAddress}: ${cleanupError.message}`))
}
}
}
}
}
+1 -10
View File
@@ -1,18 +1,9 @@
import { BenchmarkAdapter } from "./types"
import { ExercismAdapter } from "./exercism"
import { SWEBenchAdapter } from "./swe-bench"
import { SWELancerAdapter } from "./swelancer"
import { MultiSWEAdapter } from "./multi-swe"
import { BenchmarkAdapter } from "./types"
// Registry of all available adapters
const adapters: Record<string, BenchmarkAdapter> = {
// Exercism is the primary adapter with real implementation
exercism: new ExercismAdapter(),
// Dummy adapters for testing
"swe-bench": new SWEBenchAdapter(),
swelancer: new SWELancerAdapter(),
"multi-swe": new MultiSWEAdapter(),
}
/**
-192
View File
@@ -1,192 +0,0 @@
import * as path from "path"
import * as fs from "fs"
import execa from "execa"
import { BenchmarkAdapter, Task, VerificationResult } from "./types"
const EVALS_DIR = path.resolve(__dirname, "../../../")
/**
* Dummy adapter for the Multi-SWE-Bench benchmark
*/
export class MultiSWEAdapter implements BenchmarkAdapter {
name = "multi-swe"
/**
* Set up the Multi-SWE-Bench benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("Multi-SWE-Bench dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "multi-swe")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy Multi-SWE-Bench directory at ${repoDir}`)
}
}
/**
* List all available tasks in the Multi-SWE-Bench benchmark (dummy implementation)
*/
async listTasks(): Promise<Task[]> {
return [
{
id: "multi-swe-task-1",
name: "Multi-Language API Integration",
description:
"Implement a system that integrates a Python backend with a TypeScript frontend and a Rust processing service.",
workspacePath: path.join(EVALS_DIR, "repositories", "multi-swe"),
setupCommands: [],
verificationCommands: [],
metadata: {
languages: ["python", "typescript", "rust"],
complexity: "high",
type: "multi-swe",
},
},
{
id: "multi-swe-task-2",
name: "Cross-Platform Mobile App",
description: "Create a cross-platform mobile app using React Native with native modules in Swift and Kotlin.",
workspacePath: path.join(EVALS_DIR, "repositories", "multi-swe"),
setupCommands: [],
verificationCommands: [],
metadata: {
languages: ["javascript", "swift", "kotlin"],
complexity: "medium",
type: "multi-swe",
},
},
{
id: "multi-swe-task-3",
name: "Microservice Architecture",
description: "Design and implement a microservice architecture with services written in Go, Node.js, and Java.",
workspacePath: path.join(EVALS_DIR, "repositories", "multi-swe"),
setupCommands: [],
verificationCommands: [],
metadata: {
languages: ["go", "javascript", "java"],
complexity: "high",
type: "multi-swe",
},
},
]
}
/**
* Prepare a specific task for execution (dummy implementation)
* @param taskId The ID of the task to prepare
*/
async prepareTask(taskId: string): Promise<Task> {
const tasks = await this.listTasks()
const task = tasks.find((t) => t.id === taskId)
if (!task) {
throw new Error(`Task ${taskId} not found`)
}
// Create a dummy workspace for the task
const taskDir = path.join(task.workspacePath, taskId)
if (!fs.existsSync(taskDir)) {
fs.mkdirSync(taskDir, { recursive: true })
// Create a dummy file for the task
fs.writeFileSync(
path.join(taskDir, "README.md"),
`# ${task.name}\n\n${task.description}\n\nThis is a dummy task for testing purposes.`,
)
// Create additional dummy files based on task type
if (task.id === "multi-swe-task-1") {
// Python backend
fs.mkdirSync(path.join(taskDir, "backend"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "backend", "app.py"),
`# TODO: Implement Python backend\nfrom flask import Flask\n\napp = Flask(__name__)\n\n@app.route('/')\ndef hello():\n return "Hello, World!"\n`,
)
// TypeScript frontend
fs.mkdirSync(path.join(taskDir, "frontend"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "frontend", "app.ts"),
`// TODO: Implement TypeScript frontend\nconsole.log('Frontend starting...');\n`,
)
// Rust processing service
fs.mkdirSync(path.join(taskDir, "processor"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "processor", "main.rs"),
`// TODO: Implement Rust processing service\nfn main() {\n println!("Processor starting...");\n}\n`,
)
} else if (task.id === "multi-swe-task-2") {
// React Native app
fs.mkdirSync(path.join(taskDir, "app"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "app", "App.js"),
`// TODO: Implement React Native app\nimport React from 'react';\nimport { View, Text } from 'react-native';\n\nexport default function App() {\n return (\n <View>\n <Text>Hello, World!</Text>\n </View>\n );\n}\n`,
)
// Swift native module
fs.mkdirSync(path.join(taskDir, "ios"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "ios", "NativeModule.swift"),
`// TODO: Implement Swift native module\nimport Foundation\n\n@objc(NativeModule)\nclass NativeModule: NSObject {\n @objc\n func hello() -> String {\n return "Hello from Swift"\n }\n}\n`,
)
// Kotlin native module
fs.mkdirSync(path.join(taskDir, "android"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "android", "NativeModule.kt"),
`// TODO: Implement Kotlin native module\npackage com.example.app\n\nclass NativeModule {\n fun hello(): String {\n return "Hello from Kotlin"\n }\n}\n`,
)
} else if (task.id === "multi-swe-task-3") {
// Go service
fs.mkdirSync(path.join(taskDir, "service-go"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "service-go", "main.go"),
`// TODO: Implement Go service\npackage main\n\nimport "fmt"\n\nfunc main() {\n\tfmt.Println("Go service starting...")\n}\n`,
)
// Node.js service
fs.mkdirSync(path.join(taskDir, "service-node"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "service-node", "server.js"),
`// TODO: Implement Node.js service\nconsole.log('Node.js service starting...');\n`,
)
// Java service
fs.mkdirSync(path.join(taskDir, "service-java"), { recursive: true })
fs.writeFileSync(
path.join(taskDir, "service-java", "Main.java"),
`// TODO: Implement Java service\npublic class Main {\n public static void main(String[] args) {\n System.out.println("Java service starting...");\n }\n}\n`,
)
}
}
// Update the task's workspace path to the task-specific directory
return {
...task,
workspacePath: taskDir,
}
}
/**
* Verify the result of a task execution (dummy implementation)
* @param task The task that was executed
* @param result The result of the task execution
*/
async verifyResult(task: Task, result: any): Promise<VerificationResult> {
// Always return success for dummy implementation
return {
success: true,
metrics: {
testsPassed: 1,
testsFailed: 0,
testsTotal: 1,
functionalCorrectness: 1.0,
crossLanguageIntegration: 0.9, // Dummy metric specific to Multi-SWE
architectureQuality: 0.85, // Dummy metric specific to Multi-SWE
},
}
}
}
-125
View File
@@ -1,125 +0,0 @@
import * as path from "path"
import * as fs from "fs"
import execa from "execa"
import { BenchmarkAdapter, Task, VerificationResult } from "./types"
const EVALS_DIR = path.resolve(__dirname, "../../../")
/**
* Dummy adapter for the SWE-Bench benchmark
*/
export class SWEBenchAdapter implements BenchmarkAdapter {
name = "swe-bench"
/**
* Set up the SWE-Bench benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("SWE-Bench dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "swe-bench")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy SWE-Bench directory at ${repoDir}`)
}
}
/**
* List all available tasks in the SWE-Bench benchmark (dummy implementation)
*/
async listTasks(): Promise<Task[]> {
return [
{
id: "swe-bench-task-1",
name: "Fix React Component Bug",
description: "Fix a bug in a React component where the state is not properly updated.",
workspacePath: path.join(EVALS_DIR, "repositories", "swe-bench"),
setupCommands: [],
verificationCommands: [],
metadata: {
repository: "facebook/react",
issue: "#12345",
type: "swe-bench",
},
},
{
id: "swe-bench-task-2",
name: "Optimize Database Query",
description: "Optimize a slow database query in a Django application.",
workspacePath: path.join(EVALS_DIR, "repositories", "swe-bench"),
setupCommands: [],
verificationCommands: [],
metadata: {
repository: "django/django",
issue: "#6789",
type: "swe-bench",
},
},
{
id: "swe-bench-task-3",
name: "Fix Memory Leak",
description: "Fix a memory leak in a Node.js application.",
workspacePath: path.join(EVALS_DIR, "repositories", "swe-bench"),
setupCommands: [],
verificationCommands: [],
metadata: {
repository: "nodejs/node",
issue: "#9876",
type: "swe-bench",
},
},
]
}
/**
* Prepare a specific task for execution (dummy implementation)
* @param taskId The ID of the task to prepare
*/
async prepareTask(taskId: string): Promise<Task> {
const tasks = await this.listTasks()
const task = tasks.find((t) => t.id === taskId)
if (!task) {
throw new Error(`Task ${taskId} not found`)
}
// Create a dummy workspace for the task
const taskDir = path.join(task.workspacePath, taskId)
if (!fs.existsSync(taskDir)) {
fs.mkdirSync(taskDir, { recursive: true })
// Create a dummy file for the task
fs.writeFileSync(
path.join(taskDir, "README.md"),
`# ${task.name}\n\n${task.description}\n\nThis is a dummy task for testing purposes.`,
)
}
// Update the task's workspace path to the task-specific directory
return {
...task,
workspacePath: taskDir,
}
}
/**
* Verify the result of a task execution (dummy implementation)
* @param task The task that was executed
* @param result The result of the task execution
*/
async verifyResult(task: Task, result: any): Promise<VerificationResult> {
// Always return success for dummy implementation
return {
success: true,
metrics: {
testsPassed: 1,
testsFailed: 0,
testsTotal: 1,
functionalCorrectness: 1.0,
performanceImprovement: 0.25, // Dummy metric specific to SWE-Bench
codeQuality: 0.9, // Dummy metric specific to SWE-Bench
},
}
}
}
-143
View File
@@ -1,143 +0,0 @@
import * as path from "path"
import * as fs from "fs"
import execa from "execa"
import { BenchmarkAdapter, Task, VerificationResult } from "./types"
const EVALS_DIR = path.resolve(__dirname, "../../../")
/**
* Dummy adapter for the SWELancer benchmark
*/
export class SWELancerAdapter implements BenchmarkAdapter {
name = "swelancer"
/**
* Set up the SWELancer benchmark repository (dummy implementation)
*/
async setup(): Promise<void> {
console.log("SWELancer dummy setup completed")
// Create repositories directory if it doesn't exist
const repoDir = path.join(EVALS_DIR, "repositories", "swelancer")
if (!fs.existsSync(repoDir)) {
fs.mkdirSync(repoDir, { recursive: true })
console.log(`Created dummy SWELancer directory at ${repoDir}`)
}
}
/**
* List all available tasks in the SWELancer benchmark (dummy implementation)
*/
async listTasks(): Promise<Task[]> {
return [
{
id: "swelancer-task-1",
name: "Create Landing Page",
description: "Create a responsive landing page for a new product using HTML, CSS, and JavaScript.",
workspacePath: path.join(EVALS_DIR, "repositories", "swelancer"),
setupCommands: [],
verificationCommands: [],
metadata: {
client: "TechStartup Inc.",
difficulty: "medium",
type: "swelancer",
},
},
{
id: "swelancer-task-2",
name: "Build REST API",
description: "Create a RESTful API for a blog application using Node.js and Express.",
workspacePath: path.join(EVALS_DIR, "repositories", "swelancer"),
setupCommands: [],
verificationCommands: [],
metadata: {
client: "BlogCo",
difficulty: "hard",
type: "swelancer",
},
},
{
id: "swelancer-task-3",
name: "Fix CSS Layout Issues",
description: "Fix layout issues in a responsive website across different screen sizes.",
workspacePath: path.join(EVALS_DIR, "repositories", "swelancer"),
setupCommands: [],
verificationCommands: [],
metadata: {
client: "DesignAgency",
difficulty: "easy",
type: "swelancer",
},
},
]
}
/**
* Prepare a specific task for execution (dummy implementation)
* @param taskId The ID of the task to prepare
*/
async prepareTask(taskId: string): Promise<Task> {
const tasks = await this.listTasks()
const task = tasks.find((t) => t.id === taskId)
if (!task) {
throw new Error(`Task ${taskId} not found`)
}
// Create a dummy workspace for the task
const taskDir = path.join(task.workspacePath, taskId)
if (!fs.existsSync(taskDir)) {
fs.mkdirSync(taskDir, { recursive: true })
// Create a dummy file for the task
fs.writeFileSync(
path.join(taskDir, "README.md"),
`# ${task.name}\n\n${task.description}\n\nThis is a dummy task for testing purposes.`,
)
// Create additional dummy files based on task type
if (task.id === "swelancer-task-1") {
fs.writeFileSync(
path.join(taskDir, "index.html"),
`<!DOCTYPE html>\n<html>\n<head>\n <title>Landing Page</title>\n</head>\n<body>\n <!-- TODO: Implement landing page -->\n</body>\n</html>`,
)
} else if (task.id === "swelancer-task-2") {
fs.writeFileSync(
path.join(taskDir, "server.js"),
`// TODO: Implement REST API\nconsole.log('Server starting...');`,
)
} else if (task.id === "swelancer-task-3") {
fs.writeFileSync(
path.join(taskDir, "styles.css"),
`/* TODO: Fix layout issues */\nbody {\n margin: 0;\n padding: 0;\n}`,
)
}
}
// Update the task's workspace path to the task-specific directory
return {
...task,
workspacePath: taskDir,
}
}
/**
* Verify the result of a task execution (dummy implementation)
* @param task The task that was executed
* @param result The result of the task execution
*/
async verifyResult(task: Task, result: any): Promise<VerificationResult> {
// Always return success for dummy implementation
return {
success: true,
metrics: {
testsPassed: 1,
testsFailed: 0,
testsTotal: 1,
functionalCorrectness: 1.0,
clientSatisfaction: 0.95, // Dummy metric specific to SWELancer
timeEfficiency: 0.85, // Dummy metric specific to SWELancer
},
}
}
}
+4 -1
View File
@@ -17,6 +17,7 @@ export interface Task {
export interface VerificationResult {
success: boolean
metrics: Record<string, any>
rawOutput?: string
}
/**
@@ -27,5 +28,7 @@ export interface BenchmarkAdapter {
setup(): Promise<void>
listTasks(): Promise<Task[]>
prepareTask(taskId: string): Promise<Task>
verifyResult(task: Task, result: any): Promise<VerificationResult>
cleanupTask(task: Task): Promise<void>
verifyResult(task: Task): Promise<VerificationResult>
runTask(task: Task): Promise<VerificationResult | null>
}
-53
View File
@@ -1,53 +0,0 @@
import * as path from "path"
import chalk from "chalk"
import { createEvalsEnvFile, removeEvalsEnvFile, checkEvalsEnvFile } from "../utils/evals-env"
interface EvalsEnvOptions {
action: "create" | "remove" | "check"
directory?: string
}
/**
* Handler for the evals-env command
* @param options Command options
*/
export async function evalsEnvHandler(options: EvalsEnvOptions): Promise<void> {
// Determine the directory to use - default to repository root instead of current directory
const currentDir = process.cwd()
const repoRoot = path.resolve(currentDir, "..", "..") // Navigate up from evals/cli to root
const directory = options.directory || repoRoot
console.log(chalk.blue(`Working with directory: ${directory}`))
// Perform the requested action
switch (options.action) {
case "create":
console.log(chalk.blue("Creating evals.env file..."))
createEvalsEnvFile(directory)
console.log(chalk.green("The Cline extension should now detect this file and enter test mode."))
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
break
case "remove":
console.log(chalk.blue("Removing evals.env file..."))
removeEvalsEnvFile(directory)
console.log(chalk.green("The Cline extension should now exit test mode."))
console.log(chalk.yellow("Note: You may need to reload VSCode for the changes to take effect."))
break
case "check":
console.log(chalk.blue("Checking for evals.env file..."))
const exists = checkEvalsEnvFile(directory)
if (exists) {
console.log(chalk.green("The Cline extension should be in test mode."))
} else {
console.log(chalk.yellow("The Cline extension should not be in test mode."))
}
break
default:
console.error(chalk.red(`Unknown action: ${options.action}`))
console.log(chalk.yellow("Valid actions are: create, remove, check"))
break
}
}
+43 -57
View File
@@ -1,7 +1,7 @@
import * as fs from "fs"
import * as path from "path"
import chalk from "chalk"
import * as fs from "fs"
import ora from "ora"
import * as path from "path"
import { ResultsDatabase } from "../db"
import { generateMarkdownReport } from "../utils/markdown"
@@ -34,7 +34,6 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
// Generate summary report
const summary = {
runs: runs.length,
models: [...new Set(runs.map((run) => run.model))],
benchmarks: [...new Set(runs.map((run) => run.benchmark))],
tasks: 0,
successRate: 0,
@@ -45,6 +44,10 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
totalToolFailures: 0,
toolSuccessRate: 0,
toolUsage: {} as Record<string, { calls: number; failures: number }>,
totalTests: 0,
totalTestsPassed: 0,
totalTestsFailed: 0,
testSuccessRate: 0,
}
let totalTasks = 0
@@ -54,6 +57,9 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
let totalDuration = 0
let totalToolCalls = 0
let totalToolFailures = 0
let totalTests = 0
let totalTestsPassed = 0
let totalTestsFailed = 0
for (const run of runs) {
const tasks = db.getRunTasks(run.id)
@@ -73,6 +79,14 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
totalCost += metrics.find((m) => m.name === "cost")?.value || 0
totalDuration += metrics.find((m) => m.name === "duration")?.value || 0
// Collect test metrics
const testsPassed = metrics.find((m) => m.name === "testsPassed")?.value || 0
const testsFailed = metrics.find((m) => m.name === "testsFailed")?.value || 0
const testsTotal = metrics.find((m) => m.name === "testsTotal")?.value || 0
totalTestsPassed += testsPassed
totalTestsFailed += testsFailed
totalTests += testsTotal
// Collect tool call metrics
totalToolCalls += task.total_tool_calls || 0
totalToolFailures += task.total_tool_failures || 0
@@ -99,6 +113,12 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
summary.totalToolFailures = totalToolFailures
summary.toolSuccessRate = totalToolCalls > 0 ? 1 - totalToolFailures / totalToolCalls : 1.0
// Calculate test metrics
summary.totalTests = totalTests
summary.totalTestsPassed = totalTestsPassed
summary.totalTestsFailed = totalTestsFailed
summary.testSuccessRate = totalTests > 0 ? totalTestsPassed / totalTests : 0
summary.tasks = totalTasks
summary.successRate = totalTasks > 0 ? successfulTasks / totalTasks : 0
summary.averageTokens = totalTasks > 0 ? totalTokens / totalTasks : 0
@@ -112,12 +132,15 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
const benchmarkRuns = runs.filter((run) => run.benchmark === benchmark)
const benchmarkSummary = {
runs: benchmarkRuns.length,
models: [...new Set(benchmarkRuns.map((run) => run.model))],
tasks: 0,
successRate: 0,
averageTokens: 0,
averageCost: 0,
averageDuration: 0,
totalTests: 0,
totalTestsPassed: 0,
totalTestsFailed: 0,
testSuccessRate: 0,
}
let benchmarkTasks = 0
@@ -125,6 +148,9 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
let benchmarkTotalTokens = 0
let benchmarkTotalCost = 0
let benchmarkTotalDuration = 0
let benchmarkTotalTests = 0
let benchmarkTotalTestsPassed = 0
let benchmarkTotalTestsFailed = 0
for (const run of benchmarkRuns) {
const tasks = db.getRunTasks(run.id)
@@ -143,6 +169,14 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
benchmarkTotalCost += metrics.find((m) => m.name === "cost")?.value || 0
benchmarkTotalDuration += metrics.find((m) => m.name === "duration")?.value || 0
// Collect test metrics
const testsPassed = metrics.find((m) => m.name === "testsPassed")?.value || 0
const testsFailed = metrics.find((m) => m.name === "testsFailed")?.value || 0
const testsTotal = metrics.find((m) => m.name === "testsTotal")?.value || 0
benchmarkTotalTestsPassed += testsPassed
benchmarkTotalTestsFailed += testsFailed
benchmarkTotalTests += testsTotal
}
}
@@ -151,60 +185,14 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
benchmarkSummary.averageTokens = benchmarkTasks > 0 ? benchmarkTotalTokens / benchmarkTasks : 0
benchmarkSummary.averageCost = benchmarkTasks > 0 ? benchmarkTotalCost / benchmarkTasks : 0
benchmarkSummary.averageDuration = benchmarkTasks > 0 ? benchmarkTotalDuration / benchmarkTasks : 0
benchmarkSummary.totalTests = benchmarkTotalTests
benchmarkSummary.totalTestsPassed = benchmarkTotalTestsPassed
benchmarkSummary.totalTestsFailed = benchmarkTotalTestsFailed
benchmarkSummary.testSuccessRate = benchmarkTotalTests > 0 ? benchmarkTotalTestsPassed / benchmarkTotalTests : 0
benchmarkReports[benchmark] = benchmarkSummary
}
// Generate model-specific reports
const modelReports: Record<string, any> = {}
for (const model of summary.models) {
const modelRuns = runs.filter((run) => run.model === model)
const modelSummary = {
runs: modelRuns.length,
benchmarks: [...new Set(modelRuns.map((run) => run.benchmark))],
tasks: 0,
successRate: 0,
averageTokens: 0,
averageCost: 0,
averageDuration: 0,
}
let modelTasks = 0
let modelSuccessfulTasks = 0
let modelTotalTokens = 0
let modelTotalCost = 0
let modelTotalDuration = 0
for (const run of modelRuns) {
const tasks = db.getRunTasks(run.id)
modelTasks += tasks.length
for (const task of tasks) {
if (task.success) {
modelSuccessfulTasks++
}
const metrics = db.getTaskMetrics(task.id)
const tokensIn = metrics.find((m) => m.name === "tokensIn")?.value || 0
const tokensOut = metrics.find((m) => m.name === "tokensOut")?.value || 0
modelTotalTokens += tokensIn + tokensOut
modelTotalCost += metrics.find((m) => m.name === "cost")?.value || 0
modelTotalDuration += metrics.find((m) => m.name === "duration")?.value || 0
}
}
modelSummary.tasks = modelTasks
modelSummary.successRate = modelTasks > 0 ? modelSuccessfulTasks / modelTasks : 0
modelSummary.averageTokens = modelTasks > 0 ? modelTotalTokens / modelTasks : 0
modelSummary.averageCost = modelTasks > 0 ? modelTotalCost / modelTasks : 0
modelSummary.averageDuration = modelTasks > 0 ? modelTotalDuration / modelTasks : 0
modelReports[model] = modelSummary
}
// Save reports
const reportDir = path.join(path.resolve(__dirname, "../../../"), "results", "reports")
fs.mkdirSync(reportDir, { recursive: true })
@@ -217,14 +205,12 @@ export async function reportHandler(options: ReportOptions): Promise<void> {
fs.writeFileSync(path.join(reportDir, `benchmarks-${timestamp}.json`), JSON.stringify(benchmarkReports, null, 2))
fs.writeFileSync(path.join(reportDir, `models-${timestamp}.json`), JSON.stringify(modelReports, null, 2))
spinner.succeed(`JSON reports generated in ${reportDir}`)
} else {
// Generate markdown report
const outputPath = options.output || path.join(reportDir, `report-${timestamp}.md`)
generateMarkdownReport(summary, benchmarkReports, modelReports, outputPath)
generateMarkdownReport(summary, benchmarkReports, outputPath)
spinner.succeed(`Markdown report generated at ${outputPath}`)
}
+31 -50
View File
@@ -1,18 +1,13 @@
import * as path from "path"
import { v4 as uuidv4 } from "uuid"
import chalk from "chalk"
import ora from "ora"
import { v4 as uuidv4 } from "uuid"
import { getAdapter } from "../adapters"
import { ResultsDatabase } from "../db"
import { spawnVSCode, cleanupVSCode } from "../utils/vscode"
import { sendTaskToServer } from "../utils/task"
import { storeTaskResult } from "../utils/results"
interface RunOptions {
benchmark?: string
model: string
count?: number
apiKey?: string
}
/**
@@ -21,12 +16,10 @@ interface RunOptions {
*/
export async function runHandler(options: RunOptions): Promise<void> {
// Determine which benchmarks to run
const benchmarks = options.benchmark ? [options.benchmark] : ["exercism"] // Default to exercism for now
const model = options.model
const benchmarks = options.benchmark ? [options.benchmark] : ["exercism"] // Default to exercism
const count = options.count || Infinity
console.log(chalk.blue(`Running evaluations for model: ${model}`))
console.log(chalk.blue(`Benchmarks: ${benchmarks.join(", ")}`))
console.log(chalk.blue(`Running evaluations for the following benchmarks: ${benchmarks.join(", ")}`))
// Create a run for each benchmark
for (const benchmark of benchmarks) {
@@ -36,7 +29,7 @@ export async function runHandler(options: RunOptions): Promise<void> {
console.log(chalk.green(`\nStarting run for benchmark: ${benchmark}`))
// Create run in database
db.createRun(runId, model, benchmark)
db.createRun(runId, benchmark)
// Get adapter for this benchmark
try {
@@ -63,58 +56,47 @@ export async function runHandler(options: RunOptions): Promise<void> {
const preparedTask = await adapter.prepareTask(task.id)
prepareSpinner.succeed("Task prepared")
// Spawn VSCode
console.log("Spawning VSCode...")
await spawnVSCode(preparedTask.workspacePath)
let cleanedUp = false
// Send task to server
const sendSpinner = ora("Sending task to server...").start()
try {
const result = await sendTaskToServer(preparedTask.description, options.apiKey)
sendSpinner.succeed("Task completed")
// Run task using adapter's execution strategy
const finalVerification = await adapter.runTask(preparedTask)
// Verify result
const verifySpinner = ora("Verifying result...").start()
const verification = await adapter.verifyResult(preparedTask, result)
// Cleanup task
const cleanupSpinner = ora("Cleaning up task...").start()
await adapter.cleanupTask(preparedTask)
cleanedUp = true
cleanupSpinner.succeed("Cleanup complete")
// Use final verification from runTask
const verification = finalVerification || (await adapter.verifyResult(preparedTask))
if (verification.success) {
verifySpinner.succeed(
`Verification successful: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal} tests passed`,
console.log(
chalk.green(`Tests passed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`),
)
} else {
verifySpinner.fail(
`Verification failed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal} tests passed`,
console.log(
chalk.red(`Tests failed: ${verification.metrics.testsPassed}/${verification.metrics.testsTotal}`),
)
}
// Store result
const storeSpinner = ora("Storing result...").start()
await storeTaskResult(runId, preparedTask, result, verification)
await storeTaskResult(runId, preparedTask, {}, verification)
storeSpinner.succeed("Result stored")
console.log(chalk.green(`Task completed. Success: ${verification.success}`))
// Clean up VS Code and temporary files
const cleanupSpinner = ora("Cleaning up...").start()
try {
await cleanupVSCode(preparedTask.workspacePath)
cleanupSpinner.succeed("Cleanup completed")
} catch (cleanupError: any) {
cleanupSpinner.fail(`Cleanup failed: ${cleanupError.message}`)
console.error(chalk.yellow(cleanupError.stack))
}
} catch (error: any) {
sendSpinner.fail(`Task failed: ${error.message}`)
console.error(chalk.red(error.stack))
// Clean up VS Code and temporary files even if the task failed
const cleanupSpinner = ora("Cleaning up...").start()
try {
await cleanupVSCode(preparedTask.workspacePath)
cleanupSpinner.succeed("Cleanup completed")
} catch (cleanupError: any) {
cleanupSpinner.fail(`Cleanup failed: ${cleanupError.message}`)
console.error(chalk.yellow(cleanupError.stack))
console.error(chalk.red(`Task failed: ${error.message}`))
} finally {
// Ensure cleanup always happens
if (!cleanedUp) {
try {
const finalCleanupSpinner = ora("Performing cleanup...").start()
await adapter.cleanupTask(preparedTask)
finalCleanupSpinner.succeed("Cleanup complete")
} catch (cleanupError: any) {
console.error(chalk.red(`Cleanup failed: ${cleanupError.message}`))
}
}
}
}
@@ -125,7 +107,6 @@ export async function runHandler(options: RunOptions): Promise<void> {
console.log(chalk.green(`\nRun complete for benchmark: ${benchmark}`))
} catch (error: any) {
console.error(chalk.red(`Error running benchmark ${benchmark}: ${error.message}`))
console.error(error.stack)
}
}
+6 -7
View File
@@ -1,6 +1,6 @@
import * as path from "path"
import * as fs from "fs"
import Database from "better-sqlite3"
import * as fs from "fs"
import * as path from "path"
import { SCHEMA } from "./schema"
const EVALS_DIR = path.resolve(__dirname, "../../../")
@@ -34,16 +34,15 @@ export class ResultsDatabase {
/**
* Create a new evaluation run
* @param id Run ID
* @param model Model name
* @param benchmark Benchmark name
*/
createRun(id: string, model: string, benchmark: string): void {
createRun(id: string, benchmark: string): void {
const stmt = this.db.prepare(`
INSERT INTO runs (id, timestamp, model, benchmark)
VALUES (?, ?, ?, ?)
INSERT INTO runs (id, timestamp, benchmark)
VALUES (?, ?, ?)
`)
stmt.run(id, Date.now(), model, benchmark)
stmt.run(id, Date.now(), benchmark)
}
/**
-1
View File
@@ -5,7 +5,6 @@ export const SCHEMA = `
CREATE TABLE IF NOT EXISTS runs (
id TEXT PRIMARY KEY,
timestamp INTEGER NOT NULL,
model TEXT NOT NULL,
benchmark TEXT NOT NULL,
completed INTEGER NOT NULL DEFAULT 0
);
+10 -28
View File
@@ -1,11 +1,10 @@
#!/usr/bin/env node
import { Command } from "commander"
import chalk from "chalk"
import { setupHandler } from "./commands/setup"
import { runHandler } from "./commands/run"
import { Command } from "commander"
import { reportHandler } from "./commands/report"
import { evalsEnvHandler } from "./commands/evals-env"
import { runHandler } from "./commands/run"
import { runDiffEvalHandler } from "./commands/runDiffEval"
import { setupHandler } from "./commands/setup"
// Create the CLI program
const program = new Command()
@@ -17,11 +16,7 @@ program.name("cline-eval").description("CLI tool for orchestrating Cline evaluat
program
.command("setup")
.description("Clone and set up benchmark repositories")
.option(
"-b, --benchmarks <benchmarks>",
"Comma-separated list of benchmarks to set up",
"exercism,swe-bench,swelancer,multi-swe",
)
.option("-b, --benchmarks <benchmarks>", "Comma-separated list of benchmarks to set up", "exercism")
.action(async (options) => {
try {
await setupHandler(options)
@@ -36,9 +31,7 @@ program
.command("run")
.description("Run evaluations")
.option("-b, --benchmark <benchmark>", "Specific benchmark to run")
.option("-m, --model <model>", "Model to evaluate", "claude-3-opus-20240229")
.option("-c, --count <count>", "Number of tasks to run", parseInt)
.option("-k, --api-key <apiKey>", "Cline API key to use for evaluations")
.action(async (options) => {
try {
await runHandler(options)
@@ -63,21 +56,6 @@ program
}
})
// Evals-env command
program
.command("evals-env")
.description("Manage evals.env files for test mode activation")
.argument("<action>", "Action to perform: create, remove, or check")
.option("-d, --directory <directory>", "Directory to create/remove/check evals.env file in (defaults to current directory)")
.action(async (action, options) => {
try {
await evalsEnvHandler({ action, ...options })
} catch (error) {
console.error(chalk.red(`Error managing evals.env file: ${error instanceof Error ? error.message : String(error)}`))
process.exit(1)
}
})
// Run-diff-eval command
program
.command("run-diff-eval")
@@ -86,11 +64,15 @@ program
.option("--output-path <path>", "Path to the directory to save the test output JSON files")
.option("--model-ids <model_ids>", "Comma-separated list of model IDs to test")
.option("--system-prompt-name <name>", "The name of the system prompt to use", "basicSystemPrompt")
.option("-n, --valid-attempts-per-case <number>", "Number of valid attempts per test case per model (will retry until this many valid attempts are collected)", "1")
.option(
"-n, --valid-attempts-per-case <number>",
"Number of valid attempts per test case per model (will retry until this many valid attempts are collected)",
"1",
)
.option("--max-attempts-per-case <number>", "Maximum total attempts per test case (default: 10x valid attempts)")
.option("--max-cases <number>", "Maximum number of test cases to run (limits total cases loaded)")
.option("--parsing-function <name>", "The parsing function to use", "parseAssistantMessageV2")
.option("--diff-edit-function <name>", "The diff editing function to use", "constructNewFileContentV2")
.option("--diff-edit-function <name>", "The diff editing function to use", "diff-06-26-25")
.option("--thinking-budget <tokens>", "Set the thinking tokens budget", "0")
.option("--provider <provider>", "API provider to use (openrouter, openai)", "openrouter")
.option("--parallel", "Run tests in parallel", false)
-79
View File
@@ -1,79 +0,0 @@
import * as fs from "fs"
import * as path from "path"
import chalk from "chalk"
/**
* Creates an evals.env file in the specified directory
* @param directory The directory where the evals.env file should be created
* @returns True if the file was created, false if it already exists
*/
export function createEvalsEnvFile(directory: string): boolean {
const evalsEnvPath = path.join(directory, "evals.env")
// Check if the file already exists
if (fs.existsSync(evalsEnvPath)) {
console.log(chalk.yellow(`evals.env file already exists at ${evalsEnvPath}`))
return false
}
// Create the file
try {
const content = `# This file activates Cline test mode
# Created at: ${new Date().toISOString()}
#
# This file is automatically detected by the Cline extension
# and enables test mode for automated evaluations.
#
# Delete this file to deactivate test mode.
`
fs.writeFileSync(evalsEnvPath, content)
console.log(chalk.green(`Created evals.env file at ${evalsEnvPath}`))
return true
} catch (error) {
console.error(chalk.red(`Error creating evals.env file: ${error}`))
return false
}
}
/**
* Removes an evals.env file from the specified directory
* @param directory The directory where the evals.env file should be removed
* @returns True if the file was removed, false if it doesn't exist
*/
export function removeEvalsEnvFile(directory: string): boolean {
const evalsEnvPath = path.join(directory, "evals.env")
// Check if the file exists
if (!fs.existsSync(evalsEnvPath)) {
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
return false
}
// Remove the file
try {
fs.unlinkSync(evalsEnvPath)
console.log(chalk.green(`Removed evals.env file from ${evalsEnvPath}`))
return true
} catch (error) {
console.error(chalk.red(`Error removing evals.env file: ${error}`))
return false
}
}
/**
* Checks if an evals.env file exists in the specified directory
* @param directory The directory to check for an evals.env file
* @returns True if the file exists, false otherwise
*/
export function checkEvalsEnvFile(directory: string): boolean {
const evalsEnvPath = path.join(directory, "evals.env")
const exists = fs.existsSync(evalsEnvPath)
if (exists) {
console.log(chalk.green(`evals.env file found at ${evalsEnvPath}`))
} else {
console.log(chalk.yellow(`No evals.env file found at ${evalsEnvPath}`))
}
return exists
}
-131
View File
@@ -1,131 +0,0 @@
import execa from "execa"
import * as fs from "fs"
import * as path from "path"
import * as os from "os"
/**
* List of VSCode extensions to install for evaluation environments
* These extensions provide language support and other useful features
*/
export const REQUIRED_EXTENSIONS = [
"golang.go", // Go language support
"dbaeumer.vscode-eslint", // ESLint support
"redhat.java", // Java support
"ms-python.python", // Python support
"rust-lang.rust-analyzer", // Rust support
"ms-vscode.cpptools", // C/C++ support
]
/**
* Install required VSCode extensions in the specified extensions directory
* @param extensionsDir The directory where extensions should be installed
* @returns Promise that resolves when all extensions are installed
*/
export async function installRequiredExtensions(extensionsDir: string): Promise<void> {
console.log("Installing required VSCode extensions...")
// Create the extensions directory if it doesn't exist
if (!fs.existsSync(extensionsDir)) {
fs.mkdirSync(extensionsDir, { recursive: true })
}
// Install each extension
for (const extension of REQUIRED_EXTENSIONS) {
try {
console.log(`Installing extension: ${extension}...`)
await execa("code", ["--extensions-dir", extensionsDir, "--install-extension", extension, "--force"])
console.log(`✅ Extension ${extension} installed successfully`)
} catch (error: any) {
console.warn(`⚠️ Failed to install extension ${extension}: ${error.message}`)
// Continue with other extensions even if one fails
}
}
console.log("✅ All required extensions installed")
}
/**
* Check if a VSCode extension is installed in the specified directory
* @param extensionsDir The directory to check for installed extensions
* @param extensionId The ID of the extension to check
* @returns True if the extension is installed, false otherwise
*/
export function isExtensionInstalled(extensionsDir: string, extensionId: string): boolean {
// Extensions are installed in directories named publisher.name-version
// We need to check if any directory starts with the extensionId
const extensionPrefix = extensionId.toLowerCase() + "-"
try {
const files = fs.readdirSync(extensionsDir)
return files.some((file) => {
const lowerCaseFile = file.toLowerCase()
return lowerCaseFile === extensionId.toLowerCase() || lowerCaseFile.startsWith(extensionPrefix)
})
} catch (error) {
return false
}
}
/**
* Get the path to the VSCode settings file in the specified user data directory
* @param userDataDir The VSCode user data directory
* @returns The path to the settings.json file
*/
export function getSettingsPath(userDataDir: string): string {
const settingsDir = path.join(userDataDir, "User")
fs.mkdirSync(settingsDir, { recursive: true })
return path.join(settingsDir, "settings.json")
}
/**
* Configure extension settings in the VSCode user data directory
* @param userDataDir The VSCode user data directory
*/
export function configureExtensionSettings(userDataDir: string): void {
const settingsPath = getSettingsPath(userDataDir)
// Read existing settings if they exist
let settings = {}
if (fs.existsSync(settingsPath)) {
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"))
} catch (error) {
console.warn(`Error reading settings file: ${error}`)
}
}
// Add or update extension-specific settings
const updatedSettings = {
...settings,
// Go extension settings
"go.toolsManagement.autoUpdate": false,
"go.survey.prompt": false,
// ESLint settings
"eslint.enable": true,
"eslint.run": "onSave",
// Java settings
"java.configuration.checkProjectSettingsExclusions": false,
"java.configure.checkForOutdatedExtensions": false,
"java.help.firstView": false,
// Python settings
"python.experiments.enabled": false,
"python.showStartPage": false,
// Rust settings
"rust-analyzer.checkOnSave.command": "check",
// C/C++ settings
"C_Cpp.intelliSenseEngine": "default",
// General extension settings
"extensions.autoUpdate": false,
"extensions.ignoreRecommendations": true,
}
// Write updated settings
fs.writeFileSync(settingsPath, JSON.stringify(updatedSettings, null, 2))
console.log("✅ Extension settings configured")
}
+11 -40
View File
@@ -1,28 +1,24 @@
import * as fs from "fs"
import * as path from "path"
/**
* Generate a markdown report from evaluation results
* @param summary Overall summary
* @param benchmarkReports Benchmark-specific reports
* @param modelReports Model-specific reports
* @param outputPath Output file path
*/
export function generateMarkdownReport(
summary: any,
benchmarkReports: Record<string, any>,
modelReports: Record<string, any>,
outputPath: string,
): void {
export function generateMarkdownReport(summary: any, benchmarkReports: Record<string, any>, outputPath: string): void {
let markdown = `# Cline Evaluation Report\n\n`
// Generate summary section
markdown += `## Summary\n\n`
markdown += `- **Total Runs:** ${summary.runs}\n`
markdown += `- **Models:** ${summary.models.join(", ")}\n`
markdown += `- **Benchmarks:** ${summary.benchmarks.join(", ")}\n`
markdown += `- **Total Tasks:** ${summary.tasks}\n`
markdown += `- **Success Rate:** ${(summary.successRate * 100).toFixed(2)}%\n`
markdown += `- **Task Success Rate:** ${(summary.successRate * 100).toFixed(2)}%\n`
markdown += `- **Total Tests:** ${summary.totalTests}\n`
markdown += `- **Tests Passed:** ${summary.totalTestsPassed}\n`
markdown += `- **Tests Failed:** ${summary.totalTestsFailed}\n`
markdown += `- **Test Success Rate:** ${(summary.testSuccessRate * 100).toFixed(2)}%\n`
markdown += `- **Average Tokens:** ${Math.round(summary.averageTokens)}\n`
markdown += `- **Average Cost:** $${summary.averageCost.toFixed(4)}\n`
markdown += `- **Average Duration:** ${(summary.averageDuration / 1000).toFixed(2)}s\n`
@@ -48,23 +44,12 @@ export function generateMarkdownReport(
for (const [benchmark, report] of Object.entries(benchmarkReports)) {
markdown += `### ${benchmark}\n\n`
markdown += `- **Runs:** ${report.runs}\n`
markdown += `- **Models:** ${report.models.join(", ")}\n`
markdown += `- **Tasks:** ${report.tasks}\n`
markdown += `- **Success Rate:** ${(report.successRate * 100).toFixed(2)}%\n`
markdown += `- **Average Tokens:** ${Math.round(report.averageTokens)}\n`
markdown += `- **Average Cost:** $${report.averageCost.toFixed(4)}\n`
markdown += `- **Average Duration:** ${(report.averageDuration / 1000).toFixed(2)}s\n\n`
}
// Generate model results section
markdown += `## Model Results\n\n`
for (const [model, report] of Object.entries(modelReports)) {
markdown += `### ${model}\n\n`
markdown += `- **Runs:** ${report.runs}\n`
markdown += `- **Benchmarks:** ${report.benchmarks.join(", ")}\n`
markdown += `- **Tasks:** ${report.tasks}\n`
markdown += `- **Success Rate:** ${(report.successRate * 100).toFixed(2)}%\n`
markdown += `- **Task Success Rate:** ${(report.successRate * 100).toFixed(2)}%\n`
markdown += `- **Total Tests:** ${report.totalTests}\n`
markdown += `- **Tests Passed:** ${report.totalTestsPassed}\n`
markdown += `- **Tests Failed:** ${report.totalTestsFailed}\n`
markdown += `- **Test Success Rate:** ${(report.testSuccessRate * 100).toFixed(2)}%\n`
markdown += `- **Average Tokens:** ${Math.round(report.averageTokens)}\n`
markdown += `- **Average Cost:** $${report.averageCost.toFixed(4)}\n`
markdown += `- **Average Duration:** ${(report.averageDuration / 1000).toFixed(2)}s\n\n`
@@ -87,20 +72,6 @@ export function generateMarkdownReport(
markdown += "```\n\n"
// Success rate by model chart
markdown += `### Success Rate by Model\n\n`
markdown += "```mermaid\n"
markdown += "graph TD\n"
markdown += " title[Success Rate by Model]\n"
markdown += " style title fill:none,stroke:none\n\n"
for (const [model, report] of Object.entries(modelReports)) {
const successRate = (report.successRate * 100).toFixed(2)
markdown += ` ${model.replace(/[-\.]/g, "_")}[${model}: ${successRate}%]\n`
}
markdown += "```\n\n"
// Add timestamp
markdown += `\n\n---\n\nReport generated on ${new Date().toISOString()}\n`
-52
View File
@@ -1,52 +0,0 @@
import fetch from "node-fetch"
import chalk from "chalk"
/**
* Send a task to the Cline test server
* @param task The task description to send
* @param apiKey Optional Cline API key to use for the task
* @returns The result of the task execution
*/
export async function sendTaskToServer(task: string, apiKey?: string): Promise<any> {
const SERVER_URL = "http://localhost:9876/task"
try {
console.log(chalk.blue(`Sending task to server: ${task.substring(0, 100)}${task.length > 100 ? "..." : ""}`))
const response = await fetch(SERVER_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
task,
apiKey,
}),
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Server responded with status ${response.status}: ${errorText}`)
}
const result = await response.json()
if (!result.success) {
throw new Error(`Task execution failed: ${result.error || "Unknown error"}`)
}
if (result.timeout) {
throw new Error("Task execution timed out")
}
return result
} catch (error: any) {
if (error.code === "ECONNREFUSED") {
throw new Error(
"Could not connect to the test server. Make sure VSCode is running with the Cline extension and the test server is active.",
)
}
throw error
}
}
-598
View File
@@ -1,598 +0,0 @@
import execa from "execa"
import * as path from "path"
import * as fs from "fs"
import fetch from "node-fetch"
import * as os from "os"
import { installRequiredExtensions, configureExtensionSettings } from "./extensions"
// Store temporary directories for cleanup
interface VSCodeResources {
tempUserDataDir: string
tempExtensionsDir: string
vscodePid?: number
}
// Global map to track resources for each workspace
const workspaceResources = new Map<string, VSCodeResources>()
/**
* Spawn a VSCode instance with the Cline extension
* @param workspacePath The workspace path to open
* @param vsixPath Optional path to a VSIX file to install
* @returns The resources created for this VS Code instance
*/
export async function spawnVSCode(workspacePath: string, vsixPath?: string): Promise<VSCodeResources> {
// Ensure the workspace path exists
if (!fs.existsSync(workspacePath)) {
throw new Error(`Workspace path does not exist: ${workspacePath}`)
}
// If no VSIX path is provided, build one with IS_TEST=true
if (!vsixPath) {
try {
// Build the VSIX (no longer need to set IS_TEST=true as we'll use evals.env file)
console.log("Building VSIX...")
const clineRoot = path.resolve(process.cwd(), "..", "..")
await execa("npx", ["vsce", "package"], {
cwd: clineRoot,
stdio: "inherit",
})
// Find the generated VSIX file(s)
const files = fs.readdirSync(clineRoot)
const vsixFiles = files.filter((file) => file.endsWith(".vsix"))
if (vsixFiles.length > 0) {
// Get file stats to find the most recent one
const vsixFilesWithStats = vsixFiles.map((file) => {
const filePath = path.join(clineRoot, file)
return {
file,
path: filePath,
mtime: fs.statSync(filePath).mtime,
}
})
// Sort by modification time (most recent first)
vsixFilesWithStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
// Use the most recent VSIX
vsixPath = vsixFilesWithStats[0].path
console.log(`Using most recent VSIX: ${vsixPath} (modified ${vsixFilesWithStats[0].mtime.toISOString()})`)
// Log all found VSIX files for debugging
if (vsixFiles.length > 1) {
console.log(`Found ${vsixFiles.length} VSIX files:`)
vsixFilesWithStats.forEach((f) => {
console.log(` - ${f.file} (modified ${f.mtime.toISOString()})`)
})
}
} else {
console.warn("Could not find generated VSIX file")
}
} catch (error) {
console.warn("Failed to build test VSIX:", error)
}
}
// Create a temporary user data directory for this VS Code instance
const tempUserDataDir = path.join(os.tmpdir(), `vscode-cline-eval-${Date.now()}`)
fs.mkdirSync(tempUserDataDir, { recursive: true })
console.log(`Created temporary user data directory: ${tempUserDataDir}`)
// Create a temporary extensions directory to ensure no other extensions are loaded
const tempExtensionsDir = path.join(os.tmpdir(), `vscode-cline-eval-ext-${Date.now()}`)
fs.mkdirSync(tempExtensionsDir, { recursive: true })
console.log(`Created temporary extensions directory: ${tempExtensionsDir}`)
// Create evals.env file in the workspace to trigger test mode
console.log(`Creating evals.env file in workspace: ${workspacePath}`)
const evalsEnvPath = path.join(workspacePath, "evals.env")
fs.writeFileSync(
evalsEnvPath,
`# This file activates Cline test mode
# Created at: ${new Date().toISOString()}
#
# This file is automatically detected by the Cline extension
# and enables test mode for automated evaluations.
#
# Delete this file to deactivate test mode.
`,
)
// Create settings.json in the temporary user data directory to disable workspace trust
// and configure Cline to auto-open on startup
const settingsDir = path.join(tempUserDataDir, "User")
fs.mkdirSync(settingsDir, { recursive: true })
const settingsPath = path.join(settingsDir, "settings.json")
const settings = {
// Disable workspace trust
"security.workspace.trust.enabled": false,
"security.workspace.trust.startupPrompt": "never",
"security.workspace.trust.banner": "never",
"security.workspace.trust.emptyWindow": true,
// Configure startup behavior
"workbench.startupEditor": "none",
// Auto-open Cline on startup
"cline.autoOpenOnStartup": true,
// Show the activity bar and sidebar
"workbench.activityBar.visible": true,
"workbench.sideBar.visible": true,
"workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.visible": true,
"workbench.view.alwaysShowHeaderActions": true,
"workbench.editor.openSideBySideDirection": "right",
// Disable GitLens from opening automatically
"gitlens.views.repositories.autoReveal": false,
"gitlens.views.fileHistory.autoReveal": false,
"gitlens.views.lineHistory.autoReveal": false,
"gitlens.views.compare.autoReveal": false,
"gitlens.views.search.autoReveal": false,
"gitlens.showWelcomeOnInstall": false,
"gitlens.showWhatsNewAfterUpgrades": false,
// Disable other extensions that might compete for startup focus
"extensions.autoUpdate": false,
}
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
console.log(`Created settings.json to disable workspace trust and auto-open Cline`)
// Create keybindings.json to automatically open Cline on startup
const keybindingsPath = path.join(settingsDir, "keybindings.json")
const keybindings = [
{
key: "alt+c",
command: "workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
when: "viewContainer.workbench.view.extension.saoudrizwan.claude-dev-ActivityBar.enabled",
},
]
fs.writeFileSync(keybindingsPath, JSON.stringify(keybindings, null, 2))
console.log(`Created keybindings.json to help with Cline activation`)
// Build the command arguments with custom user data directory
const args = [
// Use a custom user data directory to isolate this instance
"--user-data-dir",
tempUserDataDir,
// Use a custom extensions directory to ensure only our extension is loaded
"--extensions-dir",
tempExtensionsDir,
// Disable workspace trust
"--disable-workspace-trust",
"-n",
workspacePath,
// Force the extension to be activated on startup
"--start-up-extension",
"saoudrizwan.claude-dev",
// Run a command on startup to open Cline
"--command",
"workbench.view.extension.saoudrizwan.claude-dev-ActivityBar",
// Additional flags to help with extension activation
"--disable-gpu=false",
"--max-memory=4096",
]
// Create a startup script to run commands after VS Code launches
const startupScriptPath = path.join(settingsDir, "startup.js")
const startupScript = `
// This script will be executed when VS Code starts
setTimeout(() => {
// Try to open Cline in the sidebar
require('vscode').commands.executeCommand('workbench.view.extension.saoudrizwan.claude-dev-ActivityBar');
}, 5000);
`
fs.writeFileSync(startupScriptPath, startupScript)
console.log(`Created startup script to activate Cline`)
// If a VSIX is provided, install it
if (vsixPath) {
if (!fs.existsSync(vsixPath)) {
throw new Error(`VSIX file does not exist: ${vsixPath}`)
}
args.unshift("--install-extension", vsixPath)
}
// Install required extensions
console.log("Installing required VSCode extensions...")
await installRequiredExtensions(tempExtensionsDir)
// Configure extension settings
console.log("Configuring extension settings...")
configureExtensionSettings(tempUserDataDir)
// Execute the command
try {
// We don't need to install extensions globally anymore since we're using a custom user data directory
// The VSIX will be installed in the isolated environment if provided in the args
// Launch VS Code
console.log("Launching VS Code...")
await execa("code", args, {
stdio: "inherit",
})
// Wait longer for VSCode to initialize and extension to load
console.log("Waiting for VS Code to initialize...")
await new Promise((resolve) => setTimeout(resolve, 30000))
// Create a JavaScript file that will be loaded as a VS Code extension
const extensionDir = path.join(tempExtensionsDir, "cline-activator")
fs.mkdirSync(extensionDir, { recursive: true })
// Create package.json for the extension
const packageJsonPath = path.join(extensionDir, "package.json")
const packageJson = {
name: "cline-activator",
displayName: "Cline Activator",
description: "Activates Cline and starts the test server",
version: "0.0.1",
engines: {
vscode: "^1.60.0",
},
main: "./extension.js",
activationEvents: ["*"],
contributes: {
commands: [
{
command: "cline-activator.activate",
title: "Activate Cline",
},
],
},
}
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
// Create extension.js
const extensionJsPath = path.join(extensionDir, "extension.js")
const extensionJs = `
const vscode = require('vscode');
/**
* @param {vscode.ExtensionContext} context
*/
function activate(context) {
console.log('Cline Activator is now active!');
// Register the command to activate Cline
let disposable = vscode.commands.registerCommand('cline-activator.activate', async function () {
try {
// Make sure the Cline extension is activated
const extension = vscode.extensions.getExtension('saoudrizwan.claude-dev');
if (!extension) {
console.error('Cline extension not found');
return;
}
if (!extension.isActive) {
console.log('Activating Cline extension...');
await extension.activate();
}
// Show the Cline sidebar
console.log('Opening Cline sidebar...');
await vscode.commands.executeCommand('workbench.view.extension.saoudrizwan.claude-dev-ActivityBar');
// Wait a moment for the sidebar to initialize
await new Promise(resolve => setTimeout(resolve, 2000));
// Create the test server if it doesn't exist
console.log('Creating test server...');
// Get the visible webview instance
const clineRootPath = '${path.resolve(process.cwd(), "..", "..")}';
const visibleWebview = require(path.join(clineRootPath, 'src', 'core', 'webview')).WebviewProvider.getVisibleInstance();
if (visibleWebview) {
require(path.join(clineRootPath, 'src', 'services', 'test', 'TestServer')).createTestServer(visibleWebview);
console.log('Test server created successfully');
} else {
console.error('No visible webview instance found');
}
} catch (error) {
console.error('Error activating Cline:', error);
}
});
context.subscriptions.push(disposable);
// Automatically run the command after a delay
setTimeout(() => {
vscode.commands.executeCommand('cline-activator.activate');
}, 5000);
}
function deactivate() {}
module.exports = {
activate,
deactivate
}
`
fs.writeFileSync(extensionJsPath, extensionJs)
console.log(`Created Cline Activator extension`)
// Try multiple approaches to activate the extension
let serverStarted = false
// Create an activation script to run in VS Code
const activationScriptPath = path.join(settingsDir, "activate-cline.js")
const activationScript = `
// This script will be executed to activate Cline and start the test server
const vscode = require('vscode');
// Execute the cline-activator.activate command
vscode.commands.executeCommand('cline-activator.activate');
`
fs.writeFileSync(activationScriptPath, activationScript)
console.log(`Created activation script to run in VS Code`)
// Execute the activation script
try {
console.log("Executing activation script to start Cline and test server...")
await execa(
"code",
[
"--user-data-dir",
tempUserDataDir,
"--extensions-dir",
tempExtensionsDir,
"--folder-uri",
`file://${workspacePath}`,
"--execute",
activationScriptPath,
],
{
stdio: "inherit",
},
)
// Wait for the test server to start
console.log("Waiting for test server to start...")
for (let i = 0; i < 30; i++) {
try {
// Try to connect to the test server
const response = await fetch("http://localhost:9876/task", {
method: "OPTIONS",
headers: {
"Content-Type": "application/json",
},
})
if (response.status === 204) {
console.log("Test server is running!")
serverStarted = true
break
}
} catch (error) {
// Server not started yet, wait and try again
await new Promise((resolve) => setTimeout(resolve, 1000))
}
}
} catch (error) {
console.warn("Failed to execute activation script:", error)
}
if (!serverStarted) {
console.warn("Test server did not start after multiple attempts")
console.log("You may need to manually open the Cline extension in VS Code")
}
// Store the resources for this workspace
const resources: VSCodeResources = {
tempUserDataDir,
tempExtensionsDir,
}
// Store in the global map
workspaceResources.set(workspacePath, resources)
// Return the resources
return resources
} catch (error: any) {
throw new Error(`Failed to spawn VSCode: ${error.message}`)
}
}
/**
* Clean up VS Code resources and shut down the test server
* @param workspacePath The workspace path to clean up resources for
*/
export async function cleanupVSCode(workspacePath: string): Promise<void> {
console.log(`Cleaning up VS Code resources for workspace: ${workspacePath}`)
// Get the resources for this workspace
const resources = workspaceResources.get(workspacePath)
if (!resources) {
console.log(`No resources found for workspace: ${workspacePath}`)
return
}
// Try to shut down the test server
try {
console.log("Shutting down test server...")
await fetch("http://localhost:9876/shutdown", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
}).catch(() => {
// Ignore errors, the server might already be down
})
} catch (error) {
console.warn(`Error shutting down test server: ${error}`)
}
// Try to gracefully close VS Code instead of killing it
try {
console.log("Attempting to gracefully close VS Code...")
// Create a settings file that will disable the crash reporter and the exit confirmation dialog
const settingsDir = path.join(resources.tempUserDataDir, "User")
const settingsPath = path.join(settingsDir, "settings.json")
// Read existing settings if they exist
let settings = {}
if (fs.existsSync(settingsPath)) {
try {
settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"))
} catch (error) {
console.warn(`Error reading settings file: ${error}`)
}
}
// Update settings to disable crash reporter and exit confirmation
settings = {
...settings,
"window.confirmBeforeClose": "never",
"telemetry.enableCrashReporter": false,
"window.restoreWindows": "none",
"window.newWindowDimensions": "default",
}
// Write updated settings
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
// On macOS, use AppleScript to quit VS Code gracefully
if (process.platform === "darwin") {
try {
// First try AppleScript to quit VS Code gracefully
await execa("osascript", ["-e", 'tell application "Visual Studio Code" to quit'])
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
} catch (appleScriptError) {
console.warn(`Error using AppleScript to quit VS Code: ${appleScriptError}`)
}
} else if (process.platform === "win32") {
// On Windows, try to use taskkill without /F first
try {
await execa("taskkill", ["/IM", "code.exe"])
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
} catch (taskkillError) {
console.warn(`Error using taskkill to quit VS Code: ${taskkillError}`)
}
} else {
// On Linux, try to use SIGTERM first
try {
// Find VS Code processes
const { stdout } = await execa("ps", ["aux"])
const lines = stdout.split("\n")
for (const line of lines) {
if (line.includes(resources.tempUserDataDir)) {
const parts = line.trim().split(/\s+/)
const pid = parseInt(parts[1])
if (pid && !isNaN(pid)) {
console.log(`Sending SIGTERM to VS Code process with PID: ${pid}`)
try {
// Use SIGTERM instead of SIGKILL for a graceful shutdown
process.kill(pid, "SIGTERM")
} catch (killError) {
console.warn(`Failed to terminate process ${pid}: ${killError}`)
}
}
}
}
// Wait a moment for VS Code to close
await new Promise((resolve) => setTimeout(resolve, 2000))
} catch (psError) {
console.warn(`Error listing processes: ${psError}`)
}
}
// If graceful methods failed, fall back to forceful termination as a last resort
// Check if VS Code is still running with the temp user data dir
let vsCodeStillRunning = false
if (process.platform !== "win32") {
try {
const { stdout } = await execa("ps", ["aux"])
vsCodeStillRunning = stdout.split("\n").some((line) => line.includes(resources.tempUserDataDir))
} catch (error) {
console.warn(`Error checking if VS Code is still running: ${error}`)
}
} else {
try {
const { stdout } = await execa("tasklist", ["/FI", `IMAGENAME eq code.exe`])
vsCodeStillRunning = stdout.includes("code.exe")
} catch (error) {
console.warn(`Error checking if VS Code is still running: ${error}`)
}
}
// If VS Code is still running, use forceful termination as a last resort
if (vsCodeStillRunning) {
console.log("Graceful shutdown failed, falling back to forceful termination...")
if (process.platform === "win32") {
try {
await execa("taskkill", ["/IM", "code.exe", "/F"])
} catch (error) {
console.warn(`Error forcefully terminating VS Code: ${error}`)
}
} else {
try {
const { stdout } = await execa("ps", ["aux"])
const lines = stdout.split("\n")
for (const line of lines) {
if (line.includes(resources.tempUserDataDir)) {
const parts = line.trim().split(/\s+/)
const pid = parseInt(parts[1])
if (pid && !isNaN(pid)) {
console.log(`Forcefully killing VS Code process with PID: ${pid}`)
try {
process.kill(pid, "SIGKILL")
} catch (killError) {
console.warn(`Failed to kill process ${pid}: ${killError}`)
}
}
}
}
} catch (error) {
console.warn(`Error forcefully terminating VS Code: ${error}`)
}
}
}
} catch (error) {
console.warn(`Error closing VS Code: ${error}`)
}
// Clean up temporary directories and evals.env file
try {
console.log(`Removing temporary user data directory: ${resources.tempUserDataDir}`)
fs.rmSync(resources.tempUserDataDir, { recursive: true, force: true })
} catch (error) {
console.warn(`Error removing temporary user data directory: ${error}`)
}
try {
console.log(`Removing temporary extensions directory: ${resources.tempExtensionsDir}`)
fs.rmSync(resources.tempExtensionsDir, { recursive: true, force: true })
} catch (error) {
console.warn(`Error removing temporary extensions directory: ${error}`)
}
// Remove the evals.env file
try {
const evalsEnvPath = path.join(workspacePath, "evals.env")
if (fs.existsSync(evalsEnvPath)) {
console.log(`Removing evals.env file: ${evalsEnvPath}`)
fs.unlinkSync(evalsEnvPath)
}
} catch (error) {
console.warn(`Error removing evals.env file: ${error}`)
}
// Remove from the global map
workspaceResources.delete(workspacePath)
console.log("Cleanup completed")
}
+1694 -59
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -20,7 +20,7 @@
"license": "MIT",
"dependencies": {
"axios": "^1.12.0",
"better-sqlite3": "^11.10.0",
"better-sqlite3": "^12.4.1",
"chalk": "5.6.2",
"dotenv": "^16.5.0",
"commander": "^9.4.1",
@@ -40,5 +40,8 @@
"@types/yargs": "^17.0.19",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"overrides": {
"tar-fs": "^3.1.1"
}
}
+1667 -1107
View File
File diff suppressed because it is too large Load Diff
+28 -10
View File
@@ -2,7 +2,7 @@
"name": "claude-dev",
"displayName": "Cline",
"description": "Autonomous coding agent right in your IDE, capable of creating/editing files, running commands, using the browser, and more with your permission every step of the way.",
"version": "3.33.1",
"version": "3.37.1",
"icon": "assets/icons/icon.png",
"engines": {
"vscode": "^1.84.0"
@@ -149,6 +149,12 @@
"category": "Cline",
"when": "cline.isDevMode"
},
{
"command": "cline.dev.expireMcpOAuthTokens",
"title": "Expire MCP OAuth Tokens (for testing)",
"category": "Cline",
"when": "cline.isDevMode"
},
{
"command": "cline.addToChat",
"title": "Add to Cline",
@@ -278,11 +284,11 @@
"commandPalette": [
{
"command": "cline.generateGitCommitMessage",
"when": "config.git.enabled && scmProvider == git && !cline.isGeneratingCommit"
"when": "config.git.enabled && !cline.isGeneratingCommit"
},
{
"command": "cline.abortGitCommitMessage",
"when": "config.git.enabled && scmProvider == git && cline.isGeneratingCommit"
"when": "config.git.enabled && cline.isGeneratingCommit"
}
]
},
@@ -300,16 +306,20 @@
"compile-cli-all-platforms": "scripts/build-cli-all-platforms.sh",
"compile-cli-man-page": "pandoc cli/man/cline.1.md -s -t man -o cli/man/cline.1",
"build:npm": "scripts/build-npm-package.sh",
"build:docker:dev": "node scripts/build-docker-dev.mjs",
"docker:shell": "node scripts/docker-shell.mjs",
"test:install": "bash scripts/test-install.sh",
"dev:cli:watch": "node scripts/dev-cli-watch.mjs",
"postcompile-standalone": "node scripts/package-standalone.mjs",
"postcompile-standalone-npm": "node scripts/package-standalone.mjs --target=npm",
"dev": "npm run protos && npm run watch",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.mjs --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "npm run check-types && npm run build:webview && npm run lint && node esbuild.mjs --production",
"protos": "node scripts/build-proto.mjs",
"protos-go": "node scripts/build-go-proto.mjs",
"protos-python": "node scripts/build-python-proto.mjs",
"cli-providers": "node scripts/cli-providers.mjs",
"download-ripgrep": "node scripts/download-ripgrep.mjs",
"postprotos": "biome format src/shared/proto src/core/controller src/hosts/ webview-ui/src/services src/generated --write --no-errors-on-unmatched",
@@ -319,7 +329,8 @@
"compile-tests": "node ./scripts/build-tests.js",
"watch-tests": "tsc -p . -w --outDir out",
"check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc -b --noEmit",
"lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && buf lint",
"lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && npm run lint:proto",
"lint:proto": "bash ./scripts/proto-lint.sh",
"format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
"format:fix": "biome check --changed --no-errors-on-unmatched --files-ignore-unknown=true --write",
"fix:all": "biome check --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
@@ -328,7 +339,7 @@
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
"test": "npm-run-all test:unit test:integration",
"test:integration": "vscode-test",
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha",
"test:unit": "cross-env TS_NODE_PROJECT=./tsconfig.unit-test.json mocha # Use `UPDATE_SNAPSHOTS=true npm run test:unit` to rebuild prompt snapshots",
"test:coverage": "vscode-test --coverage",
"test:sca-server": "npx tsx watch scripts/test-standalone-core-api-server.ts",
"test:tp-orchestrator": "npx tsx scripts/testing-platform-orchestrator.ts",
@@ -403,8 +414,8 @@
"dependencies": {
"@anthropic-ai/sdk": "^0.37.0",
"@anthropic-ai/vertex-sdk": "^0.6.4",
"@aws-sdk/client-bedrock-runtime": "^3.840.0",
"@aws-sdk/credential-providers": "^3.840.0",
"@aws-sdk/client-bedrock-runtime": "^3.922.0",
"@aws-sdk/credential-providers": "^3.922.0",
"@bufbuild/protobuf": "^2.2.5",
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
"@google-cloud/vertexai": "^1.9.3",
@@ -432,11 +443,12 @@
"@opentelemetry/sdk-trace-base": "^2.1.0",
"@opentelemetry/sdk-trace-node": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.37.0",
"@playwright/test": "^1.53.2",
"@playwright/test": "^1.55.1",
"@sap-ai-sdk/ai-api": "^1.17.0",
"@sap-ai-sdk/orchestration": "^1.17.0",
"@sentry/browser": "^9.12.0",
"@streamparser/json": "^0.0.22",
"@tailwindcss/vite": "^4.1.14",
"@types/uuid": "^10.0.0",
"@vscode/codicons": "^0.0.36",
"archiver": "^7.0.1",
@@ -456,14 +468,14 @@
"get-folder-size": "^5.0.0",
"globby": "^14.0.2",
"grpc-health-check": "^2.0.2",
"https-proxy-agent": "^7.0.6",
"iconv-lite": "^0.6.3",
"ignore": "^7.0.3",
"image-size": "^2.0.2",
"isbinaryfile": "^5.0.2",
"jschardet": "^3.1.4",
"jwt-decode": "^4.0.0",
"mammoth": "^1.8.0",
"mammoth": "^1.11.0",
"nanoid": "^5.1.6",
"nice-grpc": "^2.1.12",
"node-machine-id": "^1.1.12",
"ollama": "^0.5.13",
@@ -471,6 +483,7 @@
"open-graph-scraper": "^6.9.0",
"openai": "^4.83.0",
"os-name": "^6.0.0",
"p-mutex": "^1.0.0",
"p-timeout": "^6.1.4",
"p-wait-for": "^5.0.2",
"pdf-parse": "^1.1.1",
@@ -481,15 +494,20 @@
"serialize-error": "^11.0.3",
"simple-git": "^3.27.0",
"strip-ansi": "^7.1.2",
"tailwindcss": "^4.1.14",
"tree-sitter-wasms": "^0.1.11",
"ts-morph": "^25.0.1",
"turndown": "^7.2.0",
"ulid": "^2.4.0",
"undici": "^7.16.0",
"uuid": "^11.1.0",
"vscode-uri": "^3.1.0",
"web-tree-sitter": "^0.22.6",
"zod": "^3.24.2"
},
"overrides": {
"tar-fs": ">=3.1.1"
},
"c8": {
"reporter": [
"lcov",
+10 -8
View File
@@ -1,10 +1,12 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
// Service for account-related operations
service AccountService {
@@ -12,20 +14,18 @@ service AccountService {
// Generates a secure nonce for state validation, stores it in secrets,
// and opens the authentication URL in the external browser.
rpc accountLoginClicked(EmptyRequest) returns (String);
// Handles the user clicking the logout button in the UI.
// Clears API keys and user state.
rpc accountLogoutClicked(EmptyRequest) returns (Empty);
// Subscribe to auth status update events (when authentication state changes)
rpc subscribeToAuthStatusUpdate(EmptyRequest)
returns (stream AuthState);
rpc subscribeToAuthStatusUpdate(EmptyRequest) returns (stream AuthState);
// Handles authentication state changes from the Firebase context.
// Updates the user info in global state and returns the updated value.
rpc authStateChanged(AuthStateChangedRequest)
returns (AuthState);
rpc authStateChanged(AuthStateChangedRequest) returns (AuthState);
// Fetches all user credits data
// (balance, usage transactions, payment transactions)
rpc getUserCredits(EmptyRequest) returns (UserCreditsData);
@@ -40,6 +40,8 @@ service AccountService {
rpc openrouterAuthClicked(EmptyRequest) returns (Empty);
rpc requestyAuthClicked(StringRequest) returns (Empty);
// Returns a link the webview can use to redirect back to the user's IDE.
rpc getRedirectUrl(EmptyRequest) returns (String);
}
+3 -1
View File
@@ -1,10 +1,12 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
service BrowserService {
rpc getBrowserConnectionInfo(EmptyRequest) returns (BrowserConnectionInfo);
+5 -3
View File
@@ -1,11 +1,13 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "google/protobuf/timestamp.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
service CheckpointsService {
rpc checkpointDiff(Int64Request) returns (Empty);
@@ -31,13 +33,13 @@ message CheckpointEvent {
CHECKPOINT_COMMIT = 1;
CHECKPOINT_RESTORE = 2;
}
OperationType operation = 1;
string cwd_hash = 2;
bool is_active = 3;
google.protobuf.Timestamp timestamp = 4;
optional string task_id = 5;
optional string commit_hash = 6;
optional string commit_hash = 6;
}
message PathHashMap {
+7 -5
View File
@@ -1,12 +1,14 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
// Service for running IDE commands, for example context menu actions,
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
// Service for running IDE commands, for example context menu actions,
// commands, etc.
// In contrast to the rest of the ProtoBus services, these are
// intended to be called by the IDE directly instead of through the webview,
+5 -7
View File
@@ -1,18 +1,16 @@
syntax = "proto3";
package cline;
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
message Metadata {
}
message Metadata {}
message EmptyRequest {
}
message EmptyRequest {}
message Empty {
}
message Empty {}
message StringRequest {
string value = 2;
+3 -1
View File
@@ -1,10 +1,12 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
service DictationService {
rpc startRecording(EmptyRequest) returns (RecordingResult);
+58 -37
View File
@@ -1,10 +1,12 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
// Service for file-related operations
service FileService {
@@ -13,10 +15,10 @@ service FileService {
// Opens a file in the editor
rpc openFile(StringRequest) returns (Empty);
// Opens an image in the system viewer
rpc openImage(StringRequest) returns (Empty);
// Opens a mention (file, path, git commit, problem, terminal, or URL)
rpc openMention(StringRequest) returns (Empty);
@@ -25,34 +27,37 @@ service FileService {
// Creates a rule file from either global or workspace rules directory
rpc createRuleFile(RuleFileRequest) returns (RuleFile);
// Search git commits in the workspace
rpc searchCommits(StringRequest) returns (GitCommits);
// Select images and other files from the file system and returns as data URLs & paths respectively
rpc selectFiles(BooleanRequest) returns (StringArrays);
// Convert URIs to workspace-relative paths
rpc getRelativePaths(RelativePathsRequest) returns (RelativePaths);
// Search for files in the workspace with fuzzy matching
rpc searchFiles(FileSearchRequest) returns (FileSearchResults);
// Toggle a Cline rule (enable or disable)
rpc toggleClineRule(ToggleClineRuleRequest) returns (ToggleClineRules);
// Toggle a Cursor rule (enable or disable)
rpc toggleCursorRule(ToggleCursorRuleRequest) returns (ClineRulesToggles);
// Toggle a Windsurf rule (enable or disable)
rpc toggleWindsurfRule(ToggleWindsurfRuleRequest) returns (ClineRulesToggles);
// Toggle an Agents rule (enable or disable)
rpc toggleAgentsRule(ToggleAgentsRuleRequest) returns (ClineRulesToggles);
// Refreshes all rule toggles (Cline, External, and Workflows)
rpc refreshRules(EmptyRequest) returns (RefreshedRules);
// Opens a task's conversation history file on disk
rpc openDiskConversationHistory(StringRequest) returns (Empty);
// Toggles a workflow on or off
rpc toggleWorkflow(ToggleWorkflowRequest) returns (ClineRulesToggles);
@@ -61,7 +66,7 @@ service FileService {
// Open a file in editor by a relative path
rpc openFileRelativePath(StringRequest) returns (Empty);
// Opens or creates a focus chain checklist markdown file for editing
rpc openFocusChainFile(StringRequest) returns (Empty);
}
@@ -72,15 +77,23 @@ message RefreshedRules {
ClineRulesToggles local_cline_rules_toggles = 2;
ClineRulesToggles local_cursor_rules_toggles = 3;
ClineRulesToggles local_windsurf_rules_toggles = 4;
ClineRulesToggles local_workflow_toggles = 5;
ClineRulesToggles global_workflow_toggles = 6;
ClineRulesToggles local_agents_rules_toggles = 5;
ClineRulesToggles local_workflow_toggles = 6;
ClineRulesToggles global_workflow_toggles = 7;
}
// Request to toggle a Windsurf rule
message ToggleWindsurfRuleRequest {
Metadata metadata = 1;
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
}
// Request to toggle an Agents rule
message ToggleAgentsRuleRequest {
Metadata metadata = 1;
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
}
// Request to convert a list of URIs to relative paths
@@ -103,25 +116,25 @@ enum FileSearchType {
// Request for file search operations
message FileSearchRequest {
Metadata metadata = 1;
string query = 2; // Search query string
optional string mentions_request_id = 3; // Optional request ID for tracking requests
optional int32 limit = 4; // Optional limit for results (default: 20)
optional FileSearchType selected_type = 5; // Optional selected type filter
optional string workspace_hint = 6; // Optional workspace name to search in
string query = 2; // Search query string
optional string mentions_request_id = 3; // Optional request ID for tracking requests
optional int32 limit = 4; // Optional limit for results (default: 20)
optional FileSearchType selected_type = 5; // Optional selected type filter
optional string workspace_hint = 6; // Optional workspace name to search in
}
// Result for file search operations
message FileSearchResults {
repeated FileInfo results = 1; // Array of file/folder results
optional string mentions_request_id = 2; // Echo of the request ID for tracking
repeated FileInfo results = 1; // Array of file/folder results
optional string mentions_request_id = 2; // Echo of the request ID for tracking
}
// File information structure for search results
message FileInfo {
string path = 1; // Relative path from workspace root
string type = 2; // "file" or "folder"
optional string label = 3; // Display name (usually basename)
optional string workspace_name = 4; // Workspace this result came from
string path = 1; // Relative path from workspace root
string type = 2; // "file" or "folder"
optional string label = 3; // Display name (usually basename)
optional string workspace_name = 4; // Workspace this result came from
}
// Response for searchCommits
@@ -141,25 +154,32 @@ message GitCommit {
// Unified request for all rule file operations
message RuleFileRequest {
Metadata metadata = 1;
bool is_global = 2; // Common field for all operations
bool is_global = 2; // Common field for all operations
optional string rule_path = 3; // Path field for deleteRuleFile (optional)
optional string filename = 4; // Filename field for createRuleFile (optional)
optional string type = 5; // Type of the file to create (optional)
optional string filename = 4; // Filename field for createRuleFile (optional)
optional string type = 5; // Type of the file to create (optional)
}
// Result for rule file operations with meaningful data only
message RuleFile {
string file_path = 1; // Path to the rule file
string display_name = 2; // Filename for display purposes
bool already_exists = 3; // For createRuleFile, indicates if file already existed
string file_path = 1; // Path to the rule file
string display_name = 2; // Filename for display purposes
bool already_exists = 3; // For createRuleFile, indicates if file already existed
}
// Enum for rule scope (local, global, or remote)
enum RuleScope {
LOCAL = 0;
GLOBAL = 1;
REMOTE = 2;
}
// Request to toggle a Cline rule
message ToggleClineRuleRequest {
Metadata metadata = 1;
bool is_global = 2; // Whether this is a global rule or workspace rule
string rule_path = 3; // Path to the rule file
bool enabled = 4; // Whether to enable or disable the rule
RuleScope scope = 2; // Scope of the rule (local, global, or remote)
string rule_path = 3; // Path to the rule file
bool enabled = 4; // Whether to enable or disable the rule
}
// Maps from filepath to enabled/disabled status, matching app's ClineRulesToggles type
@@ -171,13 +191,14 @@ message ClineRulesToggles {
message ToggleClineRules {
ClineRulesToggles global_cline_rules_toggles = 1;
ClineRulesToggles local_cline_rules_toggles = 2;
ClineRulesToggles remote_rules_toggles = 3;
}
// Request to toggle a Cursor rule
message ToggleCursorRuleRequest {
Metadata metadata = 1;
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
string rule_path = 2; // Path to the rule file
bool enabled = 3; // Whether to enable or disable the rule
}
// Request to toggle a workflow on or off
@@ -185,5 +206,5 @@ message ToggleWorkflowRequest {
Metadata metadata = 1;
string workflow_path = 2;
bool enabled = 3;
bool is_global = 4;
RuleScope scope = 4; // Scope of the workflow (local, global, or remote)
}
+3 -2
View File
@@ -1,9 +1,10 @@
syntax = "proto3";
package cline;
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
// Input message for all hooks
message HookInput {
@@ -28,7 +29,7 @@ message HookInput {
// Output message for all hooks
message HookOutput {
string context_modification = 1;
bool should_continue = 2;
bool cancel = 2;
string error_message = 3;
}
+10 -4
View File
@@ -1,10 +1,12 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
service McpService {
rpc toggleMcpServer(ToggleMcpServerRequest) returns (McpServers);
@@ -16,11 +18,12 @@ service McpService {
rpc toggleToolAutoApprove(ToggleToolAutoApproveRequest) returns (McpServers);
rpc refreshMcpMarketplace(EmptyRequest) returns (McpMarketplaceCatalog);
rpc openMcpSettings(EmptyRequest) returns (Empty);
rpc authenticateMcpServer(StringRequest) returns (Empty);
// Subscribe to MCP marketplace catalog updates
rpc subscribeToMcpMarketplaceCatalog(EmptyRequest) returns (stream McpMarketplaceCatalog);
rpc getLatestMcpServers(Empty) returns (McpServers);
// Subscribe to MCP server updates
rpc subscribeToMcpServers(EmptyRequest) returns (stream McpServers);
}
@@ -41,6 +44,7 @@ message AddRemoteMcpServerRequest {
Metadata metadata = 1;
string server_name = 2;
string server_url = 3;
optional string transport_type = 4;
}
message ToggleToolAutoApproveRequest {
@@ -72,7 +76,7 @@ message McpResourceTemplate {
}
enum McpServerStatus {
// Protobuf enums (in proto3) must have a zero value defined, which serves as the default if the field isn't explicitly set.
// Protobuf enums (in proto3) must have a zero value defined, which serves as the default if the field isn't explicitly set.
// To align with the required nature of the TypeScript type and avoid an unnecessary UNSPECIFIED state, we map one of the existing statuses to this zero value.
MCP_SERVER_STATUS_DISCONNECTED = 0; // default
MCP_SERVER_STATUS_CONNECTED = 1;
@@ -89,6 +93,8 @@ message McpServer {
repeated McpResourceTemplate resource_templates = 7;
optional bool disabled = 8;
optional int32 timeout = 9;
optional bool oauth_required = 10;
optional string oauth_auth_status = 11;
}
message McpServers {
+232 -20
View File
@@ -1,11 +1,13 @@
syntax = "proto3";
package cline;
import "cline/common.proto";
import "google/protobuf/field_mask.proto";
option go_package = "github.com/cline/grpc-go/cline";
option java_package = "bot.cline.proto";
option java_multiple_files = true;
option java_package = "bot.cline.proto";
// Service for model-related operations
service ModelsService {
@@ -16,29 +18,33 @@ service ModelsService {
// Fetches available models from VS Code LM API
rpc getVsCodeLmModels(EmptyRequest) returns (VsCodeLmModelsArray);
// Refreshes and returns OpenRouter models
rpc refreshOpenRouterModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
rpc refreshOpenRouterModelsRpc(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns Hugging Face models
rpc refreshHuggingFaceModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns OpenAI models
rpc refreshOpenAiModels(OpenAiModelsRequest) returns (StringArray);
// Refreshes and returns Vercel AI Gateway models
rpc refreshVercelAiGatewayModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns Requesty models
rpc refreshRequestyModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns Hicap models
rpc refreshHicapModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Subscribe to OpenRouter models updates
rpc subscribeToOpenRouterModels(EmptyRequest) returns (stream OpenRouterCompatibleModelInfo);
// Updates API configuration
// Updates API configuration (legacy - uses combined configuration)
rpc updateApiConfigurationProto(UpdateApiConfigurationRequest) returns (Empty);
// Updates API configuration (new - uses separate options and secrets)
rpc updateApiConfiguration(UpdateApiConfigurationRequestNew) returns (Empty);
// Updates API configuration with partial values (only updates fields that are explicitly set)
rpc updateApiConfigurationPartial(UpdateApiConfigurationPartialRequest) returns (Empty);
// Refreshes and returns Groq models
rpc refreshGroqModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
rpc refreshGroqModelsRpc(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Refreshes and returns Baseten models
rpc refreshBasetenModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
rpc refreshBasetenModelsRpc(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
// Fetches available models from SAP AI Core
rpc getSapAiCoreModels(SapAiCoreModelsRequest) returns (SapAiCoreModelsResponse);
// Fetches available models from OCA
rpc refreshOcaModels(StringRequest) returns (OcaCompatibleModelInfo);
// Fetches available models from AIhubmix
rpc getAihubmixModels(EmptyRequest) returns (OpenRouterCompatibleModelInfo);
}
// List of VS Code LM models
@@ -56,15 +62,15 @@ message LanguageModelChatSelector {
// Price tier for tiered pricing models
message PriceTier {
int64 token_limit = 1; // Upper limit (inclusive) of input tokens for this price
double price = 2; // Price per million tokens for this tier
int64 token_limit = 1; // Upper limit (inclusive) of input tokens for this price
double price = 2; // Price per million tokens for this tier
}
// Thinking configuration for models that support thinking/reasoning
message ThinkingConfig {
optional int64 max_budget = 1; // Max allowed thinking budget tokens
optional double output_price = 2; // Output price per million tokens when budget > 0
repeated PriceTier output_price_tiers = 3; // Optional: Tiered output price when budget > 0
optional int64 max_budget = 1; // Max allowed thinking budget tokens
optional double output_price = 2; // Output price per million tokens when budget > 0
repeated PriceTier output_price_tiers = 3; // Optional: Tiered output price when budget > 0
}
// Model tier for tiered pricing structures
@@ -90,6 +96,7 @@ message OpenRouterModelInfo {
optional ThinkingConfig thinking_config = 10;
optional bool supports_global_endpoint = 11;
repeated ModelTier tiers = 12;
optional string name = 13;
}
// Shared response message for model information
@@ -120,35 +127,219 @@ message SapAiCoreModelDeployment {
string deployment_id = 2;
}
// Response for SAP AI Core models with orchestration availability
message SapAiCoreModelsResponse {
repeated SapAiCoreModelDeployment deployments = 1;
bool orchestration_available = 2;
}
// Request for updating API configuration
// API secrets (credentials, API keys)
message ModelsApiSecrets {
optional string api_key = 1;
optional string cline_api_key = 2;
optional string lite_llm_api_key = 3;
optional string open_router_api_key = 4;
optional string aws_access_key = 5;
optional string aws_secret_key = 6;
optional string aws_session_token = 7;
optional string aws_bedrock_api_key = 8;
optional string open_ai_api_key = 9;
optional string ollama_api_key = 10;
optional string gemini_api_key = 11;
optional string open_ai_native_api_key = 12;
optional string deep_seek_api_key = 13;
optional string requesty_api_key = 14;
optional string together_api_key = 15;
optional string fireworks_api_key = 16;
optional string qwen_api_key = 17;
optional string doubao_api_key = 18;
optional string mistral_api_key = 19;
optional string nebius_api_key = 20;
optional string asksage_api_key = 21;
optional string xai_api_key = 22;
optional string sambanova_api_key = 23;
optional string cerebras_api_key = 24;
optional string sap_ai_core_client_id = 25;
optional string sap_ai_core_client_secret = 26;
optional string moonshot_api_key = 27;
optional string cline_account_id = 28;
optional string groq_api_key = 29;
optional string hugging_face_api_key = 30;
optional string huawei_cloud_maas_api_key = 31;
optional string baseten_api_key = 32;
optional string zai_api_key = 33;
optional string vercel_ai_gateway_api_key = 34;
optional string dify_api_key = 35;
optional string oca_api_key = 36;
optional string oca_refresh_token = 37;
optional string minimax_api_key = 38;
optional string aihubmix_api_key = 39;
}
// API configuration options (non-secret settings)
message ModelsApiOptions {
// Global configuration fields (not mode-specific)
optional string ulid = 1;
optional string lite_llm_base_url = 2;
optional bool lite_llm_use_prompt_cache = 3;
map<string, string> open_ai_headers = 4;
optional string anthropic_base_url = 5;
optional string open_router_provider_sorting = 6;
optional string aws_region = 7;
optional bool aws_use_cross_region_inference = 8;
optional bool aws_bedrock_use_prompt_cache = 9;
optional bool aws_use_profile = 10;
optional string aws_profile = 11;
optional string aws_bedrock_endpoint = 12;
optional string claude_code_path = 13;
optional string vertex_project_id = 14;
optional string vertex_region = 15;
optional string open_ai_base_url = 16;
optional string ollama_base_url = 17;
optional string ollama_api_options_ctx_num = 18;
optional string lm_studio_base_url = 19;
optional string gemini_base_url = 20;
optional string requesty_base_url = 21;
optional int64 fireworks_model_max_completion_tokens = 22;
optional int64 fireworks_model_max_tokens = 23;
optional string azure_api_version = 24;
optional string qwen_api_line = 25;
optional string asksage_api_url = 26;
optional int64 request_timeout_ms = 27;
optional string sap_ai_resource_group = 28;
optional string sap_ai_core_token_url = 29;
optional string sap_ai_core_base_url = 30;
optional bool sap_ai_core_use_orchestration_mode = 31;
optional string moonshot_api_line = 32;
optional string aws_authentication = 33;
optional string zai_api_line = 34;
optional string lm_studio_max_tokens = 35;
optional string qwen_code_oauth_path = 36;
optional string dify_base_url = 37;
optional string oca_base_url = 38;
optional string oca_mode = 39;
optional bool aws_use_global_inference = 40;
optional string minimax_api_line = 41;
optional string aihubmix_base_url = 42;
optional string aihubmix_app_code = 43;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
optional string plan_mode_api_model_id = 101;
optional int64 plan_mode_thinking_budget_tokens = 102;
optional string plan_mode_reasoning_effort = 103;
optional LanguageModelChatSelector plan_mode_vs_code_lm_model_selector = 104;
optional bool plan_mode_aws_bedrock_custom_selected = 105;
optional string plan_mode_aws_bedrock_custom_model_base_id = 106;
optional string plan_mode_open_router_model_id = 107;
optional OpenRouterModelInfo plan_mode_open_router_model_info = 108;
optional string plan_mode_open_ai_model_id = 109;
optional OpenAiCompatibleModelInfo plan_mode_open_ai_model_info = 110;
optional string plan_mode_ollama_model_id = 111;
optional string plan_mode_lm_studio_model_id = 112;
optional string plan_mode_lite_llm_model_id = 113;
optional LiteLLMModelInfo plan_mode_lite_llm_model_info = 114;
optional string plan_mode_requesty_model_id = 115;
optional OpenRouterModelInfo plan_mode_requesty_model_info = 116;
optional string plan_mode_together_model_id = 117;
optional string plan_mode_fireworks_model_id = 118;
optional string plan_mode_sap_ai_core_model_id = 119;
optional string plan_mode_sap_ai_core_deployment_id = 120;
optional string plan_mode_groq_model_id = 121;
optional OpenRouterModelInfo plan_mode_groq_model_info = 122;
optional string plan_mode_hugging_face_model_id = 123;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 124;
optional string plan_mode_huawei_cloud_maas_model_id = 125;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 126;
optional string plan_mode_baseten_model_id = 127;
optional OpenRouterModelInfo plan_mode_baseten_model_info = 128;
optional string plan_mode_vercel_ai_gateway_model_id = 129;
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 130;
optional string plan_mode_oca_model_id = 131;
optional OcaModelInfo plan_mode_oca_model_info = 132;
optional string plan_mode_aihubmix_model_id = 133;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 134;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
optional string act_mode_api_model_id = 201;
optional int64 act_mode_thinking_budget_tokens = 202;
optional string act_mode_reasoning_effort = 203;
optional LanguageModelChatSelector act_mode_vs_code_lm_model_selector = 204;
optional bool act_mode_aws_bedrock_custom_selected = 205;
optional string act_mode_aws_bedrock_custom_model_base_id = 206;
optional string act_mode_open_router_model_id = 207;
optional OpenRouterModelInfo act_mode_open_router_model_info = 208;
optional string act_mode_open_ai_model_id = 209;
optional OpenAiCompatibleModelInfo act_mode_open_ai_model_info = 210;
optional string act_mode_ollama_model_id = 211;
optional string act_mode_lm_studio_model_id = 212;
optional string act_mode_lite_llm_model_id = 213;
optional LiteLLMModelInfo act_mode_lite_llm_model_info = 214;
optional string act_mode_requesty_model_id = 215;
optional OpenRouterModelInfo act_mode_requesty_model_info = 216;
optional string act_mode_together_model_id = 217;
optional string act_mode_fireworks_model_id = 218;
optional string act_mode_sap_ai_core_model_id = 219;
optional string act_mode_sap_ai_core_deployment_id = 220;
optional string act_mode_groq_model_id = 221;
optional OpenRouterModelInfo act_mode_groq_model_info = 222;
optional string act_mode_hugging_face_model_id = 223;
optional OpenRouterModelInfo act_mode_hugging_face_model_info = 224;
optional string act_mode_huawei_cloud_maas_model_id = 225;
optional OpenRouterModelInfo act_mode_huawei_cloud_maas_model_info = 226;
optional string act_mode_baseten_model_id = 227;
optional OpenRouterModelInfo act_mode_baseten_model_info = 228;
optional string act_mode_vercel_ai_gateway_model_id = 229;
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 230;
optional string act_mode_oca_model_id = 231;
optional OcaModelInfo act_mode_oca_model_info = 232;
optional string act_mode_aihubmix_model_id = 233;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 234;
}
// Request for updating API configuration (legacy - uses combined configuration)
message UpdateApiConfigurationRequest {
Metadata metadata = 1;
ModelsApiConfiguration api_configuration = 2;
}
// Combined API configuration containing both options and secrets
message ApiConfiguration {
ModelsApiOptions options = 1;
ModelsApiSecrets secrets = 2;
}
// Request for updating API configuration (new - uses separate options and secrets)
message UpdateApiConfigurationRequestNew {
Metadata metadata = 1;
ApiConfiguration updates = 2;
// Required field mask specifying which fields to update.
// Field paths use dot notation with camelCase field names:
// - "options.ulid" (for options fields)
// - "options.openAiHeaders" (for options fields)
// - "secrets.apiKey" (for secrets fields)
// - "secrets.openRouterApiKey" (for secrets fields)
repeated string update_mask = 3;
}
// Request for partially updating API configuration using FieldMask
// Only fields specified in update_mask will be updated from api_configuration
message UpdateApiConfigurationPartialRequest {
Metadata metadata = 1;
// The API configuration with values to update.
// Only fields listed in update_mask will be applied from this configuration.
ModelsApiConfiguration api_configuration = 2;
// Mask specifying which top-level fields from api_configuration to update.
// Field names should use camelCase (e.g., "apiKey", "planModeApiProvider").
// If a field is in the mask but not set in api_configuration, it will be cleared (set to undefined).
google.protobuf.FieldMask update_mask = 3;
}
// Model info for OCA (OpenAI-compatible) models exposed by the OCA provider
// Model info for OCA (OpenAI-compatible) models exposed by the OCA provider
message OcaModelInfo {
// Maximum completion tokens per request supported by this model
optional int64 max_tokens = 1;
@@ -182,7 +373,7 @@ message OcaModelInfo {
string model_name = 17;
}
// Aggregated OCA model catalog keyed by model identifier
// Aggregated OCA model catalog keyed by model identifier
message OcaCompatibleModelInfo {
// key: canonical model id as reported by OCA (e.g., "openai/gpt-4o-mini")
// value: OcaModelInfo describing that model
@@ -228,6 +419,10 @@ enum ApiProvider {
QWEN_CODE = 33;
DIFY = 34;
OCA = 35;
MINIMAX = 36;
HICAP = 37;
AIHUBMIX = 38;
NOUSRESEARCH = 39;
}
// Model info for OpenAI-compatible models
@@ -345,6 +540,14 @@ message ModelsApiConfiguration {
optional string oca_refresh_token = 75;
optional string oca_mode = 76;
optional bool aws_use_global_inference = 77;
optional string minimax_api_key = 78;
optional string minimax_api_line = 79;
optional string hicap_model_id = 80;
optional string hicap_api_key = 81;
optional string aihubmix_api_key = 82;
optional string aihubmix_base_url = 83;
optional string aihubmix_app_code = 84;
optional string nous_research_api_key = 85;
// Plan mode configurations
optional ApiProvider plan_mode_api_provider = 100;
@@ -373,14 +576,18 @@ message ModelsApiConfiguration {
optional string plan_mode_hugging_face_model_id = 123;
optional OpenRouterModelInfo plan_mode_hugging_face_model_info = 124;
optional string plan_mode_huawei_cloud_maas_model_id = 125;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 126;
optional OpenRouterModelInfo plan_mode_huawei_cloud_maas_model_info = 126;
optional string plan_mode_baseten_model_id = 127;
optional OpenRouterModelInfo plan_mode_baseten_model_info = 128;
optional string plan_mode_vercel_ai_gateway_model_id = 129;
optional OpenRouterModelInfo plan_mode_vercel_ai_gateway_model_info = 130;
optional string plan_mode_oca_model_id = 131;
optional OcaModelInfo plan_mode_oca_model_info = 132;
optional string plan_mode_hicap_model_id = 133;
optional OpenRouterModelInfo plan_mode_hicap_model_info = 134;
optional string plan_mode_aihubmix_model_id = 135;
optional OpenAiCompatibleModelInfo plan_mode_aihubmix_model_info = 136;
optional string plan_mode_nous_research_model_id = 137;
// Act mode configurations
optional ApiProvider act_mode_api_provider = 200;
@@ -416,4 +623,9 @@ message ModelsApiConfiguration {
optional OpenRouterModelInfo act_mode_vercel_ai_gateway_model_info = 230;
optional string act_mode_oca_model_id = 231;
optional OcaModelInfo act_mode_oca_model_info = 232;
optional string act_mode_hicap_model_id = 233;
optional OpenRouterModelInfo act_mode_hicap_model_info = 234;
optional string act_mode_aihubmix_model_id = 235;
optional OpenAiCompatibleModelInfo act_mode_aihubmix_model_info = 236;
optional string act_mode_nous_research_model_id = 237;
}

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